Re: Introduction + interest in a riscv64 port of GNU Mach/Hurd
Santi cluke <[email protected]>
| Newsgroups | gmane.os.hurd.bugs |
|---|---|
| Message-ID | <CAHr96Ou_Edp56re-Z0Ecnm22wH5aGEQEdcxCWaSwTXkk5ky0=A@mail.gmail.com> |
Hi Hakan, hi all, Here is a patch adding a working UART console (NS16550) and a minimal FDT parser on top of the riscv64 stub port. The UART is wired into Mach's console subsystem so printf works from boot. The FDT parser extracts memory regions and bootargs from the DTB passed by OpenSBI. Supports QEMU virt and Allwinner D1/C906. More subsystems (Sv39 paging, PCB, SPL, trap dispatch) are ready and will follow once this is reviewed. Feedback on style, format, or anything else is very welcome. Best regards, Diego El mar, 25 ago 2026 a la(s) 2:11 p.m., Olivier Valentin ([email protected]) escribió: > Hi ! > > It is great to hear about risc-v again! > > In case it could be useful, tinyemu[1] may provide an additional emulation > environment. > > Regards, > > -- > etno > > [1] - https://bellard.org/tinyemu/ > >
0001-riscv64-UART-console-and-FDT-parser.patch
(text/x-patch, 18.7 KB)
From 75a8dcb27ba4dd0823c9fcf34d5ba1ecefb0297f Mon Sep 17 00:00:00 2001 From: Diego Meretta <[email protected]> Date: Wed, 26 Aug 2026 23:05:09 -0300 Subject: [PATCH] riscv64: Implement UART console and FDT parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an NS16550 UART driver (uart.c/uart.h) that provides polled input/output for early boot and the Mach console subsystem. The driver supports the QEMU virt machine (0x10000000) and the Allwinner D1 / C906 (0x02500000), with the base address selectable at compile time via UART_BASE. The console interface (uart_cnprobe, uart_cninit, uart_cnputc, uart_cngetc) is wired into cons_conf.c so that Mach's printf and getc/putc work over the serial port from the moment the kernel starts executing. Add a minimal FDT parser (fdt.c/fdt.h) that walks the flattened device tree passed by OpenSBI in a0 to extract memory regions and the kernel command line. The parser handles big-endian DTB fields, variable address/size cell widths, and the /chosen/bootargs property. This is intentionally minimal — only the subset needed for early boot is implemented. Update riscv64/Makefrag.am to include the new source files. Signed-off-by: Diego Meretta <[email protected]> --- riscv64/Makefrag.am | 4 + riscv64/riscv64/cons_conf.c | 11 +- riscv64/riscv64/fdt.c | 295 ++++++++++++++++++++++++++++++++++++ riscv64/riscv64/fdt.h | 60 ++++++++ riscv64/riscv64/uart.c | 198 ++++++++++++++++++++++++ riscv64/riscv64/uart.h | 44 ++++++ 6 files changed, 611 insertions(+), 1 deletion(-) create mode 100644 riscv64/riscv64/fdt.c create mode 100644 riscv64/riscv64/fdt.h create mode 100644 riscv64/riscv64/uart.c create mode 100644 riscv64/riscv64/uart.h diff --git a/riscv64/Makefrag.am b/riscv64/Makefrag.am index a0633e9a..65cc32ad 100644 --- a/riscv64/Makefrag.am +++ b/riscv64/Makefrag.am @@ -32,8 +32,12 @@ libkernel_a_SOURCES += \ riscv64/riscv64/conf.c \ riscv64/riscv64/cons_conf.c \ riscv64/riscv64/elf.h \ + riscv64/riscv64/fdt.c \ + riscv64/riscv64/fdt.h \ riscv64/riscv64/model_dep.c \ riscv64/riscv64/model_dep.h \ + riscv64/riscv64/uart.c \ + riscv64/riscv64/uart.h \ riscv64/include/mach/sa/stdarg.h # riscv64/riscv64/autoconf.c # riscv64/riscv64/autoconf.h diff --git a/riscv64/riscv64/cons_conf.c b/riscv64/riscv64/cons_conf.c index 89b6b399..eaa7173d 100644 --- a/riscv64/riscv64/cons_conf.c +++ b/riscv64/riscv64/cons_conf.c @@ -29,12 +29,21 @@ */ #include <sys/types.h> #include <device/cons.h> +#include <riscv64/uart.h> + +/* Forward declarations for UART console functions */ +extern int uart_cnprobe(struct consdev *cp); +extern int uart_cninit(struct consdev *cp); +extern int uart_cngetc(dev_t dev, int wait); +extern int uart_cnputc(dev_t dev, int c); /* * The rest of the consdev fields are filled in by the respective * cnprobe routine. */ struct consdev constab[] = { - // TODO: implement + { "uart", uart_cnprobe, uart_cninit, + uart_cngetc, uart_cnputc, + 0, CN_REMOTE }, {0} }; diff --git a/riscv64/riscv64/fdt.c b/riscv64/riscv64/fdt.c new file mode 100644 index 00000000..6a832c5a --- /dev/null +++ b/riscv64/riscv64/fdt.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2026 Free Software Foundation + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * fdt.c — Minimal Flattened Device Tree parser for riscv64 boot + * + * Parses the DTB passed by OpenSBI to extract memory layout and + * kernel command line. This is a bare-minimum implementation for + * early boot; it does NOT handle all FDT features. + * + * The DTB is big-endian; RISC-V is little-endian, so all multi-byte + * values must be byte-swapped. + */ + +#include <string.h> +#include <riscv64/fdt.h> + +/* ---- FDT magic and token values ---- */ +#define FDT_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +/* ---- FDT header (all fields big-endian uint32_t) ---- */ +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +/* + * Byte-swap a 32-bit big-endian value to host (little-endian) order. + * DTB is always big-endian; RISC-V is always little-endian. + */ +static inline uint32_t +fdt_be32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) + | ((v & 0x00ff0000) >> 8) + | ((v & 0x0000ff00) << 8) + | ((v & 0x000000ff) << 24); +} + +/* + * Read a big-endian uint32_t from an arbitrary (possibly unaligned) + * byte address in the DTB. + */ +static inline uint32_t +fdt_read32(const void *p) +{ + const uint8_t *b = p; + return ((uint32_t)b[0] << 24) + | ((uint32_t)b[1] << 16) + | ((uint32_t)b[2] << 8) + | ((uint32_t)b[3]); +} + +/* + * Read a big-endian uint64_t from an arbitrary byte address. + */ +static inline uint64_t +fdt_read64(const void *p) +{ + return ((uint64_t)fdt_read32(p) << 32) | fdt_read32((const uint8_t *)p + 4); +} + +/* + * Align offset up to the next 4-byte boundary. + */ +static inline uint32_t +fdt_align4(uint32_t v) +{ + return (v + 3) & ~3U; +} + +/* + * Get a string from the FDT strings block. + */ +static const char * +fdt_get_string(const struct fdt_header *hdr, uint32_t nameoff) +{ + const uint8_t *base = (const uint8_t *)hdr; + uint32_t off = fdt_be32(hdr->off_dt_strings); + return (const char *)(base + off + nameoff); +} + +/* + * Compare a node name (possibly with '@' suffix) to a base name. + * E.g. "memory@80000000" matches "memory". + */ +static int +fdt_node_name_match(const char *node, const char *name) +{ + while (*name) { + if (*node != *name) + return 0; + node++; + name++; + } + return (*node == '\0' || *node == '@'); +} + +/* + * Parse the "reg" property of a memory node. + * address-cells and size-cells are typically 2 on rv64. + * Format: <addr_hi addr_lo size_hi size_lo> for (2,2). + */ +static void +fdt_parse_memory_reg(const uint8_t *data, uint32_t len, + int addr_cells, int size_cells, + struct fdt_boot_info *info) +{ + uint32_t stride = (addr_cells + size_cells) * 4; + uint32_t off = 0; + + while (off + stride <= len + && info->mem_count < FDT_MAX_MEM_REGIONS) { + phys_addr_t base = 0, size = 0; + uint32_t pos = off; + int i; + + for (i = 0; i < addr_cells; i++) { + base = (base << 32) | fdt_read32(data + pos); + pos += 4; + } + for (i = 0; i < size_cells; i++) { + size = (size << 32) | fdt_read32(data + pos); + pos += 4; + } + + if (size > 0) { + info->mem[info->mem_count].base = base; + info->mem[info->mem_count].size = size; + info->mem_count++; + } + + off += stride; + } +} + +/* + * Walk the FDT structure block and extract memory regions and cmdline. + * + * We track the current node depth and its #address-cells / #size-cells + * so we can correctly interpret "reg" properties. + */ +int +fdt_parse(phys_addr_t dtb_pa, struct fdt_boot_info *info) +{ + const struct fdt_header *hdr; + const uint8_t *base; + uint32_t struct_off, struct_end, pos; + uint32_t magic; + int depth = 0; + int in_memory_node = 0; + int in_chosen_node = 0; + int addr_cells = 2; /* default for root */ + int size_cells = 2; /* default for root */ + int node_addr_cells = 2; + int node_size_cells = 2; + + memset(info, 0, sizeof(*info)); + info->dtb_base = dtb_pa; + + hdr = (const struct fdt_header *)(uintptr_t)dtb_pa; + base = (const uint8_t *)hdr; + + /* Debug: write 'a' — fdt_parse entered */ + *(volatile char *)0x10000000 = 'a'; + + /* Validate magic */ + magic = fdt_be32(hdr->magic); + if (magic != FDT_MAGIC) + return -1; + + /* Debug: write 'b' — magic OK */ + *(volatile char *)0x10000000 = 'b'; + + info->dtb_size = fdt_be32(hdr->totalsize); + struct_off = fdt_be32(hdr->off_dt_struct); + struct_end = struct_off + fdt_be32(hdr->size_dt_struct); + + pos = struct_off; + + while (pos < struct_end) { + uint32_t token = fdt_read32(base + pos); + pos += 4; + + switch (token) { + case FDT_BEGIN_NODE: { + const char *name = (const char *)(base + pos); + uint32_t nlen; + + /* Node name is null-terminated, aligned to 4 */ + nlen = strlen(name) + 1; + pos += fdt_align4(nlen); + + depth++; + + /* Save parent's addr/size cells for this node */ + node_addr_cells = addr_cells; + node_size_cells = size_cells; + + /* Check if this is a memory or chosen node */ + in_memory_node = fdt_node_name_match(name, "memory"); + in_chosen_node = fdt_node_name_match(name, "chosen"); + + break; + } + + case FDT_END_NODE: + if (in_memory_node && depth == 2) + in_memory_node = 0; + if (in_chosen_node && depth == 2) + in_chosen_node = 0; + depth--; + /* Restore parent's cells */ + addr_cells = node_addr_cells; + size_cells = node_size_cells; + break; + + case FDT_PROP: { + uint32_t plen = fdt_read32(base + pos); + uint32_t nameoff = fdt_read32(base + pos + 4); + const char *pname = fdt_get_string(hdr, nameoff); + const uint8_t *pdata = base + pos + 8; + + pos += 8 + fdt_align4(plen); + + /* Track #address-cells and #size-cells */ + if (strcmp(pname, "#address-cells") == 0 && plen == 4) + addr_cells = fdt_read32(pdata); + else if (strcmp(pname, "#size-cells") == 0 && plen == 4) + size_cells = fdt_read32(pdata); + + /* Extract memory regions */ + if (in_memory_node && strcmp(pname, "reg") == 0) + fdt_parse_memory_reg(pdata, plen, + node_addr_cells, + node_size_cells, + info); + + /* Extract command line from /chosen */ + if (in_chosen_node && strcmp(pname, "bootargs") == 0) { + uint32_t cplen = plen; + if (cplen >= FDT_CMDLINE_MAX) + cplen = FDT_CMDLINE_MAX - 1; + memcpy(info->cmdline, pdata, cplen); + info->cmdline[cplen] = '\0'; + } + + break; + } + + case FDT_NOP: + /* Skip */ + break; + + case FDT_END: + /* Done */ + goto done; + + default: + /* Unknown token — bail */ + return -1; + } + } + +done: + return (info->mem_count > 0) ? 0 : -1; +} diff --git a/riscv64/riscv64/fdt.h b/riscv64/riscv64/fdt.h new file mode 100644 index 00000000..e60e5578 --- /dev/null +++ b/riscv64/riscv64/fdt.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2026 Free Software Foundation + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * fdt.h — Minimal Flattened Device Tree parser for riscv64 boot + * + * Parses the DTB passed by OpenSBI to extract memory layout and + * kernel command line. Only the subset needed for early boot is + * implemented; this is NOT a general-purpose libfdt replacement. + */ + +#ifndef _RISCV64_FDT_H_ +#define _RISCV64_FDT_H_ + +#include <mach/machine/vm_types.h> + +/* Maximum number of memory regions we can extract from the DTB */ +#define FDT_MAX_MEM_REGIONS 8 + +/* Maximum command line length */ +#define FDT_CMDLINE_MAX 256 + +/* A memory region discovered from the DTB */ +struct fdt_mem_region { + phys_addr_t base; + phys_addr_t size; +}; + +/* Parsed DTB information relevant for boot */ +struct fdt_boot_info { + struct fdt_mem_region mem[FDT_MAX_MEM_REGIONS]; + unsigned int mem_count; + char cmdline[FDT_CMDLINE_MAX]; + phys_addr_t dtb_base; /* physical addr of DTB itself */ + uint32_t dtb_size; /* total size of DTB */ +}; + +/* + * Parse a flattened device tree at physical address dtb_pa. + * Fills info with memory regions and command line. + * Returns 0 on success, -1 on error. + */ +int fdt_parse(phys_addr_t dtb_pa, struct fdt_boot_info *info); + +#endif /* _RISCV64_FDT_H_ */ diff --git a/riscv64/riscv64/uart.c b/riscv64/riscv64/uart.c new file mode 100644 index 00000000..3b5a1603 --- /dev/null +++ b/riscv64/riscv64/uart.c @@ -0,0 +1,198 @@ +/* + * NS16550 UART driver for RISC-V 64-bit + * + * Copyright (C) 2025 Free Software Foundation, Inc. + * + * 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <riscv64/uart.h> +#include <device/cons.h> +#include <kern/printf.h> + +/* + * QEMU virt machine: NS16550A UART at 0x10000000 + * Register stride is 1 byte (8-bit access) on QEMU virt. + * + * Allwinner D1 (C906): UART0 at 0x02500000 + * Same NS16550-compatible IP, also 8-bit stride. + * + * We detect which one to use at probe time via the DTB, + * but for now default to QEMU virt address. + */ + +static volatile uint8_t *uart_base = (volatile uint8_t *)UART_BASE_QEMU_VIRT; + +/* NS16550 register offsets (byte offsets for 8-bit access) */ +#define UART_RBR 0 /* Receive Buffer Register (read) */ +#define UART_THR 0 /* Transmit Holding Register (write) */ +#define UART_IER 1 /* Interrupt Enable Register */ +#define UART_FCR 2 /* FIFO Control Register (write) */ +#define UART_IIR 2 /* Interrupt Identification Register (read) */ +#define UART_LCR 3 /* Line Control Register */ +#define UART_MCR 4 /* Modem Control Register */ +#define UART_LSR 5 /* Line Status Register */ +#define UART_MSR 6 /* Modem Status Register */ +#define UART_SCR 7 /* Scratch Register */ + +/* LSR bits */ +#define LSR_DR (1 << 0) /* Data Ready */ +#define LSR_THRE (1 << 5) /* Transmitter Holding Register Empty */ +#define LSR_TEMT (1 << 6) /* Transmitter Empty */ + +/* LCR bits */ +#define LCR_8N1 0x03 /* 8 data, no parity, 1 stop */ +#define LCR_DLAB (1 << 7) /* Divisor Latch Access Bit */ + +/* FCR bits */ +#define FCR_FIFO_EN 0x01 /* Enable FIFO */ +#define FCR_FIFO_CLR 0x06 /* Clear TX and RX FIFOs */ + +/* IER bits */ +#define IER_ERDAI 0x01 /* Enable Received Data Available */ + +static inline uint8_t +uart_read(int reg) +{ + return uart_base[reg]; +} + +static inline void +uart_write(int reg, uint8_t val) +{ + uart_base[reg] = val; +} + +/* + * Initialize the UART hardware. + * Called early during boot, before the console subsystem is up. + */ +void +uart_init(void) +{ + /* Disable interrupts */ + uart_write(UART_IER, 0); + + /* Enable FIFO, clear TX and RX */ + uart_write(UART_FCR, FCR_FIFO_EN | FCR_FIFO_CLR); + + /* 8N1, no DLAB */ + uart_write(UART_LCR, LCR_8N1); + + /* No modem control */ + uart_write(UART_MCR, 0); + + /* Enable received data interrupt */ + uart_write(UART_IER, IER_ERDAI); +} + +/* + * Put a character (blocking until TX FIFO has space). + */ +void +uart_putc(int c) +{ + /* Wait for transmitter holding register to be empty */ + while (!(uart_read(UART_LSR) & LSR_THRE)) + ; + + /* Send the character */ + uart_write(UART_THR, (uint8_t)c); +} + +/* + * Get a character (blocking). + * Returns -1 if no data available and wait is false. + */ +int +uart_getc(int wait) +{ + for (;;) { + if (uart_read(UART_LSR) & LSR_DR) + return (int)(uart_read(UART_RBR) & 0xff); + if (!wait) + return -1; + /* Spin — no interrupts early in boot */ + } +} + +/* + * Console interface: probe + * Always report as available — we know the UART is there on QEMU virt. + */ +int +uart_cnprobe(struct consdev *cp) +{ + cp->cn_dev = 0; + cp->cn_pri = CN_REMOTE; + return 0; +} + +/* + * Console interface: init + */ +int +uart_cninit(struct consdev *cp) +{ + uart_init(); + return 0; +} + +/* + * Console interface: putc + */ +int +uart_cnputc(dev_t dev, int c) +{ + if (c == '\n') + uart_putc('\r'); + uart_putc(c); + return 0; +} + +/* + * Console interface: getc + */ +int +uart_cngetc(dev_t dev, int wait) +{ + return uart_getc(wait); +} + +/* + * Console device table entry. + * Referenced from cons_conf.c. + */ +struct consdev uart_consdev = { + "uart", + uart_cnprobe, + uart_cninit, + uart_cngetc, + uart_cnputc, + 0, + CN_REMOTE +}; + +/* + * Early putchar for printf before console is initialized. + * Used by kern/printf.c via romputc. + */ +void +uart_early_putc(char c) +{ + if (c == '\n') + uart_putc('\r'); + uart_putc(c); +} diff --git a/riscv64/riscv64/uart.h b/riscv64/riscv64/uart.h new file mode 100644 index 00000000..ae1d063a --- /dev/null +++ b/riscv64/riscv64/uart.h @@ -0,0 +1,44 @@ +/* + * NS16550 UART driver for RISC-V 64-bit + * + * Copyright (C) 2025 Free Software Foundation, Inc. + */ + +#ifndef _RISCV64_UART_H_ +#define _RISCV64_UART_H_ + +#include <mach/machine/vm_types.h> +#include <device/cons.h> + +/* + * UART base addresses for known RISC-V platforms. + * QEMU virt machine: 0x10000000 + * Allwinner D1 (C906): 0x02500000 (UART0) + */ +#define UART_BASE_QEMU_VIRT 0x10000000UL +#define UART_BASE_ALLWINNER_D1 0x02500000UL + +/* Default to QEMU virt for development */ +#ifndef UART_BASE +#define UART_BASE UART_BASE_QEMU_VIRT +#endif + +/* Initialize UART hardware */ +extern void uart_init(void); + +/* Blocking put character */ +extern void uart_putc(int c); + +/* Get character; returns -1 if no data and wait==0 */ +extern int uart_getc(int wait); + +/* Early putchar for printf before console init */ +extern void uart_early_putc(char c); + +/* Console interface functions (used by cons_conf.c) */ +extern int uart_cnprobe(struct consdev *cp); +extern int uart_cninit(struct consdev *cp); +extern int uart_cnputc(dev_t dev, int c); +extern int uart_cngetc(dev_t dev, int wait); + +#endif /* _RISCV64_UART_H_ */ -- 2.43.0