Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next
Johan Hedberg says: ==================== pull request: bluetooth-next 2015-03-27 Here's another set of Bluetooth & 802.15.4 patches for 4.1: - New API to control LE advertising data (i.e. peripheral role) - mac802154 & at86rf230 cleanups - Support for toggling quirks from debugfs (useful for testing) - Memory leak fix for LE scanning - Extra version info reading support for Broadcom controllers 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
89a3f3c55b
11 changed files with 864 additions and 94 deletions
|
@ -24,6 +24,7 @@
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/usb.h>
|
#include <linux/usb.h>
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
|
#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>
|
||||||
|
@ -53,6 +54,7 @@ static struct usb_driver btusb_driver;
|
||||||
#define BTUSB_INTEL_NEW 0x2000
|
#define BTUSB_INTEL_NEW 0x2000
|
||||||
#define BTUSB_AMP 0x4000
|
#define BTUSB_AMP 0x4000
|
||||||
#define BTUSB_QCA_ROME 0x8000
|
#define BTUSB_QCA_ROME 0x8000
|
||||||
|
#define BTUSB_BCM_APPLE 0x10000
|
||||||
|
|
||||||
static const struct usb_device_id btusb_table[] = {
|
static const struct usb_device_id btusb_table[] = {
|
||||||
/* Generic Bluetooth USB device */
|
/* Generic Bluetooth USB device */
|
||||||
|
@ -62,7 +64,8 @@ static const struct usb_device_id btusb_table[] = {
|
||||||
{ USB_DEVICE_INFO(0xe0, 0x01, 0x04), .driver_info = BTUSB_AMP },
|
{ USB_DEVICE_INFO(0xe0, 0x01, 0x04), .driver_info = BTUSB_AMP },
|
||||||
|
|
||||||
/* Apple-specific (Broadcom) devices */
|
/* Apple-specific (Broadcom) devices */
|
||||||
{ USB_VENDOR_AND_INTERFACE_INFO(0x05ac, 0xff, 0x01, 0x01) },
|
{ USB_VENDOR_AND_INTERFACE_INFO(0x05ac, 0xff, 0x01, 0x01),
|
||||||
|
.driver_info = BTUSB_BCM_APPLE },
|
||||||
|
|
||||||
/* MediaTek MT76x0E */
|
/* MediaTek MT76x0E */
|
||||||
{ USB_DEVICE(0x0e8d, 0x763f) },
|
{ USB_DEVICE(0x0e8d, 0x763f) },
|
||||||
|
@ -2458,6 +2461,25 @@ static int btusb_setup_bcm_patchram(struct hci_dev *hdev)
|
||||||
subver = le16_to_cpu(ver->lmp_subver);
|
subver = le16_to_cpu(ver->lmp_subver);
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
/* Read Verbose Config Version Info */
|
||||||
|
skb = __hci_cmd_sync(hdev, 0xfc79, 0, NULL, HCI_INIT_TIMEOUT);
|
||||||
|
if (IS_ERR(skb)) {
|
||||||
|
ret = PTR_ERR(skb);
|
||||||
|
BT_ERR("%s: BCM: Read Verbose Version failed (%ld)",
|
||||||
|
hdev->name, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skb->len != 7) {
|
||||||
|
BT_ERR("%s: BCM: Read Verbose Version event length mismatch",
|
||||||
|
hdev->name);
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_INFO("%s: BCM: chip id %u", hdev->name, skb->data[1]);
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
for (i = 0; bcm_subver_table[i].name; i++) {
|
for (i = 0; bcm_subver_table[i].name; i++) {
|
||||||
if (subver == bcm_subver_table[i].subver) {
|
if (subver == bcm_subver_table[i].subver) {
|
||||||
hw_name = bcm_subver_table[i].name;
|
hw_name = bcm_subver_table[i].name;
|
||||||
|
@ -2615,6 +2637,34 @@ static int btusb_set_bdaddr_bcm(struct hci_dev *hdev, const bdaddr_t *bdaddr)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int btusb_setup_bcm_apple(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Read Verbose Config Version Info */
|
||||||
|
skb = __hci_cmd_sync(hdev, 0xfc79, 0, NULL, HCI_INIT_TIMEOUT);
|
||||||
|
if (IS_ERR(skb)) {
|
||||||
|
err = PTR_ERR(skb);
|
||||||
|
BT_ERR("%s: BCM: Read Verbose Version failed (%d)",
|
||||||
|
hdev->name, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skb->len != 7) {
|
||||||
|
BT_ERR("%s: BCM: Read Verbose Version event length mismatch",
|
||||||
|
hdev->name);
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_INFO("%s: BCM: chip id %u build %4.4u", hdev->name, skb->data[1],
|
||||||
|
get_unaligned_le16(skb->data + 5));
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int btusb_set_bdaddr_ath3012(struct hci_dev *hdev,
|
static int btusb_set_bdaddr_ath3012(struct hci_dev *hdev,
|
||||||
const bdaddr_t *bdaddr)
|
const bdaddr_t *bdaddr)
|
||||||
{
|
{
|
||||||
|
@ -3014,6 +3064,11 @@ static int btusb_probe(struct usb_interface *intf,
|
||||||
set_bit(HCI_QUIRK_STRICT_DUPLICATE_FILTER, &hdev->quirks);
|
set_bit(HCI_QUIRK_STRICT_DUPLICATE_FILTER, &hdev->quirks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id->driver_info & BTUSB_BCM_APPLE) {
|
||||||
|
hdev->setup = btusb_setup_bcm_apple;
|
||||||
|
set_bit(HCI_QUIRK_STRICT_DUPLICATE_FILTER, &hdev->quirks);
|
||||||
|
}
|
||||||
|
|
||||||
if (id->driver_info & BTUSB_INTEL) {
|
if (id->driver_info & BTUSB_INTEL) {
|
||||||
hdev->setup = btusb_setup_intel;
|
hdev->setup = btusb_setup_intel;
|
||||||
hdev->shutdown = btusb_shutdown_intel;
|
hdev->shutdown = btusb_shutdown_intel;
|
||||||
|
|
|
@ -261,6 +261,16 @@ static int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hci_uart_setup(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
struct hci_uart *hu = hci_get_drvdata(hdev);
|
||||||
|
|
||||||
|
if (hu->proto->setup)
|
||||||
|
return hu->proto->setup(hu);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------ LDISC part ------ */
|
/* ------ LDISC part ------ */
|
||||||
/* hci_uart_tty_open
|
/* hci_uart_tty_open
|
||||||
*
|
*
|
||||||
|
@ -426,6 +436,7 @@ static int hci_uart_register_dev(struct hci_uart *hu)
|
||||||
hdev->close = hci_uart_close;
|
hdev->close = hci_uart_close;
|
||||||
hdev->flush = hci_uart_flush;
|
hdev->flush = hci_uart_flush;
|
||||||
hdev->send = hci_uart_send_frame;
|
hdev->send = hci_uart_send_frame;
|
||||||
|
hdev->setup = hci_uart_setup;
|
||||||
SET_HCIDEV_DEV(hdev, hu->tty->dev);
|
SET_HCIDEV_DEV(hdev, hu->tty->dev);
|
||||||
|
|
||||||
if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags))
|
if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags))
|
||||||
|
|
|
@ -59,6 +59,7 @@ struct hci_uart_proto {
|
||||||
int (*flush)(struct hci_uart *hu);
|
int (*flush)(struct hci_uart *hu);
|
||||||
int (*recv)(struct hci_uart *hu, void *data, int len);
|
int (*recv)(struct hci_uart *hu, void *data, int len);
|
||||||
int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb);
|
int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb);
|
||||||
|
int (*setup)(struct hci_uart *hu);
|
||||||
struct sk_buff *(*dequeue)(struct hci_uart *hu);
|
struct sk_buff *(*dequeue)(struct hci_uart *hu);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#include <linux/irq.h>
|
#include <linux/irq.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/spinlock.h>
|
|
||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/spi/at86rf230.h>
|
#include <linux/spi/at86rf230.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
|
@ -96,8 +95,6 @@ struct at86rf230_local {
|
||||||
unsigned long cal_timeout;
|
unsigned long cal_timeout;
|
||||||
s8 max_frame_retries;
|
s8 max_frame_retries;
|
||||||
bool is_tx;
|
bool is_tx;
|
||||||
/* spinlock for is_tx protection */
|
|
||||||
spinlock_t lock;
|
|
||||||
u8 tx_retry;
|
u8 tx_retry;
|
||||||
struct sk_buff *tx_skb;
|
struct sk_buff *tx_skb;
|
||||||
struct at86rf230_state_change tx;
|
struct at86rf230_state_change tx;
|
||||||
|
@ -460,6 +457,7 @@ at86rf230_async_error_recover(void *context)
|
||||||
struct at86rf230_state_change *ctx = context;
|
struct at86rf230_state_change *ctx = context;
|
||||||
struct at86rf230_local *lp = ctx->lp;
|
struct at86rf230_local *lp = ctx->lp;
|
||||||
|
|
||||||
|
lp->is_tx = 0;
|
||||||
at86rf230_async_state_change(lp, ctx, STATE_RX_AACK_ON, NULL, false);
|
at86rf230_async_state_change(lp, ctx, STATE_RX_AACK_ON, NULL, false);
|
||||||
ieee802154_wake_queue(lp->hw);
|
ieee802154_wake_queue(lp->hw);
|
||||||
}
|
}
|
||||||
|
@ -878,10 +876,8 @@ at86rf230_rx_trac_check(void *context)
|
||||||
static void
|
static void
|
||||||
at86rf230_irq_trx_end(struct at86rf230_local *lp)
|
at86rf230_irq_trx_end(struct at86rf230_local *lp)
|
||||||
{
|
{
|
||||||
spin_lock(&lp->lock);
|
|
||||||
if (lp->is_tx) {
|
if (lp->is_tx) {
|
||||||
lp->is_tx = 0;
|
lp->is_tx = 0;
|
||||||
spin_unlock(&lp->lock);
|
|
||||||
|
|
||||||
if (lp->tx_aret)
|
if (lp->tx_aret)
|
||||||
at86rf230_async_state_change(lp, &lp->irq,
|
at86rf230_async_state_change(lp, &lp->irq,
|
||||||
|
@ -894,7 +890,6 @@ at86rf230_irq_trx_end(struct at86rf230_local *lp)
|
||||||
at86rf230_tx_complete,
|
at86rf230_tx_complete,
|
||||||
true);
|
true);
|
||||||
} else {
|
} else {
|
||||||
spin_unlock(&lp->lock);
|
|
||||||
at86rf230_async_read_reg(lp, RG_TRX_STATE, &lp->irq,
|
at86rf230_async_read_reg(lp, RG_TRX_STATE, &lp->irq,
|
||||||
at86rf230_rx_trac_check, true);
|
at86rf230_rx_trac_check, true);
|
||||||
}
|
}
|
||||||
|
@ -964,9 +959,7 @@ at86rf230_write_frame(void *context)
|
||||||
u8 *buf = ctx->buf;
|
u8 *buf = ctx->buf;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
spin_lock(&lp->lock);
|
|
||||||
lp->is_tx = 1;
|
lp->is_tx = 1;
|
||||||
spin_unlock(&lp->lock);
|
|
||||||
|
|
||||||
buf[0] = CMD_FB | CMD_WRITE;
|
buf[0] = CMD_FB | CMD_WRITE;
|
||||||
buf[1] = skb->len + 2;
|
buf[1] = skb->len + 2;
|
||||||
|
@ -1698,7 +1691,6 @@ static int at86rf230_probe(struct spi_device *spi)
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
goto free_dev;
|
goto free_dev;
|
||||||
|
|
||||||
spin_lock_init(&lp->lock);
|
|
||||||
init_completion(&lp->state_complete);
|
init_completion(&lp->state_complete);
|
||||||
|
|
||||||
spi_set_drvdata(spi, lp);
|
spi_set_drvdata(spi, lp);
|
||||||
|
|
|
@ -227,6 +227,7 @@ enum {
|
||||||
HCI_LE_ENABLED,
|
HCI_LE_ENABLED,
|
||||||
HCI_ADVERTISING,
|
HCI_ADVERTISING,
|
||||||
HCI_ADVERTISING_CONNECTABLE,
|
HCI_ADVERTISING_CONNECTABLE,
|
||||||
|
HCI_ADVERTISING_INSTANCE,
|
||||||
HCI_CONNECTABLE,
|
HCI_CONNECTABLE,
|
||||||
HCI_DISCOVERABLE,
|
HCI_DISCOVERABLE,
|
||||||
HCI_LIMITED_DISCOVERABLE,
|
HCI_LIMITED_DISCOVERABLE,
|
||||||
|
@ -465,6 +466,7 @@ enum {
|
||||||
#define EIR_SSP_HASH_C 0x0E /* Simple Pairing Hash C */
|
#define EIR_SSP_HASH_C 0x0E /* Simple Pairing Hash C */
|
||||||
#define EIR_SSP_RAND_R 0x0F /* Simple Pairing Randomizer R */
|
#define EIR_SSP_RAND_R 0x0F /* Simple Pairing Randomizer R */
|
||||||
#define EIR_DEVICE_ID 0x10 /* device ID */
|
#define EIR_DEVICE_ID 0x10 /* device ID */
|
||||||
|
#define EIR_APPEARANCE 0x19 /* Device appearance */
|
||||||
#define EIR_LE_BDADDR 0x1B /* LE Bluetooth device address */
|
#define EIR_LE_BDADDR 0x1B /* LE Bluetooth device address */
|
||||||
#define EIR_LE_ROLE 0x1C /* LE role */
|
#define EIR_LE_ROLE 0x1C /* LE role */
|
||||||
#define EIR_LE_SC_CONFIRM 0x22 /* LE SC Confirmation Value */
|
#define EIR_LE_SC_CONFIRM 0x22 /* LE SC Confirmation Value */
|
||||||
|
|
|
@ -155,6 +155,17 @@ struct oob_data {
|
||||||
u8 rand256[16];
|
u8 rand256[16];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct adv_info {
|
||||||
|
struct delayed_work timeout_exp;
|
||||||
|
__u8 instance;
|
||||||
|
__u32 flags;
|
||||||
|
__u16 timeout;
|
||||||
|
__u16 adv_data_len;
|
||||||
|
__u8 adv_data[HCI_MAX_AD_LENGTH];
|
||||||
|
__u16 scan_rsp_len;
|
||||||
|
__u8 scan_rsp_data[HCI_MAX_AD_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
#define HCI_MAX_SHORT_NAME_LENGTH 10
|
#define HCI_MAX_SHORT_NAME_LENGTH 10
|
||||||
|
|
||||||
/* Default LE RPA expiry time, 15 minutes */
|
/* Default LE RPA expiry time, 15 minutes */
|
||||||
|
@ -364,6 +375,8 @@ struct hci_dev {
|
||||||
__u8 scan_rsp_data[HCI_MAX_AD_LENGTH];
|
__u8 scan_rsp_data[HCI_MAX_AD_LENGTH];
|
||||||
__u8 scan_rsp_data_len;
|
__u8 scan_rsp_data_len;
|
||||||
|
|
||||||
|
struct adv_info adv_instance;
|
||||||
|
|
||||||
__u8 irk[16];
|
__u8 irk[16];
|
||||||
__u32 rpa_timeout;
|
__u32 rpa_timeout;
|
||||||
struct delayed_work rpa_expired;
|
struct delayed_work rpa_expired;
|
||||||
|
@ -550,6 +563,11 @@ static inline void hci_discovery_filter_clear(struct hci_dev *hdev)
|
||||||
hdev->discovery.scan_duration = 0;
|
hdev->discovery.scan_duration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void adv_info_init(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
memset(&hdev->adv_instance, 0, sizeof(struct adv_info));
|
||||||
|
}
|
||||||
|
|
||||||
bool hci_discovery_active(struct hci_dev *hdev);
|
bool hci_discovery_active(struct hci_dev *hdev);
|
||||||
|
|
||||||
void hci_discovery_set_state(struct hci_dev *hdev, int state);
|
void hci_discovery_set_state(struct hci_dev *hdev, int state);
|
||||||
|
|
|
@ -539,6 +539,38 @@ struct mgmt_rp_read_adv_features {
|
||||||
__u8 instance[0];
|
__u8 instance[0];
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_OP_ADD_ADVERTISING 0x003E
|
||||||
|
struct mgmt_cp_add_advertising {
|
||||||
|
__u8 instance;
|
||||||
|
__le32 flags;
|
||||||
|
__le16 duration;
|
||||||
|
__le16 timeout;
|
||||||
|
__u8 adv_data_len;
|
||||||
|
__u8 scan_rsp_len;
|
||||||
|
__u8 data[0];
|
||||||
|
} __packed;
|
||||||
|
#define MGMT_ADD_ADVERTISING_SIZE 11
|
||||||
|
struct mgmt_rp_add_advertising {
|
||||||
|
__u8 instance;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_ADV_FLAG_CONNECTABLE BIT(0)
|
||||||
|
#define MGMT_ADV_FLAG_DISCOV BIT(1)
|
||||||
|
#define MGMT_ADV_FLAG_LIMITED_DISCOV BIT(2)
|
||||||
|
#define MGMT_ADV_FLAG_MANAGED_FLAGS BIT(3)
|
||||||
|
#define MGMT_ADV_FLAG_TX_POWER BIT(4)
|
||||||
|
#define MGMT_ADV_FLAG_APPEARANCE BIT(5)
|
||||||
|
#define MGMT_ADV_FLAG_LOCAL_NAME BIT(6)
|
||||||
|
|
||||||
|
#define MGMT_OP_REMOVE_ADVERTISING 0x003F
|
||||||
|
struct mgmt_cp_remove_advertising {
|
||||||
|
__u8 instance;
|
||||||
|
} __packed;
|
||||||
|
#define MGMT_REMOVE_ADVERTISING_SIZE 1
|
||||||
|
struct mgmt_rp_remove_advertising {
|
||||||
|
__u8 instance;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
#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;
|
||||||
|
@ -742,3 +774,13 @@ struct mgmt_ev_local_oob_data_updated {
|
||||||
__le16 eir_len;
|
__le16 eir_len;
|
||||||
__u8 eir[0];
|
__u8 eir[0];
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_EV_ADVERTISING_ADDED 0x0023
|
||||||
|
struct mgmt_ev_advertising_added {
|
||||||
|
__u8 instance;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
#define MGMT_EV_ADVERTISING_REMOVED 0x0024
|
||||||
|
struct mgmt_ev_advertising_removed {
|
||||||
|
__u8 instance;
|
||||||
|
} __packed;
|
||||||
|
|
|
@ -2874,7 +2874,6 @@ static void le_scan_disable_work_complete(struct hci_dev *hdev, u8 status,
|
||||||
{
|
{
|
||||||
/* General inquiry access code (GIAC) */
|
/* General inquiry access code (GIAC) */
|
||||||
u8 lap[3] = { 0x33, 0x8b, 0x9e };
|
u8 lap[3] = { 0x33, 0x8b, 0x9e };
|
||||||
struct hci_request req;
|
|
||||||
struct hci_cp_inquiry cp;
|
struct hci_cp_inquiry cp;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
@ -2893,13 +2892,6 @@ static void le_scan_disable_work_complete(struct hci_dev *hdev, u8 status,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DISCOV_TYPE_INTERLEAVED:
|
case DISCOV_TYPE_INTERLEAVED:
|
||||||
hci_req_init(&req, hdev);
|
|
||||||
|
|
||||||
memset(&cp, 0, sizeof(cp));
|
|
||||||
memcpy(&cp.lap, lap, sizeof(cp.lap));
|
|
||||||
cp.length = DISCOV_INTERLEAVED_INQUIRY_LEN;
|
|
||||||
hci_req_add(&req, HCI_OP_INQUIRY, sizeof(cp), &cp);
|
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
if (test_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY,
|
if (test_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY,
|
||||||
|
@ -2914,8 +2906,17 @@ static void le_scan_disable_work_complete(struct hci_dev *hdev, u8 status,
|
||||||
hci_discovery_set_state(hdev,
|
hci_discovery_set_state(hdev,
|
||||||
DISCOVERY_STOPPED);
|
DISCOVERY_STOPPED);
|
||||||
} else {
|
} else {
|
||||||
|
struct hci_request req;
|
||||||
|
|
||||||
hci_inquiry_cache_flush(hdev);
|
hci_inquiry_cache_flush(hdev);
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
|
||||||
|
memset(&cp, 0, sizeof(cp));
|
||||||
|
memcpy(&cp.lap, lap, sizeof(cp.lap));
|
||||||
|
cp.length = DISCOV_INTERLEAVED_INQUIRY_LEN;
|
||||||
|
hci_req_add(&req, HCI_OP_INQUIRY, sizeof(cp), &cp);
|
||||||
|
|
||||||
err = hci_req_run(&req, inquiry_complete);
|
err = hci_req_run(&req, inquiry_complete);
|
||||||
if (err) {
|
if (err) {
|
||||||
BT_ERR("Inquiry request failed: err %d", err);
|
BT_ERR("Inquiry request failed: err %d", err);
|
||||||
|
@ -3125,6 +3126,7 @@ struct hci_dev *hci_alloc_dev(void)
|
||||||
|
|
||||||
hci_init_sysfs(hdev);
|
hci_init_sysfs(hdev);
|
||||||
discovery_init(hdev);
|
discovery_init(hdev);
|
||||||
|
adv_info_init(hdev);
|
||||||
|
|
||||||
return hdev;
|
return hdev;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,54 @@
|
||||||
|
|
||||||
#include "hci_debugfs.h"
|
#include "hci_debugfs.h"
|
||||||
|
|
||||||
|
#define DEFINE_QUIRK_ATTRIBUTE(__name, __quirk) \
|
||||||
|
static ssize_t __name ## _read(struct file *file, \
|
||||||
|
char __user *user_buf, \
|
||||||
|
size_t count, loff_t *ppos) \
|
||||||
|
{ \
|
||||||
|
struct hci_dev *hdev = file->private_data; \
|
||||||
|
char buf[3]; \
|
||||||
|
\
|
||||||
|
buf[0] = test_bit(__quirk, &hdev->quirks) ? 'Y' : 'N'; \
|
||||||
|
buf[1] = '\n'; \
|
||||||
|
buf[2] = '\0'; \
|
||||||
|
return simple_read_from_buffer(user_buf, count, ppos, buf, 2); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
static ssize_t __name ## _write(struct file *file, \
|
||||||
|
const char __user *user_buf, \
|
||||||
|
size_t count, loff_t *ppos) \
|
||||||
|
{ \
|
||||||
|
struct hci_dev *hdev = file->private_data; \
|
||||||
|
char buf[32]; \
|
||||||
|
size_t buf_size = min(count, (sizeof(buf) - 1)); \
|
||||||
|
bool enable; \
|
||||||
|
\
|
||||||
|
if (test_bit(HCI_UP, &hdev->flags)) \
|
||||||
|
return -EBUSY; \
|
||||||
|
\
|
||||||
|
if (copy_from_user(buf, user_buf, buf_size)) \
|
||||||
|
return -EFAULT; \
|
||||||
|
\
|
||||||
|
buf[buf_size] = '\0'; \
|
||||||
|
if (strtobool(buf, &enable)) \
|
||||||
|
return -EINVAL; \
|
||||||
|
\
|
||||||
|
if (enable == test_bit(__quirk, &hdev->quirks)) \
|
||||||
|
return -EALREADY; \
|
||||||
|
\
|
||||||
|
change_bit(__quirk, &hdev->quirks); \
|
||||||
|
\
|
||||||
|
return count; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
static const struct file_operations __name ## _fops = { \
|
||||||
|
.open = simple_open, \
|
||||||
|
.read = __name ## _read, \
|
||||||
|
.write = __name ## _write, \
|
||||||
|
.llseek = default_llseek, \
|
||||||
|
} \
|
||||||
|
|
||||||
static int features_show(struct seq_file *f, void *ptr)
|
static int features_show(struct seq_file *f, void *ptr)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = f->private;
|
struct hci_dev *hdev = f->private;
|
||||||
|
@ -997,6 +1045,11 @@ static int adv_max_interval_get(void *data, u64 *val)
|
||||||
DEFINE_SIMPLE_ATTRIBUTE(adv_max_interval_fops, adv_max_interval_get,
|
DEFINE_SIMPLE_ATTRIBUTE(adv_max_interval_fops, adv_max_interval_get,
|
||||||
adv_max_interval_set, "%llu\n");
|
adv_max_interval_set, "%llu\n");
|
||||||
|
|
||||||
|
DEFINE_QUIRK_ATTRIBUTE(quirk_strict_duplicate_filter,
|
||||||
|
HCI_QUIRK_STRICT_DUPLICATE_FILTER);
|
||||||
|
DEFINE_QUIRK_ATTRIBUTE(quirk_simultaneous_discovery,
|
||||||
|
HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
|
||||||
|
|
||||||
void hci_debugfs_create_le(struct hci_dev *hdev)
|
void hci_debugfs_create_le(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
debugfs_create_file("identity", 0400, hdev->debugfs, hdev,
|
debugfs_create_file("identity", 0400, hdev->debugfs, hdev,
|
||||||
|
@ -1041,6 +1094,13 @@ void hci_debugfs_create_le(struct hci_dev *hdev)
|
||||||
&adv_max_interval_fops);
|
&adv_max_interval_fops);
|
||||||
debugfs_create_u16("discov_interleaved_timeout", 0644, hdev->debugfs,
|
debugfs_create_u16("discov_interleaved_timeout", 0644, hdev->debugfs,
|
||||||
&hdev->discov_interleaved_timeout);
|
&hdev->discov_interleaved_timeout);
|
||||||
|
|
||||||
|
debugfs_create_file("quirk_strict_duplicate_filter", 0644,
|
||||||
|
hdev->debugfs, hdev,
|
||||||
|
&quirk_strict_duplicate_filter_fops);
|
||||||
|
debugfs_create_file("quirk_simultaneous_discovery", 0644,
|
||||||
|
hdev->debugfs, hdev,
|
||||||
|
&quirk_simultaneous_discovery_fops);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hci_debugfs_create_conn(struct hci_conn *conn)
|
void hci_debugfs_create_conn(struct hci_conn *conn)
|
||||||
|
|
|
@ -100,6 +100,8 @@ static const u16 mgmt_commands[] = {
|
||||||
MGMT_OP_READ_LOCAL_OOB_EXT_DATA,
|
MGMT_OP_READ_LOCAL_OOB_EXT_DATA,
|
||||||
MGMT_OP_READ_EXT_INDEX_LIST,
|
MGMT_OP_READ_EXT_INDEX_LIST,
|
||||||
MGMT_OP_READ_ADV_FEATURES,
|
MGMT_OP_READ_ADV_FEATURES,
|
||||||
|
MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_OP_REMOVE_ADVERTISING,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u16 mgmt_events[] = {
|
static const u16 mgmt_events[] = {
|
||||||
|
@ -135,6 +137,29 @@ static const u16 mgmt_events[] = {
|
||||||
MGMT_EV_EXT_INDEX_ADDED,
|
MGMT_EV_EXT_INDEX_ADDED,
|
||||||
MGMT_EV_EXT_INDEX_REMOVED,
|
MGMT_EV_EXT_INDEX_REMOVED,
|
||||||
MGMT_EV_LOCAL_OOB_DATA_UPDATED,
|
MGMT_EV_LOCAL_OOB_DATA_UPDATED,
|
||||||
|
MGMT_EV_ADVERTISING_ADDED,
|
||||||
|
MGMT_EV_ADVERTISING_REMOVED,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u16 mgmt_untrusted_commands[] = {
|
||||||
|
MGMT_OP_READ_INDEX_LIST,
|
||||||
|
MGMT_OP_READ_INFO,
|
||||||
|
MGMT_OP_READ_UNCONF_INDEX_LIST,
|
||||||
|
MGMT_OP_READ_CONFIG_INFO,
|
||||||
|
MGMT_OP_READ_EXT_INDEX_LIST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u16 mgmt_untrusted_events[] = {
|
||||||
|
MGMT_EV_INDEX_ADDED,
|
||||||
|
MGMT_EV_INDEX_REMOVED,
|
||||||
|
MGMT_EV_NEW_SETTINGS,
|
||||||
|
MGMT_EV_CLASS_OF_DEV_CHANGED,
|
||||||
|
MGMT_EV_LOCAL_NAME_CHANGED,
|
||||||
|
MGMT_EV_UNCONF_INDEX_ADDED,
|
||||||
|
MGMT_EV_UNCONF_INDEX_REMOVED,
|
||||||
|
MGMT_EV_NEW_CONFIG_OPTIONS,
|
||||||
|
MGMT_EV_EXT_INDEX_ADDED,
|
||||||
|
MGMT_EV_EXT_INDEX_REMOVED,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000)
|
#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000)
|
||||||
|
@ -261,14 +286,20 @@ static int read_commands(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
u16 data_len)
|
u16 data_len)
|
||||||
{
|
{
|
||||||
struct mgmt_rp_read_commands *rp;
|
struct mgmt_rp_read_commands *rp;
|
||||||
const u16 num_commands = ARRAY_SIZE(mgmt_commands);
|
u16 num_commands, num_events;
|
||||||
const u16 num_events = ARRAY_SIZE(mgmt_events);
|
|
||||||
__le16 *opcode;
|
|
||||||
size_t rp_size;
|
size_t rp_size;
|
||||||
int i, err;
|
int i, err;
|
||||||
|
|
||||||
BT_DBG("sock %p", sk);
|
BT_DBG("sock %p", sk);
|
||||||
|
|
||||||
|
if (hci_sock_test_flag(sk, HCI_SOCK_TRUSTED)) {
|
||||||
|
num_commands = ARRAY_SIZE(mgmt_commands);
|
||||||
|
num_events = ARRAY_SIZE(mgmt_events);
|
||||||
|
} else {
|
||||||
|
num_commands = ARRAY_SIZE(mgmt_untrusted_commands);
|
||||||
|
num_events = ARRAY_SIZE(mgmt_untrusted_events);
|
||||||
|
}
|
||||||
|
|
||||||
rp_size = sizeof(*rp) + ((num_commands + num_events) * sizeof(u16));
|
rp_size = sizeof(*rp) + ((num_commands + num_events) * sizeof(u16));
|
||||||
|
|
||||||
rp = kmalloc(rp_size, GFP_KERNEL);
|
rp = kmalloc(rp_size, GFP_KERNEL);
|
||||||
|
@ -278,11 +309,23 @@ static int read_commands(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
rp->num_commands = cpu_to_le16(num_commands);
|
rp->num_commands = cpu_to_le16(num_commands);
|
||||||
rp->num_events = cpu_to_le16(num_events);
|
rp->num_events = cpu_to_le16(num_events);
|
||||||
|
|
||||||
for (i = 0, opcode = rp->opcodes; i < num_commands; i++, opcode++)
|
if (hci_sock_test_flag(sk, HCI_SOCK_TRUSTED)) {
|
||||||
put_unaligned_le16(mgmt_commands[i], opcode);
|
__le16 *opcode = rp->opcodes;
|
||||||
|
|
||||||
for (i = 0; i < num_events; i++, opcode++)
|
for (i = 0; i < num_commands; i++, opcode++)
|
||||||
put_unaligned_le16(mgmt_events[i], opcode);
|
put_unaligned_le16(mgmt_commands[i], opcode);
|
||||||
|
|
||||||
|
for (i = 0; i < num_events; i++, opcode++)
|
||||||
|
put_unaligned_le16(mgmt_events[i], opcode);
|
||||||
|
} else {
|
||||||
|
__le16 *opcode = rp->opcodes;
|
||||||
|
|
||||||
|
for (i = 0; i < num_commands; i++, opcode++)
|
||||||
|
put_unaligned_le16(mgmt_untrusted_commands[i], opcode);
|
||||||
|
|
||||||
|
for (i = 0; i < num_events; i++, opcode++)
|
||||||
|
put_unaligned_le16(mgmt_untrusted_events[i], opcode);
|
||||||
|
}
|
||||||
|
|
||||||
err = mgmt_cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_COMMANDS, 0,
|
err = mgmt_cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_COMMANDS, 0,
|
||||||
rp, rp_size);
|
rp, rp_size);
|
||||||
|
@ -650,7 +693,7 @@ static u32 get_current_settings(struct hci_dev *hdev)
|
||||||
* the second is to indicate if it is actually set.
|
* the second is to indicate if it is actually set.
|
||||||
*
|
*
|
||||||
* This means if the static address is not configured, this flag
|
* This means if the static address is not configured, this flag
|
||||||
* will never bet set. If the address is configured, then if the
|
* will never be set. If the address is configured, then if the
|
||||||
* address is actually used decides if the flag is set or not.
|
* address is actually used decides if the flag is set or not.
|
||||||
*
|
*
|
||||||
* For single mode LE only controllers and dual-mode controllers
|
* For single mode LE only controllers and dual-mode controllers
|
||||||
|
@ -789,7 +832,7 @@ static struct mgmt_pending_cmd *pending_find_data(u16 opcode,
|
||||||
return mgmt_pending_find_data(HCI_CHANNEL_CONTROL, opcode, hdev, data);
|
return mgmt_pending_find_data(HCI_CHANNEL_CONTROL, opcode, hdev, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 create_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||||
{
|
{
|
||||||
u8 ad_len = 0;
|
u8 ad_len = 0;
|
||||||
size_t name_len;
|
size_t name_len;
|
||||||
|
@ -815,7 +858,19 @@ static u8 create_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||||
return ad_len;
|
return ad_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_scan_rsp_data(struct hci_request *req)
|
static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
|
||||||
|
{
|
||||||
|
/* TODO: Set the appropriate entries based on advertising instance flags
|
||||||
|
* here once flags other than 0 are supported.
|
||||||
|
*/
|
||||||
|
memcpy(ptr, hdev->adv_instance.scan_rsp_data,
|
||||||
|
hdev->adv_instance.scan_rsp_len);
|
||||||
|
|
||||||
|
return hdev->adv_instance.scan_rsp_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_scan_rsp_data_for_instance(struct hci_request *req,
|
||||||
|
u8 instance)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = req->hdev;
|
struct hci_dev *hdev = req->hdev;
|
||||||
struct hci_cp_le_set_scan_rsp_data cp;
|
struct hci_cp_le_set_scan_rsp_data cp;
|
||||||
|
@ -826,10 +881,13 @@ static void update_scan_rsp_data(struct hci_request *req)
|
||||||
|
|
||||||
memset(&cp, 0, sizeof(cp));
|
memset(&cp, 0, sizeof(cp));
|
||||||
|
|
||||||
len = create_scan_rsp_data(hdev, cp.data);
|
if (instance)
|
||||||
|
len = create_instance_scan_rsp_data(hdev, cp.data);
|
||||||
|
else
|
||||||
|
len = create_default_scan_rsp_data(hdev, cp.data);
|
||||||
|
|
||||||
if (hdev->scan_rsp_data_len == len &&
|
if (hdev->scan_rsp_data_len == len &&
|
||||||
memcmp(cp.data, hdev->scan_rsp_data, len) == 0)
|
!memcmp(cp.data, hdev->scan_rsp_data, len))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data));
|
memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data));
|
||||||
|
@ -840,6 +898,25 @@ static void update_scan_rsp_data(struct hci_request *req)
|
||||||
hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp);
|
hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_scan_rsp_data(struct hci_request *req)
|
||||||
|
{
|
||||||
|
struct hci_dev *hdev = req->hdev;
|
||||||
|
u8 instance;
|
||||||
|
|
||||||
|
/* The "Set Advertising" setting supersedes the "Add Advertising"
|
||||||
|
* setting. Here we set the scan response data based on which
|
||||||
|
* setting was set. When neither apply, default to the global settings,
|
||||||
|
* represented by instance "0".
|
||||||
|
*/
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
|
||||||
|
!hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
||||||
|
instance = 0x01;
|
||||||
|
else
|
||||||
|
instance = 0x00;
|
||||||
|
|
||||||
|
update_scan_rsp_data_for_instance(req, instance);
|
||||||
|
}
|
||||||
|
|
||||||
static u8 get_adv_discov_flags(struct hci_dev *hdev)
|
static u8 get_adv_discov_flags(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
struct mgmt_pending_cmd *cmd;
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
@ -864,39 +941,116 @@ static u8 get_adv_discov_flags(struct hci_dev *hdev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 create_adv_data(struct hci_dev *hdev, u8 *ptr)
|
static u8 get_current_adv_instance(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
/* The "Set Advertising" setting supersedes the "Add Advertising"
|
||||||
|
* setting. Here we set the advertising data based on which
|
||||||
|
* setting was set. When neither apply, default to the global settings,
|
||||||
|
* represented by instance "0".
|
||||||
|
*/
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
|
||||||
|
!hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
||||||
|
return 0x01;
|
||||||
|
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_connectable(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
|
||||||
|
/* If there's a pending mgmt command the flag will not yet have
|
||||||
|
* it's final value, so check for this first.
|
||||||
|
*/
|
||||||
|
cmd = pending_find(MGMT_OP_SET_CONNECTABLE, hdev);
|
||||||
|
if (cmd) {
|
||||||
|
struct mgmt_mode *cp = cmd->param;
|
||||||
|
|
||||||
|
return cp->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hci_dev_test_flag(hdev, HCI_CONNECTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 get_adv_instance_flags(struct hci_dev *hdev, u8 instance)
|
||||||
|
{
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
if (instance > 0x01)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (instance == 0x01)
|
||||||
|
return hdev->adv_instance.flags;
|
||||||
|
|
||||||
|
/* Instance 0 always manages the "Tx Power" and "Flags" fields */
|
||||||
|
flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||||
|
|
||||||
|
/* For instance 0, assemble the flags from global settings */
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE) ||
|
||||||
|
get_connectable(hdev))
|
||||||
|
flags |= MGMT_ADV_FLAG_CONNECTABLE;
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
|
||||||
{
|
{
|
||||||
u8 ad_len = 0, flags = 0;
|
u8 ad_len = 0, flags = 0;
|
||||||
|
u32 instance_flags = get_adv_instance_flags(hdev, instance);
|
||||||
|
|
||||||
flags |= get_adv_discov_flags(hdev);
|
/* The Add Advertising command allows userspace to set both the general
|
||||||
|
* and limited discoverable flags.
|
||||||
|
*/
|
||||||
|
if (instance_flags & MGMT_ADV_FLAG_DISCOV)
|
||||||
|
flags |= LE_AD_GENERAL;
|
||||||
|
|
||||||
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
if (instance_flags & MGMT_ADV_FLAG_LIMITED_DISCOV)
|
||||||
flags |= LE_AD_NO_BREDR;
|
flags |= LE_AD_LIMITED;
|
||||||
|
|
||||||
if (flags) {
|
if (flags || (instance_flags & MGMT_ADV_FLAG_MANAGED_FLAGS)) {
|
||||||
BT_DBG("adv flags 0x%02x", flags);
|
/* If a discovery flag wasn't provided, simply use the global
|
||||||
|
* settings.
|
||||||
|
*/
|
||||||
|
if (!flags)
|
||||||
|
flags |= get_adv_discov_flags(hdev);
|
||||||
|
|
||||||
ptr[0] = 2;
|
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
||||||
ptr[1] = EIR_FLAGS;
|
flags |= LE_AD_NO_BREDR;
|
||||||
ptr[2] = flags;
|
|
||||||
|
/* If flags would still be empty, then there is no need to
|
||||||
|
* include the "Flags" AD field".
|
||||||
|
*/
|
||||||
|
if (flags) {
|
||||||
|
ptr[0] = 0x02;
|
||||||
|
ptr[1] = EIR_FLAGS;
|
||||||
|
ptr[2] = flags;
|
||||||
|
|
||||||
|
ad_len += 3;
|
||||||
|
ptr += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provide Tx Power only if we can provide a valid value for it */
|
||||||
|
if (hdev->adv_tx_power != HCI_TX_POWER_INVALID &&
|
||||||
|
(instance_flags & MGMT_ADV_FLAG_TX_POWER)) {
|
||||||
|
ptr[0] = 0x02;
|
||||||
|
ptr[1] = EIR_TX_POWER;
|
||||||
|
ptr[2] = (u8)hdev->adv_tx_power;
|
||||||
|
|
||||||
ad_len += 3;
|
ad_len += 3;
|
||||||
ptr += 3;
|
ptr += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hdev->adv_tx_power != HCI_TX_POWER_INVALID) {
|
if (instance) {
|
||||||
ptr[0] = 2;
|
memcpy(ptr, hdev->adv_instance.adv_data,
|
||||||
ptr[1] = EIR_TX_POWER;
|
hdev->adv_instance.adv_data_len);
|
||||||
ptr[2] = (u8) hdev->adv_tx_power;
|
ad_len += hdev->adv_instance.adv_data_len;
|
||||||
|
|
||||||
ad_len += 3;
|
|
||||||
ptr += 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ad_len;
|
return ad_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_adv_data(struct hci_request *req)
|
static void update_adv_data_for_instance(struct hci_request *req, u8 instance)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = req->hdev;
|
struct hci_dev *hdev = req->hdev;
|
||||||
struct hci_cp_le_set_adv_data cp;
|
struct hci_cp_le_set_adv_data cp;
|
||||||
|
@ -907,8 +1061,9 @@ static void update_adv_data(struct hci_request *req)
|
||||||
|
|
||||||
memset(&cp, 0, sizeof(cp));
|
memset(&cp, 0, sizeof(cp));
|
||||||
|
|
||||||
len = create_adv_data(hdev, cp.data);
|
len = create_instance_adv_data(hdev, instance, cp.data);
|
||||||
|
|
||||||
|
/* There's nothing to do if the data hasn't changed */
|
||||||
if (hdev->adv_data_len == len &&
|
if (hdev->adv_data_len == len &&
|
||||||
memcmp(cp.data, hdev->adv_data, len) == 0)
|
memcmp(cp.data, hdev->adv_data, len) == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -921,6 +1076,14 @@ static void update_adv_data(struct hci_request *req)
|
||||||
hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp);
|
hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_adv_data(struct hci_request *req)
|
||||||
|
{
|
||||||
|
struct hci_dev *hdev = req->hdev;
|
||||||
|
u8 instance = get_current_adv_instance(hdev);
|
||||||
|
|
||||||
|
update_adv_data_for_instance(req, instance);
|
||||||
|
}
|
||||||
|
|
||||||
int mgmt_update_adv_data(struct hci_dev *hdev)
|
int mgmt_update_adv_data(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
struct hci_request req;
|
struct hci_request req;
|
||||||
|
@ -1048,22 +1211,6 @@ static void update_class(struct hci_request *req)
|
||||||
hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod);
|
hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool get_connectable(struct hci_dev *hdev)
|
|
||||||
{
|
|
||||||
struct mgmt_pending_cmd *cmd;
|
|
||||||
|
|
||||||
/* If there's a pending mgmt command the flag will not yet have
|
|
||||||
* it's final value, so check for this first.
|
|
||||||
*/
|
|
||||||
cmd = pending_find(MGMT_OP_SET_CONNECTABLE, hdev);
|
|
||||||
if (cmd) {
|
|
||||||
struct mgmt_mode *cp = cmd->param;
|
|
||||||
return cp->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hci_dev_test_flag(hdev, HCI_CONNECTABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disable_advertising(struct hci_request *req)
|
static void disable_advertising(struct hci_request *req)
|
||||||
{
|
{
|
||||||
u8 enable = 0x00;
|
u8 enable = 0x00;
|
||||||
|
@ -1077,6 +1224,8 @@ static void enable_advertising(struct hci_request *req)
|
||||||
struct hci_cp_le_set_adv_param cp;
|
struct hci_cp_le_set_adv_param cp;
|
||||||
u8 own_addr_type, enable = 0x01;
|
u8 own_addr_type, enable = 0x01;
|
||||||
bool connectable;
|
bool connectable;
|
||||||
|
u8 instance;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
if (hci_conn_num(hdev, LE_LINK) > 0)
|
if (hci_conn_num(hdev, LE_LINK) > 0)
|
||||||
return;
|
return;
|
||||||
|
@ -1091,10 +1240,9 @@ static void enable_advertising(struct hci_request *req)
|
||||||
*/
|
*/
|
||||||
hci_dev_clear_flag(hdev, HCI_LE_ADV);
|
hci_dev_clear_flag(hdev, HCI_LE_ADV);
|
||||||
|
|
||||||
if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE))
|
instance = get_current_adv_instance(hdev);
|
||||||
connectable = true;
|
flags = get_adv_instance_flags(hdev, instance);
|
||||||
else
|
connectable = (flags & MGMT_ADV_FLAG_CONNECTABLE);
|
||||||
connectable = get_connectable(hdev);
|
|
||||||
|
|
||||||
/* Set require_privacy to true only when non-connectable
|
/* Set require_privacy to true only when non-connectable
|
||||||
* advertising is used. In that case it is fine to use a
|
* advertising is used. In that case it is fine to use a
|
||||||
|
@ -1264,6 +1412,49 @@ static bool hci_stop_discovery(struct hci_request *req)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void advertising_added(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
u8 instance)
|
||||||
|
{
|
||||||
|
struct mgmt_ev_advertising_added ev;
|
||||||
|
|
||||||
|
ev.instance = instance;
|
||||||
|
|
||||||
|
mgmt_event(MGMT_EV_ADVERTISING_ADDED, hdev, &ev, sizeof(ev), sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void advertising_removed(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
u8 instance)
|
||||||
|
{
|
||||||
|
struct mgmt_ev_advertising_removed ev;
|
||||||
|
|
||||||
|
ev.instance = instance;
|
||||||
|
|
||||||
|
mgmt_event(MGMT_EV_ADVERTISING_REMOVED, hdev, &ev, sizeof(ev), sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_adv_instance(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
struct hci_request req;
|
||||||
|
|
||||||
|
if (!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (hdev->adv_instance.timeout)
|
||||||
|
cancel_delayed_work(&hdev->adv_instance.timeout_exp);
|
||||||
|
|
||||||
|
memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
|
||||||
|
advertising_removed(NULL, hdev, 1);
|
||||||
|
hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
|
||||||
|
|
||||||
|
if (!hdev_is_powered(hdev) ||
|
||||||
|
hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
||||||
|
return;
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
disable_advertising(&req);
|
||||||
|
hci_req_run(&req, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static int clean_up_hci_state(struct hci_dev *hdev)
|
static int clean_up_hci_state(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
struct hci_request req;
|
struct hci_request req;
|
||||||
|
@ -1279,6 +1470,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hdev->adv_instance.timeout)
|
||||||
|
clear_adv_instance(hdev);
|
||||||
|
|
||||||
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
|
||||||
disable_advertising(&req);
|
disable_advertising(&req);
|
||||||
|
|
||||||
|
@ -2209,10 +2403,22 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
||||||
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
||||||
MGMT_STATUS_INVALID_PARAMS);
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
|
||||||
/* LE-only devices do not allow toggling LE on/off */
|
/* Bluetooth single mode LE only controllers or dual-mode
|
||||||
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
|
* controllers configured as LE only devices, do not allow
|
||||||
|
* switching LE off. These have either LE enabled explicitly
|
||||||
|
* or BR/EDR has been previously switched off.
|
||||||
|
*
|
||||||
|
* When trying to enable an already enabled LE, then gracefully
|
||||||
|
* send a positive response. Trying to disable it however will
|
||||||
|
* result into rejection.
|
||||||
|
*/
|
||||||
|
if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) {
|
||||||
|
if (cp->val == 0x01)
|
||||||
|
return send_settings_rsp(sk, MGMT_OP_SET_LE, hdev);
|
||||||
|
|
||||||
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
||||||
MGMT_STATUS_REJECTED);
|
MGMT_STATUS_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
@ -4362,10 +4568,17 @@ static int set_device_id(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void enable_advertising_instance(struct hci_dev *hdev, u8 status,
|
||||||
|
u16 opcode)
|
||||||
|
{
|
||||||
|
BT_DBG("status %d", status);
|
||||||
|
}
|
||||||
|
|
||||||
static void set_advertising_complete(struct hci_dev *hdev, u8 status,
|
static void set_advertising_complete(struct hci_dev *hdev, u8 status,
|
||||||
u16 opcode)
|
u16 opcode)
|
||||||
{
|
{
|
||||||
struct cmd_lookup match = { NULL, hdev };
|
struct cmd_lookup match = { NULL, hdev };
|
||||||
|
struct hci_request req;
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
@ -4390,6 +4603,21 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status,
|
||||||
if (match.sk)
|
if (match.sk)
|
||||||
sock_put(match.sk);
|
sock_put(match.sk);
|
||||||
|
|
||||||
|
/* If "Set Advertising" was just disabled and instance advertising was
|
||||||
|
* set up earlier, then enable the advertising instance.
|
||||||
|
*/
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
|
||||||
|
!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
|
||||||
|
update_adv_data(&req);
|
||||||
|
enable_advertising(&req);
|
||||||
|
|
||||||
|
if (hci_req_run(&req, enable_advertising_instance) < 0)
|
||||||
|
BT_ERR("Failed to re-configure advertising");
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
||||||
}
|
}
|
||||||
|
@ -4472,10 +4700,14 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
else
|
else
|
||||||
hci_dev_clear_flag(hdev, HCI_ADVERTISING_CONNECTABLE);
|
hci_dev_clear_flag(hdev, HCI_ADVERTISING_CONNECTABLE);
|
||||||
|
|
||||||
if (val)
|
if (val) {
|
||||||
|
/* Switch to instance "0" for the Set Advertising setting. */
|
||||||
|
update_adv_data_for_instance(&req, 0);
|
||||||
|
update_scan_rsp_data_for_instance(&req, 0);
|
||||||
enable_advertising(&req);
|
enable_advertising(&req);
|
||||||
else
|
} else {
|
||||||
disable_advertising(&req);
|
disable_advertising(&req);
|
||||||
|
}
|
||||||
|
|
||||||
err = hci_req_run(&req, set_advertising_complete);
|
err = hci_req_run(&req, set_advertising_complete);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -6281,29 +6513,69 @@ done:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u32 get_supported_adv_flags(struct hci_dev *hdev)
|
||||||
|
{
|
||||||
|
u32 flags = 0;
|
||||||
|
|
||||||
|
flags |= MGMT_ADV_FLAG_CONNECTABLE;
|
||||||
|
flags |= MGMT_ADV_FLAG_DISCOV;
|
||||||
|
flags |= MGMT_ADV_FLAG_LIMITED_DISCOV;
|
||||||
|
flags |= MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||||
|
|
||||||
|
if (hdev->adv_tx_power != HCI_TX_POWER_INVALID)
|
||||||
|
flags |= MGMT_ADV_FLAG_TX_POWER;
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
|
static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
|
||||||
void *data, u16 data_len)
|
void *data, u16 data_len)
|
||||||
{
|
{
|
||||||
struct mgmt_rp_read_adv_features *rp;
|
struct mgmt_rp_read_adv_features *rp;
|
||||||
size_t rp_len;
|
size_t rp_len;
|
||||||
int err;
|
int err;
|
||||||
|
bool instance;
|
||||||
|
u32 supported_flags;
|
||||||
|
|
||||||
BT_DBG("%s", hdev->name);
|
BT_DBG("%s", hdev->name);
|
||||||
|
|
||||||
|
if (!lmp_le_capable(hdev))
|
||||||
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_READ_ADV_FEATURES,
|
||||||
|
MGMT_STATUS_REJECTED);
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
rp_len = sizeof(*rp);
|
rp_len = sizeof(*rp);
|
||||||
|
|
||||||
|
/* Currently only one instance is supported, so just add 1 to the
|
||||||
|
* response length.
|
||||||
|
*/
|
||||||
|
instance = hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE);
|
||||||
|
if (instance)
|
||||||
|
rp_len++;
|
||||||
|
|
||||||
rp = kmalloc(rp_len, GFP_ATOMIC);
|
rp = kmalloc(rp_len, GFP_ATOMIC);
|
||||||
if (!rp) {
|
if (!rp) {
|
||||||
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
rp->supported_flags = cpu_to_le32(0);
|
supported_flags = get_supported_adv_flags(hdev);
|
||||||
rp->max_adv_data_len = 31;
|
|
||||||
rp->max_scan_rsp_len = 31;
|
rp->supported_flags = cpu_to_le32(supported_flags);
|
||||||
rp->max_instances = 0;
|
rp->max_adv_data_len = HCI_MAX_AD_LENGTH;
|
||||||
rp->num_instances = 0;
|
rp->max_scan_rsp_len = HCI_MAX_AD_LENGTH;
|
||||||
|
rp->max_instances = 1;
|
||||||
|
|
||||||
|
/* Currently only one instance is supported, so simply return the
|
||||||
|
* current instance number.
|
||||||
|
*/
|
||||||
|
if (instance) {
|
||||||
|
rp->num_instances = 1;
|
||||||
|
rp->instance[0] = 1;
|
||||||
|
} else {
|
||||||
|
rp->num_instances = 0;
|
||||||
|
}
|
||||||
|
|
||||||
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
|
@ -6315,6 +6587,318 @@ 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,
|
||||||
|
u8 len, bool is_adv_data)
|
||||||
|
{
|
||||||
|
u8 max_len = HCI_MAX_AD_LENGTH;
|
||||||
|
int i, cur_len;
|
||||||
|
bool flags_managed = false;
|
||||||
|
bool tx_power_managed = false;
|
||||||
|
u32 flags_params = MGMT_ADV_FLAG_DISCOV | MGMT_ADV_FLAG_LIMITED_DISCOV |
|
||||||
|
MGMT_ADV_FLAG_MANAGED_FLAGS;
|
||||||
|
|
||||||
|
if (is_adv_data && (adv_flags & flags_params)) {
|
||||||
|
flags_managed = true;
|
||||||
|
max_len -= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_adv_data && (adv_flags & MGMT_ADV_FLAG_TX_POWER)) {
|
||||||
|
tx_power_managed = true;
|
||||||
|
max_len -= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > max_len)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Make sure that the data is correctly formatted. */
|
||||||
|
for (i = 0, cur_len = 0; i < len; i += (cur_len + 1)) {
|
||||||
|
cur_len = data[i];
|
||||||
|
|
||||||
|
if (flags_managed && data[i + 1] == EIR_FLAGS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (tx_power_managed && data[i + 1] == EIR_TX_POWER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* If the current field length would exceed the total data
|
||||||
|
* length, then it's invalid.
|
||||||
|
*/
|
||||||
|
if (i + cur_len >= len)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_advertising_complete(struct hci_dev *hdev, u8 status,
|
||||||
|
u16 opcode)
|
||||||
|
{
|
||||||
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
struct mgmt_rp_add_advertising rp;
|
||||||
|
|
||||||
|
BT_DBG("status %d", status);
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
cmd = pending_find(MGMT_OP_ADD_ADVERTISING, hdev);
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
|
||||||
|
memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
|
||||||
|
advertising_removed(cmd ? cmd->sk : NULL, hdev, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmd)
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
|
rp.instance = 0x01;
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
mgmt_cmd_status(cmd->sk, cmd->index, cmd->opcode,
|
||||||
|
mgmt_status(status));
|
||||||
|
else
|
||||||
|
mgmt_cmd_complete(cmd->sk, cmd->index, cmd->opcode,
|
||||||
|
mgmt_status(status), &rp, sizeof(rp));
|
||||||
|
|
||||||
|
mgmt_pending_remove(cmd);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adv_timeout_expired(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct hci_dev *hdev = container_of(work, struct hci_dev,
|
||||||
|
adv_instance.timeout_exp.work);
|
||||||
|
|
||||||
|
hdev->adv_instance.timeout = 0;
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
clear_adv_instance(hdev);
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_advertising(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
void *data, u16 data_len)
|
||||||
|
{
|
||||||
|
struct mgmt_cp_add_advertising *cp = data;
|
||||||
|
struct mgmt_rp_add_advertising rp;
|
||||||
|
u32 flags;
|
||||||
|
u32 supported_flags;
|
||||||
|
u8 status;
|
||||||
|
u16 timeout;
|
||||||
|
int err;
|
||||||
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
struct hci_request req;
|
||||||
|
|
||||||
|
BT_DBG("%s", hdev->name);
|
||||||
|
|
||||||
|
status = mgmt_le_support(hdev);
|
||||||
|
if (status)
|
||||||
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
status);
|
||||||
|
|
||||||
|
flags = __le32_to_cpu(cp->flags);
|
||||||
|
timeout = __le16_to_cpu(cp->timeout);
|
||||||
|
|
||||||
|
/* The current implementation only supports adding one instance and only
|
||||||
|
* a subset of the specified flags.
|
||||||
|
*/
|
||||||
|
supported_flags = get_supported_adv_flags(hdev);
|
||||||
|
if (cp->instance != 0x01 || (flags & ~supported_flags))
|
||||||
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
if (timeout && !hdev_is_powered(hdev)) {
|
||||||
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_STATUS_REJECTED);
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending_find(MGMT_OP_ADD_ADVERTISING, hdev) ||
|
||||||
|
pending_find(MGMT_OP_REMOVE_ADVERTISING, hdev) ||
|
||||||
|
pending_find(MGMT_OP_SET_LE, hdev)) {
|
||||||
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_STATUS_BUSY);
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlv_data_is_valid(hdev, flags, cp->data, cp->adv_data_len, true) ||
|
||||||
|
!tlv_data_is_valid(hdev, flags, cp->data + cp->adv_data_len,
|
||||||
|
cp->scan_rsp_len, false)) {
|
||||||
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&hdev->adv_instance.timeout_exp, adv_timeout_expired);
|
||||||
|
|
||||||
|
hdev->adv_instance.flags = flags;
|
||||||
|
hdev->adv_instance.adv_data_len = cp->adv_data_len;
|
||||||
|
hdev->adv_instance.scan_rsp_len = cp->scan_rsp_len;
|
||||||
|
|
||||||
|
if (cp->adv_data_len)
|
||||||
|
memcpy(hdev->adv_instance.adv_data, cp->data, cp->adv_data_len);
|
||||||
|
|
||||||
|
if (cp->scan_rsp_len)
|
||||||
|
memcpy(hdev->adv_instance.scan_rsp_data,
|
||||||
|
cp->data + cp->adv_data_len, cp->scan_rsp_len);
|
||||||
|
|
||||||
|
if (hdev->adv_instance.timeout)
|
||||||
|
cancel_delayed_work(&hdev->adv_instance.timeout_exp);
|
||||||
|
|
||||||
|
hdev->adv_instance.timeout = timeout;
|
||||||
|
|
||||||
|
if (timeout)
|
||||||
|
queue_delayed_work(hdev->workqueue,
|
||||||
|
&hdev->adv_instance.timeout_exp,
|
||||||
|
msecs_to_jiffies(timeout * 1000));
|
||||||
|
|
||||||
|
if (!hci_dev_test_and_set_flag(hdev, HCI_ADVERTISING_INSTANCE))
|
||||||
|
advertising_added(sk, hdev, 1);
|
||||||
|
|
||||||
|
/* If the HCI_ADVERTISING flag is set or the device isn't powered then
|
||||||
|
* we have no HCI communication to make. Simply return.
|
||||||
|
*/
|
||||||
|
if (!hdev_is_powered(hdev) ||
|
||||||
|
hci_dev_test_flag(hdev, HCI_ADVERTISING)) {
|
||||||
|
rp.instance = 0x01;
|
||||||
|
err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
|
||||||
|
MGMT_STATUS_SUCCESS, &rp, sizeof(rp));
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're good to go, update advertising data, parameters, and start
|
||||||
|
* advertising.
|
||||||
|
*/
|
||||||
|
cmd = mgmt_pending_add(sk, MGMT_OP_ADD_ADVERTISING, hdev, data,
|
||||||
|
data_len);
|
||||||
|
if (!cmd) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
|
||||||
|
update_adv_data(&req);
|
||||||
|
update_scan_rsp_data(&req);
|
||||||
|
enable_advertising(&req);
|
||||||
|
|
||||||
|
err = hci_req_run(&req, add_advertising_complete);
|
||||||
|
if (err < 0)
|
||||||
|
mgmt_pending_remove(cmd);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_advertising_complete(struct hci_dev *hdev, u8 status,
|
||||||
|
u16 opcode)
|
||||||
|
{
|
||||||
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
struct mgmt_rp_remove_advertising rp;
|
||||||
|
|
||||||
|
BT_DBG("status %d", status);
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
/* A failure status here only means that we failed to disable
|
||||||
|
* advertising. Otherwise, the advertising instance has been removed,
|
||||||
|
* so report success.
|
||||||
|
*/
|
||||||
|
cmd = pending_find(MGMT_OP_REMOVE_ADVERTISING, hdev);
|
||||||
|
if (!cmd)
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
|
rp.instance = 1;
|
||||||
|
|
||||||
|
mgmt_cmd_complete(cmd->sk, cmd->index, cmd->opcode, MGMT_STATUS_SUCCESS,
|
||||||
|
&rp, sizeof(rp));
|
||||||
|
mgmt_pending_remove(cmd);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
|
||||||
|
void *data, u16 data_len)
|
||||||
|
{
|
||||||
|
struct mgmt_cp_remove_advertising *cp = data;
|
||||||
|
struct mgmt_rp_remove_advertising rp;
|
||||||
|
int err;
|
||||||
|
struct mgmt_pending_cmd *cmd;
|
||||||
|
struct hci_request req;
|
||||||
|
|
||||||
|
BT_DBG("%s", hdev->name);
|
||||||
|
|
||||||
|
/* The current implementation only allows modifying instance no 1. A
|
||||||
|
* value of 0 indicates that all instances should be cleared.
|
||||||
|
*/
|
||||||
|
if (cp->instance > 1)
|
||||||
|
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING,
|
||||||
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
|
||||||
|
hci_dev_lock(hdev);
|
||||||
|
|
||||||
|
if (pending_find(MGMT_OP_ADD_ADVERTISING, hdev) ||
|
||||||
|
pending_find(MGMT_OP_REMOVE_ADVERTISING, hdev) ||
|
||||||
|
pending_find(MGMT_OP_SET_LE, hdev)) {
|
||||||
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING,
|
||||||
|
MGMT_STATUS_BUSY);
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) {
|
||||||
|
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING,
|
||||||
|
MGMT_STATUS_INVALID_PARAMS);
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdev->adv_instance.timeout)
|
||||||
|
cancel_delayed_work(&hdev->adv_instance.timeout_exp);
|
||||||
|
|
||||||
|
memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
|
||||||
|
|
||||||
|
advertising_removed(sk, hdev, 1);
|
||||||
|
|
||||||
|
hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
|
||||||
|
|
||||||
|
/* If the HCI_ADVERTISING flag is set or the device isn't powered then
|
||||||
|
* we have no HCI communication to make. Simply return.
|
||||||
|
*/
|
||||||
|
if (!hdev_is_powered(hdev) ||
|
||||||
|
hci_dev_test_flag(hdev, HCI_ADVERTISING)) {
|
||||||
|
rp.instance = 1;
|
||||||
|
err = mgmt_cmd_complete(sk, hdev->id,
|
||||||
|
MGMT_OP_REMOVE_ADVERTISING,
|
||||||
|
MGMT_STATUS_SUCCESS, &rp, sizeof(rp));
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_ADVERTISING, hdev, data,
|
||||||
|
data_len);
|
||||||
|
if (!cmd) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
hci_req_init(&req, hdev);
|
||||||
|
disable_advertising(&req);
|
||||||
|
|
||||||
|
err = hci_req_run(&req, remove_advertising_complete);
|
||||||
|
if (err < 0)
|
||||||
|
mgmt_pending_remove(cmd);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct hci_mgmt_handler mgmt_handlers[] = {
|
static const struct hci_mgmt_handler mgmt_handlers[] = {
|
||||||
{ NULL }, /* 0x0000 (no command) */
|
{ NULL }, /* 0x0000 (no command) */
|
||||||
{ read_version, MGMT_READ_VERSION_SIZE,
|
{ read_version, MGMT_READ_VERSION_SIZE,
|
||||||
|
@ -6399,6 +6983,9 @@ static const struct hci_mgmt_handler mgmt_handlers[] = {
|
||||||
HCI_MGMT_NO_HDEV |
|
HCI_MGMT_NO_HDEV |
|
||||||
HCI_MGMT_UNTRUSTED },
|
HCI_MGMT_UNTRUSTED },
|
||||||
{ read_adv_features, MGMT_READ_ADV_FEATURES_SIZE },
|
{ read_adv_features, MGMT_READ_ADV_FEATURES_SIZE },
|
||||||
|
{ add_advertising, MGMT_ADD_ADVERTISING_SIZE,
|
||||||
|
HCI_MGMT_VAR_LEN },
|
||||||
|
{ remove_advertising, MGMT_REMOVE_ADVERTISING_SIZE },
|
||||||
};
|
};
|
||||||
|
|
||||||
void mgmt_index_added(struct hci_dev *hdev)
|
void mgmt_index_added(struct hci_dev *hdev)
|
||||||
|
@ -6570,7 +7157,8 @@ static int powered_update_hci(struct hci_dev *hdev)
|
||||||
update_scan_rsp_data(&req);
|
update_scan_rsp_data(&req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
|
||||||
|
hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
|
||||||
enable_advertising(&req);
|
enable_advertising(&req);
|
||||||
|
|
||||||
restart_le_actions(&req);
|
restart_le_actions(&req);
|
||||||
|
@ -6682,7 +7270,13 @@ void mgmt_discoverable_timeout(struct hci_dev *hdev)
|
||||||
sizeof(scan), &scan);
|
sizeof(scan), &scan);
|
||||||
}
|
}
|
||||||
update_class(&req);
|
update_class(&req);
|
||||||
update_adv_data(&req);
|
|
||||||
|
/* Advertising instances don't use the global discoverable setting, so
|
||||||
|
* only update AD if advertising was enabled using Set Advertising.
|
||||||
|
*/
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
||||||
|
update_adv_data(&req);
|
||||||
|
|
||||||
hci_req_run(&req, NULL);
|
hci_req_run(&req, NULL);
|
||||||
|
|
||||||
hdev->discov_timeout = 0;
|
hdev->discov_timeout = 0;
|
||||||
|
@ -7583,7 +8177,8 @@ void mgmt_reenable_advertising(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
struct hci_request req;
|
struct hci_request req;
|
||||||
|
|
||||||
if (!hci_dev_test_flag(hdev, HCI_ADVERTISING))
|
if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) &&
|
||||||
|
!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
hci_req_init(&req, hdev);
|
hci_req_init(&req, hdev);
|
||||||
|
|
|
@ -174,24 +174,16 @@ ieee802154_check_mac_settings(struct ieee802154_local *local,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local->hw.flags & IEEE802154_HW_AFILT) {
|
if (local->hw.flags & IEEE802154_HW_AFILT) {
|
||||||
if (wpan_dev->pan_id != nwpan_dev->pan_id)
|
if (wpan_dev->pan_id != nwpan_dev->pan_id ||
|
||||||
return -EBUSY;
|
wpan_dev->short_addr != nwpan_dev->short_addr ||
|
||||||
|
wpan_dev->extended_addr != nwpan_dev->extended_addr)
|
||||||
if (wpan_dev->short_addr != nwpan_dev->short_addr)
|
|
||||||
return -EBUSY;
|
|
||||||
|
|
||||||
if (wpan_dev->extended_addr != nwpan_dev->extended_addr)
|
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local->hw.flags & IEEE802154_HW_CSMA_PARAMS) {
|
if (local->hw.flags & IEEE802154_HW_CSMA_PARAMS) {
|
||||||
if (wpan_dev->min_be != nwpan_dev->min_be)
|
if (wpan_dev->min_be != nwpan_dev->min_be ||
|
||||||
return -EBUSY;
|
wpan_dev->max_be != nwpan_dev->max_be ||
|
||||||
|
wpan_dev->csma_retries != nwpan_dev->csma_retries)
|
||||||
if (wpan_dev->max_be != nwpan_dev->max_be)
|
|
||||||
return -EBUSY;
|
|
||||||
|
|
||||||
if (wpan_dev->csma_retries != nwpan_dev->csma_retries)
|
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue