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/core | |
| 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/core')
| -rw-r--r-- | src/arch/riscv/core/hart.c | 100 | ||||
| -rw-r--r-- | src/arch/riscv/core/riscv_dma.c | 190 | ||||
| -rw-r--r-- | src/arch/riscv/core/riscv_io.c | 46 | ||||
| -rw-r--r-- | src/arch/riscv/core/riscv_string.c | 233 | ||||
| -rw-r--r-- | src/arch/riscv/core/riscv_strings.S | 70 | ||||
| -rw-r--r-- | src/arch/riscv/core/riscv_tcpip.S | 138 | ||||
| -rw-r--r-- | src/arch/riscv/core/setjmp.S | 105 | ||||
| -rw-r--r-- | src/arch/riscv/core/stack.S | 45 | ||||
| -rw-r--r-- | src/arch/riscv/core/svpage.c | 292 | ||||
| -rw-r--r-- | src/arch/riscv/core/xthead.c | 65 | ||||
| -rw-r--r-- | src/arch/riscv/core/zicbom.c | 255 | ||||
| -rw-r--r-- | src/arch/riscv/core/zicntr.c | 194 | ||||
| -rw-r--r-- | src/arch/riscv/core/zkr.c | 110 |
13 files changed, 1843 insertions, 0 deletions
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, +}; |
