diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1ee873..5587fcc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,24 +15,11 @@ jobs: with: target: riscv64gc-unknown-none-elf toolchain: nightly-2025-03-31 - + - name: Install cargo-binutils and rust-objcopy run: | rustup component add llvm-tools-preview cargo install cargo-binutils - - - name: Prepare for Build - run: | - # Fix breaking update in rust dependency. - cargo fetch - find ~/.cargo/git/checkouts/ -type f -name '*.rs' -exec sed -i 's/#\[unsafe(naked)\]/#[naked]/g' {} + - - # Correct incorrect build directory name in build script. - sed -i '/^SBI :=/s#riscv64imac-unknown-none-elf#riscv64gc-unknown-none-elf#' Makefile - sed -i '/^SBI :=/s#riscv64imac-unknown-none-elf#riscv64gc-unknown-none-elf#' scripts/make/build.mk - - make clone-rustsbi - sed -i -E '/^\s*#\[repr\(align\(16\)\)\]\s*$/d' rustsbi/prototyper/prototyper/src/sbi/early_trap.rs - name: Build with Makefile run: | @@ -51,27 +38,43 @@ jobs: - name: Build EDK2 run: sh scripts/test/build_edk2.sh - + - name: Generate disk image run: sh scripts/test/disk.sh - - name: Create EFI System Partition (ESP) - run: sh scripts/test/make_esp.sh - - name: Set up QEMU run: | sudo apt update sudo apt install -y qemu-system-misc - - name: Run QEMU - run: | - make qemu-run > qemu.log + # Test 1: HelloRiscv + - name: Create ESP (HelloRiscv) + run: EFI_NAME=HelloRiscv sh scripts/test/make_esp.sh + + - name: Run QEMU (HelloRiscv) + run: make qemu-run > qemu-hello.log + + - name: Upload QEMU log (HelloRiscv) + uses: actions/upload-artifact@v4 + with: + name: log-hello + path: qemu-hello.log + + - name: Check QEMU output (HelloRiscv) + run: sh scripts/test/check_hello_test.sh + + # Test 2: AllocatePage + - name: Create ESP (AllocatePage) + run: EFI_NAME=AllocatePage sh scripts/test/make_esp.sh + + - name: Run QEMU (AllocatePage) + run: make qemu-run > qemu-allocate.log - - name: Upload QEMU log + - name: Upload QEMU log (AllocatePage) uses: actions/upload-artifact@v4 with: - name: log - path: qemu.log + name: log-allocate + path: qemu-allocate.log - - name: Check QEMU output - run: sh scripts/test/check_hello_test.sh \ No newline at end of file + - name: Check QEMU output (AllocatePage) + run: sh scripts/test/check_allocate_test.sh diff --git a/Cargo.lock b/Cargo.lock index 2d916e5..85ecb85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,7 @@ source = "git+https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.2#84eb2 dependencies = [ "axdriver_base", "axdriver_block", + "axdriver_display", "virtio-drivers", ] diff --git a/Cargo.toml b/Cargo.toml index b7105bd..bb77a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ axconfig = { git = "https://github.com/arceos-org/arceos.git" } axsync = { git = "https://github.com/arceos-org/arceos.git" } axalloc = { git = "https://github.com/arceos-org/arceos.git", optional = true } axmm = { git = "https://github.com/arceos-org/arceos.git", optional = true } -axdriver = { git = "https://github.com/arceos-org/arceos.git", optional = true, features = ["virtio-blk"] } +axdriver = { git = "https://github.com/arceos-org/arceos.git", optional = true, features = ["virtio-blk", "virtio-gpu"] } axfs = { git = "https://github.com/arceos-org/arceos.git", optional = true } axnet = { git = "https://github.com/arceos-org/arceos.git", optional = true } axdisplay = { git = "https://github.com/arceos-org/arceos.git", optional = true } @@ -34,7 +34,7 @@ axio = { version = "0.1", features = ["alloc"] } crate_interface = "0.1" ctor_bare = "0.2" cfg-if = "1.0.1" -object = { version = "0.37.1", default-features = false, features = [ "read" ] } +object = { version = "0.37.1", default-features = false, features = [ "read", "pe" ] } chrono = { version = "0.4.38", default-features = false } uefi-raw = "0.11.0" lazyinit = "0.2.2" diff --git a/Makefile b/Makefile index 91230c8..aabd6b1 100644 --- a/Makefile +++ b/Makefile @@ -48,5 +48,8 @@ build: clean defconfig all 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) +qemu-display: + qemu-system-riscv64 -m 128M -serial mon:stdio -bios $(SBI) -machine virt -device virtio-blk-pci,drive=disk0 -drive id=disk0,if=none,format=raw,file=$(DISK) -device virtio-gpu-pci,xres=1024,yres=768 + qemu-run-ramdisknotload: 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) -device loader,file=$(RAMDISK_CPIO),addr=0x84000000 diff --git a/README.md b/README.md index 2a6be38..c71aa3a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # ArceBoot Reuse [ArceOS](https://github.com/arceos-org/arceos) components to build a cross-platform operating system bootloader + +# Build + +```bash +# for serial output build: +$ make +$ make qemu-run + +# for qemu virtual monitor: +# this may require a desktop system or graphical infrastructure +# such as x11 forwarding configured on your host machine. +$ make EXACT_FEATURES="display" +$ make qemu-display +``` \ No newline at end of file diff --git a/scripts/make/build.mk b/scripts/make/build.mk index a4df56a..256b2cf 100644 --- a/scripts/make/build.mk +++ b/scripts/make/build.mk @@ -8,6 +8,16 @@ ELF := $(ROOT_DIR)/target/$(TARGET)/release/arceboot BIN := $(ROOT_DIR)/target/$(TARGET)/release/arceboot.bin RUSTSBI_DIR := $(ROOT_DIR)/rustsbi +# 用逗号或者空格分隔 feature 名称 +EXACT_FEATURES ?= +EXACT_FEATURES_CLEANED := $(subst $(space),$(comma),$(strip $(EXACT_FEATURES))) + +ifeq ($(strip $(EXACT_FEATURES)),) + FEATURE_ARGS := +else + FEATURE_ARGS := --features '$(EXACT_FEATURES_CLEANED)' +endif + # 彩色打印 define print_info @printf "\033[1;37m%s\033[0m" "[RustSBI-Arceboot Build] " @@ -22,7 +32,7 @@ all: build-arceboot extract-bin clone-rustsbi build-rustsbi build-arceboot: $(call print_info,开始编译 ArceBoot...) - cargo rustc --release --target $(TARGET) -- \ + cargo rustc --release --target $(TARGET) $(FEATURE_ARGS) -- \ -C opt-level=z \ -C panic=abort \ -C relocation-model=static \ diff --git a/scripts/test/build_edk2.sh b/scripts/test/build_edk2.sh index 6cc967d..cea1ed4 100644 --- a/scripts/test/build_edk2.sh +++ b/scripts/test/build_edk2.sh @@ -7,7 +7,7 @@ EDK_DIR="$WORKSPACE_DIR/edk2" mkdir -p "$WORKSPACE_DIR" -echo "[1/3] 准备 EDK2 构建环境..." +echo "[1/4] 准备 EDK2 构建环境..." cd "$WORKSPACE_DIR" if [ ! -d "ToolChain/RISCV" ]; then @@ -19,7 +19,7 @@ else echo "RISCV 工具链已存在,跳过下载。" fi -echo "[2/3] 克隆 EDK2 仓库..." +echo "[2/4] 克隆 EDK2 仓库..." if [ ! -d "$EDK_DIR" ]; then git clone --recurse-submodule https://github.com/tianocore/edk2.git "$EDK_DIR" else @@ -28,7 +28,7 @@ fi export PATH="$WORKSPACE_DIR/ToolChain/RISCV/riscv/bin:$PATH" -echo "[3/3] 构建 EDK2..." +echo "[3/4] 构建 EDK2..." cd "$WORKSPACE_DIR" export WORKSPACE=`pwd` @@ -40,6 +40,7 @@ export EDK_TOOLS_PATH=$WORKSPACE/edk2/BaseTools make -C edk2/BaseTools . "$EDK_DIR/edksetup.sh" BaseTools +echo "[4/4] 准备 HelloRiscv 和 AllocatePage 示例..." # mkdir -p "$EDK_DIR/Hello" # cp -r "$PROJECT_ROOT/tests/edk2-Hello" "$EDK_DIR" # mv "$EDK_DIR/edk2-Hello"/* "$EDK_DIR/Hello/" @@ -50,4 +51,8 @@ cp -r "$PROJECT_ROOT/tests/edk2-HelloRiscv" "$EDK_DIR" mv "$EDK_DIR/edk2-HelloRiscv" "$EDK_DIR/HelloRiscv/" build -a RISCV64 -t GCC5 -p "$EDK_DIR/HelloRiscv/HelloRiscv.dsc" -echo "EDK2 构建完成。生成的镜像位于:$WORKSPACE_DIR/Build/DEBUG_GCC5/RISCV64/HelloRiscv.efi" +cp -r "$PROJECT_ROOT/tests/edk2-AllocatePage" "$EDK_DIR" +mv "$EDK_DIR/edk2-AllocatePage" "$EDK_DIR/AllocatePage/" +build -a RISCV64 -t GCC5 -p "$EDK_DIR/AllocatePage/AllocatePage.dsc" + +echo "EDK2 与示例构建完成。生成的文件位于:$WORKSPACE_DIR/Build/DEBUG_GCC5/RISCV64" diff --git a/scripts/test/check_allocate_test.sh b/scripts/test/check_allocate_test.sh new file mode 100644 index 0000000..e930a04 --- /dev/null +++ b/scripts/test/check_allocate_test.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +LOG_FILE="qemu-allocate.log" +TARGET_STRING="EFI Output: 0xDEADBEEF12345678" + +if [ ! -f "$LOG_FILE" ]; then + echo "❌ $LOG_FILE 不存在" + exit 1 +fi + +if grep -qF "$TARGET_STRING" "$LOG_FILE"; then + echo "✅ 找到匹配日志行" + exit 0 +else + echo "❌ 未找到匹配日志行" + exit 2 +fi \ No newline at end of file diff --git a/scripts/test/check_hello_test.sh b/scripts/test/check_hello_test.sh index d5dc560..80d46f5 100644 --- a/scripts/test/check_hello_test.sh +++ b/scripts/test/check_hello_test.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -LOG_FILE="qemu.log" +LOG_FILE="qemu-hello.log" TARGET_STRING="EFI Output: Hello, World!" if [ ! -f "$LOG_FILE" ]; then diff --git a/scripts/test/make_esp.sh b/scripts/test/make_esp.sh index 1f942f4..5b99e93 100644 --- a/scripts/test/make_esp.sh +++ b/scripts/test/make_esp.sh @@ -5,6 +5,9 @@ PROJECT_ROOT=$(pwd) IMG_NAME="disk.img" MOUNT_DIR="mnt_fat32" ESP_DIR="$MOUNT_DIR/EFI/BOOT" +EFI_NAME="${EFI_NAME:-HelloRiscv}" +BUILD_FLAVOR="${BUILD_FLAVOR:-DEBUG_GCC5}" +ARCH_DIR="${ARCH_DIR:-RISCV64}" if [ ! -d "$MOUNT_DIR" ]; then mkdir "$MOUNT_DIR" @@ -17,7 +20,9 @@ echo "[2/3] 创建 ESP 目录结构..." sudo mkdir -p "$ESP_DIR" echo "[3/3] 复制 efi 文件到 ESP..." -sudo cp "$PROJECT_ROOT/edk2/Build/DEBUG_GCC5/RISCV64/HelloRiscv.efi" "$ESP_DIR/BOOTRISCV64.EFI" +SRC_EFI="$PROJECT_ROOT/edk2/Build/$BUILD_FLAVOR/$ARCH_DIR/${EFI_NAME}.efi" +echo "源文件: $SRC_EFI" +sudo cp "$SRC_EFI" "$ESP_DIR/BOOTRISCV64.EFI" sudo find "$ESP_DIR" -type d | while read -r dir; do echo "$dir" diff --git a/src/main.rs b/src/main.rs index fd6253b..ee626be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(test), no_std)] #![no_main] #![allow(dead_code)] +#![feature(c_variadic)] #[macro_use] extern crate axlog; @@ -41,7 +42,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"))] + #[cfg(any(feature = "fs", feature = "net", feature = "display"))] { #[allow(unused_variables)] let all_devices = axdriver::init_drivers(); @@ -53,6 +54,9 @@ 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(); @@ -62,6 +66,7 @@ pub extern "C" fn rust_main(_cpu_id: usize, dtb: usize) -> ! { } // ramdisk check + #[cfg(feature = "ramdisk_cpio")] crate::medium::ramdisk_cpio::check_ramdisk(); crate::shell::shell_main(); diff --git a/src/medium/mod.rs b/src/medium/mod.rs index 5a24f19..98a57f0 100644 --- a/src/medium/mod.rs +++ b/src/medium/mod.rs @@ -1,2 +1,2 @@ pub mod ramdisk_cpio; -pub mod virtio_disk; \ No newline at end of file +pub mod virtio_disk; diff --git a/src/medium/ramdisk_cpio.rs b/src/medium/ramdisk_cpio.rs index 17bbbee..ea76e2a 100644 --- a/src/medium/ramdisk_cpio.rs +++ b/src/medium/ramdisk_cpio.rs @@ -230,4 +230,4 @@ pub fn check_ramdisk() { } } info!("Checking for ramdisk is done!"); -} +} \ No newline at end of file diff --git a/src/medium/virtio_disk.rs b/src/medium/virtio_disk.rs index 2a4f564..d3a6371 100644 --- a/src/medium/virtio_disk.rs +++ b/src/medium/virtio_disk.rs @@ -1,5 +1,5 @@ -use axio::{self as io}; use alloc::{string::String, vec::Vec}; +use axio::{self as io}; /// Returns the current working directory as a [`String`]. pub fn current_dir() -> io::Result { @@ -14,4 +14,4 @@ pub fn read(path: &str) -> io::Result> { /// Read the entire contents of a file into a string. pub fn read_to_string(path: &str) -> io::Result { axfs::api::read_to_string(path) -} \ No newline at end of file +} diff --git a/src/runtime/entry.rs b/src/runtime/entry.rs new file mode 100644 index 0000000..f9214e3 --- /dev/null +++ b/src/runtime/entry.rs @@ -0,0 +1,11 @@ +pub type EfiMainFn = + extern "efiapi" fn(image_handle: *mut core::ffi::c_void, system_table: *mut SystemTable) -> u64; + +use core::mem::transmute; + +use uefi_raw::table::system::SystemTable; + +pub fn resolve_entry_func(mapping: *const u8, entry: u64, base_va: u64) -> EfiMainFn { + let func_addr = (mapping as usize + (entry - base_va) as usize) as *const (); + unsafe { transmute(func_addr) } +} diff --git a/src/runtime/event/mod.rs b/src/runtime/event/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/runtime/image/mod.rs b/src/runtime/image/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/runtime/loader.rs b/src/runtime/loader.rs new file mode 100644 index 0000000..e33b710 --- /dev/null +++ b/src/runtime/loader.rs @@ -0,0 +1,121 @@ +use object::FileKind; +use object::read::pe::ImageOptionalHeader; +use object::{File, Object, ObjectSection, pe, read::pe::ImageNtHeaders}; + +pub fn load_efi_file(path: &str) -> alloc::vec::Vec { + axfs::api::read(path).expect("Failed to read EFI file from ramdisk") +} + +pub fn parse_efi_file(data: &[u8]) -> File { + File::parse(data).expect("Failed to parse EFI file") +} + +pub fn get_pe_image_base(data: &[u8]) -> Option { + let mut offset = pe::ImageDosHeader::parse(data) + .ok()? + .nt_headers_offset() + .into(); + let (nt_headers, _) = Pe::parse(data, &mut offset).ok()?; + Some(nt_headers.optional_header().image_base()) +} + +pub fn detect_and_get_image_base(data: &[u8]) -> Option { + let kind = FileKind::parse(data).ok()?; + match kind { + FileKind::Pe32 => get_pe_image_base::(data), + FileKind::Pe64 => get_pe_image_base::(data), + _ => None, + } +} + +pub fn analyze_sections(file: &File) -> (u64, u64) { + let mut base_va = u64::MAX; + let mut max_va = 0; + + for section in file.sections() { + let size = section.size(); + let start = section.address(); + let end = start + size; + base_va = base_va.min(start); + max_va = max_va.max(end); + } + + (base_va, max_va) +} + +pub fn load_sections(file: &File, mapping: *mut u8, base_va: u64) { + for section in file.sections() { + if let Ok(data) = section.data() { + let offset = (section.address() - base_va) as usize; + info!( + "Loading section {} to offset 0x{:x}, size 0x{:x}", + section.name().unwrap_or(""), + offset, + data.len() + ); + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), mapping.add(offset), data.len()); + } + } + } +} + +pub fn apply_relocations(file: &File, mapping: *mut u8, loaded_base: u64, original_base: u64) { + let delta = loaded_base as i64 - original_base as i64; + + for section in file.sections() { + if section.name().unwrap_or("") == ".reloc" { + let data = section.data().unwrap(); + let mut offset = 0; + + while offset < data.len() { + if offset + 8 > data.len() { + break; + } + + let va = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()); + let size = u32::from_le_bytes(data[offset + 4..offset + 8].try_into().unwrap()); + offset += 8; + + let count = (size - 8) / 2; + for _ in 0..count { + if offset + 2 > data.len() { + break; + } + + let entry = u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap()); + offset += 2; + + let rtype = entry >> 12; + let roffset = entry & 0x0fff; + + let patch_va = va as u64 + roffset as u64; + let patch_offset = (patch_va - loaded_base) as usize; + + match rtype { + 10 => { + // IMAGE_REL_BASED_DIR64 + let patch_ptr = unsafe { mapping.add(patch_offset) as *mut u64 }; + unsafe { + let orig = patch_ptr.read(); + patch_ptr.write(orig.wrapping_add(delta as u64)); + } + } + 3 => { + // IMAGE_REL_BASED_HIGHLOW (32-bit) + let patch_ptr = unsafe { mapping.add(patch_offset) as *mut u32 }; + unsafe { + let orig = patch_ptr.read(); + patch_ptr.write(orig.wrapping_add(delta as u32)); + } + } + 0 => { /* IMAGE_REL_BASED_ABSOLUTE, do nothing */ } + _ => { + warn!("Unsupported relocation type: {}", rtype); + } + } + } + } + } + } +} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 1d8bddc..b3c6684 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1,94 +1,51 @@ -use core::{ - mem::transmute, - ptr::{copy_nonoverlapping, null_mut}, -}; - -use axhal::{ - mem::{MemoryAddr, VirtAddr}, - paging::MappingFlags, -}; -use object::File; use object::Object; -use object::ObjectSection; -use uefi_raw::table::system::SystemTable; +use uefi_raw::table::boot::MemoryType; -use crate::runtime::table::{get_system_table_raw, init_system_table}; +use crate::runtime::service::memory::AllocateType; +mod entry; +mod loader; mod protocol; mod service; -mod table; - -pub type EfiMainFn = - extern "efiapi" fn(image_handle: *mut core::ffi::c_void, system_table: *mut SystemTable) -> u64; +mod system_table; +mod utils; pub fn efi_runtime_init() { - let shellcode = - axfs::api::read("/EFI/BOOT/BOOTRISCV64.EFI").expect("Failed to read EFI file from ramdisk"); - - let file = File::parse(&*shellcode).unwrap(); - let entry = file.entry(); - info!("Entry point: 0x{:x}", entry); + // Prepare the UEFI System Table + let system_table = { + system_table::init_system_table(); + system_table::get_system_table_raw() + }; - let mut base_va = u64::MAX; - let mut max_va = 0; - for section in file.sections() { - let size = section.size(); - let start = section.address(); - let end = start + size; - base_va = base_va.min(start); - max_va = max_va.max(end); - } + // Load the bootloader EFI file + let load_bootloader = loader::load_efi_file("/EFI/BOOT/BOOTRISCV64.EFI"); + let image_base = + loader::detect_and_get_image_base(&load_bootloader).expect("Failed to get PE image base"); + let file = loader::parse_efi_file(&load_bootloader); + let (base_va, max_va) = loader::analyze_sections(&file); let mem_size = (max_va - base_va) as usize; - info!( - "Mapping memory: 0x{:x} bytes at RVA base 0x{:x}", - mem_size, base_va - ); - - let page_count = (mem_size + 4095) / 4096; - // alloc memory for the EFI image - let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE; - let layout: core::alloc::Layout = core::alloc::Layout::from_size_align(shellcode.len(), 4096) - .expect("Invalid layout for shellcode"); - - let mapping = axalloc::global_allocator() - .alloc(layout) - .expect("Failed to allocate memory for shellcode"); - let mapping = mapping.as_ptr(); - - axmm::kernel_aspace() - .lock() - .protect( - VirtAddr::from_ptr_of(mapping).align_down(4096usize), - page_count * 4096, - flags, - ) - .expect("Failed to protect efi memory"); - - for section in file.sections() { - if let Ok(data) = section.data() { - let offset = (section.address() - base_va) as usize; - info!( - "Loading section {} to offset 0x{:x}, size 0x{:x}", - section.name().unwrap_or(""), - offset, - data.len() - ); - unsafe { - copy_nonoverlapping(data.as_ptr(), (mapping as *mut u8).add(offset), data.len()); - } - } - } + // let mapping = crate::runtime::service::memory::alloc_and_map_memory(mem_size, &load_bootloader); + let mapping = crate::runtime::service::memory::alloc_pages( + AllocateType::AnyPages, + MemoryType::LOADER_CODE, + mem_size / 4096 + 1, + ); - let func_addr = (mapping as usize + (entry - base_va) as usize) as *const (); - let func: EfiMainFn = unsafe { transmute(func_addr) }; + loader::load_sections(&file, mapping, base_va); + loader::apply_relocations(&file, mapping, base_va, image_base); - let system_table = { - init_system_table(); - get_system_table_raw() - }; + info!( + "Loaded EFI file with base VA: 0x{:x}, max VA: 0x{:x}, image base {:x}, mapping {:?}, entry: 0x{:x}", + base_va, + max_va, + image_base, + mapping, + file.entry() + ); - let result = func(null_mut(), system_table); - info!("efi_main return: {}", result) + let func = entry::resolve_entry_func(mapping, file.entry(), base_va); + let result = func(core::ptr::null_mut(), system_table); + info!("efi_main return: {}", result); } diff --git a/src/runtime/protocol/block_io.rs b/src/runtime/protocol/block_io.rs new file mode 100644 index 0000000..670ba7b --- /dev/null +++ b/src/runtime/protocol/block_io.rs @@ -0,0 +1,84 @@ +use core::ffi::c_void; + +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Boolean, Status, + protocol::block::{BlockIoMedia, BlockIoProtocol, Lba}, +}; + +use alloc::boxed::Box; + +static BLOCK_IO: LazyInit> = LazyInit::new(); + +#[derive(Debug)] +pub struct BlockIo { + protocol: &'static mut BlockIoProtocol, + protocol_raw: *mut BlockIoProtocol, +} + +// https://uefi.org/specs/UEFI/2.11/13_Protocols_Media_Access.html#block-i-o-protocol +impl BlockIo { + pub fn new() -> Self { + let media: *const BlockIoMedia = core::ptr::null(); + + let protocol = BlockIoProtocol { + revision: 0x00010000, + media, + reset, + read_blocks, + write_blocks, + flush_blocks, + }; + + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + + Self { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut BlockIoProtocol { + self.protocol_raw + } +} + +unsafe impl Send for BlockIo {} +unsafe impl Sync for BlockIo {} + +pub fn init_block_io() { + BLOCK_IO.init_once(Mutex::new(BlockIo::new())); +} + +pub extern "efiapi" fn reset( + _this: *mut BlockIoProtocol, + _extended_verification: Boolean, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn read_blocks( + _this: *const BlockIoProtocol, + _media_id: u32, + _lba: Lba, + _buffer_size: usize, + _buffer: *mut c_void, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn write_blocks( + _this: *mut BlockIoProtocol, + _media_id: u32, + _lba: Lba, + _buffer_size: usize, + _buffer: *const c_void, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn flush_blocks(_this: *mut BlockIoProtocol) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/protocol/device/device_path_from_text.rs b/src/runtime/protocol/device/device_path_from_text.rs new file mode 100644 index 0000000..7643615 --- /dev/null +++ b/src/runtime/protocol/device/device_path_from_text.rs @@ -0,0 +1,54 @@ +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Char16, + protocol::device_path::{DevicePathFromTextProtocol, DevicePathProtocol}, +}; + +use alloc::boxed::Box; + +static DEVICE_PATH_FROM_TEXT: LazyInit> = LazyInit::new(); + +#[derive(Debug)] +pub struct DevicePathFromText { + protocol: &'static mut DevicePathFromTextProtocol, + protocol_raw: *mut DevicePathFromTextProtocol, +} + +impl DevicePathFromText { + pub fn new() -> Self { + let protocol = DevicePathFromTextProtocol { + convert_text_to_device_node, + convert_text_to_device_path, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + Self { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut DevicePathFromTextProtocol { + self.protocol_raw + } +} + +unsafe impl Send for DevicePathFromText {} +unsafe impl Sync for DevicePathFromText {} + +pub fn init_device_path_from_text() { + DEVICE_PATH_FROM_TEXT.init_once(Mutex::new(DevicePathFromText::new())); +} + +pub extern "efiapi" fn convert_text_to_device_node( + _text_device_node: *const Char16, +) -> *const DevicePathProtocol { + core::ptr::null() +} + +pub extern "efiapi" fn convert_text_to_device_path( + _text_device_path: *const Char16, +) -> *const DevicePathProtocol { + core::ptr::null() +} diff --git a/src/runtime/protocol/device/device_path_to_text.rs b/src/runtime/protocol/device/device_path_to_text.rs new file mode 100644 index 0000000..12d86ba --- /dev/null +++ b/src/runtime/protocol/device/device_path_to_text.rs @@ -0,0 +1,58 @@ +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Boolean, Char16, + protocol::device_path::{DevicePathProtocol, DevicePathToTextProtocol}, +}; + +use alloc::boxed::Box; + +static DEVICE_PATH_TO_TEXT: LazyInit> = LazyInit::new(); + +#[derive(Debug)] +pub struct DevicePathToText { + protocol: &'static mut DevicePathToTextProtocol, + protocol_raw: *mut DevicePathToTextProtocol, +} + +impl DevicePathToText { + pub fn new() -> Self { + let protocol = DevicePathToTextProtocol { + convert_device_node_to_text, + convert_device_path_to_text, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + Self { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut DevicePathToTextProtocol { + self.protocol_raw + } +} + +unsafe impl Send for DevicePathToText {} +unsafe impl Sync for DevicePathToText {} + +pub fn init_device_path_to_text() { + DEVICE_PATH_TO_TEXT.init_once(Mutex::new(DevicePathToText::new())); +} + +pub extern "efiapi" fn convert_device_node_to_text( + _device_node: *const DevicePathProtocol, + _display_only: Boolean, + _allow_shortcuts: Boolean, +) -> *const Char16 { + core::ptr::null() +} + +pub extern "efiapi" fn convert_device_path_to_text( + _device_path: *const DevicePathProtocol, + _display_only: Boolean, + _allow_shortcuts: Boolean, +) -> *const Char16 { + core::ptr::null() +} diff --git a/src/runtime/protocol/device/device_path_utilities.rs b/src/runtime/protocol/device/device_path_utilities.rs new file mode 100644 index 0000000..1d9e581 --- /dev/null +++ b/src/runtime/protocol/device/device_path_utilities.rs @@ -0,0 +1,449 @@ +use core::{ + mem, + ptr::{self}, + slice, +}; + +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::protocol::device_path::{ + DevicePathProtocol, DevicePathUtilitiesProtocol, DeviceSubType, DeviceType, +}; + +use alloc::boxed::Box; +use alloc::vec::Vec; + +// Device Path node type: 0x7F = End of Hardware Device Path +const END_DEVICE_PATH_TYPE: u8 = 0x7F; +// End node subtype: 0x01 = End Instance (marks end of one instance, more may follow) +const END_INSTANCE_DEVICE_PATH_SUBTYPE: u8 = 0x01; +// End node subtype: 0xFF = End Entire (marks end of the whole device path) +const END_ENTIRE_DEVICE_PATH_SUBTYPE: u8 = 0xFF; +// Common header size for every Device Path node (Type + SubType + Length[2]) +const DEVICE_PATH_HEADER_LENGTH: usize = 4; + +static DEVICE_PATH_UTILITIES: LazyInit> = LazyInit::new(); + +#[inline] +unsafe fn get_node_type(protocol: *const DevicePathProtocol) -> u8 { + unsafe { *(protocol as *const u8) } +} +#[inline] +unsafe fn get_node_subtype(protocol: *const DevicePathProtocol) -> u8 { + unsafe { *((protocol as *const u8).add(1)) } +} +#[inline] +unsafe fn get_node_length_u16(protocol: *const DevicePathProtocol) -> u16 { + // Little-endian 2 bytes at offset 2 + // equivalent to read_unaligned((b+2) as *const u16) + unsafe { *(protocol as *const u16).add(1) } +} +#[inline] +unsafe fn get_node_length(protocol: *const DevicePathProtocol) -> usize { + unsafe { get_node_length_u16(protocol) as usize } +} +#[inline] +unsafe fn is_end_node(protocol: *const DevicePathProtocol) -> bool { + unsafe { + get_node_type(protocol) == END_DEVICE_PATH_TYPE + && get_node_length(protocol) >= DEVICE_PATH_HEADER_LENGTH + } +} +#[inline] +unsafe fn is_end_entire_node(protocol: *const DevicePathProtocol) -> bool { + unsafe { is_end_node(protocol) && get_node_subtype(protocol) == END_ENTIRE_DEVICE_PATH_SUBTYPE } +} +#[inline] +unsafe fn is_end_instance_node(protocol: *const DevicePathProtocol) -> bool { + unsafe { + is_end_node(protocol) && get_node_subtype(protocol) == END_INSTANCE_DEVICE_PATH_SUBTYPE + } +} + +#[inline] +unsafe fn compute_total_device_path_size( + device_path_ptr: *const DevicePathProtocol, +) -> Option { + if device_path_ptr.is_null() { + return Some(0); + } + let mut current_ptr = device_path_ptr; + let mut total_size: usize = 0; + let hard_size_cap: usize = 1 << 20; + + loop { + let node_length = unsafe { get_node_length(current_ptr) }; + if node_length < DEVICE_PATH_HEADER_LENGTH { + return None; + } + total_size = total_size.checked_add(node_length)?; + if total_size > hard_size_cap { + return None; + } + if unsafe { is_end_entire_node(current_ptr) } { + break; + } + current_ptr = + unsafe { (current_ptr as *const u8).add(node_length) } as *const DevicePathProtocol; + } + Some(total_size) +} + +#[inline] +unsafe fn copy_device_path_bytes_to_box( + device_path_ptr: *const DevicePathProtocol, + total_size: usize, +) -> *const DevicePathProtocol { + if total_size == 0 { + return ptr::null(); + } + let source_slice = unsafe { slice::from_raw_parts(device_path_ptr as *const u8, total_size) }; + let mut buffer = Vec::::with_capacity(total_size); + buffer.extend_from_slice(source_slice); + let boxed_slice = buffer.into_boxed_slice(); + let raw_u8_ptr = Box::into_raw(boxed_slice) as *mut u8; + raw_u8_ptr as *const DevicePathProtocol +} + +pub struct DevicePathUtilities { + protocol: &'static mut DevicePathUtilitiesProtocol, + protocol_raw: *mut DevicePathUtilitiesProtocol, +} + +impl DevicePathUtilities { + pub fn new() -> Self { + let protocol = DevicePathUtilitiesProtocol { + get_device_path_size, + duplicate_device_path, + append_device_path, + append_device_node, + append_device_path_instance, + get_next_device_path_instance, + is_device_path_multi_instance, + create_device_node, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + DevicePathUtilities { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut DevicePathUtilitiesProtocol { + let guard = DEVICE_PATH_UTILITIES.lock(); + guard.protocol_raw + } +} + +unsafe impl Send for DevicePathUtilities {} +unsafe impl Sync for DevicePathUtilities {} + +pub fn init_device_path_uttilities() { + DEVICE_PATH_UTILITIES.init_once(Mutex::new(DevicePathUtilities::new())); +} + +pub extern "efiapi" fn get_device_path_size(device_path: *const DevicePathProtocol) -> usize { + unsafe { + match compute_total_device_path_size(device_path) { + Some(total_size) => total_size, + None => 0, + } + } +} + +pub extern "efiapi" fn duplicate_device_path( + device_path: *const DevicePathProtocol, +) -> *const DevicePathProtocol { + unsafe { + match compute_total_device_path_size(device_path) { + Some(0) => ptr::null_mut(), + Some(total_size) => copy_device_path_bytes_to_box(device_path, total_size) as *const _, + None => ptr::null_mut(), + } + } +} + +pub extern "efiapi" fn append_device_path( + first_path: *const DevicePathProtocol, + second_path: *const DevicePathProtocol, +) -> *const DevicePathProtocol { + unsafe { + let first_total_size = match compute_total_device_path_size(first_path) { + Some(0) => DEVICE_PATH_HEADER_LENGTH, + Some(total_size) => total_size, + None => return ptr::null_mut(), + }; + let second_total_size = match compute_total_device_path_size(second_path) { + Some(0) => DEVICE_PATH_HEADER_LENGTH, + Some(total_size) => total_size, + None => return ptr::null_mut(), + }; + + let first_without_end_length = if first_total_size >= DEVICE_PATH_HEADER_LENGTH { + first_total_size - DEVICE_PATH_HEADER_LENGTH + } else { + 0 + }; + let output_total_size = first_without_end_length + .checked_add(second_total_size) + .unwrap_or(0); + if output_total_size == 0 { + return ptr::null_mut(); + } + + let mut output_bytes = Vec::::with_capacity(output_total_size); + if first_without_end_length > 0 && !first_path.is_null() { + output_bytes.extend_from_slice(slice::from_raw_parts( + first_path as *const u8, + first_without_end_length, + )); + } + if second_total_size > 0 && !second_path.is_null() { + output_bytes.extend_from_slice(slice::from_raw_parts( + second_path as *const u8, + second_total_size, + )); + } else { + // If second path is empty, terminate with End Entire node + output_bytes.extend_from_slice(&[ + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + 0x04, + 0x00, + ]); + } + + let boxed = output_bytes.into_boxed_slice(); + Box::into_raw(boxed) as *const DevicePathProtocol + } +} + +pub extern "efiapi" fn append_device_node( + device_path: *const DevicePathProtocol, + device_node: *const DevicePathProtocol, +) -> *const DevicePathProtocol { + unsafe { + if device_node.is_null() { + return duplicate_device_path(device_path); + } + let device_node_length = get_node_length(device_node); + if device_node_length < DEVICE_PATH_HEADER_LENGTH || is_end_node(device_node) { + return duplicate_device_path(device_path); + } + + let device_path_total_size = match compute_total_device_path_size(device_path) { + Some(0) => DEVICE_PATH_HEADER_LENGTH, + Some(total_size) => total_size, + None => return ptr::null_mut(), + }; + + let device_path_without_end_length = device_path_total_size - DEVICE_PATH_HEADER_LENGTH; + let output_total_size = device_path_without_end_length + .checked_add(device_node_length) + .and_then(|v| v.checked_add(DEVICE_PATH_HEADER_LENGTH)) + .unwrap_or(0); + if output_total_size == 0 { + return ptr::null_mut(); + } + + let mut output_bytes = Vec::::with_capacity(output_total_size); + if device_path_without_end_length > 0 && !device_path.is_null() { + output_bytes.extend_from_slice(slice::from_raw_parts( + device_path as *const u8, + device_path_without_end_length, + )); + } + output_bytes.extend_from_slice(slice::from_raw_parts( + device_node as *const u8, + device_node_length, + )); + // Append End Entire node + output_bytes.extend_from_slice(&[ + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + 0x04, + 0x00, + ]); + + let boxed = output_bytes.into_boxed_slice(); + Box::into_raw(boxed) as *const DevicePathProtocol + } +} + +pub extern "efiapi" fn append_device_path_instance( + device_path: *const DevicePathProtocol, + device_path_instance: *const DevicePathProtocol, +) -> *const DevicePathProtocol { + unsafe { + let device_path_total_size = match compute_total_device_path_size(device_path) { + Some(0) => DEVICE_PATH_HEADER_LENGTH, + Some(total_size) => total_size, + None => return ptr::null_mut(), + }; + let device_path_without_end_length = device_path_total_size - DEVICE_PATH_HEADER_LENGTH; + if device_path_instance.is_null() { + return duplicate_device_path(device_path); + } + + let mut current_ptr = device_path_instance; + let mut instance_total_length: usize = 0; + loop { + let node_length = get_node_length(current_ptr); + if node_length < DEVICE_PATH_HEADER_LENGTH { + return ptr::null_mut(); + } + instance_total_length = instance_total_length.checked_add(node_length).unwrap_or(0); + if is_end_instance_node(current_ptr) || is_end_entire_node(current_ptr) { + break; + } + current_ptr = (current_ptr as *const u8).add(node_length) as *const DevicePathProtocol; + } + + // output = device_path_without_end_length + instance_total_length + End Entire (4 bytes) + let output_total_size = device_path_without_end_length + .checked_add(instance_total_length) + .and_then(|v| v.checked_add(DEVICE_PATH_HEADER_LENGTH)) + .unwrap_or(0); + if output_total_size == 0 { + return ptr::null_mut(); + } + + let mut output_bytes = Vec::::with_capacity(output_total_size); + if device_path_without_end_length > 0 && !device_path.is_null() { + output_bytes.extend_from_slice(slice::from_raw_parts( + device_path as *const u8, + device_path_without_end_length, + )); + // Insert an End Instance node between instances + output_bytes.extend_from_slice(&[ + END_DEVICE_PATH_TYPE, + END_INSTANCE_DEVICE_PATH_SUBTYPE, + 0x04, + 0x00, + ]); + } + output_bytes.extend_from_slice(slice::from_raw_parts( + device_path_instance as *const u8, + instance_total_length, + )); + // Remove the last 4 bytes of the copied instance (its End node), then add End Entire + if let Some(_) = + output_bytes.get(output_bytes.len().saturating_sub(DEVICE_PATH_HEADER_LENGTH)..) + { + output_bytes.truncate(output_bytes.len().saturating_sub(DEVICE_PATH_HEADER_LENGTH)); + } + output_bytes.extend_from_slice(&[ + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + 0x04, + 0x00, + ]); + + let boxed = output_bytes.into_boxed_slice(); + Box::into_raw(boxed) as *const DevicePathProtocol + } +} + +pub extern "efiapi" fn get_next_device_path_instance( + device_path_instance_ptr: *mut *const DevicePathProtocol, + device_path_instance_size_out: *mut usize, +) -> *const DevicePathProtocol { + unsafe { + if device_path_instance_ptr.is_null() || device_path_instance_size_out.is_null() { + return ptr::null_mut(); + } + let current_instance_ptr = *device_path_instance_ptr; + if current_instance_ptr.is_null() { + *device_path_instance_size_out = 0; + return ptr::null_mut(); + } + + let start_bytes_ptr = current_instance_ptr as *const u8; + let mut cursor_ptr = current_instance_ptr; + let mut instance_bytes_length: usize = 0; + loop { + let node_length = get_node_length(cursor_ptr); + if node_length < DEVICE_PATH_HEADER_LENGTH { + return ptr::null_mut(); + } + instance_bytes_length = instance_bytes_length.checked_add(node_length).unwrap_or(0); + if is_end_instance_node(cursor_ptr) || is_end_entire_node(cursor_ptr) { + break; + } + cursor_ptr = (cursor_ptr as *const u8).add(node_length) as *const DevicePathProtocol; + } + + let mut output_bytes = Vec::::with_capacity(instance_bytes_length); + output_bytes.extend_from_slice(slice::from_raw_parts( + start_bytes_ptr, + instance_bytes_length, + )); + if output_bytes.len() >= DEVICE_PATH_HEADER_LENGTH { + output_bytes.truncate(output_bytes.len() - DEVICE_PATH_HEADER_LENGTH); + } + output_bytes.extend_from_slice(&[ + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + 0x04, + 0x00, + ]); + + let after_current_instance = (cursor_ptr as *const u8).add(DEVICE_PATH_HEADER_LENGTH); + if is_end_instance_node(cursor_ptr) { + *device_path_instance_ptr = after_current_instance as *const DevicePathProtocol; + } else { + *device_path_instance_ptr = ptr::null(); + } + + *device_path_instance_size_out = output_bytes.len(); + let boxed = output_bytes.into_boxed_slice(); + Box::into_raw(boxed) as *const DevicePathProtocol + } +} + +pub extern "efiapi" fn is_device_path_multi_instance( + device_path: *const DevicePathProtocol, +) -> bool { + unsafe { + if device_path.is_null() { + return false; + } + let mut cursor_ptr = device_path; + loop { + if is_end_instance_node(cursor_ptr) { + return true; + } + if is_end_entire_node(cursor_ptr) { + return false; + } + let node_length = get_node_length(cursor_ptr); + if node_length < DEVICE_PATH_HEADER_LENGTH { + return false; + } + cursor_ptr = (cursor_ptr as *const u8).add(node_length) as *const DevicePathProtocol; + } + } +} + +pub extern "efiapi" fn create_device_node( + node_type: DeviceType, + node_sub_type: DeviceSubType, + node_length: u16, +) -> *const DevicePathProtocol { + unsafe { + if usize::from(node_length) < DEVICE_PATH_HEADER_LENGTH { + return ptr::null_mut(); + } + let mut buffer = Vec::::with_capacity(node_length as usize); + buffer.resize(node_length as usize, 0u8); + + buffer[0] = mem::transmute::(node_type); + buffer[1] = mem::transmute::(node_sub_type); + buffer[2] = (node_length & 0x00FF) as u8; + buffer[3] = (node_length >> 8) as u8; + + let boxed = buffer.into_boxed_slice(); + Box::into_raw(boxed) as *const DevicePathProtocol + } +} diff --git a/src/runtime/protocol/device/mod.rs b/src/runtime/protocol/device/mod.rs new file mode 100644 index 0000000..1e97d81 --- /dev/null +++ b/src/runtime/protocol/device/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod device_path_from_text; +pub(crate) mod device_path_to_text; +pub(crate) mod device_path_utilities; diff --git a/src/runtime/protocol/fs/file_protocol_v1.rs b/src/runtime/protocol/fs/file_protocol_v1.rs new file mode 100644 index 0000000..b9efee8 --- /dev/null +++ b/src/runtime/protocol/fs/file_protocol_v1.rs @@ -0,0 +1,467 @@ +use core::ffi::c_void; + +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Char16, Guid, Status, + protocol::file_system::{FileAttribute, FileMode, FileProtocolRevision, FileProtocolV1}, +}; + +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec::Vec; + +use crate::runtime::{ + protocol::fs::HandleKind, + utils::{normalize_uefi_path, utf16_cstr_to_string}, +}; + +#[derive(Debug)] +pub struct FileHandlerCounter { + path: String, + count: usize, + file_handle: FileProtocolV1Impl, +} + +#[allow(dead_code)] +#[derive(Clone)] +struct DirEnt { + name: String, + is_dir: bool, + size: u64, + create_time: u64, + access_time: u64, + modify_time: u64, +} + +static FILE_HANDLER_MAP: LazyInit>> = + LazyInit::new(); + +#[repr(C)] +#[derive(Debug)] +pub struct FileProtocolV1Impl { + protocol: &'static mut FileProtocolV1, + protocol_raw: *mut FileProtocolV1, + path: String, + mode: FileMode, + kind: HandleKind, + position: usize, // current read/write position for files +} + +impl FileProtocolV1Impl { + pub fn new(path: &str, mode: FileMode, kind: HandleKind) -> Self { + let protocol = FileProtocolV1 { + revision: FileProtocolRevision::REVISION_1, + open, + close, + delete, + read, + write, + get_position, + set_position, + get_info, + set_info, + flush, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + Self { + protocol, + protocol_raw, + path: path.to_string(), + mode, + kind, + position: 0, + } + } + + pub fn get_protocol(&self) -> *mut FileProtocolV1 { + self.protocol_raw + } +} + +unsafe impl Send for FileProtocolV1Impl {} +unsafe impl Sync for FileProtocolV1Impl {} + +#[inline] +unsafe fn from_this(this: *mut FileProtocolV1) -> *mut FileProtocolV1Impl { + this as *mut FileProtocolV1Impl +} + +pub fn open_root() -> *mut FileProtocolV1 { + // The OpenVolume() function opens a volume, and returns a file handle to the volume’s root directory. + // This handle is used to perform all other file I/O operations. + // The volume remains open until all the file handles to it are closed. + let mut mapper = FILE_HANDLER_MAP + .get() + .expect("Failed to get FILE_HANDLER_MAP") + .lock(); + + if let Some(counter_box) = mapper.get_mut("/") { + counter_box.count += 1; + return counter_box.file_handle.get_protocol(); + } + + let file_handle = + FileProtocolV1Impl::new("/", FileMode::READ | FileMode::WRITE, HandleKind::Dir); + let counter = FileHandlerCounter { + path: "/".to_string(), + file_handle: file_handle, + count: 1, + }; + + mapper.insert("/", counter); + let counter_box = mapper.get_mut("/").expect("inserted but missing"); + + return counter_box.file_handle.get_protocol(); +} + +// impl FileProtocolV1. Refer to UEFI Spec 2.11 Section 13.5.1. +pub extern "efiapi" fn open( + this: *mut FileProtocolV1, + new_handle: *mut *mut FileProtocolV1, + file_name: *const Char16, + open_mode: FileMode, + attributes: FileAttribute, +) -> Status { + if this.is_null() || new_handle.is_null() || file_name.is_null() { + return Status::INVALID_PARAMETER; + } + + let r = open_mode.contains(FileMode::READ); + let w = open_mode.contains(FileMode::WRITE); + let c = open_mode.contains(FileMode::CREATE); + match (r, w, c) { + (true, false, false) | (true, true, false) | (true, true, true) => {} + _ => return Status::INVALID_PARAMETER, + } + + let base = unsafe { &*(this as *mut FileProtocolV1Impl) }.path.clone(); + let name = unsafe { utf16_cstr_to_string(file_name) }.unwrap_or_default(); + let resolved = if name.is_empty() || name == "." { + base + } else { + normalize_uefi_path(&base, &name) + }; + + // Read the actual existence and type (Some(true)=directory, Some(false)=file, None=does not exist) + let (exists, exists_is_dir) = match axfs::api::metadata(&resolved) { + Ok(md) => (true, md.is_dir()), + Err(_) => (false, false), + }; + + // CREATE checks attributes and uses the DIRECTORY bit to determine the "expected type" + let want_dir = if c { + const VALID: u32 = (FileAttribute::READ_ONLY.bits() + | FileAttribute::HIDDEN.bits() + | FileAttribute::SYSTEM.bits() + | FileAttribute::DIRECTORY.bits() + | FileAttribute::ARCHIVE.bits()) as u32; + if (attributes.bits() as u32) & !VALID != 0 { + return Status::INVALID_PARAMETER; + } + attributes.contains(FileAttribute::DIRECTORY) + } else { + false + }; + + // 1. If CREATE is requested (c = true): + // - If the target already exists: we must check whether the actual type + // (exists_is_dir) matches the requested type (want_dir). + // * If they mismatch → return ACCESS_DENIED. + // * If they match → just open the existing object (need_create = false). + // - If the target does not exist: create a new object based on want_dir + // (directory vs file) (need_create = true). + // + // 2. If CREATE is not requested (c = false): + // - The target must already exist, otherwise return NOT_FOUND. + // - If it exists, simply use its actual type (final_is_dir = exists_is_dir). + // + // This logic boils down to two outputs: + // - final_is_dir: whether the resulting handle should represent a directory or a file + // - need_create : whether we must create a new object in the filesystem + let (final_is_dir, need_create) = if c { + if exists { + if exists_is_dir != want_dir { + return Status::ACCESS_DENIED; + } + (exists_is_dir, false) + } else { + (want_dir, true) + } + } else { + if !exists { + return Status::NOT_FOUND; + } + (exists_is_dir, false) + }; + + if need_create { + let res = if final_is_dir { + axfs::api::create_dir(&resolved) + } else { + axfs::api::write(&resolved, &[]) + }; + if let Err(_e) = res { + return Status::ACCESS_DENIED; + } + } + + let handle = FileProtocolV1Impl::new( + &resolved, + open_mode, + if final_is_dir { + HandleKind::Dir + } else { + HandleKind::File + }, + ); + unsafe { + *new_handle = handle.get_protocol(); + } + + Status::SUCCESS +} + +pub extern "efiapi" fn close(this: *mut FileProtocolV1) -> Status { + if this.is_null() { + return Status::INVALID_PARAMETER; + } + + if unsafe { &mut *from_this(this) }.path == "/" { + // Root directory is special: never remove it, just decrement count + let mut mapper = FILE_HANDLER_MAP + .get() + .expect("Failed to get FILE_HANDLER_MAP") + .lock(); + if let Some(counter_box) = mapper.get_mut("/") { + if counter_box.count > 0 { + counter_box.count -= 1; + } + + return Status::SUCCESS; + } + } + + unsafe { + drop(Box::from_raw(from_this(this))); + } + + Status::SUCCESS +} + +pub extern "efiapi" fn delete(this: *mut FileProtocolV1) -> Status { + if this.is_null() { + return Status::INVALID_PARAMETER; + } + let this = unsafe { &mut *from_this(this) }; + let path = this.path.clone(); + + let result = match this.kind { + HandleKind::Dir => axfs::api::remove_dir(&path), + HandleKind::File => axfs::api::remove_file(&path), + }; + + if let Err(_e) = result { + return Status::WARN_DELETE_FAILURE; + } + + unsafe { + drop(Box::from_raw(from_this(this.protocol))); + } + + Status::SUCCESS +} + +pub extern "efiapi" fn read( + this: *mut FileProtocolV1, + buffer_size: *mut usize, + buffer: *mut c_void, +) -> Status { + if this.is_null() || buffer_size.is_null() { + return Status::INVALID_PARAMETER; + } + + let this = unsafe { &mut *from_this(this) }; + + if !this.mode.contains(FileMode::READ) { + return Status::ACCESS_DENIED; + } + + match this.kind { + HandleKind::Dir => { + // TODO: implement directory reading + // The spec does not seem to explicitly mention directory entries, + // and axfs has limited capabilities and may not be able to construct valid information, + // so it is not implemented yet. + return Status::UNSUPPORTED; + } + HandleKind::File => { + if buffer.is_null() { + return Status::INVALID_PARAMETER; + } + + let want = unsafe { *buffer_size }; + if want == 0 { + return Status::SUCCESS; + } + + let data = match axfs::api::read(&this.path) { + Ok(v) => v, + Err(_) => return Status::DEVICE_ERROR, + }; + + let len = data.len(); + let pos = this.position; + + if pos > len { + return Status::DEVICE_ERROR; + } + + let remain = len - pos; + let take = core::cmp::min(remain, want); + + if take > 0 { + let src = &data[pos..pos + take]; + let dst = unsafe { core::slice::from_raw_parts_mut(buffer as *mut u8, take) }; + dst.copy_from_slice(src); + this.position = pos + take; + } + + unsafe { *buffer_size = take }; + Status::SUCCESS + } + } +} + +pub extern "efiapi" fn write( + this: *mut FileProtocolV1, + buffer_size: *mut usize, + buffer: *const c_void, +) -> Status { + if this.is_null() || buffer_size.is_null() { + return Status::INVALID_PARAMETER; + } + + let this = unsafe { &mut *from_this(this) }; + if !this.mode.contains(FileMode::WRITE) { + return Status::ACCESS_DENIED; + } + + match this.kind { + HandleKind::Dir => { + return Status::UNSUPPORTED; + } + HandleKind::File => { + let want = unsafe { *buffer_size }; + if want == 0 { + return Status::SUCCESS; + } + + if buffer.is_null() { + return Status::INVALID_PARAMETER; + } + + let mut data = match axfs::api::read(&this.path) { + Ok(v) => v, + Err(_) => Vec::new(), + }; + + let pos = this.position; + if pos > data.len() { + return Status::DEVICE_ERROR; + } + + if data.len() < pos { + data.resize(pos, 0); + } + + let end = pos.saturating_add(want); + if data.len() < end { + data.resize(end, 0); + } + + let src = unsafe { core::slice::from_raw_parts(buffer as *const u8, want) }; + data[pos..end].copy_from_slice(src); + if let Err(_e) = axfs::api::write(&this.path, &data) { + return Status::DEVICE_ERROR; + } + this.position = end; + unsafe { *buffer_size = want }; + Status::SUCCESS + } + } +} + +pub extern "efiapi" fn get_position(this: *const FileProtocolV1, position: *mut u64) -> Status { + if this.is_null() || position.is_null() { + return Status::INVALID_PARAMETER; + } + let this = unsafe { &*from_this(this as *mut FileProtocolV1) }; + + match this.kind { + HandleKind::Dir => Status::UNSUPPORTED, + HandleKind::File => { + unsafe { + *position = this.position as u64; + } + Status::SUCCESS + } + } +} + +pub extern "efiapi" fn set_position(this: *mut FileProtocolV1, position: u64) -> Status { + if this.is_null() { + return Status::INVALID_PARAMETER; + } + let this = unsafe { &mut *from_this(this) }; + + match this.kind { + HandleKind::Dir => Status::UNSUPPORTED, + HandleKind::File => { + if position == u64::MAX { + let size = match axfs::api::metadata(&this.path) { + Ok(md) => md.len() as u64, + Err(_) => return Status::DEVICE_ERROR, + }; + if let Ok(sz) = usize::try_from(size) { + this.position = sz; + Status::SUCCESS + } else { + Status::INVALID_PARAMETER + } + } else { + if let Ok(p) = usize::try_from(position) { + this.position = p; + Status::SUCCESS + } else { + Status::INVALID_PARAMETER + } + } + } + } +} + +pub extern "efiapi" fn get_info( + _this: *mut FileProtocolV1, + _information_type: *const Guid, + _buffer_size: *mut usize, + _buffer: *mut c_void, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn set_info( + _this: *mut FileProtocolV1, + _information_type: *const Guid, + _buffer_size: usize, + _buffer: *const c_void, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn flush(_this: *mut FileProtocolV1) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/protocol/fs/file_protocol_v2.rs b/src/runtime/protocol/fs/file_protocol_v2.rs new file mode 100644 index 0000000..f293915 --- /dev/null +++ b/src/runtime/protocol/fs/file_protocol_v2.rs @@ -0,0 +1,65 @@ +use uefi_raw::{ + Char16, Status, + protocol::file_system::{FileAttribute, FileIoToken, FileMode, FileProtocolV2}, +}; + +use crate::runtime::protocol::fs::{HandleKind, file_protocol_v1::FileProtocolV1Impl}; + +use alloc::boxed::Box; + +#[repr(C)] +#[derive(Debug)] +pub struct FileProtocolV2Impl { + protocol: &'static mut FileProtocolV2, + protocol_raw: *mut FileProtocolV2, +} + +impl FileProtocolV2Impl { + pub fn new(path: &str, mode: FileMode, kind: HandleKind) -> Self { + let v1 = FileProtocolV1Impl::new(path, mode, kind); // reuse V1 implementation + let protocol = FileProtocolV2 { + v1: unsafe { *Box::from_raw(v1.get_protocol()) }, // clone contents, not share pointer + open_ex, + read_ex, + write_ex, + flush_ex, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + Self { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut FileProtocolV2 { + self.protocol_raw + } +} + +unsafe impl Send for FileProtocolV2Impl {} +unsafe impl Sync for FileProtocolV2Impl {} + +// v2-specific. Refer to UEFI Spec 2.11 Section 13.5.7 - 13.5.10. +pub extern "efiapi" fn open_ex( + _this: *mut FileProtocolV2, + _new_handle: *mut *mut FileProtocolV2, + _file_name: *const Char16, + _open_mode: FileMode, + _attributes: FileAttribute, + _token: *mut FileIoToken, +) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn read_ex(_this: *mut FileProtocolV2, _token: *mut FileIoToken) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn write_ex(_this: *mut FileProtocolV2, _token: *mut FileIoToken) -> Status { + Status::UNSUPPORTED +} + +pub extern "efiapi" fn flush_ex(_this: *mut FileProtocolV2, _token: *mut FileIoToken) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/protocol/fs/mod.rs b/src/runtime/protocol/fs/mod.rs new file mode 100644 index 0000000..cbc97a1 --- /dev/null +++ b/src/runtime/protocol/fs/mod.rs @@ -0,0 +1,9 @@ +pub(crate) mod file_protocol_v1; +pub(crate) mod file_protocol_v2; +pub(crate) mod simple_file_system; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum HandleKind { + Dir, + File, +} diff --git a/src/runtime/protocol/fs/simple_file_system.rs b/src/runtime/protocol/fs/simple_file_system.rs new file mode 100644 index 0000000..5a8da38 --- /dev/null +++ b/src/runtime/protocol/fs/simple_file_system.rs @@ -0,0 +1,57 @@ +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Status, + protocol::file_system::{FileProtocolV1, SimpleFileSystemProtocol}, +}; + +use alloc::boxed::Box; + +use crate::runtime::protocol::fs::file_protocol_v1::open_root; + +static SIMPLE_FILE_SYSTEM: LazyInit> = LazyInit::new(); + +#[repr(C)] +#[derive(Debug)] +pub struct SimpleFileSystem { + protocol: &'static mut SimpleFileSystemProtocol, + protocol_raw: *mut SimpleFileSystemProtocol, +} + +impl SimpleFileSystem { + pub fn new() -> Self { + let protocol = SimpleFileSystemProtocol { + revision: 0x00010000, + open_volume, + }; + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + Self { + protocol, + protocol_raw, + } + } + + pub fn get_protocol(&self) -> *mut SimpleFileSystemProtocol { + self.protocol_raw + } +} + +unsafe impl Send for SimpleFileSystem {} +unsafe impl Sync for SimpleFileSystem {} + +pub fn init_simple_file_system() { + SIMPLE_FILE_SYSTEM.init_once(Mutex::new(SimpleFileSystem::new())); +} + +// impl SimpleFileSystem. Refer to UEFI Spec 2.11 Section 13.4. +pub extern "efiapi" fn open_volume( + _this: *mut SimpleFileSystemProtocol, + root: *mut *mut FileProtocolV1, +) -> Status { + unsafe { + *root = open_root(); + } + + Status::SUCCESS +} diff --git a/src/runtime/protocol/graphics_output.rs b/src/runtime/protocol/graphics_output.rs index 8b13789..2e047c5 100644 --- a/src/runtime/protocol/graphics_output.rs +++ b/src/runtime/protocol/graphics_output.rs @@ -1 +1,360 @@ +use alloc::boxed::Box; +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::{ + Status, + protocol::console::{ + GraphicsOutputBltOperation, GraphicsOutputBltPixel, GraphicsOutputModeInformation, + GraphicsOutputProtocol, GraphicsOutputProtocolMode, GraphicsPixelFormat, PixelBitmask, + }, +}; + +static GRAPHICS_OUTPUT: LazyInit> = LazyInit::new(); + +#[derive(Debug)] +pub struct GraphicsOutput { + protocol: &'static mut GraphicsOutputProtocol, + protocol_raw: *mut GraphicsOutputProtocol, + + mode_box: *mut GraphicsOutputProtocolMode, + info_box: *mut GraphicsOutputModeInformation, + + width_pixels: u32, + height_pixels: u32, + frame_buffer_base_virtual_address: usize, + frame_buffer_size_bytes: usize, + stride_pixels: u32, +} + +impl GraphicsOutput { + pub fn new( + width_pixels: u32, + height_pixels: u32, + frame_buffer_base_virtual_address: usize, + frame_buffer_size_bytes: usize, + stride_pixels: u32, + ) -> Self { + let mode_info = GraphicsOutputModeInformation { + version: 0, + horizontal_resolution: width_pixels, + vertical_resolution: height_pixels, + pixel_format: GraphicsPixelFormat::PIXEL_BLUE_GREEN_RED_RESERVED_8_BIT_PER_COLOR, + pixel_information: PixelBitmask { + red: 0, + green: 0, + blue: 0, + reserved: 0, + }, + pixels_per_scan_line: stride_pixels, + }; + let info_box = Box::into_raw(Box::new(mode_info)); + + let mode = GraphicsOutputProtocolMode { + max_mode: 1, + mode: 0, + info: info_box, + size_of_info: core::mem::size_of::(), + frame_buffer_base: frame_buffer_base_virtual_address as u64, + frame_buffer_size: frame_buffer_size_bytes, + }; + let mode_box = Box::into_raw(Box::new(mode)); + + let protocol = GraphicsOutputProtocol { + query_mode, + set_mode, + blt, + mode: mode_box, + }; + + let protocol_raw = Box::into_raw(Box::new(protocol)); + let protocol = unsafe { &mut *protocol_raw }; + + Self { + protocol, + protocol_raw, + mode_box, + info_box, + width_pixels, + height_pixels, + frame_buffer_base_virtual_address, + frame_buffer_size_bytes, + stride_pixels, + } + } + + pub fn get_protocol(&self) -> *mut GraphicsOutputProtocol { + self.protocol_raw + } +} + +unsafe impl Send for GraphicsOutput {} +unsafe impl Sync for GraphicsOutput {} + +impl Drop for GraphicsOutput { + fn drop(&mut self) { + unsafe { + // Free in reverse construction order to avoid leaks. + drop(Box::from_raw(self.protocol_raw)); + drop(Box::from_raw(self.mode_box)); + drop(Box::from_raw(self.info_box)); + } + } +} + +#[inline] +fn with_graphics_output(_this: *const GraphicsOutputProtocol, f: F) -> Option +where + F: FnOnce(&GraphicsOutput) -> R, +{ + GRAPHICS_OUTPUT.get().map(|guard| { + let lock = guard.lock(); + f(&*lock) + }) +} + +pub fn init_graphics_output() { + #[cfg(feature = "display")] + { + let display_info = axdisplay::framebuffer_info(); + info!("Graphics Output Protocol initialized: {:?}", display_info); + + let frame_buffer_base_virtual_address = display_info.fb_base_vaddr; + let frame_buffer_size_bytes = display_info.fb_size; + + // Paint the framebuffer white for a quick visual check. + unsafe { + core::ptr::write_bytes( + frame_buffer_base_virtual_address as *mut u8, + 0xFF, + frame_buffer_size_bytes, + ); + } + axdisplay::framebuffer_flush(); + + GRAPHICS_OUTPUT.init_once(Mutex::new(GraphicsOutput::new( + display_info.width, + display_info.height, + display_info.fb_base_vaddr, + display_info.fb_size, + // Note: on many devices, stride (pixels per scan line) differs from width. + // Here we temporarily set it to width. + display_info.width, + ))); + } +} + +pub unsafe extern "efiapi" fn query_mode( + this: *const GraphicsOutputProtocol, + mode_number: u32, + size_of_info_out: *mut usize, + info_out: *mut *const GraphicsOutputModeInformation, +) -> Status { + if mode_number != 0 { + return Status::UNSUPPORTED; + } + if size_of_info_out.is_null() || info_out.is_null() { + return Status::INVALID_PARAMETER; + } + + unsafe { + match with_graphics_output(this, |go| (go.mode_box, go.info_box)) { + Some((mode_box, info_box)) => { + let mode = &*mode_box; + *size_of_info_out = mode.size_of_info; + *info_out = info_box as *const GraphicsOutputModeInformation; + Status::SUCCESS + } + None => Status::DEVICE_ERROR, + } + } +} + +pub unsafe extern "efiapi" fn set_mode( + this: *mut GraphicsOutputProtocol, + mode_number: u32, +) -> Status { + if mode_number != 0 { + return Status::UNSUPPORTED; + } + match with_graphics_output(this, |go| go.mode_box) { + Some(mode_box) => { + let mode = unsafe { &mut *mode_box }; + mode.mode = 0; + // If resolution switching is added in the future, update info/stride/framebuffer here. + Status::SUCCESS + } + None => Status::DEVICE_ERROR, + } +} + +#[cfg(feature = "display")] +pub unsafe extern "efiapi" fn blt( + this: *mut GraphicsOutputProtocol, + blt_buffer: *mut GraphicsOutputBltPixel, + blt_operation: GraphicsOutputBltOperation, + source_x: usize, + source_y: usize, + destination_x: usize, + destination_y: usize, + width_pixels: usize, + height_pixels: usize, + blt_buffer_delta_bytes: usize, +) -> Status { + let Some(( + frame_buffer_base_virtual_address, + _frame_buffer_size_bytes, + screen_width_pixels, + screen_height_pixels, + stride_pixels, + )) = with_graphics_output(this, |go| { + ( + go.frame_buffer_base_virtual_address, + go.frame_buffer_size_bytes, + go.width_pixels as usize, + go.height_pixels as usize, + go.stride_pixels as usize, + ) + }) + else { + return Status::DEVICE_ERROR; + }; + + if width_pixels == 0 || height_pixels == 0 { + return Status::SUCCESS; + } + + if destination_x >= screen_width_pixels + || destination_y >= screen_height_pixels + || destination_x + width_pixels > screen_width_pixels + || destination_y + height_pixels > screen_height_pixels + { + return Status::INVALID_PARAMETER; + } + + const BYTES_PER_PIXEL: usize = 4; + let frame_buffer_pitch_bytes = stride_pixels * BYTES_PER_PIXEL; + let frame_buffer_ptr = frame_buffer_base_virtual_address as *mut u8; + + unsafe { + match blt_operation { + GraphicsOutputBltOperation::BLT_VIDEO_FILL => { + // Fill (destination_x, destination_y, width, height) with blt_buffer[0]. + if blt_buffer.is_null() { + return Status::INVALID_PARAMETER; + } + let px = *blt_buffer; + let color = [px.blue, px.green, px.red, 0]; // BGRA + for row in 0..height_pixels { + let row_ptr = frame_buffer_ptr.add( + (destination_y + row) * frame_buffer_pitch_bytes + + destination_x * BYTES_PER_PIXEL, + ); + for col in 0..width_pixels { + let p = row_ptr.add(col * BYTES_PER_PIXEL); + *p.add(0) = color[0]; + *p.add(1) = color[1]; + *p.add(2) = color[2]; + *p.add(3) = color[3]; + } + } + axdisplay::framebuffer_flush(); + Status::SUCCESS + } + + GraphicsOutputBltOperation::BLT_VIDEO_TO_BLT_BUFFER => { + if blt_buffer.is_null() { + return Status::INVALID_PARAMETER; + } + // BLT buffer line stride: if Delta == 0, it is tightly packed (width * 4). + let destination_pitch_bytes = if blt_buffer_delta_bytes == 0 { + width_pixels * BYTES_PER_PIXEL + } else { + blt_buffer_delta_bytes + }; + + for row in 0..height_pixels { + // Source: read from framebuffer at (source_x, source_y + row). + let src = frame_buffer_ptr.add( + (source_y + row) * frame_buffer_pitch_bytes + source_x * BYTES_PER_PIXEL, + ); + // Destination: write into BLT buffer at (destination_x, destination_y + row). + let dst = (blt_buffer as *mut u8).add( + (destination_y + row) * destination_pitch_bytes + + destination_x * BYTES_PER_PIXEL, + ); + core::ptr::copy_nonoverlapping(src, dst, width_pixels * BYTES_PER_PIXEL); + } + Status::SUCCESS + } + + GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO => { + if blt_buffer.is_null() { + return Status::INVALID_PARAMETER; + } + // In GOP, Delta is bytes per scan line in the BLT buffer; 0 means tightly packed. + let source_pitch_bytes = if blt_buffer_delta_bytes == 0 { + width_pixels * BYTES_PER_PIXEL + } else { + blt_buffer_delta_bytes + }; + + for row in 0..height_pixels { + let dst = frame_buffer_ptr.add( + (destination_y + row) * frame_buffer_pitch_bytes + + destination_x * BYTES_PER_PIXEL, + ); + let src = (blt_buffer as *const u8) + .add((source_y + row) * source_pitch_bytes + source_x * BYTES_PER_PIXEL); + core::ptr::copy_nonoverlapping(src, dst, width_pixels * BYTES_PER_PIXEL); + } + axdisplay::framebuffer_flush(); + Status::SUCCESS + } + + GraphicsOutputBltOperation::BLT_VIDEO_TO_VIDEO => { + // Internal framebuffer transfer; overlapping is allowed (memmove semantics). + let source_x_pixels = source_x; + let source_y_pixels = source_y; + if source_x_pixels >= screen_width_pixels + || source_y_pixels >= screen_height_pixels + || source_x_pixels + width_pixels > screen_width_pixels + || source_y_pixels + height_pixels > screen_height_pixels + { + return Status::INVALID_PARAMETER; + } + for row in 0..height_pixels { + let dst = frame_buffer_ptr.add( + (destination_y + row) * frame_buffer_pitch_bytes + + destination_x * BYTES_PER_PIXEL, + ); + let src = frame_buffer_ptr.add( + (source_y_pixels + row) * frame_buffer_pitch_bytes + + source_x_pixels * BYTES_PER_PIXEL, + ); + core::ptr::copy(src, dst, width_pixels * BYTES_PER_PIXEL); + } + axdisplay::framebuffer_flush(); + Status::SUCCESS + } + + _ => Status::UNSUPPORTED, + } + } +} + +#[cfg(not(feature = "display"))] +pub unsafe extern "efiapi" fn blt( + _this: *mut GraphicsOutputProtocol, + _blt_buffer: *mut GraphicsOutputBltPixel, + _blt_operation: GraphicsOutputBltOperation, + _source_x: usize, + _source_y: usize, + _destination_x: usize, + _destination_y: usize, + _width_pixels: usize, + _height_pixels: usize, + _blt_buffer_delta_bytes: usize, +) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/protocol/mod.rs b/src/runtime/protocol/mod.rs index 2a17d3a..e33a787 100644 --- a/src/runtime/protocol/mod.rs +++ b/src/runtime/protocol/mod.rs @@ -1,4 +1,6 @@ +pub(crate) mod block_io; +pub(crate) mod device; +pub(crate) mod fs; pub(crate) mod graphics_output; pub(crate) mod handle; -pub(crate) mod simple_file_system; pub(crate) mod simple_text_output; diff --git a/src/runtime/protocol/simple_file_system.rs b/src/runtime/protocol/simple_file_system.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/runtime/protocol/simple_file_system.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/runtime/service/boot_service.rs b/src/runtime/service/boot_service.rs index 8b13789..db019ff 100644 --- a/src/runtime/service/boot_service.rs +++ b/src/runtime/service/boot_service.rs @@ -1 +1,442 @@ +use core::{ffi::c_void, ptr}; +use axhal::mem::PhysAddr; +use uefi_raw::{ + Boolean, Char16, Event, Guid, Handle, PhysicalAddress, Status, + protocol::device_path::DevicePathProtocol, + table::boot::{ + EventNotifyFn, EventType, InterfaceType, MemoryDescriptor, MemoryType, + OpenProtocolInformationEntry, TimerDelay, Tpl, + }, +}; + +use alloc::boxed::Box; + +use crate::runtime::service::memory::AllocateType; + +#[derive(Debug)] +pub struct Boot { + services: &'static mut uefi_raw::table::boot::BootServices, + services_raw: *mut uefi_raw::table::boot::BootServices, +} + +impl Boot { + pub fn new() -> Self { + let services = uefi_raw::table::boot::BootServices { + header: Default::default(), + raise_tpl, + restore_tpl, + allocate_pages, + free_pages, + get_memory_map, + allocate_pool, + free_pool, + create_event, + set_timer, + wait_for_event, + signal_event, + close_event, + check_event, + install_protocol_interface, + reinstall_protocol_interface, + uninstall_protocol_interface, + handle_protocol, + reserved: core::ptr::null_mut::(), + register_protocol_notify, + locate_handle, + locate_device_path, + install_configuration_table, + load_image, + start_image, + exit, + unload_image, + exit_boot_services, + get_next_monotonic_count, + stall, + set_watchdog_timer, + connect_controller, + disconnect_controller, + open_protocol, + close_protocol, + open_protocol_information, + protocols_per_handle, + locate_handle_buffer, + locate_protocol, + install_multiple_protocol_interfaces, + uninstall_multiple_protocol_interfaces, + calculate_crc32, + copy_mem, + set_mem, + create_event_ex, // UEFI 2.0+ + }; + let services_raw = Box::into_raw(Box::new(services)); + let services = unsafe { &mut *services_raw }; + Boot { + services, + services_raw, + } + } + + pub fn get_services(&self) -> *mut uefi_raw::table::boot::BootServices { + self.services_raw + } +} + +unsafe impl Send for Boot {} +unsafe impl Sync for Boot {} + +impl Drop for Boot { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.services_raw)); + } + } +} + +// Task Priority services +pub unsafe extern "efiapi" fn raise_tpl(_new_tpl: Tpl) -> Tpl { + Tpl::APPLICATION +} +pub unsafe extern "efiapi" fn restore_tpl(_old_tpl: Tpl) {} + +// Memory allocation functions +pub unsafe extern "efiapi" fn allocate_pages( + alloc_ty: u32, + mem_ty: MemoryType, + count: usize, + addr: *mut PhysicalAddress, +) -> Status { + let alloc_ty = match AllocateType::try_from(alloc_ty) { + Ok(t) => t, + Err(_) => return Status::INVALID_PARAMETER, + }; + + let ptr = crate::runtime::service::memory::alloc_pages(alloc_ty, mem_ty, count); + if ptr.is_null() { + return Status::OUT_OF_RESOURCES; + } + + unsafe { *addr = ptr as u64 }; + Status::SUCCESS +} +pub unsafe extern "efiapi" fn free_pages(addr: PhysicalAddress, pages: usize) -> Status { + let phys_addr = PhysAddr::from_usize(addr.try_into().unwrap()); + let _ = crate::runtime::service::memory::free_pages(phys_addr, pages); + + Status::SUCCESS +} +pub unsafe extern "efiapi" fn get_memory_map( + _size: *mut usize, + _map: *mut MemoryDescriptor, + _key: *mut usize, + _desc_size: *mut usize, + _desc_version: *mut u32, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn allocate_pool( + _pool_type: MemoryType, + _size: usize, + _buffer: *mut *mut u8, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn free_pool(_buffer: *mut u8) -> Status { + Status::UNSUPPORTED +} + +// Event & timer functions +pub unsafe extern "efiapi" fn create_event( + _ty: EventType, + _notify_tpl: Tpl, + _notify_func: Option, + _notify_ctx: *mut c_void, + _out_event: *mut Event, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn set_timer( + _event: Event, + _ty: TimerDelay, + _trigger_time: u64, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn wait_for_event( + _number_of_events: usize, + _events: *mut Event, + _out_index: *mut usize, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn signal_event(_event: Event) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn close_event(_event: Event) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn check_event(_event: Event) -> Status { + Status::UNSUPPORTED +} + +// Protocol handlers +pub unsafe extern "efiapi" fn install_protocol_interface( + _handle: *mut Handle, + _guid: *const Guid, + _interface_type: InterfaceType, + _interface: *const c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn reinstall_protocol_interface( + _handle: Handle, + _protocol: *const Guid, + _old_interface: *const c_void, + _new_interface: *const c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn uninstall_protocol_interface( + _handle: Handle, + _protocol: *const Guid, + _interface: *const c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn handle_protocol( + _handle: Handle, + _proto: *const Guid, + _out_proto: *mut *mut c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn register_protocol_notify( + _protocol: *const Guid, + _event: Event, + _registration: *mut *const c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn locate_handle( + _search_ty: i32, + _proto: *const Guid, + _key: *const c_void, + _buf_sz: *mut usize, + _buf: *mut Handle, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn locate_device_path( + _proto: *const Guid, + _device_path: *mut *const DevicePathProtocol, + _out_handle: *mut Handle, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn install_configuration_table( + _guid_entry: *const Guid, + _table_ptr: *const c_void, +) -> Status { + Status::UNSUPPORTED +} + +// Image services +pub unsafe extern "efiapi" fn load_image( + _boot_policy: Boolean, + _parent_image_handle: Handle, + _device_path: *const DevicePathProtocol, + _source_buffer: *const u8, + _source_size: usize, + _image_handle: *mut Handle, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn start_image( + _image_handle: Handle, + _exit_data_size: *mut usize, + _exit_data: *mut *mut Char16, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn exit( + _image_handle: Handle, + _exit_status: Status, + _exit_data_size: usize, + _exit_data: *mut Char16, +) -> ! { + loop {} +} +pub unsafe extern "efiapi" fn unload_image(_image_handle: Handle) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn exit_boot_services(_image_handle: Handle, _map_key: usize) -> Status { + Status::UNSUPPORTED +} + +// Misc services +pub unsafe extern "efiapi" fn get_next_monotonic_count(_count: *mut u64) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn stall(_microseconds: usize) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn set_watchdog_timer( + _timeout: usize, + _watchdog_code: u64, + _data_size: usize, + _watchdog_data: *const u16, +) -> Status { + Status::UNSUPPORTED +} + +// Driver support +pub unsafe extern "efiapi" fn connect_controller( + _controller: Handle, + _driver_image: Handle, + _remaining_device_path: *const DevicePathProtocol, + _recursive: Boolean, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn disconnect_controller( + _controller: Handle, + _driver_image: Handle, + _child: Handle, +) -> Status { + Status::UNSUPPORTED +} + +// Protocol open/close +pub unsafe extern "efiapi" fn open_protocol( + _handle: Handle, + _protocol: *const Guid, + _interface: *mut *mut c_void, + _agent_handle: Handle, + _controller_handle: Handle, + _attributes: u32, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn close_protocol( + _handle: Handle, + _protocol: *const Guid, + _agent_handle: Handle, + _controller_handle: Handle, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn open_protocol_information( + _handle: Handle, + _protocol: *const Guid, + _entry_buffer: *mut *const OpenProtocolInformationEntry, + _entry_count: *mut usize, +) -> Status { + Status::UNSUPPORTED +} + +// Library services +pub unsafe extern "efiapi" fn protocols_per_handle( + _handle: Handle, + _protocol_buffer: *mut *mut *const Guid, + _protocol_buffer_count: *mut usize, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn locate_handle_buffer( + _search_ty: i32, + _proto: *const Guid, + _key: *const c_void, + _no_handles: *mut usize, + _buf: *mut *mut Handle, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "efiapi" fn locate_protocol( + _proto: *const Guid, + _registration: *mut c_void, + _out_proto: *mut *mut c_void, +) -> Status { + Status::UNSUPPORTED +} +pub unsafe extern "C" fn install_multiple_protocol_interfaces(_handle: *mut Handle, ...) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "C" fn uninstall_multiple_protocol_interfaces(_handle: Handle, ...) -> Status { + Status::UNSUPPORTED +} + +// CRC / memory +const CRC32_TABLE: [u32; 256] = { + const P: u32 = 0xEDB8_8320; + let mut tbl = [0u32; 256]; + let mut i = 0usize; + while i < 256 { + let mut c = i as u32; + let mut j = 0; + while j < 8 { + // reflected step + c = if (c & 1) != 0 { (c >> 1) ^ P } else { c >> 1 }; + j += 1; + } + tbl[i] = c; + i += 1; + } + tbl +}; + +pub unsafe extern "efiapi" fn calculate_crc32( + data: *const c_void, + data_size: usize, + crc32_out: *mut u32, +) -> Status { + // Parameter validation + if crc32_out.is_null() || (data.is_null() && data_size > 0) { + return Status::INVALID_PARAMETER; + } + + if data_size == 0 { + // Zero-length -> 0 + unsafe { *crc32_out = 0 }; + return Status::SUCCESS; + } + + let bytes = unsafe { core::slice::from_raw_parts(data as *const u8, data_size) }; + + // Reflected algorithm with init/final XOR + let mut crc: u32 = 0xFFFF_FFFF; + for &b in bytes { + let idx = ((crc ^ (b as u32)) & 0xFF) as usize; + crc = (crc >> 8) ^ CRC32_TABLE[idx]; + } + crc ^= 0xFFFF_FFFF; + + unsafe { *crc32_out = crc }; + Status::SUCCESS +} + +pub unsafe extern "efiapi" fn copy_mem(dest: *mut u8, src: *const u8, len: usize) { + if len == 0 || dest.is_null() || src.is_null() { + return; + } + unsafe { ptr::copy(src, dest, len) }; +} + +pub unsafe extern "efiapi" fn set_mem(buffer: *mut u8, len: usize, value: u8) { + if len == 0 || buffer.is_null() { + return; + } + unsafe { ptr::write_bytes(buffer, value, len) }; +} + +// New event (UEFI 2.0+) +pub unsafe extern "efiapi" fn create_event_ex( + _ty: EventType, + _notify_tpl: Tpl, + _notify_fn: Option, + _notify_ctx: *mut c_void, + _event_group: *mut Guid, + _out_event: *mut Event, +) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/service/image_service.rs b/src/runtime/service/image_service.rs deleted file mode 100644 index 55a3c17..0000000 --- a/src/runtime/service/image_service.rs +++ /dev/null @@ -1 +0,0 @@ -pub fn init_image_service() {} diff --git a/src/runtime/service/memory.rs b/src/runtime/service/memory.rs new file mode 100644 index 0000000..2c8815e --- /dev/null +++ b/src/runtime/service/memory.rs @@ -0,0 +1,66 @@ +use alloc::vec::Vec; +use axhal::{ + mem::{MemoryAddr, PhysAddr, VirtAddr}, + paging::MappingFlags, +}; +use axsync::Mutex; +use uefi_raw::table::boot::MemoryType; + +static ALLOCATED_PAGES: Mutex> = Mutex::new(Vec::new()); + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum AllocateType { + AnyPages = 0, // AllocateAnyPages + MaxAddress = 1, // AllocateMaxAddress + Address = 2, // AllocateAddress +} + +impl TryFrom for AllocateType { + type Error = (); + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(AllocateType::AnyPages), + 1 => Ok(AllocateType::MaxAddress), + 2 => Ok(AllocateType::Address), + _ => Err(()), + } + } +} + +impl From for u32 { + fn from(v: AllocateType) -> u32 { + v as u32 + } +} + +pub fn alloc_pages(_alloc_type: AllocateType, _memory_type: MemoryType, count: usize) -> *mut u8 { + let layout = core::alloc::Layout::from_size_align(count * 4096, 4096) + .expect("Invalid layout for allocate_pages"); + let ptr = axalloc::global_allocator() + .alloc(layout) + .expect("Failed to allocate pages for EFI") + .as_ptr(); + + let page_count = (layout.size() + 4095) / 4096; + + axmm::kernel_aspace() + .lock() + .protect( + VirtAddr::from_ptr_of(ptr).align_down(4096usize), + page_count * 4096, + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + ) + .expect("Failed to protect EFI memory"); + + ptr +} + +pub fn free_pages(_addr: PhysAddr, _page: usize) {} + +pub fn allocate_pool(_memory_type: MemoryType, _size: usize) -> *mut u8 { + core::ptr::null_mut() +} + +pub fn free_pool(_buffer: *mut u8) {} diff --git a/src/runtime/service/mod.rs b/src/runtime/service/mod.rs index 2a25b55..ce0ea7c 100644 --- a/src/runtime/service/mod.rs +++ b/src/runtime/service/mod.rs @@ -1,3 +1,33 @@ +use axsync::Mutex; +use lazyinit::LazyInit; +use uefi_raw::table::{boot::BootServices, runtime::RuntimeServices}; + +use crate::runtime::service::{boot_service::Boot, runtime_service::Runtime}; + pub(crate) mod boot_service; -pub(crate) mod image_service; +pub(crate) mod memory; pub(crate) mod runtime_service; + +static BOOT_SERVICE: LazyInit> = LazyInit::new(); +static RUNTIME_SERVICE: LazyInit> = LazyInit::new(); + +pub(crate) fn init_service() { + BOOT_SERVICE.init_once(Mutex::new(Boot::new())); + RUNTIME_SERVICE.init_once(Mutex::new(Runtime::new())); +} + +pub fn get_boot_service() -> *mut BootServices { + BOOT_SERVICE + .get() + .expect("BootService not initialized") + .lock() + .get_services() +} + +pub fn get_runtime_service() -> *mut RuntimeServices { + RUNTIME_SERVICE + .get() + .expect("RuntimeService not initialized") + .lock() + .get_services() +} diff --git a/src/runtime/service/runtime_service.rs b/src/runtime/service/runtime_service.rs index 8b13789..6aa9ae6 100644 --- a/src/runtime/service/runtime_service.rs +++ b/src/runtime/service/runtime_service.rs @@ -1 +1,173 @@ +use core::ffi::c_void; +use uefi_raw::{ + Char16, Guid, PhysicalAddress, Status, + capsule::CapsuleHeader, + table::{ + boot::MemoryDescriptor, + runtime::{ResetType, TimeCapabilities, VariableAttributes}, + }, + time::Time, +}; +use alloc::boxed::Box; + +#[derive(Debug)] +pub struct Runtime { + services: &'static mut uefi_raw::table::runtime::RuntimeServices, + services_raw: *mut uefi_raw::table::runtime::RuntimeServices, +} + +impl Runtime { + pub fn new() -> Self { + let services = uefi_raw::table::runtime::RuntimeServices { + header: Default::default(), + get_time, + set_time, + get_wakeup_time, + set_wakeup_time, + set_virtual_address_map, + convert_pointer, + get_variable, + get_next_variable_name, + set_variable, + reset_system, + update_capsule, + query_capsule_capabilities, + query_variable_info, + get_next_high_monotonic_count, + }; + let services_raw = Box::into_raw(Box::new(services)); + let services = unsafe { &mut *services_raw }; + Self { + services, + services_raw, + } + } + + pub fn get_services(&self) -> *mut uefi_raw::table::runtime::RuntimeServices { + self.services_raw + } +} + +unsafe impl Send for Runtime {} +unsafe impl Sync for Runtime {} + +impl Drop for Runtime { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.services_raw)); + } + } +} + +// Time services +pub unsafe extern "efiapi" fn get_time( + _time: *mut Time, + _capabilities: *mut TimeCapabilities, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn set_time(_time: *const Time) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn get_wakeup_time( + _enabled: *mut u8, + _pending: *mut u8, + _time: *mut Time, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn set_wakeup_time(_enable: u8, _time: *const Time) -> Status { + Status::UNSUPPORTED +} + +// Virtual memory services +pub unsafe extern "efiapi" fn set_virtual_address_map( + _map_size: usize, + _desc_size: usize, + _desc_version: u32, + _virtual_map: *mut MemoryDescriptor, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn convert_pointer( + _debug_disposition: usize, + _address: *mut *const c_void, +) -> Status { + Status::UNSUPPORTED +} + +// Variable services +pub unsafe extern "efiapi" fn get_variable( + _variable_name: *const Char16, + _vendor_guid: *const Guid, + _attributes: *mut VariableAttributes, + _data_size: *mut usize, + _data: *mut u8, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn get_next_variable_name( + _variable_name_size: *mut usize, + _variable_name: *mut u16, + _vendor_guid: *mut Guid, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn set_variable( + _variable_name: *const Char16, + _vendor_guid: *const Guid, + _attributes: VariableAttributes, + _data_size: usize, + _data: *const u8, +) -> Status { + Status::UNSUPPORTED +} + +// Misc services +pub unsafe extern "efiapi" fn get_next_high_monotonic_count(_high_count: *mut u32) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn reset_system( + _rt: ResetType, + _status: Status, + _data_size: usize, + _data: *const u8, +) -> ! { + loop {} +} + +// Capsule services +pub unsafe extern "efiapi" fn update_capsule( + _capsule_header_array: *const *const CapsuleHeader, + _capsule_count: usize, + _scatter_gather_list: PhysicalAddress, +) -> Status { + Status::UNSUPPORTED +} + +pub unsafe extern "efiapi" fn query_capsule_capabilities( + _capsule_header_array: *const *const CapsuleHeader, + _capsule_count: usize, + _maximum_capsule_size: *mut u64, + _reset_type: *mut ResetType, +) -> Status { + Status::UNSUPPORTED +} + +// Variable info +pub unsafe extern "efiapi" fn query_variable_info( + _attributes: VariableAttributes, + _maximum_variable_storage_size: *mut u64, + _remaining_variable_storage_size: *mut u64, + _maximum_variable_size: *mut u64, +) -> Status { + Status::UNSUPPORTED +} diff --git a/src/runtime/table.rs b/src/runtime/system_table.rs similarity index 67% rename from src/runtime/table.rs rename to src/runtime/system_table.rs index 4c64a78..26a51e0 100644 --- a/src/runtime/table.rs +++ b/src/runtime/system_table.rs @@ -4,8 +4,17 @@ use axsync::Mutex; use lazyinit::LazyInit; use uefi_raw::table::{Header, configuration::ConfigurationTable, system::SystemTable}; -use crate::runtime::protocol::simple_text_output::{ - get_simple_text_output, init_simple_text_output, +use crate::runtime::{ + protocol::{ + block_io::init_block_io, + device::{ + device_path_from_text::init_device_path_from_text, + device_path_to_text::init_device_path_to_text, + device_path_utilities::init_device_path_uttilities, + }, + simple_text_output::{get_simple_text_output, init_simple_text_output}, + }, + service::{get_boot_service, get_runtime_service}, }; use alloc::boxed::Box; @@ -39,19 +48,37 @@ static VENDOR: &[u16] = &[ static REVERSION: u32 = 0x0001_0000; pub fn init_system_table() { + // Initialize the tools. + init_device_path_from_text(); + init_device_path_to_text(); + init_device_path_uttilities(); + + init_block_io(); + + #[cfg(feature = "display")] + crate::runtime::protocol::graphics_output::init_graphics_output(); + #[cfg(feature = "fs")] + crate::runtime::protocol::fs::simple_file_system::init_simple_file_system(); + let simple_text_output = { init_simple_text_output(); get_simple_text_output().lock().get_protocol() }; + // Initialize the services + crate::runtime::service::init_service(); + let runtime_services = get_runtime_service(); + let boot_services = get_boot_service(); + + // Initialize the * table let configuration_table = Box::new(ConfigurationTable { vendor_guid: uefi_raw::Guid::default(), vendor_table: null_mut(), }); - let configuration_table_raw = Box::into_raw(configuration_table); + let configuration_table = Box::into_raw(configuration_table); let system_table = Box::new(SystemTable { - // Build the UEFI Table Header. + // Build the UEFI Table Header. // For the System Table, its signature is 'IBI SYST' (little-endian). // The Header size is the size of the entire Header structure, // and the CRC32 calculation will first fill the CRC32 field with 0 before calculation. @@ -69,11 +96,11 @@ pub fn init_system_table() { stderr_handle: null_mut(), stderr: simple_text_output, - runtime_services: null_mut(), - boot_services: null_mut(), + runtime_services, + boot_services, number_of_configuration_table_entries: 0, - configuration_table: configuration_table_raw, + configuration_table, }); let system_table_raw = Box::into_raw(system_table); let system_table = unsafe { &mut *system_table_raw }; diff --git a/src/runtime/utils.rs b/src/runtime/utils.rs new file mode 100644 index 0000000..35c91c1 --- /dev/null +++ b/src/runtime/utils.rs @@ -0,0 +1,64 @@ +use alloc::format; +use alloc::{string::String, string::ToString, vec::Vec}; + +/// Convert a UEFI CHAR16* (NUL-terminated) to a Rust UTF-8 String. +/// Returns None if the pointer is NULL or decoding fails. +pub unsafe fn utf16_cstr_to_string(p: *const uefi_raw::Char16) -> Option { + if p.is_null() { + return None; + } + // Count length until NUL terminator + let mut len = 0usize; + loop { + let ch = unsafe { *p.add(len) }; + if ch == 0 { + break; + } + len += 1; + } + let slice = unsafe { core::slice::from_raw_parts(p as *const u16, len) }; + String::from_utf16(slice).ok() +} + +/// Normalize a UEFI-style path: +/// - Uses backslash '\' as separator +/// - If name starts with '\' → absolute path from volume root +/// - Otherwise path is relative to `base` +/// - Handles empty string, ".", ".." +/// - Root directory is represented as "\" (a single backslash) +pub fn normalize_uefi_path(base: &str, name: &str) -> String { + if name.is_empty() || name == "." { + return base.to_string(); + } + + let combined = if name.starts_with('\\') { + name.to_string() + } else if base == "\\" { + format!("\\{name}") + } else if base.ends_with('\\') { + format!("{base}{name}") + } else { + format!("{base}\\{name}") + }; + + // Split into components, remove ".", handle ".." + let mut parts: Vec<&str> = Vec::new(); + for seg in combined.split('\\') { + if seg.is_empty() || seg == "." { + continue; + } + if seg == ".." { + if !parts.is_empty() { + parts.pop(); + } + continue; + } + parts.push(seg); + } + + if parts.is_empty() { + "\\".to_string() + } else { + format!("\\{}", parts.join("\\")) + } +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index f9ff323..648489b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -12,7 +12,10 @@ const SPACE: u8 = b' '; const MAX_CMD_LEN: usize = 256; fn print_prompt() { - axlog::ax_print!("[Arceboot]: {}$ ", &crate::medium::virtio_disk::current_dir().unwrap()); + axlog::ax_print!( + "[Arceboot]: {}$ ", + &crate::medium::virtio_disk::current_dir().unwrap() + ); } pub fn shell_main() { diff --git a/src/shell/stdio.rs b/src/shell/stdio.rs index 7f95ec5..dfe7aa5 100644 --- a/src/shell/stdio.rs +++ b/src/shell/stdio.rs @@ -71,7 +71,7 @@ impl Stdin { if buf.is_empty() || read_len > 0 { return Ok(read_len); } - return Ok(0) + return Ok(0); } } diff --git a/tests/edk2-AllocatePage/AllocatePage.c b/tests/edk2-AllocatePage/AllocatePage.c new file mode 100644 index 0000000..ffb680e --- /dev/null +++ b/tests/edk2-AllocatePage/AllocatePage.c @@ -0,0 +1,128 @@ +#ifndef EFIAPI +#define EFIAPI +#endif + +/* basic integer aliases */ +typedef unsigned long long u64; +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +/* ---- Offsets (match the layout that produced SystemTable) ---- */ +enum { OFF_ST_CONOUT = 64 }; /* EfiSystemTable->conOut */ +enum { OFF_ST_BOOTSERVICES = 96 }; /* EfiSystemTable->bootServices */ +enum { OFF_CONOUT_OUTPUTSTRING = 8 };/* EfiSimpleTextOutputProtocol->output_string */ +enum { OFF_BS_ALLOCATEPAGES = 40 }; /* BootServices->AllocatePages */ + +/* AllocatePages() parameters */ +enum { + kAllocateAnyPages = 0, + kEfiBootServicesCode = 3 +}; + + +#define MAX_OUTPUT_CHARS 260 + +typedef u64 (*EfiTextString)(void* This, u16* String); +typedef u64 (*EfiAllocatePages)(u32 Type, u32 MemoryType, u64 Pages, u64* Memory); + +/* + * Ensure instruction cache is synchronized with recently written code. + * On RISC-V, `fence.i` flushes the instruction pipeline and makes sure + * subsequent instruction fetches observe the updated memory contents. + */ +static inline void fence_i(void) { __asm__ __volatile__("fence.i" ::: "memory"); } + +static void mem_copy(void* dst, const void* src, u64 n) { + u8* d = (u8*)dst; + const u8* s = (const u8*)src; + while (n--) *d++ = *s++; +} + +/* ASCII -> UTF-16 (Char16) and print via ConOut->OutputString */ +static void put_ascii(void* SystemTable, const char* s) { + if (!SystemTable || !s) return; + + void* con_out = *(void**)((u8*)SystemTable + OFF_ST_CONOUT); + if (!con_out) return; + + EfiTextString OutputString = *(EfiTextString*)((u8*)con_out + OFF_CONOUT_OUTPUTSTRING); + if (!OutputString) return; + + u16 buf[MAX_OUTPUT_CHARS]; + u32 i = 0; + for (; s[i] && i < (MAX_OUTPUT_CHARS - 1); ++i) buf[i] = (u16)(u8)s[i]; + buf[i] = 0; + + OutputString(con_out, buf); +} + +/* print hex64 with optional label, ends with CRLF */ +static void put_hex64(void* SystemTable, const char* label, u64 v) { + static const char hex[] = "0123456789ABCDEF"; + char tmp[2 + 16 + 2 + 1]; /* "0x" + 16 nybbles + "\r\n" + NUL */ + int p = 0; + + if (label) put_ascii(SystemTable, label); + + tmp[p++] = '0'; + tmp[p++] = 'x'; + for (int i = 15; i >= 0; --i) + tmp[p++] = hex[(unsigned)((v >> (i * 4)) & 0xF)]; + tmp[p++] = '\r'; + tmp[p++] = '\n'; + tmp[p] = 0; + + put_ascii(SystemTable, tmp); +} + +/* tiny payload: "ret" for RISC-V (jalr x0, x1, 0 -> 0x00008067) */ +static const u8 payload_ret_only[4] __attribute__((section(".payload"))) = { 0x67, 0x80, 0x00, 0x00 }; + +typedef u64 (*payload_entry_t)(u64, u64, u64, u64, u64); + +/* Entry: alloc exec pages, copy payload, fence.i, call, log, return */ +u64 EFIAPI _ModuleEntryPoint(void* ImageHandle, void* SystemTable) { + (void)ImageHandle; + + void* BootServices = *(void**)((u8*)SystemTable + OFF_ST_BOOTSERVICES); + if (!BootServices) return 1; + + EfiAllocatePages AllocatePages = *(EfiAllocatePages*)((u8*)BootServices + OFF_BS_ALLOCATEPAGES); + if (!AllocatePages) return 2; + + put_ascii(SystemTable, "[OK] C AllocatePages started\r\n"); + + u64 payload_size = (u64)sizeof(payload_ret_only); + if (payload_size == 0) { + put_ascii(SystemTable, "[ERR] payload_size=0\r\n"); + return 5; + } + + u64 pages = (payload_size + 0xFFFu) >> 12; + if (pages == 0) pages = 1; + + u64 exec = 0; + u64 st = AllocatePages(kAllocateAnyPages, kEfiBootServicesCode, pages, &exec); + if (st != 0 || exec == 0) { + put_hex64(SystemTable, "[ERR] AllocatePages st=", st); + return st ? st : 6; + } + + put_hex64(SystemTable, "[OK] exec_addr=", exec); + put_hex64(SystemTable, "[OK] pages =", pages); + + mem_copy((void*)(u64)exec, payload_ret_only, payload_size); + fence_i(); + + put_ascii(SystemTable, "[OK] calling payload...\r\n"); + + payload_entry_t entry = (payload_entry_t)(void*)(u64)exec; + u64 expected = 0xDEADBEEF12345678ull; + u64 ret = entry(expected, 0, 0, 0, 0); + + put_hex64(SystemTable, "[OK] payload_ret=", ret); + put_ascii(SystemTable, "[OK] done\r\n"); + + return 0; +} diff --git a/tests/edk2-AllocatePage/AllocatePage.dsc b/tests/edk2-AllocatePage/AllocatePage.dsc new file mode 100644 index 0000000..487003d --- /dev/null +++ b/tests/edk2-AllocatePage/AllocatePage.dsc @@ -0,0 +1,14 @@ +[Defines] + PLATFORM_NAME = AllocatePage + PLATFORM_GUID = 01234567-89ab-cdef-0123-456789abcdef + PLATFORM_VERSION = 1.0 + DSC_SPECIFICATION = 0x00010005 + OUTPUT_DIRECTORY = Build + SUPPORTED_ARCHITECTURES = RISCV64 + BUILD_TARGETS = DEBUG + +[Components] + AllocatePage/AllocatePage.inf + +[Packages] + MdePkg/MdePkg.dec diff --git a/tests/edk2-AllocatePage/AllocatePage.inf b/tests/edk2-AllocatePage/AllocatePage.inf new file mode 100644 index 0000000..6e59dc6 --- /dev/null +++ b/tests/edk2-AllocatePage/AllocatePage.inf @@ -0,0 +1,16 @@ +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = AllocatePage + FILE_GUID = 01234567-89ab-cdef-0123-456789abcdef + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = efi_main + +[Sources] + AllocatePage.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + diff --git a/tests/edk2-AllocatePage/payload.S b/tests/edk2-AllocatePage/payload.S new file mode 100644 index 0000000..cf3587e --- /dev/null +++ b/tests/edk2-AllocatePage/payload.S @@ -0,0 +1,22 @@ + .text + .p2align 2 + .global _start # entry symbol name for payload (not used by EFI; we will call by address) +_start: + # Convention: + # a0 = framebuffer base (u64) + # a1 = framebuffer size (u64) + # a2 = stride_pixels (u32) (optional) + # a3 = width (u32) (optional) + # a4 = height (u32) (optional) + + # Simple, position-independent loop: write 0xFF408DEB (32-bit) across framebuffer. + li t0, 0xff408deb # color constant (fits 32-bit) + mv t1, a0 # ptr = fb_base + add t2, a0, a1 # end = fb_base + fb_size + +1: sw t0, 0(t1) + addi t1, t1, 4 + bltu t1, t2, 1b + + # never return: infinite loop +2: j 2b diff --git a/tests/edk2-AllocatePage/payload_embed.S b/tests/edk2-AllocatePage/payload_embed.S new file mode 100644 index 0000000..09c5af2 --- /dev/null +++ b/tests/edk2-AllocatePage/payload_embed.S @@ -0,0 +1,21 @@ +// payload_embed.S — return-only payload; + + .section .payload, "ax", @progbits + .p2align 2 + .globl _payload_start + .globl _payload_end +_payload_start: + + .globl entry + .type entry, @function +entry: + add t0, a0, a1 + add t0, t0, a2 + add t0, t0, a3 + add t0, t0, a4 + li t1, 0x123456789ABCDEF0 + add a0, t0, t1 + ret + + .size entry, .-entry +_payload_end: