[PATCH v2] target/riscv: pmp: clear the entire PMP state on reset

Sebastian Alba Vives posted 1 patch 1 week, 4 days ago
target/riscv/cpu.c     | 4 ++--
target/riscv/tcg/pmp.c | 9 ++-------
target/riscv/tcg/pmp.h | 2 +-
3 files changed, 5 insertions(+), 10 deletions(-)
[PATCH v2] target/riscv: pmp: clear the entire PMP state on reset
Posted by Sebastian Alba Vives 1 week, 4 days ago
pmp_unlock_entries() bounded its loop with pmp_get_num_rules(), which
returns the number of entries whose A field is not OFF rather than the
number of implemented entries. With a sparse configuration the loop
stops early: with entries 0 and 1 OFF and entries 2 and 3 locked,
num_rules is 2, the loop covers i = 0 and i = 1, and entries 2 and 3
keep their lock and address-match bits across reset.

riscv_cpu_reset_hold() clears mseccfg, and with it RLB, before calling
this, so pmp_is_readonly() then silently discards every subsequent
pmpcfg write to those entries. The stale rules survive for the
lifetime of the machine.

Clearing only PMP_LOCK and PMP_AMATCH is not enough either. The priv
spec only mandates that the A and L fields be reset, but QEMU keeps
derived state next to the architectural registers: pmp_state.addr[]
and pmp_state.num_rules are recomputed from the pmpaddr and pmpcfg
write paths only, never at reset. pmp_is_in_range() compares against
addr[].sa and addr[].ea without consulting the A field, and the
partially-inside check in pmp_hart_has_privs() runs before the A
field is tested, so a range left over from before the reset can still
deny an access that straddles its boundary once the guest configures
any entry again.

Zero the whole structure instead. Zero is the correct reset value for
every field: a cfg_reg of 0 is A=OFF with no permissions, and a
num_rules of 0 restores the no-rules short cut. Rename the function
since it no longer only unlocks entries.

Fixes: 4bf501dc0118 ("target/riscv: pmp: Clear pmp/smepmp bits on reset")
Suggested-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
---
Changes in v2:
- Zero the whole env->pmp_state instead of masking PMP_LOCK and
  PMP_AMATCH over a bounded set of entries, as suggested by Richard.
  v1 also left pmp_state.addr[] and pmp_state.num_rules stale across
  reset, which the memset covers as well.

v1: https://lore.kernel.org/qemu-devel/20260916034331.2390427-1-sebasjosue84@gmail.com/

 target/riscv/cpu.c     | 4 ++--
 target/riscv/tcg/pmp.c | 9 ++-------
 target/riscv/tcg/pmp.h | 2 +-
 3 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index da4c1a090b..0b71196507 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -1048,7 +1048,7 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
     }
 
     /*
-     * Clear mseccfg and unlock all the PMP entries upon reset.
+     * Clear mseccfg and the PMP state upon reset.
      * This is required as per the priv, smepmp, and other security
      * extension specifications that share this CSR, and is needed
      * to clear stale entries across reboots.
@@ -1060,7 +1060,7 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
         env->mseccfg = 0;
     }
 
-    pmp_unlock_entries(env);
+    pmp_reset(env);
 #endif /* ifdef CONFIG_TCG */
 #else
     env->priv = PRV_U;
diff --git a/target/riscv/tcg/pmp.c b/target/riscv/tcg/pmp.c
index 41b55519a8..437e0ff2af 100644
--- a/target/riscv/tcg/pmp.c
+++ b/target/riscv/tcg/pmp.c
@@ -199,14 +199,9 @@ static bool pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val)
     return false;
 }
 
-void pmp_unlock_entries(CPURISCVState *env)
+void pmp_reset(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);
-    }
+    memset(&env->pmp_state, 0, sizeof(env->pmp_state));
 }
 
 static void pmp_decode_napot(hwaddr a, hwaddr *sa, hwaddr *ea)
diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h
index 4c95c2767a..99546934fc 100644
--- a/target/riscv/tcg/pmp.h
+++ b/target/riscv/tcg/pmp.h
@@ -79,7 +79,7 @@ void pmp_update_rule_addr(CPURISCVState *env, uint32_t pmp_index);
 void pmp_update_rule_nums(CPURISCVState *env);
 uint32_t pmp_get_num_rules(CPURISCVState *env);
 int pmp_priv_to_page_prot(pmp_priv_t pmp_priv);
-void pmp_unlock_entries(CPURISCVState *env);
+void pmp_reset(CPURISCVState *env);
 
 #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML)
 #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg, MSECCFG_MMWP)
-- 
2.43.0
Re: [PATCH v2] target/riscv: pmp: clear the entire PMP state on reset
Posted by Alistair Francis 15 hours ago
On Sat, 2026-10-10 at 22:06 -0600, Sebastian Alba Vives wrote:
> pmp_unlock_entries() bounded its loop with pmp_get_num_rules(), which
> returns the number of entries whose A field is not OFF rather than
> the
> number of implemented entries. With a sparse configuration the loop
> stops early: with entries 0 and 1 OFF and entries 2 and 3 locked,
> num_rules is 2, the loop covers i = 0 and i = 1, and entries 2 and 3
> keep their lock and address-match bits across reset.
> 
> riscv_cpu_reset_hold() clears mseccfg, and with it RLB, before
> calling
> this, so pmp_is_readonly() then silently discards every subsequent
> pmpcfg write to those entries. The stale rules survive for the
> lifetime of the machine.
> 
> Clearing only PMP_LOCK and PMP_AMATCH is not enough either. The priv
> spec only mandates that the A and L fields be reset, but QEMU keeps
> derived state next to the architectural registers: pmp_state.addr[]
> and pmp_state.num_rules are recomputed from the pmpaddr and pmpcfg
> write paths only, never at reset. pmp_is_in_range() compares against
> addr[].sa and addr[].ea without consulting the A field, and the
> partially-inside check in pmp_hart_has_privs() runs before the A
> field is tested, so a range left over from before the reset can still
> deny an access that straddles its boundary once the guest configures
> any entry again.
> 
> Zero the whole structure instead. Zero is the correct reset value for
> every field: a cfg_reg of 0 is A=OFF with no permissions, and a
> num_rules of 0 restores the no-rules short cut. Rename the function
> since it no longer only unlocks entries.
> 
> Fixes: 4bf501dc0118 ("target/riscv: pmp: Clear pmp/smepmp bits on
> reset")
> Suggested-by: Richard Henderson <richard.henderson@linaro.org>
> Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
> ---
> Changes in v2:
> - Zero the whole env->pmp_state instead of masking PMP_LOCK and
>   PMP_AMATCH over a bounded set of entries, as suggested by Richard.
>   v1 also left pmp_state.addr[] and pmp_state.num_rules stale across
>   reset, which the memset covers as well.

I think your v1 is actually better.

Richard asked why you don't just zero `pmp_state`, but the answer is
because the RISC-V spec states that on reset "Writable PMP
registers’ A and L fields are set to 0". So we should follow that

Alistair

> 
> v1:
> https://lore.kernel.org/qemu-devel/20260916034331.2390427-1-sebasjosue84@gmail.com/
> 
>  target/riscv/cpu.c     | 4 ++--
>  target/riscv/tcg/pmp.c | 9 ++-------
>  target/riscv/tcg/pmp.h | 2 +-
>  3 files changed, 5 insertions(+), 10 deletions(-)
> 
> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> index da4c1a090b..0b71196507 100644
> --- a/target/riscv/cpu.c
> +++ b/target/riscv/cpu.c
> @@ -1048,7 +1048,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> ResetType type)
>      }
>  
>      /*
> -     * Clear mseccfg and unlock all the PMP entries upon reset.
> +     * Clear mseccfg and the PMP state upon reset.
>       * This is required as per the priv, smepmp, and other security
>       * extension specifications that share this CSR, and is needed
>       * to clear stale entries across reboots.
> @@ -1060,7 +1060,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> ResetType type)
>          env->mseccfg = 0;
>      }
>  
> -    pmp_unlock_entries(env);
> +    pmp_reset(env);
>  #endif /* ifdef CONFIG_TCG */
>  #else
>      env->priv = PRV_U;
> diff --git a/target/riscv/tcg/pmp.c b/target/riscv/tcg/pmp.c
> index 41b55519a8..437e0ff2af 100644
> --- a/target/riscv/tcg/pmp.c
> +++ b/target/riscv/tcg/pmp.c
> @@ -199,14 +199,9 @@ static bool pmp_write_cfg(CPURISCVState *env,
> uint32_t pmp_index, uint8_t val)
>      return false;
>  }
>  
> -void pmp_unlock_entries(CPURISCVState *env)
> +void pmp_reset(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);
> -    }
> +    memset(&env->pmp_state, 0, sizeof(env->pmp_state));
>  }
>  
>  static void pmp_decode_napot(hwaddr a, hwaddr *sa, hwaddr *ea)
> diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h
> index 4c95c2767a..99546934fc 100644
> --- a/target/riscv/tcg/pmp.h
> +++ b/target/riscv/tcg/pmp.h
> @@ -79,7 +79,7 @@ void pmp_update_rule_addr(CPURISCVState *env,
> uint32_t pmp_index);
>  void pmp_update_rule_nums(CPURISCVState *env);
>  uint32_t pmp_get_num_rules(CPURISCVState *env);
>  int pmp_priv_to_page_prot(pmp_priv_t pmp_priv);
> -void pmp_unlock_entries(CPURISCVState *env);
> +void pmp_reset(CPURISCVState *env);
>  
>  #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML)
>  #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg,
> MSECCFG_MMWP)
Re: [PATCH v2] target/riscv: pmp: clear the entire PMP state on reset
Posted by Sebastián Alba 15 hours ago
Fair enough on the A and L fields, I'll follow the spec there.

The reason v2 went further... v1 leaves pmp_state.addr[] and
num_rules stale. Those are QEMU's own derived state, recomputed
only on the pmpaddr/pmpcfg write paths and never at reset.
pmp_is_in_range() compares against addr[].sa and .ea without
looking at the A field, and the partially-inside check in
pmp_hart_has_privs() runs before A is tested. So a range left from
before the reset can still deny a straddling access with A=OFF.
That part isn't a spec question, it's bookkeeping the spec doesn't
know about.

I'll respin as two patches:

  1/2  bound the loop by the implemented entry count instead of
       pmp_get_num_rules(), clearing A and L. This is v1.
  2/2  reset addr[] and num_rules alongside it.

Happy for you to take just 1/2 and leave 2/2 to stand on its own.

Sebastián

El mié, 21 oct 2026 a las 21:39, Alistair Francis (<Alistair.Francis@wdc.com>)
escribió:

> On Sat, 2026-10-10 at 22:06 -0600, Sebastian Alba Vives wrote:
> > pmp_unlock_entries() bounded its loop with pmp_get_num_rules(), which
> > returns the number of entries whose A field is not OFF rather than
> > the
> > number of implemented entries. With a sparse configuration the loop
> > stops early: with entries 0 and 1 OFF and entries 2 and 3 locked,
> > num_rules is 2, the loop covers i = 0 and i = 1, and entries 2 and 3
> > keep their lock and address-match bits across reset.
> >
> > riscv_cpu_reset_hold() clears mseccfg, and with it RLB, before
> > calling
> > this, so pmp_is_readonly() then silently discards every subsequent
> > pmpcfg write to those entries. The stale rules survive for the
> > lifetime of the machine.
> >
> > Clearing only PMP_LOCK and PMP_AMATCH is not enough either. The priv
> > spec only mandates that the A and L fields be reset, but QEMU keeps
> > derived state next to the architectural registers: pmp_state.addr[]
> > and pmp_state.num_rules are recomputed from the pmpaddr and pmpcfg
> > write paths only, never at reset. pmp_is_in_range() compares against
> > addr[].sa and addr[].ea without consulting the A field, and the
> > partially-inside check in pmp_hart_has_privs() runs before the A
> > field is tested, so a range left over from before the reset can still
> > deny an access that straddles its boundary once the guest configures
> > any entry again.
> >
> > Zero the whole structure instead. Zero is the correct reset value for
> > every field: a cfg_reg of 0 is A=OFF with no permissions, and a
> > num_rules of 0 restores the no-rules short cut. Rename the function
> > since it no longer only unlocks entries.
> >
> > Fixes: 4bf501dc0118 ("target/riscv: pmp: Clear pmp/smepmp bits on
> > reset")
> > Suggested-by: Richard Henderson <richard.henderson@linaro.org>
> > Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
> > ---
> > Changes in v2:
> > - Zero the whole env->pmp_state instead of masking PMP_LOCK and
> >   PMP_AMATCH over a bounded set of entries, as suggested by Richard.
> >   v1 also left pmp_state.addr[] and pmp_state.num_rules stale across
> >   reset, which the memset covers as well.
>
> I think your v1 is actually better.
>
> Richard asked why you don't just zero `pmp_state`, but the answer is
> because the RISC-V spec states that on reset "Writable PMP
> registers’ A and L fields are set to 0". So we should follow that
>
> Alistair
>
> >
> > v1:
> >
> https://lore.kernel.org/qemu-devel/20260916034331.2390427-1-sebasjosue84@gmail.com/
> >
> >  target/riscv/cpu.c     | 4 ++--
> >  target/riscv/tcg/pmp.c | 9 ++-------
> >  target/riscv/tcg/pmp.h | 2 +-
> >  3 files changed, 5 insertions(+), 10 deletions(-)
> >
> > diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> > index da4c1a090b..0b71196507 100644
> > --- a/target/riscv/cpu.c
> > +++ b/target/riscv/cpu.c
> > @@ -1048,7 +1048,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> > ResetType type)
> >      }
> >
> >      /*
> > -     * Clear mseccfg and unlock all the PMP entries upon reset.
> > +     * Clear mseccfg and the PMP state upon reset.
> >       * This is required as per the priv, smepmp, and other security
> >       * extension specifications that share this CSR, and is needed
> >       * to clear stale entries across reboots.
> > @@ -1060,7 +1060,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> > ResetType type)
> >          env->mseccfg = 0;
> >      }
> >
> > -    pmp_unlock_entries(env);
> > +    pmp_reset(env);
> >  #endif /* ifdef CONFIG_TCG */
> >  #else
> >      env->priv = PRV_U;
> > diff --git a/target/riscv/tcg/pmp.c b/target/riscv/tcg/pmp.c
> > index 41b55519a8..437e0ff2af 100644
> > --- a/target/riscv/tcg/pmp.c
> > +++ b/target/riscv/tcg/pmp.c
> > @@ -199,14 +199,9 @@ static bool pmp_write_cfg(CPURISCVState *env,
> > uint32_t pmp_index, uint8_t val)
> >      return false;
> >  }
> >
> > -void pmp_unlock_entries(CPURISCVState *env)
> > +void pmp_reset(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);
> > -    }
> > +    memset(&env->pmp_state, 0, sizeof(env->pmp_state));
> >  }
> >
> >  static void pmp_decode_napot(hwaddr a, hwaddr *sa, hwaddr *ea)
> > diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h
> > index 4c95c2767a..99546934fc 100644
> > --- a/target/riscv/tcg/pmp.h
> > +++ b/target/riscv/tcg/pmp.h
> > @@ -79,7 +79,7 @@ void pmp_update_rule_addr(CPURISCVState *env,
> > uint32_t pmp_index);
> >  void pmp_update_rule_nums(CPURISCVState *env);
> >  uint32_t pmp_get_num_rules(CPURISCVState *env);
> >  int pmp_priv_to_page_prot(pmp_priv_t pmp_priv);
> > -void pmp_unlock_entries(CPURISCVState *env);
> > +void pmp_reset(CPURISCVState *env);
> >
> >  #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML)
> >  #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg,
> > MSECCFG_MMWP)
>


-- 
Sebastián Alba
Re: [PATCH v2] target/riscv: pmp: clear the entire PMP state on reset
Posted by Sebastián Alba 1 week, 2 days ago
Two things, one about reading the test program's output and one about the
approach.

The test program decides which boot it is in by reading pmpcfg0: zero means
first boot, so it prints 'A', programs entries 2 and 3 as locked NAPOT, and
resets through sifive_test. The second-boot path prints 'B', dumps pmpcfg0,
attempts to clear it, dumps it again, and is the only path that signals PASS
and exits. On an unpatched build the second boot reads back 0x99990000 and
the clear attempt is ignored, so the value is printed twice: the entries
survive the reset and are immutable from then on, since mseccfg is cleared
before pmp_unlock_entries() runs and RLB is gone with it. With this patch
pmpcfg0 reads zero after every reset, so the program takes the first-boot
path every time and prints 'A' indefinitely, never reaching its exit. The
repeating 'A' is the pass condition, not a hang: it means no PMP state
survived the reset.

On clearing the whole struct rather than individual fields: Spike reaches
the same post-reset state by a different route. In riscv-isa-sim at
17e8a69fb755, state_t::reset() calls csr_init() (riscv/processor.cc:132),
which reallocates mseccfg and every pmpaddr and pmpcfg object
(riscv/csr_init.cc:328 and 336-342); base_pmpaddr_csr_t's constructor starts
from val(0), cfg(0) (riscv/csrs.cc:88-93), and processor_t::reset() then
flushes the TLB (riscv/processor.cc:159). It also keeps no derived PMP state
that could go stale: state_t holds only the pmpaddr CSR objects
(riscv/processor.h:174) and recomputes the range bounds on every check
(riscv/csrs.cc:103-116), so there is no counterpart to pmp_state.addr[] or
num_rules; num_rules does not occur anywhere under riscv/. Its scan is
bounded by the number of implemented entries (mmu_t::pmp_ok calls
pmp_lookup(addr, len, 0, proc->n_pmp), riscv/mmu.cc:519), and the A field is
tested before any address comparison (base_pmpaddr_csr_t::match4,
riscv/csrs.cc:124).

Clearing the whole struct gives QEMU the same reset state, and removes the
possibility of the architectural fields and the derived bounds disagreeing.

If you would prefer the bound fix and the state clearing as two separate
patches, I can respin it that way.

El sáb, 10 oct 2026 a las 22:06, Sebastian Alba Vives (<
sebasjosue84@gmail.com>) escribió:

> pmp_unlock_entries() bounded its loop with pmp_get_num_rules(), which
> returns the number of entries whose A field is not OFF rather than the
> number of implemented entries. With a sparse configuration the loop
> stops early: with entries 0 and 1 OFF and entries 2 and 3 locked,
> num_rules is 2, the loop covers i = 0 and i = 1, and entries 2 and 3
> keep their lock and address-match bits across reset.
>
> riscv_cpu_reset_hold() clears mseccfg, and with it RLB, before calling
> this, so pmp_is_readonly() then silently discards every subsequent
> pmpcfg write to those entries. The stale rules survive for the
> lifetime of the machine.
>
> Clearing only PMP_LOCK and PMP_AMATCH is not enough either. The priv
> spec only mandates that the A and L fields be reset, but QEMU keeps
> derived state next to the architectural registers: pmp_state.addr[]
> and pmp_state.num_rules are recomputed from the pmpaddr and pmpcfg
> write paths only, never at reset. pmp_is_in_range() compares against
> addr[].sa and addr[].ea without consulting the A field, and the
> partially-inside check in pmp_hart_has_privs() runs before the A
> field is tested, so a range left over from before the reset can still
> deny an access that straddles its boundary once the guest configures
> any entry again.
>
> Zero the whole structure instead. Zero is the correct reset value for
> every field: a cfg_reg of 0 is A=OFF with no permissions, and a
> num_rules of 0 restores the no-rules short cut. Rename the function
> since it no longer only unlocks entries.
>
> Fixes: 4bf501dc0118 ("target/riscv: pmp: Clear pmp/smepmp bits on reset")
> Suggested-by: Richard Henderson <richard.henderson@linaro.org>
> Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
> ---
> Changes in v2:
> - Zero the whole env->pmp_state instead of masking PMP_LOCK and
>   PMP_AMATCH over a bounded set of entries, as suggested by Richard.
>   v1 also left pmp_state.addr[] and pmp_state.num_rules stale across
>   reset, which the memset covers as well.
>
> v1:
> https://lore.kernel.org/qemu-devel/20260916034331.2390427-1-sebasjosue84@gmail.com/
>
>  target/riscv/cpu.c     | 4 ++--
>  target/riscv/tcg/pmp.c | 9 ++-------
>  target/riscv/tcg/pmp.h | 2 +-
>  3 files changed, 5 insertions(+), 10 deletions(-)
>
> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> index da4c1a090b..0b71196507 100644
> --- a/target/riscv/cpu.c
> +++ b/target/riscv/cpu.c
> @@ -1048,7 +1048,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> ResetType type)
>      }
>
>      /*
> -     * Clear mseccfg and unlock all the PMP entries upon reset.
> +     * Clear mseccfg and the PMP state upon reset.
>       * This is required as per the priv, smepmp, and other security
>       * extension specifications that share this CSR, and is needed
>       * to clear stale entries across reboots.
> @@ -1060,7 +1060,7 @@ static void riscv_cpu_reset_hold(Object *obj,
> ResetType type)
>          env->mseccfg = 0;
>      }
>
> -    pmp_unlock_entries(env);
> +    pmp_reset(env);
>  #endif /* ifdef CONFIG_TCG */
>  #else
>      env->priv = PRV_U;
> diff --git a/target/riscv/tcg/pmp.c b/target/riscv/tcg/pmp.c
> index 41b55519a8..437e0ff2af 100644
> --- a/target/riscv/tcg/pmp.c
> +++ b/target/riscv/tcg/pmp.c
> @@ -199,14 +199,9 @@ static bool pmp_write_cfg(CPURISCVState *env,
> uint32_t pmp_index, uint8_t val)
>      return false;
>  }
>
> -void pmp_unlock_entries(CPURISCVState *env)
> +void pmp_reset(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);
> -    }
> +    memset(&env->pmp_state, 0, sizeof(env->pmp_state));
>  }
>
>  static void pmp_decode_napot(hwaddr a, hwaddr *sa, hwaddr *ea)
> diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h
> index 4c95c2767a..99546934fc 100644
> --- a/target/riscv/tcg/pmp.h
> +++ b/target/riscv/tcg/pmp.h
> @@ -79,7 +79,7 @@ void pmp_update_rule_addr(CPURISCVState *env, uint32_t
> pmp_index);
>  void pmp_update_rule_nums(CPURISCVState *env);
>  uint32_t pmp_get_num_rules(CPURISCVState *env);
>  int pmp_priv_to_page_prot(pmp_priv_t pmp_priv);
> -void pmp_unlock_entries(CPURISCVState *env);
> +void pmp_reset(CPURISCVState *env);
>
>  #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML)
>  #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg, MSECCFG_MMWP)
> --
> 2.43.0
>
>

-- 
Sebastián Alba