123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- #!/usr/bin/python3
- #
- #
- # This Source Code Form is subject to the terms of the MIT License.
- # If a copy of the MIT License was not distributed with this file,
- # you can obtain one at https://opensource.org/licenses/MIT
- #
- import argparse
- import os.path
- from datetime import datetime
- import subprocess
- import signal
- import time
- from prompt_toolkit import prompt
- from prompt_toolkit.contrib.completers import WordCompleter
- from prompt_toolkit.history import InMemoryHistory
- from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
- def auto_int(x):
- return int(x, 0)
- def parse_args():
- parser = argparse.ArgumentParser(
- description='Firmware Extractor User Interface',
- )
- parser.add_argument(
- 'SerialDeviceFILE',
- help='Device File to read from (e.g., /dev/ttyUSB0)'
- )
- parser.add_argument(
- '-i',
- '--interactive',
- action='store_true',
- help='Use interactive mode'
- )
- parser.add_argument(
- '-s',
- '--start',
- type=auto_int,
- default=0x00,
- help='Set start address',
- )
- parser.add_argument(
- '-l',
- '--length',
- type=auto_int,
- default=0x10000,
- help='Number of bytes to extract',
- )
- parser.add_argument(
- '-e',
- '--endianess',
- default='little',
- choices=['little', 'big'],
- help='Set endianess',
- )
- parser.add_argument(
- '-o',
- '--outfile',
- default='data-{}.bin'.format(datetime.now().strftime('%Y%m%d_%H%M')),
- help='Set output file path',
- )
- return parser.parse_args()
- finish = False
- def sigalarm_handler(signo, frame):
- # I want to print the statistics in the end of the program.
- # We can create an infinite loop with this sigalarm_handler,
- # as multiline mode of send_cmd() uses SIGALARM as well.
- # The global variable finish handles this case. And helps
- # us terminate the program when the timeout is fired a second
- # time.
- global finish
- if finish:
- print()
- print('Programm finished.')
- exit()
- print('End of data.')
- print()
- finish = True
- UART('/dev/ttyUSB0').send_cmd('P', True)
- def decode_ascii(s, outfile):
- out = ''
- for i in range(0, len(s), 2):
- char = int(s[i:i+2], 16)
- outfile.write(char.to_bytes(1, 'little'))
- if char > 31 and char < 127:
- out += chr(char)
- else:
- out += '.'
- return out
- def print_error(errcode):
- print()
- print('StatusCode: {:02X}'.format(errcode))
- if errcode == 0x20:
- print('Status OK')
- elif errcode == 0x40:
- print('Wait/Retry requested (bus access was not granted in time)')
- elif errcode == 0x06:
- print('Wait requested + additional OK (previous command OK, but no bus access)')
- elif errcode == 0x80:
- print('Fault during command execution (command error (access denied etc.))')
- elif errcode == 0xA0:
- print('Fault after successful command execution (reading invalid memory address?).')
- elif errcode == 0xE0:
- print('Failure during communication (check connection, no valid status reply received)')
- else:
- print('Unknown status code')
- def _read_enddata(fd):
- buf = ''
- nchar = 0
- state = 'stat'
- print()
- # Print remaining data from uart until timeout is reached.
- while True:
- signal.alarm(1)
- char = fd.read(1)
- signal.alarm(0)
- print(char, end='')
- buf += char
- if char == '!':
- state = 'err'
- if state == 'err':
- if nchar == 25:
- print_error(int(buf[-8:], 16))
- nchar += 1
- def read_ascii(fd, outfile):
- l = 0
- c = 0
- line = ''
- lineraw = ''
- outfile = open(outfile, 'ab', 16)
- while True:
- line += '0x{:08X}: '.format(16*l)
- decoded_line = ''
- while c < 32:
- char = fd.read(1)
- if char == ' ':
- continue
- lineraw += char
- if c % 2 == 0 and c != 0:
- line += ' '
- if c % 16 == 0 and c != 0:
- line += ' '
- # Reached end of data.
- # Flush all buffers and read special stuff at the end.
- if char == '\r' or char == '\n':
- try:
- line += ' |' + decode_ascii(lineraw, outfile) + '|'
- except ValueError:
- pass
- print(line)
- outfile.flush()
- outfile.close()
- _read_enddata(fd)
- c += 1
- line += char
- line += ' |' + decode_ascii(lineraw, outfile) + '|'
- print(line)
- line = ''
- lineraw = ''
- l += 1
- c = 0
- class UART:
- def __init__(self, devnode):
- self.devnode = devnode
- subprocess.call( [
- 'stty',
- '--file={}'.format(self.devnode),
- '115200',
- 'raw',
- '-echok',
- '-echo',
- ])
- def send_cmd(self, code, multiline=False):
- readfd = open(self.devnode, 'r')
- writefd = open(self.devnode, 'w')
- time.sleep(0.1)
- writefd.write(code + '\n')
- while True:
- try:
- if multiline:
- signal.alarm(1)
- char = readfd.read(1)
- signal.alarm(0)
- if not multiline:
- if char == '\r' or char == '\n':
- print()
- break
- print(char, end='')
- except UnicodeDecodeError:
- print('Received garbage from target.')
- print('Is it already running?')
- print('Shutting down...')
- exit(1)
- writefd.close()
- readfd.close()
- def open(self, mode):
- return open(self.devnode, mode)
- class REPL:
- def __init__(self, start=0x00, length=0x10000, mode='bin',
- byteorder='little', devnode='/dev/ttyUSB0'):
- self.history = InMemoryHistory()
- self.promt = '> '
- self.config = {
- 'start': start,
- 'length': length,
- 'byteorder': byteorder,
- 'outfile': 'data-{}.bin'.format(datetime.now().strftime('%Y%m%d_%H%M')),
- }
- self.uart = UART(devnode)
- def handle_cmd(self, cmd, *args):
- if cmd == 'set':
- self.set_config(args[0], args[1])
- elif cmd == 'run':
- self.apply_config()
- self.uart.send_cmd('S')
- fd = self.uart.open('r')
- print()
- read_ascii(fd, self.config['outfile'])
- fd.close()
- elif cmd == 'cmd':
- self.uart.send_cmd(args[0], True)
- elif cmd == 'help':
- self.show_help()
- elif cmd == 'exit':
- print('Exit command received. Leaving...')
- exit(0)
- else:
- print('Error: Unknown command.')
- print("Try 'help'")
- def show_config(self):
- print('# Current Configuration')
- lines = []
- if self.config:
- tpl = ' {:<12}: '
- for key, val in self.config.items():
- if isinstance(val, int):
- fstr = tpl + '0x{:X}'
- else:
- fstr = tpl + '{}'
- lines.append(fstr.format(key, val))
- print('\n'.join(sorted(lines)))
- def show_help(self):
- print('# Supported commands')
- print(' set KEY VAL : Set configuration value KEY to VAL')
- print(' cmd CODE : Send command code to UART')
- print(' run : Start reading out code')
- print(' help : Show this help page')
- print(' exit : Terminate programm')
- def set_config(self, key, val):
- if key == 'byteorder':
- if val not in ('little', 'big'):
- print('Error: Wrong byteorder. Choose "little" or "big".')
- return
- elif key == 'start':
- val = int(val, 0)
- if val > 0x10000:
- print('Error: Start address is too large.')
- return
- elif key == 'length':
- val = int(val, 0)
- elif key == 'outfile':
- self.config[key] = str(val)
- else:
- print('Error: Config key does not exist.')
- return
- self.config[key] = val
- self.show_config()
- def apply_config(self):
- self.uart.send_cmd('A{:X}'.format(self.config['start']))
- self.uart.send_cmd('L{:X}'.format(self.config['length']))
- # Hardcode HEX mode
- self.uart.send_cmd('H')
- cmd = 'e' if self.config['byteorder'] == 'little' else 'E'
- self.uart.send_cmd(cmd)
- def run_loop(self):
- self.show_help()
- print()
- self.show_config()
- try:
- while True:
- cmd = prompt(self.promt,
- history=self.history,
- auto_suggest=AutoSuggestFromHistory(),
- )
- cmd = cmd.strip().split()
- cmd = cmd if cmd else ''
- if cmd != '':
- self.handle_cmd(*cmd)
- except KeyboardInterrupt:
- print('KeyboardInterrupt received. Shutting down...')
- exit(1)
- except EOFError:
- print('Reached end of line. Leaving...')
- exit(0)
- def main():
- args = parse_args()
- signal.signal(signal.SIGALRM, sigalarm_handler)
- if not os.path.exists(args.SerialDeviceFILE):
- print('Error: No such file.')
- exit(1)
- if args.interactive:
- REPL(devnode=args.SerialDeviceFILE).run_loop()
- exit(0)
- # If the script is not in interactive mode, issue this stuff
- # manually.
- uart = UART(args.SerialDeviceFILE)
- uart.send_cmd('A{:X}'.format(args.start))
- uart.send_cmd('L{:X}'.format(args.length))
- uart.send_cmd('H') # Hardcode HEX mode
- if args.endianess == 'big':
- uart.send_cmd('E')
- else:
- uart.send_cmd('e')
- uart.send_cmd('S')
- fd = uart.open('r')
- print()
- try:
- read_ascii(fd, args.outfile)
- except KeyboardInterrupt:
- print('Leaving...')
- finally:
- fd.close()
- if __name__ == '__main__':
- main()
|