summaryrefslogtreecommitdiff
path: root/bootloader
diff options
context:
space:
mode:
authorChristian Pointner <equinox@spreadspace.org>2013-07-09 22:47:06 +0000
committerChristian Pointner <equinox@spreadspace.org>2013-07-09 22:47:06 +0000
commitba7e43c6653ac3a4b464caf7384260eaef5bf4fd (patch)
tree8f8ee3c27edd0b1484ea997b08b8b6ad9f726ae7 /bootloader
parentreenabled downloader commands (diff)
moved downloader to bootloader dir
git-svn-id: https://svn.spreadspace.org/pic/trunk@63 a09c6847-51d9-44de-8ef2-e725cf50f3c7
Diffstat (limited to 'bootloader')
-rwxr-xr-xbootloader/downloader.py353
-rw-r--r--bootloader/proto.txt200
2 files changed, 553 insertions, 0 deletions
diff --git a/bootloader/downloader.py b/bootloader/downloader.py
new file mode 100755
index 0000000..459e0d2
--- /dev/null
+++ b/bootloader/downloader.py
@@ -0,0 +1,353 @@
+#!/usr/bin/python
+#
+# spreadspace pic utils
+#
+#
+# Copyright (C) 2011 Christian Pointner <equinox@spreadspace.org>
+#
+# This file is part of spreadspace pic utils.
+#
+# spreadspace pic utils 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 3 of the License, or
+# any later version.
+#
+# spreadspace pic utils 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 spreadspace pic utils. If not, see <http://www.gnu.org/licenses/>.
+#
+
+'''spreadspace simple pic downloader.'''
+
+VERSION_MAJ = 0
+VERSION_MIN = 1
+
+### HEX File Magic
+
+def load_hex(file):
+ from intelhex import IntelHex
+
+ fin = file
+ if fin == '-':
+ fin = sys.stdin
+ elif not os.path.isfile(fin):
+ print >> sys.stderr, "ERROR: File not found: %s" % fin
+ sys.exit(1)
+
+ codedata = {}
+ ih = IntelHex(fin)
+ for a in ih.addresses():
+ if a/2 not in codedata.keys():
+ codedata[a/2] = 0
+ if a%2 == 0:
+ codedata[a/2] += ih[a]
+ else:
+ codedata[a/2] += (ih[a] << 8)
+
+ return codedata
+
+def write_hex(file, codedata):
+ from intelhex import IntelHex
+
+ fin = file
+ if fin == '-':
+ fin = sys.stdout
+
+ hexdata = {}
+ for a in codedata:
+ hexdata[2*a] = codedata[a] & 0xFF
+ hexdata[2*a+1] = codedata[a] >> 8
+
+ ih = IntelHex()
+ ih.fromdict(hexdata)
+ ih.write_hex_file(fin)
+
+
+def get_lowest_flash_addr(codedata, fss):
+ lowest_code_addr = sorted(codedata.keys())[0]
+ return lowest_code_addr - (lowest_code_addr%fss)
+
+def get_highest_flash_addr(codedata, fss):
+ highest_code_addr = sorted(codedata.keys())[-1]
+ return highest_code_addr + (fss - highest_code_addr%fss)
+
+def create_flash_image(codedata, fss, sa, ea):
+ img = ( sa, [0xFFFF]*(ea-sa) )
+ for a,d in codedata.items():
+ if a < ea:
+ img[1][a-sa] = d
+ return img
+
+def create_flash_segments(codedata, fs, fss):
+ sa = get_lowest_flash_addr(codedata, fss)
+ ea = get_highest_flash_addr(codedata, fss)
+ if ea >= fs:
+ print >> sys.stderr, "WARNING: the hex file contains data after end of flash, these words will be ignored"
+ ea = fs
+
+ img = create_flash_image(codedata, fss, sa, ea)
+ for i in xrange(0, ea, fss):
+ slice = tuple(img[1][i:i+fss])
+ if not all( (elem == 0xFFFF) for elem in slice):
+ yield (i + img[0], slice)
+
+
+### Interface to Bootloader
+
+def open_serial(device, baud):
+ import os
+ import tty
+ import termios
+ import serial
+
+ print >> sys.stderr, "opening %s (%s Baud)" % (device, baud)
+
+ try:
+ dev = serial.Serial(port=device, baudrate=baud, timeout=3)
+ dev.flushInput()
+ dev.flushOutput()
+ return dev
+
+ except (ValueError, serial.SerialException), msg:
+ print >> sys.stderr, "ERROR: opening serial device: %s" % msg
+ sys.exit(3)
+
+def calc_csum(str):
+ cs = 0
+ for c in str:
+ cs ^= c
+ return cs
+
+def exec_command(dev, cmd, param, answer):
+ import struct
+
+ return_codes = { 0: "OK", 1: "invalid command", 2: "bad checksum",
+ 3: "not implemented", 4: "flash write error",
+ 5: "address invalid", 6: "address prohibited",
+ 7: "value out of bounds" }
+
+ dev.flushInput()
+ dev.flushOutput()
+
+ cstr = bytearray(struct.pack('<BB', cmd, len(param)+3) + param)
+ cstr.extend(struct.pack("<B", calc_csum(cstr)))
+ dev.write(cstr)
+
+ astr = bytearray()
+ astr += dev.read(4)
+ if len(astr) < 4:
+ print >> sys.stderr, "ERROR: timeout while reading response header (expected %d bytes, got %d)" % (4, len(astr))
+ sys.exit(4)
+
+ if astr[0] != cstr[0]:
+ print >> sys.stderr, "ERROR: bootloader returned wrong command code"
+ sys.exit(4)
+
+ ret = astr[2]
+ if ret != 0:
+ rstr = "invalid return code"
+ try:
+ rstr = return_codes[ret]
+ except KeyError:
+ pass
+ print >> sys.stderr, "ERROR: bootloader returned %d: %s" % (ret, rstr)
+ sys.exit(4)
+
+ answer_len = astr[1] - 4
+ if answer_len < struct.calcsize(answer):
+ print >> sys.stderr, "ERROR: short answer %d bytes received: expected %s bytes" % (answer_len, struct.calcsize(answer))
+ sys.exit(4)
+
+ if answer_len > 0:
+ tmp = bytearray()
+ tmp += dev.read(answer_len)
+ if len(tmp) < answer_len:
+ print >> sys.stderr, "ERROR: timeout while reading response (expected %d bytes, got %d)" % (answer_len, len(tmp))
+ sys.exit(4)
+
+ astr += tmp
+
+ if 0 != calc_csum(astr):
+ print >> sys.stderr, "ERROR: checksum error"
+ sys.exit(4)
+
+ return struct.unpack_from(answer, astr, 3)
+
+### low level commands
+
+def cmd_identify(dev, name):
+ data = exec_command(dev, 1, '', '<BB3sHHBHBH')
+ id = { 'ver_min': data[0], 'ver_maj': data[1], 'name': data[2], 'devid': data[3],
+ 'fs': data[4], 'fss': data[5], 'es': data[6], 'mess': data[7], 'supported': data[8] }
+
+ if id['ver_maj'] != VERSION_MAJ:
+ print >> sys.stderr, "incompatible protocol version, expected: %d, got: %d" % (VERSION_MAJ, id['ver_maj'])
+ sys.exit(4)
+ if name and id['name'] != name:
+ print >> sys.stderr, "ERROR: the bootloaders name '%s' differs from the one supplied via" % id['name']
+ print >> sys.stderr, " command line option '%s'. Are sure you are connected to the" % name
+ print >> sys.stderr, " right device?"
+ sys.exit(4)
+
+ print >> sys.stderr, "connected with Bootloader '%s' Version %d.%d, (ID=%04X, %d words Flash, FSS=%d, %d bytes EEPROM, MESS=%d)" % \
+ (id['name'], id['ver_maj'], id['ver_min'], id['devid'], id['fs'], id['fss'], id['es'], id['mess'])
+ return id
+
+def cmd_boot(dev):
+ exec_command(dev, 2, '', '<')
+
+def cmd_reset(dev, id):
+ exec_command(dev, 3, '', '<')
+
+def cmd_read_flash_segment(dev, id, addr):
+ param = struct.pack('<H', addr)
+ return exec_command(dev, 4, param, '<%dH' % id['fss'])
+
+def cmd_write_flash_segment(dev, id, addr, data):
+ param = struct.pack('<H%dH' % id['fss'], addr, *data)
+ return exec_command(dev, 5, param, '<')
+
+def cmd_read_eeprom(dev, id, addr, len):
+ param = struct.pack('<HB', addr, len)
+ return exec_command(dev, 6, param, '<%dB' % len)
+
+def cmd_write_eeprom(dev, id, addr, data):
+ param = struct.pack('<HB%dB' % len(data), addr, len(data), *data)
+ return exec_command(dev, 7, param, '<')
+
+def cmd_read_config(dev, id, nr):
+ param = struct.pack('<B', nr)
+ data = exec_command(dev, 8, param, '<H')
+ return data[0]
+
+def cmd_write_config(dev, id, nr, word):
+ param = struct.pack('<BH', nr, word)
+ return exec_command(dev, 9, param, '<')
+
+
+### commands
+def boot(dev, id, file):
+ cmd_boot(dev)
+
+def write_flash(dev, id, file):
+ codedata = load_hex(file)
+ for segment in create_flash_segments(codedata, id['fs'], id['fss']):
+# print >> sys.stderr, "%05X: %s" % (segment[0], ''.join('%04X '%i for i in segment[1]))
+ cmd_write_flash_segment(dev, id, segment[0], segment[1])
+
+def read_flash(dev, id, file):
+ codedata = {}
+ for addr in xrange(0, id['fs'], id['fss']):
+ data = cmd_read_flash_segment(dev, id, addr)
+# print >> sys.stderr, "%05X: %s" % (addr, ''.join('%04X '%i for i in data))
+ a = addr
+ for d in data:
+ codedata[a] = d
+ a += 1
+
+ write_hex(file, codedata)
+
+def verify_flash(dev, id, file):
+ codedata = load_hex(file)
+ err = 0
+ for segment in create_flash_segments(codedata, id['fs'], id['fss']):
+ flashdata = cmd_read_flash_segment(dev, id, segment[0])
+ for file,flash in zip(segment[1] , flashdata):
+ if flash == 0x3FFF:
+ flash = 0xFFFF
+ if flash != file:
+ err = 1
+ break
+
+ if err !=0:
+ break
+
+ if err != 0:
+ print >> sys.stderr, "ERROR: verify failed!"
+ sys.exit(-1)
+ else:
+ print >> sys.stderr, "verify ok!"
+ sys.exit(0)
+
+commands = {
+ 'boot': boot,
+ 'write': write_flash,
+ 'read': read_flash,
+ 'verify': verify_flash
+}
+
+### Main
+
+if __name__ == '__main__':
+ import getopt
+ import sys
+ import os
+ import struct
+
+ usage = '''spreadspace simple pic downloader.
+Usage:
+ python downloader.py [options] INFILE
+
+Arguments:
+ INFILE name of hex file for downloading.
+ Use '-' for reading from stdin.
+
+Options:
+ -h, --help this help message.
+ -v, --version version info.
+ --cmd=N the command to execute, one out of: "boot,read,write,verify"
+ --device=N the serial port to use (default: /dev/ttyUSB0).
+ --baud=N baudrate to use (default: 57600).
+ --name=N the expected name of the bootloader.
+'''
+
+ device = "/dev/ttyUSB0"
+ baudrate = 57600
+ name = None
+ cmd = None
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hv",
+ ["help", "version", "cmd=", "device=", "baud=", "name="])
+
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ print >> sys.stderr, usage
+ sys.exit(0)
+ elif o in ("-v", "--version"):
+ print >> sys.stderr, "Version %d.%d" % (VERSION_MAJ, VERSION_MIN)
+ sys.exit(0)
+ elif o in ("--cmd"):
+ cmd = a
+ elif o in ("--device"):
+ device = a
+ elif o in ("--baud"):
+ baudrate = a
+ elif o in ("--name"):
+ name = a
+
+ if not args:
+ raise getopt.GetoptError('Hex file is not specified')
+
+ if len(args) > 1:
+ raise getopt.GetoptError('Too many arguments')
+
+ if not cmd:
+ raise getopt.GetoptError('You have to supply a command (boot,read,write,verify)')
+
+ except getopt.GetoptError, msg:
+ print >> sys.stderr, "ERROR: %s" % msg
+ print >> sys.stderr, usage
+ sys.exit(2)
+
+ dev = open_serial(device, baudrate)
+ id = cmd_identify(dev, name)
+
+ try:
+ commands[cmd](dev, id, args[0])
+ except KeyError:
+ print >> sys.stderr, "ERROR: unkown command '%s'" % cmd
diff --git a/bootloader/proto.txt b/bootloader/proto.txt
new file mode 100644
index 0000000..0e35e1e
--- /dev/null
+++ b/bootloader/proto.txt
@@ -0,0 +1,200 @@
+Command List:
+=============
+
+ code | command
+ ------+------------
+ 1 | identify
+ 2 | boot
+ 3 | reset
+ 4 | read flash
+ 5 | write flash
+ 6 | read eeprom
+ 7 | write eeprom
+ 8 | read config
+ 9 | write config
+
+
+Description:
+------------
+
+Every command consits of one byte command code, one byte length which counts all
+bytes of the command including the command code and checksum. The length is followed
+by zero or more data and a XOR checksum over all bytes sent. All data is
+transferred LSB first (aka little endian). All addresses and words are 2bytes long.
+Every answer to a command starts with the command code a length field which counts all
+bytes of the answer, one byte return value optionally some data and a checksum
+(XOR over all bytes received).
+The return codes have the following meaning:
+
+ code | Error
+ ------+-------------
+ 0 | OK
+ 1 | invalid command
+ 2 | bad checksum
+ 3 | not implemented
+ 4 | flash write error
+ 5 | address invalid
+ 6 | address prohibited
+ 5 | value out of bounds
+
+identify:
+~~~~~~~~~
+ command:
+ 1 | len=3 | <csum>
+
+ answer:
+ 1 | len=19 | <ret> | version | name | devid | fs | fss | es | mess | supported | <csum>
+
+ version:
+ 2bytes, protocol version
+ It is an error if the major version (MSB) is different (hence any
+ protocol update must change the major version of the bootloader).
+
+ name:
+ 3bytes, a descriptive name of the device.
+ The downloader has to compare this name with the device name supplied
+ via commandline and stop in case they don't match
+
+ devid:
+ 2bytes, device id of the PIC
+ The downlaoder may use this id to check if it talks to the right bootloader
+
+ fs:
+ 2bytes, flash size
+ The size of the flash in code words.
+
+ fss:
+ 1byte, flash segment size
+ The number of words of one flash segment which has to be written at once.
+ If less than <fss> should be updated the downloader has to perform a read
+ operation first.
+
+ es:
+ 2bytes, eeprom size
+ The size of the eeprom in bytes.
+
+ mess:
+ 1byte, maximum eeprom segment size
+ This represents the maximum number of eeprom bytes which may be written or
+ read at once. Unlike <fss> value it is ok to write or read less than <mess>
+ bytes.
+
+ supported:
+ 2bytes, a bitmap showing supported commands
+ The commands 'identify' and 'boot' are always supported by the bootloader,
+ others may not (i.e.: not all PICs allow to update the configurtion words)
+
+ bit | command
+ -----+----------
+ 0 | reset
+ 1 | read flash
+ 2 | write flash
+ 3 | read eeprom
+ 4 | write eeprom
+ 5 | read config
+ 6 | write config
+
+
+boot:
+~~~~~
+
+ command:
+ 2 | len=3 | <csum>
+
+ answer:
+ 2 | len=4 | <ret> | <csum>
+
+ This instucts the bootloader to boot to the user application directly (no reset)
+
+
+reset:
+~~~~~~
+
+ command:
+ 3 | len=3 | <csum>
+
+ answer:
+ 3 | len=4 | <ret> | <csum>
+
+ The device performs a reboot. If the boot condition (i.e.: port pin) is not met
+ this instructs the device to boot to the user application.
+
+
+read flash:
+~~~~~~~~~~~
+
+ command:
+ 4 | len=5 | addr | <csum>
+
+ answer:
+ 4 | len=4+2*<fss> | <ret> | data | <csum>
+
+ The bootloader reads <fss> words from flash address <addr> and returns it as
+ <data>.
+
+
+write flash:
+~~~~~~~~~~~~
+
+ command:
+ 5 | len=5+2*<fss> | addr | data | <csum>
+
+ answer:
+ 5 | len=4 | <ret> | <csum>
+
+ The bootloader writes <data> (which has to contain exactly <fss> words) to address
+ <addr> inside the flash. The start address has to be aligned to <fss> boundaries.
+ Before writing the memory region will be erased. If there are words which shouldn't
+ be altered the downloader has to read these words first and reprogram the whole segment.
+
+
+read eeprom:
+~~~~~~~~~~~~
+
+ command:
+ 6 | len=5 | addr | len | <csum>
+
+ answer:
+ 6 | len=4+<len> | <ret> | data | <csum>
+
+ The bootloader reads <len> bytes from eeprom at address <addr> and returns it as
+ <data>. len is 1byte long.
+
+
+write eeprom:
+~~~~~~~~~~~~~
+
+ command:
+ 7 | len=4+len(data) | addr | data | <csum>
+
+ answer:
+ 7 | len=4 | <ret> | <csum>
+
+ The bootloader writes <data> (which has to contain exactly <len>-4 bytes) to address
+ <addr> inside the eeprom. len is 1byte long and the value must not exceed <mess> bytes.
+
+
+read config:
+~~~~~~~~~~~~
+
+ command:
+ 8 | len=3 | nr | <csum>
+
+ answer:
+ 8 | len=6 | <ret> | word | <csum>
+
+ The bootloader reads and returns the configuration word number <nr>. <nr> is one
+ byte long.
+
+
+write config:
+~~~~~~~~~~~~~
+
+ command:
+ 9 | len=5 | nr | word | <csum>
+
+ answer:
+ 9 | len=4 | <ret> | <csum>
+
+ The bootloader writes <word> onto configuration word number <nr>. <nr> is one
+ byte long.