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))
|