| /* |
| * Copyright 2018 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| //todo: only one LE connection attempt can exist at a time |
| //our "extra" param for event filtering canhandle other events to as long as we're willing to have a giant switch-case... and check against it in all structs |
| //todo: always request remote versions & features before using them (eg: le encrypt support) and before telling anyone a link is up! |
| //todo: always set flush timout to infinite |
| //todo: since only one LE connection attempt can exist at a time, enqueue others... |
| //todo: when link up do not upcall to l2cap until we have all features and versions from the other side gotten already! |
| //todo: The LE_Create_Connection_Cancel command is used to cancel the LE_Create_Connection command. This command shall only be issued after the LE_Create_Connection command has been issued, a Command Status event has been received for the LE Create Connection command and before the LE Connection Complete event |
| |
| #define _INCLUDED_FROM_HCI_H_ |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include "newblue-macros.h" |
| #include "workQueue.h" |
| #include "vendorLib.h" |
| #include "hci_int.h" |
| #include "persist.h" |
| #include "config.h" |
| #include "timer.h" |
| #include "l2cap.h" |
| #include "aapi.h" |
| #include "util.h" |
| #include "uniq.h" |
| #include "hci.h" |
| #include "log.h" |
| #include "mt.h" |
| |
| |
| #define MAX_FTR_PAGES 4 /* we'll not get any more than this */ |
| #define HCI_STAT_GOING_DOWN 0xFF |
| |
| |
| struct hciCmdHdr { |
| uint16_t opcode; |
| uint8_t paramLen; |
| } __packed; |
| #define CMD_MAKE_OPCODE(ogf, ocf) ((((uint16_t)((ogf) & 0x3f)) << 10) | ((ocf) & 0x03ff)) |
| #define CMD_GET_OGF(opcode) (((opcode) >> 10) & 0x3f) |
| #define CMD_GET_OCF(opcode) ((opcode) & 0x03ff) |
| |
| |
| struct hciAclHdr { |
| uint16_t hdr; |
| uint16_t len; |
| } __packed; |
| #define ACL_HDR_MASK_CONN_ID 0x0FFF |
| #define ACL_HDR_MASK_PB 0x3000 |
| #define ACL_HDR_MASK_BC 0xC000 |
| #define ACL_HDR_PB_FIRST_NONAUTO 0x0000 |
| #define ACL_HDR_PB_CONINUED 0x1000 |
| #define ACL_HDR_PB_FIRST_AUTO 0x2000 |
| #define ACL_HDR_PB_COMPLETE 0x3000 |
| |
| struct hciScoHdr { |
| uint16_t hdr; |
| uint8_t len; |
| } __packed; |
| #define SCO_HDR_MASK_CONN_ID 0x0FFF |
| #define SCO_HDR_MASK_STATUS 0x3000 |
| #define SCO_STATUS_ALL_OK 0x0000 |
| #define SCO_STATUS_UNKNOWN 0x1000 |
| #define SCO_STATUS_NO_DATA 0x2000 |
| #define SCO_STATUS_SOME_DATA 0x3000 |
| |
| struct hciEvtHdr { |
| uint8_t code; |
| uint8_t len; |
| } __packed; |
| |
| #define ACL_CONN_ID_INVALID (ACL_HDR_MASK_CONN_ID + 2) |
| |
| /* returns true if this is the event we wanted, false else. Do not block! If evt is passed as NULL, stack is shutting down. Do not try to enqueue/dequeue handlers here either */ |
| typedef bool (*hciEvtCbk)(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID); |
| typedef void (*hciSimpleCbk)(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt); |
| |
| struct hciEvtWaitState { |
| uniq_t evtWaitStateID; /* used to cancel THIS waiter */ |
| uniq_t forCmdID; /* used to cancel ALL waiters for this CMD */ |
| |
| struct hciEvtWaitState *next; |
| struct hciEvtWaitState *prev; |
| |
| hciEvtCbk cbk; |
| void *cbkData; |
| |
| uint16_t extra; /* cmd for cmd complete/statue, subevent for LE */ |
| uint8_t evtType; |
| |
| bool persistent; /* do not delete when it fires */ |
| }; |
| |
| struct hciEvtWaitDescr { |
| uint8_t evtType; /* == 0 -> not valid/not enabled */ |
| uint16_t extra; |
| hciEvtCbk cbk; |
| void *cbkData; |
| bool persistent; |
| }; |
| |
| struct hciEvtWaitDescrWithID { |
| struct hciEvtWaitDescr descr; |
| uniq_t id; |
| }; |
| |
| struct hciCmdSubmitWithCompleteSyncData { |
| sem_t *sem; |
| void *evtBuf; |
| uint8_t evtBufSz; |
| bool goingDown; |
| }; |
| |
| struct hciCmdSendWorkItem { |
| uniq_t cmdID; |
| uint8_t numEventWaiters; |
| sg cmd; |
| |
| /* even handlers */ |
| struct hciEvtWaitDescrWithID ewds[]; |
| }; |
| |
| #define CBK_WORK_ITEM_DONE_CBK 0 |
| #define CBK_WORK_ITEM_SIMPLE_CBK 1 |
| #define CBK_WORK_LE_DISC_DEV 2 |
| #define CBK_WORK_FLUSH 3 |
| #define CBK_WORK_CONN_UP 4 |
| #define CBK_WORK_CONN_DOWN 5 |
| #define CBK_WORK_PARAMS_CHANGE 6 |
| #define CBK_WORK_ENCR_CHANGE 7 |
| #define CBK_WORK_ACL_DATA 8 |
| #define CBK_WORK_LE_KEY_REQ 9 |
| #define CBK_WORK_ENCR_KEY_REFRESH 10 |
| #define CBK_WORK_MAKE_NEXT_LE_CONN 11 |
| #define CBK_WORK_ACL_RX 12 |
| |
| struct hciCbkWorkItem { |
| uint8_t cbkWorkItemType; |
| |
| union { |
| struct { |
| hciOpDoneCbk cbk; |
| void *cbkData; |
| uint8_t status; |
| } done; |
| |
| struct { |
| hciSimpleCbk cbk; |
| void *cbkData1; |
| void *cbkData2; |
| bool stackGoingDown; |
| bool haveEvt; |
| } simple; |
| |
| struct { |
| struct bt_addr addr; |
| uint8_t advType; |
| int8_t rssi; |
| uint8_t advLen; |
| uint8_t adv[]; |
| } leDev; |
| |
| struct { |
| sem_t *sem; |
| } flush; |
| |
| struct { |
| hci_conn_t conn; |
| struct bt_addr peerAddr; |
| struct bt_addr selfAddr; |
| bool isMaster; |
| bool isEncrypted; |
| bool isMitmSafe; |
| } connUp; |
| |
| struct { |
| hci_conn_t conn; |
| uint8_t reason; |
| } connDown; |
| |
| struct { |
| hci_conn_t conn; |
| bool success; |
| uint16_t interval; |
| uint16_t latency; |
| uint16_t timeout; |
| } leParamChange; |
| |
| struct { |
| hci_conn_t conn; |
| bool nowEncrypted; |
| bool isMitmSafe; |
| } encrChange; |
| |
| struct { |
| hci_conn_t conn; |
| bool nowEncrypted; |
| bool isMitmSafe; |
| } encrKeyRefresh; |
| |
| struct { |
| hci_conn_t conn; |
| sg packet; |
| bool first; |
| } aclDataRx; |
| |
| struct { |
| hci_conn_t conn; |
| uint64_t randomNum; |
| uint16_t diversifier; |
| } leKeyReq; |
| |
| struct { |
| uint16_t hdr; |
| sg packet; |
| } aclRx; |
| }; |
| }; |
| |
| struct hciBacklogItemHdr { |
| uint32_t len; |
| uint16_t aclHdr; |
| } __packed; |
| |
| struct hciSimpleCbkData { |
| hciSimpleCbk cbk; /* may be null */ |
| void *cbkData1; |
| void *cbkData2; |
| uint8_t expectedParamLenMin; |
| uint8_t expectedParamLenMax; |
| uint8_t wantEventItself :1; |
| uint8_t complete :1; |
| }; |
| |
| struct hciAclConn { |
| struct hciAclConn *next; |
| struct hciAclConn *prev; |
| struct bt_addr peerAddr; |
| struct bt_addr selfAddr; |
| hci_conn_t handle; |
| |
| uint16_t id; |
| bool isMaster; |
| bool encrypted; |
| bool mitmSafe; |
| uint8_t state; /* CONN_STATE_* */ |
| sg rxBacklog; /* data we RXed during configuration stage */ |
| uint32_t outstandingPackets; |
| |
| union { |
| struct { |
| uint64_t ftrs; |
| uint16_t interval; |
| uint16_t latency; |
| uint16_t timeout; |
| uint8_t mca; |
| hci_adv_set_t advSet; |
| } le; |
| //EDR struct here if needed |
| }; |
| }; |
| |
| struct hciAdvSet { |
| /* bookkeeping */ |
| struct hciAdvSet *next; |
| struct hciAdvSet *prev; |
| hci_adv_set_t handle; |
| |
| /* hw state */ |
| uint16_t hwSetNum; |
| bool hwEnabled; |
| int8_t hwPowerLevel; /* if hw supports power setting, must be populated when enabled */ |
| |
| /* user data */ |
| uint32_t advDataLen; |
| uint32_t scanRspDataLen; |
| uint8_t advData[HCI_ADV_DATA_MAX_LEN]; |
| uint8_t scanRspData[HCI_SCAN_RSP_DATA_MAX_LEN]; |
| uint16_t intervalMin; |
| uint16_t intervalMax; |
| uint8_t advType; |
| uint8_t ownAddressType; |
| struct bt_addr ownRandomAddr; //only used if random addr selected |
| struct bt_addr directAddr; |
| uint8_t channelMap; |
| uint8_t filterPolicy; |
| int8_t powerLevel; |
| }; |
| |
| struct hciLeConnReq { |
| struct hciLeConnReq *next; |
| hci_conn_t aclConn; |
| uint16_t scanInt; |
| uint16_t scanWindow; |
| uint16_t intMin; |
| uint16_t intMax; |
| uint16_t latency; |
| uint16_t timeout; |
| bool useRandomAddr; |
| uint8_t selfRandomAddr[BT_MAC_LEN]; |
| }; |
| |
| #define CONN_STATE_WAIT 0 |
| #define CONN_STATE_TRYING 1 |
| #define CONN_STATE_ADV_SET_BLAMING 2 |
| #define CONN_STATE_CFG 3 |
| #define CONN_STATE_RUNNING 4 |
| #define CONN_STATE_DELETING 5 |
| |
| #define CONN_CFG_STEP_LE 0x0000 |
| |
| #define ADV_SET_BLAME_TIMEOUT_ANDROID_VENDOR 100 /* 100ms is as good a number as any */ |
| |
| #define STACK_STATE_DOWN 0 //down and not ready for up |
| #define STACK_STATE_READY_FOR_UP 1 //ready to be brought up |
| #define STACK_STATE_COMING_UP 2 //on the way up |
| #define STACK_STATE_UP 3 //working |
| #define STACK_STATE_GOING_DOWN 4 //on the way down |
| |
| /* useful constants */ |
| static const char *mBtVers[] = {"1.0b", "1.1", "1.2", "2.0", "2.1", "3.0", "4.0", "4.1", "4.2", "5.0"}; |
| |
| /* our state */ |
| |
| static pthread_mutex_t mCmdWaitLock = PTHREAD_MUTEX_INITIALIZER; |
| static struct hciEvtWaitState *mEvtWaitHead = NULL; |
| static struct hciEvtWaitState *mEvtWaitTail = NULL; |
| |
| static pthread_mutex_t mCmdLock = PTHREAD_MUTEX_INITIALIZER; |
| static sem_t mCmdSendSem; |
| static struct workQueue* mCmdsSendWork; |
| static pthread_t mCmdsSendThread; |
| static uint8_t mStackState = STACK_STATE_DOWN; |
| static hciReadyForUpCbk mReadyForUpCbk; |
| static void *mReadyForUpData; |
| |
| static struct workQueue* mCallbackWork; |
| static pthread_t mCallbackThread; |
| |
| static pthread_mutex_t mConnsLock = PTHREAD_MUTEX_INITIALIZER; |
| static struct hciAclConn *mConns = NULL; |
| static struct hciLeConnReq *mLeConnReqsHead = NULL; |
| static struct hciLeConnReq *mLeConnReqsTail = NULL; |
| static bool mLeConnInProgress = false; |
| |
| static pthread_mutex_t mAdvSetsLock = PTHREAD_MUTEX_INITIALIZER; |
| static struct hciAdvSet *mAdvSets = NULL; |
| |
| static pthread_mutex_t mDiscoveryStateLock = PTHREAD_MUTEX_INITIALIZER; |
| |
| static hciDeviceDiscoveredLeCbk mDiscoverLeCbk; |
| static void *mDiscoverLeData; |
| static uniq_t mDiscoverLeHandle = 0; |
| |
| |
| |
| /* read from chip */ |
| static uint8_t mBtVer = 0; /* HCI_VERSION_* */ |
| static uint64_t mLocalFtrs[MAX_FTR_PAGES] = {0, }; |
| static uint64_t mLocalLeFtrs = 0; |
| static uint16_t mScoBufSz = 0; |
| static uint16_t mAclBufSzEdr = 0; |
| static uint16_t mAclBufSzLe = 0; |
| static uint16_t mScoBufNum = 0; |
| static uint16_t mAclBufNumEdr = 0; |
| static uint16_t mAclBufNumLe = 0; |
| static bool mBtJointBuffers = true; /* use EDR buffers for LE */ |
| static sem_t mAclPacketsLe; |
| static sem_t mAclPacketsEdr; |
| static uint8_t mLocalAddr[BT_MAC_LEN]; |
| static uint16_t mMaxAdvSets; |
| static bool mCanSetAdvTxPower; |
| |
| |
| /* fwd declarations */ |
| static bool hciBtStackUp(void); |
| static bool hciScheduleMakeProgressOnLeConnectionReqs(void); //necessary if we are in evt rx thread |
| static void hciMakeProgressOnLeConnectionReqs(void); |
| static bool hciScheduleConnUpNotif(hci_conn_t conn, const struct bt_addr *peerAddr, const struct bt_addr *selfAddr, bool isMaster, bool isEncrypted, bool isMitmSafe); |
| static bool hciScheduleConnDownNotif(hci_conn_t conn, uint8_t reason); |
| static bool hciScheduleConnDataRx(hci_conn_t conn, sg packet, bool first); |
| static bool hciCmdSubmitSimpleWithStatusWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData); |
| static bool hciCmdSubmitSimpleWithCompleteSync(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, void* evtBuf, uint8_t evtBufSz); |
| static bool hciCmdSubmitSimpleWithStatusSync(uint8_t ogf, uint16_t ocf, const void *paramData, uint8_t paramLen, struct hciEvtCmdStatus *dstEvt); |
| static void hciHandleConnAclDown(uint16_t cid, uint8_t reason); |
| static bool hciHandleConnAclLeUp(uint8_t status, uint16_t id, bool isMaster, const uint8_t *mac, bool addrRandom, uint16_t interval, uint16_t latency, uint16_t timeout, uint8_t mca); |
| static bool hciHandleConnAclEncr(uint16_t cid, bool encrOn); |
| static bool hciHandleConnAclAuth(uint16_t cid, bool authOn); |
| static bool hciHandleConnAclEncrKeyRefresh(uint16_t cid, bool encrOn); |
| static bool hciHandleConnAclLeUpdate(uint8_t status, uint16_t cid, uint16_t interval, uint16_t latency, uint16_t timeout); |
| static bool hciHandleConnLeLtkReq(uint16_t cid, uint64_t randomNum, uint16_t diversifier); |
| static bool hciConnUpdate(uint16_t cid, uint16_t minInt, uint16_t maxInt, uint16_t latency, uint16_t timeout); |
| static void hciCmdStatusCbk(void *cbkData, uint8_t status); |
| static bool hciConnDisconnect(uint16_t cid); |
| static bool hciConnLeCancel(void); |
| static void hciConnCfg(struct hciAclConn *c, uint32_t step, uint32_t aux); |
| static bool hciRxAclForRunningConn(struct hciAclConn* conn, uint16_t hdr, sg packet); |
| static void hciRxAcl(uint16_t hdr, sg packet); |
| static bool hciScheduleAclRx(uint16_t hdr, sg packet); |
| static void hciConnAclStructDel(struct hciAclConn* conn); |
| static bool hciSetupResetEventHandler(bool cmdWaitLockHeld); |
| static void hciAdvSetGetCurAdvAddrLocked(struct hciAdvSet *set, struct bt_addr *ownAddr); |
| |
| /* adv indirection function pointers. Called with mAdvSetsLock held*/ |
| static bool (*mHciAdvSetEnable)(struct hciAdvSet *set, bool enable); |
| static bool (*mHciAdvSetHandleAndBlameNewConn)(struct hciAclConn *conn); //return true if done, false if not yet. if false is returned, you MUST later call hciConnCfg(c, CONN_CFG_STEP_LE, 0); and set state to CFG when decided |
| |
| |
| |
| /* templated commands - they need the above vars hence include here */ |
| #include "hci_templated_commands.h" |
| |
| |
| /* |
| * FUNCTION: hciConnFindById |
| * USE: Find a connection struct given an ID |
| * PARAMS: id - the id |
| * RETURN: connection struct or NULL if not found |
| * NOTES: call with mConnsLock held |
| */ |
| static struct hciAclConn* hciConnFindById(uint16_t id) |
| { |
| struct hciAclConn *c = mConns; |
| |
| while (c && c->id != id) |
| c = c->next; |
| |
| return c; |
| } |
| |
| /* |
| * FUNCTION: hciConnFindByHandle |
| * USE: Find a connection struct given a handle |
| * PARAMS: addr - the address |
| * RETURN: connection struct or NULL if not found |
| * NOTES: call with mConnsLock held |
| */ |
| static struct hciAclConn* hciConnFindByHandle(hci_conn_t handle) |
| { |
| struct hciAclConn *c = mConns; |
| |
| while (c && c->handle != handle) |
| c = c->next; |
| |
| return c; |
| } |
| |
| /* |
| * FUNCTION: hciConnFindByAddr |
| * USE: Find a connection struct given an address |
| * PARAMS: addr - the address |
| * RETURN: connection struct or NULL if not found |
| * NOTES: call with mConnsLock held |
| */ |
| static struct hciAclConn* hciConnFindByAddr(const struct bt_addr *addr) |
| { |
| struct hciAclConn *c = mConns; |
| |
| while (c && memcmp(&c->peerAddr, addr, sizeof(c->peerAddr))) |
| c = c->next; |
| |
| return c; |
| } |
| |
| /* |
| * FUNCTION: hciEvtWaitDequeueInt |
| * USE: Dequeue a handler for an event we expect |
| * PARAMS: ws - the handler struct |
| * RETURN: NONE |
| * NOTES: call with mCmdWaitLock held |
| */ |
| static void hciEvtWaitDequeueInt(struct hciEvtWaitState *ws) |
| { |
| if (ws->next) |
| ws->next->prev = ws->prev; |
| else |
| mEvtWaitTail = ws->prev; |
| |
| if (ws->prev) |
| ws->prev->next = ws->next; |
| else |
| mEvtWaitHead = ws->next; |
| |
| free(ws); |
| } |
| |
| /* |
| * FUNCTION: hciEvtWaitDequeue |
| * USE: Dequeue a handler for an event we expect |
| * PARAMS: evtWaitStateID - wait state ID from hciEvtWaitEnqueue() |
| * RETURN: true if dequeued, false if not found (already fired or never existed) |
| * NOTES: |
| */ |
| static bool hciEvtWaitDequeue(uniq_t evtWaitStateID) |
| { |
| struct hciEvtWaitState *ws; |
| bool found = true; |
| |
| pthread_mutex_lock(&mCmdWaitLock); |
| |
| ws = mEvtWaitHead; |
| while (ws && ws->evtWaitStateID != evtWaitStateID) |
| ws = ws->next; |
| |
| if (ws) |
| hciEvtWaitDequeueInt(ws); |
| else |
| found = false; |
| |
| pthread_mutex_unlock(&mCmdWaitLock); |
| |
| return found; |
| } |
| /* |
| * FUNCTION: hciEvtWaitDequeueAllForCmd |
| * USE: Dequeue all handler for a given command ID |
| * PARAMS: forCmdID - command ID as passed to hciEvtWaitEnqueue(); |
| * RETURN: how many handlers were dequeued |
| * NOTES: |
| */ |
| static uint8_t hciEvtWaitDequeueAllForCmd(uniq_t forCmdID) |
| { |
| struct hciEvtWaitState *ws, *t; |
| uint8_t found = 0; |
| |
| pthread_mutex_lock(&mCmdWaitLock); |
| |
| ws = mEvtWaitHead; |
| while (ws) { |
| t = ws; |
| ws = ws->next; |
| |
| if (t->forCmdID == forCmdID) { |
| hciEvtWaitDequeueInt(ws); |
| found++; |
| } |
| } |
| |
| pthread_mutex_unlock(&mCmdWaitLock); |
| |
| return found; |
| } |
| |
| /* |
| * FUNCTION: hciEvtWaitEnqueueUnlocked |
| * USE: Enqueue a handler for an event we expect |
| * PARAMS: ewd - the event wait descriptor |
| * forCmdID - the commandID this is for |
| * RETURN: uniq_t identifying this event wait object or 0 on failure |
| * NOTES: must be called with mCmdWaitLock held |
| */ |
| static uniq_t hciEvtWaitEnqueueUnlocked(const struct hciEvtWaitDescr *ewd, uniq_t forCmdID) |
| { |
| struct hciEvtWaitState *ws = (struct hciEvtWaitState*)malloc(sizeof(struct hciEvtWaitState)); |
| uniq_t ret; |
| |
| if (!ws) { |
| loge("Failed to enqueue handler for event 0x%02X.%04X\n", ewd->evtType, ewd->extra); |
| return 0; |
| } |
| |
| ret = uniqGetNext(); |
| |
| ws->cbk = ewd->cbk; |
| ws->cbkData = ewd->cbkData; |
| ws->evtType = ewd->evtType; |
| ws->extra = ewd->extra; |
| ws->persistent = ewd->persistent; |
| ws->next = NULL; |
| ws->evtWaitStateID = ret; |
| ws->forCmdID = forCmdID; |
| |
| ws->prev = mEvtWaitTail; |
| mEvtWaitTail = ws; |
| if (ws->prev) |
| ws->prev->next = ws; |
| else |
| mEvtWaitHead = ws; |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciEvtWaitEnqueue |
| * USE: Enqueue a handler for an event we expect |
| * PARAMS: ewd - the event wait descriptor |
| * forCmdID - the commandID this is for |
| * RETURN: uniq_t identifying this event wait object or 0 on failure |
| * NOTES: |
| */ |
| static uniq_t hciEvtWaitEnqueue(const struct hciEvtWaitDescr *ewd, uniq_t forCmdID) |
| { |
| uniq_t ret; |
| |
| pthread_mutex_lock(&mCmdWaitLock); |
| ret = hciEvtWaitEnqueueUnlocked(ewd, forCmdID); |
| pthread_mutex_unlock(&mCmdWaitLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciCbkChipHwUp |
| * USE: Called when the chip hw or fw are up |
| * PARAMS: cbkData - unused |
| * fw - true if fw status, false if hw |
| * up - success of bringing said component up |
| * RETURN: true on success, false else |
| * NOTES: |
| */ |
| static void hciCbkChipHwUp(void *cbkData, bool fw, bool up) |
| { |
| logi("BT %cW %s!\n", fw ? 'F' : 'H', up ? "UP" : "DOWN"); |
| } |
| |
| /* |
| * FUNCTION: hciRxAclForRunningConn |
| * USE: Called with a valid ACL packet/fragment for a running connection |
| * PARAMS: conn - the connection this is for |
| * hdr - packet header |
| * packet - the packet we got |
| * RETURN: NONE |
| * NOTES: call with mConnsLock held |
| */ |
| static bool hciRxAclForRunningConn(struct hciAclConn* conn, uint16_t hdr, sg packet) |
| { |
| uint16_t pb = hdr & ACL_HDR_MASK_PB; |
| |
| if (pb == ACL_HDR_PB_FIRST_NONAUTO || pb == ACL_HDR_PB_COMPLETE) { |
| logw("Invalid acl.pb flag from chp: 0x%04X. Dropped\n", pb); |
| return false; |
| } |
| |
| return hciScheduleConnDataRx(conn->handle, packet, pb == ACL_HDR_PB_FIRST_AUTO); |
| } |
| |
| /* |
| * FUNCTION: hciRxAcl |
| * USE: Called with a valid ACL packet/fragment |
| * PARAMS: hdr - packet header |
| * packet - the packet we got |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciRxAcl(uint16_t hdr, sg packet) |
| { |
| uint16_t cid = hdr & ACL_HDR_MASK_CONN_ID; |
| struct hciAclConn* conn; |
| |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindById(cid); |
| if (!conn) { |
| logi("Dropping packet for unknown acl link %d\n", cid); |
| sgFree(packet); |
| } else if (conn->state == CONN_STATE_CFG) { |
| struct hciBacklogItemHdr itemHdr = {.len = sgLength(packet), .aclHdr = hdr}; |
| |
| if (!conn->rxBacklog) |
| conn->rxBacklog = sgNew(); |
| |
| if (conn->rxBacklog && sgConcatFrontCopy(packet, &itemHdr, sizeof(itemHdr)) && sgLength(conn->rxBacklog) + sgLength(packet) <= HCI_CONN_CFG_DATA_BACKLOG_MAX) { |
| sgConcat(conn->rxBacklog, packet); |
| logd("Added packet to RX backlog\n"); |
| } else { |
| logi("Dropping connection -> we're unable to save RX in backlog\n"); |
| hciConnDisconnect(conn->id); |
| sgFree(packet); |
| } |
| } else if (conn->state != CONN_STATE_RUNNING) { |
| logi("Dropping arrived data for connection in state %u\n", conn->state); |
| sgFree(packet); |
| } else if (!hciRxAclForRunningConn(conn, hdr, packet)) { |
| logi("Failed to enqueue packet RX for link %d\n", cid); |
| sgFree(packet); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| |
| /* |
| * FUNCTION: hciRxEvt |
| * USE: Called with a valid EVT packet |
| * PARAMS: packet - the packet we got |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciRxEvt(sg packet) |
| { |
| struct hciEvtWaitState *ws = NULL, *cur = NULL; |
| bool handled = false, addCredit = false; |
| const struct hciEvtCmdComplete *cmdComplete; |
| const struct hciEvtCmdStatus *cmdStatus; |
| const struct hciEvtLeMeta *leMeta; |
| const struct hciEvtVendor *vendor; |
| const struct hciEvtHdr *evt; |
| uint8_t evtType, len; |
| uint32_t i, evtLen; |
| void* sgiter; |
| |
| |
| /* |
| * The SG may or may not be contiguous on the inside. If it is not, we'll alloc a new buffer |
| * and serialize the SG into it. If it is, we can just use the SG's internal buffer. How do |
| * we check? Iterate SG of course... Why? To save a copy and a malloc() & a free() |
| */ |
| sgiter = sgIterStart(packet); |
| evtLen = sgLength(packet); |
| if (sgiter && sgIterCurLen(sgiter) == evtLen) |
| evt = (const struct hciEvtHdr*)sgIterCurData(sgiter); |
| else { |
| evt = malloc(evtLen); |
| if (!evt) { |
| loge("Failed to alloc flat event representation\n"); |
| sgFree(packet); |
| } |
| sgSerialize(packet, 0, evtLen, (struct hciEvtHdr*)evt); |
| sgiter = NULL; |
| sgFree(packet); |
| } |
| |
| leMeta = (struct hciEvtLeMeta*)(evt + 1); |
| vendor = (struct hciEvtVendor*)(evt + 1); |
| cmdComplete = (struct hciEvtCmdComplete*)(evt + 1); |
| cmdStatus = (struct hciEvtCmdStatus*)(evt + 1); |
| |
| evtType = utilGetLE8(&evt->code); |
| len = utilGetLE8(&evt->len); |
| |
| /* handle the cases of credits: command complete */ |
| if (evtType == HCI_EVT_Command_Complete && utilGetLE8(&cmdComplete->numCmdCredits)) |
| addCredit = true; |
| |
| /* handle the cases of credits: command status */ |
| if (evtType == HCI_EVT_Command_Status && utilGetLE8(&cmdStatus->numCmdCredits)) |
| addCredit = true; |
| |
| /* handle data credits */ |
| if (evtType == HCI_EVT_Number_Of_Completed_Packets) { |
| bool hadLeCreds = false, hadEdrCreds = false; |
| uint16_t newCreditsEdr = 0, newCreditsLe = 0; |
| struct hciAclConn* conn; |
| struct hciEvtNumCompletedPackets *ncp = (struct hciEvtNumCompletedPackets*)(evt + 1); |
| struct hciEvtNumCompletedPacketsItem *items = ncp->items; |
| |
| pthread_mutex_lock(&mConnsLock); |
| for (i = 0; i < utilGetLE8(&ncp->numHandles); i++, items++) { |
| uint16_t connID = utilGetLE16(&items->conn); |
| uint16_t numPackets = utilGetLE16(&items->numPackets); |
| uint8_t acceptCredits = 0; |
| |
| conn = hciConnFindById(connID); |
| if (!conn) { |
| loge("Unknown conn 0x%04X in %d packets completed.\n", connID, numPackets); |
| } else { |
| if (conn->outstandingPackets >= numPackets) { |
| acceptCredits = numPackets; |
| conn->outstandingPackets -= numPackets; |
| } else { |
| loge("Conn %u has %u outstanding packets but got %u credits back.\n", (unsigned)conn->id, conn->outstandingPackets, numPackets); |
| acceptCredits = conn->outstandingPackets; |
| conn->outstandingPackets = 0; |
| } |
| } |
| if (HCI_TAKE_CREDS_FOR_UNKNOWN_CONNS && acceptCredits != numPackets) { |
| logw("Accepting %d credits for unknown packets\n", numPackets - acceptCredits); |
| acceptCredits = numPackets; |
| } |
| if (BT_ADDR_IS_LE(conn->peerAddr) && !mBtJointBuffers) { |
| hadLeCreds = true; |
| newCreditsLe += acceptCredits; |
| } else { |
| hadEdrCreds = true; |
| newCreditsEdr += acceptCredits; |
| } |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| while (newCreditsLe--) |
| sem_post(&mAclPacketsLe); |
| while (newCreditsEdr--) |
| sem_post(&mAclPacketsEdr); |
| if (hadLeCreds || (hadEdrCreds && mBtJointBuffers)) |
| l2cAclCreditAvail(true); |
| handled = true; |
| } |
| |
| /* handle command complete with ocf == 0 and ogf == 0 by ignoring it as per spec */ |
| if (evtType == HCI_EVT_Command_Complete && !utilGetLE16(&cmdComplete->opcode)) |
| handled = true; |
| |
| /* handle HW failure */ |
| if (evtType == HCI_EVT_Hardware_Error) { |
| struct hciEvtHwError *hwErr = (struct hciEvtHwError*)(evt + 1); |
| loge("Chip reported HW failure, code %d -> catastropic stack failure\n", utilGetLE8(&hwErr->errCode)); |
| abort(); |
| } |
| |
| if (!handled) { |
| |
| pthread_mutex_lock(&mCmdWaitLock); |
| ws = mEvtWaitHead; |
| while (ws && !handled) { |
| cur = ws; |
| ws = ws->next; |
| |
| /* type match? If no, do not go on */ |
| if (evtType != cur->evtType) |
| continue; |
| |
| /* if LE & Vendor events, check code and skip handler if wrong */ |
| if (evtType == HCI_EVT_LE_Meta) { |
| if(len < sizeof(struct hciEvtLeMeta)) { |
| logw("Got LE meta event of len %ub\n", len); |
| break; |
| } |
| if (utilGetLE8(&leMeta->subevent) != cur->extra) |
| continue; |
| } |
| if (evtType == HCI_EVT_Vendor) { |
| if(len < sizeof(struct hciEvtVendor)) { |
| logw("Got vendor event of len %ub\n", len); |
| break; |
| } |
| if (utilGetLE8(&vendor->subevent) != cur->extra) |
| continue; |
| } |
| |
| /* same for command complete */ |
| if (evtType == HCI_EVT_Command_Complete) { |
| if(len < sizeof(struct hciEvtCmdComplete)) { |
| logw("Got command complete event of len %ub\n", len); |
| break; |
| } |
| if (utilGetLE16(&cmdComplete->opcode) != cur->extra) |
| continue; |
| } |
| |
| /* same for command status */ |
| if (evtType == HCI_EVT_Command_Status) { |
| if(len < sizeof(struct hciEvtCmdStatus)) { |
| logw("Got command status event of len %ub\n", len); |
| break; |
| } |
| if (utilGetLE16(&cmdStatus->opcode) != cur->extra) |
| continue; |
| } |
| |
| /* if we're here, this handler matches - let's see what it says */ |
| handled = cur->cbk(evt, evtLen, cur->cbkData, cur->evtWaitStateID, cur->forCmdID); |
| } |
| |
| if (handled && cur && !cur->persistent) |
| hciEvtWaitDequeueInt(cur); |
| if (mStackState == STACK_STATE_GOING_DOWN) { |
| //if we are now in reset, delete all waiters. reset handler code cannot do it as we still hold the lock |
| while (mEvtWaitHead) |
| hciEvtWaitDequeueInt(mEvtWaitHead); |
| hciSetupResetEventHandler(true); |
| mStackState = STACK_STATE_DOWN; |
| } |
| pthread_mutex_unlock(&mCmdWaitLock); |
| } |
| |
| if (addCredit) { |
| sem_post(&mCmdSendSem); |
| if (mStackState == STACK_STATE_DOWN) { |
| mStackState = STACK_STATE_READY_FOR_UP; |
| if (mReadyForUpCbk) |
| mReadyForUpCbk(mReadyForUpData); |
| } |
| } |
| |
| if (!handled) |
| logw("Unhandled HCI event code 0x%02X with %ub of params\n", evtType, len); |
| |
| if (sgiter) |
| sgFree(packet); |
| else |
| free((struct hciEvtHdr*)evt); |
| } |
| |
| /* |
| * FUNCTION: hciRx |
| * USE: Called when the chip gives us data/events |
| * PARAMS: cbkData - unused |
| * typ - type of thing we're getting |
| * packet - the packet we got |
| * RETURN: true on success, false else |
| * NOTES: we must free the "data" buffer at some point in time - we own it now |
| */ |
| static void hciRx(void *cbkData, uint8_t typ, sg packet) |
| { |
| struct hciAclHdr hdrAcl; |
| struct hciEvtHdr hdrEvt; |
| uint32_t datalen = sgLength(packet); |
| uint32_t len; |
| uint16_t hdr; |
| |
| switch (typ){ |
| case HCI_PKT_TYP_ACL: |
| if (datalen < sizeof(struct hciAclHdr)) { |
| loge("Got ACL packet of %ub\n", datalen); |
| break; |
| } |
| datalen -= sizeof(struct hciAclHdr); |
| sgSerialize(packet, 0, sizeof(struct hciAclHdr), &hdrAcl); |
| hdr = utilGetLE16(&hdrAcl.hdr); |
| len = utilGetLE16(&hdrAcl.len); |
| if (len != datalen) { |
| loge("ACL packet claims %ub but had %ub\n", len, datalen); |
| break; |
| } |
| sgTruncFront(packet, sizeof(struct hciAclHdr)); |
| if (hciScheduleAclRx(hdr, packet)) |
| packet = NULL; |
| else |
| logw("Dropping data packet\n"); |
| break; |
| case HCI_PKT_TYP_SCO: |
| loge("Got SCO packet\n"); |
| break; |
| case HCI_PKT_TYP_EVT: |
| if (datalen < sizeof(struct hciEvtHdr)) { |
| loge("Got EVT packet of %ub\n", datalen); |
| break; |
| } |
| datalen -= sizeof(struct hciEvtHdr); |
| sgSerialize(packet, 0, sizeof(struct hciEvtHdr), &hdrEvt); |
| len = utilGetLE8(&hdrEvt.len); |
| if (len != datalen) { |
| loge("EVT packet claims %ub but had %ub\n", len, datalen); |
| break; |
| } |
| hciRxEvt(packet); |
| packet = NULL; |
| break; |
| default: |
| loge("Dropping unknown packet with type %d\n", typ); |
| break; |
| } |
| |
| if (packet) |
| sgFree(packet); |
| } |
| |
| /* |
| * FUNCTION: hciCallbackWorkDeallocator |
| * USE: Called to cleanup each item of callback workqueue at deinit time |
| * PARAMS: workItem - a work item |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciCallbackWorkDeallocator(void *workItem) |
| { |
| struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)workItem; |
| |
| |
| if (w->cbkWorkItemType == CBK_WORK_ACL_DATA) |
| sgFree(w->aclDataRx.packet); |
| else if (w->cbkWorkItemType == CBK_WORK_ACL_RX) |
| sgFree(w->aclRx.packet); |
| |
| free(workItem); |
| } |
| |
| /* |
| * FUNCTION: hciCallbackWorkDeallocator |
| * USE: Called to cleanup each item of command send workqueue at deinit time |
| * PARAMS: workItem - a work item |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciCmdSendWorkDeallocator(void *workItem) |
| { |
| struct hciCmdSendWorkItem *w = (struct hciCmdSendWorkItem*)workItem; |
| |
| sgFree(w->cmd); |
| free(workItem); |
| } |
| |
| /* |
| * FUNCTION: hciCbkWorkLeLtkReq |
| * USE: Called in worker thread - request key from someone |
| * PARAMS: aclConn - the connection |
| * randomNum - provied by other side |
| * diversifier - provied by other side |
| * RETURN: none |
| * NOTES: caller should soon call hciLeProvideLtk() with reply |
| */ |
| static void hciCbkWorkLeLtkReq(hci_conn_t aclConn, uint64_t randomNum, uint16_t diversifier) |
| { |
| struct hciAclConn* conn; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logd("Conn for key req not found\n"); |
| else if (!l2cAclLeLtkRequest(aclConn, randomNum, diversifier)) { |
| logd("LTK key req from L2C failed - assuming no key\n"); |
| struct hciLeLtkRequestNegativeReply cmd; |
| utilSetLE16(&cmd.conn, conn->id); |
| //we cannot call a sync function in this thread, so do the async thing |
| if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_LTK_Request_Negative_Reply, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL)) { |
| logi("Failed to request negative link key reply for acl conn "HCI_CONN_FMT"u (id %u)\n", HCI_CONN_CONV(aclConn), conn->id); |
| } |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| |
| /* |
| * FUNCTION: hciLeProvideLtk |
| * USE: Called by higher layer - provide key |
| * PARAMS: aclConn - the connection |
| * key - key to use or NULL if none |
| * RETURN: false on error |
| * NOTES: proper hci reply command will be called |
| */ |
| bool hciLeProvideLtk(hci_conn_t aclConn, const uint8_t *key) |
| { |
| struct hciAclConn* conn; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logd("Conn for key req reply found\n"); |
| else if (key) |
| ret = !hciLeLtkRequestReplySync(conn->id, key); |
| else |
| ret = !hciLeLtkRequestNegativeReplySync(conn->id); |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciCbkWorker |
| * USE: Worker thread for calling callbacks for HCI completed items |
| * PARAMS: unused - unused |
| * RETURN: unused |
| * NOTES: |
| */ |
| static void* hciCbkWorker(void *unused) |
| { |
| struct hciCbkWorkItem *w; |
| int ret; |
| |
| pthread_setname_np(pthread_self(), "bt_hci_callbacks"); |
| |
| while (1) { |
| |
| ret = workQueueGet(mCallbackWork, (void**)&w); |
| if (ret) |
| break; |
| |
| switch (w->cbkWorkItemType){ |
| case CBK_WORK_ITEM_DONE_CBK: |
| w->done.cbk(w->done.cbkData, w->done.status); |
| break; |
| case CBK_WORK_ITEM_SIMPLE_CBK: |
| w->simple.cbk(w->simple.cbkData1, w->simple.cbkData2, w->simple.stackGoingDown, w->simple.haveEvt ? (struct hciEvtHdr*)(w + 1) : NULL); |
| break; |
| case CBK_WORK_LE_DISC_DEV: |
| mDiscoverLeCbk(mDiscoverLeData, &w->leDev.addr, w->leDev.rssi, w->leDev.advType, w->leDev.adv, w->leDev.advLen); |
| break; |
| case CBK_WORK_FLUSH: |
| sem_post(w->flush.sem); |
| break; |
| case CBK_WORK_CONN_UP: |
| l2cAclLinkUp(w->connUp.conn, &w->connUp.peerAddr, &w->connUp.selfAddr, w->connUp.isMaster, w->connUp.isEncrypted, w->connUp.isMitmSafe); |
| break; |
| case CBK_WORK_CONN_DOWN: |
| l2cAclLinkDown(w->connDown.conn, w->connDown.reason); |
| break; |
| case CBK_WORK_PARAMS_CHANGE: |
| l2cAclLinkParamsChange(w->leParamChange.conn, w->leParamChange.success, w->leParamChange.interval, w->leParamChange.latency, w->leParamChange.timeout); |
| break; |
| case CBK_WORK_ENCR_CHANGE: |
| l2cAclLinkEncrChange(w->encrChange.conn, w->encrChange.nowEncrypted, w->encrChange.isMitmSafe); |
| break; |
| case CBK_WORK_ACL_DATA: |
| l2cAclDataRx(w->aclDataRx.conn, w->aclDataRx.packet, w->aclDataRx.first); |
| break; |
| case CBK_WORK_LE_KEY_REQ: |
| hciCbkWorkLeLtkReq(w->leKeyReq.conn, w->leKeyReq.randomNum, w->leKeyReq.diversifier); |
| break; |
| case CBK_WORK_ENCR_KEY_REFRESH: |
| l2cAclLinkEncrKeyRefresh(w->encrKeyRefresh.conn, w->encrKeyRefresh.nowEncrypted, w->encrKeyRefresh.isMitmSafe); |
| break; |
| case CBK_WORK_MAKE_NEXT_LE_CONN: |
| hciMakeProgressOnLeConnectionReqs(); |
| break; |
| case CBK_WORK_ACL_RX: |
| hciRxAcl(w->aclRx.hdr, w->aclRx.packet); |
| break; |
| default: |
| loge("Unknown callback work type %u\n", w->cbkWorkItemType); |
| break; |
| } |
| free(w); |
| } |
| logd("HCI callback worker exiting\n"); |
| |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleOpDoneCbk |
| * USE: Enqueue an op_done callback to be called |
| * PARAMS: cbk - the callback to call |
| * cbkData - the data for said callback |
| * status - the status byte |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleOpDoneCbk(hciOpDoneCbk cbk, void *cbkData, uint8_t status) |
| { |
| struct hciCbkWorkItem *w; |
| |
| if (!cbk) { |
| logw("Not scheduling done callback with a NULL function pointer\n"); |
| return true; |
| } |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ITEM_DONE_CBK; |
| w->done.cbk = cbk; |
| w->done.cbkData = cbkData; |
| w->done.status = status; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleSimpleCbk |
| * USE: Enqueue simple callback to be called |
| * PARAMS: cbk - the callback to call |
| * cbkData1 - the data for said callback |
| * cbkData2 - the data for said callback |
| * stackGoingDown - as the name implies... |
| * evt - event to provide, or NULL if none |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleSimpleCbk(hciSimpleCbk cbk, void *cbkData1, void *cbkData2, bool stackGoingDown, const struct hciEvtHdr *evt) |
| { |
| uint16_t evtLen = evt ? (sizeof(struct hciEvtHdr) + utilGetLE8(&evt->len)) : 0; |
| uint32_t neededLen = sizeof(struct hciCbkWorkItem) + evtLen; |
| struct hciCbkWorkItem *w; |
| |
| if (!cbk) { |
| logw("Not scheduling simple callback with a NULL function pointer\n"); |
| return true; |
| } |
| |
| w = (struct hciCbkWorkItem*)malloc(neededLen); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ITEM_SIMPLE_CBK; |
| w->simple.cbk = cbk; |
| w->simple.cbkData1 = cbkData1; |
| w->simple.cbkData2 = cbkData2; |
| w->simple.stackGoingDown = stackGoingDown; |
| w->simple.haveEvt = evt != NULL; |
| |
| if (evt) |
| memcpy(w + 1, evt, evtLen); |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciWorkFlush |
| * USE: Flush the workQueue |
| * PARAMS: NONE |
| * RETURN: true if success, false else |
| * NOTES: blocks till all existing work items have been finished |
| */ |
| static bool hciWorkFlush(void) |
| { |
| struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| bool ret = false; |
| sem_t sem; |
| |
| if (!w) { |
| loge("Failed to alloc workQ flush cmd\n"); |
| return false; |
| } |
| |
| if (sem_init(&sem, 0, 0)) { |
| free(w); |
| loge("Failed to alloc workQ flush sem\n"); |
| return false; |
| } |
| |
| w->cbkWorkItemType = CBK_WORK_FLUSH; |
| w->flush.sem = &sem; |
| |
| if (workQueuePut(mCallbackWork, w)) { |
| r_sem_wait(&sem); |
| ret = true; |
| } else |
| free(w); |
| |
| sem_destroy(&sem); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnUpNotif |
| * USE: Enqueue a call to the ACL link up notif |
| * PARAMS: conn - the connection |
| * peerAddr - peer address |
| * selfAddr - whom we were being when we made the connection |
| * isMaster - are we master on the link |
| * isEncrypted - is the link encrypted |
| * isMitmSafe - is th elink MITM-safe? |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleConnUpNotif(hci_conn_t conn, const struct bt_addr *peerAddr, const struct bt_addr *selfAddr, bool isMaster, bool isEncrypted, bool isMitmSafe) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_CONN_UP; |
| w->connUp.conn = conn; |
| w->connUp.isMaster = isMaster; |
| w->connUp.isEncrypted = isEncrypted; |
| |
| w->connUp.peerAddr = *peerAddr; |
| w->connUp.selfAddr = *selfAddr; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnDownNotif |
| * USE: Enqueue a call to the ACL link down notif |
| * PARAMS: conn - the connection handle |
| * reason - the reason to pass to upper layers |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleConnDownNotif(hci_conn_t conn, uint8_t reason) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_CONN_DOWN; |
| w->connDown.conn = conn; |
| w->connDown.reason = reason; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnParamChange |
| * USE: Enqueue a call to the connection param change notif |
| * PARAMS: conn - the conenction |
| * success - success? |
| * interval - new interval |
| * latency - new latency |
| * timeout - new timeout |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleConnParamChange(hci_conn_t conn, bool success, uint16_t interval, uint16_t latency, uint16_t timeout) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_PARAMS_CHANGE; |
| w->leParamChange.conn = conn; |
| w->leParamChange.success = success; |
| w->leParamChange.interval = interval; |
| w->leParamChange.latency = latency; |
| w->leParamChange.timeout = timeout; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleLeLtkRequest |
| * USE: Enqueue a call to the connection key request notif |
| * PARAMS: conn - the connection |
| * randomNum - provied by other side |
| * diversifier - provied by other side |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleLeLtkRequest(hci_conn_t conn, uint64_t randomNum, uint16_t diversifier) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_LE_KEY_REQ; |
| w->leKeyReq.conn = conn; |
| w->leKeyReq.randomNum = randomNum; |
| w->leKeyReq.diversifier = diversifier; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnEncrChange |
| * USE: Enqueue a call to the connection encr change notif |
| * PARAMS: conn - the connection |
| * nowEncrypted - are we now encrypted? |
| * isMitmSafe - are we now MITM-safe? |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleConnEncrChange(hci_conn_t conn, bool nowEncrypted, bool isMitmSafe) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ENCR_CHANGE; |
| w->encrChange.conn = conn; |
| w->encrChange.nowEncrypted = nowEncrypted; |
| w->encrChange.isMitmSafe = isMitmSafe; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnEncrKeyRefresh |
| * USE: Enqueue a call to the connection encryption key fresh notification |
| * PARAMS: conn - the connection |
| * nowEncrypted - are we now encrypted? |
| * isMitmSafe - are we now MITM-safe? |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleConnEncrKeyRefresh(hci_conn_t conn, bool nowEncrypted, bool isMitmSafe) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ENCR_KEY_REFRESH; |
| w->encrKeyRefresh.conn = conn; |
| w->encrKeyRefresh.nowEncrypted = nowEncrypted; |
| w->encrKeyRefresh.isMitmSafe = isMitmSafe; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleMakeProgressOnLeConnectionReqs |
| * USE: Enqueue a call to hciMakeProgressOnLeConnectionReqs |
| * PARAMS: NONE |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleMakeProgressOnLeConnectionReqs(void) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_MAKE_NEXT_LE_CONN; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleAclRx |
| * USE: Enqueue a call to hciRxAcl |
| * PARAMS: hdr - packet header |
| * packet - the packet we got |
| * RETURN: true if enqueued, false else |
| * NOTES: |
| */ |
| static bool hciScheduleAclRx(uint16_t hdr, sg packet) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ACL_RX; |
| w->aclRx.hdr = hdr; |
| w->aclRx.packet = packet; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciScheduleConnDataRx |
| * USE: Enqueue a call to the connection RX notif |
| * PARAMS: conn - the conenction |
| * packet - the packet |
| * first - was packet marked as first? |
| * RETURN: true if enqueued, false else |
| * NOTES: data is copied |
| */ |
| static bool hciScheduleConnDataRx(hci_conn_t conn, sg packet, bool first) |
| { |
| struct hciCbkWorkItem *w; |
| |
| w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem)); |
| if (!w) |
| return false; |
| |
| w->cbkWorkItemType = CBK_WORK_ACL_DATA; |
| w->aclDataRx.conn = conn; |
| w->aclDataRx.packet = packet; |
| w->aclDataRx.first = first; |
| |
| if (workQueuePut(mCallbackWork, w)) |
| return true; |
| |
| free(w); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciCmdSendWorker |
| * USE: Worker thread for sending commands to the chip |
| * PARAMS: unused - unused |
| * RETURN: unused |
| * NOTES: |
| */ |
| static void* hciCmdSendWorker(void *unused) |
| { |
| struct hciCmdSendWorkItem *w; |
| bool weExpectReply = false; |
| struct timespec ts; |
| int ret, i; |
| |
| pthread_setname_np(pthread_self(), "bt_hci_cmds_send"); |
| |
| while (1) { |
| |
| vendorTxDoneWithEvts(); |
| ret = workQueueGet(mCmdsSendWork, (void**)&w); |
| if (ret) |
| break; |
| |
| clock_gettime(CLOCK_REALTIME, &ts); |
| ts.tv_sec += BT_COMMAND_TIMEOUT; |
| if (r_sem_timedwait(&mCmdSendSem, &ts)) { |
| loge("CHIP DEAD!\n"); |
| mt_analize_death(); |
| abort(); |
| } |
| |
| /* |
| * This lock makes sure adding a handler for an event and sending the |
| * corresponding command are handled as one atomic unit. |
| */ |
| pthread_mutex_lock(&mCmdLock); |
| |
| for (i = 0; i < w->numEventWaiters; i++) { |
| if (!w->ewds[i].descr.persistent) |
| weExpectReply = true; |
| w->ewds[i].id = hciEvtWaitEnqueue(&w->ewds[i].descr, w->cmdID); |
| if (!w->ewds[i].id) { |
| loge("Failed to enqueue handler [%d] \n", i); |
| goto failed; |
| } |
| } |
| |
| if (vendorTx(HCI_PKT_TYP_CMD, w->cmd, weExpectReply)) |
| goto success; |
| |
| loge("Failed to TX a cmd\n"); |
| sgFree(w->cmd); |
| |
| failed: |
| loge("Command not sent\n"); |
| while (i) |
| hciEvtWaitDequeue(w->ewds[--i].id); |
| |
| success: |
| pthread_mutex_unlock(&mCmdLock); |
| free(w); |
| } |
| logd("HCI command send worker exiting\n"); |
| |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: hciUp |
| * USE: Bring up HCI (comms to bt chip) |
| * PARAMS: addr - the BT addr |
| * cbk - callback when stack is ready for up |
| * cbkData - data to be passed to cbk |
| * RETURN: true on success, false else |
| * NOTES: may take a while |
| */ |
| bool hciUp(const uint8_t *addr, hciReadyForUpCbk cbk, void *cbkData) |
| { |
| int ret; |
| |
| logd("Vendor open\n"); |
| if (!vendorOpen()) { |
| loge("Failed to open vendor lib\n"); |
| goto out_lib_open; |
| } |
| |
| logd("Vendor up\n"); |
| if (!vendorUp(addr, hciCbkChipHwUp, hciRx, NULL)) { |
| loge("Failed to bring up the chip\n"); |
| goto out_chip_up; |
| } |
| |
| ret = sem_init(&mCmdSendSem, 0, 0); |
| if (ret) { |
| loge("Sem init failure\n"); |
| goto out_sem; |
| } |
| |
| mCmdsSendWork = workQueueAlloc(HCI_NUM_OUTSTANDING_CMDS); |
| if (!mCmdsSendWork) { |
| loge("Failed to create command send workqueue\n"); |
| goto out_cmd_workq; |
| } |
| |
| mCallbackWork = workQueueAlloc(HCI_NUM_OUTSTANDING_CBKS); |
| if (!mCallbackWork) { |
| loge("Failed to create hci callback workqueue\n"); |
| goto out_cbk_workq; |
| } |
| |
| ret = pthread_create(&mCallbackThread, NULL, hciCbkWorker, NULL); |
| if (ret) { |
| loge("Failed(%d) to create hci callback worker\n", ret); |
| goto out_cbk_worker; |
| } |
| |
| ret = pthread_create(&mCmdsSendThread, NULL, hciCmdSendWorker, NULL); |
| if (ret) { |
| loge("Failed(%d) to create hci cmd send worker\n", ret); |
| goto out_cmd_worker; |
| } |
| |
| mReadyForUpCbk = cbk; |
| mReadyForUpData = cbkData; |
| |
| sem_init(&mAclPacketsLe, 0, 0); |
| sem_init(&mAclPacketsEdr, 0, 0); |
| |
| hciSetupResetEventHandler(false); |
| vendorStartRx(); |
| |
| logd("Stack ready for up...awaiting reset event\n"); |
| //in a real stack we'd call hciBtStackUp() here. but with splitter, we just wait... |
| return true; |
| |
| |
| out_cmd_worker: |
| workQueueWakeAll(mCallbackWork, 1); |
| pthread_join(mCallbackThread, NULL); |
| |
| out_cbk_worker: |
| workQueueFree(mCallbackWork, hciCallbackWorkDeallocator); |
| |
| out_cbk_workq: |
| workQueueFree(mCmdsSendWork, hciCmdSendWorkDeallocator); |
| |
| out_cmd_workq: |
| sem_destroy(&mCmdSendSem); |
| |
| out_sem: |
| vendorDown(); |
| |
| out_chip_up: |
| vendorClose(); |
| |
| out_lib_open: |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciDown |
| * USE: Bring down HCI (comms to bt chip) |
| * PARAMS: NONE |
| * RETURN: NONE |
| * NOTES: may take a while, some shutdown actions may go |
| * on after this (in higher layers). does not cleanup |
| * chip state - assumes chip will be reset. |
| */ |
| void hciDown(void) |
| { |
| logd("Cancelling all event waiters\n"); |
| pthread_mutex_lock(&mCmdWaitLock); |
| while (mEvtWaitHead) { |
| mEvtWaitHead->cbk(NULL, 0, mEvtWaitHead->cbkData, mEvtWaitHead->evtWaitStateID, mEvtWaitHead->forCmdID); |
| hciEvtWaitDequeueInt(mEvtWaitHead); |
| } |
| while (mConns) { |
| struct hciAclConn *t = mConns; |
| mConns = mConns->next; |
| free(t); |
| } |
| while (mAdvSets) { |
| struct hciAdvSet *t = mAdvSets; |
| mAdvSets = mAdvSets->next; |
| free(t); |
| } |
| pthread_mutex_unlock(&mCmdWaitLock); |
| |
| //more deinit here |
| pthread_mutex_lock(&mDiscoveryStateLock); |
| mDiscoverLeHandle = 0; |
| pthread_mutex_unlock(&mDiscoveryStateLock); |
| |
| workQueueWakeAll(mCallbackWork, 1); |
| workQueueWakeAll(mCmdsSendWork, 1); |
| sem_post(&mCmdSendSem); /* wakes up cmdSend worker if it is waiting on a cmd credit */ |
| pthread_join(mCallbackThread, NULL); |
| pthread_join(mCmdsSendThread, NULL); |
| workQueueFree(mCallbackWork, hciCallbackWorkDeallocator); |
| workQueueFree(mCmdsSendWork, hciCmdSendWorkDeallocator); |
| sem_destroy(&mCmdSendSem); |
| vendorDown(); |
| vendorClose(); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmit |
| * USE: Atomically enqueue a command to be sent to the chip and event handlers for |
| * events that will result. |
| * PARAMS: ogf - the command group |
| * ocf - the command code |
| * paramData - command params |
| * paramLen - parameter length |
| * ... - NULL-terminated list of const struct hciEvtWaitDescr* for wanted events |
| * RETURN: success |
| * NOTES: SHALL FINISH QUICKLY AND NOT BLOCK. WILL NOT DEADLOCK YOU! ALWAYS SAFE TO CALL! |
| */ |
| static bool hciCmdSubmit(uint8_t ogf, uint16_t ocf, const void* paramData, uint32_t paramLen, ...) |
| { |
| const struct hciEvtWaitDescr *ewd; |
| struct hciCmdSendWorkItem *w; |
| uint8_t numHandlers = 0; |
| struct hciCmdHdr cmdhdr; |
| va_list vl; |
| sg cmd; |
| |
| /* count up the events we want to wait for */ |
| va_start(vl, paramLen); |
| while (va_arg(vl, const struct hciEvtWaitDescr*)) |
| numHandlers++; |
| va_end(vl); |
| |
| cmd = sgNew(); |
| if (!cmd) { |
| loge("Failed to alloc cmd sg\n"); |
| goto out_ret; |
| } |
| |
| /* allocate the work item */ |
| w = (struct hciCmdSendWorkItem*)malloc(sizeof(struct hciCmdSendWorkItem) + sizeof(struct hciEvtWaitDescrWithID[numHandlers])); |
| if (!w) { |
| loge("Failed to alloc command work for cmd 0x%02X.%04X\n", ogf, ocf); |
| goto out_sgfree; |
| } |
| |
| /* copy wait state objects into it */ |
| numHandlers = 0; |
| va_start(vl, paramLen); |
| while ((ewd = va_arg(vl, const struct hciEvtWaitDescr*)) != NULL) |
| memcpy(&w->ewds[numHandlers++].descr, ewd, sizeof(struct hciEvtWaitDescr)); |
| va_end(vl); |
| w->numEventWaiters = numHandlers; |
| |
| /* create the command */ |
| utilSetLE16(&cmdhdr.opcode, CMD_MAKE_OPCODE(ogf, ocf)); |
| utilSetLE16(&cmdhdr.paramLen, paramLen); |
| if (!sgConcatBackCopy(cmd, &cmdhdr, sizeof(cmdhdr)) || !sgConcatBackCopy(cmd, paramData, paramLen)) { |
| loge("Failed to fill command sg\n"); |
| goto out_wfree; |
| } |
| w->cmd = cmd; |
| |
| /* try to enqueue it */ |
| if (workQueuePut(mCmdsSendWork, w)) |
| return true; |
| |
| /* in case of failure, free all that we allocated */ |
| loge("Failed to enqueue command\n"); |
| |
| out_wfree: |
| free(w); |
| |
| out_sgfree: |
| sgFree(cmd); |
| |
| out_ret: |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimpleCbk |
| * USE: Command callback for hciCmdSubmitSimple |
| * PARAMS: evt - the event |
| * evtSz - event size |
| * cbkData - struct hciSimpleCbkData - our data |
| * evtWaitStateID - event waiterID |
| * forCmdID - commandID |
| * RETURN: true if we handled it, false else |
| * NOTES: |
| */ |
| static bool hciCmdSubmitSimpleCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| struct hciSimpleCbkData *cbkd = (struct hciSimpleCbkData*)cbkData; |
| const char* name = cbkd->complete ? "complete" : "status"; |
| uint8_t paramLen; |
| uint8_t evtHdrLen; |
| bool ret = false; |
| |
| if (!evt) |
| return hciScheduleSimpleCbk(cbkd->cbk, cbkd->cbkData1, cbkd->cbkData2, true, NULL); |
| |
| paramLen = utilGetLE8(&evt->len); |
| evtHdrLen = cbkd->complete ? sizeof(struct hciEvtCmdComplete) : sizeof(struct hciEvtCmdStatus); |
| |
| if (paramLen < evtHdrLen) { |
| logw("Comand %s event too short @ %ub (wanted %ub+)\n", name, paramLen, evtHdrLen); |
| goto out; |
| } |
| paramLen -= evtHdrLen; |
| |
| /* is param length within bounds? */ |
| if (paramLen < cbkd->expectedParamLenMin || paramLen > cbkd->expectedParamLenMax) { |
| logw("Got expected %s event, but wrong size: %u !< %u !< %u\n", name, |
| cbkd->expectedParamLenMin, paramLen, cbkd->expectedParamLenMax); |
| goto out; |
| } |
| |
| /* enqueue the callback */ |
| ret = hciScheduleSimpleCbk(cbkd->cbk, cbkd->cbkData1, cbkd->cbkData2, false, cbkd->wantEventItself ? evt : NULL); |
| |
| out: |
| free(cbkData); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimple |
| * USE: Send a command and setup a handler for a command complete/command status for it |
| * PARAMS: ogf - command group |
| * ocf - command code |
| * paramData - command params |
| * paramLen - length of command params |
| * cbk - callback to call when command complete event arrives |
| * cbkData1 - data1 to pass to that callback |
| * cbkData2 - data2 to pass to that callback |
| * minEvtDataLen - minimum event data length to accept (excluding command complete/status event itself) |
| * maxEvtDataLen - maximum event data length to accept (excluding command complete/status event itself) |
| * wantEvt - pass a copy of the event to callback? |
| * expectComplete - expect command complete event? (else command status) |
| * RETURN: true if we enqueued it, false else |
| * NOTES: |
| */ |
| static bool hciCmdSubmitSimple(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciSimpleCbk cbk, void *cbkData1, |
| void *cbkData2, uint8_t minEvtDataLen, uint8_t maxEvtDataLen, bool wantEvt, bool expectComplete) |
| { |
| struct hciEvtWaitDescr ewd; |
| struct hciSimpleCbkData *cbkd = (struct hciSimpleCbkData*)malloc(sizeof(struct hciSimpleCbkData)); |
| |
| if (!cbkd) |
| return false; |
| |
| cbkd->cbk = cbk; |
| cbkd->cbkData1 = cbkData1; |
| cbkd->cbkData2 = cbkData2; |
| cbkd->complete = expectComplete ? 1 : 0; |
| cbkd->wantEventItself = wantEvt ? 1 : 0; |
| cbkd->expectedParamLenMin = minEvtDataLen; |
| cbkd->expectedParamLenMax = maxEvtDataLen; |
| |
| ewd.evtType = expectComplete ? HCI_EVT_Command_Complete : HCI_EVT_Command_Status; |
| ewd.extra = CMD_MAKE_OPCODE(ogf, ocf); |
| ewd.cbk = hciCmdSubmitSimpleCbk; |
| ewd.cbkData = cbkd; |
| ewd.persistent = false; |
| |
| if (hciCmdSubmit(ogf, ocf, paramData, paramLen, &ewd, NULL)) |
| return true; |
| |
| free(cbkd); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitWithDoneSimpleCbk |
| * USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteWithDoneCbk() |
| * PARAMS: cbkData1 - 1st callback data (actually the original hciOpDoneCbk) |
| * cbkData2 - 2nd callback data (actually original data for hciOpDoneCbk) |
| * evt - the arrived event |
| * RETURN: true if we handled the event (always yes) |
| * NOTES: |
| */ |
| static void hciCmdSubmitWithDoneSimpleCbk(void* cbkData1, void* cbkData2, bool goingDown, struct hciEvtHdr *evt) |
| { |
| struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1); |
| struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1); |
| uint8_t *paramP = (uint8_t*)(cmpl + 1); //for compelte |
| hciOpDoneCbk cbkF = (hciOpDoneCbk)cbkData1; |
| uint8_t status; |
| |
| if (goingDown) |
| status = HCI_STAT_GOING_DOWN; |
| else if (utilGetLE8(&evt->code) == HCI_EVT_Command_Complete) |
| status = utilGetLE8(paramP); |
| else |
| status = utilGetLE8(&stat->status); |
| |
| if (!hciScheduleOpDoneCbk(cbkF, cbkData2, status)) |
| loge("Failed to shedule done callback for simple command complete handler\n"); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimpleWithCompleteWithDoneCbk |
| * USE: Send a command and setup a handler for a command complete for it. call DoneCbk |
| * PARAMS: ogf - command group |
| * ocf - command code |
| * paramData - command params |
| * paramLen - length of command params |
| * cbk - doneCbk |
| * cbkData - data1 to pass to doneCbk |
| * RETURN: true if we enqueued it, false else |
| * NOTES: This only works if the first byte of the event is a "status" byte. WATCH OUT FOR THIS!!! |
| */ |
| static bool hciCmdSubmitSimpleWithCompleteWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData) |
| { |
| return hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitWithDoneSimpleCbk, (void*)cbk, cbkData, 1/* status byte required */, 255, true, true); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimpleWithStatusWithDoneCbk |
| * USE: Send a command and setup a handler for a command status for it. call DoneCbk |
| * PARAMS: ogf - command group |
| * ocf - command code |
| * paramData - command params |
| * paramLen - length of command params |
| * cbk - doneCbk |
| * cbkData - data1 to pass to doneCbk |
| * RETURN: true if we enqueued it, false else |
| * NOTES: |
| */ |
| static bool hciCmdSubmitSimpleWithStatusWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData) |
| { |
| return hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitWithDoneSimpleCbk, (void*)cbk, cbkData, 0, 255, true, false); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitCompleteSyncSimpleCbk |
| * USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteSync() |
| * PARAMS: cbkData1 - 1st callback data (struct hciCmdSubmitWithCompleteSyncData*) |
| * cbkData2 - 2nd callback data (unused) |
| * evt - the arrived event |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciCmdSubmitCompleteSyncSimpleCbk(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt) |
| { |
| struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1); |
| struct hciCmdSubmitWithCompleteSyncData *data = (struct hciCmdSubmitWithCompleteSyncData*)cbkData1; |
| void* evtData = (void*)(cmpl + 1); |
| uint8_t paramLen; |
| |
| data->goingDown = stackGoingDown; |
| |
| if (stackGoingDown) |
| sem_post(data->sem); |
| |
| paramLen = utilGetLE8(&evt->len) - sizeof(struct hciEvtCmdComplete); |
| |
| if (paramLen > data->evtBufSz) { |
| logw("Sync complete event shortened %ud->%ud\n", paramLen, data->evtBufSz); |
| paramLen = data->evtBufSz; |
| } |
| memcpy(data->evtBuf, evtData, paramLen); |
| sem_post(data->sem); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimpleWithCompleteSync |
| * USE: Send a command and setup a handler for a command complete for it. Wait for it to complete. Optionally provide resulting event back. |
| * PARAMS: ogf - command group |
| * ocf - command code |
| * paramData - command params |
| * paramLen - length of command params |
| * evtBuf - buffer where to put event (command complete event will be stripped, only params will remain) |
| * evtBufSz - size of said buffer. Event will be truncated if it does not fit |
| * RETURN: true if it happened. false else |
| * NOTES: Will block! |
| */ |
| static bool hciCmdSubmitSimpleWithCompleteSync(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, void* evtBuf, uint8_t evtBufSz) |
| { |
| bool ret = true; |
| sem_t sem; |
| struct hciCmdSubmitWithCompleteSyncData data; |
| |
| if (pthread_self() == mCallbackThread) { |
| loge("Refusing to issue sync command from callback thread. This WILL deadlock\n"); |
| return false; |
| } |
| |
| data.sem = &sem; |
| data.evtBuf = evtBuf; |
| data.evtBufSz = evtBufSz; |
| |
| if (sem_init(&sem, 0, 0)) { |
| loge("sem init failed\n"); |
| return false; |
| } |
| |
| if (hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitCompleteSyncSimpleCbk, &data, NULL, 0, 255, true, true)) { |
| r_sem_wait(&sem); |
| if (data.goingDown) { |
| logi("going down\n"); |
| ret = false; |
| } |
| } else |
| ret = false; |
| |
| sem_destroy(&sem); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitStatusSyncSimpleCbk |
| * USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteSync() |
| * PARAMS: cbkData1 - 1st callback data (sem_t* sem) |
| * cbkData2 - 2nd callback data (struct hciEvtCmdStatus* dstEvt) |
| * evt - the arrived event |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciCmdSubmitStatusSyncSimpleCbk(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt) |
| { |
| struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1); |
| sem_t *sem = (sem_t*)cbkData1; |
| struct hciEvtCmdStatus* dstSta = (struct hciEvtCmdStatus*)cbkData2; |
| |
| if (stackGoingDown) |
| utilSetLE8(&dstSta->status, HCI_STAT_GOING_DOWN); |
| else |
| memcpy(dstSta, stat, sizeof(struct hciEvtCmdStatus)); |
| sem_post(sem); |
| } |
| |
| /* |
| * FUNCTION: hciCmdSubmitSimpleWithStatusSync |
| * USE: Send a command and setup a handler for a command status for it. Wait for it. Provide resulting event back. |
| * PARAMS: ogf - command group |
| * ocf - command code |
| * paramData - command params |
| * paramLen - length of command params |
| * dstEvt - will store event here |
| * RETURN: true if it happened. false else |
| * NOTES: Will block! |
| */ |
| static bool hciCmdSubmitSimpleWithStatusSync(uint8_t ogf, uint16_t ocf, const void *paramData, uint8_t paramLen, struct hciEvtCmdStatus *dstEvt) |
| { |
| bool ret = true; |
| sem_t sem; |
| |
| if (pthread_self() == mCallbackThread) { |
| loge("Refusing to issue sync command from callback thread. This WILL deadlock\n"); |
| return false; |
| } |
| |
| if (sem_init(&sem, 0, 0)) { |
| loge("sem init failed\n"); |
| return false; |
| } |
| |
| if (hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitStatusSyncSimpleCbk, &sem, dstEvt, 0, 0, true, false)) |
| r_sem_wait(&sem); |
| else |
| ret = false; |
| |
| sem_destroy(&sem); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciInquiryUpdatePersistDeviceDb |
| * USE: Update persistent "seen devices" DB with EIR data |
| * PARAMS: addr - the address of the remote device |
| * devClsP - device class or NULL if not known |
| * eir - the provided EIR data (if any) |
| * eirLen - size of the above buffer |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void hciInquiryUpdatePersistDeviceDb(const struct bt_addr *addr, const uint32_t *devClsP, const void *eir, uint8_t eirLen) |
| { |
| const uint8_t *name = NULL; |
| uint32_t nameLen = 0; |
| uint32_t eirDevCls; |
| bool nameIsFull = false; |
| |
| if (eirLen) { /* parse eir to find name */ |
| const uint8_t *eirP = (const uint8_t*)eir; |
| const uint8_t *eirEnd = eirP + eirLen; |
| |
| while (eirP < eirEnd) { |
| uint8_t type, len = *eirP++; |
| if (!len) /* zero length item means end of EIR */ |
| break; |
| type = *eirP++; |
| len--; |
| |
| if (type == HCI_EIR_TYPE_SHORTENED_NAME || type == HCI_EIR_TYPE_FULL_NAME) { |
| name = eirP; |
| nameLen = len; |
| nameIsFull = type == HCI_EIR_TYPE_FULL_NAME; |
| } else if (type == HCI_EIR_DEV_CLS && len == 3) { |
| eirDevCls = utilGetLE24(eirP); |
| devClsP = &eirDevCls; |
| } |
| eirP += len; |
| } |
| } |
| |
| persistAddKnownDev(addr, name, name ? &nameLen : NULL, nameIsFull, devClsP); |
| } |
| |
| /* |
| * FUNCTION: hciInquiryEvtLeSingleItem |
| * USE: Enqueue a callback to discovery LE cbk |
| * PARAMS: addr - the address of the remote device |
| * advType - type of reply |
| * rssi - the RSSI |
| * advData - the provided advertisement data |
| * advDataLen - size of the above buffer |
| * RETURN: NONE |
| * NOTES: a copy of all params is made |
| */ |
| static void hciInquiryEvtLeSingleItem(const struct bt_addr *addr, uint8_t advType, int8_t rssi, const void *advData, uint8_t advDataLen) |
| { |
| struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem) + advDataLen); |
| if (!w) { |
| logw("Dropping LE discovered item due to memory error\n"); |
| return; |
| } |
| |
| w->cbkWorkItemType = CBK_WORK_LE_DISC_DEV; |
| memcpy(&w->leDev.addr, addr, sizeof(w->leDev.addr)); |
| memcpy(w->leDev.adv, advData, advDataLen); |
| w->leDev.advType = advType; |
| w->leDev.advLen = advDataLen; |
| w->leDev.rssi = rssi; |
| |
| hciInquiryUpdatePersistDeviceDb(addr, NULL, advData, advDataLen); |
| if (workQueuePut(mCallbackWork, w)) |
| return; |
| |
| free(w); |
| logi("Dropping LE discovered item dues to queue error\n"); |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetTerminatedEventHandle |
| * USE: Called when we know an adv set stopped being advertised (usually due to a connection) |
| * PARAMS: advInstance - the hw instance id of the adv set |
| * aclConnId - the connection this caused (or ACL_CONN_ID_INVALID is none) |
| * RETURN: none |
| * NOTES: will take mAdvSetsLock and mConnsLock (not concurrently). If a connection is found and |
| * it is still in a BLAMING state, it will be adjusted to config state and advSet param |
| * will be filled. |
| */ |
| static void hciAdvSetTerminatedEventHandle(uint8_t advInstance, uint16_t aclConnId) |
| { |
| hci_adv_set_t setH = 0; |
| struct hciAdvSet *t; |
| struct bt_addr self; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| for (t = mAdvSets; t; t = t->next) { |
| if (t->hwEnabled && t->hwSetNum == advInstance) { |
| hciAdvSetGetCurAdvAddrLocked(t, &self); |
| t->hwEnabled = false; //even if conn not found, this set was still fingered for being the cause so it is still already off |
| setH = t->handle; |
| break; |
| } |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| if (!setH) |
| logw("Adv id not found for vendor advt change event\n"); |
| else if (aclConnId != ACL_CONN_ID_INVALID) { |
| |
| struct hciAclConn* conn; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindById(aclConnId); |
| if (!conn) |
| logw("Connection id not found for vendor advt change event\n"); |
| else if (conn->state != CONN_STATE_ADV_SET_BLAMING) |
| logw("Conn is not in blaming state for vendor advt change event\n"); |
| else { |
| conn->le.advSet = setH; |
| conn->selfAddr = self; |
| conn->state = CONN_STATE_CFG; |
| hciConnCfg(conn, CONN_CFG_STEP_LE, 0); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| } |
| |
| /* |
| * FUNCTION: hciConnEvtCbk |
| * USE: Called with connection state events |
| * PARAMS: evt - the event |
| * evtSz - event size |
| * cbkData - callback data |
| * evtWaitStateID - event wait state ID (unused) |
| * forCmdID - event commadn ID (unused) |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool hciConnEvtCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| uint8_t evtType; |
| if (!evt) |
| return false; |
| |
| evtType = utilGetLE8(&evt->code); |
| if (evtType == HCI_EVT_Disconnection_Complete) { |
| struct hciEvtDiscComplete *disc = (struct hciEvtDiscComplete*)(evt + 1); |
| uint8_t status = utilGetLE8(&disc->status); |
| uint16_t cid = utilGetLE16(&disc->conn); |
| uint8_t reason = utilGetLE8(&disc->reason); |
| |
| logd("ACL conn %d down for reason %d with status %d\n", cid, reason, status); |
| hciHandleConnAclDown(cid, reason); |
| |
| } else if (evtType == HCI_EVT_Encryption_Change) { |
| struct hciEvtEncrChange *encr = (struct hciEvtEncrChange*)(evt + 1); |
| uint8_t status = utilGetLE8(&encr->status); |
| uint16_t cid = utilGetLE16(&encr->conn); |
| bool encrOn = !status && utilGetLE8(&encr->encrOn); |
| |
| logd("ACL conn %d encr %d with status %d\n", cid, encrOn, status); |
| if (!hciHandleConnAclEncr(cid, encrOn)) |
| loge("Failed to handle conn encr\n"); |
| } else if (evtType == HCI_EVT_Authentication_Complete) { |
| struct hciEvtEncrChange *encr = (struct hciEvtEncrChange*)(evt + 1); |
| uint8_t status = utilGetLE8(&encr->status); |
| uint16_t cid = utilGetLE16(&encr->conn); |
| |
| logd("ACL conn %d auth with status %d\n", cid, status); |
| if (!hciHandleConnAclAuth(cid, !status)) |
| loge("Failed to handle conn auth\n"); |
| } else if (evtType == HCI_EVT_Encryption_Key_Refresh_Complete) { |
| struct hciEvtEncrKeyRefreshComplete *encr = (struct hciEvtEncrKeyRefreshComplete*)(evt + 1); |
| uint8_t status = utilGetLE8(&encr->status); |
| uint16_t cid = utilGetLE16(&encr->conn); |
| bool encrOn = !status; |
| |
| logd("ACL conn %d encr key refreshed %d with status %d\n", cid, encrOn, status); |
| if (!hciHandleConnAclEncrKeyRefresh(cid, encrOn)) |
| loge("Failed to handle conn encr key refreshed\n"); |
| } else if (evtType == HCI_EVT_LE_Meta) { |
| struct hciEvtLeMeta *meta = (struct hciEvtLeMeta*)(evt + 1); |
| evtType = utilGetLE8(&meta->subevent); |
| |
| if (evtType == HCI_EVTLE_Connection_Complete) { |
| struct hciEvtLeConnectionComplete *conn = (struct hciEvtLeConnectionComplete*)(meta + 1); |
| uint8_t status = utilGetLE8(&conn->status); |
| uint16_t cid = utilGetLE16(&conn->conn); |
| bool amSlave = !!utilGetLE8(&conn->amSlave); |
| bool peerAddrRandom = !!utilGetLE8(&conn->peerAddrRandom); |
| uint16_t interval = utilGetLE16(&conn->connInterval); |
| uint16_t latency = utilGetLE16(&conn->connLatency); |
| uint16_t timeout = utilGetLE16(&conn->supervisionTimeout); |
| uint8_t mca = utilGetLE8(&conn->masterClockAccuracy); |
| |
| logd("ACL LE conn %d up to %c"MACFMT" as %c: {%d,%d,%d,%d}\n", cid, peerAddrRandom ? 'R' : 'P', MACCONV(conn->peerMac), amSlave ? 'S' : 'M', interval, latency, timeout, mca); |
| if (!hciHandleConnAclLeUp(status, cid, !amSlave, conn->peerMac, peerAddrRandom, interval, latency, timeout, mca)) |
| loge("Failed to handle conn LE conn up\n"); |
| } else if (evtType == HCI_EVTLE_Connection_Update_Complete) { |
| struct hciEvtLeConnectionUpdateComplete *updt = (struct hciEvtLeConnectionUpdateComplete*)(meta + 1); |
| uint8_t status = utilGetLE8(&updt->status); |
| uint16_t cid = utilGetLE16(&updt->conn); |
| uint16_t interval = utilGetLE16(&updt->connInterval); |
| uint16_t latency = utilGetLE16(&updt->connLatency); |
| uint16_t timeout = utilGetLE16(&updt->supervisionTimeout); |
| |
| logd("ACL LE conn %d update to {%d,%d,%d}\n", cid, interval, latency, timeout); |
| if (!hciHandleConnAclLeUpdate(status, cid, interval, latency, timeout)) |
| loge("Failed to handle LE conn update\n"); |
| } else if (evtType == HCI_EVTLE_LTK_Request) { |
| struct hciEvtLeLtkRequest *req = (struct hciEvtLeLtkRequest*)(meta + 1); |
| uint16_t cid = utilGetLE16(&req->conn); |
| uint64_t randomNum = utilGetLE16(&req->randomNum); |
| uint16_t diversifier = utilGetLE16(&req->diversifier); |
| |
| logd("ACL LE key req for conn 0x%02x with rand 0x"FMT64"x and div 0x%02x\n", cid, CNV64(randomNum), diversifier); |
| if (!hciHandleConnLeLtkReq(cid, randomNum, diversifier)) |
| loge("Failed to handle LE key req\n"); |
| } else if (evtType == HCI_EVTLE_Adv_Set_Terminated) { |
| struct hciEvtLeAdvSetTerminated *req = (struct hciEvtLeAdvSetTerminated*)(meta + 1); |
| |
| hciAdvSetTerminatedEventHandle(req->advSetHandle, req->status ? ACL_CONN_ID_INVALID : req->conn); |
| } else |
| return false; |
| } else if (evtType == HCI_EVT_Vendor) { |
| struct hciEvtVendor *vendor = (struct hciEvtVendor*)(evt + 1); |
| evtType = utilGetLE8(&vendor->subevent); |
| |
| if (evtType == HCI_EVTVENDOR_Android_Subevent_Advt_State_Change) { |
| struct hciEvtVendorAndroidVendorExtAdvtStateChange *sce = (struct hciEvtVendorAndroidVendorExtAdvtStateChange*)(vendor + 1); |
| |
| if (evtSz != sizeof(*evt) + sizeof(*vendor) + sizeof(*sce)) |
| logw("Invalid event size for vendor subevent for advt change - ignored\n"); |
| else if (sce->reason != HCI_EVTVENDOR_Android_Subevent_Advt_State_Change_Reason) |
| logw("Invalid reason code for vendor subevent for advt change - ignored\n"); |
| else |
| hciAdvSetTerminatedEventHandle(sce->advInstance, sce->connHandle); |
| } else |
| return false; |
| } else |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciInquiryEvtCbk |
| * USE: Called with inquiry events |
| * PARAMS: evt - the event |
| * evtSz - event size |
| * cbkData - callback data |
| * evtWaitStateID - event wait state ID (unused) |
| * forCmdID - event commadn ID (unused) |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool hciInquiryEvtCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| uint8_t evtType; |
| struct bt_addr addr; |
| uint8_t i, num; |
| |
| if (!evt) |
| return false; |
| |
| evtType = utilGetLE8(&evt->code); |
| |
| if(evtType == HCI_EVT_LE_Meta) { |
| struct hciEvtLeMeta *meta = (struct hciEvtLeMeta*)(evt + 1); |
| struct hciEvtLeAdvReport *adv = (struct hciEvtLeAdvReport*)(meta + 1); |
| struct hciEvtLeAdvReportItem *item = (struct hciEvtLeAdvReportItem*)(adv + 1); |
| uint8_t subevent = utilGetLE8(&meta->subevent); |
| uint8_t *rssiP; |
| |
| if (subevent != HCI_EVTLE_Advertising_Report) { |
| loge("Unexpected LE subevent 0x%02X in discovery event handler\n", subevent); |
| return false; |
| } |
| |
| num = utilGetLE8(&adv->numReports); |
| |
| for (i = 0; i < num; i++) { |
| uint8_t dataLen = utilGetLE8(&item->dataLen); |
| |
| addr.type = utilGetLE8(&item->randomAddr) ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC; |
| memcpy(addr.addr, item->mac, sizeof(addr.addr)); |
| rssiP = item->data + dataLen; |
| hciInquiryEvtLeSingleItem(&addr, utilGetLE8(&item->advType), utilGetLE8(rssiP), item->data, dataLen); |
| item = (struct hciEvtLeAdvReportItem*)(rssiP + 1); |
| } |
| } else { |
| loge("Unexpected event 0x%02X in discovery event handler\n", evtType); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciLeParamChangeRequestEventCbk |
| * USE: Called with remote's requests for parameter changes |
| * PARAMS: evt - the event |
| * evtSz - event size |
| * cbkData - callback data |
| * evtWaitStateID - event wait state ID (unused) |
| * forCmdID - event commadn ID (unused) |
| * RETURN: false on error |
| * NOTES: we deny all such requests. Remote devices have no business telling us what to do and how to live our life. We do what we want! |
| */ |
| static bool hciLeParamChangeRequestEventCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| const struct hciEvtLeMeta *meta = (const struct hciEvtLeMeta*)(evt + 1); |
| const struct hciEvtLeRemoteConnParamRequest *req = (const struct hciEvtLeRemoteConnParamRequest*)(meta + 1); |
| struct hciLeRemoteConnParamRequestNegativeReply repl = {.reason = HCI_ERR_Unsupported_Feature_Or_Parameter_Value,}; |
| |
| if (evtSz != sizeof(struct hciEvtHdr) + sizeof(struct hciEvtLeMeta) + sizeof(struct hciEvtLeRemoteConnParamRequest)) { |
| logw("Invalid event size %u\n", evtSz); |
| return false; |
| } |
| |
| utilSetLE16(&repl.conn, utilGetLE16(&req->conn)); |
| |
| if (!hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Remote_Conn_Param_Request_Negative_Reply, &repl, sizeof(repl), hciCmdStatusCbk, NULL)) { |
| logi("Failed to request connection param change denial\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciResetEvtCbk |
| * USE: Called when a reset complete event arrives. Tears down any existing state and sets the in-reset state. |
| * PARAMS: evt - the event (unused) |
| * evtSz - event size |
| * cbkData - callback data (unused) |
| * evtWaitStateID - event wait state ID (unused) |
| * forCmdID - event command ID (unused) |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool hciResetEvtCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| //notify everyone that all connections are down |
| |
| |
| pthread_mutex_lock(&mConnsLock); |
| while (mConns) { |
| //for all connections upper layers know of, tell them they are no more |
| if (mConns->state == CONN_STATE_CFG || mConns->state == CONN_STATE_RUNNING) |
| if (!hciScheduleConnDownNotif(mConns->handle, HCI_ERR_Connection_Terminated_By_Local_Host)) |
| loge("Failed to schedule conn down notif\n"); |
| hciConnAclStructDel(mConns); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| |
| //remove command send authority if it exists (this event may give us a credit or not, that will be handled later) |
| while(!sem_trywait(&mCmdSendSem)); |
| |
| //remember that we're in reset |
| mStackState = STACK_STATE_GOING_DOWN; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciSetupResetEventHandler |
| * USE: Set up the handler for the "reset" event |
| * PARAMS: cmdWaitLockHeld - set if caller has mCmdWaitLock |
| * RETURN: false on error |
| * NOTES: may be called with mCmdWaitLock held (see params) |
| */ |
| static bool hciSetupResetEventHandler(bool cmdWaitLockHeld) |
| { |
| bool ret; |
| struct hciEvtWaitDescr ewd = { |
| .cbk = hciResetEvtCbk, |
| .evtType = HCI_EVT_Command_Complete, |
| .extra = CMD_MAKE_OPCODE(HCI_OGF_Controller_and_Baseband, HCI_CMD_Reset), |
| .persistent = true, |
| }; |
| |
| if (cmdWaitLockHeld) |
| ret = hciEvtWaitEnqueueUnlocked(&ewd, 0); |
| else |
| ret = hciEvtWaitEnqueue(&ewd, 0); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciIsUp |
| * USE: Ask if chip is ready to be used |
| * PARAMS: none |
| * RETURN: the answer |
| * NOTES: |
| */ |
| bool hciIsUp(void) |
| { |
| loge(" *X* state = %d\n", mStackState); |
| |
| if (mStackState == STACK_STATE_READY_FOR_UP) |
| return hciBtStackUp(); |
| |
| return mStackState == STACK_STATE_UP; |
| } |
| |
| /* |
| * FUNCTION: hciSetManyEventPersistentHandlers |
| * USE: Set a few identical event handlers for variosu events |
| * PARAMS: evtTypes - event types list (ended with a 0) |
| * evtExtras - event extras array (or NULL if all zeros are to be assumed) |
| * cbk - the handler |
| * cbkData - data for the handler |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool hciSetManyEventPersistentHandlers(const uint8_t *evtTypes, const uint16_t *evtExtras, hciEvtCbk cbk, void *cbkData) |
| { |
| |
| struct hciEvtWaitDescr ewd = {0, }; |
| uint8_t i; |
| |
| ewd.cbk = cbk; |
| ewd.cbkData = cbkData; |
| ewd.persistent = true; |
| |
| for (i = 0; evtTypes[i]; i++) { |
| ewd.evtType = evtTypes[i]; |
| ewd.extra = evtExtras ? evtExtras[i] : 0; |
| |
| if (!hciEvtWaitEnqueue(&ewd, 0)) { |
| loge("Failed to enqueue handler %d: 0x%02X.%04X\n", i, ewd.evtType, ewd.extra); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciGetLocalAddress |
| * USE: Get the local static BT addr |
| * PARAMS: addr - the buffer to stash address in. not a bt_addr struct since type is not determinable |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void hciGetLocalAddress(uint8_t *addr) |
| { |
| memcpy(addr, mLocalAddr, sizeof(mLocalAddr)); |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetEnableDisableMethodAndroidVendorCmds |
| * USE: Enable or disable an adv set on BT4 chips that support android-vendor BT extension |
| * PARAMS: set - the set to operate on |
| * enable - on or off? |
| * RETURN: success |
| * NOTES: called with mAdvSetsLock held |
| * This is one of the few "adv set on/off handlers". This one is used |
| * when the android-vendor methods are available and BT 5.0 is not |
| */ |
| static bool hciAdvSetEnableDisableMethodAndroidVendorCmds(struct hciAdvSet *set, bool enable) |
| { |
| struct hciCmplLeReadAdvChannelTxPower pwrLvlEvt; |
| int8_t desiredPowerLvl = set->powerLevel; |
| struct hciAdvSet *t; |
| bool *usedSets; |
| uint16_t i; |
| int ret; |
| |
| /* |
| * Some explanation here: Android Vendor Extensions for BT 4 provide for multi-adv, |
| * but caveats exist. If the chip says it supports X slots, in reality it supports |
| * X - 1 slots with this custom API (indexed at 1) and the default 4.0 API (slot 0). |
| * "but," you might say - "default API has no TX power control!" Yes. Slot 0 has no |
| * tx power control. But it is even worse. Slot 0 shared the "random address" with |
| * any connection attempt we make using a random address, so we cannot really use |
| * it to advertise at the same time as we try to connect to anyone. There are some |
| * ways to cleverly use it for both, but they are fragile and error-prone. Instead, |
| * we'll just use slots 1..N and not use slot 0. This way we can always safely set |
| * the chip random address for connection purposes. |
| */ |
| |
| if (!enable) { // disable the set we do not need |
| |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetAdvEnableSync(false, set->hwSetNum); |
| else |
| ret = hciLeSetAdvEnableSync(false); |
| if (ret) { |
| logw("Cannot disable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetAdvEnable command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| set->hwEnabled = false; |
| return true; |
| } |
| |
| //sanity checks specific to this method |
| if (desiredPowerLvl != HCI_ADV_TX_PWR_LVL_DONT_CARE) { |
| if (desiredPowerLvl > 20) { |
| logi("AndroidVendorExtensions adv does not support adv power level over 20dBm - cutting set 0x"HCI_ADV_SET_FMT"x down to 20dBm from %ddBm\n", HCI_ADV_SET_CONV(set->handle), set->powerLevel); |
| desiredPowerLvl = 20; |
| } |
| if (desiredPowerLvl < -70) { |
| logi("AndroidVendorExtensions adv does not support adv power level below -70dBm - raising set 0x"HCI_ADV_SET_FMT"x up to -70dBm from %ddBm\n", HCI_ADV_SET_CONV(set->handle), set->powerLevel); |
| desiredPowerLvl = -70; |
| } |
| } |
| |
| //we want to enable: first find a free slot (if any) |
| usedSets = alloca(mMaxAdvSets + 1); |
| memset(usedSets, 0, mMaxAdvSets + 1); |
| for (t = mAdvSets; t; t = t->next) { |
| if (t->hwEnabled) { |
| if (usedSets[t->hwSetNum]) |
| logw("It seems that HW adv set %u is used by two advs... this is bad\n", t->hwSetNum); |
| else |
| usedSets[t->hwSetNum] = true; |
| } |
| } |
| for (i = 1; i < mMaxAdvSets + 1; i++) { |
| if (!usedSets[i]) |
| break; |
| } |
| if (i == mMaxAdvSets + 1) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because there are no free slots\n", HCI_ADV_SET_CONV(set->handle)); |
| return false; |
| } |
| logd("picked slot %u for adv set 0x"HCI_ADV_SET_FMT"x\n", i, HCI_ADV_SET_CONV(set->handle)); |
| |
| //if no power level was requested, use max |
| if (desiredPowerLvl == HCI_ADV_TX_PWR_LVL_DONT_CARE) |
| desiredPowerLvl = 20; |
| |
| //configure the slot: number |
| set->hwSetNum = i; |
| |
| //configure the slot: adv data |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetAdvDataSync(set->advDataLen, set->advData, set->hwSetNum); |
| else |
| ret = hciLeSetAdvDataSync(set->advDataLen, set->advData); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetAdvData command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| |
| //configure the slot: scan rsp data |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetScanRspDataSync(set->scanRspDataLen, set->scanRspData, set->hwSetNum); |
| else |
| ret = hciLeSetScanResponseDataSync(set->scanRspDataLen, set->scanRspData); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetScanRspData command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| |
| //configure the slot: adv params |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetAdvParamsSync(set->intervalMin, set->intervalMax, set->advType, set->ownAddressType, set->ownRandomAddr.addr, set->directAddr.type == BT_ADDR_TYPE_LE_RANDOM ? 1 : 0, set->directAddr.addr, set->channelMap, set->filterPolicy, set->hwSetNum, desiredPowerLvl); |
| else |
| ret = hciLeSetAdvParamsSync(set->intervalMin, set->intervalMax, set->advType, set->ownAddressType, set->directAddr.type == BT_ADDR_TYPE_LE_RANDOM ? 1 : 0, set->directAddr.addr, set->channelMap, set->filterPolicy); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetAdvParams command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| |
| //configure the slot: own random address (if needed) |
| if (set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_RANDOM || set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_AUTO_RESOLV_ELSE_RANDOM) { |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetRandomAddrSync(set->ownRandomAddr.addr, set->hwSetNum); |
| else |
| ret = hciLeSetRandomAddressSync(set->ownRandomAddr.addr); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetRandomAddr command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| } |
| |
| //configure the slot: record/read *actual* TX power level |
| if (set->hwSetNum) |
| set->hwPowerLevel = desiredPowerLvl; |
| else { |
| ret = hciLeReadAdvChannelTxPowerSync(&pwrLvlEvt); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because GetTxPwrLvl command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| set->hwPowerLevel = pwrLvlEvt.txPower; |
| } |
| |
| //enable the slot |
| if (set->hwSetNum) |
| ret = hciVendorLeMultiAdvSetAdvEnableSync(true, set->hwSetNum); |
| else |
| ret = hciLeSetAdvEnableSync(true); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x on channel %u because VendorSetAdvEnable command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), set->hwSetNum, ret); |
| return false; |
| } |
| |
| set->hwEnabled = true; |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmdsTimeCbk |
| * USE: Timer timeout for adv-set blaming for AndroidVendor connections |
| * PARAMS: timer - the timer handle |
| * aclConnHandle - the acl connection handle |
| * RETURN: none |
| * NOTES: if the connection still has not been blamed, we blame it on adv set that was on zero (it was pre-populated in hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmds) |
| */ |
| static void hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmdsTimeCbk(uniq_t timer, uint64_t aclConnHandle) |
| { |
| struct hciAclConn* conn; |
| |
| //if we got here and conn is still in "wait" state, bad things |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle((hci_conn_t)aclConnHandle); |
| if (conn && conn->state == CONN_STATE_ADV_SET_BLAMING) { |
| hciConnDisconnect(conn->id); |
| hciConnAclStructDel(conn); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmds |
| * USE: Blame a new connection on an adv set and update accounting info |
| * PARAMS: conn - the ACL connection |
| * RETURN: false always - for this we must always wait for a later "blame" event |
| * NOTES: called with mAdvSetsLock held |
| * This is one of the few "adv set conn blame handlers". This one is used |
| * when the android-vendor methods are available and BT 5.0 is not. |
| * conn->le.advSet will be set to the adv set that is currently in slot 0 |
| * since that is the one we blame if no "blame event" shows up |
| */ |
| static bool hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmds(struct hciAclConn *conn) |
| { |
| conn->le.advSet = 0; |
| |
| /* |
| * we ALWAYS MUST wait here. Either the event will come telling us which adv set |
| * caused the connection or the timer will fire and we'll assume bad things |
| */ |
| timerSet(ADV_SET_BLAME_TIMEOUT_ANDROID_VENDOR, hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmdsTimeCbk, conn->handle); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetEnableDisableMethodBT4 |
| * USE: Enable or disable an adv set on normal BT4 devices |
| * PARAMS: set - the set to operate on |
| * enable - on or off? |
| * RETURN: success |
| * NOTES: called with mAdvSetsLock held |
| * This is one of the few "adv set on/off handlers". This one is used |
| * when neither the BT 5.0 nor the android-vendor methods are available. |
| */ |
| static bool hciAdvSetEnableDisableMethodBT4(struct hciAdvSet *set, bool enable) |
| { |
| struct hciCmplLeReadAdvChannelTxPower pwrLvlEvt; |
| struct hciAdvSet *t; |
| int ret; |
| |
| if (!enable) { // we only have one set - simply disable it |
| |
| if (hciLeSetAdvEnableSync(false)) { |
| logw("Failed to disable BT4 adv\n"); |
| return false; |
| } |
| set->hwEnabled = false; |
| return true; |
| } |
| |
| //we want to enable: first see if another set is already on |
| for (t = mAdvSets; t; t = t->next) { |
| if (t->hwEnabled) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because set 0x"HCI_ADV_SET_FMT"x is enabled.\n", HCI_ADV_SET_CONV(set->handle), HCI_ADV_SET_CONV(t->handle)); |
| return false; |
| } |
| } |
| |
| //we want to enable and no other set is enabled - simply configure the chip and go |
| ret = hciLeSetAdvDataSync(set->advDataLen, set->advData); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because SetAdvData command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| ret = hciLeSetScanResponseDataSync(set->scanRspDataLen, set->scanRspData); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because SetScanRspData command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| ret = hciLeSetAdvParamsSync(set->intervalMin, set->intervalMax, set->advType, set->ownAddressType, set->directAddr.type == BT_ADDR_TYPE_LE_RANDOM ? 1 : 0, set->directAddr.addr, set->channelMap, set->filterPolicy); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because SetAdvParams command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| if (set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_RANDOM || set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_AUTO_RESOLV_ELSE_RANDOM) { |
| ret = hciLeSetRandomAddressSync(set->ownRandomAddr.addr); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because SetRandomAddr command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| } |
| ret = hciLeReadAdvChannelTxPowerSync(&pwrLvlEvt); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because GetTxPwrLvl command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| set->hwPowerLevel = pwrLvlEvt.txPower; |
| ret = hciLeSetAdvEnableSync(true); |
| if (ret) { |
| logw("Cannot enable adv 0x"HCI_ADV_SET_FMT"x because SetAdvEnable command failed: %02X\n", HCI_ADV_SET_CONV(set->handle), ret); |
| return false; |
| } |
| |
| set->hwEnabled = true; |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetHandleAndBlameNewConnMethodBT4 |
| * USE: Blame a new connection on an adv set and update accounting info |
| * PARAMS: conn - the ACL connection |
| * RETURN: true always - no point in waiting. We either know or do not |
| * NOTES: called with mAdvSetsLock held |
| * This is one of the few "adv set conn blame handlers". This one is used |
| * when neither the BT 5.0 nor the android-vendor methods are available. |
| * conn->le.advSet will be set to the adv set that caused this connection, |
| * or to 0 if we do not know |
| */ |
| static bool hciAdvSetHandleAndBlameNewConnMethodBT4(struct hciAclConn *conn) |
| { |
| struct hciAdvSet *t; |
| |
| /* easy enough here - just blame the set that is on */ |
| for (t = mAdvSets; t; t = t->next) { |
| if (t->hwEnabled) { |
| t->hwEnabled = false; //adv stops when a connection is formed |
| conn->le.advSet = t->handle; |
| return true; |
| } |
| } |
| |
| //we do not know but waiting will not resolve it |
| conn->le.advSet = 0; |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciBtStackUp |
| * USE: Brings up the chip in a sane configuration, reads versions, etc |
| * PARAMS: NONE |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool hciBtStackUp(void) |
| { |
| int ret = 0; |
| |
| /* sanity and start */ |
| { |
| if (mStackState != STACK_STATE_READY_FOR_UP) { |
| loge("Stack cannot be brought up if not ready for bringup\n"); |
| return false; |
| } |
| mStackState = STACK_STATE_COMING_UP; |
| } |
| |
| /* read local BT version */ |
| { |
| struct hciCmplReadLocalVersion evt; |
| uint8_t hciVersion, lmpVersion; |
| |
| logi("BT UP: reading version\n"); |
| ret = hciReadLocalVersionSync(&evt); |
| if (ret) |
| goto out; |
| |
| hciVersion = utilGetLE8(&evt.hciVersion); |
| lmpVersion = utilGetLE8(&evt.lmpVersion); |
| logi("BT UP: bt versions reported: %u, %d\n", hciVersion, lmpVersion); |
| |
| if (hciVersion > HCI_VER_MAX_SUPPORTED) |
| hciVersion = HCI_VER_MAX_SUPPORTED; |
| |
| if (lmpVersion > HCI_VER_MAX_SUPPORTED) |
| lmpVersion = HCI_VER_MAX_SUPPORTED; |
| |
| mBtVer = hciVersion < lmpVersion ? hciVersion : lmpVersion; |
| |
| if (mBtVer == HCI_VERSION_2_0) { /* do 2.0 chips really exist? */ |
| logi("BT 2.0 chip???\n"); |
| mBtVer = HCI_VERSION_1_2; |
| } |
| |
| logi("BT UP: effective BT version: %u (BT spec %s)\n", mBtVer, mBtVers[mBtVer]); |
| |
| if (mBtVer < HCI_VERSION_4_0) { |
| loge("BT 4.0 minimum!\n"); |
| ret = -3; |
| goto out; |
| } |
| /* past this point, we can assume BT 4.0+ */ |
| } |
| |
| /* read supported features ad make sure LE is in there*/ |
| { |
| struct hciCmplReadLocalSupportedFeatures evt; |
| |
| logi("BT UP: reading features\n"); |
| ret = hciReadLocalSupportedFeaturesSync(&evt); |
| if (ret) |
| goto out; |
| |
| mLocalFtrs[0] = utilGetLE64(&evt.features); |
| logi("BT UP: bt features reported: "FMT64"X\n", CNV64(mLocalFtrs[0])); |
| |
| if (!(mLocalFtrs[0] & HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER)) { |
| loge("BT LE required!\n"); |
| ret = -3; |
| goto out; |
| } |
| } |
| |
| /* read extended features if possible */ |
| if (mLocalFtrs[0] & HCI_LMP_FTR_EXTENDED_FEATURES) { |
| uint8_t page, numPages = 1; |
| |
| for (page = 0; page < numPages && page < MAX_FTR_PAGES; page++) { |
| struct hciCmplReadLocalExtendedFeatures evt; |
| uint64_t ftrs; |
| uint8_t claimedPage; |
| |
| ret = hciReadLocalExtendedFeaturesSync(page, &evt); |
| if (ret) |
| goto out; |
| ftrs = utilGetLE64(&evt.features); |
| claimedPage = utilGetLE8(&evt.page); |
| numPages = utilGetLE8(&evt.maxPage) + 1; |
| |
| if (claimedPage < MAX_FTR_PAGES) |
| mLocalFtrs[claimedPage] = ftrs; |
| else |
| logw("Ignoring report for ftr page %u\n", claimedPage); |
| |
| logi("BT UP: bt extended features[%d/%d, requested %u] reported: "FMT64"X\n", claimedPage, numPages, page, CNV64(ftrs)); |
| } |
| } |
| |
| /* read MAC address and show it */ |
| { |
| struct hciCmplReadBdAddr evt; |
| |
| logi("Reading local MAC\n"); |
| ret = hciReadBdAddrSync(&evt); |
| if (ret) |
| goto out; |
| |
| memcpy(mLocalAddr, evt.mac, sizeof(mLocalAddr)); |
| logi("Local MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", evt.mac[5], evt.mac[4], evt.mac[3], evt.mac[2], evt.mac[1], evt.mac[0]); |
| } |
| |
| /* read EDR buffer sizes */ |
| { |
| struct hciCmplReadBufferSize evt; |
| uint16_t i; |
| |
| logi("BT UP: reading EDR buffer size\n"); |
| ret = hciReadBufferSizeSync(&evt); |
| if (ret) |
| goto out; |
| |
| mScoBufSz = utilGetLE8(&evt.scoBufferLen); |
| mAclBufSzEdr = utilGetLE16(&evt.aclBufferLen); |
| mAclBufNumEdr = utilGetLE16(&evt.numAclBuffers); |
| mScoBufNum = utilGetLE16(&evt.numScoBuffers); |
| |
| logi("BT UP: EDR buffers: %ux%ub ACL + %ux%ub SCO\n", mAclBufNumEdr, mAclBufSzEdr, mScoBufNum, mScoBufSz); |
| |
| for (i = 0; i < mAclBufNumEdr; i++) |
| sem_post(&mAclPacketsEdr); |
| } |
| |
| /* read LE buffer sizes & features, if LE exists */ |
| { |
| struct hciCmplLeReadLocalSupportedFeatures evtFtrs; |
| struct hciCmplLeReadBufferSize evt; |
| uint16_t i; |
| |
| logi("BT UP: reading LE buffer size\n"); |
| ret = hciLeReadBufferSizeSync(&evt); |
| if (ret) |
| goto out; |
| |
| mAclBufSzLe = utilGetLE16(&evt.leBufferSize); |
| mAclBufNumLe = utilGetLE8(&evt.leNumBuffers); |
| |
| logi("BT UP: LE buffers: %ux%ub\n", mAclBufNumLe, mAclBufSzLe); |
| |
| if (!mAclBufNumLe) |
| logi("BT UP: joint EDR/LE ACL buffers detected\n"); |
| else |
| mBtJointBuffers = false; |
| |
| for (i = 0; i < mAclBufNumLe; i++) |
| sem_post(&mAclPacketsLe); |
| |
| logi("Reading local LE features\n"); |
| ret = hciLeReadLocalSupportedFeaturesSync(&evtFtrs); |
| if (ret) |
| goto out; |
| |
| mLocalLeFtrs = utilGetLE64(&evtFtrs.leFeatures); |
| logi("BT UP: le features reported: "FMT64"X\n", CNV64(mLocalLeFtrs)); |
| } |
| |
| /* enable LE */ |
| logi("Enabling LE\n"); |
| ret = hciWriteLeHostSupportedSync(true, false); |
| if (ret) |
| goto out; |
| logi("LE should be on\n"); |
| |
| /* set event masks */ |
| { |
| uint64_t evts = 0, evts2 = 0, evtsLe = 0; |
| |
| switch (mBtVer) { |
| case HCI_VERSION_4_0: |
| evts = HCI_EVENT_ALL_BT_4_0; |
| evts2 = HCI_EVENT_P2_ALL_BT_4_0; |
| evtsLe = HCI_LE_EVENT_ALL_BT_4_0; |
| break; |
| case HCI_VERSION_4_1: |
| evts = HCI_EVENT_ALL_BT_4_1; |
| evts2 = HCI_EVENT_P2_ALL_BT_4_1; |
| evtsLe = HCI_LE_EVENT_ALL_BT_4_1; |
| break; |
| case HCI_VERSION_4_2: |
| evts = HCI_EVENT_ALL_BT_4_2; |
| evts2 = HCI_EVENT_P2_ALL_BT_4_2; |
| evtsLe = HCI_LE_EVENT_ALL_BT_4_2; |
| break; |
| case HCI_VERSION_5_0: |
| evts = HCI_EVENT_ALL_BT_5_0; |
| evts2 = HCI_EVENT_P2_ALL_BT_5_0; |
| evtsLe = HCI_LE_EVENT_ALL_BT_5_0; |
| break; |
| } |
| evts &=~ HCI_EVENT_REMOTE_HOST_SUPPORTED_FEATURES; /* we do not want this one - it is spammy and useless for now */ |
| evts &=~ HCI_EVENT_MAX_SLOTS_CHANGE; /* same here, really */ |
| evtsLe &=~ HCI_LE_EVENT_SCAN_REQUEST_RECVD; /* same here */ |
| logi("Setting event mask to 0x"FMT64"X\n", CNV64(evts)); |
| ret = hciSetEventMaskSync(evts); |
| if (ret) |
| goto out; |
| |
| logi("Setting event mask page 2 to 0x"FMT64"X\n", CNV64(evts2)); |
| ret = hciSetEventMaskPage2Sync(evts2); |
| if (ret) |
| goto out; |
| |
| logi("Setting LE event mask to 0x"FMT64"X\n", CNV64(evtsLe)); |
| ret = hciLeSetEventMaskSync(evtsLe); |
| if (ret) |
| goto out; |
| } |
| |
| /* set up event handler(s) for reset */ |
| hciSetupResetEventHandler(false); |
| |
| /* set up event handler(s) for discovery results */ |
| { |
| struct hciEvtWaitDescr ewd = { |
| .cbk = hciInquiryEvtCbk, |
| .evtType = HCI_EVT_LE_Meta, |
| .extra = HCI_EVTLE_Advertising_Report, |
| .persistent = true, |
| }; |
| |
| if (!hciEvtWaitEnqueue(&ewd, 0)) { |
| ret = -2; |
| loge("Failed to enqueue scan handler\n"); |
| goto out; |
| } |
| } |
| |
| /* set event handlers for connection states */ |
| { |
| static const uint8_t connEvtTypes[] = {HCI_EVT_Disconnection_Complete, HCI_EVT_Encryption_Change, HCI_EVT_Authentication_Complete, |
| HCI_EVT_Encryption_Key_Refresh_Complete, HCI_EVT_LE_Meta, HCI_EVT_LE_Meta, HCI_EVT_LE_Meta, |
| HCI_EVT_Vendor, HCI_EVT_LE_Meta, 0}; |
| static const uint16_t connEvtExtras[] = {0, 0, 0, 0, HCI_EVTLE_Connection_Complete, HCI_EVTLE_Connection_Update_Complete, |
| HCI_EVTLE_LTK_Request, HCI_EVTVENDOR_Android_Subevent_Advt_State_Change, |
| HCI_EVTLE_Adv_Set_Terminated}; |
| |
| if (!hciSetManyEventPersistentHandlers(connEvtTypes, connEvtExtras, hciConnEvtCbk, NULL)) { |
| ret = -2; |
| loge("Failed to enqueue connection state handlers\n"); |
| goto out; |
| } |
| } |
| |
| /* set handlers for some LE things (like refusing remote connection parameter changes) */ |
| if (mBtVer >= HCI_VERSION_4_1) { |
| struct hciEvtWaitDescr ewd = { |
| .cbk = hciLeParamChangeRequestEventCbk, |
| .evtType = HCI_EVT_LE_Meta, |
| .extra = HCI_EVTLE_Remote_Connection_Parameter_Request, |
| .persistent = true, |
| }; |
| |
| if (!hciEvtWaitEnqueue(&ewd, 0)) { |
| ret = -2; |
| loge("Failed to enqueue LE connection param change request handler\n"); |
| goto out; |
| } |
| } |
| |
| /* figure out the multi-adv situation */ |
| mMaxAdvSets = 1; |
| mCanSetAdvTxPower = false; |
| mHciAdvSetEnable = hciAdvSetEnableDisableMethodBT4; |
| mHciAdvSetHandleAndBlameNewConn = hciAdvSetHandleAndBlameNewConnMethodBT4; |
| if (mBtVer >= HCI_VERSION_5_0) { |
| struct hciCmplLeReadNumSupportedAdvSets evt; |
| |
| logi("Will use BT5 multi-adv support\n"); |
| ret = hciLeReadNumberOfSupportedAdvertisingSetsSync(&evt); |
| if (ret) |
| logw(" Read number supported adv sets command errored out (0x%02x) - will use normal advertising\n", ret); |
| else { |
| mMaxAdvSets = evt.maxAdvsets; |
| mCanSetAdvTxPower = true; |
| logi(" Controller supports up to %u adv sets\n", mMaxAdvSets); |
| } |
| } |
| else if (mBtVer >= HCI_VERSION_4_0) { |
| struct hciCmplVendorLeGetVendorCapabilities evt; |
| |
| logi("Will try BT4 android-vendor multi-adv support\n"); |
| |
| ret = hciVendorLeGetVendorCapabilitiesSync(&evt); |
| if (ret == HCI_ERR_Unknown_HCI_Command) |
| logi(" android-vendor extensions not found - will use normal advertising\n"); |
| else if (ret) |
| logw(" Vendor test command errored out (0x%02x) - will use normal advertising\n", ret); |
| else if (evt.maxAdvtInstances <= 1) |
| logw(" Vendor read number supported adv sets command returned a nonsensical value: %u\n", evt.maxAdvtInstances); |
| else { |
| mMaxAdvSets = evt.maxAdvtInstances - 1; // we cannot count slot 0 |
| mCanSetAdvTxPower = true; |
| mHciAdvSetEnable = hciAdvSetEnableDisableMethodAndroidVendorCmds; |
| mHciAdvSetHandleAndBlameNewConn = hciAdvSetHandleAndBlameNewConnMethodAndroidVendorCmds; |
| logi(" Controller supports up to %u adv sets\n", mMaxAdvSets); |
| } |
| } |
| else { |
| mMaxAdvSets = 0; |
| mCanSetAdvTxPower = false; |
| logi("BT4.0 required for advetising. Advertising will not be supported\n"); |
| } |
| |
| out: |
| if (ret) { |
| loge("Status was 0x%02X\n", ret); |
| mStackState = STACK_STATE_DOWN; |
| return false; |
| } |
| mStackState = STACK_STATE_UP; |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetFindByHandle |
| * USE: Find an adv set by handle |
| * PARAMS: handle - the handle |
| * RETURN: the set structure or NULL |
| * NOTES: call with mAdvSetsLock held |
| */ |
| static struct hciAdvSet* hciAdvSetFindByHandle(hci_adv_set_t handle) |
| { |
| struct hciAdvSet* t; |
| |
| for (t = mAdvSets; t; t = t->next) { |
| if (t->handle == handle) |
| return t; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: hciAdvIsPowerLevelSettingSupported |
| * USE: Can adv tx power be set with our chip? |
| * PARAMS: none |
| * RETURN: the answer |
| * NOTES: |
| */ |
| bool hciAdvIsPowerLevelSettingSupported(void) |
| { |
| return mCanSetAdvTxPower; |
| } |
| |
| /* |
| * FUNCTION: hciAdvGetMaxAdvSetsSupported |
| * USE: How many adv sets can we support (max) on hardware |
| * PARAMS: none |
| * RETURN: the answer |
| * NOTES: |
| */ |
| uint32_t hciAdvGetMaxAdvSetsSupported(void) |
| { |
| return mMaxAdvSets; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetAllocate |
| * USE: Allocate an advertising set structure |
| * PARAMS: none |
| * RETURN: the handle to this set |
| * NOTES: this does not in any way touch hardware |
| */ |
| hci_adv_set_t hciAdvSetAllocate(void) |
| { |
| struct hciAdvSet *set = (struct hciAdvSet*)calloc(1, sizeof(struct hciAdvSet)); |
| hci_adv_set_t handle; |
| |
| if (!set) |
| return 0; |
| |
| set->handle = handle = uniqGetNext(); |
| set->ownRandomAddr.type = BT_ADDR_TYPE_EDR; //definitely not valid for adv |
| set->directAddr.type = BT_ADDR_TYPE_EDR; //definitely not valid for adv |
| pthread_mutex_lock(&mAdvSetsLock); |
| set->next = mAdvSets; |
| if (mAdvSets) |
| mAdvSets->prev = set; |
| mAdvSets = set; |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return handle; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetFree |
| * USE: Free an advertising set structure. Set must not be enabled! |
| * PARAMS: handle - the handle to this set |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetFree(hci_adv_set_t handle) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is enabled - cannot delete it\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| if (set->next) |
| set->next->prev = set->prev; |
| if (set->prev) |
| set->prev->next = set->next; |
| else |
| mAdvSets = set->next; |
| free(set); |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetConfigureData |
| * USE: Configure an advertising set's data. Set must not be enabled! |
| * PARAMS: handle - the handle to this set |
| * scanRsp - which data to config? SCAN_RSP or ADV |
| * data - the data to use |
| * len - length of said data |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetConfigureData(hci_adv_set_t handle, bool scanRsp, const uint8_t *data, uint32_t len) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is enabled - cannot edit it\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| |
| uint32_t *lenP; |
| uint8_t *dataP; |
| uint32_t lenLimit; |
| |
| if (scanRsp) { |
| lenP = &set->scanRspDataLen; |
| dataP = set->scanRspData; |
| lenLimit = HCI_SCAN_RSP_DATA_MAX_LEN; |
| } |
| else { |
| lenP = &set->advDataLen; |
| dataP = set->advData; |
| lenLimit = HCI_ADV_DATA_MAX_LEN; |
| } |
| |
| if (len > lenLimit) |
| logw("%s data is too long (%u > %u)\n", scanRsp ? "SCAN_RSP" : "ADV", len, lenLimit); |
| else { |
| |
| memcpy(dataP, data, len); |
| *lenP = len; |
| ret = true; |
| } |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetOwnRandomAddr |
| * USE: Configure an advertising set's random adress. Set must not be enabled! |
| * PARAMS: handle - the handle to this set |
| * ownRandomAddr - the random address to use when random address requested for advertising |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetOwnRandomAddr(hci_adv_set_t handle, const struct bt_addr *ownRandomAddr) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is enabled - cannot edit it\n", HCI_ADV_SET_CONV(handle)); |
| else if (ownRandomAddr->type != BT_ADDR_TYPE_LE_RANDOM) |
| logw("Set 0x"HCI_ADV_SET_FMT"x random address cannot be set - provided addr is not random\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| |
| set->ownRandomAddr = *ownRandomAddr; |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetSetAdvParams |
| * USE: Configure an advertising set's parameters. Set must not be enabled! |
| * PARAMS: handle - the handle to this set |
| * advIntervalMin - minimum adv interval (in units of 0.625ms) |
| * advIntervalMax - maximum adv interval (in units of 0.625ms) |
| * advType - adv type: HCI_ADV_TYPE_ADV_* |
| * ownAddressType - own addr type: HCI_ADV_OWN_ADDR_TYPE_* |
| * directAddr - if direct adv, the address of target, else ignored |
| * advChannelMap - channels to use: bitmask of HCI_ADV_CHAN_MAP_USE_* |
| * advFilterPolicy - filter policy: HCI_ADV_FILTER_POL_* |
| * advPower - TX power in dBm, ignored if not suported, see hciAdvIsPowerLevelSettingSupported() |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetSetAdvParams(hci_adv_set_t handle, uint16_t advIntervalMin, uint16_t advIntervalMax, uint8_t advType, uint8_t ownAddressType, struct bt_addr *directAddr, uint8_t advChannelMap, uint8_t advFilterPolicy, int8_t advPower) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is enabled - cannot edit it\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| |
| set->intervalMin = advIntervalMin; |
| set->intervalMax = advIntervalMax; |
| set->advType = advType; |
| set->ownAddressType = ownAddressType; |
| if (directAddr) |
| set->directAddr = *directAddr; |
| else |
| set->directAddr.type = BT_ADDR_TYPE_EDR; //definitely not valid for adv |
| set->channelMap = advChannelMap; |
| set->filterPolicy = advFilterPolicy; |
| set->powerLevel = advPower; |
| |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetGetCurTxPowerLevel |
| * USE: Get the current tx power level of an adv set. Set must be enabled! |
| * PARAMS: handle - the handle to this set |
| * advTxPowerLevelP - where to store the result |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetGetCurTxPowerLevel(hci_adv_set_t handle, int8_t *advTxPowerLevelP) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (!set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is not enabled - cannot get its power\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| |
| if (advTxPowerLevelP) |
| *advTxPowerLevelP = set->hwPowerLevel; |
| |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetGetCurAdvAddrLocked |
| * USE: Get the current adv addr of an adv set. |
| * PARAMS: set - the adv set structure |
| * ownAddr - where to store the result |
| * RETURN: success |
| * NOTES: must be called with mAdvSetsLock held, assumes set is currently active |
| */ |
| static void hciAdvSetGetCurAdvAddrLocked(struct hciAdvSet *set, struct bt_addr *ownAddr) |
| { |
| if (set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_RANDOM) |
| *ownAddr = set->ownRandomAddr; |
| else { |
| memcpy(ownAddr->addr, mLocalAddr, sizeof(ownAddr->addr)); |
| ownAddr->type = BT_ADDR_TYPE_LE_PUBLIC; |
| } |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetGetCurAdvAddr |
| * USE: Get the current adv addr of an adv set. Set must be enabled! |
| * PARAMS: handle - the handle to this set |
| * ownAddr - where to store the result |
| * RETURN: success |
| * NOTES: this does not in any way touch hardware |
| */ |
| bool hciAdvSetGetCurAdvAddr(hci_adv_set_t handle, struct bt_addr *ownAddr) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (!set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is not enabled - cannot get its address\n", HCI_ADV_SET_CONV(handle)); |
| else { |
| hciAdvSetGetCurAdvAddrLocked(set, ownAddr); |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| |
| /* |
| * FUNCTION: hciAdvSetEnable |
| * USE: Try to enable a given adv set |
| * PARAMS: handle - the handle to this set |
| * RETURN: success |
| * NOTES: this may fail sometimes and succeed other times based on hw state |
| */ |
| bool hciAdvSetEnable(hci_adv_set_t handle) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is enabled - cannot enable it again\n", HCI_ADV_SET_CONV(handle)); |
| else if (!mHciAdvSetEnable) |
| logw("No method for enable/disable defined for this chip - cannot enable adv set\n"); |
| else { |
| |
| ret = true; |
| |
| /* Sanity checks. All constantrs are from spec */ |
| if (!set->advDataLen) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has no adv data.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if (set->advType != HCI_ADV_TYPE_ADV_DIRECT_IND && (set->intervalMin > set->intervalMax || set->intervalMin < 0x20 || set->intervalMax > 0x4000)) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid interval params.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if (set->advType != HCI_ADV_TYPE_ADV_IND && set->advType != HCI_ADV_TYPE_ADV_DIRECT_IND && set->advType != HCI_ADV_TYPE_ADV_SCAN_IND && set->advType != HCI_ADV_TYPE_ADV_NONCONN_IND && set->advType != HCI_ADV_TYPE_ADV_DIRECT_IND_LOW_DUTY) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid adv type.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if (set->ownAddressType != HCI_ADV_OWN_ADDR_TYPE_PUBLIC && set->ownAddressType != HCI_ADV_OWN_ADDR_TYPE_RANDOM && set->ownAddressType != HCI_ADV_OWN_ADDR_TYPE_AUTO_RESOLV_ELSE_PUBLIC && set->ownAddressType != HCI_ADV_OWN_ADDR_TYPE_AUTO_RESOLV_ELSE_RANDOM) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid own addr type.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if ((set->advType == HCI_ADV_TYPE_ADV_DIRECT_IND || set->advType == HCI_ADV_TYPE_ADV_DIRECT_IND_LOW_DUTY) && set->directAddr.type != BT_ADDR_TYPE_LE_PUBLIC && set->directAddr.type != BT_ADDR_TYPE_LE_RANDOM) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid peer addr type.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if (!(set->channelMap & 7)) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid channel map.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if (set->filterPolicy != HCI_ADV_FILTER_POL_SCAN_ALL_CONNECT_ALL && set->filterPolicy != HCI_ADV_FILTER_POL_SCAN_LIST_CONNECT_ALL && set->filterPolicy != HCI_ADV_FILTER_POL_SCAN_ALL_CONNECT_LIST && set->filterPolicy != HCI_ADV_FILTER_POL_SCAN_LIST_CONNECT_LIST) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has invalid filter policy.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| if ((set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_RANDOM || set->ownAddressType == HCI_ADV_OWN_ADDR_TYPE_AUTO_RESOLV_ELSE_RANDOM) && set->ownRandomAddr.type != BT_ADDR_TYPE_LE_RANDOM) { |
| logw("Cannot enable adv set 0x"HCI_ADV_SET_FMT"x since it has no valid random address and might need it.\n", HCI_ADV_SET_CONV(handle)); |
| ret = false; |
| } |
| |
| //if all checks pass - do the actual ON procedure |
| ret = ret && mHciAdvSetEnable(set, true); |
| } |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciAdvSetDisable |
| * USE: Try to disable a given adv set |
| * PARAMS: handle - the handle to this set |
| * RETURN: success |
| * NOTES: |
| */ |
| bool hciAdvSetDisable(hci_adv_set_t handle) |
| { |
| struct hciAdvSet *set; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAdvSetsLock); |
| set = hciAdvSetFindByHandle(handle); |
| if (!set) |
| logw("Set handle 0x"HCI_ADV_SET_FMT"x not found\n", HCI_ADV_SET_CONV(handle)); |
| else if (!set->hwEnabled) |
| logw("Set 0x"HCI_ADV_SET_FMT"x is disabled - cannot disable it again\n", HCI_ADV_SET_CONV(handle)); |
| else if (!mHciAdvSetEnable) |
| logw("No method for enable/disable defined for this chip - cannot enable adv set\n"); |
| else |
| ret = mHciAdvSetEnable(set, false); |
| |
| pthread_mutex_unlock(&mAdvSetsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciDiscoverLeStart |
| * USE: Starts LE discovery |
| * PARAMS: cbk - device discovered callback |
| * cbkData - data for said callback |
| * active - do active scan? |
| * RETURN: discovery handle or 0 for error |
| * NOTES: Callbacks might be called even if func returns false, but |
| * will stop once said return value is actually returned. |
| * Discovery goes on until stopped. |
| */ |
| uniq_t hciDiscoverLeStart(hciDeviceDiscoveredLeCbk cbk, void *cbkData, bool active, bool useRandomAddr) |
| { |
| uniq_t ret = 0; |
| int sta; |
| |
| if (!cbk) { |
| loge("Refusing discovery with no callback\n"); |
| return 0; |
| } |
| |
| pthread_mutex_lock(&mDiscoveryStateLock); |
| if (mDiscoverLeHandle) |
| logw("Refusing to do LE discovery while another in progress\n"); |
| else { |
| ret = mDiscoverLeHandle = uniqGetNext(); |
| |
| mDiscoverLeCbk = cbk; |
| mDiscoverLeData = cbkData; |
| } |
| pthread_mutex_unlock(&mDiscoveryStateLock); |
| |
| if (!ret) |
| return 0; |
| |
| sta = hciLeSetScanParamsSync(active, HCI_LE_SCAN_INTERVAL, HCI_LE_SCAN_WINDOW, useRandomAddr, false); |
| if (sta) { |
| loge("Failed to set LE scan params: %d\n", sta); |
| goto err; |
| } |
| |
| sta = hciLeSetScanEnableSync(true, false); |
| if (sta) { |
| loge("Failed to set LE scan on: %d\n", sta); |
| goto err; |
| } |
| |
| return ret; |
| |
| err: |
| pthread_mutex_lock(&mDiscoveryStateLock); |
| mDiscoverLeHandle = 0; |
| pthread_mutex_unlock(&mDiscoveryStateLock); |
| |
| return 0; |
| } |
| |
| /* |
| * FUNCTION: hciDiscoverLeStop |
| * USE: Stops LE discovery |
| * PARAMS: handle - the handle from hciDiscoverLeStart() |
| * RETURN: false on error |
| * NOTES: Callbacks will stop once this returns |
| */ |
| bool hciDiscoverLeStop(uniq_t handle) |
| { |
| int sta; |
| |
| if (!handle) { |
| loge("Cannot pass NULL handle here\n"); |
| return false; |
| } |
| |
| pthread_mutex_lock(&mDiscoveryStateLock); |
| if (mDiscoverLeHandle != handle) |
| handle = 0; |
| else |
| mDiscoverLeHandle = 0; |
| pthread_mutex_unlock(&mDiscoveryStateLock); |
| |
| if (handle){ |
| sta = hciLeSetScanEnableSync(false, false); |
| if (sta) |
| loge("Failed to set LE scan off: %d\n", sta); |
| } |
| |
| hciWorkFlush(); |
| |
| return !!handle; |
| } |
| |
| |
| /* |
| * FUNCTION: hciInfoSharedBuffers |
| * USE: Provide info on whether EDR & LE ACL buffers are shared in our controller |
| * PARAMS: NONE |
| * RETURN: the answer |
| * NOTES: |
| */ |
| bool hciInfoSharedBuffers(void) |
| { |
| return mBtJointBuffers; |
| } |
| |
| /* |
| * FUNCTION: hciInfoScoBufSize |
| * USE: Provide info on SCO buffer size in our controller and SCO support |
| * PARAMS: bufSz - where to store buffer sizes (or NULL if uninterested) |
| * bufNum - where to store number of buffers (or NULL if uninterested) |
| * RETURN: true if SCO supported |
| * NOTES: |
| */ |
| bool hciInfoScoBufSize(uint16_t *bufSz, uint16_t *bufNum) |
| { |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciInfoSharedBuffers |
| * USE: Provide info on LE ACL buffer size in our controller and LE support |
| * PARAMS: bufSz - where to store buffer sizes (or NULL if uninterested) |
| * bufNum - where to store number of buffers (or NULL if uninterested) |
| * RETURN: true if LE supported |
| * NOTES: |
| */ |
| bool hciInfoAclBufSizeLe(uint16_t *bufSz, uint16_t *bufNum) |
| { |
| if (bufSz) |
| *bufSz = mAclBufSzLe; |
| if (bufNum) |
| *bufNum = mAclBufNumLe; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciCmdStatusCbk |
| * USE: Gives us the status of a command we started |
| * PARAMS: cbkData - non-null for LE |
| * status - status from the chip |
| * RETURN: NONE |
| * NOTES: This is the universal sink for status events we do not care much about |
| */ |
| static void hciCmdStatusCbk(void *cbkData, uint8_t status) |
| { |
| logd("status: %d\n", status); |
| } |
| |
| /* |
| * FUNCTION: hciConnEncryptLe |
| * USE: Start an encryption attempt for an LE connection |
| * PARAMS: conn - the connection |
| * demandMitmSafe - do we want mitm-safety? |
| * RETURN: true if attempt will be made |
| * NOTES: call with mConnsLock held |
| */ |
| static bool hciConnEncryptLe(struct hciAclConn *conn, bool demandMitmSafe) |
| { |
| if (!(mLocalLeFtrs & HCI_LE_FTR_ENCRYPTION)) { |
| loge("Encryption not supported\n"); |
| return false; |
| } |
| |
| if (!(conn->le.ftrs & HCI_LE_FTR_ENCRYPTION)) { |
| loge("Encryption not supported remotely\n"); |
| return false; |
| } |
| |
| //TODO: LE Security Manager integration |
| |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciConnectLe |
| * USE: Start an LE connection to a given address |
| * PARAMS: mac - the mac |
| * randomAddr - peer address is random? |
| * scanInt - connect scan interval |
| * scanWindow - connect scan window |
| * intMin - minimum wanted interval |
| * intMax - maximum wanted interval |
| * latency - wanted latency |
| * timeout - wanted timeout |
| * useOwnRandomAddr - send peer our random addr? |
| * RETURN: true if attempt will be made |
| * NOTES: call with mConnsLock held. Caller assumed to know limitations (one at a time, radom addr shared with adv, etc) |
| */ |
| static bool hciConnectLe(const uint8_t *mac, bool randomAddr, uint16_t scanInt, uint16_t scanWindow, uint16_t intMin, uint16_t intMax, uint16_t latency, uint16_t timeout, bool useOwnRandomAddr) |
| { |
| struct hciLeCreateConnection cmd; |
| |
| utilSetLE16(&cmd.scanInterval, scanInt); |
| utilSetLE16(&cmd.scanWindow, scanWindow); |
| utilSetLE8(&cmd.connectToAnyWhitelistedDevice, 0); |
| utilSetLE8(&cmd.peerRandomAddr, randomAddr ? 1 : 0); |
| memcpy(cmd.peerMac, mac, sizeof(cmd.peerMac)); |
| utilSetLE8(&cmd.useOwnRandomAddr, useOwnRandomAddr ? 1 : 0); |
| utilSetLE16(&cmd.connIntervalMin, intMin); |
| utilSetLE16(&cmd.connIntervalMax, intMax); |
| utilSetLE16(&cmd.connLatency, latency); |
| utilSetLE16(&cmd.supervisionTimeout, timeout); |
| utilSetLE16(&cmd.minConnLen, 0); /* not a useful hint, i know */ |
| utilSetLE16(&cmd.maxConnLen, intMin * 2); |
| |
| if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Create_Connection, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL)) { |
| logi("Failed to request connect to "MACFMT"\n", MACCONV(mac)); |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciMakeProgressOnLeConnectionReqs |
| * USE: Try to make progress on our queue of LE connection requests |
| * PARAMS: NONE |
| * RETURN: NONE |
| * NOTES: call with mConnsLock held, do not do it on the event handler thread (will deadlock in case of random addr use) |
| */ |
| static void hciMakeProgressOnLeConnectionReqs(void) |
| { |
| struct hciLeConnReq *req; |
| struct hciAclConn *conn; |
| bool done = false; |
| |
| while (!done) { |
| //can we do anything? |
| if (!mLeConnReqsHead) |
| return; |
| if (mLeConnInProgress) |
| return; |
| |
| //grab a request |
| req = mLeConnReqsHead; |
| mLeConnReqsHead = mLeConnReqsHead->next; |
| if (!mLeConnReqsHead) |
| mLeConnReqsTail = NULL; |
| |
| //verify the connection exists still and is in valid state |
| conn = hciConnFindByHandle(req->aclConn); |
| if (!conn) |
| logi("Connection request found for a nonexistent connection. Ignoring\n"); |
| else if (conn->state != CONN_STATE_WAIT) |
| logi("Connection request found for a connection not in wait state. Ignoring\n"); |
| else { |
| conn->state = CONN_STATE_TRYING; |
| if (req->useRandomAddr && hciLeSetRandomAddressSync(req->selfRandomAddr)) |
| logi("Failed to set random address for connection. Dropping request and connection\n"); |
| else if (!hciConnectLe(conn->peerAddr.addr, conn->peerAddr.type == BT_ADDR_TYPE_LE_RANDOM, req->scanInt, req->scanWindow, req->intMin, req->intMax, req->latency, req->timeout, req->useRandomAddr)) |
| logi("Failed to start connection request. Dropping request and connection\n"); |
| else |
| done = true; |
| |
| if (!done) |
| hciConnAclStructDel(conn); |
| } |
| free(req); |
| } |
| } |
| |
| /* |
| * FUNCTION: hciEnqueueConnectLe |
| * USE: Enqueue a request to make an LE connection |
| * PARAMS: aclConn - the acl conn struct allocated for this connection |
| * randomAddr - peer address is random? |
| * scanInt - connect scan interval |
| * scanWindow - connect scan window |
| * intMin - minimum wanted interval |
| * intMax - maximum wanted interval |
| * latency - wanted latency |
| * timeout - wanted timeout |
| * useOwnRandomAddr - send peer our random addr? |
| * RETURN: true if attempt will be made |
| * NOTES: call with mConnsLock held |
| */ |
| static bool hciEnqueueConnectLe(hci_conn_t aclConn, uint16_t scanInt, uint16_t scanWindow, uint16_t intMin, uint16_t intMax, uint16_t latency, uint16_t timeout, const uint8_t *sendRandomAddr) |
| { |
| struct hciLeConnReq *req = calloc(1, sizeof(struct hciLeConnReq)); |
| |
| if (!req) |
| return false; |
| |
| req->aclConn = aclConn; |
| req->scanInt = scanInt; |
| req->scanWindow = scanWindow; |
| req->intMin = intMin; |
| req->intMax = intMax; |
| req->latency = latency; |
| req->timeout = timeout; |
| req->useRandomAddr = !!sendRandomAddr; |
| if (sendRandomAddr) |
| memcpy(req->selfRandomAddr, sendRandomAddr, sizeof(req->selfRandomAddr)); |
| |
| if (mLeConnReqsTail) |
| mLeConnReqsTail->next = req; |
| else |
| mLeConnReqsHead = req; |
| mLeConnReqsTail = req; |
| |
| hciMakeProgressOnLeConnectionReqs(); |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciConnAclStructDel |
| * USE: Delete a connection structure |
| * PARAMS: conn - the structure |
| * RETURN: NONE |
| * NOTES: call with mConnsLock held |
| */ |
| static void hciConnAclStructDel(struct hciAclConn* conn) |
| { |
| if (conn->outstandingPackets) { |
| if (BT_ADDR_IS_EDR(conn->peerAddr)) { |
| while (conn->outstandingPackets--) |
| sem_post(&mAclPacketsEdr); |
| l2cAclCreditAvail(false); |
| } else { |
| while (conn->outstandingPackets--) |
| sem_post(&mAclPacketsLe); |
| l2cAclCreditAvail(true); |
| } |
| } |
| |
| if (conn->next) |
| conn->next->prev = conn->prev; |
| if (conn->prev) |
| conn->prev->next = conn->next; |
| else |
| mConns = conn->next; |
| |
| if (conn->rxBacklog) |
| sgFree(conn->rxBacklog); |
| |
| logd("deleting conn struct for "ADDRFMT" h="HANDLEFMT"\n", ADDRCONV(conn->peerAddr), HANDLECNV(conn->handle)); |
| free(conn); |
| } |
| |
| /* |
| * FUNCTION: hciConnAclStructNew |
| * USE: Create a connection structure |
| * PARAMS: id - the ACL connection ID |
| * addr - the peer address |
| * state - the state to create the connection in |
| * isMaster - set us as master? |
| * RETURN: the new conn struct or NULL |
| * NOTES: call with mConnsLock held |
| */ |
| static struct hciAclConn* hciConnAclStructNew(uint16_t id, const struct bt_addr *addr, uint8_t state, bool isMaster) |
| { |
| struct hciAclConn *conn = (struct hciAclConn*)calloc(1, sizeof(struct hciAclConn)); |
| if (!conn) |
| return NULL; |
| |
| memcpy(&conn->peerAddr, addr, sizeof(conn->peerAddr)); |
| conn->state = state; |
| conn->isMaster = isMaster; |
| conn->id = id; |
| conn->handle = uniqGetNext(); |
| |
| conn->next = mConns; |
| mConns = conn; |
| |
| return conn; |
| } |
| |
| /* |
| * FUNCTION: hciConnect |
| * USE: Start a connection to a given address |
| * PARAMS: addr - the address to connect to |
| * selfRandomAddr - if null will use public addr, else this is the random address to use as own for connection |
| * scanIntervalP - if not null, scan interval to use. if null - stack picks |
| * scanWindowP - if not null, scan window to use. if null - stack picks |
| * connIntervalMinP - if not null, min conn interval to use. if null - stack picks |
| * connIntervalMaxP - if not null, max conn interval to use. if null - stack picks |
| * latencyP - if not null, latency to use. if null - stack picks |
| * timeoutP - if not null, timeout to use. if null - stack picks |
| * RETURN: connectin handle if an attempt will be made to connect. 0 else |
| */ |
| hci_conn_t hciConnect(const struct bt_addr* addr, const uint8_t *selfRandomAddr, uint16_t *scanIntervalP, uint16_t *scanWindowP, uint16_t *connIntervalMinP, uint16_t *connIntervalMaxP, uint16_t *latencyP, uint16_t *timeoutP) |
| { |
| uint32_t scanInterval, scanWindow, connIntervalMin, connIntervalMax, latency, timeout; |
| hci_conn_t ret = 0; |
| |
| logd("Requesting a connection to "ADDRFMT"\n", ADDRCONV(*addr)); |
| |
| //EDR is not supported |
| if (BT_ADDR_IS_EDR(*addr)) { |
| loge("EDR unsupported\n"); |
| return false; |
| } |
| |
| //read params |
| if (scanIntervalP && scanWindowP) { |
| scanInterval = *scanIntervalP; |
| scanWindow = *scanWindowP; |
| } |
| else if (scanIntervalP) { |
| scanInterval = *scanIntervalP; |
| scanWindow = (scanInterval + 1) / 2; |
| if (scanWindow < 0x0004) //min as per spec |
| scanWindow = 0x0004; |
| } |
| else if (scanWindowP) { |
| scanWindow = *scanWindowP; |
| scanInterval = scanWindow * 2; |
| if (scanInterval > 0x4000) //max as per spec |
| scanInterval = 0x4000; |
| } |
| else { |
| scanWindow = HCI_LE_CONN_SCAN_WINDOW; |
| scanInterval = HCI_LE_CONN_SCAN_INTERVAL; |
| } |
| |
| if (connIntervalMinP && connIntervalMaxP) { |
| connIntervalMin = *connIntervalMinP; |
| connIntervalMax = *connIntervalMaxP; |
| } |
| else if (connIntervalMinP) { |
| connIntervalMin = *connIntervalMinP; |
| connIntervalMax = (connIntervalMin + 3) / 2; |
| if (connIntervalMax > 0x0c80) //max as per spec |
| connIntervalMax = 0x0c80; |
| } |
| else if (connIntervalMaxP) { |
| connIntervalMax = *connIntervalMaxP; |
| connIntervalMin = (connIntervalMax * 2) / 3; |
| if (connIntervalMin < 0x0006) //min as per spec |
| connIntervalMax = 0x0006; |
| } |
| else { |
| connIntervalMin = HCI_LE_CONN_INT_MIN; |
| connIntervalMax = HCI_LE_CONN_INT_MAX; |
| } |
| |
| latency = latencyP ? *latencyP : HCI_LE_CONN_LATENCY; |
| timeout = timeoutP ? *timeoutP : HCI_LE_CONN_TIMEOUT; |
| |
| //sanity check params before we get too far |
| if (scanInterval < scanWindow || scanWindow < 0x0004 || scanInterval > 0x4000) { //as per spec |
| logw("Scan settings for connection creation are invalid\n"); |
| return 0; |
| } |
| if (connIntervalMin > connIntervalMax || connIntervalMin < 0x0006 || connIntervalMax > 0x0c80) { //as per spec |
| logw("Connection interval settings for connection creation are invalid\n"); |
| return 0; |
| } |
| if (latency > 0x01f3) { //as per spec |
| logw("Latency setting for connection creation are invalid\n"); |
| return 0; |
| } |
| if (timeout < 0x000a || timeout > 0x0c80 || timeout * 8 <= (1 + latency) * connIntervalMax * 2) { //as per spec |
| logw("Timeout setting for connection creation are invalid\n"); |
| return 0; |
| } |
| |
| pthread_mutex_lock(&mConnsLock); |
| if (hciConnFindByAddr(addr)) |
| logw("refusing to connect to address I already have a connection to\n"); |
| else { |
| struct hciAclConn* conn = hciConnAclStructNew(ACL_CONN_ID_INVALID, addr, CONN_STATE_WAIT, true); |
| if (!conn) |
| loge("Failed to allocate conn struct\n"); |
| else if (hciEnqueueConnectLe(conn->handle, scanInterval, scanWindow, connIntervalMin, connIntervalMax, latency, timeout, selfRandomAddr)) |
| ret = conn->handle; |
| else |
| hciConnAclStructDel(conn); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciDisconnect |
| * USE: Kill a connection [attempt] |
| * PARAMS: aclConn - the connection handle |
| * RETURN: true if attempt will be made |
| * NOTES: |
| */ |
| bool hciDisconnect(hci_conn_t aclConn) |
| { |
| struct hciAclConn* conn; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logw("Failed to find connection to disconnect\n"); |
| else { |
| bool isLE = BT_ADDR_IS_LE(conn->peerAddr); |
| |
| switch (conn->state) { |
| case CONN_STATE_WAIT: |
| hciConnAclStructDel(conn); //conn req will go away when we get to it |
| break; |
| case CONN_STATE_TRYING: |
| if (isLE) { |
| if (!hciConnLeCancel()) |
| loge("Failed to request LE conection cancellation for "ADDRFMT"\n", ADDRCONV(conn->peerAddr)); |
| } else |
| logd("cannot cancel in-progress EDR connection\n"); |
| conn->state = CONN_STATE_DELETING; |
| break; |
| case CONN_STATE_ADV_SET_BLAMING: |
| case CONN_STATE_CFG: |
| case CONN_STATE_RUNNING: |
| hciConnDisconnect(conn->id); |
| hciConnAclStructDel(conn); |
| break; |
| case CONN_STATE_DELETING: |
| logd("cannot cancel already-in-cancellation connection\n"); |
| break; |
| default: |
| loge("Invalid connection state %d\n", conn->state); |
| break; |
| } |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return !!conn; |
| } |
| |
| /* |
| * FUNCTION: hciUpdateLeParams |
| * USE: Try to update LE params |
| * PARAMS: aclConn - the connection |
| * minInt - the minimum wanted interval |
| * maxInt - the maximum wanted interval |
| * lat - wanted latency |
| * to - wanted timeout |
| * RETURN: true if attempt will be made |
| * NOTES: |
| */ |
| bool hciUpdateLeParams(hci_conn_t aclConn, uint16_t minInt, uint16_t maxInt, uint16_t lat, uint16_t to) |
| { |
| struct hciAclConn* conn; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logw("Conection for update not found\n"); |
| else if (conn->state != CONN_STATE_RUNNING) |
| logw("Cannot update params for not-yet-established connection\n"); |
| else |
| ret = hciConnUpdate(conn->id, minInt, maxInt, lat, to); |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciDemandEncr |
| * USE: Try to encrypt a connection |
| * PARAMS: aclConn - the connection |
| * RETURN: true if attempt will be made |
| * NOTES: |
| */ |
| bool hciDemandEncr(hci_conn_t aclConn, bool demandMitmSafe) |
| { |
| struct hciAclConn* conn; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logw("Conection for encrypt not found\n"); |
| else if (conn->state != CONN_STATE_RUNNING) |
| logw("Cannot update encryption for not-yet-established connection\n"); |
| else if (conn->encrypted && (conn->mitmSafe || !demandMitmSafe)) |
| logw("Connection already encrypted up to requested level\n"); |
| else if (BT_ADDR_IS_LE(conn->peerAddr)) |
| ret = hciConnEncryptLe(conn, demandMitmSafe); |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return conn && ret; |
| } |
| |
| /* |
| * FUNCTION: hciLeConnGetInfo |
| * USE: Query info for an LE connection |
| * PARAMS: aclConn - the connection |
| * masterP - where to stor if we're master (or NULL) |
| * peerFtrsP - where to store peer features (or NULL) |
| * intP - where to store connection interval (or NULL) |
| * latP - where to sotre connectio nlatency (or NULL) |
| * toP - where to store connection timeout (or NULL) |
| * mcaP - where to store MCA (meaningless if we're master) (or NULL) |
| * RETURN: true if connection was found and results have been returned |
| * NOTES: |
| */ |
| bool hciLeConnGetInfo(hci_conn_t aclConn, bool *masterP, uint64_t *peerFtrsP, uint16_t *intP, uint16_t *latP, uint16_t *toP, uint8_t *mcaP) |
| { |
| struct hciAclConn* conn; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logw("Conection for encrypt not found\n"); |
| else if (!BT_ADDR_IS_LE(conn->peerAddr)) |
| logw("Not an LE connection\n"); |
| else { |
| if (masterP) |
| *masterP = conn->isMaster; |
| if (peerFtrsP) |
| *peerFtrsP = conn->le.ftrs; |
| if (intP) |
| *intP = conn->le.interval; |
| if (latP) |
| *latP = conn->le.latency; |
| if (toP) |
| *toP = conn->le.timeout; |
| if (mcaP) |
| *mcaP = conn->le.mca; |
| ret = true; |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciLeEncryptConn |
| * USE: Encrypt a connection using the given key |
| * PARAMS: aclConn - the connection |
| * rand - random value |
| * ediv - encrypted diversifier value |
| * key - the key used for encryption |
| * RETURN: true if attempt will be made |
| * NOTES: |
| */ |
| bool hciLeEncryptConn(hci_conn_t aclConn, uint64_t rand, uint16_t ediv, const uint8_t *key) |
| { |
| struct hciAclConn* conn; |
| uint16_t cid; |
| |
| pthread_mutex_lock(&mConnsLock); |
| |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) |
| logw("Cannot find Connection to encrypt\n"); |
| else if (conn->state != CONN_STATE_RUNNING) |
| logw("Cannot encrypt a not-yet-established connection\n"); |
| else if (conn->encrypted) |
| logw("Connection already encrypted up to requested level\n"); |
| else if (!BT_ADDR_IS_LE(conn->peerAddr)) |
| logw("Not an LE connection\n"); |
| else if (key) { |
| cid = conn->id; |
| pthread_mutex_unlock(&mConnsLock); |
| return !hciLeStartEncryptionSync(cid, rand, ediv, key); |
| } |
| |
| pthread_mutex_unlock(&mConnsLock); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: hciTryToTx |
| * USE: Try to send some data |
| * PARAMS: aclConn - the connection |
| * data - the data |
| * first - mark as first fragment? |
| * RETURN: true if attempt will be made |
| * NOTES: all data larger then controller's buffer WILL be dropped. WILL NOT BLOCK! |
| */ |
| uint8_t hciTryToTx(hci_conn_t aclConn, sg data, uint8_t boundary) |
| { |
| struct hciAclConn* conn; |
| uint8_t ret = HCI_TX_SEND_ERROR; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindByHandle(aclConn); |
| if (!conn) { |
| ret = HCI_TX_SEND_NO_CONN; |
| logw("Cannot send on a nonexistent connection\n"); |
| } else if (conn->state != CONN_STATE_RUNNING) { |
| ret = HCI_TX_SEND_NO_CONN; |
| logw("Cannot send on a not-yet-established connection\n"); |
| } else { |
| bool isLE = BT_ADDR_IS_LE(conn->peerAddr); |
| bool useLeBuffers = isLE && !mBtJointBuffers; |
| sem_t *relevantSem = useLeBuffers ? &mAclPacketsLe : &mAclPacketsEdr; |
| int rv; |
| |
| rv = r_sem_trywait(relevantSem); |
| if (rv && errno == EAGAIN) |
| ret = HCI_TX_SEND_NO_CREDITS; |
| else if (!rv) { |
| struct hciAclHdr hdr; |
| uint16_t pb = 0xFFFF; /* this will catch all other cases */ |
| uint16_t start_pb = mBtVer >= HCI_VERSION_2_1 ? ACL_HDR_PB_FIRST_NONAUTO : ACL_HDR_PB_FIRST_AUTO; |
| uint16_t complete_pb = /*mBtVer >= HCI_VERSION_3_0 ? ACL_HDR_PB_COMPLETE : */start_pb; /* XXX: this causes problems with TI BT chips - they send "complete" as a "continue" confusing the other side */ |
| |
| switch(boundary) { |
| case HCI_BOUNDARY_COMPLETE: |
| pb = isLE ? start_pb : complete_pb; |
| break; |
| case HCI_BOUNDARY_START: |
| pb = start_pb; |
| break; |
| case HCI_BOUNDARY_CONT: |
| pb = ACL_HDR_PB_CONINUED; |
| break; |
| } |
| |
| utilSetLE16(&hdr.len, sgLength(data)); |
| utilSetLE16(&hdr.hdr, conn->id | pb); |
| |
| if (sgConcatFrontCopy(data, &hdr, sizeof(hdr))) { |
| if (vendorTx(HCI_PKT_TYP_ACL, data, false)) { |
| conn->outstandingPackets++; |
| ret = HCI_TX_SEND_OK; |
| } else { |
| /* undo our concat of header in case caller wants to use this sg again */ |
| sgTruncFront(data, sizeof(hdr)); |
| } |
| } |
| } |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnAclDown |
| * USE: Called when a connection has gone down |
| * PARAMS: cid - the connection ID |
| * reason - the reason |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciHandleConnAclDown(uint16_t cid, uint8_t reason) |
| { |
| struct hciAclConn* conn; |
| |
| pthread_mutex_lock(&mConnsLock); |
| conn = hciConnFindById(cid); |
| if (!conn) |
| logd("Conn down not found %d\n", cid); |
| else { |
| switch(conn->state) { |
| case CONN_STATE_WAIT: |
| case CONN_STATE_TRYING: |
| logw("Unexpected connection down in wait or trying state!\n"); |
| /* fallthrough */ |
| case CONN_STATE_ADV_SET_BLAMING: |
| case CONN_STATE_CFG: |
| case CONN_STATE_RUNNING: |
| if (!hciScheduleConnDownNotif(conn->handle, reason)) |
| loge("Failed to schedule conn down notif\n"); |
| break; |
| case CONN_STATE_DELETING: |
| /* nothing here really */ |
| break; |
| } |
| hciConnAclStructDel(conn); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| |
| /* |
| * FUNCTION: hciConnCfgCbk |
| * USE: event handler for hciConnCfg() |
| * PARAMS: evt - the resuting event |
| * evtSz - event size |
| * cbkData - the data from hciConnCfg (step and aux encoded into a pointer) |
| * evtWaitStateID - unused |
| * forCmdID - unused |
| * RETURN: true if this is our event (always yes) |
| * NOTES: call with mConnsLock held |
| */ |
| static bool hciConnCfgCbk(const struct hciEvtHdr *evt, uint32_t evtSz, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID) |
| { |
| struct hciEvtLeMeta *leMetaEvt = (struct hciEvtLeMeta*)(evt + 1); |
| struct hciEvtLeReadRemoteFeaturesComplete *rrf = (struct hciEvtLeReadRemoteFeaturesComplete*)(leMetaEvt + 1); |
| uint16_t step = ((uint32_t)(uintptr_t)cbkData) >> 16; |
| uint16_t aux = (uint16_t)(uintptr_t)cbkData; |
| struct hciAclConn *c = NULL; |
| uint8_t sta = 0xff; |
| uint16_t cid; |
| |
| pthread_mutex_lock(&mConnsLock); |
| |
| switch (step) { |
| case CONN_CFG_STEP_LE + 0: /* first LE config step = get remote features & get self addr */ |
| cid = utilGetLE16(&rrf->conn); |
| sta = utilGetLE8(&rrf->status); |
| c = hciConnFindById(cid); |
| if (!c) |
| break; |
| if (sta) |
| break; |
| c->le.ftrs = utilGetLE64(&rrf->leFeatures); |
| step++; |
| break; |
| default: |
| loge("unknown connection config step step 0x%04X.05%04X\n", step, aux); |
| break; |
| } |
| |
| if (!c) |
| loge("connection config failed: unable to find connection\n"); |
| else if (!sta) |
| hciConnCfg(c, step, aux); |
| else if (sta) { |
| loge("connection config failed: sta 0x%02X. Closing.\n", sta); |
| hciConnDisconnect(c->id); |
| } |
| |
| pthread_mutex_unlock(&mConnsLock); |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: hciConnCfg |
| * USE: Configure a new connection to our liking and tell upper layer when done |
| * PARAMS: c - the connection struct |
| * step - the step to perform |
| * aux - substep |
| * RETURN: NONE |
| * NOTES: call with mConnsLock held |
| */ |
| static void hciConnCfg(struct hciAclConn *c, uint32_t step, uint32_t aux) |
| { |
| struct hciEvtWaitDescr ewd[2] = {{.cbk = hciConnCfgCbk, .persistent = false}, {.cbk = hciConnCfgCbk, .persistent = false}}; |
| void *userData = (void*)(uintptr_t)((step << 16) | aux); |
| struct hciLeReadRemoteUsedFeatures rrf; |
| bool success = true; |
| |
| if (c->state != CONN_STATE_CFG) { |
| logw("Tried to config a connection not in config state\n"); |
| return; |
| } |
| |
| switch (step) { |
| case CONN_CFG_STEP_LE + 0: /* first LE config step = get remote features */ |
| if (!c->isMaster) |
| break; /* as a slave there is no config to do */ |
| utilSetLE16(&rrf.conn, c->id); |
| ewd[0].evtType = HCI_EVT_LE_Meta; |
| ewd[0].extra = HCI_EVTLE_Read_Remote_Used_Features_Complete; |
| ewd[0].cbkData = userData; |
| if (!hciCmdSubmit(HCI_OGF_LE, HCI_CMD_LE_Read_Remote_Used_Features, &rrf, sizeof(rrf), ewd + 0, NULL)) { |
| success = false; |
| loge("Failed to send read remote used features command\n"); |
| break; |
| } |
| return; |
| |
| case CONN_CFG_STEP_LE + 1: /* second LE config step = done! */ |
| break; |
| } |
| |
| /* if we got here, the configuration step ended and it is time to proceed */ |
| if (success) { |
| c->state = CONN_STATE_RUNNING; |
| if (hciScheduleConnUpNotif(c->handle, &c->peerAddr, &c->selfAddr, c->isMaster, c->encrypted, c->mitmSafe)) { |
| /* if there are any backlogged RX messages, queue them up too */ |
| while (c->rxBacklog) { |
| struct hciBacklogItemHdr itemHdr; |
| sg packet; |
| |
| if (!sgSerializeCutFront(c->rxBacklog, &itemHdr, sizeof(itemHdr))) { |
| loge("backlog header dequque fail!\n"); |
| goto fail; |
| } |
| packet = sgSplit(c->rxBacklog, itemHdr.len); |
| if (!packet) { |
| loge("backlog packet dequeue fail\n"); |
| goto fail; |
| } |
| sgSwap(packet, c->rxBacklog); |
| if (!sgLength(c->rxBacklog)) { |
| sgFree(c->rxBacklog); |
| c->rxBacklog = NULL; |
| } |
| |
| if (!hciRxAclForRunningConn(c, itemHdr.aclHdr, packet)) { |
| loge("backlog packet rx enqueue fail\n"); |
| sgFree(packet); |
| goto fail; |
| } |
| } |
| return; |
| |
| } else |
| loge("Failed to enqueue upcall for conn up\n"); |
| } |
| |
| fail: |
| hciConnDisconnect(c->id); |
| } |
| |
| /* |
| * FUNCTION: hciConnAclUp |
| * USE: Setup a local ACL conn struct for this conn |
| * PARAMS: established - is it established? |
| * id - the id |
| * mac - the mac |
| * addrType - the address type for bt_addr struct |
| * encrypted - true if encrypted |
| * isMaster - am i master? |
| * RETURN: connection struct or NULL if error |
| * NOTES: call with mConnsLock held |
| */ |
| static struct hciAclConn* hciConnAclUp(bool established, uint16_t id, const uint8_t *mac, uint8_t addrType, bool encrypted, bool isMaster) |
| { |
| bool isLE = addrType != BT_ADDR_TYPE_EDR; |
| struct hciAclConn *c; |
| struct bt_addr addr; |
| |
| mLeConnInProgress = false; |
| |
| memcpy(addr.addr, mac, sizeof(addr.addr)); |
| addr.type = addrType; |
| |
| c = hciConnFindByAddr(&addr); |
| if (!established) { |
| if (!c) |
| logw("cannection we didn't know about failed. Do we care?\n"); |
| else |
| hciConnAclStructDel(c); |
| return NULL; |
| } |
| |
| if (hciConnFindById(id)) { |
| loge("Refusing to create conn struct for existing connection %d\n", id); |
| return NULL; |
| } |
| |
| if (!c) { |
| c = hciConnAclStructNew(id, &addr, CONN_STATE_CFG, isMaster); |
| if (!c) { |
| loge("Failed to alloc conn struct for new ACL link\n"); |
| hciConnDisconnect(id); |
| return NULL; |
| } |
| c->isMaster = isLE ? isMaster : false; |
| } else if (c->state == CONN_STATE_TRYING) { |
| c->state = CONN_STATE_CFG; |
| c->id = id; |
| /* TODO: to be removed once a proper way of setting the local address is provided. since |
| * the default self address is 00:00:00:00:00:00 if no public/random address was set. */ |
| hciGetLocalAddress(c->selfAddr.addr); |
| c->selfAddr.type = BT_ADDR_TYPE_LE_PUBLIC; |
| |
| if (isLE) |
| c->isMaster = isMaster; |
| hciScheduleMakeProgressOnLeConnectionReqs(); //we are in evt rx thread so no direct calls |
| } else { |
| loge("Connection in state %d at up time\n", c->state); |
| } |
| c->encrypted = encrypted; |
| |
| return c; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnAclLeUp |
| * USE: Setup a local ACL.LE conn struct for this conn |
| * PARAMS: status - the connection status |
| * id - the conection id |
| * isMaster - true if we're the master |
| * mac - the mac |
| * addrRandom - true if adddress is random |
| * interval - LE connection interval |
| * latency - LE conenction latency |
| * timeout - LE connection timeout |
| * mca - LE connection master clock accuracy |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnAclLeUp(uint8_t status, uint16_t id, bool isMaster, const uint8_t *mac, bool addrRandom, uint16_t interval, uint16_t latency, uint16_t timeout, uint8_t mca) |
| { |
| struct hciAclConn *c; |
| bool advSetDetermined; |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnAclUp(!status, id, mac, addrRandom ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC, false, isMaster); |
| if (c) { |
| c->le.interval = interval; |
| c->le.latency = latency; |
| c->le.timeout = timeout; |
| c->le.mca = mca; |
| |
| if (!isMaster) { |
| pthread_mutex_lock(&mAdvSetsLock); |
| advSetDetermined = mHciAdvSetHandleAndBlameNewConn(c); |
| pthread_mutex_unlock(&mAdvSetsLock); |
| } |
| |
| if (!isMaster && !advSetDetermined) |
| c->state = CONN_STATE_ADV_SET_BLAMING; |
| else { |
| c->state = CONN_STATE_CFG; |
| hciConnCfg(c, CONN_CFG_STEP_LE, 0); |
| } |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| return !!c; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnAclEncr |
| * USE: Notify all interested in encryption change |
| * PARAMS: cid - the connection id |
| * encrOn - is encryption on? |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnAclEncr(uint16_t cid, bool encrOn) |
| { |
| struct hciAclConn *c; |
| bool ret = false; |
| |
| logd("cid %d now %sencrypted\n", cid, encrOn ? "" : "not "); |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnFindById(cid); |
| if (c) { |
| logd("cid %d was %sencrypted\n", cid, c->encrypted ? "" : "not "); |
| c->encrypted = encrOn; |
| ret = hciScheduleConnEncrChange(c->handle, c->encrypted, c->mitmSafe |
| ); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnAclAuth |
| * USE: Notify all interested in authentication change |
| * PARAMS: cid - the connection id |
| * authOn - is auth on |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnAclAuth(uint16_t cid, bool authOn) |
| { |
| struct hciAclConn *c; |
| bool ret = false; |
| |
| logd("cid %d now %sauthed\n", cid, authOn ? "" : "not "); |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnFindById(cid); |
| if (c) { |
| logd("cid %d was %sauthed\n", cid, c->mitmSafe ? "" : "not "); |
| c->mitmSafe = authOn; |
| ret = hciScheduleConnEncrChange(c->handle, c->encrypted, c->mitmSafe); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| return ret; |
| } |
| |
| /* FUNCTION: hciHandleConnAclEncrKeyRefresh |
| * USE: Notify all interested in encryption key refresh |
| * PARAMS: cid - the connection id |
| * encrOn - is encryption on |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnAclEncrKeyRefresh(uint16_t cid, bool encrOn) |
| { |
| struct hciAclConn *c; |
| bool ret = false; |
| |
| logd("cid %d now %sencrypted with a new key\n", cid, encrOn ? "" : "not "); |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnFindById(cid); |
| if (c) { |
| logd("cid %d was %sencrypted\n", cid, c->encrypted ? "" : "not "); |
| ret = hciScheduleConnEncrKeyRefresh(c->handle, c->encrypted, c->mitmSafe); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnAclLeUpdate |
| * USE: Notify all interested in conn param update |
| * PARAMS: status - did update succeed? |
| * cid - the connection id |
| * interval - the new interval (valid only in case of success) |
| * latency - the new latency (valid only in case of success) |
| * timeout - the new timeout (valid only in case of success) |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnAclLeUpdate(uint8_t status, uint16_t cid, uint16_t interval, uint16_t latency, uint16_t timeout) |
| { |
| struct hciAclConn *c; |
| bool ret = false; |
| |
| logd("cid %d update success: %d\n", cid, status); |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnFindById(cid); |
| if (c) { |
| if (!status) { |
| logd("update: {%d,%d,%d} -> {%d,%d,%d}\n", c->le.interval, c->le.latency, c->le.timeout, interval, latency, timeout); |
| c->le.interval = interval; |
| c->le.latency = latency; |
| c->le.timeout = timeout; |
| } |
| ret = hciScheduleConnParamChange(c->handle, !status, c->le.interval, c->le.latency, c->le.timeout); |
| } |
| pthread_mutex_unlock(&mConnsLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: hciHandleConnLeLtkReq |
| * USE: Handle key request for an LE connection |
| * PARAMS: cid - the connection id |
| * randomNum - provied by other side |
| * diversifier - provied by other side |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciHandleConnLeLtkReq(uint16_t cid, uint64_t randomNum, uint16_t diversifier) |
| { |
| struct hciAclConn *c; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mConnsLock); |
| c = hciConnFindById(cid); |
| if (c) |
| ret = hciScheduleLeLtkRequest(c->handle, randomNum, diversifier); |
| pthread_mutex_unlock(&mConnsLock); |
| return ret; |
| } |
| |
| |
| /* |
| * FUNCTION: hciConnLeCancelDoneCbk |
| * USE: Event callback for command complete event used by hciConnLeCancel() |
| * PARAMS: cbkData - unused |
| * status - from command |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void hciConnLeCancelDoneCbk(void *cbkData, uint8_t status) |
| { |
| if (status == HCI_STAT_GOING_DOWN) |
| return; |
| |
| if (status) |
| logw("Failed to stop LE connection, status = 0x%02x\n", status); |
| |
| pthread_mutex_lock(&mConnsLock); |
| if (mLeConnInProgress) { //spec acknowledges this race between connection succeeded and being cancelled - handle it here |
| struct hciAclConn *c; |
| |
| c = mConns; |
| while (c && c->state != CONN_STATE_DELETING) |
| c = c->next; |
| |
| if (c) //found the connection we were trying to make |
| hciConnAclStructDel(c); |
| else |
| logw("Cannot find connection that we just cancelled\n"); |
| } |
| |
| mLeConnInProgress = false; |
| hciMakeProgressOnLeConnectionReqs(); |
| pthread_mutex_unlock(&mConnsLock); |
| } |
| |
| |
| /* |
| * FUNCTION: hciConnLeCancel |
| * USE: Cancel an ongoing LE connection attempt |
| * PARAMS: addr - whom the connection was to |
| * RETURN: true on success |
| * NOTES: |
| */ |
| static bool hciConnLeCancel(void) |
| { |
| return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Create_Connection_Cancel, NULL, 0, hciConnLeCancelDoneCbk, NULL); |
| } |
| |
| /* |
| * FUNCTION: hciConnUpdate |
| * USE: Start a connection update for an LE connection |
| * PARAMS: cid - the cid |
| * intMin - minimum wanted interval |
| * intMax - maximum wanted itnerval |
| * latency - wanted latency |
| * timeout - wanted timeout |
| * RETURN: false on failure. else wait. |
| * NOTES: |
| */ |
| static bool hciConnUpdate(uint16_t cid, uint16_t intMin, uint16_t intMax, uint16_t latency, uint16_t timeout) |
| { |
| struct hciLeConnectionUpdate cmd; |
| |
| utilSetLE16(&cmd.conn, cid); |
| utilSetLE16(&cmd.connIntervalMin, intMin); |
| utilSetLE16(&cmd.connIntervalMax, intMax); |
| utilSetLE16(&cmd.connLatency, latency); |
| utilSetLE16(&cmd.supervisionTimeout, timeout); |
| utilSetLE16(&cmd.minConnLen, 0); /* not a useful hint, i know */ |
| utilSetLE16(&cmd.maxConnLen, intMin * 2); |
| |
| return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Connection_Update, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL); |
| } |
| |
| /* |
| * FUNCTION: hciConnDisconnect |
| * USE: Start a disconnection process |
| * PARAMS: cid - the cid |
| * RETURN: false on failure. else wait. |
| * NOTES: |
| */ |
| static bool hciConnDisconnect(uint16_t cid) |
| { |
| struct hciDisconnect cmd; |
| |
| utilSetLE16(&cmd.conn, cid); |
| utilSetLE16(&cmd.reason, 0x13); //TODO: better dealings with this value... |
| |
| return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Disconnect, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL); |
| } |
| |