diff options
Diffstat (limited to 'sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c')
-rw-r--r-- | sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c | 1247 |
1 files changed, 1247 insertions, 0 deletions
diff --git a/sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c b/sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c new file mode 100644 index 0000000..443c18a --- /dev/null +++ b/sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c @@ -0,0 +1,1247 @@ +/* + * ng_bt3c_pccard.c + * + * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * 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. + * + * $Id: ng_bt3c_pccard.c,v 1.2 2002/11/12 00:51:45 max Exp $ + * $FreeBSD$ + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + * Based on information obrained from: Jose Orlando Pereira <jop@di.uminho.pt> + * and disassembled w2k driver. + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + */ + +#include <sys/param.h> +#include <sys/systm.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <sys/conf.h> +#include <sys/endian.h> +#include <sys/interrupt.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/module.h> + +#include <machine/resource.h> +#include <sys/rman.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <net/if_var.h> + +#include <dev/pccard/pccardreg.h> +#include <dev/pccard/pccardvar.h> +#include <dev/pccard/pccarddevs.h> + +#include <netgraph/ng_message.h> +#include <netgraph/netgraph.h> +#include <netgraph/ng_parse.h> +#include <ng_bluetooth.h> +#include <ng_hci.h> +#include "ng_bt3c.h" +#include "ng_bt3c_var.h" + +/* Netgraph methods */ +static ng_constructor_t ng_bt3c_constructor; +static ng_shutdown_t ng_bt3c_shutdown; +static ng_newhook_t ng_bt3c_newhook; +static ng_connect_t ng_bt3c_connect; +static ng_disconnect_t ng_bt3c_disconnect; +static ng_rcvmsg_t ng_bt3c_rcvmsg; +static ng_rcvdata_t ng_bt3c_rcvdata; + +/* PCMCIA driver methods */ +static int bt3c_pccard_match (device_t); +static int bt3c_pccard_probe (device_t); +static int bt3c_pccard_attach (device_t); +static int bt3c_pccard_detach (device_t); + +static void bt3c_intr (void *); +static void bt3c_receive (bt3c_softc_p); +static int bt3c_append (struct mbuf *, int); + +static void bt3c_swi_intr (void *); +static void bt3c_forward (node_p, hook_p, void *, int); +static void bt3c_send (node_p, hook_p, void *, int); + +static void bt3c_download_firmware (bt3c_softc_p, char const *, int); + +#define bt3c_set_address(sc, address) \ +do { \ + outb(rman_get_start((sc)->iobase) + BT3C_ADDR_L, ((address) & 0xff)); \ + outb(rman_get_start((sc)->iobase) + BT3C_ADDR_H, (((address) >> 8) & 0xff)); \ +} while (0) + +#define bt3c_read_data(sc, data) \ +do { \ + (data) = inb(rman_get_start((sc)->iobase) + BT3C_DATA_L); \ + (data) |= ((inb(rman_get_start((sc)->iobase) + BT3C_DATA_H) & 0xff) << 8); \ +} while (0) + +#define bt3c_write_data(sc, data) \ +do { \ + outb(rman_get_start((sc)->iobase) + BT3C_DATA_L, ((data) & 0xff)); \ + outb(rman_get_start((sc)->iobase) + BT3C_DATA_H, (((data) >> 8) & 0xff)); \ +} while (0) + +#define bt3c_read_control(sc, data) \ +do { \ + (data) = inb(rman_get_start((sc)->iobase) + BT3C_CONTROL); \ +} while (0) + +#define bt3c_write_control(sc, data) \ +do { \ + outb(rman_get_start((sc)->iobase) + BT3C_CONTROL, (data)); \ +} while (0) + +#define bt3c_read(sc, address, data) \ +do { \ + bt3c_set_address((sc), (address)); \ + bt3c_read_data((sc), (data)); \ +} while(0) + +#define bt3c_write(sc, address, data) \ +do { \ + bt3c_set_address((sc), (address)); \ + bt3c_write_data((sc), (data)); \ +} while(0) + +static MALLOC_DEFINE(M_BT3C, "bt3c", "bt3c data structures"); + +/**************************************************************************** + **************************************************************************** + ** Netgraph specific + **************************************************************************** + ****************************************************************************/ + +/* + * Netgraph node type + */ + +/* Queue length */ +static const struct ng_parse_struct_field ng_bt3c_node_qlen_type_fields[] = +{ + { "queue", &ng_parse_int32_type, }, + { "qlen", &ng_parse_int32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_bt3c_node_qlen_type = { + &ng_parse_struct_type, + &ng_bt3c_node_qlen_type_fields +}; + +/* Stat info */ +static const struct ng_parse_struct_field ng_bt3c_node_stat_type_fields[] = +{ + { "pckts_recv", &ng_parse_uint32_type, }, + { "bytes_recv", &ng_parse_uint32_type, }, + { "pckts_sent", &ng_parse_uint32_type, }, + { "bytes_sent", &ng_parse_uint32_type, }, + { "oerrors", &ng_parse_uint32_type, }, + { "ierrors", &ng_parse_uint32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_bt3c_node_stat_type = { + &ng_parse_struct_type, + &ng_bt3c_node_stat_type_fields +}; + +static const struct ng_cmdlist ng_bt3c_cmdlist[] = { +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_STATE, + "get_state", + NULL, + &ng_parse_uint16_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_QLEN, + "get_qlen", + NULL, + &ng_bt3c_node_qlen_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_SET_QLEN, + "set_qlen", + &ng_bt3c_node_qlen_type, + NULL +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_STAT, + "get_stat", + NULL, + &ng_bt3c_node_stat_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL +}, +{ 0, } +}; + +static struct ng_type typestruct = { + NG_ABI_VERSION, + NG_BT3C_NODE_TYPE, /* typename */ + NULL, /* modevent */ + ng_bt3c_constructor, /* constructor */ + ng_bt3c_rcvmsg, /* control message */ + ng_bt3c_shutdown, /* destructor */ + ng_bt3c_newhook, /* new hook */ + NULL, /* find hook */ + ng_bt3c_connect, /* connect hook */ + ng_bt3c_rcvdata, /* data */ + ng_bt3c_disconnect, /* disconnect hook */ + ng_bt3c_cmdlist /* node command list */ +}; +NETGRAPH_INIT(bt3c, &typestruct); +MODULE_VERSION(ng_bt3c, NG_BLUETOOTH_VERSION); + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_bt3c_constructor(node_p node) +{ + return (EINVAL); +} /* ng_bt3c_constructor */ + +/* + * Netgraph node destructor. Destroy node only when device has been detached + */ + +static int +ng_bt3c_shutdown(node_p node) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + /* Create new fresh one if we are not going down */ + if (sc == NULL) + goto out; + + /* Create new Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->node) != 0) { + device_printf(sc->dev, "Could not create Netgraph node\n"); + sc->node = NULL; + goto out; + } + + /* Name new Netgraph node */ + if (ng_name_node(sc->node, device_get_nameunit(sc->dev)) != 0) { + device_printf(sc->dev, "Could not name Netgraph node\n"); + NG_NODE_UNREF(sc->node); + sc->node = NULL; + goto out; + } + + NG_NODE_SET_PRIVATE(sc->node, sc); +out: + return (0); +} /* ng_bt3c_shutdown */ + +/* + * Create new hook. There can only be one. + */ + +static int +ng_bt3c_newhook(node_p node, hook_p hook, char const *name) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_BT3C_HOOK) != 0) + return (EINVAL); + + if (sc->hook != NULL) + return (EISCONN); + + sc->hook = hook; + + return (0); +} /* ng_bt3c_newhook */ + +/* + * Connect hook. Say YEP, that's OK with me. + */ + +static int +ng_bt3c_connect(hook_p hook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook != sc->hook) { + sc->hook = NULL; + return (EINVAL); + } + + /* set the hook into queueing mode (for incoming (from wire) packets) */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + return (0); +} /* ng_bt3c_connect */ + +/* + * Disconnect hook + */ + +static int +ng_bt3c_disconnect(hook_p hook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* + * We need to check for sc != NULL because we can be called from + * bt3c_pccard_detach() via ng_rmnode_self() + */ + + if (sc != NULL) { + if (hook != sc->hook) + return (EINVAL); + + IF_DRAIN(&sc->inq); + IF_DRAIN(&sc->outq); + + sc->hook = NULL; + } + + return (0); +} /* ng_bt3c_disconnect */ + +/* + * Process control message + */ + +static int +ng_bt3c_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + int error = 0; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + snprintf(rsp->data, NG_TEXTRESPONSE, + "Hook: %s\n" \ + "Flags: %#x\n" \ + "Debug: %d\n" \ + "State: %d\n" \ + "IncmQ: [len:%d,max:%d]\n" \ + "OutgQ: [len:%d,max:%d]\n", + (sc->hook != NULL)? NG_BT3C_HOOK : "", + sc->flags, + sc->debug, + sc->state, + _IF_QLEN(&sc->inq), /* XXX */ + sc->inq.ifq_maxlen, /* XXX */ + _IF_QLEN(&sc->outq), /* XXX */ + sc->outq.ifq_maxlen /* XXX */ + ); + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_BT3C_COOKIE: + switch (msg->header.cmd) { + case NGM_BT3C_NODE_GET_STATE: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_state_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_bt3c_node_state_ep *)(rsp->data)) = + sc->state; + break; + + case NGM_BT3C_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_bt3c_node_debug_ep)) + error = EMSGSIZE; + else + sc->debug = + *((ng_bt3c_node_debug_ep *)(msg->data)); + break; + + case NGM_BT3C_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_debug_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_bt3c_node_debug_ep *)(rsp->data)) = + sc->debug; + break; + + case NGM_BT3C_NODE_GET_QLEN: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_qlen_ep), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + switch (((ng_bt3c_node_qlen_ep *)(msg->data))->queue) { + case NGM_BT3C_NODE_IN_QUEUE: + ((ng_bt3c_node_qlen_ep *)(rsp->data))->queue = + NGM_BT3C_NODE_IN_QUEUE; + ((ng_bt3c_node_qlen_ep *)(rsp->data))->qlen = + sc->inq.ifq_maxlen; + break; + + case NGM_BT3C_NODE_OUT_QUEUE: + ((ng_bt3c_node_qlen_ep *)(rsp->data))->queue = + NGM_BT3C_NODE_OUT_QUEUE; + ((ng_bt3c_node_qlen_ep *)(rsp->data))->qlen = + sc->outq.ifq_maxlen; + break; + + default: + NG_FREE_MSG(rsp); + error = EINVAL; + break; + } + break; + + case NGM_BT3C_NODE_SET_QLEN: + if (msg->header.arglen != sizeof(ng_bt3c_node_qlen_ep)){ + error = EMSGSIZE; + break; + } + + if (((ng_bt3c_node_qlen_ep *)(msg->data))->qlen <= 0) { + error = EINVAL; + break; + } + + switch (((ng_bt3c_node_qlen_ep *)(msg->data))->queue) { + case NGM_BT3C_NODE_IN_QUEUE: + sc->inq.ifq_maxlen = ((ng_bt3c_node_qlen_ep *) + (msg->data))->qlen; /* XXX */ + break; + + case NGM_BT3C_NODE_OUT_QUEUE: + sc->outq.ifq_maxlen = ((ng_bt3c_node_qlen_ep *) + (msg->data))->qlen; /* XXX */ + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_BT3C_NODE_GET_STAT: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_stat_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + bcopy(&sc->stat, rsp->data, + sizeof(ng_bt3c_node_stat_ep)); + break; + + case NGM_BT3C_NODE_RESET_STAT: + NG_BT3C_STAT_RESET(sc->stat); + break; + + case NGM_BT3C_NODE_DOWNLOAD_FIRMWARE: + if (msg->header.arglen < + sizeof(ng_bt3c_firmware_block_ep)) + error = EMSGSIZE; + else + bt3c_download_firmware(sc, msg->data, + msg->header.arglen); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_bt3c_rcvmsg */ + +/* + * Process data + */ + +static int +ng_bt3c_rcvdata(hook_p hook, item_p item) +{ + bt3c_softc_p sc = (bt3c_softc_p)NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int error = 0; + + if (sc == NULL) { + error = EHOSTDOWN; + goto out; + } + + if (hook != sc->hook) { + error = EINVAL; + goto out; + } + + NGI_GET_M(item, m); + + IF_LOCK(&sc->outq); + if (_IF_QFULL(&sc->outq)) { + NG_BT3C_ERR(sc->dev, +"Outgoing queue is full. Dropping mbuf, len=%d\n", m->m_pkthdr.len); + + _IF_DROP(&sc->outq); + NG_BT3C_STAT_OERROR(sc->stat); + + NG_FREE_M(m); + } else + _IF_ENQUEUE(&sc->outq, m); + IF_UNLOCK(&sc->outq); + + error = ng_send_fn(sc->node, NULL, bt3c_send, NULL, 0 /* new send */); +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_bt3c_rcvdata */ + +/**************************************************************************** + **************************************************************************** + ** PCMCIA driver specific + **************************************************************************** + ****************************************************************************/ + +/* + * PC-Card (PCMCIA) match routine + */ + +static int +bt3c_pccard_match(device_t dev) +{ + static struct pccard_product const bt3c_pccard_products[] = { + PCMCIA_CARD(3COM, 3CRWB609, 0), + { NULL, } + }; + + struct pccard_product const *pp = NULL; + + pp = pccard_product_lookup(dev, bt3c_pccard_products, + sizeof(bt3c_pccard_products[0]), NULL); + if (pp == NULL) + return (EIO); + + device_set_desc(dev, pp->pp_name); + + return (0); +} /* bt3c_pccacd_match */ + +/* + * PC-Card (PCMCIA) probe routine + * XXX FIXME + */ + +static int +bt3c_pccard_probe(device_t dev) +{ + return (0); +} /* bt3c_pccacd_probe */ + +/* + * PC-Card (PCMCIA) attach routine + */ + +static int +bt3c_pccard_attach(device_t dev) +{ + bt3c_softc_p sc = NULL; + + sc = (bt3c_softc_p) malloc(sizeof(*sc), M_BT3C, M_NOWAIT|M_ZERO); + if (sc == NULL) + return (ENOMEM); + + /* Allocate I/O ports */ + sc->iobase_rid = 0; + sc->iobase = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->iobase_rid, + 0, ~0, 8, RF_ACTIVE); + if (sc->iobase == NULL) { + device_printf(dev, "Could not allocate I/O ports\n"); + goto bad; + } + + /* Allocate IRQ */ + sc->irq_rid = 0; + sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irq_rid, + 0, ~0, 1, RF_ACTIVE); + if (sc->irq == NULL) { + device_printf(dev, "Could not allocate IRQ\n"); + goto bad; + } + + sc->irq_cookie = NULL; + if (bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, bt3c_intr, sc, + &sc->irq_cookie) != 0) { + device_printf(dev, "Could not setup ISR\n"); + goto bad; + } + + /* Attach handler to TTY SWI thread */ + sc->ith = NULL; + if (swi_add(&tty_ithd, device_get_nameunit(dev), + bt3c_swi_intr, sc, SWI_TTY, 0, &sc->ith) < 0) { + device_printf(dev, "Could not setup SWI ISR\n"); + goto bad; + } + + /* Create Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->node) != 0) { + device_printf(dev, "Could not create Netgraph node\n"); + sc->node = NULL; + goto bad; + } + + /* Name Netgraph node */ + if (ng_name_node(sc->node, device_get_nameunit(dev)) != 0) { + device_printf(dev, "Could not name Netgraph node\n"); + NG_NODE_UNREF(sc->node); + sc->node = NULL; + goto bad; + } + + sc->dev = dev; + sc->debug = NG_BT3C_WARN_LEVEL; + + sc->inq.ifq_maxlen = sc->outq.ifq_maxlen = BT3C_DEFAULTQLEN; + mtx_init(&sc->inq.ifq_mtx, "BT3C inq", NULL, MTX_DEF); + mtx_init(&sc->outq.ifq_mtx, "BT3C outq", NULL, MTX_DEF); + + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + NG_NODE_SET_PRIVATE(sc->node, sc); + device_set_softc(dev, sc); + + return (0); +bad: + if (sc->ith != NULL) { + ithread_remove_handler(sc->ith); + sc->ith = NULL; + } + + if (sc->irq != NULL) { + if (sc->irq_cookie != NULL) + bus_teardown_intr(dev, sc->irq, sc->irq_cookie); + + bus_release_resource(dev, SYS_RES_IRQ, + sc->irq_rid, sc->irq); + + sc->irq = NULL; + sc->irq_rid = 0; + } + + if (sc->iobase != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, + sc->iobase_rid, sc->iobase); + + sc->iobase = NULL; + sc->iobase_rid = 0; + } + + free(sc, M_BT3C); + + return (ENXIO); +} /* bt3c_pccacd_attach */ + +/* + * PC-Card (PCMCIA) detach routine + */ + +static int +bt3c_pccard_detach(device_t dev) +{ + bt3c_softc_p sc = (bt3c_softc_p) device_get_softc(dev); + + if (sc == NULL) + return (0); + + device_set_softc(dev, NULL); + + ithread_remove_handler(sc->ith); + sc->ith = NULL; + + bus_teardown_intr(dev, sc->irq, sc->irq_cookie); + bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq); + sc->irq_cookie = NULL; + sc->irq = NULL; + sc->irq_rid = 0; + + bus_release_resource(dev, SYS_RES_IOPORT, sc->iobase_rid, sc->iobase); + sc->iobase = NULL; + sc->iobase_rid = 0; + + if (sc->node != NULL) { + NG_NODE_SET_PRIVATE(sc->node, NULL); + ng_rmnode_self(sc->node); + sc->node = NULL; + } + + NG_FREE_M(sc->m); + IF_DRAIN(&sc->inq); + IF_DRAIN(&sc->outq); + + mtx_destroy(&sc->inq.ifq_mtx); + mtx_destroy(&sc->outq.ifq_mtx); + + bzero(sc, sizeof(*sc)); + free(sc, M_BT3C); + + return (0); +} /* bt3c_pccacd_detach */ + +/* + * Interrupt service routine's + */ + +static void +bt3c_intr(void *context) +{ + bt3c_softc_p sc = (bt3c_softc_p) context; + u_int16_t control, status; + + if (sc == NULL || sc->ith == NULL) { + printf("%s: bogus interrupt\n", NG_BT3C_NODE_TYPE); + return; + } + + bt3c_read_control(sc, control); + if ((control & 0x80) == 0) + return; + + bt3c_read(sc, 0x7001, status); + NG_BT3C_INFO(sc->dev, "control=%#x, status=%#x\n", control, status); + + if ((status & 0xff) == 0x7f || (status & 0xff) == 0xff) { + NG_BT3C_WARN(sc->dev, "Strange status=%#x\n", status); + return; + } + + /* Receive complete */ + if (status & 0x0001) + bt3c_receive(sc); + + /* Record status and schedule SWI */ + sc->status |= status; + swi_sched(sc->ith, 0); + + /* Complete interrupt */ + bt3c_write(sc, 0x7001, 0x0000); + bt3c_write_control(sc, control); +} /* bt3c_intr */ + +/* + * Receive data + */ + +static void +bt3c_receive(bt3c_softc_p sc) +{ + u_int16_t i, count, c; + + /* Receive data from the card */ + bt3c_read(sc, 0x7006, count); + NG_BT3C_INFO(sc->dev, "The card has %d characters\n", count); + + bt3c_set_address(sc, 0x7480); + + for (i = 0; i < count; i++) { + /* Allocate new mbuf if needed */ + if (sc->m == NULL) { + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + MGETHDR(sc->m, M_DONTWAIT, MT_DATA); + if (sc->m == NULL) { + NG_BT3C_ERR(sc->dev, "Could not get mbuf\n"); + NG_BT3C_STAT_IERROR(sc->stat); + + break; /* XXX lost of sync */ + } + + sc->m->m_len = sc->m->m_pkthdr.len = 0; + } + + /* Read and append character to mbuf */ + bt3c_read_data(sc, c); + if (bt3c_append(sc->m, c) != 0) { + NG_FREE_M(sc->m); + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + break; /* XXX lost of sync */ + } + + NG_BT3C_INFO(sc->dev, +"Got char %#x, want=%d, got=%d\n", c, sc->want, sc->m->m_pkthdr.len); + + if (sc->m->m_pkthdr.len < sc->want) + continue; /* wait for more */ + + switch (sc->state) { + /* Got packet indicator */ + case NG_BT3C_W4_PKT_IND: + NG_BT3C_INFO(sc->dev, +"Got packet indicator %#x\n", *mtod(sc->m, u_int8_t *)); + + sc->state = NG_BT3C_W4_PKT_HDR; + + /* + * Since packet indicator included in the packet + * header just set sc->want to sizeof(packet header). + */ + + switch (*mtod(sc->m, u_int8_t *)) { + case NG_HCI_ACL_DATA_PKT: + sc->want = sizeof(ng_hci_acldata_pkt_t); + break; + + case NG_HCI_SCO_DATA_PKT: + sc->want = sizeof(ng_hci_scodata_pkt_t); + break; + + case NG_HCI_EVENT_PKT: + sc->want = sizeof(ng_hci_event_pkt_t); + break; + + default: + NG_BT3C_ERR(sc->dev, +"Ignoring unknown packet type=%#x\n", *mtod(sc->m, u_int8_t *)); + + NG_BT3C_STAT_IERROR(sc->stat); + + NG_FREE_M(sc->m); + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + break; + } + break; + + /* Got packet header */ + case NG_BT3C_W4_PKT_HDR: + sc->state = NG_BT3C_W4_PKT_DATA; + + switch (*mtod(sc->m, u_int8_t *)) { + case NG_HCI_ACL_DATA_PKT: + c = le16toh(mtod(sc->m, + ng_hci_acldata_pkt_t *)->length); + break; + + case NG_HCI_SCO_DATA_PKT: + c = mtod(sc->m, ng_hci_scodata_pkt_t*)->length; + break; + + case NG_HCI_EVENT_PKT: + c = mtod(sc->m, ng_hci_event_pkt_t *)->length; + break; + + default: + KASSERT(0, +("Invalid packet type=%#x\n", *mtod(sc->m, u_int8_t *))); + break; + } + + NG_BT3C_INFO(sc->dev, +"Got packet header, packet type=%#x, got so far %d, payload size=%d\n", + *mtod(sc->m, u_int8_t *), sc->m->m_pkthdr.len, + c); + + if (c > 0) { + sc->want += c; + break; + } + + /* else FALLTHROUGH and deliver frame */ + /* XXX is this true? should we deliver empty frame? */ + + /* Got packet data */ + case NG_BT3C_W4_PKT_DATA: + NG_BT3C_INFO(sc->dev, +"Got full packet, packet type=%#x, packet size=%d\n", + *mtod(sc->m, u_int8_t *), sc->m->m_pkthdr.len); + + NG_BT3C_STAT_BYTES_RECV(sc->stat, sc->m->m_pkthdr.len); + NG_BT3C_STAT_PCKTS_RECV(sc->stat); + + IF_LOCK(&sc->inq); + if (_IF_QFULL(&sc->inq)) { + NG_BT3C_ERR(sc->dev, +"Incoming queue is full. Dropping mbuf, len=%d\n", sc->m->m_pkthdr.len); + + _IF_DROP(&sc->inq); + NG_BT3C_STAT_IERROR(sc->stat); + + NG_FREE_M(sc->m); + } else { + _IF_ENQUEUE(&sc->inq, sc->m); + sc->m = NULL; + } + IF_UNLOCK(&sc->inq); + + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + break; + + default: + KASSERT(0, +("Invalid node state=%d", sc->state)); + break; + } + } + + bt3c_write(sc, 0x7006, 0x0000); +} /* bt3c_receive */ + +/* + * Append character to the mbuf. + * XXX assumes mbuf has header + * XXX does not handle external mbuf's + * XXX always appends char to the end of chain + */ + +static int +bt3c_append(struct mbuf *m0, int c) +{ + struct mbuf *m = m0; + int len; + + if (m0->m_next == NULL) + len = MHLEN; + else { + len = MLEN; + + while (m->m_next != NULL) + m = m->m_next; + } + + if (m->m_len >= len) { + MGET(m->m_next, M_DONTWAIT, m0->m_type); + if (m->m_next == NULL) + return (ENOBUFS); + + m = m->m_next; + m->m_len = 0; + } + + m->m_data[m->m_len ++] = (char) c; + m0->m_pkthdr.len ++; + + return (0); +} /* bt3c_append */ + +/* + * SWI interrupt handler + * Netgraph part is handled via ng_send_fn() to avoid race with hook + * connection/disconnection + */ + +static void +bt3c_swi_intr(void *context) +{ + bt3c_softc_p sc = (bt3c_softc_p) context; + u_int16_t data; + + /* Receive complete */ + if (sc->status & 0x0001) { + sc->status &= ~0x0001; /* XXX is it safe? */ + + if (ng_send_fn(sc->node, NULL, &bt3c_forward, NULL, 0) != 0) + NG_BT3C_ALERT(sc->dev, "Could not forward frames!\n"); + } + + /* Send complete */ + if (sc->status & 0x0002) { + sc->status &= ~0x0002; /* XXX is it safe */ + + if (ng_send_fn(sc->node, NULL, &bt3c_send, NULL, 1) != 0) + NG_BT3C_ALERT(sc->dev, "Could not send frames!\n"); + } + + /* Antenna position */ + if (sc->status & 0x0020) { + sc->status &= ~0x0020; /* XXX is it safe */ + + bt3c_read(sc, 0x7002, data); + data &= 0x10; + + if (data) + sc->flags |= BT3C_ANTENNA_OUT; + else + sc->flags &= ~BT3C_ANTENNA_OUT; + + NG_BT3C_INFO(sc->dev, "Antenna %s\n", data? "OUT" : "IN"); + } +} /* bt3c_swi_intr */ + +/* + * Send all incoming frames to the upper layer + */ + +static void +bt3c_forward(node_p node, hook_p hook, void *arg1, int arg2) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct mbuf *m = NULL; + int error; + + if (sc == NULL) + return; + + if (sc->hook != NULL && NG_HOOK_IS_VALID(sc->hook)) { + for (;;) { + IF_DEQUEUE(&sc->inq, m); + if (m == NULL) + break; + + NG_SEND_DATA_ONLY(error, sc->hook, m); + if (error != 0) + NG_BT3C_STAT_IERROR(sc->stat); + } + } else { + IF_LOCK(&sc->inq); + for (;;) { + _IF_DEQUEUE(&sc->inq, m); + if (m == NULL) + break; + + NG_BT3C_STAT_IERROR(sc->stat); + NG_FREE_M(m); + } + IF_UNLOCK(&sc->inq); + } +} /* bt3c_forward */ + +/* + * Send more data to the device. Must be called when node is locked + */ + +static void +bt3c_send(node_p node, hook_p hook, void *arg, int completed) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct mbuf *m = NULL; + int i, wrote, len; + + if (sc == NULL) + return; + + if (completed) + sc->flags &= ~BT3C_XMIT; + + if (sc->flags & BT3C_XMIT) + return; + + bt3c_set_address(sc, 0x7080); + + for (wrote = 0; wrote < BT3C_FIFO_SIZE; ) { + IF_DEQUEUE(&sc->outq, m); + if (m == NULL) + break; + + while (m != NULL) { + len = min((BT3C_FIFO_SIZE - wrote), m->m_len); + + for (i = 0; i < m->m_len; i++) + bt3c_write_data(sc, m->m_data[i]); + + wrote += len; + m->m_data += len; + m->m_len -= len; + + if (m->m_len > 0) + break; + + m = m_free(m); + } + + if (m != NULL) { + IF_PREPEND(&sc->outq, m); + break; + } + + NG_BT3C_STAT_PCKTS_SENT(sc->stat); + } + + if (wrote > 0) { + NG_BT3C_INFO(sc->dev, "Wrote %d bytes\n", wrote); + NG_BT3C_STAT_BYTES_SENT(sc->stat, wrote); + + bt3c_write(sc, 0x7005, wrote); + sc->flags |= BT3C_XMIT; + } +} /* bt3c_send */ + +/* + * Download chip firmware + */ + +static void +bt3c_download_firmware(bt3c_softc_p sc, char const *firmware, int firmware_size) +{ + ng_bt3c_firmware_block_ep const *block = NULL; + u_int16_t const *data = NULL; + int i, size; + u_int8_t c; + + /* Reset */ + device_printf(sc->dev, "Reseting the card...\n"); + bt3c_write(sc, 0x8040, 0x0404); + bt3c_write(sc, 0x8040, 0x0400); + DELAY(1); + + bt3c_write(sc, 0x8040, 0x0404); + DELAY(17); + + /* Download firmware */ + device_printf(sc->dev, "Starting firmware download process...\n"); + + for (size = 0; size < firmware_size; ) { + block = (ng_bt3c_firmware_block_ep const *)(firmware + size); + data = (u_int16_t const *)(block + 1); + + if (bootverbose) + device_printf(sc->dev, "Download firmware block, " \ + "address=%#08x, size=%d words, aligment=%d\n", + block->block_address, block->block_size, + block->block_alignment); + + bt3c_set_address(sc, block->block_address); + for (i = 0; i < block->block_size; i++) + bt3c_write_data(sc, data[i]); + + size += (sizeof(*block) + (block->block_size * 2) + + block->block_alignment); + } + + DELAY(17); + device_printf(sc->dev, "Firmware download process complete\n"); + + /* Boot */ + device_printf(sc->dev, "Starting the card...\n"); + bt3c_set_address(sc, 0x3000); + bt3c_read_control(sc, c); + bt3c_write_control(sc, (c | 0x40)); + DELAY(17); + + /* Clear registers */ + device_printf(sc->dev, "Clearing card registers...\n"); + bt3c_write(sc, 0x7006, 0x0000); + bt3c_write(sc, 0x7005, 0x0000); + bt3c_write(sc, 0x7001, 0x0000); + DELAY(1000); +} /* bt3c_download_firmware */ + +/**************************************************************************** + **************************************************************************** + ** Driver module + **************************************************************************** + ****************************************************************************/ + +/* + * PC-Card (PCMCIA) driver + */ + +static device_method_t bt3c_pccard_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pccard_compat_probe), + DEVMETHOD(device_attach, pccard_compat_attach), + DEVMETHOD(device_detach, bt3c_pccard_detach), + + /* Card interface */ + DEVMETHOD(card_compat_match, bt3c_pccard_match), + DEVMETHOD(card_compat_probe, bt3c_pccard_probe), + DEVMETHOD(card_compat_attach, bt3c_pccard_attach), + { 0, 0 } +}; + +static driver_t bt3c_pccard_driver = { + NG_BT3C_NODE_TYPE, + bt3c_pccard_methods, + 0 +}; + +static devclass_t bt3c_devclass; + +DRIVER_MODULE(bt3c, pccard, bt3c_pccard_driver, bt3c_devclass, 0, 0); + |