Skip to contentSkip to conversation

arm64/mm: use the contiguous attribute for kernel mappings

PATCH Superseded Ard Biesheuvel 3 participants 18 messages Oct 12, 2016 – Oct 14, 2016

Subsystems (2)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

v3 cover arm64/mm: use the contiguous attribute for kernel mappings

This 5-piece series is a followup to the single patch 'arm64: mmu: set the contiguous for kernel mappings when appropriate' sent out on the 10th [0].

Changes in v3:
- add support for contiguous PMDs for all granule sizes (not just 16k)
- add a separate patch to deal with contiguous PUDs (4k granule only), and contiguous PMDs for 2 levels of translation (which requires special handling)
- avoid pmd_none/pud_none in the BUG() statements in patch #1, since they may resolve in unexpected ways with folded PMDs/PUDs

Version v2 [1] addressed the following issues:
- the contiguous attribute is also useful for contigous PMD mappings on 16k granule kernels (i.e., 1 GB blocks)
- the function parameter 'block_mappings_allowed' does not clearly convey whether contiguous page mappings should be used, so it is renamed to 'page_mappings_only', and its meaning inverted
- instead of BUGging on changes in the PTE_CONT attribute in PMD or PTE entries that have been populated already, BUG on any modification except for permission attributes, which don't require break-before-make when changed.

[0] http://marc.info/?l=linux-arm-kernel&m=147612332130714
[1] http://marc.info/?l=linux-arm-kernel&m=147618975314593

An example memory map from a Seattle system with 64 GB running a 4k/3 levels kernel with KASLR enabled is included below.

Ard Biesheuvel (5):
arm64: mm: BUG on unsupported manipulations of live kernel mappings
arm64: mm: replace 'block_mappings_allowed' with 'page_mappings_only'
arm64: mm: set the contiguous bit for kernel mappings where
appropriate
arm64: mm: support additional contiguous kernel mapping region sizes
arm64: mm: round memstart_addr to contiguous PUD/PMD size

 arch/arm64/include/asm/kernel-pgtable.h |  11 +-
 arch/arm64/include/asm/mmu.h            |   2 +-
 arch/arm64/include/asm/pgtable-hwdef.h  |   6 +
 arch/arm64/kernel/efi.c                 |   8 +-
 arch/arm64/mm/mmu.c                     | 173 ++++++++++++++------
 5 files changed, 140 insertions(+), 60 deletions(-)
Signature · 40 lines
2.7.4

// Kernel mapping

0xffffff8a70100000-0xffffff8a70200000      1M PTE ro x  SHD AF    CON    
0xffffff8a70200000-0xffffff8a70600000      4M PMD ro x  SHD AF        BLK
0xffffff8a70600000-0xffffff8a706d0000    832K PTE ro x  SHD AF    CON    
0xffffff8a706d0000-0xffffff8a70920000   2368K PTE ro NX SHD AF    CON    
0xffffff8a70a90000-0xffffff8a70b40000    704K PTE RW NX SHD AF    CON    
0xffffff8a70b40000-0xffffff8a70b44000     16K PTE RW NX SHD AF           
...

---[ Linear Mapping ]---
0xffffffcc00e80000-0xffffffcc01000000   1536K PTE RW NX SHD AF    CON    
0xffffffcc01000000-0xffffffcc02000000     16M PMD RW NX SHD AF        BLK
0xffffffcc02000000-0xffffffcc40000000    992M PMD RW NX SHD AF    CON BLK
0xffffffcc40000000-0xffffffd180000000     21G PGD RW NX SHD AF        BLK
0xffffffd180000000-0xffffffd198000000    384M PMD RW NX SHD AF    CON BLK
0xffffffd198000000-0xffffffd199200000     18M PMD RW NX SHD AF        BLK
0xffffffd199200000-0xffffffd199300000      1M PTE RW NX SHD AF    CON    
0xffffffd199300000-0xffffffd199400000      1M PTE ro NX SHD AF    CON    
0xffffffd199400000-0xffffffd199a00000      6M PMD ro NX SHD AF        BLK
0xffffffd199a00000-0xffffffd199b20000   1152K PTE ro NX SHD AF    CON    
0xffffffd199b20000-0xffffffd199c00000    896K PTE RW NX SHD AF    CON    
0xffffffd199c00000-0xffffffd19a000000      4M PMD RW NX SHD AF        BLK
0xffffffd19a000000-0xffffffd1c0000000    608M PMD RW NX SHD AF    CON BLK
0xffffffd1c0000000-0xffffffd400000000      9G PGD RW NX SHD AF        BLK
0xffffffd400000000-0xffffffd800000000     16G PGD RW NX SHD AF    CON BLK
0xffffffd800000000-0xffffffdbc0000000     15G PGD RW NX SHD AF        BLK
0xffffffdbc0000000-0xffffffdbf8000000    896M PMD RW NX SHD AF    CON BLK
0xffffffdbf8000000-0xffffffdbf8600000      6M PMD RW NX SHD AF        BLK
0xffffffdbf8600000-0xffffffdbf8700000      1M PTE RW NX SHD AF    CON    
0xffffffdbf87a0000-0xffffffdbf8800000    384K PTE RW NX SHD AF    CON    
0xffffffdbf8800000-0xffffffdbfb800000     48M PMD RW NX SHD AF        BLK
0xffffffdbfb800000-0xffffffdbfb830000    192K PTE RW NX SHD AF    CON    
0xffffffdbfbc10000-0xffffffdbfbcd0000    768K PTE RW NX SHD AF    CON    
0xffffffdbfc000000-0xffffffdbfe000000     32M PMD RW NX SHD AF    CON BLK
0xffffffdbfe000000-0xffffffdbffe00000     30M PMD RW NX SHD AF        BLK
0xffffffdbffe00000-0xffffffdbfffd0000   1856K PTE RW NX SHD AF    CON    
0xffffffdbffff0000-0xffffffdc00000000     64K PTE RW NX SHD AF    CON    
#1 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Now that we take care not manipulate the live kernel page tables in a way that may lead to TLB conflicts, the case where a table mapping is replaced by a block mapping can no longer occur. So remove the handling of this at the PUD and PMD levels, and instead, BUG() on any occurrence of live kernel page table manipulations that modify anything other than the permission bits.

Since mark_rodata_ro() is the only caller where the kernel mappings that are being manipulated are actually live, drop the various conditional flush_tlb_all() invocations, and add a single call to mark_rodata_ro() instead.

---
 arch/arm64/mm/mmu.c | 68 ++++++++++++--------
 1 file changed, 41 insertions(+), 27 deletions(-)
arch/arm64/mm/mmu.c +41 -27
index 05615a3fdc6f..e1c34e5a1d7d 100644
@@ -28,8 +28,6 @@
 #include <linux/memblock.h>
 #include <linux/fs.h>
 #include <linux/io.h>
-#include <linux/slab.h>
-#include <linux/stop_machine.h>
 
 #include <asm/barrier.h>
 #include <asm/cputype.h>
@@ -95,6 +93,12 @@ static phys_addr_t __init early_pgtable_alloc(void)
 	return phys;
 }
 
+/*
+ * The following mapping attributes may be updated in live
+ * kernel mappings without the need for break-before-make.
+ */
+static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
+
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 				  unsigned long end, unsigned long pfn,
 				  pgprot_t prot,
@@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 
 	pte = pte_set_fixmap_offset(pmd, addr);
 	do {
+		pte_t old_pte = *pte;
+
 		set_pte(pte, pfn_pte(pfn, prot));
 		pfn++;
+
+		/*
+		 * After the PTE entry has been populated once, we
+		 * only allow updates to the permission attributes.
+		 */
+		BUG_ON(pte_val(old_pte) != 0 &&
+		       ((pte_val(old_pte) ^ pte_val(*pte)) &
+			~modifiable_attr_mask) != 0);
 	} while (pte++, addr += PAGE_SIZE, addr != end);
 
 	pte_clear_fixmap();
@@ -146,27 +160,28 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 
 	pmd = pmd_set_fixmap_offset(pud, addr);
 	do {
+		pmd_t old_pmd = *pmd;
+
 		next = pmd_addr_end(addr, end);
+
 		/* try section mapping first */
 		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
 		      allow_block_mappings) {
-			pmd_t old_pmd =*pmd;
 			pmd_set_huge(pmd, phys, prot);
+
 			/*
-			 * Check for previous table entries created during
-			 * boot (__create_page_tables) and flush them.
+			 * After the PMD entry has been populated once, we
+			 * only allow updates to the permission attributes.
 			 */
-			if (!pmd_none(old_pmd)) {
-				flush_tlb_all();
-				if (pmd_table(old_pmd)) {
-					phys_addr_t table = pmd_page_paddr(old_pmd);
-					if (!WARN_ON_ONCE(slab_is_available()))
-						memblock_free(table, PAGE_SIZE);
-				}
-			}
+			BUG_ON(pmd_val(old_pmd) != 0 &&
+			       ((pmd_val(old_pmd) ^ pmd_val(*pmd)) &
+				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pte(pmd, addr, next, __phys_to_pfn(phys),
 				       prot, pgtable_alloc);
+
+			BUG_ON(pmd_val(old_pmd) != 0 &&
+			       pmd_val(old_pmd) != pmd_val(*pmd));
 		}
 		phys += next - addr;
 	} while (pmd++, addr = next, addr != end);
@@ -204,33 +219,29 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 
 	pud = pud_set_fixmap_offset(pgd, addr);
 	do {
+		pud_t old_pud = *pud;
+
 		next = pud_addr_end(addr, end);
 
 		/*
 		 * For 4K granule only, attempt to put down a 1GB block
 		 */
 		if (use_1G_block(addr, next, phys) && allow_block_mappings) {
-			pud_t old_pud = *pud;
 			pud_set_huge(pud, phys, prot);
 
 			/*
-			 * If we have an old value for a pud, it will
-			 * be pointing to a pmd table that we no longer
-			 * need (from swapper_pg_dir).
-			 *
-			 * Look up the old pmd table and free it.
+			 * After the PUD entry has been populated once, we
+			 * only allow updates to the permission attributes.
 			 */
-			if (!pud_none(old_pud)) {
-				flush_tlb_all();
-				if (pud_table(old_pud)) {
-					phys_addr_t table = pud_page_paddr(old_pud);
-					if (!WARN_ON_ONCE(slab_is_available()))
-						memblock_free(table, PAGE_SIZE);
-				}
-			}
+			BUG_ON(pud_val(old_pud) != 0 &&
+			       ((pud_val(old_pud) ^ pud_val(*pud)) &
+				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pmd(pud, addr, next, phys, prot,
 				       pgtable_alloc, allow_block_mappings);
+
+			BUG_ON(pud_val(old_pud) != 0 &&
+			       pud_val(old_pud) != pud_val(*pud));
 		}
 		phys += next - addr;
 	} while (pud++, addr = next, addr != end);
@@ -396,6 +407,9 @@ void mark_rodata_ro(void)
 	section_size = (unsigned long)__init_begin - (unsigned long)__start_rodata;
 	create_mapping_late(__pa(__start_rodata), (unsigned long)__start_rodata,
 			    section_size, PAGE_KERNEL_RO);
+
+	/* flush the TLBs after updating live kernel mappings */
+	flush_tlb_all();
 }
 
 static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end,
Signature · 1 line
2.7.4
maintainer
#2 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 30 lines · Ard Biesheuvel
arch/arm64/mm/mmu.c +8 -2
@@ -28,8 +28,6 @@
 #include <linux/memblock.h>
 #include <linux/fs.h>
 #include <linux/io.h>
-#include <linux/slab.h>
-#include <linux/stop_machine.h>
 
 #include <asm/barrier.h>
 #include <asm/cputype.h>
@@ -95,6 +93,12 @@ static phys_addr_t __init early_pgtable_alloc(void)
 	return phys;
 }
 
+/*
+ * The following mapping attributes may be updated in live
+ * kernel mappings without the need for break-before-make.
+ */
+static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
+
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 				  unsigned long end, unsigned long pfn,
 				  pgprot_t prot,
@@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 
 	pte = pte_set_fixmap_offset(pmd, addr);
 	do {
+		pte_t old_pte = *pte;
+

arch/arm64/mm/mmu.c static void alloc_init_pte(pmd_t *pmd, unsigned long addr, · line 133

Quoted 10 lines
arch/arm64/mm/mmu.c +8 -0
 		set_pte(pte, pfn_pte(pfn, prot));
 		pfn++;
+
+		/*
+		 * After the PTE entry has been populated once, we
+		 * only allow updates to the permission attributes.
+		 */
+		BUG_ON(pte_val(old_pte) != 0 &&
+		       ((pte_val(old_pte) ^ pte_val(*pte)) &
+			~modifiable_attr_mask) != 0);

Please turn this check into a single macro. You have it in three places already (though with different types but a macro would do). Something like below (feel free to come up with a better macro name):

		BUG_ON(!safe_pgattr_change(old_pte, *pte));
Signature · 1 line
Catalin
#3 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 46 lines · Catalin Marinas
Quoted 30 lines · Ard Biesheuvel
arch/arm64/mm/mmu.c +8 -2
@@ -28,8 +28,6 @@
 #include <linux/memblock.h>
 #include <linux/fs.h>
 #include <linux/io.h>
-#include <linux/slab.h>
-#include <linux/stop_machine.h>
 
 #include <asm/barrier.h>
 #include <asm/cputype.h>
@@ -95,6 +93,12 @@ static phys_addr_t __init early_pgtable_alloc(void)
      return phys;
 }
 
+/*
+ * The following mapping attributes may be updated in live
+ * kernel mappings without the need for break-before-make.
+ */
+static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
+
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
                                unsigned long end, unsigned long pfn,
                                pgprot_t prot,
@@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 
      pte = pte_set_fixmap_offset(pmd, addr);
      do {
+             pte_t old_pte = *pte;
+

arch/arm64/mm/mmu.c static void alloc_init_pte(pmd_t *pmd, unsigned long addr, · line 133

Quoted 10 lines
arch/arm64/mm/mmu.c +8 -0
              set_pte(pte, pfn_pte(pfn, prot));
              pfn++;
+
+             /*
+              * After the PTE entry has been populated once, we
+              * only allow updates to the permission attributes.
+              */
+             BUG_ON(pte_val(old_pte) != 0 &&
+                    ((pte_val(old_pte) ^ pte_val(*pte)) &
+                     ~modifiable_attr_mask) != 0);

Please turn this check into a single macro. You have it in three places already (though with different types but a macro would do). Something like below (feel free to come up with a better macro name):

Quoted 2 lines
                BUG_ON(!safe_pgattr_change(old_pte, *pte));

Something like below? With that, I can also fold the PMD and PUD versions of the BUG() together.

"""
/*
* Returns whether updating a live page table entry is safe:
* - if the old and new values are identical,
* - if an invalid mapping is turned into a valid one (or vice versa),
* - if the entry is a block or page mapping, and the old and new values
* only differ in the PXN/RDONLY/WRITE bits.
*
* NOTE: 'safe' does not imply that no TLB maintenance is required, it only
* means that no TLB conflicts should occur as a result of the update.
*/
#define __set_pgattr_is_safe(type, old, new, blocktype) \
(type ## _val(old) == type ## _val(new) || \
((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
(((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
(((type ## _val(old) ^ type ## _val(new)) & \
~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new) {
return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE); }

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new) {
return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT); }

static inline bool set_live_pud_is_safe(pud_t old, pud_t new) {
return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT); }
"""

maintainer
#4 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 32 lines · Ard Biesheuvel
Quoted 31 lines · Catalin Marinas
Quoted 15 lines · Ard Biesheuvel
arch/arm64/mm/mmu.c +8 -0
+/*
+ * The following mapping attributes may be updated in live
+ * kernel mappings without the need for break-before-make.
+ */
+static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
+
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
                                unsigned long end, unsigned long pfn,
                                pgprot_t prot,
@@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 
      pte = pte_set_fixmap_offset(pmd, addr);
      do {
+             pte_t old_pte = *pte;
+

arch/arm64/mm/mmu.c static void alloc_init_pte(pmd_t *pmd, unsigned long addr, · line 133

Quoted 10 lines
arch/arm64/mm/mmu.c +8 -0
              set_pte(pte, pfn_pte(pfn, prot));
              pfn++;
+
+             /*
+              * After the PTE entry has been populated once, we
+              * only allow updates to the permission attributes.
+              */
+             BUG_ON(pte_val(old_pte) != 0 &&
+                    ((pte_val(old_pte) ^ pte_val(*pte)) &
+                     ~modifiable_attr_mask) != 0);

Please turn this check into a single macro. You have it in three places already (though with different types but a macro would do). Something like below (feel free to come up with a better macro name):

Quoted 4 lines
Quoted 1 line
                BUG_ON(!safe_pgattr_change(old_pte, *pte));

Something like below? With that, I can also fold the PMD and PUD versions of the BUG() together.

(fixing up alignment to make it readable)

Quoted 24 lines

"""
/*
* Returns whether updating a live page table entry is safe:
* - if the old and new values are identical,
* - if an invalid mapping is turned into a valid one (or vice versa),
* - if the entry is a block or page mapping, and the old and new values
* only differ in the PXN/RDONLY/WRITE bits.
*
* NOTE: 'safe' does not imply that no TLB maintenance is required, it only
* means that no TLB conflicts should occur as a result of the update.
*/

#define __set_pgattr_is_safe(type, old, new, blocktype) \
	(type ## _val(old) == type ## _val(new) || \
	 ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
	 (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
	  (((type ## _val(old) ^ type ## _val(new)) & \
	 ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

{
	return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);
}

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

Quoted 8 lines
{
	return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);
}

static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

{
	return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);
}

The set_ prefix is slightly confusing as it suggests (to me) having a side effect. Maybe pgattr_set_is_safe()?

But it looks like we make it more complicated needed by using pte_t instead of pteval_t as argument. How about just using the pteval_t as argument (and it's fine to call it with pmdval_t, pudval_t as well):

#define pgattr_set_is_safe(oldval, newval) \
	...
Signature · 1 line
Catalin
#5 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 4 lines · Catalin Marinas

On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

(fixing up alignment to make it readable)

I apologise on Gmail's behalf

Quoted 36 lines
Quoted 24 lines

"""
/*
* Returns whether updating a live page table entry is safe:
* - if the old and new values are identical,
* - if an invalid mapping is turned into a valid one (or vice versa),
* - if the entry is a block or page mapping, and the old and new values
* only differ in the PXN/RDONLY/WRITE bits.
*
* NOTE: 'safe' does not imply that no TLB maintenance is required, it only
* means that no TLB conflicts should occur as a result of the update.
*/

#define __set_pgattr_is_safe(type, old, new, blocktype) \
      (type ## _val(old) == type ## _val(new) || \
       ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
       (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
        (((type ## _val(old) ^ type ## _val(new)) & \
       ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

{
      return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);
}

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

Quoted 8 lines
{
      return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);
}

static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

{
      return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);
}

The set_ prefix is slightly confusing as it suggests (to me) having a side effect. Maybe pgattr_set_is_safe()?

Quoted 7 lines

But it looks like we make it more complicated needed by using pte_t instead of pteval_t as argument. How about just using the pteval_t as argument (and it's fine to call it with pmdval_t, pudval_t as well):

#define pgattr_set_is_safe(oldval, newval) \
        ...

Well, the only problem there is that the permission bit check should only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block mappings (bit[1] == 0), so we would still need two versions

maintainer
#6 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 48 lines · Ard Biesheuvel
Quoted 4 lines · Catalin Marinas

On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

(fixing up alignment to make it readable)

I apologise on Gmail's behalf

Quoted 40 lines
Quoted 24 lines

"""
/*
* Returns whether updating a live page table entry is safe:
* - if the old and new values are identical,
* - if an invalid mapping is turned into a valid one (or vice versa),
* - if the entry is a block or page mapping, and the old and new values
* only differ in the PXN/RDONLY/WRITE bits.
*
* NOTE: 'safe' does not imply that no TLB maintenance is required, it only
* means that no TLB conflicts should occur as a result of the update.
*/

#define __set_pgattr_is_safe(type, old, new, blocktype) \
      (type ## _val(old) == type ## _val(new) || \
       ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
       (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
        (((type ## _val(old) ^ type ## _val(new)) & \
       ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

{
      return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);
}

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

Quoted 8 lines
{
      return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);
}

static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

{
      return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);
}

The set_ prefix is slightly confusing as it suggests (to me) having a side effect. Maybe pgattr_set_is_safe()?

But it looks like we make it more complicated needed by using pte_t instead of pteval_t as argument. How about just using the pteval_t as argument (and it's fine to call it with pmdval_t, pudval_t as well):

Quoted 6 lines
Quoted 2 lines
#define pgattr_set_is_safe(oldval, newval) \
        ...

Well, the only problem there is that the permission bit check should only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block mappings (bit[1] == 0), so we would still need two versions

I was suggesting that you only replace the "... & ~modifiable_attr_mask" check in your patch to avoid writing it three times. The macro that you proposed above does more but it is also more unreadable.

Signature · 1 line
Catalin
#7 lore raw

PATCH v3 1/5 arm64: mm: BUG on unsupported manipulations of live kernel mappings

Quoted 52 lines · Catalin Marinas
Quoted 51 lines · Ard Biesheuvel
Quoted 4 lines · Catalin Marinas

On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

(fixing up alignment to make it readable)

I apologise on Gmail's behalf

Quoted 42 lines
Quoted 24 lines

"""
/*
* Returns whether updating a live page table entry is safe:
* - if the old and new values are identical,
* - if an invalid mapping is turned into a valid one (or vice versa),
* - if the entry is a block or page mapping, and the old and new values
* only differ in the PXN/RDONLY/WRITE bits.
*
* NOTE: 'safe' does not imply that no TLB maintenance is required, it only
* means that no TLB conflicts should occur as a result of the update.
*/

#define __set_pgattr_is_safe(type, old, new, blocktype) \
      (type ## _val(old) == type ## _val(new) || \
       ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
       (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
        (((type ## _val(old) ^ type ## _val(new)) & \
       ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

{
      return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);
}

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

Quoted 8 lines
{
      return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);
}

static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

{
      return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);
}

The set_ prefix is slightly confusing as it suggests (to me) having a side effect. Maybe pgattr_set_is_safe()?

But it looks like we make it more complicated needed by using pte_t instead of pteval_t as argument. How about just using the pteval_t as argument (and it's fine to call it with pmdval_t, pudval_t as well):

#define pgattr_set_is_safe(oldval, newval) \
        ...
Quoted 8 lines
Quoted 3 lines

Well, the only problem there is that the permission bit check should only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block mappings (bit[1] == 0), so we would still need two versions

I was suggesting that you only replace the "... & ~modifiable_attr_mask" check in your patch to avoid writing it three times. The macro that you proposed above does more but it is also more unreadable.

Ah ok, fair enough

#8 lore raw

PATCH v3 2/5 arm64: mm: replace 'block_mappings_allowed' with 'page_mappings_only'

In reply to Ard Biesheuvel

In preparation of adding support for contiguous PTE and PMD mappings, let's replace 'block_mappings_allowed' with 'page_mappings_only', which will be a more accurate description of the nature of the setting once we add such contiguous mappings into the mix.

---
 arch/arm64/include/asm/mmu.h |  2 +-
 arch/arm64/kernel/efi.c      |  8 ++---
 arch/arm64/mm/mmu.c          | 32 ++++++++++----------
 3 files changed, 21 insertions(+), 21 deletions(-)
arch/arm64/include/asm/mmu.h +1 -1
index 8d9fce037b2f..a81454ad5455 100644
@@ -34,7 +34,7 @@ extern void __iomem *early_io_map(phys_addr_t phys, unsigned long virt);
 extern void init_mem_pgprot(void);
 extern void create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
 			       unsigned long virt, phys_addr_t size,
-			       pgprot_t prot, bool allow_block_mappings);
+			       pgprot_t prot, bool page_mappings_only);
 extern void *fixmap_remap_fdt(phys_addr_t dt_phys);
 
 #endif
arch/arm64/kernel/efi.c +4 -4
index ba9bee389fd5..5d17f377d905 100644
@@ -62,8 +62,8 @@ struct screen_info screen_info __section(.data);
 int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
 {
 	pteval_t prot_val = create_mapping_protection(md);
-	bool allow_block_mappings = (md->type != EFI_RUNTIME_SERVICES_CODE &&
-				     md->type != EFI_RUNTIME_SERVICES_DATA);
+	bool page_mappings_only = (md->type == EFI_RUNTIME_SERVICES_CODE ||
+				   md->type == EFI_RUNTIME_SERVICES_DATA);
 
 	if (!PAGE_ALIGNED(md->phys_addr) ||
 	    !PAGE_ALIGNED(md->num_pages << EFI_PAGE_SHIFT)) {
@@ -76,12 +76,12 @@ int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
 		 * from the MMU routines. So avoid block mappings altogether in
 		 * that case.
 		 */
-		allow_block_mappings = false;
+		page_mappings_only = true;
 	}
 
 	create_pgd_mapping(mm, md->phys_addr, md->virt_addr,
 			   md->num_pages << EFI_PAGE_SHIFT,
-			   __pgprot(prot_val | PTE_NG), allow_block_mappings);
+			   __pgprot(prot_val | PTE_NG), page_mappings_only);
 	return 0;
 }
 
arch/arm64/mm/mmu.c +16 -16
index e1c34e5a1d7d..bf1d71b62c4f 100644
@@ -139,7 +139,7 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 				  phys_addr_t phys, pgprot_t prot,
 				  phys_addr_t (*pgtable_alloc)(void),
-				  bool allow_block_mappings)
+				  bool page_mappings_only)
 {
 	pmd_t *pmd;
 	unsigned long next;
@@ -166,7 +166,7 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 
 		/* try section mapping first */
 		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
-		      allow_block_mappings) {
+		      !page_mappings_only) {
 			pmd_set_huge(pmd, phys, prot);
 
 			/*
@@ -204,7 +204,7 @@ static inline bool use_1G_block(unsigned long addr, unsigned long next,
 static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 				  phys_addr_t phys, pgprot_t prot,
 				  phys_addr_t (*pgtable_alloc)(void),
-				  bool allow_block_mappings)
+				  bool page_mappings_only)
 {
 	pud_t *pud;
 	unsigned long next;
@@ -226,7 +226,7 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 		/*
 		 * For 4K granule only, attempt to put down a 1GB block
 		 */
-		if (use_1G_block(addr, next, phys) && allow_block_mappings) {
+		if (use_1G_block(addr, next, phys) && !page_mappings_only) {
 			pud_set_huge(pud, phys, prot);
 
 			/*
@@ -238,7 +238,7 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pmd(pud, addr, next, phys, prot,
-				       pgtable_alloc, allow_block_mappings);
+				       pgtable_alloc, page_mappings_only);
 
 			BUG_ON(pud_val(old_pud) != 0 &&
 			       pud_val(old_pud) != pud_val(*pud));
@@ -253,7 +253,7 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 				 unsigned long virt, phys_addr_t size,
 				 pgprot_t prot,
 				 phys_addr_t (*pgtable_alloc)(void),
-				 bool allow_block_mappings)
+				 bool page_mappings_only)
 {
 	unsigned long addr, length, end, next;
 	pgd_t *pgd = pgd_offset_raw(pgdir, virt);
@@ -273,7 +273,7 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 	do {
 		next = pgd_addr_end(addr, end);
 		alloc_init_pud(pgd, addr, next, phys, prot, pgtable_alloc,
-			       allow_block_mappings);
+			       page_mappings_only);
 		phys += next - addr;
 	} while (pgd++, addr = next, addr != end);
 }
@@ -302,17 +302,17 @@ static void __init create_mapping_noalloc(phys_addr_t phys, unsigned long virt,
 			&phys, virt);
 		return;
 	}
-	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, true);
+	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, false);
 }
 
 void __init create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
 			       unsigned long virt, phys_addr_t size,
-			       pgprot_t prot, bool allow_block_mappings)
+			       pgprot_t prot, bool page_mappings_only)
 {
 	BUG_ON(mm == &init_mm);
 
 	__create_pgd_mapping(mm->pgd, phys, virt, size, prot,
-			     pgd_pgtable_alloc, allow_block_mappings);
+			     pgd_pgtable_alloc, page_mappings_only);
 }
 
 static void create_mapping_late(phys_addr_t phys, unsigned long virt,
@@ -325,7 +325,7 @@ static void create_mapping_late(phys_addr_t phys, unsigned long virt,
 	}
 
 	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot,
-			     NULL, !debug_pagealloc_enabled());
+			     NULL, debug_pagealloc_enabled());
 }
 
 static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end)
@@ -343,7 +343,7 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 		__create_pgd_mapping(pgd, start, __phys_to_virt(start),
 				     end - start, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 		return;
 	}
 
@@ -356,13 +356,13 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 				     __phys_to_virt(start),
 				     kernel_start - start, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 	if (kernel_end < end)
 		__create_pgd_mapping(pgd, kernel_end,
 				     __phys_to_virt(kernel_end),
 				     end - kernel_end, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 
 	/*
 	 * Map the linear alias of the [_text, __init_begin) interval as
@@ -372,7 +372,7 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 	 */
 	__create_pgd_mapping(pgd, kernel_start, __phys_to_virt(kernel_start),
 			     kernel_end - kernel_start, PAGE_KERNEL_RO,
-			     early_pgtable_alloc, !debug_pagealloc_enabled());
+			     early_pgtable_alloc, debug_pagealloc_enabled());
 }
 
 static void __init map_mem(pgd_t *pgd)
@@ -422,7 +422,7 @@ static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end,
 	BUG_ON(!PAGE_ALIGNED(size));
 
 	__create_pgd_mapping(pgd, pa_start, (unsigned long)va_start, size, prot,
-			     early_pgtable_alloc, !debug_pagealloc_enabled());
+			     early_pgtable_alloc, debug_pagealloc_enabled());
 
 	vma->addr	= va_start;
 	vma->phys_addr	= pa_start;
Signature · 1 line
2.7.4
reviewer
#9 lore raw

PATCH v3 2/5 arm64: mm: replace 'block_mappings_allowed' with 'page_mappings_only'

Quoted 6 lines · Ard Biesheuvel

In preparation of adding support for contiguous PTE and PMD mappings, let's replace 'block_mappings_allowed' with 'page_mappings_only', which will be a more accurate description of the nature of the setting once we add such contiguous mappings into the mix.

Regardless of the contiguous bit stuff, I think this makes the code clearer. As far as I can tell, this is correct. So FWIW:

Thanks,
Mark.

Quoted 192 lines
---
 arch/arm64/include/asm/mmu.h |  2 +-
 arch/arm64/kernel/efi.c      |  8 ++---
 arch/arm64/mm/mmu.c          | 32 ++++++++++----------
 3 files changed, 21 insertions(+), 21 deletions(-)
arch/arm64/include/asm/mmu.h +1 -1
index 8d9fce037b2f..a81454ad5455 100644
@@ -34,7 +34,7 @@ extern void __iomem *early_io_map(phys_addr_t phys, unsigned long virt);
 extern void init_mem_pgprot(void);
 extern void create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
 			       unsigned long virt, phys_addr_t size,
-			       pgprot_t prot, bool allow_block_mappings);
+			       pgprot_t prot, bool page_mappings_only);
 extern void *fixmap_remap_fdt(phys_addr_t dt_phys);
 
 #endif
arch/arm64/kernel/efi.c +4 -4
index ba9bee389fd5..5d17f377d905 100644
@@ -62,8 +62,8 @@ struct screen_info screen_info __section(.data);
 int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
 {
 	pteval_t prot_val = create_mapping_protection(md);
-	bool allow_block_mappings = (md->type != EFI_RUNTIME_SERVICES_CODE &&
-				     md->type != EFI_RUNTIME_SERVICES_DATA);
+	bool page_mappings_only = (md->type == EFI_RUNTIME_SERVICES_CODE ||
+				   md->type == EFI_RUNTIME_SERVICES_DATA);
 
 	if (!PAGE_ALIGNED(md->phys_addr) ||
 	    !PAGE_ALIGNED(md->num_pages << EFI_PAGE_SHIFT)) {
@@ -76,12 +76,12 @@ int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
 		 * from the MMU routines. So avoid block mappings altogether in
 		 * that case.
 		 */
-		allow_block_mappings = false;
+		page_mappings_only = true;
 	}
 
 	create_pgd_mapping(mm, md->phys_addr, md->virt_addr,
 			   md->num_pages << EFI_PAGE_SHIFT,
-			   __pgprot(prot_val | PTE_NG), allow_block_mappings);
+			   __pgprot(prot_val | PTE_NG), page_mappings_only);
 	return 0;
 }
 
arch/arm64/mm/mmu.c +16 -16
index e1c34e5a1d7d..bf1d71b62c4f 100644
@@ -139,7 +139,7 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 				  phys_addr_t phys, pgprot_t prot,
 				  phys_addr_t (*pgtable_alloc)(void),
-				  bool allow_block_mappings)
+				  bool page_mappings_only)
 {
 	pmd_t *pmd;
 	unsigned long next;
@@ -166,7 +166,7 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 
 		/* try section mapping first */
 		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
-		      allow_block_mappings) {
+		      !page_mappings_only) {
 			pmd_set_huge(pmd, phys, prot);
 
 			/*
@@ -204,7 +204,7 @@ static inline bool use_1G_block(unsigned long addr, unsigned long next,
 static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 				  phys_addr_t phys, pgprot_t prot,
 				  phys_addr_t (*pgtable_alloc)(void),
-				  bool allow_block_mappings)
+				  bool page_mappings_only)
 {
 	pud_t *pud;
 	unsigned long next;
@@ -226,7 +226,7 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 		/*
 		 * For 4K granule only, attempt to put down a 1GB block
 		 */
-		if (use_1G_block(addr, next, phys) && allow_block_mappings) {
+		if (use_1G_block(addr, next, phys) && !page_mappings_only) {
 			pud_set_huge(pud, phys, prot);
 
 			/*
@@ -238,7 +238,7 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pmd(pud, addr, next, phys, prot,
-				       pgtable_alloc, allow_block_mappings);
+				       pgtable_alloc, page_mappings_only);
 
 			BUG_ON(pud_val(old_pud) != 0 &&
 			       pud_val(old_pud) != pud_val(*pud));
@@ -253,7 +253,7 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 				 unsigned long virt, phys_addr_t size,
 				 pgprot_t prot,
 				 phys_addr_t (*pgtable_alloc)(void),
-				 bool allow_block_mappings)
+				 bool page_mappings_only)
 {
 	unsigned long addr, length, end, next;
 	pgd_t *pgd = pgd_offset_raw(pgdir, virt);
@@ -273,7 +273,7 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 	do {
 		next = pgd_addr_end(addr, end);
 		alloc_init_pud(pgd, addr, next, phys, prot, pgtable_alloc,
-			       allow_block_mappings);
+			       page_mappings_only);
 		phys += next - addr;
 	} while (pgd++, addr = next, addr != end);
 }
@@ -302,17 +302,17 @@ static void __init create_mapping_noalloc(phys_addr_t phys, unsigned long virt,
 			&phys, virt);
 		return;
 	}
-	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, true);
+	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, false);
 }
 
 void __init create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
 			       unsigned long virt, phys_addr_t size,
-			       pgprot_t prot, bool allow_block_mappings)
+			       pgprot_t prot, bool page_mappings_only)
 {
 	BUG_ON(mm == &init_mm);
 
 	__create_pgd_mapping(mm->pgd, phys, virt, size, prot,
-			     pgd_pgtable_alloc, allow_block_mappings);
+			     pgd_pgtable_alloc, page_mappings_only);
 }
 
 static void create_mapping_late(phys_addr_t phys, unsigned long virt,
@@ -325,7 +325,7 @@ static void create_mapping_late(phys_addr_t phys, unsigned long virt,
 	}
 
 	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot,
-			     NULL, !debug_pagealloc_enabled());
+			     NULL, debug_pagealloc_enabled());
 }
 
 static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end)
@@ -343,7 +343,7 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 		__create_pgd_mapping(pgd, start, __phys_to_virt(start),
 				     end - start, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 		return;
 	}
 
@@ -356,13 +356,13 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 				     __phys_to_virt(start),
 				     kernel_start - start, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 	if (kernel_end < end)
 		__create_pgd_mapping(pgd, kernel_end,
 				     __phys_to_virt(kernel_end),
 				     end - kernel_end, PAGE_KERNEL,
 				     early_pgtable_alloc,
-				     !debug_pagealloc_enabled());
+				     debug_pagealloc_enabled());
 
 	/*
 	 * Map the linear alias of the [_text, __init_begin) interval as
@@ -372,7 +372,7 @@ static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end
 	 */
 	__create_pgd_mapping(pgd, kernel_start, __phys_to_virt(kernel_start),
 			     kernel_end - kernel_start, PAGE_KERNEL_RO,
-			     early_pgtable_alloc, !debug_pagealloc_enabled());
+			     early_pgtable_alloc, debug_pagealloc_enabled());
 }
 
 static void __init map_mem(pgd_t *pgd)
@@ -422,7 +422,7 @@ static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end,
 	BUG_ON(!PAGE_ALIGNED(size));
 
 	__create_pgd_mapping(pgd, pa_start, (unsigned long)va_start, size, prot,
-			     early_pgtable_alloc, !debug_pagealloc_enabled());
+			     early_pgtable_alloc, debug_pagealloc_enabled());
 
 	vma->addr	= va_start;
 	vma->phys_addr	= pa_start;
Signature · 1 line
2.7.4
#10 lore raw

PATCH v3 3/5 arm64: mm: set the contiguous bit for kernel mappings where appropriate

In reply to Ard Biesheuvel

Now that we no longer allow live kernel PMDs to be split, it is safe to start using the contiguous bit for kernel mappings. So set the contiguous bit in the kernel page mappings for regions whose size and alignment are suitable for this.

This enables the following contiguous range sizes for the virtual mapping of the kernel image, and for the linear mapping:

granule size | cont PTE | cont PMD |
-------------+------------+------------+
4 KB | 64 KB | 32 MB |
16 KB | 2 MB | 1 GB* |
64 KB | 2 MB | 16 GB* |

* only when built for 3 or more levels of translation

---
 arch/arm64/mm/mmu.c | 35 +++++++++++++++++---
 1 file changed, 31 insertions(+), 4 deletions(-)
arch/arm64/mm/mmu.c +31 -4
index bf1d71b62c4f..40be4979102d 100644
@@ -102,8 +102,10 @@ static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 				  unsigned long end, unsigned long pfn,
 				  pgprot_t prot,
-				  phys_addr_t (*pgtable_alloc)(void))
+				  phys_addr_t (*pgtable_alloc)(void),
+				  bool page_mappings_only)
 {
+	pgprot_t __prot = prot;
 	pte_t *pte;
 
 	BUG_ON(pmd_sect(*pmd));
@@ -121,7 +123,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 	do {
 		pte_t old_pte = *pte;
 
-		set_pte(pte, pfn_pte(pfn, prot));
+		/*
+		 * Set the contiguous bit for the subsequent group of PTEs if
+		 * its size and alignment are appropriate.
+		 */
+		if (((addr | PFN_PHYS(pfn)) & ~CONT_PTE_MASK) == 0) {
+			if (end - addr >= CONT_PTE_SIZE && !page_mappings_only)
+				__prot = __pgprot(pgprot_val(prot) | PTE_CONT);
+			else
+				__prot = prot;
+		}
+
+		set_pte(pte, pfn_pte(pfn, __prot));
 		pfn++;
 
 		/*
@@ -141,6 +154,7 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 				  phys_addr_t (*pgtable_alloc)(void),
 				  bool page_mappings_only)
 {
+	pgprot_t __prot = prot;
 	pmd_t *pmd;
 	unsigned long next;
 
@@ -167,7 +181,19 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 		/* try section mapping first */
 		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
 		      !page_mappings_only) {
-			pmd_set_huge(pmd, phys, prot);
+			/*
+			 * Set the contiguous bit for the subsequent group of
+			 * PMDs if its size and alignment are appropriate.
+			 */
+			if (((addr | phys) & ~CONT_PMD_MASK) == 0) {
+				if (end - addr >= CONT_PMD_SIZE)
+					__prot = __pgprot(pgprot_val(prot) |
+							  PTE_CONT);
+				else
+					__prot = prot;
+			}
+
+			pmd_set_huge(pmd, phys, __prot);
 
 			/*
 			 * After the PMD entry has been populated once, we
@@ -178,7 +204,8 @@ static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pte(pmd, addr, next, __phys_to_pfn(phys),
-				       prot, pgtable_alloc);
+				       prot, pgtable_alloc,
+				       page_mappings_only);
 
 			BUG_ON(pmd_val(old_pmd) != 0 &&
 			       pmd_val(old_pmd) != pmd_val(*pmd));
Signature · 1 line
2.7.4
maintainer
#11 lore raw

PATCH v3 3/5 arm64: mm: set the contiguous bit for kernel mappings where appropriate

Quoted 8 lines · Ard Biesheuvel

Now that we no longer allow live kernel PMDs to be split, it is safe to start using the contiguous bit for kernel mappings. So set the contiguous bit in the kernel page mappings for regions whose size and alignment are suitable for this.

This enables the following contiguous range sizes for the virtual mapping of the kernel image, and for the linear mapping:

Quoted 7 lines

granule size | cont PTE | cont PMD |
-------------+------------+------------+
4 KB | 64 KB | 32 MB |
16 KB | 2 MB | 1 GB* |
64 KB | 2 MB | 16 GB* |

* only when built for 3 or more levels of translation

I assume the limitation to have contiguous PMD only with 3 or move levels is because of the way p*d folding was implemented in the kernel. With nopmd, looping over pmds is done in __create_pgd_mapping() rather than alloc_init_pmd().

A potential solution would be to replicate the contiguous pmd code to the pud and pgd level, though we probably won't benefit from any contiguous entries at higher level (when more than 2 levels). Alternatively, with an #ifdef __PGTABLE_PMD_FOLDED, we could set the PMD_CONT in prot in __create_pgd_mapping() directly (if the right addr/phys alignment).

Anyway, it's probably not worth the effort given that 42-bit VA with 64K pages is becoming a less likely configuration (36-bit VA with 16K pages is even less likely, also depending on EXPERT).

Signature · 1 line
Catalin
#12 lore raw

PATCH v3 3/5 arm64: mm: set the contiguous bit for kernel mappings where appropriate

Quoted 22 lines · Catalin Marinas
Quoted 8 lines · Ard Biesheuvel

Now that we no longer allow live kernel PMDs to be split, it is safe to start using the contiguous bit for kernel mappings. So set the contiguous bit in the kernel page mappings for regions whose size and alignment are suitable for this.

This enables the following contiguous range sizes for the virtual mapping of the kernel image, and for the linear mapping:

Quoted 7 lines

granule size | cont PTE | cont PMD |
-------------+------------+------------+
4 KB | 64 KB | 32 MB |
16 KB | 2 MB | 1 GB* |
64 KB | 2 MB | 16 GB* |

* only when built for 3 or more levels of translation

I assume the limitation to have contiguous PMD only with 3 or move levels is because of the way p*d folding was implemented in the kernel. With nopmd, looping over pmds is done in __create_pgd_mapping() rather than alloc_init_pmd().

Quoted 7 lines

A potential solution would be to replicate the contiguous pmd code to the pud and pgd level, though we probably won't benefit from any contiguous entries at higher level (when more than 2 levels). Alternatively, with an #ifdef __PGTABLE_PMD_FOLDED, we could set the PMD_CONT in prot in __create_pgd_mapping() directly (if the right addr/phys alignment).

Indeed. See the next patch :-)

Quoted 4 lines

Anyway, it's probably not worth the effort given that 42-bit VA with 64K pages is becoming a less likely configuration (36-bit VA with 16K pages is even less likely, also depending on EXPERT).

This is the reason I put it in a separate patch: this one contains the most useful combinations, and the next patch adds the missing ones, but clutters up the code significantly. I'm perfectly happy to drop 4 and 5 if you don't think it is worth the trouble.

maintainer
#13 lore raw

PATCH v3 3/5 arm64: mm: set the contiguous bit for kernel mappings where appropriate

Quoted 23 lines · Ard Biesheuvel
Quoted 22 lines · Catalin Marinas
Quoted 8 lines · Ard Biesheuvel

Now that we no longer allow live kernel PMDs to be split, it is safe to start using the contiguous bit for kernel mappings. So set the contiguous bit in the kernel page mappings for regions whose size and alignment are suitable for this.

This enables the following contiguous range sizes for the virtual mapping of the kernel image, and for the linear mapping:

Quoted 7 lines

granule size | cont PTE | cont PMD |
-------------+------------+------------+
4 KB | 64 KB | 32 MB |
16 KB | 2 MB | 1 GB* |
64 KB | 2 MB | 16 GB* |

* only when built for 3 or more levels of translation

I assume the limitation to have contiguous PMD only with 3 or move levels is because of the way p*d folding was implemented in the kernel. With nopmd, looping over pmds is done in __create_pgd_mapping() rather than alloc_init_pmd().

Quoted 8 lines
Quoted 6 lines

A potential solution would be to replicate the contiguous pmd code to the pud and pgd level, though we probably won't benefit from any contiguous entries at higher level (when more than 2 levels). Alternatively, with an #ifdef __PGTABLE_PMD_FOLDED, we could set the PMD_CONT in prot in __create_pgd_mapping() directly (if the right addr/phys alignment).

Indeed. See the next patch :-)

I got there eventually ;).

Quoted 8 lines
Quoted 3 lines

Anyway, it's probably not worth the effort given that 42-bit VA with 64K pages is becoming a less likely configuration (36-bit VA with 16K pages is even less likely, also depending on EXPERT).

This is the reason I put it in a separate patch: this one contains the most useful combinations, and the next patch adds the missing ones, but clutters up the code significantly. I'm perfectly happy to drop 4 and 5 if you don't think it is worth the trouble.

I'll have a look at patch 4 first.

Both 64KB contiguous pmd and 4K contiguous pud give us a 16GB range which (AFAIK) is less likely to be optimised in hardware.

Signature · 1 line
Catalin
#14 lore raw

PATCH v3 4/5 arm64: mm: support additional contiguous kernel mapping region sizes

In reply to Ard Biesheuvel

Extend the basic support for kernel mappings using contiguous regions by adding support for contiguous PUDs (4k granule only), either as a discrete level or folded into the PGDs. In the same way, handle folded PMDs so that contiguous PMDs (for 16k and 64k granule kernels) will work as expected for 2 levels of translation as well.

---
 arch/arm64/include/asm/pgtable-hwdef.h |  6 +++
 arch/arm64/mm/mmu.c                    | 40 +++++++++++++++++++-
 2 files changed, 44 insertions(+), 2 deletions(-)
arch/arm64/include/asm/pgtable-hwdef.h +6 -0
index eb0c2bd90de9..4192072af932 100644
@@ -93,12 +93,15 @@
 #ifdef CONFIG_ARM64_64K_PAGES
 #define CONT_PTE_SHIFT		5
 #define CONT_PMD_SHIFT		5
+#define CONT_PUD_SHIFT		0
 #elif defined(CONFIG_ARM64_16K_PAGES)
 #define CONT_PTE_SHIFT		7
 #define CONT_PMD_SHIFT		5
+#define CONT_PUD_SHIFT		0
 #else
 #define CONT_PTE_SHIFT		4
 #define CONT_PMD_SHIFT		4
+#define CONT_PUD_SHIFT		4
 #endif
 
 #define CONT_PTES		(1 << CONT_PTE_SHIFT)
@@ -107,6 +110,9 @@
 #define CONT_PMDS		(1 << CONT_PMD_SHIFT)
 #define CONT_PMD_SIZE		(CONT_PMDS * PMD_SIZE)
 #define CONT_PMD_MASK		(~(CONT_PMD_SIZE - 1))
+#define CONT_PUDS		(1 << CONT_PUD_SHIFT)
+#define CONT_PUD_SIZE		(CONT_PUDS * PUD_SIZE)
+#define CONT_PUD_MASK		(~(CONT_PUD_SIZE - 1))
 /* the the numerical offset of the PTE within a range of CONT_PTES */
 #define CONT_RANGE_OFFSET(addr) (((addr)>>PAGE_SHIFT)&(CONT_PTES-1))
 
arch/arm64/mm/mmu.c +38 -2
index 40be4979102d..0e0eca45b54a 100644
@@ -233,6 +233,7 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 				  phys_addr_t (*pgtable_alloc)(void),
 				  bool page_mappings_only)
 {
+	pgprot_t __prot = prot;
 	pud_t *pud;
 	unsigned long next;
 
@@ -254,7 +255,19 @@ static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 		 * For 4K granule only, attempt to put down a 1GB block
 		 */
 		if (use_1G_block(addr, next, phys) && !page_mappings_only) {
-			pud_set_huge(pud, phys, prot);
+			/*
+			 * Set the contiguous bit for the subsequent group of
+			 * PUDs if its size and alignment are appropriate.
+			 */
+			if (((addr | phys) & ~CONT_PUD_MASK) == 0) {
+				if (end - addr >= CONT_PUD_SIZE)
+					__prot = __pgprot(pgprot_val(prot) |
+							  PTE_CONT);
+				else
+					__prot = prot;
+			}
+
+			pud_set_huge(pud, phys, __prot);
 
 			/*
 			 * After the PUD entry has been populated once, we
@@ -284,6 +297,7 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 {
 	unsigned long addr, length, end, next;
 	pgd_t *pgd = pgd_offset_raw(pgdir, virt);
+	pgprot_t __prot = prot;
 
 	/*
 	 * If the virtual and physical address don't have the same offset
@@ -299,7 +313,29 @@ static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
 	end = addr + length;
 	do {
 		next = pgd_addr_end(addr, end);
-		alloc_init_pud(pgd, addr, next, phys, prot, pgtable_alloc,
+
+		/*
+		 * If any intermediate levels are folded into the PGDs, we
+		 * need to deal with the contiguous attributes here, since
+		 * the contiguity can only be observed at this level.
+		 */
+		if (PGDIR_SHIFT == PMD_SHIFT && !page_mappings_only &&
+		    ((addr | phys) & ~CONT_PMD_MASK) == 0) {
+			if (end - addr >= CONT_PMD_SIZE)
+				__prot = __pgprot(pgprot_val(prot) |
+						  PTE_CONT);
+			else
+				__prot = prot;
+		} else if (PGDIR_SHIFT == PUD_SHIFT && CONT_PUD_SHIFT > 0 &&
+			   !page_mappings_only &&
+			   ((addr | phys) & ~CONT_PUD_MASK) == 0) {
+			if (end - addr >= CONT_PUD_SIZE)
+				__prot = __pgprot(pgprot_val(prot) |
+						  PTE_CONT);
+			else
+				__prot = prot;
+		}
+		alloc_init_pud(pgd, addr, next, phys, __prot, pgtable_alloc,
 			       page_mappings_only);
 		phys += next - addr;
 	} while (pgd++, addr = next, addr != end);
Signature · 1 line
2.7.4
maintainer
#15 lore raw

PATCH v3 4/5 arm64: mm: support additional contiguous kernel mapping region sizes

Quoted 11 lines · Ard Biesheuvel

Extend the basic support for kernel mappings using contiguous regions by adding support for contiguous PUDs (4k granule only), either as a discrete level or folded into the PGDs. In the same way, handle folded PMDs so that contiguous PMDs (for 16k and 64k granule kernels) will work as expected for 2 levels of translation as well.

---
 arch/arm64/include/asm/pgtable-hwdef.h |  6 +++
 arch/arm64/mm/mmu.c                    | 40 +++++++++++++++++++-
 2 files changed, 44 insertions(+), 2 deletions(-)

After looking at this patch, I concluded it's not worth hassle as no hardware I'm aware of currently would benefit from it. We can revisit it in the future if we hear otherwise.

Signature · 1 line
Catalin
#16 lore raw

PATCH v3 4/5 arm64: mm: support additional contiguous kernel mapping region sizes

Quoted 13 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel

Extend the basic support for kernel mappings using contiguous regions by adding support for contiguous PUDs (4k granule only), either as a discrete level or folded into the PGDs. In the same way, handle folded PMDs so that contiguous PMDs (for 16k and 64k granule kernels) will work as expected for 2 levels of translation as well.

---
 arch/arm64/include/asm/pgtable-hwdef.h |  6 +++
 arch/arm64/mm/mmu.c                    | 40 +++++++++++++++++++-
 2 files changed, 44 insertions(+), 2 deletions(-)
Quoted 4 lines

After looking at this patch, I concluded it's not worth hassle as no hardware I'm aware of currently would benefit from it. We can revisit it in the future if we hear otherwise.

That is what I expected, which is why this is a separate patch. I will drop this patch and the next one from the next version of the series.

#17 lore raw

PATCH v3 5/5 arm64: mm: round memstart_addr to contiguous PUD/PMD size

In reply to Ard Biesheuvel

Given that contiguous PUDs/PMDs allow the linear region to be mapped more efficiently, ensure that the relative alignment between the linear virtual and physical mappings of system RAM are equal modulo the contiguous PUD size (or contiguous PMD size, for 16k/64k granule size).

Note that this reduces the KASLR randomization of the linear range by PUD_SHIFT (or PMD_SHIFT) bits.

---
 arch/arm64/include/asm/kernel-pgtable.h | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
arch/arm64/include/asm/kernel-pgtable.h +4 -7
index 7e51d1b57c0c..71dbd9e1e809 100644
@@ -83,16 +83,13 @@
 /*
  * To make optimal use of block mappings when laying out the linear
  * mapping, round down the base of physical memory to a size that can
- * be mapped efficiently, i.e., either PUD_SIZE (4k granule) or PMD_SIZE
- * (64k granule), or a multiple that can be mapped using contiguous bits
- * in the page tables: 32 * PMD_SIZE (16k granule)
+ * be mapped efficiently, i.e., either CONT_PUD_SIZE (4k granule) or
+ * CONT_PMD_SIZE (16k/64k granules)
  */
 #if defined(CONFIG_ARM64_4K_PAGES)
-#define ARM64_MEMSTART_SHIFT		PUD_SHIFT
-#elif defined(CONFIG_ARM64_16K_PAGES)
-#define ARM64_MEMSTART_SHIFT		(PMD_SHIFT + 5)
+#define ARM64_MEMSTART_SHIFT		(PUD_SHIFT + CONT_PUD_SHIFT)
 #else
-#define ARM64_MEMSTART_SHIFT		PMD_SHIFT
+#define ARM64_MEMSTART_SHIFT		(PMD_SHIFT + CONT_PMD_SHIFT)
 #endif
 
 /*
Signature · 1 line
2.7.4