@@ -30,6 +30,24 @@
static bool amp;
+/* This is the struct for extended device configuration.
+ * The opcode 0x03 is used for creating an extended device and followed by
+ * the configuration data below.
+ * dev_type is Primay or AMP.
+ * flag_len is the length of flag array
+ * flag array contains the flag to use/set while creating the device.
+ */
+struct vhci_ext_config {
+ __u8 dev_type;
+ __u8 flag_len;
+ __u8 flag[0];
+};
+
+#define VHCI_EXT_FLAG_ENABLE_AOSP 0x01
+#define VHCI_EXT_FLAG_QUIRK_RAW_DEVICE 0x02
+#define VHCI_EXT_FLAG_QUIARK_EXTERNAL_CONFIG 0x03
+#define VHCI_EXT_FLAG_QUIRK_INVALID_BDADDR 0x04
+
struct vhci_data {
struct hci_dev *hdev;
@@ -375,6 +393,123 @@ static int vhci_create_device(struct vhci_data *data, __u8 opcode)
return err;
}
+static int __vhci_create_extended_device(struct vhci_data *data,
+ struct sk_buff *skb)
+{
+ struct hci_dev *hdev;
+ struct sk_buff *resp;
+ struct vhci_ext_config *config;
+ int i;
+ __u8 flag;
+
+ if (data->hdev)
+ return -EBADFD;
+
+ /* Make sure the skb has a minimum valid length */
+ if (skb->len < sizeof(*config))
+ return -EINVAL;
+
+ config = (void *)(skb->data);
+ if (skb->len < sizeof(*config) + config->flag_len)
+ return -EINVAL;
+
+ if (config->dev_type != HCI_PRIMARY && config->dev_type != HCI_AMP)
+ return -EINVAL;
+
+ resp = bt_skb_alloc(4, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ hdev = hci_alloc_dev();
+ if (!hdev) {
+ kfree_skb(resp);
+ return -ENOMEM;
+ }
+
+ data->hdev = hdev;
+
+ hdev->bus = HCI_VIRTUAL;
+ hdev->dev_type = config->dev_type;
+ hci_set_drvdata(hdev, data);
+
+ hdev->open = vhci_open_dev;
+ hdev->close = vhci_close_dev;
+ hdev->flush = vhci_flush;
+ hdev->send = vhci_send_frame;
+ hdev->get_data_path_id = vhci_get_data_path_id;
+ hdev->get_codec_config_data = vhci_get_codec_config_data;
+ hdev->wakeup = vhci_wakeup;
+ hdev->setup = vhci_setup;
+ set_bit(HCI_QUIRK_NON_PERSISTENT_SETUP, &hdev->quirks);
+
+ for (i = 0; i < config->flag_len; i++) {
+ flag = config->flag[i];
+ switch (flag) {
+ case VHCI_EXT_FLAG_ENABLE_AOSP:
+ data->aosp_capable = 1;
+ break;
+ case VHCI_EXT_FLAG_QUIRK_RAW_DEVICE:
+ set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks);
+ break;
+ case VHCI_EXT_FLAG_QUIARK_EXTERNAL_CONFIG:
+ set_bit(HCI_QUIRK_EXTERNAL_CONFIG, &hdev->quirks);
+ break;
+ case VHCI_EXT_FLAG_QUIRK_INVALID_BDADDR:
+ set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks);
+ break;
+ default:
+ BT_ERR("Invalid flag");
+ hci_free_dev(hdev);
+ data->hdev = NULL;
+ kfree_skb(resp);
+ return -EINVAL;
+ }
+ }
+
+ if (hci_register_dev(hdev) < 0) {
+ BT_ERR("Can't register HCI device");
+ hci_free_dev(hdev);
+ data->hdev = NULL;
+ kfree_skb(resp);
+ return -EBUSY;
+ }
+
+ debugfs_create_file("force_suspend", 0644, hdev->debugfs, data,
+ &force_suspend_fops);
+
+ debugfs_create_file("force_wakeup", 0644, hdev->debugfs, data,
+ &force_wakeup_fops);
+
+ if (IS_ENABLED(CONFIG_BT_MSFTEXT))
+ debugfs_create_file("msft_opcode", 0644, hdev->debugfs, data,
+ &msft_opcode_fops);
+
+ if (IS_ENABLED(CONFIG_BT_AOSPEXT))
+ debugfs_create_file("aosp_capable", 0644, hdev->debugfs, data,
+ &aosp_capable_fops);
+
+ hci_skb_pkt_type(resp) = HCI_VENDOR_PKT;
+
+ skb_put_u8(resp, 0xff);
+ skb_put_u8(resp, 0x03);
+ put_unaligned_le16(hdev->id, skb_put(resp, 2));
+ skb_queue_tail(&data->readq, resp);
+
+ wake_up_interruptible(&data->read_wait);
+ return 0;
+}
+
+static int vhci_create_extended_device(struct vhci_data *data,
+ struct sk_buff *skb)
+{
+ int err;
+ mutex_lock(&data->open_mutex);
+ err = __vhci_create_extended_device(data, skb);
+ mutex_unlock(&data->open_mutex);
+
+ return err;
+}
+
static inline ssize_t vhci_get_user(struct vhci_data *data,
struct iov_iter *from)
{
@@ -419,14 +554,22 @@ static inline ssize_t vhci_get_user(struct vhci_data *data,
opcode = *((__u8 *) skb->data);
skb_pull(skb, 1);
- if (skb->len > 0) {
- kfree_skb(skb);
- return -EINVAL;
+ /* The dev_type 3 is used as an escape opcode for extension
+ * handling. If dev_type is set to 3 all other bits must be
+ * set to zero.
+ */
+ if (opcode == 0x03) {
+ if (skb->len < 1)
+ ret = -EINVAL;
+ else
+ ret = vhci_create_extended_device(data, skb);
+ } else {
+ if (skb->len > 0)
+ ret = -EINVAL;
+ else
+ ret = vhci_create_device(data, opcode);
}
-
kfree_skb(skb);
-
- ret = vhci_create_device(data, opcode);
break;
default: