Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next
Johan Hedberg says: ==================== pull request: bluetooth-next 2016-09-19 Here's the main bluetooth-next pull request for the 4.9 kernel. - Added new messages for monitor sockets for better mgmt tracing - Added local name and appearance support in scan response - Added new Qualcomm WCNSS SMD based HCI driver - Minor fixes & cleanup to 802.15.4 code - New USB ID to btusb driver - Added Marvell support to HCI UART driver - Add combined LED trigger for controller power - Other minor fixes here and there Please let me know if there are any issues pulling. Thanks. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
204dfe1798
|
@ -180,6 +180,17 @@ config BT_HCIUART_AG6XX
|
||||||
|
|
||||||
Say Y here to compile support for Intel AG6XX protocol.
|
Say Y here to compile support for Intel AG6XX protocol.
|
||||||
|
|
||||||
|
config BT_HCIUART_MRVL
|
||||||
|
bool "Marvell protocol support"
|
||||||
|
depends on BT_HCIUART
|
||||||
|
select BT_HCIUART_H4
|
||||||
|
help
|
||||||
|
Marvell is serial protocol for communication between Bluetooth
|
||||||
|
device and host. This protocol is required for most Marvell Bluetooth
|
||||||
|
devices with UART interface.
|
||||||
|
|
||||||
|
Say Y here to compile support for HCI MRVL protocol.
|
||||||
|
|
||||||
config BT_HCIBCM203X
|
config BT_HCIBCM203X
|
||||||
tristate "HCI BCM203x USB driver"
|
tristate "HCI BCM203x USB driver"
|
||||||
depends on USB
|
depends on USB
|
||||||
|
@ -331,4 +342,16 @@ config BT_WILINK
|
||||||
Say Y here to compile support for Texas Instrument's WiLink7 driver
|
Say Y here to compile support for Texas Instrument's WiLink7 driver
|
||||||
into the kernel or say M to compile it as module (btwilink).
|
into the kernel or say M to compile it as module (btwilink).
|
||||||
|
|
||||||
|
config BT_QCOMSMD
|
||||||
|
tristate "Qualcomm SMD based HCI support"
|
||||||
|
depends on QCOM_SMD && QCOM_WCNSS_CTRL
|
||||||
|
select BT_QCA
|
||||||
|
help
|
||||||
|
Qualcomm SMD based HCI driver.
|
||||||
|
This driver is used to bridge HCI data onto the shared memory
|
||||||
|
channels to the WCNSS core.
|
||||||
|
|
||||||
|
Say Y here to compile support for HCI over Qualcomm SMD into the
|
||||||
|
kernel or say M to compile as a module.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -20,6 +20,7 @@ obj-$(CONFIG_BT_ATH3K) += ath3k.o
|
||||||
obj-$(CONFIG_BT_MRVL) += btmrvl.o
|
obj-$(CONFIG_BT_MRVL) += btmrvl.o
|
||||||
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
|
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
|
||||||
obj-$(CONFIG_BT_WILINK) += btwilink.o
|
obj-$(CONFIG_BT_WILINK) += btwilink.o
|
||||||
|
obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o
|
||||||
obj-$(CONFIG_BT_BCM) += btbcm.o
|
obj-$(CONFIG_BT_BCM) += btbcm.o
|
||||||
obj-$(CONFIG_BT_RTL) += btrtl.o
|
obj-$(CONFIG_BT_RTL) += btrtl.o
|
||||||
obj-$(CONFIG_BT_QCA) += btqca.o
|
obj-$(CONFIG_BT_QCA) += btqca.o
|
||||||
|
@ -37,6 +38,7 @@ hci_uart-$(CONFIG_BT_HCIUART_INTEL) += hci_intel.o
|
||||||
hci_uart-$(CONFIG_BT_HCIUART_BCM) += hci_bcm.o
|
hci_uart-$(CONFIG_BT_HCIUART_BCM) += hci_bcm.o
|
||||||
hci_uart-$(CONFIG_BT_HCIUART_QCA) += hci_qca.o
|
hci_uart-$(CONFIG_BT_HCIUART_QCA) += hci_qca.o
|
||||||
hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o
|
hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o
|
||||||
|
hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o
|
||||||
hci_uart-objs := $(hci_uart-y)
|
hci_uart-objs := $(hci_uart-y)
|
||||||
|
|
||||||
ccflags-y += -D__CHECK_ENDIAN__
|
ccflags-y += -D__CHECK_ENDIAN__
|
||||||
|
|
|
@ -185,10 +185,8 @@ static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id
|
||||||
data->state = BCM203X_LOAD_MINIDRV;
|
data->state = BCM203X_LOAD_MINIDRV;
|
||||||
|
|
||||||
data->urb = usb_alloc_urb(0, GFP_KERNEL);
|
data->urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||||
if (!data->urb) {
|
if (!data->urb)
|
||||||
BT_ERR("Can't allocate URB");
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
|
||||||
|
|
||||||
if (request_firmware(&firmware, "BCM2033-MD.hex", &udev->dev) < 0) {
|
if (request_firmware(&firmware, "BCM2033-MD.hex", &udev->dev) < 0) {
|
||||||
BT_ERR("Mini driver request failed");
|
BT_ERR("Mini driver request failed");
|
||||||
|
|
|
@ -55,8 +55,8 @@ static int rome_patch_ver_req(struct hci_dev *hdev, u32 *rome_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
edl = (struct edl_event_hdr *)(skb->data);
|
edl = (struct edl_event_hdr *)(skb->data);
|
||||||
if (!edl || !edl->data) {
|
if (!edl) {
|
||||||
BT_ERR("%s: TLV with no header or no data", hdev->name);
|
BT_ERR("%s: TLV with no header", hdev->name);
|
||||||
err = -EILSEQ;
|
err = -EILSEQ;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -224,8 +224,8 @@ static int rome_tlv_send_segment(struct hci_dev *hdev, int idx, int seg_size,
|
||||||
}
|
}
|
||||||
|
|
||||||
edl = (struct edl_event_hdr *)(skb->data);
|
edl = (struct edl_event_hdr *)(skb->data);
|
||||||
if (!edl || !edl->data) {
|
if (!edl) {
|
||||||
BT_ERR("%s: TLV with no header or no data", hdev->name);
|
BT_ERR("%s: TLV with no header", hdev->name);
|
||||||
err = -EILSEQ;
|
err = -EILSEQ;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, Linaro Ltd.
|
||||||
|
* Copyright (c) 2015, Sony Mobile Communications Inc.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 and
|
||||||
|
* only version 2 as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/soc/qcom/smd.h>
|
||||||
|
#include <linux/soc/qcom/wcnss_ctrl.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#include <net/bluetooth/bluetooth.h>
|
||||||
|
#include <net/bluetooth/hci_core.h>
|
||||||
|
|
||||||
|
#include "btqca.h"
|
||||||
|
|
||||||
|
struct btqcomsmd {
|
||||||
|
struct hci_dev *hdev;
|
||||||
|
|
||||||
|
struct qcom_smd_channel *acl_channel;
|
||||||
|
struct qcom_smd_channel *cmd_channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int btqcomsmd_recv(struct hci_dev *hdev, unsigned int type,
|
||||||
|
const void *data, size_t count)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* Use GFP_ATOMIC as we're in IRQ context */
|
||||||
|
skb = bt_skb_alloc(count, GFP_ATOMIC);
|
||||||
|
if (!skb) {
|
||||||
|
hdev->stat.err_rx++;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
hci_skb_pkt_type(skb) = type;
|
||||||
|
memcpy(skb_put(skb, count), data, count);
|
||||||
|
|
||||||
|
return hci_recv_frame(hdev, skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_acl_callback(struct qcom_smd_channel *channel,
|
||||||
|
const void *data, size_t count)
|
||||||
|
{
|
||||||
|
struct btqcomsmd *btq = qcom_smd_get_drvdata(channel);
|
||||||
|
|
||||||
|
btq->hdev->stat.byte_rx += count;
|
||||||
|
return btqcomsmd_recv(btq->hdev, HCI_ACLDATA_PKT, data, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_cmd_callback(struct qcom_smd_channel *channel,
|
||||||
|
const void *data, size_t count)
|
||||||
|
{
|
||||||
|
struct btqcomsmd *btq = qcom_smd_get_drvdata(channel);
|
||||||
|
|
||||||
|
return btqcomsmd_recv(btq->hdev, HCI_EVENT_PKT, data, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_send(struct hci_dev *hdev, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct btqcomsmd *btq = hci_get_drvdata(hdev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch (hci_skb_pkt_type(skb)) {
|
||||||
|
case HCI_ACLDATA_PKT:
|
||||||
|
ret = qcom_smd_send(btq->acl_channel, skb->data, skb->len);
|
||||||
|
hdev->stat.acl_tx++;
|
||||||
|
hdev->stat.byte_tx += skb->len;
|
||||||
|
break;
|
||||||
|
case HCI_COMMAND_PKT:
|
||||||
|
ret = qcom_smd_send(btq->cmd_channel, skb->data, skb->len);
|
||||||
|
hdev->stat.cmd_tx++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EILSEQ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_open(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_close(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct btqcomsmd *btq;
|
||||||
|
struct hci_dev *hdev;
|
||||||
|
void *wcnss;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
btq = devm_kzalloc(&pdev->dev, sizeof(*btq), GFP_KERNEL);
|
||||||
|
if (!btq)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
wcnss = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
|
||||||
|
btq->acl_channel = qcom_wcnss_open_channel(wcnss, "APPS_RIVA_BT_ACL",
|
||||||
|
btqcomsmd_acl_callback);
|
||||||
|
if (IS_ERR(btq->acl_channel))
|
||||||
|
return PTR_ERR(btq->acl_channel);
|
||||||
|
|
||||||
|
btq->cmd_channel = qcom_wcnss_open_channel(wcnss, "APPS_RIVA_BT_CMD",
|
||||||
|
btqcomsmd_cmd_callback);
|
||||||
|
if (IS_ERR(btq->cmd_channel))
|
||||||
|
return PTR_ERR(btq->cmd_channel);
|
||||||
|
|
||||||
|
qcom_smd_set_drvdata(btq->acl_channel, btq);
|
||||||
|
qcom_smd_set_drvdata(btq->cmd_channel, btq);
|
||||||
|
|
||||||
|
hdev = hci_alloc_dev();
|
||||||
|
if (!hdev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
hci_set_drvdata(hdev, btq);
|
||||||
|
btq->hdev = hdev;
|
||||||
|
SET_HCIDEV_DEV(hdev, &pdev->dev);
|
||||||
|
|
||||||
|
hdev->bus = HCI_SMD;
|
||||||
|
hdev->open = btqcomsmd_open;
|
||||||
|
hdev->close = btqcomsmd_close;
|
||||||
|
hdev->send = btqcomsmd_send;
|
||||||
|
hdev->set_bdaddr = qca_set_bdaddr_rome;
|
||||||
|
|
||||||
|
ret = hci_register_dev(hdev);
|
||||||
|
if (ret < 0) {
|
||||||
|
hci_free_dev(hdev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, btq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int btqcomsmd_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct btqcomsmd *btq = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
hci_unregister_dev(btq->hdev);
|
||||||
|
hci_free_dev(btq->hdev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id btqcomsmd_of_match[] = {
|
||||||
|
{ .compatible = "qcom,wcnss-bt", },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver btqcomsmd_driver = {
|
||||||
|
.probe = btqcomsmd_probe,
|
||||||
|
.remove = btqcomsmd_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "btqcomsmd",
|
||||||
|
.of_match_table = btqcomsmd_of_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(btqcomsmd_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
|
||||||
|
MODULE_DESCRIPTION("Qualcomm SMD HCI driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
|
@ -33,6 +33,7 @@
|
||||||
#define RTL_ROM_LMP_8723B 0x8723
|
#define RTL_ROM_LMP_8723B 0x8723
|
||||||
#define RTL_ROM_LMP_8821A 0x8821
|
#define RTL_ROM_LMP_8821A 0x8821
|
||||||
#define RTL_ROM_LMP_8761A 0x8761
|
#define RTL_ROM_LMP_8761A 0x8761
|
||||||
|
#define RTL_ROM_LMP_8822B 0x8822
|
||||||
|
|
||||||
static int rtl_read_rom_version(struct hci_dev *hdev, u8 *version)
|
static int rtl_read_rom_version(struct hci_dev *hdev, u8 *version)
|
||||||
{
|
{
|
||||||
|
@ -78,11 +79,15 @@ static int rtl8723b_parse_firmware(struct hci_dev *hdev, u16 lmp_subver,
|
||||||
const unsigned char *patch_length_base, *patch_offset_base;
|
const unsigned char *patch_length_base, *patch_offset_base;
|
||||||
u32 patch_offset = 0;
|
u32 patch_offset = 0;
|
||||||
u16 patch_length, num_patches;
|
u16 patch_length, num_patches;
|
||||||
const u16 project_id_to_lmp_subver[] = {
|
static const struct {
|
||||||
RTL_ROM_LMP_8723A,
|
__u16 lmp_subver;
|
||||||
RTL_ROM_LMP_8723B,
|
__u8 id;
|
||||||
RTL_ROM_LMP_8821A,
|
} project_id_to_lmp_subver[] = {
|
||||||
RTL_ROM_LMP_8761A
|
{ RTL_ROM_LMP_8723A, 0 },
|
||||||
|
{ RTL_ROM_LMP_8723B, 1 },
|
||||||
|
{ RTL_ROM_LMP_8821A, 2 },
|
||||||
|
{ RTL_ROM_LMP_8761A, 3 },
|
||||||
|
{ RTL_ROM_LMP_8822B, 8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
ret = rtl_read_rom_version(hdev, &rom_version);
|
ret = rtl_read_rom_version(hdev, &rom_version);
|
||||||
|
@ -134,14 +139,20 @@ static int rtl8723b_parse_firmware(struct hci_dev *hdev, u16 lmp_subver,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project_id >= ARRAY_SIZE(project_id_to_lmp_subver)) {
|
/* Find project_id in table */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(project_id_to_lmp_subver); i++) {
|
||||||
|
if (project_id == project_id_to_lmp_subver[i].id)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= ARRAY_SIZE(project_id_to_lmp_subver)) {
|
||||||
BT_ERR("%s: unknown project id %d", hdev->name, project_id);
|
BT_ERR("%s: unknown project id %d", hdev->name, project_id);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lmp_subver != project_id_to_lmp_subver[project_id]) {
|
if (lmp_subver != project_id_to_lmp_subver[i].lmp_subver) {
|
||||||
BT_ERR("%s: firmware is for %x but this is a %x", hdev->name,
|
BT_ERR("%s: firmware is for %x but this is a %x", hdev->name,
|
||||||
project_id_to_lmp_subver[project_id], lmp_subver);
|
project_id_to_lmp_subver[i].lmp_subver, lmp_subver);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +268,26 @@ out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int rtl_load_config(struct hci_dev *hdev, const char *name, u8 **buff)
|
||||||
|
{
|
||||||
|
const struct firmware *fw;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
BT_INFO("%s: rtl: loading %s", hdev->name, name);
|
||||||
|
ret = request_firmware(&fw, name, &hdev->dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
BT_ERR("%s: Failed to load %s", hdev->name, name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = fw->size;
|
||||||
|
*buff = kmemdup(fw->data, ret, GFP_KERNEL);
|
||||||
|
|
||||||
|
release_firmware(fw);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int btrtl_setup_rtl8723a(struct hci_dev *hdev)
|
static int btrtl_setup_rtl8723a(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
const struct firmware *fw;
|
const struct firmware *fw;
|
||||||
|
@ -296,25 +327,74 @@ static int btrtl_setup_rtl8723b(struct hci_dev *hdev, u16 lmp_subver,
|
||||||
unsigned char *fw_data = NULL;
|
unsigned char *fw_data = NULL;
|
||||||
const struct firmware *fw;
|
const struct firmware *fw;
|
||||||
int ret;
|
int ret;
|
||||||
|
int cfg_sz;
|
||||||
|
u8 *cfg_buff = NULL;
|
||||||
|
u8 *tbuff;
|
||||||
|
char *cfg_name = NULL;
|
||||||
|
|
||||||
|
switch (lmp_subver) {
|
||||||
|
case RTL_ROM_LMP_8723B:
|
||||||
|
cfg_name = "rtl_bt/rtl8723b_config.bin";
|
||||||
|
break;
|
||||||
|
case RTL_ROM_LMP_8821A:
|
||||||
|
cfg_name = "rtl_bt/rtl8821a_config.bin";
|
||||||
|
break;
|
||||||
|
case RTL_ROM_LMP_8761A:
|
||||||
|
cfg_name = "rtl_bt/rtl8761a_config.bin";
|
||||||
|
break;
|
||||||
|
case RTL_ROM_LMP_8822B:
|
||||||
|
cfg_name = "rtl_bt/rtl8822b_config.bin";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
BT_ERR("%s: rtl: no config according to lmp_subver %04x",
|
||||||
|
hdev->name, lmp_subver);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg_name) {
|
||||||
|
cfg_sz = rtl_load_config(hdev, cfg_name, &cfg_buff);
|
||||||
|
if (cfg_sz < 0)
|
||||||
|
cfg_sz = 0;
|
||||||
|
} else
|
||||||
|
cfg_sz = 0;
|
||||||
|
|
||||||
BT_INFO("%s: rtl: loading %s", hdev->name, fw_name);
|
BT_INFO("%s: rtl: loading %s", hdev->name, fw_name);
|
||||||
ret = request_firmware(&fw, fw_name, &hdev->dev);
|
ret = request_firmware(&fw, fw_name, &hdev->dev);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
BT_ERR("%s: Failed to load %s", hdev->name, fw_name);
|
BT_ERR("%s: Failed to load %s", hdev->name, fw_name);
|
||||||
return ret;
|
goto err_req_fw;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = rtl8723b_parse_firmware(hdev, lmp_subver, fw, &fw_data);
|
ret = rtl8723b_parse_firmware(hdev, lmp_subver, fw, &fw_data);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
ret = rtl_download_firmware(hdev, fw_data, ret);
|
if (cfg_sz) {
|
||||||
kfree(fw_data);
|
tbuff = kzalloc(ret + cfg_sz, GFP_KERNEL);
|
||||||
if (ret < 0)
|
if (!tbuff) {
|
||||||
|
ret = -ENOMEM;
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(tbuff, fw_data, ret);
|
||||||
|
kfree(fw_data);
|
||||||
|
|
||||||
|
memcpy(tbuff + ret, cfg_buff, cfg_sz);
|
||||||
|
ret += cfg_sz;
|
||||||
|
|
||||||
|
fw_data = tbuff;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_INFO("cfg_sz %d, total size %d", cfg_sz, ret);
|
||||||
|
|
||||||
|
ret = rtl_download_firmware(hdev, fw_data, ret);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
release_firmware(fw);
|
release_firmware(fw);
|
||||||
|
kfree(fw_data);
|
||||||
|
err_req_fw:
|
||||||
|
if (cfg_sz)
|
||||||
|
kfree(cfg_buff);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +457,9 @@ int btrtl_setup_realtek(struct hci_dev *hdev)
|
||||||
case RTL_ROM_LMP_8761A:
|
case RTL_ROM_LMP_8761A:
|
||||||
return btrtl_setup_rtl8723b(hdev, lmp_subver,
|
return btrtl_setup_rtl8723b(hdev, lmp_subver,
|
||||||
"rtl_bt/rtl8761a_fw.bin");
|
"rtl_bt/rtl8761a_fw.bin");
|
||||||
|
case RTL_ROM_LMP_8822B:
|
||||||
|
return btrtl_setup_rtl8723b(hdev, lmp_subver,
|
||||||
|
"rtl_bt/rtl8822b_fw.bin");
|
||||||
default:
|
default:
|
||||||
BT_INFO("rtl: assuming no firmware upload needed.");
|
BT_INFO("rtl: assuming no firmware upload needed.");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -62,6 +62,7 @@ static struct usb_driver btusb_driver;
|
||||||
#define BTUSB_REALTEK 0x20000
|
#define BTUSB_REALTEK 0x20000
|
||||||
#define BTUSB_BCM2045 0x40000
|
#define BTUSB_BCM2045 0x40000
|
||||||
#define BTUSB_IFNUM_2 0x80000
|
#define BTUSB_IFNUM_2 0x80000
|
||||||
|
#define BTUSB_CW6622 0x100000
|
||||||
|
|
||||||
static const struct usb_device_id btusb_table[] = {
|
static const struct usb_device_id btusb_table[] = {
|
||||||
/* Generic Bluetooth USB device */
|
/* Generic Bluetooth USB device */
|
||||||
|
@ -248,6 +249,7 @@ static const struct usb_device_id blacklist_table[] = {
|
||||||
|
|
||||||
/* QCA ROME chipset */
|
/* QCA ROME chipset */
|
||||||
{ USB_DEVICE(0x0cf3, 0xe007), .driver_info = BTUSB_QCA_ROME },
|
{ USB_DEVICE(0x0cf3, 0xe007), .driver_info = BTUSB_QCA_ROME },
|
||||||
|
{ USB_DEVICE(0x0cf3, 0xe009), .driver_info = BTUSB_QCA_ROME },
|
||||||
{ USB_DEVICE(0x0cf3, 0xe300), .driver_info = BTUSB_QCA_ROME },
|
{ USB_DEVICE(0x0cf3, 0xe300), .driver_info = BTUSB_QCA_ROME },
|
||||||
{ USB_DEVICE(0x0cf3, 0xe360), .driver_info = BTUSB_QCA_ROME },
|
{ USB_DEVICE(0x0cf3, 0xe360), .driver_info = BTUSB_QCA_ROME },
|
||||||
{ USB_DEVICE(0x0489, 0xe092), .driver_info = BTUSB_QCA_ROME },
|
{ USB_DEVICE(0x0489, 0xe092), .driver_info = BTUSB_QCA_ROME },
|
||||||
|
@ -290,7 +292,8 @@ static const struct usb_device_id blacklist_table[] = {
|
||||||
{ USB_DEVICE(0x0400, 0x080a), .driver_info = BTUSB_BROKEN_ISOC },
|
{ USB_DEVICE(0x0400, 0x080a), .driver_info = BTUSB_BROKEN_ISOC },
|
||||||
|
|
||||||
/* CONWISE Technology based adapters with buggy SCO support */
|
/* CONWISE Technology based adapters with buggy SCO support */
|
||||||
{ USB_DEVICE(0x0e5e, 0x6622), .driver_info = BTUSB_BROKEN_ISOC },
|
{ USB_DEVICE(0x0e5e, 0x6622),
|
||||||
|
.driver_info = BTUSB_BROKEN_ISOC | BTUSB_CW6622},
|
||||||
|
|
||||||
/* Roper Class 1 Bluetooth Dongle (Silicon Wave based) */
|
/* Roper Class 1 Bluetooth Dongle (Silicon Wave based) */
|
||||||
{ USB_DEVICE(0x1310, 0x0001), .driver_info = BTUSB_SWAVE },
|
{ USB_DEVICE(0x1310, 0x0001), .driver_info = BTUSB_SWAVE },
|
||||||
|
@ -2221,9 +2224,8 @@ static int btusb_setup_intel_new(struct hci_dev *hdev)
|
||||||
err = wait_on_bit_timeout(&data->flags, BTUSB_DOWNLOADING,
|
err = wait_on_bit_timeout(&data->flags, BTUSB_DOWNLOADING,
|
||||||
TASK_INTERRUPTIBLE,
|
TASK_INTERRUPTIBLE,
|
||||||
msecs_to_jiffies(5000));
|
msecs_to_jiffies(5000));
|
||||||
if (err == 1) {
|
if (err == -EINTR) {
|
||||||
BT_ERR("%s: Firmware loading interrupted", hdev->name);
|
BT_ERR("%s: Firmware loading interrupted", hdev->name);
|
||||||
err = -EINTR;
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2275,7 +2277,7 @@ done:
|
||||||
TASK_INTERRUPTIBLE,
|
TASK_INTERRUPTIBLE,
|
||||||
msecs_to_jiffies(1000));
|
msecs_to_jiffies(1000));
|
||||||
|
|
||||||
if (err == 1) {
|
if (err == -EINTR) {
|
||||||
BT_ERR("%s: Device boot interrupted", hdev->name);
|
BT_ERR("%s: Device boot interrupted", hdev->name);
|
||||||
return -EINTR;
|
return -EINTR;
|
||||||
}
|
}
|
||||||
|
@ -2845,6 +2847,9 @@ static int btusb_probe(struct usb_interface *intf,
|
||||||
hdev->send = btusb_send_frame;
|
hdev->send = btusb_send_frame;
|
||||||
hdev->notify = btusb_notify;
|
hdev->notify = btusb_notify;
|
||||||
|
|
||||||
|
if (id->driver_info & BTUSB_CW6622)
|
||||||
|
set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);
|
||||||
|
|
||||||
if (id->driver_info & BTUSB_BCM2045)
|
if (id->driver_info & BTUSB_BCM2045)
|
||||||
set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);
|
set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);
|
||||||
|
|
||||||
|
|
|
@ -798,7 +798,7 @@ static int bcm_remove(struct platform_device *pdev)
|
||||||
|
|
||||||
static const struct hci_uart_proto bcm_proto = {
|
static const struct hci_uart_proto bcm_proto = {
|
||||||
.id = HCI_UART_BCM,
|
.id = HCI_UART_BCM,
|
||||||
.name = "BCM",
|
.name = "Broadcom",
|
||||||
.manufacturer = 15,
|
.manufacturer = 15,
|
||||||
.init_speed = 115200,
|
.init_speed = 115200,
|
||||||
.oper_speed = 4000000,
|
.oper_speed = 4000000,
|
||||||
|
|
|
@ -128,7 +128,7 @@ static int intel_wait_booting(struct hci_uart *hu)
|
||||||
TASK_INTERRUPTIBLE,
|
TASK_INTERRUPTIBLE,
|
||||||
msecs_to_jiffies(1000));
|
msecs_to_jiffies(1000));
|
||||||
|
|
||||||
if (err == 1) {
|
if (err == -EINTR) {
|
||||||
bt_dev_err(hu->hdev, "Device boot interrupted");
|
bt_dev_err(hu->hdev, "Device boot interrupted");
|
||||||
return -EINTR;
|
return -EINTR;
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ static int intel_wait_lpm_transaction(struct hci_uart *hu)
|
||||||
TASK_INTERRUPTIBLE,
|
TASK_INTERRUPTIBLE,
|
||||||
msecs_to_jiffies(1000));
|
msecs_to_jiffies(1000));
|
||||||
|
|
||||||
if (err == 1) {
|
if (err == -EINTR) {
|
||||||
bt_dev_err(hu->hdev, "LPM transaction interrupted");
|
bt_dev_err(hu->hdev, "LPM transaction interrupted");
|
||||||
return -EINTR;
|
return -EINTR;
|
||||||
}
|
}
|
||||||
|
@ -813,7 +813,7 @@ static int intel_setup(struct hci_uart *hu)
|
||||||
err = wait_on_bit_timeout(&intel->flags, STATE_DOWNLOADING,
|
err = wait_on_bit_timeout(&intel->flags, STATE_DOWNLOADING,
|
||||||
TASK_INTERRUPTIBLE,
|
TASK_INTERRUPTIBLE,
|
||||||
msecs_to_jiffies(5000));
|
msecs_to_jiffies(5000));
|
||||||
if (err == 1) {
|
if (err == -EINTR) {
|
||||||
bt_dev_err(hdev, "Firmware loading interrupted");
|
bt_dev_err(hdev, "Firmware loading interrupted");
|
||||||
err = -EINTR;
|
err = -EINTR;
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
@ -810,6 +810,9 @@ static int __init hci_uart_init(void)
|
||||||
#ifdef CONFIG_BT_HCIUART_AG6XX
|
#ifdef CONFIG_BT_HCIUART_AG6XX
|
||||||
ag6xx_init();
|
ag6xx_init();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_BT_HCIUART_MRVL
|
||||||
|
mrvl_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -845,6 +848,9 @@ static void __exit hci_uart_exit(void)
|
||||||
#ifdef CONFIG_BT_HCIUART_AG6XX
|
#ifdef CONFIG_BT_HCIUART_AG6XX
|
||||||
ag6xx_deinit();
|
ag6xx_deinit();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_BT_HCIUART_MRVL
|
||||||
|
mrvl_deinit();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Release tty registration of line discipline */
|
/* Release tty registration of line discipline */
|
||||||
err = tty_unregister_ldisc(N_HCI);
|
err = tty_unregister_ldisc(N_HCI);
|
||||||
|
|
|
@ -0,0 +1,387 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Bluetooth HCI UART driver for marvell devices
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Marvell International Ltd.
|
||||||
|
* Copyright (C) 2016 Intel Corporation
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/skbuff.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/tty.h>
|
||||||
|
|
||||||
|
#include <net/bluetooth/bluetooth.h>
|
||||||
|
#include <net/bluetooth/hci_core.h>
|
||||||
|
|
||||||
|
#include "hci_uart.h"
|
||||||
|
|
||||||
|
#define HCI_FW_REQ_PKT 0xA5
|
||||||
|
#define HCI_CHIP_VER_PKT 0xAA
|
||||||
|
|
||||||
|
#define MRVL_ACK 0x5A
|
||||||
|
#define MRVL_NAK 0xBF
|
||||||
|
#define MRVL_RAW_DATA 0x1F
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STATE_CHIP_VER_PENDING,
|
||||||
|
STATE_FW_REQ_PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mrvl_data {
|
||||||
|
struct sk_buff *rx_skb;
|
||||||
|
struct sk_buff_head txq;
|
||||||
|
struct sk_buff_head rawq;
|
||||||
|
unsigned long flags;
|
||||||
|
unsigned int tx_len;
|
||||||
|
u8 id, rev;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hci_mrvl_pkt {
|
||||||
|
__le16 lhs;
|
||||||
|
__le16 rhs;
|
||||||
|
} __packed;
|
||||||
|
#define HCI_MRVL_PKT_SIZE 4
|
||||||
|
|
||||||
|
static int mrvl_open(struct hci_uart *hu)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl;
|
||||||
|
|
||||||
|
BT_DBG("hu %p", hu);
|
||||||
|
|
||||||
|
mrvl = kzalloc(sizeof(*mrvl), GFP_KERNEL);
|
||||||
|
if (!mrvl)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
skb_queue_head_init(&mrvl->txq);
|
||||||
|
skb_queue_head_init(&mrvl->rawq);
|
||||||
|
|
||||||
|
set_bit(STATE_CHIP_VER_PENDING, &mrvl->flags);
|
||||||
|
|
||||||
|
hu->priv = mrvl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_close(struct hci_uart *hu)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
|
||||||
|
BT_DBG("hu %p", hu);
|
||||||
|
|
||||||
|
skb_queue_purge(&mrvl->txq);
|
||||||
|
skb_queue_purge(&mrvl->rawq);
|
||||||
|
kfree_skb(mrvl->rx_skb);
|
||||||
|
kfree(mrvl);
|
||||||
|
|
||||||
|
hu->priv = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_flush(struct hci_uart *hu)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
|
||||||
|
BT_DBG("hu %p", hu);
|
||||||
|
|
||||||
|
skb_queue_purge(&mrvl->txq);
|
||||||
|
skb_queue_purge(&mrvl->rawq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *mrvl_dequeue(struct hci_uart *hu)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
skb = skb_dequeue(&mrvl->txq);
|
||||||
|
if (!skb) {
|
||||||
|
/* Any raw data ? */
|
||||||
|
skb = skb_dequeue(&mrvl->rawq);
|
||||||
|
} else {
|
||||||
|
/* Prepend skb with frame type */
|
||||||
|
memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_enqueue(struct hci_uart *hu, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
|
||||||
|
skb_queue_tail(&mrvl->txq, skb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mrvl_send_ack(struct hci_uart *hu, unsigned char type)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* No H4 payload, only 1 byte header */
|
||||||
|
skb = bt_skb_alloc(0, GFP_ATOMIC);
|
||||||
|
if (!skb) {
|
||||||
|
bt_dev_err(hu->hdev, "Unable to alloc ack/nak packet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hci_skb_pkt_type(skb) = type;
|
||||||
|
|
||||||
|
skb_queue_tail(&mrvl->txq, skb);
|
||||||
|
hci_uart_tx_wakeup(hu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_recv_fw_req(struct hci_dev *hdev, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct hci_mrvl_pkt *pkt = (void *)skb->data;
|
||||||
|
struct hci_uart *hu = hci_get_drvdata(hdev);
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if ((pkt->lhs ^ pkt->rhs) != 0xffff) {
|
||||||
|
bt_dev_err(hdev, "Corrupted mrvl header");
|
||||||
|
mrvl_send_ack(hu, MRVL_NAK);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
mrvl_send_ack(hu, MRVL_ACK);
|
||||||
|
|
||||||
|
if (!test_bit(STATE_FW_REQ_PENDING, &mrvl->flags)) {
|
||||||
|
bt_dev_err(hdev, "Received unexpected firmware request");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrvl->tx_len = le16_to_cpu(pkt->lhs);
|
||||||
|
|
||||||
|
clear_bit(STATE_FW_REQ_PENDING, &mrvl->flags);
|
||||||
|
smp_mb__after_atomic();
|
||||||
|
wake_up_bit(&mrvl->flags, STATE_FW_REQ_PENDING);
|
||||||
|
|
||||||
|
done:
|
||||||
|
kfree_skb(skb);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_recv_chip_ver(struct hci_dev *hdev, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct hci_mrvl_pkt *pkt = (void *)skb->data;
|
||||||
|
struct hci_uart *hu = hci_get_drvdata(hdev);
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
u16 version = le16_to_cpu(pkt->lhs);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if ((pkt->lhs ^ pkt->rhs) != 0xffff) {
|
||||||
|
bt_dev_err(hdev, "Corrupted mrvl header");
|
||||||
|
mrvl_send_ack(hu, MRVL_NAK);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
mrvl_send_ack(hu, MRVL_ACK);
|
||||||
|
|
||||||
|
if (!test_bit(STATE_CHIP_VER_PENDING, &mrvl->flags)) {
|
||||||
|
bt_dev_err(hdev, "Received unexpected chip version");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrvl->id = version;
|
||||||
|
mrvl->rev = version >> 8;
|
||||||
|
|
||||||
|
bt_dev_info(hdev, "Controller id = %x, rev = %x", mrvl->id, mrvl->rev);
|
||||||
|
|
||||||
|
clear_bit(STATE_CHIP_VER_PENDING, &mrvl->flags);
|
||||||
|
smp_mb__after_atomic();
|
||||||
|
wake_up_bit(&mrvl->flags, STATE_CHIP_VER_PENDING);
|
||||||
|
|
||||||
|
done:
|
||||||
|
kfree_skb(skb);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HCI_RECV_CHIP_VER \
|
||||||
|
.type = HCI_CHIP_VER_PKT, \
|
||||||
|
.hlen = HCI_MRVL_PKT_SIZE, \
|
||||||
|
.loff = 0, \
|
||||||
|
.lsize = 0, \
|
||||||
|
.maxlen = HCI_MRVL_PKT_SIZE
|
||||||
|
|
||||||
|
#define HCI_RECV_FW_REQ \
|
||||||
|
.type = HCI_FW_REQ_PKT, \
|
||||||
|
.hlen = HCI_MRVL_PKT_SIZE, \
|
||||||
|
.loff = 0, \
|
||||||
|
.lsize = 0, \
|
||||||
|
.maxlen = HCI_MRVL_PKT_SIZE
|
||||||
|
|
||||||
|
static const struct h4_recv_pkt mrvl_recv_pkts[] = {
|
||||||
|
{ H4_RECV_ACL, .recv = hci_recv_frame },
|
||||||
|
{ H4_RECV_SCO, .recv = hci_recv_frame },
|
||||||
|
{ H4_RECV_EVENT, .recv = hci_recv_frame },
|
||||||
|
{ HCI_RECV_FW_REQ, .recv = mrvl_recv_fw_req },
|
||||||
|
{ HCI_RECV_CHIP_VER, .recv = mrvl_recv_chip_ver },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mrvl_recv(struct hci_uart *hu, const void *data, int count)
|
||||||
|
{
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
|
||||||
|
if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
|
||||||
|
return -EUNATCH;
|
||||||
|
|
||||||
|
mrvl->rx_skb = h4_recv_buf(hu->hdev, mrvl->rx_skb, data, count,
|
||||||
|
mrvl_recv_pkts,
|
||||||
|
ARRAY_SIZE(mrvl_recv_pkts));
|
||||||
|
if (IS_ERR(mrvl->rx_skb)) {
|
||||||
|
int err = PTR_ERR(mrvl->rx_skb);
|
||||||
|
bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err);
|
||||||
|
mrvl->rx_skb = NULL;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_load_firmware(struct hci_dev *hdev, const char *name)
|
||||||
|
{
|
||||||
|
struct hci_uart *hu = hci_get_drvdata(hdev);
|
||||||
|
struct mrvl_data *mrvl = hu->priv;
|
||||||
|
const struct firmware *fw = NULL;
|
||||||
|
const u8 *fw_ptr, *fw_max;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = request_firmware(&fw, name, &hdev->dev);
|
||||||
|
if (err < 0) {
|
||||||
|
bt_dev_err(hdev, "Failed to load firmware file %s", name);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_ptr = fw->data;
|
||||||
|
fw_max = fw->data + fw->size;
|
||||||
|
|
||||||
|
bt_dev_info(hdev, "Loading %s", name);
|
||||||
|
|
||||||
|
set_bit(STATE_FW_REQ_PENDING, &mrvl->flags);
|
||||||
|
|
||||||
|
while (fw_ptr <= fw_max) {
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* Controller drives the firmware load by sending firmware
|
||||||
|
* request packets containing the expected fragment size.
|
||||||
|
*/
|
||||||
|
err = wait_on_bit_timeout(&mrvl->flags, STATE_FW_REQ_PENDING,
|
||||||
|
TASK_INTERRUPTIBLE,
|
||||||
|
msecs_to_jiffies(2000));
|
||||||
|
if (err == 1) {
|
||||||
|
bt_dev_err(hdev, "Firmware load interrupted");
|
||||||
|
err = -EINTR;
|
||||||
|
break;
|
||||||
|
} else if (err) {
|
||||||
|
bt_dev_err(hdev, "Firmware request timeout");
|
||||||
|
err = -ETIMEDOUT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_dev_dbg(hdev, "Firmware request, expecting %d bytes",
|
||||||
|
mrvl->tx_len);
|
||||||
|
|
||||||
|
if (fw_ptr == fw_max) {
|
||||||
|
/* Controller requests a null size once firmware is
|
||||||
|
* fully loaded. If controller expects more data, there
|
||||||
|
* is an issue.
|
||||||
|
*/
|
||||||
|
if (!mrvl->tx_len) {
|
||||||
|
bt_dev_info(hdev, "Firmware loading complete");
|
||||||
|
} else {
|
||||||
|
bt_dev_err(hdev, "Firmware loading failure");
|
||||||
|
err = -EINVAL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fw_ptr + mrvl->tx_len > fw_max) {
|
||||||
|
mrvl->tx_len = fw_max - fw_ptr;
|
||||||
|
bt_dev_dbg(hdev, "Adjusting tx_len to %d",
|
||||||
|
mrvl->tx_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(mrvl->tx_len, GFP_KERNEL);
|
||||||
|
if (!skb) {
|
||||||
|
bt_dev_err(hdev, "Failed to alloc mem for FW packet");
|
||||||
|
err = -ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bt_cb(skb)->pkt_type = MRVL_RAW_DATA;
|
||||||
|
|
||||||
|
memcpy(skb_put(skb, mrvl->tx_len), fw_ptr, mrvl->tx_len);
|
||||||
|
fw_ptr += mrvl->tx_len;
|
||||||
|
|
||||||
|
set_bit(STATE_FW_REQ_PENDING, &mrvl->flags);
|
||||||
|
|
||||||
|
skb_queue_tail(&mrvl->rawq, skb);
|
||||||
|
hci_uart_tx_wakeup(hu);
|
||||||
|
}
|
||||||
|
|
||||||
|
release_firmware(fw);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrvl_setup(struct hci_uart *hu)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hci_uart_set_flow_control(hu, true);
|
||||||
|
|
||||||
|
err = mrvl_load_firmware(hu->hdev, "mrvl/helper_uart_3000000.bin");
|
||||||
|
if (err) {
|
||||||
|
bt_dev_err(hu->hdev, "Unable to download firmware helper");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hci_uart_set_baudrate(hu, 3000000);
|
||||||
|
hci_uart_set_flow_control(hu, false);
|
||||||
|
|
||||||
|
err = mrvl_load_firmware(hu->hdev, "mrvl/uart8897_bt.bin");
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hci_uart_proto mrvl_proto = {
|
||||||
|
.id = HCI_UART_MRVL,
|
||||||
|
.name = "Marvell",
|
||||||
|
.init_speed = 115200,
|
||||||
|
.open = mrvl_open,
|
||||||
|
.close = mrvl_close,
|
||||||
|
.flush = mrvl_flush,
|
||||||
|
.setup = mrvl_setup,
|
||||||
|
.recv = mrvl_recv,
|
||||||
|
.enqueue = mrvl_enqueue,
|
||||||
|
.dequeue = mrvl_dequeue,
|
||||||
|
};
|
||||||
|
|
||||||
|
int __init mrvl_init(void)
|
||||||
|
{
|
||||||
|
return hci_uart_register_proto(&mrvl_proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
int __exit mrvl_deinit(void)
|
||||||
|
{
|
||||||
|
return hci_uart_unregister_proto(&mrvl_proto);
|
||||||
|
}
|
|
@ -397,7 +397,7 @@ static int qca_open(struct hci_uart *hu)
|
||||||
skb_queue_head_init(&qca->txq);
|
skb_queue_head_init(&qca->txq);
|
||||||
skb_queue_head_init(&qca->tx_wait_q);
|
skb_queue_head_init(&qca->tx_wait_q);
|
||||||
spin_lock_init(&qca->hci_ibs_lock);
|
spin_lock_init(&qca->hci_ibs_lock);
|
||||||
qca->workqueue = create_singlethread_workqueue("qca_wq");
|
qca->workqueue = alloc_ordered_workqueue("qca_wq", 0);
|
||||||
if (!qca->workqueue) {
|
if (!qca->workqueue) {
|
||||||
BT_ERR("QCA Workqueue not initialized properly");
|
BT_ERR("QCA Workqueue not initialized properly");
|
||||||
kfree(qca);
|
kfree(qca);
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
#define HCIUARTGETFLAGS _IOR('U', 204, int)
|
#define HCIUARTGETFLAGS _IOR('U', 204, int)
|
||||||
|
|
||||||
/* UART protocols */
|
/* UART protocols */
|
||||||
#define HCI_UART_MAX_PROTO 10
|
#define HCI_UART_MAX_PROTO 12
|
||||||
|
|
||||||
#define HCI_UART_H4 0
|
#define HCI_UART_H4 0
|
||||||
#define HCI_UART_BCSP 1
|
#define HCI_UART_BCSP 1
|
||||||
|
@ -47,6 +47,8 @@
|
||||||
#define HCI_UART_BCM 7
|
#define HCI_UART_BCM 7
|
||||||
#define HCI_UART_QCA 8
|
#define HCI_UART_QCA 8
|
||||||
#define HCI_UART_AG6XX 9
|
#define HCI_UART_AG6XX 9
|
||||||
|
#define HCI_UART_NOKIA 10
|
||||||
|
#define HCI_UART_MRVL 11
|
||||||
|
|
||||||
#define HCI_UART_RAW_DEVICE 0
|
#define HCI_UART_RAW_DEVICE 0
|
||||||
#define HCI_UART_RESET_ON_INIT 1
|
#define HCI_UART_RESET_ON_INIT 1
|
||||||
|
@ -189,3 +191,8 @@ int qca_deinit(void);
|
||||||
int ag6xx_init(void);
|
int ag6xx_init(void);
|
||||||
int ag6xx_deinit(void);
|
int ag6xx_deinit(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_HCIUART_MRVL
|
||||||
|
int mrvl_init(void);
|
||||||
|
int mrvl_deinit(void);
|
||||||
|
#endif
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
static int numlbs = 2;
|
static int numlbs = 2;
|
||||||
|
|
||||||
static LIST_HEAD(fakelb_phys);
|
static LIST_HEAD(fakelb_phys);
|
||||||
static DEFINE_SPINLOCK(fakelb_phys_lock);
|
static DEFINE_MUTEX(fakelb_phys_lock);
|
||||||
|
|
||||||
static LIST_HEAD(fakelb_ifup_phys);
|
static LIST_HEAD(fakelb_ifup_phys);
|
||||||
static DEFINE_RWLOCK(fakelb_ifup_phys_lock);
|
static DEFINE_RWLOCK(fakelb_ifup_phys_lock);
|
||||||
|
@ -188,9 +188,9 @@ static int fakelb_add_one(struct device *dev)
|
||||||
if (err)
|
if (err)
|
||||||
goto err_reg;
|
goto err_reg;
|
||||||
|
|
||||||
spin_lock(&fakelb_phys_lock);
|
mutex_lock(&fakelb_phys_lock);
|
||||||
list_add_tail(&phy->list, &fakelb_phys);
|
list_add_tail(&phy->list, &fakelb_phys);
|
||||||
spin_unlock(&fakelb_phys_lock);
|
mutex_unlock(&fakelb_phys_lock);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -222,10 +222,10 @@ static int fakelb_probe(struct platform_device *pdev)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_slave:
|
err_slave:
|
||||||
spin_lock(&fakelb_phys_lock);
|
mutex_lock(&fakelb_phys_lock);
|
||||||
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
|
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
|
||||||
fakelb_del(phy);
|
fakelb_del(phy);
|
||||||
spin_unlock(&fakelb_phys_lock);
|
mutex_unlock(&fakelb_phys_lock);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,10 +233,10 @@ static int fakelb_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct fakelb_phy *phy, *tmp;
|
struct fakelb_phy *phy, *tmp;
|
||||||
|
|
||||||
spin_lock(&fakelb_phys_lock);
|
mutex_lock(&fakelb_phys_lock);
|
||||||
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
|
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
|
||||||
fakelb_del(phy);
|
fakelb_del(phy);
|
||||||
spin_unlock(&fakelb_phys_lock);
|
mutex_unlock(&fakelb_phys_lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
#include <net/sock.h>
|
#include <net/sock.h>
|
||||||
#include <linux/seq_file.h>
|
#include <linux/seq_file.h>
|
||||||
|
|
||||||
#define BT_SUBSYS_VERSION "2.21"
|
#define BT_SUBSYS_VERSION 2
|
||||||
|
#define BT_SUBSYS_REVISION 22
|
||||||
|
|
||||||
#ifndef AF_BLUETOOTH
|
#ifndef AF_BLUETOOTH
|
||||||
#define AF_BLUETOOTH 31
|
#define AF_BLUETOOTH 31
|
||||||
|
@ -371,6 +372,7 @@ void hci_sock_set_flag(struct sock *sk, int nr);
|
||||||
void hci_sock_clear_flag(struct sock *sk, int nr);
|
void hci_sock_clear_flag(struct sock *sk, int nr);
|
||||||
int hci_sock_test_flag(struct sock *sk, int nr);
|
int hci_sock_test_flag(struct sock *sk, int nr);
|
||||||
unsigned short hci_sock_get_channel(struct sock *sk);
|
unsigned short hci_sock_get_channel(struct sock *sk);
|
||||||
|
u32 hci_sock_get_cookie(struct sock *sk);
|
||||||
|
|
||||||
int hci_sock_init(void);
|
int hci_sock_init(void);
|
||||||
void hci_sock_cleanup(void);
|
void hci_sock_cleanup(void);
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
#define HCI_SDIO 6
|
#define HCI_SDIO 6
|
||||||
#define HCI_SPI 7
|
#define HCI_SPI 7
|
||||||
#define HCI_I2C 8
|
#define HCI_I2C 8
|
||||||
|
#define HCI_SMD 9
|
||||||
|
|
||||||
/* HCI controller types */
|
/* HCI controller types */
|
||||||
#define HCI_PRIMARY 0x00
|
#define HCI_PRIMARY 0x00
|
||||||
|
@ -207,7 +208,11 @@ enum {
|
||||||
HCI_MGMT_INDEX_EVENTS,
|
HCI_MGMT_INDEX_EVENTS,
|
||||||
HCI_MGMT_UNCONF_INDEX_EVENTS,
|
HCI_MGMT_UNCONF_INDEX_EVENTS,
|
||||||
HCI_MGMT_EXT_INDEX_EVENTS,
|
HCI_MGMT_EXT_INDEX_EVENTS,
|
||||||
HCI_MGMT_GENERIC_EVENTS,
|
HCI_MGMT_EXT_INFO_EVENTS,
|
||||||
|
HCI_MGMT_OPTION_EVENTS,
|
||||||
|
HCI_MGMT_SETTING_EVENTS,
|
||||||
|
HCI_MGMT_DEV_CLASS_EVENTS,
|
||||||
|
HCI_MGMT_LOCAL_NAME_EVENTS,
|
||||||
HCI_MGMT_OOB_DATA_EVENTS,
|
HCI_MGMT_OOB_DATA_EVENTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,7 @@ struct hci_dev {
|
||||||
__u8 dev_name[HCI_MAX_NAME_LENGTH];
|
__u8 dev_name[HCI_MAX_NAME_LENGTH];
|
||||||
__u8 short_name[HCI_MAX_SHORT_NAME_LENGTH];
|
__u8 short_name[HCI_MAX_SHORT_NAME_LENGTH];
|
||||||
__u8 eir[HCI_MAX_EIR_LENGTH];
|
__u8 eir[HCI_MAX_EIR_LENGTH];
|
||||||
|
__u16 appearance;
|
||||||
__u8 dev_class[3];
|
__u8 dev_class[3];
|
||||||
__u8 major_class;
|
__u8 major_class;
|
||||||
__u8 minor_class;
|
__u8 minor_class;
|
||||||
|
@ -399,7 +400,9 @@ struct hci_dev {
|
||||||
struct delayed_work rpa_expired;
|
struct delayed_work rpa_expired;
|
||||||
bdaddr_t rpa;
|
bdaddr_t rpa;
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_BT_LEDS)
|
||||||
struct led_trigger *power_led;
|
struct led_trigger *power_led;
|
||||||
|
#endif
|
||||||
|
|
||||||
int (*open)(struct hci_dev *hdev);
|
int (*open)(struct hci_dev *hdev);
|
||||||
int (*close)(struct hci_dev *hdev);
|
int (*close)(struct hci_dev *hdev);
|
||||||
|
@ -1026,8 +1029,8 @@ int hci_resume_dev(struct hci_dev *hdev);
|
||||||
int hci_reset_dev(struct hci_dev *hdev);
|
int hci_reset_dev(struct hci_dev *hdev);
|
||||||
int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb);
|
int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb);
|
||||||
int hci_recv_diag(struct hci_dev *hdev, struct sk_buff *skb);
|
int hci_recv_diag(struct hci_dev *hdev, struct sk_buff *skb);
|
||||||
void hci_set_hw_info(struct hci_dev *hdev, const char *fmt, ...);
|
__printf(2, 3) void hci_set_hw_info(struct hci_dev *hdev, const char *fmt, ...);
|
||||||
void hci_set_fw_info(struct hci_dev *hdev, const char *fmt, ...);
|
__printf(2, 3) void hci_set_fw_info(struct hci_dev *hdev, const char *fmt, ...);
|
||||||
int hci_dev_open(__u16 dev);
|
int hci_dev_open(__u16 dev);
|
||||||
int hci_dev_close(__u16 dev);
|
int hci_dev_close(__u16 dev);
|
||||||
int hci_dev_do_close(struct hci_dev *hdev);
|
int hci_dev_do_close(struct hci_dev *hdev);
|
||||||
|
@ -1404,6 +1407,9 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb);
|
||||||
void hci_send_to_channel(unsigned short channel, struct sk_buff *skb,
|
void hci_send_to_channel(unsigned short channel, struct sk_buff *skb,
|
||||||
int flag, struct sock *skip_sk);
|
int flag, struct sock *skip_sk);
|
||||||
void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb);
|
void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb);
|
||||||
|
void hci_send_monitor_ctrl_event(struct hci_dev *hdev, u16 event,
|
||||||
|
void *data, u16 data_len, ktime_t tstamp,
|
||||||
|
int flag, struct sock *skip_sk);
|
||||||
|
|
||||||
void hci_sock_dev_event(struct hci_dev *hdev, int event);
|
void hci_sock_dev_event(struct hci_dev *hdev, int event);
|
||||||
|
|
||||||
|
@ -1449,6 +1455,7 @@ void hci_mgmt_chan_unregister(struct hci_mgmt_chan *c);
|
||||||
#define DISCOV_BREDR_INQUIRY_LEN 0x08
|
#define DISCOV_BREDR_INQUIRY_LEN 0x08
|
||||||
#define DISCOV_LE_RESTART_DELAY msecs_to_jiffies(200) /* msec */
|
#define DISCOV_LE_RESTART_DELAY msecs_to_jiffies(200) /* msec */
|
||||||
|
|
||||||
|
void mgmt_fill_version_info(void *ver);
|
||||||
int mgmt_new_settings(struct hci_dev *hdev);
|
int mgmt_new_settings(struct hci_dev *hdev);
|
||||||
void mgmt_index_added(struct hci_dev *hdev);
|
void mgmt_index_added(struct hci_dev *hdev);
|
||||||
void mgmt_index_removed(struct hci_dev *hdev);
|
void mgmt_index_removed(struct hci_dev *hdev);
|
||||||
|
|
|
@ -45,6 +45,10 @@ struct hci_mon_hdr {
|
||||||
#define HCI_MON_VENDOR_DIAG 11
|
#define HCI_MON_VENDOR_DIAG 11
|
||||||
#define HCI_MON_SYSTEM_NOTE 12
|
#define HCI_MON_SYSTEM_NOTE 12
|
||||||
#define HCI_MON_USER_LOGGING 13
|
#define HCI_MON_USER_LOGGING 13
|
||||||
|
#define HCI_MON_CTRL_OPEN 14
|
||||||
|
#define HCI_MON_CTRL_CLOSE 15
|
||||||
|
#define HCI_MON_CTRL_COMMAND 16
|
||||||
|
#define HCI_MON_CTRL_EVENT 17
|
||||||
|
|
||||||
struct hci_mon_new_index {
|
struct hci_mon_new_index {
|
||||||
__u8 type;
|
__u8 type;
|
||||||
|
|
|
@ -586,6 +586,24 @@ struct mgmt_rp_get_adv_size_info {
|
||||||
|
|
||||||
#define MGMT_OP_START_LIMITED_DISCOVERY 0x0041
|
#define MGMT_OP_START_LIMITED_DISCOVERY 0x0041
|
||||||
|
|
||||||
|
#define MGMT_OP_READ_EXT_INFO 0x0042
|
||||||
|
#define MGMT_READ_EXT_INFO_SIZE 0
|
||||||
|
struct mgmt_rp_read_ext_info {
|
||||||
|
bdaddr_t bdaddr;
|
||||||
|
__u8 version;
|
||||||
|
__le16 manufacturer;
|
||||||
|
__le32 supported_settings;
|
||||||
|
__le32 current_settings;
|
||||||
|
__le16 eir_len;
|
||||||
|
__u8 eir[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_OP_SET_APPEARANCE 0x0043
|
||||||
|
struct mgmt_cp_set_appearance {
|
||||||
|
__u16 appearance;
|
||||||
|
} __packed;
|
||||||
|
#define MGMT_SET_APPEARANCE_SIZE 2
|
||||||
|
|
||||||
#define MGMT_EV_CMD_COMPLETE 0x0001
|
#define MGMT_EV_CMD_COMPLETE 0x0001
|
||||||
struct mgmt_ev_cmd_complete {
|
struct mgmt_ev_cmd_complete {
|
||||||
__le16 opcode;
|
__le16 opcode;
|
||||||
|
@ -800,3 +818,9 @@ struct mgmt_ev_advertising_added {
|
||||||
struct mgmt_ev_advertising_removed {
|
struct mgmt_ev_advertising_removed {
|
||||||
__u8 instance;
|
__u8 instance;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_EV_EXT_INFO_CHANGED 0x0025
|
||||||
|
struct mgmt_ev_ext_info_changed {
|
||||||
|
__le16 eir_len;
|
||||||
|
__u8 eir[0];
|
||||||
|
} __packed;
|
||||||
|
|
|
@ -101,8 +101,6 @@ static void lowpan_ndisc_802154_update(struct neighbour *n, u32 flags,
|
||||||
ieee802154_be16_to_le16(&neigh->short_addr, lladdr_short);
|
ieee802154_be16_to_le16(&neigh->short_addr, lladdr_short);
|
||||||
if (!lowpan_802154_is_valid_src_short_addr(neigh->short_addr))
|
if (!lowpan_802154_is_valid_src_short_addr(neigh->short_addr))
|
||||||
neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
|
neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
|
||||||
} else {
|
|
||||||
neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
|
|
||||||
}
|
}
|
||||||
write_unlock_bh(&n->lock);
|
write_unlock_bh(&n->lock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,13 @@
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/stringify.h>
|
||||||
#include <asm/ioctls.h>
|
#include <asm/ioctls.h>
|
||||||
|
|
||||||
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/bluetooth.h>
|
||||||
#include <linux/proc_fs.h>
|
#include <linux/proc_fs.h>
|
||||||
|
|
||||||
|
#include "leds.h"
|
||||||
#include "selftest.h"
|
#include "selftest.h"
|
||||||
|
|
||||||
/* Bluetooth sockets */
|
/* Bluetooth sockets */
|
||||||
|
@ -712,13 +714,16 @@ static struct net_proto_family bt_sock_family_ops = {
|
||||||
struct dentry *bt_debugfs;
|
struct dentry *bt_debugfs;
|
||||||
EXPORT_SYMBOL_GPL(bt_debugfs);
|
EXPORT_SYMBOL_GPL(bt_debugfs);
|
||||||
|
|
||||||
|
#define VERSION __stringify(BT_SUBSYS_VERSION) "." \
|
||||||
|
__stringify(BT_SUBSYS_REVISION)
|
||||||
|
|
||||||
static int __init bt_init(void)
|
static int __init bt_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
sock_skb_cb_check_size(sizeof(struct bt_skb_cb));
|
sock_skb_cb_check_size(sizeof(struct bt_skb_cb));
|
||||||
|
|
||||||
BT_INFO("Core ver %s", BT_SUBSYS_VERSION);
|
BT_INFO("Core ver %s", VERSION);
|
||||||
|
|
||||||
err = bt_selftest();
|
err = bt_selftest();
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -726,6 +731,8 @@ static int __init bt_init(void)
|
||||||
|
|
||||||
bt_debugfs = debugfs_create_dir("bluetooth", NULL);
|
bt_debugfs = debugfs_create_dir("bluetooth", NULL);
|
||||||
|
|
||||||
|
bt_leds_init();
|
||||||
|
|
||||||
err = bt_sysfs_init();
|
err = bt_sysfs_init();
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
@ -785,6 +792,8 @@ static void __exit bt_exit(void)
|
||||||
|
|
||||||
bt_sysfs_cleanup();
|
bt_sysfs_cleanup();
|
||||||
|
|
||||||
|
bt_leds_cleanup();
|
||||||
|
|
||||||
debugfs_remove_recursive(bt_debugfs);
|
debugfs_remove_recursive(bt_debugfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,7 +801,7 @@ subsys_initcall(bt_init);
|
||||||
module_exit(bt_exit);
|
module_exit(bt_exit);
|
||||||
|
|
||||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||||
MODULE_DESCRIPTION("Bluetooth Core ver " BT_SUBSYS_VERSION);
|
MODULE_DESCRIPTION("Bluetooth Core ver " VERSION);
|
||||||
MODULE_VERSION(BT_SUBSYS_VERSION);
|
MODULE_VERSION(VERSION);
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_ALIAS_NETPROTO(PF_BLUETOOTH);
|
MODULE_ALIAS_NETPROTO(PF_BLUETOOTH);
|
||||||
|
|
|
@ -1562,6 +1562,7 @@ int hci_dev_do_close(struct hci_dev *hdev)
|
||||||
auto_off = hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF);
|
auto_off = hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF);
|
||||||
|
|
||||||
if (!auto_off && hdev->dev_type == HCI_PRIMARY &&
|
if (!auto_off && hdev->dev_type == HCI_PRIMARY &&
|
||||||
|
!hci_dev_test_flag(hdev, HCI_USER_CHANNEL) &&
|
||||||
hci_dev_test_flag(hdev, HCI_MGMT))
|
hci_dev_test_flag(hdev, HCI_MGMT))
|
||||||
__mgmt_power_off(hdev);
|
__mgmt_power_off(hdev);
|
||||||
|
|
||||||
|
|
|
@ -971,14 +971,14 @@ void __hci_req_enable_advertising(struct hci_request *req)
|
||||||
hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable);
|
hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
static u8 append_local_name(struct hci_dev *hdev, u8 *ptr, u8 ad_len)
|
||||||
{
|
{
|
||||||
u8 ad_len = 0;
|
|
||||||
size_t name_len;
|
size_t name_len;
|
||||||
|
int max_len;
|
||||||
|
|
||||||
|
max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
|
||||||
name_len = strlen(hdev->dev_name);
|
name_len = strlen(hdev->dev_name);
|
||||||
if (name_len > 0) {
|
if (name_len > 0 && max_len > 0) {
|
||||||
size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
|
|
||||||
|
|
||||||
if (name_len > max_len) {
|
if (name_len > max_len) {
|
||||||
name_len = max_len;
|
name_len = max_len;
|
||||||
|
@ -997,22 +997,42 @@ static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||||
return ad_len;
|
return ad_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||||
|
{
|
||||||
|
return append_local_name(hdev, ptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance,
|
static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance,
|
||||||
u8 *ptr)
|
u8 *ptr)
|
||||||
{
|
{
|
||||||
struct adv_info *adv_instance;
|
struct adv_info *adv_instance;
|
||||||
|
u32 instance_flags;
|
||||||
|
u8 scan_rsp_len = 0;
|
||||||
|
|
||||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||||
if (!adv_instance)
|
if (!adv_instance)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* TODO: Set the appropriate entries based on advertising instance flags
|
instance_flags = adv_instance->flags;
|
||||||
* here once flags other than 0 are supported.
|
|
||||||
*/
|
if ((instance_flags & MGMT_ADV_FLAG_APPEARANCE) && hdev->appearance) {
|
||||||
|
ptr[0] = 3;
|
||||||
|
ptr[1] = EIR_APPEARANCE;
|
||||||
|
put_unaligned_le16(hdev->appearance, ptr + 2);
|
||||||
|
scan_rsp_len += 4;
|
||||||
|
ptr += 4;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(ptr, adv_instance->scan_rsp_data,
|
memcpy(ptr, adv_instance->scan_rsp_data,
|
||||||
adv_instance->scan_rsp_len);
|
adv_instance->scan_rsp_len);
|
||||||
|
|
||||||
return adv_instance->scan_rsp_len;
|
scan_rsp_len += adv_instance->scan_rsp_len;
|
||||||
|
ptr += 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)
|
void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance)
|
||||||
|
@ -1194,7 +1214,7 @@ static void adv_timeout_expire(struct work_struct *work)
|
||||||
|
|
||||||
hci_req_init(&req, hdev);
|
hci_req_init(&req, hdev);
|
||||||
|
|
||||||
hci_req_clear_adv_instance(hdev, &req, instance, false);
|
hci_req_clear_adv_instance(hdev, NULL, &req, instance, false);
|
||||||
|
|
||||||
if (list_empty(&hdev->adv_instances))
|
if (list_empty(&hdev->adv_instances))
|
||||||
__hci_req_disable_advertising(&req);
|
__hci_req_disable_advertising(&req);
|
||||||
|
@ -1284,8 +1304,9 @@ static void cancel_adv_timeout(struct hci_dev *hdev)
|
||||||
* setting.
|
* setting.
|
||||||
* - force == false: Only instances that have a timeout will be removed.
|
* - force == false: Only instances that have a timeout will be removed.
|
||||||
*/
|
*/
|
||||||
void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req,
|
void hci_req_clear_adv_instance(struct hci_dev *hdev, struct sock *sk,
|
||||||
u8 instance, bool force)
|
struct hci_request *req, u8 instance,
|
||||||
|
bool force)
|
||||||
{
|
{
|
||||||
struct adv_info *adv_instance, *n, *next_instance = NULL;
|
struct adv_info *adv_instance, *n, *next_instance = NULL;
|
||||||
int err;
|
int err;
|
||||||
|
@ -1311,7 +1332,7 @@ void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req,
|
||||||
rem_inst = adv_instance->instance;
|
rem_inst = adv_instance->instance;
|
||||||
err = hci_remove_adv_instance(hdev, rem_inst);
|
err = hci_remove_adv_instance(hdev, rem_inst);
|
||||||
if (!err)
|
if (!err)
|
||||||
mgmt_advertising_removed(NULL, hdev, rem_inst);
|
mgmt_advertising_removed(sk, hdev, rem_inst);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
adv_instance = hci_find_adv_instance(hdev, instance);
|
adv_instance = hci_find_adv_instance(hdev, instance);
|
||||||
|
@ -1325,7 +1346,7 @@ void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req,
|
||||||
|
|
||||||
err = hci_remove_adv_instance(hdev, instance);
|
err = hci_remove_adv_instance(hdev, instance);
|
||||||
if (!err)
|
if (!err)
|
||||||
mgmt_advertising_removed(NULL, hdev, instance);
|
mgmt_advertising_removed(sk, hdev, instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1716,7 +1737,7 @@ void __hci_abort_conn(struct hci_request *req, struct hci_conn *conn,
|
||||||
* function. To be safe hard-code one of the
|
* function. To be safe hard-code one of the
|
||||||
* values that's suitable for SCO.
|
* values that's suitable for SCO.
|
||||||
*/
|
*/
|
||||||
rej.reason = HCI_ERROR_REMOTE_LOW_RESOURCES;
|
rej.reason = HCI_ERROR_REJ_LIMITED_RESOURCES;
|
||||||
|
|
||||||
hci_req_add(req, HCI_OP_REJECT_SYNC_CONN_REQ,
|
hci_req_add(req, HCI_OP_REJECT_SYNC_CONN_REQ,
|
||||||
sizeof(rej), &rej);
|
sizeof(rej), &rej);
|
||||||
|
|
|
@ -73,8 +73,9 @@ void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance);
|
||||||
|
|
||||||
int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance,
|
int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance,
|
||||||
bool force);
|
bool force);
|
||||||
void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req,
|
void hci_req_clear_adv_instance(struct hci_dev *hdev, struct sock *sk,
|
||||||
u8 instance, bool force);
|
struct hci_request *req, u8 instance,
|
||||||
|
bool force);
|
||||||
|
|
||||||
void __hci_req_update_class(struct hci_request *req);
|
void __hci_req_update_class(struct hci_request *req);
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
#include <linux/utsname.h>
|
#include <linux/utsname.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/bluetooth.h>
|
||||||
|
@ -38,6 +39,8 @@
|
||||||
static LIST_HEAD(mgmt_chan_list);
|
static LIST_HEAD(mgmt_chan_list);
|
||||||
static DEFINE_MUTEX(mgmt_chan_list_lock);
|
static DEFINE_MUTEX(mgmt_chan_list_lock);
|
||||||
|
|
||||||
|
static DEFINE_IDA(sock_cookie_ida);
|
||||||
|
|
||||||
static atomic_t monitor_promisc = ATOMIC_INIT(0);
|
static atomic_t monitor_promisc = ATOMIC_INIT(0);
|
||||||
|
|
||||||
/* ----- HCI socket interface ----- */
|
/* ----- HCI socket interface ----- */
|
||||||
|
@ -52,6 +55,8 @@ struct hci_pinfo {
|
||||||
__u32 cmsg_mask;
|
__u32 cmsg_mask;
|
||||||
unsigned short channel;
|
unsigned short channel;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
__u32 cookie;
|
||||||
|
char comm[TASK_COMM_LEN];
|
||||||
};
|
};
|
||||||
|
|
||||||
void hci_sock_set_flag(struct sock *sk, int nr)
|
void hci_sock_set_flag(struct sock *sk, int nr)
|
||||||
|
@ -74,6 +79,38 @@ unsigned short hci_sock_get_channel(struct sock *sk)
|
||||||
return hci_pi(sk)->channel;
|
return hci_pi(sk)->channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 hci_sock_get_cookie(struct sock *sk)
|
||||||
|
{
|
||||||
|
return hci_pi(sk)->cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hci_sock_gen_cookie(struct sock *sk)
|
||||||
|
{
|
||||||
|
int id = hci_pi(sk)->cookie;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
id = ida_simple_get(&sock_cookie_ida, 1, 0, GFP_KERNEL);
|
||||||
|
if (id < 0)
|
||||||
|
id = 0xffffffff;
|
||||||
|
|
||||||
|
hci_pi(sk)->cookie = id;
|
||||||
|
get_task_comm(hci_pi(sk)->comm, current);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hci_sock_free_cookie(struct sock *sk)
|
||||||
|
{
|
||||||
|
int id = hci_pi(sk)->cookie;
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
hci_pi(sk)->cookie = 0xffffffff;
|
||||||
|
ida_simple_remove(&sock_cookie_ida, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline int hci_test_bit(int nr, const void *addr)
|
static inline int hci_test_bit(int nr, const void *addr)
|
||||||
{
|
{
|
||||||
return *((const __u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31));
|
return *((const __u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31));
|
||||||
|
@ -305,6 +342,60 @@ void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb)
|
||||||
kfree_skb(skb_copy);
|
kfree_skb(skb_copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hci_send_monitor_ctrl_event(struct hci_dev *hdev, u16 event,
|
||||||
|
void *data, u16 data_len, ktime_t tstamp,
|
||||||
|
int flag, struct sock *skip_sk)
|
||||||
|
{
|
||||||
|
struct sock *sk;
|
||||||
|
__le16 index;
|
||||||
|
|
||||||
|
if (hdev)
|
||||||
|
index = cpu_to_le16(hdev->id);
|
||||||
|
else
|
||||||
|
index = cpu_to_le16(MGMT_INDEX_NONE);
|
||||||
|
|
||||||
|
read_lock(&hci_sk_list.lock);
|
||||||
|
|
||||||
|
sk_for_each(sk, &hci_sk_list.head) {
|
||||||
|
struct hci_mon_hdr *hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
if (hci_pi(sk)->channel != HCI_CHANNEL_CONTROL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Ignore socket without the flag set */
|
||||||
|
if (!hci_sock_test_flag(sk, flag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Skip the original socket */
|
||||||
|
if (sk == skip_sk)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(6 + data_len, GFP_ATOMIC);
|
||||||
|
if (!skb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
put_unaligned_le32(hci_pi(sk)->cookie, skb_put(skb, 4));
|
||||||
|
put_unaligned_le16(event, skb_put(skb, 2));
|
||||||
|
|
||||||
|
if (data)
|
||||||
|
memcpy(skb_put(skb, data_len), data, data_len);
|
||||||
|
|
||||||
|
skb->tstamp = tstamp;
|
||||||
|
|
||||||
|
hdr = (void *)skb_push(skb, HCI_MON_HDR_SIZE);
|
||||||
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_EVENT);
|
||||||
|
hdr->index = index;
|
||||||
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
||||||
|
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_unlock(&hci_sk_list.lock);
|
||||||
|
}
|
||||||
|
|
||||||
static struct sk_buff *create_monitor_event(struct hci_dev *hdev, int event)
|
static struct sk_buff *create_monitor_event(struct hci_dev *hdev, int event)
|
||||||
{
|
{
|
||||||
struct hci_mon_hdr *hdr;
|
struct hci_mon_hdr *hdr;
|
||||||
|
@ -384,6 +475,129 @@ static struct sk_buff *create_monitor_event(struct hci_dev *hdev, int event)
|
||||||
return skb;
|
return skb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *create_monitor_ctrl_open(struct sock *sk)
|
||||||
|
{
|
||||||
|
struct hci_mon_hdr *hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
u16 format;
|
||||||
|
u8 ver[3];
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
/* No message needed when cookie is not present */
|
||||||
|
if (!hci_pi(sk)->cookie)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (hci_pi(sk)->channel) {
|
||||||
|
case HCI_CHANNEL_RAW:
|
||||||
|
format = 0x0000;
|
||||||
|
ver[0] = BT_SUBSYS_VERSION;
|
||||||
|
put_unaligned_le16(BT_SUBSYS_REVISION, ver + 1);
|
||||||
|
break;
|
||||||
|
case HCI_CHANNEL_USER:
|
||||||
|
format = 0x0001;
|
||||||
|
ver[0] = BT_SUBSYS_VERSION;
|
||||||
|
put_unaligned_le16(BT_SUBSYS_REVISION, ver + 1);
|
||||||
|
break;
|
||||||
|
case HCI_CHANNEL_CONTROL:
|
||||||
|
format = 0x0002;
|
||||||
|
mgmt_fill_version_info(ver);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* No message for unsupported format */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(14 + TASK_COMM_LEN , GFP_ATOMIC);
|
||||||
|
if (!skb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
flags = hci_sock_test_flag(sk, HCI_SOCK_TRUSTED) ? 0x1 : 0x0;
|
||||||
|
|
||||||
|
put_unaligned_le32(hci_pi(sk)->cookie, skb_put(skb, 4));
|
||||||
|
put_unaligned_le16(format, skb_put(skb, 2));
|
||||||
|
memcpy(skb_put(skb, sizeof(ver)), ver, sizeof(ver));
|
||||||
|
put_unaligned_le32(flags, skb_put(skb, 4));
|
||||||
|
*skb_put(skb, 1) = TASK_COMM_LEN;
|
||||||
|
memcpy(skb_put(skb, TASK_COMM_LEN), hci_pi(sk)->comm, TASK_COMM_LEN);
|
||||||
|
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
|
hdr = (void *)skb_push(skb, HCI_MON_HDR_SIZE);
|
||||||
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_OPEN);
|
||||||
|
if (hci_pi(sk)->hdev)
|
||||||
|
hdr->index = cpu_to_le16(hci_pi(sk)->hdev->id);
|
||||||
|
else
|
||||||
|
hdr->index = cpu_to_le16(HCI_DEV_NONE);
|
||||||
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *create_monitor_ctrl_close(struct sock *sk)
|
||||||
|
{
|
||||||
|
struct hci_mon_hdr *hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* No message needed when cookie is not present */
|
||||||
|
if (!hci_pi(sk)->cookie)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (hci_pi(sk)->channel) {
|
||||||
|
case HCI_CHANNEL_RAW:
|
||||||
|
case HCI_CHANNEL_USER:
|
||||||
|
case HCI_CHANNEL_CONTROL:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* No message for unsupported format */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(4, GFP_ATOMIC);
|
||||||
|
if (!skb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
put_unaligned_le32(hci_pi(sk)->cookie, skb_put(skb, 4));
|
||||||
|
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
|
hdr = (void *)skb_push(skb, HCI_MON_HDR_SIZE);
|
||||||
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_CLOSE);
|
||||||
|
if (hci_pi(sk)->hdev)
|
||||||
|
hdr->index = cpu_to_le16(hci_pi(sk)->hdev->id);
|
||||||
|
else
|
||||||
|
hdr->index = cpu_to_le16(HCI_DEV_NONE);
|
||||||
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *create_monitor_ctrl_command(struct sock *sk, u16 index,
|
||||||
|
u16 opcode, u16 len,
|
||||||
|
const void *buf)
|
||||||
|
{
|
||||||
|
struct hci_mon_hdr *hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(6 + len, GFP_ATOMIC);
|
||||||
|
if (!skb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
put_unaligned_le32(hci_pi(sk)->cookie, skb_put(skb, 4));
|
||||||
|
put_unaligned_le16(opcode, skb_put(skb, 2));
|
||||||
|
|
||||||
|
if (buf)
|
||||||
|
memcpy(skb_put(skb, len), buf, len);
|
||||||
|
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
|
hdr = (void *)skb_push(skb, HCI_MON_HDR_SIZE);
|
||||||
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_COMMAND);
|
||||||
|
hdr->index = cpu_to_le16(index);
|
||||||
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
static void __printf(2, 3)
|
static void __printf(2, 3)
|
||||||
send_monitor_note(struct sock *sk, const char *fmt, ...)
|
send_monitor_note(struct sock *sk, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
|
@ -458,6 +672,26 @@ static void send_monitor_replay(struct sock *sk)
|
||||||
read_unlock(&hci_dev_list_lock);
|
read_unlock(&hci_dev_list_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void send_monitor_control_replay(struct sock *mon_sk)
|
||||||
|
{
|
||||||
|
struct sock *sk;
|
||||||
|
|
||||||
|
read_lock(&hci_sk_list.lock);
|
||||||
|
|
||||||
|
sk_for_each(sk, &hci_sk_list.head) {
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
skb = create_monitor_ctrl_open(sk);
|
||||||
|
if (!skb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sock_queue_rcv_skb(mon_sk, skb))
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_unlock(&hci_sk_list.lock);
|
||||||
|
}
|
||||||
|
|
||||||
/* Generate internal stack event */
|
/* Generate internal stack event */
|
||||||
static void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data)
|
static void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data)
|
||||||
{
|
{
|
||||||
|
@ -585,6 +819,7 @@ static int hci_sock_release(struct socket *sock)
|
||||||
{
|
{
|
||||||
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
||||||
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
BT_DBG("sock %p sk %p", sock, sk);
|
BT_DBG("sock %p sk %p", sock, sk);
|
||||||
|
|
||||||
|
@ -593,8 +828,24 @@ static int hci_sock_release(struct socket *sock)
|
||||||
|
|
||||||
hdev = hci_pi(sk)->hdev;
|
hdev = hci_pi(sk)->hdev;
|
||||||
|
|
||||||
if (hci_pi(sk)->channel == HCI_CHANNEL_MONITOR)
|
switch (hci_pi(sk)->channel) {
|
||||||
|
case HCI_CHANNEL_MONITOR:
|
||||||
atomic_dec(&monitor_promisc);
|
atomic_dec(&monitor_promisc);
|
||||||
|
break;
|
||||||
|
case HCI_CHANNEL_RAW:
|
||||||
|
case HCI_CHANNEL_USER:
|
||||||
|
case HCI_CHANNEL_CONTROL:
|
||||||
|
/* Send event to monitor */
|
||||||
|
skb = create_monitor_ctrl_close(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
hci_sock_free_cookie(sk);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
bt_sock_unlink(&hci_sk_list, sk);
|
bt_sock_unlink(&hci_sk_list, sk);
|
||||||
|
|
||||||
|
@ -721,6 +972,27 @@ static int hci_sock_ioctl(struct socket *sock, unsigned int cmd,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When calling an ioctl on an unbound raw socket, then ensure
|
||||||
|
* that the monitor gets informed. Ensure that the resulting event
|
||||||
|
* is only send once by checking if the cookie exists or not. The
|
||||||
|
* socket cookie will be only ever generated once for the lifetime
|
||||||
|
* of a given socket.
|
||||||
|
*/
|
||||||
|
if (hci_sock_gen_cookie(sk)) {
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
if (capable(CAP_NET_ADMIN))
|
||||||
|
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
|
||||||
|
|
||||||
|
/* Send event to monitor */
|
||||||
|
skb = create_monitor_ctrl_open(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
release_sock(sk);
|
release_sock(sk);
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
|
@ -784,6 +1056,7 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
struct sockaddr_hci haddr;
|
struct sockaddr_hci haddr;
|
||||||
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
||||||
struct hci_dev *hdev = NULL;
|
struct hci_dev *hdev = NULL;
|
||||||
|
struct sk_buff *skb;
|
||||||
int len, err = 0;
|
int len, err = 0;
|
||||||
|
|
||||||
BT_DBG("sock %p sk %p", sock, sk);
|
BT_DBG("sock %p sk %p", sock, sk);
|
||||||
|
@ -822,7 +1095,35 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
atomic_inc(&hdev->promisc);
|
atomic_inc(&hdev->promisc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hci_pi(sk)->channel = haddr.hci_channel;
|
||||||
|
|
||||||
|
if (!hci_sock_gen_cookie(sk)) {
|
||||||
|
/* In the case when a cookie has already been assigned,
|
||||||
|
* then there has been already an ioctl issued against
|
||||||
|
* an unbound socket and with that triggerd an open
|
||||||
|
* notification. Send a close notification first to
|
||||||
|
* allow the state transition to bounded.
|
||||||
|
*/
|
||||||
|
skb = create_monitor_ctrl_close(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capable(CAP_NET_ADMIN))
|
||||||
|
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
|
||||||
|
|
||||||
hci_pi(sk)->hdev = hdev;
|
hci_pi(sk)->hdev = hdev;
|
||||||
|
|
||||||
|
/* Send event to monitor */
|
||||||
|
skb = create_monitor_ctrl_open(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HCI_CHANNEL_USER:
|
case HCI_CHANNEL_USER:
|
||||||
|
@ -884,9 +1185,38 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic_inc(&hdev->promisc);
|
hci_pi(sk)->channel = haddr.hci_channel;
|
||||||
|
|
||||||
|
if (!hci_sock_gen_cookie(sk)) {
|
||||||
|
/* In the case when a cookie has already been assigned,
|
||||||
|
* this socket will transition from a raw socket into
|
||||||
|
* an user channel socket. For a clean transition, send
|
||||||
|
* the close notification first.
|
||||||
|
*/
|
||||||
|
skb = create_monitor_ctrl_close(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The user channel is restricted to CAP_NET_ADMIN
|
||||||
|
* capabilities and with that implicitly trusted.
|
||||||
|
*/
|
||||||
|
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
|
||||||
|
|
||||||
hci_pi(sk)->hdev = hdev;
|
hci_pi(sk)->hdev = hdev;
|
||||||
|
|
||||||
|
/* Send event to monitor */
|
||||||
|
skb = create_monitor_ctrl_open(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_inc(&hdev->promisc);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HCI_CHANNEL_MONITOR:
|
case HCI_CHANNEL_MONITOR:
|
||||||
|
@ -900,6 +1230,8 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hci_pi(sk)->channel = haddr.hci_channel;
|
||||||
|
|
||||||
/* The monitor interface is restricted to CAP_NET_RAW
|
/* The monitor interface is restricted to CAP_NET_RAW
|
||||||
* capabilities and with that implicitly trusted.
|
* capabilities and with that implicitly trusted.
|
||||||
*/
|
*/
|
||||||
|
@ -908,9 +1240,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
send_monitor_note(sk, "Linux version %s (%s)",
|
send_monitor_note(sk, "Linux version %s (%s)",
|
||||||
init_utsname()->release,
|
init_utsname()->release,
|
||||||
init_utsname()->machine);
|
init_utsname()->machine);
|
||||||
send_monitor_note(sk, "Bluetooth subsystem version %s",
|
send_monitor_note(sk, "Bluetooth subsystem version %u.%u",
|
||||||
BT_SUBSYS_VERSION);
|
BT_SUBSYS_VERSION, BT_SUBSYS_REVISION);
|
||||||
send_monitor_replay(sk);
|
send_monitor_replay(sk);
|
||||||
|
send_monitor_control_replay(sk);
|
||||||
|
|
||||||
atomic_inc(&monitor_promisc);
|
atomic_inc(&monitor_promisc);
|
||||||
break;
|
break;
|
||||||
|
@ -925,6 +1258,8 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
err = -EPERM;
|
err = -EPERM;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hci_pi(sk)->channel = haddr.hci_channel;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -946,6 +1281,8 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
if (capable(CAP_NET_ADMIN))
|
if (capable(CAP_NET_ADMIN))
|
||||||
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
|
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
|
||||||
|
|
||||||
|
hci_pi(sk)->channel = haddr.hci_channel;
|
||||||
|
|
||||||
/* At the moment the index and unconfigured index events
|
/* At the moment the index and unconfigured index events
|
||||||
* are enabled unconditionally. Setting them on each
|
* are enabled unconditionally. Setting them on each
|
||||||
* socket when binding keeps this functionality. They
|
* socket when binding keeps this functionality. They
|
||||||
|
@ -956,16 +1293,40 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
* received by untrusted users. Example for such events
|
* received by untrusted users. Example for such events
|
||||||
* are changes to settings, class of device, name etc.
|
* are changes to settings, class of device, name etc.
|
||||||
*/
|
*/
|
||||||
if (haddr.hci_channel == HCI_CHANNEL_CONTROL) {
|
if (hci_pi(sk)->channel == HCI_CHANNEL_CONTROL) {
|
||||||
|
if (!hci_sock_gen_cookie(sk)) {
|
||||||
|
/* In the case when a cookie has already been
|
||||||
|
* assigned, this socket will transtion from
|
||||||
|
* a raw socket into a control socket. To
|
||||||
|
* allow for a clean transtion, send the
|
||||||
|
* close notification first.
|
||||||
|
*/
|
||||||
|
skb = create_monitor_ctrl_close(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send event to monitor */
|
||||||
|
skb = create_monitor_ctrl_open(sk);
|
||||||
|
if (skb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
hci_sock_set_flag(sk, HCI_MGMT_INDEX_EVENTS);
|
hci_sock_set_flag(sk, HCI_MGMT_INDEX_EVENTS);
|
||||||
hci_sock_set_flag(sk, HCI_MGMT_UNCONF_INDEX_EVENTS);
|
hci_sock_set_flag(sk, HCI_MGMT_UNCONF_INDEX_EVENTS);
|
||||||
hci_sock_set_flag(sk, HCI_MGMT_GENERIC_EVENTS);
|
hci_sock_set_flag(sk, HCI_MGMT_OPTION_EVENTS);
|
||||||
|
hci_sock_set_flag(sk, HCI_MGMT_SETTING_EVENTS);
|
||||||
|
hci_sock_set_flag(sk, HCI_MGMT_DEV_CLASS_EVENTS);
|
||||||
|
hci_sock_set_flag(sk, HCI_MGMT_LOCAL_NAME_EVENTS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
hci_pi(sk)->channel = haddr.hci_channel;
|
|
||||||
sk->sk_state = BT_BOUND;
|
sk->sk_state = BT_BOUND;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
@ -1133,6 +1494,19 @@ static int hci_mgmt_cmd(struct hci_mgmt_chan *chan, struct sock *sk,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chan->channel == HCI_CHANNEL_CONTROL) {
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* 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,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (opcode >= chan->handler_count ||
|
if (opcode >= chan->handler_count ||
|
||||||
chan->handlers[opcode].func == NULL) {
|
chan->handlers[opcode].func == NULL) {
|
||||||
BT_DBG("Unknown op %u", opcode);
|
BT_DBG("Unknown op %u", opcode);
|
||||||
|
@ -1440,6 +1814,9 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
|
||||||
|
|
||||||
BT_DBG("sk %p, opt %d", sk, optname);
|
BT_DBG("sk %p, opt %d", sk, optname);
|
||||||
|
|
||||||
|
if (level != SOL_HCI)
|
||||||
|
return -ENOPROTOOPT;
|
||||||
|
|
||||||
lock_sock(sk);
|
lock_sock(sk);
|
||||||
|
|
||||||
if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) {
|
if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) {
|
||||||
|
@ -1523,6 +1900,9 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
|
||||||
|
|
||||||
BT_DBG("sk %p, opt %d", sk, optname);
|
BT_DBG("sk %p, opt %d", sk, optname);
|
||||||
|
|
||||||
|
if (level != SOL_HCI)
|
||||||
|
return -ENOPROTOOPT;
|
||||||
|
|
||||||
if (get_user(len, optlen))
|
if (get_user(len, optlen))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include "leds.h"
|
#include "leds.h"
|
||||||
|
|
||||||
|
DEFINE_LED_TRIGGER(bt_power_led_trigger);
|
||||||
|
|
||||||
struct hci_basic_led_trigger {
|
struct hci_basic_led_trigger {
|
||||||
struct led_trigger led_trigger;
|
struct led_trigger led_trigger;
|
||||||
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
||||||
|
@ -24,6 +26,21 @@ void hci_leds_update_powered(struct hci_dev *hdev, bool enabled)
|
||||||
if (hdev->power_led)
|
if (hdev->power_led)
|
||||||
led_trigger_event(hdev->power_led,
|
led_trigger_event(hdev->power_led,
|
||||||
enabled ? LED_FULL : LED_OFF);
|
enabled ? LED_FULL : LED_OFF);
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
struct hci_dev *d;
|
||||||
|
|
||||||
|
read_lock(&hci_dev_list_lock);
|
||||||
|
|
||||||
|
list_for_each_entry(d, &hci_dev_list, list) {
|
||||||
|
if (test_bit(HCI_UP, &d->flags))
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_unlock(&hci_dev_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
led_trigger_event(bt_power_led_trigger, enabled ? LED_FULL : LED_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void power_activate(struct led_classdev *led_cdev)
|
static void power_activate(struct led_classdev *led_cdev)
|
||||||
|
@ -72,3 +89,13 @@ void hci_leds_init(struct hci_dev *hdev)
|
||||||
/* initialize power_led */
|
/* initialize power_led */
|
||||||
hdev->power_led = led_allocate_basic(hdev, power_activate, "power");
|
hdev->power_led = led_allocate_basic(hdev, power_activate, "power");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bt_leds_init(void)
|
||||||
|
{
|
||||||
|
led_trigger_register_simple("bluetooth-power", &bt_power_led_trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_leds_cleanup(void)
|
||||||
|
{
|
||||||
|
led_trigger_unregister_simple(bt_power_led_trigger);
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_LEDS)
|
#if IS_ENABLED(CONFIG_BT_LEDS)
|
||||||
|
|
||||||
void hci_leds_update_powered(struct hci_dev *hdev, bool enabled);
|
void hci_leds_update_powered(struct hci_dev *hdev, bool enabled);
|
||||||
void hci_leds_init(struct hci_dev *hdev);
|
void hci_leds_init(struct hci_dev *hdev);
|
||||||
|
|
||||||
|
void bt_leds_init(void);
|
||||||
|
void bt_leds_cleanup(void);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline void hci_leds_update_powered(struct hci_dev *hdev,
|
static inline void hci_leds_update_powered(struct hci_dev *hdev,
|
||||||
bool enabled) {}
|
bool enabled) {}
|
||||||
static inline void hci_leds_init(struct hci_dev *hdev) {}
|
static inline void hci_leds_init(struct hci_dev *hdev) {}
|
||||||
|
|
||||||
|
static inline void bt_leds_init(void) {}
|
||||||
|
static inline void bt_leds_cleanup(void) {}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
#include "mgmt_util.h"
|
#include "mgmt_util.h"
|
||||||
|
|
||||||
#define MGMT_VERSION 1
|
#define MGMT_VERSION 1
|
||||||
#define MGMT_REVISION 13
|
#define MGMT_REVISION 14
|
||||||
|
|
||||||
static const u16 mgmt_commands[] = {
|
static const u16 mgmt_commands[] = {
|
||||||
MGMT_OP_READ_INDEX_LIST,
|
MGMT_OP_READ_INDEX_LIST,
|
||||||
|
@ -104,6 +104,8 @@ static const u16 mgmt_commands[] = {
|
||||||
MGMT_OP_REMOVE_ADVERTISING,
|
MGMT_OP_REMOVE_ADVERTISING,
|
||||||
MGMT_OP_GET_ADV_SIZE_INFO,
|
MGMT_OP_GET_ADV_SIZE_INFO,
|
||||||
MGMT_OP_START_LIMITED_DISCOVERY,
|
MGMT_OP_START_LIMITED_DISCOVERY,
|
||||||
|
MGMT_OP_READ_EXT_INFO,
|
||||||
|
MGMT_OP_SET_APPEARANCE,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u16 mgmt_events[] = {
|
static const u16 mgmt_events[] = {
|
||||||
|
@ -141,6 +143,7 @@ static const u16 mgmt_events[] = {
|
||||||
MGMT_EV_LOCAL_OOB_DATA_UPDATED,
|
MGMT_EV_LOCAL_OOB_DATA_UPDATED,
|
||||||
MGMT_EV_ADVERTISING_ADDED,
|
MGMT_EV_ADVERTISING_ADDED,
|
||||||
MGMT_EV_ADVERTISING_REMOVED,
|
MGMT_EV_ADVERTISING_REMOVED,
|
||||||
|
MGMT_EV_EXT_INFO_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u16 mgmt_untrusted_commands[] = {
|
static const u16 mgmt_untrusted_commands[] = {
|
||||||
|
@ -149,6 +152,7 @@ static const u16 mgmt_untrusted_commands[] = {
|
||||||
MGMT_OP_READ_UNCONF_INDEX_LIST,
|
MGMT_OP_READ_UNCONF_INDEX_LIST,
|
||||||
MGMT_OP_READ_CONFIG_INFO,
|
MGMT_OP_READ_CONFIG_INFO,
|
||||||
MGMT_OP_READ_EXT_INDEX_LIST,
|
MGMT_OP_READ_EXT_INDEX_LIST,
|
||||||
|
MGMT_OP_READ_EXT_INFO,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u16 mgmt_untrusted_events[] = {
|
static const u16 mgmt_untrusted_events[] = {
|
||||||
|
@ -162,6 +166,7 @@ static const u16 mgmt_untrusted_events[] = {
|
||||||
MGMT_EV_NEW_CONFIG_OPTIONS,
|
MGMT_EV_NEW_CONFIG_OPTIONS,
|
||||||
MGMT_EV_EXT_INDEX_ADDED,
|
MGMT_EV_EXT_INDEX_ADDED,
|
||||||
MGMT_EV_EXT_INDEX_REMOVED,
|
MGMT_EV_EXT_INDEX_REMOVED,
|
||||||
|
MGMT_EV_EXT_INFO_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000)
|
#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000)
|
||||||
|
@ -256,13 +261,6 @@ static int mgmt_limited_event(u16 event, struct hci_dev *hdev, void *data,
|
||||||
flag, skip_sk);
|
flag, skip_sk);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mgmt_generic_event(u16 event, struct hci_dev *hdev, void *data,
|
|
||||||
u16 len, struct sock *skip_sk)
|
|
||||||
{
|
|
||||||
return mgmt_send_event(event, hdev, HCI_CHANNEL_CONTROL, data, len,
|
|
||||||
HCI_MGMT_GENERIC_EVENTS, skip_sk);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, u16 len,
|
static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, u16 len,
|
||||||
struct sock *skip_sk)
|
struct sock *skip_sk)
|
||||||
{
|
{
|
||||||
|
@ -278,6 +276,14 @@ static u8 le_addr_type(u8 mgmt_addr_type)
|
||||||
return ADDR_LE_DEV_RANDOM;
|
return ADDR_LE_DEV_RANDOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mgmt_fill_version_info(void *ver)
|
||||||
|
{
|
||||||
|
struct mgmt_rp_read_version *rp = ver;
|
||||||
|
|
||||||
|
rp->version = MGMT_VERSION;
|
||||||
|
rp->revision = cpu_to_le16(MGMT_REVISION);
|
||||||
|
}
|
||||||
|
|
||||||
static int read_version(struct sock *sk, struct hci_dev *hdev, void *data,
|
static int read_version(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
u16 data_len)
|
u16 data_len)
|
||||||
{
|
{
|
||||||
|
@ -285,8 +291,7 @@ static int read_version(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
|
|
||||||
BT_DBG("sock %p", sk);
|
BT_DBG("sock %p", sk);
|
||||||
|
|
||||||
rp.version = MGMT_VERSION;
|
mgmt_fill_version_info(&rp);
|
||||||
rp.revision = cpu_to_le16(MGMT_REVISION);
|
|
||||||
|
|
||||||
return mgmt_cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, 0,
|
return mgmt_cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, 0,
|
||||||
&rp, sizeof(rp));
|
&rp, sizeof(rp));
|
||||||
|
@ -572,8 +577,8 @@ static int new_options(struct hci_dev *hdev, struct sock *skip)
|
||||||
{
|
{
|
||||||
__le32 options = get_missing_options(hdev);
|
__le32 options = get_missing_options(hdev);
|
||||||
|
|
||||||
return mgmt_generic_event(MGMT_EV_NEW_CONFIG_OPTIONS, hdev, &options,
|
return mgmt_limited_event(MGMT_EV_NEW_CONFIG_OPTIONS, hdev, &options,
|
||||||
sizeof(options), skip);
|
sizeof(options), HCI_MGMT_OPTION_EVENTS, skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_options_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev)
|
static int send_options_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev)
|
||||||
|
@ -862,6 +867,107 @@ static int read_controller_info(struct sock *sk, struct hci_dev *hdev,
|
||||||
sizeof(rp));
|
sizeof(rp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 u16 append_eir_data_to_buf(struct hci_dev *hdev, u8 *eir)
|
||||||
|
{
|
||||||
|
u16 eir_len = 0;
|
||||||
|
size_t name_len;
|
||||||
|
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
||||||
|
eir_len = eir_append_data(eir, eir_len, EIR_CLASS_OF_DEV,
|
||||||
|
hdev->dev_class, 3);
|
||||||
|
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_LE_ENABLED))
|
||||||
|
eir_len = eir_append_le16(eir, eir_len, EIR_APPEARANCE,
|
||||||
|
hdev->appearance);
|
||||||
|
|
||||||
|
name_len = strlen(hdev->dev_name);
|
||||||
|
eir_len = eir_append_data(eir, eir_len, EIR_NAME_COMPLETE,
|
||||||
|
hdev->dev_name, name_len);
|
||||||
|
|
||||||
|
name_len = strlen(hdev->short_name);
|
||||||
|
eir_len = eir_append_data(eir, eir_len, EIR_NAME_SHORT,
|
||||||
|
hdev->short_name, name_len);
|
||||||
|
|
||||||
|
return eir_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_ext_controller_info(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
void *data, u16 data_len)
|
||||||
|
{
|
||||||
|
char buf[512];
|
||||||
|
struct mgmt_rp_read_ext_info *rp = (void *)buf;
|
||||||
|
u16 eir_len;
|
||||||
|
|
||||||
|
BT_DBG("sock %p %s", sk, hdev->name);
|
||||||
|
|
||||||
|
memset(&buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
bacpy(&rp->bdaddr, &hdev->bdaddr);
|
||||||
|
|
||||||
|
rp->version = hdev->hci_ver;
|
||||||
|
rp->manufacturer = cpu_to_le16(hdev->manufacturer);
|
||||||
|
|
||||||
|
rp->supported_settings = cpu_to_le32(get_supported_settings(hdev));
|
||||||
|
rp->current_settings = cpu_to_le32(get_current_settings(hdev));
|
||||||
|
|
||||||
|
|
||||||
|
eir_len = append_eir_data_to_buf(hdev, rp->eir);
|
||||||
|
rp->eir_len = cpu_to_le16(eir_len);
|
||||||
|
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
|
/* If this command is called at least once, then the events
|
||||||
|
* for class of device and local name changes are disabled
|
||||||
|
* and only the new extended controller information event
|
||||||
|
* is used.
|
||||||
|
*/
|
||||||
|
hci_sock_set_flag(sk, HCI_MGMT_EXT_INFO_EVENTS);
|
||||||
|
hci_sock_clear_flag(sk, HCI_MGMT_DEV_CLASS_EVENTS);
|
||||||
|
hci_sock_clear_flag(sk, HCI_MGMT_LOCAL_NAME_EVENTS);
|
||||||
|
|
||||||
|
return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_READ_EXT_INFO, 0, rp,
|
||||||
|
sizeof(*rp) + eir_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_info_changed(struct hci_dev *hdev, struct sock *skip)
|
||||||
|
{
|
||||||
|
char buf[512];
|
||||||
|
struct mgmt_ev_ext_info_changed *ev = (void *)buf;
|
||||||
|
u16 eir_len;
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
eir_len = append_eir_data_to_buf(hdev, ev->eir);
|
||||||
|
ev->eir_len = cpu_to_le16(eir_len);
|
||||||
|
|
||||||
|
return mgmt_limited_event(MGMT_EV_EXT_INFO_CHANGED, hdev, ev,
|
||||||
|
sizeof(*ev) + eir_len,
|
||||||
|
HCI_MGMT_EXT_INFO_EVENTS, skip);
|
||||||
|
}
|
||||||
|
|
||||||
static int send_settings_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev)
|
static int send_settings_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
__le32 settings = cpu_to_le32(get_current_settings(hdev));
|
__le32 settings = cpu_to_le32(get_current_settings(hdev));
|
||||||
|
@ -922,7 +1028,7 @@ static int clean_up_hci_state(struct hci_dev *hdev)
|
||||||
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
|
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
|
||||||
}
|
}
|
||||||
|
|
||||||
hci_req_clear_adv_instance(hdev, NULL, 0x00, false);
|
hci_req_clear_adv_instance(hdev, NULL, NULL, 0x00, false);
|
||||||
|
|
||||||
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
||||||
__hci_req_disable_advertising(&req);
|
__hci_req_disable_advertising(&req);
|
||||||
|
@ -1000,8 +1106,8 @@ static int new_settings(struct hci_dev *hdev, struct sock *skip)
|
||||||
{
|
{
|
||||||
__le32 ev = cpu_to_le32(get_current_settings(hdev));
|
__le32 ev = cpu_to_le32(get_current_settings(hdev));
|
||||||
|
|
||||||
return mgmt_generic_event(MGMT_EV_NEW_SETTINGS, hdev, &ev,
|
return mgmt_limited_event(MGMT_EV_NEW_SETTINGS, hdev, &ev,
|
||||||
sizeof(ev), skip);
|
sizeof(ev), HCI_MGMT_SETTING_EVENTS, skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
int mgmt_new_settings(struct hci_dev *hdev)
|
int mgmt_new_settings(struct hci_dev *hdev)
|
||||||
|
@ -1690,7 +1796,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
||||||
enabled = lmp_host_le_capable(hdev);
|
enabled = lmp_host_le_capable(hdev);
|
||||||
|
|
||||||
if (!val)
|
if (!val)
|
||||||
hci_req_clear_adv_instance(hdev, NULL, 0x00, true);
|
hci_req_clear_adv_instance(hdev, NULL, NULL, 0x00, true);
|
||||||
|
|
||||||
if (!hdev_is_powered(hdev) || val == enabled) {
|
if (!hdev_is_powered(hdev) || val == enabled) {
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
@ -2513,8 +2619,8 @@ static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
BT_DBG("");
|
BT_DBG("");
|
||||||
|
|
||||||
if (cp->io_capability > SMP_IO_KEYBOARD_DISPLAY)
|
if (cp->io_capability > SMP_IO_KEYBOARD_DISPLAY)
|
||||||
return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_IO_CAPABILITY,
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_IO_CAPABILITY,
|
||||||
MGMT_STATUS_INVALID_PARAMS, NULL, 0);
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
@ -2932,6 +3038,35 @@ static int user_passkey_neg_reply(struct sock *sk, struct hci_dev *hdev,
|
||||||
HCI_OP_USER_PASSKEY_NEG_REPLY, 0);
|
HCI_OP_USER_PASSKEY_NEG_REPLY, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void adv_expire(struct hci_dev *hdev, u32 flags)
|
||||||
|
{
|
||||||
|
struct adv_info *adv_instance;
|
||||||
|
struct hci_request req;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
adv_instance = hci_find_adv_instance(hdev, hdev->cur_adv_instance);
|
||||||
|
if (!adv_instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* stop if current instance doesn't need to be changed */
|
||||||
|
if (!(adv_instance->flags & flags))
|
||||||
|
return;
|
||||||
|
|
||||||
|
cancel_adv_timeout(hdev);
|
||||||
|
|
||||||
|
adv_instance = hci_get_next_instance(hdev, adv_instance->instance);
|
||||||
|
if (!adv_instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
err = __hci_req_schedule_adv_instance(&req, adv_instance->instance,
|
||||||
|
true);
|
||||||
|
if (err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hci_req_run(&req, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void set_name_complete(struct hci_dev *hdev, u8 status, u16 opcode)
|
static void set_name_complete(struct hci_dev *hdev, u8 status, u16 opcode)
|
||||||
{
|
{
|
||||||
struct mgmt_cp_set_local_name *cp;
|
struct mgmt_cp_set_local_name *cp;
|
||||||
|
@ -2947,13 +3082,17 @@ static void set_name_complete(struct hci_dev *hdev, u8 status, u16 opcode)
|
||||||
|
|
||||||
cp = cmd->param;
|
cp = cmd->param;
|
||||||
|
|
||||||
if (status)
|
if (status) {
|
||||||
mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME,
|
mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME,
|
||||||
mgmt_status(status));
|
mgmt_status(status));
|
||||||
else
|
} else {
|
||||||
mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0,
|
mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0,
|
||||||
cp, sizeof(*cp));
|
cp, sizeof(*cp));
|
||||||
|
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
||||||
|
adv_expire(hdev, MGMT_ADV_FLAG_LOCAL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
mgmt_pending_remove(cmd);
|
mgmt_pending_remove(cmd);
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
|
@ -2993,8 +3132,9 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto failed;
|
goto failed;
|
||||||
|
|
||||||
err = mgmt_generic_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev,
|
err = mgmt_limited_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, data,
|
||||||
data, len, sk);
|
len, HCI_MGMT_LOCAL_NAME_EVENTS, sk);
|
||||||
|
ext_info_changed(hdev, sk);
|
||||||
|
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
@ -3029,6 +3169,40 @@ failed:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int set_appearance(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
|
u16 len)
|
||||||
|
{
|
||||||
|
struct mgmt_cp_set_appearance *cp = data;
|
||||||
|
u16 apperance;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
BT_DBG("");
|
||||||
|
|
||||||
|
if (!lmp_le_capable(hdev))
|
||||||
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_APPEARANCE,
|
||||||
|
MGMT_STATUS_NOT_SUPPORTED);
|
||||||
|
|
||||||
|
apperance = le16_to_cpu(cp->appearance);
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
if (hdev->appearance != apperance) {
|
||||||
|
hdev->appearance = apperance;
|
||||||
|
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
||||||
|
adv_expire(hdev, MGMT_ADV_FLAG_APPEARANCE);
|
||||||
|
|
||||||
|
ext_info_changed(hdev, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_APPEARANCE, 0, NULL,
|
||||||
|
0);
|
||||||
|
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static void read_local_oob_data_complete(struct hci_dev *hdev, u8 status,
|
static void read_local_oob_data_complete(struct hci_dev *hdev, u8 status,
|
||||||
u16 opcode, struct sk_buff *skb)
|
u16 opcode, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
|
@ -4869,7 +5043,7 @@ static int clock_info_cmd_complete(struct mgmt_pending_cmd *cmd, u8 status)
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
memset(&rp, 0, sizeof(rp));
|
memset(&rp, 0, sizeof(rp));
|
||||||
memcpy(&rp.addr, &cmd->param, sizeof(rp.addr));
|
memcpy(&rp.addr, cmd->param, sizeof(rp.addr));
|
||||||
|
|
||||||
if (status)
|
if (status)
|
||||||
goto complete;
|
goto complete;
|
||||||
|
@ -5501,17 +5675,6 @@ unlock:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 void read_local_oob_ext_data_complete(struct hci_dev *hdev, u8 status,
|
static void read_local_oob_ext_data_complete(struct hci_dev *hdev, u8 status,
|
||||||
u16 opcode, struct sk_buff *skb)
|
u16 opcode, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
|
@ -5815,6 +5978,8 @@ static u32 get_supported_adv_flags(struct hci_dev *hdev)
|
||||||
flags |= MGMT_ADV_FLAG_DISCOV;
|
flags |= MGMT_ADV_FLAG_DISCOV;
|
||||||
flags |= MGMT_ADV_FLAG_LIMITED_DISCOV;
|
flags |= MGMT_ADV_FLAG_LIMITED_DISCOV;
|
||||||
flags |= MGMT_ADV_FLAG_MANAGED_FLAGS;
|
flags |= MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||||
|
flags |= MGMT_ADV_FLAG_APPEARANCE;
|
||||||
|
flags |= MGMT_ADV_FLAG_LOCAL_NAME;
|
||||||
|
|
||||||
if (hdev->adv_tx_power != HCI_TX_POWER_INVALID)
|
if (hdev->adv_tx_power != HCI_TX_POWER_INVALID)
|
||||||
flags |= MGMT_ADV_FLAG_TX_POWER;
|
flags |= MGMT_ADV_FLAG_TX_POWER;
|
||||||
|
@ -5871,28 +6036,59 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool tlv_data_is_valid(struct hci_dev *hdev, u32 adv_flags, u8 *data,
|
static u8 tlv_data_max_len(u32 adv_flags, bool is_adv_data)
|
||||||
u8 len, bool is_adv_data)
|
|
||||||
{
|
{
|
||||||
u8 max_len = HCI_MAX_AD_LENGTH;
|
u8 max_len = HCI_MAX_AD_LENGTH;
|
||||||
int i, cur_len;
|
|
||||||
bool flags_managed = false;
|
|
||||||
bool tx_power_managed = false;
|
|
||||||
|
|
||||||
if (is_adv_data) {
|
if (is_adv_data) {
|
||||||
if (adv_flags & (MGMT_ADV_FLAG_DISCOV |
|
if (adv_flags & (MGMT_ADV_FLAG_DISCOV |
|
||||||
MGMT_ADV_FLAG_LIMITED_DISCOV |
|
MGMT_ADV_FLAG_LIMITED_DISCOV |
|
||||||
MGMT_ADV_FLAG_MANAGED_FLAGS)) {
|
MGMT_ADV_FLAG_MANAGED_FLAGS))
|
||||||
flags_managed = true;
|
|
||||||
max_len -= 3;
|
max_len -= 3;
|
||||||
|
|
||||||
|
if (adv_flags & MGMT_ADV_FLAG_TX_POWER)
|
||||||
|
max_len -= 3;
|
||||||
|
} else {
|
||||||
|
/* at least 1 byte of name should fit in */
|
||||||
|
if (adv_flags & MGMT_ADV_FLAG_LOCAL_NAME)
|
||||||
|
max_len -= 3;
|
||||||
|
|
||||||
|
if (adv_flags & (MGMT_ADV_FLAG_APPEARANCE))
|
||||||
|
max_len -= 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adv_flags & MGMT_ADV_FLAG_TX_POWER) {
|
return max_len;
|
||||||
tx_power_managed = true;
|
|
||||||
max_len -= 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool flags_managed(u32 adv_flags)
|
||||||
|
{
|
||||||
|
return adv_flags & (MGMT_ADV_FLAG_DISCOV |
|
||||||
|
MGMT_ADV_FLAG_LIMITED_DISCOV |
|
||||||
|
MGMT_ADV_FLAG_MANAGED_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool tx_power_managed(u32 adv_flags)
|
||||||
|
{
|
||||||
|
return adv_flags & MGMT_ADV_FLAG_TX_POWER;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool name_managed(u32 adv_flags)
|
||||||
|
{
|
||||||
|
return adv_flags & MGMT_ADV_FLAG_LOCAL_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool appearance_managed(u32 adv_flags)
|
||||||
|
{
|
||||||
|
return adv_flags & MGMT_ADV_FLAG_APPEARANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tlv_data_is_valid(u32 adv_flags, u8 *data, u8 len, bool is_adv_data)
|
||||||
|
{
|
||||||
|
int i, cur_len;
|
||||||
|
u8 max_len;
|
||||||
|
|
||||||
|
max_len = tlv_data_max_len(adv_flags, is_adv_data);
|
||||||
|
|
||||||
if (len > max_len)
|
if (len > max_len)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -5900,10 +6096,21 @@ static bool tlv_data_is_valid(struct hci_dev *hdev, u32 adv_flags, u8 *data,
|
||||||
for (i = 0, cur_len = 0; i < len; i += (cur_len + 1)) {
|
for (i = 0, cur_len = 0; i < len; i += (cur_len + 1)) {
|
||||||
cur_len = data[i];
|
cur_len = data[i];
|
||||||
|
|
||||||
if (flags_managed && data[i + 1] == EIR_FLAGS)
|
if (data[i + 1] == EIR_FLAGS &&
|
||||||
|
(!is_adv_data || flags_managed(adv_flags)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (tx_power_managed && data[i + 1] == EIR_TX_POWER)
|
if (data[i + 1] == EIR_TX_POWER && tx_power_managed(adv_flags))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data[i + 1] == EIR_NAME_COMPLETE && name_managed(adv_flags))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data[i + 1] == EIR_NAME_SHORT && name_managed(adv_flags))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data[i + 1] == EIR_APPEARANCE &&
|
||||||
|
appearance_managed(adv_flags))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* If the current field length would exceed the total data
|
/* If the current field length would exceed the total data
|
||||||
|
@ -6027,8 +6234,8 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tlv_data_is_valid(hdev, flags, cp->data, cp->adv_data_len, true) ||
|
if (!tlv_data_is_valid(flags, cp->data, cp->adv_data_len, true) ||
|
||||||
!tlv_data_is_valid(hdev, flags, cp->data + cp->adv_data_len,
|
!tlv_data_is_valid(flags, cp->data + cp->adv_data_len,
|
||||||
cp->scan_rsp_len, false)) {
|
cp->scan_rsp_len, false)) {
|
||||||
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
MGMT_STATUS_INVALID_PARAMS);
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
@ -6175,7 +6382,7 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
|
||||||
hci_req_init(&req, hdev);
|
hci_req_init(&req, hdev);
|
||||||
|
|
||||||
hci_req_clear_adv_instance(hdev, &req, cp->instance, true);
|
hci_req_clear_adv_instance(hdev, sk, &req, cp->instance, true);
|
||||||
|
|
||||||
if (list_empty(&hdev->adv_instances))
|
if (list_empty(&hdev->adv_instances))
|
||||||
__hci_req_disable_advertising(&req);
|
__hci_req_disable_advertising(&req);
|
||||||
|
@ -6211,23 +6418,6 @@ unlock:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 tlv_data_max_len(u32 adv_flags, bool is_adv_data)
|
|
||||||
{
|
|
||||||
u8 max_len = HCI_MAX_AD_LENGTH;
|
|
||||||
|
|
||||||
if (is_adv_data) {
|
|
||||||
if (adv_flags & (MGMT_ADV_FLAG_DISCOV |
|
|
||||||
MGMT_ADV_FLAG_LIMITED_DISCOV |
|
|
||||||
MGMT_ADV_FLAG_MANAGED_FLAGS))
|
|
||||||
max_len -= 3;
|
|
||||||
|
|
||||||
if (adv_flags & MGMT_ADV_FLAG_TX_POWER)
|
|
||||||
max_len -= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return max_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_adv_size_info(struct sock *sk, struct hci_dev *hdev,
|
static int get_adv_size_info(struct sock *sk, struct hci_dev *hdev,
|
||||||
void *data, u16 data_len)
|
void *data, u16 data_len)
|
||||||
{
|
{
|
||||||
|
@ -6356,6 +6546,9 @@ static const struct hci_mgmt_handler mgmt_handlers[] = {
|
||||||
{ remove_advertising, MGMT_REMOVE_ADVERTISING_SIZE },
|
{ remove_advertising, MGMT_REMOVE_ADVERTISING_SIZE },
|
||||||
{ get_adv_size_info, MGMT_GET_ADV_SIZE_INFO_SIZE },
|
{ get_adv_size_info, MGMT_GET_ADV_SIZE_INFO_SIZE },
|
||||||
{ start_limited_discovery, MGMT_START_DISCOVERY_SIZE },
|
{ start_limited_discovery, MGMT_START_DISCOVERY_SIZE },
|
||||||
|
{ read_ext_controller_info,MGMT_READ_EXT_INFO_SIZE,
|
||||||
|
HCI_MGMT_UNTRUSTED },
|
||||||
|
{ set_appearance, MGMT_SET_APPEARANCE_SIZE },
|
||||||
};
|
};
|
||||||
|
|
||||||
void mgmt_index_added(struct hci_dev *hdev)
|
void mgmt_index_added(struct hci_dev *hdev)
|
||||||
|
@ -6494,9 +6687,12 @@ void __mgmt_power_off(struct hci_dev *hdev)
|
||||||
|
|
||||||
mgmt_pending_foreach(0, hdev, cmd_complete_rsp, &status);
|
mgmt_pending_foreach(0, hdev, cmd_complete_rsp, &status);
|
||||||
|
|
||||||
if (memcmp(hdev->dev_class, zero_cod, sizeof(zero_cod)) != 0)
|
if (memcmp(hdev->dev_class, zero_cod, sizeof(zero_cod)) != 0) {
|
||||||
mgmt_generic_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev,
|
mgmt_limited_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev,
|
||||||
zero_cod, sizeof(zero_cod), NULL);
|
zero_cod, sizeof(zero_cod),
|
||||||
|
HCI_MGMT_DEV_CLASS_EVENTS, NULL);
|
||||||
|
ext_info_changed(hdev, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
new_settings(hdev, match.sk);
|
new_settings(hdev, match.sk);
|
||||||
|
|
||||||
|
@ -7092,9 +7288,11 @@ void mgmt_set_class_of_dev_complete(struct hci_dev *hdev, u8 *dev_class,
|
||||||
mgmt_pending_foreach(MGMT_OP_ADD_UUID, hdev, sk_lookup, &match);
|
mgmt_pending_foreach(MGMT_OP_ADD_UUID, hdev, sk_lookup, &match);
|
||||||
mgmt_pending_foreach(MGMT_OP_REMOVE_UUID, hdev, sk_lookup, &match);
|
mgmt_pending_foreach(MGMT_OP_REMOVE_UUID, hdev, sk_lookup, &match);
|
||||||
|
|
||||||
if (!status)
|
if (!status) {
|
||||||
mgmt_generic_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev,
|
mgmt_limited_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev, dev_class,
|
||||||
dev_class, 3, NULL);
|
3, HCI_MGMT_DEV_CLASS_EVENTS, NULL);
|
||||||
|
ext_info_changed(hdev, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (match.sk)
|
if (match.sk)
|
||||||
sock_put(match.sk);
|
sock_put(match.sk);
|
||||||
|
@ -7123,8 +7321,9 @@ void mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmt_generic_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, &ev, sizeof(ev),
|
mgmt_limited_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, &ev, sizeof(ev),
|
||||||
cmd ? cmd->sk : NULL);
|
HCI_MGMT_LOCAL_NAME_EVENTS, cmd ? cmd->sk : NULL);
|
||||||
|
ext_info_changed(hdev, cmd ? cmd->sk : NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool has_uuid(u8 *uuid, u16 uuid_count, u8 (*uuids)[16])
|
static inline bool has_uuid(u8 *uuid, u16 uuid_count, u8 (*uuids)[16])
|
||||||
|
|
|
@ -21,12 +21,41 @@
|
||||||
SOFTWARE IS DISCLAIMED.
|
SOFTWARE IS DISCLAIMED.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/bluetooth.h>
|
||||||
#include <net/bluetooth/hci_core.h>
|
#include <net/bluetooth/hci_core.h>
|
||||||
|
#include <net/bluetooth/hci_mon.h>
|
||||||
#include <net/bluetooth/mgmt.h>
|
#include <net/bluetooth/mgmt.h>
|
||||||
|
|
||||||
#include "mgmt_util.h"
|
#include "mgmt_util.h"
|
||||||
|
|
||||||
|
static struct sk_buff *create_monitor_ctrl_event(__le16 index, u32 cookie,
|
||||||
|
u16 opcode, u16 len, void *buf)
|
||||||
|
{
|
||||||
|
struct hci_mon_hdr *hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
skb = bt_skb_alloc(6 + len, GFP_ATOMIC);
|
||||||
|
if (!skb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
put_unaligned_le32(cookie, skb_put(skb, 4));
|
||||||
|
put_unaligned_le16(opcode, skb_put(skb, 2));
|
||||||
|
|
||||||
|
if (buf)
|
||||||
|
memcpy(skb_put(skb, len), buf, len);
|
||||||
|
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
|
hdr = (void *)skb_push(skb, HCI_MON_HDR_SIZE);
|
||||||
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_EVENT);
|
||||||
|
hdr->index = index;
|
||||||
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
int mgmt_send_event(u16 event, struct hci_dev *hdev, unsigned short channel,
|
int mgmt_send_event(u16 event, struct hci_dev *hdev, unsigned short channel,
|
||||||
void *data, u16 data_len, int flag, struct sock *skip_sk)
|
void *data, u16 data_len, int flag, struct sock *skip_sk)
|
||||||
{
|
{
|
||||||
|
@ -52,14 +81,18 @@ int mgmt_send_event(u16 event, struct hci_dev *hdev, unsigned short channel,
|
||||||
__net_timestamp(skb);
|
__net_timestamp(skb);
|
||||||
|
|
||||||
hci_send_to_channel(channel, skb, flag, skip_sk);
|
hci_send_to_channel(channel, skb, flag, skip_sk);
|
||||||
kfree_skb(skb);
|
|
||||||
|
|
||||||
|
if (channel == HCI_CHANNEL_CONTROL)
|
||||||
|
hci_send_monitor_ctrl_event(hdev, event, data, data_len,
|
||||||
|
skb_get_ktime(skb), flag, skip_sk);
|
||||||
|
|
||||||
|
kfree_skb(skb);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mgmt_cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status)
|
int mgmt_cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb, *mskb;
|
||||||
struct mgmt_hdr *hdr;
|
struct mgmt_hdr *hdr;
|
||||||
struct mgmt_ev_cmd_status *ev;
|
struct mgmt_ev_cmd_status *ev;
|
||||||
int err;
|
int err;
|
||||||
|
@ -80,17 +113,30 @@ int mgmt_cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status)
|
||||||
ev->status = status;
|
ev->status = status;
|
||||||
ev->opcode = cpu_to_le16(cmd);
|
ev->opcode = cpu_to_le16(cmd);
|
||||||
|
|
||||||
|
mskb = create_monitor_ctrl_event(hdr->index, hci_sock_get_cookie(sk),
|
||||||
|
MGMT_EV_CMD_STATUS, sizeof(*ev), ev);
|
||||||
|
if (mskb)
|
||||||
|
skb->tstamp = mskb->tstamp;
|
||||||
|
else
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
err = sock_queue_rcv_skb(sk, skb);
|
err = sock_queue_rcv_skb(sk, skb);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
if (mskb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, mskb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(mskb);
|
||||||
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mgmt_cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status,
|
int mgmt_cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status,
|
||||||
void *rp, size_t rp_len)
|
void *rp, size_t rp_len)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb, *mskb;
|
||||||
struct mgmt_hdr *hdr;
|
struct mgmt_hdr *hdr;
|
||||||
struct mgmt_ev_cmd_complete *ev;
|
struct mgmt_ev_cmd_complete *ev;
|
||||||
int err;
|
int err;
|
||||||
|
@ -114,10 +160,24 @@ int mgmt_cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status,
|
||||||
if (rp)
|
if (rp)
|
||||||
memcpy(ev->data, rp, rp_len);
|
memcpy(ev->data, rp, rp_len);
|
||||||
|
|
||||||
|
mskb = create_monitor_ctrl_event(hdr->index, hci_sock_get_cookie(sk),
|
||||||
|
MGMT_EV_CMD_COMPLETE,
|
||||||
|
sizeof(*ev) + rp_len, ev);
|
||||||
|
if (mskb)
|
||||||
|
skb->tstamp = mskb->tstamp;
|
||||||
|
else
|
||||||
|
__net_timestamp(skb);
|
||||||
|
|
||||||
err = sock_queue_rcv_skb(sk, skb);
|
err = sock_queue_rcv_skb(sk, skb);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
if (mskb) {
|
||||||
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, mskb,
|
||||||
|
HCI_SOCK_TRUSTED, NULL);
|
||||||
|
kfree_skb(mskb);
|
||||||
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3387,6 +3387,9 @@ int smp_register(struct hci_dev *hdev)
|
||||||
if (!lmp_sc_capable(hdev)) {
|
if (!lmp_sc_capable(hdev)) {
|
||||||
debugfs_create_file("force_bredr_smp", 0644, hdev->debugfs,
|
debugfs_create_file("force_bredr_smp", 0644, hdev->debugfs,
|
||||||
hdev, &force_bredr_smp_fops);
|
hdev, &force_bredr_smp_fops);
|
||||||
|
|
||||||
|
/* Flag can be already set here (due to power toggle) */
|
||||||
|
if (!hci_dev_test_flag(hdev, HCI_FORCE_BREDR_SMP))
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -663,6 +663,7 @@ ieee802154_if_add(struct ieee802154_local *local, const char *name,
|
||||||
|
|
||||||
/* TODO check this */
|
/* TODO check this */
|
||||||
SET_NETDEV_DEV(ndev, &local->phy->dev);
|
SET_NETDEV_DEV(ndev, &local->phy->dev);
|
||||||
|
dev_net_set(ndev, wpan_phy_net(local->hw.phy));
|
||||||
sdata = netdev_priv(ndev);
|
sdata = netdev_priv(ndev);
|
||||||
ndev->ieee802154_ptr = &sdata->wpan_dev;
|
ndev->ieee802154_ptr = &sdata->wpan_dev;
|
||||||
memcpy(sdata->name, ndev->name, IFNAMSIZ);
|
memcpy(sdata->name, ndev->name, IFNAMSIZ);
|
||||||
|
|
|
@ -101,11 +101,16 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
|
||||||
sdata->dev->stats.rx_bytes += skb->len;
|
sdata->dev->stats.rx_bytes += skb->len;
|
||||||
|
|
||||||
switch (mac_cb(skb)->type) {
|
switch (mac_cb(skb)->type) {
|
||||||
|
case IEEE802154_FC_TYPE_BEACON:
|
||||||
|
case IEEE802154_FC_TYPE_ACK:
|
||||||
|
case IEEE802154_FC_TYPE_MAC_CMD:
|
||||||
|
goto fail;
|
||||||
|
|
||||||
case IEEE802154_FC_TYPE_DATA:
|
case IEEE802154_FC_TYPE_DATA:
|
||||||
return ieee802154_deliver_skb(skb);
|
return ieee802154_deliver_skb(skb);
|
||||||
default:
|
default:
|
||||||
pr_warn("ieee802154: bad frame received (type = %d)\n",
|
pr_warn_ratelimited("ieee802154: bad frame received "
|
||||||
mac_cb(skb)->type);
|
"(type = %d)\n", mac_cb(skb)->type);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue