def generate_config_templates(family: str, output_folder: str) -> None: """Generate all possible configuration for selected family.""" if not family: raise SPSDKError("The chip family must be specified.") templates: Dict[str, str] = {} # 1: Generate all configuration for MBI templates.update(mbi_generate_config_templates(family)) # 2: Add TrustZone Configuration file templates.update(TrustZone.generate_config_template(family)) # 3: Optionally add Secure Binary v3.1 Configuration file templates.update(SecureBinary31.generate_config_template(family)) # And generate all config templates files for template in templates: file_name = f"{template}.yml" if os.path.isfile(output_folder): raise SPSDKError(f"The specified path {output_folder} is file.") if not os.path.isdir(output_folder): os.mkdir(output_folder) full_file_name = os.path.join(output_folder, file_name) if not os.path.isfile(full_file_name): click.echo(f"Creating {file_name} template file.") with open(full_file_name, "w") as f: f.write(templates[template]) else: click.echo(f"Skip creating {file_name}, this file already exists.")
def main( ctx: click.Context, interface: str, log_level: str, serial_no: str, debug_probe_option: List[str], device: str, revision: str, ) -> int: """NXP Shadow Registers control Tool.""" logging.basicConfig(level=log_level.upper()) logger.setLevel(level=log_level.upper()) config_filename = os.path.join(CONFIG_DIR, CONFIG_FILE) probe_user_params = {} for par in debug_probe_option: if par.count("=") != 1: raise SPSDKError(f"Invalid -o parameter {par}!") par_splitted = par.split("=") probe_user_params[par_splitted[0]] = par_splitted[1] ctx.obj = { "config_file": config_filename, "interface": interface, "serial_no": serial_no, "debug_probe_params": probe_user_params, "device": device, "revision": revision or "latest", } return 0
def main(ctx: click.Context, interface: str, log_level: str, serial_no: str, debug_probe_option: List[str], device: str) -> int: """NXP Shadow Registers control Tool.""" logging.basicConfig(level=log_level.upper()) logger.setLevel(level=log_level.upper()) config_filename = os.path.join(CONFIG_DIR, CONFIG_FILE) probe_user_params = {} for par in debug_probe_option: if par.count("=") != 1: raise SPSDKError(f"Invalid -o parameter {par}!") else: par_splitted = par.split("=") probe_user_params[par_splitted[0]] = par_splitted[1] ctx.obj = { 'config_file': config_filename, 'interface': interface, 'serial_no': serial_no, 'debug_probe_params': probe_user_params, 'device': device } return 0
def main(ctx: click.Context, interface: str, protocol: str, log_level: str, timing: float, serial_no: str, debug_probe_option: List[str], reset: bool) -> int: """NXP Debug Mailbox Tool.""" logging.basicConfig(level=log_level.upper()) logger.setLevel(level=log_level.upper()) probe_user_params = {} for par in debug_probe_option: if par.count("=") != 1: raise SPSDKError(f"Invalid -o parameter {par}!") par_splitted = par.split("=") probe_user_params[par_splitted[0]] = par_splitted[1] ctx.obj = { 'protocol': protocol, 'interface': interface, 'serial_no': serial_no, 'debug_probe_params': probe_user_params, 'timing': timing, 'reset': reset, } return 0
def setreg(pass_obj: dict, reg: str, reg_val: str) -> None: """The command sets a value of one shadow register defined by parameter.""" shadow_regs: ShadowRegisters = _open_registers(pass_obj) try: shadow_regs.set_register(reg, reg_val) click.echo(f"The Shadow register {reg} has been set to {reg_val} value") except SPSDKError as exc: raise SPSDKError(f"Setting Shadow register failed! ({str(exc)})")
def saveconfig(pass_obj: dict, filename: str = "sr_config.yml", raw: bool = False) -> None: """Save current state of shadow registers to YML file.""" try: shadow_regs: ShadowRegisters = _open_registers(pass_obj) shadow_regs.reload_registers() shadow_regs.create_yml_config(filename, raw) click.echo(f"The Shadow registers has been saved into {filename} YAML file") except SPSDKError as exc: raise SPSDKError(f"Save configuration of Shadow registers failed! ({str(exc)})")
def getreg(pass_obj: dict, reg: str) -> None: """The command prints the current value of one shadow register.""" shadow_regs: ShadowRegisters = _open_shadow_registers(pass_obj) try: register: RegsRegister = shadow_regs.regs.find_reg(reg) shadow_regs.reload_register(register) click.echo(f"Value of {reg} is: {register.get_hex_value()}") except SPSDKError as exc: raise SPSDKError(f"Getting Shadow register failed! ({str(exc)})")
def loadconfig(pass_obj: dict, filename: str = "sr_config.yml", raw: bool = False) -> None: """Load new state of shadow registers from YML file into microcontroller.""" try: shadow_regs: ShadowRegisters = _open_registers(pass_obj) shadow_regs.load_yml_config(filename, raw) shadow_regs.sets_all_registers() click.echo(f"The Shadow registers has been loaded by configuration in {filename} YAML file") except SPSDKError as exc: raise SPSDKError(f"Load configuration of Shadow registers failed ({str(exc)})!")
def set_register(self, reg_name: str, data: Any) -> None: """The function sets the value of the specified register. param reg: The register name. param data: The new data to be stored to shadow register. raises DebugProbeError: The debug probe is not specified. """ if self._probe is None: raise DebugProbeError("There is no debug probe.") try: reg = self.regs.find_reg(reg_name) value = value_to_bytes(data) start_address = self.offset + reg.offset width = reg.width if width < len(value) * 8: raise SPSDKError(f"Invalid length of data for shadow register write.") if width < 32: width = 32 data_alligned = bytearray(math.ceil(width / 8)) data_alligned[len(data_alligned) - len(value) : len(data_alligned)] = value if reg.reverse: data_alligned = self._reverse_bytes_in_longs(data_alligned) if width == 32: self._write_shadow_reg(start_address, int.from_bytes(data_alligned[:4], "big")) else: end_address = start_address + math.ceil(width / 8) addresses = range(start_address, end_address, 4) i = 0 for addr in addresses: self._write_shadow_reg(addr, int.from_bytes(data_alligned[i:i+4], "big")) i += 4 reg.set_value(value) except SPSDKError as exc: raise SPSDKError(f"The get shadow register failed({str(exc)}).")
def _open_shadow_registers(pass_obj: Dict) -> ShadowRegisters: """Method opens ShadowRegisters object based on input arguments. :param pass_obj: Input dictionary with arguments. :return: Active ShadowRegisters object. :raises SPSDKError: Raised with any kind of problems with debug probe. """ config_file = pass_obj["config_file"] interface = pass_obj["interface"] serial_no = pass_obj["serial_no"] debug_probe_params = pass_obj["debug_probe_params"] device = pass_obj["device"] revision = pass_obj["revision"] if device not in RegConfig.devices(config_file): raise SPSDKError( "Invalid or none device parameter(-dev). Use 'listdevs' command to get supported devices." ) regs_cfg = RegConfig(config_file) try: debug_probes = DebugProbeUtils.get_connected_probes( interface=interface, hardware_id=serial_no, user_params=debug_probe_params) selected_probe = debug_probes.select_probe() debug_probe = selected_probe.get_probe(debug_probe_params) debug_probe.open() if not enable_debug(debug_probe): raise SPSDKError("Cannot enable debug interface") debug_probe.enable_memory_interface() except SPSDKError as exc: raise SPSDKError( f"Error with opening debug probe: ({str(exc)})") from exc return ShadowRegisters(debug_probe=debug_probe, config=regs_cfg, device=device, revision=revision)
def _decode_report(raw_data: bytes) -> Union[CmdResponse, bytes]: """Decodes the data read on USB interface. :param raw_data: Data received :type raw_data: bytes :return: CmdResponse object or data read :raises SPSDKError: Transaction aborted by target """ logger.debug( f"IN [{len(raw_data)}]: {', '.join(f'{b:02X}' for b in raw_data)}") report_id, _, plen = unpack_from('<2BH', raw_data) if plen == 0: logger.debug("Received an abort package") raise SPSDKError('Transaction aborted') data = raw_data[4:4 + plen] if report_id == REPORT_ID['CMD_IN']: return parse_cmd_response(data) return data
def printregs(pass_obj: dict, rich: bool = False) -> None: """Print all Shadow registers including theirs current values. In case of needed more information, there is also provided rich format of print. """ try: shadow_regs: ShadowRegisters = _open_shadow_registers(pass_obj) shadow_regs.reload_registers() for reg in shadow_regs.regs.get_registers(): click.echo(f"Register Name: {reg.name}") click.echo(f"Register value: {reg.get_hex_value()}") if rich: click.echo(f"Register description: {reg.description}") address = shadow_regs.offset + reg.offset click.echo(f"Register address: 0x{address:08X}") click.echo(f"Register width: {reg.width} bits") click.echo() except SPSDKError as exc: raise SPSDKError(f"Print of Shadow registers failed! ({str(exc)})")
def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: """Write 32-bit register in memory space of MCU. This is write 32-bit register in memory space of MCU function for SPSDK library to support various DEBUG PROBES. :param addr: the register address :param data: the data to be written into register :raises DebugProbeNotOpenError: The Pemicro probe is NOT opened :raises SPSDKError: The Pemicro probe has failed during write operation """ if self.pemicro is None: raise DebugProbeNotOpenError( "The Pemicro debug probe is not opened yet") self.last_access_memory = True try: self.pemicro.write_32bit(address=addr, data=data) except PEMicroException as exc: logger.error(f"Failed write memory({str(exc)}).") raise SPSDKError(str(exc))
def mem_reg_read(self, addr: int = 0) -> int: """Read 32-bit register in memory space of MCU. This is read 32-bit register in memory space of MCU function for SPSDK library to support various DEBUG PROBES. :param addr: the register address :return: The read value of addressed register (4 bytes) :raises DebugProbeNotOpenError: The Pemicro probe is NOT opened :raises SPSDKError: The Pemicro probe has failed during read operation """ if self.pemicro is None: raise DebugProbeNotOpenError( "The Pemicro debug probe is not opened yet") self.last_access_memory = True reg = 0 try: reg = self.pemicro.read_32bit(addr) except PEMicroException as exc: logger.error(f"Failed read memory({str(exc)}).") raise SPSDKError(str(exc)) return reg
def get_register(self, reg_name: str) -> bytes: """The function returns value of the requested register. param reg: The register name. return: The value of requested register in bytes raises DebugProbeError: The debug probe is not specified. """ if self._probe is None: raise DebugProbeError("There is no debug probe.") result = bytearray() try: reg = self.regs.find_reg(reg_name) start_address = self.offset + reg.offset width = reg.width if width < 32: width = 32 if width == 32: result.extend(self._probe.mem_reg_read(start_address).to_bytes(4, "big")) else: end_address = start_address + math.ceil(width / 8) addresses = range(start_address, end_address, 4) for addr in addresses: result.extend(self._probe.mem_reg_read(addr).to_bytes(4, "big")) if reg.reverse: result = self._reverse_bytes_in_longs(result) except SPSDKError as exc: raise SPSDKError(f"The get shadow register failed({str(exc)}).") return result
def enable_debug(probe: DebugProbe, ap_mem: int = 0) -> bool: """Function that enables debug access ports on devices with debug mailbox. :param probe: Initialized debug probe. :param ap_mem: Index of Debug access port for memory interface. :return: True if debug port is enabled, False otherwise :raises SPSDKError: Unlock method failed. """ debug_enabled = False try: def test_ahb_access(ap_mem: int) -> bool: logger.debug("step T.1: Activate the correct AP") probe.coresight_reg_write(access_port=False, addr=2*4, data=ap_mem) logger.debug("step T.2: Set the AP access size and address mode") probe.coresight_reg_write(access_port=True, addr=probe.get_coresight_ap_address(ap_mem, 0*4), data=0x22000012) logger.debug("step T.3: Set the initial AHB address to access") probe.coresight_reg_write(access_port=True, addr=probe.get_coresight_ap_address(ap_mem, 1*4), data=0xE000ED00) logger.debug("step T.4: Access the memory system at that address") try: chip_id = probe.coresight_reg_read(access_port=True, addr=probe.get_coresight_ap_address(ap_mem, 3*4)) logger.debug(f"ChipID={chip_id:08X}") except DebugProbeError: chip_id = 0xFFFFFFFF logger.debug(f"ChipID can't be read") # Check if the device is locked return chip_id not in (0xFFFFFFFF, 0) logger.debug("step 3: Check if AHB is enabled") if not test_ahb_access(ap_mem): logger.debug("Locked Device. Launching unlock sequence.") # Start debug mailbox system dbg_mlbx = DebugMailbox(debug_probe=probe) StartDebugSession(dm=dbg_mlbx).run() # Recheck the AHB access if test_ahb_access(ap_mem): logger.debug(f"Access granted") debug_enabled = True else: logger.debug(f"Enable debug operation failed!") else: logger.debug("Unlocked Device") debug_enabled = True except AttributeError as exc: raise SPSDKError(f"Invalid input parameters({str(exc)})") except DebugProbeError as exc: raise SPSDKError(f"Can't unlock device ({str(exc)})") return debug_enabled
def _connect_jlink(self) -> None: """Custom J-Link connect function. :raises SPSDKError: Unsupported communication protocol. :raises SPSDKDebugProbeNotOpenError: The PyOCD probe is NOT opened :raises SPSDKDebugProbeError: General error with probe. """ if self.pyocd_session is None: raise SPSDKDebugProbeNotOpenError( "The PyOCD debug probe is not opened yet") # Attempt to connect. debug_probe = self.pyocd_session.target.dp probe = debug_probe.probe protocol = debug_probe._protocol # Connect to the target via JTAG or SWD. # Handle default protocol. if (protocol is None) or ( protocol == pyocd.probe.debug_probe.DebugProbe.Protocol.DEFAULT): protocol = probe._default_protocol # Validate selected protocol. if protocol not in probe._supported_protocols: raise SPSDKError(f"unsupported wire protocol {protocol}") # Convert protocol to port enum. if protocol == PyOCDDebugProbe.Protocol.SWD: interface = pylink.enums.JLinkInterfaces.SWD elif protocol == PyOCDDebugProbe.Protocol.JTAG: interface = pylink.enums.JLinkInterfaces.JTAG try: probe._link.set_tif(interface) if probe.session.options.get("jlink.power"): probe._link.power_on() # device_name = probe.session.options.get('jlink.device') or "Cortex-M4" # probe._link.connect(device_name, speed=200) probe._link.coresight_configure() probe._protocol = protocol except JLinkException as exc: raise SPSDKDebugProbeError(probe._convert_exception(exc)) from exc def __read_idr(probe) -> int: """Read IDR register and get DP version.""" dpidr = probe.read_dp(dap.DP_IDR, now=True) dp_partno = (dpidr & dap.DPIDR_PARTNO_MASK) >> dap.DPIDR_PARTNO_SHIFT dp_version = (dpidr & dap.DPIDR_VERSION_MASK) >> dap.DPIDR_VERSION_SHIFT dp_revision = ( dpidr & dap.DPIDR_REVISION_MASK) >> dap.DPIDR_REVISION_SHIFT is_mindp = (dpidr & dap.DPIDR_MIN_MASK) != 0 return dap.DPIDR(dpidr, dp_partno, dp_version, dp_revision, is_mindp) # Report on DP version. debug_probe.dpidr = probe.dpidr = __read_idr(probe) mindp = " MINDP" if probe.dpidr.mindp else "" logger.info( f"DP IDR = 0x{probe.dpidr.idr:08X} (v{probe.dpidr.version}{mindp} rev{probe.dpidr.revision})" )
def generate_secure_binary_21( bd_file_path: click.Path, output_file_path: click.Path, key_file_path: click.Path, private_key_file_path: click.Path, signing_certificate_file_paths: List[click.Path], root_key_certificate_paths: List[click.Path], hoh_out_path: click.Path, external_files: List[click.Path], ) -> None: """Generate SecureBinary image from BD command file. :param bd_file_path: path to BD file. :param output_file_path: output path to generated secure binary file. :param key_file_path: path to key file. :param private_key_file_path: path to private key file for signing. This key relates to last certificate from signing certificate chain. :param signing_certificate_file_paths: signing certificate chain. :param root_key_certificate_paths: paths to root key certificate(s) for verifying other certificates. Only 4 root key certificates are allowed, others are ignored. One of the certificates must match the first certificate passed in signing_certificate_file_paths. :param hoh_out_path: output path to hash of hashes of root keys. If set to None, 'hash.bin' is created under working directory. :param external_files: external files referenced from BD file. :raises SPSDKError: If incorrect bf file is provided """ # Create lexer and parser, load the BD file content and parse it for # further execution - the parsed BD file is a dictionary in JSON format with open(str(bd_file_path)) as bd_file: bd_file_content = bd_file.read() parser = bd_parser.BDParser() parsed_bd_file = parser.parse(text=bd_file_content, extern=external_files) if parsed_bd_file is None: raise SPSDKError( "Invalid bd file, secure binary file generation terminated") # The dictionary contains following content: # { # options: { # opt1: value,... # }, # sections: [ # {section_id: value, options: {}, commands: {}}, # {section_id: value, options: {}, commands: {}} # ] # } # TODO check, that section_ids differ in sections??? # we need to encrypt and sign the image, let's check, whether we have # everything we need # It appears, that flags option in BD file are irrelevant for 2.1 secure # binary images regarding encryption/signing - SB 2.1 must be encrypted # and signed. # However, bit 15 represents, whether the final SB 2.1 must include a # SHA-256 of the botable section. flags = parsed_bd_file["options"].get( "flags", BootImageV21.FLAGS_SHA_PRESENT_BIT | BootImageV21.FLAGS_ENCRYPTED_SIGNED_BIT) if (private_key_file_path is None or signing_certificate_file_paths is None or root_key_certificate_paths is None): click.echo( "error: Signed image requires private key with -s option, " "one or more certificate(s) using -S option and one or more root key " "certificates using -R option") sys.exit(1) # Versions and build number are up to the user. If he doesn't provide any, # we set these to following values. product_version = parsed_bd_file["options"].get("productVersion", "") component_version = parsed_bd_file["options"].get("componentVersion", "") build_number = parsed_bd_file["options"].get("buildNumber", -1) if not product_version: product_version = "1.0.0" click.echo( "warning: production version not defined, defaults to '1.0.0'") if not component_version: component_version = "1.0.0" click.echo( "warning: component version not defined, defaults to '1.0.0'") if build_number == -1: build_number = 1 click.echo("warning: build number not defined, defaults to '1.0.0'") if key_file_path is None: # Legacy elf2sb doesn't report no key provided, but this should # be definitely reported to tell the user, what kind of key is being # used click.echo("warning: no KEK key provided, using a zero KEK key") sb_kek = bytes.fromhex("0" * 64) else: with open(str(key_file_path)) as kek_key_file: # TODO maybe we should validate the key length and content, to make # sure the key provided in the file is valid?? sb_kek = bytes.fromhex(kek_key_file.readline()) # validate keyblobs and perform appropriate actions keyblobs = parsed_bd_file.get("keyblobs", []) # Based on content of parsed BD file, create a BootSectionV2 and assign # commands to them. # The content of section looks like this: # sections: [ # { # section_id: <number>, # options: {}, this is left empty for now... # commands: [ # {<cmd1>: {<param1>: value, ...}}, # {<cmd2>: {<param1>: value, ...}}, # ... # ] # }, # { # section_id: <number>, # ... # } # ] sb_sections = [] bd_sections = parsed_bd_file["sections"] for bd_section in bd_sections: section_id = bd_section["section_id"] commands = [] for cmd in bd_section["commands"]: for key, value in cmd.items(): # we use a helper function, based on the key ('load', 'erase' # etc.) to create a command object. The helper function knows # how to handle the parameters of each command. # TODO Only load, fill, erase and enable commands are supported # for now. But there are few more to be supported... cmd_fce = elf2sb_helper21.get_command(key) if key in ("keywrap", "encrypt"): keyblob = {"keyblobs": keyblobs} value.update(keyblob) cmd = cmd_fce(value) commands.append(cmd) sb_sections.append(BootSectionV2(section_id, *commands)) # We have a list of sections and their respective commands, lets create # a boot image v2.1 object secure_binary = BootImageV21( sb_kek, *sb_sections, product_version=product_version, component_version=component_version, build_number=build_number, flags=flags, ) # create certificate block cert_block = CertBlockV2(build_number=build_number) for cert_path in signing_certificate_file_paths: cert_data = load_certificate_as_bytes(str(cert_path)) cert_block.add_certificate(cert_data) for cert_idx, cert_path in enumerate(root_key_certificate_paths): cert_data = load_certificate_as_bytes(str(cert_path)) cert_block.set_root_key_hash(cert_idx, Certificate(cert_data)) # We have our secure binary, now we attach to it the certificate block and # the private key content # TODO legacy elf2sb doesn't require you to use certificates and private key, # so maybe we should make sure this is not necessary??? # The -s/-R/-S are mandatory, 2.0 format not supported!!! secure_binary.cert_block = cert_block secure_binary.private_key_pem_data = load_binary( str(private_key_file_path)) if hoh_out_path is None: hoh_out_path = os.path.join(os.getcwd(), "hash.bin") with open(str(hoh_out_path), "wb") as rkht_file: rkht_file.write(secure_binary.cert_block.rkht) with open(str(output_file_path), "wb") as sb_file_output: sb_file_output.write(secure_binary.export()) click.echo(f"Success. (Secure binary 2.1: {output_file_path} created.)")
def load_yml_config(self, file_name: str, raw: bool = False) -> None: """The function loads the configuration from YML file. :param file_name: The file_name (without extension) of stored configuration. :param raw: Raw input of configuration (including computed fields and anti-pole registers) :raise SPSDKError: When the configuration file not found. """ antipole_regs = self.config.get_antipole_regs(self.device) computed_fields = self.config.get_computed_fields(self.device) try: with open(file_name, "r") as yml_config_file: yaml = YAML() yaml.indent(sequence=4, offset=2) data = yaml.load(yml_config_file) except FileNotFoundError: raise SPSDKError("File with YML configuration doesn't exists.") for reg in data["registers"].keys(): if not raw and reg in antipole_regs.values(): continue if reg not in self.regs.get_reg_names(): continue #The loaded register is our if "value" in data["registers"][reg].keys(): val = data['registers'][reg]['value'] val = val.replace("0x", "") self.regs.find_reg(reg).set_value(bytes.fromhex(val)) elif "bitfields" in data["registers"][reg].keys(): for bitf_name in data["registers"][reg]["bitfields"]: try: self.regs.find_reg(reg).find_bitfield(bitf_name) except BitfieldNotFound: continue if not raw and reg in computed_fields.keys() and bitf_name in computed_fields[reg].keys(): continue bitf = self.regs.find_reg(reg).find_bitfield(bitf_name) if bitf.has_enums(): #solve the bitfields store in enums string bitf.set_enum_value(data["registers"][reg]["bitfields"][bitf_name]) else: #load bitfield data bitf.set_value(int(data["registers"][reg]["bitfields"][bitf_name])) else: logger.error(f"There are no data for {reg} register.") if not raw and reg in computed_fields.keys(): # Check the computed fields for field in computed_fields[reg].keys(): val = self.regs.find_reg(reg).get_value() if hasattr(self, computed_fields[reg][field]): method = getattr(self, computed_fields[reg][field], None) computed_val = method(val) self.regs.find_reg(reg).set_value(computed_val) else: raise SPSDKError(f"The '{computed_fields[reg][field]}' compute function doesn't exists.") if not raw and reg in antipole_regs.keys(): #Write also anti-pole value val = self.regs.find_reg(reg).get_value() self.regs.find_reg(antipole_regs[reg]).set_value(self.antipolize_reg(val)) logger.debug(f"The register {reg} has been loaded from configuration.")