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;
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",

View File

@ -27,7 +27,7 @@
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/endian.h>
#include <sys/stat.h>
@ -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)

View File

@ -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,

View File

@ -1,5 +1,6 @@
.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@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
.\" 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.

View File

@ -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";
};

View File

@ -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, &params);
if (r < 0) {
iwmbt_debug("iwmbt_get_boot_params() failed!");
goto shutdown;
}
iwmbt_dump_boot_params(&params);
/* 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, &params, 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, &params, 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, &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");
/* 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, &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: