root/sys/dev/bluetooth/bcsp.c
/*      $NetBSD: bcsp.c,v 1.34 2024/07/05 04:31:50 rin Exp $    */
/*
 * Copyright (c) 2007 KIYOHARA Takashi
 * 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 ``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 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bcsp.c,v 1.34 2024/07/05 04:31:50 rin Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/callout.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/syslimits.h>
#include <sys/systm.h>
#include <sys/tty.h>

#include <netbt/bluetooth.h>
#include <netbt/hci.h>

#include <dev/bluetooth/bcsp.h>

#include "ioconf.h"

#ifdef BCSP_DEBUG
#ifdef DPRINTF
#undef DPRINTF
#endif
#ifdef DPRINTFN
#undef DPRINTFN
#endif

#define DPRINTF(x)      printf x
#define DPRINTFN(n, x)  do { if (bcsp_debug > (n)) printf x; } while (0)
int bcsp_debug = 3;
#else
#undef DPRINTF
#undef DPRINTFN

#define DPRINTF(x)
#define DPRINTFN(n, x)
#endif

struct bcsp_softc {
        device_t sc_dev;

        struct tty *sc_tp;
        struct hci_unit *sc_unit;               /* Bluetooth HCI Unit */
        struct bt_stats sc_stats;

        int sc_flags;

        /* output queues */
        MBUFQ_HEAD()    sc_cmdq;
        MBUFQ_HEAD()    sc_aclq;
        MBUFQ_HEAD()    sc_scoq;

        int sc_baud;
        int sc_init_baud;

        /* variables of SLIP Layer */
        struct mbuf *sc_txp;                    /* outgoing packet */
        struct mbuf *sc_rxp;                    /* incoming packet */
        int sc_slip_txrsv;                      /* reserved byte data */
        int sc_slip_rxexp;                      /* expected byte data */
        void (*sc_transmit_callback)(struct bcsp_softc *, struct mbuf *);

        /* variables of Packet Integrity Layer */
        int sc_pi_txcrc;                        /* use CRC, if true */

        /* variables of MUX Layer */
        bool sc_mux_send_ack;                   /* flag for send_ack */
        bool sc_mux_choke;                      /* Choke signal */
        struct timeval sc_mux_lastrx;           /* Last Rx Pkt Time */

        /* variables of Sequencing Layer */
        MBUFQ_HEAD() sc_seqq;                   /* Sequencing Layer queue */
        MBUFQ_HEAD() sc_seq_retryq;             /* retry queue */
        uint32_t sc_seq_txseq;
        uint32_t sc_seq_txack;
        uint32_t sc_seq_expected_rxseq;
        uint32_t sc_seq_winspace;
        uint32_t sc_seq_retries;
        callout_t sc_seq_timer;
        uint32_t sc_seq_timeout;
        uint32_t sc_seq_winsize;
        uint32_t sc_seq_retry_limit;

        /* variables of Datagram Queue Layer */
        MBUFQ_HEAD() sc_dgq;                    /* Datagram Queue Layer queue */

        /* variables of BCSP Link Establishment Protocol */
        bool sc_le_muzzled;
        bcsp_le_state_t sc_le_state;
        callout_t sc_le_timer;

        struct sysctllog *sc_log;               /* sysctl log */
};

/* sc_flags */
#define BCSP_XMIT       (1 << 0)        /* transmit active */
#define BCSP_ENABLED    (1 << 1)        /* is enabled */

static int bcsp_match(device_t, cfdata_t, void *);
static void bcsp_attach(device_t, device_t, void *);
static int bcsp_detach(device_t, int);

/* tty functions */
static int bcspopen(dev_t, struct tty *);
static int bcspclose(struct tty *, int);
static int bcspioctl(struct tty *, u_long, void *, int, struct lwp *);

static int bcsp_slip_transmit(struct tty *);
static int bcsp_slip_receive(int, struct tty *);

static void bcsp_pktintegrity_transmit(struct bcsp_softc *);
static void bcsp_pktintegrity_receive(struct bcsp_softc *, struct mbuf *);
static void bcsp_crc_update(uint16_t *, uint8_t);
static uint16_t bcsp_crc_reverse(uint16_t);

static void bcsp_mux_transmit(struct bcsp_softc *sc);
static void bcsp_mux_receive(struct bcsp_softc *sc, struct mbuf *m);
static __inline void bcsp_send_ack_command(struct bcsp_softc *sc);
static __inline struct mbuf *bcsp_create_ackpkt(void);
static __inline void bcsp_set_choke(struct bcsp_softc *, bool);

static void bcsp_sequencing_receive(struct bcsp_softc *, struct mbuf *);
static bool bcsp_tx_reliable_pkt(struct bcsp_softc *, struct mbuf *, u_int);
static __inline u_int bcsp_get_txack(struct bcsp_softc *);
static void bcsp_signal_rxack(struct bcsp_softc *, uint32_t);
static void bcsp_reliabletx_callback(struct bcsp_softc *, struct mbuf *);
static void bcsp_timer_timeout(void *);
static void bcsp_sequencing_reset(struct bcsp_softc *);

static void bcsp_datagramq_receive(struct bcsp_softc *, struct mbuf *);
static bool bcsp_tx_unreliable_pkt(struct bcsp_softc *, struct mbuf *, u_int);
static void bcsp_unreliabletx_callback(struct bcsp_softc *, struct mbuf *);

static int bcsp_start_le(struct bcsp_softc *);
static void bcsp_terminate_le(struct bcsp_softc *);
static void bcsp_input_le(struct bcsp_softc *, struct mbuf *);
static void bcsp_le_timeout(void *);

static void bcsp_start(struct bcsp_softc *);

/* bluetooth hci functions */
static int bcsp_enable(device_t);
static void bcsp_disable(device_t);
static void bcsp_output_cmd(device_t, struct mbuf *);
static void bcsp_output_acl(device_t, struct mbuf *);
static void bcsp_output_sco(device_t, struct mbuf *);
static void bcsp_stats(device_t, struct bt_stats *, int);

#ifdef BCSP_DEBUG
static void bcsp_packet_print(struct mbuf *m);
#endif


/*
 * It doesn't need to be exported, as only bcspattach() uses it,
 * but there's no "official" way to make it static.
 */
CFATTACH_DECL_NEW(bcsp, sizeof(struct bcsp_softc),
    bcsp_match, bcsp_attach, bcsp_detach, NULL);

static struct linesw bcsp_disc = {
        .l_name = "bcsp",
        .l_open = bcspopen,
        .l_close = bcspclose,
        .l_read = ttyerrio,
        .l_write = ttyerrio,
        .l_ioctl = bcspioctl,
        .l_rint = bcsp_slip_receive,
        .l_start = bcsp_slip_transmit,
        .l_modem = ttymodem,
        .l_poll = ttyerrpoll
};

static const struct hci_if bcsp_hci = {
        .enable = bcsp_enable,
        .disable = bcsp_disable,
        .output_cmd = bcsp_output_cmd,
        .output_acl = bcsp_output_acl,
        .output_sco = bcsp_output_sco,
        .get_stats = bcsp_stats,
        .ipl = IPL_TTY,
};

/* ARGSUSED */
void
bcspattach(int num __unused)
{
        int error;

        error = ttyldisc_attach(&bcsp_disc);
        if (error) {
                aprint_error("%s: unable to register line discipline, "
                    "error = %d\n", bcsp_cd.cd_name, error);
                return;
        }

        error = config_cfattach_attach(bcsp_cd.cd_name, &bcsp_ca);
        if (error) {
                aprint_error("%s: unable to register cfattach, error = %d\n",
                    bcsp_cd.cd_name, error);
                config_cfdriver_detach(&bcsp_cd);
                (void) ttyldisc_detach(&bcsp_disc);
        }
}

/*
 * Autoconf match routine.
 *
 * XXX: unused: config_attach_pseudo(9) does not call ca_match.
 */
/* ARGSUSED */
static int
bcsp_match(device_t self __unused, cfdata_t cfdata __unused,
           void *arg __unused)
{

        /* pseudo-device; always present */
        return 1;
}

/*
 * Autoconf attach routine.  Called by config_attach_pseudo(9) when we
 * open the line discipline.
 */
/* ARGSUSED */
static void
bcsp_attach(device_t parent __unused, device_t self, void *aux __unused)
{
        struct bcsp_softc *sc = device_private(self);
        const struct sysctlnode *node;
        int rc, bcsp_node_num;

        aprint_normal("\n");
        aprint_naive("\n");

        sc->sc_dev = self;
        callout_init(&sc->sc_seq_timer, 0);
        callout_setfunc(&sc->sc_seq_timer, bcsp_timer_timeout, sc);
        callout_init(&sc->sc_le_timer, 0);
        callout_setfunc(&sc->sc_le_timer, bcsp_le_timeout, sc);
        sc->sc_seq_timeout = BCSP_SEQ_TX_TIMEOUT;
        sc->sc_seq_winsize = BCSP_SEQ_TX_WINSIZE;
        sc->sc_seq_retry_limit = BCSP_SEQ_TX_RETRY_LIMIT;
        MBUFQ_INIT(&sc->sc_seqq);
        MBUFQ_INIT(&sc->sc_seq_retryq);
        MBUFQ_INIT(&sc->sc_dgq);
        MBUFQ_INIT(&sc->sc_cmdq);
        MBUFQ_INIT(&sc->sc_aclq);
        MBUFQ_INIT(&sc->sc_scoq);

        /* Attach Bluetooth unit */
        sc->sc_unit = hci_attach_pcb(&bcsp_hci, self, 0);

        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            0, CTLTYPE_NODE, device_xname(self),
            SYSCTL_DESCR("bcsp controls"),
            NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        bcsp_node_num = node->sysctl_num;
        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            CTLFLAG_READWRITE, CTLTYPE_BOOL,
            "muzzled", SYSCTL_DESCR("muzzled for Link-establishment Layer"),
            NULL, 0, &sc->sc_le_muzzled,
            0, CTL_HW, bcsp_node_num, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            CTLFLAG_READWRITE, CTLTYPE_INT,
            "txcrc", SYSCTL_DESCR("txcrc for Packet Integrity Layer"),
            NULL, 0, &sc->sc_pi_txcrc,
            0, CTL_HW, bcsp_node_num, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            CTLFLAG_READWRITE, CTLTYPE_INT,
            "timeout", SYSCTL_DESCR("timeout for Sequencing Layer"),
            NULL, 0, &sc->sc_seq_timeout,
            0, CTL_HW, bcsp_node_num, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            CTLFLAG_READWRITE, CTLTYPE_INT,
            "winsize", SYSCTL_DESCR("winsize for Sequencing Layer"),
            NULL, 0, &sc->sc_seq_winsize,
            0, CTL_HW, bcsp_node_num, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        if ((rc = sysctl_createv(&sc->sc_log, 0, NULL, &node,
            CTLFLAG_READWRITE, CTLTYPE_INT,
            "retry_limit", SYSCTL_DESCR("retry limit for Sequencing Layer"),
            NULL, 0, &sc->sc_seq_retry_limit,
            0, CTL_HW, bcsp_node_num, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }
        return;

err:
        aprint_error_dev(self, "sysctl_createv failed (rc = %d)\n", rc);
}

/*
 * Autoconf detach routine.  Called when we close the line discipline.
 */
/* ARGSUSED */
static int
bcsp_detach(device_t self, int flags __unused)
{
        struct bcsp_softc *sc = device_private(self);

        if (sc->sc_unit != NULL) {
                hci_detach_pcb(sc->sc_unit);
                sc->sc_unit = NULL;
        }

        callout_halt(&sc->sc_seq_timer, NULL);
        callout_destroy(&sc->sc_seq_timer);

        callout_halt(&sc->sc_le_timer, NULL);
        callout_destroy(&sc->sc_le_timer);

        return 0;
}


/*
 * Line discipline functions.
 */
/* ARGSUSED */
static int
bcspopen(dev_t device __unused, struct tty *tp)
{
        struct bcsp_softc *sc;
        device_t dev;
        cfdata_t cfdata;
        struct lwp *l = curlwp;         /* XXX */
        int error, unit, s;
        static char name[] = "bcsp";

        error = kauth_authorize_device(l->l_cred, KAUTH_DEVICE_BLUETOOTH_BCSP,
            KAUTH_ARG(KAUTH_REQ_DEVICE_BLUETOOTH_BCSP_ADD), NULL, NULL, NULL);
        if (error)
                return (error);

        s = spltty();

        if (tp->t_linesw == &bcsp_disc) {
                sc = tp->t_sc;
                if (sc != NULL) {
                        splx(s);
                        return EBUSY;
                }
        }

        cfdata = malloc(sizeof(struct cfdata), M_DEVBUF, M_WAITOK);
        for (unit = 0; unit < bcsp_cd.cd_ndevs; unit++)
                if (device_lookup(&bcsp_cd, unit) == NULL)
                        break;
        cfdata->cf_name = name;
        cfdata->cf_atname = name;
        cfdata->cf_unit = unit;
        cfdata->cf_fstate = FSTATE_STAR;

        aprint_normal("%s%d at tty major %llu minor %llu",
            name, unit, (unsigned long long)major(tp->t_dev),
            (unsigned long long)minor(tp->t_dev));
        dev = config_attach_pseudo(cfdata);
        if (dev == NULL) {
                splx(s);
                return EIO;
        }
        sc = device_private(dev);

        ttylock(tp);
        tp->t_sc = sc;
        sc->sc_tp = tp;
        ttyflush(tp, FREAD | FWRITE);
        ttyunlock(tp);

        splx(s);

        sc->sc_slip_txrsv = BCSP_SLIP_PKTSTART;
        bcsp_sequencing_reset(sc);

        /* start link-establishment */
        bcsp_start_le(sc);

        return 0;
}

/* ARGSUSED */
static int
bcspclose(struct tty *tp, int flag __unused)
{
        struct bcsp_softc *sc = tp->t_sc;
        cfdata_t cfdata;
        int s;

        /* terminate link-establishment */
        bcsp_terminate_le(sc);

        s = spltty();

        MBUFQ_DRAIN(&sc->sc_dgq);
        bcsp_sequencing_reset(sc);

        ttylock(tp);
        ttyflush(tp, FREAD | FWRITE);
        ttyunlock(tp);  /* XXX */
        ttyldisc_release(tp->t_linesw);
        tp->t_linesw = ttyldisc_default();
        if (sc != NULL) {
                tp->t_sc = NULL;
                if (sc->sc_tp == tp) {
                        cfdata = device_cfdata(sc->sc_dev);
                        config_detach(sc->sc_dev, 0);
                        free(cfdata, M_DEVBUF);
                }

        }
        splx(s);
        return 0;
}

/* ARGSUSED */
static int
bcspioctl(struct tty *tp, u_long cmd, void *data, int flag __unused,
          struct lwp *l __unused)
{
        struct bcsp_softc *sc = tp->t_sc;
        int error;

        /*
         * XXX
         * This function can be called without KERNEL_LOCK when caller's
         * struct cdevsw is set D_MPSAFE. Is KERNEL_LOCK required?
         */

        if (sc == NULL || tp != sc->sc_tp)
                return EPASSTHROUGH;

        error = 0;
        switch (cmd) {
        default:
                error = EPASSTHROUGH;
                break;
        }

        return error;
}


/*
 * UART Driver Layer is supported by com-driver.
 */

/*
 * BCSP SLIP Layer functions:
 *   Supports to transmit/receive a byte stream.
 *   SLIP protocol described in Internet standard RFC 1055.
 */
static int
bcsp_slip_transmit(struct tty *tp)
{
        struct bcsp_softc *sc = tp->t_sc;
        struct mbuf *m;
        int count, rlen;
        uint8_t *rptr;

        m = sc->sc_txp;
        if (m == NULL) {
                sc->sc_flags &= ~BCSP_XMIT;
                bcsp_mux_transmit(sc);
                return 0;
        }

        count = 0;
        rlen = 0;
        rptr = mtod(m, uint8_t *);

        if (sc->sc_slip_txrsv != 0) {
#ifdef BCSP_DEBUG
                if (sc->sc_slip_txrsv == BCSP_SLIP_PKTSTART)
                        DPRINTFN(4, ("%s: slip transmit start\n",
                            device_xname(sc->sc_dev)));
                else
                        DPRINTFN(4, ("0x%02x ", sc->sc_slip_txrsv));
#endif

                if (putc(sc->sc_slip_txrsv, &tp->t_outq) < 0)
                        return 0;
                count++;

                if (sc->sc_slip_txrsv == BCSP_SLIP_ESCAPE_PKTEND ||
                    sc->sc_slip_txrsv == BCSP_SLIP_ESCAPE_ESCAPE) {
                        rlen++;
                        rptr++;
                }
                sc->sc_slip_txrsv = 0;
        }

        for(;;) {
                if (rlen >= m->m_len) {
                        m = m->m_next;
                        if (m == NULL) {
                                if (putc(BCSP_SLIP_PKTEND, &tp->t_outq) < 0)
                                        break;

                                DPRINTFN(4, ("\n%s: slip transmit end\n",
                                    device_xname(sc->sc_dev)));

                                m = sc->sc_txp;
                                sc->sc_txp = NULL;
                                sc->sc_slip_txrsv = BCSP_SLIP_PKTSTART;

                                sc->sc_transmit_callback(sc, m);
                                m = NULL;
                                break;
                        }

                        rlen = 0;
                        rptr = mtod(m, uint8_t *);
                        continue;
                }

                if (*rptr == BCSP_SLIP_PKTEND) {
                        if (putc(BCSP_SLIP_ESCAPE, &tp->t_outq) < 0)
                                break;
                        count++;
                        DPRINTFN(4, (" esc "));

                        if (putc(BCSP_SLIP_ESCAPE_PKTEND, &tp->t_outq) < 0) {
                                sc->sc_slip_txrsv = BCSP_SLIP_ESCAPE_PKTEND;
                                break;
                        }
                        DPRINTFN(4, ("0x%02x ", BCSP_SLIP_ESCAPE_PKTEND));
                        rptr++;
                } else if (*rptr == BCSP_SLIP_ESCAPE) {
                        if (putc(BCSP_SLIP_ESCAPE, &tp->t_outq) < 0)
                                break;
                        count++;
                        DPRINTFN(4, (" esc "));

                        if (putc(BCSP_SLIP_ESCAPE_ESCAPE, &tp->t_outq) < 0) {
                                sc->sc_slip_txrsv = BCSP_SLIP_ESCAPE_ESCAPE;
                                break;
                        }
                        DPRINTFN(4, ("0x%02x ", BCSP_SLIP_ESCAPE_ESCAPE));
                        rptr++;
                } else {
                        if (putc(*rptr++, &tp->t_outq) < 0)
                                break;
                        DPRINTFN(4, ("0x%02x ", *(rptr - 1)));
                }
                rlen++;
                count++;
        }
        if (m != NULL)
                m_adj(m, rlen);

        sc->sc_stats.byte_tx += count;

        if (tp->t_outq.c_cc != 0 && tp->t_oproc != NULL)
                (*tp->t_oproc)(tp);

        return 0;
}

static int
bcsp_slip_receive(int c, struct tty *tp)
{
        struct bcsp_softc *sc = tp->t_sc;
        struct mbuf *m = sc->sc_rxp;
        int discard = 0;
        const char *errstr;

        c &= TTY_CHARMASK;

        /* If we already started a packet, find the trailing end of it. */
        if (m) {
                while (m->m_next)
                        m = m->m_next;

                if (M_TRAILINGSPACE(m) == 0) {
                        /* extend mbuf */
                        MGET(m->m_next, M_DONTWAIT, MT_DATA);
                        if (m->m_next == NULL) {
                                aprint_error_dev(sc->sc_dev,
                                    "out of memory\n");
                                sc->sc_stats.err_rx++;
                                return 0;       /* (lost sync) */
                        }

                        m = m->m_next;
                        m->m_len = 0;
                }
        } else
                if (c != BCSP_SLIP_PKTSTART) {
                        discard = 1;
                        errstr = "not sync";
                        goto discarded;
                }

        switch (c) {
        case BCSP_SLIP_PKTSTART /* or _PKTEND */:
                if (m == NULL) {
                        /* BCSP_SLIP_PKTSTART */

                        DPRINTFN(4, ("%s: slip receive start\n",
                            device_xname(sc->sc_dev)));

                        /* new packet */
                        MGETHDR(m, M_DONTWAIT, MT_DATA);
                        if (m == NULL) {
                                aprint_error_dev(sc->sc_dev,
                                    "out of memory\n");
                                sc->sc_stats.err_rx++;
                                return 0;       /* (lost sync) */
                        }

                        sc->sc_rxp = m;
                        m->m_pkthdr.len = m->m_len = 0;
                        sc->sc_slip_rxexp = 0;
                } else {
                        /* BCSP_SLIP_PKTEND */

                        if (m == sc->sc_rxp && m->m_len == 0) {
                                DPRINTFN(4, ("%s: resynchronises\n",
                                    device_xname(sc->sc_dev)));

                                sc->sc_stats.byte_rx++;
                                return 0;
                        }

                        DPRINTFN(4, ("%s%s: slip receive end\n",
                            (m->m_len % 16 != 0) ? "\n" :  "",
                            device_xname(sc->sc_dev)));

                        bcsp_pktintegrity_receive(sc, sc->sc_rxp);
                        sc->sc_rxp = NULL;
                        sc->sc_slip_rxexp = BCSP_SLIP_PKTSTART;
                }
                sc->sc_stats.byte_rx++;
                return 0;

        case BCSP_SLIP_ESCAPE:

                DPRINTFN(4, ("  esc"));

                if (sc->sc_slip_rxexp == BCSP_SLIP_ESCAPE) {
                        discard = 1;
                        errstr = "waiting 0xdc or 0xdb"; 
                } else
                        sc->sc_slip_rxexp = BCSP_SLIP_ESCAPE;
                break;

        default:
                DPRINTFN(4, (" 0x%02x%s",
                    c, (m->m_len % 16 == 15) ? "\n" :  ""));

                switch (sc->sc_slip_rxexp) {
                case BCSP_SLIP_PKTSTART:
                        discard = 1;
                        errstr = "waiting 0xc0";
                        break;

                case BCSP_SLIP_ESCAPE:
                        if (c == BCSP_SLIP_ESCAPE_PKTEND)
                                mtod(m, uint8_t *)[m->m_len++] =
                                    BCSP_SLIP_PKTEND;
                        else if (c == BCSP_SLIP_ESCAPE_ESCAPE)
                                mtod(m, uint8_t *)[m->m_len++] =
                                    BCSP_SLIP_ESCAPE;
                        else {
                                discard = 1;
                                errstr = "unknown escape";
                        }
                        sc->sc_slip_rxexp = 0;
                        break;

                default:
                        mtod(m, uint8_t *)[m->m_len++] = c;
                }
                sc->sc_rxp->m_pkthdr.len++;
        }
        if (discard) {
discarded:
#ifdef BCSP_DEBUG
                DPRINTFN(4, ("%s: receives unexpected byte 0x%02x: %s\n",
                    device_xname(sc->sc_dev), c, errstr));
#else
                __USE(errstr);
#endif
        }
        sc->sc_stats.byte_rx++;

        return 0;
}


/*
 * BCSP Packet Integrity Layer functions:
 *   handling Payload Length, Checksum, CRC.
 */
static void
bcsp_pktintegrity_transmit(struct bcsp_softc *sc)
{
        struct mbuf *m = sc->sc_txp;
        bcsp_hdr_t *hdrp = mtod(m, bcsp_hdr_t *);
        int pldlen;

        DPRINTFN(3, ("%s: pi transmit\n", device_xname(sc->sc_dev)));

        pldlen = m->m_pkthdr.len - sizeof(bcsp_hdr_t);

        if (sc->sc_pi_txcrc)
                hdrp->flags |= BCSP_FLAGS_CRC_PRESENT;

        BCSP_SET_PLEN(hdrp, pldlen);
        BCSP_SET_CSUM(hdrp);

        if (sc->sc_pi_txcrc) {
                struct mbuf *_m;
                int n = 0;
                uint16_t crc = 0xffff;
                uint8_t *buf;

                for (_m = m; _m != NULL; _m = _m->m_next) {
                        buf = mtod(_m, uint8_t *);
                        for (n = 0; n < _m->m_len; n++)
                                bcsp_crc_update(&crc, *(buf + n));
                }
                crc = htobe16(bcsp_crc_reverse(crc));
                m_copyback(m, m->m_pkthdr.len, sizeof(crc), &crc);
        }

#ifdef BCSP_DEBUG
        if (bcsp_debug == 4)
                bcsp_packet_print(m);
#endif

        bcsp_slip_transmit(sc->sc_tp);
}

static void
bcsp_pktintegrity_receive(struct bcsp_softc *sc, struct mbuf *m)
{
        bcsp_hdr_t *hdrp = mtod(m, bcsp_hdr_t *);
        u_int pldlen;
        int discard = 0;
        uint16_t crc = 0xffff;
        const char *errstr;

        DPRINTFN(3, ("%s: pi receive\n", device_xname(sc->sc_dev)));
#ifdef BCSP_DEBUG
        if (bcsp_debug == 4)
                bcsp_packet_print(m);
#endif

        KASSERT(m->m_len >= sizeof(bcsp_hdr_t));

        pldlen = m->m_pkthdr.len - sizeof(bcsp_hdr_t) -
            ((hdrp->flags & BCSP_FLAGS_CRC_PRESENT) ? sizeof(crc) : 0);
        if (pldlen > 0xfff) {
                discard = 1;
                errstr = "Payload Length";
                goto discarded;
        }
        if (hdrp->csum != BCSP_GET_CSUM(hdrp)) {
                discard = 1;
                errstr = "Checksum";
                goto discarded;
        }
        if (BCSP_GET_PLEN(hdrp) != pldlen) {
                discard = 1;
                errstr = "Payload Length";
                goto discarded;
        }
        if (hdrp->flags & BCSP_FLAGS_CRC_PRESENT) {
                struct mbuf *_m;
                int i, n;
                uint16_t crc0;
                uint8_t *buf;

                i = 0;
                n = 0;
                for (_m = m; _m != NULL; _m = _m->m_next) {
                        buf = mtod(m, uint8_t *);
                        for (n = 0;
                            n < _m->m_len && i < sizeof(bcsp_hdr_t) + pldlen;
                            n++, i++)
                                bcsp_crc_update(&crc, *(buf + n));
                }

                m_copydata(_m, n, sizeof(crc0), &crc0);
                if (be16toh(crc0) != bcsp_crc_reverse(crc)) {
                        discard = 1;
                        errstr = "CRC";
                } else
                        /* Shaves CRC */
                        m_adj(m, (int)(0 - sizeof(crc)));
        }

        if (discard) {
discarded:
#ifdef BCSP_DEBUG
                DPRINTFN(3, ("%s: receives unexpected packet: %s\n",
                    device_xname(sc->sc_dev), errstr));
#else
                __USE(errstr);
#endif
                m_freem(m);
        } else
                bcsp_mux_receive(sc, m);
}

static const uint16_t crctbl[] = {
        0x0000, 0x1081, 0x2102, 0x3183,
        0x4204, 0x5285, 0x6306, 0x7387,
        0x8408, 0x9489, 0xa50a, 0xb58b,
        0xc60c, 0xd68d, 0xe70e, 0xf78f,
};

static void
bcsp_crc_update(uint16_t *crc, uint8_t d)
{
        uint16_t reg = *crc;

        reg = (reg >> 4) ^ crctbl[(reg ^ d) & 0x000f];
        reg = (reg >> 4) ^ crctbl[(reg ^ (d >> 4)) & 0x000f];

        *crc = reg;
}

static uint16_t
bcsp_crc_reverse(uint16_t crc)
{
        uint16_t b, rev;

        for (b = 0, rev = 0; b < 16; b++) {
                rev = rev << 1;
                rev |= (crc & 1);
                crc = crc >> 1;
        }

        return rev;
}


/*
 * BCSP MUX Layer functions
 */
static void
bcsp_mux_transmit(struct bcsp_softc *sc)
{
        struct mbuf *m;
        bcsp_hdr_t *hdrp;

        DPRINTFN(2, ("%s: mux transmit: sc_flags=0x%x, choke=%d",
            device_xname(sc->sc_dev), sc->sc_flags, sc->sc_mux_choke));

        if (sc->sc_mux_choke) {
                struct mbuf *_m = NULL;

                /* In this case, send only Link Establishment packet */
                for (m = MBUFQ_FIRST(&sc->sc_dgq); m != NULL;
                    _m = m, m = MBUFQ_NEXT(m)) {
                        hdrp = mtod(m, bcsp_hdr_t *);
                        if (hdrp->ident == BCSP_CHANNEL_LE) {
                                if (m == MBUFQ_FIRST(&sc->sc_dgq))
                                        MBUFQ_DEQUEUE(&sc->sc_dgq, m);
                                else {
                                        if (m->m_nextpkt == NULL)
                                                sc->sc_dgq.mq_last =
                                                    &_m->m_nextpkt;
                                        _m->m_nextpkt = m->m_nextpkt;
                                        m->m_nextpkt = NULL;
                                }
                                goto transmit;
                        }
                }
                DPRINTFN(2, ("\n"));
                return;
        }

        /*
         * The MUX Layer always gives priority to packets from the Datagram
         * Queue Layer over the Sequencing Layer.
         */
        if (MBUFQ_FIRST(&sc->sc_dgq)) {
                MBUFQ_DEQUEUE(&sc->sc_dgq, m);
                goto transmit;
        }
        if (MBUFQ_FIRST(&sc->sc_seqq)) {
                MBUFQ_DEQUEUE(&sc->sc_seqq, m);
                hdrp = mtod(m, bcsp_hdr_t *);
                hdrp->flags |= BCSP_FLAGS_PROTOCOL_REL;         /* Reliable */
                goto transmit;
        }
        bcsp_start(sc);
        if (sc->sc_mux_send_ack == true) {
                m = bcsp_create_ackpkt();
                if (m != NULL)
                        goto transmit;
                aprint_error_dev(sc->sc_dev, "out of memory\n");
                sc->sc_stats.err_tx++;
        }

        /* Nothing to send */
        DPRINTFN(2, ("\n"));
        return;

transmit:
        DPRINTFN(2, (", txack=%d, send_ack=%d\n",
            bcsp_get_txack(sc), sc->sc_mux_send_ack));

        hdrp = mtod(m, bcsp_hdr_t *);
        hdrp->flags |=
            (bcsp_get_txack(sc) << BCSP_FLAGS_ACK_SHIFT) & BCSP_FLAGS_ACK_MASK;
        if (sc->sc_mux_send_ack == true)
                sc->sc_mux_send_ack = false;

#ifdef BCSP_DEBUG
        if (bcsp_debug == 3)
                bcsp_packet_print(m);
#endif

        sc->sc_txp = m;
        bcsp_pktintegrity_transmit(sc);
}

static void
bcsp_mux_receive(struct bcsp_softc *sc, struct mbuf *m)
{
        bcsp_hdr_t *hdrp = mtod(m, bcsp_hdr_t *);
        const u_int rxack = BCSP_FLAGS_ACK(hdrp->flags);

        DPRINTFN(2, ("%s: mux receive: flags=0x%x, ident=%d, rxack=%d\n",
            device_xname(sc->sc_dev), hdrp->flags, hdrp->ident, rxack));
#ifdef BCSP_DEBUG
        if (bcsp_debug == 3)
                bcsp_packet_print(m);
#endif

        bcsp_signal_rxack(sc, rxack);

        microtime(&sc->sc_mux_lastrx);

        /* if the Ack Packet received then discard */
        if (BCSP_FLAGS_SEQ(hdrp->flags) == 0 &&
            hdrp->ident == BCSP_IDENT_ACKPKT &&
            BCSP_GET_PLEN(hdrp) == 0) {
                m_freem(m);
                return;
        }

        if (hdrp->flags & BCSP_FLAGS_PROTOCOL_REL)
                bcsp_sequencing_receive(sc, m);
        else
                bcsp_datagramq_receive(sc, m);
}

static __inline void
bcsp_send_ack_command(struct bcsp_softc *sc)
{

        DPRINTFN(2, ("%s: mux send_ack_command\n", device_xname(sc->sc_dev)));

        sc->sc_mux_send_ack = true;
}

static __inline struct mbuf *
bcsp_create_ackpkt(void)
{
        struct mbuf *m;
        bcsp_hdr_t *hdrp;

        MGETHDR(m, M_DONTWAIT, MT_DATA);
        if (m != NULL) {
                m->m_pkthdr.len = m->m_len = sizeof(bcsp_hdr_t);
                hdrp = mtod(m, bcsp_hdr_t *);
                /*
                 * An Ack Packet has the following fields:
                 *      Ack Field:                      txack (not set yet)
                 *      Seq Field:                      0
                 *      Protocol Identifier Field:      0
                 *      Protocol Type Field:            Any value
                 *      Payload Length Field:           0
                 */
                memset(hdrp, 0, sizeof(bcsp_hdr_t));
        }
        return m;
}

static __inline void
bcsp_set_choke(struct bcsp_softc *sc, bool choke)
{

        DPRINTFN(2, ("%s: mux set choke=%d\n", device_xname(sc->sc_dev), choke));

        sc->sc_mux_choke = choke;
}


/*
 * BCSP Sequencing Layer functions
 */
static void
bcsp_sequencing_receive(struct bcsp_softc *sc, struct mbuf *m)
{
        bcsp_hdr_t hdr;
        uint32_t rxseq;

        m_copydata(m, 0, sizeof(bcsp_hdr_t), &hdr);
        rxseq = BCSP_FLAGS_SEQ(hdr.flags);

        DPRINTFN(1, ("%s: seq receive: rxseq=%d, expected %d\n",
            device_xname(sc->sc_dev), rxseq, sc->sc_seq_expected_rxseq));
#ifdef BCSP_DEBUG
        if (bcsp_debug == 2)
                bcsp_packet_print(m);
#endif

        /*
         * We remove the header of BCSP and add the 'uint8_t type' of
         * hci_*_hdr_t to the head. 
         */
        m_adj(m, sizeof(bcsp_hdr_t) - sizeof(uint8_t));

        if (rxseq != sc->sc_seq_expected_rxseq) {
                m_freem(m);

                /* send ack packet, if needly */
                bcsp_mux_transmit(sc);

                return;
        }

        switch (hdr.ident) {
        case BCSP_CHANNEL_HCI_CMDEVT:
                *(mtod(m, uint8_t *)) = HCI_EVENT_PKT;
                if (!hci_input_event(sc->sc_unit, m))
                        sc->sc_stats.err_rx++;

                sc->sc_stats.evt_rx++;
                break;

        case BCSP_CHANNEL_HCI_ACL:
                *(mtod(m, uint8_t *)) = HCI_ACL_DATA_PKT;
                if (!hci_input_acl(sc->sc_unit, m))
                        sc->sc_stats.err_rx++;

                sc->sc_stats.acl_rx++;
                break;

        case BCSP_CHANNEL_HCI_SCO:
                *(mtod(m, uint8_t *)) = HCI_SCO_DATA_PKT;
                if (!hci_input_sco(sc->sc_unit, m))
                        sc->sc_stats.err_rx++;

                sc->sc_stats.sco_rx++;
                break;

        case BCSP_CHANNEL_HQ:
        case BCSP_CHANNEL_DEVMGT:
        case BCSP_CHANNEL_L2CAP:
        case BCSP_CHANNEL_RFCOMM:
        case BCSP_CHANNEL_SDP:
        case BCSP_CHANNEL_DFU:
        case BCSP_CHANNEL_VM:
        default:
                aprint_error_dev(sc->sc_dev,
                    "received reliable packet with not support channel %d\n",
                    hdr.ident);
                m_freem(m);
                break;
        }

        sc->sc_seq_expected_rxseq =
            (sc->sc_seq_expected_rxseq + 1) & BCSP_FLAGS_SEQ_MASK;
        sc->sc_seq_txack = sc->sc_seq_expected_rxseq;
        bcsp_send_ack_command(sc);
}

static bool
bcsp_tx_reliable_pkt(struct bcsp_softc *sc, struct mbuf *m, u_int protocol_id)
{
        bcsp_hdr_t *hdrp;
        struct mbuf *_m;
        u_int pldlen;
        int s;

        DPRINTFN(1, ("%s: seq transmit:"
            "protocol_id=%d, winspace=%d, txseq=%d\n", device_xname(sc->sc_dev),
            protocol_id, sc->sc_seq_winspace, sc->sc_seq_txseq));

        for (pldlen = 0, _m = m; _m != NULL; _m = _m->m_next) {
                if (_m->m_len < 0)
                        goto out;
                pldlen += _m->m_len;
        }
        if (pldlen > 0xfff)
                goto out;
        if (protocol_id == BCSP_IDENT_ACKPKT || protocol_id > 15)
                goto out;

        if (sc->sc_seq_winspace == 0)
                goto out;

        M_PREPEND(m, sizeof(bcsp_hdr_t), M_DONTWAIT);
        if (m == NULL) {
                aprint_error_dev(sc->sc_dev, "out of memory\n");
                return false;
        }
        KASSERT(m->m_len >= sizeof(bcsp_hdr_t));

        hdrp = mtod(m, bcsp_hdr_t *);
        memset(hdrp, 0, sizeof(bcsp_hdr_t));
        hdrp->flags |= sc->sc_seq_txseq;
        hdrp->ident = protocol_id;

        callout_schedule(&sc->sc_seq_timer, sc->sc_seq_timeout);

        s = splserial();
        MBUFQ_ENQUEUE(&sc->sc_seqq, m);
        splx(s);
        sc->sc_transmit_callback = bcsp_reliabletx_callback;

#ifdef BCSP_DEBUG
        if (bcsp_debug == 2)
                bcsp_packet_print(m);
#endif

        sc->sc_seq_txseq = (sc->sc_seq_txseq + 1) & BCSP_FLAGS_SEQ_MASK;
        sc->sc_seq_winspace--;
        _m = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
        if (_m == NULL) {
                aprint_error_dev(sc->sc_dev, "out of memory\n");
                goto out;
        }
        MBUFQ_ENQUEUE(&sc->sc_seq_retryq, _m);
        bcsp_mux_transmit(sc);

        return true;
out:
        m_freem(m);
        return false;
}

#if 0
static bool
bcsp_rx_reliable_pkt(struct bcsp_softc *sc, struct mbuf *m, u_int protocol_id)
{

        return false;
}

/* XXXX:  I can't understand meaning this function... */
static __inline void
bcsp_link_failed(struct bcsp_softc *sc)
{

        return (sc->sc_seq_retries >= sc->sc_seq_retry_limit);
}
#endif

static __inline u_int
bcsp_get_txack(struct bcsp_softc *sc)
{

        return sc->sc_seq_txack;
}

static void
bcsp_signal_rxack(struct bcsp_softc *sc, uint32_t rxack)
{
        bcsp_hdr_t *hdrp;
        struct mbuf *m;
        uint32_t seqno = (rxack - 1) & BCSP_FLAGS_SEQ_MASK;
        int s;

        DPRINTFN(1, ("%s: seq signal rxack: rxack=%d\n",
            device_xname(sc->sc_dev), rxack));

        s = splserial();
        m = MBUFQ_FIRST(&sc->sc_seq_retryq);
        while (m != NULL) {
                hdrp = mtod(m, bcsp_hdr_t *);
                if (BCSP_FLAGS_SEQ(hdrp->flags) == seqno) {
                        struct mbuf *m0;

                        for (m0 = MBUFQ_FIRST(&sc->sc_seq_retryq);
                            m0 != MBUFQ_NEXT(m);
                            m0 = MBUFQ_FIRST(&sc->sc_seq_retryq)) {
                                MBUFQ_DEQUEUE(&sc->sc_seq_retryq, m0);
                                m_freem(m0);
                                sc->sc_seq_winspace++;
                        }
                        break;
                }
                m = MBUFQ_NEXT(m);
        }
        splx(s);
        sc->sc_seq_retries = 0;

        if (sc->sc_seq_winspace == sc->sc_seq_winsize)
                callout_stop(&sc->sc_seq_timer);
        else
                callout_schedule(&sc->sc_seq_timer, sc->sc_seq_timeout);
}

static void
bcsp_reliabletx_callback(struct bcsp_softc *sc, struct mbuf *m)
{

        m_freem(m);
}

static void
bcsp_timer_timeout(void *arg)
{
        struct bcsp_softc *sc = arg;
        struct mbuf *m, *_m;
        int s, i = 0;

        DPRINTFN(1, ("%s: seq timeout: retries=%d\n",
            device_xname(sc->sc_dev), sc->sc_seq_retries));

        s = splserial();
        for (m = MBUFQ_FIRST(&sc->sc_seq_retryq); m != NULL;
            m = MBUFQ_NEXT(m)) {
                _m = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
                if (_m == NULL) {
                        aprint_error_dev(sc->sc_dev, "out of memory\n");
                        return;
                }
                MBUFQ_ENQUEUE(&sc->sc_seqq, _m);
                i++;
        }
        splx(s);

        if (i != 0) {
                if (++sc->sc_seq_retries < sc->sc_seq_retry_limit)
                        callout_schedule(&sc->sc_seq_timer, sc->sc_seq_timeout);
                else {
                        aprint_error_dev(sc->sc_dev,
                            "reached the retry limit."
                            " restart the link-establishment\n");
                        bcsp_sequencing_reset(sc);
                        bcsp_start_le(sc);
                        return;
                }
        }
        bcsp_mux_transmit(sc);
}

static void
bcsp_sequencing_reset(struct bcsp_softc *sc)
{
        int s;

        s = splserial();
        MBUFQ_DRAIN(&sc->sc_seqq);
        MBUFQ_DRAIN(&sc->sc_seq_retryq);
        splx(s);


        sc->sc_seq_txseq = 0;
        sc->sc_seq_txack = 0;
        sc->sc_seq_winspace = sc->sc_seq_winsize;
        sc->sc_seq_retries = 0;
        callout_stop(&sc->sc_seq_timer);

        sc->sc_mux_send_ack = false;

        /* XXXX: expected_rxseq should be set by MUX Layer */
        sc->sc_seq_expected_rxseq = 0;
}


/*
 * BCSP Datagram Queue Layer functions
 */
static void
bcsp_datagramq_receive(struct bcsp_softc *sc, struct mbuf *m)
{
        bcsp_hdr_t hdr;

        DPRINTFN(1, ("%s: dgq receive\n", device_xname(sc->sc_dev)));
#ifdef BCSP_DEBUG
        if (bcsp_debug == 2)
                bcsp_packet_print(m);
#endif

        m_copydata(m, 0, sizeof(bcsp_hdr_t), &hdr);

        switch (hdr.ident) {
        case BCSP_CHANNEL_LE:
                m_adj(m, sizeof(bcsp_hdr_t));
                bcsp_input_le(sc, m);
                break;

        case BCSP_CHANNEL_HCI_SCO:
                /*
                 * We remove the header of BCSP and add the 'uint8_t type' of
                 * hci_scodata_hdr_t to the head. 
                 */
                m_adj(m, sizeof(bcsp_hdr_t) - sizeof(uint8_t));
                *(mtod(m, uint8_t *)) = HCI_SCO_DATA_PKT;
                if (!hci_input_sco(sc->sc_unit, m))
                        sc->sc_stats.err_rx++;

                sc->sc_stats.sco_rx++;
                break;

        default:
                aprint_error_dev(sc->sc_dev,
                    "received unreliable packet with not support channel %d\n",
                    hdr.ident);
                m_freem(m);
                break;
        }
}

static bool
bcsp_tx_unreliable_pkt(struct bcsp_softc *sc, struct mbuf *m, u_int protocol_id)
{
        bcsp_hdr_t *hdrp;
        struct mbuf *_m;
        u_int pldlen;
        int s;

        DPRINTFN(1, ("%s: dgq transmit: protocol_id=%d,",
            device_xname(sc->sc_dev), protocol_id));

        for (pldlen = 0, _m = m; _m != NULL; _m = m->m_next) {
                if (_m->m_len < 0)
                        goto out;
                pldlen += _m->m_len;
        }
        DPRINTFN(1, (" pldlen=%d\n", pldlen));
        if (pldlen > 0xfff)
                goto out;
        if (protocol_id == BCSP_IDENT_ACKPKT || protocol_id > 15)
                goto out;

        M_PREPEND(m, sizeof(bcsp_hdr_t), M_DONTWAIT);
        if (m == NULL) {
                aprint_error_dev(sc->sc_dev, "out of memory\n");
                return false;
        }
        KASSERT(m->m_len >= sizeof(bcsp_hdr_t));

        hdrp = mtod(m, bcsp_hdr_t *);
        memset(hdrp, 0, sizeof(bcsp_hdr_t));
        hdrp->ident = protocol_id;

        s = splserial();
        MBUFQ_ENQUEUE(&sc->sc_dgq, m);
        splx(s);
        sc->sc_transmit_callback = bcsp_unreliabletx_callback;

#ifdef BCSP_DEBUG
        if (bcsp_debug == 2)
                bcsp_packet_print(m);
#endif

        bcsp_mux_transmit(sc);

        return true;
out:
        m_freem(m);
        return false;
}

#if 0
static bool
bcsp_rx_unreliable_pkt(struct bcsp_softc *sc, struct mbuf *m, u_int protocol_id)
{

        return false;
}
#endif

static void
bcsp_unreliabletx_callback(struct bcsp_softc *sc, struct mbuf *m)
{

        if (M_GETCTX(m, void *) == NULL)
                m_freem(m);
        else if (!hci_complete_sco(sc->sc_unit, m))
                sc->sc_stats.err_tx++;
}


/*
 * BlueCore Link Establishment Protocol functions
 */
static const uint8_t sync[] = BCSP_LE_SYNC;
static const uint8_t syncresp[] = BCSP_LE_SYNCRESP;
static const uint8_t conf[] = BCSP_LE_CONF;
static const uint8_t confresp[] = BCSP_LE_CONFRESP;

static int
bcsp_start_le(struct bcsp_softc *sc)
{

        DPRINTF(("%s: start link-establish\n", device_xname(sc->sc_dev)));

        bcsp_set_choke(sc, true);

        if (!sc->sc_le_muzzled) {
                struct mbuf *m;

                m = m_gethdr(M_WAIT, MT_DATA);
                m->m_pkthdr.len = m->m_len = 0;
                m_copyback(m, 0, sizeof(sync), sync);
                if (!bcsp_tx_unreliable_pkt(sc, m, BCSP_CHANNEL_LE)) {
                        aprint_error_dev(sc->sc_dev,
                            "le-packet transmit failed\n");
                        return EINVAL;
                }
        }
        callout_schedule(&sc->sc_le_timer, BCSP_LE_TSHY_TIMEOUT);

        sc->sc_le_state = le_state_shy;
        return 0;
}

static void
bcsp_terminate_le(struct bcsp_softc *sc)
{
        struct mbuf *m;

        /* terminate link-establishment */
        callout_stop(&sc->sc_le_timer);
        bcsp_set_choke(sc, true);
        MGETHDR(m, M_DONTWAIT, MT_DATA);
        if (m == NULL)
                aprint_error_dev(sc->sc_dev, "out of memory\n");
        else {
                /* length of le packets is 4 */
                m->m_pkthdr.len = m->m_len = 0;
                m_copyback(m, 0, sizeof(sync), sync);
                if (!bcsp_tx_unreliable_pkt(sc, m, BCSP_CHANNEL_LE))
                        aprint_error_dev(sc->sc_dev,
                            "link-establishment terminations failed\n");
        }
}

static void
bcsp_input_le(struct bcsp_softc *sc, struct mbuf *m)
{
        uint32_t *rcvpkt;
        int i;
        const uint8_t *rplypkt;
        static struct {
                const char *type;
                const uint8_t *datap;
        } pkt[] = {
                { "sync",       sync },
                { "sync-resp",  syncresp },
                { "conf",       conf },
                { "conf-resp",  confresp },

                { NULL, 0 }
        };

        DPRINTFN(0, ("%s: le input: state %d, muzzled %d\n",
            device_xname(sc->sc_dev), sc->sc_le_state, sc->sc_le_muzzled));
#ifdef BCSP_DEBUG
        if (bcsp_debug == 1)
                bcsp_packet_print(m);
#endif

        rcvpkt = mtod(m, uint32_t *);
        i = 0;

        /* length of le packets is 4 */
        if (m->m_len == sizeof(uint32_t))
                for (i = 0; pkt[i].type != NULL; i++)
                        if (*(const uint32_t *)pkt[i].datap == *rcvpkt)
                                break;
        if (m->m_len != sizeof(uint32_t) || pkt[i].type == NULL) {
                aprint_error_dev(sc->sc_dev, "received unknown packet\n");
                m_freem(m);
                return;
        }

        rplypkt = NULL;
        switch (sc->sc_le_state) {
        case le_state_shy:
                if (*rcvpkt == *(const uint32_t *)sync) {
                        sc->sc_le_muzzled = false;
                        rplypkt = syncresp;
                } else if (*rcvpkt == *(const uint32_t *)syncresp) {
                        DPRINTF(("%s: state change to curious\n",
                            device_xname(sc->sc_dev)));

                        rplypkt = conf;
                        callout_schedule(&sc->sc_le_timer,
                            BCSP_LE_TCONF_TIMEOUT);
                        sc->sc_le_state = le_state_curious;
                } else
                        aprint_error_dev(sc->sc_dev,
                            "received an unknown packet at shy\n");
                break;

        case le_state_curious:
                if (*rcvpkt == *(const uint32_t *)sync)
                        rplypkt = syncresp;
                else if (*rcvpkt == *(const uint32_t *)conf)
                        rplypkt = confresp;
                else if (*rcvpkt == *(const uint32_t *)confresp) {
                        DPRINTF(("%s: state change to garrulous:\n",
                            device_xname(sc->sc_dev)));

                        bcsp_set_choke(sc, false);
                        callout_stop(&sc->sc_le_timer);
                        sc->sc_le_state = le_state_garrulous;
                } else
                        aprint_error_dev(sc->sc_dev,
                            "received unknown packet at curious\n");
                break;

        case le_state_garrulous:
                if (*rcvpkt == *(const uint32_t *)conf)
                        rplypkt = confresp;
                else if (*rcvpkt == *(const uint32_t *)sync) {
                        /* XXXXX */
                        aprint_error_dev(sc->sc_dev,
                            "received sync! peer to reset?\n");

                        bcsp_sequencing_reset(sc);
                        rplypkt = sync;
                        sc->sc_le_state = le_state_shy;
                } else
                        aprint_error_dev(sc->sc_dev,
                            "received unknown packet at garrulous\n");
                break;
        }

        m_freem(m);

        if (rplypkt != NULL) {
                MGETHDR(m, M_DONTWAIT, MT_DATA);
                if (m == NULL)
                        aprint_error_dev(sc->sc_dev, "out of memory\n");
                else {
                        /* length of le packets is 4 */
                        m->m_pkthdr.len = m->m_len = 0;
                        m_copyback(m, 0, 4, rplypkt);
                        if (!bcsp_tx_unreliable_pkt(sc, m, BCSP_CHANNEL_LE))
                                aprint_error_dev(sc->sc_dev,
                                    "le-packet transmit failed\n");
                }
        }
}

static void
bcsp_le_timeout(void *arg)
{
        struct bcsp_softc *sc = arg;
        struct mbuf *m;
        int timeout;
        const uint8_t *sndpkt = NULL;

        DPRINTFN(0, ("%s: le timeout: state %d, muzzled %d\n",
            device_xname(sc->sc_dev), sc->sc_le_state, sc->sc_le_muzzled));

        switch (sc->sc_le_state) {
        case le_state_shy:
                if (!sc->sc_le_muzzled)
                        sndpkt = sync;
                timeout = BCSP_LE_TSHY_TIMEOUT;
                break;

        case le_state_curious:
                sndpkt = conf;
                timeout = BCSP_LE_TCONF_TIMEOUT;
                break;

        default:
                aprint_error_dev(sc->sc_dev,
                    "timeout happen at unknown state %d\n", sc->sc_le_state);
                return;
        }

        if (sndpkt != NULL) {
                MGETHDR(m, M_DONTWAIT, MT_DATA);
                if (m == NULL)
                        aprint_error_dev(sc->sc_dev, "out of memory\n");
                else {
                        /* length of le packets is 4 */
                        m->m_pkthdr.len = m->m_len = 0;
                        m_copyback(m, 0, 4, sndpkt);
                        if (!bcsp_tx_unreliable_pkt(sc, m, BCSP_CHANNEL_LE))
                                aprint_error_dev(sc->sc_dev,
                                    "le-packet transmit failed\n");
                }
        }

        callout_schedule(&sc->sc_le_timer, timeout);
}


/*
 * BlueCore Serial Protocol functions.
 */
static int
bcsp_enable(device_t self)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        if (sc->sc_flags & BCSP_ENABLED)
                return 0;

        s = spltty();

        sc->sc_flags |= BCSP_ENABLED;
        sc->sc_flags &= ~BCSP_XMIT;

        splx(s);

        return 0;
}

static void
bcsp_disable(device_t self)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        if ((sc->sc_flags & BCSP_ENABLED) == 0)
                return;

        s = spltty();

        m_freem(sc->sc_rxp);
        sc->sc_rxp = NULL;

        m_freem(sc->sc_txp);
        sc->sc_txp = NULL;

        MBUFQ_DRAIN(&sc->sc_cmdq);
        MBUFQ_DRAIN(&sc->sc_aclq);
        MBUFQ_DRAIN(&sc->sc_scoq);

        sc->sc_flags &= ~BCSP_ENABLED;
        splx(s);
}

static void
bcsp_start(struct bcsp_softc *sc)
{
        struct mbuf *m;

        KASSERT((sc->sc_flags & BCSP_XMIT) == 0);
        KASSERT(sc->sc_txp == NULL);

        if (MBUFQ_FIRST(&sc->sc_aclq)) {
                MBUFQ_DEQUEUE(&sc->sc_aclq, m);
                sc->sc_stats.acl_tx++;
                sc->sc_flags |= BCSP_XMIT;
                bcsp_tx_reliable_pkt(sc, m, BCSP_CHANNEL_HCI_ACL);
        }

        if (MBUFQ_FIRST(&sc->sc_cmdq)) {
                MBUFQ_DEQUEUE(&sc->sc_cmdq, m);
                sc->sc_stats.cmd_tx++;
                sc->sc_flags |= BCSP_XMIT;
                bcsp_tx_reliable_pkt(sc, m, BCSP_CHANNEL_HCI_CMDEVT);
        }

        if (MBUFQ_FIRST(&sc->sc_scoq)) {
                MBUFQ_DEQUEUE(&sc->sc_scoq, m);
                sc->sc_stats.sco_tx++;
                /* XXXX: We can transmit with reliable */
                sc->sc_flags |= BCSP_XMIT;
                bcsp_tx_unreliable_pkt(sc, m, BCSP_CHANNEL_HCI_SCO);
        }

        return;
}

static void
bcsp_output_cmd(device_t self, struct mbuf *m)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        KASSERT(sc->sc_flags & BCSP_ENABLED);

        m_adj(m, sizeof(uint8_t));
        M_SETCTX(m, NULL);

        s = spltty();
        MBUFQ_ENQUEUE(&sc->sc_cmdq, m);
        if ((sc->sc_flags & BCSP_XMIT) == 0)
                bcsp_start(sc);

        splx(s);
}

static void
bcsp_output_acl(device_t self, struct mbuf *m)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        KASSERT(sc->sc_flags & BCSP_ENABLED);

        m_adj(m, sizeof(uint8_t));
        M_SETCTX(m, NULL);

        s = spltty();
        MBUFQ_ENQUEUE(&sc->sc_aclq, m);
        if ((sc->sc_flags & BCSP_XMIT) == 0)
                bcsp_start(sc);

        splx(s);
}

static void
bcsp_output_sco(device_t self, struct mbuf *m)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        KASSERT(sc->sc_flags & BCSP_ENABLED);

        m_adj(m, sizeof(uint8_t));

        s = spltty();
        MBUFQ_ENQUEUE(&sc->sc_scoq, m);
        if ((sc->sc_flags & BCSP_XMIT) == 0)
                bcsp_start(sc);

        splx(s);
}

static void
bcsp_stats(device_t self, struct bt_stats *dest, int flush)
{
        struct bcsp_softc *sc = device_private(self);
        int s;

        s = spltty();
        memcpy(dest, &sc->sc_stats, sizeof(struct bt_stats));

        if (flush)
                memset(&sc->sc_stats, 0, sizeof(struct bt_stats));

        splx(s);
}


#ifdef BCSP_DEBUG
static void
bcsp_packet_print(struct mbuf *m)
{
        int i;
        uint8_t *p;

        for ( ; m != NULL; m = m->m_next) {
                p = mtod(m, uint8_t *);
                for (i = 0; i < m->m_len; i++) {
                        if (i % 16 == 0)
                                printf(" ");
                        printf(" %02x", *(p + i));
                        if (i % 16 == 15)
                                printf("\n");
                }
                printf("\n");
        }
}
#endif