summaryrefslogtreecommitdiffstats
path: root/sys/netgraph/bluetooth/hci/ng_hci_ulpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netgraph/bluetooth/hci/ng_hci_ulpi.c')
-rw-r--r--sys/netgraph/bluetooth/hci/ng_hci_ulpi.c1280
1 files changed, 1280 insertions, 0 deletions
diff --git a/sys/netgraph/bluetooth/hci/ng_hci_ulpi.c b/sys/netgraph/bluetooth/hci/ng_hci_ulpi.c
new file mode 100644
index 0000000..97a0ea8
--- /dev/null
+++ b/sys/netgraph/bluetooth/hci/ng_hci_ulpi.c
@@ -0,0 +1,1280 @@
+/*
+ * ng_hci_ulpi.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_hci_ulpi.c,v 1.14 2002/11/12 22:35:40 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_hci_var.h"
+#include "ng_hci_cmds.h"
+#include "ng_hci_evnt.h"
+#include "ng_hci_ulpi.h"
+#include "ng_hci_misc.h"
+
+/******************************************************************************
+ ******************************************************************************
+ ** Upper Layer Protocol Interface module
+ ******************************************************************************
+ ******************************************************************************/
+
+static int ng_hci_lp_acl_con_req (ng_hci_unit_p, item_p, hook_p);
+static int ng_hci_lp_sco_con_req (ng_hci_unit_p, item_p, hook_p);
+
+/*
+ * Process LP_ConnectReq event from the upper layer protocol
+ */
+
+int
+ng_hci_lp_con_req(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) {
+ NG_HCI_WARN(
+"%s: %s - unit is not ready, state=%#x\n",
+ __func__, NG_NODE_NAME(unit->node), unit->state);
+
+ NG_FREE_ITEM(item);
+
+ return (ENXIO);
+ }
+
+ if (NGI_MSG(item)->header.arglen != sizeof(ng_hci_lp_con_req_ep)) {
+ NG_HCI_ALERT(
+"%s: %s - invalid LP_ConnectReq message size=%d\n",
+ __func__, NG_NODE_NAME(unit->node),
+ NGI_MSG(item)->header.arglen);
+
+ NG_FREE_ITEM(item);
+
+ return (EMSGSIZE);
+ }
+
+ if (((ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data))->link_type == NG_HCI_LINK_ACL)
+ return (ng_hci_lp_acl_con_req(unit, item, hook));
+
+ if (hook != unit->sco) {
+ NG_HCI_WARN(
+"%s: %s - LP_ConnectReq for SCO connection came from wrong hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), hook);
+
+ NG_FREE_ITEM(item);
+
+ return (EINVAL);
+ }
+
+ return (ng_hci_lp_sco_con_req(unit, item, hook));
+} /* ng_hci_lp_con_req */
+
+/*
+ * Request to create new ACL connection
+ */
+
+static int
+ng_hci_lp_acl_con_req(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ struct acl_con_req {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_create_con_cp cp;
+ } __attribute__ ((packed)) *req = NULL;
+ ng_hci_lp_con_req_ep *ep = NULL;
+ ng_hci_unit_con_p con = NULL;
+ ng_hci_neighbor_t *n = NULL;
+ struct mbuf *m = NULL;
+ int error = 0;
+
+ ep = (ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data);
+
+ /*
+ * Only one ACL connection can exist between each pair of units.
+ * So try to find ACL connection descriptor (in any state) that
+ * has requested remote BD_ADDR.
+ *
+ * Two cases:
+ *
+ * 1) We do not have connection to the remote unit. This is simple.
+ * Just create new connection descriptor and send HCI command to
+ * create new connection.
+ *
+ * 2) We do have connection descriptor. We need to check connection
+ * state:
+ *
+ * 2.1) NG_HCI_CON_CLOSED mean we are in the process of closing
+ * connection to the remote unit. We will reject connection
+ * request until connection is closed.
+ *
+ * 2.2) NG_HCI_CON_W4_LP_CON_RSP means that we are in the middle of
+ * accepting connection from the remote unit. This is a race
+ * condition. We will ignore this message.
+ *
+ * 2.3) NG_HCI_CON_W4_CONN_COMPLETE means that upper layer already
+ * requested connection or we just accepted it. In any case
+ * all we need to do here is set appropriate notification bit
+ * and wait.
+ *
+ * 2.4) NG_HCI_CON_OPEN means connection is open. Just reply back
+ * and let upper layer know that we have connection already.
+ */
+
+ con = ng_hci_con_by_bdaddr(unit, &ep->bdaddr, NG_HCI_LINK_ACL);
+ if (con != NULL) {
+ switch (con->state) {
+ case NG_HCI_CON_CLOSED:
+ error = EBUSY;
+ break;
+
+ case NG_HCI_CON_W4_LP_CON_RSP: /* XXX */
+ error = EALREADY;
+ break;
+
+ case NG_HCI_CON_W4_CONN_COMPLETE:
+ if (hook == unit->acl)
+ con->flags |= NG_HCI_CON_NOTIFY_ACL;
+ else
+ con->flags |= NG_HCI_CON_NOTIFY_SCO;
+ break;
+
+ case NG_HCI_CON_OPEN: {
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_con_cfm_ep *cfm = NULL;
+
+ if (hook != NULL && NG_HOOK_IS_VALID(hook)) {
+ NGI_GET_MSG(item, msg);
+ NG_FREE_MSG(msg);
+
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE,
+ NGM_HCI_LP_CON_CFM, sizeof(*cfm),
+ M_NOWAIT);
+ if (msg != NULL) {
+ cfm = (ng_hci_lp_con_cfm_ep *)msg->data;
+ cfm->status = 0;
+ cfm->link_type = con->link_type;
+ cfm->con_handle = con->con_handle;
+ bcopy(&con->bdaddr, &cfm->bdaddr,
+ sizeof(cfm->bdaddr));
+
+ /*
+ * This will forward item back to
+ * sender and set item to NULL
+ */
+
+ _NGI_MSG(item) = msg;
+ NG_FWD_ITEM_HOOK(error, item, hook);
+ } else
+ error = ENOMEM;
+ } else
+ NG_HCI_INFO(
+"%s: %s - Source hook is not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node),
+ hook);
+ } break;
+
+ default:
+ KASSERT(0,
+("%s: %s - Invalid connection state=%d\n",
+ __func__, NG_NODE_NAME(unit->node),con->state));
+
+ error = EINVAL;
+ break;
+ }
+
+ goto out;
+ }
+
+ /*
+ * If we got here then we need to create new ACL connection descriptor
+ * and submit HCI command. First create new connection desriptor, set
+ * bdaddr and notification flags.
+ */
+
+ con = ng_hci_new_con(unit, NG_HCI_LINK_ACL);
+ if (con == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ bcopy(&ep->bdaddr, &con->bdaddr, sizeof(con->bdaddr));
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ ng_hci_free_con(con);
+ error = ENOBUFS;
+ goto out;
+ }
+
+ m->m_pkthdr.len = m->m_len = sizeof(*req);
+ req = mtod(m, struct acl_con_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+ req->hdr.length = sizeof(req->cp);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_CREATE_CON));
+
+ bcopy(&ep->bdaddr, &req->cp.bdaddr, sizeof(req->cp.bdaddr));
+
+ req->cp.pkt_type = (NG_HCI_PKT_DM1|NG_HCI_PKT_DH1);
+ if (unit->features[0] & NG_HCI_LMP_3SLOT)
+ req->cp.pkt_type |= (NG_HCI_PKT_DM3|NG_HCI_PKT_DH3);
+ if (unit->features[0] & NG_HCI_LMP_5SLOT)
+ req->cp.pkt_type |= (NG_HCI_PKT_DM5|NG_HCI_PKT_DH5);
+
+ req->cp.pkt_type &= unit->packet_mask;
+ if (req->cp.pkt_type == 0)
+ req->cp.pkt_type = (NG_HCI_PKT_DM1|NG_HCI_PKT_DH1);
+
+ req->cp.pkt_type = htole16(req->cp.pkt_type);
+
+ if (unit->features[0] & NG_HCI_LMP_SWITCH)
+ req->cp.accept_role_switch = 1;
+ else
+ req->cp.accept_role_switch = 0;
+
+ /*
+ * We may speed up connect by specifying valid parameters.
+ * So check the neighbor cache.
+ */
+
+ n = ng_hci_get_neighbor(unit, &ep->bdaddr);
+ if (n == NULL) {
+ req->cp.page_scan_rep_mode = 0;
+ req->cp.page_scan_mode = 0;
+ req->cp.clock_offset = 0;
+ } else {
+ req->cp.page_scan_rep_mode = n->page_scan_rep_mode;
+ req->cp.page_scan_mode = n->page_scan_mode;
+ req->cp.clock_offset = htole16(n->clock_offset);
+ }
+
+ /*
+ * Adust connection state
+ */
+
+ if (hook == unit->acl)
+ con->flags |= NG_HCI_CON_NOTIFY_ACL;
+ else
+ con->flags |= NG_HCI_CON_NOTIFY_SCO;
+
+ con->state = NG_HCI_CON_W4_CONN_COMPLETE;
+ ng_hci_con_timeout(con);
+
+ /*
+ * Queue and send HCI command
+ */
+
+ NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m);
+ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ error = ng_hci_send_command(unit);
+out:
+ if (item != NULL)
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_hci_lp_acl_con_req */
+
+/*
+ * Request to create new SCO connection
+ */
+
+static int
+ng_hci_lp_sco_con_req(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ struct sco_con_req {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_add_sco_con_cp cp;
+ } __attribute__ ((packed)) *req = NULL;
+ ng_hci_lp_con_req_ep *ep = NULL;
+ ng_hci_unit_con_p acl_con = NULL, sco_con = NULL;
+ struct mbuf *m = NULL;
+ int error = 0;
+
+ ep = (ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data);
+
+ /*
+ * SCO connection without ACL link
+ *
+ * If upper layer requests SCO connection and there is no open ACL
+ * connection to the desired remote unit, we will reject the request.
+ */
+
+ LIST_FOREACH(acl_con, &unit->con_list, next)
+ if (acl_con->link_type == NG_HCI_LINK_ACL &&
+ acl_con->state == NG_HCI_CON_OPEN &&
+ bcmp(&acl_con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0)
+ break;
+
+ if (acl_con == NULL) {
+ NG_HCI_INFO(
+"%s: %s - No open ACL connection to bdaddr=%x:%x:%x:%x:%x:%x\n",
+ __func__, NG_NODE_NAME(unit->node),
+ ep->bdaddr.b[5], ep->bdaddr.b[4], ep->bdaddr.b[3],
+ ep->bdaddr.b[2], ep->bdaddr.b[1], ep->bdaddr.b[0]);
+
+ error = ENOENT;
+ goto out;
+ }
+
+ /*
+ * Multiple SCO connections can exist between the same pair of units.
+ * We assume that multiple SCO connections have to be opened one after
+ * another.
+ *
+ * Try to find SCO connection descriptor that matches the following:
+ *
+ * 1) sco_con->link_type == NG_HCI_LINK_SCO
+ *
+ * 2) sco_con->state == NG_HCI_CON_W4_LP_CON_RSP ||
+ * sco_con->state == NG_HCI_CON_W4_CONN_COMPLETE
+ *
+ * 3) sco_con->bdaddr == ep->bdaddr
+ *
+ * Two cases:
+ *
+ * 1) We do not have connection descriptor. This is simple. Just
+ * create new connection and submit Add_SCO_Connection command.
+ *
+ * 2) We do have connection descriptor. We need to check the state.
+ *
+ * 2.1) NG_HCI_CON_W4_LP_CON_RSP means we in the middle of accepting
+ * connection from the remote unit. This is a race condition and
+ * we will ignore the request.
+ *
+ * 2.2) NG_HCI_CON_W4_CONN_COMPLETE means upper layer already requested
+ * connection or we just accepted it.
+ *
+ * XXX FIXME what to do with connection(s) in CLOSED state?
+ */
+
+ LIST_FOREACH(sco_con, &unit->con_list, next)
+ if (sco_con->link_type == NG_HCI_LINK_SCO &&
+ (sco_con->state == NG_HCI_CON_W4_LP_CON_RSP ||
+ sco_con->state == NG_HCI_CON_W4_CONN_COMPLETE) &&
+ bcmp(&sco_con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0)
+ break;
+
+ if (sco_con != NULL) {
+ switch (sco_con->state) {
+ case NG_HCI_CON_W4_LP_CON_RSP: /* XXX */
+ error = EALREADY;
+ break;
+
+ case NG_HCI_CON_W4_CONN_COMPLETE:
+ sco_con->flags |= NG_HCI_CON_NOTIFY_SCO;
+ break;
+
+ default:
+ KASSERT(0,
+("%s: %s - Inavalid connection state=%d\n",
+ __func__, NG_NODE_NAME(unit->node),
+ sco_con->state));
+
+ error = EINVAL;
+ break;
+ }
+
+ goto out;
+ }
+
+ /*
+ * If we got here then we need to create new SCO connection descriptor
+ * and submit HCI command.
+ */
+
+ sco_con = ng_hci_new_con(unit, NG_HCI_LINK_SCO);
+ if (sco_con == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ bcopy(&ep->bdaddr, &sco_con->bdaddr, sizeof(sco_con->bdaddr));
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ ng_hci_free_con(sco_con);
+ error = ENOBUFS;
+ goto out;
+ }
+
+ m->m_pkthdr.len = m->m_len = sizeof(*req);
+ req = mtod(m, struct sco_con_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+ req->hdr.length = sizeof(req->cp);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_ADD_SCO_CON));
+
+ req->cp.con_handle = htole16(acl_con->con_handle);
+
+ req->cp.pkt_type = NG_HCI_PKT_HV1;
+ if (unit->features[1] & NG_HCI_LMP_HV2_PKT)
+ req->cp.pkt_type |= NG_HCI_PKT_HV2;
+ if (unit->features[1] & NG_HCI_LMP_HV3_PKT)
+ req->cp.pkt_type |= NG_HCI_PKT_HV3;
+
+ req->cp.pkt_type &= unit->packet_mask;
+ if (req->cp.pkt_type == 0)
+ req->cp.pkt_type = NG_HCI_PKT_HV1;
+
+ req->cp.pkt_type = htole16(req->cp.pkt_type);
+
+ /*
+ * Adust connection state
+ */
+
+ sco_con->flags |= NG_HCI_CON_NOTIFY_SCO;
+
+ sco_con->state = NG_HCI_CON_W4_CONN_COMPLETE;
+ ng_hci_con_timeout(sco_con);
+
+ /*
+ * Queue and send HCI command
+ */
+
+ NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m);
+ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ error = ng_hci_send_command(unit);
+out:
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_hci_lp_sco_con_req */
+
+/*
+ * Process LP_DisconnectReq event from the upper layer protocol
+ *
+ * XXX XXX XXX
+ *
+ * NOTE: This is NOT defined by Bluetooth specification (why?) But i think
+ * this might be useful (at least for testing), so please do not depend on
+ * this interface.
+ */
+
+int
+ng_hci_lp_discon_req(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ struct discon_req {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_discon_cp cp;
+ } __attribute__ ((packed)) *req = NULL;
+ ng_hci_lp_discon_req_ep *ep = NULL;
+ ng_hci_unit_con_p con = NULL;
+ struct mbuf *m = NULL;
+ int error = 0;
+
+ /* Check if unit is ready */
+ if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) {
+ NG_HCI_WARN(
+"%s: %s - unit is not ready, state=%#x\n",
+ __func__, NG_NODE_NAME(unit->node), unit->state);
+
+ error = ENXIO;
+ goto out;
+ }
+
+ if (NGI_MSG(item)->header.arglen != sizeof(*ep)) {
+ NG_HCI_ALERT(
+"%s: %s - invalid LP_DisconnectReq message size=%d\n",
+ __func__, NG_NODE_NAME(unit->node),
+ NGI_MSG(item)->header.arglen);
+
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_discon_req_ep *)(NGI_MSG(item)->data);
+
+ con = ng_hci_con_by_handle(unit, ep->con_handle);
+ if (con == NULL) {
+ NG_HCI_ERR(
+"%s: %s - invalid connection handle=%d\n",
+ __func__, NG_NODE_NAME(unit->node), ep->con_handle);
+
+ error = ENOENT;
+ goto out;
+ }
+
+ if (con->state != NG_HCI_CON_OPEN) {
+ NG_HCI_ERR(
+"%s: %s - invalid connection state=%d, handle=%d\n",
+ __func__, NG_NODE_NAME(unit->node), con->state,
+ ep->con_handle);
+
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ error = ENOBUFS;
+ goto out;
+ }
+
+ m->m_pkthdr.len = m->m_len = sizeof(*req);
+ req = mtod(m, struct discon_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+ req->hdr.length = sizeof(req->cp);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_DISCON));
+
+ req->cp.con_handle = htole16(ep->con_handle);
+ req->cp.reason = ep->reason;
+
+ /*
+ * Adjust connection state
+ */
+
+ con->state = NG_HCI_CON_CLOSED;
+ ng_hci_con_timeout(con);
+
+ /*
+ * Queue and send HCI command
+ */
+
+ NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m);
+ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ error = ng_hci_send_command(unit);
+out:
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_hci_lp_discon_req */
+
+/*
+ * Send LP_ConnectCfm event to the upper layer protocol
+ */
+
+int
+ng_hci_lp_con_cfm(ng_hci_unit_con_p con, int status)
+{
+ ng_hci_unit_p unit = con->unit;
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_con_cfm_ep *ep = NULL;
+ int error;
+
+ /*
+ * Check who wants to be notified. For ACL links both ACL and SCO
+ * upstream hooks will be notified (if required). For SCO links
+ * only SCO upstream hook will receive notification
+ */
+
+ if (con->link_type == NG_HCI_LINK_ACL &&
+ con->flags & NG_HCI_CON_NOTIFY_ACL) {
+ if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_CFM,
+ sizeof(*ep), M_NOWAIT);
+ if (msg != NULL) {
+ ep = (ng_hci_lp_con_cfm_ep *) msg->data;
+ ep->status = status;
+ ep->link_type = con->link_type;
+ ep->con_handle = con->con_handle;
+ bcopy(&con->bdaddr, &ep->bdaddr,
+ sizeof(ep->bdaddr));
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg,
+ unit->acl, NULL);
+ }
+ } else
+ NG_HCI_INFO(
+"%s: %s - ACL hook not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->acl);
+
+ con->flags &= ~NG_HCI_CON_NOTIFY_ACL;
+ }
+
+ if (con->flags & NG_HCI_CON_NOTIFY_SCO) {
+ if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_CFM,
+ sizeof(*ep), M_NOWAIT);
+ if (msg != NULL) {
+ ep = (ng_hci_lp_con_cfm_ep *) msg->data;
+ ep->status = status;
+ ep->link_type = con->link_type;
+ ep->con_handle = con->con_handle;
+ bcopy(&con->bdaddr, &ep->bdaddr,
+ sizeof(ep->bdaddr));
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg,
+ unit->sco, NULL);
+ }
+ } else
+ NG_HCI_INFO(
+"%s: %s - SCO hook not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->acl);
+
+ con->flags &= ~NG_HCI_CON_NOTIFY_SCO;
+ }
+
+ return (0);
+} /* ng_hci_lp_con_cfm */
+
+/*
+ * Send LP_ConnectInd event to the upper layer protocol
+ */
+
+int
+ng_hci_lp_con_ind(ng_hci_unit_con_p con, u_int8_t *uclass)
+{
+ ng_hci_unit_p unit = con->unit;
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_con_ind_ep *ep = NULL;
+ hook_p hook = NULL;
+ int error = 0;
+
+ /*
+ * Connection_Request event is generated for specific link type.
+ * Use link_type to select upstream hook.
+ */
+
+ if (con->link_type == NG_HCI_LINK_ACL)
+ hook = unit->acl;
+ else
+ hook = unit->sco;
+
+ if (hook != NULL && NG_HOOK_IS_VALID(hook)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_IND,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_con_ind_ep *)(msg->data);
+ ep->link_type = con->link_type;
+ bcopy(uclass, ep->uclass, sizeof(ep->uclass));
+ bcopy(&con->bdaddr, &ep->bdaddr, sizeof(ep->bdaddr));
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg, hook, NULL);
+ } else {
+ NG_HCI_WARN(
+"%s: %s - Upstream hook is not connected or not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), hook);
+
+ error = ENOTCONN;
+ }
+
+ return (error);
+} /* ng_hci_lp_con_ind */
+
+/*
+ * Process LP_ConnectRsp event from the upper layer protocol
+ */
+
+int
+ng_hci_lp_con_rsp(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ struct con_rsp_req {
+ ng_hci_cmd_pkt_t hdr;
+ union {
+ ng_hci_accept_con_cp acc;
+ ng_hci_reject_con_cp rej;
+ } __attribute__ ((packed)) cp;
+ } __attribute__ ((packed)) *req = NULL;
+ ng_hci_lp_con_rsp_ep *ep = NULL;
+ ng_hci_unit_con_p con = NULL;
+ struct mbuf *m = NULL;
+ int error = 0;
+
+ /* Check if unit is ready */
+ if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) {
+ NG_HCI_WARN(
+"%s: %s - unit is not ready, state=%#x\n",
+ __func__, NG_NODE_NAME(unit->node), unit->state);
+
+ error = ENXIO;
+ goto out;
+ }
+
+ if (NGI_MSG(item)->header.arglen != sizeof(*ep)) {
+ NG_HCI_ALERT(
+"%s: %s - invalid LP_ConnectRsp message size=%d\n",
+ __func__, NG_NODE_NAME(unit->node),
+ NGI_MSG(item)->header.arglen);
+
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_con_rsp_ep *)(NGI_MSG(item)->data);
+
+ /*
+ * Here we have to deal with race. Upper layers might send conflicting
+ * requests. One might send Accept and other Reject. We will not try
+ * to solve all the problems, so first request will always win.
+ *
+ * Try to find connection that matches the following:
+ *
+ * 1) con->link_type == ep->link_type
+ *
+ * 2) con->state == NG_HCI_CON_W4_LP_CON_RSP ||
+ * con->state == NG_HCI_CON_W4_CONN_COMPLETE
+ *
+ * 3) con->bdaddr == ep->bdaddr
+ *
+ * Two cases:
+ *
+ * 1) We do not have connection descriptor. Could be bogus request or
+ * we have rejected connection already.
+ *
+ * 2) We do have connection descriptor. Then we need to check state:
+ *
+ * 2.1) NG_HCI_CON_W4_LP_CON_RSP means upper layer has requested
+ * connection and it is a first response from the upper layer.
+ * if "status == 0" (Accept) then we will send Accept_Connection
+ * command and change connection state to W4_CONN_COMPLETE, else
+ * send reject and delete connection.
+ *
+ * 2.2) NG_HCI_CON_W4_CONN_COMPLETE means that we already accepted
+ * connection. If "status == 0" we just need to link request
+ * and wait, else ignore Reject request.
+ */
+
+ LIST_FOREACH(con, &unit->con_list, next)
+ if (con->link_type == ep->link_type &&
+ (con->state == NG_HCI_CON_W4_LP_CON_RSP ||
+ con->state == NG_HCI_CON_W4_CONN_COMPLETE) &&
+ bcmp(&con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0)
+ break;
+
+ if (con == NULL) {
+ /* Reject for non-existing connection is fine */
+ error = (ep->status == 0)? ENOENT : 0;
+ goto out;
+ }
+
+ /*
+ * Remove connection timeout and check connection state
+ */
+
+ ng_hci_con_untimeout(con);
+
+ switch (con->state) {
+ case NG_HCI_CON_W4_LP_CON_RSP:
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ error = ENOBUFS;
+ goto out;
+ }
+
+ req = mtod(m, struct con_rsp_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+
+ if (ep->status == 0) {
+ req->hdr.length = sizeof(req->cp.acc);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(
+ NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_ACCEPT_CON));
+
+ bcopy(&ep->bdaddr, &req->cp.acc.bdaddr,
+ sizeof(req->cp.acc.bdaddr));
+
+ /*
+ * XXX should be configurable?
+ *
+ * We are accepting connection, so if we support role
+ * switch then set role to NG_HCI_ROLE_MASTER and let
+ * LM peform role switch. Otherwise it is probably
+ * makes sense to remain slave. In this case LM WILL
+ * NOT perform role switch.
+ */
+
+ if (unit->features[0] & NG_HCI_LMP_SWITCH)
+ req->cp.acc.role = NG_HCI_ROLE_MASTER;
+ else
+ req->cp.acc.role = NG_HCI_ROLE_SLAVE;
+
+ /*
+ * Adjust connection state
+ */
+
+ if (hook == unit->acl)
+ con->flags |= NG_HCI_CON_NOTIFY_ACL;
+ else
+ con->flags |= NG_HCI_CON_NOTIFY_SCO;
+
+ con->state = NG_HCI_CON_W4_CONN_COMPLETE;
+ ng_hci_con_timeout(con);
+ } else {
+ req->hdr.length = sizeof(req->cp.rej);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(
+ NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_REJECT_CON));
+
+ bcopy(&ep->bdaddr, &req->cp.rej.bdaddr,
+ sizeof(req->cp.rej.bdaddr));
+
+ req->cp.rej.reason = ep->status;
+
+ /*
+ * Free connection descritor
+ * Item will be deleted just before return.
+ */
+
+ ng_hci_free_con(con);
+ }
+
+ m->m_pkthdr.len = m->m_len = sizeof(req->hdr) + req->hdr.length;
+
+ /* Queue and send HCI command */
+ NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m);
+ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ error = ng_hci_send_command(unit);
+ break;
+
+ case NG_HCI_CON_W4_CONN_COMPLETE:
+ if (ep->status == 0) {
+ if (hook == unit->acl)
+ con->flags |= NG_HCI_CON_NOTIFY_ACL;
+ else
+ con->flags |= NG_HCI_CON_NOTIFY_SCO;
+ } else
+ error = EPERM;
+ break;
+
+ default:
+ KASSERT(0,
+("%s: %s - Invalid connection state=%d\n",
+ __func__, NG_NODE_NAME(unit->node), con->state));
+
+ error = EINVAL;
+ break;
+ }
+out:
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_hci_lp_con_rsp */
+
+/*
+ * Send LP_DisconnectInd to the upper layer protocol
+ */
+
+int
+ng_hci_lp_discon_ind(ng_hci_unit_con_p con, int reason)
+{
+ ng_hci_unit_p unit = con->unit;
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_discon_ind_ep *ep = NULL;
+ int error = 0;
+
+ /*
+ * Disconnect_Complete event is generated for specific connection
+ * handle. For ACL connection handles both ACL and SCO upstream
+ * hooks will receive notification. For SCO connection handles
+ * only SCO upstream hook will receive notification.
+ */
+
+ if (con->link_type == NG_HCI_LINK_ACL) {
+ if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE,
+ NGM_HCI_LP_DISCON_IND, sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_discon_ind_ep *) msg->data;
+ ep->reason = reason;
+ ep->link_type = con->link_type;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error,unit->node,msg,unit->acl,NULL);
+ } else
+ NG_HCI_INFO(
+"%s: %s - ACL hook is not connected or not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->acl);
+ }
+
+ if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_DISCON_IND,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_discon_ind_ep *) msg->data;
+ ep->reason = reason;
+ ep->link_type = con->link_type;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg, unit->sco, NULL);
+ } else
+ NG_HCI_INFO(
+"%s: %s - SCO hook is not connected or not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->sco);
+
+ return (0);
+} /* ng_hci_lp_discon_ind */
+
+/*
+ * Process LP_QoSReq action from the upper layer protocol
+ */
+
+int
+ng_hci_lp_qos_req(ng_hci_unit_p unit, item_p item, hook_p hook)
+{
+ struct qos_setup_req {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_qos_setup_cp cp;
+ } __attribute__ ((packed)) *req = NULL;
+ ng_hci_lp_qos_req_ep *ep = NULL;
+ ng_hci_unit_con_p con = NULL;
+ struct mbuf *m = NULL;
+ int error = 0;
+
+ /* Check if unit is ready */
+ if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) {
+ NG_HCI_WARN(
+"%s: %s - unit is not ready, state=%#x\n",
+ __func__, NG_NODE_NAME(unit->node), unit->state);
+
+ error = ENXIO;
+ goto out;
+ }
+
+ if (NGI_MSG(item)->header.arglen != sizeof(*ep)) {
+ NG_HCI_ALERT(
+"%s: %s - invalid LP_QoSSetupReq message size=%d\n",
+ __func__, NG_NODE_NAME(unit->node),
+ NGI_MSG(item)->header.arglen);
+
+ error = EMSGSIZE;
+ goto out;
+ }
+
+ ep = (ng_hci_lp_qos_req_ep *)(NGI_MSG(item)->data);
+
+ con = ng_hci_con_by_handle(unit, ep->con_handle);
+ if (con == NULL) {
+ NG_HCI_ERR(
+"%s: %s - invalid connection handle=%d\n",
+ __func__, NG_NODE_NAME(unit->node), ep->con_handle);
+
+ error = EINVAL;
+ goto out;
+ }
+
+ if (con->link_type != NG_HCI_LINK_ACL) {
+ NG_HCI_ERR("%s: %s - invalid link type=%d\n",
+ __func__, NG_NODE_NAME(unit->node), con->link_type);
+
+ error = EINVAL;
+ goto out;
+ }
+
+ if (con->state != NG_HCI_CON_OPEN) {
+ NG_HCI_ERR(
+"%s: %s - invalid connection state=%d, handle=%d\n",
+ __func__, NG_NODE_NAME(unit->node), con->state,
+ con->con_handle);
+
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ error = ENOBUFS;
+ goto out;
+ }
+
+ m->m_pkthdr.len = m->m_len = sizeof(*req);
+ req = mtod(m, struct qos_setup_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+ req->hdr.length = sizeof(req->cp);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY,
+ NG_HCI_OCF_QOS_SETUP));
+
+ req->cp.con_handle = htole16(ep->con_handle);
+ req->cp.flags = ep->flags;
+ req->cp.service_type = ep->service_type;
+ req->cp.token_rate = htole32(ep->token_rate);
+ req->cp.peak_bandwidth = htole32(ep->peak_bandwidth);
+ req->cp.latency = htole32(ep->latency);
+ req->cp.delay_variation = htole32(ep->delay_variation);
+
+ /*
+ * Adjust connection state
+ */
+
+ if (hook == unit->acl)
+ con->flags |= NG_HCI_CON_NOTIFY_ACL;
+ else
+ con->flags |= NG_HCI_CON_NOTIFY_SCO;
+
+ /*
+ * Queue and send HCI command
+ */
+
+ NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m);
+ if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ error = ng_hci_send_command(unit);
+out:
+ NG_FREE_ITEM(item);
+
+ return (error);
+} /* ng_hci_lp_qos_req */
+
+/*
+ * Send LP_QoSCfm event to the upper layer protocol
+ */
+
+int
+ng_hci_lp_qos_cfm(ng_hci_unit_con_p con, int status)
+{
+ ng_hci_unit_p unit = con->unit;
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_qos_cfm_ep *ep = NULL;
+ int error;
+
+ if (con->flags & NG_HCI_CON_NOTIFY_ACL) {
+ if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_CFM,
+ sizeof(*ep), M_NOWAIT);
+ if (msg != NULL) {
+ ep = (ng_hci_lp_qos_cfm_ep *) msg->data;
+ ep->status = status;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg,
+ unit->acl, NULL);
+ }
+ } else
+ NG_HCI_INFO(
+"%s: %s - ACL hook not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->acl);
+
+ con->flags &= ~NG_HCI_CON_NOTIFY_ACL;
+ }
+
+ if (con->flags & NG_HCI_CON_NOTIFY_SCO) {
+ if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_CFM,
+ sizeof(*ep), M_NOWAIT);
+ if (msg != NULL) {
+ ep = (ng_hci_lp_qos_cfm_ep *) msg->data;
+ ep->status = status;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg,
+ unit->sco, NULL);
+ }
+ } else
+ NG_HCI_INFO(
+"%s: %s - SCO hook not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->sco);
+
+ con->flags &= ~NG_HCI_CON_NOTIFY_SCO;
+ }
+
+ return (0);
+} /* ng_hci_lp_qos_cfm */
+
+/*
+ * Send LP_QoSViolationInd event to the upper layer protocol
+ */
+
+int
+ng_hci_lp_qos_ind(ng_hci_unit_con_p con)
+{
+ ng_hci_unit_p unit = con->unit;
+ struct ng_mesg *msg = NULL;
+ ng_hci_lp_qos_ind_ep *ep = NULL;
+ int error;
+
+ /*
+ * QoS Violation can only be generated for ACL connection handles.
+ * Both ACL and SCO upstream hooks will receive notification.
+ */
+
+ if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_IND,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_qos_ind_ep *) msg->data;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg, unit->acl, NULL);
+ } else
+ NG_HCI_INFO(
+"%s: %s - ACL hook is not connected or not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->acl);
+
+ if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) {
+ NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_IND,
+ sizeof(*ep), M_NOWAIT);
+ if (msg == NULL)
+ return (ENOMEM);
+
+ ep = (ng_hci_lp_qos_ind_ep *) msg->data;
+ ep->con_handle = con->con_handle;
+
+ NG_SEND_MSG_HOOK(error, unit->node, msg, unit->sco, NULL);
+ } else
+ NG_HCI_INFO(
+"%s: %s - SCO hook is not connected or not valid, hook=%p\n",
+ __func__, NG_NODE_NAME(unit->node), unit->sco);
+
+ return (0);
+} /* ng_hci_lp_qos_ind */
+
+/*
+ * Process connection timeout
+ */
+
+void
+ng_hci_process_con_timeout(node_p node, hook_p hook, void *arg1, int arg2)
+{
+ ng_hci_unit_con_p con = (ng_hci_unit_con_p) arg1;
+
+ KASSERT((con->flags & NG_HCI_CON_TIMEOUT_PENDING),
+("%s: %s - No connection timeout!\n", __func__, NG_NODE_NAME(node)));
+
+ con->flags &= ~NG_HCI_CON_TIMEOUT_PENDING;
+
+ /*
+ * We expect to receive connection timeout in one of the following
+ * states:
+ *
+ * 1) NG_HCI_CON_CLOSED means that upper layer has requested disconnect
+ * via LP_DISCON_REQ and we have not received Disconnect_Complete
+ * event. In this case we will send LP_DISCON_IND to upper layer.
+ *
+ * 2) NG_HCI_CON_W4_LP_CON_RSP means that upper layer has not responded
+ * to our LP_CON_IND. Do nothing and destroy connection. Remote peer
+ * most likely already gave up on us.
+ *
+ * 3) NG_HCI_CON_W4_CONN_COMPLETE means upper layer requested connection
+ * (or we in the process of accepting it) and baseband has timedout
+ * on us. Inform upper layers and send LP_CON_CFM.
+ */
+
+ switch (con->state) {
+ case NG_HCI_CON_CLOSED:
+ ng_hci_lp_discon_ind(con, 0x16);
+ break;
+
+ case NG_HCI_CON_W4_LP_CON_RSP:
+ break;
+
+ case NG_HCI_CON_W4_CONN_COMPLETE:
+ ng_hci_lp_con_cfm(con, 0xee);
+ break;
+
+ default:
+ KASSERT(0,
+("%s: %s - Invalid connection state=%d\n",
+ __func__, NG_NODE_NAME(node), con->state));
+ break;
+ }
+
+ ng_hci_free_con(con);
+} /* ng_hci_process_con_timeout */
+
+/*
+ * Process connection watchdog timeout
+ */
+
+void
+ng_hci_process_con_watchdog_timeout(node_p node, hook_p hook,
+ void *arg1, int arg2)
+{
+ ng_hci_unit_con_p con = (ng_hci_unit_con_p) arg1;
+ struct discon_req {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_discon_cp cp;
+ } __attribute__ ((packed)) *req = NULL;
+ struct mbuf *m = NULL;
+
+ KASSERT((con->state == NG_HCI_CON_OPEN),
+("%s: %s - invalid connection state=%d, handle=%d\n",
+ __func__, NG_NODE_NAME(node), con->state, con->con_handle));
+
+ KASSERT((con->flags & NG_HCI_CON_WATCHDOG_TIMEOUT_PENDING),
+("%s: %s - No connection watchdog timeout!\n",
+ __func__, NG_NODE_NAME(node)));
+
+ con->flags &= ~NG_HCI_CON_WATCHDOG_TIMEOUT_PENDING;
+
+ /*
+ * Create HCI command
+ */
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return; /* XXX this is bad */
+
+ m->m_pkthdr.len = m->m_len = sizeof(*req);
+ req = mtod(m, struct discon_req *);
+ req->hdr.type = NG_HCI_CMD_PKT;
+ req->hdr.length = sizeof(req->cp);
+ req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_DISCON));
+
+ req->cp.con_handle = htole16(con->con_handle);
+ req->cp.reason = 0x13; /* User ended connection */
+
+ /*
+ * Queue and send HCI command
+ */
+
+ NG_BT_MBUFQ_ENQUEUE(&con->unit->cmdq, m);
+ if (!(con->unit->state & NG_HCI_UNIT_COMMAND_PENDING))
+ ng_hci_send_command(con->unit);
+
+ /*
+ * Send LP_DISCON_IND to the upper layers
+ * Connection terminated by local host
+ */
+
+ ng_hci_lp_discon_ind(con, 0x16);
+ ng_hci_free_con(con);
+} /* ng_hci_process_con_watchdog_timeout */
+
OpenPOWER on IntegriCloud