summaryrefslogtreecommitdiffstats
path: root/src/arch/riscv/core/svpage.c
diff options
context:
space:
mode:
authorSimon Rettberg2026-01-28 12:53:53 +0100
committerSimon Rettberg2026-01-28 12:53:53 +0100
commit8e82785c584dc13e20f9229decb95bd17bbe9cd1 (patch)
treea8b359e59196be5b2e3862bed189107f4bc9975f /src/arch/riscv/core/svpage.c
parentMerge branch 'master' into openslx (diff)
parent[prefix] Make unlzma.S compatible with 386 class CPUs (diff)
downloadipxe-openslx.tar.gz
ipxe-openslx.tar.xz
ipxe-openslx.zip
Merge branch 'master' into openslxopenslx
Diffstat (limited to 'src/arch/riscv/core/svpage.c')
-rw-r--r--src/arch/riscv/core/svpage.c292
1 files changed, 292 insertions, 0 deletions
diff --git a/src/arch/riscv/core/svpage.c b/src/arch/riscv/core/svpage.c
new file mode 100644
index 000000000..e25e3920a
--- /dev/null
+++ b/src/arch/riscv/core/svpage.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <strings.h>
+#include <assert.h>
+#include <ipxe/hart.h>
+#include <ipxe/iomap.h>
+
+/** @file
+ *
+ * Supervisor page table management
+ *
+ * With the 64-bit paging schemes (Sv39, Sv48, and Sv57) we choose to
+ * identity-map as much as possible of the physical address space via
+ * PTEs 0-255, and place a recursive page table entry in PTE 511 which
+ * allows PTEs 256-510 to be used to map 1GB "gigapages" within the
+ * top 256GB of the 64-bit address space. At least one of these PTEs
+ * will already be in use to map iPXE itself. The remaining PTEs may
+ * be used to map I/O devices.
+ */
+
+/** A page table */
+struct page_table {
+ /** Page table entry */
+ uint64_t pte[512];
+};
+
+/** Page table entry flags */
+enum pte_flags {
+ /** Page table entry is valid */
+ PTE_V = 0x01,
+ /** Page is readable */
+ PTE_R = 0x02,
+ /** Page is writable */
+ PTE_W = 0x04,
+ /** Page has been accessed */
+ PTE_A = 0x40,
+ /** Page is dirty */
+ PTE_D = 0x80,
+ /** Page is the last page in an allocation
+ *
+ * This bit is ignored by the hardware. We use it to track
+ * the size of allocations made by ioremap().
+ */
+ PTE_LAST = 0x100,
+};
+
+/** Page-based memory type (Svpbmt) */
+#define PTE_SVPBMT( x ) ( ( ( unsigned long long ) (x) ) << 61 )
+
+/** Page is non-cacheable memory (Svpbmt) */
+#define PTE_SVPBMT_NC PTE_SVPBMT ( 1 )
+
+/** Page maps I/O addresses (Svpbmt) */
+#define PTE_SVPBMT_IO PTE_SVPBMT ( 2 )
+
+/** Page table entry address */
+#define PTE_PPN( addr ) ( (addr) >> 2 )
+
+/** The page table */
+extern struct page_table page_table;
+
+/** Maximum number of I/O pages */
+#define MAP_PAGE_COUNT \
+ ( sizeof ( page_table.pte ) / sizeof ( page_table.pte[0] ) )
+
+/** I/O page size
+ *
+ * We choose to use 1GB "gigapages", since these are supported by all
+ * paging levels.
+ */
+#define MAP_PAGE_SIZE 0x40000000UL
+
+/** I/O page base address
+ *
+ * The recursive page table entry maps the high 512GB of the 64-bit
+ * address space as 1GB "gigapages".
+ */
+#define MAP_BASE ( ( void * ) ( intptr_t ) ( -1ULL << 39 ) )
+
+/** Coherent DMA mapping of the 32-bit address space */
+static void *svpage_dma32_base;
+
+/** Size of the coherent DMA mapping */
+#define DMA32_LEN ( ( size_t ) 0x100000000ULL )
+
+/**
+ * Map pages
+ *
+ * @v phys Physical address
+ * @v len Length
+ * @v attrs Page attributes
+ * @ret virt Mapped virtual address, or NULL on error
+ */
+static void * svpage_map ( physaddr_t phys, size_t len, unsigned long attrs ) {
+ unsigned long satp;
+ unsigned long start;
+ unsigned int count;
+ unsigned int stride;
+ unsigned int first;
+ unsigned int i;
+ size_t offset;
+ void *virt;
+
+ DBGC ( &page_table, "SVPAGE mapping %#08lx+%#zx attrs %#016lx\n",
+ phys, len, attrs );
+
+ /* Sanity checks */
+ if ( ! len )
+ return NULL;
+ assert ( attrs & PTE_V );
+
+ /* Use physical address directly if paging is disabled */
+ __asm__ ( "csrr %0, satp" : "=r" ( satp ) );
+ if ( ! satp ) {
+ virt = phys_to_virt ( phys );
+ DBGC ( &page_table, "SVPAGE mapped %#08lx+%#zx to %p (no "
+ "paging)\n", phys, len, virt );
+ return virt;
+ }
+
+ /* Round down start address to a page boundary */
+ start = ( phys & ~( MAP_PAGE_SIZE - 1 ) );
+ offset = ( phys - start );
+ assert ( offset < MAP_PAGE_SIZE );
+
+ /* Calculate number of pages required */
+ count = ( ( offset + len + MAP_PAGE_SIZE - 1 ) / MAP_PAGE_SIZE );
+ assert ( count != 0 );
+ assert ( count <= MAP_PAGE_COUNT );
+
+ /* Round up number of pages to a power of two */
+ stride = ( 1 << fls ( count - 1 ) );
+ assert ( count <= stride );
+
+ /* Allocate pages */
+ for ( first = 0 ; first < MAP_PAGE_COUNT ; first += stride ) {
+
+ /* Calculate virtual address */
+ virt = ( MAP_BASE + ( first * MAP_PAGE_SIZE ) + offset );
+
+ /* Check that page table entries are available */
+ for ( i = first ; i < ( first + count ) ; i++ ) {
+ if ( page_table.pte[i] & PTE_V ) {
+ virt = NULL;
+ break;
+ }
+ }
+ if ( ! virt )
+ continue;
+
+ /* Create page table entries */
+ for ( i = first ; i < ( first + count ) ; i++ ) {
+ page_table.pte[i] = ( PTE_PPN ( start ) | attrs );
+ start += MAP_PAGE_SIZE;
+ }
+
+ /* Mark last page as being the last in this allocation */
+ page_table.pte[ i - 1 ] |= PTE_LAST;
+
+ /* Synchronise page table updates */
+ __asm__ __volatile__ ( "sfence.vma" );
+
+ /* Return virtual address */
+ DBGC ( &page_table, "SVPAGE mapped %#08lx+%#zx to %p using "
+ "PTEs [%d-%d]\n", phys, len, virt, first,
+ ( first + count - 1 ) );
+ return virt;
+ }
+
+ DBGC ( &page_table, "SVPAGE could not map %#08lx+%#zx\n",
+ phys, len );
+ return NULL;
+}
+
+/**
+ * Unmap pages
+ *
+ * @v virt Virtual address
+ */
+static void svpage_unmap ( const volatile void *virt ) {
+ unsigned long satp;
+ unsigned int first;
+ unsigned int i;
+ int is_last;
+
+ DBGC ( &page_table, "SVPAGE unmapping %p\n", virt );
+
+ /* Do nothing if paging is disabled */
+ __asm__ ( "csrr %0, satp" : "=r" ( satp ) );
+ if ( ! satp )
+ return;
+
+ /* Calculate first page table entry */
+ first = ( ( virt - MAP_BASE ) / MAP_PAGE_SIZE );
+
+ /* Ignore unmappings outside of the I/O range */
+ if ( first >= MAP_PAGE_COUNT )
+ return;
+
+ /* Clear page table entries */
+ for ( i = first ; ; i++ ) {
+
+ /* Sanity check */
+ assert ( page_table.pte[i] & PTE_V );
+
+ /* Check if this is the last page in this allocation */
+ is_last = ( page_table.pte[i] & PTE_LAST );
+
+ /* Clear page table entry */
+ page_table.pte[i] = 0;
+
+ /* Terminate if this was the last page */
+ if ( is_last )
+ break;
+ }
+
+ /* Synchronise page table updates */
+ __asm__ __volatile__ ( "sfence.vma" );
+
+ DBGC ( &page_table, "SVPAGE unmapped %p using PTEs [%d-%d]\n",
+ virt, first, i );
+}
+
+/**
+ * Map pages for I/O
+ *
+ * @v bus_addr Bus address
+ * @v len Length of region
+ * @ret io_addr I/O address
+ */
+static void * svpage_ioremap ( unsigned long bus_addr, size_t len ) {
+ unsigned long attrs = ( PTE_V | PTE_R | PTE_W | PTE_A | PTE_D );
+ int rc;
+
+ /* Add Svpbmt attributes if applicable */
+ if ( ( rc = hart_supported ( "_svpbmt" ) ) == 0 )
+ attrs |= PTE_SVPBMT_IO;
+
+ /* Map pages for I/O */
+ return svpage_map ( bus_addr, len, attrs );
+}
+
+/**
+ * Get 32-bit address space coherent DMA mapping address
+ *
+ * @ret base Coherent DMA mapping base address
+ */
+void * svpage_dma32 ( void ) {
+ unsigned long attrs = ( PTE_V | PTE_R | PTE_W | PTE_A | PTE_D );
+ int rc;
+
+ /* Add Svpbmt attributes if applicable */
+ if ( ( rc = hart_supported ( "_svpbmt" ) ) == 0 )
+ attrs |= PTE_SVPBMT_NC;
+
+ /* Create mapping, if necessary */
+ if ( ! svpage_dma32_base )
+ svpage_dma32_base = svpage_map ( 0, DMA32_LEN, attrs );
+
+ /* Sanity check */
+ assert ( virt_to_phys ( svpage_dma32_base ) == 0 );
+
+ return svpage_dma32_base;
+}
+
+PROVIDE_IOMAP_INLINE ( svpage, io_to_bus );
+PROVIDE_IOMAP ( svpage, ioremap, svpage_ioremap );
+PROVIDE_IOMAP ( svpage, iounmap, svpage_unmap );