forked from Minki/linux
bluetooth-next pull request for net-next:
- Add support for MediaTek MT7922 and MT7921 - Enable support for AOSP extention in Qualcomm WCN399x and Realtek 8822C/8852A. - Add initial support for link quality and audio/codec offload. - Rework of sockets sendmsg to avoid locking issues. - Add vhci suspend/resume emulation. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEE7E6oRXp8w05ovYr/9JCA4xAyCykFAmFXkGYZHGx1aXoudm9u LmRlbnR6QGludGVsLmNvbQAKCRD0kIDjEDILKVQdD/9dtVeMRjzAQbvPI5InAi4N AjEy7IUAV27AE0QVZW/Q201BNnFixAivDWiQXHFiTV1ocrmX/qiW15AsKOTlpVKx BAzy97KVLPHNoNBN3XV9PwP8OovX7zkWTL/XPHzg1lIAfniWiInuQDrlU/F3TKOO 2yJOFTy3x9RrwZcXfZUHltBo6766SC40zW4H+3WA42jljOPKXR1jH6lSIzSezBFt qsaw/CS/aW1Z8JAA8fhZurCmoljHMgRNOsnh8AfHPCYsUZSsw9ZE6wMrDUvjXBtm Zp9pI+h3mwc9tW/BGSZSpcktUDdXlxo9cXSCrXtxHjmrUxAsNGtHmXE7adogWSHs PaXfst8qFdsqp+kjrx+ZbMksfhhq2/ysYNIFWvSGim3VBBw5x74tu/VebSw19PjC 1ZWzOt/5J5WCBD6BTGApCQg+YTg1u9koPRG441ZwIJ82eYgIQx80Y78uj/tG7mUH HC3GMxEwgQzYnQsDfDI936umNAgcdGw0DL7Tu71z4zM+Tn0WcvcQgHNk42zN5gZI XMeulxdj++pUoBYweDhlgJ88lr+gTeVwvfrYGWHpNvKsfmEcQJoqBvxTS1OJvMM5 WfTwJgJCG+o4Jfj4oH2haUeMEUFQXikV0C7Dlw6MmhmcLmzhpbaMRdLQvcDocbTA hxhxSYhK6LQTRWv2h71MMw== =QRkQ -----END PGP SIGNATURE----- Merge tag 'for-net-next-2021-10-01' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next Luiz Augusto von Dentz says: ==================== bluetooth-next pull request for net-next: - Add support for MediaTek MT7922 and MT7921 - Enable support for AOSP extention in Qualcomm WCN399x and Realtek 8822C/8852A. - Add initial support for link quality and audio/codec offload. - Rework of sockets sendmsg to avoid locking issues. - Add vhci suspend/resume emulation. ==================== Link: https://lore.kernel.org/r/20211001230850.3635543-1-luiz.dentz@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
d0f1c248b4
@ -1037,8 +1037,9 @@ static bool btintel_firmware_version(struct hci_dev *hdev,
|
||||
|
||||
params = (void *)(fw_ptr + sizeof(*cmd));
|
||||
|
||||
bt_dev_info(hdev, "Boot Address: 0x%x",
|
||||
le32_to_cpu(params->boot_addr));
|
||||
*boot_addr = le32_to_cpu(params->boot_addr);
|
||||
|
||||
bt_dev_info(hdev, "Boot Address: 0x%x", *boot_addr);
|
||||
|
||||
bt_dev_info(hdev, "Firmware Version: %u-%u.%u",
|
||||
params->fw_build_num, params->fw_build_ww,
|
||||
@ -1071,9 +1072,6 @@ int btintel_download_firmware(struct hci_dev *hdev,
|
||||
/* Skip version checking */
|
||||
break;
|
||||
default:
|
||||
/* Skip reading firmware file version in bootloader mode */
|
||||
if (ver->fw_variant == 0x06)
|
||||
break;
|
||||
|
||||
/* Skip download if firmware has the same version */
|
||||
if (btintel_firmware_version(hdev, ver->fw_build_num,
|
||||
@ -1114,19 +1112,16 @@ static int btintel_download_fw_tlv(struct hci_dev *hdev,
|
||||
int err;
|
||||
u32 css_header_ver;
|
||||
|
||||
/* Skip reading firmware file version in bootloader mode */
|
||||
if (ver->img_type != 0x01) {
|
||||
/* Skip download if firmware has the same version */
|
||||
if (btintel_firmware_version(hdev, ver->min_fw_build_nn,
|
||||
ver->min_fw_build_cw,
|
||||
ver->min_fw_build_yy,
|
||||
fw, boot_param)) {
|
||||
bt_dev_info(hdev, "Firmware already loaded");
|
||||
/* Return -EALREADY to indicate that firmware has
|
||||
* already been loaded.
|
||||
*/
|
||||
return -EALREADY;
|
||||
}
|
||||
/* Skip download if firmware has the same version */
|
||||
if (btintel_firmware_version(hdev, ver->min_fw_build_nn,
|
||||
ver->min_fw_build_cw,
|
||||
ver->min_fw_build_yy,
|
||||
fw, boot_param)) {
|
||||
bt_dev_info(hdev, "Firmware already loaded");
|
||||
/* Return -EALREADY to indicate that firmware has
|
||||
* already been loaded.
|
||||
*/
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
/* The firmware variant determines if the device is in bootloader
|
||||
@ -1285,12 +1280,16 @@ static int btintel_read_debug_features(struct hci_dev *hdev,
|
||||
static int btintel_set_debug_features(struct hci_dev *hdev,
|
||||
const struct intel_debug_features *features)
|
||||
{
|
||||
u8 mask[11] = { 0x0a, 0x92, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00,
|
||||
u8 mask[11] = { 0x0a, 0x92, 0x02, 0x7f, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00 };
|
||||
u8 period[5] = { 0x04, 0x91, 0x02, 0x05, 0x00 };
|
||||
u8 trace_enable = 0x02;
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (!features)
|
||||
if (!features) {
|
||||
bt_dev_warn(hdev, "Debug features not read");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!(features->page1[0] & 0x3f)) {
|
||||
bt_dev_info(hdev, "Telemetry exception format not supported");
|
||||
@ -1303,11 +1302,95 @@ static int btintel_set_debug_features(struct hci_dev *hdev,
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
skb = __hci_cmd_sync(hdev, 0xfc8b, 5, period, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Setting periodicity for link statistics traces failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
kfree_skb(skb);
|
||||
|
||||
skb = __hci_cmd_sync(hdev, 0xfca1, 1, &trace_enable, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Enable tracing of link statistics events failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
kfree_skb(skb);
|
||||
|
||||
bt_dev_info(hdev, "set debug features: trace_enable 0x%02x mask 0x%02x",
|
||||
trace_enable, mask[3]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btintel_reset_debug_features(struct hci_dev *hdev,
|
||||
const struct intel_debug_features *features)
|
||||
{
|
||||
u8 mask[11] = { 0x0a, 0x92, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00 };
|
||||
u8 trace_enable = 0x00;
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (!features) {
|
||||
bt_dev_warn(hdev, "Debug features not read");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!(features->page1[0] & 0x3f)) {
|
||||
bt_dev_info(hdev, "Telemetry exception format not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Should stop the trace before writing ddc event mask. */
|
||||
skb = __hci_cmd_sync(hdev, 0xfca1, 1, &trace_enable, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Stop tracing of link statistics events failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
kfree_skb(skb);
|
||||
|
||||
skb = __hci_cmd_sync(hdev, 0xfc8b, 11, mask, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Setting Intel telemetry ddc write event mask failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
kfree_skb(skb);
|
||||
|
||||
bt_dev_info(hdev, "reset debug features: trace_enable 0x%02x mask 0x%02x",
|
||||
trace_enable, mask[3]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btintel_set_quality_report(struct hci_dev *hdev, bool enable)
|
||||
{
|
||||
struct intel_debug_features features;
|
||||
int err;
|
||||
|
||||
bt_dev_dbg(hdev, "enable %d", enable);
|
||||
|
||||
/* Read the Intel supported features and if new exception formats
|
||||
* supported, need to load the additional DDC config to enable.
|
||||
*/
|
||||
err = btintel_read_debug_features(hdev, &features);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Set or reset the debug features. */
|
||||
if (enable)
|
||||
err = btintel_set_debug_features(hdev, &features);
|
||||
else
|
||||
err = btintel_reset_debug_features(hdev, &features);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btintel_set_quality_report);
|
||||
|
||||
static const struct firmware *btintel_legacy_rom_get_fw(struct hci_dev *hdev,
|
||||
struct intel_version *ver)
|
||||
{
|
||||
@ -1893,7 +1976,6 @@ static int btintel_bootloader_setup(struct hci_dev *hdev,
|
||||
u32 boot_param;
|
||||
char ddcname[64];
|
||||
int err;
|
||||
struct intel_debug_features features;
|
||||
|
||||
BT_DBG("%s", hdev->name);
|
||||
|
||||
@ -1934,14 +2016,7 @@ static int btintel_bootloader_setup(struct hci_dev *hdev,
|
||||
btintel_load_ddc_config(hdev, ddcname);
|
||||
}
|
||||
|
||||
/* Read the Intel supported features and if new exception formats
|
||||
* supported, need to load the additional DDC config to enable.
|
||||
*/
|
||||
err = btintel_read_debug_features(hdev, &features);
|
||||
if (!err) {
|
||||
/* Set DDC mask for available debug features */
|
||||
btintel_set_debug_features(hdev, &features);
|
||||
}
|
||||
hci_dev_clear_flag(hdev, HCI_QUALITY_REPORT);
|
||||
|
||||
/* Read the Intel version information after loading the FW */
|
||||
err = btintel_read_version(hdev, &new_ver);
|
||||
@ -2083,13 +2158,102 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int btintel_get_codec_config_data(struct hci_dev *hdev,
|
||||
__u8 link, struct bt_codec *codec,
|
||||
__u8 *ven_len, __u8 **ven_data)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (!ven_data || !ven_len)
|
||||
return -EINVAL;
|
||||
|
||||
*ven_len = 0;
|
||||
*ven_data = NULL;
|
||||
|
||||
if (link != ESCO_LINK) {
|
||||
bt_dev_err(hdev, "Invalid link type(%u)", link);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*ven_data = kmalloc(sizeof(__u8), GFP_KERNEL);
|
||||
if (!*ven_data) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* supports only CVSD and mSBC offload codecs */
|
||||
switch (codec->id) {
|
||||
case 0x02:
|
||||
**ven_data = 0x00;
|
||||
break;
|
||||
case 0x05:
|
||||
**ven_data = 0x01;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
bt_dev_err(hdev, "Invalid codec id(%u)", codec->id);
|
||||
goto error;
|
||||
}
|
||||
/* codec and its capabilities are pre-defined to ids
|
||||
* preset id = 0x00 represents CVSD codec with sampling rate 8K
|
||||
* preset id = 0x01 represents mSBC codec with sampling rate 16K
|
||||
*/
|
||||
*ven_len = sizeof(__u8);
|
||||
return err;
|
||||
|
||||
error:
|
||||
kfree(*ven_data);
|
||||
*ven_data = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int btintel_get_data_path_id(struct hci_dev *hdev, __u8 *data_path_id)
|
||||
{
|
||||
/* Intel uses 1 as data path id for all the usecases */
|
||||
*data_path_id = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btintel_configure_offload(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int err = 0;
|
||||
struct intel_offload_use_cases *use_cases;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, 0xfc86, 0, NULL, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Reading offload use cases failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
|
||||
if (skb->len < sizeof(*use_cases)) {
|
||||
err = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
use_cases = (void *)skb->data;
|
||||
|
||||
if (use_cases->status) {
|
||||
err = -bt_to_errno(skb->data[0]);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (use_cases->preset[0] & 0x03) {
|
||||
hdev->get_data_path_id = btintel_get_data_path_id;
|
||||
hdev->get_codec_config_data = btintel_get_codec_config_data;
|
||||
}
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
|
||||
struct intel_version_tlv *ver)
|
||||
{
|
||||
u32 boot_param;
|
||||
char ddcname[64];
|
||||
int err;
|
||||
struct intel_debug_features features;
|
||||
struct intel_version_tlv new_ver;
|
||||
|
||||
bt_dev_dbg(hdev, "");
|
||||
@ -2125,14 +2289,10 @@ static int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
|
||||
*/
|
||||
btintel_load_ddc_config(hdev, ddcname);
|
||||
|
||||
/* Read the Intel supported features and if new exception formats
|
||||
* supported, need to load the additional DDC config to enable.
|
||||
*/
|
||||
err = btintel_read_debug_features(hdev, &features);
|
||||
if (!err) {
|
||||
/* Set DDC mask for available debug features */
|
||||
btintel_set_debug_features(hdev, &features);
|
||||
}
|
||||
/* Read supported use cases and set callbacks to fetch datapath id */
|
||||
btintel_configure_offload(hdev);
|
||||
|
||||
hci_dev_clear_flag(hdev, HCI_QUALITY_REPORT);
|
||||
|
||||
/* Read the Intel version information after loading the FW */
|
||||
err = btintel_read_version_tlv(hdev, &new_ver);
|
||||
@ -2232,6 +2392,9 @@ static int btintel_setup_combined(struct hci_dev *hdev)
|
||||
set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
|
||||
set_bit(HCI_QUIRK_NON_PERSISTENT_DIAG, &hdev->quirks);
|
||||
|
||||
/* Set up the quality report callback for Intel devices */
|
||||
hdev->set_quality_report = btintel_set_quality_report;
|
||||
|
||||
/* For Legacy device, check the HW platform value and size */
|
||||
if (skb->len == sizeof(ver) && skb->data[1] == 0x37) {
|
||||
bt_dev_dbg(hdev, "Read the legacy Intel version information");
|
||||
|
@ -132,6 +132,11 @@ struct intel_debug_features {
|
||||
__u8 page1[16];
|
||||
} __packed;
|
||||
|
||||
struct intel_offload_use_cases {
|
||||
__u8 status;
|
||||
__u8 preset[8];
|
||||
} __packed;
|
||||
|
||||
#define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8))
|
||||
#define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16))
|
||||
#define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff)
|
||||
@ -204,6 +209,7 @@ int btintel_configure_setup(struct hci_dev *hdev);
|
||||
void btintel_bootup(struct hci_dev *hdev, const void *ptr, unsigned int len);
|
||||
void btintel_secure_send_result(struct hci_dev *hdev,
|
||||
const void *ptr, unsigned int len);
|
||||
int btintel_set_quality_report(struct hci_dev *hdev, bool enable);
|
||||
#else
|
||||
|
||||
static inline int btintel_check_bdaddr(struct hci_dev *hdev)
|
||||
@ -294,4 +300,9 @@ static inline void btintel_secure_send_result(struct hci_dev *hdev,
|
||||
const void *ptr, unsigned int len)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int btintel_set_quality_report(struct hci_dev *hdev, bool enable)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
@ -587,12 +587,12 @@ static int btmrvl_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool btmrvl_prevent_wake(struct hci_dev *hdev)
|
||||
static bool btmrvl_wakeup(struct hci_dev *hdev)
|
||||
{
|
||||
struct btmrvl_private *priv = hci_get_drvdata(hdev);
|
||||
struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
|
||||
|
||||
return !device_may_wakeup(&card->func->dev);
|
||||
return device_may_wakeup(&card->func->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -696,7 +696,7 @@ int btmrvl_register_hdev(struct btmrvl_private *priv)
|
||||
hdev->send = btmrvl_send_frame;
|
||||
hdev->setup = btmrvl_setup;
|
||||
hdev->set_bdaddr = btmrvl_set_bdaddr;
|
||||
hdev->prevent_wake = btmrvl_prevent_wake;
|
||||
hdev->wakeup = btmrvl_wakeup;
|
||||
SET_HCIDEV_DEV(hdev, &card->func->dev);
|
||||
|
||||
hdev->dev_type = priv->btmrvl_dev.dev_type;
|
||||
|
@ -158,8 +158,10 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev,
|
||||
int err;
|
||||
|
||||
hlen = sizeof(*hdr) + wmt_params->dlen;
|
||||
if (hlen > 255)
|
||||
return -EINVAL;
|
||||
if (hlen > 255) {
|
||||
err = -EINVAL;
|
||||
goto err_free_skb;
|
||||
}
|
||||
|
||||
hdr = (struct mtk_wmt_hdr *)&wc;
|
||||
hdr->dir = 1;
|
||||
@ -173,7 +175,7 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev,
|
||||
err = __hci_cmd_send(hdev, 0xfc6f, hlen, &wc);
|
||||
if (err < 0) {
|
||||
clear_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_state);
|
||||
return err;
|
||||
goto err_free_skb;
|
||||
}
|
||||
|
||||
/* The vendor specific WMT commands are all answered by a vendor
|
||||
@ -190,13 +192,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev,
|
||||
if (err == -EINTR) {
|
||||
bt_dev_err(hdev, "Execution of wmt command interrupted");
|
||||
clear_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_state);
|
||||
return err;
|
||||
goto err_free_skb;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
bt_dev_err(hdev, "Execution of wmt command timed out");
|
||||
clear_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_state);
|
||||
return -ETIMEDOUT;
|
||||
err = -ETIMEDOUT;
|
||||
goto err_free_skb;
|
||||
}
|
||||
|
||||
/* Parse and handle the return WMT event */
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <net/rsi_91x.h>
|
||||
#include <net/genetlink.h>
|
||||
|
||||
#define RSI_DMA_ALIGN 8
|
||||
#define RSI_FRAME_DESC_SIZE 16
|
||||
|
@ -59,6 +59,7 @@ struct id_table {
|
||||
__u8 hci_bus;
|
||||
bool config_needed;
|
||||
bool has_rom_version;
|
||||
bool has_msft_ext;
|
||||
char *fw_name;
|
||||
char *cfg_name;
|
||||
};
|
||||
@ -121,6 +122,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8821A, 0xc, 0x8, HCI_USB),
|
||||
.config_needed = false,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8821c_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8821c_config" },
|
||||
|
||||
@ -135,6 +137,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8761A, 0xb, 0xa, HCI_UART),
|
||||
.config_needed = false,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8761b_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8761b_config" },
|
||||
|
||||
@ -149,6 +152,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8822B, 0xc, 0xa, HCI_UART),
|
||||
.config_needed = true,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8822cs_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8822cs_config" },
|
||||
|
||||
@ -156,6 +160,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8822B, 0xc, 0xa, HCI_USB),
|
||||
.config_needed = false,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8822cu_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8822cu_config" },
|
||||
|
||||
@ -163,6 +168,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8822B, 0xb, 0x7, HCI_USB),
|
||||
.config_needed = true,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8822b_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8822b_config" },
|
||||
|
||||
@ -170,6 +176,7 @@ static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8852A, 0xa, 0xb, HCI_USB),
|
||||
.config_needed = false,
|
||||
.has_rom_version = true,
|
||||
.has_msft_ext = true,
|
||||
.fw_name = "rtl_bt/rtl8852au_fw.bin",
|
||||
.cfg_name = "rtl_bt/rtl8852au_config" },
|
||||
};
|
||||
@ -594,8 +601,10 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
|
||||
hci_rev = le16_to_cpu(resp->hci_rev);
|
||||
lmp_subver = le16_to_cpu(resp->lmp_subver);
|
||||
|
||||
if (resp->hci_ver == 0x8 && le16_to_cpu(resp->hci_rev) == 0x826c &&
|
||||
resp->lmp_ver == 0x8 && le16_to_cpu(resp->lmp_subver) == 0xa99e)
|
||||
btrtl_dev->ic_info = btrtl_match_ic(lmp_subver, hci_rev, hci_ver,
|
||||
hdev->bus);
|
||||
|
||||
if (!btrtl_dev->ic_info)
|
||||
btrtl_dev->drop_fw = true;
|
||||
|
||||
if (btrtl_dev->drop_fw) {
|
||||
@ -634,13 +643,13 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
|
||||
hci_ver = resp->hci_ver;
|
||||
hci_rev = le16_to_cpu(resp->hci_rev);
|
||||
lmp_subver = le16_to_cpu(resp->lmp_subver);
|
||||
|
||||
btrtl_dev->ic_info = btrtl_match_ic(lmp_subver, hci_rev, hci_ver,
|
||||
hdev->bus);
|
||||
}
|
||||
out_free:
|
||||
kfree_skb(skb);
|
||||
|
||||
btrtl_dev->ic_info = btrtl_match_ic(lmp_subver, hci_rev, hci_ver,
|
||||
hdev->bus);
|
||||
|
||||
if (!btrtl_dev->ic_info) {
|
||||
rtl_dev_info(hdev, "unknown IC info, lmp subver %04x, hci rev %04x, hci ver %04x",
|
||||
lmp_subver, hci_rev, hci_ver);
|
||||
@ -684,12 +693,8 @@ out_free:
|
||||
/* The following chips supports the Microsoft vendor extension,
|
||||
* therefore set the corresponding VsMsftOpCode.
|
||||
*/
|
||||
switch (lmp_subver) {
|
||||
case RTL_ROM_LMP_8822B:
|
||||
case RTL_ROM_LMP_8852A:
|
||||
if (btrtl_dev->ic_info->has_msft_ext)
|
||||
hci_set_msft_opcode(hdev, 0xFCF0);
|
||||
break;
|
||||
}
|
||||
|
||||
return btrtl_dev;
|
||||
|
||||
@ -746,6 +751,7 @@ void btrtl_set_quirks(struct hci_dev *hdev, struct btrtl_device_info *btrtl_dev)
|
||||
case CHIP_ID_8852A:
|
||||
set_bit(HCI_QUIRK_VALID_LE_STATES, &hdev->quirks);
|
||||
set_bit(HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED, &hdev->quirks);
|
||||
hci_set_aosp_capable(hdev);
|
||||
break;
|
||||
default:
|
||||
rtl_dev_dbg(hdev, "Central-peripheral role not enabled.");
|
||||
|
@ -384,6 +384,12 @@ static const struct usb_device_id blacklist_table[] = {
|
||||
/* Realtek 8852AE Bluetooth devices */
|
||||
{ USB_DEVICE(0x0bda, 0xc852), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
{ USB_DEVICE(0x0bda, 0x4852), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
{ USB_DEVICE(0x04c5, 0x165c), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
{ USB_DEVICE(0x04ca, 0x4006), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
|
||||
/* Realtek Bluetooth devices */
|
||||
{ USB_VENDOR_AND_INTERFACE_INFO(0x0bda, 0xe0, 0x01, 0x01),
|
||||
@ -410,6 +416,9 @@ static const struct usb_device_id blacklist_table[] = {
|
||||
{ USB_DEVICE(0x13d3, 0x3563), .driver_info = BTUSB_MEDIATEK |
|
||||
BTUSB_WIDEBAND_SPEECH |
|
||||
BTUSB_VALID_LE_STATES },
|
||||
{ USB_DEVICE(0x13d3, 0x3564), .driver_info = BTUSB_MEDIATEK |
|
||||
BTUSB_WIDEBAND_SPEECH |
|
||||
BTUSB_VALID_LE_STATES },
|
||||
{ USB_DEVICE(0x0489, 0xe0cd), .driver_info = BTUSB_MEDIATEK |
|
||||
BTUSB_WIDEBAND_SPEECH |
|
||||
BTUSB_VALID_LE_STATES },
|
||||
@ -433,6 +442,10 @@ static const struct usb_device_id blacklist_table[] = {
|
||||
{ USB_DEVICE(0x0bda, 0xb009), .driver_info = BTUSB_REALTEK },
|
||||
{ USB_DEVICE(0x2ff8, 0xb011), .driver_info = BTUSB_REALTEK },
|
||||
|
||||
/* Additional Realtek 8761B Bluetooth devices */
|
||||
{ USB_DEVICE(0x2357, 0x0604), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
|
||||
/* Additional Realtek 8761BU Bluetooth devices */
|
||||
{ USB_DEVICE(0x0b05, 0x190e), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
@ -451,10 +464,6 @@ static const struct usb_device_id blacklist_table[] = {
|
||||
/* Additional Realtek 8822CE Bluetooth devices */
|
||||
{ USB_DEVICE(0x04ca, 0x4005), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
/* Bluetooth component of Realtek 8852AE device */
|
||||
{ USB_DEVICE(0x04ca, 0x4006), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
|
||||
{ USB_DEVICE(0x04c5, 0x161f), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
{ USB_DEVICE(0x0b05, 0x18ef), .driver_info = BTUSB_REALTEK |
|
||||
@ -652,11 +661,33 @@ static void btusb_rtl_cmd_timeout(struct hci_dev *hdev)
|
||||
static void btusb_qca_cmd_timeout(struct hci_dev *hdev)
|
||||
{
|
||||
struct btusb_data *data = hci_get_drvdata(hdev);
|
||||
struct gpio_desc *reset_gpio = data->reset_gpio;
|
||||
int err;
|
||||
|
||||
if (++data->cmd_timeout_cnt < 5)
|
||||
return;
|
||||
|
||||
if (reset_gpio) {
|
||||
bt_dev_err(hdev, "Reset qca device via bt_en gpio");
|
||||
|
||||
/* Toggle the hard reset line. The qca bt device is going to
|
||||
* yank itself off the USB and then replug. The cleanup is handled
|
||||
* correctly on the way out (standard USB disconnect), and the new
|
||||
* device is detected cleanly and bound to the driver again like
|
||||
* it should be.
|
||||
*/
|
||||
if (test_and_set_bit(BTUSB_HW_RESET_ACTIVE, &data->flags)) {
|
||||
bt_dev_err(hdev, "last reset failed? Not resetting again");
|
||||
return;
|
||||
}
|
||||
|
||||
gpiod_set_value_cansleep(reset_gpio, 0);
|
||||
msleep(200);
|
||||
gpiod_set_value_cansleep(reset_gpio, 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bt_dev_err(hdev, "Multiple cmd timeouts seen. Resetting usb device.");
|
||||
/* This is not an unbalanced PM reference since the device will reset */
|
||||
err = usb_autopm_get_interface(data->intf);
|
||||
@ -2200,6 +2231,23 @@ struct btmtk_section_map {
|
||||
};
|
||||
} __packed;
|
||||
|
||||
static int btusb_set_bdaddr_mtk(struct hci_dev *hdev, const bdaddr_t *bdaddr)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
long ret;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, 0xfc1a, sizeof(bdaddr), bdaddr, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
ret = PTR_ERR(skb);
|
||||
bt_dev_err(hdev, "changing Mediatek device address failed (%ld)",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
kfree_skb(skb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void btusb_mtk_wmt_recv(struct urb *urb)
|
||||
{
|
||||
struct hci_dev *hdev = urb->context;
|
||||
@ -2804,6 +2852,7 @@ static int btusb_mtk_setup(struct hci_dev *hdev)
|
||||
case 0x7668:
|
||||
fwname = FIRMWARE_MT7668;
|
||||
break;
|
||||
case 0x7922:
|
||||
case 0x7961:
|
||||
snprintf(fw_bin_name, sizeof(fw_bin_name),
|
||||
"mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
|
||||
@ -3591,11 +3640,11 @@ static void btusb_check_needs_reset_resume(struct usb_interface *intf)
|
||||
interface_to_usbdev(intf)->quirks |= USB_QUIRK_RESET_RESUME;
|
||||
}
|
||||
|
||||
static bool btusb_prevent_wake(struct hci_dev *hdev)
|
||||
static bool btusb_wakeup(struct hci_dev *hdev)
|
||||
{
|
||||
struct btusb_data *data = hci_get_drvdata(hdev);
|
||||
|
||||
return !device_may_wakeup(&data->udev->dev);
|
||||
return device_may_wakeup(&data->udev->dev);
|
||||
}
|
||||
|
||||
static int btusb_shutdown_qca(struct hci_dev *hdev)
|
||||
@ -3752,7 +3801,7 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
hdev->flush = btusb_flush;
|
||||
hdev->send = btusb_send_frame;
|
||||
hdev->notify = btusb_notify;
|
||||
hdev->prevent_wake = btusb_prevent_wake;
|
||||
hdev->wakeup = btusb_wakeup;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
err = btusb_config_oob_wake(hdev);
|
||||
@ -3819,6 +3868,7 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
hdev->shutdown = btusb_mtk_shutdown;
|
||||
hdev->manufacturer = 70;
|
||||
hdev->cmd_timeout = btusb_mtk_cmd_timeout;
|
||||
hdev->set_bdaddr = btusb_set_bdaddr_mtk;
|
||||
set_bit(HCI_QUIRK_NON_PERSISTENT_SETUP, &hdev->quirks);
|
||||
data->recv_acl = btusb_recv_acl_mtk;
|
||||
}
|
||||
|
@ -587,9 +587,11 @@ static int h5_recv(struct hci_uart *hu, const void *data, int count)
|
||||
count -= processed;
|
||||
}
|
||||
|
||||
pm_runtime_get(&hu->serdev->dev);
|
||||
pm_runtime_mark_last_busy(&hu->serdev->dev);
|
||||
pm_runtime_put_autosuspend(&hu->serdev->dev);
|
||||
if (hu->serdev) {
|
||||
pm_runtime_get(&hu->serdev->dev);
|
||||
pm_runtime_mark_last_busy(&hu->serdev->dev);
|
||||
pm_runtime_put_autosuspend(&hu->serdev->dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -814,7 +816,6 @@ static int h5_serdev_probe(struct serdev_device *serdev)
|
||||
struct device *dev = &serdev->dev;
|
||||
struct h5 *h5;
|
||||
const struct h5_device_data *data;
|
||||
int err;
|
||||
|
||||
h5 = devm_kzalloc(dev, sizeof(*h5), GFP_KERNEL);
|
||||
if (!h5)
|
||||
@ -846,6 +847,8 @@ static int h5_serdev_probe(struct serdev_device *serdev)
|
||||
h5->vnd = data->vnd;
|
||||
}
|
||||
|
||||
if (data->driver_info & H5_INFO_WAKEUP_DISABLE)
|
||||
set_bit(H5_WAKEUP_DISABLE, &h5->flags);
|
||||
|
||||
h5->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(h5->enable_gpio))
|
||||
@ -856,14 +859,7 @@ static int h5_serdev_probe(struct serdev_device *serdev)
|
||||
if (IS_ERR(h5->device_wake_gpio))
|
||||
return PTR_ERR(h5->device_wake_gpio);
|
||||
|
||||
err = hci_uart_register_device(&h5->serdev_hu, &h5p);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (data->driver_info & H5_INFO_WAKEUP_DISABLE)
|
||||
set_bit(H5_WAKEUP_DISABLE, &h5->flags);
|
||||
|
||||
return 0;
|
||||
return hci_uart_register_device(&h5->serdev_hu, &h5p);
|
||||
}
|
||||
|
||||
static void h5_serdev_remove(struct serdev_device *serdev)
|
||||
@ -962,11 +958,13 @@ static void h5_btrtl_open(struct h5 *h5)
|
||||
serdev_device_set_parity(h5->hu->serdev, SERDEV_PARITY_EVEN);
|
||||
serdev_device_set_baudrate(h5->hu->serdev, 115200);
|
||||
|
||||
pm_runtime_set_active(&h5->hu->serdev->dev);
|
||||
pm_runtime_use_autosuspend(&h5->hu->serdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&h5->hu->serdev->dev,
|
||||
SUSPEND_TIMEOUT_MS);
|
||||
pm_runtime_enable(&h5->hu->serdev->dev);
|
||||
if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags)) {
|
||||
pm_runtime_set_active(&h5->hu->serdev->dev);
|
||||
pm_runtime_use_autosuspend(&h5->hu->serdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&h5->hu->serdev->dev,
|
||||
SUSPEND_TIMEOUT_MS);
|
||||
pm_runtime_enable(&h5->hu->serdev->dev);
|
||||
}
|
||||
|
||||
/* The controller needs up to 500ms to wakeup */
|
||||
gpiod_set_value_cansleep(h5->enable_gpio, 1);
|
||||
@ -976,7 +974,8 @@ static void h5_btrtl_open(struct h5 *h5)
|
||||
|
||||
static void h5_btrtl_close(struct h5 *h5)
|
||||
{
|
||||
pm_runtime_disable(&h5->hu->serdev->dev);
|
||||
if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags))
|
||||
pm_runtime_disable(&h5->hu->serdev->dev);
|
||||
|
||||
gpiod_set_value_cansleep(h5->device_wake_gpio, 0);
|
||||
gpiod_set_value_cansleep(h5->enable_gpio, 0);
|
||||
|
@ -479,6 +479,9 @@ static int hci_uart_tty_open(struct tty_struct *tty)
|
||||
|
||||
BT_DBG("tty %p", tty);
|
||||
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
/* Error if the tty has no write op instead of leaving an exploitable
|
||||
* hole
|
||||
*/
|
||||
|
@ -1577,7 +1577,7 @@ static void qca_cmd_timeout(struct hci_dev *hdev)
|
||||
mutex_unlock(&qca->hci_memdump_lock);
|
||||
}
|
||||
|
||||
static bool qca_prevent_wake(struct hci_dev *hdev)
|
||||
static bool qca_wakeup(struct hci_dev *hdev)
|
||||
{
|
||||
struct hci_uart *hu = hci_get_drvdata(hdev);
|
||||
bool wakeup;
|
||||
@ -1730,6 +1730,7 @@ retry:
|
||||
if (qca_is_wcn399x(soc_type) ||
|
||||
qca_is_wcn6750(soc_type)) {
|
||||
set_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks);
|
||||
hci_set_aosp_capable(hdev);
|
||||
|
||||
ret = qca_read_soc_version(hdev, &ver, soc_type);
|
||||
if (ret)
|
||||
@ -1764,7 +1765,7 @@ retry:
|
||||
qca_debugfs_init(hdev);
|
||||
hu->hdev->hw_error = qca_hw_error;
|
||||
hu->hdev->cmd_timeout = qca_cmd_timeout;
|
||||
hu->hdev->prevent_wake = qca_prevent_wake;
|
||||
hu->hdev->wakeup = qca_wakeup;
|
||||
} else if (ret == -ENOENT) {
|
||||
/* No patch/nvm-config found, run with original fw/config */
|
||||
set_bit(QCA_ROM_FW, &qca->flags);
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
@ -37,6 +38,9 @@ struct vhci_data {
|
||||
|
||||
struct mutex open_mutex;
|
||||
struct delayed_work open_timeout;
|
||||
|
||||
bool suspended;
|
||||
bool wakeup;
|
||||
};
|
||||
|
||||
static int vhci_open_dev(struct hci_dev *hdev)
|
||||
@ -73,6 +77,115 @@ static int vhci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vhci_get_data_path_id(struct hci_dev *hdev, u8 *data_path_id)
|
||||
{
|
||||
*data_path_id = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vhci_get_codec_config_data(struct hci_dev *hdev, __u8 type,
|
||||
struct bt_codec *codec, __u8 *vnd_len,
|
||||
__u8 **vnd_data)
|
||||
{
|
||||
if (type != ESCO_LINK)
|
||||
return -EINVAL;
|
||||
|
||||
*vnd_len = 0;
|
||||
*vnd_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vhci_wakeup(struct hci_dev *hdev)
|
||||
{
|
||||
struct vhci_data *data = hci_get_drvdata(hdev);
|
||||
|
||||
return data->wakeup;
|
||||
}
|
||||
|
||||
static ssize_t force_suspend_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct vhci_data *data = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = data->suspended ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t force_suspend_write(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct vhci_data *data = file->private_data;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (data->suspended == enable)
|
||||
return -EALREADY;
|
||||
|
||||
if (enable)
|
||||
err = hci_suspend_dev(data->hdev);
|
||||
else
|
||||
err = hci_resume_dev(data->hdev);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
data->suspended = enable;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations force_suspend_fops = {
|
||||
.open = simple_open,
|
||||
.read = force_suspend_read,
|
||||
.write = force_suspend_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t force_wakeup_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct vhci_data *data = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = data->wakeup ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t force_wakeup_write(struct file *file,
|
||||
const char __user *user_buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct vhci_data *data = file->private_data;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (data->wakeup == enable)
|
||||
return -EALREADY;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations force_wakeup_fops = {
|
||||
.open = simple_open,
|
||||
.read = force_wakeup_read,
|
||||
.write = force_wakeup_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static int __vhci_create_device(struct vhci_data *data, __u8 opcode)
|
||||
{
|
||||
struct hci_dev *hdev;
|
||||
@ -112,6 +225,9 @@ static int __vhci_create_device(struct vhci_data *data, __u8 opcode)
|
||||
hdev->close = vhci_close_dev;
|
||||
hdev->flush = vhci_flush;
|
||||
hdev->send = vhci_send_frame;
|
||||
hdev->get_data_path_id = vhci_get_data_path_id;
|
||||
hdev->get_codec_config_data = vhci_get_codec_config_data;
|
||||
hdev->wakeup = vhci_wakeup;
|
||||
|
||||
/* bit 6 is for external configuration */
|
||||
if (opcode & 0x40)
|
||||
@ -129,6 +245,12 @@ static int __vhci_create_device(struct vhci_data *data, __u8 opcode)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
debugfs_create_file("force_suspend", 0644, hdev->debugfs, data,
|
||||
&force_suspend_fops);
|
||||
|
||||
debugfs_create_file("force_wakeup", 0644, hdev->debugfs, data,
|
||||
&force_wakeup_fops);
|
||||
|
||||
hci_skb_pkt_type(skb) = HCI_VENDOR_PKT;
|
||||
|
||||
skb_put_u8(skb, 0xff);
|
||||
|
@ -153,6 +153,30 @@ struct bt_voice {
|
||||
|
||||
#define BT_SCM_PKT_STATUS 0x03
|
||||
|
||||
#define BT_CODEC 19
|
||||
|
||||
struct bt_codec_caps {
|
||||
__u8 len;
|
||||
__u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct bt_codec {
|
||||
__u8 id;
|
||||
__u16 cid;
|
||||
__u16 vid;
|
||||
__u8 data_path;
|
||||
__u8 num_caps;
|
||||
} __packed;
|
||||
|
||||
struct bt_codecs {
|
||||
__u8 num_codecs;
|
||||
struct bt_codec codecs[];
|
||||
} __packed;
|
||||
|
||||
#define BT_CODEC_CVSD 0x02
|
||||
#define BT_CODEC_TRANSPARENT 0x03
|
||||
#define BT_CODEC_MSBC 0x05
|
||||
|
||||
__printf(1, 2)
|
||||
void bt_info(const char *fmt, ...);
|
||||
__printf(1, 2)
|
||||
@ -420,6 +444,72 @@ out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Shall not be called with lock_sock held */
|
||||
static inline struct sk_buff *bt_skb_sendmsg(struct sock *sk,
|
||||
struct msghdr *msg,
|
||||
size_t len, size_t mtu,
|
||||
size_t headroom, size_t tailroom)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
size_t size = min_t(size_t, len, mtu);
|
||||
int err;
|
||||
|
||||
skb = bt_skb_send_alloc(sk, size + headroom + tailroom,
|
||||
msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb)
|
||||
return ERR_PTR(err);
|
||||
|
||||
skb_reserve(skb, headroom);
|
||||
skb_tailroom_reserve(skb, mtu, tailroom);
|
||||
|
||||
if (!copy_from_iter_full(skb_put(skb, size), size, &msg->msg_iter)) {
|
||||
kfree_skb(skb);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
skb->priority = sk->sk_priority;
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
/* Similar to bt_skb_sendmsg but can split the msg into multiple fragments
|
||||
* accourding to the MTU.
|
||||
*/
|
||||
static inline struct sk_buff *bt_skb_sendmmsg(struct sock *sk,
|
||||
struct msghdr *msg,
|
||||
size_t len, size_t mtu,
|
||||
size_t headroom, size_t tailroom)
|
||||
{
|
||||
struct sk_buff *skb, **frag;
|
||||
|
||||
skb = bt_skb_sendmsg(sk, msg, len, mtu, headroom, tailroom);
|
||||
if (IS_ERR_OR_NULL(skb))
|
||||
return skb;
|
||||
|
||||
len -= skb->len;
|
||||
if (!len)
|
||||
return skb;
|
||||
|
||||
/* Add remaining data over MTU as continuation fragments */
|
||||
frag = &skb_shinfo(skb)->frag_list;
|
||||
while (len) {
|
||||
struct sk_buff *tmp;
|
||||
|
||||
tmp = bt_skb_sendmsg(sk, msg, len, mtu, headroom, tailroom);
|
||||
if (IS_ERR(tmp)) {
|
||||
kfree_skb(skb);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
len -= tmp->len;
|
||||
|
||||
*frag = tmp;
|
||||
frag = &(*frag)->next;
|
||||
}
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
int bt_to_errno(u16 code);
|
||||
|
||||
void hci_sock_set_flag(struct sock *sk, int nr);
|
||||
|
@ -330,6 +330,8 @@ enum {
|
||||
HCI_ENABLE_LL_PRIVACY,
|
||||
HCI_CMD_PENDING,
|
||||
HCI_FORCE_NO_MITM,
|
||||
HCI_QUALITY_REPORT,
|
||||
HCI_OFFLOAD_CODECS_ENABLED,
|
||||
|
||||
__HCI_NUM_FLAGS,
|
||||
};
|
||||
@ -871,6 +873,40 @@ struct hci_cp_logical_link_cancel {
|
||||
__u8 flow_spec_id;
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_ENHANCED_SETUP_SYNC_CONN 0x043d
|
||||
struct hci_coding_format {
|
||||
__u8 id;
|
||||
__le16 cid;
|
||||
__le16 vid;
|
||||
} __packed;
|
||||
|
||||
struct hci_cp_enhanced_setup_sync_conn {
|
||||
__le16 handle;
|
||||
__le32 tx_bandwidth;
|
||||
__le32 rx_bandwidth;
|
||||
struct hci_coding_format tx_coding_format;
|
||||
struct hci_coding_format rx_coding_format;
|
||||
__le16 tx_codec_frame_size;
|
||||
__le16 rx_codec_frame_size;
|
||||
__le32 in_bandwidth;
|
||||
__le32 out_bandwidth;
|
||||
struct hci_coding_format in_coding_format;
|
||||
struct hci_coding_format out_coding_format;
|
||||
__le16 in_coded_data_size;
|
||||
__le16 out_coded_data_size;
|
||||
__u8 in_pcm_data_format;
|
||||
__u8 out_pcm_data_format;
|
||||
__u8 in_pcm_sample_payload_msb_pos;
|
||||
__u8 out_pcm_sample_payload_msb_pos;
|
||||
__u8 in_data_path;
|
||||
__u8 out_data_path;
|
||||
__u8 in_transport_unit_size;
|
||||
__u8 out_transport_unit_size;
|
||||
__le16 max_latency;
|
||||
__le16 pkt_type;
|
||||
__u8 retrans_effort;
|
||||
} __packed;
|
||||
|
||||
struct hci_rp_logical_link_cancel {
|
||||
__u8 status;
|
||||
__u8 phy_handle;
|
||||
@ -1250,6 +1286,14 @@ struct hci_rp_read_local_oob_ext_data {
|
||||
__u8 rand256[16];
|
||||
} __packed;
|
||||
|
||||
#define HCI_CONFIGURE_DATA_PATH 0x0c83
|
||||
struct hci_op_configure_data_path {
|
||||
__u8 direction;
|
||||
__u8 data_path_id;
|
||||
__u8 vnd_len;
|
||||
__u8 vnd_data[];
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_LOCAL_VERSION 0x1001
|
||||
struct hci_rp_read_local_version {
|
||||
__u8 status;
|
||||
@ -1307,6 +1351,28 @@ struct hci_rp_read_data_block_size {
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_LOCAL_CODECS 0x100b
|
||||
struct hci_std_codecs {
|
||||
__u8 num;
|
||||
__u8 codec[];
|
||||
} __packed;
|
||||
|
||||
struct hci_vnd_codec {
|
||||
/* company id */
|
||||
__le16 cid;
|
||||
/* vendor codec id */
|
||||
__le16 vid;
|
||||
} __packed;
|
||||
|
||||
struct hci_vnd_codecs {
|
||||
__u8 num;
|
||||
struct hci_vnd_codec codec[];
|
||||
} __packed;
|
||||
|
||||
struct hci_rp_read_local_supported_codecs {
|
||||
__u8 status;
|
||||
struct hci_std_codecs std_codecs;
|
||||
struct hci_vnd_codecs vnd_codecs;
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_LOCAL_PAIRING_OPTS 0x100c
|
||||
struct hci_rp_read_local_pairing_opts {
|
||||
@ -1315,6 +1381,54 @@ struct hci_rp_read_local_pairing_opts {
|
||||
__u8 max_key_size;
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_LOCAL_CODECS_V2 0x100d
|
||||
struct hci_std_codec_v2 {
|
||||
__u8 id;
|
||||
__u8 transport;
|
||||
} __packed;
|
||||
|
||||
struct hci_std_codecs_v2 {
|
||||
__u8 num;
|
||||
struct hci_std_codec_v2 codec[];
|
||||
} __packed;
|
||||
|
||||
struct hci_vnd_codec_v2 {
|
||||
__u8 id;
|
||||
__le16 cid;
|
||||
__le16 vid;
|
||||
__u8 transport;
|
||||
} __packed;
|
||||
|
||||
struct hci_vnd_codecs_v2 {
|
||||
__u8 num;
|
||||
struct hci_vnd_codec_v2 codec[];
|
||||
} __packed;
|
||||
|
||||
struct hci_rp_read_local_supported_codecs_v2 {
|
||||
__u8 status;
|
||||
struct hci_std_codecs_v2 std_codecs;
|
||||
struct hci_vnd_codecs_v2 vendor_codecs;
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_LOCAL_CODEC_CAPS 0x100e
|
||||
struct hci_op_read_local_codec_caps {
|
||||
__u8 id;
|
||||
__le16 cid;
|
||||
__le16 vid;
|
||||
__u8 transport;
|
||||
__u8 direction;
|
||||
} __packed;
|
||||
|
||||
struct hci_codec_caps {
|
||||
__u8 len;
|
||||
__u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct hci_rp_read_local_codec_caps {
|
||||
__u8 status;
|
||||
__u8 num_caps;
|
||||
} __packed;
|
||||
|
||||
#define HCI_OP_READ_PAGE_SCAN_ACTIVITY 0x0c1b
|
||||
struct hci_rp_read_page_scan_activity {
|
||||
__u8 status;
|
||||
@ -2551,6 +2665,9 @@ static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb)
|
||||
#define hci_iso_data_len(h) ((h) & 0x3fff)
|
||||
#define hci_iso_data_flags(h) ((h) >> 14)
|
||||
|
||||
/* codec transport types */
|
||||
#define HCI_TRANSPORT_SCO_ESCO 0x01
|
||||
|
||||
/* le24 support */
|
||||
static inline void hci_cpu_to_le24(__u32 val, __u8 dst[3])
|
||||
{
|
||||
|
@ -131,6 +131,17 @@ struct bdaddr_list {
|
||||
u8 bdaddr_type;
|
||||
};
|
||||
|
||||
struct codec_list {
|
||||
struct list_head list;
|
||||
u8 id;
|
||||
__u16 cid;
|
||||
__u16 vid;
|
||||
u8 transport;
|
||||
u8 num_caps;
|
||||
u32 len;
|
||||
struct hci_codec_caps caps[];
|
||||
};
|
||||
|
||||
struct bdaddr_list_with_irk {
|
||||
struct list_head list;
|
||||
bdaddr_t bdaddr;
|
||||
@ -536,6 +547,7 @@ struct hci_dev {
|
||||
struct list_head pend_le_conns;
|
||||
struct list_head pend_le_reports;
|
||||
struct list_head blocked_keys;
|
||||
struct list_head local_codecs;
|
||||
|
||||
struct hci_dev_stats stat;
|
||||
|
||||
@ -605,7 +617,12 @@ struct hci_dev {
|
||||
int (*set_diag)(struct hci_dev *hdev, bool enable);
|
||||
int (*set_bdaddr)(struct hci_dev *hdev, const bdaddr_t *bdaddr);
|
||||
void (*cmd_timeout)(struct hci_dev *hdev);
|
||||
bool (*prevent_wake)(struct hci_dev *hdev);
|
||||
bool (*wakeup)(struct hci_dev *hdev);
|
||||
int (*set_quality_report)(struct hci_dev *hdev, bool enable);
|
||||
int (*get_data_path_id)(struct hci_dev *hdev, __u8 *data_path);
|
||||
int (*get_codec_config_data)(struct hci_dev *hdev, __u8 type,
|
||||
struct bt_codec *codec, __u8 *vnd_len,
|
||||
__u8 **vnd_data);
|
||||
};
|
||||
|
||||
#define HCI_PHY_HANDLE(handle) (handle & 0xff)
|
||||
@ -699,6 +716,7 @@ struct hci_conn {
|
||||
struct amp_mgr *amp_mgr;
|
||||
|
||||
struct hci_conn *link;
|
||||
struct bt_codec codec;
|
||||
|
||||
void (*connect_cfm_cb) (struct hci_conn *conn, u8 status);
|
||||
void (*security_cfm_cb) (struct hci_conn *conn, u8 status);
|
||||
@ -760,6 +778,7 @@ extern struct mutex hci_cb_list_lock;
|
||||
hci_dev_clear_flag(hdev, HCI_LE_ADV); \
|
||||
hci_dev_clear_flag(hdev, HCI_LL_RPA_RESOLUTION);\
|
||||
hci_dev_clear_flag(hdev, HCI_PERIODIC_INQ); \
|
||||
hci_dev_clear_flag(hdev, HCI_QUALITY_REPORT); \
|
||||
} while (0)
|
||||
|
||||
/* ----- HCI interface to upper protocols ----- */
|
||||
@ -1099,13 +1118,14 @@ struct hci_conn *hci_connect_le_scan(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
u16 conn_timeout,
|
||||
enum conn_reasons conn_reason);
|
||||
struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
u8 dst_type, u8 sec_level, u16 conn_timeout,
|
||||
u8 role, bdaddr_t *direct_rpa);
|
||||
u8 dst_type, bool dst_resolved, u8 sec_level,
|
||||
u16 conn_timeout, u8 role,
|
||||
bdaddr_t *direct_rpa);
|
||||
struct hci_conn *hci_connect_acl(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
u8 sec_level, u8 auth_type,
|
||||
enum conn_reasons conn_reason);
|
||||
struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
|
||||
__u16 setting);
|
||||
__u16 setting, struct bt_codec *codec);
|
||||
int hci_conn_check_link_mode(struct hci_conn *conn);
|
||||
int hci_conn_check_secure(struct hci_conn *conn, __u8 sec_level);
|
||||
int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type,
|
||||
@ -1360,6 +1380,8 @@ int hci_set_adv_instance_data(struct hci_dev *hdev, u8 instance,
|
||||
u16 scan_rsp_len, u8 *scan_rsp_data);
|
||||
int hci_remove_adv_instance(struct hci_dev *hdev, u8 instance);
|
||||
void hci_adv_instances_set_rpa_expired(struct hci_dev *hdev, bool rpa_expired);
|
||||
u32 hci_adv_instance_flags(struct hci_dev *hdev, u8 instance);
|
||||
bool hci_adv_instance_is_scannable(struct hci_dev *hdev, u8 instance);
|
||||
|
||||
void hci_adv_monitors_clear(struct hci_dev *hdev);
|
||||
void hci_free_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor);
|
||||
@ -1442,6 +1464,9 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
|
||||
/* Use LL Privacy based address resolution if supported */
|
||||
#define use_ll_privacy(dev) ((dev)->le_features[0] & HCI_LE_LL_PRIVACY)
|
||||
|
||||
/* Use enhanced synchronous connection if command is supported */
|
||||
#define enhanced_sco_capable(dev) ((dev)->commands[29] & 0x08)
|
||||
|
||||
/* Use ext scanning if set ext scan param and ext scan enable is supported */
|
||||
#define use_ext_scan(dev) (((dev)->commands[37] & 0x20) && \
|
||||
((dev)->commands[37] & 0x40))
|
||||
@ -1609,43 +1634,6 @@ static inline void hci_role_switch_cfm(struct hci_conn *conn, __u8 status,
|
||||
mutex_unlock(&hci_cb_list_lock);
|
||||
}
|
||||
|
||||
static inline void *eir_get_data(u8 *eir, size_t eir_len, u8 type,
|
||||
size_t *data_len)
|
||||
{
|
||||
size_t parsed = 0;
|
||||
|
||||
if (eir_len < 2)
|
||||
return NULL;
|
||||
|
||||
while (parsed < eir_len - 1) {
|
||||
u8 field_len = eir[0];
|
||||
|
||||
if (field_len == 0)
|
||||
break;
|
||||
|
||||
parsed += field_len + 1;
|
||||
|
||||
if (parsed > eir_len)
|
||||
break;
|
||||
|
||||
if (eir[1] != type) {
|
||||
eir += field_len + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Zero length data */
|
||||
if (field_len == 1)
|
||||
return NULL;
|
||||
|
||||
if (data_len)
|
||||
*data_len = field_len - 1;
|
||||
|
||||
return &eir[2];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline bool hci_bdaddr_is_rpa(bdaddr_t *bdaddr, u8 addr_type)
|
||||
{
|
||||
if (addr_type != ADDR_LE_DEV_RANDOM)
|
||||
@ -1867,4 +1855,9 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
|
||||
#define SCO_AIRMODE_CVSD 0x0000
|
||||
#define SCO_AIRMODE_TRANSP 0x0003
|
||||
|
||||
#define LOCAL_CODEC_ACL_MASK BIT(0)
|
||||
#define LOCAL_CODEC_SCO_MASK BIT(1)
|
||||
|
||||
#define TRANSPORT_TYPE_MAX 0x04
|
||||
|
||||
#endif /* __HCI_CORE_H */
|
||||
|
@ -14,7 +14,8 @@ bluetooth_6lowpan-y := 6lowpan.o
|
||||
|
||||
bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
|
||||
hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \
|
||||
ecdh_helper.o hci_request.o mgmt_util.o mgmt_config.o
|
||||
ecdh_helper.o hci_request.o mgmt_util.o mgmt_config.o hci_codec.o \
|
||||
eir.o
|
||||
|
||||
bluetooth-$(CONFIG_BT_BREDR) += sco.o
|
||||
bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o
|
||||
|
335
net/bluetooth/eir.c
Normal file
335
net/bluetooth/eir.c
Normal file
@ -0,0 +1,335 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* BlueZ - Bluetooth protocol stack for Linux
|
||||
*
|
||||
* Copyright (C) 2021 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include <net/bluetooth/mgmt.h>
|
||||
|
||||
#include "eir.h"
|
||||
|
||||
#define PNP_INFO_SVCLASS_ID 0x1200
|
||||
|
||||
u8 eir_append_local_name(struct hci_dev *hdev, u8 *ptr, u8 ad_len)
|
||||
{
|
||||
size_t short_len;
|
||||
size_t complete_len;
|
||||
|
||||
/* no space left for name (+ NULL + type + len) */
|
||||
if ((HCI_MAX_AD_LENGTH - ad_len) < HCI_MAX_SHORT_NAME_LENGTH + 3)
|
||||
return ad_len;
|
||||
|
||||
/* use complete name if present and fits */
|
||||
complete_len = strlen(hdev->dev_name);
|
||||
if (complete_len && complete_len <= HCI_MAX_SHORT_NAME_LENGTH)
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_COMPLETE,
|
||||
hdev->dev_name, complete_len + 1);
|
||||
|
||||
/* use short name if present */
|
||||
short_len = strlen(hdev->short_name);
|
||||
if (short_len)
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_SHORT,
|
||||
hdev->short_name, short_len + 1);
|
||||
|
||||
/* use shortened full name if present, we already know that name
|
||||
* is longer then HCI_MAX_SHORT_NAME_LENGTH
|
||||
*/
|
||||
if (complete_len) {
|
||||
u8 name[HCI_MAX_SHORT_NAME_LENGTH + 1];
|
||||
|
||||
memcpy(name, hdev->dev_name, HCI_MAX_SHORT_NAME_LENGTH);
|
||||
name[HCI_MAX_SHORT_NAME_LENGTH] = '\0';
|
||||
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_SHORT, name,
|
||||
sizeof(name));
|
||||
}
|
||||
|
||||
return ad_len;
|
||||
}
|
||||
|
||||
u8 eir_append_appearance(struct hci_dev *hdev, u8 *ptr, u8 ad_len)
|
||||
{
|
||||
return eir_append_le16(ptr, ad_len, EIR_APPEARANCE, hdev->appearance);
|
||||
}
|
||||
|
||||
static u8 *create_uuid16_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 4)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
u16 uuid16;
|
||||
|
||||
if (uuid->size != 16)
|
||||
continue;
|
||||
|
||||
uuid16 = get_unaligned_le16(&uuid->uuid[12]);
|
||||
if (uuid16 < 0x1100)
|
||||
continue;
|
||||
|
||||
if (uuid16 == PNP_INFO_SVCLASS_ID)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID16_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + sizeof(u16) > len) {
|
||||
uuids_start[1] = EIR_UUID16_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
*ptr++ = (uuid16 & 0x00ff);
|
||||
*ptr++ = (uuid16 & 0xff00) >> 8;
|
||||
uuids_start[0] += sizeof(uuid16);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static u8 *create_uuid32_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 6)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
if (uuid->size != 32)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID32_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + sizeof(u32) > len) {
|
||||
uuids_start[1] = EIR_UUID32_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(ptr, &uuid->uuid[12], sizeof(u32));
|
||||
ptr += sizeof(u32);
|
||||
uuids_start[0] += sizeof(u32);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 18)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
if (uuid->size != 128)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID128_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + 16 > len) {
|
||||
uuids_start[1] = EIR_UUID128_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(ptr, uuid->uuid, 16);
|
||||
ptr += 16;
|
||||
uuids_start[0] += 16;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void eir_create(struct hci_dev *hdev, u8 *data)
|
||||
{
|
||||
u8 *ptr = data;
|
||||
size_t name_len;
|
||||
|
||||
name_len = strlen(hdev->dev_name);
|
||||
|
||||
if (name_len > 0) {
|
||||
/* EIR Data type */
|
||||
if (name_len > 48) {
|
||||
name_len = 48;
|
||||
ptr[1] = EIR_NAME_SHORT;
|
||||
} else {
|
||||
ptr[1] = EIR_NAME_COMPLETE;
|
||||
}
|
||||
|
||||
/* EIR Data length */
|
||||
ptr[0] = name_len + 1;
|
||||
|
||||
memcpy(ptr + 2, hdev->dev_name, name_len);
|
||||
|
||||
ptr += (name_len + 2);
|
||||
}
|
||||
|
||||
if (hdev->inq_tx_power != HCI_TX_POWER_INVALID) {
|
||||
ptr[0] = 2;
|
||||
ptr[1] = EIR_TX_POWER;
|
||||
ptr[2] = (u8)hdev->inq_tx_power;
|
||||
|
||||
ptr += 3;
|
||||
}
|
||||
|
||||
if (hdev->devid_source > 0) {
|
||||
ptr[0] = 9;
|
||||
ptr[1] = EIR_DEVICE_ID;
|
||||
|
||||
put_unaligned_le16(hdev->devid_source, ptr + 2);
|
||||
put_unaligned_le16(hdev->devid_vendor, ptr + 4);
|
||||
put_unaligned_le16(hdev->devid_product, ptr + 6);
|
||||
put_unaligned_le16(hdev->devid_version, ptr + 8);
|
||||
|
||||
ptr += 10;
|
||||
}
|
||||
|
||||
ptr = create_uuid16_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
ptr = create_uuid32_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
ptr = create_uuid128_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
}
|
||||
|
||||
u8 eir_create_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
|
||||
{
|
||||
struct adv_info *adv = NULL;
|
||||
u8 ad_len = 0, flags = 0;
|
||||
u32 instance_flags;
|
||||
|
||||
/* Return 0 when the current instance identifier is invalid. */
|
||||
if (instance) {
|
||||
adv = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
instance_flags = hci_adv_instance_flags(hdev, instance);
|
||||
|
||||
/* If instance already has the flags set skip adding it once
|
||||
* again.
|
||||
*/
|
||||
if (adv && eir_get_data(adv->adv_data, adv->adv_data_len, EIR_FLAGS,
|
||||
NULL))
|
||||
goto skip_flags;
|
||||
|
||||
/* The Add Advertising command allows userspace to set both the general
|
||||
* and limited discoverable flags.
|
||||
*/
|
||||
if (instance_flags & MGMT_ADV_FLAG_DISCOV)
|
||||
flags |= LE_AD_GENERAL;
|
||||
|
||||
if (instance_flags & MGMT_ADV_FLAG_LIMITED_DISCOV)
|
||||
flags |= LE_AD_LIMITED;
|
||||
|
||||
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
||||
flags |= LE_AD_NO_BREDR;
|
||||
|
||||
if (flags || (instance_flags & MGMT_ADV_FLAG_MANAGED_FLAGS)) {
|
||||
/* If a discovery flag wasn't provided, simply use the global
|
||||
* settings.
|
||||
*/
|
||||
if (!flags)
|
||||
flags |= mgmt_get_adv_discov_flags(hdev);
|
||||
|
||||
/* If flags would still be empty, then there is no need to
|
||||
* include the "Flags" AD field".
|
||||
*/
|
||||
if (flags) {
|
||||
ptr[0] = 0x02;
|
||||
ptr[1] = EIR_FLAGS;
|
||||
ptr[2] = flags;
|
||||
|
||||
ad_len += 3;
|
||||
ptr += 3;
|
||||
}
|
||||
}
|
||||
|
||||
skip_flags:
|
||||
if (adv) {
|
||||
memcpy(ptr, adv->adv_data, adv->adv_data_len);
|
||||
ad_len += adv->adv_data_len;
|
||||
ptr += adv->adv_data_len;
|
||||
}
|
||||
|
||||
if (instance_flags & MGMT_ADV_FLAG_TX_POWER) {
|
||||
s8 adv_tx_power;
|
||||
|
||||
if (ext_adv_capable(hdev)) {
|
||||
if (adv)
|
||||
adv_tx_power = adv->tx_power;
|
||||
else
|
||||
adv_tx_power = hdev->adv_tx_power;
|
||||
} else {
|
||||
adv_tx_power = hdev->adv_tx_power;
|
||||
}
|
||||
|
||||
/* Provide Tx Power only if we can provide a valid value for it */
|
||||
if (adv_tx_power != HCI_TX_POWER_INVALID) {
|
||||
ptr[0] = 0x02;
|
||||
ptr[1] = EIR_TX_POWER;
|
||||
ptr[2] = (u8)adv_tx_power;
|
||||
|
||||
ad_len += 3;
|
||||
ptr += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return ad_len;
|
||||
}
|
||||
|
||||
static u8 create_default_scan_rsp(struct hci_dev *hdev, u8 *ptr)
|
||||
{
|
||||
u8 scan_rsp_len = 0;
|
||||
|
||||
if (hdev->appearance)
|
||||
scan_rsp_len = eir_append_appearance(hdev, ptr, scan_rsp_len);
|
||||
|
||||
return eir_append_local_name(hdev, ptr, scan_rsp_len);
|
||||
}
|
||||
|
||||
u8 eir_create_scan_rsp(struct hci_dev *hdev, u8 instance, u8 *ptr)
|
||||
{
|
||||
struct adv_info *adv;
|
||||
u8 scan_rsp_len = 0;
|
||||
|
||||
if (!instance)
|
||||
return create_default_scan_rsp(hdev, ptr);
|
||||
|
||||
adv = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv)
|
||||
return 0;
|
||||
|
||||
if ((adv->flags & MGMT_ADV_FLAG_APPEARANCE) && hdev->appearance)
|
||||
scan_rsp_len = eir_append_appearance(hdev, ptr, scan_rsp_len);
|
||||
|
||||
memcpy(&ptr[scan_rsp_len], adv->scan_rsp_data, adv->scan_rsp_len);
|
||||
|
||||
scan_rsp_len += adv->scan_rsp_len;
|
||||
|
||||
if (adv->flags & MGMT_ADV_FLAG_LOCAL_NAME)
|
||||
scan_rsp_len = eir_append_local_name(hdev, ptr, scan_rsp_len);
|
||||
|
||||
return scan_rsp_len;
|
||||
}
|
72
net/bluetooth/eir.h
Normal file
72
net/bluetooth/eir.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* BlueZ - Bluetooth protocol stack for Linux
|
||||
*
|
||||
* Copyright (C) 2021 Intel Corporation
|
||||
*/
|
||||
|
||||
void eir_create(struct hci_dev *hdev, u8 *data);
|
||||
|
||||
u8 eir_create_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr);
|
||||
u8 eir_create_scan_rsp(struct hci_dev *hdev, u8 instance, u8 *ptr);
|
||||
|
||||
u8 eir_append_local_name(struct hci_dev *hdev, u8 *eir, u8 ad_len);
|
||||
u8 eir_append_appearance(struct hci_dev *hdev, u8 *ptr, u8 ad_len);
|
||||
|
||||
static inline u16 eir_append_data(u8 *eir, u16 eir_len, u8 type,
|
||||
u8 *data, u8 data_len)
|
||||
{
|
||||
eir[eir_len++] = sizeof(type) + data_len;
|
||||
eir[eir_len++] = type;
|
||||
memcpy(&eir[eir_len], data, data_len);
|
||||
eir_len += data_len;
|
||||
|
||||
return eir_len;
|
||||
}
|
||||
|
||||
static inline u16 eir_append_le16(u8 *eir, u16 eir_len, u8 type, u16 data)
|
||||
{
|
||||
eir[eir_len++] = sizeof(type) + sizeof(data);
|
||||
eir[eir_len++] = type;
|
||||
put_unaligned_le16(data, &eir[eir_len]);
|
||||
eir_len += sizeof(data);
|
||||
|
||||
return eir_len;
|
||||
}
|
||||
|
||||
static inline void *eir_get_data(u8 *eir, size_t eir_len, u8 type,
|
||||
size_t *data_len)
|
||||
{
|
||||
size_t parsed = 0;
|
||||
|
||||
if (eir_len < 2)
|
||||
return NULL;
|
||||
|
||||
while (parsed < eir_len - 1) {
|
||||
u8 field_len = eir[0];
|
||||
|
||||
if (field_len == 0)
|
||||
break;
|
||||
|
||||
parsed += field_len + 1;
|
||||
|
||||
if (parsed > eir_len)
|
||||
break;
|
||||
|
||||
if (eir[1] != type) {
|
||||
eir += field_len + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Zero length data */
|
||||
if (field_len == 1)
|
||||
return NULL;
|
||||
|
||||
if (data_len)
|
||||
*data_len = field_len - 1;
|
||||
|
||||
return &eir[2];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
238
net/bluetooth/hci_codec.c
Normal file
238
net/bluetooth/hci_codec.c
Normal file
@ -0,0 +1,238 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (C) 2021 Intel Corporation */
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include "hci_codec.h"
|
||||
|
||||
static int hci_codec_list_add(struct list_head *list,
|
||||
struct hci_op_read_local_codec_caps *sent,
|
||||
struct hci_rp_read_local_codec_caps *rp,
|
||||
void *caps,
|
||||
__u32 len)
|
||||
{
|
||||
struct codec_list *entry;
|
||||
|
||||
entry = kzalloc(sizeof(*entry) + len, GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
entry->id = sent->id;
|
||||
if (sent->id == 0xFF) {
|
||||
entry->cid = __le16_to_cpu(sent->cid);
|
||||
entry->vid = __le16_to_cpu(sent->vid);
|
||||
}
|
||||
entry->transport = sent->transport;
|
||||
entry->len = len;
|
||||
entry->num_caps = rp->num_caps;
|
||||
if (rp->num_caps)
|
||||
memcpy(entry->caps, caps, len);
|
||||
list_add(&entry->list, list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hci_codec_list_clear(struct list_head *codec_list)
|
||||
{
|
||||
struct codec_list *c, *n;
|
||||
|
||||
list_for_each_entry_safe(c, n, codec_list, list) {
|
||||
list_del(&c->list);
|
||||
kfree(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void hci_read_codec_capabilities(struct hci_dev *hdev, __u8 transport,
|
||||
struct hci_op_read_local_codec_caps
|
||||
*cmd)
|
||||
{
|
||||
__u8 i;
|
||||
|
||||
for (i = 0; i < TRANSPORT_TYPE_MAX; i++) {
|
||||
if (transport & BIT(i)) {
|
||||
struct hci_rp_read_local_codec_caps *rp;
|
||||
struct hci_codec_caps *caps;
|
||||
struct sk_buff *skb;
|
||||
__u8 j;
|
||||
__u32 len;
|
||||
|
||||
cmd->transport = i;
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_CODEC_CAPS,
|
||||
sizeof(*cmd), cmd,
|
||||
HCI_CMD_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Failed to read codec capabilities (%ld)",
|
||||
PTR_ERR(skb));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (skb->len < sizeof(*rp))
|
||||
goto error;
|
||||
|
||||
rp = (void *)skb->data;
|
||||
|
||||
if (rp->status)
|
||||
goto error;
|
||||
|
||||
if (!rp->num_caps) {
|
||||
len = 0;
|
||||
/* this codec doesn't have capabilities */
|
||||
goto skip_caps_parse;
|
||||
}
|
||||
|
||||
skb_pull(skb, sizeof(*rp));
|
||||
|
||||
for (j = 0, len = 0; j < rp->num_caps; j++) {
|
||||
caps = (void *)skb->data;
|
||||
if (skb->len < sizeof(*caps))
|
||||
goto error;
|
||||
if (skb->len < caps->len)
|
||||
goto error;
|
||||
len += sizeof(caps->len) + caps->len;
|
||||
skb_pull(skb, sizeof(caps->len) + caps->len);
|
||||
}
|
||||
|
||||
skip_caps_parse:
|
||||
hci_dev_lock(hdev);
|
||||
hci_codec_list_add(&hdev->local_codecs, cmd, rp,
|
||||
(__u8 *)rp + sizeof(*rp), len);
|
||||
hci_dev_unlock(hdev);
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hci_read_supported_codecs(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct hci_rp_read_local_supported_codecs *rp;
|
||||
struct hci_std_codecs *std_codecs;
|
||||
struct hci_vnd_codecs *vnd_codecs;
|
||||
struct hci_op_read_local_codec_caps caps;
|
||||
__u8 i;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_CODECS, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Failed to read local supported codecs (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return;
|
||||
}
|
||||
|
||||
if (skb->len < sizeof(*rp))
|
||||
goto error;
|
||||
|
||||
rp = (void *)skb->data;
|
||||
|
||||
if (rp->status)
|
||||
goto error;
|
||||
|
||||
skb_pull(skb, sizeof(rp->status));
|
||||
|
||||
std_codecs = (void *)skb->data;
|
||||
|
||||
/* validate codecs length before accessing */
|
||||
if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)
|
||||
+ sizeof(std_codecs->num))
|
||||
goto error;
|
||||
|
||||
/* enumerate codec capabilities of standard codecs */
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
for (i = 0; i < std_codecs->num; i++) {
|
||||
caps.id = std_codecs->codec[i];
|
||||
caps.direction = 0x00;
|
||||
hci_read_codec_capabilities(hdev, LOCAL_CODEC_ACL_MASK, &caps);
|
||||
}
|
||||
|
||||
skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)
|
||||
+ sizeof(std_codecs->num));
|
||||
|
||||
vnd_codecs = (void *)skb->data;
|
||||
|
||||
/* validate vendor codecs length before accessing */
|
||||
if (skb->len <
|
||||
flex_array_size(vnd_codecs, codec, vnd_codecs->num)
|
||||
+ sizeof(vnd_codecs->num))
|
||||
goto error;
|
||||
|
||||
/* enumerate vendor codec capabilities */
|
||||
for (i = 0; i < vnd_codecs->num; i++) {
|
||||
caps.id = 0xFF;
|
||||
caps.cid = vnd_codecs->codec[i].cid;
|
||||
caps.vid = vnd_codecs->codec[i].vid;
|
||||
caps.direction = 0x00;
|
||||
hci_read_codec_capabilities(hdev, LOCAL_CODEC_ACL_MASK, &caps);
|
||||
}
|
||||
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
void hci_read_supported_codecs_v2(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct hci_rp_read_local_supported_codecs_v2 *rp;
|
||||
struct hci_std_codecs_v2 *std_codecs;
|
||||
struct hci_vnd_codecs_v2 *vnd_codecs;
|
||||
struct hci_op_read_local_codec_caps caps;
|
||||
__u8 i;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_CODECS_V2, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
|
||||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Failed to read local supported codecs (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return;
|
||||
}
|
||||
|
||||
if (skb->len < sizeof(*rp))
|
||||
goto error;
|
||||
|
||||
rp = (void *)skb->data;
|
||||
|
||||
if (rp->status)
|
||||
goto error;
|
||||
|
||||
skb_pull(skb, sizeof(rp->status));
|
||||
|
||||
std_codecs = (void *)skb->data;
|
||||
|
||||
/* check for payload data length before accessing */
|
||||
if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)
|
||||
+ sizeof(std_codecs->num))
|
||||
goto error;
|
||||
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
|
||||
for (i = 0; i < std_codecs->num; i++) {
|
||||
caps.id = std_codecs->codec[i].id;
|
||||
hci_read_codec_capabilities(hdev, std_codecs->codec[i].transport,
|
||||
&caps);
|
||||
}
|
||||
|
||||
skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)
|
||||
+ sizeof(std_codecs->num));
|
||||
|
||||
vnd_codecs = (void *)skb->data;
|
||||
|
||||
/* check for payload data length before accessing */
|
||||
if (skb->len <
|
||||
flex_array_size(vnd_codecs, codec, vnd_codecs->num)
|
||||
+ sizeof(vnd_codecs->num))
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < vnd_codecs->num; i++) {
|
||||
caps.id = 0xFF;
|
||||
caps.cid = vnd_codecs->codec[i].cid;
|
||||
caps.vid = vnd_codecs->codec[i].vid;
|
||||
hci_read_codec_capabilities(hdev, vnd_codecs->codec[i].transport,
|
||||
&caps);
|
||||
}
|
||||
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
}
|
7
net/bluetooth/hci_codec.h
Normal file
7
net/bluetooth/hci_codec.h
Normal file
@ -0,0 +1,7 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (C) 2014 Intel Corporation */
|
||||
|
||||
void hci_read_supported_codecs(struct hci_dev *hdev);
|
||||
void hci_read_supported_codecs_v2(struct hci_dev *hdev);
|
||||
void hci_codec_list_clear(struct list_head *codec_list);
|
@ -307,13 +307,133 @@ static bool find_next_esco_param(struct hci_conn *conn,
|
||||
return conn->attempt <= size;
|
||||
}
|
||||
|
||||
bool hci_setup_sync(struct hci_conn *conn, __u16 handle)
|
||||
static bool hci_enhanced_setup_sync_conn(struct hci_conn *conn, __u16 handle)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
struct hci_cp_enhanced_setup_sync_conn cp;
|
||||
const struct sco_param *param;
|
||||
|
||||
bt_dev_dbg(hdev, "hcon %p", conn);
|
||||
|
||||
/* for offload use case, codec needs to configured before opening SCO */
|
||||
if (conn->codec.data_path)
|
||||
hci_req_configure_datapath(hdev, &conn->codec);
|
||||
|
||||
conn->state = BT_CONNECT;
|
||||
conn->out = true;
|
||||
|
||||
conn->attempt++;
|
||||
|
||||
memset(&cp, 0x00, sizeof(cp));
|
||||
|
||||
cp.handle = cpu_to_le16(handle);
|
||||
|
||||
cp.tx_bandwidth = cpu_to_le32(0x00001f40);
|
||||
cp.rx_bandwidth = cpu_to_le32(0x00001f40);
|
||||
|
||||
switch (conn->codec.id) {
|
||||
case BT_CODEC_MSBC:
|
||||
if (!find_next_esco_param(conn, esco_param_msbc,
|
||||
ARRAY_SIZE(esco_param_msbc)))
|
||||
return false;
|
||||
|
||||
param = &esco_param_msbc[conn->attempt - 1];
|
||||
cp.tx_coding_format.id = 0x05;
|
||||
cp.rx_coding_format.id = 0x05;
|
||||
cp.tx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.rx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.in_bandwidth = __cpu_to_le32(32000);
|
||||
cp.out_bandwidth = __cpu_to_le32(32000);
|
||||
cp.in_coding_format.id = 0x04;
|
||||
cp.out_coding_format.id = 0x04;
|
||||
cp.in_coded_data_size = __cpu_to_le16(16);
|
||||
cp.out_coded_data_size = __cpu_to_le16(16);
|
||||
cp.in_pcm_data_format = 2;
|
||||
cp.out_pcm_data_format = 2;
|
||||
cp.in_pcm_sample_payload_msb_pos = 0;
|
||||
cp.out_pcm_sample_payload_msb_pos = 0;
|
||||
cp.in_data_path = conn->codec.data_path;
|
||||
cp.out_data_path = conn->codec.data_path;
|
||||
cp.in_transport_unit_size = 1;
|
||||
cp.out_transport_unit_size = 1;
|
||||
break;
|
||||
|
||||
case BT_CODEC_TRANSPARENT:
|
||||
if (!find_next_esco_param(conn, esco_param_msbc,
|
||||
ARRAY_SIZE(esco_param_msbc)))
|
||||
return false;
|
||||
param = &esco_param_msbc[conn->attempt - 1];
|
||||
cp.tx_coding_format.id = 0x03;
|
||||
cp.rx_coding_format.id = 0x03;
|
||||
cp.tx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.rx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.in_bandwidth = __cpu_to_le32(0x1f40);
|
||||
cp.out_bandwidth = __cpu_to_le32(0x1f40);
|
||||
cp.in_coding_format.id = 0x03;
|
||||
cp.out_coding_format.id = 0x03;
|
||||
cp.in_coded_data_size = __cpu_to_le16(16);
|
||||
cp.out_coded_data_size = __cpu_to_le16(16);
|
||||
cp.in_pcm_data_format = 2;
|
||||
cp.out_pcm_data_format = 2;
|
||||
cp.in_pcm_sample_payload_msb_pos = 0;
|
||||
cp.out_pcm_sample_payload_msb_pos = 0;
|
||||
cp.in_data_path = conn->codec.data_path;
|
||||
cp.out_data_path = conn->codec.data_path;
|
||||
cp.in_transport_unit_size = 1;
|
||||
cp.out_transport_unit_size = 1;
|
||||
break;
|
||||
|
||||
case BT_CODEC_CVSD:
|
||||
if (lmp_esco_capable(conn->link)) {
|
||||
if (!find_next_esco_param(conn, esco_param_cvsd,
|
||||
ARRAY_SIZE(esco_param_cvsd)))
|
||||
return false;
|
||||
param = &esco_param_cvsd[conn->attempt - 1];
|
||||
} else {
|
||||
if (conn->attempt > ARRAY_SIZE(sco_param_cvsd))
|
||||
return false;
|
||||
param = &sco_param_cvsd[conn->attempt - 1];
|
||||
}
|
||||
cp.tx_coding_format.id = 2;
|
||||
cp.rx_coding_format.id = 2;
|
||||
cp.tx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.rx_codec_frame_size = __cpu_to_le16(60);
|
||||
cp.in_bandwidth = __cpu_to_le32(16000);
|
||||
cp.out_bandwidth = __cpu_to_le32(16000);
|
||||
cp.in_coding_format.id = 4;
|
||||
cp.out_coding_format.id = 4;
|
||||
cp.in_coded_data_size = __cpu_to_le16(16);
|
||||
cp.out_coded_data_size = __cpu_to_le16(16);
|
||||
cp.in_pcm_data_format = 2;
|
||||
cp.out_pcm_data_format = 2;
|
||||
cp.in_pcm_sample_payload_msb_pos = 0;
|
||||
cp.out_pcm_sample_payload_msb_pos = 0;
|
||||
cp.in_data_path = conn->codec.data_path;
|
||||
cp.out_data_path = conn->codec.data_path;
|
||||
cp.in_transport_unit_size = 16;
|
||||
cp.out_transport_unit_size = 16;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
cp.retrans_effort = param->retrans_effort;
|
||||
cp.pkt_type = __cpu_to_le16(param->pkt_type);
|
||||
cp.max_latency = __cpu_to_le16(param->max_latency);
|
||||
|
||||
if (hci_send_cmd(hdev, HCI_OP_ENHANCED_SETUP_SYNC_CONN, sizeof(cp), &cp) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
struct hci_cp_setup_sync_conn cp;
|
||||
const struct sco_param *param;
|
||||
|
||||
BT_DBG("hcon %p", conn);
|
||||
bt_dev_dbg(hdev, "hcon %p", conn);
|
||||
|
||||
conn->state = BT_CONNECT;
|
||||
conn->out = true;
|
||||
@ -359,6 +479,14 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hci_setup_sync(struct hci_conn *conn, __u16 handle)
|
||||
{
|
||||
if (enhanced_sco_capable(conn->hdev))
|
||||
return hci_enhanced_setup_sync_conn(conn, handle);
|
||||
|
||||
return hci_setup_sync_conn(conn, handle);
|
||||
}
|
||||
|
||||
u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
|
||||
u16 to_multiplier)
|
||||
{
|
||||
@ -1040,8 +1168,8 @@ static void hci_req_directed_advertising(struct hci_request *req,
|
||||
}
|
||||
|
||||
struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
u8 dst_type, u8 sec_level, u16 conn_timeout,
|
||||
u8 role, bdaddr_t *direct_rpa)
|
||||
u8 dst_type, bool dst_resolved, u8 sec_level,
|
||||
u16 conn_timeout, u8 role, bdaddr_t *direct_rpa)
|
||||
{
|
||||
struct hci_conn_params *params;
|
||||
struct hci_conn *conn;
|
||||
@ -1078,19 +1206,24 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
/* When given an identity address with existing identity
|
||||
* resolving key, the connection needs to be established
|
||||
* to a resolvable random address.
|
||||
*
|
||||
* Storing the resolvable random address is required here
|
||||
* to handle connection failures. The address will later
|
||||
* be resolved back into the original identity address
|
||||
* from the connect request.
|
||||
/* Check if the destination address has been resolved by the controller
|
||||
* since if it did then the identity address shall be used.
|
||||
*/
|
||||
irk = hci_find_irk_by_addr(hdev, dst, dst_type);
|
||||
if (irk && bacmp(&irk->rpa, BDADDR_ANY)) {
|
||||
dst = &irk->rpa;
|
||||
dst_type = ADDR_LE_DEV_RANDOM;
|
||||
if (!dst_resolved) {
|
||||
/* When given an identity address with existing identity
|
||||
* resolving key, the connection needs to be established
|
||||
* to a resolvable random address.
|
||||
*
|
||||
* Storing the resolvable random address is required here
|
||||
* to handle connection failures. The address will later
|
||||
* be resolved back into the original identity address
|
||||
* from the connect request.
|
||||
*/
|
||||
irk = hci_find_irk_by_addr(hdev, dst, dst_type);
|
||||
if (irk && bacmp(&irk->rpa, BDADDR_ANY)) {
|
||||
dst = &irk->rpa;
|
||||
dst_type = ADDR_LE_DEV_RANDOM;
|
||||
}
|
||||
}
|
||||
|
||||
if (conn) {
|
||||
@ -1319,7 +1452,7 @@ struct hci_conn *hci_connect_acl(struct hci_dev *hdev, bdaddr_t *dst,
|
||||
}
|
||||
|
||||
struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
|
||||
__u16 setting)
|
||||
__u16 setting, struct bt_codec *codec)
|
||||
{
|
||||
struct hci_conn *acl;
|
||||
struct hci_conn *sco;
|
||||
@ -1344,6 +1477,7 @@ struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
|
||||
hci_conn_hold(sco);
|
||||
|
||||
sco->setting = setting;
|
||||
sco->codec = *codec;
|
||||
|
||||
if (acl->state == BT_CONNECTED &&
|
||||
(sco->state == BT_OPEN || sco->state == BT_CLOSED)) {
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "leds.h"
|
||||
#include "msft.h"
|
||||
#include "aosp.h"
|
||||
#include "hci_codec.h"
|
||||
|
||||
static void hci_rx_work(struct work_struct *work);
|
||||
static void hci_cmd_work(struct work_struct *work);
|
||||
@ -61,130 +62,6 @@ DEFINE_MUTEX(hci_cb_list_lock);
|
||||
/* HCI ID Numbering */
|
||||
static DEFINE_IDA(hci_index_ida);
|
||||
|
||||
/* ---- HCI debugfs entries ---- */
|
||||
|
||||
static ssize_t dut_mode_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = hci_dev_test_flag(hdev, HCI_DUT_MODE) ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t dut_mode_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
struct sk_buff *skb;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
if (!test_bit(HCI_UP, &hdev->flags))
|
||||
return -ENETDOWN;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (enable == hci_dev_test_flag(hdev, HCI_DUT_MODE))
|
||||
return -EALREADY;
|
||||
|
||||
hci_req_sync_lock(hdev);
|
||||
if (enable)
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_ENABLE_DUT_MODE, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
else
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
hci_req_sync_unlock(hdev);
|
||||
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
hci_dev_change_flag(hdev, HCI_DUT_MODE);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations dut_mode_fops = {
|
||||
.open = simple_open,
|
||||
.read = dut_mode_read,
|
||||
.write = dut_mode_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t vendor_diag_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = hci_dev_test_flag(hdev, HCI_VENDOR_DIAG) ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t vendor_diag_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* When the diagnostic flags are not persistent and the transport
|
||||
* is not active or in user channel operation, then there is no need
|
||||
* for the vendor callback. Instead just store the desired value and
|
||||
* the setting will be programmed when the controller gets powered on.
|
||||
*/
|
||||
if (test_bit(HCI_QUIRK_NON_PERSISTENT_DIAG, &hdev->quirks) &&
|
||||
(!test_bit(HCI_RUNNING, &hdev->flags) ||
|
||||
hci_dev_test_flag(hdev, HCI_USER_CHANNEL)))
|
||||
goto done;
|
||||
|
||||
hci_req_sync_lock(hdev);
|
||||
err = hdev->set_diag(hdev, enable);
|
||||
hci_req_sync_unlock(hdev);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
done:
|
||||
if (enable)
|
||||
hci_dev_set_flag(hdev, HCI_VENDOR_DIAG);
|
||||
else
|
||||
hci_dev_clear_flag(hdev, HCI_VENDOR_DIAG);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations vendor_diag_fops = {
|
||||
.open = simple_open,
|
||||
.read = vendor_diag_read,
|
||||
.write = vendor_diag_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static void hci_debugfs_create_basic(struct hci_dev *hdev)
|
||||
{
|
||||
debugfs_create_file("dut_mode", 0644, hdev->debugfs, hdev,
|
||||
&dut_mode_fops);
|
||||
|
||||
if (hdev->set_diag)
|
||||
debugfs_create_file("vendor_diag", 0644, hdev->debugfs, hdev,
|
||||
&vendor_diag_fops);
|
||||
}
|
||||
|
||||
static int hci_reset_req(struct hci_request *req, unsigned long opt)
|
||||
{
|
||||
BT_DBG("%s %ld", req->hdev->name, opt);
|
||||
@ -838,10 +715,6 @@ static int hci_init4_req(struct hci_request *req, unsigned long opt)
|
||||
if (hdev->commands[22] & 0x04)
|
||||
hci_set_event_mask_page_2(req);
|
||||
|
||||
/* Read local codec list if the HCI command is supported */
|
||||
if (hdev->commands[29] & 0x20)
|
||||
hci_req_add(req, HCI_OP_READ_LOCAL_CODECS, 0, NULL);
|
||||
|
||||
/* Read local pairing options if the HCI command is supported */
|
||||
if (hdev->commands[41] & 0x08)
|
||||
hci_req_add(req, HCI_OP_READ_LOCAL_PAIRING_OPTS, 0, NULL);
|
||||
@ -937,6 +810,12 @@ static int __hci_init(struct hci_dev *hdev)
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Read local codec list if the HCI command is supported */
|
||||
if (hdev->commands[45] & 0x04)
|
||||
hci_read_supported_codecs_v2(hdev);
|
||||
else if (hdev->commands[29] & 0x20)
|
||||
hci_read_supported_codecs(hdev);
|
||||
|
||||
/* This function is only called when the controller is actually in
|
||||
* configured state. When the controller is marked as unconfigured,
|
||||
* this initialization procedure is not run.
|
||||
@ -1848,6 +1727,7 @@ int hci_dev_do_close(struct hci_dev *hdev)
|
||||
memset(hdev->eir, 0, sizeof(hdev->eir));
|
||||
memset(hdev->dev_class, 0, sizeof(hdev->dev_class));
|
||||
bacpy(&hdev->random_addr, BDADDR_ANY);
|
||||
hci_codec_list_clear(&hdev->local_codecs);
|
||||
|
||||
hci_req_sync_unlock(hdev);
|
||||
|
||||
@ -3080,6 +2960,60 @@ int hci_set_adv_instance_data(struct hci_dev *hdev, u8 instance,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
u32 hci_adv_instance_flags(struct hci_dev *hdev, u8 instance)
|
||||
{
|
||||
u32 flags;
|
||||
struct adv_info *adv;
|
||||
|
||||
if (instance == 0x00) {
|
||||
/* Instance 0 always manages the "Tx Power" and "Flags"
|
||||
* fields
|
||||
*/
|
||||
flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||
|
||||
/* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting
|
||||
* corresponds to the "connectable" instance flag.
|
||||
*/
|
||||
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE))
|
||||
flags |= MGMT_ADV_FLAG_CONNECTABLE;
|
||||
|
||||
if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE))
|
||||
flags |= MGMT_ADV_FLAG_LIMITED_DISCOV;
|
||||
else if (hci_dev_test_flag(hdev, HCI_DISCOVERABLE))
|
||||
flags |= MGMT_ADV_FLAG_DISCOV;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
adv = hci_find_adv_instance(hdev, instance);
|
||||
|
||||
/* Return 0 when we got an invalid instance identifier. */
|
||||
if (!adv)
|
||||
return 0;
|
||||
|
||||
return adv->flags;
|
||||
}
|
||||
|
||||
bool hci_adv_instance_is_scannable(struct hci_dev *hdev, u8 instance)
|
||||
{
|
||||
struct adv_info *adv;
|
||||
|
||||
/* Instance 0x00 always set local name */
|
||||
if (instance == 0x00)
|
||||
return true;
|
||||
|
||||
adv = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv)
|
||||
return false;
|
||||
|
||||
if (adv->flags & MGMT_ADV_FLAG_APPEARANCE ||
|
||||
adv->flags & MGMT_ADV_FLAG_LOCAL_NAME)
|
||||
return true;
|
||||
|
||||
return adv->scan_rsp_len ? true : false;
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
void hci_adv_monitors_clear(struct hci_dev *hdev)
|
||||
{
|
||||
@ -3487,15 +3421,6 @@ struct hci_conn_params *hci_pend_le_action_lookup(struct list_head *list,
|
||||
{
|
||||
struct hci_conn_params *param;
|
||||
|
||||
switch (addr_type) {
|
||||
case ADDR_LE_DEV_PUBLIC_RESOLVED:
|
||||
addr_type = ADDR_LE_DEV_PUBLIC;
|
||||
break;
|
||||
case ADDR_LE_DEV_RANDOM_RESOLVED:
|
||||
addr_type = ADDR_LE_DEV_RANDOM;
|
||||
break;
|
||||
}
|
||||
|
||||
list_for_each_entry(param, list, action) {
|
||||
if (bacmp(¶m->addr, addr) == 0 &&
|
||||
param->addr_type == addr_type)
|
||||
@ -3701,55 +3626,12 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
|
||||
struct hci_dev *hdev =
|
||||
container_of(nb, struct hci_dev, suspend_notifier);
|
||||
int ret = 0;
|
||||
u8 state = BT_RUNNING;
|
||||
|
||||
/* If powering down, wait for completion. */
|
||||
if (mgmt_powering_down(hdev)) {
|
||||
set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
|
||||
ret = hci_suspend_wait_event(hdev);
|
||||
if (ret)
|
||||
goto done;
|
||||
}
|
||||
if (action == PM_SUSPEND_PREPARE)
|
||||
ret = hci_suspend_dev(hdev);
|
||||
else if (action == PM_POST_SUSPEND)
|
||||
ret = hci_resume_dev(hdev);
|
||||
|
||||
/* Suspend notifier should only act on events when powered. */
|
||||
if (!hdev_is_powered(hdev) ||
|
||||
hci_dev_test_flag(hdev, HCI_UNREGISTER))
|
||||
goto done;
|
||||
|
||||
if (action == PM_SUSPEND_PREPARE) {
|
||||
/* Suspend consists of two actions:
|
||||
* - First, disconnect everything and make the controller not
|
||||
* connectable (disabling scanning)
|
||||
* - Second, program event filter/accept list and enable scan
|
||||
*/
|
||||
ret = hci_change_suspend_state(hdev, BT_SUSPEND_DISCONNECT);
|
||||
if (!ret)
|
||||
state = BT_SUSPEND_DISCONNECT;
|
||||
|
||||
/* Only configure accept list if disconnect succeeded and wake
|
||||
* isn't being prevented.
|
||||
*/
|
||||
if (!ret && !(hdev->prevent_wake && hdev->prevent_wake(hdev))) {
|
||||
ret = hci_change_suspend_state(hdev,
|
||||
BT_SUSPEND_CONFIGURE_WAKE);
|
||||
if (!ret)
|
||||
state = BT_SUSPEND_CONFIGURE_WAKE;
|
||||
}
|
||||
|
||||
hci_clear_wake_reason(hdev);
|
||||
mgmt_suspending(hdev, state);
|
||||
|
||||
} else if (action == PM_POST_SUSPEND) {
|
||||
ret = hci_change_suspend_state(hdev, BT_RUNNING);
|
||||
|
||||
mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr,
|
||||
hdev->wake_addr_type);
|
||||
}
|
||||
|
||||
done:
|
||||
/* We always allow suspend even if suspend preparation failed and
|
||||
* attempt to recover in resume.
|
||||
*/
|
||||
if (ret)
|
||||
bt_dev_err(hdev, "Suspend notifier action (%lu) failed: %d",
|
||||
action, ret);
|
||||
@ -3857,6 +3739,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
|
||||
INIT_LIST_HEAD(&hdev->adv_instances);
|
||||
INIT_LIST_HEAD(&hdev->blocked_keys);
|
||||
|
||||
INIT_LIST_HEAD(&hdev->local_codecs);
|
||||
INIT_WORK(&hdev->rx_work, hci_rx_work);
|
||||
INIT_WORK(&hdev->cmd_work, hci_cmd_work);
|
||||
INIT_WORK(&hdev->tx_work, hci_tx_work);
|
||||
@ -3994,6 +3877,7 @@ int hci_register_dev(struct hci_dev *hdev)
|
||||
queue_work(hdev->req_workqueue, &hdev->power_on);
|
||||
|
||||
idr_init(&hdev->adv_monitors_idr);
|
||||
msft_register(hdev);
|
||||
|
||||
return id;
|
||||
|
||||
@ -4026,6 +3910,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
|
||||
cancel_work_sync(&hdev->suspend_prepare);
|
||||
}
|
||||
|
||||
msft_unregister(hdev);
|
||||
|
||||
hci_dev_do_close(hdev);
|
||||
|
||||
if (!test_bit(HCI_INIT, &hdev->flags) &&
|
||||
@ -4088,16 +3974,78 @@ EXPORT_SYMBOL(hci_release_dev);
|
||||
/* Suspend HCI device */
|
||||
int hci_suspend_dev(struct hci_dev *hdev)
|
||||
{
|
||||
int ret;
|
||||
u8 state = BT_RUNNING;
|
||||
|
||||
bt_dev_dbg(hdev, "");
|
||||
|
||||
/* Suspend should only act on when powered. */
|
||||
if (!hdev_is_powered(hdev) ||
|
||||
hci_dev_test_flag(hdev, HCI_UNREGISTER))
|
||||
return 0;
|
||||
|
||||
/* If powering down, wait for completion. */
|
||||
if (mgmt_powering_down(hdev)) {
|
||||
set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
|
||||
ret = hci_suspend_wait_event(hdev);
|
||||
if (ret)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Suspend consists of two actions:
|
||||
* - First, disconnect everything and make the controller not
|
||||
* connectable (disabling scanning)
|
||||
* - Second, program event filter/accept list and enable scan
|
||||
*/
|
||||
ret = hci_change_suspend_state(hdev, BT_SUSPEND_DISCONNECT);
|
||||
if (ret)
|
||||
goto clear;
|
||||
|
||||
state = BT_SUSPEND_DISCONNECT;
|
||||
|
||||
/* Only configure accept list if device may wakeup. */
|
||||
if (hdev->wakeup && hdev->wakeup(hdev)) {
|
||||
ret = hci_change_suspend_state(hdev, BT_SUSPEND_CONFIGURE_WAKE);
|
||||
if (!ret)
|
||||
state = BT_SUSPEND_CONFIGURE_WAKE;
|
||||
}
|
||||
|
||||
clear:
|
||||
hci_clear_wake_reason(hdev);
|
||||
mgmt_suspending(hdev, state);
|
||||
|
||||
done:
|
||||
/* We always allow suspend even if suspend preparation failed and
|
||||
* attempt to recover in resume.
|
||||
*/
|
||||
hci_sock_dev_event(hdev, HCI_DEV_SUSPEND);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(hci_suspend_dev);
|
||||
|
||||
/* Resume HCI device */
|
||||
int hci_resume_dev(struct hci_dev *hdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
bt_dev_dbg(hdev, "");
|
||||
|
||||
/* Resume should only act on when powered. */
|
||||
if (!hdev_is_powered(hdev) ||
|
||||
hci_dev_test_flag(hdev, HCI_UNREGISTER))
|
||||
return 0;
|
||||
|
||||
/* If powering down don't attempt to resume */
|
||||
if (mgmt_powering_down(hdev))
|
||||
return 0;
|
||||
|
||||
ret = hci_change_suspend_state(hdev, BT_RUNNING);
|
||||
|
||||
mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr,
|
||||
hdev->wake_addr_type);
|
||||
|
||||
hci_sock_dev_event(hdev, HCI_DEV_RESUME);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(hci_resume_dev);
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
#include "smp.h"
|
||||
#include "hci_request.h"
|
||||
#include "hci_debugfs.h"
|
||||
|
||||
#define DEFINE_QUIRK_ATTRIBUTE(__name, __quirk) \
|
||||
@ -1250,3 +1251,125 @@ void hci_debugfs_create_conn(struct hci_conn *conn)
|
||||
snprintf(name, sizeof(name), "%u", conn->handle);
|
||||
conn->debugfs = debugfs_create_dir(name, hdev->debugfs);
|
||||
}
|
||||
|
||||
static ssize_t dut_mode_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = hci_dev_test_flag(hdev, HCI_DUT_MODE) ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t dut_mode_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
struct sk_buff *skb;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
if (!test_bit(HCI_UP, &hdev->flags))
|
||||
return -ENETDOWN;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (enable == hci_dev_test_flag(hdev, HCI_DUT_MODE))
|
||||
return -EALREADY;
|
||||
|
||||
hci_req_sync_lock(hdev);
|
||||
if (enable)
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_ENABLE_DUT_MODE, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
else
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL,
|
||||
HCI_CMD_TIMEOUT);
|
||||
hci_req_sync_unlock(hdev);
|
||||
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
hci_dev_change_flag(hdev, HCI_DUT_MODE);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations dut_mode_fops = {
|
||||
.open = simple_open,
|
||||
.read = dut_mode_read,
|
||||
.write = dut_mode_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t vendor_diag_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
char buf[3];
|
||||
|
||||
buf[0] = hci_dev_test_flag(hdev, HCI_VENDOR_DIAG) ? 'Y' : 'N';
|
||||
buf[1] = '\n';
|
||||
buf[2] = '\0';
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
||||
}
|
||||
|
||||
static ssize_t vendor_diag_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct hci_dev *hdev = file->private_data;
|
||||
bool enable;
|
||||
int err;
|
||||
|
||||
err = kstrtobool_from_user(user_buf, count, &enable);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* When the diagnostic flags are not persistent and the transport
|
||||
* is not active or in user channel operation, then there is no need
|
||||
* for the vendor callback. Instead just store the desired value and
|
||||
* the setting will be programmed when the controller gets powered on.
|
||||
*/
|
||||
if (test_bit(HCI_QUIRK_NON_PERSISTENT_DIAG, &hdev->quirks) &&
|
||||
(!test_bit(HCI_RUNNING, &hdev->flags) ||
|
||||
hci_dev_test_flag(hdev, HCI_USER_CHANNEL)))
|
||||
goto done;
|
||||
|
||||
hci_req_sync_lock(hdev);
|
||||
err = hdev->set_diag(hdev, enable);
|
||||
hci_req_sync_unlock(hdev);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
done:
|
||||
if (enable)
|
||||
hci_dev_set_flag(hdev, HCI_VENDOR_DIAG);
|
||||
else
|
||||
hci_dev_clear_flag(hdev, HCI_VENDOR_DIAG);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations vendor_diag_fops = {
|
||||
.open = simple_open,
|
||||
.read = vendor_diag_read,
|
||||
.write = vendor_diag_write,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
void hci_debugfs_create_basic(struct hci_dev *hdev)
|
||||
{
|
||||
debugfs_create_file("dut_mode", 0644, hdev->debugfs, hdev,
|
||||
&dut_mode_fops);
|
||||
|
||||
if (hdev->set_diag)
|
||||
debugfs_create_file("vendor_diag", 0644, hdev->debugfs, hdev,
|
||||
&vendor_diag_fops);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ void hci_debugfs_create_common(struct hci_dev *hdev);
|
||||
void hci_debugfs_create_bredr(struct hci_dev *hdev);
|
||||
void hci_debugfs_create_le(struct hci_dev *hdev);
|
||||
void hci_debugfs_create_conn(struct hci_conn *conn);
|
||||
void hci_debugfs_create_basic(struct hci_dev *hdev);
|
||||
|
||||
#else
|
||||
|
||||
@ -45,4 +46,8 @@ static inline void hci_debugfs_create_conn(struct hci_conn *conn)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void hci_debugfs_create_basic(struct hci_dev *hdev)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "amp.h"
|
||||
#include "smp.h"
|
||||
#include "msft.h"
|
||||
#include "eir.h"
|
||||
|
||||
#define ZERO_KEY "\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
@ -2278,6 +2279,41 @@ static void hci_cs_setup_sync_conn(struct hci_dev *hdev, __u8 status)
|
||||
hci_dev_unlock(hdev);
|
||||
}
|
||||
|
||||
static void hci_cs_enhanced_setup_sync_conn(struct hci_dev *hdev, __u8 status)
|
||||
{
|
||||
struct hci_cp_enhanced_setup_sync_conn *cp;
|
||||
struct hci_conn *acl, *sco;
|
||||
__u16 handle;
|
||||
|
||||
bt_dev_dbg(hdev, "status 0x%2.2x", status);
|
||||
|
||||
if (!status)
|
||||
return;
|
||||
|
||||
cp = hci_sent_cmd_data(hdev, HCI_OP_ENHANCED_SETUP_SYNC_CONN);
|
||||
if (!cp)
|
||||
return;
|
||||
|
||||
handle = __le16_to_cpu(cp->handle);
|
||||
|
||||
bt_dev_dbg(hdev, "handle 0x%4.4x", handle);
|
||||
|
||||
hci_dev_lock(hdev);
|
||||
|
||||
acl = hci_conn_hash_lookup_handle(hdev, handle);
|
||||
if (acl) {
|
||||
sco = acl->link;
|
||||
if (sco) {
|
||||
sco->state = BT_CLOSED;
|
||||
|
||||
hci_connect_cfm(sco, status);
|
||||
hci_conn_del(sco);
|
||||
}
|
||||
}
|
||||
|
||||
hci_dev_unlock(hdev);
|
||||
}
|
||||
|
||||
static void hci_cs_sniff_mode(struct hci_dev *hdev, __u8 status)
|
||||
{
|
||||
struct hci_cp_sniff_mode *cp;
|
||||
@ -2351,7 +2387,7 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
|
||||
mgmt_disconnect_failed(hdev, &conn->dst, conn->type,
|
||||
conn->dst_type, status);
|
||||
|
||||
if (conn->type == LE_LINK) {
|
||||
if (conn->type == LE_LINK && conn->role == HCI_ROLE_SLAVE) {
|
||||
hdev->cur_adv_instance = conn->adv_instance;
|
||||
hci_req_reenable_advertising(hdev);
|
||||
}
|
||||
@ -2367,6 +2403,28 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
|
||||
hci_dev_unlock(hdev);
|
||||
}
|
||||
|
||||
static u8 ev_bdaddr_type(struct hci_dev *hdev, u8 type, bool *resolved)
|
||||
{
|
||||
/* When using controller based address resolution, then the new
|
||||
* address types 0x02 and 0x03 are used. These types need to be
|
||||
* converted back into either public address or random address type
|
||||
*/
|
||||
switch (type) {
|
||||
case ADDR_LE_DEV_PUBLIC_RESOLVED:
|
||||
if (resolved)
|
||||
*resolved = true;
|
||||
return ADDR_LE_DEV_PUBLIC;
|
||||
case ADDR_LE_DEV_RANDOM_RESOLVED:
|
||||
if (resolved)
|
||||
*resolved = true;
|
||||
return ADDR_LE_DEV_RANDOM;
|
||||
}
|
||||
|
||||
if (resolved)
|
||||
*resolved = false;
|
||||
return type;
|
||||
}
|
||||
|
||||
static void cs_le_create_conn(struct hci_dev *hdev, bdaddr_t *peer_addr,
|
||||
u8 peer_addr_type, u8 own_address_type,
|
||||
u8 filter_policy)
|
||||
@ -2378,21 +2436,7 @@ static void cs_le_create_conn(struct hci_dev *hdev, bdaddr_t *peer_addr,
|
||||
if (!conn)
|
||||
return;
|
||||
|
||||
/* When using controller based address resolution, then the new
|
||||
* address types 0x02 and 0x03 are used. These types need to be
|
||||
* converted back into either public address or random address type
|
||||
*/
|
||||
if (use_ll_privacy(hdev) &&
|
||||
hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION)) {
|
||||
switch (own_address_type) {
|
||||
case ADDR_LE_DEV_PUBLIC_RESOLVED:
|
||||
own_address_type = ADDR_LE_DEV_PUBLIC;
|
||||
break;
|
||||
case ADDR_LE_DEV_RANDOM_RESOLVED:
|
||||
own_address_type = ADDR_LE_DEV_RANDOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
own_address_type = ev_bdaddr_type(hdev, own_address_type, NULL);
|
||||
|
||||
/* Store the initiator and responder address information which
|
||||
* is needed for SMP. These values will not change during the
|
||||
@ -2961,7 +3005,7 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
|
||||
* or until a connection is created or until the Advertising
|
||||
* is timed out due to Directed Advertising."
|
||||
*/
|
||||
if (conn->type == LE_LINK) {
|
||||
if (conn->type == LE_LINK && conn->role == HCI_ROLE_SLAVE) {
|
||||
hdev->cur_adv_instance = conn->adv_instance;
|
||||
hci_req_reenable_advertising(hdev);
|
||||
}
|
||||
@ -3756,6 +3800,10 @@ static void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb,
|
||||
hci_cs_setup_sync_conn(hdev, ev->status);
|
||||
break;
|
||||
|
||||
case HCI_OP_ENHANCED_SETUP_SYNC_CONN:
|
||||
hci_cs_enhanced_setup_sync_conn(hdev, ev->status);
|
||||
break;
|
||||
|
||||
case HCI_OP_SNIFF_MODE:
|
||||
hci_cs_sniff_mode(hdev, ev->status);
|
||||
break;
|
||||
@ -4397,6 +4445,7 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev,
|
||||
{
|
||||
struct hci_ev_sync_conn_complete *ev = (void *) skb->data;
|
||||
struct hci_conn *conn;
|
||||
unsigned int notify_evt;
|
||||
|
||||
BT_DBG("%s status 0x%2.2x", hdev->name, ev->status);
|
||||
|
||||
@ -4471,15 +4520,21 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev,
|
||||
|
||||
switch (ev->air_mode) {
|
||||
case 0x02:
|
||||
if (hdev->notify)
|
||||
hdev->notify(hdev, HCI_NOTIFY_ENABLE_SCO_CVSD);
|
||||
notify_evt = HCI_NOTIFY_ENABLE_SCO_CVSD;
|
||||
break;
|
||||
case 0x03:
|
||||
if (hdev->notify)
|
||||
hdev->notify(hdev, HCI_NOTIFY_ENABLE_SCO_TRANSP);
|
||||
notify_evt = HCI_NOTIFY_ENABLE_SCO_TRANSP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Notify only in case of SCO over HCI transport data path which
|
||||
* is zero and non-zero value shall be non-HCI transport data path
|
||||
*/
|
||||
if (conn->codec.data_path == 0) {
|
||||
if (hdev->notify)
|
||||
hdev->notify(hdev, notify_evt);
|
||||
}
|
||||
|
||||
hci_connect_cfm(conn, ev->status);
|
||||
if (ev->status)
|
||||
hci_conn_del(conn);
|
||||
@ -5282,22 +5337,7 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
|
||||
conn->dst_type = irk->addr_type;
|
||||
}
|
||||
|
||||
/* When using controller based address resolution, then the new
|
||||
* address types 0x02 and 0x03 are used. These types need to be
|
||||
* converted back into either public address or random address type
|
||||
*/
|
||||
if (use_ll_privacy(hdev) &&
|
||||
hci_dev_test_flag(hdev, HCI_ENABLE_LL_PRIVACY) &&
|
||||
hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION)) {
|
||||
switch (conn->dst_type) {
|
||||
case ADDR_LE_DEV_PUBLIC_RESOLVED:
|
||||
conn->dst_type = ADDR_LE_DEV_PUBLIC;
|
||||
break;
|
||||
case ADDR_LE_DEV_RANDOM_RESOLVED:
|
||||
conn->dst_type = ADDR_LE_DEV_RANDOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
conn->dst_type = ev_bdaddr_type(hdev, conn->dst_type, NULL);
|
||||
|
||||
if (status) {
|
||||
hci_le_conn_failed(conn, status);
|
||||
@ -5479,8 +5519,8 @@ static void hci_le_conn_update_complete_evt(struct hci_dev *hdev,
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
static struct hci_conn *check_pending_le_conn(struct hci_dev *hdev,
|
||||
bdaddr_t *addr,
|
||||
u8 addr_type, u8 adv_type,
|
||||
bdaddr_t *direct_rpa)
|
||||
u8 addr_type, bool addr_resolved,
|
||||
u8 adv_type, bdaddr_t *direct_rpa)
|
||||
{
|
||||
struct hci_conn *conn;
|
||||
struct hci_conn_params *params;
|
||||
@ -5532,9 +5572,9 @@ static struct hci_conn *check_pending_le_conn(struct hci_dev *hdev,
|
||||
}
|
||||
}
|
||||
|
||||
conn = hci_connect_le(hdev, addr, addr_type, BT_SECURITY_LOW,
|
||||
hdev->def_le_autoconnect_timeout, HCI_ROLE_MASTER,
|
||||
direct_rpa);
|
||||
conn = hci_connect_le(hdev, addr, addr_type, addr_resolved,
|
||||
BT_SECURITY_LOW, hdev->def_le_autoconnect_timeout,
|
||||
HCI_ROLE_MASTER, direct_rpa);
|
||||
if (!IS_ERR(conn)) {
|
||||
/* If HCI_AUTO_CONN_EXPLICIT is set, conn is already owned
|
||||
* by higher layer that tried to connect, if no then
|
||||
@ -5575,7 +5615,7 @@ static void process_adv_report(struct hci_dev *hdev, u8 type, bdaddr_t *bdaddr,
|
||||
struct discovery_state *d = &hdev->discovery;
|
||||
struct smp_irk *irk;
|
||||
struct hci_conn *conn;
|
||||
bool match;
|
||||
bool match, bdaddr_resolved;
|
||||
u32 flags;
|
||||
u8 *ptr;
|
||||
|
||||
@ -5619,6 +5659,9 @@ static void process_adv_report(struct hci_dev *hdev, u8 type, bdaddr_t *bdaddr,
|
||||
* controller address.
|
||||
*/
|
||||
if (direct_addr) {
|
||||
direct_addr_type = ev_bdaddr_type(hdev, direct_addr_type,
|
||||
&bdaddr_resolved);
|
||||
|
||||
/* Only resolvable random addresses are valid for these
|
||||
* kind of reports and others can be ignored.
|
||||
*/
|
||||
@ -5646,13 +5689,15 @@ static void process_adv_report(struct hci_dev *hdev, u8 type, bdaddr_t *bdaddr,
|
||||
bdaddr_type = irk->addr_type;
|
||||
}
|
||||
|
||||
bdaddr_type = ev_bdaddr_type(hdev, bdaddr_type, &bdaddr_resolved);
|
||||
|
||||
/* Check if we have been requested to connect to this device.
|
||||
*
|
||||
* direct_addr is set only for directed advertising reports (it is NULL
|
||||
* for advertising reports) and is already verified to be RPA above.
|
||||
*/
|
||||
conn = check_pending_le_conn(hdev, bdaddr, bdaddr_type, type,
|
||||
direct_addr);
|
||||
conn = check_pending_le_conn(hdev, bdaddr, bdaddr_type, bdaddr_resolved,
|
||||
type, direct_addr);
|
||||
if (!ext_adv && conn && type == LE_ADV_IND && len <= HCI_MAX_AD_LENGTH) {
|
||||
/* Store report for later inclusion by
|
||||
* mgmt_device_connected
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "smp.h"
|
||||
#include "hci_request.h"
|
||||
#include "msft.h"
|
||||
#include "eir.h"
|
||||
|
||||
#define HCI_REQ_DONE 0
|
||||
#define HCI_REQ_PEND 1
|
||||
@ -521,164 +522,6 @@ void __hci_req_update_name(struct hci_request *req)
|
||||
hci_req_add(req, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
#define PNP_INFO_SVCLASS_ID 0x1200
|
||||
|
||||
static u8 *create_uuid16_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 4)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
u16 uuid16;
|
||||
|
||||
if (uuid->size != 16)
|
||||
continue;
|
||||
|
||||
uuid16 = get_unaligned_le16(&uuid->uuid[12]);
|
||||
if (uuid16 < 0x1100)
|
||||
continue;
|
||||
|
||||
if (uuid16 == PNP_INFO_SVCLASS_ID)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID16_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + sizeof(u16) > len) {
|
||||
uuids_start[1] = EIR_UUID16_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
*ptr++ = (uuid16 & 0x00ff);
|
||||
*ptr++ = (uuid16 & 0xff00) >> 8;
|
||||
uuids_start[0] += sizeof(uuid16);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static u8 *create_uuid32_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 6)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
if (uuid->size != 32)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID32_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + sizeof(u32) > len) {
|
||||
uuids_start[1] = EIR_UUID32_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(ptr, &uuid->uuid[12], sizeof(u32));
|
||||
ptr += sizeof(u32);
|
||||
uuids_start[0] += sizeof(u32);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
|
||||
{
|
||||
u8 *ptr = data, *uuids_start = NULL;
|
||||
struct bt_uuid *uuid;
|
||||
|
||||
if (len < 18)
|
||||
return ptr;
|
||||
|
||||
list_for_each_entry(uuid, &hdev->uuids, list) {
|
||||
if (uuid->size != 128)
|
||||
continue;
|
||||
|
||||
if (!uuids_start) {
|
||||
uuids_start = ptr;
|
||||
uuids_start[0] = 1;
|
||||
uuids_start[1] = EIR_UUID128_ALL;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Stop if not enough space to put next UUID */
|
||||
if ((ptr - data) + 16 > len) {
|
||||
uuids_start[1] = EIR_UUID128_SOME;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(ptr, uuid->uuid, 16);
|
||||
ptr += 16;
|
||||
uuids_start[0] += 16;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void create_eir(struct hci_dev *hdev, u8 *data)
|
||||
{
|
||||
u8 *ptr = data;
|
||||
size_t name_len;
|
||||
|
||||
name_len = strlen(hdev->dev_name);
|
||||
|
||||
if (name_len > 0) {
|
||||
/* EIR Data type */
|
||||
if (name_len > 48) {
|
||||
name_len = 48;
|
||||
ptr[1] = EIR_NAME_SHORT;
|
||||
} else
|
||||
ptr[1] = EIR_NAME_COMPLETE;
|
||||
|
||||
/* EIR Data length */
|
||||
ptr[0] = name_len + 1;
|
||||
|
||||
memcpy(ptr + 2, hdev->dev_name, name_len);
|
||||
|
||||
ptr += (name_len + 2);
|
||||
}
|
||||
|
||||
if (hdev->inq_tx_power != HCI_TX_POWER_INVALID) {
|
||||
ptr[0] = 2;
|
||||
ptr[1] = EIR_TX_POWER;
|
||||
ptr[2] = (u8) hdev->inq_tx_power;
|
||||
|
||||
ptr += 3;
|
||||
}
|
||||
|
||||
if (hdev->devid_source > 0) {
|
||||
ptr[0] = 9;
|
||||
ptr[1] = EIR_DEVICE_ID;
|
||||
|
||||
put_unaligned_le16(hdev->devid_source, ptr + 2);
|
||||
put_unaligned_le16(hdev->devid_vendor, ptr + 4);
|
||||
put_unaligned_le16(hdev->devid_product, ptr + 6);
|
||||
put_unaligned_le16(hdev->devid_version, ptr + 8);
|
||||
|
||||
ptr += 10;
|
||||
}
|
||||
|
||||
ptr = create_uuid16_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
ptr = create_uuid32_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
ptr = create_uuid128_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
|
||||
}
|
||||
|
||||
void __hci_req_update_eir(struct hci_request *req)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
@ -698,7 +541,7 @@ void __hci_req_update_eir(struct hci_request *req)
|
||||
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
|
||||
create_eir(hdev, cp.data);
|
||||
eir_create(hdev, cp.data);
|
||||
|
||||
if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0)
|
||||
return;
|
||||
@ -1134,25 +977,6 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
|
||||
addr_resolv);
|
||||
}
|
||||
|
||||
static bool adv_instance_is_scannable(struct hci_dev *hdev, u8 instance)
|
||||
{
|
||||
struct adv_info *adv_instance;
|
||||
|
||||
/* Instance 0x00 always set local name */
|
||||
if (instance == 0x00)
|
||||
return true;
|
||||
|
||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv_instance)
|
||||
return false;
|
||||
|
||||
if (adv_instance->flags & MGMT_ADV_FLAG_APPEARANCE ||
|
||||
adv_instance->flags & MGMT_ADV_FLAG_LOCAL_NAME)
|
||||
return true;
|
||||
|
||||
return adv_instance->scan_rsp_len ? true : false;
|
||||
}
|
||||
|
||||
static void hci_req_clear_event_filter(struct hci_request *req)
|
||||
{
|
||||
struct hci_cp_set_event_filter f;
|
||||
@ -1281,21 +1105,24 @@ static void suspend_req_complete(struct hci_dev *hdev, u8 status, u16 opcode)
|
||||
}
|
||||
}
|
||||
|
||||
static void hci_req_add_set_adv_filter_enable(struct hci_request *req,
|
||||
bool enable)
|
||||
static void hci_req_prepare_adv_monitor_suspend(struct hci_request *req,
|
||||
bool suspending)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
|
||||
switch (hci_get_adv_monitor_offload_ext(hdev)) {
|
||||
case HCI_ADV_MONITOR_EXT_MSFT:
|
||||
msft_req_add_set_filter_enable(req, enable);
|
||||
if (suspending)
|
||||
msft_suspend(hdev);
|
||||
else
|
||||
msft_resume(hdev);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* No need to block when enabling since it's on resume path */
|
||||
if (hdev->suspended && !enable)
|
||||
if (hdev->suspended && suspending)
|
||||
set_bit(SUSPEND_SET_ADV_FILTER, hdev->suspend_tasks);
|
||||
}
|
||||
|
||||
@ -1362,7 +1189,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
|
||||
}
|
||||
|
||||
/* Disable advertisement filters */
|
||||
hci_req_add_set_adv_filter_enable(&req, false);
|
||||
hci_req_prepare_adv_monitor_suspend(&req, true);
|
||||
|
||||
/* Prevent disconnects from causing scanning to be re-enabled */
|
||||
hdev->scanning_paused = true;
|
||||
@ -1404,7 +1231,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
|
||||
/* Reset passive/background scanning to normal */
|
||||
__hci_update_background_scan(&req);
|
||||
/* Enable all of the advertisement filters */
|
||||
hci_req_add_set_adv_filter_enable(&req, true);
|
||||
hci_req_prepare_adv_monitor_suspend(&req, false);
|
||||
|
||||
/* Unpause directed advertising */
|
||||
hdev->advertising_paused = false;
|
||||
@ -1442,7 +1269,7 @@ done:
|
||||
|
||||
static bool adv_cur_instance_is_scannable(struct hci_dev *hdev)
|
||||
{
|
||||
return adv_instance_is_scannable(hdev, hdev->cur_adv_instance);
|
||||
return hci_adv_instance_is_scannable(hdev, hdev->cur_adv_instance);
|
||||
}
|
||||
|
||||
void __hci_req_disable_advertising(struct hci_request *req)
|
||||
@ -1457,40 +1284,6 @@ void __hci_req_disable_advertising(struct hci_request *req)
|
||||
}
|
||||
}
|
||||
|
||||
static u32 get_adv_instance_flags(struct hci_dev *hdev, u8 instance)
|
||||
{
|
||||
u32 flags;
|
||||
struct adv_info *adv_instance;
|
||||
|
||||
if (instance == 0x00) {
|
||||
/* Instance 0 always manages the "Tx Power" and "Flags"
|
||||
* fields
|
||||
*/
|
||||
flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||
|
||||
/* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting
|
||||
* corresponds to the "connectable" instance flag.
|
||||
*/
|
||||
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE))
|
||||
flags |= MGMT_ADV_FLAG_CONNECTABLE;
|
||||
|
||||
if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE))
|
||||
flags |= MGMT_ADV_FLAG_LIMITED_DISCOV;
|
||||
else if (hci_dev_test_flag(hdev, HCI_DISCOVERABLE))
|
||||
flags |= MGMT_ADV_FLAG_DISCOV;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||
|
||||
/* Return 0 when we got an invalid instance identifier. */
|
||||
if (!adv_instance)
|
||||
return 0;
|
||||
|
||||
return adv_instance->flags;
|
||||
}
|
||||
|
||||
static bool adv_use_rpa(struct hci_dev *hdev, uint32_t flags)
|
||||
{
|
||||
/* If privacy is not enabled don't use RPA */
|
||||
@ -1555,15 +1348,15 @@ static bool is_advertising_allowed(struct hci_dev *hdev, bool connectable)
|
||||
void __hci_req_enable_advertising(struct hci_request *req)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
struct adv_info *adv_instance;
|
||||
struct adv_info *adv;
|
||||
struct hci_cp_le_set_adv_param cp;
|
||||
u8 own_addr_type, enable = 0x01;
|
||||
bool connectable;
|
||||
u16 adv_min_interval, adv_max_interval;
|
||||
u32 flags;
|
||||
|
||||
flags = get_adv_instance_flags(hdev, hdev->cur_adv_instance);
|
||||
adv_instance = hci_find_adv_instance(hdev, hdev->cur_adv_instance);
|
||||
flags = hci_adv_instance_flags(hdev, hdev->cur_adv_instance);
|
||||
adv = hci_find_adv_instance(hdev, hdev->cur_adv_instance);
|
||||
|
||||
/* If the "connectable" instance flag was not set, then choose between
|
||||
* ADV_IND and ADV_NONCONN_IND based on the global connectable setting.
|
||||
@ -1595,9 +1388,9 @@ void __hci_req_enable_advertising(struct hci_request *req)
|
||||
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
|
||||
if (adv_instance) {
|
||||
adv_min_interval = adv_instance->min_interval;
|
||||
adv_max_interval = adv_instance->max_interval;
|
||||
if (adv) {
|
||||
adv_min_interval = adv->min_interval;
|
||||
adv_max_interval = adv->max_interval;
|
||||
} else {
|
||||
adv_min_interval = hdev->le_adv_min_interval;
|
||||
adv_max_interval = hdev->le_adv_max_interval;
|
||||
@ -1628,85 +1421,6 @@ void __hci_req_enable_advertising(struct hci_request *req)
|
||||
hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable);
|
||||
}
|
||||
|
||||
u8 append_local_name(struct hci_dev *hdev, u8 *ptr, u8 ad_len)
|
||||
{
|
||||
size_t short_len;
|
||||
size_t complete_len;
|
||||
|
||||
/* no space left for name (+ NULL + type + len) */
|
||||
if ((HCI_MAX_AD_LENGTH - ad_len) < HCI_MAX_SHORT_NAME_LENGTH + 3)
|
||||
return ad_len;
|
||||
|
||||
/* use complete name if present and fits */
|
||||
complete_len = strlen(hdev->dev_name);
|
||||
if (complete_len && complete_len <= HCI_MAX_SHORT_NAME_LENGTH)
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_COMPLETE,
|
||||
hdev->dev_name, complete_len + 1);
|
||||
|
||||
/* use short name if present */
|
||||
short_len = strlen(hdev->short_name);
|
||||
if (short_len)
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_SHORT,
|
||||
hdev->short_name, short_len + 1);
|
||||
|
||||
/* use shortened full name if present, we already know that name
|
||||
* is longer then HCI_MAX_SHORT_NAME_LENGTH
|
||||
*/
|
||||
if (complete_len) {
|
||||
u8 name[HCI_MAX_SHORT_NAME_LENGTH + 1];
|
||||
|
||||
memcpy(name, hdev->dev_name, HCI_MAX_SHORT_NAME_LENGTH);
|
||||
name[HCI_MAX_SHORT_NAME_LENGTH] = '\0';
|
||||
|
||||
return eir_append_data(ptr, ad_len, EIR_NAME_SHORT, name,
|
||||
sizeof(name));
|
||||
}
|
||||
|
||||
return ad_len;
|
||||
}
|
||||
|
||||
static u8 append_appearance(struct hci_dev *hdev, u8 *ptr, u8 ad_len)
|
||||
{
|
||||
return eir_append_le16(ptr, ad_len, EIR_APPEARANCE, hdev->appearance);
|
||||
}
|
||||
|
||||
static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||
{
|
||||
u8 scan_rsp_len = 0;
|
||||
|
||||
if (hdev->appearance)
|
||||
scan_rsp_len = append_appearance(hdev, ptr, scan_rsp_len);
|
||||
|
||||
return append_local_name(hdev, ptr, scan_rsp_len);
|
||||
}
|
||||
|
||||
static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance,
|
||||
u8 *ptr)
|
||||
{
|
||||
struct adv_info *adv_instance;
|
||||
u32 instance_flags;
|
||||
u8 scan_rsp_len = 0;
|
||||
|
||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv_instance)
|
||||
return 0;
|
||||
|
||||
instance_flags = adv_instance->flags;
|
||||
|
||||
if ((instance_flags & MGMT_ADV_FLAG_APPEARANCE) && hdev->appearance)
|
||||
scan_rsp_len = append_appearance(hdev, ptr, scan_rsp_len);
|
||||
|
||||
memcpy(&ptr[scan_rsp_len], adv_instance->scan_rsp_data,
|
||||
adv_instance->scan_rsp_len);
|
||||
|
||||
scan_rsp_len += adv_instance->scan_rsp_len;
|
||||
|
||||
if (instance_flags & MGMT_ADV_FLAG_LOCAL_NAME)
|
||||
scan_rsp_len = append_local_name(hdev, ptr, scan_rsp_len);
|
||||
|
||||
return scan_rsp_len;
|
||||
}
|
||||
|
||||
void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
@ -1723,11 +1437,7 @@ void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance)
|
||||
|
||||
memset(&pdu, 0, sizeof(pdu));
|
||||
|
||||
if (instance)
|
||||
len = create_instance_scan_rsp_data(hdev, instance,
|
||||
pdu.data);
|
||||
else
|
||||
len = create_default_scan_rsp_data(hdev, pdu.data);
|
||||
len = eir_create_scan_rsp(hdev, instance, pdu.data);
|
||||
|
||||
if (hdev->scan_rsp_data_len == len &&
|
||||
!memcmp(pdu.data, hdev->scan_rsp_data, len))
|
||||
@ -1748,11 +1458,7 @@ void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance)
|
||||
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
|
||||
if (instance)
|
||||
len = create_instance_scan_rsp_data(hdev, instance,
|
||||
cp.data);
|
||||
else
|
||||
len = create_default_scan_rsp_data(hdev, cp.data);
|
||||
len = eir_create_scan_rsp(hdev, instance, cp.data);
|
||||
|
||||
if (hdev->scan_rsp_data_len == len &&
|
||||
!memcmp(cp.data, hdev->scan_rsp_data, len))
|
||||
@ -1767,95 +1473,6 @@ void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance)
|
||||
}
|
||||
}
|
||||
|
||||
static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
|
||||
{
|
||||
struct adv_info *adv_instance = NULL;
|
||||
u8 ad_len = 0, flags = 0;
|
||||
u32 instance_flags;
|
||||
|
||||
/* Return 0 when the current instance identifier is invalid. */
|
||||
if (instance) {
|
||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||
if (!adv_instance)
|
||||
return 0;
|
||||
}
|
||||
|
||||
instance_flags = get_adv_instance_flags(hdev, instance);
|
||||
|
||||
/* If instance already has the flags set skip adding it once
|
||||
* again.
|
||||
*/
|
||||
if (adv_instance && eir_get_data(adv_instance->adv_data,
|
||||
adv_instance->adv_data_len, EIR_FLAGS,
|
||||
NULL))
|
||||
goto skip_flags;
|
||||
|
||||
/* The Add Advertising command allows userspace to set both the general
|
||||
* and limited discoverable flags.
|
||||
*/
|
||||
if (instance_flags & MGMT_ADV_FLAG_DISCOV)
|
||||
flags |= LE_AD_GENERAL;
|
||||
|
||||
if (instance_flags & MGMT_ADV_FLAG_LIMITED_DISCOV)
|
||||
flags |= LE_AD_LIMITED;
|
||||
|
||||
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
||||
flags |= LE_AD_NO_BREDR;
|
||||
|
||||
if (flags || (instance_flags & MGMT_ADV_FLAG_MANAGED_FLAGS)) {
|
||||
/* If a discovery flag wasn't provided, simply use the global
|
||||
* settings.
|
||||
*/
|
||||
if (!flags)
|
||||
flags |= mgmt_get_adv_discov_flags(hdev);
|
||||
|
||||
/* If flags would still be empty, then there is no need to
|
||||
* include the "Flags" AD field".
|
||||
*/
|
||||
if (flags) {
|
||||
ptr[0] = 0x02;
|
||||
ptr[1] = EIR_FLAGS;
|
||||
ptr[2] = flags;
|
||||
|
||||
ad_len += 3;
|
||||
ptr += 3;
|
||||
}
|
||||
}
|
||||
|
||||
skip_flags:
|
||||
if (adv_instance) {
|
||||
memcpy(ptr, adv_instance->adv_data,
|
||||
adv_instance->adv_data_len);
|
||||
ad_len += adv_instance->adv_data_len;
|
||||
ptr += adv_instance->adv_data_len;
|
||||
}
|
||||
|
||||
if (instance_flags & MGMT_ADV_FLAG_TX_POWER) {
|
||||
s8 adv_tx_power;
|
||||
|
||||
if (ext_adv_capable(hdev)) {
|
||||
if (adv_instance)
|
||||
adv_tx_power = adv_instance->tx_power;
|
||||
else
|
||||
adv_tx_power = hdev->adv_tx_power;
|
||||
} else {
|
||||
adv_tx_power = hdev->adv_tx_power;
|
||||
}
|
||||
|
||||
/* Provide Tx Power only if we can provide a valid value for it */
|
||||
if (adv_tx_power != HCI_TX_POWER_INVALID) {
|
||||
ptr[0] = 0x02;
|
||||
ptr[1] = EIR_TX_POWER;
|
||||
ptr[2] = (u8)adv_tx_power;
|
||||
|
||||
ad_len += 3;
|
||||
ptr += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return ad_len;
|
||||
}
|
||||
|
||||
void __hci_req_update_adv_data(struct hci_request *req, u8 instance)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
@ -1872,7 +1489,7 @@ void __hci_req_update_adv_data(struct hci_request *req, u8 instance)
|
||||
|
||||
memset(&pdu, 0, sizeof(pdu));
|
||||
|
||||
len = create_instance_adv_data(hdev, instance, pdu.data);
|
||||
len = eir_create_adv_data(hdev, instance, pdu.data);
|
||||
|
||||
/* There's nothing to do if the data hasn't changed */
|
||||
if (hdev->adv_data_len == len &&
|
||||
@ -1894,7 +1511,7 @@ void __hci_req_update_adv_data(struct hci_request *req, u8 instance)
|
||||
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
|
||||
len = create_instance_adv_data(hdev, instance, cp.data);
|
||||
len = eir_create_adv_data(hdev, instance, cp.data);
|
||||
|
||||
/* There's nothing to do if the data hasn't changed */
|
||||
if (hdev->adv_data_len == len &&
|
||||
@ -2183,7 +1800,7 @@ int __hci_req_setup_ext_adv_instance(struct hci_request *req, u8 instance)
|
||||
adv_instance = NULL;
|
||||
}
|
||||
|
||||
flags = get_adv_instance_flags(hdev, instance);
|
||||
flags = hci_adv_instance_flags(hdev, instance);
|
||||
|
||||
/* If the "connectable" instance flag was not set, then choose between
|
||||
* ADV_IND and ADV_NONCONN_IND based on the global connectable setting.
|
||||
@ -2223,7 +1840,7 @@ int __hci_req_setup_ext_adv_instance(struct hci_request *req, u8 instance)
|
||||
cp.evt_properties = cpu_to_le16(LE_EXT_ADV_CONN_IND);
|
||||
else
|
||||
cp.evt_properties = cpu_to_le16(LE_LEGACY_ADV_IND);
|
||||
} else if (adv_instance_is_scannable(hdev, instance) ||
|
||||
} else if (hci_adv_instance_is_scannable(hdev, instance) ||
|
||||
(flags & MGMT_ADV_PARAM_SCAN_RSP)) {
|
||||
if (secondary_adv)
|
||||
cp.evt_properties = cpu_to_le16(LE_EXT_ADV_SCAN_IND);
|
||||
@ -3327,6 +2944,53 @@ bool hci_req_stop_discovery(struct hci_request *req)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void config_data_path_complete(struct hci_dev *hdev, u8 status,
|
||||
u16 opcode)
|
||||
{
|
||||
bt_dev_dbg(hdev, "status %u", status);
|
||||
}
|
||||
|
||||
int hci_req_configure_datapath(struct hci_dev *hdev, struct bt_codec *codec)
|
||||
{
|
||||
struct hci_request req;
|
||||
int err;
|
||||
__u8 vnd_len, *vnd_data = NULL;
|
||||
struct hci_op_configure_data_path *cmd = NULL;
|
||||
|
||||
hci_req_init(&req, hdev);
|
||||
|
||||
err = hdev->get_codec_config_data(hdev, ESCO_LINK, codec, &vnd_len,
|
||||
&vnd_data);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd) + vnd_len, GFP_KERNEL);
|
||||
if (!cmd) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = hdev->get_data_path_id(hdev, &cmd->data_path_id);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
cmd->vnd_len = vnd_len;
|
||||
memcpy(cmd->vnd_data, vnd_data, vnd_len);
|
||||
|
||||
cmd->direction = 0x00;
|
||||
hci_req_add(&req, HCI_CONFIGURE_DATA_PATH, sizeof(*cmd) + vnd_len, cmd);
|
||||
|
||||
cmd->direction = 0x01;
|
||||
hci_req_add(&req, HCI_CONFIGURE_DATA_PATH, sizeof(*cmd) + vnd_len, cmd);
|
||||
|
||||
err = hci_req_run(&req, config_data_path_complete);
|
||||
error:
|
||||
|
||||
kfree(cmd);
|
||||
kfree(vnd_data);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stop_discovery(struct hci_request *req, unsigned long opt)
|
||||
{
|
||||
hci_dev_lock(req->hdev);
|
||||
|
@ -101,6 +101,8 @@ void __hci_req_update_class(struct hci_request *req);
|
||||
/* Returns true if HCI commands were queued */
|
||||
bool hci_req_stop_discovery(struct hci_request *req);
|
||||
|
||||
int hci_req_configure_datapath(struct hci_dev *hdev, struct bt_codec *codec);
|
||||
|
||||
static inline void hci_req_update_scan(struct hci_dev *hdev)
|
||||
{
|
||||
queue_work(hdev->req_workqueue, &hdev->scan_update);
|
||||
@ -122,26 +124,3 @@ static inline void hci_update_background_scan(struct hci_dev *hdev)
|
||||
|
||||
void hci_request_setup(struct hci_dev *hdev);
|
||||
void hci_request_cancel_all(struct hci_dev *hdev);
|
||||
|
||||
u8 append_local_name(struct hci_dev *hdev, u8 *ptr, u8 ad_len);
|
||||
|
||||
static inline u16 eir_append_data(u8 *eir, u16 eir_len, u8 type,
|
||||
u8 *data, u8 data_len)
|
||||
{
|
||||
eir[eir_len++] = sizeof(type) + data_len;
|
||||
eir[eir_len++] = type;
|
||||
memcpy(&eir[eir_len], data, data_len);
|
||||
eir_len += data_len;
|
||||
|
||||
return eir_len;
|
||||
}
|
||||
|
||||
static inline u16 eir_append_le16(u8 *eir, u16 eir_len, u8 type, u16 data)
|
||||
{
|
||||
eir[eir_len++] = sizeof(type) + sizeof(data);
|
||||
eir[eir_len++] = type;
|
||||
put_unaligned_le16(data, &eir[eir_len]);
|
||||
eir_len += sizeof(data);
|
||||
|
||||
return eir_len;
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ struct hci_pinfo {
|
||||
unsigned long flags;
|
||||
__u32 cookie;
|
||||
char comm[TASK_COMM_LEN];
|
||||
__u16 mtu;
|
||||
};
|
||||
|
||||
static struct hci_dev *hci_hdev_from_sock(struct sock *sk)
|
||||
@ -1374,6 +1375,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||
break;
|
||||
}
|
||||
|
||||
/* Default MTU to HCI_MAX_FRAME_SIZE if not set */
|
||||
if (!hci_pi(sk)->mtu)
|
||||
hci_pi(sk)->mtu = HCI_MAX_FRAME_SIZE;
|
||||
|
||||
sk->sk_state = BT_BOUND;
|
||||
|
||||
done:
|
||||
@ -1506,9 +1511,8 @@ static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg,
|
||||
}
|
||||
|
||||
static int hci_mgmt_cmd(struct hci_mgmt_chan *chan, struct sock *sk,
|
||||
struct msghdr *msg, size_t msglen)
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
void *buf;
|
||||
u8 *cp;
|
||||
struct mgmt_hdr *hdr;
|
||||
u16 opcode, index, len;
|
||||
@ -1517,40 +1521,31 @@ static int hci_mgmt_cmd(struct hci_mgmt_chan *chan, struct sock *sk,
|
||||
bool var_len, no_hdev;
|
||||
int err;
|
||||
|
||||
BT_DBG("got %zu bytes", msglen);
|
||||
BT_DBG("got %d bytes", skb->len);
|
||||
|
||||
if (msglen < sizeof(*hdr))
|
||||
if (skb->len < sizeof(*hdr))
|
||||
return -EINVAL;
|
||||
|
||||
buf = kmalloc(msglen, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (memcpy_from_msg(buf, msg, msglen)) {
|
||||
err = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
hdr = buf;
|
||||
hdr = (void *)skb->data;
|
||||
opcode = __le16_to_cpu(hdr->opcode);
|
||||
index = __le16_to_cpu(hdr->index);
|
||||
len = __le16_to_cpu(hdr->len);
|
||||
|
||||
if (len != msglen - sizeof(*hdr)) {
|
||||
if (len != skb->len - sizeof(*hdr)) {
|
||||
err = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (chan->channel == HCI_CHANNEL_CONTROL) {
|
||||
struct sk_buff *skb;
|
||||
struct sk_buff *cmd;
|
||||
|
||||
/* Send event to monitor */
|
||||
skb = create_monitor_ctrl_command(sk, index, opcode, len,
|
||||
buf + sizeof(*hdr));
|
||||
if (skb) {
|
||||
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||
cmd = create_monitor_ctrl_command(sk, index, opcode, len,
|
||||
skb->data + sizeof(*hdr));
|
||||
if (cmd) {
|
||||
hci_send_to_channel(HCI_CHANNEL_MONITOR, cmd,
|
||||
HCI_SOCK_TRUSTED, NULL);
|
||||
kfree_skb(skb);
|
||||
kfree_skb(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1615,26 +1610,25 @@ static int hci_mgmt_cmd(struct hci_mgmt_chan *chan, struct sock *sk,
|
||||
if (hdev && chan->hdev_init)
|
||||
chan->hdev_init(sk, hdev);
|
||||
|
||||
cp = buf + sizeof(*hdr);
|
||||
cp = skb->data + sizeof(*hdr);
|
||||
|
||||
err = handler->func(sk, hdev, cp, len);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
err = msglen;
|
||||
err = skb->len;
|
||||
|
||||
done:
|
||||
if (hdev)
|
||||
hci_dev_put(hdev);
|
||||
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hci_logging_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
static int hci_logging_frame(struct sock *sk, struct sk_buff *skb,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct hci_mon_hdr *hdr;
|
||||
struct sk_buff *skb;
|
||||
struct hci_dev *hdev;
|
||||
u16 index;
|
||||
int err;
|
||||
@ -1643,24 +1637,13 @@ static int hci_logging_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
* the priority byte, the ident length byte and at least one string
|
||||
* terminator NUL byte. Anything shorter are invalid packets.
|
||||
*/
|
||||
if (len < sizeof(*hdr) + 3)
|
||||
if (skb->len < sizeof(*hdr) + 3)
|
||||
return -EINVAL;
|
||||
|
||||
skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb)
|
||||
return err;
|
||||
|
||||
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
|
||||
err = -EFAULT;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
hdr = (void *)skb->data;
|
||||
|
||||
if (__le16_to_cpu(hdr->len) != len - sizeof(*hdr)) {
|
||||
err = -EINVAL;
|
||||
goto drop;
|
||||
}
|
||||
if (__le16_to_cpu(hdr->len) != skb->len - sizeof(*hdr))
|
||||
return -EINVAL;
|
||||
|
||||
if (__le16_to_cpu(hdr->opcode) == 0x0000) {
|
||||
__u8 priority = skb->data[sizeof(*hdr)];
|
||||
@ -1679,25 +1662,20 @@ static int hci_logging_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
* The message follows the ident string (if present) and
|
||||
* must be NUL terminated. Otherwise it is not a valid packet.
|
||||
*/
|
||||
if (priority > 7 || skb->data[len - 1] != 0x00 ||
|
||||
ident_len > len - sizeof(*hdr) - 3 ||
|
||||
skb->data[sizeof(*hdr) + ident_len + 1] != 0x00) {
|
||||
err = -EINVAL;
|
||||
goto drop;
|
||||
}
|
||||
if (priority > 7 || skb->data[skb->len - 1] != 0x00 ||
|
||||
ident_len > skb->len - sizeof(*hdr) - 3 ||
|
||||
skb->data[sizeof(*hdr) + ident_len + 1] != 0x00)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
goto drop;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
index = __le16_to_cpu(hdr->index);
|
||||
|
||||
if (index != MGMT_INDEX_NONE) {
|
||||
hdev = hci_dev_get(index);
|
||||
if (!hdev) {
|
||||
err = -ENODEV;
|
||||
goto drop;
|
||||
}
|
||||
if (!hdev)
|
||||
return -ENODEV;
|
||||
} else {
|
||||
hdev = NULL;
|
||||
}
|
||||
@ -1705,13 +1683,11 @@ static int hci_logging_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
hdr->opcode = cpu_to_le16(HCI_MON_USER_LOGGING);
|
||||
|
||||
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb, HCI_SOCK_TRUSTED, NULL);
|
||||
err = len;
|
||||
err = skb->len;
|
||||
|
||||
if (hdev)
|
||||
hci_dev_put(hdev);
|
||||
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -1723,19 +1699,23 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
struct hci_dev *hdev;
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
const unsigned int flags = msg->msg_flags;
|
||||
|
||||
BT_DBG("sock %p sk %p", sock, sk);
|
||||
|
||||
if (msg->msg_flags & MSG_OOB)
|
||||
if (flags & MSG_OOB)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE|
|
||||
MSG_CMSG_COMPAT))
|
||||
if (flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL | MSG_ERRQUEUE | MSG_CMSG_COMPAT))
|
||||
return -EINVAL;
|
||||
|
||||
if (len < 4 || len > HCI_MAX_FRAME_SIZE)
|
||||
if (len < 4 || len > hci_pi(sk)->mtu)
|
||||
return -EINVAL;
|
||||
|
||||
skb = bt_skb_sendmsg(sk, msg, len, len, 0, 0);
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
switch (hci_pi(sk)->channel) {
|
||||
@ -1744,39 +1724,30 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
break;
|
||||
case HCI_CHANNEL_MONITOR:
|
||||
err = -EOPNOTSUPP;
|
||||
goto done;
|
||||
goto drop;
|
||||
case HCI_CHANNEL_LOGGING:
|
||||
err = hci_logging_frame(sk, msg, len);
|
||||
goto done;
|
||||
err = hci_logging_frame(sk, skb, flags);
|
||||
goto drop;
|
||||
default:
|
||||
mutex_lock(&mgmt_chan_list_lock);
|
||||
chan = __hci_mgmt_chan_find(hci_pi(sk)->channel);
|
||||
if (chan)
|
||||
err = hci_mgmt_cmd(chan, sk, msg, len);
|
||||
err = hci_mgmt_cmd(chan, sk, skb);
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
||||
mutex_unlock(&mgmt_chan_list_lock);
|
||||
goto done;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
hdev = hci_hdev_from_sock(sk);
|
||||
if (IS_ERR(hdev)) {
|
||||
err = PTR_ERR(hdev);
|
||||
goto done;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
if (!test_bit(HCI_UP, &hdev->flags)) {
|
||||
err = -ENETDOWN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb)
|
||||
goto done;
|
||||
|
||||
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
|
||||
err = -EFAULT;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
@ -1857,8 +1828,8 @@ drop:
|
||||
goto done;
|
||||
}
|
||||
|
||||
static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
sockptr_t optval, unsigned int len)
|
||||
static int hci_sock_setsockopt_old(struct socket *sock, int level, int optname,
|
||||
sockptr_t optval, unsigned int len)
|
||||
{
|
||||
struct hci_ufilter uf = { .opcode = 0 };
|
||||
struct sock *sk = sock->sk;
|
||||
@ -1866,9 +1837,6 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
|
||||
BT_DBG("sk %p, opt %d", sk, optname);
|
||||
|
||||
if (level != SOL_HCI)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) {
|
||||
@ -1943,8 +1911,56 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
sockptr_t optval, unsigned int len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
int err = 0, opt = 0;
|
||||
|
||||
BT_DBG("sk %p, opt %d", sk, optname);
|
||||
|
||||
if (level == SOL_HCI)
|
||||
return hci_sock_setsockopt_old(sock, level, optname, optval,
|
||||
len);
|
||||
|
||||
if (level != SOL_BLUETOOTH)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
case BT_SNDMTU:
|
||||
case BT_RCVMTU:
|
||||
switch (hci_pi(sk)->channel) {
|
||||
/* Don't allow changing MTU for channels that are meant for HCI
|
||||
* traffic only.
|
||||
*/
|
||||
case HCI_CHANNEL_RAW:
|
||||
case HCI_CHANNEL_USER:
|
||||
err = -ENOPROTOOPT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (copy_from_sockptr(&opt, optval, sizeof(u16))) {
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
hci_pi(sk)->mtu = opt;
|
||||
break;
|
||||
|
||||
default:
|
||||
err = -ENOPROTOOPT;
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
release_sock(sk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hci_sock_getsockopt_old(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct hci_ufilter uf;
|
||||
struct sock *sk = sock->sk;
|
||||
@ -1952,9 +1968,6 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||
|
||||
BT_DBG("sk %p, opt %d", sk, optname);
|
||||
|
||||
if (level != SOL_HCI)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
@ -2012,6 +2025,39 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
int err = 0;
|
||||
|
||||
BT_DBG("sk %p, opt %d", sk, optname);
|
||||
|
||||
if (level == SOL_HCI)
|
||||
return hci_sock_getsockopt_old(sock, level, optname, optval,
|
||||
optlen);
|
||||
|
||||
if (level != SOL_BLUETOOTH)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
case BT_SNDMTU:
|
||||
case BT_RCVMTU:
|
||||
if (put_user(hci_pi(sk)->mtu, (u16 __user *)optval))
|
||||
err = -EFAULT;
|
||||
break;
|
||||
|
||||
default:
|
||||
err = -ENOPROTOOPT;
|
||||
break;
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct proto_ops hci_sock_ops = {
|
||||
.family = PF_BLUETOOTH,
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -7902,7 +7902,7 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
|
||||
dst_type = ADDR_LE_DEV_RANDOM;
|
||||
|
||||
if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
||||
hcon = hci_connect_le(hdev, dst, dst_type,
|
||||
hcon = hci_connect_le(hdev, dst, dst_type, false,
|
||||
chan->sec_level,
|
||||
HCI_LE_CONN_TIMEOUT,
|
||||
HCI_ROLE_SLAVE, NULL);
|
||||
|
@ -1508,6 +1508,9 @@ static void l2cap_sock_close_cb(struct l2cap_chan *chan)
|
||||
{
|
||||
struct sock *sk = chan->data;
|
||||
|
||||
if (!sk)
|
||||
return;
|
||||
|
||||
l2cap_sock_kill(sk);
|
||||
}
|
||||
|
||||
@ -1516,6 +1519,9 @@ static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
|
||||
struct sock *sk = chan->data;
|
||||
struct sock *parent;
|
||||
|
||||
if (!sk)
|
||||
return;
|
||||
|
||||
BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
|
||||
|
||||
/* This callback can be called both for server (BT_LISTEN)
|
||||
@ -1707,8 +1713,10 @@ static void l2cap_sock_destruct(struct sock *sk)
|
||||
{
|
||||
BT_DBG("sk %p", sk);
|
||||
|
||||
if (l2cap_pi(sk)->chan)
|
||||
if (l2cap_pi(sk)->chan) {
|
||||
l2cap_pi(sk)->chan->data = NULL;
|
||||
l2cap_chan_put(l2cap_pi(sk)->chan);
|
||||
}
|
||||
|
||||
if (l2cap_pi(sk)->rx_busy_skb) {
|
||||
kfree_skb(l2cap_pi(sk)->rx_busy_skb);
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "mgmt_util.h"
|
||||
#include "mgmt_config.h"
|
||||
#include "msft.h"
|
||||
#include "eir.h"
|
||||
|
||||
#define MGMT_VERSION 1
|
||||
#define MGMT_REVISION 21
|
||||
@ -3791,6 +3792,18 @@ static const u8 debug_uuid[16] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* 330859bc-7506-492d-9370-9a6f0614037f */
|
||||
static const u8 quality_report_uuid[16] = {
|
||||
0x7f, 0x03, 0x14, 0x06, 0x6f, 0x9a, 0x70, 0x93,
|
||||
0x2d, 0x49, 0x06, 0x75, 0xbc, 0x59, 0x08, 0x33,
|
||||
};
|
||||
|
||||
/* a6695ace-ee7f-4fb9-881a-5fac66c629af */
|
||||
static const u8 offload_codecs_uuid[16] = {
|
||||
0xaf, 0x29, 0xc6, 0x66, 0xac, 0x5f, 0x1a, 0x88,
|
||||
0xb9, 0x4f, 0x7f, 0xee, 0xce, 0x5a, 0x69, 0xa6,
|
||||
};
|
||||
|
||||
/* 671b10b5-42c0-4696-9227-eb28d1b049d6 */
|
||||
static const u8 simult_central_periph_uuid[16] = {
|
||||
0xd6, 0x49, 0xb0, 0xd1, 0x28, 0xeb, 0x27, 0x92,
|
||||
@ -3806,7 +3819,7 @@ static const u8 rpa_resolution_uuid[16] = {
|
||||
static int read_exp_features_info(struct sock *sk, struct hci_dev *hdev,
|
||||
void *data, u16 data_len)
|
||||
{
|
||||
char buf[62]; /* Enough space for 3 features */
|
||||
char buf[102]; /* Enough space for 5 features: 2 + 20 * 5 */
|
||||
struct mgmt_rp_read_exp_features_info *rp = (void *)buf;
|
||||
u16 idx = 0;
|
||||
u32 flags;
|
||||
@ -3850,6 +3863,28 @@ static int read_exp_features_info(struct sock *sk, struct hci_dev *hdev,
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (hdev && hdev->set_quality_report) {
|
||||
if (hci_dev_test_flag(hdev, HCI_QUALITY_REPORT))
|
||||
flags = BIT(0);
|
||||
else
|
||||
flags = 0;
|
||||
|
||||
memcpy(rp->features[idx].uuid, quality_report_uuid, 16);
|
||||
rp->features[idx].flags = cpu_to_le32(flags);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (hdev && hdev->get_data_path_id) {
|
||||
if (hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED))
|
||||
flags = BIT(0);
|
||||
else
|
||||
flags = 0;
|
||||
|
||||
memcpy(rp->features[idx].uuid, offload_codecs_uuid, 16);
|
||||
rp->features[idx].flags = cpu_to_le32(flags);
|
||||
idx++;
|
||||
}
|
||||
|
||||
rp->feature_count = cpu_to_le16(idx);
|
||||
|
||||
/* After reading the experimental features information, enable
|
||||
@ -3892,150 +3927,341 @@ static int exp_debug_feature_changed(bool enabled, struct sock *skip)
|
||||
}
|
||||
#endif
|
||||
|
||||
static int exp_quality_report_feature_changed(bool enabled, struct sock *skip)
|
||||
{
|
||||
struct mgmt_ev_exp_feature_changed ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
memcpy(ev.uuid, quality_report_uuid, 16);
|
||||
ev.flags = cpu_to_le32(enabled ? BIT(0) : 0);
|
||||
|
||||
return mgmt_limited_event(MGMT_EV_EXP_FEATURE_CHANGED, NULL,
|
||||
&ev, sizeof(ev),
|
||||
HCI_MGMT_EXP_FEATURE_EVENTS, skip);
|
||||
}
|
||||
|
||||
#define EXP_FEAT(_uuid, _set_func) \
|
||||
{ \
|
||||
.uuid = _uuid, \
|
||||
.set_func = _set_func, \
|
||||
}
|
||||
|
||||
/* The zero key uuid is special. Multiple exp features are set through it. */
|
||||
static int set_zero_key_func(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp, u16 data_len)
|
||||
{
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
|
||||
memset(rp.uuid, 0, 16);
|
||||
rp.flags = cpu_to_le32(0);
|
||||
|
||||
#ifdef CONFIG_BT_FEATURE_DEBUG
|
||||
if (!hdev) {
|
||||
bool changed = bt_dbg_get();
|
||||
|
||||
bt_dbg_set(false);
|
||||
|
||||
if (changed)
|
||||
exp_debug_feature_changed(false, sk);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hdev && use_ll_privacy(hdev) && !hdev_is_powered(hdev)) {
|
||||
bool changed = hci_dev_test_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
hci_dev_clear_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
if (changed)
|
||||
exp_ll_privacy_feature_changed(false, hdev, sk);
|
||||
}
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
return mgmt_cmd_complete(sk, hdev ? hdev->id : MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_FEATURE_DEBUG
|
||||
static int set_debug_func(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp, u16 data_len)
|
||||
{
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
|
||||
bool val, changed;
|
||||
int err;
|
||||
|
||||
/* Command requires to use the non-controller index */
|
||||
if (hdev)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
val = !!cp->param[0];
|
||||
changed = val ? !bt_dbg_get() : bt_dbg_get();
|
||||
bt_dbg_set(val);
|
||||
|
||||
memcpy(rp.uuid, debug_uuid, 16);
|
||||
rp.flags = cpu_to_le32(val ? BIT(0) : 0);
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
err = mgmt_cmd_complete(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_debug_feature_changed(val, sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int set_rpa_resolution_func(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp,
|
||||
u16 data_len)
|
||||
{
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
bool val, changed;
|
||||
int err;
|
||||
u32 flags;
|
||||
|
||||
/* Command requires to use the controller index */
|
||||
if (!hdev)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Changes can only be made when controller is powered down */
|
||||
if (hdev_is_powered(hdev))
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_REJECTED);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
val = !!cp->param[0];
|
||||
|
||||
if (val) {
|
||||
changed = !hci_dev_test_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_set_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_clear_flag(hdev, HCI_ADVERTISING);
|
||||
|
||||
/* Enable LL privacy + supported settings changed */
|
||||
flags = BIT(0) | BIT(1);
|
||||
} else {
|
||||
changed = hci_dev_test_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_clear_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
/* Disable LL privacy + supported settings changed */
|
||||
flags = BIT(1);
|
||||
}
|
||||
|
||||
memcpy(rp.uuid, rpa_resolution_uuid, 16);
|
||||
rp.flags = cpu_to_le32(flags);
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
err = mgmt_cmd_complete(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_ll_privacy_feature_changed(val, hdev, sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int set_quality_report_func(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp,
|
||||
u16 data_len)
|
||||
{
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
bool val, changed;
|
||||
int err;
|
||||
|
||||
/* Command requires to use a valid controller index */
|
||||
if (!hdev)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
hci_req_sync_lock(hdev);
|
||||
|
||||
val = !!cp->param[0];
|
||||
changed = (val != hci_dev_test_flag(hdev, HCI_QUALITY_REPORT));
|
||||
|
||||
if (!hdev->set_quality_report) {
|
||||
err = mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_NOT_SUPPORTED);
|
||||
goto unlock_quality_report;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
err = hdev->set_quality_report(hdev, val);
|
||||
if (err) {
|
||||
err = mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_FAILED);
|
||||
goto unlock_quality_report;
|
||||
}
|
||||
if (val)
|
||||
hci_dev_set_flag(hdev, HCI_QUALITY_REPORT);
|
||||
else
|
||||
hci_dev_clear_flag(hdev, HCI_QUALITY_REPORT);
|
||||
}
|
||||
|
||||
bt_dev_dbg(hdev, "quality report enable %d changed %d", val, changed);
|
||||
|
||||
memcpy(rp.uuid, quality_report_uuid, 16);
|
||||
rp.flags = cpu_to_le32(val ? BIT(0) : 0);
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
err = mgmt_cmd_complete(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_quality_report_feature_changed(val, sk);
|
||||
|
||||
unlock_quality_report:
|
||||
hci_req_sync_unlock(hdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int exp_offload_codec_feature_changed(bool enabled, struct sock *skip)
|
||||
{
|
||||
struct mgmt_ev_exp_feature_changed ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
memcpy(ev.uuid, offload_codecs_uuid, 16);
|
||||
ev.flags = cpu_to_le32(enabled ? BIT(0) : 0);
|
||||
|
||||
return mgmt_limited_event(MGMT_EV_EXP_FEATURE_CHANGED, NULL,
|
||||
&ev, sizeof(ev),
|
||||
HCI_MGMT_EXP_FEATURE_EVENTS, skip);
|
||||
}
|
||||
|
||||
static int set_offload_codec_func(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp,
|
||||
u16 data_len)
|
||||
{
|
||||
bool val, changed;
|
||||
int err;
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
|
||||
/* Command requires to use a valid controller index */
|
||||
if (!hdev)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
val = !!cp->param[0];
|
||||
changed = (val != hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED));
|
||||
|
||||
if (!hdev->get_data_path_id) {
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (val)
|
||||
hci_dev_set_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED);
|
||||
else
|
||||
hci_dev_clear_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED);
|
||||
}
|
||||
|
||||
bt_dev_info(hdev, "offload codecs enable %d changed %d",
|
||||
val, changed);
|
||||
|
||||
memcpy(rp.uuid, offload_codecs_uuid, 16);
|
||||
rp.flags = cpu_to_le32(val ? BIT(0) : 0);
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
err = mgmt_cmd_complete(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_offload_codec_feature_changed(val, sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mgmt_exp_feature {
|
||||
const u8 *uuid;
|
||||
int (*set_func)(struct sock *sk, struct hci_dev *hdev,
|
||||
struct mgmt_cp_set_exp_feature *cp, u16 data_len);
|
||||
} exp_features[] = {
|
||||
EXP_FEAT(ZERO_KEY, set_zero_key_func),
|
||||
#ifdef CONFIG_BT_FEATURE_DEBUG
|
||||
EXP_FEAT(debug_uuid, set_debug_func),
|
||||
#endif
|
||||
EXP_FEAT(rpa_resolution_uuid, set_rpa_resolution_func),
|
||||
EXP_FEAT(quality_report_uuid, set_quality_report_func),
|
||||
EXP_FEAT(offload_codecs_uuid, set_offload_codec_func),
|
||||
|
||||
/* end with a null feature */
|
||||
EXP_FEAT(NULL, NULL)
|
||||
};
|
||||
|
||||
static int set_exp_feature(struct sock *sk, struct hci_dev *hdev,
|
||||
void *data, u16 data_len)
|
||||
{
|
||||
struct mgmt_cp_set_exp_feature *cp = data;
|
||||
struct mgmt_rp_set_exp_feature rp;
|
||||
size_t i = 0;
|
||||
|
||||
bt_dev_dbg(hdev, "sock %p", sk);
|
||||
|
||||
if (!memcmp(cp->uuid, ZERO_KEY, 16)) {
|
||||
memset(rp.uuid, 0, 16);
|
||||
rp.flags = cpu_to_le32(0);
|
||||
|
||||
#ifdef CONFIG_BT_FEATURE_DEBUG
|
||||
if (!hdev) {
|
||||
bool changed = bt_dbg_get();
|
||||
|
||||
bt_dbg_set(false);
|
||||
|
||||
if (changed)
|
||||
exp_debug_feature_changed(false, sk);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hdev && use_ll_privacy(hdev) && !hdev_is_powered(hdev)) {
|
||||
bool changed = hci_dev_test_flag(hdev,
|
||||
HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
hci_dev_clear_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
if (changed)
|
||||
exp_ll_privacy_feature_changed(false, hdev, sk);
|
||||
}
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
return mgmt_cmd_complete(sk, hdev ? hdev->id : MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_FEATURE_DEBUG
|
||||
if (!memcmp(cp->uuid, debug_uuid, 16)) {
|
||||
bool val, changed;
|
||||
int err;
|
||||
|
||||
/* Command requires to use the non-controller index */
|
||||
if (hdev)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
val = !!cp->param[0];
|
||||
changed = val ? !bt_dbg_get() : bt_dbg_get();
|
||||
bt_dbg_set(val);
|
||||
|
||||
memcpy(rp.uuid, debug_uuid, 16);
|
||||
rp.flags = cpu_to_le32(val ? BIT(0) : 0);
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
err = mgmt_cmd_complete(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_debug_feature_changed(val, sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!memcmp(cp->uuid, rpa_resolution_uuid, 16)) {
|
||||
bool val, changed;
|
||||
int err;
|
||||
u32 flags;
|
||||
|
||||
/* Command requires to use the controller index */
|
||||
if (!hdev)
|
||||
return mgmt_cmd_status(sk, MGMT_INDEX_NONE,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_INDEX);
|
||||
|
||||
/* Changes can only be made when controller is powered down */
|
||||
if (hdev_is_powered(hdev))
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_REJECTED);
|
||||
|
||||
/* Parameters are limited to a single octet */
|
||||
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
/* Only boolean on/off is supported */
|
||||
if (cp->param[0] != 0x00 && cp->param[0] != 0x01)
|
||||
return mgmt_cmd_status(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE,
|
||||
MGMT_STATUS_INVALID_PARAMS);
|
||||
|
||||
val = !!cp->param[0];
|
||||
|
||||
if (val) {
|
||||
changed = !hci_dev_test_flag(hdev,
|
||||
HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_set_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_clear_flag(hdev, HCI_ADVERTISING);
|
||||
|
||||
/* Enable LL privacy + supported settings changed */
|
||||
flags = BIT(0) | BIT(1);
|
||||
} else {
|
||||
changed = hci_dev_test_flag(hdev,
|
||||
HCI_ENABLE_LL_PRIVACY);
|
||||
hci_dev_clear_flag(hdev, HCI_ENABLE_LL_PRIVACY);
|
||||
|
||||
/* Disable LL privacy + supported settings changed */
|
||||
flags = BIT(1);
|
||||
}
|
||||
|
||||
memcpy(rp.uuid, rpa_resolution_uuid, 16);
|
||||
rp.flags = cpu_to_le32(flags);
|
||||
|
||||
hci_sock_set_flag(sk, HCI_MGMT_EXP_FEATURE_EVENTS);
|
||||
|
||||
err = mgmt_cmd_complete(sk, hdev->id,
|
||||
MGMT_OP_SET_EXP_FEATURE, 0,
|
||||
&rp, sizeof(rp));
|
||||
|
||||
if (changed)
|
||||
exp_ll_privacy_feature_changed(val, hdev, sk);
|
||||
|
||||
return err;
|
||||
for (i = 0; exp_features[i].uuid; i++) {
|
||||
if (!memcmp(cp->uuid, exp_features[i].uuid, 16))
|
||||
return exp_features[i].set_func(sk, hdev, cp, data_len);
|
||||
}
|
||||
|
||||
return mgmt_cmd_status(sk, hdev ? hdev->id : MGMT_INDEX_NONE,
|
||||
@ -7315,6 +7541,11 @@ static int read_local_oob_ext_data(struct sock *sk, struct hci_dev *hdev,
|
||||
if (!rp)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!status && !lmp_ssp_capable(hdev)) {
|
||||
status = MGMT_STATUS_NOT_SUPPORTED;
|
||||
eir_len = 0;
|
||||
}
|
||||
|
||||
if (status)
|
||||
goto complete;
|
||||
|
||||
@ -7526,7 +7757,7 @@ static u8 calculate_name_len(struct hci_dev *hdev)
|
||||
{
|
||||
u8 buf[HCI_MAX_SHORT_NAME_LENGTH + 3];
|
||||
|
||||
return append_local_name(hdev, buf, 0);
|
||||
return eir_append_local_name(hdev, buf, 0);
|
||||
}
|
||||
|
||||
static u8 tlv_data_max_len(struct hci_dev *hdev, u32 adv_flags,
|
||||
@ -8222,7 +8453,7 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
|
||||
* advertising.
|
||||
*/
|
||||
if (hci_dev_test_flag(hdev, HCI_ENABLE_LL_PRIVACY))
|
||||
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
|
||||
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING,
|
||||
MGMT_STATUS_NOT_SUPPORTED);
|
||||
|
||||
hci_dev_lock(hdev);
|
||||
|
@ -94,11 +94,14 @@ struct msft_data {
|
||||
__u16 pending_add_handle;
|
||||
__u16 pending_remove_handle;
|
||||
__u8 reregistering;
|
||||
__u8 suspending;
|
||||
__u8 filter_enabled;
|
||||
};
|
||||
|
||||
static int __msft_add_monitor_pattern(struct hci_dev *hdev,
|
||||
struct adv_monitor *monitor);
|
||||
static int __msft_remove_monitor(struct hci_dev *hdev,
|
||||
struct adv_monitor *monitor, u16 handle);
|
||||
|
||||
bool msft_monitor_supported(struct hci_dev *hdev)
|
||||
{
|
||||
@ -154,7 +157,7 @@ failed:
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
static void reregister_monitor_on_restart(struct hci_dev *hdev, int handle)
|
||||
static void reregister_monitor(struct hci_dev *hdev, int handle)
|
||||
{
|
||||
struct adv_monitor *monitor;
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
@ -182,31 +185,102 @@ static void reregister_monitor_on_restart(struct hci_dev *hdev, int handle)
|
||||
}
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
static void remove_monitor_on_suspend(struct hci_dev *hdev, int handle)
|
||||
{
|
||||
struct adv_monitor *monitor;
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
int err;
|
||||
|
||||
while (1) {
|
||||
monitor = idr_get_next(&hdev->adv_monitors_idr, &handle);
|
||||
if (!monitor) {
|
||||
/* All monitors have been removed */
|
||||
msft->suspending = false;
|
||||
hci_update_background_scan(hdev);
|
||||
return;
|
||||
}
|
||||
|
||||
msft->pending_remove_handle = (u16)handle;
|
||||
err = __msft_remove_monitor(hdev, monitor, handle);
|
||||
|
||||
/* If success, return and wait for monitor removed callback */
|
||||
if (!err)
|
||||
return;
|
||||
|
||||
/* Otherwise free the monitor and keep removing */
|
||||
hci_free_adv_monitor(hdev, monitor);
|
||||
handle++;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
void msft_suspend(struct hci_dev *hdev)
|
||||
{
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
|
||||
if (!msft)
|
||||
return;
|
||||
|
||||
if (msft_monitor_supported(hdev)) {
|
||||
msft->suspending = true;
|
||||
/* Quitely remove all monitors on suspend to avoid waking up
|
||||
* the system.
|
||||
*/
|
||||
remove_monitor_on_suspend(hdev, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
void msft_resume(struct hci_dev *hdev)
|
||||
{
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
|
||||
if (!msft)
|
||||
return;
|
||||
|
||||
if (msft_monitor_supported(hdev)) {
|
||||
msft->reregistering = true;
|
||||
/* Monitors are removed on suspend, so we need to add all
|
||||
* monitors on resume.
|
||||
*/
|
||||
reregister_monitor(hdev, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void msft_do_open(struct hci_dev *hdev)
|
||||
{
|
||||
struct msft_data *msft;
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
|
||||
if (hdev->msft_opcode == HCI_OP_NOP)
|
||||
return;
|
||||
|
||||
if (!msft) {
|
||||
bt_dev_err(hdev, "MSFT extension not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
bt_dev_dbg(hdev, "Initialize MSFT extension");
|
||||
|
||||
msft = kzalloc(sizeof(*msft), GFP_KERNEL);
|
||||
if (!msft)
|
||||
return;
|
||||
/* Reset existing MSFT data before re-reading */
|
||||
kfree(msft->evt_prefix);
|
||||
msft->evt_prefix = NULL;
|
||||
msft->evt_prefix_len = 0;
|
||||
msft->features = 0;
|
||||
|
||||
if (!read_supported_features(hdev, msft)) {
|
||||
hdev->msft_data = NULL;
|
||||
kfree(msft);
|
||||
return;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&msft->handle_map);
|
||||
hdev->msft_data = msft;
|
||||
|
||||
if (msft_monitor_supported(hdev)) {
|
||||
msft->reregistering = true;
|
||||
msft_set_filter_enable(hdev, true);
|
||||
reregister_monitor_on_restart(hdev, 0);
|
||||
/* Monitors get removed on power off, so we need to explicitly
|
||||
* tell the controller to re-monitor.
|
||||
*/
|
||||
reregister_monitor(hdev, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,8 +295,9 @@ void msft_do_close(struct hci_dev *hdev)
|
||||
|
||||
bt_dev_dbg(hdev, "Cleanup of MSFT extension");
|
||||
|
||||
hdev->msft_data = NULL;
|
||||
|
||||
/* The controller will silently remove all monitors on power off.
|
||||
* Therefore, remove handle_data mapping and reset monitor state.
|
||||
*/
|
||||
list_for_each_entry_safe(handle_data, tmp, &msft->handle_map, list) {
|
||||
monitor = idr_find(&hdev->adv_monitors_idr,
|
||||
handle_data->mgmt_handle);
|
||||
@ -233,6 +308,34 @@ void msft_do_close(struct hci_dev *hdev)
|
||||
list_del(&handle_data->list);
|
||||
kfree(handle_data);
|
||||
}
|
||||
}
|
||||
|
||||
void msft_register(struct hci_dev *hdev)
|
||||
{
|
||||
struct msft_data *msft = NULL;
|
||||
|
||||
bt_dev_dbg(hdev, "Register MSFT extension");
|
||||
|
||||
msft = kzalloc(sizeof(*msft), GFP_KERNEL);
|
||||
if (!msft) {
|
||||
bt_dev_err(hdev, "Failed to register MSFT extension");
|
||||
return;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&msft->handle_map);
|
||||
hdev->msft_data = msft;
|
||||
}
|
||||
|
||||
void msft_unregister(struct hci_dev *hdev)
|
||||
{
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
|
||||
if (!msft)
|
||||
return;
|
||||
|
||||
bt_dev_dbg(hdev, "Unregister MSFT extension");
|
||||
|
||||
hdev->msft_data = NULL;
|
||||
|
||||
kfree(msft->evt_prefix);
|
||||
kfree(msft);
|
||||
@ -345,8 +448,7 @@ unlock:
|
||||
|
||||
/* If in restart/reregister sequence, keep registering. */
|
||||
if (msft->reregistering)
|
||||
reregister_monitor_on_restart(hdev,
|
||||
msft->pending_add_handle + 1);
|
||||
reregister_monitor(hdev, msft->pending_add_handle + 1);
|
||||
|
||||
hci_dev_unlock(hdev);
|
||||
|
||||
@ -383,13 +485,25 @@ static void msft_le_cancel_monitor_advertisement_cb(struct hci_dev *hdev,
|
||||
if (handle_data) {
|
||||
monitor = idr_find(&hdev->adv_monitors_idr,
|
||||
handle_data->mgmt_handle);
|
||||
if (monitor)
|
||||
|
||||
if (monitor && monitor->state == ADV_MONITOR_STATE_OFFLOADED)
|
||||
monitor->state = ADV_MONITOR_STATE_REGISTERED;
|
||||
|
||||
/* Do not free the monitor if it is being removed due to
|
||||
* suspend. It will be re-monitored on resume.
|
||||
*/
|
||||
if (monitor && !msft->suspending)
|
||||
hci_free_adv_monitor(hdev, monitor);
|
||||
|
||||
list_del(&handle_data->list);
|
||||
kfree(handle_data);
|
||||
}
|
||||
|
||||
/* If in suspend/remove sequence, keep removing. */
|
||||
if (msft->suspending)
|
||||
remove_monitor_on_suspend(hdev,
|
||||
msft->pending_remove_handle + 1);
|
||||
|
||||
/* If remove all monitors is required, we need to continue the process
|
||||
* here because the earlier it was paused when waiting for the
|
||||
* response from controller.
|
||||
@ -408,7 +522,8 @@ static void msft_le_cancel_monitor_advertisement_cb(struct hci_dev *hdev,
|
||||
hci_dev_unlock(hdev);
|
||||
|
||||
done:
|
||||
hci_remove_adv_monitor_complete(hdev, status);
|
||||
if (!msft->suspending)
|
||||
hci_remove_adv_monitor_complete(hdev, status);
|
||||
}
|
||||
|
||||
static void msft_le_set_advertisement_filter_enable_cb(struct hci_dev *hdev,
|
||||
@ -541,15 +656,15 @@ int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor)
|
||||
if (!msft)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (msft->reregistering)
|
||||
if (msft->reregistering || msft->suspending)
|
||||
return -EBUSY;
|
||||
|
||||
return __msft_add_monitor_pattern(hdev, monitor);
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
|
||||
u16 handle)
|
||||
static int __msft_remove_monitor(struct hci_dev *hdev,
|
||||
struct adv_monitor *monitor, u16 handle)
|
||||
{
|
||||
struct msft_cp_le_cancel_monitor_advertisement cp;
|
||||
struct msft_monitor_advertisement_handle_data *handle_data;
|
||||
@ -557,12 +672,6 @@ int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
int err = 0;
|
||||
|
||||
if (!msft)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (msft->reregistering)
|
||||
return -EBUSY;
|
||||
|
||||
handle_data = msft_find_handle_data(hdev, monitor->handle, true);
|
||||
|
||||
/* If no matched handle, just remove without telling controller */
|
||||
@ -582,6 +691,21 @@ int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
|
||||
return err;
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
|
||||
u16 handle)
|
||||
{
|
||||
struct msft_data *msft = hdev->msft_data;
|
||||
|
||||
if (!msft)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (msft->reregistering || msft->suspending)
|
||||
return -EBUSY;
|
||||
|
||||
return __msft_remove_monitor(hdev, monitor, handle);
|
||||
}
|
||||
|
||||
void msft_req_add_set_filter_enable(struct hci_request *req, bool enable)
|
||||
{
|
||||
struct hci_dev *hdev = req->hdev;
|
||||
|
@ -13,6 +13,8 @@
|
||||
#if IS_ENABLED(CONFIG_BT_MSFTEXT)
|
||||
|
||||
bool msft_monitor_supported(struct hci_dev *hdev);
|
||||
void msft_register(struct hci_dev *hdev);
|
||||
void msft_unregister(struct hci_dev *hdev);
|
||||
void msft_do_open(struct hci_dev *hdev);
|
||||
void msft_do_close(struct hci_dev *hdev);
|
||||
void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb);
|
||||
@ -22,6 +24,8 @@ int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
|
||||
u16 handle);
|
||||
void msft_req_add_set_filter_enable(struct hci_request *req, bool enable);
|
||||
int msft_set_filter_enable(struct hci_dev *hdev, bool enable);
|
||||
void msft_suspend(struct hci_dev *hdev);
|
||||
void msft_resume(struct hci_dev *hdev);
|
||||
bool msft_curve_validity(struct hci_dev *hdev);
|
||||
|
||||
#else
|
||||
@ -31,6 +35,8 @@ static inline bool msft_monitor_supported(struct hci_dev *hdev)
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void msft_register(struct hci_dev *hdev) {}
|
||||
static inline void msft_unregister(struct hci_dev *hdev) {}
|
||||
static inline void msft_do_open(struct hci_dev *hdev) {}
|
||||
static inline void msft_do_close(struct hci_dev *hdev) {}
|
||||
static inline void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb) {}
|
||||
@ -55,6 +61,9 @@ static inline int msft_set_filter_enable(struct hci_dev *hdev, bool enable)
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline void msft_suspend(struct hci_dev *hdev) {}
|
||||
static inline void msft_resume(struct hci_dev *hdev) {}
|
||||
|
||||
static inline bool msft_curve_validity(struct hci_dev *hdev)
|
||||
{
|
||||
return false;
|
||||
|
@ -549,22 +549,58 @@ struct rfcomm_dlc *rfcomm_dlc_exists(bdaddr_t *src, bdaddr_t *dst, u8 channel)
|
||||
return dlc;
|
||||
}
|
||||
|
||||
int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb)
|
||||
static int rfcomm_dlc_send_frag(struct rfcomm_dlc *d, struct sk_buff *frag)
|
||||
{
|
||||
int len = skb->len;
|
||||
|
||||
if (d->state != BT_CONNECTED)
|
||||
return -ENOTCONN;
|
||||
int len = frag->len;
|
||||
|
||||
BT_DBG("dlc %p mtu %d len %d", d, d->mtu, len);
|
||||
|
||||
if (len > d->mtu)
|
||||
return -EINVAL;
|
||||
|
||||
rfcomm_make_uih(skb, d->addr);
|
||||
skb_queue_tail(&d->tx_queue, skb);
|
||||
rfcomm_make_uih(frag, d->addr);
|
||||
__skb_queue_tail(&d->tx_queue, frag);
|
||||
|
||||
if (!test_bit(RFCOMM_TX_THROTTLED, &d->flags))
|
||||
return len;
|
||||
}
|
||||
|
||||
int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct sk_buff *frag, *next;
|
||||
int len;
|
||||
|
||||
if (d->state != BT_CONNECTED)
|
||||
return -ENOTCONN;
|
||||
|
||||
frag = skb_shinfo(skb)->frag_list;
|
||||
skb_shinfo(skb)->frag_list = NULL;
|
||||
|
||||
/* Queue all fragments atomically. */
|
||||
spin_lock_irqsave(&d->tx_queue.lock, flags);
|
||||
|
||||
len = rfcomm_dlc_send_frag(d, skb);
|
||||
if (len < 0 || !frag)
|
||||
goto unlock;
|
||||
|
||||
for (; frag; frag = next) {
|
||||
int ret;
|
||||
|
||||
next = frag->next;
|
||||
|
||||
ret = rfcomm_dlc_send_frag(d, frag);
|
||||
if (ret < 0) {
|
||||
kfree_skb(frag);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
len += ret;
|
||||
}
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&d->tx_queue.lock, flags);
|
||||
|
||||
if (len > 0 && !test_bit(RFCOMM_TX_THROTTLED, &d->flags))
|
||||
rfcomm_schedule();
|
||||
return len;
|
||||
}
|
||||
|
@ -575,47 +575,21 @@ static int rfcomm_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
lock_sock(sk);
|
||||
|
||||
sent = bt_sock_wait_ready(sk, msg->msg_flags);
|
||||
if (sent)
|
||||
goto done;
|
||||
|
||||
while (len) {
|
||||
size_t size = min_t(size_t, len, d->mtu);
|
||||
int err;
|
||||
|
||||
skb = sock_alloc_send_skb(sk, size + RFCOMM_SKB_RESERVE,
|
||||
msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb) {
|
||||
if (sent == 0)
|
||||
sent = err;
|
||||
break;
|
||||
}
|
||||
skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE);
|
||||
|
||||
err = memcpy_from_msg(skb_put(skb, size), msg, size);
|
||||
if (err) {
|
||||
kfree_skb(skb);
|
||||
if (sent == 0)
|
||||
sent = err;
|
||||
break;
|
||||
}
|
||||
|
||||
skb->priority = sk->sk_priority;
|
||||
|
||||
err = rfcomm_dlc_send(d, skb);
|
||||
if (err < 0) {
|
||||
kfree_skb(skb);
|
||||
if (sent == 0)
|
||||
sent = err;
|
||||
break;
|
||||
}
|
||||
|
||||
sent += size;
|
||||
len -= size;
|
||||
}
|
||||
|
||||
done:
|
||||
release_sock(sk);
|
||||
|
||||
if (sent)
|
||||
return sent;
|
||||
|
||||
skb = bt_skb_sendmmsg(sk, msg, len, d->mtu, RFCOMM_SKB_HEAD_RESERVE,
|
||||
RFCOMM_SKB_TAIL_RESERVE);
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
sent = rfcomm_dlc_send(d, skb);
|
||||
if (sent < 0)
|
||||
kfree_skb(skb);
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ struct sco_pinfo {
|
||||
__u32 flags;
|
||||
__u16 setting;
|
||||
__u8 cmsg_mask;
|
||||
struct bt_codec codec;
|
||||
struct sco_conn *conn;
|
||||
};
|
||||
|
||||
@ -133,6 +134,7 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
|
||||
return NULL;
|
||||
|
||||
spin_lock_init(&conn->lock);
|
||||
INIT_DELAYED_WORK(&conn->timeout_work, sco_sock_timeout);
|
||||
|
||||
hcon->sco_data = conn;
|
||||
conn->hcon = hcon;
|
||||
@ -187,20 +189,21 @@ static void sco_conn_del(struct hci_conn *hcon, int err)
|
||||
/* Kill socket */
|
||||
sco_conn_lock(conn);
|
||||
sk = conn->sk;
|
||||
if (sk)
|
||||
sock_hold(sk);
|
||||
sco_conn_unlock(conn);
|
||||
|
||||
if (sk) {
|
||||
sock_hold(sk);
|
||||
lock_sock(sk);
|
||||
sco_sock_clear_timer(sk);
|
||||
sco_chan_del(sk, err);
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
|
||||
/* Ensure no more work items will run before freeing conn. */
|
||||
cancel_delayed_work_sync(&conn->timeout_work);
|
||||
}
|
||||
|
||||
/* Ensure no more work items will run before freeing conn. */
|
||||
cancel_delayed_work_sync(&conn->timeout_work);
|
||||
|
||||
hcon->sco_data = NULL;
|
||||
kfree(conn);
|
||||
}
|
||||
@ -213,8 +216,6 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk,
|
||||
sco_pi(sk)->conn = conn;
|
||||
conn->sk = sk;
|
||||
|
||||
INIT_DELAYED_WORK(&conn->timeout_work, sco_sock_timeout);
|
||||
|
||||
if (parent)
|
||||
bt_accept_enqueue(parent, sk, true);
|
||||
}
|
||||
@ -252,7 +253,7 @@ static int sco_connect(struct hci_dev *hdev, struct sock *sk)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
hcon = hci_connect_sco(hdev, type, &sco_pi(sk)->dst,
|
||||
sco_pi(sk)->setting);
|
||||
sco_pi(sk)->setting, &sco_pi(sk)->codec);
|
||||
if (IS_ERR(hcon))
|
||||
return PTR_ERR(hcon);
|
||||
|
||||
@ -280,11 +281,10 @@ static int sco_connect(struct hci_dev *hdev, struct sock *sk)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int sco_send_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
static int sco_send_frame(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
struct sco_conn *conn = sco_pi(sk)->conn;
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
int len = skb->len;
|
||||
|
||||
/* Check outgoing MTU */
|
||||
if (len > conn->mtu)
|
||||
@ -292,15 +292,6 @@ static int sco_send_frame(struct sock *sk, struct msghdr *msg, int len)
|
||||
|
||||
BT_DBG("sk %p len %d", sk, len);
|
||||
|
||||
skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb)
|
||||
return err;
|
||||
|
||||
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
|
||||
kfree_skb(skb);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
hci_send_sco(conn->hcon, skb);
|
||||
|
||||
return len;
|
||||
@ -444,6 +435,7 @@ static void __sco_sock_close(struct sock *sk)
|
||||
sock_set_flag(sk, SOCK_ZAPPED);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Must be called on unlocked socket. */
|
||||
@ -504,6 +496,10 @@ static struct sock *sco_sock_alloc(struct net *net, struct socket *sock,
|
||||
sk->sk_state = BT_OPEN;
|
||||
|
||||
sco_pi(sk)->setting = BT_VOICE_CVSD_16BIT;
|
||||
sco_pi(sk)->codec.id = BT_CODEC_CVSD;
|
||||
sco_pi(sk)->codec.cid = 0xffff;
|
||||
sco_pi(sk)->codec.vid = 0xffff;
|
||||
sco_pi(sk)->codec.data_path = 0x00;
|
||||
|
||||
bt_sock_link(&sco_sk_list, sk);
|
||||
return sk;
|
||||
@ -725,6 +721,7 @@ static int sco_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
size_t len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
|
||||
BT_DBG("sock %p, sk %p", sock, sk);
|
||||
@ -736,14 +733,21 @@ static int sco_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
if (msg->msg_flags & MSG_OOB)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
skb = bt_skb_sendmsg(sk, msg, len, len, 0, 0);
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
if (sk->sk_state == BT_CONNECTED)
|
||||
err = sco_send_frame(sk, msg, len);
|
||||
err = sco_send_frame(sk, skb);
|
||||
else
|
||||
err = -ENOTCONN;
|
||||
|
||||
release_sock(sk);
|
||||
|
||||
if (err < 0)
|
||||
kfree_skb(skb);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -825,6 +829,9 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
int len, err = 0;
|
||||
struct bt_voice voice;
|
||||
u32 opt;
|
||||
struct bt_codecs *codecs;
|
||||
struct hci_dev *hdev;
|
||||
__u8 buffer[255];
|
||||
|
||||
BT_DBG("sk %p", sk);
|
||||
|
||||
@ -872,6 +879,16 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
}
|
||||
|
||||
sco_pi(sk)->setting = voice.setting;
|
||||
hdev = hci_get_route(&sco_pi(sk)->dst, &sco_pi(sk)->src,
|
||||
BDADDR_BREDR);
|
||||
if (!hdev) {
|
||||
err = -EBADFD;
|
||||
break;
|
||||
}
|
||||
if (enhanced_sco_capable(hdev) &&
|
||||
voice.setting == BT_VOICE_TRANSPARENT)
|
||||
sco_pi(sk)->codec.id = BT_CODEC_TRANSPARENT;
|
||||
hci_dev_put(hdev);
|
||||
break;
|
||||
|
||||
case BT_PKT_STATUS:
|
||||
@ -886,6 +903,57 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||
sco_pi(sk)->cmsg_mask &= SCO_CMSG_PKT_STATUS;
|
||||
break;
|
||||
|
||||
case BT_CODEC:
|
||||
if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND &&
|
||||
sk->sk_state != BT_CONNECT2) {
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
hdev = hci_get_route(&sco_pi(sk)->dst, &sco_pi(sk)->src,
|
||||
BDADDR_BREDR);
|
||||
if (!hdev) {
|
||||
err = -EBADFD;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED)) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hdev->get_data_path_id) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (optlen < sizeof(struct bt_codecs) ||
|
||||
optlen > sizeof(buffer)) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_sockptr(buffer, optval, optlen)) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
codecs = (void *)buffer;
|
||||
|
||||
if (codecs->num_codecs > 1) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
sco_pi(sk)->codec = codecs->codecs[0];
|
||||
hci_dev_put(hdev);
|
||||
break;
|
||||
|
||||
default:
|
||||
err = -ENOPROTOOPT;
|
||||
break;
|
||||
@ -964,6 +1032,12 @@ static int sco_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||
struct bt_voice voice;
|
||||
u32 phys;
|
||||
int pkt_status;
|
||||
int buf_len;
|
||||
struct codec_list *c;
|
||||
u8 num_codecs, i, __user *ptr;
|
||||
struct hci_dev *hdev;
|
||||
struct hci_codec_caps *caps;
|
||||
struct bt_codec codec;
|
||||
|
||||
BT_DBG("sk %p", sk);
|
||||
|
||||
@ -1028,6 +1102,101 @@ static int sco_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||
err = -EFAULT;
|
||||
break;
|
||||
|
||||
case BT_CODEC:
|
||||
num_codecs = 0;
|
||||
buf_len = 0;
|
||||
|
||||
hdev = hci_get_route(&sco_pi(sk)->dst, &sco_pi(sk)->src, BDADDR_BREDR);
|
||||
if (!hdev) {
|
||||
err = -EBADFD;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED)) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hdev->get_data_path_id) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* find total buffer size required to copy codec + caps */
|
||||
hci_dev_lock(hdev);
|
||||
list_for_each_entry(c, &hdev->local_codecs, list) {
|
||||
if (c->transport != HCI_TRANSPORT_SCO_ESCO)
|
||||
continue;
|
||||
num_codecs++;
|
||||
for (i = 0, caps = c->caps; i < c->num_caps; i++) {
|
||||
buf_len += 1 + caps->len;
|
||||
caps = (void *)&caps->data[caps->len];
|
||||
}
|
||||
buf_len += sizeof(struct bt_codec);
|
||||
}
|
||||
hci_dev_unlock(hdev);
|
||||
|
||||
buf_len += sizeof(struct bt_codecs);
|
||||
if (buf_len > len) {
|
||||
hci_dev_put(hdev);
|
||||
err = -ENOBUFS;
|
||||
break;
|
||||
}
|
||||
ptr = optval;
|
||||
|
||||
if (put_user(num_codecs, ptr)) {
|
||||
hci_dev_put(hdev);
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ptr += sizeof(num_codecs);
|
||||
|
||||
/* Iterate all the codecs supported over SCO and populate
|
||||
* codec data
|
||||
*/
|
||||
hci_dev_lock(hdev);
|
||||
list_for_each_entry(c, &hdev->local_codecs, list) {
|
||||
if (c->transport != HCI_TRANSPORT_SCO_ESCO)
|
||||
continue;
|
||||
|
||||
codec.id = c->id;
|
||||
codec.cid = c->cid;
|
||||
codec.vid = c->vid;
|
||||
err = hdev->get_data_path_id(hdev, &codec.data_path);
|
||||
if (err < 0)
|
||||
break;
|
||||
codec.num_caps = c->num_caps;
|
||||
if (copy_to_user(ptr, &codec, sizeof(codec))) {
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ptr += sizeof(codec);
|
||||
|
||||
/* find codec capabilities data length */
|
||||
len = 0;
|
||||
for (i = 0, caps = c->caps; i < c->num_caps; i++) {
|
||||
len += 1 + caps->len;
|
||||
caps = (void *)&caps->data[caps->len];
|
||||
}
|
||||
|
||||
/* copy codec capabilities data */
|
||||
if (len && copy_to_user(ptr, c->caps, len)) {
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ptr += len;
|
||||
}
|
||||
|
||||
if (!err && put_user(buf_len, optlen))
|
||||
err = -EFAULT;
|
||||
|
||||
hci_dev_unlock(hdev);
|
||||
hci_dev_put(hdev);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
err = -ENOPROTOOPT;
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user