/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include "nimble_syscfg.h" #include "controller/ble_ll.h" #include "controller/ble_ll_hci.h" #include "controller/ble_ll_sync.h" #include "controller/ble_ll_adv.h" #include "controller/ble_ll_scan.h" #include "controller/ble_hw.h" #include "ble_ll_conn_priv.h" #include "ble_ll_priv.h" #if MYNEWT_VAL(BLE_LL_HCI_VS) SLIST_HEAD(ble_ll_hci_vs_list, ble_ll_hci_vs_cmd); static struct ble_ll_hci_vs_list g_ble_ll_hci_vs_list; static int ble_ll_hci_vs_rd_static_addr(uint16_t ocf, const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { struct ble_hci_vs_rd_static_addr_rp *rsp = (void *) rspbuf; ble_addr_t addr; if (cmdlen != 0) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (ble_hw_get_static_addr(&addr) < 0) { return BLE_ERR_UNSPECIFIED; } memcpy(rsp->addr, addr.val, sizeof(rsp->addr)); *rsplen = sizeof(*rsp); return BLE_ERR_SUCCESS; } /* disallow changing TX power if there is any radio activity * note: we could allow to change it if there is no TX activity (eg only * passive scan or sync) but lets just keep this simple for now */ static int ble_ll_hci_vs_is_controller_busy(void) { #if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL) || MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL) struct ble_ll_conn_sm *cur; int i = 0; #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV) && MYNEWT_VAL(BLE_LL_ROLE_OBSERVER) if (ble_ll_sync_enabled()) { return 1; } #endif #if MYNEWT_VAL(BLE_LL_ROLE_BROADCASTER) if (ble_ll_adv_enabled()) { return 1; } #endif #if MYNEWT_VAL(BLE_LL_ROLE_OBSERVER) if (ble_ll_scan_enabled()) { return 1; } #endif #if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL) if (g_ble_ll_conn_create_sm.connsm) { return 1; } #endif #if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL) || MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL) STAILQ_FOREACH(cur, &g_ble_ll_conn_free_list, free_stqe) { i++; } /* check if all connection objects are free */ if (i < MYNEWT_VAL(BLE_MAX_CONNECTIONS)) { return 1; } #endif return 0; } static int ble_ll_hci_vs_set_tx_power(uint16_t ocf, const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_vs_set_tx_pwr_cp *cmd = (const void *) cmdbuf; struct ble_hci_vs_set_tx_pwr_rp *rsp = (void *) rspbuf; if (cmdlen != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (ble_ll_hci_vs_is_controller_busy()) { return BLE_ERR_CMD_DISALLOWED; } if (cmd->tx_power == 127) { /* restore reset default */ g_ble_ll_tx_power = MYNEWT_VAL(BLE_LL_TX_PWR_DBM); } else { g_ble_ll_tx_power = ble_phy_txpower_round(cmd->tx_power); } rsp->tx_power = g_ble_ll_tx_power; *rsplen = sizeof(*rsp); return BLE_ERR_SUCCESS; } #if MYNEWT_VAL(BLE_LL_HCI_VS_CONN_STRICT_SCHED) #if !MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED_FIXED) static int ble_ll_hci_vs_css_configure(const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_vs_css_configure_cp *cmd = (const void *)cmdbuf; uint32_t slot_us; uint32_t period_slots; if (cmdlen != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (!SLIST_EMPTY(&g_ble_ll_conn_active_list)) { return BLE_ERR_CTLR_BUSY; } slot_us = le32toh(cmd->slot_us); period_slots = le32toh(cmd->period_slots); if (slot_us % BLE_LL_CONN_ITVL_USECS) { return BLE_ERR_INV_HCI_CMD_PARMS; } if ((slot_us == 0) || (period_slots == 0)) { return BLE_ERR_INV_HCI_CMD_PARMS; } ble_ll_sched_css_set_params(slot_us, period_slots); return BLE_ERR_SUCCESS; } #endif static int ble_ll_hci_vs_css_set_next_slot(const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_vs_css_set_next_slot_cp *cmd = (const void *)cmdbuf; uint16_t slot_idx; if (cmdlen != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } slot_idx = le16toh(cmd->slot_idx); if ((slot_idx >= ble_ll_sched_css_get_period_slots()) && (slot_idx != BLE_LL_CONN_CSS_NO_SLOT)) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (ble_ll_conn_css_is_slot_busy(slot_idx)) { return BLE_ERR_CTLR_BUSY; } ble_ll_conn_css_set_next_slot(slot_idx); return BLE_ERR_SUCCESS; } static int ble_ll_hci_vs_css_set_conn_slot(const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_vs_css_set_conn_slot_cp *cmd = (const void *)cmdbuf; struct ble_ll_conn_sm *connsm; uint16_t conn_handle; uint16_t slot_idx; if (cmdlen != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } slot_idx = le16toh(cmd->slot_idx); if ((slot_idx >= ble_ll_sched_css_get_period_slots()) && (slot_idx != BLE_LL_CONN_CSS_NO_SLOT)) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (ble_ll_conn_css_is_slot_busy(slot_idx)) { return BLE_ERR_CTLR_BUSY; } conn_handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(conn_handle); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } if (connsm->css_slot_idx_pending != BLE_LL_CONN_CSS_NO_SLOT) { return BLE_ERR_DIFF_TRANS_COLL; } if (connsm->css_slot_idx == slot_idx) { return BLE_ERR_CMD_DISALLOWED; } if (ble_ll_conn_css_move(connsm, slot_idx) < 0) { return BLE_ERR_CTLR_BUSY; } return BLE_ERR_SUCCESS; } static int ble_ll_hci_vs_css(uint16_t ocf, const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_vs_css_cp *cmd = (const void *)cmdbuf; if (cmdlen < sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } *rsplen = 0; switch (cmd->opcode) { #if !MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED_FIXED) case BLE_HCI_VS_CSS_OP_CONFIGURE: return ble_ll_hci_vs_css_configure(cmdbuf, cmdlen, rspbuf, rsplen); #endif case BLE_HCI_VS_CSS_OP_SET_NEXT_SLOT: return ble_ll_hci_vs_css_set_next_slot(cmdbuf, cmdlen, rspbuf, rsplen); case BLE_HCI_VS_CSS_OP_SET_CONN_SLOT: return ble_ll_hci_vs_css_set_conn_slot(cmdbuf, cmdlen, rspbuf, rsplen); } return BLE_ERR_INV_HCI_CMD_PARMS; } #endif static struct ble_ll_hci_vs_cmd g_ble_ll_hci_vs_cmds[] = { BLE_LL_HCI_VS_CMD(BLE_HCI_OCF_VS_RD_STATIC_ADDR, ble_ll_hci_vs_rd_static_addr), BLE_LL_HCI_VS_CMD(BLE_HCI_OCF_VS_SET_TX_PWR, ble_ll_hci_vs_set_tx_power), #if MYNEWT_VAL(BLE_LL_HCI_VS_CONN_STRICT_SCHED) BLE_LL_HCI_VS_CMD(BLE_HCI_OCF_VS_CSS, ble_ll_hci_vs_css), #endif }; static struct ble_ll_hci_vs_cmd * ble_ll_hci_vs_find_by_ocf(uint16_t ocf) { struct ble_ll_hci_vs_cmd *entry; entry = SLIST_FIRST(&g_ble_ll_hci_vs_list); while (entry) { if (entry->ocf == ocf) { return entry; } entry = SLIST_NEXT(entry, link); } return NULL; } int ble_ll_hci_vs_cmd_proc(const uint8_t *cmdbuf, uint8_t cmdlen, uint16_t ocf, uint8_t *rspbuf, uint8_t *rsplen) { struct ble_ll_hci_vs_cmd *cmd; int rc; cmd = ble_ll_hci_vs_find_by_ocf(ocf); if (!cmd) { rc = BLE_ERR_UNKNOWN_HCI_CMD; } else { rc = cmd->cb(ocf, cmdbuf, cmdlen, rspbuf, rsplen); } return rc; } void ble_ll_hci_vs_register(struct ble_ll_hci_vs_cmd *cmds, uint32_t num_cmds) { uint32_t i; /* Assume all cmds are registered early on init, so just assert in case of * invalid request since it means something is wrong with the code itself. */ for (i = 0; i < num_cmds; i++, cmds++) { BLE_LL_ASSERT(cmds->cb != NULL); BLE_LL_ASSERT(ble_ll_hci_vs_find_by_ocf(cmds->ocf) == NULL); SLIST_INSERT_HEAD(&g_ble_ll_hci_vs_list, cmds, link); } } void ble_ll_hci_vs_init(void) { SLIST_INIT(&g_ble_ll_hci_vs_list); ble_ll_hci_vs_register(g_ble_ll_hci_vs_cmds, ARRAY_SIZE(g_ble_ll_hci_vs_cmds)); } #endif