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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.