d5fa8f5fe1c08b86e99649e09bec6895f5129dcf
[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; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.altoslib_4;
19
20 import java.io.*;
21 import java.util.LinkedList;
22 import java.util.Arrays;
23
24 class HexFileInputStream extends PushbackInputStream {
25         public int line;
26
27         public HexFileInputStream(FileInputStream o) {
28                 super(new BufferedInputStream(o));
29                 line = 1;
30         }
31
32         public int read() throws IOException {
33                 int     c = super.read();
34                 if (c == '\n')
35                         line++;
36                 return c;
37         }
38
39         public void unread(int c) throws IOException {
40                 if (c == '\n')
41                         line--;
42                 if (c != -1)
43                         super.unread(c);
44         }
45 }
46
47 class HexRecord implements Comparable<Object> {
48         public int      address;
49         public int      type;
50         public byte     checksum;
51         public byte[]   data;
52
53         static final int NORMAL = 0;
54         static final int EOF = 1;
55         static final int EXTENDED_ADDRESS = 2;
56
57         enum read_state {
58                 marker,
59                 length,
60                 address,
61                 type,
62                 data,
63                 checksum,
64                 newline,
65                 white,
66                 done,
67         }
68
69         boolean ishex(int c) {
70                 if ('0' <= c && c <= '9')
71                         return true;
72                 if ('a' <= c && c <= 'f')
73                         return true;
74                 if ('A' <= c && c <= 'F')
75                         return true;
76                 return false;
77         }
78
79         boolean isspace(int c) {
80                 switch (c) {
81                 case ' ':
82                 case '\t':
83                         return true;
84                 }
85                 return false;
86         }
87
88         int fromhex(int c) {
89                 if ('0' <= c && c <= '9')
90                         return c - '0';
91                 if ('a' <= c && c <= 'f')
92                         return c - 'a' + 10;
93                 if ('A' <= c && c <= 'F')
94                         return c - 'A' + 10;
95                 return -1;
96         }
97
98         public byte checksum() {
99                 byte    got = 0;
100
101                 got += data.length;
102                 got += (address >> 8) & 0xff;
103                 got += (address     ) & 0xff;
104                 got += type;
105                 for (int i = 0; i < data.length; i++)
106                         got += data[i];
107                 return (byte) (-got);
108         }
109
110         public int compareTo(Object other) {
111                 HexRecord       o = (HexRecord) other;
112                 return address - o.address;
113         }
114
115         public String toString() {
116                 return String.format("%04x: %02x (%d)", address, type, data.length);
117         }
118
119         public HexRecord(HexFileInputStream input) throws IOException, EOFException {
120                 read_state      state = read_state.marker;
121                 int             nhexbytes = 0;
122                 int             hex = 0;
123                 int             ndata = 0;
124                 byte            got_checksum;
125
126                 while (state != read_state.done) {
127                         int c = input.read();
128                         if (c < 0 && state != read_state.white && state != read_state.marker)
129                                 throw new IOException(String.format("%d: Unexpected EOF", input.line));
130                         if (c == ' ')
131                                 continue;
132                         switch (state) {
133                         case marker:
134                                 if (c == EOF || c == -1)
135                                         throw new EOFException();
136                                 if (c != ':')
137                                         throw new IOException(String.format ("Missing ':' (got %x)", c));
138                                 state = read_state.length;
139                                 nhexbytes = 2;
140                                 hex = 0;
141                                 break;
142                         case length:
143                         case address:
144                         case type:
145                         case data:
146                         case checksum:
147                                 if(!ishex(c))
148                                         throw new IOException(String.format("Non-hex char '%c'", c));
149                                 hex = hex << 4 | fromhex(c);
150                                 --nhexbytes;
151                                 if (nhexbytes != 0)
152                                         break;
153
154                                 switch (state) {
155                                 case length:
156                                         data = new byte[hex];
157                                         state = read_state.address;
158                                         nhexbytes = 4;
159                                         break;
160                                 case address:
161                                         address = hex;
162                                         state = read_state.type;
163                                         nhexbytes = 2;
164                                         break;
165                                 case type:
166                                         type = hex;
167                                         if (data.length > 0)
168                                                 state = read_state.data;
169                                         else
170                                                 state = read_state.checksum;
171                                         nhexbytes = 2;
172                                         ndata = 0;
173                                         break;
174                                 case data:
175                                         data[ndata] = (byte) hex;
176                                         ndata++;
177                                         nhexbytes = 2;
178                                         if (ndata == data.length)
179                                                 state = read_state.checksum;
180                                         break;
181                                 case checksum:
182                                         checksum = (byte) hex;
183                                         state = read_state.newline;
184                                         break;
185                                 default:
186                                         break;
187                                 }
188                                 hex = 0;
189                                 break;
190                         case newline:
191                                 if (c != '\n' && c != '\r')
192                                         throw new IOException("Missing newline");
193                                 state = read_state.white;
194                                 break;
195                         case white:
196                                 if (!isspace(c)) {
197                                         input.unread(c);
198                                         state = read_state.done;
199                                 }
200                                 break;
201                         case done:
202                                 break;
203                         }
204                 }
205                 got_checksum = checksum();
206                 if (got_checksum != checksum)
207                         throw new IOException(String.format("Invalid checksum (read 0x%02x computed 0x%02x)\n",
208                                                             checksum, got_checksum));
209         }
210 }
211
212 public class AltosHexfile {
213         public int              address;
214         public byte[]           data;
215         LinkedList<AltosHexsym> symlist = new LinkedList<AltosHexsym>();
216
217         public byte get_byte(int a) {
218                 return data[a - address];
219         }
220
221         /* CC1111-based products have the romconfig stuff located
222          * at a fixed address; when the file we load has no symbols,
223          * assume it is one of those and set the symbols appropriately
224          */
225         final static int ao_romconfig_version_addr = 0xa0;
226         final static int ao_romconfig_check_addr = 0xa2;
227         final static int ao_serial_number_addr = 0xa4;
228         final static int ao_radio_cal_addr = 0xa6;
229         final static int ao_usb_descriptors_addr = 0xaa;
230
231         static AltosHexsym[] cc_symbols = {
232                 new AltosHexsym("ao_romconfig_version", ao_romconfig_version_addr),
233                 new AltosHexsym("ao_romconfig_check", ao_romconfig_check_addr),
234                 new AltosHexsym("ao_serial_number", ao_serial_number_addr),
235                 new AltosHexsym("ao_radio_cal", ao_radio_cal_addr),
236                 new AltosHexsym("ao_usb_descriptors", ao_usb_descriptors_addr)
237         };
238
239         private void add_cc_symbols() {
240                 for (int i = 0; i < cc_symbols.length; i++)
241                         symlist.add(cc_symbols[i]);
242         }
243
244         public void add_symbol(AltosHexsym symbol) {
245                 symlist.add(symbol);
246         }
247
248         /* Take symbols from another hexfile and duplicate them here */
249         public void add_symbols(AltosHexfile other) {
250                 for (AltosHexsym symbol : other.symlist)
251                         symlist.add(symbol);
252         }
253
254         public AltosHexsym lookup_symbol(String name) {
255                 if (symlist.isEmpty())
256                         add_cc_symbols();
257
258                 for (AltosHexsym symbol : symlist)
259                         if (name.equals(symbol.name))
260                                 return symbol;
261                 return null;
262         }
263
264         private String make_string(byte[] data, int start, int length) {
265                 String s = "";
266                 for (int i = 0; i < length; i++)
267                         s += (char) data[start + i];
268                 return s;
269         }
270
271         public AltosHexfile(byte[] bytes, int offset) {
272                 data = bytes;
273                 address = offset;
274         }
275
276         public AltosHexfile(FileInputStream file) throws IOException {
277                 HexFileInputStream      input = new HexFileInputStream(file);
278                 LinkedList<HexRecord>   record_list = new LinkedList<HexRecord>();
279                 boolean                 done = false;
280
281                 while (!done) {
282                         try {
283                                 HexRecord       record = new HexRecord(input);
284
285                                 record_list.add(record);
286                         } catch (EOFException eof) {
287                                 done = true;
288                         }
289                 }
290
291                 long    extended_addr = 0;
292                 long    base = 0;
293                 long    bound = 0;
294                 boolean set = false;
295                 for (HexRecord record : record_list) {
296                         long addr;
297                         switch (record.type) {
298                         case 0:
299                                 addr = extended_addr + record.address;
300                                 long r_bound = addr + record.data.length;
301                                 if (!set || addr < base)
302                                         base = addr;
303                                 if (!set || r_bound > bound)
304                                         bound = r_bound;
305                                 set = true;
306                                 break;
307                         case 1:
308                                 break;
309                         case 2:
310                                 if (record.data.length != 2)
311                                         throw new IOException("invalid extended segment address record");
312                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4;
313                                 break;
314                         case 4:
315                                 if (record.data.length != 2)
316                                         throw new IOException("invalid extended segment address record");
317                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16;
318                                 break;
319                         case 0xfe:
320                                 String name = make_string(record.data, 0, record.data.length);
321                                 addr = extended_addr + record.address;
322                                 AltosHexsym s = new AltosHexsym(name, addr);
323                                 symlist.add(s);
324                                 break;
325                         default:
326                                 throw new IOException ("invalid hex record type");
327                         }
328                 }
329
330                 if (!set || base >= bound)
331                         throw new IOException("invalid hex file");
332
333                 if (bound - base > 4 * 1024 * 1024)
334                         throw new IOException("hex file too large");
335
336                 data = new byte[(int) (bound - base)];
337                 address = (int) base;
338                 Arrays.fill(data, (byte) 0xff);
339
340                 /* Paint the records into the new array */
341                 for (HexRecord record : record_list) {
342                         switch (record.type) {
343                         case 0:
344                                 long addr = extended_addr + record.address;
345                                 long r_bound = addr + record.data.length;
346                                 for (int j = 0; j < record.data.length; j++)
347                                         data[(int) (addr - base) + j] = record.data[j];
348                                 break;
349                         case 1:
350                                 break;
351                         case 2:
352                                 if (record.data.length != 2)
353                                         throw new IOException("invalid extended segment address record");
354                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4;
355                                 break;
356                         case 4:
357                                 if (record.data.length != 2)
358                                         throw new IOException("invalid extended segment address record");
359                                 extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16;
360                                 break;
361                         case 0xfe:
362                                 break;
363                         default:
364                                 throw new IOException ("invalid hex record type");
365                         }
366                 }
367         }
368 }