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 #ifdef RTLBTFW_SUPPORTS_FW_V2
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 }, { /* 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 #endif
122 },
123 };
124
125 static const uint16_t project_ids[] = {
126 [ 0 ] = RTLBT_ROM_LMP_8723A,
127 [ 1 ] = RTLBT_ROM_LMP_8723B,
128 [ 2 ] = RTLBT_ROM_LMP_8821A,
129 [ 3 ] = RTLBT_ROM_LMP_8761A,
130 [ 7 ] = RTLBT_ROM_LMP_8703B,
131 [ 8 ] = RTLBT_ROM_LMP_8822B,
132 [ 9 ] = RTLBT_ROM_LMP_8723B, /* 8723DU */
133 [ 10 ] = RTLBT_ROM_LMP_8821A, /* 8821CU */
134 [ 13 ] = RTLBT_ROM_LMP_8822B, /* 8822CU */
135 [ 14 ] = RTLBT_ROM_LMP_8761A, /* 8761BU */
136 [ 18 ] = RTLBT_ROM_LMP_8852A, /* 8852AU */
137 [ 19 ] = RTLBT_ROM_LMP_8723B, /* 8723FU */
138 [ 20 ] = RTLBT_ROM_LMP_8852A, /* 8852BU */
139 [ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */
140 [ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */
141 [ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */
142 };
143
144 /* Signatures */
145 static const uint8_t fw_header_sig_v1[8] =
146 {0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */
147 #ifdef RTLBTFW_SUPPORTS_FW_V2
148 static const uint8_t fw_header_sig_v2[8] =
149 {0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */
150 #endif
151 static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77};
152
153 int
rtlbt_fw_read(struct rtlbt_firmware * fw,const char * fwname)154 rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)
155 {
156 int fd;
157 struct stat sb;
158 unsigned char *buf;
159 ssize_t r;
160
161 fd = open(fwname, O_RDONLY);
162 if (fd < 0) {
163 warn("%s: open: %s", __func__, fwname);
164 return (0);
165 }
166
167 if (fstat(fd, &sb) != 0) {
168 warn("%s: stat: %s", __func__, fwname);
169 close(fd);
170 return (0);
171 }
172
173 buf = calloc(1, sb.st_size);
174 if (buf == NULL) {
175 warn("%s: calloc", __func__);
176 close(fd);
177 return (0);
178 }
179
180 /* XXX handle partial reads */
181 r = read(fd, buf, sb.st_size);
182 if (r < 0) {
183 warn("%s: read", __func__);
184 free(buf);
185 close(fd);
186 return (0);
187 }
188
189 if (r != sb.st_size) {
190 rtlbt_err("read len %d != file size %d",
191 (int) r,
192 (int) sb.st_size);
193 free(buf);
194 close(fd);
195 return (0);
196 }
197
198 /* We have everything, so! */
199
200 memset(fw, 0, sizeof(*fw));
201
202 fw->fwname = strdup(fwname);
203 fw->len = sb.st_size;
204 fw->buf = buf;
205
206 close(fd);
207 return (1);
208 }
209
210 void
rtlbt_fw_free(struct rtlbt_firmware * fw)211 rtlbt_fw_free(struct rtlbt_firmware *fw)
212 {
213 if (fw->fwname)
214 free(fw->fwname);
215 if (fw->buf)
216 free(fw->buf);
217 memset(fw, 0, sizeof(*fw));
218 }
219
220 char *
rtlbt_get_fwname(const char * fw_name,const char * prefix,const char * suffix)221 rtlbt_get_fwname(const char *fw_name, const char *prefix, const char *suffix)
222 {
223 char *fwname;
224
225 asprintf(&fwname, "%s/%s%s", prefix, fw_name, suffix);
226
227 return (fwname);
228 }
229
230 const struct rtlbt_id_table *
rtlbt_get_ic(uint16_t lmp_subversion,uint16_t hci_revision,uint8_t hci_version)231 rtlbt_get_ic(uint16_t lmp_subversion, uint16_t hci_revision,
232 uint8_t hci_version)
233 {
234 unsigned int i;
235
236 for (i = 0; i < nitems(rtlbt_ic_id_table); i++) {
237 if (rtlbt_ic_id_table[i].lmp_subversion == lmp_subversion &&
238 rtlbt_ic_id_table[i].hci_revision == hci_revision &&
239 rtlbt_ic_id_table[i].hci_version == hci_version)
240 return (rtlbt_ic_id_table + i);
241 }
242
243 return (NULL);
244 }
245
246 enum rtlbt_fw_type
rtlbt_get_fw_type(struct rtlbt_firmware * fw,uint16_t * fw_lmp_subversion)247 rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion)
248 {
249 enum rtlbt_fw_type fw_type;
250 size_t fw_header_len;
251 uint8_t *ptr;
252 uint8_t opcode, oplen, project_id;
253
254 if (fw->len < 8) {
255 rtlbt_err("firmware file too small");
256 return (RTLBT_FW_TYPE_UNKNOWN);
257 }
258
259 if (memcmp(fw->buf, fw_header_sig_v1, sizeof(fw_header_sig_v1)) == 0) {
260 fw_type = RTLBT_FW_TYPE_V1;
261 fw_header_len = sizeof(struct rtlbt_fw_header_v1);
262 } else
263 #ifdef RTLBTFW_SUPPORTS_FW_V2
264 if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) {
265 fw_type = RTLBT_FW_TYPE_V2;
266 fw_header_len = sizeof(struct rtlbt_fw_header_v2);
267 } else
268 #endif
269 return (RTLBT_FW_TYPE_UNKNOWN);
270
271 if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) {
272 rtlbt_err("firmware file too small");
273 return (RTLBT_FW_TYPE_UNKNOWN);
274 }
275
276 ptr = fw->buf + fw->len - sizeof(fw_ext_sig);
277 if (memcmp(ptr, fw_ext_sig, sizeof(fw_ext_sig)) != 0) {
278 rtlbt_err("invalid extension section signature");
279 return (RTLBT_FW_TYPE_UNKNOWN);
280 }
281
282 do {
283 opcode = *--ptr;
284 oplen = *--ptr;
285 ptr -= oplen;
286
287 rtlbt_debug("code=%x len=%x", opcode, oplen);
288
289 if (opcode == 0x00) {
290 if (oplen != 1) {
291 rtlbt_err("invalid instruction length");
292 return (RTLBT_FW_TYPE_UNKNOWN);
293 }
294 project_id = *ptr;
295 rtlbt_debug("project_id=%x", project_id);
296 if (project_id >= nitems(project_ids) ||
297 project_ids[project_id] == 0) {
298 rtlbt_err("unknown project id %x", project_id);
299 return (RTLBT_FW_TYPE_UNKNOWN);
300 }
301 *fw_lmp_subversion = project_ids[project_id];
302 return (fw_type);
303 }
304 } while (opcode != 0xff && ptr > fw->buf + fw_header_len);
305
306 rtlbt_err("can not find project id instruction");
307 return (RTLBT_FW_TYPE_UNKNOWN);
308 };
309
310 int
rtlbt_parse_fwfile_v1(struct rtlbt_firmware * fw,uint8_t rom_version)311 rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version)
312 {
313 struct rtlbt_fw_header_v1 *fw_header;
314 uint8_t *patch_buf;
315 unsigned int i;
316 const uint8_t *chip_id_base;
317 uint32_t patch_offset;
318 uint16_t patch_length, num_patches;
319
320 fw_header = (struct rtlbt_fw_header_v1 *)fw->buf;
321 num_patches = le16toh(fw_header->num_patches);
322 rtlbt_debug("fw_version=%x, num_patches=%d",
323 le32toh(fw_header->fw_version), num_patches);
324
325 /* Find a right patch for the chip. */
326 if (fw->len < sizeof(struct rtlbt_fw_header_v1) +
327 sizeof(fw_ext_sig) + 4 + 8 * num_patches) {
328 errno = EINVAL;
329 return (-1);
330 }
331
332 chip_id_base = fw->buf + sizeof(struct rtlbt_fw_header_v1);
333 for (i = 0; i < num_patches; i++) {
334 if (le16dec(chip_id_base + i * 2) != rom_version + 1)
335 continue;
336 patch_length = le16dec(chip_id_base + 2 * (num_patches + i));
337 patch_offset = le32dec(chip_id_base + 4 * (num_patches + i));
338 break;
339 }
340
341 if (i >= num_patches) {
342 rtlbt_err("can not find patch for chip id %d", rom_version);
343 errno = EINVAL;
344 return (-1);
345 }
346
347 rtlbt_debug(
348 "index=%d length=%x offset=%x", i, patch_length, patch_offset);
349 if (fw->len < patch_offset + patch_length) {
350 errno = EINVAL;
351 return (-1);
352 }
353
354 patch_buf = malloc(patch_length);
355 if (patch_buf == NULL) {
356 errno = ENOMEM;
357 return (-1);
358 }
359
360 memcpy(patch_buf, fw->buf + patch_offset, patch_length - 4);
361 memcpy(patch_buf + patch_length - 4, &fw_header->fw_version, 4);
362
363 free(fw->buf);
364 fw->buf = patch_buf;
365 fw->len = patch_length;
366
367 return (0);
368 }
369
370 int
rtlbt_append_fwfile(struct rtlbt_firmware * fw,struct rtlbt_firmware * opt)371 rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt)
372 {
373 uint8_t *buf;
374 int len;
375
376 len = fw->len + opt->len;
377 buf = realloc(fw->buf, len);
378 if (buf == NULL)
379 return (-1);
380 memcpy(buf + fw->len, opt->buf, opt->len);
381 fw->buf = buf;
382 fw->len = len;
383
384 return (0);
385 }
386