ao-tools/ao-telem: Print out 'log_max' value. Clean up compiler warnings.
[fw/altos] / altoslib / AltosHexfile.java
1 /*
2  * Copyright © 2010 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.altoslib_13;
20
21 import java.io.*;
22 import java.util.LinkedList;
23 import java.util.Arrays;
24
25 class HexFileInputStream extends PushbackInputStream {
26         public int line;
27
28         public HexFileInputStream(FileInputStream o) {
29                 super(new BufferedInputStream(o));
30                 line = 1;
31         }
32
33         public int read() throws IOException {
34                 int     c = super.read();
35                 if (c == '\n')
36                         line++;
37                 return c;
38         }
39
40         public void unread(int c) throws IOException {
41                 if (c == '\n')
42                         line--;
43                 if (c != -1)
44                         super.unread(c);
45         }
46 }
47
48 class HexRecord implements Comparable<Object> {
49         public long     address;
50         public int      type;
51         public byte     checksum;
52         public byte[]   data;
53
54         static final int NORMAL = 0;
55         static final int EOF = 1;
56         static final int EXTENDED_ADDRESS = 2;
57
58         enum read_state {
59                 marker,
60                 length,
61                 address,
62                 type,
63                 data,
64                 checksum,
65                 newline,
66                 white,
67                 done,
68         }
69
70         boolean ishex(int c) {
71                 if ('0' <= c && c <= '9')
72                         return true;
73                 if ('a' <= c && c <= 'f')
74                         return true;
75                 if ('A' <= c && c <= 'F')
76                         return true;
77                 return false;
78         }
79
80         boolean isspace(int c) {
81                 switch (c) {
82                 case ' ':
83                 case '\t':
84                         return true;
85                 }
86                 return false;
87         }
88
89         int fromhex(int c) {
90                 if ('0' <= c && c <= '9')
91                         return c - '0';
92                 if ('a' <= c && c <= 'f')
93                         return c - 'a' + 10;
94                 if ('A' <= c && c <= 'F')
95                         return c - 'A' + 10;
96                 return -1;
97         }
98
99         public byte checksum() {
100                 byte    got = 0;
101
102                 got += data.length;
103                 got += (address >> 8) & 0xff;
104                 got += (address     ) & 0xff;
105                 got += type;
106                 for (int i = 0; i < data.length; i++)
107                         got += data[i];
108                 return (byte) (-got);
109         }
110
111         public int compareTo(Object other) {
112                 HexRecord       o = (HexRecord) other;
113
114                 long diff = address - o.address;
115
116                 if (diff > 0)
117                         return 1;
118                 if (diff < 0)
119                         return -1;
120                 return 0;
121         }
122
123         public String toString() {
124                 return String.format("%04x: %02x (%d)", address, type, data.length);
125         }
126
127         public HexRecord(HexFileInputStream input) throws IOException, EOFException {
128                 read_state      state = read_state.marker;
129                 long            nhexbytes = 0;
130                 long            hex = 0;
131                 int             ndata = 0;
132                 byte            got_checksum;
133
134                 while (state != read_state.done) {
135                         int c = input.read();
136                         if (c < 0 && state != read_state.white && state != read_state.marker)
137                                 throw new IOException(String.format("%d: Unexpected EOF", input.line));
138                         if (c == ' ')
139                                 continue;
140                         switch (state) {
141                         case marker:
142                                 if (c == EOF || c == -1)
143                                         throw new EOFException();
144                                 if (c != ':')
145                                         throw new IOException(String.format ("Missing ':' (got %x)", c));
146                                 state = read_state.length;
147                                 nhexbytes = 2;
148                                 hex = 0;
149                                 break;
150                         case length:
151                         case address:
152                         case type:
153                         case data:
154                         case checksum:
155                                 if(!ishex(c))
156                                         throw new IOException(String.format("Non-hex char '%c'", c));
157                                 hex = hex << 4 | fromhex(c);
158                                 --nhexbytes;
159                                 if (nhexbytes != 0)
160                                         break;
161
162                                 switch (state) {
163                                 case length:
164                                         data = new byte[(int) hex];
165                                         state = read_state.address;
166                                         nhexbytes = 4;
167                                         break;
168                                 case address:
169                                         address = hex;
170                                         state = read_state.type;
171                                         nhexbytes = 2;
172                                         break;
173                                 case type:
174                                         type = (int) hex;
175                                         if (data.length > 0)
176                                                 state = read_state.data;
177                                         else
178                                                 state = read_state.checksum;
179                                         nhexbytes = 2;
180                                         ndata = 0;
181                                         break;
182                                 case data:
183                                         data[ndata] = (byte) hex;
184                                         ndata++;
185                                         nhexbytes = 2;
186                                         if (ndata == data.length)
187                                                 state = read_state.checksum;
188                                         break;
189                                 case checksum:
190                                         checksum = (byte) hex;
191                                         state = read_state.newline;
192                                         break;
193                                 default:
194                                         break;
195                                 }
196                                 hex = 0;
197                                 break;
198                         case newline:
199                                 if (c != '\n' && c != '\r')
200                                         throw new IOException("Missing newline");
201                                 state = read_state.white;
202                                 break;
203                         case white:
204                                 if (!isspace(c)) {
205                                         input.unread(c);
206                                         state = read_state.done;
207                                 }
208                                 break;
209                         case done:
210                                 break;
211                         }
212                 }
213                 got_checksum = checksum();
214                 if (got_checksum != checksum)
215                         throw new IOException(String.format("Invalid checksum (read 0x%02x computed 0x%02x)\n",
216                                                             checksum, got_checksum));
217         }
218 }
219
220 public class AltosHexfile {
221         public long             address;
222         public long             max_address;
223         public byte[]           data;
224         LinkedList<AltosHexsym> symlist = new LinkedList<AltosHexsym>();
225
226         public byte get_byte(long a) {
227                 return data[(int) (a - address)];
228         }
229
230         public int get_u8(long a) {
231                 return ((int) get_byte(a)) & 0xff;
232         }
233
234         public int get_u16(long a) {
235                 return get_u8(a) | (get_u8(a+1) << 8);
236         }
237
238         /* CC1111-based products have the romconfig stuff located
239          * at a fixed address; when the file we load has no symbols,
240          * assume it is one of those and set the symbols appropriately
241          */
242         final static int ao_romconfig_version_addr = 0xa0;
243         final static int ao_romconfig_check_addr = 0xa2;
244         final static int ao_serial_number_addr = 0xa4;
245         final static int ao_radio_cal_addr = 0xa6;
246         final static int ao_usb_descriptors_addr = 0xaa;
247
248         static AltosHexsym[] cc_symbols = {
249                 new AltosHexsym("ao_romconfig_version", ao_romconfig_version_addr),
250                 new AltosHexsym("ao_romconfig_check", ao_romconfig_check_addr),
251                 new AltosHexsym("ao_serial_number", ao_serial_number_addr),
252                 new AltosHexsym("ao_radio_cal", ao_radio_cal_addr),
253                 new AltosHexsym("ao_usb_descriptors", ao_usb_descriptors_addr)
254         };
255
256         static final int AO_USB_DESC_DEVICE             = 1;
257         static final int AO_USB_DESC_STRING             = 3;
258
259         static final int AO_ROMCONFIG_VERSION_INDEX     = 0;
260         static final int AO_ROMCONFIG_CHECK_INDEX       = 1;
261         static final int AO_SERIAL_NUMBER_INDEX         = 2;
262         static final int AO_RADIO_CAL_INDEX             = 3;
263         static final int AO_USB_DESCRIPTORS_INDEX       = 4;
264
265         private void add_cc_symbols() {
266                 for (int i = 0; i < cc_symbols.length; i++)
267                         symlist.add(cc_symbols[i]);
268         }
269
270         public void add_symbol(AltosHexsym symbol) {
271                 symlist.add(symbol);
272         }
273
274         /* Take symbols from another hexfile and duplicate them here */
275         public void add_symbols(AltosHexfile other) {
276                 for (AltosHexsym symbol : other.symlist)
277                         symlist.add(symbol);
278         }
279
280         public AltosHexsym lookup_symbol(String name) {
281                 if (symlist.isEmpty())
282                         add_cc_symbols();
283
284                 for (AltosHexsym symbol : symlist)
285                         if (name.equals(symbol.name))
286                                 return symbol;
287                 return null;
288         }
289
290         private long find_usb_descriptors() {
291                 AltosHexsym     usb_descriptors = lookup_symbol("ao_usb_descriptors");
292                 long            a;
293
294                 if (usb_descriptors == null)
295                         return -1;
296
297                 try {
298                         /* Walk the descriptors looking for the device */
299                         a = usb_descriptors.address;
300                         while (get_u8(a+1) != AO_USB_DESC_DEVICE) {
301                                 int delta = get_u8(a);
302                                 a += delta;
303                                 if (delta == 0 || a >= max_address)
304                                         return -1;
305                         }
306                         return a;
307                 } catch (ArrayIndexOutOfBoundsException ae) {
308                         return -1;
309                 }
310         }
311
312         public AltosUsbId find_usb_id() {
313                 long a = find_usb_descriptors();
314
315                 if (a == -1)
316                         return null;
317
318                 /* Walk the descriptors looking for the device */
319                 while (get_u8(a+1) != AO_USB_DESC_DEVICE) {
320                         int delta = get_u8(a);
321                         a += delta;
322                         if (delta == 0 || a >= max_address)
323                                 return null;
324                 }
325
326                 return new AltosUsbId(get_u16(a + 8),
327                                       get_u16(a + 10));
328         }
329
330         public String find_usb_product() {
331                 long            a = find_usb_descriptors();
332                 int             num_strings;
333                 int             product_string;
334
335                 if (a == -1)
336                         return null;
337
338                 product_string = get_u8(a+15);
339
340                 /* Walk the descriptors looking for the device */
341                 num_strings = 0;
342                 for (;;) {
343                         if (get_u8(a+1) == AO_USB_DESC_STRING) {
344                                 ++num_strings;
345                                 if (num_strings == product_string + 1)
346                                         break;
347                         }
348
349                         int delta = get_u8(a);
350                         a += delta;
351                         if (delta == 0 || a >= max_address)
352                                 return null;
353                 }
354
355                 int product_len = get_u8(a);
356
357                 if (product_len <= 0)
358                         return null;
359
360                 String product = "";
361
362                 for (int i = 0; i < product_len - 2; i += 2) {
363                         int     c = get_u16(a + 2 + i);
364
365                         product += Character.toString((char) c);
366                 }
367
368                 if (AltosLink.debug)
369                         System.out.printf("product %s\n", product);
370
371                 return product;
372         }
373
374         private String make_string(byte[] data, int start, int length) {
375                 String s = "";
376                 for (int i = 0; i < length; i++)
377                         s += (char) data[start + i];
378                 return s;
379         }
380
381         public AltosHexfile(byte[] bytes, long offset) {
382                 data = bytes;
383                 address = offset;
384                 max_address = address + bytes.length;
385         }
386
387         public AltosHexfile(FileInputStream file) throws IOException {
388                 HexFileInputStream      input = new HexFileInputStream(file);
389                 LinkedList<HexRecord>   record_list = new LinkedList<HexRecord>();
390                 boolean                 done = false;
391
392                 while (!done) {
393                         try {
394                                 HexRecord       record = new HexRecord(input);
395
396                                 record_list.add(record);
397                         } catch (EOFException eof) {
398                                 done = true;
399                         }
400                 }
401
402                 long    extended_addr = 0;
403                 long    base = 0;
404                 long    bound = 0;
405                 boolean set = false;
406                 for (HexRecord record : record_list) {
407                         long addr;
408                         switch (record.type) {
409                         case 0:
410                                 addr = extended_addr + record.address;
411                                 long r_bound = addr + record.data.length;
412                                 if (!set || addr < base)
413                                         base = addr;
414                                 if (!set || r_bound > bound)
415                                         bound = r_bound;
416                                 set = true;
417                                 break;
418                         case 1:
419                                 break;
420                         case 2:
421                                 if (record.data.length != 2)
422                                         throw new IOException("invalid extended segment address record");
423                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4;
424                                 break;
425                         case 4:
426                                 if (record.data.length != 2)
427                                         throw new IOException("invalid extended segment address record");
428                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16;
429                                 break;
430                         case 0xfe:
431                                 String name = make_string(record.data, 0, record.data.length);
432                                 addr = extended_addr + record.address;
433                                 AltosHexsym s = new AltosHexsym(name, addr);
434                                 symlist.add(s);
435                                 break;
436                         default:
437                                 throw new IOException ("invalid hex record type");
438                         }
439                 }
440
441                 if (!set || base >= bound)
442                         throw new IOException("invalid hex file");
443
444                 if (bound - base > 4 * 1024 * 1024)
445                         throw new IOException("hex file too large");
446
447                 data = new byte[(int) (bound - base)];
448                 address = base;
449                 max_address = bound;
450                 Arrays.fill(data, (byte) 0xff);
451
452                 /* Paint the records into the new array */
453                 for (HexRecord record : record_list) {
454                         switch (record.type) {
455                         case 0:
456                                 long addr = extended_addr + record.address;
457                                 long r_bound = addr + record.data.length;
458                                 for (int j = 0; j < record.data.length; j++)
459                                         data[(int) (addr - base) + j] = record.data[j];
460                                 break;
461                         case 1:
462                                 break;
463                         case 2:
464                                 if (record.data.length != 2)
465                                         throw new IOException("invalid extended segment address record");
466                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4;
467                                 break;
468                         case 4:
469                                 if (record.data.length != 2)
470                                         throw new IOException("invalid extended segment address record");
471                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16;
472                                 break;
473                         case 0xfe:
474                                 break;
475                         default:
476                                 throw new IOException ("invalid hex record type");
477                         }
478                 }
479         }
480 }