Hi Ashwin,

On Sat, 26 Sept 2026 at 12:26, Aswin Murugan
<[email protected]> wrote:
>
> mmu_change_region_attr_nobreak() updates only the primary page
> table, leaving the emergency page table with stale attributes.
> The emergency table is used temporarily while the primary page
> table is being updated.
>
> As a result, speculative accesses can reach regions that were
> intended to be blocked in the primary table, leading to XPU
> violations.
>
> Update both primary and emergency page tables when modifying
> region attributes to keep mappings consistent regardless of the
> active translation table.
>
> Signed-off-by: Aswin Murugan <[email protected]>
> ---
>  arch/arm/cpu/armv8/cache_v8.c | 29 +++++++++++++++++++++++------
>  1 file changed, 23 insertions(+), 6 deletions(-)
>
> diff --git a/arch/arm/cpu/armv8/cache_v8.c b/arch/arm/cpu/armv8/cache_v8.c
> index ad232435eb2..f351f11007d 100644
> --- a/arch/arm/cpu/armv8/cache_v8.c
> +++ b/arch/arm/cpu/armv8/cache_v8.c
> @@ -173,7 +173,7 @@ static int level2shift(int level)
>         return (12 + 9 * (3 - level));
>  }
>
> -static u64 *find_pte(u64 addr, int level)
> +static u64 *find_pte_tbl(u64 *root, u64 addr, int level)
>  {
>         int start_level = 0;
>         u64 *pte;
> @@ -191,7 +191,7 @@ static u64 *find_pte(u64 addr, int level)
>                 return NULL;
>
>         /* Walk through all page table levels to find our PTE */
> -       pte = (u64*)gd->arch.tlb_addr;
> +       pte = root;
>         for (i = start_level; i < 4; i++) {
>                 idx = (addr >> level2shift(i)) & 0x1FF;
>                 pte += idx;
> @@ -211,6 +211,11 @@ static u64 *find_pte(u64 addr, int level)
>         return NULL;
>  }
>
> +static u64 *find_pte(u64 addr, int level)
> +{
> +       return find_pte_tbl((u64 *)gd->arch.tlb_addr, addr, level);
> +}
> +
>  #ifdef CONFIG_CMO_BY_VA_ONLY
>  static void __cmo_on_leaves(void (*cmo_fn)(unsigned long, unsigned long),
>                             u64 pte, int level, u64 base)
> @@ -998,11 +1003,11 @@ static bool is_aligned(u64 addr, u64 size, u64 align)
>  }
>
>  /* Use flag to indicate if attrs has more than d-cache attributes */
> -static u64 set_one_region(u64 start, u64 size, u64 attrs, bool flag, int 
> level)
> +static u64 set_one_region_tbl(u64 *root, u64 start, u64 size, u64 attrs, 
> bool flag, int level)
>  {
>         int levelshift = level2shift(level);
>         u64 levelsize = 1ULL << levelshift;
> -       u64 *pte = find_pte(start, level);
> +       u64 *pte = find_pte_tbl(root, start, level);
>
>         /* Can we can just modify the current level block/page? */
>         if (is_aligned(start, size, levelsize)) {
> @@ -1039,7 +1044,7 @@ static u64 set_one_region(u64 start, u64 size, u64 
> attrs, bool flag, int level)
>         return 0;
>  }
>
> -static void set_regions(u64 start, u64 size, u64 attrs, bool flag)
> +static void set_regions_tbl(u64 *root, u64 start, u64 size, u64 attrs, bool 
> flag)
>  {
>         int level;
>         u64 r;
> @@ -1050,7 +1055,7 @@ static void set_regions(u64 start, u64 size, u64 attrs, 
> bool flag)
>          */
>         while (size > 0) {
>                 for (level = 1; level < 4; level++) {
> -                       r = set_one_region(start, size, attrs, flag, level);
> +                       r = set_one_region_tbl(root, start, size, attrs, 
> flag, level);
>                         if (r) {
>                                 /* PTE successfully replaced */
>                                 size -= r;
> @@ -1061,6 +1066,11 @@ static void set_regions(u64 start, u64 size, u64 
> attrs, bool flag)
>         }
>  }
>
> +static void set_regions(u64 start, u64 size, u64 attrs, bool flag)
> +{
> +       set_regions_tbl((u64 *)gd->arch.tlb_addr, start, size, attrs, flag);
> +}
> +
>  void mmu_set_region_dcache_behaviour(phys_addr_t start, size_t size,
>                                      enum dcache_option option)
>  {
> @@ -1095,6 +1105,13 @@ void mmu_set_region_dcache_behaviour(phys_addr_t 
> start, size_t size,
>  void mmu_change_region_attr_nobreak(phys_addr_t addr, size_t size, u64 attrs)
>  {
>         set_regions(addr, size, attrs, true);
> +
> +       /* Apply the same change to the emergency table, keeping it in sync
> +        * with the primary table.
> +        */
> +       if (gd->arch.tlb_emerg)
> +               set_regions_tbl((u64 *)gd->arch.tlb_emerg, addr, size, attrs, 
> true);

So the problem I see here is that emergency page table is a snapshot
of the original mem_map, that we only used to update in
mmu_map_region(). We now update it on every change and I think this is
the right approach. But are we sure get_page_table_size() will be
enough? The 4 used to reserve to accommodate dcache settings is a
random pick. What I fear is that some architectures, for example the
ones that enable PGPROT and create even more tables, will require more
space now. I think we should also increase the get_page_table_size
size calculation.

Thanks
/Ilias

> +
>         flush_dcache_range(gd->arch.tlb_addr,
>                            gd->arch.tlb_addr + gd->arch.tlb_size);
>         __asm_invalidate_tlb_all();
> --
> 2.34.1
>

Reply via email to