Path: blob/main/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
106212 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2013 Adrian Chadd <[email protected]>4* Copyright (c) 2019 Vladimir Kondratyev <[email protected]>5* Copyright (c) 2023 Future Crew LLC.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions9* are met:10* 1. Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829#include <sys/param.h>30#include <sys/endian.h>31#include <sys/stat.h>3233#include <err.h>34#include <errno.h>35#include <fcntl.h>36#include <stdio.h>37#include <stdlib.h>38#include <string.h>39#include <unistd.h>4041#include "rtlbt_fw.h"42#include "rtlbt_dbg.h"4344static const struct rtlbt_id_table rtlbt_ic_id_table[] = {45{ /* 8723A */46.lmp_subversion = RTLBT_ROM_LMP_8723A,47.hci_revision = 0xb,48.hci_version = 0x6,49.flags = RTLBT_IC_FLAG_SIMPLE,50.fw_name = "rtl8723a",51}, { /* 8723B */52.lmp_subversion = RTLBT_ROM_LMP_8723B,53.hci_revision = 0xb,54.hci_version = 0x6,55.fw_name = "rtl8723b",56}, { /* 8723D */57.lmp_subversion = RTLBT_ROM_LMP_8723B,58.hci_revision = 0xd,59.hci_version = 0x8,60.flags = RTLBT_IC_FLAG_CONFIG,61.fw_name = "rtl8723d",62}, { /* 8821A */63.lmp_subversion = RTLBT_ROM_LMP_8821A,64.hci_revision = 0xa,65.hci_version = 0x6,66.fw_name = "rtl8821a",67}, { /* 8821C */68.lmp_subversion = RTLBT_ROM_LMP_8821A,69.hci_revision = 0xc,70.hci_version = 0x8,71.flags = RTLBT_IC_FLAG_MSFT,72.fw_name = "rtl8821c",73}, { /* 8761A */74.lmp_subversion = RTLBT_ROM_LMP_8761A,75.hci_revision = 0xa,76.hci_version = 0x6,77.fw_name = "rtl8761a",78}, { /* 8761BU */79.lmp_subversion = RTLBT_ROM_LMP_8761A,80.hci_revision = 0xb,81.hci_version = 0xa,82.fw_name = "rtl8761bu",83}, { /* 8822C with USB interface */84.lmp_subversion = RTLBT_ROM_LMP_8822B,85.hci_revision = 0xc,86.hci_version = 0xa,87.flags = RTLBT_IC_FLAG_MSFT,88.fw_name = "rtl8822cu",89}, { /* 8822B */90.lmp_subversion = RTLBT_ROM_LMP_8822B,91.hci_revision = 0xb,92.hci_version = 0x7,93.flags = RTLBT_IC_FLAG_CONFIG | RTLBT_IC_FLAG_MSFT,94.fw_name = "rtl8822b",95}, { /* 8852A */96.lmp_subversion = RTLBT_ROM_LMP_8852A,97.hci_revision = 0xa,98.hci_version = 0xb,99.flags = RTLBT_IC_FLAG_MSFT,100.fw_name = "rtl8852au",101}, { /* 8852B */102.lmp_subversion = RTLBT_ROM_LMP_8852A,103.hci_revision = 0xb,104.hci_version = 0xb,105.flags = RTLBT_IC_FLAG_MSFT,106.fw_name = "rtl8852bu",107}, { /* 8852C */108.lmp_subversion = RTLBT_ROM_LMP_8852A,109.hci_revision = 0xc,110.hci_version = 0xc,111.flags = RTLBT_IC_FLAG_MSFT,112.fw_name = "rtl8852cu",113.fw_suffix = "_fw_v2.bin",114}, { /* 8851B */115.lmp_subversion = RTLBT_ROM_LMP_8851B,116.hci_revision = 0xb,117.hci_version = 0xc,118.flags = RTLBT_IC_FLAG_MSFT,119.fw_name = "rtl8851bu",120}, { /* 8922A */121.lmp_subversion = RTLBT_ROM_LMP_8922A,122.hci_revision = 0xa,123.hci_version = 0xc,124.flags = RTLBT_IC_FLAG_MSFT,125.fw_name = "rtl8922au",126}, { /* 8852BT/8852BE-VT */127.lmp_subversion = RTLBT_ROM_LMP_8852A,128.hci_revision = 0x87,129.hci_version = 0xc,130.flags = RTLBT_IC_FLAG_MSFT,131.fw_name = "rtl8852btu",132},133};134135static const uint16_t project_ids[] = {136[ 0 ] = RTLBT_ROM_LMP_8723A,137[ 1 ] = RTLBT_ROM_LMP_8723B,138[ 2 ] = RTLBT_ROM_LMP_8821A,139[ 3 ] = RTLBT_ROM_LMP_8761A,140[ 7 ] = RTLBT_ROM_LMP_8703B,141[ 8 ] = RTLBT_ROM_LMP_8822B,142[ 9 ] = RTLBT_ROM_LMP_8723B, /* 8723DU */143[ 10 ] = RTLBT_ROM_LMP_8821A, /* 8821CU */144[ 13 ] = RTLBT_ROM_LMP_8822B, /* 8822CU */145[ 14 ] = RTLBT_ROM_LMP_8761A, /* 8761BU */146[ 18 ] = RTLBT_ROM_LMP_8852A, /* 8852AU */147[ 19 ] = RTLBT_ROM_LMP_8723B, /* 8723FU */148[ 20 ] = RTLBT_ROM_LMP_8852A, /* 8852BU */149[ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */150[ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */151[ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */152[ 44 ] = RTLBT_ROM_LMP_8922A, /* 8922A */153[ 47 ] = RTLBT_ROM_LMP_8852A, /* 8852BT */154};155156/* Signatures */157static const uint8_t fw_header_sig_v1[8] =158{0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */159static const uint8_t fw_header_sig_v2[8] =160{0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */161static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77};162163int164rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)165{166int fd;167struct stat sb;168unsigned char *buf;169ssize_t r;170171fd = open(fwname, O_RDONLY);172if (fd < 0) {173warn("%s: open: %s", __func__, fwname);174return (0);175}176177if (fstat(fd, &sb) != 0) {178warn("%s: stat: %s", __func__, fwname);179close(fd);180return (0);181}182183buf = calloc(1, sb.st_size);184if (buf == NULL) {185warn("%s: calloc", __func__);186close(fd);187return (0);188}189190/* XXX handle partial reads */191r = read(fd, buf, sb.st_size);192if (r < 0) {193warn("%s: read", __func__);194free(buf);195close(fd);196return (0);197}198199if (r != sb.st_size) {200rtlbt_err("read len %d != file size %d",201(int) r,202(int) sb.st_size);203free(buf);204close(fd);205return (0);206}207208/* We have everything, so! */209210memset(fw, 0, sizeof(*fw));211212fw->fwname = strdup(fwname);213fw->len = sb.st_size;214fw->buf = buf;215216close(fd);217return (1);218}219220void221rtlbt_fw_free(struct rtlbt_firmware *fw)222{223if (fw->fwname)224free(fw->fwname);225if (fw->buf)226free(fw->buf);227memset(fw, 0, sizeof(*fw));228}229230char *231rtlbt_get_fwname(const char *fw_name, const char *prefix, const char *suffix)232{233char *fwname;234235asprintf(&fwname, "%s/%s%s", prefix, fw_name, suffix);236237return (fwname);238}239240const struct rtlbt_id_table *241rtlbt_get_ic(uint16_t lmp_subversion, uint16_t hci_revision,242uint8_t hci_version)243{244unsigned int i;245246for (i = 0; i < nitems(rtlbt_ic_id_table); i++) {247if (rtlbt_ic_id_table[i].lmp_subversion == lmp_subversion &&248rtlbt_ic_id_table[i].hci_revision == hci_revision &&249rtlbt_ic_id_table[i].hci_version == hci_version)250return (rtlbt_ic_id_table + i);251}252253return (NULL);254}255256enum rtlbt_fw_type257rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion)258{259enum rtlbt_fw_type fw_type;260size_t fw_header_len;261uint8_t *ptr;262uint8_t opcode, oplen, project_id;263264if (fw->len < 8) {265rtlbt_err("firmware file too small");266return (RTLBT_FW_TYPE_UNKNOWN);267}268269if (memcmp(fw->buf, fw_header_sig_v1, sizeof(fw_header_sig_v1)) == 0) {270fw_type = RTLBT_FW_TYPE_V1;271fw_header_len = sizeof(struct rtlbt_fw_header_v1);272} else273if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) {274fw_type = RTLBT_FW_TYPE_V2;275fw_header_len = sizeof(struct rtlbt_fw_header_v2);276} else277return (RTLBT_FW_TYPE_UNKNOWN);278279if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) {280rtlbt_err("firmware file too small");281return (RTLBT_FW_TYPE_UNKNOWN);282}283284ptr = fw->buf + fw->len - sizeof(fw_ext_sig);285if (memcmp(ptr, fw_ext_sig, sizeof(fw_ext_sig)) != 0) {286rtlbt_err("invalid extension section signature");287return (RTLBT_FW_TYPE_UNKNOWN);288}289290do {291opcode = *--ptr;292oplen = *--ptr;293ptr -= oplen;294295rtlbt_debug("code=%x len=%x", opcode, oplen);296297if (opcode == 0x00) {298if (oplen != 1) {299rtlbt_err("invalid instruction length");300return (RTLBT_FW_TYPE_UNKNOWN);301}302project_id = *ptr;303rtlbt_debug("project_id=%x", project_id);304if (project_id >= nitems(project_ids) ||305project_ids[project_id] == 0) {306rtlbt_err("unknown project id %x", project_id);307return (RTLBT_FW_TYPE_UNKNOWN);308}309*fw_lmp_subversion = project_ids[project_id];310return (fw_type);311}312} while (opcode != 0xff && ptr > fw->buf + fw_header_len);313314rtlbt_err("can not find project id instruction");315return (RTLBT_FW_TYPE_UNKNOWN);316};317318int319rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version)320{321struct rtlbt_fw_header_v1 *fw_header;322uint8_t *patch_buf;323unsigned int i;324const uint8_t *chip_id_base;325uint32_t patch_offset;326uint16_t patch_length, num_patches;327328fw_header = (struct rtlbt_fw_header_v1 *)fw->buf;329num_patches = le16toh(fw_header->num_patches);330rtlbt_debug("fw_version=%x, num_patches=%d",331le32toh(fw_header->fw_version), num_patches);332333/* Find a right patch for the chip. */334if (fw->len < sizeof(struct rtlbt_fw_header_v1) +335sizeof(fw_ext_sig) + 4 + 8 * num_patches) {336errno = EINVAL;337return (-1);338}339340chip_id_base = fw->buf + sizeof(struct rtlbt_fw_header_v1);341for (i = 0; i < num_patches; i++) {342if (le16dec(chip_id_base + i * 2) != rom_version + 1)343continue;344patch_length = le16dec(chip_id_base + 2 * (num_patches + i));345patch_offset = le32dec(chip_id_base + 4 * (num_patches + i));346break;347}348349if (i >= num_patches) {350rtlbt_err("can not find patch for chip id %d", rom_version);351errno = EINVAL;352return (-1);353}354355rtlbt_debug(356"index=%d length=%x offset=%x", i, patch_length, patch_offset);357if (fw->len < patch_offset + patch_length) {358errno = EINVAL;359return (-1);360}361362patch_buf = malloc(patch_length);363if (patch_buf == NULL) {364errno = ENOMEM;365return (-1);366}367368memcpy(patch_buf, fw->buf + patch_offset, patch_length - 4);369memcpy(patch_buf + patch_length - 4, &fw_header->fw_version, 4);370371free(fw->buf);372fw->buf = patch_buf;373fw->len = patch_length;374375return (0);376}377378static void *379rtlbt_iov_fetch(struct rtlbt_iov *iov, uint32_t len)380{381void *data = NULL;382383if (iov->len >= len) {384data = iov->data;385iov->data += len;386iov->len -= len;387}388389return (data);390}391392static int393rtlbt_patch_entry_cmp(struct rtlbt_patch_entry *a, struct rtlbt_patch_entry *b,394void *thunk __unused)395{396return ((a->prio > b->prio) - (a->prio < b->prio));397}398399static int400rtlbt_parse_section(struct rtlbt_patch_list *patch_list, uint32_t opcode,401uint8_t *data, uint32_t len, uint8_t rom_version, uint8_t key_id)402{403struct rtlbt_sec_hdr *hdr;404struct rtlbt_patch_entry *entry;405struct rtlbt_subsec_hdr *subsec_hdr;406struct rtlbt_subsec_security_hdr *subsec_security_hdr;407uint16_t num_subsecs;408uint8_t *subsec_data;409uint32_t subsec_len;410int i, sec_len = 0;411struct rtlbt_iov iov = {412.data = data,413.len = len,414};415416hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));417if (hdr == NULL) {418errno = EINVAL;419return (-1);420}421num_subsecs = le16toh(hdr->num);422423for (i = 0; i < num_subsecs; i++) {424subsec_hdr = rtlbt_iov_fetch(&iov, sizeof(*subsec_hdr));425if (subsec_hdr == NULL)426break;427subsec_len = le32toh(subsec_hdr->len);428429rtlbt_debug("subsection, eco 0x%02x, prio 0x%02x, len 0x%x",430subsec_hdr->eco, subsec_hdr->prio, subsec_len);431432subsec_data = rtlbt_iov_fetch(&iov, subsec_len);433if (subsec_data == NULL)434break;435436if (subsec_hdr->eco == rom_version + 1) {437if (opcode == RTLBT_PATCH_SECURITY_HEADER) {438subsec_security_hdr = (void *)subsec_hdr;439if (subsec_security_hdr->key_id == key_id)440break;441continue;442}443444entry = calloc(1, sizeof(*entry));445if (entry == NULL) {446errno = ENOMEM;447return (-1);448}449*entry = (struct rtlbt_patch_entry) {450.opcode = opcode,451.len = subsec_len,452.prio = subsec_hdr->prio,453.data = subsec_data,454};455SLIST_INSERT_HEAD(patch_list, entry, next);456sec_len += subsec_len;457}458}459460return (sec_len);461}462463int464rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version,465uint8_t key_id)466{467struct rtlbt_fw_header_v2 *hdr;468struct rtlbt_section *section;469struct rtlbt_patch_entry *entry;470uint32_t num_sections;471uint32_t section_len;472uint32_t opcode;473int seclen, len = 0, patch_len = 0;474uint32_t i;475uint8_t *section_data, *patch_buf;476struct rtlbt_patch_list patch_list =477SLIST_HEAD_INITIALIZER(patch_list);478struct rtlbt_iov iov = {479.data = fw->buf,480.len = fw->len - 7,481};482483hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));484if (hdr == NULL) {485errno = EINVAL;486return (-1);487}488num_sections = le32toh(hdr->num_sections);489490rtlbt_debug("FW version %02x%02x%02x%02x-%02x%02x%02x%02x",491hdr->fw_version[0], hdr->fw_version[1],492hdr->fw_version[2], hdr->fw_version[3],493hdr->fw_version[4], hdr->fw_version[5],494hdr->fw_version[6], hdr->fw_version[7]);495496for (i = 0; i < num_sections; i++) {497section = rtlbt_iov_fetch(&iov, sizeof(*section));498if (section == NULL)499break;500section_len = le32toh(section->len);501opcode = le32toh(section->opcode);502503rtlbt_debug("section, opcode 0x%08x", section->opcode);504505section_data = rtlbt_iov_fetch(&iov, section_len);506if (section_data == NULL)507break;508509seclen = 0;510switch (opcode) {511case RTLBT_PATCH_SECURITY_HEADER:512if (key_id == 0)513break;514/* FALLTHROUGH */515case RTLBT_PATCH_SNIPPETS:516case RTLBT_PATCH_DUMMY_HEADER:517seclen = rtlbt_parse_section(&patch_list, opcode,518section_data, section_len, rom_version, key_id);519break;520default:521break;522}523if (seclen < 0) {524rtlbt_err("Section parse (0x%08x) failed. err %d",525opcode, errno);526return (-1);527}528len += seclen;529}530531if (len == 0) {532errno = ENOMSG;533return (-1);534}535536patch_buf = calloc(1, len);537if (patch_buf == NULL) {538errno = ENOMEM;539return (-1);540}541542SLIST_MERGESORT(&patch_list, NULL,543rtlbt_patch_entry_cmp, rtlbt_patch_entry, next);544while (!SLIST_EMPTY(&patch_list)) {545entry = SLIST_FIRST(&patch_list);546rtlbt_debug("opcode 0x%08x, addr 0x%p, len 0x%x",547entry->opcode, entry->data, entry->len);548memcpy(patch_buf + patch_len, entry->data, entry->len);549patch_len += entry->len;550SLIST_REMOVE_HEAD(&patch_list, next);551free(entry);552}553554if (patch_len == 0) {555errno = EPERM;556return (-1);557}558559free(fw->buf);560fw->buf = patch_buf;561fw->len = patch_len;562563return (0);564}565566int567rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt)568{569uint8_t *buf;570int len;571572len = fw->len + opt->len;573buf = realloc(fw->buf, len);574if (buf == NULL)575return (-1);576memcpy(buf + fw->len, opt->buf, opt->len);577fw->buf = buf;578fw->len = len;579580return (0);581}582583584