From 6429788f57f0a2acd522be8eeb3058dc964f3531 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Apr 2025 16:58:09 +0200 Subject: [PATCH 001/175] [ot] hw/opentitan: ot_ibex_wrapper: replace legacy reset API with Resettable API Signed-off-by: Emmanuel Blot From c74b14ff0c64f45c00d48eef54a867712e6799b0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 10 Feb 2025 15:38:24 +0000 Subject: [PATCH 002/175] [ot] target/riscv: Revert to upstream PMP implementation Use the PMP implementation from v9.2.0 Signed-off-by: Rob Bradford --- target/riscv/pmp.c | 186 +++++++++++++++++---------------------------- 1 file changed, 70 insertions(+), 116 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 0a7356529303f..a1b36664fc071 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -31,16 +31,6 @@ static bool pmp_write_cfg(CPURISCVState *env, uint32_t addr_index, uint8_t val); static uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t addr_index); -/* - * Convert the PMP permissions to match the truth table in the - * ePMP spec. - */ -static inline uint8_t pmp_get_epmp_operation(uint8_t cfg) -{ - return ((cfg & PMP_LOCK) >> 4) | ((cfg & PMP_READ) << 2) | - (cfg & PMP_WRITE) | ((cfg & PMP_EXEC) >> 2); -} - /* * Accessor method to extract address matching type 'a field' from cfg reg */ @@ -64,62 +54,12 @@ static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) return 1; } - return 0; -} - -/* - * Check whether a PMP is writable or not. - */ -static bool pmp_is_writable(CPURISCVState *env, uint32_t pmp_index) -{ - /* - * If the ePMP feature is enabled, the RLB bit allows writing to any PMP, - * regardless of PMP_LOCK bit - */ - if (riscv_cpu_cfg(env)->ext_smepmp && MSECCFG_RLB_ISSET(env)) { - return true; - } - - /* Standard PMP, just check if it is locked */ - return !pmp_is_locked(env, pmp_index); -} - -/* - * Check whether `val` is a valid ePMP config value - */ -static bool pmp_is_valid_epmp_cfg(CPURISCVState *env, uint8_t val) -{ - /* No check if MML is not set or if RLB is set */ - if (!MSECCFG_MML_ISSET(env) || MSECCFG_RLB_ISSET(env)) { - return true; + /* Top PMP has no 'next' to check */ + if ((pmp_index + 1u) >= MAX_RISCV_PMPS) { + return 0; } - /* - * Adding a rule with executable privileges that either is M-mode-only - * or a locked Shared-Region is not possible - */ - switch (pmp_get_epmp_operation(val)) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - case 12: - case 14: - case 15: - return true; - case 9: - case 10: - case 11: - case 13: - return false; - default: - g_assert_not_reached(); - } + return 0; } /* @@ -142,6 +82,7 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) return 0; } + /* * Accessor to set the cfg reg for a specific PMP/HART * Bounds checks and relevant lock bit. @@ -149,32 +90,52 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) { if (pmp_index < MAX_RISCV_PMPS) { - if (env->pmp_state.pmp[pmp_index].cfg_reg == val) { - /* no change */ - return false; - } + bool locked = true; - if (!pmp_is_writable(env, pmp_index)) { - qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpcfg[%u] write - locked" - " - current:0x%02x new:0x%02x\n", - pmp_index, env->pmp_state.pmp[pmp_index].cfg_reg, - val); - } else if (riscv_cpu_cfg(env)->ext_smepmp && - !pmp_is_valid_epmp_cfg(env, val)) { - qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpcfg[%u] write - invalid" - " - current:0x%02x new:0x%02x\n", - pmp_index, env->pmp_state.pmp[pmp_index].cfg_reg, - val); + if (riscv_cpu_cfg(env)->ext_smepmp) { + /* mseccfg.RLB is set */ + if (MSECCFG_RLB_ISSET(env)) { + locked = false; + } + + /* mseccfg.MML is not set */ + if (!MSECCFG_MML_ISSET(env) && !pmp_is_locked(env, pmp_index)) { + locked = false; + } + + /* mseccfg.MML is set */ + if (MSECCFG_MML_ISSET(env)) { + /* not adding execute bit */ + if ((val & PMP_LOCK) != 0 && (val & PMP_EXEC) != PMP_EXEC) { + locked = false; + } + /* shared region and not adding X bit */ + if ((val & PMP_LOCK) != PMP_LOCK && + (val & 0x7) != (PMP_WRITE | PMP_EXEC)) { + locked = false; + } + } } else { + if (!pmp_is_locked(env, pmp_index)) { + locked = false; + } + } + + if (locked) { + qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n"); + } else if (env->pmp_state.pmp[pmp_index].cfg_reg != val) { + /* If !mseccfg.MML then ignore writes with encoding RW=01 */ + if ((val & PMP_WRITE) && !(val & PMP_READ) && + !MSECCFG_MML_ISSET(env)) { + return false; + } env->pmp_state.pmp[pmp_index].cfg_reg = val; pmp_update_rule_addr(env, pmp_index); return true; } } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpcfg[%u] write - out of bounds\n", pmp_index); + "ignoring pmpcfg write - out of bounds\n"); } return false; @@ -184,6 +145,7 @@ void pmp_unlock_entries(CPURISCVState *env) { uint32_t pmp_num = pmp_get_num_rules(env); int i; + for (i = 0; i < pmp_num; i++) { env->pmp_state.pmp[i].cfg_reg &= ~(PMP_LOCK | PMP_AMATCH); } @@ -381,9 +343,7 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, /* partially inside */ if ((s + e) == 1) { qemu_log_mask(LOG_GUEST_ERROR, - "pmp violation at 0x" HWADDR_FMT_plx "+" TARGET_FMT_lu - " - access is partially inside pmp[%u]\n", - addr, size, i); + "pmp violation - access is partially inside\n"); *allowed_privs = 0; return false; } @@ -392,6 +352,16 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, const uint8_t a_field = pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); + /* + * Convert the PMP permissions to match the truth table in the + * Smepmp spec. + */ + const uint8_t smepmp_operation = + ((env->pmp_state.pmp[i].cfg_reg & PMP_LOCK) >> 4) | + ((env->pmp_state.pmp[i].cfg_reg & PMP_READ) << 2) | + (env->pmp_state.pmp[i].cfg_reg & PMP_WRITE) | + ((env->pmp_state.pmp[i].cfg_reg & PMP_EXEC) >> 2); + if (((s + e) == 2) && (PMP_AMATCH_OFF != a_field)) { /* * If the PMP entry is not off and the address is in range, @@ -410,11 +380,8 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, /* * If mseccfg.MML Bit set, do the enhanced pmp priv check */ - const uint8_t epmp_operation = - pmp_get_epmp_operation(env->pmp_state.pmp[i].cfg_reg); - if (mode == PRV_M) { - switch (epmp_operation) { + switch (smepmp_operation) { case 0: case 1: case 4: @@ -445,7 +412,7 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, g_assert_not_reached(); } } else { - switch (epmp_operation) { + switch (smepmp_operation) { case 0: case 8: case 9: @@ -549,11 +516,6 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, bool is_next_cfg_tor = false; if (addr_index < MAX_RISCV_PMPS) { - if (env->pmp_state.pmp[addr_index].addr_reg == val) { - /* no change */ - return; - } - /* * In TOR mode, need to check the lock bit of the next pmp * (if there is a next). @@ -562,36 +524,29 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, uint8_t pmp_cfg = env->pmp_state.pmp[addr_index + 1].cfg_reg; is_next_cfg_tor = PMP_AMATCH_TOR == pmp_get_a_field(pmp_cfg); - if (!pmp_is_writable(env, addr_index + 1) && is_next_cfg_tor) { + if (pmp_cfg & PMP_LOCK && is_next_cfg_tor) { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr[%u] write - pmpcfg+1 locked" - " - prev:0x" TARGET_FMT_lx " new:0x" TARGET_FMT_lx - "\n", - addr_index, - env->pmp_state.pmp[addr_index].addr_reg, val); + "ignoring pmpaddr write - pmpcfg + 1 locked\n"); return; } } - if (pmp_is_writable(env, addr_index)) { - env->pmp_state.pmp[addr_index].addr_reg = val; - pmp_update_rule_addr(env, addr_index); - if (is_next_cfg_tor) { - pmp_update_rule_addr(env, addr_index + 1); + if (!pmp_is_locked(env, addr_index)) { + if (env->pmp_state.pmp[addr_index].addr_reg != val) { + env->pmp_state.pmp[addr_index].addr_reg = val; + pmp_update_rule_addr(env, addr_index); + if (is_next_cfg_tor) { + pmp_update_rule_addr(env, addr_index + 1); + } + tlb_flush(env_cpu(env)); } - tlb_flush(env_cpu(env)); } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr[%u] write - locked" - " - prev:0x" TARGET_FMT_lx " new:0x" TARGET_FMT_lx - "\n", - addr_index, env->pmp_state.pmp[addr_index].addr_reg, - val); + "ignoring pmpaddr write - locked\n"); } } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr[%u] write - out of bounds\n", - addr_index); + "ignoring pmpaddr write - out of bounds\n"); } } @@ -608,8 +563,7 @@ target_ulong pmpaddr_csr_read(CPURISCVState *env, uint32_t addr_index) trace_pmpaddr_csr_read(env->mhartid, addr_index, val); } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr[%u] read - out of bounds\n", - addr_index); + "ignoring pmpaddr read - out of bounds\n"); } return val; From b9481cfcce00f5f0c6f117f503890e8ab0041d16 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 10 Feb 2025 15:07:54 +0000 Subject: [PATCH 003/175] FROMLIST: target/riscv: Respect mseccfg.RLB bit for TOR mode PMP entry When running in TOR mode (Top of Range) the next PMP entry controls whether the entry is locked. However simply checking if the PMP_LOCK bit is set is not sufficient with the Smepmp extension which now provides a bit (mseccfg.RLB (Rule Lock Bypass)) to disregard the lock bits. In order to respect this bit use the convenience pmp_is_locked() function rather than directly checking PMP_LOCK since this function checks mseccfg.RLB. Signed-off-by: Rob Bradford --- target/riscv/pmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index a1b36664fc071..fa028b425c083 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -524,7 +524,7 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, uint8_t pmp_cfg = env->pmp_state.pmp[addr_index + 1].cfg_reg; is_next_cfg_tor = PMP_AMATCH_TOR == pmp_get_a_field(pmp_cfg); - if (pmp_cfg & PMP_LOCK && is_next_cfg_tor) { + if (pmp_is_locked(env, addr_index + 1) && is_next_cfg_tor) { qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpaddr write - pmpcfg + 1 locked\n"); return; From 32fd03cfb919ffdcbaf55b3d5867fa4642f4eb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 13 Mar 2025 20:30:07 +0100 Subject: [PATCH 004/175] FROMLIST: target/riscv: pmp: don't allow RLB to bypass rule privileges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Smepmp is supported, mseccfg.RLB allows bypassing locks when writing CSRs but should not affect interpretation of actual PMP rules. This is not the case with the current implementation where pmp_hart_has_privs calls pmp_is_locked which implements mseccfg.RLB bypass. This commit implements the correct behavior by removing mseccfg.RLB bypass from pmp_is_locked. RLB bypass when writing CSRs is implemented by adding a new pmp_is_readonly function that calls pmp_is_locked and check mseccfg.RLB. pmp_write_cfg and pmpaddr_csr_write are changed to use this new function. Signed-off-by: Loïc Lefort Reviewed-by: Alistair Francis Reviewed-by: Daniel Henrique Barboza Reviewed-by: LIU Zhiwei  Message-ID: <20250313193011.720075-2-loic@rivosinc.com> --- target/riscv/pmp.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index fa028b425c083..738f19f4a2947 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -45,11 +45,6 @@ static inline uint8_t pmp_get_a_field(uint8_t cfg) */ static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) { - /* mseccfg.RLB is set */ - if (MSECCFG_RLB_ISSET(env)) { - return 0; - } - if (env->pmp_state.pmp[pmp_index].cfg_reg & PMP_LOCK) { return 1; } @@ -62,6 +57,15 @@ static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) return 0; } +/* + * Check whether a PMP is locked for writing or not. + * (i.e. has LOCK flag and mseccfg.RLB is unset) + */ +static int pmp_is_readonly(CPURISCVState *env, uint32_t pmp_index) +{ + return pmp_is_locked(env, pmp_index) && !MSECCFG_RLB_ISSET(env); +} + /* * Count the number of active rules. */ @@ -90,39 +94,38 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) { if (pmp_index < MAX_RISCV_PMPS) { - bool locked = true; + bool readonly = true; if (riscv_cpu_cfg(env)->ext_smepmp) { /* mseccfg.RLB is set */ if (MSECCFG_RLB_ISSET(env)) { - locked = false; + readonly = false; } /* mseccfg.MML is not set */ - if (!MSECCFG_MML_ISSET(env) && !pmp_is_locked(env, pmp_index)) { - locked = false; + if (!MSECCFG_MML_ISSET(env) && !pmp_is_readonly(env, pmp_index)) { + readonly = false; } /* mseccfg.MML is set */ if (MSECCFG_MML_ISSET(env)) { /* not adding execute bit */ if ((val & PMP_LOCK) != 0 && (val & PMP_EXEC) != PMP_EXEC) { - locked = false; + readonly = false; } /* shared region and not adding X bit */ if ((val & PMP_LOCK) != PMP_LOCK && (val & 0x7) != (PMP_WRITE | PMP_EXEC)) { - locked = false; + readonly = false; } } } else { - if (!pmp_is_locked(env, pmp_index)) { - locked = false; - } + readonly = pmp_is_readonly(env, pmp_index); } - if (locked) { - qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n"); + if (readonly) { + qemu_log_mask(LOG_GUEST_ERROR, + "ignoring pmpcfg write - read only\n"); } else if (env->pmp_state.pmp[pmp_index].cfg_reg != val) { /* If !mseccfg.MML then ignore writes with encoding RW=01 */ if ((val & PMP_WRITE) && !(val & PMP_READ) && @@ -524,14 +527,14 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, uint8_t pmp_cfg = env->pmp_state.pmp[addr_index + 1].cfg_reg; is_next_cfg_tor = PMP_AMATCH_TOR == pmp_get_a_field(pmp_cfg); - if (pmp_is_locked(env, addr_index + 1) && is_next_cfg_tor) { + if (pmp_is_readonly(env, addr_index + 1) && is_next_cfg_tor) { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr write - pmpcfg + 1 locked\n"); + "ignoring pmpaddr write - pmpcfg+1 read only\n"); return; } } - if (!pmp_is_locked(env, addr_index)) { + if (!pmp_is_readonly(env, addr_index)) { if (env->pmp_state.pmp[addr_index].addr_reg != val) { env->pmp_state.pmp[addr_index].addr_reg = val; pmp_update_rule_addr(env, addr_index); @@ -542,7 +545,7 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, } } else { qemu_log_mask(LOG_GUEST_ERROR, - "ignoring pmpaddr write - locked\n"); + "ignoring pmpaddr write - read only\n"); } } else { qemu_log_mask(LOG_GUEST_ERROR, From 87c92bbdb518bdf47ae684df30b8eecc1180078f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 13 Mar 2025 20:30:08 +0100 Subject: [PATCH 005/175] FROMLIST: target/riscv: pmp: move Smepmp operation conversion into a function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort Reviewed-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Reviewed-by: LIU Zhiwei Message-ID: <20250313193011.720075-3-loic@rivosinc.com> --- target/riscv/pmp.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 738f19f4a2947..e75c7920feb80 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -31,6 +31,15 @@ static bool pmp_write_cfg(CPURISCVState *env, uint32_t addr_index, uint8_t val); static uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t addr_index); +/* + * Convert the PMP permissions to match the truth table in the Smepmp spec. + */ +static inline uint8_t pmp_get_smepmp_operation(uint8_t cfg) +{ + return ((cfg & PMP_LOCK) >> 4) | ((cfg & PMP_READ) << 2) | + (cfg & PMP_WRITE) | ((cfg & PMP_EXEC) >> 2); +} + /* * Accessor method to extract address matching type 'a field' from cfg reg */ @@ -355,16 +364,6 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, const uint8_t a_field = pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); - /* - * Convert the PMP permissions to match the truth table in the - * Smepmp spec. - */ - const uint8_t smepmp_operation = - ((env->pmp_state.pmp[i].cfg_reg & PMP_LOCK) >> 4) | - ((env->pmp_state.pmp[i].cfg_reg & PMP_READ) << 2) | - (env->pmp_state.pmp[i].cfg_reg & PMP_WRITE) | - ((env->pmp_state.pmp[i].cfg_reg & PMP_EXEC) >> 2); - if (((s + e) == 2) && (PMP_AMATCH_OFF != a_field)) { /* * If the PMP entry is not off and the address is in range, @@ -383,6 +382,9 @@ bool pmp_hart_has_privs(CPURISCVState *env, hwaddr addr, /* * If mseccfg.MML Bit set, do the enhanced pmp priv check */ + const uint8_t smepmp_operation = + pmp_get_smepmp_operation(env->pmp_state.pmp[i].cfg_reg); + if (mode == PRV_M) { switch (smepmp_operation) { case 0: From d010aaa6f32dd2f98da46e32f53f8ad3f99466b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 13 Mar 2025 20:30:09 +0100 Subject: [PATCH 006/175] FROMLIST: target/riscv: pmp: fix checks on writes to pmpcfg in Smepmp MML mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With Machine Mode Lockdown (mseccfg.MML) set and RLB not set, checks on pmpcfg writes would match the wrong cases of Smepmp truth table. The existing code allows writes for the following cases: - L=1, X=0: cases 8, 10, 12, 14 - L=0, RWX!=WX: cases 0-2, 4-6 This leaves cases 3, 7, 9, 11, 13, 15 for which writes are ignored. From the Smepmp specification: "Adding a rule with executable privileges that either is M-mode-only or a locked Shared-Region is not possible (...)" This description matches cases 9-11, 13 of the truth table. This commit implements an explicit check for these cases by using pmp_get_epmp_operation to convert between PMP configuration and Smepmp truth table cases. Signed-off-by: Loïc Lefort Reviewed-by: Daniel Henrique Barboza Reviewed-by: LIU Zhiwei Message-ID: <20250313193011.720075-4-loic@rivosinc.com> --- target/riscv/pmp.c | 79 +++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index e75c7920feb80..28433148e37c5 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -75,6 +75,44 @@ static int pmp_is_readonly(CPURISCVState *env, uint32_t pmp_index) return pmp_is_locked(env, pmp_index) && !MSECCFG_RLB_ISSET(env); } +/* + * Check whether `val` is an invalid Smepmp config value + */ +static int pmp_is_invalid_smepmp_cfg(CPURISCVState *env, uint8_t val) +{ + /* No check if mseccfg.MML is not set or if mseccfg.RLB is set */ + if (!MSECCFG_MML_ISSET(env) || MSECCFG_RLB_ISSET(env)) { + return 0; + } + + /* + * Adding a rule with executable privileges that either is M-mode-only + * or a locked Shared-Region is not possible + */ + switch (pmp_get_smepmp_operation(val)) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 12: + case 14: + case 15: + return 0; + case 9: + case 10: + case 11: + case 13: + return 1; + default: + g_assert_not_reached(); + } +} + /* * Count the number of active rules. */ @@ -103,44 +141,13 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) { if (pmp_index < MAX_RISCV_PMPS) { - bool readonly = true; - - if (riscv_cpu_cfg(env)->ext_smepmp) { - /* mseccfg.RLB is set */ - if (MSECCFG_RLB_ISSET(env)) { - readonly = false; - } - - /* mseccfg.MML is not set */ - if (!MSECCFG_MML_ISSET(env) && !pmp_is_readonly(env, pmp_index)) { - readonly = false; - } - - /* mseccfg.MML is set */ - if (MSECCFG_MML_ISSET(env)) { - /* not adding execute bit */ - if ((val & PMP_LOCK) != 0 && (val & PMP_EXEC) != PMP_EXEC) { - readonly = false; - } - /* shared region and not adding X bit */ - if ((val & PMP_LOCK) != PMP_LOCK && - (val & 0x7) != (PMP_WRITE | PMP_EXEC)) { - readonly = false; - } - } - } else { - readonly = pmp_is_readonly(env, pmp_index); - } - - if (readonly) { + if (pmp_is_readonly(env, pmp_index)) { qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - read only\n"); - } else if (env->pmp_state.pmp[pmp_index].cfg_reg != val) { - /* If !mseccfg.MML then ignore writes with encoding RW=01 */ - if ((val & PMP_WRITE) && !(val & PMP_READ) && - !MSECCFG_MML_ISSET(env)) { - return false; - } + } else if (pmp_is_invalid_smepmp_cfg(env, val)) { + qemu_log_mask(LOG_GUEST_ERROR, + "ignoring pmpcfg write - invalid\n"); + } else { env->pmp_state.pmp[pmp_index].cfg_reg = val; pmp_update_rule_addr(env, pmp_index); return true; From 096f057f14c32be40dcc2a7635b2fd014ca7823b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 13 Mar 2025 20:30:10 +0100 Subject: [PATCH 007/175] FROMLIST: target/riscv: pmp: exit csr writes early if value was not changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort Reviewed-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Reviewed-by: LIU Zhiwei Message-ID: <20250313193011.720075-5-loic@rivosinc.com> --- target/riscv/pmp.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 28433148e37c5..1f2d0848c6942 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -141,6 +141,11 @@ static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) { if (pmp_index < MAX_RISCV_PMPS) { + if (env->pmp_state.pmp[pmp_index].cfg_reg == val) { + /* no change */ + return false; + } + if (pmp_is_readonly(env, pmp_index)) { qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - read only\n"); @@ -528,6 +533,11 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, bool is_next_cfg_tor = false; if (addr_index < MAX_RISCV_PMPS) { + if (env->pmp_state.pmp[addr_index].addr_reg == val) { + /* no change */ + return; + } + /* * In TOR mode, need to check the lock bit of the next pmp * (if there is a next). @@ -544,14 +554,12 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, } if (!pmp_is_readonly(env, addr_index)) { - if (env->pmp_state.pmp[addr_index].addr_reg != val) { - env->pmp_state.pmp[addr_index].addr_reg = val; - pmp_update_rule_addr(env, addr_index); - if (is_next_cfg_tor) { - pmp_update_rule_addr(env, addr_index + 1); - } - tlb_flush(env_cpu(env)); + env->pmp_state.pmp[addr_index].addr_reg = val; + pmp_update_rule_addr(env, addr_index); + if (is_next_cfg_tor) { + pmp_update_rule_addr(env, addr_index + 1); } + tlb_flush(env_cpu(env)); } else { qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpaddr write - read only\n"); From 11be082455aa9e5f1e3c484c1db7f255e8a0f95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 13 Mar 2025 20:30:11 +0100 Subject: [PATCH 008/175] FROMLIST: target/riscv: pmp: remove redundant check in pmp_is_locked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove useless check in pmp_is_locked, the function will return 0 in either case. Signed-off-by: Loïc Lefort Reviewed-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Reviewed-by: LIU Zhiwei Message-ID: <20250313193011.720075-6-loic@rivosinc.com> --- target/riscv/pmp.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 1f2d0848c6942..866bedf980771 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -58,11 +58,6 @@ static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) return 1; } - /* Top PMP has no 'next' to check */ - if ((pmp_index + 1u) >= MAX_RISCV_PMPS) { - return 0; - } - return 0; } From 84bdfe15caab08c4cfb3be98c47425f57819accd Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 12:28:59 +0200 Subject: [PATCH 009/175] [ot] hw/opentitan: ot_hmac.c: code cleanup - re-organize code to follow the common function order for all OT devices - prepare for clock management upgrade Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_hmac.c | 190 ++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 2503d43d65989..72e66b07db577 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -664,70 +664,6 @@ static inline void ot_hmac_wipe_buffer(OtHMACState *s, uint32_t *buffer, } } -static uint64_t ot_hmac_fifo_read(void *opaque, hwaddr addr, unsigned size) -{ - (void)opaque; - (void)addr; - (void)size; - qemu_log_mask(LOG_GUEST_ERROR, "%s: MSG_FIFO is write only\n", __func__); - - return 0; -} - -static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, - unsigned size) -{ - OtHMACState *s = OT_HMAC(opaque); - - uint32_t pc = ibex_get_current_pc(); - trace_ot_hmac_fifo_write(s->ot_id, (uint32_t)addr, (uint32_t)value, size, - pc); - - if (!s->regs->cmd) { - ot_hmac_report_error(s, R_ERR_CODE_PUSH_MSG_WHEN_DISALLOWED); - return; - } - - if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) { - ot_hmac_report_error(s, R_ERR_CODE_PUSH_MSG_WHEN_SHA_DISABLED); - return; - } - - if (s->regs->cfg & R_CFG_ENDIAN_SWAP_MASK) { - if (size == 4u) { - value = bswap32((uint32_t)value); - } else if (size == 2u) { - value = bswap16((uint16_t)value); - } - } - - ibex_irq_set(&s->clkmgr, true); - - for (unsigned i = 0; i < size; i++) { - uint8_t b = value; - g_assert(!fifo8_is_full(&s->input_fifo)); - fifo8_push(&s->input_fifo, b); - value >>= 8u; - } - - s->regs->msg_length += (uint64_t)size * 8u; - - /* - * Note: real HW may stall the bus till some room is available in the input - * FIFO. In QEMU, we do not want to stall the I/O thread to emulate this - * feature. The workaround is to let the FIFO fill up with an arbitrary - * length, always smaller than the FIFO capacity, here half the size of the - * FIFO then process the whole FIFO content in one step. This let the FIFO - * depth register to update on each call as the real HW. However the FIFO - * can never be full, which is not supposed to occur on the real HW anyway - * since the HMAC is reportedly faster than the Ibex capability to fill in - * the FIFO. Could be different with DMA access though. - */ - if (fifo8_num_used(&s->input_fifo) >= OT_HMAC_FIFO_LENGTH / 2u) { - ot_hmac_process_fifo(s); - } -} - static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size) { OtHMACState *s = OT_HMAC(opaque); @@ -1187,6 +1123,70 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, } } +static uint64_t ot_hmac_fifo_read(void *opaque, hwaddr addr, unsigned size) +{ + (void)opaque; + (void)addr; + (void)size; + qemu_log_mask(LOG_GUEST_ERROR, "%s: MSG_FIFO is write only\n", __func__); + + return 0; +} + +static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + OtHMACState *s = OT_HMAC(opaque); + + uint32_t pc = ibex_get_current_pc(); + trace_ot_hmac_fifo_write(s->ot_id, (uint32_t)addr, (uint32_t)value, size, + pc); + + if (!s->regs->cmd) { + ot_hmac_report_error(s, R_ERR_CODE_PUSH_MSG_WHEN_DISALLOWED); + return; + } + + if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) { + ot_hmac_report_error(s, R_ERR_CODE_PUSH_MSG_WHEN_SHA_DISABLED); + return; + } + + if (s->regs->cfg & R_CFG_ENDIAN_SWAP_MASK) { + if (size == 4u) { + value = bswap32((uint32_t)value); + } else if (size == 2u) { + value = bswap16((uint16_t)value); + } + } + + ibex_irq_set(&s->clkmgr, true); + + for (unsigned i = 0; i < size; i++) { + uint8_t b = value; + g_assert(!fifo8_is_full(&s->input_fifo)); + fifo8_push(&s->input_fifo, b); + value >>= 8u; + } + + s->regs->msg_length += (uint64_t)size * 8u; + + /* + * Note: real HW may stall the bus till some room is available in the input + * FIFO. In QEMU, we do not want to stall the I/O thread to emulate this + * feature. The workaround is to let the FIFO fill up with an arbitrary + * length, always smaller than the FIFO capacity, here half the size of the + * FIFO then process the whole FIFO content in one step. This let the FIFO + * depth register to update on each call as the real HW. However the FIFO + * can never be full, which is not supposed to occur on the real HW anyway + * since the HMAC is reportedly faster than the Ibex capability to fill in + * the FIFO. Could be different with DMA access though. + */ + if (fifo8_num_used(&s->input_fifo) >= OT_HMAC_FIFO_LENGTH / 2u) { + ot_hmac_process_fifo(s); + } +} + static Property ot_hmac_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtHMACState, ot_id), DEFINE_PROP_END_OF_LIST(), @@ -1212,32 +1212,27 @@ static const MemoryRegionOps ot_hmac_fifo_ops = { }, }; -static void ot_hmac_init(Object *obj) +static void ot_hmac_reset_enter(Object *obj, ResetType type) { + OtHMACClass *c = OT_HMAC_GET_CLASS(obj); OtHMACState *s = OT_HMAC(obj); + OtHMACRegisters *r = s->regs; - s->regs = g_new0(OtHMACRegisters, 1u); - s->ctx = g_new(OtHMACContext, 1u); - - for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) { - ibex_sysbus_init_irq(obj, &s->irqs[ix]); + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); } - ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); - ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); - memory_region_init(&s->mmio, OBJECT(s), TYPE_OT_HMAC, OT_HMAC_WHOLE_SIZE); - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); + ibex_irq_set(&s->clkmgr, false); - memory_region_init_io(&s->regs_mmio, obj, &ot_hmac_regs_ops, s, - TYPE_OT_HMAC ".regs", REGS_SIZE); - memory_region_add_subregion(&s->mmio, OT_HMAC_REGS_BASE, &s->regs_mmio); + memset(s->ctx, 0, sizeof(*(s->ctx))); + memset(s->regs, 0, sizeof(*(s->regs))); - memory_region_init_io(&s->fifo_mmio, obj, &ot_hmac_fifo_ops, s, - TYPE_OT_HMAC ".fifo", OT_HMAC_FIFO_SIZE); - memory_region_add_subregion(&s->mmio, OT_HMAC_FIFO_BASE, &s->fifo_mmio); + r->cfg = 0x4100u; - /* FIFO sizes as per OT Spec */ - fifo8_create(&s->input_fifo, OT_HMAC_FIFO_LENGTH); + ot_hmac_update_irqs(s); + ot_hmac_update_alert(s); + + fifo8_reset(&s->input_fifo); } static void ot_hmac_realize(DeviceState *dev, Error **errp) @@ -1249,27 +1244,32 @@ static void ot_hmac_realize(DeviceState *dev, Error **errp) g_assert(s->ot_id); } -static void ot_hmac_reset_enter(Object *obj, ResetType type) +static void ot_hmac_init(Object *obj) { - OtHMACClass *c = OT_HMAC_GET_CLASS(obj); OtHMACState *s = OT_HMAC(obj); - OtHMACRegisters *r = s->regs; - if (c->parent_phases.enter) { - c->parent_phases.enter(obj, type); - } + s->regs = g_new0(OtHMACRegisters, 1u); + s->ctx = g_new(OtHMACContext, 1u); - ibex_irq_set(&s->clkmgr, false); + for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) { + ibex_sysbus_init_irq(obj, &s->irqs[ix]); + } + ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); + ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); - memset(s->ctx, 0, sizeof(*(s->ctx))); - memset(s->regs, 0, sizeof(*(s->regs))); + memory_region_init(&s->mmio, OBJECT(s), TYPE_OT_HMAC, OT_HMAC_WHOLE_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); - r->cfg = 0x4100u; + memory_region_init_io(&s->regs_mmio, obj, &ot_hmac_regs_ops, s, + TYPE_OT_HMAC ".regs", REGS_SIZE); + memory_region_add_subregion(&s->mmio, OT_HMAC_REGS_BASE, &s->regs_mmio); - ot_hmac_update_irqs(s); - ot_hmac_update_alert(s); + memory_region_init_io(&s->fifo_mmio, obj, &ot_hmac_fifo_ops, s, + TYPE_OT_HMAC ".fifo", OT_HMAC_FIFO_SIZE); + memory_region_add_subregion(&s->mmio, OT_HMAC_FIFO_BASE, &s->fifo_mmio); - fifo8_reset(&s->input_fifo); + /* FIFO sizes as per OT Spec */ + fifo8_create(&s->input_fifo, OT_HMAC_FIFO_LENGTH); } static void ot_hmac_class_init(ObjectClass *klass, void *data) From 97482535be3cc7381302fce8cdb562c0a61d372c Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 22 Apr 2025 11:25:49 +0200 Subject: [PATCH 010/175] [ot] hw/opentitan: ot_kmac: replace public functions with class functions Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_kmac.c | 20 +++++++++----------- hw/opentitan/ot_lc_ctrl.c | 11 ++++++++--- hw/opentitan/ot_rom_ctrl.c | 11 ++++++++--- include/hw/opentitan/ot_kmac.h | 21 +++++++++++++++------ 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index 15e941da42a51..ce79f057d12cd 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -364,7 +364,7 @@ typedef struct { unsigned index; /* app index */ OtKMACAppCfg cfg; /* configuration */ OtKMACAppReq req; /* pending request */ - ot_kmac_response_fn fn; /* response callback */ + OtKmacResponse fn; /* response callback */ void *opaque; /* opaque parameter to response callback */ bool connected; /* app is connected to KMAC */ bool req_pending; /* true if pending request */ @@ -405,11 +405,6 @@ struct OtKMACState { uint8_t num_app; }; -struct OtKMACClass { - SysBusDeviceClass parent_class; - ResettablePhases parent_phases; -}; - static void ot_kmac_change_fsm_state_line(OtKMACState *s, OtKMACFsmState state, int line) { @@ -1444,9 +1439,9 @@ static void ot_kmac_msgfifo_write(void *opaque, hwaddr addr, uint64_t value, ot_kmac_trigger_deferred_bh(s); } -void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, - const OtKMACAppCfg *cfg, ot_kmac_response_fn fn, - void *opaque) +static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, + const OtKMACAppCfg *cfg, OtKmacResponse fn, + void *opaque) { g_assert(app_idx < s->num_app); @@ -1512,8 +1507,8 @@ static void ot_kmac_start_pending_app(OtKMACState *s) } } -void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, - const OtKMACAppReq *req) +static void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, + const OtKMACAppReq *req) { g_assert(app_idx < s->num_app); @@ -1668,6 +1663,9 @@ static void ot_kmac_class_init(ObjectClass *klass, void *data) OtKMACClass *kc = OT_KMAC_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_kmac_reset_enter, NULL, NULL, &kc->parent_phases); + + kc->connect_app = &ot_kmac_connect_app; + kc->app_request = &ot_kmac_app_request; } static const TypeInfo ot_kmac_info = { diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 98843b20230f8..434cbd838a5c6 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -1085,7 +1085,8 @@ static void ot_lc_ctrl_kmac_request(OtLcCtrlState *s) TRACE_LC_CTRL("KMAC input: %s", ot_lc_ctrl_hexdump(&req.msg_data[0], 8u)); - ot_kmac_app_request(s->kmac, s->kmac_app, &req); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->app_request(s->kmac, s->kmac_app, &req); } static void ot_lc_ctrl_kmac_handle_resp(void *opaque, const OtKMACAppRsp *rsp) @@ -1499,8 +1500,9 @@ static void ot_lc_ctrl_initialize(OtLcCtrlState *s) (((uint32_t)s->silicon_creator_id) << 16u) | ((uint32_t)s->product_id); s->regs[R_HW_REVISION1] = (uint32_t)s->revision_id; - ot_kmac_connect_app(s->kmac, s->kmac_app, &OT_LC_CTRL_KMAC_CONFIG, - &ot_lc_ctrl_kmac_handle_resp, s); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->connect_app(s->kmac, s->kmac_app, &OT_LC_CTRL_KMAC_CONFIG, + &ot_lc_ctrl_kmac_handle_resp, s); uint32_t enc_state = ot_lc_ctrl_load_lc_info(s); if (enc_state == UINT32_MAX) { @@ -2157,6 +2159,9 @@ static void ot_lc_ctrl_realize(DeviceState *dev, Error **errp) g_assert(s->otp_ctrl); g_assert(s->kmac); g_assert(s->kmac_app != UINT8_MAX); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + g_assert(kc->connect_app); + g_assert(kc->app_request); /* * "ID of the silicon creator. Assigned by the OpenTitan project. diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index 775cfe83847f8..8bcef7024a5dd 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -348,7 +348,8 @@ static void ot_rom_ctrl_send_kmac_req(OtRomCtrlState *s) g_assert(blen == req.msg_len); memcpy(req.msg_data, buf, req.msg_len); - ot_kmac_app_request(s->kmac, s->kmac_app, &req); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->app_request(s->kmac, s->kmac_app, &req); } static void @@ -1089,8 +1090,9 @@ static void ot_rom_ctrl_reset_hold(Object *obj, ResetType type) ibex_irq_set(&s->pwrmgr_done, false); /* connect to KMAC */ - ot_kmac_connect_app(s->kmac, s->kmac_app, &KMAC_APP_CFG, - ot_rom_ctrl_handle_kmac_response, s); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->connect_app(s->kmac, s->kmac_app, &KMAC_APP_CFG, + ot_rom_ctrl_handle_kmac_response, s); } static void ot_rom_ctrl_reset_exit(Object *obj, ResetType type) @@ -1139,6 +1141,9 @@ static void ot_rom_ctrl_realize(DeviceState *dev, Error **errp) g_assert(s->size); g_assert(s->kmac); g_assert(s->kmac_app != UINT8_MAX); + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + g_assert(kc->connect_app); + g_assert(kc->app_request); memory_region_init_rom_device_nomigrate(&s->mem, OBJECT(dev), &ot_rom_ctrl_mem_ops, s, diff --git a/include/hw/opentitan/ot_kmac.h b/include/hw/opentitan/ot_kmac.h index 292d6e1ffd09f..8ab1e0e37b97a 100644 --- a/include/hw/opentitan/ot_kmac.h +++ b/include/hw/opentitan/ot_kmac.h @@ -98,7 +98,7 @@ typedef struct { * function. This is usually the requester device instance. * @rsp the KMAC response. */ -typedef void (*ot_kmac_response_fn)(void *opaque, const OtKMACAppRsp *rsp); +typedef void (*OtKmacResponse)(void *opaque, const OtKMACAppRsp *rsp); /** * Connect a application to the KMAC device. @@ -109,9 +109,9 @@ typedef void (*ot_kmac_response_fn)(void *opaque, const OtKMACAppRsp *rsp); * @fn the function to call when an request has been processed. * @opaque a opaque pointer to forward to the response function. */ -void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, - const OtKMACAppCfg *cfg, ot_kmac_response_fn fn, - void *opaque); +typedef void (*OtKmacConnectApp)(OtKMACState *s, unsigned app_idx, + const OtKMACAppCfg *cfg, OtKmacResponse fn, + void *opaque); /** * Send a new application request to the KMAC device. @@ -120,7 +120,16 @@ void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, * @app_idx the application index. * @req the KMAC request to process. */ -void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, - const OtKMACAppReq *req); +typedef void (*OtKmacAppRequest)(OtKMACState *s, unsigned app_idx, + const OtKMACAppReq *req); + +struct OtKMACClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; + + OtKmacConnectApp connect_app; + OtKmacAppRequest app_request; +}; + #endif /* HW_OPENTITAN_OT_KMAC_H */ From d5ed0f44cbaba8a1e38ec0abe9ec09045b98f958 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 22 Apr 2025 11:05:10 +0200 Subject: [PATCH 011/175] [ot] hw/opentitan: ot_csrng: replace public functions with class functions Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_csrng.c | 22 +++++++++----------- hw/opentitan/ot_edn.c | 15 +++++++++----- include/hw/opentitan/ot_csrng.h | 36 ++++++++++++++------------------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/hw/opentitan/ot_csrng.c b/hw/opentitan/ot_csrng.c index a1ff8f37cacc9..2f320e8381df4 100644 --- a/hw/opentitan/ot_csrng.c +++ b/hw/opentitan/ot_csrng.c @@ -315,7 +315,7 @@ typedef struct OtCSRNGInstance { bool fips; } sw; struct { - ot_csrng_genbit_filler_fn filler; + OtCsrngGenbitFiller filler; void *opaque; QEMUBH *filler_bh; qemu_irq req_sts; @@ -356,11 +356,6 @@ struct OtCSRNGState { OtOTPState *otp_ctrl; }; -struct OtCSRNGClass { - SysBusDeviceClass parent_class; - ResettablePhases parent_phases; -}; - /* clang-format off */ static const uint8_t OtCSRNGFsmStateCode[] = { [CSRNG_IDLE] = 0b01001110, // 0x4e: idle @@ -430,12 +425,12 @@ static OtCSRNDCmdResult ot_csrng_drng_reseed(OtCSRNGInstance *inst, DeviceState *rand_dev, bool flag0); /* -------------------------------------------------------------------------- */ -/* Public API */ +/* Client API */ /* -------------------------------------------------------------------------- */ -qemu_irq -ot_csnrg_connect_hw_app(OtCSRNGState *s, unsigned app_id, qemu_irq req_sts, - ot_csrng_genbit_filler_fn filler_fn, void *opaque) +static qemu_irq +ot_csrng_connect_hw_app(OtCSRNGState *s, unsigned app_id, qemu_irq req_sts, + OtCsrngGenbitFiller filler_fn, void *opaque) { g_assert(app_id < OT_CSRNG_HW_APP_MAX); @@ -473,8 +468,8 @@ ot_csnrg_connect_hw_app(OtCSRNGState *s, unsigned app_id, qemu_irq req_sts, (int)app_id); } -OtCSRNGCmdStatus ot_csrng_push_command(OtCSRNGState *s, unsigned app_id, - uint32_t word) +static OtCSRNGCmdStatus +ot_csrng_push_command(OtCSRNGState *s, unsigned app_id, uint32_t word) { g_assert(app_id < OT_CSRNG_HW_APP_MAX); @@ -2091,6 +2086,9 @@ static void ot_csrng_class_init(ObjectClass *klass, void *data) OtCSRNGClass *cc = OT_CSRNG_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_csrng_reset_enter, NULL, NULL, &cc->parent_phases); + + cc->connect_hw_app = &ot_csrng_connect_hw_app; + cc->push_command = &ot_csrng_push_command; } static const TypeInfo ot_csrng_info = { diff --git a/hw/opentitan/ot_edn.c b/hw/opentitan/ot_edn.c index 44f2345eee91c..45e59951d5d38 100644 --- a/hw/opentitan/ot_edn.c +++ b/hw/opentitan/ot_edn.c @@ -202,6 +202,7 @@ typedef enum { } OtEDNFsmState; typedef struct { + OtCSRNGClass *csrng; /* CSRNG class */ OtCSRNGState *device; /* CSRNG instance */ qemu_irq genbits_ready; /* Set when ready to receive entropy */ uint32_t appid; /* unique HW application id to identify on CSRNG */ @@ -586,8 +587,9 @@ static void ot_edn_connect_csrng(OtEDNState *s) if (!c->genbits_ready) { qemu_irq req_sts; req_sts = qdev_get_gpio_in_named(DEVICE(s), TYPE_OT_EDN "-req_sts", 0); - c->genbits_ready = ot_csnrg_connect_hw_app(c->device, c->appid, req_sts, - &ot_edn_fill_bits, s); + c->genbits_ready = + c->csrng->connect_hw_app(c->device, c->appid, req_sts, + &ot_edn_fill_bits, s); g_assert(c->genbits_ready); } } @@ -621,7 +623,7 @@ ot_edn_push_csrng_request(OtEDNState *s, bool auto_mode, uint32_t length) for (unsigned cix = 0; cix < length; cix++) { trace_ot_edn_push_csrng_command(c->appid, auto_mode ? "auto" : "boot", c->buffer[cix]); - res = ot_csrng_push_command(c->device, c->appid, c->buffer[cix]); + res = c->csrng->push_command(c->device, c->appid, c->buffer[cix]); if (res != CSRNG_STATUS_SUCCESS) { trace_ot_edn_push_csrng_error(c->appid, (int)res); ot_edn_change_state(s, EDN_REJECT_CSRNG_ENTROPY); @@ -876,7 +878,7 @@ static void ot_edn_handle_disable(OtEDNState *s) /* disconnect */ qemu_irq rdy; - rdy = ot_csnrg_connect_hw_app(c->device, c->appid, NULL, NULL, NULL); + rdy = c->csrng->connect_hw_app(c->device, c->appid, NULL, NULL, NULL); g_assert(!rdy); c->genbits_ready = NULL; @@ -1028,7 +1030,7 @@ static void ot_edn_handle_sw_cmd_req(OtEDNState *s, uint32_t value) c->sw_ack = false; trace_ot_edn_push_csrng_command(c->appid, "sw", value); - OtCSRNGCmdStatus res = ot_csrng_push_command(c->device, c->appid, value); + OtCSRNGCmdStatus res = c->csrng->push_command(c->device, c->appid, value); if (res != CSRNG_STATUS_SUCCESS) { xtrace_ot_edn_error(c->appid, "CSRNG rejected command"); s->sw_cmd_ready = false; @@ -1529,6 +1531,9 @@ static void ot_edn_realize(DeviceState *dev, Error **errp) /* check that properties have been initialized */ g_assert(r->device); g_assert(r->appid < OT_CSRNG_HW_APP_MAX); + r->csrng = OT_CSRNG_GET_CLASS(r->device); + g_assert(r->csrng->connect_hw_app); + g_assert(r->csrng->push_command); } static void ot_edn_init(Object *obj) diff --git a/include/hw/opentitan/ot_csrng.h b/include/hw/opentitan/ot_csrng.h index 4c2722269b7b0..cd5cc5d1c9728 100644 --- a/include/hw/opentitan/ot_csrng.h +++ b/include/hw/opentitan/ot_csrng.h @@ -86,8 +86,8 @@ REG32(OT_CSNRG_CMD, 0) * @fips whether the entropy adhere to NIST requirements (simulated only, * current implementation does not support FIPS requirements) */ -typedef void (*ot_csrng_genbit_filler_fn)(void *opaque, const uint32_t *bits, - bool fips); +typedef void (*OtCsrngGenbitFiller)(void *opaque, const uint32_t *bits, + bool fips); /** * Connect or disconnect a HW application to the CSRNG device. @@ -104,23 +104,9 @@ typedef void (*ot_csrng_genbit_filler_fn)(void *opaque, const uint32_t *bits, * @return an IRQ line that signals whether the HW application is ready to * receive entropy, i.e. genbits_ready */ -qemu_irq -ot_csnrg_connect_hw_app(OtCSRNGState *s, unsigned app_id, qemu_irq req_sts, - ot_csrng_genbit_filler_fn filler_fn, void *opaque); - - -/** - * Request a generated entropy block. - * CSRNG only deliver (a single) entropy packet after this command is received, - * thought the genbit_filler function. This function is asynchronously called - * after this function is received. - * - * @s the CSRNG device - * @app_id the HW application unique identifier, as provided with the connect - * command - * @return 0 on success, -1 otherwise. - */ -int ot_csrng_request_entropy(OtCSRNGState *s, unsigned app_id); +typedef qemu_irq (*OtCsnrgConnectHwApp)( + OtCSRNGState *s, unsigned app_id, qemu_irq req_sts, + OtCsrngGenbitFiller filler_fn, void *opaque); /** * Push a new command. @@ -132,7 +118,15 @@ int ot_csrng_request_entropy(OtCSRNGState *s, unsigned app_id); * @return CSRNG_STATUS_SUCCESS on success. If failure, the req_sts is not * signalled for this command. */ -OtCSRNGCmdStatus ot_csrng_push_command(OtCSRNGState *s, unsigned app_id, - uint32_t word); +typedef OtCSRNGCmdStatus (*OtCsrngPushCommand)(OtCSRNGState *s, unsigned app_id, + uint32_t word); + +struct OtCSRNGClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; + + OtCsnrgConnectHwApp connect_hw_app; + OtCsrngPushCommand push_command; +}; #endif /* HW_OPENTITAN_OT_CSRNG_H */ From 467e33423ac8243db23b5145f4760a8444d233b9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 9 Apr 2025 10:29:57 +0200 Subject: [PATCH 012/175] [ot] hw/opentitan: ot_vmapper: add support to disable execution Some devices may disable instruction fetch while keeping data access. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_vmapper.c | 426 +++++++++++++++++++++++++++--- hw/opentitan/trace-events | 10 +- include/hw/opentitan/ot_vmapper.h | 11 + 3 files changed, 401 insertions(+), 46 deletions(-) diff --git a/hw/opentitan/ot_vmapper.c b/hw/opentitan/ot_vmapper.c index 8a923e1e9aed3..131ec9f0d2c61 100644 --- a/hw/opentitan/ot_vmapper.c +++ b/hw/opentitan/ot_vmapper.c @@ -71,8 +71,25 @@ struct OtVMapperState { char *ot_id; uint8_t cpu_idx; /* cpu index, i.e. which vCPU is translated */ uint8_t trans_count; /* count of translatable regions */ + uint8_t noexec_count; /* count of disable execution regions */ }; +/* + * Offset to let disabled execution ranges have a higher priority over + * remapped ranges. + * - ranges for translation use a priority level greater than or equal to this + * value, + * - ranges for disable execution use a priority level strictly less than this + * value. + */ +#define VMAP_TRANS_PRIORITY_BASE 1u + +/* + * Default count of supported "noexec region", can be overridden with a + * property, should be enough for most use cases + */ +#define OT_VMAPPER_DEFAULT_NOEXEC_REGION_COUNT 10u + #define VMAP_RANGE(_glist_) ((OtRegionRange *)((_glist_)->data)) #define VMAP_PRIOR(_ra_, _rb_) \ ((VMAP_RANGE(_ra_)->pos < VMAP_RANGE(_rb_)->pos) ? VMAP_RANGE(_ra_) : \ @@ -85,9 +102,6 @@ struct OtVMapperState { ((uint32_t)(((uintptr_t)(_g_)) & UINT32_MAX)) #define VMAP_TREE_KEY_TO_RANGE_END(_g_) ((uint32_t)(((uintptr_t)(_g_)) >> 32u)) -/* 'g_tree_remove_all' is deprecated: Not available before 2.70 */ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - /* * order ranges for g_list_sort * 1. by start address first, @@ -140,29 +154,33 @@ static gint ot_vmapper_compare_address(gconstpointer a, gconstpointer b, } #ifdef SHOW_RANGE_LIST -#define VMAP_SHOW_RANGE_LIST(_s_, _l_, _m_) \ - ot_vmapper_show_range_list(_s_, _l_, _m_) +#define VMAP_SHOW_RANGE_LIST(_s_, _i_, _l_, _m_) \ + ot_vmapper_show_range_list(_s_, _i_, _l_, _m_) -static void ot_vmapper_show_range_list(const OtVMapperState *s, +static void ot_vmapper_show_range_list(const OtVMapperState *s, bool insn, const GList *rglist, const char *msg) { - if (!s->show) { + if (!s->show || !trace_event_get_state(TRACE_OT_VMAPPER_SHOW_RANGE) || + !qemu_loglevel_mask(LOG_TRACE)) { return; } - qemu_log("%s: %s %s\n", __func__, s->ot_id, msg); + const char *kind = insn ? "insn" : "data"; + qemu_log("%s: %s %s %s\n", __func__, s->ot_id, kind, msg); const GList *current = rglist; unsigned pos = 0; while (current) { const OtRegionRange *rg = VMAP_RANGE(current); - qemu_log(" * %2u: [%2u] 0x%08x..0x%08x -> 0x%08x X:%u\n", pos, rg->prio, - rg->start, rg->end, rg->dest, rg->execute); + qemu_log(" * %2u: [%2u] 0x%08x..0x%08x -> 0x%08x%s\n", pos, rg->prio, + rg->start, rg->end, rg->dest, + (!insn || rg->execute) ? (rg->start == rg->dest ? "" : " vt") : + " nx"); current = current->next; pos++; } - qemu_log("%s: %s %u items\n\n", __func__, s->ot_id, pos); + qemu_log("%s: %s %s %u items\n\n", __func__, s->ot_id, kind, pos); } #else -#define VMAP_SHOW_RANGE_LIST(_s_, _l_, _m_) +#define VMAP_SHOW_RANGE_LIST(_s_, _i_, _l_, _m_) #endif static GTree *ot_vmapper_create_tree(OtVMapperState *s) @@ -171,6 +189,12 @@ static GTree *ot_vmapper_create_tree(OtVMapperState *s) &g_free); } +#if (GLIB_MAJOR_VERSION == 2) +/* 'g_tree_remove_all' is deprecated: Not available before 2.70 */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif /* (GLIB_MAJOR_VERSION == 2) */ + /* * there should be no need for such workarounds, however some prehistoric * OSes (CentOS 7, ...) rely on outdated Glib versions that lack useful @@ -178,10 +202,6 @@ static GTree *ot_vmapper_create_tree(OtVMapperState *s) */ #if (GLIB_MAJOR_VERSION == 2) -/* 'g_tree_remove_all' is deprecated: Not available before 2.70 */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - #if (GLIB_MINOR_VERSION >= 68) && (GLIB_MINOR_VERSION < 70) /* @@ -236,39 +256,53 @@ static void ot_vmapper_flush_tree(OtVMapperState *s, GTree *tree) #endif /* >= 2.68 */ } -/* "-Wdeprecated-declarations" */ -#pragma GCC diagnostic pop - #endif /* 2.x */ #ifdef SHOW_RANGE_TREE #define VMAP_SHOW_RANGE_TREE(_s_, _i_) ot_vmapper_show_range_tree(_s_, _i_) +typedef struct { + bool insn; + unsigned count; +} OtVmapperTreeNodeInfo; + static gboolean ot_vmapper_show_node(GTreeNode *node, gpointer data) { const OtRegionRange *rg = (const OtRegionRange *)g_tree_node_value(node); - unsigned *count = (unsigned *)data; - qemu_log(" * %2u: 0x%08x..0x%08x -> 0x%08x X:%u\n", *count, rg->start, - rg->end, rg->dest, rg->execute); - *count += 1; + OtVmapperTreeNodeInfo *info = (OtVmapperTreeNodeInfo *)data; + qemu_log(" * %2u: 0x%08x..0x%08x -> 0x%08x%s\n", info->count, rg->start, + rg->end, rg->dest, + (!info->insn || rg->execute) ? + (rg->start == rg->dest ? "" : " vt") : + " nx"); + info->count += 1; return FALSE; } static void ot_vmapper_show_range_tree(const OtVMapperState *s, bool insn) { - if (!s->show) { + if (!s->show || !trace_event_get_state(TRACE_OT_VMAPPER_SHOW_RANGE) || + !qemu_loglevel_mask(LOG_TRACE)) { return; } qemu_log("%s: %s %s\n", __func__, s->ot_id, insn ? "insn" : "data"); - unsigned count = 0; + OtVmapperTreeNodeInfo info = { + .insn = insn, + .count = 0, + }; g_tree_foreach_node(insn ? s->itree : s->dtree, &ot_vmapper_show_node, - &count); - qemu_log("%s: %s %u items\n\n", __func__, s->ot_id, count); + &info); + qemu_log("%s: %s %u items\n\n", __func__, s->ot_id, info.count); } #else #define VMAP_SHOW_RANGE_TREE(_s_, _i_) #endif +#if (GLIB_MAJOR_VERSION == 2) +/* "-Wdeprecated-declarations" */ +#pragma GCC diagnostic pop +#endif + /* class singleton */ static OtVMapperClass *ot_vmapper_class; @@ -438,6 +472,203 @@ static GList *ot_vmapper_range_discretize(OtVMapperState *s, GList *rglist) return rglist; } +static GList *ot_vmapper_range_split_noexec(OtVMapperState *s, GList *rglist) +{ + (void)s; + + /* + * split the list into two sub-lists + * - first list contains the "(no)exec" ranges; they can be identified + * thanks to their higher priority + * - second list contains the remap ranges + * the input list must be sorted, highest priority first, and all overlaps + * must have been discretized. + */ + GList *current; + GList *noexec = NULL; + GList *noexec_last = NULL; + GList *trans = NULL; + current = rglist; + while (current) { + if (VMAP_RANGE(current)->prio >= VMAP_TRANS_PRIORITY_BASE) { + /* first range that is not a noexec one */ + if (current->prev) { + /* there is at list one range in the noexec list */ + noexec = rglist; + noexec_last = current->prev; + /* end the noexec list */ + noexec_last->next = NULL; + } + /* first range of the remap list is the current node */ + trans = current; + trans->prev = NULL; + break; + } + current = current->next; + } + + if (!noexec) { + /* there are no defined noexec range, end work here */ + return rglist; + } + + /* + * for each translating range, check whether it translates into one or more + * noexec range. + */ + current = trans; + while (current) { + uint32_t dst_start = VMAP_RANGE(current)->dest; + uint32_t dst_end = + dst_start + (VMAP_RANGE(current)->end - VMAP_RANGE(current)->start); + GList *curnx = noexec; + while (curnx) { + if (VMAP_RANGE(curnx)->start > dst_end) { + /* + * ranges are sorted, no other noexec range may match, move to + * next translation range + * +-----------------+ + * | dst | + * +-----------------+ +---------+ + * | curnx | + * +---------+ + */ + break; + } + if (VMAP_RANGE(curnx)->end < dst_start) { + /* + * noexec range ends before translation range starts, skip + * noexec and move to the next one + * +-----------------+ + * | dst | + * +---------+ +-----------------+ + * | curnx | + * +---------+ + */ + curnx = curnx->next; + continue; + } + + if (VMAP_RANGE(curnx)->start > dst_start) { + /* + * noexec range starts within translation range, two cases: + * +-----------------+ +-----------------+ + * | dst | | dst | + * +---+---------+---+ +---+-------------+-----+ + * | curnx | (a1) | curnx | (a2) + * +---------+ +-------------------+ + */ + + uint32_t left_size; + OtRegionRange *right; + + /* + * create a left region that matches the left side of the + * translation region which does not care about the noexec + * region; then replace the current region with the remaining + * right side of the translation region + */ + left_size = VMAP_RANGE(curnx)->start - dst_start + 1u; + right = g_new0(OtRegionRange, 1); + memcpy(right, VMAP_RANGE(current), sizeof(OtRegionRange)); + right->start = VMAP_RANGE(current)->start + left_size; + right->dest = VMAP_RANGE(current)->dest + left_size; + VMAP_RANGE(current)->end = + VMAP_RANGE(current)->end + left_size - 1u; + dst_start += left_size; + current = g_list_insert(current, right, 1u); + current = current->next; /* i.e. right */ + /* + * the a1/a2 cases are now simplified as: + * +-------------+ +-------------+ + * | dst' | | dst' | + * +---------+---+ +-------------+-----+ + * | curnx | (a1') | curnx | (a2) + * +---------+ +-------------------+ + */ + + if (VMAP_RANGE(curnx)->end >= dst_end) { + /* + * case (a2'): curnx extends up to or beyond the current + * translation range. Apply the execution state of the + * noexec range to the current translation range, and move + * to the next one + */ + VMAP_RANGE(current)->execute = VMAP_RANGE(curnx)->execute; + break; + } + /* + * case (a1'): create a new region with the right side of the + * translation region which does not care about the current + * no exec region, and apply the execution state of the noexec + * region to the left side of the translation region + */ + left_size = + VMAP_RANGE(curnx)->end - VMAP_RANGE(curnx)->start + 1u; + right = g_new0(OtRegionRange, 1); + memcpy(right, VMAP_RANGE(current), sizeof(OtRegionRange)); + right->start = VMAP_RANGE(current)->start + left_size; + right->dest = VMAP_RANGE(current)->dest + left_size; + VMAP_RANGE(current)->end = + VMAP_RANGE(current)->end + left_size - 1u; + VMAP_RANGE(current)->execute = VMAP_RANGE(curnx)->execute; + dst_start += left_size; + current = g_list_insert(current, right, 1u); + current = current->next; /* i.e. right */ + } else { + /* + * noexec range starts before translation range, two cases: + * +------------+ +-----------+ + * | dst | | dst | + * +------+--+---------+ +-----+-----------------+ + * | curnx | (b1) | curnx | (b2) + * +---------+ +-----------------------+ + */ + if (VMAP_RANGE(curnx)->end >= dst_end) { + /* + * case (b2): curnx overlaps the whole translation range + * destination: apply the execution state of the noexec + * range to the translation range, and move to the next + * translation range. + */ + VMAP_RANGE(current)->execute = VMAP_RANGE(curnx)->execute; + break; + } + + /* + * case (b1): curnx partially overlaps the current translation + * range. Split the latter into two ranges: apply the execution + * state of the noexec range to the left translation range, + * leave the right transalation range state unmodified. + */ + uint32_t left_size = VMAP_RANGE(curnx)->end - dst_start + 1u; + OtRegionRange *right = g_new0(OtRegionRange, 1); + memcpy(right, VMAP_RANGE(current), sizeof(OtRegionRange)); + right->start = VMAP_RANGE(current)->start + left_size; + right->dest = VMAP_RANGE(current)->dest + left_size; + VMAP_RANGE(current)->end = + VMAP_RANGE(current)->start + left_size - 1u; + VMAP_RANGE(current)->execute = VMAP_RANGE(curnx)->execute; + current = g_list_insert(current, right, 1u); + current = current->next; /* i.e. right */ + } + + curnx = curnx->next; + } + + current = current->next; + } + + /* restore the whole range list */ + g_assert(noexec_last); + + /* reconnect the noexec list with the translation list */ + noexec_last->next = trans; + trans->prev = noexec_last; + + return noexec; +} + static GList * ot_vmapper_fill_empty_gaps(OtVMapperState *s, GList *rglist, bool insn) { @@ -456,7 +687,8 @@ ot_vmapper_fill_empty_gaps(OtVMapperState *s, GList *rglist, bool insn) gap->end = VMAP_RANGE(current)->start - 1u; gap->dest = gap->start; /* lowest priority */ - gap->prio = s->trans_count; + gap->prio = + s->trans_count + s->noexec_count + VMAP_TRANS_PRIORITY_BASE; gap->execute = insn; gap->active = true; rglist = g_list_insert_before(rglist, current, gap); @@ -483,7 +715,9 @@ ot_vmapper_fill_empty_gaps(OtVMapperState *s, GList *rglist, bool insn) last->start = end ? end + 1u : 0; last->end = UINT32_MAX; last->dest = last->start; /* 1:1 mapping */ - last->prio = s->trans_count; + /* lowest priority */ + last->prio = + s->trans_count + s->noexec_count + VMAP_TRANS_PRIORITY_BASE; last->execute = insn; last->active = true; if (current) { @@ -573,9 +807,10 @@ static void ot_vmapper_update(OtVMapperState *s, bool insn) GList *rglist = NULL; GTree *rgtree = insn ? s->itree : s->dtree; OtRegionRange *ranges = insn ? s->iranges : s->dranges; + unsigned range_count = s->trans_count + (insn ? s->noexec_count : 0); /* create sortable range items and add them to a new list */ - for (unsigned ix = 0; ix < s->trans_count; ix++) { + for (unsigned ix = 0; ix < range_count; ix++) { const OtRegionRange *crg = &ranges[ix]; /* ignore disabled range entries */ @@ -591,34 +826,44 @@ static void ot_vmapper_update(OtVMapperState *s, bool insn) } if (rglist) { - VMAP_SHOW_RANGE_LIST(s, rglist, "initial"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "initial"); /* sort the list, in start address order (end address if start are * equal) */ rglist = g_list_sort(rglist, &ot_vmapper_compare); - VMAP_SHOW_RANGE_LIST(s, rglist, "sorted"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "sorted"); rglist = ot_vmapper_range_discretize(s, rglist); - VMAP_SHOW_RANGE_LIST(s, rglist, "discretized"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "discretized"); /* now rglist contains a list of unique range permissions */ + /* + * split ranges that span across execution disabled HW regions and + * disable ranges that redirect execution disabled HW regions + */ + if (insn) { + rglist = ot_vmapper_range_split_noexec(s, rglist); + + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "split_nx"); + } + /* fill all empty gaps with denied ranges */ rglist = ot_vmapper_fill_empty_gaps(s, rglist, insn); - VMAP_SHOW_RANGE_LIST(s, rglist, "extended"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "extended"); /* combine adjacent items sharing the same properties */ rglist = ot_vmapper_fuse(s, rglist); - VMAP_SHOW_RANGE_LIST(s, rglist, "fused"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "fused"); } else { /* create a one item list with no access for the whole address range */ rglist = ot_vmapper_fill_empty_gaps(s, rglist, insn); - VMAP_SHOW_RANGE_LIST(s, rglist, "default"); + VMAP_SHOW_RANGE_LIST(s, insn, rglist, "default"); } /* rglist is freed on return */ @@ -626,14 +871,17 @@ static void ot_vmapper_update(OtVMapperState *s, bool insn) s->lranges[insn] = NULL; - VMAP_SHOW_RANGE_TREE(s, true); - VMAP_SHOW_RANGE_TREE(s, false); + VMAP_SHOW_RANGE_TREE(s, insn); } static void ot_vmapper_translate(OtVMapperState *s, bool insn, unsigned slot, hwaddr src, hwaddr dst, size_t size) { g_assert(slot < s->trans_count); + g_assert(src < UINT32_MAX); + g_assert(dst < UINT32_MAX); + g_assert(src + size <= UINT32_MAX); + g_assert(dst + size <= UINT32_MAX); /* * QEMU virtual address implementation is built around the size of a small @@ -690,6 +938,89 @@ static void ot_vmapper_translate(OtVMapperState *s, bool insn, unsigned slot, tlb_flush_all_cpus_synced(s->cpu); } +static unsigned +ot_vmapper_find_exec_slot(OtVMapperState *s, uint32_t start, uint32_t end) +{ + unsigned slot = UINT_MAX; + + for (unsigned ix = 0; ix < (unsigned)s->noexec_count; ix++) { + OtRegionRange *irange = &s->iranges[s->trans_count + ix]; + if (!irange->active) { + if (slot == UINT_MAX) { + /* store first free slot */ + slot = s->trans_count + ix; + } + continue; + } + if (irange->start == start && irange->end == end) { + /* may override the first non-active slot */ + slot = s->trans_count + ix; + continue; + } + /* ensure there is no active slot that overlaps the new one */ + if (irange->start <= end && irange->end >= start) { + error_report("%s: %s: range 0x%08x..0x%08x would overlaps slot %u " + "0x%08x..0x%08x", + __func__, s->ot_id, start, end, s->trans_count + ix, + irange->start, irange->end); + g_assert_not_reached(); + } + } + + if (slot == UINT_MAX) { + error_report("%s: %s: invalid exec slot 0x%08x..0x%08x\n", __func__, + s->ot_id, start, end); + g_assert_not_reached(); + } + + return slot; +} + +static hwaddr ot_vmapper_get_mr_abs_address(const MemoryRegion *mr) +{ + const MemoryRegion *root; + hwaddr abs_addr = 0; + + abs_addr += mr->addr; + for (root = mr; root->container;) { + root = root->container; + abs_addr += root->addr; + } + + return abs_addr; +} + +static void ot_vmapper_disable_exec(OtVMapperState *s, const MemoryRegion *mr, + bool disable) +{ + hwaddr base = ot_vmapper_get_mr_abs_address(mr); + size_t size = int128_getlo(mr->size); + + g_assert(base < UINT32_MAX); + g_assert(base + size <= UINT32_MAX); + + uint32_t start = (uint32_t)base; + uint32_t end = size ? base + (uint32_t)size - 1u : start; + + unsigned slot = ot_vmapper_find_exec_slot(s, start, end); + trace_ot_vmapper_disable_exec(s->ot_id, slot, memory_region_name(mr), start, + (uint32_t)size, disable); + + OtRegionRange *irange = &s->iranges[slot]; + irange->start = start; + irange->dest = start; /* no translation, only exec disablement */ + irange->end = end; + /* whatever the execution settings, reserves this slot */ + irange->active = true; + irange->execute = !disable; + + s->show = true; + ot_vmapper_update(s, true); + s->show = false; + + tlb_flush_all_cpus_synced(s->cpu); +} + static CPUState *ot_vmapper_retrieve_cpu(OtVMapperState *s) { DeviceState *cs = @@ -716,6 +1047,8 @@ static Property ot_vmapper_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtVMapperState, ot_id), DEFINE_PROP_UINT8("cpu_index", OtVMapperState, cpu_idx, UINT8_MAX), DEFINE_PROP_UINT8("trans_count", OtVMapperState, trans_count, UINT8_MAX), + DEFINE_PROP_UINT8("noexec_count", OtVMapperState, noexec_count, + OT_VMAPPER_DEFAULT_NOEXEC_REGION_COUNT), DEFINE_PROP_END_OF_LIST(), }; @@ -729,12 +1062,20 @@ static void ot_vmapper_reset_enter(Object *obj, ResetType type) } memset(s->dranges, 0, sizeof(OtRegionRange) * s->trans_count); - memset(s->iranges, 0, sizeof(OtRegionRange) * s->trans_count); + memset(s->iranges, 0, + sizeof(OtRegionRange) * (s->trans_count + s->noexec_count)); memset(s->lranges, 0, sizeof(s->lranges)); for (unsigned ix = 0; ix < s->trans_count; ix++) { - s->dranges[ix].prio = ix; - s->iranges[ix].prio = ix; + s->dranges[ix].prio = VMAP_TRANS_PRIORITY_BASE + ix; + s->iranges[ix].prio = VMAP_TRANS_PRIORITY_BASE + ix; + } + for (unsigned ix = 0; ix < (unsigned)s->noexec_count; ix++) { + /* + * there is no priority need for disable exec ranges, however they + * should always have a priority higher than the remapping ranges. + */ + s->iranges[s->trans_count + ix].prio = 0; } s->insert_mode = false; @@ -784,7 +1125,7 @@ static void ot_vmapper_realize(DeviceState *dev, Error **errp) c->instances[s->cpu_idx] = s; s->dranges = g_new0(OtRegionRange, s->trans_count); - s->iranges = g_new0(OtRegionRange, s->trans_count); + s->iranges = g_new0(OtRegionRange, s->trans_count + s->noexec_count); } static void ot_vmapper_init(Object *obj) @@ -821,6 +1162,7 @@ static void ot_vmapper_class_init(ObjectClass *klass, void *data) &vc->parent_phases); vc->translate = &ot_vmapper_translate; + vc->disable_exec = &ot_vmapper_disable_exec; ot_vmapper_class = vc; } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 5c8f1b4e6adac..9d1230c985427 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -545,7 +545,9 @@ ot_unimp_alert(const char *id, unsigned ix) "%s: #%u" # ot_vmapper.c -ot_vmapper_new_phy_addr(char *id, bool insn, uint32_t vaddr, uint32_t paddr) "%s: i:%u v:0x%08x p:0x%08x" -ot_vmapper_override_vcpu_config(char *id) "%s: redirect address resolution and force PMP to use virtual addresses" -ot_vmapper_translate_enable(char *id, bool insn, unsigned slot, uint32_t src, uint32_t dst, unsigned size) "%s: i:%u s:%u src:0x%08x dst:0x%08x size:0x%x" -ot_vmapper_translate_disable(char *id, bool insn, unsigned slot) "%s: i:%u s:%u" +ot_vmapper_disable_exec(const char *id, unsigned slot, const char *name, uint32_t src, unsigned size, bool dis) "%s: s:%u [%s] src:0x%08x size:0x%x dis:%u" +ot_vmapper_new_phy_addr(const char *id, bool insn, uint32_t vaddr, uint32_t paddr) "%s: i:%u v:0x%08x p:0x%08x" +ot_vmapper_override_vcpu_config(const char *id) "%s: redirect address resolution and force PMP to use virtual addresses" +ot_vmapper_translate_enable(const char *id, bool insn, unsigned slot, uint32_t src, uint32_t dst, unsigned size) "%s: i:%u s:%u src:0x%08x dst:0x%08x size:0x%x" +ot_vmapper_translate_disable(const char *id, bool insn, unsigned slot) "%s: i:%u s:%u" +ot_vmapper_show_range(const char *id) "%s" diff --git a/include/hw/opentitan/ot_vmapper.h b/include/hw/opentitan/ot_vmapper.h index d9bb2347e74a7..f04db7a9c47c7 100644 --- a/include/hw/opentitan/ot_vmapper.h +++ b/include/hw/opentitan/ot_vmapper.h @@ -30,6 +30,7 @@ #include "qom/object.h" #include "exec/hwaddr.h" +#include "exec/memory.h" #define TYPE_OT_VMAPPER "ot-vmapper" OBJECT_DECLARE_TYPE(OtVMapperState, OtVMapperClass, OT_VMAPPER) @@ -47,11 +48,21 @@ OBJECT_DECLARE_TYPE(OtVMapperState, OtVMapperClass, OT_VMAPPER) typedef void (*OtVMapperTranslate)(OtVMapperState *s, bool insn, unsigned slot, hwaddr src, hwaddr dst, size_t size); +/* + * Disable the execution of an address range. + * + * @mr the memory region to manage + * @disable whether to disable execution or (re-)enable it + */ +typedef void (*OtVMapperDisableExec)(OtVMapperState *s, const MemoryRegion *mr, + bool disable); + struct OtVMapperClass { DeviceClass parent_class; ResettablePhases parent_phases; OtVMapperTranslate translate; + OtVMapperDisableExec disable_exec; OtVMapperState **instances; unsigned num_instances; From 12895388c3a0af681939c3cf2645385e385e6baf Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 23 Apr 2025 19:02:52 +0200 Subject: [PATCH 013/175] [ot] hw/opentitan: ot_otp: SRAM ifetch depends on OTP file presence. If no OTP file is in use, default to enabling execution from SRAM. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 5 ++++- hw/opentitan/ot_otp_eg.c | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index b9d6dae384ea4..a9db924228751 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -3561,7 +3561,10 @@ static void ot_otp_dj_pwr_load_hw_cfg(OtOTPDjState *s) sizeof(*hw_cfg->manuf_state)); memcpy(&hw_cfg->soc_dbg_state[0], &otp->data[R_HW_CFG1_SOC_DBG_STATE], sizeof(uint32_t)); - hw_cfg->en_sram_ifetch = (uint8_t)otp->data[R_HW_CFG1_EN_SRAM_IFETCH]; + /* do not prevent execution from SRAM if no OTP configuration is loaded */ + hw_cfg->en_sram_ifetch = + s->blk ? (uint8_t)otp->data[R_HW_CFG1_EN_SRAM_IFETCH] : + OT_MULTIBITBOOL8_TRUE; } static void ot_otp_dj_pwr_load_tokens(OtOTPDjState *s) diff --git a/hw/opentitan/ot_otp_eg.c b/hw/opentitan/ot_otp_eg.c index d473997ffce98..2f665e2af1fb7 100644 --- a/hw/opentitan/ot_otp_eg.c +++ b/hw/opentitan/ot_otp_eg.c @@ -1174,8 +1174,9 @@ static void ot_otp_eg_load_hw_cfg(OtOTPEgState *s) memcpy(hw_cfg->manuf_state, &otp->data[R_MANUF_STATE], sizeof(*hw_cfg->manuf_state)); memset(hw_cfg->soc_dbg_state, 0, sizeof(hw_cfg->soc_dbg_state)); - hw_cfg->en_sram_ifetch = (uint8_t)otp->data[R_EN_SRAM_IFETCH]; - + /* do not prevent execution from SRAM if no OTP configuration is loaded */ + hw_cfg->en_sram_ifetch = + s->blk ? (uint8_t)otp->data[R_EN_SRAM_IFETCH] : OT_MULTIBITBOOL8_TRUE; entropy_cfg->en_csrng_sw_app_read = (uint8_t)otp->data[R_EN_CSRNG_SW_APP_READ]; } From edf73c29da60e2c0f28c2e68b7f3ca02659df98b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 22 Jul 2025 14:10:06 +0200 Subject: [PATCH 014/175] [ot] hw/opentitan: ot_sram_ctrl: delegate instruction fetch management to ot_vmapper Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_sram_ctrl.c | 77 ++++++++++++++++++++++++++++--------- hw/opentitan/trace-events | 1 + 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/hw/opentitan/ot_sram_ctrl.c b/hw/opentitan/ot_sram_ctrl.c index 2f7180de10c7b..30ed80c36d511 100644 --- a/hw/opentitan/ot_sram_ctrl.c +++ b/hw/opentitan/ot_sram_ctrl.c @@ -40,6 +40,7 @@ #include "hw/opentitan/ot_otp.h" #include "hw/opentitan/ot_prng.h" #include "hw/opentitan/ot_sram_ctrl.h" +#include "hw/opentitan/ot_vmapper.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" #include "hw/riscv/ibex_common.h" @@ -133,11 +134,12 @@ struct OtSramCtrlState { unsigned wsize; /* size of RAM in words */ bool initialized; /* SRAM has been fully initialized at least once */ bool initializing; /* CTRL.INIT has been requested */ - bool otp_ifetch; - bool cfg_ifetch; + bool otp_ifetch; /* whether OTP enable execution from this RAM */ + bool csr_ifetch; /* whether CSR enable execution from this RAM */ char *ot_id; OtOTPState *otp_ctrl; /* optional */ + OtVMapperState *vmapper; /* optional */ uint32_t size; /* in bytes */ uint32_t init_chunk_words; /* init chunk size in words */ bool ifetch; /* only used when no otp_ctrl is defined */ @@ -349,6 +351,41 @@ static void ot_sram_ctrl_start_initialization(OtSramCtrlState *s) ot_sram_ctrl_initialize(s, count, false); } +static void ot_sram_ctrl_update_exec(OtSramCtrlState *s) +{ + /* + * OTP content is not known on reset, as OTP initialization is delayed. + * Configuration need to be loaded on demand + */ + if (s->otp_ctrl) { + OtOTPClass *oc = OBJECT_GET_CLASS(OtOTPClass, s->otp_ctrl, TYPE_OT_OTP); + s->otp_ifetch = oc->get_hw_cfg(s->otp_ctrl)->en_sram_ifetch == + OT_MULTIBITBOOL8_TRUE; + } + + bool ifetch = s->ifetch && s->csr_ifetch && s->otp_ifetch; + + trace_ot_sram_ctrl_update_exec(s->ot_id, s->ifetch, s->csr_ifetch, + s->otp_ifetch, ifetch); + + if (!s->vmapper) { + if (!ifetch) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: Cannot disable execution, " + "VMapper not associated\n", + __func__, s->ot_id); + } + + /* for now, vmapper is not a mandatory feature */ + return; + } + + const MemoryRegion *mr = s->noinit ? &s->mem->sram : &s->mem->alias; + + OtVMapperClass *vm = OT_VMAPPER_GET_CLASS(s->vmapper); + vm->disable_exec(s->vmapper, mr, !ifetch); +} + static uint64_t ot_sram_ctrl_regs_read(void *opaque, hwaddr addr, unsigned size) { OtSramCtrlState *s = opaque; @@ -411,17 +448,16 @@ static void ot_sram_ctrl_regs_write(void *opaque, hwaddr addr, uint64_t val64, s->regs[reg] &= val32; /* RW0C */ break; case R_EXEC: - if (s->regs[R_EXEC_REGWEN]) { - val32 &= R_EXEC_EN_MASK; - s->regs[reg] = val32; - if ((s->regs[reg] == OT_MULTIBITBOOL4_TRUE) && s->otp_ifetch) { - s->cfg_ifetch = true; - } - } else { + if (!s->regs[R_EXEC_REGWEN]) { qemu_log_mask(LOG_GUEST_ERROR, "%s: %s R_EXEC protected w/ REGWEN\n", __func__, s->ot_id); + break; } + val32 &= R_EXEC_EN_MASK; + s->regs[reg] = val32; + s->csr_ifetch = (s->regs[reg] == OT_MULTIBITBOOL4_TRUE); + ot_sram_ctrl_update_exec(s); break; case R_CTRL_REGWEN: val32 &= R_CTRL_REGWEN_CTRL_REGWEN_MASK; @@ -654,6 +690,8 @@ static Property ot_sram_ctrl_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtSramCtrlState, ot_id), DEFINE_PROP_LINK("otp_ctrl", OtSramCtrlState, otp_ctrl, TYPE_OT_OTP, OtOTPState *), + DEFINE_PROP_LINK("vmapper", OtSramCtrlState, vmapper, TYPE_OT_VMAPPER, + OtVMapperState *), DEFINE_PROP_UINT32("size", OtSramCtrlState, size, 0u), DEFINE_PROP_UINT32("wci_size", OtSramCtrlState, init_chunk_words, 0u), DEFINE_PROP_BOOL("ifetch", OtSramCtrlState, ifetch, false), @@ -701,8 +739,6 @@ static void ot_sram_ctrl_reset_enter(Object *obj, ResetType type) s->regs[R_READBACK_REGWEN] = 0x1u; s->regs[R_READBACK] = OT_MULTIBITBOOL4_FALSE; - s->cfg_ifetch = 0u; /* not used for now */ - ibex_irq_set(&s->alert, (int)(bool)s->regs[R_ALERT_TEST]); } @@ -715,16 +751,13 @@ static void ot_sram_ctrl_reset_exit(Object *obj, ResetType type) c->parent_phases.exit(obj, type); } - if (s->otp_ctrl) { - OtOTPClass *oc = OBJECT_GET_CLASS(OtOTPClass, s->otp_ctrl, TYPE_OT_OTP); - s->otp_ifetch = oc->get_hw_cfg(s->otp_ctrl)->en_sram_ifetch == - OT_MULTIBITBOOL8_TRUE; - } else { - s->otp_ifetch = s->ifetch; - } - int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); ot_prng_reseed(s->prng, (uint32_t)now); + + s->otp_ifetch = (s->otp_ctrl == NULL); + s->csr_ifetch = (s->regs[R_EXEC] == OT_MULTIBITBOOL4_TRUE); + + ot_sram_ctrl_update_exec(s); } static void ot_sram_ctrl_realize(DeviceState *dev, Error **errp) @@ -734,6 +767,12 @@ static void ot_sram_ctrl_realize(DeviceState *dev, Error **errp) g_assert(s->ot_id); g_assert(s->size); + /* + * for now, vmapper is optional if ifetch is enabled and there's no + * associated OTP + */ + g_assert(s->ifetch || !s->otp_ctrl || s->vmapper); + s->wsize = DIV_ROUND_UP(s->size, sizeof(uint32_t)); unsigned size = s->wsize * sizeof(uint32_t); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 9d1230c985427..5e7e658c10e54 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -523,6 +523,7 @@ ot_sram_ctrl_reseed(const char *id) "%s" ot_sram_ctrl_schedule_init(const char *id) "%s" ot_sram_ctrl_seed_status(const char *id, bool seed_valid) "%s: seed valid: %u" ot_sram_ctrl_switch_mem(const char *id, const char *dest) "%s: to %s" +ot_sram_ctrl_update_exec(const char *id, bool cifetch, bool rifetch, bool oifetch, bool gifetch) "%s: cfg:%u csr:%u otp:%u res:%u" # ot_timer.c From 434e44d5070f4a7e470ebd6c9b7d56bc3917880e Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 23 Apr 2025 19:04:18 +0200 Subject: [PATCH 015/175] [ot] hw/riscv: ot_darjeeling, ot_earlgrey: connect SRAMs to ot_vmapper. Instruction fetch management is handled with ot_vmapper. Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 18 ++++++++++++------ hw/riscv/ot_earlgrey.c | 14 +++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 6323ab24989cb..4e4bbb6aeba5e 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -717,11 +717,13 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(0, 70) ), .link = IBEXDEVICELINKDEFS( - OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_DJ_SOC_DEVLINK("vmapper", VMAPPER) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("size", 0x10000u), - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ram") + IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ram"), + IBEX_DEV_BOOL_PROP("ifetch", true) ), }, [OT_DJ_SOC_DEV_SRAM_MBX] = { @@ -734,11 +736,13 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(0, 71) ), .link = IBEXDEVICELINKDEFS( - OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_DJ_SOC_DEVLINK("vmapper", VMAPPER) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("size", 0x1000u), - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "mbx") + IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "mbx"), + IBEX_DEV_BOOL_PROP("ifetch", false) ), }, [OT_DJ_SOC_DEV_ROM0] = { @@ -1354,11 +1358,13 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(0, 52) ), .link = IBEXDEVICELINKDEFS( - OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_DJ_SOC_DEVLINK("vmapper", VMAPPER) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("size", 0x1000u), - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ret") + IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ret"), + IBEX_DEV_BOOL_PROP("ifetch", false) ), }, [OT_DJ_SOC_DEV_VMAPPER] = { diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 656247db064ae..62445705b2c1f 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1,7 +1,7 @@ /* * QEMU RISC-V Board Compatible with OpenTitan EarlGrey FPGA platform * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * Copyright (c) 2024-2025 lowRISC contributors. * * Author(s): @@ -899,11 +899,13 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(0, 34) ), .link = IBEXDEVICELINKDEFS( - OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_EG_SOC_DEVLINK("vmapper", VMAPPER) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("size", 0x1000u), - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ret") + IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ret"), + IBEX_DEV_BOOL_PROP("ifetch", false) ), }, [OT_EG_SOC_DEV_FLASH_CTRL] = { @@ -1098,11 +1100,13 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(0, 59) ), .link = IBEXDEVICELINKDEFS( - OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_EG_SOC_DEVLINK("vmapper", VMAPPER) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("size", SRAM_MAIN_SIZE), - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ram") + IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "ram"), + IBEX_DEV_BOOL_PROP("ifetch", true) ), }, [OT_EG_SOC_DEV_ROM_CTRL] = { From ecdcd451c286a6db56a3fe050a5be17f535412d9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 28 Apr 2025 17:40:22 +0200 Subject: [PATCH 016/175] [ot] hw/opentitan: ot_flash_ctrl: delegate instruction fetch management to ot_vmapper Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_flash.c | 80 ++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c index 2a444cad66cca..300ccf7b126c9 100644 --- a/hw/opentitan/ot_flash.c +++ b/hw/opentitan/ot_flash.c @@ -49,6 +49,7 @@ #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_fifo32.h" #include "hw/opentitan/ot_flash.h" +#include "hw/opentitan/ot_vmapper.h" #include "hw/qdev-properties-system.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" @@ -763,6 +764,7 @@ struct OtFlashState { OtFlashStorage flash; BlockBackend *blk; /* Flash backend */ + OtVMapperState *vmapper; /* to disable execution from flash */ bool no_mem_prot; /* Flag to disable mem protection features */ }; @@ -1501,6 +1503,14 @@ static void ot_flash_op_execute(OtFlashState *s) } } +static void ot_flash_update_exec(OtFlashState *s) +{ + OtVMapperClass *vm = OT_VMAPPER_GET_CLASS(s->vmapper); + bool ifetch = s->regs[R_EXEC] == EXEC_EN; + + vm->disable_exec(s->vmapper, &s->mmio.mem, !ifetch); +} + static bool ot_flash_check_program_resolution(OtFlashState *s) { unsigned start_address = s->op.address / sizeof(uint32_t); @@ -1765,6 +1775,7 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64, break; case R_EXEC: s->regs[reg] = val32; + ot_flash_update_exec(s); break; case R_CONTROL: val32 &= CONTROL_MASK; @@ -2226,30 +2237,6 @@ static void ot_flash_csrs_write(void *opaque, hwaddr addr, uint64_t val64, } } -static Property ot_flash_properties[] = { - DEFINE_PROP_DRIVE("drive", OtFlashState, blk), - /* Optionally disable memory protection, as searching for valid memory - regions and checking their config can slow down regular operation. */ - DEFINE_PROP_BOOL("no-mem-prot", OtFlashState, no_mem_prot, false), - DEFINE_PROP_END_OF_LIST(), -}; - -static const MemoryRegionOps ot_flash_regs_ops = { - .read = &ot_flash_regs_read, - .write = &ot_flash_regs_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .impl.min_access_size = 4u, - .impl.max_access_size = 4u, -}; - -static const MemoryRegionOps ot_flash_csrs_ops = { - .read = &ot_flash_csrs_read, - .write = &ot_flash_csrs_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .impl.min_access_size = 4u, - .impl.max_access_size = 4u, -}; - #ifdef USE_HEXDUMP static char dbg_hexbuf[256]; static const char *ot_flash_hexdump(const uint8_t *buf, size_t size) @@ -2467,7 +2454,35 @@ static uint64_t ot_flash_mem_read(void *opaque, hwaddr addr, unsigned size) return (uint64_t)val32; }; +#endif /* #if DATA_PART_USE_IO_OPS */ + +static Property ot_flash_properties[] = { + DEFINE_PROP_DRIVE("drive", OtFlashState, blk), + DEFINE_PROP_LINK("vmapper", OtFlashState, vmapper, TYPE_OT_VMAPPER, + OtVMapperState *), + /* Optionally disable memory protection, as searching for valid memory + regions and checking their config can slow down regular operation. */ + DEFINE_PROP_BOOL("no-mem-prot", OtFlashState, no_mem_prot, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static const MemoryRegionOps ot_flash_regs_ops = { + .read = &ot_flash_regs_read, + .write = &ot_flash_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4u, + .impl.max_access_size = 4u, +}; +static const MemoryRegionOps ot_flash_csrs_ops = { + .read = &ot_flash_csrs_read, + .write = &ot_flash_csrs_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4u, + .impl.max_access_size = 4u, +}; + +#if DATA_PART_USE_IO_OPS static const MemoryRegionOps ot_flash_mem_ops = { .read = &ot_flash_mem_read, .endianness = DEVICE_NATIVE_ENDIAN, @@ -2581,11 +2596,25 @@ static void ot_flash_reset_enter(Object *obj, ResetType type) ot_flash_reset_prog_fifo(s); } +static void ot_flash_reset_exit(Object *obj, ResetType type) +{ + OtFlashClass *c = OT_FLASH_GET_CLASS(obj); + OtFlashState *s = OT_FLASH(obj); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + ot_flash_update_exec(s); +} + static void ot_flash_realize(DeviceState *dev, Error **errp) { OtFlashState *s = OT_FLASH(dev); (void)errp; + g_assert(s->vmapper); + ot_flash_load(s, &error_fatal); uint64_t size = (uint64_t)s->flash.data_size * s->flash.bank_count; @@ -2641,7 +2670,8 @@ static void ot_flash_class_init(ObjectClass *klass, void *data) ResettableClass *rc = RESETTABLE_CLASS(klass); OtFlashClass *fc = OT_FLASH_CLASS(klass); - resettable_class_set_parent_phases(rc, &ot_flash_reset_enter, NULL, NULL, + resettable_class_set_parent_phases(rc, &ot_flash_reset_enter, NULL, + &ot_flash_reset_exit, &fc->parent_phases); } From 28bfe578942847be07ceb678b29f6075cbcc1a95 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 28 Apr 2025 17:39:57 +0200 Subject: [PATCH 017/175] [ot] hw/riscv: ot_earlgrey: connect flash controller to ot_vmapper. Instruction fetch management is handled with ot_vmapper. Signed-off-by: Emmanuel Blot --- hw/riscv/ot_earlgrey.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 62445705b2c1f..61134912f07b8 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -929,6 +929,9 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(3, 38), OT_EG_SOC_GPIO_ALERT(4, 39) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("vmapper", VMAPPER) + ), }, [OT_EG_SOC_DEV_AES] = { .type = TYPE_OT_AES, From 770e76f5803e2f259dbee804013ee05225368b1e Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 28 Apr 2025 20:06:54 +0200 Subject: [PATCH 018/175] [ot] hw/opentitan: ot_i2c: only emit guest_errors if I2C timings are out of spec. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_i2c_dj.c | 170 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 161 insertions(+), 9 deletions(-) diff --git a/hw/opentitan/ot_i2c_dj.c b/hw/opentitan/ot_i2c_dj.c index b25a78474f972..4843b08365fc8 100644 --- a/hw/opentitan/ot_i2c_dj.c +++ b/hw/opentitan/ot_i2c_dj.c @@ -53,6 +53,7 @@ #include "qemu/osdep.h" #include "qemu/fifo8.h" #include "qemu/log.h" +#include "qemu/timer.h" #include "hw/i2c/i2c.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" @@ -287,6 +288,9 @@ struct OtI2CDjState { /* Set if NACK has been received by target during transaction. */ bool target_rx_nack; + /* Whether I2C timings should be checked before comm. over the bus */ + bool check_timings; + /* TX: Scheduled responses for target mode. */ Fifo8 target_tx_fifo; @@ -465,6 +469,133 @@ static void ot_i2c_dj_target_write_tx_fifo(OtI2CDjState *s, uint8_t val) fifo8_push(&s->target_tx_fifo, val); } +static bool ot_i2c_dj_check_timings(OtI2CDjState *s) +{ + uint32_t thigh = FIELD_EX32(s->regs[R_TIMING0], TIMING0, THIGH); + uint32_t tlow = FIELD_EX32(s->regs[R_TIMING0], TIMING0, TLOW); + uint32_t tr = FIELD_EX32(s->regs[R_TIMING1], TIMING1, T_R); + uint32_t tf = FIELD_EX32(s->regs[R_TIMING1], TIMING1, T_F); + uint32_t tsusta = FIELD_EX32(s->regs[R_TIMING2], TIMING2, TSU_STA); + uint32_t thdsta = FIELD_EX32(s->regs[R_TIMING2], TIMING2, THD_STA); + uint32_t tsudat = FIELD_EX32(s->regs[R_TIMING3], TIMING3, TSU_DAT); + uint32_t thddat = FIELD_EX32(s->regs[R_TIMING3], TIMING3, THD_DAT); + uint32_t tsusto = FIELD_EX32(s->regs[R_TIMING4], TIMING4, TSU_STO); + uint32_t tbuf = FIELD_EX32(s->regs[R_TIMING4], TIMING4, T_BUF); + + bool res = true; + + /* Check I2C HW limits (I2C input clock cycles) */ + + if (thddat == 0u || (thdsta < thddat + 2u)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid THD settings\n", + __func__, s->ot_id); + res = false; + } + if (tlow < 3u + tr) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid Tlow settings\n", + __func__, s->ot_id); + res = false; + } + if (thigh < 4u) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid Thigh settings\n", + __func__, s->ot_id); + res = false; + } + + /* Convert clock cycles into nanoseconds based on input clock */ + + thigh = (uint32_t)((((uint64_t)thigh) * NANOSECONDS_PER_SECOND) / s->pclk); + tlow = (uint32_t)((((uint64_t)tlow) * NANOSECONDS_PER_SECOND) / s->pclk); + + /* Check I2C limits (from I2C specification rev. 6, table 10) */ + + if (((thigh >= 4000u) && (tlow < 4700u)) || + ((tlow >= 4700u) && (thigh < 4000u)) || + ((thigh >= 600) && (tlow < 1300u)) || + ((tlow >= 1300u) && (thigh < 600u)) || + ((thigh < 260u) || (tlow < 500u))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid Thigh/Tlow settings\n", + __func__, s->ot_id); + res = false; + return res; /* subsequent checks would lead to more errors */ + } + + uint32_t tsusta_min; + uint32_t tsudat_min; + uint32_t tsusto_min; + uint32_t tbuf_min; + uint32_t tr_max; + uint32_t tf_max; + if (thigh >= 4000u) { + /* standard mode */ + tsusta_min = 4700u; + tsudat_min = 250u; + tsusto_min = 4000u; + tbuf_min = 4700u; + tr_max = 1000u; + tf_max = 300u; + } else if (thigh > 600u) { + /* fast mode */ + tsusta_min = 600u; + tsudat_min = 100u; + tsusto_min = 600u; + tbuf_min = 1300u; + tr_max = 300u; + tf_max = 300u; + } else { + /* fast mode plus */ + tsusta_min = 260u; + tsudat_min = 50u; + tsusto_min = 260u; + tbuf_min = 500u; + tr_max = 120u; + tf_max = 1230u; + } + + tsusta = + (uint32_t)((((uint64_t)tsusta) * NANOSECONDS_PER_SECOND) / s->pclk); + tsudat = + (uint32_t)((((uint64_t)tsudat) * NANOSECONDS_PER_SECOND) / s->pclk); + tsusto = + (uint32_t)((((uint64_t)tsusto) * NANOSECONDS_PER_SECOND) / s->pclk); + tbuf = (uint32_t)((((uint64_t)tbuf) * NANOSECONDS_PER_SECOND) / s->pclk); + tr = (uint32_t)((((uint64_t)tr) * NANOSECONDS_PER_SECOND) / s->pclk); + tf = (uint32_t)((((uint64_t)tf) * NANOSECONDS_PER_SECOND) / s->pclk); + + if (tsusta < tsusta_min) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tsu;sta too low\n", __func__, + s->ot_id); + res = false; + } + if (tsudat < tsudat_min) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tsu;dat too low\n", __func__, + s->ot_id); + res = false; + } + if (tsusto < tsusto_min) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tsu;sto too low\n", __func__, + s->ot_id); + res = false; + } + if (tbuf < tbuf_min) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tbuf too low\n", __func__, + s->ot_id); + res = false; + } + if (tr > tr_max) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tr too high\n", __func__, + s->ot_id); + res = false; + } + if (tf > tf_max) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Tf too high\n", __func__, + s->ot_id); + res = false; + } + + return res; +} + static uint64_t ot_i2c_dj_read(void *opaque, hwaddr addr, unsigned size) { OtI2CDjState *s = opaque; @@ -549,8 +680,6 @@ static uint64_t ot_i2c_dj_read(void *opaque, hwaddr addr, unsigned size) case R_TIMING3: case R_TIMING4: val32 = s->regs[reg]; - qemu_log_mask(LOG_UNIMP, "%s: %s: register %s is not implemented\n", - __func__, s->ot_id, REG_NAME(reg)); break; case R_INTR_TEST: case R_FDATA: @@ -745,11 +874,15 @@ static void ot_i2c_dj_write(void *opaque, hwaddr addr, uint64_t val64, * Allow both ENABLEHOST and ENABLETARGET to be set so the * host can decide how to configure and use the controller. */ - if (FIELD_EX32(val32, CTRL, ENABLEHOST)) { - ARRAY_FIELD_DP32(s->regs, CTRL, ENABLEHOST, 1u); - } - if (FIELD_EX32(val32, CTRL, ENABLETARGET)) { - ARRAY_FIELD_DP32(s->regs, CTRL, ENABLETARGET, 1u); + val32 &= R_CTRL_LLPBK_MASK | R_CTRL_ENABLEHOST_MASK | + R_CTRL_ENABLETARGET_MASK; + s->regs[reg] = val32; + if (s->regs[reg]) { + /* check timings once, each time one or more timings are updated */ + if (s->check_timings) { + ot_i2c_dj_check_timings(s); + s->check_timings = false; + } } break; case R_FDATA: @@ -787,13 +920,29 @@ static void ot_i2c_dj_write(void *opaque, hwaddr addr, uint64_t val64, s->regs[reg] = val32; break; case R_TIMING0: + val32 &= R_TIMING0_THIGH_MASK | R_TIMING0_TLOW_MASK; + s->regs[reg] = val32; + s->check_timings = true; + break; case R_TIMING1: + val32 &= R_TIMING1_T_R_MASK | R_TIMING1_T_F_MASK; + s->regs[reg] = val32; + s->check_timings = true; + break; case R_TIMING2: + val32 &= R_TIMING2_TSU_STA_MASK | R_TIMING2_THD_STA_MASK; + s->regs[reg] = val32; + s->check_timings = true; + break; case R_TIMING3: + val32 &= R_TIMING3_TSU_DAT_MASK | R_TIMING3_THD_DAT_MASK; + s->regs[reg] = val32; + s->check_timings = true; + break; case R_TIMING4: - qemu_log_mask(LOG_UNIMP, "%s: %s: register %s is not implemented\n", - __func__, s->ot_id, REG_NAME(reg)); + val32 &= R_TIMING4_TSU_STO_MASK | R_TIMING4_T_BUF_MASK; s->regs[reg] = val32; + s->check_timings = true; break; case R_STATUS: case R_RDATA: @@ -983,6 +1132,8 @@ static void ot_i2c_dj_reset_enter(Object *obj, ResetType type) ot_i2c_dj_host_reset_rx_fifo(s); ot_i2c_dj_target_reset_tx_fifo(s); ot_i2c_dj_target_reset_rx_fifo(s); + + s->check_timings = true; } static void ot_i2c_dj_realize(DeviceState *dev, Error **errp) @@ -991,6 +1142,7 @@ static void ot_i2c_dj_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); + g_assert(s->pclk); /* TODO: check if the following can be moved to ot_i2c_dj_init */ s->bus = i2c_init_bus(dev, TYPE_OT_I2C_DJ); From 540863e3920379a363cf75319d12b22a780f5477 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Apr 2025 12:32:44 +0200 Subject: [PATCH 019/175] [ot] hw/opentitan: ot_pwrmgr: rename PwrMgr versions * EG_252 matches EarlGrey 2.5.2-rc0, which is the first supported version of QEMU OpenTitan emulation * DJ_PRE is the initial Darjeeling version based on EarlGrey 2.5.2-rc0 Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_pwrmgr.c | 27 +++++++++++++-------------- include/hw/opentitan/ot_pwrmgr.h | 6 +++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/hw/opentitan/ot_pwrmgr.c b/hw/opentitan/ot_pwrmgr.c index 02da762d88ee3..f2496094dae9d 100644 --- a/hw/opentitan/ot_pwrmgr.c +++ b/hw/opentitan/ot_pwrmgr.c @@ -321,33 +321,33 @@ typedef struct { } OtPwrMgrConfig; /* clang-format off */ -static const OtPwrMgrConfig PWRMGR_CONFIG[OT_PWMGR_VERSION_COUNT] = { - [OT_PWMGR_VERSION_EG] = { +static const OtPwrMgrConfig PWRMGR_CONFIG[OT_PWRMGR_VERSION_COUNT] = { + [OT_PWRMGR_VERSION_EG_252] = { .wakeup_count = 6u, .reset_count = 2u, .reset_mask = 0x3u }, - [OT_PWMGR_VERSION_DJ] = { + [OT_PWRMGR_VERSION_DJ_PRE] = { .wakeup_count = 6u, .reset_count = 2u, .reset_mask = 0x3u }, }; -static int PWRMGR_RESET_DISPATCH[OT_PWMGR_VERSION_COUNT][PARAM_NUM_RST_REQS] = { - [OT_PWMGR_VERSION_EG] = { +static int PWRMGR_RESET_DISPATCH[OT_PWRMGR_VERSION_COUNT][PARAM_NUM_RST_REQS] = { + [OT_PWRMGR_VERSION_EG_252] = { [0] = OT_RSTMGR_RESET_SYSCTRL, [1] = OT_RSTMGR_RESET_AON_TIMER, }, - [OT_PWMGR_VERSION_DJ] = { + [OT_PWRMGR_VERSION_DJ_PRE] = { [0] = OT_RSTMGR_RESET_AON_TIMER, [1] = OT_RSTMGR_RESET_SOC_PROXY, }, }; static const char * -PWRMGR_WAKEUP_NAMES[OT_PWMGR_VERSION_COUNT][PWRMGR_WAKEUP_MAX] = { - [OT_PWMGR_VERSION_EG] = { +PWRMGR_WAKEUP_NAMES[OT_PWRMGR_VERSION_COUNT][PWRMGR_WAKEUP_MAX] = { + [OT_PWRMGR_VERSION_EG_252] = { [0] = "SYSRST", [1] = "ADC_CTRL", [2] = "PINMUX", @@ -355,7 +355,7 @@ PWRMGR_WAKEUP_NAMES[OT_PWMGR_VERSION_COUNT][PWRMGR_WAKEUP_MAX] = { [4] = "AON_TIMER", [5] = "SENSOR", }, - [OT_PWMGR_VERSION_DJ] = { + [OT_PWRMGR_VERSION_DJ_PRE] = { [0] = "PINMUX", [1] = "USBDEV", [2] = "AON_TIMER", @@ -365,12 +365,12 @@ PWRMGR_WAKEUP_NAMES[OT_PWMGR_VERSION_COUNT][PWRMGR_WAKEUP_MAX] = { }, }; -static const char *PWRMGR_RST_NAMES[OT_PWMGR_VERSION_COUNT][PARAM_NUM_RST_REQS] = { - [OT_PWMGR_VERSION_EG] = { +static const char *PWRMGR_RST_NAMES[OT_PWRMGR_VERSION_COUNT][PARAM_NUM_RST_REQS] = { + [OT_PWRMGR_VERSION_EG_252] = { [0] = "SYSRST", [1] = "AON_TIMER", }, - [OT_PWMGR_VERSION_DJ] = { + [OT_PWRMGR_VERSION_DJ_PRE] = { [0] = "AON_TIMER", [1] = "SOC_PROXY", } @@ -920,8 +920,6 @@ static void ot_pwrmgr_reset_enter(Object *obj, ResetType type) OtPwrMgrClass *c = OT_PWRMGR_GET_CLASS(obj); OtPwrMgrState *s = OT_PWRMGR(obj); - g_assert(s->version < OT_PWMGR_VERSION_COUNT); - /* sanity checks for platform reset count and mask */ g_assert(PWRMGR_CONFIG[s->version].reset_count <= PARAM_NUM_RST_REQS); g_assert(ctpop32(PWRMGR_CONFIG[s->version].reset_mask + 1u) == 1); @@ -980,6 +978,7 @@ static void ot_pwrmgr_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); + g_assert(s->version < OT_PWRMGR_VERSION_COUNT); if (s->num_rom) { if (s->num_rom > 8u * sizeof(uint8_t)) { diff --git a/include/hw/opentitan/ot_pwrmgr.h b/include/hw/opentitan/ot_pwrmgr.h index 76f5fd6b4932c..33c89ebd91ebd 100644 --- a/include/hw/opentitan/ot_pwrmgr.h +++ b/include/hw/opentitan/ot_pwrmgr.h @@ -36,9 +36,9 @@ OBJECT_DECLARE_TYPE(OtPwrMgrState, OtPwrMgrClass, OT_PWRMGR) /* Supported PowerManager versions */ typedef enum { - OT_PWMGR_VERSION_EG, - OT_PWMGR_VERSION_DJ, - OT_PWMGR_VERSION_COUNT, + OT_PWRMGR_VERSION_EG_252, + OT_PWRMGR_VERSION_DJ_PRE, + OT_PWRMGR_VERSION_COUNT, } OtPwrMgrVersion; /* Match PWRMGR_PARAM_*_WKUP_REQ_IDX definitions */ From 5d7754fab9926d99f0cad7a83cf9c2f27b359719 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Apr 2025 12:33:05 +0200 Subject: [PATCH 020/175] [ot] hw/riscv: ot_darjeeling, ot_earlgrey: update PwrMgr versions Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 2 +- hw/riscv/ot_earlgrey.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 4e4bbb6aeba5e..32f7cf2a14813 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -1258,7 +1258,7 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("num-rom", 2u), - IBEX_DEV_UINT_PROP("version", OT_PWMGR_VERSION_DJ) + IBEX_DEV_UINT_PROP("version", OT_PWRMGR_VERSION_DJ_PRE) ), }, [OT_DJ_SOC_DEV_RSTMGR] = { diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 61134912f07b8..eb43716d11ae1 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -771,7 +771,7 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("num-rom", 1u), - IBEX_DEV_UINT_PROP("version", OT_PWMGR_VERSION_EG) + IBEX_DEV_UINT_PROP("version", OT_PWRMGR_VERSION_EG_252) ), }, [OT_EG_SOC_DEV_RSTMGR] = { From 5e3834cc153e90496740bc359e4a7c71e685d0ca Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Apr 2025 12:33:20 +0200 Subject: [PATCH 021/175] [ot] hw/opentitan: ot_rstmgr: add support for multiple RstMgr versions * EG_252 matches EarlGrey 2.5.2-rc0, which is the first supported version of QEMU OpenTitan emulation * DJ_PRE is the initial Darjeeling version based on EarlGrey 2.5.2-rc0 Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_rstmgr.c | 172 ++++++++++++++++++++++--------- hw/opentitan/trace-events | 2 +- include/hw/opentitan/ot_rstmgr.h | 13 ++- 3 files changed, 137 insertions(+), 50 deletions(-) diff --git a/hw/opentitan/ot_rstmgr.c b/hw/opentitan/ot_rstmgr.c index 54c1507689133..5601bef9c5a1d 100644 --- a/hw/opentitan/ot_rstmgr.c +++ b/hw/opentitan/ot_rstmgr.c @@ -29,13 +29,16 @@ */ #include "qemu/osdep.h" +#include "qemu/bitops.h" #include "qemu/log.h" #include "qemu/main-loop.h" #include "qemu/typedefs.h" #include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" +#include "hw/opentitan/ot_i2c_dj.h" #include "hw/opentitan/ot_rstmgr.h" +#include "hw/opentitan/ot_spi_device.h" #include "hw/opentitan/ot_spi_host.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" @@ -46,13 +49,7 @@ #include "sysemu/runstate.h" #include "trace.h" - -#define PARAM_RD_WIDTH 32u -#define PARAM_IDX_WIDTH 4u -#define PARAM_NUM_HW_RESETS 5u -#define PARAM_NUM_SW_RESETS 8u -#define PARAM_NUM_TOTAL_RESETS 8u -#define PARAM_NUM_ALERTS 2u +#define PARAM_NUM_ALERTS 2u /* clang-format off */ REG32(ALERT_TEST, 0x0u) @@ -123,24 +120,42 @@ REG32(ERR_CODE, 0x6cu) #define REG_NAME(_reg_) \ ((((_reg_) <= REGS_COUNT) && REG_NAMES[_reg_]) ? REG_NAMES[_reg_] : "?") +/* clang-format off */ #define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) static const char *REG_NAMES[REGS_COUNT] = { - REG_NAME_ENTRY(ALERT_TEST), REG_NAME_ENTRY(RESET_REQ), - REG_NAME_ENTRY(RESET_INFO), REG_NAME_ENTRY(ALERT_REGWEN), - REG_NAME_ENTRY(ALERT_INFO_CTRL), REG_NAME_ENTRY(ALERT_INFO_ATTR), - REG_NAME_ENTRY(ALERT_INFO), REG_NAME_ENTRY(CPU_REGWEN), - REG_NAME_ENTRY(CPU_INFO_CTRL), REG_NAME_ENTRY(CPU_INFO_ATTR), - REG_NAME_ENTRY(CPU_INFO), REG_NAME_ENTRY(SW_RST_REGWEN_0), - REG_NAME_ENTRY(SW_RST_REGWEN_1), REG_NAME_ENTRY(SW_RST_REGWEN_2), - REG_NAME_ENTRY(SW_RST_REGWEN_3), REG_NAME_ENTRY(SW_RST_REGWEN_4), - REG_NAME_ENTRY(SW_RST_REGWEN_5), REG_NAME_ENTRY(SW_RST_REGWEN_6), - REG_NAME_ENTRY(SW_RST_REGWEN_7), REG_NAME_ENTRY(SW_RST_CTRL_N_0), - REG_NAME_ENTRY(SW_RST_CTRL_N_1), REG_NAME_ENTRY(SW_RST_CTRL_N_2), - REG_NAME_ENTRY(SW_RST_CTRL_N_3), REG_NAME_ENTRY(SW_RST_CTRL_N_4), - REG_NAME_ENTRY(SW_RST_CTRL_N_5), REG_NAME_ENTRY(SW_RST_CTRL_N_6), - REG_NAME_ENTRY(SW_RST_CTRL_N_7), REG_NAME_ENTRY(ERR_CODE), + REG_NAME_ENTRY(ALERT_TEST), + REG_NAME_ENTRY(RESET_REQ), + REG_NAME_ENTRY(RESET_INFO), + REG_NAME_ENTRY(ALERT_REGWEN), + REG_NAME_ENTRY(ALERT_INFO_CTRL), + REG_NAME_ENTRY(ALERT_INFO_ATTR), + REG_NAME_ENTRY(ALERT_INFO), + REG_NAME_ENTRY(CPU_REGWEN), + REG_NAME_ENTRY(CPU_INFO_CTRL), + REG_NAME_ENTRY(CPU_INFO_ATTR), + REG_NAME_ENTRY(CPU_INFO), + REG_NAME_ENTRY(SW_RST_REGWEN_0), + REG_NAME_ENTRY(SW_RST_REGWEN_1), + REG_NAME_ENTRY(SW_RST_REGWEN_2), + REG_NAME_ENTRY(SW_RST_REGWEN_3), + REG_NAME_ENTRY(SW_RST_REGWEN_4), + REG_NAME_ENTRY(SW_RST_REGWEN_5), + REG_NAME_ENTRY(SW_RST_REGWEN_6), + REG_NAME_ENTRY(SW_RST_REGWEN_7), + REG_NAME_ENTRY(SW_RST_CTRL_N_0), + REG_NAME_ENTRY(SW_RST_CTRL_N_1), + REG_NAME_ENTRY(SW_RST_CTRL_N_2), + REG_NAME_ENTRY(SW_RST_CTRL_N_3), + REG_NAME_ENTRY(SW_RST_CTRL_N_4), + REG_NAME_ENTRY(SW_RST_CTRL_N_5), + REG_NAME_ENTRY(SW_RST_CTRL_N_6), + REG_NAME_ENTRY(SW_RST_CTRL_N_7), + REG_NAME_ENTRY(ERR_CODE), }; #undef REG_NAME_ENTRY +/* clang-format on */ + +#define OT_RSTMGR_SW_RESET_MAX 8u struct OtRstMgrState { SysBusDevice parent_obj; @@ -153,10 +168,11 @@ struct OtRstMgrState { CPUState *cpu; uint32_t *regs; + bool por; /* Power-On Reset property */ char *ot_id; uint32_t fatal_reset; - bool por; /* Power-On Reset property */ + uint8_t version; }; struct OtRstMgrClass { @@ -174,32 +190,77 @@ typedef struct { bool reset; } OtRstMgrResetDesc; -static const OtRstMgrResettable SW_RESETTABLE_DEVICES[PARAM_NUM_SW_RESETS] = { - [0u] = { NULL, 0u }, - [1u] = { TYPE_OT_SPI_HOST, 0u }, - [2u] = { TYPE_OT_SPI_HOST, 1u }, - [3u] = { NULL, 0u }, - [4u] = { NULL, 0u }, - [5u] = { NULL, 0u }, - [6u] = { NULL, 0u }, - [7u] = { NULL, 0u }, +typedef struct { + uint32_t reset_request_codes[OT_RSTMGR_RESET_COUNT]; + OtRstMgrResettable sw_resettable_devices[OT_RSTMGR_SW_RESET_MAX]; +} OtRstMgrConfig; + +static const OtRstMgrConfig RSTMGR_CONFIG[OT_RSTMGR_VERSION_COUNT] = { + [OT_RSTMGR_VERSION_EG_252] = { + .reset_request_codes = { + [OT_RSTMGR_RESET_POR] = BIT(0), + [OT_RSTMGR_RESET_LOW_POWER] = BIT(1), + [OT_RSTMGR_RESET_SW] = BIT(2), + [OT_RSTMGR_RESET_SYSCTRL] = BIT(3), + [OT_RSTMGR_RESET_AON_TIMER] = BIT(4), + [OT_RSTMGR_RESET_SENSOR] = BIT(5), + [OT_RSTMGR_RESET_PWRMGR] = BIT(6), + [OT_RSTMGR_RESET_ALERT_HANDLER] = BIT(7), + [OT_RSTMGR_RESET_RV_DM] = BIT(8), + }, + .sw_resettable_devices = { + [0u] = { TYPE_OT_SPI_DEVICE, 0u }, + [1u] = { TYPE_OT_SPI_HOST, 0u }, + [2u] = { TYPE_OT_SPI_HOST, 1u }, + /* + * Not yet supported + * + * [3u] = { TYPE_OT_USB, 0u }, + * [4u] = { TYPE_OT_USB, 1u }, + * [5u] = { TYPE_OT_I2C_EG, 0u }, + * [6u] = { TYPE_OT_I2C_EG, 1u }, + * [7u] = { TYPE_OT_I2C_EG, 2u }, + */ + } + }, + [OT_RSTMGR_VERSION_DJ_PRE] = { + .reset_request_codes = { + [OT_RSTMGR_RESET_POR] = BIT(0), + [OT_RSTMGR_RESET_LOW_POWER] = BIT(1), + [OT_RSTMGR_RESET_SW] = BIT(2), + [OT_RSTMGR_RESET_AON_TIMER] = BIT(3), + [OT_RSTMGR_RESET_SOC_PROXY] = BIT(4), + [OT_RSTMGR_RESET_PWRMGR] = BIT(5), + [OT_RSTMGR_RESET_ALERT_HANDLER] = BIT(6), + [OT_RSTMGR_RESET_RV_DM] = BIT(7), + }, + .sw_resettable_devices = { + [0u] = { TYPE_OT_SPI_DEVICE, 0u }, + [1u] = { TYPE_OT_SPI_HOST, 0u }, + [2u] = { TYPE_OT_I2C_DJ, 0u }, + } + }, }; -static_assert(PARAM_NUM_TOTAL_RESETS == OT_RSTMGR_RESET_COUNT, - "Invalid reset count"); - +/* clang-format off */ #define REQ_NAME_ENTRY(_req_) [OT_RSTMGR_RESET_##_req_] = stringify(_req_) static const char *OT_RST_MGR_REQUEST_NAMES[] = { + REQ_NAME_ENTRY(NONE), REQ_NAME_ENTRY(POR), REQ_NAME_ENTRY(LOW_POWER), REQ_NAME_ENTRY(SW), REQ_NAME_ENTRY(SYSCTRL), + REQ_NAME_ENTRY(SOC_PROXY), REQ_NAME_ENTRY(AON_TIMER), + REQ_NAME_ENTRY(SYSCTRL), + REQ_NAME_ENTRY(SENSOR), REQ_NAME_ENTRY(PWRMGR), REQ_NAME_ENTRY(ALERT_HANDLER), REQ_NAME_ENTRY(RV_DM), }; #undef REQ_NAME_ENTRY +/* clang-format on */ + #define REQ_NAME(_req_) \ ((_req_) < ARRAY_SIZE(OT_RST_MGR_REQUEST_NAMES)) ? \ OT_RST_MGR_REQUEST_NAMES[(_req_)] : \ @@ -263,9 +324,11 @@ static int ot_rstmgr_sw_rst_walker(DeviceState *dev, void *opaque) static void ot_rstmgr_update_sw_reset(OtRstMgrState *s, unsigned devix) { - assert(devix < ARRAY_SIZE(SW_RESETTABLE_DEVICES)); + assert(devix < OT_RSTMGR_SW_RESET_MAX); + + const OtRstMgrConfig *config = &RSTMGR_CONFIG[s->version]; + const OtRstMgrResettable *rst = &config->sw_resettable_devices[devix]; - const OtRstMgrResettable *rst = &SW_RESETTABLE_DEVICES[devix]; if (!rst->typename) { qemu_log_mask(LOG_UNIMP, "%s: %s: Reset for slot %u not yet implemented", __func__, @@ -305,13 +368,21 @@ static void ot_rstmgr_reset_req(void *opaque, int irq, int level) bool fastclk = ((unsigned)level >> 8u) & 1u; - level &= 0xff; + level &= UINT8_MAX; g_assert(level < OT_RSTMGR_RESET_COUNT); - OtRstMgrResetReq req = (OtRstMgrResetReq)level; - s->regs[R_RESET_INFO] = 1u << req; + const OtRstMgrConfig *config = &RSTMGR_CONFIG[s->version]; + uint32_t req = config->reset_request_codes[level]; + + if (!req) { + qemu_log_mask(LOG_UNIMP, "%s: %s: unsupported reset request %d\n", + __func__, s->ot_id, level); + return; + } - trace_ot_rstmgr_reset_req(s->ot_id, REQ_NAME(req), req, fastclk); + s->regs[R_RESET_INFO] = req; + + trace_ot_rstmgr_reset_req(s->ot_id, REQ_NAME(level), req, fastclk); qemu_bh_schedule(s->bus_reset_bh); } @@ -495,8 +566,7 @@ static void ot_rstmgr_regs_write(void *opaque, hwaddr addr, uint64_t val64, static Property ot_rstmgr_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtRstMgrState, ot_id), DEFINE_PROP_UINT32("fatal_reset", OtRstMgrState, fatal_reset, 0), - /* this property is only used to store initial reset reason state */ - DEFINE_PROP_BOOL("por", OtRstMgrState, por, true), + DEFINE_PROP_UINT8("version", OtRstMgrState, version, UINT8_MAX), DEFINE_PROP_END_OF_LIST(), }; @@ -534,11 +604,15 @@ static void ot_rstmgr_reset_enter(Object *obj, ResetType type) s->regs[R_RESET_REQ] = OT_MULTIBITBOOL4_FALSE; s->regs[R_ALERT_REGWEN] = R_ALERT_REGWEN_EN_MASK; s->regs[R_CPU_REGWEN] = R_CPU_REGWEN_EN_MASK; - for (unsigned ix = 0; ix < PARAM_NUM_SW_RESETS; ix++) { - s->regs[R_SW_RST_REGWEN_0 + ix] = SW_RST_REGWEN_EN_MASK; - } - for (unsigned ix = 0; ix < PARAM_NUM_SW_RESETS; ix++) { - s->regs[R_SW_RST_CTRL_N_0 + ix] = SW_RST_CTRL_VAL_MASK; + + const OtRstMgrConfig *config = &RSTMGR_CONFIG[s->version]; + + for (unsigned devix = 0; devix < OT_RSTMGR_SW_RESET_MAX; devix++) { + const OtRstMgrResettable *rst = &config->sw_resettable_devices[devix]; + if (rst->typename) { + s->regs[R_SW_RST_REGWEN_0 + devix] = SW_RST_REGWEN_EN_MASK; + s->regs[R_SW_RST_CTRL_N_0 + devix] = SW_RST_CTRL_VAL_MASK; + } } ibex_irq_lower(&s->soc_reset); @@ -574,6 +648,10 @@ static void ot_rstmgr_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); + g_assert(s->version < OT_RSTMGR_VERSION_COUNT); + + /* only used to store initial reset reason state; never reset it */ + s->por = true; } static void ot_rstmgr_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 5e7e658c10e54..5a0f0a405b592 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -413,7 +413,7 @@ ot_rom_ctrl_reset(const char *id, const char *phase) "%s: %s" ot_rstmgr_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_rstmgr_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_rstmgr_reset(const char *id, const char *phase) "%s: %s" -ot_rstmgr_reset_req(const char *id, const char *req, unsigned reqix, bool fastclk) "%s: %s(%u) @%u" +ot_rstmgr_reset_req(const char *id, const char *req, uint32_t reqix, bool fastclk) "%s: %s reset_info:0x%8x @%u" ot_rstmgr_sw_reset(const char *id, const char *devpath) "%s: SW reset %s" ot_rstmgr_sw_rst(const char *id, const char *path, bool reset) "%s: %s: reset:%u" diff --git a/include/hw/opentitan/ot_rstmgr.h b/include/hw/opentitan/ot_rstmgr.h index 15afe9a6c0c60..09711c996e9af 100644 --- a/include/hw/opentitan/ot_rstmgr.h +++ b/include/hw/opentitan/ot_rstmgr.h @@ -33,14 +33,23 @@ #define TYPE_OT_RSTMGR "ot-rstmgr" OBJECT_DECLARE_TYPE(OtRstMgrState, OtRstMgrClass, OT_RSTMGR) +/* Supported ResetManager versions */ typedef enum { + OT_RSTMGR_VERSION_EG_252, + OT_RSTMGR_VERSION_DJ_PRE, + OT_RSTMGR_VERSION_COUNT, +} OtRstMgrVersion; + +/* Some reset reasons may not exist on the current platform */ +typedef enum { + OT_RSTMGR_RESET_NONE, OT_RSTMGR_RESET_POR, OT_RSTMGR_RESET_LOW_POWER, OT_RSTMGR_RESET_SW, OT_RSTMGR_RESET_SYSCTRL, - /* mutually exclusive, depends on the actual machine */ - OT_RSTMGR_RESET_SOC_PROXY = OT_RSTMGR_RESET_SYSCTRL, + OT_RSTMGR_RESET_SOC_PROXY, OT_RSTMGR_RESET_AON_TIMER, + OT_RSTMGR_RESET_SENSOR, OT_RSTMGR_RESET_PWRMGR, OT_RSTMGR_RESET_ALERT_HANDLER, OT_RSTMGR_RESET_RV_DM, From 632567d440e719523130c399a2c8972fcd3232ca Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Apr 2025 17:13:25 +0200 Subject: [PATCH 022/175] [ot] hw/riscv: ot_darjeeling, ot_earlgrey: select RstMgr versions Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 3 +++ hw/riscv/ot_earlgrey.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 32f7cf2a14813..2a58711348674 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -1272,6 +1272,9 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_SIGNAL(OT_RSTMGR_SW_RST, 0, PWRMGR, OT_PWRMGR_SW_RST, 0) ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("version", OT_RSTMGR_VERSION_DJ_PRE) + ), }, [OT_DJ_SOC_DEV_CLKMGR] = { .type = TYPE_OT_CLKMGR, diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index eb43716d11ae1..7df1ada4b7752 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -785,6 +785,9 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(0, 23), OT_EG_SOC_GPIO_ALERT(1, 24) ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("version", OT_RSTMGR_VERSION_EG_252) + ), }, [OT_EG_SOC_DEV_CLKMGR] = { .type = TYPE_OT_CLKMGR, From 71ca5b861158bd1540f276b8df9038e7bc63e0e4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 30 Apr 2025 10:44:34 +0200 Subject: [PATCH 023/175] [ot] hw/opentitan: ot_vmapper: use a trace message to report ifetch control warning Some SRAM controller may never be associated with an ot_vmapper, leading to recurrent warnings. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_sram_ctrl.c | 5 +---- hw/opentitan/trace-events | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/ot_sram_ctrl.c b/hw/opentitan/ot_sram_ctrl.c index 30ed80c36d511..ac4e3c2f63096 100644 --- a/hw/opentitan/ot_sram_ctrl.c +++ b/hw/opentitan/ot_sram_ctrl.c @@ -370,10 +370,7 @@ static void ot_sram_ctrl_update_exec(OtSramCtrlState *s) if (!s->vmapper) { if (!ifetch) { - qemu_log_mask(LOG_UNIMP, - "%s: %s: Cannot disable execution, " - "VMapper not associated\n", - __func__, s->ot_id); + trace_ot_sram_ctrl_ifetch_warning(s->ot_id); } /* for now, vmapper is not a mandatory feature */ diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 5a0f0a405b592..3e7b123ec2e39 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -509,6 +509,7 @@ ot_spi_host_update_irq(const char *id, const char *channel, int level) "%s: irq # ot_sram_ctrl.c ot_sram_ctrl_expediate_init(const char *id, const char *from) "%s from %s" +ot_sram_ctrl_ifetch_warning(const char *id) "%s: cannot disable execution, ot_vmapper not associated" ot_sram_ctrl_initialize(const char *id, unsigned start, unsigned end, unsigned count, bool exp) "%s 0x%08x..0x%08x (0x%x) exp:%u" ot_sram_ctrl_initialization_complete(const char *id, const char *from) "%s: after %s" ot_sram_ctrl_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" From b193671db8ddf238b59060f21c0893e253d9f2d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 12 May 2025 14:40:05 +0200 Subject: [PATCH 024/175] [ot] hw/opentitan: ot_vmapper: fix a bug with the CentOS workaround use-after-free issue. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_vmapper.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/hw/opentitan/ot_vmapper.c b/hw/opentitan/ot_vmapper.c index 131ec9f0d2c61..7a7b6652e445c 100644 --- a/hw/opentitan/ot_vmapper.c +++ b/hw/opentitan/ot_vmapper.c @@ -229,7 +229,7 @@ static void g_tree_remove_all(GTree *tree) } #endif /* >= 2.68 < 2.70 */ -static void ot_vmapper_flush_tree(OtVMapperState *s, GTree *tree) +static void ot_vmapper_flush_tree(OtVMapperState *s, bool insn) { #if (GLIB_MINOR_VERSION < 68) #ifdef SHOW_RANGE_TREE @@ -239,20 +239,18 @@ static void ot_vmapper_flush_tree(OtVMapperState *s, GTree *tree) /* * GTreeNode is only available from 2.68 * destroy the whole tree and build a new one - * this is ulgy, but should not be used except on outdated hosts + * this is ugly, but should not be used except on outdated hosts */ - if (tree == s->itree) { + if (insn) { g_tree_destroy(s->itree); s->itree = ot_vmapper_create_tree(s); - } else if (tree == s->dtree) { + } else { g_tree_destroy(s->dtree); s->dtree = ot_vmapper_create_tree(s); - } else { - g_assert_not_reached(); } #else /* >= 2.68 */ (void)s; - g_tree_remove_all(tree); + g_tree_remove_all(insn ? s->itree : s->dtree); #endif /* >= 2.68 */ } @@ -771,13 +769,14 @@ static GList *ot_vmapper_fuse(OtVMapperState *s, GList *rglist) return rglist; } -static void ot_vmapper_rebuild_tree(OtVMapperState *s, GTree *tree, - GList *rglist) +static void ot_vmapper_rebuild_tree(OtVMapperState *s, bool insn, GList *rglist) { const GList *current = rglist; /* empty the tree AND free any contained OtRegionRange items */ - ot_vmapper_flush_tree(s, tree); + ot_vmapper_flush_tree(s, insn); + + GTree *tree = insn ? s->itree : s->dtree; /* configure the tree comparison for insertion */ s->insert_mode = true; @@ -805,7 +804,6 @@ static void ot_vmapper_rebuild_tree(OtVMapperState *s, GTree *tree, static void ot_vmapper_update(OtVMapperState *s, bool insn) { GList *rglist = NULL; - GTree *rgtree = insn ? s->itree : s->dtree; OtRegionRange *ranges = insn ? s->iranges : s->dranges; unsigned range_count = s->trans_count + (insn ? s->noexec_count : 0); @@ -867,7 +865,7 @@ static void ot_vmapper_update(OtVMapperState *s, bool insn) } /* rglist is freed on return */ - ot_vmapper_rebuild_tree(s, rgtree, rglist); + ot_vmapper_rebuild_tree(s, insn, rglist); s->lranges[insn] = NULL; From c6fa5b768263ada49e6a1f80be8780e8eb85fa24 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 13 May 2025 15:46:42 +0200 Subject: [PATCH 025/175] [ot] python/qemu: ot.pyot.executer: add more trace shortcuts Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index f33f1114ad047..dd7c1695e9c3c 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -67,8 +67,10 @@ class QEMUExecuter: 'A': 'in_asm', 'E': 'exec', 'G': 'guest_errors', + 'H': 'help', 'I': 'int', 'M': 'mmu', + 'R': 'cpu_reset', 'U': 'unimp', } """Shortcut names for QEMU log sources.""" From cf5b3ff8214d64999a50002ffd2a7402ca564e3a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 May 2025 15:26:56 +0200 Subject: [PATCH 026/175] [ot] hw/riscv: dtm: add DTM capture/update traces With multiple Debug Module, it is sometimes useful to track down where DTM requests are routed to. Signed-off-by: Emmanuel Blot --- hw/riscv/dtm.c | 3 +++ hw/riscv/trace-events | 2 ++ 2 files changed, 5 insertions(+) diff --git a/hw/riscv/dtm.c b/hw/riscv/dtm.c index 0b415ed40a284..d39b4512d1126 100644 --- a/hw/riscv/dtm.c +++ b/hw/riscv/dtm.c @@ -309,6 +309,7 @@ static void riscv_dtm_tap_dmi_capture(TapDataHandler *tdh) qemu_log_mask(LOG_UNIMP, "%s: Unknown DM address 0x%x\n", __func__, addr); } else { + trace_riscv_dtm_tap_dmi_capture(addr); value = dm->dc->read_value(dm->dev); } } @@ -360,9 +361,11 @@ static void riscv_dtm_tap_dmi_update(TapDataHandler *tdh) g_assert_not_reached(); return; case DMI_READ: + trace_riscv_dtm_tap_dmi_update(addr, "read"); s->dmistat = dm->dc->read_rq(dm->dev, addr - dm->base); break; case DMI_WRITE: + trace_riscv_dtm_tap_dmi_update(addr, "write"); value = (uint32_t)FIELD_EX64(tdh->value, DMI, DATA); s->dmistat = dm->dc->write_rq(dm->dev, addr - dm->base, value); break; diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index d978b31b3a5c5..5edd56f644a62 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -52,6 +52,8 @@ riscv_dtm_error(const char *func, int line, const char *msg) "%s:%d %s" riscv_dtm_info(const char *func, int line, const char *msg, uint32_t val) "%s:%d %s 0x%08x" riscv_dtm_register_dm(const char *cls, unsigned count, uint64_t first, uint64_t last, bool enabled, bool ok) "%s: #%u 0x%" PRIx64 "..0x%" PRIx64 ": enabled:%u tap:%u" riscv_dtm_set_next_dm(const char *fromcls, uint32_t fromaddr, const char *tocls, uint32_t toaddr) "%s @ 0x%x next_dm %s @ 0x%x" +riscv_dtm_tap_dmi_capture(uint32_t addr) "0x%x" +riscv_dtm_tap_dmi_update(uint32_t addr, const char *dir) "0x%x %s" riscv_dtm_vm_state_change(const char *name, unsigned state) "VM state: %s[%u]" # ibex_common.c From 3e05b083c099dfa1bc244ed482b6943d8ddd8361 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 May 2025 16:58:47 +0200 Subject: [PATCH 027/175] [ot] hw/riscv: dtm: replace legacy reset API with Resettable API Signed-off-by: Emmanuel Blot --- hw/riscv/dtm.c | 20 ++++++++++++++------ include/hw/riscv/dtm.h | 4 +++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/hw/riscv/dtm.c b/hw/riscv/dtm.c index d39b4512d1126..121e933465439 100644 --- a/hw/riscv/dtm.c +++ b/hw/riscv/dtm.c @@ -1,7 +1,7 @@ /* * QEMU Debug Transport Module * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * Author(s): * Emmanuel Blot * @@ -111,7 +111,6 @@ struct RISCVDTMState { unsigned abits; /* address bit count */ }; -static void riscv_dtm_reset(DeviceState *dev); static RISCVDebugModule *riscv_dtm_get_dm(RISCVDTMState *s, uint32_t addr); static void riscv_dtm_sort_dms(RISCVDTMState *s); static void riscv_dtm_activate_dms(RISCVDTMState *s); @@ -522,9 +521,14 @@ static Property riscv_dtm_properties[] = { DEFINE_PROP_END_OF_LIST(), }; -static void riscv_dtm_reset(DeviceState *dev) +static void riscv_dtm_reset_enter(Object *obj, ResetType type) { - RISCVDTMState *s = RISCV_DTM(dev); + RISCVDTMClass *c = RISCV_DTM_GET_CLASS(obj); + RISCVDTMState *s = RISCV_DTM(obj); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } s->address = 0; s->last_dm = NULL; @@ -556,11 +560,15 @@ static void riscv_dtm_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); (void)data; - device_class_set_legacy_reset(dc, &riscv_dtm_reset); dc->realize = &riscv_dtm_realize; device_class_set_props(dc, riscv_dtm_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); + ResettableClass *rc = RESETTABLE_CLASS(klass); + RISCVDTMClass *tc = RISCV_DTM_CLASS(klass); + resettable_class_set_parent_phases(rc, &riscv_dtm_reset_enter, NULL, NULL, + &tc->parent_phases); + RISCVDTMClass *dmc = RISCV_DTM_CLASS(klass); dmc->register_dm = &riscv_dtm_register_dm; dmc->enable_dm = &riscv_dtm_enable_dm; @@ -571,8 +579,8 @@ static const TypeInfo riscv_dtm_info = { .parent = TYPE_DEVICE, .instance_size = sizeof(RISCVDTMState), .instance_init = &riscv_dtm_init, - .class_init = &riscv_dtm_class_init, .class_size = sizeof(RISCVDTMClass), + .class_init = &riscv_dtm_class_init, }; static void riscv_dtm_register_types(void) diff --git a/include/hw/riscv/dtm.h b/include/hw/riscv/dtm.h index 154b4d3e6a6d4..35d53e5db356f 100644 --- a/include/hw/riscv/dtm.h +++ b/include/hw/riscv/dtm.h @@ -1,7 +1,7 @@ /* * QEMU RISC-V Debug Tranport Module * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * Author(s): * Emmanuel Blot * @@ -29,6 +29,7 @@ #include "qom/object.h" #include "exec/hwaddr.h" +#include "hw/resettable.h" #include "hw/riscv/debug.h" #define TYPE_RISCV_DTM "riscv.dtm" @@ -36,6 +37,7 @@ OBJECT_DECLARE_TYPE(RISCVDTMState, RISCVDTMClass, RISCV_DTM) struct RISCVDTMClass { DeviceClass parent_class; + ResettablePhases parent_phases; /* * Register a debug module on the Debug Transport Module. From b894ff2029723dd6da8cebcade6b1d11b8663252 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 7 May 2025 16:53:59 +0200 Subject: [PATCH 028/175] [ot] hw/riscv: dm: replace legacy reset API with Resettable API Address a misunderstanding in RISC-V debug spec 0.13.2: DMCONTROL.dmactive bit should not be automatically reverted back to 1 once is reset is completed: it seems it is up to the remote debugger to release the DM from the reset state, which is now implemented as such. Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 258 +++++++++++++++++++++++++++--------------- hw/riscv/trace-events | 2 +- include/hw/riscv/dm.h | 4 +- 3 files changed, 171 insertions(+), 93 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index 334c72da1387f..a47d616f695f0 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -1,7 +1,7 @@ /* * QEMU Debug Module Interface and Controller * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * Author(s): * Emmanuel Blot * @@ -241,7 +241,8 @@ REG32(FLAGS, RISCV_DM_FLAGS_OFFSET) static_assert((A_LAST - A_FIRST) < 64u, "too many registers"); -#define REG_BIT(_addr_) (1ull << ((_addr_) - A_FIRST)) +#define REG_BIT(_addr_) \ + (((_addr_) >= (uint32_t)A_FIRST) ? (1ull << ((_addr_) - A_FIRST)) : 0u) #define REG_BIT_DEF(_reg_) REG_BIT(A_##_reg_) #define DM_REG_COUNT (1u << (ADDRESS_BITS)) @@ -349,6 +350,11 @@ struct RISCVDMState { uint32_t *cpu_idx; /* array of hart_count CPU index */ }; +struct RISCVDMClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; +}; + typedef RISCVDMCmdErr CmdErr; /** Abstract command types */ @@ -384,8 +390,6 @@ struct RISCVDMDMReg { * Forward declarations */ -static void riscv_dm_reset(DeviceState *dev); - static bool riscv_dm_cond_autoexec(RISCVDMState *dm, bool prgbf, unsigned regix); static CmdErr riscv_dm_read_absdata(RISCVDMState *dm, unsigned woffset, @@ -558,6 +562,13 @@ riscv_dm_write_rq(RISCVDebugDeviceState *dev, uint32_t addr, uint32_t value) { RISCVDMState *dm = RISCV_DM(dev); + if (resettable_is_in_reset(OBJECT(dev))) { + if (addr != A_DMCONTROL) { + xtrace_riscv_dm_error(dm->soc, "write rejected: DM in reset"); + return RISCV_DEBUG_FAILED; + } + } + CmdErr ret; bool autoexec = false; @@ -1206,6 +1217,30 @@ static uint32_t riscv_dm_insn_illegal(void) static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) { + if (unlikely(!FIELD_EX32(value, DMCONTROL, DMACTIVE))) { + /* Debug Module reset */ + if (!resettable_is_in_reset(OBJECT(dm))) { + trace_riscv_dm_reset(dm->soc, "debugger requested DM reset"); + resettable_assert_reset(OBJECT(dm), RESET_TYPE_COLD); + } + + /* + * "the dmactive bit is the only bit which can be written to something + * other than its reset value)." + * if 0, reset the debug module itself. Do not exit from reset until + * DMCONTROL.dmactive is not set. + */ + dm->regs[A_DMCONTROL] &= ~R_DMCONTROL_DMACTIVE_MASK; + + /* do not update DMCONTROL while it is maintained in reset */ + return CMD_ERR_NONE; + } + + if (unlikely(resettable_is_in_reset(OBJECT(dm)))) { + trace_riscv_dm_reset(dm->soc, "debugger released DM reset"); + resettable_release_reset(OBJECT(dm), RESET_TYPE_COLD); + } + bool hasel = (bool)FIELD_EX32(value, DMCONTROL, HASEL); uint32_t hartsel = FIELD_EX32(value, DMCONTROL, HARTSELLO) | @@ -1228,7 +1263,8 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) } else { RISCVDMHartState *hart = &dm->harts[hartsel]; dm->hart = hart; - CPUState *cs = CPU(dm->hart->cpu); + g_assert(hart->cpu); + CPUState *cs = CPU(hart->cpu); if (value & R_DMCONTROL_HARTRESET_MASK) { if (!cs->held_in_reset) { @@ -1318,12 +1354,6 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) /* HARTSELHI never used, since HARTSELLO already encodes up to 1K harts */ dm->regs[A_DMCONTROL] = FIELD_DP32(value, DMCONTROL, HARTSELLO, hartsel); - if (unlikely(!FIELD_EX32(dm->regs[A_DMCONTROL], DMCONTROL, DMACTIVE))) { - /* Debug Module reset */ - trace_riscv_dm_reset(dm->soc, "debugger requested DM reset"); - riscv_dm_reset(DEVICE(dm)); - } - return CMD_ERR_NONE; } @@ -1464,6 +1494,11 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) uint64_t mask = 1u; for (; hix < hcount; hix++, mask <<= 1u) { RISCVDMHartState *hart = &dm->harts[hix]; +#ifdef TRACE_CPU_STATES + CPUState *cs; + g_assert(hart->cpu); + cs = CPU(hart->cpu); +#endif if (hart->resumed) { resumeack += 1; } @@ -1485,8 +1520,8 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) continue; } #ifdef TRACE_CPU_STATES - qemu_log("%s: %s became available %p: %u\n", __func__, dm->soc, - CPU(hart->cpu), CPU(hart->cpu)->cpu_index); + qemu_log("%s: %s: became available %p: %u\n", __func__, dm->soc, cs, + cs->cpu_index); #endif /* clear the unavailability flag and resume w/ "regular" states */ dm->unavailable_bm &= ~mask; @@ -1500,14 +1535,12 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) hix++; #ifdef TRACE_CPU_STATES - CPUState *cpu; - cpu = CPU(hart->cpu); RISCVDMStateCache current = { .cpu = { - .ix = cpu->cpu_index, - .halted = cpu->halted, - .stopped = cpu->stopped, - .running = cpu->running, + .ix = cs->cpu_index, + .halted = cs->halted, + .stopped = cs->stopped, + .running = cs->running, }, .dm = { .halted = halted, @@ -1522,8 +1555,8 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) if (memcmp(¤t, &hart->dbgcache, sizeof(RISCVDMStateCache))) { qemu_log("%s: %s[%u] [H:%u S:%u R:%u] " "DM [h:%u r:%u u:%u x:%u a:%u z:%u]\n", - __func__, dm->soc, hart->hartid, cpu->halted, cpu->stopped, - cpu->running, halted, running, unavail, nonexistent, + __func__, dm->soc, hart->hartid, cs->halted, cs->stopped, + cs->running, halted, running, unavail, nonexistent, resumeack, havereset); hart->dbgcache = current; } @@ -1548,10 +1581,13 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) FIELD_DP32(val, DMSTATUS, ALLHAVERESET, (havereset == hcount ? 1 : 0)); if (val != dm->regs[A_DMSTATUS]) { - CPUState *cpu = CPU(dm->harts[0].cpu); - trace_riscv_dm_dmstatus_read(dm->soc, val, halted, cpu->halted, running, - cpu->running, resumeack, cpu->stopped, - (uint32_t)dm->harts[0].cpu->env.pc); + /* @todo update for multiple harts */ + RISCVCPU *cpu = dm->harts[0].cpu; + g_assert(cpu); + CPUState *cs = CPU(cpu); + trace_riscv_dm_dmstatus_read(dm->soc, val, halted, cs->halted, running, + cs->running, resumeack, cs->stopped, + (uint32_t)cpu->env.pc); } *value = dm->regs[A_DMSTATUS] = val; @@ -2416,13 +2452,13 @@ static void riscv_dm_resume_hart(RISCVDMState *dm, unsigned hartsel) static int riscv_dm_discover_cpus(RISCVDMState *dm) { unsigned hartix = 0; - CPUState *cpu; + CPUState *cs; /* NOLINTNEXTLINE */ - CPU_FOREACH(cpu) { + CPU_FOREACH(cs) { /* skips CPUs/harts that are not associated to this DM */ bool skip = true; for (unsigned ix = 0; ix < dm->hart_count; ix++) { - if (cpu->cpu_index == dm->cpu_idx[ix]) { + if (cs->cpu_index == dm->cpu_idx[ix]) { skip = false; break; } @@ -2434,13 +2470,19 @@ static int riscv_dm_discover_cpus(RISCVDMState *dm) error_setg(&error_fatal, "Incoherent hart count"); } RISCVDMHartState *hart = &dm->harts[hartix]; - hart->cpu = RISCV_CPU(cpu); + RISCVCPU *cpu = RISCV_CPU(cs); + if (!hart->cpu) { + hart->cpu = cpu; + } else { + /* associated CPU should be invariant across resets */ + g_assert(hart->cpu == cpu); + } hart->hartid = hart->cpu->env.mhartid; - hart->unlock_reset = !cpu->held_in_reset; + hart->unlock_reset = !cs->held_in_reset; if (!dm->as) { /* address space is unknown till first hart is realized */ - dm->as = cpu->as; - } else if (dm->as != cpu->as) { + dm->as = cs->as; + } else if (dm->as != cs->as) { /* for now, all harts should share the same address space */ error_setg(&error_fatal, "Incoherent address spaces"); } @@ -2450,10 +2492,93 @@ static int riscv_dm_discover_cpus(RISCVDMState *dm) return hartix ? 0 : -1; } -static void riscv_dm_internal_reset(RISCVDMState *dm) +static Property riscv_dm_properties[] = { + DEFINE_PROP_LINK("dtm", RISCVDMState, dtm, TYPE_RISCV_DTM, RISCVDTMState *), + DEFINE_PROP_ARRAY("hart", RISCVDMState, hart_count, cpu_idx, + qdev_prop_uint32, uint32_t), + DEFINE_PROP_UINT32("dmi_addr", RISCVDMState, cfg.dmi_addr, 0), + DEFINE_PROP_UINT32("dmi_next", RISCVDMState, cfg.dmi_next, 0), + DEFINE_PROP_UINT32("nscratch", RISCVDMState, cfg.nscratch, 1u), + DEFINE_PROP_UINT32("progbuf_count", RISCVDMState, cfg.progbuf_count, 0), + DEFINE_PROP_UINT32("data_count", RISCVDMState, cfg.data_count, 2u), + DEFINE_PROP_UINT32("abstractcmd_count", RISCVDMState, cfg.abstractcmd_count, + 0), + DEFINE_PROP_UINT64("dm_phyaddr", RISCVDMState, cfg.dm_phyaddr, 0), + DEFINE_PROP_UINT64("rom_phyaddr", RISCVDMState, cfg.rom_phyaddr, 0), + DEFINE_PROP_UINT64("whereto_phyaddr", RISCVDMState, cfg.whereto_phyaddr, 0), + DEFINE_PROP_UINT64("data_phyaddr", RISCVDMState, cfg.data_phyaddr, 0), + DEFINE_PROP_UINT64("progbuf_phyaddr", RISCVDMState, cfg.progbuf_phyaddr, 0), + DEFINE_PROP_UINT16("resume_offset", RISCVDMState, cfg.resume_offset, 0), + DEFINE_PROP_BOOL("sysbus_access", RISCVDMState, cfg.sysbus_access, true), + /* beware that OpenOCD (RISC-V 2024/04) assumes this is always supported */ + DEFINE_PROP_BOOL("abstractauto", RISCVDMState, cfg.abstractauto, true), + DEFINE_PROP_UINT64("mta_dm", RISCVDMState, cfg.mta_dm, RISCVDM_DEFAULT_MTA), + DEFINE_PROP_UINT64("mta_sba", RISCVDMState, cfg.mta_sba, + RISCVDM_DEFAULT_MTA), + DEFINE_PROP_BOOL("enable", RISCVDMState, cfg.enable, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void riscv_dm_reset_enter(Object *obj, ResetType type) { + RISCVDMClass *c = RISCV_DM_GET_CLASS(obj); + RISCVDMState *dm = RISCV_DM(obj); + + trace_riscv_dm_reset(dm->soc, "enter"); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } + + g_assert(dm->dtm != NULL); + RISCVDTMClass *dtmc = RISCV_DTM_GET_CLASS(OBJECT(dm->dtm)); + dm->dtm_ok = + (*dtmc->register_dm)(DEVICE(dm->dtm), RISCV_DEBUG_DEVICE(obj), + dm->cfg.dmi_addr, DM_REG_COUNT, dm->cfg.enable); + + for (unsigned ix = 0; ix < DM_REG_COUNT; ix++) { + if (ix == A_DMCONTROL) { + dm->regs[ix] = RISCVDM_DMS[ix].value & ~R_DMCONTROL_DMACTIVE_MASK; + continue; + } + if (ix == A_NEXTDM) { + continue; + } + dm->regs[ix] = RISCVDM_DMS[ix].value; + } + + /* Hart statuses are updated on reset_exit */ + dm->nonexistent_bm = 0; + dm->unavailable_bm = 0; dm->address = 0; dm->to_go_bm = 0; + for (unsigned ix = 0; ix < dm->hart_count; ix++) { + RISCVDMHartState *hart = &dm->harts[ix]; + /* + * preserve CPU reference, as DM may be queried while it is maintained + * for current status. Hart association never changes anyway. + */ + RISCVCPU *cpu = hart->cpu; + memset(hart, 0, sizeof(RISCVDMHartState)); + hart->cpu = cpu; + } +} + +static void riscv_dm_reset_exit(Object *obj, ResetType type) +{ + RISCVDMClass *c = RISCV_DM_GET_CLASS(obj); + RISCVDMState *dm = RISCV_DM(obj); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + trace_riscv_dm_reset(dm->soc, "exit"); + + /* generate hart association */ + if (riscv_dm_discover_cpus(dm)) { + error_setg(&error_fatal, "Cannot identify harts"); + } riscv_dm_set_busy(dm, false); @@ -2490,7 +2615,7 @@ static void riscv_dm_internal_reset(RISCVDMState *dm) if (cs->halted) { if (cs->held_in_reset) { dm->unavailable_bm |= 1u << ix; - trace_riscv_dm_unavailable(dm->soc, true); + trace_riscv_dm_unavailable(dm->soc, cs->cpu_index); /* a hart cannot be halted and unavailable at once */ hart->halted = false; } else { @@ -2524,9 +2649,6 @@ static void riscv_dm_internal_reset(RISCVDMState *dm) /* TODO: should we clear progbug, absdata, ...? */ - /* set dmactive once ready */ - dm->regs[A_DMCONTROL] |= R_DMCONTROL_DMACTIVE_MASK; - /* consider all harts for this DM share the same capabilities */ CPURISCVState *env = &dm->harts[0u].cpu->env; @@ -2542,59 +2664,9 @@ static void riscv_dm_internal_reset(RISCVDMState *dm) } dm->regs[A_SBCS] = value; -} -static Property riscv_dm_properties[] = { - DEFINE_PROP_LINK("dtm", RISCVDMState, dtm, TYPE_RISCV_DTM, RISCVDTMState *), - DEFINE_PROP_ARRAY("hart", RISCVDMState, hart_count, cpu_idx, - qdev_prop_uint32, uint32_t), - DEFINE_PROP_UINT32("dmi_addr", RISCVDMState, cfg.dmi_addr, 0), - DEFINE_PROP_UINT32("dmi_next", RISCVDMState, cfg.dmi_next, 0), - DEFINE_PROP_UINT32("nscratch", RISCVDMState, cfg.nscratch, 1u), - DEFINE_PROP_UINT32("progbuf_count", RISCVDMState, cfg.progbuf_count, 0), - DEFINE_PROP_UINT32("data_count", RISCVDMState, cfg.data_count, 2u), - DEFINE_PROP_UINT32("abstractcmd_count", RISCVDMState, cfg.abstractcmd_count, - 0), - DEFINE_PROP_UINT64("dm_phyaddr", RISCVDMState, cfg.dm_phyaddr, 0), - DEFINE_PROP_UINT64("rom_phyaddr", RISCVDMState, cfg.rom_phyaddr, 0), - DEFINE_PROP_UINT64("whereto_phyaddr", RISCVDMState, cfg.whereto_phyaddr, 0), - DEFINE_PROP_UINT64("data_phyaddr", RISCVDMState, cfg.data_phyaddr, 0), - DEFINE_PROP_UINT64("progbuf_phyaddr", RISCVDMState, cfg.progbuf_phyaddr, 0), - DEFINE_PROP_UINT16("resume_offset", RISCVDMState, cfg.resume_offset, 0), - DEFINE_PROP_BOOL("sysbus_access", RISCVDMState, cfg.sysbus_access, true), - /* beware that OpenOCD (RISC-V 2024/04) assumes this is always supported */ - DEFINE_PROP_BOOL("abstractauto", RISCVDMState, cfg.abstractauto, true), - DEFINE_PROP_UINT64("mta_dm", RISCVDMState, cfg.mta_dm, RISCVDM_DEFAULT_MTA), - DEFINE_PROP_UINT64("mta_sba", RISCVDMState, cfg.mta_sba, - RISCVDM_DEFAULT_MTA), - DEFINE_PROP_BOOL("enable", RISCVDMState, cfg.enable, true), - DEFINE_PROP_END_OF_LIST(), -}; - -static void riscv_dm_reset(DeviceState *dev) -{ - RISCVDMState *dm = RISCV_DM(dev); - - g_assert(dm->dtm != NULL); - RISCVDTMClass *dtmc = RISCV_DTM_GET_CLASS(OBJECT(dm->dtm)); - dm->dtm_ok = - (*dtmc->register_dm)(DEVICE(dm->dtm), RISCV_DEBUG_DEVICE(dev), - dm->cfg.dmi_addr, DM_REG_COUNT, dm->cfg.enable); - - for (unsigned ix = 0; ix < DM_REG_COUNT; ix++) { - if (ix != A_NEXTDM) { - dm->regs[ix] = RISCVDM_DMS[ix].value; - } - } - - if (riscv_dm_discover_cpus(dm)) { - error_setg(&error_fatal, "Cannot identify harts"); - } - - dm->nonexistent_bm = 0; - dm->unavailable_bm = 0; - - riscv_dm_internal_reset(dm); + /* set dmactive once ready */ + dm->regs[A_DMCONTROL] |= R_DMCONTROL_DMACTIVE_MASK; } static void riscv_dm_realize(DeviceState *dev, Error **errp) @@ -2633,11 +2705,16 @@ static void riscv_dm_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); (void)data; - device_class_set_legacy_reset(dc, &riscv_dm_reset); dc->realize = &riscv_dm_realize; device_class_set_props(dc, riscv_dm_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); + ResettableClass *rc = RESETTABLE_CLASS(klass); + RISCVDMClass *cc = RISCV_DM_CLASS(klass); + resettable_class_set_parent_phases(rc, &riscv_dm_reset_enter, NULL, + &riscv_dm_reset_exit, + &cc->parent_phases); + RISCVDebugDeviceClass *dmc = RISCV_DEBUG_DEVICE_CLASS(klass); dmc->write_rq = &riscv_dm_write_rq; dmc->read_rq = &riscv_dm_read_rq; @@ -2657,6 +2734,7 @@ static const TypeInfo riscv_dm_info = { .name = TYPE_RISCV_DM, .parent = TYPE_RISCV_DEBUG_DEVICE, .instance_size = sizeof(RISCVDMState), + .class_size = sizeof(RISCVDMClass), .class_init = &riscv_dm_class_init, }; diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 5edd56f644a62..1539da7e44b86 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -42,7 +42,7 @@ riscv_dm_sbdata_read(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0 riscv_dm_sbdata_write(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" riscv_dm_sysbus_data_read(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] <- %08" PRIx64 ": res %u" riscv_dm_sysbus_data_write(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] -> %08" PRIx64 ": res %u" -riscv_dm_unavailable(const char *soc, bool unavail) "%s: %u" +riscv_dm_unavailable(const char *soc, int cpuid) "%s (#%d)" # dtm.c diff --git a/include/hw/riscv/dm.h b/include/hw/riscv/dm.h index 9c1c3ba44c78d..845697fff5113 100644 --- a/include/hw/riscv/dm.h +++ b/include/hw/riscv/dm.h @@ -1,7 +1,7 @@ /* * QEMU RISC-V Debug Module * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * Author(s): * Emmanuel Blot * @@ -31,7 +31,7 @@ #include "exec/memattrs.h" #define TYPE_RISCV_DM "riscv-dm" -OBJECT_DECLARE_SIMPLE_TYPE(RISCVDMState, RISCV_DM) +OBJECT_DECLARE_TYPE(RISCVDMState, RISCVDMClass, RISCV_DM) #define RISCV_DM_ACK_LINES TYPE_RISCV_DM ".ack" From 142d8cc899405af3352ec3fc6225b5a42248e644 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 13 May 2025 15:45:25 +0200 Subject: [PATCH 029/175] [ot] hw/risv: ibex_common: new API retrieve function name from its address for debug/trace purposes Signed-off-by: Emmanuel Blot --- hw/riscv/ibex_common.c | 17 ++++++++++++++++- include/hw/riscv/ibex_common.h | 14 +++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c index 4bc5e8d769854..9c7d86f944a21 100644 --- a/hw/riscv/ibex_common.c +++ b/hw/riscv/ibex_common.c @@ -1,7 +1,7 @@ /* * QEMU RISC-V Helpers for LowRISC Ibex Demo System & OpenTitan EarlGrey * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * * Author(s): * Emmanuel Blot @@ -21,6 +21,9 @@ */ #include "qemu/osdep.h" +#ifdef CONFIG_POSIX +#include +#endif #include "qemu/error-report.h" #include "qemu/log.h" #include "qapi/error.h" @@ -730,6 +733,18 @@ uint32_t ibex_load_kernel(CPUState *cpu) return (uint32_t)kernel_entry; } +const char *ibex_common_get_func_name_by_addr(void *fn) +{ +#ifdef CONFIG_POSIX + Dl_info info; + if (dladdr(fn, &info)) { + return info.dli_sname; + } +#endif + + return NULL; +} + uint32_t ibex_get_current_pc(void) { CPUState *cs = current_cpu; diff --git a/include/hw/riscv/ibex_common.h b/include/hw/riscv/ibex_common.h index 6417c757384e7..08e5b71e9bb4b 100644 --- a/include/hw/riscv/ibex_common.h +++ b/include/hw/riscv/ibex_common.h @@ -1,7 +1,7 @@ /* * QEMU RISC-V Helpers for LowRISC Ibex Demo System & OpenTitan EarlGrey * - * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2022-2025 Rivos, Inc. * * Author(s): * Emmanuel Blot @@ -559,6 +559,18 @@ enum { */ void ibex_log_vcpu_registers(uint64_t regbm); +/* ------------------------------------------------------------------------ */ +/* Miscellaneous utilities */ +/* ------------------------------------------------------------------------ */ + +/* + * Find a host function name by its address. + * + * @fn the address of the function + * @return the function name, or NULL if not found or not supported + */ +const char *ibex_common_get_func_name_by_addr(void *fn); + /* ------------------------------------------------------------------------ */ /* CharDev utilities */ /* ------------------------------------------------------------------------ */ From 4c0c47a64dcebc01f11714aef31b9026b11f02f5 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 May 2025 15:10:33 +0200 Subject: [PATCH 030/175] [ot] hw/opentitan: ot_ibex_wrapper: improve execution update trace Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 3 +++ hw/opentitan/trace-events | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index 0dd9729961275..b57615f8a53ae 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -862,8 +862,11 @@ static void ot_ibex_wrapper_update_exec(OtIbexWrapperState *s) ((s->cpu_en_bm & OT_IBEX_CPU_EN_MASK) == OT_IBEX_CPU_EN_MASK) && !s->esc_rx; trace_ot_ibex_wrapper_update_exec(s->ot_id ?: "", s->cpu_en_bm, s->esc_rx, + s->cpu->halted, s->cpu->held_in_reset, enable); + g_assert(s->cpu); + if (enable) { s->cpu->halted = 0; if (s->cpu->held_in_reset) { diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 3e7b123ec2e39..d1804135da75a 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -256,7 +256,7 @@ ot_ibex_wrapper_map(const char *id, unsigned slot, uint32_t src, uint32_t dst, u ot_ibex_wrapper_request_entropy(const char *id, bool again) "%s: %u" ot_ibex_wrapper_reset(const char *id, const char *phase) "%s: %s" ot_ibex_wrapper_unmap(const char *id, unsigned slot) "%s: region %u" -ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool cpu_en) "%s: bm:0x%x esc:%u -> CPU enable %u" +ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halted, bool in_reset, bool cpu_en) "%s: bm:0x%x esc:%u halted:%u in_reset:%u -> CPU enable %u" # ot_kmac.c From f72a6ac959e656b23dd9d973a06896690bc9b79d Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 14 May 2025 16:22:28 +0200 Subject: [PATCH 031/175] [ot] hw/opentitan: ot_ibex_wrapper: when lc-ignore is enforced, ignore LC exec de-activation. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index b57615f8a53ae..ff5254dcb8809 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -887,6 +887,11 @@ static void ot_ibex_wrapper_cpu_enable_recv(void *opaque, int n, int level) g_assert((unsigned)n < OT_IBEX_CPU_EN_COUNT); + bool override = s->lc_ignore && (n == OT_IBEX_LC_CTRL_CPU_EN) && !level; + if (override) { + level = 1; + } + if (level) { s->cpu_en_bm |= 1u << (unsigned)n; } else { @@ -897,7 +902,10 @@ static void ot_ibex_wrapper_cpu_enable_recv(void *opaque, int n, int level) * "Fetch is only enabled when local fetch enable, lifecycle CPU enable and * power manager CPU enable are all enabled." */ - trace_ot_ibex_wrapper_cpu_enable(s->ot_id ?: "", n ? "PWR" : "LC", + trace_ot_ibex_wrapper_cpu_enable(s->ot_id ?: "", + n == OT_IBEX_LC_CTRL_CPU_EN ? + (override ? "LC-override" : "LC") : + "PWR", (bool)level); ot_ibex_wrapper_update_exec(s); From 928e20bb6ef58025a39172c12aaf9c9dfb1a7a0f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 2 Jun 2025 18:38:59 +0200 Subject: [PATCH 032/175] [ot] python/qemu: ot.dm.dm: improve DM status reporting Signed-off-by: Emmanuel Blot --- python/qemu/ot/dm/dm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/qemu/ot/dm/dm.py b/python/qemu/ot/dm/dm.py index acb317dc68f38..d0d5413f9d460 100644 --- a/python/qemu/ot/dm/dm.py +++ b/python/qemu/ot/dm/dm.py @@ -255,7 +255,8 @@ def halt(self, hart: int = 0) -> None: break sleep(0.001) else: - self._log.error('Status %s', status) + status = ', '.join((f'{k}: {v}' for k, v in status.items())) + self._log.error('Status: %s', status) raise TimeoutError(f'Cannot halt hart {self._hart}') def resume(self, hart: int = 0) -> None: From 5928cb449c75f168f22d5410932952db8ab8af8a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 11:48:21 +0200 Subject: [PATCH 033/175] [ot] hw/riscv: dm: fix long-lasting typo Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index a47d616f695f0..acf35ef564bc1 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -932,14 +932,14 @@ static void riscv_dm_set_cs(RISCVDMState *dm, bool enable) trace_riscv_dm_cs(dm->soc, enable); } -static uint32_t risc_dmi_get_debug_cause(RISCVCPU *cpu) +static uint32_t riscv_dmi_get_debug_cause(RISCVCPU *cpu) { return FIELD_EX32(cpu->env.dcsr, DCSR, CAUSE); } -static const char *risc_dmi_get_debug_cause_name(RISCVCPU *cpu) +static const char *riscv_dmi_get_debug_cause_name(RISCVCPU *cpu) { - return DCSR_CAUSE_NAMES[risc_dmi_get_debug_cause(cpu)]; + return DCSR_CAUSE_NAMES[riscv_dmi_get_debug_cause(cpu)]; } static void riscv_dm_acknowledge(void *opaque, int irq, int level) @@ -971,7 +971,7 @@ static void riscv_dm_acknowledge(void *opaque, int irq, int level) riscv_dm_set_busy(dm, false); trace_riscv_dm_halted(dm->soc, hart - &dm->harts[0], hart->cpu->env.dpc, - risc_dmi_get_debug_cause_name(hart->cpu)); + riscv_dmi_get_debug_cause_name(hart->cpu)); } break; case ACK_GOING: @@ -2443,7 +2443,7 @@ static void riscv_dm_resume_hart(RISCVDMState *dm, unsigned hartsel) cpu_exit(cs); cpu_reset_interrupt(cs, CPU_INTERRUPT_DEBUG); - const char *cause = risc_dmi_get_debug_cause_name(cpu); + const char *cause = riscv_dmi_get_debug_cause_name(cpu); trace_riscv_dm_resume_hart(dm->soc, sstep, cause); riscv_dm_ensure_running(dm); @@ -2633,18 +2633,18 @@ static void riscv_dm_reset_exit(Object *obj, ResetType type) */ if (is_running) { /* called from vm_state change, running */ - if (risc_dmi_get_debug_cause(cpu) == DCSR_CAUSE_RESETHALTREQ) { + if (riscv_dmi_get_debug_cause(cpu) == DCSR_CAUSE_RESETHALTREQ) { env->dcsr = FIELD_DP32(env->dcsr, DCSR, CAUSE, DCSR_CAUSE_NONE); } } else { /* called from DMI reset */ - if (risc_dmi_get_debug_cause(cpu) == DCSR_CAUSE_NONE) { + if (riscv_dmi_get_debug_cause(cpu) == DCSR_CAUSE_NONE) { env->dcsr = FIELD_DP32(env->dcsr, DCSR, CAUSE, DCSR_CAUSE_RESETHALTREQ); } } - xtrace_riscv_dm_info(dm->soc, "cause", risc_dmi_get_debug_cause(cpu)); + xtrace_riscv_dm_info(dm->soc, "cause", riscv_dmi_get_debug_cause(cpu)); } /* TODO: should we clear progbug, absdata, ...? */ From 9a5122b1b279cd86c81e81d0111ff19601ce5bd3 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 11:49:55 +0200 Subject: [PATCH 034/175] [ot] hw/riscv: dm: fix and improve log and trace messages Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 37 ++++++++++++++++++------------------- hw/riscv/trace-events | 4 ++-- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index acf35ef564bc1..811261b8531f9 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -963,8 +963,8 @@ static void riscv_dm_acknowledge(void *opaque, int irq, int level) hart->halted = true; uint64_t hbm = 1u << hartnum; if (dm->unavailable_bm & hbm) { - qemu_log("%s: ERROR, an unavailable hart should not be halted", - __func__); + qemu_log("%s: %s: an unavailable hart should not be halted\n", + __func__, dm->soc); /* ensure hart can only be a single state */ dm->unavailable_bm &= ~hbm; } @@ -1014,8 +1014,8 @@ static void riscv_dm_acknowledge(void *opaque, int irq, int level) hart->resumed = true; uint64_t hbm = 1u << hartnum; if (dm->unavailable_bm & hbm) { - qemu_log("%s: ERROR, an unavailable hart should not be resumed", - __func__); + qemu_log("%s: %s: an unavailable hart should not be resumed\n", + __func__, dm->soc); /* ensure hart can only be a single state */ dm->unavailable_bm &= ~hbm; } @@ -1252,7 +1252,7 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) dm->hart = NULL; - /* hart array not supported */ + /* hart array not supported, ignore any request that uses it */ if (!hasel) { uint64_t hbit = 1u << hartsel; if (!(hartsel < dm->hart_count)) { @@ -1268,8 +1268,8 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) if (value & R_DMCONTROL_HARTRESET_MASK) { if (!cs->held_in_reset) { - trace_riscv_dm_hart_reset("assert", dm->soc, cs->cpu_index, - dm->hart->hartid); + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "assert"); if (hart->unlock_reset) { /* * if hart is started in active reset, prevent from @@ -1293,9 +1293,8 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) * initial out-of-reset sequence. Not sure how real HW * manages this corner case. */ - trace_riscv_dm_hart_reset("release", dm->soc, - cs->cpu_index, - dm->hart->hartid); + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "release"); resettable_release_reset(OBJECT(cs), RESET_TYPE_COLD); } } @@ -1307,8 +1306,8 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) dm->unavailable_bm &= ~hbit; hart->have_reset = true; hart->halted = false; - trace_riscv_dm_hart_reset("exited", dm->soc, cs->cpu_index, - dm->hart->hartid); + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "exited"); } } } @@ -1553,11 +1552,11 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) }; /* NOLINTNEXTLINE */ if (memcmp(¤t, &hart->dbgcache, sizeof(RISCVDMStateCache))) { - qemu_log("%s: %s[%u] [H:%u S:%u R:%u] " + qemu_log("%s: %s [%u/%u] [H:%u S:%u R:%u] " "DM [h:%u r:%u u:%u x:%u a:%u z:%u]\n", - __func__, dm->soc, hart->hartid, cs->halted, cs->stopped, - cs->running, halted, running, unavail, nonexistent, - resumeack, havereset); + __func__, dm->soc, hart->hartid, hcount, cs->halted, + cs->stopped, cs->running, halted, running, unavail, + nonexistent, resumeack, havereset); hart->dbgcache = current; } #endif /* #ifdef TRACE_CPU_STATES */ @@ -1585,9 +1584,9 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) RISCVCPU *cpu = dm->harts[0].cpu; g_assert(cpu); CPUState *cs = CPU(cpu); - trace_riscv_dm_dmstatus_read(dm->soc, val, halted, cs->halted, running, - cs->running, resumeack, cs->stopped, - (uint32_t)cpu->env.pc); + trace_riscv_dm_dmstatus_read(dm->soc, val, unavail, halted, cs->halted, + running, cs->running, resumeack, + cs->stopped, (uint32_t)cpu->env.pc); } *value = dm->regs[A_DMSTATUS] = val; diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 1539da7e44b86..7ad83ec02391e 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -25,10 +25,10 @@ riscv_dm_access_register(const char *soc, const char *msg, const char *regname, riscv_dm_busy(const char *soc, bool busy) "%s: busy: %u" riscv_dm_change_hart(const char *soc, const char *msg, unsigned hart, bool cpuhalted, bool cpurunning, bool cpustopped, bool resack) "%s: [%s] hart#%u h:%u r:%u s:%u A:%u" riscv_dm_cs(const char *soc, bool enable) "%s: debug_cs: %u" -riscv_dm_dmstatus_read(const char *soc, uint32_t val, bool halted, bool cpuhalted, bool running, bool cpurunning, bool resack, bool cpustopped, uint32_t pc) "%s: 0x%08x H:%u(%u) R:%u(%u) A:%u S:%u @ %08x" +riscv_dm_dmstatus_read(const char *soc, uint32_t val, bool unavail, bool halted, bool cpuhalted, bool running, bool cpurunning, bool resack, bool cpustopped, uint32_t pc) "%s: 0x%08x U:%u H:%u(%u) R:%u(%u) A:%u S:%u @ %08x" riscv_dm_error(const char *soc, const char *func, int line, const char *msg) "%s: %s:%d %s" riscv_dm_halted(const char *soc, unsigned hart, uint64_t pc, const char *cause) "%s hart #%u halted @ 0x%08" PRIx64 " as %s" -riscv_dm_hart_reset(const char *msg, const char *soc, unsigned cpuindex, unsigned hartid) "%s: %s cpu:%u, hartid:%u" +riscv_dm_hart_reset(const char *soc, unsigned cpuid, unsigned hartid, const char *msg) "%s {%u}: hartid:%u: %s" riscv_dm_hart_state(const char *soc, unsigned hartsel, const char *msg) "%s: hart #%u %s" riscv_dm_info(const char *soc, const char *func, int line, const char *msg, uint32_t val) "%s: %s:%d %s 0x%08x" riscv_dm_progbuf(const char *soc, const char *op, unsigned woffset, uint64_t value, uint32_t res) "%s: %s: @ %u = 0x%08" PRIx64 ": res %u" From cbeb5ff4dd0c9911bea2785df0a64b994371c516 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 11:50:53 +0200 Subject: [PATCH 035/175] [ot] hw/riscv: dm: do not blindly access debugger requests A remote debugger can request halt or resume actions on an unavailable hart. Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 31 +++++++++++++++++++++++++------ hw/riscv/trace-events | 3 ++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index 811261b8531f9..c471967cd26f0 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -1321,13 +1321,23 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) } } + + RISCVDMCmdErr ret = CMD_ERR_NONE; + if (unlikely(value & R_DMCONTROL_NDMRESET_MASK)) { /* full system reset (but the Debug Module) */ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); } else if (dm->hart) { - if (hartsel < dm->hart_count) { + if (!hasel && (hartsel < dm->hart_count)) { + uint64_t hartbit = 1u << hartsel; if (value & R_DMCONTROL_HALTREQ_MASK) { - riscv_dm_halt_hart(dm, hartsel); + if (dm->unavailable_bm & hartbit) { + trace_riscv_dm_unavailable_hart_control(dm->soc, hartsel, + "halt"); + ret = CMD_ERR_HALT_RESUME; + } else { + riscv_dm_halt_hart(dm, hartsel); + } } else { if (dm->hart->halted) { /* @@ -1335,9 +1345,18 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) * specs */ if (value & R_DMCONTROL_RESUMEREQ_MASK) { - /* it also clears the resume ack bit for those harts. */ - dm->hart->resumed = false; - riscv_dm_resume_hart(dm, hartsel); + if (dm->unavailable_bm & hartbit) { + trace_riscv_dm_unavailable_hart_control(dm->soc, + hartsel, + "resume"); + ret = CMD_ERR_HALT_RESUME; + } else { + /* + * it also clears the resume ack bit for those harts + */ + dm->hart->resumed = false; + riscv_dm_resume_hart(dm, hartsel); + } } } } @@ -1353,7 +1372,7 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) /* HARTSELHI never used, since HARTSELLO already encodes up to 1K harts */ dm->regs[A_DMCONTROL] = FIELD_DP32(value, DMCONTROL, HARTSELLO, hartsel); - return CMD_ERR_NONE; + return ret; } static CmdErr riscv_dm_exec_command(RISCVDMState *dm, uint32_t value) diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 7ad83ec02391e..985b132973e03 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -42,7 +42,8 @@ riscv_dm_sbdata_read(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0 riscv_dm_sbdata_write(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" riscv_dm_sysbus_data_read(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] <- %08" PRIx64 ": res %u" riscv_dm_sysbus_data_write(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] -> %08" PRIx64 ": res %u" -riscv_dm_unavailable(const char *soc, int cpuid) "%s (#%d)" +riscv_dm_unavailable(const char *soc, int cpuid) "%s {%d}" +riscv_dm_unavailable_hart_control(const char *soc, unsigned hartsel, const char *action) "%s: #%u cannot %s as unavailable" # dtm.c From 8db0af323d4257d6b7d5c44e1995963ac1888423 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 12:23:03 +0200 Subject: [PATCH 036/175] [ot] scripts/opentitan: dtm: add an idle option It may be needed to wait for the SoC to initialize before starting DTM transactions. Signed-off-by: Emmanuel Blot --- docs/opentitan/dtm.md | 21 +++++++++++++++++---- scripts/opentitan/dtm.py | 12 ++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/opentitan/dtm.md b/docs/opentitan/dtm.md index fc3222e04a372..10eab107b0cec 100644 --- a/docs/opentitan/dtm.md +++ b/docs/opentitan/dtm.md @@ -6,8 +6,9 @@ Module to access the Ibex core. ## Usage ````text -usage: dtm.py [-h] [-S SOCKET] [-t] [-l IR_LENGTH] [-b BASE] [-I] [-c CSR] - [-C CSR_CHECK] [-x] [-X] [-a ADDRESS] [-m {read,write}] +usage: dtm.py [-h] [-S SOCKET] [-t] [-w DELAY] [-l IR_LENGTH] + [--idcode IDCODE] [--dtmcs DTMCS] [--dmi DMI] [-b BASE] [-I] + [-c CSR] [-C CSR_CHECK] [-x] [-X] [-a ADDRESS] [-m {read,write}] [-s SIZE] [-f FILE] [-D DATA] [-e ELF] [-F] [-v] [-d] Debug Transport Module tiny demo @@ -16,13 +17,16 @@ options: -h, --help show this help message and exit Virtual machine: - -S, --socket SOCKET unix:path/to/socket or tcp:host:port (default - tcp:localhost:3335) + -S, --socket SOCKET connection (default tcp:localhost:3335) -t, --terminate terminate QEMU when done + -w, --idle DELAY stay idle before interacting with DTM DMI: -l, --ir-length IR_LENGTH bit length of the IR register (default: 5) + --idcode IDCODE define the ID code (default: 1) + --dtmcs DTMCS define an alternative DTMCS register (default: 0x10) + --dmi DMI define an alternative DMI register (default: 0x11) -b, --base BASE define DMI base address (default: 0x0) Info: @@ -107,6 +111,9 @@ Extras: * `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. +* `-w` pause execution on start up before communication with the remote DTM. This enables QEMU VM + to initialize and boot the guest SW before attempting a communication. + * `-X` do not attempt to resume normal execution of the hart once DTM operation have been completed. This can be useful for example when the QEMU VM is started with `-S` and no application code has been loaded in memory: once the DTM operations are completed, the default behavior is to resume @@ -117,6 +124,12 @@ Extras: * `-x` execute the loaded ELF application from its entry point. Requires the `--elf` option. Application is executed even with `-X` defined. +* `--idcode` specify an alternate IDCODE value + +* `--dmi` specify an alternate address for the DMI register + +* `--dtmcs` specify an alternate address for the DTMCS register + ### Examples Running QEMU VM with the `-chardev socket,id=taprbb,host=localhost,port=3335,server=on,wait=off` diff --git a/scripts/opentitan/dtm.py b/scripts/opentitan/dtm.py index 35644b0a4a2d7..f6cfc554b68f2 100755 --- a/scripts/opentitan/dtm.py +++ b/scripts/opentitan/dtm.py @@ -14,6 +14,7 @@ from os import linesep from os.path import dirname, join as joinpath, normpath from socket import create_connection, socket, AF_UNIX, SOCK_STREAM +from time import sleep from traceback import format_exc from typing import Optional import sys @@ -74,6 +75,8 @@ def main(): help=f'connection (default {default_socket})') qvm.add_argument('-t', '--terminate', action='store_true', help='terminate QEMU when done') + qvm.add_argument('-w', '--idle', type=float, metavar='DELAY', + help='stay idle before interacting with DTM') dmi = argparser.add_argument_group(title='DMI') dmi.add_argument('-l', '--ir-length', type=int, default=DEFAULT_IR_LENGTH, @@ -153,8 +156,8 @@ def main(): raise RuntimeError(f'Cannot connect to {args.socket}: ' f'{exc}') from exc - configure_loggers(args.verbose, *main_loggers, -1, 'jtag', - funcname=True, name_width=20) + log = configure_loggers(args.verbose, *main_loggers, -1, 'jtag', + funcname=True, name_width=20)[0] if sock: sock.settimeout(0.1) @@ -174,6 +177,11 @@ def main(): DMI.ADDRESS = args.dmi dtm = DebugTransportModule(eng, ir_length) rvdm = None + + if args.idle: + log.info('Idling for %.1f seconds', args.idle) + sleep(args.idle) + try: if args.info: code = idcode(eng, args.idcode, ir_length) From 09f0b1436485c568a2410ede8785dae757d920cb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 18:04:56 +0200 Subject: [PATCH 037/175] [ot] hw/opentitan: ot_socdbg_ctrl: improve trace message Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_socdbg_ctrl.c | 15 +++++++++++---- hw/opentitan/trace-events | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_socdbg_ctrl.c b/hw/opentitan/ot_socdbg_ctrl.c index 3dd495f8c8915..2b073cb1944a1 100644 --- a/hw/opentitan/ot_socdbg_ctrl.c +++ b/hw/opentitan/ot_socdbg_ctrl.c @@ -236,10 +236,15 @@ static const char *SOCDBG_NAMES[] = { #define STATE_NAME_ENTRY(_st_) [ST_##_st_] = stringify(_st_) static const char *STATE_NAMES[] = { - STATE_NAME_ENTRY(IDLE), STATE_NAME_ENTRY(CHECK_LC_ST), - STATE_NAME_ENTRY(WAIT4_DFT_EN), STATE_NAME_ENTRY(CHECK_HALT_PIN), - STATE_NAME_ENTRY(CHECK_JTAG_GO), STATE_NAME_ENTRY(CONTINUE_BOOT), + /* clang-format off */ + STATE_NAME_ENTRY(IDLE), + STATE_NAME_ENTRY(CHECK_LC_ST), + STATE_NAME_ENTRY(WAIT4_DFT_EN), + STATE_NAME_ENTRY(CHECK_HALT_PIN), + STATE_NAME_ENTRY(CHECK_JTAG_GO), + STATE_NAME_ENTRY(CONTINUE_BOOT), STATE_NAME_ENTRY(HALT_DONE), + /* clang-format on */ }; #undef STATE_NAME_ENTRY #define STATE_NAME(_st_) \ @@ -377,7 +382,9 @@ static void ot_socdbg_ctrl_update(OtSoCDbgCtrlState *s) int prev_policy = ibex_irq_get_level(&s->policy); if (prev_policy != policy) { - trace_ot_socdbg_ctrl_update(s->ot_id, s->debug_policy, s->debug_valid); + trace_ot_socdbg_ctrl_update(s->ot_id, SOCDBG_NAME(socdbg_state), + STATE_NAME(s->fsm_state), s->debug_policy, + s->debug_valid); } ibex_irq_set(&s->policy, policy); } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index d1804135da75a..daf2d1a215c7e 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -443,7 +443,7 @@ ot_socdbg_ctrl_rcv(const char *id, const char *src, unsigned n, int level) "%s: ot_socdbg_ctrl_schedule_fsm(const char *id, const char *func, int line, unsigned tc) "%s: @ %s:%d (%u)" ot_socdbg_ctrl_socdbg_state(const char *id, const char *st) "%s: %s" ot_socdbg_ctrl_tick_fsm(const char *id, const char *st, uint32_t bs, uint32_t lc, uint32_t dbg, bool dfti, bool bc) "%s: %s bs:0x%03x lc:0x%02x dbg:0x%01x dfti:%u bc:%u" -ot_socdbg_ctrl_update(const char *id, unsigned policy, bool valid) "%s: debug policy:0x%1x valid:%u" +ot_socdbg_ctrl_update(const char *id, const char *socdbgst, const char *fsmst, unsigned policy, bool valid) "%s: [%s] fsm:%s policy:0x%1x valid:%u" # ot_spi_device.c From 52cb70adf0da090a1c11a9810bc5b42537fe41ee Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 3 Jun 2025 18:06:32 +0200 Subject: [PATCH 038/175] [ot] hw/opentitan: ot_lc_ctrl: fix transition matrix generation Add support for the many ways of transition matrix encodings. Fix invalid matrix generation for SocDbg and Ownership. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 62 ++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 434cbd838a5c6..a1057aec9a0b3 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -187,7 +187,7 @@ static const char *REG_NAMES[REGS_COUNT] = { #define NUM_LC_STATE (LC_STATE_VALID_COUNT) #define NUM_LC_TRANSITION_COUNT 25u -#define NUM_OWNERSHIP 8u +#define NUM_OWNERSHIP 9u #define NUM_SOCDBG 3u #define LC_TRANSITION_COUNT_WORDS 24u @@ -417,9 +417,17 @@ struct OtLcCtrlClass { ResettablePhases parent_phases; }; +/* try to cope with the many ways to encode a transition matrix */ +typedef enum { + LC_TR_MODE_HISTORICAL, /* what prevailed in EarlGrey */ + LC_TR_MODE_FIRST_FULL, /* first non-ZRO transition contains full first st */ + LC_TR_MODE_FULL_CHANGE, /* no identifical words in transititions */ +} OtLcCtrlTransitionMode; + typedef struct { unsigned word_count; /* sequence size (count of 16-bit words) */ unsigned step_count; /* how many different steps/stages, incl. raw/blank */ + OtLcCtrlTransitionMode mode; /* how transition matrix is "encoded" */ const char *name; /* helper name */ } OtLcCtrlTransitionDesc; @@ -521,21 +529,25 @@ static const OtLcCtrlTransitionDesc TRANSITION_DESC[LC_CTRL_TRANS_COUNT] = { [LC_CTRL_TRANS_LC_STATE] = { .word_count = LC_STATE_WORDS, .step_count = NUM_LC_STATE, + .mode = LC_TR_MODE_HISTORICAL, .name = "lc_state", }, [LC_CTRL_TRANS_LC_TCOUNT] = { .word_count = LC_TRANSITION_COUNT_WORDS, .step_count = NUM_LC_TRANSITION_COUNT, + .mode = LC_TR_MODE_HISTORICAL, .name = "lc_tcount", }, [LC_CTRL_TRANS_OWNERSHIP] = { .word_count = OWNERSHIP_WORDS, .step_count = NUM_OWNERSHIP, + .mode = LC_TR_MODE_FIRST_FULL, .name = "ownership", }, [LC_CTRL_TRANS_SOCDBG] = { .word_count = SOCDBG_WORDS, .step_count = NUM_SOCDBG, + .mode = LC_TR_MODE_FULL_CHANGE, .name = "socdbg", }, }; @@ -1215,9 +1227,18 @@ static void ot_lc_ctrl_load_otp_hw_cfg(OtLcCtrlState *s) /* default to lowest capabilities */ int socdbg_ix = OT_SOCDBG_ST_PROD; + TRACE_LC_CTRL("soc_dbg_state: %s", + ot_lc_ctrl_hexdump(hw_cfg->soc_dbg_state, + sizeof(OtLcCtrlSocDbgValue))); + for (unsigned six = 0; six < OT_SOCDBG_ST_COUNT; six++) { - if (!memcmp(hw_cfg->soc_dbg_state, s->socdbgs[six], - sizeof(OtLcCtrlSocDbgValue))) { + bool match = !memcmp(hw_cfg->soc_dbg_state, s->socdbgs[six], + sizeof(OtLcCtrlSocDbgValue)); + TRACE_LC_CTRL("soc_dbg ref[%u]: %s, match: %u", six, + ot_lc_ctrl_hexdump(s->socdbgs[six], + sizeof(OtLcCtrlSocDbgValue)), + match); + if (match) { socdbg_ix = (int)six; break; } @@ -2033,12 +2054,37 @@ static void ot_lc_ctrl_configure_transitions( uint16_t *lcval = table; memset(lcval, 0, tdesc->word_count * sizeof(uint16_t)); /* RAW stage */ lcval += tdesc->word_count; - for (unsigned tix = 1; tix < tdesc->step_count; - tix++, lcval += tdesc->word_count) { - memcpy(&lcval[0], &last[0], tix * sizeof(uint16_t)); - memcpy(&lcval[tix], &first[tix], - (tdesc->word_count - tix) * sizeof(uint16_t)); + + if (tdesc->mode != LC_TR_MODE_HISTORICAL) { + memcpy(lcval, first, tdesc->word_count * sizeof(uint16_t)); + lcval += tdesc->word_count; + } + + if (tdesc->mode != LC_TR_MODE_FULL_CHANGE) { + unsigned step_count = tdesc->step_count - + (tdesc->mode == LC_TR_MODE_FIRST_FULL ? 1u : 0u); + for (unsigned tix = 1u; tix < step_count; tix++) { + memcpy(&lcval[0], &last[0], tix * sizeof(uint16_t)); + memcpy(&lcval[tix], &first[tix], + (tdesc->word_count - tix) * sizeof(uint16_t)); + lcval += tdesc->word_count; + } + } else { + g_assert(tdesc->step_count == 3u); + memcpy(lcval, last, tdesc->word_count * sizeof(uint16_t)); + } + +#ifdef OT_LC_CTRL_DEBUG + /* dump the generated transition tables */ + lcval = table; + for (unsigned tix = 0; tix < tdesc->step_count; tix++) { + qemu_log("%s: %s[%2u]", __func__, tdesc->name, tix); + for (unsigned wix = 0; wix < tdesc->word_count; wix++) { + qemu_log(" %04hx", *lcval++); + }; + qemu_log("\n"); } +#endif g_free(last); g_free(first); From a983ed921a9bead7312e2e63584c09ec4b583d8a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 10:55:09 +0200 Subject: [PATCH 039/175] [ot] scripts/opentitan: pt.pyot.context: add an optional timeout Synchronous `with` context commands may execute with a specific timeout. Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 5 ++++- python/qemu/ot/pyot/context.py | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index 90c2fa71ae854..e820296729d53 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -500,7 +500,10 @@ background commands. To change the default execution style for a command, add a suffix to the command definition: 1. append a `&` character to select background execution, useful with `pre` commands -2. append a `!` character to select synchronous execution, useful with `with` commands +2. append a `!` character to select synchronous execution, useful with `with` commands; it is + possible to append `@T` with `T` is defined as an integral value, _e.g._ the `sleep 5!@6` + statement executes the sleep command which waits for 5 seconds. This command does not time out + since its timeout is set to 6 seconds. #### Temporary directories diff --git a/python/qemu/ot/pyot/context.py b/python/qemu/ot/pyot/context.py index 1af116f5e170f..2a7d87a217eb2 100644 --- a/python/qemu/ot/pyot/context.py +++ b/python/qemu/ot/pyot/context.py @@ -13,6 +13,8 @@ from threading import Event from typing import Optional +import re + from .filemgr import QEMUFileManager from .worker import QEMUContextWorker @@ -68,12 +70,16 @@ def execute(self, ctx_name: str, code: int = 0, if ctx: for cmd in ctx: bkgnd = ctx_name == 'with' - if cmd.endswith('!'): - bkgnd = False - cmd = cmd[:-1] - elif cmd.endswith('&'): - bkgnd = True - cmd = cmd[:-1] + timeout = 5 + optmo = re.match(r'^(.*)(?:(&)|(!)(?:@(\d+))?)$', cmd) + if optmo: + cmd = optmo.group(1) + if optmo.group(2): + bkgnd = True + elif optmo.group(3): + bkgnd = False + if optmo.group(4): + timeout = int(optmo.group(4)) cmd = normpath(cmd.rstrip()) if bkgnd: if ctx_name == 'post': @@ -107,7 +113,7 @@ def execute(self, ctx_name: str, code: int = 0, errors='ignore', text=True) ret = 0 try: - outs, errs = proc.communicate(timeout=5) + outs, errs = proc.communicate(timeout=timeout) ret = proc.returncode except TimeoutExpired: proc.kill() From 51e582bbdea318652d6e990066bd96bb6573e962 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 12:14:58 +0200 Subject: [PATCH 040/175] [ot] hw/opentitan: ot_socdbg_ctrl: A0_DEBUG & HALT_CPU_BOOT input signals are ibex_gpio They should not be handled as boolean I/O Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_socdbg_ctrl.c | 29 +++++++++++++++++++++-------- hw/opentitan/trace-events | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/hw/opentitan/ot_socdbg_ctrl.c b/hw/opentitan/ot_socdbg_ctrl.c index 2b073cb1944a1..daef6a603d4ee 100644 --- a/hw/opentitan/ot_socdbg_ctrl.c +++ b/hw/opentitan/ot_socdbg_ctrl.c @@ -37,6 +37,7 @@ #include "hw/qdev-properties.h" #include "hw/registerfields.h" #include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_gpio.h" #include "hw/riscv/ibex_irq.h" #include "trace.h" #include "trace/trace-hw_opentitan.h" @@ -412,9 +413,12 @@ static void ot_socdbg_ctrl_a0_debug(void *opaque, int n, int level) g_assert(n == 0); - trace_ot_socdbg_ctrl_rcv(s->ot_id, "A0_DEBUG", 0, level); + trace_ot_socdbg_ctrl_rcv(s->ot_id, "A0_DEBUG", 0, ibex_gpio_repr(level)); - if (level) { + /* expect an Ibex GPIO signal */ + g_assert(ibex_gpio_check(level)); + + if (ibex_gpio_level(level)) { s->socdbg_bm |= R_SOCDBG_A0_DEBUG_MASK; } else { s->socdbg_bm &= ~R_SOCDBG_A0_DEBUG_MASK; @@ -429,9 +433,14 @@ static void ot_socdbg_ctrl_halt_cpu_boot(void *opaque, int n, int level) g_assert(n == 0); - trace_ot_socdbg_ctrl_rcv(s->ot_id, "HALT_CPU_BOOT", 0, level); + trace_ot_socdbg_ctrl_rcv(s->ot_id, "HALT_CPU_BOOT", 0, + ibex_gpio_repr(level)); - if (level) { + /* expect an Ibex GPIO signal */ + g_assert(ibex_gpio_check(level)); + + /* active low */ + if (!ibex_gpio_level(level)) { s->socdbg_bm |= R_SOCDBG_HALT_CPU_BOOT_MASK; } else { s->socdbg_bm &= ~R_SOCDBG_HALT_CPU_BOOT_MASK; @@ -446,8 +455,10 @@ static void ot_socdbg_ctrl_lc_broadcast(void *opaque, int n, int level) unsigned bcast = (unsigned)n; g_assert(bcast < OT_LC_BROADCAST_COUNT); + g_assert(!ibex_gpio_check(level)); - trace_ot_socdbg_ctrl_rcv(s->ot_id, LC_BCAST_NAME(bcast), bcast, level); + trace_ot_socdbg_ctrl_rcv(s->ot_id, LC_BCAST_NAME(bcast), bcast, + level ? '1' : '0'); switch (n) { case OT_LC_RAW_TEST_RMA: @@ -516,8 +527,9 @@ static void ot_socdbg_ctrl_a0_force_raw(void *opaque, int n, int level) OtSoCDbgCtrlState *s = opaque; g_assert(n == 0); + g_assert(!ibex_gpio_check(level)); - trace_ot_socdbg_ctrl_rcv(s->ot_id, "FORCE_RAW", 0, level); + trace_ot_socdbg_ctrl_rcv(s->ot_id, "FORCE_RAW", 0, level ? '1' : '0'); if (level) { s->socdbg_bm |= R_SOCDBG_A0_FORCE_RAW_MASK; @@ -533,8 +545,7 @@ static void ot_socdbg_ctrl_socdbg_state(void *opaque, int n, int level) OtSoCDbgCtrlState *s = opaque; g_assert(n == 0); - - trace_ot_socdbg_ctrl_rcv(s->ot_id, "SOCDBG_STATE", 0, level); + g_assert(!ibex_gpio_check(level)); switch (level) { case 0: @@ -550,6 +561,8 @@ static void ot_socdbg_ctrl_socdbg_state(void *opaque, int n, int level) g_assert_not_reached(); } + trace_ot_socdbg_ctrl_rcv(s->ot_id, "SOCDBG_STATE", 0, (char)('0' + level)); + trace_ot_socdbg_ctrl_socdbg_state(s->ot_id, SOCDBG_NAME(s->socdbg_state)); SCHEDULE_FSM(s); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index daf2d1a215c7e..f27f5479d17e3 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -439,7 +439,7 @@ ot_socdbg_ctrl_core_io_write(const char *id, uint32_t addr, const char * regname ot_socdbg_ctrl_core_update_irq(const char *id, int new) "%s: %u" ot_socdbg_ctrl_dmi_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val) "%s: addr=0x%02x (%s), val=0x%x" ot_socdbg_ctrl_dmi_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val) "%s: addr=0x%02x (%s), val=0x%x" -ot_socdbg_ctrl_rcv(const char *id, const char *src, unsigned n, int level) "%s: %s #%d: lvl 0x%x" +ot_socdbg_ctrl_rcv(const char *id, const char *src, unsigned n, char sig) "%s: %s #%d: lvl %c" ot_socdbg_ctrl_schedule_fsm(const char *id, const char *func, int line, unsigned tc) "%s: @ %s:%d (%u)" ot_socdbg_ctrl_socdbg_state(const char *id, const char *st) "%s: %s" ot_socdbg_ctrl_tick_fsm(const char *id, const char *st, uint32_t bs, uint32_t lc, uint32_t dbg, bool dfti, bool bc) "%s: %s bs:0x%03x lc:0x%02x dbg:0x%01x dfti:%u bc:%u" From 254e9f0f21aec992600aac16ceec29bc733b82d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 15:43:40 +0200 Subject: [PATCH 041/175] [ot] hw/opentitan: ot_otp_dj: add trace messages Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 4 ++++ hw/opentitan/trace-events | 2 ++ 2 files changed, 6 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index a9db924228751..89e69ce9df188 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -3150,6 +3150,8 @@ static void ot_otp_dj_generate_otp_sram_key(OtOTPDjState *s, OtOTPKey *key) key->seed_valid = valid; key->seed_size = SRAM_KEY_BYTES; + trace_ot_otp_sram_key_generated(s->ot_id); + /* some entropy bits have been used, refill the buffer */ qemu_bh_schedule(s->keygen->entropy_bh); } @@ -3182,6 +3184,8 @@ static void ot_otp_dj_get_otp_key(OtOTPState *s, OtOTPKeyType type, unsigned avail_entropy = ot_fifo32_num_used(&ds->keygen->entropy_buf); unsigned need_entropy; + trace_ot_otp_get_otp_key(ds->ot_id, type); + switch (type) { case OTP_KEY_FLASH_DATA: case OTP_KEY_FLASH_ADDR: diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index f27f5479d17e3..65a7907956f3b 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -340,6 +340,7 @@ ot_otp_ecc_mismatch(const char * id, unsigned address, uint32_t secc, uint32_t l ot_otp_ecc_parity_error(const char * id, uint32_t d_i, uint32_t ecc) "%s: 0x%04x, ECC 0x%02x" ot_otp_ecc_recovered_error(const char * id, uint32_t d_i, uint32_t d_o) "%s: 0x%04x -> 0x%04x" ot_otp_ecc_unrecoverable_error(const char * id, uint32_t d_i) "%s: 0x%04x" +ot_otp_get_otp_key(const char * id, int type) "%s: type %d" ot_otp_initialize(const char * id) "%s" ot_otp_integrity_report(const char * id, const char* part, unsigned pix, const char *msg) "%s: partition %s (#%u) %s" ot_otp_io_reg_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" @@ -357,6 +358,7 @@ ot_otp_pwr_otp_req(const char * id, const char *where) "%s: %s" ot_otp_reset(const char * id, const char *phase) "%s: %s" ot_otp_set_error(const char * id, const char *part, unsigned pix, const char* err, unsigned eix) "%s: %s (#%u): %s (%u)" ot_otp_skip_digest(const char * id, const char* part, unsigned pix) "%s: skipping empty digest on %s (#%u)" +ot_otp_sram_key_generated(const char * id) "%s" # ot_otp_ot_be.c From 090356313a21e032cb20958e91bb9318ba3047b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 15:44:47 +0200 Subject: [PATCH 042/175] [ot] hw/opentitan: ot_ibex_wrapper: fix a major regression This regression has been introduced with the refactoring/merging of Ibex Wrapper implementation. It led to always return 0 as the random number value. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index ff5254dcb8809..c9db8123ee9f1 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -963,7 +963,7 @@ ot_ibex_wrapper_read_rnd_data(OtIbexWrapperState *s, unsigned reg) ot_ibex_wrapper_request_entropy(s); - return *s->access_regs[reg]; + return value; } static uint32_t From 0f2285e89dd030af275009cfd997f156b67567e4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 15:45:08 +0200 Subject: [PATCH 043/175] [ot] hw/opentitan: ot_csrng: simplify IRQ trace message Signed-off-by: Emmanuel Blot --- hw/opentitan/trace-events | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 65a7907956f3b..885ad45378ddc 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -79,7 +79,7 @@ ot_csrng_instantiate(unsigned slot) "#%u" ot_csrng_invalid_state(const char *func, const char *state, int st) "%s [%s:%d]" ot_csrng_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_csrng_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_csrng_irqs(uint32_t active, uint32_t mask, uint32_t eff) "act:0x%08x msk:0x%08x eff:0x%08x" +ot_csrng_irqs(uint32_t active, uint32_t mask, uint32_t eff) "act:0x%1x msk:0x%1x eff:0x%1x" ot_csrng_push_command(unsigned slot, const char *cmd, unsigned acmd, char code, unsigned len) "#%u: %s(%u) %clen: %u" ot_csrng_read_state_db(unsigned slot, unsigned pos, uint32_t val) "#%u [%u] = 0x%08x" ot_csrng_reject_command(unsigned slot, uint32_t command, int res) "#%u: cmd: 0x%08x res: %d" From fc32dd97e1998b855ad2da4583429b9b6fd7f4f7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 18:42:25 +0200 Subject: [PATCH 044/175] [ot] hw/opentitan: ot_sram_ctrl: get rid of the BH to switch memory region It just leads to potential race condition. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_sram_ctrl.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/hw/opentitan/ot_sram_ctrl.c b/hw/opentitan/ot_sram_ctrl.c index ac4e3c2f63096..62968209b5d97 100644 --- a/hw/opentitan/ot_sram_ctrl.c +++ b/hw/opentitan/ot_sram_ctrl.c @@ -30,9 +30,7 @@ */ #include "qemu/osdep.h" -#include "qemu/error-report.h" #include "qemu/log.h" -#include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/typedefs.h" #include "hw/opentitan/ot_alert.h" @@ -121,7 +119,6 @@ struct OtSramCtrlState { MemoryRegion mmio; /* SRAM controller registers */ OtSramCtrlMem *mem; /* SRAM memory */ IbexIRQ alert; - QEMUBH *switch_mr_bh; /* switch memory region */ QEMUTimer *init_timer; /* SRAM initialization timer */ uint64_t *init_sram_bm; /* initialization bitmap */ @@ -192,6 +189,18 @@ static inline size_t ot_sram_ctrl_get_slot_count(size_t wsize) return ot_sram_ctrl_get_u64_slot(wsize); } +static void ot_sram_ctrl_mem_switch_to_ram(OtSramCtrlState *s) +{ + memory_region_transaction_begin(); + memory_region_set_enabled(&s->mem->init, false); + memory_region_set_enabled(&s->mem->sram, true); + s->mem->alias.alias = &s->mem->sram; + memory_region_transaction_commit(); + memory_region_set_dirty(&s->mem->sram, 0, s->size); + + trace_ot_sram_ctrl_switch_mem(s->ot_id, "ram"); +} + static bool ot_sram_ctrl_mem_is_fully_initialized(const OtSramCtrlState *s) { for (unsigned ix = 0; ix < s->init_slot_count; ix++) { @@ -244,7 +253,7 @@ static bool ot_sram_ctrl_initialize(OtSramCtrlState *s, unsigned count, if (!s->noswitch) { /* switch memory to SRAM */ trace_ot_sram_ctrl_initialization_complete(s->ot_id, "ctrl"); - qemu_bh_schedule(s->switch_mr_bh); + ot_sram_ctrl_mem_switch_to_ram(s); } else { trace_ot_sram_ctrl_initialization_complete(s->ot_id, "ctrl/noswitch"); @@ -516,20 +525,6 @@ static void ot_sram_ctrl_regs_write(void *opaque, hwaddr addr, uint64_t val64, } }; -static void ot_sram_ctrl_mem_switch_to_ram_fn(void *opaque) -{ - OtSramCtrlState *s = opaque; - - memory_region_transaction_begin(); - memory_region_set_enabled(&s->mem->init, false); - memory_region_set_enabled(&s->mem->sram, true); - s->mem->alias.alias = &s->mem->sram; - memory_region_transaction_commit(); - memory_region_set_dirty(&s->mem->sram, 0, s->size); - - trace_ot_sram_ctrl_switch_mem(s->ot_id, "ram"); -} - static void ot_sram_ctrl_init_chunk_fn(void *opaque) { OtSramCtrlState *s = opaque; @@ -668,7 +663,7 @@ static MemTxResult ot_sram_ctrl_mem_init_write_with_attrs( trace_ot_sram_ctrl_initialization_complete(s->ot_id, "write"); - qemu_bh_schedule(s->switch_mr_bh); + ot_sram_ctrl_mem_switch_to_ram(s); } else { if (!s->initialized) { trace_ot_sram_ctrl_initialization_complete( @@ -723,7 +718,6 @@ static void ot_sram_ctrl_reset_enter(Object *obj, ResetType type) } timer_del(s->init_timer); - qemu_bh_cancel(s->switch_mr_bh); memset(s->regs, 0, REGS_SIZE); @@ -867,7 +861,6 @@ static void ot_sram_ctrl_init(Object *obj) ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); s->mem = g_new0(OtSramCtrlMem, 1u); - s->switch_mr_bh = qemu_bh_new(&ot_sram_ctrl_mem_switch_to_ram_fn, s); s->init_timer = timer_new_ns(OT_VIRTUAL_CLOCK, &ot_sram_ctrl_init_chunk_fn, s); s->prng = ot_prng_allocate(); From 7f72c0d430b93a920dab328a33d1ef0f8693f0c8 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 4 Jun 2025 20:28:52 +0200 Subject: [PATCH 045/175] [ot] scripts/opentitan: otptool.py: add HW digest generation This feature is useful when one or more fields of a HW digest protected partition have been modified. Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 21 +++++++++++++-------- python/qemu/ot/otp/image.py | 12 ++++++++++++ python/qemu/ot/otp/partition.py | 23 +++++++++++++++++++++++ scripts/opentitan/otptool.py | 29 +++++++++++++++++++---------- 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index ab4379b86fcf0..80a7c3729f880 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -9,7 +9,7 @@ controller virtual device. usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW] [-k {auto,otp,fuz}] [-e BITS] [-C CONFIG] [-c INT] [-i INT] [-w] [-n] [-f PART:FIELD] [--no-version] [-s] [-E] [-D] [-U] - [-G {LCVAL,LCTPL,PARTS,REGS}] [-F] + [-g {LCVAL,LCTPL,PARTS,REGS}] [-F] [-G PART] [--change PART:FIELD=VALUE] [--empty PARTITION] [--erase PART:FIELD] [--clear-bit CLEAR_BIT] [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [-v] [-d] @@ -44,9 +44,11 @@ Commands: -E, --ecc-recover attempt to recover errors with ECC -D, --digest check the OTP HW partition digest -U, --update update RAW file after ECC recovery or bit changes - -G, --generate {LCVAL,LCTPL,PARTS,REGS} + -g, --generate {LCVAL,LCTPL,PARTS,REGS} generate C code, see doc for options -F, --fix-ecc rebuild ECC + -G, --fix-digest PART + rebuild HW digest --change PART:FIELD=VALUE change the content of an OTP field --empty PARTITION reset the content of a whole partition, including its @@ -119,6 +121,13 @@ Fuse RAW images only use the v1 type. * `-f` select which partition(s) and partition field(s) should be shown when option `-s` is used. When not specified, all partitions and fields are reported. +* `-G` can be used to (re)build the HW digest of a partition after altering one or more of its + fields, see `--change` option. + +* `-g` can be used to generate C code for QEMU, from OTP and LifeCycle known definitions. See the + [Generation](#generation) section for details. See option `-o` to specify the path to the file to + generate + * `-i` specify the initialization vector for the Present scrambler used for partition digests. This value is "usually" found within the `hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv` OT file, from the last entry of `RndCnstDigestIV` array, _i.e._ item 0. It is used along with option @@ -134,10 +143,6 @@ Fuse RAW images only use the v1 type. the kind of the input VMEM file from its content when this option is not specified or set to `auto`. It is fails to detect the file kind or if the kind needs to be enforced, use this option. -* `-G` can be used to generate C code for QEMU, from OTP and LifeCycle known definitions. See the - [Generation](#generation) section for details. See option `-o` to specify the path to the file to - generate - * `-l` specify the life cycle system verilog file that defines the encoding of the life cycle states. This option is not required to generate a RAW image file, but required when the `-L` option switch is used. @@ -296,10 +301,10 @@ scripts/opentitan/otptool.py -r otp.raw -j otp_ctrl_mmap.hjson -D \ Generate a C source file with LifeCycle constant definitions: ````sh -scripts/opentitan/otptool.py -G LCVAL -l lc_ctrl_state_pkg.sv -o lc_state.c +scripts/opentitan/otptool.py -g LCVAL -l lc_ctrl_state_pkg.sv -o lc_state.c ```` Generates a C source file with OTP partition properties: ````sh -scripts/opentitan/otptool.py -j otp_ctrl_mmap.hjson -G PARTS -o otp_part.c +scripts/opentitan/otptool.py -j otp_ctrl_mmap.hjson -g PARTS -o otp_part.c ```` diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index bf4d12dc217e5..d9d9b4fe856e1 100644 --- a/python/qemu/ot/otp/image.py +++ b/python/qemu/ot/otp/image.py @@ -437,6 +437,18 @@ def erase_field(self, partition: Union[int, str], field: str) -> None: part.erase_field(field) self._reload(partix, True) + def build_digest(self, partition: Union[int, str], erase: bool) -> None: + """Rebuild the digest of a partition from its content. + + :param partition: the name or the index of the partition to erase + :param erase: whether to erase any existing digest or combine it + """ + if any(c is None for c in (self._digest_iv, self._digest_constant)): + raise RuntimeError('Missing Present constants') + partix, part = self._retrieve_partition(partition) + part.build_digest(self._digest_iv, self._digest_constant, erase) + self._reload(partix, True) + @staticmethod def bit_parity(data: int) -> int: """Compute the bit parity of an integer, i.e. reduce the vector to a diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index 07447ec02103f..39cd48dd63d4b 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -74,6 +74,11 @@ def has_digest(self) -> bool: """Check if the partition supports any kind of digest (SW or HW).""" return any(getattr(self, f'{k}w_digest', False) for k in 'sh') + @property + def has_hw_digest(self) -> bool: + """Check if the partition supports HW digest.""" + return getattr(self, 'hw_digest', False) + @property def is_locked(self) -> bool: """Check if the partition is locked, based on its digest.""" @@ -297,6 +302,24 @@ def erase_field(self, field: str) -> None: self._data = b''.join((self._data[:prop.offset], bytes(prop.size), self._data[prop.end:])) + def build_digest(self, digest_iv: int, digest_constant: int, erase: bool) \ + -> None: + """Rebuild the digest of a partition from its content. + + :param erase: whether to erase any existing digest or combine it + """ + if not self.has_hw_digest: + raise ValueError(f'Partition {self.name} does not have a HW digest') + assert self._digest_bytes is not None + digest = self.compute_digest(self._data, digest_iv, digest_constant) + self._log.info('New partition %s digest: %016x', self.name, digest) + digest_len = len(self._digest_bytes) + if erase: + self._digest_bytes = bytes(digest_len) + bdigest = digest.to_bytes(length=digest_len, byteorder='little') + self._digest_bytes = bytes((a | b) + for a, b in zip(self._digest_bytes, bdigest)) + def _retrieve_properties(self, field: str) -> tuple[int, int]: is_digest = self.has_digest and field.upper() == 'DIGEST' if not is_digest: diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index d076772f97ab0..a9cda2ab6a74a 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -83,10 +83,13 @@ def main(): commands.add_argument('-U', '--update', action='store_true', help='update RAW file after ECC recovery or bit ' 'changes') - commands.add_argument('-G', '--generate', choices=genfmts, + commands.add_argument('-g', '--generate', choices=genfmts, help='generate C code, see doc for options') commands.add_argument('-F', '--fix-ecc', action='store_true', help='rebuild ECC') + commands.add_argument('-G', '--fix-digest', action='append', + metavar='PART', default=[], + help='rebuild HW digest') commands.add_argument('--change', action='append', metavar='PART:FIELD=VALUE', default=[], help='change the content of an OTP field') @@ -119,7 +122,8 @@ def main(): if not (args.vmem or args.raw): if any((args.show, args.digest, args.ecc_recover, args.clear_bit, - args.set_bit, args.toggle_bit, args.change, args.erase)): + args.set_bit, args.toggle_bit, args.change, args.erase, + args.fix_digest)): argparser.error('At least one raw or vmem file is required') if not args.vmem and args.kind: @@ -164,6 +168,8 @@ def main(): argparser.error('Cannot change an OTP field without an OTP map') if args.erase: argparser.error('Cannot erase an OTP field without an OTP map') + if args.fix_digest: + argparser.error('Cannot generate HW digest without an OTP map') else: otpmap = OtpMap() otpmap.load(args.otp_map) @@ -271,12 +277,7 @@ def main(): otp.set_digest_iv(args.iv) if args.constant: otp.set_digest_constant(args.constant) - if lcext: - otp.load_lifecycle(lcext) - if args.show: - otp.decode(not args.no_decode, args.wide, output, - not args.no_version, args.filter) - if args.digest: + if args.digest or args.fix_digest: if not otp.has_present_constants: if args.raw and otp.version == 1: msg = '; OTP v1 image does not track them' @@ -285,9 +286,17 @@ def main(): # can either be defined on the CLI or in an existing QEWU # image argparser.error(f'Present scrambler constants are required ' - f'to verify the partition digest{msg}') + f'to handle the partition digest{msg}') + for part in args.fix_digest: + otp.build_digest(part, True) + check_update = True + if lcext: + otp.load_lifecycle(lcext) + if args.show: + otp.decode(not args.no_decode, args.wide, output, + not args.no_version, args.filter) + if args.digest: otp.verify(True) - for pos, bitact in enumerate(bit_actions): if alter_bits[pos]: getattr(otp, f'{bitact}_bits')(alter_bits[pos]) From cb1196aa23ba5ccfbefd3871c22a95518ad9ec3c Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 13:00:13 +0200 Subject: [PATCH 046/175] [ot] hw/opentitan: ot_mbx: add missing ot_id info in trace message Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_mbx.c | 3 ++- hw/opentitan/trace-events | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_mbx.c b/hw/opentitan/ot_mbx.c index 663aef14fc94f..6e26c9e203c03 100644 --- a/hw/opentitan/ot_mbx.c +++ b/hw/opentitan/ot_mbx.c @@ -225,7 +225,8 @@ static void ot_mbx_host_update_irqs(OtMbxState *s) for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) { int level = (int)(bool)(levels & (1u << ix)); if (level != ibex_irq_get_level(&host->irqs[ix])) { - trace_ot_mbx_host_update_irq(ibex_irq_get_level(&host->irqs[ix]), + trace_ot_mbx_host_update_irq(s->ot_id, + ibex_irq_get_level(&host->irqs[ix]), level); } ibex_irq_set(&host->irqs[ix], level); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 885ad45378ddc..749643aac6f92 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -303,7 +303,7 @@ ot_mbx_busy(const char * mid, const char *state) "%s: %s" ot_mbx_change_state(const char * mid, const char *state) "%s: %s" ot_mbx_host_io_read_out(const char * mid, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_mbx_host_io_write(const char * mid, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_mbx_host_update_irq(int prev, int next) "%d -> %d" +ot_mbx_host_update_irq(const char *mbx_id, int prev, int next) "%s: %d -> %d" ot_mbx_status(const char *mbx_id, int line, bool abort, bool error, bool busy) "%s: [%d] abort:%u error:%u busy:%u" ot_mbx_sys_io_read_out(const char * mid, uint32_t addr, const char * regname, uint32_t val) "%s: addr=0x%02x (%s), val=0x%x" ot_mbx_sys_io_write(const char * mid, uint32_t addr, const char * regname, uint32_t val) "%s: addr=0x%02x (%s), val=0x%x" From 8ab5e95d149bc8e71b555364e80c067f6accd0f1 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 13:01:20 +0200 Subject: [PATCH 047/175] [ot] hw/opentitan: ot_otp_dj: add missing ot_id info from trace and log messages Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 231 ++++++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 98 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 89e69ce9df188..05ceb0ef28d0f 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1528,8 +1528,8 @@ static bool ot_otp_dj_is_readable(OtOTPDjState *s, int partition) reg = UINT32_MAX; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid partition: %d\n", __func__, - partition); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid partition: %d\n", + __func__, s->ot_id, partition); return false; } @@ -1613,20 +1613,22 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) switch ((int)sig) { case OT_OTP_LC_DFT_EN: - qemu_log_mask(LOG_UNIMP, "%s: DFT feature not supported\n", - __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: DFT feature not supported\n", + __func__, s->ot_id); break; case OT_OTP_LC_ESCALATE_EN: if (level) { DAI_CHANGE_STATE(s, OTP_DAI_ERROR); LCI_CHANGE_STATE(s, OTP_LCI_ERROR); // TODO: manage other FSMs - qemu_log_mask(LOG_UNIMP, "%s: ESCALATE partially implemented\n", - __func__); + qemu_log_mask(LOG_UNIMP, + "%s: %s: ESCALATE partially implemented\n", + __func__, s->ot_id); } break; case OT_OTP_LC_CHECK_BYP_EN: - qemu_log_mask(LOG_UNIMP, "%s: Bypass is ignored\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: bypass is ignored\n", __func__, + s->ot_id); break; case OT_OTP_LC_CREATOR_SEED_SW_RW_EN: for (unsigned ix = 0; ix < OTP_PART_COUNT; ix++) { @@ -1645,11 +1647,12 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) } break; case OT_OTP_LC_SEED_HW_RD_EN: - qemu_log_mask(LOG_UNIMP, "%s: Seed HW read is ignored\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: seed HW read is ignored\n", + __func__, s->ot_id); break; default: - error_setg(&error_fatal, "%s: unexpected LC broadcast %d\n", - __func__, sig); + error_setg(&error_fatal, "%s: %s: unexpected LC broadcast %d\n", + __func__, s->ot_id, sig); g_assert_not_reached(); break; } @@ -1696,7 +1699,8 @@ ot_otp_dj_load_partition_digest(OtOTPDjState *s, unsigned partition) unsigned digoff = (unsigned)OtOTPPartDescs[partition].digest_offset; if ((digoff + sizeof(uint64_t)) > s->otp->data_size) { - error_setg(&error_fatal, "Partition located outside storage?\n"); + error_setg(&error_fatal, "%s: partition located outside storage?", + s->ot_id); /* linter doest not know the above call never returns */ return 0u; } @@ -1766,7 +1770,6 @@ static void ot_otp_dj_check_partition_integrity(OtOTPDjState *s, unsigned ix) /* TODO: revert buffered part to default */ } else { trace_ot_otp_integrity_report(s->ot_id, PART_NAME(ix), ix, "digest OK"); - pctrl->failed = false; } } @@ -1821,7 +1824,8 @@ static void ot_otp_dj_dai_clear_error(OtOTPDjState *s) static void ot_otp_dj_dai_read(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: DAI controller busy\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", + __func__, s->ot_id); return; } @@ -1834,16 +1838,18 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) int partition = ot_otp_dj_get_part_from_address(s, address); if (partition < 0) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid partition address 0x%x\n", - __func__, address); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: invalid partition address 0x%x\n", __func__, + s->ot_id, address); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (partition >= OTP_PART_LIFE_CYCLE) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Life cycle partition cannot be accessed from DAI\n", - __func__); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: life cycle partition cannot be accessed from DAI\n", + __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -1931,7 +1937,8 @@ static int ot_otp_dj_dai_write_u64(OtOTPDjState *s, unsigned address) uint32_t hi = s->regs[R_DIRECT_ACCESS_WDATA_1]; if ((dst_lo & ~lo) || (dst_hi & ~hi)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP bits\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Cannot clear OTP bits\n", + __func__, s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } @@ -1942,7 +1949,7 @@ static int ot_otp_dj_dai_write_u64(OtOTPDjState *s, unsigned address) if (ot_otp_dj_write_backend(s, dst, (unsigned)(offset + waddr * sizeof(uint32_t)), sizeof(uint64_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return -1; } @@ -1957,8 +1964,9 @@ static int ot_otp_dj_dai_write_u64(OtOTPDjState *s, unsigned address) uint32_t ecc = (ecc_hi << 16u) | ecc_lo; if (*edst & ~ecc) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP ECC bits\n", - __func__); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Cannot clear OTP ECC bits\n", __func__, + s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } *edst |= ecc; @@ -1966,7 +1974,8 @@ static int ot_otp_dj_dai_write_u64(OtOTPDjState *s, unsigned address) offset = (uintptr_t)s->otp->ecc - (uintptr_t)s->otp->storage; if (ot_otp_dj_write_backend(s, edst, (unsigned)(offset + (waddr << 1u)), sizeof(uint32_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, + s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return -1; } @@ -1985,7 +1994,8 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) uint32_t data = s->regs[R_DIRECT_ACCESS_WDATA_0]; if (*dst & ~data) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP bits\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: cannot clear OTP bits\n", + __func__, s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } @@ -1995,7 +2005,7 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) if (ot_otp_dj_write_backend(s, dst, (unsigned)(offset + waddr * sizeof(uint32_t)), sizeof(uint32_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return -1; } @@ -2006,8 +2016,9 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) uint16_t ecc = ot_otp_dj_compute_ecc_u32(*dst); if (*edst & ~ecc) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP ECC bits\n", - __func__); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: cannot clear OTP ECC bits\n", __func__, + s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } *edst |= ecc; @@ -2016,7 +2027,8 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) if (ot_otp_dj_write_backend(s, edst, (unsigned)(offset + (address >> 1u)), sizeof(uint16_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, + s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return -1; } @@ -2031,14 +2043,16 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) static void ot_otp_dj_dai_write(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: DAI controller busy\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", + __func__, s->ot_id); return; } if (!ot_otp_dj_is_backend_writable(s)) { /* OTP backend missing or read-only; reject any write request */ qemu_log_mask(LOG_GUEST_ERROR, - "%s: OTP backend file is missing or R/O\n", __func__); + "%s: %s: OTP backend file is missing or R/O\n", __func__, + s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return; } @@ -2052,16 +2066,18 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) int partition = ot_otp_dj_get_part_from_address(s, address); if (partition < 0) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid partition address 0x%x\n", - __func__, address); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: invalid partition address 0x%x\n", __func__, + s->ot_id, address); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (partition >= OTP_PART_LIFE_CYCLE) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Life cycle partition cannot be accessed from DAI\n", - __func__); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: Life cycle partition cannot be accessed from DAI\n", + __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -2069,16 +2085,16 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) OtOTPPartController *pctrl = &s->partctrls[partition]; if (pctrl->locked) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Partition %s (%u) is locked\n", - __func__, PART_NAME(partition), partition); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: partition %s (%u) is locked\n", + __func__, s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (pctrl->write_lock) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Partition %s (%u) is write locked\n", __func__, - PART_NAME(partition), partition); + "%s: %s: artition %s (%u) is write locked\n", __func__, + s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -2089,10 +2105,11 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) if (is_digest) { if (OtOTPPartDescs[partition].hw_digest) { /* should have been a Digest command, not a Write command */ - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Partition %s (%u) HW digest cannot be directly " - "written\n", - __func__, PART_NAME(partition), partition); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: partition %s (%u) HW digest cannot be directly " + "written\n", + __func__, s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -2120,14 +2137,16 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) static void ot_otp_dj_dai_digest(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: DAI controller busy\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", + __func__, s->ot_id); return; } if (!ot_otp_dj_is_backend_writable(s)) { /* OTP backend missing or read-only; reject any write request */ qemu_log_mask(LOG_GUEST_ERROR, - "%s: OTP backend file is missing or R/O\n", __func__); + "%s: %s: OTP backend file is missing or R/O\n", __func__, + s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return; } @@ -2141,24 +2160,26 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) int partition = ot_otp_dj_get_part_from_address(s, address); if (partition < 0) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid partition address 0x%x\n", - __func__, address); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Invalid partition address 0x%x\n", __func__, + s->ot_id, address); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (partition >= OTP_PART_LIFE_CYCLE) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Life cycle partition cannot be accessed from DAI\n", - __func__); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: Life cycle partition cannot be accessed from DAI\n", + __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (!OtOTPPartDescs[partition].hw_digest) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid partition, no HW digest on %s (#%u)\n", - __func__, PART_NAME(partition), partition); + "%s: %s: Invalid partition, no HW digest on %s (#%u)\n", + __func__, s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -2166,16 +2187,16 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) OtOTPPartController *pctrl = &s->partctrls[partition]; if (pctrl->locked) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Partition %s (%u) is locked\n", - __func__, PART_NAME(partition), partition); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Partition %s (%u) is locked\n", + __func__, s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } if (pctrl->write_lock) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Partition %s (%u) is write locked\n", __func__, - PART_NAME(partition), partition); + "%s: %s: Partition %s (%u) is write locked\n", __func__, + s->ot_id, PART_NAME(partition), partition); ot_otp_dj_dai_set_error(s, OTP_ACCESS_ERROR); return; } @@ -2198,7 +2219,7 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) ot_otp_dj_compute_partition_digest(s, data, part_size); s->dai->partition = partition; - TRACE_OTP("%s: next digest %016llx from %s\n", __func__, + TRACE_OTP("%s: %s: next digest %016llx from %s\n", __func__, s->ot_id, pctrl->buffer.next_digest, ot_otp_hexdump(data, part_size)); DAI_CHANGE_STATE(s, OTP_DAI_DIG_WAIT); @@ -2224,8 +2245,8 @@ static void ot_otp_dj_dai_write_digest(void *opaque) pctrl->buffer.next_digest = 0; if (*dst & ~data) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP data bits\n", - __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: cannot clear OTP data bits\n", + __func__, s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } *dst |= data; @@ -2234,7 +2255,7 @@ static void ot_otp_dj_dai_write_digest(void *opaque) offset = (uintptr_t)s->otp->data - (uintptr_t)s->otp->storage; if (ot_otp_dj_write_backend(s, dst, (unsigned)(offset + address), sizeof(uint64_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return; } @@ -2247,8 +2268,8 @@ static void ot_otp_dj_dai_write_digest(void *opaque) uint32_t *edst = &s->otp->ecc[ewaddr]; if (*edst & ~ecc) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear OTP ECC bits\n", - __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: cannot clear OTP ECC bits\n", + __func__, s->ot_id); ot_otp_dj_set_error(s, OTP_ENTRY_DAI, OTP_MACRO_WRITE_BLANK_ERROR); } *edst |= ecc; @@ -2256,7 +2277,7 @@ static void ot_otp_dj_dai_write_digest(void *opaque) offset = (uintptr_t)s->otp->ecc - (uintptr_t)s->otp->storage; if (ot_otp_dj_write_backend(s, edst, (unsigned)(offset + (ewaddr << 2u)), sizeof(uint32_t))) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, s->ot_id); ot_otp_dj_dai_set_error(s, OTP_MACRO_ERROR); return; } @@ -2568,13 +2589,14 @@ static uint64_t ot_otp_dj_reg_read(void *opaque, hwaddr addr, unsigned size) case R_INTR_TEST: case R_ALERT_TEST: qemu_log_mask(LOG_GUEST_ERROR, - "%s: W/O register 0x02%" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: W/O register 0x02%" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); val32 = 0; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0; break; } @@ -2623,8 +2645,9 @@ static void ot_otp_dj_reg_write(void *opaque, hwaddr addr, uint64_t value, if (!(s->regs[R_DIRECT_ACCESS_REGWEN] & R_DIRECT_ACCESS_REGWEN_REGWEN_MASK)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: %s is not enabled, %s is protected\n", __func__, - REG_NAME(R_DIRECT_ACCESS_REGWEN), REG_NAME(reg)); + "%s: %s: %s is not enabled, %s is protected\n", + __func__, s->ot_id, REG_NAME(R_DIRECT_ACCESS_REGWEN), + REG_NAME(reg)); return; } break; @@ -2632,8 +2655,9 @@ static void ot_otp_dj_reg_write(void *opaque, hwaddr addr, uint64_t value, if (!(s->regs[R_CHECK_TRIGGER_REGWEN] & R_CHECK_TRIGGER_REGWEN_REGWEN_MASK)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: %s is not enabled, %s is protected\n", __func__, - REG_NAME(R_CHECK_TRIGGER_REGWEN), REG_NAME(reg)); + "%s: %s: %s is not enabled, %s is protected\n", + __func__, s->ot_id, REG_NAME(R_CHECK_TRIGGER_REGWEN), + REG_NAME(reg)); return; } break; @@ -2642,8 +2666,9 @@ static void ot_otp_dj_reg_write(void *opaque, hwaddr addr, uint64_t value, case R_CONSISTENCY_CHECK_PERIOD: if (!(s->regs[R_CHECK_REGWEN] & R_CHECK_REGWEN_REGWEN_MASK)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: %s is not enabled, %s is protected\n", __func__, - REG_NAME(R_CHECK_REGWEN), REG_NAME(reg)); + "%s: %s: %s is not enabled, %s is protected\n", + __func__, s->ot_id, REG_NAME(R_CHECK_REGWEN), + REG_NAME(reg)); return; } break; @@ -2714,8 +2739,8 @@ static void ot_otp_dj_reg_write(void *opaque, hwaddr addr, uint64_t value, case R_SECRET3_DIGEST_0: case R_SECRET3_DIGEST_1: qemu_log_mask(LOG_GUEST_ERROR, - "%s: R/O register 0x%03" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: R/O register 0x%03" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); return; default: break; @@ -2783,12 +2808,13 @@ static void ot_otp_dj_reg_write(void *opaque, hwaddr addr, uint64_t value, case R_CHECK_TIMEOUT: case R_INTEGRITY_CHECK_PERIOD: case R_CONSISTENCY_CHECK_PERIOD: - qemu_log_mask(LOG_UNIMP, "%s: %s is not supported\n", __func__, - REG_NAME(reg)); + qemu_log_mask(LOG_UNIMP, "%s: %s: %s is not supported\n", __func__, + s->ot_id, REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } } @@ -3190,7 +3216,8 @@ static void ot_otp_dj_get_otp_key(OtOTPState *s, OtOTPKeyType type, case OTP_KEY_FLASH_DATA: case OTP_KEY_FLASH_ADDR: /* there is no flash key on Darjeeling */ - qemu_log_mask(LOG_UNIMP, "%s: flash key is not supported\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: flash key is not supported\n", + __func__, ds->ot_id); break; case OTP_KEY_OTBN: memset(key, 0, sizeof(*key)); @@ -3204,14 +3231,15 @@ static void ot_otp_dj_get_otp_key(OtOTPState *s, OtOTPKeyType type, need_entropy = (SRAM_KEY_WIDTH * 2u + SRAM_NONCE_WIDTH) / 32u; if (avail_entropy < need_entropy) { unsigned count = need_entropy - avail_entropy; - error_report("%s: not enough entropy for key %d, fake %u words", - __func__, type, count); + error_report("%s: %s: not enough entropy for key %d, fake %u words", + __func__, ds->ot_id, type, count); ot_otp_dj_fake_entropy(ds, count); } ot_otp_dj_generate_otp_sram_key(ds, key); break; default: - error_report("%s: invalid OTP key type: %d", __func__, type); + error_report("%s: %s: invalid OTP key type: %d", __func__, ds->ot_id, + type); break; } } @@ -3280,7 +3308,8 @@ static void ot_otp_dj_lci_write_complete(OtOTPDjState *s, bool success) if (ot_otp_dj_write_backend(s, &s->otp->data[lc_off], (unsigned)(offset + lcdesc->offset), lcdesc->size)) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, + s->ot_id); if (lci->error == OTP_NO_ERROR) { lci->error = OTP_MACRO_ERROR; LCI_CHANGE_STATE(s, OTP_LCI_ERROR); @@ -3292,7 +3321,8 @@ static void ot_otp_dj_lci_write_complete(OtOTPDjState *s, bool success) (unsigned)(offset + (lcdesc->offset >> 1u)), lcdesc->size >> 1u)) { - error_report("%s: cannot update OTP backend", __func__); + error_report("%s: %s: cannot update OTP backend", __func__, + s->ot_id); if (lci->error == OTP_NO_ERROR) { lci->error = OTP_MACRO_ERROR; LCI_CHANGE_STATE(s, OTP_LCI_ERROR); @@ -3331,7 +3361,8 @@ static void ot_otp_dj_lci_write_word(void *opaque) if (!ot_otp_dj_is_backend_writable(s)) { /* OTP backend missing or read-only; reject any write request */ qemu_log_mask(LOG_GUEST_ERROR, - "%s: OTP backend file is missing or R/O\n", __func__); + "%s: %s: OTP backend file is missing or R/O\n", __func__, + s->ot_id); lci->error = OTP_MACRO_ERROR; LCI_CHANGE_STATE(s, OTP_LCI_ERROR); ot_otp_dj_lci_write_complete(s, false); @@ -3363,8 +3394,8 @@ static void ot_otp_dj_lci_write_word(void *opaque) if (cur_val & ~new_val) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Cannot clear OTP bits @ %u: 0x%04x / 0x%04x\n", - __func__, lci->hpos, cur_val, new_val); + "%s: %s: cannot clear OTP bits @ %u: 0x%04x / 0x%04x\n", + __func__, s->ot_id, lci->hpos, cur_val, new_val); if (lci->error == OTP_NO_ERROR) { lci->error = OTP_MACRO_WRITE_BLANK_ERROR; } @@ -3387,9 +3418,10 @@ static void ot_otp_dj_lci_write_word(void *opaque) trace_ot_otp_lci_write_ecc(s->ot_id, lci->hpos, cur_ecc, new_ecc); if (cur_ecc & ~new_ecc) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Cannot clear OTP ECC @ %u: 0x%02x / 0x%02x\n", - __func__, lci->hpos, cur_ecc, new_ecc); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: cannot clear OTP ECC @ %u: 0x%02x / 0x%02x\n", + __func__, s->ot_id, lci->hpos, cur_ecc, new_ecc); if (lci->error == OTP_NO_ERROR) { lci->error = OTP_MACRO_WRITE_BLANK_ERROR; } @@ -3491,26 +3523,28 @@ static void ot_otp_dj_pwr_load(OtOTPDjState *s) bool write = blk_supports_write_perm(s->blk); uint64_t perm = BLK_PERM_CONSISTENT_READ | (write ? BLK_PERM_WRITE : 0); if (blk_set_perm(s->blk, perm, perm, &error_fatal)) { - warn_report("%s: OTP backend is R/O", __func__); + warn_report("%s: %s: OTP backend is R/O", __func__, s->ot_id); write = false; } int rc = blk_pread(s->blk, 0, (int64_t)otp_size, otp->storage, 0); if (rc < 0) { error_setg(&error_fatal, - "failed to read the initial OTP content: %d", rc); + "%s: failed to read the initial OTP content: %d", + s->ot_id, rc); return; } const struct otp_header *otp_hdr = (const struct otp_header *)base; if (memcmp(otp_hdr->magic, "vOTP", sizeof(otp_hdr->magic)) != 0) { - error_setg(&error_fatal, "OTP file is not a valid OTP backend"); + error_setg(&error_fatal, "%s: OTP file is not a valid OTP backend", + s->ot_id); return; } if (otp_hdr->version != 1u && otp_hdr->version != 2u) { - error_setg(&error_fatal, "OTP file version %u is not supported", - otp_hdr->version); + error_setg(&error_fatal, "%s: OTP file version %u is not supported", + s->ot_id, otp_hdr->version); return; } @@ -3524,8 +3558,9 @@ static void ot_otp_dj_pwr_load(OtOTPDjState *s) if (otp->ecc_bit_count != 6u || !ot_otp_dj_is_ecc_enabled(s)) { qemu_log_mask(LOG_UNIMP, - "%s: support for ECC %u/%u not implemented\n", - __func__, otp->ecc_granule, otp->ecc_bit_count); + "%s: %s: support for ECC %u/%u not implemented\n", + __func__, s->ot_id, otp->ecc_granule, + otp->ecc_bit_count); } trace_ot_otp_load_backend(s->ot_id, otp_hdr->version, From 299b07c62b72a2dfb1063391bb151fe16346c2ab Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 13:01:54 +0200 Subject: [PATCH 048/175] [ot] hw/opentitan: ot_otp_dj: fix alert generation and add irq and alert traces Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 33 +++++++++++++++++++++++++++++---- hw/opentitan/trace-events | 2 ++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 05ceb0ef28d0f..2ad0b118220d3 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1071,22 +1071,47 @@ static const char *ot_otp_hexdump(const void *data, size_t size) static void ot_otp_dj_update_irqs(OtOTPDjState *s) { - uint32_t level = s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE]; - - level |= s->alert_bm; + uint32_t levels = s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE]; for (unsigned ix = 0; ix < ARRAY_SIZE(s->irqs); ix++) { + int level = (int)(bool)(levels & (1u << ix)); + if (level != ibex_irq_get_level(&s->irqs[ix])) { + trace_ot_otp_update_irq(s->ot_id, ibex_irq_get_level(&s->irqs[ix]), + level); + } ibex_irq_set(&s->irqs[ix], (int)((level >> ix) & 0x1)); } } static void ot_otp_dj_update_alerts(OtOTPDjState *s) { - uint32_t level = s->regs[R_ALERT_TEST]; + uint32_t levels = s->regs[R_ALERT_TEST]; + + levels |= s->alert_bm; for (unsigned ix = 0; ix < ARRAY_SIZE(s->alerts); ix++) { + int level = (int)(bool)(levels & (1u << ix)); + if (level != ibex_irq_get_level(&s->alerts[ix])) { + trace_ot_otp_update_alert(s->ot_id, + ibex_irq_get_level(&s->alerts[ix]), + level); + } ibex_irq_set(&s->alerts[ix], (int)((level >> ix) & 0x1u)); } + + /* alert test is transient */ + if (s->regs[R_ALERT_TEST]) { + s->regs[R_ALERT_TEST] = 0; + + levels = s->alert_bm; + for (unsigned ix = 0; ix < ARRAY_SIZE(s->alerts); ix++) { + int level = (int)(bool)(levels & (1u << ix)); + trace_ot_otp_update_alert(s->ot_id, + ibex_irq_get_level(&s->alerts[ix]), + level); + ibex_irq_set(&s->alerts[ix], (int)((level >> ix) & 0x1u)); + } + } } static bool ot_otp_dj_is_wide_granule(int partition, unsigned address) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 749643aac6f92..fe3826a42202e 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -359,6 +359,8 @@ ot_otp_reset(const char * id, const char *phase) "%s: %s" ot_otp_set_error(const char * id, const char *part, unsigned pix, const char* err, unsigned eix) "%s: %s (#%u): %s (%u)" ot_otp_skip_digest(const char * id, const char* part, unsigned pix) "%s: skipping empty digest on %s (#%u)" ot_otp_sram_key_generated(const char * id) "%s" +ot_otp_update_irq(const char * id, int prev, int next) "%s: %d -> %d" +ot_otp_update_alert(const char * id, int prev, int next) "%s: %d -> %d" # ot_otp_ot_be.c From 543b103de7901b22605f88cc755f76732dab5d92 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 16:17:31 +0200 Subject: [PATCH 049/175] [ot] hw/opentitan: ot_otp_dj: add DAI status on DAI request denial Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 2ad0b118220d3..6476512d83e70 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1849,8 +1849,8 @@ static void ot_otp_dj_dai_clear_error(OtOTPDjState *s) static void ot_otp_dj_dai_read(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", - __func__, s->ot_id); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy: %s\n", + __func__, s->ot_id, DAI_STATE_NAME(s->dai->state)); return; } @@ -2068,8 +2068,8 @@ static int ot_otp_dj_dai_write_u32(OtOTPDjState *s, unsigned address) static void ot_otp_dj_dai_write(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", - __func__, s->ot_id); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy: %s\n", + __func__, s->ot_id, DAI_STATE_NAME(s->dai->state)); return; } @@ -2162,8 +2162,8 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) static void ot_otp_dj_dai_digest(OtOTPDjState *s) { if (ot_otp_dj_dai_is_busy(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy\n", - __func__, s->ot_id); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: DAI controller busy: %s\n", + __func__, s->ot_id, DAI_STATE_NAME(s->dai->state)); return; } From 7f093d9ecb4cafcea10f1a9dfa692164285af586 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 16:16:17 +0200 Subject: [PATCH 050/175] [ot] hw/opentitan: ot_otp_dj: fix clang-format issue Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 48 ++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 6476512d83e70..a547345a96529 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -827,6 +827,7 @@ struct OtOTPDjState { #define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) static const char *REG_NAMES[REGS_COUNT] = { + /* clang-format off */ REG_NAME_ENTRY(INTR_STATE), REG_NAME_ENTRY(INTR_ENABLE), REG_NAME_ENTRY(INTR_TEST), @@ -922,37 +923,57 @@ static const char *REG_NAMES[REGS_COUNT] = { REG_NAME_ENTRY(SECRET2_DIGEST_1), REG_NAME_ENTRY(SECRET3_DIGEST_0), REG_NAME_ENTRY(SECRET3_DIGEST_1), + /* clang-format on */ }; #undef REG_NAME_ENTRY #define OTP_NAME_ENTRY(_st_) [_st_] = stringify(_st_) static const char *DAI_STATE_NAMES[] = { - OTP_NAME_ENTRY(OTP_DAI_RESET), OTP_NAME_ENTRY(OTP_DAI_INIT_OTP), - OTP_NAME_ENTRY(OTP_DAI_INIT_PART), OTP_NAME_ENTRY(OTP_DAI_IDLE), - OTP_NAME_ENTRY(OTP_DAI_ERROR), OTP_NAME_ENTRY(OTP_DAI_READ), - OTP_NAME_ENTRY(OTP_DAI_READ_WAIT), OTP_NAME_ENTRY(OTP_DAI_DESCR), - OTP_NAME_ENTRY(OTP_DAI_DESCR_WAIT), OTP_NAME_ENTRY(OTP_DAI_WRITE), - OTP_NAME_ENTRY(OTP_DAI_WRITE_WAIT), OTP_NAME_ENTRY(OTP_DAI_SCR), - OTP_NAME_ENTRY(OTP_DAI_SCR_WAIT), OTP_NAME_ENTRY(OTP_DAI_DIG_CLR), - OTP_NAME_ENTRY(OTP_DAI_DIG_READ), OTP_NAME_ENTRY(OTP_DAI_DIG_READ_WAIT), - OTP_NAME_ENTRY(OTP_DAI_DIG), OTP_NAME_ENTRY(OTP_DAI_DIG_PAD), - OTP_NAME_ENTRY(OTP_DAI_DIG_FIN), OTP_NAME_ENTRY(OTP_DAI_DIG_WAIT), + /* clang-format off */ + OTP_NAME_ENTRY(OTP_DAI_RESET), + OTP_NAME_ENTRY(OTP_DAI_INIT_OTP), + OTP_NAME_ENTRY(OTP_DAI_INIT_PART), + OTP_NAME_ENTRY(OTP_DAI_IDLE), + OTP_NAME_ENTRY(OTP_DAI_ERROR), + OTP_NAME_ENTRY(OTP_DAI_READ), + OTP_NAME_ENTRY(OTP_DAI_READ_WAIT), + OTP_NAME_ENTRY(OTP_DAI_DESCR), + OTP_NAME_ENTRY(OTP_DAI_DESCR_WAIT), + OTP_NAME_ENTRY(OTP_DAI_WRITE), + OTP_NAME_ENTRY(OTP_DAI_WRITE_WAIT), + OTP_NAME_ENTRY(OTP_DAI_SCR), + OTP_NAME_ENTRY(OTP_DAI_SCR_WAIT), + OTP_NAME_ENTRY(OTP_DAI_DIG_CLR), + OTP_NAME_ENTRY(OTP_DAI_DIG_READ), + OTP_NAME_ENTRY(OTP_DAI_DIG_READ_WAIT), + OTP_NAME_ENTRY(OTP_DAI_DIG), + OTP_NAME_ENTRY(OTP_DAI_DIG_PAD), + OTP_NAME_ENTRY(OTP_DAI_DIG_FIN), + OTP_NAME_ENTRY(OTP_DAI_DIG_WAIT), + /* clang-format on */ }; static const char *LCI_STATE_NAMES[] = { - OTP_NAME_ENTRY(OTP_LCI_RESET), OTP_NAME_ENTRY(OTP_LCI_IDLE), - OTP_NAME_ENTRY(OTP_LCI_WRITE), OTP_NAME_ENTRY(OTP_LCI_WRITE_WAIT), + /* clang-format off */ + OTP_NAME_ENTRY(OTP_LCI_RESET), + OTP_NAME_ENTRY(OTP_LCI_IDLE), + OTP_NAME_ENTRY(OTP_LCI_WRITE), + OTP_NAME_ENTRY(OTP_LCI_WRITE_WAIT), OTP_NAME_ENTRY(OTP_LCI_ERROR), + /* clang-format on */ }; static const char *OTP_TOKEN_NAMES[] = { + /* clang-format off */ OTP_NAME_ENTRY(OTP_TOKEN_TEST_UNLOCK), OTP_NAME_ENTRY(OTP_TOKEN_TEST_EXIT), OTP_NAME_ENTRY(OTP_TOKEN_RMA), + /* clang-format on */ }; static const char *PART_NAMES[] = { + /* clang-format off */ OTP_NAME_ENTRY(OTP_PART_VENDOR_TEST), OTP_NAME_ENTRY(OTP_PART_CREATOR_SW_CFG), OTP_NAME_ENTRY(OTP_PART_OWNER_SW_CFG), @@ -978,9 +999,11 @@ static const char *PART_NAMES[] = { /* fake partitions */ OTP_NAME_ENTRY(OTP_ENTRY_DAI), OTP_NAME_ENTRY(OTP_ENTRY_KDI), + /* clang-format on */ }; static const char *ERR_CODE_NAMES[] = { + /* clang-format off */ OTP_NAME_ENTRY(OTP_NO_ERROR), OTP_NAME_ENTRY(OTP_MACRO_ERROR), OTP_NAME_ENTRY(OTP_MACRO_ECC_CORR_ERROR), @@ -989,6 +1012,7 @@ static const char *ERR_CODE_NAMES[] = { OTP_NAME_ENTRY(OTP_ACCESS_ERROR), OTP_NAME_ENTRY(OTP_CHECK_FAIL_ERROR), OTP_NAME_ENTRY(OTP_FSM_STATE_ERROR), + /* clang-format on */ }; /* clang-format on */ From b498aec39d8f3673cde9c70e47e2fffd02524374 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 16:17:21 +0200 Subject: [PATCH 051/175] [ot] hw/opentitan: ot_otp_dj: disable all partitions on fatal error Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index a547345a96529..faacd8265bc53 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1189,6 +1189,17 @@ static bool ot_otp_dj_has_digest(unsigned partition) OtOTPPartDescs[partition].sw_digest; } +static void ot_otp_dj_disable_all_partitions(OtOTPDjState *s) +{ + DAI_CHANGE_STATE(s, OTP_DAI_ERROR); + LCI_CHANGE_STATE(s, OTP_LCI_ERROR); + + for (unsigned pix = 0; pix < OTP_PART_COUNT; pix++) { + OtOTPPartController *pctrl = &s->partctrls[pix]; + pctrl->failed = true; + } +} + static void ot_otp_dj_set_error(OtOTPDjState *s, unsigned part, OtOTPError err) { /* This is it NUM_ERROR_ENTRIES */ @@ -1228,6 +1239,12 @@ static void ot_otp_dj_set_error(OtOTPDjState *s, unsigned part, OtOTPError err) break; } + if (s->alert_bm & ALERT_FATAL_CHECK_ERROR_MASK) { + ot_otp_dj_disable_all_partitions(s); + s->regs[R_INTR_STATE] |= INTR_OTP_ERROR_MASK; + error_report("%s: %s: OTP disabled on fatal error", __func__, s->ot_id); + } + if (err != OTP_NO_ERROR) { s->regs[R_INTR_STATE] |= INTR_OTP_ERROR_MASK; ot_otp_dj_update_irqs(s); @@ -1903,6 +1920,13 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) return; } + const OtOTPPartController *pctrl = &s->partctrls[partition]; + if (pctrl->failed) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: partition %s is disabled\n", + __func__, s->ot_id, PART_NAME(partition)); + return; + } + bool is_digest = ot_otp_dj_is_part_digest_offset(partition, address); bool is_readable = ot_otp_dj_is_readable(s, partition); bool is_wide = ot_otp_dj_is_wide_granule(partition, address); @@ -2133,6 +2157,12 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) OtOTPPartController *pctrl = &s->partctrls[partition]; + if (pctrl->failed) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: partition %s is disabled\n", + __func__, s->ot_id, PART_NAME(partition)); + return; + } + if (pctrl->locked) { qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: partition %s (%u) is locked\n", __func__, s->ot_id, PART_NAME(partition), partition); @@ -2235,6 +2265,12 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) OtOTPPartController *pctrl = &s->partctrls[partition]; + if (pctrl->failed) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: partition %s is disabled\n", + __func__, s->ot_id, PART_NAME(partition)); + return; + } + if (pctrl->locked) { qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Partition %s (%u) is locked\n", __func__, s->ot_id, PART_NAME(partition), partition); From 60b6ca714e4e74deeabb9fc497e2cb2de0bb95e5 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 16:18:02 +0200 Subject: [PATCH 052/175] [ot] hw/opentitan: ot_otp_dj: add an option to early abort VM on LC escalation Signed-off-by: Emmanuel Blot --- docs/opentitan/darjeeling.md | 3 +++ hw/opentitan/ot_otp_dj.c | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/docs/opentitan/darjeeling.md b/docs/opentitan/darjeeling.md index d47c12fb53d15..cc89a161fea29 100644 --- a/docs/opentitan/darjeeling.md +++ b/docs/opentitan/darjeeling.md @@ -231,6 +231,9 @@ There are two modes to handle address remapping, with different limitations: file used as the OpenTitan OTP image. This _RAW_ file should have been generated with the [`otptool.py`](otptool.md) tool. +* on LC escalate reception, it is possible to early abort VM execution. Specify + `-global ot-otp-dj.fatal_escalate=true` to enable this feature. + ### SoC Debug controller SoC debug controller manages SoC debug policies based on external signals - such as GPIO, Power diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index faacd8265bc53..af6cf8b71baef 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -823,6 +823,7 @@ struct OtOTPDjState { char *sram_const_xstr; char *sram_iv_xstr; uint8_t edn_ep; + bool fatal_escalate; }; #define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) @@ -1690,6 +1691,9 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) qemu_log_mask(LOG_UNIMP, "%s: %s: ESCALATE partially implemented\n", __func__, s->ot_id); + if (s->fatal_escalate) { + error_setg(&error_fatal, "%s: OTP LC escalate", s->ot_id); + } } break; case OT_OTP_LC_CHECK_BYP_EN: @@ -3931,6 +3935,7 @@ static Property ot_otp_dj_properties[] = { DEFINE_PROP_STRING("digest_iv", OtOTPDjState, digest_iv_xstr), DEFINE_PROP_STRING("sram_const", OtOTPDjState, sram_const_xstr), DEFINE_PROP_STRING("sram_iv", OtOTPDjState, sram_iv_xstr), + DEFINE_PROP_BOOL("fatal_escalate", OtOTPDjState, fatal_escalate, false), DEFINE_PROP_END_OF_LIST(), }; From 6aa29d12d8cf0d6ba25e6cf7ade46b2a59c519c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 17:14:01 +0200 Subject: [PATCH 053/175] [ot] python/qemu: ot.pyot.executer: be sure to flush all sub-loggers Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index dd7c1695e9c3c..c44696161b567 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -194,6 +194,9 @@ def run(self, debug: bool, allow_no_test: bool) -> int: finally: self._qfm.cleanup_transient() flush_memory_loggers(['pyot', 'pyot.vcp'], LOG_INFO) + flush_memory_loggers(['pyot', 'pyot.vcp', 'pyot.ctx', + 'pyot.file'], + LOG_INFO) results[tret] += 1 sret = self.RESULT_MAP.get(tret, tret) try: From fd41af16e0fbc58acf6cc4dc0792fae412040ece Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Jun 2025 17:49:51 +0200 Subject: [PATCH 054/175] [ot] hw/riscv: ot_darjeeling: fix indentation Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 2a58711348674..31e5ab40ee482 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -754,9 +754,9 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_DJ_SOC_GPIO_ALERT(0, 72), OT_DJ_SOC_SIGNAL(OT_ROM_CTRL_GOOD, 0, PWRMGR, - OT_PWRMGR_ROM_GOOD, 0), + OT_PWRMGR_ROM_GOOD, 0), OT_DJ_SOC_SIGNAL(OT_ROM_CTRL_DONE, 0, PWRMGR, - OT_PWRMGR_ROM_DONE, 0) + OT_PWRMGR_ROM_DONE, 0) ), .link = IBEXDEVICELINKDEFS( OT_DJ_SOC_DEVLINK("kmac", KMAC) @@ -778,9 +778,9 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_DJ_SOC_GPIO_ALERT(0, 73), OT_DJ_SOC_SIGNAL(OT_ROM_CTRL_GOOD, 0, PWRMGR, - OT_PWRMGR_ROM_GOOD, 1), + OT_PWRMGR_ROM_GOOD, 1), OT_DJ_SOC_SIGNAL(OT_ROM_CTRL_DONE, 0, PWRMGR, - OT_PWRMGR_ROM_DONE, 1) + OT_PWRMGR_ROM_DONE, 1) ), .link = IBEXDEVICELINKDEFS( OT_DJ_SOC_DEVLINK("kmac", KMAC) @@ -1127,26 +1127,21 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(0, 10), OT_DJ_SOC_GPIO_ALERT(1, 11), OT_DJ_SOC_GPIO_ALERT(2, 12), - OT_DJ_SOC_D2S(OT_LC_BROADCAST, OT_LC_HW_DEBUG_EN, - LC_HW_DEBUG), - OT_DJ_SOC_D2S(OT_LC_BROADCAST, OT_LC_ESCALATE_EN, - LC_ESCALATE), - OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CPU_EN, - IBEX_WRAPPER, OT_IBEX_WRAPPER_CPU_EN, - OT_IBEX_LC_CTRL_CPU_EN), - OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CHECK_BYP_EN, - OTP_CTRL, OT_LC_BROADCAST, - OT_OTP_LC_CHECK_BYP_EN), - OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, - OT_LC_CREATOR_SEED_SW_RW_EN, - OTP_CTRL, OT_LC_BROADCAST, - OT_OTP_LC_CREATOR_SEED_SW_RW_EN), + OT_DJ_SOC_D2S(OT_LC_BROADCAST, OT_LC_HW_DEBUG_EN, LC_HW_DEBUG), + OT_DJ_SOC_D2S(OT_LC_BROADCAST, OT_LC_ESCALATE_EN, LC_ESCALATE), + OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CPU_EN, IBEX_WRAPPER, + OT_IBEX_WRAPPER_CPU_EN, OT_IBEX_LC_CTRL_CPU_EN), + OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CHECK_BYP_EN, OTP_CTRL, + OT_LC_BROADCAST, OT_OTP_LC_CHECK_BYP_EN), + OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CREATOR_SEED_SW_RW_EN, + OTP_CTRL, OT_LC_BROADCAST, + OT_OTP_LC_CREATOR_SEED_SW_RW_EN), OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_OWNER_SEED_SW_RW_EN, - OTP_CTRL, OT_LC_BROADCAST, - OT_OTP_LC_OWNER_SEED_SW_RW_EN), + OTP_CTRL, OT_LC_BROADCAST, + OT_OTP_LC_OWNER_SEED_SW_RW_EN), OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_SEED_HW_RD_EN, - OTP_CTRL, OT_LC_BROADCAST, - OT_OTP_LC_SEED_HW_RD_EN), + OTP_CTRL, OT_LC_BROADCAST, + OT_OTP_LC_SEED_HW_RD_EN), OT_DJ_SOC_RSP(OT_PWRMGR_LC, PWRMGR) ), .link = IBEXDEVICELINKDEFS( From 917dc36a770aceebc31ef42b5f5b15ace00a9697 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 6 Jun 2025 12:13:59 +0200 Subject: [PATCH 055/175] [ot] scripts/opentitan: tktool.py: add a new tool to manage LC tokens Signed-off-by: Emmanuel Blot --- python/qemu/ot/lc_ctrl/tools.py | 158 ++++++++++++++++++++++++++++++++ scripts/opentitan/tktool.py | 112 ++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 python/qemu/ot/lc_ctrl/tools.py create mode 100755 scripts/opentitan/tktool.py diff --git a/python/qemu/ot/lc_ctrl/tools.py b/python/qemu/ot/lc_ctrl/tools.py new file mode 100644 index 0000000000000..134c2fd24caac --- /dev/null +++ b/python/qemu/ot/lc_ctrl/tools.py @@ -0,0 +1,158 @@ +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""LifeCycle Controller utilities. + + :author: Emmanuel Blot +""" + +from binascii import hexlify +from logging import getLogger +from io import StringIO +from random import randbytes +from typing import NamedTuple + +import re + +try: + from Crypto.Hash import cSHAKE128 +except ModuleNotFoundError: + # see pip3 install -r requirements.txt + from Cryptodome.Hash import cSHAKE128 + + +class LifeCycleTokenPair(NamedTuple): + """Life Cycle token pair""" + + text: bytes + hashed: bytes + + +class LifeCycleTokenEngine: + """Life Controller token management. + """ + + TOKEN_LENGTH = 16 + """Token length in bytes.""" + + TOKEN_CUSTOM = 'LC_CTRL' + """Token customisation string.""" + + TOKEN_VAR_NAME = 'LC_{var}_TOKEN{hashed}' + """Variable name template used to store a token value.""" + + def __init__(self): + self._log = getLogger('lc.token') + + def generate(self) -> LifeCycleTokenPair: + """Generate a random token pair (clear, hashed) + + :note: random quality is not suitable for production use + """ + token_text = randbytes(self.TOKEN_LENGTH) + token_hash = self.hash(token_text) + return LifeCycleTokenPair(token_text, token_hash) + + def hash(self, token: bytes) -> bytes: + """Hash a plain text token. + + :param token: token to hash + :return: the hashed tocken + """ + cshake = cSHAKE128.new(custom=self.TOKEN_CUSTOM.encode()) + # convert natural byte order to little order + # (human readable, left to right, MSB to LSB) to LE + token_le = bytes(reversed(token)) + cshake.update(token_le) + hash_le = cshake.read(self.TOKEN_LENGTH) + # convert back to natural byte order (from LE to human) + return bytes(reversed(hash_le)) + + def build_from_text(self, token: bytes) -> LifeCycleTokenPair: + """Build a token pair from a clear token. + + :param token: clear token + :return: the token pair + """ + return LifeCycleTokenPair(token, self.hash(token)) + + @classmethod + def build_rust_def(cls, name: str, value: bytes) -> str: + """Build Rust definition for a token. + + :param name: the token name radix + :param value: the token value + :return: the generated definition + """ + code = StringIO() + print(f'// {hexlify(value).decode()}', file=code) + print(f'const {name.upper()}: [u8; {len(value)}] = [', file=code) + value = bytes(value) + for pos in range(0, len(value), 8): + s = ', '.join((f'0x{c:02x}' for c in value[pos:pos+8])) + print(f' {s},', file=code) + print('];', file=code) + return code.getvalue() + + @classmethod + def build_qemu_def(cls, name: str, value: bytes) -> str: + """Build QEMU definition for a token. + + :param name: the token name radix + :param value: the token value + :return: the generated definition + """ + code = StringIO() + print(f'// {hexlify(value).decode()}', file=code) + print(f'static const OtOTPTokenValue {name.upper()} = {{', file=code) + print(f' .hi = 0x{int.from_bytes(value[:8], "big"):016x}u,' + f' .lo = 0x{int.from_bytes(value[8:], "big"):016x}u,', + file=code) + print('}};', file=code) + return code.getvalue() + + def generate_code(self, lang: str, name: str, tkpair: LifeCycleTokenPair) \ + -> str: + """Generate code to store a token pair. + + :param lang: the output type + :param name: the token name radix + :param value: the token value + :return: the generated code + """ + builder = getattr(self, f'build_{lang}_def', None) + if not builder and not callable(builder): + raise ValueError('Unsupported language') + # pylint: disable=not-callable + lines = ( + builder(self.TOKEN_VAR_NAME.format(var=name, hashed=''), + tkpair.text), + builder(self.TOKEN_VAR_NAME.format(var=name, hashed='_HASHED'), + tkpair.hashed), + '' + ) + return '\n'.join(lines) + + def parse_rust(self, rust_code: str) -> dict[str, LifeCycleTokenPair]: + """Parse Rust code and extract Token definitions. + + :param rust_code: rust code to parse + :return: a dictionary of token pair, indexed by name + """ + var_re = self.TOKEN_VAR_NAME.format(var='(.*)', hashed='(_HASHED)?') + rcre = re.compile(r'const ' + var_re + r': *\[u8; +16\] *= ' + r'*\[((?:[\s\n]+0x[0-9a-fA-F]{2},){16})(?:[\s\n]+)];') + tokens: dict[str, list[bytes, bytes]] = {} + for rmo in rcre.finditer(rust_code): + token = rmo.group(1) + hashed = bool(rmo.group(2)) + seq = re.sub(r'(0x|\s|,)', '', rmo.group(3)) + if token not in tokens: + tokens[token] = [bytes(0), bytes(0)] + pos = int(hashed) + if tokens[token][pos]: + raise ValueError(f'Redefinition of ' + f'{"hashed" if hashed else "plain"} {token}') + tokens[token][pos] = int(seq, 16).to_bytes(self.TOKEN_LENGTH) + tkpairs = {n: LifeCycleTokenPair(*v) for n, v in tokens.items()} + return tkpairs diff --git a/scripts/opentitan/tktool.py b/scripts/opentitan/tktool.py new file mode 100755 index 0000000000000..abf22fb280238 --- /dev/null +++ b/scripts/opentitan/tktool.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""LifeCycle Controller tiny token tools + + :author: Emmanuel Blot +""" + +from argparse import ArgumentParser, FileType +from binascii import hexlify, unhexlify +from os.path import dirname, join as joinpath, normpath +from traceback import format_exception +from typing import Optional + +import sys + +QEMU_PYPATH = joinpath(dirname(dirname(dirname(normpath(__file__)))), + 'python', 'qemu') +sys.path.append(QEMU_PYPATH) + +# ruff: noqa: E402 +from ot.util.log import configure_loggers + +_IEXC: Optional[Exception] = None +try: + from ot.lc_ctrl.tools import LifeCycleTokenEngine +except ImportError as _IEXC: + pass + + +def main(): + """Main routine""" + debug = True + try: + desc = sys.modules[__name__].__doc__.split('.', 1)[0].strip() + argparser = ArgumentParser(description=f'{desc}.') + argparser.add_argument('-s', '--hash', metavar='TOKEN', + help='hash the submitted token') + argparser.add_argument('-g', '--generate', metavar='TOKEN_NAME', + help='generate a new token pair') + argparser.add_argument('-r', '--parse-rust', metavar='FILE', + type=FileType('rt'), + help='parse token from a Rust file') + argparser.add_argument('-t', '--token', metavar='NAME', + help='only report named token') + extra = argparser.add_argument_group(title='Extras') + extra.add_argument('-v', '--verbose', action='count', + help='increase verbosity') + extra.add_argument('-d', '--debug', action='store_true', + help='enable debug mode') + + args = argparser.parse_args() + debug = args.debug + + configure_loggers(args.verbose, 'tkgen', -1, 'otp') + + if _IEXC is not None: + if debug: + print(''.join(format_exception(_IEXC, chain=False)), + file=sys.stderr) + argparser.error(f'Missing PYTHONPATH: {_IEXC}') + + tkeng = LifeCycleTokenEngine() + + if args.hash: + token = unhexlify(args.hash) + hashed_str = hexlify(tkeng.hash(token)).decode() + # try to follow the same case for the result as the input + if args.hash == args.hash.upper(): + hashed_str = hashed_str.upper() + print(hashed_str) + + if args.generate: + tkpair = tkeng.generate() + prefix = args.generate.upper() + rust_code = tkeng.generate_code('rust', prefix, tkpair) + print(rust_code) + qemu_code = tkeng.generate_code('qemu', prefix, tkpair) + print(qemu_code) + + if args.token and not args.parse_rust: + argparser.error('Token name requires Rust file') + + if args.parse_rust: + rust = args.parse_rust.read() + args.parse_rust.close() + tkpairs = tkeng.parse_rust(rust) + if args.token: + token_name = args.token.upper() + if token_name not in tkpairs: + argparser.error('No such token') + print(hexlify(tkpairs[token_name].text).decode().upper()) + else: + for name, tkpair in tkpairs.items(): + for kind in tkpair.__annotations__: + print(f'LC_{name}_TOKEN_{kind.upper()}=' + f'{hexlify(getattr(tkpair, kind)).decode()}') + print() + + except (IOError, ValueError, ImportError) as exc: + print(f'\nError: {exc}', file=sys.stderr) + if debug: + print(''.join(format_exception(exc, chain=False)), file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(2) + + +if __name__ == '__main__': + main() From d8a562c11febb1a9b3abac7fe45aed58b0dbb921 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 6 Jun 2025 16:35:37 +0200 Subject: [PATCH 056/175] [ot] scripts/opentitan: otptool.py: fix invalid class names Signed-off-by: Emmanuel Blot --- python/qemu/ot/otp/__init__.py | 2 +- python/qemu/ot/otp/descriptor.py | 4 ++-- scripts/opentitan/otptool.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/qemu/ot/otp/__init__.py b/python/qemu/ot/otp/__init__.py index 8f820e1f88e3d..0d512184f8fa6 100644 --- a/python/qemu/ot/otp/__init__.py +++ b/python/qemu/ot/otp/__init__.py @@ -3,7 +3,7 @@ """One-Time Programmable controller.""" -from .descriptor import OTPPartitionDesc, OTPRegisterDef # noqa: F401 +from .descriptor import OtpPartitionDesc, OtpRegisterDef # noqa: F401 from .image import OtpImage # noqa: F401 from .map import OtpMap # noqa: F401 from .partition import OtpLifecycleExtension, OtpPartition # noqa: F401 diff --git a/python/qemu/ot/otp/descriptor.py b/python/qemu/ot/otp/descriptor.py index ab6aa724d82ad..d9586e929389b 100644 --- a/python/qemu/ot/otp/descriptor.py +++ b/python/qemu/ot/otp/descriptor.py @@ -13,7 +13,7 @@ from .map import OtpMap -class OTPPartitionDesc: +class OtpPartitionDesc: """OTP Partition descriptor generator.""" ATTRS = { @@ -111,7 +111,7 @@ def _convert_to_rlock(cls, value) -> list[tuple[str, bool]]: assert False, 'Unknown RLOCK type' -class OTPRegisterDef: +class OtpRegisterDef: """OTP Partition register generator.""" def __init__(self, otpmap: 'OtpMap'): diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index a9cda2ab6a74a..18ebe4a848d62 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -21,7 +21,7 @@ sys.path.append(QEMU_PYPATH) from ot.otp import (OtpImage, OtpLifecycleExtension, OtpMap, - OTPPartitionDesc, OTPRegisterDef) + OtpPartitionDesc, OtpRegisterDef) from ot.util.log import configure_loggers from ot.util.misc import HexInt, to_bool @@ -153,7 +153,7 @@ def main(): otpmap: Optional[OtpMap] = None lcext: Optional[OtpLifecycleExtension] = None - partdesc: Optional[OTPPartitionDesc] = None + partdesc: Optional[OtpPartitionDesc] = None if not args.otp_map: if args.generate in ('PARTS', 'REGS'): @@ -185,11 +185,11 @@ def main(): if not args.generate: pass elif args.generate == 'PARTS': - partdesc = OTPPartitionDesc(otpmap) + partdesc = OtpPartitionDesc(otpmap) partdesc.save(basename(args.otp_map.name), basename(sys.argv[0]), output) elif args.generate == 'REGS': - regdef = OTPRegisterDef(otpmap) + regdef = OtpRegisterDef(otpmap) regdef.save(basename(args.otp_map.name), basename(sys.argv[0]), output) elif args.generate == 'LCVAL': From 30fe854ef96ca316607018d135db121b57082a9f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 6 Jun 2025 16:02:46 +0200 Subject: [PATCH 057/175] [ot] scripts/opentitan: otptool.py: add LC token update feature. Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 14 ++++- python/qemu/ot/otp/map.py | 8 +++ python/qemu/ot/otp/partition.py | 12 ++++ scripts/opentitan/otptool.py | 98 +++++++++++++++++++++++++----- scripts/opentitan/requirements.txt | 2 +- 5 files changed, 116 insertions(+), 18 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 80a7c3729f880..456616537929d 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -12,7 +12,8 @@ usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW] [-g {LCVAL,LCTPL,PARTS,REGS}] [-F] [-G PART] [--change PART:FIELD=VALUE] [--empty PARTITION] [--erase PART:FIELD] [--clear-bit CLEAR_BIT] - [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [-v] [-d] + [--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] + [--patch-token NAME=VALUE] [-v] [-d] QEMU OT tool to manage OTP files. @@ -59,6 +60,8 @@ Commands: --set-bit SET_BIT set a bit at specified location --toggle-bit TOGGLE_BIT toggle a bit at specified location + --patch-token NAME=VALUE + change a LC hashed token, using Rust file Extras: -v, --verbose increase verbosity @@ -197,6 +200,15 @@ Fuse RAW images only use the v1 type. * `--no-version` disable OTP image version reporting when `-s` is used. +* `--patch-token` patch a Life Cycle hashed token. This feature is primary aimed at testing the + Life Cycle controller. With this option, the partition to update is automatically found using + the token `NAME`. If the partition containing the token to update is already locked, its digest is + automatically patched; otherwise the digest is left empty. The token `VALUE` should be specified + as a hexadecimal string with no `0x` prefix. The token value is expected to be the plaintext + token, as the script takes care of hashing it and storing the hashed value into the OTP image. + To change a LC token file with an immediate value with no further processing, see the `--change` + option. + * `--set-bit` sets the specified bit in the OTP data. This flag may be repeated. This option is only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit. diff --git a/python/qemu/ot/otp/map.py b/python/qemu/ot/otp/map.py index 6fcbf8a285aef..718a0dd84ad0a 100644 --- a/python/qemu/ot/otp/map.py +++ b/python/qemu/ot/otp/map.py @@ -69,6 +69,14 @@ def enumerate_partitions(self) -> Iterator['OtpPartition']: """Enumerate the partitions in their address order.""" return iter(self._partitions) + def find_field(self, field: str) -> list['OtpPartition']: + """Find in which partition(s) a field is defined. + + :param name: the name of the field to locate + :return: a list of partitions which contains such a field + """ + return [p for p in self.enumerate_partitions() if p.has_field(field)] + def _generate_partitions(self) -> None: parts = self._map.get('partitions', []) have_offset = all('offset' in p for p in parts) diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index 39cd48dd63d4b..215bd3c9c78f2 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -320,6 +320,18 @@ def build_digest(self, digest_iv: int, digest_constant: int, erase: bool) \ self._digest_bytes = bytes((a | b) for a, b in zip(self._digest_bytes, bdigest)) + def has_field(self, field: str) -> bool: + """Tell whehther the partition has a field by its name. + + :param field: the name of the field to locate + :return: true if the field is defined, false otherwise + """ + try: + self._retrieve_properties(field) + return True + except ValueError: + return False + def _retrieve_properties(self, field: str) -> tuple[int, int]: is_digest = self.has_digest and field.upper() == 'DIGEST' if not is_digest: diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index 18ebe4a848d62..7914c9793d113 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -12,7 +12,7 @@ from binascii import unhexlify from os.path import basename, dirname, join as joinpath, normpath from re import match as re_match -from traceback import format_exc +from traceback import format_exception from typing import Optional import sys @@ -20,12 +20,36 @@ 'python', 'qemu') sys.path.append(QEMU_PYPATH) -from ot.otp import (OtpImage, OtpLifecycleExtension, OtpMap, +# ruff: noqa: E402 +_EXC: Optional[Exception] = None +try: + from ot.lc_ctrl.tools import LifeCycleTokenEngine, LifeCycleTokenPair +except ModuleNotFoundError as exc: + _EXC = exc +from ot.otp import (OtpImage, OtpLifecycleExtension, OtpMap, OtpPartition, OtpPartitionDesc, OtpRegisterDef) from ot.util.log import configure_loggers from ot.util.misc import HexInt, to_bool +def parse_lc_token(tkdesc: str) -> tuple[str, 'LifeCycleTokenPair']: + """Parse a Rust file for a LC token and return its name and VALUE + + :param tkdesc: argument parser input string + :return: a 2-uple of OTP map token name and LifeCycleTokenPair + """ + token_desc, token_val = tkdesc.split('=', 1) + token_name = token_desc.upper() + tkeng = LifeCycleTokenEngine() + try: + tktext = unhexlify(token_val) + if len(tktext) != tkeng.TOKEN_LENGTH: + raise ValueError() + except ValueError as exc: + raise ValueError(f"No valid token '{token_name}'") from exc + return token_name, tkeng.build_from_text(tktext) + + def main(): """Main routine""" debug = True @@ -106,6 +130,9 @@ def main(): help='set a bit at specified location') commands.add_argument('--toggle-bit', action='append', default=[], help='toggle a bit at specified location') + commands.add_argument('--patch-token', action='append', + metavar='NAME=VALUE', default=[], + help='change a LC hashed token, using Rust file') extra = argparser.add_argument_group(title='Extras') extra.add_argument('-v', '--verbose', action='count', help='increase verbosity') @@ -158,18 +185,11 @@ def main(): if not args.otp_map: if args.generate in ('PARTS', 'REGS'): argparser.error('Generator requires an OTP map') - if args.show: - argparser.error('Cannot decode OTP values without an OTP map') - if args.digest: - argparser.error('Cannot verify OTP digests without an OTP map') - if args.empty: - argparser.error('Cannot empty OTP partition without an OTP map') - if args.change: - argparser.error('Cannot change an OTP field without an OTP map') - if args.erase: - argparser.error('Cannot erase an OTP field without an OTP map') - if args.fix_digest: - argparser.error('Cannot generate HW digest without an OTP map') + for feat in ('show', 'digest', 'empty', 'change', 'erase', + 'fix_digest', 'patch_token'): + if not getattr(args, feat): + continue + argparser.error('Specified option requires an OTP map') else: otpmap = OtpMap() otpmap.load(args.otp_map) @@ -216,6 +236,7 @@ def main(): otp.verify_ecc(args.ecc_recover) if otp.loaded: + if not otp.is_opentitan: ot_opts = ('iv', 'constant', 'digest', 'generate', 'otp_map', 'lifecycle') @@ -228,6 +249,7 @@ def main(): if args.empty: for part in args.empty: otp.empty_partition(part) + for field_desc in args.erase: try: part, field = field_desc.split(':') @@ -238,6 +260,7 @@ def main(): ': syntax') otp.erase_field(part, field) check_update = True + for chg_desc in args.change: try: fdesc, value = chg_desc.split('=') @@ -271,12 +294,47 @@ def main(): argparser.error(f'Unknown value type: {value}') otp.change_field(part, field, value) check_update = True + if args.config: otp.load_config(args.config) + if args.iv: otp.set_digest_iv(args.iv) + if args.constant: otp.set_digest_constant(args.constant) + + token_parts: set[OtpPartition] = set() + + if args.patch_token and _EXC: + if debug: + print(''.join(format_exception(_EXC, chain=False)), + file=sys.stderr) + argparser.error(f'Missing PYTHONPATH: {_EXC}') + for tkdesc in args.patch_token: + if '=' not in tkdesc: + argparser.error(f"Invalid token syntax: '{tkdesc}'") + token, value = parse_lc_token(tkdesc) + token_name = f'{token}_TOKEN' + token_parts = otpmap.find_field(token_name) + if len(token_parts) == 0: + argparser.error(f"No such token '{token}' in OTP map") + if len(token_parts) > 1: + argparser.error( + f"Token '{token}' found in multiple partitions " + f"{', '.join(p.name for p in token_parts)}") + part = token_parts[0] + # no sure why we need to revert the bytes here + # could be due to any other inversion in any other piece of SW + # ... + token_hash = bytes(reversed(value.hashed)) + otp.change_field(part.name, token_name, token_hash) + if part.is_locked: + token_parts.add(part.name) + check_update = True + for part in token_parts: + otp.build_digest(part.name, True) + if args.digest or args.fix_digest: if not otp.has_present_constants: if args.raw and otp.version == 1: @@ -287,16 +345,23 @@ def main(): # image argparser.error(f'Present scrambler constants are required ' f'to handle the partition digest{msg}') - for part in args.fix_digest: + build_digest_parts = {p for p in args.fix_digest if p != 'tokens'} + if 'tokens' in args.fix_digest: + build_digest_parts.update((p.name for p in token_parts)) + for part in build_digest_parts: otp.build_digest(part, True) check_update = True + if lcext: otp.load_lifecycle(lcext) + if args.show: otp.decode(not args.no_decode, args.wide, output, not args.no_version, args.filter) + if args.digest: otp.verify(True) + for pos, bitact in enumerate(bit_actions): if alter_bits[pos]: getattr(otp, f'{bitact}_bits')(alter_bits[pos]) @@ -321,7 +386,8 @@ def main(): except (IOError, ValueError, ImportError) as exc: print(f'\nError: {exc}', file=sys.stderr) if debug: - print(format_exc(chain=False), file=sys.stderr) + print(''.join(format_exception(exc, chain=False)), + file=sys.stderr) sys.exit(1) except KeyboardInterrupt: sys.exit(2) diff --git a/scripts/opentitan/requirements.txt b/scripts/opentitan/requirements.txt index b2ea25b4a7a90..d537a42678505 100644 --- a/scripts/opentitan/requirements.txt +++ b/scripts/opentitan/requirements.txt @@ -3,4 +3,4 @@ pylint>=3.3.3 pyyaml>=6 hjson>=3.1 pyelftools>=0.30 - +pycryptodome >= 3.12 From 4aa8eceaaf324f2afe22015f67f8da70bcb9cffc Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 10 Jun 2025 11:01:04 +0200 Subject: [PATCH 058/175] [ot] hw/opentitan: ot_lc_ctrl: update broadcast signals on each state change Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index a1057aec9a0b3..731b3aec0cb5a 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -562,7 +562,11 @@ static const OtLcCtrlTransitionDesc TRANSITION_DESC[LC_CTRL_TRANS_COUNT] = { "?") #define LC_FSM_CHANGE_STATE(_s_, _st_) \ - ot_lc_ctrl_change_state_line(_s_, _st_, __LINE__) + do { \ + if (ot_lc_ctrl_change_state_line(_s_, _st_, __LINE__)) { \ + ot_lc_ctrl_update_broadcast(_s_); \ + } \ + } while (0) #define LC_TOKEN_NAME(_tk_) \ (((unsigned)(_tk_)) < ARRAY_SIZE(LC_TOKEN_NAMES) ? \ @@ -721,13 +725,17 @@ static const char *ot_lc_ctrl_hexdump(const void *data, size_t size) static void ot_lc_ctrl_resume_transition(OtLcCtrlState *s); -static void +static bool ot_lc_ctrl_change_state_line(OtLcCtrlState *s, OtLcCtrlFsmState state, int line) { trace_ot_lc_ctrl_change_state(s->ot_id, line, LC_FSM_STATE_NAME(s->state), s->state, LC_FSM_STATE_NAME(state), state); + bool change = s->state != state; + s->state = state; + + return change; } static void ot_lc_ctrl_update_alerts(OtLcCtrlState *s) @@ -1383,8 +1391,12 @@ static void ot_lc_ctrl_start_transition(OtLcCtrlState *s) case LC_STATE_TESTLOCKED6: case LC_STATE_TESTUNLOCKED7: case LC_STATE_RMA: - trace_ot_lc_ctrl_info(s->ot_id, "External clock enabled"); - s->regs[R_STATUS] |= R_STATUS_EXT_CLOCK_SWITCHED_MASK; + if (s->ext_clock_en) { + trace_ot_lc_ctrl_info(s->ot_id, "using external clock"); + s->regs[R_STATUS] |= R_STATUS_EXT_CLOCK_SWITCHED_MASK; + } else { + trace_ot_lc_ctrl_info(s->ot_id, "using default clock"); + } break; default: break; From f677030a6ff9e2c59ef8c2a5a5b38ef45c2c4f09 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 10 Jun 2025 15:14:26 +0200 Subject: [PATCH 059/175] [ot] hw/opentitan: ot_otp_dj: use QEMU_CLOCK_VIRTUAL_RT for programming delay QEMU_CLOCK_VIRTUAL may stall when the CPU is maintained in reset, while the LC DMI interface may issue OTP write requests. Also replace the prog BH with the prog timer to simplify code. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index af6cf8b71baef..4da68bb62689f 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -546,11 +546,20 @@ REG32(LC_STATE, 16344u) #define REG_NAME(_reg_) \ ((((_reg_) <= REGS_COUNT) && REG_NAMES[_reg_]) ? REG_NAMES[_reg_] : "?") +/* + * The OTP may be used before any CPU is started, This may cause the default + * virtual clock to stall, as the hart does not execute. OTP nevertheless may + * be active, updating the OTP content where write delays are still needed. + * Use the alternative clock source which counts even when the CPU is stalled. + */ +#define OT_OTP_HW_CLOCK QEMU_CLOCK_VIRTUAL_RT + /* the following delays are arbitrary for now */ #define DAI_READ_DELAY_NS 100000u /* 100us */ #define DAI_WRITE_DELAY_NS 1000000u /* 1ms */ #define DAI_DIGEST_DELAY_NS 5000000u /* 5ms */ #define LCI_PROG_DELAY_NS 500000u /* 500us*/ +#define LCI_PROG_SCHED_NS 1000u /* 1us*/ #define SRAM_KEY_SEED_WIDTH (SRAM_DATA_KEY_SEED_SIZE * 8u) #define KEY_MGR_KEY_WIDTH 256u @@ -737,8 +746,7 @@ typedef struct { } OtOTPDAIController; typedef struct { - QEMUTimer *prog_delay; /* OTP cell prog delay */ - QEMUBH *prog_bh; /* OTP prog trigger */ + QEMUTimer *prog_delay; /* OTP cell prog delay (use OT_OTP_HW_CLOCK) */ OtOTPLCIState state; OtOTPError error; ot_otp_program_ack_fn ack_fn; @@ -3377,7 +3385,8 @@ static bool ot_otp_dj_program_req(OtOTPState *s, const uint16_t *lc_tcount, * schedule even if LCI FSM is already in error to report the issue * asynchronously */ - qemu_bh_schedule(lci->prog_bh); + timer_mod(lci->prog_delay, + qemu_clock_get_ns(OT_OTP_HW_CLOCK) + LCI_PROG_SCHED_NS); return true; } @@ -3522,7 +3531,7 @@ static void ot_otp_dj_lci_write_word(void *opaque) lci->hpos += 1; timer_mod(lci->prog_delay, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + LCI_PROG_DELAY_NS); + qemu_clock_get_ns(OT_OTP_HW_CLOCK) + LCI_PROG_DELAY_NS); LCI_CHANGE_STATE(s, OTP_LCI_WRITE_WAIT); } @@ -3986,7 +3995,6 @@ static void ot_otp_dj_reset_enter(Object *obj, ResetType type) } qemu_bh_cancel(s->dai->digest_bh); - qemu_bh_cancel(s->lci->prog_bh); qemu_bh_cancel(s->lc_broadcast.bh); qemu_bh_cancel(s->pwr_otp_bh); @@ -4140,8 +4148,7 @@ static void ot_otp_dj_init(Object *obj) s->dai->delay = timer_new_ns(OT_VIRTUAL_CLOCK, &ot_otp_dj_dai_complete, s); s->dai->digest_bh = qemu_bh_new(&ot_otp_dj_dai_write_digest, s); s->lci->prog_delay = - timer_new_ns(OT_VIRTUAL_CLOCK, &ot_otp_dj_lci_write_word, s); - s->lci->prog_bh = qemu_bh_new(&ot_otp_dj_lci_write_word, s); + timer_new_ns(OT_OTP_HW_CLOCK, &ot_otp_dj_lci_write_word, s); s->pwr_otp_bh = qemu_bh_new(&ot_otp_dj_pwr_otp_bh, s); s->lc_broadcast.bh = qemu_bh_new(&ot_otp_dj_lc_broadcast_bh, s); s->keygen->entropy_bh = qemu_bh_new(&ot_otp_dj_request_entropy_bh, s); From ec7c5aec4138c5ec11acda34126db5a88fb4c964 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 11 Jun 2025 15:23:35 +0200 Subject: [PATCH 060/175] [ot] hw/riscv: dm: rework dmstatus content generation Any hart held in reset should be considered as unavailable. Moreover, the exclusive status of an hart (running/halted/unavailable/nonexistent) should be re-evaluated whenever dmstatus is called since the hart has no way to keep its DM in sync. Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 47 ++++++++++++++++++++++++------------------- hw/riscv/trace-events | 1 + 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index c471967cd26f0..31834405dd888 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -1512,41 +1512,46 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) uint64_t mask = 1u; for (; hix < hcount; hix++, mask <<= 1u) { RISCVDMHartState *hart = &dm->harts[hix]; -#ifdef TRACE_CPU_STATES CPUState *cs; g_assert(hart->cpu); cs = CPU(hart->cpu); -#endif - if (hart->resumed) { - resumeack += 1; - } - if (hart->have_reset) { - havereset += 1; - } if (dm->nonexistent_bm & mask) { nonexistent += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "nonexistent"); + continue; + } + /* + * The hart may have been started since last poll. There is no way + * for the hart to inform the DM in this case, so rely on polling + * for now. + */ + if (cs->held_in_reset) { + hart->resumed = false; + hart->halted = false; + dm->unavailable_bm |= mask; + dm->nonexistent_bm &= ~mask; + unavail += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "unavailable"); continue; } if (dm->unavailable_bm & mask) { - /* - * The hart may have been started since last poll. There is no way - * for the hart to inform the DM in this case, so rely on polling - * for now. - */ - if (CPU(hart->cpu)->halted) { - unavail += 1; - continue; - } -#ifdef TRACE_CPU_STATES - qemu_log("%s: %s: became available %p: %u\n", __func__, dm->soc, cs, - cs->cpu_index); -#endif + trace_riscv_dm_status(dm->soc, cs->cpu_index, "became available"); /* clear the unavailability flag and resume w/ "regular" states */ dm->unavailable_bm &= ~mask; } + if (hart->resumed) { + resumeack += 1; + trace_riscv_dm_status(dm->soc, cs->cpu_index, "resumed"); + } + if (hart->have_reset) { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "have reset"); + havereset += 1; + } if (hart->halted) { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "halted"); halted += 1; } else { + trace_riscv_dm_status(dm->soc, cs->cpu_index, "running"); running += 1; } mask <<= 1u; diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 985b132973e03..5e05d7e2ce5c1 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -40,6 +40,7 @@ riscv_dm_sbcs_write(const char *soc, bool err, bool busyerr, unsigned access, bo riscv_dm_sbaddr_write(const char *soc, unsigned slot, uint32_t address) "%s: @[%u] 0x%08x" riscv_dm_sbdata_read(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" riscv_dm_sbdata_write(const char *soc, unsigned slot, uint32_t data) "%s: @[%u] 0x%08x" +riscv_dm_status(const char *soc, int cpuid, const char *status) "%s {%d}: %s" riscv_dm_sysbus_data_read(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] <- %08" PRIx64 ": res %u" riscv_dm_sysbus_data_write(const char *soc, uint64_t address, unsigned size, uint64_t val64, unsigned res) "%s: 0x%08" PRIx64 "[+%u] -> %08" PRIx64 ": res %u" riscv_dm_unavailable(const char *soc, int cpuid) "%s {%d}" From 921054c7a3eb4e19cd8664c16eb44feef5d8f3cc Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 11 Jun 2025 15:26:08 +0200 Subject: [PATCH 061/175] [ot] hw/opentitan: ot_ibex_wrapper: force a disable event to maintain the hart in reset. Otherwise, the availability of the hart may not be tracked by the DM module, in which case a remote debugger may override the HW signal enforced from the LC controller. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index c9db8123ee9f1..77e3631a9ded7 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -874,6 +874,9 @@ static void ot_ibex_wrapper_update_exec(OtIbexWrapperState *s) } cpu_resume(s->cpu); } else { + if (!s->cpu->held_in_reset) { + resettable_assert_reset(OBJECT(s->cpu), RESET_TYPE_COLD); + } if (!s->cpu->halted) { s->cpu->halted = 1; cpu_exit(s->cpu); From 1d2f5ab36ce7e50572853b6fd862cd4157bd9c58 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 11 Jun 2025 16:50:21 +0200 Subject: [PATCH 062/175] [ot] python/qemu: ot.lc_ctrl.lcdmi: fix volatile_raw_unlock getter Signed-off-by: Emmanuel Blot --- python/qemu/ot/lc_ctrl/lcdmi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/lc_ctrl/lcdmi.py b/python/qemu/ot/lc_ctrl/lcdmi.py index 18382abd5e27c..8b03e1f7eabff 100644 --- a/python/qemu/ot/lc_ctrl/lcdmi.py +++ b/python/qemu/ot/lc_ctrl/lcdmi.py @@ -152,8 +152,8 @@ def transition_start(self) -> str: @property def volatile_raw_unlock(self) -> bool: """Report whether volatile unlock is enabled.""" - reg = 'volatile_raw_unlock' - vru = bool(self._read_reg(reg) & 0b1) + reg = 'transition_ctrl' + vru = bool(self._read_reg(reg) & 0b10) return vru @volatile_raw_unlock.setter From 32e98c2afd14fe8b5f0c3f0db6cd1ccd72249270 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 11 Jun 2025 17:17:28 +0200 Subject: [PATCH 063/175] [ot] hw/opentitan: ot_lc_ctrl: fix signal broadcast on volatile transition. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 731b3aec0cb5a..05e4c341687c7 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -735,7 +735,7 @@ ot_lc_ctrl_change_state_line(OtLcCtrlState *s, OtLcCtrlFsmState state, int line) s->state = state; - return change; + return change || state == ST_IDLE; } static void ot_lc_ctrl_update_alerts(OtLcCtrlState *s) @@ -1356,7 +1356,7 @@ static void ot_lc_ctrl_start_transition(OtLcCtrlState *s) s->regs[R_STATUS] |= R_STATUS_TRANSITION_SUCCESSFUL_MASK; trace_ot_lc_ctrl_info(s->ot_id, "Successful volatile unlock"); s->regs[R_STATUS] |= R_STATUS_READY_MASK; - /* FSM state is kept in IDLE */ + LC_FSM_CHANGE_STATE(s, ST_IDLE); } else { trace_ot_lc_ctrl_error(s->ot_id, "Invalid volatile unlock token"); From bff633ef58c99ac02f20c3f67f7087306090bb27 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:56:30 +0200 Subject: [PATCH 064/175] [ot] hw/opentitan: ot_ibex_common: code clean up Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index 77e3631a9ded7..c2a8c97666174 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -861,14 +861,14 @@ static void ot_ibex_wrapper_update_exec(OtIbexWrapperState *s) bool enable = ((s->cpu_en_bm & OT_IBEX_CPU_EN_MASK) == OT_IBEX_CPU_EN_MASK) && !s->esc_rx; - trace_ot_ibex_wrapper_update_exec(s->ot_id ?: "", s->cpu_en_bm, s->esc_rx, - s->cpu->halted, s->cpu->held_in_reset, - enable); - g_assert(s->cpu); + CPUState *cs = s->cpu; + g_assert(cs); + trace_ot_ibex_wrapper_update_exec(s->ot_id ?: "", s->cpu_en_bm, s->esc_rx, + cs->halted, cs->held_in_reset, enable); if (enable) { - s->cpu->halted = 0; + cs->halted = 0; if (s->cpu->held_in_reset) { resettable_release_reset(OBJECT(s->cpu), RESET_TYPE_COLD); } From de20930dd9fb831bacd9b1ab1d790d279d0a047b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:48:34 +0200 Subject: [PATCH 065/175] [ot] hw/core: cpu: replace held_in_reset with disabled Signed-off-by: Emmanuel Blot --- accel/tcg/cpu-exec.c | 2 +- hw/core/cpu-common.c | 2 -- include/hw/core/cpu.h | 4 ++-- system/cpus.c | 3 +-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index e736e0cde0981..ef9ac0616cb46 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -677,7 +677,7 @@ static inline void tb_add_jump(TranslationBlock *tb, int n, static inline bool cpu_handle_halt(CPUState *cpu) { #ifndef CONFIG_USER_ONLY - if (unlikely(cpu->held_in_reset)) { + if (unlikely(cpu->disabled)) { return true; } diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 5e5be046212f9..74f2927a1ae9d 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -120,7 +120,6 @@ static void cpu_common_reset_enter(Object *obj, ResetType type) { CPUState *cpu = CPU(obj); CPUClass *cc = CPU_GET_CLASS(cpu); - cpu->held_in_reset = true; if (qemu_loglevel_mask(CPU_LOG_RESET)) { qemu_log("CPU Reset Enter (CPU %d)\n", cpu->cpu_index); @@ -131,7 +130,6 @@ static void cpu_common_reset_enter(Object *obj, ResetType type) static void cpu_common_reset_exit(Object *obj, ResetType type) { CPUState *cpu = CPU(obj); - cpu->held_in_reset = false; if (qemu_loglevel_mask(CPU_LOG_RESET)) { CPUClass *cc = CPU_GET_CLASS(cpu); diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 5f20a8a3dbf4c..288c360f8330e 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -485,8 +485,8 @@ struct CPUState { /* Should CPU start in powered-off state? */ bool start_powered_off; - /* Is CPU currently held in reset? */ - bool held_in_reset; + /* Is CPU currently currently disabled? */ + bool disabled; bool unplug; bool crash_occurred; diff --git a/system/cpus.c b/system/cpus.c index 40422a086cab4..cce0c98155a9b 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -321,7 +321,7 @@ int vm_shutdown(void) bool cpu_can_run(CPUState *cpu) { - if (cpu->stop) { + if (cpu->stop || unlikely(cpu->disabled)) { return false; } if (cpu_is_stopped(cpu)) { @@ -878,4 +878,3 @@ void qmp_inject_nmi(Error **errp) { nmi_monitor_handle(monitor_get_cpu_index(monitor_cur()), errp); } - From f915467cf618db82b6bec6e0fdf448ffc7c958ee Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:48:55 +0200 Subject: [PATCH 066/175] [ot] hw/riscv: dm: replace held_in_reset with disabled Signed-off-by: Emmanuel Blot --- hw/riscv/dm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c index 31834405dd888..cadd69d56ece0 100644 --- a/hw/riscv/dm.c +++ b/hw/riscv/dm.c @@ -1267,9 +1267,9 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) CPUState *cs = CPU(hart->cpu); if (value & R_DMCONTROL_HARTRESET_MASK) { - if (!cs->held_in_reset) { - trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, - dm->hart->hartid, "assert"); + trace_riscv_dm_hart_reset(dm->soc, cs->cpu_index, + dm->hart->hartid, "assert"); + if (!cs->disabled) { if (hart->unlock_reset) { /* * if hart is started in active reset, prevent from @@ -1283,7 +1283,7 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) } } } else { - if (cs->held_in_reset) { + if (cs->disabled) { if (hart->unlock_reset) { /* * if hart is started in active reset, prevent from @@ -1301,7 +1301,7 @@ static CmdErr riscv_dm_dmcontrol_write(RISCVDMState *dm, uint32_t value) } if (dm->unavailable_bm & hbit) { - if (!cs->held_in_reset) { + if (!cs->disabled) { /* hart exited from reset, became available */ dm->unavailable_bm &= ~hbit; hart->have_reset = true; @@ -1525,7 +1525,7 @@ static CmdErr riscv_dm_dmstatus_read(RISCVDMState *dm, uint32_t *value) * for the hart to inform the DM in this case, so rely on polling * for now. */ - if (cs->held_in_reset) { + if (cs->disabled) { hart->resumed = false; hart->halted = false; dm->unavailable_bm |= mask; @@ -2396,7 +2396,7 @@ static void riscv_dm_ensure_running(RISCVDMState *dm) vm_start(); } - if (cs->stopped && !cs->held_in_reset) { + if (cs->stopped && !cs->disabled) { cpu_resume(cs); } } @@ -2501,7 +2501,7 @@ static int riscv_dm_discover_cpus(RISCVDMState *dm) g_assert(hart->cpu == cpu); } hart->hartid = hart->cpu->env.mhartid; - hart->unlock_reset = !cs->held_in_reset; + hart->unlock_reset = !cs->disabled; if (!dm->as) { /* address space is unknown till first hart is realized */ dm->as = cs->as; @@ -2636,7 +2636,7 @@ static void riscv_dm_reset_exit(Object *obj, ResetType type) CPUState *cs = CPU(cpu); if (cs->halted) { - if (cs->held_in_reset) { + if (cs->disabled) { dm->unavailable_bm |= 1u << ix; trace_riscv_dm_unavailable(dm->soc, cs->cpu_index); /* a hart cannot be halted and unavailable at once */ From 046167c5ea8e537e544b1630fcf326b7fdf5e4c7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:49:13 +0200 Subject: [PATCH 067/175] [ot] hw/riscv: ibex_common: replace held_in_reset with disabled Signed-off-by: Emmanuel Blot --- hw/riscv/ibex_common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c index 9c7d86f944a21..7f137a44a30b4 100644 --- a/hw/riscv/ibex_common.c +++ b/hw/riscv/ibex_common.c @@ -816,12 +816,12 @@ static void hmp_info_ibex(Monitor *mon, const QDict *qdict) pc = -1; symbol = "?"; } - if (cpu->halted && cpu->held_in_reset) { - cpu_state = " [HR]"; + if (cpu->halted && cpu->disabled) { + cpu_state = " [HD]"; } else if (cpu->halted) { cpu_state = " [H]"; - } else if (cpu->held_in_reset) { - cpu_state = " [R]"; + } else if (cpu->disabled) { + cpu_state = " [D]"; } else { cpu_state = ""; } From a9b2a7cc0b9fd86cbef4e91ba5cff21d8895eb8a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 18:54:38 +0200 Subject: [PATCH 068/175] [ot] hw/riscv: ot_ibex_wrapper: replace held_in_reset with disabled Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_ibex_wrapper.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/hw/opentitan/ot_ibex_wrapper.c b/hw/opentitan/ot_ibex_wrapper.c index c2a8c97666174..b8538fcb4a507 100644 --- a/hw/opentitan/ot_ibex_wrapper.c +++ b/hw/opentitan/ot_ibex_wrapper.c @@ -865,22 +865,15 @@ static void ot_ibex_wrapper_update_exec(OtIbexWrapperState *s) CPUState *cs = s->cpu; g_assert(cs); trace_ot_ibex_wrapper_update_exec(s->ot_id ?: "", s->cpu_en_bm, s->esc_rx, - cs->halted, cs->held_in_reset, enable); + cs->halted, cs->disabled, enable); if (enable) { cs->halted = 0; - if (s->cpu->held_in_reset) { - resettable_release_reset(OBJECT(s->cpu), RESET_TYPE_COLD); - } - cpu_resume(s->cpu); + cs->disabled = false; + cpu_resume(cs); } else { - if (!s->cpu->held_in_reset) { - resettable_assert_reset(OBJECT(s->cpu), RESET_TYPE_COLD); - } - if (!s->cpu->halted) { - s->cpu->halted = 1; - cpu_exit(s->cpu); - } + cs->disabled = true; + cpu_pause(cs); } } From 40014b25fec6468b3fa140de804bda03fc190d47 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:43:16 +0200 Subject: [PATCH 069/175] [ot] hw/riscv: ot_earlgrey, ot_darjeeling: add missing hart reset on SoC reset. Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 2 +- hw/riscv/ot_earlgrey.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 31e5ab40ee482..1b5f109ef9d39 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -1547,6 +1547,7 @@ static void ot_dj_soc_hw_reset(void *opaque, int irq, int level) CPUState *cs = CPU(s->devices[OT_DJ_SOC_DEV_HART]); cpu_synchronize_state(cs); bus_cold_reset(sysbus_get_default()); + resettable_reset(OBJECT(cs), RESET_TYPE_COLD); cpu_synchronize_post_reset(cs); } } @@ -1566,7 +1567,6 @@ static void ot_dj_soc_reset_hold(Object *obj, ResetType type) resettable_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_DM]), type); resettable_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_VMAPPER]), type); - /* keep ROM_CTRLs in reset, we'll release them last */ resettable_assert_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM0]), type); resettable_assert_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM1]), type); diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 7df1ada4b7752..7c8cfeacf8eeb 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1383,6 +1383,7 @@ static void ot_eg_soc_hw_reset(void *opaque, int irq, int level) CPUState *cs = CPU(s->devices[OT_EG_SOC_DEV_HART]); cpu_synchronize_state(cs); bus_cold_reset(sysbus_get_default()); + resettable_reset(OBJECT(cs), RESET_TYPE_COLD); cpu_synchronize_post_reset(cs); } } From cc7e6ae8cb37226940dff6e86bb668535952dddf Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:42:37 +0200 Subject: [PATCH 070/175] [ot] hw/riscv: darjeeling, earlgrey: use CPU disabled rather than asserting reset Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 2 +- hw/riscv/ot_earlgrey.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 1b5f109ef9d39..f30207795432b 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -1580,7 +1580,7 @@ static void ot_dj_soc_reset_hold(Object *obj, ResetType type) * realization. */ CPUState *cs = CPU(s->devices[OT_DJ_SOC_DEV_HART]); - resettable_assert_reset(OBJECT(cs), type); + cs->disabled = 1; } static void ot_dj_soc_reset_exit(Object *obj, ResetType type) diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 7c8cfeacf8eeb..9c7401346b059 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1413,7 +1413,7 @@ static void ot_eg_soc_reset_hold(Object *obj, ResetType type) * realization. */ CPUState *cs = CPU(s->devices[OT_EG_SOC_DEV_HART]); - resettable_assert_reset(OBJECT(cs), type); + cs->disabled = 1; } static void ot_eg_soc_reset_exit(Object *obj, ResetType type) From 89c680a3e9e901c0a38580f3bf13f2c48bef9a7d Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:19:46 +0200 Subject: [PATCH 071/175] [ot] hw/opentitan: ot_rom_ctrl: move ROM load and check into a dedicated function. This feature is no longer triggered by the reset sequence, but a boolean property should be explicitly set. This enables removing a special handling for the ROM controller whose reset needed to be called at a precise stage in the SoC reset sequence. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_rom_ctrl.c | 56 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index 8bcef7024a5dd..e8b57a8c38681 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -156,6 +156,7 @@ struct OtRomCtrlState { unsigned recovered_error_count; unsigned unrecoverable_error_count; bool first_reset; + bool loaded; char *ot_id; uint32_t size; @@ -1078,8 +1079,11 @@ static void ot_rom_ctrl_reset_hold(Object *obj, ResetType type) c->parent_phases.hold(obj, type); } + s->loaded = false; + /* reset all registers on first reset, otherwise keep digests */ if (s->first_reset) { + memset(memory_region_get_ram_ptr(&s->mem), 0, s->size); memset(s->regs, 0, REGS_SIZE); } else { s->regs[R_ALERT_TEST] = 0; @@ -1095,42 +1099,40 @@ static void ot_rom_ctrl_reset_hold(Object *obj, ResetType type) ot_rom_ctrl_handle_kmac_response, s); } -static void ot_rom_ctrl_reset_exit(Object *obj, ResetType type) +static void ot_rom_ctrl_set_load(Object *obj, bool value, Error **errp) { - OtRomCtrlClass *c = OT_ROM_CTRL_GET_CLASS(obj); OtRomCtrlState *s = OT_ROM_CTRL(obj); - uint8_t *rom_ptr = (uint8_t *)memory_region_get_ram_ptr(&s->mem); + (void)errp; - if (c->parent_phases.exit) { - c->parent_phases.exit(obj, type); + if (!value) { + return; } - bool notify = true; + if (!s->loaded) { + s->loaded = true; - /* on initial reset, load ROM then set it read-only */ - if (s->first_reset) { - /* pre-fill ROM region with zeros */ - memset(rom_ptr, 0, s->size); + bool notify = true; - /* load ROM from file */ - bool dig = ot_rom_ctrl_load_rom(s); + /* on initial reset, load ROM then set it read-only */ + if (s->first_reset) { + /* load ROM from file */ + bool dig = ot_rom_ctrl_load_rom(s); - /* ensure ROM can no longer be written */ - s->first_reset = false; + /* ensure ROM can no longer be written */ + s->first_reset = false; - if (!dig) { - ot_rom_ctrl_fake_digest(s); - } + if (!dig) { + ot_rom_ctrl_fake_digest(s); + } - notify = !dig; - } + notify = !dig; + } - if (notify) { - /* compare existing digests and send notification to pwrmgr */ - ot_rom_ctrl_compare_and_notify(s); + if (notify) { + /* compare existing digests and send notification to pwrmgr */ + ot_rom_ctrl_compare_and_notify(s); + } } - - trace_ot_rom_ctrl_reset(s->ot_id, "exit"); } static void ot_rom_ctrl_realize(DeviceState *dev, Error **errp) @@ -1202,6 +1204,9 @@ static void ot_rom_ctrl_init(Object *obj) ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); fifo8_create(&s->hash_fifo, OT_KMAC_APP_MSG_BYTES); + + object_property_add_bool(obj, "load", NULL, &ot_rom_ctrl_set_load); + object_property_set_description(obj, "load", "Trigger initial ROM loading"); } static void ot_rom_ctrl_class_init(ObjectClass *klass, void *data) @@ -1212,8 +1217,7 @@ static void ot_rom_ctrl_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(dc); - resettable_class_set_parent_phases(rc, NULL, &ot_rom_ctrl_reset_hold, - &ot_rom_ctrl_reset_exit, + resettable_class_set_parent_phases(rc, NULL, &ot_rom_ctrl_reset_hold, NULL, &rcc->parent_phases); dc->realize = &ot_rom_ctrl_realize; device_class_set_props(dc, ot_rom_ctrl_properties); From 983718a882bd04c69f3169ee4eab9d18301c6171 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 12 Jun 2025 17:21:00 +0200 Subject: [PATCH 072/175] [ot] hw/opentitan: darjeeling, earlgrey: trigger ROM load and check explictly Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 18 ++++++------------ hw/riscv/ot_earlgrey.c | 14 ++++---------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index f30207795432b..643c2a695854d 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -1567,17 +1567,9 @@ static void ot_dj_soc_reset_hold(Object *obj, ResetType type) resettable_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_DM]), type); resettable_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_VMAPPER]), type); - /* keep ROM_CTRLs in reset, we'll release them last */ - resettable_assert_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM0]), type); - resettable_assert_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM1]), type); - /* - * Power-On-Reset: leave hart on reset + * Power-On-Reset: leave hart disabled on reset * PowerManager takes care of managing Ibex reset when ready - * - * Note that an initial, extra single reset cycle (assert/release) is - * performed from the generic #riscv_cpu_realize function on machine - * realization. */ CPUState *cs = CPU(s->devices[OT_DJ_SOC_DEV_HART]); cs->disabled = 1; @@ -1592,9 +1584,11 @@ static void ot_dj_soc_reset_exit(Object *obj, ResetType type) c->parent_phases.exit(obj, type); } - /* let ROM_CTRLs get out of reset now */ - resettable_release_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM0]), type); - resettable_release_reset(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM1]), type); + /* Kick off ROM checks and boot */ + object_property_set_bool(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM0]), "load", + true, &error_fatal); + object_property_set_bool(OBJECT(s->devices[OT_DJ_SOC_DEV_ROM1]), "load", + true, &error_fatal); } static void ot_dj_soc_realize(DeviceState *dev, Error **errp) diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 9c7401346b059..d94bcbe2c8dd2 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1401,16 +1401,9 @@ static void ot_eg_soc_reset_hold(Object *obj, ResetType type) resettable_reset(OBJECT(s->devices[OT_EG_SOC_DEV_DM]), type); resettable_reset(OBJECT(s->devices[OT_EG_SOC_DEV_VMAPPER]), type); - /* keep ROM_CTRL in reset, we'll release it last */ - resettable_assert_reset(OBJECT(s->devices[OT_EG_SOC_DEV_ROM_CTRL]), type); - /* - * Power-On-Reset: leave hart on reset + * Power-On-Reset: leave hart disabled on reset * PowerManager takes care of managing Ibex reset when ready - * - * Note that an initial, extra single reset cycle (assert/release) is - * performed from the generic #riscv_cpu_realize function on machine - * realization. */ CPUState *cs = CPU(s->devices[OT_EG_SOC_DEV_HART]); cs->disabled = 1; @@ -1425,8 +1418,9 @@ static void ot_eg_soc_reset_exit(Object *obj, ResetType type) c->parent_phases.exit(obj, type); } - /* let ROM_CTRL get out of reset now */ - resettable_release_reset(OBJECT(s->devices[OT_EG_SOC_DEV_ROM_CTRL]), type); + /* Kick off ROM check and boot */ + object_property_set_bool(OBJECT(s->devices[OT_EG_SOC_DEV_ROM_CTRL]), "load", + true, &error_fatal); } static void From f0d0da285f949706ed73929e272c47b5ecb467db Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 16 Jun 2025 12:54:30 +0200 Subject: [PATCH 073/175] [ot] python/qemu: ot.dm, ot.dtm: decrease log verbosity of some traces Signed-off-by: Emmanuel Blot --- python/qemu/ot/dm/dm.py | 2 +- python/qemu/ot/dtm/dtm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/dm/dm.py b/python/qemu/ot/dm/dm.py index d0d5413f9d460..0c764de6c7eb4 100644 --- a/python/qemu/ot/dm/dm.py +++ b/python/qemu/ot/dm/dm.py @@ -190,7 +190,7 @@ def decode(cls, name: str, value: int) -> dict[str, Any]: def initialize(self) -> None: """Initialize the debug module.""" - self._log.info('Initialize') + self._log.debug('Initialize') btf = self.BITFIELDS['DMCONTROL'] self.dmcontrol = 0 enable = btf.encode(dmactive=True) diff --git a/python/qemu/ot/dtm/dtm.py b/python/qemu/ot/dtm/dtm.py index d10961e282409..15531b03c7ce0 100644 --- a/python/qemu/ot/dtm/dtm.py +++ b/python/qemu/ot/dtm/dtm.py @@ -211,7 +211,7 @@ def _build_dmi(self, address: int) -> int: self._abits = self._dtm.abits() if self._abits < 1: raise DMIError('Invalid reported address bits') - self._log.info('DMI width: %d bits', self._abits) + self._log.debug('DMI width: %d bits', self._abits) if address >= (1 << self._abits): raise ValueError(f'Address 0x{address:x} too large, ' f'max 0x{(1 << self._abits) -1:x}') From f2ebefc85ddfc9079e3f26f3c2a8dbea9e65ee32 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 16 Jun 2025 12:55:52 +0200 Subject: [PATCH 074/175] [ot] scripts/opentitan: dtm.py: add an optional delay before terminating VM Signed-off-by: Emmanuel Blot --- docs/opentitan/dtm.md | 3 ++- scripts/opentitan/dtm.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/opentitan/dtm.md b/docs/opentitan/dtm.md index 10eab107b0cec..6950a730dec80 100644 --- a/docs/opentitan/dtm.md +++ b/docs/opentitan/dtm.md @@ -112,7 +112,8 @@ Extras: * `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. * `-w` pause execution on start up before communication with the remote DTM. This enables QEMU VM - to initialize and boot the guest SW before attempting a communication. + to initialize and boot the guest SW before attempting a communication. It can be repeated once, + the second value being used to delay the termination of the VM when the `-t` option is used. * `-X` do not attempt to resume normal execution of the hart once DTM operation have been completed. This can be useful for example when the QEMU VM is started with `-S` and no application code has diff --git a/scripts/opentitan/dtm.py b/scripts/opentitan/dtm.py index f6cfc554b68f2..1ba4b8c8e6943 100755 --- a/scripts/opentitan/dtm.py +++ b/scripts/opentitan/dtm.py @@ -76,6 +76,7 @@ def main(): qvm.add_argument('-t', '--terminate', action='store_true', help='terminate QEMU when done') qvm.add_argument('-w', '--idle', type=float, metavar='DELAY', + action='append', help='stay idle before interacting with DTM') dmi = argparser.add_argument_group(title='DMI') dmi.add_argument('-l', '--ir-length', type=int, @@ -179,8 +180,8 @@ def main(): rvdm = None if args.idle: - log.info('Idling for %.1f seconds', args.idle) - sleep(args.idle) + log.info('Idling for %.1f seconds', args.idle[0]) + sleep(args.idle[0]) try: if args.info: @@ -295,6 +296,9 @@ def main(): argparser.error('Cannot execute without loaded an ELF file') finally: if args.terminate: + if len(args.idle or []) > 1: + log.info('Idling for %.1f seconds', args.idle[1]) + sleep(args.idle[1]) ctrl.quit() # pylint: disable=broad-except From 7e65f34fa9fe559cd6e1560f3d5f70ea75384af6 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 16 Jun 2025 19:46:21 +0200 Subject: [PATCH 075/175] [ot] scripts/opentitan: pyot.py: add an option to handle ASAN output Default Address Sanitizer output is stderr; moreover ASAN keeps emitting useless traces on every single QEMU session, whatever the actual execution outcome, which are interpreted as legit ERROR message. Add an option to redirect the ASAN message to a temporary file, and post process the content of this file once QEMU execution has completed. The ASAN output messages (if there are some useful) are therefore redirected to the Python logger. Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 22 +++++++++------ python/qemu/ot/pyot/executer.py | 6 +++-- python/qemu/ot/pyot/wrapper.py | 47 +++++++++++++++++++++++++++++++-- scripts/opentitan/pyot.py | 2 ++ 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index e820296729d53..c9e158b92bdb9 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -5,14 +5,14 @@ ## Usage ````text -usage: pyot.py [-h] [-D DELAY] [-i ICOUNT] [-L LOG_FILE] [-M VARIANT] [-N LOG] - [-m MACHINE] [-Q OPTS] [-q QEMU] [-P VCP] [-p DEVICE] +usage: pyot.py [-h] [-A] [-D DELAY] [-i ICOUNT] [-L LOG_FILE] [-M VARIANT] + [-N LOG] [-m MACHINE] [-Q OPTS] [-q QEMU] [-P VCP] [-p DEVICE] [-t TRACE] [-S FIRST_SOC] [-s] [-U] [-b file] [-c HJSON] - [-e BUS] [-f RAW] [-g file] [-K] [-l file] [-O RAW] [-o VMEM] - [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] [-k SECONDS] [-z] - [-R] [-T FACTOR] [-Z] [-v] [-V] [-d] [--quiet] [--log-time] - [--log-udp UDP_PORT] [--debug LOGGER] [--info LOGGER] - [--warn LOGGER] + [-e BUS] [-f RAW] [-g file] [-H] [-K] [-l file] [-O RAW] + [-o VMEM] [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] + [-k SECONDS] [-z] [-R] [-T FACTOR] [-Z] [-v] [-V] [-d] + [--quiet] [--log-time] [--log-udp UDP_PORT] [--debug LOGGER] + [--info LOGGER] [--warn LOGGER] OpenTitan QEMU unit test sequencer. @@ -20,6 +20,7 @@ options: -h, --help show this help message and exit Virtual machine: + -A, --asan Redirect address sanitizer error log stream -D, --start-delay DELAY QEMU start up delay before initial comm -i, --icount ICOUNT virtual instruction counter with 2^ICOUNT clock ticks @@ -51,6 +52,9 @@ Files: generate an eflash image file for MTD bus -f, --flash RAW SPI flash image file -g, --otcfg file configuration options for OpenTitan devices + -H, --no-flash-header + application and/or bootloader files contain no OT + header -K, --keep-tmp Do not automatically remove temporary files and dirs on exit -l, --loader file ROM trampoline to execute, if any @@ -93,6 +97,7 @@ This tool may be used in two ways, which can be combined: ### Virtual machine +* `-A` / `--asan` filter address sanitizer traces and report them as log traces * `-D` / `--start-delay` VM start up delay. Grace period to wait for the VM to start up before attempting to communicate with its char devices. * `-i` / `--icount` to specify virtual instruction counter with 2^N clock ticks per instruction. @@ -138,7 +143,8 @@ This tool may be used in two ways, which can be combined: * `-f` / `--flash` specify a RAW image file that stores the embedded Flash content, which can be generated with the [`flashgen.py`](flashgen.md) tool. Alternatively, see the `-x` option. * `-g` / `--otcfg` specify a configuration file with OpenTitan configuration options, such as - cryptographic constants (seeds, keys, nonces, ...) + cryptographic constants (seeds, keys, nonces, ...). +* `-H` / `--no-flash-header` executable files should be considered as raw files with no OpenTitan flash headers. * `-K` / `--keep-tmp` do not automatically remove temporary files and directories on exit. The user is in charge of discarding any generated files and directories after execution. The paths to the generated items are emitted as warning messages. diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index c44696161b567..979f0d8af4f7a 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -137,7 +137,8 @@ def run(self, debug: bool, allow_no_test: bool) -> int: DEFAULT_TIMEOUT_FACTOR))) self._log.debug('Execute %s', basename(self._argdict['exec'])) adef = EasyDict(command=self._qemu_cmd, timeout=timeout, - start_delay=self.DEFAULT_START_DELAY) + start_delay=self.DEFAULT_START_DELAY, + asan=self._argdict.get('asan', False)) ret, xtime, err = qot.run(adef) results[ret] += 1 sret = self.RESULT_MAP.get(ret, ret) @@ -482,6 +483,7 @@ def _build_qemu_command(self, args: Namespace, if trigger and validate: raise ValueError(f"{getattr(args, 'exec', '?')}: 'trigger' and " f"'validate' are mutually exclusive") + asan = getattr(args, 'asan', False) vcp_args, vcp_map = self._build_qemu_vcp_args(args) qemu_args.extend(vcp_args) qemu_args.extend(args.global_opts or []) @@ -489,7 +491,7 @@ def _build_qemu_command(self, args: Namespace, qemu_args.extend((str(o) for o in opts)) return EasyDict(command=qemu_args, vcp_map=vcp_map, tmpfiles=temp_files, start_delay=start_delay, - trigger=trigger, validate=validate) + trigger=trigger, validate=validate, asan=asan) def _build_qemu_test_command(self, filename: str) -> EasyDict[str, Any]: test_name = self.get_test_radix(filename) diff --git a/python/qemu/ot/pyot/wrapper.py b/python/qemu/ot/pyot/wrapper.py index a3a686235d058..99f4b5bc9e47c 100644 --- a/python/qemu/ot/pyot/wrapper.py +++ b/python/qemu/ot/pyot/wrapper.py @@ -7,7 +7,8 @@ """ from collections import deque -from os.path import basename, dirname +from os import environ, unlink +from os.path import basename, dirname, isfile, join as joinpath from select import POLLIN, POLLERR, POLLHUP, poll as spoll from socket import socket, timeout as LegacyTimeoutError from subprocess import Popen, PIPE, TimeoutExpired @@ -55,6 +56,9 @@ class QEMUWrapper: NO_MATCH_RETURN_CODE = 100 """Return code when no matching string is found in guest output.""" + ASAN_LOGFILE_PREFIX = 'asan.log' + """Address sanitizer log prefix.""" + def __init__(self, log_classifiers: dict[str, list[str]], debug: bool): self._log_classifiers = log_classifiers self._debug = debug @@ -85,6 +89,8 @@ def run(self, tdef: EasyDict[str, Any]) -> tuple[int, ExecTime, str]: expression. - start_delay, the delay to wait before starting the execution of the context once QEMU command has been started. + - asan, whether to redirect and post-process AddressSanitizer + output log :return: a 3-uple of exit code, execution time, and last guest error """ # stdout and stderr belongs to QEMU VM @@ -119,13 +125,24 @@ def trig_match(bline): last_error = '' vcp_map = tdef.vcp_map vcp_ctxs: dict[int, list[str, socket, bytearray, logging.Logger]] = {} + asan_file = None try: workdir = dirname(tdef.command[0]) log.debug('Executing QEMU as %s', ' '.join(tdef.command)) + env = dict(environ) + if tdef.asan: + # note cannot use a QEMUFileManager temp file here, as the full + # pathname is built by the ASAN tool once QEMU has started. + # store this temporary file in the QEMUFileManager-managed + # work directory + asan_prefix = joinpath(workdir, self.ASAN_LOGFILE_PREFIX) + env['ASAN_OPTIONS'] = f'log_path={asan_prefix}' + else: + asan_prefix = None # pylint: disable=consider-using-with proc = Popen(tdef.command, bufsize=1, cwd=workdir, stdout=PIPE, stderr=PIPE, encoding='utf-8', errors='ignore', - text=True) + text=True, env=env) try: proc.wait(0.1) except TimeoutExpired: @@ -135,6 +152,8 @@ def trig_match(bline): log.error('QEMU bailed out: %d for "%s"', ret, tdef.test_name) raise OSError() log.debug('Execute QEMU for %.0f secs', tdef.timeout) + if asan_prefix: + asan_file = f'{asan_prefix}.{proc.pid}' # unfortunately, subprocess's stdout calls are blocking, so the # only way to get near real-time output from QEMU is to use a # dedicated thread that may block whenever no output is available @@ -328,6 +347,8 @@ def trig_match(bline): line = line.strip() if line: logger(line) + if asan_file: + self._post_process_asan(asan_file) xtime = ExecTime(xend-xstart) if xstart and xend else 0.0 return abs(ret) or 0, xtime, last_error @@ -404,3 +425,25 @@ def _get_exit_code(self, xmo: re.Match) -> int: # any other case self._log.debug('No match, using defaut code') return self.NO_MATCH_RETURN_CODE + + def _post_process_asan(self, asan_file: str): + alog = logging.getLogger('pyot.asan') + try: + if not isfile(asan_file): + raise ValueError('ASAN output lost') + with open(asan_file, 'rt') as afp: + asan = afp.read() + # 3 following messages are useless, discard them + for aline in asan.split('\n'): + if aline.find('ASan is ignoring requested') >= 0: + continue + if aline.find('False positive error reports may follow') >= 0: + continue + if aline.find('https://github.com') >= 0: + continue + if not aline: + continue + alog.error(aline.rstrip()) + unlink(asan_file) + except (OSError, ValueError) as exc: + self._qlog.error('Cannot process ASAN file: %s', exc) diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index e4d8e4a8f2b16..b9ece161cab51 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -67,6 +67,8 @@ def main(): argparser = ArgumentParser(description=f'{desc}.') qvm = argparser.add_argument_group(title='Virtual machine') rel_qemu_path = relpath(qemu_path) if qemu_path else '?' + qvm.add_argument('-A', '--asan', action='store_const', const=True, + help='Redirect address sanitizer error log stream') qvm.add_argument('-D', '--start-delay', type=float, metavar='DELAY', help='QEMU start up delay before initial comm') qvm.add_argument('-i', '--icount', From b1e97480bd9c34e4c68c295d81ffefca0f5c3200 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 16:23:15 +0200 Subject: [PATCH 076/175] [ot] scripts/opentitan: pyot.py: recover hidden error message A small vulnerability window where early QEMU log error message could be bufferized in log queue and not popped on error reporting Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/wrapper.py | 57 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/python/qemu/ot/pyot/wrapper.py b/python/qemu/ot/pyot/wrapper.py index 99f4b5bc9e47c..0f483309073df 100644 --- a/python/qemu/ot/pyot/wrapper.py +++ b/python/qemu/ot/pyot/wrapper.py @@ -49,6 +49,9 @@ class QEMUWrapper: ANSI_CRE = re.compile(rb'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]') """ANSI escape sequences.""" + USELESS_ERR_CRE = re.compile(r'^\*{1,4}$') + """Useless error stuff from QEMU.""" + GUEST_ERROR_OFFSET = 40 """Offset for guest errors. Should be larger than the host max signal value. """ @@ -126,6 +129,10 @@ def trig_match(bline): vcp_map = tdef.vcp_map vcp_ctxs: dict[int, list[str, socket, bytearray, logging.Logger]] = {} asan_file = None + log_q = deque() + qemu_exec = f'{basename(tdef.command[0])}: ' + classifier = LogMessageClassifier(classifiers=self._log_classifiers, + qemux=qemu_exec) try: workdir = dirname(tdef.command[0]) log.debug('Executing QEMU as %s', ' '.join(tdef.command)) @@ -149,7 +156,7 @@ def trig_match(bline): pass else: ret = proc.returncode - log.error('QEMU bailed out: %d for "%s"', ret, tdef.test_name) + log.fatal('QEMU bailed out: %d for "%s"', ret, tdef.test_name) raise OSError() log.debug('Execute QEMU for %.0f secs', tdef.timeout) if asan_prefix: @@ -161,7 +168,6 @@ def trig_match(bline): # queue, which is popped and logged to the local logger on each # loop. Note that Popen's communicate() also relies on threads to # perform stdout/stderr read out. - log_q = deque() Thread(target=self._qemu_logger, name='qemu_out_logger', args=(proc, log_q, True), daemon=True).start() Thread(target=self._qemu_logger, name='qemu_err_logger', @@ -178,6 +184,8 @@ def trig_match(bline): if now() > timeout: minfo = ', '.join(f'{d} @ {r[0]}:{r[1]}' for d, r in connect_map.items()) + if proc.poll(): + raise OSError(proc.returncode) raise TimeoutError(f'Cannot connect to QEMU VCPs: {minfo}') connected = [] for vcpid, (host, port) in connect_map.items(): @@ -219,23 +227,12 @@ def trig_match(bline): ret = 126 last_error = str(exc) raise - qemu_exec = f'{basename(tdef.command[0])}: ' - classifier = LogMessageClassifier(classifiers=self._log_classifiers, - qemux=qemu_exec) abstimeout = float(tdef.timeout) + now() - qemu_default_log = logging.ERROR vcp_default_log = logging.DEBUG while now() < abstimeout: - while log_q: - err, qline = log_q.popleft() - if err: - level = classifier.classify(qline, qemu_default_log) - if level == logging.INFO and \ - qline.find('QEMU waiting for connection') >= 0: - level = logging.DEBUG - else: - level = logging.INFO - self._qlog.log(level, qline) + log_err = self._process_log_queue(log_q, classifier) + if not last_error and log_err: + last_error = log_err if tdef.context: wret = tdef.context.check_error() if wret: @@ -318,14 +315,17 @@ def trig_match(bline): ret = 124 # timeout except (OSError, ValueError) as exc: if ret is None: - log.error('Unable to execute QEMU: %s', exc) ret = proc.returncode if proc.poll() is not None else 125 + log.fatal('Unable to execute QEMU: %s', exc) finally: if xend is None: xend = now() for _, sock, _, _ in vcp_ctxs.values(): sock.close() vcp_ctxs.clear() + log_err = self._process_log_queue(log_q, classifier) + if not last_error and log_err: + last_error = log_err if proc: if xend is None: xend = now() @@ -341,7 +341,7 @@ def trig_match(bline): ret = proc.returncode # retrieve the remaining log messages stdlog = self._qlog.info if ret else self._qlog.debug - for msg, logger in zip(proc.communicate(timeout=0.1), + for msg, logger in zip(proc.communicate(timeout=1.1), (stdlog, self._qlog.error)): for line in msg.split('\n'): line = line.strip() @@ -350,6 +350,9 @@ def trig_match(bline): if asan_file: self._post_process_asan(asan_file) xtime = ExecTime(xend-xstart) if xstart and xend else 0.0 + qemu_cmd_lead = f'{basename(tdef.command[0])}: ' + if last_error.startswith(qemu_cmd_lead): + last_error = last_error[len(qemu_cmd_lead):] return abs(ret) or 0, xtime, last_error @classmethod @@ -392,6 +395,24 @@ def _colorize_vcp_log(self, vcplogname: str, lognames: list[str]) -> None: for color, logname in enumerate(sorted(lognames)): clr_fmt.add_logger_colors(f'{vcplogname}.{logname}', color) + def _process_log_queue(self, log_q, classifier) -> Optional[str]: + first_err = None + while log_q: + err, qline = log_q.popleft() + if err: + if self.USELESS_ERR_CRE.match(qline): + continue + level = classifier.classify(qline, logging.ERROR) + if level == logging.INFO and \ + qline.find('QEMU waiting for connection') >= 0: + level = logging.DEBUG + if level >= logging.ERROR and not first_err: + first_err = qline + else: + level = logging.INFO + self._qlog.log(level, qline) + return first_err + def _qemu_logger(self, proc: Popen, queue: deque, err: bool): # worker thread, blocking on VM stdout/stderr stream = proc.stderr if err else proc.stdout From 69764466d9e929a68fd6a24b559a9a1aeb8bfe03 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 23 Jun 2025 11:12:49 +0200 Subject: [PATCH 077/175] [ot] scripts/opentitan: pyot.py: --otcfg may now be repeated. This enables using multiple config files, for example a 'static' clock configuration file that defines the invariant platform clocks, and a 'variable' configuration file that defines specific cryptographic constants. Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 22 +++++++++++----------- python/qemu/ot/pyot/executer.py | 4 ++-- scripts/opentitan/pyot.py | 24 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index c9e158b92bdb9..c75797356c549 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -8,11 +8,11 @@ usage: pyot.py [-h] [-A] [-D DELAY] [-i ICOUNT] [-L LOG_FILE] [-M VARIANT] [-N LOG] [-m MACHINE] [-Q OPTS] [-q QEMU] [-P VCP] [-p DEVICE] [-t TRACE] [-S FIRST_SOC] [-s] [-U] [-b file] [-c HJSON] - [-e BUS] [-f RAW] [-g file] [-H] [-K] [-l file] [-O RAW] + [-e BUS] [-f RAW] [-g CFGFILE] [-H] [-K] [-l file] [-O RAW] [-o VMEM] [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] - [-k SECONDS] [-z] [-R] [-T FACTOR] [-Z] [-v] [-V] [-d] - [--quiet] [--log-time] [--log-udp UDP_PORT] [--debug LOGGER] - [--info LOGGER] [--warn LOGGER] + [-k SECONDS] [-z] [-R] [-T FACTOR] [-Z] [-d] [--quiet] [-G] + [-V] [-v] [--log-udp UDP_PORT] [--debug LOGGER] [--info LOGGER] + [--warn LOGGER] OpenTitan QEMU unit test sequencer. @@ -51,7 +51,7 @@ Files: -e, --embedded-flash BUS generate an eflash image file for MTD bus -f, --flash RAW SPI flash image file - -g, --otcfg file configuration options for OpenTitan devices + -g, --otcfg CFGFILE configuration option file for OT devices -H, --no-flash-header application and/or bootloader files contain no OT header @@ -77,11 +77,11 @@ Execution: -Z, --zero do not error if no test can be executed Extras: - -v, --verbose increase verbosity - -V, --vcp-verbose increase verbosity of QEMU virtual comm ports -d enable debug mode --quiet quiet logging: only be verbose on errors - --log-time show local time in log messages + -G, --log-time show local time in log messages + -V, --vcp-verbose increase verbosity of QEMU virtual comm ports + -v, --verbose increase verbosity --log-udp UDP_PORT Change UDP port for log messages, use 0 to disable --debug LOGGER assign debug level to logger(s) --info LOGGER assign info level to logger(s) @@ -143,7 +143,7 @@ This tool may be used in two ways, which can be combined: * `-f` / `--flash` specify a RAW image file that stores the embedded Flash content, which can be generated with the [`flashgen.py`](flashgen.md) tool. Alternatively, see the `-x` option. * `-g` / `--otcfg` specify a configuration file with OpenTitan configuration options, such as - cryptographic constants (seeds, keys, nonces, ...). + clock definitions, cryptographic constants (seeds, keys, nonces, ...). May be repeated. * `-H` / `--no-flash-header` executable files should be considered as raw files with no OpenTitan flash headers. * `-K` / `--keep-tmp` do not automatically remove temporary files and directories on exit. The user is in charge of discarding any generated files and directories after execution. The paths to the @@ -181,11 +181,11 @@ This tool may be used in two ways, which can be combined: ### Extras +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. +* `-G` / `--log-time` show local time before each logged message * `-V` / `--vcp-verbose` can be repeated to increase verbosity of the QEMU virtual comm ports * `-v` / `--verbose` can be repeated to increase verbosity of the script, mostly for debug purpose. -* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. * `--quiet` only emit verbose log traces if an error is detected -* `--log-time` show local time before each logged message * `--log-udp` change the port of the UDP log service on specified UDP port. Use `0` to disable the service. * `--debug` enable the debug level for the selected logger, may be repeated diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index 979f0d8af4f7a..b79353af660b8 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -414,8 +414,8 @@ def _build_qemu_command(self, args: Namespace, raise ValueError('QEMU path is not defined') machine, xtype, fw_args, xexec = self._build_qemu_fw_args(args) qemu_args = [args.qemu, '-M', machine] - if args.otcfg: - qemu_args.extend(('-readconfig', self.abspath(args.otcfg))) + for otcfg in args.otcfg or []: + qemu_args.extend(('-readconfig', self.abspath(otcfg))) qemu_args.extend(fw_args) temp_files = defaultdict(set) if all((args.otp, args.otp_raw)): diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index b9ece161cab51..1d512abfbb61a 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -18,8 +18,7 @@ def hjload(*_, **__): # noqa: E301 """dummy func if HJSON module is not available""" return {} from os import close, linesep, unlink -from os.path import (basename, dirname, isfile, join as joinpath, normpath, - relpath) +from os.path import dirname, isfile, join as joinpath, normpath, relpath from tempfile import mkstemp from time import sleep from traceback import format_exc @@ -113,8 +112,8 @@ def main(): help='generate an eflash image file for MTD bus') files.add_argument('-f', '--flash', metavar='RAW', help='SPI flash image file') - files.add_argument('-g', '--otcfg', metavar='file', - help='configuration options for OpenTitan devices') + files.add_argument('-g', '--otcfg', metavar='CFGFILE', action='append', + help='configuration option file for OT devices') files.add_argument('-H', '--no-flash-header', action='store_const', const=True, help='application and/or bootloader files contain ' @@ -153,16 +152,16 @@ def main(): exe.add_argument('-Z', '--zero', action='store_true', help='do not error if no test can be executed') extra = argparser.add_argument_group(title='Extras') - extra.add_argument('-v', '--verbose', action='count', - help='increase verbosity') - extra.add_argument('-V', '--vcp-verbose', action='count', - help='increase verbosity of QEMU virtual comm ports') extra.add_argument('-d', dest='dbg', action='store_true', help='enable debug mode') extra.add_argument('--quiet', action='store_true', help='quiet logging: only be verbose on errors') - extra.add_argument('--log-time', action='store_true', + extra.add_argument('-G', '--log-time', action='store_true', help='show local time in log messages') + extra.add_argument('-V', '--vcp-verbose', action='count', + help='increase verbosity of QEMU virtual comm ports') + extra.add_argument('-v', '--verbose', action='count', + help='increase verbosity') extra.add_argument('--log-udp', type=int, metavar='UDP_PORT', help='Change UDP port for log messages, ' 'use 0 to disable') @@ -272,9 +271,10 @@ def main(): qopts = getattr(args, 'opts') or [] qopts.extend(cli_opts) setattr(args, 'opts', qopts) - if args.otcfg and not isfile(args.otcfg): - argparser.error(f'Invalid OpenTitan configuration file ' - f'{basename(args.otcfg)}') + for otcfg in args.otcfg or []: + if not isfile(otcfg): + argparser.error(f'Invalid OpenTitan configuration file ' + f'{otcfg}') # as the JSON configuration file may contain default value, the # argparser default method cannot be used to define default values, or # they would take precedence over the JSON defined ones From 022960f3c105f489398b78150bfb7bbf6a954a9f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Jun 2025 14:21:40 +0200 Subject: [PATCH 078/175] [ot] scripts/opentitan: cfggen.py: add parsing of clock definitions Signed-off-by: Emmanuel Blot --- scripts/opentitan/cfggen.py | 179 +++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 12654057210b5..828c87fe87ce9 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -14,7 +14,7 @@ from logging import getLogger from os.path import abspath, dirname, isdir, isfile, join as joinpath, normpath from traceback import format_exc -from typing import Optional +from typing import NamedTuple, Optional import re import sys @@ -34,13 +34,58 @@ def hjload(*_, **__): # noqa: E301 from ot.otp.const import OtpConstants from ot.otp.lifecycle import OtpLifecycle from ot.util.log import configure_loggers -from ot.util.misc import camel_to_snake_case +from ot.util.misc import camel_to_snake_case, to_bool OtParamRegex = str """Definition of a parameter to seek and how to shorten it.""" +class OtClock(NamedTuple): + """Clock definition.""" + + name: str + """Clock signal name.""" + + frequency: int + """Clock frequency in Hz.""" + + aon: bool + """Whether the clock is always on.""" + + ref: bool + """Whether the clock is a reference clock.""" + + +class OtDerivedClock(NamedTuple): + """Clock derived from a top level clock definition.""" + + name: str + """Clock signal name.""" + + source: str + """Clock source signal name.""" + + div: int + """Divider.""" + + +class OtClockGroup(NamedTuple): + """Clock logicial group definition.""" + + name: str + """Group name.""" + + sources: list[str] + """Clock source signal names.""" + + sw_cg: bool + """Whether clock group can be managed by SW.""" + + hint: bool + """Whether clock group can be hinted by SW.""" + + class OtConfiguration: """QEMU configuration file generator.""" @@ -53,6 +98,9 @@ def __init__(self): self._roms: dict[Optional[int], dict[str, str]] = {} self._otp: dict[str, str] = {} self._lc: dict[str, str] = {} + self._top_clocks: dict[str, OtClock] = {} + self._sub_clocks: dict[str, OtDerivedClock] = {} + self._clock_groups: dict[str, OtClockGroup] = {} self._top_name: Optional[str] = None @property @@ -76,6 +124,59 @@ def load_top_config(self, toppath: str) -> None: self._load_top_values(module, self._otp, False, r'RndCnst(.*)Init') continue + clocks = cfg.get('clocks', {}) + for clock in clocks.get('srcs', []): + name = clock['name'] + aon = to_bool(clock['aon'], False) + ref = to_bool(clock['ref'], False) + freq = int(clock['freq']) + self._top_clocks[name] = OtClock(name, freq, aon, ref) + for clock in clocks.get('derived_srcs', []): + name = clock['name'] + src = clock['src'] + aon = to_bool(clock['aon'], False) + freq = int(clock['freq']) + div = int(clock['div']) + src_clock = self._top_clocks.get(src) + if not src_clock: + raise ValueError(f'Invalid top clock {src} ' + f'referenced from {name}') + if src_clock.frequency // div != freq: + raise ValueError(f'Incoherent derived clock {name} frequency: ' + f'{src_clock.frequency}/{div} != {freq}') + if aon and not src_clock.aon: + raise ValueError(f'Incoherent derived clock {name} AON') + self._sub_clocks[name] = OtDerivedClock(name, src, div) + clock_names = set(self._top_clocks.keys()) + clock_names.update(set(self._sub_clocks.keys())) + for group in clocks.get('groups', []): + ext = group['src'] == 'ext' + if ext: + continue + name = group['name'] + hint = group['sw_cg'] == 'hint' + sw_cg = not hint and to_bool(group['sw_cg'], False) + clk_srcs = [] + for clk_name, clk_src in group.get('clocks', {}).items(): + if not hint: + exp_name = f'clk_{clk_src}_{name}' + if clk_name != exp_name: + raise ValueError(f'Unexpected clock {clk_name} in group' + f' {name} (exp: {exp_name})') + clk_srcs.append(clk_src) + else: + exp_prefix = f'clk_{clk_src}_' + if not clk_name.startswith(exp_prefix): + raise ValueError(f'Unexpected clock {clk_name} in group' + f' {name}') + src_name = clk_name[len(exp_prefix):] + clk_srcs.append(src_name) + if src_name in self._sub_clocks: + raise ValueError(f'Refinition of clock {src_name}') + self._sub_clocks[src_name] = OtDerivedClock(src_name, + clk_src, 1) + self._clock_groups[name] = OtClockGroup(name, clk_srcs, sw_cg, + hint) def load_lifecycle(self, lcpath: str) -> None: """Load LifeCycle data from RTL file.""" @@ -138,6 +239,9 @@ def save(self, variant: str, socid: Optional[str], count: Optional[int], self._generate_roms(cfg, socid, count or 1) self._generate_otp(cfg, variant, socid) self._generate_life_cycle(cfg, socid) + self._generate_ast(cfg, variant, socid) + self._generate_clkmgr(cfg, socid) + self._generate_pwrmgr(cfg, socid) if outpath: with open(outpath, 'wt') as ofp: cfg.write(ofp) @@ -227,6 +331,77 @@ def _generate_life_cycle(self, cfg: ConfigParser, lcdata = dict(sorted(lcdata.items())) cfg[f'ot_device "{lcname}"'] = lcdata + def _generate_ast(self, cfg: ConfigParser, variant: str, + socid: Optional[str] = None) -> None: + nameargs = [f'ot-ast-{variant}'] + if socid: + nameargs.append(socid) + clkname = '.'.join(nameargs) + clkdata = {} + topclockstr = ','.join(f'{c.name}:{c.frequency}' + for c in self._top_clocks.values()) + aonclockstr = ','.join(c.name for c in self._top_clocks.values() + if c.aon) + self.add_pair(clkdata, 'topclocks', topclockstr) + self.add_pair(clkdata, 'aonclocks', aonclockstr) + cfg[f'ot_device "{clkname}"'] = clkdata + + def _generate_clkmgr(self, cfg: ConfigParser, + socid: Optional[str] = None) -> None: + nameargs = ['ot-clkmgr'] + if socid: + nameargs.append(socid) + clkname = '.'.join(nameargs) + clkdata = {} + refclocks = [c for c in self._top_clocks.values() if c.ref] + if len(refclocks) > 1: + raise ValueError(f'Multiple reference clocks detected: ' + f'{", ".join(refclocks)}') + if refclocks: + clkrefname = refclocks[0].name + clfrefval = self._top_clocks.get(clkrefname) + if not clfrefval: + raise ValueError(f'Invalid reference clock {clkrefname}') + else: + clkrefname = None + clfrefval = None + topclockdefs = [] + for clkname, clkval in self._top_clocks.items(): + if clfrefval: + clkratio = clkval.frequency // clfrefval.frequency + else: + clkratio = 1 + topclockdefs.append(f'{clkname}:{clkratio}') + topclockstr = ','.join(topclockdefs) + subclockstr = ','.join(f'{c.name}:{c.source}:{c.div}' + for c in self._sub_clocks.values()) + groupstr = ','.join(f'{g.name}:{"+".join(sorted(g.sources))}' + for g in self._clock_groups.values()) + swcfgstr = ','.join(g.name for g in self._clock_groups.values() + if g.sw_cg) + hintstr = ','.join(g.name for g in self._clock_groups.values() + if g.hint) + self.add_pair(clkdata, 'topclocks', topclockstr) + if clkrefname: + self.add_pair(clkdata, 'refclock', clkrefname) + self.add_pair(clkdata, 'subclocks', subclockstr) + self.add_pair(clkdata, 'groups', groupstr) + self.add_pair(clkdata, 'swcfg', swcfgstr) + self.add_pair(clkdata, 'hint', hintstr) + cfg[f'ot_device "{clkname}"'] = clkdata + + def _generate_pwrmgr(self, cfg: ConfigParser, + socid: Optional[str] = None) -> None: + nameargs = ['ot-pwrmgr'] + if socid: + nameargs.append(socid) + pwrname = '.'.join(nameargs) + pwrdata = {} + clockstr = ','.join(c.name for c in self._top_clocks.values() + if not c.aon) + self.add_pair(pwrdata, 'clocks', clockstr) + cfg[f'ot_device "{pwrname}"'] = pwrdata + def main(): """Main routine""" From 237915848b9c305e47716a04ba08857e35d89f09 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 26 Jun 2025 19:05:48 +0200 Subject: [PATCH 079/175] [ot] scripts/opentitan: cfggen.py: parse module input clocks ... and optionally emit them in plain text. Signed-off-by: Emmanuel Blot --- docs/opentitan/cfggen.md | 10 +++++++- scripts/opentitan/cfggen.py | 48 ++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/docs/opentitan/cfggen.md b/docs/opentitan/cfggen.md index f8036c4e45efd..58b527d890983 100644 --- a/docs/opentitan/cfggen.md +++ b/docs/opentitan/cfggen.md @@ -12,7 +12,8 @@ the QEMU binary. ````text usage: cfggen.py [-h] [-T {darjeeling,earlgrey}] [-o CFG] [-c SV] [-l SV] - [-t HJSON] [-s SOCID] [-C COUNT] [-v] [-d] + [-t HJSON] [-s SOCID] [-C COUNT] [-a {config,clock}] [-v] + [-d] [OTDIR] OpenTitan QEMU configuration file generator. @@ -33,6 +34,10 @@ Modifiers: -s, --socid SOCID SoC identifier, if any -C, --count COUNT SoC count (default: 1) +Actions: + -a, --action {config,clock} + Action(s) to perform, default: config + Extras: -v, --verbose increase verbosity -d, --debug enable debug mode @@ -44,6 +49,9 @@ Extras: repository to analyze. It is used to generate the path towards the required files to parse, each of which can be overidden with options `-c`, `-l` and `-t`. +* `-a` specify one or more actions to execute. Default is to generate a configuration file. It is + also possible to emit the list of module input clocks in a plain text format. + * `-C` specify how many SoCs are used on the platform * `-c` alternative path to the `otp_ctrl_part_pkg.sv` file diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 828c87fe87ce9..3febf3b8cb79a 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -14,7 +14,7 @@ from logging import getLogger from os.path import abspath, dirname, isdir, isfile, join as joinpath, normpath from traceback import format_exc -from typing import NamedTuple, Optional +from typing import NamedTuple, Optional, TextIO import re import sys @@ -101,6 +101,7 @@ def __init__(self): self._top_clocks: dict[str, OtClock] = {} self._sub_clocks: dict[str, OtDerivedClock] = {} self._clock_groups: dict[str, OtClockGroup] = {} + self._mod_clocks: dict[str, list[str]] = {} self._top_name: Optional[str] = None @property @@ -177,6 +178,23 @@ def load_top_config(self, toppath: str) -> None: clk_src, 1) self._clock_groups[name] = OtClockGroup(name, clk_srcs, sw_cg, hint) + modules = cfg.get('module', []) + mod_clocks = {} + for module in modules: + type_ = module['type'] + if type_ in ('ast', 'clkmgr'): + continue + name = module['name'] + clk_srcs = module.get('clock_srcs', {}) + clk_grp = module.get('clock_group', '') + clocks = [] + for clk in clk_srcs.values(): + if isinstance(clk, dict): + clocks.append(f'{clk["group"]}.{clk["clock"]}') + else: + clocks.append(f'{clk_grp}.{clk}') + mod_clocks[name] = clocks + self._mod_clocks = mod_clocks def load_lifecycle(self, lcpath: str) -> None: """Load LifeCycle data from RTL file.""" @@ -230,8 +248,7 @@ def load_otp_constants(self, otppath: str) -> None: self._otp.update(otpconst.get_digest_pair('sram_data_key', 'sram')) def save(self, variant: str, socid: Optional[str], count: Optional[int], - outpath: Optional[str]) \ - -> None: + ofp: Optional[TextIO]) -> None: """Save QEMU configuration file using a INI-like file format, compatible with the `-readconfig` option of QEMU. """ @@ -242,11 +259,13 @@ def save(self, variant: str, socid: Optional[str], count: Optional[int], self._generate_ast(cfg, variant, socid) self._generate_clkmgr(cfg, socid) self._generate_pwrmgr(cfg, socid) - if outpath: - with open(outpath, 'wt') as ofp: - cfg.write(ofp) - else: - cfg.write(sys.stdout) + cfg.write(ofp) + + def show_clocks(self, ofp: Optional[TextIO]) -> None: + """List clock inputs for each module.""" + mod_max_len = max(map(len, self._mod_clocks.keys())) + for modname, modclocks in sorted(self._mod_clocks.items()): + print(f'{modname:{mod_max_len}s}', ', '.join(modclocks), file=ofp) @classmethod def add_pair(cls, data: dict[str, str], kname: str, value: str) -> None: @@ -410,6 +429,7 @@ def main(): 'darjeeling': 'dj', 'earlgrey': 'eg', } + actions = ['config', 'clock'] try: desc = sys.modules[__name__].__doc__.split('.', 1)[0].strip() argparser = ArgumentParser(description=f'{desc}.') @@ -432,6 +452,10 @@ def main(): help='SoC identifier, if any') mods.add_argument('-C', '--count', default=1, type=int, help='SoC count (default: 1)') + mods = argparser.add_argument_group(title='Actions') + mods.add_argument('-a', '--action', choices=actions, + action='append', default=[], + help=f'Action(s) to perform, default: {actions[0]}') extra = argparser.add_argument_group(title='Extras') extra.add_argument('-v', '--verbose', action='count', help='increase verbosity') @@ -449,6 +473,8 @@ def main(): topcfg = args.topcfg ot_dir = args.opentitan + if not args.action: + args.action.append(actions[0]) if not topcfg: if not args.opentitan: argparser.error('OTDIR is required is no top file is specified') @@ -511,7 +537,11 @@ def main(): cfg.load_lifecycle(lcpath) cfg.load_otp_constants(ocpath) - cfg.save(topvar, args.socid, args.count, args.out) + with open(args.out, 'wt') if args.out else sys.stdout as ofp: + if 'config' in args.action: + cfg.save(topvar, args.socid, args.count, ofp) + if 'clock' in args.action: + cfg.show_clocks(ofp) except (IOError, ValueError, ImportError) as exc: print(f'\nError: {exc}', file=sys.stderr) From 605667f46a39d435315db5d34528e8a1ebc8d484 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 23 Jun 2025 14:47:44 +0200 Subject: [PATCH 080/175] [ot] hw/opentitan: ot_clock_ctrl: create a new interface to manage clocks Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 3 ++ hw/opentitan/meson.build | 1 + hw/opentitan/ot_clock_ctrl.c | 42 ++++++++++++++++ include/hw/opentitan/ot_clock_ctrl.h | 71 ++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 hw/opentitan/ot_clock_ctrl.c create mode 100644 include/hw/opentitan/ot_clock_ctrl.h diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 25002fc91b86e..80c5cf0a0d013 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -23,6 +23,9 @@ config OT_AST_EG config OT_CLKMGR bool +config OT_CLOCK_CTRL + bool + config OT_COMMON bool diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build index 6ce0036b74ba3..7094bc49ee9f3 100644 --- a/hw/opentitan/meson.build +++ b/hw/opentitan/meson.build @@ -11,6 +11,7 @@ system_ss.add(when: 'CONFIG_OT_AON_TIMER', if_true: files('ot_aon_timer.c')) system_ss.add(when: 'CONFIG_OT_AST_DJ', if_true: files('ot_ast_dj.c')) system_ss.add(when: 'CONFIG_OT_AST_EG', if_true: files('ot_ast_eg.c')) system_ss.add(when: 'CONFIG_OT_CLKMGR', if_true: files('ot_clkmgr.c')) +system_ss.add(when: 'CONFIG_OT_CLOCK_CTRL', if_true: files('ot_clock_ctrl.c')) system_ss.add(when: 'CONFIG_OT_COMMON', if_true: files('ot_common.c')) system_ss.add(when: 'CONFIG_OT_CSRNG', if_true: [files('ot_csrng.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_DEV_PROXY', if_true: files('ot_dev_proxy.c')) diff --git a/hw/opentitan/ot_clock_ctrl.c b/hw/opentitan/ot_clock_ctrl.c new file mode 100644 index 0000000000000..11f0fdcf56e01 --- /dev/null +++ b/hw/opentitan/ot_clock_ctrl.c @@ -0,0 +1,42 @@ +/* + * QEMU OpenTitan Clock controller interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/opentitan/ot_clock_ctrl.h" + +static const TypeInfo ot_clock_ctrl_info = { + .name = TYPE_OT_CLOCK_CTRL_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(OtClockCtrlIfClass), +}; + +static void ot_clock_ctrl_register_types(void) +{ + type_register_static(&ot_clock_ctrl_info); +} + +type_init(ot_clock_ctrl_register_types); diff --git a/include/hw/opentitan/ot_clock_ctrl.h b/include/hw/opentitan/ot_clock_ctrl.h new file mode 100644 index 0000000000000..d59066790feed --- /dev/null +++ b/include/hw/opentitan/ot_clock_ctrl.h @@ -0,0 +1,71 @@ +/* + * QEMU OpenTitan Clock controller interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_OPENTITAN_OT_CLOCK_CTRL_H +#define HW_OPENTITAN_OT_CLOCK_CTRL_H + +#include "qom/object.h" + +#define TYPE_OT_CLOCK_CTRL_IF "ot-clock_ctrl_if" +typedef struct OtClockCtrlIfClass OtClockCtrlIfClass; +DECLARE_CLASS_CHECKERS(OtClockCtrlIfClass, OT_CLOCK_CTRL_IF, + TYPE_OT_CLOCK_CTRL_IF) +#define OT_CLOCK_CTRL_IF(_obj_) \ + INTERFACE_CHECK(OtClockCtrlIf, (_obj_), TYPE_OT_CLOCK_CTRL_IF) + +typedef struct OtClockCtrlIf OtClockCtrlIf; + +struct OtClockCtrlIfClass { + InterfaceClass parent_class; + + /* + * Enable or disable a clock source. + * + * @clk the clock source + * @enable whether to enable or disable the clock source + */ + void (*clock_enable)(OtClockCtrlIf *dev, const char *clkname, bool enable); + + /* + * Select the external clock. + * + * @enable whether to enable or disable the external clock. + */ + void (*clock_ext_freq_select)(OtClockCtrlIf *dev, bool enable); +}; + +/* + * Output clock (OT_CLOCK_CTRL_COUNT) lines + * + * IRQ signal carries the current clock value in Hz, as an unsigned value + * encoded in the `int` type, _i.e._ up to 4GHz. + */ +#define OT_CLOCK_CTRL_CLOCK_OUTPUT TYPE_OT_CLOCK_CTRL_IF "-clock-out" + +#define OT_CLOCK_CTRL_CLOCK_INPUT TYPE_OT_CLOCK_CTRL_IF "-clock-in" + +#endif /* HW_OPENTITAN_OT_CLOCK_CTRL_H */ From 3ae4fcb300d9b92db1ded270e2beeb414f5cad13 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 13:16:52 +0200 Subject: [PATCH 081/175] [ot] hw/riscv: ibex_common: create new helpers to define clock connections Signed-off-by: Emmanuel Blot --- hw/riscv/ibex_common.c | 45 ++++++++++++++++++++++++++++++ include/hw/riscv/ibex_common.h | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c index 7f137a44a30b4..1f83940823f32 100644 --- a/hw/riscv/ibex_common.c +++ b/hw/riscv/ibex_common.c @@ -41,6 +41,7 @@ #include "hw/misc/unimp.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "monitor/monitor.h" #include "sysemu/runstate.h" @@ -370,6 +371,49 @@ void ibex_connect_devices(DeviceState **devices, const IbexDeviceDef *defs, } } +void ibex_clock_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count) +{ + IbexClockSrcIfClass *ic = NULL; + IbexClockSrcIf *is = NULL; + int ii = -1; + + for (unsigned idx = 0; idx < count; idx++) { + DeviceState *dev = devices[idx]; + if (!dev) { + continue; + } + const IbexClockConnDef *clock = defs[idx].clock; + if (!clock) { + continue; + } + + while (clock->out.name && clock->in.name) { + if (clock->out.index != ii) { + /* new clock source */ + DeviceState *cs = devices[clock->out.index]; + ic = IBEX_CLOCK_SRC_IF_GET_CLASS(cs); + is = IBEX_CLOCK_SRC_IF(cs); + ii = clock->out.index; + } + + qemu_irq clk_irq = + qdev_get_gpio_in_named(dev, clock->in.name, clock->in.num); + if (!clk_irq) { + error_setg(&error_fatal, "no such clock '%s.%s[%d]'\n", + object_get_typename(OBJECT(dev)), clock->in.name, + clock->in.num); + } + + g_assert(ic && ic->get_clock_source); + const char *out_name = + ic->get_clock_source(is, clock->out.name, dev, &error_fatal); + qdev_connect_gpio_out_named(dev, out_name, 0, clk_irq); + clock++; + } + } +} + /* List of exported GPIOs */ typedef QLIST_HEAD(, NamedGPIOList) IbexXGPIOList; @@ -578,6 +622,7 @@ void ibex_configure_devices_with_id(DeviceState **devices, BusState *bus, ibex_identify_devices(devices, id_prop, id_value, id_prepend, count); } ibex_realize_devices(devices, bus, defs, count); + ibex_clock_devices(devices, defs, count); ibex_connect_devices(devices, defs, count); } diff --git a/include/hw/riscv/ibex_common.h b/include/hw/riscv/ibex_common.h index 08e5b71e9bb4b..459f011a244f0 100644 --- a/include/hw/riscv/ibex_common.h +++ b/include/hw/riscv/ibex_common.h @@ -132,6 +132,23 @@ typedef struct { int index; } IbexDeviceLinkDef; +typedef struct { + /* Clock source */ + struct { + /* Clock source device index */ + int index; + /* Clock name */ + const char *name; + } out; + /* Clock sink */ + struct { + /* Name of target input clock */ + const char *name; + /* Index of target input clock */ + int num; + } in; +} IbexClockConnDef; + /* Type of device property */ typedef enum { IBEX_PROP_TYPE_BOOL, @@ -200,6 +217,8 @@ struct IbexDeviceDef { const IbexDeviceLinkDef *link; /* Array of properties */ const IbexDevicePropDef *prop; + /* Array of clock sources */ + const IbexClockConnDef *clock; /* Array of GPIO export */ const IbexGpioExportDef *gpio_export; }; @@ -293,6 +312,18 @@ typedef struct { } \ } +/* + * Create clock connection entries, each arg is IbexClockConnDef definition + */ +#define IBEXCLOCKCONNDEFS(...) \ + (const IbexClockConnDef[]) \ + { \ + __VA_ARGS__, \ + { \ + .out.name = NULL \ + } \ + } + /* * Create device property entries, each arg is IbexDevicePropDef definition */ @@ -368,6 +399,23 @@ typedef struct { .index = (_idx_), \ } +/* + * Create a IbexClockConnDef to connect a clock output to a clock input + */ +#define IBEX_CLOCK_CONN(_out_idx_, _out_type_, _out_name_, _in_name_, \ + _in_idx_) \ + { \ + .out = { \ + .index = (_out_idx_), \ + .type = (_out_type_), \ + .name = (_out_name_), \ + }, \ + .in = { \ + .name = (_in_name_), \ + .num = (_in_idx_), \ + } \ + } + /* * Create a IbexGpioExportDef to export a GPIO */ @@ -444,6 +492,8 @@ void ibex_realize_system_devices(DeviceState **devices, const IbexDeviceDef *defs, unsigned count); void ibex_realize_devices(DeviceState **devices, BusState *bus, const IbexDeviceDef *defs, unsigned count); +void ibex_clock_devices(DeviceState **devices, const IbexDeviceDef *defs, + unsigned count); void ibex_connect_devices(DeviceState **devices, const IbexDeviceDef *defs, unsigned count); #define ibex_map_devices(_devs_, _mrs_, _defs_, _cnt_) \ From 41743ec2c23ef4ce2c540bff947b3160c4f8e88b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 13:16:13 +0200 Subject: [PATCH 082/175] [ot] hw/riscv: ibex_clock_src: create a new interface to manage clocks Signed-off-by: Emmanuel Blot --- hw/riscv/Kconfig | 3 ++ hw/riscv/ibex_clock_src.c | 42 ++++++++++++++++++++++ hw/riscv/meson.build | 1 + include/hw/riscv/ibex_clock_src.h | 58 +++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 hw/riscv/ibex_clock_src.c create mode 100644 include/hw/riscv/ibex_clock_src.h diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index f1fb6f98699e7..96532de736ced 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -7,6 +7,9 @@ config RISCV_NUMA config IBEX bool +config IBEX_CLOCK_SRC + bool + config IBEX_COMMON bool diff --git a/hw/riscv/ibex_clock_src.c b/hw/riscv/ibex_clock_src.c new file mode 100644 index 0000000000000..92ef0fc7ea7c5 --- /dev/null +++ b/hw/riscv/ibex_clock_src.c @@ -0,0 +1,42 @@ +/* + * QEMU Ibex Clock Source interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/riscv/ibex_clock_src.h" + +static const TypeInfo ibex_clock_src_info = { + .name = TYPE_IBEX_CLOCK_SRC_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(IbexClockSrcIfClass), +}; + +static void ibex_clock_src_register_types(void) +{ + type_register_static(&ibex_clock_src_info); +} + +type_init(ibex_clock_src_register_types); diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index e6272b20a35c0..33c9842fdc267 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -4,6 +4,7 @@ riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c')) riscv_ss.add(files('riscv_hart.c')) riscv_ss.add(when: 'CONFIG_IBEXDEMO', if_true: files('ibexdemo.c')) riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c')) +riscv_ss.add(when: 'CONFIG_IBEX_CLOCK_SRC', if_true: files('ibex_clock_src.c')) riscv_ss.add(when: 'CONFIG_IBEX_COMMON', if_true: files('ibex_common.c')) riscv_ss.add(when: 'CONFIG_IBEX_GPIO', if_true: files('ibex_gpio.c')) riscv_ss.add(when: 'CONFIG_OT_DARJEELING', if_true: files('ot_darjeeling.c')) diff --git a/include/hw/riscv/ibex_clock_src.h b/include/hw/riscv/ibex_clock_src.h new file mode 100644 index 0000000000000..6c1fbd5cccfce --- /dev/null +++ b/include/hw/riscv/ibex_clock_src.h @@ -0,0 +1,58 @@ +/* + * QEMU Ibex Clock Source interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_RISCV_IBEX_CLOCK_SRC_H +#define HW_RISCV_IBEX_CLOCK_SRC_H + +#include "qom/object.h" + +#define TYPE_IBEX_CLOCK_SRC_IF "ibex-clock_src_if" +typedef struct IbexClockSrcIfClass IbexClockSrcIfClass; +DECLARE_CLASS_CHECKERS(IbexClockSrcIfClass, IBEX_CLOCK_SRC_IF, + TYPE_IBEX_CLOCK_SRC_IF) +#define IBEX_CLOCK_SRC_IF(_obj_) \ + INTERFACE_CHECK(IbexClockSrcIf, (_obj_), TYPE_IBEX_CLOCK_SRC_IF) + +typedef struct IbexClockSrcIf IbexClockSrcIf; + +struct IbexClockSrcIfClass { + InterfaceClass parent_class; + + /* + * Get a clock source by its type/name, to connect to the specified sink + * device. The clock line may already be connected or not. + * + * @name clock name + * @sink the sink device for the clock line + * @errp the error to use for reporting an invalid request + * @return the name of IRQ line to carry the clock information + */ + const char *(*get_clock_source)(IbexClockSrcIf *ifd, const char *name, + const DeviceState *sink, Error **errp); +}; + +#endif /* HW_RISCV_IBEX_CLOCK_SRC_H */ From bfef364f676ef2da77e0a99d86d2bbb172b000f9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 18:09:20 +0200 Subject: [PATCH 083/175] [ot] hw/opentitan: ot_common: create clock connections on device configuration Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/opentitan/ot_common.c b/hw/opentitan/ot_common.c index 9afe83614f945..6864bdbf7b3ea 100644 --- a/hw/opentitan/ot_common.c +++ b/hw/opentitan/ot_common.c @@ -338,6 +338,7 @@ void ot_common_configure_devices_with_id( } ot_common_configure_device_opts(devices, count); ibex_realize_devices(devices, bus, defs, count); + ibex_clock_devices(devices, defs, count); ibex_connect_devices(devices, defs, count); } From 7a42c4dd348d23981955f277024b2e49def115eb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 18:10:03 +0200 Subject: [PATCH 084/175] [ot] hw/riscv: ibexdemo: create clock connections on device configuration Signed-off-by: Emmanuel Blot --- hw/riscv/Kconfig | 1 + hw/riscv/ibexdemo.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 96532de736ced..2c90c200111ce 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -105,6 +105,7 @@ config OT_EARLGREY config IBEXDEMO bool select IBEX + select IBEX_CLOCK_SRC select IBEX_COMMON select IBEXDEMO_GPIO select IBEXDEMO_SIMCTRL diff --git a/hw/riscv/ibexdemo.c b/hw/riscv/ibexdemo.c index 7c7cc90b93651..e8a468cce7495 100644 --- a/hw/riscv/ibexdemo.c +++ b/hw/riscv/ibexdemo.c @@ -380,6 +380,8 @@ static void ibexdemo_soc_realize(DeviceState *dev, Error **errp) ARRAY_SIZE(ibexdemo_soc_devices)); ibex_realize_system_devices(s->devices, ibexdemo_soc_devices, ARRAY_SIZE(ibexdemo_soc_devices)); + ibex_clock_devices(s->devices, ibexdemo_soc_devices, + ARRAY_SIZE(ibexdemo_soc_devices)); ibex_connect_devices(s->devices, ibexdemo_soc_devices, ARRAY_SIZE(ibexdemo_soc_devices)); From 0bc31df94ba40a4ec604cc3d90655cb1dd9f5cb6 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 23 Jun 2025 15:04:13 +0200 Subject: [PATCH 085/175] [ot] hw/opentitan: ot_ast_dj: implement ot_clock_src interface Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_ast_dj.c | 164 ++++++++++++++++++++++++++++++++++++++ hw/opentitan/trace-events | 2 + 3 files changed, 167 insertions(+) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 80c5cf0a0d013..c8a62b11c3cd2 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -15,6 +15,7 @@ config OT_AON_TIMER config OT_AST_DJ select OT_RANDOM_SRC + select OT_CLOCK_CTRL bool config OT_AST_EG diff --git a/hw/opentitan/ot_ast_dj.c b/hw/opentitan/ot_ast_dj.c index be89257d282f3..688ffb66ba7f9 100644 --- a/hw/opentitan/ot_ast_dj.c +++ b/hw/opentitan/ot_ast_dj.c @@ -33,12 +33,15 @@ #include "qemu/log.h" #include "qemu/timer.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_ast_dj.h" +#include "hw/opentitan/ot_clock_ctrl.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_random_src.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" #include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" #include "trace.h" @@ -136,14 +139,27 @@ typedef struct { bool avail; } OtASTDjRandom; +typedef struct { + char *name; + unsigned frequency; + IbexIRQ out; + bool aon; + bool active; +} OtASTDjClock; + struct OtASTDjState { SysBusDevice parent_obj; MemoryRegion mmio; OtASTDjRandom random; + GList *clocks; /* OtASTDjClock */ + uint32_t *regsa; uint32_t *regsb; + + char *cfg_topclocks; + char *cfg_aonclocks; }; struct OtASTDjClass { @@ -200,6 +216,134 @@ static void ot_ast_dj_random_scheduler(void *opaque) rnd->avail = true; } + +static const char *CFGSEP = ","; + +static gint ot_ast_dj_match_clock_by_name(gconstpointer a, gconstpointer b) +{ + const OtASTDjClock *ca = a; + const OtASTDjClock *cb = b; + + return strcmp(ca->name, cb->name); +} + +static OtASTDjClock *ot_ast_dj_find_clock(OtASTDjState *s, const char *name) +{ + OtASTDjClock clock = { .name = (char *)name }; + + GList *glist = + g_list_find_custom(s->clocks, &clock, &ot_ast_dj_match_clock_by_name); + + return glist ? glist->data : NULL; +} + +static void ot_ast_dj_reset_clock(gpointer data, gpointer user_data) +{ + OtASTDjState *s = user_data; + OtASTDjClock *clk = data; + (void)s; + + clk->active = clk->aon; +} + +static void ot_ast_dj_update_clock(gpointer data, gpointer user_data) +{ + OtASTDjState *s = user_data; + OtASTDjClock *clk = data; + (void)s; + + unsigned frequency = clk->active ? clk->frequency : 0; + trace_ot_ast_upate_clock(clk->name, frequency); + + ibex_irq_set(&clk->out, (int)frequency); +} + +static void ot_ast_dj_clock_enable(OtClockCtrlIf *dev, const char *clkname, + bool enable) +{ + OtASTDjState *s = OT_AST_DJ(dev); + + OtASTDjClock *clk = ot_ast_dj_find_clock(s, clkname); + g_assert(clk); + + clk->active = enable; + unsigned frequency = clk->active ? clk->frequency : 0; + + trace_ot_ast_upate_clock(clk->name, frequency); + + ibex_irq_set(&clk->out, (int)frequency); +} + +static void ot_ast_dj_clock_ext_freq_select(OtClockCtrlIf *dev, bool enable) +{ + OtASTDjState *s = OT_AST_DJ(dev); + + (void)s; + + qemu_log_mask(LOG_UNIMP, "%s: not implemented: %u\n", __func__, enable); +} + +static void ot_ast_dj_parse_clocks(OtASTDjState *s, Error **errp) +{ + if (!s->cfg_topclocks) { + error_setg(errp, "%s: topclocks config not defined", __func__); + return; + } + + if (!s->cfg_aonclocks) { + error_setg(errp, "%s: aonclocks config not defined", __func__); + return; + } + + char *config = g_strdup(s->cfg_topclocks); + for (char *clkbrk, *clkdesc = strtok_r(config, CFGSEP, &clkbrk); clkdesc; + clkdesc = strtok_r(NULL, CFGSEP, &clkbrk)) { + char clkname[16]; + unsigned clkfreq = 0; + unsigned length = 0; + int ret; + /* NOLINTNEXTLINE(cert-err34-c) */ + ret = sscanf(clkdesc, "%15[a-z0-9_]:%u%n", clkname, &clkfreq, &length); + if (ret != 2) { + error_setg(errp, "%s: invalid clock %s format: %d", __func__, + clkdesc, ret); + g_free(config); + return; + } + if (clkdesc[length]) { + error_setg(errp, "%s: trailing chars in subclock %s", __func__, + clkdesc); + g_free(config); + return; + } + + OtASTDjClock *clk = g_new0(OtASTDjClock, 1u); + clk->name = strdup(clkname); + clk->frequency = clkfreq; + char *clock_name = g_strdup_printf("clock-out-%s", clk->name); + ibex_qdev_init_irq(OBJECT(s), &clk->out, clock_name); + s->clocks = g_list_append(s->clocks, clk); + trace_ot_ast_create_clock(clk->name, clk->frequency, clock_name); + g_free(clock_name); + } + g_free(config); + + config = g_strdup(s->cfg_aonclocks); + for (char *clkbrk, *clkname = strtok_r(config, CFGSEP, &clkbrk); clkname; + clkname = strtok_r(NULL, CFGSEP, &clkbrk)) { + OtASTDjClock *clk = ot_ast_dj_find_clock(s, clkname); + if (!clk) { + error_setg(errp, "%s: invalid AON clock name %s", __func__, + clkname); + return; + } + + clk->aon = true; + clk->active = true; + } + g_free(config); +} + static uint64_t ot_ast_dj_regs_read(void *opaque, hwaddr addr, unsigned size) { OtASTDjState *s = opaque; @@ -339,6 +483,8 @@ static void ot_ast_dj_regs_write(void *opaque, hwaddr addr, uint64_t val64, }; static Property ot_ast_dj_properties[] = { + DEFINE_PROP_STRING("topclocks", OtASTDjState, cfg_topclocks), + DEFINE_PROP_STRING("aonclocks", OtASTDjState, cfg_aonclocks), DEFINE_PROP_END_OF_LIST(), }; @@ -405,6 +551,8 @@ static void ot_ast_dj_reset_enter(Object *obj, ResetType type) s->regsa[R_REGA36] = 0x24u; s->regsa[R_REGA37] = 0x25u; s->regsa[R_REGAL] = 0x26u; + + g_list_foreach(s->clocks, ot_ast_dj_reset_clock, s); } static void ot_ast_dj_reset_exit(Object *obj, ResetType type) @@ -419,6 +567,16 @@ static void ot_ast_dj_reset_exit(Object *obj, ResetType type) uint64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); timer_mod(rnd->timer, (int64_t)(now + OT_AST_DJ_RANDOM_FILL_RATE_NS)); + + g_list_foreach(s->clocks, ot_ast_dj_update_clock, s); +} + +static void ot_ast_dj_realize(DeviceState *dev, Error **errp) +{ + OtASTDjState *s = OT_AST_DJ(dev); + (void)errp; + + ot_ast_dj_parse_clocks(s, &error_fatal); } static void ot_ast_dj_init(Object *obj) @@ -443,6 +601,7 @@ static void ot_ast_dj_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); (void)data; + dc->realize = &ot_ast_dj_realize; device_class_set_props(dc, ot_ast_dj_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); @@ -454,6 +613,10 @@ static void ot_ast_dj_class_init(ObjectClass *klass, void *data) OtRandomSrcIfClass *rdc = OT_RANDOM_SRC_IF_CLASS(klass); rdc->get_random_values = &ot_ast_dj_get_random; + + OtClockCtrlIfClass *cc = OT_CLOCK_CTRL_IF_CLASS(klass); + cc->clock_enable = &ot_ast_dj_clock_enable; + cc->clock_ext_freq_select = &ot_ast_dj_clock_ext_freq_select; } static const TypeInfo ot_ast_dj_info = { @@ -466,6 +629,7 @@ static const TypeInfo ot_ast_dj_info = { .interfaces = (InterfaceInfo[]){ { TYPE_OT_RANDOM_SRC_IF }, + { TYPE_OT_CLOCK_CTRL_IF }, {}, }, }; diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index fe3826a42202e..77f9f63e100fc 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -41,9 +41,11 @@ ot_aon_timer_write(const char *id, uint32_t addr, const char * regname, uint32_t # ot_ast.c +ot_ast_create_clock(const char *clock, unsigned frequency, const char *out) "%s @ %u Hz, out: %s" ot_ast_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_ast_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_ast_no_entropy(unsigned count) "only %u words available" +ot_ast_upate_clock(const char *clock, unsigned frequency) "%s @ %u Hz" # ot_clkmgr.c From e43a9af45bf8d0426377dd17f159341284301ef9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Jun 2025 16:32:34 +0200 Subject: [PATCH 086/175] [ot] hw/opentitan: ot_ast_dj: implement ibex_clock_src interface Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_ast_dj.c | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index c8a62b11c3cd2..f8718f641590d 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -14,6 +14,7 @@ config OT_AON_TIMER bool config OT_AST_DJ + select IBEX_CLOCK_SRC select OT_RANDOM_SRC select OT_CLOCK_CTRL bool diff --git a/hw/opentitan/ot_ast_dj.c b/hw/opentitan/ot_ast_dj.c index 688ffb66ba7f9..2a0b97fd62ca2 100644 --- a/hw/opentitan/ot_ast_dj.c +++ b/hw/opentitan/ot_ast_dj.c @@ -40,6 +40,7 @@ #include "hw/opentitan/ot_random_src.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" @@ -143,6 +144,8 @@ typedef struct { char *name; unsigned frequency; IbexIRQ out; + char *irq_name; + const DeviceState *sink; bool aon; bool active; } OtASTDjClock; @@ -258,6 +261,29 @@ static void ot_ast_dj_update_clock(gpointer data, gpointer user_data) ibex_irq_set(&clk->out, (int)frequency); } +static const char * +ot_ast_dj_get_clock_source(IbexClockSrcIf *ifd, const char *name, + const DeviceState *sink, Error **errp) +{ + OtASTDjState *s = OT_AST_DJ(ifd); + + OtASTDjClock *clk = ot_ast_dj_find_clock(s, name); + if (!clk) { + error_setg(errp, "%s: AST: no such clock: %s", __func__, name); + return NULL; + } + + if (clk->sink && clk->sink != sink) { + error_setg(errp, "%s: AST supports a unique sink per clock: %s", + __func__, name); + return NULL; + } + + clk->sink = sink; + + return clk->irq_name; +} + static void ot_ast_dj_clock_enable(OtClockCtrlIf *dev, const char *clkname, bool enable) { @@ -320,11 +346,10 @@ static void ot_ast_dj_parse_clocks(OtASTDjState *s, Error **errp) OtASTDjClock *clk = g_new0(OtASTDjClock, 1u); clk->name = strdup(clkname); clk->frequency = clkfreq; - char *clock_name = g_strdup_printf("clock-out-%s", clk->name); - ibex_qdev_init_irq(OBJECT(s), &clk->out, clock_name); + clk->irq_name = g_strdup_printf("clock-out-%s", clk->name); + ibex_qdev_init_irq(OBJECT(s), &clk->out, clk->irq_name); s->clocks = g_list_append(s->clocks, clk); - trace_ot_ast_create_clock(clk->name, clk->frequency, clock_name); - g_free(clock_name); + trace_ot_ast_create_clock(clk->name, clk->frequency, clk->irq_name); } g_free(config); @@ -614,6 +639,9 @@ static void ot_ast_dj_class_init(ObjectClass *klass, void *data) OtRandomSrcIfClass *rdc = OT_RANDOM_SRC_IF_CLASS(klass); rdc->get_random_values = &ot_ast_dj_get_random; + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_CLASS(klass); + ic->get_clock_source = &ot_ast_dj_get_clock_source; + OtClockCtrlIfClass *cc = OT_CLOCK_CTRL_IF_CLASS(klass); cc->clock_enable = &ot_ast_dj_clock_enable; cc->clock_ext_freq_select = &ot_ast_dj_clock_ext_freq_select; @@ -629,6 +657,7 @@ static const TypeInfo ot_ast_dj_info = { .interfaces = (InterfaceInfo[]){ { TYPE_OT_RANDOM_SRC_IF }, + { TYPE_IBEX_CLOCK_SRC_IF }, { TYPE_OT_CLOCK_CTRL_IF }, {}, }, From 2878a814a34223051e1a09fc646d91e0bf4aff82 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 23 Jun 2025 15:30:16 +0200 Subject: [PATCH 087/175] [ot] hw/opentitan: ot_ast_eg: implement ibex_clock_src and ot_clock_ctrl interfaces Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 2 + hw/opentitan/ot_ast_eg.c | 214 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index f8718f641590d..8644c9a6cf42c 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -20,6 +20,8 @@ config OT_AST_DJ bool config OT_AST_EG + select IBEX_CLOCK_SRC + select OT_CLOCK_CTRL bool config OT_CLKMGR diff --git a/hw/opentitan/ot_ast_eg.c b/hw/opentitan/ot_ast_eg.c index 9981f05f0c6f7..08d2b11028482 100644 --- a/hw/opentitan/ot_ast_eg.c +++ b/hw/opentitan/ot_ast_eg.c @@ -32,10 +32,15 @@ #include "qemu/guest-random.h" #include "qemu/log.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_ast_eg.h" +#include "hw/opentitan/ot_clock_ctrl.h" +#include "hw/opentitan/ot_common.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" #include "trace.h" @@ -127,13 +132,28 @@ static const char REGB_NAMES[REGSB_COUNT][6U] = { }; #undef REG_NAME_ENTRY +typedef struct { + char *name; + unsigned frequency; + IbexIRQ out; + char *irq_name; + const DeviceState *sink; + bool aon; + bool active; +} OtASTEgClock; + struct OtASTEgState { SysBusDevice parent_obj; MemoryRegion mmio; + GList *clocks; /* OtASTEgClock */ + uint32_t *regsa; uint32_t *regsb; + + char *cfg_topclocks; + char *cfg_aonclocks; }; struct OtASTEgClass { @@ -150,9 +170,156 @@ void ot_ast_eg_getrandom(void *buf, size_t len) qemu_guest_getrandom_nofail(buf, len); } -/* -------------------------------------------------------------------------- */ -/* Private implementation */ -/* -------------------------------------------------------------------------- */ + +static const char *CFGSEP = ","; + +static gint ot_ast_eg_match_clock_by_name(gconstpointer a, gconstpointer b) +{ + const OtASTEgClock *ca = a; + const OtASTEgClock *cb = b; + + return strcmp(ca->name, cb->name); +} + +static OtASTEgClock *ot_ast_eg_find_clock(OtASTEgState *s, const char *name) +{ + OtASTEgClock clock = { .name = (char *)name }; + + GList *glist = + g_list_find_custom(s->clocks, &clock, &ot_ast_eg_match_clock_by_name); + + return glist ? glist->data : NULL; +} + +static void ot_ast_eg_reset_clock(gpointer data, gpointer user_data) +{ + OtASTEgState *s = user_data; + OtASTEgClock *clk = data; + (void)s; + + clk->active = clk->aon; +} + +static void ot_ast_eg_update_clock(gpointer data, gpointer user_data) +{ + OtASTEgState *s = user_data; + OtASTEgClock *clk = data; + (void)s; + + unsigned frequency = clk->active ? clk->frequency : 0; + trace_ot_ast_upate_clock(clk->name, frequency); + + ibex_irq_set(&clk->out, (int)frequency); +} + +static const char * +ot_ast_eg_get_clock_source(IbexClockSrcIf *ifd, const char *name, + const DeviceState *sink, Error **errp) +{ + OtASTEgState *s = OT_AST_EG(ifd); + + OtASTEgClock *clk = ot_ast_eg_find_clock(s, name); + if (!clk) { + error_setg(errp, "%s: AST: no such clock: %s", __func__, name); + return NULL; + } + + if (clk->sink && clk->sink != sink) { + error_setg(errp, "%s: AST supports a unique sink per clock: %s", + __func__, name); + return NULL; + } + + clk->sink = sink; + + return clk->irq_name; +} + +static void ot_ast_eg_clock_enable(OtClockCtrlIf *dev, const char *clkname, + bool enable) +{ + OtASTEgState *s = OT_AST_EG(dev); + + OtASTEgClock *clk = ot_ast_eg_find_clock(s, clkname); + g_assert(clk); + + clk->active = enable; + unsigned frequency = clk->active ? clk->frequency : 0; + + trace_ot_ast_upate_clock(clk->name, frequency); + + ibex_irq_set(&clk->out, (int)frequency); +} + +static void ot_ast_eg_clock_ext_freq_select(OtClockCtrlIf *dev, bool enable) +{ + OtASTEgState *s = OT_AST_EG(dev); + + (void)s; + + qemu_log_mask(LOG_UNIMP, "%s: not implemented: %u\n", __func__, enable); +} + +static void ot_ast_eg_parse_clocks(OtASTEgState *s, Error **errp) +{ + if (!s->cfg_topclocks) { + error_setg(errp, "%s: topclocks config not defined", __func__); + return; + } + + if (!s->cfg_aonclocks) { + error_setg(errp, "%s: aonclocks config not defined", __func__); + return; + } + + char *config = g_strdup(s->cfg_topclocks); + for (char *clkbrk, *clkdesc = strtok_r(config, CFGSEP, &clkbrk); clkdesc; + clkdesc = strtok_r(NULL, CFGSEP, &clkbrk)) { + char clkname[16]; + unsigned clkfreq = 0; + unsigned length = 0; + int ret; + /* NOLINTNEXTLINE(cert-err34-c) */ + ret = sscanf(clkdesc, "%15[a-z0-9_]:%u%n", clkname, &clkfreq, &length); + if (ret != 2) { + error_setg(errp, "%s: invalid clock %s format: %d", __func__, + clkdesc, ret); + g_free(config); + return; + } + if (clkdesc[length]) { + error_setg(errp, "%s: trailing chars in subclock %s", __func__, + clkdesc); + g_free(config); + return; + } + + OtASTEgClock *clk = g_new0(OtASTEgClock, 1u); + clk->name = strdup(clkname); + clk->frequency = clkfreq; + clk->irq_name = g_strdup_printf("clock-out-%s", clk->name); + ibex_qdev_init_irq(OBJECT(s), &clk->out, clk->irq_name); + s->clocks = g_list_append(s->clocks, clk); + trace_ot_ast_create_clock(clk->name, clk->frequency, clk->irq_name); + } + g_free(config); + + config = g_strdup(s->cfg_aonclocks); + for (char *clkbrk, *clkname = strtok_r(config, CFGSEP, &clkbrk); clkname; + clkname = strtok_r(NULL, CFGSEP, &clkbrk)) { + OtASTEgClock *clk = ot_ast_eg_find_clock(s, clkname); + if (!clk) { + error_setg(errp, "%s: invalid AON clock name %s", __func__, + clkname); + return; + } + + clk->aon = true; + clk->active = true; + } + g_free(config); +} + static uint64_t ot_ast_eg_regs_read(void *opaque, hwaddr addr, unsigned size) { @@ -293,6 +460,8 @@ static void ot_ast_eg_regs_write(void *opaque, hwaddr addr, uint64_t val64, }; static Property ot_ast_eg_properties[] = { + DEFINE_PROP_STRING("topclocks", OtASTEgState, cfg_topclocks), + DEFINE_PROP_STRING("aonclocks", OtASTEgState, cfg_aonclocks), DEFINE_PROP_END_OF_LIST(), }; @@ -354,6 +523,28 @@ static void ot_ast_eg_reset_enter(Object *obj, ResetType type) s->regsa[R_REGA36] = 0x24u; s->regsa[R_REGA37] = 0x25u; s->regsa[R_REGAL] = 0x26u; + + g_list_foreach(s->clocks, ot_ast_eg_reset_clock, s); +} + +static void ot_ast_eg_reset_exit(Object *obj, ResetType type) +{ + OtASTEgClass *c = OT_AST_EG_GET_CLASS(obj); + OtASTEgState *s = OT_AST_EG(obj); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + g_list_foreach(s->clocks, ot_ast_eg_update_clock, s); +} + +static void ot_ast_eg_realize(DeviceState *dev, Error **errp) +{ + OtASTEgState *s = OT_AST_EG(dev); + (void)errp; + + ot_ast_eg_parse_clocks(s, &error_fatal); } static void ot_ast_eg_init(Object *obj) @@ -373,13 +564,22 @@ static void ot_ast_eg_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); (void)data; + dc->realize = &ot_ast_eg_realize; device_class_set_props(dc, ot_ast_eg_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); ResettableClass *rc = RESETTABLE_CLASS(klass); OtASTEgClass *ac = OT_AST_EG_CLASS(klass); - resettable_class_set_parent_phases(rc, &ot_ast_eg_reset_enter, NULL, NULL, + resettable_class_set_parent_phases(rc, &ot_ast_eg_reset_enter, NULL, + &ot_ast_eg_reset_exit, &ac->parent_phases); + + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_CLASS(klass); + ic->get_clock_source = &ot_ast_eg_get_clock_source; + + OtClockCtrlIfClass *cc = OT_CLOCK_CTRL_IF_CLASS(klass); + cc->clock_enable = &ot_ast_eg_clock_enable; + cc->clock_ext_freq_select = &ot_ast_eg_clock_ext_freq_select; } static const TypeInfo ot_ast_eg_info = { @@ -389,6 +589,12 @@ static const TypeInfo ot_ast_eg_info = { .instance_init = &ot_ast_eg_init, .class_size = sizeof(OtASTEgClass), .class_init = &ot_ast_eg_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_IBEX_CLOCK_SRC_IF }, + { TYPE_OT_CLOCK_CTRL_IF }, + {}, + }, }; static void ot_ast_eg_register_types(void) From 7622b7e7dc972a93ec187187d4e4042fcb95131f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 14:28:36 +0200 Subject: [PATCH 088/175] [ot] hw/opentitan: ot_clkmgr: add clock configuration and distribution Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_clkmgr.c | 1385 +++++++++++++++++++++++------- hw/opentitan/trace-events | 18 +- include/hw/opentitan/ot_clkmgr.h | 5 + 4 files changed, 1090 insertions(+), 319 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 8644c9a6cf42c..29fee84dcabca 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -25,6 +25,7 @@ config OT_AST_EG bool config OT_CLKMGR + select IBEX_CLOCK_SRC bool config OT_CLOCK_CTRL diff --git a/hw/opentitan/ot_clkmgr.c b/hw/opentitan/ot_clkmgr.c index 2efb51977685f..ce0a5a0214a83 100644 --- a/hw/opentitan/ot_clkmgr.c +++ b/hw/opentitan/ot_clkmgr.c @@ -30,21 +30,18 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" #include "trace.h" -#define PARAM_NUM_GROUPS 7u -#define PARAM_NUM_SW_GATEABLE_CLOCKS 4u -#define PARAM_NUM_HINTABLE_CLOCKS 4u -#define PARAM_NUM_ALERTS 2u - /* clang-format off */ REG32(ALERT_TEST, 0x0u) FIELD(ALERT_TEST, RECOV_FAULT, 0u, 1u) @@ -61,63 +58,32 @@ REG32(JITTER_REGWEN, 0x10u) REG32(JITTER_ENABLE, 0x14u) FIELD(JITTER_ENABLE, VAL, 0, 4u) REG32(CLK_ENABLES, 0x18u) + /* seems field order is randomized */ FIELD(CLK_ENABLES, CLK_IO_DIV4_PERI_EN, 0u, 1u) FIELD(CLK_ENABLES, CLK_IO_DIV2_PERI_EN, 1u, 1u) FIELD(CLK_ENABLES, CLK_IO_PERI_EN, 2u, 1u) FIELD(CLK_ENABLES, CLK_USB_PERI_EN, 3u, 1u) REG32(CLK_HINTS, 0x1cu) - SHARED_FIELD(CLK_HINTS_MAIN_AES, (unsigned)OT_CLKMGR_HINT_AES, 1u) - SHARED_FIELD(CLK_HINTS_MAIN_HMAC, (unsigned)OT_CLKMGR_HINT_HMAC, 1u) - SHARED_FIELD(CLK_HINTS_MAIN_KMAC, (unsigned)OT_CLKMGR_HINT_KMAC, 1u) - SHARED_FIELD(CLK_HINTS_MAIN_OTBN, (unsigned)OT_CLKMGR_HINT_OTBN, 1u) REG32(CLK_HINTS_STATUS, 0x20u) REG32(MEASURE_CTRL_REGWEN, 0x24u) FIELD(MEASURE_CTRL_REGWEN, EN, 0u, 1u) -REG32(IO_MEAS_CTRL_EN, 0x28u) - FIELD(IO_MEAS_CTRL_EN, EN, 0u, 4u) -REG32(IO_MEAS_CTRL_SHADOWED, 0x2cu) - FIELD(IO_MEAS_CTRL_SHADOWED, HI, 0u, 10u) - FIELD(IO_MEAS_CTRL_SHADOWED, LO, 10u, 10u) -REG32(IO_DIV2_MEAS_CTRL_EN, 0x30u) - FIELD(IO_DIV2_MEAS_CTRL_EN, EN, 0u, 4u) -REG32(IO_DIV2_MEAS_CTRL_SHADOWED, 0x34u) - FIELD(IO_DIV2_MEAS_CTRL_SHADOWED, HI, 0u, 9u) - FIELD(IO_DIV2_MEAS_CTRL_SHADOWED, LO, 9u, 9u) -REG32(IO_DIV4_MEAS_CTRL_EN, 0x38u) - FIELD(IO_DIV4_MEAS_CTRL_EN, EN, 0u, 4u) -REG32(IO_DIV4_MEAS_CTRL_SHADOWED, 0x3cu) - FIELD(IO_DIV4_MEAS_CTRL_SHADOWED, HI, 0u, 8u) - FIELD(IO_DIV4_MEAS_CTRL_SHADOWED, LO, 8u, 8u) -REG32(MAIN_MEAS_CTRL_EN, 0x40u) - FIELD(MAIN_MEAS_CTRL_EN, EN, 0u, 4u) -REG32(MAIN_MEAS_CTRL_SHADOWED, 0x44u) - FIELD(MAIN_MEAS_CTRL_SHADOWED, HI, 0u, 10u) - FIELD(MAIN_MEAS_CTRL_SHADOWED, LO, 10u, 10u) -REG32(USB_MEAS_CTRL_EN, 0x48u) - FIELD(USB_MEAS_CTRL_EN, EN, 0u, 4u) -REG32(USB_MEAS_CTRL_SHADOWED, 0x4cu) - FIELD(USB_MEAS_CTRL_SHADOWED, HI, 0u, 9u) - FIELD(USB_MEAS_CTRL_SHADOWED, LO, 9u, 9u) -REG32(RECOV_ERR_CODE, 0x50u) +SHARED_FIELD(MEAS_CTRL_EN, 0u, 4u) +SHARED_FIELD(MEAS_CTRL_SHADOWED_HI, 0u, 10u) +SHARED_FIELD(MEAS_CTRL_SHADOWED_LO, 10u, 10u) +/* not the real address, they are offset by (2 * measure_count) * 4 */ +REG32(RECOV_ERR_CODE, 0x28u) FIELD(RECOV_ERR_CODE, SHADOW_UPDATE_ERR, 0u, 1u) - FIELD(RECOV_ERR_CODE, IO_MEASURE_ERR, 1u, 1u) - FIELD(RECOV_ERR_CODE, IO_DIV2_MEASURE_ERR, 2u, 1u) - FIELD(RECOV_ERR_CODE, IO_DIV4_MEASURE_ERR, 3u, 1u) - FIELD(RECOV_ERR_CODE, MAIN_MEASURE_ERR, 4u, 1u) - FIELD(RECOV_ERR_CODE, USB_MEASURE_ERR, 5u, 1u) - FIELD(RECOV_ERR_CODE, IO_TIMEOUT_ERR, 6u, 1u) - FIELD(RECOV_ERR_CODE, IO_DIV2_TIMEOUT_ERR, 7u, 1u) - FIELD(RECOV_ERR_CODE, IO_DIV4_TIMEOUT_ERR, 8u, 1u) - FIELD(RECOV_ERR_CODE, MAIN_TIMEOUT_ERR, 9u, 1u) - FIELD(RECOV_ERR_CODE, USB_TIMEOUT_ERR, 10u, 1u) -REG32(FATAL_ERR_CODE, 0x54u) +REG32(FATAL_ERR_CODE, 0x2cu) FIELD(FATAL_ERR_CODE, REG_INTG, 0u, 1u) FIELD(FATAL_ERR_CODE, IDLE_CNT, 1u, 1u) FIELD(FATAL_ERR_CODE, SHADOW_STORAGE_ERR, 2u, 1u) /* clang-format on */ +REG32(MEASURE_REG_BASE, A_RECOV_ERR_CODE) + #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) +/* last statically defined register */ #define R_LAST_REG (R_FATAL_ERR_CODE) #define REGS_COUNT (R_LAST_REG + 1u) #define REGS_SIZE (REGS_COUNT * sizeof(uint32_t)) @@ -126,75 +92,178 @@ REG32(FATAL_ERR_CODE, 0x54u) #define ALERT_TEST_MASK \ (R_ALERT_TEST_RECOV_FAULT_MASK | R_ALERT_TEST_FATAL_FAULT_MASK) -#define CLK_ENABLES_MASK \ - (R_CLK_ENABLES_CLK_IO_DIV4_PERI_EN_MASK | \ - R_CLK_ENABLES_CLK_IO_DIV2_PERI_EN_MASK | \ - R_CLK_ENABLES_CLK_IO_PERI_EN_MASK | R_CLK_ENABLES_CLK_USB_PERI_EN_MASK) -#define CLK_HINTS_MASK \ - (CLK_HINTS_MAIN_AES_MASK | CLK_HINTS_MAIN_HMAC_MASK | \ - CLK_HINTS_MAIN_KMAC_MASK | CLK_HINTS_MAIN_OTBN_MASK) -#define RECOV_ERR_CODE_MASK \ - (R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_MEASURE_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_DIV2_MEASURE_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_DIV4_MEASURE_ERR_MASK | \ - R_RECOV_ERR_CODE_MAIN_MEASURE_ERR_MASK | \ - R_RECOV_ERR_CODE_USB_MEASURE_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_TIMEOUT_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_DIV2_TIMEOUT_ERR_MASK | \ - R_RECOV_ERR_CODE_IO_DIV4_TIMEOUT_ERR_MASK | \ - R_RECOV_ERR_CODE_MAIN_TIMEOUT_ERR_MASK | \ - R_RECOV_ERR_CODE_USB_TIMEOUT_ERR_MASK) #define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) static const char *REG_NAMES[REGS_COUNT] = { - REG_NAME_ENTRY(ALERT_TEST), - REG_NAME_ENTRY(EXTCLK_CTRL_REGWEN), - REG_NAME_ENTRY(EXTCLK_CTRL), - REG_NAME_ENTRY(EXTCLK_STATUS), - REG_NAME_ENTRY(JITTER_REGWEN), - REG_NAME_ENTRY(JITTER_ENABLE), - REG_NAME_ENTRY(CLK_ENABLES), - REG_NAME_ENTRY(CLK_HINTS), - REG_NAME_ENTRY(CLK_HINTS_STATUS), - REG_NAME_ENTRY(MEASURE_CTRL_REGWEN), - REG_NAME_ENTRY(IO_MEAS_CTRL_EN), - REG_NAME_ENTRY(IO_MEAS_CTRL_SHADOWED), - REG_NAME_ENTRY(IO_DIV2_MEAS_CTRL_EN), - REG_NAME_ENTRY(IO_DIV2_MEAS_CTRL_SHADOWED), - REG_NAME_ENTRY(IO_DIV4_MEAS_CTRL_EN), - REG_NAME_ENTRY(IO_DIV4_MEAS_CTRL_SHADOWED), - REG_NAME_ENTRY(MAIN_MEAS_CTRL_EN), - REG_NAME_ENTRY(MAIN_MEAS_CTRL_SHADOWED), - REG_NAME_ENTRY(USB_MEAS_CTRL_EN), - REG_NAME_ENTRY(USB_MEAS_CTRL_SHADOWED), - REG_NAME_ENTRY(RECOV_ERR_CODE), - REG_NAME_ENTRY(FATAL_ERR_CODE), + REG_NAME_ENTRY(ALERT_TEST), REG_NAME_ENTRY(EXTCLK_CTRL_REGWEN), + REG_NAME_ENTRY(EXTCLK_CTRL), REG_NAME_ENTRY(EXTCLK_STATUS), + REG_NAME_ENTRY(JITTER_REGWEN), REG_NAME_ENTRY(JITTER_ENABLE), + REG_NAME_ENTRY(CLK_ENABLES), REG_NAME_ENTRY(CLK_HINTS), + REG_NAME_ENTRY(CLK_HINTS_STATUS), REG_NAME_ENTRY(MEASURE_CTRL_REGWEN), + REG_NAME_ENTRY(RECOV_ERR_CODE), REG_NAME_ENTRY(FATAL_ERR_CODE), }; #undef REG_NAME_ENTRY -enum { - ALERT_RECOVERABLE, - ALERT_FATAL, -}; +enum { ALERT_RECOVERABLE, ALERT_FATAL, ALERT_COUNT }; + +/* note: cannot use strlcpy as CentOS 7 (...) does not support it */ +#define strbcpy(_b_, _d_, _s_) \ + do { \ + size_t l = ARRAY_SIZE(_b_) - 1u; \ + const char *end = (_b_) + l; \ + g_assert(end > (_d_)); \ + strncpy((_d_), (_s_), (size_t)((uintptr_t)end) - ((uintptr_t)(_d_))); \ + } while (0) + +/* + * Any clock. + * + * A clock may have derived clocks, denoted subclocks here, which are in sync + * with their parent, but beat at a lower frequency, the ratio being stored + * in the divider field. + * + * A clock may have outputs (leaf clocks) which are connected to other OT + * devices via OtClkMgrClockSink. + * + * All clocks are instantiated at device realization time, from the clock + * properties fields (which are usually loaded from a QEMU readconf file). + */ +typedef struct { + char *name; /* clock name */ + GList *subclocks; /* weakrefs to OtClkMgrClock */ + GList *outputs; /* wekrefs to OtClkMgrClockOutput */ + unsigned divider; /* divider applied on parent clock if any (0 if top) */ + unsigned ratio; /* ratio w/ reference clock (may be 0) */ + bool ref; /* reference clock */ + bool loose; /* clock which is declared but not connected */ +} OtClkMgrClock; + +/* + * Logical clock group. + * + * A group defines how clock can be activated/disabled, depending on the group + * property. + * + * Each group may reference one or more physical clocks, and each clock may be + * referenced in one or may groups. + * + * A group may be fully configured by SW, or only receive deactivation hints, + * depending on the group property. + * + * All groups are instantiated at device realization time, from the clock + * properties fields (which are usually loaded from a QEMU readconf file), + * which also define the group properties (if any). + */ +typedef struct { + char *name; /* group name */ + GList *clocks; /* weakrefs to OtClkMgrClock */ + bool sw_cg; /* software configurable */ + bool hint; /* software hintable */ +} OtClkMgrClockGroup; + +/* + * Each clock output defines a unique physical (clock, group) pair. The clock + * output beats at the pace of its clock, while its activation is driven by its + * group. + * + * Each clock output may be connected to many output. To match the QEMU IRQ API, + * each IRQ connection to another device is managed as clock sinks. All clock + * sinks of a clock group behave (beat and active) the same. + * + * Clock outputs are lazily instantiated whenever a remote OT device queries the + * clock manager via the #get_clock_source API, or when an output is defined as + * a SW configurable output. This avoid creating useless clock output instances + * as most defined HW clocks are not actually used in the QEMU implementation. + */ +typedef struct { + OtClkMgrClock *clock; /* weak ref */ + const OtClkMgrClockGroup *group; /* weak ref */ + GList *sinks; /* OtClkMgrClockSink */ + unsigned frequency; /* computed frequency */ + bool disabled; /* whether the output is disabled */ +} OtClkMgrClockOutput; + +/* + * A clock sink represents a unique connection from a clock output to a single + * OT device clock input. Its name is dynmically generated based on its parent + * output and the number of connected OT devices of this output. + * + * Clock sinks are lazily instantiated, when a connection for the specified OT + * device and the selected clock output are queried via the #get_clock_source + * API. + * + * It is guaranteed that an OT device querying the same clock receives the same + * IRQ name. + */ +typedef struct { + const char *irq_name; + const DeviceState *dev; + IbexIRQ out; +} OtClkMgrClockSink; + +/* + * Software configurable clock. + * + * This is used to handle clock enable/disable requests for clocks that can be + * driven this way. + * + * The name of a SW configurable clock is suffixed with the group name, as this + * identifier is used to sort the SW clocks. This identifier strongly matters to + * order the SW clocks in the management fields. + */ +typedef struct { + char *name; /* full clock name, built from _ */ + OtClkMgrClockOutput *output; /* weakref to the managed clock output */ +} OtClkMgrSwCgClock; -typedef struct OtClkMgrShadowRegisters { - OtShadowReg io_meas_ctrl; - OtShadowReg io_div2_meas_ctrl; - OtShadowReg io_div4_meas_ctrl; - OtShadowReg main_meas_ctrl; - OtShadowReg usb_meas_ctrl; -} OtClkMgrRegisters; +/* Measure register pair */ +typedef struct { + uint32_t ctrl_en; /* multibit bool4 */ + OtShadowReg ctrl; /* value */ + OtClkMgrClock *clock; /* driven clocks */ +} OtClkMgrMeasureRegs; struct OtClkMgrState { SysBusDevice parent_obj; + MemoryRegion mmio; - IbexIRQ hints[OT_CLKMGR_HINT_COUNT]; - IbexIRQ alerts[PARAM_NUM_ALERTS]; + IbexIRQ alerts[ALERT_COUNT]; + + GList *clocks; /* OtClkMgrClock (top clocks and derived clocks) */ + GList *groups; /* OtClkMgrClockGroup */ + GList *outputs; /* OtClkMgrClockOutput */ + GList *ordered; /* OtClkMgrClock ordered weakref for register/field usage */ + OtClkMgrClock **tops; /* ordered array of wearef top clocks */ + OtClkMgrClock **hints; /* ordered array of wearef hintable clocks */ + OtClkMgrSwCgClock **swcgs; /* ordered array of SW configurable clocks */ + unsigned clock_count; /* count of all clocks */ + unsigned top_count; /* count of top clocks */ + unsigned hint_count; /* count of hintable clocks */ + unsigned swcg_count; /* count of SW configurable clocks */ uint32_t clock_states; /* bit set: active, reset: clock is idle */ uint32_t regs[REGS_COUNT]; /* shadowed slots are not used */ - OtClkMgrRegisters sdw_regs; + OtClkMgrMeasureRegs *measure_regs; + unsigned measure_count; /* count of measure_regs */ + bool input_clock_connected; /* true once clock source are connected */ + + char *ot_id; + DeviceState *clock_src; /* Top clock source */ + /* comma-separated definition list */ + /* pair of name:ratio of top level clocks, ratio w/ ref clock */ + char *cfg_topclocks; + /* name of reference clock, i.e. clock that is not measured */ + char *cfg_refclock; + /* name of top level loose clocks, i.e. clocks not connected */ + char *cfg_looseclocks; + /* triplets of name:parent_name:divider derivated clock definitions */ + char *cfg_subclocks; + /* pairs of name:clocks definition, where clocks are joined with '+' char */ + char *cfg_groups; + /* list of software-configurable groups */ + char *cfg_swcg; + /* list of software-hintable groups */ + char *cfg_hint; }; struct OtClkMgrClass { @@ -202,14 +271,7 @@ struct OtClkMgrClass { ResettablePhases parent_phases; }; -static const char *CLOCK_NAMES[OT_CLKMGR_HINT_COUNT] = { - [OT_CLKMGR_HINT_AES] = "AES", - [OT_CLKMGR_HINT_HMAC] = "HMAC", - [OT_CLKMGR_HINT_KMAC] = "KMAC", - [OT_CLKMGR_HINT_OTBN] = "OTBN", -}; -#define CLOCK_NAME(_clk_) \ - ((_clk_) < ARRAY_SIZE(CLOCK_NAMES) ? CLOCK_NAMES[(_clk_)] : "?") +static const char *CFGSEP = ","; static void ot_clkmgr_update_alerts(OtClkMgrState *s) { @@ -218,33 +280,691 @@ static void ot_clkmgr_update_alerts(OtClkMgrState *s) ibex_irq_set(&s->alerts[ALERT_RECOVERABLE], recov); } +/* NOLINTNEXTLINE(misc-no-recursion) */ +static void ot_clkmgr_update_clock_frequency( + OtClkMgrState *s, OtClkMgrClock *clk, unsigned input_freq) +{ + unsigned frequency = + clk->divider > 1 ? (input_freq / clk->divider) : input_freq; + + trace_ot_clkmgr_update_clock(s->ot_id, clk->name, frequency); + + /* propagate to derived clocks */ + for (GList *cnode = clk->subclocks; cnode; cnode = cnode->next) { + OtClkMgrClock *sclk = (OtClkMgrClock *)(cnode->data); + ot_clkmgr_update_clock_frequency(s, sclk, frequency); + } + + /* update clock outputs */ + for (GList *onode = clk->outputs; onode; onode = onode->next) { + OtClkMgrClockOutput *out = (OtClkMgrClockOutput *)onode->data; + + out->frequency = frequency; + + /* update each sink */ + for (GList *snode = out->sinks; snode; snode = snode->next) { + OtClkMgrClockSink *sink = (OtClkMgrClockSink *)(snode->data); + unsigned active_freq = out->disabled ? 0 : out->frequency; + trace_ot_clkmgr_update_sink(s->ot_id, sink->irq_name, active_freq); + + ibex_irq_set(&sink->out, (int)active_freq); + } + } +} + +static void ot_clkmgr_update_swcg(OtClkMgrState *s, uint32_t change) +{ + for (unsigned ix = 0; ix < s->swcg_count; ix++) { + uint32_t bm = 1u << ix; + if (!(change & bm)) { + continue; + } + + bool enabled = (bool)(s->regs[R_CLK_ENABLES] & bm); + + OtClkMgrSwCgClock *swcg_clk = s->swcgs[ix]; + OtClkMgrClockOutput *out = swcg_clk->output; + + out->disabled = !enabled; + + /* update each sink */ + for (GList *snode = out->sinks; snode; snode = snode->next) { + OtClkMgrClockSink *sink = (OtClkMgrClockSink *)(snode->data); + unsigned active_freq = out->disabled ? 0 : out->frequency; + trace_ot_clkmgr_update_sink(s->ot_id, sink->irq_name, active_freq); + + ibex_irq_set(&sink->out, (int)active_freq); + } + } +} + static void ot_clkmgr_clock_hint(void *opaque, int irq, int level) { OtClkMgrState *s = opaque; - unsigned clock = (unsigned)irq; + unsigned hint = (unsigned)irq; - g_assert(clock < OT_CLKMGR_HINT_COUNT); + g_assert(hint < s->hint_count); - trace_ot_clkmgr_clock_hint(CLOCK_NAME(clock), clock, (bool)level); + trace_ot_clkmgr_clock_hint(s->ot_id, s->hints[hint]->name, hint, + (bool)level); if (level) { - s->clock_states |= 1u << clock; + s->clock_states |= 1u << hint; } else { - s->clock_states &= ~(1u << clock); + s->clock_states &= ~(1u << hint); } } +static void ot_clkmgr_clock_input(void *opaque, int irq, int level) +{ + OtClkMgrState *s = opaque; + + unsigned clknum = (unsigned)irq; + g_assert(clknum < s->clock_count); + + OtClkMgrClock *clk = g_list_nth_data(s->clocks, clknum); + g_assert(clk); + + trace_ot_clkmgr_clock_input(s->ot_id, clk->name, (unsigned)level); + + ot_clkmgr_update_clock_frequency(s, clk, (unsigned)level); +} + static uint32_t ot_clkmgr_get_clock_hints(OtClkMgrState *s) { uint32_t hint_status = s->regs[R_CLK_HINTS] | s->clock_states; - trace_ot_clkmgr_get_clock_hints(s->regs[R_CLK_HINTS], s->clock_states, - hint_status); + trace_ot_clkmgr_get_clock_hints(s->ot_id, s->regs[R_CLK_HINTS], + s->clock_states, hint_status); return hint_status; } +static gint ot_clkmgr_compare_group_by_name(gconstpointer a, gconstpointer b) +{ + const OtClkMgrClockGroup *ga = a; + const OtClkMgrClockGroup *gb = b; + + return strcmp(ga->name, gb->name); +} + +static OtClkMgrClockGroup * +ot_clkmgr_find_group(OtClkMgrState *s, const char *name) +{ + OtClkMgrClockGroup group = { .name = (char *)name }; + + GList *glist = + g_list_find_custom(s->groups, &group, &ot_clkmgr_compare_group_by_name); + + return glist ? glist->data : NULL; +} + +static gint ot_clkmgr_compare_clock_by_name(gconstpointer a, gconstpointer b) +{ + const OtClkMgrClock *ca = a; + const OtClkMgrClock *cb = b; + + return strcmp(ca->name, cb->name); +} + +static OtClkMgrClock *ot_clkmgr_find_clock(GList *clock_list, const char *name) +{ + OtClkMgrClock clk = { .name = (char *)name }; + + GList *glist = + g_list_find_custom(clock_list, &clk, &ot_clkmgr_compare_clock_by_name); + + return glist ? glist->data : NULL; +} + +static gint ot_clkmgr_match_output(gconstpointer a, gconstpointer b) +{ + const OtClkMgrClockOutput *oa = a; + const OtClkMgrClockOutput *ob = b; + + return (int)((oa->clock != ob->clock) || (oa->group != ob->group)); +} + +static OtClkMgrClockOutput *ot_clkmgr_find_output( + GList *outlist, const OtClkMgrClockGroup *group, const OtClkMgrClock *clock) +{ + const OtClkMgrClockOutput output = { + .clock = (OtClkMgrClock *)clock, + .group = (OtClkMgrClockGroup *)group, + }; + + GList *glist = + g_list_find_custom(outlist, &output, &ot_clkmgr_match_output); + + return glist ? glist->data : NULL; +} + +static OtClkMgrClockOutput *ot_clkmgr_get_output( + OtClkMgrState *s, const OtClkMgrClockGroup *group, OtClkMgrClock *clock) +{ + OtClkMgrClockOutput *clk_out = + ot_clkmgr_find_output(s->outputs, group, clock); + + if (!clk_out) { + clk_out = g_new0(OtClkMgrClockOutput, 1u); + clk_out->clock = clock; + clk_out->group = group; + s->outputs = g_list_append(s->outputs, clk_out); + clock->outputs = g_list_append(clock->outputs, clk_out); + char *outname = g_strdup_printf("%s.%s", group->name, clock->name); + trace_ot_clkmgr_create(s->ot_id, "output", outname); + g_free(outname); + } + + return clk_out; +} + +static gint ot_clkmgr_match_sink(gconstpointer a, gconstpointer b) +{ + const OtClkMgrClockSink *sa = a; + const OtClkMgrClockSink *sb = b; + + return (int)(sa->dev != sb->dev); +} + +static OtClkMgrClockSink * +ot_clkmgr_find_sink(OtClkMgrClockOutput *clkout, const DeviceState *dev) +{ + const OtClkMgrClockSink sink = { + .dev = dev, + }; + + GList *glist = + g_list_find_custom(clkout->sinks, &sink, &ot_clkmgr_match_sink); + + return glist ? glist->data : NULL; +} + +static const char * +ot_clkmgr_get_clock_source(IbexClockSrcIf *ifd, const char *name, + const DeviceState *sinkdev, Error **errp) +{ + OtClkMgrState *s = OT_CLKMGR(ifd); + + gchar **parts = g_strsplit(name, ".", 2); + + if (g_strv_length(parts) < 2) { + g_strfreev(parts); + /* clock manager always require a group name */ + error_setg(errp, "%s: %s: group not defined: %s", __func__, s->ot_id, + name); + return NULL; + } + + OtClkMgrClockGroup *group = ot_clkmgr_find_group(s, parts[0]); + if (!group) { + error_setg(errp, "%s: %s: no such group: %s", __func__, s->ot_id, + parts[0]); + g_strfreev(parts); + return NULL; + }; + + OtClkMgrClock *clock = ot_clkmgr_find_clock(group->clocks, parts[1]); + if (!clock) { + error_setg(errp, "%s: %s: no such clock %s.%s", __func__, s->ot_id, + parts[0], parts[1]); + g_strfreev(parts); + return NULL; + }; + + g_strfreev(parts); + + OtClkMgrClockOutput *clk_out = ot_clkmgr_get_output(s, group, clock); + + OtClkMgrClockSink *clk_sink = NULL; + bool first = g_list_length(clk_out->sinks) == 0; + if (first) { + clk_sink = ot_clkmgr_find_sink(clk_out, sinkdev); + } + if (!clk_sink) { + if (!first) { + if (clk_out->group->hint) { + error_setg(errp, + "%s: %s: hintable clock %s can only have one sink, " + "deny sink %s", + __func__, s->ot_id, clk_out->clock->name, + object_get_typename(OBJECT(sinkdev))); + return NULL; + } + } + clk_sink = g_new0(OtClkMgrClockSink, 1u); + clk_sink->irq_name = + g_strdup_printf("clock-out-%s-%s-%d", group->name, clock->name, + g_list_length(clk_out->sinks)); + ibex_qdev_init_irq(OBJECT(s), &clk_sink->out, clk_sink->irq_name); + clk_out->sinks = g_list_append(clk_out->sinks, clk_sink); + const char *sinktype = object_get_typename(OBJECT(sinkdev)); + char *ot_id = + object_property_get_str(OBJECT(sinkdev), OT_COMMON_DEV_ID, NULL); + trace_ot_clkmgr_register_sink(s->ot_id, clk_sink->irq_name, sinktype, + ot_id ?: "?"); + g_free(ot_id); + }; + + return clk_sink->irq_name; +} + +static void ot_clkmgr_parse_top_clocks(OtClkMgrState *s, Error **errp) +{ + if (!s->cfg_topclocks) { + error_setg(errp, "%s: topclocks config not defined", __func__); + return; + } + + char *config = g_strdup(s->cfg_topclocks); + for (char *clkbrk, *clkdesc = strtok_r(config, CFGSEP, &clkbrk); clkdesc; + clkdesc = strtok_r(NULL, CFGSEP, &clkbrk)) { + char clkname[16]; + unsigned clkratio = 0; + unsigned length = 0; + int ret; + /* NOLINTNEXTLINE(cert-err34-c) */ + ret = sscanf(clkdesc, "%15[a-z0-9_]:%u%n", clkname, &clkratio, &length); + if (ret != 2) { + error_setg(errp, "%s: %s: invalid top clock %s format: %d", + __func__, s->ot_id, s->cfg_topclocks, ret); + break; + } + if (clkdesc[length]) { + error_setg(errp, "%s: %s: trailing chars in top clock %s", __func__, + s->ot_id, clkdesc); + break; + } + if (!clkratio || clkratio > UINT16_MAX) { + error_setg(errp, "%s: %s: invalid ratio in top clock %s", __func__, + s->ot_id, clkdesc); + break; + } + if (ot_clkmgr_find_clock(s->clocks, clkname)) { + error_setg(errp, "%s: %s: top clock redefinition '%s'", __func__, + s->ot_id, clkname); + break; + } + OtClkMgrClock *clk = g_new0(OtClkMgrClock, 1u); + clk->name = g_strdup(clkname); + clk->ref = s->cfg_refclock && !strcmp(clkname, s->cfg_refclock); + clk->ratio = clkratio; + s->clocks = g_list_append(s->clocks, clk); + trace_ot_clkmgr_create(s->ot_id, "clock", clk->name); + } + g_free(config); +} + +static void ot_clkmgr_parse_loose_clocks(OtClkMgrState *s, Error **errp) +{ + if (!s->cfg_looseclocks) { + return; + } + + char *config = g_strdup(s->cfg_looseclocks); + for (char *clkbrk, *clkname = strtok_r(config, CFGSEP, &clkbrk); clkname; + clkname = strtok_r(NULL, CFGSEP, &clkbrk)) { + OtClkMgrClock *clk = ot_clkmgr_find_clock(s->clocks, clkname); + if (!clk) { + error_setg(errp, "%s: %s: no such loose clock '%s'", __func__, + s->ot_id, clkname); + break; + } + clk->loose = true; + trace_ot_clkmgr_create(s->ot_id, "loose", clk->name); + } + g_free(config); +} + +static void ot_clkmgr_parse_derived_clocks(OtClkMgrState *s, Error **errp) +{ + if (!s->cfg_subclocks) { + error_setg(errp, "%s: subclocks config not defined", __func__); + return; + } + + /* parse derived clocks */ + char *config = g_strdup(s->cfg_subclocks); + for (char *clkbrk, *clkdesc = strtok_r(config, CFGSEP, &clkbrk); clkdesc; + clkdesc = strtok_r(NULL, CFGSEP, &clkbrk)) { + char subclkname[16]; + char clkname[16]; + unsigned clkdiv = 0; + unsigned length = 0; + /* NOLINTNEXTLINE(cert-err34-c) */ + int ret = sscanf(clkdesc, "%15[a-z0-9_]:%15[a-z0-9_]:%u%n", subclkname, + clkname, &clkdiv, &length); + if (ret != 3) { + error_setg(errp, "%s: %s: invalid subclock %s format: %d", __func__, + s->ot_id, clkdesc, ret); + break; + } + if (clkdesc[length]) { + error_setg(errp, "%s: %s: trailing chars in subclock %s", __func__, + s->ot_id, clkdesc); + break; + } + if (!clkdiv || clkdiv > UINT16_MAX) { + error_setg(errp, "%s: %s: invalid divider in subclock %s", __func__, + s->ot_id, clkdesc); + break; + } + if (ot_clkmgr_find_clock(s->clocks, subclkname)) { + error_setg(errp, "%s: %s: derived clock redefinition '%s'", + __func__, s->ot_id, subclkname); + break; + } + OtClkMgrClock *parclk = ot_clkmgr_find_clock(s->clocks, clkname); + if (!parclk) { + error_setg(errp, "%s: %s: invalid parent clock '%s' for %s", + __func__, s->ot_id, clkname, subclkname); + break; + } + OtClkMgrClock *clk = g_new0(OtClkMgrClock, 1u); + clk->name = g_strdup(subclkname); + clk->divider = clkdiv; + clk->ratio = parclk->ratio / clk->divider; + s->clocks = g_list_append(s->clocks, clk); + parclk->subclocks = g_list_append(parclk->subclocks, clk); + trace_ot_clkmgr_create(s->ot_id, "subclock", clk->name); + } + g_free(config); +} + +static void ot_clkmgr_parse_groups(OtClkMgrState *s, Error **errp) +{ + if (!s->cfg_groups) { + error_setg(errp, "%s: groups config not defined", __func__); + return; + } + + char *config = g_strdup(s->cfg_groups); + for (char *clkbrk, *clkdesc = strtok_r(config, CFGSEP, &clkbrk); clkdesc; + clkdesc = strtok_r(NULL, CFGSEP, &clkbrk)) { + char groupname[16]; + unsigned length = 0; + int ret = sscanf(clkdesc, "%15[a-z0-9_]:%n", groupname, &length); + if (ret != 1 || !clkdesc[length]) { + error_setg(errp, "%s: %s: invalid group %s format: %d", __func__, + s->ot_id, clkdesc, ret); + break; + } + + if (ot_clkmgr_find_group(s, groupname)) { + error_setg(errp, "%s: %s: multiple group definitions '%s'", + __func__, s->ot_id, groupname); + break; + } + + OtClkMgrClockGroup *grp = g_new0(OtClkMgrClockGroup, 1u); + grp->name = g_strdup(groupname); + trace_ot_clkmgr_create(s->ot_id, "group", grp->name); + + const char *gsep = "+"; + for (char *grpbrk, *clkname = strtok_r(&clkdesc[length], gsep, &grpbrk); + clkname; clkname = strtok_r(NULL, gsep, &grpbrk)) { + OtClkMgrClock *clk = ot_clkmgr_find_clock(s->clocks, clkname); + if (!clk) { + error_setg(errp, "%s: %s: invalid group clock '%s' for %s", + __func__, s->ot_id, clkname, groupname); + g_free(grp->name); + g_free(grp); + grp = NULL; + break; + } + grp->clocks = g_list_append(grp->clocks, clk); + trace_ot_clkmgr_add_group(s->ot_id, clk->name, grp->name); + } + + if (grp) { + s->groups = g_list_append(s->groups, grp); + } + } + g_free(config); +} + +static void ot_clkmgr_parse_sw_cg(OtClkMgrState *s, Error **errp) +{ + if (!s->cfg_swcg) { + error_setg(errp, "%s: sw configurable clocks not defined", __func__); + return; + } + + char *config = g_strdup(s->cfg_swcg); + for (char *grpbrk, *grpname = strtok_r(config, CFGSEP, &grpbrk); grpname; + grpname = strtok_r(NULL, CFGSEP, &grpbrk)) { + OtClkMgrClockGroup *grp = ot_clkmgr_find_group(s, grpname); + if (!grp) { + error_setg(errp, "%s: %s: invalid group '%s' for sw_cg", __func__, + s->ot_id, grpname); + break; + } + + grp->sw_cg = true; + } + g_free(config); +} + +static unsigned ot_clkmgr_parse_hint(OtClkMgrState *s, Error **errp) +{ + unsigned hint_count = 0; + + if (!s->cfg_hint) { + error_setg(errp, "%s: hintabkle clocks not defined", __func__); + return hint_count; + } + + char *config = g_strdup(s->cfg_hint); + for (char *grpbrk, *grpname = strtok_r(config, CFGSEP, &grpbrk); grpname; + grpname = strtok_r(NULL, CFGSEP, &grpbrk)) { + OtClkMgrClockGroup *grp = ot_clkmgr_find_group(s, grpname); + if (!grp) { + error_setg(errp, "%s: %s: invalid group '%s' for hint", __func__, + s->ot_id, grpname); + break; + } + + grp->hint = true; + hint_count = g_list_length(grp->clocks); + } + g_free(config); + + return hint_count; +} + +static void ot_clkmgr_assign_top(gpointer data, gpointer user_data) +{ + OtClkMgrState *s = user_data; + OtClkMgrClock *clk = data; + + s->tops[s->top_count++] = clk; +} + +static void ot_clkmgr_connect_input_clocks(OtClkMgrState *s) +{ + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + for (unsigned cix = 0; cix < s->top_count; cix++) { + OtClkMgrClock *clk = s->tops[cix]; + if (clk->loose) { + /* do not attempt to connect this clock */ + continue; + } + const char *irq_name = + ic->get_clock_source(ii, clk->name, DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), "clock-in", (int)cix); + qdev_connect_gpio_out_named(s->clock_src, irq_name, 0, in_irq); + trace_ot_clkmgr_connect_input_clock(s->ot_id, irq_name, cix); + } +} + +static void ot_clkmgr_configure_groups(gpointer data, gpointer user_data) +{ + OtClkMgrState *s = user_data; + OtClkMgrClockGroup *group = data; + + if (group->hint) { + for (GList *cnode = group->clocks; cnode; cnode = cnode->next) { + OtClkMgrClock *clk = (OtClkMgrClock *)(cnode->data); + + char *hintname = g_strdup_printf(OT_CLOCK_HINT_PREFIX "%s.%s", + group->name, clk->name); + qdev_init_gpio_in_named(DEVICE(s), &ot_clkmgr_clock_hint, hintname, + 1); + trace_ot_clkmgr_create(s->ot_id, "hint", hintname); + g_free(hintname); + + s->hints[s->hint_count++] = clk; + } + } +} + +static void ot_clkmgr_add_clock(gpointer data, gpointer user_data) +{ + OtClkMgrClock *clk = data; + OtClkMgrState *s = user_data; + + /* discard reference clock and duplicates */ + if (!clk->ref && !ot_clkmgr_find_clock(s->ordered, clk->name)) { + bool hint = false; + for (unsigned hix = 0; hix < s->hint_count; hix++) { + if (clk == s->hints[hix]) { + hint = true; + break; + } + } + /* hint clocks are only aliases, not main clocks */ + if (!hint) { + s->ordered = g_list_append(s->ordered, clk); + } + } + + /* add derived clocks */ + g_list_foreach(clk->subclocks, &ot_clkmgr_add_clock, s); +} + +static void ot_clkmgr_sort_clocks(OtClkMgrState *s) +{ + g_assert(!s->ordered); + + g_list_foreach(s->clocks, &ot_clkmgr_add_clock, s); + + s->ordered = g_list_sort(s->ordered, &ot_clkmgr_compare_clock_by_name); +} + +static gint ot_clkmgr_compare_swcg_by_name(gconstpointer a, gconstpointer b) +{ + const OtClkMgrSwCgClock *sa = a; + const OtClkMgrSwCgClock *sb = b; + + return strcmp(sa->name, sb->name); +} + +static GList * +ot_clkmgr_build_swcg_clock_name(OtClkMgrState *s, GList *swcgs, + const OtClkMgrClockGroup *group, GList *clocks) +{ + for (GList *node = clocks; node; node = node->next) { + OtClkMgrClock *clk = (OtClkMgrClock *)(node->data); + /* + * hack to remove AON from the configurable list, there is something + * weird to address as peri_aon is defined as sw_cg but not present in + * the CLK_ENABLES register... + * */ + if (!strcmp(clk->name, s->cfg_refclock)) { + continue; + } + + OtClkMgrSwCgClock *swcg_clk = g_new0(OtClkMgrSwCgClock, 1u); + swcg_clk->name = g_strdup_printf("%s_%s", clk->name, group->name); + swcg_clk->output = ot_clkmgr_get_output(s, group, clk); + g_assert(swcg_clk->output); + + /* ignore duplicates */ + if (g_list_find_custom(swcgs, swcg_clk, + &ot_clkmgr_compare_swcg_by_name)) { + g_free(swcg_clk->name); + g_free(swcg_clk); + continue; + } + swcgs = g_list_append(swcgs, swcg_clk); + } + + return swcgs; +} + +static void ot_clkmgr_generate_swcg_clocks(OtClkMgrState *s) +{ + GList *swcgs = NULL; + + for (GList *node = s->groups; node; node = node->next) { + const OtClkMgrClockGroup *group = + (const OtClkMgrClockGroup *)(node->data); + if (!group->sw_cg) { + continue; + } + + swcgs = ot_clkmgr_build_swcg_clock_name(s, swcgs, group, group->clocks); + } + + swcgs = g_list_sort(swcgs, &ot_clkmgr_compare_swcg_by_name); + + s->swcg_count = g_list_length(swcgs); + s->swcgs = g_new0(OtClkMgrSwCgClock *, s->swcg_count); + unsigned ix = 0; + for (GList *node = swcgs; node; node = node->next, ix++) { + OtClkMgrSwCgClock *swcg = (OtClkMgrSwCgClock *)(node->data); + s->swcgs[ix] = swcg; + trace_ot_clkmgr_define_swcg(s->ot_id, ix, swcg->name); + } + + /* discard the list, not the items that have been moved to the array */ + g_list_free(swcgs); +} + +static void ot_clkmgr_create_measure_regs(OtClkMgrState *s) +{ + /* generate the ordered list of clocks for register/field indexed access */ + ot_clkmgr_sort_clocks(s); + + s->measure_count = g_list_length(s->ordered); + + /* the following registers are indexed by the s->ordered list */ + s->measure_regs = g_new0(OtClkMgrMeasureRegs, s->measure_count); + + unsigned ix = 0; + for (GList *node = s->ordered; node; node = node->next, ix++) { + OtClkMgrClock *clk = (OtClkMgrClock *)(node->data); + + trace_ot_clkmgr_define_meas(s->ot_id, ix, clk->name); + + s->measure_regs[ix].clock = clk; + } +} + +static void ot_clkmgr_reset_measure_regs(OtClkMgrState *s) +{ + for (unsigned ix = 0; ix < s->measure_count; ix++) { + OtClkMgrMeasureRegs *mreg = &s->measure_regs[ix]; + + mreg->ctrl_en = OT_MULTIBITBOOL4_TRUE; + + uint32_t hi = mreg->clock->ratio + 10u; + uint32_t lo = (uint32_t)MAX(0, ((int)mreg->clock->ratio) - 10); + + uint32_t value = 0; + value = SHARED_FIELD_DP32(value, MEAS_CTRL_SHADOWED_HI, hi); + value = SHARED_FIELD_DP32(value, MEAS_CTRL_SHADOWED_LO, lo); + + trace_ot_clkmgr_reset_meas(s->ot_id, ix, mreg->clock->name, lo, hi); + + ot_shadow_reg_init(&s->measure_regs[ix].ctrl, value); + } +} + static uint64_t ot_clkmgr_read(void *opaque, hwaddr addr, unsigned size) { OtClkMgrState *s = opaque; @@ -263,30 +983,6 @@ static uint64_t ot_clkmgr_read(void *opaque, hwaddr addr, unsigned size) case R_CLK_ENABLES: case R_CLK_HINTS: case R_MEASURE_CTRL_REGWEN: - case R_IO_MEAS_CTRL_EN: - case R_IO_DIV2_MEAS_CTRL_EN: - case R_IO_DIV4_MEAS_CTRL_EN: - case R_MAIN_MEAS_CTRL_EN: - case R_USB_MEAS_CTRL_EN: - case R_RECOV_ERR_CODE: - case R_FATAL_ERR_CODE: - val32 = s->regs[reg]; - break; - case R_IO_MEAS_CTRL_SHADOWED: - val32 = ot_shadow_reg_read(&s->sdw_regs.io_meas_ctrl); - break; - case R_IO_DIV2_MEAS_CTRL_SHADOWED: - val32 = ot_shadow_reg_read(&s->sdw_regs.io_div2_meas_ctrl); - break; - case R_IO_DIV4_MEAS_CTRL_SHADOWED: - val32 = ot_shadow_reg_read(&s->sdw_regs.io_div4_meas_ctrl); - break; - case R_MAIN_MEAS_CTRL_SHADOWED: - val32 = ot_shadow_reg_read(&s->sdw_regs.main_meas_ctrl); - break; - case R_USB_MEAS_CTRL_SHADOWED: - val32 = ot_shadow_reg_read(&s->sdw_regs.usb_meas_ctrl); - break; case R_CLK_HINTS_STATUS: val32 = ot_clkmgr_get_clock_hints(s); break; @@ -297,14 +993,71 @@ static uint64_t ot_clkmgr_read(void *opaque, hwaddr addr, unsigned size) val32 = 0; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); - val32 = 0u; + val32 = 0; break; } uint32_t pc = ibex_get_current_pc(); - trace_ot_clkmgr_io_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + /* statically defined register offsets, handled with switch statement */ + if (reg < R_MEASURE_REG_BASE) { + trace_ot_clkmgr_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), + val32, pc); + return (uint64_t)val32; + } + + if (reg < (R_MEASURE_REG_BASE + (s->measure_count * 2u))) { + /* measure registers */ + unsigned measure = (reg - R_MEASURE_REG_BASE) >> 1u; + unsigned offset = (reg - R_MEASURE_REG_BASE) & 1u; + g_assert(measure < s->measure_count); + OtClkMgrMeasureRegs *mreg = &s->measure_regs[measure]; + GList *node = g_list_nth(s->ordered, measure); + g_assert(node); + const OtClkMgrClock *clk = (const OtClkMgrClock *)node->data; + + char reg_name[40u]; + unsigned ix = 0; + for (; clk->name[ix] && ix < 16u; ix++) { + reg_name[ix] = toupper(clk->name[ix]); + } + strbcpy(reg_name, ®_name[ix], "_MEAS_CTRL_"); + strbcpy(reg_name, ®_name[ix] + ARRAY_SIZE("_MEAS_CTRL_") - 1u, + offset ? "SHADOWED" : "EN"); + + switch (offset) { + case 0u: + val32 = mreg->ctrl_en; + break; + case 1u: + val32 = ot_shadow_reg_read(&mreg->ctrl); + break; + default: + g_assert_not_reached(); + break; + } + + trace_ot_clkmgr_io_read_out(s->ot_id, (uint32_t)addr, reg_name, val32, + pc); + return (uint64_t)val32; + } + + /* remaining registers after the dynamic register range */ + unsigned reg_err = reg - (s->measure_count * 2u); + + switch (reg_err) { + case R_RECOV_ERR_CODE: + case R_FATAL_ERR_CODE: + val32 = s->regs[reg_err]; + break; + default: + val32 = 0; + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n", + __func__, addr); + break; + } + + trace_ot_clkmgr_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg_err), + val32, pc); return (uint64_t)val32; }; @@ -319,12 +1072,17 @@ static void ot_clkmgr_write(void *opaque, hwaddr addr, uint64_t val64, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_clkmgr_io_write((uint32_t)addr, REG_NAME(reg), val32, pc); + + /* statically defined register offsets, handled with switch statement */ + if (reg < R_MEASURE_REG_BASE) { + trace_ot_clkmgr_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); + } switch (reg) { case R_ALERT_TEST: val32 &= ALERT_TEST_MASK; - for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { + for (unsigned ix = 0; ix < ALERT_COUNT; ix++) { ibex_irq_set(&s->alerts[ix], (int)((val32 >> ix) & 0x1u)); } break; @@ -354,184 +1112,134 @@ static void ot_clkmgr_write(void *opaque, hwaddr addr, uint64_t val64, "%s: JITTER_ENABLE protected w/ REGWEN\n", __func__); } break; - case R_CLK_ENABLES: - val32 &= CLK_ENABLES_MASK; + case R_CLK_ENABLES: { + uint32_t prev = s->regs[reg]; + val32 &= (1u << s->swcg_count) - 1u; s->regs[reg] = val32; - break; + ot_clkmgr_update_swcg(s, prev ^ s->regs[reg]); + } break; case R_CLK_HINTS: - val32 &= CLK_HINTS_MASK; + val32 &= (1u << s->hint_count) - 1u; s->regs[reg] = val32; break; case R_MEASURE_CTRL_REGWEN: val32 &= R_MEASURE_CTRL_REGWEN_EN_MASK; s->regs[reg] &= val32; break; - case R_IO_MEAS_CTRL_EN: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - s->regs[reg] = val32; - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_EN protected w/ REGWEN\n", - __func__); - } - break; - case R_IO_MEAS_CTRL_SHADOWED: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - switch (ot_shadow_reg_write(&s->sdw_regs.io_meas_ctrl, val32)) { - case OT_SHADOW_REG_STAGED: - case OT_SHADOW_REG_COMMITTED: - break; - case OT_SHADOW_REG_ERROR: - default: - s->regs[R_RECOV_ERR_CODE] |= - R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; - ot_clkmgr_update_alerts(s); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_SHADOWED protected w/ REGWEN\n", - __func__); - } - break; - case R_IO_DIV2_MEAS_CTRL_EN: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_DIV2_MEAS_CTRL_EN_EN_MASK; - s->regs[reg] = val32; - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: R_IO_DIV2_MEAS_CTRL_EN protected w/ REGWEN\n", - __func__); - } - break; - case R_IO_DIV2_MEAS_CTRL_SHADOWED: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - switch ( - ot_shadow_reg_write(&s->sdw_regs.io_div2_meas_ctrl, val32)) { - case OT_SHADOW_REG_STAGED: - case OT_SHADOW_REG_COMMITTED: - break; - case OT_SHADOW_REG_ERROR: - default: - s->regs[R_RECOV_ERR_CODE] |= - R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; - ot_clkmgr_update_alerts(s); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_SHADOWED protected w/ REGWEN\n", - __func__); - } + case R_EXTCLK_STATUS: + case R_CLK_HINTS_STATUS: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, + addr, REG_NAME(reg)); break; - case R_IO_DIV4_MEAS_CTRL_EN: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_DIV4_MEAS_CTRL_EN_EN_MASK; - s->regs[reg] = val32; - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: R_IO_DIV4_MEAS_CTRL_EN protected w/ REGWEN\n", - __func__); - } + default: break; - case R_IO_DIV4_MEAS_CTRL_SHADOWED: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - switch ( - ot_shadow_reg_write(&s->sdw_regs.io_div4_meas_ctrl, val32)) { - case OT_SHADOW_REG_STAGED: - case OT_SHADOW_REG_COMMITTED: - break; - case OT_SHADOW_REG_ERROR: - default: - s->regs[R_RECOV_ERR_CODE] |= - R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; - ot_clkmgr_update_alerts(s); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_SHADOWED protected w/ REGWEN\n", - __func__); + } + + /* low, statically defined register offsets, handled above */ + if (reg < R_MEASURE_REG_BASE) { + return; + } + + if (reg < (R_MEASURE_REG_BASE + (s->measure_count * 2u))) { + /* measure registers */ + unsigned measure = (reg - R_MEASURE_REG_BASE) >> 1u; + unsigned offset = (reg - R_MEASURE_REG_BASE) & 1u; + g_assert(measure < s->measure_count); + OtClkMgrMeasureRegs *mreg = &s->measure_regs[measure]; + GList *node = g_list_nth(s->ordered, measure); + g_assert(node); + const OtClkMgrClock *clk = (const OtClkMgrClock *)(node->data); + + char reg_name[40u]; + unsigned ix = 0; + for (; clk->name[ix] && ix < 20u; ix++) { + reg_name[ix] = toupper(clk->name[ix]); } - break; - case R_MAIN_MEAS_CTRL_EN: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_MAIN_MEAS_CTRL_EN_EN_MASK; - s->regs[reg] = val32; - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: R_MAIN_MEAS_CTRL_EN protected w/ REGWEN\n", - __func__); + strbcpy(reg_name, ®_name[ix], "_MEAS_CTRL_"); + strbcpy(reg_name, ®_name[ix] + ARRAY_SIZE("_MEAS_CTRL_") - 1u, + offset ? "SHADOWED" : "EN"); + trace_ot_clkmgr_io_write(s->ot_id, (uint32_t)addr, reg_name, val32, pc); + + if (!s->regs[R_MEASURE_CTRL_REGWEN]) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s protected w/ REGWEN\n", + __func__, s->ot_id, reg_name); + return; } - break; - case R_MAIN_MEAS_CTRL_SHADOWED: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - switch (ot_shadow_reg_write(&s->sdw_regs.main_meas_ctrl, val32)) { - case OT_SHADOW_REG_STAGED: - case OT_SHADOW_REG_COMMITTED: - break; - case OT_SHADOW_REG_ERROR: - default: - s->regs[R_RECOV_ERR_CODE] |= - R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; - ot_clkmgr_update_alerts(s); + + switch (offset) { + case 0u: + if (mreg->ctrl_en == OT_MULTIBITBOOL4_TRUE) { + val32 &= MEAS_CTRL_EN_MASK; + mreg->ctrl_en = val32; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s protected w/ EN\n", + __func__, s->ot_id, reg_name); } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_SHADOWED protected w/ REGWEN\n", - __func__); - } - break; - case R_USB_MEAS_CTRL_EN: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_USB_MEAS_CTRL_EN_EN_MASK; - s->regs[reg] = val32; - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: R_USB_MEAS_CTRL_EN protected w/ REGWEN\n", - __func__); - } - break; - case R_USB_MEAS_CTRL_SHADOWED: - if (s->regs[R_MEASURE_CTRL_REGWEN]) { - val32 &= R_IO_MEAS_CTRL_EN_EN_MASK; - switch (ot_shadow_reg_write(&s->sdw_regs.usb_meas_ctrl, val32)) { - case OT_SHADOW_REG_STAGED: - case OT_SHADOW_REG_COMMITTED: - break; - case OT_SHADOW_REG_ERROR: - default: - s->regs[R_RECOV_ERR_CODE] |= - R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; - ot_clkmgr_update_alerts(s); + break; + case 1u: + if (mreg->ctrl_en) { + val32 &= + MEAS_CTRL_SHADOWED_HI_MASK | MEAS_CTRL_SHADOWED_LO_MASK; + switch (ot_shadow_reg_write(&mreg->ctrl, val32)) { + case OT_SHADOW_REG_STAGED: + case OT_SHADOW_REG_COMMITTED: + break; + case OT_SHADOW_REG_ERROR: + default: + s->regs[R_RECOV_ERR_CODE] |= + R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_MASK; + ot_clkmgr_update_alerts(s); + } + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s protected w/ EN\n", + __func__, s->ot_id, reg_name); } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: IO_MEAS_CTRL_SHADOWED protected w/ REGWEN\n", - __func__); + break; + default: + g_assert_not_reached(); + break; } - break; + + return; + } + + /* remaining registers after the dynamic register range */ + unsigned reg_err = reg - (s->measure_count * 2u); + + trace_ot_clkmgr_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg_err), val32, + pc); + + switch (reg_err) { case R_RECOV_ERR_CODE: - val32 &= RECOV_ERR_CODE_MASK; - s->regs[reg] &= ~val32; /* RW1C */ + val32 &= (1u << ((R_RECOV_ERR_CODE_SHADOW_UPDATE_ERR_SHIFT + 1u + + (s->measure_count * 2u)))) - + 1u; + s->regs[reg_err] &= ~val32; /* RW1C */ break; - case R_EXTCLK_STATUS: - case R_CLK_HINTS_STATUS: case R_FATAL_ERR_CODE: qemu_log_mask(LOG_GUEST_ERROR, "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, addr, REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); break; } }; static Property ot_clkmgr_properties[] = { + DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtClkMgrState, ot_id), + DEFINE_PROP_LINK("clock-src", OtClkMgrState, clock_src, TYPE_DEVICE, + DeviceState *), + DEFINE_PROP_STRING("topclocks", OtClkMgrState, cfg_topclocks), + DEFINE_PROP_STRING("refclock", OtClkMgrState, cfg_refclock), + DEFINE_PROP_STRING("looseclocks", OtClkMgrState, cfg_looseclocks), + DEFINE_PROP_STRING("subclocks", OtClkMgrState, cfg_subclocks), + DEFINE_PROP_STRING("groups", OtClkMgrState, cfg_groups), + DEFINE_PROP_STRING("swcg", OtClkMgrState, cfg_swcg), + DEFINE_PROP_STRING("hint", OtClkMgrState, cfg_hint), DEFINE_PROP_END_OF_LIST(), }; @@ -563,36 +1271,74 @@ static void ot_clkmgr_reset_enter(Object *obj, ResetType type) s->regs[R_CLK_HINTS] = 0xfu; s->regs[R_CLK_HINTS_STATUS] = 0xfu; s->regs[R_MEASURE_CTRL_REGWEN] = 0x1u; - s->regs[R_IO_MEAS_CTRL_EN] = 0x9u; - s->regs[R_IO_DIV2_MEAS_CTRL_EN] = 0x9u; - s->regs[R_IO_DIV4_MEAS_CTRL_EN] = 0x9u; - s->regs[R_MAIN_MEAS_CTRL_EN] = 0x9u; - s->regs[R_USB_MEAS_CTRL_EN] = 0x9u; - ot_shadow_reg_init(&s->sdw_regs.io_meas_ctrl, 0x759eau); - ot_shadow_reg_init(&s->sdw_regs.io_div2_meas_ctrl, 0x1ccfau); - ot_shadow_reg_init(&s->sdw_regs.io_div4_meas_ctrl, 0x6e82u); - ot_shadow_reg_init(&s->sdw_regs.main_meas_ctrl, 0x7a9feu); - ot_shadow_reg_init(&s->sdw_regs.usb_meas_ctrl, 0x1ccfau); - - for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { + + for (unsigned ix = 0; ix < s->swcg_count; ix++) { + g_assert(s->swcgs[ix] && s->swcgs[ix]->output); + s->swcgs[ix]->output->disabled = 0; + } + + ot_clkmgr_reset_measure_regs(s); + + for (unsigned ix = 0; ix < ALERT_COUNT; ix++) { ibex_irq_set(&s->alerts[ix], 0); } + + if (!s->input_clock_connected) { + ot_clkmgr_connect_input_clocks(s); + s->input_clock_connected = true; + } } -static void ot_clkmgr_init(Object *obj) +static void ot_clkmgr_realize(DeviceState *dev, Error **errp) { - OtClkMgrState *s = OT_CLKMGR(obj); + OtClkMgrState *s = OT_CLKMGR(dev); + (void)errp; /* do not want to use abort */ + + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + ot_clkmgr_parse_top_clocks(s, &error_fatal); + unsigned top_count = g_list_length(s->clocks); + qdev_init_gpio_in_named(DEVICE(s), &ot_clkmgr_clock_input, "clock-in", + (int)top_count); + s->tops = g_new0(OtClkMgrClock *, top_count); + s->top_count = 0u; + g_list_foreach(s->clocks, &ot_clkmgr_assign_top, s); + g_assert(s->top_count == top_count); - memory_region_init_io(&s->mmio, obj, &ot_clkmgr_regs_ops, s, TYPE_OT_CLKMGR, - REGS_SIZE); + ot_clkmgr_parse_loose_clocks(s, &error_fatal); + /* not fatal per se, but highly likely to lead to missing clocks */ + ot_clkmgr_parse_derived_clocks(s, &error_warn); + /* at least one group is required */ + ot_clkmgr_parse_groups(s, &error_fatal); + /* following configs are not mandatory, however always defined */ + ot_clkmgr_parse_sw_cg(s, &error_warn); + unsigned hint_count = ot_clkmgr_parse_hint(s, &error_warn); + + s->clock_count = g_list_length(s->clocks); + + s->hints = g_new0(OtClkMgrClock *, hint_count); + s->hint_count = 0u; + g_list_foreach(s->groups, &ot_clkmgr_configure_groups, s); + g_assert(s->hint_count == hint_count); + + ot_clkmgr_generate_swcg_clocks(s); + + ot_clkmgr_create_measure_regs(s); + + memory_region_init_io(&s->mmio, OBJECT(dev), &ot_clkmgr_regs_ops, s, + TYPE_OT_CLKMGR, + REGS_SIZE + s->measure_count * 2u * sizeof(uint32_t)); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); +} - for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { +static void ot_clkmgr_init(Object *obj) +{ + OtClkMgrState *s = OT_CLKMGR(obj); + + for (unsigned ix = 0; ix < ALERT_COUNT; ix++) { ibex_qdev_init_irq(obj, &s->alerts[ix], OT_DEVICE_ALERT); } - - qdev_init_gpio_in_named(DEVICE(obj), &ot_clkmgr_clock_hint, OT_CLKMGR_HINT, - OT_CLKMGR_HINT_COUNT); } static void ot_clkmgr_class_init(ObjectClass *klass, void *data) @@ -600,6 +1346,7 @@ static void ot_clkmgr_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); (void)data; + dc->realize = &ot_clkmgr_realize; device_class_set_props(dc, ot_clkmgr_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); @@ -607,6 +1354,9 @@ static void ot_clkmgr_class_init(ObjectClass *klass, void *data) OtClkMgrClass *cc = OT_CLKMGR_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_clkmgr_reset_enter, NULL, NULL, &cc->parent_phases); + + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_CLASS(klass); + ic->get_clock_source = &ot_clkmgr_get_clock_source; } static const TypeInfo ot_clkmgr_info = { @@ -616,6 +1366,11 @@ static const TypeInfo ot_clkmgr_info = { .instance_init = &ot_clkmgr_init, .class_size = sizeof(OtClkMgrClass), .class_init = &ot_clkmgr_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_IBEX_CLOCK_SRC_IF }, + {}, + }, }; static void ot_clkmgr_register_types(void) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 77f9f63e100fc..0dee1b4953a71 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -49,10 +49,20 @@ ot_ast_upate_clock(const char *clock, unsigned frequency) "%s @ %u Hz" # ot_clkmgr.c -ot_clkmgr_clock_hint(const char *name, unsigned clock, bool active) "%s(%u): %u" -ot_clkmgr_get_clock_hints(uint32_t req, uint32_t status, uint32_t hint) "req:0x%02x clk:0x%02x hint:0x%02x" -ot_clkmgr_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_clkmgr_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_clkmgr_add_group(const char *id, const char *clock, const char *group) "%s: %s added to %s" +ot_clkmgr_clock_hint(const char *id, const char *name, unsigned clock, bool active) "%s: %s(%u): %u" +ot_clkmgr_clock_input(const char *id, const char *name, unsigned frequency) "%s: %s: %u Hz" +ot_clkmgr_connect_input_clock(const char *id, const char * srcname, unsigned cix) "%s: %s -> clock-in[%u]" +ot_clkmgr_create(const char *id, const char *what, const char *name) "%s: %s: %s" +ot_clkmgr_get_clock_hints(const char *id, uint32_t req, uint32_t status, uint32_t hint) "%s: req:0x%02x clk:0x%02x hint:0x%02x" +ot_clkmgr_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_clkmgr_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_clkmgr_define_meas(const char *id, unsigned ix, const char *name) "%s: [%u]: %s" +ot_clkmgr_define_swcg(const char *id, unsigned ix, const char *name) "%s: [%u]: %s" +ot_clkmgr_register_sink(const char *id, const char *irqname, const char *devtype, const char *devid) "%s: %s -> %s:%s" +ot_clkmgr_reset_meas(const char *id, unsigned ix, const char *clkname, unsigned lo, unsigned hi) "%s: [%u] %s [%u..%u]" +ot_clkmgr_update_clock(const char *id, const char *clock, unsigned frequency) "%s: %s @ %u Hz" +ot_clkmgr_update_sink(const char *id, const char *irqname, unsigned frequency) "%s: %s -> %u Hz" # ot_common.c diff --git a/include/hw/opentitan/ot_clkmgr.h b/include/hw/opentitan/ot_clkmgr.h index be92509ea4ab0..2ade5dd8ab6d6 100644 --- a/include/hw/opentitan/ot_clkmgr.h +++ b/include/hw/opentitan/ot_clkmgr.h @@ -33,6 +33,11 @@ #define TYPE_OT_CLKMGR "ot-clkmgr" OBJECT_DECLARE_TYPE(OtClkMgrState, OtClkMgrClass, OT_CLKMGR) +#define OT_CLOCK_HINT_PREFIX "ot-clock-hint-" + +#define OT_CLKMGR_CLOCK_INPUT TYPE_OT_CLKMGR "-clock-in" + +/* deprecated definitions */ typedef enum { OT_CLKMGR_HINT_AES, OT_CLKMGR_HINT_HMAC, From c4fba4857e8a2a2cfd88bb8f660a109845a1ef7c Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 19 Jun 2025 18:45:32 +0200 Subject: [PATCH 089/175] [ot] hw/opentitan: ot_pwrmgr: add basic clock enablement to FSM Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_pwrmgr.c | 59 +++++++++++++++++++++++++++++++++++++++ hw/opentitan/trace-events | 1 + 3 files changed, 61 insertions(+) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 29fee84dcabca..fcb912bdb55d3 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -126,6 +126,7 @@ config OT_PRNG bool config OT_PWRMGR + select OT_CLOCK_CTRL bool config OT_RANDOM_SRC diff --git a/hw/opentitan/ot_pwrmgr.c b/hw/opentitan/ot_pwrmgr.c index f2496094dae9d..519a73cc74dfd 100644 --- a/hw/opentitan/ot_pwrmgr.c +++ b/hw/opentitan/ot_pwrmgr.c @@ -33,6 +33,7 @@ #include "qemu/typedefs.h" #include "qapi/error.h" #include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_clock_ctrl.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_pwrmgr.h" #include "hw/opentitan/ot_rstmgr.h" @@ -206,6 +207,10 @@ typedef enum { OT_PWRMGR_FAST_DOMAIN, } OtPwrMgrClockDomain; +typedef struct { + char *name; +} OtPwrMgrClock; + typedef struct { OtPwrMgrClockDomain domain; int req; @@ -250,8 +255,11 @@ struct OtPwrMgrState { uint32_t *regs; OtPwrMgrResetReq reset_request; OtPwrMgrBootStatus boot_status; + GList *clocks; char *ot_id; + char *cfg_clocks; + DeviceState *clock_ctrl; uint8_t num_rom; uint8_t version; bool main; /* main power manager (for machines w/ multiple PwrMgr) */ @@ -263,6 +271,8 @@ struct OtPwrMgrClass { ResettablePhases parent_phases; }; +static const char *CFGSEP = ","; + #define PWRMGR_NAME_ENTRY(_pre_, _name_) [_pre_##_##_name_] = stringify(_name_) #define FAST_ST_NAME_ENTRY(_name_) PWRMGR_NAME_ENTRY(OT_PWR_FAST_ST, _name_) @@ -320,6 +330,11 @@ typedef struct { uint32_t reset_mask; } OtPwrMgrConfig; +typedef struct { + OtPwrMgrState *s; + bool enable; +} OtPwrMgrClockConfig; + /* clang-format off */ static const OtPwrMgrConfig PWRMGR_CONFIG[OT_PWRMGR_VERSION_COUNT] = { [OT_PWRMGR_VERSION_EG_252] = { @@ -474,6 +489,26 @@ static void ot_pwrmgr_wkup(void *opaque, int irq, int level) trace_ot_pwrmgr_wkup(s->ot_id, WAKEUP_NAME(s, src), src, (bool)level); } +static void ot_pwrmgr_clock_enable(gpointer data, gpointer user_data) +{ + OtPwrMgrClock *clk = data; + OtPwrMgrClockConfig *cfg = user_data; + OtClockCtrlIfClass *oc = OT_CLOCK_CTRL_IF_GET_CLASS(cfg->s->clock_ctrl); + OtClockCtrlIf *oi = OT_CLOCK_CTRL_IF(cfg->s->clock_ctrl); + trace_ot_pwrmgr_clock_enable(cfg->s->ot_id, clk->name, cfg->enable); + oc->clock_enable(oi, clk->name, cfg->enable); +} + +static void ot_pwrmgr_clock_enable_all(OtPwrMgrState *s, bool enable) +{ + OtPwrMgrClockConfig cfg = { + .s = s, + .enable = enable, + }; + + g_list_foreach(s->clocks, &ot_pwrmgr_clock_enable, &cfg); +} + static void ot_pwrmgr_rst_req(void *opaque, int irq, int level) { OtPwrMgrState *s = opaque; @@ -582,6 +617,8 @@ static void ot_pwrmgr_fast_fsm_tick(OtPwrMgrState *s) switch (s->f_state) { case OT_PWR_FAST_ST_LOW_POWER: PWR_CHANGE_FAST_STATE(s, ENABLE_CLOCKS); + /* shortcut: real HW use a much more complex FSM to enable clocks */ + ot_pwrmgr_clock_enable_all(s, true); break; case OT_PWR_FAST_ST_ENABLE_CLOCKS: s->boot_status.main_ip_clk_en = 1u; @@ -751,6 +788,21 @@ static void ot_pwrmgr_holdon_fetch(void *opaque, int n, int level) ot_pwrmgr_schedule_fsm(s); } +static void ot_pwrmgr_parse_clocks(OtPwrMgrState *s, Error **errp) +{ + if (!s->cfg_clocks) { + error_setg(errp, "%s: clocks config not defined", __func__); + return; + } + + for (char *clkbrk, *clkname = strtok_r(s->cfg_clocks, CFGSEP, &clkbrk); + clkname; clkname = strtok_r(NULL, CFGSEP, &clkbrk)) { + OtPwrMgrClock *clk = g_new0(OtPwrMgrClock, 1u); + clk->name = strdup(clkname); + s->clocks = g_list_append(s->clocks, clk); + } +} + static uint64_t ot_pwrmgr_regs_read(void *opaque, hwaddr addr, unsigned size) { OtPwrMgrState *s = opaque; @@ -900,6 +952,9 @@ static void ot_pwrmgr_regs_write(void *opaque, hwaddr addr, uint64_t val64, static Property ot_pwrmgr_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtPwrMgrState, ot_id), + DEFINE_PROP_STRING("clocks", OtPwrMgrState, cfg_clocks), + DEFINE_PROP_LINK("clock_ctrl", OtPwrMgrState, clock_ctrl, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_UINT8("num-rom", OtPwrMgrState, num_rom, 0), DEFINE_PROP_UINT8("version", OtPwrMgrState, version, UINT8_MAX), DEFINE_PROP_BOOL("fetch-ctrl", OtPwrMgrState, fetch_ctrl, false), @@ -979,6 +1034,8 @@ static void ot_pwrmgr_realize(DeviceState *dev, Error **errp) g_assert(s->ot_id); g_assert(s->version < OT_PWRMGR_VERSION_COUNT); + g_assert(s->clock_ctrl); + OBJECT_CHECK(OtClockCtrlIf, s->clock_ctrl, TYPE_OT_CLOCK_CTRL_IF); if (s->num_rom) { if (s->num_rom > 8u * sizeof(uint8_t)) { @@ -995,6 +1052,8 @@ static void ot_pwrmgr_realize(DeviceState *dev, Error **errp) qdev_init_gpio_in_named(dev, &ot_pwrmgr_holdon_fetch, OT_PWRMGR_HOLDON_FETCH, 1u); } + + ot_pwrmgr_parse_clocks(s, &error_fatal); } static void ot_pwrmgr_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 0dee1b4953a71..7d35df587fc56 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -394,6 +394,7 @@ ot_plic_ext_io_alert_write(const char *id, uint32_t addr, const char *regname, u # ot_pwrmgr.c ot_pwrmgr_change_state(const char *id, int line, const char *type, const char *old, int nold, const char *new, int nnew) "%s @ %d %s: [%s:%d] -> [%s:%d]" +ot_pwrmgr_clock_enable(const char *d, const char *clkname, bool enable) "%s: %s en:%u" ot_pwrmgr_escalate_rx(const char *id, bool level) "%s: level %u" ot_pwrmgr_go_idle(const char *id, const char *state) "%s: %s" ot_pwrmgr_ignore_req(const char *reason) "%s" From 00e631fd29d5dacbd791c35b73b184f1589e4e23 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 10:44:05 +0200 Subject: [PATCH 090/175] [ot] hw/opentitan: ot_hmac: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_hmac.c | 88 +++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 72e66b07db577..a509df7756404 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -32,12 +32,14 @@ #include "qemu/fifo8.h" #include "qemu/log.h" #include "qemu/module.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_hmac.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "tomcrypt.h" @@ -158,6 +160,9 @@ REG32(MSG_LENGTH_UPPER, 0xe8u) /* value representing 'KEY_NONE' in the config key length field */ #define OT_HMAC_CFG_KEY_LENGTH_NONE 0x20u +#define OT_HMAC_CLOCK_ACTIVE "clock-active" +#define OT_HMAC_CLOCK_INPUT "clock-in" + #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) #define R_LAST_REG (R_MSG_LENGTH_UPPER) @@ -275,13 +280,17 @@ struct OtHMACState { IbexIRQ irqs[PARAM_NUM_IRQS]; IbexIRQ alert; - IbexIRQ clkmgr; + IbexIRQ clock_active; + unsigned pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ OtHMACRegisters *regs; OtHMACContext *ctx; Fifo8 input_fifo; char *ot_id; + char *clock_name; + DeviceState *clock_src; }; struct OtHMACClass { @@ -652,7 +661,7 @@ static void ot_hmac_process_fifo(OtHMACState *s) ot_hmac_update_irqs(s); - ibex_irq_set(&s->clkmgr, + ibex_irq_set(&s->clock_active, !fifo8_is_empty(&s->input_fifo) || (bool)s->regs->cmd); } @@ -664,6 +673,17 @@ static inline void ot_hmac_wipe_buffer(OtHMACState *s, uint32_t *buffer, } } +static void ot_hmac_clock_input(void *opaque, int irq, int level) +{ + OtHMACState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable HMAC execution when PCLK is 0 */ +} + static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size) { OtHMACState *s = OT_HMAC(opaque); @@ -889,7 +909,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs->cmd = R_CMD_HASH_START_MASK; s->regs->msg_length = 0; - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); /* * Hold the previous digest size until the HMAC is started with the @@ -936,7 +956,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs->cmd |= R_CMD_HASH_PROCESS_MASK; /* trigger delayed processing of FIFO */ - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); ot_hmac_process_fifo(s); } @@ -947,7 +967,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, * trigger delayed processing of FIFO until the next block is * processed. */ - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); ot_hmac_process_fifo(s); } @@ -973,7 +993,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, ot_hmac_restore_context(s); /* trigger delayed processing of FIFO */ - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); ot_hmac_process_fifo(s); } @@ -1160,7 +1180,7 @@ static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, } } - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); for (unsigned i = 0; i < size; i++) { uint8_t b = value; @@ -1189,6 +1209,9 @@ static void ot_hmac_fifo_write(void *opaque, hwaddr addr, uint64_t value, static Property ot_hmac_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtHMACState, ot_id), + DEFINE_PROP_STRING("clock-name", OtHMACState, clock_name), + DEFINE_PROP_LINK("clock-src", OtHMACState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_END_OF_LIST(), }; @@ -1216,23 +1239,55 @@ static void ot_hmac_reset_enter(Object *obj, ResetType type) { OtHMACClass *c = OT_HMAC_GET_CLASS(obj); OtHMACState *s = OT_HMAC(obj); - OtHMACRegisters *r = s->regs; if (c->parent_phases.enter) { c->parent_phases.enter(obj, type); } - ibex_irq_set(&s->clkmgr, false); + ibex_irq_set(&s->clock_active, false); memset(s->ctx, 0, sizeof(*(s->ctx))); memset(s->regs, 0, sizeof(*(s->regs))); - r->cfg = 0x4100u; - ot_hmac_update_irqs(s); ot_hmac_update_alert(s); fifo8_reset(&s->input_fifo); + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), OT_HMAC_CLOCK_INPUT, 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + + if (object_dynamic_cast(OBJECT(s->clock_src), TYPE_OT_CLKMGR)) { + char *hint_name = + g_strdup_printf(OT_CLOCK_HINT_PREFIX "%s", s->clock_name); + qemu_irq hint_irq = + qdev_get_gpio_in_named(s->clock_src, hint_name, 0); + g_assert(hint_irq); + qdev_connect_gpio_out_named(DEVICE(s), OT_HMAC_CLOCK_ACTIVE, 0, + hint_irq); + g_free(hint_name); + } + } +} + +static void ot_hmac_reset_exit(Object *obj, ResetType type) +{ + OtHMACClass *c = OT_HMAC_GET_CLASS(obj); + OtHMACState *s = OT_HMAC(obj); + OtHMACRegisters *r = s->regs; + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + r->cfg = 0x4100u; } static void ot_hmac_realize(DeviceState *dev, Error **errp) @@ -1242,6 +1297,11 @@ static void ot_hmac_realize(DeviceState *dev, Error **errp) OtHMACState *s = OT_HMAC(dev); g_assert(s->ot_id); + g_assert(s->clock_name); + g_assert(s->clock_src); + + qdev_init_gpio_in_named(DEVICE(s), &ot_hmac_clock_input, + OT_HMAC_CLOCK_INPUT, 1); } static void ot_hmac_init(Object *obj) @@ -1255,7 +1315,7 @@ static void ot_hmac_init(Object *obj) ibex_sysbus_init_irq(obj, &s->irqs[ix]); } ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); - ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); + ibex_qdev_init_irq(obj, &s->clock_active, OT_HMAC_CLOCK_ACTIVE); memory_region_init(&s->mmio, OBJECT(s), TYPE_OT_HMAC, OT_HMAC_WHOLE_SIZE); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); @@ -1283,8 +1343,8 @@ static void ot_hmac_class_init(ObjectClass *klass, void *data) ResettableClass *rc = RESETTABLE_CLASS(klass); OtHMACClass *hc = OT_HMAC_CLASS(klass); - resettable_class_set_parent_phases(rc, &ot_hmac_reset_enter, NULL, NULL, - &hc->parent_phases); + resettable_class_set_parent_phases(rc, &ot_hmac_reset_enter, NULL, + &ot_hmac_reset_exit, &hc->parent_phases); } static const TypeInfo ot_hmac_info = { From 9e4fdfbbac25f1132e9cbdbb3af2eadcef4213eb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 16:20:59 +0200 Subject: [PATCH 091/175] [ot] hw/opentitan: ot_uart: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_uart.c | 65 ++++++++++++++++++++++++++++++++++----- hw/opentitan/trace-events | 2 ++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/hw/opentitan/ot_uart.c b/hw/opentitan/ot_uart.c index ee636d01c8b78..3d07c13336a2d 100644 --- a/hw/opentitan/ot_uart.c +++ b/hw/opentitan/ot_uart.c @@ -34,6 +34,7 @@ #include "qemu/fifo8.h" #include "qemu/log.h" #include "qemu/module.h" +#include "qapi/error.h" #include "chardev/char-fe.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" @@ -41,6 +42,7 @@ #include "hw/qdev-properties-system.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "trace.h" @@ -157,9 +159,12 @@ struct OtUARTState { Fifo8 rx_fifo; uint32_t tx_watermark_level; guint watch_tag; + unsigned pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ char *ot_id; - uint32_t pclk; + char *clock_name; + DeviceState *clock_src; CharBackend chr; }; @@ -168,7 +173,7 @@ struct OtUARTClass { ResettablePhases parent_phases; }; -static uint32_t ot_uart_get_tx_watermark_level(OtUARTState *s) +static uint32_t ot_uart_get_tx_watermark_level(const OtUARTState *s) { uint32_t tx_ilvl = (s->regs[R_FIFO_CTRL] & R_FIFO_CTRL_TXILVL_MASK) >> R_FIFO_CTRL_TXILVL_SHIFT; @@ -176,7 +181,7 @@ static uint32_t ot_uart_get_tx_watermark_level(OtUARTState *s) return tx_ilvl < 7u ? (1u << tx_ilvl) : 64u; } -static uint32_t ot_uart_get_rx_watermark_level(OtUARTState *s) +static uint32_t ot_uart_get_rx_watermark_level(const OtUARTState *s) { uint32_t rx_ilvl = (s->regs[R_FIFO_CTRL] & R_FIFO_CTRL_RXILVL_MASK) >> R_FIFO_CTRL_RXILVL_SHIFT; @@ -197,21 +202,33 @@ static void ot_uart_update_irqs(OtUARTState *s) } } -static bool ot_uart_is_sys_loopack_enabled(OtUARTState *s) +static bool ot_uart_is_sys_loopack_enabled(const OtUARTState *s) { return (bool)FIELD_EX32(s->regs[R_CTRL], CTRL, SLPBK); } -static bool ot_uart_is_tx_enabled(OtUARTState *s) +static bool ot_uart_is_tx_enabled(const OtUARTState *s) { return (bool)FIELD_EX32(s->regs[R_CTRL], CTRL, TX); } -static bool ot_uart_is_rx_enabled(OtUARTState *s) +static bool ot_uart_is_rx_enabled(const OtUARTState *s) { return (bool)FIELD_EX32(s->regs[R_CTRL], CTRL, RX); } +static void ot_uart_check_baudrate(const OtUARTState *s) +{ + uint32_t nco = FIELD_EX32(s->regs[R_CTRL], CTRL, NCO); + + unsigned baudrate = (unsigned)(((uint64_t)nco * (uint64_t)s->pclk) >> + (R_CTRL_NCO_LENGTH + 4)); + + if (baudrate) { + trace_ot_uart_check_baudrate(s->ot_id, s->pclk, baudrate); + } +} + static void ot_uart_reset_rx_fifo(OtUARTState *s) { fifo8_reset(&s->rx_fifo); @@ -381,6 +398,18 @@ static void uart_write_tx_fifo(OtUARTState *s, uint8_t val) } } +static void ot_uart_clock_input(void *opaque, int irq, int level) +{ + OtUARTState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable UART transfer when PCLK is 0 */ + ot_uart_check_baudrate(s); +} + static uint64_t ot_uart_read(void *opaque, hwaddr addr, unsigned size) { OtUARTState *s = opaque; @@ -507,6 +536,9 @@ static void ot_uart_write(void *opaque, hwaddr addr, uint64_t val64, uint32_t prev = s->regs[R_CTRL]; s->regs[R_CTRL] = val32 & CTRL_MASK; uint32_t change = prev ^ s->regs[R_CTRL]; + if (change & R_CTRL_NCO_MASK) { + ot_uart_check_baudrate(s); + } if ((change & R_CTRL_RX_MASK) && ot_uart_is_rx_enabled(s) && !ot_uart_is_sys_loopack_enabled(s)) { qemu_chr_fe_accept_input(&s->chr); @@ -568,7 +600,9 @@ static const MemoryRegionOps ot_uart_ops = { static Property ot_uart_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtUARTState, ot_id), DEFINE_PROP_CHR("chardev", OtUARTState, chr), - DEFINE_PROP_UINT32("pclk", OtUARTState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtUARTState, clock_name), + DEFINE_PROP_LINK("clock-src", OtUARTState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_END_OF_LIST(), }; @@ -609,6 +643,17 @@ static void ot_uart_reset_enter(Object *obj, ResetType type) ot_uart_update_irqs(s); ibex_irq_set(&s->alert, 0); + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = qdev_get_gpio_in_named(DEVICE(s), "clock-in", 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + trace_ot_uart_connect_input_clock(s->ot_id, s->clock_src_name); + } } static void ot_uart_realize(DeviceState *dev, Error **errp) @@ -617,7 +662,11 @@ static void ot_uart_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); - g_assert(s->pclk); + g_assert(s->clock_name); + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + qdev_init_gpio_in_named(DEVICE(s), &ot_uart_clock_input, "clock-in", 1); fifo8_create(&s->tx_fifo, OT_UART_TX_FIFO_SIZE); fifo8_create(&s->rx_fifo, OT_UART_RX_FIFO_SIZE); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 7d35df587fc56..76c3b24393bd9 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -552,6 +552,8 @@ ot_timer_update_irq(const char *id, bool level) "%s: %d" # ot_uart.c +ot_uart_check_baudrate(const char *id, unsigned pclk, unsigned baud) "%s: @ %u Hz: %u bps" +ot_uart_connect_input_clock(const char *id, const char * srcname) "%s: %s" ot_uart_debug(const char *id, const char *msg) "%s: %s" ot_uart_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_uart_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" From c4c1178f6d1b6e8a21b8df8ecd8e065e87fbab78 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 16:21:51 +0200 Subject: [PATCH 092/175] [ot] hw/opentitan: ot_spi_host: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_spi_host.c | 37 ++++++++++++++++++++++++++++++++++--- hw/opentitan/trace-events | 1 + 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index e782aef0cb011..a7dbd402e988a 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -45,6 +45,7 @@ #include "hw/opentitan/ot_spi_host.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/ssi/ssi.h" @@ -348,10 +349,13 @@ struct OtSPIHostState { OtSPIHostFsm fsm; bool on_reset; + unsigned pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ /* properties */ char *ot_id; - uint32_t pclk; /* input peripheral clock (Hz) */ + char *clock_name; /* clock name */ + DeviceState *clock_src; /* clock source */ uint32_t start_delay_ns; /* initial command kick off delay */ uint32_t completion_delay_ns; /** completion delay/pacing */ uint32_t bus_num; /* SPI host port number */ @@ -948,6 +952,18 @@ static void ot_spi_host_schedule_fsm(void *opaque) ot_spi_host_trace_status(s, "P<", ot_spi_host_get_status(s)); } +static void ot_spi_host_clock_input(void *opaque, int irq, int level) +{ + OtSPIHostState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable SPI transfer when PCLK is 0 */ + trace_ot_spi_host_clock_update(s->ot_id, s->pclk); +} + static uint64_t ot_spi_host_io_read(void *opaque, hwaddr addr, unsigned int size) { @@ -1272,7 +1288,9 @@ static Property ot_spi_host_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtSPIHostState, ot_id), DEFINE_PROP_UINT32("num-cs", OtSPIHostState, num_cs, 1u), DEFINE_PROP_UINT32("bus-num", OtSPIHostState, bus_num, 0u), - DEFINE_PROP_UINT32("pclk", OtSPIHostState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtSPIHostState, clock_name), + DEFINE_PROP_LINK("clock-src", OtSPIHostState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_UINT32("start-delay", OtSPIHostState, start_delay_ns, FSM_START_DELAY_NS), DEFINE_PROP_UINT32("completion-delay", OtSPIHostState, completion_delay_ns, @@ -1301,6 +1319,16 @@ static void ot_spi_host_reset_enter(Object *obj, ResetType type) s->on_reset = true; + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = qdev_get_gpio_in_named(DEVICE(s), "clock-in", 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + } + ot_spi_host_internal_reset(s); } @@ -1322,12 +1350,15 @@ static void ot_spi_host_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); - g_assert(s->pclk); + g_assert(s->clock_name); + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); s->cs_lines = g_new0(qemu_irq, (size_t)s->num_cs); qdev_init_gpio_out_named(DEVICE(s), s->cs_lines, SSI_GPIO_CS, (int)s->num_cs); + qdev_init_gpio_in_named(DEVICE(s), &ot_spi_host_clock_input, "clock-in", 1); char busname[16u]; if (snprintf(busname, sizeof(busname), "spi%u", s->bus_num) >= diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 76c3b24393bd9..c59668340897e 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -521,6 +521,7 @@ ot_spi_host_retire_command(const char *id, unsigned cmdid) "%s: {%u}" ot_spi_host_stall(const char *id, const char *msg, uint32_t val) "%s: %s rem %u" ot_spi_host_status(const char *id, const char *msg, uint32_t status, const char *str, unsigned cmd, unsigned rxd, unsigned txd, char state) "%s: %s 0x%08x s:%s cq:%u rq:%u tq:%u st:%c" ot_spi_host_transfer(const char *id, uint64_t transfer, uint32_t tx_data, uint32_t rx_data) "%s: {%" PRIu64 "} tx_data: 0x%02x rx_data: 0x%02x" +ot_spi_host_clock_update(const char *id, unsigned pclk) "%s: %u Hz" ot_spi_host_update_irq(const char *id, const char *channel, int level) "%s: irq %s: %d" # ot_sram_ctrl.c From b4c4ad1b3a23f67223a994b000afabef73791d32 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 17:15:34 +0200 Subject: [PATCH 093/175] [ot] hw/opentitan: ot_i2c_dj: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_i2c_dj.c | 47 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/hw/opentitan/ot_i2c_dj.c b/hw/opentitan/ot_i2c_dj.c index 4843b08365fc8..3491a90fe8833 100644 --- a/hw/opentitan/ot_i2c_dj.c +++ b/hw/opentitan/ot_i2c_dj.c @@ -54,14 +54,15 @@ #include "qemu/fifo8.h" #include "qemu/log.h" #include "qemu/timer.h" +#include "qapi/error.h" #include "hw/i2c/i2c.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_fifo32.h" #include "hw/opentitan/ot_i2c_dj.h" -#include "hw/qdev-clock.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "trace.h" @@ -294,8 +295,12 @@ struct OtI2CDjState { /* TX: Scheduled responses for target mode. */ Fifo8 target_tx_fifo; + uint32_t pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ + char *ot_id; - uint32_t pclk; + char *clock_name; + DeviceState *clock_src; }; struct OtI2CDjClass { @@ -471,6 +476,10 @@ static void ot_i2c_dj_target_write_tx_fifo(OtI2CDjState *s, uint8_t val) static bool ot_i2c_dj_check_timings(OtI2CDjState *s) { + if (!s->pclk) { + return 0; + } + uint32_t thigh = FIELD_EX32(s->regs[R_TIMING0], TIMING0, THIGH); uint32_t tlow = FIELD_EX32(s->regs[R_TIMING0], TIMING0, TLOW); uint32_t tr = FIELD_EX32(s->regs[R_TIMING1], TIMING1, T_R); @@ -596,6 +605,20 @@ static bool ot_i2c_dj_check_timings(OtI2CDjState *s) return res; } +static void ot_i2c_dj_clock_input(void *opaque, int irq, int level) +{ + OtI2CDjState *s = opaque; + + g_assert(irq == 0); + + if (level && ((uint32_t)level != s->pclk)) { + s->check_timings = true; + } + + s->pclk = (uint32_t)level; + /* TODO: disable I2C transfers when PCLK is 0 */ +} + static uint64_t ot_i2c_dj_read(void *opaque, hwaddr addr, unsigned size) { OtI2CDjState *s = opaque; @@ -1106,7 +1129,9 @@ static const MemoryRegionOps ot_i2c_dj_ops = { static Property ot_i2c_dj_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtI2CDjState, ot_id), - DEFINE_PROP_UINT32("pclk", OtI2CDjState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtI2CDjState, clock_name), + DEFINE_PROP_LINK("clock-src", OtI2CDjState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_END_OF_LIST(), }; @@ -1133,6 +1158,16 @@ static void ot_i2c_dj_reset_enter(Object *obj, ResetType type) ot_i2c_dj_target_reset_tx_fifo(s); ot_i2c_dj_target_reset_rx_fifo(s); + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = qdev_get_gpio_in_named(DEVICE(s), "clock-in", 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + } + s->check_timings = true; } @@ -1142,7 +1177,11 @@ static void ot_i2c_dj_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); - g_assert(s->pclk); + g_assert(s->clock_name); + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + qdev_init_gpio_in_named(DEVICE(s), &ot_i2c_dj_clock_input, "clock-in", 1); /* TODO: check if the following can be moved to ot_i2c_dj_init */ s->bus = i2c_init_bus(dev, TYPE_OT_I2C_DJ); From 46f565d3ac029fda3a8bbc63ce003087226b7217 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 17:25:55 +0200 Subject: [PATCH 094/175] [ot] hw/opentitan: ot_timer: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_timer.c | 61 ++++++++++++++++++++++++++++++++++----- hw/opentitan/trace-events | 1 + 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/hw/opentitan/ot_timer.c b/hw/opentitan/ot_timer.c index f5e6f923eacec..43de0af9b9be6 100644 --- a/hw/opentitan/ot_timer.c +++ b/hw/opentitan/ot_timer.c @@ -28,11 +28,13 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/timer.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_timer.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "trace.h" @@ -90,9 +92,12 @@ struct OtTimerState { uint32_t regs[REGS_COUNT]; int64_t origin_ns; + uint32_t pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ char *ot_id; - uint32_t pclk; + char *clock_name; + DeviceState *clock_src; }; struct OtTimerClass { @@ -110,6 +115,8 @@ static uint64_t ot_timer_ns_to_ticks(OtTimerState *s, int64_t ns) static int64_t ot_timer_ticks_to_ns(OtTimerState *s, uint64_t ticks) { + g_assert(s->pclk); + uint32_t prescaler = FIELD_EX32(s->regs[R_CFG0], CFG0, PRESCALE); uint32_t step = FIELD_EX32(s->regs[R_CFG0], CFG0, STEP); uint64_t ns = muldiv64(ticks, (prescaler + 1u), step); @@ -133,6 +140,8 @@ ot_timer_compute_next_timeout(OtTimerState *s, int64_t now, int64_t delta) { int64_t next; + g_assert(s->pclk); + /* wait at least 1 peripheral clock tick */ delta = MAX(delta, (int64_t)(NANOSECONDS_PER_SECOND / s->pclk)); @@ -170,6 +179,10 @@ static void ot_timer_rearm(OtTimerState *s, bool reset_origin) { timer_del(s->timer); + if (!s->pclk) { + return; + } + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); if (reset_origin) { @@ -205,6 +218,22 @@ static void ot_timer_cb(void *opaque) ot_timer_rearm(s, false); } +static void ot_timer_clock_input(void *opaque, int irq, int level) +{ + OtTimerState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + if (!s->pclk) { + timer_del(s->timer); + } + + trace_ot_timer_update_clock(s->ot_id, s->pclk); + /* TODO: @loic: update on-going timer */ +} + static uint64_t ot_timer_read(void *opaque, hwaddr addr, unsigned size) { OtTimerState *s = opaque; @@ -306,10 +335,12 @@ static void ot_timer_write(void *opaque, hwaddr addr, uint64_t value, * schedule the timer for the next peripheral clock tick to check again * for interrupt condition */ - int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); - int64_t next = ot_timer_compute_next_timeout(s, now, 0); - trace_ot_timer_timer_mod(s->ot_id, now, next, true); - timer_mod_anticipate(s->timer, next); + if (s->pclk) { + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + int64_t next = ot_timer_compute_next_timeout(s, now, 0); + trace_ot_timer_timer_mod(s->ot_id, now, next, true); + timer_mod_anticipate(s->timer, next); + } break; } case R_INTR_TEST0: @@ -357,7 +388,9 @@ static const MemoryRegionOps ot_timer_ops = { static Property ot_timer_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtTimerState, ot_id), - DEFINE_PROP_UINT32("pclk", OtTimerState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtTimerState, clock_name), + DEFINE_PROP_LINK("clock-src", OtTimerState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_END_OF_LIST(), }; @@ -379,6 +412,16 @@ static void ot_timer_reset_enter(Object *obj, ResetType type) ot_timer_update_irqs(s); ot_timer_update_alert(s); + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = qdev_get_gpio_in_named(DEVICE(s), "clock-in", 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + } } static void ot_timer_realize(DeviceState *dev, Error **errp) @@ -387,7 +430,11 @@ static void ot_timer_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); - g_assert(s->pclk > 0); + g_assert(s->clock_name); + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + qdev_init_gpio_in_named(DEVICE(s), &ot_timer_clock_input, "clock-in", 1); } static void ot_timer_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index c59668340897e..44d559e1ddc38 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -549,6 +549,7 @@ ot_sram_ctrl_update_exec(const char *id, bool cifetch, bool rifetch, bool oifetc ot_timer_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_timer_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_timer_timer_mod(const char *id, int64_t now, int64_t next, bool anticipate) "%s: @ %" PRId64 ": %" PRId64 " ant: %u" +ot_timer_update_clock(const char *id, uint32_t frequency) "%s: @ %u Hz" ot_timer_update_irq(const char *id, bool level) "%s: %d" # ot_uart.c From 6d68faf09afd4aabefce1917594fef353478c66a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 18:03:47 +0200 Subject: [PATCH 095/175] [ot] hw/opentitan: ot_aon_timer: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_aon_timer.c | 135 ++++++++++++++++++++++++++++-------- hw/opentitan/trace-events | 5 +- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/hw/opentitan/ot_aon_timer.c b/hw/opentitan/ot_aon_timer.c index db8e4d4153c89..3e78bf502b13f 100644 --- a/hw/opentitan/ot_aon_timer.c +++ b/hw/opentitan/ot_aon_timer.c @@ -33,11 +33,13 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/timer.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_aon_timer.h" #include "hw/opentitan/ot_common.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "trace.h" @@ -99,6 +101,12 @@ static const char REG_NAMES[REGS_COUNT][20u] = { }; #undef REG_NAME_ENTRY +typedef enum { + OT_AON_TIMER_CLOCK_SRC_IO, + OT_AON_TIMER_CLOCK_SRC_AON, + OT_AON_TIMER_CLOCK_SRC_COUNT +} OtAonTimerClockSrc; + struct OtAonTimerState { SysBusDevice parent_obj; @@ -119,9 +127,12 @@ struct OtAonTimerState { int64_t wkup_origin_ns; int64_t wdog_origin_ns; bool wdog_bite; + uint32_t pclks[OT_AON_TIMER_CLOCK_SRC_COUNT]; + const char *clock_src_names[OT_AON_TIMER_CLOCK_SRC_COUNT]; char *ot_id; - uint32_t pclk; + char *clock_names[OT_AON_TIMER_CLOCK_SRC_COUNT]; + DeviceState *clock_src; }; struct OtAonTimerClass { @@ -132,15 +143,17 @@ struct OtAonTimerClass { static uint64_t ot_aon_timer_ns_to_ticks(OtAonTimerState *s, uint32_t prescaler, int64_t ns) { - uint64_t ticks = muldiv64((uint64_t)ns, s->pclk, NANOSECONDS_PER_SECOND); + uint64_t ticks = + muldiv64((uint64_t)ns, s->pclks[OT_AON_TIMER_CLOCK_SRC_AON], + NANOSECONDS_PER_SECOND); return ticks / (prescaler + 1u); } static int64_t ot_aon_timer_ticks_to_ns(OtAonTimerState *s, uint32_t prescaler, uint64_t ticks) { - uint64_t ns = - muldiv64(ticks * (prescaler + 1u), NANOSECONDS_PER_SECOND, s->pclk); + uint64_t ns = muldiv64(ticks * (prescaler + 1u), NANOSECONDS_PER_SECOND, + s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]); if (ns > INT64_MAX) { return INT64_MAX; } @@ -170,8 +183,11 @@ static int64_t ot_aon_timer_compute_next_timeout(OtAonTimerState *s, { int64_t next; + g_assert(s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]); + /* wait at least 1 peripheral clock tick */ - delta = MAX(delta, (int64_t)(NANOSECONDS_PER_SECOND / s->pclk)); + delta = MAX(delta, (int64_t)(NANOSECONDS_PER_SECOND / + s->pclks[OT_AON_TIMER_CLOCK_SRC_AON])); if (sadd64_overflow(now, delta, &next)) { /* we overflowed the timer, just set it as large as we can */ @@ -219,6 +235,10 @@ static void ot_aon_timer_rearm_wkup(OtAonTimerState *s, bool reset_origin) { timer_del(s->wkup_timer); + if (!s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]) { + return; + } + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); if (reset_origin) { @@ -261,6 +281,10 @@ static void ot_aon_timer_rearm_wdog(OtAonTimerState *s, bool reset_origin) { int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + if (!s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]) { + return; + } + if (reset_origin) { s->wdog_origin_ns = now; } @@ -314,6 +338,26 @@ static void ot_aon_timer_wdog_cb(void *opaque) ot_aon_timer_rearm_wdog(s, false); } +static void ot_aon_timer_clock_input(void *opaque, int irq, int level) +{ + OtAonTimerState *s = opaque; + + g_assert((unsigned)irq < OT_AON_TIMER_CLOCK_SRC_COUNT); + + s->pclks[irq] = (unsigned)level; + + if (irq == OT_AON_TIMER_CLOCK_SRC_AON) { + if (!s->pclks[irq]) { + timer_del(s->wkup_timer); + timer_del(s->wdog_timer); + } + } + + trace_ot_aon_timer_update_clock(s->ot_id, irq, s->pclks[irq]); + /* TODO: @loic: update on-going timer */ +} + + static uint64_t ot_aon_timer_read(void *opaque, hwaddr addr, unsigned size) { OtAonTimerState *s = opaque; @@ -370,8 +414,8 @@ static uint64_t ot_aon_timer_read(void *opaque, hwaddr addr, unsigned size) } uint32_t pc = ibex_get_current_pc(); - trace_ot_aon_timer_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, - pc); + trace_ot_aon_timer_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), + val32, pc); return (uint64_t)val32; } @@ -386,8 +430,8 @@ static void ot_aon_timer_write(void *opaque, hwaddr addr, uint64_t value, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_aon_timer_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, - pc); + trace_ot_aon_timer_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); switch (reg) { case R_ALERT_TEST: @@ -406,12 +450,15 @@ static void ot_aon_timer_write(void *opaque, hwaddr addr, uint64_t value, } else { /* stop timer */ timer_del(s->wkup_timer); - /* save current count */ - int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); - uint64_t count = ot_aon_timer_get_wkup_count(s, (uint64_t)now); - s->regs[R_WKUP_COUNT_HI] = (uint32_t)(count >> 32u); - s->regs[R_WKUP_COUNT_LO] = (uint32_t)count; - s->wkup_origin_ns = now; + if (s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]) { + /* save current count */ + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + uint64_t count = + ot_aon_timer_get_wkup_count(s, (uint64_t)now); + s->regs[R_WKUP_COUNT_HI] = (uint32_t)(count >> 32u); + s->regs[R_WKUP_COUNT_LO] = (uint32_t)count; + s->wkup_origin_ns = now; + } } } break; @@ -443,10 +490,13 @@ static void ot_aon_timer_write(void *opaque, hwaddr addr, uint64_t value, } else { /* stop timer */ timer_del(s->wdog_timer); - /* save current count */ - int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); - s->regs[R_WDOG_COUNT] = ot_aon_timer_get_wdog_count(s, now); - s->wdog_origin_ns = now; + if (s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]) { + /* save current count */ + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + s->regs[R_WDOG_COUNT] = + ot_aon_timer_get_wdog_count(s, now); + s->wdog_origin_ns = now; + } } } } else { @@ -478,13 +528,15 @@ static void ot_aon_timer_write(void *opaque, hwaddr addr, uint64_t value, * schedule the timer for the next peripheral clock tick to check again * for interrupt condition */ - int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); - int64_t next = ot_aon_timer_compute_next_timeout(s, now, 0); - if (change & INTR_WKUP_TIMER_EXPIRED_MASK) { - timer_mod_anticipate(s->wkup_timer, next); - } - if (change & INTR_WDOG_TIMER_BARK_MASK) { - timer_mod_anticipate(s->wdog_timer, next); + if (s->pclks[OT_AON_TIMER_CLOCK_SRC_AON]) { + int64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + int64_t next = ot_aon_timer_compute_next_timeout(s, now, 0); + if (change & INTR_WKUP_TIMER_EXPIRED_MASK) { + timer_mod_anticipate(s->wkup_timer, next); + } + if (change & INTR_WDOG_TIMER_BARK_MASK) { + timer_mod_anticipate(s->wdog_timer, next); + } } break; } @@ -511,7 +563,12 @@ static const MemoryRegionOps ot_aon_timer_ops = { static Property ot_aon_timer_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtAonTimerState, ot_id), - DEFINE_PROP_UINT32("pclk", OtAonTimerState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtAonTimerState, + clock_names[OT_AON_TIMER_CLOCK_SRC_IO]), + DEFINE_PROP_STRING("clock-name-aon", OtAonTimerState, + clock_names[OT_AON_TIMER_CLOCK_SRC_AON]), + DEFINE_PROP_LINK("clock-src", OtAonTimerState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_END_OF_LIST(), }; @@ -533,6 +590,21 @@ static void ot_aon_timer_reset_enter(Object *obj, ResetType type) ot_aon_timer_update_irqs(s); ot_aon_timer_update_alert(s); + + for (unsigned ix = 0; ix < OT_AON_TIMER_CLOCK_SRC_COUNT; ix++) { + if (s->clock_src_names[ix]) { + continue; + } + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_names[ix] = ic->get_clock_source(ii, s->clock_names[ix], + DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), "clock-in", (int)ix); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_names[ix], 0, + in_irq); + } } static void ot_aon_timer_realize(DeviceState *dev, Error **errp) @@ -542,7 +614,14 @@ static void ot_aon_timer_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); - g_assert(s->pclk > 0); + for (unsigned ix = 0; ix < OT_AON_TIMER_CLOCK_SRC_COUNT; ix++) { + g_assert(s->clock_names[ix]); + } + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + qdev_init_gpio_in_named(DEVICE(s), &ot_aon_timer_clock_input, "clock-in", + OT_AON_TIMER_CLOCK_SRC_COUNT); } static void ot_aon_timer_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 44d559e1ddc38..da49904ad4092 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -35,9 +35,10 @@ ot_alert_skip_active(const char *id, char cls, const char *stname) "%s: class %c # ot_aon_timer.c ot_aon_timer_irqs(const char *id, bool wakeup, bool bark, bool bite) "%s: wkup:%u bark:%u bite:%u" -ot_aon_timer_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aon_timer_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aon_timer_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_aon_timer_set_wdog(const char *id, int64_t now, int64_t next) "%s: now %" PRId64 ", next %" PRId64 -ot_aon_timer_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aon_timer_update_clock(const char *id, int tid, uint32_t frequency) "%s: [%d] @ %u Hz" # ot_ast.c From 5b52c90222042d1e876acd075fa256296975d90b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 20 Jun 2025 18:27:16 +0200 Subject: [PATCH 096/175] [ot] hw/opentitan: ot_alert: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_alert.c | 70 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/hw/opentitan/ot_alert.c b/hw/opentitan/ot_alert.c index f04593fb627b3..ded43847c3ea7 100644 --- a/hw/opentitan/ot_alert.c +++ b/hw/opentitan/ot_alert.c @@ -26,6 +26,7 @@ * * Note: for now, only a minimalist subset of Alert Handler device is * implemented in order to enable OpenTitan's ROM boot to progress + * secondary clock source is not supported (secure.io_div4) */ #include "qemu/osdep.h" @@ -34,11 +35,13 @@ #include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" @@ -214,6 +217,12 @@ typedef enum { STATE_COUNT, } OtAlertAClassState; +typedef enum { + OT_ALERT_CLOCK_SRC_IO, + OT_ALERT_CLOCK_SRC_EDN, + OT_ALERT_CLOCK_SRC_COUNT +} OtAonTimerClockSrc; + struct OtAlertState { SysBusDevice parent_obj; @@ -227,10 +236,13 @@ struct OtAlertState { char **reg_names; /* ordered by register index */ unsigned reg_count; /* total count of registers */ unsigned reg_aclass_pos; /* index of the first register of OtAlertAClass */ + uint32_t pclks[OT_ALERT_CLOCK_SRC_COUNT]; + const char *clock_src_names[OT_ALERT_CLOCK_SRC_COUNT]; char *ot_id; OtEDNState *edn; - uint32_t pclk; + char *clock_names[OT_ALERT_CLOCK_SRC_COUNT]; + DeviceState *clock_src; uint16_t n_alerts; uint8_t edn_ep; uint8_t n_low_power_groups; @@ -395,9 +407,9 @@ static uint32_t ot_alert_reg_esc_count_read(OtAlertState *s, unsigned reg) } uint32_t cnt; - if (expire >= now) { - uint64_t rem64 = - muldiv64(expire - now, s->pclk, NANOSECONDS_PER_SECOND); + if ((expire >= now) && (s->pclks[OT_ALERT_CLOCK_SRC_IO] != 0)) { + uint64_t rem64 = muldiv64(expire - now, s->pclks[OT_ALERT_CLOCK_SRC_IO], + NANOSECONDS_PER_SECOND); uint32_t rem32 = (uint32_t)MIN(rem64, (uint64_t)UINT32_MAX); cnt = (rem32 < cycles) ? cycles - rem32 : 0; } else { @@ -478,9 +490,15 @@ ot_alert_reg_intr_state_write(OtAlertState *s, unsigned reg, uint32_t value) static void ot_alert_set_class_timer(OtAlertState *s, unsigned nclass, uint32_t timeout) { + if (s->pclks[OT_ALERT_CLOCK_SRC_IO] == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: no clock\n", __func__, + s->ot_id); + return; + } OtAlertScheduler *atimer = &s->schedulers[nclass]; /* TODO: update running schedulers if timeout_cyc_shadowed is updated */ - int64_t ns = (int64_t)muldiv64(timeout, NANOSECONDS_PER_SECOND, s->pclk); + int64_t ns = (int64_t)muldiv64(timeout, NANOSECONDS_PER_SECOND, + s->pclks[OT_ALERT_CLOCK_SRC_IO]); OtAlertAClassState state = ot_alert_get_class_state(s, nclass); trace_ot_alert_set_class_timer(s->ot_id, ACLASS(nclass), ST_NAME(state), @@ -776,6 +794,17 @@ static void ot_alert_signal_tx(void *opaque, int n, int level) ot_alert_update_irqs(s); } +static void ot_alert_clock_input(void *opaque, int irq, int level) +{ + OtAlertState *s = opaque; + + g_assert((unsigned)irq < OT_ALERT_CLOCK_SRC_COUNT); + + s->pclks[irq] = (unsigned)level; + + /* TODO: reinitialize timers on PCLK change */ +} + static uint64_t ot_alert_regs_read(void *opaque, hwaddr addr, unsigned size) { OtAlertState *s = opaque; @@ -964,7 +993,12 @@ static Property ot_alert_properties[] = { DEFINE_PROP_UINT16("n_alerts", OtAlertState, n_alerts, 0), DEFINE_PROP_UINT8("n_lpg", OtAlertState, n_low_power_groups, 1u), DEFINE_PROP_UINT8("n_classes", OtAlertState, n_classes, 4u), - DEFINE_PROP_UINT32("pclk", OtAlertState, pclk, 0u), + DEFINE_PROP_STRING("clock-name", OtAlertState, + clock_names[OT_ALERT_CLOCK_SRC_IO]), + DEFINE_PROP_STRING("clock-name-edn", OtAlertState, + clock_names[OT_ALERT_CLOCK_SRC_EDN]), + DEFINE_PROP_LINK("clock-src", OtAlertState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_LINK("edn", OtAlertState, edn, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtAlertState, edn_ep, UINT8_MAX), DEFINE_PROP_END_OF_LIST(), @@ -1011,6 +1045,21 @@ static void ot_alert_reset_enter(Object *obj, ResetType type) } ot_alert_update_irqs(s); + + for (unsigned ix = 0; ix < OT_ALERT_CLOCK_SRC_COUNT; ix++) { + if (s->clock_src_names[ix]) { + continue; + } + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_names[ix] = ic->get_clock_source(ii, s->clock_names[ix], + DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), "clock-in", (int)ix); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_names[ix], 0, + in_irq); + } } static void ot_alert_realize(DeviceState *dev, Error **errp) @@ -1021,8 +1070,15 @@ static void ot_alert_realize(DeviceState *dev, Error **errp) g_assert(s->ot_id); g_assert(s->n_alerts != 0); - g_assert(s->pclk != 0); g_assert(s->n_classes > 0 && s->n_classes <= 32); + for (unsigned ix = 0; ix < OT_ALERT_CLOCK_SRC_COUNT; ix++) { + g_assert(s->clock_names[ix]); + } + g_assert(s->clock_src); + OBJECT_CHECK(IbexClockSrcIf, s->clock_src, TYPE_IBEX_CLOCK_SRC_IF); + + qdev_init_gpio_in_named(DEVICE(s), &ot_alert_clock_input, "clock-in", + OT_ALERT_CLOCK_SRC_COUNT); size_t size = sizeof(OtAlertIntr) + sizeof(OtAlertPing) + sizeof(OtAlertTemplate) * s->n_alerts + From 4d19becd1e327045f3e5120045bf6b97b8e9b4c4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 14:25:10 +0200 Subject: [PATCH 097/175] [ot] hw/opentitan: ot_aes: add ot_id trace information Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_aes.c | 101 +++++++++++++++++++++----------------- hw/opentitan/trace-events | 24 ++++----- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index 63809ecd5f2e3..4ebde7693c5ac 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -254,6 +254,8 @@ struct OtAESState { OtPrngState *prng; unsigned reseed_count; bool fast_mode; + + char *ot_id; }; struct OtAESClass { @@ -281,14 +283,14 @@ static const char *ot_aes_hexdump(OtAESState *s, const uint8_t *buf, } #define trace_ot_aes_buf(_s_, _a_, _m_, _b_) \ - trace_ot_aes_buffer((_a_), (_m_), \ + trace_ot_aes_buffer((_s_)->ot_id, (_a_), (_m_), \ ot_aes_hexdump(_s_, (const uint8_t *)(_b_), \ OT_AES_DATA_SIZE)) #define trace_ot_aes_key(_s_, _a_, _b_, _l_) \ - trace_ot_aes_buffer((_a_), "key", \ + trace_ot_aes_buffer((_s_)->ot_id, (_a_), "key", \ ot_aes_hexdump(_s_, (const uint8_t *)(_b_), (_l_))) #define trace_ot_aes_iv(_s_, _a_, _b_) \ - trace_ot_aes_buffer((_a_), "iv", \ + trace_ot_aes_buffer((_s_)->ot_id, (_a_), "iv", \ ot_aes_hexdump(_s_, (const uint8_t *)(_b_), \ OT_AES_IV_SIZE)) #else @@ -296,9 +298,12 @@ static const char *ot_aes_hexdump(OtAESState *s, const uint8_t *buf, #define trace_ot_aes_key(_s_, _a_, _b_, _l_) #define trace_ot_aes_iv(_s_, _a_, _b_) #endif /* DEBUG_AES */ -#define xtrace_ot_aes_debug(_msg_) trace_ot_aes_debug(__func__, __LINE__, _msg_) -#define xtrace_ot_aes_info(_msg_) trace_ot_aes_info(__func__, __LINE__, _msg_) -#define xtrace_ot_aes_error(_msg_) trace_ot_aes_error(__func__, __LINE__, _msg_) +#define xtrace_ot_aes_debug(_otid_, _msg_) \ + trace_ot_aes_debug(__func__, __LINE__, _otid_, _msg_) +#define xtrace_ot_aes_info(_otid_, _msg_) \ + trace_ot_aes_info(__func__, __LINE__, _otid_, _msg_) +#define xtrace_ot_aes_error(_otid_, _msg_) \ + trace_ot_aes_error(__func__, __LINE__, _otid_, _msg_) static void ot_aes_reseed(OtAESState *s); @@ -406,7 +411,7 @@ static inline void ot_aes_load_reseed_rate(OtAESState *s) break; } - trace_ot_aes_reseed_rate(reseed); + trace_ot_aes_reseed_rate(s->ot_id, reseed); s->reseed_count = reseed; } @@ -440,10 +445,10 @@ static void ot_aes_init_keyshare(OtAESState *s, bool randomize) OtAESContext *c = s->ctx; if (randomize) { - trace_ot_aes_init("keyshare init (randomize data)"); + trace_ot_aes_init(s->ot_id, "keyshare init (randomize data)"); ot_aes_randomize(s, r->keyshare, ARRAY_SIZE(r->keyshare)); } else { - trace_ot_aes_init("keyshare init (data preserved)"); + trace_ot_aes_init(s->ot_id, "keyshare init (data preserved)"); } bitmap_zero(r->keyshare_bm, (int64_t)(PARAM_NUM_REGS_KEY * 2u)); c->key_ready = false; @@ -455,10 +460,10 @@ static void ot_aes_init_iv(OtAESState *s, bool randomize) OtAESContext *c = s->ctx; if (randomize) { - trace_ot_aes_init("iv init (randomize data)"); + trace_ot_aes_init(s->ot_id, "iv init (randomize data)"); ot_aes_randomize(s, r->iv, ARRAY_SIZE(r->iv)); } else { - trace_ot_aes_init("iv init (data preserved)"); + trace_ot_aes_init(s->ot_id, "iv init (data preserved)"); } bitmap_zero(r->iv_bm, PARAM_NUM_REGS_IV); c->iv_ready = false; @@ -469,11 +474,11 @@ static void ot_aes_init_data(OtAESState *s, bool io) OtAESRegisters *r = s->regs; if (!io) { - trace_ot_aes_init("data_in"); + trace_ot_aes_init(s->ot_id, "data_in"); ot_aes_randomize(s, r->data_in, ARRAY_SIZE(r->data_in)); bitmap_zero(r->data_in_bm, PARAM_NUM_REGS_DATA); } else { - trace_ot_aes_init("data_out"); + trace_ot_aes_init(s->ot_id, "data_out"); ot_aes_randomize(s, r->data_out, ARRAY_SIZE(r->data_out)); bitmap_zero(r->data_out_bm, PARAM_NUM_REGS_DATA); } @@ -512,7 +517,7 @@ static void ot_aes_trigger_reseed(OtAESState *s) ot_aes_reseed(s); } else { r->trigger &= ~R_TRIGGER_PRNG_RESEED_MASK; - xtrace_ot_aes_info("reseed on trigger disabled"); + xtrace_ot_aes_info(s->ot_id, "reseed on trigger disabled"); } } @@ -547,7 +552,7 @@ static void ot_aes_update_key(OtAESState *s) if (!c->key_ready && ot_aes_key_touch_force_reseed(r)) { r->trigger |= R_TRIGGER_PRNG_RESEED_MASK; - trace_ot_aes_reseed("new key"); + trace_ot_aes_reseed(s->ot_id, "new key"); ot_aes_trigger_reseed(s); } @@ -579,19 +584,19 @@ static inline bool ot_aes_can_process(const OtAESState *s) bool need_iv; if (!ot_aes_is_mode_ready(r, &need_iv)) { - xtrace_ot_aes_debug("mode not ready"); + xtrace_ot_aes_debug(s->ot_id, "mode not ready"); return false; } OtAESContext *c = s->ctx; if (!c->key_ready) { - xtrace_ot_aes_debug("key not ready"); + xtrace_ot_aes_debug(s->ot_id, "key not ready"); return false; } if (need_iv && !c->iv_ready) { - xtrace_ot_aes_debug("IV not ready"); + xtrace_ot_aes_debug(s->ot_id, "IV not ready"); return false; } @@ -599,19 +604,19 @@ static inline bool ot_aes_can_process(const OtAESState *s) /* auto mode */ if (c->do_full) { /* cannot schedule a round if output FIFO has not been emptied */ - xtrace_ot_aes_debug("DO full"); + xtrace_ot_aes_debug(s->ot_id, "DO full"); return false; } } else { if (!(r->trigger & R_TRIGGER_START_MASK)) { /* cannot execute in manual mode w/o an explicit trigger */ - xtrace_ot_aes_debug("manual not triggered"); + xtrace_ot_aes_debug(s->ot_id, "manual not triggered"); return false; } } if (!c->di_full) { - xtrace_ot_aes_debug("DI not filled"); + xtrace_ot_aes_debug(s->ot_id, "DI not filled"); } /* TODO: not sure if this also applies in manual mode */ @@ -629,10 +634,10 @@ static void ot_aes_handle_trigger(OtAESState *s) ibex_irq_set(&s->clkmgr, (int)true); if (r->trigger & R_TRIGGER_PRNG_RESEED_MASK) { - trace_ot_aes_reseed("trigger write"); + trace_ot_aes_reseed(s->ot_id, "trigger write"); ot_aes_trigger_reseed(s); if (s->edn.scheduled) { - xtrace_ot_aes_debug("EDN scheduled, defer"); + xtrace_ot_aes_debug(s->ot_id, "EDN scheduled, defer"); return; } } @@ -652,18 +657,18 @@ static void ot_aes_handle_trigger(OtAESState *s) if (r->trigger & R_TRIGGER_START_MASK) { if (ot_aes_get_mode(r) == AES_NONE || !ot_aes_is_manual(r)) { /* ignore */ - xtrace_ot_aes_debug("start trigger ignored"); + xtrace_ot_aes_debug(s->ot_id, "start trigger ignored"); return; } } /* an AES round might have been delayed */ if (ot_aes_can_process(s)) { - trace_ot_aes_schedule(); + trace_ot_aes_schedule(s->ot_id); qemu_bh_schedule(s->process_bh); } - xtrace_ot_aes_debug(ot_aes_is_idle(s) ? "IDLE" : "NOT IDLE"); + xtrace_ot_aes_debug(s->ot_id, ot_aes_is_idle(s) ? "IDLE" : "NOT IDLE"); ibex_irq_set(&s->clkmgr, (int)!ot_aes_is_idle(s)); } @@ -673,7 +678,7 @@ static void ot_aes_update_config(OtAESState *s) bool need_iv; - xtrace_ot_aes_debug("CONFIG"); + xtrace_ot_aes_debug(s->ot_id, "CONFIG"); if (!ot_aes_is_mode_ready(r, &need_iv)) { return; @@ -829,7 +834,7 @@ static void ot_aes_process(OtAESState *s) int rc; - xtrace_ot_aes_debug("process"); + xtrace_ot_aes_debug(s->ot_id, "process"); if (encrypt) { trace_ot_aes_buf(s, OT_AES_MODE_NAMES[mode], "enc/in ", c->src); @@ -936,7 +941,7 @@ static inline void ot_aes_do_process(OtAESState *s) * otherwise, IDLE status may be false once the vCPU has read the data, * which would not match the HW behavior */ - trace_ot_aes_reseed("reseed_count reached"); + trace_ot_aes_reseed(s->ot_id, "reseed_count reached"); s->regs->trigger |= R_TRIGGER_PRNG_RESEED_MASK; ot_aes_trigger_reseed(s); ot_aes_load_reseed_rate(s); @@ -947,7 +952,7 @@ static inline void ot_aes_do_process(OtAESState *s) OtAESRegisters *r = s->regs; if (ot_aes_is_manual(r)) { - xtrace_ot_aes_info("end of manual seq"); + xtrace_ot_aes_info(s->ot_id, "end of manual seq"); s->regs->trigger &= ~R_TRIGGER_START_MASK; } } @@ -980,7 +985,7 @@ static void ot_aes_process_cond(OtAESState *s) } } } else { - xtrace_ot_aes_info("defer exec, waiting for EDN"); + xtrace_ot_aes_info(s->ot_id, "defer exec, waiting for EDN"); } } @@ -994,10 +999,10 @@ static void ot_aes_fill_entropy(void *opaque, uint32_t bits, bool fips) OtAESRegisters *r = s->regs; if (!edn->scheduled) { - xtrace_ot_aes_error("unexpected entropy"); + xtrace_ot_aes_error(s->ot_id, "unexpected entropy"); return; } - trace_ot_aes_fill_entropy(bits, fips); + trace_ot_aes_fill_entropy(s->ot_id, bits, fips); edn->scheduled = false; r->trigger &= ~R_TRIGGER_PRNG_RESEED_MASK; @@ -1029,11 +1034,11 @@ static void ot_aes_reseed(OtAESState *s) edn->connected = true; } if (!edn->scheduled) { - trace_ot_aes_request_entropy(); + trace_ot_aes_request_entropy(s->ot_id); if (!ot_edn_request_entropy(edn->device, edn->ep)) { edn->scheduled = true; } else { - xtrace_ot_aes_error("cannot request new entropy"); + xtrace_ot_aes_error(s->ot_id, "cannot request new entropy"); } } } @@ -1072,8 +1077,8 @@ static uint64_t ot_aes_read(void *opaque, hwaddr addr, unsigned size) case R_DATA_IN_3: case R_TRIGGER: qemu_log_mask(LOG_GUEST_ERROR, - "%s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); val32 = 0u; break; case R_IV_0: @@ -1113,14 +1118,16 @@ static uint64_t ot_aes_read(void *opaque, hwaddr addr, unsigned size) } break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0u; break; } uint32_t pc = ibex_get_current_pc(); - trace_ot_aes_io_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_aes_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); return (uint64_t)val32; }; @@ -1136,7 +1143,7 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_aes_io_write((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_aes_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, pc); switch (reg) { case R_ALERT_TEST: @@ -1154,8 +1161,8 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, case R_DATA_OUT_3: case R_STATUS: qemu_log_mask(LOG_GUEST_ERROR, - "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); break; case R_KEY_SHARE0_0: case R_KEY_SHARE0_1: @@ -1263,13 +1270,15 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, ot_aes_handle_trigger(s); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } }; static Property ot_aes_properties[] = { + DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtAESState, ot_id), DEFINE_PROP_LINK("edn", OtAESState, edn.device, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtAESState, edn.ep, UINT8_MAX), /* @@ -1330,7 +1339,7 @@ static void ot_aes_reset_exit(Object *obj, ResetType type) qemu_bh_cancel(s->process_bh); - trace_ot_aes_reseed("reset"); + trace_ot_aes_reseed(s->ot_id, "reset"); ot_aes_handle_trigger(s); } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index da49904ad4092..fb08af25c382d 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -2,18 +2,18 @@ # ot_aes.c -ot_aes_buffer(const char *mode, const char * msg, const char * hexbuf) "[%s] %s: %s" -ot_aes_debug(const char *func, int line, const char *msg) "%s:%d %s" -ot_aes_error(const char *func, int line, const char *err) "%s:%d %s" -ot_aes_fill_entropy(uint32_t bits, bool fips) "0x%08x fips:%u" -ot_aes_info(const char *func, int line, const char *errl) "%s:%d %s" -ot_aes_init(const char *what) "%s" -ot_aes_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_aes_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_aes_request_entropy(void) "" -ot_aes_reseed(const char *reason) "%s" -ot_aes_reseed_rate(unsigned rate) "%u" -ot_aes_schedule(void) "" +ot_aes_buffer(const char *id, const char *mode, const char * msg, const char * hexbuf) "%s: [%s] %s: %s" +ot_aes_debug(const char *func, int line, const char *id, const char *msg) "%s:%d %s: %s" +ot_aes_error(const char *func, int line, const char *id, const char *err) "%s:%d %s: %s" +ot_aes_fill_entropy(const char *id, uint32_t bits, bool fips) "%s: 0x%08x fips:%u" +ot_aes_info(const char *func, int line, const char *id, const char *errl) "%s:%d %s: %s" +ot_aes_init(const char *id, const char *what) "%s: %s" +ot_aes_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_aes_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_aes_request_entropy(const char *id) "%s" +ot_aes_reseed(const char *id, const char *reason) "%s: %s" +ot_aes_reseed_rate(const char *id, unsigned rate) "%s: %u" +ot_aes_schedule(const char *id) "%s" # ot_alert.c From 6c7f3aba363ff5496130fe1018e6f1360972a201 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 11:07:52 +0200 Subject: [PATCH 098/175] [ot] hw/opentitan: ot_aes: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_aes.c | 61 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index 4ebde7693c5ac..a1929efc6bad3 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -34,6 +34,7 @@ #include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_aes.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_clkmgr.h" @@ -42,6 +43,7 @@ #include "hw/opentitan/ot_prng.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" @@ -129,6 +131,9 @@ REG32(STATUS, 0x84u) #define OT_AES_KEY_SIZE (PARAM_NUM_REGS_KEY * sizeof(uint32_t)) #define OT_AES_IV_SIZE (PARAM_NUM_REGS_IV * sizeof(uint32_t)) +#define OT_AES_CLOCK_ACTIVE "clock-active" +#define OT_AES_CLOCK_INPUT "clock-in" + /* arbitrary value long enough to give back execution to vCPU */ #define OT_AES_RETARD_DELAY_NS 10000u /* 10 us */ @@ -244,7 +249,7 @@ struct OtAESState { SysBusDevice parent_obj; MemoryRegion mmio; IbexIRQ alerts[PARAM_NUM_ALERTS]; - IbexIRQ clkmgr; + IbexIRQ clock_active; QEMUBH *process_bh; QEMUTimer *retard_timer; /* only used with disabled fast-mode */ @@ -252,10 +257,14 @@ struct OtAESState { OtAESContext *ctx; OtAESEDN edn; OtPrngState *prng; + const char *clock_src_name; /* IRQ name once connected */ + unsigned pclk; /* Current input clock */ unsigned reseed_count; bool fast_mode; char *ot_id; + char *clock_name; + DeviceState *clock_src; }; struct OtAESClass { @@ -631,7 +640,7 @@ static void ot_aes_handle_trigger(OtAESState *s) */ OtAESRegisters *r = s->regs; - ibex_irq_set(&s->clkmgr, (int)true); + ibex_irq_set(&s->clock_active, (int)true); if (r->trigger & R_TRIGGER_PRNG_RESEED_MASK) { trace_ot_aes_reseed(s->ot_id, "trigger write"); @@ -669,7 +678,7 @@ static void ot_aes_handle_trigger(OtAESState *s) } xtrace_ot_aes_debug(s->ot_id, ot_aes_is_idle(s) ? "IDLE" : "NOT IDLE"); - ibex_irq_set(&s->clkmgr, (int)!ot_aes_is_idle(s)); + ibex_irq_set(&s->clock_active, (int)!ot_aes_is_idle(s)); } static void ot_aes_update_config(OtAESState *s) @@ -1043,6 +1052,17 @@ static void ot_aes_reseed(OtAESState *s) } } +static void ot_aes_clock_input(void *opaque, int irq, int level) +{ + OtAESState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable AES execution when PCLK is 0 */ +} + static uint64_t ot_aes_read(void *opaque, hwaddr addr, unsigned size) { OtAESState *s = opaque; @@ -1205,7 +1225,7 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, r->data_in[reg - R_DATA_IN_0] = val32; set_bit((int64_t)(reg - R_DATA_IN_0), r->data_in_bm); if (ot_aes_is_data_in_ready(r)) { - ibex_irq_set(&s->clkmgr, (int)true); + ibex_irq_set(&s->clock_active, (int)true); ot_aes_pop(s); } if (!ot_aes_is_manual(r)) { @@ -1279,6 +1299,9 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, static Property ot_aes_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtAESState, ot_id), + DEFINE_PROP_STRING("clock-name", OtAESState, clock_name), + DEFINE_PROP_LINK("clock-src", OtAESState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_LINK("edn", OtAESState, edn.device, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtAESState, edn.ep, UINT8_MAX), /* @@ -1326,6 +1349,28 @@ static void ot_aes_reset_enter(Object *obj, ResetType type) for (unsigned ix = 0; ix < PARAM_NUM_ALERTS; ix++) { ibex_irq_set(&s->alerts[ix], 0); } + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), OT_AES_CLOCK_INPUT, 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + + if (object_dynamic_cast(OBJECT(s->clock_src), TYPE_OT_CLKMGR)) { + char *hint_name = + g_strdup_printf(OT_CLOCK_HINT_PREFIX "%s", s->clock_name); + qemu_irq hint_irq = + qdev_get_gpio_in_named(s->clock_src, hint_name, 0); + g_assert(hint_irq); + qdev_connect_gpio_out_named(DEVICE(s), OT_AES_CLOCK_ACTIVE, 0, + hint_irq); + g_free(hint_name); + } + } } static void ot_aes_reset_exit(Object *obj, ResetType type) @@ -1352,6 +1397,12 @@ static void ot_aes_realize(DeviceState *dev, Error **errp) g_assert(e->device); g_assert(e->ep != UINT8_MAX); + g_assert(s->ot_id); + g_assert(s->clock_name); + g_assert(s->clock_src); + + qdev_init_gpio_in_named(DEVICE(s), &ot_aes_clock_input, OT_AES_CLOCK_INPUT, + 1); s->prng = ot_prng_allocate(); } @@ -1377,7 +1428,7 @@ static void ot_aes_init(Object *obj) ibex_qdev_init_irq(obj, &s->alerts[ix], OT_DEVICE_ALERT); } - ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); + ibex_qdev_init_irq(obj, &s->clock_active, OT_AES_CLOCK_ACTIVE); s->process_bh = qemu_bh_new(&ot_aes_handle_process, s); s->retard_timer = timer_new_ns(OT_VIRTUAL_CLOCK, &ot_aes_handle_process, s); From 19fd068e47e11ebedc9437b9c2533fcf07f1c59a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 11:31:10 +0200 Subject: [PATCH 099/175] [ot] hw/opentitan: ot_otbn: add ot_id trace information Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otbn.c | 70 ++++++++++++++++++++++----------------- hw/opentitan/trace-events | 28 ++++++++-------- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/hw/opentitan/ot_otbn.c b/hw/opentitan/ot_otbn.c index 11b3d5da15607..358b8a7193aea 100644 --- a/hw/opentitan/ot_otbn.c +++ b/hw/opentitan/ot_otbn.c @@ -164,8 +164,9 @@ struct OtOTBNState { enum OtOTBNCommand last_cmd; - OtOTBNRandom rnds[OT_OTBN_RND_COUNT]; + char *ot_id; char *log_file; + OtOTBNRandom rnds[OT_OTBN_RND_COUNT]; bool log_asm; }; @@ -189,7 +190,7 @@ static bool ot_otbn_is_locked(OtOTBNState *s) static void ot_otbn_update_irq(OtOTBNState *s) { bool level = s->intr_state & s->intr_enable & INTR_DONE_MASK; - trace_ot_otbn_irq(s->intr_state, s->intr_enable, level); + trace_ot_otbn_irq(s->ot_id, s->intr_state, s->intr_enable, level); ibex_irq_set(&s->irq_done, level); } @@ -210,7 +211,7 @@ static void ot_otbn_post_execute(void *opaque) uint32_t errbits = ot_otbn_proxy_get_err_bits(s->proxy); uint32_t insncount = ot_otbn_proxy_get_instruction_count(s->proxy); - trace_ot_otbn_post_execute(errbits, insncount); + trace_ot_otbn_post_execute(s->ot_id, errbits, insncount); s->fatal_alert_cause |= errbits >> 16U; s->intr_state |= INTR_DONE_MASK; ot_otbn_proxy_acknowledge_execution(s->proxy); @@ -232,7 +233,7 @@ static void ot_otbn_trigger_entropy_req(void *opaque) /* sanity check */ unsigned slot = (unsigned)(uintptr_t)(r - &r->otbn->rnds[0]); - trace_ot_otbn_proxy_entropy_request(slot); + trace_ot_otbn_proxy_entropy_request(r->otbn->ot_id, slot); switch (slot) { case OT_OTBN_URND: @@ -253,7 +254,7 @@ static void ot_otbn_proxy_completion_bh(void *opaque) enum OtOTBNCommand last_cmd = s->last_cmd; s->last_cmd = OT_OTBN_CMD_NONE; - trace_ot_otbn_proxy_completion_bh(last_cmd); + trace_ot_otbn_proxy_completion_bh(s->ot_id, last_cmd); switch (last_cmd) { case OT_OTBN_CMD_EXECUTE: @@ -283,16 +284,17 @@ static void ot_otbn_proxy_completion_bh(void *opaque) static void ot_otbn_fill_entropy(void *opaque, uint32_t bits, bool fips) { OtOTBNRandom *rnd = opaque; + OtOTBNState *s = rnd->otbn; if (!rnd->entropy_requested) { /* entropy not expected, may occur on reset */ - trace_ot_otbn_error("received unexpected entropy"); + trace_ot_otbn_error(s->ot_id, "received unexpected entropy"); return; } if (ot_fifo32_is_full(&rnd->packer)) { /* too many entropy bits, internal error */ - trace_ot_otbn_error("received too many entropy"); + trace_ot_otbn_error(s->ot_id, "received too many entropy"); return; } @@ -313,18 +315,17 @@ static void ot_otbn_fill_entropy(void *opaque, uint32_t bits, bool fips) const uint8_t *buf8 = (const uint8_t *)buf; g_assert(num == OT_OTBN_RANDOM_WORD_COUNT); num *= sizeof(uint32_t); - OtOTBNState *s = rnd->otbn; g_assert(s != NULL); unsigned rnd_ix = (unsigned)(rnd - &s->rnds[0]); int res; switch (rnd_ix) { case OT_OTBN_URND: - trace_ot_otbn_proxy_push_entropy("urnd", !rnd->no_fips); + trace_ot_otbn_proxy_push_entropy(s->ot_id, "urnd", !rnd->no_fips); res = ot_otbn_proxy_push_entropy(s->proxy, rnd_ix, buf8, num, !rnd->no_fips); break; case OT_OTBN_RND: - trace_ot_otbn_proxy_push_entropy("rnd", !rnd->no_fips); + trace_ot_otbn_proxy_push_entropy(s->ot_id, "rnd", !rnd->no_fips); res = ot_otbn_proxy_push_entropy(s->proxy, rnd_ix, buf8, num, !rnd->no_fips); break; @@ -335,7 +336,7 @@ static void ot_otbn_fill_entropy(void *opaque, uint32_t bits, bool fips) ot_fifo32_reset(&rnd->packer); rnd->no_fips = false; if (res) { - trace_ot_otbn_error("cannot push entropy"); + trace_ot_otbn_error(s->ot_id, "cannot push entropy"); } } @@ -352,10 +353,12 @@ static void ot_otbn_request_entropy(OtOTBNRandom *rnd) return; } + OtOTBNState *s = rnd->otbn; + rnd->entropy_requested = true; - trace_ot_otbn_request_entropy(rnd->ep); + trace_ot_otbn_request_entropy(s->ot_id, rnd->ep); if (ot_edn_request_entropy(rnd->device, rnd->ep)) { - trace_ot_otbn_error("failed to request entropy"); + trace_ot_otbn_error(s->ot_id, "failed to request entropy"); rnd->entropy_requested = false; } } @@ -371,14 +374,15 @@ static void ot_otbn_handle_command(OtOTBNState *s, unsigned command) /* "Writes are ignored if OTBN is not idle" */ if (!ot_otbn_is_idle(s)) { qemu_log_mask(LOG_GUEST_ERROR, - "Cannot execute command %02X from a not IDLE state\n", - command); + "%s: %s: cannot execute cmd %02X from a not IDLE state\n", + __func__, s->ot_id, command); return; } if (s->last_cmd != OT_OTBN_CMD_NONE) { qemu_log_mask(LOG_GUEST_ERROR, - "Previous command %02X did not complete\n", s->last_cmd); + "%s: %s: previous command %02X did not complete\n", + __func__, s->ot_id, s->last_cmd); return; } @@ -399,7 +403,8 @@ static void ot_otbn_handle_command(OtOTBNState *s, unsigned command) break; default: ibex_irq_set(&s->clkmgr, false); - qemu_log_mask(LOG_GUEST_ERROR, "Invalid command %02X\n", s->last_cmd); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Invalid command %02X\n", + __func__, s->ot_id, s->last_cmd); break; } } @@ -442,17 +447,19 @@ static uint64_t ot_otbn_regs_read(void *opaque, hwaddr addr, unsigned size) case R_ALERT_TEST: case R_CMD: val32 = 0; - qemu_log_mask(LOG_GUEST_ERROR, "%s: %s is write only\n", __func__, - REG_NAME(reg)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s is write only\n", __func__, + s->ot_id, REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0; break; } - trace_ot_otbn_io_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_otbn_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); return (uint64_t)val32; } @@ -467,10 +474,10 @@ static void ot_otbn_regs_write(void *opaque, hwaddr addr, uint64_t val64, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_otbn_io_write((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_otbn_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, pc); if (ot_otbn_is_locked(s)) { - trace_ot_otbn_deny(pc, "write denied: locked"); + trace_ot_otbn_deny(s->ot_id, pc, "write denied: locked"); return; } @@ -515,12 +522,13 @@ static void ot_otbn_regs_write(void *opaque, hwaddr addr, uint64_t val64, break; case R_STATUS: case R_FATAL_ALERT_CAUSE: - qemu_log_mask(LOG_GUEST_ERROR, "%s: %s is read only\n", __func__, - REG_NAME(reg)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s is read only\n", __func__, + s->ot_id, REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } } @@ -540,7 +548,7 @@ static void ot_otbn_update_checksum(OtOTBNState *s, bool doi, uint32_t addr, static uint32_t ot_otbn_mem_read(OtOTBNState *s, bool doi, hwaddr addr) { uint32_t value = ot_otbn_proxy_read_memory(s->proxy, doi, addr); - trace_ot_otbn_mem_read(doi ? 'I' : 'D', (uint32_t)addr, value); + trace_ot_otbn_mem_read(s->ot_id, doi ? 'I' : 'D', (uint32_t)addr, value); return value; } @@ -548,7 +556,7 @@ static void ot_otbn_mem_write(OtOTBNState *s, bool doi, hwaddr addr, uint32_t value) { bool written = ot_otbn_proxy_write_memory(s->proxy, doi, addr, value); - trace_ot_otbn_mem_write(doi ? 'I' : 'D', (uint32_t)addr, value, + trace_ot_otbn_mem_write(s->ot_id, doi ? 'I' : 'D', (uint32_t)addr, value, written ? "" : " FAILED"); if (written) { ot_otbn_update_checksum(s, doi, addr, value); @@ -590,6 +598,7 @@ static inline void ot_otbn_dmem_write(void *opaque, hwaddr addr, uint64_t val64, } static Property ot_otbn_properties[] = { + DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtOTBNState, ot_id), DEFINE_PROP_LINK("edn-u", OtOTBNState, rnds[OT_OTBN_URND].device, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_LINK("edn-r", OtOTBNState, rnds[OT_OTBN_RND].device, @@ -680,6 +689,7 @@ static void ot_otbn_realize(DeviceState *dev, Error **errp) (void)errp; OtOTBNState *s = OT_OTBN(dev); + g_assert(s->ot_id); g_assert(s->rnds[OT_OTBN_URND].device); g_assert(s->rnds[OT_OTBN_RND].device); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index fb08af25c382d..f692f5a48a5a3 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -323,20 +323,20 @@ ot_mbx_sys_io_write(const char * mid, uint32_t addr, const char * regname, uint3 # ot_otbn.c -ot_otbn_change_status(const char * status) "status=%s" -ot_otbn_deny(uint32_t pc, const char *msg) "pc=0x%x %s" -ot_otbn_error(const char *msg) "%s" -ot_otbn_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_otbn_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_otbn_irq(uint32_t active, uint32_t mask, bool level) "act:0x%08x msk:0x%08x lvl:%u" -ot_otbn_mem_read(char mem, uint32_t addr, uint32_t value) "%cmem addr=0x%04x, val=0x%08x" -ot_otbn_mem_write(char mem, uint32_t addr, uint32_t value, const char *outcome) "%cmem addr=0x%04x, val=0x%08x%s" -ot_otbn_post_execute(uint32_t errbits, uint32_t insncount) "errbits=0x%08x, insncount=%u" -ot_otbn_proxy_completion_bh(unsigned cmd) "aftercmd=0x%02x" -ot_otbn_proxy_entropy_request(unsigned rnd) "%u" -ot_otbn_proxy_entropy_req_bh(void) "" -ot_otbn_proxy_push_entropy(const char *kind, bool fips) "%s: fips %u" -ot_otbn_request_entropy(unsigned ep) "ep:%u" +ot_otbn_change_status(const char * id, const char * status) "%s: status=%s" +ot_otbn_deny(const char * id, uint32_t pc, const char *msg) "%s: pc=0x%x %s" +ot_otbn_error(const char * id, const char *msg) "%s: %s" +ot_otbn_io_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char * id, %s), val=0x%x, pc=0x%x" +ot_otbn_io_write(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char * id, %s), val=0x%x, pc=0x%x" +ot_otbn_irq(const char * id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" +ot_otbn_mem_read(const char * id, char mem, uint32_t addr, uint32_t value) "%s: %cmem addr=0x%04x, val=0x%08x" +ot_otbn_mem_write(const char * id, char mem, uint32_t addr, uint32_t value, const char *outcome) "%s: %cmem addr=0x%04x, val=0x%08x%s" +ot_otbn_post_execute(const char * id, uint32_t errbits, uint32_t insncount) "%s: errbits=0x%08x, insncount=%u" +ot_otbn_proxy_completion_bh(const char * id, unsigned cmd) "%s: aftercmd=0x%02x" +ot_otbn_proxy_entropy_request(const char * id, unsigned rnd) "%s: %u" +ot_otbn_proxy_entropy_req_bh(const char * id, void) "%s" +ot_otbn_proxy_push_entropy(const char * id, const char *kind, bool fips) "%s: %s: fips %u" +ot_otbn_request_entropy(const char * id, unsigned ep) "%s: ep:%u" # ot_otp.c From 9f6bd1d2ce6a7bbcc32f1fe9b91413880c58ea55 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 11:45:42 +0200 Subject: [PATCH 100/175] [ot] hw/opentitan: ot_otbn: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otbn.c | 60 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_otbn.c b/hw/opentitan/ot_otbn.c index 358b8a7193aea..fd04dd405bc9c 100644 --- a/hw/opentitan/ot_otbn.c +++ b/hw/opentitan/ot_otbn.c @@ -35,6 +35,7 @@ #include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/typedefs.h" +#include "qapi/error.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" @@ -44,6 +45,7 @@ #include "hw/opentitan/otbn/otbnproxy.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "hw/sysbus.h" @@ -99,6 +101,9 @@ REG32(LOAD_CHECKSUM, 0x28u) #define OT_OTBN_IMEM_BASE 0x4000u #define OT_OTBN_DMEM_BASE 0x8000u +#define OT_OTBN_CLOCK_ACTIVE "clock-active" +#define OT_OTBN_CLOCK_INPUT "clock-in" + #define R_LAST_REG (R_LOAD_CHECKSUM) #define REGS_COUNT (R_LAST_REG + 1u) #define REGS_SIZE (REGS_COUNT * sizeof(uint32_t)) @@ -149,11 +154,13 @@ struct OtOTBNState { IbexIRQ irq_done; IbexIRQ alerts[ALERT_COUNT]; - IbexIRQ clkmgr; + IbexIRQ clock_active; QEMUBH *proxy_completion_bh; QEMUTimer *proxy_defer; OTBNProxy proxy; + unsigned pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ uint32_t intr_state; uint32_t intr_enable; @@ -165,6 +172,8 @@ struct OtOTBNState { enum OtOTBNCommand last_cmd; char *ot_id; + char *clock_name; + DeviceState *clock_src; char *log_file; OtOTBNRandom rnds[OT_OTBN_RND_COUNT]; bool log_asm; @@ -217,7 +226,7 @@ static void ot_otbn_post_execute(void *opaque) ot_otbn_proxy_acknowledge_execution(s->proxy); ot_otbn_update_alert(s); ot_otbn_update_irq(s); - ibex_irq_set(&s->clkmgr, false); + ibex_irq_set(&s->clock_active, false); } static void ot_otbn_signal_on_completion(void *opaque) @@ -386,7 +395,7 @@ static void ot_otbn_handle_command(OtOTBNState *s, unsigned command) return; } - ibex_irq_set(&s->clkmgr, true); + ibex_irq_set(&s->clock_active, true); switch (command) { case (unsigned)OT_OTBN_CMD_EXECUTE: @@ -402,13 +411,24 @@ static void ot_otbn_handle_command(OtOTBNState *s, unsigned command) ot_otbn_proxy_wipe_memory(s->proxy, true); break; default: - ibex_irq_set(&s->clkmgr, false); + ibex_irq_set(&s->clock_active, false); qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Invalid command %02X\n", __func__, s->ot_id, s->last_cmd); break; } } +static void ot_otbn_clock_input(void *opaque, int irq, int level) +{ + OtOTBNState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable OTBN execution when PCLK is 0 */ +} + static uint64_t ot_otbn_regs_read(void *opaque, hwaddr addr, unsigned size) { OtOTBNState *s = opaque; @@ -599,6 +619,9 @@ static inline void ot_otbn_dmem_write(void *opaque, hwaddr addr, uint64_t val64, static Property ot_otbn_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtOTBNState, ot_id), + DEFINE_PROP_STRING("clock-name", OtOTBNState, clock_name), + DEFINE_PROP_LINK("clock-src", OtOTBNState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_LINK("edn-u", OtOTBNState, rnds[OT_OTBN_URND].device, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_LINK("edn-r", OtOTBNState, rnds[OT_OTBN_RND].device, @@ -666,6 +689,28 @@ static void ot_otbn_reset_enter(Object *obj, ResetType type) rnd->entropy_requested = false; ot_fifo32_reset(&rnd->packer); } + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), OT_OTBN_CLOCK_INPUT, 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + + if (object_dynamic_cast(OBJECT(s->clock_src), TYPE_OT_CLKMGR)) { + char *hint_name = + g_strdup_printf(OT_CLOCK_HINT_PREFIX "%s", s->clock_name); + qemu_irq hint_irq = + qdev_get_gpio_in_named(s->clock_src, hint_name, 0); + g_assert(hint_irq); + qdev_connect_gpio_out_named(DEVICE(s), OT_OTBN_CLOCK_ACTIVE, 0, + hint_irq); + g_free(hint_name); + } + } } static void ot_otbn_reset_exit(Object *obj, ResetType type) @@ -690,11 +735,16 @@ static void ot_otbn_realize(DeviceState *dev, Error **errp) OtOTBNState *s = OT_OTBN(dev); g_assert(s->ot_id); + g_assert(s->clock_name); + g_assert(s->clock_src); g_assert(s->rnds[OT_OTBN_URND].device); g_assert(s->rnds[OT_OTBN_RND].device); g_assert(s->rnds[OT_OTBN_URND].ep != UINT8_MAX); g_assert(s->rnds[OT_OTBN_RND].ep != UINT8_MAX); + + qdev_init_gpio_in_named(DEVICE(s), &ot_otbn_clock_input, + OT_OTBN_CLOCK_INPUT, 1); } static void ot_otbn_init(Object *obj) @@ -726,7 +776,7 @@ static void ot_otbn_init(Object *obj) ibex_sysbus_init_irq(obj, &s->irq_done); ibex_qdev_init_irqs(obj, s->alerts, OT_DEVICE_ALERT, ALERT_COUNT); - ibex_qdev_init_irq(obj, &s->clkmgr, OT_CLOCK_ACTIVE); + ibex_qdev_init_irq(obj, &s->clock_active, OT_OTBN_CLOCK_ACTIVE); for (unsigned rix = 0; rix < (unsigned)OT_OTBN_RND_COUNT; rix++) { OtOTBNRandom *r = &s->rnds[rix]; From 800e39f05a3fb640d463b6122ee164c5d936d571 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 13:46:00 +0200 Subject: [PATCH 101/175] [ot] hw/opentitan: ot_kmac: add ot_id trace information Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_kmac.c | 112 +++++++++++++++++++++----------------- hw/opentitan/trace-events | 22 ++++---- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index ce79f057d12cd..06c621252fb96 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -400,6 +400,7 @@ struct OtKMACState { QEMUTimer *bh_timer; /* timer to delay bh when triggered from vCPU */ QEMUBH *bh; + char *ot_id; OtEDNState *edn; uint8_t edn_ep; uint8_t num_app; @@ -413,12 +414,12 @@ ot_kmac_change_fsm_state_line(OtKMACState *s, OtKMACFsmState state, int line) } if (s->current_app) { - trace_ot_kmac_change_state_app(s->current_app->index, line, + trace_ot_kmac_change_state_app(s->ot_id, s->current_app->index, line, STATE_NAME(s->state), s->state, STATE_NAME(state), state); } else { - trace_ot_kmac_change_state_sw(line, STATE_NAME(s->state), s->state, - STATE_NAME(state), state); + trace_ot_kmac_change_state_sw(s->ot_id, line, STATE_NAME(s->state), + s->state, STATE_NAME(state), state); } s->state = state; @@ -472,7 +473,7 @@ static void ot_kmac_update_alert(OtKMACState *s) static void ot_kmac_report_error(OtKMACState *s, int code, uint32_t info) { - trace_ot_kmac_report_error(code, ERR_NAME(code), info); + trace_ot_kmac_report_error(s->ot_id, code, ERR_NAME(code), info); uint32_t error = 0; error = FIELD_DP32(error, ERR_CODE, CODE, code); @@ -661,7 +662,7 @@ static void ot_kmac_process(void *opaque) ot_kmac_change_fsm_state(s, KMAC_ST_IDLE); ot_kmac_reset_state(s); ot_kmac_cancel_bh(s); - trace_ot_kmac_app_finished(s->current_app->index); + trace_ot_kmac_app_finished(s->ot_id, s->current_app->index); s->current_app = NULL; /* now is a good time to check for pending app requests */ ot_kmac_start_pending_app(s); @@ -691,8 +692,9 @@ static inline bool ot_kmac_config_enabled(OtKMACState *s) static inline bool ot_kmac_check_reg_write(OtKMACState *s, hwaddr reg) { if (!ot_kmac_config_enabled(s)) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Write to %s ignored while busy\n", - __func__, REG_NAME(reg)); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to %s ignored while busy\n", __func__, + s->ot_id, REG_NAME(reg)); return false; } @@ -896,7 +898,7 @@ static void ot_kmac_process_sw_command(OtKMACState *s, int cmd) return; } - trace_ot_kmac_process_sw_command(cmd, CMD_NAME(cmd)); + trace_ot_kmac_process_sw_command(s->ot_id, cmd, CMD_NAME(cmd)); switch (s->state) { case KMAC_ST_IDLE: @@ -926,9 +928,9 @@ static void ot_kmac_process_sw_command(OtKMACState *s, int cmd) s->sw_cfg.mode == OT_KMAC_MODE_KMAC) { if (!ot_kmac_decode_sw_prefix(s)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Could not decode cSHAKE prefix, digest " - "result will be wrong!\n", - __func__); + "%s: %s: could not decode cSHAKE prefix, " + "digest result will be wrong!\n", + __func__, s->ot_id); memset(&s->sw_cfg.prefix, 0, sizeof(s->sw_cfg.prefix)); } } @@ -1120,19 +1122,21 @@ static uint64_t ot_kmac_regs_read(void *opaque, hwaddr addr, unsigned size) case R_KEY_SHARE1_15: case R_KEY_LEN: qemu_log_mask(LOG_GUEST_ERROR, - "%s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); val32 = 0; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0; break; } uint32_t pc = ibex_get_current_pc(); - trace_ot_kmac_io_read_out((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_kmac_io_read_out(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, + pc); return (uint64_t)val32; } @@ -1147,7 +1151,7 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, hwaddr reg = R32_OFF(addr); uint32_t pc = ibex_get_current_pc(); - trace_ot_kmac_io_write((uint32_t)addr, REG_NAME(reg), val32, pc); + trace_ot_kmac_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, pc); switch (reg) { case R_INTR_STATE: @@ -1190,14 +1194,16 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, if (val32 & R_CMD_ENTROPY_REQ_MASK) { /* TODO: implement entropy */ - qemu_log_mask(LOG_UNIMP, "%s: CMD.ENTROPY_REQ is not supported\n", - __func__); + qemu_log_mask(LOG_UNIMP, + "%s: %s: CMD.ENTROPY_REQ is not supported\n", + __func__, s->ot_id); } if (val32 & R_CMD_HASH_CNT_CLR_MASK) { /* TODO: implement entropy */ - qemu_log_mask(LOG_UNIMP, "%s: CMD.HASH_CNT_CLR is not supported\n", - __func__); + qemu_log_mask(LOG_UNIMP, + "%s: %s: CMD.HASH_CNT_CLR is not supported\n", + __func__, s->ot_id); } if (val32 & R_CMD_ERR_PROCESSED_MASK) { @@ -1235,8 +1241,8 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, break; case R_ENTROPY_SEED: /* TODO: implement entropy */ - qemu_log_mask(LOG_UNIMP, "%s: R_ENTROPY_SEED_* is not supported\n", - __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: R_ENTROPY_SEED_* is not supported\n", + __func__, s->ot_id); break; case R_KEY_LEN: if (!ot_kmac_check_reg_write(s, reg)) { @@ -1246,8 +1252,8 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs[reg] = val32; if (!ot_kmac_get_key_length(s)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid KEY_LEN=%d, using key length 0\n", - __func__, val32); + "%s: :%s invalid KEY_LEN=%d, using key length 0\n", + __func__, s->ot_id, val32); } break; case R_KEY_SHARE0_0: @@ -1303,12 +1309,13 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, case R_ENTROPY_REFRESH_HASH_CNT: case R_ERR_CODE: qemu_log_mask(LOG_GUEST_ERROR, - "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, REG_NAME(reg)); + "%s: %s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } } @@ -1325,8 +1332,8 @@ static uint64_t ot_kmac_state_read(void *opaque, hwaddr addr, unsigned size) */ if (!s->invalid_state_read) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: STATE read while in invalid FSM state\n", - __func__); + "%s: %s: STATE read while in invalid FSM state\n", + __func__, s->ot_id); s->invalid_state_read = true; } val32 = 0; @@ -1367,15 +1374,15 @@ static uint64_t ot_kmac_state_read(void *opaque, hwaddr addr, unsigned size) break; default: qemu_log_mask(LOG_GUEST_ERROR, - "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, - addr); + "%s: %s: bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0; break; } } uint32_t pc = ibex_get_current_pc(); - trace_ot_kmac_state_read_out((uint32_t)addr, val32, pc); + trace_ot_kmac_state_read_out(s->ot_id, (uint32_t)addr, val32, pc); return (uint64_t)val32; } @@ -1383,21 +1390,23 @@ static uint64_t ot_kmac_state_read(void *opaque, hwaddr addr, unsigned size) static void ot_kmac_state_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { - (void)opaque; + OtKMACState *s = OT_KMAC(opaque); (void)addr; (void)value; (void)size; /* on real hardware, writes to STATE are ignored */ - qemu_log_mask(LOG_GUEST_ERROR, "%s: STATE is read only\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: STATE is read only\n", __func__, + s->ot_id); } static uint64_t ot_kmac_msgfifo_read(void *opaque, hwaddr addr, unsigned size) { - (void)opaque; + OtKMACState *s = OT_KMAC(opaque); (void)addr; (void)size; /* on real hardware, writes to FIFO will block. Let's just return 0. */ - qemu_log_mask(LOG_GUEST_ERROR, "%s: MSG_FIFO is write only\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: MSG_FIFO is write only\n", __func__, + s->ot_id); return 0; } @@ -1407,7 +1416,8 @@ static void ot_kmac_msgfifo_write(void *opaque, hwaddr addr, uint64_t value, OtKMACState *s = OT_KMAC(opaque); uint32_t pc = ibex_get_current_pc(); - trace_ot_kmac_msgfifo_write((uint32_t)addr, (uint32_t)value, size, pc); + trace_ot_kmac_msgfifo_write(s->ot_id, (uint32_t)addr, (uint32_t)value, size, + pc); /* trigger error if an app is running of not in MSG_FEED state */ if (s->current_app || s->state != KMAC_ST_MSG_FEED) { @@ -1457,9 +1467,10 @@ static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, */ return; } - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Ignoring connection to already used app index %u\n", - __func__, app_idx); + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: ignoring connection to already used app index %u\n", + __func__, s->ot_id, app_idx); return; } @@ -1467,8 +1478,8 @@ static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, app->cfg = *cfg; if (!ot_kmac_check_mode_and_strength(&app->cfg)) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid mode/strength for app index %u\n", __func__, - app_idx); + "%s: %s: Invalid mode/strength for app index %u\n", + __func__, s->ot_id, app_idx); /* force dummy values, digest will be wrong */ app->cfg.mode = OT_KMAC_MODE_CSHAKE; app->cfg.strength = 128u; @@ -1477,9 +1488,9 @@ static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, if (memcmp(app->cfg.prefix.funcname, "KMAC", 4u) != 0 || app->cfg.prefix.funcname_len != 4u) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid config for app index %u: invalid prefix" - " for KMAC\n", - __func__, app_idx); + "%s: %s: invalid config for app index %u: " + "invalid prefix for KMAC\n", + __func__, s->ot_id, app_idx); } } app->fn = fn; @@ -1497,7 +1508,7 @@ static void ot_kmac_start_pending_app(OtKMACState *s) s->pending_apps &= ~(1u << app_idx); /* process start */ - trace_ot_kmac_app_start(app_idx); + trace_ot_kmac_app_start(s->ot_id, app_idx); s->current_cfg = &s->current_app->cfg; ot_kmac_process_start(s); ot_kmac_change_fsm_state(s, KMAC_ST_MSG_FEED); @@ -1516,7 +1527,8 @@ static void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, if (app->req_pending) { error_setg(&error_fatal, - "Dropping request to already busy app index %u", app_idx); + "%s: %s: dropping request to already busy app index %u", + __func__, s->ot_id, app_idx); } /* save request */ @@ -1535,6 +1547,7 @@ static void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, } static Property ot_kmac_properties[] = { + DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtKMACState, ot_id), DEFINE_PROP_LINK("edn", OtKMACState, edn, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtKMACState, edn_ep, UINT8_MAX), DEFINE_PROP_UINT8("num-app", OtKMACState, num_app, 0), @@ -1604,6 +1617,7 @@ static void ot_kmac_realize(DeviceState *dev, Error **errp) OtKMACState *s = OT_KMAC(dev); (void)errp; + g_assert(s->ot_id); /* make sure num-app property is set */ g_assert(s->num_app > 0); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index f692f5a48a5a3..5f12652feacc2 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -273,17 +273,17 @@ ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halte # ot_kmac.c -ot_kmac_app_finished(unsigned app_idx) "#%u" -ot_kmac_app_start(unsigned app_idx) "#%u" -ot_kmac_change_state_app(unsigned app_idx, int line, const char *old, int nold, const char *new, int nnew) "#%u @ %d [%s:%d] -> [%s:%d]" -ot_kmac_change_state_sw(int line, const char *old, int nold, const char *new, int nnew) "@ %d [%s:%d] -> [%s:%d]" -ot_kmac_debug(const char *msg) "%s" -ot_kmac_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_kmac_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x" -ot_kmac_msgfifo_write(uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "addr=0x%03x, val=0x%x (%u), pc=0x%x" -ot_kmac_process_sw_command(int cmd, const char *cmd_str) "cmd=0x%02x (%s)" -ot_kmac_report_error(int code, const char *code_str, uint32_t info) "code=0x%02x (%s) info=0x%06x" -ot_kmac_state_read_out(uint32_t addr, uint32_t val, uint32_t pc) "addr=0x%03x, val=0x%x, pc=0x%x" +ot_kmac_app_finished(const char *id, unsigned app_idx) "%s: #%u" +ot_kmac_app_start(const char *id, unsigned app_idx) "%s: #%u" +ot_kmac_change_state_app(const char *id, unsigned app_idx, int line, const char *old, int nold, const char *new, int nnew) "%s: #%u @ %d [%s:%d] -> [%s:%d]" +ot_kmac_change_state_sw(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_kmac_debug(const char *id, const char *msg) "%s: %s" +ot_kmac_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_kmac_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_kmac_msgfifo_write(const char *id, uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "%s: addr=0x%03x, val=0x%x (const char *id, %u), pc=0x%x" +ot_kmac_process_sw_command(const char *id, int cmd, const char *cmd_str) "%s: cmd=0x%02x (const char *id, %s)" +ot_kmac_report_error(const char *id, int code, const char *code_str, uint32_t info) "%s: code=0x%02x (const char *id, %s) info=0x%06x" +ot_kmac_state_read_out(const char *id, uint32_t addr, uint32_t val, uint32_t pc) "%s: addr=0x%03x, val=0x%x, pc=0x%x" # ot_lc_ctrl.c From 038940518375507235cb677f0e84858fc2039230 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 24 Jun 2025 13:50:26 +0200 Subject: [PATCH 102/175] [ot] hw/opentitan: ot_kmac: add initial dynamic clock management Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_kmac.c | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index 06c621252fb96..a22324df2e0cd 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -39,11 +39,13 @@ #include "qemu/timer.h" #include "qapi/error.h" #include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" #include "hw/opentitan/ot_kmac.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" +#include "hw/riscv/ibex_clock_src.h" #include "hw/riscv/ibex_common.h" #include "hw/riscv/ibex_irq.h" #include "tomcrypt.h" @@ -228,6 +230,9 @@ static const char *ERR_NAMES[] = { /* length of the whole device MMIO region */ #define OT_KMAC_WHOLE_SIZE (OT_KMAC_MSG_FIFO_BASE + OT_KMAC_MSG_FIFO_SIZE) +#define OT_KMAC_CLOCK_ACTIVE "clock-active" +#define OT_KMAC_CLOCK_INPUT "clock-in" + #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) #define R_LAST_REG (R_ERR_CODE) @@ -379,6 +384,7 @@ struct OtKMACState { MemoryRegion msgfifo_mmio; IbexIRQ irqs[3u]; IbexIRQ alerts[KMAC_PARAM_NUM_ALERTS]; + IbexIRQ clock_active; uint32_t *regs; OtShadowReg cfg; @@ -391,6 +397,8 @@ struct OtKMACState { OtKMACAppCfg sw_cfg; OtKMACAppCfg *current_cfg; + unsigned pclk; /* Current input clock */ + const char *clock_src_name; /* IRQ name once connected */ OtKMACApp *apps; OtKMACApp *current_app; @@ -401,6 +409,8 @@ struct OtKMACState { QEMUBH *bh; char *ot_id; + char *clock_name; + DeviceState *clock_src; OtEDNState *edn; uint8_t edn_ep; uint8_t num_app; @@ -1020,6 +1030,17 @@ static void ot_kmac_process_sw_command(OtKMACState *s, int cmd) } } +static void ot_kmac_clock_input(void *opaque, int irq, int level) +{ + OtKMACState *s = opaque; + + g_assert(irq == 0); + + s->pclk = (unsigned)level; + + /* TODO: disable KMAC execution when PCLK is 0 */ +} + static uint64_t ot_kmac_regs_read(void *opaque, hwaddr addr, unsigned size) { OtKMACState *s = OT_KMAC(opaque); @@ -1548,6 +1569,9 @@ static void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, static Property ot_kmac_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtKMACState, ot_id), + DEFINE_PROP_STRING("clock-name", OtKMACState, clock_name), + DEFINE_PROP_LINK("clock-src", OtKMACState, clock_src, TYPE_DEVICE, + DeviceState *), DEFINE_PROP_LINK("edn", OtKMACState, edn, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtKMACState, edn_ep, UINT8_MAX), DEFINE_PROP_UINT8("num-app", OtKMACState, num_app, 0), @@ -1610,6 +1634,28 @@ static void ot_kmac_reset_enter(Object *obj, ResetType type) ot_kmac_update_alert(s); fifo8_reset(&s->input_fifo); + + if (!s->clock_src_name) { + IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); + IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); + + s->clock_src_name = + ic->get_clock_source(ii, s->clock_name, DEVICE(s), &error_fatal); + qemu_irq in_irq = + qdev_get_gpio_in_named(DEVICE(s), OT_KMAC_CLOCK_INPUT, 0); + qdev_connect_gpio_out_named(s->clock_src, s->clock_src_name, 0, in_irq); + + if (object_dynamic_cast(OBJECT(s->clock_src), TYPE_OT_CLKMGR)) { + char *hint_name = + g_strdup_printf(OT_CLOCK_HINT_PREFIX "%s", s->clock_name); + qemu_irq hint_irq = + qdev_get_gpio_in_named(s->clock_src, hint_name, 0); + g_assert(hint_irq); + qdev_connect_gpio_out_named(DEVICE(s), OT_KMAC_CLOCK_ACTIVE, 0, + hint_irq); + g_free(hint_name); + } + } } static void ot_kmac_realize(DeviceState *dev, Error **errp) @@ -1618,6 +1664,9 @@ static void ot_kmac_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); + g_assert(s->clock_name); + g_assert(s->clock_src); + /* make sure num-app property is set */ g_assert(s->num_app > 0); @@ -1625,6 +1674,9 @@ static void ot_kmac_realize(DeviceState *dev, Error **errp) g_assert(s->num_app <= 32); s->apps = g_new0(OtKMACApp, s->num_app); + + qdev_init_gpio_in_named(DEVICE(s), &ot_kmac_clock_input, + OT_KMAC_CLOCK_INPUT, 1); } static void ot_kmac_init(Object *obj) @@ -1640,6 +1692,8 @@ static void ot_kmac_init(Object *obj) ibex_qdev_init_irq(obj, &s->alerts[ix], OT_DEVICE_ALERT); } + ibex_qdev_init_irq(obj, &s->clock_active, OT_KMAC_CLOCK_ACTIVE); + memory_region_init(&s->mmio, OBJECT(s), TYPE_OT_KMAC, OT_KMAC_WHOLE_SIZE); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); From 641f4abe2de346cd8f70ac5043d42344bb59a71c Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 18:13:22 +0200 Subject: [PATCH 103/175] [ot] hw/riscv: ot_darjeeling: remove all static clock definitions All clocks are now generated by the AST_DJ device, forwarded to the CLKMGR devices and clocks are dispatched to the peripheral such as: - AES - ALERT - AON_TIMER - HMAC - KMAC - I2C - OTBN - SPI_HOST - TIMER - UART Signed-off-by: Emmanuel Blot --- hw/riscv/Kconfig | 1 + hw/riscv/ot_darjeeling.c | 93 ++++++++++++++++++++++++++++++++-------- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 2c90c200111ce..87847bb83d958 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -21,6 +21,7 @@ config IBEX_GPIO config OT_DARJEELING bool select IBEX + select IBEX_CLOCK_SRC select IBEX_COMMON select OT_ADDRESS_SPACE select OT_AES diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 643c2a695854d..96d33155ac6d3 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -296,9 +296,6 @@ enum OtDjPinmuxMioOut { #define OT_DJ_DEBUG_LC_CTRL_SIZE 0x400u #define OT_DJ_DBG_XBAR_SIZE 0x4000u -#define OT_DJ_PERIPHERAL_CLK_HZ 250000000u /* 250 MHz */ -#define OT_DJ_AON_CLK_HZ 62500000u /* 62.5 MHz */ - static const uint8_t ot_dj_pmp_cfgs[] = { /* clang-format off */ IBEX_PMP_CFG(0, IBEX_PMP_MODE_OFF, 0, 0, 0), @@ -438,8 +435,13 @@ static const uint32_t ot_dj_pmp_addrs[] = { OT_DJ_SOC_SIGNAL(OT_PINMUX_##_type_, _type_##_##_name_, _tgt_, \ OT_PINMUX_PAD, (_num_)) -#define OT_DJ_SOC_CLKMGR_HINT(_num_) \ - OT_DJ_SOC_SIGNAL(OT_CLOCK_ACTIVE, 0, CLKMGR, OT_CLKMGR_HINT, _num_) +#define OT_DJ_SOC_CLOCK_CONN(_src_, _src_type_, _src_name_, _in_name_, \ + _in_num_) \ + IBEX_CLOCK_CONN((OT_DJ_SOC_DEV_##_src_), _src_type_, _src_name_, \ + _in_name_, _in_num_) + +#define OT_DJ_SOC_CLOCK(_src_, _src_type_, _src_name_) \ + OT_DJ_SOC_CLOCK_CONN(_src_, _src_type_, _src_name_, "clock", 0) #define OT_DJ_XPORT_MEMORY(_addr_) \ IBEX_MEMMAP_MAKE_REG((_addr_), OT_DJ_CTN_MEMORY_REGION) @@ -577,14 +579,15 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { { .base = 0x21100000u } ), .gpio = IBEXGPIOCONNDEFS( - OT_DJ_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_AES), OT_DJ_SOC_GPIO_ALERT(0, 55), OT_DJ_SOC_GPIO_ALERT(1, 56) ), .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), OT_DJ_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.aes"), IBEX_DEV_UINT_PROP("edn-ep", 5u) ), }, @@ -597,8 +600,13 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 115), OT_DJ_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 116), OT_DJ_SOC_GPIO_SYSBUS_IRQ(2, PLIC, 117), - OT_DJ_SOC_GPIO_ALERT(0, 57), - OT_DJ_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_HMAC) + OT_DJ_SOC_GPIO_ALERT(0, 57) + ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.hmac") ), }, [OT_DJ_SOC_DEV_KMAC] = { @@ -614,9 +622,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(1, 59) ), .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), OT_DJ_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.kmac"), IBEX_DEV_UINT_PROP("edn-ep", 3u), IBEX_DEV_UINT_PROP("num-app", 4u) ), @@ -629,14 +639,15 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_DJ_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 121), OT_DJ_SOC_GPIO_ALERT(0, 60), - OT_DJ_SOC_GPIO_ALERT(1, 61), - OT_DJ_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_OTBN) + OT_DJ_SOC_GPIO_ALERT(1, 61) ), .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), OT_DJ_SOC_DEVLINK("edn-u", EDN0), OT_DJ_SOC_DEVLINK("edn-r", EDN1) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.otbn"), IBEX_DEV_UINT_PROP("edn-u-ep", 6u), IBEX_DEV_UINT_PROP("edn-r-ep", 0u) ), @@ -1039,8 +1050,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(7, PLIC, 8), OT_DJ_SOC_GPIO_ALERT(0, 0) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_DJ_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_DJ_SOC_DEV_I2C0] = { @@ -1066,8 +1080,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(14, PLIC, 67), OT_DJ_SOC_GPIO_ALERT(0, 3) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_DJ_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_DJ_SOC_DEV_TIMER] = { @@ -1080,8 +1097,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 68), OT_DJ_SOC_GPIO_ALERT(0, 4) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_DJ_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "timers.io_div4") ), }, [OT_DJ_SOC_DEV_OTP_CTRL] = { @@ -1186,14 +1206,16 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ESCALATE(3, PWRMGR, 0) ), .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), OT_DJ_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_DJ_PERIPHERAL_CLK_HZ), IBEX_DEV_UINT_PROP("n_alerts", 99u), IBEX_DEV_UINT_PROP("n_classes", 4u), IBEX_DEV_UINT_PROP("n_lpg", 18u), - IBEX_DEV_UINT_PROP("edn-ep", 4u) + IBEX_DEV_UINT_PROP("edn-ep", 4u), + IBEX_DEV_STRING_PROP("clock-name", "secure.io_div4"), + IBEX_DEV_STRING_PROP("clock-name-edn", "secure.main") ), }, [OT_DJ_SOC_DEV_SPI_HOST0] = { @@ -1206,10 +1228,13 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 77), OT_DJ_SOC_GPIO_ALERT(0, 13) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "spi0"), IBEX_DEV_UINT_PROP("bus-num", 0), - IBEX_DEV_UINT_PROP("pclk", OT_DJ_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_DJ_SOC_DEV_SPI_DEVICE] = { @@ -1251,7 +1276,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_SIGNAL(OT_PWRMGR_RST_REQ, 0, RSTMGR, OT_RSTMGR_RST_REQ, 0) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock_ctrl", AST) + ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clocks", "main,io,usb"), IBEX_DEV_UINT_PROP("num-rom", 2u), IBEX_DEV_UINT_PROP("version", OT_PWRMGR_VERSION_DJ_PRE) ), @@ -1279,7 +1308,26 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_DJ_SOC_GPIO_ALERT(0, 17), OT_DJ_SOC_GPIO_ALERT(1, 18) - ) + ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", AST) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("topclocks", "main:16,io:16,usb:16,aon:1"), + IBEX_DEV_STRING_PROP("refclock", "aon"), + IBEX_DEV_STRING_PROP("subclocks", + "io_div2:io:2,io_div4:io:4," + "aes:main:1,hmac:main:1,kmac:main:1,otbn:main:1"), + IBEX_DEV_STRING_PROP("groups", + "powerup:io_div4+aon+main+io+usb+io_div2," + "trans:aes+hmac+kmac+otbn," + "infra:io_div4+main+aon+usb+io," + "secure:io_div4+main+aon," + "peri:io_div4+io_div2+io+aon+usb," + "timers:io_div4+aon"), + IBEX_DEV_STRING_PROP("swcg", "peri"), + IBEX_DEV_STRING_PROP("hint", "trans") + ), }, [OT_DJ_SOC_DEV_PINMUX] = { .type = TYPE_OT_PINMUX_DJ, @@ -1336,8 +1384,12 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_SIGNAL(OT_AON_TIMER_BITE, 0, PWRMGR, OT_PWRMGR_RST, OT_DJ_RESET_AON_TIMER) ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_DJ_AON_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "timers.io_div4"), + IBEX_DEV_STRING_PROP("clock-name-aon", "timers.aon") ), }, [OT_DJ_SOC_DEV_AST] = { @@ -1345,6 +1397,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .memmap = MEMMAPENTRIES( { .base = 0x30480000u } ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("topclocks", + "main:1000000000,io:1000000000,usb:1000000000,aon:62500000"), + IBEX_DEV_STRING_PROP("aonclocks", "aon") + ), }, [OT_DJ_SOC_DEV_SRAM_RET] = { .type = TYPE_OT_SRAM_CTRL, From 95aa0a8fc1b781c18f90d92f0482371d5629f005 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 18 Jun 2025 18:08:30 +0200 Subject: [PATCH 104/175] [ot] hw/riscv: ot_earlgrey: remove all static clock definitions All clocks are now generated by the AST_DJ device, forwarded to the CLKMGR devices and clocks are dispatched to the peripheral such as: - ALERT - AON_TIMER - SPI_HOSTs - TIMER - UARTs Signed-off-by: Emmanuel Blot --- hw/riscv/Kconfig | 1 + hw/riscv/ot_earlgrey.c | 214 +++++++++++++++++++++-------------------- 2 files changed, 110 insertions(+), 105 deletions(-) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 87847bb83d958..e7f90ab7ffa26 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -67,6 +67,7 @@ config OT_DARJEELING config OT_EARLGREY bool select IBEX + select IBEX_CLOCK_SRC select IBEX_COMMON select OT_AES select OT_ALERT diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index d94bcbe2c8dd2..2970d431d760b 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -83,6 +83,8 @@ /* Forward Declarations */ /* ------------------------------------------------------------------------ */ +static void ot_eg_soc_ast_configure(DeviceState *dev, const IbexDeviceDef *def, + DeviceState *parent); static void ot_eg_soc_dm_configure(DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent); static void ot_eg_soc_flash_ctrl_configure( @@ -182,20 +184,6 @@ enum OtEGBoardDevice { OT_EG_BOARD_DEV_COUNT, }; -/* EarlGrey/CW310 Core clock is 24 MHz */ -#define OT_EG_CORE_CLK_HZ 24000000u -/* EarlGrey/CW310 Peripheral clock is 6 MHz */ -#define OT_EG_PERIPHERAL_CLK_HZ ((OT_EG_CORE_CLK_HZ) / 4u) -/* EarlGrey/CW310 AON clock is 250 kHz */ -#define OT_EG_AON_CLK_HZ 250000u - -/* Verilator Core clock is 500 kHz */ -#define OT_EG_VERILATOR_CORE_CLK_HZ 500000u -/* Verilator Peripheral clock is 125 kHz */ -#define OT_EG_VERILATOR_PERIPHERAL_CLK_HZ ((OT_EG_VERILATOR_CORE_CLK_HZ) / 4u) -/* Verilator AON clock is 125 kHz */ -#define OT_EG_VERILATOR_AON_CLK_HZ OT_EG_VERILATOR_PERIPHERAL_CLK_HZ - #define OT_EG_IBEX_WRAPPER_NUM_REGIONS 2u static const uint8_t ot_eg_pmp_cfgs[] = { @@ -282,10 +270,6 @@ static const uint32_t ot_eg_pmp_addrs[] = { #define OT_EG_SOC_RSP(_rsp_, _tgt_) \ OT_EG_SOC_SIGNAL(_rsp_##_RSP, 0, _tgt_, _rsp_##_RSP, 0) - -#define OT_EG_SOC_CLKMGR_HINT(_num_) \ - OT_EG_SOC_SIGNAL(OT_CLOCK_ACTIVE, 0, CLKMGR, OT_CLKMGR_HINT, _num_) - #define OT_EG_SOC_DM_CONNECTION(_dst_dev_, _num_) \ { \ .out = { \ @@ -389,9 +373,12 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(8, PLIC, 9), OT_EG_SOC_GPIO_ALERT(0, 0) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "u0"), - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_UART1] = { @@ -413,9 +400,12 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(8, PLIC, 18), OT_EG_SOC_GPIO_ALERT(0, 1) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "u1"), - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_UART2] = { @@ -437,9 +427,12 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(8, PLIC, 27), OT_EG_SOC_GPIO_ALERT(0, 2) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "u2"), - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_UART3] = { @@ -461,9 +454,12 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(8, PLIC, 36), OT_EG_SOC_GPIO_ALERT(0, 3) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "u3"), - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_GPIO] = { @@ -603,8 +599,11 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 124), OT_EG_SOC_GPIO_ALERT(0, 10) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "timers.io_div4") ), }, [OT_EG_SOC_DEV_OTP_CTRL] = { @@ -622,13 +621,13 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(3, 14), OT_EG_SOC_GPIO_ALERT(4, 15) ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_UINT_PROP("edn-ep", 1u) + ), .link = IBEXDEVICELINKDEFS( OT_EG_SOC_DEVLINK("edn", EDN0), OT_EG_SOC_DEVLINK("backend", OTP_BACKEND) ), - .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("edn-ep", 1u) - ), }, [OT_EG_SOC_DEV_OTP_BACKEND] = { .type = TYPE_OT_OTP_OT_BE, @@ -692,14 +691,16 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ESCALATE(3, PWRMGR, 0) ), .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR), OT_EG_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_EG_PERIPHERAL_CLK_HZ), IBEX_DEV_UINT_PROP("n_alerts", 65u), IBEX_DEV_UINT_PROP("n_classes", 4u), IBEX_DEV_UINT_PROP("n_lpg", 22u), - IBEX_DEV_UINT_PROP("edn-ep", 4u) + IBEX_DEV_UINT_PROP("edn-ep", 4u), + IBEX_DEV_STRING_PROP("clock-name", "secure.io_div4"), + IBEX_DEV_STRING_PROP("clock-name-edn", "secure.main") ), }, [OT_EG_SOC_DEV_SPI_HOST0] = { @@ -712,10 +713,13 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 132), OT_EG_SOC_GPIO_ALERT(0, 19) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "spi0"), IBEX_DEV_UINT_PROP("bus-num", 0), - IBEX_DEV_UINT_PROP("pclk", OT_EG_CORE_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_SPI_HOST1] = { @@ -728,10 +732,13 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 134), OT_EG_SOC_GPIO_ALERT(0, 20) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "spi1"), IBEX_DEV_UINT_PROP("bus-num", 1), - IBEX_DEV_UINT_PROP("pclk", OT_EG_CORE_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "peri.io_div4") ), }, [OT_EG_SOC_DEV_USBDEV] = { @@ -769,7 +776,11 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_RSTMGR_RST_REQ, 0), OT_EG_SOC_GPIO_ALERT(0, 22) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock_ctrl", AST) + ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clocks", "main,io,usb"), IBEX_DEV_UINT_PROP("num-rom", 1u), IBEX_DEV_UINT_PROP("version", OT_PWRMGR_VERSION_EG_252) ), @@ -797,7 +808,26 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_EG_SOC_GPIO_ALERT(0, 25), OT_EG_SOC_GPIO_ALERT(1, 26) - ) + ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", AST) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("topclocks", "main:500,io:480,usb:240,aon:1"), + IBEX_DEV_STRING_PROP("refclock", "aon"), + IBEX_DEV_STRING_PROP("subclocks", + "io_div2:io:2,io_div4:io:4," + "aes:main:1,hmac:main:1,kmac:main:1,otbn:main:1"), + IBEX_DEV_STRING_PROP("groups", + "powerup:io_div4+aon+main+io+usb+io_div2," + "trans:aes+hmac+kmac+otbn," + "infra:io_div4+main+usb+io," + "secure:io_div4+main+aon," + "peri:io_div4+io_div2+io+aon+usb," + "timers:io_div4+aon"), + IBEX_DEV_STRING_PROP("swcg", "peri"), + IBEX_DEV_STRING_PROP("hint", "trans") + ), }, [OT_EG_SOC_DEV_SYSRST_CTRL] = { .type = TYPE_OT_UNIMP, @@ -872,15 +902,23 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_PWRMGR_RST, OT_EG_RESET_AON_TIMER), OT_EG_SOC_GPIO_ALERT(0, 31) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("pclk", OT_EG_AON_CLK_HZ) + IBEX_DEV_STRING_PROP("clock-name", "timers.io_div4"), + IBEX_DEV_STRING_PROP("clock-name-aon", "timers.aon") ), }, [OT_EG_SOC_DEV_AST] = { .type = TYPE_OT_AST_EG, + .cfg = &ot_eg_soc_ast_configure, .memmap = MEMMAPENTRIES( { .base = 0x40480000u } ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("aonclocks", "aon") + ), }, [OT_EG_SOC_DEV_SENSOR_CTRL] = { .type = TYPE_OT_SENSOR_EG, @@ -890,7 +928,7 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { .gpio = IBEXGPIOCONNDEFS( OT_EG_SOC_GPIO_ALERT(0, 32), OT_EG_SOC_GPIO_ALERT(1, 33) - ) + ), }, [OT_EG_SOC_DEV_SRAM_RET_CTRL] = { .type = TYPE_OT_SRAM_CTRL, @@ -942,14 +980,15 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { { .base = 0x41100000u } ), .gpio = IBEXGPIOCONNDEFS( - OT_EG_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_AES), OT_EG_SOC_GPIO_ALERT(0, 42), OT_EG_SOC_GPIO_ALERT(1, 43) ), .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR), OT_EG_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.aes"), IBEX_DEV_UINT_PROP("edn-ep", 5u) ), }, @@ -959,12 +998,17 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { { .base = 0x41110000u } ), .gpio = IBEXGPIOCONNDEFS( - OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 166), - OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 167), - OT_EG_SOC_GPIO_SYSBUS_IRQ(2, PLIC, 168), - OT_EG_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_HMAC), + OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 165), + OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 166), + OT_EG_SOC_GPIO_SYSBUS_IRQ(2, PLIC, 167), OT_EG_SOC_GPIO_ALERT(0, 44) ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR) + ), + .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.hmac") + ), }, [OT_EG_SOC_DEV_KMAC] = { .type = TYPE_OT_KMAC, @@ -979,9 +1023,11 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(1, 46) ), .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR), OT_EG_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.kmac"), IBEX_DEV_UINT_PROP("edn-ep", 3u), IBEX_DEV_UINT_PROP("num-app", 3u) ), @@ -992,16 +1038,17 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { { .base = 0x41130000u } ), .gpio = IBEXGPIOCONNDEFS( - OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 172), - OT_EG_SOC_CLKMGR_HINT(OT_CLKMGR_HINT_OTBN), + OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 171), OT_EG_SOC_GPIO_ALERT(0, 47), OT_EG_SOC_GPIO_ALERT(1, 48) ), .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("clock-src", CLKMGR), OT_EG_SOC_DEVLINK("edn-u", EDN0), OT_EG_SOC_DEVLINK("edn-r", EDN1) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("clock-name", "trans.otbn"), IBEX_DEV_UINT_PROP("edn-u-ep", 6u), IBEX_DEV_UINT_PROP("edn-r-ep", 0u) ), @@ -1261,6 +1308,25 @@ struct OtEGMachineClass { /* Device Configuration */ /* ------------------------------------------------------------------------ */ +static void ot_eg_soc_ast_configure(DeviceState *dev, const IbexDeviceDef *def, + DeviceState *parent) +{ + (void)def; + (void)parent; + + bool verilator_mode = + object_property_get_bool(qdev_get_machine(), "verilator", NULL); + const char *clock_cfg; + if (!verilator_mode) { + /* EarlGrey/CW310 */ + clock_cfg = "main:10000000,io:10000000,usb:10000000,aon:250000"; + } else { + clock_cfg = "main:500000,io:500000,usb:500000,aon:250000"; + } + + qdev_prop_set_string(dev, "topclocks", clock_cfg); +} + static void ot_eg_soc_dm_configure(DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) { @@ -1423,58 +1489,6 @@ static void ot_eg_soc_reset_exit(Object *obj, ResetType type) true, &error_fatal); } -static void -ot_earlgrey_update_device_clocks(DeviceState **devices, size_t count) -{ - for (unsigned ix = 0; ix < (unsigned)count; ix++) { - DeviceState *dev = devices[ix]; - if (!dev) { - continue; - } - Error *errp = NULL; - uint64_t pclk = object_property_get_uint(OBJECT(dev), "pclk", &errp); - if (errp) { - error_free(errp); - continue; - } - switch (pclk) { - case 0: - /* PCLK property exists, but is not used, skip it */ - continue; - case OT_EG_CORE_CLK_HZ: - pclk = OT_EG_VERILATOR_CORE_CLK_HZ; - break; - case OT_EG_PERIPHERAL_CLK_HZ: - pclk = OT_EG_VERILATOR_PERIPHERAL_CLK_HZ; - break; - case OT_EG_AON_CLK_HZ: - pclk = OT_EG_VERILATOR_AON_CLK_HZ; - break; - default: - warn_report("%s: OT device %s has invalid pclk value: %" PRIu64, - __func__, object_get_typename(OBJECT(dev)), pclk); - continue; - } - - if (!object_property_set_uint(OBJECT(dev), "pclk", pclk, &errp)) { - error_propagate(&error_fatal, errp); - g_assert_not_reached(); - } - } -} - -static void -ot_earlgrey_configure_verilator_devices(DeviceState **devices, BusState *bus, - const IbexDeviceDef *defs, size_t count) -{ - ibex_link_devices(devices, defs, count); - ibex_define_device_props(devices, defs, count); - ot_common_configure_device_opts(devices, count); - ot_earlgrey_update_device_clocks(devices, count); - ibex_realize_devices(devices, bus, defs, count); - ibex_connect_devices(devices, defs, count); -} - static void ot_eg_soc_realize(DeviceState *dev, Error **errp) { OtEGSoCState *s = RISCV_OT_EG_SOC(dev); @@ -1482,19 +1496,9 @@ static void ot_eg_soc_realize(DeviceState *dev, Error **errp) /* Link, define properties and realize devices, then connect GPIOs */ BusState *bus = sysbus_get_default(); - bool verilator_mode; - - verilator_mode = - object_property_get_bool(qdev_get_machine(), "verilator", NULL); - if (!verilator_mode) { - ot_common_configure_devices_with_id(s->devices, bus, "soc", false, - ot_eg_soc_devices, - ARRAY_SIZE(ot_eg_soc_devices)); - } else { - ot_earlgrey_configure_verilator_devices(s->devices, bus, - ot_eg_soc_devices, - ARRAY_SIZE(ot_eg_soc_devices)); - } + ot_common_configure_devices_with_id(s->devices, bus, "soc", false, + ot_eg_soc_devices, + ARRAY_SIZE(ot_eg_soc_devices)); MemoryRegion *mrs[] = { get_system_memory(), NULL, NULL, NULL }; ibex_map_devices(s->devices, mrs, ot_eg_soc_devices, From 206e17309d6e169f9c16032f67f6813b2c4050e7 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 27 Jun 2025 18:52:31 +0200 Subject: [PATCH 105/175] [ot] docs/opentitan: darjeeling, earlgrey: update emulation status Signed-off-by: Emmanuel Blot --- docs/opentitan/darjeeling.md | 14 +++++++++----- docs/opentitan/earlgrey.md | 24 ++++++++++++++---------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/opentitan/darjeeling.md b/docs/opentitan/darjeeling.md index cc89a161fea29..6126a8e7ad29c 100644 --- a/docs/opentitan/darjeeling.md +++ b/docs/opentitan/darjeeling.md @@ -44,6 +44,11 @@ Please check out `hw/opentitan/ot_ref.log` Devices in this group implement subset(s) of the real HW. +* Clock Manager + * Runtime-configurable device, through properties + * Manage clock dividers, groups, hints, software configurable clocks + * Propagate clock signals from source (AST, ...) to devices + * Hint management and measurement are not implemented * DMA * Only memory-to-memory transfers (inc. hashing) are supported, Handshake modes are not supported * Flash controller @@ -54,6 +59,9 @@ Devices in this group implement subset(s) of the real HW. * A CharDev backend can be used to get GPIO outputs and update GPIO inputs, * KMAC * Side loading is not supported +* Ibex wrapper + * random source (connected to CSR), FPGA version, virtual remapper, fetch enable can be controlled + from Power Manager * Lifecycle controller * [LC controller](lc_ctrl_dmi.md) can be accessed through JTAG using a DM-TL bridge * Escalation is not supported @@ -73,11 +81,7 @@ features are implemented. * AST * entropy source only (from host source) -* Clock Manager - * Clock hints only -* Ibex wrapper - * random source (connected to CSR), FPGA version, virtual remapper, fetch enable can be controlled - from Power Manager + * configurable clock sources * Reset Manager * HW and SW reset requests are supported * Pinmux diff --git a/docs/opentitan/earlgrey.md b/docs/opentitan/earlgrey.md index af36048c61449..946b0c52099db 100644 --- a/docs/opentitan/earlgrey.md +++ b/docs/opentitan/earlgrey.md @@ -15,6 +15,8 @@ EarlGrey 2.5.2-RC0 * AES * missing side-loading +* Alert controller + * ping mechanism is not supported * AON Timer * CSRNG * EDN @@ -32,6 +34,11 @@ EarlGrey 2.5.2-RC0 Devices in this group implement subset(s) of the real HW. +* Clock Manager + * Runtime-configurable device, through properties + * Manage clock dividers, groups, hints, software configurable clocks + * Propagate clock signals from source (AST, ...) to devices + * Hint management and measurement are not implemented * Flash controller * read-only features only * OTP controller @@ -41,6 +48,9 @@ Devices in this group implement subset(s) of the real HW. * AES CTR not supported (uses xoroshiro128++ reseeded from entropy src) * [GPIO](gpio.md) * A CharDev backend can be used to get GPIO outputs and update GPIO inputs, +* Ibex wrapper + * random source (connected to CSR), FPGA version, virtual remapper, fetch enable can be controlled + from Power Manager * KMAC * Side loading is not supported * [ROM controller](rom_ctrl.md) @@ -54,13 +64,11 @@ In this group, device CSRs are supported (w/ partial or full access control & ma features are implemented. * AST - * entropy source only (from host source) -* Clock Manager - * Clock hints only -* Ibex wrapper - * random source (connected to CSR), FPGA version, virtual remapper + * configurable clock sources +* GPIO + * Connections with pinmux not implemented (need to be ported from [Darjeeling](darjeeling.md) version) * Lifecycle controller - * only forwards LC state from OTP + * only forwards LC state from OTP (need to be ported from [Darjeeling](darjeeling.md) version) * Power Manager * Fast FSM is partially supported, Slow FSM is bypassed * Interactions with other devices (such as the Reset Manager) are limited @@ -70,12 +78,8 @@ features are implemented. Devices in this group are mostly implemented with a RAM backend or real CSRs but do not implement any useful feature (only allow guest test code to execute as expected). -* Alert controller * Key manager -* KMAC (in development) -* GPIO * Pinmux -* Power Manager * Sensor ## Running the virtual machine From 812f404ae00ae95cd6d46a676544d0dfd4a651f4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 1 Jul 2025 18:13:35 +0200 Subject: [PATCH 106/175] [ot] hw/opentitan: ot_clkmgr: fix clang-tidy warning with toupper() Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_clkmgr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_clkmgr.c b/hw/opentitan/ot_clkmgr.c index ce0a5a0214a83..55e203515465b 100644 --- a/hw/opentitan/ot_clkmgr.c +++ b/hw/opentitan/ot_clkmgr.c @@ -1018,7 +1018,7 @@ static uint64_t ot_clkmgr_read(void *opaque, hwaddr addr, unsigned size) char reg_name[40u]; unsigned ix = 0; for (; clk->name[ix] && ix < 16u; ix++) { - reg_name[ix] = toupper(clk->name[ix]); + reg_name[ix] = (char)toupper(clk->name[ix]); } strbcpy(reg_name, ®_name[ix], "_MEAS_CTRL_"); strbcpy(reg_name, ®_name[ix] + ARRAY_SIZE("_MEAS_CTRL_") - 1u, @@ -1154,7 +1154,7 @@ static void ot_clkmgr_write(void *opaque, hwaddr addr, uint64_t val64, char reg_name[40u]; unsigned ix = 0; for (; clk->name[ix] && ix < 20u; ix++) { - reg_name[ix] = toupper(clk->name[ix]); + reg_name[ix] = (char)toupper(clk->name[ix]); } strbcpy(reg_name, ®_name[ix], "_MEAS_CTRL_"); strbcpy(reg_name, ®_name[ix] + ARRAY_SIZE("_MEAS_CTRL_") - 1u, From 4cfa72dc0f704feae3078c0a259607932318dcfb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 1 Jul 2025 18:27:37 +0200 Subject: [PATCH 107/175] [ot] hw/opentitan: ot_rom_ctrl: disable clang-tidy warning (Linux-only) Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_rom_ctrl.c | 2 ++ hw/opentitan/ot_rom_ctrl_img.c | 1 + 2 files changed, 3 insertions(+) diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index e8b57a8c38681..0f4cb4b03a15c 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -564,6 +564,7 @@ static bool ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) return false; } + /* NOLINTNEXTLINE(misc-redundant-expression) */ int fd = open(ri->filename, O_RDONLY | O_BINARY | O_CLOEXEC); if (fd == -1) { error_setg(&error_fatal, "%s: %s: could not open ROM '%s': %s", @@ -601,6 +602,7 @@ static char *ot_rom_ctrl_read_text_file(OtRomCtrlState *s, const OtRomImg *ri) return NULL; } + /* NOLINTNEXTLINE(misc-redundant-expression) */ int fd = open(ri->filename, O_RDONLY | O_BINARY | O_CLOEXEC); if (fd == -1) { error_setg(&error_fatal, "%s: %s: could not open ROM '%s': %s\n", diff --git a/hw/opentitan/ot_rom_ctrl_img.c b/hw/opentitan/ot_rom_ctrl_img.c index ab175859dae9e..f8d43b20e0607 100644 --- a/hw/opentitan/ot_rom_ctrl_img.c +++ b/hw/opentitan/ot_rom_ctrl_img.c @@ -40,6 +40,7 @@ static const uint8_t ELF_HEADER[] = { static OtRomImgFormat ot_rom_img_guess_image_format(const char *filename) { + /* NOLINTNEXTLINE(misc-redundant-expression) */ int fd = open(filename, O_RDONLY | O_BINARY | O_CLOEXEC); if (fd == -1) { return OT_ROM_IMG_FORMAT_NONE; From 1b3113d7d139f43bdafc15d43165509434862be6 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 1 Jul 2025 18:12:55 +0200 Subject: [PATCH 108/175] [ot] scripts/opentitan: clang-tidy: update to LLVM 20.x Signed-off-by: Emmanuel Blot --- scripts/opentitan/ot-tidy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/opentitan/ot-tidy.sh b/scripts/opentitan/ot-tidy.sh index c111fac6d3303..791ee2b216507 100755 --- a/scripts/opentitan/ot-tidy.sh +++ b/scripts/opentitan/ot-tidy.sh @@ -3,7 +3,7 @@ # Copyright (c) 2024-2025 Rivos, Inc. # SPDX-License-Identifier: Apache2 -EXPECTED_VERSION="19" +EXPECTED_VERSION="20" # find clang-tidy executable: either 'clang-tidy-19' or 'clang-tidy' for ver_suffix in "-${EXPECTED_VERSION}" ""; do From 35aa1fc58b9b82e365cf4840a30ea13a3eb57d64 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 1 Jul 2025 18:08:09 +0200 Subject: [PATCH 109/175] [ot] scripts/opentitan: clang-format: update to LLVM 20.x Signed-off-by: Emmanuel Blot --- scripts/opentitan/clang-format.yml | 19 ++++++++++++++++--- scripts/opentitan/ot-format.sh | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/opentitan/clang-format.yml b/scripts/opentitan/clang-format.yml index 47d544c3595ee..8946a7407c9d1 100644 --- a/scripts/opentitan/clang-format.yml +++ b/scripts/opentitan/clang-format.yml @@ -9,6 +9,7 @@ AlignConsecutiveAssignments: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveBitFields: @@ -16,6 +17,7 @@ AlignConsecutiveBitFields: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveDeclarations: @@ -23,6 +25,7 @@ AlignConsecutiveDeclarations: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveMacros: @@ -30,6 +33,7 @@ AlignConsecutiveMacros: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveShortCaseStatements: @@ -43,6 +47,7 @@ AlignConsecutiveTableGenBreakingDAGArgColons: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenCondOperatorColons: @@ -50,6 +55,7 @@ AlignConsecutiveTableGenCondOperatorColons: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenDefinitionColons: @@ -57,6 +63,7 @@ AlignConsecutiveTableGenDefinitionColons: AcrossEmptyLines: false AcrossComments: false AlignCompound: false + AlignFunctionDeclarations: false AlignFunctionPointers: false PadOperators: false AlignEscapedNewlines: DontAlign @@ -76,11 +83,12 @@ AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false +AllowShortNamespacesOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakBeforeMultilineStrings: false AttributeMacros: BinPackArguments: true -BinPackParameters: true +BinPackParameters: BinPack BitFieldColonSpacing: Both BraceWrapping: AfterCaseLabel: false @@ -111,6 +119,7 @@ BreakBeforeConceptDeclarations: Always BreakBeforeInheritanceComma: false BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: false +BreakBinaryOperations: Never BreakConstructorInitializers: BeforeColon BreakConstructorInitializersBeforeComma: false BreakFunctionDefinitionParameters: false @@ -184,6 +193,7 @@ IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseBlocks: false IndentCaseLabels: false +IndentExportBlock: true IndentExternBlock: AfterExternBlock IndentGotoLabels: true IndentPPDirectives: None @@ -206,6 +216,7 @@ KeepEmptyLines: AtEndOfFile: true AtStartOfBlock: false AtStartOfFile: false +KeepFormFeed: false LambdaBodyIndentation: Signature LineEnding: LF MacroBlockBegin: '' @@ -221,6 +232,7 @@ ObjCSpaceBeforeProtocolList: true PackConstructorInitializers: BinPack PenaltyBreakAssignment: 0 PenaltyBreakBeforeFirstCallParameter: 500 +PenaltyBreakBeforeMemberAccess: 150 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 @@ -234,8 +246,9 @@ PointerAlignment: Right PPIndentWidth: -1 QualifierAlignment: Leave ReferenceAlignment: Pointer -ReflowComments: true +ReflowComments: Always RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false RemoveParentheses: Leave RemoveSemicolon: false RequiresClausePosition: OwnLine @@ -292,5 +305,5 @@ TabWidth: 4 UseTab: Never VerilogBreakBetweenInstancePorts: true WhitespaceSensitiveMacros: +WrapNamespaceBodyWithEmptyLines: Leave ... - diff --git a/scripts/opentitan/ot-format.sh b/scripts/opentitan/ot-format.sh index 32e3ed5b5a7cc..79691359d6847 100755 --- a/scripts/opentitan/ot-format.sh +++ b/scripts/opentitan/ot-format.sh @@ -3,7 +3,7 @@ # Copyright (c) 2024-2025 Rivos, Inc. # SPDX-License-Identifier: Apache2 -EXPECTED_VERSION="19" +EXPECTED_VERSION="20" # find clang-format executable: either 'clang-format-19' or 'clang-format' for ver_suffix in "-${EXPECTED_VERSION}" ""; do From a0544a2b0940409682cce594889bd402119710fe Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 12:43:41 +0200 Subject: [PATCH 110/175] [ot] .github: workflows: update to LLVM 20.x Signed-off-by: Emmanuel Blot --- .github/workflows/build_test.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 6f6633e33cb8d..bdc77e23c62b1 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -18,9 +18,9 @@ jobs: run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" && + sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" && sudo apt-get update && - sudo apt-get install -y git make pkg-config clang-19 cmake ninja-build python3 rust-all \ + sudo apt-get install -y git make pkg-config clang-20 cmake ninja-build python3 rust-all \ libpixman-1-dev libglib2.0-dev - name: Check out QEMU uses: actions/checkout@v4 @@ -30,7 +30,7 @@ jobs: git clean -dffx subprojects mkdir build-clang (cd build-clang && - ../configure --cc=clang-19 --disable-werror --disable-install-blobs \ + ../configure --cc=clang-20 --disable-werror --disable-install-blobs \ --target-list=riscv32-softmmu,riscv64-softmmu) - name: Build run: | @@ -73,9 +73,9 @@ jobs: run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" && + sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" && sudo apt-get update && - sudo apt-get install -y clang-format-19 + sudo apt-get install -y clang-format-20 - name: Check out QEMU uses: actions/checkout@v4 - name: Check C code format @@ -106,9 +106,9 @@ jobs: run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" && + sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" && sudo apt-get update && - sudo apt-get install -y clang-tidy-19 libglib2.0-dev + sudo apt-get install -y clang-tidy-20 libglib2.0-dev - name: Check out QEMU uses: actions/checkout@v4 - name: Download QEMU source artifacts @@ -166,7 +166,7 @@ jobs: run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" && + sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" && sudo apt-get update && sudo apt-get install -y git make pkg-config gcc cmake ninja-build python3 rust-all \ libpixman-1-dev libglib2.0-dev From e69c54383262cac660f86b1ccc4e61b795d24115 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 2 Jul 2025 18:07:34 +0200 Subject: [PATCH 111/175] [ot] hw/opentitan: ot_otp_be_if: define OTP characteristics retrieval API Signed-off-by: Emmanuel Blot --- include/hw/opentitan/ot_otp_be_if.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/include/hw/opentitan/ot_otp_be_if.h b/include/hw/opentitan/ot_otp_be_if.h index 8a5a5eb2cdf77..c4201c477a6bf 100644 --- a/include/hw/opentitan/ot_otp_be_if.h +++ b/include/hw/opentitan/ot_otp_be_if.h @@ -1,7 +1,7 @@ /* * QEMU OpenTitan OTP backend interface * - * Copyright (c) 2024 Rivos, Inc. + * Copyright (c) 2024-2025 Rivos, Inc. * * Author(s): * Emmanuel Blot @@ -39,6 +39,13 @@ DECLARE_CLASS_CHECKERS(OtOtpBeIfClass, OT_OTP_BE_IF, TYPE_OT_OTP_BE_IF) typedef struct OtOtpBeIf OtOtpBeIf; +typedef struct { + struct { + unsigned read_ns; /* time to read an OTP cell */ + unsigned write_ns; /* time to write an OTP cell */ + } timings; +} OtOtpBeCharacteristics; + struct OtOtpBeIfClass { InterfaceClass parent_class; @@ -49,6 +56,13 @@ struct OtOtpBeIfClass { * @return @c true if ECC mode is active, @c false otherwise */ bool (*is_ecc_enabled)(OtOtpBeIf *beif); + + /* + * Retrieve OTP back end characteristics + * + * @return the OTP characteristics + */ + const OtOtpBeCharacteristics *(*get_characteristics)(OtOtpBeIf *beif); }; #endif /* HW_OPENTITAN_OT_OTP_BE_IF_H */ From 44791f63321c6eed5c161cf9c88518d3c8b26768 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 2 Jul 2025 18:08:37 +0200 Subject: [PATCH 112/175] [ot] hw/opentitan: ot_otp_be: define OTP backend characteristics Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_ot_be.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hw/opentitan/ot_otp_ot_be.c b/hw/opentitan/ot_otp_ot_be.c index 408ea57daf2f8..be541196286f9 100644 --- a/hw/opentitan/ot_otp_ot_be.c +++ b/hw/opentitan/ot_otp_ot_be.c @@ -129,6 +129,13 @@ struct OtOtpOtBeClass { ResettablePhases parent_phases; }; +static const OtOtpBeCharacteristics OTP_BE_CHARACTERISTICS = { + .timings = { + .read_ns = 5000u /* 5 us */, + .write_ns = 50000u /* 50 us */, + }, +}; + static uint64_t ot_otp_ot_be_read(void *opaque, hwaddr addr, unsigned size) { OtOtpOtBeState *s = opaque; @@ -203,6 +210,14 @@ static bool ot_otp_ot_be_is_ecc_enabled(OtOtpBeIf *beif) return true; } +static const OtOtpBeCharacteristics* +ot_otp_ot_be_get_characteristics(OtOtpBeIf *beif) +{ + (void)beif; + + return &OTP_BE_CHARACTERISTICS; +} + static Property ot_otp_ot_be_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtOtpOtBeState, ot_id), DEFINE_PROP_LINK("parent", OtOtpOtBeState, parent, TYPE_DEVICE, @@ -254,6 +269,7 @@ static void ot_otp_ot_be_class_init(ObjectClass *klass, void *data) OtOtpBeIfClass *bec = OT_OTP_BE_IF_CLASS(klass); bec->is_ecc_enabled = &ot_otp_ot_be_is_ecc_enabled; + bec->get_characteristics = &ot_otp_ot_be_get_characteristics; } static const TypeInfo ot_otp_ot_be_init_info = { From 9dbe072ad174db7761dcd2ebc33d921d736c6cea Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 2 Jul 2025 18:09:23 +0200 Subject: [PATCH 113/175] [ot] hw/opentitan: ot_otp_dj: use OTP backend timings to fake delays Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 4da68bb62689f..4497cee1ce806 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -555,10 +555,7 @@ REG32(LC_STATE, 16344u) #define OT_OTP_HW_CLOCK QEMU_CLOCK_VIRTUAL_RT /* the following delays are arbitrary for now */ -#define DAI_READ_DELAY_NS 100000u /* 100us */ -#define DAI_WRITE_DELAY_NS 1000000u /* 1ms */ -#define DAI_DIGEST_DELAY_NS 5000000u /* 5ms */ -#define LCI_PROG_DELAY_NS 500000u /* 500us*/ +#define DAI_DIGEST_DELAY_NS 50000u /* 50us */ #define LCI_PROG_SCHED_NS 1000u /* 1us*/ #define SRAM_KEY_SEED_WIDTH (SRAM_DATA_KEY_SEED_SIZE * 8u) @@ -812,6 +809,7 @@ struct OtOTPDjState { OtOTPPartController *partctrls; OtOTPKeyGen *keygen; OtOTPScrmblKeyInit *scrmbl_key_init; + OtOtpBeCharacteristics be_chars; uint64_t digest_iv; uint8_t digest_const[16u]; uint64_t sram_iv; @@ -823,7 +821,7 @@ struct OtOTPDjState { char *ot_id; BlockBackend *blk; /* OTP host backend */ - OtOtpBeIf *otp_backend; /* may be NULL */ + OtOtpBeIf *otp_backend; OtEDNState *edn; char *scrmbl_key_xstr; char *digest_const_xstr; @@ -1174,10 +1172,6 @@ static bool ot_otp_dj_is_buffered(int partition) static bool ot_otp_dj_is_backend_ecc_enabled(const OtOTPDjState *s) { - if (!s->otp_backend) { - return true; - } - OtOtpBeIfClass *bec = OT_OTP_BE_IF_GET_CLASS(s->otp_backend); if (!bec->is_ecc_enabled) { return true; @@ -1957,6 +1951,7 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) uint32_t data_lo, data_hi; unsigned err = 0; + unsigned cell_count = sizeof(uint32_t) + (do_ecc ? sizeof(uint16_t) : 0); if (is_wide || is_digest) { waddr &= ~0b1u; @@ -1972,6 +1967,8 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) data_hi = ot_otp_dj_verify_ecc(s, data_hi, ecc >> 16u, &err); } } + + cell_count *= 2u; } else { data_lo = s->otp->data[waddr]; data_hi = 0u; @@ -1986,6 +1983,9 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) if (ot_otp_dj_is_ecc_enabled(s)) { data_lo = ot_otp_dj_verify_ecc(s, data_lo, ecc & 0xffffu, &err); } + cell_count = 4u + 2u; + } else { + cell_count = 4u; } } @@ -2003,8 +2003,9 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) if (!ot_otp_dj_is_buffered(partition)) { /* fake slow access to OTP cell */ + unsigned access_time = s->be_chars.timings.read_ns * cell_count; timer_mod(s->dai->delay, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + DAI_READ_DELAY_NS); + qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + access_time); } else { DAI_CHANGE_STATE(s, OTP_DAI_IDLE); } @@ -2208,21 +2209,29 @@ static void ot_otp_dj_dai_write(OtOTPDjState *s) s->dai->partition = partition; + bool do_ecc = ot_otp_dj_is_ecc_enabled(s); + unsigned cell_count = sizeof(uint32_t); + if (is_wide || is_digest) { if (ot_otp_dj_dai_write_u64(s, address)) { return; } + cell_count *= 2u; } else { if (ot_otp_dj_dai_write_u32(s, address)) { return; } } + if (do_ecc) { + cell_count += cell_count / 2u; + }; + DAI_CHANGE_STATE(s, OTP_DAI_WRITE_WAIT); - /* fake slow access to OTP cell */ - timer_mod(s->dai->delay, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + DAI_WRITE_DELAY_NS); + /* fake slow update of OTP cell */ + unsigned update_time = s->be_chars.timings.write_ns * cell_count; + timer_mod(s->dai->delay, qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + update_time); } static void ot_otp_dj_dai_digest(OtOTPDjState *s) @@ -2321,7 +2330,7 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) DAI_CHANGE_STATE(s, OTP_DAI_DIG_WAIT); - /* fake slow access to OTP cell */ + /* fake slow update of OTP cell */ timer_mod(s->dai->delay, qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + DAI_DIGEST_DELAY_NS); } @@ -2384,9 +2393,10 @@ static void ot_otp_dj_dai_write_digest(void *opaque) DAI_CHANGE_STATE(s, OTP_DAI_WRITE_WAIT); - /* fake slow access to OTP cell */ - timer_mod(s->dai->delay, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + DAI_WRITE_DELAY_NS); + /* fake slow update of OTP cell */ + unsigned cell_count = sizeof(uint64_t) + sizeof(uint32_t); + unsigned update_time = s->be_chars.timings.write_ns * cell_count; + timer_mod(s->dai->delay, qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + update_time); } static void ot_otp_dj_dai_complete(void *opaque) @@ -3530,8 +3540,9 @@ static void ot_otp_dj_lci_write_word(void *opaque) lci->hpos += 1; + unsigned update_time = s->be_chars.timings.write_ns * sizeof(uint16_t); timer_mod(lci->prog_delay, - qemu_clock_get_ns(OT_OTP_HW_CLOCK) + LCI_PROG_DELAY_NS); + qemu_clock_get_ns(OT_OTP_HW_CLOCK) + update_time); LCI_CHANGE_STATE(s, OTP_LCI_WRITE_WAIT); } @@ -4065,6 +4076,10 @@ static void ot_otp_dj_reset_exit(Object *obj, ResetType type) c->parent_phases.exit(obj, type); } + OtOtpBeIfClass *bec = OT_OTP_BE_IF_GET_CLASS(s->otp_backend); + memcpy(&s->be_chars, bec->get_characteristics(s->otp_backend), + sizeof(OtOtpBeCharacteristics)); + ot_edn_connect_endpoint(s->edn, s->edn_ep, &ot_otp_dj_keygen_push_entropy, s); @@ -4077,6 +4092,7 @@ static void ot_otp_dj_realize(DeviceState *dev, Error **errp) (void)errp; g_assert(s->ot_id); + g_assert(s->otp_backend); ot_otp_dj_configure_scrmbl_key(s); ot_otp_dj_configure_digest(s); From 5f34095ad499165498bd76e9c7cf2414a5ba1fb8 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 16:26:54 +0200 Subject: [PATCH 114/175] [ot] hw/opentitan: ot_entropy_src: fix a bug in entropy fill-in function Potential buffer overflow as the wrong constant was used. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_entropy_src.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/opentitan/ot_entropy_src.c b/hw/opentitan/ot_entropy_src.c index 367cda7083b63..23bda3f7bd3f1 100644 --- a/hw/opentitan/ot_entropy_src.c +++ b/hw/opentitan/ot_entropy_src.c @@ -1019,7 +1019,7 @@ static bool ot_entropy_src_fill_noise(OtEntropySrcState *s) /* push the whole entropy buffer into the input FIFO */ unsigned pos = 0; - while (!ot_fifo32_is_full(&s->input_fifo) && pos < ES_WORD_COUNT) { + while (!ot_fifo32_is_full(&s->input_fifo) && pos < ARRAY_SIZE(buffer)) { ot_fifo32_push(&s->input_fifo, buffer[pos++]); } From e00cb0c6150ddedb26d0a57a93ec0ac46e33293d Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 3 Jul 2025 14:26:21 +0200 Subject: [PATCH 115/175] [ot] scripts/opentitan: pyot: improve ROM support - decrease file manager verbosity (use -vvvv to get debug log from it) - replace `-kernel` with `-device loader` option when ROM is used - add a special syntax to disable a ROM slot loading while still generating the proper ROM option strings - fix an issue with test config parser as options with dash were not replace with an underscore as the regular argument parser does. Those options were simply silently discarded Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 14 ++++++++++++-- scripts/opentitan/pyot.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index b79353af660b8..b669814bf5928 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -280,6 +280,7 @@ def _build_qemu_fw_args(self, args: Namespace) \ -> tuple[str, Optional[str], list[str], Optional[str]]: rom_exec = bool(args.rom_exec) roms = args.rom or [] + # rom can be specified as a string or a list of strings if isinstance(roms, str): roms = [roms] multi_rom = (len(roms) + int(rom_exec)) > 1 @@ -300,6 +301,10 @@ def _build_qemu_fw_args(self, args: Namespace) \ for chip_id in range(chiplet_count): rom_count = 0 for rom in roms: + if rom in ('-', '_'): + # special marker to disable a specific ROM + rom_count += 1 + continue rom_path = self._qfm.interpolate(rom) if not isfile(rom_path): raise ValueError(f'Unable to find ROM file {rom_path}') @@ -354,7 +359,11 @@ def _build_qemu_fw_args(self, args: Namespace) \ rom_count += 1 else: if args.embedded_flash is None: - fw_args.extend(('-kernel', exec_path)) + if not roms: + fw_args.extend(('-kernel', exec_path)) + else: + fw_args.extend(('-device', + f'loader,file={exec_path}')) else: exec_path = None return machine, xtype, fw_args, exec_path @@ -641,7 +650,8 @@ def _build_test_args(self, test_name: str) \ self._log.debug('No configuration for test %s', test_name) opts = None else: - test_cfg = {k: v for k, v in test_cfg.items() + # use same arg parser dash-underscore replacement for option name + test_cfg = {k.replace('-', '_'): v for k, v in test_cfg.items() if k not in ('pre', 'post', 'with')} self._log.debug('Using custom test config for %s', test_name) discards = {k for k, v in test_cfg.items() if v == ''} diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 1d512abfbb61a..789a5d1d75738 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -196,7 +196,7 @@ def main(): result_file = args.result log = configure_loggers(args.verbose, 'pyot', - -1, 'flashgen', 'elf', 'otp', 1, + -1, 'flashgen', 'elf', 'otp', 'pyot.file', 1, args.vcp_verbose or 0, 'pyot.vcp', name_width=30, ms=args.log_time, quiet=args.quiet, From 9e878be3d1412a63c6ff652f1c4582c3a84decda Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 3 Jul 2025 15:47:07 +0200 Subject: [PATCH 116/175] [ot] python/qemu: jtagtools.bits: fix to_bytestream implementation This function was not able to cope with BitSequence whose length was not a multiple of 8 bits Signed-off-by: Emmanuel Blot --- python/qemu/jtagtools/bits/__init__.py | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/python/qemu/jtagtools/bits/__init__.py b/python/qemu/jtagtools/bits/__init__.py index 148f716e25b68..c5ccb5b7c39e9 100644 --- a/python/qemu/jtagtools/bits/__init__.py +++ b/python/qemu/jtagtools/bits/__init__.py @@ -7,6 +7,12 @@ """Bit sequence helpers for JTAG. BitSequence handle bit manipulation for the JTAG tools. + + To run all tests, use: + python3 jtagtools/bits/__init__.py [-v] + + To test a single function, install pytest and use: + pytest --doctest-modules jtagtools/bits -k """ from typing import Any, Iterable, Union @@ -22,7 +28,7 @@ class BitSequenceError(Exception): """ -BitSequenceInitializer = Union['BitSequence', str, int, memoryview, +BitSequenceInitializer = Union['BitSequence', str, int, bytes, bytearray, Iterable[int], Iterable[bool], None] """Supported types to initialize a BitSequence.""" @@ -155,7 +161,7 @@ def from_int(cls, value: int, width: int) -> 'BitSequence': return bseq @classmethod - def from_bytes(cls, value: memoryview) -> 'BitSequence': + def from_bytes(cls, value: Union[bytes, bytearray]) -> 'BitSequence': """Instanciate a BitSequence from a sequence of bytes, one bit for each input byte. @@ -165,8 +171,8 @@ def from_bytes(cls, value: memoryview) -> 'BitSequence': return cls.from_iterable(value) @classmethod - def from_bytestream(cls, value: memoryview, lsbyte: bool = False) \ - -> 'BitSequence': + def from_bytestream(cls, value: Union[bytes, bytearray], + lsbyte: bool = False) -> 'BitSequence': """Instanciate a BitSequence from a sequence of bytes, 8 bits for each input byte. @@ -342,12 +348,23 @@ def to_bytestream(self, lsbyte: bool = False, lsbit: bool = False)\ b'0ba523' >>> hexlify(BitSequence(0xC4A5D01234, 40).to_bytestream(False, False)) b'c4a5d01234' + >>> hexlify(BitSequence(0x51234, 20).to_bytestream(False, False)) + b'051234' + >>> hexlify(BitSequence(0x51234, 21).to_bytestream(False, False)) + b'051234' + >>> hexlify(BitSequence(0x51234, 21).to_bytestream(True, False)) + b'341205' + >>> hexlify(BitSequence(0x51234, 21).to_bytestream(True, True)) + b'2c48a0' """ out: list[int] = [] bseq = BitSequence(self) + xbitlen = len(bseq) & 7 + if xbitlen: + bseq.push_left([0] * (8 - xbitlen)) if lsbit: bseq.reverse() - while bseq._width: + while bseq._width > 0: out.append(bseq._int & 0xff) bseq._int >>= 8 bseq._width -= 8 From bfd6946fd9d92971f53299a593a2bc95d2ea44e8 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 14:39:47 +0200 Subject: [PATCH 117/175] [ot] scripts/opentitan: clang-tidy.yml: discard buggy rule Discard a not so clever clang-tidy heuristic where struct { struct { int a; int b; int c; } s; enum { x, y } e; } foo; triggers a 'bugprone-tagged-union-member-count' warning as clang-tidy assumes that `enum e` selects one member in `struct s`! Signed-off-by: Emmanuel Blot --- scripts/opentitan/clang-tidy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/opentitan/clang-tidy.yml b/scripts/opentitan/clang-tidy.yml index d4c929bc7bad0..1e40512c7f660 100644 --- a/scripts/opentitan/clang-tidy.yml +++ b/scripts/opentitan/clang-tidy.yml @@ -12,6 +12,7 @@ Checks: '*, -bugprone-implicit-widening-of-multiplication-result, -bugprone-multi-level-implicit-pointer-conversion, -bugprone-reserved-identifier, + -bugprone-tagged-union-member-count, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -clang-diagnostic-error, -clang-diagnostic-unknown-warning-option, From bed1594aa0a8885ba043c1b15d6bc74a997a2dfa Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 2 Jul 2025 16:18:34 +0200 Subject: [PATCH 118/175] [ot] hw/opentitan: multiple components: use C-style comment style (QEMU prohibits C++ style) Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_common.c | 2 +- hw/opentitan/ot_dev_proxy.c | 2 +- hw/opentitan/ot_gpio_dj.c | 2 +- hw/opentitan/ot_gpio_eg.c | 2 +- hw/opentitan/ot_lc_ctrl.c | 10 ++++++---- hw/opentitan/ot_otp_dj.c | 22 +++++++++++++--------- hw/opentitan/ot_pwrmgr.c | 6 ++++-- hw/opentitan/ot_socdbg_ctrl.c | 5 +++-- hw/opentitan/ot_spi_device.c | 4 ++-- hw/opentitan/ot_uart.c | 2 +- hw/opentitan/ot_vmapper.c | 2 +- 11 files changed, 34 insertions(+), 25 deletions(-) diff --git a/hw/opentitan/ot_common.c b/hw/opentitan/ot_common.c index 6864bdbf7b3ea..62e06890588ea 100644 --- a/hw/opentitan/ot_common.c +++ b/hw/opentitan/ot_common.c @@ -224,7 +224,7 @@ AddressSpace *ot_common_get_local_address_space(DeviceState *s) void ot_common_configure_device_opts(DeviceState **devices, unsigned count) { - // TODO need to use qemu_find_opts_err if no config is ok + /* TODO need to use qemu_find_opts_err if no config is ok */ QemuOptsList *optlist = qemu_find_opts("ot_device"); if (!optlist) { qemu_log("%s: no config\n", __func__); diff --git a/hw/opentitan/ot_dev_proxy.c b/hw/opentitan/ot_dev_proxy.c index de296fa4be22a..6b27c7a6dc3fd 100644 --- a/hw/opentitan/ot_dev_proxy.c +++ b/hw/opentitan/ot_dev_proxy.c @@ -1642,7 +1642,7 @@ static int ot_dev_proxy_be_change(void *opaque) if (s->watch_tag > 0) { g_source_remove(s->watch_tag); - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, &ot_dev_proxy_watch_cb, s); } diff --git a/hw/opentitan/ot_gpio_dj.c b/hw/opentitan/ot_gpio_dj.c index c8570e7cee90a..5ffd7d68279b5 100644 --- a/hw/opentitan/ot_gpio_dj.c +++ b/hw/opentitan/ot_gpio_dj.c @@ -781,7 +781,7 @@ static int ot_gpio_dj_chr_be_change(void *opaque) if (s->watch_tag > 0) { g_source_remove(s->watch_tag); - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, &ot_gpio_dj_chr_watch_cb, s); } diff --git a/hw/opentitan/ot_gpio_eg.c b/hw/opentitan/ot_gpio_eg.c index f6b8e2591e644..99318c6fc1d14 100644 --- a/hw/opentitan/ot_gpio_eg.c +++ b/hw/opentitan/ot_gpio_eg.c @@ -677,7 +677,7 @@ static int ot_gpio_eg_chr_be_change(void *opaque) if (s->watch_tag > 0) { g_source_remove(s->watch_tag); - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, &ot_gpio_eg_chr_watch_cb, s); } diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 05e4c341687c7..e6fb9fd66b7be 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -1350,8 +1350,9 @@ static void ot_lc_ctrl_start_transition(OtLcCtrlState *s) if (s->lc_tcount == 0) { s->lc_tcount = 1u; } - // TODO DFT start override (see RTL) - // TODO change FSM behavior once this is selected + /* TODO DFT start override (see RTL) */ + + /* TODO change FSM behavior once this is selected */ s->volatile_unlocked = true; s->regs[R_STATUS] |= R_STATUS_TRANSITION_SUCCESSFUL_MASK; trace_ot_lc_ctrl_info(s->ot_id, "Successful volatile unlock"); @@ -1683,8 +1684,9 @@ static uint32_t ot_lc_ctrl_regs_read(OtLcCtrlState *s, hwaddr addr, switch (reg) { case R_LC_TRANSITION_CNT: - // TODO: >= 24 -> state == SCRAP - // Error: should be 31 + /* TODO: >= 24 -> state == SCRAP */ + + /* Error: should be 31 */ val32 = s->lc_tcount; break; case R_LC_STATE: diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 4497cee1ce806..0ab71c6cd647a 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -685,7 +685,7 @@ typedef enum { OTP_LCI_ERROR, } OtOTPLCIState; -// TODO: wr and rd lock need to be rewritten (not simple boolean) +/* TODO: wr and rd lock need to be rewritten (not simple boolean) */ typedef struct { uint16_t size; @@ -1450,8 +1450,10 @@ static uint64_t ot_otd_dj_verify_digest(OtOTPDjState *s, unsigned partition, if (err) { OtOTPError otp_err = (err > 1) ? OTP_MACRO_ECC_UNCORR_ERROR : OTP_MACRO_ECC_CORR_ERROR; - // Note: need to check if any caller could override the error/state - // in this case + /* + * Note: need to check if any caller could override the error/state + * in this case + */ ot_otp_dj_set_error(s, partition, otp_err); } @@ -1478,8 +1480,10 @@ static int ot_otp_dj_apply_ecc(OtOTPDjState *s, unsigned partition) if (err) { OtOTPError otp_err = (err > 1) ? OTP_MACRO_ECC_UNCORR_ERROR : OTP_MACRO_ECC_CORR_ERROR; - // Note: need to check if any caller could override the error/state - // in this case + /* + * Note: need to check if any caller could override the error/state + * in this case + */ ot_otp_dj_set_error(s, partition, otp_err); if (err > 1) { trace_ot_otp_ecc_init_error(s->ot_id, PART_NAME(partition), @@ -1689,7 +1693,7 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) if (level) { DAI_CHANGE_STATE(s, OTP_DAI_ERROR); LCI_CHANGE_STATE(s, OTP_LCI_ERROR); - // TODO: manage other FSMs + /* TODO: manage other FSMs */ qemu_log_mask(LOG_UNIMP, "%s: %s: ESCALATE partially implemented\n", __func__, s->ot_id); @@ -1859,11 +1863,11 @@ static inline int ot_otp_dj_write_backend(OtOTPDjState *s, const void *buffer, */ g_assert(offset + size <= s->otp->size); - // NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) */ return blk_pwrite(s->blk, (int64_t)(intptr_t)offset, (int64_t)size, buffer, /* a bitfield of enum is not an enum item */ (BdrvRequestFlags)0); - // NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) */ } static void ot_otp_dj_dai_init(OtOTPDjState *s) @@ -2368,7 +2372,7 @@ static void ot_otp_dj_dai_write_digest(void *opaque) uint32_t ecc = ot_otp_dj_compute_ecc_u64(data); - // dwaddr is 64-bit based, convert it to 32-bit base for ECC + /* dwaddr is 64-bit based, convert it to 32-bit base for ECC */ unsigned ewaddr = (dwaddr << 1u) / s->otp->ecc_granule; g_assert(ewaddr < s->otp->ecc_size); uint32_t *edst = &s->otp->ecc[ewaddr]; diff --git a/hw/opentitan/ot_pwrmgr.c b/hw/opentitan/ot_pwrmgr.c index 519a73cc74dfd..f68630f260aab 100644 --- a/hw/opentitan/ot_pwrmgr.c +++ b/hw/opentitan/ot_pwrmgr.c @@ -625,8 +625,10 @@ static void ot_pwrmgr_fast_fsm_tick(OtPwrMgrState *s) s->boot_status.io_ip_clk_en = 1u; ibex_irq_set(&s->boot_st, s->boot_status.i32); PWR_CHANGE_FAST_STATE(s, RELEASE_LC_RST); - // TODO: need to release ROM controllers from reset here to emulate - // they are clocked and start to verify their contents. + /* + * TODO: need to release ROM controllers from reset here to emulate + * they are clocked and start to verify their contents. + */ break; case OT_PWR_FAST_ST_RELEASE_LC_RST: PWR_CHANGE_FAST_STATE(s, OTP_INIT); diff --git a/hw/opentitan/ot_socdbg_ctrl.c b/hw/opentitan/ot_socdbg_ctrl.c index daef6a603d4ee..bd4feeae7bf8b 100644 --- a/hw/opentitan/ot_socdbg_ctrl.c +++ b/hw/opentitan/ot_socdbg_ctrl.c @@ -477,14 +477,15 @@ static void ot_socdbg_ctrl_lc_broadcast(void *opaque, int n, int level) case OT_LC_ISO_PART_SW_RD_EN: case OT_LC_ISO_PART_SW_WR_EN: case OT_LC_OWNER_SEED_SW_RW_EN: - // do not seem to be routed... + /* do not seem to be routed... */ break; case OT_LC_CREATOR_SEED_SW_RW_EN: case OT_LC_SEED_HW_RD_EN: case OT_LC_ESCALATE_EN: case OT_LC_CHECK_BYP_EN: /* verbatim from RTL: "Use unused singals to make lint clean" */ - // why do we explictly route signals that are then discarded? + + /* why do we explictly route signals that are then discarded? */ break; /* NOLINTEND(bugprone-branch-clone) */ default: diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index af5514a7d58ad..869ec6e7c3475 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1826,7 +1826,7 @@ static MemTxResult ot_spi_device_buf_read_with_attrs( val32 = 0; } - // TODO: check which buffers can only be accessed as 32-bit locations + /* TODO: check which buffers can only be accessed as 32-bit locations */ unsigned addr_offset = (addr & 3u); g_assert((addr_offset + size) <= 4u); @@ -2073,7 +2073,7 @@ static int ot_spi_device_chr_be_change(void *opaque) if (s->watch_tag > 0) { g_source_remove(s->watch_tag); - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, &ot_spi_device_chr_watch_cb, s); } diff --git a/hw/opentitan/ot_uart.c b/hw/opentitan/ot_uart.c index 3d07c13336a2d..cdf35ffe1bf3c 100644 --- a/hw/opentitan/ot_uart.c +++ b/hw/opentitan/ot_uart.c @@ -615,7 +615,7 @@ static int ot_uart_be_change(void *opaque) if (s->watch_tag > 0) { g_source_remove(s->watch_tag); - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, ot_uart_watch_cb, s); } diff --git a/hw/opentitan/ot_vmapper.c b/hw/opentitan/ot_vmapper.c index 7a7b6652e445c..4ccbdee78aa6b 100644 --- a/hw/opentitan/ot_vmapper.c +++ b/hw/opentitan/ot_vmapper.c @@ -754,7 +754,7 @@ static GList *ot_vmapper_fuse(OtVMapperState *s, GList *rglist) VMAP_RANGE(current)->end = VMAP_RANGE(next)->end; rglist = g_list_remove_link(rglist, next); - g_free(next->data); // OtRegionRange + g_free(next->data); /* OtRegionRange */ g_list_free(next); /* From d7319266364edd00c360f9be965112b3dacb44cd Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Oct 2024 16:10:54 +0100 Subject: [PATCH 119/175] [ot] scripts/opentitan: cfggen.py: add LC ctrl keymgr parameters Signed-off-by: Emmanuel Blot --- scripts/opentitan/cfggen.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 3febf3b8cb79a..f288e1b168b73 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -125,6 +125,10 @@ def load_top_config(self, toppath: str) -> None: self._load_top_values(module, self._otp, False, r'RndCnst(.*)Init') continue + if modtype == 'lc_ctrl': + self._load_top_values(module, self._lc, False, + r'RndCnstLcKeymgrDiv(.*)') + continue clocks = cfg.get('clocks', {}) for clock in clocks.get('srcs', []): name = clock['name'] From ed299ed6aecc29e8d9c0a9e3d425d15b30de00eb Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Oct 2024 17:54:23 +0100 Subject: [PATCH 120/175] [ot] scripts/opentitan: cfggen.py: add OTP default invalid partition values Signed-off-by: Emmanuel Blot --- python/qemu/ot/otp/const.py | 70 +++++++++++++++++++++++++++++++++++-- scripts/opentitan/cfggen.py | 9 +++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/otp/const.py b/python/qemu/ot/otp/const.py index 6982e657335ed..67917907e6614 100644 --- a/python/qemu/ot/otp/const.py +++ b/python/qemu/ot/otp/const.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023-2024 Rivos, Inc. +# Copyright (c) 2023-2025 Rivos, Inc. # SPDX-License-Identifier: Apache2 """Lifecycle helpers. @@ -6,8 +6,9 @@ :author: Emmanuel Blot """ +from binascii import hexlify, unhexlify from logging import getLogger -from typing import TextIO +from typing import Optional, TextIO import re from ot.util.misc import camel_to_snake_case @@ -21,6 +22,7 @@ def __init__(self): self._log = getLogger('otp.const') self._consts: dict[str, list[str]] = {} self._enums: dict[str, dict[str, int]] = {} + self._defaults: list[Optional[bytes]] = [] def load(self, svp: TextIO): """Decode OTP information. @@ -56,6 +58,53 @@ def load(self, svp: TextIO): consts.append(cmo.group(2).lower()) # RTL order in array is reversed consts.reverse() + # +parameter +logic +\[\d+:0\] +PartInvDefault += +\d+'\(\{(.*)\}\); + inv_default = [] + for imo in re.finditer(r"(?s) +parameter +logic +\[\d+:0\] +" + r"PartInvDefault += +(\d+)'\(\{(.*)\}\);", + svdata): + if inv_default: + raise ValueError('PartInvDefault redefined') + total_byte_count = int(imo.group(1)) // 8 + for pmo in re.finditer(r"(\d+)'\(\{([^\}]*)\}\)", imo.group(2)): + part_byte_count = int(pmo.group(1)) // 8 + chunks = [] + for bmo in re.finditer(r"(\d+)'h([0-9A-Fa-f]+)", pmo.group(2)): + byte_count = int(bmo.group(1)) // 8 + hexa_str = bmo.group(2) + hexa_str_len = len(hexa_str) + exp_len = byte_count * 2 + if hexa_str_len < exp_len: + pad_str = '0' * (exp_len - hexa_str_len) + hexa_str = f'{pad_str}{hexa_str}' + hexa_str_len += 1 + hexa_bytes = unhexlify(hexa_str) + assert len(hexa_bytes) == byte_count + chunks.append(hexa_bytes) + chunk_byte_count = sum(len(c) for c in chunks) + if chunk_byte_count != part_byte_count: + raise RuntimeError('Invalid partition default bytes') + # RTL order is last-to-first + inv_default.append(list(reversed(chunks))) + byte_count = sum(len(c) for part in inv_default for c in part) + if byte_count != total_byte_count: + raise RuntimeError('Invalid partition default bytes') + # RTL order is last-to-first + inv_default.reverse() + self._defaults.clear() + for pno, part_chunks in enumerate(inv_default): + last_part = pno == len(inv_default) - 1 + # last partition does not have digest, + # all digests are 8-byte long + if not last_part and len(part_chunks[-1]) == 8: + check_chunks = part_chunks[:-1] + else: + check_chunks = part_chunks + if not any(any(c) for c in check_chunks): + self._defaults.append(None) + continue + defaults = b''.join(bytes(reversed(c)) for c in part_chunks) + self._defaults.append(defaults) def get_enums(self) -> list[str]: """Return a list of parsed enumerations.""" @@ -78,3 +127,20 @@ def get_digest_pair(self, name: str, prefix: str) -> dict[str, str]: oname = f"{prefix}_{kname.split('_', 1)[-1]}" odict[oname] = values[idx] return odict + + def get_partition_inv_defaults(self, partition: int) -> Optional[list[str]]: + """Return the invalid default values for a partition, if any. + Partition with only digest defaults are considered without default + values. + + :param partition: the partition index + :return: either None or the default hex-encoded bytes, including the + digest + """ + try: + defaults = self._defaults[partition] + if not defaults: + return defaults + return hexlify(defaults).decode() + except IndexError as exc: + raise ValueError(f'No such partition:{partition}') from exc diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index f288e1b168b73..52ee6fb18c171 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -250,6 +250,15 @@ def load_otp_constants(self, otppath: str) -> None: otpconst.load(cfp) self._otp.update(otpconst.get_digest_pair('cnsty_digest', 'digest')) self._otp.update(otpconst.get_digest_pair('sram_data_key', 'sram')) + idx = 0 + while True: + try: + defaults = otpconst.get_partition_inv_defaults(idx) + if defaults: + self._otp[f'inv_default_part_{idx}'] = defaults + idx += 1 + except ValueError: + break def save(self, variant: str, socid: Optional[str], count: Optional[int], ofp: Optional[TextIO]) -> None: From 1a5a5d76c879856717d9f6dafcb365528eebb8a9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 6 Dec 2024 15:55:19 +0100 Subject: [PATCH 121/175] [ot] scripts/opentitan: cfggen.py: add KeyMgr parameters Signed-off-by: Emmanuel Blot --- scripts/opentitan/cfggen.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index 52ee6fb18c171..bf823c8ff6e60 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -98,6 +98,8 @@ def __init__(self): self._roms: dict[Optional[int], dict[str, str]] = {} self._otp: dict[str, str] = {} self._lc: dict[str, str] = {} + self._keymgr: dict[str, str] = {} + self._keymgr_name: Optional[str] = None self._top_clocks: dict[str, OtClock] = {} self._sub_clocks: dict[str, OtDerivedClock] = {} self._clock_groups: dict[str, OtClockGroup] = {} @@ -129,6 +131,11 @@ def load_top_config(self, toppath: str) -> None: self._load_top_values(module, self._lc, False, r'RndCnstLcKeymgrDiv(.*)') continue + if modtype.startswith('keymgr'): + self._keymgr_name = modtype + self._load_top_values(module, self._keymgr, False, + r'RndCnst((?:.*)Seed)') + continue clocks = cfg.get('clocks', {}) for clock in clocks.get('srcs', []): name = clock['name'] @@ -269,6 +276,7 @@ def save(self, variant: str, socid: Optional[str], count: Optional[int], self._generate_roms(cfg, socid, count or 1) self._generate_otp(cfg, variant, socid) self._generate_life_cycle(cfg, socid) + self._generate_key_mgr(cfg, socid) self._generate_ast(cfg, variant, socid) self._generate_clkmgr(cfg, socid) self._generate_pwrmgr(cfg, socid) @@ -363,6 +371,18 @@ def _generate_life_cycle(self, cfg: ConfigParser, lcdata = dict(sorted(lcdata.items())) cfg[f'ot_device "{lcname}"'] = lcdata + def _generate_key_mgr(self, cfg: ConfigParser, + socid: Optional[str] = None) -> None: + nameargs = [f'ot-{self._keymgr_name}'] + if socid: + nameargs.append(socid) + kmname = '.'.join(nameargs) + kmdata = {} + for kname, value in self._keymgr.items(): + self.add_pair(kmdata, kname, value) + kmdata = dict(sorted(kmdata.items())) + cfg[f'ot_device "{kmname}"'] = kmdata + def _generate_ast(self, cfg: ConfigParser, variant: str, socid: Optional[str] = None) -> None: nameargs = [f'ot-ast-{variant}'] From bbecae4c9bce906466587b986acc6fa6994726db Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Oct 2024 18:47:59 +0100 Subject: [PATCH 122/175] [ot] hw/opentitan: ot_lc_ctrl: add keymgr properties Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 67 +++++++++++++++++++++++-------- hw/opentitan/trace-events | 1 + include/hw/opentitan/ot_lc_ctrl.h | 6 +++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index e6fb9fd66b7be..d8cffd499e397 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -321,12 +321,13 @@ typedef enum { LC_TK_COUNT, } OtLcCtrlToken; -/* ife cycle state group diversification value for keymgr */ +/* Life cycle state group diversification value for keymgr */ typedef enum { LC_DIV_INVALID, LC_DIV_TEST_DEV_RMA, LC_DIV_PROD, -} OtLcCtrlKeyMgrDiv; + LC_DIV_COUNT, +} OtLcCtrlKeyMgrDivType; /* Ownership states */ typedef enum { @@ -377,7 +378,7 @@ struct OtLcCtrlState { uint32_t xregs[EXCLUSIVE_SLOTS_COUNT][EXCLUSIVE_REGS_COUNT]; OtLcState lc_state; uint32_t lc_tcount; - OtLcCtrlKeyMgrDiv km_div; + OtLcCtrlKeyMgrDivType km_div_type; OtOTPTokenValue hash_token; OtLcCtrlIf owner; OtLcCtrlFsmState state; @@ -387,6 +388,8 @@ struct OtLcCtrlState { OtLcCtrlOwnershipValue *ownerships; OtLcCtrlSocDbgValue *socdbgs; OtOTPTokenValue *hashed_tokens; + uint8_t km_divs[LC_DIV_COUNT][OT_LC_KEYMGR_DIV_BYTES]; + uint32_t hashed_token_bm; struct { uint32_t value; @@ -403,6 +406,7 @@ struct OtLcCtrlState { OtOTPState *otp_ctrl; OtKMACState *kmac; char *raw_unlock_token_xstr; + char *km_div_xstrs[LC_DIV_COUNT]; OtLcCtrlTransitionConfig trans_cfg[LC_CTRL_TRANS_COUNT]; uint16_t silicon_creator_id; uint16_t product_id; @@ -750,7 +754,7 @@ static void ot_lc_ctrl_update_alerts(OtLcCtrlState *s) static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) { uint32_t sigbm = 0; - OtLcCtrlKeyMgrDiv div = LC_DIV_INVALID; + OtLcCtrlKeyMgrDivType div_type = LC_DIV_INVALID; switch (s->state) { case ST_RESET: @@ -786,13 +790,13 @@ static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) sigbm = LC_BCAST_BIT(RAW_TEST_RMA) | LC_BCAST_BIT(DFT_EN) | LC_BCAST_BIT(NVM_DEBUG_EN) | LC_BCAST_BIT(HW_DEBUG_EN) | LC_BCAST_BIT(CPU_EN) | LC_BCAST_BIT(ISO_PART_SW_WR_EN); - div = LC_DIV_TEST_DEV_RMA; + div_type = LC_DIV_TEST_DEV_RMA; break; case LC_STATE_TESTUNLOCKED7: sigbm = LC_BCAST_BIT(RAW_TEST_RMA) | LC_BCAST_BIT(DFT_EN) | LC_BCAST_BIT(HW_DEBUG_EN) | LC_BCAST_BIT(CPU_EN) | LC_BCAST_BIT(ISO_PART_SW_WR_EN); - div = LC_DIV_TEST_DEV_RMA; + div_type = LC_DIV_TEST_DEV_RMA; break; case LC_STATE_PROD: case LC_STATE_PRODEND: @@ -813,7 +817,7 @@ static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) if (s->regs[R_LC_ID_STATE] == LC_ID_STATE_PERSONALIZED) { sigbm |= LC_BCAST_BIT(SEED_HW_RD_EN); } - div = LC_DIV_PROD; + div_type = LC_DIV_PROD; break; case LC_STATE_DEV: sigbm = @@ -826,7 +830,7 @@ static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) if (s->regs[R_LC_ID_STATE] == LC_ID_STATE_PERSONALIZED) { sigbm |= LC_BCAST_BIT(SEED_HW_RD_EN); } - div = LC_DIV_TEST_DEV_RMA; + div_type = LC_DIV_TEST_DEV_RMA; break; case LC_STATE_RMA: sigbm = @@ -838,7 +842,7 @@ static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) LC_BCAST_BIT(OWNER_SEED_SW_RW_EN) | LC_BCAST_BIT(ISO_PART_SW_RD_EN) | LC_BCAST_BIT(ISO_PART_SW_WR_EN) | LC_BCAST_BIT(SEED_HW_RD_EN); - div = LC_DIV_TEST_DEV_RMA; + div_type = LC_DIV_TEST_DEV_RMA; break; case LC_STATE_SCRAP: default: @@ -860,7 +864,7 @@ static void ot_lc_ctrl_update_broadcast(OtLcCtrlState *s) break; } - s->km_div = div; + s->km_div_type = div_type; for (unsigned ix = 0; ix < ARRAY_SIZE(s->broadcasts); ix++) { bool level = (bool)(sigbm & (1u << ix)); @@ -1478,7 +1482,7 @@ static inline size_t ot_lc_ctrl_get_keccak_rate_bytes(size_t kstrength) return (KECCAK_STATE_BITS - 2u * kstrength) / 8u; } -static void ot_lc_ctrl_compute_predefined_tokens(OtLcCtrlState *s, Error **errp) +static void ot_lc_ctrl_compute_predefined_tokens(OtLcCtrlState *s) { if (!s->raw_unlock_token_xstr) { trace_ot_lc_ctrl_token_missing(s->ot_id, "raw_unlock_token"); @@ -1490,15 +1494,15 @@ static void ot_lc_ctrl_compute_predefined_tokens(OtLcCtrlState *s, Error **errp) size_t len = strlen(s->raw_unlock_token_xstr); if (len != sizeof(OtOTPTokenValue) * 2u) { - error_setg(errp, "%s: %s invalid raw_unlock_token length\n", __func__, - s->ot_id); + error_setg(&error_fatal, "%s: %s invalid raw_unlock_token length\n", + __func__, s->ot_id); return; } if (ot_common_parse_hexa_str(raw_unlock_token, s->raw_unlock_token_xstr, sizeof(OtOTPTokenValue), true, false)) { - error_setg(errp, "%s: %s unable to parse raw_unlock_token\n", __func__, - s->ot_id); + error_setg(&error_fatal, "%s: %s unable to parse raw_unlock_token\n", + __func__, s->ot_id); return; } @@ -2104,6 +2108,30 @@ static void ot_lc_ctrl_configure_transitions( g_free(first); } +static void ot_lc_ctrl_configure_km_div(OtLcCtrlState *s) +{ + for (unsigned ix = 0; ix < LC_DIV_COUNT; ix++) { + if (!s->km_div_xstrs[ix]) { + trace_ot_lc_ctrl_km_div_missing(s->ot_id, ix); + continue; + } + + size_t len = strlen(s->km_div_xstrs[ix]); + if (len != OT_LC_KEYMGR_DIV_BYTES * 2u) { + error_setg(&error_fatal, "%s: %s invalid km_div #%u length\n", + __func__, s->ot_id, ix); + continue; + } + + if (ot_common_parse_hexa_str(s->km_divs[ix], s->km_div_xstrs[ix], + OT_LC_KEYMGR_DIV_BYTES, true, true)) { + error_setg(&error_fatal, "%s: %s unable to parse km_div #%u\n", + __func__, s->ot_id, ix); + continue; + } + } +} + static Property ot_lc_ctrl_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtLcCtrlState, ot_id), DEFINE_PROP_LINK("otp_ctrl", OtLcCtrlState, otp_ctrl, TYPE_OT_OTP, @@ -2111,6 +2139,10 @@ static Property ot_lc_ctrl_properties[] = { DEFINE_PROP_LINK("kmac", OtLcCtrlState, kmac, TYPE_OT_KMAC, OtKMACState *), DEFINE_PROP_STRING("raw_unlock_token", OtLcCtrlState, raw_unlock_token_xstr), + DEFINE_PROP_STRING("invalid", OtLcCtrlState, km_div_xstrs[LC_DIV_INVALID]), + DEFINE_PROP_STRING("production", OtLcCtrlState, km_div_xstrs[LC_DIV_PROD]), + DEFINE_PROP_STRING("test_dev_rma", OtLcCtrlState, + km_div_xstrs[LC_DIV_TEST_DEV_RMA]), DEFINE_PROP_STRING("lc_state_first", OtLcCtrlState, trans_cfg[LC_CTRL_TRANS_LC_STATE] .state[LC_CTRL_TSTATE_FIRST]), @@ -2202,7 +2234,7 @@ static void ot_lc_ctrl_reset_enter(Object *obj, ResetType type) s->lc_state = LC_STATE_INVALID; s->lc_tcount = LC_TRANSITION_COUNT_MAX + 1u; - s->km_div = LC_DIV_INVALID; + s->km_div_type = LC_DIV_INVALID; /* * do not broadcast the current states, wait for initialization to happen, @@ -2263,7 +2295,8 @@ static void ot_lc_ctrl_realize(DeviceState *dev, Error **errp) ot_lc_ctrl_configure_transitions(s, LC_CTRL_TRANS_SOCDBG, (uint16_t *)s->socdbgs); } - ot_lc_ctrl_compute_predefined_tokens(s, &error_fatal); + ot_lc_ctrl_configure_km_div(s); + ot_lc_ctrl_compute_predefined_tokens(s); } static void ot_lc_ctrl_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 5f12652feacc2..db432ae1f0eb5 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -298,6 +298,7 @@ ot_lc_ctrl_initialize(const char * id, const char *cst, int cstix, unsigned tcou ot_lc_ctrl_io_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_lc_ctrl_io_read_out_repeat(const char * id, uint32_t addr, const char * regname, unsigned count, uint32_t val) "%s: addr=0x%02x (%s) repeated %u times, val=0x%x" ot_lc_ctrl_io_write(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_lc_ctrl_km_div_missing(const char * id, unsigned ix) "%s: #%u" ot_lc_ctrl_load_lc_info_force_raw(const char * id) "%s: force_raw enabled" ot_lc_ctrl_load_otp_hw_cfg(const char * id, const char *socdbg) "%s: socdbg_state: %s" ot_lc_ctrl_load_otp_token(const char * id, const char *tk, unsigned tkix, const char *inv, uint64_t hi, uint64_t lo) "%s: token %s (%u) %svalid: 0x%016" PRIx64 "%016" PRIx64 diff --git a/include/hw/opentitan/ot_lc_ctrl.h b/include/hw/opentitan/ot_lc_ctrl.h index 05b2f301fda8e..7b632d511adbf 100644 --- a/include/hw/opentitan/ot_lc_ctrl.h +++ b/include/hw/opentitan/ot_lc_ctrl.h @@ -59,4 +59,10 @@ typedef enum { OT_LC_BROADCAST_COUNT, } OtLcCtrlBroadcast; +#define OT_LC_KEYMGR_DIV_BYTES 16u /* 128 bits */ + +typedef struct { + uint8_t data[OT_LC_KEYMGR_DIV_BYTES]; +} OtLcCtrlKeyMgrDiv; + #endif /* HW_OPENTITAN_OT_LC_CTRL_H */ From c0a661e34c6fda3ec020ca0c50c8b772d76623fd Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 29 Oct 2024 18:48:48 +0100 Subject: [PATCH 123/175] [ot] hw/opentitan: ot_otp_dj: add invalid default partition properties Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 0ab71c6cd647a..51dd12f74bfb2 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -814,6 +814,7 @@ struct OtOTPDjState { uint8_t digest_const[16u]; uint64_t sram_iv; uint8_t sram_const[16u]; + uint8_t *inv_default_parts[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ OtOTPStorage *otp; OtOTPHWCfg *hw_cfg; @@ -828,6 +829,7 @@ struct OtOTPDjState { char *digest_iv_xstr; char *sram_const_xstr; char *sram_iv_xstr; + char *inv_default_part_xstrs[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ uint8_t edn_ep; bool fatal_escalate; }; @@ -3947,6 +3949,62 @@ static void ot_otp_dj_configure_sram(OtOTPDjState *s) s->sram_iv = ldq_le_p(sram_iv); } +static void ot_otp_dj_configure_inv_default_parts(OtOTPDjState *s) +{ + for (unsigned ix = 0; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!s->inv_default_part_xstrs[ix]) { + continue; + } + + const OtOTPPartDesc *part = &OtOTPPartDescs[ix]; + + size_t len; + + len = strlen(s->inv_default_part_xstrs[ix]); + if (len != part->size * 2u) { + error_setg(&error_fatal, + "%s: %s invalid inv_default_part[%u] length\n", __func__, + s->ot_id, ix); + return; + } + + g_assert(!s->inv_default_parts[ix]); + + s->inv_default_parts[ix] = g_new0(uint8_t, part->size + 1u); + if (ot_common_parse_hexa_str(s->inv_default_parts[ix], + s->inv_default_part_xstrs[ix], part->size, + false, true)) { + error_setg(&error_fatal, + "%s: %s unable to parse inv_default_part[%u]\n", + __func__, s->ot_id, ix); + return; + } + + TRACE_OTP("inv_default_part[%s] %s", PART_NAME(ix), + ot_otp_hexdump(s->inv_default_parts[ix], part->size)); + } +} + +static void ot_otp_dj_class_add_inv_def_props(OtOTPClass *odc) +{ + for (unsigned ix = 0; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!OtOTPPartDescs[ix].buffered) { + continue; + } + + Property *prop = g_new0(Property, 1u); + + prop->name = g_strdup_printf("inv_default_part_%u", ix); + prop->info = &qdev_prop_string; + prop->offset = offsetof(OtOTPDjState, inv_default_part_xstrs) + + sizeof(char *) * ix; + + object_class_property_add(OBJECT_CLASS(odc), prop->name, + prop->info->name, prop->info->get, + prop->info->set, prop->info->release, prop); + } +} + static Property ot_otp_dj_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtOTPDjState, ot_id), DEFINE_PROP_DRIVE("drive", OtOTPDjState, blk), @@ -4101,6 +4159,7 @@ static void ot_otp_dj_realize(DeviceState *dev, Error **errp) ot_otp_dj_configure_scrmbl_key(s); ot_otp_dj_configure_digest(s); ot_otp_dj_configure_sram(s); + ot_otp_dj_configure_inv_default_parts(s); } static void ot_otp_dj_init(Object *obj) @@ -4200,6 +4259,8 @@ static void ot_otp_dj_class_init(ObjectClass *klass, void *data) oc->get_entropy_cfg = &ot_otp_dj_get_entropy_cfg; oc->get_otp_key = &ot_otp_dj_get_otp_key; oc->program_req = &ot_otp_dj_program_req; + + ot_otp_dj_class_add_inv_def_props(oc); } static const TypeInfo ot_otp_dj_info = { From eeff54f9829cc2f2d24f93540d39c45f744c2aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Tue, 5 Sep 2023 11:40:56 +0200 Subject: [PATCH 124/175] [ot] hw/opentitan: ot_kmac: fix KMAC for app interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For app mode, KMAC should append output length to the processed message. If software mode, software is supposed to do so itself. Signed-off-by: Loïc Lefort --- hw/opentitan/ot_kmac.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index a22324df2e0cd..a8934ea53ae6b 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -612,6 +612,16 @@ static void ot_kmac_process(void *opaque) s->current_app->req.msg_len); s->current_app->req_pending = false; if (s->current_app->req.last) { + /* append right-encoded output width if KMAC */ + if (cfg->mode == OT_KMAC_MODE_KMAC) { + uint8_t enc_out_len[3]; + uint32_t output_length = OT_KMAC_APP_DIGEST_BYTES * 8u; + enc_out_len[0] = (output_length >> 8u) & 0xffu; + enc_out_len[1] = output_length & 0xffu; + enc_out_len[2] = 2u; + sha3_process(&s->ltc_state, enc_out_len, + sizeof(enc_out_len)); + } /* go to PROCESSING state, response will be sent there */ ot_kmac_change_fsm_state(s, KMAC_ST_PROCESSING); } else { From 1a1f3d276602ac0b5fc77ede921103d4d1df7fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 17:58:11 +0100 Subject: [PATCH 125/175] [ot] hw/opentitan: ot_rom_ctrl: add public function to retrieve ROM digest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_rom_ctrl.c | 29 +++++++++++++++++------------ include/hw/opentitan/ot_rom_ctrl.h | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index 0f4cb4b03a15c..24f7c61ff9a09 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -114,8 +114,7 @@ static const char *REG_NAMES[REGS_COUNT] = { #define OT_ROM_CTRL_WORD_BITS (OT_ROM_CTRL_DATA_BITS + OT_ROM_CTRL_ECC_BITS) #define OT_ROM_CTRL_WORD_BYTES ((OT_ROM_CTRL_WORD_BITS + 7u) / 8u) -#define ROM_DIGEST_WORDS 8u -#define ROM_DIGEST_BYTES (ROM_DIGEST_WORDS * sizeof(uint32_t)) +#define ROM_DIGEST_WORDS (OT_ROM_DIGEST_BYTES / sizeof(uint32_t)) /* clang-format off */ static const uint8_t SBOX4[16u] = { @@ -126,12 +125,6 @@ static const uint8_t SBOX4[16u] = { static const OtKMACAppCfg KMAC_APP_CFG = OT_KMAC_CONFIG(CSHAKE, 256u, "", "ROM_CTRL"); -struct OtRomCtrlClass { - DeviceClass parent_class; - DeviceRealize parent_realize; - ResettablePhases parent_phases; -}; - struct OtRomCtrlState { SysBusDevice parent_obj; @@ -502,7 +495,7 @@ static uint32_t ot_rom_ctrl_verify_ecc_39_32_u32( static void ot_rom_ctrl_unscramble(OtRomCtrlState *s, const uint64_t *src, uint32_t *dst, unsigned size) { - unsigned scr_word_size = (size - ROM_DIGEST_BYTES) / sizeof(uint32_t); + unsigned scr_word_size = (size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); unsigned log_addr = 0; /* unscramble the whole ROM, except the trailing ROM digest bytes */ s->recovered_error_count = 0; @@ -729,7 +722,7 @@ static bool ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, /* spawn hash calculation */ s->se_buffer = (uint64_t *)baseptr; unsigned word_count = - (s->size - ROM_DIGEST_BYTES) / sizeof(uint32_t); + (s->size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); s->se_last_pos = word_count * OT_ROM_CTRL_WORD_BYTES; s->se_pos = 0; ot_rom_ctrl_send_kmac_req(s); @@ -807,7 +800,8 @@ static bool ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) /* spawn hash calculation */ s->se_buffer = (uint64_t *)baseptr; - unsigned word_count = (s->size - ROM_DIGEST_BYTES) / sizeof(uint32_t); + unsigned word_count = + (s->size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); s->se_last_pos = word_count * OT_ROM_CTRL_WORD_BYTES; s->se_pos = 0; ot_rom_ctrl_send_kmac_req(s); @@ -967,6 +961,15 @@ static void ot_rom_ctrl_regs_write(void *opaque, hwaddr addr, uint64_t val64, } }; +static void ot_rom_ctrl_get_rom_digest(const OtRomCtrlState *s, + uint8_t digest[OT_ROM_DIGEST_BYTES]) +{ + g_assert(s != NULL); + for (unsigned wix = 0; wix < ROM_DIGEST_WORDS; wix++) { + stl_le_p(&digest[wix * sizeof(uint32_t)], s->regs[R_DIGEST_0 + wix]); + } +} + static void ot_rom_ctrl_mem_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { @@ -1213,17 +1216,19 @@ static void ot_rom_ctrl_init(Object *obj) static void ot_rom_ctrl_class_init(ObjectClass *klass, void *data) { - OtRomCtrlClass *rcc = OT_ROM_CTRL_CLASS(klass); (void)data; DeviceClass *dc = DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(dc); + OtRomCtrlClass *rcc = OT_ROM_CTRL_CLASS(klass); resettable_class_set_parent_phases(rc, NULL, &ot_rom_ctrl_reset_hold, NULL, &rcc->parent_phases); dc->realize = &ot_rom_ctrl_realize; device_class_set_props(dc, ot_rom_ctrl_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + rcc->get_rom_digest = &ot_rom_ctrl_get_rom_digest; } static const TypeInfo ot_rom_ctrl_info = { diff --git a/include/hw/opentitan/ot_rom_ctrl.h b/include/hw/opentitan/ot_rom_ctrl.h index 4ce56d77eec0f..c2448c24e733e 100644 --- a/include/hw/opentitan/ot_rom_ctrl.h +++ b/include/hw/opentitan/ot_rom_ctrl.h @@ -1,7 +1,7 @@ /* * QEMU OpenTitan ROM controller * - * Copyright (c) 2023-2024 Rivos, Inc. + * Copyright (c) 2023-2025 Rivos, Inc. * * Author(s): * Loïc Lefort @@ -36,4 +36,19 @@ OBJECT_DECLARE_TYPE(OtRomCtrlState, OtRomCtrlClass, OT_ROM_CTRL) #define OT_ROM_CTRL_GOOD TYPE_OT_ROM_CTRL "-good" #define OT_ROM_CTRL_DONE TYPE_OT_ROM_CTRL "-done" +#define OT_ROM_DIGEST_BYTES 32u + +struct OtRomCtrlClass { + DeviceClass parent_class; + ResettablePhases parent_phases; + + /* + * Retrieve ROM digest. + * + * @digest a pointer to an array that will be filled with the ROM digest + */ + void (*get_rom_digest)(const OtRomCtrlState *s, + uint8_t digest[OT_ROM_DIGEST_BYTES]); +}; + #endif /* HW_OPENTITAN_OT_ROM_CTRL */ From 8af03a4441bc3369c47bae3d63cc0a34df3ab9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:02:28 +0100 Subject: [PATCH 126/175] [ot] hw/opentitan: ot_otp: encode all hw_cfg data as byte arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_otp_dj.c | 8 ++++---- include/hw/opentitan/ot_otp.h | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 51dd12f74bfb2..2c193b441637e 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -3710,11 +3710,11 @@ static void ot_otp_dj_pwr_load_hw_cfg(OtOTPDjState *s) OtOTPHWCfg *hw_cfg = s->hw_cfg; memcpy(hw_cfg->device_id, &otp->data[R_HW_CFG0_DEVICE_ID], - sizeof(*hw_cfg->device_id)); + sizeof(hw_cfg->device_id)); memcpy(hw_cfg->manuf_state, &otp->data[R_HW_CFG0_MANUF_STATE], - sizeof(*hw_cfg->manuf_state)); - memcpy(&hw_cfg->soc_dbg_state[0], &otp->data[R_HW_CFG1_SOC_DBG_STATE], - sizeof(uint32_t)); + sizeof(hw_cfg->manuf_state)); + memcpy(hw_cfg->soc_dbg_state, &otp->data[R_HW_CFG1_SOC_DBG_STATE], + sizeof(hw_cfg->soc_dbg_state)); /* do not prevent execution from SRAM if no OTP configuration is loaded */ hw_cfg->en_sram_ifetch = s->blk ? (uint8_t)otp->data[R_HW_CFG1_EN_SRAM_IFETCH] : diff --git a/include/hw/opentitan/ot_otp.h b/include/hw/opentitan/ot_otp.h index ac94e2cfafb75..e5f87dab9dba1 100644 --- a/include/hw/opentitan/ot_otp.h +++ b/include/hw/opentitan/ot_otp.h @@ -52,13 +52,18 @@ typedef enum { OT_OTP_LC_BROADCAST_COUNT, } OtOtpLcBroadcast; +#define OT_OTP_HWCFG_DEVICE_ID_BYTES 32u +#define OT_OTP_HWCFG_MANUF_STATE_BYTES 32u +#define OT_OTP_HWCFG_SOC_DBG_STATE_BYTES 4u + /* * Hardware configuration (for HW_CFG partition) */ typedef struct { - uint32_t device_id[8u]; - uint32_t manuf_state[8u]; - uint16_t soc_dbg_state[2u]; /* may be meaningless, dep. on the platform */ + uint8_t device_id[OT_OTP_HWCFG_DEVICE_ID_BYTES]; + uint8_t manuf_state[OT_OTP_HWCFG_MANUF_STATE_BYTES]; + /* soc_dbg_state may be meaningless, dep. on the platform */ + uint8_t soc_dbg_state[OT_OTP_HWCFG_SOC_DBG_STATE_BYTES]; /* the following value is stored as OT_MULTIBITBOOL8 */ uint8_t en_sram_ifetch; } OtOTPHWCfg; From b5912af483875d4bf55d7e0513457bfcd69d855f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:04:13 +0100 Subject: [PATCH 127/175] [ot] hw/opentitan: ot_otp_dj: store current LC broadcast for later use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_otp_dj.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 2c193b441637e..2c241f7357b5b 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -767,6 +767,7 @@ typedef struct { QEMUBH *bh; uint16_t signal; /* each bit tells if signal needs to be handled */ uint16_t level; /* level of the matching signal */ + uint16_t current_level; /* current level of all signals */ } OtOTPLcBroadcast; static_assert(OT_OTP_LC_BROADCAST_COUNT < 8 * sizeof(uint16_t), @@ -1682,7 +1683,9 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) unsigned sig = ctz16(bcast->signal); uint16_t bit = 1u << (unsigned)sig; bcast->signal &= ~bit; - bool level = (bool)(bcast->level & bit); + bcast->current_level = + (bcast->current_level & ~bit) | (bcast->level & bit); + bool level = (bool)(bcast->current_level & bit); trace_ot_otp_lc_broadcast(s->ot_id, sig, level); @@ -1725,8 +1728,7 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) } break; case OT_OTP_LC_SEED_HW_RD_EN: - qemu_log_mask(LOG_UNIMP, "%s: %s: seed HW read is ignored\n", - __func__, s->ot_id); + /* nothing to do here, SEED_HW_RD_EN flag is in current_level */ break; default: error_setg(&error_fatal, "%s: %s: unexpected LC broadcast %d\n", @@ -4099,6 +4101,7 @@ static void ot_otp_dj_reset_enter(Object *obj, ResetType type) s->alert_bm = 0u; + s->lc_broadcast.current_level = 0u; s->lc_broadcast.level = 0u; s->lc_broadcast.signal = 0u; From 263bdc8091c5d5ac4025644b4557f7555e0a53a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:05:59 +0100 Subject: [PATCH 128/175] [ot] hw/opentitan: ot_otp_dj: fix creator/owner LC broadcast handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_otp_dj.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 2c241f7357b5b..4a196872e5562 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1713,17 +1713,17 @@ static void ot_otp_dj_lc_broadcast_bh(void *opaque) break; case OT_OTP_LC_CREATOR_SEED_SW_RW_EN: for (unsigned ix = 0; ix < OTP_PART_COUNT; ix++) { - if (OtOTPPartDescs[OTP_PART_SECRET2].iskeymgr_creator) { - s->partctrls[OTP_PART_SECRET2].read_lock = !level; - s->partctrls[OTP_PART_SECRET2].write_lock = !level; + if (OtOTPPartDescs[ix].iskeymgr_creator) { + s->partctrls[ix].read_lock = !level; + s->partctrls[ix].write_lock = !level; } } break; case OT_OTP_LC_OWNER_SEED_SW_RW_EN: for (unsigned ix = 0; ix < OTP_PART_COUNT; ix++) { - if (OtOTPPartDescs[OTP_PART_SECRET2].iskeymgr_owner) { - s->partctrls[OTP_PART_SECRET2].read_lock = !level; - s->partctrls[OTP_PART_SECRET2].write_lock = !level; + if (OtOTPPartDescs[ix].iskeymgr_owner) { + s->partctrls[ix].read_lock = !level; + s->partctrls[ix].write_lock = !level; } } break; From ae66c976fdfbe959eb39e4f8f05b237a67d3a0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:06:37 +0100 Subject: [PATCH 129/175] [ot] hw/opentitan: ot_otp: add function to retrieve KeyMgr secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_otp_dj.c | 49 +++++++++++++++++++++++++++++++++++ include/hw/opentitan/ot_otp.h | 25 ++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 4a196872e5562..12562a960e812 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -3359,6 +3359,54 @@ static void ot_otp_dj_get_otp_key(OtOTPState *s, OtOTPKeyType type, } } +static void ot_otp_dj_get_keymgr_secret( + OtOTPState *s, OtOTPKeyMgrSecretType type, OtOTPKeyMgrSecret *secret) +{ + OtOTPDjState *ds = OT_OTP_DJ(s); + int partition; + size_t offset; + + switch (type) { + case OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE0: + partition = OTP_PART_SECRET2; + offset = A_SECRET2_CREATOR_ROOT_KEY_SHARE0 - + OtOTPPartDescs[partition].offset; + break; + case OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1: + partition = OTP_PART_SECRET2; + offset = A_SECRET2_CREATOR_ROOT_KEY_SHARE1 - + OtOTPPartDescs[partition].offset; + break; + case OTP_KEYMGR_SECRET_CREATOR_SEED: + partition = OTP_PART_SECRET2; + offset = A_SECRET2_CREATOR_SEED - OtOTPPartDescs[partition].offset; + break; + case OTP_KEYMGR_SECRET_OWNER_SEED: + partition = OTP_PART_SECRET3; + offset = A_SECRET3_OWNER_SEED - OtOTPPartDescs[partition].offset; + break; + default: + error_report("%s: %s: invalid OTP keymgr secret type: %d", __func__, + ds->ot_id, type); + secret->valid = false; + memset(secret->secret, 0, OT_OTP_KEYMGR_SECRET_SIZE); + return; + } + + g_assert(ot_otp_dj_is_buffered(partition)); + + const uint8_t *data_ptr; + if (ds->lc_broadcast.current_level & BIT(OT_OTP_LC_SEED_HW_RD_EN)) { + data_ptr = (const uint8_t *)ds->partctrls[partition].buffer.data; + } else { + /* source data from PartInvDefault instead of real buffer */ + data_ptr = ds->inv_default_parts[partition]; + } + + secret->valid = ot_otp_dj_get_buffered_part_digest(ds, partition) != 0; + memcpy(secret->secret, &data_ptr[offset], OT_OTP_KEYMGR_SECRET_SIZE); +} + static bool ot_otp_dj_program_req(OtOTPState *s, const uint16_t *lc_tcount, const uint16_t *lc_state, ot_otp_program_ack_fn ack, void *opaque) @@ -4261,6 +4309,7 @@ static void ot_otp_dj_class_init(ObjectClass *klass, void *data) oc->get_hw_cfg = &ot_otp_dj_get_hw_cfg; oc->get_entropy_cfg = &ot_otp_dj_get_entropy_cfg; oc->get_otp_key = &ot_otp_dj_get_otp_key; + oc->get_keymgr_secret = &ot_otp_dj_get_keymgr_secret; oc->program_req = &ot_otp_dj_program_req; ot_otp_dj_class_add_inv_def_props(oc); diff --git a/include/hw/opentitan/ot_otp.h b/include/hw/opentitan/ot_otp.h index e5f87dab9dba1..7378376f72cc4 100644 --- a/include/hw/opentitan/ot_otp.h +++ b/include/hw/opentitan/ot_otp.h @@ -109,6 +109,21 @@ typedef struct { bool seed_valid; /* whether the seed is valid */ } OtOTPKey; +typedef enum { + OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE0, + OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1, + OTP_KEYMGR_SECRET_CREATOR_SEED, + OTP_KEYMGR_SECRET_OWNER_SEED, + OTP_KEYMGR_SECRET_COUNT +} OtOTPKeyMgrSecretType; + +#define OT_OTP_KEYMGR_SECRET_SIZE 32u /* 256 bits (for both keys and seeds) */ + +typedef struct { + uint8_t secret[OT_OTP_KEYMGR_SECRET_SIZE]; /* key/seed data */ + bool valid; /* whether the key/seed data is valid */ +} OtOTPKeyMgrSecret; + struct OtOTPState { SysBusDevice parent_obj; }; @@ -160,6 +175,16 @@ struct OtOTPClass { */ void (*get_otp_key)(OtOTPState *s, OtOTPKeyType type, OtOTPKey *key); + /* + * Retrieve Key Manager secret (key or seeds). + * + * @s the OTP device + * @type the type of secret to retrieve + * @secret the key manager secret record to update + */ + void (*get_keymgr_secret)(OtOTPState *s, OtOTPKeyMgrSecretType type, + OtOTPKeyMgrSecret *secret); + /** * Request the OTP to program the state, transition count pair. * OTP only accepts one request at a time. If another program request is From 41e5a2a98aa9e51ae3cbee6d7a6a0c6536ac1977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:08:28 +0100 Subject: [PATCH 130/175] [ot] hw/opentitan: ot_lc_ctrl: add function to retrieve KeyMgr div values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_lc_ctrl.c | 15 ++++++++++----- include/hw/opentitan/ot_lc_ctrl.h | 13 +++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index d8cffd499e397..5c751bff1025f 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -416,11 +416,6 @@ struct OtLcCtrlState { bool socdbg; /* whether this instance use SoCDbg state */ }; -struct OtLcCtrlClass { - SysBusDeviceClass parent_class; - ResettablePhases parent_phases; -}; - /* try to cope with the many ways to encode a transition matrix */ typedef enum { LC_TR_MODE_HISTORICAL, /* what prevailed in EarlGrey */ @@ -2132,6 +2127,14 @@ static void ot_lc_ctrl_configure_km_div(OtLcCtrlState *s) } } +static void ot_lc_ctrl_get_keymgr_div(const OtLcCtrlState *s, + OtLcCtrlKeyMgrDiv *div) +{ + g_assert(div); + + memcpy(&div->data[0], s->km_divs[s->km_div_type], OT_LC_KEYMGR_DIV_BYTES); +} + static Property ot_lc_ctrl_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtLcCtrlState, ot_id), DEFINE_PROP_LINK("otp_ctrl", OtLcCtrlState, otp_ctrl, TYPE_OT_OTP, @@ -2355,6 +2358,8 @@ static void ot_lc_ctrl_class_init(ObjectClass *klass, void *data) OtLcCtrlClass *lc = OT_LC_CTRL_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_lc_ctrl_reset_enter, NULL, NULL, &lc->parent_phases); + + lc->get_keymgr_div = &ot_lc_ctrl_get_keymgr_div; } static const TypeInfo ot_lc_ctrl_info = { diff --git a/include/hw/opentitan/ot_lc_ctrl.h b/include/hw/opentitan/ot_lc_ctrl.h index 7b632d511adbf..976f302a2cbe0 100644 --- a/include/hw/opentitan/ot_lc_ctrl.h +++ b/include/hw/opentitan/ot_lc_ctrl.h @@ -65,4 +65,17 @@ typedef struct { uint8_t data[OT_LC_KEYMGR_DIV_BYTES]; } OtLcCtrlKeyMgrDiv; +struct OtLcCtrlClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; + + /* + * Retrieve key manager diversification value. + * + * @div a pointer to a structure that will be filled with the key manager + * diversification data. + */ + void (*get_keymgr_div)(const OtLcCtrlState *s, OtLcCtrlKeyMgrDiv *div); +}; + #endif /* HW_OPENTITAN_OT_LC_CTRL_H */ From c6a4d26d0b1ab1ec82e42e454033f3df2882efab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:22:24 +0100 Subject: [PATCH 131/175] [ot] hw/opentitan: ot_kmac: fix some clang-tidy errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_kmac.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index a8934ea53ae6b..0ba6a92749475 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -652,11 +652,11 @@ static void ot_kmac_process(void *opaque) case OT_KMAC_MODE_SHA3: sha3_done(&s->ltc_state, &s->keccak_state[0]); break; - /* NOLINTNEXTLINE */ case OT_KMAC_MODE_SHAKE: sha3_shake_done(&s->ltc_state, &s->keccak_state[0], ot_kmac_get_keccak_rate_bytes(cfg->strength)); break; + /* NOLINTNEXTLINE(bugprone-branch-clone) */ case OT_KMAC_MODE_CSHAKE: case OT_KMAC_MODE_KMAC: sha3_cshake_done(&s->ltc_state, &s->keccak_state[0], @@ -1450,7 +1450,7 @@ static void ot_kmac_msgfifo_write(void *opaque, hwaddr addr, uint64_t value, trace_ot_kmac_msgfifo_write(s->ot_id, (uint32_t)addr, (uint32_t)value, size, pc); - /* trigger error if an app is running of not in MSG_FEED state */ + /* trigger error if an app is running or not in MSG_FEED state */ if (s->current_app || s->state != KMAC_ST_MSG_FEED) { /* info field mux_sel=1 (SW) or 2 (App) */ ot_kmac_report_error(s, OT_KMAC_ERR_SW_PUSHED_MSG_FIFO, @@ -1480,6 +1480,18 @@ static void ot_kmac_msgfifo_write(void *opaque, hwaddr addr, uint64_t value, ot_kmac_trigger_deferred_bh(s); } +static bool ot_kmac_compare_app_cfg(const OtKMACAppCfg *cfg1, + const OtKMACAppCfg *cfg2) +{ + return cfg1->mode == cfg2->mode && cfg1->strength == cfg2->strength && + cfg1->prefix.funcname_len == cfg2->prefix.funcname_len && + cfg1->prefix.customstr_len == cfg2->prefix.customstr_len && + memcmp(cfg1->prefix.funcname, cfg2->prefix.funcname, + cfg1->prefix.funcname_len) == 0 && + memcmp(cfg1->prefix.customstr, cfg2->prefix.customstr, + cfg1->prefix.customstr_len) == 0; +} + static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, const OtKMACAppCfg *cfg, OtKmacResponse fn, void *opaque) @@ -1489,9 +1501,8 @@ static void ot_kmac_connect_app(OtKMACState *s, unsigned app_idx, OtKMACApp *app = &s->apps[app_idx]; if (app->connected) { - /* NOLINTNEXTLINE */ - if (memcmp(&app->cfg, cfg, sizeof(OtKMACAppCfg)) == 0 && - fn == app->fn && opaque == app->opaque) { + if (ot_kmac_compare_app_cfg(&app->cfg, cfg) && fn == app->fn && + opaque == app->opaque) { /* * silently ignore duplicate connection from the same component with * the same parameters. From 5b63f5491faf538bdeb3ae4ed24f70162a509a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:24:11 +0100 Subject: [PATCH 132/175] [ot] hw/opentitan: ot_kmac: add traces for unimplemented entropy config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_kmac.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index 0ba6a92749475..d391911209281 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -1206,6 +1206,37 @@ static void ot_kmac_regs_write(void *opaque, hwaddr addr, uint64_t value, break; } + /* check for unimplemented config bits */ + if (val32 & R_CFG_SHADOWED_ENTROPY_MODE_MASK) { + qemu_log_mask( + LOG_UNIMP, + "%s: %s: CFG_SHADOWED.ENTROPY_MODE is not supported\n", + __func__, s->ot_id); + } + if (val32 & R_CFG_SHADOWED_ENTROPY_FAST_PROCESS_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: CFG_SHADOWED.ENTROPY_FAST_PROCESS is not " + "supported\n", + __func__, s->ot_id); + } + if (val32 & R_CFG_SHADOWED_MSG_MASK_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: CFG_SHADOWED.MSG_MASK is not supported\n", + __func__, s->ot_id); + } + if (val32 & R_CFG_SHADOWED_ENTROPY_READY_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: CFG_SHADOWED.ENTROPY_READY is not " + "supported\n", + __func__, s->ot_id); + } + if (val32 & R_CFG_SHADOWED_EN_UNSUPPORTED_MODESTRENGTH_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: CFG_SHADOWED.EN_UNSUPPORTED_MODESTRENGTH is " + "not supported\n", + __func__, s->ot_id); + } + val32 &= CFG_MASK; switch (ot_shadow_reg_write(&s->cfg, val32)) { case OT_SHADOW_REG_STAGED: From 8eb252123a438ab82c9d9bf95e88fa2cbc54e579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:26:36 +0100 Subject: [PATCH 133/175] [ot] hw/opentitan: ot_keymgr_dpe: add initial implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/Kconfig | 5 + hw/opentitan/meson.build | 1 + hw/opentitan/ot_keymgr_dpe.c | 2097 ++++++++++++++++++++++++++ hw/opentitan/trace-events | 21 + include/hw/opentitan/ot_keymgr_dpe.h | 84 ++ 5 files changed, 2208 insertions(+) create mode 100644 hw/opentitan/ot_keymgr_dpe.c create mode 100644 include/hw/opentitan/ot_keymgr_dpe.h diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index fcb912bdb55d3..3a3b5eb79999e 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -74,6 +74,11 @@ config OT_IBEX_WRAPPER select OT_VMAPPER bool +config OT_KEYMGR_DPE + select OT_LC_CTRL + select OT_ROM_CTRL + bool + config OT_KMAC bool diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build index 7094bc49ee9f3..53b9144db8bd5 100644 --- a/hw/opentitan/meson.build +++ b/hw/opentitan/meson.build @@ -25,6 +25,7 @@ system_ss.add(when: 'CONFIG_OT_GPIO_EG', if_true: files('ot_gpio_eg.c')) system_ss.add(when: 'CONFIG_OT_HMAC', if_true: [files('ot_hmac.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_I2C_DJ', if_true: files('ot_i2c_dj.c')) system_ss.add(when: 'CONFIG_OT_IBEX_WRAPPER', if_true: files('ot_ibex_wrapper.c')) +system_ss.add(when: 'CONFIG_OT_KEYMGR_DPE', if_true: files('ot_keymgr_dpe.c')) system_ss.add(when: 'CONFIG_OT_KMAC', if_true: [files('ot_kmac.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_LC_CTRL', if_true: files('ot_lc_ctrl.c')) system_ss.add(when: 'CONFIG_OT_MBX', if_true: files('ot_mbx.c')) diff --git a/hw/opentitan/ot_keymgr_dpe.c b/hw/opentitan/ot_keymgr_dpe.c new file mode 100644 index 0000000000000..ce42754147d2e --- /dev/null +++ b/hw/opentitan/ot_keymgr_dpe.c @@ -0,0 +1,2097 @@ +/* + * QEMU OpenTitan Key Manager DPE device + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Loïc Lefort + * Samuel Ortiz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include +#include +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_common.h" +#include "hw/opentitan/ot_edn.h" +#include "hw/opentitan/ot_keymgr_dpe.h" +#include "hw/opentitan/ot_kmac.h" +#include "hw/opentitan/ot_lc_ctrl.h" +#include "hw/opentitan/ot_otp.h" +#include "hw/opentitan/ot_prng.h" +#include "hw/opentitan/ot_rom_ctrl.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "hw/riscv/ibex_common.h" +#include "hw/riscv/ibex_irq.h" +#include "trace.h" + +#undef OT_KEYMGR_DPE_DEBUG + +#define NUM_SALT_REG 8u +#define NUM_SW_BINDING_REG 8u +#define NUM_ROM_DIGEST_INPUTS 2u +#define NUM_BOOT_STAGES 4u +#define NUM_SLOTS 4u + +#define KEYMGR_DPE_KEY_WIDTH 256u +#define KEYMGR_DPE_SW_BINDING_WIDTH ((NUM_SW_BINDING_REG) * 32u) +#define KEYMGR_DPE_SALT_WITDH ((NUM_SALT_REG) * 32u) +#define KEYMGR_DPE_HEALTH_STATE_WIDTH 128u +#define KEYMGR_DPE_DEV_ID_WIDTH 256u + +#define KEYMGR_DPE_ADV_DATA_BYTES \ + ((KEYMGR_DPE_SW_BINDING_WIDTH + KEYMGR_DPE_KEY_WIDTH + \ + (1u + (NUM_ROM_DIGEST_INPUTS)) * KEYMGR_DPE_KEY_WIDTH + \ + KEYMGR_DPE_DEV_ID_WIDTH + KEYMGR_DPE_HEALTH_STATE_WIDTH) / \ + 8u) + +/* key version + salt + key ID + constant */ +#define KEYMGR_DPE_GEN_DATA_BYTES \ + ((32u + KEYMGR_DPE_SALT_WITDH + KEYMGR_DPE_KEY_WIDTH * 2u) / 8u) + +#define KEYMGR_DPE_SEED_BYTES (KEYMGR_DPE_KEY_WIDTH / 8u) +#define KEYMGR_DPE_KDF_BUFFER_BYTES (1984u / 8u) + +static_assert(KEYMGR_DPE_ADV_DATA_BYTES <= KEYMGR_DPE_KDF_BUFFER_BYTES, + "KeyMgr ADV data does not fit in KDF buffer"); +static_assert(KEYMGR_DPE_GEN_DATA_BYTES <= KEYMGR_DPE_KDF_BUFFER_BYTES, + "KeyMgr GEN data does not fit in KDF buffer"); +static_assert((KEYMGR_DPE_KDF_BUFFER_BYTES % OT_KMAC_APP_MSG_BYTES) == 0u, + "KeyMgr KDF buffer not a multiple of KMAC message size"); +static_assert(OT_KEYMGR_DPE_KEY_BYTES <= OT_KMAC_APP_DIGEST_BYTES, + "KeyMgr key size does not match KMAC digest size"); +/* NOLINTNEXTLINE(misc-redundant-expression) */ +static_assert(OT_KEYMGR_DPE_OTBN_KEY_BYTES <= OT_KMAC_APP_DIGEST_BYTES, + "KeyMgr OTBN key size does not match KMAC digest size"); +static_assert(OT_KEYMGR_DPE_KEY_BYTES == OT_OTP_KEYMGR_SECRET_SIZE, + "KeyMgr key size does not match OTP KeyMgr secret size"); + +/* clang-format off */ +REG32(INTR_STATE, 0x0u) + SHARED_FIELD(INTR_OP_DONE, 0u, 1u) +REG32(INTR_ENABLE, 0x4u) +REG32(INTR_TEST, 0x8u) +REG32(ALERT_TEST, 0xcu) + FIELD(ALERT_TEST, RECOV_OPERATION, 0u, 1u) + FIELD(ALERT_TEST, FATAL_FAULT, 1u, 1u) +REG32(CFG_REGWEN, 0x10u) + FIELD(CFG_REGWEN, EN, 0u, 1u) +REG32(START, 0x14u) + FIELD(START, EN, 0u, 1u) +REG32(CONTROL_SHADOWED, 0x18u) + FIELD(CONTROL_SHADOWED, OPERATION, 4u, 3u) + FIELD(CONTROL_SHADOWED, DEST_SEL, 12u, 2u) + FIELD(CONTROL_SHADOWED, SLOT_SRC_SEL, 14u, 2u) + FIELD(CONTROL_SHADOWED, SLOT_DST_SEL, 18u, 2u) +REG32(SIDELOAD_CLEAR, 0x1cu) + FIELD(SIDELOAD_CLEAR, VAL, 0u, 3u) +REG32(RESEED_INTERVAL_REGWEN, 0x20u) + FIELD(RESEED_INTERVAL_REGWEN, EN, 0u, 1u) +REG32(RESEED_INTERVAL_SHADOWED, 0x24u) + FIELD(RESEED_INTERVAL_SHADOWED, VAL, 0u, 16u) +REG32(SLOT_POLICY_REGWEN, 0x28u) + FIELD(SLOT_POLICY_REGWEN, EN, 0u, 1u) +REG32(SLOT_POLICY, 0x2cu) + FIELD(SLOT_POLICY, ALLOW_CHILD, 0u, 1u) + FIELD(SLOT_POLICY, EXPORTABLE, 1u, 1u) + FIELD(SLOT_POLICY, RETAIN_PARENT, 2u, 1u) +REG32(SW_BINDING_REGWEN, 0x30u) + FIELD(SW_BINDING_REGWEN, EN, 0u, 1u) +REG32(SW_BINDING_0, 0x34u) +REG32(SW_BINDING_1, 0x38u) +REG32(SW_BINDING_2, 0x3cu) +REG32(SW_BINDING_3, 0x40u) +REG32(SW_BINDING_4, 0x44u) +REG32(SW_BINDING_5, 0x48u) +REG32(SW_BINDING_6, 0x4cu) +REG32(SW_BINDING_7, 0x50u) +REG32(SALT_0, 0x54u) +REG32(SALT_1, 0x58u) +REG32(SALT_2, 0x5cu) +REG32(SALT_3, 0x60u) +REG32(SALT_4, 0x64u) +REG32(SALT_5, 0x68u) +REG32(SALT_6, 0x6cu) +REG32(SALT_7, 0x70u) +REG32(KEY_VERSION, 0x74u) +REG32(MAX_KEY_VER_REGWEN, 0x78u) + FIELD(MAX_KEY_VER_REGWEN, EN, 0u, 1u) +REG32(MAX_KEY_VER_SHADOWED, 0x7cu) +REG32(SW_SHARE0_OUTPUT_0, 0x80u) +REG32(SW_SHARE0_OUTPUT_1, 0x84u) +REG32(SW_SHARE0_OUTPUT_2, 0x88u) +REG32(SW_SHARE0_OUTPUT_3, 0x8cu) +REG32(SW_SHARE0_OUTPUT_4, 0x90u) +REG32(SW_SHARE0_OUTPUT_5, 0x94u) +REG32(SW_SHARE0_OUTPUT_6, 0x98u) +REG32(SW_SHARE0_OUTPUT_7, 0x9cu) +REG32(SW_SHARE1_OUTPUT_0, 0xa0u) +REG32(SW_SHARE1_OUTPUT_1, 0xa4u) +REG32(SW_SHARE1_OUTPUT_2, 0xa8u) +REG32(SW_SHARE1_OUTPUT_3, 0xacu) +REG32(SW_SHARE1_OUTPUT_4, 0xb0u) +REG32(SW_SHARE1_OUTPUT_5, 0xb4u) +REG32(SW_SHARE1_OUTPUT_6, 0xb8u) +REG32(SW_SHARE1_OUTPUT_7, 0xbcu) +REG32(WORKING_STATE, 0xc0u) + FIELD(WORKING_STATE, VAL, 0u, 2u) +REG32(OP_STATUS, 0xc4u) + FIELD(OP_STATUS, VAL, 0u, 2u) +REG32(ERR_CODE, 0xc8u) + FIELD(ERR_CODE, INVALID_OP, 0u, 1u) + FIELD(ERR_CODE, INVALID_KMAC_INPUT, 1u, 1u) + FIELD(ERR_CODE, INVALID_SHADOW_UPDATE, 2u, 1u) +REG32(FAULT_STATUS, 0xccu) + FIELD(FAULT_STATUS, CMD, 0u, 1u) + FIELD(FAULT_STATUS, KMAC_FSM, 1u, 1u) + FIELD(FAULT_STATUS, KMAC_DONE, 2u, 1u) + FIELD(FAULT_STATUS, KMAC_OP, 3u, 1u) + FIELD(FAULT_STATUS, KMAC_OUT, 4u, 1u) + FIELD(FAULT_STATUS, REGFILE_INTG, 5u, 1u) + FIELD(FAULT_STATUS, SHADOW, 6u, 1u) + FIELD(FAULT_STATUS, CTRL_FSM_INTG, 7u, 1u) + FIELD(FAULT_STATUS, CTRL_FSM_CHK, 8u, 1u) + FIELD(FAULT_STATUS, CTRL_FSM_CNT, 9u, 1u) + FIELD(FAULT_STATUS, RESEED_CNT, 10u, 1u) + FIELD(FAULT_STATUS, SIDE_CTRL_FSM, 11u, 1u) + FIELD(FAULT_STATUS, SIDE_CTRL_SEL, 12u, 1u) + FIELD(FAULT_STATUS, KEY_ECC, 13u, 1u) +REG32(DEBUG, 0xd0u) + FIELD(DEBUG, INVALID_CREATOR_SEED, 0u, 1u) + FIELD(DEBUG, INVALID_OWNER_SEED, 1u, 1u) + FIELD(DEBUG, INVALID_DEV_ID, 2u, 1u) + FIELD(DEBUG, INVALID_HEALTH_STATE, 3u, 1u) + FIELD(DEBUG, INVALID_KEY_VERSION, 4u, 1u) + FIELD(DEBUG, INVALID_KEY, 5u, 1u) + FIELD(DEBUG, INVALID_DIGEST, 6u, 1u) + FIELD(DEBUG, INVALID_ROOT_KEY, 7u, 1u) + FIELD(DEBUG, INACTIVE_LC_EN, 8u, 1u) +/* clang-format on */ + +#define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) + +#define R_LAST_REG (R_DEBUG) +#define REGS_COUNT (R_LAST_REG + 1u) +#define REGS_SIZE (REGS_COUNT * sizeof(uint32_t)) + +#define INTR_MASK (INTR_OP_DONE_MASK) +#define ALERT_MASK \ + (R_ALERT_TEST_RECOV_OPERATION_MASK | R_ALERT_TEST_FATAL_FAULT_MASK) + +#define R_CONTROL_SHADOWED_MASK \ + (R_CONTROL_SHADOWED_OPERATION_MASK | R_CONTROL_SHADOWED_DEST_SEL_MASK | \ + R_CONTROL_SHADOWED_SLOT_SRC_SEL_MASK | \ + R_CONTROL_SHADOWED_SLOT_DST_SEL_MASK) + +#define ERR_CODE_MASK \ + (R_ERR_CODE_INVALID_OP_MASK | R_ERR_CODE_INVALID_KMAC_INPUT_MASK | \ + R_ERR_CODE_INVALID_SHADOW_UPDATE_MASK) + +#define FAULT_STATUS_MASK \ + (R_FAULT_STATUS_CMD_MASK | R_FAULT_STATUS_KMAC_FSM_MASK | \ + R_FAULT_STATUS_KMAC_DONE_MASK | R_FAULT_STATUS_KMAC_OP_MASK | \ + R_FAULT_STATUS_KMAC_OUT_MASK | R_FAULT_STATUS_REGFILE_INTG_MASK | \ + R_FAULT_STATUS_SHADOW_MASK | R_FAULT_STATUS_CTRL_FSM_INTG_MASK | \ + R_FAULT_STATUS_CTRL_FSM_CHK_MASK | R_FAULT_STATUS_CTRL_FSM_CNT_MASK | \ + R_FAULT_STATUS_RESEED_CNT_MASK | R_FAULT_STATUS_SIDE_CTRL_FSM_MASK | \ + R_FAULT_STATUS_SIDE_CTRL_SEL_MASK | R_FAULT_STATUS_KEY_ECC_MASK) + +#define DEBUG_MASK \ + (R_DEBUG_INVALID_CREATOR_SEED_MASK | R_DEBUG_INVALID_OWNER_SEED_MASK | \ + R_DEBUG_INVALID_DEV_ID_MASK | R_DEBUG_INVALID_HEALTH_STATE_MASK | \ + R_DEBUG_INVALID_KEY_VERSION_MASK | R_DEBUG_INVALID_KEY_MASK | \ + R_DEBUG_INVALID_DIGEST_MASK | R_DEBUG_INVALID_ROOT_KEY_MASK | \ + R_DEBUG_INACTIVE_LC_EN_MASK) + +#define KEYMGR_DPE_KEY_WIDTH 256u +#define KEYMGR_DPE_OTBN_KEY_WIDTH 384u + +#define KEYMGR_DPE_LFSR_WIDTH 64u +#define KEYMGR_DPE_ENTROPY_WIDTH (KEYMGR_DPE_LFSR_WIDTH / 2u) +#define KEYMGR_DPE_ENTROPY_ROUNDS \ + (KEYMGR_DPE_KEY_WIDTH / KEYMGR_DPE_ENTROPY_WIDTH) + +#define KEYMGR_DPE_RESEED_COUNT \ + (KEYMGR_DPE_LFSR_WIDTH / (8u * sizeof(uint32_t))) + +/* values for CONTROL_SHADOWED.OPERATION */ +typedef enum { + KEYMGR_DPE_OP_ADVANCE = 0, + KEYMGR_DPE_OP_ERASE_SLOT = 1, + KEYMGR_DPE_OP_GENERATE_SW_OUTPUT = 2, + KEYMGR_DPE_OP_GENERATE_HW_OUTPUT = 3, + KEYMGR_DPE_OP_DISABLE = 4, +} OtKeyMgrOperation; + +/* values for CONTROL_SHADOWED.DEST_SEL */ +typedef enum { + KEYMGR_DPE_DEST_SEL_VALUE_NONE = 0, + KEYMGR_DPE_DEST_SEL_VALUE_AES = 1, + KEYMGR_DPE_DEST_SEL_VALUE_KMAC = 2, + KEYMGR_DPE_DEST_SEL_VALUE_OTBN = 3, +} OtKeyMgrDpeDestSel; + +/* values for SIDELOAD_CLEAR.VAL */ +typedef enum { + KEYMGR_DPE_SIDELOAD_CLEAR_NONE = 0, + KEYMGR_DPE_SIDELOAD_CLEAR_AES = 1, + KEYMGR_DPE_SIDELOAD_CLEAR_KMAC = 2, + KEYMGR_DPE_SIDELOAD_CLEAR_OTBN = 3, +} OtKeyMgrDpeSideloadClear; + +/* values for WORKING_STATE.STATE */ +typedef enum { + KEYMGR_DPE_WORKING_STATE_RESET = 0, + KEYMGR_DPE_WORKING_STATE_AVAILABLE = 1, + KEYMGR_DPE_WORKING_STATE_DISABLED = 2, + KEYMGR_DPE_WORKING_STATE_INVALID = 3, +} OtKeyMgrDpeWorkingState; + +/* value for OP_STATUS.STATUS */ +typedef enum { + KEYMGR_DPE_OP_STATUS_IDLE = 0, + KEYMGR_DPE_OP_STATUS_WIP = 1, + KEYMGR_DPE_OP_STATUS_DONE_SUCCESS = 2, + KEYMGR_DPE_OP_STATUS_DONE_ERROR = 3, +} OtKeyMgrOpStatus; + +enum { + /* clang-format off */ + ALERT_RECOVERABLE, + ALERT_FATAL, + ALERT_COUNT + /* clang-format on */ +}; + +enum { + KEYMGR_DPE_SEED_LFSR, + KEYMGR_DPE_SEED_REV, + KEYMGR_DPE_SEED_SW_OUT, + KEYMGR_DPE_SEED_HW_OUT, + KEYMGR_DPE_SEED_AES, + KEYMGR_DPE_SEED_KMAC, + KEYMGR_DPE_SEED_OTBN, + KEYMGR_DPE_SEED_NONE, + KEYMGR_DPE_SEED_COUNT, +}; + +typedef enum { + KEYMGR_DPE_ST_RESET, + KEYMGR_DPE_ST_ENTROPY_RESEED, + KEYMGR_DPE_ST_RANDOM, + KEYMGR_DPE_ST_ROOTKEY, + KEYMGR_DPE_ST_AVAILABLE, + KEYMGR_DPE_ST_WIPE, + KEYMGR_DPE_ST_DISABLING, + KEYMGR_DPE_ST_DISABLED, + KEYMGR_DPE_ST_INVALID, +} OtKeyMgrDpeFSMState; + +typedef struct { + OtEDNState *device; + uint8_t ep; + bool connected; + bool scheduled; +} OtKeyMgrDpeEDN; + +typedef struct { + OtPrngState *state; + bool reseed_req; + bool reseed_ack; + uint8_t reseed_cnt; +} OtKeyMgrDpePrng; + +typedef struct { + bool allow_child; + bool exportable; + bool retain_parent; +} OtKeyMgrDpeSlotPolicy; + +typedef struct { + OtKeyMgrDpeKey key; /* always 256 bit keys */ + uint32_t max_key_version; + uint8_t boot_stage; + OtKeyMgrDpeSlotPolicy policy; + bool valid; +} OtKeyMgrDpeSlot; + +typedef struct { + OtKeyMgrDpeKey aes; + OtKeyMgrDpeKey kmac; + OtKeyMgrDpeOtbnKey otbn; + OtKeyMgrDpeKey sw; +} OtKeyMgrDpeOutKeys; + +typedef struct { + bool op_req; + bool op_ack; +} OtKeyMgrOpState; + +typedef struct { + uint8_t *data; + unsigned offset; /* current read offset (in bytes) */ + unsigned length; /* current length (in bytes) */ +} OtKeyMgrDpeKdfBuffer; + +typedef struct OtKeyMgrDpeState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + IbexIRQ irq; + IbexIRQ alerts[ALERT_COUNT]; + QEMUBH *fsm_tick_bh; + OtKeyMgrDpeKdfBuffer kdf_buf; + + /* entries for shadowed regs, salt and sw_binding are not used */ + uint32_t regs[REGS_COUNT]; + OtShadowReg control; + OtShadowReg reseed_interval; + OtShadowReg max_key_ver; + uint8_t *salt; + uint8_t *sw_binding; + + bool enabled; + OtKeyMgrDpeFSMState state; + OtKeyMgrDpePrng prng; + OtKeyMgrOpState op_state; + uint8_t *seeds[KEYMGR_DPE_SEED_COUNT]; + + /* key slots */ + OtKeyMgrDpeSlot *key_slots; + + /* output keys */ + OtKeyMgrDpeOutKeys *out_keys; + + /* properties */ + char *ot_id; + OtKeyMgrDpeEDN edn; + OtKMACState *kmac; + uint8_t kmac_app; + OtLcCtrlState *lc_ctrl; + OtOTPState *otp; + OtRomCtrlState *rom_ctrl[NUM_ROM_DIGEST_INPUTS]; + char *seed_xstrs[KEYMGR_DPE_SEED_COUNT]; +} OtKeyMgrDpeState; + +static const OtKeyMgrDpeSlotPolicy DEFAULT_UDS_POLICY = { + .allow_child = true, + .exportable = false, + .retain_parent = false, +}; + +static const OtKMACAppCfg KMAC_APP_CFG = OT_KMAC_CONFIG(KMAC, 256u, "KMAC", ""); + +#define REG_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) +static const char *REG_NAMES[REGS_COUNT] = { + REG_ENTRY(INTR_STATE), + REG_ENTRY(INTR_ENABLE), + REG_ENTRY(INTR_TEST), + REG_ENTRY(ALERT_TEST), + REG_ENTRY(CFG_REGWEN), + REG_ENTRY(START), + REG_ENTRY(CONTROL_SHADOWED), + REG_ENTRY(SIDELOAD_CLEAR), + REG_ENTRY(RESEED_INTERVAL_REGWEN), + REG_ENTRY(RESEED_INTERVAL_SHADOWED), + REG_ENTRY(SLOT_POLICY_REGWEN), + REG_ENTRY(SLOT_POLICY), + REG_ENTRY(SW_BINDING_REGWEN), + REG_ENTRY(SW_BINDING_0), + REG_ENTRY(SW_BINDING_1), + REG_ENTRY(SW_BINDING_2), + REG_ENTRY(SW_BINDING_3), + REG_ENTRY(SW_BINDING_4), + REG_ENTRY(SW_BINDING_5), + REG_ENTRY(SW_BINDING_6), + REG_ENTRY(SW_BINDING_7), + REG_ENTRY(SALT_0), + REG_ENTRY(SALT_1), + REG_ENTRY(SALT_2), + REG_ENTRY(SALT_3), + REG_ENTRY(SALT_4), + REG_ENTRY(SALT_5), + REG_ENTRY(SALT_6), + REG_ENTRY(SALT_7), + REG_ENTRY(KEY_VERSION), + REG_ENTRY(MAX_KEY_VER_REGWEN), + REG_ENTRY(MAX_KEY_VER_SHADOWED), + REG_ENTRY(SW_SHARE0_OUTPUT_0), + REG_ENTRY(SW_SHARE0_OUTPUT_1), + REG_ENTRY(SW_SHARE0_OUTPUT_2), + REG_ENTRY(SW_SHARE0_OUTPUT_3), + REG_ENTRY(SW_SHARE0_OUTPUT_4), + REG_ENTRY(SW_SHARE0_OUTPUT_5), + REG_ENTRY(SW_SHARE0_OUTPUT_6), + REG_ENTRY(SW_SHARE0_OUTPUT_7), + REG_ENTRY(SW_SHARE1_OUTPUT_0), + REG_ENTRY(SW_SHARE1_OUTPUT_1), + REG_ENTRY(SW_SHARE1_OUTPUT_2), + REG_ENTRY(SW_SHARE1_OUTPUT_3), + REG_ENTRY(SW_SHARE1_OUTPUT_4), + REG_ENTRY(SW_SHARE1_OUTPUT_5), + REG_ENTRY(SW_SHARE1_OUTPUT_6), + REG_ENTRY(SW_SHARE1_OUTPUT_7), + REG_ENTRY(WORKING_STATE), + REG_ENTRY(OP_STATUS), + REG_ENTRY(ERR_CODE), + REG_ENTRY(FAULT_STATUS), + REG_ENTRY(DEBUG), +}; +#undef REG_ENTRY +#define REG_NAME(_reg_) \ + ((((_reg_) <= REGS_COUNT) && REG_NAMES[_reg_]) ? REG_NAMES[_reg_] : "?") + +#define OP_ENTRY(_op_) [KEYMGR_DPE_OP_##_op_] = stringify(_op_) +static const char *OP_NAMES[] = { + OP_ENTRY(ADVANCE), + OP_ENTRY(ERASE_SLOT), + OP_ENTRY(GENERATE_SW_OUTPUT), + OP_ENTRY(GENERATE_HW_OUTPUT), + OP_ENTRY(DISABLE), +}; +#undef OP_ENTRY +#define OP_NAME(_op_) \ + ((_op_) >= 0 && (_op_) < ARRAY_SIZE(OP_NAMES) ? OP_NAMES[(_op_)] : "?") + +#define SIDELOAD_CLEAR_ENTRY(_st_) \ + [KEYMGR_DPE_SIDELOAD_CLEAR_##_st_] = stringify(_st_) +static const char *SIDELOAD_CLEAR_NAMES[] = { + SIDELOAD_CLEAR_ENTRY(NONE), + SIDELOAD_CLEAR_ENTRY(AES), + SIDELOAD_CLEAR_ENTRY(KMAC), + SIDELOAD_CLEAR_ENTRY(OTBN), +}; +#undef SIDELOAD_CLEAR_ENTRY +#define SIDELOAD_CLEAR_NAME(_st_) \ + ((_st_) >= 0 && (_st_) < ARRAY_SIZE(SIDELOAD_CLEAR_NAMES) ? \ + SIDELOAD_CLEAR_NAMES[(_st_)] : \ + "?") + +#define WORKING_STATE_ENTRY(_st_) \ + [KEYMGR_DPE_WORKING_STATE_##_st_] = stringify(_st_) +static const char *WORKING_STATE_NAMES[] = { + WORKING_STATE_ENTRY(RESET), + WORKING_STATE_ENTRY(AVAILABLE), + WORKING_STATE_ENTRY(DISABLED), + WORKING_STATE_ENTRY(INVALID), +}; +#undef WORKING_STATE_ENTRY + +#define WORKING_STATE_NAME(_st_) \ + ((_st_) >= 0 && (_st_) < ARRAY_SIZE(WORKING_STATE_NAMES) ? \ + WORKING_STATE_NAMES[(_st_)] : \ + "?") + +#define OP_STATUS_ENTRY(_st_) [KEYMGR_DPE_OP_STATUS_##_st_] = stringify(_st_) +static const char *OP_STATUS_NAMES[] = { + OP_STATUS_ENTRY(IDLE), + OP_STATUS_ENTRY(WIP), + OP_STATUS_ENTRY(DONE_SUCCESS), + OP_STATUS_ENTRY(DONE_ERROR), +}; +#undef OP_STATUS_ENTRY +#define OP_STATUS_NAME(_st_) \ + ((_st_) >= 0 && (_st_) < ARRAY_SIZE(OP_STATUS_NAMES) ? \ + OP_STATUS_NAMES[(_st_)] : \ + "?") + +#define FST_ENTRY(_st_) [KEYMGR_DPE_ST_##_st_] = stringify(_st_) +static const char *FST_NAMES[] = { + /* clang-format off */ + FST_ENTRY(RESET), + FST_ENTRY(ENTROPY_RESEED), + FST_ENTRY(RANDOM), + FST_ENTRY(ROOTKEY), + FST_ENTRY(AVAILABLE), + FST_ENTRY(WIPE), + FST_ENTRY(DISABLING), + FST_ENTRY(DISABLED), + FST_ENTRY(INVALID), + /* clang-format on */ +}; +#undef FST_ENTRY +#define FST_NAME(_st_) \ + ((_st_) >= 0 && (_st_) < ARRAY_SIZE(FST_NAMES) ? FST_NAMES[(_st_)] : "?") + +#ifdef OT_KEYMGR_DPE_DEBUG +#define TRACE_KEYMGR_DPE(_s_, _msg_, ...) \ + qemu_log("%s: %s: " _msg_ "\n", __func__, (_s_)->ot_id, ##__VA_ARGS__); +#else +#define TRACE_KEYMGR_DPE(_s_, _msg_, ...) +#endif + +#ifdef OT_KEYMGR_DPE_DEBUG +static char hexbuf[256u]; +static const char *ot_keymgr_dpe_dump_bigint(const void *data, size_t size) +{ + static const char _hex[] = "0123456789abcdef"; + const uint8_t *buf = (const uint8_t *)data; + + if (size > ((sizeof(hexbuf) / 2u) - 2u)) { + size = sizeof(hexbuf) / 2u - 2u; + } + + char *hexstr = hexbuf; + for (size_t ix = 0u; ix < size; ix++) { + hexstr[(ix * 2u)] = _hex[(buf[size - 1u - ix] >> 4u) & 0xfu]; + hexstr[(ix * 2u) + 1u] = _hex[buf[size - 1u - ix] & 0xfu]; + } + hexstr[size * 2u] = '\0'; + return hexbuf; +} +#endif + +static void ot_keymgr_dpe_dump_kdf_buf(OtKeyMgrDpeState *s) +{ +#ifdef OT_KEYMGR_DPE_DEBUG + size_t msgs = (s->kdf_buf.length + OT_KMAC_APP_MSG_BYTES - 1u) / + OT_KMAC_APP_MSG_BYTES; + for (size_t ix = 0u; ix < msgs; ix++) { + TRACE_KEYMGR_DPE(s, "kdf_buf[%lu]: %s", ix, + ot_keymgr_dpe_dump_bigint( + &s->kdf_buf.data[ix * OT_KMAC_APP_MSG_BYTES], + OT_KMAC_APP_MSG_BYTES)); + } +#endif +} + +#define ot_keymgr_dpe_schedule_fsm(_s_) \ + ot_keymgr_dpe_xschedule_fsm(_s_, __func__, __LINE__) + +static void +ot_keymgr_dpe_xschedule_fsm(OtKeyMgrDpeState *s, const char *func, int line) +{ + trace_ot_keymgr_dpe_schedule_fsm(s->ot_id, func, line); + qemu_bh_schedule(s->fsm_tick_bh); +} + +static void ot_keymgr_dpe_update_irq(OtKeyMgrDpeState *s) +{ + bool level = (bool)(s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE]); + trace_ot_keymgr_dpe_irq(s->ot_id, s->regs[R_INTR_STATE], + s->regs[R_INTR_ENABLE], level); + ibex_irq_set(&s->irq, (int)level); +} + +static void ot_keymgr_dpe_update_alert(OtKeyMgrDpeState *s) +{ + uint32_t level = s->regs[R_ALERT_TEST]; + + if (s->regs[R_FAULT_STATUS] & FAULT_STATUS_MASK) { + level |= 1u << ALERT_FATAL; + } + if (s->regs[R_ERR_CODE] & ERR_CODE_MASK) { + level |= 1u << ALERT_RECOVERABLE; + } + + for (unsigned ix = 0u; ix < ARRAY_SIZE(s->alerts); ix++) { + ibex_irq_set(&s->alerts[ix], (int)((level >> ix) & 0x1u)); + } +} + +static OtKeyMgrDpeWorkingState +ot_keymgr_dpe_get_working_state(const OtKeyMgrDpeState *s) +{ + switch (FIELD_EX32(s->regs[R_WORKING_STATE], WORKING_STATE, VAL)) { + case KEYMGR_DPE_WORKING_STATE_RESET: + return KEYMGR_DPE_WORKING_STATE_RESET; + case KEYMGR_DPE_WORKING_STATE_AVAILABLE: + return KEYMGR_DPE_WORKING_STATE_AVAILABLE; + case KEYMGR_DPE_WORKING_STATE_DISABLED: + return KEYMGR_DPE_WORKING_STATE_DISABLED; + default: + return KEYMGR_DPE_WORKING_STATE_INVALID; + } +} + +#define ot_keymgr_dpe_change_working_state(_s_, _working_state_) \ + ot_keymgr_dpe_xchange_working_state(_s_, _working_state_, __LINE__) + +static void ot_keymgr_dpe_xchange_working_state( + OtKeyMgrDpeState *s, OtKeyMgrDpeWorkingState working_state, int line) +{ + OtKeyMgrDpeWorkingState prev_working_state = + ot_keymgr_dpe_get_working_state(s); + + if (prev_working_state != working_state) { + trace_ot_keymgr_dpe_change_working_state(s->ot_id, line, + WORKING_STATE_NAME( + prev_working_state), + prev_working_state, + WORKING_STATE_NAME( + working_state), + working_state); + s->regs[R_WORKING_STATE] = working_state; + } +} + +static OtKeyMgrOpStatus ot_keymgr_dpe_get_op_status(const OtKeyMgrDpeState *s) +{ + switch (FIELD_EX32(s->regs[R_OP_STATUS], OP_STATUS, VAL)) { + case KEYMGR_DPE_OP_STATUS_IDLE: + return KEYMGR_DPE_OP_STATUS_IDLE; + case KEYMGR_DPE_OP_STATUS_WIP: + return KEYMGR_DPE_OP_STATUS_WIP; + case KEYMGR_DPE_OP_STATUS_DONE_SUCCESS: + return KEYMGR_DPE_OP_STATUS_DONE_SUCCESS; + case KEYMGR_DPE_OP_STATUS_DONE_ERROR: + return KEYMGR_DPE_OP_STATUS_DONE_ERROR; + default: + g_assert_not_reached(); + } +} + +#define ot_keymgr_dpe_change_op_status(_s_, _op_status_) \ + ot_keymgr_dpe_xchange_op_status(_s_, _op_status_, __LINE__) + +static void ot_keymgr_dpe_xchange_op_status( + OtKeyMgrDpeState *s, OtKeyMgrOpStatus op_status, int line) +{ + OtKeyMgrOpStatus prev_op_status = ot_keymgr_dpe_get_op_status(s); + if (prev_op_status != op_status) { + trace_ot_keymgr_dpe_change_op_status(s->ot_id, line, + OP_STATUS_NAME(prev_op_status), + prev_op_status, + OP_STATUS_NAME(op_status), + op_status); + s->regs[R_OP_STATUS] = + FIELD_DP32(s->regs[R_OP_STATUS], OP_STATUS, VAL, op_status); + } +} + +static void ot_keymgr_dpe_request_entropy(OtKeyMgrDpeState *s); + +static void ot_keymgr_dpe_push_entropy(void *opaque, uint32_t bits, bool fips) +{ + (void)fips; + OtKeyMgrDpeState *s = opaque; + OtKeyMgrDpeEDN *edn = &s->edn; + OtKeyMgrDpePrng *prng = &s->prng; + + if (!edn->scheduled) { + trace_ot_keymgr_dpe_error(s->ot_id, "Unexpected entropy"); + return; + } + edn->scheduled = false; + + ot_prng_reseed(prng->state, bits); + prng->reseed_cnt++; + + bool resched = prng->reseed_cnt < KEYMGR_DPE_RESEED_COUNT; + + trace_ot_keymgr_dpe_entropy(s->ot_id, prng->reseed_cnt, resched); + + if (resched) { + /* We need more entropy */ + ot_keymgr_dpe_request_entropy(s); + } else { + if (prng->reseed_req) { + prng->reseed_ack = true; + prng->reseed_cnt = 0u; + ot_keymgr_dpe_schedule_fsm(s); + } + } +} + +static void ot_keymgr_dpe_request_entropy(OtKeyMgrDpeState *s) +{ + OtKeyMgrDpeEDN *edn = &s->edn; + + if (!edn->connected) { + ot_edn_connect_endpoint(edn->device, edn->ep, + &ot_keymgr_dpe_push_entropy, s); + edn->connected = true; + } + + if (!edn->scheduled) { + edn->scheduled = s->prng.reseed_req; + if (!edn->scheduled) { + return; + } + if (ot_edn_request_entropy(edn->device, edn->ep)) { + error_setg(&error_fatal, + "%s: %s: keymgr_dpe failed to request entropy", __func__, + s->ot_id); + } + } +} + +static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) +{ + uint32_t len = s->kdf_buf.offset; + + g_assert(s->kdf_buf.length); + g_assert(s->kdf_buf.offset < s->kdf_buf.length); + + unsigned msg_len = s->kdf_buf.length - s->kdf_buf.offset; + if (msg_len > OT_KMAC_APP_MSG_BYTES) { + msg_len = OT_KMAC_APP_MSG_BYTES; + } + + unsigned offset = s->kdf_buf.offset; + s->kdf_buf.offset += msg_len; + + OtKMACAppReq req = { + .last = s->kdf_buf.offset == s->kdf_buf.length, + .msg_len = msg_len, + }; + memcpy(req.msg_data, &s->kdf_buf.data[offset], msg_len); + + TRACE_KEYMGR_DPE(s, "KMAC req: %s last:%d", + ot_keymgr_dpe_dump_bigint(req.msg_data, req.msg_len), + req.last); + + trace_ot_keymgr_dpe_kmac_req(s->ot_id, len, req.msg_len, req.last); + + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->app_request(s->kmac, s->kmac_app, &req); +} + +static void ot_keymgr_dpe_handle_kmac_resp_advance( + OtKeyMgrDpeState *s, uint8_t slot_src_sel, uint8_t slot_dst_sel, + const OtKMACAppRsp *rsp) +{ + uint32_t max_key_version = ot_shadow_reg_peek(&s->max_key_ver); + uint32_t slot_policy = s->regs[R_SLOT_POLICY]; + OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; + OtKeyMgrDpeSlot *dst_slot = &s->key_slots[slot_dst_sel]; + + dst_slot->valid = true; + memcpy(dst_slot->key.share0, rsp->digest_share0, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(dst_slot->key.share1, rsp->digest_share1, OT_KEYMGR_DPE_KEY_BYTES); + dst_slot->max_key_version = max_key_version; + dst_slot->boot_stage = src_slot->boot_stage + 1u; + dst_slot->policy.allow_child = + (bool)(slot_policy & R_SLOT_POLICY_ALLOW_CHILD_MASK); + dst_slot->policy.exportable = + (bool)(slot_policy & R_SLOT_POLICY_EXPORTABLE_MASK); + dst_slot->policy.retain_parent = + (bool)(slot_policy & R_SLOT_POLICY_RETAIN_PARENT_MASK); + + /* + * Unlock `SW_BINDING`, `SLOT_POLICY` and `MAX_KEY_VERSION` registers + * after succesful advance + */ + s->regs[R_SW_BINDING_REGWEN] |= R_SW_BINDING_REGWEN_EN_MASK; + s->regs[R_SLOT_POLICY_REGWEN] |= R_SLOT_POLICY_REGWEN_EN_MASK; + s->regs[R_MAX_KEY_VER_REGWEN] |= R_MAX_KEY_VER_REGWEN_EN_MASK; +} + +static void ot_keymgr_dpe_handle_kmac_resp_gen_hw_out( + OtKeyMgrDpeState *s, uint8_t dest_sel, const OtKMACAppRsp *rsp) +{ + switch (dest_sel) { + case KEYMGR_DPE_DEST_SEL_VALUE_AES: + memcpy(s->out_keys->aes.share0, rsp->digest_share0, + OT_KEYMGR_DPE_KEY_BYTES); + memcpy(s->out_keys->aes.share1, rsp->digest_share1, + OT_KEYMGR_DPE_KEY_BYTES); + s->out_keys->aes.valid = true; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_KMAC: + memcpy(s->out_keys->kmac.share0, rsp->digest_share0, + OT_KEYMGR_DPE_KEY_BYTES); + memcpy(s->out_keys->kmac.share1, rsp->digest_share1, + OT_KEYMGR_DPE_KEY_BYTES); + s->out_keys->kmac.valid = true; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_OTBN: + memcpy(s->out_keys->otbn.share0, rsp->digest_share0, + OT_KEYMGR_DPE_OTBN_KEY_BYTES); + memcpy(s->out_keys->otbn.share1, rsp->digest_share1, + OT_KEYMGR_DPE_OTBN_KEY_BYTES); + s->out_keys->otbn.valid = true; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_NONE: + /* no output, just ack the operation */ + break; + default: + g_assert_not_reached(); + } +} + +static void ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(OtKeyMgrDpeState *s, + const OtKMACAppRsp *rsp) +{ + memcpy(s->out_keys->sw.share0, rsp->digest_share0, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(s->out_keys->sw.share1, rsp->digest_share1, OT_KEYMGR_DPE_KEY_BYTES); + s->out_keys->sw.valid = true; +} + +static void +ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) +{ + OtKeyMgrDpeState *s = OT_KEYMGR_DPE(opaque); + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + trace_ot_keymgr_dpe_kmac_rsp(s->ot_id, rsp->done); + + if (!rsp->done) { + /* not last response from KMAC, send more data */ + ot_keymgr_dpe_send_kmac_req(s); + return; + } + + g_assert(s->kdf_buf.offset == s->kdf_buf.length); + +#ifdef OT_KEYMGR_DPE_DEBUG + uint8_t key[OT_KMAC_APP_DIGEST_BYTES]; + for (unsigned ix = 0u; ix < OT_KMAC_APP_DIGEST_BYTES; ix++) { + key[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; + } + TRACE_KEYMGR_DPE(s, "KMAC resp: %s", + ot_keymgr_dpe_dump_bigint(key, OT_KMAC_APP_DIGEST_BYTES)); +#endif + + switch (FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION)) { + case KEYMGR_DPE_OP_ADVANCE: { + uint8_t slot_src_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); + uint8_t slot_dst_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_DST_SEL); + ot_keymgr_dpe_handle_kmac_resp_advance(s, slot_src_sel, slot_dst_sel, + rsp); + break; + } + case KEYMGR_DPE_OP_GENERATE_HW_OUTPUT: { + uint8_t dest_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, DEST_SEL); + ot_keymgr_dpe_handle_kmac_resp_gen_hw_out(s, dest_sel, rsp); + break; + } + case KEYMGR_DPE_OP_GENERATE_SW_OUTPUT: + ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(s, rsp); + break; + default: + g_assert_not_reached(); + } + + /* operation complete */ + s->op_state.op_ack = true; + ot_keymgr_dpe_schedule_fsm(s); +} + +/* check that 'data' is not all zeros or all ones */ +static bool ot_keymgr_dpe_valid_data_check(const uint8_t *data, size_t len) +{ + size_t popcount = 0u; + for (unsigned ix = 0u; ix < len; ix++) { + popcount += __builtin_popcount(data[ix]); + } + return (popcount && popcount != (len * BITS_PER_BYTE)); +} + +static void ot_keymgr_reset_kdf_buffer(OtKeyMgrDpeState *s) +{ + memset(s->kdf_buf.data, 0u, KEYMGR_DPE_KDF_BUFFER_BYTES); + s->kdf_buf.offset = 0u; + s->kdf_buf.length = 0u; +} + +static void ot_keymgr_dpe_kdf_push_bytes(OtKeyMgrDpeState *s, + const uint8_t *data, size_t len) +{ + g_assert(s->kdf_buf.length + len <= KEYMGR_DPE_KDF_BUFFER_BYTES); + + memcpy(&s->kdf_buf.data[s->kdf_buf.length], data, len); + s->kdf_buf.length += len; +} + +static void ot_keymgr_dpe_kdf_push_key_version(OtKeyMgrDpeState *s) +{ + uint8_t buf[sizeof(uint32_t)]; + stl_le_p(buf, s->regs[R_KEY_VERSION]); + ot_keymgr_dpe_kdf_push_bytes(s, buf, sizeof(uint32_t)); + + TRACE_KEYMGR_DPE(s, "Key Version: %s", + ot_keymgr_dpe_dump_bigint(buf, sizeof(uint32_t))); +} + +static size_t +ot_keymgr_dpe_kdf_append_creator_seed(OtKeyMgrDpeState *s, bool *dvalid) +{ + OtOTPKeyMgrSecret secret = { 0u }; + + OtOTPClass *otp_oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); + g_assert(otp_oc); + otp_oc->get_keymgr_secret(s->otp, OTP_KEYMGR_SECRET_CREATOR_SEED, &secret); + + ot_keymgr_dpe_kdf_push_bytes(s, secret.secret, OT_OTP_KEYMGR_SECRET_SIZE); + *dvalid &= ot_keymgr_dpe_valid_data_check(secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE); + + TRACE_KEYMGR_DPE(s, "Creator Seed: %s", + ot_keymgr_dpe_dump_bigint(secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + return OT_OTP_KEYMGR_SECRET_SIZE; +} + +static size_t ot_keymgr_dpe_kdf_append_rom_digest( + OtKeyMgrDpeState *s, unsigned rom_idx, bool *dvalid) +{ + uint8_t rom_digest[OT_ROM_DIGEST_BYTES] = { 0u }; + + OtRomCtrlClass *rcc = OT_ROM_CTRL_GET_CLASS(s->rom_ctrl[rom_idx]); + rcc->get_rom_digest(s->rom_ctrl[rom_idx], rom_digest); + + ot_keymgr_dpe_kdf_push_bytes(s, rom_digest, OT_ROM_DIGEST_BYTES); + *dvalid &= ot_keymgr_dpe_valid_data_check(rom_digest, OT_ROM_DIGEST_BYTES); + TRACE_KEYMGR_DPE(s, "ROM%u digest: %s", rom_idx, + ot_keymgr_dpe_dump_bigint(&rom_digest[rom_idx], + OT_ROM_DIGEST_BYTES)); + + return OT_ROM_DIGEST_BYTES; +} + +static size_t ot_keymgr_dpe_kdf_append_km_div(OtKeyMgrDpeState *s, bool *dvalid) +{ + OtLcCtrlKeyMgrDiv km_div = { 0u }; + + OtLcCtrlClass *lc = OT_LC_CTRL_GET_CLASS(s->lc_ctrl); + lc->get_keymgr_div(s->lc_ctrl, &km_div); + + ot_keymgr_dpe_kdf_push_bytes(s, km_div.data, OT_LC_KEYMGR_DIV_BYTES); + *dvalid &= + ot_keymgr_dpe_valid_data_check(km_div.data, OT_LC_KEYMGR_DIV_BYTES); + + TRACE_KEYMGR_DPE(s, "KeyManager div: %s", + ot_keymgr_dpe_dump_bigint(km_div.data, + OT_LC_KEYMGR_DIV_BYTES)); + return OT_LC_KEYMGR_DIV_BYTES; +} + +static size_t ot_keymgr_dpe_kdf_append_dev_id(OtKeyMgrDpeState *s, bool *dvalid) +{ + OtOTPClass *otp_oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); + const OtOTPHWCfg *hw_cfg = otp_oc->get_hw_cfg(s->otp); + + ot_keymgr_dpe_kdf_push_bytes(s, hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); + *dvalid &= ot_keymgr_dpe_valid_data_check(hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); + + TRACE_KEYMGR_DPE(s, "Device ID: %s", + ot_keymgr_dpe_dump_bigint(hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES)); + return OT_OTP_HWCFG_DEVICE_ID_BYTES; +} + +static size_t ot_keymgr_dpe_kdf_append_rev_seed(OtKeyMgrDpeState *s) +{ + ot_keymgr_dpe_kdf_push_bytes(s, s->seeds[KEYMGR_DPE_SEED_REV], + KEYMGR_DPE_SEED_BYTES); + + TRACE_KEYMGR_DPE(s, "Revision Seed: %s", + ot_keymgr_dpe_dump_bigint(s->seeds[KEYMGR_DPE_SEED_REV], + KEYMGR_DPE_SEED_BYTES)); + + return KEYMGR_DPE_SEED_BYTES; +} + +static size_t +ot_keymgr_dpe_kdf_append_owner_seed(OtKeyMgrDpeState *s, bool *dvalid) +{ + OtOTPKeyMgrSecret secret = { 0u }; + + OtOTPClass *otp_oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); + otp_oc->get_keymgr_secret(s->otp, OTP_KEYMGR_SECRET_OWNER_SEED, &secret); + + ot_keymgr_dpe_kdf_push_bytes(s, secret.secret, OT_OTP_KEYMGR_SECRET_SIZE); + *dvalid &= ot_keymgr_dpe_valid_data_check(secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE); + + TRACE_KEYMGR_DPE(s, "Owner Seed: %s", + ot_keymgr_dpe_dump_bigint(secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + return OT_OTP_KEYMGR_SECRET_SIZE; +} + +static void ot_keymgr_dpe_operation_advance(OtKeyMgrDpeState *s) +{ + bool dvalid = true; + + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t slot_dst_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_DST_SEL); + uint8_t slot_src_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); + OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; + OtKeyMgrDpeSlot *dst_slot = &s->key_slots[slot_dst_sel]; + + bool invalid_allow_child = !src_slot->policy.allow_child; + bool invalid_max_boot_stage = + src_slot->boot_stage >= (NUM_BOOT_STAGES - 1u); + bool invalid_src_slot = !src_slot->valid; + bool invalid_retain_parent = + src_slot->policy.retain_parent ? + (slot_src_sel == slot_dst_sel || dst_slot->valid) : + (slot_src_sel != slot_dst_sel); + + /* TODO trigger error */ + (void)(invalid_allow_child || invalid_max_boot_stage || invalid_src_slot || + invalid_retain_parent); + + ot_keymgr_reset_kdf_buffer(s); + + size_t expected_kdf_len = 0u; + + switch (src_slot->boot_stage) { + /* Creator */ + case 0u: { + /* Creator Seed (OTP) */ + expected_kdf_len += ot_keymgr_dpe_kdf_append_creator_seed(s, &dvalid); + + /* ROM digests (ROM_CTRL) */ + for (unsigned ix = 0u; ix < NUM_ROM_DIGEST_INPUTS; ix++) { + expected_kdf_len += + ot_keymgr_dpe_kdf_append_rom_digest(s, ix, &dvalid); + } + + /* KeyManager diversification (LC_CTRL) */ + expected_kdf_len += ot_keymgr_dpe_kdf_append_km_div(s, &dvalid); + + /* Device ID (OTP) */ + expected_kdf_len += ot_keymgr_dpe_kdf_append_dev_id(s, &dvalid); + + /* Revision Seed (netlist constant) */ + expected_kdf_len += ot_keymgr_dpe_kdf_append_rev_seed(s); + + break; + } + /* OwnerInt */ + case 1u: { + /* Owner Seed (OTP) */ + expected_kdf_len += ot_keymgr_dpe_kdf_append_owner_seed(s, &dvalid); + + break; + } + default: + break; + } + + /* Software Binding (software-provided via SW_BINDING_x registers) */ + ot_keymgr_dpe_kdf_push_bytes(s, s->sw_binding, + NUM_SW_BINDING_REG * sizeof(uint32_t)); + expected_kdf_len += NUM_SW_BINDING_REG * sizeof(uint32_t); + TRACE_KEYMGR_DPE(s, "Software Binding: %s", + ot_keymgr_dpe_dump_bigint(s->sw_binding, + NUM_SW_BINDING_REG * + sizeof(uint32_t))); + + /* check that we have pushed all expected KDF data*/ + g_assert(s->kdf_buf.length == expected_kdf_len); + + (void)dvalid; /* TODO use this */ + + g_assert(s->kdf_buf.length <= KEYMGR_DPE_ADV_DATA_BYTES); + s->kdf_buf.length = KEYMGR_DPE_ADV_DATA_BYTES; + + ot_keymgr_dpe_dump_kdf_buf(s); + ot_keymgr_dpe_send_kmac_req(s); +} + +static void ot_keymgr_dpe_operation_erase_slot(OtKeyMgrDpeState *s) +{ + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t slot_dst_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_DST_SEL); + OtKeyMgrDpeSlot *dst_slot = &s->key_slots[slot_dst_sel]; + + bool invalid_erase = !dst_slot->valid; + + /* TODO trigger error */ + (void)invalid_erase; + + memset(dst_slot, 0u, sizeof(OtKeyMgrDpeSlot)); + + s->op_state.op_ack = true; + ot_keymgr_dpe_schedule_fsm(s); +} + +static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) +{ + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t dest_sel = (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, DEST_SEL); + uint8_t slot_src_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); + OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; + + ot_keymgr_reset_kdf_buffer(s); + + if (src_slot->valid) { + /* Output Key Seed (SW/HW key) */ + uint8_t *output_key = sw ? s->seeds[KEYMGR_DPE_SEED_SW_OUT] : + s->seeds[KEYMGR_DPE_SEED_HW_OUT]; + ot_keymgr_dpe_kdf_push_bytes(s, output_key, KEYMGR_DPE_SEED_BYTES); + TRACE_KEYMGR_DPE(s, "Output Key Seed: %s", + ot_keymgr_dpe_dump_bigint(output_key, + KEYMGR_DPE_SEED_BYTES)); + + /* Destination Seed (AES/KMAC/OTBN/other) */ + uint8_t *dest_seed; + switch (dest_sel) { + case KEYMGR_DPE_DEST_SEL_VALUE_AES: + dest_seed = s->seeds[KEYMGR_DPE_SEED_AES]; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_KMAC: + dest_seed = s->seeds[KEYMGR_DPE_SEED_KMAC]; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_OTBN: + dest_seed = s->seeds[KEYMGR_DPE_SEED_OTBN]; + break; + case KEYMGR_DPE_DEST_SEL_VALUE_NONE: + default: + dest_seed = s->seeds[KEYMGR_DPE_SEED_NONE]; + break; + } + ot_keymgr_dpe_kdf_push_bytes(s, dest_seed, KEYMGR_DPE_SEED_BYTES); + TRACE_KEYMGR_DPE(s, "Destination Seed: %s", + ot_keymgr_dpe_dump_bigint(dest_seed, + KEYMGR_DPE_SEED_BYTES)); + + /* Salt (software-provided via SALT_x registers) */ + ot_keymgr_dpe_kdf_push_bytes(s, s->salt, + NUM_SALT_REG * sizeof(uint32_t)); + TRACE_KEYMGR_DPE(s, "Salt: %s", + ot_keymgr_dpe_dump_bigint(s->salt, + NUM_SALT_REG * + sizeof(uint32_t))); + + /* Key Version (software-provided via KEY_VERSION register) */ + ot_keymgr_dpe_kdf_push_key_version(s); + } else { + /* active key slot is not valid, push random data */ + unsigned count = KEYMGR_DPE_GEN_DATA_BYTES / sizeof(uint32_t); + while (count--) { + uint32_t data = ot_prng_random_u32(s->prng.state); + ot_keymgr_dpe_kdf_push_bytes(s, (uint8_t *)&data, sizeof(data)); + } + } + + bool key_version_valid = + s->regs[R_KEY_VERSION] <= src_slot->max_key_version; + + /* TODO trigger error */ + (void)key_version_valid; + + g_assert(s->kdf_buf.length == KEYMGR_DPE_GEN_DATA_BYTES); + + ot_keymgr_dpe_dump_kdf_buf(s); + ot_keymgr_dpe_send_kmac_req(s); +} + +static void ot_keymgr_dpe_operation_sw_output(OtKeyMgrDpeState *s) +{ + ot_keymgr_dpe_operation_gen_output(s, true); +} + +static void ot_keymgr_dpe_operation_hw_output(OtKeyMgrDpeState *s) +{ + ot_keymgr_dpe_operation_gen_output(s, false); +} + +static void ot_keymgr_dpe_operation_disable(OtKeyMgrDpeState *s) +{ + /* TODO implement */ + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + s->op_state.op_ack = true; + ot_keymgr_dpe_schedule_fsm(s); +} + +static void ot_keymgr_dpe_start_operation(OtKeyMgrDpeState *s) +{ + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + int op = (int)FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); + + trace_ot_keymgr_dpe_operation(s->ot_id, OP_NAME(op), op); + + switch (op) { + case KEYMGR_DPE_OP_ADVANCE: + ot_keymgr_dpe_operation_advance(s); + break; + case KEYMGR_DPE_OP_ERASE_SLOT: + ot_keymgr_dpe_operation_erase_slot(s); + break; + case KEYMGR_DPE_OP_GENERATE_SW_OUTPUT: + ot_keymgr_dpe_operation_sw_output(s); + break; + case KEYMGR_DPE_OP_GENERATE_HW_OUTPUT: + ot_keymgr_dpe_operation_hw_output(s); + break; + case KEYMGR_DPE_OP_DISABLE: + ot_keymgr_dpe_operation_disable(s); + break; + default: + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + s->op_state.op_ack = true; + ot_keymgr_dpe_schedule_fsm(s); + break; + } +} + +static void ot_keymgr_dpe_sideload_clear(OtKeyMgrDpeState *s) +{ + int sideload_clear = + (int)FIELD_EX32(s->regs[R_SIDELOAD_CLEAR], SIDELOAD_CLEAR, VAL); + + trace_ot_keymgr_dpe_sideload_clear(s->ot_id, + SIDELOAD_CLEAR_NAME(sideload_clear), + sideload_clear); + + /* TODO this needs to use random data */ + + switch (sideload_clear) { + case KEYMGR_DPE_SIDELOAD_CLEAR_NONE: + /* nothing to clear, exit */ + return; + case KEYMGR_DPE_SIDELOAD_CLEAR_AES: + memset(&s->out_keys->aes, 0u, sizeof(OtKeyMgrDpeKey)); + return; + case KEYMGR_DPE_SIDELOAD_CLEAR_OTBN: + memset(&s->out_keys->otbn, 0u, sizeof(OtKeyMgrDpeOtbnKey)); + return; + case KEYMGR_DPE_SIDELOAD_CLEAR_KMAC: + memset(&s->out_keys->kmac, 0u, sizeof(OtKeyMgrDpeKey)); + return; + default: + /* + * "If the value programmed is not one of the enumerated values below, + * ALL sideload key slots are continuously cleared." + */ + memset(&s->out_keys->aes, 0u, sizeof(OtKeyMgrDpeKey)); + memset(&s->out_keys->otbn, 0u, sizeof(OtKeyMgrDpeOtbnKey)); + memset(&s->out_keys->kmac, 0u, sizeof(OtKeyMgrDpeKey)); + return; + } +} + +static void ot_keymgr_dpe_lc_signal(void *opaque, int irq, int level) +{ + OtKeyMgrDpeState *s = opaque; + bool enable_keymgr = (bool)level; + + g_assert(irq == 0); + + trace_ot_keymgr_dpe_lc_signal(s->ot_id, level); + + bool changed = enable_keymgr ^ s->enabled; + if (!changed) { + /* no change, exit */ + return; + } + + s->enabled = enable_keymgr; + + if (s->enabled) { + s->regs[R_DEBUG] &= ~R_DEBUG_INACTIVE_LC_EN_MASK; + } else { + s->regs[R_DEBUG] |= R_DEBUG_INACTIVE_LC_EN_MASK; + } + + ot_keymgr_dpe_schedule_fsm(s); +} + +#define ot_keymgr_dpe_change_main_fsm_state(_s_, _op_status_) \ + ot_keymgr_dpe_xchange_main_fsm_state(_s_, _op_status_, __LINE__) + +static void ot_keymgr_dpe_xchange_main_fsm_state( + OtKeyMgrDpeState *s, OtKeyMgrDpeFSMState state, int line) +{ + if (s->state != state) { + trace_ot_keymgr_dpe_change_main_fsm_state(s->ot_id, line, + FST_NAME(s->state), s->state, + FST_NAME(state), state); + s->state = state; + } +} + +static bool ot_keymgr_dpe_main_fsm_tick(OtKeyMgrDpeState *s) +{ + OtKeyMgrDpeFSMState state = s->state; + bool op_start = s->regs[R_START] & R_START_EN_MASK; + bool init = false; + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t slot_dst_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_DST_SEL); + OtKeyMgrDpeSlot *dst_slot = &s->key_slots[slot_dst_sel]; + + trace_ot_keymgr_dpe_main_fsm_tick(s->ot_id, FST_NAME(s->state), s->state); + + switch (s->state) { + case KEYMGR_DPE_ST_RESET: + ot_keymgr_dpe_change_working_state(s, KEYMGR_DPE_WORKING_STATE_RESET); + if (!op_start) { + break; + } + bool op_advance = FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION) == + KEYMGR_DPE_OP_ADVANCE; + if (!s->enabled || !op_advance) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + } else { + ot_keymgr_dpe_change_main_fsm_state(s, + KEYMGR_DPE_ST_ENTROPY_RESEED); + } + break; + case KEYMGR_DPE_ST_ENTROPY_RESEED: + ot_keymgr_dpe_change_working_state(s, KEYMGR_DPE_WORKING_STATE_RESET); + if (!s->enabled) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_INVALID); + } else if (!s->prng.reseed_req) { + s->prng.reseed_ack = false; + s->prng.reseed_req = true; + ot_keymgr_dpe_request_entropy(s); + } else if (s->prng.reseed_ack) { + s->prng.reseed_req = true; + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_RANDOM); + } + break; + case KEYMGR_DPE_ST_RANDOM: + ot_keymgr_dpe_change_working_state(s, KEYMGR_DPE_WORKING_STATE_RESET); + if (!s->enabled) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_INVALID); + } else { + /* current RTL only initializes slot 'slot_dst_sel' with 0s */ + memset(dst_slot, 0u, sizeof(OtKeyMgrDpeSlot)); + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_ROOTKEY); + } + break; + case KEYMGR_DPE_ST_ROOTKEY: + ot_keymgr_dpe_change_working_state(s, KEYMGR_DPE_WORKING_STATE_RESET); + if (!s->enabled) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_INVALID); + } else { + init = true; + + /* retrieve Creator Root Key from OTP */ + OtOTPKeyMgrSecret secret_share0 = { 0u }; + OtOTPKeyMgrSecret secret_share1 = { 0u }; + OtOTPClass *oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); + g_assert(oc); + oc->get_keymgr_secret(s->otp, + OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE0, + &secret_share0); + TRACE_KEYMGR_DPE( + s, "Creator Root Key share 0: %s", + ot_keymgr_dpe_dump_bigint(secret_share0.secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + oc->get_keymgr_secret(s->otp, + OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1, + &secret_share1); + TRACE_KEYMGR_DPE( + s, "Creator Root Key share 1: %s", + ot_keymgr_dpe_dump_bigint(secret_share1.secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + + if (secret_share0.valid && secret_share1.valid) { + memset(dst_slot, 0u, sizeof(OtKeyMgrDpeSlot)); + dst_slot->valid = true; + dst_slot->boot_stage = 0u; + memcpy(dst_slot->key.share0, secret_share0.secret, + OT_OTP_KEYMGR_SECRET_SIZE); + memcpy(dst_slot->key.share1, secret_share1.secret, + OT_OTP_KEYMGR_SECRET_SIZE); + dst_slot->max_key_version = ot_shadow_reg_peek(&s->max_key_ver); + dst_slot->policy = DEFAULT_UDS_POLICY; + + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_AVAILABLE); + } else { + s->regs[R_DEBUG] |= R_DEBUG_INVALID_ROOT_KEY_MASK; + + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_INVALID); + } + } + break; + case KEYMGR_DPE_ST_AVAILABLE: + ot_keymgr_dpe_change_working_state(s, + KEYMGR_DPE_WORKING_STATE_AVAILABLE); + if (!op_start) { + /* no state change if op_start is not set */ + break; + } + if (!s->enabled) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + /* + * Given that the root key was latched by an earlier FSM state, we + * need to take care of clearing the sensitive root key. + */ + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_WIPE); + } else if (!s->op_state.op_req) { + s->op_state.op_req = true; + ot_keymgr_dpe_start_operation(s); + } + break; + case KEYMGR_DPE_ST_WIPE: + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_DISABLING); + break; + case KEYMGR_DPE_ST_DISABLING: + /* TODO wipe before going to disabled state */ + ot_keymgr_dpe_change_main_fsm_state(s, KEYMGR_DPE_ST_DISABLED); + break; + case KEYMGR_DPE_ST_DISABLED: + ot_keymgr_dpe_change_working_state(s, + KEYMGR_DPE_WORKING_STATE_DISABLED); + break; + case KEYMGR_DPE_ST_INVALID: + default: + ot_keymgr_dpe_change_working_state(s, KEYMGR_DPE_WORKING_STATE_INVALID); + break; + } + + /* last requested operation status */ + bool op_done = + s->op_state.op_req ? + s->op_state.op_ack : + (init || (bool)(s->regs[R_ERR_CODE] & R_ERR_CODE_INVALID_OP_MASK)); + if (op_done) { + s->op_state.op_req = false; + s->op_state.op_ack = false; + s->regs[R_START] &= ~R_START_EN_MASK; + if (s->regs[R_ERR_CODE] | s->regs[R_FAULT_STATUS]) { + ot_keymgr_dpe_update_alert(s); + ot_keymgr_dpe_change_op_status(s, KEYMGR_DPE_OP_STATUS_DONE_ERROR); + } else { + ot_keymgr_dpe_change_op_status(s, + KEYMGR_DPE_OP_STATUS_DONE_SUCCESS); + } + } else if (op_start) { + ot_keymgr_dpe_change_op_status(s, KEYMGR_DPE_OP_STATUS_WIP); + } else { + ot_keymgr_dpe_change_op_status(s, KEYMGR_DPE_OP_STATUS_IDLE); + } + + /* CFG_REGWEN */ + if (op_start) { + if (op_done) { + s->regs[R_CFG_REGWEN] |= R_CFG_REGWEN_EN_MASK; + } else { + s->regs[R_CFG_REGWEN] &= ~R_CFG_REGWEN_EN_MASK; + } + } + + return state != s->state; +} + +static void ot_keymgr_dpe_fsm_tick(void *opaque) +{ + OtKeyMgrDpeState *s = opaque; + + if (ot_keymgr_dpe_main_fsm_tick(s)) { + /* schedule FSM update once more if its state has changed */ + ot_keymgr_dpe_schedule_fsm(s); + } else { + /* otherwise, go idle and wait for an external event */ + trace_ot_keymgr_dpe_go_idle(s->ot_id); + } +} + +#define ot_keymgr_dpe_check_reg_write(_s_, _reg_, _regwen_) \ + ot_keymgr_dpe_check_reg_write_func(__func__, _s_, _reg_, _regwen_) + +static inline bool ot_keymgr_dpe_check_reg_write_func( + const char *func, OtKeyMgrDpeState *s, hwaddr reg, hwaddr regwen) +{ + if (!s->regs[regwen]) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Write to %s protected with %s\n", + func, REG_NAME(reg), REG_NAME(regwen)); + return false; + } + + return true; +} + +static uint64_t ot_keymgr_dpe_read(void *opaque, hwaddr addr, unsigned size) +{ + OtKeyMgrDpeState *s = opaque; + (void)size; + + uint32_t val32; + + hwaddr reg = R32_OFF(addr); + + switch (reg) { + case R_INTR_STATE: + case R_INTR_ENABLE: + case R_CFG_REGWEN: + case R_START: + case R_SIDELOAD_CLEAR: + case R_RESEED_INTERVAL_REGWEN: + case R_SLOT_POLICY_REGWEN: + case R_SLOT_POLICY: + case R_SW_BINDING_REGWEN: + case R_KEY_VERSION: + case R_MAX_KEY_VER_REGWEN: + case R_WORKING_STATE: + case R_OP_STATUS: + case R_ERR_CODE: + case R_FAULT_STATUS: + case R_DEBUG: + val32 = s->regs[reg]; + break; + case R_CONTROL_SHADOWED: + val32 = ot_shadow_reg_read(&s->control); + break; + case R_RESEED_INTERVAL_SHADOWED: + val32 = ot_shadow_reg_read(&s->reseed_interval); + break; + case R_MAX_KEY_VER_SHADOWED: + val32 = ot_shadow_reg_read(&s->max_key_ver); + break; + case R_SW_BINDING_0: + case R_SW_BINDING_1: + case R_SW_BINDING_2: + case R_SW_BINDING_3: + case R_SW_BINDING_4: + case R_SW_BINDING_5: + case R_SW_BINDING_6: + case R_SW_BINDING_7: { + unsigned offset = (reg - R_SW_BINDING_0) * sizeof(uint32_t); + val32 = ldl_le_p(&s->sw_binding[offset]); + break; + } + case R_SALT_0: + case R_SALT_1: + case R_SALT_2: + case R_SALT_3: + case R_SALT_4: + case R_SALT_5: + case R_SALT_6: + case R_SALT_7: { + unsigned offset = (reg - R_SALT_0) * sizeof(uint32_t); + val32 = ldl_le_p(&s->salt[offset]); + break; + } + case R_SW_SHARE0_OUTPUT_0: + case R_SW_SHARE0_OUTPUT_1: + case R_SW_SHARE0_OUTPUT_2: + case R_SW_SHARE0_OUTPUT_3: + case R_SW_SHARE0_OUTPUT_4: + case R_SW_SHARE0_OUTPUT_5: + case R_SW_SHARE0_OUTPUT_6: + case R_SW_SHARE0_OUTPUT_7: { + /* TODO should this depend on the current state? */ + unsigned offset = (reg - R_SW_SHARE0_OUTPUT_0) * sizeof(uint32_t); + void *ptr = &s->out_keys->sw.share0[offset]; + val32 = ldl_le_p(ptr); + stl_le_p(ptr, 0u); /* RC */ + break; + } + case R_SW_SHARE1_OUTPUT_0: + case R_SW_SHARE1_OUTPUT_1: + case R_SW_SHARE1_OUTPUT_2: + case R_SW_SHARE1_OUTPUT_3: + case R_SW_SHARE1_OUTPUT_4: + case R_SW_SHARE1_OUTPUT_5: + case R_SW_SHARE1_OUTPUT_6: + case R_SW_SHARE1_OUTPUT_7: { + /* TODO should this depend on the current state? */ + unsigned offset = (reg - R_SW_SHARE1_OUTPUT_0) * sizeof(uint32_t); + void *ptr = &s->out_keys->sw.share1[offset]; + val32 = ldl_le_p(ptr); + stl_le_p(ptr, 0u); /* RC */ + break; + } + case R_INTR_TEST: + case R_ALERT_TEST: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); + val32 = 0u; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); + val32 = 0u; + break; + } + + uint64_t pc = ibex_get_current_pc(); + trace_ot_keymgr_dpe_io_read_out(s->ot_id, (unsigned)addr, REG_NAME(reg), + (uint64_t)val32, pc); + + return (uint64_t)val32; +}; + +static void ot_keymgr_dpe_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned size) +{ + OtKeyMgrDpeState *s = opaque; + (void)size; + uint32_t val32 = (uint32_t)val64; + + hwaddr reg = R32_OFF(addr); + + uint64_t pc = ibex_get_current_pc(); + trace_ot_keymgr_dpe_io_write(s->ot_id, (unsigned)addr, REG_NAME(reg), val64, + pc); + + switch (reg) { + case R_INTR_STATE: + val32 &= INTR_MASK; + s->regs[reg] &= ~val32; /* RW1C */ + ot_keymgr_dpe_update_irq(s); + break; + case R_INTR_ENABLE: + val32 &= INTR_MASK; + s->regs[reg] = val32; + ot_keymgr_dpe_update_irq(s); + break; + case R_INTR_TEST: + val32 &= INTR_MASK; + s->regs[R_INTR_STATE] |= val32; + ot_keymgr_dpe_update_irq(s); + break; + case R_ALERT_TEST: + val32 &= ALERT_MASK; + s->regs[R_ALERT_TEST] |= val32; + ot_keymgr_dpe_update_alert(s); + break; + case R_START: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_CFG_REGWEN)) { + break; + } + val32 &= R_START_EN_MASK; + s->regs[reg] = val32; + ot_keymgr_dpe_fsm_tick(s); + break; + case R_CONTROL_SHADOWED: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_CFG_REGWEN)) { + break; + } + val32 &= R_CONTROL_SHADOWED_MASK; + switch (ot_shadow_reg_write(&s->control, val32)) { + case OT_SHADOW_REG_STAGED: + case OT_SHADOW_REG_COMMITTED: { + break; + } + case OT_SHADOW_REG_ERROR: + default: + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_SHADOW_UPDATE_MASK; + ot_keymgr_dpe_update_alert(s); + } + break; + case R_SIDELOAD_CLEAR: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_CFG_REGWEN)) { + break; + } + val32 &= R_SIDELOAD_CLEAR_VAL_MASK; + s->regs[reg] = val32; + ot_keymgr_dpe_sideload_clear(s); + break; + case R_RESEED_INTERVAL_REGWEN: + val32 &= R_RESEED_INTERVAL_REGWEN_EN_MASK; + s->regs[reg] &= val32; /* RW0C */ + break; + case R_RESEED_INTERVAL_SHADOWED: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_RESEED_INTERVAL_REGWEN)) { + break; + } + val32 &= R_RESEED_INTERVAL_SHADOWED_VAL_MASK; + switch (ot_shadow_reg_write(&s->reseed_interval, val32)) { + case OT_SHADOW_REG_STAGED: + case OT_SHADOW_REG_COMMITTED: + break; + case OT_SHADOW_REG_ERROR: + default: + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_SHADOW_UPDATE_MASK; + ot_keymgr_dpe_update_alert(s); + } + break; + case R_SLOT_POLICY_REGWEN: + val32 &= R_SLOT_POLICY_REGWEN_EN_MASK; + s->regs[reg] &= val32; /* RW0C */ + break; + case R_SLOT_POLICY: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_SLOT_POLICY_REGWEN)) { + break; + } + s->regs[reg] = val32; + break; + case R_SW_BINDING_REGWEN: + val32 &= R_SW_BINDING_REGWEN_EN_MASK; + s->regs[reg] &= val32; /* RW0C */ + break; + case R_SW_BINDING_0: + case R_SW_BINDING_1: + case R_SW_BINDING_2: + case R_SW_BINDING_3: + case R_SW_BINDING_4: + case R_SW_BINDING_5: + case R_SW_BINDING_6: + case R_SW_BINDING_7: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_SW_BINDING_REGWEN)) { + break; + } + stl_le_p(&s->sw_binding[(reg - R_SW_BINDING_0) * sizeof(uint32_t)], + val32); + break; + case R_SALT_0: + case R_SALT_1: + case R_SALT_2: + case R_SALT_3: + case R_SALT_4: + case R_SALT_5: + case R_SALT_6: + case R_SALT_7: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_CFG_REGWEN)) { + break; + } + stl_le_p(&s->salt[(reg - R_SALT_0) * sizeof(uint32_t)], val32); + break; + case R_KEY_VERSION: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_CFG_REGWEN)) { + break; + } + /* TODO */ + s->regs[reg] = val32; + break; + case R_MAX_KEY_VER_REGWEN: + val32 &= R_MAX_KEY_VER_REGWEN_EN_MASK; + s->regs[reg] &= val32; /* RW0C */ + break; + case R_MAX_KEY_VER_SHADOWED: + if (!ot_keymgr_dpe_check_reg_write(s, reg, R_MAX_KEY_VER_REGWEN)) { + break; + } + switch (ot_shadow_reg_write(&s->max_key_ver, val32)) { + case OT_SHADOW_REG_STAGED: + case OT_SHADOW_REG_COMMITTED: + break; + case OT_SHADOW_REG_ERROR: + default: + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_SHADOW_UPDATE_MASK; + ot_keymgr_dpe_update_alert(s); + } + break; + case R_OP_STATUS: + val32 &= R_OP_STATUS_VAL_MASK; + ot_keymgr_dpe_change_op_status(s, s->regs[R_OP_STATUS] & + ~val32); /* RW1C */ + break; + case R_ERR_CODE: + val32 &= ERR_CODE_MASK; + s->regs[reg] &= ~val32; /* RW1C */ + ot_keymgr_dpe_update_alert(s); + break; + case R_DEBUG: + val32 &= DEBUG_MASK; + s->regs[reg] &= val32; /* RW0C */ + break; + case R_CFG_REGWEN: + case R_WORKING_STATE: + case R_SW_SHARE0_OUTPUT_0: + case R_SW_SHARE0_OUTPUT_1: + case R_SW_SHARE0_OUTPUT_2: + case R_SW_SHARE0_OUTPUT_3: + case R_SW_SHARE0_OUTPUT_4: + case R_SW_SHARE0_OUTPUT_5: + case R_SW_SHARE0_OUTPUT_6: + case R_SW_SHARE0_OUTPUT_7: + case R_SW_SHARE1_OUTPUT_0: + case R_SW_SHARE1_OUTPUT_1: + case R_SW_SHARE1_OUTPUT_2: + case R_SW_SHARE1_OUTPUT_3: + case R_SW_SHARE1_OUTPUT_4: + case R_SW_SHARE1_OUTPUT_5: + case R_SW_SHARE1_OUTPUT_6: + case R_SW_SHARE1_OUTPUT_7: + case R_FAULT_STATUS: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: R/O register 0x02%" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, REG_NAME(reg)); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); + break; + } +}; + +static void ot_keymgr_dpe_get_aes_key(const OtKeyMgrDpeState *s, + OtKeyMgrDpeKey *key) +{ + g_assert(key); + + memcpy(key->share0, s->out_keys->aes.share0, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(key->share1, s->out_keys->aes.share1, OT_KEYMGR_DPE_KEY_BYTES); + key->valid = s->out_keys->aes.valid; +} + +static void ot_keymgr_dpe_get_kmac_key(const OtKeyMgrDpeState *s, + OtKeyMgrDpeKey *key) +{ + g_assert(key); + + /* return either key slot or sideload key depending on current state */ + if (s->op_state.op_req) { + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t slot_src_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); + const OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; + +#ifdef OT_KEYMGR_DPE_DEBUG + uint8_t key_value[OT_KEYMGR_DPE_KEY_BYTES]; + for (unsigned ix = 0u; ix < OT_KEYMGR_DPE_KEY_BYTES; ix++) { + key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; + } + TRACE_KEYMGR_DPE(s, "KMAC key for adv/gen: %s", + ot_keymgr_dpe_dump_bigint(key_value, + OT_KEYMGR_DPE_KEY_BYTES)); +#endif + + memcpy(key->share0, src_slot->key.share0, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(key->share1, src_slot->key.share1, OT_KEYMGR_DPE_KEY_BYTES); + key->valid = src_slot->valid; + } else { + memcpy(key->share0, s->out_keys->kmac.share0, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(key->share1, s->out_keys->kmac.share1, OT_KEYMGR_DPE_KEY_BYTES); + key->valid = s->out_keys->kmac.valid; + } +} + +static void ot_keymgr_dpe_get_otbn_key(const OtKeyMgrDpeState *s, + OtKeyMgrDpeOtbnKey *key) +{ + g_assert(key); + + memcpy(key->share0, s->out_keys->otbn.share0, OT_KEYMGR_DPE_OTBN_KEY_BYTES); + memcpy(key->share1, s->out_keys->otbn.share1, OT_KEYMGR_DPE_OTBN_KEY_BYTES); + key->valid = s->out_keys->otbn.valid; +} + +static void ot_keymgr_dpe_configure_constants(OtKeyMgrDpeState *s) +{ + for (unsigned ix = 0u; ix < KEYMGR_DPE_SEED_COUNT; ix++) { + if (!s->seed_xstrs[ix]) { + trace_ot_keymgr_dpe_seed_missing(s->ot_id, ix); + continue; + } + + size_t len = strlen(s->seed_xstrs[ix]); + size_t seed_len_bytes = KEYMGR_DPE_SEED_BYTES; + if (ix == KEYMGR_DPE_SEED_LFSR) { + seed_len_bytes = 8u; + } + if (len != (seed_len_bytes * 2u)) { + error_setg(&error_fatal, "%s: %s invalid seed #%u length\n", + __func__, s->ot_id, ix); + continue; + } + + if (ot_common_parse_hexa_str(s->seeds[ix], s->seed_xstrs[ix], + seed_len_bytes, true, true)) { + error_setg(&error_fatal, "%s: %s unable to parse seed #%u\n", + __func__, s->ot_id, ix); + continue; + } + } +} + +static Property ot_keymgr_dpe_properties[] = { + DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtKeyMgrDpeState, ot_id), + DEFINE_PROP_LINK("edn", OtKeyMgrDpeState, edn.device, TYPE_OT_EDN, + OtEDNState *), + DEFINE_PROP_UINT8("edn-ep", OtKeyMgrDpeState, edn.ep, UINT8_MAX), + DEFINE_PROP_LINK("kmac", OtKeyMgrDpeState, kmac, TYPE_OT_KMAC, + OtKMACState *), + DEFINE_PROP_UINT8("kmac-app", OtKeyMgrDpeState, kmac_app, UINT8_MAX), + DEFINE_PROP_LINK("lc_ctrl", OtKeyMgrDpeState, lc_ctrl, TYPE_OT_LC_CTRL, + OtLcCtrlState *), + DEFINE_PROP_LINK("otp_ctrl", OtKeyMgrDpeState, otp, TYPE_OT_OTP, + OtOTPState *), + DEFINE_PROP_LINK("rom0", OtKeyMgrDpeState, rom_ctrl[0], TYPE_OT_ROM_CTRL, + OtRomCtrlState *), + DEFINE_PROP_LINK("rom1", OtKeyMgrDpeState, rom_ctrl[1], TYPE_OT_ROM_CTRL, + OtRomCtrlState *), + DEFINE_PROP_STRING("aes_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_AES]), + DEFINE_PROP_STRING("hard_output_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_HW_OUT]), + DEFINE_PROP_STRING("kmac_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_KMAC]), + DEFINE_PROP_STRING("lfsr_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_LFSR]), + DEFINE_PROP_STRING("none_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_NONE]), + DEFINE_PROP_STRING("otbn_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_OTBN]), + DEFINE_PROP_STRING("revision_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_REV]), + DEFINE_PROP_STRING("soft_output_seed", OtKeyMgrDpeState, + seed_xstrs[KEYMGR_DPE_SEED_SW_OUT]), + DEFINE_PROP_END_OF_LIST(), +}; + +static const MemoryRegionOps ot_keymgr_dpe_regs_ops = { + .read = &ot_keymgr_dpe_read, + .write = &ot_keymgr_dpe_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4u, + .impl.max_access_size = 4u, +}; + +static void ot_keymgr_dpe_reset_enter(Object *obj, ResetType type) +{ + OtKeyMgrDpeClass *c = OT_KEYMGR_DPE_GET_CLASS(obj); + OtKeyMgrDpeState *s = OT_KEYMGR_DPE(obj); + + trace_ot_keymgr_dpe_reset(s->ot_id); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } + + g_assert(s->edn.device); + g_assert(s->edn.ep != UINT8_MAX); + g_assert(s->kmac); + g_assert(s->kmac_app != UINT8_MAX); + g_assert(s->lc_ctrl); + g_assert(s->otp); + g_assert(s->rom_ctrl[0]); + g_assert(s->rom_ctrl[1]); + + /* reset registers */ + memset(s->regs, 0u, sizeof(s->regs)); + s->regs[R_CFG_REGWEN] = 0x1u; + ot_shadow_reg_init(&s->control, 10u); + s->regs[R_RESEED_INTERVAL_REGWEN] = 0x1u; + ot_shadow_reg_init(&s->reseed_interval, 0x100u); + s->regs[R_SLOT_POLICY_REGWEN] = 0x1u; + s->regs[R_SW_BINDING_REGWEN] = 0x1u; + memset(s->sw_binding, 0u, NUM_SW_BINDING_REG * sizeof(uint32_t)); + memset(s->salt, 0u, NUM_SALT_REG * sizeof(uint32_t)); + s->regs[R_MAX_KEY_VER_REGWEN] = 0x1u; + ot_shadow_reg_init(&s->max_key_ver, 0u); + s->regs[R_WORKING_STATE] = 0u; + s->regs[R_OP_STATUS] = 0u; + + /* reset internal state */ + s->enabled = false; + s->state = KEYMGR_DPE_ST_RESET; + s->prng.reseed_req = false; + s->prng.reseed_ack = false; + s->prng.reseed_cnt = 0u; + s->op_state.op_req = false; + s->op_state.op_ack = false; + ot_keymgr_reset_kdf_buffer(s); + + /* reset slots */ + memset(s->key_slots, 0u, NUM_SLOTS * sizeof(OtKeyMgrDpeSlot)); + + /* reset output keys */ + memset(s->out_keys, 0u, sizeof(OtKeyMgrDpeOutKeys)); + + /* update IRQ and alert states */ + ot_keymgr_dpe_update_irq(s); + ot_keymgr_dpe_update_alert(s); + + /* connect to KMAC */ + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->connect_app(s->kmac, s->kmac_app, &KMAC_APP_CFG, + ot_keymgr_dpe_handle_kmac_response, s); +} + +static void ot_keymgr_dpe_realize(DeviceState *dev, Error **errp) +{ + OtKeyMgrDpeState *s = OT_KEYMGR_DPE(dev); + + (void)errp; /* unused */ + + if (!s->ot_id) { + s->ot_id = + g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); + } + + ot_keymgr_dpe_configure_constants(s); +} + +static void ot_keymgr_dpe_init(Object *obj) +{ + OtKeyMgrDpeState *s = OT_KEYMGR_DPE(obj); + + memory_region_init_io(&s->mmio, obj, &ot_keymgr_dpe_regs_ops, s, + TYPE_OT_KEYMGR_DPE, REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); + + ibex_sysbus_init_irq(obj, &s->irq); + for (unsigned ix = 0u; ix < ALERT_COUNT; ix++) { + ibex_qdev_init_irq(obj, &s->alerts[ix], OT_DEVICE_ALERT); + } + qdev_init_gpio_in_named(DEVICE(s), ot_keymgr_dpe_lc_signal, + OT_KEYMGR_DPE_ENABLE, 1); + + s->prng.state = ot_prng_allocate(); + + s->kdf_buf.data = g_new0(uint8_t, KEYMGR_DPE_KDF_BUFFER_BYTES); + s->salt = g_new0(uint8_t, NUM_SALT_REG * sizeof(uint32_t)); + s->sw_binding = g_new0(uint8_t, NUM_SW_BINDING_REG * sizeof(uint32_t)); + for (unsigned ix = 0; ix < ARRAY_SIZE(s->seeds); ix++) { + s->seeds[ix] = g_new0(uint8_t, KEYMGR_DPE_SEED_BYTES); + } + s->key_slots = g_new0(OtKeyMgrDpeSlot, NUM_SLOTS); + s->out_keys = g_new0(OtKeyMgrDpeOutKeys, 1u); + + s->fsm_tick_bh = qemu_bh_new(&ot_keymgr_dpe_fsm_tick, s); +} + +static void ot_keymgr_dpe_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + (void)data; /* unused */ + + dc->realize = &ot_keymgr_dpe_realize; + device_class_set_props(dc, ot_keymgr_dpe_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + ResettableClass *rc = RESETTABLE_CLASS(klass); + OtKeyMgrDpeClass *kmc = OT_KEYMGR_DPE_CLASS(klass); + resettable_class_set_parent_phases(rc, &ot_keymgr_dpe_reset_enter, NULL, + NULL, &kmc->parent_phases); + + kmc->get_aes_key = &ot_keymgr_dpe_get_aes_key; + kmc->get_kmac_key = &ot_keymgr_dpe_get_kmac_key; + kmc->get_otbn_key = &ot_keymgr_dpe_get_otbn_key; +} + +static const TypeInfo ot_keymgr_dpe_info = { + .name = TYPE_OT_KEYMGR_DPE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OtKeyMgrDpeState), + .instance_init = &ot_keymgr_dpe_init, + .class_size = sizeof(OtKeyMgrDpeClass), + .class_init = &ot_keymgr_dpe_class_init, +}; + +static void ot_keymgr_dpe_register_types(void) +{ + type_register_static(&ot_keymgr_dpe_info); +} + +type_init(ot_keymgr_dpe_register_types) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index db432ae1f0eb5..4204b9776ca7b 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -271,6 +271,27 @@ ot_ibex_wrapper_reset(const char *id, const char *phase) "%s: %s" ot_ibex_wrapper_unmap(const char *id, unsigned slot) "%s: region %u" ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halted, bool in_reset, bool cpu_en) "%s: bm:0x%x esc:%u halted:%u in_reset:%u -> CPU enable %u" +# ot_keymgr_dpe + +ot_keymgr_dpe_entropy(const char * id, unsigned reseed_cnt, bool resched) "%s: reseed_cnt: %u, resched: %u" +ot_keymgr_dpe_error(const char *id, const char *msg) "%s: %s" +ot_keymgr_dpe_change_working_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_keymgr_dpe_change_op_status(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_keymgr_dpe_change_main_fsm_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_keymgr_dpe_go_idle(const char *id) "%s" +ot_keymgr_dpe_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" +ot_keymgr_dpe_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" +ot_keymgr_dpe_irq(const char *id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" +ot_keymgr_dpe_lc_signal(const char *id, int level) "%s: LC signal level:%d" +ot_keymgr_dpe_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" +ot_keymgr_dpe_operation(const char *id, const char *op, int nop) "%s: [%s:%d]" +ot_keymgr_dpe_reset(const char *id) "%s" +ot_keymgr_dpe_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" +ot_keymgr_dpe_seed_missing(const char * id, unsigned ix) "%s: #%u" +ot_keymgr_dpe_sideload_clear(const char *id, const char *sc, int nsc) "%s: [%s:%d]" +ot_keymgr_dpe_kmac_req(const char *id, uint32_t fifo_len, uint32_t msg_len, bool last) "%s: fifo_len:%u msg_len:%u, last:%d" +ot_keymgr_dpe_kmac_rsp(const char *id, bool done) "%s: done:%d" + # ot_kmac.c ot_kmac_app_finished(const char *id, unsigned app_idx) "%s: #%u" diff --git a/include/hw/opentitan/ot_keymgr_dpe.h b/include/hw/opentitan/ot_keymgr_dpe.h new file mode 100644 index 0000000000000..9b026a96c04c6 --- /dev/null +++ b/include/hw/opentitan/ot_keymgr_dpe.h @@ -0,0 +1,84 @@ +/* + * QEMU OpenTitan Key Manager DPE device + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Loïc Lefort + * Samuel Ortiz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_OPENTITAN_OT_KEYMGR_DPE_H +#define HW_OPENTITAN_OT_KEYMGR_DPE_H + +#include "qom/object.h" + +#define TYPE_OT_KEYMGR_DPE "ot-keymgr_dpe" +OBJECT_DECLARE_TYPE(OtKeyMgrDpeState, OtKeyMgrDpeClass, OT_KEYMGR_DPE) + +/* Input signal to enable the key manager (from lifecycle controller) */ +#define OT_KEYMGR_DPE_ENABLE TYPE_OT_KEYMGR_DPE "-enable" + +/* AES and KMAC Keys have 2 shares of 256 bits */ +#define OT_KEYMGR_DPE_KEY_BYTES (256u / 8u) + +typedef struct { + uint8_t share0[OT_KEYMGR_DPE_KEY_BYTES]; + uint8_t share1[OT_KEYMGR_DPE_KEY_BYTES]; + bool valid; +} OtKeyMgrDpeKey; + +/* OTBN Keys have 2 shares of 384 bits */ +#define OT_KEYMGR_DPE_OTBN_KEY_BYTES (384u / 8u) + +typedef struct { + uint8_t share0[OT_KEYMGR_DPE_OTBN_KEY_BYTES]; + uint8_t share1[OT_KEYMGR_DPE_OTBN_KEY_BYTES]; + bool valid; +} OtKeyMgrDpeOtbnKey; + +struct OtKeyMgrDpeClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; + + /** + * Retrieve output key data for AES sideloading. + * + * @key pointer to a structure to be filled with key data. + */ + void (*get_aes_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeKey *key); + + /** + * Retrieve output key data for KMAC sideloading. + * + * @key pointer to a structure to be filled with key data. + */ + void (*get_kmac_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeKey *key); + + /** + * Retrieve output key data for OTBN sideloading. + * + * @key pointer to a structure to be filled with key data. + */ + void (*get_otbn_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeOtbnKey *key); +}; + +#endif /* HW_OPENTITAN_OT_KEYMGR_DPE_H */ From 55e67a91173b1df45c30074a7908fe720889f561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:29:36 +0100 Subject: [PATCH 134/175] [ot] hw/opentitan: ot_kmac: add Key Manager DPE support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_kmac.c | 64 +++++++++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 3a3b5eb79999e..406e168351ef2 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -80,6 +80,7 @@ config OT_KEYMGR_DPE bool config OT_KMAC + select OT_KEYMGR_DPE bool config OT_LC_CTRL diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index d391911209281..ac9789697ce2c 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -33,6 +33,7 @@ */ #include "qemu/osdep.h" +#include "qemu/bitops.h" #include "qemu/fifo8.h" #include "qemu/log.h" #include "qemu/main-loop.h" @@ -42,6 +43,7 @@ #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" +#include "hw/opentitan/ot_keymgr_dpe.h" #include "hw/opentitan/ot_kmac.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" @@ -411,6 +413,7 @@ struct OtKMACState { char *ot_id; char *clock_name; DeviceState *clock_src; + OtKeyMgrDpeState *keymgr; OtEDNState *edn; uint8_t edn_ep; uint8_t num_app; @@ -546,31 +549,56 @@ static inline size_t ot_kmac_get_key_length(OtKMACState *s) uint32_t key_len = FIELD_EX32(s->regs[R_KEY_LEN], KEY_LEN, LEN); switch (key_len) { case 0: - return 128u; + return 16u; case 1u: - return 192u; + return 24u; case 2u: - return 256u; + return 32u; case 3u: - return 384u; + return 48u; case 4u: - return 512u; + return 64u; default: /* invalid key length values are traced at register write */ return 0; } } -static void ot_kmac_get_key(OtKMACState *s, uint8_t *key, size_t keylen) +static void ot_kmac_get_key(OtKMACState *s, uint8_t *key, size_t *keylen) { - for (size_t ix = 0; ix < keylen && ix < (size_t)NUM_KEY_REGS * 4u; ix++) { - uint8_t reg = ix >> 2u; - uint8_t byteoffset = ix & 3u; - - uint8_t share0 = - (uint8_t)(s->regs[R_KEY_SHARE0_0 + reg] >> (byteoffset * 8u)); - uint8_t share1 = - (uint8_t)(s->regs[R_KEY_SHARE1_0 + reg] >> (byteoffset * 8u)); + uint32_t cfg = ot_shadow_reg_peek(&s->cfg); + bool sideload = FIELD_EX32(cfg, CFG_SHADOWED, SIDELOAD) != 0; + + /* force sideload for app interface */ + if (s->current_app) { + sideload = true; + } + + if (sideload) { + OtKeyMgrDpeKey keymgr_key; + OtKeyMgrDpeClass *kmc = OT_KEYMGR_DPE_GET_CLASS(s->keymgr); + kmc->get_kmac_key(s->keymgr, &keymgr_key); + *keylen = OT_KEYMGR_DPE_KEY_BYTES; + for (size_t ix = 0; ix < *keylen; ix++) { + key[ix] = keymgr_key.share0[ix] ^ keymgr_key.share1[ix]; + } + /* only check key validity in App mode */ + if (s->current_app && !keymgr_key.valid) { + ot_kmac_report_error(s, OT_KMAC_ERR_KEY_NOT_VALID, + s->current_app->index); + } + return; + } + + *keylen = ot_kmac_get_key_length(s); + for (size_t ix = 0; ix < *keylen; ix++) { + uint8_t reg = ix / sizeof(uint32_t); + uint8_t byteoffset = ix & (sizeof(uint32_t) - 1u); + + uint8_t share0 = (uint8_t)(s->regs[R_KEY_SHARE0_0 + reg] >> + (byteoffset * BITS_PER_BYTE)); + uint8_t share1 = (uint8_t)(s->regs[R_KEY_SHARE1_0 + reg] >> + (byteoffset * BITS_PER_BYTE)); key[ix] = share0 ^ share1; } } @@ -887,8 +915,10 @@ static void ot_kmac_process_start(OtKMACState *s) /* if KMAC mode is enabled, process key */ if (cfg->mode == OT_KMAC_MODE_KMAC) { uint8_t key[NUM_KEY_REGS * sizeof(uint32_t)]; - size_t keylen = ot_kmac_get_key_length(s) / 8u; - ot_kmac_get_key(s, key, keylen); + size_t keylen = 0; + static_assert(OT_KEYMGR_DPE_KEY_BYTES <= ARRAY_SIZE(key), + "key buffer too small to hold sideloaded key"); + ot_kmac_get_key(s, key, &keylen); sha3_process_kmac_key(&s->ltc_state, key, keylen); } break; @@ -1626,6 +1656,8 @@ static Property ot_kmac_properties[] = { DeviceState *), DEFINE_PROP_LINK("edn", OtKMACState, edn, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtKMACState, edn_ep, UINT8_MAX), + DEFINE_PROP_LINK("keymgr", OtKMACState, keymgr, TYPE_OT_KEYMGR_DPE, + OtKeyMgrDpeState *), DEFINE_PROP_UINT8("num-app", OtKMACState, num_app, 0), DEFINE_PROP_END_OF_LIST(), }; From af2e6de0d1425c447ed4ff8378cced691208e959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 26 Mar 2025 18:27:54 +0100 Subject: [PATCH 135/175] [ot] hw/opentitan: ot_darjeeling: add Key Manager DPE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/riscv/Kconfig | 1 + hw/riscv/ot_darjeeling.c | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index e7f90ab7ffa26..830fac0ab7ea3 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -39,6 +39,7 @@ config OT_DARJEELING select OT_HMAC select OT_I2C_DJ select OT_IBEX_WRAPPER + select OT_KEYMGR_DPE select OT_KMAC select OT_LC_CTRL select OT_MBX diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 96d33155ac6d3..200ee0227f78a 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -51,6 +51,7 @@ #include "hw/opentitan/ot_hmac.h" #include "hw/opentitan/ot_i2c_dj.h" #include "hw/opentitan/ot_ibex_wrapper.h" +#include "hw/opentitan/ot_keymgr_dpe.h" #include "hw/opentitan/ot_kmac.h" #include "hw/opentitan/ot_lc_ctrl.h" #include "hw/opentitan/ot_mbx.h" @@ -623,7 +624,8 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { ), .link = IBEXDEVICELINKDEFS( OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), - OT_DJ_SOC_DEVLINK("edn", EDN0) + OT_DJ_SOC_DEVLINK("edn", EDN0), + OT_DJ_SOC_DEVLINK("keymgr", KEYMGR_DPE) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP("clock-name", "trans.kmac"), @@ -653,15 +655,26 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { ), }, [OT_DJ_SOC_DEV_KEYMGR_DPE] = { - .type = TYPE_UNIMPLEMENTED_DEVICE, - .name = "ot-keymgr_dpe", - .cfg = &ibex_unimp_configure, + .type = TYPE_OT_KEYMGR_DPE, .memmap = MEMMAPENTRIES( { .base = 0x21140000u } ), + .gpio = IBEXGPIOCONNDEFS( + OT_DJ_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 122), + OT_DJ_SOC_GPIO_ALERT(0, 63), + OT_DJ_SOC_GPIO_ALERT(1, 64) + ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("edn", EDN0), + OT_DJ_SOC_DEVLINK("kmac", KMAC), + OT_DJ_SOC_DEVLINK("lc_ctrl", LC_CTRL), + OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_DJ_SOC_DEVLINK("rom0", ROM0), + OT_DJ_SOC_DEVLINK("rom1", ROM1) + ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("size", 0x100u), - IBEX_DEV_BOOL_PROP("warn-once", true) + IBEX_DEV_UINT_PROP("edn-ep", 0u), + IBEX_DEV_UINT_PROP("kmac-app", 0u) ), }, [OT_DJ_SOC_DEV_CSRNG] = { @@ -861,7 +874,7 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { }, [OT_DJ_SOC_DEV_MBX_JTAG] = { OT_DJ_SOC_DEV_MBX_DUAL(7, 0x22000800u, "ot-mbx.sram", 155, 89, - DEBUG_MEMORY(OT_DJ_DEBUG_MBX_JTAG_ADDR)), + DEBUG_MEMORY(OT_DJ_DEBUG_MBX_JTAG_ADDR)), }, [OT_DJ_SOC_DEV_DMA] = { .type = TYPE_OT_DMA, @@ -1153,6 +1166,8 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_IBEX_WRAPPER_CPU_EN, OT_IBEX_LC_CTRL_CPU_EN), OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CHECK_BYP_EN, OTP_CTRL, OT_LC_BROADCAST, OT_OTP_LC_CHECK_BYP_EN), + OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_KEYMGR_EN, KEYMGR_DPE, + OT_KEYMGR_DPE_ENABLE, 0), OT_DJ_SOC_SIGNAL(OT_LC_BROADCAST, OT_LC_CREATOR_SEED_SW_RW_EN, OTP_CTRL, OT_LC_BROADCAST, OT_OTP_LC_CREATOR_SEED_SW_RW_EN), From a5fc5d0bb138ad3f79c1fc6df9de7f5747e21706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Fri, 4 Jul 2025 19:10:15 +0200 Subject: [PATCH 136/175] [ot] hw/opentitan: ot_kmac: constify some function parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_kmac.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index ac9789697ce2c..0fbb1879d4e8a 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -544,7 +544,7 @@ static void ot_kmac_get_sw_config(OtKMACState *s) } } -static inline size_t ot_kmac_get_key_length(OtKMACState *s) +static inline size_t ot_kmac_get_key_length(const OtKMACState *s) { uint32_t key_len = FIELD_EX32(s->regs[R_KEY_LEN], KEY_LEN, LEN); switch (key_len) { @@ -731,13 +731,13 @@ static void ot_kmac_process(void *opaque) ot_kmac_update_irq(s); } -static inline bool ot_kmac_config_enabled(OtKMACState *s) +static inline bool ot_kmac_config_enabled(const OtKMACState *s) { /* configuration is enabled only in idle mode */ return s->state == KMAC_ST_IDLE; } -static inline bool ot_kmac_check_reg_write(OtKMACState *s, hwaddr reg) +static inline bool ot_kmac_check_reg_write(const OtKMACState *s, hwaddr reg) { if (!ot_kmac_config_enabled(s)) { qemu_log_mask(LOG_GUEST_ERROR, @@ -782,7 +782,8 @@ static bool ot_kmac_check_mode_and_strength(const OtKMACAppCfg *cfg) } } -static inline uint8_t ot_kmac_get_prefix_byte(OtKMACState *s, size_t offset) +static inline uint8_t +ot_kmac_get_prefix_byte(const OtKMACState *s, size_t offset) { size_t reg = offset / sizeof(uint32_t); size_t byteoffset = offset - reg * sizeof(uint32_t); @@ -798,7 +799,8 @@ static inline uint8_t ot_kmac_get_prefix_byte(OtKMACState *s, size_t offset) return (uint8_t)(s->regs[R_PREFIX_0 + reg] >> (byteoffset * 8u)); } -static size_t ot_kmac_left_decode(OtKMACState *s, size_t offset, size_t *value) +static size_t ot_kmac_left_decode(const OtKMACState *s, size_t offset, + size_t *value) { size_t len; size_t val = 0; @@ -857,7 +859,7 @@ static bool ot_kmac_decode_sw_prefix(OtKMACState *s) return false; } -static bool ot_kmac_check_kmac_sw_prefix(OtKMACState *s) +static bool ot_kmac_check_kmac_sw_prefix(const OtKMACState *s) { /* * check that the encoded prefix in PREFIX_x registers starts with a "KMAC" From 89d790a30e427668093094f382733f755c6553d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Wed, 16 Jul 2025 10:59:03 +0200 Subject: [PATCH 137/175] [ot] hw/opentitan: trace-events: fix trace messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/trace-events | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 4204b9776ca7b..4470730e01372 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -8,8 +8,8 @@ ot_aes_error(const char *func, int line, const char *id, const char *err) "%s:%d ot_aes_fill_entropy(const char *id, uint32_t bits, bool fips) "%s: 0x%08x fips:%u" ot_aes_info(const char *func, int line, const char *id, const char *errl) "%s:%d %s: %s" ot_aes_init(const char *id, const char *what) "%s: %s" -ot_aes_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" -ot_aes_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_aes_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_aes_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_aes_request_entropy(const char *id) "%s" ot_aes_reseed(const char *id, const char *reason) "%s: %s" ot_aes_reseed_rate(const char *id, unsigned rate) "%s: %u" @@ -299,11 +299,11 @@ ot_kmac_app_start(const char *id, unsigned app_idx) "%s: #%u" ot_kmac_change_state_app(const char *id, unsigned app_idx, int line, const char *old, int nold, const char *new, int nnew) "%s: #%u @ %d [%s:%d] -> [%s:%d]" ot_kmac_change_state_sw(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_kmac_debug(const char *id, const char *msg) "%s: %s" -ot_kmac_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" -ot_kmac_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" -ot_kmac_msgfifo_write(const char *id, uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "%s: addr=0x%03x, val=0x%x (const char *id, %u), pc=0x%x" -ot_kmac_process_sw_command(const char *id, int cmd, const char *cmd_str) "%s: cmd=0x%02x (const char *id, %s)" -ot_kmac_report_error(const char *id, int code, const char *code_str, uint32_t info) "%s: code=0x%02x (const char *id, %s) info=0x%06x" +ot_kmac_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_kmac_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_kmac_msgfifo_write(const char *id, uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "%s: addr=0x%03x, val=0x%x (%u), pc=0x%x" +ot_kmac_process_sw_command(const char *id, int cmd, const char *cmd_str) "%s: cmd=0x%02x (%s)" +ot_kmac_report_error(const char *id, int code, const char *code_str, uint32_t info) "%s: code=0x%02x (%s) info=0x%06x" ot_kmac_state_read_out(const char *id, uint32_t addr, uint32_t val, uint32_t pc) "%s: addr=0x%03x, val=0x%x, pc=0x%x" # ot_lc_ctrl.c @@ -348,8 +348,8 @@ ot_mbx_sys_io_write(const char * mid, uint32_t addr, const char * regname, uint3 ot_otbn_change_status(const char * id, const char * status) "%s: status=%s" ot_otbn_deny(const char * id, uint32_t pc, const char *msg) "%s: pc=0x%x %s" ot_otbn_error(const char * id, const char *msg) "%s: %s" -ot_otbn_io_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char * id, %s), val=0x%x, pc=0x%x" -ot_otbn_io_write(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char * id, %s), val=0x%x, pc=0x%x" +ot_otbn_io_read_out(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_otbn_io_write(const char * id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_otbn_irq(const char * id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" ot_otbn_mem_read(const char * id, char mem, uint32_t addr, uint32_t value) "%s: %cmem addr=0x%04x, val=0x%08x" ot_otbn_mem_write(const char * id, char mem, uint32_t addr, uint32_t value, const char *outcome) "%s: %cmem addr=0x%04x, val=0x%08x%s" @@ -513,11 +513,11 @@ ot_spi_device_gen_fifo_error(const char *id, const char *msg) "%s: %s" ot_spi_device_gen_phase(const char *id, const char *func, unsigned off, unsigned lim, bool phase) "%s: %s off:0x%03x lim:0x%03x ph:%u" ot_spi_device_gen_rx_timeout(const char *id, unsigned count) "%s: %d" ot_spi_device_gen_update_fifo(const char *id, const char *fifo, int line, uint32_t val) "%s: %s@%d: 0x%08x" -ot_spi_device_io_spi_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" -ot_spi_device_io_spi_write_in(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_spi_device_io_spi_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_spi_device_io_spi_write_in(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_spi_device_set_irq(const char *id, const char *name, unsigned irq, bool level) "%s: %s [%u]: %u" -ot_spi_device_io_tpm_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" -ot_spi_device_io_tpm_write_in(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (const char *id, %s), val=0x%x, pc=0x%x" +ot_spi_device_io_tpm_read_out(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" +ot_spi_device_io_tpm_write_in(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_spi_device_release(const char *id) "%s" ot_spi_device_reset(const char *id, const char *stage) "%s: %s" ot_spi_device_update_last_read_addr(const char *id, uint32_t addr) "%s: 0x%08x" From 67229a89f61a3bee188b229be05e99ed8ac28ca4 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 15:36:19 +0200 Subject: [PATCH 138/175] [ot] hw/opentitan: ot_common: add hex string utility functions Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_common.c | 39 ++++++++++++++++++++++++++++++++ include/hw/opentitan/ot_common.h | 28 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/hw/opentitan/ot_common.c b/hw/opentitan/ot_common.c index 62e06890588ea..9146318aa769e 100644 --- a/hw/opentitan/ot_common.c +++ b/hw/opentitan/ot_common.c @@ -369,6 +369,45 @@ int ot_common_parse_hexa_str(uint8_t *out, const char *xstr, size_t olen, return (!exact || !xstr[olen * 2u]) ? 0 : 1; } +static const char * +ot_common_hexdump(const uint8_t *buf, size_t size, char *hexstr, bool order, + size_t hexstr_size, const char *hex) +{ + if (size > ((hexstr_size / 2u) - 1u)) { + size = (hexstr_size / 2u) - 1u; + } + + if (!order) { + for (unsigned ix = 0u; ix < (unsigned)size; ix++) { + hexstr[(ix * 2u)] = hex[(buf[ix] >> 4u) & 0xfu]; + hexstr[(ix * 2u) + 1u] = hex[buf[ix] & 0xfu]; + } + } else { + for (unsigned ix = 0u; ix < (unsigned)size; ix++) { + hexstr[(ix * 2u)] = hex[(buf[size - 1u - ix] >> 4u) & 0xfu]; + hexstr[(ix * 2u) + 1u] = hex[buf[size - 1u - ix] & 0xfu]; + } + } + + + hexstr[size * 2u] = '\0'; + return hexstr; +} + +const char *ot_common_uhexdump(const uint8_t *buf, size_t size, bool order, + char *hexstr, size_t hexstr_size) +{ + static const char uhex[] = "0123456789ABCDEF"; + return ot_common_hexdump(buf, size, hexstr, order, hexstr_size, uhex); +} + +const char *ot_common_lhexdump(const uint8_t *buf, size_t size, bool order, + char *hexstr, size_t hexstr_size) +{ + static const char lhex[] = "0123456789abcdef"; + return ot_common_hexdump(buf, size, hexstr, order, hexstr_size, lhex); +} + /* * Unfortunately, there is no QEMU API to properly disable serial control lines */ diff --git a/include/hw/opentitan/ot_common.h b/include/hw/opentitan/ot_common.h index 0961088bafdc7..47b26cae35778 100644 --- a/include/hw/opentitan/ot_common.h +++ b/include/hw/opentitan/ot_common.h @@ -313,6 +313,34 @@ int ot_common_string_ends_with(const char *str, const char *suffix); int ot_common_parse_hexa_str(uint8_t *out, const char *xstr, size_t olen, bool reverse, bool exact); +/* + * Generate a uppercase hex-string representation of a buffer. + * + * @buf input byte buffer + * @size size of the buffer + * @order true to print the buffer in reverse order + * @hexstr output hex string buffer + * @hexstr_size max size of the output hex string buffer + * + * @return the hex string representation of the buffer + */ +const char *ot_common_uhexdump(const uint8_t *buf, size_t size, bool order, + char *hexstr, size_t hexstr_size); + +/* + * Generate a lowercase hex-string representation of a buffer. + * + * @buf input byte buffer + * @size size of the buffer + * @order true to print the buffer in reverse order + * @hexstr output hex string buffer + * @hexstr_size max size of the output hex string buffer + * + * @return the hex string representation of the buffer + */ +const char *ot_common_lhexdump(const uint8_t *buf, size_t size, bool order, + char *hexstr, size_t hexstr_size); + /* ------------------------------------------------------------------------ */ /* Configuration utilities */ /* ------------------------------------------------------------------------ */ From 3b7f52c4aa3d68175110fc3a1120aa846ec6a800 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 15:42:43 +0200 Subject: [PATCH 139/175] [ot] hw/opentitan: ot_flash: remove dead code Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_flash.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c index 300ccf7b126c9..808cc5c6e6872 100644 --- a/hw/opentitan/ot_flash.c +++ b/hw/opentitan/ot_flash.c @@ -2237,26 +2237,6 @@ static void ot_flash_csrs_write(void *opaque, hwaddr addr, uint64_t val64, } } -#ifdef USE_HEXDUMP -static char dbg_hexbuf[256]; -static const char *ot_flash_hexdump(const uint8_t *buf, size_t size) -{ - static const char _hex[] = "0123456789ABCDEF"; - - if (size > ((sizeof(dbg_hexbuf) / 2u) - 2u)) { - size = sizeof(dbg_hexbuf) / 2u - 2u; - } - - char *hexstr = dbg_hexbuf; - for (unsigned int ix = 0; ix < size; ix++) { - hexstr[(ix * 2)] = _hex[(buf[ix] >> 4) & 0xf]; - hexstr[(ix * 2) + 1] = _hex[buf[ix] & 0xf]; - } - hexstr[size * 2] = '\0'; - return dbg_hexbuf; -} -#endif - static void ot_flash_load(OtFlashState *s, Error **errp) { OtFlashStorage *flash = &s->flash; From 6f62283a7a983132d2e1bf54915031427085611a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 15:44:17 +0200 Subject: [PATCH 140/175] [ot] hw/opentitan: ot_aes: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_aes.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index a1929efc6bad3..e419fe491640f 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -258,6 +258,7 @@ struct OtAESState { OtAESEDN edn; OtPrngState *prng; const char *clock_src_name; /* IRQ name once connected */ + char *hexstr; unsigned pclk; /* Current input clock */ unsigned reseed_count; bool fast_mode; @@ -273,35 +274,23 @@ struct OtAESClass { }; #ifdef DEBUG_AES -static const char *ot_aes_hexdump(OtAESState *s, const uint8_t *buf, - size_t size) -{ - static const char _hex[] = "0123456789ABCDEF"; - static char hexstr[AES_DEBUG_HEXBUF_SIZE]; - - if (size > ((AES_DEBUG_HEXBUF_SIZE / 2u) - 2u)) { - size = AES_DEBUG_HEXBUF_SIZE / 2u - 2u; - } - - for (unsigned ix = 0u; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexstr; -} - #define trace_ot_aes_buf(_s_, _a_, _m_, _b_) \ trace_ot_aes_buffer((_s_)->ot_id, (_a_), (_m_), \ - ot_aes_hexdump(_s_, (const uint8_t *)(_b_), \ - OT_AES_DATA_SIZE)) + ot_common_uhexdump((const uint8_t *)(_b_), \ + OT_AES_DATA_SIZE, false, \ + (_s_)->hexstr, \ + AES_DEBUG_HEXBUF_SIZE)) #define trace_ot_aes_key(_s_, _a_, _b_, _l_) \ trace_ot_aes_buffer((_s_)->ot_id, (_a_), "key", \ - ot_aes_hexdump(_s_, (const uint8_t *)(_b_), (_l_))) + ot_common_uhexdump((const uint8_t *)(_b_), (_l_), \ + false, (_s_)->hexstr, \ + AES_DEBUG_HEXBUF_SIZE)) #define trace_ot_aes_iv(_s_, _a_, _b_) \ trace_ot_aes_buffer((_s_)->ot_id, (_a_), "iv", \ - ot_aes_hexdump(_s_, (const uint8_t *)(_b_), \ - OT_AES_IV_SIZE)) + ot_common_uhexdump((const uint8_t *)(_b_), \ + OT_AES_IV_SIZE, false, \ + (_s_)->hexstr, \ + AES_DEBUG_HEXBUF_SIZE)) #else #define trace_ot_aes_buf(_s_, _a_, _m_, _b_) #define trace_ot_aes_key(_s_, _a_, _b_, _l_) @@ -1432,6 +1421,10 @@ static void ot_aes_init(Object *obj) s->process_bh = qemu_bh_new(&ot_aes_handle_process, s); s->retard_timer = timer_new_ns(OT_VIRTUAL_CLOCK, &ot_aes_handle_process, s); + +#ifdef DEBUG_AES + s->hexstr = g_new0(char, AES_DEBUG_HEXBUF_SIZE); +#endif } static void ot_aes_class_init(ObjectClass *klass, void *data) From bb98fe4b95ad0a7f55520d4280800c6cabd80a35 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 15:53:04 +0200 Subject: [PATCH 141/175] [ot] hw/opentitan: ot_keymgr_dpe: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_keymgr_dpe.c | 82 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/hw/opentitan/ot_keymgr_dpe.c b/hw/opentitan/ot_keymgr_dpe.c index ce42754147d2e..0e9efea9f25d6 100644 --- a/hw/opentitan/ot_keymgr_dpe.c +++ b/hw/opentitan/ot_keymgr_dpe.c @@ -385,6 +385,8 @@ typedef struct OtKeyMgrDpeState { /* output keys */ OtKeyMgrDpeOutKeys *out_keys; + char *hexstr; + /* properties */ char *ot_id; OtKeyMgrDpeEDN edn; @@ -537,43 +539,28 @@ static const char *FST_NAMES[] = { ((_st_) >= 0 && (_st_) < ARRAY_SIZE(FST_NAMES) ? FST_NAMES[(_st_)] : "?") #ifdef OT_KEYMGR_DPE_DEBUG +#define OT_KEYMGR_DPE_HEXSTR_SIZE 256u #define TRACE_KEYMGR_DPE(_s_, _msg_, ...) \ qemu_log("%s: %s: " _msg_ "\n", __func__, (_s_)->ot_id, ##__VA_ARGS__); +#define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) \ + ot_common_lhexdump(_b_, _l_, false, (_s_)->hexstr, \ + OT_KEYMGR_DPE_HEXSTR_SIZE) #else +#define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) #define TRACE_KEYMGR_DPE(_s_, _msg_, ...) #endif -#ifdef OT_KEYMGR_DPE_DEBUG -static char hexbuf[256u]; -static const char *ot_keymgr_dpe_dump_bigint(const void *data, size_t size) -{ - static const char _hex[] = "0123456789abcdef"; - const uint8_t *buf = (const uint8_t *)data; - - if (size > ((sizeof(hexbuf) / 2u) - 2u)) { - size = sizeof(hexbuf) / 2u - 2u; - } - - char *hexstr = hexbuf; - for (size_t ix = 0u; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[size - 1u - ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[size - 1u - ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexbuf; -} -#endif - static void ot_keymgr_dpe_dump_kdf_buf(OtKeyMgrDpeState *s) { #ifdef OT_KEYMGR_DPE_DEBUG size_t msgs = (s->kdf_buf.length + OT_KMAC_APP_MSG_BYTES - 1u) / OT_KMAC_APP_MSG_BYTES; for (size_t ix = 0u; ix < msgs; ix++) { - TRACE_KEYMGR_DPE(s, "kdf_buf[%lu]: %s", ix, - ot_keymgr_dpe_dump_bigint( - &s->kdf_buf.data[ix * OT_KMAC_APP_MSG_BYTES], - OT_KMAC_APP_MSG_BYTES)); + TRACE_KEYMGR_DPE( + s, "kdf_buf[%lu]: %s", ix, + ot_common_lhexdump(&s->kdf_buf.data[ix * OT_KMAC_APP_MSG_BYTES], + OT_KMAC_APP_MSG_BYTES, true, s->hexstr, + OT_KEYMGR_DPE_HEXSTR_SIZE)); } #endif } @@ -761,7 +748,7 @@ static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) memcpy(req.msg_data, &s->kdf_buf.data[offset], msg_len); TRACE_KEYMGR_DPE(s, "KMAC req: %s last:%d", - ot_keymgr_dpe_dump_bigint(req.msg_data, req.msg_len), + ot_keymgr_dpe_dump_bigint(s, req.msg_data, req.msg_len), req.last); trace_ot_keymgr_dpe_kmac_req(s->ot_id, len, req.msg_len, req.last); @@ -862,7 +849,8 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) key[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; } TRACE_KEYMGR_DPE(s, "KMAC resp: %s", - ot_keymgr_dpe_dump_bigint(key, OT_KMAC_APP_DIGEST_BYTES)); + ot_keymgr_dpe_dump_bigint(s, key, + OT_KMAC_APP_DIGEST_BYTES)); #endif switch (FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION)) { @@ -926,7 +914,7 @@ static void ot_keymgr_dpe_kdf_push_key_version(OtKeyMgrDpeState *s) ot_keymgr_dpe_kdf_push_bytes(s, buf, sizeof(uint32_t)); TRACE_KEYMGR_DPE(s, "Key Version: %s", - ot_keymgr_dpe_dump_bigint(buf, sizeof(uint32_t))); + ot_keymgr_dpe_dump_bigint(s, buf, sizeof(uint32_t))); } static size_t @@ -943,7 +931,7 @@ ot_keymgr_dpe_kdf_append_creator_seed(OtKeyMgrDpeState *s, bool *dvalid) OT_OTP_KEYMGR_SECRET_SIZE); TRACE_KEYMGR_DPE(s, "Creator Seed: %s", - ot_keymgr_dpe_dump_bigint(secret.secret, + ot_keymgr_dpe_dump_bigint(s, secret.secret, OT_OTP_KEYMGR_SECRET_SIZE)); return OT_OTP_KEYMGR_SECRET_SIZE; } @@ -959,7 +947,7 @@ static size_t ot_keymgr_dpe_kdf_append_rom_digest( ot_keymgr_dpe_kdf_push_bytes(s, rom_digest, OT_ROM_DIGEST_BYTES); *dvalid &= ot_keymgr_dpe_valid_data_check(rom_digest, OT_ROM_DIGEST_BYTES); TRACE_KEYMGR_DPE(s, "ROM%u digest: %s", rom_idx, - ot_keymgr_dpe_dump_bigint(&rom_digest[rom_idx], + ot_keymgr_dpe_dump_bigint(s, &rom_digest[rom_idx], OT_ROM_DIGEST_BYTES)); return OT_ROM_DIGEST_BYTES; @@ -977,7 +965,7 @@ static size_t ot_keymgr_dpe_kdf_append_km_div(OtKeyMgrDpeState *s, bool *dvalid) ot_keymgr_dpe_valid_data_check(km_div.data, OT_LC_KEYMGR_DIV_BYTES); TRACE_KEYMGR_DPE(s, "KeyManager div: %s", - ot_keymgr_dpe_dump_bigint(km_div.data, + ot_keymgr_dpe_dump_bigint(s, km_div.data, OT_LC_KEYMGR_DIV_BYTES)); return OT_LC_KEYMGR_DIV_BYTES; } @@ -993,7 +981,7 @@ static size_t ot_keymgr_dpe_kdf_append_dev_id(OtKeyMgrDpeState *s, bool *dvalid) OT_OTP_HWCFG_DEVICE_ID_BYTES); TRACE_KEYMGR_DPE(s, "Device ID: %s", - ot_keymgr_dpe_dump_bigint(hw_cfg->device_id, + ot_keymgr_dpe_dump_bigint(s, hw_cfg->device_id, OT_OTP_HWCFG_DEVICE_ID_BYTES)); return OT_OTP_HWCFG_DEVICE_ID_BYTES; } @@ -1004,7 +992,7 @@ static size_t ot_keymgr_dpe_kdf_append_rev_seed(OtKeyMgrDpeState *s) KEYMGR_DPE_SEED_BYTES); TRACE_KEYMGR_DPE(s, "Revision Seed: %s", - ot_keymgr_dpe_dump_bigint(s->seeds[KEYMGR_DPE_SEED_REV], + ot_keymgr_dpe_dump_bigint(s, s->seeds[KEYMGR_DPE_SEED_REV], KEYMGR_DPE_SEED_BYTES)); return KEYMGR_DPE_SEED_BYTES; @@ -1023,7 +1011,7 @@ ot_keymgr_dpe_kdf_append_owner_seed(OtKeyMgrDpeState *s, bool *dvalid) OT_OTP_KEYMGR_SECRET_SIZE); TRACE_KEYMGR_DPE(s, "Owner Seed: %s", - ot_keymgr_dpe_dump_bigint(secret.secret, + ot_keymgr_dpe_dump_bigint(s, secret.secret, OT_OTP_KEYMGR_SECRET_SIZE)); return OT_OTP_KEYMGR_SECRET_SIZE; } @@ -1096,7 +1084,7 @@ static void ot_keymgr_dpe_operation_advance(OtKeyMgrDpeState *s) NUM_SW_BINDING_REG * sizeof(uint32_t)); expected_kdf_len += NUM_SW_BINDING_REG * sizeof(uint32_t); TRACE_KEYMGR_DPE(s, "Software Binding: %s", - ot_keymgr_dpe_dump_bigint(s->sw_binding, + ot_keymgr_dpe_dump_bigint(s, s->sw_binding, NUM_SW_BINDING_REG * sizeof(uint32_t))); @@ -1146,7 +1134,7 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) s->seeds[KEYMGR_DPE_SEED_HW_OUT]; ot_keymgr_dpe_kdf_push_bytes(s, output_key, KEYMGR_DPE_SEED_BYTES); TRACE_KEYMGR_DPE(s, "Output Key Seed: %s", - ot_keymgr_dpe_dump_bigint(output_key, + ot_keymgr_dpe_dump_bigint(s, output_key, KEYMGR_DPE_SEED_BYTES)); /* Destination Seed (AES/KMAC/OTBN/other) */ @@ -1168,14 +1156,14 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) } ot_keymgr_dpe_kdf_push_bytes(s, dest_seed, KEYMGR_DPE_SEED_BYTES); TRACE_KEYMGR_DPE(s, "Destination Seed: %s", - ot_keymgr_dpe_dump_bigint(dest_seed, + ot_keymgr_dpe_dump_bigint(s, dest_seed, KEYMGR_DPE_SEED_BYTES)); /* Salt (software-provided via SALT_x registers) */ ot_keymgr_dpe_kdf_push_bytes(s, s->salt, NUM_SALT_REG * sizeof(uint32_t)); TRACE_KEYMGR_DPE(s, "Salt: %s", - ot_keymgr_dpe_dump_bigint(s->salt, + ot_keymgr_dpe_dump_bigint(s, s->salt, NUM_SALT_REG * sizeof(uint32_t))); @@ -1397,14 +1385,14 @@ static bool ot_keymgr_dpe_main_fsm_tick(OtKeyMgrDpeState *s) &secret_share0); TRACE_KEYMGR_DPE( s, "Creator Root Key share 0: %s", - ot_keymgr_dpe_dump_bigint(secret_share0.secret, + ot_keymgr_dpe_dump_bigint(s, secret_share0.secret, OT_OTP_KEYMGR_SECRET_SIZE)); oc->get_keymgr_secret(s->otp, OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1, &secret_share1); TRACE_KEYMGR_DPE( s, "Creator Root Key share 1: %s", - ot_keymgr_dpe_dump_bigint(secret_share1.secret, + ot_keymgr_dpe_dump_bigint(s, secret_share1.secret, OT_OTP_KEYMGR_SECRET_SIZE)); if (secret_share0.valid && secret_share1.valid) { @@ -1856,17 +1844,17 @@ static void ot_keymgr_dpe_get_kmac_key(const OtKeyMgrDpeState *s, const OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; #ifdef OT_KEYMGR_DPE_DEBUG - uint8_t key_value[OT_KEYMGR_DPE_KEY_BYTES]; - for (unsigned ix = 0u; ix < OT_KEYMGR_DPE_KEY_BYTES; ix++) { + uint8_t key_value[OT_KMAC_KEY_SIZE]; + for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; } TRACE_KEYMGR_DPE(s, "KMAC key for adv/gen: %s", ot_keymgr_dpe_dump_bigint(key_value, - OT_KEYMGR_DPE_KEY_BYTES)); + OT_KMAC_KEY_SIZE)); #endif - memcpy(key->share0, src_slot->key.share0, OT_KEYMGR_DPE_KEY_BYTES); - memcpy(key->share1, src_slot->key.share1, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(key->share0, src_slot->key.share0, OT_KMAC_KEY_SIZE); + memcpy(key->share1, src_slot->key.share1, OT_KMAC_KEY_SIZE); key->valid = src_slot->valid; } else { memcpy(key->share0, s->out_keys->kmac.share0, OT_KEYMGR_DPE_KEY_BYTES); @@ -2058,6 +2046,10 @@ static void ot_keymgr_dpe_init(Object *obj) s->out_keys = g_new0(OtKeyMgrDpeOutKeys, 1u); s->fsm_tick_bh = qemu_bh_new(&ot_keymgr_dpe_fsm_tick, s); + +#ifdef OT_KEYMGR_DPE_DEBUG + s->hexstr = g_new0(char, OT_KEYMGR_DPE_HEXSTR_SIZE); +#endif } static void ot_keymgr_dpe_class_init(ObjectClass *klass, void *data) From a466594ddc1e4e3ada207161259f0565f7ba2ebf Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 15:59:11 +0200 Subject: [PATCH 142/175] [ot] hw/opentitan: ot_lc_ctrl: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_lc_ctrl.c | 40 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/hw/opentitan/ot_lc_ctrl.c b/hw/opentitan/ot_lc_ctrl.c index 5c751bff1025f..265b16cd7fe5d 100644 --- a/hw/opentitan/ot_lc_ctrl.c +++ b/hw/opentitan/ot_lc_ctrl.c @@ -400,6 +400,7 @@ struct OtLcCtrlState { bool force_raw; /* survivability mode */ uint8_t volatile_raw_unlock_bm; /* xslot-indexed bitmap */ uint8_t state_invalid_error_bm; /* error bitmap */ + char *hexstr; /* properties */ char *ot_id; @@ -695,31 +696,15 @@ LC_STATES_TPL[NUM_LC_STATE][LC_STATE_WORDS] = { #undef B #ifdef OT_LC_CTRL_DEBUG +#define OT_LC_CTRL_HEXSTR_SIZE 256u #define TRACE_LC_CTRL(msg, ...) \ qemu_log("%s: " msg "\n", __func__, ##__VA_ARGS__); +#define ot_lc_ctrl_hexdump(_s_, _b_, _l_) \ + ot_common_lhexdump((const uint8_t *)_b_, _l_, false, (_s_)->hexstr, \ + OT_LC_CTRL_HEXSTR_SIZE) #else #define TRACE_LC_CTRL(msg, ...) -#endif - -#ifdef OT_LC_CTRL_DEBUG -static char hexbuf[256u]; -static const char *ot_lc_ctrl_hexdump(const void *data, size_t size) -{ - static const char _hex[] = "0123456789abcdef"; - const uint8_t *buf = (const uint8_t *)data; - - if (size > ((sizeof(hexbuf) / 2u) - 2u)) { - size = sizeof(hexbuf) / 2u - 2u; - } - - char *hexstr = hexbuf; - for (size_t ix = 0; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexbuf; -} +#define ot_lc_ctrl_hexdump(_s_, _b_, _l_) #endif static void ot_lc_ctrl_resume_transition(OtLcCtrlState *s); @@ -1102,7 +1087,8 @@ static void ot_lc_ctrl_kmac_request(OtLcCtrlState *s) stl_le_p(&req.msg_data[0], token[0]); stl_le_p(&req.msg_data[sizeof(uint32_t)], token[1]); - TRACE_LC_CTRL("KMAC input: %s", ot_lc_ctrl_hexdump(&req.msg_data[0], 8u)); + TRACE_LC_CTRL("KMAC input: %s", + ot_lc_ctrl_hexdump(s, &req.msg_data[0], 8u)); OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); kc->app_request(s->kmac, s->kmac_app, &req); @@ -1133,7 +1119,7 @@ static void ot_lc_ctrl_kmac_handle_resp(void *opaque, const OtKMACAppRsp *rsp) s->hash_token.hi = dig0 ^ dig1; TRACE_LC_CTRL("MKAC output: %s", - ot_lc_ctrl_hexdump(&s->hash_token.lo, 16u)); + ot_lc_ctrl_hexdump(s, &s->hash_token.lo, 16u)); ot_lc_ctrl_resume_transition(s); } @@ -1235,14 +1221,14 @@ static void ot_lc_ctrl_load_otp_hw_cfg(OtLcCtrlState *s) int socdbg_ix = OT_SOCDBG_ST_PROD; TRACE_LC_CTRL("soc_dbg_state: %s", - ot_lc_ctrl_hexdump(hw_cfg->soc_dbg_state, + ot_lc_ctrl_hexdump(s, hw_cfg->soc_dbg_state, sizeof(OtLcCtrlSocDbgValue))); for (unsigned six = 0; six < OT_SOCDBG_ST_COUNT; six++) { bool match = !memcmp(hw_cfg->soc_dbg_state, s->socdbgs[six], sizeof(OtLcCtrlSocDbgValue)); TRACE_LC_CTRL("soc_dbg ref[%u]: %s, match: %u", six, - ot_lc_ctrl_hexdump(s->socdbgs[six], + ot_lc_ctrl_hexdump(s, s->socdbgs[six], sizeof(OtLcCtrlSocDbgValue)), match); if (match) { @@ -2343,6 +2329,10 @@ static void ot_lc_ctrl_init(Object *obj) s->pwc_lc_bh = qemu_bh_new(&ot_lc_ctrl_pwr_lc_bh, s); s->escalate_bh = qemu_bh_new(&ot_lc_ctrl_escalate_bh, s); + +#ifdef OT_LC_CTRL_DEBUG + s->hexstr = g_new0(char, OT_LC_CTRL_HEXSTR_SIZE); +#endif } static void ot_lc_ctrl_class_init(ObjectClass *klass, void *data) From 4bb310887a9c0d46b2e7c9fefe4e71fc78eeda79 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 16:02:42 +0200 Subject: [PATCH 143/175] [ot] hw/opentitan: ot_otp_dj: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_otp_dj.c | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 12562a960e812..cd226932b7098 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -820,6 +820,7 @@ struct OtOTPDjState { OtOTPStorage *otp; OtOTPHWCfg *hw_cfg; OtOTPTokens *tokens; + char *hexstr; char *ot_id; BlockBackend *blk; /* OTP host backend */ @@ -1077,30 +1078,14 @@ ot_otp_dj_lci_change_state_line(OtOTPDjState *s, OtOTPLCIState state, int line); sizeof(uint32_t) * NUM_DIGEST_WORDS)) #ifdef OT_OTP_DEBUG +#define OT_OTP_HEXSTR_SIZE 256u #define TRACE_OTP(msg, ...) qemu_log("%s: " msg "\n", __func__, ##__VA_ARGS__); +#define ot_otp_hexdump(_s_, _b_, _l_) \ + ot_common_lhexdump((const uint8_t *)_b_, _l_, false, (_s_)->hexstr, \ + OT_OTP_HEXSTR_SIZE) #else #define TRACE_OTP(msg, ...) -#endif - -#ifdef OT_OTP_DEBUG -static char hexbuf[256u]; -static const char *ot_otp_hexdump(const void *data, size_t size) -{ - static const char _hex[] = "0123456789abcdef"; - const uint8_t *buf = (const uint8_t *)data; - - if (size > ((sizeof(hexbuf) / 2u) - 2u)) { - size = sizeof(hexbuf) / 2u - 2u; - } - - char *hexstr = hexbuf; - for (size_t ix = 0; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexbuf; -} +#define ot_otp_hexdump(_s_, _b_, _l_) #endif static void ot_otp_dj_update_irqs(OtOTPDjState *s) @@ -1842,7 +1827,7 @@ static void ot_otp_dj_check_partition_integrity(OtOTPDjState *s, unsigned ix) pctrl->buffer.digest); TRACE_OTP("compute digest of %s: %016llx from %s\n", PART_NAME(ix), - digest, ot_otp_hexdump(pctrl->buffer.data, part_size)); + digest, ot_otp_hexdump(s, pctrl->buffer.data, part_size)); pctrl->failed = true; /* this is a fatal error */ @@ -2334,7 +2319,7 @@ static void ot_otp_dj_dai_digest(OtOTPDjState *s) s->dai->partition = partition; TRACE_OTP("%s: %s: next digest %016llx from %s\n", __func__, s->ot_id, - pctrl->buffer.next_digest, ot_otp_hexdump(data, part_size)); + pctrl->buffer.next_digest, ot_otp_hexdump(s, data, part_size)); DAI_CHANGE_STATE(s, OTP_DAI_DIG_WAIT); @@ -4031,7 +4016,7 @@ static void ot_otp_dj_configure_inv_default_parts(OtOTPDjState *s) } TRACE_OTP("inv_default_part[%s] %s", PART_NAME(ix), - ot_otp_hexdump(s->inv_default_parts[ix], part->size)); + ot_otp_hexdump(s, s->inv_default_parts[ix], part->size)); } } @@ -4285,6 +4270,10 @@ static void ot_otp_dj_init(Object *obj) int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); ot_prng_reseed(s->keygen->prng, (uint32_t)now); + +#ifdef OT_OTP_DEBUG + s->hexstr = g_new0(char, OT_OTP_HEXSTR_SIZE); +#endif } static void ot_otp_dj_class_init(ObjectClass *klass, void *data) From 543d5ed2c62dd00745ac66270c2a8540fbc59505 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 16:09:06 +0200 Subject: [PATCH 144/175] [ot] hw/opentitan: ot_present: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_present.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/hw/opentitan/ot_present.c b/hw/opentitan/ot_present.c index 1e3d612276e71..db330b766047b 100644 --- a/hw/opentitan/ot_present.c +++ b/hw/opentitan/ot_present.c @@ -16,6 +16,8 @@ #include "qemu/osdep.h" #include "qemu/bitops.h" +#include "qemu/log.h" +#include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_present.h" /* warning: not thread safe when enabled */ @@ -30,6 +32,9 @@ typedef struct { struct OtPresentState { uint64_t keys[OT_PRESENT_ROUND]; +#ifdef OT_PRESENT_ROUND + char *hexstr; +#endif }; static const uint8_t OT_PRESENT_SBOX4[16u] = { @@ -146,24 +151,12 @@ static uint64_t perm_inv_layer(uint64_t data) } #ifdef OT_PRESENT_DEBUG -static char hexbuf[256u]; -static const char *ot_present_hexdump(const void *data, size_t size) -{ - static const char _hex[] = "0123456789abcdef"; - const uint8_t *buf = (const uint8_t *)data; - - if (size > ((sizeof(hexbuf) / 2u) - 2u)) { - size = sizeof(hexbuf) / 2u - 2u; - } - - char *hexstr = hexbuf; - for (size_t ix = 0; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexbuf; -} +#define OT_PRESENT_HEXSTR_SIZE 256u +#define ot_present_hexdump(_s_, _b_, _l_) \ + ot_common_lhexdump((const uint8_t *)_b_, _l_, false, (_s_)->hexstr, \ + OT_PRESENT_HEXSTR_SIZE) +#else +#define ot_present_hexdump(_s_, _b_, _l_) #endif /*----------------------------------------------------------------------------*/ @@ -172,7 +165,11 @@ static const char *ot_present_hexdump(const void *data, size_t size) OtPresentState *ot_present_new(void) { - return g_new0(OtPresentState, 1u); + OtPresentState *ps = g_new0(OtPresentState, 1u); +#ifdef OT_PRESENT_DEBUG + ps->hexstr = g_new0(char, OT_PRESENT_HEXSTR_SIZE); +#endif + return ps; } void ot_present_free(OtPresentState *ps) @@ -184,7 +181,7 @@ void ot_present_init(OtPresentState *ps, const uint8_t *key) { OtPresentKey k128; - TRACE_PRESENT("present init %s", ot_present_hexdump(key, 16u)); + TRACE_PRESENT("present init %s", ot_present_hexdump(ps, key, 16u)); memcpy(&k128.hi, &key[8u], sizeof(uint64_t)); memcpy(&k128.lo, &key[0u], sizeof(uint64_t)); From 7b6d1b76ebc19a1fd2a1a717c9a5c20ea24bc9b2 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 16:12:23 +0200 Subject: [PATCH 145/175] [ot] hw/opentitan: ot_sram_ctrl: replace hex string debug function Use ot_common implementation, remove global variable which could cause havoc with multiple instances. Signed-off-by: Emmanuel Blot --- hw/opentitan/ot_sram_ctrl.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/hw/opentitan/ot_sram_ctrl.c b/hw/opentitan/ot_sram_ctrl.c index 62968209b5d97..0b3e4b5f2e028 100644 --- a/hw/opentitan/ot_sram_ctrl.c +++ b/hw/opentitan/ot_sram_ctrl.c @@ -133,6 +133,7 @@ struct OtSramCtrlState { bool initializing; /* CTRL.INIT has been requested */ bool otp_ifetch; /* whether OTP enable execution from this RAM */ bool csr_ifetch; /* whether CSR enable execution from this RAM */ + char *hexstr; char *ot_id; OtOTPState *otp_ctrl; /* optional */ @@ -150,28 +151,15 @@ struct OtSramCtrlClass { }; #ifdef OT_SRAM_CTRL_DEBUG +#define OT_SRAM_CTRL_HEXSTR_SIZE 256u #define TRACE_SRAM_CTRL(msg, ...) \ qemu_log("%s: " msg "\n", __func__, ##__VA_ARGS__); -static char hexbuf[256u]; -static const char *ot_sram_ctrl_hexdump(const void *data, size_t size) -{ - static const char _hex[] = "0123456789abcdef"; - const uint8_t *buf = (const uint8_t *)data; - - if (size > ((sizeof(hexbuf) / 2u) - 2u)) { - size = sizeof(hexbuf) / 2u - 2u; - } - - char *hexstr = hexbuf; - for (size_t ix = 0; ix < size; ix++) { - hexstr[(ix * 2u)] = _hex[(buf[ix] >> 4u) & 0xfu]; - hexstr[(ix * 2u) + 1u] = _hex[buf[ix] & 0xfu]; - } - hexstr[size * 2u] = '\0'; - return hexbuf; -} +#define ot_sram_ctrl_hexdump(_s_, _b_, _l_) \ + ot_common_lhexdump((const uint8_t *)_b_, _l_, false, (_s_)->hexstr, \ + OT_SRAM_CTRL_HEXSTR_SIZE) #else #define TRACE_SRAM_CTRL(msg, ...) +#define ot_sram_ctrl_hexdump(_s_, _b_, _l_) #endif static inline unsigned ot_sram_ctrl_get_u64_slot(unsigned idx) @@ -305,11 +293,11 @@ static void ot_sram_ctrl_reseed(OtSramCtrlState *s) oc->get_otp_key(s->otp_ctrl, OTP_KEY_SRAM, s->otp_key); TRACE_SRAM_CTRL("Scrambing seed: %s (valid: %u)", - ot_sram_ctrl_hexdump(s->otp_key->seed, + ot_sram_ctrl_hexdump(s, s->otp_key->seed, s->otp_key->seed_size), s->otp_key->seed_valid); TRACE_SRAM_CTRL("Scrambing nonce: %s", - ot_sram_ctrl_hexdump(s->otp_key->nonce, + ot_sram_ctrl_hexdump(s, s->otp_key->nonce, s->otp_key->nonce_size)); if (s->otp_key->seed_valid) { @@ -865,6 +853,10 @@ static void ot_sram_ctrl_init(Object *obj) timer_new_ns(OT_VIRTUAL_CLOCK, &ot_sram_ctrl_init_chunk_fn, s); s->prng = ot_prng_allocate(); s->otp_key = g_new0(OtOTPKey, 1u); + +#ifdef OT_SRAM_CTRL_DEBUG + s->hexstr = g_new0(char, OT_SRAM_CTRL_HEXSTR_SIZE); +#endif } static void ot_sram_ctrl_class_init(ObjectClass *klass, void *data) From f6f60343b3081dc9c16d1b7c07a968671cf283f8 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 19:21:37 +0200 Subject: [PATCH 146/175] [ot] hw/opentitan: ot_kmac: use OT common idiom to define KMAC interface Signed-off-by: Emmanuel Blot --- include/hw/opentitan/ot_kmac.h | 46 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/include/hw/opentitan/ot_kmac.h b/include/hw/opentitan/ot_kmac.h index 8ab1e0e37b97a..3d46b18d72dd3 100644 --- a/include/hw/opentitan/ot_kmac.h +++ b/include/hw/opentitan/ot_kmac.h @@ -100,36 +100,30 @@ typedef struct { */ typedef void (*OtKmacResponse)(void *opaque, const OtKMACAppRsp *rsp); -/** - * Connect a application to the KMAC device. - * - * @s the KMAC device. - * @app_idx the application index. - * @cfg pointer to the KMAC configuration for this application. - * @fn the function to call when an request has been processed. - * @opaque a opaque pointer to forward to the response function. - */ -typedef void (*OtKmacConnectApp)(OtKMACState *s, unsigned app_idx, - const OtKMACAppCfg *cfg, OtKmacResponse fn, - void *opaque); - -/** - * Send a new application request to the KMAC device. - * - * @s the KMAC device. - * @app_idx the application index. - * @req the KMAC request to process. - */ -typedef void (*OtKmacAppRequest)(OtKMACState *s, unsigned app_idx, - const OtKMACAppReq *req); - struct OtKMACClass { SysBusDeviceClass parent_class; ResettablePhases parent_phases; - OtKmacConnectApp connect_app; - OtKmacAppRequest app_request; + /* + * Connect a application to the KMAC device. + * + * @app_idx the application index. + * @cfg pointer to the KMAC configuration for this application. + * @fn the function to call when an request has been processed. + * @opaque a opaque pointer to forward to the response function. + */ + void (*connect_app)(OtKMACState *s, unsigned app_idx, + const OtKMACAppCfg *cfg, OtKmacResponse fn, + void *opaque); + + /* + * Send a new application request to the KMAC device. + * + * @app_idx the application index. + * @req the KMAC request to process. + */ + void (*app_request)(OtKMACState *s, unsigned app_idx, + const OtKMACAppReq *req); }; - #endif /* HW_OPENTITAN_OT_KMAC_H */ From bdefda776c15f8981267973788653df166d83787 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 19:19:39 +0200 Subject: [PATCH 147/175] [ot] hw/opentitan: ot_key_sink: define a new interface for key sideloaders Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 3 ++ hw/opentitan/meson.build | 1 + hw/opentitan/ot_key_sink.c | 42 ++++++++++++++++++++++ include/hw/opentitan/ot_key_sink.h | 58 ++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 hw/opentitan/ot_key_sink.c create mode 100644 include/hw/opentitan/ot_key_sink.h diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 406e168351ef2..31ac1ec134903 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -74,6 +74,9 @@ config OT_IBEX_WRAPPER select OT_VMAPPER bool +config OT_KEY_SINK + bool + config OT_KEYMGR_DPE select OT_LC_CTRL select OT_ROM_CTRL diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build index 53b9144db8bd5..1c66e4465c6da 100644 --- a/hw/opentitan/meson.build +++ b/hw/opentitan/meson.build @@ -25,6 +25,7 @@ system_ss.add(when: 'CONFIG_OT_GPIO_EG', if_true: files('ot_gpio_eg.c')) system_ss.add(when: 'CONFIG_OT_HMAC', if_true: [files('ot_hmac.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_I2C_DJ', if_true: files('ot_i2c_dj.c')) system_ss.add(when: 'CONFIG_OT_IBEX_WRAPPER', if_true: files('ot_ibex_wrapper.c')) +system_ss.add(when: 'CONFIG_OT_KEY_SINK', if_true: files('ot_key_sink.c')) system_ss.add(when: 'CONFIG_OT_KEYMGR_DPE', if_true: files('ot_keymgr_dpe.c')) system_ss.add(when: 'CONFIG_OT_KMAC', if_true: [files('ot_kmac.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_LC_CTRL', if_true: files('ot_lc_ctrl.c')) diff --git a/hw/opentitan/ot_key_sink.c b/hw/opentitan/ot_key_sink.c new file mode 100644 index 0000000000000..b26f76e708cd9 --- /dev/null +++ b/hw/opentitan/ot_key_sink.c @@ -0,0 +1,42 @@ +/* + * QEMU OpenTitan Key Sink interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/opentitan/ot_key_sink.h" + +static const TypeInfo ot_key_sink_info = { + .name = TYPE_OT_KEY_SINK_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(OtKeySinkIfClass), +}; + +static void ot_key_sink_register_types(void) +{ + type_register_static(&ot_key_sink_info); +} + +type_init(ot_key_sink_register_types); diff --git a/include/hw/opentitan/ot_key_sink.h b/include/hw/opentitan/ot_key_sink.h new file mode 100644 index 0000000000000..f0b3da48668a7 --- /dev/null +++ b/include/hw/opentitan/ot_key_sink.h @@ -0,0 +1,58 @@ +/* + * QEMU OpenTitan Key Sink interface + * + * Copyright (c) 2025 Rivos, Inc. + * + * Author(s): + * Emmanuel Blot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_OPENTITAN_OT_KEY_SINK_H +#define HW_OPENTITAN_OT_KEY_SINK_H + +#include "qom/object.h" + +#define TYPE_OT_KEY_SINK_IF "ot-key_sink_if" +typedef struct OtKeySinkIfClass OtKeySinkIfClass; +DECLARE_CLASS_CHECKERS(OtKeySinkIfClass, OT_KEY_SINK_IF, TYPE_OT_KEY_SINK_IF) +#define OT_KEY_SINK_IF(_obj_) \ + INTERFACE_CHECK(OtKeySinkIf, (_obj_), TYPE_OT_KEY_SINK_IF) + +typedef struct OtKeySinkIf OtKeySinkIf; + +struct OtKeySinkIfClass { + InterfaceClass parent_class; + + /* + * Push key material to a key sink. + * + * Key is split in two shares, such as: key = share0 ^ share1 + * + * @share0 first key share, may be NULL + * @share1 second key share, may be NULL + * @key_len the length of the key shares in bytes, may be 0 + * @valid whether the key is valid or not + */ + void (*push_key)(OtKeySinkIf *ifd, const uint8_t *share0, + const uint8_t *share1, size_t key_len, bool valid); +}; + +#endif /* HW_OPENTITAN_OT_KEY_SINK_H */ From d2e09a6c92fdd31051c60488ed0d17fb0a344d01 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 19:20:25 +0200 Subject: [PATCH 148/175] [ot] hw/opentitan: ot_otbn: implement the OtKeySinkIf interface Signed-off-by: Emmanuel Blot --- docs/opentitan/darjeeling.md | 8 ++- hw/opentitan/Kconfig | 3 +- hw/opentitan/ot_otbn.c | 61 +++++++++++++++++++-- hw/opentitan/otbn/otbn/src/csrs.rs | 72 ++++++++++++++----------- hw/opentitan/otbn/otbn/src/insn_exec.rs | 15 +++--- hw/opentitan/otbn/otbn/src/key.rs | 57 ++++++++++++++++++++ hw/opentitan/otbn/otbn/src/lib.rs | 2 + hw/opentitan/otbn/otbn/src/otbn.rs | 7 ++- hw/opentitan/otbn/otbn/src/proxy.rs | 46 +++++++++++++--- include/hw/opentitan/ot_otbn.h | 2 + include/hw/opentitan/otbn/otbnproxy.h | 9 ++-- 11 files changed, 227 insertions(+), 55 deletions(-) create mode 100644 hw/opentitan/otbn/otbn/src/key.rs diff --git a/docs/opentitan/darjeeling.md b/docs/opentitan/darjeeling.md index 6126a8e7ad29c..2bcce9dfc390e 100644 --- a/docs/opentitan/darjeeling.md +++ b/docs/opentitan/darjeeling.md @@ -225,8 +225,12 @@ There are two modes to handle address remapping, with different limitations: ### OTBN -* `-global ot-otbn.logfile=` dumps executed instructions on OTBN core into the specified - filename. Beware that is even further slows down execution speed, which could likely result into +* `-global ot-otbn.logfile=` output OTBN execution message to the specified logfile. When + _logasm_ option (see below) is not enabled, only execution termination and error messages are + logged. `stderr` can be used to log the messages to the standard error stream instead of a file. + +* `-global ot-otbn.logasm=` dumps executed instructions on OTBN core into the _logfile_ + filename. Beware that this further slows down execution speed, which could likely result in the guest application on the Ibex core to time out. ### OTP diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 31ac1ec134903..44d1c8696d5e5 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -94,8 +94,9 @@ config OT_MBX bool config OT_OTBN - bool + select OT_KEY_SINK select OT_BIGNUMBER + bool config OT_OTP select OT_OTP_BE_IF diff --git a/hw/opentitan/ot_otbn.c b/hw/opentitan/ot_otbn.c index fd04dd405bc9c..459a69be2c469 100644 --- a/hw/opentitan/ot_otbn.c +++ b/hw/opentitan/ot_otbn.c @@ -41,6 +41,7 @@ #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" #include "hw/opentitan/ot_fifo32.h" +#include "hw/opentitan/ot_key_sink.h" #include "hw/opentitan/ot_otbn.h" #include "hw/opentitan/otbn/otbnproxy.h" #include "hw/qdev-properties.h" @@ -51,6 +52,8 @@ #include "hw/sysbus.h" #include "trace.h" +#undef OT_OTBN_DEBUG + /* clang-format off */ REG32(INTR_STATE, 0x00u) SHARED_FIELD(INTR_DONE, 0u, 1u) @@ -136,11 +139,11 @@ typedef struct { bool entropy_requested; /* EDN request on-going */ } OtOTBNRandom; -typedef enum { +enum { ALERT_FATAL, ALERT_RECOVERABLE, ALERT_COUNT, -} OtOtbnAlert; +}; struct OtOTBNState { /* */ @@ -161,6 +164,7 @@ struct OtOTBNState { OTBNProxy proxy; unsigned pclk; /* Current input clock */ const char *clock_src_name; /* IRQ name once connected */ + char *hexstr; uint32_t intr_state; uint32_t intr_enable; @@ -184,6 +188,17 @@ struct OtOTBNClass { ResettablePhases parent_phases; }; +#ifdef OT_OTBN_DEBUG +#define OT_OTBN_HEXSTR_SIZE (OT_OTBN_KEY_SIZE * 2u + 2u) +#define TRACE_OTBN(msg, ...) qemu_log("%s: " msg "\n", __func__, ##__VA_ARGS__); +#define ot_otbn_hexdump(_s_, _b_, _l_) \ + ot_common_lhexdump((const uint8_t *)_b_, _l_, false, (_s_)->hexstr, \ + OT_OTBN_HEXSTR_SIZE) +#else +#define TRACE_OTBN(msg, ...) +#define ot_otbn_hexdump(_s_, _b_, _l_) +#endif + static void ot_otbn_request_entropy(OtOTBNRandom *rnd); static bool ot_otbn_is_idle(OtOTBNState *s) @@ -326,7 +341,7 @@ static void ot_otbn_fill_entropy(void *opaque, uint32_t bits, bool fips) num *= sizeof(uint32_t); g_assert(s != NULL); unsigned rnd_ix = (unsigned)(rnd - &s->rnds[0]); - int res; + bool res; switch (rnd_ix) { case OT_OTBN_URND: trace_ot_otbn_proxy_push_entropy(s->ot_id, "urnd", !rnd->no_fips); @@ -344,7 +359,7 @@ static void ot_otbn_fill_entropy(void *opaque, uint32_t bits, bool fips) } ot_fifo32_reset(&rnd->packer); rnd->no_fips = false; - if (res) { + if (!res) { trace_ot_otbn_error(s->ot_id, "cannot push entropy"); } } @@ -429,6 +444,32 @@ static void ot_otbn_clock_input(void *opaque, int irq, int level) /* TODO: disable OTBN execution when PCLK is 0 */ } +static void ot_otbn_push_key(OtKeySinkIf *ifd, const uint8_t *share0, + const uint8_t *share1, size_t key_len, bool valid) +{ + g_assert(!key_len || key_len == OT_OTBN_KEY_SIZE); + + OtOTBNState *s = OT_OTBN(ifd); + static const uint8_t empty_share[OT_OTBN_KEY_SIZE] = { 0 }; + + if (!key_len || !share0) { + key_len = sizeof(empty_share); + share0 = empty_share; + } + if (!key_len || !share1) { + key_len = sizeof(empty_share); + share1 = empty_share; + } + + TRACE_OTBN("%s: share0 %s, valid: %u", s->ot_id, + ot_otbn_hexdump(s, share0, OT_OTBN_KEY_SIZE), valid); + + bool res = ot_otbn_proxy_push_key(s->proxy, share0, share1, key_len, valid); + if (!res) { + trace_ot_otbn_error(s->ot_id, "Cannot push sideload key"); + } +} + static uint64_t ot_otbn_regs_read(void *opaque, hwaddr addr, unsigned size) { OtOTBNState *s = opaque; @@ -790,6 +831,10 @@ static void ot_otbn_init(Object *obj) ot_otbn_proxy_new(&ot_otbn_trigger_entropy_req, &s->rnds[OT_OTBN_URND], &ot_otbn_trigger_entropy_req, &s->rnds[OT_OTBN_RND], &ot_otbn_signal_on_completion, s); + +#ifdef OT_OTBN_DEBUG + s->hexstr = g_new0(char, OT_OTBN_HEXSTR_SIZE); +#endif } static void ot_otbn_class_init(ObjectClass *klass, void *data) @@ -805,6 +850,9 @@ static void ot_otbn_class_init(ObjectClass *klass, void *data) OtOTBNClass *oc = OT_OTBN_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_otbn_reset_enter, NULL, &ot_otbn_reset_exit, &oc->parent_phases); + + OtKeySinkIfClass *kc = OT_KEY_SINK_IF_CLASS(klass); + kc->push_key = &ot_otbn_push_key; } static const TypeInfo ot_otbn_info = { @@ -814,6 +862,11 @@ static const TypeInfo ot_otbn_info = { .instance_init = &ot_otbn_init, .class_size = sizeof(OtOTBNClass), .class_init = &ot_otbn_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_OT_KEY_SINK_IF }, + {}, + }, }; static void ot_otbn_register_types(void) diff --git a/hw/opentitan/otbn/otbn/src/csrs.rs b/hw/opentitan/otbn/otbn/src/csrs.rs index 8593e66d21ee4..46ae6515887f3 100644 --- a/hw/opentitan/otbn/otbn/src/csrs.rs +++ b/hw/opentitan/otbn/otbn/src/csrs.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex}; use ethnum::{u256, U256}; use super::insn_proc; +use super::key; use super::otbn::FlagMode; use super::random; use super::{CSR, WSR}; @@ -333,6 +334,36 @@ impl CSR for CSRRndPrefetcher { } } +/// CryptoSecure Random generator +struct CSRWideKey { + key: Arc, + share: u8, + high: bool, +} + +impl CSRWideKey { + pub fn new(key: Arc, share: u8, high: bool) -> Self { + Self { key, share, high } + } +} + +impl WSR for CSRWideKey { + fn read(&self) -> Result { + let store = self.key.store.lock().unwrap(); + if !store.valid { + return Err(ExceptionCause::EKeyInvalid); + } + let share = if self.share == 0 { store.lo } else { store.hi }; + let value = share[self.high as usize]; + Ok(value) + } + + fn write(&mut self, _val: u256) -> Result<(), ExceptionCause> { + // as per OTBN definition, do not generate an error for R/O CSR + Ok(()) + } +} + #[derive(Default)] struct CSRWideGeneric { pub val: u256, @@ -379,16 +410,16 @@ pub struct CSRSet { wrnd: CSRWideRnd, wurnd: CSRWideUrnd, acc: CSRWideGeneric, - key_s0_l: CSRWideGeneric, - key_s0_h: CSRWideGeneric, - key_s1_l: CSRWideGeneric, - key_s1_h: CSRWideGeneric, + key_s0_l: CSRWideKey, + key_s0_h: CSRWideKey, + key_s1_l: CSRWideKey, + key_s1_h: CSRWideKey, shared_flags: SharedFlags, } impl CSRSet { - pub fn new(urnd: Arc>, rnd: Arc) -> Self { + pub fn new(urnd: Arc>, rnd: Arc, key: Arc) -> Self { let mut csrs = Self { fg0: CSRFlagGroup::default(), fg1: CSRFlagGroup::default(), @@ -401,10 +432,10 @@ impl CSRSet { wrnd: CSRWideRnd::new(rnd), wurnd: CSRWideUrnd::new(urnd.clone()), acc: CSRWideGeneric::default(), - key_s0_l: CSRWideGeneric::default(), - key_s0_h: CSRWideGeneric::default(), - key_s1_l: CSRWideGeneric::default(), - key_s1_h: CSRWideGeneric::default(), + key_s0_l: CSRWideKey::new(key.clone(), 0, false), + key_s0_h: CSRWideKey::new(key.clone(), 0, true), + key_s1_l: CSRWideKey::new(key.clone(), 1, false), + key_s1_h: CSRWideKey::new(key.clone(), 1, true), shared_flags: SharedFlags::default(), }; csrs.fg0.plug(&csrs.shared_flags, FlagMode::Fg0); @@ -520,29 +551,6 @@ impl CSRSet { } pub fn set_test_mode(&mut self, enable: bool) { - if enable { - self.key_s0_l.val = U256::from_str_radix( - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - 16, - ) - .unwrap(); - self.key_s0_h.val = - U256::from_str_radix("deadbeefdeadbeefdeadbeefdeadbeef", 16).unwrap(); - self.key_s1_l.val = U256::from_str_radix( - "baadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d", - 16, - ) - .unwrap(); - self.key_s1_h.val = - U256::from_str_radix("baadf00dbaadf00dbaadf00dbaadf00d", 16).unwrap(); - } else { - self.key_s0_l.val = U256::from(0u32); - self.key_s0_h.val = U256::from(0u32); - self.key_s1_l.val = U256::from(0u32); - self.key_s1_h.val = U256::from(0u32); - } - - // self.wrnd.test_mode = enable; self.wurnd.test_mode = enable; } } diff --git a/hw/opentitan/otbn/otbn/src/insn_exec.rs b/hw/opentitan/otbn/otbn/src/insn_exec.rs index bf440824aec65..661f1dbb5ba74 100644 --- a/hw/opentitan/otbn/otbn/src/insn_exec.rs +++ b/hw/opentitan/otbn/otbn/src/insn_exec.rs @@ -15,6 +15,7 @@ use super::csrs; use super::insn_decode; use super::insn_format; use super::insn_proc; +use super::key; use super::random; use super::Memory; use crate::{ExceptionCause, PRNG}; @@ -87,7 +88,7 @@ pub struct HartState { } impl HartState { - pub fn new(urnd: Arc>, rnd: Arc) -> Self { + pub fn new(urnd: Arc>, rnd: Arc, key: Arc) -> Self { HartState { registers: [0; 32], wregisters: [0.as_u256(); 32], @@ -95,7 +96,7 @@ impl HartState { loopstack: Vec::with_capacity(8), hwstack: Vec::with_capacity(8), updated: StateTracker::default(), - csr_set: csrs::CSRSet::new(urnd, rnd), + csr_set: csrs::CSRSet::new(urnd, rnd, key), } } @@ -159,7 +160,7 @@ impl HartState { ))?; if let Err(exc) = csr.write(data) { - return Err(InstructionTrap::Exception(exc, Some(csr_addr))); + return Err(InstructionTrap::Exception(exc, Some(csr_addr))); } self.updated.csr = Some((false, csr_addr)); Ok(()) @@ -174,7 +175,8 @@ impl HartState { Some(csr_addr), ))?; - csr.read().map_err(|exc| InstructionTrap::Exception(exc, Some(csr_addr))) + csr.read() + .map_err(|exc| InstructionTrap::Exception(exc, Some(csr_addr))) } fn write_wsr(&mut self, wsr_addr: u32, data: u256) -> Result<(), InstructionTrap> { @@ -187,7 +189,7 @@ impl HartState { ))?; if let Err(exc) = wsr.write(data) { - return Err(InstructionTrap::Exception(exc, Some(wsr_addr))); + return Err(InstructionTrap::Exception(exc, Some(wsr_addr))); } self.updated.csr = Some((true, wsr_addr)); Ok(()) @@ -202,7 +204,8 @@ impl HartState { Some(wsr_addr), ))?; - wsr.read().map_err(|exc| InstructionTrap::Exception(exc, Some(wsr_addr))) + wsr.read() + .map_err(|exc| InstructionTrap::Exception(exc, Some(wsr_addr))) } fn set_mlz_wide_flags(&mut self, fg: usize, carry: bool, value: u256) { diff --git a/hw/opentitan/otbn/otbn/src/key.rs b/hw/opentitan/otbn/otbn/src/key.rs new file mode 100644 index 0000000000000..6df160970eae4 --- /dev/null +++ b/hw/opentitan/otbn/otbn/src/key.rs @@ -0,0 +1,57 @@ +// Copyright 2023 Rivos, Inc. +// Licensed under the Apache License Version 2.0, with LLVM Exceptions, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use std::convert::TryInto; +use std::sync::Mutex; + +use ethnum::{u256, U256}; + +#[derive(Default)] +pub struct KeyStore { + pub lo: [u256; 2], + pub hi: [u256; 2], + pub valid: bool, +} + +pub struct Key { + pub store: Mutex, +} + +impl Default for Key { + fn default() -> Self { + let store = KeyStore::default(); + Self { + store: Mutex::new(store), + } + } +} + +impl Key { + pub fn new() -> Self { + Self::default() + } + + pub fn clear(&mut self) { + /* called from OTBN proxy */ + let mut store = self.store.lock().unwrap(); + store.lo = [U256::from(0u32), U256::from(0u32)]; + store.hi = [U256::from(0u32), U256::from(0u32)]; + store.valid = false; + } + + pub fn fill(&self, share0: &[u8; 48], share1: &[u8; 48], valid: bool) { + /* called from OTBN proxy */ + let lo0 = U256::from_le_bytes(share0[0..32].try_into().unwrap()); + let lo1 = U256::from_words(0, u128::from_le_bytes(share0[32..48].try_into().unwrap())); + + let hi0 = U256::from_le_bytes(share1[0..32].try_into().unwrap()); + let hi1 = U256::from_words(0, u128::from_le_bytes(share1[32..48].try_into().unwrap())); + + let mut store = self.store.lock().unwrap(); + + store.lo = [lo0, lo1]; + store.hi = [hi0, hi1]; + store.valid = valid; + } +} diff --git a/hw/opentitan/otbn/otbn/src/lib.rs b/hw/opentitan/otbn/otbn/src/lib.rs index 142baf564180c..6492798c1a02a 100644 --- a/hw/opentitan/otbn/otbn/src/lib.rs +++ b/hw/opentitan/otbn/otbn/src/lib.rs @@ -18,6 +18,7 @@ pub mod insn_disasm; pub mod insn_exec; pub mod insn_format; pub mod insn_proc; +pub mod key; pub mod memory; pub mod otbn; pub mod proxy; @@ -32,6 +33,7 @@ pub enum ExceptionCause { ECallStack = 1 << 2, EIllegalInsn = 1 << 3, ELoop = 1 << 4, + EKeyInvalid = 1 << 5, ERndRepChkFail = 1 << 6, ERndFipsChkFail = 1 << 7, EFatal = 1 << 20, diff --git a/hw/opentitan/otbn/otbn/src/otbn.rs b/hw/opentitan/otbn/otbn/src/otbn.rs index 5eb383450d819..a49e4d789941c 100644 --- a/hw/opentitan/otbn/otbn/src/otbn.rs +++ b/hw/opentitan/otbn/otbn/src/otbn.rs @@ -21,6 +21,7 @@ use super::csrs; use super::insn_decode; use super::insn_disasm; use super::insn_exec; +use super::key; use super::memory; use super::random; use super::Memory; @@ -122,6 +123,7 @@ impl Executer { dmem: Arc>, syncurnd: Arc, rnd: Arc, + key: Arc, on_complete: Option>, log_name: Option, log_asm: bool, @@ -137,7 +139,7 @@ impl Executer { log_file = None; } Self { - hart_state: insn_exec::HartState::new(syncurnd.urnd(), rnd), + hart_state: insn_exec::HartState::new(syncurnd.urnd(), rnd, key), imem, dmem, channel, @@ -157,6 +159,7 @@ impl Executer { dmem: Arc>, urnd: Arc, rnd: Arc, + key: Arc, on_complete: Option>, log_name: Option, log_asm: bool, @@ -168,6 +171,7 @@ impl Executer { dmem, urnd, rnd, + key, on_complete, log_name, log_asm, @@ -276,6 +280,7 @@ impl Executer { ExceptionCause::ECallStack => (false, ErrBits::CALL_STACK.bits()), ExceptionCause::EIllegalInsn => (false, ErrBits::ILLEGAL_INSN.bits()), ExceptionCause::ELoop => (false, ErrBits::LOOP.bits()), + ExceptionCause::EKeyInvalid => (false, ErrBits::KEY_INVALID.bits()), ExceptionCause::ERndRepChkFail => (false, ErrBits::RND_REP_CHK_FAIL.bits()), ExceptionCause::ERndFipsChkFail => (false, ErrBits::RND_FIPS_CHK_FAIL.bits()), ExceptionCause::EFatal => { diff --git a/hw/opentitan/otbn/otbn/src/proxy.rs b/hw/opentitan/otbn/otbn/src/proxy.rs index 5789f39661f6c..42307dd3c14f9 100644 --- a/hw/opentitan/otbn/otbn/src/proxy.rs +++ b/hw/opentitan/otbn/otbn/src/proxy.rs @@ -14,6 +14,7 @@ use std::sync::{Arc, Mutex}; use std::thread; use super::comm; +use super::key; use super::memory; use super::otbn; use super::random; @@ -45,6 +46,9 @@ pub struct Proxy { /// True random generator from EDN rnd: Arc, + /// Sideload key + key: Arc, + /// Transient optional callback to invoke on completion on_complete: Option>, } @@ -60,6 +64,7 @@ impl Default for Proxy { core_id: None, syncurnd: Arc::new(random::SyncUrnd::new()), rnd: Arc::new(random::Rnd::new()), + key: Arc::new(key::Key::new()), on_complete: None, } } @@ -142,6 +147,7 @@ impl Proxy { let on_complete = self.on_complete.take(); let urnd = self.syncurnd.clone(); let rnd = Arc::clone(&self.rnd); + let key = Arc::clone(&self.key); let log_name: Option = log_name.map(|l| l.into()); self.join_handle = Some( executer @@ -153,6 +159,7 @@ impl Proxy { dmem, urnd, rnd, + key, on_complete, log_name, log_asm, @@ -253,6 +260,21 @@ impl Proxy { false } + /// Push a 384-bit key buffer + pub fn push_key(&mut self, share0: &[u8], share1: &[u8], valid: bool) -> bool { + if share0.len() != 48 && share1.len() != 48 { + return false; + } + if let (Ok(share0), Ok(share1)) = + (<&[u8; 48]>::try_from(share0), <&[u8; 48]>::try_from(share1)) + { + self.key.fill(share0, share1, valid); + return true; + } + + false + } + /// Execute the loaded code pub fn execute(&mut self, dump: bool) -> bool { self.check_request(); @@ -431,14 +453,26 @@ pub unsafe extern "C" fn ot_otbn_proxy_push_entropy( seed: *const u8, len: u32, fips: bool, -) -> c_int { +) -> bool { assert!(!seed.is_null()); let rust_seed = slice::from_raw_parts(seed, len as usize); - if proxy.unwrap().push_entropy(rndix as usize, rust_seed, fips) { - 0 - } else { - -1 - } + proxy.unwrap().push_entropy(rndix as usize, rust_seed, fips) +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn ot_otbn_proxy_push_key( + proxy: Option<&mut Proxy>, + share0: *const u8, + share1: *const u8, + len: u32, + valid: bool, +) -> bool { + assert!(!share0.is_null()); + assert!(!share1.is_null()); + let rust_share0 = slice::from_raw_parts(share0, len as usize); + let rust_share1 = slice::from_raw_parts(share1, len as usize); + proxy.unwrap().push_key(rust_share0, rust_share1, valid) } #[no_mangle] diff --git a/include/hw/opentitan/ot_otbn.h b/include/hw/opentitan/ot_otbn.h index 3c60ccd608f7b..a0e57f2296812 100644 --- a/include/hw/opentitan/ot_otbn.h +++ b/include/hw/opentitan/ot_otbn.h @@ -33,4 +33,6 @@ #define TYPE_OT_OTBN "ot-otbn" OBJECT_DECLARE_TYPE(OtOTBNState, OtOTBNClass, OT_OTBN) +#define OT_OTBN_KEY_SIZE (384u / 8u) + #endif /* HW_OPENTITAN_OT_OTBN_H */ diff --git a/include/hw/opentitan/otbn/otbnproxy.h b/include/hw/opentitan/otbn/otbnproxy.h index b54e79bd3f1b6..2d5080b6cf5d2 100644 --- a/include/hw/opentitan/otbn/otbnproxy.h +++ b/include/hw/opentitan/otbn/otbnproxy.h @@ -67,9 +67,12 @@ ot_otbn_proxy_new(ot_otbn_fetch_entropy_fn urnd_req_entropy, void *urnd_opaque, extern void ot_otbn_proxy_start(OTBNProxy proxy, bool test_mode, const char *logname, bool log_asm); extern void ot_otbn_proxy_terminate(OTBNProxy proxy); -extern int ot_otbn_proxy_push_entropy(OTBNProxy proxy, uint32_t rndix, - const uint8_t *seed, uint32_t len, - bool fips); +extern bool ot_otbn_proxy_push_entropy(OTBNProxy proxy, uint32_t rndix, + const uint8_t *seed, uint32_t len, + bool fips); +extern bool ot_otbn_proxy_push_key(OTBNProxy proxy, const uint8_t *share0, + const uint8_t *share1, uint32_t len, + bool valid); extern int ot_otbn_proxy_execute(OTBNProxy proxy, bool dumpstate); extern int ot_otbn_proxy_wipe_memory(OTBNProxy proxy, bool doi); extern bool ot_otbn_proxy_acknowledge_execution(OTBNProxy proxy); From 82f67a9a7490cd86eb140b878d213cb4d86710d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 7 Jul 2025 19:20:38 +0200 Subject: [PATCH 149/175] [ot] hw/opentitan: ot_aes: implement the OtKeySinkIf interface Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_aes.c | 98 +++++++++++++++++++++++++++++------ include/hw/opentitan/ot_aes.h | 2 + 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 44d1c8696d5e5..31348b819e9fa 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -4,6 +4,7 @@ config OT_ADDRESS_SPACE bool config OT_AES + select OT_KEY_SINK select OT_PRNG bool diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index e419fe491640f..2ee15d248fcd9 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -40,6 +40,7 @@ #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" +#include "hw/opentitan/ot_key_sink.h" #include "hw/opentitan/ot_prng.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" @@ -128,9 +129,11 @@ REG32(STATUS, 0x84u) ALERT_RECOV_CTRL_UPDATE_ERR_SHIFT) #define OT_AES_DATA_SIZE (PARAM_NUM_REGS_DATA * sizeof(uint32_t)) -#define OT_AES_KEY_SIZE (PARAM_NUM_REGS_KEY * sizeof(uint32_t)) #define OT_AES_IV_SIZE (PARAM_NUM_REGS_IV * sizeof(uint32_t)) +static_assert(OT_AES_KEY_SIZE == (PARAM_NUM_REGS_KEY * sizeof(uint32_t)), + "Invalid key size"); + #define OT_AES_CLOCK_ACTIVE "clock-active" #define OT_AES_CLOCK_INPUT "clock-in" @@ -189,6 +192,9 @@ static const char *OT_AES_MODE_NAMES[6u] = { "NONE", "ECB", "CBC", "CFB", "OFB", "CTR", }; +#define OT_AES_KEY_DWORD_COUNT (OT_AES_KEY_SIZE / sizeof(uint64_t)) +#define OT_AES_IV_DWORD_COUNT (OT_AES_IV_SIZE / sizeof(uint64_t)) + typedef struct OtAESRegisters { /* public registers */ uint32_t keyshare[PARAM_NUM_REGS_KEY * 2u]; /* wo */ @@ -218,8 +224,8 @@ typedef struct OtAESContext { symmetric_CBC cbc; symmetric_CTR ctr; }; - uint64_t key[OT_AES_KEY_SIZE / sizeof(uint64_t)]; - uint64_t iv[OT_AES_IV_SIZE / sizeof(uint64_t)]; + uint64_t key[OT_AES_KEY_DWORD_COUNT]; + uint64_t iv[OT_AES_IV_DWORD_COUNT]; uint8_t src[OT_AES_DATA_SIZE]; uint8_t dst[OT_AES_DATA_SIZE]; bool key_ready; /* Key has been fully loaded */ @@ -236,14 +242,20 @@ typedef struct OtAESEDN { bool scheduled; } OtAESEDN; -enum OtAESMode { +typedef struct { + uint64_t share0[OT_AES_KEY_DWORD_COUNT]; + uint64_t share1[OT_AES_KEY_DWORD_COUNT]; + bool valid; +} OtAESKey; + +typedef enum { AES_NONE, AES_ECB, AES_CBC, AES_CFB, AES_OFB, AES_CTR, -}; +} OtAESMode; struct OtAESState { SysBusDevice parent_obj; @@ -255,6 +267,7 @@ struct OtAESState { OtAESRegisters *regs; OtAESContext *ctx; + OtAESKey *sl_key; OtAESEDN edn; OtPrngState *prng; const char *clock_src_name; /* IRQ name once connected */ @@ -366,7 +379,7 @@ static inline bool ot_aes_is_encryption(OtAESRegisters *r) return FIELD_EX32(ctrl, CTRL_SHADOWED, OPERATION) != 0x2u; } -static inline enum OtAESMode ot_aes_get_mode(OtAESRegisters *r) +static inline OtAESMode ot_aes_get_mode(OtAESRegisters *r) { uint32_t ctrl = ot_shadow_reg_peek(&r->ctrl); switch (FIELD_EX32(ctrl, CTRL_SHADOWED, MODE)) { @@ -414,18 +427,12 @@ static inline void ot_aes_load_reseed_rate(OtAESState *s) s->reseed_count = reseed; } -/* @todo temporary, as some helper functions are not yet used */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" - static inline bool ot_aes_is_sideload(OtAESRegisters *r) { uint32_t ctrl = ot_shadow_reg_peek(&r->ctrl); return FIELD_EX32(ctrl, CTRL_SHADOWED, SIDELOAD) == 1u; } -#pragma GCC diagnostic pop - static inline bool ot_aes_is_data_in_ready(OtAESRegisters *r) { return (r->data_in_bm[0] & OT_AES_DATA_BM_MASK) == OT_AES_DATA_BM_MASK; @@ -484,7 +491,7 @@ static void ot_aes_init_data(OtAESState *s, bool io) static bool ot_aes_is_mode_ready(OtAESRegisters *r, bool *need_iv) { - enum OtAESMode mode = ot_aes_get_mode(r); + OtAESMode mode = ot_aes_get_mode(r); switch (mode) { case AES_ECB: @@ -519,6 +526,23 @@ static void ot_aes_trigger_reseed(OtAESState *s) } } +static void ot_aes_sideload_key(OtAESState *s) +{ + OtAESKey *key = s->sl_key; + + if (!key->valid) { + return; + } + + OtAESContext *c = s->ctx; + + for (unsigned ix = 0u; ix < OT_AES_KEY_DWORD_COUNT; ix++) { + c->key[ix] = key->share0[ix] ^ key->share1[ix]; + } + + c->key_ready = true; +} + static void ot_aes_update_key(OtAESState *s) { OtAESRegisters *r = s->regs; @@ -693,7 +717,7 @@ static void ot_aes_update_config(OtAESState *s) } size_t key_size = ot_aes_get_key_length(r); - enum OtAESMode mode = ot_aes_get_mode(r); + OtAESMode mode = ot_aes_get_mode(r); int rc; @@ -734,7 +758,7 @@ static void ot_aes_update_config(OtAESState *s) } } -static void ot_aes_finalize(OtAESState *s, enum OtAESMode mode) +static void ot_aes_finalize(OtAESState *s, OtAESMode mode) { int rc; @@ -827,7 +851,7 @@ static void ot_aes_process(OtAESState *s) OtAESRegisters *r = s->regs; OtAESContext *c = s->ctx; - enum OtAESMode mode = ot_aes_get_mode(s->regs); + OtAESMode mode = ot_aes_get_mode(s->regs); bool encrypt = ot_aes_is_encryption(r); int rc; @@ -1052,6 +1076,27 @@ static void ot_aes_clock_input(void *opaque, int irq, int level) /* TODO: disable AES execution when PCLK is 0 */ } +static void ot_aes_push_key(OtKeySinkIf *ifd, const uint8_t *share0, + const uint8_t *share1, size_t key_len, bool valid) +{ + g_assert(!key_len || key_len == OT_AES_KEY_SIZE); + + OtAESState *s = OT_AES(ifd); + OtAESKey *key = s->sl_key; + + if (key_len && share0) { + memcpy(key->share0, share0, key_len); + } else { + memset(key->share0, 0, OT_AES_KEY_SIZE); + } + if (key_len && share1) { + memcpy(key->share1, share1, key_len); + } else { + memset(key->share1, 0, OT_AES_KEY_SIZE); + } + key->valid = valid; +} + static uint64_t ot_aes_read(void *opaque, hwaddr addr, unsigned size) { OtAESState *s = opaque; @@ -1189,6 +1234,12 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, case R_KEY_SHARE1_5: case R_KEY_SHARE1_6: case R_KEY_SHARE1_7: + if (ot_aes_is_sideload(r)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: key share disabled, sideload is active\n", + __func__, s->ot_id); + break; + } if (ot_aes_is_idle(s)) { r->keyshare[reg - R_KEY_SHARE0_0] = val32; set_bit((int64_t)(reg - R_KEY_SHARE0_0), r->keyshare_bm); @@ -1230,7 +1281,7 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, R_CTRL_SHADOWED_PRNG_RESEED_RATE_MASK | R_CTRL_SHADOWED_MANUAL_OPERATION_MASK | R_CTRL_SHADOWED_FORCE_ZERO_MASKS_MASK; - enum OtAESMode prev_mode = ot_aes_get_mode(s->regs); + OtAESMode prev_mode = ot_aes_get_mode(s->regs); switch (ot_shadow_reg_write(&r->ctrl, val32)) { case OT_SHADOW_REG_STAGED: break; @@ -1251,6 +1302,9 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, ot_aes_update_alert(s); break; } + if (ot_aes_is_sideload(s->regs)) { + ot_aes_sideload_key(s); + } break; case R_CTRL_AUX_SHADOWED: if (!r->ctrl_aux_regwen) { @@ -1325,6 +1379,7 @@ static void ot_aes_reset_enter(Object *obj, ResetType type) memset(s->ctx, 0, sizeof(*s->ctx)); memset(r, 0, sizeof(*r)); + memset(s->sl_key, 0, sizeof(*s->sl_key)); ot_shadow_reg_init(&r->ctrl, 0x1181u); ot_shadow_reg_init(&r->ctrl_aux, 1u); @@ -1406,6 +1461,7 @@ static void ot_aes_init(Object *obj) s->regs = g_new0(OtAESRegisters, 1u); s->ctx = g_new0(OtAESContext, 1u); + s->sl_key = g_new0(OtAESKey, 1u); /* aes_desc is defined in libtomcrypt */ s->ctx->aes_cipher = register_cipher(&aes_desc); @@ -1440,6 +1496,9 @@ static void ot_aes_class_init(ObjectClass *klass, void *data) OtAESClass *ac = OT_AES_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_aes_reset_enter, NULL, &ot_aes_reset_exit, &ac->parent_phases); + + OtKeySinkIfClass *kc = OT_KEY_SINK_IF_CLASS(klass); + kc->push_key = &ot_aes_push_key; } static const TypeInfo ot_aes_info = { @@ -1449,6 +1508,11 @@ static const TypeInfo ot_aes_info = { .instance_init = &ot_aes_init, .class_size = sizeof(OtAESClass), .class_init = &ot_aes_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_OT_KEY_SINK_IF }, + {}, + }, }; static void ot_aes_register_types(void) diff --git a/include/hw/opentitan/ot_aes.h b/include/hw/opentitan/ot_aes.h index 6b0cac34c8b09..dc69ad5041cb3 100644 --- a/include/hw/opentitan/ot_aes.h +++ b/include/hw/opentitan/ot_aes.h @@ -33,4 +33,6 @@ #define TYPE_OT_AES "ot-aes" OBJECT_DECLARE_TYPE(OtAESState, OtAESClass, OT_AES) +#define OT_AES_KEY_SIZE (256u / 8u) + #endif /* HW_OPENTITAN_OT_AES_H */ From 7c564ac4e31f827c0eea77fc3b8808da16b70c56 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 11:58:18 +0200 Subject: [PATCH 150/175] [ot] hw/opentitan: ot_kmac: implement the OtKeySinkIf interface Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 2 +- hw/opentitan/ot_kmac.c | 76 ++++++++++++++++++++++++++++------ hw/opentitan/trace-events | 1 + include/hw/opentitan/ot_kmac.h | 1 + 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 31348b819e9fa..b5e831f82e78b 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -84,7 +84,7 @@ config OT_KEYMGR_DPE bool config OT_KMAC - select OT_KEYMGR_DPE + select OT_KEY_SINK bool config OT_LC_CTRL diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index 0fbb1879d4e8a..082298deaec00 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -43,7 +43,7 @@ #include "hw/opentitan/ot_clkmgr.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" -#include "hw/opentitan/ot_keymgr_dpe.h" +#include "hw/opentitan/ot_key_sink.h" #include "hw/opentitan/ot_kmac.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" @@ -305,6 +305,8 @@ static const char *REG_NAMES[REGS_COUNT] = { }; #undef REG_NAME_ENTRY +#define OT_KMAC_KEY_HEXSTR_SIZE (OT_KMAC_KEY_SIZE * 2u + 2u) + /* Input FIFO length is 80 bytes (10 x 64 bits) */ #define FIFO_LENGTH 80u @@ -377,6 +379,12 @@ typedef struct { bool req_pending; /* true if pending request */ } OtKMACApp; +typedef struct { + uint8_t share0[OT_KMAC_KEY_SIZE]; + uint8_t share1[OT_KMAC_KEY_SIZE]; + bool valid; +} OtKMACKey; + struct OtKMACState { SysBusDevice parent_obj; @@ -404,6 +412,8 @@ struct OtKMACState { OtKMACApp *apps; OtKMACApp *current_app; + OtKMACKey *sl_key; + char *hexstr; uint32_t pending_apps; Fifo8 input_fifo; @@ -413,7 +423,6 @@ struct OtKMACState { char *ot_id; char *clock_name; DeviceState *clock_src; - OtKeyMgrDpeState *keymgr; OtEDNState *edn; uint8_t edn_ep; uint8_t num_app; @@ -564,6 +573,39 @@ static inline size_t ot_kmac_get_key_length(const OtKMACState *s) } } +static void ot_kmac_push_key(OtKeySinkIf *ifd, const uint8_t *share0, + const uint8_t *share1, size_t key_len, bool valid) +{ + g_assert(!key_len || key_len == OT_KMAC_KEY_SIZE); + + OtKMACState *s = OT_KMAC(ifd); + OtKMACKey *key = s->sl_key; + + if (key_len && share0) { + memcpy(key->share0, share0, key_len); + } else { + memset(key->share0, 0, OT_KMAC_KEY_SIZE); + } + if (key_len && share1) { + memcpy(key->share1, share1, key_len); + } else { + memset(key->share1, 0, OT_KMAC_KEY_SIZE); + } + key->valid = valid; + + if (trace_event_get_state(TRACE_OT_KMAC_PUSH_KEY)) { + uint8_t key_value[OT_KMAC_KEY_SIZE]; + for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { + key_value[ix] = key->share0[ix] ^ key->share1[ix]; + } + + trace_ot_kmac_push_key(s->ot_id, valid, + ot_common_lhexdump(key_value, OT_KMAC_KEY_SIZE, + true, s->hexstr, + OT_KMAC_KEY_HEXSTR_SIZE)); + } +} + static void ot_kmac_get_key(OtKMACState *s, uint8_t *key, size_t *keylen) { uint32_t cfg = ot_shadow_reg_peek(&s->cfg); @@ -575,15 +617,13 @@ static void ot_kmac_get_key(OtKMACState *s, uint8_t *key, size_t *keylen) } if (sideload) { - OtKeyMgrDpeKey keymgr_key; - OtKeyMgrDpeClass *kmc = OT_KEYMGR_DPE_GET_CLASS(s->keymgr); - kmc->get_kmac_key(s->keymgr, &keymgr_key); - *keylen = OT_KEYMGR_DPE_KEY_BYTES; - for (size_t ix = 0; ix < *keylen; ix++) { - key[ix] = keymgr_key.share0[ix] ^ keymgr_key.share1[ix]; + *keylen = OT_KMAC_KEY_SIZE; + const OtKMACKey *sl_key = s->sl_key; + for (size_t ix = 0; ix < OT_KMAC_KEY_SIZE; ix++) { + key[ix] = sl_key->share0[ix] ^ sl_key->share1[ix]; } /* only check key validity in App mode */ - if (s->current_app && !keymgr_key.valid) { + if (s->current_app && !sl_key->valid) { ot_kmac_report_error(s, OT_KMAC_ERR_KEY_NOT_VALID, s->current_app->index); } @@ -918,7 +958,7 @@ static void ot_kmac_process_start(OtKMACState *s) if (cfg->mode == OT_KMAC_MODE_KMAC) { uint8_t key[NUM_KEY_REGS * sizeof(uint32_t)]; size_t keylen = 0; - static_assert(OT_KEYMGR_DPE_KEY_BYTES <= ARRAY_SIZE(key), + static_assert(OT_KMAC_KEY_SIZE <= ARRAY_SIZE(key), "key buffer too small to hold sideloaded key"); ot_kmac_get_key(s, key, &keylen); sha3_process_kmac_key(&s->ltc_state, key, keylen); @@ -1658,8 +1698,6 @@ static Property ot_kmac_properties[] = { DeviceState *), DEFINE_PROP_LINK("edn", OtKMACState, edn, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtKMACState, edn_ep, UINT8_MAX), - DEFINE_PROP_LINK("keymgr", OtKMACState, keymgr, TYPE_OT_KEYMGR_DPE, - OtKeyMgrDpeState *), DEFINE_PROP_UINT8("num-app", OtKMACState, num_app, 0), DEFINE_PROP_END_OF_LIST(), }; @@ -1721,6 +1759,8 @@ static void ot_kmac_reset_enter(Object *obj, ResetType type) fifo8_reset(&s->input_fifo); + memset(s->sl_key, 0, sizeof(OtKMACKey)); + if (!s->clock_src_name) { IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); @@ -1802,6 +1842,10 @@ static void ot_kmac_init(Object *obj) /* FIFO sizes as per OT Spec */ fifo8_create(&s->input_fifo, FIFO_LENGTH); + + s->sl_key = g_new0(OtKMACKey, 1u); + + s->hexstr = g_new0(char, OT_KMAC_KEY_HEXSTR_SIZE); } static void ot_kmac_class_init(ObjectClass *klass, void *data) @@ -1820,6 +1864,9 @@ static void ot_kmac_class_init(ObjectClass *klass, void *data) kc->connect_app = &ot_kmac_connect_app; kc->app_request = &ot_kmac_app_request; + + OtKeySinkIfClass *sc = OT_KEY_SINK_IF_CLASS(klass); + sc->push_key = &ot_kmac_push_key; } static const TypeInfo ot_kmac_info = { @@ -1829,6 +1876,11 @@ static const TypeInfo ot_kmac_info = { .instance_init = &ot_kmac_init, .class_size = sizeof(OtKMACClass), .class_init = &ot_kmac_class_init, + .interfaces = + (InterfaceInfo[]){ + { TYPE_OT_KEY_SINK_IF }, + {}, + }, }; static void ot_kmac_register_types(void) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 4470730e01372..569865dc35e23 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -303,6 +303,7 @@ ot_kmac_io_read_out(const char *id, uint32_t addr, const char * regname, uint32_ ot_kmac_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_kmac_msgfifo_write(const char *id, uint32_t addr, uint32_t val, unsigned size, uint32_t pc) "%s: addr=0x%03x, val=0x%x (%u), pc=0x%x" ot_kmac_process_sw_command(const char *id, int cmd, const char *cmd_str) "%s: cmd=0x%02x (%s)" +ot_kmac_push_key(const char *id, bool valid, const char *hexstr) "%s: valid:%u key:%s" ot_kmac_report_error(const char *id, int code, const char *code_str, uint32_t info) "%s: code=0x%02x (%s) info=0x%06x" ot_kmac_state_read_out(const char *id, uint32_t addr, uint32_t val, uint32_t pc) "%s: addr=0x%03x, val=0x%x, pc=0x%x" diff --git a/include/hw/opentitan/ot_kmac.h b/include/hw/opentitan/ot_kmac.h index 3d46b18d72dd3..f120173b7239a 100644 --- a/include/hw/opentitan/ot_kmac.h +++ b/include/hw/opentitan/ot_kmac.h @@ -65,6 +65,7 @@ typedef struct { #define OT_KMAC_APP_MSG_BYTES (64u / 8u) #define OT_KMAC_APP_DIGEST_BYTES (384u / 8u) +#define OT_KMAC_KEY_SIZE (256u / 8u) typedef struct { uint8_t msg_data[OT_KMAC_APP_MSG_BYTES]; From b96ad6e0573bb5cc066d852a3be237a9da2a6e8b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 11:58:42 +0200 Subject: [PATCH 151/175] [ot] hw/opentitan: ot_keymgr_dpe: use OtKeyMgrIf for AES, KMAC and OTBN Signed-off-by: Emmanuel Blot --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_keymgr_dpe.c | 349 ++++++++++++++++----------- hw/opentitan/trace-events | 14 +- include/hw/opentitan/ot_keymgr_dpe.h | 44 ---- 4 files changed, 221 insertions(+), 187 deletions(-) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index b5e831f82e78b..36b8b3a55482c 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -79,6 +79,7 @@ config OT_KEY_SINK bool config OT_KEYMGR_DPE + select OT_KEY_SINK select OT_LC_CTRL select OT_ROM_CTRL bool diff --git a/hw/opentitan/ot_keymgr_dpe.c b/hw/opentitan/ot_keymgr_dpe.c index 0e9efea9f25d6..efc1c9989fb47 100644 --- a/hw/opentitan/ot_keymgr_dpe.c +++ b/hw/opentitan/ot_keymgr_dpe.c @@ -33,12 +33,15 @@ #include "qemu/log.h" #include "qemu/main-loop.h" #include "qapi/error.h" +#include "hw/opentitan/ot_aes.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" +#include "hw/opentitan/ot_key_sink.h" #include "hw/opentitan/ot_keymgr_dpe.h" #include "hw/opentitan/ot_kmac.h" #include "hw/opentitan/ot_lc_ctrl.h" +#include "hw/opentitan/ot_otbn.h" #include "hw/opentitan/ot_otp.h" #include "hw/opentitan/ot_prng.h" #include "hw/opentitan/ot_rom_ctrl.h" @@ -74,6 +77,9 @@ #define KEYMGR_DPE_SEED_BYTES (KEYMGR_DPE_KEY_WIDTH / 8u) #define KEYMGR_DPE_KDF_BUFFER_BYTES (1984u / 8u) +#define KEYMGR_DPE_KEY_SIZE (256u / 8u) +#define _MAX(_a_, _b_) ((_a_) > (_b_) ? (_a_) : (_b_)) +#define KEYMGR_DPE_KEY_SIZE_MAX _MAX(KEYMGR_DPE_KEY_SIZE, OT_OTBN_KEY_SIZE) static_assert(KEYMGR_DPE_ADV_DATA_BYTES <= KEYMGR_DPE_KDF_BUFFER_BYTES, "KeyMgr ADV data does not fit in KDF buffer"); @@ -81,13 +87,17 @@ static_assert(KEYMGR_DPE_GEN_DATA_BYTES <= KEYMGR_DPE_KDF_BUFFER_BYTES, "KeyMgr GEN data does not fit in KDF buffer"); static_assert((KEYMGR_DPE_KDF_BUFFER_BYTES % OT_KMAC_APP_MSG_BYTES) == 0u, "KeyMgr KDF buffer not a multiple of KMAC message size"); -static_assert(OT_KEYMGR_DPE_KEY_BYTES <= OT_KMAC_APP_DIGEST_BYTES, +static_assert(OT_KMAC_KEY_SIZE <= OT_KMAC_APP_DIGEST_BYTES, "KeyMgr key size does not match KMAC digest size"); /* NOLINTNEXTLINE(misc-redundant-expression) */ -static_assert(OT_KEYMGR_DPE_OTBN_KEY_BYTES <= OT_KMAC_APP_DIGEST_BYTES, +static_assert(OT_OTBN_KEY_SIZE <= OT_KMAC_APP_DIGEST_BYTES, "KeyMgr OTBN key size does not match KMAC digest size"); -static_assert(OT_KEYMGR_DPE_KEY_BYTES == OT_OTP_KEYMGR_SECRET_SIZE, +static_assert(OT_KMAC_KEY_SIZE == OT_OTP_KEYMGR_SECRET_SIZE, "KeyMgr key size does not match OTP KeyMgr secret size"); +/* NOLINTBEGIN(misc-redundant-expression) */ +static_assert(KEYMGR_DPE_KEY_SIZE == OT_AES_KEY_SIZE, "invalid key size"); +static_assert(KEYMGR_DPE_KEY_SIZE == OT_KMAC_KEY_SIZE, "invalid key size"); +/* NOLINTEND(misc-redundant-expression) */ /* clang-format off */ REG32(INTR_STATE, 0x0u) @@ -237,6 +247,15 @@ REG32(DEBUG, 0xd0u) #define KEYMGR_DPE_RESEED_COUNT \ (KEYMGR_DPE_LFSR_WIDTH / (8u * sizeof(uint32_t))) +typedef enum { + KEYMGR_DPE_KEY_SINK_AES, + KEYMGR_DPE_KEY_SINK_KMAC, + KEYMGR_DPE_KEY_SINK_OTBN, + KEYMGR_DPE_KEY_SINK_COUNT +} OtKeyMgrDpeKeySink; + +#define KEY_SINK_OFFSET 1 + /* values for CONTROL_SHADOWED.OPERATION */ typedef enum { KEYMGR_DPE_OP_ADVANCE = 0, @@ -249,17 +268,17 @@ typedef enum { /* values for CONTROL_SHADOWED.DEST_SEL */ typedef enum { KEYMGR_DPE_DEST_SEL_VALUE_NONE = 0, - KEYMGR_DPE_DEST_SEL_VALUE_AES = 1, - KEYMGR_DPE_DEST_SEL_VALUE_KMAC = 2, - KEYMGR_DPE_DEST_SEL_VALUE_OTBN = 3, + KEYMGR_DPE_DEST_SEL_VALUE_AES = KEYMGR_DPE_KEY_SINK_AES + KEY_SINK_OFFSET, + KEYMGR_DPE_DEST_SEL_VALUE_KMAC = KEYMGR_DPE_KEY_SINK_KMAC + KEY_SINK_OFFSET, + KEYMGR_DPE_DEST_SEL_VALUE_OTBN = KEYMGR_DPE_KEY_SINK_OTBN + KEY_SINK_OFFSET, } OtKeyMgrDpeDestSel; /* values for SIDELOAD_CLEAR.VAL */ typedef enum { KEYMGR_DPE_SIDELOAD_CLEAR_NONE = 0, - KEYMGR_DPE_SIDELOAD_CLEAR_AES = 1, - KEYMGR_DPE_SIDELOAD_CLEAR_KMAC = 2, - KEYMGR_DPE_SIDELOAD_CLEAR_OTBN = 3, + KEYMGR_DPE_SIDELOAD_CLEAR_AES = KEYMGR_DPE_KEY_SINK_AES + KEY_SINK_OFFSET, + KEYMGR_DPE_SIDELOAD_CLEAR_KMAC = KEYMGR_DPE_KEY_SINK_KMAC + KEY_SINK_OFFSET, + KEYMGR_DPE_SIDELOAD_CLEAR_OTBN = KEYMGR_DPE_KEY_SINK_OTBN + KEY_SINK_OFFSET, } OtKeyMgrDpeSideloadClear; /* values for WORKING_STATE.STATE */ @@ -330,6 +349,12 @@ typedef struct { bool retain_parent; } OtKeyMgrDpeSlotPolicy; +typedef struct { + uint8_t share0[KEYMGR_DPE_KEY_SIZE]; + uint8_t share1[KEYMGR_DPE_KEY_SIZE]; + bool valid; +} OtKeyMgrDpeKey; + typedef struct { OtKeyMgrDpeKey key; /* always 256 bit keys */ uint32_t max_key_version; @@ -339,11 +364,10 @@ typedef struct { } OtKeyMgrDpeSlot; typedef struct { - OtKeyMgrDpeKey aes; - OtKeyMgrDpeKey kmac; - OtKeyMgrDpeOtbnKey otbn; - OtKeyMgrDpeKey sw; -} OtKeyMgrDpeOutKeys; + uint8_t share0[OT_OTBN_KEY_SIZE]; + uint8_t share1[OT_OTBN_KEY_SIZE]; + bool valid; +} OtKeyMgrDpeOtbnKey; typedef struct { bool op_req; @@ -382,8 +406,8 @@ typedef struct OtKeyMgrDpeState { /* key slots */ OtKeyMgrDpeSlot *key_slots; - /* output keys */ - OtKeyMgrDpeOutKeys *out_keys; + /* SW output kes */ + OtKeyMgrDpeKey *sw_out_key; char *hexstr; @@ -395,15 +419,27 @@ typedef struct OtKeyMgrDpeState { OtLcCtrlState *lc_ctrl; OtOTPState *otp; OtRomCtrlState *rom_ctrl[NUM_ROM_DIGEST_INPUTS]; + DeviceState *key_sinks[KEYMGR_DPE_KEY_SINK_COUNT]; char *seed_xstrs[KEYMGR_DPE_SEED_COUNT]; } OtKeyMgrDpeState; +struct OtKeyMgrDpeClass { + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; +}; + static const OtKeyMgrDpeSlotPolicy DEFAULT_UDS_POLICY = { .allow_child = true, .exportable = false, .retain_parent = false, }; +static const size_t OT_KEY_MGR_DPE_KEY_SINK_SIZES[KEYMGR_DPE_KEY_SINK_COUNT] = { + [KEYMGR_DPE_KEY_SINK_AES] = OT_AES_KEY_SIZE, + [KEYMGR_DPE_KEY_SINK_KMAC] = OT_KMAC_KEY_SIZE, + [KEYMGR_DPE_KEY_SINK_OTBN] = OT_OTBN_KEY_SIZE, +}; + static const OtKMACAppCfg KMAC_APP_CFG = OT_KMAC_CONFIG(KMAC, 256u, "KMAC", ""); #define REG_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) @@ -476,7 +512,7 @@ static const char *OP_NAMES[] = { }; #undef OP_ENTRY #define OP_NAME(_op_) \ - ((_op_) >= 0 && (_op_) < ARRAY_SIZE(OP_NAMES) ? OP_NAMES[(_op_)] : "?") + (((unsigned)(_op_)) < ARRAY_SIZE(OP_NAMES) ? OP_NAMES[(_op_)] : "?") #define SIDELOAD_CLEAR_ENTRY(_st_) \ [KEYMGR_DPE_SIDELOAD_CLEAR_##_st_] = stringify(_st_) @@ -488,7 +524,7 @@ static const char *SIDELOAD_CLEAR_NAMES[] = { }; #undef SIDELOAD_CLEAR_ENTRY #define SIDELOAD_CLEAR_NAME(_st_) \ - ((_st_) >= 0 && (_st_) < ARRAY_SIZE(SIDELOAD_CLEAR_NAMES) ? \ + (((unsigned)(_st_)) < ARRAY_SIZE(SIDELOAD_CLEAR_NAMES) ? \ SIDELOAD_CLEAR_NAMES[(_st_)] : \ "?") @@ -503,7 +539,7 @@ static const char *WORKING_STATE_NAMES[] = { #undef WORKING_STATE_ENTRY #define WORKING_STATE_NAME(_st_) \ - ((_st_) >= 0 && (_st_) < ARRAY_SIZE(WORKING_STATE_NAMES) ? \ + (((unsigned)(_st_)) < ARRAY_SIZE(WORKING_STATE_NAMES) ? \ WORKING_STATE_NAMES[(_st_)] : \ "?") @@ -516,7 +552,7 @@ static const char *OP_STATUS_NAMES[] = { }; #undef OP_STATUS_ENTRY #define OP_STATUS_NAME(_st_) \ - ((_st_) >= 0 && (_st_) < ARRAY_SIZE(OP_STATUS_NAMES) ? \ + (((unsigned)(_st_)) < ARRAY_SIZE(OP_STATUS_NAMES) ? \ OP_STATUS_NAMES[(_st_)] : \ "?") @@ -536,15 +572,14 @@ static const char *FST_NAMES[] = { }; #undef FST_ENTRY #define FST_NAME(_st_) \ - ((_st_) >= 0 && (_st_) < ARRAY_SIZE(FST_NAMES) ? FST_NAMES[(_st_)] : "?") + (((unsigned)(_st_)) < ARRAY_SIZE(FST_NAMES) ? FST_NAMES[(_st_)] : "?") #ifdef OT_KEYMGR_DPE_DEBUG #define OT_KEYMGR_DPE_HEXSTR_SIZE 256u #define TRACE_KEYMGR_DPE(_s_, _msg_, ...) \ qemu_log("%s: %s: " _msg_ "\n", __func__, (_s_)->ot_id, ##__VA_ARGS__); #define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) \ - ot_common_lhexdump(_b_, _l_, false, (_s_)->hexstr, \ - OT_KEYMGR_DPE_HEXSTR_SIZE) + ot_common_lhexdump(_b_, _l_, true, (_s_)->hexstr, OT_KEYMGR_DPE_HEXSTR_SIZE) #else #define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) #define TRACE_KEYMGR_DPE(_s_, _msg_, ...) @@ -726,6 +761,49 @@ static void ot_keymgr_dpe_request_entropy(OtKeyMgrDpeState *s) } } +static void ot_keymgr_dpe_push_key( + OtKeyMgrDpeState *s, OtKeyMgrDpeKeySink key_sink, const uint8_t *key_share0, + const uint8_t *key_share1, bool valid) +{ + g_assert((unsigned)key_sink < KEYMGR_DPE_KEY_SINK_COUNT); + + size_t key_size = OT_KEY_MGR_DPE_KEY_SINK_SIZES[key_sink]; + + DeviceState *sink = s->key_sinks[key_sink]; + if (!sink) { + return; + } + + OtKeySinkIfClass *kc = OT_KEY_SINK_IF_GET_CLASS(sink); + OtKeySinkIf *ki = OT_KEY_SINK_IF(sink); + + g_assert(kc->push_key); + + kc->push_key(ki, key_share0, key_share1, key_size, valid); +} + +static void ot_keymgr_dpe_send_kmac_key(OtKeyMgrDpeState *s) +{ + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + uint8_t slot_src_sel = + (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); + OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; + +#ifdef OT_KEYMGR_DPE_DEBUG + uint8_t key_value[OT_KMAC_KEY_SIZE]; + for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { + key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; + } + + TRACE_KEYMGR_DPE(s, "KMAC key: %s valid: %d", + ot_keymgr_dpe_dump_bigint(s, key_value, OT_KMAC_KEY_SIZE), + src_slot->valid); +#endif + + ot_keymgr_dpe_push_key(s, KEYMGR_DPE_KEY_SINK_KMAC, src_slot->key.share0, + src_slot->key.share1, src_slot->valid); +} + static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) { uint32_t len = s->kdf_buf.offset; @@ -747,7 +825,7 @@ static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) }; memcpy(req.msg_data, &s->kdf_buf.data[offset], msg_len); - TRACE_KEYMGR_DPE(s, "KMAC req: %s last:%d", + TRACE_KEYMGR_DPE(s, "KMAC req: %s last: %d", ot_keymgr_dpe_dump_bigint(s, req.msg_data, req.msg_len), req.last); @@ -757,7 +835,7 @@ static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) kc->app_request(s->kmac, s->kmac_app, &req); } -static void ot_keymgr_dpe_handle_kmac_resp_advance( +static bool ot_keymgr_dpe_handle_kmac_resp_advance( OtKeyMgrDpeState *s, uint8_t slot_src_sel, uint8_t slot_dst_sel, const OtKMACAppRsp *rsp) { @@ -767,8 +845,8 @@ static void ot_keymgr_dpe_handle_kmac_resp_advance( OtKeyMgrDpeSlot *dst_slot = &s->key_slots[slot_dst_sel]; dst_slot->valid = true; - memcpy(dst_slot->key.share0, rsp->digest_share0, OT_KEYMGR_DPE_KEY_BYTES); - memcpy(dst_slot->key.share1, rsp->digest_share1, OT_KEYMGR_DPE_KEY_BYTES); + memcpy(dst_slot->key.share0, rsp->digest_share0, OT_KMAC_KEY_SIZE); + memcpy(dst_slot->key.share1, rsp->digest_share1, OT_KMAC_KEY_SIZE); dst_slot->max_key_version = max_key_version; dst_slot->boot_stage = src_slot->boot_stage + 1u; dst_slot->policy.allow_child = @@ -785,47 +863,37 @@ static void ot_keymgr_dpe_handle_kmac_resp_advance( s->regs[R_SW_BINDING_REGWEN] |= R_SW_BINDING_REGWEN_EN_MASK; s->regs[R_SLOT_POLICY_REGWEN] |= R_SLOT_POLICY_REGWEN_EN_MASK; s->regs[R_MAX_KEY_VER_REGWEN] |= R_MAX_KEY_VER_REGWEN_EN_MASK; + + return true; } -static void ot_keymgr_dpe_handle_kmac_resp_gen_hw_out( +static bool ot_keymgr_dpe_handle_kmac_resp_gen_hw_out( OtKeyMgrDpeState *s, uint8_t dest_sel, const OtKMACAppRsp *rsp) { switch (dest_sel) { case KEYMGR_DPE_DEST_SEL_VALUE_AES: - memcpy(s->out_keys->aes.share0, rsp->digest_share0, - OT_KEYMGR_DPE_KEY_BYTES); - memcpy(s->out_keys->aes.share1, rsp->digest_share1, - OT_KEYMGR_DPE_KEY_BYTES); - s->out_keys->aes.valid = true; - break; case KEYMGR_DPE_DEST_SEL_VALUE_KMAC: - memcpy(s->out_keys->kmac.share0, rsp->digest_share0, - OT_KEYMGR_DPE_KEY_BYTES); - memcpy(s->out_keys->kmac.share1, rsp->digest_share1, - OT_KEYMGR_DPE_KEY_BYTES); - s->out_keys->kmac.valid = true; - break; - case KEYMGR_DPE_DEST_SEL_VALUE_OTBN: - memcpy(s->out_keys->otbn.share0, rsp->digest_share0, - OT_KEYMGR_DPE_OTBN_KEY_BYTES); - memcpy(s->out_keys->otbn.share1, rsp->digest_share1, - OT_KEYMGR_DPE_OTBN_KEY_BYTES); - s->out_keys->otbn.valid = true; - break; + case KEYMGR_DPE_DEST_SEL_VALUE_OTBN: { + OtKeyMgrDpeKeySink key_sink = dest_sel - KEY_SINK_OFFSET; + ot_keymgr_dpe_push_key(s, key_sink, rsp->digest_share0, + rsp->digest_share1, true); + return dest_sel != KEYMGR_DPE_DEST_SEL_VALUE_KMAC; + } case KEYMGR_DPE_DEST_SEL_VALUE_NONE: /* no output, just ack the operation */ - break; + return true; default: g_assert_not_reached(); } } -static void ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(OtKeyMgrDpeState *s, +static bool ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(OtKeyMgrDpeState *s, const OtKMACAppRsp *rsp) { - memcpy(s->out_keys->sw.share0, rsp->digest_share0, OT_KEYMGR_DPE_KEY_BYTES); - memcpy(s->out_keys->sw.share1, rsp->digest_share1, OT_KEYMGR_DPE_KEY_BYTES); - s->out_keys->sw.valid = true; + memcpy(s->sw_out_key->share0, rsp->digest_share0, OT_KMAC_KEY_SIZE); + memcpy(s->sw_out_key->share1, rsp->digest_share1, OT_KMAC_KEY_SIZE); + s->sw_out_key->valid = true; + return true; } static void @@ -833,6 +901,8 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) { OtKeyMgrDpeState *s = OT_KEYMGR_DPE(opaque); uint32_t ctrl = ot_shadow_reg_peek(&s->control); + bool reset_kmac_key = true; + trace_ot_keymgr_dpe_kmac_rsp(s->ot_id, rsp->done); if (!rsp->done) { @@ -859,23 +929,31 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); uint8_t slot_dst_sel = (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_DST_SEL); - ot_keymgr_dpe_handle_kmac_resp_advance(s, slot_src_sel, slot_dst_sel, - rsp); + reset_kmac_key = + ot_keymgr_dpe_handle_kmac_resp_advance(s, slot_src_sel, + slot_dst_sel, rsp); break; } case KEYMGR_DPE_OP_GENERATE_HW_OUTPUT: { uint8_t dest_sel = (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, DEST_SEL); - ot_keymgr_dpe_handle_kmac_resp_gen_hw_out(s, dest_sel, rsp); + reset_kmac_key = + ot_keymgr_dpe_handle_kmac_resp_gen_hw_out(s, dest_sel, rsp); break; } case KEYMGR_DPE_OP_GENERATE_SW_OUTPUT: - ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(s, rsp); + reset_kmac_key = ot_keymgr_dpe_handle_kmac_resp_gen_sw_out(s, rsp); break; default: g_assert_not_reached(); } + /* reset KMAC key if required */ + if (reset_kmac_key) { + /* TODO: maybe push last sideloaded KMAC key instead? */ + ot_keymgr_dpe_push_key(s, KEYMGR_DPE_KEY_SINK_KMAC, NULL, NULL, false); + } + /* operation complete */ s->op_state.op_ack = true; ot_keymgr_dpe_schedule_fsm(s); @@ -1096,6 +1174,8 @@ static void ot_keymgr_dpe_operation_advance(OtKeyMgrDpeState *s) g_assert(s->kdf_buf.length <= KEYMGR_DPE_ADV_DATA_BYTES); s->kdf_buf.length = KEYMGR_DPE_ADV_DATA_BYTES; + ot_keymgr_dpe_send_kmac_key(s); + ot_keymgr_dpe_dump_kdf_buf(s); ot_keymgr_dpe_send_kmac_req(s); } @@ -1186,6 +1266,8 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) g_assert(s->kdf_buf.length == KEYMGR_DPE_GEN_DATA_BYTES); + ot_keymgr_dpe_send_kmac_key(s); + ot_keymgr_dpe_dump_kdf_buf(s); ot_keymgr_dpe_send_kmac_req(s); } @@ -1241,37 +1323,56 @@ static void ot_keymgr_dpe_start_operation(OtKeyMgrDpeState *s) static void ot_keymgr_dpe_sideload_clear(OtKeyMgrDpeState *s) { - int sideload_clear = - (int)FIELD_EX32(s->regs[R_SIDELOAD_CLEAR], SIDELOAD_CLEAR, VAL); - - trace_ot_keymgr_dpe_sideload_clear(s->ot_id, - SIDELOAD_CLEAR_NAME(sideload_clear), - sideload_clear); + uint32_t sl_clear = + FIELD_EX32(s->regs[R_SIDELOAD_CLEAR], SIDELOAD_CLEAR, VAL); - /* TODO this needs to use random data */ + trace_ot_keymgr_dpe_sideload_clear(s->ot_id, SIDELOAD_CLEAR_NAME(sl_clear), + sl_clear); - switch (sideload_clear) { + uint32_t bm; + switch ((int)sl_clear) { case KEYMGR_DPE_SIDELOAD_CLEAR_NONE: /* nothing to clear, exit */ return; case KEYMGR_DPE_SIDELOAD_CLEAR_AES: - memset(&s->out_keys->aes, 0u, sizeof(OtKeyMgrDpeKey)); - return; case KEYMGR_DPE_SIDELOAD_CLEAR_OTBN: - memset(&s->out_keys->otbn, 0u, sizeof(OtKeyMgrDpeOtbnKey)); - return; case KEYMGR_DPE_SIDELOAD_CLEAR_KMAC: - memset(&s->out_keys->kmac, 0u, sizeof(OtKeyMgrDpeKey)); - return; + bm = 1u << (sl_clear - KEY_SINK_OFFSET); + break; default: /* * "If the value programmed is not one of the enumerated values below, * ALL sideload key slots are continuously cleared." */ - memset(&s->out_keys->aes, 0u, sizeof(OtKeyMgrDpeKey)); - memset(&s->out_keys->otbn, 0u, sizeof(OtKeyMgrDpeOtbnKey)); - memset(&s->out_keys->kmac, 0u, sizeof(OtKeyMgrDpeKey)); - return; + bm = (1u << KEYMGR_DPE_KEY_SINK_COUNT) - 1u; + break; + } + + uint8_t share0[KEYMGR_DPE_KEY_SIZE_MAX]; + uint8_t share1[KEYMGR_DPE_KEY_SIZE_MAX]; + + for (unsigned kix = 0; bm && kix < KEYMGR_DPE_KEY_SINK_COUNT; kix++) { + if (bm & (1u << kix)) { + bm &= ~(1u << kix); + + DeviceState *sink = s->key_sinks[kix]; + if (!sink) { + continue; + } + + OtKeySinkIfClass *kc = OT_KEY_SINK_IF_GET_CLASS(sink); + OtKeySinkIf *ki = OT_KEY_SINK_IF(sink); + + g_assert(kc->push_key); + + size_t key_size = OT_KEY_MGR_DPE_KEY_SINK_SIZES[kix]; + + /* TODO this needs to use random data */ + memset(share0, 0, key_size); + memset(share1, 0, key_size); + + kc->push_key(ki, share0, share1, key_size, false); + } } } @@ -1583,7 +1684,7 @@ static uint64_t ot_keymgr_dpe_read(void *opaque, hwaddr addr, unsigned size) case R_SW_SHARE0_OUTPUT_7: { /* TODO should this depend on the current state? */ unsigned offset = (reg - R_SW_SHARE0_OUTPUT_0) * sizeof(uint32_t); - void *ptr = &s->out_keys->sw.share0[offset]; + void *ptr = &s->sw_out_key->share0[offset]; val32 = ldl_le_p(ptr); stl_le_p(ptr, 0u); /* RC */ break; @@ -1598,7 +1699,7 @@ static uint64_t ot_keymgr_dpe_read(void *opaque, hwaddr addr, unsigned size) case R_SW_SHARE1_OUTPUT_7: { /* TODO should this depend on the current state? */ unsigned offset = (reg - R_SW_SHARE1_OUTPUT_0) * sizeof(uint32_t); - void *ptr = &s->out_keys->sw.share1[offset]; + void *ptr = &s->sw_out_key->share1[offset]; val32 = ldl_le_p(ptr); stl_le_p(ptr, 0u); /* RC */ break; @@ -1821,58 +1922,6 @@ static void ot_keymgr_dpe_write(void *opaque, hwaddr addr, uint64_t val64, } }; -static void ot_keymgr_dpe_get_aes_key(const OtKeyMgrDpeState *s, - OtKeyMgrDpeKey *key) -{ - g_assert(key); - - memcpy(key->share0, s->out_keys->aes.share0, OT_KEYMGR_DPE_KEY_BYTES); - memcpy(key->share1, s->out_keys->aes.share1, OT_KEYMGR_DPE_KEY_BYTES); - key->valid = s->out_keys->aes.valid; -} - -static void ot_keymgr_dpe_get_kmac_key(const OtKeyMgrDpeState *s, - OtKeyMgrDpeKey *key) -{ - g_assert(key); - - /* return either key slot or sideload key depending on current state */ - if (s->op_state.op_req) { - uint32_t ctrl = ot_shadow_reg_peek(&s->control); - uint8_t slot_src_sel = - (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); - const OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; - -#ifdef OT_KEYMGR_DPE_DEBUG - uint8_t key_value[OT_KMAC_KEY_SIZE]; - for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { - key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; - } - TRACE_KEYMGR_DPE(s, "KMAC key for adv/gen: %s", - ot_keymgr_dpe_dump_bigint(key_value, - OT_KMAC_KEY_SIZE)); -#endif - - memcpy(key->share0, src_slot->key.share0, OT_KMAC_KEY_SIZE); - memcpy(key->share1, src_slot->key.share1, OT_KMAC_KEY_SIZE); - key->valid = src_slot->valid; - } else { - memcpy(key->share0, s->out_keys->kmac.share0, OT_KEYMGR_DPE_KEY_BYTES); - memcpy(key->share1, s->out_keys->kmac.share1, OT_KEYMGR_DPE_KEY_BYTES); - key->valid = s->out_keys->kmac.valid; - } -} - -static void ot_keymgr_dpe_get_otbn_key(const OtKeyMgrDpeState *s, - OtKeyMgrDpeOtbnKey *key) -{ - g_assert(key); - - memcpy(key->share0, s->out_keys->otbn.share0, OT_KEYMGR_DPE_OTBN_KEY_BYTES); - memcpy(key->share1, s->out_keys->otbn.share1, OT_KEYMGR_DPE_OTBN_KEY_BYTES); - key->valid = s->out_keys->otbn.valid; -} - static void ot_keymgr_dpe_configure_constants(OtKeyMgrDpeState *s) { for (unsigned ix = 0u; ix < KEYMGR_DPE_SEED_COUNT; ix++) { @@ -1917,6 +1966,12 @@ static Property ot_keymgr_dpe_properties[] = { OtRomCtrlState *), DEFINE_PROP_LINK("rom1", OtKeyMgrDpeState, rom_ctrl[1], TYPE_OT_ROM_CTRL, OtRomCtrlState *), + DEFINE_PROP_LINK("aes", OtKeyMgrDpeState, + key_sinks[KEYMGR_DPE_KEY_SINK_AES], TYPE_OT_KEY_SINK_IF, + DeviceState *), + DEFINE_PROP_LINK("otbn", OtKeyMgrDpeState, + key_sinks[KEYMGR_DPE_KEY_SINK_OTBN], TYPE_OT_KEY_SINK_IF, + DeviceState *), DEFINE_PROP_STRING("aes_seed", OtKeyMgrDpeState, seed_xstrs[KEYMGR_DPE_SEED_AES]), DEFINE_PROP_STRING("hard_output_seed", OtKeyMgrDpeState, @@ -1949,7 +2004,7 @@ static void ot_keymgr_dpe_reset_enter(Object *obj, ResetType type) OtKeyMgrDpeClass *c = OT_KEYMGR_DPE_GET_CLASS(obj); OtKeyMgrDpeState *s = OT_KEYMGR_DPE(obj); - trace_ot_keymgr_dpe_reset(s->ot_id); + trace_ot_keymgr_dpe_reset(s->ot_id, "enter"); if (c->parent_phases.enter) { c->parent_phases.enter(obj, type); @@ -1964,6 +2019,8 @@ static void ot_keymgr_dpe_reset_enter(Object *obj, ResetType type) g_assert(s->rom_ctrl[0]); g_assert(s->rom_ctrl[1]); + s->key_sinks[KEYMGR_DPE_KEY_SINK_KMAC] = DEVICE(s->kmac); + /* reset registers */ memset(s->regs, 0u, sizeof(s->regs)); s->regs[R_CFG_REGWEN] = 0x1u; @@ -1993,7 +2050,7 @@ static void ot_keymgr_dpe_reset_enter(Object *obj, ResetType type) memset(s->key_slots, 0u, NUM_SLOTS * sizeof(OtKeyMgrDpeSlot)); /* reset output keys */ - memset(s->out_keys, 0u, sizeof(OtKeyMgrDpeOutKeys)); + memset(s->sw_out_key, 0u, sizeof(OtKeyMgrDpeKey)); /* update IRQ and alert states */ ot_keymgr_dpe_update_irq(s); @@ -2005,6 +2062,23 @@ static void ot_keymgr_dpe_reset_enter(Object *obj, ResetType type) ot_keymgr_dpe_handle_kmac_response, s); } +static void ot_keymgr_dpe_reset_exit(Object *obj, ResetType type) +{ + OtKeyMgrDpeClass *c = OT_KEYMGR_DPE_GET_CLASS(obj); + OtKeyMgrDpeState *s = OT_KEYMGR_DPE(obj); + + trace_ot_keymgr_dpe_reset(s->ot_id, "exit"); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); + } + + for (unsigned ix = 0u; ix < KEYMGR_DPE_KEY_SINK_COUNT; ix++) { + OtKeyMgrDpeKeySink key_sink = (OtKeyMgrDpeKeySink)ix; + ot_keymgr_dpe_push_key(s, key_sink, NULL, NULL, false); + } +} + static void ot_keymgr_dpe_realize(DeviceState *dev, Error **errp) { OtKeyMgrDpeState *s = OT_KEYMGR_DPE(dev); @@ -2016,6 +2090,12 @@ static void ot_keymgr_dpe_realize(DeviceState *dev, Error **errp) g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); } + for (unsigned ix = 0u; ix < KEYMGR_DPE_KEY_SINK_COUNT; ix++) { + if (s->key_sinks[ix]) { + OBJECT_CHECK(OtKeySinkIf, s->key_sinks[ix], TYPE_OT_KEY_SINK_IF); + } + } + ot_keymgr_dpe_configure_constants(s); } @@ -2043,7 +2123,7 @@ static void ot_keymgr_dpe_init(Object *obj) s->seeds[ix] = g_new0(uint8_t, KEYMGR_DPE_SEED_BYTES); } s->key_slots = g_new0(OtKeyMgrDpeSlot, NUM_SLOTS); - s->out_keys = g_new0(OtKeyMgrDpeOutKeys, 1u); + s->sw_out_key = g_new0(OtKeyMgrDpeKey, 1u); s->fsm_tick_bh = qemu_bh_new(&ot_keymgr_dpe_fsm_tick, s); @@ -2065,11 +2145,8 @@ static void ot_keymgr_dpe_class_init(ObjectClass *klass, void *data) ResettableClass *rc = RESETTABLE_CLASS(klass); OtKeyMgrDpeClass *kmc = OT_KEYMGR_DPE_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_keymgr_dpe_reset_enter, NULL, - NULL, &kmc->parent_phases); - - kmc->get_aes_key = &ot_keymgr_dpe_get_aes_key; - kmc->get_kmac_key = &ot_keymgr_dpe_get_kmac_key; - kmc->get_otbn_key = &ot_keymgr_dpe_get_otbn_key; + &ot_keymgr_dpe_reset_exit, + &kmc->parent_phases); } static const TypeInfo ot_keymgr_dpe_info = { diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 569865dc35e23..423c9d76d2cf9 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -273,24 +273,24 @@ ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halte # ot_keymgr_dpe +ot_keymgr_dpe_change_main_fsm_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_keymgr_dpe_change_op_status(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" +ot_keymgr_dpe_change_working_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_dpe_entropy(const char * id, unsigned reseed_cnt, bool resched) "%s: reseed_cnt: %u, resched: %u" ot_keymgr_dpe_error(const char *id, const char *msg) "%s: %s" -ot_keymgr_dpe_change_working_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" -ot_keymgr_dpe_change_op_status(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" -ot_keymgr_dpe_change_main_fsm_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_dpe_go_idle(const char *id) "%s" ot_keymgr_dpe_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_dpe_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_dpe_irq(const char *id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" +ot_keymgr_dpe_kmac_req(const char *id, uint32_t fifo_len, uint32_t msg_len, bool last) "%s: fifo_len:%u msg_len:%u, last:%d" +ot_keymgr_dpe_kmac_rsp(const char *id, bool done) "%s: done:%d" ot_keymgr_dpe_lc_signal(const char *id, int level) "%s: LC signal level:%d" ot_keymgr_dpe_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" ot_keymgr_dpe_operation(const char *id, const char *op, int nop) "%s: [%s:%d]" -ot_keymgr_dpe_reset(const char *id) "%s" +ot_keymgr_dpe_reset(const char *id, const char *stage) "%s: %s" ot_keymgr_dpe_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" ot_keymgr_dpe_seed_missing(const char * id, unsigned ix) "%s: #%u" -ot_keymgr_dpe_sideload_clear(const char *id, const char *sc, int nsc) "%s: [%s:%d]" -ot_keymgr_dpe_kmac_req(const char *id, uint32_t fifo_len, uint32_t msg_len, bool last) "%s: fifo_len:%u msg_len:%u, last:%d" -ot_keymgr_dpe_kmac_rsp(const char *id, bool done) "%s: done:%d" +ot_keymgr_dpe_sideload_clear(const char *id, const char *sc, unsigned nsc) "%s: [%s:%u]" # ot_kmac.c diff --git a/include/hw/opentitan/ot_keymgr_dpe.h b/include/hw/opentitan/ot_keymgr_dpe.h index 9b026a96c04c6..ac7376c01f61b 100644 --- a/include/hw/opentitan/ot_keymgr_dpe.h +++ b/include/hw/opentitan/ot_keymgr_dpe.h @@ -37,48 +37,4 @@ OBJECT_DECLARE_TYPE(OtKeyMgrDpeState, OtKeyMgrDpeClass, OT_KEYMGR_DPE) /* Input signal to enable the key manager (from lifecycle controller) */ #define OT_KEYMGR_DPE_ENABLE TYPE_OT_KEYMGR_DPE "-enable" -/* AES and KMAC Keys have 2 shares of 256 bits */ -#define OT_KEYMGR_DPE_KEY_BYTES (256u / 8u) - -typedef struct { - uint8_t share0[OT_KEYMGR_DPE_KEY_BYTES]; - uint8_t share1[OT_KEYMGR_DPE_KEY_BYTES]; - bool valid; -} OtKeyMgrDpeKey; - -/* OTBN Keys have 2 shares of 384 bits */ -#define OT_KEYMGR_DPE_OTBN_KEY_BYTES (384u / 8u) - -typedef struct { - uint8_t share0[OT_KEYMGR_DPE_OTBN_KEY_BYTES]; - uint8_t share1[OT_KEYMGR_DPE_OTBN_KEY_BYTES]; - bool valid; -} OtKeyMgrDpeOtbnKey; - -struct OtKeyMgrDpeClass { - SysBusDeviceClass parent_class; - ResettablePhases parent_phases; - - /** - * Retrieve output key data for AES sideloading. - * - * @key pointer to a structure to be filled with key data. - */ - void (*get_aes_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeKey *key); - - /** - * Retrieve output key data for KMAC sideloading. - * - * @key pointer to a structure to be filled with key data. - */ - void (*get_kmac_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeKey *key); - - /** - * Retrieve output key data for OTBN sideloading. - * - * @key pointer to a structure to be filled with key data. - */ - void (*get_otbn_key)(const OtKeyMgrDpeState *s, OtKeyMgrDpeOtbnKey *key); -}; - #endif /* HW_OPENTITAN_OT_KEYMGR_DPE_H */ From c17f4af57fca74c88232d67bba08cacd45804661 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 8 Jul 2025 11:56:54 +0200 Subject: [PATCH 152/175] [ot] hw/riscv: ot_darjeeling, ot_earlgrey: update keymgr_dpe connections Signed-off-by: Emmanuel Blot --- hw/riscv/ot_darjeeling.c | 5 +++-- hw/riscv/ot_earlgrey.c | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 200ee0227f78a..3a39ebc0e979c 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -624,8 +624,7 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { ), .link = IBEXDEVICELINKDEFS( OT_DJ_SOC_DEVLINK("clock-src", CLKMGR), - OT_DJ_SOC_DEVLINK("edn", EDN0), - OT_DJ_SOC_DEVLINK("keymgr", KEYMGR_DPE) + OT_DJ_SOC_DEVLINK("edn", EDN0) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_STRING_PROP("clock-name", "trans.kmac"), @@ -665,8 +664,10 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_ALERT(1, 64) ), .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("aes", AES), OT_DJ_SOC_DEVLINK("edn", EDN0), OT_DJ_SOC_DEVLINK("kmac", KMAC), + OT_DJ_SOC_DEVLINK("otbn", OTBN), OT_DJ_SOC_DEVLINK("lc_ctrl", LC_CTRL), OT_DJ_SOC_DEVLINK("otp_ctrl", OTP_CTRL), OT_DJ_SOC_DEVLINK("rom0", ROM0), diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 2970d431d760b..052876dba1e00 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1060,7 +1060,6 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { { .base = 0x41140000u } ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_STRING_PROP(OT_COMMON_DEV_ID, "keymgr"), IBEX_DEV_UINT_PROP("size", 0x100u), IBEX_DEV_UINT_PROP("irq-count", 1u), IBEX_DEV_UINT_PROP("alert-count", 2u), From 5b18594e0d4b544b1149e725840ebcc8ece76767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 17 Jul 2025 17:41:50 +0200 Subject: [PATCH 153/175] [ot] hw/opentitan: ot_keymgr_dpe: fix debug trace for ROM digests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_keymgr_dpe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/opentitan/ot_keymgr_dpe.c b/hw/opentitan/ot_keymgr_dpe.c index efc1c9989fb47..ecd9d7565b40b 100644 --- a/hw/opentitan/ot_keymgr_dpe.c +++ b/hw/opentitan/ot_keymgr_dpe.c @@ -1025,7 +1025,7 @@ static size_t ot_keymgr_dpe_kdf_append_rom_digest( ot_keymgr_dpe_kdf_push_bytes(s, rom_digest, OT_ROM_DIGEST_BYTES); *dvalid &= ot_keymgr_dpe_valid_data_check(rom_digest, OT_ROM_DIGEST_BYTES); TRACE_KEYMGR_DPE(s, "ROM%u digest: %s", rom_idx, - ot_keymgr_dpe_dump_bigint(s, &rom_digest[rom_idx], + ot_keymgr_dpe_dump_bigint(s, rom_digest, OT_ROM_DIGEST_BYTES)); return OT_ROM_DIGEST_BYTES; From 02d3241f70d5eb2bf1886b78f984f8da93ccd85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 17 Jul 2025 17:42:25 +0200 Subject: [PATCH 154/175] [ot] hw/opentitan: ot_rom_ctrl: compute digests for ELF/BIN ROMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_rom_ctrl.c | 150 +++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 83 deletions(-) diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index 24f7c61ff9a09..f6e7c5dd170bb 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -145,11 +145,13 @@ struct OtRomCtrlState { unsigned data_nonce_width; /* bit count */ unsigned se_pos; unsigned se_last_pos; + unsigned se_word_bytes; uint64_t *se_buffer; unsigned recovered_error_count; unsigned unrecoverable_error_count; bool first_reset; bool loaded; + bool scrambled_n_ecc; char *ot_id; uint32_t size; @@ -315,13 +317,15 @@ static void ot_rom_ctrl_send_kmac_req(OtRomCtrlState *s) fifo8_reset(&s->hash_fifo); while (!fifo8_is_full(&s->hash_fifo) && (s->se_pos < s->se_last_pos)) { - unsigned word_pos = s->se_pos / OT_ROM_CTRL_WORD_BYTES; - unsigned word_off = s->se_pos % OT_ROM_CTRL_WORD_BYTES; - unsigned phy_addr = ot_rom_ctrl_addr_sp_enc(s, word_pos); + unsigned word_pos = s->se_pos / s->se_word_bytes; + unsigned word_off = s->se_pos % s->se_word_bytes; + unsigned phy_addr = + s->scrambled_n_ecc ? ot_rom_ctrl_addr_sp_enc(s, word_pos) : + word_pos; uint8_t wbuf[sizeof(uint64_t)]; stq_le_p(wbuf, s->se_buffer[phy_addr]); uint8_t *wb = wbuf; - unsigned wl = OT_ROM_CTRL_WORD_BYTES; + unsigned wl = s->se_word_bytes; wb += word_off; wl -= word_off; wl = MIN(wl, fifo8_num_free(&s->hash_fifo)); @@ -356,9 +360,10 @@ ot_rom_ctrl_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) return; } - g_assert(s->se_buffer); - qemu_vfree(s->se_buffer); - s->se_buffer = NULL; + if (s->scrambled_n_ecc) { + qemu_vfree(s->se_buffer); + s->se_buffer = NULL; + } g_assert(s->se_pos == s->se_last_pos); @@ -380,6 +385,9 @@ ot_rom_ctrl_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) memcpy(&share1, &rsp->digest_share1[ix * sizeof(uint32_t)], sizeof(uint32_t)); s->regs[R_DIGEST_0 + ix] = share0 ^ share1; + if (!s->scrambled_n_ecc) { + s->regs[R_EXP_DIGEST_0 + ix] = s->regs[R_DIGEST_0 + ix]; + } } trace_ot_rom_ctrl_digest_mode(s->ot_id, "stored"); @@ -388,19 +396,6 @@ ot_rom_ctrl_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) ot_rom_ctrl_compare_and_notify(s); } -static void ot_rom_ctrl_fake_digest(OtRomCtrlState *s) -{ - /* initialize a all-zero fake digest */ - for (unsigned ix = 0; ix < ROM_DIGEST_WORDS; ix++) { - s->regs[R_EXP_DIGEST_0 + ix] = s->regs[R_DIGEST_0 + ix] = 0; - } - - /* switch to ROMD mode */ - memory_region_rom_device_set_romd(&s->mem, true); - - trace_ot_rom_ctrl_digest_mode(s->ot_id, "fake"); -} - static uint64_t ot_rom_ctrl_unscramble_word(const OtRomCtrlState *s, unsigned addr, uint64_t in) { @@ -525,20 +520,40 @@ static void ot_rom_ctrl_unscramble(OtRomCtrlState *s, const uint64_t *src, } } -static bool ot_rom_ctrl_load_elf(OtRomCtrlState *s, const OtRomImg *ri) +static void ot_rom_ctrl_spawn_hash_calculation( + OtRomCtrlState *s, uintptr_t baseptr, bool scrambled_n_ecc) +{ + g_assert(baseptr % sizeof(uint64_t) == 0); + + s->scrambled_n_ecc = scrambled_n_ecc; + s->se_buffer = (uint64_t *)baseptr; + unsigned word_count = (s->size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); + if (scrambled_n_ecc) { + s->se_word_bytes = OT_ROM_CTRL_WORD_BYTES; + s->se_last_pos = word_count * OT_ROM_CTRL_WORD_BYTES; + } else { + s->se_word_bytes = sizeof(uint64_t); + s->se_last_pos = word_count * sizeof(uint32_t); + } + s->se_pos = 0; + ot_rom_ctrl_send_kmac_req(s); +} + +static void ot_rom_ctrl_load_elf(OtRomCtrlState *s, const OtRomImg *ri) { AddressSpace *as = ot_common_get_local_address_space(DEVICE(s)); hwaddr minaddr; hwaddr maxaddr; ot_rom_ctrl_get_mem_bounds(s, &minaddr, &maxaddr); uint64_t loaddr; + if (load_elf_ram_sym_nosz(ri->filename, NULL, NULL, NULL, NULL, &loaddr, NULL, NULL, 0, EM_RISCV, 1, 0, as, false, &ot_rom_ctrl_rust_demangle_fn, true) <= 0) { error_setg(&error_fatal, "ot_rom_ctrl: %s: ROM image '%s', ELF loading failed", s->ot_id, ri->filename); - return false; + return; } if ((loaddr < minaddr) || (loaddr > maxaddr)) { /* cannot test upper load address as QEMU loader returns VMA, not LMA */ @@ -546,15 +561,16 @@ static bool ot_rom_ctrl_load_elf(OtRomCtrlState *s, const OtRomImg *ri) s->ot_id); } - return false; + uintptr_t hostptr = (uintptr_t)memory_region_get_ram_ptr(&s->mem); + ot_rom_ctrl_spawn_hash_calculation(s, hostptr, false); } -static bool ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) +static void ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) { if (ri->raw_size > s->size) { error_setg(&error_fatal, "%s: %s: cannot fit into ROM", __func__, s->ot_id); - return false; + return; } /* NOLINTNEXTLINE(misc-redundant-expression) */ @@ -562,7 +578,7 @@ static bool ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) if (fd == -1) { error_setg(&error_fatal, "%s: %s: could not open ROM '%s': %s", __func__, s->ot_id, ri->filename, strerror(errno)); - return false; + return; } uint8_t *data = g_malloc0(ri->raw_size); @@ -574,7 +590,7 @@ static bool ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) error_setg(&error_fatal, "%s: %s: file %s: read error: rc=%zd (expected %u)", __func__, s->ot_id, ri->filename, rc, ri->raw_size); - return false; + return; } uintptr_t hostptr = (uintptr_t)memory_region_get_ram_ptr(&s->mem); @@ -583,7 +599,7 @@ static bool ot_rom_ctrl_load_binary(OtRomCtrlState *s, const OtRomImg *ri) memory_region_set_dirty(&s->mem, 0, ri->raw_size); - return false; + ot_rom_ctrl_spawn_hash_calculation(s, hostptr, false); } static char *ot_rom_ctrl_read_text_file(OtRomCtrlState *s, const OtRomImg *ri) @@ -618,12 +634,12 @@ static char *ot_rom_ctrl_read_text_file(OtRomCtrlState *s, const OtRomImg *ri) return buffer; } -static bool ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, +static void ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, bool scrambled_n_ecc) { char *buffer = ot_rom_ctrl_read_text_file(s, ri); if (!buffer) { - return false; + return; } uintptr_t baseptr; @@ -670,7 +686,7 @@ static bool ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, error_setg(&error_fatal, "%s: %s: address discrepancy in VMEM file '%s'", __func__, s->ot_id, ri->filename); - return false; + return; } if (blk_addr != exp_addr) { /* each block contains 32-bit of data */ @@ -687,7 +703,7 @@ static bool ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, g_free(buffer); error_setg(&error_fatal, "%s: %s: VMEM file '%s' too large", __func__, s->ot_id, ri->filename); - return false; + return; } for (unsigned blk = 0; blk < blk_count; blk++) { @@ -718,26 +734,15 @@ static bool ot_rom_ctrl_load_vmem(OtRomCtrlState *s, const OtRomImg *ri, memory_region_set_dirty(&s->mem, 0, memptr - baseptr); - if (scrambled_n_ecc) { - /* spawn hash calculation */ - s->se_buffer = (uint64_t *)baseptr; - unsigned word_count = - (s->size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); - s->se_last_pos = word_count * OT_ROM_CTRL_WORD_BYTES; - s->se_pos = 0; - ot_rom_ctrl_send_kmac_req(s); - return true; - } + ot_rom_ctrl_spawn_hash_calculation(s, baseptr, scrambled_n_ecc); } - - return false; } -static bool ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) +static void ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) { char *buffer = ot_rom_ctrl_read_text_file(s, ri); if (!buffer) { - return false; + return; } /* @@ -766,7 +771,7 @@ static bool ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) g_free(buffer); error_setg(&error_fatal, "%s: %s: HEX file '%s' too large", __func__, s->ot_id, ri->filename); - return false; + return; } char *end; @@ -775,7 +780,7 @@ static bool ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) g_free(buffer); error_setg(&error_fatal, "%s: %s: invalid line in HEX file '%s'", __func__, s->ot_id, ri->filename); - return false; + return; } stq_le_p((void *)memptr, value); memptr += sizeof(uint64_t); @@ -795,23 +800,14 @@ static bool ot_rom_ctrl_load_hex(OtRomCtrlState *s, const OtRomImg *ri) error_setg(&error_fatal, "%s: %s: incomplete HEX file '%s': %u bytes", __func__, s->ot_id, ri->filename, loaded_size / 2u); - return false; + return; } - /* spawn hash calculation */ - s->se_buffer = (uint64_t *)baseptr; - unsigned word_count = - (s->size - OT_ROM_DIGEST_BYTES) / sizeof(uint32_t); - s->se_last_pos = word_count * OT_ROM_CTRL_WORD_BYTES; - s->se_pos = 0; - ot_rom_ctrl_send_kmac_req(s); - return true; + ot_rom_ctrl_spawn_hash_calculation(s, baseptr, true); } - - return false; } -static bool ot_rom_ctrl_load_rom(OtRomCtrlState *s) +static void ot_rom_ctrl_load_rom(OtRomCtrlState *s) { Object *obj = NULL; OtRomImg *rom_img = NULL; @@ -820,50 +816,48 @@ static bool ot_rom_ctrl_load_rom(OtRomCtrlState *s) obj = object_resolve_path_component(object_get_objects_root(), s->ot_id); if (!obj) { trace_ot_rom_ctrl_load_rom_no_image(s->ot_id); - return false; + uintptr_t hostptr = (uintptr_t)memory_region_get_ram_ptr(&s->mem); + ot_rom_ctrl_spawn_hash_calculation(s, hostptr, false); + return; } rom_img = (OtRomImg *)object_dynamic_cast(obj, TYPE_OT_ROM_IMG); if (!rom_img) { error_setg(&error_fatal, "%s: %s: Object is not a ROM Image", __func__, s->ot_id); - return false; + return; } const char *basename = strrchr(rom_img->filename, '/'); basename = basename ? basename + 1 : rom_img->filename; - bool dig; switch (rom_img->format) { case OT_ROM_IMG_FORMAT_VMEM_PLAIN: trace_ot_rom_ctrl_image_identify(s->ot_id, basename, "plain VMEM"); - dig = ot_rom_ctrl_load_vmem(s, rom_img, false); + ot_rom_ctrl_load_vmem(s, rom_img, false); break; case OT_ROM_IMG_FORMAT_VMEM_SCRAMBLED_ECC: trace_ot_rom_ctrl_image_identify(s->ot_id, basename, "scrambled VMEM w/ ECC"); - dig = ot_rom_ctrl_load_vmem(s, rom_img, true); + ot_rom_ctrl_load_vmem(s, rom_img, true); break; case OT_ROM_IMG_FORMAT_HEX_SCRAMBLED_ECC: trace_ot_rom_ctrl_image_identify(s->ot_id, basename, "scrambled HEX w/ ECC"); - dig = ot_rom_ctrl_load_hex(s, rom_img); + ot_rom_ctrl_load_hex(s, rom_img); break; case OT_ROM_IMG_FORMAT_ELF: trace_ot_rom_ctrl_image_identify(s->ot_id, basename, "ELF32"); - dig = ot_rom_ctrl_load_elf(s, rom_img); + ot_rom_ctrl_load_elf(s, rom_img); break; case OT_ROM_IMG_FORMAT_BINARY: trace_ot_rom_ctrl_image_identify(s->ot_id, basename, "Binary"); - dig = ot_rom_ctrl_load_binary(s, rom_img); + ot_rom_ctrl_load_binary(s, rom_img); break; case OT_ROM_IMG_FORMAT_NONE: default: error_setg(&error_fatal, "%s: %s: unable to read binary file '%s'", __func__, s->ot_id, rom_img->filename); - dig = false; } - - return dig; } static uint64_t ot_rom_ctrl_regs_read(void *opaque, hwaddr addr, unsigned size) @@ -1116,24 +1110,14 @@ static void ot_rom_ctrl_set_load(Object *obj, bool value, Error **errp) if (!s->loaded) { s->loaded = true; - bool notify = true; - /* on initial reset, load ROM then set it read-only */ if (s->first_reset) { /* load ROM from file */ - bool dig = ot_rom_ctrl_load_rom(s); + ot_rom_ctrl_load_rom(s); /* ensure ROM can no longer be written */ s->first_reset = false; - - if (!dig) { - ot_rom_ctrl_fake_digest(s); - } - - notify = !dig; - } - - if (notify) { + } else { /* compare existing digests and send notification to pwrmgr */ ot_rom_ctrl_compare_and_notify(s); } From 83a5299b1832c099f712bd6ffb4c90f1578b775a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 17 Jul 2025 18:32:32 +0200 Subject: [PATCH 155/175] [ot] hw/opentitan: ot_kmac: add trace for incoming app requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_kmac.c | 10 ++++++++++ hw/opentitan/trace-events | 1 + 2 files changed, 11 insertions(+) diff --git a/hw/opentitan/ot_kmac.c b/hw/opentitan/ot_kmac.c index 082298deaec00..eee2d4dc84cb5 100644 --- a/hw/opentitan/ot_kmac.c +++ b/hw/opentitan/ot_kmac.c @@ -1667,6 +1667,16 @@ static void ot_kmac_app_request(OtKMACState *s, unsigned app_idx, const OtKMACAppReq *req) { g_assert(app_idx < s->num_app); + g_assert(req); + g_assert(req->msg_len <= OT_KMAC_APP_MSG_BYTES); + + if (trace_event_get_state(TRACE_OT_KMAC_APP_REQUEST)) { + trace_ot_kmac_app_request(s->ot_id, app_idx, req->last, req->msg_len, + ot_common_lhexdump(req->msg_data, + req->msg_len, false, + s->hexstr, + OT_KMAC_KEY_HEXSTR_SIZE)); + } OtKMACApp *app = &s->apps[app_idx]; diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 423c9d76d2cf9..53e35f95a5679 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -295,6 +295,7 @@ ot_keymgr_dpe_sideload_clear(const char *id, const char *sc, unsigned nsc) "%s: # ot_kmac.c ot_kmac_app_finished(const char *id, unsigned app_idx) "%s: #%u" +ot_kmac_app_request(const char *id, unsigned app_idx, bool last, size_t msg_len, const char *hexstr) "%s: #%u last:%u msg_len:%zu msg_data:%s" ot_kmac_app_start(const char *id, unsigned app_idx) "%s: #%u" ot_kmac_change_state_app(const char *id, unsigned app_idx, int line, const char *old, int nold, const char *new, int nnew) "%s: #%u @ %d [%s:%d] -> [%s:%d]" ot_kmac_change_state_sw(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" From fce326aeb7bc68f6156fa3a84007782ae7c2fc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Thu, 17 Jul 2025 19:03:32 +0200 Subject: [PATCH 156/175] [ot] hw/opentitan: ot_rom_ctrl: add trace for computed ROM digests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_rom_ctrl.c | 15 +++++++++++++++ hw/opentitan/trace-events | 1 + 2 files changed, 16 insertions(+) diff --git a/hw/opentitan/ot_rom_ctrl.c b/hw/opentitan/ot_rom_ctrl.c index f6e7c5dd170bb..b3d3d8a7b9272 100644 --- a/hw/opentitan/ot_rom_ctrl.c +++ b/hw/opentitan/ot_rom_ctrl.c @@ -116,6 +116,8 @@ static const char *REG_NAMES[REGS_COUNT] = { #define ROM_DIGEST_WORDS (OT_ROM_DIGEST_BYTES / sizeof(uint32_t)) +#define OT_ROM_CTRL_HEXSTR_SIZE (OT_ROM_DIGEST_BYTES * 2u + 2u) + /* clang-format off */ static const uint8_t SBOX4[16u] = { 12u, 5u, 6u, 11u, 9u, 0u, 10u, 13u, 3u, 14u, 15u, 8u, 4u, 7u, 1u, 2u @@ -149,6 +151,7 @@ struct OtRomCtrlState { uint64_t *se_buffer; unsigned recovered_error_count; unsigned unrecoverable_error_count; + char *hexstr; bool first_reset; bool loaded; bool scrambled_n_ecc; @@ -390,6 +393,16 @@ ot_rom_ctrl_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) } } + if (trace_event_get_state(TRACE_OT_ROM_CTRL_COMPUTED_DIGEST)) { + uint8_t digest[OT_ROM_DIGEST_BYTES]; + for (unsigned ix = 0; ix < OT_ROM_DIGEST_BYTES; ix++) { + digest[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; + } + trace_ot_rom_ctrl_computed_digest( + s->ot_id, ot_common_lhexdump(digest, OT_ROM_DIGEST_BYTES, true, + s->hexstr, OT_ROM_CTRL_HEXSTR_SIZE)); + } + trace_ot_rom_ctrl_digest_mode(s->ot_id, "stored"); /* compare digests and send notification */ @@ -1196,6 +1209,8 @@ static void ot_rom_ctrl_init(Object *obj) object_property_add_bool(obj, "load", NULL, &ot_rom_ctrl_set_load); object_property_set_description(obj, "load", "Trigger initial ROM loading"); + + s->hexstr = g_new0(char, OT_ROM_CTRL_HEXSTR_SIZE); } static void ot_rom_ctrl_class_init(ObjectClass *klass, void *data) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 53e35f95a5679..f00a8294fa263 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -436,6 +436,7 @@ ot_pwrmgr_wkup(const char *id, const char *name, unsigned src, bool active) "%s: # ot_rom_ctrl.c +ot_rom_ctrl_computed_digest(const char *id, const char *hexstr) "%s: digest:%s" ot_rom_ctrl_digest_mode(const char *id, const char *mode) "%s: %s digest mode" ot_rom_ctrl_parity_error(const char * id, uint32_t d_i, unsigned ecc) "%s: 0x%08x, ECC 0x%02x" ot_rom_ctrl_recovered_error(const char * id, uint32_t d_i, uint32_t d_o) "%s: 0x%08x -> 0x%08x" From 097bbc9888b47584f101358978bb73cfcecb23b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Fri, 18 Jul 2025 11:00:26 +0200 Subject: [PATCH 157/175] [ot] hw/opentitan: ot_keymgr_dpe: switch compile-time traces to runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_keymgr_dpe.c | 197 +++++++++++++++++------------------ hw/opentitan/trace-events | 11 +- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/hw/opentitan/ot_keymgr_dpe.c b/hw/opentitan/ot_keymgr_dpe.c index ecd9d7565b40b..beeba5c49334c 100644 --- a/hw/opentitan/ot_keymgr_dpe.c +++ b/hw/opentitan/ot_keymgr_dpe.c @@ -51,8 +51,6 @@ #include "hw/riscv/ibex_irq.h" #include "trace.h" -#undef OT_KEYMGR_DPE_DEBUG - #define NUM_SALT_REG 8u #define NUM_SW_BINDING_REG 8u #define NUM_ROM_DIGEST_INPUTS 2u @@ -574,30 +572,24 @@ static const char *FST_NAMES[] = { #define FST_NAME(_st_) \ (((unsigned)(_st_)) < ARRAY_SIZE(FST_NAMES) ? FST_NAMES[(_st_)] : "?") -#ifdef OT_KEYMGR_DPE_DEBUG #define OT_KEYMGR_DPE_HEXSTR_SIZE 256u -#define TRACE_KEYMGR_DPE(_s_, _msg_, ...) \ - qemu_log("%s: %s: " _msg_ "\n", __func__, (_s_)->ot_id, ##__VA_ARGS__); + #define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) \ ot_common_lhexdump(_b_, _l_, true, (_s_)->hexstr, OT_KEYMGR_DPE_HEXSTR_SIZE) -#else -#define ot_keymgr_dpe_dump_bigint(_s_, _b_, _l_) -#define TRACE_KEYMGR_DPE(_s_, _msg_, ...) -#endif -static void ot_keymgr_dpe_dump_kdf_buf(OtKeyMgrDpeState *s) +static void ot_keymgr_dpe_dump_kdf_buf(OtKeyMgrDpeState *s, const char *op) { -#ifdef OT_KEYMGR_DPE_DEBUG - size_t msgs = (s->kdf_buf.length + OT_KMAC_APP_MSG_BYTES - 1u) / - OT_KMAC_APP_MSG_BYTES; - for (size_t ix = 0u; ix < msgs; ix++) { - TRACE_KEYMGR_DPE( - s, "kdf_buf[%lu]: %s", ix, - ot_common_lhexdump(&s->kdf_buf.data[ix * OT_KMAC_APP_MSG_BYTES], - OT_KMAC_APP_MSG_BYTES, true, s->hexstr, - OT_KEYMGR_DPE_HEXSTR_SIZE)); + if (trace_event_get_state(TRACE_OT_KEYMGR_DPE_DUMP_KDF_BUF)) { + size_t msgs = (s->kdf_buf.length + OT_KMAC_APP_MSG_BYTES - 1u) / + OT_KMAC_APP_MSG_BYTES; + for (size_t ix = 0u; ix < msgs; ix++) { + trace_ot_keymgr_dpe_dump_kdf_buf( + s->ot_id, op, ix, + ot_keymgr_dpe_dump_bigint( + s, &s->kdf_buf.data[ix * OT_KMAC_APP_MSG_BYTES], + OT_KMAC_APP_MSG_BYTES)); + } } -#endif } #define ot_keymgr_dpe_schedule_fsm(_s_) \ @@ -782,23 +774,23 @@ static void ot_keymgr_dpe_push_key( kc->push_key(ki, key_share0, key_share1, key_size, valid); } -static void ot_keymgr_dpe_send_kmac_key(OtKeyMgrDpeState *s) +static void ot_keymgr_dpe_kmac_push_key(OtKeyMgrDpeState *s) { uint32_t ctrl = ot_shadow_reg_peek(&s->control); uint8_t slot_src_sel = (uint8_t)FIELD_EX32(ctrl, CONTROL_SHADOWED, SLOT_SRC_SEL); OtKeyMgrDpeSlot *src_slot = &s->key_slots[slot_src_sel]; -#ifdef OT_KEYMGR_DPE_DEBUG - uint8_t key_value[OT_KMAC_KEY_SIZE]; - for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { - key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; - } + if (trace_event_get_state(TRACE_OT_KEYMGR_DPE_KMAC_PUSH_KEY)) { + uint8_t key_value[OT_KMAC_KEY_SIZE]; + for (unsigned ix = 0u; ix < OT_KMAC_KEY_SIZE; ix++) { + key_value[ix] = src_slot->key.share0[ix] ^ src_slot->key.share1[ix]; + } - TRACE_KEYMGR_DPE(s, "KMAC key: %s valid: %d", - ot_keymgr_dpe_dump_bigint(s, key_value, OT_KMAC_KEY_SIZE), - src_slot->valid); -#endif + trace_ot_keymgr_dpe_kmac_push_key( + s->ot_id, src_slot->valid, + ot_keymgr_dpe_dump_bigint(s, key_value, OT_KMAC_KEY_SIZE)); + } ot_keymgr_dpe_push_key(s, KEYMGR_DPE_KEY_SINK_KMAC, src_slot->key.share0, src_slot->key.share1, src_slot->valid); @@ -825,10 +817,6 @@ static void ot_keymgr_dpe_send_kmac_req(OtKeyMgrDpeState *s) }; memcpy(req.msg_data, &s->kdf_buf.data[offset], msg_len); - TRACE_KEYMGR_DPE(s, "KMAC req: %s last: %d", - ot_keymgr_dpe_dump_bigint(s, req.msg_data, req.msg_len), - req.last); - trace_ot_keymgr_dpe_kmac_req(s->ot_id, len, req.msg_len, req.last); OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); @@ -903,7 +891,19 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) uint32_t ctrl = ot_shadow_reg_peek(&s->control); bool reset_kmac_key = true; - trace_ot_keymgr_dpe_kmac_rsp(s->ot_id, rsp->done); + if (trace_event_get_state(TRACE_OT_KEYMGR_DPE_KMAC_RSP)) { + if (rsp->done) { + uint8_t key[OT_KMAC_APP_DIGEST_BYTES]; + for (unsigned ix = 0u; ix < OT_KMAC_APP_DIGEST_BYTES; ix++) { + key[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; + } + trace_ot_keymgr_dpe_kmac_rsp( + s->ot_id, true, + ot_keymgr_dpe_dump_bigint(s, key, OT_KMAC_APP_DIGEST_BYTES)); + } else { + trace_ot_keymgr_dpe_kmac_rsp(s->ot_id, false, ""); + } + } if (!rsp->done) { /* not last response from KMAC, send more data */ @@ -913,16 +913,6 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) g_assert(s->kdf_buf.offset == s->kdf_buf.length); -#ifdef OT_KEYMGR_DPE_DEBUG - uint8_t key[OT_KMAC_APP_DIGEST_BYTES]; - for (unsigned ix = 0u; ix < OT_KMAC_APP_DIGEST_BYTES; ix++) { - key[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; - } - TRACE_KEYMGR_DPE(s, "KMAC resp: %s", - ot_keymgr_dpe_dump_bigint(s, key, - OT_KMAC_APP_DIGEST_BYTES)); -#endif - switch (FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION)) { case KEYMGR_DPE_OP_ADVANCE: { uint8_t slot_src_sel = @@ -951,6 +941,7 @@ ot_keymgr_dpe_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) /* reset KMAC key if required */ if (reset_kmac_key) { /* TODO: maybe push last sideloaded KMAC key instead? */ + trace_ot_keymgr_dpe_reset_kmac_key(s->ot_id); ot_keymgr_dpe_push_key(s, KEYMGR_DPE_KEY_SINK_KMAC, NULL, NULL, false); } @@ -985,14 +976,22 @@ static void ot_keymgr_dpe_kdf_push_bytes(OtKeyMgrDpeState *s, s->kdf_buf.length += len; } +static void ot_keymgr_dpe_dump_kdf_material( + OtKeyMgrDpeState *s, const char *what, const uint8_t *buf, size_t len) +{ + if (trace_event_get_state(TRACE_OT_KEYMGR_DPE_DUMP_KDF_MATERIAL)) { + const char *hexstr = ot_keymgr_dpe_dump_bigint(s, buf, len); + trace_ot_keymgr_dpe_dump_kdf_material(s->ot_id, what, hexstr); + } +} + static void ot_keymgr_dpe_kdf_push_key_version(OtKeyMgrDpeState *s) { uint8_t buf[sizeof(uint32_t)]; stl_le_p(buf, s->regs[R_KEY_VERSION]); ot_keymgr_dpe_kdf_push_bytes(s, buf, sizeof(uint32_t)); - TRACE_KEYMGR_DPE(s, "Key Version: %s", - ot_keymgr_dpe_dump_bigint(s, buf, sizeof(uint32_t))); + ot_keymgr_dpe_dump_kdf_material(s, "KEY_VERSION", buf, sizeof(uint32_t)); } static size_t @@ -1008,9 +1007,8 @@ ot_keymgr_dpe_kdf_append_creator_seed(OtKeyMgrDpeState *s, bool *dvalid) *dvalid &= ot_keymgr_dpe_valid_data_check(secret.secret, OT_OTP_KEYMGR_SECRET_SIZE); - TRACE_KEYMGR_DPE(s, "Creator Seed: %s", - ot_keymgr_dpe_dump_bigint(s, secret.secret, - OT_OTP_KEYMGR_SECRET_SIZE)); + ot_keymgr_dpe_dump_kdf_material(s, "CREATOR_SEED", secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE); return OT_OTP_KEYMGR_SECRET_SIZE; } @@ -1024,9 +1022,10 @@ static size_t ot_keymgr_dpe_kdf_append_rom_digest( ot_keymgr_dpe_kdf_push_bytes(s, rom_digest, OT_ROM_DIGEST_BYTES); *dvalid &= ot_keymgr_dpe_valid_data_check(rom_digest, OT_ROM_DIGEST_BYTES); - TRACE_KEYMGR_DPE(s, "ROM%u digest: %s", rom_idx, - ot_keymgr_dpe_dump_bigint(s, rom_digest, - OT_ROM_DIGEST_BYTES)); + + char buf[16u]; + snprintf(buf, sizeof(buf), "ROM%u_DIGEST", rom_idx); + ot_keymgr_dpe_dump_kdf_material(s, buf, rom_digest, OT_ROM_DIGEST_BYTES); return OT_ROM_DIGEST_BYTES; } @@ -1042,9 +1041,8 @@ static size_t ot_keymgr_dpe_kdf_append_km_div(OtKeyMgrDpeState *s, bool *dvalid) *dvalid &= ot_keymgr_dpe_valid_data_check(km_div.data, OT_LC_KEYMGR_DIV_BYTES); - TRACE_KEYMGR_DPE(s, "KeyManager div: %s", - ot_keymgr_dpe_dump_bigint(s, km_div.data, - OT_LC_KEYMGR_DIV_BYTES)); + ot_keymgr_dpe_dump_kdf_material(s, "KM_DIV", km_div.data, + OT_LC_KEYMGR_DIV_BYTES); return OT_LC_KEYMGR_DIV_BYTES; } @@ -1058,9 +1056,8 @@ static size_t ot_keymgr_dpe_kdf_append_dev_id(OtKeyMgrDpeState *s, bool *dvalid) *dvalid &= ot_keymgr_dpe_valid_data_check(hw_cfg->device_id, OT_OTP_HWCFG_DEVICE_ID_BYTES); - TRACE_KEYMGR_DPE(s, "Device ID: %s", - ot_keymgr_dpe_dump_bigint(s, hw_cfg->device_id, - OT_OTP_HWCFG_DEVICE_ID_BYTES)); + ot_keymgr_dpe_dump_kdf_material(s, "DEVICE_ID", hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); return OT_OTP_HWCFG_DEVICE_ID_BYTES; } @@ -1069,9 +1066,9 @@ static size_t ot_keymgr_dpe_kdf_append_rev_seed(OtKeyMgrDpeState *s) ot_keymgr_dpe_kdf_push_bytes(s, s->seeds[KEYMGR_DPE_SEED_REV], KEYMGR_DPE_SEED_BYTES); - TRACE_KEYMGR_DPE(s, "Revision Seed: %s", - ot_keymgr_dpe_dump_bigint(s, s->seeds[KEYMGR_DPE_SEED_REV], - KEYMGR_DPE_SEED_BYTES)); + ot_keymgr_dpe_dump_kdf_material(s, "REV_SEED", + s->seeds[KEYMGR_DPE_SEED_REV], + KEYMGR_DPE_SEED_BYTES); return KEYMGR_DPE_SEED_BYTES; } @@ -1088,9 +1085,8 @@ ot_keymgr_dpe_kdf_append_owner_seed(OtKeyMgrDpeState *s, bool *dvalid) *dvalid &= ot_keymgr_dpe_valid_data_check(secret.secret, OT_OTP_KEYMGR_SECRET_SIZE); - TRACE_KEYMGR_DPE(s, "Owner Seed: %s", - ot_keymgr_dpe_dump_bigint(s, secret.secret, - OT_OTP_KEYMGR_SECRET_SIZE)); + ot_keymgr_dpe_dump_kdf_material(s, "OWNER_SEED", secret.secret, + OT_OTP_KEYMGR_SECRET_SIZE); return OT_OTP_KEYMGR_SECRET_SIZE; } @@ -1161,10 +1157,8 @@ static void ot_keymgr_dpe_operation_advance(OtKeyMgrDpeState *s) ot_keymgr_dpe_kdf_push_bytes(s, s->sw_binding, NUM_SW_BINDING_REG * sizeof(uint32_t)); expected_kdf_len += NUM_SW_BINDING_REG * sizeof(uint32_t); - TRACE_KEYMGR_DPE(s, "Software Binding: %s", - ot_keymgr_dpe_dump_bigint(s, s->sw_binding, - NUM_SW_BINDING_REG * - sizeof(uint32_t))); + ot_keymgr_dpe_dump_kdf_material(s, "SW_BINDING", s->sw_binding, + NUM_SW_BINDING_REG * sizeof(uint32_t)); /* check that we have pushed all expected KDF data*/ g_assert(s->kdf_buf.length == expected_kdf_len); @@ -1174,9 +1168,9 @@ static void ot_keymgr_dpe_operation_advance(OtKeyMgrDpeState *s) g_assert(s->kdf_buf.length <= KEYMGR_DPE_ADV_DATA_BYTES); s->kdf_buf.length = KEYMGR_DPE_ADV_DATA_BYTES; - ot_keymgr_dpe_send_kmac_key(s); + ot_keymgr_dpe_kmac_push_key(s); - ot_keymgr_dpe_dump_kdf_buf(s); + ot_keymgr_dpe_dump_kdf_buf(s, "adv"); ot_keymgr_dpe_send_kmac_req(s); } @@ -1213,9 +1207,8 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) uint8_t *output_key = sw ? s->seeds[KEYMGR_DPE_SEED_SW_OUT] : s->seeds[KEYMGR_DPE_SEED_HW_OUT]; ot_keymgr_dpe_kdf_push_bytes(s, output_key, KEYMGR_DPE_SEED_BYTES); - TRACE_KEYMGR_DPE(s, "Output Key Seed: %s", - ot_keymgr_dpe_dump_bigint(s, output_key, - KEYMGR_DPE_SEED_BYTES)); + ot_keymgr_dpe_dump_kdf_material(s, "OUT_KEY_SEED", output_key, + KEYMGR_DPE_SEED_BYTES); /* Destination Seed (AES/KMAC/OTBN/other) */ uint8_t *dest_seed; @@ -1235,17 +1228,14 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) break; } ot_keymgr_dpe_kdf_push_bytes(s, dest_seed, KEYMGR_DPE_SEED_BYTES); - TRACE_KEYMGR_DPE(s, "Destination Seed: %s", - ot_keymgr_dpe_dump_bigint(s, dest_seed, - KEYMGR_DPE_SEED_BYTES)); + ot_keymgr_dpe_dump_kdf_material(s, "DEST_SEED", dest_seed, + KEYMGR_DPE_SEED_BYTES); /* Salt (software-provided via SALT_x registers) */ ot_keymgr_dpe_kdf_push_bytes(s, s->salt, NUM_SALT_REG * sizeof(uint32_t)); - TRACE_KEYMGR_DPE(s, "Salt: %s", - ot_keymgr_dpe_dump_bigint(s, s->salt, - NUM_SALT_REG * - sizeof(uint32_t))); + ot_keymgr_dpe_dump_kdf_material(s, "SALT", s->salt, + NUM_SALT_REG * sizeof(uint32_t)); /* Key Version (software-provided via KEY_VERSION register) */ ot_keymgr_dpe_kdf_push_key_version(s); @@ -1266,9 +1256,9 @@ static void ot_keymgr_dpe_operation_gen_output(OtKeyMgrDpeState *s, bool sw) g_assert(s->kdf_buf.length == KEYMGR_DPE_GEN_DATA_BYTES); - ot_keymgr_dpe_send_kmac_key(s); + ot_keymgr_dpe_kmac_push_key(s); - ot_keymgr_dpe_dump_kdf_buf(s); + ot_keymgr_dpe_dump_kdf_buf(s, "gen"); ot_keymgr_dpe_send_kmac_req(s); } @@ -1416,6 +1406,28 @@ static void ot_keymgr_dpe_xchange_main_fsm_state( } } +static void ot_keymgr_dpe_get_root_key( + OtKeyMgrDpeState *s, OtOTPKeyMgrSecret *share0, OtOTPKeyMgrSecret *share1) +{ + OtOTPClass *oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); + g_assert(oc); + oc->get_keymgr_secret(s->otp, OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE0, + share0); + oc->get_keymgr_secret(s->otp, OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1, + share1); + + if (trace_event_get_state(TRACE_OT_KEYMGR_DPE_DUMP_CREATOR_ROOT_KEY)) { + trace_ot_keymgr_dpe_dump_creator_root_key( + s->ot_id, 0, share0->valid, + ot_keymgr_dpe_dump_bigint(s, share0->secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + trace_ot_keymgr_dpe_dump_creator_root_key( + s->ot_id, 1, share1->valid, + ot_keymgr_dpe_dump_bigint(s, share1->secret, + OT_OTP_KEYMGR_SECRET_SIZE)); + } +} + static bool ot_keymgr_dpe_main_fsm_tick(OtKeyMgrDpeState *s) { OtKeyMgrDpeFSMState state = s->state; @@ -1479,22 +1491,7 @@ static bool ot_keymgr_dpe_main_fsm_tick(OtKeyMgrDpeState *s) /* retrieve Creator Root Key from OTP */ OtOTPKeyMgrSecret secret_share0 = { 0u }; OtOTPKeyMgrSecret secret_share1 = { 0u }; - OtOTPClass *oc = OBJECT_GET_CLASS(OtOTPClass, s->otp, TYPE_OT_OTP); - g_assert(oc); - oc->get_keymgr_secret(s->otp, - OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE0, - &secret_share0); - TRACE_KEYMGR_DPE( - s, "Creator Root Key share 0: %s", - ot_keymgr_dpe_dump_bigint(s, secret_share0.secret, - OT_OTP_KEYMGR_SECRET_SIZE)); - oc->get_keymgr_secret(s->otp, - OTP_KEYMGR_SECRET_CREATOR_ROOT_KEY_SHARE1, - &secret_share1); - TRACE_KEYMGR_DPE( - s, "Creator Root Key share 1: %s", - ot_keymgr_dpe_dump_bigint(s, secret_share1.secret, - OT_OTP_KEYMGR_SECRET_SIZE)); + ot_keymgr_dpe_get_root_key(s, &secret_share0, &secret_share1); if (secret_share0.valid && secret_share1.valid) { memset(dst_slot, 0u, sizeof(OtKeyMgrDpeSlot)); @@ -2127,9 +2124,7 @@ static void ot_keymgr_dpe_init(Object *obj) s->fsm_tick_bh = qemu_bh_new(&ot_keymgr_dpe_fsm_tick, s); -#ifdef OT_KEYMGR_DPE_DEBUG s->hexstr = g_new0(char, OT_KEYMGR_DPE_HEXSTR_SIZE); -#endif } static void ot_keymgr_dpe_class_init(ObjectClass *klass, void *data) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index f00a8294fa263..53efadff2f489 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -276,20 +276,25 @@ ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halte ot_keymgr_dpe_change_main_fsm_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_dpe_change_op_status(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_dpe_change_working_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" -ot_keymgr_dpe_entropy(const char * id, unsigned reseed_cnt, bool resched) "%s: reseed_cnt: %u, resched: %u" +ot_keymgr_dpe_dump_creator_root_key(const char *id, unsigned idx, bool valid, const char *hexstr) "%s: share:%u valid:%u %s" +ot_keymgr_dpe_dump_kdf_buf(const char *id, const char *op, size_t idx, const char *hexstr) "%s: %s: kdf[%02zu] %s" +ot_keymgr_dpe_dump_kdf_material(const char *id, const char *what, const char *hexstr) "%s: %s %s" +ot_keymgr_dpe_entropy(const char *id, unsigned reseed_cnt, bool resched) "%s: reseed_cnt: %u, resched: %u" ot_keymgr_dpe_error(const char *id, const char *msg) "%s: %s" ot_keymgr_dpe_go_idle(const char *id) "%s" ot_keymgr_dpe_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_dpe_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_dpe_irq(const char *id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" +ot_keymgr_dpe_kmac_push_key(const char *id, bool valid, const char *hexstr) "%s: valid:%u key:%s" ot_keymgr_dpe_kmac_req(const char *id, uint32_t fifo_len, uint32_t msg_len, bool last) "%s: fifo_len:%u msg_len:%u, last:%d" -ot_keymgr_dpe_kmac_rsp(const char *id, bool done) "%s: done:%d" +ot_keymgr_dpe_kmac_rsp(const char *id, bool done, const char *hexstr) "%s: done:%d %s" ot_keymgr_dpe_lc_signal(const char *id, int level) "%s: LC signal level:%d" ot_keymgr_dpe_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" ot_keymgr_dpe_operation(const char *id, const char *op, int nop) "%s: [%s:%d]" ot_keymgr_dpe_reset(const char *id, const char *stage) "%s: %s" +ot_keymgr_dpe_reset_kmac_key(const char *id) "%s" ot_keymgr_dpe_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" -ot_keymgr_dpe_seed_missing(const char * id, unsigned ix) "%s: #%u" +ot_keymgr_dpe_seed_missing(const char *id, unsigned ix) "%s: #%u" ot_keymgr_dpe_sideload_clear(const char *id, const char *sc, unsigned nsc) "%s: [%s:%u]" # ot_kmac.c From 43b4bdd623bec9fd3119fe18c3b6b2bcfcba032f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:22:16 +0200 Subject: [PATCH 158/175] [ot] python/qemu: ot.util.file: rename guess_test_type as guess_file_type Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 6 +++--- python/qemu/ot/pyot/filemgr.py | 4 ++-- python/qemu/ot/util/file.py | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index b669814bf5928..c3d9965b1dc16 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -21,7 +21,7 @@ import re import sys -from ot.util.file import guess_test_type +from ot.util.file import guess_file_type from ot.util.log import flush_memory_loggers from ot.util.misc import EasyDict @@ -110,7 +110,7 @@ def enumerate_tests(self) -> Iterator[str]: """ self._argdict = dict(self._args.__dict__) for tst in sorted(self._build_test_list()): - ttype = guess_test_type(tst) + ttype = guess_file_type(tst) yield f'{basename(tst)} ({ttype})' def run(self, debug: bool, allow_no_test: bool) -> int: @@ -328,7 +328,7 @@ def _build_qemu_fw_args(self, args: Namespace) \ exec_path = self._virtual_tests.get(args.exec) if not exec_path: exec_path = self.abspath(args.exec) - xtype = guess_test_type(exec_path) + xtype = guess_file_type(exec_path) if xtype == 'spiflash': fw_args.extend(('-drive', f'if=mtd,id=spiflash,bus=0,format=raw,' diff --git a/python/qemu/ot/pyot/filemgr.py b/python/qemu/ot/pyot/filemgr.py index ea1c1e09ecdd1..75d7f9b2c97db 100644 --- a/python/qemu/ot/pyot/filemgr.py +++ b/python/qemu/ot/pyot/filemgr.py @@ -18,7 +18,7 @@ import re from ot.util.elf import ElfBlob -from ot.util.file import guess_test_type +from ot.util.file import guess_file_type class QEMUFileManager: @@ -206,7 +206,7 @@ def create_eflash_image(self, app: Optional[str] = None, for xpath, xhdlr in xfiles: if not xpath: continue - xtype = guess_test_type(xpath) + xtype = guess_file_type(xpath) xstore = getattr(gen, f'store_{xhdlr}') xname = basename(xpath) if xtype == 'elf': diff --git a/python/qemu/ot/util/file.py b/python/qemu/ot/util/file.py index 14f71dd75d1c0..5046510c9dbcf 100644 --- a/python/qemu/ot/util/file.py +++ b/python/qemu/ot/util/file.py @@ -17,8 +17,10 @@ from .recfmt import RecordSegment, VMemBuilder -def guess_test_type(file_path: str) -> str: - """Guess a test file type from its contents. +def guess_file_type(file: Union[str, BufferedReader]) -> str: + """Guess a file type from its contents. + + Only supports useful type for QEMU/OT needs. :return: identified content """ From 61bda7a708a9e1cfaff9aa7a0c62b6e2927c3807 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:22:51 +0200 Subject: [PATCH 159/175] [ot] python/qemu: ot.util.file: add 'hex' file support to guess_file_type Signed-off-by: Emmanuel Blot --- python/qemu/ot/util/file.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/util/file.py b/python/qemu/ot/util/file.py index 5046510c9dbcf..34946231dd96e 100644 --- a/python/qemu/ot/util/file.py +++ b/python/qemu/ot/util/file.py @@ -6,6 +6,7 @@ :author: Emmanuel Blot """ +from io import BufferedReader from os import stat from os.path import relpath from time import localtime, strftime @@ -24,8 +25,13 @@ def guess_file_type(file: Union[str, BufferedReader]) -> str: :return: identified content """ - with open(file_path, 'rb') as bfp: - header = bfp.read(1024) + if isinstance(file, str): + with open(file, 'rb') as bfp: + header = bfp.read(1024) + elif isinstance(file, BufferedReader): + header = file.peek(1024) + else: + raise TypeError('file must be a string or a binary file object') if header[:4] == b'\x7fELF': return 'elf' if header[:4] == b'OTPT': @@ -36,6 +42,16 @@ def guess_file_type(file: Union[str, BufferedReader]) -> str: continue if re.match(vmem_re, line): return 'vmem' + hex_re = rb'(?i)^[0-9A-F]{6,}' + count = 0 + for line in header.split(b'\n'): + if re.match(hex_re, line) and (len(line) & 1) == 0: + count += 1 + else: + count = 0 + break + if count > 4: + return 'hex' return 'bin' From dec07034c2327d573e8473ee5cae8e8aa9393785 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:24:26 +0200 Subject: [PATCH 160/175] [ot] python/qemu: ot.otp: add get_field, decode_field, get_partition methods These should enable providing data extraction method to other modules. Signed-off-by: Emmanuel Blot --- python/qemu/ot/otp/image.py | 19 +++++++++++++++++++ python/qemu/ot/otp/partition.py | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index d9d9b4fe856e1..548aecb78d001 100644 --- a/python/qemu/ot/otp/image.py +++ b/python/qemu/ot/otp/image.py @@ -395,6 +395,15 @@ def toggle_bits(self, bitdefs: Sequence[tuple[int, int]]) -> None: """ self._change_bits(bitdefs, None) + def get_partition(self, partition: Union[int, str]) -> OtpPartition: + """Retrieve a partition. + + :param partition: the partition to retrieve, either specified as an + index or as the partition name + :return: the partition object + """ + return self._retrieve_partition(partition)[1] + def empty_partition(self, partition: Union[int, str]) -> None: """Empty the whole content of a partition, including its digest if any, and its ECC bits if any. @@ -409,6 +418,16 @@ def empty_partition(self, partition: Union[int, str]) -> None: start, end = self._reload(partix, False) self._ecc[start // 2:end // 2] = bytes((end-start) // 2) + def get_field(self, partition: Union[int, str], field: str) -> bytes: + """Get the content of a field within a partition. + + :param partition: the name or the index of the partition to erase + :param field: the name of the field to erase + :return: the content of the field as bytes + """ + part = self._retrieve_partition(partition)[1] + return part.get_field(field) + def change_field(self, partition: Union[int, str], field: str, value: Union[bytes, bytearray, bool, int, str]) -> None: """Change the content of a field within a partition. diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index 215bd3c9c78f2..668f92b10da15 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -291,6 +291,26 @@ def change_field(self, field: str, self._data = b''.join((self._data[:prop.offset], value, self._data[prop.end:])) + def decode_field(self, field: str) -> None: + """Decode the content of a field. + + :param field: the name of the field to decode + :param value: the value to decode + """ + if not self._decoder: + return None + val = self.get_field(field) + sval = hexlify(bytes(reversed(val))) + return self._decoder.decode(field, sval).upper() + + def get_field(self, field: str) -> bytes: + """Retrieve the content of a field. + + :param field: the name of the field to retrieve + """ + prop = self._retrieve_properties(field) + return self._data[prop.offset:prop.end] + def erase_field(self, field: str) -> None: """Erase (reset) the content of a field. From fabbdbfffc141da9a7f8ed364ebcfa172334c43f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:25:02 +0200 Subject: [PATCH 161/175] [ot] python/qemu: ot.util.prince: add a basic PRINCE cipher implementation Not yet fully tested. Signed-off-by: Emmanuel Blot --- python/qemu/ot/util/prince.py | 140 ++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 python/qemu/ot/util/prince.py diff --git a/python/qemu/ot/util/prince.py b/python/qemu/ot/util/prince.py new file mode 100644 index 0000000000000..8aced6c5c6c3a --- /dev/null +++ b/python/qemu/ot/util/prince.py @@ -0,0 +1,140 @@ +# Copyright (c) 2025 lowRISC contributors. +# SPDX-License-Identifier: Apache2 + +"""PRINCE cipher implementation. +""" + +# pylint: disable=missing-docstring + +class PrinceCipher: + + SBOX4 = [ + 0xb, 0xf, 0x3, 0x2, 0xa, 0xc, 0x9, 0x1, + 0x6, 0x7, 0x8, 0x0, 0xe, 0x5, 0xd, 0x4 + ] + + SBOX4_INV = [ + 0xb, 0x7, 0x3, 0x2, 0xf, 0xd, 0x8, 0x9, + 0xa, 0x6, 0x4, 0x0, 0x5, 0xe, 0xc, 0x1 + ] + + SHIFT_ROWS64 = [ + 0x4, 0x9, 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, + 0xc, 0x1, 0x6, 0xb, 0x0, 0x5, 0xa, 0xf + ] + + SHIFT_ROWS64_INV = [ + 0xc, 0x9, 0x6, 0x3, 0x0, 0xd, 0xa, 0x7, + 0x4, 0x1, 0xe, 0xb, 0x8, 0x5, 0x2, 0xf + ] + ROUND_CONSTS = [ + 0x0000000000000000, 0x13198a2e03707344, + 0xa4093822299f31d0, 0x082efa98ec4e6c89, + 0x452821e638d01377, 0xbe5466cf34e90c6c, + 0x7ef84f78fd955cb1, 0x85840851f1ac43aa, + 0xc882d32f25323c54, 0x64a51195e0e3610d, + 0xd3b5a399ca0c2399, 0xc0ac29b7c97c50dd + ] + + SHIFT_ROWS_CONSTS = [ + 0x7bde, 0xbde7, 0xde7b, 0xe7bd + ] + + + @classmethod + def sbox(cls, in_: int, width: int, sbox: list[int]) -> int: + full_mask = 0 if (width >= 64) else (1 << width) - 1 + width &= ~3 + sbox_mask = 0 if (width >= 64) else (1 << width) - 1 + + ret = in_ & (full_mask & ~sbox_mask) + + for ix in range(0, width, 4): + nibble = (in_ >> ix) & 0xf + ret |= sbox[nibble] << ix + + return ret + + @classmethod + def nibble_red16(cls, data: int) -> int: + nib0 = (data >> 0) & 0xf + nib1 = (data >> 4) & 0xf + nib2 = (data >> 8) & 0xf + nib3 = (data >> 12) & 0xf + return nib0 ^ nib1 ^ nib2 ^ nib3 + + @classmethod + def mult_prime(cls, data: int) -> int: + ret = 0 + for blk_idx in range(4): + data_hw = (data >> (16 * blk_idx)) & 0xffff + start_sr_idx = 0 if blk_idx in (0, 3) else 1 + for nibble_idx in range(4): + sr_idx = (start_sr_idx + 3 - nibble_idx) & 0x3 + sr_const = cls.SHIFT_ROWS_CONSTS[sr_idx] + nibble = cls.nibble_red16(data_hw & sr_const) + ret |= nibble << (16 * blk_idx + 4 * nibble_idx) + return ret + + @classmethod + def shiftrows(cls, data: int, invert: bool) -> int: + shifts = cls.SHIFT_ROWS64_INV if invert else cls.SHIFT_ROWS64 + ret = 0 + for nibble_idx in range(64//4): + src_nibble_idx = shifts[nibble_idx] + src_nibble = (data >> (4 * src_nibble_idx)) & 0xf + ret |= src_nibble << (4 * nibble_idx) + return ret + + @classmethod + def fwd_round(cls, rc: int, key: int, data: int) -> int: + data = cls.sbox(data, 64, cls.SBOX4) + data = cls.mult_prime(data) + data = cls.shiftrows(data, False) + data ^= rc + data ^= key + return data + + @classmethod + def inv_round(cls, rc: int, key: int, data: int) -> int: + data ^= key + data ^= rc + data = cls.shiftrows(data, True) + data = cls.mult_prime(data) + data = cls.sbox(data, 64, cls.SBOX4_INV) + return data + + # Run the PRINCE cipher. + # This uses the new keyschedule proposed by Dinur in "Cryptanalytic + # Time-Memory-Data Tradeoffs for FX-Constructions with Applications to PRINCE + # and PRIDE". + @classmethod + def run(cls, data: int, khi: int, klo: int, num_rounds_half: int) -> int: + khi_rot1 = ((khi & 1) << 63) | (khi >> 1) + khi_prime = khi_rot1 ^ (khi >> 63) + + data ^= khi + data ^= klo + data ^= cls.ROUND_CONSTS[0] + + for hri in range(num_rounds_half): + round_idx = 1 + hri + rc = cls.ROUND_CONSTS[round_idx] + rk = khi if (round_idx & 1) else klo + data = cls.fwd_round(rc, rk, data) + + data = cls.sbox(data, 64, cls.SBOX4) + data = cls.mult_prime(data) + data = cls.sbox(data, 64, cls.SBOX4_INV) + + for hri in range(num_rounds_half): + round_idx = 11 - num_rounds_half + hri + rc = cls.ROUND_CONSTS[round_idx] + rk = klo if (round_idx & 1) else khi + data = cls.inv_round(rc, rk, data) + + data ^= cls.ROUND_CONSTS[11] + data ^= klo + data ^= khi_prime + + return data From fc10bccbe064bc9e14682193b12e4bc93469cda6 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:25:42 +0200 Subject: [PATCH 162/175] [ot] python/qemu: ot.rom.image: add a new module to handle ROM scrambled image files Signed-off-by: Emmanuel Blot --- python/qemu/ot/rom/image.py | 297 ++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 python/qemu/ot/rom/image.py diff --git a/python/qemu/ot/rom/image.py b/python/qemu/ot/rom/image.py new file mode 100644 index 0000000000000..fccb3b81a0171 --- /dev/null +++ b/python/qemu/ot/rom/image.py @@ -0,0 +1,297 @@ +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""OpenTitan ROM image support. + + :author: Emmanuel Blot +""" + +from binascii import hexlify +from io import BytesIO +from logging import getLogger +from typing import BinaryIO, Optional, Union + +from ..util.elf import ElfBlob +from ..util.file import guess_file_type +from ..util.prince import PrinceCipher + +# ruff: noqa: E402 +_CRYPTO_EXC: Optional[Exception] = None +try: + from Crypto.Hash import cSHAKE256 +except ModuleNotFoundError as exc: + _CRYPTO_EXC = exc + +class ROMImage: + """ + """ + + ADDR_SUBST_PERM_ROUNDS = 2 + DATA_SUBST_PERM_ROUNDS = 2 + PRINCE_HALF_ROUNDS = 2 + DATA_BITS = 4 * 8 + ECC_BITS = 7 + WORD_BITS = DATA_BITS + ECC_BITS + WORD_BYTES = (WORD_BITS + 7) // 8 + DIGEST_BYTES = 32 + DIGEST_WORDS = DIGEST_BYTES // 4 + + SBOX4 = [12, 5, 6, 11, 9, 0, 10, 13, 3, 14, 15, 8, 4, 7, 1, 2] + SBOX4_INV = [5, 14, 15, 8, 12, 1, 2, 13, 11, 4, 6, 3, 0, 7, 9, 10] + + def __init__(self, name: Union[str, int, None] = None): + logname = 'romimg' + if isinstance(name, (int, str)): + logname = f'{logname}.{name}' + self._log = getLogger(logname) + self._name = name + self._data = b'' + self._key = 0 + self._nonce = 0 + self._addr_nonce = 0 + self._data_nonce = 0 + self._addr_width = 0 + self._khi = 0 + self._klo = 0 + self._digest = bytes(32) + + def load(self, rfp: BinaryIO, size: Optional[int] = None): + ftype = guess_file_type(rfp) + loader = getattr(self, f'_load_{ftype}', None) + if not loader: + raise ValueError(f'Unsupported ROM file type: {ftype}') + loader(rfp, size) + + @property + def digest(self): + return self._digest + + @property + def key(self): + return self._key.to_bytes(16, 'big') + + @key.setter + def key(self, value: bytes): + if not isinstance(value, bytes): + raise TypeError('Key must be bytes') + self._key = int.from_bytes(value, 'big') + self._khi = self._key >> 64 + self._klo = self._key & 0xFFFFFFFFFFFFFFFF + + @property + def nonce(self): + return self._nonce.to_bytes(8, 'big') + + @nonce.setter + def nonce(self, value: bytes): + if not isinstance(value, bytes): + raise TypeError('Nonce must be bytes') + self._nonce = int.from_bytes(value, 'big') + self._addr_nonce = 0 + self._data_nonce = 0 + + def _load_hex(self, rfp: BinaryIO, size: Optional[int] = None) -> None: + data: list[int] = [] # 64-bit values + for lpos, line in enumerate(rfp.readlines(), start=1): + line = line.strip() + if len(line) != 10: + raise ValueError(f'Unsupported ROM HEX format at line {lpos}') + try: + data.append(int(line, 16)) + except ValueError as exc: + raise ValueError(f'Invalid HEX data at line {lpos}: {exc}') + word_size = lpos + addr_bits = self.ctz(word_size) + data_nonce_width = 64 - addr_bits + self._addr_nonce = self._nonce >> data_nonce_width + self._data_nonce = self._nonce & ((1 << data_nonce_width) - 1) + self._addr_width = addr_bits + self._log.info('data_nonce_width: %d', data_nonce_width) + self._log.info('addr_width: %d', self._addr_width) + self._log.info('addr_nonce: %06x', self._addr_nonce) + self._log.info('data_nonce: %012x', self._data_nonce) + self._log.info('key_hi: %016x', self._khi) + self._log.info('key_lo: %016x', self._klo) + digest = self._unscramble(data) + bndigest = bytes(reversed(digest)) + self._log.info('digest: %s', hexlify(bndigest).decode()) + self._digest = digest + + def _load_bin(self, rfp: BinaryIO, size: Optional[int] = None) -> None: + if not size: + raise ValueError('ROM size not specified') + if _CRYPTO_EXC: + raise ModuleNotFoundError('Crypto module not found') + data = bytearray(rfp.read()) + # digest storage is not included in digest computation + data_len = len(data) + size -= self.DIGEST_BYTES + if data_len > size: + raise ValueError('ROM size is too small') + if data_len < size: + data.extend(bytes(size - data_len)) + shake = cSHAKE256.new(custom=b'ROM_CTRL') + shake.update(data) + digest = shake.read(32) + self._log.info('size: %d bytes', size) + bndigest = bytes(reversed(digest)) + self._log.info('digest: %s', hexlify(bndigest).decode()) + self._digest = digest + self._data = data + + def _load_elf(self, rfp: BinaryIO, size: Optional[int] = None) -> None: + elf = ElfBlob() + elf.load(rfp) + bin_io = BytesIO(elf.blob) + self._load_bin(bin_io, size) + + @classmethod + def ctz(cls, val): + if val == 0: + raise ValueError('CTZ undefined') + pos = 0 + while (val & 1) == 0: + val >>= 1 + pos += 1 + return pos + + @classmethod + def bitswap(cls, in_, mask, shift): + return ((in_ & mask) << shift) | ((in_ & ~mask) >> shift) + + @classmethod + def bitswap64(cls, val): + val = cls.bitswap(val, 0x5555555555555555, 1) + val = cls.bitswap(val, 0x3333333333333333, 2) + val = cls.bitswap(val, 0x0f0f0f0f0f0f0f0f, 4) + val = cls.bitswap(val, 0x00ff00ff00ff00ff, 8) + val = cls.bitswap(val, 0x0000ffff0000ffff, 16) + val = (val << 32) | (val >> 32) + + return val + + @classmethod + def sbox(cls, in_, width, sbox): + assert width < 64 + + full_mask = (1 << width) - 1 + width &= ~3 + sbox_mask = (1 << width) - 1 + + out = in_ & (full_mask & ~sbox_mask) + for ix in range(0, width, 4): + nibble = (in_ >> ix) & 0xf + out |= sbox[nibble] << ix + + return out + + @classmethod + def flip(cls, in_, width): + out = cls.bitswap64(in_) + out >>= 64 - width + + return out + + @classmethod + def perm(cls, in_, width, invert): + assert width < 64 + + full_mask = (1 << width) - 1 + width &= ~1 + bfly_mask = (1 << width) - 1 + + out = in_ & (full_mask & ~bfly_mask) + + width >>= 1 + if not invert: + for ix in range(width): + bit = (in_ >> (ix << 1)) & 1 + out |= bit << ix + bit = (in_ >> ((ix << 1) + 1)) & 1 + out |= bit << (width + ix) + else: + for ix in range(width): + bit = (in_ >> ix) & 1 + out |= bit << (ix << 1) + bit = (in_ >> (ix + width)) & 1 + out |= bit << ((ix << 1) + 1) + + return out + + @classmethod + def subst_perm_enc(cls, in_, key, width, num_round): + state = in_ + while num_round: + num_round -= 1 + state ^= key + state = cls.sbox(state, width, cls.SBOX4) + state = cls.flip(state, width) + state = cls.perm(state, width, False) + state ^= key + + return state + + @classmethod + def subst_perm_dec(cls, val, key, width, num_round): + state = val + while num_round: + num_round -= 1 + state ^= key + state = cls.perm(state, width, True) + state = cls.flip(state, width) + state = cls.sbox(state, width, cls.SBOX4_INV) + state ^= key + + return state + + def addr_sp_enc(self, addr): + return self.subst_perm_enc(addr, self._addr_nonce, self._addr_width, + self.ADDR_SUBST_PERM_ROUNDS) + + @classmethod + def data_sp_dec(cls, val): + return cls.subst_perm_dec(val, 0, cls.WORD_BITS, + cls.DATA_SUBST_PERM_ROUNDS) + + def _get_keystream(self, addr: int): + scramble = (self._data_nonce << self._addr_width) | addr + stream = PrinceCipher.run(scramble, self._khi, self._klo, + self.PRINCE_HALF_ROUNDS) + return stream & ((1 << self.WORD_BITS) - 1) + + def _unscramble_word(self, addr: int, value: int): + keystream = self._get_keystream(addr) + spd = self.data_sp_dec(value) + return keystream ^ spd + + + def _unscramble(self, src: list[int]) -> bytes: + # do not attempt to detect or correct errors for now + size = len(src) + scr_word_size = (size - self.DIGEST_BYTES) // 4 + log_addr = 0 + dst: list[int] = [0] * size + while log_addr < scr_word_size: + phy_addr = self.addr_sp_enc(log_addr) + assert(phy_addr < size) + + srcdata = src[phy_addr] + clrdata = self._unscramble_word(log_addr, srcdata) + dst[log_addr] = clrdata & 0xffffffff + log_addr += 1 + wix = 0 + digest_parts: list[int] = [] + while wix < self.DIGEST_WORDS: + phy_addr = self.addr_sp_enc(log_addr) + assert(phy_addr < size) + digest_parts.append(src[phy_addr] & 0xffffffff) + wix += 1 + log_addr += 1 + digest = b''.join((dp.to_bytes(4, 'little') for dp in digest_parts)) + for addr in range(0x20, 0x30): + self._log.debug('@ %06x: %08x', addr, dst[addr]) + data = bytearray() + for val in dst: + data.extend(val.to_bytes(4, 'little')) + self._data = bytes(data) + return digest From 156f7c612d6b904af0ab38eb92dc602cac6cf883 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:26:50 +0200 Subject: [PATCH 163/175] [ot] python/qemu: ot.util.misc: add a custom ArgError exception It should be used to report an invalid parsed argument, to delegate ArgumentParser validation to modules. Signed-off-by: Emmanuel Blot --- python/qemu/ot/util/misc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/qemu/ot/util/misc.py b/python/qemu/ot/util/misc.py index 3e2f5511a69ea..e45f4ef01fc41 100644 --- a/python/qemu/ot/util/misc.py +++ b/python/qemu/ot/util/misc.py @@ -74,6 +74,10 @@ def xparse(value: Union[None, int, str]) -> Optional['HexInt']: return HexInt(int(value.strip(), value.startswith('0x') and 16 or 10)) +class ArgError(Exception): + """Argument error.""" + + class EasyDict(dict): """Dictionary whose members can be accessed as instance members """ From 135e989d0a89144e88039ee6bfabe331e938641f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:59:29 +0200 Subject: [PATCH 164/175] [ot] docs/opentitan: pymod.md: add missing modules Signed-off-by: Emmanuel Blot --- docs/opentitan/otptool.md | 3 +-- docs/opentitan/pymod.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index 456616537929d..f276c7f261be5 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -115,8 +115,7 @@ Fuse RAW images only use the v1 type. * `-E` use ECC data to fix recoverable errors -* `-e` specify how many bits are used in the VMEM file to store ECC information. Note that ECC - information is not stored in the QEMU RAW file for now. +* `-e` specify how many bits are used in the VMEM file to store ECC information. * `-F` may be used to rebuild the ECC values for all slots that have been modified using any modification operation, and any detected error. diff --git a/docs/opentitan/pymod.md b/docs/opentitan/pymod.md index 0b43de8ff6395..fca40d8464d54 100644 --- a/docs/opentitan/pymod.md +++ b/docs/opentitan/pymod.md @@ -8,12 +8,20 @@ using a TCP socket). * `python/qemu/ot`: OpenTitan tools * `dtm`: Debug Transport Module support, * `dm`: RISC-V Debug Module support, + * `eflash`: Embedded Flash support, + * `gpio`: GPIO support, + * `km`: Key Manager support, * `lc_ctrl`: [Life Cycle controller](lc_ctrl_dmi.md) over JTAG/DMI support, * `mailbox`: support for accessing the responder and the requester sides of the DOE mailbox. Also - support the [JTAG mailbox](jtagmbx.md) for accessing the mailbox from a JTAG/DMI link. + support the [JTAG mailbox](jtagmbx.md) for accessing the mailbox from a JTAG/DMI link, * `otp`: support for parsing and verifing OTP VMEM images, as well as generating and decoding QEMU - RAW image files. - * `util`: miscellaneous utililies such as ELF format tools and logging utilities + RAW image files, + * `pyot`: implements the Python OpenTitan test orchestrator tool, + * `rom`: support for parsing and verifying ROM images, + * `socdbg`: support for communication with the SoC debug module of DMI, + * `spi`: support SPI device communication, _i.e._ acts as a SPI master connected to QEMU SPI + device port, + * `util`: miscellaneous utililies such as ELF format tools and logging utilities, * `devproxy`: implementation of the communication channel with the QEMU devproxy device. Please check the [Python tools](tools.md) documentation for details and scripts that rely From 7194d502ac425aedb306dbcf1154cc373896ea1b Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 18 Jul 2025 11:57:48 +0200 Subject: [PATCH 165/175] [ot] python/qemu: ot.pyot.execute: fix test listing for virtual tests Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index c3d9965b1dc16..f96c1c277a899 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -110,8 +110,10 @@ def enumerate_tests(self) -> Iterator[str]: """ self._argdict = dict(self._args.__dict__) for tst in sorted(self._build_test_list()): - ttype = guess_file_type(tst) - yield f'{basename(tst)} ({ttype})' + tpath = self._virtual_tests.get(tst) + ttype = guess_file_type(tpath or tst) + rpath = f' [{basename(tpath)}]' if tpath else '' + yield f'{basename(tst)} ({ttype}){rpath}' def run(self, debug: bool, allow_no_test: bool) -> int: """Execute all requested tests. From 04bcc34b80413b94de12ed7093bc0acd1f2b58ac Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 18 Jul 2025 14:24:50 +0200 Subject: [PATCH 166/175] [ot] scripts/opentitan: pyot.py: add an option to copy log messages to a file Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 32 ++++++++++++++++---------------- python/qemu/ot/pyot/executer.py | 4 ++-- python/qemu/ot/util/log.py | 11 +++++++++++ scripts/opentitan/pyot.py | 19 +++++++++++-------- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index c75797356c549..04b34c7d886fe 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -5,13 +5,13 @@ ## Usage ````text -usage: pyot.py [-h] [-A] [-D DELAY] [-i ICOUNT] [-L LOG_FILE] [-M VARIANT] +usage: pyot.py [-h] [-A] [-D DELAY] [-i ICOUNT] [-L FILE] [-M VARIANT] [-N LOG] [-m MACHINE] [-Q OPTS] [-q QEMU] [-P VCP] [-p DEVICE] - [-t TRACE] [-S FIRST_SOC] [-s] [-U] [-b file] [-c HJSON] - [-e BUS] [-f RAW] [-g CFGFILE] [-H] [-K] [-l file] [-O RAW] - [-o VMEM] [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] - [-k SECONDS] [-z] [-R] [-T FACTOR] [-Z] [-d] [--quiet] [-G] - [-V] [-v] [--log-udp UDP_PORT] [--debug LOGGER] [--info LOGGER] + [-t TRACE] [-S SOC] [-s] [-U] [-b file] [-c HJSON] [-e BUS] + [-f RAW] [-g CFGFILE] [-H] [-K] [-l file] [-O RAW] [-o VMEM] + [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] [-k SECONDS] [-z] + [-R] [-T FACTOR] [-Z] [-d] [-G] [-V] [-v] [--log-file FILE] + [--log-udp UDP_PORT] [--quiet] [--debug LOGGER] [--info LOGGER] [--warn LOGGER] OpenTitan QEMU unit test sequencer. @@ -20,27 +20,25 @@ options: -h, --help show this help message and exit Virtual machine: - -A, --asan Redirect address sanitizer error log stream + -A, --asan redirect address sanitizer error log stream -D, --start-delay DELAY QEMU start up delay before initial comm -i, --icount ICOUNT virtual instruction counter with 2^ICOUNT clock ticks per inst. or 'auto' - -L, --log_file LOG_FILE - log file for trace and log messages + -L, --qemu-log FILE log file for trace and log messages -M, --variant VARIANT machine variant (machine specific) -N, --log LOG log message types -m, --machine MACHINE virtual machine (default to ot-earlgrey) - -Q, --opts OPTS QEMU verbatim option (can be repeated) + -Q, --opts OPTS QEMU verbatim option (may be repeated) -q, --qemu QEMU path to qemu application (default: build/qemu-system- - riscv32) + riscv32-unsigned) -P, --vcp VCP serial port devices (default: use serial0) -p, --device DEVICE serial port device name / template name (default to localhost:8000) -t, --trace TRACE trace event definition file - -S, --first-soc FIRST_SOC - Identifier of the first SoC, if any + -S, --first-soc SOC Identifier of the first SoC, if any -s, --singlestep enable "single stepping" QEMU execution mode -U, --muxserial enable multiple virtual UARTs to be muxed into same host output channel @@ -60,7 +58,7 @@ Files: -l, --loader file ROM trampoline to execute, if any -O, --otp-raw RAW OTP image file -o, --otp VMEM OTP VMEM file - -r, --rom ELF ROM file (can be repeated, in load order) + -r, --rom ELF ROM file (may be repeated, in load order) -w, --result CSV path to output result file -x, --exec file application to load -X, --rom-exec load application as ROM image (default: as kernel) @@ -78,11 +76,12 @@ Execution: Extras: -d enable debug mode - --quiet quiet logging: only be verbose on errors -G, --log-time show local time in log messages -V, --vcp-verbose increase verbosity of QEMU virtual comm ports -v, --verbose increase verbosity - --log-udp UDP_PORT Change UDP port for log messages, use 0 to disable + --log-file FILE copy log messages to a file + --log-udp UDP_PORT change UDP port for log messages, use 0 to disable + --quiet quiet logging: only be verbose on errors --debug LOGGER assign debug level to logger(s) --info LOGGER assign info level to logger(s) --warn LOGGER assign warning level to logger(s) @@ -186,6 +185,7 @@ This tool may be used in two ways, which can be combined: * `-V` / `--vcp-verbose` can be repeated to increase verbosity of the QEMU virtual comm ports * `-v` / `--verbose` can be repeated to increase verbosity of the script, mostly for debug purpose. * `--quiet` only emit verbose log traces if an error is detected +* `--log-file` copy log messages to the specified file (previous content if any is overwritten) * `--log-udp` change the port of the UDP log service on specified UDP port. Use `0` to disable the service. * `--debug` enable the debug level for the selected logger, may be repeated diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index f96c1c277a899..4b681200c610d 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -469,8 +469,8 @@ def _build_qemu_command(self, args: Namespace, qemu_args.extend(('-drive', f'if=mtd,id=eflash,' f'bus={args.embedded_flash},' f'file={flash_file},format=raw')) - if args.log_file: - qemu_args.extend(('-D', self.abspath(args.log_file))) + if args.qemu_log: + qemu_args.extend(('-D', self.abspath(args.qemu_log))) if args.trace: # use a FileType to let argparser validate presence and type args.trace.close() diff --git a/python/qemu/ot/util/log.py b/python/qemu/ot/util/log.py index f68abf4ec743b..d2d26937fb0df 100644 --- a/python/qemu/ot/util/log.py +++ b/python/qemu/ot/util/log.py @@ -32,6 +32,9 @@ def getLevelNamesMapping() -> dict[str, int]: if k not in ('NOTSET', 'WARN')} +FileHandler = logging.FileHandler + + class Color(NamedTuple): """Simple color wrapper.""" color: str @@ -301,6 +304,7 @@ def configure_loggers(level: int, *lognames: list[Union[str, int, Color]], lnames = [lnames] loglevels[lvl] = tuple(lnames) quiet = kwargs.pop('quiet', False) + filelog = kwargs.pop('filelog', None) udplog = kwargs.pop('udplog', None) formatter = ColorLogFormatter(**kwargs) if udplog is not None: @@ -311,6 +315,11 @@ def configure_loggers(level: int, *lognames: list[Union[str, int, Color]], shandler = DatagramHandler('127.0.0.1', udpport) else: shandler = logging.StreamHandler(stderr) + if filelog: + logfh = FileHandler(filelog, 'w') + logfh.setFormatter(formatter) + else: + logfh = None shandler.setFormatter(formatter) if quiet: logh = MemoryHandler(100000, target=shandler, flushOnClose=False) @@ -342,6 +351,8 @@ def configure_loggers(level: int, *lognames: list[Union[str, int, Color]], for _, log in logdefs: if not log.hasHandlers(): log.addHandler(logh) + if logfh: + log.addHandler(logfh) return loggers diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index 789a5d1d75738..fc7f938c23908 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -67,13 +67,13 @@ def main(): qvm = argparser.add_argument_group(title='Virtual machine') rel_qemu_path = relpath(qemu_path) if qemu_path else '?' qvm.add_argument('-A', '--asan', action='store_const', const=True, - help='Redirect address sanitizer error log stream') + help='redirect address sanitizer error log stream') qvm.add_argument('-D', '--start-delay', type=float, metavar='DELAY', help='QEMU start up delay before initial comm') qvm.add_argument('-i', '--icount', help='virtual instruction counter with 2^ICOUNT clock ' 'ticks per inst. or \'auto\'') - qvm.add_argument('-L', '--log_file', + qvm.add_argument('-L', '--qemu-log', metavar='FILE', help='log file for trace and log messages') qvm.add_argument('-M', '--variant', help='machine variant (machine specific)') @@ -82,7 +82,7 @@ def main(): qvm.add_argument('-m', '--machine', help=f'virtual machine (default to {DEFAULT_MACHINE})') qvm.add_argument('-Q', '--opts', action='append', - help='QEMU verbatim option (can be repeated)') + help='QEMU verbatim option (may be repeated)') qvm.add_argument('-q', '--qemu', help=f'path to qemu application ' f'(default: {rel_qemu_path})') @@ -93,7 +93,7 @@ def main(): f'(default to {DEFAULT_DEVICE})') qvm.add_argument('-t', '--trace', type=FileType('rt', encoding='utf-8'), help='trace event definition file') - qvm.add_argument('-S', '--first-soc', default=None, + qvm.add_argument('-S', '--first-soc', metavar='SOC', default=None, help='Identifier of the first SoC, if any') qvm.add_argument('-s', '--singlestep', action='store_const', const=True, @@ -127,7 +127,7 @@ def main(): help='OTP image file') files.add_argument('-o', '--otp', metavar='VMEM', help='OTP VMEM file') files.add_argument('-r', '--rom', metavar='ELF', action='append', - help='ROM file (can be repeated, in load order)') + help='ROM file (may be repeated, in load order)') files.add_argument('-w', '--result', metavar='CSV', help='path to output result file') files.add_argument('-x', '--exec', metavar='file', @@ -154,17 +154,19 @@ def main(): extra = argparser.add_argument_group(title='Extras') extra.add_argument('-d', dest='dbg', action='store_true', help='enable debug mode') - extra.add_argument('--quiet', action='store_true', - help='quiet logging: only be verbose on errors') extra.add_argument('-G', '--log-time', action='store_true', help='show local time in log messages') extra.add_argument('-V', '--vcp-verbose', action='count', help='increase verbosity of QEMU virtual comm ports') extra.add_argument('-v', '--verbose', action='count', help='increase verbosity') + extra.add_argument('--log-file', metavar='FILE', + help='copy log messages to a file') extra.add_argument('--log-udp', type=int, metavar='UDP_PORT', - help='Change UDP port for log messages, ' + help='change UDP port for log messages, ' 'use 0 to disable') + extra.add_argument('--quiet', action='store_true', + help='quiet logging: only be verbose on errors') extra.add_argument('--debug', action='append', metavar='LOGGER', help='assign debug level to logger(s)') extra.add_argument('--info', action='append', metavar='LOGGER', @@ -199,6 +201,7 @@ def main(): -1, 'flashgen', 'elf', 'otp', 'pyot.file', 1, args.vcp_verbose or 0, 'pyot.vcp', name_width=30, + filelog=args.log_file, ms=args.log_time, quiet=args.quiet, debug=args.debug, info=args.info, warning=args.warn)[0] From 2dc1e0bac3ee89a71cc2a27fbcf7c411a537596f Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 18 Jul 2025 14:24:55 +0200 Subject: [PATCH 167/175] [ot] scripts/opentitan: pyot.py: `-t` may now be used to specified trace event name Signed-off-by: Emmanuel Blot --- docs/opentitan/pyot.md | 22 ++++++++++++---------- python/qemu/ot/pyot/executer.py | 11 ++++++----- scripts/opentitan/pyot.py | 5 +++-- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/opentitan/pyot.md b/docs/opentitan/pyot.md index 04b34c7d886fe..80362a0b5e4a5 100644 --- a/docs/opentitan/pyot.md +++ b/docs/opentitan/pyot.md @@ -7,12 +7,12 @@ ````text usage: pyot.py [-h] [-A] [-D DELAY] [-i ICOUNT] [-L FILE] [-M VARIANT] [-N LOG] [-m MACHINE] [-Q OPTS] [-q QEMU] [-P VCP] [-p DEVICE] - [-t TRACE] [-S SOC] [-s] [-U] [-b file] [-c HJSON] [-e BUS] - [-f RAW] [-g CFGFILE] [-H] [-K] [-l file] [-O RAW] [-o VMEM] - [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] [-k SECONDS] [-z] - [-R] [-T FACTOR] [-Z] [-d] [-G] [-V] [-v] [--log-file FILE] - [--log-udp UDP_PORT] [--quiet] [--debug LOGGER] [--info LOGGER] - [--warn LOGGER] + [-S SOC] [-s] [-t TRACE|FILE] [-U] [-b file] [-c HJSON] + [-e BUS] [-f RAW] [-g CFGFILE] [-H] [-K] [-l file] [-O RAW] + [-o VMEM] [-r ELF] [-w CSV] [-x file] [-X] [-F TEST] + [-k SECONDS] [-z] [-R] [-T FACTOR] [-Z] [-d] [-G] [-V] [-v] + [--log-file FILE] [--log-udp UDP_PORT] [--quiet] + [--debug LOGGER] [--info LOGGER] [--warn LOGGER] OpenTitan QEMU unit test sequencer. @@ -37,9 +37,10 @@ Virtual machine: -P, --vcp VCP serial port devices (default: use serial0) -p, --device DEVICE serial port device name / template name (default to localhost:8000) - -t, --trace TRACE trace event definition file -S, --first-soc SOC Identifier of the first SoC, if any -s, --singlestep enable "single stepping" QEMU execution mode + -t, --trace TRACE|FILE + enable trace (may be repeated) -U, --muxserial enable multiple virtual UARTs to be muxed into same host output channel @@ -123,8 +124,8 @@ This tool may be used in two ways, which can be combined: * `-S` / `--first-soc` define the name of the first SoC to boot, if any. This flag is only used to prefix property names * `-s` / `--singlestep` enable QEMU "single stepping" mode. -* `-t` / `--trace` trace event definition file. To obtain a list of available traces, invoke QEMU - with `-trace help` option +* `-t` / `--trace` add trace message. May be repeaded. Either specified an event definition file, + or a trace event name. To obtain a list of available traces, invoke with `-trace help`. * `-T` / `--timeout-factor` apply a multiplier factor to all timeouts. Specified as a real number, it can be greater to increase timeouts or lower than 1 to decrease timeouts. * `-U` / `--muxserial` enable muxing QEMU VCP. This option is required when several virtual UARTs @@ -143,7 +144,8 @@ This tool may be used in two ways, which can be combined: generated with the [`flashgen.py`](flashgen.md) tool. Alternatively, see the `-x` option. * `-g` / `--otcfg` specify a configuration file with OpenTitan configuration options, such as clock definitions, cryptographic constants (seeds, keys, nonces, ...). May be repeated. -* `-H` / `--no-flash-header` executable files should be considered as raw files with no OpenTitan flash headers. +* `-H` / `--no-flash-header` executable files should be considered as raw files with no OpenTitan + flash headers. * `-K` / `--keep-tmp` do not automatically remove temporary files and directories on exit. The user is in charge of discarding any generated files and directories after execution. The paths to the generated items are emitted as warning messages. diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index 4b681200c610d..2880939dfb343 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -471,11 +471,12 @@ def _build_qemu_command(self, args: Namespace, f'file={flash_file},format=raw')) if args.qemu_log: qemu_args.extend(('-D', self.abspath(args.qemu_log))) - if args.trace: - # use a FileType to let argparser validate presence and type - args.trace.close() - qemu_args.extend(('-trace', - f'events={self.abspath(args.trace.name)}')) + for trace in args.trace or []: + qemu_args.append('-trace') + if isfile(trace): + qemu_args.append(f'events={self.abspath(trace)}') + else: + qemu_args.append(trace) qemu_args.extend(self._build_qemu_log_sources(args)) if args.singlestep: qemu_args.extend(('-accel', 'tcg,one-insn-per-tb=on')) diff --git a/scripts/opentitan/pyot.py b/scripts/opentitan/pyot.py index fc7f938c23908..e4a96fc2a3581 100755 --- a/scripts/opentitan/pyot.py +++ b/scripts/opentitan/pyot.py @@ -91,13 +91,14 @@ def main(): qvm.add_argument('-p', '--device', help=f'serial port device name / template name ' f'(default to {DEFAULT_DEVICE})') - qvm.add_argument('-t', '--trace', type=FileType('rt', encoding='utf-8'), - help='trace event definition file') qvm.add_argument('-S', '--first-soc', metavar='SOC', default=None, help='Identifier of the first SoC, if any') qvm.add_argument('-s', '--singlestep', action='store_const', const=True, help='enable "single stepping" QEMU execution mode') + qvm.add_argument('-t', '--trace', action='append', metavar='TRACE|FILE', + default=[], + help='enable trace (may be repeated)') qvm.add_argument('-U', '--muxserial', action='store_const', const=True, help='enable multiple virtual UARTs to be muxed into ' From 72c09d29beb6ef027608a39039338bdd82e5b950 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 21 Jul 2025 18:04:45 +0200 Subject: [PATCH 168/175] [ot] python/qemu: ot.pyot.executer: `log_file` option may be used for a single test When define as a test option, the VCP port are also logged into an additional file. It is possible to use a temporary file to perform post-analysis of the UART outputs Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/executer.py | 47 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/python/qemu/ot/pyot/executer.py b/python/qemu/ot/pyot/executer.py index 2880939dfb343..04135d1e989e1 100644 --- a/python/qemu/ot/pyot/executer.py +++ b/python/qemu/ot/pyot/executer.py @@ -11,10 +11,10 @@ from csv import writer as csv_writer from fnmatch import fnmatchcase from glob import glob -from logging import INFO as LOG_INFO, getLogger +from logging import DEBUG as LOG_DEBUG, INFO as LOG_INFO, getLogger, FileHandler from os import curdir, environ, getcwd, sep -from os.path import (basename, dirname, isabs, isfile, join as joinpath, - normpath) +from os.path import (abspath, basename, dirname, isabs, isfile, + join as joinpath, normpath) from traceback import format_exc from typing import Any, Iterator, Optional @@ -158,8 +158,10 @@ def run(self, debug: bool, allow_no_test: bool) -> int: targs = None temp_files = {} for tpos, test in enumerate(tests, start=1): + test_name = None self._log.info('[TEST %s] (%d/%d)', self.get_test_radix(test), tpos, tcount) + vcplogfile = None try: self._qfm.define_transient({ 'UTPATH': test, @@ -169,6 +171,7 @@ def run(self, debug: bool, allow_no_test: bool) -> int: test_name = self.get_test_radix(test) exec_info = self._build_qemu_test_command(test) exec_info.test_name = test_name + vcplogfile = self._log_vcp_streams(exec_info) exec_info.context.execute('pre') tret, xtime, err = qot.run(exec_info) cret = exec_info.context.finalize() @@ -195,11 +198,12 @@ def run(self, debug: bool, allow_no_test: bool) -> int: xtime = 0.0 err = str(exc) finally: + self._discard_vcp_log(vcplogfile) self._qfm.cleanup_transient() - flush_memory_loggers(['pyot', 'pyot.vcp'], LOG_INFO) + if not self._qfm.keep_temporary: + self._qfm.delete_default_dir(test_name) flush_memory_loggers(['pyot', 'pyot.vcp', 'pyot.ctx', - 'pyot.file'], - LOG_INFO) + 'pyot.file'], LOG_INFO) results[tret] += 1 sret = self.RESULT_MAP.get(tret, tret) try: @@ -496,6 +500,7 @@ def _build_qemu_command(self, args: Namespace, raise ValueError(f"{getattr(args, 'exec', '?')}: 'trigger' and " f"'validate' are mutually exclusive") asan = getattr(args, 'asan', False) + logfile = getattr(args, 'log_file', None) vcp_args, vcp_map = self._build_qemu_vcp_args(args) qemu_args.extend(vcp_args) qemu_args.extend(args.global_opts or []) @@ -503,7 +508,8 @@ def _build_qemu_command(self, args: Namespace, qemu_args.extend((str(o) for o in opts)) return EasyDict(command=qemu_args, vcp_map=vcp_map, tmpfiles=temp_files, start_delay=start_delay, - trigger=trigger, validate=validate, asan=asan) + trigger=trigger, validate=validate, asan=asan, + logfile=logfile) def _build_qemu_test_command(self, filename: str) -> EasyDict[str, Any]: test_name = self.get_test_radix(filename) @@ -715,3 +721,30 @@ def _build_test_context(self, test_name: str) -> QEMUContext: test_env = {k: self._qfm.interpolate(v) for k, v in env.items()} return QEMUContext(test_name, self._qfm, self._qemu_cmd, dict(context), test_env) + + def _log_vcp_streams(self, exec_info: EasyDict[str, Any]) -> str: + logfile = exec_info.get('logfile', None) + if not logfile: + return None + assert exec_info.test_name + logfile = self._qfm.interpolate_dirs(logfile, exec_info.test_name) + vcplog = getLogger('pyot.vcp') + logfh = FileHandler(logfile, 'w') + # log everything + logfh.setLevel(LOG_DEBUG) + # force the logger to emit all messages as well + # (side effect on all handlers) + vcplog.setLevel(LOG_DEBUG) + vcplog.handlers.append(logfh) + return logfile + + def _discard_vcp_log(self, vcplogfile: Optional[str]) -> None: + if not vcplogfile: + return + vcplogfile = abspath(vcplogfile) + vcplog = getLogger('pyot.vcp') + for handler in vcplog.handlers: + if isinstance(handler, FileHandler): + if handler.baseFilename == vcplogfile: + handler.close() + vcplog.removeHandler(handler) From 39a2c066e31016f59ed5b71a482f71bf65defbb9 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Mon, 21 Jul 2025 18:05:45 +0200 Subject: [PATCH 169/175] [ot] python/qemu: ot.pyot.context: classify context log messages Signed-off-by: Emmanuel Blot --- python/qemu/ot/pyot/context.py | 28 +++++++++++++++++----------- python/qemu/ot/pyot/wrapper.py | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/python/qemu/ot/pyot/context.py b/python/qemu/ot/pyot/context.py index 2a7d87a217eb2..607afed897a86 100644 --- a/python/qemu/ot/pyot/context.py +++ b/python/qemu/ot/pyot/context.py @@ -6,7 +6,7 @@ :author: Emmanuel Blot """ -from logging import getLogger +from logging import getLogger, DEBUG, ERROR, INFO from os import environ, pardir, sep from os.path import basename, dirname, normpath, relpath from subprocess import Popen, PIPE, TimeoutExpired @@ -16,6 +16,7 @@ import re from .filemgr import QEMUFileManager +from .util import LogMessageClassifier from .worker import QEMUContextWorker @@ -42,6 +43,12 @@ def __init__(self, test_name: str, qfm: QEMUFileManager, self._env = env or {} self._workers: list[Popen] = [] self._first_error: str = '' + self._classifier = LogMessageClassifier() + + @property + def test_name(self) -> str: + """Provide the test name.""" + return self._test_name def execute(self, ctx_name: str, code: int = 0, sync: Optional[Event] = None) -> None: @@ -119,25 +126,24 @@ def execute(self, ctx_name: str, code: int = 0, proc.kill() outs, errs = proc.communicate() ret = proc.returncode - if not self._first_error: - self._first_error = errs.split('\n', 1)[0] - for sfp, logger in zip( - (outs, errs), - (self._clog.debug, - self._clog.error if ret else self._clog.info)): + for sfp, deflevel in zip((outs, errs), + (DEBUG, ERROR if ret else INFO)): for line in sfp.split('\n'): line = line.strip() if line: - logger(line) + loglevel = self._classifier.classify(line, + deflevel) + self._clog.log(loglevel, line) + if loglevel >= ERROR and not self._first_error: + self._first_error = line + if not self._first_error: + self._first_error = errs.split('\n', 1)[0] if ret: self._clog.error("Fail to execute '%s' command for " "'%s'", cmd, self._test_name) errmsg = self._first_error or \ f'Cannot execute [{ctx_name}] command' raise OSError(ret, errmsg) - if ctx_name == 'post': - if not self._qfm.keep_temporary: - self._qfm.delete_default_dir(self._test_name) def check_error(self) -> int: """Check if any background worker exited in error. diff --git a/python/qemu/ot/pyot/wrapper.py b/python/qemu/ot/pyot/wrapper.py index 0f483309073df..8e6f9b076df2d 100644 --- a/python/qemu/ot/pyot/wrapper.py +++ b/python/qemu/ot/pyot/wrapper.py @@ -24,7 +24,7 @@ from ot.util.log import ColorLogFormatter from ot.util.misc import EasyDict -from ot.pyot.util import ExecTime, LogMessageClassifier +from .util import ExecTime, LogMessageClassifier class QEMUWrapper: From 83eaeafc622970cf05c9dd181baab3551452abc1 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 17 Jul 2025 16:28:06 +0200 Subject: [PATCH 170/175] [ot] scripts/opentitan: keymgr-dpe.py: add a new script for KeyManager DPE testing Signed-off-by: Emmanuel Blot --- docs/opentitan/keymgr-dpe.md | 212 +++++++++++++++++ docs/opentitan/tools.md | 2 + python/qemu/ot/km/__init__.py | 6 + python/qemu/ot/km/dpe.py | 405 ++++++++++++++++++++++++++++++++ python/qemu/ot/km/engine.py | 360 ++++++++++++++++++++++++++++ python/qemu/ot/rom/image.py | 9 +- scripts/opentitan/keymgr-dpe.py | 140 +++++++++++ 7 files changed, 1132 insertions(+), 2 deletions(-) create mode 100644 docs/opentitan/keymgr-dpe.md create mode 100644 python/qemu/ot/km/__init__.py create mode 100644 python/qemu/ot/km/dpe.py create mode 100644 python/qemu/ot/km/engine.py create mode 100755 scripts/opentitan/keymgr-dpe.py diff --git a/docs/opentitan/keymgr-dpe.md b/docs/opentitan/keymgr-dpe.md new file mode 100644 index 0000000000000..9fc05d178dca5 --- /dev/null +++ b/docs/opentitan/keymgr-dpe.md @@ -0,0 +1,212 @@ +# `keymgr-dpe.py` + +`keymgr-dpe.py` is a helper tool that can be used to generate or verify Key Manager DPE keys, using +the same parameters as the QEMU machine and the real HW, for debugging purposes. + +## Usage + +````text +usage: keymgr-dpe.py [-h] -c CFG -j HJSON [-l SV] [-m VMEM] [-R RAW] [-r ROM] + [-e BITS] [-z SIZE] [-v] [-d] + {generate,execute,verify} ... + +QEMU OT tool to generate Key Manager DPE keys. + +positional arguments: + {generate,execute,verify} + Execution mode + generate generate a key + execute execute sqeuence + verify verify execution log + +options: + -h, --help show this help message and exit + +Files: + -c, --config CFG input QEMU OT config file + -j, --otp-map HJSON input OTP controller memory map file + -l, --lifecycle SV input lifecycle system verilog file + -m, --vmem VMEM input VMEM file + -R, --raw RAW input QEMU OTP raw image file + -r, --rom ROM input ROM image file, may be repeated + +Parameters: + -e, --ecc BITS ECC bit count (default: 6) + -z, --rom-size SIZE ROM image size in bytes, may be repeated + +Extras: + -v, --verbose increase verbosity + -d, --debug enable debug mode +```` + +### Arguments + +* `-c` QEMU OT config file, which can be generated with the [`cfggen.py`](cfggen.md) script. + +* `-d` only useful to debug the script, reports any Python traceback to the standard error stream. + +* `-e` specify how many bits are used in the HEX and VMEM files to store ECC information. + +* `-j` specify the path to the HJSON OTP controller map file, usually stored in OT + `hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson`, required to decode any OTP file. + +* `-l` specify the life cycle system verilog file that defines the encoding of the life cycle + states, required to decode the Life Cycle state, which is stored in the OTP file. + +* `-m` specify the input VMEM file that contains the OTP fuse content, mutually exclusive with `-R` + +* `-r` specify the path to a ROM file. This option should be repeated for each ROM file. The ROM + file may either be a ELF or a binary file, in which case the same count of `-z` ROM size option, + specified in the same order as the ROM file, is required. When such a ROM file format is used, the + ROM digest is computed from the ROM file content. The ROM file can also be specified a `HEX` + scrambled file, in which case the digest is read from the file itself. `VMEM` format is not yet + supported. + +* `-R` specify the path to the QEMU OTP RAW image file, which can be generated with the + [`otptool.py`](otptool.md), mutually exclusive with option `-m`. + +* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose. + +* `-z` specify the size of each ROM file in bytes. It should be repeated for each specified ROM + file, except if all ROM files are specified as HEX formatted files. It can either be specified as + an integer or an integer with an SI-suffix (Ki, Mi), such as `-z 64Ki`. + +Depending on the execution mode, the following options are available: + +## Generate options + +This mode can be used to generate a single output key, which can be stored into an output file. + +``` +usage: keymgr-dpe.py generate [-h] [-b HEXSTR] [-g {HW,SW}] -k HEXSTR [-o OUTPUT] [-R NAME] -s HEXSTR -t {AES,KMAC,OTBN,NONE} + +options: + -h, --help show this help message and exit + -b, --swbindings HEXSTR + SW bindings, may be repeated + -g, --gen-out {HW,SW} + generation output (default: auto) + -k, --key-version HEXSTR + Key version + -o, --output OUTPUT output file with generated key + -R, --rust-const NAME + Rust constant name for the generated key + -s, --salt HEXSTR Salt + -t, --target {AES,KMAC,OTBN,NONE} + destination device +``` + +### Arguments + +* `-b` specify the software binding for an _advance_ stage of the key manager. It should be repeated + for each new stage to execute. Note the first stage does not require a software binding value and + is always executed. The expected format is a hexa-decimal encoded string (without 0x prefix). It + is automatically padded with zero bytes up to the maximum software binding size supported by the + HW. + +* `-g` specify the kind of generation to perform. If not specified, it is inferred from the `-t` + target option. + +* `-k` the version of the key to generate + +* `-o` specify the output file path for the generated key + +* `-R` when specified, the Key Manager output slot is encoded as a Rust constant, whose + name is specified with this option. Default is to print the output slot in plain text. + +* `-s` specify the salt for the _generate_ stage of the key manager. The expected format is a hexa- + decimal encoded string (without 0x prefix). It is automatically padded with zero bytes up to the + maximum salt size supported by the HW. + +* `-t` specify the destination for the _generate_ stage. + +### Examples + +````sh +keymgr-dpe.py -vv -c ot.cfg -j otp_ctrl_mmap.hjson -m img_otp.vmem -l lc_ctrl_state_pkg.sv \ + -r base_rom.39.scr.hex -r second_rom.39.scr.hex -b 0 -b 0 -t AES -k 0 -s 0 +```` + +````sh +keymgr-dpe.py -vv -c ot.cfg -j otp_ctrl_mmap.hjson -m img_otp.vmem -l lc_ctrl_state_pkg.sv \ + -r rom0.elf -r keymgr-dpe-basic-test -z 64Ki -z 32Ki -t SW -k 0 -s 0 +```` + +## Execute options + +This mode can be used to execute a sequence of steps, with multiple key generations. + +It requires an input file containing the sequence of steps to execute, which follows the INI file +format. Each section in the file represents a step in the sequence, and the options within each +section specify the parameters for that step. + +``` +usage: keymgr-dpe.py execute [-h] -s INI + +options: + -h, --help show this help message and exit + -s, --sequence INI execution sequence definition +``` + +Typical input file: + +````ini +[step_1] +mode = initialize +dst = 0 + +[step_2] +mode = advance +binding = [0] +src = 0 +dst = 1 +allow_child = true +exportable = true +retain_parent = true + +[step_3] +mode = generate +src = 1 +dst = otbn +key_version = 0 +salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" + +[step_4] +mode = generate +src = 1 +dst = none +key_version = 0 +salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" +```` + +## Verify options + +This mode can be used to verify the execution of a unit test, which is expected to print out the +input parameters it sends to the KeyManger DPE and the output key which is generated by the latter. + +Note that as the generated sideloaded key cannot usually be accessed by the Ibex core, the following +tricks are used: + +* for AES sideloaded key, the AES key is used to encrypt a zeroed plaintext, and the verification is +performed on the ciphertext +* for KMAC sideloaded key, the KMAC key is used to perform a hash on a zeroed input buffer, and the +verification is performed on the resulting hash value. +* for OTBN sideloaded key, the OTBN key is read by the OTBN core and replicated into its data +memory, which is then read back by the Ibex core. In this cas, the direct key is verified. + +``` +usage: keymgr-dpe.py execute [-h] -s INI + +options: + -h, --help show this help message and exit + -s, --sequence INI execution sequence definition +Rivos/qemu-ga0> scripts/opentitan/keymgr-dpe.py verify -h +usage: keymgr-dpe.py verify [-h] -l LOG + +options: + -h, --help show this help message and exit + -l, --exec-log LOG execution log to verify +``` + +[`pyot.py`](pyot.md) script may be used to generate the log file, see `--log-file` option or the +`log_file` test parameter. diff --git a/docs/opentitan/tools.md b/docs/opentitan/tools.md index 090fc4fbdc345..279deca9fba7b 100644 --- a/docs/opentitan/tools.md +++ b/docs/opentitan/tools.md @@ -47,6 +47,8 @@ of options and the available features. * [`gpiodev.py`](gpiodev.md) is a tiny script to run regression tests with GPIO device. * `mbbdef.py` is a simple Python tool to extract multi-bit bool definition from OpenTitan' HJSON configuration file +* [`keymgre-dpe.py`](keymgre-dpe.md) is a simple Python tool to generate KeyManager DPE output keys + using the same parameters as the KeyManager DPE. It is dedicated to unit test purposes. * `ot-format.sh` is a simple shell wrapper to run clang-format (code formatter) on OpenTitan files * `ot-tidy.sh` is a simple shell wrapper to run clang-tidy (C linter) on OpenTitan files * `present.py` implements the Present 128-bit scrambler/descrambler used in OTP image files for diff --git a/python/qemu/ot/km/__init__.py b/python/qemu/ot/km/__init__.py new file mode 100644 index 0000000000000..93ce568e53b61 --- /dev/null +++ b/python/qemu/ot/km/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""KeyManager tools.""" + +from .dpe import KeyManagerDpe diff --git a/python/qemu/ot/km/dpe.py b/python/qemu/ot/km/dpe.py new file mode 100644 index 0000000000000..652eaf6a0bd0e --- /dev/null +++ b/python/qemu/ot/km/dpe.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""QEMU OT tool to generate Key Manager DPE keys. + + :author: Emmanuel Blot +""" + +from argparse import Namespace +from binascii import unhexlify +from configparser import RawConfigParser +from io import BufferedReader +from logging import getLogger +from os.path import dirname, join as joinpath, normpath +from textwrap import fill +from typing import BinaryIO, Optional, TextIO +import sys + +QEMU_PYPATH = joinpath(dirname(dirname(dirname(normpath(__file__)))), + 'python', 'qemu') +sys.path.append(QEMU_PYPATH) + +from ..otp import OtpImage, OtpLifecycleExtension, OtpMap +from ..rom.image import ROMImage +from ..util.misc import ArgError + +# ruff: noqa: E402 +_CRYPTO_EXC: Optional[Exception] = None +try: + from Crypto.Hash import KMAC256 +except ModuleNotFoundError: + try: + # see pip3 install -r requirements.txt + from Cryptodome.Hash import KMAC256 + except ModuleNotFoundError as exc: + _CRYPTO_EXC = exc + + +class KeyManagerDpe: + """Key Manager DPE. + """ + + SLOT_COUNT = 4 + ADVANCE_DATA_WIDTH = 208 + GENERATE_DATA_WIDTH = 100 + SW_BINDINGS_WIDTH = 32 # 256 bits + SALT_WIDTH = 32 # 256 bits + KMAC_LEN = 48 # 384 bits + + OUTPUTS = ('HW', 'SW') + TARGETS = ('AES', 'KMAC', 'OTBN', 'NONE') + + def __init__(self, otp_img: OtpImage, rom_count: int): + if _CRYPTO_EXC: + raise _CRYPTO_EXC + self._log = getLogger('keymgr') + self._otp_img = otp_img + self._roms = [ROMImage(x) for x in range(rom_count)] + self._config = RawConfigParser() + self._step: int = 0 + self._command: str = '' + self._slots: list[bytes] = [b'' for _ in range(self.SLOT_COUNT)] + self._lc_ctrl_km_divs: dict[str, bytes] = {} + self._seeds: dict[str, bytes] = {} + + @classmethod + def from_args(cls, args: Namespace) -> 'KeyManagerDpe': + """Generate a key based on the provided arguments + + :param args: ArgumentPaser argument + :return KeyManagerDpe: KeyManagerDpe instance + """ + swbindings = [] + if not args.swbindings: + raise ArgError('Missing SW bindings for advance') + for sval in reversed(args.swbindings): + if len(sval) & 1: + sval = f'0{sval}' + try: + val = unhexlify(sval) + except ValueError as exc: + raise ArgError(f'Invalid SW binding: {sval}: {exc}') from exc + if len(val) > cls.SW_BINDINGS_WIDTH: + raise ArgError('Invalid SW bindings length') + swbindings.append(val) + # first step needs no bindings + swbindings.append(None) + + ssalt = args.salt + if len(ssalt) & 1: + ssalt = f'0{ssalt}' + try: + salt = unhexlify(ssalt) + except ValueError as exc: + raise ArgError(f'Invalid SW binding: {ssalt}: {exc}') from exc + + salt_len = len(salt) + if salt_len > cls.SALT_WIDTH: + raise ArgError('Invalid salt length') + + if not 0<= args.key_version < 1<<32: + raise ArgError('Invalid key version value') + + kmd = cls.create(args.otp_map, args.ecc, args.vmem, args.raw, + args.lifecycle, args.config, args.rom, args.rom_size) + + # if swbindings is not empty, use advance mode + while swbindings: + swb = swbindings.pop() + kmd.advance(0, 0, swb) + + res = kmd.generate(0, args.target, args.gen_out, salt, args.key_version) + + if args.rust_const: + const_name = args.rust_const.upper() + res_array = ', '.join((f'0x{v:02x}' for v in res)) + res_array = fill(res_array, width=100, + initial_indent=' ', subsequent_indent=' ') + outstr = (f'const {const_name}: [u8; {len(res)}] = ' + f'[\n{res_array}\n];') + else: + outstr = kmd.hexstr(res) + + if args.output: + mode = 'at' if args.rust_const else 'wt' + with open(args.output, mode) as ofp: + print(outstr, file=ofp) + else: + print(outstr) + + return kmd + + @classmethod + def create(cls, otpmap: BinaryIO, ecc: int, vmem: Optional[BinaryIO], + raw: Optional[BinaryIO], lifecycle: TextIO, config: TextIO, + roms: list[ROMImage], rom_sizes: list[int]) -> 'KeyManagerDpe': + """Create a KeyManagerDpe instance from dependencies.""" + otp_map = OtpMap() + otp_map.load(otpmap) + otpmap.close() + + otp_img = OtpImage(ecc) + if vmem: + otp_img.load_vmem(vmem, 'otp') + vmem.close() + elif raw: + otp_img.load_raw(raw) + raw.close() + otp_img.dispatch(otp_map) + + lcext = OtpLifecycleExtension() + lcext.load(lifecycle) + otp_img.load_lifecycle(lcext) + lifecycle.close() + + kmd = cls(otp_img, len(roms)) + + kmd.load_config(config) + config.close() + + if len(rom_sizes) < len(roms): + rom_sizes.extend([None] * (len(roms) - len(rom_sizes))) + for rom, (rfp, rom_size) in enumerate(zip(roms, rom_sizes)): + kmd.load_rom(rom, rfp, rom_size) + rfp.close() + + return kmd + + def load_config(self, config_file: TextIO) -> None: + """Load QEMU 'readconfig' file. + + :param config_file: the config file text stream + """ + self._config.read_file(config_file) + loaded: set[str] = set() + for section in self._config.sections(): + if not section.startswith('ot_device '): + continue + devname = section[len('ot_device '):].strip(' "') + devdescs = devname.split('.') + devtype = devdescs[0] + if devtype == 'ot-lc_ctrl' and devtype not in loaded: + loaded.add(devtype) + for opt in ('invalid', 'production', 'test_dev_rma'): + sval = self._config.get(section, opt) + if not sval: + raise ValueError(f'Unable to load {opt} LC KM div') + sval = sval.strip('"') + val = unhexlify(sval) + # TODO: check why we need to reverse the div + val = bytes(reversed(val)) + self._lc_ctrl_km_divs[opt] = val + continue + if devtype == 'ot-keymgr_dpe' and devtype not in loaded: + loaded.add(devtype) + for opt in self._config.options(section): + sval = self._config.get(section, opt).strip('"') + seed_name = opt.replace('_seed', '') + val = unhexlify(sval) + # TODO: check why we need to reverse the seeds + val = bytes(reversed(val)) + self._seeds[seed_name] = val + continue + if devtype == 'ot-rom_ctrl': + devinst = devdescs[-1] + devload = f'{devtype}.{devinst}' + if devload in loaded: + continue + loaded.add(devload) + if not devinst.startswith('rom'): + raise ValueError(f'Invalid ROM instance name: {devinst}') + devidx = int(devinst[len('rom'):]) + try: + rom_img = self._roms[devidx] + except IndexError: + self._log.warning('No ROM image loaded for device %s', + devinst) + continue + for opt in self._config.options(section): + val = self._config.get(section, opt).strip('"') + setattr(rom_img, opt, unhexlify(val)) + continue + + def load_rom(self, rom_idx: int, rfp: BufferedReader, + size: Optional[int] = None) -> None: + """Load QEMU 'readconfig' file. + + :param rom_idx: the ROM index + :param rfp: the ROM data stream + :param size: the size of the ROM image + """ + rom_img = self._roms[rom_idx] + rom_img.load(rfp, size) + + def initialize(self, dst: int): + """Initialize the KeyManager DPE statemachine. + + :param dst: the destination slot index + """ + return self.advance(0, dst) + + def advance(self, src: int, dst: int, swbindings: Optional[bytes] = None): + """Advance the KeyManager DPE statemachine to the next step. + + :param src: the source slot index + :param dst: the destination slot index + :param swbindings: the software bindings for the current step + """ + assert 0 <= src < self.SLOT_COUNT, f'Invalid source slot {src}' + assert 0 <= dst < self.SLOT_COUNT, f'Invalid destination slot {dst}' + try: + advfn = getattr(self, f'_advance_{self._step}') + except AttributeError: + self._log.error('Unknown advance step %s', self._step) + raise + exp_swbindings = self._step not in (0,) + if exp_swbindings: + if swbindings is None: + raise ValueError(f'Missing SW bindings for step {self._step}') + swb_len = len(swbindings) + if swb_len < self.SW_BINDINGS_WIDTH: + swbindings = b''.join((swbindings, + bytes(self.SW_BINDINGS_WIDTH - swb_len))) + if not exp_swbindings and swbindings is not None: + raise ValueError(f'Unexpected SW bindings for step {self._step}') + self._log.debug('Advance #%d', self._step) + self._slots[dst] = advfn(src, swbindings) + if self._step < 3: + self._step += 1 + return self._slots[dst] + + def generate(self, src: int, target: str, output: Optional[str], + salt: bytes, key_version: int) -> bytes: + """Generate an output key. + + :param src: the source slot index + :param target: the target device for the key. + :param output: the type of output key to generate. + :param salt: the salt to use for the key generation. + :param key_version: the version of the key to generate. + :return: the generated key. + """ + assert 0 <= src < self.SLOT_COUNT, f'Invalid source slot {src}' + if not output: + output = 'SW' if target == 'NONE' else 'HW' + outmap = {'SW': 'soft', 'HW': 'hard'} + output_seed = self._seeds[f'{outmap[output]}_output'] + self._log.debug('Output Key Seed: %s', self.bnstr(output_seed)) + dest = 'NONE' if target == 'SW' else target + dest_seed = self._seeds[dest.lower()] + self._log.debug('Destination Seed: %s', self.bnstr(dest_seed)) + salt_len = len(salt) + if salt_len < self.SALT_WIDTH: + salt = b''.join((salt, bytes(self.SALT_WIDTH - salt_len))) + self._log.debug('Salt: %s', self.bnstr(salt)) + key_ver = key_version.to_bytes(4, 'little') + self._log.debug('Key Version: %s', self.bnstr(key_ver)) + buf_parts = [ + output_seed, + dest_seed, + salt, + key_ver, + ] + resp = self._kmac_generate(src, buf_parts) + return resp + + @classmethod + def bnstr(cls, data: bytes) -> str: + """Convert a byte array to a big-endian hex string.""" + return bytes(reversed(data)).hex() + + @classmethod + def hexstr(cls, data: bytes) -> str: + """Convert a byte array to a hex string.""" + return data.hex() + + def _advance_0(self, *_) -> bytes: + share0 = self._otp_img.get_field('SECRET2', 'CREATOR_ROOT_KEY_SHARE0') + share1 = self._otp_img.get_field('SECRET2', 'CREATOR_ROOT_KEY_SHARE1') + self._log.debug('Creator Root Key share 0: %s', self.bnstr(share0)) + self._log.debug('Creator Root Key share 1: %s', self.bnstr(share1)) + return bytes((s0 ^ s1 for s0, s1 in zip(share0, share1))) + + def _advance_1(self, src: int, swbindings: bytes) -> bytes: + if len(self._roms) < 2: + raise ValueError('Missing ROM') + creator_seed = self._otp_img.get_field('SECRET2', 'CREATOR_SEED') + self._log.debug('Creator Seed: %s', self.bnstr(creator_seed)) + rom0_digest = self._roms[0].digest + rom1_digest = self._roms[1].digest + self._log.debug('ROM0 digest: %s', self.bnstr(rom0_digest)) + self._log.debug('ROM1 digest: %s', self.bnstr(rom1_digest)) + lc_div = self._get_lc_div() + lc_ctrl_km_div = self._lc_ctrl_km_divs[lc_div] + self._log.debug('KeyManager div: %s', self.hexstr(lc_ctrl_km_div)) + device_id = self._otp_img.get_field('HW_CFG0', 'DEVICE_ID') + self._log.debug('Device ID: %s', self.bnstr(device_id)) + revision_seed = self._seeds['revision'] + self._log.debug('Revision Seed: %s', self.bnstr(revision_seed)) + self._log.debug('Software Binding: %s', self.hexstr(swbindings)) + buf_parts = [ + creator_seed, + self._roms[0].digest, + self._roms[1].digest, + lc_ctrl_km_div, + device_id, + revision_seed, + swbindings + ] + resp = self._kmac_advance(src, buf_parts) + return resp + + def _advance_2(self, src: int, swbindings: bytes): + owner_seed = self._otp_img.get_field('SECRET3', 'OWNER_SEED') + self._log.debug('Owner Seed: %s', self.bnstr(owner_seed)) + self._log.debug('Software Binding: %s', self.hexstr(swbindings)) + buf_parts = [ + owner_seed, + swbindings + ] + resp = self._kmac_advance(src, buf_parts) + return resp + + def _advance_3(self, src: int, swbindings: bytes): + self._log.debug('Software Binding: %s', self.hexstr(swbindings)) + resp = self._kmac_advance(src, [swbindings]) + return resp + + def _kmac_advance(self, src: int, buffers: list[bytes]) -> bytes: + return self._kmac(src, buffers, self.ADVANCE_DATA_WIDTH) + + def _kmac_generate(self, src: int, buffers: list[bytes]) -> bytes: + return self._kmac(src, buffers, self.GENERATE_DATA_WIDTH) + + def _kmac(self, src: int, buffers: list[bytes], data_width: int) -> bytes: + buffer = bytearray() + for buf in buffers: + buffer.extend(buf) + buflen = len(buffer) + assert buflen <= data_width, \ + f'Invalid data width: {buflen}' + if buflen < data_width: + buffer.extend(bytes(data_width - buflen)) + kmac_key = self._slots[src][:32] + self._log.debug('KMAC key: %s (%d)', + self.bnstr(kmac_key), len(kmac_key)) + kmac = KMAC256.new(key=kmac_key, mac_len=self.KMAC_LEN) + kmac.update(buffer) + resp = kmac.digest() + self._log.info('KMAC resp: %s (%d)', self.bnstr(resp), len(resp)) + return resp + + def _get_lc_div(self): + lc_part = self._otp_img.get_partition('LIFE_CYCLE') + lc_state = lc_part.decode_field('LC_STATE') + self._log.debug('LC state: %s', lc_state) + if lc_state.startswith('TESTUNLOCKED'): + return 'test_dev_rma' + if lc_state in ('DEV', 'RMA'): + return 'test_dev_rma' + if lc_state.startswith('PROD'): + return 'production' + return 'invalid' diff --git a/python/qemu/ot/km/engine.py b/python/qemu/ot/km/engine.py new file mode 100644 index 0000000000000..d60c41eac894e --- /dev/null +++ b/python/qemu/ot/km/engine.py @@ -0,0 +1,360 @@ +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""QEMU OT tool to verify Key Manager DPE test execution + + :author: Emmanuel Blot +""" + +from binascii import unhexlify +from collections.abc import Iterator +from configparser import ConfigParser, NoOptionError +from logging import getLogger +from typing import BinaryIO, NamedTuple, Optional, TextIO, Union + +import re +import sys + +from .dpe import KeyManagerDpe +from ..util.misc import to_bool, HexInt + +# ruff: noqa: E402 +_CRYPTO_EXC: Optional[Exception] = None +try: + from Crypto.Cipher import AES + from Crypto.Hash import KMAC256 +except ModuleNotFoundError: + try: + # see pip3 install -r requirements.txt + from Cryptodome.Cipher import AES + from Cryptodome.Hash import KMAC256 + except ModuleNotFoundError as exc: + _CRYPTO_EXC = exc + + +KeyManagerDpeStep = NamedTuple +"""Key Manager DPE test step.""" + + +class KeyManagerDpeStepInitialize(KeyManagerDpeStep): + """Key Manager DPE test initialization step.""" + + dst: int + """Destination slot.""" + + +class KeyManagerDpeStepErase(KeyManagerDpeStep): + """Key Manager DPE test erase step.""" + + dst: int + """Destination slot.""" + + +class KeyManagerDpeStepAdvance(KeyManagerDpeStep): + """Key Manager DPE test advance step.""" + + src: int + """Source slot.""" + + dst: int + """Destination slot.""" + + max_key_version: int + """Maximum key version.""" + + binding: bytes + """Software bindings.""" + + allow_child: bool + """Whether this context allows derivation of further children.""" + + exportable: bool + """Whether the key for the target slot is exportable.""" + + reatin_parent: bool + """Whether further advance operations force erasure of the slot.""" + + +class KeyManagerDpeStepGenerate(KeyManagerDpeStep): + """Key Manager DPE test generate step""" + + src: int + """Source slot.""" + + dst: str + """Destination device.""" + + output: Optional[str] + """Type of output.""" + + key_version: int + """Key version.""" + + salt: bytes + """Salt.""" + + +class KeyManagerDpeEngine: + """ + Key Manager DPE test executer + """ + + ANSI_CRE = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]') + """Filter out ANSI escape sequences from input stream (colors, etc.).""" + + LOG_CRE = re.compile(r'^(?:.*\s)?T>\s(.*)$') + """Lines of interest in the log file.""" + + def __init__(self, kmd: KeyManagerDpe): + self._log = getLogger('keymgr.eng') + self._kmd = kmd + self._steps: dict[str, KeyManagerDpeStep] = [] + self._exp_results: dict[str, bytes] = {} + + def execute(self, ifp: TextIO) -> None: + """Verify Key Manager DPE test execution + + :param ifp: ini-style sequence definition stream + """ + seq = ConfigParser() + seq.read_file(ifp) + self._steps = self._build_steps_from_seq(seq) + self._exp_results = self._execute_steps() + + def verify(self, lfp: BinaryIO) -> None: + """Verify Key Manager DPE test execution + + :param lfp: bianry log stream + """ + exec_log = self._filter_log(lfp) + self._steps, results = self._build_steps_from_log(exec_log) + if not self._steps: + raise ValueError('No step found in log') + self._exp_results = self._execute_steps() + error_count = self._verify_results(results) + if error_count: + raise ValueError(f'{error_count} errors found') + + def _filter_log(self, lfp: BinaryIO) -> Iterator[KeyManagerDpeStep]: + """Filter log stream + + :param lfp: log stream + """ + for line in lfp: + line = self.ANSI_CRE.sub('', line.decode(errors='ignore')).strip() + lmo = self.LOG_CRE.match(line) + if not lmo: + continue + yield lmo.group(1) + + @classmethod + def _parse_bytes(cls, value: str) -> bytes: + """Parse bytes from string + + :param value: string representation of bytes + :return: the parsed bytes + """ + value = value.strip('"') + if value.startswith('[') and value.endswith(']'): + value = value[1:-1] + if len(value) & 1: + value = f'0{value}' + return unhexlify(value) + if value.startswith('0x'): + ivalue = int(value, 16) + return ivalue.to_bytes((ivalue.bit_length() + 7) // 8, 'big') + raise ValueError(f'invalid bytes: {value}') + + + def _build_steps_from_seq(self, seq: ConfigParser) -> \ + dict[str, KeyManagerDpeStep]: + module = sys.modules[__name__] + steps: dict[str, KeyManagerDpeStep] = {} + for step in seq.sections(): + if step in steps: + raise ValueError(f'Duplicate step: {step}') + mode = seq.get(step, 'mode') + try: + step_cls = getattr(module, f'KeyManagerDpeStep{mode.title()}') + except AttributeError as exc: + raise ValueError(f'Unknown mode: {mode}') from exc + attrs = [] + for name, type_ in step_cls.__annotations__.items(): + if type_ is bool: + val = seq.getboolean(step, name, fallback=True) + elif type_ is int: + val = seq.getint(step, name, fallback=None) + if val is None: + if name == 'max_key_version': + val = (1 << 32) - 1 + else: + raise ValueError(f'Step {step} is missing {name}') + elif type_ is str: + val = seq.get(step, name, fallback=None) + if val is None: + raise ValueError(f'Step {step} is missing {name}') + elif type_ is Optional[str]: + val = seq.get(step, name, fallback=None) + elif type_ is bytes: + try: + val = self._parse_bytes(seq.get(step, name)) + except (KeyError, NoOptionError) as exc: + raise ValueError(f'Step {step} is missing attribute ' + f"'{name}'") from exc + except (AttributeError, ValueError) as exc: + raise ValueError(f'Step {step}, invalid bytes for ' + f'{name}: {exc}') from exc + else: + raise ValueError(f'Step {step}, invalid type for {name}: ' + f'{type_}') + if not isinstance(val, type_): + raise TypeError(f'Step {step}, invalid type for {name}: ' + f'{type(val)}, expected {type_}') + self._log.debug("Step %s: attr '%s': type '%s', val= %s", + step, name, type_.__name__, val) + attrs.append(val) + kmd_step = step_cls(*attrs) + steps[step] = kmd_step + return steps + + def _build_steps_from_log(self, itlog: Iterator[str]) -> \ + tuple[dict[str, KeyManagerDpeStep], dict[str, bytes]]: + module = sys.modules[__name__] + steps: dict[str, KeyManagerDpeStep] = {} + results: dict[str, bytes] = {} + for step, values, result in self._enumerate_steps_from_log(itlog): + self._log.info('Parsing %s log', step) + if step in steps: + raise ValueError(f'Duplicate step: {step}') + try: + mode = values['mode'] + step_cls = getattr(module, f'KeyManagerDpeStep{mode.title()}') + except KeyError as exc: + raise ValueError('Missing mode in result') from exc + except AttributeError as exc: + raise ValueError(f'Unknown mode: {mode}') from exc + attrs = [] + for name, type_ in step_cls.__annotations__.items(): + if type_ is bool: + val = to_bool(values.get(name, True), permissive=False) + elif type_ is int: + val = values.get(name, None) + if val is None: + if name == 'max_key_version': + val = (1 << 32) - 1 + else: + raise ValueError(f'Step {step} is missing {name}') + else: + val = HexInt.parse(val) + elif type_ is str: + val = values.get(name, None) + if val is None: + raise ValueError(f'Step {step} is missing {name}') + elif type_ is Optional[str]: + val = values.get(name, None) + elif type_ is bytes: + try: + val = self._parse_bytes(values[name]) + except (KeyError, ValueError) as exc: + raise ValueError(f'Step {step}, invalid bytes for ' + f'{name}: {exc}') from exc + else: + raise ValueError(f'Step {step}, invalid type for {name}: ' + f'{type_}') + if not isinstance(val, type_): + raise TypeError(f'Step {step}, invalid type for {name}: ' + f'{type(val)}, expected {type_}') + attrs.append(val) + kmd_step = step_cls(*attrs) + steps[step] = kmd_step + if result: + try: + results[step] = val = self._parse_bytes(result) + except ValueError as exc: + raise ValueError(f'Step {step}, ' + f'invalid result bytes: {exc}') from exc + elif mode == 'generate': + raise RuntimeError(f'Step {step}, no result bytes provided') + return steps, results + + def _enumerate_steps_from_log(self, itlog: Iterator[str]) -> \ + Iterator[tuple[str, dict[str, Union[str, int, bytes, bool]], + Optional[bytes]]]: + step = None + values: dict[str, Union[str, int, bytes, bool]] = {} + result: Optional[bytes] = None + for line in itlog: + smo = re.match(r'^\[(step_\d+)\]$', line) + if smo: + if step: + yield step, values, result + step = None + values = {} + result = None + step = smo.group(1) + continue + if not step: + raise ValueError('Parsing error') + attr, val = [x.strip() for x in line.split('=')] + if attr == 'output': + result = val + else: + values[attr] = val + if step: + yield step, values, result + + def _execute_steps(self) -> dict[str, bytes]: + results: dict[str, bytes] = {} + for name, step in self._steps.items(): + self._log.info('Executing %s (%s: %s)', name, + step.__class__.__name__[len('KeyManagerDpeStep'):], + step.dst) + if isinstance(step, KeyManagerDpeStepInitialize): + res = self._kmd.initialize(step.dst) + elif isinstance(step, KeyManagerDpeStepErase): + res = self._kmd.erase(step.dst) + elif isinstance(step, KeyManagerDpeStepAdvance): + res = self._kmd.advance(step.src, step.dst, step.binding) + elif isinstance(step, KeyManagerDpeStepGenerate): + res = self._kmd.generate(step.src, step.dst, step.output, + step.salt, step.key_version) + res = self._retrieve_output(step.dst, res) + results[name] = res + else: + raise ValueError(f'Unknown step type: ' + f'{step.__class__.__name__}') + self._log.debug('Result: %s', res.hex()) + return results + + def _verify_results(self, results: dict[str, bytes]) -> int: + error = 0 + for name, step in self._steps.items(): + if not isinstance(step, KeyManagerDpeStepGenerate): + continue + self._log.info('Verifying result for %s', name) + exp_result = self._exp_results[name] + result = results[name] + if result != exp_result: + self._log.error('Key mismatch for %s, %s != %s', + name, result.hex(), exp_result.hex()) + error += 1 + continue + self._log.info('Key verified for %s, %s', name, result.hex()) + return error + + def _retrieve_output(self, output: str, key_: bytes) -> bytes: + if _CRYPTO_EXC: + raise _CRYPTO_EXC + output = output.lower() + self._log.debug('retrieve output %s', output) + if output == 'aes': + assert len(key_) >= 32, 'AES Key must be at least 32 bytes long' + aes = AES.new(key_[:32], AES.MODE_ECB) + return aes.encrypt(bytes(16)) + if output == 'kmac': + kmac = KMAC256.new(key=key_[:32], mac_len=256//8) + kmac.update(bytes(4)) + return kmac.digest() + if output == 'otbn': + return key_ + raise ValueError(f'Invalid output type: {output}') diff --git a/python/qemu/ot/rom/image.py b/python/qemu/ot/rom/image.py index fccb3b81a0171..43558182055ed 100644 --- a/python/qemu/ot/rom/image.py +++ b/python/qemu/ot/rom/image.py @@ -19,8 +19,13 @@ _CRYPTO_EXC: Optional[Exception] = None try: from Crypto.Hash import cSHAKE256 -except ModuleNotFoundError as exc: - _CRYPTO_EXC = exc +except ModuleNotFoundError: + try: + # see pip3 install -r requirements.txt + from Cryptodome.Hash import cSHAKE256 + except ModuleNotFoundError as exc: + _CRYPTO_EXC = exc + class ROMImage: """ diff --git a/scripts/opentitan/keymgr-dpe.py b/scripts/opentitan/keymgr-dpe.py new file mode 100755 index 0000000000000..cfef1fff33af6 --- /dev/null +++ b/scripts/opentitan/keymgr-dpe.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2025 Rivos, Inc. +# SPDX-License-Identifier: Apache2 + +"""QEMU OT tool to generate Key Manager DPE keys. + + :author: Emmanuel Blot +""" + +# pylint: disable=invalid-name +# pylint: enable=invalid-name + +from argparse import ArgumentParser, FileType +from os.path import dirname, join as joinpath, normpath +from traceback import format_exception +import sys + +QEMU_PYPATH = joinpath(dirname(dirname(dirname(normpath(__file__)))), + 'python', 'qemu') +sys.path.append(QEMU_PYPATH) + +# ruff: noqa: E402 +from ot.km.dpe import KeyManagerDpe +from ot.km.engine import KeyManagerDpeEngine +from ot.otp.image import OtpImage +from ot.util.log import configure_loggers +from ot.util.misc import ArgError, HexInt + + +def main(): + """Main routine""" + debug = True + desc = sys.modules[__name__].__doc__.split('.', 1)[0].strip() + argparser = ArgumentParser(description=f'{desc}.') + try: + + files = argparser.add_argument_group(title='Files') + files.add_argument('-c', '--config', type=FileType('rt'), + metavar='CFG', required=True, + help='input QEMU OT config file') + files.add_argument('-j', '--otp-map', type=FileType('rt'), + metavar='HJSON', required=True, + help='input OTP controller memory map file') + files.add_argument('-l', '--lifecycle', type=FileType('rt'), + metavar='SV', + help='input lifecycle system verilog file') + files.add_argument('-m', '--vmem', type=FileType('rt'), + help='input VMEM file') + files.add_argument('-R', '--raw', type=FileType('rb'), + help='input QEMU OTP raw image file') + files.add_argument('-r', '--rom', type=FileType('rb'), action='append', + help='input ROM image file, may be repeated') + + params = argparser.add_argument_group(title='Parameters') + params.add_argument('-e', '--ecc', type=int, + default=OtpImage.DEFAULT_ECC_BITS, + metavar='BITS', help=f'ECC bit count (default: ' + f'{OtpImage.DEFAULT_ECC_BITS})') + params.add_argument('-z', '--rom-size', metavar='SIZE', + type=HexInt.xparse, + action='append', default=[], + help='ROM image size in bytes, may be repeated') + + extra = argparser.add_argument_group(title='Extras') + extra.add_argument('-v', '--verbose', action='count', + help='increase verbosity') + extra.add_argument('-d', '--debug', action='store_true', + help='enable debug mode') + + subparsers = argparser.add_subparsers(help='Execution mode', + dest='exec') + + genparser = subparsers.add_parser('generate', help='generate a key') + genparser.add_argument('-b', '--swbindings', action='append', + default=[], metavar='HEXSTR', + help='SW bindings, may be repeated') + genparser.add_argument('-g', '--gen-out', choices=KeyManagerDpe.OUTPUTS, + help='generation output (default: auto)') + genparser.add_argument('-k', '--key-version', metavar='HEXSTR', + type=HexInt, required=True, + help='Key version') + genparser.add_argument('-o', '--output', + help='output file with generated key') + genparser.add_argument('-R', '--rust-const', metavar='NAME', + help='Rust constant name for the generated key') + genparser.add_argument('-s', '--salt', default=[], metavar='HEXSTR', + required=True, + help='Salt') + genparser.add_argument('-t', '--target', + choices=KeyManagerDpe.TARGETS, required=True, + help='destination device') + + exeparser = subparsers.add_parser('execute', help='execute sqeuence') + exeparser.add_argument('-s', '--sequence', metavar='INI', required=True, + type=FileType('rt'), + help='execution sequence definition') + + vrfparser = subparsers.add_parser('verify', help='verify execution log') + vrfparser.add_argument('-l', '--exec-log', metavar='LOG', required=True, + type=FileType('rb'), + help='execution log to verify') + + args = argparser.parse_args() + debug = args.debug + + configure_loggers(args.verbose, 'keymgr', 'romimg', -1, 'otp', + name_width=12) + + if not (args.vmem or args.raw): + argparser.error('Either VMEM or RAW image file must be specified') + if args.vmem and args.raw: + argparser.error('Only one of VMEM or RAW image file can be specified') + + if args.exec == 'generate': + KeyManagerDpe.from_args(args) + else: + kmd = KeyManagerDpe.create( + args.otp_map, args.ecc, args.vmem, args.raw, args.lifecycle, + args.config, args.rom, args.rom_size) + kme = KeyManagerDpeEngine(kmd) + if args.exec == 'execute': + kme.execute(args.sequence) + elif args.exec == 'verify': + kme.verify(args.exec_log) + + except ArgError as exc: + argparser.error(str(exc)) + except (IOError, ValueError, ImportError) as exc: + print(f'\nError: {exc}', file=sys.stderr) + if debug: + print(''.join(format_exception(exc, chain=False)), + file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(2) + + +if __name__ == '__main__': + main() From 2930b3030efa20f381ecf8e00ebf30f3b4051643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Mon, 21 Jul 2025 18:26:53 +0200 Subject: [PATCH 171/175] [ot] hw/opentitan: ot_aes: fix key sideloading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/opentitan/ot_aes.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_aes.c b/hw/opentitan/ot_aes.c index 2ee15d248fcd9..509cc0328fbdc 100644 --- a/hw/opentitan/ot_aes.c +++ b/hw/opentitan/ot_aes.c @@ -529,13 +529,13 @@ static void ot_aes_trigger_reseed(OtAESState *s) static void ot_aes_sideload_key(OtAESState *s) { OtAESKey *key = s->sl_key; + OtAESContext *c = s->ctx; if (!key->valid) { + c->key_ready = false; return; } - OtAESContext *c = s->ctx; - for (unsigned ix = 0u; ix < OT_AES_KEY_DWORD_COUNT; ix++) { c->key[ix] = key->share0[ix] ^ key->share1[ix]; } @@ -1095,6 +1095,11 @@ static void ot_aes_push_key(OtKeySinkIf *ifd, const uint8_t *share0, memset(key->share1, 0, OT_AES_KEY_SIZE); } key->valid = valid; + + if (ot_aes_is_sideload(s->regs)) { + ot_aes_sideload_key(s); + ot_aes_update_config(s); + } } static uint64_t ot_aes_read(void *opaque, hwaddr addr, unsigned size) @@ -1305,6 +1310,7 @@ static void ot_aes_write(void *opaque, hwaddr addr, uint64_t val64, if (ot_aes_is_sideload(s->regs)) { ot_aes_sideload_key(s); } + ot_aes_update_config(s); break; case R_CTRL_AUX_SHADOWED: if (!r->ctrl_aux_regwen) { From 38524fb751a6dca6923e89c8195c05e5abe1432f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Tue, 22 Jul 2025 17:23:42 +0200 Subject: [PATCH 172/175] [ot] hw/riscv: ot_earlgrey: re-align clock frequencies with RTL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- hw/riscv/ot_earlgrey.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 052876dba1e00..37a21dc91a7a4 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -813,7 +813,8 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_DEVLINK("clock-src", AST) ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_STRING_PROP("topclocks", "main:500,io:480,usb:240,aon:1"), + /* topclocks property overridden in ot_eg_soc_ast_configure */ + IBEX_DEV_STRING_PROP("topclocks", "main:240,io:240,usb:480,aon:25"), IBEX_DEV_STRING_PROP("refclock", "aon"), IBEX_DEV_STRING_PROP("subclocks", "io_div2:io:2,io_div4:io:4," @@ -1318,9 +1319,9 @@ static void ot_eg_soc_ast_configure(DeviceState *dev, const IbexDeviceDef *def, const char *clock_cfg; if (!verilator_mode) { /* EarlGrey/CW310 */ - clock_cfg = "main:10000000,io:10000000,usb:10000000,aon:250000"; + clock_cfg = "main:24000000,io:24000000,usb:48000000,aon:250000"; } else { - clock_cfg = "main:500000,io:500000,usb:500000,aon:250000"; + clock_cfg = "main:500000,io:500000,usb:500000,aon:125000"; } qdev_prop_set_string(dev, "topclocks", clock_cfg); From cead76d0a71c058cb0ed1eba9343bf3d58070fef Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Tue, 22 Jul 2025 16:23:59 +0200 Subject: [PATCH 173/175] [ot] docs/opentitan: keymgr-dpe.md: fix documentation Signed-off-by: Emmanuel Blot --- docs/opentitan/keymgr-dpe.md | 83 +++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/docs/opentitan/keymgr-dpe.md b/docs/opentitan/keymgr-dpe.md index 9fc05d178dca5..e4299550629c5 100644 --- a/docs/opentitan/keymgr-dpe.md +++ b/docs/opentitan/keymgr-dpe.md @@ -132,7 +132,7 @@ keymgr-dpe.py -vv -c ot.cfg -j otp_ctrl_mmap.hjson -m img_otp.vmem -l lc_ctrl_st -r rom0.elf -r keymgr-dpe-basic-test -z 64Ki -z 32Ki -t SW -k 0 -s 0 ```` -## Execute options +## Execute options [execute-options] This mode can be used to execute a sequence of steps, with multiple key generations. @@ -148,36 +148,40 @@ options: -s, --sequence INI execution sequence definition ``` -Typical input file: - -````ini -[step_1] -mode = initialize -dst = 0 - -[step_2] -mode = advance -binding = [0] -src = 0 -dst = 1 -allow_child = true -exportable = true -retain_parent = true - -[step_3] -mode = generate -src = 1 -dst = otbn -key_version = 0 -salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" - -[step_4] -mode = generate -src = 1 -dst = none -key_version = 0 -salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" -```` +### Arguments + +* `-s` specify an INI-like configuration file containing the sequence of steps to execute + + Typical input file: + + ````ini + [step_1] + mode = initialize + dst = 0 + + [step_2] + mode = advance + binding = [0] + src = 0 + dst = 1 + allow_child = true + exportable = true + retain_parent = true + + [step_3] + mode = generate + src = 1 + dst = otbn + key_version = 0 + salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" + + [step_4] + mode = generate + src = 1 + dst = none + key_version = 0 + salt = "[49379059ff52399275666880c0e44716999612df80f1a9de481eae4045e2c7f0]" + ```` ## Verify options @@ -195,12 +199,6 @@ verification is performed on the resulting hash value. memory, which is then read back by the Ibex core. In this cas, the direct key is verified. ``` -usage: keymgr-dpe.py execute [-h] -s INI - -options: - -h, --help show this help message and exit - -s, --sequence INI execution sequence definition -Rivos/qemu-ga0> scripts/opentitan/keymgr-dpe.py verify -h usage: keymgr-dpe.py verify [-h] -l LOG options: @@ -208,5 +206,12 @@ options: -l, --exec-log LOG execution log to verify ``` -[`pyot.py`](pyot.md) script may be used to generate the log file, see `--log-file` option or the -`log_file` test parameter. +### Arguments + +* `-l` specify the execution log to verify. The execution log is expected to contain the output of + a test that has run on the OpenTitan platform. It should emit a syntax identitical to the format + described in the [Execute options](#execute-options) section, _i.e._ an INI-like syntax. To distinguish INI + syntax from any other log output, each line of interest should be prefixed with a `T> ` marker. + + [`pyot.py`](pyot.md) script may be used to generate the log file, see `--log-file` option or the + `log_file` test parameter. From 5b23c885505c7f29a4fe22946ff21512f22d2868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Tue, 22 Jul 2025 18:25:47 +0200 Subject: [PATCH 174/175] [ot] .gitlab-ci.d: opentitan: update BM tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Lefort --- .gitlab-ci.d/opentitan/qemu-ot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.d/opentitan/qemu-ot.yml b/.gitlab-ci.d/opentitan/qemu-ot.yml index 2e0fdae3a1ea5..3630fbf9e20f9 100644 --- a/.gitlab-ci.d/opentitan/qemu-ot.yml +++ b/.gitlab-ci.d/opentitan/qemu-ot.yml @@ -1,5 +1,5 @@ variables: - BAREMETAL_REF: "b0-250325-1" + BAREMETAL_REF: "b0-250722-1" QEMU_BUILD_OPTS: "--disable-install-blobs" include: From 0f1e87b540130088b0cfdc9aac33092d30f4f8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lefort?= Date: Tue, 22 Jul 2025 19:28:52 +0200 Subject: [PATCH 175/175] [ot] scripts: opentitan: allow additional commit prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow commit line to start with "FROMLIST:" to indicate a commit cherry-picked from a mailing list. Signed-off-by: Loïc Lefort --- scripts/opentitan/lint-commits.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/opentitan/lint-commits.sh b/scripts/opentitan/lint-commits.sh index 04ada58dd7d12..7232f627687c3 100755 --- a/scripts/opentitan/lint-commits.sh +++ b/scripts/opentitan/lint-commits.sh @@ -27,6 +27,10 @@ lint_title() { echo " [ot] hw/opentitan: ot_hmac: fix i2c register address" >&2 } + if echo "$title" | grep -P -q '^FROMLIST:'; then + exit 0 + fi + if ! echo "$title" | grep -P -q '^\[ot\]'; then echo "::error::${short_hash}: commit titles must have the prefix '[ot]'" >&2 example