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