From b28ec5e54702b256c60900bd924e88c10920391e Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Fri, 31 May 2024 19:49:27 +0200 Subject: [PATCH 1/7] [fi] Add OTFI base class Shared implementation logic in OTFI Ibex, Otbn, and Crypto is moved in a base class named `OTFI`. --- target/communication/fi_crypto_commands.py | 69 +++----------- target/communication/fi_ibex_commands.py | 102 ++++++--------------- target/communication/fi_otbn_commands.py | 70 ++++---------- target/communication/otfi.py | 62 +++++++++++++ 4 files changed, 121 insertions(+), 182 deletions(-) create mode 100644 target/communication/otfi.py diff --git a/target/communication/fi_crypto_commands.py b/target/communication/fi_crypto_commands.py index 1c9df92f..4d046e20 100644 --- a/target/communication/fi_crypto_commands.py +++ b/target/communication/fi_crypto_commands.py @@ -10,33 +10,18 @@ from typing import Optional -class OTFICrypto: - def __init__(self, target) -> None: - self.target = target +from target.communication.otfi import OTFI - def _ujson_crypto_cmd(self) -> None: - time.sleep(0.01) - self.target.write(json.dumps("CryptoFi").encode("ascii")) - time.sleep(0.01) - def init(self) -> None: - """ Initialize the Crypto FI code on the chip. - Returns: - The device ID of the device. - """ - # CryptoFi command. - self._ujson_crypto_cmd() - # Init command. - time.sleep(0.01) - self.target.write(json.dumps("Init").encode("ascii")) - # Read back device ID from device. - return self.read_response(max_tries=30) +class OTFICrypto(OTFI): + def __init__(self, target) -> None: + super().__init__(target, "Crypto") def crypto_shadow_reg_access(self) -> None: """ Starts the crypto.fi.shadow_reg_access test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # ShadowRegAccess command. time.sleep(0.01) self.target.write(json.dumps("ShadowRegAccess").encode("ascii")) @@ -45,7 +30,7 @@ def crypto_aes_key(self) -> None: """ Starts the crypto.fi.aes_key test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Aes command. time.sleep(0.01) self.target.write(json.dumps("Aes").encode("ascii")) @@ -59,7 +44,7 @@ def crypto_aes_plaintext(self) -> None: """ Starts the crypto.fi.aes_plaintext test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Aes command. time.sleep(0.01) self.target.write(json.dumps("Aes").encode("ascii")) @@ -73,7 +58,7 @@ def crypto_aes_encrypt(self) -> None: """ Starts the crypto.fi.aes_encrypt test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Aes command. time.sleep(0.01) self.target.write(json.dumps("Aes").encode("ascii")) @@ -87,7 +72,7 @@ def crypto_aes_ciphertext(self) -> None: """ Starts the crypto.fi.aes_ciphertext test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Aes command. time.sleep(0.01) self.target.write(json.dumps("Aes").encode("ascii")) @@ -101,7 +86,7 @@ def crypto_kmac_key(self) -> None: """ Starts the crypto.fi.kmac_key test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Kmac command. time.sleep(0.01) self.target.write(json.dumps("Kmac").encode("ascii")) @@ -115,7 +100,7 @@ def crypto_kmac_absorb(self) -> None: """ Starts the crypto.fi.kmac_absorb test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Kmac command. time.sleep(0.01) self.target.write(json.dumps("Kmac").encode("ascii")) @@ -129,7 +114,7 @@ def crypto_kmac_squeeze(self) -> None: """ Starts the crypto.fi.kmac_squeeze test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Kmac command. time.sleep(0.01) self.target.write(json.dumps("Kmac").encode("ascii")) @@ -143,7 +128,7 @@ def crypto_kmac_static(self) -> None: """ Starts the crypto.fi.kmac_static test. """ # CryptoFi command. - self._ujson_crypto_cmd() + self._ujson_fi_cmd() # Kmac command. time.sleep(0.01) self.target.write(json.dumps("Kmac").encode("ascii")) @@ -152,31 +137,3 @@ def crypto_kmac_static(self) -> None: mode = {"key_trigger": False, "absorb_trigger": False, "static_trigger": True, "squeeze_trigger": False} self.target.write(json.dumps(mode).encode("ascii")) - - def start_test(self, cfg: dict) -> None: - """ Start the selected test. - - Call the function selected in the config file. Uses the getattr() - construct to call the function. - - Args: - cfg: Config dict containing the selected test. - """ - test_function = getattr(self, cfg["test"]["which_test"]) - test_function() - - def read_response(self, max_tries: Optional[int] = 1) -> str: - """ Read response from Crypto FI framework. - Args: - max_tries: Maximum number of attempts to read from UART. - - Returns: - The JSON response of OpenTitan. - """ - it = 0 - while it != max_tries: - read_line = str(self.target.readline()) - if "RESP_OK" in read_line: - return read_line.split("RESP_OK:")[1].split(" CRC:")[0] - it += 1 - return "" diff --git a/target/communication/fi_ibex_commands.py b/target/communication/fi_ibex_commands.py index 8785bae0..03a3983e 100644 --- a/target/communication/fi_ibex_commands.py +++ b/target/communication/fi_ibex_commands.py @@ -10,19 +10,18 @@ from typing import Optional -class OTFIIbex: - def __init__(self, target) -> None: - self.target = target +from target.communication.otfi import OTFI - def _ujson_ibex_fi_cmd(self) -> None: - time.sleep(0.01) - self.target.write(json.dumps("IbexFi").encode("ascii")) + +class OTFIIbex(OTFI): + def __init__(self, target) -> None: + super().__init__(target, "Ibex") def ibex_char_unrolled_reg_op_loop(self) -> None: """ Starts the ibex.char.unrolled_reg_op_loop test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharUnrolledRegOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharUnrolledRegOpLoop").encode("ascii")) @@ -31,7 +30,7 @@ def ibex_char_unrolled_mem_op_loop(self) -> None: """ Starts the ibex.char.unrolled_mem_op_loop test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharUnrolledMemOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharUnrolledMemOpLoop").encode("ascii")) @@ -40,7 +39,7 @@ def ibex_char_reg_op_loop(self) -> None: """ Starts the ibex.char.reg_op_loop test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharRegOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharRegOpLoop").encode("ascii")) @@ -49,7 +48,7 @@ def ibex_char_mem_op_loop(self) -> None: """ Starts the ibex.char.mem_op_loop test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharMemOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharMemOpLoop").encode("ascii")) @@ -58,7 +57,7 @@ def ibex_char_flash_read(self) -> None: """ Starts the ibex.char.flash_read test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharFlashRead command. time.sleep(0.01) self.target.write(json.dumps("CharFlashRead").encode("ascii")) @@ -67,7 +66,7 @@ def ibex_char_flash_write(self) -> None: """ Starts the ibex.char.flash_write test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharFlashWrite command. time.sleep(0.01) self.target.write(json.dumps("CharFlashWrite").encode("ascii")) @@ -76,7 +75,7 @@ def ibex_char_sram_read(self) -> None: """ Starts the ibex.char.sram_read test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharSramRead command. time.sleep(0.01) self.target.write(json.dumps("CharSramRead").encode("ascii")) @@ -85,7 +84,7 @@ def ibex_char_sram_write_static_unrolled(self) -> None: """ Starts the ibex.char.sram_write_static_unrolled test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharSramWriteStaticUnrolled command. time.sleep(0.01) self.target.write(json.dumps("CharSramWriteStaticUnrolled").encode("ascii")) @@ -94,7 +93,7 @@ def ibex_char_sram_write_read(self) -> None: """ Starts the ibex.char.sram_write_read test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharSramWriteRead command. time.sleep(0.01) self.target.write(json.dumps("CharSramWriteRead").encode("ascii")) @@ -103,7 +102,7 @@ def ibex_char_sram_write(self) -> None: """ Starts the ibex.char.sram_write test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharSramWrite command. time.sleep(0.01) self.target.write(json.dumps("CharSramWrite").encode("ascii")) @@ -112,7 +111,7 @@ def ibex_char_sram_static(self) -> None: """ Starts the ibex.char.sram_static test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharSramWrite command. time.sleep(0.01) self.target.write(json.dumps("CharSramStatic").encode("ascii")) @@ -121,7 +120,7 @@ def ibex_char_conditional_branch_beq(self) -> None: """ Starts the ibex.char.conditional_branch_beq test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBeq command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBeq").encode("ascii")) @@ -130,7 +129,7 @@ def ibex_char_conditional_branch_bne(self) -> None: """ Starts the ibex.char.conditional_branch_bne test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBne command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBne").encode("ascii")) @@ -139,7 +138,7 @@ def ibex_char_conditional_branch_bge(self) -> None: """ Starts the ibex.char.conditional_branch_bge test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBge command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBge").encode("ascii")) @@ -148,7 +147,7 @@ def ibex_char_conditional_branch_bgeu(self) -> None: """ Starts the ibex.char.conditional_branch_bgeu test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBgeu command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBgeu").encode("ascii")) @@ -157,7 +156,7 @@ def ibex_char_conditional_branch_blt(self) -> None: """ Starts the ibex.char.conditional_branch_blt test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBglt command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBlt").encode("ascii")) @@ -166,7 +165,7 @@ def ibex_char_conditional_branch_bltu(self) -> None: """ Starts the ibex.char.conditional_branch_bltu test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCondBranchBgltu command. time.sleep(0.01) self.target.write(json.dumps("CharCondBranchBltu").encode("ascii")) @@ -175,7 +174,7 @@ def ibex_char_unconditional_branch(self) -> None: """ Starts the ibex.char.unconditional_branch test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharUncondBranch command. time.sleep(0.01) self.target.write(json.dumps("CharUncondBranch").encode("ascii")) @@ -184,7 +183,7 @@ def ibex_char_unconditional_branch_nop(self) -> None: """ Starts the ibex.char.unconditional_branch_nop test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharUncondBranchNop command. time.sleep(0.01) self.target.write(json.dumps("CharUncondBranchNop").encode("ascii")) @@ -193,7 +192,7 @@ def ibex_char_register_file(self) -> None: """ Starts the ibex.char.register_file test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharRegisterFile command. time.sleep(0.01) self.target.write(json.dumps("CharRegisterFile").encode("ascii")) @@ -202,41 +201,16 @@ def ibex_char_register_file_read(self) -> None: """ Starts the ibex.char.register_file_read test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharRegisterFileRead command. time.sleep(0.01) self.target.write(json.dumps("CharRegisterFileRead").encode("ascii")) - def init(self) -> list: - """ Initialize the Ibex FI code on the chip. - Returns: - The device ID of the device. - """ - # IbexFi command. - self._ujson_ibex_fi_cmd() - # InitTrigger command. - time.sleep(0.01) - self.target.write(json.dumps("Init").encode("ascii")) - # Read back device ID from device. - return self.read_response(max_tries=30) - - def start_test(self, cfg: dict) -> None: - """ Start the selected test. - - Call the function selected in the config file. Uses the getattr() - construct to call the function. - - Args: - cfg: Config dict containing the selected test. - """ - test_function = getattr(self, cfg["test"]["which_test"]) - test_function() - def ibex_char_csr_write(self) -> None: """ Starts the ibex.fi.char.csr_write test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCsrWrite command. time.sleep(0.01) self.target.write(json.dumps("CharCsrWrite").encode("ascii")) @@ -245,7 +219,7 @@ def ibex_char_csr_read(self) -> None: """ Starts the ibex.fi.char.csr_read test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # CharCsrRead command. time.sleep(0.01) self.target.write(json.dumps("CharCsrRead").encode("ascii")) @@ -254,7 +228,7 @@ def ibex_address_translation_config(self) -> None: """ Starts the ibex.fi.address_translation_config test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # AddressTranslationCfg command. time.sleep(0.01) self.target.write(json.dumps("AddressTranslationCfg").encode("ascii")) @@ -263,23 +237,7 @@ def ibex_address_translation(self) -> None: """ Starts the ibex.fi.address_translation test. """ # IbexFi command. - self._ujson_ibex_fi_cmd() + self._ujson_fi_cmd() # AddressTranslation command. time.sleep(0.01) self.target.write(json.dumps("AddressTranslation").encode("ascii")) - - def read_response(self, max_tries: Optional[int] = 1) -> str: - """ Read response from Ibex FI framework. - Args: - max_tries: Maximum number of attempts to read from UART. - - Returns: - The JSON response of OpenTitan. - """ - it = 0 - while it != max_tries: - read_line = str(self.target.readline()) - if "RESP_OK" in read_line: - return read_line.split("RESP_OK:")[1].split(" CRC:")[0] - it += 1 - return "" diff --git a/target/communication/fi_otbn_commands.py b/target/communication/fi_otbn_commands.py index 371c2b35..2e38d1e9 100644 --- a/target/communication/fi_otbn_commands.py +++ b/target/communication/fi_otbn_commands.py @@ -10,19 +10,22 @@ from typing import Optional -class OTFIOtbn: +from otfi import OTFI + + +class OTFIOtbn(OTFI): def __init__(self, target) -> None: - self.target = target + super().__init__(target, "Otbn") - def _ujson_otbn_fi_cmd(self) -> None: - time.sleep(0.01) - self.target.write(json.dumps("OtbnFi").encode("ascii")) + def init(self, test: Optional[str] = "") -> None: + self.init_keymgr(test) + super().init(test) def otbn_char_unrolled_reg_op_loop(self) -> None: """ Starts the otbn.fi.char.unrolled.reg.op.loop test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # CharUnrolledRegOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharUnrolledRegOpLoop").encode("ascii")) @@ -31,7 +34,7 @@ def otbn_char_unrolled_dmem_op_loop(self) -> None: """ Starts the otbn.fi.char.unrolled.dmem.op.loop test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # CharUnrolledDmemOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharUnrolledDmemOpLoop").encode("ascii")) @@ -40,7 +43,7 @@ def otbn_char_hardware_reg_op_loop(self) -> None: """ Starts the otbn.fi.char.hardware.reg.op.loop test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # CharHardwareRegOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharHardwareRegOpLoop").encode("ascii")) @@ -49,7 +52,7 @@ def otbn_char_hardware_dmem_op_loop(self) -> None: """ Starts the otbn.fi.char.hardware.dmem.op.loop test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # CharMemOpLoop command. time.sleep(0.01) self.target.write(json.dumps("CharHardwareDmemOpLoop").encode("ascii")) @@ -58,7 +61,7 @@ def otbn_key_sideload(self) -> None: """ Starts the otbn.fi.key_sideload test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # KeySideload command. time.sleep(0.01) self.target.write(json.dumps("KeySideload").encode("ascii")) @@ -67,7 +70,7 @@ def otbn_load_integrity(self) -> None: """ Starts the otbn.fi.load_integrity test. """ # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # LoadIntegrity command. time.sleep(0.01) self.target.write(json.dumps("LoadIntegrity").encode("ascii")) @@ -80,55 +83,14 @@ def init_keymgr(self, test: str) -> None: """ if "key_sideload" in test: # OtbnFi command. - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() # InitTrigger command. time.sleep(0.01) self.target.write(json.dumps("InitKeyMgr").encode("ascii")) # As the init resets the chip, we need to call it again to complete # the initialization of the key manager. time.sleep(2) - self._ujson_otbn_fi_cmd() + self._ujson_fi_cmd() time.sleep(0.01) self.target.write(json.dumps("InitKeyMgr").encode("ascii")) time.sleep(2) - - def init(self) -> None: - """ Initialize the OTBN FI code on the chip. - Returns: - The device ID of the device. - """ - # OtbnFi command. - self._ujson_otbn_fi_cmd() - # Init command. - time.sleep(0.01) - self.target.write(json.dumps("Init").encode("ascii")) - # Read back device ID from device. - return self.read_response(max_tries=30) - - def start_test(self, cfg: dict) -> None: - """ Start the selected test. - - Call the function selected in the config file. Uses the getattr() - construct to call the function. - - Args: - cfg: Config dict containing the selected test. - """ - test_function = getattr(self, cfg["test"]["which_test"]) - test_function() - - def read_response(self, max_tries: Optional[int] = 1) -> str: - """ Read response from Otbn FI framework. - Args: - max_tries: Maximum number of attempts to read from UART. - - Returns: - The JSON response of OpenTitan. - """ - it = 0 - while it != max_tries: - read_line = str(self.target.readline()) - if "RESP_OK" in read_line: - return read_line.split("RESP_OK:")[1].split(" CRC:")[0] - it += 1 - return "" diff --git a/target/communication/otfi.py b/target/communication/otfi.py new file mode 100644 index 00000000..8e4618b8 --- /dev/null +++ b/target/communication/otfi.py @@ -0,0 +1,62 @@ +"""Base class of communication interface for OpenTitan FI framework. + +Communication with OpenTitan happens over the uJSON command interface. +""" +import json +import time +from typing import Optional + + +class OTFI: + IP = ["Ibex", "Otbn", "Crypto"] + def __init__(self, target, ip) -> None: + self.target = target + self.ip = ip + + assert self.ip in OTFI.IP, "ip ({self.ip} not in OTFI.IP ({OTFI.IP})" + + def _ujson_fi_cmd(self) -> None: + time.sleep(0.01) + self.target.write(json.dumps(f"{self.ip}Fi").encode("ascii")) + time.sleep(0.01) + + def init(self, test: Optional[str] = "") -> None: + """ Initialize the FI code on the chip. + Returns: + The device ID of the device. + """ + # Fi command. + self._ujson_fi_cmd() + # Init command. + time.sleep(0.01) + self.target.write(json.dumps("Init").encode("ascii")) + # Read back device ID from device. + return self.read_response(max_tries=30) + + def start_test(self, cfg: dict) -> None: + """ Start the selected test. + + Call the function selected in the config file. Uses the getattr() + construct to call the function. + + Args: + cfg: Config dict containing the selected test. + """ + test_function = getattr(self, cfg["test"]["which_test"]) + test_function() + + def read_response(self, max_tries: Optional[int] = 1) -> str: + """ Read response from Crypto FI framework. + Args: + max_tries: Maximum number of attempts to read from UART. + + Returns: + The JSON response of OpenTitan. + """ + it = 0 + while it != max_tries: + read_line = str(self.target.readline()) + if "RESP_OK" in read_line: + return read_line.split("RESP_OK:")[1].split(" CRC:")[0] + it += 1 + return "" From 2f30aafd3d226241f9296f698078826fe70f9b22 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 19:22:58 +0200 Subject: [PATCH 2/7] [fi] Add OTFITest base class --- target/communication/otfi_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 target/communication/otfi_test.py diff --git a/target/communication/otfi_test.py b/target/communication/otfi_test.py new file mode 100644 index 00000000..7d250093 --- /dev/null +++ b/target/communication/otfi_test.py @@ -0,0 +1,9 @@ +"""Class of tests for OpenTitan FI framework. +""" + + +class OTFITest: + def __init__(self, name, cmd=None, mode=None): + self.name = name + self.cmd = "".join(s.capitalize() for s in name.split("_")) if cmd is None else cmd + self.mode = mode From 1e2ffbfa943c3d3d96b09703bc0c173fb0b05967 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 17:57:06 +0200 Subject: [PATCH 3/7] [fi] OTFI: Use OTFITest --- target/communication/otfi.py | 39 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/target/communication/otfi.py b/target/communication/otfi.py index 8e4618b8..99c23a71 100644 --- a/target/communication/otfi.py +++ b/target/communication/otfi.py @@ -7,13 +7,18 @@ from typing import Optional +from target.communication.otfi_test import OTFITest + + class OTFI: - IP = ["Ibex", "Otbn", "Crypto"] + TESTS = [] + IP = ["Ibex", "Otbn", "Crypto", "Rng"] + def __init__(self, target, ip) -> None: self.target = target self.ip = ip - assert self.ip in OTFI.IP, "ip ({self.ip} not in OTFI.IP ({OTFI.IP})" + assert self.ip in OTFI.IP, f"ip ({self.ip} not in OTFI.IP ({OTFI.IP})" def _ujson_fi_cmd(self) -> None: time.sleep(0.01) @@ -21,7 +26,7 @@ def _ujson_fi_cmd(self) -> None: time.sleep(0.01) def init(self, test: Optional[str] = "") -> None: - """ Initialize the FI code on the chip. + """Initialize the FI code on the chip. Returns: The device ID of the device. """ @@ -33,8 +38,10 @@ def init(self, test: Optional[str] = "") -> None: # Read back device ID from device. return self.read_response(max_tries=30) - def start_test(self, cfg: dict) -> None: - """ Start the selected test. + def start_test( + self, cfg: Optional[dict] = {}, testname: Optional[str] = "" + ) -> None: + """Start the selected test. Call the function selected in the config file. Uses the getattr() construct to call the function. @@ -42,11 +49,27 @@ def start_test(self, cfg: dict) -> None: Args: cfg: Config dict containing the selected test. """ - test_function = getattr(self, cfg["test"]["which_test"]) - test_function() + if cfg != {}: + testname = cfg["test"]["which_test"] + testname = testname.replace(f"{self.ip.lower()}_", "", 1) + tests = [test for test in self.TESTS if test.name == testname] + assert not len(tests) == 0, f"{testname} not found in {self.TESTS}" + assert len(tests) == 1, f"Test duplicates with name {testname}" + self._run_test(tests[0]) + + def _run_test(self, test: OTFITest) -> None: + # OTFIx Fi command. + self._ujson_fi_cmd() + # Test command. + time.sleep(0.01) + self.target.write(json.dumps(test.cmd).encode("ascii")) + # Test mode. + if test.mode is not None: + time.sleep(0.01) + self.target.write(json.dumps(test.mode).encode("ascii")) def read_response(self, max_tries: Optional[int] = 1) -> str: - """ Read response from Crypto FI framework. + """Read response from Crypto FI framework. Args: max_tries: Maximum number of attempts to read from UART. From 7c0fc01edcfdd51e14bb2b35c0e513f56e248ea6 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 17:57:35 +0200 Subject: [PATCH 4/7] [fi] OTFIOtbn: Use OTFITest --- target/communication/fi_otbn_commands.py | 66 ++++-------------------- 1 file changed, 11 insertions(+), 55 deletions(-) diff --git a/target/communication/fi_otbn_commands.py b/target/communication/fi_otbn_commands.py index 2e38d1e9..418e6f13 100644 --- a/target/communication/fi_otbn_commands.py +++ b/target/communication/fi_otbn_commands.py @@ -10,10 +10,20 @@ from typing import Optional -from otfi import OTFI +from target.communication.otfi import OTFI +from target.communication.otfi_test import OTFITest class OTFIOtbn(OTFI): + TESTS = [ + OTFITest("char_unrolled_reg_op_loop"), + OTFITest("char_unrolled_dmem_op_loop"), + OTFITest("char_hardware_reg_op_loop"), + OTFITest("char_hardware_dmem_op_loop"), + OTFITest("key_sideload"), + OTFITest("load_integrity"), + ] + def __init__(self, target) -> None: super().__init__(target, "Otbn") @@ -21,60 +31,6 @@ def init(self, test: Optional[str] = "") -> None: self.init_keymgr(test) super().init(test) - def otbn_char_unrolled_reg_op_loop(self) -> None: - """ Starts the otbn.fi.char.unrolled.reg.op.loop test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # CharUnrolledRegOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharUnrolledRegOpLoop").encode("ascii")) - - def otbn_char_unrolled_dmem_op_loop(self) -> None: - """ Starts the otbn.fi.char.unrolled.dmem.op.loop test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # CharUnrolledDmemOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharUnrolledDmemOpLoop").encode("ascii")) - - def otbn_char_hardware_reg_op_loop(self) -> None: - """ Starts the otbn.fi.char.hardware.reg.op.loop test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # CharHardwareRegOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharHardwareRegOpLoop").encode("ascii")) - - def otbn_char_hardware_dmem_op_loop(self) -> None: - """ Starts the otbn.fi.char.hardware.dmem.op.loop test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # CharMemOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharHardwareDmemOpLoop").encode("ascii")) - - def otbn_key_sideload(self) -> None: - """ Starts the otbn.fi.key_sideload test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # KeySideload command. - time.sleep(0.01) - self.target.write(json.dumps("KeySideload").encode("ascii")) - - def otbn_load_integrity(self) -> None: - """ Starts the otbn.fi.load_integrity test. - """ - # OtbnFi command. - self._ujson_fi_cmd() - # LoadIntegrity command. - time.sleep(0.01) - self.target.write(json.dumps("LoadIntegrity").encode("ascii")) - def init_keymgr(self, test: str) -> None: """ Initialize the key manager on the chip. Args: From 6ffd978d98c75c45270f41a2a43feff861ce793e Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 19:44:11 +0200 Subject: [PATCH 5/7] [fi] OTFICrypto: Use OTFITest --- target/communication/fi_crypto_commands.py | 151 +++++---------------- 1 file changed, 32 insertions(+), 119 deletions(-) diff --git a/target/communication/fi_crypto_commands.py b/target/communication/fi_crypto_commands.py index 4d046e20..eb86983d 100644 --- a/target/communication/fi_crypto_commands.py +++ b/target/communication/fi_crypto_commands.py @@ -5,135 +5,48 @@ Communication with OpenTitan happens over the uJSON command interface. """ +import copy import json import time from typing import Optional from target.communication.otfi import OTFI +from target.communication.otfi_test import OTFITest -class OTFICrypto(OTFI): - def __init__(self, target) -> None: - super().__init__(target, "Crypto") - - def crypto_shadow_reg_access(self) -> None: - """ Starts the crypto.fi.shadow_reg_access test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # ShadowRegAccess command. - time.sleep(0.01) - self.target.write(json.dumps("ShadowRegAccess").encode("ascii")) - - def crypto_aes_key(self) -> None: - """ Starts the crypto.fi.aes_key test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Aes command. - time.sleep(0.01) - self.target.write(json.dumps("Aes").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": True, "plaintext_trigger": False, - "encrypt_trigger": False, "ciphertext_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) +MODES = { + "aes": { + "key_trigger": False, "plaintext_trigger": False, + "encrypt_trigger": False, "ciphertext_trigger": False + }, + "kmac": { + "key_trigger": False, "absorb_trigger": False, + "static_trigger": False, "squeeze_trigger": False + }, +} - def crypto_aes_plaintext(self) -> None: - """ Starts the crypto.fi.aes_plaintext test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Aes command. - time.sleep(0.01) - self.target.write(json.dumps("Aes").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "plaintext_trigger": True, - "encrypt_trigger": False, "ciphertext_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) - def crypto_aes_encrypt(self) -> None: - """ Starts the crypto.fi.aes_encrypt test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Aes command. - time.sleep(0.01) - self.target.write(json.dumps("Aes").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "plaintext_trigger": False, - "encrypt_trigger": True, "ciphertext_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) +def _get_mode(ip, mode_id): + assert ip in MODES, f"IP {ip} not in MODES ({MODES})" + mode = copy.deepcopy(MODES[ip]) + assert mode_id in mode, f"Mode id {mode_id} not in {ip} mode ({mode})" + mode[mode_id] = True + return mode - def crypto_aes_ciphertext(self) -> None: - """ Starts the crypto.fi.aes_ciphertext test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Aes command. - time.sleep(0.01) - self.target.write(json.dumps("Aes").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "plaintext_trigger": False, - "encrypt_trigger": False, "ciphertext_trigger": True} - self.target.write(json.dumps(mode).encode("ascii")) - def crypto_kmac_key(self) -> None: - """ Starts the crypto.fi.kmac_key test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Kmac command. - time.sleep(0.01) - self.target.write(json.dumps("Kmac").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": True, "absorb_trigger": False, - "static_trigger": False, "squeeze_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) - - def crypto_kmac_absorb(self) -> None: - """ Starts the crypto.fi.kmac_absorb test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Kmac command. - time.sleep(0.01) - self.target.write(json.dumps("Kmac").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "absorb_trigger": True, - "static_trigger": False, "squeeze_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) - - def crypto_kmac_squeeze(self) -> None: - """ Starts the crypto.fi.kmac_squeeze test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Kmac command. - time.sleep(0.01) - self.target.write(json.dumps("Kmac").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "absorb_trigger": False, - "static_trigger": False, "squeeze_trigger": True} - self.target.write(json.dumps(mode).encode("ascii")) +class OTFICrypto(OTFI): + TESTS = [ + OTFITest("shadow_reg_access"), + OTFITest("aes_key", "Aes", _get_mode("aes", "key_trigger")), + OTFITest("aes_plaintext", "Aes", _get_mode("aes", "plaintext_trigger")), + OTFITest("aes_encrypt", "Aes", _get_mode("aes", "encrypt_trigger")), + OTFITest("aes_ciphertext", "Aes", _get_mode("aes", "ciphertext_trigger")), + OTFITest("kmac_key", "Kmac", _get_mode("kmac", "key_trigger")), + OTFITest("kmac_absorb", "Kmac", _get_mode("kmac", "absorb_trigger")), + OTFITest("kmac_static", "Kmac", _get_mode("kmac", "static_trigger")), + OTFITest("kmac_squeeze", "Kmac", _get_mode("kmac", "squeeze_trigger")), + ] - def crypto_kmac_static(self) -> None: - """ Starts the crypto.fi.kmac_static test. - """ - # CryptoFi command. - self._ujson_fi_cmd() - # Kmac command. - time.sleep(0.01) - self.target.write(json.dumps("Kmac").encode("ascii")) - # Mode payload. - time.sleep(0.01) - mode = {"key_trigger": False, "absorb_trigger": False, - "static_trigger": True, "squeeze_trigger": False} - self.target.write(json.dumps(mode).encode("ascii")) + def __init__(self, target) -> None: + super().__init__(target, "Crypto") From 220db3783bfb376a50d90d97695fe7838836bf6a Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 19:52:40 +0200 Subject: [PATCH 6/7] [fi] OTFIIbex: Use OTFITest --- target/communication/fi_ibex_commands.py | 259 +++-------------------- 1 file changed, 29 insertions(+), 230 deletions(-) diff --git a/target/communication/fi_ibex_commands.py b/target/communication/fi_ibex_commands.py index 03a3983e..e69e6544 100644 --- a/target/communication/fi_ibex_commands.py +++ b/target/communication/fi_ibex_commands.py @@ -5,239 +5,38 @@ Communication with OpenTitan happens over the uJSON command interface. """ -import json -import time -from typing import Optional - - from target.communication.otfi import OTFI +from target.communication.otfi_test import OTFITest class OTFIIbex(OTFI): + TESTS = [ + OTFITest("char_unrolled_reg_op_loop"), + OTFITest("char_unrolled_mem_op_loop"), + OTFITest("char_reg_op_loop"), + OTFITest("char_mem_op_loop"), + OTFITest("char_flash_read"), + OTFITest("char_flash_write"), + OTFITest("char_sram_read"), + OTFITest("char_sram_write_static_unrolled"), + OTFITest("char_sram_write_read"), + OTFITest("char_sram_write"), + OTFITest("char_sram_static"), + OTFITest("char_conditional_branch_beq", "CharCondBranchBeq"), + OTFITest("char_conditional_branch_bne", "CharCondBranchBne"), + OTFITest("char_conditional_branch_bge", "CharCondBranchBge"), + OTFITest("char_conditional_branch_bgeu", "CharCondBranchBgeu"), + OTFITest("char_conditional_branch_blt", "CharCondBranchBlt"), + OTFITest("char_conditional_branch_bltu", "CharCondBranchBltu"), + OTFITest("char_unconditional_branch", "CharUncondBranch"), + OTFITest("char_unconditional_branch_nop", "CharUncondBranchNop"), + OTFITest("char_register_file"), + OTFITest("char_register_file_read"), + OTFITest("char_csr_write"), + OTFITest("char_csr_read"), + OTFITest("address_translation"), + OTFITest("address_translation_config", "AddressTranslationCfg"), + ] + def __init__(self, target) -> None: super().__init__(target, "Ibex") - - def ibex_char_unrolled_reg_op_loop(self) -> None: - """ Starts the ibex.char.unrolled_reg_op_loop test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharUnrolledRegOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharUnrolledRegOpLoop").encode("ascii")) - - def ibex_char_unrolled_mem_op_loop(self) -> None: - """ Starts the ibex.char.unrolled_mem_op_loop test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharUnrolledMemOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharUnrolledMemOpLoop").encode("ascii")) - - def ibex_char_reg_op_loop(self) -> None: - """ Starts the ibex.char.reg_op_loop test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharRegOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharRegOpLoop").encode("ascii")) - - def ibex_char_mem_op_loop(self) -> None: - """ Starts the ibex.char.mem_op_loop test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharMemOpLoop command. - time.sleep(0.01) - self.target.write(json.dumps("CharMemOpLoop").encode("ascii")) - - def ibex_char_flash_read(self) -> None: - """ Starts the ibex.char.flash_read test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharFlashRead command. - time.sleep(0.01) - self.target.write(json.dumps("CharFlashRead").encode("ascii")) - - def ibex_char_flash_write(self) -> None: - """ Starts the ibex.char.flash_write test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharFlashWrite command. - time.sleep(0.01) - self.target.write(json.dumps("CharFlashWrite").encode("ascii")) - - def ibex_char_sram_read(self) -> None: - """ Starts the ibex.char.sram_read test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharSramRead command. - time.sleep(0.01) - self.target.write(json.dumps("CharSramRead").encode("ascii")) - - def ibex_char_sram_write_static_unrolled(self) -> None: - """ Starts the ibex.char.sram_write_static_unrolled test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharSramWriteStaticUnrolled command. - time.sleep(0.01) - self.target.write(json.dumps("CharSramWriteStaticUnrolled").encode("ascii")) - - def ibex_char_sram_write_read(self) -> None: - """ Starts the ibex.char.sram_write_read test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharSramWriteRead command. - time.sleep(0.01) - self.target.write(json.dumps("CharSramWriteRead").encode("ascii")) - - def ibex_char_sram_write(self) -> None: - """ Starts the ibex.char.sram_write test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharSramWrite command. - time.sleep(0.01) - self.target.write(json.dumps("CharSramWrite").encode("ascii")) - - def ibex_char_sram_static(self) -> None: - """ Starts the ibex.char.sram_static test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharSramWrite command. - time.sleep(0.01) - self.target.write(json.dumps("CharSramStatic").encode("ascii")) - - def ibex_char_conditional_branch_beq(self) -> None: - """ Starts the ibex.char.conditional_branch_beq test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBeq command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBeq").encode("ascii")) - - def ibex_char_conditional_branch_bne(self) -> None: - """ Starts the ibex.char.conditional_branch_bne test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBne command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBne").encode("ascii")) - - def ibex_char_conditional_branch_bge(self) -> None: - """ Starts the ibex.char.conditional_branch_bge test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBge command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBge").encode("ascii")) - - def ibex_char_conditional_branch_bgeu(self) -> None: - """ Starts the ibex.char.conditional_branch_bgeu test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBgeu command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBgeu").encode("ascii")) - - def ibex_char_conditional_branch_blt(self) -> None: - """ Starts the ibex.char.conditional_branch_blt test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBglt command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBlt").encode("ascii")) - - def ibex_char_conditional_branch_bltu(self) -> None: - """ Starts the ibex.char.conditional_branch_bltu test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCondBranchBgltu command. - time.sleep(0.01) - self.target.write(json.dumps("CharCondBranchBltu").encode("ascii")) - - def ibex_char_unconditional_branch(self) -> None: - """ Starts the ibex.char.unconditional_branch test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharUncondBranch command. - time.sleep(0.01) - self.target.write(json.dumps("CharUncondBranch").encode("ascii")) - - def ibex_char_unconditional_branch_nop(self) -> None: - """ Starts the ibex.char.unconditional_branch_nop test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharUncondBranchNop command. - time.sleep(0.01) - self.target.write(json.dumps("CharUncondBranchNop").encode("ascii")) - - def ibex_char_register_file(self) -> None: - """ Starts the ibex.char.register_file test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharRegisterFile command. - time.sleep(0.01) - self.target.write(json.dumps("CharRegisterFile").encode("ascii")) - - def ibex_char_register_file_read(self) -> None: - """ Starts the ibex.char.register_file_read test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharRegisterFileRead command. - time.sleep(0.01) - self.target.write(json.dumps("CharRegisterFileRead").encode("ascii")) - - def ibex_char_csr_write(self) -> None: - """ Starts the ibex.fi.char.csr_write test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCsrWrite command. - time.sleep(0.01) - self.target.write(json.dumps("CharCsrWrite").encode("ascii")) - - def ibex_char_csr_read(self) -> None: - """ Starts the ibex.fi.char.csr_read test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # CharCsrRead command. - time.sleep(0.01) - self.target.write(json.dumps("CharCsrRead").encode("ascii")) - - def ibex_address_translation_config(self) -> None: - """ Starts the ibex.fi.address_translation_config test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # AddressTranslationCfg command. - time.sleep(0.01) - self.target.write(json.dumps("AddressTranslationCfg").encode("ascii")) - - def ibex_address_translation(self) -> None: - """ Starts the ibex.fi.address_translation test. - """ - # IbexFi command. - self._ujson_fi_cmd() - # AddressTranslation command. - time.sleep(0.01) - self.target.write(json.dumps("AddressTranslation").encode("ascii")) From 1396dcc4bdfc1ab4997667edec09640f0b002766 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Mon, 3 Jun 2024 18:07:05 +0200 Subject: [PATCH 7/7] [fi] Add OTFIRng --- target/communication/fi_rng_commands.py | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 target/communication/fi_rng_commands.py diff --git a/target/communication/fi_rng_commands.py b/target/communication/fi_rng_commands.py new file mode 100644 index 00000000..3c44fff0 --- /dev/null +++ b/target/communication/fi_rng_commands.py @@ -0,0 +1,43 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +"""Communication interface for OpenTitan RNG FI framework. + +Communication with OpenTitan happens over the uJSON command interface. +""" +import json +import time +from typing import Optional + + +from target.communication.otfi import OTFI +from target.communication.otfi_test import OTFITest + + +class OTFIRng(OTFI): + TESTS = [ + OTFITest("csrng_bias"), + OTFITest("edn_bus_ack"), + ] + + def __init__(self, target) -> None: + super().__init__(target, "Rng") + + def init(self, test: Optional[str] = "") -> None: + """ Initialize the RNG FI code on the chip. + + Args: + test: The selected test. + Returns: + The device ID of the device. + """ + # RngFi command. + self._ujson_fi_cmd() + # Init command. + time.sleep(0.01) + if "csrng" in test: + self.target.write(json.dumps("CsrngInit").encode("ascii")) + else: + self.target.write(json.dumps("EdnInit").encode("ascii")) + # Read back device ID from device. + return self.read_response(max_tries=30)