diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6ff6843..75e3064 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,12 +16,22 @@ jobs: strategy: fail-fast: false matrix: - target: - - x86_64-unknown-linux-gnu - - aarch64-unknown-linux-gnu - - riscv64gc-unknown-linux-gnu - - loongarch64-unknown-linux-gnu - - s390x-unknown-linux-gnu + include: + - target: x86_64-unknown-linux-gnu + toolchain: stable + - target: aarch64-unknown-linux-gnu + toolchain: stable + - target: riscv64gc-unknown-linux-gnu + toolchain: stable + - target: loongarch64-unknown-linux-gnu + toolchain: stable + - target: s390x-unknown-linux-gnu + toolchain: stable + # powerpc64 uses experimental asm features + - target: powerpc64le-unknown-linux-gnu + toolchain: nightly + - target: powerpc64-unknown-linux-gnu + toolchain: nightly steps: - name: "Checkout" @@ -30,6 +40,7 @@ jobs: - name: "Setup Rust toolchain" uses: actions-rust-lang/setup-rust-toolchain@v1 with: + toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} rustflags: "" diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index 92cc420..238cd69 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -5,10 +5,11 @@ //! What do you mean I wasted two whole hours to make the program only 100µs //! faster? //! -//! Supports `x86_64`, `aarch64`, `riscv64`, `loongarch64`, and `s390x` -//! architectures. +//! Supports `x86_64`, `aarch64`, `riscv64`, `loongarch64`, `s390x`, and +//! `powerpc64` architectures. #![no_std] +#![cfg_attr(target_arch = "powerpc64", feature(asm_experimental_arch))] // Ensure we're compiling for a supported architecture. #[cfg(not(any( @@ -16,11 +17,12 @@ target_arch = "aarch64", target_arch = "riscv64", target_arch = "loongarch64", - target_arch = "s390x" + target_arch = "s390x", + target_arch = "powerpc64" )))] compile_error!( - "Unsupported architecture: only x86_64, aarch64, riscv64, loongarch64, and \ - s390x are supported" + "Unsupported architecture: only x86_64, aarch64, riscv64, loongarch64, \ + s390x, and powerpc64 are supported" ); // Per-arch syscall implementations live in their own module files. @@ -39,6 +41,9 @@ mod arch; #[cfg(target_arch = "s390x")] #[path = "s390x.rs"] mod arch; +#[cfg(target_arch = "powerpc64")] +#[path = "powerpc64.rs"] +mod arch; /// Copies `n` bytes from `src` to `dest`. /// diff --git a/crates/asm/src/powerpc64.rs b/crates/asm/src/powerpc64.rs new file mode 100644 index 0000000..13e0b33 --- /dev/null +++ b/crates/asm/src/powerpc64.rs @@ -0,0 +1,200 @@ +//! Syscall implementations for `powerpc64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 5i64 => _, // SYS_open + inlateout("r3") path => ret, + inlateout("r4") flags => _, + inlateout("r5") 0i32 => _, // mode + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 3i64 => _, // SYS_read + inlateout("r3") fd as i64 => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 4i64 => _, // SYS_write + inlateout("r3") fd as i64 => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 6i64 => _, // SYS_close + inlateout("r3") fd as i64 => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 122i64 => _, // SYS_uname + inlateout("r3") buf => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 99i64 => _, // SYS_statfs + inlateout("r3") path => ret, + inlateout("r4") buf => _, + out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 116_i64 => _, // __NR_sysinfo + inlateout("r3") info => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 223i64 => _, // __NR_sched_getaffinity + inlateout("r3") pid as i64 => ret, + inlateout("r4") mask_size => _, + inlateout("r5") mask => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "li 0, 1", // SYS_exit + "sc", + in("r3") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/lib/src/cpu.rs b/crates/lib/src/cpu.rs index ebff676..c3e23bd 100644 --- a/crates/lib/src/cpu.rs +++ b/crates/lib/src/cpu.rs @@ -148,6 +148,7 @@ fn get_cpu_freq_mhz() -> Option { b"cpu MHz dynamic", b"cpu MHz static", b"CPU MHz", + b"clock", ] { if let Some(val) = extract_field(data, key) { // Parse integer part of the MHz value (e.g. "5200.00" -> 5200) diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index c3957df..b22f291 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![cfg_attr(target_arch = "powerpc64", feature(asm_experimental_arch))] extern crate alloc; @@ -88,6 +89,57 @@ unsafe extern "C" fn _start() { ); } +// ELFv2 (ppc64le / new BE): entry point is code directly, no function +// descriptor needed. +#[cfg(all(target_arch = "powerpc64", target_abi = "elfv2"))] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + // Set up TOC (r2) for ELFv2 by computing from current PC + "bl 0f", + "0: mflr 12", + "addis 2, 12, .TOC. - 0b@ha", + "addi 2, 2, .TOC. - 0b@l", + // Save sp as first arg, set up stack frame + "mr 3, 1", + "clrrdi 1, 1, 4", + "stdu 1, -64(1)", + "bl {entry_rust}", + "nop", + "li 0, 1", + "sc", + entry_rust = sym entry_rust, + ); +} + +// On ELFv1, the ELF entry point must be a function descriptor +// in .opd whose first word is the real code address. We emit both manually via +// `global_asm` so the kernel reads the descriptor and jumps into `_start_impl`. +#[cfg(all(target_arch = "powerpc64", target_abi = "elfv1"))] +core::arch::global_asm!( + ".section .opd, \"aw\"", + ".balign 8", + ".globl _start", + ".type _start, @object", + ".size _start, 24", + "_start:", + " .quad _start_impl", + " .quad .TOC.@tocbase", + " .quad 0", + ".previous", + ".text", + ".type _start_impl, @function", + "_start_impl:", + " mr 3, 1", + " clrrdi 1, 1, 4", + " stdu 1, -64(1)", + " bl entry_rust", + " nop", + " li 0, 1", + " sc", +); + // Global allocator #[global_allocator] static ALLOCATOR: BumpAllocator = BumpAllocator::new();