bootloader/mylab2-programmer.py

210 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'''
Script de programmation pour le bootloader myLab2 du cours MIP
Christian Abegg, HEPIA
'''
import argparse
import binascii
import sys
try:
import serial
except:
print("pyserial est requis: pip3 install pyserial")
DEFAULT_PORT = '/dev/ttyUSB0'
BL_ERR_CODE = {'0': 'Success', 1: 'Erreur générale', '2': 'Commande invalide',
'3': 'Checksum global incorrect', '4': 'Checksum de bloc incorrect',
'5': 'Erreur lors de leffacement', '6': 'Erreur lors de lécriture',
'7': 'Taille des données invalide', '8': 'Offset invalide',
'9': 'Argument(s) invalide(s)',
'99': 'Pas de réponse'}
LPC1769_PARTID = 0x26113f37
LPC1769_FLASH_SIZE = 0x80000
APP_OFFSET = 0x4000
MAX_PROG_SIZE = LPC1769_FLASH_SIZE - APP_OFFSET
def show_error(error_code):
if error_code != 0:
return "Erreur {} lors de la commande: {}".format(error_code, BL_ERR_CODE.get(error_code, '---'))
def raise_error(error_code):
raise Exception(show_error(error_code))
class MyLab2:
def __init__(self, serialport, verbose=False, timeout=1):
self.v = verbose
self.connected = False
if timeout == -1:
self.timeout = 0
elif timeout == 0:
self.timeout = None
else:
self.timeout = timeout
try:
self.port = serial.Serial(serialport, baudrate=115200, bytesize=8,
stopbits=1, parity=serial.PARITY_NONE, timeout=self.timeout)
# Empty buffer in case some char are left in FTDI device
self.port.timeout = 0.01
for i in range(16):
self.port.read()
self.port.timeout = self.timeout
self.partid = self.get_part_id()
self.serial = self.get_serial()
if self.partid and self.serial:
self.connected = True
except Exception as e:
print("Impossible d'établir la connexion à la carte: {}".format(e))
self.port = None
self.connected = False
if sys.platform.startswith('win') and 'PermissionError' in str(e):
print("Windows refuse l'accès au port série spécifié. "
"Vérifiez que vous n'avez pas d'autres applications qui utilisent ce port.")
def run_cmd(self, cmd, wait_answer=True):
send_buf = "{}\r\n".format(cmd).encode()
if self.v: print(">>> {}".format(send_buf))
self.port.write(send_buf)
self.last_cmd = cmd
if not wait_answer:
return 0, None
retcode = self.port.readline()
if self.v: print("<<< {}".format(retcode))
retcode = retcode.strip().decode()
if retcode == "OK":
# Read answer
if cmd.startswith("GETID") or cmd.startswith("GETSERIAL"):
retvalue = self.port.readline()
if self.v: print("<<< {}".format(retvalue))
return 0, retvalue.strip().decode()
else:
return 0, None
else:
if not retcode:
return '99', None
err = retcode.split(",")[-1]
return err, None
def get_answer(self):
retcode = self.port.readline()
if self.v: print("<<< {}".format(retcode))
retcode = retcode.strip().decode()
if retcode == "OK":
return 0, None
else:
if not retcode:
return '99', None
err = retcode.split(",")[-1]
return err, None
def get_part_id(self):
code, val = self.run_cmd("GETID")
return val
def get_serial(self):
code, val = self.run_cmd("GETSERIAL")
return val
def write_program(self, filename, blocksize=1024, fuzzing=False):
if not self.partid or int(self.partid, 16) != LPC1769_PARTID:
raise Exception("Part id non reconnu: {}".format(self.partid))
try:
with open(filename, 'rb') as file:
bindata = file.read()
except Exception as e:
print("Impossible d'ouvrir le fichier '{}': {}".format(filename, e))
return
if len(bindata) > MAX_PROG_SIZE:
raise Exception("Taille du fichier binaire trop grande: {} (le maximum est de {})".format(len(bindata),
MAX_PROG_SIZE))
binfile_crc = binascii.crc32(bindata)
binfile_length = len(bindata)
nb_blocs = binfile_length//blocksize + 1
print("Envoi {} à la myLab2, taille: {}, CRC32: {}, bs={}".format(filename,
binfile_length,
hex(binfile_crc),
blocksize))
_r, _ = self.run_cmd("PROG,0x{:08x},0x{:08x},0x{:08x}".format(APP_OFFSET, binfile_length, binfile_crc))
if _r:
raise_error(_r)
remaining_size = binfile_length
position = 0
for block in range(nb_blocs):
if remaining_size < blocksize:
blocksize = remaining_size
blockcrc = binascii.crc32(bindata[position:(position+blocksize)])
if self.v:
print("Envoi bloc {:3d}/{:3d} (0x{:08x} à 0x{:08x})".format(block+1, nb_blocs,
position,
position + blocksize - 1))
else:
print("\r{:3d}% ".format(int(position / binfile_length * 100)), end='')
self.run_cmd("DATA,0x{:04x},0x{:08x}".format(blocksize, blockcrc), wait_answer=False)
# send the data
blockdata = bindata[position:(position + blocksize)]
is_fuzz = False
if fuzzing and block == 3:
# TODO: Meilleur fuzzing
blockdata = b'\x02' + bindata[position+1:(position+blocksize)]
is_fuzz = True
self.port.write(blockdata)
_r, _ = self.get_answer()
if _r:
print(show_error(_r))
if is_fuzz:
print("Note: le fuzzing était actif sur ce bloc")
return
if is_fuzz:
print("!!! Fuzzing actif lors de l'envoi de ce bloc, mais la carte a quand même validé le CRC !!!")
# if ok....
position += blocksize
remaining_size -= blocksize
if not self.v:
print("\r{:3d}% ".format(int(position/binfile_length*100)), end='')
if not self.v:
print()
_r, _ = self.run_cmd("CHECK")
if _r:
raise_error(_r)
else:
print("Vérification OK")
def __str__(self):
if not self.connected:
return "Pas de connexion au bootloader myLab2-MIP"
return "Connecté au bootloader myLab2-MIP:\nPartID: {}\nSerial: {}".format(self.partid,
self.serial)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Programmeur pour bootloader myLab2 MIP')
parser.add_argument('--port', default=DEFAULT_PORT,
help='Port série à utiliser pour la communication, "{}" par défaut'.format(DEFAULT_PORT))
parser.add_argument('--fuzzing', action="store_true",
help='Introduit des erreurs lors de la programmation')
parser.add_argument('--verbose', dest="verbose", action="store_true",
help='Mode verbeux')
parser.add_argument('binfile', default='',
help='Programme à écrire, au format bin')
parser.add_argument('--blksize', default=1024, type=int,
help='Taille des blocs lors de l\'envoi')
parser.add_argument('--timeout', default=2, type=int,
help='Durée en secondes après laquelle la réponse doit être reçue. 0=attente infinie')
args = parser.parse_args()
ml2 = MyLab2(args.port, verbose=args.verbose, timeout=args.timeout)
print(ml2)
if not ml2.connected:
sys.exit(1)
try:
ml2.write_program(args.binfile, blocksize=args.blksize, fuzzing=args.fuzzing)
except Exception as e:
print("Erreur lors de la programmation: {}".format(e))