diff options
| author | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
|---|---|---|
| committer | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
| commit | 8e82785c584dc13e20f9229decb95bd17bbe9cd1 (patch) | |
| tree | a8b359e59196be5b2e3862bed189107f4bc9975f /src/arch/riscv | |
| parent | Merge branch 'master' into openslx (diff) | |
| parent | [prefix] Make unlzma.S compatible with 386 class CPUs (diff) | |
| download | ipxe-openslx.tar.gz ipxe-openslx.tar.xz ipxe-openslx.zip | |
Merge branch 'master' into openslxopenslx
Diffstat (limited to 'src/arch/riscv')
52 files changed, 5575 insertions, 0 deletions
diff --git a/src/arch/riscv/Makefile b/src/arch/riscv/Makefile new file mode 100644 index 000000000..324e1403e --- /dev/null +++ b/src/arch/riscv/Makefile @@ -0,0 +1,22 @@ +# Assembler section type character +# +ASM_TCHAR := @ +ASM_TCHAR_OPS := @ + +# Include RISCV-specific headers +# +INCDIRS := arch/$(ARCH)/include arch/riscv/include $(INCDIRS) + +# RISCV-specific directories containing source files +# +SRCDIRS += arch/riscv/core +SRCDIRS += arch/riscv/interface/sbi +SRCDIRS += arch/riscv/prefix + +# RISCV-specific flags +# +CFLAGS += -mno-strict-align -mno-plt + +# EFI requires -fshort-wchar, and nothing else currently uses wchar_t +# +CFLAGS += -fshort-wchar diff --git a/src/arch/riscv/Makefile.efi b/src/arch/riscv/Makefile.efi new file mode 100644 index 000000000..957e8b884 --- /dev/null +++ b/src/arch/riscv/Makefile.efi @@ -0,0 +1,10 @@ +# -*- makefile -*- : Force emacs to use Makefile mode + +# RISCV-specific flags +# +CFLAGS += -mcmodel=medany + +# Include generic EFI Makefile +# +MAKEDEPS += Makefile.efi +include Makefile.efi diff --git a/src/arch/riscv/Makefile.linux b/src/arch/riscv/Makefile.linux new file mode 100644 index 000000000..42590441e --- /dev/null +++ b/src/arch/riscv/Makefile.linux @@ -0,0 +1,6 @@ +# -*- makefile -*- : Force emacs to use Makefile mode + +# Include generic Linux Makefile +# +MAKEDEPS += Makefile.linux +include Makefile.linux diff --git a/src/arch/riscv/Makefile.sbi b/src/arch/riscv/Makefile.sbi new file mode 100644 index 000000000..5e5dc9a6b --- /dev/null +++ b/src/arch/riscv/Makefile.sbi @@ -0,0 +1,31 @@ +# -*- makefile -*- : Force emacs to use Makefile mode + +# Build a position-independent executable, with relocations required +# only for data values. Runtime relocations are applied by the +# prefix code. +# +CFLAGS += -mcmodel=medany -fpie +LDFLAGS += -pie --no-dynamic-linker -z combreloc + +# Place explicitly zero-initialised variables in the .data section +# rather than in .bss, so that we can rely on their values even during +# parsing of the system memory map prior to relocation (and therefore +# prior to explicit zeroing of the .bss section). +# +CFLAGS += -fno-zero-initialized-in-bss + +# Linker script +# +LDSCRIPT = arch/riscv/scripts/sbi.lds + +# Media types +# +MEDIA += sbi +MEDIA += lkrn + +# Padded flash device images (e.g. for QEMU's -pflash option) +# +NON_AUTO_MEDIA += pf32 +%.pf32 : %.sbi $(MAKEDEPS) + $(Q)$(CP) $< $@ + $(Q)$(TRUNCATE) -s 32M $@ diff --git a/src/arch/riscv/core/hart.c b/src/arch/riscv/core/hart.c new file mode 100644 index 000000000..d9cfb4e5d --- /dev/null +++ b/src/arch/riscv/core/hart.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 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 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 ); + +/** @file + * + * Hardware threads (harts) + * + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/fdt.h> +#include <ipxe/hart.h> + +/** Boot hart ID */ +unsigned long boot_hart; + +/** Colour for debug messages */ +#define colour &boot_hart + +/** + * Find boot hart node + * + * @v offset Boot hart node offset + * @ret rc Return status code + */ +static int hart_node ( unsigned int *offset ) { + char path[27 /* "/cpus/cpu@XXXXXXXXXXXXXXXX" + NUL */ ]; + int rc; + + /* Construct node path */ + snprintf ( path, sizeof ( path ), "/cpus/cpu@%lx", boot_hart ); + + /* Find node */ + if ( ( rc = fdt_path ( &sysfdt, path, offset ) ) != 0 ) { + DBGC ( colour, "HART could not find %s: %s\n", + path, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Check for supported extension + * + * @v ext Extension name (including leading underscore) + * @ret rc Return status code + */ +int hart_supported ( const char *ext ) { + unsigned int offset; + const char *isa; + const char *tmp; + int rc; + + /* Find boot hart node */ + if ( ( rc = hart_node ( &offset ) ) != 0 ) + return rc; + + /* Get ISA description */ + isa = fdt_string ( &sysfdt, offset, "riscv,isa" ); + if ( ! isa ) { + DBGC ( colour, "HART could not identify ISA\n" ); + return -ENOENT; + } + DBGC ( colour, "HART supports %s\n", isa ); + + /* Check for presence of extension */ + tmp = isa; + while ( ( tmp = strstr ( tmp, ext ) ) != NULL ) { + tmp += strlen ( ext ); + if ( ( *tmp == '\0' ) || ( *tmp == '_' ) ) + return 0; + } + + return -ENOTSUP; +} diff --git a/src/arch/riscv/core/riscv_dma.c b/src/arch/riscv/core/riscv_dma.c new file mode 100644 index 000000000..7b48b1bdd --- /dev/null +++ b/src/arch/riscv/core/riscv_dma.c @@ -0,0 +1,190 @@ +/* + * 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 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 <assert.h> +#include <ipxe/zicbom.h> +#include <ipxe/iomap.h> +#include <ipxe/dma.h> + +/** @file + * + * iPXE DMA API for RISC-V + * + */ + +/** Minimum alignment for coherent DMA allocations + * + * We set this sufficiently high to ensure that we do not end up with + * both cached and uncached uses in the same cacheline. + */ +#define RISCV_DMA_ALIGN 256 + +/** + * Map buffer for DMA + * + * @v dma DMA device + * @v map DMA mapping to fill in + * @v addr Buffer address + * @v len Length of buffer + * @v flags Mapping flags + * @ret rc Return status code + */ +static int riscv_dma_map ( struct dma_device *dma, + struct dma_mapping *map, + void *addr, size_t len, int flags ) { + + /* Sanity check: we cannot support bidirectional mappings */ + assert ( ! ( ( flags & DMA_TX ) & ( flags & DMA_RX ) ) ); + + /* Populate mapping */ + map->dma = dma; + map->offset = 0; + map->token = NULL; + + /* Flush cached data to transmit buffers */ + if ( flags & DMA_TX ) + cache_clean ( addr, len ); + + /* Invalidate cached data in receive buffers and record address */ + if ( flags & DMA_RX ) { + cache_invalidate ( addr, len ); + map->token = addr; + } + + /* Increment mapping count (for debugging) */ + if ( DBG_LOG ) + dma->mapped++; + + return 0; +} + +/** + * Unmap buffer + * + * @v map DMA mapping + * @v len Used length + */ +static void riscv_dma_unmap ( struct dma_mapping *map, size_t len ) { + struct dma_device *dma = map->dma; + void *addr = map->token; + + /* Invalidate cached data in receive buffers */ + if ( addr ) + cache_invalidate ( addr, len ); + + /* Clear mapping */ + map->dma = NULL; + + /* Decrement mapping count (for debugging) */ + if ( DBG_LOG ) + dma->mapped--; +} + +/** + * Allocate and map DMA-coherent buffer + * + * @v dma DMA device + * @v map DMA mapping to fill in + * @v len Length of buffer + * @v align Physical alignment + * @ret addr Buffer address, or NULL on error + */ +static void * riscv_dma_alloc ( struct dma_device *dma, + struct dma_mapping *map, + size_t len, size_t align ) { + physaddr_t phys; + void *addr; + void *caddr; + + /* Round up length and alignment */ + len = ( ( len + RISCV_DMA_ALIGN - 1 ) & ~( RISCV_DMA_ALIGN - 1 ) ); + if ( align < RISCV_DMA_ALIGN ) + align = RISCV_DMA_ALIGN; + + /* Allocate from heap */ + addr = malloc_phys ( len, align ); + if ( ! addr ) + return NULL; + + /* Invalidate any existing cached data */ + cache_invalidate ( addr, len ); + + /* Record mapping */ + map->dma = dma; + map->token = addr; + + /* Calculate coherently-mapped virtual address */ + phys = virt_to_phys ( addr ); + assert ( phys == ( ( uint32_t ) phys ) ); + caddr = ( ( void * ) ( intptr_t ) ( phys + svpage_dma32() ) ); + assert ( phys == virt_to_phys ( caddr ) ); + DBGC ( dma, "DMA allocated [%#08lx,%#08lx) via %p\n", + phys, ( phys + len ), caddr ); + + /* Increment allocation count (for debugging) */ + if ( DBG_LOG ) + dma->allocated++; + + return caddr; +} + +/** + * Unmap and free DMA-coherent buffer + * + * @v dma DMA device + * @v map DMA mapping + * @v addr Buffer address + * @v len Length of buffer + */ +static void riscv_dma_free ( struct dma_mapping *map, + void *addr, size_t len ) { + struct dma_device *dma = map->dma; + + /* Sanity check */ + assert ( virt_to_phys ( addr ) == virt_to_phys ( map->token ) ); + + /* Round up length to match allocation */ + len = ( ( len + RISCV_DMA_ALIGN - 1 ) & ~( RISCV_DMA_ALIGN - 1 ) ); + + /* Free original allocation */ + free_phys ( map->token, len ); + + /* Clear mapping */ + map->dma = NULL; + map->token = NULL; + + /* Decrement allocation count (for debugging) */ + if ( DBG_LOG ) + dma->allocated--; +} + +PROVIDE_DMAAPI ( riscv, dma_map, riscv_dma_map ); +PROVIDE_DMAAPI ( riscv, dma_unmap, riscv_dma_unmap ); +PROVIDE_DMAAPI ( riscv, dma_alloc, riscv_dma_alloc ); +PROVIDE_DMAAPI ( riscv, dma_free, riscv_dma_free ); +PROVIDE_DMAAPI ( riscv, dma_umalloc, riscv_dma_alloc ); +PROVIDE_DMAAPI ( riscv, dma_ufree, riscv_dma_free ); +PROVIDE_DMAAPI_INLINE ( riscv, dma_set_mask ); +PROVIDE_DMAAPI_INLINE ( riscv, dma ); diff --git a/src/arch/riscv/core/riscv_io.c b/src/arch/riscv/core/riscv_io.c new file mode 100644 index 000000000..756b39752 --- /dev/null +++ b/src/arch/riscv/core/riscv_io.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 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 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 <ipxe/io.h> +#include <ipxe/riscv_io.h> + +/** @file + * + * iPXE I/O API for RISC-V + * + */ + +PROVIDE_IOAPI_INLINE ( riscv, phys_to_bus ); +PROVIDE_IOAPI_INLINE ( riscv, bus_to_phys ); +PROVIDE_IOAPI_INLINE ( riscv, readb ); +PROVIDE_IOAPI_INLINE ( riscv, readw ); +PROVIDE_IOAPI_INLINE ( riscv, readl ); +PROVIDE_IOAPI_INLINE ( riscv, writeb ); +PROVIDE_IOAPI_INLINE ( riscv, writew ); +PROVIDE_IOAPI_INLINE ( riscv, writel ); +PROVIDE_IOAPI_INLINE ( riscv, readq ); +PROVIDE_IOAPI_INLINE ( riscv, writeq ); +PROVIDE_IOAPI_INLINE ( riscv, mb ); +PROVIDE_DUMMY_PIO ( riscv ); diff --git a/src/arch/riscv/core/riscv_string.c b/src/arch/riscv/core/riscv_string.c new file mode 100644 index 000000000..e28dc8951 --- /dev/null +++ b/src/arch/riscv/core/riscv_string.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2024 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 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 + * + * Optimised string operations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <string.h> + +/** + * Copy memory area + * + * @v dest Destination address + * @v src Source address + * @v len Length + * @ret dest Destination address + */ +void riscv_memcpy ( void *dest, const void *src, size_t len ) { + size_t len_pre; + size_t len_mid; + size_t len_post; + unsigned long discard_data; + + /* Calculate pre-aligned, aligned, and post-aligned lengths. + * (Align on the destination address, on the assumption that + * misaligned stores are likely to be more expensive than + * misaligned loads.) + */ + len_pre = ( ( sizeof ( unsigned long ) - ( ( intptr_t ) dest ) ) & + ( sizeof ( unsigned long ) - 1 ) ); + if ( len_pre > len ) + len_pre = len; + len -= len_pre; + len_mid = ( len & ~( sizeof ( unsigned long ) - 1 ) ); + len -= len_mid; + len_post = len; + + /* Copy pre-aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + "lb %2, (%1)\n\t" + "sb %2, (%0)\n\t" + "addi %0, %0, 1\n\t" + "addi %1, %1, 1\n\t" + "\n2:\n\t" + "bne %0, %3, 1b\n\t" + : "+r" ( dest ), "+r" ( src ), + "=&r" ( discard_data ) + : "r" ( dest + len_pre ) + : "memory" ); + + /* Copy aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + LOADN " %2, (%1)\n\t" + STOREN " %2, (%0)\n\t" + "addi %0, %0, %4\n\t" + "addi %1, %1, %4\n\t" + "\n2:\n\t" + "bne %0, %3, 1b\n\t" + : "+r" ( dest ), "+r" ( src ), + "=&r" ( discard_data ) + : "r" ( dest + len_mid ), + "i" ( sizeof ( unsigned long ) ) + : "memory" ); + + /* Copy post-aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + "lb %2, (%1)\n\t" + "sb %2, (%0)\n\t" + "addi %0, %0, 1\n\t" + "addi %1, %1, 1\n\t" + "\n2:\n\t" + "bne %0, %3, 1b\n\t" + : "+r" ( dest ), "+r" ( src ), + "=&r" ( discard_data ) + : "r" ( dest + len_post ) + : "memory" ); +} + +/** + * Zero memory region + * + * @v dest Destination region + * @v len Length + */ +void riscv_bzero ( void *dest, size_t len ) { + size_t len_pre; + size_t len_mid; + size_t len_post; + + /* Calculate pre-aligned, aligned, and post-aligned lengths */ + len_pre = ( ( sizeof ( unsigned long ) - ( ( intptr_t ) dest ) ) & + ( sizeof ( unsigned long ) - 1 ) ); + if ( len_pre > len ) + len_pre = len; + len -= len_pre; + len_mid = ( len & ~( sizeof ( unsigned long ) - 1 ) ); + len -= len_mid; + len_post = len; + + /* Zero pre-aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + "sb zero, (%0)\n\t" + "addi %0, %0, 1\n\t" + "\n2:\n\t" + "bne %0, %1, 1b\n\t" + : "+r" ( dest ) + : "r" ( dest + len_pre ) + : "memory" ); + + /* Zero aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + STOREN " zero, (%0)\n\t" + "addi %0, %0, %2\n\t" + "\n2:\n\t" + "bne %0, %1, 1b\n\t" + : "+r" ( dest ) + : "r" ( dest + len_mid ), + "i" ( sizeof ( unsigned long ) ) + : "memory" ); + + /* Zero post-aligned section */ + __asm__ __volatile__ ( "j 2f\n\t" + "\n1:\n\t" + "sb zero, (%0)\n\t" + "addi %0, %0, 1\n\t" + "\n2:\n\t" + "bne %0, %1, 1b\n\t" + : "+r" ( dest ) + : "r" ( dest + len_post ) + : "memory" ); +} + +/** + * Fill memory region + * + * @v dest Destination region + * @v len Length + * @v character Fill character + * + * The unusual parameter order is to allow for more efficient + * tail-calling to riscv_bzero() when zeroing a region. + */ +void riscv_memset ( void *dest, size_t len, int character ) { + + /* Do nothing if length is zero */ + if ( ! len ) + return; + + /* Use optimised zeroing code if applicable */ + if ( character == 0 ) { + riscv_bzero ( dest, len ); + return; + } + + /* Fill one byte at a time. Calling memset() with a non-zero + * value is relatively rare and unlikely to be + * performance-critical. + */ + __asm__ __volatile__ ( "\n1:\n\t" + "sb %2, (%0)\n\t" + "addi %0, %0, 1\n\t" + "bne %0, %1, 1b\n\t" + : "+r" ( dest ) + : "r" ( dest + len ), "r" ( character ) + : "memory" ); +} + +/** + * Copy (possibly overlapping) memory region + * + * @v dest Destination region + * @v src Source region + * @v len Length + */ +void riscv_memmove ( void *dest, const void *src, size_t len ) { + void *orig_dest = dest; + unsigned long discard_data; + + /* Do nothing if length is zero */ + if ( ! len ) + return; + + /* Use memcpy() if copy direction is forwards */ + if ( dest <= src ) { + memcpy ( dest, src, len ); + return; + } + + /* Assume memmove() is not performance-critical, and perform a + * bytewise copy backwards for simplicity. + */ + dest += len; + src += len; + __asm__ __volatile__ ( "\n1:\n\t" + "addi %1, %1, -1\n\t" + "addi %0, %0, -1\n\t" + "lb %2, (%1)\n\t" + "sb %2, (%0)\n\t" + "bne %0, %3, 1b\n\t" + : "+r" ( dest ), "+r" ( src ), + "=&r" ( discard_data ) + : "r" ( orig_dest ) + : "memory" ); +} diff --git a/src/arch/riscv/core/riscv_strings.S b/src/arch/riscv/core/riscv_strings.S new file mode 100644 index 000000000..eb1b397b9 --- /dev/null +++ b/src/arch/riscv/core/riscv_strings.S @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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 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 ) + +/** @file + * + * Byte swapping + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + +/** + * Find first (i.e. least significant) set bit + * + * @v value Value + * @ret lsb Least significant bit set in value (LSB=1), or zero + */ + .section ".text.riscv_ffs" + .globl riscv_ffs +riscv_ffs: + beqz a0, 2f + mv t0, a0 + li a0, ( __riscv_xlen + 1 ) +1: slli t0, t0, 1 + addi a0, a0, -1 + bnez t0, 1b +2: ret + .size riscv_ffs, . - riscv_ffs + +/** + * Find last (i.e. most significant) set bit + * + * @v value Value + * @ret msb Most significant bit set in value (LSB=1), or zero + */ + .section ".text.riscv_fls" + .globl riscv_fls +riscv_fls: + beqz a0, 2f + mv t0, a0 + li a0, __riscv_xlen + bltz t0, 2f +1: slli t0, t0, 1 + addi a0, a0, -1 + bgez t0, 1b +2: ret + .size riscv_fls, . - riscv_fls diff --git a/src/arch/riscv/core/riscv_tcpip.S b/src/arch/riscv/core/riscv_tcpip.S new file mode 100644 index 000000000..8c3e90ba6 --- /dev/null +++ b/src/arch/riscv/core/riscv_tcpip.S @@ -0,0 +1,138 @@ +/* + * 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 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 ) + +/** @file + * + * TCP/IP checksum + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + +/** + * Calculate continued TCP/IP checkum + * + * @v partial Checksum of already-summed data, in network byte order + * @v data Data buffer + * @v len Length of data buffer + * @ret cksum Updated checksum, in network byte order + * + * In practice, this routine will only ever be called with a data + * pointer aligned to a 16-bit boundary. We optimise for this case, + * ensuring that the code would still give correct output if called + * with a misaligned pointer. + */ + .section ".text.tcpip_continue_chksum", "ax", @progbits + .globl tcpip_continue_chksum +tcpip_continue_chksum: + + /* Set up register usage: + * + * a0: checksum low xlen bits + * a1: data pointer + * a2: end of data pointer + * a3: end of data pointer minus a constant offset of interest + * a4: checksum high bits (guaranteed to never carry) / constant 0xffff + * a5: temporary register + */ + not a0, a0 + add a2, a2, a1 + addi a3, a2, -( __riscv_xlen / 8 ) + mv a4, zero + + /* Skip aligned checksumming if data is too short */ + bgtu a1, a3, post_aligned + + /* Checksum 16-bit words until we reach xlen-bit alignment (or + * one byte past xlen-bit alignment). + */ + j 2f +1: lhu a5, (a1) + addi a1, a1, 2 + add a4, a4, a5 +2: andi a5, a1, ( ( ( __riscv_xlen / 8 ) - 1 ) & ~1 ) + bnez a5, 1b + + /* Checksum aligned xlen-bit words */ + j 2f +1: LOADN a5, (a1) + addi a1, a1, ( __riscv_xlen / 8 ) + add a0, a0, a5 + sltu a5, a0, a5 + add a4, a4, a5 +2: bleu a1, a3, 1b + +post_aligned: + /* Checksum remaining 16-bit words */ + addi a3, a2, -2 + j 2f +1: lhu a5, (a1) + addi a1, a1, 2 + add a4, a4, a5 +2: bleu a1, a3, 1b + + /* Checksum final byte if present */ + beq a1, a2, 1f + lbu a5, (a1) + add a4, a4, a5 +1: + /* Fold down to xlen+1 bits */ + add a0, a0, a4 + sltu a4, a0, a4 + + /* Fold down to (xlen/2)+2 bits */ + slli a5, a0, ( __riscv_xlen / 2 ) + srli a0, a0, ( __riscv_xlen / 2 ) + srli a5, a5, ( __riscv_xlen / 2 ) + add a0, a0, a4 + add a0, a0, a5 + + /* Load constant 0xffff for use in subsequent folding */ + li a4, 0xffff + +#if __riscv_xlen >= 64 + /* Fold down to (xlen/4)+3 bits (if xlen >= 64) */ + and a5, a0, a4 + srli a0, a0, ( __riscv_xlen / 4 ) + add a0, a0, a5 +#endif + + /* Fold down to 16+1 bits */ + and a5, a0, a4 + srli a0, a0, 16 + add a0, a0, a5 + + /* Fold down to 16 bits */ + srli a5, a0, 16 + add a0, a0, a5 + srli a5, a0, 17 + add a0, a0, a5 + and a0, a0, a4 + + /* Negate and return */ + xor a0, a0, a4 + ret + .size tcpip_continue_chksum, . - tcpip_continue_chksum diff --git a/src/arch/riscv/core/setjmp.S b/src/arch/riscv/core/setjmp.S new file mode 100644 index 000000000..69d844ac9 --- /dev/null +++ b/src/arch/riscv/core/setjmp.S @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 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 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 ) + +/** @file + * + * Long jumps + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + + /* Must match jmp_buf structure layout */ + .struct 0 +env_ra: .space ( __riscv_xlen / 8 ) +env_sp: .space ( __riscv_xlen / 8 ) +env_s0: .space ( __riscv_xlen / 8 ) +env_s1: .space ( __riscv_xlen / 8 ) +env_s2: .space ( __riscv_xlen / 8 ) +env_s3: .space ( __riscv_xlen / 8 ) +env_s4: .space ( __riscv_xlen / 8 ) +env_s5: .space ( __riscv_xlen / 8 ) +env_s6: .space ( __riscv_xlen / 8 ) +env_s7: .space ( __riscv_xlen / 8 ) +env_s8: .space ( __riscv_xlen / 8 ) +env_s9: .space ( __riscv_xlen / 8 ) +env_s10: .space ( __riscv_xlen / 8 ) +env_s11: .space ( __riscv_xlen / 8 ) + .previous + +/* + * Save stack context for non-local goto + */ + .section ".text.setjmp", "ax", @progbits + .globl setjmp +setjmp: + /* Save registers */ + STOREN ra, env_ra(a0) + STOREN sp, env_sp(a0) + STOREN s0, env_s0(a0) + STOREN s1, env_s1(a0) + STOREN s2, env_s2(a0) + STOREN s3, env_s3(a0) + STOREN s4, env_s4(a0) + STOREN s5, env_s5(a0) + STOREN s6, env_s6(a0) + STOREN s7, env_s7(a0) + STOREN s8, env_s8(a0) + STOREN s9, env_s9(a0) + STOREN s10, env_s10(a0) + STOREN s11, env_s11(a0) + /* Return zero when returning as setjmp() */ + mv a0, zero + ret + .size setjmp, . - setjmp + +/* + * Non-local jump to a saved stack context + */ + .section ".text.longjmp", "ax", @progbits + .globl longjmp +longjmp: + /* Restore registers */ + LOADN s11, env_s11(a0) + LOADN s10, env_s10(a0) + LOADN s9, env_s9(a0) + LOADN s8, env_s8(a0) + LOADN s7, env_s7(a0) + LOADN s6, env_s6(a0) + LOADN s5, env_s5(a0) + LOADN s4, env_s4(a0) + LOADN s3, env_s3(a0) + LOADN s2, env_s2(a0) + LOADN s1, env_s1(a0) + LOADN s0, env_s0(a0) + LOADN sp, env_sp(a0) + LOADN ra, env_ra(a0) + /* Force result to non-zero */ + seqz a0, a1 + or a0, a0, a1 + /* Return to setjmp() caller */ + ret + .size longjmp, . - longjmp diff --git a/src/arch/riscv/core/stack.S b/src/arch/riscv/core/stack.S new file mode 100644 index 000000000..1cd1da7c5 --- /dev/null +++ b/src/arch/riscv/core/stack.S @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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 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 ) + +/** @file + * + * Internal stack + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + +#define STACK_ALIGN 16 + +#define STACK_SIZE 8192 + + .section ".stack", "aw", @nobits + .balign STACK_ALIGN + .globl _stack +_stack: + .space STACK_SIZE + .globl _estack +_estack: 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 ); diff --git a/src/arch/riscv/core/xthead.c b/src/arch/riscv/core/xthead.c new file mode 100644 index 000000000..c947c9dc8 --- /dev/null +++ b/src/arch/riscv/core/xthead.c @@ -0,0 +1,65 @@ +/* + * 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 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 ); + +/** @file + * + * T-Head vendor extensions + * + */ + +#include <ipxe/sbi.h> +#include <ipxe/xthead.h> + +/** Colour for debug messages */ +#define colour THEAD_MVENDORID + +/** + * Check for a T-Head feature via SXSTATUS register + * + * @v feature Feature bit + * @ret supported Feature is supported + */ +int xthead_supported ( unsigned long feature ) { + struct sbi_return ret; + unsigned long sxstatus; + + /* Check for a T-Head CPU */ + ret = sbi_ecall_0 ( SBI_BASE, SBI_BASE_MVENDORID ); + if ( ret.error ) + return 0; + if ( ret.value != THEAD_MVENDORID ) { + DBGC ( colour, "THEAD vendor ID mismatch: %#08lx\n", + ret.value ); + return 0; + } + + /* Read SXSTATUS CSR */ + __asm__ ( "csrr %0, %1" + : "=r" ( sxstatus ) : "i" ( THEAD_SXSTATUS ) ); + DBGC ( colour, "THEAD sxstatus %#08lx\n", sxstatus ); + + /* Check feature bit */ + return ( !! ( sxstatus & feature ) ); +} diff --git a/src/arch/riscv/core/zicbom.c b/src/arch/riscv/core/zicbom.c new file mode 100644 index 000000000..28ff62c22 --- /dev/null +++ b/src/arch/riscv/core/zicbom.c @@ -0,0 +1,255 @@ +/* + * 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 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 ); + +/** @file + * + * Cache-block management operations (Zicbom) + * + * We support explicit cache management operations on I/O buffers. + * These are guaranteed to be aligned on their own size and at least + * as large as a (reasonable) cacheline, and therefore cannot cross a + * cacheline boundary. + */ + +#include <stdint.h> +#include <ipxe/hart.h> +#include <ipxe/xthead.h> +#include <ipxe/zicbom.h> + +/** Minimum supported cacheline size + * + * We assume that cache management operations will ignore the least + * significant address bits, and so we are safe to assume a cacheline + * size that is smaller than the size actually used by the CPU. + * + * Cache clean and invalidate loops could be made faster by detecting + * the actual cacheline size. + */ +#define CACHE_STRIDE 32 + +/** A cache management extension */ +struct cache_extension { + /** + * Clean data cache (i.e. write cached content back to memory) + * + * @v first First byte + * @v last Last byte + */ + void ( * clean ) ( const void *first, const void *last ); + /** + * Invalidate data cache (i.e. discard any cached content) + * + * @v first First byte + * @v last Last byte + */ + void ( * invalidate ) ( void *first, void *last ); +}; + +/** Define an operation to clean the data cache */ +#define CACHE_CLEAN( extension, insn ) \ + static void extension ## _clean ( const void *first, \ + const void *last ) { \ + \ + __asm__ __volatile__ ( ".option arch, +" #extension "\n\t" \ + "\n1:\n\t" \ + insn "\n\t" \ + "addi %0, %0, %2\n\t" \ + "bltu %0, %1, 1b\n\t" \ + : "+r" ( first ) \ + : "r" ( last ), "i" ( CACHE_STRIDE ) ); \ + } + +/** Define an operation to invalidate the data cache */ +#define CACHE_INVALIDATE( extension, insn ) \ + static void extension ## _invalidate ( void *first, \ + void *last ) { \ + \ + __asm__ __volatile__ ( ".option arch, +" #extension "\n\t" \ + "\n1:\n\t" \ + insn "\n\t" \ + "addi %0, %0, %2\n\t" \ + "bltu %0, %1, 1b\n\t" \ + : "+r" ( first ) \ + : "r" ( last ), "i" ( CACHE_STRIDE ) \ + : "memory" ); \ + } + +/** Define a cache management extension */ +#define CACHE_EXTENSION( extension, clean_insn, invalidate_insn ) \ + CACHE_CLEAN ( extension, clean_insn ); \ + CACHE_INVALIDATE ( extension, invalidate_insn ); \ + static struct cache_extension extension = { \ + .clean = extension ## _clean, \ + .invalidate = extension ## _invalidate, \ + }; + +/** The standard Zicbom extension */ +CACHE_EXTENSION ( zicbom, "cbo.clean (%0)", "cbo.inval (%0)" ); + +/** The T-Head cache management extension */ +CACHE_EXTENSION ( xtheadcmo, "th.dcache.cva %0", "th.dcache.iva %0" ); + +/** + * Clean data cache (with fully coherent memory) + * + * @v first First byte + * @v last Last byte + */ +static void cache_coherent_clean ( const void *first __unused, + const void *last __unused ) { + /* Nothing to do */ +} + +/** + * Invalidate data cache (with fully coherent memory) + * + * @v first First byte + * @v last Last byte + */ +static void cache_coherent_invalidate ( void *first __unused, + void *last __unused ) { + /* Nothing to do */ +} + +/** Dummy cache management extension for fully coherent memory */ +static struct cache_extension cache_coherent = { + .clean = cache_coherent_clean, + .invalidate = cache_coherent_invalidate, +}; + +static void cache_auto_detect ( void ); +static void cache_auto_clean ( const void *first, const void *last ); +static void cache_auto_invalidate ( void *first, void *last ); + +/** The autodetect cache management extension */ +static struct cache_extension cache_auto = { + .clean = cache_auto_clean, + .invalidate = cache_auto_invalidate, +}; + +/** Active cache management extension */ +static struct cache_extension *cache_extension = &cache_auto; + +/** + * Clean data cache (i.e. write cached content back to memory) + * + * @v start Start address + * @v len Length + */ +void cache_clean ( const void *start, size_t len ) { + const void *first; + const void *last; + + /* Do nothing for zero-length buffers */ + if ( ! len ) + return; + + /* Construct address range */ + first = ( ( const void * ) + ( ( ( intptr_t ) start ) & ~( CACHE_STRIDE - 1 ) ) ); + last = ( start + len - 1 ); + + /* Clean cache lines */ + cache_extension->clean ( first, last ); +} + +/** + * Invalidate data cache (i.e. discard any cached content) + * + * @v start Start address + * @v len Length + */ +void cache_invalidate ( void *start, size_t len ) { + void *first; + void *last; + + /* Do nothing for zero-length buffers */ + if ( ! len ) + return; + + /* Construct address range */ + first = ( ( void * ) + ( ( ( intptr_t ) start ) & ~( CACHE_STRIDE - 1 ) ) ); + last = ( start + len - 1 ); + + /* Invalidate cache lines */ + cache_extension->invalidate ( first, last ); +} + +/** + * Autodetect and clean data cache + * + * @v first First byte + * @v last Last byte + */ +static void cache_auto_clean ( const void *first, const void *last ) { + + /* Detect cache extension */ + cache_auto_detect(); + + /* Clean data cache */ + cache_extension->clean ( first, last ); +} + +/** + * Autodetect and invalidate data cache + * + * @v first First byte + * @v last Last byte + */ +static void cache_auto_invalidate ( void *first, void *last ) { + + /* Detect cache extension */ + cache_auto_detect(); + + /* Clean data cache */ + cache_extension->invalidate ( first, last ); +} + +/** + * Autodetect cache + * + */ +static void cache_auto_detect ( void ) { + int rc; + + /* Check for standard Zicbom extension */ + if ( ( rc = hart_supported ( "_zicbom" ) ) == 0 ) { + DBGC ( &cache_extension, "CACHE detected Zicbom\n" ); + cache_extension = &zicbom; + return; + } + + /* Check for T-Head cache management extension */ + if ( xthead_supported ( THEAD_SXSTATUS_THEADISAEE ) ) { + DBGC ( &cache_extension, "CACHE detected XTheadCmo\n" ); + cache_extension = &xtheadcmo; + return; + } + + /* Assume coherent memory if no supported extension detected */ + DBGC ( &cache_extension, "CACHE assuming coherent memory\n" ); + cache_extension = &cache_coherent; +} diff --git a/src/arch/riscv/core/zicntr.c b/src/arch/riscv/core/zicntr.c new file mode 100644 index 000000000..fb632b8c0 --- /dev/null +++ b/src/arch/riscv/core/zicntr.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 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 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 ); + +/** @file + * + * Base counters and timers extension (Zicntr) + * + */ + +#include <string.h> +#include <errno.h> +#include <ipxe/fdt.h> +#include <ipxe/csr.h> +#include <ipxe/timer.h> + +/** Timer increment per microsecond */ +static unsigned long zicntr_mhz; + +/** Minimum resolution for scaled timer */ +#define ZICNTR_SCALED_HZ 32 + +/** + * Timer scale (expressed as a bit shift) + * + * We use this to avoid the need for 64-bit divsion on 32-bit systems. + */ +static unsigned int zicntr_scale; + +/** Number of timer ticks per scaled timer increment */ +static unsigned long zicntr_ticks; + +/** Colour for debug messages */ +#define colour &zicntr_mhz + +/** + * Get low XLEN bits of current time + * + * @ret time Current time + */ +static inline __attribute__ (( always_inline )) unsigned long +rdtime_low ( void ) { + unsigned long time; + + /* Read low XLEN bits of current time */ + __asm__ __volatile__ ( "rdtime %0" : "=r" ( time ) ); + return time; +} + +/** + * Get current time, scaled to avoid rollover within a realistic timescale + * + * @ret time Scaled current time + */ +static inline __attribute__ (( always_inline )) unsigned long +rdtime_scaled ( void ) { + union { + uint64_t time; + struct { + uint32_t low; + uint32_t high; + }; + } u; + unsigned long tmp __attribute__ (( unused )); + + /* Read full current time */ +#if __riscv_xlen >= 64 + __asm__ __volatile__ ( "rdtime %0" : "=r" ( u.time ) ); +#else + __asm__ __volatile__ ( "1:\n\t" + "rdtimeh %1\n\t" + "rdtime %0\n\t" + "rdtimeh %2\n\t" + "bne %1, %2, 1b\n\t" + : "=r" ( u.low ), "=r" ( u.high ), + "=r" ( tmp ) ); +#endif + + /* Scale time to avoid XLEN-bit rollover */ + return ( u.time >> zicntr_scale ); +} + +/** + * Get current system time in ticks + * + * @ret ticks Current time, in ticks + */ +static unsigned long zicntr_currticks ( void ) { + unsigned long scaled; + + /* Get scaled time and convert to ticks */ + scaled = rdtime_scaled(); + return ( scaled * zicntr_ticks ); +} + +/** + * Delay for a fixed number of microseconds + * + * @v usecs Number of microseconds for which to delay + */ +static void zicntr_udelay ( unsigned long usecs ) { + unsigned long start; + unsigned long elapsed; + unsigned long threshold; + + /* Delay until sufficient time has elapsed */ + start = rdtime_low(); + threshold = ( usecs * zicntr_mhz ); + do { + elapsed = ( rdtime_low() - start ); + } while ( elapsed < threshold ); +} + +/** + * Probe timer + * + * @ret rc Return status code + */ +static int zicntr_probe ( void ) { + unsigned int offset; + union { + uint64_t freq; + int64_t sfreq; + } u; + int rc; + + /* Check if time CSR can be read */ + if ( ! csr_can_read ( "time" ) ) { + DBGC ( colour, "ZICNTR cannot read TIME CSR\n" ); + return -ENOTSUP; + } + + /* Get timer frequency */ + if ( ( ( rc = fdt_path ( &sysfdt, "/cpus", &offset ) ) != 0 ) || + ( ( rc = fdt_u64 ( &sysfdt, offset, "timebase-frequency", + &u.freq ) ) != 0 ) ) { + DBGC ( colour, "ZICNTR could not determine frequency: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Convert to MHz (without 64-bit division) */ + do { + zicntr_mhz++; + u.sfreq -= 1000000; + } while ( u.sfreq > 0 ); + + /* Calibrate currticks() scaling factor */ + zicntr_scale = 31; + zicntr_ticks = ( ( 1UL << zicntr_scale ) / + ( zicntr_mhz * ( 1000000 / TICKS_PER_SEC ) ) ); + while ( zicntr_ticks > ( TICKS_PER_SEC / ZICNTR_SCALED_HZ ) ) { + zicntr_scale--; + zicntr_ticks >>= 1; + } + DBGC ( colour, "ZICNTR at %ld MHz, %ld ticks per 2^%d increments\n", + zicntr_mhz, zicntr_ticks, zicntr_scale ); + if ( ! zicntr_ticks ) { + DBGC ( colour, "ZICNTR has zero ticks per 2^%d increments\n", + zicntr_scale ); + return -EIO; + } + + return 0; +} + +/** Zicntr timer */ +struct timer zicntr_timer __timer ( TIMER_PREFERRED ) = { + .name = "zicntr", + .probe = zicntr_probe, + .currticks = zicntr_currticks, + .udelay = zicntr_udelay, +}; diff --git a/src/arch/riscv/core/zkr.c b/src/arch/riscv/core/zkr.c new file mode 100644 index 000000000..b27a7f076 --- /dev/null +++ b/src/arch/riscv/core/zkr.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 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 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 ); + +/** @file + * + * Entropy source extension (Zkr) + * + */ + +#include <errno.h> +#include <ipxe/csr.h> +#include <ipxe/entropy.h> +#include <ipxe/drbg.h> + +struct entropy_source zkr_entropy __entropy_source ( ENTROPY_PREFERRED ); + +/** Seed CSR operational state */ +#define ZKR_SEED_OPST_MASK 0xc0000000UL +#define ZKR_SEED_OPST_ES16 0x80000000UL /**< 16 bits of entropy available */ + +/** Number of times to retry reading from seed CSR */ +#define ZKR_SEED_MAX_RETRY 1024 + +/** Colour for debug messages */ +#define colour &zkr_entropy + +/** + * Enable entropy gathering + * + * @ret rc Return status code + */ +static int zkr_entropy_enable ( void ) { + + /* Check if seed CSR is accessible in S-mode */ + if ( ! csr_can_write ( "seed", 0 ) ) { + DBGC ( colour, "ZKR cannot access seed CSR\n" ); + return -ENOTSUP; + } + + /* RISC-V ISA mandates that 128 bits of full entropy shall be + * obtained from 256 entropy bits read from the seed CSR. + * + * Each 16-bit sample therefore contains 8 bits of + * min-entropy. + */ + entropy_init ( &zkr_entropy, MIN_ENTROPY ( 8.0 ) ); + + return 0; +} + +/** + * Get noise sample + * + * @ret noise Noise sample + * @ret rc Return status code + */ +static int zkr_get_noise ( noise_sample_t *noise ) { + unsigned long seed; + unsigned int i; + + /* Read entropy from seed CSR */ + for ( i = 0 ; i < ZKR_SEED_MAX_RETRY ; i++ ) { + + /* Read seed CSR */ + __asm__ __volatile__ ( "csrrw %0, seed, zero" : + "=r" ( seed ) ); + + /* Check operationsl state */ + if ( ( seed & ZKR_SEED_OPST_MASK ) == ZKR_SEED_OPST_ES16 ) { + + /* Return entropy from both halves of the + * 16-bit entropy source value. + */ + *noise = ( seed ^ ( seed >> 8 ) ); + return 0; + } + } + + DBGC ( colour, "ZKR could not source entropy (seed %#08lx)\n", seed ); + return -EBUSY; +} + +/** Hardware entropy source */ +struct entropy_source zkr_entropy __entropy_source ( ENTROPY_PREFERRED ) = { + .name = "zkr", + .enable = zkr_entropy_enable, + .get_noise = zkr_get_noise, +}; diff --git a/src/arch/riscv/include/bits/bigint.h b/src/arch/riscv/include/bits/bigint.h new file mode 100644 index 000000000..7f87d9748 --- /dev/null +++ b/src/arch/riscv/include/bits/bigint.h @@ -0,0 +1,381 @@ +#ifndef _BITS_BIGINT_H +#define _BITS_BIGINT_H + +/** @file + * + * Big integer support + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> +#include <string.h> +#include <strings.h> + +/** Element of a big integer */ +typedef unsigned long bigint_element_t; + +/** + * Initialise big integer + * + * @v value0 Element 0 of big integer to initialise + * @v size Number of elements + * @v data Raw data + * @v len Length of raw data + */ +static inline __attribute__ (( always_inline )) void +bigint_init_raw ( unsigned long *value0, unsigned int size, + const void *data, size_t len ) { + size_t pad_len = ( sizeof ( bigint_t ( size ) ) - len ); + uint8_t *value_byte = ( ( void * ) value0 ); + const uint8_t *data_byte = ( data + len ); + + /* Copy raw data in reverse order, padding with zeros */ + while ( len-- ) + *(value_byte++) = *(--data_byte); + while ( pad_len-- ) + *(value_byte++) = 0; +} + +/** + * Add big integers + * + * @v addend0 Element 0 of big integer to add + * @v value0 Element 0 of big integer to be added to + * @v size Number of elements + * @ret carry Carry out + */ +static inline __attribute__ (( always_inline )) int +bigint_add_raw ( const unsigned long *addend0, unsigned long *value0, + unsigned int size ) { + bigint_t ( size ) __attribute__ (( may_alias )) *value = + ( ( void * ) value0 ); + unsigned long *valueN = ( value0 + size ); + unsigned long *discard_addend; + unsigned long *discard_value; + unsigned long discard_addend_i; + unsigned long discard_value_i; + unsigned long discard_temp; + unsigned long carry; + + __asm__ __volatile__ ( "\n1:\n\t" + /* Load addend[i] and value[i] */ + LOADN " %2, (%0)\n\t" + LOADN " %3, (%1)\n\t" + /* Add carry flag and addend */ + "add %3, %3, %5\n\t" + "sltu %4, %3, %5\n\t" + "add %3, %3, %2\n\t" + "sltu %5, %3, %2\n\t" + "or %5, %4, %5\n\t" + /* Store value[i] */ + STOREN " %3, (%1)\n\t" + /* Loop */ + "addi %0, %0, %8\n\t" + "addi %1, %1, %8\n\t" + "bne %1, %7, 1b\n\t" + : "=&r" ( discard_addend ), + "=&r" ( discard_value ), + "=&r" ( discard_addend_i ), + "=&r" ( discard_value_i ), + "=&r" ( discard_temp ), + "=&r" ( carry ), + "+m" ( *value ) + : "r" ( valueN ), + "i" ( sizeof ( unsigned long ) ), + "0" ( addend0 ), "1" ( value0 ), "5" ( 0 ) ); + return carry; +} + +/** + * Subtract big integers + * + * @v subtrahend0 Element 0 of big integer to subtract + * @v value0 Element 0 of big integer to be subtracted from + * @v size Number of elements + * @ret borrow Borrow out + */ +static inline __attribute__ (( always_inline )) int +bigint_subtract_raw ( const unsigned long *subtrahend0, unsigned long *value0, + unsigned int size ) { + bigint_t ( size ) __attribute__ (( may_alias )) *value = + ( ( void * ) value0 ); + unsigned long *valueN = ( value0 + size ); + unsigned long *discard_subtrahend; + unsigned long *discard_value; + unsigned long discard_subtrahend_i; + unsigned long discard_value_i; + unsigned long discard_temp; + unsigned long borrow; + + __asm__ __volatile__ ( "\n1:\n\t" + /* Load subtrahend[i] and value[i] */ + LOADN " %2, (%0)\n\t" + LOADN " %3, (%1)\n\t" + /* Subtract carry flag and subtrahend */ + "sltu %4, %3, %5\n\t" + "sub %3, %3, %5\n\t" + "sltu %5, %3, %2\n\t" + "sub %3, %3, %2\n\t" + "or %5, %5, %4\n\t" + /* Store value[i] */ + STOREN " %3, (%1)\n\t" + /* Loop */ + "addi %0, %0, %8\n\t" + "addi %1, %1, %8\n\t" + "bne %1, %7, 1b\n\t" + : "=&r" ( discard_subtrahend ), + "=&r" ( discard_value ), + "=&r" ( discard_subtrahend_i ), + "=&r" ( discard_value_i ), + "=&r" ( discard_temp ), + "=&r" ( borrow ), + "+m" ( *value ) + : "r" ( valueN ), + "i" ( sizeof ( unsigned long ) ), + "0" ( subtrahend0 ), "1" ( value0 ), + "5" ( 0 ) ); + return borrow; +} + +/** + * Shift big integer left + * + * @v value0 Element 0 of big integer + * @v size Number of elements + * @ret out Bit shifted out + */ +static inline __attribute__ (( always_inline )) int +bigint_shl_raw ( unsigned long *value0, unsigned int size ) { + bigint_t ( size ) __attribute__ (( may_alias )) *value = + ( ( void * ) value0 ); + unsigned long *valueN = ( value0 + size ); + unsigned long *discard_value; + unsigned long discard_value_i; + unsigned long discard_temp; + unsigned long carry; + + __asm__ __volatile__ ( "\n1:\n\t" + /* Load value[i] */ + LOADN " %1, (%0)\n\t" + /* Shift left */ + "slli %2, %1, 1\n\t" + "or %2, %2, %3\n\t" + "srli %3, %1, %7\n\t" + /* Store value[i] */ + STOREN " %2, (%0)\n\t" + /* Loop */ + "addi %0, %0, %6\n\t" + "bne %0, %5, 1b\n\t" + : "=&r" ( discard_value ), + "=&r" ( discard_value_i ), + "=&r" ( discard_temp ), + "=&r" ( carry ), + "+m" ( *value ) + : "r" ( valueN ), + "i" ( sizeof ( unsigned long ) ), + "i" ( ( 8 * sizeof ( unsigned long ) - 1 ) ), + "0" ( value0 ), "3" ( 0 ) ); + return carry; +} + +/** + * Shift big integer right + * + * @v value0 Element 0 of big integer + * @v size Number of elements + * @ret out Bit shifted out + */ +static inline __attribute__ (( always_inline )) int +bigint_shr_raw ( unsigned long *value0, unsigned int size ) { + bigint_t ( size ) __attribute__ (( may_alias )) *value = + ( ( void * ) value0 ); + unsigned long *valueN = ( value0 + size ); + unsigned long *discard_value; + unsigned long discard_value_i; + unsigned long discard_temp; + unsigned long carry; + + __asm__ __volatile__ ( "\n1:\n\t" + /* Load value[i] */ + LOADN " %1, %6(%0)\n\t" + /* Shift right */ + "srli %2, %1, 1\n\t" + "or %2, %2, %3\n\t" + "slli %3, %1, %7\n\t" + /* Store value[i] */ + STOREN " %2, %6(%0)\n\t" + /* Loop */ + "addi %0, %0, %6\n\t" + "bne %0, %5, 1b\n\t" + : "=&r" ( discard_value ), + "=&r" ( discard_value_i ), + "=&r" ( discard_temp ), + "=&r" ( carry ), + "+m" ( *value ) + : "r" ( value0 ), + "i" ( -( sizeof ( unsigned long ) ) ), + "i" ( ( 8 * sizeof ( unsigned long ) - 1 ) ), + "0" ( valueN ), "3" ( 0 ) ); + return ( !! carry ); +} + +/** + * Test if big integer is equal to zero + * + * @v value0 Element 0 of big integer + * @v size Number of elements + * @ret is_zero Big integer is equal to zero + */ +static inline __attribute__ (( always_inline, pure )) int +bigint_is_zero_raw ( const unsigned long *value0, unsigned int size ) { + const unsigned long *value = value0; + unsigned long value_i; + + do { + value_i = *(value++); + if ( value_i ) + break; + } while ( --size ); + + return ( value_i == 0 ); +} + +/** + * Compare big integers + * + * @v value0 Element 0 of big integer + * @v reference0 Element 0 of reference big integer + * @v size Number of elements + * @ret geq Big integer is greater than or equal to the reference + */ +static inline __attribute__ (( always_inline, pure )) int +bigint_is_geq_raw ( const unsigned long *value0, + const unsigned long *reference0, unsigned int size ) { + const unsigned long *value = ( value0 + size ); + const unsigned long *reference = ( reference0 + size ); + unsigned long value_i; + unsigned long reference_i; + + do { + value_i = *(--value); + reference_i = *(--reference); + if ( value_i != reference_i ) + break; + } while ( --size ); + + return ( value_i >= reference_i ); +} + +/** + * Find highest bit set in big integer + * + * @v value0 Element 0 of big integer + * @v size Number of elements + * @ret max_bit Highest bit set + 1 (or 0 if no bits set) + */ +static inline __attribute__ (( always_inline )) int +bigint_max_set_bit_raw ( const unsigned long *value0, unsigned int size ) { + const unsigned long *value = ( value0 + size ); + int max_bit = ( 8 * sizeof ( bigint_t ( size ) ) ); + unsigned long value_i; + + do { + value_i = *(--value); + max_bit -= ( ( 8 * sizeof ( *value0 ) ) - fls ( value_i ) ); + if ( value_i ) + break; + } while ( --size ); + + return max_bit; +} + +/** + * Grow big integer + * + * @v source0 Element 0 of source big integer + * @v source_size Number of elements in source big integer + * @v dest0 Element 0 of destination big integer + * @v dest_size Number of elements in destination big integer + */ +static inline __attribute__ (( always_inline )) void +bigint_grow_raw ( const unsigned long *source0, unsigned int source_size, + unsigned long *dest0, unsigned int dest_size ) { + unsigned int pad_size = ( dest_size - source_size ); + + memcpy ( dest0, source0, sizeof ( bigint_t ( source_size ) ) ); + memset ( ( dest0 + source_size ), 0, sizeof ( bigint_t ( pad_size ) ) ); +} + +/** + * Shrink big integer + * + * @v source0 Element 0 of source big integer + * @v source_size Number of elements in source big integer + * @v dest0 Element 0 of destination big integer + * @v dest_size Number of elements in destination big integer + */ +static inline __attribute__ (( always_inline )) void +bigint_shrink_raw ( const unsigned long *source0, + unsigned int source_size __unused, + unsigned long *dest0, unsigned int dest_size ) { + + memcpy ( dest0, source0, sizeof ( bigint_t ( dest_size ) ) ); +} + +/** + * Finalise big integer + * + * @v value0 Element 0 of big integer to finalise + * @v size Number of elements + * @v out Output buffer + * @v len Length of output buffer + */ +static inline __attribute__ (( always_inline )) void +bigint_done_raw ( const unsigned long *value0, unsigned int size __unused, + void *out, size_t len ) { + const uint8_t *value_byte = ( ( const void * ) value0 ); + uint8_t *out_byte = ( out + len ); + + /* Copy raw data in reverse order */ + while ( len-- ) + *(--out_byte) = *(value_byte++); +} + +/** + * Multiply big integer elements + * + * @v multiplicand Multiplicand element + * @v multiplier Multiplier element + * @v result Result element + * @v carry Carry element + */ +static inline __attribute__ (( always_inline )) void +bigint_multiply_one ( const unsigned long multiplicand, + const unsigned long multiplier, + unsigned long *result, unsigned long *carry ) { + unsigned long discard_low; + unsigned long discard_high; + unsigned long discard_carry; + + __asm__ __volatile__ ( /* Perform multiplication */ + "mulhu %1, %5, %6\n\t" + "mul %0, %5, %6\n\t" + /* Accumulate low half */ + "add %3, %3, %0\n\t" + "sltu %2, %3, %0\n\t" + "add %1, %1, %2\n\t" + /* Accumulate carry (cannot overflow) */ + "add %3, %3, %4\n\t" + "sltu %2, %3, %4\n\t" + "add %4, %1, %2\n\t" + : "=r" ( discard_low ), + "=&r" ( discard_high ), + "=r" ( discard_carry ), + "+r" ( *result ), + "+r" ( *carry ) + : "r" ( multiplicand ), + "r" ( multiplier ) ); +} + +#endif /* _BITS_BIGINT_H */ diff --git a/src/arch/riscv/include/bits/bitops.h b/src/arch/riscv/include/bits/bitops.h new file mode 100644 index 000000000..2019db99a --- /dev/null +++ b/src/arch/riscv/include/bits/bitops.h @@ -0,0 +1,82 @@ +#ifndef _BITS_BITOPS_H +#define _BITS_BITOPS_H + +/** @file + * + * RISC-V bit operations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +/** + * Test and set bit atomically + * + * @v bit Bit to set + * @v bits Bit field + * @ret old Old value of bit (zero or non-zero) + */ +static inline __attribute__ (( always_inline )) int +test_and_set_bit ( unsigned int bit, volatile void *bits ) { + unsigned int index = ( bit / 32 ); + unsigned int offset = ( bit % 32 ); + volatile uint32_t *word = ( ( ( volatile uint32_t * ) bits ) + index ); + uint32_t mask = ( 1U << offset ); + uint32_t old; + + __asm__ __volatile__ ( "amoor.w %0, %2, %1" + : "=r" ( old ), "+A" ( *word ) + : "r" ( mask ) ); + + return ( !! ( old & mask ) ); +} + +/** + * Test and clear bit atomically + * + * @v bit Bit to set + * @v bits Bit field + * @ret old Old value of bit (zero or non-zero) + */ +static inline __attribute__ (( always_inline )) int +test_and_clear_bit ( unsigned int bit, volatile void *bits ) { + unsigned int index = ( bit / 32 ); + unsigned int offset = ( bit % 32 ); + volatile uint32_t *word = ( ( ( volatile uint32_t * ) bits ) + index ); + uint32_t mask = ( 1U << offset ); + uint32_t old; + + __asm__ __volatile__ ( "amoand.w %0, %2, %1" + : "=r" ( old ), "+A" ( *word ) + : "r" ( ~mask ) ); + + return ( !! ( old & mask ) ); +} + +/** + * Set bit atomically + * + * @v bit Bit to set + * @v bits Bit field + */ +static inline __attribute__ (( always_inline )) void +set_bit ( unsigned int bit, volatile void *bits ) { + + test_and_set_bit ( bit, bits ); +} + +/** + * Clear bit atomically + * + * @v bit Bit to set + * @v bits Bit field + */ +static inline __attribute__ (( always_inline )) void +clear_bit ( unsigned int bit, volatile void *bits ) { + + test_and_clear_bit ( bit, bits ); +} + +#endif /* _BITS_BITOPS_H */ diff --git a/src/arch/riscv/include/bits/byteswap.h b/src/arch/riscv/include/bits/byteswap.h new file mode 100644 index 000000000..56d03f64e --- /dev/null +++ b/src/arch/riscv/include/bits/byteswap.h @@ -0,0 +1,48 @@ +#ifndef _BITS_BYTESWAP_H +#define _BITS_BYTESWAP_H + +/** @file + * + * Byte-order swapping functions + * + */ + +#include <stdint.h> + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern __asmcall uint64_t riscv_swap_word ( uint64_t x ); +extern __asmcall unsigned long riscv_swap_half ( unsigned long x ); +extern __asmcall unsigned long riscv_swap_byte ( unsigned long x ); + +static inline __attribute__ (( always_inline, const )) uint16_t +__bswap_variable_16 ( uint16_t x ) { + return riscv_swap_byte ( x ); +} + +static inline __attribute__ (( always_inline )) void +__bswap_16s ( uint16_t *x ) { + *x = riscv_swap_byte ( *x ); +} + +static inline __attribute__ (( always_inline, const )) uint32_t +__bswap_variable_32 ( uint32_t x ) { + return riscv_swap_half ( x ); +} + +static inline __attribute__ (( always_inline )) void +__bswap_32s ( uint32_t *x ) { + *x = riscv_swap_half ( *x ); +} + +static inline __attribute__ (( always_inline, const )) uint64_t +__bswap_variable_64 ( uint64_t x ) { + return riscv_swap_word ( x ); +} + +static inline __attribute__ (( always_inline )) void +__bswap_64s ( uint64_t *x ) { + *x = riscv_swap_word ( *x ); +} + +#endif /* _BITS_BYTESWAP_H */ diff --git a/src/arch/riscv/include/bits/compiler.h b/src/arch/riscv/include/bits/compiler.h new file mode 100644 index 000000000..624a16108 --- /dev/null +++ b/src/arch/riscv/include/bits/compiler.h @@ -0,0 +1,40 @@ +#ifndef _BITS_COMPILER_H +#define _BITS_COMPILER_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** Dummy relocation type */ +#define RELOC_TYPE_NONE R_RISCV_NONE + +/* Determine load/store instructions for natural bit width */ +#if __riscv_xlen == 128 +#define NATURAL_SUFFIX q +#elif __riscv_xlen == 64 +#define NATURAL_SUFFIX d +#elif __riscv_xlen == 32 +#define NATURAL_SUFFIX w +#else +#error "Unsupported bit width" +#endif +#ifdef ASSEMBLY +#define LOADN _C2 ( L, NATURAL_SUFFIX ) +#define STOREN _C2 ( S, NATURAL_SUFFIX ) +#else +#define LOADN "L" _S2 ( NATURAL_SUFFIX ) +#define STOREN "S" _S2 ( NATURAL_SUFFIX ) +#endif + +#ifndef ASSEMBLY + +/** Unprefixed constant operand modifier */ +#define ASM_NO_PREFIX "" + +/** Declare a function with standard calling conventions */ +#define __asmcall + +/** Declare a function with libgcc implicit linkage */ +#define __libgcc + +#endif /* ASSEMBLY */ + +#endif /* _BITS_COMPILER_H */ diff --git a/src/arch/riscv/include/bits/dma.h b/src/arch/riscv/include/bits/dma.h new file mode 100644 index 000000000..f7decd14c --- /dev/null +++ b/src/arch/riscv/include/bits/dma.h @@ -0,0 +1,14 @@ +#ifndef _BITS_DMA_H +#define _BITS_DMA_H + +/** @file + * + * RISCV-specific DMA API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/riscv_dma.h> + +#endif /* _BITS_DMA_H */ diff --git a/src/arch/riscv/include/bits/endian.h b/src/arch/riscv/include/bits/endian.h new file mode 100644 index 000000000..85718cfdd --- /dev/null +++ b/src/arch/riscv/include/bits/endian.h @@ -0,0 +1,8 @@ +#ifndef _BITS_ENDIAN_H +#define _BITS_ENDIAN_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#define __BYTE_ORDER __LITTLE_ENDIAN + +#endif /* _BITS_ENDIAN_H */ diff --git a/src/arch/riscv/include/bits/errfile.h b/src/arch/riscv/include/bits/errfile.h new file mode 100644 index 000000000..bdd2927a4 --- /dev/null +++ b/src/arch/riscv/include/bits/errfile.h @@ -0,0 +1,24 @@ +#ifndef _BITS_ERRFILE_H +#define _BITS_ERRFILE_H + +/** @file + * + * RISC-V error file identifiers + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @addtogroup errfile Error file identifiers + * @{ + */ + +#define ERRFILE_sbi_reboot ( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 ) +#define ERRFILE_hart ( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 ) +#define ERRFILE_zicntr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 ) +#define ERRFILE_zkr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00030000 ) + +/** @} */ + +#endif /* _BITS_ERRFILE_H */ diff --git a/src/arch/riscv/include/bits/io.h b/src/arch/riscv/include/bits/io.h new file mode 100644 index 000000000..4296e318a --- /dev/null +++ b/src/arch/riscv/include/bits/io.h @@ -0,0 +1,17 @@ +#ifndef _BITS_IO_H +#define _BITS_IO_H + +/** @file + * + * RISCV-specific I/O API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** Page shift */ +#define PAGE_SHIFT 12 + +#include <ipxe/riscv_io.h> + +#endif /* _BITS_IO_H */ 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 <ipxe/svpage.h> + +#endif /* _BITS_IOMAP_H */ diff --git a/src/arch/riscv/include/bits/lkrn.h b/src/arch/riscv/include/bits/lkrn.h new file mode 100644 index 000000000..d26108647 --- /dev/null +++ b/src/arch/riscv/include/bits/lkrn.h @@ -0,0 +1,34 @@ +#ifndef _BITS_LKRN_H +#define _BITS_LKRN_H + +/** @file + * + * Linux kernel image invocation + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/hart.h> + +/** Header magic value */ +#define LKRN_MAGIC_ARCH LKRN_MAGIC_RISCV + +/** + * Jump to kernel entry point + * + * @v entry Kernel entry point + * @v fdt Device tree + */ +static inline __attribute__ (( noreturn )) void +lkrn_jump ( physaddr_t entry, physaddr_t fdt ) { + register unsigned long a0 asm ( "a0" ) = boot_hart; + register unsigned long a1 asm ( "a1" ) = fdt; + + __asm__ __volatile__ ( "call disable_paging\n\t" + "jr %2\n\t" + : : "r" ( a0 ), "r" ( a1 ), "r" ( entry ) ); + __builtin_unreachable(); +} + +#endif /* _BITS_LKRN_H */ diff --git a/src/arch/riscv/include/bits/nap.h b/src/arch/riscv/include/bits/nap.h new file mode 100644 index 000000000..331399f46 --- /dev/null +++ b/src/arch/riscv/include/bits/nap.h @@ -0,0 +1,20 @@ +#ifndef _BITS_NAP_H +#define _BITS_NAP_H + +/** @file + * + * RISCV-specific CPU sleeping API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * Sleep until next CPU interrupt + * + */ +static inline __attribute__ (( always_inline )) void cpu_halt ( void ) { + __asm__ __volatile__ ( "wfi" ); +} + +#endif /* _BITS_NAP_H */ diff --git a/src/arch/riscv/include/bits/profile.h b/src/arch/riscv/include/bits/profile.h new file mode 100644 index 000000000..e9e003dab --- /dev/null +++ b/src/arch/riscv/include/bits/profile.h @@ -0,0 +1,28 @@ +#ifndef _BITS_PROFILE_H +#define _BITS_PROFILE_H + +/** @file + * + * Profiling + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +/** + * Get profiling timestamp + * + * @ret timestamp Timestamp + */ +static inline __attribute__ (( always_inline )) unsigned long +profile_timestamp ( void ) { + unsigned long cycles; + + /* Read timestamp counter */ + __asm__ __volatile__ ( "rdcycle %0" : "=r" ( cycles ) ); + return cycles; +} + +#endif /* _BITS_PROFILE_H */ diff --git a/src/arch/riscv/include/bits/reboot.h b/src/arch/riscv/include/bits/reboot.h new file mode 100644 index 000000000..01272483b --- /dev/null +++ b/src/arch/riscv/include/bits/reboot.h @@ -0,0 +1,14 @@ +#ifndef _BITS_REBOOT_H +#define _BITS_REBOOT_H + +/** @file + * + * RISCV-specific reboot API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/sbi_reboot.h> + +#endif /* _BITS_REBOOT_H */ diff --git a/src/arch/riscv/include/bits/setjmp.h b/src/arch/riscv/include/bits/setjmp.h new file mode 100644 index 000000000..5186fadaf --- /dev/null +++ b/src/arch/riscv/include/bits/setjmp.h @@ -0,0 +1,16 @@ +#ifndef _BITS_SETJMP_H +#define _BITS_SETJMP_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** A jump buffer */ +typedef struct { + /** Return address (ra) */ + unsigned long ra; + /** Stack pointer (sp) */ + unsigned long sp; + /** Callee-saved registers (s0-s11) */ + unsigned long s[12]; +} jmp_buf[1]; + +#endif /* _BITS_SETJMP_H */ diff --git a/src/arch/riscv/include/bits/stdint.h b/src/arch/riscv/include/bits/stdint.h new file mode 100644 index 000000000..fe1f9946a --- /dev/null +++ b/src/arch/riscv/include/bits/stdint.h @@ -0,0 +1,23 @@ +#ifndef _BITS_STDINT_H +#define _BITS_STDINT_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +typedef __SIZE_TYPE__ size_t; +typedef signed long ssize_t; +typedef signed long off_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef signed long long int64_t; + +typedef unsigned long physaddr_t; +typedef unsigned long intptr_t; + +#endif /* _BITS_STDINT_H */ diff --git a/src/arch/riscv/include/bits/string.h b/src/arch/riscv/include/bits/string.h new file mode 100644 index 000000000..87834d91a --- /dev/null +++ b/src/arch/riscv/include/bits/string.h @@ -0,0 +1,82 @@ +#ifndef _BITS_STRING_H +#define _BITS_STRING_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * String functions + * + */ + +extern void riscv_bzero ( void *dest, size_t len ); +extern void riscv_memset ( void *dest, size_t len, int character ); +extern void riscv_memcpy ( void *dest, const void *src, size_t len ); +extern void riscv_memmove ( void *dest, const void *src, size_t len ); + +/** + * Fill memory region + * + * @v dest Destination region + * @v character Fill character + * @v len Length + * @ret dest Destination region + */ +static inline __attribute__ (( always_inline )) void * +memset ( void *dest, int character, size_t len ) { + + /* For zeroing larger or non-constant lengths, use the + * optimised variable-length zeroing code. + */ + if ( __builtin_constant_p ( character ) && ( character == 0 ) ) { + riscv_bzero ( dest, len ); + return dest; + } + + /* Not necessarily zeroing: use basic variable-length code */ + riscv_memset ( dest, len, character ); + return dest; +} + +/** + * Copy memory region + * + * @v dest Destination region + * @v src Source region + * @v len Length + * @ret dest Destination region + */ +static inline __attribute__ (( always_inline )) void * +memcpy ( void *dest, const void *src, size_t len ) { + + /* Otherwise, use variable-length code */ + riscv_memcpy ( dest, src, len ); + return dest; +} + +/** + * Copy (possibly overlapping) memory region + * + * @v dest Destination region + * @v src Source region + * @v len Length + * @ret dest Destination region + */ +static inline __attribute__ (( always_inline )) void * +memmove ( void *dest, const void *src, size_t len ) { + ssize_t offset = ( dest - src ); + + /* If direction of copy is known to be forwards at build time, + * then use variable-length memcpy(). + */ + if ( __builtin_constant_p ( offset ) && ( offset <= 0 ) ) { + riscv_memcpy ( dest, src, len ); + return dest; + } + + /* Otherwise, use ambidirectional copy */ + riscv_memmove ( dest, src, len ); + return dest; +} + +#endif /* _BITS_STRING_H */ diff --git a/src/arch/riscv/include/bits/strings.h b/src/arch/riscv/include/bits/strings.h new file mode 100644 index 000000000..dd6d458b2 --- /dev/null +++ b/src/arch/riscv/include/bits/strings.h @@ -0,0 +1,91 @@ +#ifndef _BITS_STRINGS_H +#define _BITS_STRINGS_H + +/** @file + * + * String functions + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern __asmcall unsigned long riscv_ffs ( unsigned long value ); +extern __asmcall unsigned long riscv_fls ( unsigned long value ); + +/** + * Find first (i.e. least significant) set bit + * + * @v value Value + * @ret lsb Least significant bit set in value (LSB=1), or zero + */ +static inline __attribute__ (( always_inline )) int __ffsl ( long value ) { + + return riscv_ffs ( value ); +} + +/** + * Find first (i.e. least significant) set bit + * + * @v value Value + * @ret lsb Least significant bit set in value (LSB=1), or zero + */ +static inline __attribute__ (( always_inline )) int __ffsll ( long long value ){ + unsigned long low = value; + unsigned long high; + + /* Check machine word size */ + if ( sizeof ( value ) > sizeof ( low ) ) { + /* 32-bit */ + high = ( value >> 32 ); + if ( low ) { + return ( __ffsl ( low ) ); + } else if ( high ) { + return ( 32 + __ffsl ( high ) ); + } else { + return 0; + } + } else { + /* 64-bit */ + return ( __ffsl ( low ) ); + } +} + +/** + * Find last (i.e. most significant) set bit + * + * @v value Value + * @ret msb Most significant bit set in value (LSB=1), or zero + */ +static inline __attribute__ (( always_inline )) int __flsl ( long value ) { + + return riscv_fls ( value ); +} + +/** + * Find last (i.e. most significant) set bit + * + * @v value Value + * @ret msb Most significant bit set in value (LSB=1), or zero + */ +static inline __attribute__ (( always_inline )) int __flsll ( long long value ){ + unsigned long low = value; + unsigned long high; + + /* Check machine word size */ + if ( sizeof ( value ) > sizeof ( low ) ) { + /* 32-bit */ + high = ( value >> 32 ); + if ( high ) { + return ( 32 + __flsl ( high ) ); + } else if ( low ) { + return ( __flsl ( low ) ); + } else { + return 0; + } + } else { + /* 64-bit */ + return ( __flsl ( low ) ); + } +} + +#endif /* _BITS_STRINGS_H */ diff --git a/src/arch/riscv/include/bits/tcpip.h b/src/arch/riscv/include/bits/tcpip.h new file mode 100644 index 000000000..0ac55b1a0 --- /dev/null +++ b/src/arch/riscv/include/bits/tcpip.h @@ -0,0 +1,15 @@ +#ifndef _BITS_TCPIP_H +#define _BITS_TCPIP_H + +/** @file + * + * Transport-network layer interface + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern uint16_t tcpip_continue_chksum ( uint16_t partial, const void *data, + size_t len ); + +#endif /* _BITS_TCPIP_H */ diff --git a/src/arch/riscv/include/bits/virt_offset.h b/src/arch/riscv/include/bits/virt_offset.h new file mode 100644 index 000000000..83ac17551 --- /dev/null +++ b/src/arch/riscv/include/bits/virt_offset.h @@ -0,0 +1,33 @@ +#ifndef _BITS_VIRT_OFFSET_H +#define _BITS_VIRT_OFFSET_H + +/** @file + * + * RISCV-specific virtual address offset + * + * We use the thread pointer register (tp) to hold the virtual address + * offset, so that virtual-to-physical address translations work as + * expected even while we are executing directly from read-only memory + * (and so cannot store a value in a global virt_offset variable). + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * Read virtual address offset held in thread pointer register + * + * @ret virt_offset Virtual address offset + */ +static inline __attribute__ (( const, always_inline )) unsigned long +tp_virt_offset ( void ) { + register unsigned long tp asm ( "tp" ); + + __asm__ ( "" : "=r" ( tp ) ); + return tp; +} + +/** Always read thread pointer register to get virtual address offset */ +#define virt_offset tp_virt_offset() + +#endif /* _BITS_VIRT_OFFSET_H */ diff --git a/src/arch/riscv/include/ipxe/csr.h b/src/arch/riscv/include/ipxe/csr.h new file mode 100644 index 000000000..c14974472 --- /dev/null +++ b/src/arch/riscv/include/ipxe/csr.h @@ -0,0 +1,75 @@ +#ifndef _IPXE_CSR_H +#define _IPXE_CSR_H + +/** @file + * + * Control and status registers (CSRs) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * Check if CSR can be read + * + * @v name CSR name + * @v allowed CSR can be read + */ +#define csr_can_read( name ) ( { \ + unsigned long stvec_orig; \ + unsigned long stvec_temp; \ + unsigned long csr; \ + int allowed = 0; \ + \ + __asm__ __volatile__ ( /* Set temporary trap vector */ \ + "la %3, 1f\n\t" \ + "csrrw %2, stvec, %3\n\t" \ + /* Try reading CSR */ \ + "csrr %1, " name "\n\t" \ + /* Mark as allowed if not trapped */ \ + "addi %0, %0, 1\n\t" \ + /* Temporary trap vector */ \ + ".balign 4\n\t" \ + "\n1:\n\t" \ + /* Restore original trap vector */ \ + "csrw stvec, %2\n\t" \ + : "+r" ( allowed ), \ + "=r" ( csr ), \ + "=r" ( stvec_orig ), \ + "=r" ( stvec_temp ) ); \ + allowed; \ + } ) + +/** + * Check if CSR can be written + * + * @v name CSR name + * @v value Value to write + * @v allowed CSR can be written + */ +#define csr_can_write( name, value ) ( { \ + unsigned long stvec_orig; \ + unsigned long stvec_temp; \ + unsigned long csr = (value); \ + int allowed = 0; \ + \ + __asm__ __volatile__ ( /* Set temporary trap vector */ \ + "la %3, 1f\n\t" \ + "csrrw %2, stvec, %3\n\t" \ + /* Try writing CSR */ \ + "csrrw %1, " name ", %1\n\t" \ + /* Mark as allowed if not trapped */ \ + "addi %0, %0, 1\n\t" \ + /* Temporary trap vector */ \ + ".balign 4\n\t" \ + "\n1:\n\t" \ + /* Restore original trap vector */ \ + "csrw stvec, %2\n\t" \ + : "+r" ( allowed ), \ + "+r" ( csr ), \ + "=r" ( stvec_orig ), \ + "=r" ( stvec_temp ) ); \ + allowed; \ + } ) + +#endif /* _IPXE_CSR_H */ diff --git a/src/arch/riscv/include/ipxe/errno/sbi.h b/src/arch/riscv/include/ipxe/errno/sbi.h new file mode 100644 index 000000000..2428183d4 --- /dev/null +++ b/src/arch/riscv/include/ipxe/errno/sbi.h @@ -0,0 +1,19 @@ +#ifndef _IPXE_ERRNO_SBI_H +#define _IPXE_ERRNO_SBI_H + +/** + * @file + * + * RISC-V SBI platform error codes + * + * We never need to return SBI error codes ourselves, so we + * arbitrarily choose to use the Linux error codes as platform error + * codes. + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/errno/linux.h> + +#endif /* _IPXE_ERRNO_SBI_H */ diff --git a/src/arch/riscv/include/ipxe/hart.h b/src/arch/riscv/include/ipxe/hart.h new file mode 100644 index 000000000..c201b6c77 --- /dev/null +++ b/src/arch/riscv/include/ipxe/hart.h @@ -0,0 +1,16 @@ +#ifndef _IPXE_HART_H +#define _IPXE_HART_H + +/** @file + * + * Hardware threads (harts) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern unsigned long boot_hart; + +extern int hart_supported ( const char *ext ); + +#endif /* _IPXE_HART_H */ diff --git a/src/arch/riscv/include/ipxe/riscv_dma.h b/src/arch/riscv/include/ipxe/riscv_dma.h new file mode 100644 index 000000000..d35904d88 --- /dev/null +++ b/src/arch/riscv/include/ipxe/riscv_dma.h @@ -0,0 +1,45 @@ +#ifndef _IPXE_RISCV_DMA_H +#define _IPXE_RISCV_DMA_H + +/** @file + * + * iPXE DMA API for RISC-V + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#ifdef DMAAPI_RISCV +#define DMAAPI_PREFIX_riscv +#else +#define DMAAPI_PREFIX_riscv __riscv_ +#endif + +/** + * Set addressable space mask + * + * @v dma DMA device + * @v mask Addressable space mask + */ +static inline __always_inline void +DMAAPI_INLINE ( riscv, dma_set_mask ) ( struct dma_device *dma __unused, + physaddr_t mask __unused ) { + + /* Nothing to do */ +} + +/** + * Get DMA address from virtual address + * + * @v map DMA mapping + * @v addr Address within the mapped region + * @ret addr Device-side DMA address + */ +static inline __always_inline physaddr_t +DMAAPI_INLINE ( riscv, dma ) ( struct dma_mapping *map __unused, void *addr ) { + + /* Use physical address as device address */ + return virt_to_phys ( addr ); +} + +#endif /* _IPXE_RISCV_DMA_H */ diff --git a/src/arch/riscv/include/ipxe/riscv_io.h b/src/arch/riscv/include/ipxe/riscv_io.h new file mode 100644 index 000000000..539dbd7ed --- /dev/null +++ b/src/arch/riscv/include/ipxe/riscv_io.h @@ -0,0 +1,141 @@ +#ifndef _IPXE_RISCV_IO_H +#define _IPXE_RISCV_IO_H + +/** @file + * + * iPXE I/O API for RISC-V + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#ifdef IOAPI_RISCV +#define IOAPI_PREFIX_riscv +#else +#define IOAPI_PREFIX_riscv __riscv_ +#endif + +#include <ipxe/dummy_pio.h> + +/* + * Memory space mappings + * + */ + +/* + * Physical<->Bus address mappings + * + */ + +static inline __always_inline unsigned long +IOAPI_INLINE ( riscv, phys_to_bus ) ( unsigned long phys_addr ) { + return phys_addr; +} + +static inline __always_inline unsigned long +IOAPI_INLINE ( riscv, bus_to_phys ) ( unsigned long bus_addr ) { + return bus_addr; +} + +/* + * MMIO reads and writes + * + */ + +/* Single-register read */ +#define RISCV_READX( _suffix, _type, _insn_suffix ) \ +static inline __always_inline _type \ +IOAPI_INLINE ( riscv, read ## _suffix ) ( volatile _type *io_addr ) { \ + unsigned long data; \ + __asm__ __volatile__ ( "fence io, io\n\t" \ + "l" _insn_suffix " %0, %1\n\t" \ + : "=r" ( data ) : "m" ( *io_addr ) ); \ + return data; \ +} + +/* Single-register write */ +#define RISCV_WRITEX( _suffix, _type, _insn_suffix) \ +static inline __always_inline void \ +IOAPI_INLINE ( riscv, write ## _suffix ) ( _type data, \ + volatile _type *io_addr ) { \ + __asm__ __volatile__ ( "fence io, io\n\t" \ + "s" _insn_suffix " %0, %1\n\t" \ + : : "r" ( data ), "m" ( *io_addr ) ); \ +} + +/* Double-register hopefully-fused read */ +#define RISCV_READX_FUSED( _suffix, _type, _insn_suffix ) \ +static inline __always_inline _type \ +IOAPI_INLINE ( riscv, read ## _suffix ) ( volatile _type *io_addr ) { \ + union { \ + unsigned long half[2]; \ + _type data; \ + } u; \ + __asm__ __volatile__ ( "fence io, io\n\t" \ + "l" _insn_suffix " %0, 0(%2)\n\t" \ + "l" _insn_suffix " %1, %3(%2)\n\t" \ + : "=&r" ( u.half[0] ), \ + "=&r" ( u.half[1] ) \ + : "r" ( io_addr ), \ + "i" ( sizeof ( u.half[0] ) ) ); \ + return u.data; \ +} + +/* Double-register hopefully-fused write */ +#define RISCV_WRITEX_FUSED( _suffix, _type, _insn_suffix ) \ +static inline __always_inline void \ +IOAPI_INLINE ( riscv, write ## _suffix ) ( _type data, \ + volatile _type *io_addr ) { \ + union { \ + unsigned long half[2]; \ + _type data; \ + } u = { .data = data }; \ + __asm__ __volatile__ ( "fence io, io\n\t" \ + "s" _insn_suffix " %0, 0(%2)\n\t" \ + "s" _insn_suffix " %1, %3(%2)\n\t" : \ + : "r" ( u.half[0] ), \ + "r" ( u.half[1] ), \ + "r" ( io_addr ), \ + "i" ( sizeof ( u.half[0] ) ) ); \ +} + +RISCV_READX ( b, uint8_t, "bu" ); +RISCV_WRITEX ( b, uint8_t, "b" ); + +RISCV_READX ( w, uint16_t, "hu" ); +RISCV_WRITEX ( w, uint16_t, "h" ); + +#if __riscv_xlen > 32 + RISCV_READX ( l, uint32_t, "wu" ); + RISCV_WRITEX ( l, uint32_t, "w" ); +#else + RISCV_READX ( l, uint32_t, "w" ); + RISCV_WRITEX ( l, uint32_t, "w" ); +#endif + +#if __riscv_xlen >= 64 + #if __riscv_xlen > 64 + RISCV_READX ( q, uint64_t, "du" ); + RISCV_WRITEX ( q, uint64_t, "d" ); + #else + RISCV_READX ( q, uint64_t, "d" ); + RISCV_WRITEX ( q, uint64_t, "d" ); + #endif +#else + RISCV_READX_FUSED ( q, uint64_t, "w" ); + RISCV_WRITEX_FUSED ( q, uint64_t, "w" ); +#endif + +/* + * Memory barrier + * + */ +static inline __always_inline void +IOAPI_INLINE ( riscv, mb ) ( void ) { + __asm__ __volatile__ ( "fence" : : : "memory" ); +} + +/* Dummy PIO */ +DUMMY_PIO ( riscv ); + +#endif /* _IPXE_RISCV_IO_H */ diff --git a/src/arch/riscv/include/ipxe/sbi.h b/src/arch/riscv/include/ipxe/sbi.h new file mode 100644 index 000000000..4364098b9 --- /dev/null +++ b/src/arch/riscv/include/ipxe/sbi.h @@ -0,0 +1,213 @@ +#ifndef _IPXE_SBI_H +#define _IPXE_SBI_H + +/** @file + * + * Supervisor Binary Interface (SBI) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +/** An SBI function return value */ +struct sbi_return { + /** Error status (returned in a0) */ + long error; + /** Data value (returned in a1) */ + long value; +}; + +/** + * @defgroup sbierrors SBI errors + * + * *{ + */ +#define SBI_SUCCESS 0 /**< Completed successfully */ +#define SBI_ERR_FAILED -1 /**< Failed */ +#define SBI_ERR_NOT_SUPPORTED -2 /**< Not supported */ +#define SBI_ERR_INVALID_PARAM -3 /**< Invalid parameter(s) */ +#define SBI_ERR_DENIED -4 /**< Denied or not allowed */ +#define SBI_ERR_INVALID_ADDRESS -5 /**< Invalid address(es) */ +#define SBI_ERR_ALREADY_AVAILABLE -6 /**< Already available */ +#define SBI_ERR_ALREADY_STARTED -7 /**< Already started */ +#define SBI_ERR_ALREADY_STOPPED -8 /**< Already stopped */ +#define SBI_ERR_NO_SHMEM -9 /**< Shared memory not available */ +#define SBI_ERR_INVALID_STATE -10 /**< Invalid state */ +#define SBI_ERR_BAD_RANGE -11 /**< Bad (or invalid) range */ +#define SBI_ERR_TIMEOUT -12 /**< Failed due to timeout */ +#define SBI_ERR_IO -13 /**< Input/output error */ +/** @} */ + +/** Construct SBI extension ID */ +#define SBI_EID( c1, c2, c3, c4 ) \ + ( (int) ( ( (c1) << 24 ) | ( (c2) << 16 ) | ( (c3) << 8 ) | (c4) ) ) + +/** + * Call supervisor with no parameters + * + * @v eid Extension ID + * @v fid Function ID + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) struct sbi_return +sbi_ecall_0 ( int eid, int fid ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) eid ); + register unsigned long a6 asm ( "a6" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ); + register unsigned long a1 asm ( "a1" ); + struct sbi_return ret; + + __asm__ __volatile__ ( "ecall" + : "=r" ( a0 ), "=r" ( a1 ) + : "r" ( a6 ), "r" ( a7 ) + : "memory" ); + ret.error = a0; + ret.value = a1; + return ret; +} + +/** + * Call supervisor with one parameter + * + * @v eid Extension ID + * @v fid Function ID + * @v p0 Parameter 0 + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) struct sbi_return +sbi_ecall_1 ( int eid, int fid, unsigned long p0 ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) eid ); + register unsigned long a6 asm ( "a6" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ) = p0; + register unsigned long a1 asm ( "a1" ); + struct sbi_return ret; + + __asm__ __volatile__ ( "ecall" + : "+r" ( a0 ), "=r" ( a1 ) + : "r" ( a6 ), "r" ( a7 ) + : "memory" ); + ret.error = a0; + ret.value = a1; + return ret; +} + +/** + * Call supervisor with two parameters + * + * @v eid Extension ID + * @v fid Function ID + * @v p0 Parameter 0 + * @v p1 Parameter 1 + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) struct sbi_return +sbi_ecall_2 ( int eid, int fid, unsigned long p0, unsigned long p1 ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) eid ); + register unsigned long a6 asm ( "a6" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ) = p0; + register unsigned long a1 asm ( "a1" ) = p1; + struct sbi_return ret; + + __asm__ __volatile__ ( "ecall" + : "+r" ( a0 ), "+r" ( a1 ) + : "r" ( a6 ), "r" ( a7 ) + : "memory" ); + ret.error = a0; + ret.value = a1; + return ret; +} + +/** + * Call supervisor with three parameters + * + * @v eid Extension ID + * @v fid Function ID + * @v p0 Parameter 0 + * @v p1 Parameter 1 + * @v p2 Parameter 2 + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) struct sbi_return +sbi_ecall_3 ( int eid, int fid, unsigned long p0, unsigned long p1, + unsigned long p2 ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) eid ); + register unsigned long a6 asm ( "a6" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ) = p0; + register unsigned long a1 asm ( "a1" ) = p1; + register unsigned long a2 asm ( "a2" ) = p2; + struct sbi_return ret; + + __asm__ __volatile__ ( "ecall" + : "+r" ( a0 ), "+r" ( a1 ) + : "r" ( a2 ), "r" ( a6 ), "r" ( a7 ) + : "memory" ); + ret.error = a0; + ret.value = a1; + return ret; +} + +/** + * Call supervisor with no parameters + * + * @v fid Legacy function ID + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) long +sbi_legacy_ecall_0 ( int fid ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ); + + __asm__ __volatile__ ( "ecall" + : "=r" ( a0 ) + : "r" ( a7 ) + : "memory" ); + return a0; +} + +/** + * Call supervisor with one parameter + * + * @v fid Legacy function ID + * @v p0 Parameter 0 + * @ret ret Return value + */ +static inline __attribute__ (( always_inline )) long +sbi_legacy_ecall_1 ( int fid, unsigned long p0 ) { + register unsigned long a7 asm ( "a7" ) = ( ( long ) fid ); + register unsigned long a0 asm ( "a0" ) = p0; + + __asm__ __volatile__ ( "ecall" + : "+r" ( a0 ) + : "r" ( a7 ) + : "memory" ); + return a0; +} + +/** Convert an SBI error code to an iPXE status code */ +#define ESBI( error ) EPLATFORM ( EINFO_EPLATFORM, error ) + +/** Legacy extensions */ +#define SBI_LEGACY_PUTCHAR 0x01 /**< Console Put Character */ +#define SBI_LEGACY_GETCHAR 0x02 /**< Console Get Character */ +#define SBI_LEGACY_SHUTDOWN 0x08 /**< System Shutdown */ + +/** Base extension */ +#define SBI_BASE 0x10 +#define SBI_BASE_MVENDORID 0x04 /**< Get machine vendor ID */ + +/** System reset extension */ +#define SBI_SRST SBI_EID ( 'S', 'R', 'S', 'T' ) +#define SBI_SRST_SYSTEM_RESET 0x00 /**< Reset system */ +#define SBI_RESET_SHUTDOWN 0x00000000 /**< Shutdown */ +#define SBI_RESET_COLD 0x00000001 /**< Cold reboot */ +#define SBI_RESET_WARM 0x00000002 /**< Warm reboot */ + +/** Debug console extension */ +#define SBI_DBCN SBI_EID ( 'D', 'B', 'C', 'N' ) +#define SBI_DBCN_WRITE 0x00 /**< Console Write */ +#define SBI_DBCN_READ 0x01 /**< Console Read */ +#define SBI_DBCN_WRITE_BYTE 0x02 /**< Console Write Byte */ + +#endif /* _IPXE_SBI_H */ diff --git a/src/arch/riscv/include/ipxe/sbi_reboot.h b/src/arch/riscv/include/ipxe/sbi_reboot.h new file mode 100644 index 000000000..e8d6e82bf --- /dev/null +++ b/src/arch/riscv/include/ipxe/sbi_reboot.h @@ -0,0 +1,18 @@ +#ifndef _IPXE_BIOS_REBOOT_H +#define _IPXE_BIOS_REBOOT_H + +/** @file + * + * Supervisor Binary Interface (SBI) reboot mechanism + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#ifdef REBOOT_SBI +#define REBOOT_PREFIX_sbi +#else +#define REBOOT_PREFIX_sbi __sbi_ +#endif + +#endif /* _IPXE_BIOS_REBOOT_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..897a3379a --- /dev/null +++ b/src/arch/riscv/include/ipxe/svpage.h @@ -0,0 +1,28 @@ +#ifndef _IPXE_SVPAGE_H +#define _IPXE_SVPAGE_H + +/** @file + * + * Supervisor page table management + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +#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 ); +} + +extern void * svpage_dma32 ( void ); + +#endif /* _IPXE_SVPAGE_H */ diff --git a/src/arch/riscv/include/ipxe/xthead.h b/src/arch/riscv/include/ipxe/xthead.h new file mode 100644 index 000000000..d0c9449ef --- /dev/null +++ b/src/arch/riscv/include/ipxe/xthead.h @@ -0,0 +1,21 @@ +#ifndef _IPXE_XTHEAD_H +#define _IPXE_XTHEAD_H + +/** @file + * + * T-Head vendor extensions + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** T-Head machine vendor ID */ +#define THEAD_MVENDORID 0x5b7 + +/** T-Head SXSTATUS CSR */ +#define THEAD_SXSTATUS 0x5c0 +#define THEAD_SXSTATUS_THEADISAEE 0x00400000 /**< General ISA extensions */ + +extern int xthead_supported ( unsigned long feature ); + +#endif /* _IPXE_XTHEAD_H */ diff --git a/src/arch/riscv/include/ipxe/zicbom.h b/src/arch/riscv/include/ipxe/zicbom.h new file mode 100644 index 000000000..4ba165f3c --- /dev/null +++ b/src/arch/riscv/include/ipxe/zicbom.h @@ -0,0 +1,17 @@ +#ifndef _IPXE_ZICBOM_H +#define _IPXE_ZICBOM_H + +/** @file + * + * Cache-block management operations (Zicbom) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +extern void cache_clean ( const void *start, size_t len ); +extern void cache_invalidate ( void *start, size_t len ); + +#endif /* _IPXE_ZICBOM_H */ diff --git a/src/arch/riscv/interface/sbi/sbi_console.c b/src/arch/riscv/interface/sbi/sbi_console.c new file mode 100644 index 000000000..1c3784ec8 --- /dev/null +++ b/src/arch/riscv/interface/sbi/sbi_console.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 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 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 + * + * SBI debug console + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/sbi.h> +#include <ipxe/io.h> +#include <ipxe/keys.h> +#include <ipxe/serial.h> +#include <ipxe/console.h> +#include <config/console.h> + +/* Set default console usage if applicable */ +#if ! ( defined ( CONSOLE_SBI ) && CONSOLE_EXPLICIT ( CONSOLE_SBI ) ) +#undef CONSOLE_SBI +#define CONSOLE_SBI ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) +#endif + +extern void early_uart_putchar ( int character ); + +/** Dummy serial console (if not present in build) */ +struct uart *serial_console __attribute__ (( weak )); + +/** Buffered input character (if any) */ +static unsigned char sbi_console_input; + +/** + * Print a character to SBI console + * + * @v character Character to be printed + */ +static void sbi_putchar ( int character ) { + struct sbi_return ret; + + /* Do nothing if a real serial console has been enabled */ + if ( serial_console ) + return; + + /* Write byte to early UART, if enabled */ + early_uart_putchar ( character ); + + /* Write byte to console */ + ret = sbi_ecall_1 ( SBI_DBCN, SBI_DBCN_WRITE_BYTE, character ); + if ( ! ret.error ) + return; + + /* Debug extension not supported: try legacy method */ + sbi_legacy_ecall_1 ( SBI_LEGACY_PUTCHAR, character ); +} + +/** + * Get character from SBI console + * + * @ret character Character read from console, if any + */ +static int sbi_getchar ( void ) { + int character; + + /* Consume and return buffered character, if any */ + character = sbi_console_input; + sbi_console_input = 0; + + /* Convert DEL to backspace */ + if ( character == DEL ) + character = BACKSPACE; + + return character; +} + +/** + * Check for character ready to read from SBI console + * + * @ret True Character available to read + * @ret False No character available to read + */ +static int sbi_iskey ( void ) { + struct sbi_return ret; + long key; + + /* Do nothing if we already have a buffered character */ + if ( sbi_console_input ) + return sbi_console_input; + + /* Do nothing if a real serial console has been enabled */ + if ( serial_console ) + return 0; + + /* Read and buffer byte from console, if any */ + ret = sbi_ecall_3 ( SBI_DBCN, SBI_DBCN_READ, + sizeof ( sbi_console_input ), + virt_to_phys ( &sbi_console_input ), 0 ); + if ( ! ret.error ) + return ret.value; + + /* Debug extension not supported: try legacy method */ + key = sbi_legacy_ecall_0 ( SBI_LEGACY_GETCHAR ); + if ( key > 0 ) { + sbi_console_input = key; + return key; + } + + /* No character available */ + return 0; +} + +/** SBI console */ +struct console_driver sbi_console_driver __console_driver = { + .putchar = sbi_putchar, + .getchar = sbi_getchar, + .iskey = sbi_iskey, + .usage = CONSOLE_SBI, +}; diff --git a/src/arch/riscv/interface/sbi/sbi_reboot.c b/src/arch/riscv/interface/sbi/sbi_reboot.c new file mode 100644 index 000000000..b1c742ec7 --- /dev/null +++ b/src/arch/riscv/interface/sbi/sbi_reboot.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 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 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 ); + +/** @file + * + * Supervisor Binary Interface (SBI) reboot mechanism + * + */ + +#include <errno.h> +#include <string.h> +#include <ipxe/sbi.h> +#include <ipxe/reboot.h> + +/** + * Reboot system + * + * @v flags Reboot flags + */ +static void sbi_reboot ( int flags ) { + struct sbi_return ret; + int warm; + int rc; + + /* Reboot system */ + warm = ( flags & REBOOT_WARM ); + ret = sbi_ecall_2 ( SBI_SRST, SBI_SRST_SYSTEM_RESET, + ( warm ? SBI_RESET_WARM : SBI_RESET_COLD ), 0 ); + + /* Any return is an error */ + rc = -ESBI ( ret.error ); + DBGC ( SBI_SRST, "SBI %s reset failed: %s\n", + ( warm ? "warm" : "cold" ), strerror ( rc ) ); + + /* Try a legacy shutdown */ + sbi_legacy_ecall_0 ( SBI_LEGACY_SHUTDOWN ); + DBGC ( SBI_SRST, "SBI legacy shutdown failed\n" ); +} + +/** + * Power off system + * + * @ret rc Return status code + */ +static int sbi_poweroff ( void ) { + struct sbi_return ret; + int rc; + + /* Shut down system */ + ret = sbi_ecall_2 ( SBI_SRST, SBI_SRST_SYSTEM_RESET, + SBI_RESET_SHUTDOWN, 0 ); + + /* Any return is an error */ + rc = -ESBI ( ret.error ); + DBGC ( SBI_SRST, "SBI shutdown failed: %s\n", strerror ( rc ) ); + + /* Try a legacy shutdown */ + sbi_legacy_ecall_0 ( SBI_LEGACY_SHUTDOWN ); + DBGC ( SBI_SRST, "SBI legacy shutdown failed\n" ); + + return rc; +} + +PROVIDE_REBOOT ( sbi, reboot, sbi_reboot ); +PROVIDE_REBOOT ( sbi, poweroff, sbi_poweroff ); diff --git a/src/arch/riscv/prefix/libprefix.S b/src/arch/riscv/prefix/libprefix.S new file mode 100644 index 000000000..338131103 --- /dev/null +++ b/src/arch/riscv/prefix/libprefix.S @@ -0,0 +1,1529 @@ +/* + * 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 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 ) + +/** @file + * + * RISC-V prefix library + * + */ + +#include <config/serial.h> +#include <config/fault.h> + + .section ".note.GNU-stack", "", @progbits + .text + + /* Link-time base address of _prefix + * + * This will be not be updated if runtime relocations are applied. + */ + .section ".rodata.prefix_link", "a", @progbits + .balign ( __riscv_xlen / 8 ) +prefix_link: + .dword _base + .size prefix_link, . - prefix_link + + /* Virtual address of _prefix + * + * This will be updated if runtime relocations are applied. + */ + .section ".rodata.prefix_virt", "a", @progbits + .balign ( __riscv_xlen / 8 ) +prefix_virt: + .dword _prefix + .size prefix_virt, . - prefix_virt + +/***************************************************************************** + * + * Print character via debug console extension + * + ***************************************************************************** + * + * Print a single character via the SBI DBCN extension. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: + * + * a0 - Zero if character printed successfully + * a1 - Overwritten + * a6 - Overwritten + * a7 - Overwritten + * + */ + +/* SBI debug console extension */ +#define SBI_DBCN ( ( 'D' << 24 ) | ( 'B' << 16 ) | ( 'C' << 8 ) | 'N' ) +#define SBI_DBCN_WRITE_BYTE 0x02 + + .macro print_char_dbcn + li a7, SBI_DBCN + li a6, SBI_DBCN_WRITE_BYTE + ecall + .endm + +/***************************************************************************** + * + * Print character via legacy extension + * + ***************************************************************************** + * + * Print a single character via the SBI putchar legacy extension. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: + * + * a0 - Overwritten + * a7 - Overwritten + * + */ + +/* SBI legacy console putchar */ +#define SBI_LEGACY_PUTCHAR 0x01 + + .macro print_char_legacy + li a7, SBI_LEGACY_PUTCHAR + ecall + .endm + +/***************************************************************************** + * + * Print character via early UART + * + ***************************************************************************** + * + * Print a single character via a UART. + * + * For devices without a functional SBI console, a UART at a hardcoded + * address can be used as a last resort mechanism for obtaining debug + * output from the prefix. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: + * + * a0 - Preserved + * a1 - May be overwritten + * a6 - May be overwritten + * a7 - May be overwritten + * + */ + +/* Default to no UART, if not specified */ +#ifndef EARLY_UART_MODEL +#define EARLY_UART_MODEL none +#endif + +/* Default to a register shift of zero, if not specified */ +#ifndef EARLY_UART_REG_SHIFT +#define EARLY_UART_REG_SHIFT 0 +#endif + +#define print_char_uart _C2 ( print_char_uart_, EARLY_UART_MODEL ) + +#define early_uart_reg_base _C2 ( early_uart_reg_base_, __riscv_xlen ) + + /* Print character via nonexistent UART */ + .macro print_char_uart_none + .endm + + /* + * Get UART base address (64-bit addressing) + */ + .macro early_uart_reg_base_64 reg + csrr \reg, satp + beqz \reg, early_uart_reg_base_64_nonpaged_\@ + LOADN \reg, early_uart_reg_base_64_virt + j early_uart_reg_base_64_done_\@ +early_uart_reg_base_64_nonpaged_\@: + li \reg, EARLY_UART_REG_BASE +early_uart_reg_base_64_done_\@: + .endm + + /* + * Get UART base address (32-bit addressing) + */ + .macro early_uart_reg_base_32 reg + li \reg, EARLY_UART_REG_BASE + sub \reg, \reg, tp + .endm + +/***************************************************************************** + * + * Print character via 8250-compatible early UART + * + ***************************************************************************** + * + * Print a single character via an 8250- or 16550-compatible UART. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: + * + * a0 - Preserved + * a1 - Overwritten + * a7 - Overwritten + * + */ + +/* 8250-compatible UART transmit registers */ +#define EARLY_UART_8250_TX ( 0 << EARLY_UART_REG_SHIFT ) +#define EARLY_UART_8250_LSR ( 5 << EARLY_UART_REG_SHIFT ) +#define EARLY_UART_8250_LSR_THRE 0x20 + + .macro print_char_uart_8250 + early_uart_reg_base a7 + sb a0, EARLY_UART_8250_TX(a7) + fence +early_uart_8250_wait_\@: + lbu a1, EARLY_UART_8250_LSR(a7) + andi a1, a1, EARLY_UART_8250_LSR_THRE + beqz a1, early_uart_8250_wait_\@ + .endm + +/***************************************************************************** + * + * Print character via SiFive-compatible early UART + * + ***************************************************************************** + * + * Print a single character via a SiFive-compatible UART. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: + * + * a0 - Preserved + * a1 - Overwritten + * a7 - Overwritten + * + */ + +/* SiFive-compatible UART transmit registers */ +#define EARLY_UART_SIFIVE_TXFIFO ( 0 << EARLY_UART_REG_SHIFT ) + + .macro print_char_uart_sifive + early_uart_reg_base a7 + sw a0, EARLY_UART_SIFIVE_TXFIFO(a7) + fence +early_uart_sifive_wait_\@: + lw a1, EARLY_UART_SIFIVE_TXFIFO(a7) + bltz a1, early_uart_sifive_wait_\@ + .endm + +/***************************************************************************** + * + * Print single character to early UART (from C code) + * + ***************************************************************************** + * + * This function is called by the SBI console driver to output a + * character to the early UART (if enabled). + * + * The standard C ABI applies to this function. + * + * Parameters: + * + * a0 - Character to print + * + * Returns: none + * + */ + + .section ".prefix.early_uart_putchar", "ax", @progbits + .globl early_uart_putchar +early_uart_putchar: + print_char_uart + ret + .size early_uart_putchar, . - early_uart_putchar + +/***************************************************************************** + * + * Print message to debug console + * + ***************************************************************************** + * + * Print a NUL-terminated string to the debug console. + * + * This function prints one character at a time via the "write byte" + * call (rather than using "write string"), since this avoids any need + * to know the current virtual-physical address translation. It does + * not require a valid stack. + * + * Note that the parameter is passed in register t1 (rather than a0) + * and all non-temporary registers are preserved. + * + * Parameters: + * + * t1 - Pointer to string + * + * Returns: none + * + */ + + .section ".prefix.print_message", "ax", @progbits + .globl print_message +print_message: + /* Handle alternate link register */ + mv t0, ra +print_message_alt: + /* Register usage: + * + * a0 - current character + * t0 - alternate link register + * t1 - character pointer + * t2 - preserved a0 + * t3 - preserved a1 + * t4 - preserved a6 + * t5 - preserved a7 + */ + mv t2, a0 + mv t3, a1 + mv t4, a6 + mv t5, a7 + +1: /* Print each character in turn */ + lbu a0, (t1) + addi t1, t1, 1 + beqz a0, 2f + print_char_uart + print_char_dbcn + beqz a0, 1b + lbu a0, -1(t1) + print_char_legacy + j 1b +2: + /* Restore registers and return (via alternate link register) */ + mv a7, t5 + mv a6, t4 + mv a1, t3 + mv a0, t2 + jr t0 + .size print_message, . - print_message + + /* + * Display progress message (if debugging is enabled) + */ + .macro progress message +#ifndef NDEBUG + .section ".rodata.progress_\@", "a", @progbits +progress_\@: + .asciz "\message" + .size progress_\@, . - progress_\@ + .previous + la t1, progress_\@ + jal t0, print_message_alt +#endif + .endm + +/***************************************************************************** + * + * Print hexadecimal value to debug console + * + ***************************************************************************** + * + * Print a register value in hexadecimal to the debug console. + * + * This function does not require a valid stack. + * + * Note that the parameters are passed in registers t1 and t2 (rather + * than a0) and all non-temporary registers are preserved. + * + * Parameters: + * + * t1 - Value to print + * t2 - Number of bits to print (must be a multiple of 4) + * + * Returns: none + * + */ + + /* + * Convert a single nibble to an ASCII character + */ + .macro nibble_to_ascii reg + addi \reg, \reg, -10 + bltz \reg, dec_\@ + addi \reg, \reg, ( 'a' - ( '0' + 10 ) ) +dec_\@: addi \reg, \reg, ( '0' + 10 ) + .endm + + .section ".prefix.print_hex_value", "ax", @progbits + .globl print_hex_value +print_hex_value: + /* Handle alternate link register */ + mv t0, ra +print_hex_value_alt: + /* Register usage: + * + * a0 - current digit / general temporary + * t0 - alternate link register + * t1 - current value + * t2 - digit counter + * t3 - preserved a0 + * t4 - preserved a1 + * t5 - preserved a6 + * t6 - preserved a7 + */ + mv t3, a0 + mv t4, a1 + mv t5, a6 + mv t6, a7 + + /* Skip any unprinted digits */ + li a0, __riscv_xlen + sub a0, a0, t2 + sll t1, t1, a0 + +1: /* Print each digit in turn */ + srli a0, t1, ( __riscv_xlen - 4 ) + nibble_to_ascii a0 + print_char_uart + print_char_dbcn + beqz a0, 2f + srli a0, t1, ( __riscv_xlen - 4 ) + nibble_to_ascii a0 + print_char_legacy +2: slli t1, t1, 4 + addi t2, t2, -4 + bgtz t2, 1b + + /* Restore registers and return (via alternate link register) */ + mv a7, t6 + mv a6, t5 + mv a1, t4 + mv a0, t3 + jr t0 + .size print_hex_value, . - print_hex_value + + /* + * Display hexadecimal register value (if debugging is enabled) + */ + .macro print_hex_reg reg, bits=__riscv_xlen +#ifndef NDEBUG + mv t1, \reg + li t2, \bits + jal t0, print_hex_value_alt +#endif + .endm + + /* + * Display hexadecimal symbol address (if debugging is enabled) + */ + .macro print_hex_addr sym +#ifndef NDEBUG + la t1, \sym + li t2, __riscv_xlen + jal t0, print_hex_value_alt +#endif + .endm + + /* + * Display hexadecimal data value (if debugging is enabled) + */ + .macro print_hex_data sym +#ifndef NDEBUG + LOADN t1, \sym + li t2, __riscv_xlen + jal t0, print_hex_value_alt +#endif + .endm + +/***************************************************************************** + * + * Apply compressed relocation records + * + ***************************************************************************** + * + * Apply compressed relocation records to fix up iPXE to run at its + * current virtual address. + * + * This function must run before .bss is zeroed (since the relocation + * records are overlaid with .bss). It does not require a valid stack + * pointer. + * + * Parameters: none + * + * a0 - Relocation records + * + * Returns: none + * + */ + +/** Number of bits in a skip value */ +#define ZREL_SKIP_BITS 19 + + .section ".prefix.apply_relocs", "ax", @progbits + .globl apply_relocs +apply_relocs: + /* Register usage: + * + * a0 - current relocation record pointer + * a1 - current relocation target address + * a2 - relocation addend + * a3 - current relocation record value + * a4 - number of bits remaining in current relocation record + */ + la a1, _prefix + + /* Calculate relocation addend */ + LOADN a2, prefix_virt + sub a2, a1, a2 + + /* Skip applying relocations if addend is zero */ + beqz a2, apply_relocs_done + progress " reloc" + + /* Test writability + * + * We do this to avoid accidentally sending an undefined + * sequence of commands to a flash device, if we are started + * from read-only memory with no paging support. + * + * We attempt to write an all-ones pattern, on the basis that + * this pattern will harmlessly cause any flash device + * conforming to the CFI01 specification to enter the default + * "read array" state. + */ + la t0, apply_relocs_test + li t1, -1 + STOREN t1, (t0) + LOADN t2, (t0) + bne t1, t2, apply_relocs_failed + +apply_relocs_loop: + /* Read new relocation record */ + LOADN a3, (a0) + addi a0, a0, ( __riscv_xlen / 8 ) + li a4, ( __riscv_xlen - 1 ) + + /* Consume and apply skip, if present (i.e. if MSB=0) */ + bltz a3, 1f + addi a4, a4, -ZREL_SKIP_BITS + srli t0, a3, ( __riscv_xlen - ( ZREL_SKIP_BITS + 1 ) ) + slli t0, t0, ( ( __riscv_xlen / 32 ) + 1 ) + add a1, a1, t0 +1: + /* Apply relocations corresponding to set bits in record */ +1: andi t0, a3, 1 + beqz t0, 2f + LOADN t1, (a1) + add t1, t1, a2 + STOREN t1, (a1) +2: addi a1, a1, ( __riscv_xlen / 8 ) + srli a3, a3, 1 + addi a4, a4, -1 + bnez a4, 1b + + /* Loop until we have reached a terminator record (MSB=0, offset=0) */ + bnez a3, apply_relocs_loop + + /* Check that relocations were applied successfully */ + la t0, _prefix + LOADN t1, prefix_virt + bne t0, t1, apply_relocs_failed + +apply_relocs_done: + /* Return to caller */ + progress " ok\r\n" + ret + +apply_relocs_failed: + /* Failure to apply relocations (if relocations were needed) + * is a fatal error. + */ + progress " failed\r\n" + j reset_system + .size apply_relocs, . - apply_relocs + + /* Writability test + * + * Placed within .data rather than .bss, since we need this to + * be within the range of the stored iPXE image. + */ + .section ".data.apply_relocs_test", "aw", @progbits + .balign ( __riscv_xlen / 8 ) +apply_relocs_test: + .space ( __riscv_xlen / 8 ) + .size apply_relocs_test, . - apply_relocs_test + +/***************************************************************************** + * + * Enable paging + * + ***************************************************************************** + * + * This function must be called with flat physical addressing. It + * does not require a valid stack pointer. + * + * Parameters: + * + * a0 - Page table to fill in (4kB, must be aligned to a 4kB boundary) + * + * Returns: + * + * a0 - Size of accessible physical address space (or zero for no limit) + * tp - Virtual address offset + * pc - Updated to a virtual address if paging enabled + * + */ + +/** Number of bits in a page offset */ +#define PAGE_SHIFT 12 + +/** Page size */ +#define PAGE_SIZE ( 1 << PAGE_SHIFT ) + +/** Size of a page table entry (log2) */ +#define PTE_SIZE_LOG2 ( ( __riscv_xlen / 32 ) + 1 ) + +/** Size of a page table entry */ +#define PTE_SIZE ( 1 << PTE_SIZE_LOG2 ) + +/** Number of page table entries (log2) */ +#define PTE_COUNT_LOG2 ( PAGE_SHIFT - PTE_SIZE_LOG2 ) + +/** Number of page table entries */ +#define PTE_COUNT ( 1 << PTE_COUNT_LOG2 ) + +/** Number of bits in a virtual or physical page number */ +#define VPPN_SHIFT PTE_COUNT_LOG2 + +/* Page table entry flags */ +#define PTE_V 0x00000001 /**< Page table entry is valid */ +#define PTE_R 0x00000002 /**< Page is readable */ +#define PTE_W 0x00000004 /**< Page is writable */ +#define PTE_X 0x00000008 /**< Page is executable */ +#define PTE_A 0x00000040 /**< Page has been accessed */ +#define PTE_D 0x00000080 /**< Page is dirty */ + +/* Page table entry flags for our leaf pages */ +#define PTE_LEAF ( PTE_D | PTE_A | PTE_X | PTE_W | PTE_R | PTE_V ) + +/** Physical page number LSB in PTE */ +#define PTE_PPN_LSB(x) ( 10 + (x) * VPPN_SHIFT ) +#define PTE_PPN4_LSB PTE_PPN_LSB(4) /**< PPN[4] LSB (Sv57) */ +#define PTE_PPN3_LSB PTE_PPN_LSB(3) /**< PPN[3] LSB (Sv57 & Sv48) */ +#define PTE_PPN2_LSB PTE_PPN_LSB(2) /**< PPN[2] LSB (Sv57, Sv48, & Sv39) */ +#define PTE_PPN1_LSB PTE_PPN_LSB(1) /**< PPN[1] LSB (all levels) */ +#define PTE_PPN0_LSB PTE_PPN_LSB(0) /**< PPN[0] LSB (all levels) */ + +/** Page table entry physical page address shift */ +#define PTE_PPN_SHIFT ( PAGE_SHIFT - PTE_PPN0_LSB ) + +/** Virtual page number LSB */ +#define VPN_LSB(x) ( PAGE_SHIFT + (x) * VPPN_SHIFT ) +#define VPN4_LSB VPN_LSB(4) /**< VPN[4] LSB (Sv57) */ +#define VPN3_LSB VPN_LSB(3) /**< VPN[3] LSB (Sv57 & Sv48) */ +#define VPN2_LSB VPN_LSB(2) /**< VPN[2] LSB (Sv57, Sv48, & Sv39) */ +#define VPN1_LSB VPN_LSB(1) /**< VPN[1] LSB (all levels) */ +#define VPN0_LSB VPN_LSB(0) /**< VPN[0] LSB (all levels) */ + +/* Paging modes */ +#define SATP_MODE_SV57 10 /**< Five-level paging (Sv57) */ +#define SATP_MODE_SV48 9 /**< Four-level paging (Sv48) */ +#define SATP_MODE_SV39 8 /**< Three-level paging (Sv39) */ +#define SATP_MODE_SV32 1 /**< Two-level paging (Sv32) */ + +/** Paging mode shift */ +#if __riscv_xlen == 64 +#define SATP_MODE_SHIFT 60 +#else +#define SATP_MODE_SHIFT 31 +#endif + + .globl enable_paging + .equ enable_paging, _C2 ( enable_paging_, __riscv_xlen ) + + /* Paging mode names (for debug messages) */ + .section ".rodata.paging_mode_names", "a", @progbits +paging_mode_names: + .asciz "none" + .org ( paging_mode_names + 5 * SATP_MODE_SV32 ) + .asciz "Sv32" + .org ( paging_mode_names + 5 * SATP_MODE_SV39 ) + .asciz "Sv39" + .org ( paging_mode_names + 5 * SATP_MODE_SV48 ) + .asciz "Sv48" + .org ( paging_mode_names + 5 * SATP_MODE_SV57 ) + .asciz "Sv57" + .size paging_mode_names, . - paging_mode_names + + /* + * Display paging mode name (if debugging is enabled) + */ + .macro paging_mode_name reg +#ifndef NDEBUG + slli t0, \reg, 2 + add t0, t0, \reg + la t1, paging_mode_names + add t1, t1, t0 + jal t0, print_message_alt +#endif + .endm + + /* Maximum physical alignment + * + * We align to a "megapage" boundary to simplify the task of + * setting up page table mappings. + */ + .globl _max_align + .equ _max_align, ( 1 << VPN1_LSB ) + + /* Space for page table + * + * This can be used only once .bss is known to be writable. + */ + .section ".bss.page_table", "a", @nobits + .globl page_table + .balign PAGE_SIZE +page_table: + .space PAGE_SIZE + .size page_table, . - page_table + + /* Convert physical address to virtual address */ + .macro phys_to_virt rd, rs:vararg + _C2 ( phys_to_virt_, __riscv_xlen ) \rd, \rs + .endm + +/***************************************************************************** + * + * Disable paging + * + ***************************************************************************** + * + * This function may be called with either virtual or flat physical + * addressing. It does not require a valid stack pointer. + * + * Parameters: + * + * tp - Virtual address offset + * + * Returns: + * + * tp - Virtual address offset (zeroed) + * pc - Updated to a physical address + * + */ + + .globl disable_paging + .equ disable_paging, _C2 ( disable_paging_, __riscv_xlen ) + +/***************************************************************************** + * + * Enable 64-bit paging + * + ***************************************************************************** + * + * Construct a 64-bit page table to identity-map the whole of the + * mappable physical address space, and to map iPXE itself at its + * link-time address (which must be 2MB-aligned and be within the + * upper half of the kernel address space). + * + * This function must be called with flat physical addressing. It + * does not require a valid stack pointer. + * + * Parameters: + * + * a0 - Page table to fill in (4kB, must be aligned to a 4kB boundary) + * + * Returns: + * + * a0 - Size of accessible physical address space (or zero for no limit) + * tp - Virtual address offset + * pc - Updated to a virtual address if paging enabled + * + * A 4kB 64-bit page table contains 512 8-byte PTEs. We choose to use + * these as: + * + * - PTE[0-255] : Identity map for the physical address space. + * + * This conveniently requires exactly 256 PTEs, regardless of the + * paging level. Higher paging levels are able to identity-map a + * larger physical address space: + * + * Sv57 : 256 x 256TB "petapages" (55-bit physical address space) + * Sv48 : 256 x 512GB "terapages" (46-bit physical address space) + * Sv39 : 256 x 1GB "gigapages" (37-bit physical address space) + * + * Note that Sv48 and Sv39 cannot identity-map the whole of the + * available physical address space, since the virtual address + * space is not large enough (and is halved by the constraint + * that virtual addresses with bit 47/38 set must also have all + * higher bits set, and so cannot identity-map to a 55-bit + * physical address). + * + * - PTE[x-y] : Virtual address map for iPXE + * + * These are 2MB "megapages" used to map the link-time virtual + * address range used by iPXE itself. We can use any 2MB-aligned + * range within 0xffffffffe0800000-0xffffffffffc00000, which + * breaks down as: + * + * VPN[4] = 511 (in Sv57, must be all-ones in Sv48 and Sv39) + * VPN[3] = 511 (in Sv57 and Sv48, must be all-ones in Sv39) + * VPN[2] = 511 (in all paging levels) + * VPN[1] = 260-510 (in all paging levels) + * VPN[0] = 0 (in all paging levels) + * + * In most builds, only a single 2MB "megapage" will be needed. + * We choose a link-time starting address of 0xffffffffeb000000 + * within the permitted range, since the "eb" pattern is fairly + * distinctive and so makes it easy to visually identify any + * addresses originating from within iPXE's virtual address + * space. + * + * - PTE[511] : Recursive next level page table pointer + * + * This is a non-leaf PTE that points back to the page table + * itself. It acts as the next level page table pointer for: + * + * VPN[4] = 511 (in Sv57) + * VPN[3] = 511 (in Sv57 and Sv48) + * VPN[2] = 511 (in Sv57, Sv48, and Sv39) + * + * This recursive usage creates some duplicate mappings within + * unused portions of the virtual address space, but allows us to + * use only a single physical 4kB page table. + */ + +/** SBI base extension */ +#define SBI_BASE 0x10 +#define SBI_BASE_MVENDORID 0x04 + +/** Non-standard T-Head page table entry additional flags + * + * T-Head processors such as the C910 use the high bits of the PTE in + * a very non-standard way that is incompatible with the RISC-V + * specification. + * + * As per the "Memory Attribute Extension (XTheadMae)", bits 62 and 61 + * represent cacheability and "bufferability" (i.e. write-back + * cacheability) respectively. If we do not enable these bits, then + * the processor gets incredibly confused at the point that paging is + * enabled. The symptom is that cache lines will occasionally fail to + * fill, and so reads from any address may return unrelated data from + * a previously read cache line for a different address. + */ +#define THEAD_PTE_MAEE ( 0x60 << ( __riscv_xlen - 8 ) ) + +/** T-Head vendor ID */ +#define THEAD_MVENDORID 0x5b7 + +/** T-Head "sxstatus" CSR */ +#define THEAD_CSR_SXSTATUS 0x5c0 +#define THEAD_CSR_SXSTATUS_MAEE 0x00200000 /**< XTheadMae enabled */ + + .section ".prefix.enable_paging_64", "ax", @progbits +enable_paging_64: + /* Register usage: + * + * tp - return value (virtual address offset) + * a0 - page table base address + * a1 - currently attempted paging level + * a2 - enabled paging level + * a3 - PTE pointer + * a4 - PTE stride + * a5 - size of accessible physical address space + */ + progress " paging:" + + /* Calculate virtual address offset */ + LOADN t0, prefix_link + la t1, _prefix + sub tp, t1, t0 + + /* Zero PTE[0-511] */ + li t0, PTE_COUNT + mv a3, a0 +1: STOREN zero, (a3) + addi a3, a3, PTE_SIZE + addi t0, t0, -1 + bgtz t0, 1b + + /* Construct PTE[511] as next level page table pointer */ + srli t0, a0, PTE_PPN_SHIFT + ori t0, t0, PTE_V + STOREN t0, -PTE_SIZE(a3) + + /* Construct base page table entry for address zero */ + li t0, PTE_LEAF + STOREN t0, (a0) + + /* Check for broken T-Head paging extensions */ + mv a3, a0 + li a7, SBI_BASE + li a6, SBI_BASE_MVENDORID + ecall + bnez a0, 1f + li t0, THEAD_MVENDORID + bne a1, t0, 1f + progress "thead-" + csrr t0, THEAD_CSR_SXSTATUS + li t1, THEAD_CSR_SXSTATUS_MAEE + and t0, t0, t1 + beqz t0, 1f + progress "mae-" + LOADN t0, (a3) + li t1, THEAD_PTE_MAEE + or t0, t0, t1 + STOREN t0, (a3) +1: mv a0, a3 + + /* Calculate PTE[x] address for iPXE virtual address map */ + LOADN t0, prefix_link + srli t0, t0, VPN1_LSB + andi t0, t0, ( PTE_COUNT - 1 ) + slli t0, t0, PTE_SIZE_LOG2 + add a3, a0, t0 + + /* Calculate PTE stride for iPXE virtual address map + * + * PPN[1] LSB is PTE bit 19 in all paging modes, and so the + * stride is always ( 1 << 19 ) + */ + li a4, 1 + slli a4, a4, PTE_PPN1_LSB + + /* Construct PTE[x-1] for early UART, if applicable */ +#ifdef EARLY_UART_REG_BASE + li t0, ( EARLY_UART_REG_BASE & ~( ( 1 << VPN1_LSB ) - 1 ) ) + srli t0, t0, PTE_PPN_SHIFT + ori t0, t0, ( PTE_LEAF & ~PTE_X ) + STOREN t0, -PTE_SIZE(a3) +#endif + + /* Construct PTE[x-y] for iPXE virtual address map */ + la t0, _prefix + srli t0, t0, PTE_PPN_SHIFT + LOADN t1, (a0) + or t0, t0, t1 + la t2, _ebss + srli t2, t2, PTE_PPN_SHIFT +1: STOREN t0, (a3) + addi a3, a3, PTE_SIZE + add t0, t0, a4 + ble t0, t2, 1b + + /* Find highest supported paging level */ + li a1, SATP_MODE_SV57 +enable_paging_64_loop: + + /* Calculate PTE stride for identity map at this paging level + * + * a1 == 10 == Sv57: PPN[4] LSB is PTE bit 46 => stride := 1 << 46 + * a1 == 9 == Sv48: PPN[3] LSB is PTE bit 37 => stride := 1 << 37 + * a1 == 8 == Sv39: PPN[2] LSB is PTE bit 28 => stride := 1 << 28 + * + * and so we calculate stride a4 := ( 1 << ( 9 * a1 - 44 ) ) + */ + slli a4, a1, 3 + add a4, a4, a1 + addi a4, a4, -44 + li t0, 1 + sll a4, t0, a4 + + /* Calculate size of accessible physical address space + * + * The identity map comprises only the lower half of the PTEs, + * since virtual addresses for the higher half must have all + * high bits set, and so cannot form part of an identity map. + */ + slli a5, a4, ( PTE_PPN_SHIFT + ( PTE_COUNT_LOG2 - 1 ) ) + + /* Construct PTE[0-255] for identity map at this paging level */ + mv a3, a0 + li t0, ( PTE_COUNT / 2 ) + LOADN t1, (a0) +1: STOREN t1, (a3) + addi a3, a3, PTE_SIZE + add t1, t1, a4 + addi t0, t0, -1 + bgtz t0, 1b + + /* Attempt to enable paging, and read back active paging level */ + slli t0, a1, SATP_MODE_SHIFT + srli t1, a0, PAGE_SHIFT + or t0, t0, t1 + csrw satp, t0 + sfence.vma + csrr a2, satp + srli a2, a2, SATP_MODE_SHIFT + + /* Loop until we successfully enable paging, or run out of levels */ + beq a2, a1, 1f + csrw satp, zero + addi a1, a1, -1 + li t0, SATP_MODE_SV39 + bge a1, t0, enable_paging_64_loop + mv tp, zero + mv a5, zero +1: + /* Adjust return address to a virtual address */ + sub ra, ra, tp + + /* Return, with or without paging enabled */ + paging_mode_name a2 + mv a0, a5 + ret + .size enable_paging_64, . - enable_paging_64 + + /* Convert 64-bit physical address to virtual address */ + .macro phys_to_virt_64 rd, rs:vararg + .ifnb \rs + mv \rd, \rs + .endif + .endm + + /* Early UART base address when 64-bit paging is enabled + * + * When an early UART is in use, we choose to use the 2MB + * "megapage" immediately below iPXE itself to map the UART. + */ +#ifdef EARLY_UART_REG_BASE + .section ".rodata.early_uart_reg_base_64_virt", "a", @progbits + .balign 8 +early_uart_reg_base_64_virt: + .dword ( _base - ( 1 << VPN1_LSB ) + \ + ( EARLY_UART_REG_BASE & ( ( 1 << VPN1_LSB ) - 1 ) ) ) + .size early_uart_reg_base_64_virt, . - early_uart_reg_base_64_virt +#endif + +/***************************************************************************** + * + * Disable 64-bit paging + * + ***************************************************************************** + * + * This function may be called with either virtual or flat physical + * addressing. It does not require a valid stack pointer. + * + * Parameters: + * + * tp - Virtual address offset + * + * Returns: + * + * tp - Virtual address offset (zeroed) + * pc - Updated to a physical address + * + */ + + .section ".prefix.disable_paging_64", "ax", @progbits +disable_paging_64: + /* Register usage: + * + * tp - virtual address offset + */ + + /* Jump to physical address */ + la t0, 1f + bgez t0, 1f + add t0, t0, tp + jr t0 +1: + /* Disable paging */ + csrw satp, zero + sfence.vma + + /* Update return address to a physical address */ + bgez ra, 1f + add ra, ra, tp +1: + /* Return with paging disabled and virtual offset zeroed */ + mv tp, zero + ret + .size disable_paging_64, . - disable_paging_64 + +/***************************************************************************** + * + * Enable 32-bit paging + * + ***************************************************************************** + * + * Construct a 32-bit page table to map the whole of the 32-bit + * address space with a fixed offset selected to map iPXE itself at + * its link-time address (which must be 4MB-aligned). + * + * This function must be called with flat physical addressing. It + * does not require a valid stack pointer. + * + * Parameters: + * + * a0 - Page table to fill in (4kB, must be aligned to a 4kB boundary) + * + * Returns: + * + * a0 - Size of accessible physical address space (or zero for no limit) + * tp - Virtual address offset + * pc - Updated to a virtual address if paging enabled + * + * A 4kB 32-bit page table contains 1024 4-byte PTEs. We choose to + * use these to produce a circular map of the 32-bit address space + * using 4MB "megapages", with a fixed offset to align the virtual and + * link-time addresses. + * + * To handle the transition from physical to virtual addresses, we + * temporarily adjust the PTE covering the current program counter to + * be a direct physical map (so that the program counter remains valid + * at the moment when paging is enabled), then jump to a virtual + * address, then restore the temporarily modified PTE. + */ + + .equ enable_paging_32_xalign, 32 + + .section ".prefix.enable_paging_32", "ax", @progbits +enable_paging_32: + /* Register usage: + * + * tp - return value (virtual address offset) + * a0 - page table base address + * a1 - enabled paging level + * a2 - PTE pointer + * a3 - saved content of temporarily modified PTE + */ + progress " paging:" + + /* Calculate virtual address offset */ + LOADN t0, prefix_link + la t1, _prefix + sub tp, t1, t0 + + /* Construct PTEs for circular map */ + mv a2, a0 + li t0, PTE_COUNT + mv t1, tp + ori t1, t1, ( PTE_LEAF << PTE_PPN_SHIFT ) + li t2, ( 1 << ( PTE_PPN1_LSB + PTE_PPN_SHIFT ) ) +1: srli t3, t1, PTE_PPN_SHIFT + STOREN t3, (a2) + addi a2, a2, PTE_SIZE + add t1, t1, t2 + addi t0, t0, -1 + bgtz t0, 1b + + /* Temporarily modify PTE for transition code to be an identity map */ + la t0, enable_paging_32_xstart + srli t0, t0, VPN1_LSB + slli t1, t0, PTE_SIZE_LOG2 + add a2, a0, t1 + LOADN a3, (a2) + slli t0, t0, PTE_PPN1_LSB + ori t0, t0, PTE_LEAF + STOREN t0, (a2) + + /* Adjust PTE pointer to a virtual address */ + sub a2, a2, tp + + /* Attempt to enable paging, and read back active paging level */ + la t0, 1f + sub t0, t0, tp + li t1, ( SATP_MODE_SV32 << SATP_MODE_SHIFT ) + srli t2, a0, PAGE_SHIFT + or t1, t1, t2 + .balign enable_paging_32_xalign + /* Start of transition code */ +enable_paging_32_xstart: + csrw satp, t1 + sfence.vma + csrr a1, satp + beqz a1, 2f + jr t0 +1: /* Restore temporarily modified PTE */ + STOREN a3, (a2) + sfence.vma + /* End of transition code */ + .equ enable_paging_32_xlen, . - enable_paging_32_xstart +2: srli a1, a1, SATP_MODE_SHIFT + + /* Zero SATP and virtual address offset if paging is not enabled */ + bnez a1, 1f + csrw satp, zero + mv tp, zero +1: + /* Adjust return address to a virtual address */ + sub ra, ra, tp + + /* Return, with or without paging enabled */ + paging_mode_name a1 + mv a0, zero + ret + .size enable_paging_32, . - enable_paging_32 + + /* Ensure that transition code did not cross an alignment boundary */ + .section ".bss.enable_paging_32_xcheck", "aw", @nobits + .org . + enable_paging_32_xalign - enable_paging_32_xlen + + /* Convert 32-bit physical address to virtual address */ + .macro phys_to_virt_32 rd, rs:vararg + .ifnb \rs + sub \rd, \rs, tp + .else + sub \rd, \rd, tp + .endif + .endm + +/***************************************************************************** + * + * Disable 32-bit paging + * + ***************************************************************************** + * + * This function may be called with either virtual or flat physical + * addressing. It does not require a valid stack pointer. + * + * Parameters: + * + * tp - Virtual address offset + * + * Returns: + * + * tp - Virtual address offset (zeroed) + * pc - Updated to a physical address + * + */ + + .equ disable_paging_32_xalign, 16 + + .section ".prefix.disable_paging_32", "ax", @progbits +disable_paging_32: + /* Register usage: + * + * tp - virtual address offset + * a0 - page table address + * a1 - transition PTE pointer + * a2 - transition PTE content + */ + + /* Get page table address, and exit if paging is already disabled */ + csrr a0, satp + beqz a0, 99f + slli a0, a0, PAGE_SHIFT + sub a0, a0, tp + + /* Prepare for modifying transition PTE */ + la t0, disable_paging_32_xstart + add t0, t0, tp + srli t0, t0, VPN1_LSB + slli a1, t0, PTE_SIZE_LOG2 + add a1, a1, a0 + slli a2, t0, PTE_PPN1_LSB + ori a2, a2, PTE_LEAF + + /* Jump to physical address in transition PTE, and disable paging */ + la t0, 1f + add t0, t0, tp + .balign disable_paging_32_xalign + /* Start of transition code */ +disable_paging_32_xstart: + STOREN a2, (a1) + sfence.vma + jr t0 +1: csrw satp, zero + sfence.vma + /* End of transition code */ + .equ disable_paging_32_xlen, . - disable_paging_32_xstart + + /* Update return address to a physical address */ + add ra, ra, tp + +99: /* Return with paging disabled and virtual offset zeroed */ + mv tp, zero + ret + .size disable_paging_32, . - disable_paging_32 + + /* Ensure that transition code did not cross an alignment boundary */ + .section ".bss.disable_paging_32_xcheck", "aw", @nobits + .org . + disable_paging_32_xalign - disable_paging_32_xlen + +/***************************************************************************** + * + * Poison .bss section + * + ***************************************************************************** + * + * Fill the .bss section with an invalid non-zero value to expose bugs + * in early initialisation code that erroneously relies upon variables + * in .bss before the section has been zeroed. + * + * We use the value 0xeb55eb55eb55eb55 ("EBSS") since this is + * immediately recognisable as a value in a crash dump, and will + * trigger a page fault if dereferenced since the address is in a + * non-canonical form. + * + * Poisoning the .bss will overwrite the relocation records, and so + * can be done only as a debugging step on a system where relocation + * is known to be unnecessary (e.g. because paging is supported). + * + * This function does not require a valid stack pointer, but will + * destroy any existing stack contents if the stack happens to be + * placed within the original .bss section. + * + * Parameters: none + * + * Returns: none + * + */ + + .equ poison_bss_value_32, 0xeb55eb55 + .equ poison_bss_value_64, 0xeb55eb55eb55eb55 + .equ poison_bss_value, _C2 ( poison_bss_value_, __riscv_xlen ) + + .section ".prefix.poison_bss", "ax", @progbits +poison_bss: + /* Fill .bss section */ + la t0, _bss + la t1, _ebss + li t2, poison_bss_value +1: STOREN t2, (t0) + addi t0, t0, ( __riscv_xlen / 8 ) + blt t0, t1, 1b + ret + .size poison_bss, . - poison_bss + +/***************************************************************************** + * + * Install iPXE to a suitable runtime address + * + ***************************************************************************** + * + * Identify a suitable runtime address for iPXE, relocate there, and + * set up for running normal C code. + * + * A valid temporary stack pointer is required. A 4kB space for a + * temporary page table may be provided, and must be provided if the + * iPXE image is running from read-only memory. + * + * Note that this function does not preserve the callee-save registers. + * + * Parameters: + * + * a0 - Boot hart ID + * a1 - Device tree physical address + * a2 - Optional temporary page table space (4kB, aligned to a 4kB boundary) + * sp - Valid temporary stack pointer + * + * Returns: + * + * pc - Updated to be within the relocated iPXE + * sp - Top of internal stack + * tp - Virtual address offset + * + */ + + .section ".prefix.install", "ax", @progbits + .globl install +install: + /* Register usage: + * + * s0 - boot hart ID + * s1 - device tree physical address + * s2 - saved return address + * s3 - relocation records physical address + * s4 - maximum accessible physical address + * s5 - relocation physical address + * s6 - relocation offset + * tp - virtual address offset + */ + mv tp, zero + progress "\r\nSBI->iPXE hart:" + print_hex_reg a0 + progress " temp:" + print_hex_reg a2 + progress " fdt:" + print_hex_reg a1 + progress "\r\nSBI->iPXE phys:" + print_hex_addr _prefix + progress " virt:" + print_hex_data prefix_virt + mv s0, a0 + mv s1, a1 + mv s2, ra + la s3, _edata + + /* Poison .bss if configured to do so */ +#if POISON_BSS + call poison_bss +#endif + + /* Attempt to enable paging, if we have temporary page table space */ + mv a0, a2 + beqz a2, 1f + call enable_paging +1: addi s4, a0, -1 + + /* Apply relocations, if still needed after enabling paging */ + mv a0, s3 + call apply_relocs + + /* Find a suitable address for relocation (using temporary stack) */ + phys_to_virt a0, s1 + mv a1, s4 + phys_to_virt sp + call fdtmem_relocate + mv s5, a0 + progress "SBI->iPXE dest:" + print_hex_reg a0 + + /* Disable paging */ + call disable_paging + + /* Determine relocation offset */ + la s6, _prefix + sub s6, s5, s6 + + /* Copy iPXE image to new location and zero .bss */ + mv t0, s5 + la t1, _prefix + la t2, _edata +1: LOADN t3, (t1) + STOREN t3, (t0) + addi t0, t0, ( __riscv_xlen / 8 ) + addi t1, t1, ( __riscv_xlen / 8 ) + blt t1, t2, 1b + la t1, _ebss + add t1, t1, s6 +2: STOREN zero, (t0) + addi t0, t0, ( __riscv_xlen / 8 ) + blt t0, t1, 2b + + /* Jump to relocated copy */ + la t0, 1f + add t0, t0, s6 + jr t0 +1: + /* Attempt to re-enable paging */ + la a0, page_table + call enable_paging + + /* Reapply relocations, if still needed after enabling paging */ + phys_to_virt a0, s3 + call apply_relocs + + /* Load stack pointer */ + la sp, _estack + + /* Store boot hart */ + STOREN s0, boot_hart, t0 + + /* Copy and register system device tree */ + phys_to_virt a0, s1 + mv a1, s4 + call fdtmem_register + + /* Return to a virtual address in the relocated copy */ + add ra, s2, s6 + sub ra, ra, tp + progress "\r\n" + ret + .size install, . - install + +/***************************************************************************** + * + * Reset (or lock up) system + * + ***************************************************************************** + * + * Reset via system via SBI, as a means of exiting from a prefix that + * has no other defined exit path. If the reset fails, lock up the + * system since there is nothing else that can sensibly be done. + * + * This function does not require a valid stack pointer. + * + * Parameters: none + * + * Returns: n/a (does not return) + * + */ + +/* SBI system reset extension */ +#define SBI_SRST ( ( 'S' << 24 ) | ( 'R' << 16 ) | ( 'S' << 8 ) | 'T' ) +#define SBI_SRST_SYSTEM_RESET 0x00 +#define SBI_RESET_COLD 0x00000001 + +/* SBI legacy shutdown */ +#define SBI_LEGACY_SHUTDOWN 0x08 + + .section ".prefix.reset_system", "ax", @progbits + .globl reset_system +reset_system: + /* Register usage: irrelevant (does not return) */ + progress "\r\niPXE->SBI reset\r\n" + + /* Attempt reset */ + li a7, SBI_SRST + li a6, SBI_SRST_SYSTEM_RESET + li a0, SBI_RESET_COLD + mv a1, zero + ecall + progress "(reset failed)\r\n" + + /* Attempt legacy shutdown */ + li a7, SBI_LEGACY_SHUTDOWN + ecall + progress "(legacy shutdown failed)\r\n" + + /* If reset failed, lock the system */ +1: wfi + j 1b + .size reset_system, . - reset_system + +/***************************************************************************** + * + * File split information for the compressor + * + ***************************************************************************** + */ + +/* ELF machine type */ +#define EM_RISCV 243 + + .section ".zinfo", "a", @progbits + .org 0 + /* Copy initialised-data portion of image */ + .ascii "COPY" + .word 0 + .word _filesz + .word 1 + /* Notify compressor of link-time base address */ + .ascii "BASE" + .word 0 + .dword _base + /* Construct compressed relocation records */ + .ascii "ZREL" + .word _reloc_offset + .word _reloc_filesz + .word EM_RISCV diff --git a/src/arch/riscv/prefix/lkrnprefix.S b/src/arch/riscv/prefix/lkrnprefix.S new file mode 100644 index 000000000..9493d016c --- /dev/null +++ b/src/arch/riscv/prefix/lkrnprefix.S @@ -0,0 +1,129 @@ +/* + * 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 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 ) + +/** @file + * + * Linux kernel prefix + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + + /* Layout of kernel header */ + .struct 0 +hdr_code0: .space 4 +hdr_code1: .space 4 +hdr_text_offset: .space 8 +hdr_image_size: .space 8 +hdr_flags: .space 8 +hdr_version: .space 4 +hdr_res1: .space 4 +hdr_res2: .space 8 +hdr_magic: .space 8 +hdr_magic2: .space 4 +hdr_res3: .space 4 +hdr_end: + .org 64 + .previous + +/* Header version */ +#define HDR_VERSION( major, minor ) ( ( (major) << 16 ) | (minor) ) +#define HDR_VERSION_0_2 HDR_VERSION ( 0, 2 ) + +/* Header flags */ +#define HDR_FL_BIG_ENDIAN 0x00000001 + +/* Magic numbers */ +#define HDR_MAGIC "RISCV\0\0\0" +#define HDR_MAGIC2 "RSC\x05" + + /* + * Linux kernel header + */ + .section ".prefix", "ax", @progbits + + /* Executable code / MZ header (for EFI-compatible binaries) */ + .org hdr_code0 + j _lkrn_start + + /* Image load offset + * + * Must be set to the size of a single "megapage" (2MB for + * 64-bit, 4MB for 32-bit). + */ + .org hdr_text_offset + .dword _max_align + + /* Image size (including uninitialised-data potions) */ + .org hdr_image_size + .dword _memsz + + /* Flags */ + .org hdr_flags + .dword 0 + + /* Version */ + .org hdr_version + .word HDR_VERSION_0_2 + + /* Magic numbers */ + .org hdr_magic + .ascii HDR_MAGIC + .org hdr_magic2 + .ascii HDR_MAGIC2 + + .org hdr_end + + /* + * Linux kernel entry point + */ + .globl _lkrn_start +_lkrn_start: + /* Identify temporary page table and stack space + * + * Linux expects to be placed at the image load offset from + * the start of RAM. Assume that our loaded image is + * therefore already writable, and that we can therefore use + * the page table and stack within our (not yet zeroed) .bss + * section. + */ + la a2, page_table + la sp, _estack + + /* Install iPXE */ + call install + + /* Call main program */ + call main + + /* We have no return path, since the Linux kernel does not + * define that a valid return address exists. + * + * Attempt a system reset, since there is nothing else we can + * viably do at this point. + */ + j reset_system + .size _lkrn_start, . - _lkrn_start diff --git a/src/arch/riscv/prefix/sbiprefix.S b/src/arch/riscv/prefix/sbiprefix.S new file mode 100644 index 000000000..da3202aa2 --- /dev/null +++ b/src/arch/riscv/prefix/sbiprefix.S @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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 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 ) + +/** @file + * + * SBI position-independent executable prefix + * + */ + + .section ".note.GNU-stack", "", @progbits + .text + +/* Page size */ +#define PAGE_SIZE 4096 + + /* + * SBI entry point + */ + .section ".prefix", "ax", @progbits + .org 0 + .globl _sbi_start +_sbi_start: + /* Identify temporary page table and stack space + * + * Assume that there is sufficient writable memory (~8kB) + * directly below the device tree. + */ + li t0, ~( PAGE_SIZE - 1 ) + and sp, a1, t0 + li t0, PAGE_SIZE + sub sp, sp, t0 + mv a2, sp + + /* Install iPXE */ + call install + + /* Call main program */ + call main + + /* We have no return path, since the M-mode SBI implementation + * will have jumped to us by setting our start address in MEPC + * and issuing an MRET instruction. + * + * Attempt a system reset, since there is nothing else we can + * viably do at this point. + */ + j reset_system + .size _sbi_start, . - _sbi_start diff --git a/src/arch/riscv/scripts/sbi.lds b/src/arch/riscv/scripts/sbi.lds new file mode 100644 index 000000000..04cabcf85 --- /dev/null +++ b/src/arch/riscv/scripts/sbi.lds @@ -0,0 +1,133 @@ +/* + * Linker script for RISC-V SBI images + * + */ + +SECTIONS { + + /* Weak symbols that need zero values if not otherwise defined */ + saved_pos = .; + .weak 0x0 : { + _weak = .; + *(.weak) + *(.weak.*) + _eweak = .; + } + _assert = ASSERT ( ( _weak == _eweak ), ".weak is non-zero length" ); + _assert = ASSERT ( ( . == saved_pos ), ".weak altered current position" ); + + /* Prefix code */ + .prefix : { + _prefix = .; + *(.prefix) + *(.prefix.*) + _eprefix = .; + } + + /* Program code */ + .text : { + _text = .; + *(.text) + *(.text.*) + _etext = .; + } + + /* Align to page size to allow linker to generate W^X segments */ + . = ALIGN ( 4096 ); + + /* Read-only data */ + .rodata : { + _rodata = .; + *(.srodata) + *(.srodata.*) + *(.rodata) + *(.rodata.*) + _erodata = .; + } + + /* Writable data */ + .data : { + _data = .; + *(.sdata) + *(.sdata.*) + *(.data) + *(.data.*) + KEEP(*(SORT(.tbl.*))) /* Various tables. See include/tables.h */ + KEEP(*(.provided)) + KEEP(*(.provided.*)) + *(.got) + *(.got.plt) + /* Ensure compressed relocations end up aligned */ + . = ALIGN ( 16 ); + _edata = .; + } + + /* Uninitialised and discardable data */ + OVERLAY : { + + /* Runtime relocations (discarded after use) */ + .rela.dyn { + *(.rela) + *(.rela.dyn) + } + + /* Compressor information block */ + .zinfo { + _zinfo = .; + KEEP(*(.zinfo)) + KEEP(*(.zinfo.*)) + _ezinfo = .; + } + + /* Uninitialised data */ + .bss { + _bss = .; + *(.sbss) + *(.sbss.*) + *(.bss) + *(.bss.*) + *(COMMON) + *(.stack) + *(.stack.*) + /* Align to allow for easy zeroing by prefix code */ + . = ALIGN ( 16 ); + _ebss = .; + } + } + + /* End virtual address */ + _end = .; + + /* Base virtual address */ + _base = ABSOLUTE ( _prefix ); + + /* Relocations */ + _reloc_offset = ( LOADADDR ( .rela.dyn ) - LOADADDR ( .prefix ) ); + _reloc_filesz = SIZEOF ( .rela.dyn ); + + /* Length of initialised data */ + _filesz = ( ABSOLUTE ( _edata ) - ABSOLUTE ( _prefix ) ); + + /* Length of in-memory image */ + _memsz = ( ABSOLUTE ( _end ) - ABSOLUTE ( _prefix ) ); + + /* Unwanted sections */ + /DISCARD/ : { + *(.comment) + *(.comment.*) + *(.note) + *(.note.*) + *(.eh_frame) + *(.eh_frame.*) + *(.dynamic) + *(.dynsym) + *(.dynstr) + *(.hash) + *(.gnu.hash) + *(.einfo) + *(.einfo.*) + *(.discard) + *(.discard.*) + *(.pci_devlist.*) + } +} |
