From: Sascha Hauer <s.hauer@pengutronix.de>
To: BAREBOX <barebox@lists.infradead.org>
Cc: "Claude Sonnet 4.5" <noreply@anthropic.com>
Subject: [PATCH v2 21/21] riscv: add ELF segment-based memory protection with MMU
Date: Tue, 06 Jan 2026 13:53:24 +0100 [thread overview]
Message-ID: <20260106-pbl-load-elf-v2-21-487bc760f045@pengutronix.de> (raw)
In-Reply-To: <20260106-pbl-load-elf-v2-0-487bc760f045@pengutronix.de>
Enable hardware-enforced W^X (Write XOR Execute) memory protection through
ELF segment-based permissions using the RISC-V MMU.
This implementation provides memory protection for RISC-V S-mode using
Sv39 (RV64) or Sv32 (RV32) page tables.
Linker Script Changes:
- Add PHDRS directives to pbl.lds.S and barebox.lds.S
- Create three separate PT_LOAD segments with proper permissions:
* text segment (FLAGS(5) = PF_R|PF_X): code sections
* rodata segment (FLAGS(4) = PF_R): read-only data
* data segment (FLAGS(6) = PF_R|PF_W): data and BSS
- Add 4K alignment between segments for page-granular protection
S-mode MMU Implementation (mmu.c):
- Implement page table walking for Sv39/Sv32
- pbl_remap_range(): remap segments with ELF-derived permissions
- mmu_early_enable(): create identity mapping and enable SATP CSR
- Map ELF flags to PTE bits:
* MAP_CODE → PTE_R | PTE_X (read + execute)
* MAP_CACHED_RO → PTE_R (read only)
* MAP_CACHED → PTE_R | PTE_W (read + write)
Integration:
- Update uncompress.c to call mmu_early_enable() before decompression
(enables caching for faster decompression)
- Call pbl_mmu_setup_from_elf() after ELF relocation to apply final
segment-based permissions
- Uses portable pbl/mmu.c infrastructure to parse PT_LOAD segments
Configuration:
- Add CONFIG_MMU option (default y for RISCV_S_MODE)
- Update asm/mmu.h with ARCH_HAS_REMAP and function declarations
Security Benefits:
- Text sections are read-only and executable (cannot be modified)
- Read-only data sections are read-only and non-executable
- Data sections are read-write and non-executable (cannot be executed)
- Hardware-enforced W^X prevents code injection attacks
This matches the ARM implementation philosophy and provides genuine
security improvements on RISC-V S-mode platforms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
arch/riscv/Kconfig | 17 ++
arch/riscv/boot/uncompress.c | 26 ++-
arch/riscv/cpu/Makefile | 1 +
arch/riscv/cpu/mmu.c | 386 +++++++++++++++++++++++++++++++++++++++++++
arch/riscv/cpu/mmu.h | 144 ++++++++++++++++
arch/riscv/include/asm/asm.h | 3 +-
arch/riscv/include/asm/mmu.h | 44 +++++
include/mmu.h | 8 +-
8 files changed, 622 insertions(+), 7 deletions(-)
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index d9794354f4ed2e8bf7276e03b968c566002c2ec6..99562c7df8927e11d4de448ad486e49dd5a0d0fd 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -129,4 +129,21 @@ config RISCV_MULTI_MODE
config RISCV_SBI
def_bool RISCV_S_MODE
+config MMU
+ bool "MMU-based memory protection"
+ default y if RISCV_S_MODE
+ help
+ Enable MMU (Memory Management Unit) support for RISC-V S-mode.
+ This provides hardware-enforced W^X (Write XOR Execute) memory
+ protection using page tables (Sv39 for RV64, Sv32 for RV32).
+
+ The PBL sets up page table entries based on ELF segment permissions,
+ ensuring that:
+ - Text sections are read-only and executable
+ - Read-only data sections are read-only and non-executable
+ - Data sections are read-write and non-executable
+
+ Say Y if running in S-mode (supervisor mode) with virtual memory.
+ Say N if running in M-mode or if you don't need memory protection.
+
endmenu
diff --git a/arch/riscv/boot/uncompress.c b/arch/riscv/boot/uncompress.c
index fba04cf4fb6ed450d80b83a8a595346a3186f1e7..10861724acc0f44fe431a0384ce15124d9198e4c 100644
--- a/arch/riscv/boot/uncompress.c
+++ b/arch/riscv/boot/uncompress.c
@@ -10,11 +10,14 @@
#include <init.h>
#include <linux/sizes.h>
#include <pbl.h>
+#include <pbl/mmu.h>
#include <asm/barebox-riscv.h>
#include <asm-generic/memory_layout.h>
#include <asm/sections.h>
#include <asm/unaligned.h>
+#include <asm/mmu.h>
#include <asm/irq.h>
+#include <elf.h>
#include <debug_ll.h>
@@ -63,6 +66,15 @@ void __noreturn barebox_pbl_start(unsigned long membase, unsigned long memsize,
free_mem_ptr = riscv_mem_early_malloc(membase, endmem);
free_mem_end_ptr = riscv_mem_early_malloc_end(membase, endmem);
+#ifdef CONFIG_MMU
+ /*
+ * Enable MMU early to enable caching for faster decompression.
+ * This creates an initial identity mapping that will be refined
+ * later based on ELF segments.
+ */
+ mmu_early_enable(membase, memsize, barebox_base);
+#endif
+
pr_debug("uncompressing barebox binary at 0x%p (size 0x%08x) to 0x%08lx (uncompressed size: 0x%08x)\n",
pg_start, pg_len, barebox_base, uncompressed_len);
@@ -82,10 +94,20 @@ void __noreturn barebox_pbl_start(unsigned long membase, unsigned long memsize,
hang();
}
+ pr_debug("ELF entry point: 0x%llx\n", elf.entry);
+
/*
- * TODO: Add pbl_mmu_setup_from_elf() call when RISC-V PBL
- * MMU support is implemented, similar to ARM
+ * Now that the ELF image is relocated, we know the exact addresses
+ * of all segments. Set up MMU with proper permissions based on
+ * ELF segment flags (PF_R/W/X).
*/
+#ifdef CONFIG_MMU
+ ret = pbl_mmu_setup_from_elf(&elf, membase, memsize);
+ if (ret) {
+ pr_err("Failed to setup memory protection from ELF: %d\n", ret);
+ hang();
+ }
+#endif
barebox = (void *)elf.entry;
diff --git a/arch/riscv/cpu/Makefile b/arch/riscv/cpu/Makefile
index d79bafc6f142a0060d2a86078f0fb969b298ba98..6bf31b574cd6242df6393fbdc8accc08dceb822a 100644
--- a/arch/riscv/cpu/Makefile
+++ b/arch/riscv/cpu/Makefile
@@ -7,3 +7,4 @@ obj-pbl-$(CONFIG_RISCV_M_MODE) += mtrap.o
obj-pbl-$(CONFIG_RISCV_S_MODE) += strap.o
obj-pbl-y += interrupts.o
endif
+obj-pbl-$(CONFIG_MMU) += mmu.o
diff --git a/arch/riscv/cpu/mmu.c b/arch/riscv/cpu/mmu.c
new file mode 100644
index 0000000000000000000000000000000000000000..6cf4586f364c98dd69105dfa1c558b560755b7d4
--- /dev/null
+++ b/arch/riscv/cpu/mmu.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: 2026 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+
+#define pr_fmt(fmt) "mmu: " fmt
+
+#include <common.h>
+#include <init.h>
+#include <mmu.h>
+#include <errno.h>
+#include <linux/sizes.h>
+#include <linux/bitops.h>
+#include <asm/sections.h>
+
+#include "mmu.h"
+
+#ifdef __PBL__
+
+/*
+ * Page table storage for early MMU setup in PBL.
+ * Static allocation before BSS is available.
+ */
+static char early_pt_storage[RISCV_EARLY_PAGETABLE_SIZE] __aligned(RISCV_PGSIZE);
+static unsigned int early_pt_idx;
+
+/*
+ * Allocate a page table from the early PBL storage
+ */
+static pte_t *alloc_pte(void)
+{
+ pte_t *pt;
+
+ if ((early_pt_idx + 1) * RISCV_PGSIZE >= RISCV_EARLY_PAGETABLE_SIZE) {
+ pr_err("Out of early page table memory (need more than %d KB)\n",
+ RISCV_EARLY_PAGETABLE_SIZE / 1024);
+ hang();
+ }
+
+ pt = (pte_t *)(early_pt_storage + early_pt_idx * RISCV_PGSIZE);
+ early_pt_idx++;
+
+ /* Clear the page table */
+ memset(pt, 0, RISCV_PGSIZE);
+
+ return pt;
+}
+
+/*
+ * split_pte - Split a megapage/gigapage PTE into a page table
+ * @pte: Pointer to the PTE to split
+ * @level: Current page table level (0-2 for Sv39)
+ *
+ * This function takes a leaf PTE (megapage/gigapage) and converts it into
+ * a page table pointer with 512 entries, each covering 1/512th of the
+ * original range with identical permissions.
+ *
+ * Example: A 2MB megapage at Level 1 becomes a Level 2 page table with
+ * 512 × 4KB pages, all with the same R/W/X attributes.
+ */
+static void split_pte(pte_t *pte, int level)
+{
+ pte_t old_pte = *pte;
+ pte_t *new_table;
+ pte_t phys_base;
+ pte_t attrs;
+ unsigned long granularity;
+ int i;
+
+ /* If already a table pointer (no RWX bits), nothing to do */
+ if (!(*pte & (PTE_R | PTE_W | PTE_X)))
+ return;
+
+ /* Allocate new page table (512 entries × 8 bytes = 4KB) */
+ new_table = alloc_pte();
+
+ /* Extract physical base address from old PTE */
+ phys_base = (old_pte >> PTE_PPN_SHIFT) << RISCV_PGSHIFT;
+
+ /* Extract permission attributes to replicate */
+ attrs = old_pte & (PTE_R | PTE_W | PTE_X | PTE_A | PTE_D | PTE_U | PTE_G);
+
+ /*
+ * Calculate granularity of child level.
+ * Level 0 (1GB) → Level 1 (2MB): granularity = 2MB = 1 << 21
+ * Level 1 (2MB) → Level 2 (4KB): granularity = 4KB = 1 << 12
+ *
+ * Formula: granularity = 1 << (12 + 9 * (Levels - 2 - level))
+ * For Sv39 (3 levels):
+ * level=0: 1 << (12 + 9*1) = 2MB
+ * level=1: 1 << (12 + 9*0) = 4KB
+ */
+ granularity = 1UL << (RISCV_PGSHIFT + RISCV_PGLEVEL_BITS *
+ (RISCV_PGTABLE_LEVELS - 2 - level));
+
+ /* Populate new table: replicate old mapping across 512 entries */
+ for (i = 0; i < RISCV_PTE_ENTRIES; i++) {
+ unsigned long new_phys = phys_base + (i * granularity);
+ pte_t new_pte = ((new_phys >> RISCV_PGSHIFT) << PTE_PPN_SHIFT) |
+ attrs | PTE_V;
+ new_table[i] = new_pte;
+ }
+
+ /*
+ * Replace old leaf PTE with table pointer.
+ * No RWX bits = pointer to next level.
+ */
+ *pte = (((unsigned long)new_table >> RISCV_PGSHIFT) << PTE_PPN_SHIFT) | PTE_V;
+
+ pr_debug("Split level %d PTE at phys=0x%llx (granularity=%lu KB)\n",
+ level, (unsigned long long)phys_base, granularity / 1024);
+}
+
+/*
+ * Get the root page table base
+ */
+static pte_t *get_ttb(void)
+{
+ return (pte_t *)early_pt_storage;
+}
+
+/*
+ * Convert maptype flags to PTE permission bits
+ */
+static unsigned long flags_to_pte(maptype_t flags)
+{
+ unsigned long pte = PTE_V; /* Valid bit always set */
+
+ /*
+ * Map barebox memory types to RISC-V PTE flags:
+ * - ARCH_MAP_CACHED_RWX: read + write + execute (early boot, full RAM access)
+ * - MAP_CODE: read + execute (text sections)
+ * - MAP_CACHED_RO: read only (rodata sections)
+ * - MAP_CACHED: read + write (data/bss sections)
+ * - MAP_UNCACHED: read + write, uncached (device memory)
+ */
+ switch (flags & MAP_TYPE_MASK) {
+ case ARCH_MAP_CACHED_RWX:
+ /* Full access for early boot: R + W + X */
+ pte |= PTE_R | PTE_W | PTE_X;
+ break;
+ case MAP_CACHED_RO:
+ /* Read-only data: R, no W, no X */
+ pte |= PTE_R;
+ break;
+ case MAP_CODE:
+ /* Code: R + X, no W */
+ pte |= PTE_R | PTE_X;
+ break;
+ case MAP_CACHED:
+ case MAP_UNCACHED:
+ default:
+ /* Data or uncached: R + W, no X */
+ pte |= PTE_R | PTE_W;
+ break;
+ }
+
+ /* Set accessed and dirty bits to avoid hardware updates */
+ pte |= PTE_A | PTE_D;
+
+ return pte;
+}
+
+/*
+ * Walk page tables and get/create PTE for given address at specified level
+ */
+static pte_t *walk_pgtable(unsigned long addr, int target_level)
+{
+ pte_t *table = get_ttb();
+ int level;
+
+ for (level = 0; level < target_level; level++) {
+ unsigned int index = VPN(addr, RISCV_PGTABLE_LEVELS - 1 - level);
+ pte_t *pte = &table[index];
+
+ if (!(*pte & PTE_V)) {
+ /* Entry not valid - allocate new page table */
+ pte_t *new_table = alloc_pte();
+ pte_t new_pte = ((unsigned long)new_table >> RISCV_PGSHIFT) << PTE_PPN_SHIFT;
+ new_pte |= PTE_V;
+ *pte = new_pte;
+ table = new_table;
+ } else if (*pte & (PTE_R | PTE_W | PTE_X)) {
+ /* This is a leaf PTE - split it before descending */
+ split_pte(pte, level);
+ /* After split, PTE is now a table pointer - follow it */
+ table = (pte_t *)(((*pte >> PTE_PPN_SHIFT) << RISCV_PGSHIFT));
+ } else {
+ /* Valid non-leaf PTE - follow to next level */
+ table = (pte_t *)(((*pte >> PTE_PPN_SHIFT) << RISCV_PGSHIFT));
+ }
+ }
+
+ return table;
+}
+
+/*
+ * Create a page table entry mapping virt -> phys with given permissions
+ */
+static void create_pte(unsigned long virt, phys_addr_t phys, maptype_t flags)
+{
+ pte_t *table;
+ unsigned int index;
+ pte_t pte;
+
+ /* Walk to leaf level page table */
+ table = walk_pgtable(virt, RISCV_PGTABLE_LEVELS - 1);
+
+ /* Get index for this address at leaf level */
+ index = VPN(virt, 0);
+
+ /* Build PTE: PPN + flags */
+ pte = (phys >> RISCV_PGSHIFT) << PTE_PPN_SHIFT;
+ pte |= flags_to_pte(flags);
+
+ /* Write PTE */
+ table[index] = pte;
+}
+
+/*
+ * create_megapage - Create a 2MB megapage mapping
+ * @virt: Virtual address (should be 2MB-aligned)
+ * @phys: Physical address (should be 2MB-aligned)
+ * @flags: Mapping flags (MAP_CACHED, etc.)
+ *
+ * Creates a leaf PTE at Level 1 covering 2MB. This is identical to a 4KB
+ * PTE except it's placed at Level 1 instead of Level 2, saving page tables.
+ */
+static void create_megapage(unsigned long virt, phys_addr_t phys, maptype_t flags)
+{
+ pte_t *table;
+ unsigned int index;
+ pte_t pte;
+
+ /* Walk to Level 1 (one level above 4KB leaf) */
+ table = walk_pgtable(virt, RISCV_PGTABLE_LEVELS - 2);
+
+ /* Get VPN[1] index for this address at Level 1 */
+ index = VPN(virt, 1);
+
+ /* Build leaf PTE at Level 1: PPN + RWX flags make it a megapage */
+ pte = (phys >> RISCV_PGSHIFT) << PTE_PPN_SHIFT;
+ pte |= flags_to_pte(flags);
+
+ /* Write megapage PTE */
+ table[index] = pte;
+}
+
+/*
+ * pbl_remap_range - Remap a virtual address range with specified permissions
+ *
+ * This is called by the portable pbl/mmu.c code after ELF relocation to set up
+ * proper memory protection based on ELF segment flags.
+ */
+void pbl_remap_range(void *virt, phys_addr_t phys, size_t size, maptype_t flags)
+{
+ unsigned long addr = (unsigned long)virt;
+ unsigned long end = addr + size;
+
+ pr_debug("Remapping 0x%08lx-0x%08lx -> 0x%08llx (flags=0x%x)\n",
+ addr, end, (unsigned long long)phys, flags);
+
+ /* Align to page boundaries */
+ addr &= ~(RISCV_PGSIZE - 1);
+ end = ALIGN(end, RISCV_PGSIZE);
+
+ /* Create page table entries for each page in the range */
+ while (addr < end) {
+ create_pte(addr, phys, flags);
+ addr += RISCV_PGSIZE;
+ phys += RISCV_PGSIZE;
+ }
+
+ /* Flush TLB for the remapped range */
+ sfence_vma();
+}
+
+/*
+ * mmu_early_enable - Set up initial MMU with identity mapping
+ *
+ * Called before barebox decompression to enable caching for faster decompression.
+ * Creates a simple identity map of all RAM with RWX permissions.
+ */
+void mmu_early_enable(unsigned long membase, unsigned long memsize,
+ unsigned long barebox_base)
+{
+ unsigned long addr;
+ unsigned long end = membase + memsize;
+ unsigned long satp;
+
+ pr_debug("Enabling MMU: mem=0x%08lx-0x%08lx barebox=0x%08lx\n",
+ membase, end, barebox_base);
+
+ /* Reset page table allocator */
+ early_pt_idx = 0;
+
+ /* Allocate root page table */
+ (void)alloc_pte();
+
+ pr_debug("Creating flat identity mapping...\n");
+
+ /*
+ * Create a flat identity mapping of the lower address space as uncached.
+ * This ensures I/O devices (UART, etc.) are accessible after MMU is enabled.
+ * RV64: Map lower 4GB using 2MB megapages (2048 entries).
+ * RV32: Map entire 4GB using 4MB superpages (1024 entries in root table).
+ */
+ addr = 0;
+ do {
+ create_megapage(addr, addr, MAP_UNCACHED);
+ addr += RISCV_L1_SIZE;
+ } while (lower_32_bits(addr) != 0); /* Wraps around to 0 after 0xFFFFFFFF */
+
+ /*
+ * Remap RAM as cached with RWX permissions using superpages.
+ * This overwrites the uncached mappings for RAM regions, providing
+ * better performance. Later, pbl_mmu_setup_from_elf() will split
+ * superpages as needed to set fine-grained permissions based on ELF segments.
+ */
+ pr_debug("Remapping RAM 0x%08lx-0x%08lx as cached RWX...\n", membase, end);
+ for (addr = membase; addr < end; addr += RISCV_L1_SIZE)
+ create_megapage(addr, addr, ARCH_MAP_CACHED_RWX);
+
+ pr_debug("Page table setup complete, used %lu KB\n",
+ (early_pt_idx * RISCV_PGSIZE) / 1024);
+
+ /*
+ * Enable MMU by setting SATP CSR:
+ * - MODE field: Sv39 (RV64) or Sv32 (RV32)
+ * - ASID: 0 (no address space ID)
+ * - PPN: physical address of root page table
+ */
+ satp = SATP_MODE | (((unsigned long)get_ttb() >> RISCV_PGSHIFT) & SATP_PPN_MASK);
+
+ pr_debug("Enabling MMU: SATP=0x%08lx\n", satp);
+
+ /* Synchronize before enabling MMU */
+ sfence_vma();
+
+ /* Enable MMU */
+ csr_write(satp, satp);
+
+ /* Synchronize after enabling MMU */
+ sfence_vma();
+
+ pr_debug("MMU enabled with %lu %spages for RAM\n",
+ (memsize / RISCV_L1_SIZE),
+ IS_ENABLED(CONFIG_64BIT) ? "2MB mega" : "4MB super");
+}
+
+#else /* !__PBL__ */
+
+/*
+ * arch_remap_range - Remap a virtual address range (barebox proper)
+ *
+ * This is the non-PBL version used in barebox proper after full relocation.
+ * Currently provides basic remapping support. For full MMU management in
+ * barebox proper, this would need to be extended with:
+ * - Dynamic page table allocation
+ * - Cache flushing for non-cached mappings
+ * - TLB management
+ * - Support for MAP_FAULT (guard pages)
+ */
+int arch_remap_range(void *virt_addr, phys_addr_t phys_addr, size_t size,
+ maptype_t map_type)
+{
+ /*
+ * For now, only allow identity mappings that match the default
+ * cached mapping. This is sufficient for most barebox proper use cases
+ * where the PBL has already set up the basic MMU configuration.
+ *
+ * TODO: Implement full remapping support for:
+ * - Non-identity mappings
+ * - Uncached device memory (MAP_UNCACHED)
+ * - Guard pages (MAP_FAULT)
+ */
+ if (phys_addr == virt_to_phys(virt_addr) &&
+ maptype_is_compatible(map_type, MAP_ARCH_DEFAULT))
+ return 0;
+
+ pr_warn("arch_remap_range: non-identity or non-default mapping not yet supported\n");
+ pr_warn(" virt=0x%p phys=0x%pad size=0x%zx type=0x%x\n",
+ virt_addr, &phys_addr, size, map_type);
+
+ return -ENOSYS;
+}
+
+#endif /* __PBL__ */
diff --git a/arch/riscv/cpu/mmu.h b/arch/riscv/cpu/mmu.h
new file mode 100644
index 0000000000000000000000000000000000000000..dda1e30ad97cd7c5cf99867735c5376c22edf938
--- /dev/null
+++ b/arch/riscv/cpu/mmu.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: 2026 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix */
+
+#ifndef __RISCV_CPU_MMU_H
+#define __RISCV_CPU_MMU_H
+
+#include <linux/types.h>
+
+/*
+ * RISC-V MMU constants for Sv39 (RV64) and Sv32 (RV32) page tables
+ */
+
+/* Page table configuration */
+#define RISCV_PGSHIFT 12
+#define RISCV_PGSIZE (1UL << RISCV_PGSHIFT) /* 4KB */
+
+#ifdef CONFIG_64BIT
+/* Sv39: 9-bit VPN fields, 512 entries per table */
+#define RISCV_PGLEVEL_BITS 9
+#define RISCV_PGTABLE_ENTRIES 512
+#else
+/* Sv32: 10-bit VPN fields, 1024 entries per table */
+#define RISCV_PGLEVEL_BITS 10
+#define RISCV_PGTABLE_ENTRIES 1024
+#endif
+
+/* Page table entry (PTE) bit definitions */
+#define PTE_V (1UL << 0) /* Valid */
+#define PTE_R (1UL << 1) /* Read */
+#define PTE_W (1UL << 2) /* Write */
+#define PTE_X (1UL << 3) /* Execute */
+#define PTE_U (1UL << 4) /* User accessible */
+#define PTE_G (1UL << 5) /* Global mapping */
+#define PTE_A (1UL << 6) /* Accessed */
+#define PTE_D (1UL << 7) /* Dirty */
+#define PTE_RSW_MASK (3UL << 8) /* Reserved for software */
+
+/* PTE physical page number (PPN) field position */
+#define PTE_PPN_SHIFT 10
+
+#ifdef CONFIG_64BIT
+/*
+ * Sv39: 39-bit virtual addressing, 3-level page tables
+ * Virtual address format: [38:30] VPN[2], [29:21] VPN[1], [20:12] VPN[0], [11:0] offset
+ */
+#define SATP_MODE_SV39 (8UL << 60)
+#define SATP_MODE SATP_MODE_SV39
+#define RISCV_PGTABLE_LEVELS 3
+#define VA_BITS 39
+#else
+/*
+ * Sv32: 32-bit virtual addressing, 2-level page tables
+ * Virtual address format: [31:22] VPN[1], [21:12] VPN[0], [11:0] offset
+ */
+#define SATP_MODE_SV32 (1UL << 31)
+#define SATP_MODE SATP_MODE_SV32
+#define RISCV_PGTABLE_LEVELS 2
+#define VA_BITS 32
+#endif
+
+/* SATP register fields */
+#ifdef CONFIG_64BIT
+#define SATP_PPN_MASK ((1ULL << 44) - 1) /* Physical page number (Sv39) */
+#else
+#define SATP_PPN_MASK ((1UL << 22) - 1) /* Physical page number (Sv32) */
+#endif
+#define SATP_ASID_SHIFT 44
+#define SATP_ASID_MASK (0xFFFFUL << SATP_ASID_SHIFT)
+
+/* Extract VPN (Virtual Page Number) from virtual address */
+#define VPN_MASK ((1UL << RISCV_PGLEVEL_BITS) - 1)
+#define VPN(addr, level) (((addr) >> (RISCV_PGSHIFT + (level) * RISCV_PGLEVEL_BITS)) & VPN_MASK)
+
+/* RISC-V page sizes by level */
+#ifdef CONFIG_64BIT
+/* Sv39: 3-level page tables */
+#define RISCV_L2_SHIFT 30 /* 1GB gigapages (Level 0 in Sv39) */
+#define RISCV_L1_SHIFT 21 /* 2MB megapages (Level 1 in Sv39) */
+#define RISCV_L0_SHIFT 12 /* 4KB pages (Level 2 in Sv39) */
+#else
+/* Sv32: 2-level page tables */
+#define RISCV_L1_SHIFT 22 /* 4MB superpages (Level 0 in Sv32) */
+#define RISCV_L0_SHIFT 12 /* 4KB pages (Level 1 in Sv32) */
+#endif
+
+#ifdef CONFIG_64BIT
+#define RISCV_L2_SIZE (1UL << RISCV_L2_SHIFT) /* 1GB (RV64 only) */
+#endif
+#define RISCV_L1_SIZE (1UL << RISCV_L1_SHIFT) /* 2MB (RV64) or 4MB (RV32) */
+#define RISCV_L0_SIZE (1UL << RISCV_L0_SHIFT) /* 4KB */
+
+/* Number of entries per page table (use RISCV_PGTABLE_ENTRIES instead) */
+#define RISCV_PTE_ENTRIES RISCV_PGTABLE_ENTRIES
+
+/* PTE type - 64-bit on RV64, 32-bit on RV32 */
+#ifdef CONFIG_64BIT
+typedef uint64_t pte_t;
+#else
+typedef uint32_t pte_t;
+#endif
+
+/* Early page table allocation size (PBL) */
+#ifdef CONFIG_64BIT
+/* Sv39: 3 levels, allocate space for root + worst case intermediate tables */
+#define RISCV_EARLY_PAGETABLE_SIZE (64 * 1024) /* 64KB */
+#else
+/* Sv32: 2 levels, smaller allocation */
+#define RISCV_EARLY_PAGETABLE_SIZE (32 * 1024) /* 32KB */
+#endif
+
+#ifndef __ASSEMBLY__
+
+/* CSR access */
+#define csr_read(csr) \
+({ \
+ unsigned long __v; \
+ __asm__ __volatile__ ("csrr %0, " #csr \
+ : "=r" (__v) : \
+ : "memory"); \
+ __v; \
+})
+
+#define csr_write(csr, val) \
+({ \
+ unsigned long __v = (unsigned long)(val); \
+ __asm__ __volatile__ ("csrw " #csr ", %0" \
+ : : "rK" (__v) \
+ : "memory"); \
+})
+
+/* SFENCE.VMA - Synchronize updates to page tables */
+static inline void sfence_vma(void)
+{
+ __asm__ __volatile__ ("sfence.vma" : : : "memory");
+}
+
+static inline void sfence_vma_addr(unsigned long addr)
+{
+ __asm__ __volatile__ ("sfence.vma %0" : : "r" (addr) : "memory");
+}
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* __RISCV_CPU_MMU_H */
diff --git a/arch/riscv/include/asm/asm.h b/arch/riscv/include/asm/asm.h
index 9c992a88d858fe6105f16978849f3a564d42b85f..23f60b615ea91e680c57b7b65b868260a761de5e 100644
--- a/arch/riscv/include/asm/asm.h
+++ b/arch/riscv/include/asm/asm.h
@@ -9,7 +9,8 @@
#ifdef __ASSEMBLY__
#define __ASM_STR(x) x
#else
-#define __ASM_STR(x) #x
+#define __ASM_STR_HELPER(x) #x
+#define __ASM_STR(x) __ASM_STR_HELPER(x)
#endif
#if __riscv_xlen == 64
diff --git a/arch/riscv/include/asm/mmu.h b/arch/riscv/include/asm/mmu.h
index 1c2646ebb3393f120ad7208109372fef8bc32e81..63878b4bb4f13287ffe86f86425a167c0beb852b 100644
--- a/arch/riscv/include/asm/mmu.h
+++ b/arch/riscv/include/asm/mmu.h
@@ -3,6 +3,50 @@
#ifndef __ASM_MMU_H
#define __ASM_MMU_H
+#include <linux/types.h>
+
+/*
+ * RISC-V supports memory protection through two mechanisms:
+ * - S-mode: Virtual memory with page tables (MMU)
+ * - M-mode: Physical Memory Protection (PMP) regions
+ */
+
+#if defined(CONFIG_MMU) || defined(CONFIG_RISCV_PMP)
+#define ARCH_HAS_REMAP
+#define MAP_ARCH_DEFAULT MAP_CACHED
+
+/* Architecture-specific memory type flags */
+#define ARCH_MAP_CACHED_RWX MAP_ARCH(2) /* Cached, RWX (early boot) */
+#define ARCH_MAP_FLAG_PAGEWISE (1 << 16) /* Force page-wise mapping */
+
+#ifdef __PBL__
+/*
+ * PBL remap function - used by pbl/mmu.c to apply ELF segment permissions.
+ * Implementation is in arch/riscv/cpu/mmu.c (S-mode) or pmp.c (M-mode).
+ */
+void pbl_remap_range(void *virt, phys_addr_t phys, size_t size, maptype_t flags);
+
+/*
+ * Early MMU/PMP setup - called before decompression for performance.
+ * S-mode: Sets up basic page tables and enables MMU via SATP CSR.
+ * M-mode: Configures initial PMP regions.
+ */
+void mmu_early_enable(unsigned long membase, unsigned long memsize,
+ unsigned long barebox_base);
+#endif /* __PBL__ */
+
+/*
+ * Remap a virtual address range with specified memory type (barebox proper).
+ * Used by the generic remap infrastructure after barebox is fully relocated.
+ * Implementation is in arch/riscv/cpu/mmu.c (S-mode) or pmp.c (M-mode).
+ */
+int arch_remap_range(void *virt_addr, phys_addr_t phys_addr, size_t size,
+ maptype_t map_type);
+
+#else
#define MAP_ARCH_DEFAULT MAP_UNCACHED
+#endif
+
+#include <mmu.h>
#endif /* __ASM_MMU_H */
diff --git a/include/mmu.h b/include/mmu.h
index 32d9a7aca3b9a61d542bf3e21e27f1ac51f43ee2..ecd44d4ac756009cd44d7dedbda5a13f1ca3f93d 100644
--- a/include/mmu.h
+++ b/include/mmu.h
@@ -20,6 +20,9 @@
#define MAP_TYPE_MASK 0xFFFF
#define MAP_ARCH(x) ((u16)~(x))
+#include <asm/mmu.h>
+#include <asm/io.h>
+
/*
* Depending on the architecture the default mapping can be
* cached or uncached. Without ARCH_HAS_REMAP being set this
@@ -27,9 +30,6 @@
*/
#define MAP_DEFAULT MAP_ARCH_DEFAULT
-#include <asm/mmu.h>
-#include <asm/io.h>
-
static inline bool maptype_is_compatible(maptype_t active, maptype_t check)
{
active &= MAP_TYPE_MASK;
@@ -47,7 +47,7 @@ static inline bool maptype_is_compatible(maptype_t active, maptype_t check)
static inline int arch_remap_range(void *virt_addr, phys_addr_t phys_addr,
size_t size, maptype_t map_type)
{
- if (maptype_is_compatible(map_type, MAP_ARCH_DEFAULT) &&
+ if (maptype_is_compatible(map_type, MAP_DEFAULT) &&
phys_addr == virt_to_phys(virt_addr))
return 0;
--
2.47.3
next prev parent reply other threads:[~2026-01-06 13:02 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-06 12:53 [PATCH v2 00/21] PBL: Add PBL ELF loading support with dynamic relocations Sascha Hauer
2026-01-06 12:53 ` [PATCH v2 01/21] elf: only accept images matching the native ELF_CLASS Sascha Hauer
2026-01-06 12:58 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 02/21] elf: build for PBL as well Sascha Hauer
2026-01-06 13:26 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 03/21] elf: add dynamic relocation support Sascha Hauer
2026-01-06 13:51 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 04/21] ARM: implement elf_apply_relocations() for ELF " Sascha Hauer
2026-01-06 13:07 ` Ahmad Fatoum
2026-01-06 14:25 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 05/21] riscv: define generic relocate_image Sascha Hauer
2026-01-06 13:10 ` Ahmad Fatoum
2026-01-06 13:11 ` Sascha Hauer
2026-01-06 12:53 ` [PATCH v2 06/21] riscv: implement elf_apply_relocations() for ELF relocation support Sascha Hauer
2026-01-06 13:11 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 07/21] elf: implement elf_load_inplace() Sascha Hauer
2026-01-06 13:53 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 08/21] elf: create elf_open_binary_into() Sascha Hauer
2026-01-06 13:55 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 09/21] Makefile: add barebox.elf build target Sascha Hauer
2026-01-06 13:13 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 10/21] PBL: allow to link ELF image into PBL Sascha Hauer
2026-01-06 13:18 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 11/21] mmu: add MAP_CACHED_RO mapping type Sascha Hauer
2026-01-06 13:14 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 12/21] mmu: introduce pbl_remap_range() Sascha Hauer
2026-01-06 13:15 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 13/21] ARM: use relative jumps in exception table Sascha Hauer
2026-01-06 13:57 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 14/21] ARM: exceptions: make in-binary exception table const Sascha Hauer
2026-01-06 14:00 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 15/21] ARM: linker script: create separate PT_LOAD segments for text, rodata, and data Sascha Hauer
2026-01-06 14:05 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 16/21] ARM: link ELF image into PBL Sascha Hauer
2026-01-06 14:06 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 17/21] ARM: PBL: setup MMU with proper permissions from ELF segments Sascha Hauer
2026-01-06 14:10 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 18/21] riscv: link ELF image into PBL Sascha Hauer
2026-01-06 14:11 ` Ahmad Fatoum
2026-01-06 12:53 ` [PATCH v2 19/21] riscv: linker script: create separate PT_LOAD segments for text, rodata, and data Sascha Hauer
2026-01-06 14:12 ` Ahmad Fatoum
2026-01-08 15:25 ` Sascha Hauer
2026-01-06 12:53 ` [PATCH v2 20/21] riscv: Allwinner D1: Drop M-Mode Sascha Hauer
2026-01-06 13:11 ` Ahmad Fatoum
2026-01-06 12:53 ` Sascha Hauer [this message]
2026-01-06 14:20 ` [PATCH v2 21/21] riscv: add ELF segment-based memory protection with MMU Ahmad Fatoum
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260106-pbl-load-elf-v2-21-487bc760f045@pengutronix.de \
--to=s.hauer@pengutronix.de \
--cc=barebox@lists.infradead.org \
--cc=noreply@anthropic.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox