TeleStern/altoslib/AltosHexfile.java

486 lines
11 KiB
Java

/*
* Copyright © 2010 Keith Packard <keithp@keithp.com>
*
* 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<Object> {
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<AltosHexsym> symlist = new LinkedList<AltosHexsym>();
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<HexRecord> record_list = new LinkedList<HexRecord>();
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");
}
}
}
}