diff options
Diffstat (limited to 'usr.sbin/bluetooth/iwmbtfw')
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/Makefile | 11 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/Makefile.depend | 16 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h | 45 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c | 194 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h | 156 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c | 777 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h | 119 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 | 101 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf | 12 | ||||
| -rw-r--r-- | usr.sbin/bluetooth/iwmbtfw/main.c | 770 |
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, ¶ms); + if (r < 0) { + iwmbt_debug("iwmbt_get_boot_params() failed!"); + return 1; + } + iwmbt_dump_boot_params(¶ms); + + /* 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, ¶ms, 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, ¶ms, 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); +} |
