210 lines
8.4 KiB
Python
210 lines
8.4 KiB
Python
'''
|
||
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 l’effacement', '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))
|