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
51 #define LXL_BLOB_CERTIFICATE 0x0001
52 #define LXL_BLOB_SIGNATURE 0x0002
55 char magic
[4]; /* "LXL#" */
67 uint32_t blobs_offset
;
70 } __attribute__((packed
));
73 char magic
[2]; /* "D#" */
77 } __attribute__((packed
));
79 /**************************************************
81 **************************************************/
83 static uint32_t lxlfw_hdr_len(uint32_t version
)
87 return offsetof(struct lxl_hdr
, v0_end
);
89 return offsetof(struct lxl_hdr
, v1_end
);
91 return offsetof(struct lxl_hdr
, v2_end
);
93 return offsetof(struct lxl_hdr
, v3_end
);
95 fprintf(stderr
, "Unsupported version %d\n", version
);
101 * lxlfw_open - open Luxul firmware file and validate it
103 * @pathname: Luxul firmware file
104 * @hdr: struct to read to
106 static FILE *lxlfw_open(const char *pathname
, struct lxl_hdr
*hdr
)
108 size_t v0_len
= lxlfw_hdr_len(0);
115 lxl
= fopen(pathname
, "r");
117 fprintf(stderr
, "Could not open \"%s\" file\n", pathname
);
121 bytes
= fread(hdr
, 1, v0_len
, lxl
);
122 if (bytes
!= v0_len
) {
123 fprintf(stderr
, "Input file too small to use Luxul format\n");
127 if (memcmp(hdr
->magic
, "LXL#", 4)) {
128 fprintf(stderr
, "File <file> does not use Luxul's format\n");
132 version
= le32_to_cpu(hdr
->version
);
134 min_hdr_len
= lxlfw_hdr_len(min(version
, MAX_SUPPORTED_VERSION
));
136 bytes
= fread(((uint8_t *)hdr
) + v0_len
, 1, min_hdr_len
- v0_len
, lxl
);
137 if (bytes
!= min_hdr_len
- v0_len
) {
138 fprintf(stderr
, "Input file too small for header version %d\n", version
);
142 hdr_len
= le32_to_cpu(hdr
->hdr_len
);
144 if (hdr_len
< min_hdr_len
) {
145 fprintf(stderr
, "Header length mismatch: 0x%x (expected: 0x%zx)\n", hdr_len
, min_hdr_len
);
149 if (version
>= 3 && hdr
->blobs_offset
&& hdr
->blobs_len
) {
150 uint32_t blobs_end
= le32_to_cpu(hdr
->blobs_offset
) + le32_to_cpu(hdr
->blobs_len
);
152 if (blobs_end
> hdr_len
) {
153 fprintf(stderr
, "Blobs section ends beyond header end: 0x%x (max: 0x%x)\n", blobs_end
, hdr_len
);
167 * lxlfw_copy_data - read data from one stream and write to another
169 * @from: input stream
171 * @size: amount of bytes to copy (0 to copy all data)
173 static ssize_t
lxlfw_copy_data(FILE *from
, FILE *to
, size_t size
)
175 int copy_all
= size
== 0;
179 while (copy_all
|| size
) {
180 size_t to_read
= copy_all
? sizeof(buf
) : min(size
, sizeof(buf
));
183 bytes
= fread(buf
, 1, to_read
, from
);
184 if (bytes
== 0 && copy_all
) {
186 } else if (bytes
<= 0) {
187 fprintf(stderr
, "Failed to read data\n");
191 if (fwrite(buf
, 1, bytes
, to
) != bytes
) {
192 fprintf(stderr
, "Failed to write data\n");
205 * lxlfw_write_blob - read data from external file and write blob to stream
207 * @lxl: stream to write to
209 * @pathname: external file pathname to read blob data from
211 static ssize_t
lxlfw_write_blob(FILE *lxl
, uint16_t type
, const char *pathname
)
213 struct lxl_blob blob
= {
214 .magic
= { 'D', '#' },
215 .type
= cpu_to_le16(type
),
218 size_t blob_data_len
;
222 data
= fopen(pathname
, "r");
224 fprintf(stderr
, "Could not open input file %s\n", pathname
);
229 fseek(lxl
, sizeof(blob
), SEEK_CUR
);
230 while ((bytes
= fread(buf
, 1, sizeof(buf
), data
)) > 0) {
231 if (fwrite(buf
, 1, bytes
, lxl
) != bytes
) {
232 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
236 blob_data_len
+= bytes
;
241 blob
.len
= cpu_to_le32(blob_data_len
);
243 fseek(lxl
, -(blob_data_len
+ sizeof(blob
)), SEEK_CUR
);
244 bytes
= fwrite(&blob
, 1, sizeof(blob
), lxl
);
245 if (bytes
!= sizeof(blob
)) {
246 fprintf(stderr
, "Could not write Luxul's header\n");
250 fseek(lxl
, blob_data_len
, SEEK_CUR
);
252 return blob_data_len
+ sizeof(blob
);
255 /**************************************************
257 **************************************************/
259 static int lxlfw_info(int argc
, char **argv
) {
268 fprintf(stderr
, "Missing <file> argument\n");
273 lxl
= lxlfw_open(argv
[2], &hdr
);
275 fprintf(stderr
, "Could not open \"%s\" Luxul firmware\n", argv
[2]);
280 version
= le32_to_cpu(hdr
.version
);
282 printf("Format version:\t%d\n", version
);
283 printf("Header length:\t%d\n", le32_to_cpu(hdr
.hdr_len
));
285 printf("Flags:\t\t");
286 flags
= le32_to_cpu(hdr
.flags
);
287 if (flags
& LXL_FLAGS_VENDOR_LUXUL
)
288 printf("VENDOR_LUXUL ");
290 memcpy(board
, hdr
.board
, sizeof(hdr
.board
));
292 printf("Board:\t\t%s\n", board
);
295 printf("Release:\t");
296 if (hdr
.release
[0] || hdr
.release
[1] || hdr
.release
[2] || hdr
.release
[3]) {
297 printf("%hu.%hu.%hu", hdr
.release
[0], hdr
.release
[1], hdr
.release
[2]);
299 printf(".%hu", hdr
.release
[3]);
304 printf("Blobs offset:\t%d\n", le32_to_cpu(hdr
.blobs_offset
));
305 printf("Blobs length:\t%d\n", le32_to_cpu(hdr
.blobs_len
));
308 if (version
>= 3 && hdr
.blobs_offset
) {
311 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
312 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
313 struct lxl_blob blob
;
317 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
318 if (bytes
!= sizeof(blob
)) {
319 fprintf(stderr
, "Failed to read blob section\n");
324 len
= le32_to_cpu(blob
.len
);
328 printf("Magic:\t\t%s\n", blob
.magic
);
329 printf("Type:\t\t0x%04x\n", le16_to_cpu(blob
.type
));
330 printf("Length:\t\t%zu\n", len
);
332 offset
+= sizeof(blob
) + len
;
333 fseek(lxl
, len
, SEEK_CUR
);
336 if (offset
!= le32_to_cpu(hdr
.blobs_len
)) {
338 fprintf(stderr
, "Blobs size (0x%zx) doesn't match declared length (0x%x)\n", offset
, le32_to_cpu(hdr
.blobs_len
));
348 /**************************************************
350 **************************************************/
352 static int lxlfw_extract(int argc
, char **argv
) {
354 char *out_path
= NULL
;
362 fprintf(stderr
, "Missing <file> argument\n");
368 while ((c
= getopt(argc
, argv
, "O:")) != -1) {
377 fprintf(stderr
, "Missing output file path\n");
382 lxl
= lxlfw_open(argv
[2], &hdr
);
384 fprintf(stderr
, "Failed to open \"%s\" Luxul firmware\n", argv
[2]);
389 fseek(lxl
, le32_to_cpu(hdr
.hdr_len
), SEEK_SET
);
391 if (!strcmp(out_path
, "-")) {
394 out
= fopen(out_path
, "w+");
396 fprintf(stderr
, "Failed to open \"%s\" file\n", out_path
);
402 bytes
= lxlfw_copy_data(lxl
, out
, 0);
404 fprintf(stderr
, "Failed to copy image: %zd\n", bytes
);
419 /**************************************************
421 **************************************************/
424 * lxlfw_blob_save - save blob data to external file
426 * @lxl: Luxul firmware FILE with position seeked to blob data
427 * @len: blob data length
428 * @path: external file pathname to write
430 static int lxlfw_blob_save(FILE *lxl
, size_t len
, const char *path
) {
436 out
= fopen(path
, "w+");
438 fprintf(stderr
, "Could not open \"%s\" file\n", path
);
443 while (len
&& (bytes
= fread(buf
, 1, min(len
, sizeof(buf
)), lxl
)) > 0) {
444 if (fwrite(buf
, 1, bytes
, out
) != bytes
) {
445 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
453 fprintf(stderr
, "Could not copy all signature\n");
464 static int lxlfw_blobs(int argc
, char **argv
) {
465 char *certificate_path
= NULL
;
466 char *signature_path
= NULL
;
476 fprintf(stderr
, "Missing <file> argument\n");
482 while ((c
= getopt(argc
, argv
, "c:s:")) != -1) {
485 certificate_path
= optarg
;
488 signature_path
= optarg
;
493 if (!certificate_path
&& !signature_path
) {
494 fprintf(stderr
, "Missing info on blobs to extract\n");
499 lxl
= lxlfw_open(argv
[2], &hdr
);
501 fprintf(stderr
, "Failed to open \"%s\" Luxul firmware\n", argv
[2]);
506 version
= le32_to_cpu(hdr
.version
);
508 if (version
< 3 || !hdr
.blobs_offset
) {
509 fprintf(stderr
, "File <file> doesn't contain any blobs\n");
514 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
515 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
516 struct lxl_blob blob
;
520 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
521 if (bytes
!= sizeof(blob
)) {
522 fprintf(stderr
, "Failed to read blob section\n");
528 if (memcmp(blob
.magic
, "D#", 2)) {
529 fprintf(stderr
, "Failed to parse blob section\n");
534 type
= le16_to_cpu(blob
.type
);
535 len
= le32_to_cpu(blob
.len
);
537 if (type
== LXL_BLOB_CERTIFICATE
&& certificate_path
) {
538 err
= lxlfw_blob_save(lxl
, len
, certificate_path
);
539 certificate_path
= NULL
;
540 } else if (type
== LXL_BLOB_SIGNATURE
&& signature_path
) {
541 err
= lxlfw_blob_save(lxl
, len
, signature_path
);
542 signature_path
= NULL
;
544 fseek(lxl
, len
, SEEK_CUR
);
547 fprintf(stderr
, "Failed to save blob section\n");
553 if (certificate_path
) {
554 fprintf(stderr
, "Failed to find certificate blob\n");
557 if (signature_path
) {
558 fprintf(stderr
, "Failed to find signature blob\n");
568 /**************************************************
570 **************************************************/
572 static int lxlfw_create(int argc
, char **argv
) {
573 struct lxl_hdr hdr
= {
574 .magic
= { 'L', 'X', 'L', '#' },
576 char *certificate_path
= NULL
;
577 char *signature_path
= NULL
;
578 char *in_path
= NULL
;
579 uint32_t version
= 0;
580 uint32_t hdr_raw_len
; /* Header length without blobs */
581 uint32_t hdr_len
; /* Header length with blobs */
590 fprintf(stderr
, "Missing <file> argument\n");
596 while ((c
= getopt(argc
, argv
, "i:lb:r:")) != -1) {
602 hdr
.flags
|= cpu_to_le32(LXL_FLAGS_VENDOR_LUXUL
);
603 version
= max(version
, 1);
606 memcpy(hdr
.board
, optarg
, strlen(optarg
) > 16 ? 16 : strlen(optarg
));
607 version
= max(version
, 1);
610 if (sscanf(optarg
, "%hhu.%hhu.%hhu.%hhu", &hdr
.release
[0], &hdr
.release
[1], &hdr
.release
[2], &hdr
.release
[3]) < 1) {
611 fprintf(stderr
, "Failed to parse release number \"%s\"\n", optarg
);
615 version
= max(version
, 2);
618 certificate_path
= optarg
;
619 version
= max(version
, 3);
622 signature_path
= optarg
;
623 version
= max(version
, 3);
628 hdr_raw_len
= lxlfw_hdr_len(version
);
629 hdr_len
= hdr_raw_len
;
632 fprintf(stderr
, "Missing input file argument\n");
637 in
= fopen(in_path
, "r");
639 fprintf(stderr
, "Could not open input file %s\n", in_path
);
644 lxl
= fopen(argv
[2], "w+");
646 fprintf(stderr
, "Could not open \"%s\" file\n", argv
[2]);
655 fseek(lxl
, hdr_raw_len
, SEEK_SET
);
656 if (certificate_path
) {
657 bytes
= lxlfw_write_blob(lxl
, LXL_BLOB_CERTIFICATE
, certificate_path
);
659 fprintf(stderr
, "Failed to write certificate\n");
664 if (signature_path
) {
665 bytes
= lxlfw_write_blob(lxl
, LXL_BLOB_SIGNATURE
, signature_path
);
667 fprintf(stderr
, "Failed to write signature\n");
674 hdr
.blobs_offset
= cpu_to_le32(hdr_raw_len
);
675 hdr
.blobs_len
= cpu_to_le32(blobs_len
);
676 hdr_len
+= blobs_len
;
681 hdr
.version
= cpu_to_le32(version
);
682 hdr
.hdr_len
= cpu_to_le32(hdr_len
);
684 fseek(lxl
, 0, SEEK_SET
);
685 bytes
= fwrite(&hdr
, 1, hdr_raw_len
, lxl
);
686 if (bytes
!= hdr_raw_len
) {
687 fprintf(stderr
, "Could not write Luxul's header\n");
692 /* Write input data */
694 fseek(lxl
, 0, SEEK_END
);
695 bytes
= lxlfw_copy_data(in
, lxl
, 0);
697 fprintf(stderr
, "Could not copy %zu bytes from input file\n", bytes
);
710 /**************************************************
712 **************************************************/
714 static int lxlfw_insert(int argc
, char **argv
) {
715 struct lxl_hdr hdr
= { };
716 char *certificate_path
= NULL
;
717 char *signature_path
= NULL
;
718 char *tmp_path
= NULL
;
719 uint32_t version
= 0;
720 uint32_t hdr_raw_len
; /* Header length without blobs */
721 uint32_t hdr_len
; /* Header length with blobs */
732 fprintf(stderr
, "Missing <file> argument\n");
738 while ((c
= getopt(argc
, argv
, "c:s:")) != -1) {
741 certificate_path
= optarg
;
744 signature_path
= optarg
;
749 if (!certificate_path
&& !signature_path
) {
750 fprintf(stderr
, "Missing info on blobs to insert\n");
755 lxl
= lxlfw_open(argv
[2], &hdr
);
757 fprintf(stderr
, "Failed to open \"%s\" Luxul firmware\n", argv
[2]);
762 version
= le32_to_cpu(hdr
.version
);
763 if (version
> MAX_SUPPORTED_VERSION
) {
764 fprintf(stderr
, "Unsupported <file> version %d\n", version
);
769 version
= max(version
, 3);
771 hdr_raw_len
= lxlfw_hdr_len(version
);
772 hdr_len
= hdr_raw_len
;
776 path
= strdup(argv
[2]);
781 asprintf(&tmp_path
, "%s/lxlfwXXXXXX", dirname(path
));
788 fd
= mkstemp(tmp_path
);
791 fprintf(stderr
, "Failed to open temporary file\n");
794 tmp
= fdopen(fd
, "w+");
798 fseek(tmp
, hdr_raw_len
, SEEK_SET
);
803 if (hdr
.blobs_offset
) {
806 fseek(lxl
, le32_to_cpu(hdr
.blobs_offset
), SEEK_SET
);
807 for (offset
= 0; offset
< le32_to_cpu(hdr
.blobs_len
); ) {
808 struct lxl_blob blob
;
812 bytes
= fread(&blob
, 1, sizeof(blob
), lxl
);
813 if (bytes
!= sizeof(blob
)) {
814 fprintf(stderr
, "Failed to read blob section\n");
819 type
= le16_to_cpu(blob
.type
);
820 len
= le32_to_cpu(blob
.len
);
822 /* Don't copy blobs that have to be replaced */
823 if ((type
== LXL_BLOB_CERTIFICATE
&& certificate_path
) ||
824 (type
== LXL_BLOB_SIGNATURE
&& signature_path
)) {
825 fseek(lxl
, len
, SEEK_CUR
);
827 fseek(lxl
, -sizeof(blob
), SEEK_CUR
);
828 bytes
= lxlfw_copy_data(lxl
, tmp
, sizeof(blob
) + len
);
829 if (bytes
!= sizeof(blob
) + len
) {
830 fprintf(stderr
, "Failed to copy original blob\n");
834 blobs_len
+= sizeof(blob
) + len
;
837 offset
+= sizeof(blob
) + len
;
841 /* Write new blobs */
843 if (certificate_path
) {
844 bytes
= lxlfw_write_blob(tmp
, LXL_BLOB_CERTIFICATE
, certificate_path
);
846 fprintf(stderr
, "Failed to write certificate\n");
851 if (signature_path
) {
852 bytes
= lxlfw_write_blob(tmp
, LXL_BLOB_SIGNATURE
, signature_path
);
854 fprintf(stderr
, "Failed to write signature\n");
860 hdr
.blobs_offset
= cpu_to_le32(hdr_raw_len
);
861 hdr
.blobs_len
= cpu_to_le32(blobs_len
);
862 hdr_len
+= blobs_len
;
866 hdr
.version
= cpu_to_le32(version
);
867 hdr
.hdr_len
= cpu_to_le32(hdr_len
);
869 fseek(tmp
, 0, SEEK_SET
);
870 bytes
= fwrite(&hdr
, 1, hdr_raw_len
, tmp
);
871 if (bytes
!= hdr_raw_len
) {
872 fprintf(stderr
, "Could not write Luxul's header\n");
877 /* Write original data */
879 fseek(tmp
, 0, SEEK_END
);
880 bytes
= lxlfw_copy_data(lxl
, tmp
, 0);
882 fprintf(stderr
, "Failed to copy original file\n");
891 /* Replace original file */
893 if (rename(tmp_path
, argv
[2])) {
895 fprintf(stderr
, "Failed to rename %s: %d\n", tmp_path
, err
);
912 /**************************************************
914 **************************************************/
916 static void usage() {
919 printf("Get info about Luxul firmware:\n");
920 printf("\tlxlfw info <file>\n");
922 printf("Extract image from Luxul firmware:\n");
923 printf("\tlxlfw extract <file> [options]\n");
924 printf("\t-O file\t\t\t\toutput file (- for stdout)\n");
926 printf("Extract blobs from Luxul firmware:\n");
927 printf("\tlxlfw blobs <file> [options]\n");
928 printf("\t-c file\t\t\t\tcertificate output file\n");
929 printf("\t-s file\t\t\t\tsignature output file\n");
931 printf("Create new Luxul firmware:\n");
932 printf("\tlxlfw create <file> [options]\n");
933 printf("\t-i file\t\t\t\tinput file for Luxul's firmware container\n");
934 printf("\t-l\t\t\t\tmark firmware as created by Luxul company (DON'T USE)\n");
935 printf("\t-b board\t\t\tboard (device) name\n");
936 printf("\t-r release\t\t\trelease number (e.g. 5.1.0, 7.1.0.2)\n");
937 printf("\t-c file\t\t\t\tcertificate file\n");
938 printf("\t-s file\t\t\t\tsignature file\n");
940 printf("Insert blob to Luxul firmware:\n");
941 printf("\tlxlfw insert <file> [options]\n");
942 printf("\t-c file\t\t\t\tcertificate file\n");
943 printf("\t-s file\t\t\t\tsignature file\n");
947 int main(int argc
, char **argv
) {
949 if (!strcmp(argv
[1], "info"))
950 return lxlfw_info(argc
, argv
);
951 else if (!strcmp(argv
[1], "extract"))
952 return lxlfw_extract(argc
, argv
);
953 else if (!strcmp(argv
[1], "blobs"))
954 return lxlfw_blobs(argc
, argv
);
955 else if (!strcmp(argv
[1], "create"))
956 return lxlfw_create(argc
, argv
);
957 else if (!strcmp(argv
[1], "insert"))
958 return lxlfw_insert(argc
, argv
);