Bluetooth: Add class of device control to the management interface
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>
This commit is contained in:
parent
d5859e22cd
commit
1aff6f0949
|
@ -81,6 +81,7 @@ enum {
|
||||||
HCI_AUTO_OFF,
|
HCI_AUTO_OFF,
|
||||||
HCI_MGMT,
|
HCI_MGMT,
|
||||||
HCI_PAIRABLE,
|
HCI_PAIRABLE,
|
||||||
|
HCI_SERVICE_CACHE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* HCI ioctl defines */
|
/* HCI ioctl defines */
|
||||||
|
|
|
@ -70,6 +70,7 @@ struct bdaddr_list {
|
||||||
struct bt_uuid {
|
struct bt_uuid {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
u8 uuid[16];
|
u8 uuid[16];
|
||||||
|
u8 svc_hint;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NUM_REASSEMBLY 4
|
#define NUM_REASSEMBLY 4
|
||||||
|
@ -86,6 +87,8 @@ struct hci_dev {
|
||||||
bdaddr_t bdaddr;
|
bdaddr_t bdaddr;
|
||||||
__u8 dev_name[248];
|
__u8 dev_name[248];
|
||||||
__u8 dev_class[3];
|
__u8 dev_class[3];
|
||||||
|
__u8 major_class;
|
||||||
|
__u8 minor_class;
|
||||||
__u8 features[8];
|
__u8 features[8];
|
||||||
__u8 commands[64];
|
__u8 commands[64];
|
||||||
__u8 ssp_mode;
|
__u8 ssp_mode;
|
||||||
|
|
|
@ -76,6 +76,7 @@ struct mgmt_mode {
|
||||||
struct mgmt_cp_add_uuid {
|
struct mgmt_cp_add_uuid {
|
||||||
__le16 index;
|
__le16 index;
|
||||||
__u8 uuid[16];
|
__u8 uuid[16];
|
||||||
|
__u8 svc_hint;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
#define MGMT_OP_REMOVE_UUID 0x000A
|
#define MGMT_OP_REMOVE_UUID 0x000A
|
||||||
|
@ -84,6 +85,19 @@ struct mgmt_cp_remove_uuid {
|
||||||
__u8 uuid[16];
|
__u8 uuid[16];
|
||||||
} __packed;
|
} __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
|
#define MGMT_EV_CMD_COMPLETE 0x0001
|
||||||
struct mgmt_ev_cmd_complete {
|
struct mgmt_ev_cmd_complete {
|
||||||
__le16 opcode;
|
__le16 opcode;
|
||||||
|
|
|
@ -571,7 +571,7 @@ failed:
|
||||||
return err;
|
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_hdr *hdr;
|
||||||
struct mgmt_ev_cmd_complete *ev;
|
struct mgmt_ev_cmd_complete *ev;
|
||||||
|
@ -596,6 +596,39 @@ static int uuid_rsp(struct sock *sk, u16 opcode, u16 index)
|
||||||
return 0;
|
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)
|
static int add_uuid(struct sock *sk, unsigned char *data, u16 len)
|
||||||
{
|
{
|
||||||
struct mgmt_cp_add_uuid *cp;
|
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);
|
memcpy(uuid->uuid, cp->uuid, 16);
|
||||||
|
uuid->svc_hint = cp->svc_hint;
|
||||||
|
|
||||||
list_add(&uuid->list, &hdev->uuids);
|
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:
|
failed:
|
||||||
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
||||||
|
@ -676,7 +714,11 @@ static int remove_uuid(struct sock *sk, unsigned char *data, u16 len)
|
||||||
goto unlock;
|
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:
|
unlock:
|
||||||
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
||||||
|
@ -685,6 +727,73 @@ unlock:
|
||||||
return err;
|
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)
|
int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
|
||||||
{
|
{
|
||||||
unsigned char *buf;
|
unsigned char *buf;
|
||||||
|
@ -743,6 +852,12 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
|
||||||
case MGMT_OP_REMOVE_UUID:
|
case MGMT_OP_REMOVE_UUID:
|
||||||
err = remove_uuid(sk, buf + sizeof(*hdr), len);
|
err = remove_uuid(sk, buf + sizeof(*hdr), len);
|
||||||
break;
|
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:
|
default:
|
||||||
BT_DBG("Unknown op %u", opcode);
|
BT_DBG("Unknown op %u", opcode);
|
||||||
err = cmd_status(sk, opcode, 0x01);
|
err = cmd_status(sk, opcode, 0x01);
|
||||||
|
|
Loading…
Reference in a new issue