For systems where can_set_direct_map() is false, it is still possible that a subregion of the direct map can be modified if it is mapped at the last-level. Add an implementation for can_set_direct_map_range(). It falls back to a page table walk to verify the mapping level if can_set_direct_map() is false.
Signed-off-by: Vincent Donnefort vdonnefort@google.com
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h index 90f61b17275e..1a37f4ef130b 100644 --- a/arch/arm64/include/asm/set_memory.h +++ b/arch/arm64/include/asm/set_memory.h @@ -9,6 +9,9 @@ bool can_set_direct_map(void); #define can_set_direct_map can_set_direct_map
+bool can_set_direct_map_range(struct page *page, unsigned long nr_pages); +#define can_set_direct_map_range can_set_direct_map_range + int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page); diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index 58898ae53f5d..c59ef17eb0d0 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -103,6 +103,48 @@ bool can_set_direct_map(void) system_supports_bbml3(); }
+bool can_set_direct_map_range(struct page *page, unsigned long nr_pages) +{ + unsigned long addr = (unsigned long)page_address(page); + unsigned long end = addr + nr_pages * PAGE_SIZE; + + if (can_set_direct_map()) + return true; + + /* + * If !can_set_direct_map() then no one can split blocks and it is safe + * to walk the page-table lockless. + */ + while (addr < end) { + pud_t *pudp, pud; + pmd_t *pmdp, pmd; + pgd_t *pgdp; + p4d_t *p4dp; + + pgdp = pgd_offset_k(addr); + if (pgd_none(READ_ONCE(*pgdp))) + return false; + + p4dp = p4d_offset(pgdp, addr); + if (p4d_none(READ_ONCE(*p4dp))) + return false; + + pudp = pud_offset(p4dp, addr); + pud = READ_ONCE(*pudp); + if (pud_none(pud) || pud_leaf(pud)) + return false; + + pmdp = pmd_offset(pudp, addr); + pmd = READ_ONCE(*pmdp); + if (pmd_none(pmd) || pmd_leaf(pmd)) + return false; + + addr = pmd_addr_end(addr, end); + } + + return true; +} + static int update_range_prot(unsigned long start, unsigned long size, pgprot_t set_mask, pgprot_t clear_mask) {