diff --git a/target/arm/cpu-param.h b/target/arm/cpu-param.h
index 18ac562346..d593b60b28 100644
--- a/target/arm/cpu-param.h
+++ b/target/arm/cpu-param.h
@@ -29,6 +29,6 @@
 # define TARGET_PAGE_BITS_MIN  10
 #endif
 
-#define NB_MMU_MODES 9
+#define NB_MMU_MODES 12
 
 #endif
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 22935e4433..22c5706835 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -2715,20 +2715,22 @@ bool write_cpustate_to_list(ARMCPU *cpu, bool kvm_sync);
  *  5. we want to be able to use the TLB for accesses done as part of a
  *     stage1 page table walk, rather than having to walk the stage2 page
  *     table over and over.
+ *  6. we need separate EL1/EL2 mmu_idx for handling the Priviledged Access
+ *     Never (PAN) bit within PSTATE.
  *
  * This gives us the following list of cases:
  *
  * NS EL0 (aka NS PL0) EL1&0 stage 1+2
- * NS EL1 (aka NS PL1) EL1&0 stage 1+2
+ * NS EL1 (aka NS PL1) EL1&0 stage 1+2 (+PAN)
  * NS EL0 EL2&0
- * NS EL2 EL2&0
+ * NS EL2 EL2&0 (+PAN)
  * NS EL2 (aka NS PL2)
  * S EL0 (aka S PL0)
- * S EL1 (not used if EL3 is 32 bit)
+ * S EL1 (not used if EL3 is 32 bit) (+PAN)
  * S EL3 (aka S PL1)
  * NS EL0&1 stage 2
  *
- * for a total of 9 different mmu_idx.
+ * for a total of 12 different mmu_idx.
  *
  * R profile CPUs have an MPU, but can use the same set of MMU indexes
  * as A profile. They only need to distinguish NS EL0 and NS EL1 (and
@@ -2783,19 +2785,22 @@ typedef enum ARMMMUIdx {
     /*
      * A-profile.
      */
-    ARMMMUIdx_EL10_0 = 0 | ARM_MMU_IDX_A,
-    ARMMMUIdx_EL20_0 = 1 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL10_0 =     0 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL20_0 =     1 | ARM_MMU_IDX_A,
 
-    ARMMMUIdx_EL10_1 = 2 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL10_1 =     2 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL10_1_PAN = 3 | ARM_MMU_IDX_A,
 
-    ARMMMUIdx_E2 =     3 | ARM_MMU_IDX_A,
-    ARMMMUIdx_EL20_2 = 4 | ARM_MMU_IDX_A,
+    ARMMMUIdx_E2 =         4 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL20_2 =     5 | ARM_MMU_IDX_A,
+    ARMMMUIdx_EL20_2_PAN = 6 | ARM_MMU_IDX_A,
 
-    ARMMMUIdx_SE0 =    5 | ARM_MMU_IDX_A,
-    ARMMMUIdx_SE1 =    6 | ARM_MMU_IDX_A,
-    ARMMMUIdx_SE3 =    7 | ARM_MMU_IDX_A,
+    ARMMMUIdx_SE0 =        7 | ARM_MMU_IDX_A,
+    ARMMMUIdx_SE1 =        8 | ARM_MMU_IDX_A,
+    ARMMMUIdx_SE1_PAN =    9 | ARM_MMU_IDX_A,
+    ARMMMUIdx_SE3 =       10 | ARM_MMU_IDX_A,
 
-    ARMMMUIdx_Stage2 = 8 | ARM_MMU_IDX_A,
+    ARMMMUIdx_Stage2 =    11 | ARM_MMU_IDX_A,
 
     /*
      * These are not allocated TLBs and are used only for AT system
@@ -2803,6 +2808,7 @@ typedef enum ARMMMUIdx {
      */
     ARMMMUIdx_Stage1_E0 = 0 | ARM_MMU_IDX_NOTLB,
     ARMMMUIdx_Stage1_E1 = 1 | ARM_MMU_IDX_NOTLB,
+    ARMMMUIdx_Stage1_E1_PAN = 2 | ARM_MMU_IDX_NOTLB,
 
     /*
      * M-profile.
@@ -2828,10 +2834,13 @@ typedef enum ARMMMUIdxBit {
     TO_CORE_BIT(EL10_0),
     TO_CORE_BIT(EL20_0),
     TO_CORE_BIT(EL10_1),
+    TO_CORE_BIT(EL10_1_PAN),
     TO_CORE_BIT(E2),
     TO_CORE_BIT(EL20_2),
+    TO_CORE_BIT(EL20_2_PAN),
     TO_CORE_BIT(SE0),
     TO_CORE_BIT(SE1),
+    TO_CORE_BIT(SE1_PAN),
     TO_CORE_BIT(SE3),
     TO_CORE_BIT(Stage2),
 
diff --git a/target/arm/internals.h b/target/arm/internals.h
index 850f204f14..2408953031 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -843,12 +843,16 @@ static inline bool regime_has_2_ranges(ARMMMUIdx mmu_idx)
     switch (mmu_idx) {
     case ARMMMUIdx_Stage1_E0:
     case ARMMMUIdx_Stage1_E1:
+    case ARMMMUIdx_Stage1_E1_PAN:
     case ARMMMUIdx_EL10_0:
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
     case ARMMMUIdx_EL20_0:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
     case ARMMMUIdx_SE0:
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
         return true;
     default:
         return false;
@@ -861,10 +865,13 @@ static inline bool regime_is_secure(CPUARMState *env, ARMMMUIdx mmu_idx)
     switch (mmu_idx) {
     case ARMMMUIdx_EL10_0:
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
     case ARMMMUIdx_EL20_0:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
     case ARMMMUIdx_Stage1_E0:
     case ARMMMUIdx_Stage1_E1:
+    case ARMMMUIdx_Stage1_E1_PAN:
     case ARMMMUIdx_E2:
     case ARMMMUIdx_Stage2:
     case ARMMMUIdx_MPrivNegPri:
@@ -874,6 +881,7 @@ static inline bool regime_is_secure(CPUARMState *env, ARMMMUIdx mmu_idx)
         return false;
     case ARMMMUIdx_SE0:
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
     case ARMMMUIdx_SE3:
     case ARMMMUIdx_MSPrivNegPri:
     case ARMMMUIdx_MSUserNegPri:
@@ -1039,6 +1047,7 @@ static inline bool arm_mmu_idx_is_stage1(ARMMMUIdx mmu_idx)
     switch (mmu_idx) {
     case ARMMMUIdx_Stage1_E0:
     case ARMMMUIdx_Stage1_E1:
+    case ARMMMUIdx_Stage1_E1_PAN:
         return true;
     default:
         return false;
diff --git a/target/arm/helper.c b/target/arm/helper.c
index fdb86ea427..4e3fe00316 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -671,6 +671,7 @@ static void tlbiall_nsnh_write(CPUARMState *env, const ARMCPRegInfo *ri,
 
     tlb_flush_by_mmuidx(cs,
                         ARMMMUIdxBit_EL10_1 |
+                        ARMMMUIdxBit_EL10_1_PAN |
                         ARMMMUIdxBit_EL10_0 |
                         ARMMMUIdxBit_Stage2);
 }
@@ -682,6 +683,7 @@ static void tlbiall_nsnh_is_write(CPUARMState *env, const ARMCPRegInfo *ri,
 
     tlb_flush_by_mmuidx_all_cpus_synced(cs,
                                         ARMMMUIdxBit_EL10_1 |
+                                        ARMMMUIdxBit_EL10_1_PAN |
                                         ARMMMUIdxBit_EL10_0 |
                                         ARMMMUIdxBit_Stage2);
 }
@@ -2660,6 +2662,7 @@ static int gt_phys_redir_timeridx(CPUARMState *env)
     switch (arm_mmu_idx(env)) {
     case ARMMMUIdx_EL20_0:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
         return GTIMER_HYP;
     default:
         return GTIMER_PHYS;
@@ -2671,6 +2674,7 @@ static int gt_virt_redir_timeridx(CPUARMState *env)
     switch (arm_mmu_idx(env)) {
     case ARMMMUIdx_EL20_0:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
         return GTIMER_HYPVIRT;
     default:
         return GTIMER_VIRT;
@@ -3288,7 +3292,9 @@ static uint64_t do_ats_write(CPUARMState *env, uint64_t value,
         format64 = arm_s1_regime_using_lpae_format(env, mmu_idx);
 
         if (arm_feature(env, ARM_FEATURE_EL2)) {
-            if (mmu_idx == ARMMMUIdx_EL10_0 || mmu_idx == ARMMMUIdx_EL10_1) {
+            if (mmu_idx == ARMMMUIdx_EL10_0 ||
+                mmu_idx == ARMMMUIdx_EL10_1 ||
+                mmu_idx == ARMMMUIdx_EL10_1_PAN) {
                 format64 |= env->cp15.hcr_el2 & (HCR_VM | HCR_DC);
             } else {
                 format64 |= arm_current_el(env) == 2;
@@ -3746,7 +3752,9 @@ static void vmsa_tcr_ttbr_el2_write(CPUARMState *env, const ARMCPRegInfo *ri,
     if ((arm_hcr_el2_eff(env) & HCR_E2H) &&
         extract64(raw_read(env, ri) ^ value, 48, 16)) {
         tlb_flush_by_mmuidx(env_cpu(env),
-                            ARMMMUIdxBit_EL20_2 | ARMMMUIdxBit_EL20_0);
+                            ARMMMUIdxBit_EL20_2 |
+                            ARMMMUIdxBit_EL20_2_PAN |
+                            ARMMMUIdxBit_EL20_0);
     }
     raw_write(env, ri, value);
 }
@@ -3764,6 +3772,7 @@ static void vttbr_write(CPUARMState *env, const ARMCPRegInfo *ri,
     if (raw_read(env, ri) != value) {
         tlb_flush_by_mmuidx(cs,
                             ARMMMUIdxBit_EL10_1 |
+                            ARMMMUIdxBit_EL10_1_PAN |
                             ARMMMUIdxBit_EL10_0 |
                             ARMMMUIdxBit_Stage2);
         raw_write(env, ri, value);
@@ -4124,12 +4133,18 @@ static int vae1_tlbmask(CPUARMState *env)
 {
     /* Since we exclude secure first, we may read HCR_EL2 directly. */
     if (arm_is_secure_below_el3(env)) {
-        return ARMMMUIdxBit_SE1 | ARMMMUIdxBit_SE0;
+        return ARMMMUIdxBit_SE1 |
+               ARMMMUIdxBit_SE1_PAN |
+               ARMMMUIdxBit_SE0;
     } else if ((env->cp15.hcr_el2 & (HCR_E2H | HCR_TGE))
                == (HCR_E2H | HCR_TGE)) {
-        return ARMMMUIdxBit_EL20_2 | ARMMMUIdxBit_EL20_0;
+        return ARMMMUIdxBit_EL20_2 |
+               ARMMMUIdxBit_EL20_2_PAN |
+               ARMMMUIdxBit_EL20_0;
     } else {
-        return ARMMMUIdxBit_EL10_1 | ARMMMUIdxBit_EL10_0;
+        return ARMMMUIdxBit_EL10_1 |
+               ARMMMUIdxBit_EL10_1_PAN |
+               ARMMMUIdxBit_EL10_0;
     }
 }
 
@@ -4165,14 +4180,23 @@ static int vmalle1_tlbmask(CPUARMState *env)
      * Since we exclude secure first, we may read HCR_EL2 directly.
      */
     if (arm_is_secure_below_el3(env)) {
-        return ARMMMUIdxBit_SE1 | ARMMMUIdxBit_SE0;
+        return ARMMMUIdxBit_SE1 |
+               ARMMMUIdxBit_SE1_PAN |
+               ARMMMUIdxBit_SE0;
     } else if ((env->cp15.hcr_el2 & (HCR_E2H | HCR_TGE))
                == (HCR_E2H | HCR_TGE)) {
-        return ARMMMUIdxBit_EL20_2 | ARMMMUIdxBit_EL20_0;
+        return ARMMMUIdxBit_EL20_2 |
+               ARMMMUIdxBit_EL20_2_PAN |
+               ARMMMUIdxBit_EL20_0;
     } else if (arm_feature(env, ARM_FEATURE_EL2)) {
-        return ARMMMUIdxBit_EL10_1 | ARMMMUIdxBit_EL10_0 | ARMMMUIdxBit_Stage2;
+        return ARMMMUIdxBit_EL10_1 |
+               ARMMMUIdxBit_EL10_1_PAN |
+               ARMMMUIdxBit_EL10_0 |
+               ARMMMUIdxBit_Stage2;
     } else {
-        return ARMMMUIdxBit_EL10_1 | ARMMMUIdxBit_EL10_0;
+        return ARMMMUIdxBit_EL10_1 |
+               ARMMMUIdxBit_EL10_1_PAN |
+               ARMMMUIdxBit_EL10_0;
     }
 }
 
@@ -4188,7 +4212,9 @@ static void tlbi_aa64_alle1_write(CPUARMState *env, const ARMCPRegInfo *ri,
 static int vae2_tlbmask(CPUARMState *env)
 {
     if (arm_hcr_el2_eff(env) & HCR_E2H) {
-        return ARMMMUIdxBit_EL20_0 | ARMMMUIdxBit_EL20_2;
+        return ARMMMUIdxBit_EL20_0 |
+               ARMMMUIdxBit_EL20_2 |
+               ARMMMUIdxBit_EL20_2_PAN;
     } else {
         return ARMMMUIdxBit_E2;
     }
@@ -9080,6 +9106,7 @@ static uint32_t regime_el(CPUARMState *env, ARMMMUIdx mmu_idx)
     switch (mmu_idx) {
     case ARMMMUIdx_EL20_0:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
     case ARMMMUIdx_Stage2:
     case ARMMMUIdx_E2:
         return 2;
@@ -9088,10 +9115,13 @@ static uint32_t regime_el(CPUARMState *env, ARMMMUIdx mmu_idx)
     case ARMMMUIdx_SE0:
         return arm_el_is_aa64(env, 3) ? 1 : 3;
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
     case ARMMMUIdx_Stage1_E0:
     case ARMMMUIdx_Stage1_E1:
+    case ARMMMUIdx_Stage1_E1_PAN:
     case ARMMMUIdx_EL10_0:
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
     case ARMMMUIdx_MPrivNegPri:
     case ARMMMUIdx_MUserNegPri:
     case ARMMMUIdx_MPriv:
@@ -9207,6 +9237,8 @@ static inline ARMMMUIdx stage_1_mmu_idx(ARMMMUIdx mmu_idx)
         return ARMMMUIdx_Stage1_E0;
     case ARMMMUIdx_EL10_1:
         return ARMMMUIdx_Stage1_E1;
+    case ARMMMUIdx_EL10_1_PAN:
+        return ARMMMUIdx_Stage1_E1_PAN;
     default:
         return mmu_idx;
     }
@@ -9253,6 +9285,7 @@ static inline bool regime_is_user(CPUARMState *env, ARMMMUIdx mmu_idx)
         return false;
     case ARMMMUIdx_EL10_0:
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
         g_assert_not_reached();
     }
 }
@@ -11145,7 +11178,9 @@ bool get_phys_addr(CPUARMState *env, target_ulong address,
                    target_ulong *page_size,
                    ARMMMUFaultInfo *fi, ARMCacheAttrs *cacheattrs)
 {
-    if (mmu_idx == ARMMMUIdx_EL10_0 || mmu_idx == ARMMMUIdx_EL10_1) {
+    if (mmu_idx == ARMMMUIdx_EL10_0 ||
+        mmu_idx == ARMMMUIdx_EL10_1 ||
+        mmu_idx == ARMMMUIdx_EL10_1_PAN) {
         /* Call ourselves recursively to do the stage 1 and then stage 2
          * translations.
          */
@@ -11672,10 +11707,13 @@ int arm_mmu_idx_to_el(ARMMMUIdx mmu_idx)
     case ARMMMUIdx_SE0:
         return 0;
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
         return 1;
     case ARMMMUIdx_E2:
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
         return 2;
     case ARMMMUIdx_SE3:
         return 3;
@@ -11886,11 +11924,14 @@ static uint32_t rebuild_hflags_a64(CPUARMState *env, int el, int fp_el,
     /* TODO: ARMv8.2-UAO */
     switch (mmu_idx) {
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
         /* TODO: ARMv8.3-NV */
         flags = FIELD_DP32(flags, TBFLAG_A64, UNPRIV, 1);
         break;
     case ARMMMUIdx_EL20_2:
+    case ARMMMUIdx_EL20_2_PAN:
         /* TODO: ARMv8.4-SecEL2 */
         /*
          * Note that EL20_2 is gated by HCR_EL2.E2H == 1, but EL20_0 is
diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c
index fe492bea90..b5c7bc2d76 100644
--- a/target/arm/translate-a64.c
+++ b/target/arm/translate-a64.c
@@ -124,9 +124,11 @@ static int get_a64_user_mem_index(DisasContext *s)
          */
         switch (useridx) {
         case ARMMMUIdx_EL10_1:
+        case ARMMMUIdx_EL10_1_PAN:
             useridx = ARMMMUIdx_EL10_0;
             break;
         case ARMMMUIdx_EL20_2:
+        case ARMMMUIdx_EL20_2_PAN:
             useridx = ARMMMUIdx_EL20_0;
             break;
         case ARMMMUIdx_SE1:
diff --git a/target/arm/translate.c b/target/arm/translate.c
index b7f726e733..47a374b53d 100644
--- a/target/arm/translate.c
+++ b/target/arm/translate.c
@@ -155,10 +155,12 @@ static inline int get_a32_user_mem_index(DisasContext *s)
     case ARMMMUIdx_E2:        /* this one is UNPREDICTABLE */
     case ARMMMUIdx_EL10_0:
     case ARMMMUIdx_EL10_1:
+    case ARMMMUIdx_EL10_1_PAN:
         return arm_to_core_mmu_idx(ARMMMUIdx_EL10_0);
     case ARMMMUIdx_SE3:
     case ARMMMUIdx_SE0:
     case ARMMMUIdx_SE1:
+    case ARMMMUIdx_SE1_PAN:
         return arm_to_core_mmu_idx(ARMMMUIdx_SE0);
     case ARMMMUIdx_MUser:
     case ARMMMUIdx_MPriv:
