def program(self, args): from intelhex import IntelHex nrf = SetupCommand(args) if args.eraseall: nrf.api.erase_all() if args.sectorsanduicrerase: nrf.api.erase_uicr() hex_file = IntelHex(args.file) for segment in hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr if args.sectorserase or args.sectorsanduicrerase: start_page = int(start_addr / nrf.device.page_size) end_page = int(end_addr / nrf.device.page_size) for page in range(start_page, end_page + 1): nrf.api.erase_page(page * nrf.device.page_size) data = hex_file.tobinarray(start=start_addr, size=(size)) nrf.api.write(start_addr, data.tolist(), True) if args.verify: read_data = nrf.api.read(start_addr, len(data)) assert (self.byte_lists_equal(data, read_data)), 'Verify failed. Data readback from memory does not match data written.' self._reset(nrf, args) nrf.cleanup()
def verify(self, args): board = self._setup() hex_file = IntelHex(args.file) for segment in hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr data = hex_file.tobinarray(start=start_addr, size=size) read_data = board.target.readBlockMemoryUnaligned8(start_addr, size) assert (self.byte_lists_equal(data, read_data)), 'Verify failed. Data readback from memory does not match data written.'
def get_firmware_object(sk_name, hex_file): from ecdsa import SigningKey, NIST256p sk = SigningKey.from_pem(open(sk_name).read()) fw = open(hex_file, 'r').read() fw = base64.b64encode(fw.encode()) fw = to_websafe(fw.decode()) ih = IntelHex() ih.fromfile(hex_file, format='hex') # start of firmware and the size of the flash region allocated for it. # TODO put this somewhere else. START = ih.segments()[0][0] END = (0x08000000 + ((128 - 19) * 2048)) - 8 ih = IntelHex(hex_file) segs = ih.segments() arr = ih.tobinarray(start=START, size=END - START) im_size = END - START print('im_size: ', im_size) print('firmware_size: ', len(arr)) byts = (arr).tobytes() if hasattr(arr, 'tobytes') else (arr).tostring() h = sha256() h.update(byts) sig = binascii.unhexlify(h.hexdigest()) print('hash', binascii.hexlify(sig)) sig = sk.sign_digest(sig) print('sig', binascii.hexlify(sig)) sig = base64.b64encode(sig) sig = to_websafe(sig.decode()) # msg = {'data': read()} msg = {'firmware': fw, 'signature': sig} return msg
def hexloader(hex_file, emu, verbose=False): """ Load a intel hex file into emu's memory using IntelHex """ itx = IntelHex(hex_file) if verbose: print("[x] Loading HEX segments ...") for s_start, s_end in itx.segments(): emu.map_space(s_start, s_end) if verbose: print( f"Writing : {s_start:x} - {len(itx.tobinstr(s_start, s_end-1)):x}" ) emu.emu.mem_write(s_start, itx.tobinstr(s_start, s_end - 1))
def program_file(self, name): if name.lower().endswith(".json"): data = json.loads(open(name, "r").read()) fw = base64.b64decode( helpers.from_websafe(data["firmware"]).encode()) sig = base64.b64decode( helpers.from_websafe(data["signature"]).encode()) ih = IntelHex() tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(fw) tmp.seek(0) tmp.close() ih.fromfile(tmp.name, format="hex") else: if not name.lower().endswith(".hex"): print('Warning, assuming "%s" is an Intel Hex file.' % name) sig = None ih = IntelHex() ih.fromfile(name, format="hex") if self.exchange == self.exchange_hid: chunk = 2048 else: chunk = 240 seg = ih.segments()[0] size = seg[1] - seg[0] total = 0 t1 = time.time() * 1000 print("erasing firmware...") for i in range(seg[0], seg[1], chunk): s = i e = min(i + chunk, seg[1]) data = ih.tobinarray(start=i, size=e - s) self.write_flash(i, data) total += chunk progress = total / float(size) * 100 sys.stdout.write("updating firmware %.2f%%...\r" % progress) sys.stdout.write("updated firmware 100% \r\n") t2 = time.time() * 1000 print("time: %.2f s" % ((t2 - t1) / 1000.0)) if sig is None: sig = b"A" * 64 if self.do_reboot: self.verify_flash(sig) return sig
def verify(self, args): board = self._setup() hex_file = IntelHex(args.file) for segment in hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr data = hex_file.tobinarray(start=start_addr, size=size) read_data = board.target.readBlockMemoryUnaligned8( start_addr, size) assert (self.byte_lists_equal(data, read_data) ), 'Verify failed. Data readback from memory does not match data written.'
def sign_firmware_for_version(sk_name, hex_file, APPLICATION_END_PAGE, PAGES=128): # Maybe this is not the optimal module... import base64 import binascii from hashlib import sha256 from ecdsa import SigningKey from intelhex import IntelHex sk = SigningKey.from_pem(open(sk_name).read()) fw = open(hex_file, "r").read() fw = base64.b64encode(fw.encode()) fw = helpers.to_websafe(fw.decode()) ih = IntelHex() ih.fromfile(hex_file, format="hex") # start of firmware and the size of the flash region allocated for it. # TODO put this somewhere else. START = ih.segments()[0][0] # keep in sync with targets/stm32l432/src/memory_layout.h PAGE_SIZE = 2048 END = (0x08000000 + ((PAGES - APPLICATION_END_PAGE) * PAGE_SIZE)) - 8 ih = IntelHex(hex_file) # segs = ih.segments() arr = ih.tobinarray(start=START, size=END - START) im_size = END - START print("im_size: ", im_size) print("firmware_size: ", len(arr)) byts = (arr).tobytes() if hasattr(arr, "tobytes") else (arr).tostring() h = sha256() h.update(byts) sig = binascii.unhexlify(h.hexdigest()) print("hash", binascii.hexlify(sig)) sig = sk.sign_digest(sig) print("sig", binascii.hexlify(sig)) sig = base64.b64encode(sig) sig = helpers.to_websafe(sig.decode()) # msg = {'data': read()} msg = {"firmware": fw, "signature": sig} return msg
def main(): parser = argparse.ArgumentParser() parser.add_argument("--binary", required=True, type=pathlib.Path) parser.add_argument("--hex", required=True, type=pathlib.Path) parser.add_argument("--lzss_tool", required=True, type=pathlib.Path) option = parser.parse_args() intel_hex = IntelHex(str(option.hex)) ds_segment, xs_segment = intel_hex.segments()[1:3] ds_header = intel_hex.tobinarray( start=ds_segment[0], end=ds_segment[0] + 0x10 - 1) ds_data = intel_hex.tobinarray( start=ds_segment[0] + 0x10, end=ds_segment[1] - 1) xs_data = intel_hex.tobinarray(start=xs_segment[0], end=xs_segment[1] - 1) # Align to 4 bytes ds_data = pad_data(ds_data, 4) xs_data = pad_data(xs_data, 4) # Pad XS data CRC to DS data xs_crc = zlib.crc32(xs_data) ds_data += pack("<L", xs_crc) # Compressed data cx_data = compress_data(option, xs_data, ".xs.bin") cx_crc = zlib.crc32(cx_data) # DS header ds_crc = zlib.crc32(ds_data) pack_into("<LL", ds_header, 0x08, ds_crc, len(ds_data)) # XS header xs_header = ds_header.tobytes() xs_header += pack("<LL", xs_crc, len(xs_data)) xs_header += pack("<LL", cx_crc, len(cx_data)) print("DS: Length 0x{:08x}, CRC 0x{:08x}".format(len(ds_data), ds_crc)) print("XS: Length 0x{:08x}, CRC 0x{:08x}".format(len(xs_data), xs_crc)) print("CX: Length 0x{:08x}, CRC 0x{:08x}".format(len(cx_data), cx_crc)) with open(str(option.binary), mode="wb") as binary: binary.write(ds_header) binary.write(ds_data) binary.write(xs_header) binary.write(cx_data) return 0
def verify(self, args): from intelhex import IntelHex nrf = SetupCommand(args) hex_file = IntelHex(args.file) for segment in hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr data = hex_file.tobinarray(start=start_addr, size=size) read_data = nrf.api.read(start_addr, size) assert (self.byte_lists_equal(data, read_data)), 'Verify failed. Data readback from memory does not match data written.' nrf.cleanup()
def verify(self, args): from intelhex import IntelHex nrf = SetupCommand(args) hex_file = IntelHex(args.file) for segment in hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr data = hex_file.tobinarray(start=start_addr, size=size) read_data = nrf.api.read(start_addr, size) assert (self.byte_lists_equal(data, read_data) ), 'Verify failed. Data readback from memory does not match data written.' nrf.cleanup()
def program_file(self, name): if name.lower().endswith('.json'): data = json.loads(open(name, 'r').read()) fw = base64.b64decode(from_websafe(data['firmware']).encode()) sig = base64.b64decode(from_websafe(data['signature']).encode()) ih = IntelHex() tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(fw) tmp.seek(0) tmp.close() ih.fromfile(tmp.name, format='hex') else: if not name.lower().endswith('.hex'): print('Warning, assuming "%s" is an Intel Hex file.' % name) sig = None ih = IntelHex() ih.fromfile(name, format='hex') if self.exchange == self.exchange_hid: chunk = 2048 else: chunk = 240 seg = ih.segments()[0] size = seg[1] - seg[0] total = 0 t1 = time.time() * 1000 print('erasing...') for i in range(seg[0], seg[1], chunk): s = i e = min(i + chunk, seg[1]) data = ih.tobinarray(start=i, size=e - s) self.write_flash(i, data) total += chunk progress = total / float(size) * 100 sys.stdout.write('downloading %.2f%%...\r' % progress) sys.stdout.write('downloaded 100% \r\n') t2 = time.time() * 1000 print('time: %.2f s' % ((t2 - t1) / 1000.0)) print('Verifying...') if self.do_reboot: if sig is not None: self.verify_flash(sig) else: self.verify_flash(b'A' * 64)
def read_hex_file(filename): """ -Richter 2021 Reads a hex file and generates flashable elements like with a dfu file """ print("File: {}".format(filename)) ih = IntelHex() ih.loadhex(filename) segments = ih.segments() print("Segments:", segments) elements = [] for segId, segment in enumerate(segments): size = segment[1] - segment[0] dat = [ih[i] for i in range(segment[0], segment[1])] elem = {"addr": segment[0], "size": size, "num": segId, "data": dat} elements.append(elem) return elements
def read_hexdata(hex_path): ih = IntelHex(hex_path) ih.padding = 0x00 print(len(ih), MAX_BINLEN, ih.start_addr, ih.minaddr(), ih.maxaddr()) print(ih.segments()) print(ih[0], ih[1], ih[2]) #print([x for x in ih[0:2]]) if (ih[1] == 0x38 and ih[2] == 0x00): print(">>> Fixing hex file") ih[0] = ih[0x37FB] ih[1] = ih[0x37FC] ih[2] = ih[0x37FD] ih[0x37FB] = 0x00; ih[0x37FC] = 0x00 ih[0x37FD] = 0x00 print(ih[0], ih[1], ih[2]) return ih.tobinarray(size=MAX_BINLEN)
def summarize_yaml(fname): print("{:s}file: '{:s}'".format(INLIST, fname)) from intelhex import IntelHex ih = IntelHex(fname) if ih.start_addr: keys = sorted(ih.start_addr.keys()) if keys == ['CS','IP']: entry = ih.start_addr['CS'] * 65536 + ih.start_addr['IP'] elif keys == ['EIP']: entry = ih.start_addr['EIP'] else: raise RuntimeError("unknown 'IntelHex.start_addr' found") print("{:s}entry: 0x{:08X}".format(INDENT, entry)) segments = ih.segments() if segments: print("{:s}data:".format(INDENT)) for s in segments: print("{:s}{:s}{{ first: 0x{:08X}, last: 0x{:08X}, length: 0x{:08X} }}".format(INDENT, INLIST, s[0], s[1]-1, s[1]-s[0])) print("")
def ihexsize(ihexf, granularity=1): ih = IntelHex() ih.loadhex(ihexf) all_sections = ih.segments() low_addr = all_sections[0][0] high_addr = all_sections[0][1] logging.debug("input hex file sections:") for sec in all_sections: logging.debug("0x%08X 0x%08X" % (sec[0], sec[1] - 1)) low_addr = min(low_addr, sec[0]) high_addr = max(high_addr, sec[1]) logging.debug("low_addr =0x%x" % low_addr) logging.debug("high_addr=0x%x" % high_addr) size = high_addr - low_addr part = size % granularity if 0 != part: size += granularity - part return size
def load_state(filename): reg_regex = re.compile(r"^([^=]{2,4})=0x([0-9a-f]+)$") with open(filename, "r") as file: reg_vals = {} for _ in uc_reg_consts: line = file.readline() name, val_str = reg_regex.match(line).groups() val = int(val_str, 16) reg_vals[name] = val mem_segments = {} ih = IntelHex(file) for addr, end in ih.segments(): contents = ih.gets(addr, end - addr) mem_segments[addr] = contents return reg_vals, mem_segments
def summarize_yaml(fname): print("{:s}file: '{:s}'".format(INLIST, fname)) from intelhex import IntelHex ih = IntelHex(fname) if ih.start_addr: keys = ih.start_addr.keys() keys.sort() if keys == ['CS','IP']: entry = ih.start_addr['CS'] * 65536 + ih.start_addr['IP'] elif keys == ['EIP']: entry = ih.start_addr['EIP'] else: raise RuntimeError("unknown 'IntelHex.start_addr' found") print("{:s}entry: 0x{:08X}".format(INDENT, entry)) segments = ih.segments() if segments: print("{:s}data:".format(INDENT)) for s in segments: print("{:s}{:s}{{ first: 0x{:08X}, last: 0x{:08X}, length: 0x{:08X} }}".format(INDENT, INLIST, s[0], s[1]-1, s[1]-s[0])) print("")
def load_firmware_update(fw_update_file): print('loading firmware update ...') # the update file has multiple End Of File records (which is invalid) # so we split the intel hex update file into multiple files hex_strs = [] s = "" with open(fw_update_file, 'r') as f: for l in f.readlines(): s += l # end of file record if l.rstrip() == ':00000001FF': hex_strs.append(s) s = "" assert len(hex_strs) > 0, 'firmware update file has no firmware!' # IntelHex library doesn't like multiple Start Address records # so we only load the boot loader since that is what we need ih = IntelHex(io.StringIO(hex_strs[0])) for s, e in ih.segments(): print(f' {hex(s)}-{hex(e)}') return ih
def verify_flash_from_hex(hex_filename, backend): """ Verify the contents of flash against a hex-file :param filename: Name/path of hex-file to verify :param device_memory_info: DeviceMemoryInfo instance for the device the hex file should be verified against :param backend: Reference to the Backend class of pymcuprog :returns: Boolean value indicating success or failure of the operation """ hexfile = IntelHex(hex_filename) segments = hexfile.segments() for i in range(len(segments)): segment_data = [] for j in range(segments[i][1]-segments[i][0]): segment_data.append(hexfile[segments[i][0]+j]) verify_status = backend.verify_memory(segment_data, 'flash', segments[i][0]) if verify_status is False: return False return True
def install_app(self, app_manifest: AppManifest): hex_file = IntelHex(app_manifest.get_binary()) code_length = hex_file.maxaddr() - hex_file.minaddr() + 1 data_length = app_manifest.data_size code_length -= data_length assert code_length % 64 == 0 # code length must be aligned flags = app_manifest.get_application_flags() # not handled yet params = app_manifest.serialize_parameters() main_address = hex_file.start_addr["EIP"] - hex_file.minaddr() data = struct.pack(">IIIII", code_length, data_length, len(params), flags, main_address) self.apdu_secure_exchange(LedgerSecureIns.CREATE_APP, data) hex_file.puts(hex_file.maxaddr() + 1, params) for segment in hex_file.segments(): self._load_segment(hex_file, segment) self.apdu_secure_exchange(LedgerSecureIns.COMMIT)
async def file_service(): # config r/w for cfg in os.listdir('configs'): if cfg.endswith('.json'): csa['cfgs'].append(cfg) sock = CDWebSocket(ws_ns, 'file') while True: dat, src = await sock.recvfrom() logger.debug(f'file ser: {dat}') if dat['action'] == 'get_cfgs': await sock.sendto(csa['cfgs'], src) elif dat['action'] == 'get_cfg': with open(os.path.join('configs', dat['cfg'])) as c_file: c = json5.load(c_file) await sock.sendto(c, src) elif dat['action'] == 'get_ihex': ret = [] ih = IntelHex() try: ih.loadhex(dat['path']) segs = ih.segments() logger.info( f'parse ihex file, segments: {[list(map(hex, l)) for l in segs]} (end addr inclusive)' ) for seg in segs: s = [seg[0], ih.tobinstr(seg[0], size=seg[1] - seg[0])] ret.append(s) except Exception as err: logger.error(f'parse ihex file error: {err}') await sock.sendto(ret, src) else: await sock.sendto('err: file: unknown cmd', src)
except ValueError: print("Address %s invalid." % address) sys.exit(1) if not os.path.isfile(binfile): print("Unreadable file '%s'." % binfile) sys.exit(1) checkbin(binfile) target.append({ 'address': address, 'data': open(binfile, 'rb').read() }) if options.hexfiles: for hex in options.hexfiles: ih = IntelHex(hex) for (address, end) in ih.segments(): try: address = address & 0xFFFFFFFF except ValueError: print "Address %s invalid." % address sys.exit(1) target.append({ 'address': address, 'data': ih.tobinstr(start=address, end=end - 1) }) outfile = args[0] device = DEFAULT_DEVICE if options.device:
def program_hex_nrf53(self, erase_arg, program_commands): # program_hex() helper for nRF53. # *********************** NOTE ******************************* # self.hex_ can contain code for both the application core and # the network core. # # We can't assume, for example, that # CONFIG_SOC_NRF5340_CPUAPP=y means self.hex_ only contains # data for the app core's flash: the user can put arbitrary # addresses into one of the files in HEX_FILES_TO_MERGE. # # Therefore, on this family, we may need to generate two new # hex files, one for each core, and flash them individually # with the correct '--coprocessor' arguments. # # Kind of hacky, but it works, and nrfjprog is not capable of # flashing to both cores at once. If self.hex_ only affects # one core's flash, then we skip the extra work to save time. # ************************************************************ def add_program_cmd(hex_file, coprocessor): program_commands.append([ 'nrfjprog', '--program', hex_file, erase_arg, '-f', 'NRF53', '--snr', self.dev_id, '--coprocessor', coprocessor ] + self.tool_opt) full_hex = IntelHex() full_hex.loadfile(self.hex_, format='hex') min_addr, max_addr = full_hex.minaddr(), full_hex.maxaddr() # Base address of network coprocessor's flash. From nRF5340 # OPS. We should get this from DTS instead if multiple values # are possible, but this is fine for now. net_base = 0x01000000 if min_addr < net_base <= max_addr: net_hex, app_hex = IntelHex(), IntelHex() for start, stop in full_hex.segments(): segment_hex = net_hex if start >= net_base else app_hex segment_hex.merge(full_hex[start:stop]) hex_path = Path(self.hex_) hex_dir, hex_name = hex_path.parent, hex_path.name net_hex_file = os.fspath(hex_dir / f'GENERATED_CP_NETWORK_{hex_name}') app_hex_file = os.fspath(hex_dir / f'GENERATED_CP_APPLICATION_{hex_name}') self.logger.info( f'{self.hex_} targets both nRF53 coprocessors; ' f'splitting it into: {net_hex_file} and {app_hex_file}') net_hex.write_hex_file(net_hex_file) app_hex.write_hex_file(app_hex_file) add_program_cmd(net_hex_file, 'CP_NETWORK') add_program_cmd(app_hex_file, 'CP_APPLICATION') else: coprocessor = 'CP_NETWORK' if max_addr >= net_base else 'CP_APPLICATION' add_program_cmd(self.hex_, coprocessor)
def program_file(self, name): def parseField(f): return base64.b64decode(helpers.from_websafe(f).encode()) def isCorrectVersion(current, target): """ current is tuple (x,y,z). target is string '>=x.y.z'. Return True if current satisfies the target expression. """ if "=" in target: target = target.split("=") assert target[0] in [">", "<"] target_num = [int(x) for x in target[1].split(".")] assert len(target_num) == 3 comp = target[0] + "=" else: assert target[0] in [">", "<"] target_num = [int(x) for x in target[1:].split(".")] comp = target[0] target_num = ((target_num[0] << 16) | (target_num[1] << 8) | (target_num[2] << 0)) current_num = (current[0] << 16) | (current[1] << 8) | ( current[2] << 0) return eval(str(current_num) + comp + str(target_num)) if name.lower().endswith(".json"): data = json.loads(open(name, "r").read()) fw = parseField(data["firmware"]) sig = None if "versions" in data: current = (0, 0, 0) try: current = self.bootloader_version() except CtapError as e: if e.code == CtapError.ERR.INVALID_COMMAND: pass else: raise (e) for v in data["versions"]: if isCorrectVersion(current, v): print("using signature version", v) sig = parseField(data["versions"][v]["signature"]) break if sig is None: raise RuntimeError( "Improperly formatted firmware file. Could not match version." ) else: sig = parseField(data["signature"]) ih = IntelHex() tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(fw) tmp.seek(0) tmp.close() ih.fromfile(tmp.name, format="hex") else: if not name.lower().endswith(".hex"): print('Warning, assuming "%s" is an Intel Hex file.' % name) sig = None ih = IntelHex() ih.fromfile(name, format="hex") if self.exchange == self.exchange_hid: chunk = 2048 else: chunk = 240 seg = ih.segments()[0] size = seg[1] - seg[0] total = 0 t1 = time.time() * 1000 print("erasing firmware...") for i in range(seg[0], seg[1], chunk): s = i e = min(i + chunk, seg[1]) data = ih.tobinarray(start=i, size=e - s) self.write_flash(i, data) total += chunk progress = total / float(size) * 100 sys.stdout.write("updating firmware %.2f%%...\r" % progress) sys.stdout.write("updated firmware 100% \r\n") t2 = time.time() * 1000 print("time: %.2f s" % ((t2 - t1) / 1000.0)) if sig is None: sig = b"A" * 64 if self.do_reboot: self.verify_flash(sig) return sig
def dfu(serial, connect_attempts, detach, dry_run, firmware): """Program via STMicroelectronics DFU interface. Enter dfu mode using `solo program aux enter-dfu` first. """ import time import usb.core from intelhex import IntelHex dfu = solo.dfu.find(serial, attempts=connect_attempts) if dfu is None: print("No STU DFU device found.") if serial is not None: print("Serial number used: ", serial) sys.exit(1) dfu.init() if not dry_run: # The actual programming # TODO: move to `operations.py` or elsewhere ih = IntelHex() ih.fromfile(firmware, format="hex") chunk = 2048 # Why is this unused # seg = ih.segments()[0] size = sum([max(x[1] - x[0], chunk) for x in ih.segments()]) total = 0 t1 = time.time() * 1000 print("erasing...") try: dfu.mass_erase() except usb.core.USBError: # garbage write, sometimes needed before mass_erase dfu.write_page(0x08000000 + 2048 * 10, "ZZFF" * (2048 // 4)) dfu.mass_erase() page = 0 for start, end in ih.segments(): for i in range(start, end, chunk): page += 1 data = ih.tobinarray(start=i, size=chunk) dfu.write_page(i, data) total += chunk # here and below, progress would overshoot 100% otherwise progress = min(100, total / float(size) * 100) sys.stdout.write( "downloading %.2f%% %08x - %08x ... \r" % (progress, i, i + page)) # time.sleep(0.100) # print('done') # print(dfu.read_mem(i,16)) t2 = time.time() * 1000 print() print("time: %d ms" % (t2 - t1)) print("verifying...") progress = 0 for start, end in ih.segments(): for i in range(start, end, chunk): data1 = dfu.read_mem(i, 2048) data2 = ih.tobinarray(start=i, size=chunk) total += chunk progress = min(100, total / float(size) * 100) sys.stdout.write("reading %.2f%% %08x - %08x ... \r" % (progress, i, i + page)) if (end - start) == chunk: assert data1 == data2 print() print("firmware readback verified.") if detach: dfu.prepare_options_bytes_detach() dfu.detach() print("Please powercycle the device (pull out, plug in again)") hot_patch_windows_libusb()
# System registers # # PEEK_ROM_COMMAND = 6 # # COMMAND 10125 # RESULT_DATA 10126 This is the base address to read peek results from ####################################### # # This is very slow but shows an example of how to peek into the Smartbox ROM and compare result to hex file print("Reading rom hex file") ih = IntelHex("test_rom1.hex") print("Segments found:") print(ih.segments()) numWrites = 0 # number of write chunks. This is used for verifying for segment in ih.segments(): start = segment[0] end = segment[1] if start < 0x1003000: # this is the magic dual partition boot config that should never be changed FBOOT print("Segment: " + str(start) + " - " + str(end)) # in bytes address = start while address < end: fileValue = ih[address] | (ih[address + 1] << 8) | ( ih[address + 2] << 16) # peek in bank 0 (active boot) ROM romAddress = address >> 1
usage = """ %prog {-i|--ihex} file.hex [-i file.hex ...] [{-D|--device}=vendor:device] outfile.dfu""" parser = OptionParser(usage=usage) parser.add_option("-i", "--ihex", action="append", dest="hexfiles", help="build a DFU file from given HEXFILES", metavar="HEXFILES") parser.add_option("-D", "--device", action="store", dest="device", help="build for DEVICE, defaults to %s" % DEFAULT_DEVICE, metavar="DEVICE") (options, args) = parser.parse_args() if options.hexfiles and len(args)==1: target = [] for h in options.hexfiles: ih = IntelHex(h) for (s,e) in ih.segments(): target.append({ 'address': s, 'data': ih.tobinstr(s,e-1) }) outfile = args[0] device = DEFAULT_DEVICE if options.device: device=options.device try: v,d=map(lambda x: int(x,0) & 0xFFFF, device.split(':',1)) except: print("Invalid device '%s'." % device) sys.exit(1) build(outfile,[target],device) else: parser.print_help() sys.exit(1)
def update_device(device, firmware, logger, cancellation_token): """ Updates the specified device with the specified firmware. The device passed to this function can either be in normal mode or in DFU mode. The firmware should be an instance of Firmware or None. If firmware is None, the newest firmware for the device is downloaded from GitHub releases. """ if isinstance(device, usb.core.Device): serial_number = device.serial_number dfudev = DfuDevice(device) if (logger._verbose): logger.debug("OTP:") dump_otp(dfudev) # Read hardware version from one-time-programmable memory otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0] otp_data = dfudev.read_sector(otp_sector) if otp_data[0] == 0: otp_data = otp_data[16:] if otp_data[0] == 0xfe: hw_version = (otp_data[3], otp_data[4], otp_data[5]) else: hw_version = (0, 0, 0) else: serial_number = device.__channel__.usb_device.serial_number dfudev = None # Read hardware version as reported from firmware hw_version_major = device.hw_version_major if hasattr(device, 'hw_version_major') else 0 hw_version_minor = device.hw_version_minor if hasattr(device, 'hw_version_minor') else 0 hw_version_variant = device.hw_version_variant if hasattr(device, 'hw_version_variant') else 0 hw_version = (hw_version_major, hw_version_minor, hw_version_variant) if hw_version < (3, 5, 0): print(" DFU mode is not supported on board version 3.4 or earlier.") print(" This is because entering DFU mode on such a device would") print(" break the brake resistor FETs under some circumstances.") print("Warning: DFU mode is not supported on ODrives earlier than v3.5 unless you perform a hardware mod.") if not odrive.utils.yes_no_prompt("Do you still want to continue?", False): raise OperationAbortedException() fw_version_major = device.fw_version_major if hasattr(device, 'fw_version_major') else 0 fw_version_minor = device.fw_version_minor if hasattr(device, 'fw_version_minor') else 0 fw_version_revision = device.fw_version_revision if hasattr(device, 'fw_version_revision') else 0 fw_version_prerelease = device.fw_version_prerelease if hasattr(device, 'fw_version_prerelease') else True fw_version = (fw_version_major, fw_version_minor, fw_version_revision, fw_version_prerelease) print("Found ODrive {} ({}) with firmware {}{}".format( serial_number, get_hw_version_string(hw_version), get_fw_version_string(fw_version), " in DFU mode" if dfudev is not None else "")) if firmware is None: if hw_version == (0, 0, 0): if dfudev is None: suggestion = 'You have to manually flash an up-to-date firmware to make automatic checks work. Run `odrivetool dfu --help` for more info.' else: suggestion = 'Run "make write_otp" to program the board version.' raise Exception('Cannot check online for new firmware because the board version is unknown. ' + suggestion) print("Checking online for newest firmware...", end='') firmware = get_newest_firmware(hw_version) if firmware is None: raise Exception("could not find any firmware release for this board version") print(" found {}".format(get_fw_version_string(firmware.fw_version))) if firmware.fw_version <= fw_version: print() if firmware.fw_version < fw_version: print("Warning: you are about to flash firmware {} which is older than the firmware on the device ({}).".format( get_fw_version_string(firmware.fw_version), get_fw_version_string(fw_version))) else: print("You are about to flash firmware {} which is the same version as the firmware on the device ({}).".format( get_fw_version_string(firmware.fw_version), get_fw_version_string(fw_version))) if not odrive.utils.yes_no_prompt("Do you want to flash this firmware anyway?", False): raise OperationAbortedException() # load hex file # TODO: Either use the elf format or pack a custom format with a manifest. # This way we can for instance verify the target board version and only # have to publish one file for every board (instead of elf AND hex files). hexfile = IntelHex(firmware.get_as_hex()) logger.debug("Contiguous segments in hex file:") for start, end in hexfile.segments(): logger.debug(" {:08X} to {:08X}".format(start, end - 1)) # Back up configuration if dfudev is None: do_backup_config = device.user_config_loaded if hasattr(device, 'user_config_loaded') else False if do_backup_config: odrive.configuration.backup_config(device, None, logger) elif not odrive.utils.yes_no_prompt("The configuration cannot be backed up because the device is already in DFU mode. The configuration may be lost after updating. Do you want to continue anyway?", True): raise OperationAbortedException() # Put the device into DFU mode if it's not already in DFU mode if dfudev is None: find_odrive_cancellation_token = Event(cancellation_token) put_into_dfu_mode(device, find_odrive_cancellation_token) stm_device = find_device_in_dfu_mode(serial_number, cancellation_token) find_odrive_cancellation_token.set() dfudev = DfuDevice(stm_device) logger.debug("Sectors on device: ") for sector in dfudev.sectors: logger.debug(" {:08X} to {:08X} ({})".format( sector['addr'], sector['addr'] + sector['len'] - 1, sector['name'])) # fill sectors with data touched_sectors = list(populate_sectors(dfudev.sectors, hexfile)) logger.debug("The following sectors will be flashed: ") for sector,_ in touched_sectors: logger.debug(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1)) # Erase try: for i, (sector, data) in enumerate(touched_sectors): print("Erasing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) dfudev.erase_sector(sector) print('Erasing... done \r', end='', flush=True) finally: print('', flush=True) # Flash try: for i, (sector, data) in enumerate(touched_sectors): print("Flashing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) dfudev.write_sector(sector, data) print('Flashing... done \r', end='', flush=True) finally: print('', flush=True) # Verify try: for i, (sector, expected_data) in enumerate(touched_sectors): print("Verifying... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) observed_data = dfudev.read_sector(sector) mismatch_pos = get_first_mismatch_index(observed_data, expected_data) if not mismatch_pos is None: mismatch_pos -= mismatch_pos % 16 observed_snippet = ' '.join('{:02X}'.format(x) for x in observed_data[mismatch_pos:mismatch_pos+16]) expected_snippet = ' '.join('{:02X}'.format(x) for x in expected_data[mismatch_pos:mismatch_pos+16]) raise RuntimeError("Verification failed around address 0x{:08X}:\n".format(sector['addr'] + mismatch_pos) + " expected: " + expected_snippet + "\n" " observed: " + observed_snippet) print('Verifying... done \r', end='', flush=True) finally: print('', flush=True) # If the flash operation failed for some reason, your device is bricked now. # You can unbrick it as long as the device remains powered on. # (or always with an STLink) # So for debugging you should comment this last part out. # Jump to application dfudev.jump_to_application(0x08000000) logger.info("Waiting for the device to reappear...") device = odrive.find_any("usb", serial_number, cancellation_token, cancellation_token, timeout=30) if do_backup_config: odrive.configuration.restore_config(device, None, logger) os.remove(odrive.configuration.get_temp_config_filename(device)) logger.success("Device firmware update successful.")
def use_dfu(args): fw = args.__dict__["[firmware]"] for i in range(0, 8): dfu = DFUDevice() try: dfu.find(ser=args.dfu_serial) except RuntimeError: time.sleep(0.25) dfu = None if dfu is None: print("No STU DFU device found. ") if args.dfu_serial: print("Serial number used: ", args.dfu_serial) sys.exit(1) dfu.init() if fw: ih = IntelHex() ih.fromfile(fw, format="hex") chunk = 2048 seg = ih.segments()[0] size = sum([max(x[1] - x[0], chunk) for x in ih.segments()]) total = 0 t1 = time.time() * 1000 print("erasing...") try: dfu.mass_erase() except usb.core.USBError: dfu.write_page(0x08000000 + 2048 * 10, "ZZFF" * (2048 // 4)) dfu.mass_erase() page = 0 for start, end in ih.segments(): for i in range(start, end, chunk): page += 1 s = i data = ih.tobinarray(start=i, size=chunk) dfu.write_page(i, data) total += chunk progress = total / float(size) * 100 sys.stdout.write( "downloading %.2f%% %08x - %08x ... \r" % (progress, i, i + page)) # time.sleep(0.100) # print('done') # print(dfu.read_mem(i,16)) t2 = time.time() * 1000 print() print("time: %d ms" % (t2 - t1)) print("verifying...") progress = 0 for start, end in ih.segments(): for i in range(start, end, chunk): data1 = dfu.read_mem(i, 2048) data2 = ih.tobinarray(start=i, size=chunk) total += chunk progress = total / float(size) * 100 sys.stdout.write("reading %.2f%% %08x - %08x ... \r" % (progress, i, i + page)) if (end - start) == chunk: assert data1 == data2 print() print("firmware readback verified.") if args.detach: dfu.detach()
def merge_region_list(region_list, destination, notify, config, padding=b'\xFF'): """Merge the region_list into a single image Positional Arguments: region_list - list of regions, which should contain filenames destination - file name to write all regions to padding - bytes to fill gaps with """ merged = IntelHex() _, format = splitext(destination) notify.info("Merging Regions") # Merged file list: Keep track of binary/hex files that we have already # merged. e.g In some cases, bootloader may be split into multiple parts, # but all internally referring to the same bootloader file. merged_list = [] for region in region_list: if region.active and not region.filename: raise ToolException( "Active region has no contents: No file found.") if isinstance(region.filename, list): header_basename, _ = splitext(destination) header_filename = header_basename + "_header.hex" _fill_header(region_list, region).tofile(header_filename, format='hex') region = region._replace(filename=header_filename) if region.filename and (region.filename not in merged_list): notify.info(" Filling region %s with %s" % (region.name, region.filename)) part = intelhex_offset(region.filename, offset=region.start) part.start_addr = None # Normally, we assume that part.maxddr() can be beyond # end of rom. If the size is restricted with config, don't # allow this. if config.target.restrict_size is not None: part_size = (part.maxaddr() - part.minaddr()) + 1 if part_size > region.size: raise ToolException("Contents of region %s does not fit" % region.name) merged_list.append(region.filename) merged.merge(part) elif region.filename in merged_list: notify.info(" Skipping %s as it is merged previously" % (region.name)) # Hex file can have gaps, so no padding needed. While other formats may # need padding. Iterate through segments and pad the gaps. if format != ".hex": # begin patching from the end of the first segment _, begin = merged.segments()[0] for start, stop in merged.segments()[1:]: pad_size = start - begin merged.puts(begin, padding * pad_size) begin = stop + 1 if not exists(dirname(destination)): makedirs(dirname(destination)) notify.info("Space used after regions merged: 0x%x" % (merged.maxaddr() - merged.minaddr() + 1)) merged.tofile(destination, format=format.strip("."))
class nRF5MultiFlash(object): def __init__(self, args): self.nRF5_instances = {} self.erase_all = args.eraseall self.family = args.family self.file = args.file self.sectors_erase = args.sectorserase self.sectors_and_uicr_erase = args.sectorsanduicrerase self.snrs = args.snrs self.systemreset = args.systemreset self.verify = args.verify if not self.family: self.family = 'NRF51' if not self.snrs: with MultiAPI.MultiAPI('NRF51') as nrf: self.snrs = nrf.enum_emu_snr() if self.family is 'NRF51': self.PAGE_SIZE = 0x400 else: self.PAGE_SIZE = 0x1000 self.hex_file = IntelHex(self.file) def _byte_lists_equal(self, data, read_data): for i in xrange(len(data)): if data[i] != read_data[i]: return False return True def _connect_to_device(self, device): self.nRF5_instances[device] = MultiAPI.MultiAPI(self.family) self.nRF5_instances[device].open() self.nRF5_instances[device].connect_to_emu_with_snr(device) def _program_device(self, device): if self.erase_all: self.nRF5_instances[device].recover() # NOTE: using recover() instead of erase_all(). if self.sectors_and_uicr_erase: self.nRF5_instances[device].erase_uicr() for segment in self.hex_file.segments(): start_addr, end_addr = segment size = end_addr - start_addr if self.sectors_erase or self.sectors_and_uicr_erase: start_page = int(start_addr / self.PAGE_SIZE) end_page = int(end_addr / self.PAGE_SIZE) for page in range(start_page, end_page + 1): self.nRF5_instances[device].erase_page(page * self.PAGE_SIZE) data = self.hex_file.tobinarray(start=start_addr, size=(size)) # TODO: this can be optimized. self.nRF5_instances[device].write(start_addr, data.tolist(), True) if self.verify: read_data = self.nRF5_instances[device].read(start_addr, len(data)) assert (self._byte_lists_equal(data, read_data)), 'Verify failed. Data readback from memory does not match data written.' if self.systemreset: self.nRF5_instances[device].sys_reset() self.nRF5_instances[device].go() def _cleanup(self, device): self.nRF5_instances[device].disconnect_from_emu() self.nRF5_instances[device].close() # Public methods. def perform_command(self, device): self._connect_to_device(device) self._program_device(device) self._cleanup(device)
def send_hex(conn, filename, modbus_address, logger=logging): """ Takes the name of a file in Intel hex format, and sends it to the specified Modbus address, then commands the microcontroller to swap to the new ROM bank. The caller must issue a reset command using the reset_microcontroller() function after this function exits, to boot into the new firmware. Note that it's up to the caller to make sure that the firmware in the file matches the hardware on the specified modbus address - otherwise the device will be 'bricked', and need a manual firmware upload in the lab. :param conn: A pasd.transport.Connection() object :param filename: The name of a file containing and Intel Hex format firmware binary :param modbus_address: Modbus address :param logger: A logging.logger object, or defaults to the logging module with basicConfig() called :return: """ logger.info('Writing %s to modbus address %d' % (filename, modbus_address)) if IntelHex is None: logger.critical('intelhex library no available, exiting.') return False # this is a pain. In order to calculate CRC32 we need to give zlib.crc32() an array of bytes # The CRC is calculated for registers ADDRESS_LOW to COMMAND and these are stored in these # 246 bytes least significant byte first. registerBytes = bytearray(246) ####################################### # start by erasing the EEPROM print("Issuing erase command...") registerBytes[244] = ERASE_COMMAND # least sig byte of COMMAND register crc32 = zlib.crc32(registerBytes) for i in range(0, 246): # clear for next calc registerBytes[i] = 0 # write CRC separately to command conn.writeMultReg(modbus_address=modbus_address, regnum=10001, valuelist=[crc32 & 0xffff, crc32 >> 16]) conn.writeReg(modbus_address=modbus_address, regnum=10125, value=ERASE_COMMAND) # , timeout=10.0) logger.debug("Erase return code: " + str(conn.readReg(modbus_address=modbus_address, regnum=10126)[0][1])) # least sig byte # a rom hex file consists of segments which are start/end marker of addresses of bytes to write # PIC24 has 24-bit instructions but addressing is compatible with 16-bit data presumably so increments of # 2 for addressed instructions. # # So, every 4th byte is zero in the hex file logger.info("Reading file %s" % filename) ih = IntelHex(filename) logger.info("Segments found:") logger.info(ih.segments()) numWrites = 0 # number of write chunks. This is used for verifying for segment in ih.segments(): start = segment[0] end = segment[1] if start < 0x1003000: # this is the magic dual partition boot config that should never be changed logger.info("Segment: " + str(start) + " - " + str(end)) # in bytes address = start addressWords = start >> 1 # addresses are in bytes = 4 bytes per instruction. But as far as PIC24 addressing goes this has to be halved while address < end: length = end - address if length > 320: # 320 = 80 "4 byte" instructions which is 240 bytes packed into SEGMENT_DATA length = 320 logger.info("Chunk: " + str(address) + " - " + str(length)) i = 0 j = 4 # there are 2 address registers below here while i < length: registerBytes[j] = ih[address + i] registerBytes[j + 1] = ih[address + i + 1] registerBytes[j + 2] = ih[address + i + 2] i = i + 4 j = j + 3 # word count and set into highcount reg numWords = j // 2 if j & 1 > 0: numWords = numWords + 1 addressLow = addressWords & 0xffff addressHighCount = (addressWords >> 16) | ((numWords - 2) << 8) # the -2 is because we don't count address # mirror address registers in registerBytes registerBytes[0] = addressLow & 0xff registerBytes[1] = addressLow >> 8 registerBytes[2] = addressHighCount & 0xff registerBytes[3] = addressHighCount >> 8 # and the write command registerBytes[244] = WRITE_SEGMENT_COMMAND # least sig byte of COMMAND register # now, calc crc crc32 = zlib.crc32(registerBytes) # and build a list for multiwrite regValues = [crc32 & 0xffff, crc32 >> 16] for i in range(0, numWords): regValues.append(registerBytes[i * 2] + (registerBytes[i * 2 + 1] << 8)) if length < 320: # partial write logger.info("writing partial chunk...") conn.writeMultReg(modbus_address=modbus_address, regnum=10001, valuelist=regValues) conn.writeReg(modbus_address=modbus_address, regnum=10125, value=WRITE_SEGMENT_COMMAND) # write command else: # full write, add on command print("writing chunk... " + str(len(regValues))) # regValues.append(2) conn.writeMultReg(modbus_address=modbus_address, regnum=10001, valuelist=regValues) # ideally, should just append 2 to regValues above but for some reason transport.py hangs with 125 registers # so split into 2 writes - 124 registers and the separate command. conn.writeReg(modbus_address=modbus_address, regnum=10125, value=WRITE_SEGMENT_COMMAND) # write command logger.debug("write return code: " + str(conn.readReg(modbus_address=modbus_address, regnum=10126))) # [0][1])) # least sig byte # one more numWrites = numWrites + 1 # clear for next lop for i in range(0, 246): # clear for next calc registerBytes[i] = 0 # and do the next block address = address + 320 addressWords = addressWords + 160 logger.info(str(numWrites) + " chunks written. Verifying...") # to verify, put numWrites as a 32-bit unsigned int into the first two SEGMENT_DATA registers registerBytes[4] = numWrites & 0xff registerBytes[5] = (numWrites >> 8) & 0xff registerBytes[6] = (numWrites >> 16) & 0xff registerBytes[7] = (numWrites >> 24) & 0xff # set address to zero registerBytes[0] = 0 registerBytes[1] = 0 registerBytes[2] = 0 registerBytes[3] = 0 # and the verify command registerBytes[244] = VERIFY_COMMAND # least sig byte of COMMAND register # now, calc crc crc32 = zlib.crc32(registerBytes) for i in range(0, 246): # clear for next calc registerBytes[i] = 0 # and build a list for multiwrite regValues = [crc32 & 0xffff, crc32 >> 16] regValues.append(0) # empty address x 2 regValues.append(0) regValues.append(numWrites & 0xffff) regValues.append(numWrites >> 16) conn.writeMultReg(modbus_address=modbus_address, regnum=10001, valuelist=regValues) conn.writeReg(modbus_address=modbus_address, regnum=10125, value=VERIFY_COMMAND) # trust but verify verifyResult = conn.readReg(modbus_address=modbus_address, regnum=10126)[0][1] if verifyResult == 0: logger.info("verify ok. Updating.") # the update command registerBytes[244] = UPDATE_COMMAND # least sig byte of COMMAND register # now, calc crc crc32 = zlib.crc32(registerBytes) for i in range(0, 246): # clear for next calc registerBytes[i] = 0 regValues = [crc32 & 0xffff, crc32 >> 16] conn.writeMultReg(modbus_address=modbus_address, regnum=10001, valuelist=regValues) conn.writeReg(modbus_address=modbus_address, regnum=10125, value=UPDATE_COMMAND) # update updateResult = conn.readReg(modbus_address=modbus_address, regnum=10126)[0][1] if updateResult == 0: logger.info("Update ok. Call reset_microcontroller to boot into new firmware.") return True else: logger.info("Update FAILED, new firmware NOT swapped in!: " + str(updateResult)) return False else: logger.info("Verify FAILED, new firmware NOT written!: " + str(verifyResult)) return False
def chunks(seq, size): return (seq[pos:pos + size] for pos in range(0, len(seq), size)) hex = IntelHex(sys.argv[1]) print('READY') fails = 0 write_bytes(0x0000, [0x76]) reset() for (start, stop) in hex.segments(): for chunk in chunks(range(start, stop), 1024): address = chunk[0] data = [hex[a] for a in chunk] print(f'WRITING {len(data)} BYTES AT ADDRESS ${address:0{4}x}') while True: check = write_bytes(address, data) if check == data: break if check is None: print(f"--- COMMAND ERROR - RETRYING ---") elif len(check) != len(data): print(f"--- LENGTH ERROR ({len(check)}) - RETRYING ---") else: print(f"--- VERIFICATION ERROR - RETRYING ---")
) print("Usage:") print("phyihex.py <ihex> <start> <end> [<start> <end>]*") exit() ihexf = sys.argv[1] sections = [] for i in range(2, len(sys.argv), 2): start = int(sys.argv[i], 0) end = int(sys.argv[i + 1], 0) sections.append([start, end + 1]) ih = IntelHex() iho = IntelHex() ih.loadhex(ihexf) all_sections = ih.segments() print("input hex file sections:") for sec in all_sections: print("0x%08X 0x%08X" % (sec[0], sec[1] - 1)) #copy all regular sections for sec in sections: for i in range(sec[0], sec[1]): iho[i] = ih[i] #copy start address #print("start address: ",ih.start_addr) iho.start_addr = ih.start_addr iho.write_hex_file(ihexf + ".phy.ihex")
def dump_header(infile, image, header): inhex = IntelHex(infile) (start, end) = inhex.segments()[0] inhex.tobinfile(image, start=start, end=end - 1) (start, end) = inhex.segments()[-1] inhex.tobinfile(header, start=start, end=end - 1)
from intelhex import IntelHex ih = IntelHex() ih.padding = 0 ih.fromfile("sbos.hex", format="hex") start = ih.segments()[0][0] ih.tobinfile("sbos.bin", start=start, size=8192)