Bluetooth: Add class of device control to the management interface
authorJohan Hedberg <johan.hedberg@nokia.com>
Thu, 13 Jan 2011 19:56:52 +0000 (21:56 +0200)
committerGustavo F. Padovan <padovan@profusion.mobi>
Tue, 8 Feb 2011 03:40:06 +0000 (01:40 -0200)
This patch adds the possibility for user space to fully control the
Class of Device value of local adapters. To control the service class
bits each UUID that's added comes with a service class "hint" which acts
as a mask of bits that the UUID needs to have enabled. The
set_service_cache management command is used to make sure we queue up
all UUID changes as user space initializes its drivers and then send a
single HCI_Write_Class_of_Device command when initialization is
complete.

Signed-off-by: Johan Hedberg <johan.hedberg@nokia.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
include/net/bluetooth/hci.h
include/net/bluetooth/hci_core.h
include/net/bluetooth/mgmt.h
net/bluetooth/mgmt.c

index 99ac3516fe9d8c6ae2b6e5917f948d80e3025b8d..9ce46cd00ba22563ddff5b7f617bcdd9cb1c47c1 100644 (file)
@@ -81,6 +81,7 @@ enum {
        HCI_AUTO_OFF,
        HCI_MGMT,
        HCI_PAIRABLE,
+       HCI_SERVICE_CACHE,
 };
 
 /* HCI ioctl defines */
index 71a3fbf1e785f37e7b816ccab68d5c99bdb8821e..e62da084e01d0054a68a3df52e9add7f04d290d8 100644 (file)
@@ -70,6 +70,7 @@ struct bdaddr_list {
 struct bt_uuid {
        struct list_head list;
        u8 uuid[16];
+       u8 svc_hint;
 };
 
 #define NUM_REASSEMBLY 4
@@ -86,6 +87,8 @@ struct hci_dev {
        bdaddr_t        bdaddr;
        __u8            dev_name[248];
        __u8            dev_class[3];
+       __u8            major_class;
+       __u8            minor_class;
        __u8            features[8];
        __u8            commands[64];
        __u8            ssp_mode;
index c118ad3af332ae827e69e49e5ff91ad291332c53..b092c4c014eb9c6eb67aa58755c7e99f30035397 100644 (file)
@@ -76,6 +76,7 @@ struct mgmt_mode {
 struct mgmt_cp_add_uuid {
        __le16 index;
        __u8 uuid[16];
+       __u8 svc_hint;
 } __packed;
 
 #define MGMT_OP_REMOVE_UUID            0x000A
@@ -84,6 +85,19 @@ struct mgmt_cp_remove_uuid {
        __u8 uuid[16];
 } __packed;
 
+#define MGMT_OP_SET_DEV_CLASS          0x000B
+struct mgmt_cp_set_dev_class {
+       __le16 index;
+       __u8 major;
+       __u8 minor;
+} __packed;
+
+#define MGMT_OP_SET_SERVICE_CACHE      0x000C
+struct mgmt_cp_set_service_cache {
+       __le16 index;
+       __u8 enable;
+} __packed;
+
 #define MGMT_EV_CMD_COMPLETE           0x0001
 struct mgmt_ev_cmd_complete {
        __le16 opcode;
index 0854c2f1073c89eeb2c8f4c6197bd7931a59cd27..a08f4ce03182239990399093d2d70a48ad1edd5b 100644 (file)
@@ -571,7 +571,7 @@ failed:
        return err;
 }
 
-static int uuid_rsp(struct sock *sk, u16 opcode, u16 index)
+static int index_rsp(struct sock *sk, u16 opcode, u16 index)
 {
        struct mgmt_hdr *hdr;
        struct mgmt_ev_cmd_complete *ev;
@@ -596,6 +596,39 @@ static int uuid_rsp(struct sock *sk, u16 opcode, u16 index)
        return 0;
 }
 
+static u8 get_service_classes(struct hci_dev *hdev)
+{
+       struct list_head *p;
+       u8 val = 0;
+
+       list_for_each(p, &hdev->uuids) {
+               struct bt_uuid *uuid = list_entry(p, struct bt_uuid, list);
+
+               val |= uuid->svc_hint;
+       }
+
+       return val;
+}
+
+static int update_class(struct hci_dev *hdev)
+{
+       u8 cod[3];
+
+       BT_DBG("%s", hdev->name);
+
+       if (test_bit(HCI_SERVICE_CACHE, &hdev->flags))
+               return 0;
+
+       cod[0] = hdev->minor_class;
+       cod[1] = hdev->major_class;
+       cod[2] = get_service_classes(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);
+}
+
 static int add_uuid(struct sock *sk, unsigned char *data, u16 len)
 {
        struct mgmt_cp_add_uuid *cp;
@@ -622,10 +655,15 @@ static int add_uuid(struct sock *sk, unsigned char *data, u16 len)
        }
 
        memcpy(uuid->uuid, cp->uuid, 16);
+       uuid->svc_hint = cp->svc_hint;
 
        list_add(&uuid->list, &hdev->uuids);
 
-       err = uuid_rsp(sk, MGMT_OP_ADD_UUID, dev_id);
+       err = update_class(hdev);
+       if (err < 0)
+               goto failed;
+
+       err = index_rsp(sk, MGMT_OP_ADD_UUID, dev_id);
 
 failed:
        hci_dev_unlock_bh(hdev);
@@ -676,7 +714,11 @@ static int remove_uuid(struct sock *sk, unsigned char *data, u16 len)
                goto unlock;
        }
 
-       err = uuid_rsp(sk, MGMT_OP_REMOVE_UUID, dev_id);
+       err = update_class(hdev);
+       if (err < 0)
+               goto unlock;
+
+       err = index_rsp(sk, MGMT_OP_REMOVE_UUID, dev_id);
 
 unlock:
        hci_dev_unlock_bh(hdev);
@@ -685,6 +727,73 @@ unlock:
        return err;
 }
 
+static int set_dev_class(struct sock *sk, unsigned char *data, u16 len)
+{
+       struct hci_dev *hdev;
+       struct mgmt_cp_set_dev_class *cp;
+       u16 dev_id;
+       int err;
+
+       cp = (void *) data;
+       dev_id = get_unaligned_le16(&cp->index);
+
+       BT_DBG("request for hci%u", dev_id);
+
+       hdev = hci_dev_get(dev_id);
+       if (!hdev)
+               return cmd_status(sk, MGMT_OP_SET_DEV_CLASS, ENODEV);
+
+       hci_dev_lock_bh(hdev);
+
+       hdev->major_class = cp->major;
+       hdev->minor_class = cp->minor;
+
+       err = update_class(hdev);
+
+       if (err == 0)
+               err = index_rsp(sk, MGMT_OP_SET_DEV_CLASS, dev_id);
+
+       hci_dev_unlock_bh(hdev);
+       hci_dev_put(hdev);
+
+       return err;
+}
+
+static int set_service_cache(struct sock *sk, unsigned char *data, u16 len)
+{
+       struct hci_dev *hdev;
+       struct mgmt_cp_set_service_cache *cp;
+       u16 dev_id;
+       int err;
+
+       cp = (void *) data;
+       dev_id = get_unaligned_le16(&cp->index);
+
+       hdev = hci_dev_get(dev_id);
+       if (!hdev)
+               return cmd_status(sk, MGMT_OP_SET_SERVICE_CACHE, ENODEV);
+
+       hci_dev_lock_bh(hdev);
+
+       BT_DBG("hci%u enable %d", dev_id, cp->enable);
+
+       if (cp->enable) {
+               set_bit(HCI_SERVICE_CACHE, &hdev->flags);
+               err = 0;
+       } else {
+               clear_bit(HCI_SERVICE_CACHE, &hdev->flags);
+               err = update_class(hdev);
+       }
+
+       if (err == 0)
+               err = index_rsp(sk, MGMT_OP_SET_SERVICE_CACHE, dev_id);
+
+       hci_dev_unlock_bh(hdev);
+       hci_dev_put(hdev);
+
+       return err;
+}
+
 int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
 {
        unsigned char *buf;
@@ -743,6 +852,12 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
        case MGMT_OP_REMOVE_UUID:
                err = remove_uuid(sk, buf + sizeof(*hdr), len);
                break;
+       case MGMT_OP_SET_DEV_CLASS:
+               err = set_dev_class(sk, buf + sizeof(*hdr), len);
+               break;
+       case MGMT_OP_SET_SERVICE_CACHE:
+               err = set_service_cache(sk, buf + sizeof(*hdr), len);
+               break;
        default:
                BT_DBG("Unknown op %u", opcode);
                err = cmd_status(sk, opcode, 0x01);