From 05fc3d8df9e5baabcaa52504e97b94cd634f60a3 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Thu, 16 Apr 2026 18:23:22 -0400 Subject: [PATCH] arch: add s390x support --- .github/workflows/rust.yml | 1 + crates/asm/src/lib.rs | 34 +++++++- crates/asm/src/s390x.rs | 160 +++++++++++++++++++++++++++++++++++++ crates/lib/src/cpu.rs | 9 ++- microfetch/src/main.rs | 16 ++++ 5 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 crates/asm/src/s390x.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6ef14a9..6ff6843 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,6 +21,7 @@ jobs: - aarch64-unknown-linux-gnu - riscv64gc-unknown-linux-gnu - loongarch64-unknown-linux-gnu + - s390x-unknown-linux-gnu steps: - name: "Checkout" diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index 66ad335..92cc420 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -5,7 +5,8 @@ //! What do you mean I wasted two whole hours to make the program only 100µs //! faster? //! -//! Supports `x86_64`, `aarch64`, `riscv64`, and `loongarch64` architectures. +//! Supports `x86_64`, `aarch64`, `riscv64`, `loongarch64`, and `s390x` +//! architectures. #![no_std] @@ -14,11 +15,12 @@ target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "s390x" )))] compile_error!( - "Unsupported architecture: only x86_64, aarch64, riscv64, and loongarch64 \ - are supported" + "Unsupported architecture: only x86_64, aarch64, riscv64, loongarch64, and \ + s390x are supported" ); // Per-arch syscall implementations live in their own module files. @@ -34,6 +36,9 @@ mod arch; #[cfg(target_arch = "loongarch64")] #[path = "loongarch64.rs"] mod arch; +#[cfg(target_arch = "s390x")] +#[path = "s390x.rs"] +mod arch; /// Copies `n` bytes from `src` to `dest`. /// @@ -267,6 +272,7 @@ 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(target_arch = "s390x"))] pub struct StatfsBuf { pub f_type: i64, pub f_bsize: i64, @@ -284,6 +290,26 @@ pub struct StatfsBuf { pub _pad: [i64; 4], } +/// on s390x `f_type` and `f_bsize` are 32-bit. +#[repr(C)] +#[cfg(target_arch = "s390x")] +pub struct StatfsBuf { + pub f_type: u32, + pub f_bsize: u32, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: [i32; 2], + pub f_namelen: u32, + pub f_frsize: u32, + pub f_flags: u32, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad: [u32; 5], +} + /// Direct `statfs(2)` syscall /// /// # Returns diff --git a/crates/asm/src/s390x.rs b/crates/asm/src/s390x.rs new file mode 100644 index 0000000..3112ed6 --- /dev/null +++ b/crates/asm/src/s390x.rs @@ -0,0 +1,160 @@ +//! Syscall implementations for `s390x`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::arch::asm!( + "svc 0", + in("r1") 5i64, // SYS_open + in("r2") path, + in("r3") flags, + in("r4") 0i32, // mode + lateout("r2") fd, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 3i64, // SYS_read + in("r2") fd, + in("r3") buf, + in("r4") count, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 4i64, // SYS_write + in("r2") fd, + in("r3") buf, + in("r4") count, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 6i64, // SYS_close + in("r2") fd, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 122i64, // SYS_uname + in("r2") buf, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 99i64, // SYS_statfs + in("r2") path, + in("r3") buf, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 116_i64, // __NR_sysinfo + in("r2") info, + lateout("r2") ret, + 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!( + "svc 0", + in("r1") 240i64, // __NR_sched_getaffinity + in("r2") pid, + in("r3") mask_size, + in("r4") mask, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "svc 0", + in("r1") 1i64, // SYS_exit + in("r2") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/lib/src/cpu.rs b/crates/lib/src/cpu.rs index c6ed8a0..ebff676 100644 --- a/crates/lib/src/cpu.rs +++ b/crates/lib/src/cpu.rs @@ -140,10 +140,15 @@ fn get_cpu_freq_mhz() -> Option { } } // Fall back to cpuinfo fields - let mut buf2 = [0u8; 2048]; + let mut buf2 = [0u8; 4096]; let n = read_file_fast("/proc/cpuinfo", &mut buf2).ok()?; let data = &buf2[..n]; - for key in &[b"cpu MHz" as &[u8], b"cpu MHz dynamic", b"CPU MHz"] { + for key in &[ + b"cpu MHz" as &[u8], + b"cpu MHz dynamic", + b"cpu MHz static", + b"CPU MHz", + ] { if let Some(val) = extract_field(data, key) { // Parse integer part of the MHz value (e.g. "5200.00" -> 5200) let mut mhz = 0u32; diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index c24e69a..c3957df 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -72,6 +72,22 @@ unsafe extern "C" fn _start() { ); } +#[cfg(target_arch = "s390x")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "lgr %r2, %r15", // save original sp (argc/argv) as arg + "aghi %r15, -160", // allocate s390x mandatory stack frame + "lghi %r0, -16", + "ngr %r15, %r0", // align stack to 16 bytes + "brasl %r14, {entry_rust}", + "lghi %r1, 1", // SYS_exit + "svc 0", + entry_rust = sym entry_rust, + ); +} + // Global allocator #[global_allocator] static ALLOCATOR: BumpAllocator = BumpAllocator::new();