git: 84488787f42b - main - iwmbtfw(8): Identify device type based on HCI quieries result

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
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);