diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-21 04:04:47 (GMT) |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-21 04:04:47 (GMT) |
commit | 3b59bf081622b6446db77ad06c93fe23677bc533 (patch) | |
tree | 3f4bb5a27c90cc86994a1f6d3c53fbf9208003cb /net/bluetooth/mgmt.c | |
parent | e45836fafe157df137a837093037f741ad8f4c90 (diff) | |
parent | bbdb32cb5b73597386913d052165423b9d736145 (diff) | |
download | linux-fsl-qoriq-3b59bf081622b6446db77ad06c93fe23677bc533.tar.xz |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking merge from David Miller:
"1) Move ixgbe driver over to purely page based buffering on receive.
From Alexander Duyck.
2) Add receive packet steering support to e1000e, from Bruce Allan.
3) Convert TCP MD5 support over to RCU, from Eric Dumazet.
4) Reduce cpu usage in handling out-of-order TCP packets on modern
systems, also from Eric Dumazet.
5) Support the IP{,V6}_UNICAST_IF socket options, making the wine
folks happy, from Erich Hoover.
6) Support VLAN trunking from guests in hyperv driver, from Haiyang
Zhang.
7) Support byte-queue-limtis in r8169, from Igor Maravic.
8) Outline code intended for IP_RECVTOS in IP_PKTOPTIONS existed but
was never properly implemented, Jiri Benc fixed that.
9) 64-bit statistics support in r8169 and 8139too, from Junchang Wang.
10) Support kernel side dump filtering by ctmark in netfilter
ctnetlink, from Pablo Neira Ayuso.
11) Support byte-queue-limits in gianfar driver, from Paul Gortmaker.
12) Add new peek socket options to assist with socket migration, from
Pavel Emelyanov.
13) Add sch_plug packet scheduler whose queue is controlled by
userland daemons using explicit freeze and release commands. From
Shriram Rajagopalan.
14) Fix FCOE checksum offload handling on transmit, from Yi Zou."
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1846 commits)
Fix pppol2tp getsockname()
Remove printk from rds_sendmsg
ipv6: fix incorrent ipv6 ipsec packet fragment
cpsw: Hook up default ndo_change_mtu.
net: qmi_wwan: fix build error due to cdc-wdm dependecy
netdev: driver: ethernet: Add TI CPSW driver
netdev: driver: ethernet: add cpsw address lookup engine support
phy: add am79c874 PHY support
mlx4_core: fix race on comm channel
bonding: send igmp report for its master
fs_enet: Add MPC5125 FEC support and PHY interface selection
net: bpf_jit: fix BPF_S_LDX_B_MSH compilation
net: update the usage of CHECKSUM_UNNECESSARY
fcoe: use CHECKSUM_UNNECESSARY instead of CHECKSUM_PARTIAL on tx
net: do not do gso for CHECKSUM_UNNECESSARY in netif_needs_gso
ixgbe: Fix issues with SR-IOV loopback when flow control is disabled
net/hyperv: Fix the code handling tx busy
ixgbe: fix namespace issues when FCoE/DCB is not enabled
rtlwifi: Remove unused ETH_ADDR_LEN defines
igbvf: Use ETH_ALEN
...
Fix up fairly trivial conflicts in drivers/isdn/gigaset/interface.c and
drivers/net/usb/{Kconfig,qmi_wwan.c} as per David.
Diffstat (limited to 'net/bluetooth/mgmt.c')
-rw-r--r-- | net/bluetooth/mgmt.c | 2647 |
1 files changed, 1717 insertions, 930 deletions
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index bc8e59d..7fcff88 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1,6 +1,8 @@ /* BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2010 Nokia Corporation + Copyright (C) 2011-2012 Intel Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as @@ -32,12 +34,92 @@ #include <net/bluetooth/mgmt.h> #include <net/bluetooth/smp.h> -#define MGMT_VERSION 0 -#define MGMT_REVISION 1 +bool enable_hs; +bool enable_le; + +#define MGMT_VERSION 1 +#define MGMT_REVISION 0 + +static const u16 mgmt_commands[] = { + MGMT_OP_READ_INDEX_LIST, + MGMT_OP_READ_INFO, + MGMT_OP_SET_POWERED, + MGMT_OP_SET_DISCOVERABLE, + MGMT_OP_SET_CONNECTABLE, + MGMT_OP_SET_FAST_CONNECTABLE, + MGMT_OP_SET_PAIRABLE, + MGMT_OP_SET_LINK_SECURITY, + MGMT_OP_SET_SSP, + MGMT_OP_SET_HS, + MGMT_OP_SET_LE, + MGMT_OP_SET_DEV_CLASS, + MGMT_OP_SET_LOCAL_NAME, + MGMT_OP_ADD_UUID, + MGMT_OP_REMOVE_UUID, + MGMT_OP_LOAD_LINK_KEYS, + MGMT_OP_LOAD_LONG_TERM_KEYS, + MGMT_OP_DISCONNECT, + MGMT_OP_GET_CONNECTIONS, + MGMT_OP_PIN_CODE_REPLY, + MGMT_OP_PIN_CODE_NEG_REPLY, + MGMT_OP_SET_IO_CAPABILITY, + MGMT_OP_PAIR_DEVICE, + MGMT_OP_CANCEL_PAIR_DEVICE, + MGMT_OP_UNPAIR_DEVICE, + MGMT_OP_USER_CONFIRM_REPLY, + MGMT_OP_USER_CONFIRM_NEG_REPLY, + MGMT_OP_USER_PASSKEY_REPLY, + MGMT_OP_USER_PASSKEY_NEG_REPLY, + MGMT_OP_READ_LOCAL_OOB_DATA, + MGMT_OP_ADD_REMOTE_OOB_DATA, + MGMT_OP_REMOVE_REMOTE_OOB_DATA, + MGMT_OP_START_DISCOVERY, + MGMT_OP_STOP_DISCOVERY, + MGMT_OP_CONFIRM_NAME, + MGMT_OP_BLOCK_DEVICE, + MGMT_OP_UNBLOCK_DEVICE, +}; + +static const u16 mgmt_events[] = { + MGMT_EV_CONTROLLER_ERROR, + 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_NEW_LINK_KEY, + MGMT_EV_NEW_LONG_TERM_KEY, + MGMT_EV_DEVICE_CONNECTED, + MGMT_EV_DEVICE_DISCONNECTED, + MGMT_EV_CONNECT_FAILED, + MGMT_EV_PIN_CODE_REQUEST, + MGMT_EV_USER_CONFIRM_REQUEST, + MGMT_EV_USER_PASSKEY_REQUEST, + MGMT_EV_AUTH_FAILED, + MGMT_EV_DEVICE_FOUND, + MGMT_EV_DISCOVERING, + MGMT_EV_DEVICE_BLOCKED, + MGMT_EV_DEVICE_UNBLOCKED, + MGMT_EV_DEVICE_UNPAIRED, +}; + +/* + * These LE scan and inquiry parameters were chosen according to LE General + * Discovery Procedure specification. + */ +#define LE_SCAN_TYPE 0x01 +#define LE_SCAN_WIN 0x12 +#define LE_SCAN_INT 0x12 +#define LE_SCAN_TIMEOUT_LE_ONLY 10240 /* TGAP(gen_disc_scan_min) */ +#define LE_SCAN_TIMEOUT_BREDR_LE 5120 /* TGAP(100)/2 */ + +#define INQUIRY_LEN_BREDR 0x08 /* TGAP(100) */ +#define INQUIRY_LEN_BREDR_LE 0x04 /* TGAP(100)/2 */ -#define INQUIRY_LEN_BREDR 0x08 /* TGAP(100) */ +#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000) -#define SERVICE_CACHE_TIMEOUT (5 * 1000) +#define hdev_is_powered(hdev) (test_bit(HCI_UP, &hdev->flags) && \ + !test_bit(HCI_AUTO_OFF, &hdev->dev_flags)) struct pending_cmd { struct list_head list; @@ -151,8 +233,8 @@ static int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) return err; } -static int cmd_complete(struct sock *sk, u16 index, u16 cmd, void *rp, - size_t rp_len) +static int cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, + void *rp, size_t rp_len) { struct sk_buff *skb; struct mgmt_hdr *hdr; @@ -173,6 +255,7 @@ static int cmd_complete(struct sock *sk, u16 index, u16 cmd, void *rp, ev = (void *) skb_put(skb, sizeof(*ev) + rp_len); put_unaligned_le16(cmd, &ev->opcode); + ev->status = status; if (rp) memcpy(ev->data, rp, rp_len); @@ -181,10 +264,11 @@ static int cmd_complete(struct sock *sk, u16 index, u16 cmd, void *rp, if (err < 0) kfree_skb(skb); - return err;; + return err; } -static int read_version(struct sock *sk) +static int read_version(struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len) { struct mgmt_rp_read_version rp; @@ -193,11 +277,46 @@ static int read_version(struct sock *sk) rp.version = MGMT_VERSION; put_unaligned_le16(MGMT_REVISION, &rp.revision); - return cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, &rp, - sizeof(rp)); + return cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, 0, &rp, + sizeof(rp)); +} + +static int read_commands(struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len) +{ + struct mgmt_rp_read_commands *rp; + u16 num_commands = ARRAY_SIZE(mgmt_commands); + u16 num_events = ARRAY_SIZE(mgmt_events); + u16 *opcode; + size_t rp_size; + int i, err; + + BT_DBG("sock %p", sk); + + rp_size = sizeof(*rp) + ((num_commands + num_events) * sizeof(u16)); + + rp = kmalloc(rp_size, GFP_KERNEL); + if (!rp) + return -ENOMEM; + + put_unaligned_le16(num_commands, &rp->num_commands); + put_unaligned_le16(num_events, &rp->num_events); + + for (i = 0, opcode = rp->opcodes; i < num_commands; i++, opcode++) + put_unaligned_le16(mgmt_commands[i], opcode); + + for (i = 0; i < num_events; i++, opcode++) + put_unaligned_le16(mgmt_events[i], opcode); + + err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_COMMANDS, 0, rp, + rp_size); + kfree(rp); + + return err; } -static int read_index_list(struct sock *sk) +static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len) { struct mgmt_rp_read_index_list *rp; struct list_head *p; @@ -226,10 +345,7 @@ static int read_index_list(struct sock *sk) i = 0; list_for_each_entry(d, &hci_dev_list, list) { - if (test_and_clear_bit(HCI_AUTO_OFF, &d->flags)) - cancel_delayed_work(&d->power_off); - - if (test_bit(HCI_SETUP, &d->flags)) + if (test_bit(HCI_SETUP, &d->dev_flags)) continue; put_unaligned_le16(d->id, &rp->index[i++]); @@ -238,8 +354,8 @@ static int read_index_list(struct sock *sk) read_unlock(&hci_dev_list_lock); - err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_INDEX_LIST, rp, - rp_len); + err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_INDEX_LIST, 0, rp, + rp_len); kfree(rp); @@ -264,8 +380,13 @@ static u32 get_supported_settings(struct hci_dev *hdev) settings |= MGMT_SETTING_LINK_SECURITY; } - if (hdev->features[4] & LMP_LE) - settings |= MGMT_SETTING_LE; + if (enable_hs) + settings |= MGMT_SETTING_HS; + + if (enable_le) { + if (hdev->features[4] & LMP_LE) + settings |= MGMT_SETTING_LE; + } return settings; } @@ -274,47 +395,36 @@ static u32 get_current_settings(struct hci_dev *hdev) { u32 settings = 0; - if (test_bit(HCI_UP, &hdev->flags)) + if (hdev_is_powered(hdev)) settings |= MGMT_SETTING_POWERED; - else - return settings; - if (test_bit(HCI_PSCAN, &hdev->flags)) + if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) settings |= MGMT_SETTING_CONNECTABLE; - if (test_bit(HCI_ISCAN, &hdev->flags)) + if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) settings |= MGMT_SETTING_DISCOVERABLE; - if (test_bit(HCI_PAIRABLE, &hdev->flags)) + if (test_bit(HCI_PAIRABLE, &hdev->dev_flags)) settings |= MGMT_SETTING_PAIRABLE; if (!(hdev->features[4] & LMP_NO_BREDR)) settings |= MGMT_SETTING_BREDR; - if (hdev->host_features[0] & LMP_HOST_LE) + if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) settings |= MGMT_SETTING_LE; - if (test_bit(HCI_AUTH, &hdev->flags)) + if (test_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) settings |= MGMT_SETTING_LINK_SECURITY; - if (hdev->ssp_mode > 0) + if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) settings |= MGMT_SETTING_SSP; + if (test_bit(HCI_HS_ENABLED, &hdev->dev_flags)) + settings |= MGMT_SETTING_HS; + return settings; } -#define EIR_FLAGS 0x01 /* flags */ -#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ -#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ -#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ -#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ -#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ -#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ -#define EIR_NAME_SHORT 0x08 /* shortened local name */ -#define EIR_NAME_COMPLETE 0x09 /* complete local name */ -#define EIR_TX_POWER 0x0A /* transmit power level */ -#define EIR_DEVICE_ID 0x10 /* device ID */ - #define PNP_INFO_SVCLASS_ID 0x1200 static u8 bluetooth_base_uuid[] = { @@ -425,13 +535,16 @@ static int update_eir(struct hci_dev *hdev) { struct hci_cp_write_eir cp; + if (!hdev_is_powered(hdev)) + return 0; + if (!(hdev->features[6] & LMP_EXT_INQ)) return 0; - if (hdev->ssp_mode == 0) + if (!test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) return 0; - if (test_bit(HCI_SERVICE_CACHE, &hdev->flags)) + if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) return 0; memset(&cp, 0, sizeof(cp)); @@ -460,10 +573,14 @@ static u8 get_service_classes(struct hci_dev *hdev) static int update_class(struct hci_dev *hdev) { u8 cod[3]; + int err; BT_DBG("%s", hdev->name); - if (test_bit(HCI_SERVICE_CACHE, &hdev->flags)) + if (!hdev_is_powered(hdev)) + return 0; + + if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) return 0; cod[0] = hdev->minor_class; @@ -473,15 +590,19 @@ static int update_class(struct hci_dev *hdev) if (memcmp(cod, hdev->dev_class, 3) == 0) return 0; - return hci_send_cmd(hdev, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); + err = hci_send_cmd(hdev, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); + if (err == 0) + set_bit(HCI_PENDING_CLASS, &hdev->dev_flags); + + return err; } static void service_cache_off(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, - service_cache.work); + service_cache.work); - if (!test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->flags)) + if (!test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) return; hci_dev_lock(hdev); @@ -492,36 +613,30 @@ static void service_cache_off(struct work_struct *work) hci_dev_unlock(hdev); } -static void mgmt_init_hdev(struct hci_dev *hdev) +static void mgmt_init_hdev(struct sock *sk, struct hci_dev *hdev) { - if (!test_and_set_bit(HCI_MGMT, &hdev->flags)) - INIT_DELAYED_WORK(&hdev->service_cache, service_cache_off); + if (test_and_set_bit(HCI_MGMT, &hdev->dev_flags)) + return; + + INIT_DELAYED_WORK(&hdev->service_cache, service_cache_off); - if (!test_and_set_bit(HCI_SERVICE_CACHE, &hdev->flags)) - schedule_delayed_work(&hdev->service_cache, - msecs_to_jiffies(SERVICE_CACHE_TIMEOUT)); + /* Non-mgmt controlled devices get this bit set + * implicitly so that pairing works for them, however + * for mgmt we require user-space to explicitly enable + * it + */ + clear_bit(HCI_PAIRABLE, &hdev->dev_flags); } -static int read_controller_info(struct sock *sk, u16 index) +static int read_controller_info(struct sock *sk, struct hci_dev *hdev, + void *data, u16 data_len) { struct mgmt_rp_read_info rp; - struct hci_dev *hdev; - - BT_DBG("sock %p hci%u", sk, index); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_READ_INFO, - MGMT_STATUS_INVALID_PARAMS); - if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->flags)) - cancel_delayed_work_sync(&hdev->power_off); + BT_DBG("sock %p %s", sk, hdev->name); hci_dev_lock(hdev); - if (test_and_clear_bit(HCI_PI_MGMT_INIT, &hci_pi(sk)->flags)) - mgmt_init_hdev(hdev); - memset(&rp, 0, sizeof(rp)); bacpy(&rp.bdaddr, &hdev->bdaddr); @@ -536,11 +651,12 @@ static int read_controller_info(struct sock *sk, u16 index) memcpy(rp.dev_class, hdev->dev_class, 3); memcpy(rp.name, hdev->dev_name, sizeof(hdev->dev_name)); + memcpy(rp.short_name, hdev->short_name, sizeof(hdev->short_name)); hci_dev_unlock(hdev); - hci_dev_put(hdev); - return cmd_complete(sk, index, MGMT_OP_READ_INFO, &rp, sizeof(rp)); + return cmd_complete(sk, hdev->id, MGMT_OP_READ_INFO, 0, &rp, + sizeof(rp)); } static void mgmt_pending_free(struct pending_cmd *cmd) @@ -551,8 +667,8 @@ static void mgmt_pending_free(struct pending_cmd *cmd) } static struct pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode, - struct hci_dev *hdev, - void *data, u16 len) + struct hci_dev *hdev, void *data, + u16 len) { struct pending_cmd *cmd; @@ -581,8 +697,8 @@ static struct pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode, } static void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev, - void (*cb)(struct pending_cmd *cmd, void *data), - void *data) + void (*cb)(struct pending_cmd *cmd, void *data), + void *data) { struct list_head *p, *n; @@ -620,40 +736,39 @@ static int send_settings_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev) { __le32 settings = cpu_to_le32(get_current_settings(hdev)); - return cmd_complete(sk, hdev->id, opcode, &settings, sizeof(settings)); + return cmd_complete(sk, hdev->id, opcode, 0, &settings, + sizeof(settings)); } -static int set_powered(struct sock *sk, u16 index, unsigned char *data, u16 len) +static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_mode *cp; - struct hci_dev *hdev; + struct mgmt_mode *cp = data; struct pending_cmd *cmd; - int err, up; - - cp = (void *) data; + int err; - BT_DBG("request for hci%u", index); + BT_DBG("request for %s", hdev->name); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_POWERED, - MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_POWERED, - MGMT_STATUS_INVALID_PARAMS); + if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) { + cancel_delayed_work(&hdev->power_off); - hci_dev_lock(hdev); + if (cp->val) { + err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev); + mgmt_powered(hdev, 1); + goto failed; + } + } - up = test_bit(HCI_UP, &hdev->flags); - if ((cp->val && up) || (!cp->val && !up)) { + if (!!cp->val == hdev_is_powered(hdev)) { err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev); goto failed; } if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) { - err = cmd_status(sk, index, MGMT_OP_SET_POWERED, - MGMT_STATUS_BUSY); + err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED, + MGMT_STATUS_BUSY); goto failed; } @@ -672,49 +787,115 @@ static int set_powered(struct sock *sk, u16 index, unsigned char *data, u16 len) failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); return err; } -static int set_discoverable(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, u16 data_len, + struct sock *skip_sk) { - struct mgmt_cp_set_discoverable *cp; - struct hci_dev *hdev; + struct sk_buff *skb; + struct mgmt_hdr *hdr; + + skb = alloc_skb(sizeof(*hdr) + data_len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + hdr = (void *) skb_put(skb, sizeof(*hdr)); + hdr->opcode = cpu_to_le16(event); + if (hdev) + hdr->index = cpu_to_le16(hdev->id); + else + hdr->index = cpu_to_le16(MGMT_INDEX_NONE); + hdr->len = cpu_to_le16(data_len); + + if (data) + memcpy(skb_put(skb, data_len), data, data_len); + + /* Time stamp */ + __net_timestamp(skb); + + hci_send_to_control(skb, skip_sk); + kfree_skb(skb); + + return 0; +} + +static int new_settings(struct hci_dev *hdev, struct sock *skip) +{ + __le32 ev; + + ev = cpu_to_le32(get_current_settings(hdev)); + + return mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), skip); +} + +static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_set_discoverable *cp = data; struct pending_cmd *cmd; + u16 timeout; u8 scan; int err; - cp = (void *) data; - - BT_DBG("request for hci%u", index); + BT_DBG("request for %s", hdev->name); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_INVALID_PARAMS); + timeout = get_unaligned_le16(&cp->timeout); + if (!cp->val && timeout > 0) + return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, + MGMT_STATUS_INVALID_PARAMS); hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev) && timeout > 0) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, + MGMT_STATUS_NOT_POWERED); goto failed; } if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev) || mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, hdev)) { - err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_BUSY); + err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, + MGMT_STATUS_BUSY); goto failed; } - if (cp->val == test_bit(HCI_ISCAN, &hdev->flags) && - test_bit(HCI_PSCAN, &hdev->flags)) { + if (!test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, + MGMT_STATUS_REJECTED); + goto failed; + } + + if (!hdev_is_powered(hdev)) { + bool changed = false; + + if (!!cp->val != test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { + change_bit(HCI_DISCOVERABLE, &hdev->dev_flags); + changed = true; + } + + err = send_settings_rsp(sk, MGMT_OP_SET_DISCOVERABLE, hdev); + if (err < 0) + goto failed; + + if (changed) + err = new_settings(hdev, sk); + + goto failed; + } + + if (!!cp->val == test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { + if (hdev->discov_timeout > 0) { + cancel_delayed_work(&hdev->discov_off); + hdev->discov_timeout = 0; + } + + if (cp->val && timeout > 0) { + hdev->discov_timeout = timeout; + queue_delayed_work(hdev->workqueue, &hdev->discov_off, + msecs_to_jiffies(hdev->discov_timeout * 1000)); + } + err = send_settings_rsp(sk, MGMT_OP_SET_DISCOVERABLE, hdev); goto failed; } @@ -737,53 +918,56 @@ static int set_discoverable(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); if (cp->val) - hdev->discov_timeout = get_unaligned_le16(&cp->timeout); + hdev->discov_timeout = timeout; failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int set_connectable(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_mode *cp; - struct hci_dev *hdev; + struct mgmt_mode *cp = data; struct pending_cmd *cmd; u8 scan; int err; - cp = (void *) data; + BT_DBG("request for %s", hdev->name); - BT_DBG("request for hci%u", index); + hci_dev_lock(hdev); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!hdev_is_powered(hdev)) { + bool changed = false; - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!!cp->val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) + changed = true; - hci_dev_lock(hdev); + if (cp->val) { + set_bit(HCI_CONNECTABLE, &hdev->dev_flags); + } else { + clear_bit(HCI_CONNECTABLE, &hdev->dev_flags); + clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags); + } + + err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev); + if (err < 0) + goto failed; + + if (changed) + err = new_settings(hdev, sk); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, - MGMT_STATUS_NOT_POWERED); goto failed; } if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev) || mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, hdev)) { - err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, - MGMT_STATUS_BUSY); + err = cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE, + MGMT_STATUS_BUSY); goto failed; } - if (cp->val == test_bit(HCI_PSCAN, &hdev->flags)) { + if (!!cp->val == test_bit(HCI_PSCAN, &hdev->flags)) { err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev); goto failed; } @@ -794,116 +978,282 @@ static int set_connectable(struct sock *sk, u16 index, unsigned char *data, goto failed; } - if (cp->val) + if (cp->val) { scan = SCAN_PAGE; - else + } else { scan = 0; + if (test_bit(HCI_ISCAN, &hdev->flags) && + hdev->discov_timeout > 0) + cancel_delayed_work(&hdev->discov_off); + } + err = hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); if (err < 0) mgmt_pending_remove(cmd); failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, - u16 data_len, struct sock *skip_sk) +static int set_pairable(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct sk_buff *skb; - struct mgmt_hdr *hdr; + struct mgmt_mode *cp = data; + int err; - skb = alloc_skb(sizeof(*hdr) + data_len, GFP_ATOMIC); - if (!skb) - return -ENOMEM; + BT_DBG("request for %s", hdev->name); - bt_cb(skb)->channel = HCI_CHANNEL_CONTROL; + hci_dev_lock(hdev); - hdr = (void *) skb_put(skb, sizeof(*hdr)); - hdr->opcode = cpu_to_le16(event); - if (hdev) - hdr->index = cpu_to_le16(hdev->id); + if (cp->val) + set_bit(HCI_PAIRABLE, &hdev->dev_flags); else - hdr->index = cpu_to_le16(MGMT_INDEX_NONE); - hdr->len = cpu_to_le16(data_len); + clear_bit(HCI_PAIRABLE, &hdev->dev_flags); - if (data) - memcpy(skb_put(skb, data_len), data, data_len); + err = send_settings_rsp(sk, MGMT_OP_SET_PAIRABLE, hdev); + if (err < 0) + goto failed; - hci_send_to_sock(NULL, skb, skip_sk); - kfree_skb(skb); + err = new_settings(hdev, sk); - return 0; +failed: + hci_dev_unlock(hdev); + return err; } -static int set_pairable(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int set_link_security(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_mode *cp; - struct hci_dev *hdev; - __le32 ev; + struct mgmt_mode *cp = data; + struct pending_cmd *cmd; + u8 val; int err; - cp = (void *) data; + BT_DBG("request for %s", hdev->name); - BT_DBG("request for hci%u", index); + hci_dev_lock(hdev); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_PAIRABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!hdev_is_powered(hdev)) { + bool changed = false; - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_PAIRABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!!cp->val != test_bit(HCI_LINK_SECURITY, + &hdev->dev_flags)) { + change_bit(HCI_LINK_SECURITY, &hdev->dev_flags); + changed = true; + } - hci_dev_lock(hdev); + err = send_settings_rsp(sk, MGMT_OP_SET_LINK_SECURITY, hdev); + if (err < 0) + goto failed; - if (cp->val) - set_bit(HCI_PAIRABLE, &hdev->flags); - else - clear_bit(HCI_PAIRABLE, &hdev->flags); + if (changed) + err = new_settings(hdev, sk); - err = send_settings_rsp(sk, MGMT_OP_SET_PAIRABLE, hdev); - if (err < 0) goto failed; + } - ev = cpu_to_le32(get_current_settings(hdev)); + if (mgmt_pending_find(MGMT_OP_SET_LINK_SECURITY, hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY, + MGMT_STATUS_BUSY); + goto failed; + } + + val = !!cp->val; + + if (test_bit(HCI_AUTH, &hdev->flags) == val) { + err = send_settings_rsp(sk, MGMT_OP_SET_LINK_SECURITY, hdev); + goto failed; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_SET_LINK_SECURITY, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } - err = mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), sk); + err = hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE, sizeof(val), &val); + if (err < 0) { + mgmt_pending_remove(cmd); + goto failed; + } failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); + return err; +} + +static int set_ssp(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) +{ + struct mgmt_mode *cp = data; + struct pending_cmd *cmd; + u8 val; + int err; + + BT_DBG("request for %s", hdev->name); + + hci_dev_lock(hdev); + + if (!(hdev->features[6] & LMP_SIMPLE_PAIR)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, + MGMT_STATUS_NOT_SUPPORTED); + goto failed; + } + + val = !!cp->val; + + if (!hdev_is_powered(hdev)) { + bool changed = false; + + if (val != test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) { + change_bit(HCI_SSP_ENABLED, &hdev->dev_flags); + changed = true; + } + + err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev); + if (err < 0) + goto failed; + + if (changed) + err = new_settings(hdev, sk); + goto failed; + } + + if (mgmt_pending_find(MGMT_OP_SET_SSP, hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, + MGMT_STATUS_BUSY); + goto failed; + } + + if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags) == val) { + err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev); + goto failed; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_SET_SSP, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } + + err = hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, sizeof(val), &val); + if (err < 0) { + mgmt_pending_remove(cmd); + goto failed; + } + +failed: + hci_dev_unlock(hdev); return err; } -static int add_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) +static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { - struct mgmt_cp_add_uuid *cp; - struct hci_dev *hdev; - struct bt_uuid *uuid; + struct mgmt_mode *cp = data; + + BT_DBG("request for %s", hdev->name); + + if (!enable_hs) + return cmd_status(sk, hdev->id, MGMT_OP_SET_HS, + MGMT_STATUS_NOT_SUPPORTED); + + if (cp->val) + set_bit(HCI_HS_ENABLED, &hdev->dev_flags); + else + clear_bit(HCI_HS_ENABLED, &hdev->dev_flags); + + return send_settings_rsp(sk, MGMT_OP_SET_HS, hdev); +} + +static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) +{ + struct mgmt_mode *cp = data; + struct hci_cp_write_le_host_supported hci_cp; + struct pending_cmd *cmd; int err; + u8 val, enabled; + + BT_DBG("request for %s", hdev->name); - cp = (void *) data; + hci_dev_lock(hdev); - BT_DBG("request for hci%u", index); + if (!enable_le || !(hdev->features[4] & LMP_LE)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE, + MGMT_STATUS_NOT_SUPPORTED); + goto unlock; + } - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_ADD_UUID, - MGMT_STATUS_INVALID_PARAMS); + val = !!cp->val; + enabled = !!(hdev->host_features[0] & LMP_HOST_LE); + + if (!hdev_is_powered(hdev) || val == enabled) { + bool changed = false; + + if (val != test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) { + change_bit(HCI_LE_ENABLED, &hdev->dev_flags); + changed = true; + } - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_ADD_UUID, - MGMT_STATUS_INVALID_PARAMS); + err = send_settings_rsp(sk, MGMT_OP_SET_LE, hdev); + if (err < 0) + goto unlock; + + if (changed) + err = new_settings(hdev, sk); + + goto unlock; + } + + if (mgmt_pending_find(MGMT_OP_SET_LE, hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE, + MGMT_STATUS_BUSY); + goto unlock; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_SET_LE, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto unlock; + } + + memset(&hci_cp, 0, sizeof(hci_cp)); + + if (val) { + hci_cp.le = val; + hci_cp.simul = !!(hdev->features[6] & LMP_SIMUL_LE_BR); + } + + err = hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp), + &hci_cp); + if (err < 0) { + mgmt_pending_remove(cmd); + goto unlock; + } + +unlock: + hci_dev_unlock(hdev); + return err; +} + +static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) +{ + struct mgmt_cp_add_uuid *cp = data; + struct pending_cmd *cmd; + struct bt_uuid *uuid; + int err; + + BT_DBG("request for %s", hdev->name); hci_dev_lock(hdev); + if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_status(sk, hdev->id, MGMT_OP_ADD_UUID, + MGMT_STATUS_BUSY); + goto failed; + } + uuid = kmalloc(sizeof(*uuid), GFP_ATOMIC); if (!uuid) { err = -ENOMEM; @@ -923,41 +1273,65 @@ static int add_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) if (err < 0) goto failed; - err = cmd_complete(sk, index, MGMT_OP_ADD_UUID, NULL, 0); + if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_UUID, 0, + hdev->dev_class, 3); + goto failed; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_ADD_UUID, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) +static bool enable_service_cache(struct hci_dev *hdev) { - struct list_head *p, *n; - struct mgmt_cp_remove_uuid *cp; - struct hci_dev *hdev; - u8 bt_uuid_any[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - int err, found; + if (!hdev_is_powered(hdev)) + return false; - cp = (void *) data; + if (!test_and_set_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) { + schedule_delayed_work(&hdev->service_cache, CACHE_TIMEOUT); + return true; + } - BT_DBG("request for hci%u", index); + return false; +} - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_REMOVE_UUID, - MGMT_STATUS_INVALID_PARAMS); +static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_remove_uuid *cp = data; + struct pending_cmd *cmd; + struct list_head *p, *n; + u8 bt_uuid_any[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int err, found; - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_REMOVE_UUID, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("request for %s", hdev->name); hci_dev_lock(hdev); + if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_status(sk, hdev->id, MGMT_OP_REMOVE_UUID, + MGMT_STATUS_BUSY); + goto unlock; + } + if (memcmp(cp->uuid, bt_uuid_any, 16) == 0) { err = hci_uuids_clear(hdev); - goto unlock; + + if (enable_service_cache(hdev)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_UUID, + 0, hdev->dev_class, 3); + goto unlock; + } + + goto update_class; } found = 0; @@ -973,11 +1347,12 @@ static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) } if (found == 0) { - err = cmd_status(sk, index, MGMT_OP_REMOVE_UUID, - MGMT_STATUS_INVALID_PARAMS); + err = cmd_status(sk, hdev->id, MGMT_OP_REMOVE_UUID, + MGMT_STATUS_INVALID_PARAMS); goto unlock; } +update_class: err = update_class(hdev); if (err < 0) goto unlock; @@ -986,41 +1361,50 @@ static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) if (err < 0) goto unlock; - err = cmd_complete(sk, index, MGMT_OP_REMOVE_UUID, NULL, 0); + if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_UUID, 0, + hdev->dev_class, 3); + goto unlock; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_UUID, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto unlock; + } unlock: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int set_dev_class(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int set_dev_class(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_set_dev_class *cp; + struct mgmt_cp_set_dev_class *cp = data; + struct pending_cmd *cmd; int err; - cp = (void *) data; - - BT_DBG("request for hci%u", index); - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_DEV_CLASS, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_DEV_CLASS, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("request for %s", hdev->name); hci_dev_lock(hdev); + if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_status(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, + MGMT_STATUS_BUSY); + goto unlock; + } + hdev->major_class = cp->major; hdev->minor_class = cp->minor; - if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->flags)) { + if (!hdev_is_powered(hdev)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, 0, + hdev->dev_class, 3); + goto unlock; + } + + if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) { hci_dev_unlock(hdev); cancel_delayed_work_sync(&hdev->service_cache); hci_dev_lock(hdev); @@ -1028,30 +1412,33 @@ static int set_dev_class(struct sock *sk, u16 index, unsigned char *data, } err = update_class(hdev); + if (err < 0) + goto unlock; - if (err == 0) - err = cmd_complete(sk, index, MGMT_OP_SET_DEV_CLASS, NULL, 0); + if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, 0, + hdev->dev_class, 3); + goto unlock; + } - hci_dev_unlock(hdev); - hci_dev_put(hdev); + cmd = mgmt_pending_add(sk, MGMT_OP_SET_DEV_CLASS, hdev, data, len); + if (!cmd) { + err = -ENOMEM; + goto unlock; + } +unlock: + hci_dev_unlock(hdev); return err; } -static int load_link_keys(struct sock *sk, u16 index, unsigned char *data, +static int load_link_keys(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_load_link_keys *cp; + struct mgmt_cp_load_link_keys *cp = data; u16 key_count, expected_len; int i; - cp = (void *) data; - - if (len < sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_LOAD_LINK_KEYS, - MGMT_STATUS_INVALID_PARAMS); - key_count = get_unaligned_le16(&cp->key_count); expected_len = sizeof(*cp) + key_count * @@ -1059,92 +1446,103 @@ static int load_link_keys(struct sock *sk, u16 index, unsigned char *data, if (expected_len != len) { BT_ERR("load_link_keys: expected %u bytes, got %u bytes", len, expected_len); - return cmd_status(sk, index, MGMT_OP_LOAD_LINK_KEYS, - MGMT_STATUS_INVALID_PARAMS); + return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS, + MGMT_STATUS_INVALID_PARAMS); } - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_LOAD_LINK_KEYS, - MGMT_STATUS_INVALID_PARAMS); - - BT_DBG("hci%u debug_keys %u key_count %u", index, cp->debug_keys, + BT_DBG("%s debug_keys %u key_count %u", hdev->name, cp->debug_keys, key_count); hci_dev_lock(hdev); hci_link_keys_clear(hdev); - set_bit(HCI_LINK_KEYS, &hdev->flags); + set_bit(HCI_LINK_KEYS, &hdev->dev_flags); if (cp->debug_keys) - set_bit(HCI_DEBUG_KEYS, &hdev->flags); + set_bit(HCI_DEBUG_KEYS, &hdev->dev_flags); else - clear_bit(HCI_DEBUG_KEYS, &hdev->flags); + clear_bit(HCI_DEBUG_KEYS, &hdev->dev_flags); for (i = 0; i < key_count; i++) { struct mgmt_link_key_info *key = &cp->keys[i]; - hci_add_link_key(hdev, NULL, 0, &key->bdaddr, key->val, key->type, - key->pin_len); + hci_add_link_key(hdev, NULL, 0, &key->addr.bdaddr, key->val, + key->type, key->pin_len); } - cmd_complete(sk, index, MGMT_OP_LOAD_LINK_KEYS, NULL, 0); + cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS, 0, NULL, 0); hci_dev_unlock(hdev); - hci_dev_put(hdev); return 0; } -static int remove_keys(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int device_unpaired(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 addr_type, struct sock *skip_sk) { - struct hci_dev *hdev; - struct mgmt_cp_remove_keys *cp; - struct mgmt_rp_remove_keys rp; + struct mgmt_ev_device_unpaired ev; + + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = addr_type; + + return mgmt_event(MGMT_EV_DEVICE_UNPAIRED, hdev, &ev, sizeof(ev), + skip_sk); +} + +static int unpair_device(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_unpair_device *cp = data; + struct mgmt_rp_unpair_device rp; struct hci_cp_disconnect dc; struct pending_cmd *cmd; struct hci_conn *conn; int err; - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_REMOVE_KEYS, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_REMOVE_KEYS, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); memset(&rp, 0, sizeof(rp)); - bacpy(&rp.bdaddr, &cp->bdaddr); - rp.status = MGMT_STATUS_FAILED; + bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); + rp.addr.type = cp->addr.type; - err = hci_remove_link_key(hdev, &cp->bdaddr); - if (err < 0) { - rp.status = MGMT_STATUS_NOT_PAIRED; + if (!hdev_is_powered(hdev)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, + MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp)); goto unlock; } - if (!test_bit(HCI_UP, &hdev->flags) || !cp->disconnect) { - err = cmd_complete(sk, index, MGMT_OP_REMOVE_KEYS, &rp, - sizeof(rp)); + if (cp->addr.type == MGMT_ADDR_BREDR) + err = hci_remove_link_key(hdev, &cp->addr.bdaddr); + else + err = hci_remove_ltk(hdev, &cp->addr.bdaddr); + + if (err < 0) { + err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, + MGMT_STATUS_NOT_PAIRED, &rp, sizeof(rp)); goto unlock; } - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + if (cp->disconnect) { + if (cp->addr.type == MGMT_ADDR_BREDR) + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, + &cp->addr.bdaddr); + else + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, + &cp->addr.bdaddr); + } else { + conn = NULL; + } + if (!conn) { - err = cmd_complete(sk, index, MGMT_OP_REMOVE_KEYS, &rp, - sizeof(rp)); + err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, 0, + &rp, sizeof(rp)); + device_unpaired(hdev, &cp->addr.bdaddr, cp->addr.type, sk); goto unlock; } - cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_KEYS, hdev, cp, sizeof(*cp)); + cmd = mgmt_pending_add(sk, MGMT_OP_UNPAIR_DEVICE, hdev, cp, + sizeof(*cp)); if (!cmd) { err = -ENOMEM; goto unlock; @@ -1157,19 +1555,14 @@ static int remove_keys(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); unlock: - if (err < 0) - err = cmd_complete(sk, index, MGMT_OP_REMOVE_KEYS, &rp, - sizeof(rp)); hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) +static int disconnect(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_disconnect *cp; + struct mgmt_cp_disconnect *cp = data; struct hci_cp_disconnect dc; struct pending_cmd *cmd; struct hci_conn *conn; @@ -1177,38 +1570,28 @@ static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) BT_DBG(""); - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_DISCONNECT, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_DISCONNECT, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_DISCONNECT, - MGMT_STATUS_NOT_POWERED); + err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, + MGMT_STATUS_NOT_POWERED); goto failed; } if (mgmt_pending_find(MGMT_OP_DISCONNECT, hdev)) { - err = cmd_status(sk, index, MGMT_OP_DISCONNECT, - MGMT_STATUS_BUSY); + err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, + MGMT_STATUS_BUSY); goto failed; } - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); - if (!conn) - conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (cp->addr.type == MGMT_ADDR_BREDR) + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); + else + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr); if (!conn) { - err = cmd_status(sk, index, MGMT_OP_DISCONNECT, - MGMT_STATUS_NOT_CONNECTED); + err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, + MGMT_STATUS_NOT_CONNECTED); goto failed; } @@ -1227,8 +1610,6 @@ static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } @@ -1251,41 +1632,42 @@ static u8 link_to_mgmt(u8 link_type, u8 addr_type) } } -static int get_connections(struct sock *sk, u16 index) +static int get_connections(struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len) { struct mgmt_rp_get_connections *rp; - struct hci_dev *hdev; struct hci_conn *c; - struct list_head *p; size_t rp_len; - u16 count; - int i, err; + int err; + u16 i; BT_DBG(""); - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_GET_CONNECTIONS, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); - count = 0; - list_for_each(p, &hdev->conn_hash.list) { - count++; + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_GET_CONNECTIONS, + MGMT_STATUS_NOT_POWERED); + goto unlock; } - rp_len = sizeof(*rp) + (count * sizeof(struct mgmt_addr_info)); + i = 0; + list_for_each_entry(c, &hdev->conn_hash.list, list) { + if (test_bit(HCI_CONN_MGMT_CONNECTED, &c->flags)) + i++; + } + + rp_len = sizeof(*rp) + (i * sizeof(struct mgmt_addr_info)); rp = kmalloc(rp_len, GFP_ATOMIC); if (!rp) { err = -ENOMEM; goto unlock; } - put_unaligned_le16(count, &rp->conn_count); - i = 0; list_for_each_entry(c, &hdev->conn_hash.list, list) { + if (!test_bit(HCI_CONN_MGMT_CONNECTED, &c->flags)) + continue; bacpy(&rp->addr[i].bdaddr, &c->dst); rp->addr[i].type = link_to_mgmt(c->type, c->dst_type); if (rp->addr[i].type == MGMT_ADDR_INVALID) @@ -1293,85 +1675,77 @@ static int get_connections(struct sock *sk, u16 index) i++; } + put_unaligned_le16(i, &rp->conn_count); + /* Recalculate length in case of filtered SCO connections, etc */ rp_len = sizeof(*rp) + (i * sizeof(struct mgmt_addr_info)); - err = cmd_complete(sk, index, MGMT_OP_GET_CONNECTIONS, rp, rp_len); + err = cmd_complete(sk, hdev->id, MGMT_OP_GET_CONNECTIONS, 0, rp, + rp_len); -unlock: kfree(rp); + +unlock: hci_dev_unlock(hdev); - hci_dev_put(hdev); return err; } -static int send_pin_code_neg_reply(struct sock *sk, u16 index, - struct hci_dev *hdev, struct mgmt_cp_pin_code_neg_reply *cp) +static int send_pin_code_neg_reply(struct sock *sk, struct hci_dev *hdev, + struct mgmt_cp_pin_code_neg_reply *cp) { struct pending_cmd *cmd; int err; cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_NEG_REPLY, hdev, cp, - sizeof(*cp)); + sizeof(*cp)); if (!cmd) return -ENOMEM; - err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, sizeof(cp->bdaddr), - &cp->bdaddr); + err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, + sizeof(cp->addr.bdaddr), &cp->addr.bdaddr); if (err < 0) mgmt_pending_remove(cmd); return err; } -static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int pin_code_reply(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; struct hci_conn *conn; - struct mgmt_cp_pin_code_reply *cp; - struct mgmt_cp_pin_code_neg_reply ncp; + struct mgmt_cp_pin_code_reply *cp = data; struct hci_cp_pin_code_reply reply; struct pending_cmd *cmd; int err; BT_DBG(""); - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, + MGMT_STATUS_NOT_POWERED); goto failed; } - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); if (!conn) { - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_NOT_CONNECTED); + err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, + MGMT_STATUS_NOT_CONNECTED); goto failed; } if (conn->pending_sec_level == BT_SECURITY_HIGH && cp->pin_len != 16) { - bacpy(&ncp.bdaddr, &cp->bdaddr); + struct mgmt_cp_pin_code_neg_reply ncp; + + memcpy(&ncp.addr, &cp->addr, sizeof(ncp.addr)); BT_ERR("PIN code is not 16 bytes long"); - err = send_pin_code_neg_reply(sk, index, hdev, &ncp); + err = send_pin_code_neg_reply(sk, hdev, &ncp); if (err >= 0) - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_INVALID_PARAMS); + err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, + MGMT_STATUS_INVALID_PARAMS); goto failed; } @@ -1382,7 +1756,7 @@ static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, goto failed; } - bacpy(&reply.bdaddr, &cp->bdaddr); + bacpy(&reply.bdaddr, &cp->addr.bdaddr); reply.pin_len = cp->pin_len; memcpy(reply.pin_code, cp->pin_code, sizeof(reply.pin_code)); @@ -1392,67 +1766,39 @@ static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int pin_code_neg_reply(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int pin_code_neg_reply(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_pin_code_neg_reply *cp; + struct mgmt_cp_pin_code_neg_reply *cp = data; int err; BT_DBG(""); - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, - MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_NEG_REPLY, + MGMT_STATUS_NOT_POWERED); goto failed; } - err = send_pin_code_neg_reply(sk, index, hdev, cp); + err = send_pin_code_neg_reply(sk, hdev, cp); failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int set_io_capability(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_set_io_capability *cp; + struct mgmt_cp_set_io_capability *cp = data; BT_DBG(""); - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_IO_CAPABILITY, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_IO_CAPABILITY, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); hdev->io_capability = cp->io_capability; @@ -1461,9 +1807,9 @@ static int set_io_capability(struct sock *sk, u16 index, unsigned char *data, hdev->io_capability); hci_dev_unlock(hdev); - hci_dev_put(hdev); - return cmd_complete(sk, index, MGMT_OP_SET_IO_CAPABILITY, NULL, 0); + return cmd_complete(sk, hdev->id, MGMT_OP_SET_IO_CAPABILITY, 0, NULL, + 0); } static inline struct pending_cmd *find_pairing(struct hci_conn *conn) @@ -1491,9 +1837,9 @@ static void pairing_complete(struct pending_cmd *cmd, u8 status) bacpy(&rp.addr.bdaddr, &conn->dst); rp.addr.type = link_to_mgmt(conn->type, conn->dst_type); - rp.status = status; - cmd_complete(cmd->sk, cmd->index, MGMT_OP_PAIR_DEVICE, &rp, sizeof(rp)); + cmd_complete(cmd->sk, cmd->index, MGMT_OP_PAIR_DEVICE, status, + &rp, sizeof(rp)); /* So we don't get further callbacks for this connection */ conn->connect_cfm_cb = NULL; @@ -1515,13 +1861,13 @@ static void pairing_complete_cb(struct hci_conn *conn, u8 status) if (!cmd) BT_DBG("Unable to find a pending command"); else - pairing_complete(cmd, status); + pairing_complete(cmd, mgmt_status(status)); } -static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) +static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_pair_device *cp; + struct mgmt_cp_pair_device *cp = data; struct mgmt_rp_pair_device rp; struct pending_cmd *cmd; u8 sec_level, auth_type; @@ -1530,19 +1876,14 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) BT_DBG(""); - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - hci_dev_lock(hdev); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_PAIR_DEVICE, + MGMT_STATUS_NOT_POWERED); + goto unlock; + } + sec_level = BT_SECURITY_MEDIUM; if (cp->io_cap == 0x03) auth_type = HCI_AT_DEDICATED_BONDING; @@ -1551,27 +1892,26 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) if (cp->addr.type == MGMT_ADDR_BREDR) conn = hci_connect(hdev, ACL_LINK, &cp->addr.bdaddr, sec_level, - auth_type); + auth_type); else conn = hci_connect(hdev, LE_LINK, &cp->addr.bdaddr, sec_level, - auth_type); + auth_type); memset(&rp, 0, sizeof(rp)); bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); rp.addr.type = cp->addr.type; if (IS_ERR(conn)) { - rp.status = -PTR_ERR(conn); - err = cmd_complete(sk, index, MGMT_OP_PAIR_DEVICE, - &rp, sizeof(rp)); + err = cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE, + MGMT_STATUS_CONNECT_FAILED, &rp, + sizeof(rp)); goto unlock; } if (conn->connect_cfm_cb) { hci_conn_put(conn); - rp.status = EBUSY; - err = cmd_complete(sk, index, MGMT_OP_PAIR_DEVICE, - &rp, sizeof(rp)); + err = cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE, + MGMT_STATUS_BUSY, &rp, sizeof(rp)); goto unlock; } @@ -1599,58 +1939,88 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) unlock: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int user_pairing_resp(struct sock *sk, u16 index, bdaddr_t *bdaddr, - u16 mgmt_op, u16 hci_op, __le32 passkey) +static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { + struct mgmt_addr_info *addr = data; struct pending_cmd *cmd; - struct hci_dev *hdev; struct hci_conn *conn; int err; - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, mgmt_op, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG(""); hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, mgmt_op, MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, + MGMT_STATUS_NOT_POWERED); + goto unlock; + } + + cmd = mgmt_pending_find(MGMT_OP_PAIR_DEVICE, hdev); + if (!cmd) { + err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, + MGMT_STATUS_INVALID_PARAMS); + goto unlock; + } + + conn = cmd->user_data; + + if (bacmp(&addr->bdaddr, &conn->dst) != 0) { + err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, + MGMT_STATUS_INVALID_PARAMS); + goto unlock; + } + + pairing_complete(cmd, MGMT_STATUS_CANCELLED); + + err = cmd_complete(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, 0, + addr, sizeof(*addr)); +unlock: + hci_dev_unlock(hdev); + return err; +} + +static int user_pairing_resp(struct sock *sk, struct hci_dev *hdev, + bdaddr_t *bdaddr, u8 type, u16 mgmt_op, + u16 hci_op, __le32 passkey) +{ + struct pending_cmd *cmd; + struct hci_conn *conn; + int err; + + hci_dev_lock(hdev); + + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, mgmt_op, + MGMT_STATUS_NOT_POWERED); goto done; } - /* - * Check for an existing ACL link, if present pair via - * HCI commands. - * - * If no ACL link is present, check for an LE link and if - * present, pair via the SMP engine. - * - * If neither ACL nor LE links are present, fail with error. - */ - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, bdaddr); - if (!conn) { + if (type == MGMT_ADDR_BREDR) + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, bdaddr); + else conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, bdaddr); - if (!conn) { - err = cmd_status(sk, index, mgmt_op, - MGMT_STATUS_NOT_CONNECTED); - goto done; - } + if (!conn) { + err = cmd_status(sk, hdev->id, mgmt_op, + MGMT_STATUS_NOT_CONNECTED); + goto done; + } + + if (type == MGMT_ADDR_LE_PUBLIC || type == MGMT_ADDR_LE_RANDOM) { /* Continue with pairing via SMP */ err = smp_user_confirm_reply(conn, mgmt_op, passkey); if (!err) - err = cmd_status(sk, index, mgmt_op, - MGMT_STATUS_SUCCESS); + err = cmd_status(sk, hdev->id, mgmt_op, + MGMT_STATUS_SUCCESS); else - err = cmd_status(sk, index, mgmt_op, - MGMT_STATUS_FAILED); + err = cmd_status(sk, hdev->id, mgmt_op, + MGMT_STATUS_FAILED); goto done; } @@ -1676,94 +2046,96 @@ static int user_pairing_resp(struct sock *sk, u16 index, bdaddr_t *bdaddr, done: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int user_confirm_reply(struct sock *sk, u16 index, void *data, u16 len) +static int user_confirm_reply(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_cp_user_confirm_reply *cp = (void *) data; + struct mgmt_cp_user_confirm_reply *cp = data; BT_DBG(""); if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_USER_CONFIRM_REPLY, - MGMT_STATUS_INVALID_PARAMS); + return cmd_status(sk, hdev->id, MGMT_OP_USER_CONFIRM_REPLY, + MGMT_STATUS_INVALID_PARAMS); - return user_pairing_resp(sk, index, &cp->bdaddr, - MGMT_OP_USER_CONFIRM_REPLY, - HCI_OP_USER_CONFIRM_REPLY, 0); + return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, + MGMT_OP_USER_CONFIRM_REPLY, + HCI_OP_USER_CONFIRM_REPLY, 0); } -static int user_confirm_neg_reply(struct sock *sk, u16 index, void *data, - u16 len) +static int user_confirm_neg_reply(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { struct mgmt_cp_user_confirm_neg_reply *cp = data; BT_DBG(""); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_USER_CONFIRM_NEG_REPLY, - MGMT_STATUS_INVALID_PARAMS); - - return user_pairing_resp(sk, index, &cp->bdaddr, - MGMT_OP_USER_CONFIRM_NEG_REPLY, - HCI_OP_USER_CONFIRM_NEG_REPLY, 0); + return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, + MGMT_OP_USER_CONFIRM_NEG_REPLY, + HCI_OP_USER_CONFIRM_NEG_REPLY, 0); } -static int user_passkey_reply(struct sock *sk, u16 index, void *data, u16 len) +static int user_passkey_reply(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_cp_user_passkey_reply *cp = (void *) data; + struct mgmt_cp_user_passkey_reply *cp = data; BT_DBG(""); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_USER_PASSKEY_REPLY, - EINVAL); - - return user_pairing_resp(sk, index, &cp->bdaddr, - MGMT_OP_USER_PASSKEY_REPLY, - HCI_OP_USER_PASSKEY_REPLY, cp->passkey); + return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, + MGMT_OP_USER_PASSKEY_REPLY, + HCI_OP_USER_PASSKEY_REPLY, cp->passkey); } -static int user_passkey_neg_reply(struct sock *sk, u16 index, void *data, - u16 len) +static int user_passkey_neg_reply(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct mgmt_cp_user_passkey_neg_reply *cp = (void *) data; + struct mgmt_cp_user_passkey_neg_reply *cp = data; BT_DBG(""); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_USER_PASSKEY_NEG_REPLY, - EINVAL); + return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, + MGMT_OP_USER_PASSKEY_NEG_REPLY, + HCI_OP_USER_PASSKEY_NEG_REPLY, 0); +} + +static int update_name(struct hci_dev *hdev, const char *name) +{ + struct hci_cp_write_local_name cp; - return user_pairing_resp(sk, index, &cp->bdaddr, - MGMT_OP_USER_PASSKEY_NEG_REPLY, - HCI_OP_USER_PASSKEY_NEG_REPLY, 0); + memcpy(cp.name, name, sizeof(cp.name)); + + return hci_send_cmd(hdev, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp); } -static int set_local_name(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct mgmt_cp_set_local_name *mgmt_cp = (void *) data; - struct hci_cp_write_local_name hci_cp; - struct hci_dev *hdev; + struct mgmt_cp_set_local_name *cp = data; struct pending_cmd *cmd; int err; BT_DBG(""); - if (len != sizeof(*mgmt_cp)) - return cmd_status(sk, index, MGMT_OP_SET_LOCAL_NAME, - MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_LOCAL_NAME, - MGMT_STATUS_INVALID_PARAMS); + memcpy(hdev->short_name, cp->short_name, sizeof(hdev->short_name)); - hci_dev_lock(hdev); + if (!hdev_is_powered(hdev)) { + memcpy(hdev->dev_name, cp->name, sizeof(hdev->dev_name)); + + err = cmd_complete(sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0, + data, len); + if (err < 0) + goto failed; + + err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, data, len, + sk); + + goto failed; + } cmd = mgmt_pending_add(sk, MGMT_OP_SET_LOCAL_NAME, hdev, data, len); if (!cmd) { @@ -1771,49 +2143,40 @@ static int set_local_name(struct sock *sk, u16 index, unsigned char *data, goto failed; } - memcpy(hci_cp.name, mgmt_cp->name, sizeof(hci_cp.name)); - err = hci_send_cmd(hdev, HCI_OP_WRITE_LOCAL_NAME, sizeof(hci_cp), - &hci_cp); + err = update_name(hdev, cp->name); if (err < 0) mgmt_pending_remove(cmd); failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int read_local_oob_data(struct sock *sk, u16 index) +static int read_local_oob_data(struct sock *sk, struct hci_dev *hdev, + void *data, u16 data_len) { - struct hci_dev *hdev; struct pending_cmd *cmd; int err; - BT_DBG("hci%u", index); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, + MGMT_STATUS_NOT_POWERED); goto unlock; } if (!(hdev->features[6] & LMP_SIMPLE_PAIR)) { - err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_NOT_SUPPORTED); + err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, + MGMT_STATUS_NOT_SUPPORTED); goto unlock; } if (mgmt_pending_find(MGMT_OP_READ_LOCAL_OOB_DATA, hdev)) { - err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_BUSY); + err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, + MGMT_STATUS_BUSY); goto unlock; } @@ -1829,104 +2192,112 @@ static int read_local_oob_data(struct sock *sk, u16 index) unlock: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int add_remote_oob_data(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int add_remote_oob_data(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_add_remote_oob_data *cp = (void *) data; + struct mgmt_cp_add_remote_oob_data *cp = data; + u8 status; int err; - BT_DBG("hci%u ", index); - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("%s ", hdev->name); hci_dev_lock(hdev); - err = hci_add_remote_oob_data(hdev, &cp->bdaddr, cp->hash, - cp->randomizer); + if (!hdev_is_powered(hdev)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_REMOTE_OOB_DATA, + MGMT_STATUS_NOT_POWERED, &cp->addr, + sizeof(cp->addr)); + goto unlock; + } + + err = hci_add_remote_oob_data(hdev, &cp->addr.bdaddr, cp->hash, + cp->randomizer); if (err < 0) - err = cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, - MGMT_STATUS_FAILED); + status = MGMT_STATUS_FAILED; else - err = cmd_complete(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, NULL, - 0); + status = 0; - hci_dev_unlock(hdev); - hci_dev_put(hdev); + err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_REMOTE_OOB_DATA, status, + &cp->addr, sizeof(cp->addr)); +unlock: + hci_dev_unlock(hdev); return err; } -static int remove_remote_oob_data(struct sock *sk, u16 index, - unsigned char *data, u16 len) +static int remove_remote_oob_data(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_remove_remote_oob_data *cp = (void *) data; + struct mgmt_cp_remove_remote_oob_data *cp = data; + u8 status; int err; - BT_DBG("hci%u ", index); + BT_DBG("%s", hdev->name); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); + + if (!hdev_is_powered(hdev)) { + err = cmd_complete(sk, hdev->id, + MGMT_OP_REMOVE_REMOTE_OOB_DATA, + MGMT_STATUS_NOT_POWERED, &cp->addr, + sizeof(cp->addr)); + goto unlock; + } + + err = hci_remove_remote_oob_data(hdev, &cp->addr.bdaddr); + if (err < 0) + status = MGMT_STATUS_INVALID_PARAMS; + else + status = 0; - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); + err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + status, &cp->addr, sizeof(cp->addr)); + +unlock: + hci_dev_unlock(hdev); + return err; +} + +int mgmt_interleaved_discovery(struct hci_dev *hdev) +{ + int err; + + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - err = hci_remove_remote_oob_data(hdev, &cp->bdaddr); + err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR_LE); if (err < 0) - err = cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, - MGMT_STATUS_INVALID_PARAMS); - else - err = cmd_complete(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, - NULL, 0); + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); hci_dev_unlock(hdev); - hci_dev_put(hdev); return err; } -static int start_discovery(struct sock *sk, u16 index, - unsigned char *data, u16 len) +static int start_discovery(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct mgmt_cp_start_discovery *cp = (void *) data; + struct mgmt_cp_start_discovery *cp = data; struct pending_cmd *cmd; - struct hci_dev *hdev; int err; - BT_DBG("hci%u", index); - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, index, MGMT_OP_START_DISCOVERY, - MGMT_STATUS_NOT_POWERED); + if (!hdev_is_powered(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY, + MGMT_STATUS_NOT_POWERED); + goto failed; + } + + if (hdev->discovery.state != DISCOVERY_STOPPED) { + err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY, + MGMT_STATUS_BUSY); goto failed; } @@ -1936,137 +2307,217 @@ static int start_discovery(struct sock *sk, u16 index, goto failed; } - err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR); + hdev->discovery.type = cp->type; + + switch (hdev->discovery.type) { + case DISCOV_TYPE_BREDR: + if (lmp_bredr_capable(hdev)) + err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR); + else + err = -ENOTSUPP; + break; + + case DISCOV_TYPE_LE: + if (lmp_host_le_capable(hdev)) + err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT, + LE_SCAN_WIN, LE_SCAN_TIMEOUT_LE_ONLY); + else + err = -ENOTSUPP; + break; + + case DISCOV_TYPE_INTERLEAVED: + if (lmp_host_le_capable(hdev) && lmp_bredr_capable(hdev)) + err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT, + LE_SCAN_WIN, + LE_SCAN_TIMEOUT_BREDR_LE); + else + err = -ENOTSUPP; + break; + + default: + err = -EINVAL; + } + if (err < 0) mgmt_pending_remove(cmd); + else + hci_discovery_set_state(hdev, DISCOVERY_STARTING); failed: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int stop_discovery(struct sock *sk, u16 index) +static int stop_discovery(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; + struct mgmt_cp_stop_discovery *mgmt_cp = data; struct pending_cmd *cmd; + struct hci_cp_remote_name_req_cancel cp; + struct inquiry_entry *e; int err; - BT_DBG("hci%u", index); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_STOP_DISCOVERY, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); + if (!hci_discovery_active(hdev)) { + err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, + MGMT_STATUS_REJECTED, &mgmt_cp->type, + sizeof(mgmt_cp->type)); + goto unlock; + } + + if (hdev->discovery.type != mgmt_cp->type) { + err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, + MGMT_STATUS_INVALID_PARAMS, &mgmt_cp->type, + sizeof(mgmt_cp->type)); + goto unlock; + } + cmd = mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, hdev, NULL, 0); if (!cmd) { err = -ENOMEM; - goto failed; + goto unlock; + } + + if (hdev->discovery.state == DISCOVERY_FINDING) { + err = hci_cancel_inquiry(hdev); + if (err < 0) + mgmt_pending_remove(cmd); + else + hci_discovery_set_state(hdev, DISCOVERY_STOPPING); + goto unlock; + } + + e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_PENDING); + if (!e) { + mgmt_pending_remove(cmd); + err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, 0, + &mgmt_cp->type, sizeof(mgmt_cp->type)); + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + goto unlock; } - err = hci_cancel_inquiry(hdev); + bacpy(&cp.bdaddr, &e->data.bdaddr); + err = hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ_CANCEL, sizeof(cp), + &cp); if (err < 0) mgmt_pending_remove(cmd); + else + hci_discovery_set_state(hdev, DISCOVERY_STOPPING); -failed: +unlock: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } -static int block_device(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int confirm_name(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_block_device *cp = (void *) data; + struct mgmt_cp_confirm_name *cp = data; + struct inquiry_entry *e; int err; - BT_DBG("hci%u", index); + BT_DBG("%s", hdev->name); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, - MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); + + if (!hci_discovery_active(hdev)) { + err = cmd_status(sk, hdev->id, MGMT_OP_CONFIRM_NAME, + MGMT_STATUS_FAILED); + goto failed; + } - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, - MGMT_STATUS_INVALID_PARAMS); + e = hci_inquiry_cache_lookup_unknown(hdev, &cp->addr.bdaddr); + if (!e) { + err = cmd_status(sk, hdev->id, MGMT_OP_CONFIRM_NAME, + MGMT_STATUS_INVALID_PARAMS); + goto failed; + } + + if (cp->name_known) { + e->name_state = NAME_KNOWN; + list_del(&e->list); + } else { + e->name_state = NAME_NEEDED; + hci_inquiry_cache_update_resolve(hdev, e); + } + + err = 0; + +failed: + hci_dev_unlock(hdev); + return err; +} + +static int block_device(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_block_device *cp = data; + u8 status; + int err; + + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - err = hci_blacklist_add(hdev, &cp->bdaddr); + err = hci_blacklist_add(hdev, &cp->addr.bdaddr, cp->addr.type); if (err < 0) - err = cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, - MGMT_STATUS_FAILED); + status = MGMT_STATUS_FAILED; else - err = cmd_complete(sk, index, MGMT_OP_BLOCK_DEVICE, - NULL, 0); + status = 0; + + err = cmd_complete(sk, hdev->id, MGMT_OP_BLOCK_DEVICE, status, + &cp->addr, sizeof(cp->addr)); hci_dev_unlock(hdev); - hci_dev_put(hdev); return err; } -static int unblock_device(struct sock *sk, u16 index, unsigned char *data, - u16 len) +static int unblock_device(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) { - struct hci_dev *hdev; - struct mgmt_cp_unblock_device *cp = (void *) data; + struct mgmt_cp_unblock_device *cp = data; + u8 status; int err; - BT_DBG("hci%u", index); - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - err = hci_blacklist_del(hdev, &cp->bdaddr); - + err = hci_blacklist_del(hdev, &cp->addr.bdaddr, cp->addr.type); if (err < 0) - err = cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, - MGMT_STATUS_INVALID_PARAMS); + status = MGMT_STATUS_INVALID_PARAMS; else - err = cmd_complete(sk, index, MGMT_OP_UNBLOCK_DEVICE, - NULL, 0); + status = 0; + + err = cmd_complete(sk, hdev->id, MGMT_OP_UNBLOCK_DEVICE, status, + &cp->addr, sizeof(cp->addr)); hci_dev_unlock(hdev); - hci_dev_put(hdev); return err; } -static int set_fast_connectable(struct sock *sk, u16 index, - unsigned char *data, u16 len) +static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) { - struct hci_dev *hdev; - struct mgmt_mode *cp = (void *) data; + struct mgmt_mode *cp = data; struct hci_cp_write_page_scan_activity acp; u8 type; int err; - BT_DBG("hci%u", index); + BT_DBG("%s", hdev->name); - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!hdev_is_powered(hdev)) + return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, + MGMT_STATUS_NOT_POWERED); - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_INVALID_PARAMS); + if (!test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) + return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, + MGMT_STATUS_REJECTED); hci_dev_lock(hdev); @@ -2080,35 +2531,128 @@ static int set_fast_connectable(struct sock *sk, u16 index, acp.window = 0x0012; /* default 11.25 msec page scan window */ - err = hci_send_cmd(hdev, HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, - sizeof(acp), &acp); + err = hci_send_cmd(hdev, HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, sizeof(acp), + &acp); if (err < 0) { - err = cmd_status(sk, index, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_FAILED); + err = cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, + MGMT_STATUS_FAILED); goto done; } err = hci_send_cmd(hdev, HCI_OP_WRITE_PAGE_SCAN_TYPE, 1, &type); if (err < 0) { - err = cmd_status(sk, index, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_FAILED); + err = cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, + MGMT_STATUS_FAILED); goto done; } - err = cmd_complete(sk, index, MGMT_OP_SET_FAST_CONNECTABLE, - NULL, 0); + err = cmd_complete(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, 0, + NULL, 0); done: hci_dev_unlock(hdev); - hci_dev_put(hdev); - return err; } +static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev, + void *cp_data, u16 len) +{ + struct mgmt_cp_load_long_term_keys *cp = cp_data; + u16 key_count, expected_len; + int i; + + key_count = get_unaligned_le16(&cp->key_count); + + expected_len = sizeof(*cp) + key_count * + sizeof(struct mgmt_ltk_info); + if (expected_len != len) { + BT_ERR("load_keys: expected %u bytes, got %u bytes", + len, expected_len); + return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, + EINVAL); + } + + BT_DBG("%s key_count %u", hdev->name, key_count); + + hci_dev_lock(hdev); + + hci_smp_ltks_clear(hdev); + + for (i = 0; i < key_count; i++) { + struct mgmt_ltk_info *key = &cp->keys[i]; + u8 type; + + if (key->master) + type = HCI_SMP_LTK; + else + type = HCI_SMP_LTK_SLAVE; + + hci_add_ltk(hdev, &key->addr.bdaddr, key->addr.type, + type, 0, key->authenticated, key->val, + key->enc_size, key->ediv, key->rand); + } + + hci_dev_unlock(hdev); + + return 0; +} + +struct mgmt_handler { + int (*func) (struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len); + bool var_len; + size_t data_len; +} mgmt_handlers[] = { + { NULL }, /* 0x0000 (no command) */ + { read_version, false, MGMT_READ_VERSION_SIZE }, + { read_commands, false, MGMT_READ_COMMANDS_SIZE }, + { read_index_list, false, MGMT_READ_INDEX_LIST_SIZE }, + { read_controller_info, false, MGMT_READ_INFO_SIZE }, + { set_powered, false, MGMT_SETTING_SIZE }, + { set_discoverable, false, MGMT_SET_DISCOVERABLE_SIZE }, + { set_connectable, false, MGMT_SETTING_SIZE }, + { set_fast_connectable, false, MGMT_SETTING_SIZE }, + { set_pairable, false, MGMT_SETTING_SIZE }, + { set_link_security, false, MGMT_SETTING_SIZE }, + { set_ssp, false, MGMT_SETTING_SIZE }, + { set_hs, false, MGMT_SETTING_SIZE }, + { set_le, false, MGMT_SETTING_SIZE }, + { set_dev_class, false, MGMT_SET_DEV_CLASS_SIZE }, + { set_local_name, false, MGMT_SET_LOCAL_NAME_SIZE }, + { add_uuid, false, MGMT_ADD_UUID_SIZE }, + { remove_uuid, false, MGMT_REMOVE_UUID_SIZE }, + { load_link_keys, true, MGMT_LOAD_LINK_KEYS_SIZE }, + { load_long_term_keys, true, MGMT_LOAD_LONG_TERM_KEYS_SIZE }, + { disconnect, false, MGMT_DISCONNECT_SIZE }, + { get_connections, false, MGMT_GET_CONNECTIONS_SIZE }, + { pin_code_reply, false, MGMT_PIN_CODE_REPLY_SIZE }, + { pin_code_neg_reply, false, MGMT_PIN_CODE_NEG_REPLY_SIZE }, + { set_io_capability, false, MGMT_SET_IO_CAPABILITY_SIZE }, + { pair_device, false, MGMT_PAIR_DEVICE_SIZE }, + { cancel_pair_device, false, MGMT_CANCEL_PAIR_DEVICE_SIZE }, + { unpair_device, false, MGMT_UNPAIR_DEVICE_SIZE }, + { user_confirm_reply, false, MGMT_USER_CONFIRM_REPLY_SIZE }, + { user_confirm_neg_reply, false, MGMT_USER_CONFIRM_NEG_REPLY_SIZE }, + { user_passkey_reply, false, MGMT_USER_PASSKEY_REPLY_SIZE }, + { user_passkey_neg_reply, false, MGMT_USER_PASSKEY_NEG_REPLY_SIZE }, + { read_local_oob_data, false, MGMT_READ_LOCAL_OOB_DATA_SIZE }, + { add_remote_oob_data, false, MGMT_ADD_REMOTE_OOB_DATA_SIZE }, + { remove_remote_oob_data, false, MGMT_REMOVE_REMOTE_OOB_DATA_SIZE }, + { start_discovery, false, MGMT_START_DISCOVERY_SIZE }, + { stop_discovery, false, MGMT_STOP_DISCOVERY_SIZE }, + { confirm_name, false, MGMT_CONFIRM_NAME_SIZE }, + { block_device, false, MGMT_BLOCK_DEVICE_SIZE }, + { unblock_device, false, MGMT_UNBLOCK_DEVICE_SIZE }, +}; + + int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) { - unsigned char *buf; + void *buf; + u8 *cp; struct mgmt_hdr *hdr; u16 opcode, index, len; + struct hci_dev *hdev = NULL; + struct mgmt_handler *handler; int err; BT_DBG("got %zu bytes", msglen); @@ -2125,7 +2669,7 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) goto done; } - hdr = (struct mgmt_hdr *) buf; + hdr = buf; opcode = get_unaligned_le16(&hdr->opcode); index = get_unaligned_le16(&hdr->index); len = get_unaligned_le16(&hdr->len); @@ -2135,117 +2679,54 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) goto done; } - switch (opcode) { - case MGMT_OP_READ_VERSION: - err = read_version(sk); - break; - case MGMT_OP_READ_INDEX_LIST: - err = read_index_list(sk); - break; - case MGMT_OP_READ_INFO: - err = read_controller_info(sk, index); - break; - case MGMT_OP_SET_POWERED: - err = set_powered(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_SET_DISCOVERABLE: - err = set_discoverable(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_SET_CONNECTABLE: - err = set_connectable(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_SET_FAST_CONNECTABLE: - err = set_fast_connectable(sk, index, buf + sizeof(*hdr), - len); - break; - case MGMT_OP_SET_PAIRABLE: - err = set_pairable(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_ADD_UUID: - err = add_uuid(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_REMOVE_UUID: - err = remove_uuid(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_SET_DEV_CLASS: - err = set_dev_class(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_LOAD_LINK_KEYS: - err = load_link_keys(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_REMOVE_KEYS: - err = remove_keys(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_DISCONNECT: - err = disconnect(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_GET_CONNECTIONS: - err = get_connections(sk, index); - break; - case MGMT_OP_PIN_CODE_REPLY: - err = pin_code_reply(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_PIN_CODE_NEG_REPLY: - err = pin_code_neg_reply(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_SET_IO_CAPABILITY: - err = set_io_capability(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_PAIR_DEVICE: - err = pair_device(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_USER_CONFIRM_REPLY: - err = user_confirm_reply(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_USER_CONFIRM_NEG_REPLY: - err = user_confirm_neg_reply(sk, index, buf + sizeof(*hdr), - len); - break; - case MGMT_OP_USER_PASSKEY_REPLY: - err = user_passkey_reply(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_USER_PASSKEY_NEG_REPLY: - err = user_passkey_neg_reply(sk, index, buf + sizeof(*hdr), - len); - break; - case MGMT_OP_SET_LOCAL_NAME: - err = set_local_name(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_READ_LOCAL_OOB_DATA: - err = read_local_oob_data(sk, index); - break; - case MGMT_OP_ADD_REMOTE_OOB_DATA: - err = add_remote_oob_data(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_REMOVE_REMOTE_OOB_DATA: - err = remove_remote_oob_data(sk, index, buf + sizeof(*hdr), - len); - break; - case MGMT_OP_START_DISCOVERY: - err = start_discovery(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_STOP_DISCOVERY: - err = stop_discovery(sk, index); - break; - case MGMT_OP_BLOCK_DEVICE: - err = block_device(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_UNBLOCK_DEVICE: - err = unblock_device(sk, index, buf + sizeof(*hdr), len); - break; - default: + if (index != MGMT_INDEX_NONE) { + hdev = hci_dev_get(index); + if (!hdev) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + } + + if (opcode >= ARRAY_SIZE(mgmt_handlers) || + mgmt_handlers[opcode].func == NULL) { BT_DBG("Unknown op %u", opcode); err = cmd_status(sk, index, opcode, - MGMT_STATUS_UNKNOWN_COMMAND); - break; + MGMT_STATUS_UNKNOWN_COMMAND); + goto done; + } + + if ((hdev && opcode < MGMT_OP_READ_INFO) || + (!hdev && opcode >= MGMT_OP_READ_INFO)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; } + handler = &mgmt_handlers[opcode]; + + if ((handler->var_len && len < handler->data_len) || + (!handler->var_len && len != handler->data_len)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_PARAMS); + goto done; + } + + if (hdev) + mgmt_init_hdev(sk, hdev); + + cp = buf + sizeof(*hdr); + + err = handler->func(sk, hdev, cp, len); if (err < 0) goto done; err = msglen; done: + if (hdev) + hci_dev_put(hdev); + kfree(buf); return err; } @@ -2265,7 +2746,7 @@ int mgmt_index_added(struct hci_dev *hdev) int mgmt_index_removed(struct hci_dev *hdev) { - u8 status = ENODEV; + u8 status = MGMT_STATUS_INVALID_INDEX; mgmt_pending_foreach(0, hdev, cmd_status_rsp, &status); @@ -2273,9 +2754,9 @@ int mgmt_index_removed(struct hci_dev *hdev) } struct cmd_lookup { - u8 val; struct sock *sk; struct hci_dev *hdev; + u8 mgmt_status; }; static void settings_rsp(struct pending_cmd *cmd, void *data) @@ -2296,63 +2777,91 @@ static void settings_rsp(struct pending_cmd *cmd, void *data) int mgmt_powered(struct hci_dev *hdev, u8 powered) { - struct cmd_lookup match = { powered, NULL, hdev }; - __le32 ev; - int ret; + struct cmd_lookup match = { NULL, hdev }; + int err; + + if (!test_bit(HCI_MGMT, &hdev->dev_flags)) + return 0; mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, &match); - if (!powered) { - u8 status = ENETDOWN; + if (powered) { + u8 scan = 0; + + if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) + scan |= SCAN_PAGE; + if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) + scan |= SCAN_INQUIRY; + + if (scan) + hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); + + update_class(hdev); + update_name(hdev, hdev->dev_name); + update_eir(hdev); + } else { + u8 status = MGMT_STATUS_NOT_POWERED; mgmt_pending_foreach(0, hdev, cmd_status_rsp, &status); } - ev = cpu_to_le32(get_current_settings(hdev)); - - ret = mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), - match.sk); + err = new_settings(hdev, match.sk); if (match.sk) sock_put(match.sk); - return ret; + return err; } int mgmt_discoverable(struct hci_dev *hdev, u8 discoverable) { - struct cmd_lookup match = { discoverable, NULL, hdev }; - __le32 ev; - int ret; + struct cmd_lookup match = { NULL, hdev }; + bool changed = false; + int err = 0; - mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, hdev, settings_rsp, &match); + if (discoverable) { + if (!test_and_set_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) + changed = true; + } else { + if (test_and_clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) + changed = true; + } - ev = cpu_to_le32(get_current_settings(hdev)); + mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, hdev, settings_rsp, + &match); + + if (changed) + err = new_settings(hdev, match.sk); - ret = mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), - match.sk); if (match.sk) sock_put(match.sk); - return ret; + return err; } int mgmt_connectable(struct hci_dev *hdev, u8 connectable) { - __le32 ev; - struct cmd_lookup match = { connectable, NULL, hdev }; - int ret; + struct cmd_lookup match = { NULL, hdev }; + bool changed = false; + int err = 0; - mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, hdev, settings_rsp, - &match); + if (connectable) { + if (!test_and_set_bit(HCI_CONNECTABLE, &hdev->dev_flags)) + changed = true; + } else { + if (test_and_clear_bit(HCI_CONNECTABLE, &hdev->dev_flags)) + changed = true; + } - ev = cpu_to_le32(get_current_settings(hdev)); + mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, hdev, settings_rsp, + &match); - ret = mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), match.sk); + if (changed) + err = new_settings(hdev, match.sk); if (match.sk) sock_put(match.sk); - return ret; + return err; } int mgmt_write_scan_failed(struct hci_dev *hdev, u8 scan, u8 status) @@ -2361,24 +2870,24 @@ int mgmt_write_scan_failed(struct hci_dev *hdev, u8 scan, u8 status) if (scan & SCAN_PAGE) mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, hdev, - cmd_status_rsp, &mgmt_err); + cmd_status_rsp, &mgmt_err); if (scan & SCAN_INQUIRY) mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, hdev, - cmd_status_rsp, &mgmt_err); + cmd_status_rsp, &mgmt_err); return 0; } -int mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, - u8 persistent) +int mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, u8 persistent) { struct mgmt_ev_new_link_key ev; memset(&ev, 0, sizeof(ev)); ev.store_hint = persistent; - bacpy(&ev.key.bdaddr, &key->bdaddr); + bacpy(&ev.key.addr.bdaddr, &key->bdaddr); + ev.key.addr.type = MGMT_ADDR_BREDR; ev.key.type = key->type; memcpy(ev.key.val, key->val, 16); ev.key.pin_len = key->pin_len; @@ -2386,15 +2895,54 @@ int mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, return mgmt_event(MGMT_EV_NEW_LINK_KEY, hdev, &ev, sizeof(ev), NULL); } -int mgmt_connected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type) +int mgmt_new_ltk(struct hci_dev *hdev, struct smp_ltk *key, u8 persistent) { - struct mgmt_addr_info ev; + struct mgmt_ev_new_long_term_key ev; - bacpy(&ev.bdaddr, bdaddr); - ev.type = link_to_mgmt(link_type, addr_type); + memset(&ev, 0, sizeof(ev)); - return mgmt_event(MGMT_EV_CONNECTED, hdev, &ev, sizeof(ev), NULL); + ev.store_hint = persistent; + bacpy(&ev.key.addr.bdaddr, &key->bdaddr); + ev.key.addr.type = key->bdaddr_type; + ev.key.authenticated = key->authenticated; + ev.key.enc_size = key->enc_size; + ev.key.ediv = key->ediv; + + if (key->type == HCI_SMP_LTK) + ev.key.master = 1; + + memcpy(ev.key.rand, key->rand, sizeof(key->rand)); + memcpy(ev.key.val, key->val, sizeof(key->val)); + + return mgmt_event(MGMT_EV_NEW_LONG_TERM_KEY, hdev, &ev, sizeof(ev), + NULL); +} + +int mgmt_device_connected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, + u8 addr_type, u32 flags, u8 *name, u8 name_len, + u8 *dev_class) +{ + char buf[512]; + struct mgmt_ev_device_connected *ev = (void *) buf; + u16 eir_len = 0; + + bacpy(&ev->addr.bdaddr, bdaddr); + ev->addr.type = link_to_mgmt(link_type, addr_type); + + ev->flags = __cpu_to_le32(flags); + + if (name_len > 0) + eir_len = eir_append_data(ev->eir, 0, EIR_NAME_COMPLETE, + name, name_len); + + if (dev_class && memcmp(dev_class, "\0\0\0", 3) != 0) + eir_len = eir_append_data(&ev->eir[eir_len], eir_len, + EIR_CLASS_OF_DEV, dev_class, 3); + + put_unaligned_le16(eir_len, &ev->eir_len); + + return mgmt_event(MGMT_EV_DEVICE_CONNECTED, hdev, buf, + sizeof(*ev) + eir_len, NULL); } static void disconnect_rsp(struct pending_cmd *cmd, void *data) @@ -2403,10 +2951,11 @@ static void disconnect_rsp(struct pending_cmd *cmd, void *data) struct sock **sk = data; struct mgmt_rp_disconnect rp; - bacpy(&rp.bdaddr, &cp->bdaddr); - rp.status = 0; + bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); + rp.addr.type = cp->addr.type; - cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, &rp, sizeof(rp)); + cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, 0, &rp, + sizeof(rp)); *sk = cmd->sk; sock_hold(*sk); @@ -2414,25 +2963,25 @@ static void disconnect_rsp(struct pending_cmd *cmd, void *data) mgmt_pending_remove(cmd); } -static void remove_keys_rsp(struct pending_cmd *cmd, void *data) +static void unpair_device_rsp(struct pending_cmd *cmd, void *data) { - u8 *status = data; - struct mgmt_cp_remove_keys *cp = cmd->param; - struct mgmt_rp_remove_keys rp; + struct hci_dev *hdev = data; + struct mgmt_cp_unpair_device *cp = cmd->param; + struct mgmt_rp_unpair_device rp; memset(&rp, 0, sizeof(rp)); - bacpy(&rp.bdaddr, &cp->bdaddr); - if (status != NULL) - rp.status = *status; + bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); + rp.addr.type = cp->addr.type; - cmd_complete(cmd->sk, cmd->index, MGMT_OP_REMOVE_KEYS, &rp, - sizeof(rp)); + device_unpaired(hdev, &cp->addr.bdaddr, cp->addr.type, cmd->sk); + + cmd_complete(cmd->sk, cmd->index, cmd->opcode, 0, &rp, sizeof(rp)); mgmt_pending_remove(cmd); } -int mgmt_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type) +int mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 link_type, u8 addr_type) { struct mgmt_addr_info ev; struct sock *sk = NULL; @@ -2443,45 +2992,44 @@ int mgmt_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, bacpy(&ev.bdaddr, bdaddr); ev.type = link_to_mgmt(link_type, addr_type); - err = mgmt_event(MGMT_EV_DISCONNECTED, hdev, &ev, sizeof(ev), sk); + err = mgmt_event(MGMT_EV_DEVICE_DISCONNECTED, hdev, &ev, sizeof(ev), + sk); if (sk) - sock_put(sk); + sock_put(sk); - mgmt_pending_foreach(MGMT_OP_REMOVE_KEYS, hdev, remove_keys_rsp, NULL); + mgmt_pending_foreach(MGMT_OP_UNPAIR_DEVICE, hdev, unpair_device_rsp, + hdev); return err; } -int mgmt_disconnect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 status) +int mgmt_disconnect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 link_type, u8 addr_type, u8 status) { + struct mgmt_rp_disconnect rp; struct pending_cmd *cmd; - u8 mgmt_err = mgmt_status(status); int err; cmd = mgmt_pending_find(MGMT_OP_DISCONNECT, hdev); if (!cmd) return -ENOENT; - if (bdaddr) { - struct mgmt_rp_disconnect rp; + bacpy(&rp.addr.bdaddr, bdaddr); + rp.addr.type = link_to_mgmt(link_type, addr_type); - bacpy(&rp.bdaddr, bdaddr); - rp.status = status; - - err = cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, - &rp, sizeof(rp)); - } else - err = cmd_status(cmd->sk, hdev->id, MGMT_OP_DISCONNECT, - mgmt_err); + err = cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, + mgmt_status(status), &rp, sizeof(rp)); mgmt_pending_remove(cmd); + mgmt_pending_foreach(MGMT_OP_UNPAIR_DEVICE, hdev, unpair_device_rsp, + hdev); return err; } int mgmt_connect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 status) + u8 addr_type, u8 status) { struct mgmt_ev_connect_failed ev; @@ -2496,15 +3044,16 @@ int mgmt_pin_code_request(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 secure) { struct mgmt_ev_pin_code_request ev; - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = MGMT_ADDR_BREDR; ev.secure = secure; return mgmt_event(MGMT_EV_PIN_CODE_REQUEST, hdev, &ev, sizeof(ev), - NULL); + NULL); } int mgmt_pin_code_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) + u8 status) { struct pending_cmd *cmd; struct mgmt_rp_pin_code_reply rp; @@ -2514,11 +3063,11 @@ int mgmt_pin_code_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, if (!cmd) return -ENOENT; - bacpy(&rp.bdaddr, bdaddr); - rp.status = mgmt_status(status); + bacpy(&rp.addr.bdaddr, bdaddr); + rp.addr.type = MGMT_ADDR_BREDR; - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, &rp, - sizeof(rp)); + err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, + mgmt_status(status), &rp, sizeof(rp)); mgmt_pending_remove(cmd); @@ -2526,7 +3075,7 @@ int mgmt_pin_code_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, } int mgmt_pin_code_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) + u8 status) { struct pending_cmd *cmd; struct mgmt_rp_pin_code_reply rp; @@ -2536,11 +3085,11 @@ int mgmt_pin_code_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, if (!cmd) return -ENOENT; - bacpy(&rp.bdaddr, bdaddr); - rp.status = mgmt_status(status); + bacpy(&rp.addr.bdaddr, bdaddr); + rp.addr.type = MGMT_ADDR_BREDR; - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_NEG_REPLY, &rp, - sizeof(rp)); + err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_NEG_REPLY, + mgmt_status(status), &rp, sizeof(rp)); mgmt_pending_remove(cmd); @@ -2548,34 +3097,39 @@ int mgmt_pin_code_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, } int mgmt_user_confirm_request(struct hci_dev *hdev, bdaddr_t *bdaddr, - __le32 value, u8 confirm_hint) + u8 link_type, u8 addr_type, __le32 value, + u8 confirm_hint) { struct mgmt_ev_user_confirm_request ev; BT_DBG("%s", hdev->name); - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = link_to_mgmt(link_type, addr_type); ev.confirm_hint = confirm_hint; put_unaligned_le32(value, &ev.value); return mgmt_event(MGMT_EV_USER_CONFIRM_REQUEST, hdev, &ev, sizeof(ev), - NULL); + NULL); } -int mgmt_user_passkey_request(struct hci_dev *hdev, bdaddr_t *bdaddr) +int mgmt_user_passkey_request(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 link_type, u8 addr_type) { struct mgmt_ev_user_passkey_request ev; BT_DBG("%s", hdev->name); - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = link_to_mgmt(link_type, addr_type); return mgmt_event(MGMT_EV_USER_PASSKEY_REQUEST, hdev, &ev, sizeof(ev), - NULL); + NULL); } static int user_pairing_resp_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status, u8 opcode) + u8 link_type, u8 addr_type, u8 status, + u8 opcode) { struct pending_cmd *cmd; struct mgmt_rp_user_confirm_reply rp; @@ -2585,9 +3139,10 @@ static int user_pairing_resp_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, if (!cmd) return -ENOENT; - bacpy(&rp.bdaddr, bdaddr); - rp.status = mgmt_status(status); - err = cmd_complete(cmd->sk, hdev->id, opcode, &rp, sizeof(rp)); + bacpy(&rp.addr.bdaddr, bdaddr); + rp.addr.type = link_to_mgmt(link_type, addr_type); + err = cmd_complete(cmd->sk, hdev->id, opcode, mgmt_status(status), + &rp, sizeof(rp)); mgmt_pending_remove(cmd); @@ -2595,72 +3150,215 @@ static int user_pairing_resp_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, } int mgmt_user_confirm_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) + u8 link_type, u8 addr_type, u8 status) { - return user_pairing_resp_complete(hdev, bdaddr, status, - MGMT_OP_USER_CONFIRM_REPLY); + return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, + status, MGMT_OP_USER_CONFIRM_REPLY); } -int mgmt_user_confirm_neg_reply_complete(struct hci_dev *hdev, - bdaddr_t *bdaddr, u8 status) +int mgmt_user_confirm_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 link_type, u8 addr_type, u8 status) { - return user_pairing_resp_complete(hdev, bdaddr, status, - MGMT_OP_USER_CONFIRM_NEG_REPLY); + return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, + status, MGMT_OP_USER_CONFIRM_NEG_REPLY); } int mgmt_user_passkey_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) + u8 link_type, u8 addr_type, u8 status) { - return user_pairing_resp_complete(hdev, bdaddr, status, - MGMT_OP_USER_PASSKEY_REPLY); + return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, + status, MGMT_OP_USER_PASSKEY_REPLY); } -int mgmt_user_passkey_neg_reply_complete(struct hci_dev *hdev, - bdaddr_t *bdaddr, u8 status) +int mgmt_user_passkey_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, + u8 link_type, u8 addr_type, u8 status) { - return user_pairing_resp_complete(hdev, bdaddr, status, - MGMT_OP_USER_PASSKEY_NEG_REPLY); + return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, + status, MGMT_OP_USER_PASSKEY_NEG_REPLY); } -int mgmt_auth_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 status) +int mgmt_auth_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, + u8 addr_type, u8 status) { struct mgmt_ev_auth_failed ev; - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = link_to_mgmt(link_type, addr_type); ev.status = mgmt_status(status); return mgmt_event(MGMT_EV_AUTH_FAILED, hdev, &ev, sizeof(ev), NULL); } +int mgmt_auth_enable_complete(struct hci_dev *hdev, u8 status) +{ + struct cmd_lookup match = { NULL, hdev }; + bool changed = false; + int err = 0; + + if (status) { + u8 mgmt_err = mgmt_status(status); + mgmt_pending_foreach(MGMT_OP_SET_LINK_SECURITY, hdev, + cmd_status_rsp, &mgmt_err); + return 0; + } + + if (test_bit(HCI_AUTH, &hdev->flags)) { + if (!test_and_set_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) + changed = true; + } else { + if (test_and_clear_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) + changed = true; + } + + mgmt_pending_foreach(MGMT_OP_SET_LINK_SECURITY, hdev, settings_rsp, + &match); + + if (changed) + err = new_settings(hdev, match.sk); + + if (match.sk) + sock_put(match.sk); + + return err; +} + +static int clear_eir(struct hci_dev *hdev) +{ + struct hci_cp_write_eir cp; + + if (!(hdev->features[6] & LMP_EXT_INQ)) + return 0; + + memset(hdev->eir, 0, sizeof(hdev->eir)); + + memset(&cp, 0, sizeof(cp)); + + return hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); +} + +int mgmt_ssp_enable_complete(struct hci_dev *hdev, u8 enable, u8 status) +{ + struct cmd_lookup match = { NULL, hdev }; + bool changed = false; + int err = 0; + + if (status) { + u8 mgmt_err = mgmt_status(status); + + if (enable && test_and_clear_bit(HCI_SSP_ENABLED, + &hdev->dev_flags)) + err = new_settings(hdev, NULL); + + mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, cmd_status_rsp, + &mgmt_err); + + return err; + } + + if (enable) { + if (!test_and_set_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) + changed = true; + } else { + if (test_and_clear_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) + changed = true; + } + + mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, settings_rsp, &match); + + if (changed) + err = new_settings(hdev, match.sk); + + if (match.sk) + sock_put(match.sk); + + if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) + update_eir(hdev); + else + clear_eir(hdev); + + return err; +} + +static void class_rsp(struct pending_cmd *cmd, void *data) +{ + struct cmd_lookup *match = data; + + cmd_complete(cmd->sk, cmd->index, cmd->opcode, match->mgmt_status, + match->hdev->dev_class, 3); + + list_del(&cmd->list); + + if (match->sk == NULL) { + match->sk = cmd->sk; + sock_hold(match->sk); + } + + mgmt_pending_free(cmd); +} + +int mgmt_set_class_of_dev_complete(struct hci_dev *hdev, u8 *dev_class, + u8 status) +{ + struct cmd_lookup match = { NULL, hdev, mgmt_status(status) }; + int err = 0; + + clear_bit(HCI_PENDING_CLASS, &hdev->dev_flags); + + mgmt_pending_foreach(MGMT_OP_SET_DEV_CLASS, hdev, class_rsp, &match); + mgmt_pending_foreach(MGMT_OP_ADD_UUID, hdev, class_rsp, &match); + mgmt_pending_foreach(MGMT_OP_REMOVE_UUID, hdev, class_rsp, &match); + + if (!status) + err = mgmt_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev, dev_class, + 3, NULL); + + if (match.sk) + sock_put(match.sk); + + return err; +} + int mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status) { struct pending_cmd *cmd; struct mgmt_cp_set_local_name ev; - int err; + bool changed = false; + int err = 0; + + if (memcmp(name, hdev->dev_name, sizeof(hdev->dev_name)) != 0) { + memcpy(hdev->dev_name, name, sizeof(hdev->dev_name)); + changed = true; + } memset(&ev, 0, sizeof(ev)); memcpy(ev.name, name, HCI_MAX_NAME_LENGTH); + memcpy(ev.short_name, hdev->short_name, HCI_MAX_SHORT_NAME_LENGTH); cmd = mgmt_pending_find(MGMT_OP_SET_LOCAL_NAME, hdev); if (!cmd) goto send_event; + /* Always assume that either the short or the complete name has + * changed if there was a pending mgmt command */ + changed = true; + if (status) { err = cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, - mgmt_status(status)); + mgmt_status(status)); goto failed; } - update_eir(hdev); - - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, &ev, - sizeof(ev)); + err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0, &ev, + sizeof(ev)); if (err < 0) goto failed; send_event: - err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, &ev, sizeof(ev), - cmd ? cmd->sk : NULL); + if (changed) + err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, &ev, + sizeof(ev), cmd ? cmd->sk : NULL); + + update_eir(hdev); failed: if (cmd) @@ -2669,7 +3367,7 @@ failed: } int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, - u8 *randomizer, u8 status) + u8 *randomizer, u8 status) { struct pending_cmd *cmd; int err; @@ -2681,9 +3379,8 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, return -ENOENT; if (status) { - err = cmd_status(cmd->sk, hdev->id, - MGMT_OP_READ_LOCAL_OOB_DATA, - mgmt_status(status)); + err = cmd_status(cmd->sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, + mgmt_status(status)); } else { struct mgmt_rp_read_local_oob_data rp; @@ -2691,8 +3388,8 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, memcpy(rp.randomizer, randomizer, sizeof(rp.randomizer)); err = cmd_complete(cmd->sk, hdev->id, - MGMT_OP_READ_LOCAL_OOB_DATA, - &rp, sizeof(rp)); + MGMT_OP_READ_LOCAL_OOB_DATA, 0, &rp, + sizeof(rp)); } mgmt_pending_remove(cmd); @@ -2700,48 +3397,120 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, return err; } +int mgmt_le_enable_complete(struct hci_dev *hdev, u8 enable, u8 status) +{ + struct cmd_lookup match = { NULL, hdev }; + bool changed = false; + int err = 0; + + if (status) { + u8 mgmt_err = mgmt_status(status); + + if (enable && test_and_clear_bit(HCI_LE_ENABLED, + &hdev->dev_flags)) + err = new_settings(hdev, NULL); + + mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, + cmd_status_rsp, &mgmt_err); + + return err; + } + + if (enable) { + if (!test_and_set_bit(HCI_LE_ENABLED, &hdev->dev_flags)) + changed = true; + } else { + if (test_and_clear_bit(HCI_LE_ENABLED, &hdev->dev_flags)) + changed = true; + } + + mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match); + + if (changed) + err = new_settings(hdev, match.sk); + + if (match.sk) + sock_put(match.sk); + + return err; +} + int mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 *dev_class, s8 rssi, u8 *eir) + u8 addr_type, u8 *dev_class, s8 rssi, u8 cfm_name, u8 + ssp, u8 *eir, u16 eir_len) { - struct mgmt_ev_device_found ev; + char buf[512]; + struct mgmt_ev_device_found *ev = (void *) buf; + size_t ev_size; - memset(&ev, 0, sizeof(ev)); + /* Leave 5 bytes for a potential CoD field */ + if (sizeof(*ev) + eir_len + 5 > sizeof(buf)) + return -EINVAL; - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = link_to_mgmt(link_type, addr_type); - ev.rssi = rssi; + memset(buf, 0, sizeof(buf)); + + bacpy(&ev->addr.bdaddr, bdaddr); + ev->addr.type = link_to_mgmt(link_type, addr_type); + ev->rssi = rssi; + if (cfm_name) + ev->flags[0] |= MGMT_DEV_FOUND_CONFIRM_NAME; + if (!ssp) + ev->flags[0] |= MGMT_DEV_FOUND_LEGACY_PAIRING; + + if (eir_len > 0) + memcpy(ev->eir, eir, eir_len); - if (eir) - memcpy(ev.eir, eir, sizeof(ev.eir)); + if (dev_class && !eir_has_data_type(ev->eir, eir_len, EIR_CLASS_OF_DEV)) + eir_len = eir_append_data(ev->eir, eir_len, EIR_CLASS_OF_DEV, + dev_class, 3); - if (dev_class) - memcpy(ev.dev_class, dev_class, sizeof(ev.dev_class)); + put_unaligned_le16(eir_len, &ev->eir_len); - return mgmt_event(MGMT_EV_DEVICE_FOUND, hdev, &ev, sizeof(ev), NULL); + ev_size = sizeof(*ev) + eir_len; + + return mgmt_event(MGMT_EV_DEVICE_FOUND, hdev, ev, ev_size, NULL); } -int mgmt_remote_name(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *name) +int mgmt_remote_name(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, + u8 addr_type, s8 rssi, u8 *name, u8 name_len) { - struct mgmt_ev_remote_name ev; + struct mgmt_ev_device_found *ev; + char buf[sizeof(*ev) + HCI_MAX_NAME_LENGTH + 2]; + u16 eir_len; - memset(&ev, 0, sizeof(ev)); + ev = (struct mgmt_ev_device_found *) buf; - bacpy(&ev.bdaddr, bdaddr); - memcpy(ev.name, name, HCI_MAX_NAME_LENGTH); + memset(buf, 0, sizeof(buf)); + + bacpy(&ev->addr.bdaddr, bdaddr); + ev->addr.type = link_to_mgmt(link_type, addr_type); + ev->rssi = rssi; + + eir_len = eir_append_data(ev->eir, 0, EIR_NAME_COMPLETE, name, + name_len); + + put_unaligned_le16(eir_len, &ev->eir_len); - return mgmt_event(MGMT_EV_REMOTE_NAME, hdev, &ev, sizeof(ev), NULL); + return mgmt_event(MGMT_EV_DEVICE_FOUND, hdev, ev, + sizeof(*ev) + eir_len, NULL); } int mgmt_start_discovery_failed(struct hci_dev *hdev, u8 status) { struct pending_cmd *cmd; + u8 type; int err; + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + cmd = mgmt_pending_find(MGMT_OP_START_DISCOVERY, hdev); if (!cmd) return -ENOENT; - err = cmd_status(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status)); + type = hdev->discovery.type; + + err = cmd_complete(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status), + &type, sizeof(type)); mgmt_pending_remove(cmd); return err; @@ -2756,7 +3525,8 @@ int mgmt_stop_discovery_failed(struct hci_dev *hdev, u8 status) if (!cmd) return -ENOENT; - err = cmd_status(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status)); + err = cmd_complete(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status), + &hdev->discovery.type, sizeof(hdev->discovery.type)); mgmt_pending_remove(cmd); return err; @@ -2764,44 +3534,61 @@ int mgmt_stop_discovery_failed(struct hci_dev *hdev, u8 status) int mgmt_discovering(struct hci_dev *hdev, u8 discovering) { + struct mgmt_ev_discovering ev; struct pending_cmd *cmd; + BT_DBG("%s discovering %u", hdev->name, discovering); + if (discovering) cmd = mgmt_pending_find(MGMT_OP_START_DISCOVERY, hdev); else cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, hdev); if (cmd != NULL) { - cmd_complete(cmd->sk, hdev->id, cmd->opcode, NULL, 0); + u8 type = hdev->discovery.type; + + cmd_complete(cmd->sk, hdev->id, cmd->opcode, 0, &type, + sizeof(type)); mgmt_pending_remove(cmd); } - return mgmt_event(MGMT_EV_DISCOVERING, hdev, &discovering, - sizeof(discovering), NULL); + memset(&ev, 0, sizeof(ev)); + ev.type = hdev->discovery.type; + ev.discovering = discovering; + + return mgmt_event(MGMT_EV_DISCOVERING, hdev, &ev, sizeof(ev), NULL); } -int mgmt_device_blocked(struct hci_dev *hdev, bdaddr_t *bdaddr) +int mgmt_device_blocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) { struct pending_cmd *cmd; struct mgmt_ev_device_blocked ev; cmd = mgmt_pending_find(MGMT_OP_BLOCK_DEVICE, hdev); - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = type; return mgmt_event(MGMT_EV_DEVICE_BLOCKED, hdev, &ev, sizeof(ev), - cmd ? cmd->sk : NULL); + cmd ? cmd->sk : NULL); } -int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr) +int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) { struct pending_cmd *cmd; struct mgmt_ev_device_unblocked ev; cmd = mgmt_pending_find(MGMT_OP_UNBLOCK_DEVICE, hdev); - bacpy(&ev.bdaddr, bdaddr); + bacpy(&ev.addr.bdaddr, bdaddr); + ev.addr.type = type; return mgmt_event(MGMT_EV_DEVICE_UNBLOCKED, hdev, &ev, sizeof(ev), - cmd ? cmd->sk : NULL); + cmd ? cmd->sk : NULL); } + +module_param(enable_hs, bool, 0644); +MODULE_PARM_DESC(enable_hs, "Enable High Speed support"); + +module_param(enable_le, bool, 0644); +MODULE_PARM_DESC(enable_le, "Enable Low Energy support"); |