diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c405912..3f786d8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,6 +34,11 @@ jobs: toolchain: nightly - target: armv7-unknown-linux-gnueabihf toolchain: stable + # riscv32 is tier-3 + - target: riscv32gc-unknown-linux-gnu + toolchain: nightly + components: rust-src + build_std: true steps: - name: "Checkout" @@ -43,9 +48,13 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} + components: ${{ matrix.components || '' }} rustflags: "" + - name: "Add Rust target" + if: ${{ !matrix.build_std }} + run: rustup target add ${{ matrix.target }} + - name: "Make Mold the default linker" uses: rui314/setup-mold@v1 @@ -55,7 +64,9 @@ jobs: target: ${{ matrix.target }} - name: "Build" - run: cargo build --verbose + run: cargo build --verbose --target ${{ matrix.target }} ${{ matrix.build_std && '-Z build-std=core,alloc,panic_abort' || '' }} + # tier-3 targets have no prebuilt std and tests are arch-agnostic, so + # run them against the host toolchain instead of the cross target. - name: "Run tests" - run: cargo test --workspace --exclude microfetch --verbose + run: cargo test --workspace --exclude microfetch --verbose ${{ matrix.build_std && '--target x86_64-unknown-linux-gnu' || '' }} diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index 3148f94..01afbde 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -6,7 +6,7 @@ //! faster? //! //! Supports `x86_64`, `aarch64`, `riscv64`, `loongarch64`, `s390x`, -//! `powerpc64`, and `arm` (armv7) architectures. +//! `powerpc64`, `arm` (armv7), and `riscv32` architectures. #![no_std] #![cfg_attr(target_arch = "powerpc64", feature(asm_experimental_arch))] @@ -19,11 +19,12 @@ target_arch = "loongarch64", target_arch = "s390x", target_arch = "powerpc64", - target_arch = "arm" + target_arch = "arm", + target_arch = "riscv32" )))] compile_error!( "Unsupported architecture: only x86_64, aarch64, riscv64, loongarch64, \ - s390x, powerpc64, and arm are supported" + s390x, powerpc64, arm, and riscv32 are supported" ); // Per-arch syscall implementations live in their own module files. @@ -48,6 +49,9 @@ mod arch; #[cfg(target_arch = "arm")] #[path = "arm.rs"] mod arch; +#[cfg(target_arch = "riscv32")] +#[path = "riscv32.rs"] +mod arch; /// Copies `n` bytes from `src` to `dest`. /// @@ -281,7 +285,11 @@ pub unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { /// offsets on both architectures. Only the fields needed for disk usage are /// declared; the remainder of the 120-byte struct is covered by `_pad`. #[repr(C)] -#[cfg(not(any(target_arch = "s390x", target_arch = "arm")))] +#[cfg(not(any( + target_arch = "s390x", + target_arch = "arm", + target_arch = "riscv32" +)))] pub struct StatfsBuf { pub f_type: i64, pub f_bsize: i64, @@ -322,7 +330,7 @@ pub struct StatfsBuf { /// on armv7 `statfs64(2)` has 32-bit word fields; see /// https://github.com/torvalds/linux/blob/v6.19/include/uapi/asm-generic/statfs.h #[repr(C)] -#[cfg(target_arch = "arm")] +#[cfg(any(target_arch = "arm", target_arch = "riscv32"))] pub struct StatfsBuf { pub f_type: u32, pub f_bsize: u32, @@ -409,7 +417,7 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> Result { /// The layout matches the kernel's `struct sysinfo` *exactly*: /// `mem_unit` ends at offset 108, then 4 bytes of implicit padding to 112. #[repr(C)] -#[cfg(not(target_arch = "arm"))] +#[cfg(not(any(target_arch = "arm", target_arch = "riscv32")))] pub struct SysInfo { pub uptime: i64, pub loads: [u64; 3], @@ -433,7 +441,7 @@ pub struct SysInfo { /// on armv7 `__kernel_long_t` is 4 bytes; see /// https://github.com/torvalds/linux/blob/v6.19/include/uapi/linux/sysinfo.h #[repr(C)] -#[cfg(target_arch = "arm")] +#[cfg(any(target_arch = "arm", target_arch = "riscv32"))] pub struct SysInfo { pub uptime: i32, pub loads: [u32; 3], diff --git a/crates/asm/src/riscv32.rs b/crates/asm/src/riscv32.rs new file mode 100644 index 0000000..1e4b29a --- /dev/null +++ b/crates/asm/src/riscv32.rs @@ -0,0 +1,142 @@ +//! Syscall implementations for `riscv32`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 56i32, // SYS_openat + in("a0") -100i32, // AT_FDCWD + in("a1") path, + in("a2") flags, + in("a3") 0i32, // mode + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 63i32, // SYS_read + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 64i32, // SYS_write + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 57i32, // SYS_close + in("a0") fd, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 160i32, // SYS_uname + in("a0") buf, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + // asm-generic __NR_statfs routes to sys_statfs64 on 32-bit: 3-arg form. + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 43i32, // __NR_statfs + in("a0") path, + in("a1") core::mem::size_of::(), + in("a2") buf, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 179_i32, // __NR_sysinfo + in("a0") info, + lateout("a0") ret, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 123i32, // __NR_sched_getaffinity + in("a0") pid, + in("a1") mask_size, + in("a2") mask, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "ecall", + in("a7") 93i32, // SYS_exit + in("a0") code, + options(noreturn, nostack) + ); + } +} diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index 7f6a4cb..68a7bbf 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -154,6 +154,20 @@ unsafe extern "C" fn _start() { ); } +#[cfg(target_arch = "riscv32")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mv a0, sp", // first arg = original sp (argc/argv) + "andi sp, sp, -16", // align sp to 16 bytes + "call {entry_rust}", + "li a7, 93", // SYS_exit + "ecall", + entry_rust = sym entry_rust, + ); +} + // Global allocator #[global_allocator] static ALLOCATOR: BumpAllocator = BumpAllocator::new();