| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011-2012 Intel Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * Copyright 2023-2024 NXP |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <alloca.h> |
| #include <sys/uio.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/hci.h" |
| |
| #include "src/shared/util.h" |
| #include "src/shared/timeout.h" |
| #include "src/shared/crypto.h" |
| #include "src/shared/ecc.h" |
| #include "src/shared/queue.h" |
| #include "monitor/bt.h" |
| #include "monitor/msft.h" |
| #include "monitor/emulator.h" |
| #include "btdev.h" |
| |
| #define AL_SIZE 16 |
| #define RL_SIZE 16 |
| #define CIS_SIZE 3 |
| #define BIS_SIZE 3 |
| #define CIG_SIZE 3 |
| |
| #define MAX_PA_DATA_LEN 252 |
| |
| #define has_bredr(btdev) (!((btdev)->features[4] & 0x20)) |
| #define has_le(btdev) (!!((btdev)->features[4] & 0x40)) |
| |
| #define ACL_HANDLE 42 |
| #define ISO_HANDLE 257 |
| #define SCO_HANDLE 257 |
| #define SYC_HANDLE 1 |
| #define INV_HANDLE 0xffff |
| |
| struct hook { |
| btdev_hook_func handler; |
| void *user_data; |
| enum btdev_hook_type type; |
| uint16_t opcode; |
| }; |
| |
| #define MAX_HOOK_ENTRIES 16 |
| #define MAX_EXT_ADV_SETS 3 |
| #define MAX_PENDING_CONN 16 |
| |
| struct btdev_conn { |
| uint16_t handle; |
| uint8_t type; |
| struct btdev *dev; |
| struct btdev_conn *link; |
| void *data; |
| }; |
| |
| struct btdev_al { |
| uint8_t type; |
| bdaddr_t addr; |
| }; |
| |
| struct btdev_rl { |
| uint8_t type; |
| bdaddr_t addr; |
| uint8_t mode; |
| uint8_t peer_irk[16]; |
| uint8_t local_irk[16]; |
| }; |
| |
| struct le_ext_adv { |
| struct btdev *dev; |
| uint8_t handle; |
| uint8_t enable; |
| uint8_t type; /* evt_properties */ |
| uint8_t own_addr_type; /* own_addr_type */ |
| uint8_t direct_addr_type; /* peer_addr_type */ |
| uint8_t direct_addr[6]; /* peer_addr */ |
| uint8_t filter_policy; /* filter_policy */ |
| uint8_t random_addr[6]; |
| bool rpa; |
| uint8_t adv_data[252]; |
| uint8_t adv_data_len; |
| uint8_t scan_data[252]; |
| uint8_t scan_data_len; |
| unsigned int id; |
| }; |
| |
| struct le_cig { |
| struct bt_hci_cmd_le_set_cig_params params; |
| struct bt_hci_cis_params cis[CIS_SIZE]; |
| bool activated; |
| } __attribute__ ((packed)); |
| |
| struct btdev { |
| enum btdev_type type; |
| uint16_t id; |
| |
| struct queue *conns; |
| |
| bool auth_init; |
| uint8_t link_key[16]; |
| uint16_t pin[16]; |
| uint8_t pin_len; |
| uint8_t io_cap; |
| uint8_t auth_req; |
| bool ssp_auth_complete; |
| uint8_t ssp_status; |
| |
| btdev_command_func command_handler; |
| void *command_data; |
| |
| btdev_send_func send_handler; |
| void *send_data; |
| |
| unsigned int inquiry_id; |
| unsigned int inquiry_timeout_id; |
| |
| struct hook *hook_list[MAX_HOOK_ENTRIES]; |
| |
| struct bt_crypto *crypto; |
| |
| uint16_t manufacturer; |
| uint8_t version; |
| uint16_t revision; |
| uint8_t commands[64]; |
| uint8_t max_page; |
| uint8_t features[8]; |
| uint8_t feat_page_2[8]; |
| uint16_t acl_mtu; |
| uint16_t acl_max_pkt; |
| uint16_t sco_mtu; |
| uint16_t sco_max_pkt; |
| uint16_t iso_mtu; |
| uint16_t iso_max_pkt; |
| uint8_t country_code; |
| uint8_t bdaddr[6]; |
| uint8_t random_addr[6]; |
| uint8_t le_features[8]; |
| uint8_t le_states[8]; |
| const struct btdev_cmd *cmds; |
| uint16_t msft_opcode; |
| const struct btdev_cmd *msft_cmds; |
| uint16_t emu_opcode; |
| const struct btdev_cmd *emu_cmds; |
| bool aosp_capable; |
| |
| uint16_t default_link_policy; |
| uint8_t event_mask[8]; |
| uint8_t event_mask_page2[8]; |
| uint8_t event_filter; |
| uint8_t name[248]; |
| uint8_t dev_class[3]; |
| uint16_t voice_setting; |
| uint16_t conn_accept_timeout; |
| uint16_t page_timeout; |
| uint8_t scan_enable; |
| uint16_t page_scan_interval; |
| uint16_t page_scan_window; |
| uint16_t page_scan_type; |
| uint8_t auth_enable; |
| uint16_t inquiry_scan_interval; |
| uint16_t inquiry_scan_window; |
| uint8_t inquiry_mode; |
| uint8_t afh_assessment_mode; |
| uint8_t ext_inquiry_fec; |
| uint8_t ext_inquiry_rsp[240]; |
| uint8_t simple_pairing_mode; |
| uint8_t ssp_debug_mode; |
| uint8_t secure_conn_support; |
| uint8_t host_flow_control; |
| uint8_t le_supported; |
| uint8_t le_simultaneous; |
| uint8_t le_event_mask[8]; |
| uint8_t le_adv_data[31]; |
| uint8_t le_adv_data_len; |
| uint8_t le_adv_type; |
| uint8_t le_adv_own_addr; |
| uint8_t le_adv_direct_addr_type; |
| uint8_t le_adv_direct_addr[6]; |
| uint8_t le_adv_filter_policy; |
| uint8_t le_scan_data[31]; |
| uint8_t le_scan_data_len; |
| uint8_t le_scan_enable; |
| uint8_t le_scan_type; |
| uint8_t le_scan_own_addr_type; |
| uint8_t le_scan_filter_policy; |
| uint8_t le_filter_dup; |
| uint8_t le_adv_enable; |
| uint8_t le_pa_enable; |
| uint16_t le_pa_properties; |
| uint16_t le_pa_min_interval; |
| uint16_t le_pa_max_interval; |
| uint8_t le_pa_data_len; |
| uint8_t le_pa_data[MAX_PA_DATA_LEN]; |
| struct bt_hci_cmd_le_pa_create_sync pa_sync_cmd; |
| uint16_t le_pa_sync_handle; |
| uint8_t big_handle; |
| uint8_t le_ltk[16]; |
| struct le_cig le_cig[CIG_SIZE]; |
| uint8_t le_iso_path[2]; |
| |
| /* Real time length of AL array */ |
| uint8_t le_al_len; |
| /* Real time length of RL array */ |
| uint8_t le_rl_len; |
| struct btdev_al le_al[AL_SIZE]; |
| struct btdev_rl le_rl[RL_SIZE]; |
| uint8_t le_rl_enable; |
| uint16_t le_rl_timeout; |
| |
| struct btdev *pending_conn[MAX_PENDING_CONN]; |
| |
| uint8_t le_local_sk256[32]; |
| |
| uint16_t sync_train_interval; |
| uint32_t sync_train_timeout; |
| uint8_t sync_train_service_data; |
| |
| uint16_t le_ext_adv_type; |
| |
| struct queue *le_ext_adv; |
| |
| btdev_debug_func_t debug_callback; |
| btdev_destroy_func_t debug_destroy; |
| void *debug_data; |
| }; |
| |
| struct inquiry_data { |
| struct btdev *btdev; |
| int num_resp; |
| |
| int sent_count; |
| int iter; |
| }; |
| |
| #define DEFAULT_INQUIRY_INTERVAL 100 /* 100 miliseconds */ |
| |
| #define MAX_BTDEV_ENTRIES 16 |
| |
| static const uint8_t LINK_KEY_NONE[16] = { 0 }; |
| static const uint8_t LINK_KEY_DUMMY[16] = { 0, 1, 2, 3, 4, 5, 6, 7, |
| 8, 9, 0, 1, 2, 3, 4, 5 }; |
| |
| static struct btdev *btdev_list[MAX_BTDEV_ENTRIES] = { }; |
| |
| static int get_hook_index(struct btdev *btdev, enum btdev_hook_type type, |
| uint16_t opcode) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_HOOK_ENTRIES; i++) { |
| if (btdev->hook_list[i] == NULL) |
| continue; |
| |
| if (btdev->hook_list[i]->type == type && |
| btdev->hook_list[i]->opcode == opcode) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| static bool run_hooks(struct btdev *btdev, enum btdev_hook_type type, |
| uint16_t opcode, const void *data, uint16_t len) |
| { |
| int index = get_hook_index(btdev, type, opcode); |
| if (index < 0) |
| return true; |
| |
| return btdev->hook_list[index]->handler(data, len, |
| btdev->hook_list[index]->user_data); |
| } |
| |
| static inline int add_btdev(struct btdev *btdev) |
| { |
| int i, index = -1; |
| |
| for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { |
| if (btdev_list[i] == NULL) { |
| index = i; |
| btdev_list[index] = btdev; |
| break; |
| } |
| } |
| |
| return index; |
| } |
| |
| static inline int del_btdev(struct btdev *btdev) |
| { |
| int i, index = -1; |
| |
| for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { |
| if (btdev_list[i] == btdev) { |
| index = i; |
| btdev_list[index] = NULL; |
| break; |
| } |
| } |
| |
| return index; |
| } |
| |
| static inline bool valid_btdev(struct btdev *btdev) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { |
| if (btdev_list[i] == btdev) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static inline struct btdev *find_btdev_by_bdaddr(const uint8_t *bdaddr) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { |
| if (btdev_list[i] && !memcmp(btdev_list[i]->bdaddr, bdaddr, 6)) |
| return btdev_list[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static bool match_adv_addr(const void *data, const void *match_data) |
| { |
| const struct le_ext_adv *adv = data; |
| const uint8_t *bdaddr = match_data; |
| |
| return !memcmp(adv->random_addr, bdaddr, 6); |
| } |
| |
| static inline struct btdev *find_btdev_by_bdaddr_type(const uint8_t *bdaddr, |
| uint8_t bdaddr_type) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { |
| struct btdev *dev = btdev_list[i]; |
| int cmp; |
| struct le_ext_adv *adv; |
| |
| if (!dev) |
| continue; |
| |
| if (bdaddr_type == 0x01) |
| cmp = memcmp(dev->random_addr, bdaddr, 6); |
| else |
| cmp = memcmp(dev->bdaddr, bdaddr, 6); |
| |
| if (!cmp) |
| return dev; |
| |
| /* Check for instance own Random addresses */ |
| if (bdaddr_type == 0x01) { |
| adv = queue_find(dev->le_ext_adv, match_adv_addr, |
| bdaddr); |
| if (adv) |
| return dev; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void get_bdaddr(uint16_t id, uint8_t index, uint8_t *bdaddr) |
| { |
| bdaddr[0] = id & 0xff; |
| bdaddr[1] = id >> 8; |
| bdaddr[2] = index; |
| bdaddr[3] = 0x01; |
| bdaddr[4] = 0xaa; |
| bdaddr[5] = 0x00; |
| } |
| |
| struct btdev_cmd { |
| uint16_t opcode; |
| int (*func)(struct btdev *dev, const void *data, uint8_t len); |
| int (*complete)(struct btdev *dev, const void *data, uint8_t len); |
| }; |
| |
| #define CMD(_opcode, _func, _complete) \ |
| { \ |
| .opcode = _opcode, \ |
| .func = _func, \ |
| .complete = _complete, \ |
| } |
| |
| static void send_packet(struct btdev *btdev, const struct iovec *iov, |
| int iovlen) |
| { |
| int i; |
| |
| if (!btdev->send_handler) |
| return; |
| |
| for (i = 0; i < iovlen; i++) { |
| if (!i) |
| util_hexdump('<', iov[i].iov_base, iov[i].iov_len, |
| btdev->debug_callback, btdev->debug_data); |
| else |
| util_hexdump(' ', iov[i].iov_base, iov[i].iov_len, |
| btdev->debug_callback, btdev->debug_data); |
| } |
| |
| btdev->send_handler(iov, iovlen, btdev->send_data); |
| } |
| |
| static void send_cmd(struct btdev *btdev, uint8_t evt, uint16_t opcode, |
| const struct iovec *iov, int iovlen) |
| { |
| struct bt_hci_evt_hdr hdr; |
| struct iovec iov2[2 + iovlen]; |
| uint8_t pkt = BT_H4_EVT_PKT; |
| int i; |
| |
| util_debug(btdev->debug_callback, btdev->debug_data, |
| "event 0x%02x opcode 0x%04x", evt, opcode); |
| |
| iov2[0].iov_base = &pkt; |
| iov2[0].iov_len = sizeof(pkt); |
| |
| hdr.evt = evt; |
| hdr.plen = 0; |
| |
| iov2[1].iov_base = &hdr; |
| iov2[1].iov_len = sizeof(hdr); |
| |
| for (i = 0; i < iovlen; i++) { |
| hdr.plen += iov[i].iov_len; |
| iov2[2 + i].iov_base = iov[i].iov_base; |
| iov2[2 + i].iov_len = iov[i].iov_len; |
| } |
| |
| if (run_hooks(btdev, BTDEV_HOOK_POST_CMD, opcode, iov[i -1].iov_base, |
| iov[i -1].iov_len)) |
| send_packet(btdev, iov2, 2 + iovlen); |
| } |
| |
| static void cmd_complete(struct btdev *btdev, uint16_t opcode, |
| const void *data, uint8_t len) |
| { |
| struct bt_hci_evt_cmd_complete cc; |
| struct iovec iov[2]; |
| |
| cc.ncmd = 0x01; |
| cc.opcode = cpu_to_le16(opcode); |
| |
| iov[0].iov_base = &cc; |
| iov[0].iov_len = sizeof(cc); |
| |
| iov[1].iov_base = (void *) data; |
| iov[1].iov_len = len; |
| |
| send_cmd(btdev, BT_HCI_EVT_CMD_COMPLETE, opcode, iov, 2); |
| } |
| |
| static int cmd_set_event_mask(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_set_event_mask *cmd = data; |
| uint8_t status; |
| |
| memcpy(dev->event_mask, cmd->mask, 8); |
| status = BT_HCI_ERR_SUCCESS; |
| |
| cmd_complete(dev, BT_HCI_CMD_SET_EVENT_MASK, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static void al_reset(struct btdev_al *al) |
| { |
| al->type = 0xff; |
| bacpy(&al->addr, BDADDR_ANY); |
| } |
| |
| static void al_clear(struct btdev *dev) |
| { |
| int i; |
| |
| for (i = 0; i < AL_SIZE; i++) |
| al_reset(&dev->le_al[i]); |
| } |
| |
| static void rl_reset(struct btdev_rl *rl) |
| { |
| rl->type = 0xff; |
| bacpy(&rl->addr, BDADDR_ANY); |
| memset(rl->peer_irk, 0, 16); |
| memset(rl->local_irk, 0, 16); |
| } |
| |
| static void rl_clear(struct btdev *dev) |
| { |
| int i; |
| |
| for (i = 0; i < RL_SIZE; i++) |
| rl_reset(&dev->le_rl[i]); |
| } |
| |
| /* Set the real time length of AL array */ |
| void btdev_set_al_len(struct btdev *btdev, uint8_t len) |
| { |
| btdev->le_al_len = len; |
| } |
| |
| /* Set the real time length of RL array */ |
| void btdev_set_rl_len(struct btdev *btdev, uint8_t len) |
| { |
| btdev->le_rl_len = len; |
| } |
| |
| static void conn_unlink(struct btdev_conn *conn1, struct btdev_conn *conn2) |
| { |
| conn1->link = NULL; |
| conn2->link = NULL; |
| } |
| |
| static void conn_remove(void *data) |
| { |
| struct btdev_conn *conn = data; |
| |
| if (conn->link) { |
| struct btdev_conn *link = conn->link; |
| |
| conn_unlink(conn, conn->link); |
| conn_remove(link); |
| } |
| |
| queue_remove(conn->dev->conns, conn); |
| |
| free(conn->data); |
| free(conn); |
| } |
| |
| static void le_ext_adv_free(void *data) |
| { |
| struct le_ext_adv *ext_adv = data; |
| |
| /* Remove to queue */ |
| queue_remove(ext_adv->dev->le_ext_adv, ext_adv); |
| |
| if (ext_adv->id) |
| timeout_remove(ext_adv->id); |
| |
| free(ext_adv); |
| } |
| |
| static void btdev_reset(struct btdev *btdev) |
| { |
| /* FIXME: include here clearing of all states that should be |
| * cleared upon HCI_Reset |
| */ |
| |
| btdev->le_scan_enable = 0x00; |
| btdev->le_adv_enable = 0x00; |
| btdev->le_pa_enable = 0x00; |
| btdev->le_pa_sync_handle = 0x0000; |
| btdev->big_handle = 0xff; |
| |
| al_clear(btdev); |
| rl_clear(btdev); |
| |
| btdev->le_al_len = AL_SIZE; |
| btdev->le_rl_len = RL_SIZE; |
| |
| queue_remove_all(btdev->conns, NULL, NULL, conn_remove); |
| queue_remove_all(btdev->le_ext_adv, NULL, NULL, le_ext_adv_free); |
| } |
| |
| static int cmd_reset(struct btdev *dev, const void *data, uint8_t len) |
| { |
| uint8_t status; |
| |
| btdev_reset(dev); |
| status = BT_HCI_ERR_SUCCESS; |
| |
| cmd_complete(dev, BT_HCI_CMD_RESET, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_local_version(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_version rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.hci_ver = dev->version; |
| rsp.hci_rev = cpu_to_le16(dev->revision); |
| rsp.lmp_ver = dev->version; |
| rsp.manufacturer = cpu_to_le16(dev->manufacturer); |
| rsp.lmp_subver = cpu_to_le16(dev->revision); |
| |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_VERSION, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_local_commands(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_commands rsp; |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.commands, dev->commands, 64); |
| |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_COMMANDS, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_local_features(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_features rsp; |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.features, dev->features, 8); |
| |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_buffer_size(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_buffer_size rsp; |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.acl_mtu = cpu_to_le16(dev->acl_mtu); |
| rsp.sco_mtu = cpu_to_le16(dev->sco_mtu); |
| rsp.acl_max_pkt = cpu_to_le16(dev->acl_max_pkt); |
| rsp.sco_max_pkt = cpu_to_le16(dev->sco_max_pkt); |
| |
| cmd_complete(dev, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| #define CMD_COMMON_ALL \ |
| CMD(BT_HCI_CMD_SET_EVENT_MASK, cmd_set_event_mask, NULL), \ |
| CMD(BT_HCI_CMD_RESET, cmd_reset, NULL), \ |
| CMD(BT_HCI_CMD_READ_LOCAL_VERSION, cmd_read_local_version, NULL), \ |
| CMD(BT_HCI_CMD_READ_LOCAL_COMMANDS, cmd_read_local_commands, NULL), \ |
| CMD(BT_HCI_CMD_READ_LOCAL_FEATURES, cmd_read_local_features, NULL), \ |
| CMD(BT_HCI_CMD_READ_BUFFER_SIZE, cmd_read_buffer_size, NULL) |
| |
| static void set_common_commands_all(struct btdev *btdev) |
| { |
| btdev->commands[5] |= 0x40; /* Set Event Mask */ |
| btdev->commands[5] |= 0x80; /* Reset */ |
| btdev->commands[14] |= 0x08; /* Read Local Version */ |
| btdev->commands[14] |= 0x10; /* Read Local Supported Commands */ |
| btdev->commands[14] |= 0x20; /* Read Local Supported Features */ |
| btdev->commands[14] |= 0x80; /* Read Buffer Size */ |
| } |
| |
| static void cmd_status(struct btdev *btdev, uint8_t status, uint16_t opcode) |
| { |
| struct bt_hci_evt_cmd_status cs; |
| struct iovec iov; |
| |
| cs.status = status; |
| cs.ncmd = 0x01; |
| cs.opcode = cpu_to_le16(opcode); |
| |
| iov.iov_base = &cs; |
| iov.iov_len = sizeof(cs); |
| |
| send_cmd(btdev, BT_HCI_EVT_CMD_STATUS, opcode, &iov, 1); |
| } |
| |
| static int cmd_disconnect(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_DISCONNECT); |
| |
| return 0; |
| } |
| |
| static void send_event(struct btdev *btdev, uint8_t event, |
| const void *data, uint8_t len) |
| { |
| struct bt_hci_evt_hdr hdr; |
| struct iovec iov[3]; |
| uint8_t pkt = BT_H4_EVT_PKT; |
| |
| util_debug(btdev->debug_callback, btdev->debug_data, |
| "event 0x%02x", event); |
| |
| iov[0].iov_base = &pkt; |
| iov[0].iov_len = sizeof(pkt); |
| |
| hdr.evt = event; |
| hdr.plen = len; |
| |
| iov[1].iov_base = &hdr; |
| iov[1].iov_len = sizeof(hdr); |
| |
| if (len > 0) { |
| iov[2].iov_base = (void *) data; |
| iov[2].iov_len = len; |
| } |
| |
| if (run_hooks(btdev, BTDEV_HOOK_POST_EVT, event, data, len)) |
| send_packet(btdev, iov, len > 0 ? 3 : 2); |
| } |
| |
| static bool match_handle(const void *data, const void *match_data) |
| { |
| const struct btdev_conn *conn = data; |
| uint16_t handle = PTR_TO_UINT(match_data); |
| |
| return conn->handle == handle; |
| } |
| |
| static void disconnect_complete(struct btdev *dev, uint16_t handle, |
| uint8_t status, uint8_t reason) |
| { |
| struct bt_hci_evt_disconnect_complete rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| |
| rsp.status = status; |
| rsp.handle = cpu_to_le16(handle); |
| rsp.reason = reason; |
| |
| send_event(dev, BT_HCI_EVT_DISCONNECT_COMPLETE, &rsp, sizeof(rsp)); |
| } |
| |
| static int cmd_disconnect_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_disconnect *cmd = data; |
| struct bt_hci_evt_disconnect_complete rsp; |
| struct btdev_conn *conn; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| |
| conn = queue_remove_if(dev->conns, match_handle, |
| UINT_TO_PTR(cpu_to_le16(cmd->handle))); |
| if (!conn) { |
| disconnect_complete(dev, 0x0000, BT_HCI_ERR_UNKNOWN_CONN_ID, |
| 0x00); |
| return 0; |
| } |
| |
| /* Local host has different reason (Core v5.3 Vol 4 Part E Sec 7.1.6) */ |
| disconnect_complete(dev, conn->handle, BT_HCI_ERR_SUCCESS, |
| BT_HCI_ERR_LOCAL_HOST_TERM); |
| |
| if (conn->link) |
| disconnect_complete(conn->link->dev, conn->link->handle, |
| BT_HCI_ERR_SUCCESS, cmd->reason); |
| |
| conn_remove(conn); |
| |
| return 0; |
| } |
| |
| static int cmd_remote_version(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_REMOTE_VERSION); |
| |
| return 0; |
| } |
| |
| static int cmd_remote_version_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_read_remote_version *cmd = data; |
| struct bt_hci_evt_remote_version_complete ev; |
| struct btdev_conn *conn; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(cpu_to_le16(cmd->handle))); |
| if (conn) { |
| ev.status = BT_HCI_ERR_SUCCESS; |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.lmp_ver = conn->link->dev->version; |
| ev.manufacturer = cpu_to_le16(conn->link->dev->manufacturer); |
| ev.lmp_subver = cpu_to_le16(conn->link->dev->revision); |
| } else { |
| ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.lmp_ver = 0x00; |
| ev.manufacturer = cpu_to_le16(0); |
| ev.lmp_subver = cpu_to_le16(0); |
| } |
| |
| send_event(dev, BT_HCI_EVT_REMOTE_VERSION_COMPLETE, &ev, sizeof(ev)); |
| |
| return 0; |
| } |
| |
| static int cmd_set_host_flowctl(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_set_host_flow_control *cmd = data; |
| uint8_t status; |
| |
| if (cmd->enable > 0x03) { |
| status = BT_HCI_ERR_INVALID_PARAMETERS; |
| } else { |
| dev->host_flow_control = cmd->enable; |
| status = BT_HCI_ERR_SUCCESS; |
| } |
| |
| cmd_complete(dev, BT_HCI_CMD_SET_HOST_FLOW_CONTROL, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_host_buffer_size(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| cmd_complete(dev, BT_HCI_CMD_HOST_BUFFER_SIZE, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_host_num_completed_pkts(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| /* This command is special in the sense that no event is |
| * normally generated after the command has completed. |
| */ |
| return 0; |
| } |
| |
| static int cmd_read_bdaddr(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_bd_addr rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, dev->bdaddr, 6); |
| |
| cmd_complete(dev, BT_HCI_CMD_READ_BD_ADDR, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| #define CMD_COMMON_BREDR_LE \ |
| CMD(BT_HCI_CMD_DISCONNECT, cmd_disconnect, cmd_disconnect_complete), \ |
| CMD(BT_HCI_CMD_READ_REMOTE_VERSION, cmd_remote_version, \ |
| cmd_remote_version_complete), \ |
| CMD(BT_HCI_CMD_SET_HOST_FLOW_CONTROL, cmd_set_host_flowctl, NULL), \ |
| CMD(BT_HCI_CMD_HOST_BUFFER_SIZE, cmd_host_buffer_size, NULL), \ |
| CMD(BT_HCI_CMD_HOST_NUM_COMPLETED_PACKETS, \ |
| cmd_host_num_completed_pkts, NULL), \ |
| CMD(BT_HCI_CMD_READ_BD_ADDR, cmd_read_bdaddr, NULL) |
| |
| static void set_common_commands_bredrle(struct btdev *btdev) |
| { |
| btdev->commands[0] |= 0x20; /* Disconnect */ |
| btdev->commands[2] |= 0x80; /* Read Remote Version Information */ |
| btdev->commands[10] |= 0x20; /* Set Host Flow Control */ |
| btdev->commands[10] |= 0x40; /* Host Buffer Size */ |
| btdev->commands[15] |= 0x02; /* Read BD ADDR */ |
| } |
| |
| static int cmd_inquiry(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_INQUIRY); |
| |
| return 0; |
| } |
| |
| static bool inquiry_callback(void *user_data) |
| { |
| struct inquiry_data *data = user_data; |
| struct btdev *btdev = data->btdev; |
| struct bt_hci_evt_inquiry_complete ic; |
| int sent = data->sent_count; |
| int i; |
| |
| /*Report devices only once and wait for inquiry timeout*/ |
| if (data->iter == MAX_BTDEV_ENTRIES) |
| return true; |
| |
| for (i = data->iter; i < MAX_BTDEV_ENTRIES; i++) { |
| /*Lets sent 10 inquiry results at once */ |
| if (sent + 10 == data->sent_count) |
| break; |
| |
| if (!btdev_list[i] || btdev_list[i] == btdev) |
| continue; |
| |
| if (!(btdev_list[i]->scan_enable & 0x02)) |
| continue; |
| |
| if (btdev->inquiry_mode == 0x02 && |
| btdev_list[i]->ext_inquiry_rsp[0]) { |
| struct bt_hci_evt_ext_inquiry_result ir; |
| |
| ir.num_resp = 0x01; |
| memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); |
| ir.pscan_rep_mode = 0x00; |
| ir.pscan_period_mode = 0x00; |
| memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); |
| ir.clock_offset = 0x0000; |
| ir.rssi = -60; |
| memcpy(ir.data, btdev_list[i]->ext_inquiry_rsp, 240); |
| |
| send_event(btdev, BT_HCI_EVT_EXT_INQUIRY_RESULT, |
| &ir, sizeof(ir)); |
| data->sent_count++; |
| continue; |
| } |
| |
| if (btdev->inquiry_mode > 0x00) { |
| struct bt_hci_evt_inquiry_result_with_rssi ir; |
| |
| ir.num_resp = 0x01; |
| memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); |
| ir.pscan_rep_mode = 0x00; |
| ir.pscan_period_mode = 0x00; |
| memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); |
| ir.clock_offset = 0x0000; |
| ir.rssi = -60; |
| |
| send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI, |
| &ir, sizeof(ir)); |
| data->sent_count++; |
| } else { |
| struct bt_hci_evt_inquiry_result ir; |
| |
| ir.num_resp = 0x01; |
| memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); |
| ir.pscan_rep_mode = 0x00; |
| ir.pscan_period_mode = 0x00; |
| ir.pscan_mode = 0x00; |
| memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); |
| ir.clock_offset = 0x0000; |
| |
| send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT, |
| &ir, sizeof(ir)); |
| data->sent_count++; |
| } |
| } |
| data->iter = i; |
| |
| /* Check if we sent already required amount of responses*/ |
| if (data->num_resp && data->sent_count == data->num_resp) |
| goto finish; |
| |
| return true; |
| |
| finish: |
| /* Note that destroy will be called */ |
| ic.status = BT_HCI_ERR_SUCCESS; |
| send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); |
| |
| return false; |
| } |
| |
| static void inquiry_destroy(void *user_data) |
| { |
| struct inquiry_data *data = user_data; |
| struct btdev *btdev = data->btdev; |
| |
| if (!btdev) |
| goto finish; |
| |
| btdev->inquiry_id = 0; |
| |
| if (btdev->inquiry_timeout_id > 0) { |
| timeout_remove(btdev->inquiry_timeout_id); |
| btdev->inquiry_timeout_id = 0; |
| } |
| |
| finish: |
| free(data); |
| } |
| |
| static bool inquiry_timeout(void *user_data) |
| { |
| struct inquiry_data *data = user_data; |
| struct btdev *btdev = data->btdev; |
| struct bt_hci_evt_inquiry_complete ic; |
| |
| timeout_remove(btdev->inquiry_id); |
| btdev->inquiry_timeout_id = 0; |
| |
| /* Inquiry is stopped, send Inquiry complete event. */ |
| ic.status = BT_HCI_ERR_SUCCESS; |
| send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); |
| |
| return false; |
| } |
| |
| static int cmd_inquiry_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_inquiry *cmd = data; |
| struct inquiry_data *idata; |
| struct bt_hci_evt_inquiry_complete ic; |
| int status = BT_HCI_ERR_HARDWARE_FAILURE; |
| unsigned int inquiry_len_ms; |
| |
| if (dev->inquiry_id > 0) { |
| status = BT_HCI_ERR_COMMAND_DISALLOWED; |
| goto failed; |
| } |
| |
| idata = malloc0(sizeof(*idata)); |
| if (!idata) |
| goto failed; |
| |
| idata->btdev = dev; |
| idata->num_resp = cmd->num_resp; |
| |
| /* Add timeout to cancel inquiry */ |
| inquiry_len_ms = 1280 * cmd->length; |
| if (inquiry_len_ms) |
| dev->inquiry_timeout_id = timeout_add(inquiry_len_ms, |
| inquiry_timeout, |
| idata, NULL); |
| |
| dev->inquiry_id = timeout_add(DEFAULT_INQUIRY_INTERVAL, |
| inquiry_callback, idata, |
| inquiry_destroy); |
| /* Return if success */ |
| if (dev->inquiry_id > 0) |
| return 0; |
| |
| failed: |
| ic.status = status; |
| send_event(dev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); |
| |
| return 0; |
| } |
| |
| static int cmd_inquiry_cancel(struct btdev *dev, const void *data, uint8_t len) |
| { |
| uint8_t status; |
| |
| if (!dev->inquiry_id) { |
| status = BT_HCI_ERR_COMMAND_DISALLOWED; |
| goto done; |
| } |
| |
| timeout_remove(dev->inquiry_timeout_id); |
| dev->inquiry_timeout_id = 0; |
| timeout_remove(dev->inquiry_id); |
| dev->inquiry_id = 0; |
| |
| status = BT_HCI_ERR_SUCCESS; |
| |
| done: |
| cmd_complete(dev, BT_HCI_CMD_INQUIRY_CANCEL, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_create_conn(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_CREATE_CONN); |
| |
| return 0; |
| } |
| |
| static struct btdev_conn *conn_new(struct btdev *dev, uint16_t handle, |
| uint8_t type) |
| { |
| struct btdev_conn *conn; |
| |
| while ((conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(handle)))) |
| handle++; |
| |
| conn = new0(struct btdev_conn, 1); |
| conn->handle = handle; |
| conn->type = type; |
| conn->dev = dev; |
| |
| if (!queue_push_tail(dev->conns, conn)) { |
| free(conn); |
| return NULL; |
| } |
| |
| return conn; |
| } |
| |
| static struct btdev_conn *conn_link(struct btdev *dev, struct btdev *remote, |
| uint16_t handle, uint8_t type) |
| { |
| struct btdev_conn *conn1, *conn2; |
| |
| conn1 = conn_new(dev, handle, type); |
| if (!conn1) |
| return NULL; |
| |
| conn2 = conn_new(remote, handle, type); |
| if (!conn2) { |
| free(conn1); |
| return NULL; |
| } |
| |
| conn1->link = conn2; |
| conn2->link = conn1; |
| |
| util_debug(dev->debug_callback, dev->debug_data, |
| "conn1 %p handle 0x%04x", conn1, conn1->handle); |
| util_debug(dev->debug_callback, dev->debug_data, |
| "conn2 %p handle 0x%04x", conn2, conn2->handle); |
| |
| return conn1; |
| } |
| |
| static struct btdev_conn *conn_add(struct btdev *dev, |
| const uint8_t *bdaddr, uint8_t bdaddr_type, |
| uint16_t handle, uint8_t type) |
| { |
| struct btdev *remote; |
| |
| remote = find_btdev_by_bdaddr_type(bdaddr, bdaddr_type); |
| if (!remote) |
| return NULL; |
| |
| return conn_link(dev, remote, handle, type); |
| } |
| |
| static struct btdev_conn *conn_add_acl(struct btdev *dev, |
| const uint8_t *bdaddr, uint8_t bdaddr_type) |
| { |
| return conn_add(dev, bdaddr, bdaddr_type, ACL_HANDLE, HCI_ACLDATA_PKT); |
| } |
| |
| static struct btdev_conn *conn_add_sco(struct btdev_conn *acl) |
| { |
| return conn_link(acl->dev, acl->link->dev, SCO_HANDLE, HCI_SCODATA_PKT); |
| } |
| |
| static struct btdev_conn *conn_add_cis(struct btdev_conn *acl, uint16_t handle) |
| { |
| return conn_link(acl->dev, acl->link->dev, handle, HCI_ISODATA_PKT); |
| } |
| |
| static struct btdev_conn *conn_add_bis(struct btdev *dev, uint16_t handle, |
| const struct bt_hci_bis *bis) |
| { |
| struct btdev_conn *conn; |
| |
| conn = conn_new(dev, handle, HCI_ISODATA_PKT); |
| if (!conn) |
| return conn; |
| |
| conn->data = util_memdup(bis, sizeof(*bis)); |
| |
| return conn; |
| } |
| |
| static struct btdev_conn *find_bis_index(const struct btdev *remote, |
| uint8_t index) |
| { |
| struct btdev_conn *conn; |
| const struct queue_entry *entry; |
| |
| for (entry = queue_get_entries(remote->conns); entry; |
| entry = entry->next) { |
| conn = entry->data; |
| |
| /* Skip if not a broadcast */ |
| if (conn->type != HCI_ISODATA_PKT || conn->link) |
| continue; |
| |
| if (!index) |
| return conn; |
| |
| index--; |
| } |
| |
| return NULL; |
| } |
| |
| static struct btdev_conn *conn_link_bis(struct btdev *dev, struct btdev *remote, |
| uint8_t index) |
| { |
| struct btdev_conn *conn; |
| struct btdev_conn *bis; |
| |
| bis = find_bis_index(remote, index); |
| if (!bis) |
| return NULL; |
| |
| conn = conn_add_bis(dev, ISO_HANDLE, bis->data); |
| if (!conn) |
| return NULL; |
| |
| bis->link = conn; |
| conn->link = bis; |
| |
| util_debug(dev->debug_callback, dev->debug_data, |
| "bis %p handle 0x%04x", bis, bis->handle); |
| util_debug(dev->debug_callback, dev->debug_data, |
| "conn %p handle 0x%04x", conn, conn->handle); |
| |
| return conn; |
| } |
| |
| static void pending_conn_add(struct btdev *btdev, struct btdev *remote) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(btdev->pending_conn); ++i) { |
| if (!btdev->pending_conn[i]) { |
| btdev->pending_conn[i] = remote; |
| return; |
| } |
| } |
| } |
| |
| static bool pending_conn_del(struct btdev *btdev, struct btdev *remote) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(btdev->pending_conn); ++i) { |
| if (btdev->pending_conn[i] == remote) { |
| btdev->pending_conn[i] = NULL; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static void conn_complete(struct btdev *btdev, |
| const uint8_t *bdaddr, uint8_t status) |
| { |
| struct bt_hci_evt_conn_complete cc; |
| struct btdev *remote = find_btdev_by_bdaddr(bdaddr); |
| |
| if (!remote) |
| return; |
| |
| if (!status) { |
| struct btdev_conn *conn; |
| |
| conn = conn_add_acl(btdev, bdaddr, BDADDR_BREDR); |
| if (!conn) |
| return; |
| |
| pending_conn_del(conn->link->dev, btdev); |
| |
| cc.status = status; |
| memcpy(cc.bdaddr, btdev->bdaddr, 6); |
| cc.encr_mode = 0x00; |
| |
| cc.handle = cpu_to_le16(conn->link->handle); |
| cc.link_type = 0x01; |
| |
| send_event(conn->link->dev, BT_HCI_EVT_CONN_COMPLETE, &cc, |
| sizeof(cc)); |
| |
| cc.handle = cpu_to_le16(conn->handle); |
| cc.link_type = 0x01; |
| } else { |
| cc.handle = cpu_to_le16(0x0000); |
| cc.link_type = 0x01; |
| } |
| |
| pending_conn_del(btdev, remote); |
| |
| cc.status = status; |
| memcpy(cc.bdaddr, bdaddr, 6); |
| cc.encr_mode = 0x00; |
| |
| send_event(btdev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); |
| } |
| |
| struct page_timeout_data { |
| struct btdev *btdev; |
| uint8_t bdaddr[6]; |
| unsigned int timeout_id; |
| }; |
| |
| static bool page_timeout(void *user_data) |
| { |
| struct page_timeout_data *pt_data = user_data; |
| struct btdev *btdev = pt_data->btdev; |
| const uint8_t *bdaddr = pt_data->bdaddr; |
| |
| timeout_remove(pt_data->timeout_id); |
| pt_data->timeout_id = 0; |
| |
| if (valid_btdev(btdev)) |
| conn_complete(btdev, bdaddr, BT_HCI_ERR_PAGE_TIMEOUT); |
| |
| free(pt_data); |
| return false; |
| } |
| |
| static int cmd_create_conn_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_create_conn *cmd = data; |
| struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| |
| if (remote && remote->scan_enable & 0x02) { |
| struct bt_hci_evt_conn_request cr; |
| |
| memcpy(cr.bdaddr, dev->bdaddr, 6); |
| memcpy(cr.dev_class, dev->dev_class, 3); |
| cr.link_type = 0x01; |
| |
| pending_conn_add(dev, remote); |
| |
| send_event(remote, BT_HCI_EVT_CONN_REQUEST, &cr, sizeof(cr)); |
| } else { |
| struct page_timeout_data *pt_data = |
| new0(struct page_timeout_data, 1); |
| |
| pt_data->btdev = dev; |
| memcpy(pt_data->bdaddr, cmd->bdaddr, 6); |
| |
| /* Send page timeout after 5.12 seconds to emulate real |
| * paging. |
| */ |
| pt_data->timeout_id = timeout_add(5120, |
| page_timeout, |
| pt_data, NULL); |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_add_sco_conn(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_add_sco_conn *cmd = data; |
| struct bt_hci_evt_conn_complete cc; |
| struct btdev_conn *conn; |
| |
| memset(&cc, 0, sizeof(cc)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(cpu_to_le16(cmd->handle))); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| goto done; |
| } |
| |
| conn = conn_add_sco(conn); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; |
| goto done; |
| } |
| |
| cc.status = BT_HCI_ERR_SUCCESS; |
| memcpy(cc.bdaddr, conn->link->dev->bdaddr, 6); |
| cc.handle = cpu_to_le16(conn->handle); |
| cc.link_type = 0x00; |
| cc.encr_mode = 0x00; |
| |
| done: |
| pending_conn_del(dev, conn->link->dev); |
| |
| send_event(dev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); |
| |
| return 0; |
| } |
| |
| static bool match_bdaddr(const void *data, const void *match_data) |
| { |
| const struct btdev_conn *conn = data; |
| const uint8_t *bdaddr = match_data; |
| |
| return !memcmp(conn->link->dev->bdaddr, bdaddr, 6); |
| } |
| |
| static int cmd_create_conn_cancel(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| return 0; |
| } |
| |
| static int cmd_create_conn_cancel_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_create_conn_cancel *cmd = data; |
| struct bt_hci_rsp_create_conn_cancel rp; |
| struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| struct btdev_conn *conn; |
| |
| /* BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 4, Part E page 1848 |
| * |
| * If the connection is already established, and the |
| * HCI_Connection_Complete event has been sent, then the Controller |
| * shall return an HCI_Command_Complete event with the error code |
| * Connection Already Exists (0x0B). If the HCI_Create_Connection_Cancel |
| * command is sent to the Controller without a preceding |
| * HCI_Create_Connection command to the same device, the BR/EDR |
| * Controller shall return an HCI_Command_Complete event with the error |
| * code Unknown Connection Identifier (0x02). |
| */ |
| if (pending_conn_del(dev, remote)) { |
| rp.status = BT_HCI_ERR_SUCCESS; |
| } else { |
| conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); |
| if (conn) |
| rp.status = BT_HCI_ERR_CONN_ALREADY_EXISTS; |
| else |
| rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| } |
| |
| memcpy(rp.bdaddr, cmd->bdaddr, sizeof(rp.bdaddr)); |
| |
| cmd_complete(dev, BT_HCI_CMD_CREATE_CONN_CANCEL, &rp, sizeof(rp)); |
| |
| if (!rp.status) |
| conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); |
| |
| return 0; |
| } |
| |
| static int cmd_accept_conn(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_ACCEPT_CONN_REQUEST); |
| |
| return 0; |
| } |
| |
| static int cmd_accept_conn_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_accept_conn_request *cmd = data; |
| struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| |
| if (!remote) |
| return 0; |
| |
| if (dev->auth_enable || remote->auth_enable) |
| send_event(remote, BT_HCI_EVT_LINK_KEY_REQUEST, dev->bdaddr, 6); |
| else |
| conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_SUCCESS); |
| |
| return 0; |
| } |
| |
| static int cmd_reject_conn(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_REJECT_CONN_REQUEST); |
| |
| return 0; |
| } |
| |
| static int cmd_reject_conn_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_reject_conn_request *cmd = data; |
| |
| conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); |
| |
| return 0; |
| } |
| |
| static int cmd_link_key_reply(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_link_key_request_reply rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, data, 6); |
| cmd_complete(dev, BT_HCI_CMD_LINK_KEY_REQUEST_REPLY, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static void auth_complete(struct btdev_conn *conn, uint8_t status) |
| { |
| struct bt_hci_evt_auth_complete ev; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| ev.handle = conn ? cpu_to_le16(conn->handle) : 0x0000; |
| ev.status = status; |
| |
| send_event(conn->dev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); |
| |
| conn->dev->ssp_status = 0; |
| conn->dev->ssp_auth_complete = false; |
| conn->link->dev->ssp_status = 0; |
| conn->link->dev->ssp_auth_complete = false; |
| } |
| |
| static int cmd_link_key_reply_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_link_key_request_reply *cmd = data; |
| struct btdev_conn *conn; |
| uint8_t status; |
| |
| conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); |
| if (!conn) { |
| status = BT_HCI_ERR_INVALID_PARAMETERS; |
| goto done; |
| } |
| |
| memcpy(dev->link_key, cmd->link_key, 16); |
| |
| if (!memcmp(conn->link->dev->link_key, LINK_KEY_NONE, 16)) { |
| send_event(conn->link->dev, BT_HCI_EVT_LINK_KEY_REQUEST, |
| dev->bdaddr, 6); |
| return 0; |
| } |
| |
| if (!memcmp(dev->link_key, conn->link->dev->link_key, 16)) |
| status = BT_HCI_ERR_SUCCESS; |
| else |
| status = BT_HCI_ERR_AUTH_FAILURE; |
| |
| done: |
| auth_complete(conn, status); |
| |
| if (conn) |
| auth_complete(conn->link, status); |
| |
| return 0; |
| } |
| |
| static int cmd_link_key_neg_reply(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_link_key_request_neg_reply rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, data, 6); |
| cmd_complete(dev, BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static bool use_ssp(struct btdev *btdev1, struct btdev *btdev2) |
| { |
| if (btdev1->auth_enable || btdev2->auth_enable) |
| return false; |
| |
| return (btdev1->simple_pairing_mode && btdev2->simple_pairing_mode); |
| } |
| |
| static int cmd_link_key_neg_reply_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_link_key_request_neg_reply *cmd = data; |
| struct btdev *remote; |
| |
| remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| if (!remote) |
| return 0; |
| |
| if (use_ssp(dev, remote)) { |
| struct bt_hci_evt_io_capability_request io_req; |
| |
| memcpy(io_req.bdaddr, cmd->bdaddr, 6); |
| send_event(dev, BT_HCI_EVT_IO_CAPABILITY_REQUEST, &io_req, |
| sizeof(io_req)); |
| } else { |
| struct bt_hci_evt_pin_code_request pin_req; |
| |
| memcpy(pin_req.bdaddr, cmd->bdaddr, 6); |
| send_event(dev, BT_HCI_EVT_PIN_CODE_REQUEST, &pin_req, |
| sizeof(pin_req)); |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_pin_code_reply(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_pin_code_request_neg_reply rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, data, 6); |
| cmd_complete(dev, BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static uint8_t get_link_key_type(struct btdev *btdev, const uint8_t *bdaddr) |
| { |
| struct btdev_conn *conn; |
| uint8_t auth, unauth; |
| |
| conn = queue_find(btdev->conns, match_bdaddr, bdaddr); |
| if (!conn) |
| return 0x00; |
| |
| if (!btdev->simple_pairing_mode) |
| return 0x00; |
| |
| if (btdev->ssp_debug_mode || conn->link->dev->ssp_debug_mode) |
| return 0x03; |
| |
| if (btdev->secure_conn_support && |
| conn->link->dev->secure_conn_support) { |
| unauth = 0x07; |
| auth = 0x08; |
| } else { |
| unauth = 0x04; |
| auth = 0x05; |
| } |
| |
| if (btdev->io_cap == 0x03 || conn->link->dev->io_cap == 0x03) |
| return unauth; |
| |
| if (!(btdev->auth_req & 0x01) && !(conn->link->dev->auth_req & 0x01)) |
| return unauth; |
| |
| /* DisplayOnly only produces authenticated with KeyboardOnly */ |
| if (btdev->io_cap == 0x00 && conn->link->dev->io_cap != 0x02) |
| return unauth; |
| |
| /* DisplayOnly only produces authenticated with KeyboardOnly */ |
| if (conn->link->dev->io_cap == 0x00 && btdev->io_cap != 0x02) |
| return unauth; |
| |
| return auth; |
| } |
| |
| static void link_key_notify(struct btdev *btdev, const uint8_t *bdaddr, |
| const uint8_t *key) |
| { |
| struct bt_hci_evt_link_key_notify ev; |
| |
| memcpy(btdev->link_key, key, 16); |
| |
| memcpy(ev.bdaddr, bdaddr, 6); |
| memcpy(ev.link_key, key, 16); |
| ev.key_type = get_link_key_type(btdev, bdaddr); |
| |
| send_event(btdev, BT_HCI_EVT_LINK_KEY_NOTIFY, &ev, sizeof(ev)); |
| } |
| |
| static int cmd_pin_code_reply_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_pin_code_request_reply *cmd = data; |
| struct btdev *remote; |
| struct btdev_conn *conn; |
| uint8_t status; |
| |
| conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); |
| if (!conn) { |
| remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| if (!remote) |
| return 0; |
| } else |
| remote = conn->link->dev; |
| |
| memcpy(dev->pin, cmd->pin_code, cmd->pin_len); |
| dev->pin_len = cmd->pin_len; |
| |
| if (!remote->pin_len) { |
| struct bt_hci_evt_pin_code_request pin_req; |
| |
| memcpy(pin_req.bdaddr, dev->bdaddr, 6); |
| send_event(remote, BT_HCI_EVT_PIN_CODE_REQUEST, |
| &pin_req, sizeof(pin_req)); |
| return 0; |
| } |
| |
| if (dev->pin_len == remote->pin_len && |
| !memcmp(dev->pin, remote->pin, dev->pin_len)) { |
| link_key_notify(dev, remote->bdaddr, LINK_KEY_DUMMY); |
| link_key_notify(remote, dev->bdaddr, LINK_KEY_DUMMY); |
| status = BT_HCI_ERR_SUCCESS; |
| } else { |
| status = BT_HCI_ERR_AUTH_FAILURE; |
| } |
| |
| if (conn) |
| auth_complete(conn->link, status); |
| else |
| conn_complete(remote, dev->bdaddr, status); |
| |
| dev->pin_len = 0; |
| remote->pin_len = 0; |
| |
| return 0; |
| } |
| |
| static int cmd_pin_code_neg_reply(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_pin_code_request_neg_reply rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, data, 6); |
| cmd_complete(dev, BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_pin_code_neg_reply_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_pin_code_request_neg_reply *cmd = data; |
| struct btdev *remote; |
| struct btdev_conn *conn; |
| uint8_t status; |
| |
| remote = find_btdev_by_bdaddr(cmd->bdaddr); |
| if (!remote) |
| return 0; |
| |
| status = BT_HCI_ERR_PIN_OR_KEY_MISSING; |
| |
| conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); |
| if (conn) |
| auth_complete(conn, status); |
| else |
| conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_PIN_OR_KEY_MISSING); |
| |
| if (conn) { |
| if (remote->pin_len) |
| auth_complete(conn->link, status); |
| } else { |
| conn_complete(remote, dev->bdaddr, |
| BT_HCI_ERR_PIN_OR_KEY_MISSING); |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_auth_requested(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_AUTH_REQUESTED); |
| |
| return 0; |
| } |
| |
| static int cmd_auth_requested_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_auth_requested *cmd = data; |
| struct btdev_conn *conn; |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (!conn) { |
| struct bt_hci_evt_auth_complete ev; |
| |
| ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| ev.handle = cpu_to_le16(cmd->handle); |
| |
| send_event(dev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); |
| |
| return 0; |
| } |
| |
| dev->auth_init = true; |
| |
| send_event(dev, BT_HCI_EVT_LINK_KEY_REQUEST, conn->link->dev->bdaddr, |
| sizeof(conn->link->dev->bdaddr)); |
| |
| return 0; |
| } |
| |
| static int cmd_set_conn_encrypt(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_SET_CONN_ENCRYPT); |
| |
| return 0; |
| } |
| |
| static void encrypt_change(struct btdev_conn *conn, uint8_t mode, |
| uint8_t status) |
| { |
| struct bt_hci_evt_encrypt_change ev; |
| |
| if (!conn) |
| return; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| ev.status = status; |
| ev.handle = cpu_to_le16(conn->handle); |
| ev.encr_mode = mode; |
| |
| send_event(conn->dev, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); |
| } |
| |
| static int cmd_set_conn_encrypt_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_set_conn_encrypt *cmd = data; |
| struct btdev_conn *conn; |
| uint8_t mode; |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (!conn) |
| return 0; |
| |
| if (!cmd->encr_mode) |
| mode = 0x00; |
| else if (dev->secure_conn_support && |
| conn->link->dev->secure_conn_support) |
| mode = 0x02; |
| else |
| mode = 0x01; |
| |
| encrypt_change(conn, mode, BT_HCI_ERR_SUCCESS); |
| encrypt_change(conn->link, mode, BT_HCI_ERR_SUCCESS); |
| |
| return 0; |
| } |
| |
| static int cmd_remote_name(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_REMOTE_NAME_REQUEST); |
| |
| return 0; |
| } |
| |
| static void name_request_complete(struct btdev *btdev, |
| const uint8_t *bdaddr, uint8_t status) |
| { |
| struct bt_hci_evt_remote_name_request_complete nc; |
| |
| nc.status = status; |
| memcpy(nc.bdaddr, bdaddr, 6); |
| memset(nc.name, 0, 248); |
| |
| if (!status) { |
| struct btdev *remote = find_btdev_by_bdaddr(bdaddr); |
| |
| if (remote) |
| memcpy(nc.name, remote->name, 248); |
| else |
| nc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| } |
| |
| send_event(btdev, BT_HCI_EVT_REMOTE_NAME_REQUEST_COMPLETE, |
| &nc, sizeof(nc)); |
| } |
| |
| static int cmd_remote_name_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_remote_name_request *cmd = data; |
| |
| name_request_complete(dev, cmd->bdaddr, BT_HCI_ERR_SUCCESS); |
| |
| return 0; |
| } |
| |
| static int cmd_remote_name_cancel(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_remote_name_request_cancel *cmd = data; |
| struct bt_hci_rsp_remote_name_request_cancel rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.bdaddr, cmd->bdaddr, 6); |
| cmd_complete(dev, BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_remote_name_cancel_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_remote_name_request_cancel *cmd = data; |
| |
| name_request_complete(dev, cmd->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); |
| |
| return 0; |
| } |
| |
| static int cmd_read_remote_features(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_REMOTE_FEATURES); |
| |
| return 0; |
| } |
| |
| static int cmd_read_remote_features_complete(struct btdev *dev, |
| const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_read_remote_features *cmd = data; |
| struct bt_hci_evt_remote_features_complete rfc; |
| struct btdev_conn *conn; |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (conn) { |
| rfc.status = BT_HCI_ERR_SUCCESS; |
| rfc.handle = cpu_to_le16(cmd->handle); |
| memcpy(rfc.features, conn->link->dev->features, 8); |
| } else { |
| rfc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| rfc.handle = cpu_to_le16(cmd->handle); |
| memset(rfc.features, 0, 8); |
| } |
| |
| send_event(dev, BT_HCI_EVT_REMOTE_FEATURES_COMPLETE, &rfc, sizeof(rfc)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_remote_ext_features(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, |
| BT_HCI_CMD_READ_REMOTE_EXT_FEATURES); |
| |
| return 0; |
| } |
| |
| static void btdev_get_host_features(struct btdev *btdev, uint8_t features[8]) |
| { |
| memset(features, 0, 8); |
| if (btdev->simple_pairing_mode) |
| features[0] |= 0x01; |
| if (btdev->le_supported) |
| features[0] |= 0x02; |
| if (btdev->le_simultaneous) |
| features[0] |= 0x04; |
| if (btdev->secure_conn_support) |
| features[0] |= 0x08; |
| } |
| |
| static int cmd_read_remote_ext_features_compl(struct btdev *dev, |
| const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_read_remote_ext_features *cmd = data; |
| struct bt_hci_evt_remote_ext_features_complete ev; |
| struct btdev_conn *conn; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (conn && cmd->page < 0x02) { |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.page = cmd->page; |
| ev.max_page = 0x01; |
| |
| switch (cmd->page) { |
| case 0x00: |
| ev.status = BT_HCI_ERR_SUCCESS; |
| memcpy(ev.features, conn->link->dev->features, 8); |
| break; |
| case 0x01: |
| ev.status = BT_HCI_ERR_SUCCESS; |
| btdev_get_host_features(conn->link->dev, ev.features); |
| break; |
| default: |
| ev.status = BT_HCI_ERR_INVALID_PARAMETERS; |
| memset(ev.features, 0, 8); |
| break; |
| } |
| } else { |
| ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.page = cmd->page; |
| ev.max_page = 0x01; |
| memset(ev.features, 0, 8); |
| } |
| |
| send_event(dev, BT_HCI_EVT_REMOTE_EXT_FEATURES_COMPLETE, &ev, |
| sizeof(ev)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_clock_offset(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_CLOCK_OFFSET); |
| |
| return 0; |
| } |
| |
| static int cmd_read_clock_offset_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_read_clock_offset *cmd = data; |
| struct bt_hci_evt_clock_offset_complete ev; |
| struct btdev_conn *conn; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (conn) { |
| ev.status = BT_HCI_ERR_SUCCESS; |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.clock_offset = 0; |
| } else { |
| ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| ev.handle = cpu_to_le16(cmd->handle); |
| ev.clock_offset = 0; |
| } |
| |
| send_event(dev, BT_HCI_EVT_CLOCK_OFFSET_COMPLETE, &ev, sizeof(ev)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_link_policy(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_default_link_policy rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.policy = cpu_to_le16(dev->default_link_policy); |
| cmd_complete(dev, BT_HCI_CMD_READ_DEFAULT_LINK_POLICY, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_link_policy(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_default_link_policy *cmd = data; |
| uint8_t status; |
| |
| dev->default_link_policy = le16_to_cpu(cmd->policy); |
| status = BT_HCI_ERR_SUCCESS; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_set_event_filter(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_set_event_filter *cmd = data; |
| uint8_t status; |
| |
| dev->event_filter = cmd->type; |
| status = BT_HCI_ERR_SUCCESS; |
| cmd_complete(dev, BT_HCI_CMD_SET_EVENT_FILTER, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_link_key(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_stored_link_key rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.max_num_keys = cpu_to_le16(0); |
| rsp.num_keys = cpu_to_le16(0); |
| cmd_complete(dev, BT_HCI_CMD_READ_STORED_LINK_KEY, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_link_key(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_write_stored_link_key rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.num_keys = 0; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_STORED_LINK_KEY, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_delete_link_key(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_delete_stored_link_key rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.num_keys = cpu_to_le16(0); |
| cmd_complete(dev, BT_HCI_CMD_DELETE_STORED_LINK_KEY, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_local_name(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_name rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.name, dev->name, 248); |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_NAME, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_local_name(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_local_name *cmd = data; |
| uint8_t status; |
| |
| memcpy(dev->name, cmd->name, 248); |
| status = BT_HCI_ERR_SUCCESS; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_LOCAL_NAME, &status, sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_accept_timeout(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_conn_accept_timeout rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.timeout = cpu_to_le16(dev->conn_accept_timeout); |
| cmd_complete(dev, BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_accept_timeout(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_conn_accept_timeout *cmd = data; |
| uint8_t status; |
| |
| dev->conn_accept_timeout = le16_to_cpu(cmd->timeout); |
| status = BT_HCI_ERR_SUCCESS; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_page_timeout(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_page_timeout rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.timeout = cpu_to_le16(dev->page_timeout); |
| cmd_complete(dev, BT_HCI_CMD_READ_PAGE_TIMEOUT, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_page_timeout(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_page_timeout *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->page_timeout = le16_to_cpu(cmd->timeout); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_TIMEOUT, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_scan_enable(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_scan_enable rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.enable = dev->scan_enable; |
| cmd_complete(dev, BT_HCI_CMD_READ_SCAN_ENABLE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_scan_enable(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_scan_enable *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->scan_enable = cmd->enable; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_SCAN_ENABLE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_page_scan(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_page_scan_activity rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.interval = cpu_to_le16(dev->page_scan_interval); |
| rsp.window = cpu_to_le16(dev->page_scan_window); |
| cmd_complete(dev, BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_page_scan(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_page_scan_activity *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->page_scan_interval = le16_to_cpu(cmd->interval); |
| dev->page_scan_window = le16_to_cpu(cmd->window); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_inquiry_scan(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_inquiry_scan_activity rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.interval = cpu_to_le16(dev->inquiry_scan_interval); |
| rsp.window = cpu_to_le16(dev->inquiry_scan_window); |
| cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_inquiry_scan(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_inquiry_scan_activity *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->inquiry_scan_interval = le16_to_cpu(cmd->interval); |
| dev->inquiry_scan_window = le16_to_cpu(cmd->window); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_auth_enable(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_auth_enable rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.enable = dev->auth_enable; |
| cmd_complete(dev, BT_HCI_CMD_READ_AUTH_ENABLE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_auth_enable(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_auth_enable *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->auth_enable = cmd->enable; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_AUTH_ENABLE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_class(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_class_of_dev rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| memcpy(rsp.dev_class, dev->dev_class, 3); |
| cmd_complete(dev, BT_HCI_CMD_READ_CLASS_OF_DEV, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_class(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_write_class_of_dev *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| memcpy(dev->dev_class, cmd->dev_class, 3); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_CLASS_OF_DEV, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_voice(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_voice_setting rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.setting = cpu_to_le16(dev->voice_setting); |
| cmd_complete(dev, BT_HCI_CMD_READ_VOICE_SETTING, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_voice(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_write_voice_setting *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->voice_setting = le16_to_cpu(cmd->setting); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_VOICE_SETTING, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_tx_power_level(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_read_tx_power *cmd = data; |
| struct bt_hci_rsp_read_tx_power rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.handle = le16_to_cpu(cmd->handle); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| if (cmd->type) |
| rsp.level = 4; |
| else |
| rsp.level = -1; |
| cmd_complete(dev, BT_HCI_CMD_READ_TX_POWER, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_num_iac(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_num_supported_iac rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.num_iac = 0x01; |
| cmd_complete(dev, BT_HCI_CMD_READ_NUM_SUPPORTED_IAC, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_current_iac_lap(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_current_iac_lap *rsp; |
| |
| rsp = alloca(sizeof(*rsp) + 3); |
| rsp->status = BT_HCI_ERR_SUCCESS; |
| rsp->num_iac = 0x01; |
| rsp->iac_lap[0] = 0x33; |
| rsp->iac_lap[1] = 0x8b; |
| rsp->iac_lap[2] = 0x9e; |
| cmd_complete(dev, BT_HCI_CMD_READ_CURRENT_IAC_LAP, rsp, |
| sizeof(*rsp) + 3); |
| |
| return 0; |
| } |
| |
| static int cmd_write_current_iac_lap(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| cmd_complete(dev, BT_HCI_CMD_WRITE_CURRENT_IAC_LAP, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_inquiry_mode(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_inquiry_mode rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.mode = dev->inquiry_mode; |
| cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_MODE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_inquiry_mode(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_inquiry_mode *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->inquiry_mode = cmd->mode; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_INQUIRY_MODE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_page_scan_type(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_page_scan_type rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.type = dev->page_scan_type; |
| cmd_complete(dev, BT_HCI_CMD_READ_PAGE_SCAN_TYPE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_page_scan_type(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_page_scan_type *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->page_scan_type = cmd->type; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_afh_mode(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_afh_assessment_mode rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.mode = dev->afh_assessment_mode; |
| cmd_complete(dev, BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_afh_mode(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_write_afh_assessment_mode *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->afh_assessment_mode = cmd->mode; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_local_ext_features(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_ext_features rsp; |
| uint8_t page = ((const uint8_t *) data)[0]; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.page = page; |
| rsp.max_page = dev->max_page; |
| |
| if (page > dev->max_page) { |
| rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; |
| goto done; |
| } |
| |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| |
| switch (page) { |
| case 0x00: |
| memcpy(rsp.features, dev->features, 8); |
| break; |
| case 0x01: |
| btdev_get_host_features(dev, rsp.features); |
| break; |
| case 0x02: |
| memcpy(rsp.features, dev->feat_page_2, 8); |
| break; |
| default: |
| rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; |
| break; |
| } |
| |
| done: |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_EXT_FEATURES, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_country_code(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_country_code rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.code = dev->country_code; |
| cmd_complete(dev, BT_HCI_CMD_READ_COUNTRY_CODE, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_rssi(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_read_rssi *cmd = data; |
| struct bt_hci_rsp_read_rssi rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.handle = le16_to_cpu(cmd->handle); |
| rsp.rssi = -1; |
| cmd_complete(dev, BT_HCI_CMD_READ_RSSI, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_clock(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_read_clock *cmd = data; |
| struct bt_hci_rsp_read_clock rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.handle = le16_to_cpu(cmd->handle); |
| rsp.clock = 0x11223344; |
| rsp.accuracy = 0x5566; |
| cmd_complete(dev, BT_HCI_CMD_READ_CLOCK, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_enable_dut_mode(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| cmd_complete(dev, BT_HCI_CMD_ENABLE_DUT_MODE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| #define CMD_COMMON_BREDR_20 \ |
| CMD(BT_HCI_CMD_INQUIRY, cmd_inquiry, cmd_inquiry_complete), \ |
| CMD(BT_HCI_CMD_INQUIRY_CANCEL, cmd_inquiry_cancel, NULL), \ |
| CMD(BT_HCI_CMD_CREATE_CONN, cmd_create_conn, \ |
| cmd_create_conn_complete), \ |
| CMD(BT_HCI_CMD_ADD_SCO_CONN, cmd_add_sco_conn, NULL), \ |
| CMD(BT_HCI_CMD_CREATE_CONN_CANCEL, cmd_create_conn_cancel, \ |
| cmd_create_conn_cancel_complete), \ |
| CMD(BT_HCI_CMD_ACCEPT_CONN_REQUEST, cmd_accept_conn, \ |
| cmd_accept_conn_complete), \ |
| CMD(BT_HCI_CMD_REJECT_CONN_REQUEST, cmd_reject_conn, \ |
| cmd_reject_conn_complete), \ |
| CMD(BT_HCI_CMD_LINK_KEY_REQUEST_REPLY, cmd_link_key_reply, \ |
| cmd_link_key_reply_complete), \ |
| CMD(BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, \ |
| cmd_link_key_neg_reply, \ |
| cmd_link_key_neg_reply_complete), \ |
| CMD(BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, cmd_pin_code_reply, \ |
| cmd_pin_code_reply_complete), \ |
| CMD(BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, \ |
| cmd_pin_code_neg_reply, \ |
| cmd_pin_code_neg_reply_complete), \ |
| CMD(BT_HCI_CMD_AUTH_REQUESTED, cmd_auth_requested, \ |
| cmd_auth_requested_complete), \ |
| CMD(BT_HCI_CMD_SET_CONN_ENCRYPT, cmd_set_conn_encrypt, \ |
| cmd_set_conn_encrypt_complete), \ |
| CMD(BT_HCI_CMD_REMOTE_NAME_REQUEST, cmd_remote_name, \ |
| cmd_remote_name_complete), \ |
| CMD(BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL, cmd_remote_name_cancel, \ |
| cmd_remote_name_cancel_complete), \ |
| CMD(BT_HCI_CMD_READ_REMOTE_FEATURES, cmd_read_remote_features, \ |
| cmd_read_remote_features_complete), \ |
| CMD(BT_HCI_CMD_READ_REMOTE_EXT_FEATURES, \ |
| cmd_read_remote_ext_features, \ |
| cmd_read_remote_ext_features_compl), \ |
| CMD(BT_HCI_CMD_READ_CLOCK_OFFSET, cmd_read_clock_offset, \ |
| cmd_read_clock_offset_complete), \ |
| CMD(BT_HCI_CMD_READ_DEFAULT_LINK_POLICY, cmd_read_link_policy, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY, cmd_write_link_policy, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_SET_EVENT_FILTER, cmd_set_event_filter, NULL), \ |
| CMD(BT_HCI_CMD_READ_STORED_LINK_KEY, cmd_read_link_key, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_STORED_LINK_KEY, cmd_write_link_key, NULL), \ |
| CMD(BT_HCI_CMD_DELETE_STORED_LINK_KEY, cmd_delete_link_key, NULL), \ |
| CMD(BT_HCI_CMD_READ_LOCAL_NAME, cmd_read_local_name, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_LOCAL_NAME, cmd_write_local_name, NULL), \ |
| CMD(BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT, cmd_read_accept_timeout, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT, cmd_write_accept_timeout, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_READ_PAGE_TIMEOUT, cmd_read_page_timeout, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_PAGE_TIMEOUT, cmd_write_page_timeout, NULL), \ |
| CMD(BT_HCI_CMD_READ_SCAN_ENABLE, cmd_read_scan_enable, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_SCAN_ENABLE, cmd_write_scan_enable, NULL), \ |
| CMD(BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY, cmd_read_page_scan, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY, cmd_write_page_scan, NULL), \ |
| CMD(BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY, cmd_read_inquiry_scan, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY, cmd_write_inquiry_scan, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_READ_AUTH_ENABLE, cmd_read_auth_enable, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_AUTH_ENABLE, cmd_write_auth_enable, NULL), \ |
| CMD(BT_HCI_CMD_READ_CLASS_OF_DEV, cmd_read_class, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_CLASS_OF_DEV, cmd_write_class, NULL), \ |
| CMD(BT_HCI_CMD_READ_VOICE_SETTING, cmd_read_voice, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_VOICE_SETTING, cmd_write_voice, NULL), \ |
| CMD(BT_HCI_CMD_READ_TX_POWER, cmd_read_tx_power_level, NULL), \ |
| CMD(BT_HCI_CMD_READ_NUM_SUPPORTED_IAC, cmd_read_num_iac, NULL), \ |
| CMD(BT_HCI_CMD_READ_CURRENT_IAC_LAP, cmd_read_current_iac_lap, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_WRITE_CURRENT_IAC_LAP, cmd_write_current_iac_lap, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_READ_INQUIRY_MODE, cmd_read_inquiry_mode, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_INQUIRY_MODE, cmd_write_inquiry_mode, NULL), \ |
| CMD(BT_HCI_CMD_READ_PAGE_SCAN_TYPE, cmd_read_page_scan_type, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE, cmd_write_page_scan_type, NULL), \ |
| CMD(BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE, cmd_read_afh_mode, NULL), \ |
| CMD(BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE, cmd_write_afh_mode, NULL), \ |
| CMD(BT_HCI_CMD_READ_LOCAL_EXT_FEATURES, cmd_read_local_ext_features, \ |
| NULL), \ |
| CMD(BT_HCI_CMD_READ_COUNTRY_CODE, cmd_read_country_code, NULL), \ |
| CMD(BT_HCI_CMD_READ_RSSI, cmd_read_rssi, NULL), \ |
| CMD(BT_HCI_CMD_READ_CLOCK, cmd_read_clock, NULL), \ |
| CMD(BT_HCI_CMD_ENABLE_DUT_MODE, cmd_enable_dut_mode, NULL) |
| |
| static void set_common_commands_bredr20(struct btdev *btdev) |
| { |
| btdev->commands[0] |= 0x01; /* Inquiry */ |
| btdev->commands[0] |= 0x02; /* Inquiry Cancel */ |
| btdev->commands[0] |= 0x10; /* Create Connection */ |
| btdev->commands[0] |= 0x40; /* Add SCO Connection */ |
| btdev->commands[0] |= 0x80; /* Cancel Create Connection */ |
| btdev->commands[1] |= 0x01; /* Accept Connection Request */ |
| btdev->commands[1] |= 0x02; /* Reject Connection Request */ |
| btdev->commands[1] |= 0x04; /* Link Key Request Reply */ |
| btdev->commands[1] |= 0x08; /* Link Key Request Negative Reply */ |
| btdev->commands[1] |= 0x10; /* PIN Code Request Reply */ |
| btdev->commands[1] |= 0x20; /* PIN Code Request Negative Reply */ |
| btdev->commands[1] |= 0x80; /* Authentication Requested */ |
| btdev->commands[2] |= 0x01; /* Set Connection Encryption */ |
| btdev->commands[2] |= 0x08; /* Remote Name Request */ |
| btdev->commands[2] |= 0x10; /* Cancel Remote Name Request */ |
| btdev->commands[2] |= 0x20; /* Read Remote Supported Features */ |
| btdev->commands[2] |= 0x40; /* Read Remote Extended Features */ |
| btdev->commands[3] |= 0x01; /* Read Clock Offset */ |
| btdev->commands[5] |= 0x08; /* Read Default Link Policy */ |
| btdev->commands[5] |= 0x10; /* Write Default Link Policy */ |
| btdev->commands[6] |= 0x01; /* Set Event Filter */ |
| btdev->commands[6] |= 0x20; /* Read Stored Link Key */ |
| btdev->commands[6] |= 0x40; /* Write Stored Link Key */ |
| btdev->commands[6] |= 0x80; /* Delete Stored Link Key */ |
| btdev->commands[7] |= 0x01; /* Write Local Name */ |
| btdev->commands[7] |= 0x02; /* Read Local Name */ |
| btdev->commands[7] |= 0x04; /* Read Connection Accept Timeout */ |
| btdev->commands[7] |= 0x08; /* Write Connection Accept Timeout */ |
| btdev->commands[7] |= 0x10; /* Read Page Timeout */ |
| btdev->commands[7] |= 0x20; /* Write Page Timeout */ |
| btdev->commands[7] |= 0x40; /* Read Scan Enable */ |
| btdev->commands[7] |= 0x80; /* Write Scan Enable */ |
| btdev->commands[8] |= 0x01; /* Read Page Scan Activity */ |
| btdev->commands[8] |= 0x02; /* Write Page Scan Activity */ |
| btdev->commands[8] |= 0x04; /* Read Inquiry Scan Activity */ |
| btdev->commands[8] |= 0x08; /* Write Inquiry Scan Activity */ |
| btdev->commands[8] |= 0x10; /* Read Authentication Enable */ |
| btdev->commands[8] |= 0x20; /* Write Authentication Enable */ |
| btdev->commands[9] |= 0x01; /* Read Class Of Device */ |
| btdev->commands[9] |= 0x02; /* Write Class Of Device */ |
| btdev->commands[9] |= 0x04; /* Read Voice Setting */ |
| btdev->commands[9] |= 0x08; /* Write Voice Setting */ |
| btdev->commands[10] |= 0x04; /* Read TX Power Level */ |
| btdev->commands[11] |= 0x04; /* Read Number of Supported IAC */ |
| btdev->commands[11] |= 0x08; /* Read Current IAC LAP */ |
| btdev->commands[11] |= 0x10; /* Write Current IAC LAP */ |
| btdev->commands[12] |= 0x40; /* Read Inquiry Mode */ |
| btdev->commands[12] |= 0x80; /* Write Inquiry Mode */ |
| btdev->commands[13] |= 0x01; /* Read Page Scan Type */ |
| btdev->commands[13] |= 0x02; /* Write Page Scan Type */ |
| btdev->commands[13] |= 0x04; /* Read AFH Assess Mode */ |
| btdev->commands[13] |= 0x08; /* Write AFH Assess Mode */ |
| btdev->commands[14] |= 0x40; /* Read Local Extended Features */ |
| btdev->commands[15] |= 0x01; /* Read Country Code */ |
| btdev->commands[15] |= 0x20; /* Read RSSI */ |
| btdev->commands[15] |= 0x80; /* Read Clock */ |
| btdev->commands[16] |= 0x04; /* Enable Device Under Test Mode */ |
| } |
| |
| static int cmd_enhanced_setup_sync_conn(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| if (cmd->tx_coding_format[0] > 5) |
| status = BT_HCI_ERR_INVALID_PARAMETERS; |
| |
| cmd_status(dev, status, BT_HCI_CMD_ENHANCED_SETUP_SYNC_CONN); |
| |
| return 0; |
| } |
| |
| static int cmd_enhanced_setup_sync_conn_complete(struct btdev *dev, |
| const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = data; |
| struct bt_hci_evt_sync_conn_complete cc; |
| struct btdev_conn *conn; |
| |
| memset(&cc, 0, sizeof(cc)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| goto done; |
| } |
| |
| conn = conn_add_sco(conn); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; |
| goto done; |
| } |
| |
| /* TODO: HCI_Connection_Request connection flow */ |
| |
| cc.status = BT_HCI_ERR_SUCCESS; |
| memcpy(cc.bdaddr, conn->link->dev->bdaddr, 6); |
| |
| cc.handle = cpu_to_le16(conn->handle); |
| cc.link_type = 0x02; |
| cc.tx_interval = 0x000c; |
| cc.retrans_window = 0x06; |
| cc.rx_pkt_len = 60; |
| cc.tx_pkt_len = 60; |
| cc.air_mode = cmd->tx_coding_format[0]; |
| |
| done: |
| send_event(dev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); |
| |
| return 0; |
| } |
| |
| static int cmd_setup_sync_conn(struct btdev *dev, const void *data, uint8_t len) |
| { |
| cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_SETUP_SYNC_CONN); |
| |
| return 0; |
| } |
| |
| static int cmd_setup_sync_conn_complete(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_setup_sync_conn *cmd = data; |
| struct bt_hci_evt_sync_conn_complete cc; |
| struct btdev_conn *conn; |
| |
| memset(&cc, 0, sizeof(cc)); |
| |
| conn = queue_find(dev->conns, match_handle, |
| UINT_TO_PTR(le16_to_cpu(cmd->handle))); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| goto done; |
| } |
| |
| conn = conn_add_sco(conn); |
| if (!conn) { |
| cc.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; |
| goto done; |
| } |
| |
| cc.status = BT_HCI_ERR_SUCCESS; |
| memcpy(cc.bdaddr, conn->link->dev->bdaddr, 6); |
| |
| cc.handle = cpu_to_le16(conn->handle); |
| cc.link_type = 0x02; |
| cc.tx_interval = 0x000c; |
| cc.retrans_window = 0x06; |
| cc.rx_pkt_len = 60; |
| cc.tx_pkt_len = 60; |
| cc.air_mode = (cmd->voice_setting == 0x0060) ? 0x02 : 0x03; |
| |
| done: |
| send_event(dev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_ext_inquiry(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_ext_inquiry_response rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.fec = dev->ext_inquiry_fec; |
| memcpy(rsp.data, dev->ext_inquiry_rsp, 240); |
| cmd_complete(dev, BT_HCI_CMD_READ_EXT_INQUIRY_RESPONSE, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_ext_inquiry(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| const struct bt_hci_cmd_write_ext_inquiry_response *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->ext_inquiry_fec = cmd->fec; |
| memcpy(dev->ext_inquiry_rsp, cmd->data, 240); |
| cmd_complete(dev, BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_ssp_mode(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_simple_pairing_mode rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.mode = dev->simple_pairing_mode; |
| cmd_complete(dev, BT_HCI_CMD_READ_SIMPLE_PAIRING_MODE, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_ssp_mode(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_write_simple_pairing_mode *cmd = data; |
| uint8_t status = BT_HCI_ERR_SUCCESS; |
| |
| dev->simple_pairing_mode = cmd->mode; |
| cmd_complete(dev, BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, &status, |
| sizeof(status)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_oob_data(struct btdev *dev, const void *data, uint8_t len) |
| { |
| struct bt_hci_rsp_read_local_oob_data rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_OOB_DATA, &rsp, sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_read_inquiry_tx_power(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| struct bt_hci_rsp_read_inquiry_resp_tx_power rsp; |
| |
| memset(&rsp, 0, sizeof(rsp)); |
| rsp.status = BT_HCI_ERR_SUCCESS; |
| rsp.level = 0; |
| cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER, &rsp, |
| sizeof(rsp)); |
| |
| return 0; |
| } |
| |
| static int cmd_write_inquiry_tx_power(struct btdev *dev, const void *data, |
| uint8_t len) |
| { |
| return -ENOTSUP; |
| } |
| |
| static int cmd_io_cap_reply(struct btdev *dev, const void *data, uint8_t len) |
| { |
| const struct bt_hci_cmd_io_capability_request_reply *cmd = data; |
| struct bt_hci_evt_io_capability_response ev; |
| struct bt_hci_rsp_io_capability_request_reply rsp; |
| struct btdev_conn *conn; |
| uint8_t status; |
| |
| conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); |
| if (!conn) { |
| status = BT_HCI_ERR_UNKNOWN_CONN_ID; |
| goto done; |
| } |
| |
| status = BT_HCI_ERR_SUCCESS; |
| |
| dev->io_cap = cmd->capability; |
| dev->auth_req = cmd->authentication; |
| |
| memcpy(ev.bdaddr, dev->bdaddr, 6); |
| ev.capability = cmd->capability; |
| ev.oob_data = cmd->oob_data; |
| ev.authentication = cmd->authentication; |
| |
| send_event(conn->link->dev, BT_HCI_EVT_IO_CAPABILITY_RESPONSE, &ev, |
| sizeof(ev)); |
| |
| if (conn->link->dev->io_cap) { |
| struct bt_hci_evt_user_confirm_request cfm; |
| |
| memcpy(cfm.bdaddr, dev->bdaddr, 6); |
| cfm.passkey = 0; |
| |
| send_event(conn->link->dev, BT_HCI_EVT_USER_CONFIRM_REQUEST, |
| &cfm, sizeof(cfm)); |
| |
| memcpy(cfm.bdaddr, cmd->bdaddr, 6); |
| send_event(dev, BT_HCI_EVT_USER_CONFIRM_REQUEST, |
|
|