minos M1: minimal multi-arch kernel (x86 + RISC-V + ARM/Zynq)
Boots under QEMU on all three ISAs from one clang build: - shared C core (kmain, console) calls a small per-arch contract (arch.h) - per-arch asm entry + trap/vector table + UART driver - parallel Makefile (make -j), 'make run-<arch>', 'make sizes' M1 = boot, UART up, trap vector installed, banner from kmain().
This commit is contained in:
commit
8c44d12357
18 changed files with 597 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
build/
|
||||||
|
*.o
|
||||||
|
*.d
|
||||||
|
*.elf
|
||||||
|
*.bin
|
||||||
77
Makefile
Normal file
77
Makefile
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
# minos — minimal multi-arch kernel (M1)
|
||||||
|
# One clang, three ISAs. Build all in parallel: make -j
|
||||||
|
#
|
||||||
|
# make build all three (x86, riscv, arm)
|
||||||
|
# make x86 build one arch
|
||||||
|
# make run-riscv build + boot that arch under QEMU (-nographic; Ctrl-A X to quit)
|
||||||
|
# make sizes compare code size across arches
|
||||||
|
# make clean
|
||||||
|
|
||||||
|
CC := clang
|
||||||
|
LD := ld.lld
|
||||||
|
OBJCOPY := llvm-objcopy
|
||||||
|
|
||||||
|
ARCHES := x86 riscv arm
|
||||||
|
|
||||||
|
# Shared flags. Per-arch target/flags below.
|
||||||
|
CFLAGS := -ffreestanding -nostdlib -fno-pic -O2 -Wall -Wextra -Iinclude -g -MMD -MP
|
||||||
|
COMMON := common/main.c common/console.c
|
||||||
|
|
||||||
|
# --- per-arch knobs -------------------------------------------------------
|
||||||
|
x86_TARGET := --target=i386-unknown-none -m32
|
||||||
|
x86_LDEMU := elf_i386
|
||||||
|
x86_SRC := arch/x86/boot.S arch/x86/arch.c $(COMMON)
|
||||||
|
x86_QEMU := qemu-system-i386 -kernel build/x86/minos.elf -nographic
|
||||||
|
|
||||||
|
riscv_TARGET := --target=riscv64-unknown-none-elf -march=rv64imac -mabi=lp64 -mcmodel=medany
|
||||||
|
riscv_LDEMU := elf64lriscv
|
||||||
|
riscv_SRC := arch/riscv/boot.S arch/riscv/trap.S arch/riscv/arch.c $(COMMON)
|
||||||
|
riscv_QEMU := qemu-system-riscv64 -machine virt -bios none -nographic -kernel build/riscv/minos.elf
|
||||||
|
|
||||||
|
arm_TARGET := --target=arm-none-eabi -mcpu=cortex-a9 -marm
|
||||||
|
arm_LDEMU := armelf
|
||||||
|
arm_SRC := arch/arm/boot.S arch/arm/vectors.S arch/arm/arch.c $(COMMON)
|
||||||
|
arm_QEMU := qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel build/arm/minos.elf
|
||||||
|
|
||||||
|
.PHONY: all clean sizes $(ARCHES) $(addprefix run-,$(ARCHES))
|
||||||
|
|
||||||
|
all: $(ARCHES)
|
||||||
|
|
||||||
|
# Generate per-arch build rules.
|
||||||
|
define ARCH_rules
|
||||||
|
$(1)_OBJS := $$(patsubst %,build/$(1)/%.o,$$(basename $$(notdir $$($(1)_SRC))))
|
||||||
|
|
||||||
|
build/$(1)/%.o: arch/$(1)/%.S | build/$(1)
|
||||||
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
||||||
|
build/$(1)/%.o: arch/$(1)/%.c | build/$(1)
|
||||||
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
||||||
|
build/$(1)/%.o: common/%.c | build/$(1)
|
||||||
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
||||||
|
|
||||||
|
build/$(1):
|
||||||
|
mkdir -p $$@
|
||||||
|
|
||||||
|
build/$(1)/minos.elf: $$($(1)_OBJS) arch/$(1)/link.ld
|
||||||
|
$$(LD) -m $$($(1)_LDEMU) -T arch/$(1)/link.ld -nostdlib $$($(1)_OBJS) -o $$@
|
||||||
|
@echo " [$(1)] linked -> $$@"
|
||||||
|
|
||||||
|
$(1): build/$(1)/minos.elf
|
||||||
|
run-$(1): build/$(1)/minos.elf
|
||||||
|
@echo "== booting $(1) (Ctrl-A X to quit) =="
|
||||||
|
$$($(1)_QEMU)
|
||||||
|
|
||||||
|
-include $$($(1)_OBJS:.o=.d)
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(foreach a,$(ARCHES),$(eval $(call ARCH_rules,$(a))))
|
||||||
|
|
||||||
|
sizes: all
|
||||||
|
@echo "arch text+data+bss (bytes)"; \
|
||||||
|
for a in $(ARCHES); do \
|
||||||
|
printf '%-7s ' $$a; \
|
||||||
|
$(OBJCOPY) -O binary build/$$a/minos.elf /tmp/_sz.bin 2>/dev/null; \
|
||||||
|
stat -c '%s' /tmp/_sz.bin; \
|
||||||
|
done; rm -f /tmp/_sz.bin
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build
|
||||||
65
README.md
Normal file
65
README.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# minos — a minimal multi-arch kernel
|
||||||
|
|
||||||
|
One tiny OS core, three ISAs: **x86 (i386)**, **RISC-V (rv64)**, **ARM
|
||||||
|
(Cortex-A9 / Zynq-7000)**. Built by a single `clang` in parallel so you can
|
||||||
|
compare how the same C behaves across architectures.
|
||||||
|
|
||||||
|
Milestone **M1** (this state): each target boots, brings up its UART, sets its
|
||||||
|
interrupt/trap vector, and prints a banner from shared C (`kmain`).
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
The only boundary is a small per-arch **contract** (`include/arch.h`):
|
||||||
|
|
||||||
|
```
|
||||||
|
arch_early_init() bring up the UART
|
||||||
|
arch_uart_putc(c) emit one byte
|
||||||
|
arch_set_trap_vector() install IDT / mtvec / VBAR
|
||||||
|
arch_name() banner string
|
||||||
|
arch_halt() hlt / wfi
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything in `common/` is portable C and calls only that contract. Each
|
||||||
|
`arch/<name>/` supplies the irreducible assembly (`boot.S`, trap/vector table)
|
||||||
|
plus a C file implementing the contract and a linker script.
|
||||||
|
|
||||||
|
```
|
||||||
|
common/ main.c (kmain), console.c (putc/puts/puthex)
|
||||||
|
include/ arch.h (the contract), console.h
|
||||||
|
arch/x86/ boot.S (multiboot) arch.c (COM1 0x3F8, IDT) link.ld (1 MiB)
|
||||||
|
arch/riscv/ boot.S (_start) trap.S (mtvec) arch.c (UART 0x10000000)
|
||||||
|
arch/arm/ boot.S (_start) vectors.S (VBAR) arch.c (Cadence UART 0xE0000000)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build & run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -j # build all three in parallel
|
||||||
|
make run-x86 # boot under QEMU (Ctrl-A X to quit)
|
||||||
|
make run-riscv
|
||||||
|
make run-arm
|
||||||
|
make sizes # compare stripped binary size
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## The interesting comparison (M1)
|
||||||
|
|
||||||
|
| Aspect | x86 (i386) | RISC-V (rv64) | ARM (Cortex-A9) |
|
||||||
|
|-------------------|-------------------------|-----------------------|--------------------------|
|
||||||
|
| Entry protocol | Multiboot1 header | `_start` @ 0x80000000 | `_start` @ DDR |
|
||||||
|
| Console | port I/O `outb` 0x3F8 | MMIO 16550 0x10000000 | MMIO Cadence 0xE0000000 |
|
||||||
|
| Trap vector | IDT via `lidt` | `mtvec` CSR | `VBAR` (CP15) |
|
||||||
|
| Multi-core gate | (single by default) | `mhartid` check | `MPIDR` check |
|
||||||
|
| Halt | `hlt` | `wfi` | `wfi` |
|
||||||
|
| Idle byte size | ~890 | ~816 | ~800 |
|
||||||
|
|
||||||
|
## Real hardware (next)
|
||||||
|
|
||||||
|
- **RISC-V**: the only change is the UART base — `0x10000000` (QEMU virt) →
|
||||||
|
`0x02500000` (Allwinner D1 / Lichee RV). Loadable via `xfel`.
|
||||||
|
- **ARM/Zynq**: same Cadence UART base as the real PS; boot via U-Boot/JTAG.
|
||||||
|
- **x86**: boot the multiboot ELF from GRUB.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
- **M2**: real trap handlers + a timer tick per arch.
|
||||||
|
- **M3**: expose a tiny syscall-style interface (the "own interface").
|
||||||
59
arch/arm/arch.c
Normal file
59
arch/arm/arch.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* arch/arm/arch.c — Cortex-A9 (Zynq-7000 PS) implementation.
|
||||||
|
* Uses the Cadence UART (UART0 at 0xE0000000) present on the real Zynq
|
||||||
|
* and modeled by QEMU's xilinx-zynq-a9 machine. On QEMU/Zynq the UART is
|
||||||
|
* already configured by the bootrom, so we just poll TX-full and write. */
|
||||||
|
#include "arch.h"
|
||||||
|
#include "console.h"
|
||||||
|
|
||||||
|
#define UART0_BASE 0xE0000000UL
|
||||||
|
#define UART_CR 0x00 /* control register */
|
||||||
|
#define UART_MR 0x04 /* mode register */
|
||||||
|
#define UART_SR 0x2C /* channel status register */
|
||||||
|
#define UART_FIFO 0x30 /* tx/rx FIFO */
|
||||||
|
#define UART_SR_TXFULL (1u << 4)
|
||||||
|
|
||||||
|
/* Control register bits */
|
||||||
|
#define CR_RXRES (1u << 0) /* RX logic reset */
|
||||||
|
#define CR_TXRES (1u << 1) /* TX logic reset */
|
||||||
|
#define CR_RXEN (1u << 2)
|
||||||
|
#define CR_TXEN (1u << 4)
|
||||||
|
|
||||||
|
static volatile unsigned int *reg(unsigned long off)
|
||||||
|
{
|
||||||
|
return (volatile unsigned int *)(UART0_BASE + off);
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_early_init(void)
|
||||||
|
{
|
||||||
|
/* Reset and enable TX/RX. On real Zynq the bootrom sets the baud
|
||||||
|
* divisors; QEMU ignores baud, so enabling TX is enough. */
|
||||||
|
*reg(UART_CR) = CR_RXRES | CR_TXRES; /* pulse resets */
|
||||||
|
*reg(UART_MR) = 0x20; /* 8N1, normal */
|
||||||
|
*reg(UART_CR) = CR_RXEN | CR_TXEN; /* enable TX/RX */
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_uart_putc(char c)
|
||||||
|
{
|
||||||
|
while (*reg(UART_SR) & UART_SR_TXFULL)
|
||||||
|
;
|
||||||
|
*reg(UART_FIFO) = (unsigned int)(unsigned char)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* M1: point the vector base (VBAR) at our table (defined in vectors.S). */
|
||||||
|
extern void vector_table(void);
|
||||||
|
void arch_set_trap_vector(void)
|
||||||
|
{
|
||||||
|
unsigned long v = (unsigned long)vector_table;
|
||||||
|
__asm__ volatile("mcr p15, 0, %0, c12, c0, 0" :: "r"(v)); /* VBAR */
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *arch_name(void)
|
||||||
|
{
|
||||||
|
return "arm (cortex-a9, zynq-7000)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_halt(void)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
__asm__ volatile("wfi");
|
||||||
|
}
|
||||||
29
arch/arm/boot.S
Normal file
29
arch/arm/boot.S
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
@ boot.S (ARM, Cortex-A9 / ARMv7-A) — minimal entry for the Zynq PS.
|
||||||
|
@ QEMU 'xilinx-zynq-a9' enters at the image load address. We set a stack,
|
||||||
|
@ clear .bss, and call kmain(). Irreducible asm part for ARM.
|
||||||
|
|
||||||
|
.section .text.boot
|
||||||
|
.global _start
|
||||||
|
_start:
|
||||||
|
@ Park secondary CPUs: only CPU0 continues.
|
||||||
|
mrc p15, 0, r1, c0, c0, 5 @ read MPIDR
|
||||||
|
and r1, r1, #3 @ CPU id (affinity level 0)
|
||||||
|
cmp r1, #0
|
||||||
|
bne park
|
||||||
|
|
||||||
|
ldr sp, =_stack_top @ set up the stack
|
||||||
|
|
||||||
|
@ Zero .bss: [__bss_start, __bss_end)
|
||||||
|
ldr r0, =__bss_start
|
||||||
|
ldr r1, =__bss_end
|
||||||
|
mov r2, #0
|
||||||
|
1: cmp r0, r1
|
||||||
|
bge 2f
|
||||||
|
str r2, [r0], #4
|
||||||
|
b 1b
|
||||||
|
2:
|
||||||
|
bl kmain
|
||||||
|
|
||||||
|
park:
|
||||||
|
wfi
|
||||||
|
b park
|
||||||
28
arch/arm/link.ld
Normal file
28
arch/arm/link.ld
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* link.ld (ARM, Zynq-7000) — DDR starts at 0x00000000 on Zynq. QEMU
|
||||||
|
* xilinx-zynq-a9 loads the -kernel image; place us at a safe low DDR
|
||||||
|
* address above the vector/OCM region used by the model. */
|
||||||
|
OUTPUT_FORMAT(elf32-littlearm)
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 0x00100000; /* 1 MiB into DDR */
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
KEEP(*(.text.boot))
|
||||||
|
*(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : { *(.rodata .rodata.*) }
|
||||||
|
.data : { *(.data .data.*) }
|
||||||
|
|
||||||
|
. = ALIGN(8);
|
||||||
|
__bss_start = .;
|
||||||
|
.bss : { *(.bss .bss.*) *(COMMON) }
|
||||||
|
. = ALIGN(8);
|
||||||
|
__bss_end = .;
|
||||||
|
|
||||||
|
. = ALIGN(16);
|
||||||
|
. += 0x4000; /* 16 KiB stack */
|
||||||
|
_stack_top = .;
|
||||||
|
}
|
||||||
16
arch/arm/vectors.S
Normal file
16
arch/arm/vectors.S
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
@ vectors.S (ARMv7-A) — the exception vector table VBAR points at.
|
||||||
|
@ M1 keeps every entry a self-loop; M2 will wire IRQ to a C handler
|
||||||
|
@ via the GIC. The 8 standard ARM vectors, in order.
|
||||||
|
|
||||||
|
.section .text
|
||||||
|
.align 5 @ vector base must be 32-byte aligned
|
||||||
|
.global vector_table
|
||||||
|
vector_table:
|
||||||
|
b . @ 0x00 Reset
|
||||||
|
b . @ 0x04 Undefined instruction
|
||||||
|
b . @ 0x08 Supervisor Call (SVC)
|
||||||
|
b . @ 0x0C Prefetch Abort
|
||||||
|
b . @ 0x10 Data Abort
|
||||||
|
b . @ 0x14 (reserved)
|
||||||
|
b . @ 0x18 IRQ
|
||||||
|
b . @ 0x1C FIQ
|
||||||
47
arch/riscv/arch.c
Normal file
47
arch/riscv/arch.c
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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"
|
||||||
|
|
||||||
|
#define UART0_BASE 0x10000000UL
|
||||||
|
#define UART_THR 0x00 /* transmit holding register */
|
||||||
|
#define UART_LSR 0x05 /* line status register */
|
||||||
|
#define UART_LSR_THRE 0x20 /* THR empty */
|
||||||
|
|
||||||
|
static volatile unsigned char *const uart =
|
||||||
|
(volatile unsigned char *)UART0_BASE;
|
||||||
|
|
||||||
|
void arch_early_init(void)
|
||||||
|
{
|
||||||
|
/* QEMU's UART is usable from reset; nothing to do. */
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_uart_putc(char c)
|
||||||
|
{
|
||||||
|
while ((uart[UART_LSR] & UART_LSR_THRE) == 0)
|
||||||
|
;
|
||||||
|
uart[UART_THR] = (unsigned char)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
return "riscv64 (rv64imac)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_halt(void)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
__asm__ volatile("wfi");
|
||||||
|
}
|
||||||
28
arch/riscv/boot.S
Normal file
28
arch/riscv/boot.S
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# boot.S (RISC-V rv64) — the minimal asm entry.
|
||||||
|
# QEMU 'virt' with -bios none enters here at 0x80000000 in M-mode,
|
||||||
|
# all harts. We park every hart but hart 0, set a stack, clear .bss,
|
||||||
|
# and jump to kmain(). This is the irreducible asm part for this arch.
|
||||||
|
|
||||||
|
.section .text.boot
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
# Only hart 0 continues; others wait forever (WFI loop).
|
||||||
|
csrr t0, mhartid
|
||||||
|
bnez t0, park
|
||||||
|
|
||||||
|
# Set up the stack (defined in the linker script).
|
||||||
|
la sp, _stack_top
|
||||||
|
|
||||||
|
# Zero the .bss section: [__bss_start, __bss_end).
|
||||||
|
la t0, __bss_start
|
||||||
|
la t1, __bss_end
|
||||||
|
1: bgeu t0, t1, 2f
|
||||||
|
sd zero, (t0)
|
||||||
|
addi t0, t0, 8
|
||||||
|
j 1b
|
||||||
|
2:
|
||||||
|
call kmain
|
||||||
|
|
||||||
|
park:
|
||||||
|
wfi
|
||||||
|
j park
|
||||||
27
arch/riscv/link.ld
Normal file
27
arch/riscv/link.ld
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* link.ld (RISC-V) — QEMU 'virt' loads the kernel at 0x80000000. */
|
||||||
|
OUTPUT_ARCH(riscv)
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 0x80000000;
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
KEEP(*(.text.boot)) /* _start must be first */
|
||||||
|
*(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : { *(.rodata .rodata.*) }
|
||||||
|
.data : { *(.data .data.*) }
|
||||||
|
|
||||||
|
. = ALIGN(8);
|
||||||
|
__bss_start = .;
|
||||||
|
.bss : { *(.bss .bss.*) *(COMMON) }
|
||||||
|
. = ALIGN(8);
|
||||||
|
__bss_end = .;
|
||||||
|
|
||||||
|
/* 16 KiB boot stack */
|
||||||
|
. = ALIGN(16);
|
||||||
|
. += 0x4000;
|
||||||
|
_stack_top = .;
|
||||||
|
}
|
||||||
10
arch/riscv/trap.S
Normal file
10
arch/riscv/trap.S
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# trap.S (RISC-V) — the machine-mode trap entry the M1 mtvec points at.
|
||||||
|
# M1 keeps it trivial: just loop. M2 will save registers and dispatch to
|
||||||
|
# a C handler for the timer interrupt.
|
||||||
|
|
||||||
|
.section .text
|
||||||
|
.globl trap_entry
|
||||||
|
.align 4 # mtvec base must be 4-byte aligned
|
||||||
|
trap_entry:
|
||||||
|
wfi
|
||||||
|
j trap_entry
|
||||||
69
arch/x86/arch.c
Normal file
69
arch/x86/arch.c
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* arch/x86/arch.c — i386 implementation of the arch contract.
|
||||||
|
* Uses the legacy 16550 UART on COM1 (port 0x3F8), which QEMU wires to
|
||||||
|
* the -nographic serial. Port-mapped I/O — the CISC flavor. */
|
||||||
|
#include "arch.h"
|
||||||
|
#include "console.h"
|
||||||
|
|
||||||
|
#define COM1 0x3F8
|
||||||
|
|
||||||
|
static inline void outb(unsigned short port, unsigned char val)
|
||||||
|
{
|
||||||
|
__asm__ volatile("outb %0, %1" :: "a"(val), "Nd"(port));
|
||||||
|
}
|
||||||
|
static inline unsigned char inb(unsigned short port)
|
||||||
|
{
|
||||||
|
unsigned char r;
|
||||||
|
__asm__ volatile("inb %1, %0" : "=a"(r) : "Nd"(port));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_early_init(void)
|
||||||
|
{
|
||||||
|
/* Program COM1: 115200 8N1, FIFO on. */
|
||||||
|
outb(COM1 + 1, 0x00); /* disable interrupts */
|
||||||
|
outb(COM1 + 3, 0x80); /* DLAB on */
|
||||||
|
outb(COM1 + 0, 0x01); /* divisor lo -> 115200 */
|
||||||
|
outb(COM1 + 1, 0x00); /* divisor hi */
|
||||||
|
outb(COM1 + 3, 0x03); /* 8N1, DLAB off */
|
||||||
|
outb(COM1 + 2, 0xC7); /* enable+clear FIFO */
|
||||||
|
outb(COM1 + 4, 0x0B); /* RTS/DSR set */
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_uart_putc(char c)
|
||||||
|
{
|
||||||
|
while ((inb(COM1 + 5) & 0x20) == 0) /* wait THR empty */
|
||||||
|
;
|
||||||
|
outb(COM1, (unsigned char)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* M1: minimal IDT. We load a valid-but-empty IDT so the CPU has a
|
||||||
|
* table; M2 will fill gates and handle the timer (IRQ0). */
|
||||||
|
struct idt_entry {
|
||||||
|
unsigned short off_lo, sel;
|
||||||
|
unsigned char zero, flags;
|
||||||
|
unsigned short off_hi;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct idt_ptr {
|
||||||
|
unsigned short limit;
|
||||||
|
unsigned int base;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static struct idt_entry idt[256];
|
||||||
|
|
||||||
|
void arch_set_trap_vector(void)
|
||||||
|
{
|
||||||
|
struct idt_ptr ptr = { sizeof(idt) - 1, (unsigned int)(unsigned long)idt };
|
||||||
|
__asm__ volatile("lidt %0" :: "m"(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *arch_name(void)
|
||||||
|
{
|
||||||
|
return "x86 (i386, multiboot)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_halt(void)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
__asm__ volatile("hlt");
|
||||||
|
}
|
||||||
28
arch/x86/boot.S
Normal file
28
arch/x86/boot.S
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# boot.S (x86, i386) — multiboot header + entry.
|
||||||
|
# The Multiboot1 header lets QEMU -kernel (and GRUB) load us directly.
|
||||||
|
# We set a stack and call kmain(). Irreducible asm part for x86.
|
||||||
|
|
||||||
|
.set MB_MAGIC, 0x1BADB002
|
||||||
|
.set MB_FLAGS, 0x0
|
||||||
|
.set MB_CHECKSUM, -(MB_MAGIC + MB_FLAGS)
|
||||||
|
|
||||||
|
.section .multiboot, "a"
|
||||||
|
.align 4
|
||||||
|
.long MB_MAGIC
|
||||||
|
.long MB_FLAGS
|
||||||
|
.long MB_CHECKSUM
|
||||||
|
|
||||||
|
.section .bss
|
||||||
|
.align 16
|
||||||
|
stack_bottom:
|
||||||
|
.skip 0x4000 # 16 KiB stack
|
||||||
|
stack_top:
|
||||||
|
|
||||||
|
.section .text.boot
|
||||||
|
.globl _start
|
||||||
|
.type _start, @function
|
||||||
|
_start:
|
||||||
|
mov $stack_top, %esp # set up the stack
|
||||||
|
call kmain
|
||||||
|
1: hlt # kmain shouldn't return; if it does, halt
|
||||||
|
jmp 1b
|
||||||
19
arch/x86/link.ld
Normal file
19
arch/x86/link.ld
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* link.ld (i386) — multiboot kernels load at 1 MiB. The multiboot
|
||||||
|
* header must appear within the first 8 KiB, so put it first. */
|
||||||
|
OUTPUT_FORMAT(elf32-i386)
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 0x00100000; /* 1 MiB */
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
*(.multiboot)
|
||||||
|
KEEP(*(.text.boot))
|
||||||
|
*(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : { *(.rodata .rodata.*) }
|
||||||
|
.data : { *(.data .data.*) }
|
||||||
|
.bss : { *(.bss .bss.*) *(COMMON) }
|
||||||
|
}
|
||||||
27
common/console.c
Normal file
27
common/console.c
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* console.c — portable text output built on arch_uart_putc().
|
||||||
|
* No libc: we implement the tiny bit of formatting we need. */
|
||||||
|
#include "arch.h"
|
||||||
|
#include "console.h"
|
||||||
|
|
||||||
|
void putc(char c)
|
||||||
|
{
|
||||||
|
if (c == '\n')
|
||||||
|
arch_uart_putc('\r'); /* CRLF for dumb terminals */
|
||||||
|
arch_uart_putc(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void puts(const char *s)
|
||||||
|
{
|
||||||
|
while (*s)
|
||||||
|
putc(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimal unsigned hex printer — enough to show addresses/registers. */
|
||||||
|
void puthex(unsigned long v)
|
||||||
|
{
|
||||||
|
static const char d[] = "0123456789abcdef";
|
||||||
|
int i;
|
||||||
|
puts("0x");
|
||||||
|
for (i = (int)(sizeof(v) * 2) - 1; i >= 0; i--)
|
||||||
|
putc(d[(v >> (i * 4)) & 0xf]);
|
||||||
|
}
|
||||||
26
common/main.c
Normal file
26
common/main.c
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/* main.c — the arch-agnostic OS entry point.
|
||||||
|
*
|
||||||
|
* By the time kmain() runs, the arch's asm entry has: set up a stack,
|
||||||
|
* cleared .bss (or the arch stub did), and jumped here. Everything below
|
||||||
|
* is identical C for x86, RISC-V and ARM — that's the whole point of the
|
||||||
|
* exercise: same core, three ISAs. */
|
||||||
|
#include "arch.h"
|
||||||
|
#include "console.h"
|
||||||
|
|
||||||
|
void kmain(void)
|
||||||
|
{
|
||||||
|
arch_early_init();
|
||||||
|
arch_set_trap_vector();
|
||||||
|
|
||||||
|
puts("\n");
|
||||||
|
puts("========================================\n");
|
||||||
|
puts(" minos — minimal multi-arch kernel\n");
|
||||||
|
puts(" arch: ");
|
||||||
|
puts(arch_name());
|
||||||
|
puts("\n");
|
||||||
|
puts(" M1: booted, UART up, trap vector set\n");
|
||||||
|
puts("========================================\n");
|
||||||
|
puts("hello from kmain()\n");
|
||||||
|
|
||||||
|
arch_halt();
|
||||||
|
}
|
||||||
29
include/arch.h
Normal file
29
include/arch.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* arch.h — the per-arch contract.
|
||||||
|
*
|
||||||
|
* The common C core is arch-agnostic: it only calls these functions.
|
||||||
|
* Each arch/<name>/ provides an implementation. This is the whole
|
||||||
|
* "boundary" between the minimal asm/glue and the portable C.
|
||||||
|
*/
|
||||||
|
#ifndef MINOS_ARCH_H
|
||||||
|
#define MINOS_ARCH_H
|
||||||
|
|
||||||
|
/* Early, arch-specific bring-up done before kmain() prints anything:
|
||||||
|
* e.g. point UART at the right MMIO/port. Called from the asm entry's
|
||||||
|
* C landing pad, or the very top of kmain(). */
|
||||||
|
void arch_early_init(void);
|
||||||
|
|
||||||
|
/* Emit one byte to the platform's debug UART. The console layer in
|
||||||
|
* common/console.c builds print()/puts() on top of just this. */
|
||||||
|
void arch_uart_putc(char c);
|
||||||
|
|
||||||
|
/* Install the interrupt/trap vector table (IDT / mtvec / VBAR).
|
||||||
|
* M1 stubs this; M2 makes it handle a timer tick. */
|
||||||
|
void arch_set_trap_vector(void);
|
||||||
|
|
||||||
|
/* Name of the architecture, for the banner. */
|
||||||
|
const char *arch_name(void);
|
||||||
|
|
||||||
|
/* Halt the CPU (wfi / hlt) — end of kmain(). */
|
||||||
|
void arch_halt(void) __attribute__((noreturn));
|
||||||
|
|
||||||
|
#endif /* MINOS_ARCH_H */
|
||||||
8
include/console.h
Normal file
8
include/console.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef MINOS_CONSOLE_H
|
||||||
|
#define MINOS_CONSOLE_H
|
||||||
|
|
||||||
|
void putc(char c);
|
||||||
|
void puts(const char *s);
|
||||||
|
void puthex(unsigned long v);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Add table
Add a link
Reference in a new issue