From fe70d7b26d7a98b17e315bc3455bee267c618b4e Mon Sep 17 00:00:00 2001 From: Philippe Michaud-Boudreault Date: Wed, 5 May 2021 02:48:21 +0300 Subject: [PATCH] iwmbtfw(8): Add support for Intel 7260/7265 bluetooth adapter firmwares To use it comms/iwmbt-firmware port should be updated to 20210315 version. Submitted by: Philippe Michaud-Boudreault Tested by: Helge Oldach Reviewed by: wulf PR: 228787 MFC after: 2 weeks --- usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c | 14 ++ usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c | 189 ++++++++++++++++- usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h | 5 + usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 | 10 +- usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf | 2 +- usr.sbin/bluetooth/iwmbtfw/main.c | 267 +++++++++++++++++------- 6 files changed, 408 insertions(+), 79 deletions(-) diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c index 7764f8bc4ac3..fc93ce094adc 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c @@ -119,6 +119,20 @@ iwmbt_get_fwname(struct iwmbt_version *ver, struct iwmbt_boot_params *params, char *fwname; switch (ver->hw_variant) { + case 0x07: /* 7260 */ + asprintf(&fwname, "%s/ibt-hw-%x.%x.%x-fw-%x.%x.%x.%x.%x.%s", + prefix, + le16toh(ver->hw_platform), + le16toh(ver->hw_variant), + le16toh(ver->hw_revision), + le16toh(ver->fw_variant), + le16toh(ver->fw_revision), + le16toh(ver->fw_build_num), + le16toh(ver->fw_build_ww), + le16toh(ver->fw_build_yy), + suffix); + break; + case 0x0b: /* 8260 */ case 0x0c: /* 8265 */ asprintf(&fwname, "%s/ibt-%u-%u.%s", diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c index af48e038340f..f4272548d560 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c @@ -27,7 +27,7 @@ * $FreeBSD$ */ -#include +#include #include #include @@ -126,6 +126,125 @@ iwmbt_hci_command(struct libusb_device_handle *hdl, struct iwmbt_hci_cmd *cmd, return (ret); } +int +iwmbt_patch_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw) +{ + int ret, transferred; + struct iwmbt_firmware fw_job = *fw; + uint16_t cmd_opcode; + uint8_t cmd_length; + uint8_t cmd_buf[IWMBT_HCI_MAX_CMD_SIZE]; + uint8_t evt_code; + uint8_t evt_length; + uint8_t evt_buf[IWMBT_HCI_MAX_EVENT_SIZE]; + int skip_patch = 0; + + for (;;) { + skip_patch = 0; + + if (fw_job.len < 4) + break; + + if (fw_job.buf[0] != 0x01) { + iwmbt_err("Invalid firmware, expected HCI command (%d)", + fw_job.buf[0]); + return (-1); + } + + /* Advance by one. */ + fw_job.buf++; + fw_job.len--; + + /* Load in the HCI command to perform. */ + cmd_opcode = le16dec(fw_job.buf); + cmd_length = fw_job.buf[2]; + memcpy(cmd_buf, fw_job.buf, 3); + + iwmbt_debug("opcode=%04x, len=%02x", cmd_opcode, cmd_length); + + /* For some reason the command 0xfc2f hangs up my card. */ + if (cmd_opcode == 0xfc2f) + skip_patch = 1; + + /* Advance by three. */ + fw_job.buf += 3; + fw_job.len -= 3; + + if (fw_job.len < cmd_length) + cmd_length = fw_job.len; + + /* Copy data to HCI command buffer. */ + memcpy(cmd_buf + 3, fw_job.buf, + MIN(cmd_length, IWMBT_HCI_MAX_CMD_SIZE - 3)); + + /* Advance by data length. */ + fw_job.buf += cmd_length; + fw_job.len -= cmd_length; + + /* + * Every command has its associated event: data must match + * what is recorded in the firmware file. Perform that check + * now. + * + * Some commands are mapped to more than one event sequence, + * in that case we can drop the non-patch commands, as we + * probably don't need them for operation of the card. + * + */ + + for (;;) { + /* Is this the end of the file? */ + if (fw_job.len < 3) + break; + + if (fw_job.buf[0] != 0x02) + break; + + /* Advance by one. */ + fw_job.buf++; + fw_job.len--; + + /* Load in the HCI event. */ + evt_code = fw_job.buf[0]; + evt_length = fw_job.buf[1]; + + /* Advance by two. */ + fw_job.buf += 2; + fw_job.len -= 2; + + /* Prepare HCI event buffer. */ + memset(evt_buf, 0, IWMBT_HCI_MAX_EVENT_SIZE); + + iwmbt_debug("event=%04x, len=%02x", + evt_code, evt_length); + + /* Advance by data length. */ + fw_job.buf += evt_length; + fw_job.len -= evt_length; + + if (skip_patch == 0) { + ret = iwmbt_hci_command(hdl, + (struct iwmbt_hci_cmd *)cmd_buf, + evt_buf, + IWMBT_HCI_MAX_EVENT_SIZE, + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Can't send patch: " + "code=%d, size=%d", + ret, + transferred); + return (-1); + } + } + } + } + + return (0); +} + int iwmbt_load_fwfile(struct libusb_device_handle *hdl, const struct iwmbt_firmware *fw, uint32_t *boot_param) @@ -217,6 +336,74 @@ iwmbt_load_fwfile(struct libusb_device_handle *hdl, return (0); } +int +iwmbt_enter_manufacturer(struct libusb_device_handle *hdl) +{ + int ret, transferred; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc11), + .length = 2, + .data = { 0x01, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Can't enter manufacturer mode: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + return (0); +} + +int +iwmbt_exit_manufacturer(struct libusb_device_handle *hdl, int mode) +{ + int ret, transferred; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc11), + .length = 2, + .data = { 0x00, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + /* + * The mode sets the type of reset we want to perform: + * 0x00: simply exit manufacturer mode without a reset. + * 0x01: exit manufacturer mode with a reset and patches disabled + * 0x02: exit manufacturer mode with a reset and patches enabled + */ + if (mode > 2) { + iwmbt_debug("iwmbt_exit_manufacturer(): unknown mode (%d)", + mode); + } + cmd.data[1] = mode; + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Can't exit manufacturer mode: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + return (0); +} + int iwmbt_get_version(struct libusb_device_handle *hdl, struct iwmbt_version *version) diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h index 6a87f499fa26..5bc1d15181cd 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h @@ -73,8 +73,13 @@ struct iwmbt_hci_event_cmd_compl { #define IWMBT_HCI_CMD_TIMEOUT 2000 /* ms */ #define IWMBT_LOADCMPL_TIMEOUT 5000 /* ms */ +extern int iwmbt_patch_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw); extern int iwmbt_load_fwfile(struct libusb_device_handle *hdl, const struct iwmbt_firmware *fw, uint32_t *boot_param); +extern int iwmbt_enter_manufacturer(struct libusb_device_handle *hdl); +extern int iwmbt_exit_manufacturer(struct libusb_device_handle *hdl, + int mode); extern int iwmbt_get_version(struct libusb_device_handle *hdl, struct iwmbt_version *version); extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl, diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 index 3afbf54793f9..10e68040e0e4 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 @@ -1,5 +1,6 @@ .\" Copyright (c) 2013, 2016 Adrian Chadd .\" Copyright (c) 2019 Vladimir Kondratyev +.\" Copyright (c) 2021 Philippe Michaud-Boudreault .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -24,12 +25,12 @@ .\" .\" $FreeBSD$ .\" -.Dd June 4, 2019 +.Dd May 3, 2021 .Dt IWMBTFW 8 .Os .Sh NAME .Nm iwmbtfw -.Nd firmware download utility for Intel Wireless 8260/8265 chip based Bluetooth +.Nd firmware download utility for Intel Wireless 7260/8260/8265 chip based Bluetooth USB devices .Sh SYNOPSIS .Nm @@ -46,7 +47,7 @@ device. .Pp This utility will .Em only -work with Intel Wireless 8260/8265 chip based Bluetooth USB devices and some of +work with Intel Wireless 7260/8260/8265 chip based Bluetooth USB devices and some of their successors. The identification is currently based on USB vendor ID/product ID pair. The vendor ID should be 0x8087 @@ -91,6 +92,9 @@ utility used as firmware downloader template and on Linux btintel driver source code. It is written by .An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +.Pp +Support for the 7260 card added by +.An Philippe Michaud-Boudreault Aq Mt pitwuu@gmail.com . .Sh BUGS Most likely. Please report if found. diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf index 4bd1f020237c..6b417089c68b 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf @@ -7,6 +7,6 @@ notify 100 { match "subsystem" "DEVICE"; match "type" "ATTACH"; match "vendor" "0x8087"; - match "product" "(0x0a2b|0x0aaa|0x0025|0x0026|0x0029)"; + match "product" "(0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029)"; action "/usr/sbin/iwmbtfw -d $cdev -f /usr/local/share/iwmbt-firmware"; }; diff --git a/usr.sbin/bluetooth/iwmbtfw/main.c b/usr.sbin/bluetooth/iwmbtfw/main.c index ecd9d226b91f..3476e3fcd613 100644 --- a/usr.sbin/bluetooth/iwmbtfw/main.c +++ b/usr.sbin/bluetooth/iwmbtfw/main.c @@ -57,7 +57,15 @@ struct iwmbt_devid { uint16_t vendor_id; }; -static struct iwmbt_devid iwmbt_list[] = { +static struct iwmbt_devid iwmbt_list_72xx[] = { + + /* Intel Wireless 7260/7265 and successors */ + { .vendor_id = 0x8087, .product_id = 0x07dc }, + { .vendor_id = 0x8087, .product_id = 0x0a2a }, + { .vendor_id = 0x8087, .product_id = 0x0aa7 }, +}; + +static struct iwmbt_devid iwmbt_list_82xx[] = { /* Intel Wireless 8260/8265 and successors */ { .vendor_id = 0x8087, .product_id = 0x0a2b }, @@ -67,15 +75,33 @@ static struct iwmbt_devid iwmbt_list[] = { { .vendor_id = 0x8087, .product_id = 0x0029 }, }; +static int +iwmbt_is_7260(struct libusb_device_descriptor *d) +{ + int i; + + /* Search looking for whether it's an 7260/7265 */ + for (i = 0; i < (int) nitems(iwmbt_list_72xx); i++) { + if ((iwmbt_list_72xx[i].product_id == d->idProduct) && + (iwmbt_list_72xx[i].vendor_id == d->idVendor)) { + iwmbt_info("found 7260/7265"); + return (1); + } + } + + /* Not found */ + return (0); +} + static int iwmbt_is_8260(struct libusb_device_descriptor *d) { int i; /* Search looking for whether it's an 8260/8265 */ - for (i = 0; i < (int) nitems(iwmbt_list); i++) { - if ((iwmbt_list[i].product_id == d->idProduct) && - (iwmbt_list[i].vendor_id == d->idVendor)) { + for (i = 0; i < (int) nitems(iwmbt_list_82xx); i++) { + if ((iwmbt_list_82xx[i].product_id == d->idProduct) && + (iwmbt_list_82xx[i].vendor_id == d->idVendor)) { iwmbt_info("found 8260/8265"); return (1); } @@ -86,7 +112,8 @@ iwmbt_is_8260(struct libusb_device_descriptor *d) } static libusb_device * -iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id) +iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id, + int *iwmbt_use_old_method) { libusb_device **list, *dev = NULL, *found = NULL; struct libusb_device_descriptor d; @@ -116,11 +143,20 @@ iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id) } /* Match on the vendor/product id */ + if (iwmbt_is_7260(&d)) { + /* + * Take a reference so it's not freed later on. + */ + found = libusb_ref_device(dev); + *iwmbt_use_old_method = 1; + break; + } else if (iwmbt_is_8260(&d)) { /* * Take a reference so it's not freed later on. */ found = libusb_ref_device(dev); + *iwmbt_use_old_method = 0; break; } } @@ -166,6 +202,31 @@ iwmbt_dump_boot_params(struct iwmbt_boot_params *params) params->otp_bdaddr[0]); } +static int +iwmbt_patch_firmware(libusb_device_handle *hdl, const char *firmware_path) +{ + struct iwmbt_firmware fw; + int ret; + + iwmbt_debug("loading %s", firmware_path); + + /* Read in the firmware */ + if (iwmbt_fw_read(&fw, firmware_path) <= 0) { + iwmbt_debug("iwmbt_fw_read() failed"); + return (-1); + } + + /* Load in the firmware */ + ret = iwmbt_patch_fwfile(hdl, &fw); + if (ret < 0) + iwmbt_debug("Loading firmware file failed"); + + /* free it */ + iwmbt_fw_free(&fw); + + return (ret); +} + static int iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path, uint32_t *boot_param) @@ -268,6 +329,7 @@ main(int argc, char *argv[]) char *firmware_dir = NULL; char *firmware_path = NULL; int retcode = 1; + int iwmbt_use_old_method = 0; /* Parse command line arguments */ while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) { @@ -312,7 +374,7 @@ main(int argc, char *argv[]) iwmbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id); /* Find a device based on the bus/dev id */ - dev = iwmbt_find_device(ctx, bus_id, dev_id); + dev = iwmbt_find_device(ctx, bus_id, dev_id, &iwmbt_use_old_method); if (dev == NULL) { iwmbt_err("device not found"); goto shutdown; @@ -344,87 +406,144 @@ main(int argc, char *argv[]) /* Get Intel version */ r = iwmbt_get_version(hdl, &ver); if (r < 0) { - iwmbt_debug("iwmbt_get_version() failedL code %d", r); + iwmbt_debug("iwmbt_get_version() failed code %d", r); goto shutdown; } iwmbt_dump_version(&ver); iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant); - /* fw_variant = 0x06 bootloader mode / 0x23 operational mode */ - if (ver.fw_variant == 0x23) { - iwmbt_info("Firmware has already been downloaded"); - retcode = 0; - goto reset; - } + if (iwmbt_use_old_method) { - if (ver.fw_variant != 0x06){ - iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant); - goto shutdown; - } + /* fw_patch_num = >0 operational mode */ + if (ver.fw_patch_num > 0x00) { + iwmbt_info("Firmware has already been downloaded"); + retcode = 0; + goto reset; + } - /* Read Intel Secure Boot Params */ - r = iwmbt_get_boot_params(hdl, ¶ms); - if (r < 0) { - iwmbt_debug("iwmbt_get_boot_params() failed!"); - goto shutdown; - } - iwmbt_dump_boot_params(¶ms); + /* Default the firmware path */ + if (firmware_dir == NULL) + firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); - /* Check if firmware fragments are ACKed with a cmd complete event */ - if (params.limited_cce != 0x00) { - iwmbt_err("Unsupported Intel firmware loading method (%u)", - params.limited_cce); - goto shutdown; - } + firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "bseq"); + if (firmware_path == NULL) + goto shutdown; - /* Default the firmware path */ - if (firmware_dir == NULL) - firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); + iwmbt_debug("firmware_path = %s", firmware_path); - firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "sfi"); - if (firmware_path == NULL) - goto shutdown; + /* Enter manufacturer mode */ + r = iwmbt_enter_manufacturer(hdl); + if (r < 0) { + iwmbt_debug("iwmbt_enter_manufacturer() failed code %d", r); + goto shutdown; + } - iwmbt_debug("firmware_path = %s", firmware_path); - - /* Download firmware and parse it for magic Intel Reset parameter */ - r = iwmbt_init_firmware(hdl, firmware_path, &boot_param); - free(firmware_path); - if (r < 0) - goto shutdown; - - iwmbt_info("Firmware download complete"); - - r = iwmbt_intel_reset(hdl, boot_param); - if (r < 0) { - iwmbt_debug("iwmbt_intel_reset() failed!"); - goto shutdown; - } - - iwmbt_info("Firmware operational"); - - /* Once device is running in operational mode we can ignore failures */ - retcode = 0; - - /* Execute Read Intel Version one more time */ - r = iwmbt_get_version(hdl, &ver); - if (r == 0) - iwmbt_dump_version(&ver); - - /* Apply the device configuration (DDC) parameters */ - firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "ddc"); - iwmbt_debug("ddc_path = %s", firmware_path); - if (firmware_path != NULL) { - r = iwmbt_init_ddc(hdl, firmware_path); - if (r == 0) - iwmbt_info("DDC download complete"); + /* Download firmware and parse it for magic Intel Reset parameter */ + r = iwmbt_patch_firmware(hdl, firmware_path); free(firmware_path); - } + if (r < 0) + goto shutdown; - /* Set Intel Event mask */ - r = iwmbt_set_event_mask(hdl); - if (r == 0) - iwmbt_info("Intel Event Mask is set"); + iwmbt_info("Firmware download complete"); + + /* Exit manufacturer mode */ + r = iwmbt_exit_manufacturer(hdl, 0x02); + if (r < 0) { + iwmbt_debug("iwmbt_exit_manufacturer() failed code %d", r); + goto shutdown; + } + + /* Once device is running in operational mode we can ignore failures */ + retcode = 0; + + /* Execute Read Intel Version one more time */ + r = iwmbt_get_version(hdl, &ver); + if (r == 0) + iwmbt_dump_version(&ver); + + /* Set Intel Event mask */ + r = iwmbt_set_event_mask(hdl); + if (r == 0) + iwmbt_info("Intel Event Mask is set"); + + } else { + + /* fw_variant = 0x06 bootloader mode / 0x23 operational mode */ + if (ver.fw_variant == 0x23) { + iwmbt_info("Firmware has already been downloaded"); + retcode = 0; + goto reset; + } + + if (ver.fw_variant != 0x06){ + iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant); + goto shutdown; + } + + /* Read Intel Secure Boot Params */ + r = iwmbt_get_boot_params(hdl, ¶ms); + if (r < 0) { + iwmbt_debug("iwmbt_get_boot_params() failed!"); + goto shutdown; + } + iwmbt_dump_boot_params(¶ms); + + /* Check if firmware fragments are ACKed with a cmd complete event */ + if (params.limited_cce != 0x00) { + iwmbt_err("Unsupported Intel firmware loading method (%u)", + params.limited_cce); + goto shutdown; + } + + /* Default the firmware path */ + if (firmware_dir == NULL) + firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); + + firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "sfi"); + if (firmware_path == NULL) + goto shutdown; + + iwmbt_debug("firmware_path = %s", firmware_path); + + /* Download firmware and parse it for magic Intel Reset parameter */ + r = iwmbt_init_firmware(hdl, firmware_path, &boot_param); + free(firmware_path); + if (r < 0) + goto shutdown; + + iwmbt_info("Firmware download complete"); + + r = iwmbt_intel_reset(hdl, boot_param); + if (r < 0) { + iwmbt_debug("iwmbt_intel_reset() failed!"); + goto shutdown; + } + + iwmbt_info("Firmware operational"); + + /* Once device is running in operational mode we can ignore failures */ + retcode = 0; + + /* Execute Read Intel Version one more time */ + r = iwmbt_get_version(hdl, &ver); + if (r == 0) + iwmbt_dump_version(&ver); + + /* Apply the device configuration (DDC) parameters */ + firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "ddc"); + iwmbt_debug("ddc_path = %s", firmware_path); + if (firmware_path != NULL) { + r = iwmbt_init_ddc(hdl, firmware_path); + if (r == 0) + iwmbt_info("DDC download complete"); + free(firmware_path); + } + + /* Set Intel Event mask */ + r = iwmbt_set_event_mask(hdl); + if (r == 0) + iwmbt_info("Intel Event Mask is set"); + } reset: