def test_read_mem__uboot(state: dict, script: str): """ Read 512KiB of post-relocated U-Boot code/data to a u-boot.bin file in the resource directory in preparation for test_find_cmd() can be exercised. Populates state['config]', state['uboot_bin_file'] """ _READ_SIZE = 512 * 1024 state['uboot_bin_file'] = os.path.join(state['test_dir'], 'uboot_512K.bin') uboot_addr = state['config']['gd']['bd']['relocaddr']['value'] log.note('Reading 512K of U-Boot code/data to a binary file') args = [ script, '-a', hex(uboot_addr), '-l', str(_READ_SIZE), '-f', state['uboot_bin_file'], '-A', '-R', ] run_script(args)
def test_stratagem(state: dict, script: str): """ Produce a stratagem file to be used with depthcharge-write-mem. Uses state['write_data_file'] as input. """ loadaddr = int(state['config']['env_vars']['loadaddr'], 0) stratagem = os.path.join(state['test_dir'], 'stratagem.json') state['stratagem'] = stratagem stratagem_payload = b'Hello World!' state['stratagem_payload'] = stratagem_payload state['payload_file'] = os.path.join(state['test_dir'], 'payload.bin') save_file(state['payload_file'], stratagem_payload, 'wb') log.note(' Producing CRC32MemoryWriter stratagem') args = [ script, '-a', hex(loadaddr), '-f', state['write_data_file'], '-P', state['payload_file'], '-X', 'revlut_maxlen=512,max_iterations=10000', '-o', stratagem, '-s', 'crc32', ] run(args, check=True)
def test_help(_state: dict, script: str): """ Confirm that the script doesn't explode before argument parsing takes place. """ log.note(' Verifying help results in 0 return status') run([script, '-h'], stdout=DEVNULL, check=True) run([script, '--help'], stdout=DEVNULL, check=True)
def test_read_mem__readback(state: dict, script: str): """ Read back data written by test_write_mem__deploy_stratagem() and verify it. Writes data to a file whose name is stored in state['readback_file'] """ zeros_preceeding = state['write_append_addr'] - state['zeroized_addr'] zeros_following = state['zeroized_len'] - zeros_preceeding - len(state['stratagem_payload']) expected = state['write_data'] expected += b'\x00' * zeros_preceeding expected += state['stratagem_payload'] expected += b'\x00' * zeros_following read_len = len(expected) state['readback_file'] = os.path.join(state['test_dir'], 'readback.bin') loadaddr = int(state['config']['env_vars']['loadaddr'], 0) log.note(' Reading back data at $loadaddr.') args = [ script, '-c', state['config_file'], '-a', hex(loadaddr), '-l', str(read_len), '-f', state['readback_file'], '-D', ] run(args, check=True) readback_data = load_file(state['readback_file'], 'rb') assert readback_data == expected
def test_inspect(state: dict, script: str): """ Exercises depthcharge-inspect. Produces state['config_file'] that is used by later scripts. Loads state['config_file]'. """ # TODO: Still need to exercise -C, -p, -b # Inspect device and produce config file args = [ script, '-c', state['config_file'], '-X', '_unused_value=foo,_unused_bar', '-m', 'file:/dev/null', ] run(args, check=True) # Run again to force loading of config run(args, check=True) # This should trigger a timeout if -P is working; log.note(' Inducing a timeout to test -P ... please wait') env = os.environ.copy() env['DEPTHCHARGE_LOG_LEVEL'] = 'error' result = run(args + ['-P', 'BAD PROMPT >'], text=True, capture_output=True, env=env, check=False) if result.returncode != 1: raise ValueError('Expected returcode = 1, got ' + str(result.returncode)) if 'Timed out' not in result.stderr: raise ValueError('Did not get expected timeout: ' + result.stderr) state['config'] = load_config(state['config_file'])
def find_devices(ctx, buses): results = [] for bus in buses: log.note('Probing bus {:d}'.format(bus)) try: cmd = 'i2c dev {:d}'.format(bus) # Raise an exception on error via check=True resp = ctx.send_command(cmd, check=True) # This may fail for buses (or pinmux settings) that are configured # appropriately. Thus, we drop check=True and just look results resp = ctx.send_command('i2c probe') match = re.match(r'Valid chip addresses: ([0-9a-fA-F\t ]+)', resp) if not match: # A failing bus will spew failures for a while. Keep trying # to interrupt it (Ctrl-C) until we know we're back at a prompt. log.warning('No devices or bus failing. Waiting for prompt.') ctx.interrupt(timeout=120) continue for addr in match.group(1).split(): addr = int(addr, 16) log.info('Found device: Bus={:d}, Address=0x{:02x}'.format( bus, addr)) results.append((bus, addr)) except OperationFailed as error: log.error('Command failed: ' + cmd + os.linesep + str(error)) return results
def test_help(_state: dict, script: str): """ Confirm that the script doesn't explode before argument parsing takes place. """ log.note(' Verifying help results in 0 return status') # No --arch shouldn't matter. Confirm. run_script([script, '-h'], stdout=DEVNULL, arch=None) run_script([script, '--help'], stdout=DEVNULL, arch=None) run_script([script, '-h'], stdout=DEVNULL) run_script([script, '--help'], stdout=DEVNULL)
def get_buses(ctx): buses = [] resp = ctx.send_command('i2c bus') for line in resp.splitlines(): match = re.match(r'Bus (\d+)', line) if match: busno = int(match.group(1)) log.note('Available: Bus {:d}'.format(busno)) buses.append(busno) return buses
def read_nand_to_file(ctx, filename: str, name: str, load_addr: int, nand_addr: int, size: int): # Copy NAND contents to ${loadaddr} cmd = 'nand read 0x{loadaddr:x} 0x{nand_addr:x} 0x{size:x}' cmd = cmd.format(loadaddr=load_addr, nand_addr=nand_addr, size=size) log.info('Copying ' + name + ' to RAM buffer') resp = ctx.send_command(cmd, check=True) log.note('Device response: ' + resp.strip()) log.info('Reading RAM buffer to file: ' + filename) ctx.read_memory_to_file(load_addr, size, filename)
def test_find_cmd(state: dict, script): """ Confirm that a command table is found in state['uboot_bin_file'] for a few different settings. """ image_file = state['uboot_bin_file'] uboot_addr = state['config']['gd']['bd']['relocaddr']['value'] log.note(' Testing default-usage of depthcharge-find-cmd') args = [ script, '-a', hex(uboot_addr), '-f', image_file, ] results = run(args, capture_output=True, text=True, check=True) assert 'Command table @ 0x' in results.stdout assert 'cmd_rep' not in results.stdout log.note(' Testing depthcharge-find-cmd with additional arguments') args = [ script, '-a', hex(uboot_addr), '-A', state['config']['arch'], '-f', image_file, '--details', '--subcmds', '--threshold', '6' ] results = run(args, capture_output=True, text=True, check=True) assert 'Command table @ 0x' in results.stdout assert 'cmd_rep' in results.stdout log.note(' Testing depthcharge-find-cmd with --longhelp and --autocomplete') args = [ script, '-a', hex(uboot_addr), '-A', state['config']['arch'], '-f', image_file, '--longhelp', 'Y', '--autocomplete', 'Y' ] results = run(args, capture_output=True, text=True, check=True) assert 'Command table @ 0x' in results.stdout assert 'cmd_rep' not in results.stdout log.note(' Testing depthcharge-find-cmd with incorrect --longhelp and --autocomplete') args = [ script, '-a', hex(uboot_addr), '-A', state['config']['arch'], '-f', image_file, '--longhelp', 'N', '--autocomplete', 'N' ] results = run(args, capture_output=True, text=True, check=True) assert len(results.stdout) == 0
def test_print(state: dict, script: str): """ Exercise depthcharge-print. Requires state['config_file']. """ items = ('all', 'arch', 'commands', 'commands=detail', 'env', 'env=expand', 'gd', 'version') output_dir = create_resource_dir(os.path.join(state['test_dir'], 'print')) for item in items: args = [script, '-c', state['config_file'], '-i', item] filename = os.path.join(output_dir, item.replace(':', '_')) log.note(' Printing ' + item + ' > ' + filename) with open(filename, 'w') as outfile: run_script(args, stdout=outfile)
def test_write_mem__deploy_stratagem(state: dict, script: str): """ Append to our write payload using a Stratagem produced by CRC32MemoryWriter. Uses state['stratagem'] and state['stratagem_payload'] produced by test_stratagem. """ loadaddr = int(state['config']['env_vars']['loadaddr'], 0) target_addr = loadaddr + len(state['write_data']) # We'll begin zeroizing memory here to <this address> + 32 bytes # The deployed payload will land within this region zero_addr = target_addr state['zeroized_addr'] = zero_addr # Align on an 8-byte boundary to ensure this works across architectures... target_addr = (target_addr + 7) // 8 * 8 state['write_append_addr'] = target_addr # This just aims to ensure we're working from a clean memory state, such # that a previously run successful test test doesn't result in a false # negative for a failure in the current test to actually write any data. log.note(' Zeroizing target memory') state['zeroized_len'] = 32 args = [ script, '-c', state['config_file'], '-a', hex(zero_addr), '-d', '00' * state['zeroized_len'], ] run(args, check=True) log.note(' Executing CRC32MemoryWriter stratagem') args = [ script, '-c', state['config_file'], '-a', hex(target_addr), '-s', state['stratagem'], ] run(args, check=True)
def test_read_mem__readback(state: dict, script: str): """ Read back data written by test_write_mem__deploy_stratagem() and verify it. Writes data to a file whose name is stored in state['readback_file'] """ zeros_preceeding = state['write_append_addr'] - state['zeroized_addr'] zeros_following = state['zeroized_len'] - zeros_preceeding - len( state['stratagem_payload']) expected = b'\x00' * zeros_preceeding + state[ 'stratagem_payload'] + b'\x00' * zeros_following read_len = len(expected) expected_file = os.path.join(state['test_dir'], 'readback.expected.bin') with open(expected_file, 'wb') as outfile: outfile.write(expected) state['readback_file'] = os.path.join(state['test_dir'], 'readback.bin') addr = state['zeroized_addr'] log.note(' Reading back data at $loadaddr.') args = [ script, '-c', state['config_file'], '-a', hex(addr), '-l', str(read_len), '-f', state['readback_file'], '-S', '-R', ] run_script(args) readback_data = load_file(state['readback_file'], 'rb') assert readback_data == expected
def validate_requirements(ctx): REQUIRED_CMDS = ('nand', ) log.note('Checking for required target commands.') cmds = ctx.commands() for cmd in REQUIRED_CMDS: if cmd not in cmds: msg = 'Command not present in target environment: ' + cmd raise ValueError(msg) REQUIRED_VARS = ('loadaddr', 'dtbimage', 'dtb_offset', 'dtb_size', 'image', 'kernel_offset', 'kernel_size') log.note('Checking for required target environment variables.') env = ctx.environment() for var in REQUIRED_VARS: if var not in env: msg = 'Variable not present in target environment: ' + var raise ValueError(msg) return env
def load_resource(filename: str, load_file_func, test_dir='', test_subdir='', msg_pfx=''): """ Load a resource file a test-specific specific directory and subdirectory. `load_file_func` should take a single filename argument. This function returns that of `load_file_func()`. """ resource_dir = create_resource_dir(test_dir, test_subdir) file_path = path.join(resource_dir, filename) ret = load_file_func(file_path) if msg_pfx is not None: if msg_pfx == '': msg_pfx = 'Loaded resource from prior test: {:s}' log.note(msg_pfx.format(file_path)) return ret
def run_tests(ctx, reader, writer, address): report = [] log.info(' Currently testing: {} / {}'.format(reader.name, writer.name)) for (data, pattern_name) in test_cases(): try: if isinstance(writer, StratagemMemoryWriter): stratagem = setup_stratagem(ctx, data, pattern_name, writer) msg = ' Using {:d}-entry stratagem for {:d}-byte payload with {:s}' msg = msg.format(len(stratagem), len(data), pattern_name.replace('_', ' ')) else: msg = ' Using {:d}-byte payload with {:s}' msg = msg.format(len(data), pattern_name.replace('_', ' ')) stratagem = None log.note(msg) result = run_test(ctx, reader, writer, address, data, stratagem) except (StratagemCreationFailed, OperationAlignmentError): result = 'Skipped (Alignment)' entry = {'size': len(data), 'pattern': pattern_name, 'status': result} report.append(entry) return report
def locate_scripts() -> dict: """ Return full paths to each depthcharge script. Dict keys are the script names, with the 'depthcharge-' prefix removed. Values are the full paths to the script. """ script_dir = realpath(os.path.join(_THIS_DIR, '../../scripts')) log.note('Searching for scripts in: ' + script_dir) ret = {} for root, _, files in os.walk(script_dir): for filename in files: script = os.path.join(root, filename) key = basename(script).replace('depthcharge-', '') ret[key] = script for key in _EXPECTED: if key not in ret: raise FileNotFoundError('Failed to locate depthcharge-' + key) return ret
log.info(msg) # Give the user a moment to digest this countdown = ctx.create_progress_indicator('countdown', 5, desc='Delaying') for _ in range(0, 5): time.sleep(1) countdown.update(1) ctx.close_progress_indicator(countdown) # Close the serial console we were using ctx.console.close() # Now re-inspect the device in its unlocked state by creating a new context # This will require crashing the device to obtain register r9 from , # So we need to re-perform the unlock bypass after this reset. args.config = None # Zap this so we perform inspection ctx = create_depthcharge_ctx(args, post_reboot_cb=post_reboot_cb, post_reboot_cb_data='self', console_kwargs=console_kwargs) ctx.save(_INSPECT_CFG) if args.boot: log.info('Sending sonosboot command. Device will enter fallback root shell.') log.note('Refer to /etc/Configure and /etc/inittab to finish initializing the platform.') ctx.send_command('sonosboot', read_response=False) else: sys.exit(1)
if __name__ == '__main__': if len(sys.argv) < 2 or '-h' in sys.argv or '--help' in sys.argv: print('MT7628 (MediaTek/Ralink) boot menu example') print('Usage: <serial port> [baud rate]') print('Baud rate defaults to 57600.') print() sys.exit(1 if len(sys.argv) < 1 else 0) uart_device = sys.argv[1] try: baud_rate = sys.argv[2] print(baud_rate) except IndexError: baud_rate = '57600' console = Console(uart_device, baudrate=baud_rate) log.note('Opened ' + uart_device + ': ' + baud_rate) try: read_loop(console) except KeyboardInterrupt: print('\r', end='') # Overwrite "^C" on terminal log.warning('Interrupted! Exiting.') sys.exit(1) msg = ('You may now run the following command to inspect the platform.\n' ' depthcharge-inspect -i {:s}:{:s} -c rp-wd008.cfg [-m term]') log.info(msg.format(uart_device, baud_rate))
if __name__ == '__main__': args = handle_cmdline() try: scripts = locate_scripts() except FileNotFoundError as error: log.error(str(error)) sys.exit(1) tests = load_tests(args) state = load_state(args) t_start = time.time() for test in tests: script = test_name_to_script(test.__name__) log.note('Running ' + test.__name__) test_help(state, script) test(state, script) save_state(state) t_end = time.time() t_elapsed = timedelta(seconds=(t_end - t_start)) msg = ('=' * 76 + os.linesep + ' Tests complete successfully.' + os.linesep + ' Elapsed: ' + str(t_elapsed) + os.linesep) log.info(msg)