xref: /freebsd/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c (revision b1879975794772ee51f0b4865753364c7d7626c3)
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
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
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 *
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 *
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
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
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
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