Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
106212 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2013 Adrian Chadd <[email protected]>
5
* Copyright (c) 2019 Vladimir Kondratyev <[email protected]>
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
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
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 *
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 *
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
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
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 *
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
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
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
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
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
584