diff mbox series

[3/6] target/arm: Honor TCR_ELx.{I}PS

Message ID 20211208231154.392029-4-richard.henderson@linaro.org
State New
Headers show
Series target/arm: Implement LVA, LPA, LPA2 features | expand

Commit Message

Richard Henderson Dec. 8, 2021, 11:11 p.m. UTC
This field controls the output (intermediate) physical address size
of the translation process.  V8 requires to raise an AddressSize
fault if the page tables are programmed incorrectly, such that any
intermediate descriptor address, or the final translated address,
is out of range.

Add an outputsize field to ARMVAParameters, and fill it in during
aa64_va_parameters.  Pass the value to check_s2_mmu_setup to use
instead of the raw PAMax value.  Test the descaddr as extracted
from TTBR and from page table entries.

Restrict descaddrmask so that we won't raise the fault for v7.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
---
 target/arm/internals.h |  1 +
 target/arm/helper.c    | 92 +++++++++++++++++++++++++++++-------------
 2 files changed, 65 insertions(+), 28 deletions(-)

Comments

Philippe Mathieu-Daudé Dec. 9, 2021, 7:43 a.m. UTC | #1
Hi Richard,

On 12/9/21 00:11, Richard Henderson wrote:
> This field controls the output (intermediate) physical address size
> of the translation process.  V8 requires to raise an AddressSize
> fault if the page tables are programmed incorrectly, such that any
> intermediate descriptor address, or the final translated address,
> is out of range.
> 

I'd split this patch as:

> Add an outputsize field to ARMVAParameters,
^ 1

> and fill it in during
> aa64_va_parameters.
^2

> Pass the value to check_s2_mmu_setup to use
> instead of the raw PAMax value.
^1

> Test the descaddr as extracted
> from TTBR and from page table entries.
^2

> Restrict descaddrmask so that we won't raise the fault for v7.
^ could be in 1 (simpler) or 2 if you think it makes sense.

This way #1 is a preliminary refactor/cleanup,
and #2 is only the ps field and V8 addition.

> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
> ---
>  target/arm/internals.h |  1 +
>  target/arm/helper.c    | 92 +++++++++++++++++++++++++++++-------------
>  2 files changed, 65 insertions(+), 28 deletions(-)
Alex Bennée Dec. 14, 2021, 2:47 p.m. UTC | #2
Richard Henderson <richard.henderson@linaro.org> writes:

> This field controls the output (intermediate) physical address size
> of the translation process.  V8 requires to raise an AddressSize
> fault if the page tables are programmed incorrectly, such that any
> intermediate descriptor address, or the final translated address,
> is out of range.
>
> Add an outputsize field to ARMVAParameters, and fill it in during
> aa64_va_parameters.  Pass the value to check_s2_mmu_setup to use
> instead of the raw PAMax value.  Test the descaddr as extracted
> from TTBR and from page table entries.
>
> Restrict descaddrmask so that we won't raise the fault for v7.
>
> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
> ---
>  target/arm/internals.h |  1 +
>  target/arm/helper.c    | 92 +++++++++++++++++++++++++++++-------------
>  2 files changed, 65 insertions(+), 28 deletions(-)
>
> diff --git a/target/arm/internals.h b/target/arm/internals.h
> index 27d2fcd26c..3e801833b4 100644
> --- a/target/arm/internals.h
> +++ b/target/arm/internals.h
> @@ -1032,6 +1032,7 @@ static inline uint32_t aarch64_pstate_valid_mask(const ARMISARegisters *id)
>   */
>  typedef struct ARMVAParameters {
>      unsigned tsz    : 8;
> +    unsigned ps     : 3;
>      unsigned select : 1;
>      bool tbi        : 1;
>      bool epd        : 1;
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index fab9ee70d8..568914bd42 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -11003,7 +11003,7 @@ do_fault:
>   * false otherwise.
>   */
>  static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
> -                               int inputsize, int stride)
> +                               int inputsize, int stride, int outputsize)
>  {
>      const int grainsize = stride + 3;
>      int startsizecheck;
> @@ -11019,22 +11019,19 @@ static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
>      }
>  
>      if (is_aa64) {
> -        CPUARMState *env = &cpu->env;
> -        unsigned int pamax = arm_pamax(cpu);
> -
>          switch (stride) {
>          case 13: /* 64KB Pages.  */
> -            if (level == 0 || (level == 1 && pamax <= 42)) {
> +            if (level == 0 || (level == 1 && outputsize <= 42)) {
>                  return false;
>              }
>              break;
>          case 11: /* 16KB Pages.  */
> -            if (level == 0 || (level == 1 && pamax <= 40)) {
> +            if (level == 0 || (level == 1 && outputsize <= 40)) {
>                  return false;
>              }
>              break;
>          case 9: /* 4KB Pages.  */
> -            if (level == 0 && pamax <= 42) {
> +            if (level == 0 && outputsize <= 42) {
>                  return false;
>              }
>              break;
> @@ -11043,8 +11040,8 @@ static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
>          }
>  
>          /* Inputsize checks.  */
> -        if (inputsize > pamax &&
> -            (arm_el_is_aa64(env, 1) || inputsize > 40)) {
> +        if (inputsize > outputsize &&
> +            (arm_el_is_aa64(&cpu->env, 1) || inputsize > 40)) {
>              /* This is CONSTRAINED UNPREDICTABLE and we choose to fault.  */
>              return false;
>          }
> @@ -11090,17 +11087,19 @@ static uint8_t convert_stage2_attrs(CPUARMState *env, uint8_t s2attrs)
>  }
>  #endif /* !CONFIG_USER_ONLY */
>  
> +/* This mapping is common between ID_AA64MMFR0.PARANGE and TCR_ELx.{I}PS. */
> +static const uint8_t pamax_map[] = {
> +    [0] = 32,
> +    [1] = 36,
> +    [2] = 40,
> +    [3] = 42,
> +    [4] = 44,
> +    [5] = 48,
> +};

I note that the IPS encoding includes b110 (6) for 52 bits or 4PB
addressing. I guess that gets introduced later.

Anyway:

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Peter Maydell Jan. 6, 2022, 8:08 p.m. UTC | #3
On Wed, 8 Dec 2021 at 23:18, Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> This field controls the output (intermediate) physical address size
> of the translation process.  V8 requires to raise an AddressSize
> fault if the page tables are programmed incorrectly, such that any
> intermediate descriptor address, or the final translated address,
> is out of range.
>
> Add an outputsize field to ARMVAParameters, and fill it in during
> aa64_va_parameters.  Pass the value to check_s2_mmu_setup to use
> instead of the raw PAMax value.  Test the descaddr as extracted
> from TTBR and from page table entries.
>
> Restrict descaddrmask so that we won't raise the fault for v7.
>
> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
> ---
>  target/arm/internals.h |  1 +
>  target/arm/helper.c    | 92 +++++++++++++++++++++++++++++-------------
>  2 files changed, 65 insertions(+), 28 deletions(-)
>
> diff --git a/target/arm/internals.h b/target/arm/internals.h
> index 27d2fcd26c..3e801833b4 100644
> --- a/target/arm/internals.h
> +++ b/target/arm/internals.h
> @@ -1032,6 +1032,7 @@ static inline uint32_t aarch64_pstate_valid_mask(const ARMISARegisters *id)
>   */
>  typedef struct ARMVAParameters {
>      unsigned tsz    : 8;
> +    unsigned ps     : 3;
>      unsigned select : 1;
>      bool tbi        : 1;
>      bool epd        : 1;
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index fab9ee70d8..568914bd42 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -11003,7 +11003,7 @@ do_fault:
>   * false otherwise.
>   */
>  static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
> -                               int inputsize, int stride)
> +                               int inputsize, int stride, int outputsize)
>  {
>      const int grainsize = stride + 3;
>      int startsizecheck;
> @@ -11019,22 +11019,19 @@ static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
>      }
>
>      if (is_aa64) {
> -        CPUARMState *env = &cpu->env;
> -        unsigned int pamax = arm_pamax(cpu);
> -
>          switch (stride) {
>          case 13: /* 64KB Pages.  */
> -            if (level == 0 || (level == 1 && pamax <= 42)) {
> +            if (level == 0 || (level == 1 && outputsize <= 42)) {
>                  return false;
>              }
>              break;
>          case 11: /* 16KB Pages.  */
> -            if (level == 0 || (level == 1 && pamax <= 40)) {
> +            if (level == 0 || (level == 1 && outputsize <= 40)) {
>                  return false;
>              }
>              break;
>          case 9: /* 4KB Pages.  */
> -            if (level == 0 && pamax <= 42) {
> +            if (level == 0 && outputsize <= 42) {
>                  return false;
>              }
>              break;
> @@ -11043,8 +11040,8 @@ static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
>          }
>
>          /* Inputsize checks.  */
> -        if (inputsize > pamax &&
> -            (arm_el_is_aa64(env, 1) || inputsize > 40)) {
> +        if (inputsize > outputsize &&
> +            (arm_el_is_aa64(&cpu->env, 1) || inputsize > 40)) {
>              /* This is CONSTRAINED UNPREDICTABLE and we choose to fault.  */
>              return false;
>          }
> @@ -11090,17 +11087,19 @@ static uint8_t convert_stage2_attrs(CPUARMState *env, uint8_t s2attrs)
>  }
>  #endif /* !CONFIG_USER_ONLY */
>
> +/* This mapping is common between ID_AA64MMFR0.PARANGE and TCR_ELx.{I}PS. */
> +static const uint8_t pamax_map[] = {
> +    [0] = 32,
> +    [1] = 36,
> +    [2] = 40,
> +    [3] = 42,
> +    [4] = 44,
> +    [5] = 48,
> +};
> +
>  /* The cpu-specific constant value of PAMax; also used by hw/arm/virt. */
>  unsigned int arm_pamax(ARMCPU *cpu)
>  {
> -    static const unsigned int pamax_map[] = {
> -        [0] = 32,
> -        [1] = 36,
> -        [2] = 40,
> -        [3] = 42,
> -        [4] = 44,
> -        [5] = 48,
> -    };
>      unsigned int parange =
>          FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
>
> @@ -11151,7 +11150,7 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
>  {
>      uint64_t tcr = regime_tcr(env, mmu_idx)->raw_tcr;
>      bool epd, hpd, using16k, using64k;
> -    int select, tsz, tbi;
> +    int select, tsz, tbi, ps;
>
>      if (!regime_has_2_ranges(mmu_idx)) {
>          select = 0;
> @@ -11165,6 +11164,7 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
>              hpd = extract32(tcr, 24, 1);
>          }
>          epd = false;
> +        ps = extract64(tcr, 16, 3);
>      } else {
>          /*
>           * Bit 55 is always between the two regions, and is canonical for
> @@ -11185,6 +11185,7 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
>              epd = extract32(tcr, 23, 1);
>              hpd = extract64(tcr, 42, 1);
>          }
> +        ps = extract64(tcr, 32, 3);
>      }
>
>      /* Present TBI as a composite with TBID.  */
> @@ -11196,6 +11197,7 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
>
>      return (ARMVAParameters) {
>          .tsz = tsz,
> +        .ps = ps,
>          .select = select,
>          .tbi = tbi,
>          .epd = epd,
> @@ -11312,7 +11314,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>      target_ulong page_size;
>      uint32_t attrs;
>      int32_t stride;
> -    int addrsize, inputsize;
> +    int addrsize, inputsize, outputsize;
>      TCR *tcr = regime_tcr(env, mmu_idx);
>      int ap, ns, xn, pxn;
>      uint32_t el = regime_el(env, mmu_idx);
> @@ -11323,6 +11325,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>      /* TODO: This code does not support shareability levels. */
>      if (aarch64) {
>          int min_tsz = 16, max_tsz = 39;  /* TODO: ARMv8.2-LVA  */
> +        int parange;
>
>          param = aa64_va_parameters(env, address, mmu_idx,
>                                     access_type != MMU_INST_FETCH);
> @@ -11348,11 +11351,22 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>
>          addrsize = 64 - 8 * param.tbi;
>          inputsize = 64 - param.tsz;
> +
> +        /*
> +         * Bound PS by PARANGE to find the effective output address size.
> +         * ID_AA64MMFR0 is a read-only register so values outside of the
> +         * supported mappings can be considered an implementation error.
> +         */
> +        parange = FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
> +        parange = MIN(parange, param.ps);
> +        assert(parange < ARRAY_SIZE(pamax_map));
> +        outputsize = pamax_map[parange];
>      } else {
>          param = aa32_va_parameters(env, address, mmu_idx);
>          level = 1;
>          addrsize = (mmu_idx == ARMMMUIdx_Stage2 ? 40 : 32);
>          inputsize = addrsize - param.tsz;
> +        outputsize = 40;
>      }
>
>      /*
> @@ -11437,7 +11451,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>
>          /* Check that the starting level is valid. */
>          ok = check_s2_mmu_setup(cpu, aarch64, startlevel,
> -                                inputsize, stride);
> +                                inputsize, stride, outputsize);
>          if (!ok) {
>              fault_type = ARMFault_Translation;
>              goto do_fault;
> @@ -11445,24 +11459,41 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>          level = startlevel;
>      }
>
> -    indexmask_grainsize = (1ULL << (stride + 3)) - 1;
> -    indexmask = (1ULL << (inputsize - (stride * (4 - level)))) - 1;
> +    indexmask_grainsize = MAKE_64BIT_MASK(0, stride + 3);
> +    indexmask = MAKE_64BIT_MASK(0, inputsize - (stride * (4 - level)));

This is just a refactoring to use MAKE_64BIT_MASK, right? Could
you keep that kind of thing in its own patch, please?

>
>      /* Now we can extract the actual base address from the TTBR */
>      descaddr = extract64(ttbr, 0, 48);
> +
> +    /*
> +     * If the base address is out of range, raise AddressSizeFault.
> +     * In the pseudocode, this is !IsZero(baseregister<47:outputsize>),
> +     * but we've just cleared the bits above 47, so simplify the test.
> +     */
> +    if (descaddr >> outputsize) {
> +        level = 0;
> +        fault_type = ARMFault_AddressSize;
> +        goto do_fault;
> +    }
> +
>      /*
>       * We rely on this masking to clear the RES0 bits at the bottom of the TTBR
>       * and also to mask out CnP (bit 0) which could validly be non-zero.
>       */
>      descaddr &= ~indexmask;
>
> -    /* The address field in the descriptor goes up to bit 39 for ARMv7
> -     * but up to bit 47 for ARMv8, but we use the descaddrmask
> -     * up to bit 39 for AArch32, because we don't need other bits in that case
> -     * to construct next descriptor address (anyway they should be all zeroes).
> +    /*
> +     * The address field in the descriptor goes up to bit 39 for ARMv7
> +     * but up to bit 47 for ARMv8.  In ARMv7, those middle bits are SBZP,
> +     * but in ARMv8 they are checked for zero and an AddressSize fault
> +     * is raised if they are not.

This text seems a bit confused about whether it wants to talk about
v7 vs v8 or AArch64 vs AArch32. For both v7 and v8 the table address
goes only up to 39 for AArch32; the difference is that in v8 the
SBZ (not SBZP) bits [47:40] must be 0 to avoid an Address size fault.
Maybe:

 /*
  * For AArch32, the address field in the descriptor goes up to bit 39
  * in both v7 and v8. However, for v8 the SBZ bits [47:40] must be 0
  * or an AddressSize fault is raised. So for v8 we extract those SBZ
  * bits as part of the address too.
  * For AArch64 the address field always goes up to bit 47 (ignoring
  * the case of FEAT_LPA's 52-bit output addresses, which we don't
  * yet implement). AArch64 always implies v8.
  */

(feel free to tweak that last bit, I haven't read the second half
of this patchset yet.)

>       */
> -    descaddrmask = ((1ull << (aarch64 ? 48 : 40)) - 1) &
> -                   ~indexmask_grainsize;
> +    if (aarch64 || arm_feature(env, ARM_FEATURE_V8)) {

Why are we testing both of these ? If aarch64 is true then we
must have ARM_FEATURE_V8 set, so we could just test that.
(This then matches what the comment text says we're checking.)

> +        descaddrmask = MAKE_64BIT_MASK(0, 48);
> +    } else {
> +        descaddrmask = MAKE_64BIT_MASK(0, 40);
> +    }
> +    descaddrmask &= ~indexmask_grainsize;
>
>      /* Secure accesses start with the page table in secure memory and
>       * can be downgraded to non-secure at any step. Non-secure accesses
> @@ -11487,7 +11518,12 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
>              /* Invalid, or the Reserved level 3 encoding */
>              goto do_fault;
>          }
> +
>          descaddr = descriptor & descaddrmask;
> +        if (descaddr >> outputsize) {
> +            fault_type = ARMFault_AddressSize;
> +            goto do_fault;
> +        }
>
>          if ((descriptor & 2) && (level < 3)) {
>              /* Table entry. The top five bits are attributes which may
> --
> 2.25.1

thanks
-- PMM
diff mbox series

Patch

diff --git a/target/arm/internals.h b/target/arm/internals.h
index 27d2fcd26c..3e801833b4 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -1032,6 +1032,7 @@  static inline uint32_t aarch64_pstate_valid_mask(const ARMISARegisters *id)
  */
 typedef struct ARMVAParameters {
     unsigned tsz    : 8;
+    unsigned ps     : 3;
     unsigned select : 1;
     bool tbi        : 1;
     bool epd        : 1;
diff --git a/target/arm/helper.c b/target/arm/helper.c
index fab9ee70d8..568914bd42 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -11003,7 +11003,7 @@  do_fault:
  * false otherwise.
  */
 static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
-                               int inputsize, int stride)
+                               int inputsize, int stride, int outputsize)
 {
     const int grainsize = stride + 3;
     int startsizecheck;
@@ -11019,22 +11019,19 @@  static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
     }
 
     if (is_aa64) {
-        CPUARMState *env = &cpu->env;
-        unsigned int pamax = arm_pamax(cpu);
-
         switch (stride) {
         case 13: /* 64KB Pages.  */
-            if (level == 0 || (level == 1 && pamax <= 42)) {
+            if (level == 0 || (level == 1 && outputsize <= 42)) {
                 return false;
             }
             break;
         case 11: /* 16KB Pages.  */
-            if (level == 0 || (level == 1 && pamax <= 40)) {
+            if (level == 0 || (level == 1 && outputsize <= 40)) {
                 return false;
             }
             break;
         case 9: /* 4KB Pages.  */
-            if (level == 0 && pamax <= 42) {
+            if (level == 0 && outputsize <= 42) {
                 return false;
             }
             break;
@@ -11043,8 +11040,8 @@  static bool check_s2_mmu_setup(ARMCPU *cpu, bool is_aa64, int level,
         }
 
         /* Inputsize checks.  */
-        if (inputsize > pamax &&
-            (arm_el_is_aa64(env, 1) || inputsize > 40)) {
+        if (inputsize > outputsize &&
+            (arm_el_is_aa64(&cpu->env, 1) || inputsize > 40)) {
             /* This is CONSTRAINED UNPREDICTABLE and we choose to fault.  */
             return false;
         }
@@ -11090,17 +11087,19 @@  static uint8_t convert_stage2_attrs(CPUARMState *env, uint8_t s2attrs)
 }
 #endif /* !CONFIG_USER_ONLY */
 
+/* This mapping is common between ID_AA64MMFR0.PARANGE and TCR_ELx.{I}PS. */
+static const uint8_t pamax_map[] = {
+    [0] = 32,
+    [1] = 36,
+    [2] = 40,
+    [3] = 42,
+    [4] = 44,
+    [5] = 48,
+};
+
 /* The cpu-specific constant value of PAMax; also used by hw/arm/virt. */
 unsigned int arm_pamax(ARMCPU *cpu)
 {
-    static const unsigned int pamax_map[] = {
-        [0] = 32,
-        [1] = 36,
-        [2] = 40,
-        [3] = 42,
-        [4] = 44,
-        [5] = 48,
-    };
     unsigned int parange =
         FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
 
@@ -11151,7 +11150,7 @@  ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
 {
     uint64_t tcr = regime_tcr(env, mmu_idx)->raw_tcr;
     bool epd, hpd, using16k, using64k;
-    int select, tsz, tbi;
+    int select, tsz, tbi, ps;
 
     if (!regime_has_2_ranges(mmu_idx)) {
         select = 0;
@@ -11165,6 +11164,7 @@  ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
             hpd = extract32(tcr, 24, 1);
         }
         epd = false;
+        ps = extract64(tcr, 16, 3);
     } else {
         /*
          * Bit 55 is always between the two regions, and is canonical for
@@ -11185,6 +11185,7 @@  ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
             epd = extract32(tcr, 23, 1);
             hpd = extract64(tcr, 42, 1);
         }
+        ps = extract64(tcr, 32, 3);
     }
 
     /* Present TBI as a composite with TBID.  */
@@ -11196,6 +11197,7 @@  ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va,
 
     return (ARMVAParameters) {
         .tsz = tsz,
+        .ps = ps,
         .select = select,
         .tbi = tbi,
         .epd = epd,
@@ -11312,7 +11314,7 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
     target_ulong page_size;
     uint32_t attrs;
     int32_t stride;
-    int addrsize, inputsize;
+    int addrsize, inputsize, outputsize;
     TCR *tcr = regime_tcr(env, mmu_idx);
     int ap, ns, xn, pxn;
     uint32_t el = regime_el(env, mmu_idx);
@@ -11323,6 +11325,7 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
     /* TODO: This code does not support shareability levels. */
     if (aarch64) {
         int min_tsz = 16, max_tsz = 39;  /* TODO: ARMv8.2-LVA  */
+        int parange;
 
         param = aa64_va_parameters(env, address, mmu_idx,
                                    access_type != MMU_INST_FETCH);
@@ -11348,11 +11351,22 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
 
         addrsize = 64 - 8 * param.tbi;
         inputsize = 64 - param.tsz;
+
+        /*
+         * Bound PS by PARANGE to find the effective output address size.
+         * ID_AA64MMFR0 is a read-only register so values outside of the
+         * supported mappings can be considered an implementation error.
+         */
+        parange = FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
+        parange = MIN(parange, param.ps);
+        assert(parange < ARRAY_SIZE(pamax_map));
+        outputsize = pamax_map[parange];
     } else {
         param = aa32_va_parameters(env, address, mmu_idx);
         level = 1;
         addrsize = (mmu_idx == ARMMMUIdx_Stage2 ? 40 : 32);
         inputsize = addrsize - param.tsz;
+        outputsize = 40;
     }
 
     /*
@@ -11437,7 +11451,7 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
 
         /* Check that the starting level is valid. */
         ok = check_s2_mmu_setup(cpu, aarch64, startlevel,
-                                inputsize, stride);
+                                inputsize, stride, outputsize);
         if (!ok) {
             fault_type = ARMFault_Translation;
             goto do_fault;
@@ -11445,24 +11459,41 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
         level = startlevel;
     }
 
-    indexmask_grainsize = (1ULL << (stride + 3)) - 1;
-    indexmask = (1ULL << (inputsize - (stride * (4 - level)))) - 1;
+    indexmask_grainsize = MAKE_64BIT_MASK(0, stride + 3);
+    indexmask = MAKE_64BIT_MASK(0, inputsize - (stride * (4 - level)));
 
     /* Now we can extract the actual base address from the TTBR */
     descaddr = extract64(ttbr, 0, 48);
+
+    /*
+     * If the base address is out of range, raise AddressSizeFault.
+     * In the pseudocode, this is !IsZero(baseregister<47:outputsize>),
+     * but we've just cleared the bits above 47, so simplify the test.
+     */
+    if (descaddr >> outputsize) {
+        level = 0;
+        fault_type = ARMFault_AddressSize;
+        goto do_fault;
+    }
+
     /*
      * We rely on this masking to clear the RES0 bits at the bottom of the TTBR
      * and also to mask out CnP (bit 0) which could validly be non-zero.
      */
     descaddr &= ~indexmask;
 
-    /* The address field in the descriptor goes up to bit 39 for ARMv7
-     * but up to bit 47 for ARMv8, but we use the descaddrmask
-     * up to bit 39 for AArch32, because we don't need other bits in that case
-     * to construct next descriptor address (anyway they should be all zeroes).
+    /*
+     * The address field in the descriptor goes up to bit 39 for ARMv7
+     * but up to bit 47 for ARMv8.  In ARMv7, those middle bits are SBZP,
+     * but in ARMv8 they are checked for zero and an AddressSize fault
+     * is raised if they are not.
      */
-    descaddrmask = ((1ull << (aarch64 ? 48 : 40)) - 1) &
-                   ~indexmask_grainsize;
+    if (aarch64 || arm_feature(env, ARM_FEATURE_V8)) {
+        descaddrmask = MAKE_64BIT_MASK(0, 48);
+    } else {
+        descaddrmask = MAKE_64BIT_MASK(0, 40);
+    }
+    descaddrmask &= ~indexmask_grainsize;
 
     /* Secure accesses start with the page table in secure memory and
      * can be downgraded to non-secure at any step. Non-secure accesses
@@ -11487,7 +11518,12 @@  static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address,
             /* Invalid, or the Reserved level 3 encoding */
             goto do_fault;
         }
+
         descaddr = descriptor & descaddrmask;
+        if (descaddr >> outputsize) {
+            fault_type = ARMFault_AddressSize;
+            goto do_fault;
+        }
 
         if ((descriptor & 2) && (level < 3)) {
             /* Table entry. The top five bits are attributes which may