bootloader/mylab2-programmer.py

210 lines
8.4 KiB
Python
Raw Permalink Normal View History

2022-05-19 08:57:38 -04:00
'''
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))