aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/config9
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml8
-rw-r--r--build.rs61
-rw-r--r--src/lib.rs105
-rw-r--r--src/main.rs20
7 files changed, 211 insertions, 0 deletions
diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..6588394
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,9 @@
+[build]
+target = "riscv64imac-unknown-none-elf"
+
+[target.riscv64imac-unknown-none-elf]
+# Effectively build for rv64i.
+rustflags = ["-C", "target-feature=-m,-a,-c"]
+runner = "qemu-riscv64"
+#runner = "qemu-riscv64 -d in_asm"
+#runner = "qemu-riscv64 -strace"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..51c8e32
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "user-sw"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..f7e203e
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "user-sw"
+version = "0.1.0"
+authors = ["Johannes Stoelp"]
+edition = "2021"
+license = "<TODO>"
+
+[dependencies]
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..827c302
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,61 @@
+/// Generate rust constants for linux riscv syscall numbers.
+///
+/// Syscall numbers are parsed from the `linux/unistd.h` header.
+/// This script uses the `riscv64-linux-gnu-*` tools to find to header, make sure those tools are
+/// in the PATH.
+///
+/// For simplicity, this script unwraps any Option/Result because we want to fail in case of any
+/// error.
+
+fn main() {
+ // Get sysroot to find header location.
+ let out = std::process::Command::new("riscv64-linux-gnu-gcc")
+ .arg("-print-sysroot")
+ .output()
+ .unwrap();
+ let sysroot = std::str::from_utf8(&out.stdout).unwrap();
+ let header = format!("{}/include/linux/unistd.h", sysroot.trim_end());
+
+ // The header should "basically" never change.
+ println!("cargo:rerun-if-changed={}", header);
+
+ // Run header through preprocessor and dump macro definitions.
+ let out = std::process::Command::new("riscv64-linux-gnu-cpp")
+ .arg("-dM")
+ .arg(header)
+ .output()
+ .unwrap();
+ let defines = std::str::from_utf8(&out.stdout).unwrap();
+
+ let mut output = String::with_capacity(256);
+
+ // Parse out lines of the format
+ // #define __NR_<syscall> <nr>
+ // and generate output strings.
+ for line in defines.lines() {
+ let line = match line.strip_prefix("#define __NR_") {
+ Some(line) => line,
+ None => continue,
+ };
+
+ let (sys, nr) = match line.split_once(' ') {
+ Some(split) => split,
+ None => continue,
+ };
+
+ let nr = match nr.parse::<usize>() {
+ Ok(nr) => nr,
+ Err(_) => continue,
+ };
+
+ output.push_str(&format!(
+ "#[allow(unused)] const SYS_{}: usize = {};\n",
+ sys.to_uppercase(),
+ nr
+ ));
+ }
+
+ // Write out rust file with syscall numbers.
+ let outfile = format!("{}/syscalls.rs", std::env::var("OUT_DIR").unwrap());
+ std::fs::write(&outfile, output).unwrap();
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3191f8d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,105 @@
+#![no_std]
+
+/// Raw file descriptor.
+///
+/// Instances of `Fd` can be constructed with [`stdout()`] and [`stderr()`].
+pub struct Fd(i32);
+
+impl core::fmt::Write for Fd {
+ fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
+ if sys::write(self.0, s.as_bytes()) != -1 {
+ Ok(())
+ } else {
+ Err(core::fmt::Error)
+ }
+ }
+}
+
+/// Construct raw file descriptor for `stdout`.
+pub fn stdout() -> Fd {
+ Fd(1)
+}
+
+/// Construct raw file descriptor for `stderr`.
+pub fn stderr() -> Fd {
+ Fd(2)
+}
+
+/// Write format string to `stdout`, swallowing any errors.
+#[macro_export]
+macro_rules! println {
+ () => ({
+ $crate::println!("")
+ });
+ ($($arg:tt)*) => ({
+ use core::fmt::Write;
+ let _ = writeln!(&mut $crate::stdout(), $($arg)*);
+ });
+}
+
+/// Write format string to `stderr`, swallowing any errors.
+#[macro_export]
+macro_rules! eprintln {
+ () => ({
+ $crate::eprintln!("")
+ });
+ ($($arg:tt)*) => ({
+ use core::fmt::Write;
+ let _ = writeln!(&mut $crate::stderr(), $($arg)*);
+ });
+}
+
+/// Wrapper to invoke `riscv` linux syscalls.
+///
+/// Syscalls are generated according to the linux [syscall(2)][man-syscall] abi.
+///
+/// # Invocation
+/// ```
+/// Arch/ABI Instruction System Ret Ret
+/// call # val val2
+/// ----------------------------------------
+/// riscv ecall a7 a0 a1
+/// ```
+///
+/// # Arguments
+/// ```
+/// Arch/ABI arg1 arg2 arg3 arg4 arg5 arg6
+/// --------------------------------------------
+/// riscv a0 a1 a2 a3 a4 a5
+/// ```
+///
+/// [man-syscall]: https://man7.org/linux/man-pages/man2/syscall.2.html
+#[cfg(target_arch = "riscv64")]
+pub mod sys {
+ use core::arch::asm;
+
+ // Include generated syscall numbers (build.rs).
+ include!(concat!(env!("OUT_DIR"), "/syscalls.rs"));
+
+ #[inline]
+ pub fn write(fd: i32, buf: &[u8]) -> i32 {
+ let mut ret;
+ unsafe {
+ asm!(
+ "ecall",
+ in("a7") SYS_WRITE,
+ inlateout("a0") fd => ret,
+ in("a1") buf.as_ptr(),
+ in("a2") buf.len(),
+ );
+ }
+ ret
+ }
+
+ #[inline]
+ pub fn exit(status: i32) -> ! {
+ unsafe {
+ asm!(
+ "ecall",
+ in("a7") SYS_EXIT,
+ in("a0") status,
+ options(noreturn),
+ );
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..f1ea8b0
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,20 @@
+#![no_std]
+#![no_main]
+
+use user_sw::{eprintln, println, sys};
+
+fn main() {
+ println!("Hello {} from rust main().", 1337);
+}
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ main();
+ sys::exit(0);
+}
+
+#[panic_handler]
+fn panic_handler(info: &core::panic::PanicInfo) -> ! {
+ eprintln!("{}", info);
+ sys::exit(42);
+}