jsph-tg-rcore-tutorial-user 0.4.14

User-space apps for rCore Tutorial chapters
Documentation
#![no_std]
//!
//! 教程阅读建议:
//!
//! - `_start` 展示用户程序最小运行时(初始化控制台、堆、调用 main);
//! - 其余辅助函数(sleep/pipe_*)展示了常见 syscall 组合用法。

mod heap;

extern crate alloc;

use tg_console::log;

pub use tg_console::{print, println};
pub use tg_syscall::*;

#[cfg(feature = "tangram")]
pub mod tangram;

#[cfg(feature = "snake")]
pub mod snake;

#[cfg(feature = "tetris")]
pub mod tetris;

#[cfg(feature = "pingpong")]
pub mod pingpong;

#[cfg(feature = "breakout")]
pub mod breakout;

#[cfg(feature = "pacman")]
pub mod pacman;

#[cfg(feature = "doom")]
pub mod doom;

/// Query framebuffer dimensions from kernel.
/// Returns (width, height).
pub fn fb_info() -> (u32, u32) {
    let packed: usize;
    unsafe {
        core::arch::asm!(
            "ecall",
            in("a7") 2000usize,
            lateout("a0") packed,
        );
    }
    let width = (packed >> 32) as u32;
    let height = packed as u32;
    (width, height)
}

/// Write a rectangular BGRA pixel region to the kernel framebuffer.
/// Does NOT flush — call `fb_flush()` after all writes for the frame.
pub fn fb_write(x: u32, y: u32, w: u32, h: u32, data: *const u8) -> isize {
    let ret: isize;
    unsafe {
        core::arch::asm!(
            "ecall",
            in("a7") 2001usize,
            inlateout("a0") x as usize => ret,
            in("a1") y as usize,
            in("a2") w as usize,
            in("a3") h as usize,
            in("a4") data as usize,
        );
    }
    ret
}

/// Request a shared memory page from the kernel.
/// Returns the virtual address of the shared page, or usize::MAX on failure.
pub fn shm_create() -> usize {
    let ret: usize;
    unsafe {
        core::arch::asm!(
            "ecall",
            in("a7") 2002usize,
            lateout("a0") ret,
        );
    }
    ret
}

/// Flush the GPU framebuffer to the display.
/// Call once per frame after all fb_write calls.
pub fn fb_flush() {
    unsafe {
        core::arch::asm!(
            "ecall",
            in("a7") 2003usize,
            lateout("a0") _,
        );
    }
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".text.entry")]
pub extern "C" fn _start() -> ! {
    // 用户态运行时初始化顺序与内核类似:先 I/O,再堆,再进入 main。
    tg_console::init_console(&Console);
    tg_console::set_log_level(option_env!("LOG"));
    heap::init();

    unsafe extern "C" {
        fn main() -> i32;
    }

    // SAFETY: main 函数由用户程序提供,链接器保证其存在且符合 C ABI
    exit(unsafe { main() });
    unreachable!()
}

#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
    let err = panic_info.message();
    if let Some(location) = panic_info.location() {
        log::error!("Panicked at {}:{}, {err}", location.file(), location.line());
    } else {
        log::error!("Panicked: {err}");
    }
    exit(1);
    unreachable!()
}

/// File descriptor for buffered keyboard input (timer-polled ring buffer in kernel).
pub const STDIN_BUFFERED: usize = 3;

pub fn getchar() -> u8 {
    let mut c = [0u8; 1];
    loop {
        let n = read(STDIN, &mut c);
        if n > 0 && c[0] != 0 {
            return c[0];
        }
        sched_yield();
    }
}

struct Console;

impl tg_console::Console for Console {
    #[inline]
    fn put_char(&self, c: u8) {
        tg_syscall::write(STDOUT, &[c]);
    }

    #[inline]
    fn put_str(&self, s: &str) {
        tg_syscall::write(STDOUT, s.as_bytes());
    }
}

pub fn sleep(period_ms: usize) {
    // 轮询时钟 + 主动让出 CPU 的教学实现,便于理解 time/yield 系统调用协作。
    let mut time: TimeSpec = TimeSpec::ZERO;
    clock_gettime(ClockId::CLOCK_MONOTONIC, &mut time as *mut _ as _);
    let time = time + TimeSpec::from_millsecond(period_ms);
    loop {
        let mut now: TimeSpec = TimeSpec::ZERO;
        clock_gettime(ClockId::CLOCK_MONOTONIC, &mut now as *mut _ as _);
        if now > time {
            break;
        }
        sched_yield();
    }
}

pub fn get_time() -> isize {
    let mut time: TimeSpec = TimeSpec::ZERO;
    clock_gettime(ClockId::CLOCK_MONOTONIC, &mut time as *mut _ as _);
    (time.tv_sec * 1000 + time.tv_nsec / 1_000_000) as isize
}

pub fn trace_read(ptr: *const u8) -> Option<u8> {
    let ret = trace(0, ptr as usize, 0);
    if ret >= 0 && ret <= 255 {
        Some(ret as u8)
    } else {
        None
    }
}

pub fn trace_write(ptr: *const u8, value: u8) -> isize {
    trace(1, ptr as usize, value as usize)
}

pub fn count_syscall(syscall_id: usize) -> isize {
    trace(2, syscall_id, 0)
}

/// 从管道读取数据
/// 返回实际读取的总字节数,负数表示错误
pub fn pipe_read(pipe_fd: usize, buffer: &mut [u8]) -> isize {
    let mut total_read = 0usize;
    let len = buffer.len();
    loop {
        if total_read >= len {
            return total_read as isize;
        }
        let ret = read(pipe_fd, &mut buffer[total_read..]);
        if ret == -2 {
            // 暂时无数据,让出 CPU 后重试
            sched_yield();
            continue;
        } else if ret == 0 {
            // EOF,写端关闭
            return total_read as isize;
        } else if ret < 0 {
            // 其他错误
            return ret;
        } else {
            total_read += ret as usize;
        }
    }
}

/// 向管道写入数据
/// 返回实际写入的总字节数,负数表示错误
pub fn pipe_write(pipe_fd: usize, buffer: &[u8]) -> isize {
    let mut total_write = 0usize;
    let len = buffer.len();
    loop {
        if total_write >= len {
            return total_write as isize;
        }
        let ret = write(pipe_fd, &buffer[total_write..]);
        if ret == -2 {
            // 缓冲区满,让出 CPU 后重试
            sched_yield();
            continue;
        } else if ret < 0 {
            // 其他错误
            return ret;
        } else {
            total_write += ret as usize;
        }
    }
}