diff options
| author | dsainty <dsainty@NetBSD.org> | 2003-01-05 05:12:38 +0000 |
|---|---|---|
| committer | dsainty <dsainty@NetBSD.org> | 2003-01-05 05:12:38 +0000 |
| commit | 466f76bf7b86916c6e848e97bdf3240a697a034e (patch) | |
| tree | 4102c598f4423b02594fab1a68d2075a98bb4079 /sys/dev | |
| parent | 6957647e8bcd0b2abe989293ce3c0505bca8b492 (diff) | |
Make the bthci driver functional. This is an experimental, incomplete, but
actually functional driver. It provides user access through a character
device to a Bluetooth HCI capable driver.
The device interface is the same (open/read/write) as the RAW HCI socket
(socket/recv/send) interface provided by the Linux and FreeBSD netgraph
Bluetooth stacks. This allows a (very small) number of diagnostic programs to
be trivially ported and actually work.
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/bluetooth/bluetooth.h | 76 | ||||
| -rw-r--r-- | sys/dev/bluetooth/bthci.c | 401 | ||||
| -rw-r--r-- | sys/dev/bluetooth/bthcivar.h | 13 |
3 files changed, 445 insertions, 45 deletions
diff --git a/sys/dev/bluetooth/bluetooth.h b/sys/dev/bluetooth/bluetooth.h index 29d9277f4c4..1b1e01a76e7 100644 --- a/sys/dev/bluetooth/bluetooth.h +++ b/sys/dev/bluetooth/bluetooth.h @@ -1,11 +1,12 @@ -/* $NetBSD: bluetooth.h,v 1.1 2002/08/24 17:30:13 augustss Exp $ */ +/* $NetBSD: bluetooth.h,v 1.2 2003/01/05 05:12:38 dsainty Exp $ */ /* - * Copyright (c) 2000 The NetBSD Foundation, Inc. + * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation - * by Lennart Augustsson (lennart@augustsson.net). + * by Lennart Augustsson (lennart@augustsson.net) and + * David Sainty (David.Sainty@dtsp.co.nz). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -36,14 +37,77 @@ * POSSIBILITY OF SUCH DAMAGE. */ +struct btframe_methods { + int (*bt_open)(void *h, int flag, int mode, struct proc *p); + int (*bt_close)(void *h, int flag, int mode, struct proc *p); + + int (*bt_control)(void *h, u_int8_t *data, size_t len); + int (*bt_sendacldata)(void *h, u_int8_t *data, size_t len); + int (*bt_sendscodata)(void *h, u_int8_t *data, size_t len); + + int (*bt_splraise)(void); + + /* + * Attempt to activate flow control. The driver MAY be able + * to block certain events, but it isn't guaranteed, and even + * blocked events may still be delivered if one was in + * progress at the time. + * + * + * In addition, it is possible for an error to occur + * unblocking an event. + * + * Returns bit map of post-operation blocked/unblocked events. + */ +#define BT_CBBLOCK_EVENT 1 +#define BT_CBBLOCK_ACL_DATA 2 +#define BT_CBBLOCK_SCO_DATA 4 + unsigned int (*bt_blockcb)(void *h, unsigned int cbblocks); + unsigned int (*bt_unblockcb)(void *h, unsigned int cbblocks); +}; + +struct btframe_callback_methods { + /* Received data */ + void (*bt_recveventdata)(void *h, u_int8_t *data, size_t len); + void (*bt_recvacldata)(void *h, u_int8_t *data, size_t len); + void (*bt_recvscodata)(void *h, u_int8_t *data, size_t len); +}; + struct bt_attach_args { - const void *bt_methods; + struct btframe_methods const *bt_methods; + struct btframe_callback_methods const **bt_cb; void *bt_handle; }; -struct btframe_methods; - int bt_print(void *aux, const char *pnp); #define splbt spltty #define IPL_BT IPL_TTY + +#define BTGETW(x) (((u_int8_t const*)(x))[0] | ((u_int8_t const*)(x))[1] << 8) + +/* HCI interface constants */ + +/* Maximum event packet length, including header */ +#define BTHCI_EVENT_MIN_LEN 2 +#define BTHCI_EVENT_MAX_LEN (0xff + BTHCI_EVENT_MIN_LEN) +#define BTHCI_EVENT_LEN_OFFT 1 +#define BTHCI_EVENT_LEN_LENGTH 1 + +/* Maximum command packet length, including header */ +#define BTHCI_COMMAND_MIN_LEN 3 +#define BTHCI_COMMAND_MAX_LEN (0xff + BTHCI_COMMAND_MIN_LEN) +#define BTHCI_COMMAND_LEN_OFFT 2 +#define BTHCI_COMMAND_LEN_LENGTH 1 + +/* Maximum ACL data packet length, including header */ +#define BTHCI_ACL_DATA_MIN_LEN 4 +#define BTHCI_ACL_DATA_MAX_LEN (0xffff + BTHCI_ACL_DATA_MIN_LEN) +#define BTHCI_ACL_DATA_LEN_OFFT 2 +#define BTHCI_ACL_DATA_LEN_LENGTH 2 + +/* HCI consumer interface constants */ +#define BTHCI_PKTID_COMMAND 1 +#define BTHCI_PKTID_ACL_DATA 2 +#define BTHCI_PKTID_SCO_DATA 3 +#define BTHCI_PKTID_EVENT 4 diff --git a/sys/dev/bluetooth/bthci.c b/sys/dev/bluetooth/bthci.c index 595add903b1..687e3c891af 100644 --- a/sys/dev/bluetooth/bthci.c +++ b/sys/dev/bluetooth/bthci.c @@ -1,11 +1,12 @@ -/* $NetBSD: bthci.c,v 1.10 2002/10/24 01:36:34 augustss Exp $ */ +/* $NetBSD: bthci.c,v 1.11 2003/01/05 05:12:38 dsainty Exp $ */ /* - * Copyright (c) 2001 The NetBSD Foundation, Inc. + * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation - * by Lennart Augustsson (lennart@augustsson.net). + * by Lennart Augustsson (lennart@augustsson.net) and + * David Sainty (David.Sainty@dtsp.co.nz). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -54,13 +55,30 @@ #ifdef BTHCI_DEBUG #define DPRINTF(x) if (bthcidebug) printf x +#define DPRINTFN(n,x) if (bthcidebug>(n)) printf x #define Static -int bthcidebug = 0; +int bthcidebug = 99; #else #define DPRINTF(x) +#define DPRINTFN(n,x) #define Static static #endif +struct bthci_softc { + struct device sc_dev; + struct btframe_methods const *sc_methods; + void *sc_handle; + + u_int8_t *sc_rd_buf; + size_t sc_rd_len; + struct selinfo sc_rd_sel; + + int sc_refcnt; + int sc_outputready; + char sc_open; + char sc_dying; +}; + int bthci_match(struct device *parent, struct cfdata *match, void *aux); void bthci_attach(struct device *parent, struct device *self, void *aux); int bthci_activate(struct device *self, enum devact act); @@ -83,13 +101,24 @@ dev_type_close(bthciclose); dev_type_poll(bthcipoll); dev_type_kqfilter(bthcikqfilter); +static int bthciread(dev_t dev, struct uio *uio, int flag); +static int bthciwrite(dev_t dev, struct uio *uio, int flag); + +static void bthci_recveventdata(void *h, u_int8_t *data, size_t len); +static void bthci_recvacldata(void *h, u_int8_t *data, size_t len); +static void bthci_recvscodata(void *h, u_int8_t *data, size_t len); + const struct cdevsw bthci_cdevsw = { - bthciopen, bthciclose, noread, nowrite, noioctl, + bthciopen, bthciclose, bthciread, bthciwrite, noioctl, nostop, notty, bthcipoll, nommap, bthcikqfilter, }; #define BTHCIUNIT(dev) (minor(dev)) +struct btframe_callback_methods const bthci_callbacks = { + bthci_recveventdata, bthci_recvacldata, bthci_recvscodata +}; + int bthci_match(struct device *parent, struct cfdata *match, void *aux) { @@ -107,15 +136,17 @@ bthci_attach(struct device *parent, struct device *self, void *aux) sc->sc_methods = bt->bt_methods; sc->sc_handle = bt->bt_handle; -#ifdef DIAGNOSTIC_XXX - if (sc->sc_methods->bt_read == NULL || - sc->sc_methods->bt_write == NULL || - sc->sc_methods->bt_poll == NULL || - sc->sc_methods->bt_kqfilter == NULL) + *bt->bt_cb = &bthci_callbacks; + +#ifdef DIAGNOSTIC + if (sc->sc_methods->bt_open == NULL || + sc->sc_methods->bt_close == NULL || + sc->sc_methods->bt_control == NULL || + sc->sc_methods->bt_sendacldata == NULL || + sc->sc_methods->bt_sendscodata == NULL) panic("%s: missing methods", sc->sc_dev.dv_xname); #endif - printf("driver not implemented"); printf("\n"); } @@ -135,13 +166,35 @@ bthci_activate(struct device *self, enum devact act) return (0); } +static void +bthci_abortdealloc(struct bthci_softc *sc) +{ + DPRINTFN(0, ("%s: sc=%p\n", __func__, sc)); + + if (sc->sc_rd_buf != NULL) { + free(sc->sc_rd_buf, M_TEMP); + sc->sc_rd_buf = NULL; + } +} + int bthci_detach(struct device *self, int flags) { - /*struct bthci_softc *sc = (struct bthci_softc *)self;*/ + struct bthci_softc *sc = (struct bthci_softc *)self; int maj, mn; - /* XXX needs reference count */ + sc->sc_dying = 1; + wakeup(&sc->sc_outputready); + + DPRINTFN(1, ("%s: waiting for refcount (%d)\n", + __func__, sc->sc_refcnt)); + + if (--sc->sc_refcnt >= 0) + tsleep(&sc->sc_refcnt, PZERO, "bthcdt", 0); + + DPRINTFN(1, ("%s: refcount complete\n", __func__)); + + bthci_abortdealloc(sc); /* locate the major number */ maj = cdevsw_lookup_major(&bthci_cdevsw); @@ -150,6 +203,8 @@ bthci_detach(struct device *self, int flags) mn = self->dv_unit; vdevgone(maj, mn, mn, VCHR); + DPRINTFN(1, ("%s: driver detached\n", __func__)); + return (0); } @@ -166,13 +221,30 @@ bthciopen(dev_t dev, int flag, int mode, struct proc *p) return (EIO); if (sc->sc_open) return (EBUSY); + + sc->sc_rd_buf = malloc(BTHCI_ACL_DATA_MAX_LEN + 1, M_TEMP, M_NOWAIT); + if (sc->sc_rd_buf == NULL) + return (ENOMEM); + + sc->sc_refcnt++; + if (sc->sc_methods->bt_open != NULL) { error = sc->sc_methods->bt_open(sc->sc_handle, flag, mode, p); if (error) - return (error); + goto bad; } + + if (--sc->sc_refcnt < 0) + wakeup(&sc->sc_refcnt); + sc->sc_open = 1; return (0); + + bad: + free(sc->sc_rd_buf, M_TEMP); + sc->sc_rd_buf = NULL; + + return error; } int @@ -184,55 +256,172 @@ bthciclose(dev_t dev, int flag, int mode, struct proc *p) sc = device_lookup(&bthci_cd, BTHCIUNIT(dev)); if (sc == NULL) return (ENXIO); + + if (!sc->sc_open) + return (0); + + sc->sc_refcnt++; + sc->sc_open = 0; + if (sc->sc_methods->bt_close != NULL) error = sc->sc_methods->bt_close(sc->sc_handle, flag, mode, p); else error = 0; + + bthci_abortdealloc(sc); + + if (--sc->sc_refcnt < 0) + wakeup(&sc->sc_refcnt); + return (error); } -#if 0 -int +static int bthciread(dev_t dev, struct uio *uio, int flag) { struct bthci_softc *sc; + int error; + int s; + unsigned int blocked; sc = device_lookup(&bthci_cd, BTHCIUNIT(dev)); if (sc == NULL) return (ENXIO); - if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0 || !sc->sc_open) + + if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0 || + !sc->sc_open || sc->sc_dying) return (EIO); - if (uio->uio_resid < sc->sc_params.maxsize) { + + DPRINTFN(1, ("%s: length=%lu\n", __func__, + (unsigned long)uio->uio_resid)); + + sc->sc_refcnt++; + + s = sc->sc_methods->bt_splraise(); + while (!sc->sc_outputready) { +#define BTHCI_READ_EVTS (BT_CBBLOCK_ACL_DATA | BT_CBBLOCK_EVENT) + blocked = sc->sc_methods->bt_unblockcb(sc->sc_handle, + BTHCI_READ_EVTS); + if ((blocked & BTHCI_READ_EVTS) != 0) { + /* Problem unblocking events */ + splx(s); + DPRINTFN(1,("%s: couldn't unblock events\n", + __func__)); + error = EIO; + goto ret; + } + + DPRINTFN(5,("%s: calling tsleep()\n", __func__)); + error = tsleep(&sc->sc_outputready, PZERO | PCATCH, + "bthcrd", 0); + if (sc->sc_dying) + error = EIO; + if (error) { + splx(s); + DPRINTFN(0, ("%s: tsleep() = %d\n", + __func__, error)); + goto ret; + } + } + splx(s); + + if (uio->uio_resid < sc->sc_rd_len) { #ifdef DIAGNOSTIC printf("bthciread: short read %d < %d\n", uio->uio_resid, - sc->sc_params.maxsize); + sc->sc_rd_len); #endif - return (EINVAL); + error = EINVAL; + goto ret; } - return (sc->sc_methods->im_read(sc->sc_handle, uio, flag)); + + error = uiomove(sc->sc_rd_buf, sc->sc_rd_len, uio); + + if (!error) { + sc->sc_outputready = 0; + sc->sc_rd_len = 0; + + /* Unblock events (in case they were blocked) */ + sc->sc_methods->bt_unblockcb(sc->sc_handle, + BT_CBBLOCK_ACL_DATA | + BT_CBBLOCK_EVENT); + } + + ret: + if (--sc->sc_refcnt < 0) + wakeup(&sc->sc_refcnt); + + return error; } -int +static int bthciwrite(dev_t dev, struct uio *uio, int flag) { struct bthci_softc *sc; + u_int8_t *buf; + int error; + size_t uiolen; sc = device_lookup(&bthci_cd, BTHCIUNIT(dev)); if (sc == NULL) return (ENXIO); + if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0 || !sc->sc_open) return (EIO); - if (uio->uio_resid > sc->sc_params.maxsize) { + + uiolen = uio->uio_resid; + + DPRINTFN(1, ("%s: length=%lu\n", __func__, (unsigned long)uiolen)); + + if (uiolen > BTHCI_ACL_DATA_MAX_LEN + 1) { #ifdef DIAGNOSTIC - printf("bthciread: long write %d > %d\n", uio->uio_resid, - sc->sc_params.maxsize); + printf("bthciread: long write %d\n", uio->uio_resid); #endif return (EINVAL); } - return (sc->sc_methods->im_write(sc->sc_handle, uio, flag)); -} + + if (uiolen <= 1) { +#ifdef DIAGNOSTIC + printf("bthciread: short write %d\n", uio->uio_resid); #endif + return (EINVAL); + } + + sc->sc_refcnt++; + + buf = malloc(uiolen, M_TEMP, M_WAITOK); + + error = uiomove(buf, uiolen, uio); + if (error) { + free(buf, M_TEMP); + goto ret; + } + + if (buf[0] == BTHCI_PKTID_COMMAND) { + /* Command */ + error = sc->sc_methods->bt_control(sc->sc_handle, + &buf[1], uiolen - 1); + } else if (buf[0] == BTHCI_PKTID_ACL_DATA) { + /* ACL data */ + error = sc->sc_methods->bt_sendacldata(sc->sc_handle, + &buf[1], uiolen - 1); + } else if (buf[0] == BTHCI_PKTID_SCO_DATA) { + /* SCO data */ + error = sc->sc_methods->bt_sendscodata(sc->sc_handle, + &buf[1], uiolen - 1); + } else { + /* Bad packet type */ + error = EINVAL; + } + + free(buf, M_TEMP); + + ret: + if (--sc->sc_refcnt < 0) + wakeup(&sc->sc_refcnt); + + return error; +} #if 0 int @@ -266,6 +455,8 @@ int bthcipoll(dev_t dev, int events, struct proc *p) { struct bthci_softc *sc; + int revents; + int s; sc = device_lookup(&bthci_cd, BTHCIUNIT(dev)); if (sc == NULL) @@ -273,13 +464,54 @@ bthcipoll(dev_t dev, int events, struct proc *p) if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0 || !sc->sc_open) return (EIO); - return (sc->sc_methods->bt_poll(sc->sc_handle, events, p)); + revents = events & (POLLOUT | POLLWRNORM); + + if (events & (POLLIN | POLLRDNORM)) { + s = sc->sc_methods->bt_splraise(); + if (sc->sc_outputready) { + DPRINTFN(2,("%s: have data\n", __func__)); + revents |= events & (POLLIN | POLLRDNORM); + } else { + DPRINTFN(2,("%s: recording read select\n", + __func__)); + selrecord(p, &sc->sc_rd_sel); + } + splx(s); + } + + return revents; } +static void +filt_bthcirdetach(struct knote *kn) +{ + struct bthci_softc *sc = kn->kn_hook; + int s; + + s = sc->sc_methods->bt_splraise(); + SLIST_REMOVE(&sc->sc_rd_sel.sel_klist, kn, knote, kn_selnext); + splx(s); +} + +/* ARGSUSED */ +static int +filt_bthciread(struct knote *kn, long hint) +{ + struct bthci_softc *sc = kn->kn_hook; + + kn->kn_data = sc->sc_rd_len; + return (kn->kn_data > 0); +} + +static const struct filterops bthciread_filtops = + { 1, NULL, filt_bthcirdetach, filt_bthciread }; + int bthcikqfilter(dev_t dev, struct knote *kn) { struct bthci_softc *sc; + struct klist *klist; + int s; sc = device_lookup(&bthci_cd, BTHCIUNIT(dev)); if (sc == NULL) @@ -287,5 +519,116 @@ bthcikqfilter(dev_t dev, struct knote *kn) if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0 || !sc->sc_open) return (EIO); - return (sc->sc_methods->bt_kqfilter(sc->sc_handle, kn)); + switch (kn->kn_filter) { + case EVFILT_READ: + klist = &sc->sc_rd_sel.sel_klist; + kn->kn_fop = &bthciread_filtops; + break; + default: + return (1); + } + + kn->kn_hook = sc; + + s = sc->sc_methods->bt_splraise(); + SLIST_INSERT_HEAD(klist, kn, kn_selnext); + splx(s); + + return (0); +} + +static void bthci_recveventdata(void *h, u_int8_t *data, size_t len) +{ + struct bthci_softc *sc = h; + size_t pktlength; + + if (!sc->sc_open) + return; + + if (len < BTHCI_EVENT_MIN_LEN || len > BTHCI_EVENT_MAX_LEN) { + DPRINTFN(1,("%s: invalid sized event, size=%u\n", + __func__, (unsigned int)len)); + return; + } + + DPRINTFN(1,("%s: received event: %02x\n", __func__, data[0])); + + pktlength = data[BTHCI_EVENT_LEN_OFFT] + BTHCI_EVENT_LEN_OFFT + + BTHCI_EVENT_LEN_LENGTH; + if (pktlength != len) { + DPRINTFN(1,("%s: event length didn't match, " + "pktlen=%u size=%u\n", + __func__, (unsigned int)pktlength, + (unsigned int)len)); + return; + } + + if (sc->sc_rd_len != 0) { + DPRINTFN(1,("%s: dropping an event, size=%u\n", + __func__, (unsigned int)len)); + return; + } + + sc->sc_rd_buf[0] = BTHCI_PKTID_EVENT; + memcpy(&sc->sc_rd_buf[1], data, len); + sc->sc_rd_len = len + 1; + sc->sc_outputready = 1; + + sc->sc_methods->bt_blockcb(sc->sc_handle, + BT_CBBLOCK_ACL_DATA | BT_CBBLOCK_EVENT); + + wakeup(&sc->sc_outputready); + selnotify(&sc->sc_rd_sel, 0); +} + +static void bthci_recvacldata(void *h, u_int8_t *data, size_t len) +{ + struct bthci_softc *sc = h; + size_t pktlength; + + if (!sc->sc_open) + return; + + if (len < BTHCI_ACL_DATA_MIN_LEN || len > BTHCI_ACL_DATA_MAX_LEN) { + DPRINTFN(1,("%s: invalid acl packet, size=%u\n", + __func__, (unsigned int)len)); + return; + } + + pktlength = BTGETW(&data[BTHCI_ACL_DATA_LEN_OFFT]) + + BTHCI_ACL_DATA_LEN_OFFT + + BTHCI_ACL_DATA_LEN_LENGTH; + + if (pktlength != len) { + DPRINTFN(1,("%s: acl packet length didn't match, " + "pktlen=%u size=%u\n", + __func__, (unsigned int)pktlength, + (unsigned int)len)); + return; + } + + if (sc->sc_rd_len != 0) { + DPRINTFN(1,("%s: dropping an acl packet, size=%u\n", + __func__, (unsigned int)len)); + return; + } + + sc->sc_rd_buf[0] = BTHCI_PKTID_ACL_DATA; + memcpy(&sc->sc_rd_buf[1], data, len); + sc->sc_rd_len = len + 1; + sc->sc_outputready = 1; + + sc->sc_methods->bt_blockcb(sc->sc_handle, + BT_CBBLOCK_ACL_DATA | BT_CBBLOCK_EVENT); + + wakeup(&sc->sc_outputready); + selnotify(&sc->sc_rd_sel, 0); +} + +static void bthci_recvscodata(void *h, u_int8_t *data, size_t len) +{ + struct bthci_softc *sc = h; + + if (!sc->sc_open) + return; } diff --git a/sys/dev/bluetooth/bthcivar.h b/sys/dev/bluetooth/bthcivar.h index 63fbd090ff3..a786903e0c0 100644 --- a/sys/dev/bluetooth/bthcivar.h +++ b/sys/dev/bluetooth/bthcivar.h @@ -1,7 +1,7 @@ -/* $NetBSD: bthcivar.h,v 1.2 2002/10/23 09:13:09 jdolecek Exp $ */ +/* $NetBSD: bthcivar.h,v 1.3 2003/01/05 05:12:38 dsainty Exp $ */ /* - * Copyright (c) 2002 The NetBSD Foundation, Inc. + * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -45,11 +45,4 @@ struct bthci_methods { int (*bt_kqfilter)(void *h, struct knote *kn); }; -struct bthci_softc { - struct device sc_dev; - const struct bthci_methods *sc_methods; - void *sc_handle; - char sc_open; -}; - -void bthci_frame_available(struct device *); +struct btframe_methods; |
