diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/bluetooth/btusb.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 3d684d2..7a7e5f8 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -23,6 +23,7 @@ #include <linux/module.h> #include <linux/usb.h> +#include <linux/firmware.h> #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> @@ -47,6 +48,7 @@ static struct usb_driver btusb_driver; #define BTUSB_BROKEN_ISOC 0x20 #define BTUSB_WRONG_SCO_MTU 0x40 #define BTUSB_ATH3012 0x80 +#define BTUSB_INTEL 0x100 static struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -207,6 +209,9 @@ static struct usb_device_id blacklist_table[] = { /* Frontline ComProbe Bluetooth Sniffer */ { USB_DEVICE(0x16d3, 0x0002), .driver_info = BTUSB_SNIFFER }, + /* Intel Bluetooth device */ + { USB_DEVICE(0x8087, 0x07dc), .driver_info = BTUSB_INTEL }, + { } /* Terminating entry */ }; @@ -943,6 +948,375 @@ static int btusb_setup_bcm92035(struct hci_dev *hdev) return 0; } +struct intel_version { + u8 status; + u8 hw_platform; + u8 hw_variant; + u8 hw_revision; + u8 fw_variant; + u8 fw_revision; + u8 fw_build_num; + u8 fw_build_ww; + u8 fw_build_yy; + u8 fw_patch_num; +} __packed; + +static const struct firmware *btusb_setup_intel_get_fw(struct hci_dev *hdev, + struct intel_version *ver) +{ + const struct firmware *fw; + char fwname[64]; + int ret; + + snprintf(fwname, sizeof(fwname), + "intel/ibt-hw-%x.%x.%x-fw-%x.%x.%x.%x.%x.bseq", + ver->hw_platform, ver->hw_variant, ver->hw_revision, + ver->fw_variant, ver->fw_revision, ver->fw_build_num, + ver->fw_build_ww, ver->fw_build_yy); + + ret = request_firmware(&fw, fwname, &hdev->dev); + if (ret < 0) { + if (ret == -EINVAL) { + BT_ERR("%s Intel firmware file request failed (%d)", + hdev->name, ret); + return NULL; + } + + BT_ERR("%s failed to open Intel firmware file: %s(%d)", + hdev->name, fwname, ret); + + /* If the correct firmware patch file is not found, use the + * default firmware patch file instead + */ + snprintf(fwname, sizeof(fwname), "intel/ibt-hw-%x.%x.bseq", + ver->hw_platform, ver->hw_variant); + if (request_firmware(&fw, fwname, &hdev->dev) < 0) { + BT_ERR("%s failed to open default Intel fw file: %s", + hdev->name, fwname); + return NULL; + } + } + + BT_INFO("%s: Intel Bluetooth firmware file: %s", hdev->name, fwname); + + return fw; +} + +static int btusb_setup_intel_patching(struct hci_dev *hdev, + const struct firmware *fw, + const u8 **fw_ptr, int *disable_patch) +{ + struct sk_buff *skb; + struct hci_command_hdr *cmd; + const u8 *cmd_param; + struct hci_event_hdr *evt = NULL; + const u8 *evt_param = NULL; + int remain = fw->size - (*fw_ptr - fw->data); + + /* The first byte indicates the types of the patch command or event. + * 0x01 means HCI command and 0x02 is HCI event. If the first bytes + * in the current firmware buffer doesn't start with 0x01 or + * the size of remain buffer is smaller than HCI command header, + * the firmware file is corrupted and it should stop the patching + * process. + */ + if (remain > HCI_COMMAND_HDR_SIZE && *fw_ptr[0] != 0x01) { + BT_ERR("%s Intel fw corrupted: invalid cmd read", hdev->name); + return -EINVAL; + } + (*fw_ptr)++; + remain--; + + cmd = (struct hci_command_hdr *)(*fw_ptr); + *fw_ptr += sizeof(*cmd); + remain -= sizeof(*cmd); + + /* Ensure that the remain firmware data is long enough than the length + * of command parameter. If not, the firmware file is corrupted. + */ + if (remain < cmd->plen) { + BT_ERR("%s Intel fw corrupted: invalid cmd len", hdev->name); + return -EFAULT; + } + + /* If there is a command that loads a patch in the firmware + * file, then enable the patch upon success, otherwise just + * disable the manufacturer mode, for example patch activation + * is not required when the default firmware patch file is used + * because there are no patch data to load. + */ + if (*disable_patch && le16_to_cpu(cmd->opcode) == 0xfc8e) + *disable_patch = 0; + + cmd_param = *fw_ptr; + *fw_ptr += cmd->plen; + remain -= cmd->plen; + + /* This reads the expected events when the above command is sent to the + * device. Some vendor commands expects more than one events, for + * example command status event followed by vendor specific event. + * For this case, it only keeps the last expected event. so the command + * can be sent with __hci_cmd_sync_ev() which returns the sk_buff of + * last expected event. + */ + while (remain > HCI_EVENT_HDR_SIZE && *fw_ptr[0] == 0x02) { + (*fw_ptr)++; + remain--; + + evt = (struct hci_event_hdr *)(*fw_ptr); + *fw_ptr += sizeof(*evt); + remain -= sizeof(*evt); + + if (remain < evt->plen) { + BT_ERR("%s Intel fw corrupted: invalid evt len", + hdev->name); + return -EFAULT; + } + + evt_param = *fw_ptr; + *fw_ptr += evt->plen; + remain -= evt->plen; + } + + /* Every HCI commands in the firmware file has its correspond event. + * If event is not found or remain is smaller than zero, the firmware + * file is corrupted. + */ + if (!evt || !evt_param || remain < 0) { + BT_ERR("%s Intel fw corrupted: invalid evt read", hdev->name); + return -EFAULT; + } + + skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cmd->opcode), cmd->plen, + cmd_param, evt->evt, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s sending Intel patch command (0x%4.4x) failed (%ld)", + hdev->name, cmd->opcode, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + + /* It ensures that the returned event matches the event data read from + * the firmware file. At fist, it checks the length and then + * the contents of the event. + */ + if (skb->len != evt->plen) { + BT_ERR("%s mismatch event length (opcode 0x%4.4x)", hdev->name, + le16_to_cpu(cmd->opcode)); + kfree_skb(skb); + return -EFAULT; + } + + if (memcmp(skb->data, evt_param, evt->plen)) { + BT_ERR("%s mismatch event parameter (opcode 0x%4.4x)", + hdev->name, le16_to_cpu(cmd->opcode)); + kfree_skb(skb); + return -EFAULT; + } + kfree_skb(skb); + + return 0; +} + +static int btusb_setup_intel(struct hci_dev *hdev) +{ + struct sk_buff *skb; + const struct firmware *fw; + const u8 *fw_ptr; + int disable_patch; + struct intel_version *ver; + + const u8 mfg_enable[] = { 0x01, 0x00 }; + const u8 mfg_disable[] = { 0x00, 0x00 }; + const u8 mfg_reset_deactivate[] = { 0x00, 0x01 }; + const u8 mfg_reset_activate[] = { 0x00, 0x02 }; + + BT_DBG("%s", hdev->name); + + /* The controller has a bug with the first HCI command sent to it + * returning number of completed commands as zero. This would stall the + * command processing in the Bluetooth core. + * + * As a workaround, send HCI Reset command first which will reset the + * number of completed commands and allow normal command processing + * from now on. + */ + skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s sending initial HCI reset command failed (%ld)", + hdev->name, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + kfree_skb(skb); + + /* Read Intel specific controller version first to allow selection of + * which firmware file to load. + * + * The returned information are hardware variant and revision plus + * firmware variant, revision and build number. + */ + skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s reading Intel fw version command failed (%ld)", + hdev->name, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + + if (skb->len != sizeof(*ver)) { + BT_ERR("%s Intel version event length mismatch", hdev->name); + kfree_skb(skb); + return -EIO; + } + + ver = (struct intel_version *)skb->data; + if (ver->status) { + BT_ERR("%s Intel fw version event failed (%02x)", hdev->name, + ver->status); + kfree_skb(skb); + return -bt_to_errno(ver->status); + } + + BT_INFO("%s: read Intel version: %02x%02x%02x%02x%02x%02x%02x%02x%02x", + hdev->name, ver->hw_platform, ver->hw_variant, + ver->hw_revision, ver->fw_variant, ver->fw_revision, + ver->fw_build_num, ver->fw_build_ww, ver->fw_build_yy, + ver->fw_patch_num); + + /* fw_patch_num indicates the version of patch the device currently + * have. If there is no patch data in the device, it is always 0x00. + * So, if it is other than 0x00, no need to patch the deivce again. + */ + if (ver->fw_patch_num) { + BT_INFO("%s: Intel device is already patched. patch num: %02x", + hdev->name, ver->fw_patch_num); + kfree_skb(skb); + return 0; + } + + /* Opens the firmware patch file based on the firmware version read + * from the controller. If it fails to open the matching firmware + * patch file, it tries to open the default firmware patch file. + * If no patch file is found, allow the device to operate without + * a patch. + */ + fw = btusb_setup_intel_get_fw(hdev, ver); + if (!fw) { + kfree_skb(skb); + return 0; + } + fw_ptr = fw->data; + + /* This Intel specific command enables the manufacturer mode of the + * controller. + * + * Only while this mode is enabled, the driver can download the + * firmware patch data and configuration parameters. + */ + skb = __hci_cmd_sync(hdev, 0xfc11, 2, mfg_enable, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s entering Intel manufacturer mode failed (%ld)", + hdev->name, PTR_ERR(skb)); + release_firmware(fw); + return -PTR_ERR(skb); + } + + if (skb->data[0]) { + u8 evt_status = skb->data[0]; + BT_ERR("%s enable Intel manufacturer mode event failed (%02x)", + hdev->name, evt_status); + kfree_skb(skb); + release_firmware(fw); + return -bt_to_errno(evt_status); + } + kfree_skb(skb); + + disable_patch = 1; + + /* The firmware data file consists of list of Intel specific HCI + * commands and its expected events. The first byte indicates the + * type of the message, either HCI command or HCI event. + * + * It reads the command and its expected event from the firmware file, + * and send to the controller. Once __hci_cmd_sync_ev() returns, + * the returned event is compared with the event read from the firmware + * file and it will continue until all the messages are downloaded to + * the controller. + * + * Once the firmware patching is completed successfully, + * the manufacturer mode is disabled with reset and activating the + * downloaded patch. + * + * If the firmware patching fails, the manufacturer mode is + * disabled with reset and deactivating the patch. + * + * If the default patch file is used, no reset is done when disabling + * the manufacturer. + */ + while (fw->size > fw_ptr - fw->data) { + int ret; + + ret = btusb_setup_intel_patching(hdev, fw, &fw_ptr, + &disable_patch); + if (ret < 0) + goto exit_mfg_deactivate; + } + + release_firmware(fw); + + if (disable_patch) + goto exit_mfg_disable; + + /* Patching completed successfully and disable the manufacturer mode + * with reset and activate the downloaded firmware patches. + */ + skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_reset_activate), + mfg_reset_activate, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", + hdev->name, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + kfree_skb(skb); + + BT_INFO("%s: Intel Bluetooth firmware patch completed and activated", + hdev->name); + + return 0; + +exit_mfg_disable: + /* Disable the manufacturer mode without reset */ + skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_disable), mfg_disable, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", + hdev->name, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + kfree_skb(skb); + + BT_INFO("%s: Intel Bluetooth firmware patch completed", hdev->name); + return 0; + +exit_mfg_deactivate: + release_firmware(fw); + + /* Patching failed. Disable the manufacturer mode with reset and + * deactivate the downloaded firmware patches. + */ + skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_reset_deactivate), + mfg_reset_deactivate, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", + hdev->name, PTR_ERR(skb)); + return -PTR_ERR(skb); + } + kfree_skb(skb); + + BT_INFO("%s: Intel Bluetooth firmware patch completed and deactivated", + hdev->name); + + return 0; +} + static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) { @@ -1048,6 +1422,9 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_BCM92035) hdev->setup = btusb_setup_bcm92035; + if (id->driver_info & BTUSB_INTEL) + hdev->setup = btusb_setup_intel; + /* Interface numbers are hardcoded in the specification */ data->isoc = usb_ifnum_to_if(data->udev, 1); |