From: Michael Brown Date: Mon, 26 May 2025 14:45:27 +0000 (+0100) Subject: [riscv] Support mapping I/O devices outside of the identity map X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=eae9a27542b020c4192c0435718f7c7d71251a4a;p=thirdparty%2Fipxe.git [riscv] Support mapping I/O devices outside of the identity map With the 64-bit paging schemes (Sv39, Sv48, and Sv57), we identity-map as much of the physical address space as is possible. Experimentation shows that this is not sufficient to provide access to all I/O devices. For example: the Sipeed Lichee Pi 4A includes a CPU that supports only Sv39, but places I/O devices at the top of a 40-bit address space. Add support for creating I/O page table entries on demand to map I/O devices, based on the existing design used for x86_64 BIOS. Signed-off-by: Michael Brown --- diff --git a/src/arch/riscv/core/svpage.c b/src/arch/riscv/core/svpage.c new file mode 100644 index 000000000..380f3df8b --- /dev/null +++ b/src/arch/riscv/core/svpage.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * 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 +#include +#include +#include + +/** @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 2MB "megapages" within the + * top 512MB of the 64-bit address space. At least one of these 2MB + * 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 table entry address */ +#define PTE_PPN( addr ) ( (addr) >> 2 ) + +/** The page table */ +extern struct page_table page_table; + +/** I/O page size + * + * We choose to use 2MB "megapages", since these are supported by all + * paging levels. + */ +#define IO_PAGE_SIZE 0x200000UL + +/** I/O page base address + * + * The recursive page table entry maps the high 1024MB of the 64-bit + * address space as 2MB "megapages". + */ +#define IO_BASE ( ( void * ) ( intptr_t ) ( -1024 * 1024 * 1024 ) ) + +/** + * 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 satp; + unsigned long start; + unsigned int count; + unsigned int stride; + unsigned int first; + unsigned int i; + size_t offset; + void *io_addr; + + DBGC ( &page_table, "SVPAGE mapping %#08lx+%#zx\n", bus_addr, len ); + + /* Sanity check */ + if ( ! len ) + return NULL; + + /* Use physical address directly if paging is disabled */ + __asm__ ( "csrr %0, satp" : "=r" ( satp ) ); + if ( ! satp ) { + io_addr = phys_to_virt ( bus_addr ); + DBGC ( &page_table, "SVPAGE mapped %#08lx+%#zx to %p (no " + "paging)\n", bus_addr, len, io_addr ); + return io_addr; + } + + /* Round down start address to a page boundary */ + start = ( bus_addr & ~( IO_PAGE_SIZE - 1 ) ); + offset = ( bus_addr - start ); + assert ( offset < IO_PAGE_SIZE ); + + /* Calculate number of pages required */ + count = ( ( offset + len + IO_PAGE_SIZE - 1 ) / IO_PAGE_SIZE ); + assert ( count != 0 ); + assert ( count < ( sizeof ( page_table.pte ) / + sizeof ( page_table.pte[0] ) ) ); + + /* Round up number of pages to a power of two */ + stride = ( 1 << ( fls ( count ) - 1 ) ); + assert ( count <= stride ); + + /* Allocate pages */ + for ( first = 0 ; first < ( sizeof ( page_table.pte ) / + sizeof ( page_table.pte[0] ) ) ; + first += stride ) { + + /* Calculate I/O address */ + io_addr = ( IO_BASE + ( first * IO_PAGE_SIZE ) + offset ); + + /* Check that page table entries are available */ + for ( i = first ; i < ( first + count ) ; i++ ) { + if ( page_table.pte[i] & PTE_V ) { + io_addr = NULL; + break; + } + } + if ( ! io_addr ) + continue; + + /* Create page table entries */ + for ( i = first ; i < ( first + count ) ; i++ ) { + page_table.pte[i] = ( PTE_PPN ( start ) | PTE_V | + PTE_R | PTE_W | PTE_A | PTE_D ); + start += IO_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 I/O address */ + DBGC ( &page_table, "SVPAGE mapped %#08lx+%#zx to %p using " + "PTEs [%d-%d]\n", bus_addr, len, io_addr, first, + ( first + count - 1 ) ); + return io_addr; + } + + DBGC ( &page_table, "SVPAGE could not map %#08lx+%#zx\n", + bus_addr, len ); + return NULL; +} + +/** + * Unmap pages for I/O + * + * @v io_addr I/O address + */ +static void svpage_iounmap ( volatile const void *io_addr ) { + unsigned long satp; + unsigned int first; + unsigned int i; + int is_last; + + DBGC ( &page_table, "SVPAGE unmapping %p\n", io_addr ); + + /* Do nothing if paging is disabled */ + __asm__ ( "csrr %0, satp" : "=r" ( satp ) ); + if ( ! satp ) + return; + + /* Calculate first page table entry */ + first = ( ( io_addr - IO_BASE ) / IO_PAGE_SIZE ); + + /* 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", + io_addr, first, i ); +} + +PROVIDE_IOMAP_INLINE ( svpage, io_to_bus ); +PROVIDE_IOMAP ( svpage, ioremap, svpage_ioremap ); +PROVIDE_IOMAP ( svpage, iounmap, svpage_iounmap ); diff --git a/src/arch/riscv/include/bits/iomap.h b/src/arch/riscv/include/bits/iomap.h new file mode 100644 index 000000000..fd8e37825 --- /dev/null +++ b/src/arch/riscv/include/bits/iomap.h @@ -0,0 +1,14 @@ +#ifndef _BITS_IOMAP_H +#define _BITS_IOMAP_H + +/** @file + * + * RISCV-specific I/O mapping API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +#endif /* _BITS_IOMAP_H */ diff --git a/src/arch/riscv/include/ipxe/svpage.h b/src/arch/riscv/include/ipxe/svpage.h new file mode 100644 index 000000000..374c607d9 --- /dev/null +++ b/src/arch/riscv/include/ipxe/svpage.h @@ -0,0 +1,26 @@ +#ifndef _IPXE_SVPAGE_H +#define _IPXE_SVPAGE_H + +/** @file + * + * Supervisor page table management + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +#ifdef IOMAP_SVPAGE +#define IOMAP_PREFIX_svpage +#else +#define IOMAP_PREFIX_svpage __svpage_ +#endif + +static inline __always_inline unsigned long +IOMAP_INLINE ( svpage, io_to_bus ) ( volatile const void *io_addr ) { + /* Not easy to do; just return the CPU address for debugging purposes */ + return ( ( intptr_t ) io_addr ); +} + +#endif /* _IPXE_SVPAGE_H */ diff --git a/src/config/defaults/sbi.h b/src/config/defaults/sbi.h index 900d27be4..5dfeaf951 100644 --- a/src/config/defaults/sbi.h +++ b/src/config/defaults/sbi.h @@ -10,12 +10,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define IOAPI_RISCV -#define IOMAP_VIRT #define DMAAPI_FLAT #define UACCESS_OFFSET #define TIMER_ZICNTR #define ENTROPY_ZKR +#if __riscv_xlen == 64 +#define IOMAP_SVPAGE +#else +#define IOMAP_VIRT +#endif + #define CONSOLE_SBI #define REBOOT_SBI #define UMALLOC_UHEAP