1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
5 * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
6 * Copyright (c) 2023 Future Crew LLC.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #include <sys/param.h>
31 #include <sys/endian.h>
32 #include <sys/mman.h>
33 #include <sys/stat.h>
34
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #include "rtlbt_fw.h"
44 #include "rtlbt_dbg.h"
45
46 static const struct rtlbt_id_table rtlbt_ic_id_table[] = {
47 { /* 8723A */
48 .lmp_subversion = RTLBT_ROM_LMP_8723A,
49 .hci_revision = 0xb,
50 .hci_version = 0x6,
51 .flags = RTLBT_IC_FLAG_SIMPLE,
52 .fw_name = "rtl8723a",
53 }, { /* 8723B */
54 .lmp_subversion = RTLBT_ROM_LMP_8723B,
55 .hci_revision = 0xb,
56 .hci_version = 0x6,
57 .fw_name = "rtl8723b",
58 }, { /* 8723D */
59 .lmp_subversion = RTLBT_ROM_LMP_8723B,
60 .hci_revision = 0xd,
61 .hci_version = 0x8,
62 .flags = RTLBT_IC_FLAG_CONFIG,
63 .fw_name = "rtl8723d",
64 }, { /* 8821A */
65 .lmp_subversion = RTLBT_ROM_LMP_8821A,
66 .hci_revision = 0xa,
67 .hci_version = 0x6,
68 .fw_name = "rtl8821a",
69 }, { /* 8821C */
70 .lmp_subversion = RTLBT_ROM_LMP_8821A,
71 .hci_revision = 0xc,
72 .hci_version = 0x8,
73 .flags = RTLBT_IC_FLAG_MSFT,
74 .fw_name = "rtl8821c",
75 }, { /* 8761A */
76 .lmp_subversion = RTLBT_ROM_LMP_8761A,
77 .hci_revision = 0xa,
78 .hci_version = 0x6,
79 .fw_name = "rtl8761a",
80 }, { /* 8761BU */
81 .lmp_subversion = RTLBT_ROM_LMP_8761A,
82 .hci_revision = 0xb,
83 .hci_version = 0xa,
84 .fw_name = "rtl8761bu",
85 }, { /* 8822C with USB interface */
86 .lmp_subversion = RTLBT_ROM_LMP_8822B,
87 .hci_revision = 0xc,
88 .hci_version = 0xa,
89 .flags = RTLBT_IC_FLAG_MSFT,
90 .fw_name = "rtl8822cu",
91 }, { /* 8822B */
92 .lmp_subversion = RTLBT_ROM_LMP_8822B,
93 .hci_revision = 0xb,
94 .hci_version = 0x7,
95 .flags = RTLBT_IC_FLAG_CONFIG | RTLBT_IC_FLAG_MSFT,
96 .fw_name = "rtl8822b",
97 }, { /* 8852A */
98 .lmp_subversion = RTLBT_ROM_LMP_8852A,
99 .hci_revision = 0xa,
100 .hci_version = 0xb,
101 .flags = RTLBT_IC_FLAG_MSFT,
102 .fw_name = "rtl8852au",
103 }, { /* 8852B */
104 .lmp_subversion = RTLBT_ROM_LMP_8852A,
105 .hci_revision = 0xb,
106 .hci_version = 0xb,
107 .flags = RTLBT_IC_FLAG_MSFT,
108 .fw_name = "rtl8852bu",
109 }, { /* 8852C */
110 .lmp_subversion = RTLBT_ROM_LMP_8852A,
111 .hci_revision = 0xc,
112 .hci_version = 0xc,
113 .flags = RTLBT_IC_FLAG_MSFT,
114 .fw_name = "rtl8852cu",
115 .fw_suffix = "_fw_v2.bin",
116 }, { /* 8851B */
117 .lmp_subversion = RTLBT_ROM_LMP_8851B,
118 .hci_revision = 0xb,
119 .hci_version = 0xc,
120 .flags = RTLBT_IC_FLAG_MSFT,
121 .fw_name = "rtl8851bu",
122 }, { /* 8922A */
123 .lmp_subversion = RTLBT_ROM_LMP_8922A,
124 .hci_revision = 0xa,
125 .hci_version = 0xc,
126 .flags = RTLBT_IC_FLAG_MSFT,
127 .fw_name = "rtl8922au",
128 }, { /* 8852BT/8852BE-VT */
129 .lmp_subversion = RTLBT_ROM_LMP_8852A,
130 .hci_revision = 0x87,
131 .hci_version = 0xc,
132 .flags = RTLBT_IC_FLAG_MSFT,
133 .fw_name = "rtl8852btu",
134 },
135 };
136
137 static const uint16_t project_ids[] = {
138 [ 0 ] = RTLBT_ROM_LMP_8723A,
139 [ 1 ] = RTLBT_ROM_LMP_8723B,
140 [ 2 ] = RTLBT_ROM_LMP_8821A,
141 [ 3 ] = RTLBT_ROM_LMP_8761A,
142 [ 7 ] = RTLBT_ROM_LMP_8703B,
143 [ 8 ] = RTLBT_ROM_LMP_8822B,
144 [ 9 ] = RTLBT_ROM_LMP_8723B, /* 8723DU */
145 [ 10 ] = RTLBT_ROM_LMP_8821A, /* 8821CU */
146 [ 13 ] = RTLBT_ROM_LMP_8822B, /* 8822CU */
147 [ 14 ] = RTLBT_ROM_LMP_8761A, /* 8761BU */
148 [ 18 ] = RTLBT_ROM_LMP_8852A, /* 8852AU */
149 [ 19 ] = RTLBT_ROM_LMP_8723B, /* 8723FU */
150 [ 20 ] = RTLBT_ROM_LMP_8852A, /* 8852BU */
151 [ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */
152 [ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */
153 [ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */
154 [ 44 ] = RTLBT_ROM_LMP_8922A, /* 8922A */
155 [ 47 ] = RTLBT_ROM_LMP_8852A, /* 8852BT */
156 };
157
158 /* Signatures */
159 static const uint8_t fw_header_sig_v1[8] =
160 {0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */
161 static const uint8_t fw_header_sig_v2[8] =
162 {0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */
163 static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77};
164
165 int
rtlbt_fw_read(struct rtlbt_firmware * fw,const char * fwname)166 rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)
167 {
168 int fd;
169 struct stat sb;
170 unsigned char *buf, *mmap_addr;
171
172 fd = open(fwname, O_RDONLY);
173 if (fd < 0) {
174 warn("%s: open: %s", __func__, fwname);
175 return (0);
176 }
177
178 if (fstat(fd, &sb) != 0) {
179 warn("%s: stat: %s", __func__, fwname);
180 close(fd);
181 return (0);
182 }
183
184 buf = calloc(1, sb.st_size);
185 if (buf == NULL) {
186 warn("%s: calloc", __func__);
187 close(fd);
188 return (0);
189 }
190
191 mmap_addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
192 if (mmap_addr == MAP_FAILED) {
193 warn("%s: mmap: %s", __func__, fwname);
194 free(buf);
195 close(fd);
196 return (0);
197 }
198
199 memcpy(buf, mmap_addr, sb.st_size);
200 munmap(mmap_addr, sb.st_size);
201
202 /* We have everything, so! */
203
204 memset(fw, 0, sizeof(*fw));
205
206 fw->fwname = strdup(fwname);
207 fw->len = sb.st_size;
208 fw->buf = buf;
209
210 close(fd);
211 return (1);
212 }
213
214 void
rtlbt_fw_free(struct rtlbt_firmware * fw)215 rtlbt_fw_free(struct rtlbt_firmware *fw)
216 {
217 if (fw->fwname)
218 free(fw->fwname);
219 if (fw->buf)
220 free(fw->buf);
221 memset(fw, 0, sizeof(*fw));
222 }
223
224 char *
rtlbt_get_fwname(const char * fw_name,const char * prefix,const char * suffix)225 rtlbt_get_fwname(const char *fw_name, const char *prefix, const char *suffix)
226 {
227 char *fwname;
228
229 asprintf(&fwname, "%s/%s%s", prefix, fw_name, suffix);
230
231 return (fwname);
232 }
233
234 const struct rtlbt_id_table *
rtlbt_get_ic(uint16_t lmp_subversion,uint16_t hci_revision,uint8_t hci_version)235 rtlbt_get_ic(uint16_t lmp_subversion, uint16_t hci_revision,
236 uint8_t hci_version)
237 {
238 unsigned int i;
239
240 for (i = 0; i < nitems(rtlbt_ic_id_table); i++) {
241 if (rtlbt_ic_id_table[i].lmp_subversion == lmp_subversion &&
242 rtlbt_ic_id_table[i].hci_revision == hci_revision &&
243 rtlbt_ic_id_table[i].hci_version == hci_version)
244 return (rtlbt_ic_id_table + i);
245 }
246
247 return (NULL);
248 }
249
250 enum rtlbt_fw_type
rtlbt_get_fw_type(struct rtlbt_firmware * fw,uint16_t * fw_lmp_subversion)251 rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion)
252 {
253 enum rtlbt_fw_type fw_type;
254 size_t fw_header_len;
255 uint8_t *ptr;
256 uint8_t opcode, oplen, project_id;
257
258 if (fw->len < 8) {
259 rtlbt_err("firmware file too small");
260 return (RTLBT_FW_TYPE_UNKNOWN);
261 }
262
263 if (memcmp(fw->buf, fw_header_sig_v1, sizeof(fw_header_sig_v1)) == 0) {
264 fw_type = RTLBT_FW_TYPE_V1;
265 fw_header_len = sizeof(struct rtlbt_fw_header_v1);
266 } else
267 if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) {
268 fw_type = RTLBT_FW_TYPE_V2;
269 fw_header_len = sizeof(struct rtlbt_fw_header_v2);
270 } else
271 return (RTLBT_FW_TYPE_UNKNOWN);
272
273 if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) {
274 rtlbt_err("firmware file too small");
275 return (RTLBT_FW_TYPE_UNKNOWN);
276 }
277
278 ptr = fw->buf + fw->len - sizeof(fw_ext_sig);
279 if (memcmp(ptr, fw_ext_sig, sizeof(fw_ext_sig)) != 0) {
280 rtlbt_err("invalid extension section signature");
281 return (RTLBT_FW_TYPE_UNKNOWN);
282 }
283
284 do {
285 opcode = *--ptr;
286 oplen = *--ptr;
287 ptr -= oplen;
288
289 rtlbt_debug("code=%x len=%x", opcode, oplen);
290
291 if (opcode == 0x00) {
292 if (oplen != 1) {
293 rtlbt_err("invalid instruction length");
294 return (RTLBT_FW_TYPE_UNKNOWN);
295 }
296 project_id = *ptr;
297 rtlbt_debug("project_id=%x", project_id);
298 if (project_id >= nitems(project_ids) ||
299 project_ids[project_id] == 0) {
300 rtlbt_err("unknown project id %x", project_id);
301 return (RTLBT_FW_TYPE_UNKNOWN);
302 }
303 *fw_lmp_subversion = project_ids[project_id];
304 return (fw_type);
305 }
306 } while (opcode != 0xff && ptr > fw->buf + fw_header_len);
307
308 rtlbt_err("can not find project id instruction");
309 return (RTLBT_FW_TYPE_UNKNOWN);
310 };
311
312 int
rtlbt_parse_fwfile_v1(struct rtlbt_firmware * fw,uint8_t rom_version)313 rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version)
314 {
315 struct rtlbt_fw_header_v1 *fw_header;
316 uint8_t *patch_buf;
317 unsigned int i;
318 const uint8_t *chip_id_base;
319 uint32_t patch_offset;
320 uint16_t patch_length, num_patches;
321
322 fw_header = (struct rtlbt_fw_header_v1 *)fw->buf;
323 num_patches = le16toh(fw_header->num_patches);
324 rtlbt_debug("fw_version=%x, num_patches=%d",
325 le32toh(fw_header->fw_version), num_patches);
326
327 /* Find a right patch for the chip. */
328 if (fw->len < sizeof(struct rtlbt_fw_header_v1) +
329 sizeof(fw_ext_sig) + 4 + 8 * num_patches) {
330 errno = EINVAL;
331 return (-1);
332 }
333
334 chip_id_base = fw->buf + sizeof(struct rtlbt_fw_header_v1);
335 for (i = 0; i < num_patches; i++) {
336 if (le16dec(chip_id_base + i * 2) != rom_version + 1)
337 continue;
338 patch_length = le16dec(chip_id_base + 2 * (num_patches + i));
339 patch_offset = le32dec(chip_id_base + 4 * (num_patches + i));
340 break;
341 }
342
343 if (i >= num_patches) {
344 rtlbt_err("can not find patch for chip id %d", rom_version);
345 errno = EINVAL;
346 return (-1);
347 }
348
349 rtlbt_debug(
350 "index=%d length=%x offset=%x", i, patch_length, patch_offset);
351 if (fw->len < patch_offset + patch_length) {
352 errno = EINVAL;
353 return (-1);
354 }
355
356 patch_buf = malloc(patch_length);
357 if (patch_buf == NULL) {
358 errno = ENOMEM;
359 return (-1);
360 }
361
362 memcpy(patch_buf, fw->buf + patch_offset, patch_length - 4);
363 memcpy(patch_buf + patch_length - 4, &fw_header->fw_version, 4);
364
365 free(fw->buf);
366 fw->buf = patch_buf;
367 fw->len = patch_length;
368
369 return (0);
370 }
371
372 static void *
rtlbt_iov_fetch(struct rtlbt_iov * iov,uint32_t len)373 rtlbt_iov_fetch(struct rtlbt_iov *iov, uint32_t len)
374 {
375 void *data = NULL;
376
377 if (iov->len >= len) {
378 data = iov->data;
379 iov->data += len;
380 iov->len -= len;
381 }
382
383 return (data);
384 }
385
386 static int
rtlbt_patch_entry_cmp(struct rtlbt_patch_entry * a,struct rtlbt_patch_entry * b,void * thunk __unused)387 rtlbt_patch_entry_cmp(struct rtlbt_patch_entry *a, struct rtlbt_patch_entry *b,
388 void *thunk __unused)
389 {
390 return ((a->prio > b->prio) - (a->prio < b->prio));
391 }
392
393 static int
rtlbt_parse_section(struct rtlbt_patch_list * patch_list,uint32_t opcode,uint8_t * data,uint32_t len,uint8_t rom_version,uint8_t key_id)394 rtlbt_parse_section(struct rtlbt_patch_list *patch_list, uint32_t opcode,
395 uint8_t *data, uint32_t len, uint8_t rom_version, uint8_t key_id)
396 {
397 struct rtlbt_sec_hdr *hdr;
398 struct rtlbt_patch_entry *entry;
399 struct rtlbt_subsec_hdr *subsec_hdr;
400 struct rtlbt_subsec_security_hdr *subsec_security_hdr;
401 uint16_t num_subsecs;
402 uint8_t *subsec_data;
403 uint32_t subsec_len;
404 int i, sec_len = 0;
405 struct rtlbt_iov iov = {
406 .data = data,
407 .len = len,
408 };
409
410 hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));
411 if (hdr == NULL) {
412 errno = EINVAL;
413 return (-1);
414 }
415 num_subsecs = le16toh(hdr->num);
416
417 for (i = 0; i < num_subsecs; i++) {
418 subsec_hdr = rtlbt_iov_fetch(&iov, sizeof(*subsec_hdr));
419 if (subsec_hdr == NULL)
420 break;
421 subsec_len = le32toh(subsec_hdr->len);
422
423 rtlbt_debug("subsection, eco 0x%02x, prio 0x%02x, len 0x%x",
424 subsec_hdr->eco, subsec_hdr->prio, subsec_len);
425
426 subsec_data = rtlbt_iov_fetch(&iov, subsec_len);
427 if (subsec_data == NULL)
428 break;
429
430 if (subsec_hdr->eco == rom_version + 1) {
431 if (opcode == RTLBT_PATCH_SECURITY_HEADER) {
432 subsec_security_hdr = (void *)subsec_hdr;
433 if (subsec_security_hdr->key_id == key_id)
434 break;
435 continue;
436 }
437
438 entry = calloc(1, sizeof(*entry));
439 if (entry == NULL) {
440 errno = ENOMEM;
441 return (-1);
442 }
443 *entry = (struct rtlbt_patch_entry) {
444 .opcode = opcode,
445 .len = subsec_len,
446 .prio = subsec_hdr->prio,
447 .data = subsec_data,
448 };
449 SLIST_INSERT_HEAD(patch_list, entry, next);
450 sec_len += subsec_len;
451 }
452 }
453
454 return (sec_len);
455 }
456
457 int
rtlbt_parse_fwfile_v2(struct rtlbt_firmware * fw,uint8_t rom_version,uint8_t key_id)458 rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version,
459 uint8_t key_id)
460 {
461 struct rtlbt_fw_header_v2 *hdr;
462 struct rtlbt_section *section;
463 struct rtlbt_patch_entry *entry;
464 uint32_t num_sections;
465 uint32_t section_len;
466 uint32_t opcode;
467 int seclen, len = 0, patch_len = 0;
468 uint32_t i;
469 uint8_t *section_data, *patch_buf;
470 struct rtlbt_patch_list patch_list =
471 SLIST_HEAD_INITIALIZER(patch_list);
472 struct rtlbt_iov iov = {
473 .data = fw->buf,
474 .len = fw->len - 7,
475 };
476
477 hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));
478 if (hdr == NULL) {
479 errno = EINVAL;
480 return (-1);
481 }
482 num_sections = le32toh(hdr->num_sections);
483
484 rtlbt_debug("FW version %02x%02x%02x%02x-%02x%02x%02x%02x",
485 hdr->fw_version[0], hdr->fw_version[1],
486 hdr->fw_version[2], hdr->fw_version[3],
487 hdr->fw_version[4], hdr->fw_version[5],
488 hdr->fw_version[6], hdr->fw_version[7]);
489
490 for (i = 0; i < num_sections; i++) {
491 section = rtlbt_iov_fetch(&iov, sizeof(*section));
492 if (section == NULL)
493 break;
494 section_len = le32toh(section->len);
495 opcode = le32toh(section->opcode);
496
497 rtlbt_debug("section, opcode 0x%08x", section->opcode);
498
499 section_data = rtlbt_iov_fetch(&iov, section_len);
500 if (section_data == NULL)
501 break;
502
503 seclen = 0;
504 switch (opcode) {
505 case RTLBT_PATCH_SECURITY_HEADER:
506 if (key_id == 0)
507 break;
508 /* FALLTHROUGH */
509 case RTLBT_PATCH_SNIPPETS:
510 case RTLBT_PATCH_DUMMY_HEADER:
511 seclen = rtlbt_parse_section(&patch_list, opcode,
512 section_data, section_len, rom_version, key_id);
513 break;
514 default:
515 break;
516 }
517 if (seclen < 0) {
518 rtlbt_err("Section parse (0x%08x) failed. err %d",
519 opcode, errno);
520 return (-1);
521 }
522 len += seclen;
523 }
524
525 if (len == 0) {
526 errno = ENOMSG;
527 return (-1);
528 }
529
530 patch_buf = calloc(1, len);
531 if (patch_buf == NULL) {
532 errno = ENOMEM;
533 return (-1);
534 }
535
536 SLIST_MERGESORT(&patch_list, NULL,
537 rtlbt_patch_entry_cmp, rtlbt_patch_entry, next);
538 while (!SLIST_EMPTY(&patch_list)) {
539 entry = SLIST_FIRST(&patch_list);
540 rtlbt_debug("opcode 0x%08x, addr 0x%p, len 0x%x",
541 entry->opcode, entry->data, entry->len);
542 memcpy(patch_buf + patch_len, entry->data, entry->len);
543 patch_len += entry->len;
544 SLIST_REMOVE_HEAD(&patch_list, next);
545 free(entry);
546 }
547
548 if (patch_len == 0) {
549 errno = EPERM;
550 return (-1);
551 }
552
553 free(fw->buf);
554 fw->buf = patch_buf;
555 fw->len = patch_len;
556
557 return (0);
558 }
559
560 int
rtlbt_append_fwfile(struct rtlbt_firmware * fw,struct rtlbt_firmware * opt)561 rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt)
562 {
563 uint8_t *buf;
564 int len;
565
566 len = fw->len + opt->len;
567 buf = realloc(fw->buf, len);
568 if (buf == NULL)
569 return (-1);
570 memcpy(buf + fw->len, opt->buf, opt->len);
571 fw->buf = buf;
572 fw->len = len;
573
574 return (0);
575 }
576