Merge branch 'next' into for-linus
[linux-2.6-microblaze.git] / tools / firmware / ihex2fw.c
1 /*
2  * Parser/loader for IHEX formatted data.
3  *
4  * Copyright © 2008 David Woodhouse <dwmw2@infradead.org>
5  * Copyright © 2005 Jan Harkes <jaharkes@cs.cmu.edu>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  */
11
12 #include <stdint.h>
13 #include <arpa/inet.h>
14 #include <stdio.h>
15 #include <errno.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <sys/mman.h>
19 #include <fcntl.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #define _GNU_SOURCE
24 #include <getopt.h>
25
26
27 #define __ALIGN_KERNEL_MASK(x, mask)    (((x) + (mask)) & ~(mask))
28 #define __ALIGN_KERNEL(x, a)            __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
29 #define ALIGN(x, a)                     __ALIGN_KERNEL((x), (a))
30
31 struct ihex_binrec {
32         struct ihex_binrec *next; /* not part of the real data structure */
33         uint32_t addr;
34         uint16_t len;
35         uint8_t data[];
36 };
37
38 /**
39  * nybble/hex are little helpers to parse hexadecimal numbers to a byte value
40  **/
41 static uint8_t nybble(const uint8_t n)
42 {
43         if      (n >= '0' && n <= '9') return n - '0';
44         else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
45         else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
46         return 0;
47 }
48
49 static uint8_t hex(const uint8_t *data, uint8_t *crc)
50 {
51         uint8_t val = (nybble(data[0]) << 4) | nybble(data[1]);
52         *crc += val;
53         return val;
54 }
55
56 static int process_ihex(uint8_t *data, ssize_t size);
57 static void file_record(struct ihex_binrec *record);
58 static int output_records(int outfd);
59
60 static int sort_records = 0;
61 static int wide_records = 0;
62 static int include_jump = 0;
63
64 static int usage(void)
65 {
66         fprintf(stderr, "ihex2fw: Convert ihex files into binary "
67                 "representation for use by Linux kernel\n");
68         fprintf(stderr, "usage: ihex2fw [<options>] <src.HEX> <dst.fw>\n");
69         fprintf(stderr, "       -w: wide records (16-bit length)\n");
70         fprintf(stderr, "       -s: sort records by address\n");
71         fprintf(stderr, "       -j: include records for CS:IP/EIP address\n");
72         return 1;
73 }
74
75 int main(int argc, char **argv)
76 {
77         int infd, outfd;
78         struct stat st;
79         uint8_t *data;
80         int opt;
81
82         while ((opt = getopt(argc, argv, "wsj")) != -1) {
83                 switch (opt) {
84                 case 'w':
85                         wide_records = 1;
86                         break;
87                 case 's':
88                         sort_records = 1;
89                         break;
90                 case 'j':
91                         include_jump = 1;
92                         break;
93                 default:
94                         return usage();
95                 }
96         }
97
98         if (optind + 2 != argc)
99                 return usage();
100
101         if (!strcmp(argv[optind], "-"))
102                 infd = 0;
103         else
104                 infd = open(argv[optind], O_RDONLY);
105         if (infd == -1) {
106                 fprintf(stderr, "Failed to open source file: %s",
107                         strerror(errno));
108                 return usage();
109         }
110         if (fstat(infd, &st)) {
111                 perror("stat");
112                 return 1;
113         }
114         data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, infd, 0);
115         if (data == MAP_FAILED) {
116                 perror("mmap");
117                 return 1;
118         }
119
120         if (!strcmp(argv[optind+1], "-"))
121                 outfd = 1;
122         else
123                 outfd = open(argv[optind+1], O_TRUNC|O_CREAT|O_WRONLY, 0644);
124         if (outfd == -1) {
125                 fprintf(stderr, "Failed to open destination file: %s",
126                         strerror(errno));
127                 return usage();
128         }
129         if (process_ihex(data, st.st_size))
130                 return 1;
131
132         return output_records(outfd);
133 }
134
135 static int process_ihex(uint8_t *data, ssize_t size)
136 {
137         struct ihex_binrec *record;
138         size_t record_size;
139         uint32_t offset = 0;
140         uint32_t data32;
141         uint8_t type, crc = 0, crcbyte = 0;
142         int i, j;
143         int line = 1;
144         int len;
145
146         i = 0;
147 next_record:
148         /* search for the start of record character */
149         while (i < size) {
150                 if (data[i] == '\n') line++;
151                 if (data[i++] == ':') break;
152         }
153
154         /* Minimum record length would be about 10 characters */
155         if (i + 10 > size) {
156                 fprintf(stderr, "Can't find valid record at line %d\n", line);
157                 return -EINVAL;
158         }
159
160         len = hex(data + i, &crc); i += 2;
161         if (wide_records) {
162                 len <<= 8;
163                 len += hex(data + i, &crc); i += 2;
164         }
165         record_size = ALIGN(sizeof(*record) + len, 4);
166         record = malloc(record_size);
167         if (!record) {
168                 fprintf(stderr, "out of memory for records\n");
169                 return -ENOMEM;
170         }
171         memset(record, 0, record_size);
172         record->len = len;
173
174         /* now check if we have enough data to read everything */
175         if (i + 8 + (record->len * 2) > size) {
176                 fprintf(stderr, "Not enough data to read complete record at line %d\n",
177                         line);
178                 return -EINVAL;
179         }
180
181         record->addr  = hex(data + i, &crc) << 8; i += 2;
182         record->addr |= hex(data + i, &crc); i += 2;
183         type = hex(data + i, &crc); i += 2;
184
185         for (j = 0; j < record->len; j++, i += 2)
186                 record->data[j] = hex(data + i, &crc);
187
188         /* check CRC */
189         crcbyte = hex(data + i, &crc); i += 2;
190         if (crc != 0) {
191                 fprintf(stderr, "CRC failure at line %d: got 0x%X, expected 0x%X\n",
192                         line, crcbyte, (unsigned char)(crcbyte-crc));
193                 return -EINVAL;
194         }
195
196         /* Done reading the record */
197         switch (type) {
198         case 0:
199                 /* old style EOF record? */
200                 if (!record->len)
201                         break;
202
203                 record->addr += offset;
204                 file_record(record);
205                 goto next_record;
206
207         case 1: /* End-Of-File Record */
208                 if (record->addr || record->len) {
209                         fprintf(stderr, "Bad EOF record (type 01) format at line %d",
210                                 line);
211                         return -EINVAL;
212                 }
213                 break;
214
215         case 2: /* Extended Segment Address Record (HEX86) */
216         case 4: /* Extended Linear Address Record (HEX386) */
217                 if (record->addr || record->len != 2) {
218                         fprintf(stderr, "Bad HEX86/HEX386 record (type %02X) at line %d\n",
219                                 type, line);
220                         return -EINVAL;
221                 }
222
223                 /* We shouldn't really be using the offset for HEX86 because
224                  * the wraparound case is specified quite differently. */
225                 offset = record->data[0] << 8 | record->data[1];
226                 offset <<= (type == 2 ? 4 : 16);
227                 goto next_record;
228
229         case 3: /* Start Segment Address Record */
230         case 5: /* Start Linear Address Record */
231                 if (record->addr || record->len != 4) {
232                         fprintf(stderr, "Bad Start Address record (type %02X) at line %d\n",
233                                 type, line);
234                         return -EINVAL;
235                 }
236
237                 memcpy(&data32, &record->data[0], sizeof(data32));
238                 data32 = htonl(data32);
239                 memcpy(&record->data[0], &data32, sizeof(data32));
240
241                 /* These records contain the CS/IP or EIP where execution
242                  * starts. If requested output this as a record. */
243                 if (include_jump)
244                         file_record(record);
245                 goto next_record;
246
247         default:
248                 fprintf(stderr, "Unknown record (type %02X)\n", type);
249                 return -EINVAL;
250         }
251
252         return 0;
253 }
254
255 static struct ihex_binrec *records;
256
257 static void file_record(struct ihex_binrec *record)
258 {
259         struct ihex_binrec **p = &records;
260
261         while ((*p) && (!sort_records || (*p)->addr < record->addr))
262                 p = &((*p)->next);
263
264         record->next = *p;
265         *p = record;
266 }
267
268 static uint16_t ihex_binrec_size(struct ihex_binrec *p)
269 {
270         return p->len + sizeof(p->addr) + sizeof(p->len);
271 }
272
273 static int output_records(int outfd)
274 {
275         unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0};
276         struct ihex_binrec *p = records;
277
278         while (p) {
279                 uint16_t writelen = ALIGN(ihex_binrec_size(p), 4);
280
281                 p->addr = htonl(p->addr);
282                 p->len = htons(p->len);
283                 if (write(outfd, &p->addr, writelen) != writelen)
284                         return 1;
285                 p = p->next;
286         }
287         /* EOF record is zero length, since we don't bother to represent
288            the type field in the binary version */
289         if (write(outfd, zeroes, 6) != 6)
290                 return 1;
291         return 0;
292 }