Bluetooth: Add support for changing the public device address
authorMarcel Holtmann <marcel@holtmann.org>
Sun, 6 Jul 2014 10:11:15 +0000 (12:11 +0200)
committerJohan Hedberg <johan.hedberg@intel.com>
Sun, 6 Jul 2014 10:42:20 +0000 (13:42 +0300)
This adds support for changing the public device address. This feature
is required by controllers that do not provide a public address and
have HCI_QUIRK_INVALID_BDADDR set.

Even if a controller has a public device address, this is useful when
an embedded system wants to use its own value. As long as the driver
provides the set_bdaddr callback, this allows changing the device
address before powering on the controller.

Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
include/net/bluetooth/mgmt.h
net/bluetooth/hci_core.c
net/bluetooth/mgmt.c

index 80606d2fe086391049e049376d67d851fe781577..623d5203c5926292d626ff5aa2d5311f3dfcb33b 100644 (file)
@@ -489,6 +489,12 @@ struct mgmt_cp_set_external_config {
 } __packed;
 #define MGMT_SET_EXTERNAL_CONFIG_SIZE  1
 
+#define MGMT_OP_SET_PUBLIC_ADDRESS     0x0039
+struct mgmt_cp_set_public_address {
+       bdaddr_t bdaddr;
+} __packed;
+#define MGMT_SET_PUBLIC_ADDRESS_SIZE   6
+
 #define MGMT_EV_CMD_COMPLETE           0x0001
 struct mgmt_ev_cmd_complete {
        __le16  opcode;
index bf7cf512df8895142adfd85e0bbfb227d70b6107..c96b96ca41ac6ea88c38d26cc2fbc21537274b25 100644 (file)
@@ -2302,12 +2302,14 @@ static int hci_dev_do_open(struct hci_dev *hdev)
                        ret = __hci_unconf_init(hdev);
        }
 
-       /* If public address change is configured, ensure that the
-        * address gets programmed. If the driver does not support
-        * changing the public address, fail the power on procedure.
-        */
-       if (!ret && bacmp(&hdev->public_addr, BDADDR_ANY)) {
-               if (hdev->set_bdaddr)
+       if (test_bit(HCI_CONFIG, &hdev->dev_flags)) {
+               /* If public address change is configured, ensure that
+                * the address gets programmed. If the driver does not
+                * support changing the public address, fail the power
+                * on procedure.
+                */
+               if (bacmp(&hdev->public_addr, BDADDR_ANY) &&
+                   hdev->set_bdaddr)
                        ret = hdev->set_bdaddr(hdev, &hdev->public_addr);
                else
                        ret = -EADDRNOTAVAIL;
index d66463a52280407c1e56594650e0a4c028d3edda..8275316ae0991b2ca0b8d2523ced059e942706e1 100644 (file)
@@ -92,6 +92,7 @@ static const u16 mgmt_commands[] = {
        MGMT_OP_READ_UNCONF_INDEX_LIST,
        MGMT_OP_READ_CONFIG_INFO,
        MGMT_OP_SET_EXTERNAL_CONFIG,
+       MGMT_OP_SET_PUBLIC_ADDRESS,
 };
 
 static const u16 mgmt_events[] = {
@@ -5459,6 +5460,58 @@ unlock:
        return err;
 }
 
+static int set_public_address(struct sock *sk, struct hci_dev *hdev,
+                             void *data, u16 len)
+{
+       struct mgmt_cp_set_public_address *cp = data;
+       bool changed;
+       int err;
+
+       BT_DBG("%s", hdev->name);
+
+       if (hdev_is_powered(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_PUBLIC_ADDRESS,
+                                 MGMT_STATUS_REJECTED);
+
+       if (!bacmp(&cp->bdaddr, BDADDR_ANY))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_PUBLIC_ADDRESS,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
+       if (!hdev->set_bdaddr)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_PUBLIC_ADDRESS,
+                                 MGMT_STATUS_NOT_SUPPORTED);
+
+       hci_dev_lock(hdev);
+
+       changed = !!bacmp(&hdev->public_addr, &cp->bdaddr);
+       bacpy(&hdev->public_addr, &cp->bdaddr);
+
+       err = send_options_rsp(sk, MGMT_OP_SET_PUBLIC_ADDRESS, hdev);
+       if (err < 0)
+               goto unlock;
+
+       if (!changed)
+               goto unlock;
+
+       if (test_bit(HCI_UNCONFIGURED, &hdev->dev_flags))
+               err = new_options(hdev, sk);
+
+       if (is_configured(hdev)) {
+               mgmt_index_removed(hdev);
+
+               clear_bit(HCI_UNCONFIGURED, &hdev->dev_flags);
+
+               set_bit(HCI_CONFIG, &hdev->dev_flags);
+               set_bit(HCI_AUTO_OFF, &hdev->dev_flags);
+
+               queue_work(hdev->req_workqueue, &hdev->power_on);
+       }
+
+unlock:
+       hci_dev_unlock(hdev);
+       return err;
+}
+
 static const struct mgmt_handler {
        int (*func) (struct sock *sk, struct hci_dev *hdev, void *data,
                     u16 data_len);
@@ -5522,6 +5575,7 @@ static const struct mgmt_handler {
        { read_unconf_index_list, false, MGMT_READ_UNCONF_INDEX_LIST_SIZE },
        { read_config_info,       false, MGMT_READ_CONFIG_INFO_SIZE },
        { set_external_config,    false, MGMT_SET_EXTERNAL_CONFIG_SIZE },
+       { set_public_address,     false, MGMT_SET_PUBLIC_ADDRESS_SIZE },
 };
 
 int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
@@ -5576,7 +5630,8 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
 
                if (test_bit(HCI_UNCONFIGURED, &hdev->dev_flags) &&
                    opcode != MGMT_OP_READ_CONFIG_INFO &&
-                   opcode != MGMT_OP_SET_EXTERNAL_CONFIG) {
+                   opcode != MGMT_OP_SET_EXTERNAL_CONFIG &&
+                   opcode != MGMT_OP_SET_PUBLIC_ADDRESS) {
                        err = cmd_status(sk, index, opcode,
                                         MGMT_STATUS_INVALID_INDEX);
                        goto done;