/* * Copyright © 2010 Keith Packard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package org.altusmetrum.altoslib_14; import java.io.*; import java.util.LinkedList; import java.util.Arrays; class HexFileInputStream extends PushbackInputStream { public int line; public HexFileInputStream(FileInputStream o) { super(new BufferedInputStream(o)); line = 1; } public int read() throws IOException { int c = super.read(); if (c == '\n') line++; return c; } public void unread(int c) throws IOException { if (c == '\n') line--; if (c != -1) super.unread(c); } } class HexRecord implements Comparable { public long address; public int type; public byte checksum; public byte[] data; static final int NORMAL = 0; static final int EOF = 1; static final int EXTENDED_ADDRESS = 2; enum read_state { marker, length, address, type, data, checksum, newline, white, done, } boolean ishex(int c) { if ('0' <= c && c <= '9') return true; if ('a' <= c && c <= 'f') return true; if ('A' <= c && c <= 'F') return true; return false; } boolean isspace(int c) { switch (c) { case ' ': case '\t': return true; } return false; } int fromhex(int c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; return -1; } public byte checksum() { byte got = 0; got += data.length; got += (address >> 8) & 0xff; got += (address ) & 0xff; got += type; for (int i = 0; i < data.length; i++) got += data[i]; return (byte) (-got); } public int compareTo(Object other) { HexRecord o = (HexRecord) other; long diff = address - o.address; if (diff > 0) return 1; if (diff < 0) return -1; return 0; } public String toString() { return String.format("%04x: %02x (%d)", address, type, data.length); } public HexRecord(HexFileInputStream input) throws IOException, EOFException { read_state state = read_state.marker; long nhexbytes = 0; long hex = 0; int ndata = 0; byte got_checksum; while (state != read_state.done) { int c = input.read(); if (c < 0 && state != read_state.white && state != read_state.marker) throw new IOException(String.format("%d: Unexpected EOF", input.line)); if (c == ' ') continue; switch (state) { case marker: if (c == EOF || c == -1) throw new EOFException(); if (c != ':') throw new IOException(String.format ("Missing ':' (got %x)", c)); state = read_state.length; nhexbytes = 2; hex = 0; break; case length: case address: case type: case data: case checksum: if(!ishex(c)) throw new IOException(String.format("Non-hex char '%c'", c)); hex = hex << 4 | fromhex(c); --nhexbytes; if (nhexbytes != 0) break; switch (state) { case length: data = new byte[(int) hex]; state = read_state.address; nhexbytes = 4; break; case address: address = hex; state = read_state.type; nhexbytes = 2; break; case type: type = (int) hex; if (data.length > 0) state = read_state.data; else state = read_state.checksum; nhexbytes = 2; ndata = 0; break; case data: data[ndata] = (byte) hex; ndata++; nhexbytes = 2; if (ndata == data.length) state = read_state.checksum; break; case checksum: checksum = (byte) hex; state = read_state.newline; break; default: break; } hex = 0; break; case newline: if (c != '\n' && c != '\r') throw new IOException("Missing newline"); state = read_state.white; break; case white: if (!isspace(c)) { input.unread(c); state = read_state.done; } break; case done: break; } } got_checksum = checksum(); if (got_checksum != checksum) throw new IOException(String.format("Invalid checksum (read 0x%02x computed 0x%02x)\n", checksum, got_checksum)); } } public class AltosHexfile { public long address; public long max_address; public byte[] data; LinkedList symlist = new LinkedList(); public byte get_byte(long a) { return data[(int) (a - address)]; } public int get_u8(long a) { return ((int) get_byte(a)) & 0xff; } public int get_u16(long a) { return get_u8(a) | (get_u8(a+1) << 8); } /* CC1111-based products have the romconfig stuff located * at a fixed address; when the file we load has no symbols, * assume it is one of those and set the symbols appropriately */ final static int ao_romconfig_version_addr = 0xa0; final static int ao_romconfig_check_addr = 0xa2; final static int ao_serial_number_addr = 0xa4; final static int ao_radio_cal_addr = 0xa6; final static int ao_usb_descriptors_addr = 0xaa; static AltosHexsym[] cc_symbols = { new AltosHexsym("ao_romconfig_version", ao_romconfig_version_addr), new AltosHexsym("ao_romconfig_check", ao_romconfig_check_addr), new AltosHexsym("ao_serial_number", ao_serial_number_addr), new AltosHexsym("ao_radio_cal", ao_radio_cal_addr), new AltosHexsym("ao_usb_descriptors", ao_usb_descriptors_addr) }; static final int AO_USB_DESC_DEVICE = 1; static final int AO_USB_DESC_STRING = 3; static final int AO_ROMCONFIG_VERSION_INDEX = 0; static final int AO_ROMCONFIG_CHECK_INDEX = 1; static final int AO_SERIAL_NUMBER_INDEX = 2; static final int AO_RADIO_CAL_INDEX = 3; static final int AO_USB_DESCRIPTORS_INDEX = 4; private void add_cc_symbols() { for (int i = 0; i < cc_symbols.length; i++) symlist.add(cc_symbols[i]); } public void add_symbol(AltosHexsym symbol) { symlist.add(symbol); } /* Take symbols from another hexfile and duplicate them here */ public void add_symbols(AltosHexfile other) { for (AltosHexsym symbol : other.symlist) symlist.add(symbol); } public AltosHexsym lookup_symbol(String name) { if (symlist.isEmpty()) add_cc_symbols(); for (AltosHexsym symbol : symlist) if (name.equals(symbol.name)) return symbol; return null; } private static final int look_around[] = { 0, -2, 2, -4, 4 }; private long find_usb_descriptors() { AltosHexsym usb_descriptors = lookup_symbol("ao_usb_descriptors"); long a; if (usb_descriptors == null) return -1; /* The address of this has moved depending on padding * in the linker script and romconfig symbols. Look * forward and backwards two and four bytes to see if * we can find it */ a = usb_descriptors.address; for (int look : look_around) { try { if (get_u8(a + look) == 0x12 && get_u8(a + look + 1) == AO_USB_DESC_DEVICE) return a; } catch (ArrayIndexOutOfBoundsException ae) { continue; } } return -1; } public AltosUsbId find_usb_id() { long a = find_usb_descriptors(); if (a == -1) return null; /* Walk the descriptors looking for the device */ while (get_u8(a+1) != AO_USB_DESC_DEVICE) { int delta = get_u8(a); a += delta; if (delta == 0 || a >= max_address) return null; } return new AltosUsbId(get_u16(a + 8), get_u16(a + 10)); } public String find_usb_product() { long a = find_usb_descriptors(); int num_strings; int product_string; if (a == -1) return null; product_string = get_u8(a+15); /* Walk the descriptors looking for the device */ num_strings = 0; for (;;) { if (get_u8(a+1) == AO_USB_DESC_STRING) { ++num_strings; if (num_strings == product_string + 1) break; } int delta = get_u8(a); a += delta; if (delta == 0 || a >= max_address) return null; } int product_len = get_u8(a); if (product_len <= 0) return null; String product = ""; for (int i = 0; i < product_len - 2; i += 2) { int c = get_u16(a + 2 + i); product += Character.toString((char) c); } if (AltosLink.debug) System.out.printf("product %s\n", product); return product; } private String make_string(byte[] data, int start, int length) { String s = ""; for (int i = 0; i < length; i++) s += (char) data[start + i]; return s; } public AltosHexfile(byte[] bytes, long offset) { data = bytes; address = offset; max_address = address + bytes.length; } public AltosHexfile(FileInputStream file) throws IOException { HexFileInputStream input = new HexFileInputStream(file); LinkedList record_list = new LinkedList(); boolean done = false; while (!done) { try { HexRecord record = new HexRecord(input); record_list.add(record); } catch (EOFException eof) { done = true; } } long extended_addr = 0; long base = 0; long bound = 0; boolean set = false; for (HexRecord record : record_list) { long addr; switch (record.type) { case 0: addr = extended_addr + record.address; long r_bound = addr + record.data.length; if (!set || addr < base) base = addr; if (!set || r_bound > bound) bound = r_bound; set = true; break; case 1: break; case 2: if (record.data.length != 2) throw new IOException("invalid extended segment address record"); extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4; break; case 4: if (record.data.length != 2) throw new IOException("invalid extended segment address record"); extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16; break; case 0xfe: String name = make_string(record.data, 0, record.data.length); addr = extended_addr + record.address; AltosHexsym s = new AltosHexsym(name, addr); symlist.add(s); break; default: throw new IOException ("invalid hex record type"); } } if (!set || base >= bound) throw new IOException("invalid hex file"); if (bound - base > 4 * 1024 * 1024) throw new IOException("hex file too large"); data = new byte[(int) (bound - base)]; address = base; max_address = bound; Arrays.fill(data, (byte) 0xff); /* Paint the records into the new array */ for (HexRecord record : record_list) { switch (record.type) { case 0: long addr = extended_addr + record.address; long r_bound = addr + record.data.length; for (int j = 0; j < record.data.length; j++) data[(int) (addr - base) + j] = record.data[j]; break; case 1: break; case 2: if (record.data.length != 2) throw new IOException("invalid extended segment address record"); extended_addr = ((record.data[0] << 8) + (record.data[1])) << 4; break; case 4: if (record.data.length != 2) throw new IOException("invalid extended segment address record"); extended_addr = ((record.data[0] << 8) + (record.data[1])) << 16; break; case 0xfe: break; default: throw new IOException ("invalid hex record type"); } } } }