git: 84488787f42b - main - iwmbtfw(8): Identify device type based on HCI quieries result
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 12 Apr 2026 18:10:10 UTC
The branch main has been updated by wulf:
URL: https://cgit.FreeBSD.org/src/commit/?id=84488787f42bc62b428da37793ac45d1411f2b74
commit 84488787f42bc62b428da37793ac45d1411f2b74
Author: Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2026-04-12 18:09:22 +0000
Commit: Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2026-04-12 18:09:22 +0000
iwmbtfw(8): Identify device type based on HCI quieries result
rather than on VID/PID. Later is not reliable for some types.
VID/PID identification can be restored by specifying of -p option.
Tested by: arrowd, wulf
PR: 290639
MFC after: 1 week
---
usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c | 105 +++++++++++++++++++++++++
usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h | 2 +
usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c | 140 ++++++++++------------------------
usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h | 3 +
usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 | 7 +-
usr.sbin/bluetooth/iwmbtfw/main.c | 80 +++++++++++++++++--
6 files changed, 232 insertions(+), 105 deletions(-)
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
index 3a5cd9d42658..f71aacd6f2e9 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
@@ -31,6 +31,7 @@
#include <sys/endian.h>
#include <sys/stat.h>
+#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
@@ -192,3 +193,107 @@ iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver, const char *prefix,
return (fwname);
}
+
+int
+iwmbt_parse_tlv(uint8_t *data, uint8_t datalen,
+ struct iwmbt_version_tlv *version)
+{
+ uint8_t status, type, len;
+
+ status = *data++;
+ if (status != 0)
+ return (-1);
+ datalen--;
+
+ while (datalen >= 2) {
+ type = *data++;
+ len = *data++;
+ datalen -= 2;
+
+ if (datalen < len)
+ return (-1);
+
+ switch (type) {
+ case IWMBT_TLV_CNVI_TOP:
+ assert(len == 4);
+ version->cnvi_top = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVR_TOP:
+ assert(len == 4);
+ version->cnvr_top = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVI_BT:
+ assert(len == 4);
+ version->cnvi_bt = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVR_BT:
+ assert(len == 4);
+ version->cnvr_bt = le32dec(data);
+ break;
+ case IWMBT_TLV_DEV_REV_ID:
+ assert(len == 2);
+ version->dev_rev_id = le16dec(data);
+ break;
+ case IWMBT_TLV_IMAGE_TYPE:
+ assert(len == 1);
+ version->img_type = *data;
+ break;
+ case IWMBT_TLV_TIME_STAMP:
+ assert(len == 2);
+ version->min_fw_build_cw = data[0];
+ version->min_fw_build_yy = data[1];
+ version->timestamp = le16dec(data);
+ break;
+ case IWMBT_TLV_BUILD_TYPE:
+ assert(len == 1);
+ version->build_type = *data;
+ break;
+ case IWMBT_TLV_BUILD_NUM:
+ assert(len == 4);
+ version->min_fw_build_nn = *data;
+ version->build_num = le32dec(data);
+ break;
+ case IWMBT_TLV_SECURE_BOOT:
+ assert(len == 1);
+ version->secure_boot = *data;
+ break;
+ case IWMBT_TLV_OTP_LOCK:
+ assert(len == 1);
+ version->otp_lock = *data;
+ break;
+ case IWMBT_TLV_API_LOCK:
+ assert(len == 1);
+ version->api_lock = *data;
+ break;
+ case IWMBT_TLV_DEBUG_LOCK:
+ assert(len == 1);
+ version->debug_lock = *data;
+ break;
+ case IWMBT_TLV_MIN_FW:
+ assert(len == 3);
+ version->min_fw_build_nn = data[0];
+ version->min_fw_build_cw = data[1];
+ version->min_fw_build_yy = data[2];
+ break;
+ case IWMBT_TLV_LIMITED_CCE:
+ assert(len == 1);
+ version->limited_cce = *data;
+ break;
+ case IWMBT_TLV_SBE_TYPE:
+ assert(len == 1);
+ version->sbe_type = *data;
+ break;
+ case IWMBT_TLV_OTP_BDADDR:
+ memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t));
+ break;
+ default:
+ /* Ignore other types */
+ break;
+ }
+
+ datalen -= len;
+ data += len;
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
index eb6909a1f91d..1763f8688ed0 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
@@ -152,5 +152,7 @@ extern char *iwmbt_get_fwname(struct iwmbt_version *ver,
const char *suffix);
extern char *iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver,
const char *prefix, const char *suffix);
+extern int iwmbt_parse_tlv(uint8_t *data, uint8_t datalen,
+ struct iwmbt_version_tlv *ver);
#endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
index 255181b8f4bc..81f1fbe7c5ce 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
@@ -30,7 +30,6 @@
#include <sys/endian.h>
#include <sys/stat.h>
-#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stddef.h>
@@ -408,6 +407,29 @@ iwmbt_load_fwfile(struct libusb_device_handle *hdl,
return (0);
}
+int
+iwmbt_bt_reset(struct libusb_device_handle *hdl)
+{
+ int ret, transferred;
+ static struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0x0c03),
+ .length = 0,
+ };
+ 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("HCI reset command failed: code=%d", ret);
+
+ return (ret);
+}
+
int
iwmbt_enter_manufacturer(struct libusb_device_handle *hdl)
{
@@ -502,8 +524,8 @@ iwmbt_get_version(struct libusb_device_handle *hdl,
}
int
-iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
- struct iwmbt_version_tlv *version)
+iwmbt_read_version_tlv(struct libusb_device_handle *hdl,
+ uint8_t *data, uint8_t *datalen)
{
int ret, transferred;
struct iwmbt_hci_event_cmd_compl *event;
@@ -512,8 +534,6 @@ iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
.length = 1,
.data = { 0xff },
};
- uint8_t status, datalen, type, len;
- uint8_t *data;
uint8_t buf[255];
memset(buf, 0, sizeof(buf));
@@ -533,106 +553,30 @@ iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
}
event = (struct iwmbt_hci_event_cmd_compl *)buf;
- memcpy(version, event->data, sizeof(struct iwmbt_version));
+ *datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE;
+ memcpy(data, event->data, *datalen);
- datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE;
- data = event->data;
- status = *data++;
- if (status != 0)
- return (-1);
- datalen--;
+ return (0);
+}
- while (datalen >= 2) {
- type = *data++;
- len = *data++;
- datalen -= 2;
+int
+iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
+ struct iwmbt_version_tlv *version)
+{
- if (datalen < len)
- return (-1);
+ uint8_t data[255];
+ uint8_t datalen;
+ int ret;
- switch (type) {
- case IWMBT_TLV_CNVI_TOP:
- assert(len == 4);
- version->cnvi_top = le32dec(data);
- break;
- case IWMBT_TLV_CNVR_TOP:
- assert(len == 4);
- version->cnvr_top = le32dec(data);
- break;
- case IWMBT_TLV_CNVI_BT:
- assert(len == 4);
- version->cnvi_bt = le32dec(data);
- break;
- case IWMBT_TLV_CNVR_BT:
- assert(len == 4);
- version->cnvr_bt = le32dec(data);
- break;
- case IWMBT_TLV_DEV_REV_ID:
- assert(len == 2);
- version->dev_rev_id = le16dec(data);
- break;
- case IWMBT_TLV_IMAGE_TYPE:
- assert(len == 1);
- version->img_type = *data;
- break;
- case IWMBT_TLV_TIME_STAMP:
- assert(len == 2);
- version->min_fw_build_cw = data[0];
- version->min_fw_build_yy = data[1];
- version->timestamp = le16dec(data);
- break;
- case IWMBT_TLV_BUILD_TYPE:
- assert(len == 1);
- version->build_type = *data;
- break;
- case IWMBT_TLV_BUILD_NUM:
- assert(len == 4);
- version->min_fw_build_nn = *data;
- version->build_num = le32dec(data);
- break;
- case IWMBT_TLV_SECURE_BOOT:
- assert(len == 1);
- version->secure_boot = *data;
- break;
- case IWMBT_TLV_OTP_LOCK:
- assert(len == 1);
- version->otp_lock = *data;
- break;
- case IWMBT_TLV_API_LOCK:
- assert(len == 1);
- version->api_lock = *data;
- break;
- case IWMBT_TLV_DEBUG_LOCK:
- assert(len == 1);
- version->debug_lock = *data;
- break;
- case IWMBT_TLV_MIN_FW:
- assert(len == 3);
- version->min_fw_build_nn = data[0];
- version->min_fw_build_cw = data[1];
- version->min_fw_build_yy = data[2];
- break;
- case IWMBT_TLV_LIMITED_CCE:
- assert(len == 1);
- version->limited_cce = *data;
- break;
- case IWMBT_TLV_SBE_TYPE:
- assert(len == 1);
- version->sbe_type = *data;
- break;
- case IWMBT_TLV_OTP_BDADDR:
- memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t));
- break;
- default:
- /* Ignore other types */
- break;
- }
+ memset(data, 0, sizeof(data));
- datalen -= len;
- data += len;
+ ret = iwmbt_read_version_tlv(hdl, data, &datalen);
+ if (ret < 0) {
+ iwmbt_debug("Can't get version tlv");
+ return (-1);
}
- return (0);
+ return (iwmbt_parse_tlv(data, datalen, version));
}
int
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
index aac885dfd153..f1f1f56e0ca2 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
@@ -101,11 +101,14 @@ extern int iwmbt_load_ecdsa_header(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, int offset);
+extern int iwmbt_bt_reset(struct libusb_device_handle *hdl);
extern int iwmbt_enter_manufacturer(struct libusb_device_handle *hdl);
extern int iwmbt_exit_manufacturer(struct libusb_device_handle *hdl,
enum iwmbt_mm_exit mode);
extern int iwmbt_get_version(struct libusb_device_handle *hdl,
struct iwmbt_version *version);
+extern int iwmbt_read_version_tlv(struct libusb_device_handle *hdl,
+ uint8_t *data, uint8_t *datalen);
extern int iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
struct iwmbt_version_tlv *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 fd0118655a67..342656613421 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
@@ -26,7 +26,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 15, 2025
+.Dd April 2, 2026
.Dt IWMBTFW 8
.Os
.Sh NAME
@@ -50,7 +50,8 @@ This utility will
.Em only
work with Intel Wireless 7260/8260/9260 and newer chip based Bluetooth
USB devices, including AX and BE series wireless adapters.
-The identification is currently based on USB vendor ID/product ID pair.
+The identification is currently based on USB vendor ID/product ID pair
+and result of HCI queries.
The vendor ID should be 0x8087
.Pq Dv USB_VENDOR_INTEL2
and the product ID should be one of the supported devices.
@@ -78,6 +79,8 @@ device name.
Specify the directory containing the firmware files to search and upload.
.It Fl h
Display usage message and exit.
+.It Fl p
+Use only USB vendor ID/product ID pair for device model identification.
.El
.Sh EXIT STATUS
.Ex -std
diff --git a/usr.sbin/bluetooth/iwmbtfw/main.c b/usr.sbin/bluetooth/iwmbtfw/main.c
index 1e11cc468015..560735fabd67 100644
--- a/usr.sbin/bluetooth/iwmbtfw/main.c
+++ b/usr.sbin/bluetooth/iwmbtfw/main.c
@@ -35,6 +35,7 @@
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -50,6 +51,7 @@
int iwmbt_do_debug = 0;
int iwmbt_do_info = 0;
+static bool iwmbt_do_pidvid = false;
enum iwmbt_device {
IWMBT_DEVICE_UNKNOWN,
@@ -228,6 +230,65 @@ iwmbt_dump_version_tlv(struct iwmbt_version_tlv *ver)
ver->build_num);
}
+static enum iwmbt_device
+iwmbt_identify(libusb_device_handle *hdl, enum iwmbt_device device)
+{
+ uint8_t data[255];
+ uint8_t datalen, hw_platform, hw_variant;
+ struct iwmbt_version *ver = (struct iwmbt_version *)data;
+ struct iwmbt_version_tlv ver_tlv;
+ int r;
+
+ if (device == IWMBT_DEVICE_7260) {
+ r = iwmbt_bt_reset(hdl);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_bt_reset() failed!");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+ }
+
+ memset(data, 0, sizeof(data));
+ r = iwmbt_read_version_tlv(hdl, data, &datalen);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_read_version_tlv() failed");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ if (datalen == sizeof(*ver) && ver->hw_platform == 0x37) {
+ switch (ver->hw_variant) {
+ case 0x07:
+ case 0x08:
+ return (IWMBT_DEVICE_7260);
+ case 0x0b:
+ case 0x0c:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ return (IWMBT_DEVICE_8260);
+ default:
+ iwmbt_debug("Unsupported hw_variant (0x%2x)",
+ ver->hw_variant);
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+ }
+
+ r = iwmbt_parse_tlv(data, datalen, &ver_tlv);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_parse_tlv() failed");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ hw_platform = (ver_tlv.cnvi_bt >> 8) & 0xff;
+ hw_variant = (ver_tlv.cnvi_bt >> 16) & 0x3f;
+
+ if (hw_platform != 0x37) {
+ iwmbt_debug("Unsupported hw_platform (0x%2x)", hw_platform);
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ return (hw_variant < 0x17 ? IWMBT_DEVICE_8260 : IWMBT_DEVICE_9260);
+}
static int
iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path,
@@ -377,11 +438,10 @@ usage(void)
fprintf(stderr, " -f: firmware path (defaults to %s)\n",
_DEFAULT_IWMBT_FIRMWARE_PATH);
fprintf(stderr, " -I: enable informational output\n");
+ fprintf(stderr, " -p: use PID/VID for model identification\n");
exit(127);
}
-
-
/*
* Returns 0 on success.
*/
@@ -558,7 +618,6 @@ handle_9260(libusb_device_handle *hdl, char *firmware_dir)
{
int r;
uint32_t boot_param;
- struct iwmbt_version vl;
struct iwmbt_version_tlv vt;
char *firmware_path = NULL;
@@ -618,9 +677,9 @@ handle_9260(libusb_device_handle *hdl, char *firmware_dir)
/* Once device is running in operational mode we can ignore failures */
- r = iwmbt_get_version(hdl, &vl);
+ r = iwmbt_get_version_tlv(hdl, &vt);
if (r == 0)
- iwmbt_dump_version(&vl);
+ iwmbt_dump_version_tlv(&vt);
/* Apply the device configuration (DDC) parameters */
firmware_path = iwmbt_get_fwname_tlv(&vt, firmware_dir, "ddc");
@@ -673,6 +732,9 @@ main(int argc, char *argv[])
case 'I':
iwmbt_do_info = 1;
break;
+ case 'p':
+ iwmbt_do_pidvid = true;
+ break;
case 'h':
default:
usage();
@@ -730,6 +792,14 @@ main(int argc, char *argv[])
goto shutdown;
}
+ if (!iwmbt_do_pidvid) {
+ iwmbt_device = iwmbt_identify(hdl, iwmbt_device);
+ if (iwmbt_device == IWMBT_DEVICE_UNKNOWN) {
+ iwmbt_err("Failed to identify device");
+ goto shutdown;
+ }
+ }
+
switch(iwmbt_device) {
case IWMBT_DEVICE_7260:
retcode = handle_7260(hdl, firmware_dir);