diff options
author | phk <phk@FreeBSD.org> | 2004-09-19 21:52:15 +0000 |
---|---|---|
committer | phk <phk@FreeBSD.org> | 2004-09-19 21:52:15 +0000 |
commit | 591680661ddfbef9bd12a8537684ab99a9860753 (patch) | |
tree | 46754bd395d4d80cc4dc69207014a79548bb507d | |
parent | 517c86564e65d04c23914a65676c6317eab6d343 (diff) | |
download | FreeBSD-src-591680661ddfbef9bd12a8537684ab99a9860753.zip FreeBSD-src-591680661ddfbef9bd12a8537684ab99a9860753.tar.gz |
Commit the new version of the adlink driver which can do non-cyclic
capture. Now we just need somebody to write a gnu-radio frontend :-)
-rw-r--r-- | sys/dev/adlink/adlink.c | 555 |
1 files changed, 250 insertions, 305 deletions
diff --git a/sys/dev/adlink/adlink.c b/sys/dev/adlink/adlink.c index b32ef3d..0644f29 100644 --- a/sys/dev/adlink/adlink.c +++ b/sys/dev/adlink/adlink.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003 Poul-Henning Kamp + * Copyright (c) 2003-2004 Poul-Henning Kamp * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,12 +25,26 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. + * + * This is a device driver or the Adlink 9812 and 9810 ADC cards, mainly + * intended to support Software Defined Radio reception of timesignals + * in the VLF band. See http://phk.freebsd.dk/loran-c + * + * The driver is configured with ioctls which define a ringbuffer with + * a given number of chunks in it. The a control structure and the + * ringbuffer can then be mmap(2)'ed into userland and the application + * can operate on the data directly. + * + * Tested with 10MHz external clock, divisor of 2 (ie: 5MHz sampling), + * One channel active (ie: 2 bytes per sample = 10MB/sec) on a 660MHz + * Celeron PC. + * */ +#ifdef _KERNEL #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#ifdef _KERNEL #include <sys/param.h> #include <sys/systm.h> #include <sys/malloc.h> @@ -52,50 +66,38 @@ __FBSDID("$FreeBSD$"); #include <sys/ioccom.h> -struct wave { - int index; - int period; - int offset; - int length; - int avg; - off_t mapvir; - int flags; - - int npages; - void **virtual; +#define ADLINK_SETDIVISOR _IOWR('A', 255, u_int) /* divisor */ +#define ADLINK_SETCHUNKSIZE _IOWR('A', 254, u_int) /* bytes */ +#define ADLINK_SETRINGSIZE _IOWR('A', 253, u_int) /* bytes */ +#define ADLINK_START _IOWR('A', 252, u_int) /* dummy */ +#define ADLINK_STOP _IOWR('A', 251, u_int) /* dummy */ +#define ADLINK_RESET _IOWR('A', 250, u_int) /* dummy */ + +struct page0 { + u_int version; + int state; +# define STATE_RESET -1 +# define STATE_RUN 0 + u_int divisor; /* int */ + u_int chunksize; /* bytes */ + u_int ringsize; /* chunks */ + u_int o_ringgen; /* + * offset of ring generation + * array + */ + u_int o_ring; /* offset of ring */ }; -#define ADLINK_SETWAVE _IOWR('A', 232, struct wave) -#define ADLINK_GETWAVE _IOWR('A', 233, struct wave) +#define PAGE0VERSION 20031021 #ifdef _KERNEL -#define INTPERPAGE (PAGE_SIZE / sizeof(int)) -#define I16PERPAGE (PAGE_SIZE / sizeof(int16_t)) - -/* - * Sample rate - */ -#define SPS 1250000 - -/* - * We sample one channel (= 16 bits) at 1.25 msps giving 2.5Mbyte/sec, - * 100 pages will give us about 1/6 second buffering. - */ -#define NRING 100 - -/* - * How many waves are we willing to entertain - */ -#define NWAVE 25 - -struct info { - int nring; - off_t o_ring; - - int ngri; - int ppgri; - off_t o_gri; +struct pgstat { + u_int *genp; + u_int gen; + vm_paddr_t phys; + void *virt; + struct pgstat *next; }; struct softc { @@ -104,306 +106,240 @@ struct softc { struct resource *r0, *r1, *ri; bus_space_tag_t t0, t1; bus_space_handle_t h0, h1; - struct cdev *dev; + struct cdev *dev; off_t mapvir; + int error; + struct page0 *p0; + u_int nchunks; + struct pgstat *chunks; + struct pgstat *next; +}; - struct proc *procp; +static d_ioctl_t adlink_ioctl; +static d_mmap_t adlink_mmap; +static void adlink_intr(void *arg); - struct info *info; +static struct cdevsw adlink_cdevsw = { + .d_version = D_VERSION, + .d_ioctl = adlink_ioctl, + .d_mmap = adlink_mmap, + .d_name = "adlink", +}; - struct wave *wave[NWAVE]; +static void +adlink_intr(void *arg) +{ + struct softc *sc; + struct pgstat *pg; + uint32_t u; - int idx; - void *ring[NRING]; - vm_paddr_t pring[NRING]; - int stat[NRING]; + sc = arg; + u = bus_space_read_4(sc->t0, sc->h0, 0x38); + if (!(u & 0x00800000)) + return; + bus_space_write_4(sc->t0, sc->h0, 0x38, u | 0x003f4000); - uint64_t cnt; + pg = sc->next; + *(pg->genp) = ++pg->gen; - u_char flags[I16PERPAGE]; -}; + u = bus_space_read_4(sc->t1, sc->h1, 0x18); + if (u & 1) + sc->p0->state = EIO; -static void -adlink_wave(struct softc *sc, struct wave *wp, int16_t *sp) -{ - int f, i, k, m, *ip; - - f = 0; - for (i = 0; i < I16PERPAGE; ) { - k = (sc->cnt - wp->offset + i) % wp->period; - if (k >= wp->length) { - i += wp->period - k; - sp += wp->period - k; - continue; - } - m = k % INTPERPAGE; - ip = (int *)(wp->virtual[k / INTPERPAGE]) + m; - while (m < INTPERPAGE && i < I16PERPAGE && k < wp->length) { - if (sc->flags[i] >= wp->index) - *ip += (*sp * 8 - *ip) >> wp->avg; - if (wp->flags & 1) - sc->flags[i] = wp->index; - sp++; - ip++; - m++; - i++; - k++; - } + if (sc->p0->state != STATE_RUN) { + printf("adlink: stopping %d\n", sc->p0->state); + return; } -} - -static void -adlink_tickle(struct softc *sc) -{ + pg = pg->next; + sc->next = pg; + *(pg->genp) = 0; + bus_space_write_4(sc->t0, sc->h0, 0x24, pg->phys); + bus_space_write_4(sc->t0, sc->h0, 0x28, sc->p0->chunksize); wakeup(sc); - tsleep(&sc->ring, PUSER | PCATCH, "tickle", 1); } static int -adlink_new_wave(struct softc *sc, int index, int period, int offset, int length, int avg, int flags) -{ - struct wave *wp; - int l, i; - void **oldvir, **newvir; - - if (index < 0 || index >= NWAVE) - return (EINVAL); - wp = sc->wave[index]; - if (wp == NULL) { - adlink_tickle(sc); - wp = malloc(sizeof *wp, M_DEVBUF, M_WAITOK | M_ZERO); - } - l = howmany(length, INTPERPAGE); - /* Setting a high average here to neuter the realtime bits */ - wp->avg = 31; - if (wp->npages < l) { - oldvir = wp->virtual; - adlink_tickle(sc); - newvir = malloc(sizeof(void *) * l, M_DEVBUF, M_WAITOK | M_ZERO); - if (wp->npages > 0) { - adlink_tickle(sc); - bcopy(oldvir, newvir, wp->npages * sizeof(void *)); - } - for (i = wp->npages; i < l; i++) { - adlink_tickle(sc); - newvir[i] = malloc(PAGE_SIZE, M_DEVBUF, M_WAITOK); - } - wp->virtual = newvir; - wp->npages = l; - wp->mapvir = sc->mapvir; - sc->mapvir += l * PAGE_SIZE; - } else { - oldvir = NULL; - } - wp->index = index; - wp->period = period; - wp->offset = offset; - wp->length = length; - wp->flags = flags; - - for (i = 0; i < l; i++) { - adlink_tickle(sc); - bzero(wp->virtual[i], PAGE_SIZE); - } - wp->avg = avg; - sc->wave[index] = wp; - printf("Wave[%d] {period %d, offset %d, length %d, avg %d, flags %x}\n", - wp->index, wp->period, wp->offset, wp->length, wp->avg, wp->flags); - free(oldvir, M_DEVBUF); - return (0); -} - -static void -adlink_loran(void *arg) +adlink_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { struct softc *sc; - int idx, i; + vm_offset_t o; + int i; + struct pgstat *pg; - sc = arg; - idx = 0; - mtx_lock(&Giant); - for (;;) { - while (sc->stat[idx] == 0) - msleep(sc, NULL, PRIBIO, "loran", 1); - memset(sc->flags, NWAVE, sizeof sc->flags); - for (i = 0; i < NWAVE; i++) { - if (sc->wave[i] != NULL) - adlink_wave(sc, sc->wave[i], sc->ring[idx]); + sc = dev->si_drv1; + if (nprot != VM_PROT_READ) + return (-1); + if (offset == 0) { + *paddr = vtophys(sc->p0); + return (0); + } + o = PAGE_SIZE; + pg = sc->chunks; + for (i = 0; i < sc->nchunks; i++, pg++) { + if (offset - o >= sc->p0->chunksize) { + o += sc->p0->chunksize; + continue; } - sc->cnt += I16PERPAGE; - sc->stat[idx] = 0; - idx++; - idx %= NRING; + *paddr = pg->phys + (offset - o); + return (0); } - mtx_unlock(&Giant); - kthread_exit(0); + return (-1); } static int -adlink_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +adlink_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { - static int once; struct softc *sc; int i, error; - uint32_t u; - - if (once) - return (0); - once = 1; - + u_int u; + struct pgstat *pg; + u_int *genp; + sc = dev->si_drv1; - sc->info = malloc(PAGE_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); - sc->info->nring = NRING; - - sc->info->o_ring = PAGE_SIZE; - for (i = 0; i < NRING; i++) { - sc->ring[i] = malloc(PAGE_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); - sc->pring[i] = vtophys(sc->ring[i]); - } - - error = adlink_new_wave(sc, NWAVE - 1, SPS, 0, SPS, 7, 0); - if (error) - return (error); - - error = kthread_create(adlink_loran, sc, &sc->procp, - 0, 0, "adlink%d", device_get_unit(sc->device)); - if (error) - return (error); + u = *(u_int*)data; + error = 0; + switch (cmd) { + case ADLINK_SETDIVISOR: + if (sc->p0->state == STATE_RUN) + return (EBUSY); + if (u & 1) + return (EINVAL); + sc->p0->divisor = u; + break; + case ADLINK_SETCHUNKSIZE: + if (sc->p0->state != STATE_RESET) + return (EBUSY); + if (u % PAGE_SIZE) + return (EINVAL); + if (sc->p0->ringsize != 0 && sc->p0->ringsize % u) + return (EINVAL); + sc->p0->chunksize = u; + break; + case ADLINK_SETRINGSIZE: + if (sc->p0->state != STATE_RESET) + return (EBUSY); + if (u % PAGE_SIZE) + return (EINVAL); + if (sc->p0->chunksize != 0 && u % sc->p0->chunksize) + return (EINVAL); + sc->p0->ringsize = u; + break; + case ADLINK_START: + if (sc->p0->state == STATE_RUN) + return (EBUSY); + if (sc->p0->state == STATE_RESET) { + + if (sc->p0->chunksize == 0) + sc->p0->chunksize = 4 * PAGE_SIZE; + if (sc->p0->ringsize == 0) + sc->p0->ringsize = 16 * sc->p0->chunksize; + if (sc->p0->divisor == 0) + sc->p0->divisor = 4; + + sc->nchunks = sc->p0->ringsize / sc->p0->chunksize; + if (sc->nchunks * sizeof (*pg->genp) + + sizeof *sc->p0 > PAGE_SIZE) + return (EINVAL); + sc->p0->o_ring = PAGE_SIZE; + genp = (u_int *)(sc->p0 + 1); + sc->p0->o_ringgen = (intptr_t)genp - (intptr_t)(sc->p0); + pg = malloc(sizeof *pg * sc->nchunks, + M_DEVBUF, M_WAITOK | M_ZERO); + sc->chunks = pg; + for (i = 0; i < sc->nchunks; i++) { + pg->genp = genp; + *pg->genp = 1; + genp++; + pg->virt = contigmalloc(sc->p0->chunksize, + M_DEVBUF, M_WAITOK, + 0ul, 0xfffffffful, + PAGE_SIZE, 0); + pg->phys = vtophys(pg->virt); + if (i == sc->nchunks - 1) + pg->next = sc->chunks; + else + pg->next = pg + 1; + pg++; + } + sc->next = sc->chunks; + } - /* Enable interrupts on write complete */ - bus_space_write_4(sc->t0, sc->h0, 0x38, 0x00004000); + /* Reset generation numbers */ + pg = sc->chunks; + for (i = 0; i < sc->nchunks; i++) { + *pg->genp = 0; + pg->gen = 0; + pg++; + } - /* Sample CH0 only */ - bus_space_write_4(sc->t1, sc->h1, 0x00, 1); + /* Enable interrupts on write complete */ + bus_space_write_4(sc->t0, sc->h0, 0x38, 0x00004000); - /* Divide clock by four */ - bus_space_write_4(sc->t1, sc->h1, 0x04, 4); + /* Sample CH0 only */ + bus_space_write_4(sc->t1, sc->h1, 0x00, 1); - /* Software trigger mode: software */ - bus_space_write_4(sc->t1, sc->h1, 0x08, 0); + /* Divide clock by four */ + bus_space_write_4(sc->t1, sc->h1, 0x04, sc->p0->divisor); - /* Trigger level zero */ - bus_space_write_4(sc->t1, sc->h1, 0x0c, 0); + /* Software trigger mode: software */ + bus_space_write_4(sc->t1, sc->h1, 0x08, 0); - /* Trigger source CH0 (not used) */ - bus_space_write_4(sc->t1, sc->h1, 0x10, 0); + /* Trigger level zero */ + bus_space_write_4(sc->t1, sc->h1, 0x0c, 0); - /* Fifo control/status: flush */ - bus_space_write_4(sc->t1, sc->h1, 0x18, 3); + /* Trigger source CH0 (not used) */ + bus_space_write_4(sc->t1, sc->h1, 0x10, 0); - /* Clock source: external sine */ - bus_space_write_4(sc->t1, sc->h1, 0x20, 2); + /* Fifo control/status: flush */ + bus_space_write_4(sc->t1, sc->h1, 0x18, 3); - /* Set up Write DMA */ - bus_space_write_4(sc->t0, sc->h0, 0x24, sc->pring[i]); - bus_space_write_4(sc->t0, sc->h0, 0x28, PAGE_SIZE); - u = bus_space_read_4(sc->t0, sc->h0, 0x3c); - bus_space_write_4(sc->t0, sc->h0, 0x3c, u | 0x00000600); + /* Clock source: external sine */ + bus_space_write_4(sc->t1, sc->h1, 0x20, 2); - /* Acquisition Enable Register: go! */ - bus_space_write_4(sc->t1, sc->h1, 0x1c, 1); - return (0); -} + /* Chipmunks are go! */ + sc->p0->state = STATE_RUN; -static int -adlink_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) -{ - struct softc *sc; - struct wave *wp; - int i, error; - - sc = dev->si_drv1; - wp = (struct wave *)data; - i = wp->index; - if (i < 0 || i >= NWAVE) - return (EINVAL); - if (cmd == ADLINK_GETWAVE) { - if (sc->wave[i] == NULL) - return (ENOENT); - bcopy(sc->wave[i], wp, sizeof(*wp)); - return (0); - } - if (cmd == ADLINK_SETWAVE) { - error = adlink_new_wave(sc, - i, - wp->period, - wp->offset, - wp->length, - wp->avg, - wp->flags); - if (error) - return (error); - bcopy(sc->wave[i], wp, sizeof(*wp)); - return (0); - } - return (ENOIOCTL); -} + /* Set up Write DMA */ + pg = sc->next = sc->chunks; + *(pg->genp) = 0; + bus_space_write_4(sc->t0, sc->h0, 0x24, pg->phys); + bus_space_write_4(sc->t0, sc->h0, 0x28, sc->p0->chunksize); + u = bus_space_read_4(sc->t0, sc->h0, 0x3c); + bus_space_write_4(sc->t0, sc->h0, 0x3c, u | 0x00000600); -static int -adlink_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) -{ - struct softc *sc; - struct wave *wp; - int i, j; + /* Acquisition Enable Register: go! */ + bus_space_write_4(sc->t1, sc->h1, 0x1c, 1); - sc = dev->si_drv1; - if (nprot != VM_PROT_READ) - return (-1); - for (i = 0; i < NWAVE; i++) { - if (sc->wave[i] == NULL) - continue; - wp = sc->wave[i]; - if (offset < wp->mapvir) - continue; - j = (offset - wp->mapvir) / PAGE_SIZE; - if (j >= wp->npages) - continue; - *paddr = vtophys(wp->virtual[j]); - return (0); - } - return (-1); -} - -static void -adlink_intr(void *arg) -{ - struct softc *sc; - uint32_t u; - int i, j; - - sc = arg; - u = bus_space_read_4(sc->t0, sc->h0, 0x38); - if (!(u & 0x00800000)) - return; - bus_space_write_4(sc->t0, sc->h0, 0x38, u | 0x003f4000); - - j = sc->idx; - sc->stat[j] = 1; - i = (j + 1) % NRING; - sc->idx = i; - u = bus_space_read_4(sc->t1, sc->h1, 0x18); - if (u & 1) { - printf("adlink FIFO overrun\n"); - return; - } - bus_space_write_4(sc->t0, sc->h0, 0x24, sc->pring[i]); - bus_space_write_4(sc->t0, sc->h0, 0x28, PAGE_SIZE); - wakeup(sc); - if (sc->stat[i]) { - printf("adlink page busy\n"); + break; + case ADLINK_STOP: + if (sc->p0->state == STATE_RESET) + break; + sc->p0->state = EINTR; + while (*(sc->next->genp) == 0) + tsleep(sc, PUSER | PCATCH, "adstop", 1); + break; +#ifdef notyet + /* + * I'm not sure we can actually do this. How do we revoke + * the mmap'ed pages from any process having them mmapped ? + */ + case ADLINK_RESET: + if (sc->p0->state == STATE_RESET) + break; + sc->p0->state = EINTR; + while (*(sc->next->genp) == 0) + tsleep(sc, PUSER | PCATCH, "adreset", 1); + /* deallocate ring buffer */ + break; +#endif + default: + error = ENOIOCTL; + break; } + return (error); } -static struct cdevsw adlink_cdevsw = { - .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, - .d_open = adlink_open, - .d_ioctl = adlink_ioctl, - .d_mmap = adlink_mmap, - .d_name = "adlink", -}; - static devclass_t adlink_devclass; static int @@ -431,7 +367,8 @@ adlink_attach(device_t self) * chip. */ rid = 0x10; - sc->r0 = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, RF_ACTIVE); + sc->r0 = bus_alloc_resource(self, SYS_RES_IOPORT, &rid, + 0, ~0, 1, RF_ACTIVE); if (sc->r0 == NULL) return(ENODEV); sc->t0 = rman_get_bustag(sc->r0); @@ -443,7 +380,8 @@ adlink_attach(device_t self) * are described in the manual which comes with the card. */ rid = 0x14; - sc->r1 = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, RF_ACTIVE); + sc->r1 = bus_alloc_resource(self, SYS_RES_IOPORT, &rid, + 0, ~0, 1, RF_ACTIVE); if (sc->r1 == NULL) return(ENODEV); sc->t1 = rman_get_bustag(sc->r1); @@ -451,22 +389,28 @@ adlink_attach(device_t self) printf("Res1 %x %x\n", sc->t1, sc->h1); rid = 0x0; - sc->ri = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, - RF_ACTIVE | RF_SHAREABLE); + sc->ri = bus_alloc_resource(self, SYS_RES_IRQ, &rid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (sc->ri == NULL) return (ENODEV); - i = bus_setup_intr(self, sc->ri, INTR_MPSAFE | INTR_TYPE_MISC | INTR_FAST, + i = bus_setup_intr(self, sc->ri, + INTR_MPSAFE | INTR_TYPE_MISC | INTR_FAST, adlink_intr, sc, &sc->intrhand); if (i) { printf("adlink: Couldn't get FAST intr\n"); - i = bus_setup_intr(self, sc->ri, INTR_TYPE_MISC, + i = bus_setup_intr(self, sc->ri, + INTR_MPSAFE | INTR_TYPE_MISC, adlink_intr, sc, &sc->intrhand); } if (i) return (ENODEV); + sc->p0 = malloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); + sc->p0->version = PAGE0VERSION; + sc->p0->state = STATE_RESET; + sc->dev = make_dev(&adlink_cdevsw, device_get_unit(self), UID_ROOT, GID_WHEEL, 0444, "adlink%d", device_get_unit(self)); sc->dev->si_drv1 = sc; @@ -491,4 +435,5 @@ static driver_t adlink_driver = { }; DRIVER_MODULE(adlink, pci, adlink_driver, adlink_devclass, 0, 0); + #endif /* _KERNEL */ |