1 // SPDX-License-Identifier: GPL-2.0-or-later OR MIT
3 * Luxul's firmware container format
5 * Copyright 2020 Legrand AV Inc.
21 #if __BYTE_ORDER == __BIG_ENDIAN
22 #define cpu_to_le32(x) bswap_32(x)
23 #define cpu_to_le16(x) bswap_16(x)
24 #define le32_to_cpu(x) bswap_32(x)
25 #define le16_to_cpu(x) bswap_16(x)
26 #elif __BYTE_ORDER == __LITTLE_ENDIAN
27 #define cpu_to_le32(x) (x)
28 #define cpu_to_le16(x) (x)
29 #define le32_to_cpu(x) (x)
30 #define le16_to_cpu(x) (x)
35 __typeof__ (a) _a = (a); \
36 __typeof__ (b) _b = (b); \
42 __typeof__ (a) _a = (a); \
43 __typeof__ (b) _b = (b); \
47 #define MAX_SUPPORTED_VERSION 3
49 #define LXL_FLAGS_VENDOR_LUXUL 0x00000001
52 char magic
[4]; /* "LXL#" */
64 uint32_t blobs_offset
;
67 } __attribute__((packed
));
70 char magic
[2]; /* "D#" */
74 } __attribute__((packed
));
76 /**************************************************
78 **************************************************/
80 static uint32_t lxlfw_hdr_len(uint32_t version
)
84 return offsetof(struct lxl_hdr
, v0_end
);
86 return offsetof(struct lxl_hdr
, v1_end
);
88 return offsetof(struct lxl_hdr
, v2_end
);
90 return offsetof(struct lxl_hdr
, v3_end
);
92 fprintf(stderr
, "Unsupported version %d\n", version
);
98 * lxlfw_open - open Luxul firmware file and validate it
100 * @pathname: Luxul firmware file
101 * @hdr: struct to read to
103 static FILE *lxlfw_open(const char *pathname
, struct lxl_hdr
*hdr
)
105 size_t v0_len
= lxlfw_hdr_len(0);
112 lxl
= fopen(pathname
, "r");
114 fprintf(stderr
, "Could not open \"%s\" file\n", pathname
);
118 bytes
= fread(hdr
, 1, v0_len
, lxl
);
119 if (bytes
!= v0_len
) {
120 fprintf(stderr
, "Input file too small to use Luxul format\n");
124 if (memcmp(hdr
->magic
, "LXL#", 4)) {
125 fprintf(stderr
, "File <file> does not use Luxul's format\n");
129 version
= le32_to_cpu(hdr
->version
);
131 min_hdr_len
= lxlfw_hdr_len(min(version
, MAX_SUPPORTED_VERSION
));
133 bytes
= fread(((uint8_t *)hdr
) + v0_len
, 1, min_hdr_len
- v0_len
, lxl
);
134 if (bytes
!= min_hdr_len
- v0_len
) {
135 fprintf(stderr
, "Input file too small for header version %d\n", version
);
139 hdr_len
= le32_to_cpu(hdr
->hdr_len
);
141 if (hdr_len
< min_hdr_len
) {
142 fprintf(stderr
, "Header length mismatch: 0x%x (expected: 0x%zx)\n", hdr_len
, min_hdr_len
);
146 if (version
>= 3 && hdr
->blobs_offset
&& hdr
->blobs_len
) {
147 uint32_t blobs_end
= le32_to_cpu(hdr
->blobs_offset
) + le32_to_cpu(hdr
->blobs_len
);
149 if (blobs_end
> hdr_len
) {
150 fprintf(stderr
, "Blobs section ends beyond header end: 0x%x (max: 0x%x)\n", blobs_end
, hdr_len
);
164 * lxlfw_copy_data - read data from one stream and write to another
166 * @from: input stream
168 * @size: amount of bytes to copy (0 to copy all data)
170 static ssize_t
lxlfw_copy_data(FILE *from
, FILE *to
, size_t size
)
172 int copy_all
= size
== 0;
176 while (copy_all
|| size
) {
177 size_t to_read
= copy_all
? sizeof(buf
) : min(size
, sizeof(buf
));
180 bytes
= fread(buf
, 1, to_read
, from
);
181 if (bytes
== 0 && copy_all
) {
183 } else if (bytes
<= 0) {
184 fprintf(stderr
, "Failed to read data\n");
188 if (fwrite(buf
, 1, bytes
, to
) != bytes
) {
189 fprintf(stderr
, "Failed to write data\n");
202 * lxlfw_write_blob - read data from external file and write blob to stream
204 * @lxl: stream to write to
206 * @pathname: external file pathname to read blob data from
209 static ssize_t
lxlfw_write_blob(FILE *lxl
, uint16_t type
, const char *pathname
)
211 struct lxl_blob blob
= {
212 .magic
= { 'D', '#' },
213 .type
= cpu_to_le16(type
),
216 size_t blob_data_len
;
220 data
= fopen(pathname
, "r");
222 fprintf(stderr
, "Could not open input file %s\n", pathname
);
227 fseek(lxl
, sizeof(blob
), SEEK_CUR
);
228 while ((bytes
= fread(buf
, 1, sizeof(buf
), data
)) > 0) {
229 if (fwrite(buf
, 1, bytes
, lxl
) != bytes
) {
230 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
234 blob_data_len
+= bytes
;
239 blob
.len
= cpu_to_le32(blob_data_len
);
241 fseek(lxl
, -(blob_data_len
+ sizeof(blob
)), SEEK_CUR
);
242 bytes
= fwrite(&blob
, 1, sizeof(blob
), lxl
);
243 if (bytes
!= sizeof(blob
)) {
244 fprintf(stderr
, "Could not write Luxul's header\n");
248 fseek(lxl
, blob_data_len
, SEEK_CUR
);
250 return blob_data_len
+ sizeof(blob
);
254 /**************************************************
256 **************************************************/
258 static int lxlfw_info(int argc
, char **argv
) {
267 fprintf(stderr
, "Missing <file> argument\n");
272 lxl
= lxlfw_open(argv
[2], &hdr
);
274 fprintf(stderr
, "Could not open \"%s\" Luxul firmware\n", argv
[2]);
279 version
= le32_to_cpu(hdr
.version
);
281 printf("Format version:\t%d\n", version
);
282 printf("Header length:\t%d\n", le32_to_cpu(hdr
.hdr_len
));
284 printf("Flags:\t\t");
285 flags
= le32_to_cpu(hdr
.flags
);
286 if (flags
& LXL_FLAGS_VENDOR_LUXUL
)
287 printf("VENDOR_LUXUL ");
289 memcpy(board
, hdr
.board
, sizeof(hdr
.board
));
291 printf("Board:\t\t%s\n", board
);
294 printf("Release:\t");
295 if (hdr
.release
[0] || hdr
.release
[1] || hdr
.release
[2] || hdr
.release
[3]) {
296 printf("%hu.%hu.%hu", hdr
.release
[0], hdr
.release
[1], hdr
.release
[2]);
298 printf(".%hu", hdr
.release
[3]);
303 printf("Blobs offset:\t%d\n", le32_to_cpu(hdr
.blobs_offset
));
304 printf("Blobs length:\t%d\n", le32_to_cpu(hdr
.blobs_len
));
307 if (version
>= 3 && hdr
.blobs_offset
) {
310 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
311 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
312 struct lxl_blob blob
;
316 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
317 if (bytes
!= sizeof(blob
)) {
318 fprintf(stderr
, "Failed to read blob section\n");
323 len
= le32_to_cpu(blob
.len
);
327 printf("Magic:\t\t%s\n", blob
.magic
);
328 printf("Type:\t\t0x%04x\n", le16_to_cpu(blob
.type
));
329 printf("Length:\t\t%zu\n", len
);
331 offset
+= sizeof(blob
) + len
;
332 fseek(lxl
, len
, SEEK_CUR
);
335 if (offset
!= le32_to_cpu(hdr
.blobs_len
)) {
337 fprintf(stderr
, "Blobs size (0x%zx) doesn't match declared length (0x%x)\n", offset
, le32_to_cpu(hdr
.blobs_len
));
347 /**************************************************
349 **************************************************/
352 * lxlfw_blob_save - save blob data to external file
354 * @lxl: Luxul firmware FILE with position seeked to blob data
355 * @len: blob data length
356 * @path: external file pathname to write
359 static int lxlfw_blob_save(FILE *lxl
, size_t len
, const char *path
) {
365 out
= fopen(path
, "w+");
367 fprintf(stderr
, "Could not open \"%s\" file\n", path
);
372 while (len
&& (bytes
= fread(buf
, 1, min(len
, sizeof(buf
)), lxl
)) > 0) {
373 if (fwrite(buf
, 1, bytes
, out
) != bytes
) {
374 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
382 fprintf(stderr
, "Could not copy all signature\n");
394 static int lxlfw_blobs(int argc
, char **argv
) {
404 fprintf(stderr
, "Missing <file> argument\n");
410 while ((c
= getopt(argc
, argv
, "")) != -1) {
414 fprintf(stderr
, "Missing info on blobs to extract\n");
419 lxl
= lxlfw_open(argv
[2], &hdr
);
421 fprintf(stderr
, "Failed to open \"%s\" Luxul firmware\n", argv
[2]);
426 version
= le32_to_cpu(hdr
.version
);
428 if (version
< 3 || !hdr
.blobs_offset
) {
429 fprintf(stderr
, "File <file> doesn't contain any blobs\n");
434 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
435 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
436 struct lxl_blob blob
;
439 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
440 if (bytes
!= sizeof(blob
)) {
441 fprintf(stderr
, "Failed to read blob section\n");
447 if (memcmp(blob
.magic
, "D#", 2)) {
448 fprintf(stderr
, "Failed to parse blob section\n");
453 len
= le32_to_cpu(blob
.len
);
458 fseek(lxl
, len
, SEEK_CUR
);
461 fprintf(stderr
, "Failed to save blob section\n");
473 /**************************************************
475 **************************************************/
477 static int lxlfw_create(int argc
, char **argv
) {
478 struct lxl_hdr hdr
= {
479 .magic
= { 'L', 'X', 'L', '#' },
481 char *in_path
= NULL
;
482 uint32_t version
= 0;
483 uint32_t hdr_raw_len
; /* Header length without blobs */
484 uint32_t hdr_len
; /* Header length with blobs */
493 fprintf(stderr
, "Missing <file> argument\n");
499 while ((c
= getopt(argc
, argv
, "i:lb:r:")) != -1) {
505 hdr
.flags
|= cpu_to_le32(LXL_FLAGS_VENDOR_LUXUL
);
506 version
= max(version
, 1);
509 memcpy(hdr
.board
, optarg
, strlen(optarg
) > 16 ? 16 : strlen(optarg
));
510 version
= max(version
, 1);
513 if (sscanf(optarg
, "%hhu.%hhu.%hhu.%hhu", &hdr
.release
[0], &hdr
.release
[1], &hdr
.release
[2], &hdr
.release
[3]) < 1) {
514 fprintf(stderr
, "Failed to parse release number \"%s\"\n", optarg
);
518 version
= max(version
, 2);
523 hdr_raw_len
= lxlfw_hdr_len(version
);
524 hdr_len
= hdr_raw_len
;
527 fprintf(stderr
, "Missing input file argument\n");
532 in
= fopen(in_path
, "r");
534 fprintf(stderr
, "Could not open input file %s\n", in_path
);
539 lxl
= fopen(argv
[2], "w+");
541 fprintf(stderr
, "Could not open \"%s\" file\n", argv
[2]);
550 fseek(lxl
, hdr_raw_len
, SEEK_SET
);
554 hdr
.blobs_offset
= cpu_to_le32(hdr_raw_len
);
555 hdr
.blobs_len
= cpu_to_le32(blobs_len
);
556 hdr_len
+= blobs_len
;
561 hdr
.version
= cpu_to_le32(version
);
562 hdr
.hdr_len
= cpu_to_le32(hdr_len
);
564 fseek(lxl
, 0, SEEK_SET
);
565 bytes
= fwrite(&hdr
, 1, hdr_raw_len
, lxl
);
566 if (bytes
!= hdr_raw_len
) {
567 fprintf(stderr
, "Could not write Luxul's header\n");
572 /* Write input data */
574 fseek(lxl
, 0, SEEK_END
);
575 bytes
= lxlfw_copy_data(in
, lxl
, 0);
577 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
590 /**************************************************
592 **************************************************/
594 static int lxlfw_insert(int argc
, char **argv
) {
595 struct lxl_hdr hdr
= { };
596 char *tmp_path
= NULL
;
597 uint32_t version
= 0;
598 uint32_t hdr_raw_len
; /* Header length without blobs */
599 uint32_t hdr_len
; /* Header length with blobs */
610 fprintf(stderr
, "Missing <file> argument\n");
616 while ((c
= getopt(argc
, argv
, "")) != -1) {
620 fprintf(stderr
, "Missing info on blobs to insert\n");
625 lxl
= lxlfw_open(argv
[2], &hdr
);
627 fprintf(stderr
, "Failed to open \"%s\" Luxul firmware\n", argv
[2]);
632 version
= le32_to_cpu(hdr
.version
);
633 if (version
> MAX_SUPPORTED_VERSION
) {
634 fprintf(stderr
, "Unsupported <file> version %d\n", version
);
639 version
= max(version
, 3);
641 hdr_raw_len
= lxlfw_hdr_len(version
);
642 hdr_len
= hdr_raw_len
;
646 path
= strdup(argv
[2]);
651 asprintf(&tmp_path
, "%s/lxlfwXXXXXX", dirname(path
));
658 fd
= mkstemp(tmp_path
);
661 fprintf(stderr
, "Failed to open temporary file\n");
664 tmp
= fdopen(fd
, "w+");
668 fseek(tmp
, hdr_raw_len
, SEEK_SET
);
673 if (hdr
.blobs_offset
) {
676 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
677 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
678 struct lxl_blob blob
;
681 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
682 if (bytes
!= sizeof(blob
)) {
683 fprintf(stderr
, "Failed to read blob section\n");
688 len
= le32_to_cpu(blob
.len
);
690 /* Don't copy blobs that have to be replaced */
694 fseek(lxl
, -sizeof(blob
), SEEK_CUR
);
695 bytes
= lxlfw_copy_data(lxl
, tmp
, sizeof(blob
) + len
);
696 if (bytes
!= sizeof(blob
) + len
) {
697 fprintf(stderr
, "Failed to copy original blob\n");
701 blobs_len
+= sizeof(blob
) + len
;
704 offset
+= sizeof(blob
) + len
;
708 /* Write new blobs */
712 hdr
.blobs_offset
= cpu_to_le32(hdr_raw_len
);
713 hdr
.blobs_len
= cpu_to_le32(blobs_len
);
714 hdr_len
+= blobs_len
;
718 hdr
.version
= cpu_to_le32(version
);
719 hdr
.hdr_len
= cpu_to_le32(hdr_len
);
721 fseek(tmp
, 0, SEEK_SET
);
722 bytes
= fwrite(&hdr
, 1, hdr_raw_len
, tmp
);
723 if (bytes
!= hdr_raw_len
) {
724 fprintf(stderr
, "Could not write Luxul's header\n");
729 /* Write original data */
731 fseek(tmp
, 0, SEEK_END
);
732 bytes
= lxlfw_copy_data(lxl
, tmp
, 0);
734 fprintf(stderr
, "Failed to copy original file\n");
743 /* Replace original file */
745 if (rename(tmp_path
, argv
[2])) {
747 fprintf(stderr
, "Failed to rename %s: %d\n", tmp_path
, err
);
764 /**************************************************
766 **************************************************/
768 static void usage() {
771 printf("Get info about Luxul firmware:\n");
772 printf("\tlxlfw info <file>\n");
774 printf("Extract blobs from Luxul firmware:\n");
775 printf("\tlxlfw blobs <file> [options]\n");
777 printf("Create new Luxul firmware:\n");
778 printf("\tlxlfw create <file> [options]\n");
779 printf("\t-i file\t\t\t\tinput file for Luxul's firmware container\n");
780 printf("\t-l\t\t\t\tmark firmware as created by Luxul company (DON'T USE)\n");
781 printf("\t-b board\t\t\tboard (device) name\n");
782 printf("\t-r release\t\t\trelease number (e.g. 5.1.0, 7.1.0.2)\n");
784 printf("Insert blob to Luxul firmware:\n");
785 printf("\tlxlfw insert <file> [options]\n");
788 int main(int argc
, char **argv
) {
790 if (!strcmp(argv
[1], "info"))
791 return lxlfw_info(argc
, argv
);
792 else if (!strcmp(argv
[1], "blobs"))
793 return lxlfw_blobs(argc
, argv
);
794 else if (!strcmp(argv
[1], "create"))
795 return lxlfw_create(argc
, argv
);
796 else if (!strcmp(argv
[1], "insert"))
797 return lxlfw_insert(argc
, argv
);