aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/bluetooth/iwmbtfw
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth/iwmbtfw')
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/Makefile11
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h45
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c194
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h156
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c777
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h119
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8101
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf12
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/main.c770
10 files changed, 2201 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/iwmbtfw/Makefile b/usr.sbin/bluetooth/iwmbtfw/Makefile
new file mode 100644
index 000000000000..c5cf037eac06
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/Makefile
@@ -0,0 +1,11 @@
+PACKAGE= bluetooth
+CONFS= iwmbtfw.conf
+CONFSDIR= /etc/devd
+PROG= iwmbtfw
+MAN= iwmbtfw.8
+LIBADD+= usb
+# Not having NDEBUG defined will enable assertions
+CFLAGS+= -DNDEBUG
+SRCS= main.c iwmbt_fw.c iwmbt_hw.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/iwmbtfw/Makefile.depend b/usr.sbin/bluetooth/iwmbtfw/Makefile.depend
new file mode 100644
index 000000000000..34fbfadfcfb6
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libusb \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h
new file mode 100644
index 000000000000..9cfded6f33cf
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h
@@ -0,0 +1,45 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef __IWMBT_DEBUG_H__
+#define __IWMBT_DEBUG_H__
+
+extern int iwmbt_do_debug;
+extern int iwmbt_do_info;
+
+#define iwmbt_err(fmt, ...) \
+ fprintf(stderr, "iwmbtfw: %s: "fmt"\n", __func__, ##__VA_ARGS__)
+#define iwmbt_info(fmt, ...) do { \
+ if (iwmbt_do_info) \
+ fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\
+} while (0)
+#define iwmbt_debug(fmt, ...) do { \
+ if (iwmbt_do_debug) \
+ fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\
+} while (0)
+
+#endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
new file mode 100644
index 000000000000..3a5cd9d42658
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
@@ -0,0 +1,194 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iwmbt_fw.h"
+#include "iwmbt_dbg.h"
+
+int
+iwmbt_fw_read(struct iwmbt_firmware *fw, const char *fwname)
+{
+ int fd;
+ struct stat sb;
+ unsigned char *buf;
+ ssize_t r;
+
+ fd = open(fwname, O_RDONLY);
+ if (fd < 0) {
+ warn("%s: open: %s", __func__, fwname);
+ return (0);
+ }
+
+ if (fstat(fd, &sb) != 0) {
+ warn("%s: stat: %s", __func__, fwname);
+ close(fd);
+ return (0);
+ }
+
+ buf = calloc(1, sb.st_size);
+ if (buf == NULL) {
+ warn("%s: calloc", __func__);
+ close(fd);
+ return (0);
+ }
+
+ /* XXX handle partial reads */
+ r = read(fd, buf, sb.st_size);
+ if (r < 0) {
+ warn("%s: read", __func__);
+ free(buf);
+ close(fd);
+ return (0);
+ }
+
+ if (r != sb.st_size) {
+ iwmbt_err("read len %d != file size %d",
+ (int) r,
+ (int) sb.st_size);
+ free(buf);
+ close(fd);
+ return (0);
+ }
+
+ /* We have everything, so! */
+
+ memset(fw, 0, sizeof(*fw));
+
+ fw->fwname = strdup(fwname);
+ fw->len = sb.st_size;
+ fw->buf = buf;
+
+ close(fd);
+ return (1);
+}
+
+void
+iwmbt_fw_free(struct iwmbt_firmware *fw)
+{
+ if (fw->fwname)
+ free(fw->fwname);
+ if (fw->buf)
+ free(fw->buf);
+ memset(fw, 0, sizeof(*fw));
+}
+
+char *
+iwmbt_get_fwname(struct iwmbt_version *ver, struct iwmbt_boot_params *params,
+ const char *prefix, const char *suffix)
+{
+ struct stat sb;
+ char *fwname;
+
+ switch (ver->hw_variant) {
+ case 0x07: /* 7260 */
+ case 0x08: /* 7265 */
+ // NB: don't use params, they are NULL for 7xxx
+ 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);
+ /*
+ * Fallback to the default firmware patch if
+ * the correct firmware patch file is not found.
+ */
+ if (stat(fwname, &sb) != 0 && errno == ENOENT) {
+ free(fwname);
+ asprintf(&fwname, "%s/ibt-hw-%x.%x.%s",
+ prefix,
+ le16toh(ver->hw_platform),
+ le16toh(ver->hw_variant),
+ suffix);
+ }
+ break;
+
+ case 0x0b: /* 8260 */
+ case 0x0c: /* 8265 */
+ asprintf(&fwname, "%s/ibt-%u-%u.%s",
+ prefix,
+ le16toh(ver->hw_variant),
+ le16toh(params->dev_revid),
+ suffix);
+ break;
+
+ case 0x11: /* 9560 */
+ case 0x12: /* 9260 */
+ case 0x13:
+ case 0x14: /* 22161 */
+ asprintf(&fwname, "%s/ibt-%u-%u-%u.%s",
+ prefix,
+ le16toh(ver->hw_variant),
+ le16toh(ver->hw_revision),
+ le16toh(ver->fw_revision),
+ suffix);
+ break;
+
+ default:
+ fwname = NULL;
+ }
+
+ return (fwname);
+}
+
+char *
+iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver, const char *prefix,
+ const char *suffix)
+{
+ char *fwname;
+
+#define IWMBT_PACK_CNVX_TOP(cnvx_top) ((uint16_t)( \
+ ((cnvx_top) & 0x0f000000) >> 16 | \
+ ((cnvx_top) & 0x0000000f) << 12 | \
+ ((cnvx_top) & 0x00000ff0) >> 4))
+
+ asprintf(&fwname, "%s/ibt-%04x-%04x.%s",
+ prefix,
+ IWMBT_PACK_CNVX_TOP(ver->cnvi_top),
+ IWMBT_PACK_CNVX_TOP(ver->cnvr_top),
+ suffix);
+
+ return (fwname);
+}
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
new file mode 100644
index 000000000000..eb6909a1f91d
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
@@ -0,0 +1,156 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __IWMBT_FW_H__
+#define __IWMBT_FW_H__
+
+#include <sys/types.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+
+#define RSA_HEADER_LEN 644
+#define ECDSA_HEADER_LEN 320
+#define ECDSA_OFFSET RSA_HEADER_LEN
+#define CSS_HEADER_OFFSET 8
+
+struct iwmbt_version {
+ uint8_t status;
+ uint8_t hw_platform;
+ uint8_t hw_variant;
+ uint8_t hw_revision;
+ uint8_t fw_variant;
+ uint8_t fw_revision;
+ uint8_t fw_build_num;
+ uint8_t fw_build_ww;
+ uint8_t fw_build_yy;
+ uint8_t fw_patch_num;
+} __attribute__ ((packed));
+
+/* Known values for fw_variant */
+#define FW_VARIANT_BOOTLOADER 0x06 /* Bootloader mode */
+#define FW_VARIANT_OPERATIONAL 0x23 /* Operational mode */
+
+struct iwmbt_boot_params {
+ uint8_t status;
+ uint8_t otp_format;
+ uint8_t otp_content;
+ uint8_t otp_patch;
+ uint16_t dev_revid;
+ uint8_t secure_boot;
+ uint8_t key_from_hdr;
+ uint8_t key_type;
+ uint8_t otp_lock;
+ uint8_t api_lock;
+ uint8_t debug_lock;
+ uint8_t otp_bdaddr[6];
+ uint8_t min_fw_build_nn;
+ uint8_t min_fw_build_cw;
+ uint8_t min_fw_build_yy;
+ uint8_t limited_cce;
+ uint8_t unlocked_state;
+} __attribute__ ((packed));
+
+enum {
+ IWMBT_TLV_CNVI_TOP = 0x10,
+ IWMBT_TLV_CNVR_TOP,
+ IWMBT_TLV_CNVI_BT,
+ IWMBT_TLV_CNVR_BT,
+ IWMBT_TLV_CNVI_OTP,
+ IWMBT_TLV_CNVR_OTP,
+ IWMBT_TLV_DEV_REV_ID,
+ IWMBT_TLV_USB_VENDOR_ID,
+ IWMBT_TLV_USB_PRODUCT_ID,
+ IWMBT_TLV_PCIE_VENDOR_ID,
+ IWMBT_TLV_PCIE_DEVICE_ID,
+ IWMBT_TLV_PCIE_SUBSYSTEM_ID,
+ IWMBT_TLV_IMAGE_TYPE,
+ IWMBT_TLV_TIME_STAMP,
+ IWMBT_TLV_BUILD_TYPE,
+ IWMBT_TLV_BUILD_NUM,
+ IWMBT_TLV_FW_BUILD_PRODUCT,
+ IWMBT_TLV_FW_BUILD_HW,
+ IWMBT_TLV_FW_STEP,
+ IWMBT_TLV_BT_SPEC,
+ IWMBT_TLV_MFG_NAME,
+ IWMBT_TLV_HCI_REV,
+ IWMBT_TLV_LMP_SUBVER,
+ IWMBT_TLV_OTP_PATCH_VER,
+ IWMBT_TLV_SECURE_BOOT,
+ IWMBT_TLV_KEY_FROM_HDR,
+ IWMBT_TLV_OTP_LOCK,
+ IWMBT_TLV_API_LOCK,
+ IWMBT_TLV_DEBUG_LOCK,
+ IWMBT_TLV_MIN_FW,
+ IWMBT_TLV_LIMITED_CCE,
+ IWMBT_TLV_SBE_TYPE,
+ IWMBT_TLV_OTP_BDADDR,
+ IWMBT_TLV_UNLOCKED_STATE
+};
+
+struct iwmbt_version_tlv {
+ uint32_t cnvi_top;
+ uint32_t cnvr_top;
+ uint32_t cnvi_bt;
+ uint32_t cnvr_bt;
+ uint16_t dev_rev_id;
+ uint8_t img_type;
+ uint16_t timestamp;
+ uint8_t build_type;
+ uint32_t build_num;
+ uint8_t secure_boot;
+ uint8_t otp_lock;
+ uint8_t api_lock;
+ uint8_t debug_lock;
+ uint8_t min_fw_build_nn;
+ uint8_t min_fw_build_cw;
+ uint8_t min_fw_build_yy;
+ uint8_t limited_cce;
+ uint8_t sbe_type;
+ bdaddr_t otp_bd_addr;
+};
+
+/* Known TLV img_type values */
+#define TLV_IMG_TYPE_BOOTLOADER 0x01 /* Bootloader mode */
+#define TLV_IMG_TYPE_OPERATIONAL 0x03 /* Operational mode */
+
+struct iwmbt_firmware {
+ char *fwname;
+ int len;
+ unsigned char *buf;
+};
+
+extern int iwmbt_fw_read(struct iwmbt_firmware *fw, const char *fwname);
+extern void iwmbt_fw_free(struct iwmbt_firmware *fw);
+extern char *iwmbt_get_fwname(struct iwmbt_version *ver,
+ struct iwmbt_boot_params *params, const char *prefix,
+ const char *suffix);
+extern char *iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver,
+ const char *prefix, const char *suffix);
+
+#endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
new file mode 100644
index 000000000000..255181b8f4bc
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
@@ -0,0 +1,777 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libusb.h>
+
+#include <netgraph/bluetooth/include/ng_hci.h>
+
+#include "iwmbt_fw.h"
+#include "iwmbt_hw.h"
+#include "iwmbt_dbg.h"
+
+#define XMIN(x, y) ((x) < (y) ? (x) : (y))
+
+static int
+iwmbt_send_fragment(struct libusb_device_handle *hdl,
+ uint8_t fragment_type, const void *data, uint8_t len, int timeout)
+{
+ int ret, transferred;
+ uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE];
+ struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *) buf;
+
+ memset(buf, 0, sizeof(buf));
+ cmd->opcode = htole16(0xfc09),
+ cmd->length = len + 1,
+ cmd->data[0] = fragment_type;
+ memcpy(cmd->data + 1, data, len);
+
+ ret = libusb_bulk_transfer(hdl,
+ IWMBT_BULK_OUT_ENDPOINT_ADDR,
+ (uint8_t *)cmd,
+ IWMBT_HCI_CMD_SIZE(cmd),
+ &transferred,
+ timeout);
+
+ if (ret < 0 || transferred != (int)IWMBT_HCI_CMD_SIZE(cmd)) {
+ iwmbt_err("libusb_bulk_transfer() failed: err=%s, size=%zu",
+ libusb_strerror(ret),
+ IWMBT_HCI_CMD_SIZE(cmd));
+ return (-1);
+ }
+
+ ret = libusb_bulk_transfer(hdl,
+ IWMBT_BULK_IN_ENDPOINT_ADDR,
+ buf,
+ sizeof(buf),
+ &transferred,
+ timeout);
+
+ if (ret < 0) {
+ iwmbt_err("libusb_bulk_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+iwmbt_hci_command(struct libusb_device_handle *hdl, struct iwmbt_hci_cmd *cmd,
+ void *event, int size, int *transferred, int timeout)
+{
+ struct timespec to, now, remains;
+ int ret;
+
+ ret = libusb_control_transfer(hdl,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,
+ 0,
+ 0,
+ 0,
+ (uint8_t *)cmd,
+ IWMBT_HCI_CMD_SIZE(cmd),
+ timeout);
+
+ if (ret < 0) {
+ iwmbt_err("libusb_control_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (ret);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ to = IWMBT_MSEC2TS(timeout);
+ timespecadd(&to, &now, &to);
+
+ do {
+ timespecsub(&to, &now, &remains);
+ ret = libusb_interrupt_transfer(hdl,
+ IWMBT_INTERRUPT_ENDPOINT_ADDR,
+ event,
+ size,
+ transferred,
+ IWMBT_TS2MSEC(remains) + 1);
+
+ if (ret < 0) {
+ iwmbt_err("libusb_interrupt_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (ret);
+ }
+
+ switch (((struct iwmbt_hci_event *)event)->header.event) {
+ case NG_HCI_EVENT_COMMAND_COMPL:
+ if (*transferred <
+ (int)offsetof(struct iwmbt_hci_event_cmd_compl, data))
+ break;
+ if (cmd->opcode !=
+ ((struct iwmbt_hci_event_cmd_compl *)event)->opcode)
+ break;
+ /* FALLTHROUGH */
+ case 0xFF:
+ return (0);
+ default:
+ break;
+ }
+ iwmbt_debug("Stray HCI event: %x",
+ ((struct iwmbt_hci_event *)event)->header.event);
+ } while (timespeccmp(&to, &now, >));
+
+ iwmbt_err("libusb_interrupt_transfer() failed: err=%s",
+ libusb_strerror(LIBUSB_ERROR_TIMEOUT));
+
+ return (LIBUSB_ERROR_TIMEOUT);
+}
+
+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;
+ struct iwmbt_hci_cmd *cmd_buf;
+ uint8_t evt_code;
+ uint8_t evt_length;
+ uint8_t evt_buf[IWMBT_HCI_MAX_EVENT_SIZE];
+ int activate_patch = 0;
+
+ while (fw_job.len > 0) {
+ if (fw_job.len < 4) {
+ iwmbt_err("Invalid firmware, unexpected EOF in HCI "
+ "command header. Remains=%d", fw_job.len);
+ return (-1);
+ }
+
+ 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];
+ cmd_buf = (struct iwmbt_hci_cmd *)fw_job.buf;
+
+ iwmbt_debug("opcode=%04x, len=%02x", cmd_opcode, cmd_length);
+
+ /*
+ * If there is a command that loads a patch in the
+ * firmware file, then activate the patch upon success,
+ * otherwise just disable the manufacturer mode.
+ */
+ if (cmd_opcode == 0xfc8e)
+ activate_patch = 1;
+
+ /* Advance by three. */
+ fw_job.buf += 3;
+ fw_job.len -= 3;
+
+ if (fw_job.len < cmd_length) {
+ iwmbt_err("Invalid firmware, unexpected EOF in HCI "
+ "command data. len=%d, remains=%d",
+ cmd_length, fw_job.len);
+ return (-1);
+ }
+
+ /* Advance by data length. */
+ fw_job.buf += cmd_length;
+ fw_job.len -= cmd_length;
+
+ ret = libusb_control_transfer(hdl,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,
+ 0,
+ 0,
+ 0,
+ (uint8_t *)cmd_buf,
+ IWMBT_HCI_CMD_SIZE(cmd_buf),
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0) {
+ iwmbt_err("libusb_control_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (-1);
+ }
+
+ /*
+ * Every command has its associated event: data must match
+ * what is recorded in the firmware file. Perform that check
+ * now.
+ */
+
+ while (fw_job.len > 0 && fw_job.buf[0] == 0x02) {
+ /* Is this the end of the file? */
+ if (fw_job.len < 3) {
+ iwmbt_err("Invalid firmware, unexpected EOF in"
+ "event header. remains=%d", fw_job.len);
+ return (-1);
+ }
+
+ /* 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);
+
+ if (fw_job.len < evt_length) {
+ iwmbt_err("Invalid firmware, unexpected EOF in"
+ " event data. len=%d, remains=%d",
+ evt_length, fw_job.len);
+ return (-1);
+ }
+
+ ret = libusb_interrupt_transfer(hdl,
+ IWMBT_INTERRUPT_ENDPOINT_ADDR,
+ evt_buf,
+ IWMBT_HCI_MAX_EVENT_SIZE,
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0) {
+ iwmbt_err("libusb_interrupt_transfer() failed:"
+ " err=%s", libusb_strerror(ret));
+ return (-1);
+ }
+
+ if ((int)evt_length + 2 != transferred ||
+ memcmp(evt_buf + 2, fw_job.buf, evt_length) != 0) {
+ iwmbt_err("event does not match firmware");
+ return (-1);
+ }
+
+ /* Advance by data length. */
+ fw_job.buf += evt_length;
+ fw_job.len -= evt_length;
+ }
+ }
+
+ return (activate_patch);
+}
+
+#define IWMBT_SEND_FRAGMENT(fragment_type, size, msg) do { \
+ iwmbt_debug("transferring %d bytes, offset %d", size, sent); \
+ \
+ ret = iwmbt_send_fragment(hdl, \
+ fragment_type, \
+ fw->buf + sent, \
+ XMIN(size, fw->len - sent), \
+ IWMBT_HCI_CMD_TIMEOUT); \
+ \
+ if (ret < 0) { \
+ iwmbt_debug("Failed to send "msg": code=%d", ret); \
+ return (-1); \
+ } \
+ sent += size; \
+} while (0)
+
+int
+iwmbt_load_rsa_header(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *fw)
+{
+ int ret, sent = 0;
+
+ IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment");
+ IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 1");
+ IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 2");
+
+ /* skip 4 bytes */
+ sent += 4;
+
+ IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 1");
+ IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 2");
+
+ return (0);
+}
+
+int
+iwmbt_load_ecdsa_header(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *fw)
+{
+ int ret, sent = ECDSA_OFFSET;
+
+ IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment");
+ IWMBT_SEND_FRAGMENT(0x03, 0x60, "public key");
+ IWMBT_SEND_FRAGMENT(0x02, 0x60, "signature");
+
+ return (0);
+}
+
+int
+iwmbt_load_fwfile(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *fw, uint32_t *boot_param, int offset)
+{
+ int ready = 0, sent = offset;
+ int ret, transferred;
+ struct iwmbt_hci_cmd *cmd;
+ struct iwmbt_hci_event *event;
+ uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];
+
+ /*
+ * Send firmware chunks. Chunk len must be 4 byte aligned.
+ * multiple commands can be combined
+ */
+ while (fw->len - sent - ready >= (int) sizeof(struct iwmbt_hci_cmd)) {
+ cmd = (struct iwmbt_hci_cmd *)(fw->buf + sent + ready);
+ /* Parse firmware for Intel Reset HCI command parameter */
+ if (cmd->opcode == htole16(0xfc0e)) {
+ *boot_param = le32dec(cmd->data);
+ iwmbt_debug("boot_param=0x%08x", *boot_param);
+ }
+ ready += IWMBT_HCI_CMD_SIZE(cmd);
+ while (ready >= 0xFC) {
+ IWMBT_SEND_FRAGMENT(0x01, 0xFC, "firmware chunk");
+ ready -= 0xFC;
+ }
+ if (ready > 0 && ready % 4 == 0) {
+ IWMBT_SEND_FRAGMENT(0x01, ready, "firmware chunk");
+ ready = 0;
+ }
+ }
+
+ /* Wait for firmware download completion event */
+ ret = libusb_interrupt_transfer(hdl,
+ IWMBT_INTERRUPT_ENDPOINT_ADDR,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_LOADCMPL_TIMEOUT);
+
+ if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) {
+ iwmbt_err("libusb_interrupt_transfer() failed: "
+ "err=%s, size=%d",
+ libusb_strerror(ret),
+ transferred);
+ return (-1);
+ }
+
+ /* Expect Vendor Specific Event 0x06 */
+ event = (struct iwmbt_hci_event *)buf;
+ if (event->header.event != 0xFF || event->data[0] != 0x06) {
+ iwmbt_err("firmware download completion event missed");
+ return (-1);
+ }
+
+ 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,
+ enum iwmbt_mm_exit 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];
+
+ cmd.data[1] = (uint8_t)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)
+{
+ int ret, transferred;
+ struct iwmbt_hci_event_cmd_compl*event;
+ struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc05),
+ .length = 0,
+ };
+ uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_version)];
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = iwmbt_hci_command(hdl,
+ &cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0 || transferred != sizeof(buf)) {
+ iwmbt_debug("Can't get version: : code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ event = (struct iwmbt_hci_event_cmd_compl *)buf;
+ memcpy(version, event->data, sizeof(struct iwmbt_version));
+
+ return (0);
+}
+
+int
+iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
+ struct iwmbt_version_tlv *version)
+{
+ int ret, transferred;
+ struct iwmbt_hci_event_cmd_compl *event;
+ static struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc05),
+ .length = 1,
+ .data = { 0xff },
+ };
+ uint8_t status, datalen, type, len;
+ uint8_t *data;
+ uint8_t buf[255];
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = iwmbt_hci_command(hdl,
+ &cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0 || transferred < (int)IWMBT_HCI_EVT_COMPL_SIZE(uint16_t)) {
+ iwmbt_debug("Can't get version: code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ 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;
+ data = event->data;
+ 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);
+}
+
+int
+iwmbt_get_boot_params(struct libusb_device_handle *hdl,
+ struct iwmbt_boot_params *params)
+{
+ int ret, transferred = 0;
+ struct iwmbt_hci_event_cmd_compl *event;
+ struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc0d),
+ .length = 0,
+ };
+ uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_boot_params)];
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = iwmbt_hci_command(hdl,
+ &cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0 || transferred != sizeof(buf)) {
+ iwmbt_debug("Can't get boot params: code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ event = (struct iwmbt_hci_event_cmd_compl *)buf;
+ memcpy(params, event->data, sizeof(struct iwmbt_boot_params));
+
+ return (0);
+}
+
+int
+iwmbt_intel_reset(struct libusb_device_handle *hdl, uint32_t boot_param)
+{
+ int ret, transferred = 0;
+ struct iwmbt_hci_event *event;
+ static struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc01),
+ .length = 8,
+ .data = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ };
+ uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];
+
+ le32enc(cmd.data + 4, boot_param);
+ memset(buf, 0, sizeof(buf));
+
+ ret = iwmbt_hci_command(hdl,
+ &cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) {
+ iwmbt_debug("Intel Reset command failed: code=%d, size=%d",
+ ret,
+ transferred);
+ return (ret);
+ }
+
+ /* expect Vendor Specific Event 0x02 */
+ event = (struct iwmbt_hci_event *)buf;
+ if (event->header.event != 0xFF || event->data[0] != 0x02) {
+ iwmbt_err("Intel Reset completion event missed");
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+iwmbt_load_ddc(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *ddc)
+{
+ int size, sent = 0;
+ int ret, transferred;
+ uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE];
+ uint8_t evt[IWMBT_HCI_MAX_CMD_SIZE];
+ struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *)buf;
+
+ size = ddc->len;
+
+ iwmbt_debug("file=%s, size=%d", ddc->fwname, size);
+
+ while (size > 0) {
+
+ memset(buf, 0, sizeof(buf));
+ cmd->opcode = htole16(0xfc8b);
+ cmd->length = ddc->buf[sent] + 1;
+ memcpy(cmd->data, ddc->buf + sent, XMIN(ddc->buf[sent], size));
+
+ iwmbt_debug("transferring %d bytes, offset %d",
+ cmd->length,
+ sent);
+
+ size -= cmd->length;
+ sent += cmd->length;
+
+ ret = iwmbt_hci_command(hdl,
+ cmd,
+ evt,
+ sizeof(evt),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0) {
+ iwmbt_debug("Intel Write DDC failed: code=%d", ret);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
+iwmbt_set_event_mask(struct libusb_device_handle *hdl)
+{
+ int ret, transferred = 0;
+ static struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc52),
+ .length = 8,
+ .data = { 0x87, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 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("Intel Set Event Mask failed: code=%d", ret);
+
+ return (ret);
+}
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
new file mode 100644
index 000000000000..aac885dfd153
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
@@ -0,0 +1,119 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef __IWMBT_HW_H__
+#define __IWMBT_HW_H__
+
+/* USB control request (HCI command) structure */
+struct iwmbt_hci_cmd {
+ uint16_t opcode;
+ uint8_t length;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+#define IWMBT_HCI_CMD_SIZE(cmd) \
+ ((cmd)->length + offsetof(struct iwmbt_hci_cmd, data))
+
+/* USB interrupt transfer HCI event header structure */
+struct iwmbt_hci_evhdr {
+ uint8_t event;
+ uint8_t length;
+} __attribute__ ((packed));
+
+/* USB interrupt transfer (generic HCI event) structure */
+struct iwmbt_hci_event {
+ struct iwmbt_hci_evhdr header;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+/* USB interrupt transfer (HCI command completion event) structure */
+struct iwmbt_hci_event_cmd_compl {
+ struct iwmbt_hci_evhdr header;
+ uint8_t numpkt;
+ uint16_t opcode;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+/*
+ * Manufacturer mode exit type: selects reset type,
+ * 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
+ */
+enum iwmbt_mm_exit {
+ IWMBT_MM_EXIT_ONLY = 0x00,
+ IWMBT_MM_EXIT_COLD_RESET = 0x01,
+ IWMBT_MM_EXIT_WARM_RESET = 0x02,
+};
+
+#define IWMBT_HCI_EVT_COMPL_SIZE(payload) \
+ (offsetof(struct iwmbt_hci_event_cmd_compl, data) + sizeof(payload))
+#define IWMBT_HCI_EVENT_COMPL_HEAD_SIZE \
+ (offsetof(struct iwmbt_hci_event_cmd_compl, data) - \
+ offsetof(struct iwmbt_hci_event_cmd_compl, numpkt))
+
+#define IWMBT_CONTROL_ENDPOINT_ADDR 0x00
+#define IWMBT_INTERRUPT_ENDPOINT_ADDR 0x81
+#define IWMBT_BULK_IN_ENDPOINT_ADDR 0x82
+#define IWMBT_BULK_OUT_ENDPOINT_ADDR 0x02
+
+#define IWMBT_HCI_MAX_CMD_SIZE 256
+#define IWMBT_HCI_MAX_EVENT_SIZE 16
+
+#define IWMBT_MSEC2TS(msec) \
+ (struct timespec) { \
+ .tv_sec = (msec) / 1000, \
+ .tv_nsec = ((msec) % 1000) * 1000000 \
+ };
+#define IWMBT_TS2MSEC(ts) ((ts).tv_sec * 1000 + (ts).tv_nsec / 1000000)
+#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_rsa_header(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *fw);
+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_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_get_version_tlv(struct libusb_device_handle *hdl,
+ struct iwmbt_version_tlv *version);
+extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl,
+ struct iwmbt_boot_params *params);
+extern int iwmbt_intel_reset(struct libusb_device_handle *hdl,
+ uint32_t boot_param);
+extern int iwmbt_load_ddc(struct libusb_device_handle *hdl,
+ const struct iwmbt_firmware *ddc);
+extern int iwmbt_set_event_mask(struct libusb_device_handle *hdl);
+
+#endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
new file mode 100644
index 000000000000..ac32a675aa63
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
@@ -0,0 +1,101 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" 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
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 15, 2025
+.Dt IWMBTFW 8
+.Os
+.Sh NAME
+.Nm iwmbtfw
+.Nd load firmware for Intel Wireless AC Bluetooth USB devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl DI
+.Fl d Ar device_name
+.Op Fl f Ar firmware_path
+.Nm
+.Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility downloads the specified firmware file to the specified
+.Xr ugen 4
+device.
+.Pp
+This utility will
+.Em only
+work with Intel Wireless 7260/8260/9260 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
+.Pq Dv USB_VENDOR_INTEL2
+and the product ID should be one of the supported devices.
+.Pp
+Firmware files are available in the
+.Pa comms/iwmbt-firmware
+port.
+.Pp
+The
+.Nm
+utility will query the device to determine which firmware image and board
+configuration to load in at runtime.
+.Pp
+The options are as follows:
+.Bl -tag -width "-f firmware_path"
+.It Fl I
+Enable informational debugging.
+.It Fl D
+Enable verbose debugging.
+.It Fl d Ar device_name
+Specify
+.Xr ugen 4
+device name.
+.It Fl f Ar firmware_path
+Specify the directory containing the firmware files to search and upload.
+.It Fl h
+Display usage message and exit.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr libusb 3 ,
+.Xr ugen 4 ,
+.Xr devd 8
+.Sh AUTHORS
+.Nm
+is based on
+.Xr ath3kfw 8
+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
new file mode 100644
index 000000000000..e30a3c15ccaa
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf
@@ -0,0 +1,12 @@
+#
+# Download Intel Wireless bluetooth adaptor firmware
+#
+
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x8087";
+ match "product" "(0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029|0x0032|0x0033)";
+ 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
new file mode 100644
index 000000000000..b27c5ad62239
--- /dev/null
+++ b/usr.sbin/bluetooth/iwmbtfw/main.c
@@ -0,0 +1,770 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libusb.h>
+
+#include "iwmbt_fw.h"
+#include "iwmbt_hw.h"
+#include "iwmbt_dbg.h"
+
+#define _DEFAULT_IWMBT_FIRMWARE_PATH "/usr/share/firmware/intel"
+
+int iwmbt_do_debug = 0;
+int iwmbt_do_info = 0;
+
+enum iwmbt_device {
+ IWMBT_DEVICE_UNKNOWN,
+ IWMBT_DEVICE_7260,
+ IWMBT_DEVICE_8260,
+ IWMBT_DEVICE_9260,
+};
+
+struct iwmbt_devid {
+ uint16_t product_id;
+ uint16_t vendor_id;
+ enum iwmbt_device device;
+};
+
+static struct iwmbt_devid iwmbt_list[] = {
+
+ /* Intel Wireless 7260/7265 and successors */
+ { .vendor_id = 0x8087, .product_id = 0x07dc, .device = IWMBT_DEVICE_7260 },
+ { .vendor_id = 0x8087, .product_id = 0x0a2a, .device = IWMBT_DEVICE_7260 },
+ { .vendor_id = 0x8087, .product_id = 0x0aa7, .device = IWMBT_DEVICE_7260 },
+
+ /* Intel Wireless 8260/8265 and successors */
+ { .vendor_id = 0x8087, .product_id = 0x0a2b, .device = IWMBT_DEVICE_8260 },
+ { .vendor_id = 0x8087, .product_id = 0x0aaa, .device = IWMBT_DEVICE_8260 },
+ { .vendor_id = 0x8087, .product_id = 0x0025, .device = IWMBT_DEVICE_8260 },
+ { .vendor_id = 0x8087, .product_id = 0x0026, .device = IWMBT_DEVICE_8260 },
+ { .vendor_id = 0x8087, .product_id = 0x0029, .device = IWMBT_DEVICE_8260 },
+
+ /* Intel Wireless 9260/9560 and successors */
+ { .vendor_id = 0x8087, .product_id = 0x0032, .device = IWMBT_DEVICE_9260 },
+ { .vendor_id = 0x8087, .product_id = 0x0033, .device = IWMBT_DEVICE_9260 },
+};
+
+static enum iwmbt_device
+iwmbt_is_supported(struct libusb_device_descriptor *d)
+{
+ int i;
+
+ /* Search looking for whether it's an 7260/7265 */
+ for (i = 0; i < (int) nitems(iwmbt_list); i++) {
+ if ((iwmbt_list[i].product_id == d->idProduct) &&
+ (iwmbt_list[i].vendor_id == d->idVendor)) {
+ iwmbt_info("found iwmbtfw compatible");
+ return (iwmbt_list[i].device);
+ }
+ }
+
+ /* Not found */
+ return (IWMBT_DEVICE_UNKNOWN);
+}
+
+static libusb_device *
+iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id,
+ enum iwmbt_device *iwmbt_device)
+{
+ libusb_device **list, *dev = NULL, *found = NULL;
+ struct libusb_device_descriptor d;
+ enum iwmbt_device device;
+ ssize_t cnt, i;
+ int r;
+
+ cnt = libusb_get_device_list(ctx, &list);
+ if (cnt < 0) {
+ iwmbt_err("libusb_get_device_list() failed: code %lld",
+ (long long int) cnt);
+ return (NULL);
+ }
+
+ /*
+ * Scan through USB device list.
+ */
+ for (i = 0; i < cnt; i++) {
+ dev = list[i];
+ if (bus_id == libusb_get_bus_number(dev) &&
+ dev_id == libusb_get_device_address(dev)) {
+ /* Get the device descriptor for this device entry */
+ r = libusb_get_device_descriptor(dev, &d);
+ if (r != 0) {
+ iwmbt_err("libusb_get_device_descriptor: %s",
+ libusb_strerror(r));
+ break;
+ }
+
+ /* Match on the vendor/product id */
+ device = iwmbt_is_supported(&d);
+ if (device != IWMBT_DEVICE_UNKNOWN) {
+ /*
+ * Take a reference so it's not freed later on.
+ */
+ found = libusb_ref_device(dev);
+ *iwmbt_device = device;
+ break;
+ }
+ }
+ }
+
+ libusb_free_device_list(list, 1);
+ return (found);
+}
+
+static void
+iwmbt_dump_version(struct iwmbt_version *ver)
+{
+ iwmbt_info("status 0x%02x", ver->status);
+ iwmbt_info("hw_platform 0x%02x", ver->hw_platform);
+ iwmbt_info("hw_variant 0x%02x", ver->hw_variant);
+ iwmbt_info("hw_revision 0x%02x", ver->hw_revision);
+ iwmbt_info("fw_variant 0x%02x", ver->fw_variant);
+ iwmbt_info("fw_revision 0x%02x", ver->fw_revision);
+ iwmbt_info("fw_build_num 0x%02x", ver->fw_build_num);
+ iwmbt_info("fw_build_ww 0x%02x", ver->fw_build_ww);
+ iwmbt_info("fw_build_yy 0x%02x", ver->fw_build_yy);
+ iwmbt_info("fw_patch_num 0x%02x", ver->fw_patch_num);
+}
+
+static void
+iwmbt_dump_boot_params(struct iwmbt_boot_params *params)
+{
+ iwmbt_info("Device revision: %u", le16toh(params->dev_revid));
+ iwmbt_info("Secure Boot: %s", params->secure_boot ? "on" : "off");
+ iwmbt_info("OTP lock: %s", params->otp_lock ? "on" : "off");
+ iwmbt_info("API lock: %s", params->api_lock ? "on" : "off");
+ iwmbt_info("Debug lock: %s", params->debug_lock ? "on" : "off");
+ iwmbt_info("Minimum firmware build %u week %u year %u",
+ params->min_fw_build_nn,
+ params->min_fw_build_cw,
+ 2000 + params->min_fw_build_yy);
+ iwmbt_info("OTC BD_ADDR: %02x:%02x:%02x:%02x:%02x:%02x",
+ params->otp_bdaddr[5],
+ params->otp_bdaddr[4],
+ params->otp_bdaddr[3],
+ params->otp_bdaddr[2],
+ params->otp_bdaddr[1],
+ params->otp_bdaddr[0]);
+}
+
+static void
+iwmbt_dump_version_tlv(struct iwmbt_version_tlv *ver)
+{
+ iwmbt_info("cnvi_top 0x%08x", ver->cnvi_top);
+ iwmbt_info("cnvr_top 0x%08x", ver->cnvr_top);
+ iwmbt_info("cnvi_bt 0x%08x", ver->cnvi_bt);
+ iwmbt_info("cnvr_bt 0x%08x", ver->cnvr_bt);
+ iwmbt_info("dev_rev_id 0x%04x", ver->dev_rev_id);
+ iwmbt_info("img_type 0x%02x", ver->img_type);
+ iwmbt_info("timestamp 0x%04x", ver->timestamp);
+ iwmbt_info("build_type 0x%02x", ver->build_type);
+ iwmbt_info("build_num 0x%08x", ver->build_num);
+ iwmbt_info("Secure Boot: %s", ver->secure_boot ? "on" : "off");
+ iwmbt_info("OTP lock: %s", ver->otp_lock ? "on" : "off");
+ iwmbt_info("API lock: %s", ver->api_lock ? "on" : "off");
+ iwmbt_info("Debug lock: %s", ver->debug_lock ? "on" : "off");
+ iwmbt_info("Minimum firmware build %u week %u year %u",
+ ver->min_fw_build_nn,
+ ver->min_fw_build_cw,
+ 2000 + ver->min_fw_build_yy);
+ iwmbt_info("limited_cce 0x%02x", ver->limited_cce);
+ iwmbt_info("sbe_type 0x%02x", ver->sbe_type);
+ iwmbt_info("OTC BD_ADDR: %02x:%02x:%02x:%02x:%02x:%02x",
+ ver->otp_bd_addr.b[5],
+ ver->otp_bd_addr.b[4],
+ ver->otp_bd_addr.b[3],
+ ver->otp_bd_addr.b[2],
+ ver->otp_bd_addr.b[1],
+ ver->otp_bd_addr.b[0]);
+ if (ver->img_type == TLV_IMG_TYPE_BOOTLOADER ||
+ ver->img_type == TLV_IMG_TYPE_OPERATIONAL)
+ iwmbt_info("%s timestamp %u.%u buildtype %u build %u",
+ (ver->img_type == TLV_IMG_TYPE_BOOTLOADER ?
+ "Bootloader" : "Firmware"),
+ 2000 + (ver->timestamp >> 8),
+ ver->timestamp & 0xff,
+ ver->build_type,
+ ver->build_num);
+}
+
+
+static int
+iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path,
+ uint32_t *boot_param, uint8_t hw_variant, uint8_t sbe_type)
+{
+ struct iwmbt_firmware fw;
+ int header_len, ret = -1;
+
+ 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);
+ }
+
+ iwmbt_debug("Firmware file size=%d", fw.len);
+
+ if (hw_variant <= 0x14) {
+ /*
+ * Hardware variants 0x0b, 0x0c, 0x11 - 0x14 .sfi file have
+ * a RSA header of 644 bytes followed by Command Buffer.
+ */
+ header_len = RSA_HEADER_LEN;
+ if (fw.len < header_len) {
+ iwmbt_err("Invalid size of firmware file (%d)", fw.len);
+ ret = -1;
+ goto exit;
+ }
+
+ /* Check if the CSS Header version is RSA(0x00010000) */
+ if (le32dec(fw.buf + CSS_HEADER_OFFSET) != 0x00010000) {
+ iwmbt_err("Invalid CSS Header version");
+ ret = -1;
+ goto exit;
+ }
+
+ /* Only RSA secure boot engine supported */
+ if (sbe_type != 0x00) {
+ iwmbt_err("Invalid SBE type for hardware variant (%d)",
+ hw_variant);
+ ret = -1;
+ goto exit;
+ }
+
+ } else if (hw_variant >= 0x17) {
+ /*
+ * Hardware variants 0x17, 0x18 onwards support both RSA and
+ * ECDSA secure boot engine. As a result, the corresponding sfi
+ * file will have RSA header of 644, ECDSA header of 320 bytes
+ * followed by Command Buffer.
+ */
+ header_len = ECDSA_OFFSET + ECDSA_HEADER_LEN;
+ if (fw.len < header_len) {
+ iwmbt_err("Invalid size of firmware file (%d)", fw.len);
+ ret = -1;
+ goto exit;
+ }
+
+ /* Check if CSS header for ECDSA follows the RSA header */
+ if (fw.buf[ECDSA_OFFSET] != 0x06) {
+ ret = -1;
+ goto exit;
+ }
+
+ /* Check if the CSS Header version is ECDSA(0x00020000) */
+ if (le32dec(fw.buf + ECDSA_OFFSET + CSS_HEADER_OFFSET) != 0x00020000) {
+ iwmbt_err("Invalid CSS Header version");
+ ret = -1;
+ goto exit;
+ }
+ }
+
+ /* Load in the CSS header */
+ if (sbe_type == 0x00)
+ ret = iwmbt_load_rsa_header(hdl, &fw);
+ else if (sbe_type == 0x01)
+ ret = iwmbt_load_ecdsa_header(hdl, &fw);
+ if (ret < 0)
+ goto exit;
+
+ /* Load in the Command Buffer */
+ ret = iwmbt_load_fwfile(hdl, &fw, boot_param, header_len);
+
+exit:
+ /* free firmware */
+ iwmbt_fw_free(&fw);
+
+ return (ret);
+}
+
+static int
+iwmbt_init_ddc(libusb_device_handle *hdl, const char *ddc_path)
+{
+ struct iwmbt_firmware ddc;
+ int ret;
+
+ iwmbt_debug("loading %s", ddc_path);
+
+ /* Read in the DDC file */
+ if (iwmbt_fw_read(&ddc, ddc_path) <= 0) {
+ iwmbt_debug("iwmbt_fw_read() failed");
+ return (-1);
+ }
+
+ /* Load in the DDC file */
+ ret = iwmbt_load_ddc(hdl, &ddc);
+ if (ret < 0)
+ iwmbt_debug("Loading DDC file failed");
+
+ /* free it */
+ iwmbt_fw_free(&ddc);
+
+ return (ret);
+}
+
+/*
+ * Parse ugen name and extract device's bus and address
+ */
+
+static int
+parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr)
+{
+ char *ep;
+
+ if (strncmp(ugen, "ugen", 4) != 0)
+ return (-1);
+
+ *bus = (uint8_t) strtoul(ugen + 4, &ep, 10);
+ if (*ep != '.')
+ return (-1);
+
+ *addr = (uint8_t) strtoul(ep + 1, &ep, 10);
+ if (*ep != '\0')
+ return (-1);
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: iwmbtfw [-DI] -d ugenX.Y [-f firmware path]\n");
+ fprintf(stderr, " -D: enable debugging\n");
+ fprintf(stderr, " -d: device to operate upon\n");
+ fprintf(stderr, " -f: firmware path (defaults to %s)\n",
+ _DEFAULT_IWMBT_FIRMWARE_PATH);
+ fprintf(stderr, " -I: enable informational output\n");
+ exit(127);
+}
+
+
+
+/*
+ * Returns 0 on success.
+ */
+static int
+handle_7260(libusb_device_handle *hdl, char *firmware_dir)
+{
+ int r;
+ char *firmware_path;
+ struct iwmbt_version ver;
+ struct iwmbt_firmware fw;
+
+ r = iwmbt_get_version(hdl, &ver);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_get_version() failed code %d", r);
+ return 1;
+ }
+ iwmbt_dump_version(&ver);
+ iwmbt_debug("fw_patch_num=0x%02x", (int) ver.fw_patch_num);
+
+ /* fw_patch_num = >0 operational mode */
+ if (ver.fw_patch_num > 0x00) {
+ iwmbt_info("Firmware has already been downloaded");
+ return 0;
+ }
+
+ firmware_path = iwmbt_get_fwname(&ver, NULL, firmware_dir, "bseq");
+ if (firmware_path == NULL)
+ return 1;
+ iwmbt_debug("firmware_path = %s", firmware_path);
+
+ r = iwmbt_fw_read(&fw, firmware_path);
+ free(firmware_path);
+ if (r <= 0) {
+ iwmbt_debug("iwmbt_fw_read() failed");
+ return 1;
+ }
+
+ r = iwmbt_enter_manufacturer(hdl);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_enter_manufacturer() failed code %d", r);
+ iwmbt_fw_free(&fw);
+ return 1;
+ }
+
+ /* Download firmware */
+ r = iwmbt_patch_fwfile(hdl, &fw);
+ iwmbt_fw_free(&fw);
+ if (r < 0) {
+ iwmbt_debug("Loading firmware file failed");
+ (void)iwmbt_exit_manufacturer(hdl, IWMBT_MM_EXIT_COLD_RESET);
+ return 1;
+ }
+
+ iwmbt_info("Firmware download complete");
+
+ r = iwmbt_exit_manufacturer(hdl,
+ (r == 0 ? IWMBT_MM_EXIT_ONLY : IWMBT_MM_EXIT_WARM_RESET));
+ if (r < 0) {
+ iwmbt_debug("iwmbt_exit_manufacturer() failed code %d", r);
+ return 1;
+ }
+
+ /* Once device is running in operational mode we can ignore failures */
+
+ /* Dump actual controller version */
+ r = iwmbt_get_version(hdl, &ver);
+ if (r == 0)
+ iwmbt_dump_version(&ver);
+
+ if (iwmbt_enter_manufacturer(hdl) < 0)
+ return 0;
+ r = iwmbt_set_event_mask(hdl);
+ if (r == 0)
+ iwmbt_info("Intel Event Mask is set");
+ (void)iwmbt_exit_manufacturer(hdl, IWMBT_MM_EXIT_ONLY);
+
+ return 0;
+}
+
+
+/*
+ * Returns 0 on success.
+ */
+static int
+handle_8260(libusb_device_handle *hdl, char *firmware_dir)
+{
+ int r;
+ uint32_t boot_param;
+ struct iwmbt_version ver;
+ struct iwmbt_boot_params params;
+ char *firmware_path = NULL;
+
+ r = iwmbt_get_version(hdl, &ver);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_get_version() failed code %d", r);
+ return 1;
+ }
+ iwmbt_dump_version(&ver);
+ iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant);
+
+ if (ver.fw_variant == FW_VARIANT_OPERATIONAL) {
+ iwmbt_info("Firmware has already been downloaded");
+ return 0;
+ }
+
+ if (ver.fw_variant != FW_VARIANT_BOOTLOADER){
+ iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant);
+ return 1;
+ }
+
+ /* Read Intel Secure Boot Params */
+ r = iwmbt_get_boot_params(hdl, &params);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_get_boot_params() failed!");
+ return 1;
+ }
+ 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);
+ return 1;
+ }
+
+ firmware_path = iwmbt_get_fwname(&ver, &params, firmware_dir, "sfi");
+ if (firmware_path == NULL)
+ return 1;
+ 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, 0, 0);
+ free(firmware_path);
+ if (r < 0)
+ return 1;
+
+ iwmbt_info("Firmware download complete");
+
+ r = iwmbt_intel_reset(hdl, boot_param);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_intel_reset() failed!");
+ return 1;
+ }
+
+ iwmbt_info("Firmware operational");
+
+ /* Once device is running in operational mode we can ignore failures */
+
+ /* Dump actual controller version */
+ 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);
+ }
+
+ r = iwmbt_set_event_mask(hdl);
+ if (r == 0)
+ iwmbt_info("Intel Event Mask is set");
+
+ return 0;
+}
+
+
+static int
+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;
+
+ r = iwmbt_get_version_tlv(hdl, &vt);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_get_version_tlv() failed code %d", r);
+ return 1;
+ }
+ iwmbt_dump_version_tlv(&vt);
+ iwmbt_debug("img_type=0x%02x", (int) vt.img_type);
+
+ if (vt.img_type == TLV_IMG_TYPE_OPERATIONAL) {
+ iwmbt_info("Firmware has already been downloaded");
+ return 0;
+ }
+
+ if (vt.img_type != TLV_IMG_TYPE_BOOTLOADER) {
+ iwmbt_err("unknown img_type 0x%02x", (int) vt.img_type);
+ return 1;
+ }
+
+ /* Check if firmware fragments are ACKed with a cmd complete event */
+ if (vt.limited_cce != 0x00) {
+ iwmbt_err("Unsupported Intel firmware loading method (%u)",
+ vt.limited_cce);
+ return 1;
+ }
+
+ /* Check if secure boot engine is supported: 1 (ECDSA) or 0 (RSA) */
+ if (vt.sbe_type > 0x01) {
+ iwmbt_err("Unsupported secure boot engine (%u)",
+ vt.sbe_type);
+ return 1;
+ }
+
+ firmware_path = iwmbt_get_fwname_tlv(&vt, firmware_dir, "sfi");
+ if (firmware_path == NULL)
+ return 1;
+ 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,
+ vt.cnvi_bt >> 16 & 0x3f, vt.sbe_type);
+ free(firmware_path);
+ if (r < 0)
+ return 1;
+
+ iwmbt_info("Firmware download complete");
+
+ r = iwmbt_intel_reset(hdl, boot_param);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_intel_reset() failed!");
+ return 1;
+ }
+
+ iwmbt_info("Firmware operational");
+
+ /* Once device is running in operational mode we can ignore failures */
+
+ r = iwmbt_get_version(hdl, &vl);
+ if (r == 0)
+ iwmbt_dump_version(&vl);
+
+ /* Apply the device configuration (DDC) parameters */
+ firmware_path = iwmbt_get_fwname_tlv(&vt, 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);
+ }
+
+ r = iwmbt_set_event_mask(hdl);
+ if (r == 0)
+ iwmbt_info("Intel Event Mask is set");
+
+ return 0;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ libusb_context *ctx = NULL;
+ libusb_device *dev = NULL;
+ libusb_device_handle *hdl = NULL;
+ int r;
+ uint8_t bus_id = 0, dev_id = 0;
+ int devid_set = 0;
+ int n;
+ char *firmware_dir = NULL;
+ int retcode = 1;
+ enum iwmbt_device iwmbt_device;
+
+ /* Parse command line arguments */
+ while ((n = getopt(argc, argv, "Dd:f:hI")) != -1) {
+ switch (n) {
+ case 'd': /* ugen device name */
+ devid_set = 1;
+ if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0)
+ usage();
+ break;
+ case 'D':
+ iwmbt_do_debug = 1;
+ break;
+ case 'f': /* firmware dir */
+ if (firmware_dir)
+ free(firmware_dir);
+ firmware_dir = strdup(optarg);
+ break;
+ case 'I':
+ iwmbt_do_info = 1;
+ break;
+ case 'h':
+ default:
+ usage();
+ break;
+ /* NOT REACHED */
+ }
+ }
+
+ /* Ensure the devid was given! */
+ if (devid_set == 0) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ /* Default the firmware path */
+ if (firmware_dir == NULL)
+ firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH);
+
+ /* libusb setup */
+ r = libusb_init(&ctx);
+ if (r != 0) {
+ iwmbt_err("libusb_init failed: code %d", r);
+ exit(127);
+ }
+
+ 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, &iwmbt_device);
+ if (dev == NULL) {
+ iwmbt_err("device not found");
+ goto shutdown;
+ }
+
+ /* XXX enforce that bInterfaceNumber is 0 */
+
+ /* XXX enforce the device/product id if they're non-zero */
+
+ /* Grab device handle */
+ r = libusb_open(dev, &hdl);
+ if (r != 0) {
+ iwmbt_err("libusb_open() failed: code %d", r);
+ goto shutdown;
+ }
+
+ /* Check if ng_ubt is attached */
+ r = libusb_kernel_driver_active(hdl, 0);
+ if (r < 0) {
+ iwmbt_err("libusb_kernel_driver_active() failed: code %d", r);
+ goto shutdown;
+ }
+ if (r > 0) {
+ iwmbt_info("Firmware has already been downloaded");
+ retcode = 0;
+ goto shutdown;
+ }
+
+ switch(iwmbt_device) {
+ case IWMBT_DEVICE_7260:
+ retcode = handle_7260(hdl, firmware_dir);
+ break;
+ case IWMBT_DEVICE_8260:
+ retcode = handle_8260(hdl, firmware_dir);
+ break;
+ case IWMBT_DEVICE_9260:
+ retcode = handle_9260(hdl, firmware_dir);
+ break;
+ default:
+ iwmbt_err("FIXME: unknown iwmbt type %d", (int)iwmbt_device);
+ retcode = 1;
+ }
+
+ if (retcode == 0) {
+ /* Ask kernel driver to probe and attach device again */
+ r = libusb_reset_device(hdl);
+ if (r != 0)
+ iwmbt_err("libusb_reset_device() failed: %s",
+ libusb_strerror(r));
+ }
+
+shutdown:
+ if (hdl != NULL)
+ libusb_close(hdl);
+
+ if (dev != NULL)
+ libusb_unref_device(dev);
+
+ if (ctx != NULL)
+ libusb_exit(ctx);
+
+ if (retcode == 0)
+ iwmbt_info("Firmware download is successful!");
+ else
+ iwmbt_err("Firmware download failed!");
+
+ return (retcode);
+}