From 3bf2325aae16352d4bce87ca534eeb7f9188aa8f Mon Sep 17 00:00:00 2001 From: JensenWei007 Date: Thu, 24 Jul 2025 19:17:01 +0800 Subject: [PATCH] feat(main): Adapt command line and autoboot, optimize Makefile - Add command line module to implement module framework for subsequent expansion. - Change the boot process, enable autoboot by default, automatically search for bootable devices and start them, and you can also interrupt and enter the command line to operate on your own - Optimize the makefile content. Signed-off-by: JensenWei007 --- .github/workflows/test.yml | 3 +- .gitignore | 3 + Makefile | 15 +-- scripts/make/build.mk | 1 - src/main.rs | 8 +- src/shell/cmd.rs | 39 ++++++++ src/shell/mod.rs | 90 ++++++++++++++++++ src/shell/stdio.rs | 184 +++++++++++++++++++++++++++++++++++++ 8 files changed, 325 insertions(+), 18 deletions(-) create mode 100644 src/shell/cmd.rs create mode 100644 src/shell/mod.rs create mode 100644 src/shell/stdio.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 711f297..c1ee873 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,8 @@ jobs: sudo apt install -y qemu-system-misc - name: Run QEMU - run: make virtiodisk > qemu.log + run: | + make qemu-run > qemu.log - name: Upload QEMU log uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 5cd0307..75603d9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ myramdisk/ *.bin *.img *.cpio + +edk2/ +mnt_fat32/ diff --git a/Makefile b/Makefile index ab035f6..76b2349 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ # - `PLATFORM`: Target platform in the `platforms` directory # - `SMP`: Number of CPUs # - `LOG:` Logging level: warn, error, info, debug, trace -# - `MEDIUM:` Boot Medium Type: ramdisk-cpio, virtio-blk # - `EXTRA_CONFIG`: Extra config specification file # - `OUT_CONFIG`: Final config file that takes effect # * QEMU options: @@ -17,15 +16,12 @@ PLATFORM ?= SMP ?= 1 LOG ?= debug -# 下面的目前还没用, 现在需要手动去cargo.toml中修改, 后面补上 -MEDIUM ?= ramdisk-cpio - OUT_CONFIG ?= $(PWD)/.axconfig.toml EXTRA_CONFIG ?= # QEMU options DISK:= disk.img -SBI:=rustsbi/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper-payload.elf +SBI ?= rustsbi/target/riscv64gc-unknown-none-elf/release/rustsbi-prototyper-payload.elf RAMDISK_CPIO:=ramdisk.cpio export AX_CONFIG_PATH=$(OUT_CONFIG) @@ -44,13 +40,10 @@ oldconfig: _axconfig-gen clean: cargo clean - cd rustsbi && cargo clean + test -d "rustsbi" && cd rustsbi && cargo clean rm -f $(OUT_CONFIG) build: clean defconfig all -ramdiskcpio: - qemu-system-riscv64 -m 128M -serial mon:stdio -bios $(SBI) -nographic -machine virt -device loader,file=$(RAMDISK_CPIO),addr=0x84000000 - -virtiodisk: - qemu-system-riscv64 -m 128M -serial mon:stdio -bios $(SBI) -nographic -machine virt -device virtio-blk-pci,drive=disk0 -drive id=disk0,if=none,format=raw,file=$(DISK) +qemu-run: + qemu-system-riscv64 -m 128M -serial mon:stdio -bios $(SBI) -nographic -machine virt -nographic -machine virt -device virtio-blk-pci,drive=disk0 -drive id=disk0,if=none,format=raw,file=$(DISK) diff --git a/scripts/make/build.mk b/scripts/make/build.mk index 714c3fc..a4df56a 100644 --- a/scripts/make/build.mk +++ b/scripts/make/build.mk @@ -7,7 +7,6 @@ ROOT_DIR := $(abspath $(SCRIPT_DIR)/../..) ELF := $(ROOT_DIR)/target/$(TARGET)/release/arceboot BIN := $(ROOT_DIR)/target/$(TARGET)/release/arceboot.bin RUSTSBI_DIR := $(ROOT_DIR)/rustsbi -SBI := $(RUSTSBI_DIR)/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper-payload.elf # 彩色打印 define print_info diff --git a/src/main.rs b/src/main.rs index 36d1c8d..25dfe6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod log; mod medium; mod panic; mod runtime; +mod shell; #[cfg_attr(not(test), unsafe(no_mangle))] pub extern "C" fn rust_main(_cpu_id: usize, _dtb: usize) -> ! { @@ -36,7 +37,7 @@ pub extern "C" fn rust_main(_cpu_id: usize, _dtb: usize) -> ! { info!("Initialize platform devices..."); axhal::platform_init(); - #[cfg(any(feature = "fs", feature = "net", feature = "display"))] + #[cfg(any(feature = "fs", feature = "net"))] { #[allow(unused_variables)] let all_devices = axdriver::init_drivers(); @@ -48,16 +49,13 @@ pub extern "C" fn rust_main(_cpu_id: usize, _dtb: usize) -> ! { #[cfg(feature = "net")] axnet::init_network(all_devices.net); - - #[cfg(feature = "display")] - axdisplay::init_display(all_devices.display); } ctor_bare::call_ctors(); info!("current root dir: {}", crate::medium::current_dir().unwrap()); info!("read test file context: {}", crate::medium::read_to_string("/test/arceboot.txt").unwrap()); - crate::runtime::efi_runtime_init(); + crate::shell::shell_main(); info!("will shut down."); diff --git a/src/shell/cmd.rs b/src/shell/cmd.rs new file mode 100644 index 0000000..14da24a --- /dev/null +++ b/src/shell/cmd.rs @@ -0,0 +1,39 @@ +type CmdHandler = fn(&str); + +const CMD_TABLE: &[(&str, &str, CmdHandler)] = &[ + ("help", "-- List the help for ArceBoot", do_help), + ("exit", "-- Exit ArceBoot", do_exit), +]; + +pub fn run_cmd(line: &[u8]) { + let line_str = unsafe { core::str::from_utf8_unchecked(line) }; + let (cmd, args) = split_whitespace(line_str); + if !cmd.is_empty() { + for (name, _, func) in CMD_TABLE { + if cmd == *name { + func(args); + return; + } + } + axlog::ax_println!("{}: command not found", cmd); + } +} + +fn split_whitespace(str: &str) -> (&str, &str) { + let str = str.trim(); + str.find(char::is_whitespace) + .map_or((str, ""), |n| (&str[..n], str[n + 1..].trim())) +} + +fn do_help(_args: &str) { + axlog::ax_println!("Available commands:"); + for (name, desc, _) in CMD_TABLE { + axlog::ax_print!(" {}", name); + axlog::ax_println!(" {}", desc); + } +} + +fn do_exit(_args: &str) { + axlog::ax_println!("======== ArceBoot will exit and shut down ========"); + axhal::misc::terminate(); +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs new file mode 100644 index 0000000..8a907b4 --- /dev/null +++ b/src/shell/mod.rs @@ -0,0 +1,90 @@ +use axio::{Read, Write}; + +mod cmd; +mod stdio; + +const LF: u8 = b'\n'; +const CR: u8 = b'\r'; +const DL: u8 = b'\x7f'; +const BS: u8 = b'\x08'; +const SPACE: u8 = b' '; + +const MAX_CMD_LEN: usize = 256; + +fn print_prompt() { + axlog::ax_print!("[Arceboot]: {}$ ", &crate::medium::current_dir().unwrap()); +} + +pub fn shell_main() { + let mut stdin = self::stdio::stdin(); + let mut stdout = self::stdio::stdout(); + + let mut buf = [0; MAX_CMD_LEN]; + let mut cursor = 0; + + // Attempt to autoboot + const COUNTDOWN_MSG: &[u8] = + b"[Arceboot] Attempt to autoboot, print any key to stop it, will start autoboot in: "; + for i in (0..=5).rev() { + if stdin.read_nb(&mut buf[cursor..cursor + 1]).ok() == Some(1) { + break; + } + stdout.write_all(COUNTDOWN_MSG).unwrap(); + + let num_char = b'0' + i; + stdout.write_all(&[num_char, b' ']).unwrap(); + + axhal::time::busy_wait(core::time::Duration::new(1, 0)); + + if i > 0 { + stdout.write_all(&[CR]).unwrap(); + } + + if i == 0 { + autoboot(); + // boot 完成后理论上不应该回到程序 + return; + } + } + axlog::ax_println!(); + + // Cannot autoboot or cancel autoboot + self::cmd::run_cmd("help".as_bytes()); + print_prompt(); + + loop { + if stdin.read(&mut buf[cursor..cursor + 1]).ok() != Some(1) { + continue; + } + if buf[cursor] == b'\x1b' { + buf[cursor] = b'^'; + } + match buf[cursor] { + CR | LF => { + axlog::ax_println!(); + if cursor > 0 { + cmd::run_cmd(&buf[..cursor]); + cursor = 0; + } + print_prompt(); + } + BS | DL => { + if cursor > 0 { + stdout.write_all(&[BS, SPACE, BS]).unwrap(); + cursor -= 1; + } + } + 0..=31 => {} + c => { + if cursor < MAX_CMD_LEN - 1 { + stdout.write_all(&[c]).unwrap(); + cursor += 1; + } + } + } + } +} + +fn autoboot() { + crate::runtime::efi_runtime_init(); +} diff --git a/src/shell/stdio.rs b/src/shell/stdio.rs new file mode 100644 index 0000000..f1a0568 --- /dev/null +++ b/src/shell/stdio.rs @@ -0,0 +1,184 @@ +extern crate alloc; + +use alloc::{string::String, vec::Vec}; +pub use axio::{BufRead, BufReader, Read, Write}; +use axsync::{Mutex, MutexGuard}; + +pub type CmdResult = axio::Result; + +struct StdinRaw; + +fn ax_console_read_bytes(buf: &mut [u8]) -> CmdResult { + let len = axhal::console::read_bytes(buf); + for c in &mut buf[..len] { + if *c == b'\r' { + *c = b'\n'; + } + } + Ok(len) +} + +fn ax_console_write_bytes(buf: &[u8]) -> CmdResult { + axhal::console::write_bytes(buf); + Ok(buf.len()) +} + +impl Read for StdinRaw { + // Non-blocking read, returns number of bytes read. + fn read(&mut self, buf: &mut [u8]) -> CmdResult { + let mut read_len = 0; + while read_len < buf.len() { + let len = ax_console_read_bytes(buf[read_len..].as_mut())?; + if len == 0 { + break; + } + read_len += len; + } + Ok(read_len) + } +} + +/// A handle to the standard input stream of a process. +pub struct Stdin { + inner: &'static Mutex>, +} + +/// A locked reference to the [`Stdin`] handle. +pub struct StdinLock<'a> { + inner: MutexGuard<'a, BufReader>, +} + +impl Stdin { + /// Locks this handle to the standard input stream, returning a readable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the [`Read`] and [`BufRead`] traits for + /// accessing the underlying data. + pub fn lock(&self) -> StdinLock<'static> { + // Locks this handle with 'static lifetime. This depends on the + // implementation detail that the underlying `Mutex` is static. + StdinLock { + inner: self.inner.lock(), + } + } + + /// Locks this handle and reads a line of input, appending it to the specified buffer. + pub fn read_line(&self, buf: &mut String) -> CmdResult { + self.inner.lock().read_line(buf) + } + + pub fn read_nb(&mut self, buf: &mut [u8]) -> CmdResult { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + return Ok(0) + } +} + +impl Read for Stdin { + // Block until at least one byte is read. + fn read(&mut self, buf: &mut [u8]) -> CmdResult { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + // try again until we got something + loop { + let read_len = self.inner.lock().read(buf)?; + if read_len > 0 { + return Ok(read_len); + } + // yield_now(); + } + } +} + +impl Read for StdinLock<'_> { + fn read(&mut self, buf: &mut [u8]) -> CmdResult { + self.inner.read(buf) + } +} + +impl BufRead for StdinLock<'_> { + fn fill_buf(&mut self) -> CmdResult<&[u8]> { + self.inner.fill_buf() + } + + fn consume(&mut self, n: usize) { + self.inner.consume(n) + } + + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> CmdResult { + self.inner.read_until(byte, buf) + } + + fn read_line(&mut self, buf: &mut String) -> CmdResult { + self.inner.read_line(buf) + } +} + +struct StdoutRaw; + +impl Write for StdoutRaw { + fn write(&mut self, buf: &[u8]) -> CmdResult { + ax_console_write_bytes(buf) + } + fn flush(&mut self) -> CmdResult<()> { + Ok(()) + } +} + +/// A handle to the global standard output stream of the current process. +pub struct Stdout { + inner: &'static Mutex, +} + +/// A locked reference to the [`Stdout`] handle. +pub struct StdoutLock<'a> { + inner: MutexGuard<'a, StdoutRaw>, +} + +impl Stdout { + /// Locks this handle to the standard output stream, returning a writable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the `Write` trait for writing data. + pub fn lock(&self) -> StdoutLock<'static> { + StdoutLock { + inner: self.inner.lock(), + } + } +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> CmdResult { + self.inner.lock().write(buf) + } + fn flush(&mut self) -> CmdResult<()> { + self.inner.lock().flush() + } +} + +impl Write for StdoutLock<'_> { + fn write(&mut self, buf: &[u8]) -> CmdResult { + self.inner.write(buf) + } + fn flush(&mut self) -> CmdResult<()> { + self.inner.flush() + } +} + +/// Constructs a new handle to the standard input of the current process. +pub fn stdin() -> Stdin { + static INSTANCE: Mutex> = Mutex::new(BufReader::new(StdinRaw)); + Stdin { inner: &INSTANCE } +} + +/// Constructs a new handle to the standard output of the current process. +pub fn stdout() -> Stdout { + static INSTANCE: Mutex = Mutex::new(StdoutRaw); + Stdout { inner: &INSTANCE } +}