X-Git-Url: http://git.code-monkey.de/?a=blobdiff_plain;f=tools%2Fupdate-firmware;h=306674031a8784291cc5c7d82430fe3a0d7c0f02;hb=c017864cc96e65ced1eece625d41877c0c834f34;hp=76002935f8f6d030f001f1d295015e82acd3261c;hpb=a0037d48ec5b0a60aca8410a3579a7da843e90ff;p=gps-watch.git diff --git a/tools/update-firmware b/tools/update-firmware index 7600293..3066740 100755 --- a/tools/update-firmware +++ b/tools/update-firmware @@ -1,4 +1,25 @@ #!/usr/bin/env python3 +# +# Copyright (c) 2019-2020 Tilman Sauerbeck (tilman at code-monkey de) +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import argparse import serial @@ -94,6 +115,12 @@ class ChecksumMismatch(BootloaderError): super(ChecksumMismatch, self).__init__(msg) +class PermissionDenied(BootloaderError): + def __init__(self, command): + msg = 'Target reported permission denied for command {}'.format(command) + + super(PermissionDenied, self).__init__(msg) + class Command: def __init__(self, num, name): self._num = num @@ -121,6 +148,10 @@ class LoadChunkCommand(Command): def __init__(self): super(LoadChunkCommand, self).__init__(0x1b329768, 'LOAD_CHUNK') +class StartAppCommand(Command): + def __init__(self): + super(StartAppCommand, self).__init__(0xd27df1bf, 'START_APP') + class Application: SECTOR_SIZE = 1024 MAX_NUM_CHUNKS = 256 @@ -136,6 +167,7 @@ class Application: self._do_write = args.write self._do_verify = args.verify + self._do_start = args.start self._filename = args.filename self._offset = args.offset @@ -143,20 +175,32 @@ class Application: def run(self): chunks = [] - with open(self._filename, 'rb') as f: - while True: - chunk = f.read(Application.SECTOR_SIZE) + if self._filename: + with open(self._filename, 'rb') as f: + while True: + chunk = f.read(Application.SECTOR_SIZE) - if len(chunk) == 0: - break + if len(chunk) == 0: + break - chunks.append(chunk) + chunks.append(chunk) - if len(chunks) > Application.MAX_NUM_CHUNKS: - sys.stderr.write('File too large.\n') - return 3 + if len(chunks) > Application.MAX_NUM_CHUNKS: + sys.stderr.write('File too large.\n') + return 3 - sector0 = self._offset // Application.SECTOR_SIZE + # The bootloader only accepts chunks whose size is word-aligned: + num_extra = len(chunks[-1]) & 3 + + if num_extra != 0: + num_pad = 4 - extra + chunks[-1] += b'\xff' * num_pad + + # Defaulting to zero seems too dangerous: + if self._offset is None: + sector0 = None + else: + sector0 = self._offset // Application.SECTOR_SIZE try: return self._run(sector0, chunks) @@ -169,15 +213,19 @@ class Application: for i in range(len(chunks)): sector = sector0 + i - self._erase(sector) + # The bootloader will refuse to erase the second sector + # as it contains the precious flash configuration field. + if sector != 1: + self._erase(sector) # Write first sector last, to prevent the bootloader from # jumping to partially programmed code. for i, chunk in reversed(list(enumerate(chunks))): sector = sector0 + i - self._load_chunk(chunk) - self._program(sector) + if sector != 1: + self._load_chunk(chunk) + self._program(sector) num_verify_errors = 0 @@ -192,6 +240,9 @@ class Application: num_verify_errors += 1 + if self._do_start and num_verify_errors == 0: + self._start_app() + if num_verify_errors == 0: return 0 else: @@ -255,6 +306,15 @@ class Application: return False + def _start_app(self): + self._log(0, 'Starting application... ') + + self._prepare_command(StartAppCommand()) + self._serial.flush() + self._read_and_handle_error() + + self._log(0, 'OK\n') + def _prepare_command(self, command): self._last_command = command @@ -273,6 +333,8 @@ class Application: raise InvalidArgument(self._last_command) elif e == 3: raise ChecksumMismatch(self._last_command) + elif e == 4: + raise PermissionDenied(self._last_command) else: msg = 'Target reported some other error for {}'.format(self._last_command) @@ -297,18 +359,19 @@ if __name__ == '__main__': parser = argparse.ArgumentParser(description='Flash GPS watch firmware') parser.add_argument('-d', '--device', type=str, required=True) - parser.add_argument('-o', '--offset', type=offset, required=True) + parser.add_argument('-o', '--offset', type=offset, default=None) parser.add_argument('-n', '--dry-run', action='store_true') parser.add_argument('-l', '--log-level', type=int, default=0) parser.add_argument('-w', '--write', action='store_true') parser.add_argument('-v', '--verify', action='store_true') + parser.add_argument('-s', '--start', action='store_true') parser.add_argument('filename', type=str, nargs='?', default=None) args = parser.parse_args() - if not args.write and not args.verify: + if not args.write and not args.verify and not args.start: parser.error('one or more of the following arguments are required: ' + - '-w/--write, -v/--verify') + '-w/--write, -v/--verify, -s/--start') if args.filename is None: if args.write: @@ -316,4 +379,8 @@ if __name__ == '__main__': elif args.verify: parser.error('argument -v/--verify: expected one argument') + if args.offset is None: + if args.write or args.verify: + parser.error('the following arguments are required: -o/--offset') + sys.exit(Application(args).run())