3 # Copyright (c) 2019-2020 Tilman Sauerbeck (tilman at code-monkey de)
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
31 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
32 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
33 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
34 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
35 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
36 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
37 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
38 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
39 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
40 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
41 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
42 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
43 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
44 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
45 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
46 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
47 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
48 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
49 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
50 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
51 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
52 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
53 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
54 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
55 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
56 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
57 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
58 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
59 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
60 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
61 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
62 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
63 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
64 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
65 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
66 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
67 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
68 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
69 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
70 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
71 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
72 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
79 c = crc32_table[(c & 0xff) ^ b] ^ (c >> 8)
87 def read(self, count):
96 class BootloaderError(Exception):
97 def __init__(self, msg):
98 super(BootloaderError, self).__init__(msg)
100 class UnknownCommand(BootloaderError):
101 def __init__(self, command):
102 msg = 'Command {} not recognized by target'.format(command)
104 super(UnknownCommand, self).__init__(msg)
106 class InvalidArgument(BootloaderError):
107 def __init__(self, command):
108 msg = 'Target rejected arguments for command {}'.format(command)
110 super(InvalidArgument, self).__init__(msg)
112 class ChecksumMismatch(BootloaderError):
113 def __init__(self, command):
114 msg = 'Target reported bad checksum for command {}'.format(command)
116 super(ChecksumMismatch, self).__init__(msg)
118 class PermissionDenied(BootloaderError):
119 def __init__(self, command):
120 msg = 'Target reported permission denied for command {}'.format(command)
122 super(PermissionDenied, self).__init__(msg)
125 def __init__(self, num, name):
135 class EraseCommand(Command):
137 super(EraseCommand, self).__init__(0x49e89a20, 'ERASE')
139 class ProgramCommand(Command):
141 super(ProgramCommand, self).__init__(0x37f7dc8d, 'PROGRAM')
143 class VerifyCommand(Command):
145 super(VerifyCommand, self).__init__(0x4a213efb, 'VERIFY')
147 class LoadChunkCommand(Command):
149 super(LoadChunkCommand, self).__init__(0x1b329768, 'LOAD_CHUNK')
151 class StartAppCommand(Command):
153 super(StartAppCommand, self).__init__(0xd27df1bf, 'START_APP')
159 def __init__(self, args):
160 self._last_command = None
161 self._log_level = args.log_level
164 self._serial = DummySerial()
166 self._serial = serial.Serial(args.device, 115200)
168 self._do_write = args.write
169 self._do_verify = args.verify
170 self._do_start = args.start
172 self._filename = args.filename
173 self._offset = args.offset
179 with open(self._filename, 'rb') as f:
181 chunk = f.read(Application.SECTOR_SIZE)
188 if len(chunks) > Application.MAX_NUM_CHUNKS:
189 sys.stderr.write('File too large.\n')
192 # The bootloader only accepts chunks whose size is word-aligned:
193 num_extra = len(chunks[-1]) & 3
197 chunks[-1] += b'\xff' * num_pad
199 # Defaulting to zero seems too dangerous:
200 if self._offset is None:
203 sector0 = self._offset // Application.SECTOR_SIZE
206 return self._run(sector0, chunks)
207 except BootloaderError as e:
211 def _run(self, sector0, chunks):
213 for i in range(len(chunks)):
216 # The bootloader will refuse to erase the second sector
217 # as it contains the precious flash configuration field.
221 # Write first sector last, to prevent the bootloader from
222 # jumping to partially programmed code.
223 for i, chunk in reversed(list(enumerate(chunks))):
227 self._load_chunk(chunk)
228 self._program(sector)
230 num_verify_errors = 0
233 for i, chunk in enumerate(chunks):
236 self._load_chunk(chunk)
238 if not self._verify(sector):
239 self._log(0, 'Failed to verify sector {} contents.\n'.format(sector))
241 num_verify_errors += 1
243 if self._do_start and num_verify_errors == 0:
246 if num_verify_errors == 0:
251 def _erase(self, sector):
252 self._log(0, 'Erasing sector {}... '.format(sector))
254 self._prepare_command(EraseCommand())
255 self._serial.write(struct.pack('<L', sector))
258 self._read_and_handle_error()
262 def _program(self, sector):
263 self._log(0, 'Programming sector {}... '.format(sector))
265 self._prepare_command(ProgramCommand())
266 self._serial.write(struct.pack('<L', sector))
269 self._read_and_handle_error()
273 def _load_chunk(self, chunk):
274 self._log(3, 'Loading chunk of {} bytes... '.format(len(chunk)))
276 self._prepare_command(LoadChunkCommand())
277 self._serial.write(struct.pack('<L', len(chunk)))
279 self._read_and_handle_error()
281 self._serial.write(chunk)
282 self._serial.write(struct.pack('<L', crc32(chunk)))
284 self._read_and_handle_error()
288 def _verify(self, sector):
289 self._log(0, 'Verifying sector {}... '.format(sector))
291 self._prepare_command(VerifyCommand())
292 self._serial.write(struct.pack('<L', sector))
294 self._read_and_handle_error()
296 response = self._serial.read(8)
297 num_mismatches, first_mismatch = struct.unpack('<LL', response)
299 if num_mismatches == 0:
304 self._log(0, 'FAILED\n')
305 self._log(0, 'Found {} mismatches. first one at 0x{:x}.\n'.format(num_mismatches, first_mismatch))
309 def _start_app(self):
310 self._log(0, 'Starting application... ')
312 self._prepare_command(StartAppCommand())
314 self._read_and_handle_error()
318 def _prepare_command(self, command):
319 self._last_command = command
321 self._serial.write(struct.pack('<L', int(command)))
323 def _read_and_handle_error(self):
324 error_response = self._serial.read(1)
325 e, = struct.unpack('<B', error_response)
331 raise UnknownCommand(self._last_command)
333 raise InvalidArgument(self._last_command)
335 raise ChecksumMismatch(self._last_command)
337 raise PermissionDenied(self._last_command)
339 msg = 'Target reported some other error for {}'.format(self._last_command)
341 raise BootloaderError(msg)
343 def _log(self, required_log_level, s):
344 if self._log_level >= required_log_level:
351 if (o & (Application.SECTOR_SIZE - 1)) != 0:
352 msg = 'offset needs to be multiple of {}'.format(Application.SECTOR_SIZE)
354 raise argparse.ArgumentTypeError(msg)
358 if __name__ == '__main__':
359 parser = argparse.ArgumentParser(description='Flash GPS watch firmware')
361 parser.add_argument('-d', '--device', type=str, required=True)
362 parser.add_argument('-o', '--offset', type=offset, default=None)
363 parser.add_argument('-n', '--dry-run', action='store_true')
364 parser.add_argument('-l', '--log-level', type=int, default=0)
365 parser.add_argument('-w', '--write', action='store_true')
366 parser.add_argument('-v', '--verify', action='store_true')
367 parser.add_argument('-s', '--start', action='store_true')
368 parser.add_argument('filename', type=str, nargs='?', default=None)
370 args = parser.parse_args()
372 if not args.write and not args.verify and not args.start:
373 parser.error('one or more of the following arguments are required: ' +
374 '-w/--write, -v/--verify, -s/--start')
376 if args.filename is None:
378 parser.error('argument -w/--write: expected one argument')
380 parser.error('argument -v/--verify: expected one argument')
382 if args.offset is None:
383 if args.write or args.verify:
384 parser.error('the following arguments are required: -o/--offset')
386 sys.exit(Application(args).run())