1/*
2 * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
7 *
8 */
9
10#include <stdio.h>
11#include <stdlib.h>
12#include <stdint.h>
13#include <string.h>
14#include <unistd.h>     /* for unlink() */
15#include <libgen.h>
16#include <getopt.h>     /* for getopt() */
17#include <stdarg.h>
18#include <errno.h>
19#include <sys/stat.h>
20
21#include <arpa/inet.h>
22#include <netinet/in.h>
23
24#define MAX_MAGIC_LEN		16
25#define MAX_MODEL_LEN		32
26#define MAX_VERSION_LEN		14
27#define MAX_MTD_NAME_LEN	16
28
29#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
30
31struct edimax_header {
32	char		magic[MAX_MAGIC_LEN];
33	char		model[MAX_MODEL_LEN];
34	unsigned char	force;
35	unsigned char	header_csum;
36	unsigned char	data_csum;
37	uint32_t	data_size;
38	uint32_t	start_addr;
39	uint32_t	end_addr;
40	char		fw_version[MAX_VERSION_LEN];
41	unsigned char	type;
42	char		mtd_name[MAX_MTD_NAME_LEN];
43} __attribute__ ((packed));
44
45/*
46 * Globals
47 */
48static char *ofname;
49static char *ifname;
50static char *progname;
51
52static char *model;
53static char *magic = "eDiMaX";
54static char *fw_version = "";
55static char *mtd_name;
56static int force;
57static uint32_t start_addr;
58static uint32_t end_addr;
59static uint8_t image_type;
60static int data_size;
61
62/*
63 * Message macros
64 */
65#define ERR(fmt, ...) do { \
66	fflush(0); \
67	fprintf(stderr, "[%s] *** error: " fmt "\n", \
68			progname, ## __VA_ARGS__ ); \
69} while (0)
70
71#define ERRS(fmt, ...) do { \
72	int save = errno; \
73	fflush(0); \
74	fprintf(stderr, "[%s] *** error: " fmt " (%s)\n", \
75			progname, ## __VA_ARGS__, strerror(save)); \
76} while (0)
77
78#define DBG(fmt, ...) do { \
79	fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
80} while (0)
81
82static void usage(int status)
83{
84	FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
85
86	fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
87	fprintf(stream,
88"\n"
89"Options:\n"
90"  -e <addr>       set end addr to <addr>\n"
91"  -f              set force flag\n"
92"  -h              show this screen\n"
93"  -i <file>       read input data from the file <file>\n"
94"  -o <file>       write output to the file <file>\n"
95"  -m <model>      set model to <model>\n"
96"  -M <magic>      set image magic to <magic>\n"
97"  -n <name>       set MTD device name to <name>\n"
98"  -s <addr>       set start address to <addr>\n"
99"  -t <type>       set image type to <type>\n"
100"  -v <version>    set firmware version to <version>\n"
101	);
102
103	exit(status);
104}
105
106int
107str2u32(char *arg, uint32_t *val)
108{
109	char *err = NULL;
110	uint32_t t;
111
112	errno=0;
113	t = strtoul(arg, &err, 0);
114	if (errno || (err==arg) || ((err != NULL) && *err)) {
115		return -1;
116	}
117
118	*val = t;
119	return 0;
120}
121
122int
123str2u8(char *arg, uint8_t *val)
124{
125	char *err = NULL;
126	uint32_t t;
127
128	errno=0;
129	t = strtoul(arg, &err, 0);
130	if (errno || (err==arg) || ((err != NULL) && *err) || (t >= 0x100)) {
131		return -1;
132	}
133
134	*val = t & 0xFF;
135	return 0;
136}
137
138static int get_file_size(char *name)
139{
140	struct stat st;
141	int res;
142
143	res = stat(name, &st);
144	if (res){
145		ERRS("stat failed on %s", name);
146		return -1;
147	}
148
149	return st.st_size;
150}
151
152static int read_to_buf(char *name, char *buf, int buflen)
153{
154	FILE *f;
155	int ret = EXIT_FAILURE;
156
157	f = fopen(name, "r");
158	if (f == NULL) {
159		ERRS("could not open \"%s\" for reading", name);
160		goto out;
161	}
162
163	errno = 0;
164	fread(buf, buflen, 1, f);
165	if (errno != 0) {
166		ERRS("unable to read from file \"%s\"", name);
167		goto out_close;
168	}
169
170	ret = EXIT_SUCCESS;
171
172out_close:
173	fclose(f);
174out:
175	return ret;
176}
177
178static int check_options(void)
179{
180#define CHKSTR(_name, _msg)				\
181	do {						\
182		if (_name == NULL) {			\
183			ERR("no %s specified", _msg);	\
184			return -1;			\
185		}					\
186	} while (0)
187
188#define CHKSTRLEN(_name, _msg)						\
189	do {								\
190		int field_len;						\
191		CHKSTR(_name, _msg);					\
192		field_len = FIELD_SIZEOF(struct edimax_header, _name) - 1; \
193		if (strlen(_name) > field_len) { 			\
194			ERR("'%s' is too long, max %s length is %d",	\
195			    _name, _msg, field_len);			\
196			return -1;					\
197		}							\
198	} while (0)
199
200	CHKSTR(ofname, "output file");
201	CHKSTR(ifname, "input file");
202
203	CHKSTRLEN(magic, "magic");
204	CHKSTRLEN(model, "model");
205	CHKSTRLEN(mtd_name, "MTD device name");
206	CHKSTRLEN(fw_version, "firware version");
207
208	data_size = get_file_size(ifname);
209	if (data_size < 0)
210		return -1;
211
212	return 0;
213}
214
215static int write_fw(char *data, int len)
216{
217	FILE *f;
218	int ret = EXIT_FAILURE;
219
220	f = fopen(ofname, "w");
221	if (f == NULL) {
222		ERRS("could not open \"%s\" for writing", ofname);
223		goto out;
224	}
225
226	errno = 0;
227	fwrite(data, len, 1, f);
228	if (errno) {
229		ERRS("unable to write output file");
230		goto out_flush;
231	}
232
233	DBG("firmware file \"%s\" completed", ofname);
234
235	ret = EXIT_SUCCESS;
236
237out_flush:
238	fflush(f);
239	fclose(f);
240	if (ret != EXIT_SUCCESS) {
241		unlink(ofname);
242	}
243out:
244	return ret;
245}
246
247static unsigned char checksum(unsigned char *p, unsigned len)
248{
249	unsigned char csum = 0;
250
251	while (len--)
252		csum += *p++;
253
254	csum ^= 0xb9;
255
256	return csum;
257}
258
259static int build_fw(void)
260{
261	int buflen;
262	char *buf;
263	char *data;
264	struct edimax_header *hdr;
265	int ret = EXIT_FAILURE;
266
267	buflen = sizeof(struct edimax_header) + data_size;
268
269	buf = malloc(buflen);
270	if (!buf) {
271		ERR("no memory for buffer\n");
272		goto out;
273	}
274
275	data = buf + sizeof(struct edimax_header);
276
277	/* read input file */
278	ret = read_to_buf(ifname, data, data_size);
279	if (ret)
280		goto out_free_buf;
281
282	/* fill firmware header */
283	hdr = (struct edimax_header *)buf;
284	memset(hdr, 0, sizeof(struct edimax_header));
285
286	strncpy(hdr->model, model, sizeof(hdr->model));
287	strncpy(hdr->magic, magic, sizeof(hdr->magic));
288	strncpy(hdr->fw_version, fw_version, sizeof(hdr->fw_version));
289	strncpy(hdr->mtd_name, mtd_name, sizeof(hdr->mtd_name));
290
291	hdr->force = force;
292	hdr->start_addr = htonl(start_addr);
293	hdr->end_addr = htonl(end_addr);
294	hdr->data_size = htonl(data_size);
295	hdr->type = image_type;
296
297	hdr->data_csum = checksum((unsigned char *)data, data_size);
298	hdr->header_csum = checksum((unsigned char *)hdr,
299				    sizeof(struct edimax_header));
300
301	ret = write_fw(buf, buflen);
302	if (ret)
303		goto out_free_buf;
304
305	ret = EXIT_SUCCESS;
306
307out_free_buf:
308	free(buf);
309out:
310	return ret;
311}
312
313int main(int argc, char *argv[])
314{
315	int ret = EXIT_FAILURE;
316
317	progname = basename(argv[0]);
318
319	while (1) {
320		int c;
321
322		c = getopt(argc, argv, "e:fhi:o:m:M:n:s:t:v:");
323		if (c == -1)
324			break;
325
326		switch (c) {
327		case 'e':
328			if (str2u32(optarg, &end_addr)) {
329				ERR("%s is invalid '%s'",
330				    "end address", optarg);
331				goto out;
332			}
333			break;
334		case 'f':
335			force = 1;
336			break;
337		case 'i':
338			ifname = optarg;
339			break;
340		case 'h':
341			usage(EXIT_SUCCESS);
342			break;
343		case 'o':
344			ofname = optarg;
345			break;
346		case 'm':
347			model = optarg;
348			break;
349		case 'M':
350			magic = optarg;
351			break;
352		case 'n':
353			mtd_name = optarg;
354			break;
355		case 's':
356			if (str2u32(optarg, &start_addr)) {
357				ERR("%s is invalid '%s'",
358				    "start address", optarg);
359				goto out;
360			}
361			break;
362		case 't':
363			if (str2u8(optarg, &image_type)) {
364				ERR("%s is invalid '%s'",
365				    "image type", optarg);
366				goto out;
367			}
368			break;
369		case 'v':
370			fw_version = optarg;
371			break;
372		default:
373			usage(EXIT_FAILURE);
374			break;
375		}
376	}
377
378	ret = check_options();
379	if (ret)
380		goto out;
381
382	ret = build_fw();
383
384out:
385	return ret;
386}
387