diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c index fc93ce094adc..963d5d5d9008 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c @@ -1,162 +1,176 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Adrian Chadd * Copyright (c) 2019 Vladimir Kondratyev * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #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; int i; 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); } i = 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 */ 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); } diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c index f4272548d560..218fd28b74a2 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c @@ -1,579 +1,603 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 Vladimir Kondratyev * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #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) { 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); } ret = libusb_interrupt_transfer(hdl, IWMBT_INTERRUPT_ENDPOINT_ADDR, event, size, transferred, timeout); if (ret < 0) iwmbt_err("libusb_interrupt_transfer() failed: err=%s", libusb_strerror(ret)); return (ret); } int iwmbt_patch_fwfile(struct libusb_device_handle *hdl, const struct iwmbt_firmware *fw) { int ret, transferred; struct iwmbt_firmware fw_job = *fw; uint16_t cmd_opcode; uint8_t cmd_length; - uint8_t cmd_buf[IWMBT_HCI_MAX_CMD_SIZE]; + struct iwmbt_hci_cmd *cmd_buf; uint8_t evt_code; uint8_t evt_length; uint8_t evt_buf[IWMBT_HCI_MAX_EVENT_SIZE]; - int skip_patch = 0; + int activate_patch = 0; - for (;;) { - skip_patch = 0; - - if (fw_job.len < 4) - break; + 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]; - memcpy(cmd_buf, fw_job.buf, 3); + cmd_buf = (struct iwmbt_hci_cmd *)fw_job.buf; iwmbt_debug("opcode=%04x, len=%02x", cmd_opcode, cmd_length); - /* For some reason the command 0xfc2f hangs up my card. */ - if (cmd_opcode == 0xfc2f) - skip_patch = 1; + /* + * 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) - cmd_length = fw_job.len; - - /* Copy data to HCI command buffer. */ - memcpy(cmd_buf + 3, fw_job.buf, - MIN(cmd_length, IWMBT_HCI_MAX_CMD_SIZE - 3)); + 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. - * - * Some commands are mapped to more than one event sequence, - * in that case we can drop the non-patch commands, as we - * probably don't need them for operation of the card. - * */ - for (;;) { + while (fw_job.len > 0 && fw_job.buf[0] == 0x02) { /* Is this the end of the file? */ - if (fw_job.len < 3) - break; - - if (fw_job.buf[0] != 0x02) - break; + 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; - - if (skip_patch == 0) { - ret = iwmbt_hci_command(hdl, - (struct iwmbt_hci_cmd *)cmd_buf, - evt_buf, - IWMBT_HCI_MAX_EVENT_SIZE, - &transferred, - IWMBT_HCI_CMD_TIMEOUT); - - if (ret < 0) { - iwmbt_debug("Can't send patch: " - "code=%d, size=%d", - ret, - transferred); - return (-1); - } - } } } - return (0); + return (activate_patch); } int iwmbt_load_fwfile(struct libusb_device_handle *hdl, const struct iwmbt_firmware *fw, uint32_t *boot_param) { int ready = 0, sent = 0; int ret, transferred; struct iwmbt_hci_cmd *cmd; struct iwmbt_hci_event *event; uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; #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) if (fw->len < 644) { iwmbt_err("Invalid size of firmware file (%d)", fw->len); return (-1); } iwmbt_debug("file=%s, size=%d", fw->fwname, fw->len); 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"); /* * 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, int mode) { int ret, transferred; static struct iwmbt_hci_cmd cmd = { .opcode = htole16(0xfc11), .length = 2, .data = { 0x00, 0x00 }, }; uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; /* * The mode sets the type of reset we want to perform: * 0x00: simply exit manufacturer mode without a reset. * 0x01: exit manufacturer mode with a reset and patches disabled * 0x02: exit manufacturer mode with a reset and patches enabled */ if (mode > 2) { iwmbt_debug("iwmbt_exit_manufacturer(): unknown mode (%d)", mode); } cmd.data[1] = mode; ret = iwmbt_hci_command(hdl, &cmd, buf, sizeof(buf), &transferred, IWMBT_HCI_CMD_TIMEOUT); if (ret < 0) { iwmbt_debug("Can't exit manufacturer mode: code=%d, size=%d", ret, transferred); return (-1); } return (0); } int iwmbt_get_version(struct libusb_device_handle *hdl, struct iwmbt_version *version) { 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_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]; 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, buf, sizeof(buf), &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/main.c b/usr.sbin/bluetooth/iwmbtfw/main.c index 3476e3fcd613..202894740805 100644 --- a/usr.sbin/bluetooth/iwmbtfw/main.c +++ b/usr.sbin/bluetooth/iwmbtfw/main.c @@ -1,575 +1,580 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Adrian Chadd * Copyright (c) 2019 Vladimir Kondratyev * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #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; struct iwmbt_devid { uint16_t product_id; uint16_t vendor_id; }; static struct iwmbt_devid iwmbt_list_72xx[] = { /* Intel Wireless 7260/7265 and successors */ { .vendor_id = 0x8087, .product_id = 0x07dc }, { .vendor_id = 0x8087, .product_id = 0x0a2a }, { .vendor_id = 0x8087, .product_id = 0x0aa7 }, }; static struct iwmbt_devid iwmbt_list_82xx[] = { /* Intel Wireless 8260/8265 and successors */ { .vendor_id = 0x8087, .product_id = 0x0a2b }, { .vendor_id = 0x8087, .product_id = 0x0aaa }, { .vendor_id = 0x8087, .product_id = 0x0025 }, { .vendor_id = 0x8087, .product_id = 0x0026 }, { .vendor_id = 0x8087, .product_id = 0x0029 }, }; static int iwmbt_is_7260(struct libusb_device_descriptor *d) { int i; /* Search looking for whether it's an 7260/7265 */ for (i = 0; i < (int) nitems(iwmbt_list_72xx); i++) { if ((iwmbt_list_72xx[i].product_id == d->idProduct) && (iwmbt_list_72xx[i].vendor_id == d->idVendor)) { iwmbt_info("found 7260/7265"); return (1); } } /* Not found */ return (0); } static int iwmbt_is_8260(struct libusb_device_descriptor *d) { int i; /* Search looking for whether it's an 8260/8265 */ for (i = 0; i < (int) nitems(iwmbt_list_82xx); i++) { if ((iwmbt_list_82xx[i].product_id == d->idProduct) && (iwmbt_list_82xx[i].vendor_id == d->idVendor)) { iwmbt_info("found 8260/8265"); return (1); } } /* Not found */ return (0); } static libusb_device * iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id, int *iwmbt_use_old_method) { libusb_device **list, *dev = NULL, *found = NULL; struct libusb_device_descriptor d; 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 */ if (iwmbt_is_7260(&d)) { /* * Take a reference so it's not freed later on. */ found = libusb_ref_device(dev); *iwmbt_use_old_method = 1; break; } else if (iwmbt_is_8260(&d)) { /* * Take a reference so it's not freed later on. */ found = libusb_ref_device(dev); *iwmbt_use_old_method = 0; break; } } } 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 int iwmbt_patch_firmware(libusb_device_handle *hdl, const char *firmware_path) { struct iwmbt_firmware fw; int ret; iwmbt_debug("loading %s", firmware_path); /* Read in the firmware */ if (iwmbt_fw_read(&fw, firmware_path) <= 0) { iwmbt_debug("iwmbt_fw_read() failed"); return (-1); } /* Load in the firmware */ ret = iwmbt_patch_fwfile(hdl, &fw); if (ret < 0) iwmbt_debug("Loading firmware file failed"); /* free it */ iwmbt_fw_free(&fw); return (ret); } static int iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path, uint32_t *boot_param) { struct iwmbt_firmware fw; int ret; iwmbt_debug("loading %s", firmware_path); /* Read in the firmware */ if (iwmbt_fw_read(&fw, firmware_path) <= 0) { iwmbt_debug("iwmbt_fw_read() failed"); return (-1); } /* Load in the firmware */ ret = iwmbt_load_fwfile(hdl, &fw, boot_param); if (ret < 0) iwmbt_debug("Loading firmware file failed"); /* free it */ 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 (-D) -d ugenX.Y (-f firmware path) (-I)\n"); fprintf(stderr, " -D: enable debugging\n"); fprintf(stderr, " -d: device to operate upon\n"); fprintf(stderr, " -f: firmware path, if not default\n"); fprintf(stderr, " -I: enable informational output\n"); exit(127); } int main(int argc, char *argv[]) { libusb_context *ctx = NULL; libusb_device *dev = NULL; libusb_device_handle *hdl = NULL; static struct iwmbt_version ver; static struct iwmbt_boot_params params; uint32_t boot_param; int r; uint8_t bus_id = 0, dev_id = 0; int devid_set = 0; int n; char *firmware_dir = NULL; char *firmware_path = NULL; int retcode = 1; int iwmbt_use_old_method = 0; /* Parse command line arguments */ while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) { 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 */ } /* 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_use_old_method); 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; } /* Get Intel version */ r = iwmbt_get_version(hdl, &ver); if (r < 0) { iwmbt_debug("iwmbt_get_version() failed code %d", r); goto shutdown; } iwmbt_dump_version(&ver); iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant); if (iwmbt_use_old_method) { /* fw_patch_num = >0 operational mode */ if (ver.fw_patch_num > 0x00) { iwmbt_info("Firmware has already been downloaded"); retcode = 0; goto reset; } /* Default the firmware path */ if (firmware_dir == NULL) firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "bseq"); if (firmware_path == NULL) goto shutdown; iwmbt_debug("firmware_path = %s", firmware_path); /* Enter manufacturer mode */ r = iwmbt_enter_manufacturer(hdl); if (r < 0) { iwmbt_debug("iwmbt_enter_manufacturer() failed code %d", r); goto shutdown; } /* Download firmware and parse it for magic Intel Reset parameter */ r = iwmbt_patch_firmware(hdl, firmware_path); free(firmware_path); - if (r < 0) + if (r < 0) { + (void)iwmbt_exit_manufacturer(hdl, 0x01); goto shutdown; + } iwmbt_info("Firmware download complete"); /* Exit manufacturer mode */ - r = iwmbt_exit_manufacturer(hdl, 0x02); + r = iwmbt_exit_manufacturer(hdl, r == 0 ? 0x00 : 0x02); if (r < 0) { iwmbt_debug("iwmbt_exit_manufacturer() failed code %d", r); goto shutdown; } /* Once device is running in operational mode we can ignore failures */ retcode = 0; /* Execute Read Intel Version one more time */ r = iwmbt_get_version(hdl, &ver); if (r == 0) iwmbt_dump_version(&ver); /* Set Intel Event mask */ + if (iwmbt_enter_manufacturer(hdl) < 0) + goto reset; r = iwmbt_set_event_mask(hdl); if (r == 0) iwmbt_info("Intel Event Mask is set"); + (void)iwmbt_exit_manufacturer(hdl, 0x00); } else { /* fw_variant = 0x06 bootloader mode / 0x23 operational mode */ if (ver.fw_variant == 0x23) { iwmbt_info("Firmware has already been downloaded"); retcode = 0; goto reset; } if (ver.fw_variant != 0x06){ iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant); goto shutdown; } /* Read Intel Secure Boot Params */ r = iwmbt_get_boot_params(hdl, ¶ms); if (r < 0) { iwmbt_debug("iwmbt_get_boot_params() failed!"); goto shutdown; } 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); goto shutdown; } /* Default the firmware path */ if (firmware_dir == NULL) firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "sfi"); if (firmware_path == NULL) goto shutdown; iwmbt_debug("firmware_path = %s", firmware_path); /* Download firmware and parse it for magic Intel Reset parameter */ r = iwmbt_init_firmware(hdl, firmware_path, &boot_param); free(firmware_path); if (r < 0) goto shutdown; iwmbt_info("Firmware download complete"); r = iwmbt_intel_reset(hdl, boot_param); if (r < 0) { iwmbt_debug("iwmbt_intel_reset() failed!"); goto shutdown; } iwmbt_info("Firmware operational"); /* Once device is running in operational mode we can ignore failures */ retcode = 0; /* Execute Read Intel Version one more time */ r = iwmbt_get_version(hdl, &ver); if (r == 0) iwmbt_dump_version(&ver); /* Apply the device configuration (DDC) parameters */ firmware_path = iwmbt_get_fwname(&ver, ¶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); } /* Set Intel Event mask */ r = iwmbt_set_event_mask(hdl); if (r == 0) iwmbt_info("Intel Event Mask is set"); } reset: /* 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: /* 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 succesful!"); else iwmbt_err("Firmware download failed!"); return (retcode); }