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 } +}