def test_grouped_register_invalid_params(data_dir, group_reg): """Test of register grouping with invalid width""" regs = Registers(TEST_DEVICE_NAME) with pytest.raises(SPSDKRegsErrorRegisterGroupMishmash): regs.load_registers_from_xml(data_dir + "/grp_regs.xml", grouped_regs=group_reg)
class ShadowRegsXlsToXml(): "Class to convert XLSX to XML with shadow register description" def __init__(self, xls_file: str, xml_file: str = "", xls_type: int=1) -> None: self.registers = Registers("Unknown") self.xls_type = xls_type self.header_cells = {} self.xml_file_name = xml_file if xml_file != "" else xls_file.replace(".xlsx", ".xml") self.wb = None print(os.path.dirname(os.path.realpath(__file__))) self.wb = openpyxl.load_workbook(xls_file) print(f"Loaded XLSX file ({xls_file})") self.convert() self.registers.write_xml(self.xml_file_name) print(f"Written XML file ({self.xml_file_name})") print(str(self.registers)) def convert(self) -> None: raise NotImplementedError def _get_worksheet(self) -> Any: """Find the valid worksheet with the fuse map.""" raise NotImplementedError def _get_header(self) -> None: """Returns the dictionary with cells of header.""" raise NotImplementedError def _get_registers(self) -> None: """Function finds all registers in XLS sheet and store them.""" raise NotImplementedError def __del__(self) -> None: """Just close all open files.""" if self.wb: self.wb.close()
def __init__(self, debug_probe: DebugProbe, config: RegConfig, device: str, revision: str = "latest") -> None: """Initialization of Shadow register class.""" self._probe = debug_probe self.config = config self.device = device self.offset = int(self.config.get_address(self.device, remove_underscore=True), 16) self.regs = Registers(self.device) rev = revision if revision != "latest" else config.get_latest_revision(self.device) self.regs.load_registers_from_xml(config.get_data_file(self.device, rev))
def __init__(self, device: str = None, revision: str = None, user_config: PfrConfiguration = None) -> None: """Initialize an instance. :param device: device to use, list of supported devices is available via 'devices' method :param revision: silicon revision, if not specified, the latest is being used :param user_config: PfrConfiguration with user configuration to use with initialization :raises SPSDKError: When no device is provided :raises SPSDKError: When no device is not supported :raises SPSDKError: When there is invalid revision """ if not (device or user_config): raise SPSDKError("No device provided") self.config = self._load_config() # either 'device' or 'user_config' IS defined! Mypy doesn't understand the check above self.device = device or user_config.device # type: ignore if self.device not in self.config.get_devices(): raise SPSDKError(f"Device '{self.device}' is not supported") self.revision = revision or (user_config.revision if user_config else "latest") if not self.revision or self.revision == "latest": self.revision = self.config.get_latest_revision(self.device) logger.warning( f"The silicon revision is not specified, the latest: '{self.revision}' has been used." ) if self.revision not in self.config.get_revisions(self.device): raise SPSDKError( f"Invalid revision '{self.revision}' for '{self.device}'") self.registers = Registers(self.device) self.registers.load_registers_from_xml( xml=self.config.get_data_file(self.device, self.revision), filter_reg=self.config.get_ignored_registers(self.device), grouped_regs=self.config.get_grouped_registers(self.device), ) # Set the computed field handler for reg, fields in self.config.get_computed_fields( self.device).items(): reg_obj = self.registers.find_reg(reg) reg_obj.add_setvalue_hook(self.reg_computed_fields_handler, fields) self.user_config = PfrConfiguration( config=user_config, device=self.device, revision=self.revision, cfg_type=self.__class__.__name__, ) if self.user_config.settings: self.set_config(self.user_config, raw=False)
def __init__(self, xls_file: str, xml_file: str = "", xls_type: int=1) -> None: self.registers = Registers("Unknown") self.xls_type = xls_type self.header_cells = {} self.xml_file_name = xml_file if xml_file != "" else xls_file.replace(".xlsx", ".xml") self.wb = None print(os.path.dirname(os.path.realpath(__file__))) self.wb = openpyxl.load_workbook(xls_file) print(f"Loaded XLSX file ({xls_file})") self.convert() self.registers.write_xml(self.xml_file_name) print(f"Written XML file ({self.xml_file_name})") print(str(self.registers))
def create_simple_regs(): """Create siple reg structure with basic cases.""" regs = Registers(TEST_DEVICE_NAME) reg1 = RegsRegister( TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS, ) reg2 = RegsRegister( TEST_REG_NAME + "_2", TEST_REG_OFFSET + 4, TEST_REG_WIDTH, TEST_REG_DESCR + "_2", TEST_REG_REV, TEST_REG_ACCESS, ) bitfield1 = RegsBitField( reg2, TEST_BITFIELD_NAME, TEST_BITFIELD_OFFSET, TEST_BITFIELD_WIDTH, TEST_BITFIELD_DESCR, TEST_BITFIELD_RESET_VAL, TEST_BITFIELD_ACCESS, ) bitfield2 = RegsBitField( reg2, TEST_BITFIELD_NAME + "_2", TEST_BITFIELD_OFFSET + TEST_BITFIELD_WIDTH, 1, ".", 0, TEST_BITFIELD_ACCESS, ) enum1 = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR, 1) enum2 = RegsEnum(TEST_ENUM_NAME + "_2", 0, TEST_ENUM_DESCR + "_2", 1) bitfield2.add_enum(enum1) bitfield2.add_enum(enum2) reg2.add_bitfield(bitfield1) reg2.add_bitfield(bitfield2) regs.add_register(reg1) regs.add_register(reg2) return regs
def test_registers_xml(data_dir, tmpdir): regs = Registers(TEST_DEVICE_NAME) with use_working_directory(data_dir): regs.load_registers_from_xml("registers.xml") with use_working_directory(tmpdir): regs.write_xml("registers.xml") regs2 = Registers(TEST_DEVICE_NAME) with use_working_directory(tmpdir): regs2.load_registers_from_xml("registers.xml") assert str(regs) == str(regs2)
def info(pass_obj: dict, output: str, open_result: bool) -> None: """The command generate HTML of Shadow registers.""" config = RegConfig(pass_obj["config_file"]) device = pass_obj["device"] revision = pass_obj["revision"] registers = Registers(device) rev = revision if revision != "latest" else config.get_latest_revision( device) registers.load_registers_from_xml(config.get_data_file(device, rev)) html_output = registers.generate_html( f"{device} - Shadow Registers", f"The table with Shadow registers description for {device}", ) with open(output, "w", encoding="utf-8") as f: f.write(html_output) if open_result: # pragma: no cover # can't test opening the html document click.launch(f"{output}")
def test_bitfield_enums(): parent_reg = RegsRegister(TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS) bitfield = RegsBitField(parent_reg, TEST_BITFIELD_NAME, TEST_BITFILED_OFFSET, TEST_BITFILED_WIDTH, TEST_BITFIELD_DESCR, TEST_BITFIELD_RESET_VAL, TEST_BITFIELD_ACCESS) parent_reg.add_bitfield(bitfield) enums = [] for n in range((1 << TEST_BITFILED_WIDTH) - 1): enum = RegsEnum(f"{TEST_ENUM_NAME}{n}", n, f"{TEST_ENUM_DESCR}{n}", TEST_BITFILED_WIDTH) enums.append(enum) bitfield.add_enum(enum) enum_names = bitfield.get_enum_names() for n in range((1 << TEST_BITFILED_WIDTH) - 1): assert n == bitfield.get_enum_constant(f"{TEST_ENUM_NAME}{n}") assert enums[n].name in enum_names for n in range((1 << TEST_BITFILED_WIDTH)): bitfield.set_value(n) if n < (1 << TEST_BITFILED_WIDTH) - 1: assert f"{TEST_ENUM_NAME}{n}" == bitfield.get_enum_value() else: assert n == bitfield.get_enum_value() for n in range((1 << TEST_BITFILED_WIDTH) - 1): bitfield.set_enum_value(f"{TEST_ENUM_NAME}{n}") assert n == bitfield.get_value() with pytest.raises(EnumNotFound): bitfield.get_enum_constant("Invalid name") regs = Registers(TEST_DEVICE_NAME) regs.add_register(parent_reg)
def __init__( self, debug_probe: DebugProbe, config: RegConfig, device: str, revision: str = "latest", ) -> None: """Initialization of Shadow register class.""" self.probe = debug_probe self.config = config self.device = device self.offset = int( self.config.get_address(self.device, remove_underscore=True), 16) self.regs = Registers(self.device) rev = revision or "latest" rev = rev if rev != "latest" else config.get_latest_revision( self.device) self.regs.load_registers_from_xml( config.get_data_file(self.device, rev), grouped_regs=config.get_grouped_registers(self.device), ) # Set the computed field handler for reg, fields in self.config.get_computed_fields( self.device).items(): reg_obj = self.regs.find_reg(reg) reg_obj.add_setvalue_hook(self.reg_computed_fields_handler, fields) # Set the antipolize handler for reg, antipole_reg in self.config.get_antipole_regs( self.device).items(): src = self.regs.find_reg(reg) dst = self.regs.find_reg(antipole_reg) src.add_setvalue_hook(self.reg_antipolize_src_handler, dst) dst.add_setvalue_hook(self.reg_antipolize_dst_handler, src)
def __init__(self, xls_file: str, xml_file: str = "", xls_type: int = 1) -> None: self.registers = Registers("Unknown") self.xls_type = xls_type self.header_cells: Dict[str, str] = {} self.xml_file_name = xml_file if xml_file != "" else xls_file.replace( ".xlsx", ".xml") self.workbook = None self.worksheet = None self.merged_cells = None click.echo(os.path.dirname(os.path.realpath(__file__))) self.workbook = openpyxl.load_workbook(xls_file) click.echo(f"Loaded XLSX file ({xls_file})")
def test_registers_corrupted_xml(data_dir): """Test registers XML support with invalid data.""" regs = Registers(TEST_DEVICE_NAME) with pytest.raises(SPSDKError): with use_working_directory(data_dir): regs.load_registers_from_xml("registers_corr.xml") with pytest.raises(SPSDKError): with use_working_directory(data_dir): regs.load_registers_from_xml("registers_corr2.xml")
def test_register_duplicate(): """Test registers add duplicate.""" reg = RegsRegister( TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS, ) reg1 = RegsRegister( TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS, ) regs = Registers(TEST_DEVICE_NAME) regs.add_register(reg) with pytest.raises(SPSDKRegsError): regs.add_register(reg1)
def test_load_grouped_register_value_compatibility(data_dir): """Simply test to handle load of individual registers into grouped from YML.""" regs = Registers(TEST_DEVICE_NAME) group = [{"name": "TestRegA"}] regs.load_registers_from_xml(data_dir + "/grp_regs.xml", grouped_regs=group) yaml = YAML() with open(data_dir + "/group_none_reg.yml", "r") as yml_file: data = yaml.load(yml_file) regs.load_yml_config(data) reg = regs.find_reg("TestRegA") assert reg.get_hex_value() == "0x01020304111213142122232431323334" assert regs.find_reg( "TestRegA0", include_group_regs=True).get_hex_value() == "0x01020304" assert regs.find_reg( "TestRegA1", include_group_regs=True).get_hex_value() == "0x11121314" assert regs.find_reg( "TestRegA2", include_group_regs=True).get_hex_value() == "0x21222324" assert regs.find_reg( "TestRegA3", include_group_regs=True).get_hex_value() == "0x31323334"
def test_basic_grouped_register_reversed_value(data_dir): """Test basic functionality of register grouping functionality with reversed value""" regs = Registers(TEST_DEVICE_NAME) group = [{"name": "TestRegA", "reverse": "True"}] regs.load_registers_from_xml(data_dir + "/grp_regs.xml", grouped_regs=group) reg = regs.find_reg("TestRegA") assert reg.offset == 0x400 assert reg.width == 4 * 32 reg.set_value("0x01020304_11121314_21222324_31323334") assert regs.find_reg( "TestRegA0", include_group_regs=True).get_hex_value() == "0x01020304" assert regs.find_reg( "TestRegA1", include_group_regs=True).get_hex_value() == "0x11121314" assert regs.find_reg( "TestRegA2", include_group_regs=True).get_hex_value() == "0x21222324" assert regs.find_reg( "TestRegA3", include_group_regs=True).get_hex_value() == "0x31323334" assert regs.find_reg("TestRegA0", include_group_regs=True).reverse == True assert regs.find_reg("TestRegA1", include_group_regs=True).reverse == True assert regs.find_reg("TestRegA2", include_group_regs=True).reverse == True assert regs.find_reg("TestRegA3", include_group_regs=True).reverse == True assert reg.get_hex_value() == "0x01020304111213142122232431323334"
def test_registers_xml_bad_format(data_dir): """Test registers XML support - BAd XML format exception.""" regs = Registers(TEST_DEVICE_NAME) with pytest.raises(SPSDKRegsError): regs.load_registers_from_xml(data_dir + "/bad_format.xml")
def test_registers_xml_hidden(data_dir, tmpdir): """Test registers XML support.""" regs = Registers(TEST_DEVICE_NAME) with use_working_directory(data_dir): regs.load_registers_from_xml("registers_reserved.xml") assert len(regs.get_registers()[0].get_bitfields()) == 1 assert regs.get_registers()[0].get_bitfields()[0].get_value() == 0xA assert regs.get_registers()[0].get_value() == 0x550A00 with use_working_directory(tmpdir): regs.write_xml("registers_reserved.xml") regs2 = Registers(TEST_DEVICE_NAME) with use_working_directory(tmpdir): regs2.load_registers_from_xml("registers_reserved.xml") assert str(regs) == str(regs2)
class ShadowRegisters: """SPSDK support to control the shadow registers.""" def __init__( self, debug_probe: DebugProbe, config: RegConfig, device: str, revision: str = "latest", ) -> None: """Initialization of Shadow register class.""" self.probe = debug_probe self.config = config self.device = device self.offset = int( self.config.get_address(self.device, remove_underscore=True), 16) self.regs = Registers(self.device) rev = revision or "latest" rev = rev if rev != "latest" else config.get_latest_revision( self.device) self.regs.load_registers_from_xml( config.get_data_file(self.device, rev), grouped_regs=config.get_grouped_registers(self.device), ) # Set the computed field handler for reg, fields in self.config.get_computed_fields( self.device).items(): reg_obj = self.regs.find_reg(reg) reg_obj.add_setvalue_hook(self.reg_computed_fields_handler, fields) # Set the antipolize handler for reg, antipole_reg in self.config.get_antipole_regs( self.device).items(): src = self.regs.find_reg(reg) dst = self.regs.find_reg(antipole_reg) src.add_setvalue_hook(self.reg_antipolize_src_handler, dst) dst.add_setvalue_hook(self.reg_antipolize_dst_handler, src) def _write_shadow_reg(self, addr: int, data: int, verify: int = True) -> None: """The function write a shadow register. The function writes shadow register in to MCU and verify the write if requested. param addr: Shadow register address. param data: Shadow register data to write. param verify: If True the write is read back and compare, otherwise no check is done raises IoVerificationError """ self.probe.mem_reg_write(addr, data) if verify: readback = self.probe.mem_reg_read(addr) if readback != data: raise IoVerificationError( f"The written data 0x{data:08X} to 0x{addr:08X} address are invalid." ) def reload_registers(self) -> None: """Reload all the values in managed registers.""" for reg in self.regs.get_registers(): self.reload_register(reg) def sets_all_registers(self) -> None: """Update all shadow registers in target by local values.""" for reg in self.regs.get_registers(): self.set_register(reg.name, reg.get_value()) def reload_register(self, reg: RegsRegister) -> None: """Reload the value in requested register. :param reg: The register to reload from the HW. """ reg.set_value(self.get_register(reg.name)) 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 SPSDKDebugProbeError: The debug probe is not specified. """ if self.probe is None: raise SPSDKDebugProbeError("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( "Invalid length of data for shadow register write.") width = max(width, 32) data_aligned = bytearray(math.ceil(width / 8)) data_aligned[len(data_aligned) - len(value):len(data_aligned)] = value end_address = start_address + math.ceil(width / 8) addresses = range(start_address, end_address, 4) for i, addr in enumerate(addresses): val = data_aligned[i * 4:i * 4 + 4] self._write_shadow_reg( addr, int.from_bytes( change_endianism(val) if reg.reverse else val, "big"), ) reg.set_value(value, raw=True) except SPSDKError as exc: raise SPSDKError( f"The get shadow register failed({str(exc)}).") from exc 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 SPSDKDebugProbeError: The debug probe is not specified. """ if self.probe is None: raise SPSDKDebugProbeError("There is no debug probe.") array = bytearray() try: reg = self.regs.find_reg(reg_name) start_address = self.offset + reg.offset width = max(reg.width, 32) if width == 32: array.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: array.extend( self.probe.mem_reg_read(addr).to_bytes(4, "big")) result = reverse_bytes_in_longs( bytes(array)) if reg.reverse else bytes(array) except SPSDKError as exc: raise SPSDKError( f"The get shadow register failed({str(exc)}).") from exc return result def create_yml_config(self, file_name: str, raw: bool = False) -> None: """The function creates the configuration YML file. :param file_name: The file_name (without extension) of stored configuration. :param raw: Raw output of configuration (including computed fields and anti-pole registers) """ antipole_regs = None if raw else list( self.config.get_antipole_regs(self.device).values()) computed_fields = None if raw else self.config.get_computed_fields( self.device) yaml = YAML() yaml.indent(sequence=SPSDK_YML_INDENT * 2, offset=SPSDK_YML_INDENT) data = CM() description = CM() description.yaml_set_start_comment( f"NXP {self.device.upper()} Shadow registers configuration", indent=2) description.insert(1, "device", self.device, comment="The NXP device name.") description.insert(2, "version", __version__, comment="The SPSDK Shadow register tool version.") description.insert(3, "author", __author__, comment="The author of the configuration.") description.insert(4, "release", __release__, comment="The SPSDK release.") data["description"] = description data["registers"] = self.regs.create_yml_config( exclude_regs=antipole_regs, exclude_fields=computed_fields, indent=2) with open(file_name, "w", encoding="utf8") as out_file: yaml.dump(data, out_file) 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) :raises SPSDKError: When the configuration file not found. """ antipole_regs = None if raw else list( self.config.get_antipole_regs(self.device).values()) computed_fields = None if raw else self.config.get_computed_fields( self.device) try: with open(file_name, "r", encoding="utf8") as yml_config_file: yaml = YAML() yaml.indent(sequence=4, offset=2) data = yaml.load(yml_config_file) except FileNotFoundError as exc: raise SPSDKError( "File with YML configuration doesn't exists.") from exc self.regs.load_yml_config(data["registers"], antipole_regs, computed_fields) if not raw: # Just update only configured registers exclude_hooks = list( set(self.regs.get_reg_names()) - set(data["registers"].keys())) self.regs.run_hooks(exclude_hooks) logger.debug( "The shadow registers has been loaded from configuration.") @staticmethod def reg_antipolize_src_handler(val: int, context: Any) -> int: """Antipolize given register value. :param val: Input register value. :param context: The method context. :return: Antipolized value. """ dst_reg: RegsRegister = context dst_reg.set_value(val ^ 0xFFFFFFFF, raw=True) return val @staticmethod def reg_antipolize_dst_handler(val: int, context: Any) -> int: """Keep same antipolized register value in computed register. :param val: Input register value. :param context: The method context. :return: Antipolized value. """ src_reg: RegsRegister = context val = src_reg.get_value() new_val = val ^ 0xFFFFFFFF return new_val def reg_computed_fields_handler(self, val: bytes, context: Any) -> bytes: """Recalculate all fields for given register value. :param val: Input register value. :param context: The method context (fields). :return: recomputed value. :raises SPSDKError: Raises when the computing routine is not found. """ fields: dict = context for method in fields.values(): if hasattr(self, method): method_ref = getattr(self, method, None) val = method_ref(val) else: raise SPSDKError( f"The '{method}' compute function doesn't exists.") return val # CRC8 - ITU @staticmethod def crc_update(data: bytes, crc: int = 0, is_final: bool = True) -> int: """The function compute the CRC8 ITU method from given bytes. :param data: Input data to compute CRC. :param crc: The seed for CRC. :param is_final: The flag the the function should return final result. :return: The CRC result. """ k = 0 data_len = len(data) while data_len != 0: data_len -= 1 carry = data[k] k += 1 for i in range(8): bit = (crc & 0x80) != 0 if (carry & (0x80 >> i)) != 0: bit = not bit crc <<= 1 if bit: crc ^= 0x07 crc &= 0xFF if is_final: return (crc & 0xFF) ^ 0x55 return crc & 0xFF @staticmethod def comalg_dcfg_cc_socu_crc8(val: int) -> int: """Function that creates the crc for DCFG_CC_SOCU. :param val: Input DCFG_CC_SOCU Value. :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. """ in_val = bytearray(3) for i in range(3): in_val[i] = (val >> (8 + i * 8)) & 0xFF val &= ~0xFF val |= ShadowRegisters.crc_update(in_val) return val @staticmethod def comalg_dcfg_cc_socu_rsvd(val: int) -> int: """Function fill up the DCFG_CC_SOCU RSVD filed by 0x80 to satisfy MCU needs. :param val: Input DCFG_CC_SOCU Value. :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. """ new_val = val | 0x80000000 return new_val @staticmethod def comalg_do_nothing(val: int) -> int: """Function that do nothing. :param val: Input Value. :return: Returns same value as it get. """ return val
def test_registers_corrupted_xml(data_dir, tmpdir): regs = Registers(TEST_DEVICE_NAME) with use_working_directory(data_dir): regs.load_registers_from_xml("registers_corr.xml") with use_working_directory(tmpdir): regs.write_xml("registers_corr.xml") assert not filecmp.cmp(os.path.join(data_dir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr.xml")) regs.clear() with use_working_directory(tmpdir): regs.load_registers_from_xml("registers_corr.xml") regs.write_xml("registers_corr1.xml") assert filecmp.cmp(os.path.join(tmpdir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr1.xml")) # Without clear - Cannot add register with same name as is already added with use_working_directory(tmpdir): regs.load_registers_from_xml("registers_corr.xml") regs.write_xml("registers_corr1.xml") assert filecmp.cmp(os.path.join(tmpdir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr1.xml"))
class BaseConfigArea: """Base for CMPA and CFPA classes.""" CONFIG_DIR = PFR_DATA_FOLDER CONFIG_FILE = "database.json" BINARY_SIZE = 512 ROTKH_SIZE = 32 ROTKH_REGISTER = "ROTKH" MARK = b"SEAL" DESCRIPTION = "Base Config Area" IMAGE_PREFILL_PATTERN = 0 def __init__(self, device: str = None, revision: str = None, user_config: PfrConfiguration = None) -> None: """Initialize an instance. :param device: device to use, list of supported devices is available via 'devices' method :param revision: silicon revision, if not specified, the latest is being used :param user_config: PfrConfiguration with user configuration to use with initialization :raises SPSDKError: When no device is provided :raises SPSDKError: When no device is not supported :raises SPSDKError: When there is invalid revision """ if not (device or user_config): raise SPSDKError("No device provided") self.config = self._load_config() # either 'device' or 'user_config' IS defined! Mypy doesn't understand the check above self.device = device or user_config.device # type: ignore if self.device not in self.config.get_devices(): raise SPSDKError(f"Device '{self.device}' is not supported") self.revision = revision or (user_config.revision if user_config else "latest") if not self.revision or self.revision == "latest": self.revision = self.config.get_latest_revision(self.device) logger.warning( f"The silicon revision is not specified, the latest: '{self.revision}' has been used." ) if self.revision not in self.config.get_revisions(self.device): raise SPSDKError( f"Invalid revision '{self.revision}' for '{self.device}'") self.registers = Registers(self.device) self.registers.load_registers_from_xml( xml=self.config.get_data_file(self.device, self.revision), filter_reg=self.config.get_ignored_registers(self.device), grouped_regs=self.config.get_grouped_registers(self.device), ) # Set the computed field handler for reg, fields in self.config.get_computed_fields( self.device).items(): reg_obj = self.registers.find_reg(reg) reg_obj.add_setvalue_hook(self.reg_computed_fields_handler, fields) self.user_config = PfrConfiguration( config=user_config, device=self.device, revision=self.revision, cfg_type=self.__class__.__name__, ) if self.user_config.settings: self.set_config(self.user_config, raw=False) def reg_computed_fields_handler(self, val: bytes, context: Any) -> bytes: """Recalculate all fields for given register value. :param val: Input register value. :param context: The method context (fields). :return: recomputed value. :raises SPSDKPfrError: Raises when the computing routine is not found. """ fields: dict = context for method in fields.values(): if hasattr(self, method): method_ref = getattr(self, method, None) val = method_ref(val) else: raise SPSDKPfrError( f"The '{method}' compute function doesn't exists.") return val @staticmethod def pfr_reg_inverse_high_half(val: int) -> int: """Function that inverse low 16-bits of register value to high 16 bits. :param val: Input current reg value. :return: Returns the complete register value with updated higher half field. """ ret = val & 0xFFFF ret |= (ret ^ 0xFFFF) << 16 return ret @classmethod def _load_config(cls) -> RegConfig: """Loads the PFR block configuration file. :return: PFR block configuration database. """ return RegConfig(os.path.join(cls.CONFIG_DIR, cls.CONFIG_FILE)) @classmethod def devices(cls) -> List[str]: """Classmethod to get list of supported devices. :return: List of supported devices. """ config = cls._load_config() return config.get_devices() def _get_registers(self) -> List[RegsRegister]: """Get a list of all registers. :return: List of PFR configuration registers. """ exclude = self.config.get_ignored_registers(self.device) return self.registers.get_registers(exclude) def set_config(self, config: PfrConfiguration, raw: bool = False) -> None: """Apply configuration from file. :param config: PFR configuration. :param raw: When set all (included computed fields) configuration will be applied. :raises SPSDKError: When device is not provided. :raises SPSDKError: When revision is not provided. :raises SPSDKPfrConfigError: Invalid config file. """ if not self.device: raise SPSDKError("No device provided") if not self.revision: raise SPSDKError("No revision provided") if config.device != self.device: raise SPSDKPfrConfigError( f"Invalid device in configuration. {self.device} != {config.device}" ) if not config.revision or config.revision in ("latest", ""): config.revision = self.config.get_latest_revision(self.device) logger.warning( f"The configuration file doesn't contains silicon revision, \ the latest: '{config.revision}' has been used.") if config.revision != self.revision: raise SPSDKPfrConfigError( f"Invalid revision in configuration. {self.revision} != {config.revision}" ) if config.type and config.type.upper() != self.__class__.__name__: raise SPSDKPfrConfigError( f"Invalid configuration type. {self.__class__.__name__} != {config.type}" ) if not config.settings: raise SPSDKPfrConfigError("Missing configuration of PFR fields!") computed_regs = [] computed_regs.extend(self.config.get_ignored_registers(self.device)) if not raw: computed_regs.extend( self.config.get_computed_registers(self.device)) computed_fields = None if raw else self.config.get_computed_fields( self.device) self.registers.load_yml_config(config.settings, computed_regs, computed_fields) if not raw: # # Just update only configured registers exclude_hooks = [] if not self.config.get_value("mandatory_computed_regs", self.device): exclude_hooks.extend( list( set(self.registers.get_reg_names()) - set(config.settings.keys()))) self.registers.run_hooks(exclude_hooks) def get_yaml_config(self, exclude_computed: bool = True, diff: bool = False, indent: int = 0) -> CM: """Return YAML configuration from loaded registers. :param exclude_computed: Omit computed registers and fields. :param diff: Get only configuration with difference value to reset state. :param indent: YAML start indent. :return: YAML PFR configuration in commented map(ordered dict). """ computed_regs = (None if not exclude_computed else self.config.get_computed_registers(self.device)) computed_fields = (None if not exclude_computed else self.config.get_computed_fields(self.device)) ignored_fields = self.config.get_ignored_fields(self.device) data = self.registers.create_yml_config(computed_regs, computed_fields, ignored_fields, diff, indent + 2) return self.user_config.get_yaml_config(data, indent) def generate_config(self, exclude_computed: bool = True) -> CM: """Generate configuration structure for user configuration. :param exclude_computed: Exclude computed fields, defaults to True. :return: YAML commented map with PFR configuration in reset state. """ # Create own copy to keep self as is and get reset values by standard YML output copy_of_self = copy.deepcopy(self) copy_of_self.registers.reset_values() return copy_of_self.get_yaml_config(exclude_computed) def _calc_rotkh(self, keys: List[PublicKey]) -> bytes: """Calculate ROTKH (Root Of Trust Key Hash). :param keys: List of Keys to compute ROTKH. :return: Value of ROTKH with right width. :raises SPSDKPfrError: Algorithm width doesn't fit into ROTKH field. """ # the data structure use for computing final ROTKH is 4*32B long # 32B is a hash of individual keys # 4 is the max number of keys, if a key is not provided the slot is filled with '\x00' # The LPC55S3x has two options to compute ROTKH, so it's needed to be # detected the right algorithm and mandatory warn user about this selection because # it's MUST correspond to settings in eFuses! reg_rotkh = self.registers.find_reg("ROTKH") width = reg_rotkh.width if isinstance(keys[0], rsa.RSAPublicKey): algorithm_width = 256 else: algorithm_width = keys[0].key_size if algorithm_width > width: raise SPSDKPfrError( "The ROTKH field is smaller than used algorithm width.") key_hashes = [ calc_pub_key_hash(key, openssl_backend, algorithm_width) for key in keys ] data = [ key_hashes[i] if i < len(key_hashes) else bytes(algorithm_width // 8) for i in range(4) ] return openssl_backend.hash(bytearray().join(data), f"sha{algorithm_width}").ljust( width // 8, b"\x00") def _get_seal_start_address(self) -> int: """Function returns start of seal fields for the device. :return: Start of seals fields. :raises SPSDKError: When 'seal_start_address' in database.json can not be found """ start = self.config.get_seal_start_address(self.device) if not start: raise SPSDKError( "Can't find 'seal_start_address' in database.json") return self.registers.find_reg(start).offset def _get_seal_count(self) -> int: """Function returns seal count for the device. :return: Count of seals fields. :raises SPSDKError: When 'seal_count' in database.json can not be found """ count = self.config.get_seal_count(self.device) if not count: raise SPSDKError("Can't find 'seal_count' in database.json") return value_to_int(count) def export(self, add_seal: bool = False, keys: List[PublicKey] = None) -> bytes: """Generate binary output. :param add_seal: The export is finished in the PFR record by seal. :param keys: List of Keys to compute ROTKH field. :return: Binary block with PFR configuration(CMPA or CFPA). :raises SPSDKPfrRotkhIsNotPresent: This PFR block doesn't contains ROTKH field. :raises SPSDKError: The size of data is {len(data)}, is not equal to {self.BINARY_SIZE}. """ if keys: try: # ROTKH may or may not be present, derived class defines its presense rotkh_reg = self.registers.find_reg(self.ROTKH_REGISTER) rotkh_data = self._calc_rotkh(keys) rotkh_reg.set_value(rotkh_data, True) except SPSDKRegsErrorRegisterNotFound as exc: raise SPSDKPfrRotkhIsNotPresent( "This device doesn't contain ROTKH register!") from exc data = bytearray([self.IMAGE_PREFILL_PATTERN] * self.BINARY_SIZE) for reg in self._get_registers(): data[reg.offset:reg.offset + reg.width // 8] = reg.get_bytes_value() if add_seal: seal_start = self._get_seal_start_address() seal_count = self._get_seal_count() data[seal_start:seal_start + seal_count * 4] = self.MARK * seal_count if len(data) != self.BINARY_SIZE: raise SPSDKError( f"The size of data is {len(data)}, is not equal to {self.BINARY_SIZE}" ) return bytes(data) def parse(self, data: bytes) -> None: """Parse input binary data to registers. :param data: Input binary data of PFR block. """ for reg in self._get_registers(): value = bytearray(data[reg.offset:reg.offset + reg.width // 8]) # don't change endian if register is meant to be used in 'reverse' (array of bytes) reg.set_value(value if reg.reverse else change_endianism(value), raw=True)
def test_basic_regs(tmpdir): """Basic test of registers class.""" regs = Registers(TEST_DEVICE_NAME) assert regs.dev_name == TEST_DEVICE_NAME reg1 = RegsRegister(TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS) with pytest.raises(RegisterNotFound): regs.find_reg("NonExisting") # Ther Registers MUST return empty erray assert regs.get_reg_names() == [] with pytest.raises(TypeError): regs.remove_register("String") with pytest.raises(ValueError): regs.remove_register(reg1) # Now we could do tests with a register added to list regs.add_register(reg1) regs.remove_register_by_name(["String"]) assert TEST_REG_NAME in regs.get_reg_names() regt = regs.find_reg(TEST_REG_NAME) assert regt == reg1 with pytest.raises(TypeError): regs.add_register("Invalid Parameter") regt.set_value(TEST_REG_VALUE) assert reg1.get_value() == TEST_REG_VALUE.to_bytes(4, "big") filename = os.path.join(tmpdir, TEST_XML_FILE) regs.write_xml(filename) assert os.path.isfile(filename) printed_str = str(regs) assert TEST_DEVICE_NAME in printed_str assert TEST_REG_NAME in printed_str regs.remove_register_by_name([TEST_REG_NAME]) with pytest.raises(RegisterNotFound): regs.find_reg(TEST_REG_NAME) assert False
class ShadowRegisters(): """SPSDK support to control the shadow registers.""" def __init__(self, debug_probe: DebugProbe, config: RegConfig, device: str, revision: str = "latest") -> None: """Initialization of Shadow register class.""" self._probe = debug_probe self.config = config self.device = device self.offset = int(self.config.get_address(self.device, remove_underscore=True), 16) self.regs = Registers(self.device) rev = revision if revision != "latest" else config.get_latest_revision(self.device) self.regs.load_registers_from_xml(config.get_data_file(self.device, rev)) def _write_shadow_reg(self, addr: int, data: int, verify: int = True) -> None: """The function write a shadow register. The funstion writes shadow register in to MCU and verify the write if requested. param addr: Shadow register address. param data: Shadow register data to write. param verify: If True the write is read back and compare, otherwise no check is done raises IoVerificationError """ self._probe.mem_reg_write(addr, data) if verify: readback = self._probe.mem_reg_read(addr) if readback != data: raise IoVerificationError(f"The written data 0x{data:08X} to 0x{addr:08X} address are invalid.") def reload_registers(self) -> None: """Reload all the values in managed registers.""" for reg in self.regs.registers: self.reload_register(reg) def sets_all_registers(self) -> None: """Update all shadow registers in target by local values.""" for reg in self.regs.registers: self.set_register(reg.name, reg.get_value()) def reload_register(self, reg: RegsRegister) -> None: """Reload the value in requested register. :param reg: The register to reload from the HW. """ reg.set_value(self.get_register(reg.name)) @staticmethod def _reverse_bytes_in_longs(arr: bytearray) -> bytearray: """The function reverse byte order in longs from input bytes. param arr: Input array. :return: New array with reversed bytes. :raises ValueError: Raises when invalid value is in input. """ arr_len = len(arr) if arr_len % 4 != 0: raise ValueError("The input array is not in modulo 4!") result = bytearray() for x in range(arr_len): word = bytearray(arr[x*4:x*4+4]) word.reverse() result.extend(word) return result 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 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 create_yml_config(self, file_name: str, raw: bool = False) -> None: """The function creates the configuration YML file. :param file_name: The file_name (without extension) of stored configuration. :param raw: Raw output of configuration (including computed fields and anti-pole registers) """ CM = ruamel.yaml.comments.CommentedMap # defaults to block style antipole_regs = self.config.get_antipole_regs(self.device) computed_fields = self.config.get_computed_fields(self.device) yaml = YAML() yaml.indent(sequence=4, offset=2) data = CM() data["registers"] = CM() for reg in self.regs.registers: if not raw and reg.name in antipole_regs.values(): continue reg_yml = CM() reg_yml.yaml_set_start_comment("Reg Description:" + reg.description) reg_yml.insert(1, "name", reg.name, comment="The name of the register") data["registers"][reg.name] = reg_yml if len(reg.get_bitfields()) > 0: btf_yml = CM() reg_yml["bitfields"] = btf_yml for i, bitf in enumerate(reg.get_bitfields()): if not raw and reg.name in computed_fields.keys() and bitf.name in computed_fields[reg.name].keys(): continue possible_values = "" if bitf.has_enums(): # print the comments as a hint of possible values possible_values = f", (Possible values: {', '.join(bitf.get_enum_names())})" btf_yml.insert(i, bitf.name, bitf.get_enum_value(), comment=f"The width: {bitf.width} bits{possible_values}") else: reg_yml.insert(2, "value", reg.get_hex_value(), comment="The value of the register") with open(file_name, "w") as out_file: yaml.dump(data, out_file) 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.") @staticmethod def antipolize_reg(val: bytes) -> bytes: """Antipolize given register value. :param val: Input register value. :return: Antipolized value. """ newval = [0]*len(val) for i, val_byte in enumerate(val): newval[i] = val_byte ^ 0xFF return bytes(newval) # CRC8 - ITU @staticmethod def crc_update(data: bytes, crc: int = 0, is_final: bool = True) -> int: """The function compute the CRC8 ITU method from given bytes. :param data: Input data to compute CRC. :param crc: The seed for CRC. :param is_final: The flag the the function should retrn final result. :return: The CRC result. """ k = 0 data_len = len(data) while data_len != 0: data_len -= 1 c = data[k] k += 1 for i in range(8): bit = (crc & 0x80) != 0 if (c & (0x80>>i)) != 0: bit = not bit crc <<= 1 if bit: crc ^= 0x07 crc &= 0xff if is_final: return (crc & 0xff) ^ 0x55 else: return crc & 0xff def comalg_dcfg_cc_socu_crc8(self, val: bytes) -> bytes: """Function that creates the crc for DCFG_CC_SOCU. :param val: Input DCFG_CC_SOCU Value. :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. """ ret = [0]*4 ret[0:3] = val[0:3] input = bytearray(val[0:3]) input.reverse() ret[3] = self.crc_update(input) return bytes(ret) def comalg_dcfg_cc_socu_rsvd(self, val: bytes) -> bytes: """Function fill up the DCFG_CC_SOCU RSVD filed by 0x40 to satisfy MCU needs. :param val: Input DCFG_CC_SOCU Value. :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. """ new_val = bytearray(val) new_val[0] &= ~0xFE new_val[0] |= 0x40 return new_val def comalg_do_nothig(self, val: bytes) -> bytes: """Function that do nothing. :param val: Input Value. :return: Returns same value as it get. """ return val