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