flashfloppy/scripts/mk_update.py

204 lines
6.2 KiB
Python

# mk_update.py new <output> <firmware> <model>
# mk_update.py old <output> <firmware>
# mk_update.py verify <update_file>
#
# Convert a raw firmware binary into an update file for our bootloader.
#
# New Update Format (Little endian, unless otherwise stated):
# File Header:
# 4 bytes: 'FFUP'
# 4 bytes: <offset to catalogue>
# 4 bytes: <number of catalogue entries>
# Catalogue Entry:
# 1 byte: <hw_model>
# 3 bytes: mbz
# 4 bytes: <offset>
# 4 bytes: <length>
# Catalog Footer:
# 2 bytes: 'FZ'
# 2 bytes: CRC16-CCITT, seed 0xFFFF (big endian)
# Payload Footer:
# 2 bytes: 'FY'
# 2 bytes: CRC16-CCITT, seed 0xFFFF (big endian)
# File Footer:
# 4 bytes: CRC32 (MPEG-2, big endian)
# 4 bytes: 'FFUP'
#
# Old Update Format:
# N bytes: <raw binary data>
# 2 bytes: 'FY'
# 2 bytes: CRC16-CCITT, seed 0xFFFF, stored big endian
#
# Written & released by Keir Fraser <keir.xen@gmail.com>
#
# This is free and unencumbered software released into the public domain.
# See the file COPYING for more details, or visit <http://unlicense.org>.
import crcmod.predefined
import re, struct, sys, os
name_to_hw_model = { 'stm32f105': 1,
'at32f435': 4 }
hw_model_to_name = { 1: 'STM32F105',
4: 'AT32F435' }
class Firmware:
def __init__(self,model,binary):
self.model = model
self.binary = binary
def __str__(self):
s = hw_model_to_name[self.model] + ': '
s += '%d bytes' % len(self.binary)
return s
class Catalog:
def __init__(self, f=None):
self.catalog = []
if f is None:
return
b = f.read()
assert len(b) > 12, 'short header'
# Check the file footer
crc32 = crcmod.predefined.Crc('crc-32-mpeg')
crc32.update(b[:-4])
assert crc32.crcValue == 0, 'bad footer crc32'
assert b[-4:] == b'FFUP', 'bad footer signature'
# Check the file header
sig, off, nr = struct.unpack('<4s2I', b[:12])
assert sig == b'FFUP', 'bad header signature'
assert off == 12, 'unexpected header offset'
header_size = off + nr*12 + 4
assert len(b) >= header_size, 'short header'
# Check the header CRC16
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
crc16.update(b[:header_size])
assert crc16.crcValue == 0
for i in range(nr):
# Read catalog entry and payload
m, o, l = struct.unpack('<B3x2I', b[off+i*12:off+(i+1)*12])
assert len(b) >= o+l, 'payload past end of file'
fw = b[o:o+l]
# Check the payload CRC16
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
crc16.update(fw)
assert crc16.crcValue == 0
# Check the payload footer
sig, = struct.unpack('<2s', fw[-4:-2])
assert sig == b'FY', 'Footer signature must be FY'
# All good: Append to the catalog
self.append(Firmware(m, fw))
def append(self,firmware):
# Models must be uniquely represented in the catalog
for fw in self.catalog:
assert fw.model != firmware.model, 'Model already in catalog'
self.catalog.append(firmware)
def serialise(self):
# Header
b = struct.pack('<4s2I', b'FFUP', 12, len(self.catalog))
# Catalog entries
off = 12 + len(self.catalog)*12 + 4
for firmware in self.catalog:
b += struct.pack('<B3x2I', firmware.model, off,
len(firmware.binary))
off += len(firmware.binary)
# Catalog footer
b += b'FZ'
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
crc16.update(b)
b += struct.pack(">H", crc16.crcValue)
# Payloads
for firmware in self.catalog:
b += firmware.binary
# File footer
crc32 = crcmod.predefined.Crc('crc-32-mpeg')
crc32.update(b)
b += struct.pack(">I4s", crc32.crcValue, b'FFUP')
return b
# New: 'flashfloppy-*.upd'
def new_upd(argv):
# Open the catalog, or else create a new one
try:
with open(argv[0], 'rb') as f:
catalog = Catalog(f)
except FileNotFoundError:
catalog = Catalog()
# Read the new firmware payload
with open(argv[1], 'rb') as f:
b = f.read()
assert (len(b) & 3) == 0, "input is not longword padded"
# Append the payload footer
b += b'FY'
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
crc16.update(b)
b += struct.pack(">H", crc16.crcValue)
# Add the new firmware to the catalog
catalog.append(Firmware(name_to_hw_model[argv[2]], b))
# Rewrite the catalog
with open(argv[0], 'wb') as f:
f.write(catalog.serialise())
# Old: 'FF_Gotek*.upd"
def old_upd(argv):
in_f = open(argv[1], "rb")
out_f = open(argv[0], "wb")
in_dat = in_f.read()
in_len = len(in_dat)
assert (in_len & 3) == 0, "input is not longword padded"
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
out_f.write(in_dat)
crc16.update(in_dat)
in_dat = struct.pack("cc", b'F', b'Y')
out_f.write(in_dat)
crc16.update(in_dat)
in_dat = struct.pack(">H", crc16.crcValue)
out_f.write(in_dat)
def verify_upd(argv):
# Read the file footer to work out type of update file, old vs new
with open(argv[0], 'rb') as f:
f.seek(-4, os.SEEK_END)
sig = f.read(2)
if sig == b'FY':
# Old
print('Old Update File:')
with open(argv[0], 'rb') as f:
b = f.read()
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
crc16.update(b)
assert crc16.crcValue == 0
print(' %s: %d bytes' % (hw_model_to_name[1], len(b)))
else:
# New
print('New Update File:')
with open(argv[0], 'rb') as f:
catalog = Catalog(f)
for firmware in catalog.catalog:
print(' ' + str(firmware))
def main(argv):
if argv[1] == 'new':
dat = new_upd(argv[2:])
elif argv[1] == 'old':
dat = old_upd(argv[2:])
elif argv[1] == 'verify':
verify_upd(argv[2:])
return
else:
assert False
if __name__ == "__main__":
main(sys.argv)
# Local variables:
# python-indent: 4
# End: