* [PATCH 1/5] target/arm/cpu-features.h: x-rme now means GPC3
2026-05-28 15:34 [PATCH 0/5] target/arm: Add aarch64 GPC3 bypass windows Jim MacArthur
@ 2026-05-28 15:34 ` Jim MacArthur
2026-05-28 18:03 ` Richard Henderson
2026-05-28 15:34 ` [PATCH 2/5] target/arm: Setup new registers for GPC3 Jim MacArthur
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Jim MacArthur @ 2026-05-28 15:34 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Maydell, qemu-arm, Jim MacArthur
Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
---
target/arm/cpu-features.h | 5 +++++
target/arm/tcg/cpu64.c | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
index f9c979d20b..6819d5f6a7 100644
--- a/target/arm/cpu-features.h
+++ b/target/arm/cpu-features.h
@@ -1138,6 +1138,11 @@ static inline bool isar_feature_aa64_rme_gpc2(const ARMISARegisters *id)
return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 2;
}
+static inline bool isar_feature_aa64_rme_gpc3(const ARMISARegisters *id)
+{
+ return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 3;
+}
+
static inline bool isar_feature_aa64_dit(const ARMISARegisters *id)
{
return FIELD_EX64_IDREG(id, ID_AA64PFR0, DIT) != 0;
diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
index a377f67b9c..53f0ea0404 100644
--- a/target/arm/tcg/cpu64.c
+++ b/target/arm/tcg/cpu64.c
@@ -159,8 +159,8 @@ static void cpu_arm_set_rme(Object *obj, bool value, Error **errp)
{
ARMCPU *cpu = ARM_CPU(obj);
- /* Enable FEAT_RME_GPC2 */
- FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 2 : 0);
+ /* Enable FEAT_RME_GPC3 */
+ FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 3 : 0);
}
static void cpu_max_set_l0gptsz(Object *obj, Visitor *v, const char *name,
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH 1/5] target/arm/cpu-features.h: x-rme now means GPC3
2026-05-28 15:34 ` [PATCH 1/5] target/arm/cpu-features.h: x-rme now means GPC3 Jim MacArthur
@ 2026-05-28 18:03 ` Richard Henderson
0 siblings, 0 replies; 12+ messages in thread
From: Richard Henderson @ 2026-05-28 18:03 UTC (permalink / raw)
To: qemu-devel
On 5/28/26 08:34, Jim MacArthur wrote:
> Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
> ---
> target/arm/cpu-features.h | 5 +++++
> target/arm/tcg/cpu64.c | 4 ++--
> 2 files changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
> index f9c979d20b..6819d5f6a7 100644
> --- a/target/arm/cpu-features.h
> +++ b/target/arm/cpu-features.h
> @@ -1138,6 +1138,11 @@ static inline bool isar_feature_aa64_rme_gpc2(const ARMISARegisters *id)
> return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 2;
> }
>
> +static inline bool isar_feature_aa64_rme_gpc3(const ARMISARegisters *id)
> +{
> + return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 3;
> +}
> +
> static inline bool isar_feature_aa64_dit(const ARMISARegisters *id)
> {
> return FIELD_EX64_IDREG(id, ID_AA64PFR0, DIT) != 0;
This hunk is ok.
> diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
> index a377f67b9c..53f0ea0404 100644
> --- a/target/arm/tcg/cpu64.c
> +++ b/target/arm/tcg/cpu64.c
> @@ -159,8 +159,8 @@ static void cpu_arm_set_rme(Object *obj, bool value, Error **errp)
> {
> ARMCPU *cpu = ARM_CPU(obj);
>
> - /* Enable FEAT_RME_GPC2 */
> - FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 2 : 0);
> + /* Enable FEAT_RME_GPC3 */
> + FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 3 : 0);
> }
This hunk has to go last, once the implementation is complete.
r~
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/5] target/arm: Setup new registers for GPC3
2026-05-28 15:34 [PATCH 0/5] target/arm: Add aarch64 GPC3 bypass windows Jim MacArthur
2026-05-28 15:34 ` [PATCH 1/5] target/arm/cpu-features.h: x-rme now means GPC3 Jim MacArthur
@ 2026-05-28 15:34 ` Jim MacArthur
2026-05-28 18:14 ` Richard Henderson
2026-05-28 18:25 ` Richard Henderson
2026-05-28 15:34 ` [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows Jim MacArthur
` (2 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: Jim MacArthur @ 2026-05-28 15:34 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Maydell, qemu-arm, Jim MacArthur
Adds GPCBW_EL3. A custom write function is necessary to flush TLBs
when this register is written. Also allows write to the GPCBW bit of
GPCCR_EL3.
Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
---
target/arm/cpu.h | 7 +++++++
target/arm/helper.c | 19 +++++++++++++++++++
2 files changed, 26 insertions(+)
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 85552b573c..9944dc9727 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -546,6 +546,7 @@ typedef struct CPUArchState {
/* RME registers */
uint64_t gpccr_el3;
uint64_t gptbr_el3;
+ uint64_t gpcbw_el3;
uint64_t mfar_el3;
/* NV2 register */
@@ -1228,6 +1229,8 @@ void arm_v7m_cpu_do_interrupt(CPUState *cpu);
typedef struct ARMGranuleProtectionConfig {
/* GPCCR_EL3 */
uint64_t gpccr;
+ /* GPCBW_EL3 */
+ uint64_t gpcbw;
/* GPTBR_EL3 */
uint64_t gptbr;
/* ID_AA64MMFR0_EL1.PARange */
@@ -2098,6 +2101,10 @@ FIELD(GPCCR, NA6, 27, 1)
FIELD(GPCCR, NA7, 28, 1)
FIELD(GPCCR, GPCBW, 29, 1)
+FIELD(GPCBW, BWSIZE, 37, 2)
+FIELD(GPCBW, BWSTRIDE, 32, 5)
+FIELD(GPCBW, BWADDR, 0, 25)
+
FIELD(MFAR, FPA, 12, 40)
FIELD(MFAR, NSE, 62, 1)
FIELD(MFAR, NS, 63, 1)
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 34487eeaa3..12453fe39f 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -4990,6 +4990,10 @@ static void gpccr_write(CPUARMState *env, const ARMCPRegInfo *ri,
R_GPCCR_SPAD_MASK | R_GPCCR_NSPAD_MASK | R_GPCCR_RLPAD_MASK;
}
+ if (cpu_isar_feature(aa64_rme_gpc3 , env_archcpu(env))) {
+ rw_mask |= R_GPCCR_GPCBW_MASK;
+ }
+
env->cp15.gpccr_el3 = (value & rw_mask) | (env->cp15.gpccr_el3 & ~rw_mask);
}
@@ -4999,11 +5003,26 @@ static void gpccr_reset(CPUARMState *env, const ARMCPRegInfo *ri)
env_archcpu(env)->reset_l0gptsz);
}
+static void gpcbw_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ uint64_t rw_mask = R_GPCBW_BWADDR_MASK | R_GPCBW_BWSTRIDE_MASK |
+ R_GPCBW_BWSIZE_MASK;
+ ARMCPU *cpu = env_archcpu(env);
+ tlb_flush_by_mmuidx(CPU(cpu), alle1_tlbmask(env));
+
+ env->cp15.gpcbw_el3 = (value & rw_mask) | (env->cp15.gpcbw_el3 & ~rw_mask);
+}
+
static const ARMCPRegInfo rme_reginfo[] = {
{ .name = "GPCCR_EL3", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 6,
.access = PL3_RW, .writefn = gpccr_write, .resetfn = gpccr_reset,
.fieldoffset = offsetof(CPUARMState, cp15.gpccr_el3) },
+ { .name = "GPCBW_EL3", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 5,
+ .access = PL3_RW, .writefn = gpcbw_write,
+ .fieldoffset = offsetof(CPUARMState, cp15.gpcbw_el3) },
{ .name = "GPTBR_EL3", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 4,
.access = PL3_RW, .fieldoffset = offsetof(CPUARMState, cp15.gptbr_el3) },
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH 2/5] target/arm: Setup new registers for GPC3
2026-05-28 15:34 ` [PATCH 2/5] target/arm: Setup new registers for GPC3 Jim MacArthur
@ 2026-05-28 18:14 ` Richard Henderson
2026-05-28 18:25 ` Richard Henderson
1 sibling, 0 replies; 12+ messages in thread
From: Richard Henderson @ 2026-05-28 18:14 UTC (permalink / raw)
To: qemu-devel
On 5/28/26 08:34, Jim MacArthur wrote:
> Adds GPCBW_EL3. A custom write function is necessary to flush TLBs
> when this register is written. Also allows write to the GPCBW bit of
> GPCCR_EL3.
>
> Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
> ---
> target/arm/cpu.h | 7 +++++++
> target/arm/helper.c | 19 +++++++++++++++++++
> 2 files changed, 26 insertions(+)
>
> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
> index 85552b573c..9944dc9727 100644
> --- a/target/arm/cpu.h
> +++ b/target/arm/cpu.h
> @@ -546,6 +546,7 @@ typedef struct CPUArchState {
> /* RME registers */
> uint64_t gpccr_el3;
> uint64_t gptbr_el3;
> + uint64_t gpcbw_el3;
> uint64_t mfar_el3;
>
> /* NV2 register */
> @@ -1228,6 +1229,8 @@ void arm_v7m_cpu_do_interrupt(CPUState *cpu);
> typedef struct ARMGranuleProtectionConfig {
> /* GPCCR_EL3 */
> uint64_t gpccr;
> + /* GPCBW_EL3 */
> + uint64_t gpcbw;
> /* GPTBR_EL3 */
> uint64_t gptbr;
> /* ID_AA64MMFR0_EL1.PARange */
> @@ -2098,6 +2101,10 @@ FIELD(GPCCR, NA6, 27, 1)
> FIELD(GPCCR, NA7, 28, 1)
> FIELD(GPCCR, GPCBW, 29, 1)
>
> +FIELD(GPCBW, BWSIZE, 37, 2)
> +FIELD(GPCBW, BWSTRIDE, 32, 5)
> +FIELD(GPCBW, BWADDR, 0, 25)
> +
> FIELD(MFAR, FPA, 12, 40)
> FIELD(MFAR, NSE, 62, 1)
> FIELD(MFAR, NS, 63, 1)
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index 34487eeaa3..12453fe39f 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -4990,6 +4990,10 @@ static void gpccr_write(CPUARMState *env, const ARMCPRegInfo *ri,
> R_GPCCR_SPAD_MASK | R_GPCCR_NSPAD_MASK | R_GPCCR_RLPAD_MASK;
> }
>
> + if (cpu_isar_feature(aa64_rme_gpc3 , env_archcpu(env))) {
> + rw_mask |= R_GPCCR_GPCBW_MASK;
> + }
> +
> env->cp15.gpccr_el3 = (value & rw_mask) | (env->cp15.gpccr_el3 & ~rw_mask);
> }
>
> @@ -4999,11 +5003,26 @@ static void gpccr_reset(CPUARMState *env, const ARMCPRegInfo *ri)
> env_archcpu(env)->reset_l0gptsz);
> }
>
> +static void gpcbw_write(CPUARMState *env, const ARMCPRegInfo *ri,
> + uint64_t value)
> +{
> + uint64_t rw_mask = R_GPCBW_BWADDR_MASK | R_GPCBW_BWSTRIDE_MASK |
> + R_GPCBW_BWSIZE_MASK;
> + ARMCPU *cpu = env_archcpu(env);
> + tlb_flush_by_mmuidx(CPU(cpu), alle1_tlbmask(env));
> +
> + env->cp15.gpcbw_el3 = (value & rw_mask) | (env->cp15.gpcbw_el3 & ~rw_mask);
There's no need to merge, because all other fields are RES0.
Just value & rw_mask is sufficient.
This is true for GPCCR too, fwiw, but fixing that as a separate patch.
r~
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH 2/5] target/arm: Setup new registers for GPC3
2026-05-28 15:34 ` [PATCH 2/5] target/arm: Setup new registers for GPC3 Jim MacArthur
2026-05-28 18:14 ` Richard Henderson
@ 2026-05-28 18:25 ` Richard Henderson
1 sibling, 0 replies; 12+ messages in thread
From: Richard Henderson @ 2026-05-28 18:25 UTC (permalink / raw)
To: qemu-devel
On 5/28/26 08:34, Jim MacArthur wrote:
> +static void gpcbw_write(CPUARMState *env, const ARMCPRegInfo *ri,
> + uint64_t value)
> +{
> + uint64_t rw_mask = R_GPCBW_BWADDR_MASK | R_GPCBW_BWSTRIDE_MASK |
> + R_GPCBW_BWSIZE_MASK;
> + ARMCPU *cpu = env_archcpu(env);
> + tlb_flush_by_mmuidx(CPU(cpu), alle1_tlbmask(env));
The GPT affects all EL, so this must flush all tlb, not just EL1.
r~
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows
2026-05-28 15:34 [PATCH 0/5] target/arm: Add aarch64 GPC3 bypass windows Jim MacArthur
2026-05-28 15:34 ` [PATCH 1/5] target/arm/cpu-features.h: x-rme now means GPC3 Jim MacArthur
2026-05-28 15:34 ` [PATCH 2/5] target/arm: Setup new registers for GPC3 Jim MacArthur
@ 2026-05-28 15:34 ` Jim MacArthur
2026-05-28 18:52 ` Richard Henderson
2026-05-28 15:34 ` [PATCH 4/5] tests/tcg/aarch64/system: Alternative boot object for exception logging Jim MacArthur
2026-05-28 15:34 ` [PATCH 5/5] tests/tcg/aarch64/system/gpc-test.c: Basic test for granule protection check Jim MacArthur
4 siblings, 1 reply; 12+ messages in thread
From: Jim MacArthur @ 2026-05-28 15:34 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Maydell, qemu-arm, Jim MacArthur
Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
---
target/arm/ptw.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index 0a5201763a..46568cc5cb 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -341,11 +341,21 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
.space = ARMSS_Root,
};
const uint64_t gpccr = config.gpccr;
+ const uint64_t gpcbw = config.gpcbw;
unsigned pps, pgs, l0gptsz, level = 0;
uint64_t tableaddr, pps_mask, align, entry, index;
MemTxResult result;
int gpi;
+ const uint64_t BW_ADDR_SHIFT = 30;
+ const uint64_t BW_SIZE_SHIFT = 30;
+ const uint64_t BW_STRIDE_SHIFT = 40;
+
+ uint64_t bw_start = FIELD_EX64(gpcbw, GPCBW, BWADDR) << BW_ADDR_SHIFT;
+ uint64_t bw_size_field = FIELD_EX64(gpcbw, GPCBW, BWSIZE);
+ uint64_t bw_stride_mask = -1L << (FIELD_EX64(gpcbw, GPCBW, BWSTRIDE) +
+ BW_STRIDE_SHIFT + 1);
+
/*
* We assume Granule Protection Check is enabled when
* calling this function (GPCCR.GPC == 1).
@@ -383,6 +393,28 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
goto fault_walk;
}
+ /* At this point, GPCCR_EL3 is valid */
+
+ /*
+ * GPC Priority 1 (R_GMGRR):
+ * If GPCCR_EL3.GPCBW is 1 and the configuration GPCBW
+ * is invalid, the access fails as GPT walk fault at level 0.
+ */
+ if (FIELD_EX64(gpccr, GPCCR, GPCBW)) {
+ /*
+ * GPCBW is invalid if the base address is:
+ * not aligned to the size programmed in BWSIZE, or
+ * greater than or equal to the stride value configured by BWSTRIDE.
+ */
+ uint64_t bw_size_mask = -1L << (bw_size_field + 31);
+ if (bw_start & bw_size_mask) {
+ goto fault_walk;
+ }
+ if (bw_start & bw_stride_mask) {
+ goto fault_walk;
+ }
+ }
+
switch (FIELD_EX64(gpccr, GPCCR, PGS)) {
case 0b00: /* 4KB */
pgs = 12;
@@ -431,6 +463,23 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
goto fault_fail;
}
+ /*
+ * Bypass window check.
+ * I_JJLRM: Granule Protection Table (GPT) lookups can be skipped
+ * in portions of the memory map by using GPC bypass windows.
+ * I_XNHTX: The GPC bypass window check (...) is performed
+ * immediately after priority 3.
+ */
+ if (FIELD_EX64(gpccr, GPCCR, GPCBW)) {
+ uint64_t bw_size = 1 << (bw_size_field + BW_SIZE_SHIFT);
+ uint64_t effective_address = paddress & bw_stride_mask;
+
+ if (effective_address >= bw_start &&
+ effective_address < (bw_start + bw_size)) {
+ return true;
+ }
+ }
+
/* GPC Priority 4: the base address of GPTBR_EL3 exceeds PPS. */
tableaddr = config.gptbr << 12;
if (tableaddr & ~pps_mask) {
@@ -3817,6 +3866,7 @@ static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw,
};
struct ARMGranuleProtectionConfig config = {
.gpccr = env->cp15.gpccr_el3,
+ .gpcbw = env->cp15.gpcbw_el3,
.gptbr = env->cp15.gptbr_el3,
.parange = FIELD_EX64_IDREG(&cpu->isar, ID_AA64MMFR0, PARANGE),
.support_sel2 = cpu_isar_feature(aa64_sel2, cpu),
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows
2026-05-28 15:34 ` [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows Jim MacArthur
@ 2026-05-28 18:52 ` Richard Henderson
2026-05-29 16:44 ` Jim MacArthur
0 siblings, 1 reply; 12+ messages in thread
From: Richard Henderson @ 2026-05-28 18:52 UTC (permalink / raw)
To: Jim MacArthur, qemu-devel; +Cc: Peter Maydell, qemu-arm
On 5/28/26 08:34, Jim MacArthur wrote:
> Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
> ---
> target/arm/ptw.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
>
> diff --git a/target/arm/ptw.c b/target/arm/ptw.c
> index 0a5201763a..46568cc5cb 100644
> --- a/target/arm/ptw.c
> +++ b/target/arm/ptw.c
> @@ -341,11 +341,21 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
> .space = ARMSS_Root,
> };
> const uint64_t gpccr = config.gpccr;
> + const uint64_t gpcbw = config.gpcbw;
> unsigned pps, pgs, l0gptsz, level = 0;
> uint64_t tableaddr, pps_mask, align, entry, index;
> MemTxResult result;
> int gpi;
>
> + const uint64_t BW_ADDR_SHIFT = 30;
> + const uint64_t BW_SIZE_SHIFT = 30;
> + const uint64_t BW_STRIDE_SHIFT = 40;
> +
> + uint64_t bw_start = FIELD_EX64(gpcbw, GPCBW, BWADDR) << BW_ADDR_SHIFT;
> + uint64_t bw_size_field = FIELD_EX64(gpcbw, GPCBW, BWSIZE);
> + uint64_t bw_stride_mask = -1L << (FIELD_EX64(gpcbw, GPCBW, BWSTRIDE) +
> + BW_STRIDE_SHIFT + 1);
This appears to be the inverse of what you wanted, setting high bits instead of setting
low bits.
Never "L", always LL or ULL; but here you certainly want MAKE_64BIT_MASK.
> +
> /*
> * We assume Granule Protection Check is enabled when
> * calling this function (GPCCR.GPC == 1).
> @@ -383,6 +393,28 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
> goto fault_walk;
> }
>
> + /* At this point, GPCCR_EL3 is valid */
Not true, because you added the new block before validating PGS...
> +
> + /*
> + * GPC Priority 1 (R_GMGRR):
> + * If GPCCR_EL3.GPCBW is 1 and the configuration GPCBW
> + * is invalid, the access fails as GPT walk fault at level 0.
> + */
> + if (FIELD_EX64(gpccr, GPCCR, GPCBW)) {
> + /*
> + * GPCBW is invalid if the base address is:
> + * not aligned to the size programmed in BWSIZE, or
> + * greater than or equal to the stride value configured by BWSTRIDE.
> + */
> + uint64_t bw_size_mask = -1L << (bw_size_field + 31);
> + if (bw_start & bw_size_mask) {
> + goto fault_walk;
> + }
> + if (bw_start & bw_stride_mask) {
> + goto fault_walk;
> + }
> + }
> +
> switch (FIELD_EX64(gpccr, GPCCR, PGS)) {
... here.
None of the checks you're adding are correct:
- Missing size and stride validation,
- Alignment check vs size is incorrect; you wanted
bw_start & MAKE_64BIT_MASK(0, bw_size + BW_ADDR_SHIFT)
- Check vs stride is incorrect; you wanted bw_stride <= bw_addr.
See GPCRegistersConsistent().
> @@ -431,6 +463,23 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config,
> goto fault_fail;
> }
>
> + /*
> + * Bypass window check.
> + * I_JJLRM: Granule Protection Table (GPT) lookups can be skipped
> + * in portions of the memory map by using GPC bypass windows.
> + * I_XNHTX: The GPC bypass window check (...) is performed
> + * immediately after priority 3.
> + */
> + if (FIELD_EX64(gpccr, GPCCR, GPCBW)) {
> + uint64_t bw_size = 1 << (bw_size_field + BW_SIZE_SHIFT);
> + uint64_t effective_address = paddress & bw_stride_mask;
> +
> + if (effective_address >= bw_start &&
> + effective_address < (bw_start + bw_size)) {
No need for extra ().
r~
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows
2026-05-28 18:52 ` Richard Henderson
@ 2026-05-29 16:44 ` Jim MacArthur
2026-05-29 17:11 ` Richard Henderson
0 siblings, 1 reply; 12+ messages in thread
From: Jim MacArthur @ 2026-05-29 16:44 UTC (permalink / raw)
To: Richard Henderson; +Cc: qemu-arm, qemu-devel
On Thu, May 28, 2026 at 11:52:09AM -0700, Richard Henderson wrote:
> On 5/28/26 08:34, Jim MacArthur wrote:
> > + /*
> > + * GPC Priority 1 (R_GMGRR):
> > + * If GPCCR_EL3.GPCBW is 1 and the configuration GPCBW
> > + * is invalid, the access fails as GPT walk fault at level 0.
> > + */
> > + if (FIELD_EX64(gpccr, GPCCR, GPCBW)) {
> > + /*
> > + * GPCBW is invalid if the base address is:
> > + * not aligned to the size programmed in BWSIZE, or
> > + * greater than or equal to the stride value configured by BWSTRIDE.
> > + */
> > + uint64_t bw_size_mask = -1L << (bw_size_field + 31);
> > + if (bw_start & bw_size_mask) {
> > + goto fault_walk;
> > + }
> > + if (bw_start & bw_stride_mask) {
> > + goto fault_walk;
> > + }
> > + }
> > +
> > switch (FIELD_EX64(gpccr, GPCCR, PGS)) {
>
> ... here.
>
> None of the checks you're adding are correct:
> - Missing size and stride validation,
> - Alignment check vs size is incorrect; you wanted
>
> bw_start & MAKE_64BIT_MASK(0, bw_size + BW_ADDR_SHIFT)
>
> - Check vs stride is incorrect; you wanted bw_stride <= bw_addr.
>
> See GPCRegistersConsistent().
Thanks for the review Richard; the bw_size_mask check was indeed wrong.
On bw_stride_mask: it was intended to keep the high bits in bw_stride_mask
hence this bitwise comparison should have been equivalent to
bw_stride <= bw_addr; in fact the other use of bw_stride_mask was incorrect.
However, since both Arm and Intel GCC produce roughly the same code for both
I will replace it with the more readable arithmetic operation.
On size and stride validation: there are reserved values for these fields,
but the ARM does not say explicitly (that I can see) that they make GPCBW
/invalid/, so it wasn't clear to me that we should fault on these.
Jim
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 4/5] tests/tcg/aarch64/system: Alternative boot object for exception logging
2026-05-28 15:34 [PATCH 0/5] target/arm: Add aarch64 GPC3 bypass windows Jim MacArthur
` (2 preceding siblings ...)
2026-05-28 15:34 ` [PATCH 3/5] target/arm/ptw.c: Add Granule Bypass Windows Jim MacArthur
@ 2026-05-28 15:34 ` Jim MacArthur
2026-05-28 15:34 ` [PATCH 5/5] tests/tcg/aarch64/system/gpc-test.c: Basic test for granule protection check Jim MacArthur
4 siblings, 0 replies; 12+ messages in thread
From: Jim MacArthur @ 2026-05-28 15:34 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Maydell, qemu-arm, Jim MacArthur
This allows us to record information about exceptions using a small
area of memory, and continue with the test so it can verify exceptions
have been taken where expected.
ALT_VECTOR_TABLE is added to switch this on, and altboot.o
is built from boot.S with this flag. Since boot.o is created for multiple
test targets, we have to build a separate object and selectively link this
new altboot.o into particular test binaries.
Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
---
tests/tcg/aarch64/Makefile.softmmu-target | 11 ++++--
tests/tcg/aarch64/system/boot.S | 62 +++++++++++++++++++++++++++++++
tests/tcg/aarch64/system/boot.h | 14 +++++++
3 files changed, 83 insertions(+), 4 deletions(-)
diff --git a/tests/tcg/aarch64/Makefile.softmmu-target b/tests/tcg/aarch64/Makefile.softmmu-target
index f7a7d2b800..47b32968fb 100644
--- a/tests/tcg/aarch64/Makefile.softmmu-target
+++ b/tests/tcg/aarch64/Makefile.softmmu-target
@@ -25,7 +25,7 @@ LDFLAGS=-Wl,-T$(LINK_SCRIPT)
TESTS+=$(AARCH64_TESTS) $(MULTIARCH_TESTS)
EXTRA_RUNS+=$(MULTIARCH_RUNS)
CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
-LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc
+LDFLAGS+=-static -nostdlib $(MINILIB_OBJS) -lgcc
config-cc.mak: Makefile
$(quiet-@)( \
@@ -36,17 +36,20 @@ config-cc.mak: Makefile
# building head blobs
.PRECIOUS: $(CRT_OBJS)
+altboot.o: $(CRT_PATH)/boot.S
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -DALT_VECTOR_TABLE -x assembler-with-cpp -Wa,--noexecstack -c $< -o $@
+
%.o: $(CRT_PATH)/%.S
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -x assembler-with-cpp -Wa,--noexecstack -c $< -o $@
# Build and link the tests
%: %.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
- $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o
memory: CFLAGS+=-DCHECK_UNALIGNED=1
memory-sve: memory.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
- $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o
memory-sve: CFLAGS+=-DCHECK_UNALIGNED=1 -march=armv8.1-a+sve -O3
@@ -107,7 +110,7 @@ QEMU_MTE_ENABLED_MACHINE=-M virt,mte=on -cpu max -display none
QEMU_OPTS_WITH_MTE_ON = $(QEMU_MTE_ENABLED_MACHINE) $(QEMU_BASE_ARGS) -kernel
mte: CFLAGS+=-march=armv8.5-a+memtag
mte: mte.S $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
- $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o
run-mte: QEMU_OPTS=$(QEMU_OPTS_WITH_MTE_ON)
run-mte: mte
diff --git a/tests/tcg/aarch64/system/boot.S b/tests/tcg/aarch64/system/boot.S
index 8bfa4e4efc..225ae76388 100644
--- a/tests/tcg/aarch64/system/boot.S
+++ b/tests/tcg/aarch64/system/boot.S
@@ -60,6 +60,40 @@ curr_sp0_irq:
curr_sp0_fiq:
curr_sp0_serror:
curr_spx_sync:
+#ifdef ALT_VECTOR_TABLE
+ sub sp, sp, #16
+ stp x0, x1, [sp, #0]
+ mrs x0, ESR_EL3
+ lsr x0, x0, #26
+ and x0, x0, #0x3f
+ cmp x0, #37
+ beq data_fault
+ cmp x0, #30
+ beq gpc_fault
+ b generic_exception
+
+data_fault:
+ mrs x0, FAR_EL3
+ adrp x1, exception_log
+ str x0, [x1]
+ ldr x0, =0x1001
+ str x0, [x1, #8]
+ b skip_return
+gpc_fault:
+ mrs x0, FAR_EL3
+ adrp x1, exception_log
+ str x0, [x1]
+ ldr x0, =0x1002
+ str x0, [x1, #8]
+ /* Fall through */
+skip_return:
+ mrs x0, ELR_EL3
+ add x0, x0, #4 /* Skip faulting instruction */
+ msr ELR_EL3, x0
+ ldp x0, x1, [sp, #0]
+ add sp, sp, #16
+ eret
+#endif
curr_spx_irq:
curr_spx_fiq:
curr_spx_serror:
@@ -71,6 +105,7 @@ lower_a32_sync:
lower_a32_irq:
lower_a32_fiq:
lower_a32_serror:
+generic_exception:
adr x1, .unexp_excp
exit_msg:
mov x0, SYS_WRITE0
@@ -404,6 +439,33 @@ ttb_stage2:
.space 4096, 0
.align 12
+ .global realms_gpt0
+ /* GPT stage 0 table */
+realms_gpt0:
+ .space 4096, 0
+ .align 17
+ .global realms_gpt1
+ /* GPT stage 1 table, initialised to all 0xFF (full access) */
+realms_gpt1:
+ .space 524288, 0xFF
+#ifdef ALT_VECTOR_TABLE
+ .align 8
+ .global exception_log
+ .global exception_fault_address
+ .global exception_type_code
+exception_log:
+ /*
+ *These fields record details of the last exception, if ALT_VECTOR_TABLE
+ * is defined.
+ */
+exception_fault_address:
+ /* The contents of FAR_EL3 when an exception is taken. */
+ .space 8, 0
+exception_type_code:
+ /* A generic code indicating what type of exception occurred. */
+ .space 8, 0
+#endif
+ .align 12
system_stack:
.space 4096, 0
system_stack_end:
diff --git a/tests/tcg/aarch64/system/boot.h b/tests/tcg/aarch64/system/boot.h
new file mode 100644
index 0000000000..cb9ab6c1b9
--- /dev/null
+++ b/tests/tcg/aarch64/system/boot.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *
+ * Copyright (c) 2026 Linaro Ltd
+ *
+ */
+
+
+/* Global variables exported in boot.S */
+extern volatile uint64_t exception_fault_address; /* Updated by ISR */
+extern volatile uint64_t exception_type_code; /* Updated by ISR */
+extern uint64_t realms_gpt0[];
+extern uint64_t realms_gpt1[];
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 5/5] tests/tcg/aarch64/system/gpc-test.c: Basic test for granule protection check
2026-05-28 15:34 [PATCH 0/5] target/arm: Add aarch64 GPC3 bypass windows Jim MacArthur
` (3 preceding siblings ...)
2026-05-28 15:34 ` [PATCH 4/5] tests/tcg/aarch64/system: Alternative boot object for exception logging Jim MacArthur
@ 2026-05-28 15:34 ` Jim MacArthur
4 siblings, 0 replies; 12+ messages in thread
From: Jim MacArthur @ 2026-05-28 15:34 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Maydell, qemu-arm, Jim MacArthur
* Sets up granule protection tables
* Enables GPC and bypass windows
* Performs memory accesses in the protected region to
check for allowed and disallowed reads.
Signed-off-by: Jim MacArthur <jim.macarthur@linaro.org>
---
tests/tcg/aarch64/Makefile.softmmu-target | 11 +-
tests/tcg/aarch64/system/gpc-test.c | 160 ++++++++++++++++++++++++++++++
2 files changed, 170 insertions(+), 1 deletion(-)
diff --git a/tests/tcg/aarch64/Makefile.softmmu-target b/tests/tcg/aarch64/Makefile.softmmu-target
index 47b32968fb..d76ca52e63 100644
--- a/tests/tcg/aarch64/Makefile.softmmu-target
+++ b/tests/tcg/aarch64/Makefile.softmmu-target
@@ -53,7 +53,10 @@ memory-sve: memory.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
memory-sve: CFLAGS+=-DCHECK_UNALIGNED=1 -march=armv8.1-a+sve -O3
-TESTS+=memory-sve
+gpc-test: gpc-test.c $(LINK_SCRIPT) altboot.o $(MINILIB_OBJS)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) altboot.o
+
+TESTS+=memory-sve gpc-test
# Running
QEMU_BASE_MACHINE=-M virt -cpu max -display none
@@ -74,6 +77,12 @@ QEMU_EL2_MACHINE=-machine virt,virtualization=on,gic-version=2 -cpu cortex-a57 -
QEMU_EL2_BASE_ARGS=-semihosting-config enable=on,target=native,chardev=output,arg="2"
run-vtimer: QEMU_OPTS=$(QEMU_EL2_MACHINE) $(QEMU_EL2_BASE_ARGS) -kernel
+# gpc tests need EL3 and RME
+QEMU_EL3_MACHINE=-machine virt,virtualization=on,secure=on,gic-version=3 -cpu max,x-rme=on
+QEMU_EL3_BASE_ARGS=-semihosting-config enable=on,target=native,chardev=output,arg="3"
+run-gpc-test: QEMU_OPTS=$(QEMU_EL3_MACHINE) $(QEMU_EL3_BASE_ARGS) -kernel
+run-gpc3-test: QEMU_OPTS=$(QEMU_EL3_MACHINE) $(QEMU_EL3_BASE_ARGS) -kernel
+
# Simple Record/Replay Test
.PHONY: memory-record
run-memory-record: memory-record memory
diff --git a/tests/tcg/aarch64/system/gpc-test.c b/tests/tcg/aarch64/system/gpc-test.c
new file mode 100644
index 0000000000..06b25d2787
--- /dev/null
+++ b/tests/tcg/aarch64/system/gpc-test.c
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *
+ * Copyright (c) 2026 Linaro Ltd
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <minilib.h>
+#include "boot.h"
+
+#define ID_AA64PFR0_EL1 "S3_0_C0_C4_0"
+
+#define GPTBR_EL3 "S3_6_C2_C1_4"
+#define GPCBW_EL3 "S3_6_C2_C1_5"
+#define GPCCR_EL3 "S3_6_C2_C1_6"
+#define VBAR_EL3 "S3_6_C12_C0_0"
+
+#define get_sys_reg(register_name, dest) \
+ asm("mrs %[reg], " register_name "\n\t" : [reg] "=r" (dest))
+#define set_sys_reg(register_name, value) \
+ asm("msr " register_name ", %[reg]\n\r" : : [reg] "r" (value))
+
+const uint32_t gpc_granule_size = 4096;
+const uint32_t gpis_per_64_bits = 16;
+
+int main(uint64_t sp)
+{
+ uint64_t out;
+ uint64_t pfr0;
+ uint64_t gpt_base;
+ uint64_t rme_status;
+ uint64_t currentel_raw;
+ uint64_t currentel;
+ uint64_t gpcbw;
+ uint64_t gpt_table0_addr = (uint64_t) realms_gpt0;
+ uint64_t gpt_table1_addr = (uint64_t) realms_gpt1;
+
+ /* Mask is FNG1, FNG0, and A2 */
+ const uint64_t feature_mask = (1ULL << 18 | 1ULL << 17 | 1ULL << 16);
+ const uint64_t in = feature_mask;
+
+ get_sys_reg("CurrentEL", currentel_raw);
+ currentel = (currentel_raw >> 2) & 0x3;
+
+ if (currentel < 3) {
+ ml_printf("FAIL: Test must be run at EL3 (it is %d)\n", currentel);
+ return 1;
+ }
+
+ get_sys_reg(ID_AA64PFR0_EL1, pfr0);
+
+ /* rme_status is 1 for RME, 2 for RME + GPC2, 3 for RME+GPC3 */
+ rme_status = (pfr0 >> 52) & 0xF;
+ ml_printf("RME is %ld\n", rme_status);
+ if (rme_status < 1) {
+ ml_printf("SKIP: System does not support GPC (RME=%ld)\n", rme_status);
+ return 0;
+ }
+
+ /* Configure the level 0 table for the first 4GB of memory */
+ realms_gpt0[0] = gpt_table1_addr | 0x3; /* Covers GB 0; table descriptor */
+ realms_gpt0[1] = 0xf1; /* Covers GB 1; full access */
+ realms_gpt0[2] = 0xf1; /* Covers GB 2; full access */
+ realms_gpt0[3] = 0xf1; /* Covers GB 3; full access */
+
+ /* Pick a random location to read inside the first 1GB. */
+ uint64_t fault_location = 0x10202008;
+ uint32_t gpi_index = fault_location / gpc_granule_size;
+ realms_gpt1[gpi_index / gpis_per_64_bits] = 0;
+
+ gpt_base = gpt_table0_addr >> 12;
+ set_sys_reg(GPTBR_EL3, gpt_base);
+
+ /*
+ * Default values:
+ * PPS=0: GPC table 0 protects 4GB.
+ * RLPAD=0: Realm physical address spaces are normal
+ * NSPAD=0: Non-secure physical address spaces are normal
+ * SPAD=0: Secure physical address spaces are normal
+ * IRGN=0: Inner non-cacheable
+ * ORGN=0: Outer non-cacheable
+ * PGS=0: Physical granule size is 4KB.
+ * GPCP=0: All GPC faults reported
+ * TBGPCP=0: Trace buffer rejects trace
+ * L0GPTSZ=0: Each entry in table 0 protects 1GB.
+ * APPSAA=0: Accesses above 4GB must be to Non-secure PAs
+ * GPCBW=0: Bypass windows disabled.
+ * NA6, NA7, NSP, SA, NSO are all reserved values for GPI.
+ */
+ uint64_t gpccr = 0;
+
+ /* Switch on granule protection check */
+ gpccr |= 1 << 16; /* GPC enabled. */
+ gpccr |= 0b10 << 12; /* SH = Outer shareable */
+ set_sys_reg(GPCCR_EL3, gpccr);
+
+ /* Access some memory outside the GPC forbidden region */
+ uint64_t x = *(unsigned int *) (fault_location + 4096 * 16);
+ ml_printf("Fault address: %lx\n", exception_fault_address);
+ if (exception_fault_address != 0) {
+ ml_printf("FAIL: Memory access was blocked by GPC, "
+ "and should not have been\n");
+ return 1;
+ }
+
+ /* Access the GPC forbidden region */
+ x = *(unsigned int *) fault_location;
+
+ ml_printf("Fault address: %lx\n", exception_fault_address);
+ if (exception_fault_address != fault_location) {
+ ml_printf("FAIL: Memory access was not blocked by GPC, "
+ "and should have been\n");
+ return 1;
+ }
+
+ /* Clear the exception record */
+ exception_fault_address = 0;
+
+ /* Enable bypass windows */
+ gpccr |= 1 << 29; /* GPC Bypass windows enabled */
+ set_sys_reg(GPCCR_EL3, gpccr);
+
+ gpcbw = 0; /* Base 0GB, Size 1GB, Stride 1TB */
+ set_sys_reg(GPCBW_EL3, gpcbw);
+ ml_printf("GPCBW configured\n");
+
+ /* Access the GPC forbidden region again */
+ x = *(unsigned int *) fault_location;
+
+ ml_printf("Fault address: %lx\n", exception_fault_address);
+ if (exception_fault_address != 0) {
+ ml_printf("FAIL: [3] Memory access was blocked by GPC, "
+ "and should have been allowed by bypass window. code=%lx\n",
+ exception_type_code);
+ return 1;
+ }
+
+ /* Clear the exception record */
+ exception_fault_address = 0;
+ /* Reconfigure GPCBW to 1GB start */
+ gpcbw = 1; /* Base 1GB, Size 1GB, Stride 1TB */
+ set_sys_reg(GPCBW_EL3, gpcbw);
+ ml_printf("GPCBW reconfigured for 1GB start\n");
+
+ /* Access the GPC forbidden region again */
+ x = *(unsigned int *) fault_location;
+
+ ml_printf("Fault address: %lx\n", exception_fault_address);
+ if (exception_fault_address != fault_location) {
+ ml_printf("FAIL: [4] Memory access was allowed by GPC, "
+ "and should not have been allowed by bypass window. log=%lx\n",
+ exception_type_code);
+ return 1;
+ }
+
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread