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 <pitwuu@gmail.com>
Tested by:	Helge Oldach <freebsd@oldach.net>
Reviewed by:	wulf
PR:		228787
MFC after:	2 weeks
This commit is contained in:
Philippe Michaud-Boudreault 2021-05-05 02:48:21 +03:00 committed by Vladimir Kondratyev
parent 15c0aaf517
commit fe70d7b26d
6 changed files with 408 additions and 79 deletions

View File

@ -119,6 +119,20 @@ iwmbt_get_fwname(struct iwmbt_version *ver, struct iwmbt_boot_params *params,
char *fwname; char *fwname;
switch (ver->hw_variant) { 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 0x0b: /* 8260 */
case 0x0c: /* 8265 */ case 0x0c: /* 8265 */
asprintf(&fwname, "%s/ibt-%u-%u.%s", asprintf(&fwname, "%s/ibt-%u-%u.%s",

View File

@ -27,7 +27,7 @@
* $FreeBSD$ * $FreeBSD$
*/ */
#include <sys/types.h> #include <sys/param.h>
#include <sys/endian.h> #include <sys/endian.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -126,6 +126,125 @@ iwmbt_hci_command(struct libusb_device_handle *hdl, struct iwmbt_hci_cmd *cmd,
return (ret); 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 int
iwmbt_load_fwfile(struct libusb_device_handle *hdl, iwmbt_load_fwfile(struct libusb_device_handle *hdl,
const struct iwmbt_firmware *fw, uint32_t *boot_param) const struct iwmbt_firmware *fw, uint32_t *boot_param)
@ -217,6 +336,74 @@ iwmbt_load_fwfile(struct libusb_device_handle *hdl,
return (0); 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 int
iwmbt_get_version(struct libusb_device_handle *hdl, iwmbt_get_version(struct libusb_device_handle *hdl,
struct iwmbt_version *version) struct iwmbt_version *version)

View File

@ -73,8 +73,13 @@ struct iwmbt_hci_event_cmd_compl {
#define IWMBT_HCI_CMD_TIMEOUT 2000 /* ms */ #define IWMBT_HCI_CMD_TIMEOUT 2000 /* ms */
#define IWMBT_LOADCMPL_TIMEOUT 5000 /* 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, extern int iwmbt_load_fwfile(struct libusb_device_handle *hdl,
const struct iwmbt_firmware *fw, uint32_t *boot_param); 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, extern int iwmbt_get_version(struct libusb_device_handle *hdl,
struct iwmbt_version *version); struct iwmbt_version *version);
extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl, extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl,

View File

@ -1,5 +1,6 @@
.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org> .\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org>
.\" Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> .\" Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
.\" Copyright (c) 2021 Philippe Michaud-Boudreault <pitwuu@gmail.com>
.\" .\"
.\" Redistribution and use in source and binary forms, with or without .\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions .\" modification, are permitted provided that the following conditions
@ -24,12 +25,12 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd June 4, 2019 .Dd May 3, 2021
.Dt IWMBTFW 8 .Dt IWMBTFW 8
.Os .Os
.Sh NAME .Sh NAME
.Nm iwmbtfw .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 USB devices
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
@ -46,7 +47,7 @@ device.
.Pp .Pp
This utility will This utility will
.Em only .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. their successors.
The identification is currently based on USB vendor ID/product ID pair. The identification is currently based on USB vendor ID/product ID pair.
The vendor ID should be 0x8087 The vendor ID should be 0x8087
@ -91,6 +92,9 @@ utility used as firmware downloader template and on Linux btintel driver
source code. source code.
It is written by It is written by
.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . .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 .Sh BUGS
Most likely. Most likely.
Please report if found. Please report if found.

View File

@ -7,6 +7,6 @@ notify 100 {
match "subsystem" "DEVICE"; match "subsystem" "DEVICE";
match "type" "ATTACH"; match "type" "ATTACH";
match "vendor" "0x8087"; 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"; action "/usr/sbin/iwmbtfw -d $cdev -f /usr/local/share/iwmbt-firmware";
}; };

View File

@ -57,7 +57,15 @@ struct iwmbt_devid {
uint16_t vendor_id; 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 */ /* Intel Wireless 8260/8265 and successors */
{ .vendor_id = 0x8087, .product_id = 0x0a2b }, { .vendor_id = 0x8087, .product_id = 0x0a2b },
@ -67,15 +75,33 @@ static struct iwmbt_devid iwmbt_list[] = {
{ .vendor_id = 0x8087, .product_id = 0x0029 }, { .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 static int
iwmbt_is_8260(struct libusb_device_descriptor *d) iwmbt_is_8260(struct libusb_device_descriptor *d)
{ {
int i; int i;
/* Search looking for whether it's an 8260/8265 */ /* Search looking for whether it's an 8260/8265 */
for (i = 0; i < (int) nitems(iwmbt_list); i++) { for (i = 0; i < (int) nitems(iwmbt_list_82xx); i++) {
if ((iwmbt_list[i].product_id == d->idProduct) && if ((iwmbt_list_82xx[i].product_id == d->idProduct) &&
(iwmbt_list[i].vendor_id == d->idVendor)) { (iwmbt_list_82xx[i].vendor_id == d->idVendor)) {
iwmbt_info("found 8260/8265"); iwmbt_info("found 8260/8265");
return (1); return (1);
} }
@ -86,7 +112,8 @@ iwmbt_is_8260(struct libusb_device_descriptor *d)
} }
static libusb_device * 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; libusb_device **list, *dev = NULL, *found = NULL;
struct libusb_device_descriptor d; 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 */ /* 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)) { if (iwmbt_is_8260(&d)) {
/* /*
* Take a reference so it's not freed later on. * Take a reference so it's not freed later on.
*/ */
found = libusb_ref_device(dev); found = libusb_ref_device(dev);
*iwmbt_use_old_method = 0;
break; break;
} }
} }
@ -166,6 +202,31 @@ iwmbt_dump_boot_params(struct iwmbt_boot_params *params)
params->otp_bdaddr[0]); 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 static int
iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path, iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path,
uint32_t *boot_param) uint32_t *boot_param)
@ -268,6 +329,7 @@ main(int argc, char *argv[])
char *firmware_dir = NULL; char *firmware_dir = NULL;
char *firmware_path = NULL; char *firmware_path = NULL;
int retcode = 1; int retcode = 1;
int iwmbt_use_old_method = 0;
/* Parse command line arguments */ /* Parse command line arguments */
while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) { 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); iwmbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id);
/* Find a device based on the bus/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) { if (dev == NULL) {
iwmbt_err("device not found"); iwmbt_err("device not found");
goto shutdown; goto shutdown;
@ -344,87 +406,144 @@ main(int argc, char *argv[])
/* Get Intel version */ /* Get Intel version */
r = iwmbt_get_version(hdl, &ver); r = iwmbt_get_version(hdl, &ver);
if (r < 0) { if (r < 0) {
iwmbt_debug("iwmbt_get_version() failedL code %d", r); iwmbt_debug("iwmbt_get_version() failed code %d", r);
goto shutdown; goto shutdown;
} }
iwmbt_dump_version(&ver); iwmbt_dump_version(&ver);
iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant); iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant);
/* fw_variant = 0x06 bootloader mode / 0x23 operational mode */ if (iwmbt_use_old_method) {
if (ver.fw_variant == 0x23) {
iwmbt_info("Firmware has already been downloaded");
retcode = 0;
goto reset;
}
if (ver.fw_variant != 0x06){ /* fw_patch_num = >0 operational mode */
iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant); if (ver.fw_patch_num > 0x00) {
goto shutdown; iwmbt_info("Firmware has already been downloaded");
} retcode = 0;
goto reset;
}
/* Read Intel Secure Boot Params */ /* Default the firmware path */
r = iwmbt_get_boot_params(hdl, &params); if (firmware_dir == NULL)
if (r < 0) { firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH);
iwmbt_debug("iwmbt_get_boot_params() failed!");
goto shutdown;
}
iwmbt_dump_boot_params(&params);
/* Check if firmware fragments are ACKed with a cmd complete event */ firmware_path = iwmbt_get_fwname(&ver, &params, firmware_dir, "bseq");
if (params.limited_cce != 0x00) { if (firmware_path == NULL)
iwmbt_err("Unsupported Intel firmware loading method (%u)", goto shutdown;
params.limited_cce);
goto shutdown;
}
/* Default the firmware path */ iwmbt_debug("firmware_path = %s", firmware_path);
if (firmware_dir == NULL)
firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH);
firmware_path = iwmbt_get_fwname(&ver, &params, firmware_dir, "sfi"); /* Enter manufacturer mode */
if (firmware_path == NULL) r = iwmbt_enter_manufacturer(hdl);
goto shutdown; 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_patch_firmware(hdl, 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, &params, 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); free(firmware_path);
} if (r < 0)
goto shutdown;
/* Set Intel Event mask */ iwmbt_info("Firmware download complete");
r = iwmbt_set_event_mask(hdl);
if (r == 0) /* Exit manufacturer mode */
iwmbt_info("Intel Event Mask is set"); 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, &params);
if (r < 0) {
iwmbt_debug("iwmbt_get_boot_params() failed!");
goto shutdown;
}
iwmbt_dump_boot_params(&params);
/* 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, &params, 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, &params, 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: reset: