summaryrefslogtreecommitdiffstats
path: root/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c')
-rw-r--r--sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c806
1 files changed, 806 insertions, 0 deletions
diff --git a/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c b/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c
new file mode 100644
index 0000000..f505158
--- /dev/null
+++ b/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c
@@ -0,0 +1,806 @@
+/*
+ * ng_l2cap_llpi.c
+ *
+ * Copyright (c) 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_l2cap_llpi.c,v 1.16 2002/09/04 21:38:38 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/endian.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/queue.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/netgraph.h>
+#include "ng_bluetooth.h"
+#include "ng_hci.h"
+#include "ng_l2cap.h"
+#include "ng_l2cap_var.h"
+#include "ng_l2cap_cmds.h"
+#include "ng_l2cap_evnt.h"
+#include "ng_l2cap_llpi.h"
+#include "ng_l2cap_ulpi.h"
+#include "ng_l2cap_misc.h"
+
+/******************************************************************************
+ ******************************************************************************
+ ** Lower Layer Protocol (HCI) Interface module
+ ******************************************************************************
+ ******************************************************************************/
+
+/*
+ * Send LP_ConnectReq event to the lower layer protocol. Create new connection
+ * descriptor and initialize it. Create LP_ConnectReq event and send it to the
+ * lower layer, then adjust connection state and start timer. The function WILL
+ * FAIL if connection to the remote unit already exists.
+ */
+
+int
+ng_l2cap_lp_con_req(ng_l2cap_p l2cap, bdaddr_p bdaddr)
+{
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_con_req_ep *ep = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Verify that we DO NOT have connection to the remote unit */
+ con = ng_l2cap_con_by_addr(l2cap, bdaddr);
+ if (con != NULL) {
+ NG_L2CAP_ALERT(
+"%s: %s - unexpected LP_ConnectReq event. " \
+"Connection already exists, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+
+ return (EEXIST);
+ }
+
+ /* Check if lower layer protocol is still connected */
+ if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) {
+ NG_L2CAP_ERR(
+"%s: %s - hook \"%s\" is not connected or valid\n",
+ __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI);
+
+ return (ENOTCONN);
+ }
+
+ /* Create and intialize new connection descriptor */
+ con = ng_l2cap_new_con(l2cap, bdaddr);
+ if (con == NULL)
+ return (ENOMEM);
+
+ /* Create and send LP_ConnectReq event */
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_REQ,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL) {
+ ng_l2cap_free_con(con);
+
+ return (ENOMEM);
+ }
+
+ ep = (ng_hci_lp_con_req_ep *) (msg->data);
+ bcopy(bdaddr, &ep->bdaddr, sizeof(ep->bdaddr));
+ ep->link_type = NG_HCI_LINK_ACL;
+
+ con->state = NG_L2CAP_W4_LP_CON_CFM;
+ ng_l2cap_lp_timeout(con);
+
+ NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->hci, NULL);
+ if (error != 0)
+ ng_l2cap_free_con(con); /* will remove timeout */
+
+ return (error);
+} /* ng_l2cap_lp_con_req */
+
+/*
+ * Process LP_ConnectCfm event from the lower layer protocol. It could be
+ * positive or negative. Verify remote unit address then stop the timer and
+ * process event.
+ */
+
+int
+ng_l2cap_lp_con_cfm(ng_l2cap_p l2cap, struct ng_mesg *msg)
+{
+ ng_hci_lp_con_cfm_ep *ep = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Check message */
+ if (msg->header.arglen != sizeof(*ep)) {
+ NG_L2CAP_ALERT(
+"%s: %s - invalid LP_ConnectCfm[Neg] message size\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_con_cfm_ep *) (msg->data);
+
+ /* Check if we have requested/accepted this connection */
+ con = ng_l2cap_con_by_addr(l2cap, &ep->bdaddr);
+ if (con == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_ConnectCfm event. Connection does not exist\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = ENOENT;
+ goto out;
+ }
+
+ /* Check connection state */
+ if (con->state != NG_L2CAP_W4_LP_CON_CFM) {
+ NG_L2CAP_ALERT(
+"%s: %s - unexpected LP_ConnectCfm event. " \
+"Invalid connection state, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Looks like it is our confirmation. It is safe now to cancel
+ * connection timer and notify upper layer.
+ */
+
+ ng_l2cap_lp_untimeout(con);
+
+ if (ep->status == 0) {
+ con->state = NG_L2CAP_CON_OPEN;
+ con->con_handle = ep->con_handle;
+ ng_l2cap_lp_deliver(con);
+ } else {
+ /* Negative confirmation - remove connection descriptor */
+ con->state = NG_L2CAP_CON_CLOSED;
+ ng_l2cap_con_fail(con, ep->status);
+ }
+out:
+ return (error);
+} /* ng_l2cap_lp_con_cfm */
+
+/*
+ * Process LP_ConnectInd event from the lower layer protocol. This is a good
+ * place to put some extra check on remote unit address and/or class. We could
+ * even forward this information to control hook (or check against internal
+ * black list) and thus implement some kind of firewall. But for now be simple
+ * and create new connection descriptor, start timer and send LP_ConnectRsp
+ * event (i.e. accept connection).
+ */
+
+int
+ng_l2cap_lp_con_ind(ng_l2cap_p l2cap, struct ng_mesg *msg)
+{
+ ng_hci_lp_con_ind_ep *ep = NULL;
+ ng_hci_lp_con_rsp_ep *rp = NULL;
+ struct ng_mesg *rsp = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Check message */
+ if (msg->header.arglen != sizeof(*ep)) {
+ NG_L2CAP_ALERT(
+"%s: %s - invalid LP_ConnectInd message size\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_con_ind_ep *) (msg->data);
+
+ /* Make sure we have only one connection to the remote unit */
+ con = ng_l2cap_con_by_addr(l2cap, &ep->bdaddr);
+ if (con != NULL) {
+ NG_L2CAP_ALERT(
+"%s: %s - unexpected LP_ConnectInd event. " \
+"Connection already exists, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+ error = EEXIST;
+ goto out;
+ }
+
+ /* Check if lower layer protocol is still connected */
+ if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) {
+ NG_L2CAP_ERR(
+"%s: %s - hook \"%s\" is not connected or valid",
+ __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI);
+ error = ENOTCONN;
+ goto out;
+ }
+
+ /* Create and intialize new connection descriptor */
+ con = ng_l2cap_new_con(l2cap, &ep->bdaddr);
+ if (con == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ /* Create and send LP_ConnectRsp event */
+ NG_MKMESSAGE(rsp, NGM_HCI_COOKIE, NGM_HCI_LP_CON_RSP,
+ sizeof(*rp), M_NOWAIT);
+ if (msg == NULL) {
+ ng_l2cap_free_con(con);
+ error = ENOMEM;
+ goto out;
+ }
+
+ rp = (ng_hci_lp_con_rsp_ep *)(rsp->data);
+ rp->status = 0x00; /* accept connection */
+ rp->link_type = NG_HCI_LINK_ACL;
+ bcopy(&ep->bdaddr, &rp->bdaddr, sizeof(rp->bdaddr));
+
+ con->state = NG_L2CAP_W4_LP_CON_CFM;
+ ng_l2cap_lp_timeout(con);
+
+ NG_SEND_MSG_HOOK(error, l2cap->node, rsp, l2cap->hci, NULL);
+ if (error != 0)
+ ng_l2cap_free_con(con); /* will remove timeout */
+out:
+ return (error);
+} /* ng_hci_lp_con_ind */
+
+/*
+ * Process LP_DisconnectInd event from the lower layer protocol. We have been
+ * disconnected from the remote unit. So notify the upper layer protocol.
+ */
+
+int
+ng_l2cap_lp_discon_ind(ng_l2cap_p l2cap, struct ng_mesg *msg)
+{
+ ng_hci_lp_discon_ind_ep *ep = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Check message */
+ if (msg->header.arglen != sizeof(*ep)) {
+ NG_L2CAP_ALERT(
+"%s: %s - invalid LP_DisconnectInd message size\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_discon_ind_ep *) (msg->data);
+
+ /* Check if we have this connection */
+ con = ng_l2cap_con_by_handle(l2cap, ep->con_handle);
+ if (con == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_DisconnectInd event. " \
+"Connection does not exist, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), ep->con_handle);
+ error = ENOENT;
+ goto out;
+ }
+
+ /* XXX Verify connection state -- do we need to check this? */
+ if (con->state != NG_L2CAP_CON_OPEN) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_DisconnectInd event. " \
+"Invalid connection state, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+ error = EINVAL;
+ goto out;
+ }
+
+ /* Notify upper layer and remove connection */
+ con->state = NG_L2CAP_CON_CLOSED;
+ ng_l2cap_con_fail(con, ep->reason);
+out:
+ return (error);
+} /* ng_l2cap_lp_discon_ind */
+
+/*
+ * Send LP_QoSSetupReq event to the lower layer protocol
+ */
+
+int
+ng_l2cap_lp_qos_req(ng_l2cap_p l2cap, u_int16_t con_handle,
+ ng_l2cap_flow_p flow)
+{
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_qos_req_ep *ep = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Verify that we have this connection */
+ con = ng_l2cap_con_by_handle(l2cap, con_handle);
+ if (con == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_QoSSetupReq event. " \
+"Connection does not exist, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con_handle);
+
+ return (ENOENT);
+ }
+
+ /* Verify connection state */
+ if (con->state != NG_L2CAP_CON_OPEN) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_QoSSetupReq event. " \
+"Invalid connection state, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+
+ return (EINVAL);
+ }
+
+ /* Check if lower layer protocol is still connected */
+ if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) {
+ NG_L2CAP_ERR(
+"%s: %s - hook \"%s\" is not connected or valid",
+ __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI);
+
+ return (ENOTCONN);
+ }
+
+ /* Create and send LP_QoSSetupReq event */
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_REQ,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_qos_req_ep *) (msg->data);
+ ep->con_handle = con_handle;
+ ep->flags = flow->flags;
+ ep->service_type = flow->service_type;
+ ep->token_rate = flow->token_rate;
+ ep->peak_bandwidth = flow->peak_bandwidth;
+ ep->latency = flow->latency;
+ ep->delay_variation = flow->delay_variation;
+
+ NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->hci, NULL);
+
+ return (error);
+} /* ng_l2cap_lp_con_req */
+
+/*
+ * Process LP_QoSSetupCfm from the lower layer protocol
+ */
+
+int
+ng_l2cap_lp_qos_cfm(ng_l2cap_p l2cap, struct ng_mesg *msg)
+{
+ ng_hci_lp_qos_cfm_ep *ep = NULL;
+ int error = 0;
+
+ /* Check message */
+ if (msg->header.arglen != sizeof(*ep)) {
+ NG_L2CAP_ALERT(
+"%s: %s - invalid LP_QoSSetupCfm[Neg] message size\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_qos_cfm_ep *) (msg->data);
+ /* XXX FIXME do something */
+out:
+ return (error);
+} /* ng_l2cap_lp_qos_cfm */
+
+/*
+ * Process LP_QoSViolationInd event from the lower layer protocol. Lower
+ * layer protocol has detected QoS Violation, so we MUST notify the
+ * upper layer.
+ */
+
+int
+ng_l2cap_lp_qos_ind(ng_l2cap_p l2cap, struct ng_mesg *msg)
+{
+ ng_hci_lp_qos_ind_ep *ep = NULL;
+ ng_l2cap_con_p con = NULL;
+ int error = 0;
+
+ /* Check message */
+ if (msg->header.arglen != sizeof(*ep)) {
+ NG_L2CAP_ALERT(
+"%s: %s - invalid LP_QoSViolation message size\n",
+ __func__, NG_NODE_NAME(l2cap->node));
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_qos_ind_ep *) (msg->data);
+
+ /* Check if we have this connection */
+ con = ng_l2cap_con_by_handle(l2cap, ep->con_handle);
+ if (con == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_QoSViolationInd event. " \
+"Connection does not exist, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), ep->con_handle);
+ error = ENOENT;
+ goto out;
+ }
+
+ /* Verify connection state */
+ if (con->state != NG_L2CAP_CON_OPEN) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected LP_QoSViolationInd event. " \
+"Invalid connection state, state=%d, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state,
+ con->con_handle);
+ error = EINVAL;
+ goto out;
+ }
+
+ /* XXX FIXME Notify upper layer and terminate channels if required */
+out:
+ return (error);
+} /* ng_l2cap_qos_ind */
+
+/*
+ * Prepare L2CAP packet. Prepend packet with L2CAP packet header and then
+ * segment it according to HCI MTU.
+ */
+
+int
+ng_l2cap_lp_send(ng_l2cap_con_p con, u_int16_t dcid, struct mbuf *m0)
+{
+ ng_l2cap_p l2cap = con->l2cap;
+ ng_l2cap_hdr_t *l2cap_hdr = NULL;
+ ng_hci_acldata_pkt_t *acl_hdr = NULL;
+ struct mbuf *m_last = NULL, *m = NULL;
+ int len, flag = NG_HCI_PACKET_START;
+
+ KASSERT((con->tx_pkt == NULL),
+("%s: %s - another packet pending?!\n", __func__, NG_NODE_NAME(l2cap->node)));
+ KASSERT((l2cap->pkt_size > 0),
+("%s: %s - invalid l2cap->pkt_size?!\n", __func__, NG_NODE_NAME(l2cap->node)));
+
+ /* Prepend mbuf with L2CAP header */
+ m0 = ng_l2cap_prepend(m0, sizeof(*l2cap_hdr));
+ if (m0 == NULL) {
+ NG_L2CAP_ALERT(
+"%s: %s - ng_l2cap_prepend(%d) failed\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ sizeof(*l2cap_hdr));
+
+ goto fail;
+ }
+
+ l2cap_hdr = mtod(m0, ng_l2cap_hdr_t *);
+ l2cap_hdr->length = htole16(m0->m_pkthdr.len - sizeof(*l2cap_hdr));
+ l2cap_hdr->dcid = htole16(dcid);
+
+ /*
+ * Segment single L2CAP packet according to the HCI layer MTU. Convert
+ * each segment into ACL data packet and prepend it with ACL data packet
+ * header. Link all segments together via m_nextpkt link.
+ *
+ * XXX BC (Broadcast flag) will always be 0 (zero).
+ */
+
+ while (m0 != NULL) {
+ /* Check length of the packet against HCI MTU */
+ len = m0->m_pkthdr.len;
+ if (len > l2cap->pkt_size) {
+ m = m_split(m0, l2cap->pkt_size, M_DONTWAIT);
+ if (m == NULL) {
+ NG_L2CAP_ALERT(
+"%s: %s - m_split(%d) failed\n", __func__, NG_NODE_NAME(l2cap->node),
+ l2cap->pkt_size);
+ goto fail;
+ }
+
+ len = l2cap->pkt_size;
+ }
+
+ /* Convert packet fragment into ACL data packet */
+ m0 = ng_l2cap_prepend(m0, sizeof(*acl_hdr));
+ if (m0 == NULL) {
+ NG_L2CAP_ALERT(
+"%s: %s - ng_l2cap_prepend(%d) failed\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ sizeof(*acl_hdr));
+ goto fail;
+ }
+
+ acl_hdr = mtod(m0, ng_hci_acldata_pkt_t *);
+ acl_hdr->type = NG_HCI_ACL_DATA_PKT;
+ acl_hdr->length = htole16(len);
+ acl_hdr->con_handle = htole16(NG_HCI_MK_CON_HANDLE(
+ con->con_handle, flag, 0));
+
+ /* Add fragment to the chain */
+ m0->m_nextpkt = NULL;
+
+ if (con->tx_pkt == NULL)
+ con->tx_pkt = m_last = m0;
+ else {
+ m_last->m_nextpkt = m0;
+ m_last = m0;
+ }
+
+ NG_L2CAP_INFO(
+"%s: %s - attaching ACL packet, con_handle=%d, PB=%#x, length=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->con_handle,
+ flag, len);
+
+ m0 = m;
+ m = NULL;
+ flag = NG_HCI_PACKET_FRAGMENT;
+ }
+
+ return (0);
+fail:
+ NG_FREE_M(m0);
+ NG_FREE_M(m);
+
+ while (con->tx_pkt != NULL) {
+ m = con->tx_pkt->m_nextpkt;
+ m_freem(con->tx_pkt);
+ con->tx_pkt = m;
+ }
+
+ return (ENOBUFS);
+} /* ng_l2cap_lp_send */
+
+/*
+ * Receive ACL data packet from the HCI layer. First strip ACL packet header
+ * and get connection handle, PB (Packet Boundary) flag and payload length.
+ * Then find connection descriptor and verify its state. Then process ACL
+ * packet as follows.
+ *
+ * 1) If we got first segment (pb == NG_HCI_PACKET_START) then extract L2CAP
+ * header and get total length of the L2CAP packet. Then start new L2CAP
+ * packet.
+ *
+ * 2) If we got other (then first :) segment (pb == NG_HCI_PACKET_FRAGMENT)
+ * then add segment to the packet.
+ */
+
+int
+ng_l2cap_lp_receive(ng_l2cap_p l2cap, struct mbuf *m)
+{
+ ng_hci_acldata_pkt_t *acl_hdr = NULL;
+ ng_l2cap_hdr_t *l2cap_hdr = NULL;
+ ng_l2cap_con_p con = NULL;
+ u_int16_t con_handle, length, pb;
+ int error = 0;
+
+ /* Check ACL data packet */
+ if (m->m_pkthdr.len < sizeof(*acl_hdr)) {
+ NG_L2CAP_ERR(
+"%s: %s - invalid ACL data packet. Packet too small, length=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), m->m_pkthdr.len);
+ error = EMSGSIZE;
+ goto drop;
+ }
+
+ /* Strip ACL data packet header */
+ NG_L2CAP_M_PULLUP(m, sizeof(*acl_hdr));
+ if (m == NULL)
+ return (ENOBUFS);
+
+ acl_hdr = mtod(m, ng_hci_acldata_pkt_t *);
+ m_adj(m, sizeof(*acl_hdr));
+
+ /* Get ACL connection handle, PB flag and payload length */
+ acl_hdr->con_handle = le16toh(acl_hdr->con_handle);
+ con_handle = NG_HCI_CON_HANDLE(acl_hdr->con_handle);
+ pb = NG_HCI_PB_FLAG(acl_hdr->con_handle);
+ length = le16toh(acl_hdr->length);
+
+ NG_L2CAP_INFO(
+"%s: %s - got ACL data packet, con_handle=%d, PB=%#x, length=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con_handle, pb, length);
+
+ /* Get connection descriptor */
+ con = ng_l2cap_con_by_handle(l2cap, con_handle);
+ if (con == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected ACL data packet. " \
+"Connection does not exist, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con_handle);
+ error = ENOENT;
+ goto drop;
+ }
+
+ /* Verify connection state */
+ if (con->state != NG_L2CAP_CON_OPEN) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected ACL data packet. Invalid connection state=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->state);
+ error = EHOSTDOWN;
+ goto drop;
+ }
+
+ /* Process packet */
+ if (pb == NG_HCI_PACKET_START) {
+ if (con->rx_pkt != NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - dropping incomplete L2CAP packet, got %d bytes, want %d bytes\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ con->rx_pkt->m_pkthdr.len, con->rx_pkt_len);
+ NG_FREE_M(con->rx_pkt);
+ con->rx_pkt_len = 0;
+ }
+
+ /* Get L2CAP header */
+ if (m->m_pkthdr.len < sizeof(*l2cap_hdr)) {
+ NG_L2CAP_ERR(
+"%s: %s - invalid L2CAP packet start fragment. Packet too small, length=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ m->m_pkthdr.len);
+ error = EMSGSIZE;
+ goto drop;
+ }
+
+ NG_L2CAP_M_PULLUP(m, sizeof(*l2cap_hdr));
+ if (m == NULL)
+ return (ENOBUFS);
+
+ l2cap_hdr = mtod(m, ng_l2cap_hdr_t *);
+
+ NG_L2CAP_INFO(
+"%s: %s - staring new L2CAP packet, con_handle=%d, length=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con_handle,
+ le16toh(l2cap_hdr->length));
+
+ /* Start new L2CAP packet */
+ con->rx_pkt = m;
+ con->rx_pkt_len = le16toh(l2cap_hdr->length)+sizeof(*l2cap_hdr);
+ } else if (pb == NG_HCI_PACKET_FRAGMENT) {
+ if (con->rx_pkt == NULL) {
+ NG_L2CAP_ERR(
+"%s: %s - unexpected ACL data packet fragment, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ con->con_handle);
+ goto drop;
+ }
+
+ /* Add fragment to the L2CAP packet */
+ m_cat(con->rx_pkt, m);
+ con->rx_pkt->m_pkthdr.len += length;
+ } else {
+ NG_L2CAP_ERR(
+"%s: %s - invalid ACL data packet. Invalid PB flag=%#x\n",
+ __func__, NG_NODE_NAME(l2cap->node), pb);
+ error = EINVAL;
+ goto drop;
+ }
+
+ con->rx_pkt_len -= length;
+ if (con->rx_pkt_len < 0) {
+ NG_L2CAP_ALERT(
+"%s: %s - packet length mismatch. Got %d bytes, offset %d bytes\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ con->rx_pkt->m_pkthdr.len, con->rx_pkt_len);
+ NG_FREE_M(con->rx_pkt);
+ con->rx_pkt_len = 0;
+ } else if (con->rx_pkt_len == 0) {
+ /* OK, we have got complete L2CAP packet, so process it */
+ error = ng_l2cap_receive(con);
+ con->rx_pkt = NULL;
+ con->rx_pkt_len = 0;
+ }
+
+ return (error);
+
+drop:
+ NG_FREE_M(m);
+
+ return (error);
+} /* ng_l2cap_lp_receive */
+
+/*
+ * Send queued ACL packets to the HCI layer
+ */
+
+void
+ng_l2cap_lp_deliver(ng_l2cap_con_p con)
+{
+ ng_l2cap_p l2cap = con->l2cap;
+ struct mbuf *m = NULL;
+ int error;
+
+ /* Check connection */
+ if (con->state != NG_L2CAP_CON_OPEN)
+ return;
+
+ if (con->tx_pkt == NULL)
+ ng_l2cap_con_wakeup(con);
+
+ if (con->tx_pkt == NULL)
+ return;
+
+ /* Check if lower layer protocol is still connected */
+ if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) {
+ NG_L2CAP_ERR(
+"%s: %s - hook \"%s\" is not connected or valid",
+ __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI);
+
+ goto drop; /* XXX what to do with "pending"? */
+ }
+
+ /* Send ACL data packets */
+ while (con->pending < con->l2cap->num_pkts && con->tx_pkt != NULL) {
+ m = con->tx_pkt;
+ con->tx_pkt = con->tx_pkt->m_nextpkt;
+ m->m_nextpkt = NULL;
+
+ NG_L2CAP_INFO(
+"%s: %s - sending ACL packet, con_handle=%d, len=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->con_handle,
+ m->m_pkthdr.len);
+
+ NG_SEND_DATA_ONLY(error, l2cap->hci, m);
+ if (error != 0) {
+ NG_L2CAP_ERR(
+"%s: %s - could not send ACL data packet, con_handle=%d, error=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ con->con_handle, error);
+
+ goto drop; /* XXX what to do with "pending"? */
+ }
+
+ con->pending ++;
+ }
+
+ NG_L2CAP_INFO(
+"%s: %s - %d ACL packets have been sent, con_handle=%d\n",
+ __func__, NG_NODE_NAME(l2cap->node), con->pending,
+ con->con_handle);
+
+ return;
+
+drop:
+ while (con->tx_pkt != NULL) {
+ m = con->tx_pkt->m_nextpkt;
+ m_freem(con->tx_pkt);
+ con->tx_pkt = m;
+ }
+} /* ng_l2cap_lp_deliver */
+
+/*
+ * Process connection timeout. Remove connection from the list. If there
+ * are any channels that wait for the connection then notify them. Free
+ * connection descriptor.
+ */
+
+void
+ng_l2cap_process_lp_timeout(node_p node, hook_p hook, void *arg1, int arg2)
+{
+ ng_l2cap_con_p con = (ng_l2cap_con_p) arg1;
+ ng_l2cap_p l2cap = con->l2cap;
+
+ NG_L2CAP_ERR(
+"%s: %s - ACL connection timeout\n", __func__, NG_NODE_NAME(l2cap->node));
+
+ /*
+ * Notify channels that connection has timed out. This will remove
+ * connection, channels and pending commands.
+ */
+
+ con->state = NG_L2CAP_CON_CLOSED;
+ ng_l2cap_con_fail(con, NG_L2CAP_TIMEOUT);
+} /* ng_l2cap_process_lp_timeout */
+
OpenPOWER on IntegriCloud