lxlfw: fix struct lxl_hdr attribute
[project/firmware-utils.git] / src / lxlfw.c
1 // SPDX-License-Identifier: GPL-2.0-or-later OR MIT
2 /*
3 * Luxul's firmware container format
4 *
5 * Copyright 2020 Legrand AV Inc.
6 */
7
8 #define _GNU_SOURCE
9
10 #include <byteswap.h>
11 #include <endian.h>
12 #include <errno.h>
13 #include <libgen.h>
14 #include <stddef.h>
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
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)
31 #endif
32
33 #define min(a, b) \
34 ({ \
35 __typeof__ (a) _a = (a); \
36 __typeof__ (b) _b = (b); \
37 _a < _b ? _a : _b; \
38 })
39
40 #define max(a, b) \
41 ({ \
42 __typeof__ (a) _a = (a); \
43 __typeof__ (b) _b = (b); \
44 _a > _b ? _a : _b; \
45 })
46
47 #define MAX_SUPPORTED_VERSION 2
48
49 #define LXL_FLAGS_VENDOR_LUXUL 0x00000001
50
51 struct lxl_hdr {
52 char magic[4]; /* "LXL#" */
53 uint32_t version;
54 uint32_t hdr_len;
55 uint8_t v0_end[0];
56 /* Version: 1+ */
57 uint32_t flags;
58 char board[16];
59 uint8_t v1_end[0];
60 /* Version: 2+ */
61 uint8_t release[4];
62 uint8_t v2_end[0];
63 } __attribute__((packed));
64
65 /**************************************************
66 * Helpers
67 **************************************************/
68
69 static uint32_t lxlfw_hdr_len(uint32_t version)
70 {
71 switch (version) {
72 case 0:
73 return offsetof(struct lxl_hdr, v0_end);
74 case 1:
75 return offsetof(struct lxl_hdr, v1_end);
76 case 2:
77 return offsetof(struct lxl_hdr, v2_end);
78 default:
79 fprintf(stderr, "Unsupported version %d\n", version);
80 return 0;
81 }
82 }
83
84 /**
85 * lxlfw_open - open Luxul firmware file and validate it
86 *
87 * @pathname: Luxul firmware file
88 * @hdr: struct to read to
89 */
90 static FILE *lxlfw_open(const char *pathname, struct lxl_hdr *hdr)
91 {
92 size_t v0_len = lxlfw_hdr_len(0);
93 size_t min_hdr_len;
94 uint32_t version;
95 size_t bytes;
96 FILE *lxl;
97
98 lxl = fopen(pathname, "r");
99 if (!lxl) {
100 fprintf(stderr, "Could not open \"%s\" file\n", pathname);
101 goto err_out;
102 }
103
104 bytes = fread(hdr, 1, v0_len, lxl);
105 if (bytes != v0_len) {
106 fprintf(stderr, "Input file too small to use Luxul format\n");
107 goto err_close;
108 }
109
110 if (memcmp(hdr->magic, "LXL#", 4)) {
111 fprintf(stderr, "File <file> does not use Luxul's format\n");
112 goto err_close;
113 }
114
115 version = le32_to_cpu(hdr->version);
116
117 min_hdr_len = lxlfw_hdr_len(min(version, MAX_SUPPORTED_VERSION));
118
119 bytes = fread(((uint8_t *)hdr) + v0_len, 1, min_hdr_len - v0_len, lxl);
120 if (bytes != min_hdr_len - v0_len) {
121 fprintf(stderr, "Input file too small for header version %d\n", version);
122 goto err_close;
123 }
124
125 return lxl;
126
127 err_close:
128 fclose(lxl);
129 err_out:
130 return NULL;
131 }
132
133 /**
134 * lxlfw_copy_data - read data from one stream and write to another
135 *
136 * @from: input stream
137 * @to: output stream
138 * @size: amount of bytes to copy (0 to copy all data)
139 */
140 static ssize_t lxlfw_copy_data(FILE *from, FILE *to, size_t size)
141 {
142 int copy_all = size == 0;
143 char buf[512];
144 size_t ret = 0;
145
146 while (copy_all || size) {
147 size_t to_read = copy_all ? sizeof(buf) : min(size, sizeof(buf));
148 size_t bytes;
149
150 bytes = fread(buf, 1, to_read, from);
151 if (bytes == 0 && copy_all) {
152 break;
153 } else if (bytes <= 0) {
154 fprintf(stderr, "Failed to read data\n");
155 return -EIO;
156 }
157
158 if (fwrite(buf, 1, bytes, to) != bytes) {
159 fprintf(stderr, "Failed to write data\n");
160 return -EIO;
161 }
162
163 if (!copy_all)
164 size -= bytes;
165 ret += bytes;
166 }
167
168 return ret;
169 }
170
171 /**************************************************
172 * Info
173 **************************************************/
174
175 static int lxlfw_info(int argc, char **argv) {
176 struct lxl_hdr hdr;
177 uint32_t version;
178 char board[17];
179 int err = 0;
180 FILE *lxl;
181 int flags;
182
183 if (argc < 3) {
184 fprintf(stderr, "Missing <file> argument\n");
185 err = -EINVAL;
186 goto out;
187 }
188
189 lxl = lxlfw_open(argv[2], &hdr);
190 if (!lxl) {
191 fprintf(stderr, "Could not open \"%s\" Luxul firmware\n", argv[2]);
192 err = -ENOENT;
193 goto out;
194 }
195
196 version = le32_to_cpu(hdr.version);
197
198 printf("Format version:\t%d\n", version);
199 printf("Header length:\t%d\n", le32_to_cpu(hdr.hdr_len));
200 if (version >= 1) {
201 printf("Flags:\t\t");
202 flags = le32_to_cpu(hdr.flags);
203 if (flags & LXL_FLAGS_VENDOR_LUXUL)
204 printf("VENDOR_LUXUL ");
205 printf("\n");
206 memcpy(board, hdr.board, sizeof(hdr.board));
207 board[16] = '\0';
208 printf("Board:\t\t%s\n", board);
209 }
210 if (version >= 2) {
211 printf("Release:\t");
212 if (hdr.release[0] || hdr.release[1] || hdr.release[2] || hdr.release[3]) {
213 printf("%hu.%hu.%hu", hdr.release[0], hdr.release[1], hdr.release[2]);
214 if (hdr.release[3])
215 printf(".%hu", hdr.release[3]);
216 }
217 printf("\n");
218 }
219
220 err_close:
221 fclose(lxl);
222 out:
223 return err;
224 }
225
226 /**************************************************
227 * Create
228 **************************************************/
229
230 static int lxlfw_create(int argc, char **argv) {
231 struct lxl_hdr hdr = {
232 .magic = { 'L', 'X', 'L', '#' },
233 };
234 char *in_path = NULL;
235 uint32_t version = 0;
236 uint32_t hdr_len;
237 ssize_t bytes;
238 char buf[512];
239 int err = 0;
240 FILE *lxl;
241 FILE *in;
242 int c;
243
244 if (argc < 3) {
245 fprintf(stderr, "Missing <file> argument\n");
246 err = -EINVAL;
247 goto out;
248 }
249
250 optind = 3;
251 while ((c = getopt(argc, argv, "i:lb:r:")) != -1) {
252 switch (c) {
253 case 'i':
254 in_path = optarg;
255 break;
256 case 'l':
257 hdr.flags |= cpu_to_le32(LXL_FLAGS_VENDOR_LUXUL);
258 version = max(version, 1);
259 break;
260 case 'b':
261 memcpy(hdr.board, optarg, strlen(optarg) > 16 ? 16 : strlen(optarg));
262 version = max(version, 1);
263 break;
264 case 'r':
265 if (sscanf(optarg, "%hhu.%hhu.%hhu.%hhu", &hdr.release[0], &hdr.release[1], &hdr.release[2], &hdr.release[3]) < 1) {
266 fprintf(stderr, "Failed to parse release number \"%s\"\n", optarg);
267 err = -EINVAL;
268 goto out;
269 }
270 version = max(version, 2);
271 break;
272 }
273 }
274
275 hdr.version = cpu_to_le32(version);
276 hdr_len = lxlfw_hdr_len(version);
277 if (!hdr_len) {
278 err = -EINVAL;
279 goto out;
280 }
281 hdr.hdr_len = cpu_to_le32(hdr_len);
282
283 if (!in_path) {
284 fprintf(stderr, "Missing input file argument\n");
285 err = -EINVAL;
286 goto out;
287 }
288
289 in = fopen(in_path, "r");
290 if (!in) {
291 fprintf(stderr, "Could not open input file %s\n", in_path);
292 err = -EIO;
293 goto out;
294 }
295
296 lxl = fopen(argv[2], "w+");
297 if (!lxl) {
298 fprintf(stderr, "Could not open \"%s\" file\n", argv[2]);
299 err = -EIO;
300 goto err_close_in;
301 }
302
303 bytes = fwrite(&hdr, 1, hdr_len, lxl);
304 if (bytes != hdr_len) {
305 fprintf(stderr, "Could not write Luxul's header\n");
306 err = -EIO;
307 goto err_close_lxl;
308 }
309
310 bytes = lxlfw_copy_data(in, lxl, 0);
311 if (bytes < 0) {
312 fprintf(stderr, "Could not copy %zu bytes from input file\n", bytes);
313 err = -EIO;
314 goto err_close_lxl;
315 }
316
317 err_close_lxl:
318 fclose(lxl);
319 err_close_in:
320 fclose(in);
321 out:
322 return err;
323 }
324
325 /**************************************************
326 * Start
327 **************************************************/
328
329 static void usage() {
330 printf("Usage:\n");
331 printf("\n");
332 printf("Get info about Luxul firmware:\n");
333 printf("\tlxlfw info <file>\n");
334 printf("\n");
335 printf("Create new Luxul firmware:\n");
336 printf("\tlxlfw create <file> [options]\n");
337 printf("\t-i file\t\t\t\tinput file for Luxul's firmware container\n");
338 printf("\t-l\t\t\t\tmark firmware as created by Luxul company (DON'T USE)\n");
339 printf("\t-b board\t\t\tboard (device) name\n");
340 printf("\t-r release\t\t\trelease number (e.g. 5.1.0, 7.1.0.2)\n");
341 }
342
343 int main(int argc, char **argv) {
344 if (argc > 1) {
345 if (!strcmp(argv[1], "info"))
346 return lxlfw_info(argc, argv);
347 else if (!strcmp(argv[1], "create"))
348 return lxlfw_create(argc, argv);
349 }
350
351 usage();
352 return 0;
353 }