diff options
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 bc8e59dda78..7fcff888713 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; |