summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorphk <phk@FreeBSD.org>2004-09-19 21:52:15 +0000
committerphk <phk@FreeBSD.org>2004-09-19 21:52:15 +0000
commit591680661ddfbef9bd12a8537684ab99a9860753 (patch)
tree46754bd395d4d80cc4dc69207014a79548bb507d
parent517c86564e65d04c23914a65676c6317eab6d343 (diff)
downloadFreeBSD-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.c555
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 */
OpenPOWER on IntegriCloud