Path: blob/main/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
108545 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2019 Vladimir Kondratyev <[email protected]>4* Copyright (c) 2023 Future Crew LLC.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND16* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE17* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE18* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE19* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL20* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS21* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)22* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT23* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF25* SUCH DAMAGE.26*/2728#include <sys/param.h>29#include <sys/endian.h>30#include <sys/stat.h>3132#include <assert.h>33#include <err.h>34#include <errno.h>35#include <stddef.h>36#include <stdio.h>37#include <stdlib.h>38#include <string.h>39#include <time.h>40#include <unistd.h>4142#include <libusb.h>4344#include <netgraph/bluetooth/include/ng_hci.h>4546#include "iwmbt_fw.h"47#include "iwmbt_hw.h"48#include "iwmbt_dbg.h"4950#define XMIN(x, y) ((x) < (y) ? (x) : (y))5152static int53iwmbt_send_fragment(struct libusb_device_handle *hdl,54uint8_t fragment_type, const void *data, uint8_t len, int timeout)55{56int ret, transferred;57uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE];58struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *) buf;5960memset(buf, 0, sizeof(buf));61cmd->opcode = htole16(0xfc09),62cmd->length = len + 1,63cmd->data[0] = fragment_type;64memcpy(cmd->data + 1, data, len);6566ret = libusb_bulk_transfer(hdl,67IWMBT_BULK_OUT_ENDPOINT_ADDR,68(uint8_t *)cmd,69IWMBT_HCI_CMD_SIZE(cmd),70&transferred,71timeout);7273if (ret < 0 || transferred != (int)IWMBT_HCI_CMD_SIZE(cmd)) {74iwmbt_err("libusb_bulk_transfer() failed: err=%s, size=%zu",75libusb_strerror(ret),76IWMBT_HCI_CMD_SIZE(cmd));77return (-1);78}7980ret = libusb_bulk_transfer(hdl,81IWMBT_BULK_IN_ENDPOINT_ADDR,82buf,83sizeof(buf),84&transferred,85timeout);8687if (ret < 0) {88iwmbt_err("libusb_bulk_transfer() failed: err=%s",89libusb_strerror(ret));90return (-1);91}9293return (0);94}9596static int97iwmbt_hci_command(struct libusb_device_handle *hdl, struct iwmbt_hci_cmd *cmd,98void *event, int size, int *transferred, int timeout)99{100struct timespec to, now, remains;101int ret;102103ret = libusb_control_transfer(hdl,104LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,1050,1060,1070,108(uint8_t *)cmd,109IWMBT_HCI_CMD_SIZE(cmd),110timeout);111112if (ret < 0) {113iwmbt_err("libusb_control_transfer() failed: err=%s",114libusb_strerror(ret));115return (ret);116}117118clock_gettime(CLOCK_MONOTONIC, &now);119to = IWMBT_MSEC2TS(timeout);120timespecadd(&to, &now, &to);121122do {123timespecsub(&to, &now, &remains);124ret = libusb_interrupt_transfer(hdl,125IWMBT_INTERRUPT_ENDPOINT_ADDR,126event,127size,128transferred,129IWMBT_TS2MSEC(remains) + 1);130131if (ret < 0) {132iwmbt_err("libusb_interrupt_transfer() failed: err=%s",133libusb_strerror(ret));134return (ret);135}136137switch (((struct iwmbt_hci_event *)event)->header.event) {138case NG_HCI_EVENT_COMMAND_COMPL:139if (*transferred <140(int)offsetof(struct iwmbt_hci_event_cmd_compl, data))141break;142if (cmd->opcode !=143((struct iwmbt_hci_event_cmd_compl *)event)->opcode)144break;145/* FALLTHROUGH */146case 0xFF:147return (0);148default:149break;150}151iwmbt_debug("Stray HCI event: %x",152((struct iwmbt_hci_event *)event)->header.event);153} while (timespeccmp(&to, &now, >));154155iwmbt_err("libusb_interrupt_transfer() failed: err=%s",156libusb_strerror(LIBUSB_ERROR_TIMEOUT));157158return (LIBUSB_ERROR_TIMEOUT);159}160161int162iwmbt_patch_fwfile(struct libusb_device_handle *hdl,163const struct iwmbt_firmware *fw)164{165int ret, transferred;166struct iwmbt_firmware fw_job = *fw;167uint16_t cmd_opcode;168uint8_t cmd_length;169struct iwmbt_hci_cmd *cmd_buf;170uint8_t evt_code;171uint8_t evt_length;172uint8_t evt_buf[IWMBT_HCI_MAX_EVENT_SIZE];173int activate_patch = 0;174175while (fw_job.len > 0) {176if (fw_job.len < 4) {177iwmbt_err("Invalid firmware, unexpected EOF in HCI "178"command header. Remains=%d", fw_job.len);179return (-1);180}181182if (fw_job.buf[0] != 0x01) {183iwmbt_err("Invalid firmware, expected HCI command (%d)",184fw_job.buf[0]);185return (-1);186}187188/* Advance by one. */189fw_job.buf++;190fw_job.len--;191192/* Load in the HCI command to perform. */193cmd_opcode = le16dec(fw_job.buf);194cmd_length = fw_job.buf[2];195cmd_buf = (struct iwmbt_hci_cmd *)fw_job.buf;196197iwmbt_debug("opcode=%04x, len=%02x", cmd_opcode, cmd_length);198199/*200* If there is a command that loads a patch in the201* firmware file, then activate the patch upon success,202* otherwise just disable the manufacturer mode.203*/204if (cmd_opcode == 0xfc8e)205activate_patch = 1;206207/* Advance by three. */208fw_job.buf += 3;209fw_job.len -= 3;210211if (fw_job.len < cmd_length) {212iwmbt_err("Invalid firmware, unexpected EOF in HCI "213"command data. len=%d, remains=%d",214cmd_length, fw_job.len);215return (-1);216}217218/* Advance by data length. */219fw_job.buf += cmd_length;220fw_job.len -= cmd_length;221222ret = libusb_control_transfer(hdl,223LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,2240,2250,2260,227(uint8_t *)cmd_buf,228IWMBT_HCI_CMD_SIZE(cmd_buf),229IWMBT_HCI_CMD_TIMEOUT);230231if (ret < 0) {232iwmbt_err("libusb_control_transfer() failed: err=%s",233libusb_strerror(ret));234return (-1);235}236237/*238* Every command has its associated event: data must match239* what is recorded in the firmware file. Perform that check240* now.241*/242243while (fw_job.len > 0 && fw_job.buf[0] == 0x02) {244/* Is this the end of the file? */245if (fw_job.len < 3) {246iwmbt_err("Invalid firmware, unexpected EOF in"247"event header. remains=%d", fw_job.len);248return (-1);249}250251/* Advance by one. */252fw_job.buf++;253fw_job.len--;254255/* Load in the HCI event. */256evt_code = fw_job.buf[0];257evt_length = fw_job.buf[1];258259/* Advance by two. */260fw_job.buf += 2;261fw_job.len -= 2;262263/* Prepare HCI event buffer. */264memset(evt_buf, 0, IWMBT_HCI_MAX_EVENT_SIZE);265266iwmbt_debug("event=%04x, len=%02x",267evt_code, evt_length);268269if (fw_job.len < evt_length) {270iwmbt_err("Invalid firmware, unexpected EOF in"271" event data. len=%d, remains=%d",272evt_length, fw_job.len);273return (-1);274}275276ret = libusb_interrupt_transfer(hdl,277IWMBT_INTERRUPT_ENDPOINT_ADDR,278evt_buf,279IWMBT_HCI_MAX_EVENT_SIZE,280&transferred,281IWMBT_HCI_CMD_TIMEOUT);282283if (ret < 0) {284iwmbt_err("libusb_interrupt_transfer() failed:"285" err=%s", libusb_strerror(ret));286return (-1);287}288289if ((int)evt_length + 2 != transferred ||290memcmp(evt_buf + 2, fw_job.buf, evt_length) != 0) {291iwmbt_err("event does not match firmware");292return (-1);293}294295/* Advance by data length. */296fw_job.buf += evt_length;297fw_job.len -= evt_length;298}299}300301return (activate_patch);302}303304#define IWMBT_SEND_FRAGMENT(fragment_type, size, msg) do { \305iwmbt_debug("transferring %d bytes, offset %d", size, sent); \306\307ret = iwmbt_send_fragment(hdl, \308fragment_type, \309fw->buf + sent, \310XMIN(size, fw->len - sent), \311IWMBT_HCI_CMD_TIMEOUT); \312\313if (ret < 0) { \314iwmbt_debug("Failed to send "msg": code=%d", ret); \315return (-1); \316} \317sent += size; \318} while (0)319320int321iwmbt_load_rsa_header(struct libusb_device_handle *hdl,322const struct iwmbt_firmware *fw)323{324int ret, sent = 0;325326IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment");327IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 1");328IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 2");329330/* skip 4 bytes */331sent += 4;332333IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 1");334IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 2");335336return (0);337}338339int340iwmbt_load_ecdsa_header(struct libusb_device_handle *hdl,341const struct iwmbt_firmware *fw)342{343int ret, sent = ECDSA_OFFSET;344345IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment");346IWMBT_SEND_FRAGMENT(0x03, 0x60, "public key");347IWMBT_SEND_FRAGMENT(0x02, 0x60, "signature");348349return (0);350}351352int353iwmbt_load_fwfile(struct libusb_device_handle *hdl,354const struct iwmbt_firmware *fw, uint32_t *boot_param, int offset)355{356int ready = 0, sent = offset;357int ret, transferred;358struct iwmbt_hci_cmd *cmd;359struct iwmbt_hci_event *event;360uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];361362/*363* Send firmware chunks. Chunk len must be 4 byte aligned.364* multiple commands can be combined365*/366while (fw->len - sent - ready >= (int) sizeof(struct iwmbt_hci_cmd)) {367cmd = (struct iwmbt_hci_cmd *)(fw->buf + sent + ready);368/* Parse firmware for Intel Reset HCI command parameter */369if (cmd->opcode == htole16(0xfc0e)) {370*boot_param = le32dec(cmd->data);371iwmbt_debug("boot_param=0x%08x", *boot_param);372}373ready += IWMBT_HCI_CMD_SIZE(cmd);374while (ready >= 0xFC) {375IWMBT_SEND_FRAGMENT(0x01, 0xFC, "firmware chunk");376ready -= 0xFC;377}378if (ready > 0 && ready % 4 == 0) {379IWMBT_SEND_FRAGMENT(0x01, ready, "firmware chunk");380ready = 0;381}382}383384/* Wait for firmware download completion event */385ret = libusb_interrupt_transfer(hdl,386IWMBT_INTERRUPT_ENDPOINT_ADDR,387buf,388sizeof(buf),389&transferred,390IWMBT_LOADCMPL_TIMEOUT);391392if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) {393iwmbt_err("libusb_interrupt_transfer() failed: "394"err=%s, size=%d",395libusb_strerror(ret),396transferred);397return (-1);398}399400/* Expect Vendor Specific Event 0x06 */401event = (struct iwmbt_hci_event *)buf;402if (event->header.event != 0xFF || event->data[0] != 0x06) {403iwmbt_err("firmware download completion event missed");404return (-1);405}406407return (0);408}409410int411iwmbt_enter_manufacturer(struct libusb_device_handle *hdl)412{413int ret, transferred;414static struct iwmbt_hci_cmd cmd = {415.opcode = htole16(0xfc11),416.length = 2,417.data = { 0x01, 0x00 },418};419uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];420421ret = iwmbt_hci_command(hdl,422&cmd,423buf,424sizeof(buf),425&transferred,426IWMBT_HCI_CMD_TIMEOUT);427428if (ret < 0) {429iwmbt_debug("Can't enter manufacturer mode: code=%d, size=%d",430ret,431transferred);432return (-1);433}434435return (0);436}437438int439iwmbt_exit_manufacturer(struct libusb_device_handle *hdl,440enum iwmbt_mm_exit mode)441{442int ret, transferred;443static struct iwmbt_hci_cmd cmd = {444.opcode = htole16(0xfc11),445.length = 2,446.data = { 0x00, 0x00 },447};448uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];449450cmd.data[1] = (uint8_t)mode;451452ret = iwmbt_hci_command(hdl,453&cmd,454buf,455sizeof(buf),456&transferred,457IWMBT_HCI_CMD_TIMEOUT);458459if (ret < 0) {460iwmbt_debug("Can't exit manufacturer mode: code=%d, size=%d",461ret,462transferred);463return (-1);464}465466return (0);467}468469int470iwmbt_get_version(struct libusb_device_handle *hdl,471struct iwmbt_version *version)472{473int ret, transferred;474struct iwmbt_hci_event_cmd_compl*event;475struct iwmbt_hci_cmd cmd = {476.opcode = htole16(0xfc05),477.length = 0,478};479uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_version)];480481memset(buf, 0, sizeof(buf));482483ret = iwmbt_hci_command(hdl,484&cmd,485buf,486sizeof(buf),487&transferred,488IWMBT_HCI_CMD_TIMEOUT);489490if (ret < 0 || transferred != sizeof(buf)) {491iwmbt_debug("Can't get version: : code=%d, size=%d",492ret,493transferred);494return (-1);495}496497event = (struct iwmbt_hci_event_cmd_compl *)buf;498memcpy(version, event->data, sizeof(struct iwmbt_version));499500return (0);501}502503int504iwmbt_get_version_tlv(struct libusb_device_handle *hdl,505struct iwmbt_version_tlv *version)506{507int ret, transferred;508struct iwmbt_hci_event_cmd_compl *event;509static struct iwmbt_hci_cmd cmd = {510.opcode = htole16(0xfc05),511.length = 1,512.data = { 0xff },513};514uint8_t status, datalen, type, len;515uint8_t *data;516uint8_t buf[255];517518memset(buf, 0, sizeof(buf));519520ret = iwmbt_hci_command(hdl,521&cmd,522buf,523sizeof(buf),524&transferred,525IWMBT_HCI_CMD_TIMEOUT);526527if (ret < 0 || transferred < (int)IWMBT_HCI_EVT_COMPL_SIZE(uint16_t)) {528iwmbt_debug("Can't get version: code=%d, size=%d",529ret,530transferred);531return (-1);532}533534event = (struct iwmbt_hci_event_cmd_compl *)buf;535memcpy(version, event->data, sizeof(struct iwmbt_version));536537datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE;538data = event->data;539status = *data++;540if (status != 0)541return (-1);542datalen--;543544while (datalen >= 2) {545type = *data++;546len = *data++;547datalen -= 2;548549if (datalen < len)550return (-1);551552switch (type) {553case IWMBT_TLV_CNVI_TOP:554assert(len == 4);555version->cnvi_top = le32dec(data);556break;557case IWMBT_TLV_CNVR_TOP:558assert(len == 4);559version->cnvr_top = le32dec(data);560break;561case IWMBT_TLV_CNVI_BT:562assert(len == 4);563version->cnvi_bt = le32dec(data);564break;565case IWMBT_TLV_CNVR_BT:566assert(len == 4);567version->cnvr_bt = le32dec(data);568break;569case IWMBT_TLV_DEV_REV_ID:570assert(len == 2);571version->dev_rev_id = le16dec(data);572break;573case IWMBT_TLV_IMAGE_TYPE:574assert(len == 1);575version->img_type = *data;576break;577case IWMBT_TLV_TIME_STAMP:578assert(len == 2);579version->min_fw_build_cw = data[0];580version->min_fw_build_yy = data[1];581version->timestamp = le16dec(data);582break;583case IWMBT_TLV_BUILD_TYPE:584assert(len == 1);585version->build_type = *data;586break;587case IWMBT_TLV_BUILD_NUM:588assert(len == 4);589version->min_fw_build_nn = *data;590version->build_num = le32dec(data);591break;592case IWMBT_TLV_SECURE_BOOT:593assert(len == 1);594version->secure_boot = *data;595break;596case IWMBT_TLV_OTP_LOCK:597assert(len == 1);598version->otp_lock = *data;599break;600case IWMBT_TLV_API_LOCK:601assert(len == 1);602version->api_lock = *data;603break;604case IWMBT_TLV_DEBUG_LOCK:605assert(len == 1);606version->debug_lock = *data;607break;608case IWMBT_TLV_MIN_FW:609assert(len == 3);610version->min_fw_build_nn = data[0];611version->min_fw_build_cw = data[1];612version->min_fw_build_yy = data[2];613break;614case IWMBT_TLV_LIMITED_CCE:615assert(len == 1);616version->limited_cce = *data;617break;618case IWMBT_TLV_SBE_TYPE:619assert(len == 1);620version->sbe_type = *data;621break;622case IWMBT_TLV_OTP_BDADDR:623memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t));624break;625default:626/* Ignore other types */627break;628}629630datalen -= len;631data += len;632}633634return (0);635}636637int638iwmbt_get_boot_params(struct libusb_device_handle *hdl,639struct iwmbt_boot_params *params)640{641int ret, transferred = 0;642struct iwmbt_hci_event_cmd_compl *event;643struct iwmbt_hci_cmd cmd = {644.opcode = htole16(0xfc0d),645.length = 0,646};647uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_boot_params)];648649memset(buf, 0, sizeof(buf));650651ret = iwmbt_hci_command(hdl,652&cmd,653buf,654sizeof(buf),655&transferred,656IWMBT_HCI_CMD_TIMEOUT);657658if (ret < 0 || transferred != sizeof(buf)) {659iwmbt_debug("Can't get boot params: code=%d, size=%d",660ret,661transferred);662return (-1);663}664665event = (struct iwmbt_hci_event_cmd_compl *)buf;666memcpy(params, event->data, sizeof(struct iwmbt_boot_params));667668return (0);669}670671int672iwmbt_intel_reset(struct libusb_device_handle *hdl, uint32_t boot_param)673{674int ret, transferred = 0;675struct iwmbt_hci_event *event;676static struct iwmbt_hci_cmd cmd = {677.opcode = htole16(0xfc01),678.length = 8,679.data = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 },680};681uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];682683le32enc(cmd.data + 4, boot_param);684memset(buf, 0, sizeof(buf));685686ret = iwmbt_hci_command(hdl,687&cmd,688buf,689sizeof(buf),690&transferred,691IWMBT_HCI_CMD_TIMEOUT);692693if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) {694iwmbt_debug("Intel Reset command failed: code=%d, size=%d",695ret,696transferred);697return (ret);698}699700/* expect Vendor Specific Event 0x02 */701event = (struct iwmbt_hci_event *)buf;702if (event->header.event != 0xFF || event->data[0] != 0x02) {703iwmbt_err("Intel Reset completion event missed");704return (-1);705}706707return (0);708}709710int711iwmbt_load_ddc(struct libusb_device_handle *hdl,712const struct iwmbt_firmware *ddc)713{714int size, sent = 0;715int ret, transferred;716uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE];717uint8_t evt[IWMBT_HCI_MAX_CMD_SIZE];718struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *)buf;719720size = ddc->len;721722iwmbt_debug("file=%s, size=%d", ddc->fwname, size);723724while (size > 0) {725726memset(buf, 0, sizeof(buf));727cmd->opcode = htole16(0xfc8b);728cmd->length = ddc->buf[sent] + 1;729memcpy(cmd->data, ddc->buf + sent, XMIN(ddc->buf[sent], size));730731iwmbt_debug("transferring %d bytes, offset %d",732cmd->length,733sent);734735size -= cmd->length;736sent += cmd->length;737738ret = iwmbt_hci_command(hdl,739cmd,740evt,741sizeof(evt),742&transferred,743IWMBT_HCI_CMD_TIMEOUT);744745if (ret < 0) {746iwmbt_debug("Intel Write DDC failed: code=%d", ret);747return (-1);748}749}750751return (0);752}753754int755iwmbt_set_event_mask(struct libusb_device_handle *hdl)756{757int ret, transferred = 0;758static struct iwmbt_hci_cmd cmd = {759.opcode = htole16(0xfc52),760.length = 8,761.data = { 0x87, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },762};763uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];764765ret = iwmbt_hci_command(hdl,766&cmd,767buf,768sizeof(buf),769&transferred,770IWMBT_HCI_CMD_TIMEOUT);771772if (ret < 0)773iwmbt_debug("Intel Set Event Mask failed: code=%d", ret);774775return (ret);776}777778779