From dad9d817678b305348817acaf5edb668aff6b48b Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Thu, 23 Jan 2025 11:55:13 -0800 Subject: [PATCH 1/8] [orchestrator] enable overriding package ID via CLI Previously the package ID was defined in the HJSON SKU configuration file. This updates the orchestrator.py script to enable overriding this field via a command line arg. Signed-off-by: Tim Trippel (cherry picked from commit c564ac4181e2ad17e19e0be29ca357e7f7cec6c9) --- .../orchestrator/src/orchestrator.py | 11 ++++++ .../orchestrator/src/sku_config.py | 19 +++++----- sw/host/provisioning/orchestrator/tests/BUILD | 21 +++++++++++ .../orchestrator/tests/e2e_option_flags.sh | 36 +++++++++++++++++++ 4 files changed, 79 insertions(+), 8 deletions(-) create mode 100755 sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh diff --git a/sw/host/provisioning/orchestrator/src/orchestrator.py b/sw/host/provisioning/orchestrator/src/orchestrator.py index dfc4c671209a4..d01aac05330cd 100644 --- a/sw/host/provisioning/orchestrator/src/orchestrator.py +++ b/sw/host/provisioning/orchestrator/src/orchestrator.py @@ -83,6 +83,11 @@ def main(args_in): type=str, help="SKU HJSON configuration file.", ) + parser.add_argument( + "--package", + type=str, + help="Override of package string that is in the SKU config.", + ) parser.add_argument( "--test-unlock-token", required=True, @@ -151,6 +156,12 @@ def main(args_in): sku_config_args = hjson.load(fp) sku_config = SkuConfig(**sku_config_args) + # Override package ID if requested. + if args.package: + sku_config.package = args.package + sku_config.validate() + sku_config.load_hw_ids() + # The device identification number is determined during CP by extracting data # from the device. din = DeviceIdentificationNumber(0) diff --git a/sw/host/provisioning/orchestrator/src/sku_config.py b/sw/host/provisioning/orchestrator/src/sku_config.py index 63d08f1663936..cc10127a1bbf9 100644 --- a/sw/host/provisioning/orchestrator/src/sku_config.py +++ b/sw/host/provisioning/orchestrator/src/sku_config.py @@ -53,15 +53,10 @@ def __post_init__(self): # Validate inputs. self.validate() - # Set product, SiliconCreator, and package IDs. - self.si_creator_id = int( - self._product_ids["si_creator_ids"][self.si_creator], 16) - self.product_id = int(self._product_ids["product_ids"][self.product], - 16) - - if self.package in self._package_ids: - self.package_id = int(self._package_ids[self.package], 16) + # Load HW IDs. + self.load_hw_ids() + # Resolve LC token encryption key path. if self.token_encrypt_key: self.token_encrypt_key = resolve_runfile(self.token_encrypt_key) @@ -131,3 +126,11 @@ def validate(self) -> None: raise ValueError( "Target LC state ({}) must be in [\"dev\", \"prod\", \"prod_end\"]" .format(self.target_lc_state)) + + def load_hw_ids(self) -> None: + """Sets product, SiliconCreator, and package IDs.""" + self.si_creator_id = int( + self._product_ids["si_creator_ids"][self.si_creator], 16) + self.product_id = int(self._product_ids["product_ids"][self.product], + 16) + self.package_id = int(self._package_ids[self.package], 16) diff --git a/sw/host/provisioning/orchestrator/tests/BUILD b/sw/host/provisioning/orchestrator/tests/BUILD index 87c3c8971df8c..365b54d6323e5 100644 --- a/sw/host/provisioning/orchestrator/tests/BUILD +++ b/sw/host/provisioning/orchestrator/tests/BUILD @@ -69,6 +69,27 @@ orchestrator_cw340_test_settings_transition( target = "//sw/host/provisioning/orchestrator/src:orchestrator.zip", ) +sh_test( + name = "e2e_option_flags_test", + timeout = "moderate", + srcs = ["e2e_option_flags.sh"], + data = [ + ":orchestrator_cw310_zip", + "@//sw/host/provisioning/orchestrator/configs/skus:emulation.hjson", + "@python3", + ], + env = { + "PYTHON": "$(location @python3//:python3)", + "PACKAGE": "npcr11", + }, + tags = [ + "changes_otp", + "exclusive", + "hyper310", + "manuf", + ], +) + [ sh_test( name = "e2e_{}_{}_test".format(sku, fpga), diff --git a/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh b/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh new file mode 100755 index 0000000000000..8729eee5ea187 --- /dev/null +++ b/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Test Description: +# +# This tests fuly provisioning an OpenTitan chip by executing both CP and FT +# stages in a single execution of the orchestrator script. + +set -ex + +cp sw/host/provisioning/orchestrator/src/orchestrator.zip $TEST_TMPDIR + +ORCHESTRATOR_PATH=$TEST_TMPDIR/orchestrator.zip + +# This script is run by a Bazel sh_test rule, which sets RUNFILES_DIR to point +# at the test's runfiles. However, if RUNFILES_DIR is set, orchestrator.zip will +# inherit its value instead of setting it to the proper directory. This breaks +# runfile resolution, so we unset this variable here. +unset RUNFILES_DIR + +# Run tool. The path to the --sku-config parameter is relative to the +# runfiles-dir. Note: "use-ext-clk" flag on FPGA does nothing, but this tests +# the flag can be piped through to the test harness. +$PYTHON ${ORCHESTRATOR_PATH} \ + --sku-config=sw/host/provisioning/orchestrator/configs/skus/emulation.hjson \ + --test-unlock-token="0x11111111_11111111_11111111_11111111" \ + --test-exit-token="0x22222222_22222222_22222222_22222222" \ + --package=${PACKAGE} \ + --fpga=cw310 \ + --enable-alerts \ + --use-ext-clk \ + --non-interactive \ + --cp-only \ + --db-path=$TEST_TMPDIR/registry.sqlite From b6c775da785f3e677dd0f4f99069da17e7f629de Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Thu, 23 Jan 2025 11:56:20 -0800 Subject: [PATCH 2/8] [tests] remove duplicate test behavior The "enable-alerts" and "use-ext-clk" flags of the orchestrator.py script are already tested in a separate test. Signed-off-by: Tim Trippel (cherry picked from commit eaf6c7e8927df68cfd2e2061a3774fea1bca544c) --- sw/host/provisioning/orchestrator/tests/e2e.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sw/host/provisioning/orchestrator/tests/e2e.sh b/sw/host/provisioning/orchestrator/tests/e2e.sh index bb85285b3258f..b7071b9025d19 100755 --- a/sw/host/provisioning/orchestrator/tests/e2e.sh +++ b/sw/host/provisioning/orchestrator/tests/e2e.sh @@ -21,14 +21,11 @@ ORCHESTRATOR_PATH=$TEST_TMPDIR/orchestrator.zip unset RUNFILES_DIR # Run tool. The path to the --sku-config parameter is relative to the -# runfiles-dir. Note: "use-ext-clk" flag on FPGA does nothing, but this tests -# the flag can be piped through to the test harness. +# runfiles-dir. $PYTHON ${ORCHESTRATOR_PATH} \ --sku-config=${SKU_CONFIG_PATH} \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ --fpga=${FPGA} \ - --enable-alerts \ - --use-ext-clk \ --non-interactive \ --db-path=$TEST_TMPDIR/registry.sqlite From 476a7d961e1e579f308c878185475289cc9ade1f Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Thu, 23 Jan 2025 11:59:30 -0800 Subject: [PATCH 3/8] [orchestrator] re-enable the multistage test This test was temporarily disabled to deal with CI issues that have now been resolved. Signed-off-by: Tim Trippel (cherry picked from commit 922fc1b0bd9906efadb3630da43b66cc195d30c8) --- sw/host/provisioning/orchestrator/tests/BUILD | 40 ++++++++++--------- .../orchestrator/tests/e2e_multistage.sh | 4 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/sw/host/provisioning/orchestrator/tests/BUILD b/sw/host/provisioning/orchestrator/tests/BUILD index 365b54d6323e5..d9fec3bffcc43 100644 --- a/sw/host/provisioning/orchestrator/tests/BUILD +++ b/sw/host/provisioning/orchestrator/tests/BUILD @@ -84,8 +84,28 @@ sh_test( }, tags = [ "changes_otp", + "cw310", + "exclusive", + "fpga", + "manuf", + ], +) + +sh_test( + name = "e2e_multistage_test", + srcs = ["e2e_multistage.sh"], + data = [ + ":orchestrator_cw310_zip", + "@python3", + ], + env = { + "PYTHON": "$(location @python3//:python3)", + }, + tags = [ + "changes_otp", + "cw310", "exclusive", - "hyper310", + "fpga", "manuf", ], ) @@ -150,21 +170,3 @@ sh_test( "cw340", ] ] - -# TODO: this test seems to work locally but fails in CI. -# sh_test( -# name = "e2e_multistage_test", -# srcs = ["e2e_multistage.sh"], -# data = [ -# ":orchestrator_zip", -# "@rules_python//python:current_py_toolchain", -# ], -# env = { -# "PYTHON": "$(PYTHON3)", -# }, -# tags = [ -# "cw310", -# "manuf", -# ], -# toolchains = ["@rules_python//python:current_py_toolchain"], -# ) diff --git a/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh b/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh index 9c7339adce31e..cdc14a802385e 100755 --- a/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh +++ b/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh @@ -30,7 +30,7 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --sku-config=sw/host/provisioning/orchestrator/configs/skus/emulation.hjson \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ - --fpga=hyper310 \ + --fpga=cw310 \ --non-interactive \ --cp-only \ --db-path=$TEST_TMPDIR/registry.sqlite @@ -42,7 +42,7 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --sku-config=sw/host/provisioning/orchestrator/configs/skus/emulation.hjson \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ - --fpga=hyper310 \ + --fpga=cw310 \ --fpga-dont-clear-bitstream \ --non-interactive \ --db-path=$TEST_TMPDIR/registry.sqlite From 36b692936c7f617b5f7ff296cfda4965f061bbf2 Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Thu, 23 Jan 2025 12:01:46 -0800 Subject: [PATCH 4/8] [testutils] increase OTP programming timeout to 10ms Signed-off-by: Tim Trippel (cherry picked from commit ae75936adb5d5a9fcf1621621c7d3b238bd426d4) --- sw/device/lib/testing/otp_ctrl_testutils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sw/device/lib/testing/otp_ctrl_testutils.c b/sw/device/lib/testing/otp_ctrl_testutils.c index 9e80fc54c7687..269822ca2fb74 100644 --- a/sw/device/lib/testing/otp_ctrl_testutils.c +++ b/sw/device/lib/testing/otp_ctrl_testutils.c @@ -15,9 +15,9 @@ * OTP the Direct Access Interface (DAI) operation time-out in micro seconds. * * It is not possible to predict the specific cycle count that a DAI operation - * takes, thus arbitrary value of 100us is used. + * takes, thus arbitrary value of 10ms is used. */ -const uint16_t kOtpDaiTimeoutUs = 5000; +const uint16_t kOtpDaiTimeoutUs = 10000; /** * Checks whether the DAI operation has finished. From 94fbd2f92ead1f6ca56e64ff51816fa8f76b6253 Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Sat, 25 Jan 2025 11:13:21 -0800 Subject: [PATCH 5/8] [orchestrator] refactor SKU specific portion of device ID This updates the SKU specific portion of the device ID to add the following subfields: 1. AST config version - 1 byte 2. OTP ID - 2 byte ASCII string 3. OTP version - 1 byte hex number 4. SKU specific version - 1 byte hex number Signed-off-by: Tim Trippel [manuf] align format of OTP image names This aligns all OTP image names to a four character format: This allows easily converting the OTP image name string into a two character OTP ID code and 2 hex digit OTP version number to be embedded in the SKU specific portion of the device ID. Signed-off-by: Tim Trippel (cherry picked from commit ba4b885dcb89a2e506cb960b7d56993fbf673ed0) --- sw/device/silicon_creator/manuf/base/BUILD | 2 +- .../manuf/base/provisioning_inputs.bzl | 12 +-- sw/device/silicon_creator/manuf/lib/BUILD | 2 +- sw/device/silicon_creator/manuf/tests/BUILD | 2 +- sw/device/tests/BUILD | 2 +- .../orchestrator/configs/skus/emulation.hjson | 2 +- .../configs/skus/emulation_dice_cwt.hjson | 2 +- .../configs/skus/emulation_tpm.hjson | 2 +- .../orchestrator/configs/skus/sival.hjson | 2 +- .../orchestrator/src/device_id.py | 83 ++++++++++++++----- .../orchestrator/src/orchestrator.py | 9 +- .../orchestrator/src/sku_config.py | 32 +++++-- .../orchestrator/tests/device_id_test.py | 54 +++++++++--- .../provisioning/orchestrator/tests/e2e.sh | 1 + .../orchestrator/tests/e2e_multistage.sh | 2 + .../orchestrator/tests/e2e_option_flags.sh | 1 + 16 files changed, 155 insertions(+), 55 deletions(-) diff --git a/sw/device/silicon_creator/manuf/base/BUILD b/sw/device/silicon_creator/manuf/base/BUILD index 5c4afd13c08ae..513681531fc84 100644 --- a/sw/device/silicon_creator/manuf/base/BUILD +++ b/sw/device/silicon_creator/manuf/base/BUILD @@ -366,7 +366,7 @@ filegroup( EXT_SIGNED_PERSO_BINS, ) -_DISQUALIFIED_FOR_SIGNING = ["emulation"] +_DISQUALIFIED_FOR_SIGNING = ["em00"] [ offline_presigning_artifacts( diff --git a/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl b/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl index 940486f250a34..272ae862e05fe 100644 --- a/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl +++ b/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl @@ -12,8 +12,8 @@ load( # individualization binaries that configure OTP with the constants defined in # these bazel targets. EARLGREY_OTP_CFGS = { - "sival": "//hw/top_earlgrey/data/otp/sival_skus:otp_consts", - "emulation": "//hw/top_earlgrey/data/otp/emulation:otp_consts", + "sv00": "//hw/top_earlgrey/data/otp/sival_skus:otp_consts", + "em00": "//hw/top_earlgrey/data/otp/emulation:otp_consts", } | EXT_EARLGREY_OTP_CFGS EXT_SIGNED_PERSO_BINS = [] @@ -24,7 +24,7 @@ EXT_SIGNED_PERSO_BINS = [] EARLGREY_SKUS = { # OTP Config: Emulation; DICE Certs: X.509; Additional Certs: None "emulation": { - "otp": "emulation", + "otp": "em00", "ca_data": "@//sw/device/silicon_creator/manuf/keys/fake:ca_data", "dice_libs": ["//sw/device/silicon_creator/lib/cert:dice"], "host_ext_libs": ["@provisioning_exts//:default_ft_ext_lib"], @@ -38,7 +38,7 @@ EARLGREY_SKUS = { }, # OTP Config: Emulation; DICE Certs: CWT; Additional Certs: None "emulation_dice_cwt": { - "otp": "emulation", + "otp": "em00", "ca_data": "@//sw/device/silicon_creator/manuf/keys/fake:ca_data", "dice_libs": ["//sw/device/silicon_creator/lib/cert:dice_cwt"], "host_ext_libs": ["@provisioning_exts//:default_ft_ext_lib"], @@ -54,7 +54,7 @@ EARLGREY_SKUS = { }, # OTP Config: Emulation; DICE Certs: X.509; Additional Certs: TPM EK "emulation_tpm": { - "otp": "emulation", + "otp": "em00", "ca_data": "@//sw/device/silicon_creator/manuf/keys/fake:ca_data", "dice_libs": ["//sw/device/silicon_creator/lib/cert:dice"], "host_ext_libs": ["@provisioning_exts//:default_ft_ext_lib"], @@ -74,7 +74,7 @@ EARLGREY_SKUS = { # This configuration is not really usable in master but left here as an example until # a more appropriate solution is found. # "sival": { - # "otp": "sival", + # "otp": "sv00", # "ca_data": "@//sw/device/silicon_creator/manuf/keys/sival:ca_data", # "dice_libs": ["//sw/device/silicon_creator/lib/cert:dice"], # "host_ext_libs": ["@provisioning_exts//:default_ft_ext_lib"], diff --git a/sw/device/silicon_creator/manuf/lib/BUILD b/sw/device/silicon_creator/manuf/lib/BUILD index 8d9f9c30f7ae7..a254ff2ca8e53 100644 --- a/sw/device/silicon_creator/manuf/lib/BUILD +++ b/sw/device/silicon_creator/manuf/lib/BUILD @@ -224,7 +224,7 @@ opentitan_test( deps = [ ":flash_info_fields", # Testing sival SKU only should be sufficient. - ":individualize_sw_cfg_emulation", + ":individualize_sw_cfg_em00", "//hw/top:otp_ctrl_c_regs", "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:status", diff --git a/sw/device/silicon_creator/manuf/tests/BUILD b/sw/device/silicon_creator/manuf/tests/BUILD index 16e6e069b8cee..efa3caff3eb89 100644 --- a/sw/device/silicon_creator/manuf/tests/BUILD +++ b/sw/device/silicon_creator/manuf/tests/BUILD @@ -320,7 +320,7 @@ opentitan_binary( "//sw/device/lib/testing/test_framework:ottf_test_config", "//sw/device/lib/testing/test_framework:status", "//sw/device/silicon_creator/manuf/lib:flash_info_fields", - "//sw/device/silicon_creator/manuf/lib:individualize_sw_cfg_sival", + "//sw/device/silicon_creator/manuf/lib:individualize_sw_cfg_sv00", "//sw/device/silicon_creator/manuf/lib:sram_start", ], ) diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index 2f8981e9432cd..4ef505646e329 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -2517,7 +2517,7 @@ opentitan_binary( "//sw/device/lib/testing/test_framework:ottf_test_config", "//sw/device/lib/testing/test_framework:status", "//sw/device/silicon_creator/manuf/lib:individualize", - "//sw/device/silicon_creator/manuf/lib:individualize_sw_cfg_sival", + "//sw/device/silicon_creator/manuf/lib:individualize_sw_cfg_sv00", "//sw/device/silicon_creator/manuf/lib:otp_fields", "//sw/device/silicon_creator/manuf/lib:sram_start", ], diff --git a/sw/host/provisioning/orchestrator/configs/skus/emulation.hjson b/sw/host/provisioning/orchestrator/configs/skus/emulation.hjson index cbca464e9e339..7b436b313d92e 100644 --- a/sw/host/provisioning/orchestrator/configs/skus/emulation.hjson +++ b/sw/host/provisioning/orchestrator/configs/skus/emulation.hjson @@ -8,7 +8,7 @@ si_creator: "nuvoton", package: "npcr10", target_lc_state: "prod", - otp: "emulation", + otp: "em00", perso_bin: "sw/device/silicon_creator/manuf/base/ft_personalize_{sku}_{target}.prod_key_0.prod_key_0.signed.bin", owner_fw_boot_str: "Bare metal PASS!" dice_ca: { diff --git a/sw/host/provisioning/orchestrator/configs/skus/emulation_dice_cwt.hjson b/sw/host/provisioning/orchestrator/configs/skus/emulation_dice_cwt.hjson index 35c4fe76180d0..53770e2a7a52c 100644 --- a/sw/host/provisioning/orchestrator/configs/skus/emulation_dice_cwt.hjson +++ b/sw/host/provisioning/orchestrator/configs/skus/emulation_dice_cwt.hjson @@ -8,7 +8,7 @@ si_creator: "nuvoton", package: "npcr10", target_lc_state: "prod", - otp: "emulation", + otp: "em00", perso_bin: "sw/device/silicon_creator/manuf/base/ft_personalize_{sku}_{target}.prod_key_0.prod_key_0.signed.bin", owner_fw_boot_str: "Bare metal PASS!", dice_ca: { diff --git a/sw/host/provisioning/orchestrator/configs/skus/emulation_tpm.hjson b/sw/host/provisioning/orchestrator/configs/skus/emulation_tpm.hjson index 49f724b08c1a0..04d0815a8acbb 100644 --- a/sw/host/provisioning/orchestrator/configs/skus/emulation_tpm.hjson +++ b/sw/host/provisioning/orchestrator/configs/skus/emulation_tpm.hjson @@ -8,7 +8,7 @@ si_creator: "nuvoton", package: "npcr10", target_lc_state: "prod", - otp: "emulation", + otp: "em00", perso_bin: "sw/device/silicon_creator/manuf/base/ft_personalize_{sku}_{target}.prod_key_0.prod_key_0.signed.bin", owner_fw_boot_str: "Bare metal PASS!" dice_ca: { diff --git a/sw/host/provisioning/orchestrator/configs/skus/sival.hjson b/sw/host/provisioning/orchestrator/configs/skus/sival.hjson index ffcedd2daf9a3..5195a77d719e4 100644 --- a/sw/host/provisioning/orchestrator/configs/skus/sival.hjson +++ b/sw/host/provisioning/orchestrator/configs/skus/sival.hjson @@ -8,7 +8,7 @@ si_creator: "nuvoton", package: "npcr10", target_lc_state: "prod", - otp: "sival", + otp: "sv00", perso_bin: "sw/device/silicon_creator/manuf/base/binaries/ft_personalize_{sku}_{target}.signed.bin", # owner_fw_boot_str: "Bare metal PASS!" dice_ca: { diff --git a/sw/host/provisioning/orchestrator/src/device_id.py b/sw/host/provisioning/orchestrator/src/device_id.py index 6a829c6cb1108..7ad0a64976596 100644 --- a/sw/host/provisioning/orchestrator/src/device_id.py +++ b/sw/host/provisioning/orchestrator/src/device_id.py @@ -9,7 +9,12 @@ import util from sku_config import SkuConfig -_RESERVED_WORD = 0 +_RESERVED_VALUE = 0 + +# This defines the format of the "sku_specific" portion of the device ID. This +# should be updated everytime the the "sku_specific" portion of the device ID is +# updated. +_SKU_SPECIFIC_FORMAT_VERSION = 1 @dataclass @@ -123,15 +128,26 @@ def __init__(self, sku_config: SkuConfig, din: DeviceIdentificationNumber): # Build SKU specific field (i.e., FT device ID). self.package_id = sku_config.package_id + self.ast_cfg_version = sku_config.ast_cfg_version + self.otp_id = util.bytes_to_int( + sku_config.otp.upper()[0:2].encode("utf-8")[::-1]) + self.otp_version = sku_config.otp_version self.sku_id = util.bytes_to_int( self.sku.upper()[:4].encode("utf-8")[::-1]) + self.sku_specific_version = _SKU_SPECIFIC_FORMAT_VERSION self.sku_specific = util.bytes_to_int( struct.pack( - " "DeviceId": @staticmethod def from_int(device_id: int) -> "DeviceId": """Creates a DeviceId object from an int.""" - # Extract SKU specific field. - sku_specific = device_id >> 128 - package_id = sku_specific & 0xFFFF - sku_id = (sku_specific >> 32) & 0xFFFFFFFF - # Extract base unique ID. mask = (1 << 128) - 1 base_uid = device_id & mask @@ -174,9 +185,27 @@ def from_int(device_id: int) -> "DeviceId": si_creator_id = hw_origin & 0xFFFF product_id = (hw_origin >> 16) & 0xFFFF + # Extract SKU specific field. + sku_specific = device_id >> 128 + package_id = sku_specific & 0xFF + ast_cfg_version = (sku_specific >> 8) & 0xFF + otp_id = (sku_specific >> 16) & 0xFFFF + otp_version = (sku_specific >> 32) & 0xFF + sku_id = (sku_specific >> 64) & 0xFFFFFFFF + + # Unpack OTP name. + try: + otp = (struct.pack('>H', otp_id).decode('ascii') + + f"{otp_version:02x}") + except UnicodeDecodeError: + otp = "Invalid" + print("HERE", otp) + # Extract SKU config. - sku_config = SkuConfig.from_ids(product_id, si_creator_id, package_id) + sku_config = SkuConfig.from_ids(product_id, si_creator_id, package_id, + otp, ast_cfg_version) + # Unpack SKU name. try: sku_name = struct.pack('>I', sku_id).decode('ascii') except UnicodeDecodeError: @@ -198,26 +227,34 @@ def to_int(self) -> int: return self.device_id def pretty_print(self): - print("> Device ID: {}".format(self)) - print("SiliconCreator ID: {} ({})".format( + print("> Device ID: {}".format(self)) + print("SiliconCreator ID: {} ({})".format( util.format_hex(self.si_creator_id, width=4), self._si_creator)) - print("Product ID: {} ({})".format( + print("Product ID: {} ({})".format( util.format_hex(self.product_id, width=4), self._product)) if self.din is not None: - print("DIN Year: {}".format(self.din.year)) - print("DIN Week: {}".format(self.din.week)) - print("DIN Lot: {}".format(self.din.lot)) - print("DIN Wafer: {}".format(self.din.wafer)) - print("DIN Wafer X Coord: {}".format(self.din.wafer_x_coord)) - print("DIN Wafer Y Coord: {}".format(self.din.wafer_y_coord)) + print("DIN Year: {}".format(self.din.year)) + print("DIN Week: {}".format(self.din.week)) + print("DIN Lot: {}".format(self.din.lot)) + print("DIN Wafer: {}".format(self.din.wafer)) + print("DIN Wafer X Coord: {}".format(self.din.wafer_x_coord)) + print("DIN Wafer Y Coord: {}".format(self.din.wafer_y_coord)) else: print("DIN: ") - print("Reserved: {}".format(hex(0))) - print("SKU ID: {} ({})".format( + print("Reserved (40 bits): {}".format(hex(_RESERVED_VALUE))) + print("Package ID: {} ({})".format(self.package_id, + self._package)) + print("AST Config Version: {}".format(self.ast_cfg_version)) + print("OTP ID: {} ({})".format( + hex(self.otp_id), + self.otp_id.to_bytes(length=4, byteorder="big").decode("utf-8"))) + print("OTP Version: {}".format(self.otp_version)) + print("Reserved (24 bits): {}".format(hex(_RESERVED_VALUE))) + print("SKU ID: {} ({})".format( util.format_hex(self.sku_id), self.sku_id.to_bytes(length=4, byteorder="big").decode("utf-8"))) - print("Package ID: {} ({})".format(self.package_id, - self._package)) + print("Reserved (24 bits): {}".format(hex(_RESERVED_VALUE))) + print("SKU Specific Version: {}".format(self.sku_specific_version)) def __str__(self): return self.to_hexstr() diff --git a/sw/host/provisioning/orchestrator/src/orchestrator.py b/sw/host/provisioning/orchestrator/src/orchestrator.py index d01aac05330cd..757c3c3a27f97 100644 --- a/sw/host/provisioning/orchestrator/src/orchestrator.py +++ b/sw/host/provisioning/orchestrator/src/orchestrator.py @@ -83,6 +83,12 @@ def main(args_in): type=str, help="SKU HJSON configuration file.", ) + parser.add_argument( + "--ast-cfg-version", + required=True, + type=int, + help="AST configuration version to be written to OTP.", + ) parser.add_argument( "--package", type=str, @@ -154,7 +160,8 @@ def main(args_in): sku_config_args = {} with open(sku_config_path, "r") as fp: sku_config_args = hjson.load(fp) - sku_config = SkuConfig(**sku_config_args) + sku_config = SkuConfig(ast_cfg_version=args.ast_cfg_version, + **sku_config_args) # Override package ID if requested. if args.package: diff --git a/sw/host/provisioning/orchestrator/src/sku_config.py b/sw/host/provisioning/orchestrator/src/sku_config.py index cc10127a1bbf9..ca56478b01110 100644 --- a/sw/host/provisioning/orchestrator/src/sku_config.py +++ b/sw/host/provisioning/orchestrator/src/sku_config.py @@ -24,9 +24,10 @@ class SkuConfig: si_creator: Optional[str] # valid: any SiliconCreator that exists in product database package: Optional[str] # valid: any package that exists in package database target_lc_state: str # valid: must be in ["dev", "prod", "prod_end"] - otp: str # valid: any string + otp: str # valid: any 4 char string whose first char is alphabetic and last two are numeric + ast_cfg_version: int # valid: any positive integer < 256 (to fit in one byte) perso_bin: str # valid: any string - token_encrypt_key: str + token_encrypt_key: str # valid: any file path that exists dice_ca: Optional[OrderedDict] # valid: see CaConfig ext_ca: Optional[OrderedDict] = None # valid: see CaConfig owner_fw_boot_str: str = None # valid: any string @@ -61,9 +62,9 @@ def __post_init__(self): self.token_encrypt_key = resolve_runfile(self.token_encrypt_key) @staticmethod - def from_ids(product_id: int, si_creator_id: int, - package_id: int) -> "SkuConfig": - """Creates a SKU configuration object from product, SiliconCreator, and package IDs.""" + def from_ids(product_id: int, si_creator_id: int, package_id: int, + otp: str, ast_cfg_version: int) -> "SkuConfig": + """Creates a SKU configuration object from various subcomponent IDs.""" # Load product IDs database. product_ids_hjson = resolve_runfile(_PRODUCT_IDS_HJSON) product_ids = None @@ -97,7 +98,8 @@ def from_ids(product_id: int, si_creator_id: int, si_creator=si_creator, package=package, target_lc_state="dev", - otp="", + otp=otp, + ast_cfg_version=ast_cfg_version, perso_bin="", dice_ca=OrderedDict(), ext_ca=OrderedDict(), @@ -126,6 +128,21 @@ def validate(self) -> None: raise ValueError( "Target LC state ({}) must be in [\"dev\", \"prod\", \"prod_end\"]" .format(self.target_lc_state)) + # Validate AST configuration version. + if self.ast_cfg_version < 0 or self.ast_cfg_version > 255: + raise ValueError("AST config version should be in range [0, 256).") + # Validate OTP string. + if len(self.otp) != 4: + raise ValueError("OTP must be a non-empty 4 character string.") + # Validate OTP ID string. + if not self.otp[0].isalpha() or not self.otp[1].isalpha(): + raise ValueError( + "First two chars of OTP string must be alphabetic.") + # Validate OTP version. + try: + _ = int(self.otp[2:], 16) + except ValueError: + raise ValueError("OTP version should two digit hexstring.") def load_hw_ids(self) -> None: """Sets product, SiliconCreator, and package IDs.""" @@ -134,3 +151,6 @@ def load_hw_ids(self) -> None: self.product_id = int(self._product_ids["product_ids"][self.product], 16) self.package_id = int(self._package_ids[self.package], 16) + # We process the OTP str into a two character ID and version number. + self.otp_id = self.otp[:2] + self.otp_version = int(self.otp[2:], 16) diff --git a/sw/host/provisioning/orchestrator/tests/device_id_test.py b/sw/host/provisioning/orchestrator/tests/device_id_test.py index 75069afeb9ae3..cb1c9d99211be 100644 --- a/sw/host/provisioning/orchestrator/tests/device_id_test.py +++ b/sw/host/provisioning/orchestrator/tests/device_id_test.py @@ -19,7 +19,7 @@ def setUp(self): # Create SKU config object. with open(_SIVAL_SKU_CONFIG, "r") as fp: sku_config_args = hjson.load(fp) - self.sku_config = SkuConfig(**sku_config_args) + self.sku_config = SkuConfig(ast_cfg_version=7, **sku_config_args) # Create DIN object. self.din = DeviceIdentificationNumber(year=4, @@ -114,15 +114,39 @@ def test_uid_reserved_field(self): def test_package_id_field(self): expected_field = 0x0 - actual_field = (self.device_id.to_int() >> 128) & 0xffff + actual_field = (self.device_id.to_int() >> 128) & 0xff self.assertEqual( actual_field, expected_field, "actual: {}, expected: {}.".format( - format_hex(actual_field, width=4), - format_hex(expected_field, width=4))) + format_hex(actual_field, width=2), + format_hex(expected_field, width=2))) - def test_sku_specific_reserved_field_0(self): - expected_field = 0 + def test_ast_cfg_version_field(self): + expected_field = 0x7 + actual_field = (self.device_id.to_int() >> 136) & 0xff + self.assertEqual( + actual_field, expected_field, "actual: {}, expected: {}.".format( + format_hex(actual_field, width=2), + format_hex(expected_field, width=2))) + + def test_otp_id_field(self): + expected_field = bytes_to_int("ME".encode("utf-8")) actual_field = (self.device_id.to_int() >> 144) & 0xffff + self.assertEqual( + actual_field, expected_field, "actual: {}, expected: {}.".format( + format_hex(actual_field, width=2), + format_hex(expected_field, width=2))) + + def test_otp_version_field(self): + expected_field = 0 + actual_field = (self.device_id.to_int() >> 160) & 0xff + self.assertEqual( + actual_field, expected_field, "actual: {}, expected: {}.".format( + format_hex(actual_field, width=2), + format_hex(expected_field, width=2))) + + def test_sku_specific_reserved_field0(self): + expected_field = 0 + actual_field = (self.device_id.to_int() >> 168) & 0xffffff self.assertEqual( actual_field, expected_field, "actual: {}, expected: {}.".format( format_hex(actual_field, width=4), @@ -130,19 +154,27 @@ def test_sku_specific_reserved_field_0(self): def test_sku_id_field(self): expected_field = bytes_to_int("LUME".encode("utf-8")) - actual_field = (self.device_id.to_int() >> 160) & 0xffffffff + actual_field = (self.device_id.to_int() >> 192) & 0xffffffff self.assertEqual( actual_field, expected_field, "actual: {}, expected: {}.".format( format_hex(actual_field, width=8), format_hex(expected_field, width=8))) - def test_sku_specific_reserved_field_1(self): + def test_sku_specific_reserved_field1(self): expected_field = 0 - actual_field = (self.device_id.to_int() >> 224) & 0xffffffff + actual_field = (self.device_id.to_int() >> 224) & 0xffffff self.assertEqual( actual_field, expected_field, "actual: {}, expected: {}.".format( - format_hex(actual_field, width=8), - format_hex(expected_field, width=8))) + format_hex(actual_field, width=4), + format_hex(expected_field, width=4))) + + def test_sku_specific_version_field(self): + expected_field = 1 + actual_field = (self.device_id.to_int() >> 248) & 0xff + self.assertEqual( + actual_field, expected_field, "actual: {}, expected: {}.".format( + format_hex(actual_field, width=4), + format_hex(expected_field, width=4))) def test_pretty_print(self): self.device_id.pretty_print() diff --git a/sw/host/provisioning/orchestrator/tests/e2e.sh b/sw/host/provisioning/orchestrator/tests/e2e.sh index b7071b9025d19..f1969c0ae3ac9 100755 --- a/sw/host/provisioning/orchestrator/tests/e2e.sh +++ b/sw/host/provisioning/orchestrator/tests/e2e.sh @@ -27,5 +27,6 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ --fpga=${FPGA} \ + --ast-cfg-version=0 \ --non-interactive \ --db-path=$TEST_TMPDIR/registry.sqlite diff --git a/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh b/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh index cdc14a802385e..2b8c17165c65f 100755 --- a/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh +++ b/sw/host/provisioning/orchestrator/tests/e2e_multistage.sh @@ -31,6 +31,7 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ --fpga=cw310 \ + --ast-cfg-version=0 \ --non-interactive \ --cp-only \ --db-path=$TEST_TMPDIR/registry.sqlite @@ -43,6 +44,7 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --test-unlock-token="0x11111111_11111111_11111111_11111111" \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ --fpga=cw310 \ + --ast-cfg-version=0 \ --fpga-dont-clear-bitstream \ --non-interactive \ --db-path=$TEST_TMPDIR/registry.sqlite diff --git a/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh b/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh index 8729eee5ea187..700588d60612c 100755 --- a/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh +++ b/sw/host/provisioning/orchestrator/tests/e2e_option_flags.sh @@ -29,6 +29,7 @@ $PYTHON ${ORCHESTRATOR_PATH} \ --test-exit-token="0x22222222_22222222_22222222_22222222" \ --package=${PACKAGE} \ --fpga=cw310 \ + --ast-cfg-version=0 \ --enable-alerts \ --use-ext-clk \ --non-interactive \ From 03ca5363b2d725c68dec08ec464288b053bb8a50 Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Mon, 27 Jan 2025 14:54:54 -0800 Subject: [PATCH 6/8] [orchestrator] enable clasification of blind assembly parts Blind assembly parts should have a DIN set to all Fs. Signed-off-by: Tim Trippel (cherry picked from commit 845ae36498e7795462ecf9c25596faf5883151ca) --- .../orchestrator/src/device_id.py | 29 +++++++++++++++++-- .../orchestrator/src/orchestrator.py | 2 +- .../provisioning/orchestrator/src/ot_dut.py | 6 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/sw/host/provisioning/orchestrator/src/device_id.py b/sw/host/provisioning/orchestrator/src/device_id.py index 7ad0a64976596..4571bbab45538 100644 --- a/sw/host/provisioning/orchestrator/src/device_id.py +++ b/sw/host/provisioning/orchestrator/src/device_id.py @@ -19,16 +19,27 @@ @dataclass class DeviceIdentificationNumber: - """Class for storing Device Identification Number portion of Device ID.""" + """Class for storing Device Identification Number portion of Device ID. + + DINs for blind assembly parts have all values set to -1 or (UINT*_MAX). + """ year: int = 0 # valid range: [0, 9] week: int = 0 # valid range: [0, 51] lot: int = 0 # valid range: [0, 999] wafer: int = 0 # valid range: [0,99] wafer_x_coord: int = 0 # valid range: [0, 999] wafer_y_coord: int = 0 # valid range: [0, 999] + blind_asm: bool = False # boolean indicating if blind assembly part def __post_init__(self): - self.validate() + # Check if blind assembly part, if so, we skip field validation. + if (self.year == -1 and self.week == -1 and self.lot == -1 and + self.wafer == -1 and self.wafer_x_coord == -1 and + self.wafer_y_coord == -1): + self.blind_asm = True + else: + self.blind_asm = False + self.validate() def validate(self) -> None: """Validates this object's attributes.""" @@ -57,6 +68,10 @@ def validate(self) -> None: self.wafer_y_coord)) def to_int(self) -> int: + """Convert DIN to an int.""" + # If blind assembly part, the DIN is UINT64_MAX. + if self.blind_asm: + return 2**64 - 1 din = 0 din |= util.bcd_encode(self.wafer_y_coord) << 44 din |= util.bcd_encode(self.wafer_x_coord) << 32 @@ -81,6 +96,15 @@ def from_int(din: int) -> "DeviceIdentificationNumber": wafer_x_coord=wafer_x_coord, wafer_y_coord=wafer_y_coord) + @staticmethod + def blind_asm() -> "DeviceIdentificationNumber": + return DeviceIdentificationNumber(year=-1, + week=-1, + lot=-1, + wafer=-1, + wafer_x_coord=-1, + wafer_y_coord=-1) + class DeviceId(): """An OpenTitan device ID. @@ -199,7 +223,6 @@ def from_int(device_id: int) -> "DeviceId": f"{otp_version:02x}") except UnicodeDecodeError: otp = "Invalid" - print("HERE", otp) # Extract SKU config. sku_config = SkuConfig.from_ids(product_id, si_creator_id, package_id, diff --git a/sw/host/provisioning/orchestrator/src/orchestrator.py b/sw/host/provisioning/orchestrator/src/orchestrator.py index 757c3c3a27f97..1b4a5ac1d8eb7 100644 --- a/sw/host/provisioning/orchestrator/src/orchestrator.py +++ b/sw/host/provisioning/orchestrator/src/orchestrator.py @@ -171,7 +171,7 @@ def main(args_in): # The device identification number is determined during CP by extracting data # from the device. - din = DeviceIdentificationNumber(0) + din = DeviceIdentificationNumber.blind_asm() device_id = DeviceId(sku_config, din) # TODO: Setup remote and/or local DB connections. diff --git a/sw/host/provisioning/orchestrator/src/ot_dut.py b/sw/host/provisioning/orchestrator/src/ot_dut.py index a500aca7a684c..dc5675c53afde 100644 --- a/sw/host/provisioning/orchestrator/src/ot_dut.py +++ b/sw/host/provisioning/orchestrator/src/ot_dut.py @@ -173,12 +173,12 @@ def run_cp(self) -> None: # has already run through CP, and only needs to execute FT. if chip_probe_data["cp_device_id"] == "": logging.warning( - "cp_device_id empty; setting default of all zeros.") - din_from_device = DeviceIdentificationNumber(0) + "cp_device_id empty; setting default DIN of all 0xFF.") + din_from_device = DeviceIdentificationNumber.blind_asm() else: din_from_device = DeviceIdentificationNumber.from_int( (int(chip_probe_data["cp_device_id"], 16) >> 32) & - 0xFFFFFFFFFFFFFFFF) + (2**64 - 1)) logging.info( f"Updating device ID to: {chip_probe_data['cp_device_id']}") self.device_id.update_din(din_from_device) From dd04d05b114fe968a09f392cea73bf40ddaa6f17 Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Mon, 27 Jan 2025 14:59:40 -0800 Subject: [PATCH 7/8] [orchestrator] fix confirmation requirement bug Confirmation requirement should only be required when the `--non-interactive` mode CLI arg is NOT passed. This updates the script behavior to disble the confirmation requirement when said flag is passed and a host/device device ID mismatch occurs, which typically happens on parts that have had CP run, and later FT run, as opposed to running both stages back to back. Signed-off-by: Tim Trippel (cherry picked from commit 1a9b06976f25bdc324746b157243e7720688154d) --- sw/host/provisioning/orchestrator/src/ot_dut.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sw/host/provisioning/orchestrator/src/ot_dut.py b/sw/host/provisioning/orchestrator/src/ot_dut.py index dc5675c53afde..caa20a575ae6c 100644 --- a/sw/host/provisioning/orchestrator/src/ot_dut.py +++ b/sw/host/provisioning/orchestrator/src/ot_dut.py @@ -320,7 +320,8 @@ def run_ft(self) -> None: f"Final (device) DeviceId: {device_id_in_otp.to_hexstr()}") logging.error( f"Final (host) DeviceId: {self.device_id.to_hexstr()}") - confirm() + if self.require_confirmation: + confirm() self.device_id = device_id_in_otp logging.info("FT completed successfully.") From d3eba236176e685a838ca9e64b5974d9f53e63e3 Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Mon, 17 Mar 2025 22:58:02 -0700 Subject: [PATCH 8/8] [device_id] fix duplicate member and constructor names This was causing Python lint errors in CI. Signed-off-by: Tim Trippel (cherry picked from commit e11d41fa571e381a12de1bc8f4c5e29f86104853) --- sw/host/provisioning/orchestrator/src/device_id.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sw/host/provisioning/orchestrator/src/device_id.py b/sw/host/provisioning/orchestrator/src/device_id.py index 4571bbab45538..62f73160a6140 100644 --- a/sw/host/provisioning/orchestrator/src/device_id.py +++ b/sw/host/provisioning/orchestrator/src/device_id.py @@ -29,16 +29,16 @@ class DeviceIdentificationNumber: wafer: int = 0 # valid range: [0,99] wafer_x_coord: int = 0 # valid range: [0, 999] wafer_y_coord: int = 0 # valid range: [0, 999] - blind_asm: bool = False # boolean indicating if blind assembly part + blind_asm_status: bool = False # boolean indicating if blind assembly part def __post_init__(self): # Check if blind assembly part, if so, we skip field validation. if (self.year == -1 and self.week == -1 and self.lot == -1 and self.wafer == -1 and self.wafer_x_coord == -1 and self.wafer_y_coord == -1): - self.blind_asm = True + self.blind_asm_status = True else: - self.blind_asm = False + self.blind_asm_status = False self.validate() def validate(self) -> None: @@ -70,7 +70,7 @@ def validate(self) -> None: def to_int(self) -> int: """Convert DIN to an int.""" # If blind assembly part, the DIN is UINT64_MAX. - if self.blind_asm: + if self.blind_asm_status: return 2**64 - 1 din = 0 din |= util.bcd_encode(self.wafer_y_coord) << 44