minos/arch/riscv/arch.c
Шурупов Илья Викторович 775a02cf3f Add D1/Lichee RV hardware boot and UART input
Bring up minos on the Allwinner D1 (Lichee RV) over FEL/xfel and add
bidirectional UART so the console can read keystrokes, not just print.

D1 / Lichee RV boot:
- riscv arch.c: select UART0 base by -DBOARD_D1 (0x02500000 vs QEMU
  0x10000000) and add a reg-shift abstraction (uart_rd/uart_wr): the D1's
  Synopsys DW-8250 uses 32-bit registers at 4-byte spacing, which is what
  made the first hardware boot silent.
- d1_uart0_init(): gate/deassert UART0 clock, mux PB8/PB9 to UART0, set
  115200 8N1, clear FIFOs, force non-loopback, drain stale RX.
- link-d1.ld: link for DRAM at 0x40000000 (xfel exec target, M-mode).
- Makefile: 'lichee' builds the flat binary; 'run-lichee' loads it via
  xfel (ddr d1 / write / exec).
- arch_name() reports the concrete board.

UART input (all archs):
- arch contract gains arch_uart_getc() and arch_uart_rx_ready(),
  implemented for riscv, x86 (COM1) and arm (Cadence UART).
- console gains getc() and drain_rx().
- kmain() runs an interactive echo loop (seed of the M3 console).

Verified: echo works under QEMU on x86/riscv/arm, and on real D1
hardware (banner once, then live keystroke echo over UART0 @115200).
2026-09-25 21:13:59 +03:00

169 lines
5.2 KiB
C

/* arch/riscv/arch.c — RISC-V implementation of the arch contract.
*
* QEMU 'virt' exposes a standard 16550-style UART at 0x10000000.
* (On the real Lichee RV / D1 this address becomes 0x02500000 — same
* driver, different base; that's the only line that changes for hardware.) */
#include "arch.h"
#include "console.h"
/* UART base differs by platform. Both are 16550-compatible, so the same
* driver works; only the MMIO base changes.
* QEMU 'virt' : 0x10000000
* Allwinner D1 (Lichee RV) UART0 : 0x02500000
* Select with -DBOARD_D1 at build time (see 'make lichee'). */
#ifdef BOARD_D1
#define UART0_BASE 0x02500000UL
#else
#define UART0_BASE 0x10000000UL
#endif
/* Register numbers (16550). The BYTE offset depends on the platform's
* register spacing:
* QEMU 'virt' : 8-bit registers, 1-byte spacing (reg-shift 0)
* Allwinner D1: Synopsys DW 8250, 32-bit registers, 4-byte spacing
* (reg-shift 2) — this is the difference that made the
* first hardware boot silent. */
#define UART_RBR 0 /* receive buffer register (read) */
#define UART_THR 0 /* transmit holding register (write) */
#define UART_LSR 5 /* line status register */
#define UART_LSR_DR 0x01 /* data ready (RX byte available) */
#define UART_LSR_THRE 0x20 /* THR empty */
#ifdef BOARD_D1
#define UART_SHIFT 2
static inline unsigned int uart_rd(int reg)
{
return *(volatile unsigned int *)(UART0_BASE + ((unsigned long)reg << UART_SHIFT));
}
static inline void uart_wr(int reg, unsigned int v)
{
*(volatile unsigned int *)(UART0_BASE + ((unsigned long)reg << UART_SHIFT)) = v;
}
#else
static inline unsigned int uart_rd(int reg)
{
return *(volatile unsigned char *)(UART0_BASE + reg);
}
static inline void uart_wr(int reg, unsigned int v)
{
*(volatile unsigned char *)(UART0_BASE + reg) = (unsigned char)v;
}
#endif
#ifdef BOARD_D1
/* Full UART0 bring-up on the Allwinner D1 (sun20i). The FEL boot ROM does
* NOT guarantee UART0 is clocked/muxed/baud-set, so we do it ourselves.
* Register map from the D1 user manual. */
#define CCU_BASE 0x02001000UL
#define CCU_UART_BGR (CCU_BASE + 0x090C) /* UART bus gating & reset */
#define PIO_BASE 0x02000000UL
#define PB_CFG1 (PIO_BASE + 0x0034) /* port B, pins 8..15, 4 bits each */
#define w32(a, v) (*(volatile unsigned int *)(a) = (unsigned int)(v))
#define r32(a) (*(volatile unsigned int *)(a))
/* DW-8250 register numbers (reg-shift 2 applied by uart_wr/uart_rd). */
#define UART_DLL 0 /* divisor low (DLAB=1) */
#define UART_DLH 1 /* divisor high (DLAB=1) */
#define UART_FCR 2 /* FIFO control (write) */
#define UART_LCR 3 /* line control */
#define UART_MCR 4 /* modem control */
#define UART_MCR_LOOP 0x10 /* internal loopback (TX->RX) */
static void d1_uart0_init(void)
{
unsigned int v;
/* 1. Enable UART0: bus gate (bit0) + deassert reset (bit16). */
v = r32(CCU_UART_BGR);
v |= (1u << 16) | (1u << 0);
w32(CCU_UART_BGR, v);
/* 2. Mux PB8=UART0_TX, PB9=UART0_RX to function 6. */
v = r32(PB_CFG1);
v &= ~0xFFu; /* clear PB8 [3:0] and PB9 [7:4] */
v |= (6u << 0) | (6u << 4);
w32(PB_CFG1, v);
/* 3. Program 115200 8N1. UART src = 24 MHz OSC ->
* divisor = 24000000 / (16 * 115200) ~= 13. */
uart_wr(UART_LCR, 0x80); /* DLAB = 1 */
uart_wr(UART_DLL, 13);
uart_wr(UART_DLH, 0);
uart_wr(UART_LCR, 0x03); /* 8N1, DLAB = 0 */
uart_wr(UART_FCR, 0x07); /* enable + clear RX/TX FIFOs */
/* Defensive: force normal (non-loopback) operation in case the boot
* ROM left the modem-control internal-loopback bit set, which would
* feed TX straight back into RX. */
uart_wr(UART_MCR, 0x00);
/* Drain any stale bytes sitting in the RX FIFO so the first getc()
* waits for a real keypress instead of returning garbage. */
while (uart_rd(UART_LSR) & UART_LSR_DR)
(void)uart_rd(UART_RBR);
}
#endif
void arch_early_init(void)
{
#ifdef BOARD_D1
d1_uart0_init();
#endif
/* QEMU's UART is usable from reset; nothing to do there. */
}
void arch_uart_putc(char c)
{
while ((uart_rd(UART_LSR) & UART_LSR_THRE) == 0)
;
uart_wr(UART_THR, (unsigned char)c);
}
char arch_uart_getc(void)
{
while ((uart_rd(UART_LSR) & UART_LSR_DR) == 0)
;
return (char)(uart_rd(UART_RBR) & 0xff);
}
int arch_uart_rx_ready(void)
{
return (uart_rd(UART_LSR) & UART_LSR_DR) ? 1 : 0;
}
/* M1: install a minimal machine trap vector (defined in trap.S). */
extern void trap_entry(void);
void arch_set_trap_vector(void)
{
unsigned long addr = (unsigned long)trap_entry;
/* mtvec = base | mode(0=direct) */
__asm__ volatile("csrw mtvec, %0" :: "r"(addr));
}
const char *arch_name(void)
{
#ifdef BOARD_D1
return "riscv64 (Allwinner D1 / Lichee RV)";
#else
return "riscv64 (rv64imac, QEMU virt)";
#endif
}
const char *arch_cpu_mode(void)
{
/* RISC-V has no register that reports the current privilege level to
* software (by design). But mhartid is an M-mode-only CSR: if this
* code is running and already read it in boot.S without trapping, we
* are in M-mode. (A trap here would prove otherwise.) */
unsigned long id;
__asm__ volatile("csrr %0, mhartid" : "=r"(id));
(void)id;
return "M-mode";
}
void arch_halt(void)
{
for (;;)
__asm__ volatile("wfi");
}