diff --git a/.vscode/settings.json b/.vscode/settings.json index d8de0342..752dc8b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "-c", "if [ \"$(basename $(pwd))\" = \"fs-fuse\" ]; then target=\"x86_64-unknown-linux-gnu\"; else target=\"riscv64gc-unknown-none-elf\"; fi; cargo clippy --target $target --workspace --message-format=json --message-format=json-diagnostic-rendered-ansi" ], + "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", "rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave": true, "files.watcherExclude": { diff --git a/Makefile b/Makefile index a44b8857..c151b152 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,10 @@ ARCH ?= rv TARGET ?= riscv64gc-unknown-none-elf USER_MODE ?= release USER_BIN_DIR := user/target/$(TARGET)/$(USER_MODE) +USER_BIN_DIR_RV := user/target/riscv64gc-unknown-none-elf/$(USER_MODE) +USER_BIN_DIR_LA := user/target/loongarch64-unknown-none/$(USER_MODE) KERNEL_RV_ELF := os/target/$(TARGET)/release/os +KERNEL_LA_ELF := os/target/loongarch64-unknown-none/release/os QEMU_RV ?= qemu-system-riscv64 QEMU_LA ?= qemu-system-loongarch64 MEM ?= 1G @@ -12,16 +15,22 @@ SMP ?= 1 TEST_FS ?= sdcard-$(ARCH).img # make run 使用写时复制副本,避免 QEMU 写坏原始测试镜像。 RUN_TEST_FS ?= .make/sdcard-$(ARCH)-run.img +TEST_FS_LA ?= sdcard-la.img +RUN_TEST_FS_LA ?= .make/sdcard-la-run.img QEMU_NETDEV ?= user,id=net QEMU_TRACE_ARGS ?= QEMU_COMP_BLK_ARGS = -drive file=$(RUN_TEST_FS),if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 QEMU_COMP_EXTRA_BLK_ARGS = -drive file=$(RUN_DISK_IMG),if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 STAMP_DIR := .make -USER_BUILD_STAMP := $(STAMP_DIR)/user-build.stamp -KERNEL_BUILD_STAMP := $(STAMP_DIR)/kernel-build.stamp +USER_BUILD_STAMP_RV := $(USER_BIN_DIR_RV)/.xxos-build.stamp +USER_BUILD_STAMP_LA := $(USER_BIN_DIR_LA)/.xxos-build.stamp +KERNEL_BUILD_STAMP_RV := $(STAMP_DIR)/kernel-build-rv.stamp +KERNEL_BUILD_STAMP_LA := $(STAMP_DIR)/kernel-build-la.stamp USER_BUILD_DEPS := user/Makefile user/Cargo.toml $(shell find user/src -type f | sort) KERNEL_BUILD_DEPS := os/Makefile os/Cargo.toml os/build.rs $(shell find os/src fs/src -type f | sort) +LA_BOOTLOADER_DIR ?= bootloader/loongarch64-direct +LA_BOOTLOADER_DEPS := $(LA_BOOTLOADER_DIR)/Cargo.toml $(LA_BOOTLOADER_DIR)/Cargo.lock $(LA_BOOTLOADER_DIR)/build.rs $(LA_BOOTLOADER_DIR)/linker.ld $(shell find $(LA_BOOTLOADER_DIR)/src -type f | sort) ROOTFS_REPO := CosmOS-rootfs ROOTFS_BASE_DIR := $(ROOTFS_REPO)/rootfs ROOTFS_RV_DIR := $(ROOTFS_REPO)/rootfs-rv @@ -34,6 +43,8 @@ ROOTFS_RV_FILES := $(shell if [ -d $(ROOTFS_RV_DIR) ]; then find $(ROOTFS_RV_DIR ROOTFS_LA_FILES := $(shell if [ -d $(ROOTFS_LA_DIR) ]; then find $(ROOTFS_LA_DIR) -type f | sort; fi) DISK_RV_IMG := disk.img DISK_LA_IMG := disk-la.img +QEMU_LA_BLK_ARGS = -drive file=$(RUN_TEST_FS_LA),if=none,format=raw,id=x0 -device virtio-blk-pci,drive=x0,id=x0 +QEMU_LA_EXTRA_BLK_ARGS = -drive file=$(DISK_LA_IMG),if=none,format=raw,id=x1 -device virtio-blk-pci,drive=x1,id=x1 RV_ROOTFS_TARGET ?= riscv64-linux-musl RV_TOOLCHAIN_BIN ?= /opt/riscv64-linux-musl-cross/bin RV_GLIBC_LIB ?= /usr/riscv64-linux-gnu/lib @@ -46,6 +57,10 @@ LA_GLIBC_TOOLCHAIN ?= /opt/gcc-13.2.0-loongarch64-linux-gnu LA_MUSL_LIB ?= /opt/loongarch64-linux-musl-cross/loongarch64-linux-musl/lib LA_MUSL_ARCH ?= loongarch64 LA_MUSL_LOADER_ALIASES ?= ld-musl-loongarch64.so.1 +LA_BOOTLOADER_ELF ?= $(LA_BOOTLOADER_DIR)/target/loongarch64-unknown-none/release/loongarch64-direct-boot +LA_KERNEL_ENTRY_PA ?= 0x90000000 +MEM_LA ?= 1G +QEMU_LA_NETDEV ?= user,id=net0 OPTIONAL_RUNTIME_FILES := $(wildcard lib/musl/ar lib/glibc/ar) ifeq ($(ARCH),rv) @@ -60,12 +75,12 @@ else $(error unsupported ARCH=$(ARCH), expected rv or la) endif -.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs sync-rootfs-variants rootfs-rv rootfs-la rv la disk-rv disk-la clean run run-trace run-comp-rv run-comp-la debug gdbserver gdbclient check-kernel check-user-apps check-rootfs check-rootfs-rv check-rootfs-la check-rootfs-rv-ready check-rootfs-la-ready prepare-run-test-fs force +.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs sync-rootfs-variants rootfs-rv rootfs-la rv la disk-rv disk-la clean run run-trace run-comp-rv run-comp-la fast-run fast-run-la clean-all debug gdbserver gdbclient check-kernel check-user-apps check-rootfs check-rootfs-rv check-rootfs-la check-rootfs-rv-ready check-rootfs-la-ready prepare-run-test-fs prepare-run-test-fs-la force all: $(MAKE) submodules $(MAKE) cargo-config - $(MAKE) user-apps kernel-rv kernel-la $(DISK_RV_IMG) $(DISK_LA_IMG) + $(MAKE) user-apps user-apps-la kernel-rv kernel-la $(DISK_RV_IMG) $(DISK_LA_IMG) # 拉取所有子模块,确保后续构建依赖完整。 submodules: @@ -84,25 +99,35 @@ cargo-config: $(STAMP_DIR): mkdir -p $@ -$(USER_BUILD_STAMP): $(USER_BUILD_DEPS) | $(STAMP_DIR) cargo-config - $(MAKE) -C user build +$(USER_BUILD_STAMP_RV): $(USER_BUILD_DEPS) + $(MAKE) -C user build ARCH=riscv64 touch $@ -user-apps: $(USER_BUILD_STAMP) +$(USER_BUILD_STAMP_LA): $(USER_BUILD_DEPS) + $(MAKE) -C user build ARCH=loongarch64 + touch $@ + +user-apps: $(USER_BUILD_STAMP_RV) +user-apps-la: $(USER_BUILD_STAMP_LA) + +$(KERNEL_BUILD_STAMP_RV): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C os kernel ARCH=riscv64 + touch $@ -$(KERNEL_BUILD_STAMP): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) cargo-config - $(MAKE) -C os kernel +$(KERNEL_BUILD_STAMP_LA): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C os kernel ARCH=loongarch64 touch $@ -kernel-rv: $(KERNEL_BUILD_STAMP) +kernel-rv: $(KERNEL_BUILD_STAMP_RV) cp $(KERNEL_RV_ELF) $@ -kernel-la: kernel-rv - @echo "warning: LoongArch kernel is not implemented in this repository yet; using kernel-rv as a temporary placeholder." >&2 - cp kernel-rv $@ +kernel-la: $(KERNEL_BUILD_STAMP_LA) + cp $(KERNEL_LA_ELF) $@ -rootfs: - $(MAKE) -C $(ROOTFS_REPO) rootfs-init +$(ROOTFS_STAMP): $(ROOTFS_SRC_FILES) | $(STAMP_DIR) + rm -rf $(ROOTFS_DIR) + cp -a $(ROOTFS_SRC_DIR)/rootfs/. $(ROOTFS_DIR)/ + touch $@ sync-rootfs-variants: @test -d "$(ROOTFS_BASE_DIR)" || { \ @@ -155,19 +180,31 @@ check-kernel: $(RUN_KERNEL) exit 1; \ } -check-user-apps: - @test -d "$(USER_BIN_DIR)" || { \ - echo "missing user binaries in $(USER_BIN_DIR); run 'make all' first" >&2; \ +check-kernel-la: + @test -x kernel-la || { \ + echo "missing kernel-la; run 'make all' first" >&2; \ + exit 1; \ + } + +check-user-apps-rv: user-apps + @test -d "$(USER_BIN_DIR_RV)" || { \ + echo "missing user binaries in $(USER_BIN_DIR_RV); run 'make user-apps' first" >&2; \ + exit 1; \ + } + +check-user-apps-la: user-apps-la + @test -d "$(USER_BIN_DIR_LA)" || { \ + echo "missing user binaries in $(USER_BIN_DIR_LA); run 'make user-apps-la' first" >&2; \ exit 1; \ } check-rootfs: rootfs - @test -d "$(ROOTFS_BASE_DIR)" || { \ - echo "missing rootfs directory $(ROOTFS_BASE_DIR); run 'make all' first" >&2; \ + @test -d "$(ROOTFS_DIR)" || { \ + echo "missing rootfs directory $(ROOTFS_DIR); run 'make all' first" >&2; \ exit 1; \ } - @test -d "$(ROOTFS_BASE_DIR)/root" || { \ - echo "rootfs is incomplete under $(ROOTFS_BASE_DIR); run 'make all' first" >&2; \ + @test -d "$(ROOTFS_DIR)/root" || { \ + echo "rootfs is incomplete under $(ROOTFS_DIR); run 'make all' first" >&2; \ exit 1; \ } @@ -211,14 +248,17 @@ check-rootfs-la-ready: exit 1; \ } -$(DISK_RV_IMG): force check-user-apps rootfs-rv check-rootfs-rv-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh - MUSL_ARCH=$(RV_MUSL_ARCH) MUSL_LOADER_ALIASES="$(RV_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_RV_DIR) $(USER_BIN_DIR) $@ +$(DISK_RV_IMG): force check-user-apps-rv rootfs-rv check-rootfs-rv-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh + MUSL_ARCH=$(RV_MUSL_ARCH) MUSL_LOADER_ALIASES="$(RV_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_RV_DIR) $(USER_BIN_DIR_RV) $@ -$(DISK_LA_IMG): force check-user-apps rootfs-la check-rootfs-la-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh - MUSL_ARCH=$(LA_MUSL_ARCH) MUSL_LOADER_ALIASES="$(LA_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_LA_DIR) $(USER_BIN_DIR) $@ +$(DISK_LA_IMG): force check-user-apps-la rootfs-la check-rootfs-la-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh + MUSL_ARCH=$(LA_MUSL_ARCH) MUSL_LOADER_ALIASES="$(LA_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_LA_DIR) $(USER_BIN_DIR_LA) $@ force: +$(LA_BOOTLOADER_ELF): $(LA_BOOTLOADER_DEPS) + cd $(LA_BOOTLOADER_DIR) && cargo build --release + prepare-run-test-fs: | $(STAMP_DIR) @if [ ! -f "$(TEST_FS)" ]; then \ echo "Test image not found: $(TEST_FS)"; \ @@ -226,9 +266,25 @@ prepare-run-test-fs: | $(STAMP_DIR) fi cp -c "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp --reflink=auto "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp "$(TEST_FS)" "$(RUN_TEST_FS)" +prepare-run-test-fs-la: | $(STAMP_DIR) + @if [ ! -f "$(TEST_FS_LA)" ]; then \ + echo "Test image not found: $(TEST_FS_LA)"; \ + exit 2; \ + fi + cp -c "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp --reflink=auto "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" + run: check-kernel $(RUN_DISK_IMG) prepare-run-test-fs $(QEMU) -machine virt -kernel $(RUN_KERNEL) -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) +run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(DISK_LA_IMG) prepare-run-test-fs-la + $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) + +fast-run: check-kernel + $(QEMU) -machine virt -kernel kernel-rv -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) + +fast-run-la: check-kernel-la $(LA_BOOTLOADER_ELF) + $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) + run-trace: QEMU_TRACE_ARGS = -d int,in_asm -D qemu.log run-trace: run @@ -257,6 +313,9 @@ fmt: cd fs; cargo fmt; cd ../fs-fuse; cargo fmt; cd ../os; cargo fmt; cd ../user; cargo fmt; cd .. clean: - rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_BUILD_DIR) $(ROOTFS_LA_BUILD_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR) + rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(MAKE) -C os clean $(MAKE) -C user clean + +clean-all: clean + rm -rf $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_BUILD_DIR) $(ROOTFS_LA_BUILD_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR) diff --git a/bootloader/loongarch64-direct/.cargo/config.toml b/bootloader/loongarch64-direct/.cargo/config.toml new file mode 100644 index 00000000..e3cb4e94 --- /dev/null +++ b/bootloader/loongarch64-direct/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "loongarch64-unknown-none" + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-arg=-Tlinker.ld", +] + diff --git a/bootloader/loongarch64-direct/Cargo.lock b/bootloader/loongarch64-direct/Cargo.lock new file mode 100644 index 00000000..f6622164 --- /dev/null +++ b/bootloader/loongarch64-direct/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "loongarch64-direct-boot" +version = "0.1.0" diff --git a/bootloader/loongarch64-direct/Cargo.toml b/bootloader/loongarch64-direct/Cargo.toml new file mode 100644 index 00000000..136d476f --- /dev/null +++ b/bootloader/loongarch64-direct/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "loongarch64-direct-boot" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[profile.release] +opt-level = "z" +lto = true +strip = true +panic = "abort" diff --git a/bootloader/loongarch64-direct/build.rs b/bootloader/loongarch64-direct/build.rs new file mode 100644 index 00000000..f94aceb3 --- /dev/null +++ b/bootloader/loongarch64-direct/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tlinker.ld"); + println!("cargo:rerun-if-changed=linker.ld"); + println!("cargo:rerun-if-changed=src/main.rs"); +} diff --git a/bootloader/loongarch64-direct/linker.ld b/bootloader/loongarch64-direct/linker.ld new file mode 100644 index 00000000..178f4a7f --- /dev/null +++ b/bootloader/loongarch64-direct/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) + +BASE_ADDRESS = 0x81000000; + +SECTIONS +{ + . = BASE_ADDRESS; + + .text : ALIGN(16) { + *(.text.entry) + *(.text .text.*) + } + + .rodata : ALIGN(16) { + *(.rodata .rodata.*) + } + + .data : ALIGN(16) { + *(.data .data.*) + } + + .bss : ALIGN(16) { + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.debug*) + } +} diff --git a/bootloader/loongarch64-direct/src/main.rs b/bootloader/loongarch64-direct/src/main.rs new file mode 100644 index 00000000..547117f1 --- /dev/null +++ b/bootloader/loongarch64-direct/src/main.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![feature(naked_functions)] +use core::arch::{asm, naked_asm}; +use core::panic::PanicInfo; + +const UART_BASE: usize = 0x1fe0_01e0; +const IO_OFFSET: usize = 0x8000_0000_0000_0000; +const UART_THR: usize = 0x00; +const UART_LSR: usize = 0x05; +const UART_LSR_THRE: u8 = 1 << 5; +const KERNEL_ENTRY: usize = 0x9000_0000_9000_0000; + +#[unsafe(no_mangle)] +static mut BOOT_STACK: [u8; 4096] = [0; 4096]; + +#[naked] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _start() -> ! { + naked_asm!( + // Setup DMW0: UC window 0x8000xxxxxxxxxxxx → PLV0 + "ori $t0, $zero, 0x1", + "lu52i.d $t0, $t0, -2048", + "csrwr $t0, 0x180", + // Setup DMW1: CA window 0x9000xxxxxxxxxxxx → PLV0 + "ori $t0, $zero, 0x11", + "lu52i.d $t0, $t0, -1792", + "csrwr $t0, 0x181", + // Setup stack + "la.global $t0, {stack}", + "ori $t1, $zero, 2048", + "add.d $sp, $t0, $t1", + "add.d $sp, $sp, $t1", + "csrrd $a0, 0x20", + "b {main}", + stack = sym BOOT_STACK, + main = sym boot_main, + ) +} + +#[unsafe(no_mangle)] +extern "C" fn boot_main(hart_id: usize) -> ! { + if hart_id == 0 { + puts("[boot] loongarch64 direct loader\r\n"); + puts("[boot] jumping to kernel @ 0x90000000\r\n"); + } + unsafe { + let entry: extern "C" fn(usize) -> ! = core::mem::transmute(KERNEL_ENTRY); + entry(hart_id); + } +} + +fn puts(s: &str) { + for byte in s.bytes() { + putc(byte); + } +} + +fn putc(byte: u8) { + unsafe { + let uart = IO_OFFSET | UART_BASE; + while (mmio_read8(uart + UART_LSR) & UART_LSR_THRE) == 0 {} + mmio_write8(uart + UART_THR, byte); + } +} + +unsafe fn mmio_read8(addr: usize) -> u8 { + core::ptr::read_volatile(addr as *const u8) +} + +unsafe fn mmio_write8(addr: usize, value: u8) { + core::ptr::write_volatile(addr as *mut u8, value) +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + puts("[boot] panic\r\n"); + loop { + unsafe { asm!("idle 0", options(nomem, nostack)) }; + } +} diff --git a/fs/src/ext4_rs b/fs/src/ext4_rs index 016bbbba..d396d88e 160000 --- a/fs/src/ext4_rs +++ b/fs/src/ext4_rs @@ -1 +1 @@ -Subproject commit 016bbbba3bb2670472eeda35b3b497c6ff2222ef +Subproject commit d396d88e9ea206ed137aaa0c41fe5ab9bbf6012b diff --git a/os/Cargo.toml b/os/Cargo.toml index 7bc14e8c..8c9dd06f 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -31,4 +31,5 @@ strum_macros = "0.28.0" fat32 = [] easyfs = [] ext4 = [] -default = ["ext4"] +platform-qemu-virt = [] +default = ["ext4", "platform-qemu-virt"] diff --git a/os/Makefile b/os/Makefile index 4dd55fd3..7f786a80 100644 --- a/os/Makefile +++ b/os/Makefile @@ -1,5 +1,36 @@ # Building -TARGET := riscv64gc-unknown-none-elf +ARCH ?= riscv64 +ARCH_REQUEST := $(strip $(or $(target),$(ARCH),riscv64)) +ARCH_NORMALIZED := $(shell printf '%s' "$(ARCH_REQUEST)" | tr '[:upper:]' '[:lower:]') + +ifneq ($(filter $(ARCH_NORMALIZED),riscv64 rv64 rv),) + ARCH := riscv64 +else ifneq ($(filter $(ARCH_NORMALIZED),loongarch64 la64 la),) + ARCH := loongarch64 +else +$(error Unsupported target '$(ARCH_REQUEST)'. Use riscv64/rv64/rv or loongarch64/la64/la) +endif + +ifeq ($(ARCH),loongarch64) + TARGET := loongarch64-unknown-none + QEMU ?= qemu-system-loongarch64 + OBJDUMP := rust-objdump --arch-name=loongarch64 + OBJCOPY := rust-objcopy --binary-architecture=loongarch64 + LOONGARCH_BIOS ?= /usr/local/share/qemu/edk2-loongarch64-code.fd + LOONGARCH_BOOT_MODE ?= direct + LOONGARCH_BOOTLOADER := ../bootloader/loongarch64-direct/target/loongarch64-unknown-none/release/loongarch64-direct-boot + BASE_IMG ?= ../sdcard-la.img + QEMU_MEMORY ?= 4G + GDB ?= loongarch64-unknown-elf-gdb +else + TARGET := riscv64gc-unknown-none-elf + QEMU ?= qemu-system-riscv64 + OBJDUMP := rust-objdump --arch-name=riscv64 + OBJCOPY := rust-objcopy --binary-architecture=riscv64 + BASE_IMG ?= ../sdcard-rv.img + QEMU_MEMORY ?= 2G + GDB ?= riscv64-unknown-elf-gdb +endif MODE ?= release KERNEL_ELF = target/$(TARGET)/$(MODE)/os KERNEL_BIN = $(KERNEL_ELF).bin @@ -10,18 +41,12 @@ FS_IMG ?= ../user/target/$(TARGET)/$(USER_MODE)/fs.img MAIN_FS := ext4 MEM ?= 1G SMP ?= 1 -# Optional prebuilt ext4 image path. When set and valid, fs-fuse clones this -# image and injects user apps into it. -BASE_IMG ?= ../sdcard-rv.img APPS := ../user/src/bin/* OFFLINE := # BOARD BOARD := qemu SBI ?= rustsbi - -# QEMU -QEMU ?= qemu-system-riscv64 QEMU_VERSION := $(shell $(QEMU) --version 2>/dev/null | head -n1 | sed -E 's/.*version ([0-9.]+).*/\1/') QEMU_MAJOR := $(shell echo "$(QEMU_VERSION)" | cut -d. -f1) @@ -42,11 +67,39 @@ QEMU_EXTRA_ARGS ?= MODE_ARG = $(if $(filter release,$(MODE)),--release) # KERNEL ENTRY -KERNEL_ENTRY_PA := 0x80200000 - -# Binutils -OBJDUMP := rust-objdump --arch-name=riscv64 -OBJCOPY := rust-objcopy --binary-architecture=riscv64 +KERNEL_ENTRY_PA := $(if $(filter $(ARCH),loongarch64),0x90000000,0x80200000) +DEBUG_MEMORY ?= 512M +NET_PORT_FORWARD ?= user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777,hostfwd=tcp::7778-:7778,hostfwd=udp::7778-:7778 +QEMU_EXTRA_DEVICES = \ + # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ + # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ + # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 + +ifeq ($(ARCH),loongarch64) +QEMU_COMMON_ARGS = -m $(QEMU_MEMORY) -machine virt -cpu la464 -smp $(SMP) -nographic +QEMU_NET_ARGS = \ + -netdev $(NET_PORT_FORWARD) \ + -device virtio-net-pci,netdev=net0,id=net0 +QEMU_BLK_ARGS = \ + -drive file=$(FS_IMG),if=none,format=raw,id=x0 \ + -device virtio-blk-pci,drive=x0,id=x0 +ifeq ($(LOONGARCH_BOOT_MODE),direct) +QEMU_BOOT_ARGS = -kernel $(LOONGARCH_BOOTLOADER) -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +else +QEMU_BOOT_ARGS = -bios $(LOONGARCH_BIOS) -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +endif +GDB_ARCH_EX = +else +QEMU_COMMON_ARGS = -m $(QEMU_MEMORY) -machine virt -smp $(SMP) -nographic +QEMU_NET_ARGS = \ + -netdev $(NET_PORT_FORWARD) \ + -device virtio-net-device,netdev=net0,id=net0 +QEMU_BLK_ARGS = \ + -drive file=$(FS_IMG),if=none,format=raw,id=x0 \ + -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +QEMU_BOOT_ARGS = -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) +GDB_ARCH_EX = -ex 'set arch riscv:rv64' +endif # Disassembly DISASM ?= -x @@ -54,13 +107,20 @@ DISASM ?= -x build: env kernel fs-img @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $(KERNEL_BIN) +ifeq ($(ARCH),loongarch64) +bootloader: + @cd ../bootloader/loongarch64-direct && cargo build --release +else +bootloader: + @true +endif + env: ifeq ($(OFFLINE),) - (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET) - # 评测镜像已预装 cargo-binutils;缺失时才尝试安装。 - command -v rust-objcopy >/dev/null || cargo install cargo-binutils - # rust-objcopy/rust-objdump 依赖 llvm-tools-preview。 - rustup component list --installed | grep -q "llvm-tools-preview" || rustup component add llvm-tools-preview + (rustup target list | grep "$(TARGET) (installed)") || rustup target add $(TARGET) + cargo install cargo-binutils + rustup component add rust-src + rustup component add llvm-tools-preview endif fs-img: @@ -80,20 +140,20 @@ fs-img: exit 2; \ fi; \ echo "Injecting user apps into base ext4 image: $(BASE_IMG)"; \ - cd ../fs-fuse && cargo run --release -- -s ../user/target/riscv64gc-unknown-none-elf/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS) --ext4-base $(BASE_IMG); \ + cd ../fs-fuse && cargo run --release -- -s ../user/target/$(TARGET)/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS) --ext4-base $(BASE_IMG); \ else \ rm -f $(FS_IMG); \ echo "Packing fresh $(MAIN_FS) image from user apps."; \ - cd ../fs-fuse && cargo run --release -- -s ../user/target/riscv64gc-unknown-none-elf/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS); \ + cd ../fs-fuse && cargo run --release -- -s ../user/target/$(TARGET)/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS); \ fi; \ fi kernel: @echo Platform: $(BOARD) ifeq ($(QEMU7_CFG),--cfg qemu7) - @LOG=$(LOG) cargo rustc $(MODE_ARG) --no-default-features --features $(MAIN_FS) -- --cfg qemu7 + @LOG=$(LOG) cargo rustc $(MODE_ARG) --target $(TARGET) --no-default-features --features $(MAIN_FS) -- --cfg qemu7 else - @LOG=$(LOG) cargo build $(MODE_ARG) --no-default-features --features $(MAIN_FS) + @LOG=$(LOG) cargo build $(MODE_ARG) --target $(TARGET) --no-default-features --features $(MAIN_FS) endif clean: @@ -108,9 +168,9 @@ disasm-vim: kernel @vim $(DISASM_TMP) @rm $(DISASM_TMP) -run: build run-inner +run: build bootloader run-inner -run-trace: build run-inner-trace +run-trace: build bootloader run-inner-trace fast-run: kernel @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $(KERNEL_BIN) @@ -118,54 +178,36 @@ fast-run: kernel run-inner: @$(QEMU) \ - -m $(MEM) \ - -machine virt \ - -smp $(SMP) \ - -nographic \ - -bios $(BOOTLOADER) \ - -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - -netdev user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777,hostfwd=tcp::7778-:7778,hostfwd=udp::7778-:7778 \ - -device virtio-net-device,netdev=net0,id=net0 \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ - # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ - # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ - # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 + $(QEMU_COMMON_ARGS) \ + $(QEMU_BOOT_ARGS) \ + $(QEMU_NET_ARGS) \ + $(QEMU_BLK_ARGS) # Uncomment above lines to add a second virtio-blk device (for testing mount/umount) run-inner-trace: @$(QEMU) \ - -m $(MEM) \ - -machine virt \ - -smp $(SMP) \ - -nographic \ - -bios $(BOOTLOADER) \ - -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - -netdev user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777 \ - -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ - -device virtio-net-device,netdev=net0,id=net0 \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ + $(QEMU_COMMON_ARGS) \ + $(QEMU_BOOT_ARGS) \ + $(QEMU_NET_ARGS) \ + $(QEMU_BLK_ARGS) \ -d int,in_asm \ - -D qemu.log \ - # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ - # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 -# Uncomment above lines to add a second virtio-blk device (for testing mount/umount) + -D qemu.log debug: MODE=debug -debug: build +debug: build bootloader @tmux new-session -d \ - "$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ - tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ + "$(QEMU) -m $(DEBUG_MEMORY) $(filter-out -m $(QEMU_MEMORY),$(QEMU_COMMON_ARGS)) $(QEMU_BOOT_ARGS) $(QEMU_NET_ARGS) $(QEMU_BLK_ARGS) -s -S" && \ + tmux split-window -h "$(GDB) -ex 'file $(KERNEL_ELF)' $(GDB_ARCH_EX) -ex 'target remote localhost:1234'" && \ tmux -2 attach-session -d gdbserver: MODE=debug -gdbserver: build - @$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ +gdbserver: build bootloader + @$(QEMU) -m $(DEBUG_MEMORY) $(filter-out -m $(QEMU_MEMORY),$(QEMU_COMMON_ARGS)) $(QEMU_BOOT_ARGS) $(QEMU_NET_ARGS) $(QEMU_BLK_ARGS) \ -s -S gdbclient: MODE=debug gdbclient: - @riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234' + @$(GDB) -ex 'file $(KERNEL_ELF)' $(GDB_ARCH_EX) -ex 'target remote localhost:1234' .PHONY: build env kernel clean disasm disasm-vim run run-trace fast-run run-inner fs-img debug gdbserver gdbclient diff --git a/os/build.rs b/os/build.rs index 5529b4fe..59d2cf9c 100644 --- a/os/build.rs +++ b/os/build.rs @@ -1,6 +1,9 @@ -static TARGET_PATH: &str = "../user/target/riscv64gc-unknown-none-elf/release/"; +fn target_path() -> String { + let target = std::env::var("TARGET").unwrap_or_else(|_| "riscv64gc-unknown-none-elf".to_string()); + format!("../user/target/{}/release/", target) +} fn main() { println!("cargo:rerun-if-changed=../user/src/"); - println!("cargo:rerun-if-changed={}", TARGET_PATH); + println!("cargo:rerun-if-changed={}", target_path()); } diff --git a/os/cargo-config/config.toml b/os/cargo-config/config.toml index 4275fcad..fb7c4f8e 100644 --- a/os/cargo-config/config.toml +++ b/os/cargo-config/config.toml @@ -3,5 +3,12 @@ target = "riscv64gc-unknown-none-elf" [target.riscv64gc-unknown-none-elf] rustflags = [ - "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes" + "-Clink-arg=-Tsrc/linker.ld", + "-Cforce-frame-pointers=yes", +] + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-arg=-Tsrc/linker-loongarch64.ld", + "-Cforce-frame-pointers=yes", ] diff --git a/os/src/arch/loongarch64/entry.S b/os/src/arch/loongarch64/entry.S new file mode 100644 index 00000000..9d2a1aa3 --- /dev/null +++ b/os/src/arch/loongarch64/entry.S @@ -0,0 +1,49 @@ + .section .text.entry + .equ BOOT_STACK_SHIFT, 20 + .equ BOOT_STACK_SIZE, 1 << BOOT_STACK_SHIFT + .equ BOOT_STACK_HARTS, 8 + + .globl _start +_start: + ori $t0, $zero, 0x1 + lu52i.d $t0, $t0, -2048 + csrwr $t0, 0x180 + ori $t0, $zero, 0x11 + lu52i.d $t0, $t0, -1792 + csrwr $t0, 0x181 + + li.w $t0, 0xb0 + csrwr $t0, 0x0 + li.w $t0, 0x00 + csrwr $t0, 0x1 + csrwr $t0, 0x2 + + csrrd $a0, 0x20 + # Do not branch to a numeric local label here: `uart_putc` expands its + # own `1:` label, and secondaries would jump into the middle of the + # bootstrap-hart UART polling sequence with an uninitialized `$t3`. + bnez $a0, .Lsecondary_start + + la.global $sp, boot_stack_lower_bound + li.d $t2, BOOT_STACK_SIZE + add.d $sp, $sp, $t2 + bl rust_main + +.Lsecondary_start: + la.global $sp, boot_stack_lower_bound + addi.d $t1, $a0, 1 + slli.d $t0, $t1, BOOT_STACK_SHIFT + add.d $sp, $sp, $t0 + bl rust_main + + # Keep early boot stacks out of `.bss`: secondary harts may start running + # while the bootstrap hart is still in the one-time memory-init path, so + # using a preloaded data section lets us rule out any interaction with + # zeroing or other `.bss`-specific handling during bring-up. + .section .data.bootstack + .balign 16 + .globl boot_stack_lower_bound +boot_stack_lower_bound: + .space BOOT_STACK_SIZE * BOOT_STACK_HARTS + .globl boot_stack_top +boot_stack_top: diff --git a/os/src/arch/loongarch64/entry.rs b/os/src/arch/loongarch64/entry.rs new file mode 100644 index 00000000..d1882b63 --- /dev/null +++ b/os/src/arch/loongarch64/entry.rs @@ -0,0 +1,5 @@ +//! LoongArch64 kernel entry assembly. + +use core::arch::global_asm; + +global_asm!(include_str!("entry.S")); diff --git a/os/src/arch/loongarch64/hart.rs b/os/src/arch/loongarch64/hart.rs new file mode 100644 index 00000000..93b2d81c --- /dev/null +++ b/os/src/arch/loongarch64/hart.rs @@ -0,0 +1,82 @@ +//! LoongArch64 hart-local register access. + +use core::arch::asm; + +use crate::hal::traits::HartId; + +const CSR_CPUID: usize = 0x20; +const CSR_CRMD: usize = 0x0; +const CSR_EUEN: usize = 0x2; +const CSR_TCFG: usize = 0x41; +const CSR_TICLR: usize = 0x44; +const CRMD_IE: usize = 1 << 2; +const EUEN_FPEN: usize = 1 << 0; +const TCFG_ENABLE: usize = 1 << 0; +const TCFG_PERIODIC: usize = 1 << 1; +const TICLR_CLEAR: usize = 1 << 0; + +/// LoongArch64 implementation of [`HartId`](crate::hal::traits::HartId). +pub struct LoongArchHartId; + +#[inline] +pub fn read_time() -> usize { + let time: usize; + unsafe { asm!("rdtime.d {}, $zero", out(reg) time) }; + time +} + +#[inline] +pub unsafe fn set_timer_deadline(deadline: usize) { + let now = read_time(); + // TCFG.InitVal requires a multiple-of-4 countdown value. + let delta = deadline.saturating_sub(now).max(4) & !0b11; + asm!( + "csrwr {clear}, {ticlr}", + "csrwr {tcfg}, {tcfg_num}", + clear = in(reg) TICLR_CLEAR, + tcfg = in(reg) (delta | TCFG_ENABLE), + ticlr = const CSR_TICLR, + tcfg_num = const CSR_TCFG, + ); +} + +impl HartId for LoongArchHartId { + fn current() -> usize { + let id: usize; + unsafe { asm!("csrrd {}, {}", out(reg) id, const CSR_CPUID) } + id + } + + unsafe fn init(_id: usize) {} + + unsafe fn enable_fp() { + let mut euen: usize; + asm!("csrrd {}, {}", out(reg) euen, const CSR_EUEN); + euen |= EUEN_FPEN; + asm!("csrwr {}, {}", in(reg) euen, const CSR_EUEN); + } + + fn irqs_enabled() -> bool { + let crmd: usize; + unsafe { asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD) }; + crmd & CRMD_IE != 0 + } + + unsafe fn disable_irqs() { + let mut crmd: usize; + asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD); + crmd &= !CRMD_IE; + asm!("csrwr {}, {}", in(reg) crmd, const CSR_CRMD); + } + + unsafe fn enable_irqs() { + let mut crmd: usize; + asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD); + crmd |= CRMD_IE; + asm!("csrwr {}, {}", in(reg) crmd, const CSR_CRMD); + } + + unsafe fn wait_for_interrupt() { + asm!("idle 0"); + } +} diff --git a/os/src/arch/loongarch64/mod.rs b/os/src/arch/loongarch64/mod.rs new file mode 100644 index 00000000..5a09840b --- /dev/null +++ b/os/src/arch/loongarch64/mod.rs @@ -0,0 +1,12 @@ +//! LoongArch64 arch implementation of HAL traits. +#![allow(missing_docs)] + +pub mod hart; +mod entry; +pub mod paging; +mod switch; +pub mod trap; + +pub use hart::{read_time, set_timer_deadline, LoongArchHartId}; +pub use paging::LoongArchPaging; +pub use trap::{LoongArchInterruptControl, LoongArchTrapContextAbi, LoongArchTrapMachine}; diff --git a/os/src/arch/loongarch64/paging.rs b/os/src/arch/loongarch64/paging.rs new file mode 100644 index 00000000..d8aa8025 --- /dev/null +++ b/os/src/arch/loongarch64/paging.rs @@ -0,0 +1,162 @@ +//! LoongArch64 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). + +use core::arch::asm; + +use crate::hal::traits::{AddressSpaceToken, PTEFlags, PagingArch}; +use crate::mm::PageTableEntry; + +const CSR_PGDL: usize = 0x19; +const CSR_ASID: usize = 0x18; +const PTE_V: usize = 1 << 0; +const PTE_D: usize = 1 << 1; +const PTE_PLV_USER: usize = 0b11 << 2; +const PTE_MAT_CC: usize = 0b01 << 4; +const PTE_G: usize = 1 << 6; +const PTE_P: usize = 1 << 7; +const PTE_W: usize = 1 << 8; +const PTE_A: usize = 1 << 10; +const PTE_GNX: usize = 1 << 62; +const PTE_GNR: usize = 1 << 61; +const PPN_SHIFT: usize = 12; + +/// LoongArch64 three-level paging (39-bit VA, matching PWCL Dir1/Dir2 setup). +pub struct LoongArchPaging; + +impl PagingArch for LoongArchPaging { + type Entry = PageTableEntry; + const PA_BITS: usize = 48; + const VA_BITS: usize = 39; + const PPN_BITS: usize = Self::PA_BITS - 12; + const ROOT_TOKEN_MODE: usize = 0; + const LEVELS: usize = 3; + const INDEX_BITS: usize = 9; + + fn make_token(root_ppn: usize) -> AddressSpaceToken { + root_ppn << PPN_SHIFT + } + + fn root_ppn(token: AddressSpaceToken) -> usize { + token >> PPN_SHIFT + } + + unsafe fn activate_token(token: AddressSpaceToken) { + asm!( + "dbar 0", + "csrwr {pgd}, {pgdl}", + "csrwr $zero, {asid}", + "invtlb 0x00, $zero, $zero", + "ibar 0", + pgd = in(reg) token, + pgdl = const CSR_PGDL, + asid = const CSR_ASID, + ); + } + + unsafe fn current_token() -> AddressSpaceToken { + let token: usize; + asm!("csrrd {}, {}", out(reg) token, const CSR_PGDL); + token + } + + unsafe fn flush_tlb() { + asm!( + "dbar 0", + "invtlb 0x00, $zero, $zero", + "ibar 0", + ); + } + + fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + let mut bits = (ppn << PPN_SHIFT) | PTE_P | PTE_V | PTE_MAT_CC; + if flags.contains(PTEFlags::A) { + bits |= PTE_A; + } + if flags.contains(PTEFlags::W) { + bits |= PTE_W | PTE_D; + } + if flags.contains(PTEFlags::U) { + bits |= PTE_PLV_USER; + } + if flags.contains(PTEFlags::G) { + bits |= PTE_G; + } + if !flags.contains(PTEFlags::R) { + bits |= PTE_GNR; + } + if !flags.contains(PTEFlags::X) { + bits |= PTE_GNX; + } + bits + } + + fn make_dir_entry(ppn: usize) -> usize { + // LoongArch hardware walkers consume non-leaf directory entries as the + // physical address of the next-level table. Keep them as a bare next + // table pointer instead of reusing leaf-style permission bits. + ppn << PPN_SHIFT + } + + fn pte_ppn(entry_bits: usize) -> usize { + (entry_bits >> PPN_SHIFT) & ((1usize << Self::PPN_BITS) - 1) + } + + fn pte_flags(entry_bits: usize) -> PTEFlags { + let mut flags = PTEFlags::empty(); + if entry_bits & PTE_V != 0 { + flags |= PTEFlags::V; + } + if entry_bits & PTE_W != 0 { + flags |= PTEFlags::W; + } + if entry_bits & PTE_D != 0 { + flags |= PTEFlags::D; + } + if entry_bits & PTE_A != 0 { + flags |= PTEFlags::A; + } + if entry_bits & PTE_PLV_USER != 0 { + flags |= PTEFlags::U; + } + if entry_bits & PTE_G != 0 { + flags |= PTEFlags::G; + } + if entry_bits & PTE_GNR == 0 { + flags |= PTEFlags::R; + } + if entry_bits & PTE_GNX == 0 { + flags |= PTEFlags::X; + } + flags + } + + fn pte_is_valid(entry_bits: usize) -> bool { + entry_bits != 0 + } + + fn normalize_leaf_flags(mut flags: PTEFlags) -> PTEFlags { + flags.insert(PTEFlags::A); + if flags.contains(PTEFlags::W) { + flags.insert(PTEFlags::D); + } + flags + } + + fn normalize_virt_addr_input(bits: usize) -> usize { + bits + } + + fn virt_page_num_from_addr(bits: usize) -> usize { + bits >> 12 + } + + fn trap_context_flags() -> PTEFlags { + PTEFlags::R | PTEFlags::W | PTEFlags::U + } + + fn vpn_index(vpn: usize, level: usize) -> usize { + debug_assert!(level < Self::LEVELS); + let mask = (1usize << Self::INDEX_BITS) - 1; + let shift = (Self::LEVELS - 1 - level) * Self::INDEX_BITS; + (vpn >> shift) & mask + } +} diff --git a/os/src/arch/loongarch64/switch.S b/os/src/arch/loongarch64/switch.S new file mode 100644 index 00000000..ed24b4d5 --- /dev/null +++ b/os/src/arch/loongarch64/switch.S @@ -0,0 +1,29 @@ + .section .text + .globl __switch +__switch: + st.d $sp, $a0, 8 + st.d $ra, $a0, 0 + st.d $fp, $a0, 16 + st.d $s0, $a0, 24 + st.d $s1, $a0, 32 + st.d $s2, $a0, 40 + st.d $s3, $a0, 48 + st.d $s4, $a0, 56 + st.d $s5, $a0, 64 + st.d $s6, $a0, 72 + st.d $s7, $a0, 80 + st.d $s8, $a0, 88 + + ld.d $ra, $a1, 0 + ld.d $sp, $a1, 8 + ld.d $fp, $a1, 16 + ld.d $s0, $a1, 24 + ld.d $s1, $a1, 32 + ld.d $s2, $a1, 40 + ld.d $s3, $a1, 48 + ld.d $s4, $a1, 56 + ld.d $s5, $a1, 64 + ld.d $s6, $a1, 72 + ld.d $s7, $a1, 80 + ld.d $s8, $a1, 88 + jr $ra diff --git a/os/src/arch/loongarch64/switch.rs b/os/src/arch/loongarch64/switch.rs new file mode 100644 index 00000000..feb16f83 --- /dev/null +++ b/os/src/arch/loongarch64/switch.rs @@ -0,0 +1,5 @@ +//! LoongArch64 task-switch assembly entrypoints. + +use core::arch::global_asm; + +global_asm!(include_str!("switch.S")); diff --git a/os/src/arch/loongarch64/trap.S b/os/src/arch/loongarch64/trap.S new file mode 100644 index 00000000..db305964 --- /dev/null +++ b/os/src/arch/loongarch64/trap.S @@ -0,0 +1,107 @@ + # TLB refill handler — must be 4K-aligned, placed first in trampoline section. + # Uses hardware page-table walker: PGD → lddir × N → ldpte × 2 → tlbfill. + .section .text.trampoline + .equ CSR_SAVE, 0x30 + .equ CSR_SAVE1, 0x31 + .altmacro + .macro STORE_X n + st.d $r\n, $sp, \n*8 + .endm + .macro LOAD_X n + ld.d $r\n, $sp, \n*8 + .endm + .globl __tlb_refill + .balign 4096 +__tlb_refill: + csrwr $t0, 0x8b # save t0 to TLBRSAVE + csrrd $t0, 0x1b # t0 = effective PGD selected by hardware + lddir $t0, $t0, 2 # walk root → mid (Dir2 → Dir1) + beqz $t0, 1f + lddir $t0, $t0, 1 # walk mid → leaf PT + beqz $t0, 1f + ldpte $t0, 0 + ldpte $t0, 1 + b 2f +1: + csrwr $zero, 0x8c + csrwr $zero, 0x8d +2: + tlbfill + csrrd $t0, 0x8b + ertn + + .globl __alltraps + .globl __restore + .align 3 +__alltraps: + # Swap user sp with the per-task trap context pointer kept in CSR_SAVE. + # This preserves every user GPR, including t0/r12. + csrwr $sp, CSR_SAVE + st.d $ra, $sp, 1*8 + st.d $tp, $sp, 2*8 + .set n, 4 + .rept 28 + STORE_X %n + .set n, n + 1 + .endr + + csrrd $t0, 0x1 + csrrd $t1, 0x6 + # CSR_SAVE now holds the interrupted user sp after the swap above. + csrrd $t2, CSR_SAVE + st.d $t0, $sp, 32*8 + st.d $t1, $sp, 33*8 + st.d $t2, $sp, 3*8 + + # Keep the first user-trap path free of FP instructions. + # LoongArch can trap on FP use when EUEN/FPU state is not fully prepared, + # and nested traps here corrupt the user trap frame before Rust sees it. + st.d $zero, $sp, 70*8 + + ld.d $tp, $sp, 34*8 + ld.d $t0, $sp, 35*8 + ld.d $t1, $sp, 37*8 + ld.d $sp, $sp, 36*8 + csrwr $t0, 0x19 + invtlb 0x00, $zero, $zero + jr $t1 + +__restore: + csrwr $a0, CSR_SAVE + csrrd $a0, CSR_SAVE + csrwr $a1, 0x19 + invtlb 0x00, $zero, $zero + move $sp, $a0 + ld.d $t0, $sp, 32*8 + ld.d $t1, $sp, 33*8 + csrwr $t0, 0x1 + csrwr $t1, 0x6 + ld.d $ra, $sp, 1*8 + ld.d $tp, $sp, 2*8 + .set n, 4 + .rept 28 + LOAD_X %n + .set n, n + 1 + .endr + ld.d $sp, $sp, 3*8 + ertn + + .section .text + .globl __trap_from_kernel + .align 3 +__trap_from_kernel: + addi.d $sp, $sp, -256 + st.d $ra, $sp, 0 + st.d $a0, $sp, 8 + st.d $a1, $sp, 16 + st.d $t0, $sp, 24 + st.d $t1, $sp, 32 + la.local $t0, trap_from_kernel + jirl $ra, $t0, 0 + ld.d $ra, $sp, 0 + ld.d $a0, $sp, 8 + ld.d $a1, $sp, 16 + ld.d $t0, $sp, 24 + ld.d $t1, $sp, 32 + addi.d $sp, $sp, 256 + ertn diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs new file mode 100644 index 00000000..c7d1288e --- /dev/null +++ b/os/src/arch/loongarch64/trap.rs @@ -0,0 +1,381 @@ +//! LoongArch64 interrupt control, trap decoding and user-return operations. + +use core::arch::{asm, global_asm}; + +use crate::config::TRAMPOLINE; +use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; + +global_asm!(include_str!("trap.S")); + +const CSR_CRMD: usize = 0x0; +const CSR_PRMD: usize = 0x1; +const CSR_EUEN: usize = 0x2; +const CSR_ECFG: usize = 0x4; +const CSR_ESTAT: usize = 0x5; +const CSR_ERA: usize = 0x6; +const CSR_BADV: usize = 0x7; +const CSR_BADI: usize = 0x8; +const CSR_EENTRY: usize = 0xc; +const CSR_TLBRENTRY: usize = 0x88; +const CSR_TLBREHI: usize = 0x8e; +const CSR_PWCL: usize = 0x1c; +const CSR_PWCH: usize = 0x1d; +const CSR_STLBPS: usize = 0x1e; + +const CRMD_IE: usize = 1 << 2; +const EUEN_FPEN: usize = 1 << 0; +const ECFG_SIP: usize = 1 << 1; +const ECFG_HWI0: usize = 1 << 2; +const ECFG_TIMER: usize = 1 << 11; +const ECFG_IPI: usize = 1 << 12; +// QEMU `virt` routes EXTIOI sources to CPU IP3, which is exposed in +// ESTAT/ECFG as HWI3 (interrupt number 5, bit 5). +const ECFG_EXTERNAL: usize = ECFG_HWI0 << 3; + +const ECODE_INT: usize = 0x0; +const ECODE_PIL: usize = 0x1; +const ECODE_PIS: usize = 0x2; +const ECODE_PIF: usize = 0x3; +const ECODE_PME: usize = 0x4; +const ECODE_ADE: usize = 0x8; +const ECODE_SYS: usize = 0xb; +const ECODE_INE: usize = 0xd; + +/// LoongArch64 implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). +pub struct LoongArchInterruptControl; + +/// LoongArch64 implementation of trap decoding and user-return operations. +pub struct LoongArchTrapMachine; + +/// LoongArch64 register-layout helpers for the common trap context. +pub struct LoongArchTrapContextAbi; + +/// LoongArch64 trap frame layout shared with `trap.S`. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct LoongArchTrapContextFrame { + pub r: [usize; 32], + pub prmd: usize, + pub era: usize, + pub kernel_hartid: usize, + pub kernel_pgdl: usize, + pub kernel_sp: usize, + pub trap_handler: usize, + pub f: [u64; 32], + pub fcsr: usize, +} + +/// 用户态 `rt_sigreturn` trampoline 机器码。 +/// +/// 等价指令序列: +/// ori $a7, $zero, 139 +/// syscall 0 +const USER_VDSO_CODE: [u8; 8] = [ + 0x0b, 0x2c, 0x82, 0x03, // ori $a7, $zero, 139 + 0x00, 0x00, 0x2b, 0x00, // syscall 0 +]; + +impl InterruptControl for LoongArchInterruptControl { + unsafe fn enable_timer() { + update_ecfg(ECFG_TIMER, true); + } + + unsafe fn disable_timer() { + update_ecfg(ECFG_TIMER, false); + } + + unsafe fn enable_external() { + update_ecfg(ECFG_EXTERNAL, true); + } + + unsafe fn disable_external() { + update_ecfg(ECFG_EXTERNAL, false); + } + + unsafe fn enable_software() { + update_ecfg(ECFG_SIP, true); + update_ecfg(ECFG_IPI, true); + } + + unsafe fn clear_software_pending() { + asm!( + "csrrd $t0, {estat}", + "and $t0, $t0, {mask}", + "csrwr $t0, {estat}", + estat = const CSR_ESTAT, + mask = in(reg) (!(1usize << 1)), + out("$t0") _, + ); + } + + unsafe fn set_kernel_trap_entry() { + extern "C" { + fn __trap_from_kernel(); + fn __tlb_refill(); + } + // PWCL: PTbase=12, PTwidth=9, Dir1base=21, Dir1width=9, Dir2base=30, Dir2width=9 + // PWCH: Dir3=unused (3-level paging: root=Dir2, middle=Dir1, leaf=PT) + const PWCL: usize = 12 | (9 << 5) | (21 << 10) | (9 << 15) | (30 << 20) | (9 << 25); + const PWCH: usize = 0; + asm!( + "csrwr {eentry}, {eentry_csr}", + "csrwr {tlbr}, {tlbr_csr}", + "csrwr {pwcl}, {pwcl_csr}", + "csrwr {pwch}, {pwch_csr}", + // STLBPS / TLBREHI.PS: page size = 12 (4KB) for software-managed + // refill entries as well as the shared TLB configuration. + "ori $t0, $zero, 12", + "csrwr $t0, {stlbps_csr}", + "csrwr $t0, {tlbrehi_csr}", + eentry = in(reg) (__trap_from_kernel as usize), + eentry_csr = const CSR_EENTRY, + tlbr = in(reg) (__tlb_refill as usize), + tlbr_csr = const CSR_TLBRENTRY, + pwcl = in(reg) PWCL, + pwcl_csr = const CSR_PWCL, + pwch = in(reg) PWCH, + pwch_csr = const CSR_PWCH, + stlbps_csr = const CSR_STLBPS, + tlbrehi_csr = const CSR_TLBREHI, + out("$t0") _, + ); + } + + unsafe fn set_user_trap_entry() { + extern "C" { + fn __alltraps(); + fn strampoline(); + } + let trap_entry = __alltraps as usize - strampoline as usize + TRAMPOLINE; + asm!( + "csrwr {entry}, {eentry}", + entry = in(reg) trap_entry, + eentry = const CSR_EENTRY, + ); + } +} + +impl TrapMachine for LoongArchTrapMachine { + fn read_trap_info() -> TrapInfo { + let estat = read_estat(); + let ecfg = read_ecfg(); + let badv = read_badv(); + let ecode = (estat >> 16) & 0x3f; + let cause = match ecode { + ECODE_SYS => TrapCause::UserSyscall, + ECODE_PIS | ECODE_PME => TrapCause::StorePageFault, + ECODE_PIL => TrapCause::LoadPageFault, + ECODE_PIF => TrapCause::InstructionPageFault, + ECODE_INE => TrapCause::IllegalInstruction, + ECODE_ADE => TrapCause::InstructionFault, + ECODE_INT => { + decode_interrupt_cause(estat, ecfg) + } + _ => TrapCause::Unknown, + }; + TrapInfo { cause, fault_addr: badv } + } + + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> ! { + extern "C" { + fn __restore(); + fn strampoline(); + } + let restore_va = __restore as usize - strampoline as usize + TRAMPOLINE; + asm!( + "ibar 0", + "jirl $zero, {restore}, 0", + restore = in(reg) restore_va, + in("$a0") trap_cx_user_va, + in("$a1") user_token, + options(noreturn) + ); + } + + fn syscall_instruction_len() -> usize { + 4 + } + + fn rt_sigreturn_trampoline() -> &'static [u8] { + &USER_VDSO_CODE + } +} + +impl TrapContextAbi for LoongArchTrapContextAbi { + type Frame = LoongArchTrapContextFrame; + + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: usize, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame { + let mut frame = LoongArchTrapContextFrame { + r: [0; 32], + // PPLV=3 (user) and PIE=1 so `ertn` returns to PLV3 with + // interrupts restored according to the saved user context. + prmd: 0b0111, + era: entry, + kernel_hartid: 0, + kernel_pgdl: kernel_token, + kernel_sp, + trap_handler, + f: [0; 32], + fcsr: 0, + }; + frame.r[3] = sp; + frame + } + + fn reg(frame: &Self::Frame, index: usize) -> usize { + frame.r[index] + } + + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize) { + if index != 0 { + frame.r[index] = value; + } + } + + fn user_pc(frame: &Self::Frame) -> usize { + frame.era + } + + fn set_user_pc(frame: &mut Self::Frame, pc: usize) { + frame.era = pc; + } + + fn user_sp(frame: &Self::Frame) -> usize { + frame.r[3] + } + + fn set_user_sp(frame: &mut Self::Frame, sp: usize) { + frame.r[3] = sp; + } + + fn ra(frame: &Self::Frame) -> usize { + frame.r[1] + } + + fn set_ra(frame: &mut Self::Frame, ra: usize) { + frame.r[1] = ra; + } + + fn tls(frame: &Self::Frame) -> usize { + frame.r[2] + } + + fn set_tls(frame: &mut Self::Frame, tls: usize) { + frame.r[2] = tls; + } + + fn syscall_nr(frame: &Self::Frame) -> usize { + frame.r[11] + } + + fn syscall_args(frame: &Self::Frame) -> [usize; 6] { + [frame.r[4], frame.r[5], frame.r[6], frame.r[7], frame.r[8], frame.r[9]] + } + + fn syscall_ret(frame: &Self::Frame) -> usize { + frame.r[4] + } + + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize) { + frame.r[4] = ret; + } + + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize) { + frame.r[4 + index] = value; + } + + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize) { + frame.kernel_hartid = hartid; + } + + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize) { + frame.kernel_sp = kernel_sp; + } + + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32] { + let mut exported = frame.r; + exported[0] = frame.era; + exported + } + + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]) { + frame.r.copy_from_slice(signal_gprs); + frame.r[0] = 0; + frame.era = signal_gprs[0]; + } + + fn signal_gpr_arg0_index() -> usize { + 4 // LoongArch: r4 = a0 + } + + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { + fpregs.copy_from_slice(&frame.f); + *fcsr = frame.fcsr as u32; + } + + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32) { + frame.f.copy_from_slice(fpregs); + frame.fcsr = fcsr as usize; + } +} + +#[inline] +fn read_estat() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_ESTAT) }; + value +} + +#[inline] +fn read_ecfg() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_ECFG) }; + value +} + +#[inline] +fn read_badv() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_BADV) }; + value +} + +#[inline] +fn read_badi() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_BADI) }; + value +} + +#[inline] +fn decode_interrupt_cause(estat: usize, ecfg: usize) -> TrapCause { + let int_vec = (estat & ecfg) & 0x1fff; + let highest = (0..=12).rev().find(|bit| int_vec & (1usize << bit) != 0); + match highest { + Some(12) => TrapCause::SoftwareInterrupt, + Some(11) => TrapCause::TimerInterrupt, + Some(1) | Some(0) => TrapCause::SoftwareInterrupt, + Some(2..=9) => TrapCause::ExternalInterrupt, + Some(10) => TrapCause::Unknown, + None => TrapCause::Unknown, + Some(_) => TrapCause::Unknown, + } +} + +#[inline] +unsafe fn update_ecfg(mask: usize, enable: bool) { + let mut ecfg: usize; + asm!("csrrd {}, {}", out(reg) ecfg, const CSR_ECFG); + if enable { + ecfg |= mask; + } else { + ecfg &= !mask; + } + asm!("csrwr {}, {}", in(reg) ecfg, const CSR_ECFG); +} diff --git a/os/src/arch/mod.rs b/os/src/arch/mod.rs new file mode 100644 index 00000000..21293e20 --- /dev/null +++ b/os/src/arch/mod.rs @@ -0,0 +1,11 @@ +//! Architecture-specific CPU and privilege-architecture support. +//! +//! This layer owns ISA-defined mechanisms such as traps, CSR access, paging +//! formats, context-switch ABI, and hart-local interrupt control. It does not +//! know which board or machine model wires devices onto that CPU. + +#[cfg(target_arch = "riscv64")] +pub mod riscv; + +#[cfg(target_arch = "loongarch64")] +pub mod loongarch64; diff --git a/os/src/arch/riscv/address.rs b/os/src/arch/riscv/address.rs new file mode 100644 index 00000000..f0fd0af2 --- /dev/null +++ b/os/src/arch/riscv/address.rs @@ -0,0 +1,8 @@ +//! SV39 address-space constants for RISC-V. + +/// Physical address width under Sv39. +pub const PA_WIDTH_SV39: usize = 56; +/// Virtual address width under Sv39. +pub const VA_WIDTH_SV39: usize = 39; +/// Physical page number width under Sv39. +pub const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - 12; // PAGE_SIZE_BITS = 12 diff --git a/os/src/entry.asm b/os/src/arch/riscv/entry.asm similarity index 100% rename from os/src/entry.asm rename to os/src/arch/riscv/entry.asm diff --git a/os/src/arch/riscv/entry.rs b/os/src/arch/riscv/entry.rs new file mode 100644 index 00000000..193d5d27 --- /dev/null +++ b/os/src/arch/riscv/entry.rs @@ -0,0 +1,5 @@ +//! RISC-V kernel entry assembly. + +use core::arch::global_asm; + +global_asm!(include_str!("entry.asm")); diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs new file mode 100644 index 00000000..e3ee6454 --- /dev/null +++ b/os/src/arch/riscv/hart.rs @@ -0,0 +1,47 @@ +//! RISC-V hart-local register access, implementing [`HartId`](crate::hal::traits::HartId). + +use core::arch::asm; +use crate::hal::traits::HartId; +use riscv::{asm::wfi, register::{mstatus::FS, sstatus}}; + +/// RISC-V implementation of [`HartId`](crate::hal::traits::HartId) via the `tp` register. +pub struct RiscvHartId; + +impl HartId for RiscvHartId { + #[inline] + fn current() -> usize { + let id; + unsafe { asm!("mv {}, tp", out(reg) id) } + id + } + + #[inline] + unsafe fn init(id: usize) { + asm!("mv tp, {}", in(reg) id); + } + + #[inline] + unsafe fn enable_fp() { + sstatus::set_fs(FS::Initial); + } + + #[inline] + fn irqs_enabled() -> bool { + sstatus::read().sie() + } + + #[inline] + unsafe fn disable_irqs() { + sstatus::clear_sie(); + } + + #[inline] + unsafe fn enable_irqs() { + sstatus::set_sie(); + } + + #[inline] + unsafe fn wait_for_interrupt() { + wfi(); + } +} diff --git a/os/src/arch/riscv/mod.rs b/os/src/arch/riscv/mod.rs new file mode 100644 index 00000000..65213789 --- /dev/null +++ b/os/src/arch/riscv/mod.rs @@ -0,0 +1,13 @@ +//! RISC-V arch implementation of HAL traits. +#![allow(missing_docs)] + +pub mod address; +pub mod hart; +mod entry; +pub mod paging; +mod switch; +pub mod trap; + +pub use hart::RiscvHartId; +pub use paging::Sv39Paging; +pub use trap::{RiscvInterruptControl, RiscvTrapContextAbi, RiscvTrapMachine}; diff --git a/os/src/arch/riscv/paging.rs b/os/src/arch/riscv/paging.rs new file mode 100644 index 00000000..760c6577 --- /dev/null +++ b/os/src/arch/riscv/paging.rs @@ -0,0 +1,62 @@ +//! SV39 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). + +use crate::hal::traits::{AddressSpaceToken, PTEFlags, PagingArch}; +use crate::mm::PageTableEntry; + +/// RISC-V Sv39 three-level paging implementation. +pub struct Sv39Paging; + +impl PagingArch for Sv39Paging { + type Entry = PageTableEntry; + const PA_BITS: usize = 56; + const VA_BITS: usize = 39; + const PPN_BITS: usize = Self::PA_BITS - 12; + const ROOT_TOKEN_MODE: usize = 8; // MODE=8 → Sv39 + const LEVELS: usize = 3; + const INDEX_BITS: usize = 9; + + fn make_token(root_ppn: usize) -> AddressSpaceToken { + Self::ROOT_TOKEN_MODE << 60 | root_ppn + } + + fn root_ppn(token: AddressSpaceToken) -> usize { + token & ((1usize << 44) - 1) + } + + unsafe fn activate_token(token: AddressSpaceToken) { + use riscv::register::satp; + satp::write(token); + core::arch::asm!("sfence.vma"); + } + + unsafe fn current_token() -> AddressSpaceToken { + riscv::register::satp::read().bits() + } + + unsafe fn flush_tlb() { + core::arch::asm!("sfence.vma"); + } + + fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + ppn << 10 | flags.bits() as usize + } + + fn pte_ppn(entry_bits: usize) -> usize { + entry_bits >> 10 & ((1usize << 44) - 1) + } + + fn pte_flags(entry_bits: usize) -> PTEFlags { + PTEFlags::from_bits_truncate(entry_bits as u16) + } + + fn normalize_virt_addr_input(bits: usize) -> usize { + bits & ((1usize << Self::VA_BITS) - 1) + } + + fn vpn_index(vpn: usize, level: usize) -> usize { + debug_assert!(level < Self::LEVELS); + let mask = (1usize << Self::INDEX_BITS) - 1; + let shift = (Self::LEVELS - 1 - level) * Self::INDEX_BITS; + (vpn >> shift) & mask + } +} diff --git a/os/src/sched/switch.S b/os/src/arch/riscv/switch.S similarity index 100% rename from os/src/sched/switch.S rename to os/src/arch/riscv/switch.S diff --git a/os/src/arch/riscv/switch.rs b/os/src/arch/riscv/switch.rs new file mode 100644 index 00000000..b7d25ddc --- /dev/null +++ b/os/src/arch/riscv/switch.rs @@ -0,0 +1,5 @@ +//! RISC-V task-switch assembly entrypoints. + +use core::arch::global_asm; + +global_asm!(include_str!("switch.S")); diff --git a/os/src/trap/trap.S b/os/src/arch/riscv/trap.S similarity index 100% rename from os/src/trap/trap.S rename to os/src/arch/riscv/trap.S diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs new file mode 100644 index 00000000..6197644a --- /dev/null +++ b/os/src/arch/riscv/trap.rs @@ -0,0 +1,245 @@ +//! RISC-V interrupt control, implementing [`InterruptControl`](crate::hal::traits::InterruptControl). + +use core::arch::{asm, global_asm}; +use riscv::register::{ + sstatus::{self, Sstatus, SPP}, + mtvec::TrapMode, + scause::{self, Exception, Interrupt, Trap}, + sie, stval, stvec, +}; +use crate::config::TRAMPOLINE; +use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; + +global_asm!(include_str!("trap.S")); + +/// RISC-V implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). +pub struct RiscvInterruptControl; + +/// RISC-V implementation of trap decoding and user-return operations. +pub struct RiscvTrapMachine; + +/// RISC-V register-layout helpers for the common [`TrapContext`](crate::trap::TrapContext). +pub struct RiscvTrapContextAbi; + +/// RISC-V trap frame layout shared with `trap.S`. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct RiscvTrapContextFrame { + /// General-purpose registers x0..x31. + pub x: [usize; 32], + /// Saved supervisor status register. + pub sstatus: Sstatus, + /// Saved exception PC. + pub sepc: usize, + /// Kernel hart id restored into `tp` on trap entry. + pub kernel_hartid: usize, + /// Kernel address-space token installed on trap entry. + pub kernel_satp: usize, + /// Kernel stack pointer used on trap entry. + pub kernel_sp: usize, + /// Common Rust trap handler entry. + pub trap_handler: usize, + /// Floating-point registers f0..f31. + pub f: [u64; 32], + /// Floating-point CSR. + pub fcsr: usize, +} + +/// 用户态 `rt_sigreturn` trampoline 机器码。 +const USER_VDSO_CODE: [u8; 8] = [ + 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 + 0x73, 0x00, 0x00, 0x00, // ecall +]; + +impl InterruptControl for RiscvInterruptControl { + unsafe fn enable_timer() { sie::set_stimer(); } + unsafe fn disable_timer() { sie::clear_stimer(); } + unsafe fn enable_external() { sie::set_sext(); } + unsafe fn disable_external() { sie::clear_sext(); } + unsafe fn enable_software() { sie::set_ssoft(); } + + unsafe fn clear_software_pending() { + asm!("csrc sip, {}", in(reg) 1usize << 1); + } + + unsafe fn set_kernel_trap_entry() { + extern "C" { fn __trap_from_kernel(); } + stvec::write(__trap_from_kernel as usize, TrapMode::Direct); + } + + unsafe fn set_user_trap_entry() { + stvec::write(TRAMPOLINE, TrapMode::Direct); + } +} + +impl TrapMachine for RiscvTrapMachine { + fn read_trap_info() -> TrapInfo { + let cause = match scause::read().cause() { + Trap::Exception(Exception::UserEnvCall) => TrapCause::UserSyscall, + Trap::Exception(Exception::StorePageFault) => TrapCause::StorePageFault, + Trap::Exception(Exception::LoadPageFault) => TrapCause::LoadPageFault, + Trap::Exception(Exception::InstructionPageFault) => TrapCause::InstructionPageFault, + Trap::Exception(Exception::StoreFault) => TrapCause::StoreFault, + Trap::Exception(Exception::InstructionFault) => TrapCause::InstructionFault, + Trap::Exception(Exception::LoadFault) => TrapCause::LoadFault, + Trap::Exception(Exception::IllegalInstruction) => TrapCause::IllegalInstruction, + Trap::Interrupt(Interrupt::SupervisorTimer) => TrapCause::TimerInterrupt, + Trap::Interrupt(Interrupt::SupervisorSoft) => TrapCause::SoftwareInterrupt, + Trap::Interrupt(Interrupt::SupervisorExternal) => TrapCause::ExternalInterrupt, + _ => TrapCause::Unknown, + }; + TrapInfo { + cause, + fault_addr: stval::read(), + } + } + + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> ! { + extern "C" { + fn __alltraps(); + fn __restore(); + } + let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; + asm!( + "fence.i", + "jr {restore_va}", + restore_va = in(reg) restore_va, + in("a0") trap_cx_user_va, + in("a1") user_token, + options(noreturn) + ); + } + + fn syscall_instruction_len() -> usize { + 4 + } + + fn rt_sigreturn_trampoline() -> &'static [u8] { + &USER_VDSO_CODE + } +} + +impl TrapContextAbi for RiscvTrapContextAbi { + type Frame = RiscvTrapContextFrame; + + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: usize, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame { + let mut status = sstatus::read(); + status.set_spp(SPP::User); + let mut frame = RiscvTrapContextFrame { + x: [0; 32], + sstatus: status, + sepc: entry, + kernel_hartid: 0, + kernel_satp: kernel_token, + kernel_sp, + trap_handler, + f: [0; 32], + fcsr: 0, + }; + frame.x[2] = sp; + frame + } + + fn reg(frame: &Self::Frame, index: usize) -> usize { + frame.x[index] + } + + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize) { + if index != 0 { + frame.x[index] = value; + } + } + + fn user_pc(frame: &Self::Frame) -> usize { + frame.sepc + } + + fn set_user_pc(frame: &mut Self::Frame, pc: usize) { + frame.sepc = pc; + } + + fn user_sp(frame: &Self::Frame) -> usize { + frame.x[2] + } + + fn set_user_sp(frame: &mut Self::Frame, sp: usize) { + frame.x[2] = sp; + } + + fn ra(frame: &Self::Frame) -> usize { + frame.x[1] + } + + fn set_ra(frame: &mut Self::Frame, ra: usize) { + frame.x[1] = ra; + } + + fn tls(frame: &Self::Frame) -> usize { + frame.x[4] + } + + fn set_tls(frame: &mut Self::Frame, tls: usize) { + frame.x[4] = tls; + } + + fn syscall_nr(frame: &Self::Frame) -> usize { + frame.x[17] + } + + fn syscall_args(frame: &Self::Frame) -> [usize; 6] { + [frame.x[10], frame.x[11], frame.x[12], frame.x[13], frame.x[14], frame.x[15]] + } + + fn syscall_ret(frame: &Self::Frame) -> usize { + frame.x[10] + } + + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize) { + frame.x[10] = ret; + } + + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize) { + frame.x[10 + index] = value; + } + + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize) { + frame.kernel_hartid = hartid; + } + + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize) { + frame.kernel_sp = kernel_sp; + } + + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32] { + let mut exported = [0usize; 32]; + exported[0] = frame.sepc; + exported[1..].copy_from_slice(&frame.x[1..]); + exported + } + + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]) { + frame.x[0] = 0; + frame.x[1..].copy_from_slice(&signal_gprs[1..]); + frame.sepc = signal_gprs[0]; + } + + fn signal_gpr_arg0_index() -> usize { + 10 // RISC-V: x10 = a0 + } + + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { + fpregs.copy_from_slice(&frame.f); + *fcsr = frame.fcsr as u32; + } + + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32) { + frame.f.copy_from_slice(fpregs); + frame.fcsr = fcsr as usize; + } +} diff --git a/os/src/batch.rs b/os/src/batch.rs deleted file mode 100644 index 43e2c55a..00000000 --- a/os/src/batch.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! batch subsystem - -use crate::sync::UPSafeCell; -use crate::trap::TrapContext; -use core::arch::asm; -use lazy_static::*; - -const USER_STACK_SIZE: usize = 4096 * 2; -const KERNEL_STACK_SIZE: usize = 4096 * 2; -const MAX_APP_NUM: usize = 16; -const APP_BASE_ADDRESS: usize = 0x80400000; -const APP_SIZE_LIMIT: usize = 0x20000; - -#[repr(align(4096))] -struct KernelStack { - data: [u8; KERNEL_STACK_SIZE], -} - -#[repr(align(4096))] -struct UserStack { - data: [u8; USER_STACK_SIZE], -} - -static KERNEL_STACK: KernelStack = KernelStack { - data: [0; KERNEL_STACK_SIZE], -}; -static USER_STACK: UserStack = UserStack { - data: [0; USER_STACK_SIZE], -}; - -impl KernelStack { - fn get_sp(&self) -> usize { - self.data.as_ptr() as usize + KERNEL_STACK_SIZE - } - pub fn push_context(&self, cx: TrapContext) -> &'static mut TrapContext { - let cx_ptr = (self.get_sp() - core::mem::size_of::()) as *mut TrapContext; - unsafe { - *cx_ptr = cx; - } - unsafe { cx_ptr.as_mut().unwrap() } - } -} - -impl UserStack { - fn get_sp(&self) -> usize { - self.data.as_ptr() as usize + USER_STACK_SIZE - } -} - -struct AppManager { - num_app: usize, - current_app: usize, - app_start: [usize; MAX_APP_NUM + 1], -} - -impl AppManager { - pub fn print_app_info(&self) { - println!("[kernel] num_app = {}", self.num_app); - for i in 0..self.num_app { - println!( - "[kernel] app_{} [{:#x}, {:#x})", - i, - self.app_start[i], - self.app_start[i + 1] - ); - } - } - - unsafe fn load_app(&self, app_id: usize) { - if app_id >= self.num_app { - println!("All applications completed!"); - use crate::board::QEMUExit; - crate::board::QEMU_EXIT_HANDLE.exit_success(); - } - println!("[kernel] Loading app_{}", app_id); - // clear app area - core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0); - let app_src = core::slice::from_raw_parts( - self.app_start[app_id] as *const u8, - self.app_start[app_id + 1] - self.app_start[app_id], - ); - let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len()); - app_dst.copy_from_slice(app_src); - // Memory fence about fetching the instruction memory - // It is guaranteed that a subsequent instruction fetch must - // observes all previous writes to the instruction memory. - // Therefore, fence.i must be executed after we have loaded - // the code of the next app into the instruction memory. - // See also: riscv non-priv spec chapter 3, 'Zifencei' extension. - asm!("fence.i"); - } - - pub fn get_current_app(&self) -> usize { - self.current_app - } - - pub fn move_to_next_app(&mut self) { - self.current_app += 1; - } -} - -lazy_static! { - static ref APP_MANAGER: UPSafeCell = unsafe { - UPSafeCell::new({ - extern "C" { - fn _num_app(); - } - let num_app_ptr = _num_app as usize as *const usize; - let num_app = num_app_ptr.read_volatile(); - let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1]; - let app_start_raw: &[usize] = - core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1); - app_start[..=num_app].copy_from_slice(app_start_raw); - AppManager { - num_app, - current_app: 0, - app_start, - } - }) - }; -} - -/// init batch subsystem -pub fn init() { - print_app_info(); -} - -/// print apps info -pub fn print_app_info() { - APP_MANAGER.exclusive_access().print_app_info(); -} - -/// run next app -pub fn run_next_app() -> ! { - let mut app_manager = APP_MANAGER.exclusive_access(); - let current_app = app_manager.get_current_app(); - unsafe { - app_manager.load_app(current_app); - } - app_manager.move_to_next_app(); - drop(app_manager); - // before this we have to drop local variables related to resources manually - // and release the resources - extern "C" { - fn __restore(cx_addr: usize); - } - unsafe { - __restore(KERNEL_STACK.push_context(TrapContext::app_init_context( - APP_BASE_ADDRESS, - USER_STACK.get_sp(), - )) as *const _ as usize); - } - panic!("Unreachable in batch::run_current_app!"); -} diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs deleted file mode 100644 index 17eaf9cf..00000000 --- a/os/src/boards/qemu.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! QEMU riscv-64 virt machine - -/// clock frequency -pub const CLOCK_FREQ: usize = 12500000; -//pub const MEMORY_END: usize = 0x801000000; - -/// The base address of control registers in VIRT_TEST/RTC/Virtio_Block device -pub const MMIO: &[(usize, usize)] = &[ - (0x0C00_0000, 0x400000), // PLIC - (0x0010_0000, 0x00_2000), // VIRT_TEST/RTC in virt machine - (0x1000_0000, 0x100), // UART0 (NS16550a) - (0x1000_1000, 0x8000), // Virtio MMIO devices, 8 slots, each slot occupies 0x1000 bytes -]; - -pub const VIRT_UART: usize = 0x1000_0000; -/// QEMU virt 机型上的 Goldfish RTC MMIO 基址。 -pub const VIRT_RTC: usize = 0x0010_1000; - -pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; -pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; - -//ref:: https://github.com/andre-richter/qemu-exit -use core::arch::asm; - -const EXIT_SUCCESS: u32 = 0x5555; // Equals `exit(0)`. qemu successful exit - -const EXIT_FAILURE_FLAG: u32 = 0x3333; -const EXIT_FAILURE: u32 = exit_code_encode(1); // Equals `exit(1)`. qemu failed exit -const EXIT_RESET: u32 = 0x7777; // qemu reset - -pub trait QEMUExit { - /// Exit with specified return code. - /// - /// Note: For `X86`, code is binary-OR'ed with `0x1` inside QEMU. - fn exit(&self, code: u32) -> !; - - /// Exit QEMU using `EXIT_SUCCESS`, aka `0`, if possible. - /// - /// Note: Not possible for `X86`. - fn exit_success(&self) -> !; - - /// Exit QEMU using `EXIT_FAILURE`, aka `1`. - fn exit_failure(&self) -> !; -} - -/// RISCV64 configuration -pub struct RISCV64 { - /// Address of the sifive_test mapped device. - addr: u64, -} - -/// Encode the exit code using EXIT_FAILURE_FLAG. -const fn exit_code_encode(code: u32) -> u32 { - (code << 16) | EXIT_FAILURE_FLAG -} - -impl RISCV64 { - /// Create an instance. - pub const fn new(addr: u64) -> Self { - RISCV64 { addr } - } -} - -impl QEMUExit for RISCV64 { - /// Exit qemu with specified exit code. - fn exit(&self, code: u32) -> ! { - // If code is not a special value, we need to encode it with EXIT_FAILURE_FLAG. - let code_new = match code { - EXIT_SUCCESS | EXIT_FAILURE | EXIT_RESET => code, - _ => exit_code_encode(code), - }; - - unsafe { - asm!( - "sw {0}, 0({1})", - in(reg)code_new, in(reg)self.addr - ); - - // For the case that the QEMU exit attempt did not work, transition into an infinite - // loop. Calling `panic!()` here is unfeasible, since there is a good chance - // this function here is the last expression in the `panic!()` handler - // itself. This prevents a possible infinite loop. - loop { - asm!("wfi", options(nomem, nostack)); - } - } - } - - fn exit_success(&self) -> ! { - self.exit(EXIT_SUCCESS); - } - - fn exit_failure(&self) -> ! { - self.exit(EXIT_FAILURE); - } -} - -const VIRT_TEST: u64 = 0x100000; - -pub const QEMU_EXIT_HANDLE: RISCV64 = RISCV64::new(VIRT_TEST); diff --git a/os/src/config.rs b/os/src/config.rs index 3d9592bf..cf3379f6 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -8,8 +8,6 @@ pub const USER_STACK_SIZE: usize = 1024 * 512; // 512 KiB pub const KERNEL_STACK_SIZE: usize = 4096 * 32; /// kernel heap size pub const MAX_KERNEL_HEAP_SIZE: usize = 0x4000_0000; -/// base address of the dynamically mapped kernel heap window -pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; /// max harts reserved by the kernel SMP bootstrap path pub const MAX_HARTS: usize = 8; /// QEMU virt 1GiB 内存的物理结束地址,起始地址为 0x8000_0000。 @@ -18,22 +16,15 @@ pub const MEMORY_END: usize = 0xC0000000; pub const PAGE_SIZE: usize = 0x1000; /// page size bits: 12 pub const PAGE_SIZE_BITS: usize = 0xc; -/// default base address for anonymous mmap allocations -pub const USER_MMAP_BASE: usize = 0x1000_0000; /// fixed load bias used for PIE main executables without an interpreter pub const USER_PIE_BASE: usize = 0x0020_0000; -/// default base address for the main thread's user stack region -pub const USER_STACK_BASE: usize = 0x0800_0000; -/// base address for loading dynamic linker (interpreter) -/// placed between stack and mmap region to avoid conflicts -pub const INTERP_BASE: usize = 0x4000_0000; -/// the virtual addr of trapoline -pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; + +/// qemu board info +pub use crate::platform::{USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, KERNEL_HEAP_BASE, MMIO, TRAMPOLINE}; + /// the virtual addr of trap context pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; /// 用户态 signal trampoline 页起始地址。 pub const USER_VDSO_BASE: usize = USER_MMAP_BASE - PAGE_SIZE; /// 用户态 rt_sigreturn trampoline 入口地址。 pub const USER_VDSO_RT_SIGRETURN: usize = USER_VDSO_BASE; -/// qemu board info -pub use crate::board::{CLOCK_FREQ, MMIO}; diff --git a/os/src/console.rs b/os/src/console.rs index 34511aca..5cab489b 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,9 +1,8 @@ //! Kernel console output helpers. -use crate::{drivers::chardev::{CharDevice, UART}}; +use crate::drivers::chardev::{CharDevice, UART}; use core::fmt::{self, Write}; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; /// 串行化所有 hart 的控制台输出,避免多个 hart 同时逐字符写 UART 时互相穿插。 static CONSOLE_LOCK: AtomicBool = AtomicBool::new(false); @@ -21,6 +20,15 @@ impl Write for Stdout { } } +struct EarlyStdout; + +impl Write for EarlyStdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + crate::platform::early_console_write(s); + Ok(()) + } +} + /// 控制台输出期间的临界区守卫。 /// /// 它同时负责两件事: @@ -34,8 +42,8 @@ struct ConsoleGuard { impl ConsoleGuard { /// 获取控制台输出锁。 fn lock() -> Self { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while CONSOLE_LOCK .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_err() @@ -50,13 +58,17 @@ impl Drop for ConsoleGuard { fn drop(&mut self) { CONSOLE_LOCK.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } /// print to the host console using the format string and arguments. pub fn print(args: fmt::Arguments) { let _guard = ConsoleGuard::lock(); + if crate::platform::use_early_console() { + EarlyStdout.write_fmt(args).unwrap(); + return; + } Stdout.write_fmt(args).unwrap(); } diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index 612d0a7f..ca01900f 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -12,7 +12,13 @@ use core::convert::TryFrom; use fs::BlockDevice; use lazy_static::*; use core::ptr::NonNull; -use virtio_drivers::transport::{DeviceType, mmio::{MmioTransport, VirtIOHeader}}; +use virtio_drivers::transport::{ + DeviceType, SomeTransport, + mmio::{MmioTransport, VirtIOHeader}, +}; +use crate::platform::{ + VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; fn virtio_blk_name(idx: usize) -> String { alloc::format!("vd{}", (b'a' + idx as u8) as char) @@ -61,11 +67,6 @@ lazy_static! { /// /// Must be called **before** `fs::init_rootfs` and `fs::init_dev`. pub fn probe_block_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - let mut map = BLOCK_DEVICES.lock(); let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); let mut idx = 0usize; @@ -88,7 +89,7 @@ pub fn probe_block_devices() { Err(_) => continue, }; - if let Some(dev) = VirtIOBlock::try_new(transport) { + if let Some(dev) = VirtIOBlock::try_new(SomeTransport::from(transport)) { let dev = Arc::new(dev); // let name = alloc::format!("vd{}", (b'a' + idx as u8) as char); let name = virtio_blk_name(idx); diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index bd06668b..0519cb52 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -2,17 +2,16 @@ use super::BlockDevice; use crate::sync::SpinNoIrqLock; use crate::task::{current_task, WaitQueueKeyed, WaitReason}; use core::hint::spin_loop; -use riscv::register::sstatus; use virtio_drivers::{ device::blk::{BlkReq, BlkResp, RespStatus, VirtIOBlk}, - transport::mmio::MmioTransport, + transport::SomeTransport, }; use crate::drivers::virtio::VirtioHal; /// VirtIOBlock device driver strcuture for virtio_blk device pub struct VirtIOBlock { - inner: SpinNoIrqLock>>, + inner: SpinNoIrqLock>>, wait_queue: WaitQueueKeyed, } @@ -136,8 +135,8 @@ impl BlockDevice for VirtIOBlock { } impl VirtIOBlock { - /// Build a wrapper from an initialized MMIO transport. - pub fn try_new(transport: MmioTransport<'static>) -> Option { + /// Build a wrapper from an initialized VirtIO transport. + pub fn try_new(transport: SomeTransport<'static>) -> Option { VirtIOBlk::::new(transport).ok().map(|blk| Self { inner: SpinNoIrqLock::new(blk), wait_queue: WaitQueueKeyed::new(), @@ -146,7 +145,7 @@ impl VirtIOBlock { fn wait_token(&self, token: u16) { // TODO Enable kernel interrupt in more cases. - let irq_disabled = !sstatus::read().sie(); + let irq_disabled = !crate::hal::local_irqs_enabled(); if current_task().is_none() || irq_disabled { while !self.token_ready(token) { spin_loop(); diff --git a/os/src/drivers/chardev/mod.rs b/os/src/drivers/chardev/mod.rs index 8b0c8264..bb425661 100644 --- a/os/src/drivers/chardev/mod.rs +++ b/os/src/drivers/chardev/mod.rs @@ -4,7 +4,7 @@ use alloc::sync::Arc; use core::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; -use crate::board::CharDeviceImpl; +use crate::platform::CharDeviceImpl; mod ns16550a; diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 575a924a..77c45dc4 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -5,7 +5,9 @@ use bitflags::bitflags; use crate::poll::{notify_poll_source, POLLIN}; use crate::sync::SpinNoIrqLock; -use crate::task::{WaitQueue, WaitReason}; +use crate::task::WaitQueue; +use crate::task::yield_current_and_run_next; +use crate::task::WaitReason; use super::{set_uart_ready, CharDevice}; @@ -231,6 +233,14 @@ impl CharDevice for NS16550a { } drop(inner); + if !crate::platform::console_rx_irq_ready() { + // Before the platform IRQ controller is configured, keep a + // cooperative polling fallback so early console input still + // works during bring-up. + yield_current_and_run_next(); + continue; + } + // No data: block current task until UART IRQ pushes data and signals. self.rx_wait_queue .wait_with_reason_or_skip(WaitReason::UartRx, || { diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 44d97c6a..cd3db5c4 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,123 +1,18 @@ -//! block device driver +//! Reusable device drivers. +//! +//! Drivers implement one device IP block or transport protocol such as +//! NS16550A or VirtIO. They should stay as platform-agnostic as +//! practical; the `platform` layer is responsible for deciding which drivers +//! are instantiated and how their MMIO ranges and IRQs are routed. pub mod block; pub mod chardev; pub mod net; pub mod plic; -pub mod rtc; pub mod virtio; -use core::ptr::NonNull; - -use alloc::{string::String, sync::Arc}; pub use block::BLOCK_DEVICE; -use virtio_drivers::transport::{DeviceType, mmio::{MmioTransport, VirtIOHeader}}; - -use crate::drivers::{block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, net::VirtIONetDevice}; -fn virtio_blk_name(idx: usize) -> String { - alloc::format!("vd{}", (b'a' + idx as u8) as char) -} - -/// Initialize all drivers (block, char, PLIC, …). +/// Initialize reusable drivers. pub fn init() { chardev::init(); - rtc::init(); - plic::init(); - probe_virtio_devices(); -} - - -#[inline] -fn mmio_slot_device_type(header: NonNull) -> Option { - // VirtIO MMIO register layout: magic(0x00), version(0x04), device_id(0x08). - const MAGIC_VALUE: u32 = 0x7472_6976; - const LEGACY_VERSION: u32 = 1; - const MODERN_VERSION: u32 = 2; - - let base = header.as_ptr() as *const u32; - // SAFETY: caller passes an MMIO header address on the virt bus. - let magic = unsafe { core::ptr::read_volatile(base) }; - if magic != MAGIC_VALUE { - return None; - } - // SAFETY: MMIO header word reads are volatile. - let version = unsafe { core::ptr::read_volatile(base.add(1)) }; - if version != LEGACY_VERSION && version != MODERN_VERSION { - return None; - } - // SAFETY: MMIO header word reads are volatile. - let device_id = unsafe { core::ptr::read_volatile(base.add(2)) }; - DeviceType::try_from(device_id).ok() -} - -/// Probe all VirtIO MMIO slots once and register devices found. -pub fn probe_virtio_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - - let mut map = BLOCK_DEVICES.lock(); - let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); - let mut idx = 0usize; - - for slot in 0..VIRTIO_MMIO_SLOTS { - let addr = VIRTIO_MMIO_BASE + slot * VIRTIO_MMIO_STRIDE; - - let Some(header) = NonNull::new(addr as *mut VirtIOHeader) else { - continue; - }; - - match mmio_slot_device_type(header) { - Some(DeviceType::Block) => { - let transport = match unsafe { MmioTransport::new(header, VIRTIO_MMIO_STRIDE) } { - Ok(t) => t, - Err(_) => continue, - }; - - if let Some(dev) = VirtIOBlock::try_new(transport) { - let dev = Arc::new(dev); - let name: String = virtio_blk_name(idx); - debug!("[kernel] block device {} idx {} at {:#x}", name, idx, addr); - map.insert(name, dev.clone()); - irq_map.insert(VIRTIO_MMIO_IRQ_BASE + slot as u32, dev); - idx += 1; - } - } - Some(DeviceType::Network) => { - let transport = match unsafe { MmioTransport::new(header, VIRTIO_MMIO_STRIDE) } { - Ok(t) => t, - Err(_) => continue, - }; - - let irq = VIRTIO_MMIO_IRQ_BASE + slot as u32; - if let Some(dev) = VirtIONetDevice::try_new(transport, irq) { - let mac = dev.mac_address(); - info!( - "[kernel] virtio-net found at slot {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - slot, - irq, - mac[0], - mac[1], - mac[2], - mac[3], - mac[4], - mac[5] - ); - // Register into net module via its public helper. - net::register_device(Arc::new(dev)); - // For now we stop at the first net device like the old behaviour. - // Continue scanning for block devices though. - } - } - Some(kind) => { - debug!("[kernel] VirtIO slot {} is {:?}, skipping", slot, kind); - } - None => {} - } - } - - if idx == 0 { - panic!("[kernel] no VirtIO block devices found"); - } } diff --git a/os/src/drivers/net/mod.rs b/os/src/drivers/net/mod.rs index 724fca2d..6d5da163 100644 --- a/os/src/drivers/net/mod.rs +++ b/os/src/drivers/net/mod.rs @@ -7,10 +7,13 @@ use core::convert::TryFrom; use core::ptr::NonNull; use lazy_static::lazy_static; use virtio_drivers::transport::{ - DeviceType, + DeviceType, SomeTransport, mmio::{MmioTransport, VirtIOHeader}, }; +use crate::platform::{ + VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; use crate::sync::SpinNoIrqLock; pub use virtio_net::VirtIONetDevice; @@ -50,11 +53,6 @@ pub fn register_device(dev: Arc) { /// Probe all VirtIO MMIO slots and register the first network device. pub fn probe_net_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - for slot in 0..VIRTIO_MMIO_SLOTS { let addr = VIRTIO_MMIO_BASE + slot * VIRTIO_MMIO_STRIDE; let Some(header) = NonNull::new(addr as *mut VirtIOHeader) else { @@ -70,7 +68,7 @@ pub fn probe_net_devices() { }; let irq = VIRTIO_MMIO_IRQ_BASE + slot as u32; - if let Some(dev) = VirtIONetDevice::try_new(transport, irq) { + if let Some(dev) = VirtIONetDevice::try_new(SomeTransport::from(transport), irq) { let mac = dev.mac_address(); info!( "[kernel] virtio-net found at slot {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", diff --git a/os/src/drivers/net/virtio_net.rs b/os/src/drivers/net/virtio_net.rs index 383e85ee..98a8eb36 100644 --- a/os/src/drivers/net/virtio_net.rs +++ b/os/src/drivers/net/virtio_net.rs @@ -7,7 +7,7 @@ use core::hint::spin_loop; use virtio_drivers::{ device::net::VirtIONetRaw, - transport::mmio::MmioTransport, + transport::SomeTransport, Error, }; @@ -28,7 +28,7 @@ const QUEUE_SIZE: usize = 16; pub struct VirtIONetDevice { irq: u32, mac: [u8; 6], - inner: SpinNoIrqLock, QUEUE_SIZE>>, + inner: SpinNoIrqLock, QUEUE_SIZE>>, tx_wait_queue: WaitQueueKeyed, /// TX buffers that are in-flight via non-blocking `try_send`. tx_slots: SpinNoIrqLock<[Option>; QUEUE_SIZE]>, @@ -39,7 +39,7 @@ pub struct VirtIONetDevice { impl VirtIONetDevice { /// Try to create one device instance from an already initialized transport. - pub fn try_new(transport: MmioTransport<'static>, irq: u32) -> Option { + pub fn try_new(transport: SomeTransport<'static>, irq: u32) -> Option { let mut inner = VirtIONetRaw::::new(transport).ok()?; let mac = inner.mac_address(); diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs index ef68d8cf..288a2830 100644 --- a/os/src/drivers/plic.rs +++ b/os/src/drivers/plic.rs @@ -5,7 +5,7 @@ use core::ptr::{read_volatile, write_volatile}; use crate::bootstrap_hart_id; use crate::config::MAX_HARTS; use crate::drivers::chardev::{CharDevice, UART}; -use crate::hart::hartid; +use crate::hal::hartid; use crate::sync::SpinNoIrqLock; use lazy_static::*; diff --git a/os/src/drivers/rtc.rs b/os/src/drivers/rtc.rs deleted file mode 100644 index 5b689a24..00000000 --- a/os/src/drivers/rtc.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Goldfish RTC 驱动。 -// 文档: https://android.googlesource.com/platform/external/qemu/%2B/master/docs/GOLDFISH-VIRTUAL-HARDWARE.TXT -use core::marker::PhantomData; -use core::sync::atomic::{AtomicBool, Ordering}; - -use alloc::sync::Arc; -use lazy_static::lazy_static; - -use crate::board::VIRT_RTC; -use crate::sync::SpinNoIrqLock; - -/// Goldfish RTC 时间低 32 位寄存器。 -const REG_TIME_LOW: usize = 0x00; -/// Goldfish RTC 时间高 32 位寄存器。 -const REG_TIME_HIGH: usize = 0x04; -/// Goldfish RTC 闹钟低 32 位寄存器。 -const REG_ALARM_LOW: usize = 0x08; -/// Goldfish RTC 闹钟高 32 位寄存器。 -const REG_ALARM_HIGH: usize = 0x0c; -/// Goldfish RTC 中断清除寄存器。 -const REG_CLEAR_INTERRUPT: usize = 0x10; - -/// 简单的 MMIO 寄存器访问封装。 -#[derive(Copy, Clone)] -struct Mmio { - addr: *mut T, - _pd: PhantomData, -} - -impl Mmio { - /// 根据 MMIO 地址创建访问句柄。 - const fn new(addr: usize) -> Self { - Self { - addr: addr as *mut T, - _pd: PhantomData, - } - } -} - -impl Mmio { - /// 以 volatile 方式读取寄存器。 - fn read(&self) -> T { - unsafe { core::ptr::read_volatile(self.addr) } - } -} - -impl Mmio { - /// 以 volatile 方式写寄存器。 - fn write(&self, value: T) { - unsafe { core::ptr::write_volatile(self.addr, value) } - } -} - -/// Goldfish RTC 原始寄存器访问器。 -struct GoldfishRtcRaw { - base_addr: usize, -} - -impl GoldfishRtcRaw { - /// 创建一个绑定到给定基址的 RTC 访问器。 - const fn new(base_addr: usize) -> Self { - Self { base_addr } - } - - /// 读取一个 32 位寄存器。 - fn reg(&self, offset: usize) -> Mmio { - Mmio::new(self.base_addr + offset) - } - - /// 初始化 RTC 设备。 - fn init(&self) { - // Goldfish RTC 在 virt 机型上默认可直接读时间;这里主动清一次中断状态。 - self.reg(REG_CLEAR_INTERRUPT).write(1); - // 兼容旧实现中暴露出来但当前未使用的寄存器,避免后续误判未映射。 - let _ = self.reg(REG_ALARM_LOW); - let _ = self.reg(REG_ALARM_HIGH); - } - - /// 读取当前 RTC 时间,单位为纳秒。 - fn read_time_ns(&self) -> u64 { - // 按设备规范必须先读 TIME_LOW,再读 TIME_HIGH,后者返回前一次低位读取对应的高位快照。 - let low = self.reg(REG_TIME_LOW).read() as u64; - let high = self.reg(REG_TIME_HIGH).read() as u64; - (high << 32) | low - } - - /// 写入当前 RTC 时间,单位为纳秒。 - fn write_time_ns(&self, time_ns: u64) { - // TODO:Goldfish RTC 对 TIME_LOW/TIME_HIGH 的写入不是原子的; - // 这里先提供一个“尽力设置当前时间”的接口,后续若用于严格校时, - // 需要增加回读校验或改为更高层 offset 方案。 - self.reg(REG_TIME_LOW).write(time_ns as u32); - self.reg(REG_TIME_HIGH).write((time_ns >> 32) as u32); - } -} - -/// RTC 驱动实例的内部状态。 -struct GoldfishRtc { - raw: GoldfishRtcRaw, -} - -impl GoldfishRtc { - /// 创建一个新的 Goldfish RTC 驱动实例。 - fn new(base_addr: usize) -> Self { - Self { - raw: GoldfishRtcRaw::new(base_addr), - } - } - - /// 初始化底层硬件状态。 - fn init(&self) { - self.raw.init(); - } - - /// 读取 RTC 当前时间,单位为纳秒。 - fn read_time_ns(&self) -> u64 { - self.raw.read_time_ns() - } - - /// 写入 RTC 当前时间,单位为纳秒。 - fn write_time_ns(&self, time_ns: u64) { - self.raw.write_time_ns(time_ns); - } -} - -lazy_static! { - /// 全局 RTC 驱动实例。 - static ref RTC: Arc> = - Arc::new(SpinNoIrqLock::new(GoldfishRtc::new(VIRT_RTC))); -} - -/// 标记 RTC 是否已完成初始化。 -static RTC_READY: AtomicBool = AtomicBool::new(false); - -/// 初始化全局 RTC 驱动。 -pub fn init() { - let rtc = RTC.lock(); - rtc.init(); - let time_ns = rtc.read_time_ns(); - drop(rtc); - // Mix RTC-derived timestamp into kernel entropy pool so getrandom can seed early. - crate::random::add_entropy(&time_ns.to_le_bytes()); - RTC_READY.store(true, Ordering::Release); - info!( - "rtc init done, realtime = {}.{:09} s", - time_ns / 1_000_000_000, - time_ns % 1_000_000_000 - ); -} - -/// 返回 RTC 是否已完成初始化。 -pub fn rtc_ready() -> bool { - RTC_READY.load(Ordering::Acquire) -} - -/// 读取当前 RTC 时间,单位为纳秒。 -pub fn read_time_ns() -> u64 { - RTC.lock().read_time_ns() -} - -/// 写入当前 RTC 时间,单位为纳秒。 -pub fn write_time_ns(time_ns: u64) { - RTC.lock().write_time_ns(time_ns); -} diff --git a/os/src/drivers/virtio/mod.rs b/os/src/drivers/virtio/mod.rs index d1cf6093..8f4ecf10 100644 --- a/os/src/drivers/virtio/mod.rs +++ b/os/src/drivers/virtio/mod.rs @@ -10,8 +10,8 @@ use virtio_drivers::{BufferDirection, Hal, PhysAddr as VirtioPhysAddr}; use crate::config::PAGE_SIZE; use crate::mm::{ - frame_alloc_contiguous, frame_dealloc_range, kernel_token, ContiguousFrames, PageTable, - PhysAddr as KernelPhysAddr, PhysPageNum, VirtAddr, + frame_alloc_contiguous, frame_dealloc_range, kernel_token, phys_to_virt, ContiguousFrames, + PageTable, PhysAddr as KernelPhysAddr, PhysPageNum, VirtAddr, }; use crate::sync::SpinNoIrqLock; @@ -35,6 +35,10 @@ struct BounceMapping { #[inline] fn translate_kernel_va(va: usize) -> usize { + if let Some(pa) = crate::platform::translate_direct_mapped_kernel_va(va) { + return pa; + } + PageTable::from_token(kernel_token()) .translate_va(VirtAddr::from(va)) .expect("virtio: translate_va failed") @@ -115,7 +119,8 @@ unsafe impl Hal for VirtioHal { let pa: KernelPhysAddr = ppn_base.into(); let paddr = pa.0 as VirtioPhysAddr; - let vaddr = NonNull::new(pa.0 as *mut u8).expect("virtio dma_alloc: null vaddr"); + let vaddr = NonNull::new(phys_to_virt(pa.0) as *mut u8) + .expect("virtio dma_alloc: null vaddr"); QUEUE_FRAMES.lock().push(frames); (paddr, vaddr) } @@ -142,7 +147,8 @@ unsafe impl Hal for VirtioHal { } unsafe fn mmio_phys_to_virt(paddr: VirtioPhysAddr, _size: usize) -> NonNull { - NonNull::new(paddr as usize as *mut u8).expect("virtio mmio_phys_to_virt: null") + NonNull::new(crate::platform::mmio_phys_to_virt(paddr as usize) as *mut u8) + .expect("virtio mmio_phys_to_virt: null") } unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> VirtioPhysAddr { @@ -162,7 +168,7 @@ unsafe impl Hal for VirtioHal { let base_ppn = frames.start_ppn(); let base_pa: KernelPhysAddr = base_ppn.into(); let base_pa_usize = base_pa.0; - let bounced_vaddr = base_pa_usize + page_offset; + let bounced_vaddr = phys_to_virt(base_pa_usize) + page_offset; let shared_paddr = (base_pa_usize + page_offset) as VirtioPhysAddr; if matches!( diff --git a/os/src/fs/devfs.rs b/os/src/fs/devfs.rs index f8788775..91e5d3a9 100644 --- a/os/src/fs/devfs.rs +++ b/os/src/fs/devfs.rs @@ -18,10 +18,10 @@ use fs::vfs::{VfsFileType, VfsNode}; use fs::{BlockDevice, STATFS_MAGIC_TMPFS, STATFS_NAMELEN_DEFAULT}; use crate::drivers::block::BLOCK_DEVICES; -use crate::drivers::rtc; use crate::fs::{Stat, StatMode}; use super::{empty_statfs, StatFs64}; use crate::mm::translated_ref; +use crate::platform::rtc; use crate::syscall::errno::ERRNO; use crate::syscall::{write_pod_to_user, Pod}; use crate::task::current_user_token; diff --git a/os/src/fs/page_cache.rs b/os/src/fs/page_cache.rs index bb3f63e1..cc9e866c 100644 --- a/os/src/fs/page_cache.rs +++ b/os/src/fs/page_cache.rs @@ -1122,7 +1122,7 @@ fn dynamic_watermarks() -> (usize, usize) { /// 初始化时使用的保守水位估算。 fn default_watermarks() -> (usize, usize) { - let managed_start = PhysAddr::from(ekernel as usize).ceil(); + let managed_start = PhysAddr::from(crate::mm::virt_to_phys(ekernel as usize)).ceil(); let managed_end = PhysAddr::from(MEMORY_END).floor(); let total_pages = max(1, managed_end.0.saturating_sub(managed_start.0)); let high_watermark = max(128, total_pages / 4); diff --git a/os/src/fs/tty.rs b/os/src/fs/tty.rs index d36516f0..91535f8a 100644 --- a/os/src/fs/tty.rs +++ b/os/src/fs/tty.rs @@ -543,6 +543,12 @@ impl TtyCore { if let Some(byte) = self.take_ready_byte() { return byte; } + if !crate::platform::console_rx_irq_ready() { + // Fall back to cooperative polling only before the platform + // external IRQ path has finished setup. + crate::task::yield_current_and_run_next(); + continue; + } // 3. 阻塞,直到中断路径补满输入或有信号到来。 self.read_wq .wait_with_reason_or_skip(WaitReason::UartRx, || self.read_ready()); diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs new file mode 100644 index 00000000..e5d27544 --- /dev/null +++ b/os/src/hal/mod.rs @@ -0,0 +1,204 @@ +//! HAL — re-exports arch/platform concrete types under stable aliases. +#![allow(missing_docs)] + +pub mod traits; + +use crate::hal::traits::{AddressSpaceToken, HartId, PTEFlags, PagingArch}; + +#[cfg(target_arch = "riscv64")] +pub use crate::arch::riscv::{ + RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt, + RiscvTrapContextAbi as ArchTrapContextAbi, + RiscvTrapMachine as ArchTrapMachine, Sv39Paging as ArchPaging, +}; + +#[cfg(target_arch = "loongarch64")] +pub use crate::arch::loongarch64::{ + LoongArchHartId as ArchHart, LoongArchInterruptControl as ArchInterrupt, + LoongArchTrapContextAbi as ArchTrapContextAbi, + LoongArchTrapMachine as ArchTrapMachine, LoongArchPaging as ArchPaging, +}; + +pub use crate::platform::PlatformImpl as Plat; + +/// Return the current hart id. +#[inline] +pub fn hartid() -> usize { + ArchHart::current() +} + +/// Initialize the current hart-local state, including floating-point availability. +#[inline] +pub unsafe fn init_with_hartid(hart_id: usize) -> usize { + ArchHart::init(hart_id); + ArchHart::enable_fp(); + hart_id +} + +/// Enable the local floating-point unit on the current hart. +#[inline] +pub unsafe fn enable_fp() { + ArchHart::enable_fp(); +} + +/// Return whether local interrupts are currently enabled. +#[inline] +pub fn local_irqs_enabled() -> bool { + ArchHart::irqs_enabled() +} + +/// Disable local interrupts on the current hart. +#[inline] +pub unsafe fn disable_local_irqs() { + ArchHart::disable_irqs(); +} + +/// Enable local interrupts on the current hart. +#[inline] +pub unsafe fn enable_local_irqs() { + ArchHart::enable_irqs(); +} + +/// Wait for the next interrupt/event on the current hart. +#[inline] +pub unsafe fn wait_for_interrupt() { + ArchHart::wait_for_interrupt(); +} + +/// Execute the architecture-specific scheduler idle wait sequence. +#[inline] +pub unsafe fn enable_irqs_and_wait() { + ArchHart::enable_irqs_and_wait(); +} + +/// Build an architecture-specific address-space token from a root page-table PPN. +#[inline] +pub fn make_address_space_token(root_ppn: usize) -> AddressSpaceToken { + ArchPaging::make_token(root_ppn) +} + +/// Extract the root page-table PPN from an architecture-specific address-space token. +#[inline] +pub fn root_ppn_from_token(token: AddressSpaceToken) -> usize { + ArchPaging::root_ppn(token) +} + +/// Activate the given address space on the current hart. +#[inline] +pub unsafe fn activate_address_space(token: AddressSpaceToken) { + ArchPaging::activate_token(token); +} + +/// Read the current hart's active address-space token. +#[inline] +pub unsafe fn current_address_space_token() -> AddressSpaceToken { + ArchPaging::current_token() +} + +/// Flush the local TLB on the current hart. +#[inline] +pub unsafe fn flush_tlb() { + ArchPaging::flush_tlb(); +} + +/// Encode a non-leaf directory PTE (must not set GNR/GNX on LoongArch). +#[inline] +pub fn make_dir_entry(ppn: usize) -> usize { + ArchPaging::make_dir_entry(ppn) +} + +/// Encode one architecture-specific PTE from a physical page number and semantic flags. +#[inline] +pub fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + ArchPaging::make_pte(ppn, flags) +} + +/// Extract the pointed-to physical page number from one architecture-specific PTE. +#[inline] +pub fn pte_ppn(entry_bits: usize) -> usize { + ArchPaging::pte_ppn(entry_bits) +} + +/// Extract the semantic flags from one architecture-specific PTE. +#[inline] +pub fn pte_flags(entry_bits: usize) -> PTEFlags { + ArchPaging::pte_flags(entry_bits) +} + +/// Return whether one raw PTE/directory entry should be treated as present. +#[inline] +pub fn pte_is_valid(entry_bits: usize) -> bool { + ArchPaging::pte_is_valid(entry_bits) +} + +/// Normalize leaf PTE flags for the current architecture. +#[inline] +pub fn normalize_leaf_pte_flags(flags: PTEFlags) -> PTEFlags { + ArchPaging::normalize_leaf_flags(flags) +} + +/// Convert an arbitrary raw virtual address input into the stored form. +#[inline] +pub fn normalize_virt_addr_input(bits: usize) -> usize { + ArchPaging::normalize_virt_addr_input(bits) +} + +/// Convert an arbitrary raw virtual address input into a virtual page number. +#[inline] +pub fn virt_page_num_from_addr(bits: usize) -> usize { + ArchPaging::virt_page_num_from_addr(bits) +} + +/// Return the default trap-context page permissions for this architecture. +#[inline] +pub fn trap_context_flags() -> PTEFlags { + ArchPaging::trap_context_flags() +} + +/// Return the architecture's physical address width in bits. +#[inline] +pub const fn phys_addr_bits() -> usize { + ArchPaging::PA_BITS +} + +/// Return the architecture's virtual address width in bits. +#[inline] +pub const fn virt_addr_bits() -> usize { + ArchPaging::VA_BITS +} + +/// Return the architecture's physical page-number width in bits. +#[inline] +pub const fn phys_page_num_bits() -> usize { + ArchPaging::PPN_BITS +} + +/// Return the architecture's page-table level count. +#[inline] +pub const fn page_table_levels() -> usize { + ArchPaging::LEVELS +} + +/// Return the bit-width of one page-table index. +#[inline] +pub const fn page_table_index_bits() -> usize { + ArchPaging::INDEX_BITS +} + +/// Return the exclusive end of the user address space. +#[inline] +pub fn user_space_end() -> usize { + ArchPaging::user_space_end() +} + +/// Canonicalize one virtual address according to the current architecture. +#[inline] +pub fn canonicalize_vaddr(bits: usize) -> usize { + ArchPaging::canonicalize_vaddr(bits) +} + +/// Return the page-table index at one level for the given virtual page number. +#[inline] +pub fn vpn_index(vpn: usize, level: usize) -> usize { + ArchPaging::vpn_index(vpn, level) +} diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs new file mode 100644 index 00000000..bd464b01 --- /dev/null +++ b/os/src/hal/traits.rs @@ -0,0 +1,262 @@ +//! HAL trait definitions — pure interfaces, no implementations. + +bitflags! { + /// Architecture-neutral page-table entry semantics. + pub struct PTEFlags: u16 { + /// Entry is present/valid. + const V = 1 << 0; + /// Entry permits reads. + const R = 1 << 1; + /// Entry permits writes. + const W = 1 << 2; + /// Entry permits instruction fetches. + const X = 1 << 3; + /// Entry is user-accessible. + const U = 1 << 4; + /// Entry is global across address spaces. + const G = 1 << 5; + /// Entry has been accessed. + const A = 1 << 6; + /// Entry has been dirtied by writes. + const D = 1 << 7; + } +} + +/// Per-hart interrupt control (enable/disable, trap entry setup). +pub trait InterruptControl { + /// Enable supervisor timer interrupt. + unsafe fn enable_timer(); + /// Disable supervisor timer interrupt. + unsafe fn disable_timer(); + /// Enable supervisor external interrupt. + unsafe fn enable_external(); + /// Disable supervisor external interrupt. + unsafe fn disable_external(); + /// Enable supervisor software interrupt. + unsafe fn enable_software(); + /// Clear pending supervisor software interrupt. + unsafe fn clear_software_pending(); + /// Set trap entry for kernel-mode traps. + unsafe fn set_kernel_trap_entry(); + /// Set trap entry for user-mode traps. + unsafe fn set_user_trap_entry(); +} + +/// Architecture-normalized trap causes observed by common kernel logic. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TrapCause { + UserSyscall, + StorePageFault, + LoadPageFault, + InstructionPageFault, + StoreFault, + InstructionFault, + LoadFault, + IllegalInstruction, + TimerInterrupt, + SoftwareInterrupt, + ExternalInterrupt, + Unknown, +} + +/// Trap metadata passed from arch-specific code into common trap handling. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct TrapInfo { + /// Decoded trap cause. + pub cause: TrapCause, + /// Fault address or trap-value register contents when applicable. + pub fault_addr: usize, +} + +/// Arch-specific trap/syscall machine operations used by common code. +pub trait TrapMachine { + /// Read the current trap cause and associated fault address. + fn read_trap_info() -> TrapInfo; + /// Return to user mode using the given trap-context VA and address-space token. + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> !; + /// Size in bytes of the userspace syscall instruction. + fn syscall_instruction_len() -> usize; + /// Machine-code trampoline used for `rt_sigreturn`. + fn rt_sigreturn_trampoline() -> &'static [u8]; +} + +/// Read and write the current hart id. +pub trait HartId { + /// Return current hart id from arch register. + fn current() -> usize; + /// Write hart id to arch register at boot. + unsafe fn init(id: usize); + /// Enable the local floating-point unit for subsequent kernel/user execution. + unsafe fn enable_fp(); + /// Return whether local interrupts are currently enabled on this hart. + fn irqs_enabled() -> bool; + /// Disable local interrupts on this hart. + unsafe fn disable_irqs(); + /// Enable local interrupts on this hart. + unsafe fn enable_irqs(); + /// Wait for the next interrupt/event while keeping the current interrupt state. + unsafe fn wait_for_interrupt(); + /// Enter the architecture-specific idle wait sequence used by the scheduler. + unsafe fn enable_irqs_and_wait() { + Self::enable_irqs(); + Self::wait_for_interrupt(); + Self::disable_irqs(); + } +} + +/// Architecture-specific user trap-context ABI helpers. +pub trait TrapContextAbi { + /// Architecture-owned trap-frame layout stored at the front of `TrapContext`. + type Frame: Copy; + + /// Construct a trap frame prepared for first return to user mode. + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: AddressSpaceToken, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame; + /// Return the raw general-purpose register value at `index`. + fn reg(frame: &Self::Frame, index: usize) -> usize; + /// Update the raw general-purpose register value at `index`. + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize); + /// Return the saved user PC from the trap context. + fn user_pc(frame: &Self::Frame) -> usize; + /// Set the saved user PC in the trap context. + fn set_user_pc(frame: &mut Self::Frame, pc: usize); + /// Return the saved user SP from the trap context. + fn user_sp(frame: &Self::Frame) -> usize; + /// Set the saved user SP in the trap context. + fn set_user_sp(frame: &mut Self::Frame, sp: usize); + /// Return the saved return-address register. + fn ra(frame: &Self::Frame) -> usize; + /// Set the saved return-address register. + fn set_ra(frame: &mut Self::Frame, ra: usize); + /// Return the saved TLS/thread-pointer register. + fn tls(frame: &Self::Frame) -> usize; + /// Set the saved TLS/thread-pointer register. + fn set_tls(frame: &mut Self::Frame, tls: usize); + /// Return the saved syscall number. + fn syscall_nr(frame: &Self::Frame) -> usize; + /// Return the saved syscall arguments. + fn syscall_args(frame: &Self::Frame) -> [usize; 6]; + /// Return the saved syscall return value. + fn syscall_ret(frame: &Self::Frame) -> usize; + /// Set the saved syscall return value. + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize); + /// Set one syscall/user argument register. + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize); + /// Set the kernel hart id restored by the trap trampoline. + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize); + /// Set the kernel stack pointer restored on the next trap entry. + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize); + /// Export the Linux-compatible signal GPR layout. + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32]; + /// Import the Linux-compatible signal GPR layout back into the trap context. + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]); + /// Return the index of the a0 register within the 32-entry signal GPR array + fn signal_gpr_arg0_index() -> usize; + /// Copy floating-point state into an external signal frame. + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32); + /// Restore floating-point state from an external signal frame. + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32); +} + +/// Opaque address-space activation token used by the current architecture. +pub type AddressSpaceToken = usize; + +/// Arch-level paging operations. +pub trait PagingArch { + /// Page-table entry type. + type Entry: Copy; + /// Physical address width in bits. + const PA_BITS: usize; + /// Virtual address width in bits. + const VA_BITS: usize; + /// Physical page-number width in bits. + const PPN_BITS: usize; + /// Architecture-specific mode bits embedded in the root token. + const ROOT_TOKEN_MODE: usize; + /// Number of page-table levels. + const LEVELS: usize; + /// Bits consumed per page-table level. + const INDEX_BITS: usize; + /// Build an architecture token from a root page-table physical page number. + fn make_token(root_ppn: usize) -> AddressSpaceToken; + /// Extract the root page-table physical page number from an architecture token. + fn root_ppn(token: AddressSpaceToken) -> usize; + /// Activate the given address-space token and flush the local TLB. + unsafe fn activate_token(token: AddressSpaceToken); + /// Read current address-space token. + unsafe fn current_token() -> AddressSpaceToken; + /// Flush entire TLB. + unsafe fn flush_tlb(); + /// Encode one leaf/intermediate PTE for this architecture. + fn make_pte(ppn: usize, flags: PTEFlags) -> usize; + /// Encode a non-leaf directory entry pointing to the next page-table level. + /// Architectures where leaf and directory entries have different encodings + /// (e.g. LoongArch, which must NOT set GNR/GNX in directory entries) should + /// override this. Default: delegates to make_pte with V only. + fn make_dir_entry(ppn: usize) -> usize { + Self::make_pte(ppn, PTEFlags::V) + } + /// Extract the pointed-to physical page number from one raw PTE. + fn pte_ppn(entry_bits: usize) -> usize; + /// Extract the semantic flags from one raw PTE. + fn pte_flags(entry_bits: usize) -> PTEFlags; + /// Return whether one raw PTE/directory entry should be treated as present. + fn pte_is_valid(entry_bits: usize) -> bool { + (Self::pte_flags(entry_bits) & PTEFlags::V) != PTEFlags::empty() + } + /// Normalize leaf PTE flags for the current architecture. + fn normalize_leaf_flags(flags: PTEFlags) -> PTEFlags { + flags + } + /// Convert an arbitrary raw virtual address input into the stored form. + fn normalize_virt_addr_input(bits: usize) -> usize { + bits & ((1usize << Self::VA_BITS) - 1) + } + /// Convert an arbitrary raw virtual address input into a virtual page number. + fn virt_page_num_from_addr(bits: usize) -> usize { + Self::normalize_virt_addr_input(bits) >> 12 + } + /// Return the default trap-context page permissions for this architecture. + fn trap_context_flags() -> PTEFlags { + PTEFlags::R | PTEFlags::W + } + /// Return the exclusive end of the canonical low-half user address range. + fn user_space_end() -> usize { + 1usize << (Self::VA_BITS - 1) + } + /// Canonicalize a virtual address value according to the current architecture. + fn canonicalize_vaddr(bits: usize) -> usize { + if bits >= (1usize << (Self::VA_BITS - 1)) { + bits | (!((1usize << Self::VA_BITS) - 1)) + } else { + bits + } + } + /// Return the page-table index at `level` for the given virtual page number. + /// + /// `level=0` refers to the root-most level and `level=LEVELS-1` to the leaf level. + fn vpn_index(vpn: usize, level: usize) -> usize; +} + +/// Platform timer: read monotonic time, program next interrupt. +pub trait Timer { + /// Read raw tick counter. + fn read_time() -> usize; + /// Program next timer interrupt deadline (raw ticks). + fn set_next(deadline: usize); + /// Clock frequency in Hz. + fn clock_freq() -> usize; +} + +/// Hart lifecycle control (SMP startup, IPI). +pub trait HartCtrl { + /// Start a hart at the given address with an opaque argument. + fn start_hart(hart_id: usize, start_addr: usize, opaque: usize) -> Result<(), ()>; + /// Send IPI to harts described by the mask. + fn send_ipi(hart_mask: usize); +} diff --git a/os/src/hart.rs b/os/src/hart.rs deleted file mode 100644 index 70ed8ba5..00000000 --- a/os/src/hart.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! hart-local 辅助接口。 -//! -//! 在 SMP A 阶段,内核态约定将 `tp` 用于保存当前 hart 的本地状态。 -//! 用户态可能会把 `tp` 当作 TLS 指针使用,因此 trap 边界会负责在 -//! 用户/内核切换时保存和恢复两边各自的 `tp` 语义。后续模块统一通过 -//! 这里提供的接口获取当前 hart 信息,而不是在各处直接读取 CSR。 - -use core::arch::asm; - -/// 使用当前已经保存在 `tp` 中的 hart id 完成初始化接口兼容。 -pub fn init() -> usize { - hartid() -} - -/// 使用启动阶段已经得到的 hart id 初始化 hart-local 寄存器。 -/// -/// 这用于 RustSBI / HSM 已经通过 `a0` 把 hart id 传给 Rust 入口的场景, -/// 避免在 Rust 中再额外依赖某个特定 CSR 读取路径。 -pub fn init_with_hartid(hart_id: usize) -> usize { - unsafe { write_tp(hart_id) }; - hart_id -} - -/// 从 `tp` 中读取当前 hart id。 -#[inline] -pub fn hartid() -> usize { - let hart_id; - unsafe { - asm!("mv {}, tp", out(reg) hart_id); - } - hart_id -} - -#[inline] -unsafe fn write_tp(hart_id: usize) { - asm!("mv tp, {}", in(reg) hart_id); -} diff --git a/os/src/linker-loongarch64.ld b/os/src/linker-loongarch64.ld new file mode 100644 index 00000000..576c90d3 --- /dev/null +++ b/os/src/linker-loongarch64.ld @@ -0,0 +1,54 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) +VIRT_BASE = 0x9000000090000000; +PHYS_BASE = 0x90000000; + +SECTIONS +{ + . = VIRT_BASE; + skernel = .; + + stext = .; + .text : AT(PHYS_BASE) { + *(.text.entry) + . = ALIGN(4K); + strampoline = .; + *(.text.trampoline); + . = ALIGN(4K); + *(.text .text.*) + } + + . = ALIGN(4K); + etext = .; + srodata = .; + .rodata : AT(PHYS_BASE + (srodata - VIRT_BASE)) { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + + . = ALIGN(4K); + erodata = .; + sdata = .; + .data : AT(PHYS_BASE + (sdata - VIRT_BASE)) { + *(.data .data.*) + *(.sdata .sdata.*) + } + + . = ALIGN(4K); + edata = .; + sbss_with_stack = .; + .bss : AT(PHYS_BASE + (sbss_with_stack - VIRT_BASE)) { + *(.bss.stack) + sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + } + + . = ALIGN(4K); + ebss = .; + ekernel = .; + + /DISCARD/ : { + *(.eh_frame) + } +} diff --git a/os/src/loader.rs b/os/src/loader.rs deleted file mode 100644 index e5e7775d..00000000 --- a/os/src/loader.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Loading user applications into memory - -/// Get the total number of applications. -use alloc::vec::Vec; -use lazy_static::*; -///get app number -pub fn get_num_app() -> usize { - extern "C" { - fn _num_app(); - } - unsafe { (_num_app as usize as *const usize).read_volatile() } -} -/// get applications data -pub fn get_app_data(app_id: usize) -> &'static [u8] { - extern "C" { - fn _num_app(); - } - let num_app_ptr = _num_app as usize as *const usize; - let num_app = get_num_app(); - let app_start = unsafe { core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) }; - assert!(app_id < num_app); - unsafe { - core::slice::from_raw_parts( - app_start[app_id] as *const u8, - app_start[app_id + 1] - app_start[app_id], - ) - } -} - -lazy_static! { - ///All of app's name - static ref APP_NAMES: Vec<&'static str> = { - let num_app = get_num_app(); - extern "C" { - fn _app_names(); - } - let mut start = _app_names as usize as *const u8; - let mut v = Vec::new(); - unsafe { - for _ in 0..num_app { - let mut end = start; - while end.read_volatile() != b'\0' { - end = end.add(1); - } - let slice = core::slice::from_raw_parts(start, end as usize - start as usize); - let str = core::str::from_utf8(slice).unwrap(); - v.push(str); - start = end.add(1); - } - } - v - }; -} - -#[allow(unused)] -///get app data from name -pub fn get_app_data_by_name(name: &str) -> Option<&'static [u8]> { - let num_app = get_num_app(); - (0..num_app) - .find(|&i| APP_NAMES[i] == name) - .map(get_app_data) -} -///list all apps -pub fn list_apps() { - println!("/**** APPS ****"); - for app in APP_NAMES.iter() { - println!("{}", app); - } - println!("**************/"); -} diff --git a/os/src/main.rs b/os/src/main.rs index 07c6adb3..b6a6f009 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -11,7 +11,7 @@ //! - [`fs`]: Separate user from file system with some structures //! //! The operating system also starts in this module. Kernel code starts -//! executing from `entry.asm`, after which [`rust_main()`] is called to +//! executing from the architecture entry assembly, after which [`rust_main()`] is called to //! initialize various pieces of functionality. (See its source code for //! details.) //! @@ -25,6 +25,7 @@ #![feature(panic_info_message)] #![feature(alloc_error_handler)] + #[macro_use] extern crate log; @@ -33,15 +34,15 @@ extern crate alloc; #[macro_use] extern crate bitflags; -#[path = "boards/qemu.rs"] -mod board; +pub mod arch; +pub mod hal; +pub mod platform; #[macro_use] mod console; pub mod config; pub mod drivers; pub mod fs; -pub mod hart; pub mod ipc; pub mod keys; pub mod lang_items; @@ -59,20 +60,15 @@ pub mod task; pub mod timer; pub mod trap; -use core::arch::global_asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -global_asm!(include_str!("entry.asm")); +use alloc::format; + +#[cfg(target_arch = "riscv64")] +use crate::platform::riscv::qemu_virt::sbi::hart_get_status; const ANSI_RESET: &str = "\u{1b}[0m"; -const ANSI_FRAME: &str = "\u{1b}[38;5;45m"; -const ANSI_GLOW: &str = "\u{1b}[38;5;117m"; -const ANSI_TITLE: &str = "\u{1b}[1;38;5;159m"; -const ANSI_SUBTITLE: &str = "\u{1b}[38;5;111m"; -const ANSI_STAGE: &str = "\u{1b}[38;5;81m"; -const ANSI_DETAIL: &str = "\u{1b}[38;5;252m"; -const ANSI_ACCENT: &str = "\u{1b}[38;5;218m"; /// secondary hart 在访问 `.bss` 中的全局对象前,必须先等 bootstrap hart 完成 `clear_bss()`。 /// @@ -102,7 +98,29 @@ fn clear_bss() { } } + +macro_rules! ansi_fg_256 { + ($n:literal) => { + concat!("\x1b[38;5;", stringify!($n), "m") + }; +} + +#[allow(non_upper_case_globals)] +const ANSI_FRAME: &str = if cfg!(target_arch = "loongarch64") { + ansi_fg_256!(222) +} else { + ansi_fg_256!(45) +}; + +#[allow(non_upper_case_globals)] +const ANSI_GLOW: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(226) } else { ansi_fg_256!(117) }; +const ANSI_TITLE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(214) } else { ansi_fg_256!(159) }; +const ANSI_SUBTITLE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(11) } else { ansi_fg_256!(111) }; +const ANSI_STAGE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(229) } else { ansi_fg_256!(81) }; +const ANSI_DETAIL: &str = ansi_fg_256!(252); + fn print_boot_splash(hart_id: usize, hart_count: usize) { + const LOGO: [&str; 7] = [ " ______ ____ _____ ", " / ____/___ _________ ___ / __ \\ / ___/ ", @@ -146,7 +164,7 @@ fn print_boot_splash(hart_id: usize, hart_count: usize) { "{frame}|{reset} {subtitle}{msg:<66}{reset} {frame}|{reset}", frame = ANSI_FRAME, subtitle = ANSI_SUBTITLE, - msg = "Target: RISC-V virt machine", + msg = format!("Target: {} - {}", crate::platform::machine_name(), crate::platform::platform_name()), reset = ANSI_RESET, ); println!( @@ -156,6 +174,7 @@ fn print_boot_splash(hart_id: usize, hart_count: usize) { ); println!(""); } + fn print_boot_stage(stage: &str, detail: &str) { println!( "{stage_color}>> {stage:<12}{reset} {detail_color}{detail}{reset}", @@ -173,18 +192,20 @@ fn print_boot_stage(stage: &str, detail: &str) { fn init_local_hart(hart_id: usize) { trap::init_hart(); timer::init_hart(); - drivers::plic::init_hart(hart_id); + crate::platform::init_local_hart(); + crate::platform::init_external_irq_hart(hart_id); mm::mark_online(hart_id); debug!("hart {} local init done", hart_id); } /// Probe the firmware-visible hart IDs and return the number of usable harts. +#[cfg(target_arch = "riscv64")] fn detect_hart_count() -> usize { const SBI_SUCCESS: isize = 0; const SBI_ERR_INVALID_PARAM: isize = -3; let mut hart_count = 0usize; for target_hart in 0..config::MAX_HARTS { - let status = sbi::hart_get_status(target_hart); + let status = hart_get_status(target_hart); if status.error == SBI_ERR_INVALID_PARAM { break; } @@ -195,69 +216,10 @@ fn detect_hart_count() -> usize { hart_count.max(1) } -/// 记录当前环境下各 hart 的 HSM 状态,并尝试拉起处于 stopped 状态的 hart。 -/// -/// 这里的目标不是“盲目对所有 hart 重复 `hart_start`”,而是先看清固件报告的 -/// 状态,再只对明确处于 `Stopped` 的 hart 发起启动请求。 -fn probe_and_start_other_harts(bootstrap_hart_id: usize) { - extern "C" { - fn _start(); - } - - const SBI_SUCCESS: isize = 0; - const SBI_ERR_INVALID_PARAM: isize = -3; - const SBI_ERR_ALREADY_AVAILABLE: isize = -6; - - info!( - "hart {} entering HSM probe/start loop", - bootstrap_hart_id - ); - - for target_hart in 0..config::MAX_HARTS { - let status = sbi::hart_get_status(target_hart); - if status.error == SBI_ERR_INVALID_PARAM { - info!( - "hart {} got invalid hart id while probing hart {}, stop scan", - bootstrap_hart_id, target_hart - ); - break; - } - if status.error != SBI_SUCCESS { - info!( - "hart {} HSM status query for hart {} failed: error={}, value={}", - bootstrap_hart_id, target_hart, status.error, status.value - ); - continue; - } - - let state = sbi::hart_state(status.value); - info!( - "hart {} sees hart {} in HSM state {:?}", - bootstrap_hart_id, target_hart, state - ); - - if target_hart == bootstrap_hart_id { - continue; - } - - if let sbi::HartState::Stopped = state { - let ret = sbi::hart_start(target_hart, _start as usize, 0); - match ret.error { - SBI_SUCCESS => info!( - "hart {} requested startup for hart {}", - bootstrap_hart_id, target_hart - ), - SBI_ERR_ALREADY_AVAILABLE => info!( - "hart {} found hart {} already available while starting", - bootstrap_hart_id, target_hart - ), - error => info!( - "hart {} failed to start hart {}: error={}, value={}", - bootstrap_hart_id, target_hart, error, ret.value - ), - } - } - } +/// Probe the usable hart count on non-RISC-V platforms. +#[cfg(target_arch = "loongarch64")] +fn detect_hart_count() -> usize { + 1 } /// 竞争并记录负责一次性全局初始化的 bootstrap hart。 @@ -288,8 +250,10 @@ fn wait_for_bootstrap() { fn first_hart_main(hart_id: usize) -> ! { clear_bss(); BOOT_BSS_READY.store(0, Ordering::Release); + // Install TLB refill handler and page-walker CSRs before activating page tables. + trap::init(); mm::init(); - mm::remap_test(); + // mm::remap_test(); klog::init(); let hart_count = detect_hart_count(); print_boot_splash(hart_id, hart_count); @@ -297,6 +261,7 @@ fn first_hart_main(hart_id: usize) -> ! { info!("hart {} boot", hart_id); info!("hart {} elected as bootstrap hart", hart_id); drivers::init(); + platform::init(); print_boot_stage("devices", "virtio buses enumerated"); net::init(); print_boot_stage("network", "smoltcp stack synchronized"); @@ -304,7 +269,7 @@ fn first_hart_main(hart_id: usize) -> ! { print_boot_stage("storage", "root filesystem mounted"); timer::init_realtime_offset_from_rtc(); print_boot_stage("clock", "realtime source calibrated"); - probe_and_start_other_harts(hart_id); + platform::start_secondary_harts(hart_id); init_local_hart(hart_id); print_boot_stage("scheduler", "bootstrap hart entering run queue"); task::add_initproc(); @@ -322,7 +287,7 @@ fn first_hart_main(hart_id: usize) -> ! { fn secondary_hart_main(hart_id: usize) -> ! { wait_for_bootstrap(); mm::activate_kernel_space(); // 激活内核页表:但 satp 是 per-hart 寄存器 - info!("hart {} boot", hart_id); + info!("hart {} entered secondary_hart_main", hart_id); init_local_hart(hart_id); debug!("hart {} entered scheduler", hart_id); sched::run_tasks(); @@ -335,10 +300,7 @@ fn secondary_hart_main(hart_id: usize) -> ! { /// 第一个进入该入口的 hart 会成为 bootstrap hart,负责一次性全局初始化 /// 并进入调度器;其他 hart 等待 bootstrap 完成后只做本地初始化并进入 idle。 pub fn rust_main(hart_id: usize) -> ! { - let _hart_id = hart::init_with_hartid(hart_id); - unsafe { - riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); - } + unsafe { crate::hal::init_with_hartid(hart_id) }; if !try_claim_bootstrap_hart(hart_id) { secondary_hart_main(hart_id); } else { diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 295816c4..aa23a35e 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -4,12 +4,22 @@ use super::PageTableEntry; use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; -const PA_WIDTH_SV39: usize = 56; -const VA_WIDTH_SV39: usize = 39; -const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - PAGE_SIZE_BITS; -const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS; -/// Exclusive end of the canonical low-half user virtual-address range under Sv39. -pub const USER_SPACE_END: usize = 1usize << (VA_WIDTH_SV39 - 1); +/// Convert a physical address to a kernel virtual address for direct access. +#[inline(always)] +pub fn phys_to_virt(pa: usize) -> usize { + crate::platform::direct_map_phys_to_virt(pa) +} + +/// Convert one direct-mapped kernel virtual address back to a physical address. +#[inline(always)] +pub fn virt_to_phys(va: usize) -> usize { + crate::platform::direct_map_virt_to_phys(va) +} +/// Exclusive end of the canonical low-half user virtual-address range. +pub const USER_SPACE_END: usize = { + let _ = crate::hal::page_table_levels(); + 1usize << (crate::hal::virt_addr_bits() - 1) +}; /// Physical Address #[repr(C)] @@ -60,22 +70,22 @@ impl Debug for PhysPageNum { impl From for PhysAddr { fn from(v: usize) -> Self { - Self(v & ((1 << PA_WIDTH_SV39) - 1)) + Self(v & ((1 << crate::hal::phys_addr_bits()) - 1)) } } impl From for PhysPageNum { fn from(v: usize) -> Self { - Self(v & ((1 << PPN_WIDTH_SV39) - 1)) + Self(v & ((1 << crate::hal::phys_page_num_bits()) - 1)) } } impl From for VirtAddr { fn from(v: usize) -> Self { - Self(v & ((1 << VA_WIDTH_SV39) - 1)) + Self(crate::hal::normalize_virt_addr_input(v)) } } impl From for VirtPageNum { fn from(v: usize) -> Self { - Self(v & ((1 << VPN_WIDTH_SV39) - 1)) + Self(crate::hal::virt_page_num_from_addr(v)) } } impl From for usize { @@ -90,11 +100,7 @@ impl From for usize { } impl From for usize { fn from(v: VirtAddr) -> Self { - if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) { - v.0 | (!((1 << VA_WIDTH_SV39) - 1)) - } else { - v.0 - } + crate::hal::canonicalize_vaddr(v.0) } } impl From for usize { @@ -165,39 +171,27 @@ impl From for PhysAddr { } } -impl VirtPageNum { - /// Get the indexes of the page table entry - pub fn indexes(&self) -> [usize; 3] { - let mut vpn = self.0; - let mut idx = [0usize; 3]; - for i in (0..3).rev() { - idx[i] = vpn & 511; - vpn >>= 9; - } - idx - } -} - impl PhysAddr { /// Get the immutable reference of physical address pub fn get_ref(&self) -> &'static T { - unsafe { (self.0 as *const T).as_ref().unwrap() } + unsafe { (phys_to_virt(self.0) as *const T).as_ref().unwrap() } } /// Get the mutable reference of physical address pub fn get_mut(&self) -> &'static mut T { - unsafe { (self.0 as *mut T).as_mut().unwrap() } + unsafe { (phys_to_virt(self.0) as *mut T).as_mut().unwrap() } } } impl PhysPageNum { /// Get the reference of page table(array of ptes) pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { let pa: PhysAddr = (*self).into(); - unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) } + let entry_count = 1usize << crate::hal::page_table_index_bits(); + unsafe { core::slice::from_raw_parts_mut(phys_to_virt(pa.0) as *mut PageTableEntry, entry_count) } } /// Get the reference of page(array of bytes) pub fn get_bytes_array(&self) -> &'static mut [u8] { let pa: PhysAddr = (*self).into(); - unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) } + unsafe { core::slice::from_raw_parts_mut(phys_to_virt(pa.0) as *mut u8, 4096) } } /// Get the mutable reference of physical address pub fn get_mut(&self) -> &'static mut T { diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index 9963ddbf..e062dbc1 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -1,6 +1,6 @@ //! Physical page frame allocator -use super::{PhysAddr, PhysPageNum}; +use super::{virt_to_phys, PhysAddr, PhysPageNum}; use crate::fs::PAGE_CACHE_MANAGER; use crate::mm::heap_allocator::KERNEL_HEAP_BYTES; use crate::{config::MEMORY_END, sync::SpinNoIrqLock}; @@ -279,7 +279,7 @@ pub fn init_frame_allocator() { fn ekernel(); } FRAME_ALLOCATOR.lock().init( - PhysAddr::from(ekernel as usize).ceil(), + PhysAddr::from(virt_to_phys(ekernel as usize)).ceil(), PhysAddr::from(MEMORY_END).floor(), ); } diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 3c93d33a..b3d10776 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -1,15 +1,15 @@ //! The heap allocator. use super::frame_allocator::{frame_alloc, frame_alloc_contiguous, frame_dealloc}; -use super::page_table::PTEFlags; -use super::{PageTableEntry, PhysPageNum, VirtAddr, KERNEL_SPACE}; -use crate::config::{KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE, MEMORY_END, PAGE_SIZE}; +use super::{phys_to_virt, PageTableEntry, PhysPageNum, PTEFlags, VirtAddr, KERNEL_SPACE}; +use crate::config::{ + KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE, MEMORY_END, PAGE_SIZE, PAGE_SIZE_BITS, +}; use crate::sync::SpinNoIrqLock; use buddy_system_allocator::LockedHeap; use core::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use riscv::register::sstatus; #[global_allocator] static HEAP_ALLOCATOR: KernelHeapAllocator = KernelHeapAllocator::new(); @@ -38,8 +38,8 @@ struct HeapIrqGuard { impl HeapIrqGuard { #[inline] fn new() -> Self { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; Self { sie_was_enabled } } } @@ -48,7 +48,7 @@ impl Drop for HeapIrqGuard { #[inline] fn drop(&mut self) { if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } @@ -61,24 +61,33 @@ pub static KERNEL_HEAP_BYTES: AtomicUsize = AtomicUsize::new(0); static KERNEL_HEAP_VIRTUAL_BYTES: AtomicUsize = AtomicUsize::new(0); static KERNEL_HEAP_VIRTUAL_READY: AtomicBool = AtomicBool::new(false); +const ROOT_ENTRY_SPAN: usize = 1usize + << (PAGE_SIZE_BITS + + (crate::hal::page_table_levels() - 1) * crate::hal::page_table_index_bits()); + +const _: () = assert!( + crate::hal::page_table_levels() >= 2, + "kernel heap virtual window requires a multi-level page table" +); + // The dedicated heap page-table machinery below relies on the whole virtual -// heap window living under a single Sv39 1GiB (VPN[2]) entry, i.e. one shared -// level-1 table, so that runtime heap growth never has to touch the kernel root -// page table (which other code mutates under `KERNEL_SPACE`). +// heap window living under a single root page-table entry, i.e. one shared +// first-level subtree, so that runtime heap growth never has to touch the +// kernel root page table (which other code mutates under `KERNEL_SPACE`). const _: () = assert!( - KERNEL_HEAP_BASE & ((1usize << 30) - 1) == 0, - "KERNEL_HEAP_BASE must be 1GiB-aligned" + KERNEL_HEAP_BASE & (ROOT_ENTRY_SPAN - 1) == 0, + "KERNEL_HEAP_BASE must be aligned to one root page-table entry span" ); const _: () = assert!( - MAX_KERNEL_HEAP_SIZE <= (1usize << 30), - "kernel heap window must fit within a single Sv39 1GiB (VPN[2]) entry" + MAX_KERNEL_HEAP_SIZE <= ROOT_ENTRY_SPAN, + "kernel heap window must fit within a single root page-table entry span" ); -/// Physical page number of the level-1 page table that backs the entire virtual -/// kernel-heap window. Built once at boot (single-threaded) and cached here so -/// that runtime [`map_heap_pages`] can install leaf PTEs without re-walking from -/// — and re-locking — the global `KERNEL_SPACE` page table. -static KERNEL_HEAP_L1_PPN: AtomicUsize = AtomicUsize::new(0); +/// Physical page number of the first-level subtree table that backs the entire +/// virtual kernel-heap window. Built once at boot (single-threaded) and cached +/// here so that runtime [`map_heap_pages`] can install leaf PTEs without +/// re-walking from — and re-locking — the global `KERNEL_SPACE` page table. +static KERNEL_HEAP_SUBTREE_ROOT_PPN: AtomicUsize = AtomicUsize::new(0); /// Serializes page-table edits within the kernel-heap subtree. /// @@ -89,20 +98,26 @@ static KERNEL_HEAP_L1_PPN: AtomicUsize = AtomicUsize::new(0); /// then recurse into `grow` → `map_heap_pages` → `KERNEL_SPACE.lock()` and /// self-deadlock on the non-reentrant lock — wedging every hart, with /// interrupts disabled so nothing (not even an RT task) could preempt. Because -/// the heap window is a disjoint VPN[2] subtree, edits to it never alias the +/// the heap window is a disjoint root-entry subtree, edits to it never alias the /// page-table memory `KERNEL_SPACE` touches, so a separate lock is sufficient /// and correct. `SpinNoIrqLock` keeps interrupts masked while held so a timer /// IRQ cannot re-enter the allocator on the same hart. static HEAP_PT_LOCK: SpinNoIrqLock<()> = SpinNoIrqLock::new(()); -/// Build the kernel-heap window's level-1 page table and cache its PPN. +/// Build the kernel-heap window's first-level subtree table and cache its PPN. /// /// Must run once, single-threaded, after `KERNEL_SPACE` is active and before the /// first virtual-window heap growth (see [`init_heap_virtual_window`]). pub fn init_kernel_heap_mapping() { let base_vpn = VirtAddr::from(KERNEL_HEAP_BASE).floor(); - let l1_ppn = KERNEL_SPACE.lock().page_table.ensure_l1_table_untracked(base_vpn); - KERNEL_HEAP_L1_PPN.store(l1_ppn.0, Ordering::Release); + let subtree_root_ppn = KERNEL_SPACE + .lock() + .page_table + .ensure_subtree_root_untracked(base_vpn); + if subtree_root_ppn.0 == 0 { + panic!("ensure_subtree_root_untracked returned PPN 0"); + } + KERNEL_HEAP_SUBTREE_ROOT_PPN.store(subtree_root_ppn.0, Ordering::Release); } struct KernelHeapAllocator { @@ -200,6 +215,14 @@ pub fn init_heap() { } pub fn init_heap_virtual_window() { + if !crate::platform::kernel_heap_virtual_window_supported() { + // LA64 bring-up still faults on the first access into the low-half + // heap window even after the leaf PTE is installed and TLB state is + // refreshed. Keep using the already-working DMW-backed bootstrap heap + // path for now so the kernel can continue booting on LoongArch. + crate::platform::early_console_write("[heap] virtual window disabled on loongarch64\r\n"); + return; + } KERNEL_HEAP_VIRTUAL_READY.store(true, Ordering::Release); assert!( HEAP_ALLOCATOR.grow(KERNEL_HEAP_GROW_SIZE), @@ -207,6 +230,27 @@ pub fn init_heap_virtual_window() { ); } +/// Map a single heap VA page. Called from trap_from_kernel on LoongArch. +pub fn map_one_heap_page(va: usize) -> bool { + map_heap_pages(va, 1) +} + +fn early_put_hex(label: &str, value: usize) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut buf = [0u8; 2 + 16 + 2]; + buf[0] = b'0'; + buf[1] = b'x'; + for (idx, slot) in buf[2..18].iter_mut().enumerate() { + let shift = (15 - idx) * 4; + *slot = HEX[(value >> shift) & 0xf]; + } + buf[18] = b'\r'; + buf[19] = b'\n'; + crate::platform::early_console_write(label); + // SAFETY: ASCII hex buffer is always valid UTF-8. + crate::platform::early_console_write(core::str::from_utf8(&buf).unwrap()); +} + fn layout_required_bytes(layout: Layout) -> Option { let min_size = layout .size() @@ -271,39 +315,66 @@ fn alloc_bootstrap_heap_pages(pages: usize) -> Option { let first = frames.start_ppn(); core::mem::forget(frames); let start_pa: super::PhysAddr = first.into(); - Some(start_pa.into()) + Some(phys_to_virt(start_pa.into())) } -/// Index the level-0 leaf PTE slot for a kernel-heap VA, given the (pre-built, -/// cached) level-1 table. Allocates the level-2 leaf table on demand. Returns -/// `None` only on frame exhaustion. The heap window is a single VPN[2] subtree, -/// so VPN[2] is constant and we walk straight from the cached level-1 table — -/// never touching the kernel root page table that `KERNEL_SPACE` guards. +/// Index the leaf PTE slot for a kernel-heap VA, given the pre-built cached +/// subtree root table. Allocates lower-level page tables on demand. Returns +/// `None` only on frame exhaustion. The heap window is a single root-entry +/// subtree, so we walk straight from that cached subtree root — never touching +/// the kernel root page table that `KERNEL_SPACE` guards. /// /// Caller must hold [`HEAP_PT_LOCK`]. -fn heap_leaf_pte(l1_ppn: PhysPageNum, vpn: super::VirtPageNum) -> Option<*mut PageTableEntry> { - let idxs = vpn.indexes(); - let l1 = &mut l1_ppn.get_pte_array()[idxs[1]]; - if !l1.is_valid() { - let frame = frame_alloc()?; - *l1 = PageTableEntry::new(frame.ppn, PTEFlags::V); - // The leaf table is a permanent kernel mapping; never reclaimed. - core::mem::forget(frame); +/// Walk from `subtree_root_ppn` (which PGDL's root[0] already points to) down +/// to the leaf PTE slot for `vpn`. `subtree_root_ppn` is at depth 1, so we +/// walk exactly `levels - 2` more directory hops before reaching the leaf table. +/// +/// Caller must hold [`HEAP_PT_LOCK`]. +fn heap_leaf_pte( + subtree_root_ppn: PhysPageNum, + vpn: super::VirtPageNum, +) -> Option<*mut PageTableEntry> { + let levels = crate::hal::page_table_levels(); + let mut ppn = subtree_root_ppn; + // Walk levels 1 .. levels-1 (directories), then return the leaf slot at level levels-1. + for level in 1..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + // This pte slot IS the leaf PTE (will be filled by map_heap_pages). + return Some(pte as *mut PageTableEntry); + } + if !pte.is_valid() { + let frame = frame_alloc()?; + frame.ppn.get_bytes_array().fill(0); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); + core::mem::forget(frame); + } + ppn = pte.ppn(); } - let l0_ppn = l1.ppn(); - Some(&mut l0_ppn.get_pte_array()[idxs[2]] as *mut PageTableEntry) + None } fn map_heap_pages(start_va: usize, pages: usize) -> bool { - let l1_ppn = PhysPageNum(KERNEL_HEAP_L1_PPN.load(Ordering::Acquire)); - debug_assert!(l1_ppn.0 != 0, "kernel heap mapping used before init"); - + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] map_heap_pages\r\n"); + } + let subtree_root_ppn = PhysPageNum(KERNEL_HEAP_SUBTREE_ROOT_PPN.load(Ordering::Acquire)); + if subtree_root_ppn.0 == 0 { + panic!("map_heap_pages: subtree root ppn is 0"); + } + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] locking HEAP_PT_LOCK\r\n"); + } let _guard = HEAP_PT_LOCK.lock(); + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] HEAP_PT_LOCK locked\r\n"); + } for page in 0..pages { let va = start_va + page * PAGE_SIZE; let vpn = VirtAddr::from(va).floor(); - let Some(pte) = heap_leaf_pte(l1_ppn, vpn) else { - rollback_heap_pages(l1_ppn, start_va, page); + let Some(pte) = heap_leaf_pte(subtree_root_ppn, vpn) else { + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; }; // SAFETY: `pte` points into a leaf table reachable only through @@ -311,19 +382,35 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { // `reserve_virtual_heap_bytes`, so no two writers target the same slot. let entry = unsafe { &mut *pte }; if entry.is_valid() { - rollback_heap_pages(l1_ppn, start_va, page); + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; } let Some(frame) = frame_alloc() else { - rollback_heap_pages(l1_ppn, start_va, page); + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; }; - *entry = PageTableEntry::new(frame.ppn, PTEFlags::R | PTEFlags::W | PTEFlags::V); + *entry = PageTableEntry::new( + frame.ppn, + PTEFlags::R | PTEFlags::W | PTEFlags::V | PTEFlags::A | PTEFlags::D, + ); core::mem::forget(frame); } - unsafe { - core::arch::asm!("sfence.vma"); + if crate::platform::heap_debug_enabled() && pages > 0 { + let vpn = VirtAddr::from(start_va).floor(); + let root_idx = crate::hal::vpn_index(vpn.0, 0); + let mid_idx = crate::hal::vpn_index(vpn.0, 1); + let leaf_idx = crate::hal::vpn_index(vpn.0, 2); + let root = PhysPageNum(crate::hal::root_ppn_from_token(crate::mm::kernel_token())); + let root_pte = root.get_pte_array()[root_idx].bits; + let mid_ppn = PhysPageNum(crate::hal::pte_ppn(root_pte)); + let mid_pte = mid_ppn.get_pte_array()[mid_idx].bits; + let leaf_ppn = PhysPageNum(crate::hal::pte_ppn(mid_pte)); + let leaf_pte = leaf_ppn.get_pte_array()[leaf_idx].bits; + early_put_hex("[heap] root_pte=", root_pte); + early_put_hex("[heap] mid_pte=", mid_pte); + early_put_hex("[heap] leaf_pte=", leaf_pte); } + unsafe { crate::hal::flush_tlb() }; true } @@ -340,9 +427,7 @@ fn rollback_heap_pages(l1_ppn: PhysPageNum, start_va: usize, pages: usize) { } } } - unsafe { - core::arch::asm!("sfence.vma"); - } + unsafe { crate::hal::flush_tlb() }; } #[allow(unused)] diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 5e88489b..3a857ff5 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -1,8 +1,7 @@ //! Address Space [`MemorySet`] management of Process use super::{frame_alloc, shootdown, FrameTracker, MmError, PageFaultHandled, ShootdownKind}; -use super::page_table::PTEFlags; -use super::{PageTable, PageTableEntry}; +use super::{PageTable, PageTableEntry, PTEFlags}; use super::{PhysAddr, PhysPageNum, USER_SPACE_END, VirtAddr, VirtPageNum}; use super::{StepByOne, VPNRange}; use crate::config::{ @@ -13,6 +12,8 @@ use crate::fs::{ mark_cached_page_dirty, release_mapped_page, retain_mapped_page, sync_inode_range, CachePage, FileDescription, }; +use crate::hal::ArchTrapMachine; +use crate::hal::traits::{AddressSpaceToken, TrapMachine}; use crate::sync::SpinNoIrqLock; use crate::task::ProcessControlBlock; use crate::syscall::errno::ERRNO; @@ -21,11 +22,9 @@ use alloc::string::String; use alloc::sync::{Arc, Weak}; use alloc::vec::Vec; use core::fmt::Write; -use core::arch::asm; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use fs::Inode; use lazy_static::*; -use riscv::register::satp; extern "C" { fn stext(); @@ -40,15 +39,6 @@ extern "C" { fn strampoline(); } -/// 用户态 rt_sigreturn trampoline 机器码。 -/// -/// RISC-V Linux 不依赖用户提供 `SA_RESTORER`,handler 返回后跳到这里执行 -/// `rt_sigreturn` 系统调用。 -const USER_VDSO_CODE: [u8; 8] = [ - 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 - 0x73, 0x00, 0x00, 0x00, // ecall -]; - /// ELF 加载结果,包含动态链接所需的额外信息 pub struct ElfLoadInfo { /// 程序入口点 @@ -76,7 +66,7 @@ lazy_static! { } /// the kernel token -pub fn kernel_token() -> usize { +pub fn kernel_token() -> AddressSpaceToken { KERNEL_SPACE.lock().token() } /// 用于稳定标识一个底层 inode。 @@ -457,19 +447,36 @@ impl DeferredUserReclaim { self.token, self.mask ); - shootdown(self.mask, ShootdownKind::AddressSpace { satp: self.token }); + shootdown(self.mask, ShootdownKind::AddressSpace { token: self.token }); } // self 在函数返回时析构,batch 的 Drop 会真正释放旧页引用。 } } impl MemorySet { + fn map_perm_to_pte_flags(map_perm: MapPermission) -> PTEFlags { + let mut flags = PTEFlags::empty(); + if map_perm.contains(MapPermission::R) { + flags.insert(PTEFlags::R); + } + if map_perm.contains(MapPermission::W) { + flags.insert(PTEFlags::W); + } + if map_perm.contains(MapPermission::X) { + flags.insert(PTEFlags::X); + } + if map_perm.contains(MapPermission::U) { + flags.insert(PTEFlags::U); + } + crate::hal::normalize_leaf_pte_flags(flags) + } + /// 完成一次会返回延迟回收 batch 的本地页表修改。 fn finish_deferred_page_table_edit(&self) { // 本地 hart 可能刚刚使用过被拆除的翻译,必须先清掉本地 TLB; // 远端 hart 的同步由调用方构造 `DeferredUserReclaim` 后在锁外完成。 unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } } @@ -482,7 +489,7 @@ impl MemorySet { } } /// Get he page table token - pub fn token(&self) -> usize { + pub fn token(&self) -> AddressSpaceToken { self.page_table.token() } /// 标记某个 hart 即将返回用户态并装载该地址空间。 @@ -539,7 +546,7 @@ impl MemorySet { self.token(), mask ); - shootdown(mask, ShootdownKind::AddressSpace { satp: self.token() }); + shootdown(mask, ShootdownKind::AddressSpace { token: self.token() }); } /// Assume that no conflicts. pub fn insert_framed_area( @@ -558,7 +565,7 @@ impl MemorySet { let start_va = VirtAddr::from(USER_VDSO_BASE); let end_va = VirtAddr::from(USER_VDSO_BASE + PAGE_SIZE); let vma = Vma::new_vdso(start_va, end_va); - self.insert_vma(vma, Some(&USER_VDSO_CODE)) + self.insert_vma(vma, Some(ArchTrapMachine::rt_sigreturn_trampoline())) } /// 根据起始虚拟页号删除一段用户区域,并延迟释放拆下的旧页对象。 pub(crate) fn remove_vma_with_start_vpn_user_deferred( @@ -792,10 +799,12 @@ impl MemorySet { } /// Mention that trampoline is not collected by areas. fn map_trampoline(&mut self) { + let trampoline_pa = crate::platform::direct_map_virt_to_phys(strampoline as usize); + self.page_table .map( VirtAddr::from(TRAMPOLINE).into(), - PhysAddr::from(strampoline as usize).into(), + PhysAddr::from(trampoline_pa).into(), PTEFlags::R | PTEFlags::X, ) .expect("failed to map trampoline"); @@ -805,6 +814,11 @@ impl MemorySet { let mut memory_set = Self::new_bare(); // map trampoline memory_set.map_trampoline(); + // On LoongArch, kernel sections / physical memory / MMIO are covered by + // DMW windows, but trap trampoline and task kernel stacks live in the + // low-half page-table space and are mapped explicitly. + #[cfg(not(target_arch = "loongarch64"))] + { // map kernel sections info!(".text [{:#x}, {:#x})", stext as usize, etext as usize); info!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); @@ -893,6 +907,7 @@ impl MemorySet { ) .expect("failed to map mmio window"); } + } // end #[cfg(not(loongarch64))] memory_set } /// Include ELF segments and trampoline, and compute initial process VM layout. @@ -1122,18 +1137,16 @@ impl MemorySet { } if parent_tlb_needs_flush { unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!("[cow] fork flush parent local TLB after write-protecting shared private pages"); } Ok((memory_set, parent_tlb_needs_flush)) } - /// Change page table by writing satp CSR Register. + /// Change page table by activating the current architecture token. pub fn activate(&self) { - let satp = self.page_table.token(); unsafe { - satp::write(satp); - asm!("sfence.vma"); + crate::hal::activate_address_space(self.page_table.token()); } } /// Translate a virtual page number to a page table entry @@ -1159,7 +1172,7 @@ impl MemorySet { } self.vmas.clear(); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } } @@ -1258,7 +1271,7 @@ impl MemorySet { if let Some(area) = self.vmas.get_mut(&start.floor()) { area.shrink_present_to(&mut self.page_table, new_end.ceil()); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } true } else { @@ -1323,7 +1336,7 @@ impl MemorySet { ); self.register_vma_metadata(Vma::new_anonymous(start_va, end_va, permission))?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(()) } @@ -1352,7 +1365,7 @@ impl MemorySet { None, )?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(()) } @@ -1529,7 +1542,7 @@ impl MemorySet { ppn } }; - let pte_flags = PTEFlags::from_bits(map_perm.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(map_perm); self.page_table.map(vpn, ppn, pte_flags)?; Ok(()) } @@ -1554,7 +1567,7 @@ impl MemorySet { } self.map_private_page_in_vma(vpn)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(PageFaultHandled::Handled) } @@ -1571,7 +1584,7 @@ impl MemorySet { if !self.can_commit_file_page_fault(plan) { return Ok(PageFaultHandled::NotHandled); } - let mut pte_flags = PTEFlags::from_bits(plan.map_perm.bits).unwrap(); + let mut pte_flags = Self::map_perm_to_pte_flags(plan.map_perm); if plan.shared && plan.map_perm.contains(MapPermission::W) && plan.access != PageFaultAccess::Write @@ -1594,7 +1607,7 @@ impl MemorySet { } self.page_table.map(plan.vpn, ppn, pte_flags)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[mmap] committed MAP_SHARED fault: vpn={:#x} page_idx={} ppn={:#x} writable={} path={:?}", @@ -1658,7 +1671,7 @@ impl MemorySet { if !self.can_commit_file_page_fault(plan) { return Ok(PageFaultHandled::NotHandled); } - let mut pte_flags = PTEFlags::from_bits(plan.map_perm.bits).unwrap(); + let mut pte_flags = Self::map_perm_to_pte_flags(plan.map_perm); pte_flags.remove(PTEFlags::W); pte_flags.remove(PTEFlags::D); let ppn = page.lock().ppn(); @@ -1671,7 +1684,7 @@ impl MemorySet { } self.page_table.map(plan.vpn, ppn, pte_flags)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[cow] install MAP_PRIVATE readonly cache page: vpn={:#x} page_idx={} ppn={:#x} access={:?} path={:?}", @@ -1719,7 +1732,7 @@ impl MemorySet { // 首次写 fault 时立即把 page cache 页记脏,避免等待 teardown 才传播脏状态。 mark_cached_page_dirty(&page); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[mmap] shared write-notify fault: vpn={:#x} ppn={:#x} path={:?}", @@ -1892,7 +1905,7 @@ impl MemorySet { let src = page_guard.ppn().get_bytes_array(); dst.copy_from_slice(src); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[cow] materialize MAP_PRIVATE page on first write fault: vpn={:#x} page_idx={} dst_ppn={:#x} path={:?}", @@ -2036,7 +2049,7 @@ impl MemorySet { }; // update middle pages' PTE flags - let pte_flags = PTEFlags::from_bits(permission.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(permission); for vpn in VPNRange::new(overlap_start, overlap_end) { if self.page_table.translate(vpn).is_some() { self.page_table.update_flags(vpn, pte_flags); @@ -2049,7 +2062,7 @@ impl MemorySet { new_areas.push(right_area); } else { // no right split, area becomes the middle area - let pte_flags = PTEFlags::from_bits(permission.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(permission); for vpn in VPNRange::new(overlap_start, overlap_end) { if self.page_table.translate(vpn).is_some() { self.page_table.update_flags(vpn, pte_flags); @@ -2064,7 +2077,7 @@ impl MemorySet { self.rebuild_vmas_from_vec(new_areas); self.merge_adjacent_vmas(); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } true } @@ -2240,11 +2253,13 @@ impl Vma { } /// 为某个线程生成 Trap 上下文页对应的区域描述。 pub fn new_trap_context(start_va: VirtAddr, end_va: VirtAddr, tid: usize) -> Self { + let map_perm = + MapPermission::from_bits_truncate(crate::hal::trap_context_flags().bits() as u8); Self::new( start_va, end_va, MapType::Framed, - MapPermission::R | MapPermission::W, + map_perm, VmaKind::TrapContext { tid }, ) } @@ -2535,7 +2550,7 @@ impl Vma { self.data_frames.insert(vpn, page); } } - let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap(); + let pte_flags = MemorySet::map_perm_to_pte_flags(self.map_perm); page_table.map(vpn, ppn, pte_flags)?; Ok(()) } diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index b4618fdf..eeae38c2 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -43,11 +43,12 @@ pub enum PageFaultHandled { NotHandled, } -pub use address::{PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; +pub use address::{phys_to_virt, virt_to_phys, PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; pub use frame_allocator::{ frame_alloc, frame_alloc_contiguous, frame_allocator_stats, frame_dealloc, frame_dealloc_range, ContiguousFrames, FrameAllocatorStats, FrameTracker, }; +pub use heap_allocator::map_one_heap_page; pub use memory_set::remap_test; pub use memory_set::{ invalidate_inode_mappings_after_truncate, kernel_token, register_file_mapping, @@ -63,14 +64,13 @@ pub use page_table::{ translated_byte_buffer, translated_ref, translated_refmut, translated_str, PageTable, PageTableEntry, UserBuffer, UserBufferIterator, }; +pub use crate::hal::traits::PTEFlags; /// initiate heap allocator, frame allocator and kernel space pub fn init() { frame_allocator::init_frame_allocator(); heap_allocator::init_heap(); KERNEL_SPACE.lock().activate(); - // Build the kernel-heap window's page-table backbone before any virtual-window - // growth, so growth never recurses into the `KERNEL_SPACE` lock. heap_allocator::init_kernel_heap_mapping(); heap_allocator::init_heap_virtual_window(); } diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 9233a8ad..c5103031 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -4,24 +4,10 @@ use super::{ VirtAddr, VirtPageNum, }; use crate::config::PAGE_SIZE; +use crate::hal::traits::{AddressSpaceToken, PagingArch, PTEFlags}; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use bitflags::*; - -bitflags! { - /// page table entry flags - pub struct PTEFlags: u8 { - const V = 1 << 0; - const R = 1 << 1; - const W = 1 << 2; - const X = 1 << 3; - const U = 1 << 4; - const G = 1 << 5; - const A = 1 << 6; - const D = 1 << 7; - } -} #[derive(Copy, Clone)] #[repr(C)] @@ -35,7 +21,7 @@ impl PageTableEntry { /// Create a new page table entry pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self { PageTableEntry { - bits: ppn.0 << 10 | flags.bits as usize, + bits: crate::hal::make_pte(ppn.0, flags), } } /// Create an empty page table entry @@ -44,15 +30,15 @@ impl PageTableEntry { } /// Get the physical page number from the page table entry pub fn ppn(&self) -> PhysPageNum { - (self.bits >> 10 & ((1usize << 44) - 1)).into() + crate::hal::pte_ppn(self.bits).into() } /// Get the flags from the page table entry pub fn flags(&self) -> PTEFlags { - PTEFlags::from_bits(self.bits as u8).unwrap() + crate::hal::pte_flags(self.bits) } /// The page pointered by page table entry is valid? pub fn is_valid(&self) -> bool { - (self.flags() & PTEFlags::V) != PTEFlags::empty() + crate::hal::pte_is_valid(self.bits) } /// The page pointered by page table entry is readable? pub fn readable(&self) -> bool { @@ -88,69 +74,66 @@ impl PageTable { }) } /// Temporarily used to get arguments from user space. - pub fn from_token(satp: usize) -> Self { + pub fn from_token(token: AddressSpaceToken) -> Self { Self { - root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)), + root_ppn: PhysPageNum::from(crate::hal::root_ppn_from_token(token)), frames: Vec::new(), } } fn find_pte_create(&mut self, vpn: VirtPageNum) -> Result, MmError> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Ok(Some(pte)); } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); self.frames.push(frame); } ppn = pte.ppn(); } - Ok(result) + Ok(None) } fn find_pte_create_untracked( &mut self, vpn: VirtPageNum, ) -> Result, MmError> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Ok(Some(pte)); } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); core::mem::forget(frame); } ppn = pte.ppn(); } - Ok(result) + Ok(None) } fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Some(pte); } if !pte.is_valid() { return None; } ppn = pte.ppn(); } - result + None } /// set the map between virtual page number and physical page number #[allow(unused)] @@ -179,19 +162,24 @@ impl PageTable { *pte = PageTableEntry::new(ppn, flags | PTEFlags::V); Ok(()) } - /// Ensure the level-1 (1GiB-granularity) intermediate table covering `vpn` + /// Ensure the first-level subtree table under the root covering `vpn` /// exists, creating it untracked if necessary, and return its physical page /// number. /// - /// Used to pre-build the kernel-heap window's level-1 table once at boot so - /// that subsequent heap growth can install leaf PTEs into a disjoint subtree - /// without re-walking (and re-locking) the global kernel page table. - pub fn ensure_l1_table_untracked(&mut self, vpn: VirtPageNum) -> PhysPageNum { - let idxs = vpn.indexes(); - let pte = &mut self.root_ppn.get_pte_array()[idxs[0]]; + /// Used to pre-build the kernel-heap window's root-entry subtree once at + /// boot so that subsequent heap growth can install leaf PTEs into a + /// disjoint subtree without re-walking (and re-locking) the global kernel + /// page table. + pub fn ensure_subtree_root_untracked(&mut self, vpn: VirtPageNum) -> PhysPageNum { + debug_assert!( + crate::hal::page_table_levels() >= 2, + "kernel heap subtree caching requires a multi-level page table" + ); + let idx = crate::hal::vpn_index(vpn.0, 0); + let pte = &mut self.root_ppn.get_pte_array()[idx]; if !pte.is_valid() { let frame = frame_alloc().unwrap(); - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); core::mem::forget(frame); } pte.ppn() @@ -247,8 +235,8 @@ impl PageTable { }) } /// get the token from the page table - pub fn token(&self) -> usize { - 8usize << 60 | self.root_ppn.0 + pub fn token(&self) -> AddressSpaceToken { + crate::hal::make_address_space_token(self.root_ppn.0) } } @@ -268,10 +256,7 @@ fn checked_user_range(start: usize, len: usize) -> Option { } /// Create mutable `Vec` slice in kernel space from ptr in other address space. NOTICE: the content pointed to by the pointer `ptr` can cross physical pages. -pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Option> { - if len == 0 { - return Some(Vec::new()); - } +pub fn translated_byte_buffer(token: AddressSpaceToken, ptr: *const u8, len: usize) -> Option> { let page_table = PageTable::from_token(token); let mut start = ptr as usize; let end = checked_user_range(start, len)?; @@ -296,7 +281,7 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Optio } /// Create String in kernel address space from u8 Array(end with 0) in other address space -pub fn translated_str(token: usize, ptr: *const u8) -> Option { +pub fn translated_str(token: AddressSpaceToken, ptr: *const u8) -> Option { let page_table = PageTable::from_token(token); let mut string = String::new(); let mut va = ptr as usize; @@ -319,7 +304,7 @@ pub fn translated_str(token: usize, ptr: *const u8) -> Option { } /// translate a pointer `ptr` in other address space to a immutable u8 slice in kernel address space. NOTICE: the content pointed to by the pointer `ptr` cannot cross physical pages, otherwise translated_byte_buffer should be used. -pub fn translated_ref(token: usize, ptr: *const T) -> Option<&'static T> { +pub fn translated_ref(token: AddressSpaceToken, ptr: *const T) -> Option<&'static T> { let page_table = PageTable::from_token(token); checked_user_range(ptr as usize, core::mem::size_of::().max(1))?; page_table @@ -328,7 +313,7 @@ pub fn translated_ref(token: usize, ptr: *const T) -> Option<&'static T> { } /// translate a pointer `ptr` in other address space to a mutable u8 slice in kernel address space. NOTICE: the content pointed to by the pointer `ptr` cannot cross physical pages, otherwise translated_byte_buffer should be used. -pub fn translated_refmut(token: usize, ptr: *mut T) -> Option<&'static mut T> { +pub fn translated_refmut(token: AddressSpaceToken, ptr: *mut T) -> Option<&'static mut T> { let page_table = PageTable::from_token(token); let va = ptr as usize; checked_user_range(va, core::mem::size_of::().max(1))?; diff --git a/os/src/mm/tlb_shootdown.rs b/os/src/mm/tlb_shootdown.rs index f3522152..6f4d9ec3 100644 --- a/os/src/mm/tlb_shootdown.rs +++ b/os/src/mm/tlb_shootdown.rs @@ -1,15 +1,14 @@ //! TLB shootdown state and deferred recycle helpers. use super::{kernel_token, FrameTracker}; -use crate::hart::hartid; +use crate::hal::hartid; +use crate::hal::traits::AddressSpaceToken; use crate::sbi::send_ipi_mask; use crate::sync::{SpinLock, SpinNoIrqLock}; use alloc::vec::Vec; -use core::arch::asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use lazy_static::*; -use riscv::register::satp; /// 一个处于 deferred 状态的内核虚拟地址区间。 #[derive(Copy, Clone)] @@ -31,8 +30,8 @@ pub enum ShootdownKind { /// 时可能已经从用户态切入内核,但仍需要完成本地 flush 并 ack。 /// TODO:引入 ASID 后,应把这里改成按 ASID 或地址范围精确刷新。 AddressSpace { - /// 目标地址空间的 satp token。 - satp: usize, + /// 目标地址空间的架构 token。 + token: AddressSpaceToken, }, } @@ -52,8 +51,8 @@ struct TlbShootdownState { online_hart_mask: AtomicUsize, /// 当前请求类型编码。 kind_bits: AtomicUsize, - /// 当前请求附带的 satp 参数。 - arg_satp: AtomicUsize, + /// 当前请求附带的地址空间 token 参数。 + arg_token: AtomicUsize, } impl TlbShootdownState { @@ -66,7 +65,7 @@ impl TlbShootdownState { ack_mask: AtomicUsize::new(0), online_hart_mask: AtomicUsize::new(0), kind_bits: AtomicUsize::new(0), - arg_satp: AtomicUsize::new(0), + arg_token: AtomicUsize::new(0), } } } @@ -287,13 +286,13 @@ fn shootdown_inner(hart_mask: usize, kind: ShootdownKind) { let target_mask = hart_mask & online_mask & !self_bit; let seq = TLB_SHOOTDOWN_STATE.seq.load(Ordering::Acquire) + 1; - let (kind_bits, arg_satp) = encode_shootdown_kind(kind); + let (kind_bits, arg_token) = encode_shootdown_kind(kind); TLB_SHOOTDOWN_STATE .kind_bits .store(kind_bits, Ordering::Release); TLB_SHOOTDOWN_STATE - .arg_satp - .store(arg_satp, Ordering::Release); + .arg_token + .store(arg_token, Ordering::Release); TLB_SHOOTDOWN_STATE .target_mask .store(target_mask, Ordering::Release); @@ -377,7 +376,7 @@ pub fn handle_ipi() { } let kind = decode_shootdown_kind( TLB_SHOOTDOWN_STATE.kind_bits.load(Ordering::Acquire), - TLB_SHOOTDOWN_STATE.arg_satp.load(Ordering::Acquire), + TLB_SHOOTDOWN_STATE.arg_token.load(Ordering::Acquire), ); let seq = TLB_SHOOTDOWN_STATE.seq.load(Ordering::Acquire); trace!( @@ -398,14 +397,14 @@ pub fn handle_ipi() { fn encode_shootdown_kind(kind: ShootdownKind) -> (usize, usize) { match kind { ShootdownKind::Global => (KIND_GLOBAL, 0), - ShootdownKind::AddressSpace { satp } => (KIND_ADDRESS_SPACE, satp), + ShootdownKind::AddressSpace { token } => (KIND_ADDRESS_SPACE, token), } } /// 从全局请求槽解码出当前请求语义。 -fn decode_shootdown_kind(kind_bits: usize, arg_satp: usize) -> ShootdownKind { +fn decode_shootdown_kind(kind_bits: usize, arg_token: usize) -> ShootdownKind { match kind_bits { - KIND_ADDRESS_SPACE => ShootdownKind::AddressSpace { satp: arg_satp }, + KIND_ADDRESS_SPACE => ShootdownKind::AddressSpace { token: arg_token }, _ => ShootdownKind::Global, } } @@ -414,12 +413,12 @@ fn decode_shootdown_kind(kind_bits: usize, arg_satp: usize) -> ShootdownKind { fn perform_local_tlb_shootdown(kind: ShootdownKind) { match kind { ShootdownKind::Global => local_sfence_vma_all(), - ShootdownKind::AddressSpace { satp: target_satp } => { + ShootdownKind::AddressSpace { token: target_token } => { // 目标 hart 可能是在用户态收到 IPI 后刚切入内核,因此当前 satp // 可能已经是 kernel_token;此时仍然要完成本地 flush 并回 ack。 // TODO:引入 ASID 后,应避免把 kernel_token 情况退化成全量 flush。 - let current_satp = satp::read().bits(); - if current_satp == target_satp || current_satp == kernel_token() { + let current_token = unsafe { crate::hal::current_address_space_token() }; + if current_token == target_token || current_token == kernel_token() { local_sfence_vma_all(); } } @@ -436,7 +435,5 @@ fn shootdown_kind_name(kind: ShootdownKind) -> &'static str { /// 在当前 hart 上执行一次全量 `sfence.vma`。 fn local_sfence_vma_all() { - unsafe { - asm!("sfence.vma"); - } + unsafe { crate::hal::flush_tlb() } } diff --git a/os/src/platform/loongarch/mod.rs b/os/src/platform/loongarch/mod.rs new file mode 100644 index 00000000..c9ae8e32 --- /dev/null +++ b/os/src/platform/loongarch/mod.rs @@ -0,0 +1,6 @@ +//! LoongArch platform implementations. +//! +//! This layer binds one concrete machine/board model onto the generic +//! LoongArch architecture support and reusable device drivers. + +pub mod qemu_virt; diff --git a/os/src/platform/loongarch/qemu_virt/board.rs b/os/src/platform/loongarch/qemu_virt/board.rs new file mode 100644 index 00000000..20d76b23 --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/board.rs @@ -0,0 +1,112 @@ +//! Static board description for the LoongArch64 QEMU `virt` machine. + +/// default base address for anonymous mmap allocations +pub const USER_MMAP_BASE: usize = 0x20_0000_0000; + +/// default base address for the main thread's user stack region +pub const USER_STACK_BASE: usize = 0x3e_0000_0000; + +/// base address for loading dynamic linker (interpreter) +pub const INTERP_BASE: usize = 0x1e_0000_0000; + +/// Direct-mapped uncached I/O virtual-address offset used during early bring-up. +pub const IO_ADDR_OFFSET: usize = 0x8000_0000_0000_0000; +/// Direct-mapped cached kernel-address offset used during early bring-up. +pub const KERNEL_ADDR_OFFSET: usize = 0x9000_0000_0000_0000; + +/// QEMU loongarch64 `virt` clock frequency. +pub const CLOCK_FREQ: usize = 100_000_000; + +/// MMIO windows used by the kernel on QEMU loongarch64 `virt` (uncached DMW0 window). +pub const MMIO: &[(usize, usize)] = &[ + (IO_ADDR_OFFSET | 0x1000_0000, 0x100000), // LS7A bridge (RTC at 0x100d0100, GED at 0x100e001c) + (IO_ADDR_OFFSET | 0x1fe0_0000, 0x10000), // covers all 1fe0_xxxx MMIO + (IO_ADDR_OFFSET | 0x1fe2_0000, 0x8000), // VirtIO +]; + +/// UART MMIO virtual address (uncached DMW0 window). +pub const VIRT_UART: usize = IO_ADDR_OFFSET | 0x1fe0_01e0; +/// LS7A RTC MMIO virtual address (uncached DMW0 window). +pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x100d_0100; +/// VirtIO MMIO window base address. +pub const VIRTIO_MMIO_BASE: usize = IO_ADDR_OFFSET | 0x1fe2_0000; +/// Size of each VirtIO MMIO slot. +pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; +/// Number of VirtIO MMIO slots exposed by the machine. +pub const VIRTIO_MMIO_SLOTS: usize = 8; +/// First IRQ line assigned to VirtIO MMIO devices. +pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; + +/// Block device implementation for QEMU `virt`. +pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +/// Char device implementation for QEMU `virt`. +pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; + +use core::arch::asm; + +const EXIT_SUCCESS: u32 = 0x5555; +const EXIT_FAILURE: u32 = 0x0001_3333; + +/// QEMU exit interface. +pub trait QEMUExit { + /// Exit with specified return code. + fn exit(&self, code: u32) -> !; + + /// Exit QEMU using `EXIT_SUCCESS`. + fn exit_success(&self) -> !; + + /// Exit QEMU using `EXIT_FAILURE`. + fn exit_failure(&self) -> !; +} + +/// LoongArch64 QEMU power-management wrapper. +pub struct LOONGARCH64 { + sleep_ctl_addr: u64, +} + +// QEMU `virt` exposes ACPI GED power-management registers at: +// VIRT_GED_EVT_ADDR = 0x100e0000 +// VIRT_GED_REG_ADDR = VIRT_GED_EVT_ADDR + ACPI_GED_EVT_SEL_LEN(0x4) +// + MEMORY_HOTPLUG_IO_LEN(24) +// = 0x100e001c +// `ACPI_GED_REG_SLEEP_CTL` is offset 0 and powers off the VM when written +// with SLP_EN | (S5 << SLP_TYP_POS) = 0x34. +const GED_SLEEP_CTL_VALUE: u8 = 0x34; +const GED_REG_BASE: u64 = (IO_ADDR_OFFSET | 0x100e_001c) as u64; + +impl LOONGARCH64 { + /// Create an instance. + pub const fn new(addr: u64) -> Self { + Self { + sleep_ctl_addr: addr, + } + } +} + +impl QEMUExit for LOONGARCH64 { + fn exit(&self, _code: u32) -> ! { + unsafe { + asm!( + "st.b {value}, {addr}, 0", + value = in(reg) GED_SLEEP_CTL_VALUE, + addr = in(reg) self.sleep_ctl_addr, + ); + loop { + asm!("idle 0", options(nomem, nostack)); + } + } + } + + fn exit_success(&self) -> ! { + self.exit(EXIT_SUCCESS); + } + + fn exit_failure(&self) -> ! { + self.exit(EXIT_FAILURE); + } +} + +const VIRT_TEST: u64 = GED_REG_BASE; + +/// Global QEMU exit handle using the ACPI GED poweroff register. +pub const QEMU_EXIT_HANDLE: LOONGARCH64 = LOONGARCH64::new(VIRT_TEST); diff --git a/os/src/platform/loongarch/qemu_virt/irq.rs b/os/src/platform/loongarch/qemu_virt/irq.rs new file mode 100644 index 00000000..9115ecd6 --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/irq.rs @@ -0,0 +1,141 @@ +//! LoongArch64 QEMU `virt` external IRQ routing. +//! +//! This is platform glue rather than generic architecture code: QEMU wires the +//! console UART into the LS7A PCH PIC, then forwards it through EXTIOI onto a +//! CPU hardware interrupt line. + +use core::arch::asm; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::bootstrap_hart_id; +use crate::drivers::chardev::{CharDevice, UART}; + +static UART_IRQ_READY: AtomicBool = AtomicBool::new(false); + +const PCH_PIC_BASE: usize = super::IO_ADDR_OFFSET | 0x1000_0000; +const PCH_PIC_INT_MASK: usize = 0x20; +const PCH_PIC_HTMSI_VEC: usize = 0x200; + +const EXTIOI_BASE: usize = 0x1400; +const EXTIOI_IPMAP_START: usize = 0x0c0; +const EXTIOI_ENABLE_START: usize = 0x200; +const EXTIOI_COREISR_START: usize = 0x400; +const EXTIOI_COREMAP_START: usize = 0x800; + +const UART0_PCH_IRQ: u32 = 2; +const EXTIOI_ROUTE_IP3: u32 = 0x0808_0808; + +#[inline] +fn mmio_read64(addr: usize) -> u64 { + unsafe { read_volatile(addr as *const u64) } +} + +#[inline] +fn mmio_write64(addr: usize, value: u64) { + unsafe { write_volatile(addr as *mut u64, value) } +} + +#[inline] +fn iocsr_read32(addr: usize) -> u32 { + let value: u32; + unsafe { + asm!( + "iocsrrd.w {value}, {addr}", + value = out(reg) value, + addr = in(reg) addr, + ); + } + value +} + +#[inline] +fn iocsr_write32(addr: usize, value: u32) { + unsafe { + asm!( + "iocsrwr.w {value}, {addr}", + value = in(reg) value, + addr = in(reg) addr, + ); + } +} + +fn init_uart_pch_pic() { + let irq = UART0_PCH_IRQ as usize; + let vec_reg = PCH_PIC_BASE + PCH_PIC_HTMSI_VEC + (irq & !7); + let vec_shift = (irq & 7) * 8; + let mut vectors = mmio_read64(vec_reg); + vectors &= !(0xffu64 << vec_shift); + vectors |= (UART0_PCH_IRQ as u64) << vec_shift; + mmio_write64(vec_reg, vectors); + + let mask = mmio_read64(PCH_PIC_BASE + PCH_PIC_INT_MASK); + mmio_write64(PCH_PIC_BASE + PCH_PIC_INT_MASK, mask & !(1u64 << irq)); +} + +fn init_uart_extioi() { + let target_hart = bootstrap_hart_id().min(3); + let cpu_bit = 1u32 << target_hart; + + for reg in 0..2 { + iocsr_write32(EXTIOI_BASE + EXTIOI_IPMAP_START + reg * 4, EXTIOI_ROUTE_IP3); + } + + let coremap_word = cpu_bit | (cpu_bit << 8) | (cpu_bit << 16) | (cpu_bit << 24); + for reg in 0..64 { + iocsr_write32(EXTIOI_BASE + EXTIOI_COREMAP_START + reg * 4, coremap_word); + } + + iocsr_write32(EXTIOI_BASE + EXTIOI_COREISR_START, 1u32 << UART0_PCH_IRQ); + let enable = iocsr_read32(EXTIOI_BASE + EXTIOI_ENABLE_START); + iocsr_write32( + EXTIOI_BASE + EXTIOI_ENABLE_START, + enable | (1u32 << UART0_PCH_IRQ), + ); +} + +/// Initialize platform external interrupt routing on the bootstrap hart. +pub fn init_external_irq() { + init_uart_extioi(); + init_uart_pch_pic(); + UART_IRQ_READY.store(true, Ordering::Release); + info!( + "[irq] loongarch uart IRQ enabled on hart {}", + bootstrap_hart_id().min(3) + ); +} + +/// Initialize per-hart external interrupt state. +pub fn init_external_irq_hart(_hart_id: usize) {} + +/// Whether the console RX interrupt path is ready for blocking reads. +pub fn console_rx_irq_ready() -> bool { + UART_IRQ_READY.load(Ordering::Acquire) +} + +/// Dispatch one platform external interrupt. +pub fn handle_external_irq() { + if !console_rx_irq_ready() { + return; + } + + let pending = iocsr_read32(EXTIOI_BASE + EXTIOI_COREISR_START); + let uart_mask = 1u32 << UART0_PCH_IRQ; + let mut clear_mask = 0u32; + + if pending & uart_mask != 0 { + UART.handle_irq(); + crate::fs::console_receive(); + clear_mask |= uart_mask; + } + + let unexpected = pending & !clear_mask; + if unexpected != 0 { + warn!("[irq] loongarch unexpected EXTIOI pending bits {:#x}", unexpected); + clear_mask |= unexpected; + } + + if clear_mask != 0 { + iocsr_write32(EXTIOI_BASE + EXTIOI_COREISR_START, clear_mask); + } +} diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs new file mode 100644 index 00000000..acd4624d --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -0,0 +1,230 @@ +//! QEMU `virt` platform for LoongArch64. + +mod board; +mod irq; +mod pci; +pub mod rtc; + +pub use board::{ + BlockDeviceImpl, CharDeviceImpl, USER_STACK_BASE, USER_MMAP_BASE, INTERP_BASE, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, + VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; +pub use irq::{ + console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, +}; +pub use pci::probe_platform_devices; + +use crate::drivers::chardev::CharDevice; +use crate::hal::traits::{HartCtrl, Timer}; + +pub const KERNEL_HEAP_BASE: usize = 0x0000_0038_0000_0000; +pub const TRAMPOLINE: usize = 0x0000_003f_ffff_f000; + +/// LoongArch64 platform implementation used by the generic HAL facade. +pub struct LoongArchPlatform; + +impl Timer for LoongArchPlatform { + fn read_time() -> usize { + crate::arch::loongarch64::read_time() + } + + fn set_next(deadline: usize) { + unsafe { crate::arch::loongarch64::set_timer_deadline(deadline) }; + } + + fn clock_freq() -> usize { + crate::config::CLOCK_FREQ + } +} + +const IOCSR_IPI_SEND: usize = 0x1040; +const IOCSR_IPI_EN: usize = 0x1004; +const IOCSR_IPI_CLEAR: usize = 0x100c; +const IOCSR_MBUF_SEND: usize = 0x1048; + +const IOCSR_IPI_SEND_CPU_SHIFT: u32 = 16; +const IOCSR_MBUF_SEND_CPU_SHIFT: u64 = 16; +const IOCSR_MBUF_SEND_DATA_SHIFT: u64 = 32; + +// QEMU's LoongArch IPI model treats IOCSR_IPI_SEND[4:0] as a vector number, +// and the per-core enable/clear registers as vector bitmasks. +const IPI_VECTOR_WAKEUP: u32 = 1; +const IPI_VECTOR_MASK: u32 = 1 << IPI_VECTOR_WAKEUP; + +#[inline] +unsafe fn iocsr_write32(addr: usize, val: u32) { + core::arch::asm!("iocsrwr.w {v}, {a}", v = in(reg) val, a = in(reg) addr); +} + +#[inline] +unsafe fn iocsr_write64(addr: usize, val: u64) { + core::arch::asm!("iocsrwr.d {v}, {a}", v = in(reg) val, a = in(reg) addr); +} + +fn ipi_send(hart_id: usize, vector: u32) { + let val = vector | (hart_id as u32) << IOCSR_IPI_SEND_CPU_SHIFT; + unsafe { iocsr_write32(IOCSR_IPI_SEND, val) }; +} + +fn enable_ipi() { + unsafe { iocsr_write32(IOCSR_IPI_EN, IPI_VECTOR_MASK) }; +} + +fn clear_ipi(vector: u32) { + unsafe { iocsr_write32(IOCSR_IPI_CLEAR, 1 << vector) }; +} + +#[inline] +fn mailbox_word_slot(mailbox: u64, upper_half: bool) -> u64 { + debug_assert!(mailbox < 4); + (mailbox << 3) | ((upper_half as u64) << 2) +} + +#[inline] +fn mail_send_word(word: u32, hart_id: usize, slot: u64) { + let val = ((word as u64) << IOCSR_MBUF_SEND_DATA_SHIFT) + | ((hart_id as u64) << IOCSR_MBUF_SEND_CPU_SHIFT) + | slot; + unsafe { iocsr_write64(IOCSR_MBUF_SEND, val) }; +} + +// QEMU decodes MAIL_SEND as one 32-bit mailbox-slot write per request. To +// publish a 64-bit entry address in MBUF0, we must write its low/high halves +// into CORE_BUF_20 and CORE_BUF_24 separately. +fn mail_send(data: u64, hart_id: usize, mailbox: u64) { + mail_send_word(data as u32, hart_id, mailbox_word_slot(mailbox, false)); + mail_send_word((data >> 32) as u32, hart_id, mailbox_word_slot(mailbox, true)); +} + +impl HartCtrl for LoongArchPlatform { + fn start_hart(hart_id: usize, start_addr: usize, _opaque: usize) -> Result<(), ()> { + mail_send(start_addr as u64, hart_id, 0); + ipi_send(hart_id, IPI_VECTOR_WAKEUP); + Ok(()) + } + + fn send_ipi(hart_mask: usize) { + for hart_id in 0..usize::BITS as usize { + if hart_mask & (1 << hart_id) != 0 { + ipi_send(hart_id, IPI_VECTOR_WAKEUP); + } + } + } +} + +/// Whether console output should still use the earliest UART path. +pub fn use_early_console() -> bool { + !crate::drivers::chardev::uart_ready() +} + +/// Write one string through the earliest available console path. +pub fn early_console_write(s: &str) { + for b in s.bytes() { + unsafe { + while core::ptr::read_volatile((VIRT_UART + 5) as *const u8) & 0x20 == 0 {} + core::ptr::write_volatile(VIRT_UART as *mut u8, b); + } + } +} + +/// Write one character to the platform console. +pub fn console_putchar(c: usize) { + crate::drivers::chardev::UART.write(c as u8); +} + +/// Read one character from the platform console. +pub fn console_getchar() -> usize { + crate::drivers::chardev::UART.read() as usize +} + +/// Power off the virtual machine. +pub fn shutdown() -> ! { + QEMU_EXIT_HANDLE.exit_success() +} + +/// Return the uname-style machine string. +pub fn machine_name() -> &'static str { + "loongarch64" +} + +/// Return the platform name for display purposes. +pub fn platform_name() -> &'static str { + "qemu virt" +} + +/// Start all secondary harts via IOCSR mailbox + IPI. +pub fn start_secondary_harts(bootstrap_hart_id: usize) { + extern "C" { + fn _start(); + } + + // Under direct boot, CPU0 reaches the kernel through our tiny bootloader, + // which jumps to the high-half DMW alias of `_start`. QEMU's secondary + // slave stub simply `jirl`s to the mailbox value, so feeding it the raw + // physical address would drop APs outside the cached DMW window right + // after wakeup. + let entry = _start as usize; + enable_ipi(); + for hart_id in 0..crate::config::MAX_HARTS { + if hart_id == bootstrap_hart_id { + continue; + } + let _ = ::start_hart(hart_id, entry, 0); + warn!( + "hart {} requested startup for hart {} at {:#x}", + bootstrap_hart_id, hart_id, entry + ); + } +} + +/// Initialize per-hart IPI receive state. +pub fn init_ipi_hart() { + enable_ipi(); +} + +/// Clear the wake/reschedule IPI vector on the current hart. +pub fn clear_ipi_vector() { + clear_ipi(IPI_VECTOR_WAKEUP); +} + +/// Translate one direct-mapped physical address into the kernel VA used on this platform. +pub fn direct_map_phys_to_virt(pa: usize) -> usize { + pa | KERNEL_ADDR_OFFSET +} + +/// Translate one direct-mapped kernel VA back into a physical address. +pub fn direct_map_virt_to_phys(va: usize) -> usize { + va & !KERNEL_ADDR_OFFSET +} + +/// Translate a direct-mapped kernel VA into a physical address when applicable. +pub fn translate_direct_mapped_kernel_va(va: usize) -> Option { + if va & KERNEL_ADDR_OFFSET == KERNEL_ADDR_OFFSET { + return Some(va & !KERNEL_ADDR_OFFSET); + } + if va & IO_ADDR_OFFSET == IO_ADDR_OFFSET { + return Some(va & !IO_ADDR_OFFSET); + } + None +} + +/// Translate one MMIO physical address into the VA used by drivers. +pub fn mmio_phys_to_virt(paddr: usize) -> usize { + paddr | IO_ADDR_OFFSET +} + +/// Whether the RTC is supported on this platform. +pub fn rtc_is_supported() -> bool { + true +} + +/// Whether the kernel heap may grow inside its dedicated virtual window. +pub fn kernel_heap_virtual_window_supported() -> bool { + false +} + +/// Whether extra heap bring-up debugging is enabled for this platform. +pub fn heap_debug_enabled() -> bool { + true +} diff --git a/os/src/platform/loongarch/qemu_virt/pci.rs b/os/src/platform/loongarch/qemu_virt/pci.rs new file mode 100644 index 00000000..782391f0 --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/pci.rs @@ -0,0 +1,188 @@ +//! LoongArch64 QEMU `virt` PCI/ECAM probing for VirtIO PCI devices. + +use alloc::{string::String, sync::Arc}; +use virtio_drivers::transport::{ + pci::{ + bus::{ + BarInfo, Cam, Command, DeviceFunction, DeviceFunctionInfo, HeaderType, MemoryBarType, + MmioCam, PciRoot, + }, + virtio_device_type, PciTransport, + }, + DeviceType, SomeTransport, Transport, +}; + +use crate::drivers::{ + block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, + net::{self, VirtIONetDevice}, +}; + +const PCI_ECAM_BASE: usize = 0x2000_0000; +const PCI_ECAM_SIZE: usize = 0x1000_0000; +const PCI_RANGE_BASE: usize = 0x4000_0000; +const PCI_RANGE_SIZE: usize = 0x4000_0000; +const PCI_BUS_END: u8 = 0x7f; +const LEGACY_VIRTIO_NET_IRQ: u32 = 1; + +/// Probe the LA64 PCIe ECAM bus and register VirtIO PCI devices. +pub fn probe_platform_devices() { + let ecam_vaddr = PCI_ECAM_BASE | super::IO_ADDR_OFFSET; + let ecam_end = ecam_vaddr + PCI_ECAM_SIZE; + if ecam_end < ecam_vaddr { + panic!("PCI ECAM window overflow"); + } + + let mut root = unsafe { PciRoot::new(MmioCam::new(ecam_vaddr as *mut u8, Cam::Ecam)) }; + let mut allocator = PciRangeAllocator::new(PCI_RANGE_BASE as u64, PCI_RANGE_SIZE as u64); + let mut map = BLOCK_DEVICES.lock(); + let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); + let mut block_idx = 0usize; + + for bus in 0..=PCI_BUS_END { + for (bdf, dev_info) in root.enumerate_bus(bus) { + if dev_info.header_type != HeaderType::Standard { + continue; + } + + if let Err(err) = configure_pci_device(&mut root, bdf, &mut allocator) { + warn!( + "[pci] failed to configure device at {} ({}:{:#06x}): {:?}", + bdf, dev_info.vendor_id, dev_info.device_id, err + ); + continue; + } + + let Some(transport) = probe_virtio_pci_device(&mut root, bdf, &dev_info) else { + continue; + }; + + match transport.device_type() { + DeviceType::Block => { + let Some(dev) = VirtIOBlock::try_new(SomeTransport::from(transport)) else { + warn!("[pci] failed to create virtio-blk for {}", bdf); + continue; + }; + let dev = Arc::new(dev); + let name: String = alloc::format!("vd{}", (b'a' + block_idx as u8) as char); + info!("[pci] virtio-blk {} at {}", name, bdf); + map.insert(name, dev.clone()); + let _ = &mut irq_map; + block_idx += 1; + } + DeviceType::Network => { + let Some(dev) = VirtIONetDevice::try_new( + SomeTransport::from(transport), + LEGACY_VIRTIO_NET_IRQ, + ) else { + warn!("[pci] failed to create virtio-net for {}", bdf); + continue; + }; + let mac = dev.mac_address(); + info!( + "[pci] virtio-net at {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + bdf, + LEGACY_VIRTIO_NET_IRQ, + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + net::register_device(Arc::new(dev)); + } + other => { + warn!("[pci] ignore unsupported virtio device {:?} at {}", other, bdf); + } + } + } + } + + if block_idx == 0 { + panic!("[kernel] no VirtIO block devices found"); + } +} + +#[derive(Debug)] +struct PciRangeAllocator { + end: u64, + current: u64, +} + +impl PciRangeAllocator { + const fn new(base: u64, size: u64) -> Self { + Self { + end: base + size, + current: base, + } + } + + fn alloc(&mut self, size: u64) -> Option { + if !size.is_power_of_two() { + return None; + } + let ret = align_up(self.current, size); + if ret + size > self.end { + return None; + } + self.current = ret + size; + Some(ret) + } +} + +const fn align_up(addr: u64, align: u64) -> u64 { + (addr + align - 1) & !(align - 1) +} + +fn configure_pci_device( + root: &mut PciRoot>, + bdf: DeviceFunction, + allocator: &mut PciRangeAllocator, +) -> Result<(), ()> { + let mut bar = 0u8; + while bar < 6 { + let info = root.bar_info(bdf, bar).map_err(|_| ())?; + if let Some(BarInfo::Memory { + address_type, + address, + size, + .. + }) = info.clone() + { + if size > 0 && address == 0 { + let Some(new_addr) = allocator.alloc(size) else { + return Err(()); + }; + match address_type { + MemoryBarType::Width32 => root.set_bar_32(bdf, bar, new_addr as u32), + MemoryBarType::Width64 => root.set_bar_64(bdf, bar, new_addr), + MemoryBarType::Below1MiB => root.set_bar_32(bdf, bar, new_addr as u32), + } + } + } + let takes_two = info.as_ref().is_some_and(BarInfo::takes_two_entries); + bar += if takes_two { 2 } else { 1 }; + } + + let (_status, cmd) = root.get_status_command(bdf); + root.set_command( + bdf, + cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + Ok(()) +} + +fn probe_virtio_pci_device( + root: &mut PciRoot>, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, +) -> Option { + let dev_type = virtio_device_type(dev_info)?; + match (dev_type, dev_info.device_id) { + (DeviceType::Network, 0x1000) | (DeviceType::Network, 0x1040) => {} + (DeviceType::Block, 0x1001) | (DeviceType::Block, 0x1041) => {} + _ => return None, + } + info!("[pci] found virtio device {:?} at {}", dev_type, bdf); + PciTransport::new::>(root, bdf).ok() +} diff --git a/os/src/platform/loongarch/qemu_virt/rtc.rs b/os/src/platform/loongarch/qemu_virt/rtc.rs new file mode 100644 index 00000000..2ab55b2c --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/rtc.rs @@ -0,0 +1,154 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::sync::Arc; +use lazy_static::lazy_static; + +use crate::sync::SpinNoIrqLock; + +use super::VIRT_RTC; + +const TOY_TRIM: usize = 0x20; +const TOY_WRITE0: usize = 0x24; +const TOY_WRITE1: usize = 0x28; +const TOY_READ0: usize = 0x2c; +const TOY_READ1: usize = 0x30; +const RTC_CTRL: usize = 0x40; + +#[inline(always)] +fn mmio_read32(addr: usize) -> u32 { + unsafe { core::ptr::read_volatile(addr as *const u32) } +} + +#[inline(always)] +fn mmio_write32(addr: usize, val: u32) { + unsafe { core::ptr::write_volatile(addr as *mut u32, val) } +} + +struct Rtc { + base: usize, +} + +impl Rtc { + fn new(base: usize) -> Self { + Self { base } + } + + fn init(&self) { + mmio_write32(self.base + TOY_TRIM, 0); + let ctrl = mmio_read32(self.base + RTC_CTRL); + mmio_write32(self.base + RTC_CTRL, ctrl | (1 << 11) | (1 << 8)); + } + + fn read_time_ns(&self) -> u64 { + let read0 = mmio_read32(self.base + TOY_READ0); + let raw_year = mmio_read32(self.base + TOY_READ1) as u64; + let year = if raw_year < 1900 { + raw_year + 1900 + } else { + raw_year + }; + warn!("ls7a-rtc raw: TOY_READ0={:#010x} TOY_READ1={}", read0, raw_year); + let mon = ((read0 >> 26) & 0x3f) as u64; + let day = ((read0 >> 21) & 0x1f) as u64; + let hour = ((read0 >> 16) & 0x1f) as u64; + let min = ((read0 >> 10) & 0x3f) as u64; + let sec = ((read0 >> 4) & 0x3f) as u64; + let days = days_since_epoch(year, mon, day); + (days * 86_400 + hour * 3_600 + min * 60 + sec) * 1_000_000_000 + } + + fn write_time_ns(&self, time_ns: u64) { + let secs = time_ns / 1_000_000_000; + let (year, mon, day, hour, min, sec) = secs_to_calendar(secs); + let read0 = ((mon as u32) << 26) + | ((day as u32) << 21) + | ((hour as u32) << 16) + | ((min as u32) << 10) + | ((sec as u32) << 4); + mmio_write32(self.base + TOY_WRITE0, read0); + let write_year = if year >= 1900 { year - 1900 } else { year }; + mmio_write32(self.base + TOY_WRITE1, write_year as u32); + } +} + +fn days_since_epoch(year: u64, mon: u64, day: u64) -> u64 { + const DAYS_BEFORE_MONTH: [u64; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + let month = mon.max(1); + let day = day.max(1); + let leap_days = (year / 4).saturating_sub(year / 100) + year / 400; + let base = year * 365 + leap_days; + let is_leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + let leap_adjust = if month > 2 && is_leap { 1 } else { 0 }; + let year_days = base + DAYS_BEFORE_MONTH[month as usize] + leap_adjust + day - 1; + const EPOCH_DAYS: u64 = 1970 * 365 + (1970 / 4) - (1970 / 100) + (1970 / 400); + year_days.saturating_sub(EPOCH_DAYS) +} + +fn secs_to_calendar(secs: u64) -> (u64, u64, u64, u64, u64, u64) { + const DAYS_IN_MONTH: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + let sec = secs % 60; + let min = (secs / 60) % 60; + let hour = (secs / 3_600) % 24; + let mut days = secs / 86_400; + let mut year = 1970u64; + loop { + let days_in_year = if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 { + 366 + } else { + 365 + }; + if days < days_in_year { + break; + } + days -= days_in_year; + year += 1; + } + let is_leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + let mut mon = 1u64; + for (idx, days_in_month) in DAYS_IN_MONTH.iter().enumerate() { + let days_in_month = *days_in_month + if idx == 1 && is_leap { 1 } else { 0 }; + if days < days_in_month { + mon = idx as u64 + 1; + break; + } + days -= days_in_month; + } + (year, mon, days + 1, hour, min, sec) +} + +lazy_static! { + static ref RTC: Arc> = Arc::new(SpinNoIrqLock::new(Rtc::new(VIRT_RTC))); +} + +static RTC_READY: AtomicBool = AtomicBool::new(false); + +pub fn init() { + if !super::rtc_is_supported() { + error!("rtc init skipped on this platform"); + return; + } + let rtc = RTC.lock(); + rtc.init(); + let time_ns = rtc.read_time_ns(); + drop(rtc); + crate::random::add_entropy(&time_ns.to_le_bytes()); + RTC_READY.store(true, Ordering::Release); + warn!( + "rtc init done, realtime = {}.{:09} s", + time_ns / 1_000_000_000, + time_ns % 1_000_000_000 + ); +} + +pub fn rtc_ready() -> bool { + RTC_READY.load(Ordering::Acquire) +} + +pub fn read_time_ns() -> u64 { + RTC.lock().read_time_ns() +} + +pub fn write_time_ns(time_ns: u64) { + RTC.lock().write_time_ns(time_ns); +} diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs new file mode 100644 index 00000000..95b17a6e --- /dev/null +++ b/os/src/platform/mod.rs @@ -0,0 +1,64 @@ +//! Platform-specific machine composition. +//! +//! `arch` describes ISA and privilege-architecture behavior. +//! `drivers` describe reusable device-IP drivers. +//! `platform` binds one concrete machine model to those two layers: MMIO +//! layout, interrupt routing, device probing, poweroff, SMP bring-up, and +//! early console policy all belong here. +#![allow(missing_docs)] + +#[cfg(target_arch = "riscv64")] +pub mod riscv; + +#[cfg(target_arch = "loongarch64")] +pub mod loongarch; + +#[cfg(target_arch = "riscv64")] +pub use riscv::qemu_virt::rtc; + +#[cfg(target_arch = "loongarch64")] +pub use loongarch::qemu_virt::rtc; + +#[cfg(target_arch = "riscv64")] +pub use riscv::qemu_virt::{ + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, + console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, + direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, + init_external_irq, init_external_irq_hart, kernel_heap_virtual_window_supported, + machine_name, platform_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, + KERNEL_HEAP_BASE, SbiPlatform as PlatformImpl, TRAMPOLINE, +}; + +#[cfg(target_arch = "loongarch64")] +pub use loongarch::qemu_virt::{ + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, + VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, console_getchar, console_putchar, + console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, + early_console_write, handle_external_irq, heap_debug_enabled, init_external_irq, + init_external_irq_hart, init_ipi_hart, kernel_heap_virtual_window_supported, machine_name, + platform_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, clear_ipi_vector, + start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, + KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, +}; + +/// Initialize platform-owned devices and interrupt routing. +pub fn init() { + rtc::init(); + init_external_irq(); + probe_platform_devices(); +} + +/// Initialize per-hart platform-owned local interrupt/IPI state. +pub fn init_local_hart() { + #[cfg(target_arch = "loongarch64")] + init_ipi_hart(); +} + +/// Clear the current hart's pending platform IPI state when applicable. +pub fn clear_ipi() { + #[cfg(target_arch = "loongarch64")] + clear_ipi_vector(); +} diff --git a/os/src/platform/riscv/mod.rs b/os/src/platform/riscv/mod.rs new file mode 100644 index 00000000..b98ba6a2 --- /dev/null +++ b/os/src/platform/riscv/mod.rs @@ -0,0 +1,6 @@ +//! RISC-V platform implementations. +//! +//! This layer binds one concrete machine/board model onto the generic RISC-V +//! architecture support and reusable device drivers. + +pub mod qemu_virt; diff --git a/os/src/platform/riscv/qemu_virt/board.rs b/os/src/platform/riscv/qemu_virt/board.rs new file mode 100644 index 00000000..1f0a45d1 --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/board.rs @@ -0,0 +1,110 @@ +//! Static board description for the RISC-V QEMU `virt` machine. + +/// default base address for anonymous mmap allocations +pub const USER_MMAP_BASE: usize = 0x1000_0000; + +/// default base address for the main thread's user stack region +pub const USER_STACK_BASE: usize = 0x0800_0000; + +/// base address for loading dynamic linker (interpreter) +pub const INTERP_BASE: usize = 0x4000_0000; + +/// Clock frequency. +pub const CLOCK_FREQ: usize = 12_500_000; + +/// MMIO windows exposed by the machine. +pub const MMIO: &[(usize, usize)] = &[ + (0x0C00_0000, 0x400000), // PLIC + (0x0010_0000, 0x00_2000), // VIRT_TEST/RTC + (0x1000_0000, 0x100), // UART0 (NS16550a) + (0x1000_1000, 0x8000), // VirtIO MMIO devices, 8 slots, each slot occupies 0x1000 bytes +]; + +/// UART0 MMIO base address. +pub const VIRT_UART: usize = 0x1000_0000; +/// Goldfish RTC MMIO base address. +pub const VIRT_RTC: usize = 0x0010_1000; +/// VirtIO MMIO window base address. +pub const VIRTIO_MMIO_BASE: usize = 0x1000_1000; +/// Size of each VirtIO MMIO slot. +pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; +/// Number of VirtIO MMIO slots exposed by the machine. +pub const VIRTIO_MMIO_SLOTS: usize = 8; +/// First IRQ line assigned to VirtIO MMIO devices. +pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; + +/// Block device implementation for QEMU `virt`. +pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +/// Char device implementation for QEMU `virt`. +pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; + +use core::arch::asm; + +const EXIT_SUCCESS: u32 = 0x5555; +const EXIT_FAILURE_FLAG: u32 = 0x3333; +const EXIT_FAILURE: u32 = exit_code_encode(1); +const EXIT_RESET: u32 = 0x7777; + +/// QEMU exit interface. +pub trait QEMUExit { + /// Exit with the specified return code. + fn exit(&self, code: u32) -> !; + + /// Exit QEMU using `EXIT_SUCCESS`, aka `0`, if possible. + fn exit_success(&self) -> !; + + /// Exit QEMU using `EXIT_FAILURE`, aka `1`. + fn exit_failure(&self) -> !; +} + +/// RISC-V QEMU exit wrapper. +pub struct RISCV64 { + /// Address of the sifive_test mapped device. + addr: u64, +} + +/// Encode the exit code using `EXIT_FAILURE_FLAG`. +const fn exit_code_encode(code: u32) -> u32 { + (code << 16) | EXIT_FAILURE_FLAG +} + +impl RISCV64 { + /// Create an instance. + pub const fn new(addr: u64) -> Self { + RISCV64 { addr } + } +} + +impl QEMUExit for RISCV64 { + fn exit(&self, code: u32) -> ! { + let code_new = match code { + EXIT_SUCCESS | EXIT_FAILURE | EXIT_RESET => code, + _ => exit_code_encode(code), + }; + + unsafe { + asm!( + "sw {0}, 0({1})", + in(reg) code_new, + in(reg) self.addr + ); + + loop { + asm!("wfi", options(nomem, nostack)); + } + } + } + + fn exit_success(&self) -> ! { + self.exit(EXIT_SUCCESS); + } + + fn exit_failure(&self) -> ! { + self.exit(EXIT_FAILURE); + } +} + +const VIRT_TEST: u64 = 0x100000; + +/// Global QEMU exit handle using the sifive_test device. +pub const QEMU_EXIT_HANDLE: RISCV64 = RISCV64::new(VIRT_TEST); diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs new file mode 100644 index 00000000..c0495ca5 --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -0,0 +1,174 @@ +//! QEMU `virt` platform for RISC-V. + +mod board; +pub mod rtc; +pub mod sbi; + +pub use board::{ + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; +pub use sbi::SbiPlatform; + +pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; +pub const TRAMPOLINE: usize = usize::MAX - 0x1000 + 1; + +/// Initialize platform external interrupt routing on the bootstrap hart. +pub fn init_external_irq() { + crate::drivers::plic::init(); +} + +/// Initialize per-hart external interrupt state. +pub fn init_external_irq_hart(hart_id: usize) { + crate::drivers::plic::init_hart(hart_id); +} + +/// Dispatch one platform external interrupt. +pub fn handle_external_irq() { + crate::drivers::plic::handle_supervisor_external(); +} + +/// Whether the console RX interrupt path is ready for blocking reads. +pub fn console_rx_irq_ready() -> bool { + true +} + +/// Probe platform-specific devices after generic driver init. +pub fn probe_platform_devices() { + crate::drivers::block::probe_block_devices(); + crate::drivers::net::probe_net_devices(); +} + +/// RISC-V always uses the normal UART path once the console layer is up. +pub fn use_early_console() -> bool { + false +} + +/// Write one string through the earliest available console path. +pub fn early_console_write(s: &str) { + for byte in s.bytes() { + sbi::console_putchar(byte as usize); + } +} + +/// Write one character to the platform console. +pub fn console_putchar(c: usize) { + sbi::console_putchar(c); +} + +/// Read one character from the platform console. +pub fn console_getchar() -> usize { + sbi::console_getchar() +} + +/// Power off the virtual machine. +pub fn shutdown() -> ! { + sbi::shutdown() +} + +/// Return the uname-style machine string. +pub fn machine_name() -> &'static str { + "riscv64" +} + +/// Return the platform name for display purposes. +pub fn platform_name() -> &'static str { + "qemu virt" +} + +/// Discover stopped harts via SBI HSM and start them on QEMU `virt`. +pub fn start_secondary_harts(bootstrap_hart_id: usize) { + extern "C" { + fn _start(); + } + + const SBI_SUCCESS: isize = 0; + const SBI_ERR_INVALID_PARAM: isize = -3; + const SBI_ERR_ALREADY_AVAILABLE: isize = -6; + + info!( + "hart {} entering HSM probe/start loop", + bootstrap_hart_id + ); + + for target_hart in 0..crate::config::MAX_HARTS { + let status = sbi::hart_get_status(target_hart); + if status.error == SBI_ERR_INVALID_PARAM { + info!( + "hart {} got invalid hart id while probing hart {}, stop scan", + bootstrap_hart_id, target_hart + ); + break; + } + if status.error != SBI_SUCCESS { + info!( + "hart {} HSM status query for hart {} failed: error={}, value={}", + bootstrap_hart_id, target_hart, status.error, status.value + ); + continue; + } + + let state = sbi::hart_state(status.value); + info!( + "hart {} sees hart {} in HSM state {:?}", + bootstrap_hart_id, target_hart, state + ); + + if target_hart == bootstrap_hart_id { + continue; + } + + if let sbi::HartState::Stopped = state { + let ret = sbi::hart_start(target_hart, _start as usize, 0); + match ret.error { + SBI_SUCCESS => info!( + "hart {} requested startup for hart {}", + bootstrap_hart_id, target_hart + ), + SBI_ERR_ALREADY_AVAILABLE => info!( + "hart {} found hart {} already available while starting", + bootstrap_hart_id, target_hart + ), + error => info!( + "hart {} failed to start hart {}: error={}, value={}", + bootstrap_hart_id, target_hart, error, ret.value + ), + } + } + } +} + +/// Translate one direct-mapped physical address into the kernel VA used on this platform. +pub fn direct_map_phys_to_virt(pa: usize) -> usize { + pa +} + +/// Translate one direct-mapped kernel VA back into a physical address. +pub fn direct_map_virt_to_phys(va: usize) -> usize { + va +} + +/// Translate a direct-mapped kernel VA into a physical address when applicable. +pub fn translate_direct_mapped_kernel_va(_va: usize) -> Option { + None +} + +/// Translate one MMIO physical address into the VA used by drivers. +pub fn mmio_phys_to_virt(paddr: usize) -> usize { + paddr +} + +/// Whether the Goldfish RTC is supported on this platform. +pub fn rtc_is_supported() -> bool { + true +} + +/// Whether the kernel heap may grow inside its dedicated virtual window. +pub fn kernel_heap_virtual_window_supported() -> bool { + true +} + +/// Whether extra heap bring-up debugging is enabled for this platform. +pub fn heap_debug_enabled() -> bool { + false +} diff --git a/os/src/platform/riscv/qemu_virt/rtc.rs b/os/src/platform/riscv/qemu_virt/rtc.rs new file mode 100644 index 00000000..d0222896 --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/rtc.rs @@ -0,0 +1,82 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::sync::Arc; +use lazy_static::lazy_static; + +use crate::sync::SpinNoIrqLock; + +use super::VIRT_RTC; + +#[inline(always)] +fn mmio_read32(addr: usize) -> u32 { + unsafe { core::ptr::read_volatile(addr as *const u32) } +} + +#[inline(always)] +fn mmio_write32(addr: usize, val: u32) { + unsafe { core::ptr::write_volatile(addr as *mut u32, val) } +} + +struct Rtc { + base: usize, +} + +impl Rtc { + fn new(base: usize) -> Self { + Self { base } + } + + fn init(&self) { + // Clear any pending interrupt. + mmio_write32(self.base + 0x10, 1); + } + + fn read_time_ns(&self) -> u64 { + // Must read LOW first; HIGH then latches to match that read. + let low = mmio_read32(self.base + 0x00) as u64; + let high = mmio_read32(self.base + 0x04) as u64; + (high << 32) | low + } + + fn write_time_ns(&self, time_ns: u64) { + // TODO: writes are non-atomic on Goldfish RTC. + mmio_write32(self.base + 0x00, time_ns as u32); + mmio_write32(self.base + 0x04, (time_ns >> 32) as u32); + } +} + +lazy_static! { + static ref RTC: Arc> = Arc::new(SpinNoIrqLock::new(Rtc::new(VIRT_RTC))); +} + +static RTC_READY: AtomicBool = AtomicBool::new(false); + +pub fn init() { + if !super::rtc_is_supported() { + error!("rtc init skipped on this platform"); + return; + } + let rtc = RTC.lock(); + rtc.init(); + let time_ns = rtc.read_time_ns(); + drop(rtc); + crate::random::add_entropy(&time_ns.to_le_bytes()); + RTC_READY.store(true, Ordering::Release); + warn!( + "rtc init done, realtime = {}.{:09} s", + time_ns / 1_000_000_000, + time_ns % 1_000_000_000 + ); +} + +pub fn rtc_ready() -> bool { + RTC_READY.load(Ordering::Acquire) +} + +pub fn read_time_ns() -> u64 { + RTC.lock().read_time_ns() +} + +pub fn write_time_ns(time_ns: u64) { + RTC.lock().write_time_ns(time_ns); +} diff --git a/os/src/platform/riscv/qemu_virt/sbi.rs b/os/src/platform/riscv/qemu_virt/sbi.rs new file mode 100644 index 00000000..7f675bde --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/sbi.rs @@ -0,0 +1,38 @@ +//! SBI glue for the RISC-V QEMU `virt` platform. + +pub(crate) use crate::sbi::{ + console_getchar_raw as console_getchar, console_putchar_raw as console_putchar, + hart_get_status_raw as hart_get_status, hart_start_raw as hart_start, hart_state, + send_ipi_mask_raw as send_ipi_mask, set_timer_raw as set_timer, shutdown_raw as shutdown, + HartState, +}; + +use crate::hal::traits::{HartCtrl, Timer}; + +/// SBI-backed implementation of [`Timer`] and [`HartCtrl`] for QEMU `virt`. +pub struct SbiPlatform; + +impl Timer for SbiPlatform { + fn read_time() -> usize { + riscv::register::time::read() + } + + fn set_next(deadline: usize) { + set_timer(deadline); + } + + fn clock_freq() -> usize { + crate::config::CLOCK_FREQ + } +} + +impl HartCtrl for SbiPlatform { + fn start_hart(hart_id: usize, start_addr: usize, opaque: usize) -> Result<(), ()> { + let ret = hart_start(hart_id, start_addr, opaque); + if ret.error == 0 { Ok(()) } else { Err(()) } + } + + fn send_ipi(hart_mask: usize) { + send_ipi_mask(hart_mask); + } +} diff --git a/os/src/sbi.rs b/os/src/sbi.rs index f118287d..43c56c90 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -1,7 +1,12 @@ //! SBI call wrappers +//! Low-level implementation kept here; platform/qemu_virt/sbi.rs wraps HAL traits on top. #![allow(unused)] +use crate::fs::sync_page_cache_all; +use crate::hal::traits::HartCtrl as _; + +#[cfg(target_arch = "riscv64")] use core::arch::asm; /// SBI v0.2+ 调用返回值。 @@ -75,6 +80,7 @@ fn sbi_call_legacy(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize /// 通用 SBI v0.2+ 扩展调用。 #[inline(always)] +#[cfg(target_arch = "riscv64")] fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize, arg2: usize) -> SbiRet { let error: usize; let value: usize; @@ -96,76 +102,107 @@ fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize, arg2: u /// use sbi call to set timer #[cfg(qemu7)] -pub fn set_timer(timer: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn set_timer_raw(timer: usize) { sbi_call_legacy(SBI_SET_TIMER, timer, 0, 0); } /// use sbi call to putchar in console (qemu uart handler) #[cfg(not(qemu7))] -pub fn set_timer(timer: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn set_timer_raw(timer: usize) { let _ = sbi_call(SBI_SET_TIMER, 0, timer, 0, 0); } /// use sbi call to putchar in console (qemu uart handler) #[cfg(qemu7)] -pub fn console_putchar(c: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn console_putchar_raw(c: usize) { sbi_call_legacy(SBI_CONSOLE_PUTCHAR, c, 0, 0); } /// use sbi call to getchar from console (qemu uart handler) #[cfg(not(qemu7))] -pub fn console_putchar(c: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn console_putchar_raw(c: usize) { let _ = sbi_call(SBI_CONSOLE_PUTCHAR, 0, c, 0, 0); } +/// Write one byte to the platform console. +pub fn console_putchar(c: usize) { + crate::platform::console_putchar(c); +} + /// use sbi call to getchar from console (qemu uart handler) #[cfg(qemu7)] -pub fn console_getchar() -> usize { +#[cfg(target_arch = "riscv64")] +pub(crate) fn console_getchar_raw() -> usize { sbi_call_legacy(SBI_CONSOLE_GETCHAR, 0, 0, 0) } /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] -pub fn console_getchar() -> usize { +#[cfg(target_arch = "riscv64")] +pub(crate) fn console_getchar_raw() -> usize { sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0, 0).value } +/// Read one byte from the platform console. +pub fn console_getchar() -> usize { + crate::platform::console_getchar() +} + /// use sbi call to shutdown the kernel #[cfg(qemu7)] -pub fn shutdown() -> ! { - let _ = sync_page_cache_all(); +#[cfg(target_arch = "riscv64")] +pub(crate) fn shutdown_raw() -> ! { sbi_call_legacy(SBI_SHUTDOWN, 0, 0, 0); panic!("It should shutdown!"); } /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] -pub fn shutdown() -> ! { - let _ = sync_page_cache_all(); +#[cfg(target_arch = "riscv64")] +pub(crate) fn shutdown_raw() -> ! { let _ = sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); panic!("It should shutdown!"); } +/// Shut down the machine through the current platform backend. +pub fn shutdown() -> ! { + let _ = sync_page_cache_all(); + crate::platform::shutdown() +} + /// 发送 IPI 到给定 hart mask。 #[cfg(qemu7)] -pub fn send_ipi_mask(hart_mask: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn send_ipi_mask_raw(hart_mask: usize) { let hart_mask_ptr = &hart_mask as *const usize as usize; sbi_call_legacy(SBI_SEND_IPI, hart_mask_ptr, 0, 0); } /// 发送 IPI 到给定 hart mask。 #[cfg(not(qemu7))] -pub fn send_ipi_mask(hart_mask: usize) { +#[cfg(target_arch = "riscv64")] +pub(crate) fn send_ipi_mask_raw(hart_mask: usize) { let _ = sbi_call(SBI_IPI, 0, hart_mask, 0, 0); } +/// Send an inter-processor interrupt to the given hart mask. +pub fn send_ipi_mask(hart_mask: usize) { + crate::hal::Plat::send_ipi(hart_mask); +} + /// 查询指定 hart 的 HSM 状态。 -pub fn hart_get_status(hart_id: usize) -> SbiRet { +#[cfg(target_arch = "riscv64")] +pub(crate) fn hart_get_status_raw(hart_id: usize) -> SbiRet { sbi_call(SBI_HSM, 2, hart_id, 0, 0) } /// 请求启动指定 hart,并让它从 `start_addr` 开始执行。 -pub fn hart_start(hart_id: usize, start_addr: usize, opaque: usize) -> SbiRet { +#[cfg(target_arch = "riscv64")] +pub(crate) fn hart_start_raw(hart_id: usize, start_addr: usize, opaque: usize) -> SbiRet { sbi_call(SBI_HSM, 0, hart_id, start_addr, opaque) } @@ -182,4 +219,3 @@ pub fn hart_state(raw: usize) -> HartState { other => HartState::Unknown(other), } } -use crate::fs::sync_page_cache_all; diff --git a/os/src/sched/api.rs b/os/src/sched/api.rs index 86a9f13d..8a641869 100644 --- a/os/src/sched/api.rs +++ b/os/src/sched/api.rs @@ -5,7 +5,7 @@ use super::{ defer_task_release_after_switch, has_runnable_task_at_or_above, schedule, take_current_task, TaskContext, }; -use crate::hart::hartid; +use crate::hal::hartid; use crate::sched::CFS_YIELD_PENALTY_NS; use crate::task::{ReschedReason, SchedPolicy, TaskStatus, WaitReason}; use crate::timer::{get_time, get_time_ns}; diff --git a/os/src/sched/processor.rs b/os/src/sched/processor.rs index 873d40b8..5da42c09 100644 --- a/os/src/sched/processor.rs +++ b/os/src/sched/processor.rs @@ -7,7 +7,8 @@ use super::__switch; use super::{add_task, pick_next_task, TaskContext}; use crate::config::MAX_HARTS; -use crate::hart::hartid; +use crate::hal::{enable_irqs_and_wait, hartid}; +use crate::hal::traits::AddressSpaceToken; use crate::sync::SpinNoIrqLock; use crate::task::{ProcessControlBlock, SchedPolicy, TaskControlBlock, TaskStatus, INITPROC}; use crate::timer::{get_time, get_time_ns}; @@ -128,12 +129,15 @@ pub(crate) fn run_tasks() { } // debug!("No task to run, idle..."); + if !crate::platform::console_rx_irq_ready() { + // Keep the old cooperative polling path only as a pre-init + // fallback before the EXTIOI/PCH-PIC chain is configured. + crate::fs::console_receive(); + } crate::trap::set_kernel_trap_entry(); - unsafe { riscv::register::sstatus::set_sie() }; - unsafe { riscv::asm::wfi() }; - unsafe { riscv::register::sstatus::clear_sie() }; + unsafe { enable_irqs_and_wait() }; } } } @@ -174,8 +178,8 @@ pub fn current_process() -> Arc { current_task().unwrap().process.upgrade().unwrap() } -/// Get the current user token(addr of page table) -pub fn current_user_token() -> usize { +/// Get the current user address-space token. +pub fn current_user_token() -> AddressSpaceToken { let task = current_task().unwrap(); task.get_user_token() } diff --git a/os/src/sched/runqueue.rs b/os/src/sched/runqueue.rs index f4f26a55..3b66ded7 100644 --- a/os/src/sched/runqueue.rs +++ b/os/src/sched/runqueue.rs @@ -2,7 +2,7 @@ use super::current_task; use crate::config::MAX_HARTS; -use crate::hart::hartid; +use crate::hal::hartid; use crate::mm::online_mask as online_hart_mask; use crate::sbi::send_ipi_mask; use crate::sched::{request_current_task_resched, CFS_WAKEUP_GRANULARITY_NS}; diff --git a/os/src/sched/switch.rs b/os/src/sched/switch.rs index 502c26f4..c8e338b6 100644 --- a/os/src/sched/switch.rs +++ b/os/src/sched/switch.rs @@ -1,8 +1,5 @@ //!provides __switch asm function to switch between two task contexts [`TaskContext`] use super::context::TaskContext; -use core::arch::global_asm; - -global_asm!(include_str!("switch.S")); extern "C" { /// Switch to the context of `next_task_cx_ptr`, saving the current context diff --git a/os/src/signal/action.rs b/os/src/signal/action.rs index e8448641..11b136bc 100644 --- a/os/src/signal/action.rs +++ b/os/src/signal/action.rs @@ -1,5 +1,6 @@ use crate::signal::signals::MAX_SIG; use crate::syscall::Pod; +use crate::trap::TrapContext; /// Action for a signal (Linux rt_sigaction layout) #[repr(C)] @@ -183,6 +184,20 @@ impl Default for FpState { impl Pod for FpState {} +impl FpState { + /// Build an fp-state blob from the saved trap context. + pub fn from_trap_context(trap_cx: &TrapContext) -> Self { + let mut fpstate = Self::default(); + trap_cx.copy_fp_state_to(&mut fpstate.fpregs, &mut fpstate.fcsr); + fpstate + } + + /// Restore floating-point state into the saved trap context. + pub fn apply_to_trap_context(&self, trap_cx: &mut TrapContext) { + trap_cx.restore_fp_state(&self.fpregs, self.fcsr); + } +} + /// riscv64 Linux `mcontext_t`. #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -195,6 +210,22 @@ pub struct MContext { impl Pod for MContext {} +impl MContext { + /// Build a Linux-compatible machine context from the saved trap context. + pub fn from_trap_context(trap_cx: &TrapContext) -> Self { + Self { + gregs: trap_cx.export_signal_gprs(), + fpstate: FpState::from_trap_context(trap_cx), + } + } + + /// Restore a Linux-compatible machine context into the saved trap context. + pub fn apply_to_trap_context(&self, trap_cx: &mut TrapContext) { + trap_cx.import_signal_gprs(&self.gregs); + self.fpstate.apply_to_trap_context(trap_cx); + } +} + /// ucontext_t structure #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/os/src/signal/mod.rs b/os/src/signal/mod.rs index c27a2840..6cdc7f16 100644 --- a/os/src/signal/mod.rs +++ b/os/src/signal/mod.rs @@ -2,6 +2,7 @@ use crate::{ config::USER_VDSO_RT_SIGRETURN, + hal::{ArchTrapContextAbi, ArchTrapMachine, traits::{TrapContextAbi, TrapMachine}}, syscall::write_pod_to_user, task::{current_task, current_trap_cx}, }; @@ -199,7 +200,7 @@ pub fn handle_signals() -> Option { let trap_cx = current_trap_cx(); // Save the current user stack pointer - let mut user_sp = trap_cx.x[2]; // sp register + let mut user_sp = trap_cx.user_sp(); // Construct sigframe on user stack // Layout: sp points to ucontext, siginfo is above it if SA_SIGINFO @@ -235,30 +236,29 @@ pub fn handle_signals() -> Option { .bits() }; - let mut mcontext = MContext { - gregs: [0; 32], - fpstate: FpState::default(), - }; - // riscv64 glibc expects the saved PC in gregs[0], followed by x1..x31. - mcontext.gregs[0] = trap_cx.sepc; - mcontext.gregs[1..].copy_from_slice(&trap_cx.x[1..]); - mcontext.fpstate.fpregs.copy_from_slice(&trap_cx.f); - mcontext.fpstate.fcsr = trap_cx.fcsr as u32; + let mut mcontext = MContext::from_trap_context(trap_cx); // Syscall restart: if the signal interrupted a syscall that returned -EINTR // and SA_RESTART is set, back up PC to the ecall instruction and restore // original a0 so the syscall can be restarted after sigreturn. if trap_cx.in_syscall { - let result = trap_cx.x[10] as isize; + let result = trap_cx.syscall_ret() as isize; if result == -(crate::syscall::errno::ERRNO::EINTR as isize) { if trap_cx.restartable_syscall && action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { + let a0_idx = ::signal_gpr_arg0_index(); debug!( - "handle_signals: syscall restart: backing up PC from {:#x} to {:#x}, restoring a0 from {:#x} to {:#x}", - mcontext.gregs[0], trap_cx.sepc.wrapping_sub(4), - mcontext.gregs[10], trap_cx.orig_a0 + "handle_signals: syscall restart: backing up PC from {:#x} to {:#x}, restoring a0 (gregs[{}]) from {:#x} to {:#x}", + mcontext.gregs[0], + trap_cx + .user_pc() + .wrapping_sub(ArchTrapMachine::syscall_instruction_len()), + a0_idx, + mcontext.gregs[a0_idx], trap_cx.orig_a0 ); - mcontext.gregs[0] = trap_cx.sepc.wrapping_sub(4); - mcontext.gregs[10] = trap_cx.orig_a0; + mcontext.gregs[0] = trap_cx + .user_pc() + .wrapping_sub(ArchTrapMachine::syscall_instruction_len()); + mcontext.gregs[a0_idx] = trap_cx.orig_a0; } else if action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { debug!( "handle_signals: syscall returned EINTR but syscall is not restartable, preserving EINTR" @@ -329,29 +329,29 @@ pub fn handle_signals() -> Option { // Set up trap context to call signal handler // sp points to ucontext (aligned) - trap_cx.x[2] = user_sp; + trap_cx.set_user_sp(user_sp); // Set up arguments based on SA_SIGINFO if action.sa_flags & SaFlags::SA_SIGINFO.bits() != 0 { // SA_SIGINFO: handler(signum, siginfo*, ucontext*) - trap_cx.x[10] = signum as usize; // a0 = signum - trap_cx.x[11] = siginfo_ptr; // a1 = siginfo* - trap_cx.x[12] = ucontext_ptr; // a2 = ucontext* + trap_cx.set_user_arg(0, signum as usize); // a0 = signum + trap_cx.set_user_arg(1, siginfo_ptr); // a1 = siginfo* + trap_cx.set_user_arg(2, ucontext_ptr); // a2 = ucontext* } else { // Traditional: handler(signum) - trap_cx.x[10] = signum as usize; // a0 = signum + trap_cx.set_user_arg(0, signum as usize); // a0 = signum } // Set return address (ra) to restorer or kernel fallback if action.sa_flags & SaFlags::SA_RESTORER.bits() != 0 && action.sa_restorer != 0 { - trap_cx.x[1] = action.sa_restorer; // ra = sa_restorer + trap_cx.set_ra(action.sa_restorer); // ra = sa_restorer debug!( "handle_signals: using user restorer at {:#x}", action.sa_restorer ); } else { // RISC-V Linux 不要求用户态提供 SA_RESTORER,统一回到内核提供的 trampoline。 - trap_cx.x[1] = USER_VDSO_RT_SIGRETURN; + trap_cx.set_ra(USER_VDSO_RT_SIGRETURN); debug!( "handle_signals: using kernel vdso rt_sigreturn at {:#x}", USER_VDSO_RT_SIGRETURN @@ -359,11 +359,13 @@ pub fn handle_signals() -> Option { } // Jump to signal handler - trap_cx.sepc = action.handler; + trap_cx.set_user_pc(action.handler); debug!( "handle_signals: setup complete, jumping to handler={:#x}, ra={:#x}, sp={:#x}", - trap_cx.sepc, trap_cx.x[1], trap_cx.x[2] + trap_cx.user_pc(), + trap_cx.ra(), + trap_cx.user_sp() ); None } diff --git a/os/src/sync/spin.rs b/os/src/sync/spin.rs index e948e184..bfa69520 100644 --- a/os/src/sync/spin.rs +++ b/os/src/sync/spin.rs @@ -15,8 +15,6 @@ use core::cell::UnsafeCell; use core::ops::{Deref, DerefMut}; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; - // --------------------------------------------------------------------------- // SpinLock – plain spinlock (no interrupt masking) // --------------------------------------------------------------------------- @@ -118,8 +116,8 @@ impl SpinNoIrqLock { /// Acquire the lock with interrupts disabled. pub fn lock(&self) -> SpinNoIrqLockGuard<'_, T> { // Save the current SIE bit state and then disable. - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -172,7 +170,7 @@ impl Drop for SpinNoIrqLockGuard<'_, T> { fn drop(&mut self) { self.lock.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index 543f6729..42609d84 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -9,8 +9,6 @@ use core::cell::UnsafeCell; use core::ops::{Deref, DerefMut}; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; - /// Wrap a static data structure inside a spinlock with interrupt masking. /// /// `exclusive_access()` returns a guard that holds the lock and disables @@ -41,8 +39,8 @@ impl UPSafeCell { /// /// Disables supervisor interrupts and spins until the lock is acquired. pub fn exclusive_access(&self) -> UPSafeCellGuard<'_, T> { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -50,10 +48,10 @@ impl UPSafeCell { .is_err() { if sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } core::hint::spin_loop(); - unsafe { sstatus::clear_sie() }; + unsafe { crate::hal::disable_local_irqs() }; } UPSafeCellGuard { @@ -86,7 +84,7 @@ impl Drop for UPSafeCellGuard<'_, T> { fn drop(&mut self) { self.cell.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } @@ -115,8 +113,8 @@ impl UPIntrFreeCell { /// Get exclusive access with interrupts disabled + spinlock. pub fn exclusive_access(&self) -> UPIntrFreeCellRefMut<'_, T> { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -124,10 +122,10 @@ impl UPIntrFreeCell { .is_err() { if sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } core::hint::spin_loop(); - unsafe { sstatus::clear_sie() }; + unsafe { crate::hal::disable_local_irqs() }; } UPIntrFreeCellRefMut { @@ -162,7 +160,7 @@ impl Drop for UPIntrFreeCellRefMut<'_, T> { fn drop(&mut self) { self.cell.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } -} \ No newline at end of file +} diff --git a/os/src/syscall/fs.rs b/os/src/syscall/fs.rs index a9ece4d1..a4f92bc6 100644 --- a/os/src/syscall/fs.rs +++ b/os/src/syscall/fs.rs @@ -477,6 +477,38 @@ fn alloc_anonymous_fd_with_bits( fn alloc_anonymous_fd(status_flags: FileStatusFlags, cloexec: bool) -> Result { alloc_anonymous_fd_with_bits(status_flags, cloexec, 0) } +const AT_NO_AUTOMOUNT: u32 = 0x800; +const AT_STATX_SYNC_TYPE: u32 = 0x6000; + +bitflags! { + struct StatxMask: u32 { + const TYPE = 0x0001; + const MODE = 0x0002; + const NLINK = 0x0004; + const UID = 0x0008; + const GID = 0x0010; + const ATIME = 0x0020; + const MTIME = 0x0040; + const CTIME = 0x0080; + const INO = 0x0100; + const SIZE = 0x0200; + const BLOCKS = 0x0400; + const BTIME = 0x0800; + + const BASIC_STATS = Self::TYPE.bits + | Self::MODE.bits + | Self::NLINK.bits + | Self::UID.bits + | Self::GID.bits + | Self::ATIME.bits + | Self::MTIME.bits + | Self::CTIME.bits + | Self::INO.bits + | Self::SIZE.bits + | Self::BLOCKS.bits; + const ALL = Self::BASIC_STATS.bits | Self::BTIME.bits; + } +} #[derive(Clone, Copy, Debug, Default)] struct PselectFdMeta { @@ -494,6 +526,76 @@ struct PselectSigmaskArg { impl Pod for PselectSigmaskArg {} +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +struct StatxTimestamp { + tv_sec: i64, + tv_nsec: u32, + reserved: i32, +} + +impl From<(isize, isize)> for StatxTimestamp { + fn from((sec, nsec): (isize, isize)) -> Self { + Self { + tv_sec: sec as i64, + tv_nsec: nsec as u32, + reserved: 0, + } + } +} + +/// Linux `statx(2)` userspace ABI. +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Statx { + stx_mask: u32, + stx_blksize: u32, + stx_attributes: u64, + stx_nlink: u32, + stx_uid: u32, + stx_gid: u32, + stx_mode: u16, + stx_spare0: u16, + stx_ino: u64, + stx_size: u64, + stx_blocks: u64, + stx_attributes_mask: u64, + stx_atime: StatxTimestamp, + stx_btime: StatxTimestamp, + stx_ctime: StatxTimestamp, + stx_mtime: StatxTimestamp, + stx_rdev_major: u32, + stx_rdev_minor: u32, + stx_dev_major: u32, + stx_dev_minor: u32, + stx_mnt_id: u64, + stx_dio_mem_align: u32, + stx_dio_offset_align: u32, + stx_spare3: [u64; 12], +} + +impl Pod for Statx {} + +fn stat_to_statx(stat: &Stat, requested_mask: StatxMask) -> Statx { + let available_mask = StatxMask::BASIC_STATS; + let _ = requested_mask; + Statx { + stx_mask: available_mask.bits(), + stx_blksize: stat.blksize, + stx_nlink: stat.nlink, + stx_uid: stat.uid, + stx_gid: stat.gid, + stx_mode: stat.mode.bits() as u16, + stx_ino: stat.ino, + stx_size: stat.size.max(0) as u64, + stx_blocks: stat.blocks, + stx_atime: (stat.atime_sec, stat.atime_nsec).into(), + stx_ctime: (stat.ctime_sec, stat.ctime_nsec).into(), + stx_mtime: (stat.mtime_sec, stat.mtime_nsec).into(), + ..Statx::default() + } +} + /// 从用户态复制 `pollfd` 数组,兼容跨页布局。 fn copy_user_pollfds(token: usize, ufds: *mut PollFd, nfds: usize) -> Result, ERRNO> { if nfds == 0 { @@ -2647,6 +2749,59 @@ let time5 = get_time_us(); }) } +/// `statx(2)` 系统调用:按目录 fd 与路径查询增强版文件元数据。 +pub fn sys_statx( + dirfd: isize, + path: *const u8, + flags: i32, + mask: u32, + stx: *mut Statx, +) -> isize { + trace!( + "kernel:pid[{}] sys_statx", + current_task().unwrap().process.upgrade().unwrap().getpid() + ); + syscall_body!({ + let flags = flags as u32; + let supported_flags = + AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE; + if flags & !supported_flags != 0 { + return Err(ERRNO::EINVAL); + } + let mask = StatxMask::from_bits(mask).ok_or(ERRNO::EINVAL)?; + let path = if path.is_null() { + if flags & AT_EMPTY_PATH == 0 { + return Err(ERRNO::EFAULT); + } + String::new() + } else { + read_cstring_from_user(path, PATH_MAX)? + }; + + let stat = if path.is_empty() { + if flags & AT_EMPTY_PATH == 0 { + return Err(ERRNO::ENOENT); + } + match resolve_at_target(dirfd, "", flags as i32)? { + ResolvedAtTarget::Inode(inode) => inode_stat(&inode), + ResolvedAtTarget::FileDesc(desc) => desc.stat(), + } + } else { + let cwd = resolve_dirfd_base(dirfd, path.as_str())?; + let inode = lookup_inode_follow( + cwd.as_str(), + path.as_str(), + flags & AT_SYMLINK_NOFOLLOW == 0, + )?; + inode_stat(&inode) + }; + + let statx = stat_to_statx(&stat, mask); + write_pod_to_user(stx, &statx)?; + Ok(0) + }) +} + /// `faccessat` 系统调用:按目录 fd 与路径检查可访问性。 pub fn sys_faccessat(dirfd: isize, path: *const u8, mode: i32) -> isize { trace!( diff --git a/os/src/syscall/mman.rs b/os/src/syscall/mman.rs index 56b274f7..183c15b0 100644 --- a/os/src/syscall/mman.rs +++ b/os/src/syscall/mman.rs @@ -265,9 +265,9 @@ pub fn sys_brk(addr: usize) -> isize { "sys_brk: pid={} addr={:#x} tp={:#x} sp={:#x} sepc={:#x}", pid, addr, - cx.x[4], - cx.x[2], - cx.sepc + cx.tls(), + cx.user_sp(), + cx.user_pc() ); current_process().set_program_brk(addr) as isize } diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index 4b974897..7073abf2 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -326,6 +326,8 @@ pub const SYSCALL_MEMFD_CREATE: usize = 279; pub const SYSCALL_BPF: usize = 280; /// userfaultfd syscall pub const SYSCALL_USERFAULTFD: usize = 282; +/// statx syscall +pub const SYSCALL_STATX: usize = 291; /// spawn syscall pub const SYSCALL_SPAWN: usize = 400; /// clock_adjtime64 syscall @@ -604,6 +606,13 @@ pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { args[2] as *mut Stat, args[3] as i32, ), + SYSCALL_STATX => sys_statx( + args[0] as isize, + args[1] as *const u8, + args[2] as i32, + args[3] as u32, + args[4] as *mut crate::syscall::fs::Statx, + ), SYSCALL_UTIMENSAT => sys_utimensat( args[0] as isize, args[1] as *const u8, diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index c189aa47..8ec46933 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -7,7 +7,7 @@ use crate::timer::get_time_ns; use crate::{ config::PAGE_SIZE, fs::{canonicalize, open_file, open_file_at, File, OpenFlags}, - hart::hartid, + hal::hartid, ipc::{self, IPC_RMID}, mm::{translated_ref, translated_str, PageFaultAccess}, task::{ @@ -754,13 +754,13 @@ pub fn sys_clone( } let trap_cx = new_inner.get_trap_cx(); *trap_cx = inherited_cx; - trap_cx.kernel_sp = new_task.kstack.get_top(); - trap_cx.x[10] = 0; + trap_cx.set_kernel_sp(new_task.kstack.get_top()); + trap_cx.set_syscall_ret(0); if stack != 0 { - trap_cx.x[2] = stack; + trap_cx.set_user_sp(stack); } if let Some(tls) = child_tls { - trap_cx.x[4] = tls; + trap_cx.set_tls(tls); } if let Some(ptr) = child_set_tid { write_pod_to_user(ptr as *mut i32, &new_tid)?; @@ -1225,7 +1225,7 @@ impl UtsName { let nodename = b"localhost"; let release = b"6.6.0"; let version = b"#1 SMP PREEMPT cosmOS"; - let machine = b"riscv64"; + let machine = crate::platform::machine_name().as_bytes(); let domainname = b"localdomain"; uname.sysname[..sysname.len()].copy_from_slice(sysname); uname.nodename[..nodename.len()].copy_from_slice(nodename); diff --git a/os/src/syscall/sched.rs b/os/src/syscall/sched.rs index 6667927c..25b14a45 100644 --- a/os/src/syscall/sched.rs +++ b/os/src/syscall/sched.rs @@ -4,7 +4,7 @@ use crate::syscall::{read_pod_from_user, write_bytes_to_user, write_pod_to_user, use crate::syscall_body; use crate::{ config::MAX_HARTS, - hart::hartid, + hal::hartid, mm::{online_mask as online_hart_mask, translated_byte_buffer, translated_ref}, sched::{ enqueue_task_on, has_runnable_task_at_or_above, nice_to_weight, pid2process, remove_task, diff --git a/os/src/syscall/signal.rs b/os/src/syscall/signal.rs index 5041280f..06b9f2c0 100644 --- a/os/src/syscall/signal.rs +++ b/os/src/syscall/signal.rs @@ -365,11 +365,15 @@ pub fn sys_sigprocmask(how: i32, set: *const u64, oset: *mut u64, sigsetsize: us pub fn sys_sigreturn() -> isize { syscall_body!({ let trap_cx = crate::task::current_trap_cx(); - let user_sp = trap_cx.x[2]; // Current sp + let user_sp = trap_cx.user_sp(); debug!( "sys_sigreturn: ENTRY sepc={:#x}, sp={:#x}, ra={:#x}, a0={:#x}, a7={:#x}", - trap_cx.sepc, user_sp, trap_cx.x[1], trap_cx.x[10], trap_cx.x[17] + trap_cx.user_pc(), + user_sp, + trap_cx.ra(), + trap_cx.syscall_ret(), + trap_cx.syscall_nr() ); // Read ucontext from user stack at sp. The frame can cross a page boundary, @@ -401,22 +405,16 @@ pub fn sys_sigreturn() -> isize { // Restore registers from mcontext let mcontext = &ucontext.uc_mcontext; - - trap_cx.x[0] = 0; - trap_cx.x[1..].copy_from_slice(&mcontext.gregs[1..]); - trap_cx.sepc = mcontext.gregs[0]; + mcontext.apply_to_trap_context(trap_cx); debug!( "sys_sigreturn: restored sepc={:#x}, a0={:#x}", - trap_cx.sepc, trap_cx.x[10] + trap_cx.user_pc(), + trap_cx.syscall_ret() ); - // Restore floating-point registers - trap_cx.f.copy_from_slice(&mcontext.fpstate.fpregs); - trap_cx.fcsr = mcontext.fpstate.fcsr as usize; - // Return the original a0 value (which was saved in the trap context) - Ok(trap_cx.x[10] as isize) + Ok(trap_cx.syscall_ret() as isize) }) } diff --git a/os/src/syscall/thread.rs b/os/src/syscall/thread.rs index 8cea4219..de1a4ab9 100644 --- a/os/src/syscall/thread.rs +++ b/os/src/syscall/thread.rs @@ -61,7 +61,7 @@ pub fn sys_thread_create(entry: usize, arg: usize) -> isize { new_task.kstack.get_top(), trap_handler as usize, ); - (*new_task_trap_cx).x[10] = arg; + new_task_trap_cx.set_user_arg(0, arg); drop(new_task_inner); process.attach_task(Arc::clone(&new_task)); add_task(new_task); diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index f01181f3..7dfabe0f 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -56,7 +56,7 @@ pub use task::{ }; pub(crate) use task::TaskControlBlockInner; -use crate::board::QEMUExit; +use crate::platform::QEMUExit; use alloc::string::String; /// Exit the current 'Running' task and run the next task in task list. @@ -152,10 +152,10 @@ pub fn exit_current_and_run_next(reason: ExitReason) { ); if task_exit_code != 0 { //crate::sbi::shutdown(255); //255 == -1 for err hint - crate::board::QEMU_EXIT_HANDLE.exit_failure(); + crate::platform::QEMU_EXIT_HANDLE.exit_failure(); } else { //crate::sbi::shutdown(0); //0 for success hint - crate::board::QEMU_EXIT_HANDLE.exit_success(); + crate::platform::QEMU_EXIT_HANDLE.exit_success(); } } let mut process_inner = process.inner_exclusive_access(); @@ -504,7 +504,7 @@ pub fn check_itimers_of_all_processes(now_raw: usize, now_realtime_ns: u64) { if process::armed_itimers_count() == 0 { return; } - if crate::hart::hartid() != 0 { + if crate::hal::hartid() != 0 { return; } let processes: Vec> = { diff --git a/os/src/task/process.rs b/os/src/task/process.rs index f74b2ff5..ca004547 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -16,6 +16,7 @@ use crate::mm::{ MapPermission, MemorySet, MmError, PageFaultAccess, PageFaultHandled, ShootdownKind, UserSpaceLayout, VirtAddr, Vma, KERNEL_SPACE, }; +use crate::hal::traits::AddressSpaceToken; use crate::sync::{Condvar, DeadlockDetector, Mutex, Semaphore, SpinNoIrqLock, SpinNoIrqLockGuard}; use crate::syscall::errno::ERRNO; use crate::syscall::{write_pod_to_process_user, ResourceLimits}; @@ -645,8 +646,8 @@ fn load_process_image( impl ProcessControlBlockInner { #[allow(unused)] - /// get the address of app's page table - pub fn get_user_token(&self) -> usize { +/// get the current user address-space token + pub fn get_user_token(&self) -> AddressSpaceToken { self.memory_set.token() } fn nofile_limit(&self) -> usize { @@ -983,14 +984,14 @@ impl ProcessControlBlock { trap_handler as usize, ); // RISC-V glibc _start treats a0 as rtld_fini and reads argc/argv from the stack. - trap_cx.x[10] = 0; - trap_cx.x[11] = 0; + trap_cx.set_user_arg(0, 0); + trap_cx.set_user_arg(1, 0); debug!( "kernel: exec trap init entry={:#x} sp={:#x} a0={:#x} a1={:#x}", final_entry, user_sp, - trap_cx.x[10], - trap_cx.x[11] + trap_cx.reg(10), + trap_cx.reg(11) ); *task_inner.get_trap_cx() = trap_cx; Ok(()) @@ -1109,7 +1110,7 @@ impl ProcessControlBlock { parent_token, parent_mask ); - shootdown(parent_mask, ShootdownKind::AddressSpace { satp: parent_token }); + shootdown(parent_mask, ShootdownKind::AddressSpace { token: parent_token }); } debug!( "[cow] clone_process created child process: parent_pid={} child_pid={}", @@ -1141,15 +1142,15 @@ impl ProcessControlBlock { // patches the inherited return register, breaking fork semantics. let task_inner = task.inner_exclusive_access(); let trap_cx = task_inner.get_trap_cx(); - trap_cx.kernel_sp = task.kstack.get_top(); - trap_cx.x[10] = 0; + trap_cx.set_kernel_sp(task.kstack.get_top()); + trap_cx.set_syscall_ret(0); if child_stack != 0 { // Linux clone ABI 要求子进程从指定用户栈继续执行。 - trap_cx.x[2] = child_stack; + trap_cx.set_user_sp(child_stack); } if let Some(tls) = child_tls { // RISC-V 用户态 TLS 指针使用 tp,也就是 x4。 - trap_cx.x[4] = tls; + trap_cx.set_tls(tls); } drop(task_inner); if let Some(child_tid_ptr) = child_set_tid { @@ -1593,7 +1594,7 @@ impl ProcessControlBlock { token, mask ); - shootdown(mask, ShootdownKind::AddressSpace { satp: token }); + shootdown(mask, ShootdownKind::AddressSpace { token }); } ok } @@ -1698,7 +1699,7 @@ impl ProcessControlBlock { pub fn enter_kernel(&self, now: usize) { let mut inner = self.inner.lock(); // trap 入口已经切到内核页表并做过本地 sfence.vma,此 hart 不再持有该用户 mm。 - inner.memory_set.mark_user_unloaded(crate::hart::hartid()); + inner.memory_set.mark_user_unloaded(crate::hal::hartid()); match inner.accounting_state { CpuAccountingState::User => { inner.user_time = inner @@ -1725,7 +1726,7 @@ impl ProcessControlBlock { inner.accounting_state = CpuAccountingState::User; inner.accounting_timestamp = now; // 即将跳回用户态,后续其他 hart 修改该 mm 时需要把当前 hart 作为 shootdown 目标。 - inner.memory_set.mark_user_loaded(crate::hart::hartid()); + inner.memory_set.mark_user_loaded(crate::hal::hartid()); } /// Flush the current running slice into the corresponding accumulator. diff --git a/os/src/task/task.rs b/os/src/task/task.rs index 484ed802..4633f777 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -5,6 +5,7 @@ use super::wait_queue::WaitQueueHandle; use super::{kstack_alloc, KernelStack, ProcessControlBlock, SigInfo, SignalBit, MAX_SIG}; use crate::mm::MmError; use crate::config::MAX_HARTS; +use crate::hal::traits::AddressSpaceToken; use crate::mm::PhysPageNum; use crate::sched::{ReschedReason, SchedAttr, SchedPolicy, TaskContext, NICE_0_LOAD}; use crate::sync::{SpinNoIrqLock, SpinNoIrqLockGuard}; @@ -147,8 +148,8 @@ impl TaskControlBlock { pub fn inner_exclusive_access(&self) -> SpinNoIrqLockGuard<'_, TaskControlBlockInner> { self.inner.lock() } - /// Get the address of app's page table - pub fn get_user_token(&self) -> usize { + /// Get the current user address-space token for this task. + pub fn get_user_token(&self) -> AddressSpaceToken { let process = self.process.upgrade().unwrap(); let inner = process.inner_exclusive_access(); inner.memory_set.token() diff --git a/os/src/timer.rs b/os/src/timer.rs index 3e401a42..6e2cafdd 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -5,19 +5,19 @@ use core::sync::atomic::{AtomicI64, Ordering as AtomicOrdering}; use crate::config::CLOCK_FREQ; use crate::config::MAX_HARTS; -use crate::drivers::rtc; -use crate::hart::hartid; +use crate::hal::hartid; +use crate::hal::Plat; +use crate::hal::traits::Timer as _; +use crate::platform::rtc; use crate::poll::{self, PollTimerTag}; use crate::net::{handle_socket_wait_timeout, SocketTimerTag}; use crate::signal::{handle_signal_wait_timeout, SignalTimerTag}; -use crate::sbi::set_timer; use crate::sync::{FutexTimerTag, SpinNoIrqLock, handle_futex_wait_timeout}; use crate::task::{current_task, wakeup_task, TaskControlBlock}; use alloc::collections::BinaryHeap; use alloc::sync::Arc; use core::array; use lazy_static::*; -use riscv::register::time; /// The number of ticks per second pub const TICKS_PER_SEC: usize = 100; /// The number of milliseconds per second @@ -36,22 +36,22 @@ static REALTIME_OFFSET_NS: AtomicI64 = AtomicI64::new(0); /// Get the current time in ticks pub fn get_time() -> usize { - time::read() + Plat::read_time() } /// Get the current time in milliseconds pub fn get_time_ms() -> usize { - time::read() * MSEC_PER_SEC / CLOCK_FREQ + get_time() * MSEC_PER_SEC / CLOCK_FREQ } /// get current time in microseconds pub fn get_time_us() -> usize { - time::read() * MICRO_PER_SEC / CLOCK_FREQ + get_time() * MICRO_PER_SEC / CLOCK_FREQ } /// 获取当前单调时间,单位为纳秒。 pub fn get_time_ns() -> u64 { - ((time::read() as u128) * (NSEC_PER_SEC as u128) / (CLOCK_FREQ as u128)) as u64 + ((get_time() as u128) * (NSEC_PER_SEC as u128) / (CLOCK_FREQ as u128)) as u64 } /// 使用“当前单调时间 + 实时时钟偏移”得到 `CLOCK_REALTIME`,单位为纳秒。 @@ -87,7 +87,7 @@ pub fn init_realtime_offset_from_rtc() { /// Get current time in clock ticks used by times(2). pub fn get_time_ticks() -> usize { - time::read() * TICKS_PER_SEC / CLOCK_FREQ + get_time() * TICKS_PER_SEC / CLOCK_FREQ } /// Convert a raw timer counter delta into clock ticks used by times(2). @@ -272,7 +272,7 @@ fn program_next_trigger_for_hart(hart: usize, now_raw: usize) { None => periodic_raw, }; let now_raw = now_raw as u64; - set_timer(next_raw.max(now_raw.saturating_add(1)) as usize); + Plat::set_next(next_raw.max(now_raw.saturating_add(1)) as usize); } /// Check if the timer has expired for the current hart. diff --git a/os/src/trap/context.rs b/os/src/trap/context.rs index 1ed1f720..a35a3fce 100644 --- a/os/src/trap/context.rs +++ b/os/src/trap/context.rs @@ -1,32 +1,13 @@ //! Implementation of [`TrapContext`] -use riscv::register::sstatus::{self, Sstatus, SPP}; +use crate::hal::ArchTrapContextAbi; +use crate::hal::traits::TrapContextAbi; #[repr(C)] #[derive(Debug, Clone, Copy)] /// trap context structure containing sstatus, sepc and registers pub struct TrapContext { - /// General-Purpose Register x0-31 - pub x: [usize; 32], - /// Supervisor Status Register - pub sstatus: Sstatus, - /// Supervisor Exception Program Counter - pub sepc: usize, - /// 当前任务上次返回用户态前所在的 hart id。 - /// - /// 用户态可能会把 `tp` 当作 TLS 指针或普通寄存器使用,因此内核不能再假设 - /// trap 进入时 `tp` 里仍然保存着 hart-local 信息。这里单独记录内核需要恢复 - /// 的 hart id,供 trap 入口在切回内核上下文前重新写回 `tp`。 - pub kernel_hartid: usize, - /// Token of kernel address space - pub kernel_satp: usize, - /// Kernel stack pointer of the current application - pub kernel_sp: usize, - /// Virtual address of trap handler entry point in kernel - pub trap_handler: usize, - /// Floating-point registers f0-f31 - pub f: [u64; 32], - /// Floating-point control and status register - pub fcsr: usize, + /// Architecture-specific trap-frame payload consumed by trampoline assembly. + pub arch: ::Frame, /// Whether the current trap originated from a syscall (UserEnvCall). /// Used by signal delivery to implement syscall restart (SA_RESTART). pub in_syscall: bool, @@ -39,10 +20,127 @@ pub struct TrapContext { } impl TrapContext { + /// Return the raw general-purpose register value at `index`. + pub fn reg(&self, index: usize) -> usize { + ArchTrapContextAbi::reg(&self.arch, index) + } + + /// Update the raw general-purpose register value at `index`. + pub fn set_reg(&mut self, index: usize, value: usize) { + ArchTrapContextAbi::set_reg(&mut self.arch, index, value); + } + + /// Return the saved user-mode PC. + pub fn user_pc(&self) -> usize { + ArchTrapContextAbi::user_pc(&self.arch) + } + + /// Overwrite the saved user-mode PC. + pub fn set_user_pc(&mut self, pc: usize) { + ArchTrapContextAbi::set_user_pc(&mut self.arch, pc); + } + + /// Advance the saved user-mode PC by `delta` bytes. + pub fn advance_user_pc(&mut self, delta: usize) { + let next = self.user_pc().wrapping_add(delta); + self.set_user_pc(next); + } + + /// Return the saved user stack pointer. + pub fn user_sp(&self) -> usize { + ArchTrapContextAbi::user_sp(&self.arch) + } + /// put the sp(stack pointer) into x\[2\] field of TrapContext pub fn set_sp(&mut self, sp: usize) { - self.x[2] = sp; + ArchTrapContextAbi::set_user_sp(&mut self.arch, sp); + } + + /// Set the saved user stack pointer. + pub fn set_user_sp(&mut self, sp: usize) { + self.set_sp(sp); + } + + /// Return the saved return address register. + pub fn ra(&self) -> usize { + ArchTrapContextAbi::ra(&self.arch) + } + + /// Set the saved return address register. + pub fn set_ra(&mut self, ra: usize) { + ArchTrapContextAbi::set_ra(&mut self.arch, ra); + } + + /// Return the saved user TLS/thread-pointer register. + pub fn tls(&self) -> usize { + ArchTrapContextAbi::tls(&self.arch) + } + + /// Set the saved user TLS/thread-pointer register. + pub fn set_tls(&mut self, tls: usize) { + ArchTrapContextAbi::set_tls(&mut self.arch, tls); + } + + /// Return the architecture syscall number register. + pub fn syscall_nr(&self) -> usize { + ArchTrapContextAbi::syscall_nr(&self.arch) } + + /// Return the six syscall arguments from the saved user context. + pub fn syscall_args(&self) -> [usize; 6] { + ArchTrapContextAbi::syscall_args(&self.arch) + } + + /// Return the saved syscall return register. + pub fn syscall_ret(&self) -> usize { + ArchTrapContextAbi::syscall_ret(&self.arch) + } + + /// Set the saved syscall return register. + pub fn set_syscall_ret(&mut self, ret: usize) { + ArchTrapContextAbi::set_syscall_ret(&mut self.arch, ret); + } + + /// Set one user-call argument register. + pub fn set_user_arg(&mut self, index: usize, value: usize) { + ArchTrapContextAbi::set_user_arg(&mut self.arch, index, value); + } + + /// Save the original first syscall argument for possible restart. + pub fn save_syscall_arg0_for_restart(&mut self) { + self.orig_a0 = self.syscall_ret(); + } + + /// Set the kernel hart id restored by the trap trampoline. + pub fn set_kernel_hartid(&mut self, hartid: usize) { + ArchTrapContextAbi::set_kernel_hartid(&mut self.arch, hartid); + } + + /// Set the kernel stack pointer restored on the next trap entry. + pub fn set_kernel_sp(&mut self, kernel_sp: usize) { + ArchTrapContextAbi::set_kernel_sp(&mut self.arch, kernel_sp); + } + + /// Export the saved register file using the riscv64 Linux signal ABI layout. + pub fn export_signal_gprs(&self) -> [usize; 32] { + ArchTrapContextAbi::export_signal_gprs(&self.arch) + } + + /// Restore the saved register file using the riscv64 Linux signal ABI layout. + pub fn import_signal_gprs(&mut self, gregs: &[usize; 32]) { + ArchTrapContextAbi::import_signal_gprs(&mut self.arch, gregs); + } + + /// Copy floating-point state into an external signal frame. + pub fn copy_fp_state_to(&self, fpregs: &mut [u64; 32], fcsr: &mut u32) { + ArchTrapContextAbi::copy_fp_state_to(&self.arch, fpregs, fcsr); + } + + /// Restore floating-point state from an external signal frame. + pub fn restore_fp_state(&mut self, fpregs: &[u64; 32], fcsr: u32) { + ArchTrapContextAbi::restore_fp_state(&mut self.arch, fpregs, fcsr); + } + /// init the trap context of an application pub fn app_init_context( entry: usize, @@ -51,25 +149,19 @@ impl TrapContext { kernel_sp: usize, trap_handler: usize, ) -> Self { - let mut sstatus = sstatus::read(); - // set CPU privilege to User after trapping back - sstatus.set_spp(SPP::User); - unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial) }; + unsafe { crate::hal::enable_fp() }; let mut cx = Self { - x: [0; 32], - sstatus, - sepc: entry, // entry point of app - kernel_hartid: 0, - kernel_satp, // addr of page table - kernel_sp, // kernel stack - trap_handler, // addr of trap_handler function - f: [0; 32], - fcsr: 0, + arch: ArchTrapContextAbi::new_user_frame( + entry, + sp, + kernel_satp, + kernel_sp, + trap_handler, + ), in_syscall: false, orig_a0: 0, restartable_syscall: false, }; - cx.set_sp(sp); // app's user stack pointer cx // return initial Trap Context of app } } diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 12a2e9a8..0fbd50ea 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -3,10 +3,9 @@ //! For rCore, we have a single trap entry point, namely `__alltraps`. At //! initialization in [`init()`], we set the `stvec` CSR to point to it. //! -//! All traps go through `__alltraps`, which is defined in `trap.S`. The -//! assembly language code does just enough work restore the kernel space -//! context, ensuring that Rust code safely runs, and transfers control to -//! [`trap_handler()`]. +//! All traps go through an architecture-defined trampoline. The assembly code +//! does just enough work restore the kernel space context, ensuring that Rust +//! code safely runs, and transfers control to [`trap_handler()`]. //! //! It then calls different functionality based on what exactly the exception //! was. For example, timer interrupts trigger task preemption, and syscalls go @@ -14,8 +13,8 @@ mod context; -use crate::config::{PAGE_SIZE, TRAMPOLINE}; -use crate::hart::hartid; +use crate::config::PAGE_SIZE; +use crate::hal::hartid; use crate::mm::{handle_ipi, MmError, PageFaultAccess, PageFaultHandled}; use crate::signal::{SignalBit, handle_signals}; use crate::syscall::{syscall, syscall_supports_sa_restart}; @@ -26,14 +25,8 @@ use crate::task::{ current_trap_cx_user_va, current_user_token, exit_current_and_run_next, }; use crate::timer::{get_realtime_ns, get_time, handle_timer_interrupt}; -use core::arch::{asm, global_asm}; -use riscv::register::{ - mtvec::TrapMode, - scause::{self, Exception, Interrupt, Trap}, - sie, stval, stvec, -}; - -global_asm!(include_str!("trap.S")); +use crate::hal::{ArchInterrupt, ArchTrapMachine}; +use crate::hal::traits::{InterruptControl, TrapCause, TrapMachine}; /// 输出用户态致命异常现场,区分 fault 地址、用户 PC 与关键寄存器。 fn log_user_fault(reason: &str, access: &str, fault_addr: usize, signal: &str) { @@ -44,37 +37,37 @@ fn log_user_fault(reason: &str, access: &str, fault_addr: usize, signal: &str) { access, current_process().getpid(), fault_addr, - cx.sepc, - cx.x[1], - cx.x[2], - cx.x[3], - cx.x[4], - cx.x[10], - cx.x[11], - cx.x[17], + cx.user_pc(), + cx.ra(), + cx.user_sp(), + cx.reg(3), + cx.tls(), + cx.reg(10), + cx.reg(11), + cx.syscall_nr(), signal, ); error!( "[kernel] user fault regs: t0={:#x}, t1={:#x}, t2={:#x}, s0={:#x}, s1={:#x}, s2={:#x}, s3={:#x}, s4={:#x}, s5={:#x}, s6={:#x}, s7={:#x}, s8={:#x}, s9={:#x}, s10={:#x}, s11={:#x}, t3={:#x}, t4={:#x}, t5={:#x}, t6={:#x}", - cx.x[5], - cx.x[6], - cx.x[7], - cx.x[8], - cx.x[9], - cx.x[18], - cx.x[19], - cx.x[20], - cx.x[21], - cx.x[22], - cx.x[23], - cx.x[24], - cx.x[25], - cx.x[26], - cx.x[27], - cx.x[28], - cx.x[29], - cx.x[30], - cx.x[31], + cx.reg(5), + cx.reg(6), + cx.reg(7), + cx.reg(8), + cx.reg(9), + cx.reg(18), + cx.reg(19), + cx.reg(20), + cx.reg(21), + cx.reg(22), + cx.reg(23), + cx.reg(24), + cx.reg(25), + cx.reg(26), + cx.reg(27), + cx.reg(28), + cx.reg(29), + cx.reg(30), + cx.reg(31), ); } @@ -86,13 +79,13 @@ fn log_lazy_fault_oom(path: &str, access: &str, fault_addr: usize) { access, current_process().getpid(), fault_addr, - cx.sepc, - cx.x[1], - cx.x[2], - cx.x[4], - cx.x[10], - cx.x[11], - cx.x[17], + cx.user_pc(), + cx.ra(), + cx.user_sp(), + cx.tls(), + cx.reg(10), + cx.reg(11), + cx.syscall_nr(), ); } @@ -106,67 +99,45 @@ pub fn init() { /// 初始化当前 hart 的 trap 相关状态。 pub fn init_hart() { - set_kernel_trap_entry(); unsafe { - sie::set_sext(); - sie::set_ssoft(); + ArchInterrupt::set_kernel_trap_entry(); + ArchInterrupt::enable_external(); + ArchInterrupt::enable_software(); } info!("hart {} trap init done", hartid()); } /// set trap entry for traps happen in kernel(supervisor) mode pub fn set_kernel_trap_entry() { - extern "C" { - fn __trap_from_kernel(); - } - unsafe { - stvec::write(__trap_from_kernel as usize, TrapMode::Direct); - } + unsafe { ArchInterrupt::set_kernel_trap_entry(); } } /// set trap entry for traps happen in user mode pub fn set_user_trap_entry() { - unsafe { - stvec::write(TRAMPOLINE as usize, TrapMode::Direct); - } + unsafe { ArchInterrupt::set_user_trap_entry(); } } /// 为当前 hart 开启 supervisor timer interrupt。 pub fn enable_timer_interrupt() { - unsafe { - sie::set_stimer(); - } + unsafe { ArchInterrupt::enable_timer(); } } /// 为当前 hart 关闭 supervisor timer interrupt。 -/// -/// 这用于 secondary hart 进入“纯 idle 占位”状态的场景,避免它在尚未完成 -/// 全局共享状态并发化之前,进入会访问共享 `UPSafeCell` 的 timer 路径。 pub fn disable_timer_interrupt() { - unsafe { - sie::clear_stimer(); - } + unsafe { ArchInterrupt::disable_timer(); } } /// 为当前 hart 关闭 supervisor external interrupt。 -/// -/// 这用于 secondary hart 暂时只作为已上线但不参与设备中断处理的 idle hart。 pub fn disable_external_interrupt() { - unsafe { - sie::clear_sext(); - } + unsafe { ArchInterrupt::disable_external(); } } /// 为当前 hart 开启 supervisor software interrupt。 pub fn enable_software_interrupt() { - unsafe { - sie::set_ssoft(); - } + unsafe { ArchInterrupt::enable_software(); } } /// 清除当前 hart 挂起的 supervisor software interrupt。 pub fn clear_software_interrupt_pending() { - unsafe { - asm!("csrc sip, {}", in(reg) 1 << 1); - } + unsafe { ArchInterrupt::clear_software_pending(); } } /// Handle a scheduler reschedule IPI. @@ -176,6 +147,7 @@ pub fn clear_software_interrupt_pending() { /// so the idle loop can observe newly queued work on the next iteration. fn handle_reschedule_ipi() { handle_ipi(); + crate::platform::clear_ipi(); clear_software_interrupt_pending(); request_current_task_resched(ReschedReason::HigherRtPriority); } @@ -187,49 +159,47 @@ pub fn trap_handler() -> ! { current_process().enter_kernel(get_time()); current_trap_cx().in_syscall = false; current_trap_cx().restartable_syscall = false; - let scause = scause::read(); - let stval = stval::read(); - // trace!("into {:?}", scause.cause()); - match scause.cause() { - Trap::Exception(Exception::UserEnvCall) => { + let trap_info = ArchTrapMachine::read_trap_info(); + match trap_info.cause { + TrapCause::UserSyscall => { // jump to next instruction anyway let mut cx = current_trap_cx(); - let syscall_id = cx.x[17]; - let syscall_args = [cx.x[10], cx.x[11], cx.x[12], cx.x[13], cx.x[14], cx.x[15]]; - cx.orig_a0 = cx.x[10]; + let syscall_id = cx.syscall_nr(); + let syscall_args = cx.syscall_args(); + cx.save_syscall_arg0_for_restart(); cx.restartable_syscall = syscall_supports_sa_restart(syscall_id); - cx.sepc += 4; + cx.advance_user_pc(ArchTrapMachine::syscall_instruction_len()); // get system call return value let result = syscall(syscall_id, syscall_args); // cx is changed during sys_execve, so we have to call it again cx = current_trap_cx(); - cx.x[10] = result as usize; + cx.set_syscall_ret(result as usize); cx.in_syscall = true; } - Trap::Exception(Exception::StorePageFault) => { + TrapCause::StorePageFault => { debug!( "[mmap] trap store page fault: bad_addr={:#x} sepc={:#x}", - stval, - current_trap_cx().sepc + trap_info.fault_addr, + current_trap_cx().user_pc() ); let process = current_process(); let mut handled = false; - match process.handle_private_cow_fault(stval) { + match process.handle_private_cow_fault(trap_info.fault_addr) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("private_cow", "write", stval); + log_lazy_fault_oom("private_cow", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match process.handle_lazy_user_fault(stval, PageFaultAccess::Write) { + match process.handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Write) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "write", stval); + log_lazy_fault_oom("lazy_user", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } @@ -237,22 +207,22 @@ pub fn trap_handler() -> ! { } } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Write) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Write) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("store page fault", "write", stval, "SIGSEGV"); + log_user_fault("store page fault", "write", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("store page fault beyond file EOF", "write", stval, "SIGBUS"); + log_user_fault("store page fault beyond file EOF", "write", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "write", stval); + log_lazy_fault_oom("file_mmap", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("store page fault", "write", stval, "SIGSEGV"); + log_user_fault("store page fault", "write", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } @@ -262,104 +232,102 @@ pub fn trap_handler() -> ! { inner.vm_layout.start_brk }; let tls_page = start_brk & !(PAGE_SIZE - 1); - if (stval & !(PAGE_SIZE - 1)) == tls_page { + if (trap_info.fault_addr & !(PAGE_SIZE - 1)) == tls_page { debug!( "[entry-static errno] store fault mapped tls page: fault_addr={:#x} tls_page={:#x}", - stval, + trap_info.fault_addr, tls_page ); } } } - Trap::Exception(Exception::LoadPageFault) => { + TrapCause::LoadPageFault => { // debug!( // "[mmap] trap load page fault: bad_addr={:#x} sepc={:#x}", - // stval, - // current_trap_cx().sepc + // trap_info.fault_addr, + // current_trap_cx().user_pc() // ); let mut handled = false; - match current_process().handle_lazy_user_fault(stval, PageFaultAccess::Read) { + match current_process().handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Read) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "read", stval); + log_lazy_fault_oom("lazy_user", "read", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Read) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Read) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("load page fault", "read", stval, "SIGSEGV"); + log_user_fault("load page fault", "read", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("load page fault beyond file EOF", "read", stval, "SIGBUS"); + log_user_fault("load page fault beyond file EOF", "read", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "read", stval); + log_lazy_fault_oom("file_mmap", "read", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("load page fault", "read", stval, "SIGSEGV"); + log_user_fault("load page fault", "read", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } } } - Trap::Exception(Exception::InstructionPageFault) => { + TrapCause::InstructionPageFault => { debug!( "[mmap] trap instruction page fault: bad_addr={:#x} sepc={:#x}", - stval, - current_trap_cx().sepc + trap_info.fault_addr, + current_trap_cx().user_pc() ); let mut handled = false; - match current_process().handle_lazy_user_fault(stval, PageFaultAccess::Exec) { + match current_process().handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Exec) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "exec", stval); + log_lazy_fault_oom("lazy_user", "exec", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Exec) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Exec) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("instruction page fault", "exec", stval, "SIGSEGV"); + log_user_fault("instruction page fault", "exec", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("instruction page fault beyond file EOF", "exec", stval, "SIGBUS"); + log_user_fault("instruction page fault beyond file EOF", "exec", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "exec", stval); + log_lazy_fault_oom("file_mmap", "exec", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("instruction page fault", "exec", stval, "SIGSEGV"); + log_user_fault("instruction page fault", "exec", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } } } - Trap::Exception(Exception::StoreFault) - | Trap::Exception(Exception::InstructionFault) - | Trap::Exception(Exception::LoadFault) => { - log_user_fault("access fault", "unknown", stval, "SIGSEGV"); + TrapCause::StoreFault | TrapCause::InstructionFault | TrapCause::LoadFault => { + log_user_fault("access fault", "unknown", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } - Trap::Exception(Exception::IllegalInstruction) => { - log_user_fault("illegal instruction", "exec", stval, "SIGILL"); + TrapCause::IllegalInstruction => { + log_user_fault("illegal instruction", "exec", trap_info.fault_addr, "SIGILL"); current_add_signal(SignalBit::SIGILL); } - Trap::Interrupt(Interrupt::SupervisorTimer) => { + TrapCause::TimerInterrupt => { // trace!("hart {} timer tick", hartid()); if handle_timer_interrupt() { let now_raw = get_time(); @@ -368,18 +336,18 @@ pub fn trap_handler() -> ! { on_timer_tick(); } } - Trap::Interrupt(Interrupt::SupervisorSoft) => { + TrapCause::SoftwareInterrupt => { handle_reschedule_ipi(); } - Trap::Interrupt(Interrupt::SupervisorExternal) => { - crate::drivers::plic::handle_supervisor_external(); + TrapCause::ExternalInterrupt => { + crate::platform::handle_external_irq(); crate::net::poll(); } _ => { panic!( - "Unsupported trap {:?}, stval = {:#x}!", - scause.cause(), - stval + "Unsupported trap {:?}, fault_addr = {:#x}!", + trap_info.cause, + trap_info.fault_addr ); } } @@ -407,47 +375,24 @@ pub fn trap_handler() -> ! { /// return to user space #[no_mangle] pub fn trap_return() -> ! { - //disable_supervisor_interrupt(); set_user_trap_entry(); let trap_cx_user_va = current_trap_cx_user_va(); - current_trap_cx().kernel_hartid = hartid(); - let user_satp = current_user_token(); - extern "C" { - fn __alltraps(); - fn __restore(); - } - let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; - // trace!("[kernel] trap_return: ..before return"); + current_trap_cx().set_kernel_hartid(hartid()); + let user_token = current_user_token(); current_process().enter_user(get_time()); - unsafe { - asm!( - "fence.i", - "jr {restore_va}", // jump to new addr of __restore asm function - restore_va = in(reg) restore_va, - in("a0") trap_cx_user_va, // a0 = virt addr of Trap Context - in("a1") user_satp, // a1 = user satp token - options(noreturn) - ); - } + unsafe { ArchTrapMachine::return_to_user(trap_cx_user_va, user_token) } } /// handle trap from kernel #[no_mangle] pub fn trap_from_kernel() { - // debug!("Trap from kernel: scause = {:?}, stval = {:#x}", scause::read(), stval::read()); - let scause = scause::read(); - let stval = stval::read(); - let cause: Trap = scause - .cause() - .try_into() - .unwrap_or_else(|_| panic!("Invalid trap {:?}, stval = {:#x}!", scause.cause(), stval)); - match cause.try_into() { - Ok(Trap::Interrupt(Interrupt::SupervisorExternal)) => { - // debug!("External interrupt from kernel: scause = {:?}, stval = {:#x}", scause, stval); - crate::drivers::plic::handle_supervisor_external(); + let trap_info = ArchTrapMachine::read_trap_info(); + match trap_info.cause { + TrapCause::ExternalInterrupt => { + crate::platform::handle_external_irq(); crate::net::poll(); // 处理完外部中断后立即poll,让smoltcp响应ARP等请求 } - Ok(Trap::Interrupt(Interrupt::SupervisorTimer)) => { + TrapCause::TimerInterrupt => { // trace!("hart {} timer tick", hartid()); if handle_timer_interrupt() { let now_raw = get_time(); @@ -459,13 +404,16 @@ pub fn trap_from_kernel() { // user-mode ticks. on_timer_tick(); } - // crate::net::poll(); } - Ok(Trap::Interrupt(Interrupt::SupervisorSoft)) => { + TrapCause::SoftwareInterrupt => { handle_reschedule_ipi(); } _ => { - panic!("Kernel trap: {:?}, stval = {:#x}", scause.cause(), stval); + panic!( + "Kernel trap: {:?}, fault_addr = {:#x}", + trap_info.cause, + trap_info.fault_addr + ); } } // check_timer(); diff --git a/user/Cargo.toml b/user/Cargo.toml index 1179ec04..bdd50193 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -17,9 +17,3 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } opt-level = "z" # Optimize for size. strip = true # Automatically strip symbols from the binary. lto = true - -[target.riscv64gc-unknown-none-elf] -rustflags = [ - "-C", "link-arg=-Tsrc/linker.ld", - "-C", "target-feature=+f,+d", -] \ No newline at end of file diff --git a/user/Makefile b/user/Makefile index 453d3a8c..0066a510 100644 --- a/user/Makefile +++ b/user/Makefile @@ -1,10 +1,17 @@ -TARGET := riscv64gc-unknown-none-elf +ARCH ?= riscv64 +ifeq ($(ARCH),loongarch64) + TARGET := loongarch64-unknown-none + OBJDUMP := rust-objdump --arch-name=loongarch64 + OBJCOPY := rust-objcopy --binary-architecture=loongarch64 +else + TARGET := riscv64gc-unknown-none-elf + OBJDUMP := rust-objdump --arch-name=riscv64 + OBJCOPY := rust-objcopy --binary-architecture=riscv64 +endif MODE := release APP_DIR := src/bin TARGET_DIR := target/$(TARGET)/$(MODE) BUILD_DIR := build -OBJDUMP := rust-objdump --arch-name=riscv64 -OBJCOPY := rust-objcopy --binary-architecture=riscv64 ifeq ($(MODE), release) MODE_ARG := --release @@ -16,7 +23,7 @@ ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS)) binary: @echo $(ELFS) - @cargo build $(MODE_ARG) + @cargo build $(MODE_ARG) --target $(TARGET) @$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf)); cp $(elf) $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.elf, $(elf));) disasm: @@ -30,14 +37,18 @@ pre: @mkdir -p $(BUILD_DIR)/asm/ @$(foreach t, $(APPS), cp $(t) $(BUILD_DIR)/app/;) -build: clean pre binary - @$(foreach t, $(ELFS), cp $(t).bin $(BUILD_DIR)/bin/;) - @$(foreach t, $(ELFS), cp $(t).elf $(BUILD_DIR)/elf/;) - clean: @cargo clean @rm -rf $(BUILD_DIR) +prepare: + @rm -rf $(BUILD_DIR) + @mkdir -p $(BUILD_DIR) + +build: prepare pre binary + @$(foreach t, $(ELFS), cp $(t).bin $(BUILD_DIR)/bin/;) + @$(foreach t, $(ELFS), cp $(t).elf $(BUILD_DIR)/elf/;) + all: build -.PHONY: elf binary build clean all +.PHONY: elf binary prepare build clean all diff --git a/user/build.py b/user/build.py index b4061821..aa8a9326 100644 --- a/user/build.py +++ b/user/build.py @@ -3,6 +3,7 @@ base_address = 0x80400000 step = 0x20000 linker = "src/linker.ld" +target = os.getenv("TARGET", "riscv64gc-unknown-none-elf") app_id = 0 apps = os.listdir("build/app") @@ -17,8 +18,8 @@ for app in apps: app = app[: app.find(".")] os.system( - "cargo rustc --bin %s %s -- -Clink-args=-Ttext=%x" - % (app, mode_arg, base_address + step * app_id) + "cargo rustc --target %s --bin %s %s -- -Clink-args=-Ttext=%x" + % (target, app, mode_arg, base_address + step * app_id) ) print( "[build.py] application %s start with address %s" diff --git a/user/cargo-config/config.toml b/user/cargo-config/config.toml index e5ded8a1..d20e8541 100644 --- a/user/cargo-config/config.toml +++ b/user/cargo-config/config.toml @@ -4,4 +4,10 @@ target = "riscv64gc-unknown-none-elf" [target.riscv64gc-unknown-none-elf] rustflags = [ "-Clink-args=-Tsrc/linker.ld", + "-Ctarget-feature=+f,+d", +] + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-args=-Tsrc/linker-loongarch64.ld", ] diff --git a/user/src/bin/remote_shell.rs b/user/src/bin/remote_shell.rs index 86b4d39c..381bf546 100644 --- a/user/src/bin/remote_shell.rs +++ b/user/src/bin/remote_shell.rs @@ -14,6 +14,7 @@ use user_lib::{ // `user_lib::wait()` blocks in a yield-loop, so we issue the raw syscall once. const SYSCALL_WAITPID: usize = 260; +#[cfg(target_arch = "riscv64")] fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; unsafe { @@ -29,6 +30,21 @@ fn syscall(id: usize, args: [usize; 3]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +fn syscall(id: usize, args: [usize; 3]) -> isize { + let mut ret: isize; + unsafe { + asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a7") id, + ); + } + ret +} + fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize { syscall(SYSCALL_WAITPID, [pid as usize, exit_code as usize, 0]) } diff --git a/user/src/bin/setupsh.rs b/user/src/bin/setupsh.rs index 61d4f274..f88459ed 100644 --- a/user/src/bin/setupsh.rs +++ b/user/src/bin/setupsh.rs @@ -9,7 +9,8 @@ use alloc::string::String; use core::ptr; use user_lib::{ - chdir, close, exec, execve, exit, fork, fstatat, getdents64, link, mkdir, open, unlink, waitpid, write, + chdir, close, exec_ptr, exit, fork, fstatat, getdents64, link, mkdir, open, unlink, waitpid, + write, OpenFlags, Stat, }; @@ -25,6 +26,7 @@ const BIN_DATE: &str = "/bin/date"; const BOOT_DIR: &str = "/boot"; const BOOT_CONFIG_PATH: &str = "/boot/config-6.6.0"; const LIB_DIR: &str = "/lib"; +const LIB64_DIR: &str = "/lib64"; const ETC_DIR: &str = "/etc"; const ETC_PASSWD_PATH: &str = "/etc/passwd"; const ETC_GROUP_PATH: &str = "/etc/group"; @@ -57,21 +59,53 @@ const MODULES_BUILTIN_PATH: &str = "/lib/modules/6.6.0/modules.builtin"; const MODULES_DEP_PATH: &str = "/lib/modules/6.6.0/modules.dep"; const MUSL_BUSYBOX_PATH: &str = "/musl/busybox"; const MUSL_LEGACY_LIB_DIR: &str = "/musl/lib"; +#[cfg(target_arch = "riscv64")] const MUSL_LIB_DIR: &str = "/usr/lib/riscv64-linux-musl"; +#[cfg(target_arch = "riscv64")] const MUSL_LIBC_PATH: &str = "/usr/lib/riscv64-linux-musl/libc.so"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_PATH: &str = "/lib/ld-musl-riscv64-sf.so.1"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_COMPAT_PATH: &str = "/lib/ld-musl-riscv64.so.1"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_CONFIG_PATH: &str = "/etc/ld-musl-riscv64-sf.path"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_CONFIG_CONTENT: &[u8] = b"/usr/lib/riscv64-linux-musl\n/lib\n"; + +#[cfg(target_arch = "loongarch64")] +const MUSL_LIB_DIR: &str = "/usr/lib/loongarch64-linux-musl"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LIBC_PATH: &str = "/usr/lib/loongarch64-linux-musl/libc.so"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_PATH: &str = "/lib64/ld-musl-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_CONFIG_PATH: &str = "/etc/ld-musl-loongarch-lp64d.path"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_CONFIG_CONTENT: &[u8] = b"/usr/lib/loongarch64-linux-musl\n/lib\n/lib64\n"; const GLIBC_BUSYBOX_PATH: &str = "/glibc/busybox"; const GLIBC_BUSYBOX_TARGET: &str = "/usr/bin/glibc-busybox"; const GLIBC_BUSYBOX_TARGET_CSTR: &str = "/usr/bin/glibc-busybox\0"; const GLIBC_LEGACY_LIB_DIR: &str = "/glibc/lib"; +#[cfg(target_arch = "riscv64")] const GLIBC_LIB_DIR: &str = "/lib/riscv64-linux-gnu"; +#[cfg(target_arch = "riscv64")] const GLIBC_USR_LIB_DIR: &str = "/usr/lib/riscv64-linux-gnu"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_NAME: &str = "ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_TARGET: &str = "/lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_PATH: &str = "/lib/ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LIB_DIR: &str = "/lib/loongarch64-linux-gnu"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_USR_LIB_DIR: &str = "/usr/lib/loongarch64-linux-gnu"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_NAME: &str = "ld-linux-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_TARGET: &str = "/lib/loongarch64-linux-gnu/ld-linux-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_PATH: &str = "/lib64/ld-linux-loongarch-lp64d.so.1"; const ROOT_GROUPDEL: &str = "/root/groupdel"; const ROOT_USERADD: &str = "/root/useradd"; const ROOT_USERDEL: &str = "/root/userdel"; @@ -115,15 +149,15 @@ fn path_exists(path: &str) -> bool { } /// 运行一个外部程序并等待其退出。 -fn spawn_and_wait(path: &str, argv: &[*const u8]) -> i32 { +fn spawn_and_wait(child_exec: fn() -> isize, path_display: &str) -> i32 { let pid = fork(); if pid < 0 { - println!("[setupsh] fork failed for {}", path); + println!("[setupsh] fork failed for {}", path_display); return -1; } if pid == 0 { - let ret = exec(path, argv); - println!("[setupsh] exec {} failed: {}", path, ret); + let ret = child_exec(); + println!("[setupsh] exec {} failed: {}", path_display, ret); exit(127); } @@ -139,29 +173,29 @@ fn spawn_and_wait(path: &str, argv: &[*const u8]) -> i32 { exit_code } -/// 运行一个外部程序并显式传入环境变量,然后等待其退出。 -fn spawn_and_wait_with_env(path: &str, argv: &[*const u8], envp: &[*const u8]) -> i32 { - let pid = fork(); - if pid < 0 { - println!("[setupsh] fork failed for {}", path); - return -1; - } - if pid == 0 { - let ret = execve(path, argv, envp); - println!("[setupsh] execve {} failed: {}", path, ret); - exit(127); - } +fn exec_bin_busybox_install() -> isize { + let argv = [ + BUSYBOX_ARG0_CSTR.as_ptr(), + INSTALL_ARG_CSTR.as_ptr(), + BIN_DIR_CSTR.as_ptr(), + ptr::null(), + ]; + exec_ptr(BIN_BUSYBOX_CSTR.as_ptr(), &argv) +} - let mut exit_code = 0i32; - let waited = waitpid(pid as usize, &mut exit_code); - if waited != pid { - println!( - "[setupsh] waitpid mismatch: expected {}, got {}", - pid, waited - ); - return -1; - } - exit_code +fn exec_glibc_busybox_install() -> isize { + let argv = [ + BUSYBOX_ARG0_CSTR.as_ptr(), + INSTALL_ARG_CSTR.as_ptr(), + USR_BIN_DIR_CSTR.as_ptr(), + ptr::null(), + ]; + exec_ptr(GLIBC_BUSYBOX_TARGET_CSTR.as_ptr(), &argv) +} + +fn exec_bin_sh() -> isize { + let argv = [BIN_SH_CSTR.as_ptr(), ptr::null()]; + exec_ptr(BIN_SH_CSTR.as_ptr(), &argv) } /// 打印阶段进度,便于观察 `setupsh` 当前执行到哪一步。 @@ -248,6 +282,16 @@ fn install_lib_link(src: &str, dst: &str) -> bool { ensure_hard_link(src, dst) } +#[cfg(target_arch = "riscv64")] +fn keep_musl_compat_top_level_entry(name: &str) -> bool { + name == MUSL_LD_COMPAT_PATH.trim_start_matches('/') +} + +#[cfg(not(target_arch = "riscv64"))] +fn keep_musl_compat_top_level_entry(_name: &str) -> bool { + false +} + /// 目标目录中已有普通文件时,认为镜像已迁移过。 fn dir_has_runtime_files(target_dir: &str) -> bool { let fd = open(target_dir, OpenFlags::RDONLY | OpenFlags::DIRECTORY); @@ -350,7 +394,7 @@ fn keep_top_level_lib_entry(name: &str, dtype: u8) -> bool { || name == ".." || name == "ar" || name == MUSL_LD_PATH.trim_start_matches('/') - || name == MUSL_LD_COMPAT_PATH.trim_start_matches('/') + || keep_musl_compat_top_level_entry(name) || name == GLIBC_LD_PATH.trim_start_matches('/') } @@ -412,6 +456,7 @@ fn ensure_dirs() -> bool { LIB_DIR, MODULES_ROOT_DIR, MODULES_DIR, + LIB64_DIR, ETC_DIR, HOME_DIR, ROOT_HOME_DIR, @@ -433,6 +478,26 @@ fn ensure_dirs() -> bool { true } +fn install_loader_links() -> bool { + if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_PATH) { + return false; + } + if !ensure_hard_link(GLIBC_LD_TARGET, GLIBC_LD_PATH) { + println!( + "[setupsh] glibc loader must exist as {} in {}", + GLIBC_LD_NAME, GLIBC_LIB_DIR + ); + return false; + } + #[cfg(target_arch = "riscv64")] + { + if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_COMPAT_PATH) { + return false; + } + } + true +} + fn write_file(path: &str, content: &[u8]) -> bool { let fd = open( path, @@ -480,23 +545,6 @@ fn install_ltp_env_scripts() -> bool { true } -fn install_loader_links() -> bool { - if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_PATH) { - return false; - } - if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_COMPAT_PATH) { - return false; - } - if !ensure_hard_link(GLIBC_LD_TARGET, GLIBC_LD_PATH) { - println!( - "[setupsh] glibc loader must exist as {} in {}", - GLIBC_LD_NAME, GLIBC_LIB_DIR - ); - return false; - } - true -} - fn install_busybox_entries() -> bool { if !ensure_hard_link(MUSL_BUSYBOX_PATH, BIN_BUSYBOX) { return false; @@ -526,13 +574,7 @@ fn install_kernel_module_metadata() -> bool { fn install_busybox_applets() -> bool { if !path_exists("/bin/sh") { - let musl_install_argv = [ - BUSYBOX_ARG0_CSTR.as_ptr(), - INSTALL_ARG_CSTR.as_ptr(), - BIN_DIR_CSTR.as_ptr(), - ptr::null(), - ]; - let install_exit = spawn_and_wait(BIN_BUSYBOX_CSTR, &musl_install_argv); + let install_exit = spawn_and_wait(exec_bin_busybox_install, BIN_BUSYBOX); if install_exit != 0 { println!("[setupsh] musl busybox --install failed: {}", install_exit); return false; @@ -540,13 +582,7 @@ fn install_busybox_applets() -> bool { } if !path_exists("/usr/bin/sh") { - let glibc_install_argv = [ - BUSYBOX_ARG0_CSTR.as_ptr(), - INSTALL_ARG_CSTR.as_ptr(), - USR_BIN_DIR_CSTR.as_ptr(), - ptr::null(), - ]; - let install_exit = spawn_and_wait(GLIBC_BUSYBOX_TARGET_CSTR, &glibc_install_argv); + let install_exit = spawn_and_wait(exec_glibc_busybox_install, GLIBC_BUSYBOX_TARGET); if install_exit != 0 { println!("[setupsh] glibc busybox --install failed: {}", install_exit); return false; @@ -680,9 +716,7 @@ fn main(_argc: usize, argv: &[&str]) -> i32 { ROOT_HOME_DIR, chdir_ret ); } - let shell_argv = [BIN_SH_CSTR.as_ptr(), ptr::null()]; - let shell_envp = [SHELL_PATH_ENV_CSTR.as_ptr(), ptr::null()]; - let shell_exit = spawn_and_wait_with_env(BIN_SH_CSTR, &shell_argv, &shell_envp); + let shell_exit = spawn_and_wait(exec_bin_sh, "/bin/sh"); println!("[setupsh] /bin/sh exited with {}", shell_exit); shell_exit } diff --git a/user/src/lib.rs b/user/src/lib.rs index 92ce1170..b91b06c6 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -47,6 +47,7 @@ fn clear_bss() { } } +#[cfg(target_arch = "riscv64")] global_asm!( r#" .section .text.entry @@ -57,6 +58,17 @@ _start: "# ); +#[cfg(target_arch = "loongarch64")] +global_asm!( + r#" + .section .text.entry + .globl _start +_start: + move $a0, $sp + bl __user_start +"# +); + /// 用户程序入口:从 Linux ABI 初始栈解析 argc/argv 后调用 main。 #[no_mangle] pub extern "C" fn __user_start(user_sp: usize) -> ! { @@ -580,6 +592,17 @@ pub fn execve(path: &str, args: &[*const u8], envp: &[*const u8]) -> isize { sys_execve(path, args, envp) } +/// 显式使用 NUL 结尾 C 字符串路径的 `execve` 变体。 +pub fn exec_ptr(path: *const u8, args: &[*const u8]) -> isize { + let envp: [*const u8; 1] = [core::ptr::null()]; + sys_execve_ptr(path, args, &envp) +} + +/// 显式使用 NUL 结尾 C 字符串路径的 `execve` 变体。 +pub fn execve_ptr(path: *const u8, args: &[*const u8], envp: &[*const u8]) -> isize { + sys_execve_ptr(path, args, envp) +} + pub fn set_priority(prio: isize) -> isize { sys_set_priority(prio) } diff --git a/user/src/linker-loongarch64.ld b/user/src/linker-loongarch64.ld new file mode 100644 index 00000000..d1e5c619 --- /dev/null +++ b/user/src/linker-loongarch64.ld @@ -0,0 +1,33 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) + +BASE_ADDRESS = 0x0; + +SECTIONS +{ + . = BASE_ADDRESS; + .text : { + *(.text.entry) + *(.text .text.*) + } + . = ALIGN(4K); + .rodata : { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + . = ALIGN(4K); + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + } + .bss : { + start_bss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + end_bss = .; + } + /DISCARD/ : { + *(.eh_frame) + *(.debug*) + } +} diff --git a/user/src/syscall.rs b/user/src/syscall.rs index ff641e20..92883927 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -88,6 +88,7 @@ pub const SYSCALL_CONDVAR_CREATE: usize = 471; pub const SYSCALL_CONDVAR_SIGNAL: usize = 472; pub const SYSCALL_CONDVAR_WAIT: usize = 473; +#[cfg(target_arch = "riscv64")] pub fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; unsafe { @@ -102,6 +103,34 @@ pub fn syscall(id: usize, args: [usize; 3]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +pub fn syscall(id: usize, args: [usize; 3]) -> isize { + let mut ret: isize; + unsafe { + core::arch::asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a7") id, + // A kernel syscall may freely clobber LA64 caller-saved temporaries. + // Mark them explicitly so LLVM won't keep live values such as execve + // path pointers in t0 across a syscall boundary. + lateout("$t0") _, + lateout("$t1") _, + lateout("$t2") _, + lateout("$t3") _, + lateout("$t4") _, + lateout("$t5") _, + lateout("$t6") _, + lateout("$t7") _, + lateout("$t8") _, + ); + } + ret +} + +#[cfg(target_arch = "riscv64")] pub fn syscall6(id: usize, args: [usize; 6]) -> isize { let mut ret: isize; unsafe { @@ -118,6 +147,33 @@ pub fn syscall6(id: usize, args: [usize; 6]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +pub fn syscall6(id: usize, args: [usize; 6]) -> isize { + let mut ret: isize; + unsafe { + core::arch::asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a3") args[3], + in("$a4") args[4], + in("$a5") args[5], + in("$a7") id, + lateout("$t0") _, + lateout("$t1") _, + lateout("$t2") _, + lateout("$t3") _, + lateout("$t4") _, + lateout("$t5") _, + lateout("$t6") _, + lateout("$t7") _, + lateout("$t8") _, + ); + } + ret +} + pub fn sys_openat(dirfd: usize, path: &str, flags: u32, mode: u32) -> isize { syscall6( SYSCALL_OPENAT, @@ -446,6 +502,13 @@ pub fn sys_execve(path: &str, args: &[*const u8], envp: &[*const u8]) -> isize { ) } +pub fn sys_execve_ptr(path: *const u8, args: &[*const u8], envp: &[*const u8]) -> isize { + syscall( + SYSCALL_EXECVE, + [path as usize, args.as_ptr() as usize, envp.as_ptr() as usize], + ) +} + pub fn sys_waitpid(pid: isize, xstatus: *mut i32) -> isize { syscall(SYSCALL_WAITPID, [pid as usize, xstatus as usize, 0]) }