#![no_std]
#![no_main]
#![cfg_attr(target_arch = "riscv64", deny(warnings, missing_docs))]
#![cfg_attr(not(target_arch = "riscv64"), allow(dead_code, unused_imports))]
mod fs;
mod process;
mod processor;
mod virtio_block;
#[macro_use]
extern crate tg_console;
#[macro_use]
extern crate alloc;
use crate::{
fs::{read_all, FS},
impls::{Sv39Manager, SyscallContext},
process::Process,
processor::ProcManager,
};
use alloc::alloc::alloc;
use core::{alloc::Layout, cell::UnsafeCell, mem::MaybeUninit};
use impls::Console;
pub use processor::PROCESSOR;
use riscv::register::*;
#[cfg(not(target_arch = "riscv64"))]
use stub::Sv39;
use tg_console::log;
use tg_easy_fs::{FSManager, OpenFlags};
use tg_kernel_context::foreign::MultislotPortal;
#[cfg(target_arch = "riscv64")]
use tg_kernel_vm::page_table::Sv39;
use tg_kernel_vm::{
page_table::{MmuMeta, VAddr, VmFlags, VmMeta, PPN, VPN},
AddressSpace,
};
use tg_sbi;
use tg_signal::SignalResult;
use tg_syscall::Caller;
use tg_task_manage::{PManager, ProcId};
use xmas_elf::ElfFile;
#[cfg(target_arch = "riscv64")]
const fn build_flags(s: &str) -> VmFlags<Sv39> {
VmFlags::build_from_str(s)
}
#[cfg(target_arch = "riscv64")]
fn parse_flags(s: &str) -> Result<VmFlags<Sv39>, ()> {
s.parse()
}
#[cfg(not(target_arch = "riscv64"))]
use stub::{build_flags, parse_flags};
#[cfg(target_arch = "riscv64")]
#[unsafe(naked)]
#[unsafe(no_mangle)]
#[unsafe(link_section = ".text.entry")]
unsafe extern "C" fn _start() -> ! {
const STACK_SIZE: usize = 32 * 4096;
#[unsafe(link_section = ".boot.stack")]
static mut STACK: [u8; STACK_SIZE] = [0u8; STACK_SIZE];
core::arch::naked_asm!(
"la sp, {stack} + {stack_size}",
"j {main}",
stack = sym STACK,
stack_size = const STACK_SIZE,
main = sym rust_main,
)
}
const MEMORY: usize = 48 << 20;
const PROTAL_TRANSIT: VPN<Sv39> = VPN::MAX;
struct KernelSpace {
inner: UnsafeCell<MaybeUninit<AddressSpace<Sv39, Sv39Manager>>>,
}
unsafe impl Sync for KernelSpace {}
impl KernelSpace {
const fn new() -> Self {
Self {
inner: UnsafeCell::new(MaybeUninit::uninit()),
}
}
unsafe fn write(&self, space: AddressSpace<Sv39, Sv39Manager>) {
unsafe { *self.inner.get() = MaybeUninit::new(space) };
}
unsafe fn assume_init_ref(&self) -> &AddressSpace<Sv39, Sv39Manager> {
unsafe { &*(*self.inner.get()).as_ptr() }
}
}
static KERNEL_SPACE: KernelSpace = KernelSpace::new();
pub const MMIO: &[(usize, usize)] = &[(0x1000_1000, 0x00_1000)];
extern "C" fn rust_main() -> ! {
let layout = tg_linker::KernelLayout::locate();
unsafe { layout.zero_bss() };
tg_console::init_console(&Console);
tg_console::set_log_level(option_env!("LOG"));
tg_console::test_log();
tg_kernel_alloc::init(layout.start() as _);
unsafe {
tg_kernel_alloc::transfer(core::slice::from_raw_parts_mut(
layout.end() as _,
MEMORY - layout.len(),
))
};
let portal_size = MultislotPortal::calculate_size(1);
let portal_layout = Layout::from_size_align(portal_size, 1 << Sv39::PAGE_BITS).unwrap();
let portal_ptr = unsafe { alloc(portal_layout) };
assert!(portal_layout.size() < 1 << Sv39::PAGE_BITS);
kernel_space(layout, MEMORY, portal_ptr as _);
let portal = unsafe { MultislotPortal::init_transit(PROTAL_TRANSIT.base().val(), 1) };
tg_syscall::init_io(&SyscallContext);
tg_syscall::init_process(&SyscallContext);
tg_syscall::init_scheduling(&SyscallContext);
tg_syscall::init_clock(&SyscallContext);
tg_syscall::init_signal(&SyscallContext); let initproc = read_all(FS.open("initproc", OpenFlags::RDONLY).unwrap());
if let Some(process) = Process::from_elf(ElfFile::new(initproc.as_slice()).unwrap()) {
PROCESSOR.get_mut().set_manager(ProcManager::new());
PROCESSOR
.get_mut()
.add(process.pid, process, ProcId::from_usize(usize::MAX));
}
loop {
let processor: *mut PManager<Process, ProcManager> = PROCESSOR.get_mut() as *mut _;
if let Some(task) = unsafe { (*processor).find_next() } {
unsafe { task.context.execute(portal, ()) };
match scause::read().cause() {
scause::Trap::Exception(scause::Exception::UserEnvCall) => {
use tg_syscall::{SyscallId as Id, SyscallResult as Ret};
let ctx = &mut task.context.context;
ctx.move_next();
let id: Id = ctx.a(7).into();
let args = [ctx.a(0), ctx.a(1), ctx.a(2), ctx.a(3), ctx.a(4), ctx.a(5)];
let syscall_ret = tg_syscall::handle(Caller { entity: 0, flow: 0 }, id, args);
match task.signal.handle_signals(ctx) {
SignalResult::ProcessKilled(exit_code) => unsafe {
(*processor).make_current_exited(exit_code as _)
},
_ => match syscall_ret {
Ret::Done(ret) => match id {
Id::EXIT => unsafe { (*processor).make_current_exited(ret) },
_ => {
let ctx = &mut task.context.context;
*ctx.a_mut(0) = ret as _;
unsafe { (*processor).make_current_suspend() };
}
},
Ret::Unsupported(_) => {
log::info!("id = {id:?}");
unsafe { (*processor).make_current_exited(-2) };
}
},
}
}
e => {
log::error!("unsupported trap: {e:?}");
unsafe { (*processor).make_current_exited(-3) };
}
}
} else {
println!("no task");
break;
}
}
tg_sbi::shutdown(false)
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
tg_sbi::shutdown(true)
}
fn kernel_space(layout: tg_linker::KernelLayout, memory: usize, portal: usize) {
let mut space = AddressSpace::new();
for region in layout.iter() {
log::info!("{region}");
use tg_linker::KernelRegionTitle::*;
let flags = match region.title {
Text => "X_RV",
Rodata => "__RV",
Data | Boot => "_WRV",
};
let s = VAddr::<Sv39>::new(region.range.start);
let e = VAddr::<Sv39>::new(region.range.end);
space.map_extern(
s.floor()..e.ceil(),
PPN::new(s.floor().val()),
build_flags(flags),
)
}
let s = VAddr::<Sv39>::new(layout.end());
let e = VAddr::<Sv39>::new(layout.start() + memory);
log::info!("(heap) ---> {:#10x}..{:#10x}", s.val(), e.val());
space.map_extern(
s.floor()..e.ceil(),
PPN::new(s.floor().val()),
build_flags("_WRV"),
);
space.map_extern(
PROTAL_TRANSIT..PROTAL_TRANSIT + 1,
PPN::new(portal >> Sv39::PAGE_BITS),
build_flags("__G_XWRV"),
);
println!();
for (base, len) in MMIO {
let s = VAddr::<Sv39>::new(*base);
let e = VAddr::<Sv39>::new(*base + *len);
log::info!("MMIO range -> {:#10x}..{:#10x}", s.val(), e.val());
space.map_extern(
s.floor()..e.ceil(),
PPN::new(s.floor().val()),
build_flags("_WRV"),
);
}
unsafe { satp::set(satp::Mode::Sv39, 0, space.root_ppn().val()) };
unsafe { KERNEL_SPACE.write(space) };
}
fn map_portal(space: &AddressSpace<Sv39, Sv39Manager>) {
let portal_idx = PROTAL_TRANSIT.index_in(Sv39::MAX_LEVEL);
space.root()[portal_idx] = unsafe { KERNEL_SPACE.assume_init_ref() }.root()[portal_idx];
}
mod impls {
use crate::{
build_flags,
fs::{read_all, Fd, FS},
process::Process as ProcStruct,
processor::ProcManager,
Sv39, PROCESSOR,
};
use alloc::{alloc::alloc_zeroed, string::String, vec::Vec};
use core::{alloc::Layout, ptr::NonNull};
use spin::Mutex;
use tg_console::log;
use tg_easy_fs::{make_pipe, FSManager, OpenFlags, UserBuffer};
use tg_kernel_vm::{
page_table::{MmuMeta, Pte, VAddr, VmFlags, PPN, VPN},
PageManager,
};
use tg_signal::SignalNo;
use tg_syscall::*;
use tg_task_manage::{PManager, ProcId};
use xmas_elf::ElfFile;
#[repr(transparent)]
pub struct Sv39Manager(NonNull<Pte<Sv39>>);
impl Sv39Manager {
const OWNED: VmFlags<Sv39> = unsafe { VmFlags::from_raw(1 << 8) };
#[inline]
fn page_alloc<T>(count: usize) -> *mut T {
unsafe {
alloc_zeroed(Layout::from_size_align_unchecked(
count << Sv39::PAGE_BITS,
1 << Sv39::PAGE_BITS,
))
}
.cast()
}
}
impl PageManager<Sv39> for Sv39Manager {
#[inline]
fn new_root() -> Self {
Self(NonNull::new(Self::page_alloc(1)).unwrap())
}
#[inline]
fn root_ppn(&self) -> PPN<Sv39> {
PPN::new(self.0.as_ptr() as usize >> Sv39::PAGE_BITS)
}
#[inline]
fn root_ptr(&self) -> NonNull<Pte<Sv39>> {
self.0
}
#[inline]
fn p_to_v<T>(&self, ppn: PPN<Sv39>) -> NonNull<T> {
unsafe { NonNull::new_unchecked(VPN::<Sv39>::new(ppn.val()).base().as_mut_ptr()) }
}
#[inline]
fn v_to_p<T>(&self, ptr: NonNull<T>) -> PPN<Sv39> {
PPN::new(VAddr::<Sv39>::new(ptr.as_ptr() as _).floor().val())
}
#[inline]
fn check_owned(&self, pte: Pte<Sv39>) -> bool {
pte.flags().contains(Self::OWNED)
}
#[inline]
fn allocate(&mut self, len: usize, flags: &mut VmFlags<Sv39>) -> NonNull<u8> {
*flags |= Self::OWNED;
NonNull::new(Self::page_alloc(len)).unwrap()
}
fn deallocate(&mut self, _pte: Pte<Sv39>, _len: usize) -> usize {
todo!()
}
fn drop_root(&mut self) {
todo!()
}
}
pub struct Console;
impl tg_console::Console for Console {
#[inline]
fn put_char(&self, c: u8) {
tg_sbi::console_putchar(c);
}
}
pub struct SyscallContext;
const READABLE: VmFlags<Sv39> = build_flags("RV");
const WRITEABLE: VmFlags<Sv39> = build_flags("W_V");
impl IO for SyscallContext {
fn write(&self, _caller: Caller, fd: usize, buf: usize, count: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if let Some(ptr) = current.address_space.translate(VAddr::new(buf), READABLE) {
if fd == STDOUT || fd == STDDEBUG {
print!("{}", unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
ptr.as_ptr(),
count,
))
});
count as _
} else if let Some(file) = ¤t.fd_table[fd] {
let file = file.lock();
if file.writable() {
let mut v: Vec<&'static mut [u8]> = Vec::new();
unsafe { v.push(core::slice::from_raw_parts_mut(ptr.as_ptr(), count)) };
file.write(UserBuffer::new(v)) as _
} else {
log::error!("file not writable");
-1
}
} else {
log::error!("unsupported fd: {fd}");
-1
}
} else {
log::error!("ptr not readable");
-1
}
}
fn read(&self, _caller: Caller, fd: usize, buf: usize, count: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if let Some(ptr) = current.address_space.translate(VAddr::new(buf), WRITEABLE) {
if fd == STDIN {
let mut ptr = ptr.as_ptr();
for _ in 0..count {
unsafe {
*ptr = tg_sbi::console_getchar() as u8;
ptr = ptr.add(1);
}
}
count as _
} else if let Some(file) = ¤t.fd_table[fd] {
let file = file.lock();
if file.readable() {
let mut v: Vec<&'static mut [u8]> = Vec::new();
unsafe { v.push(core::slice::from_raw_parts_mut(ptr.as_ptr(), count)) };
file.read(UserBuffer::new(v)) as _
} else {
log::error!("file not readable");
-1
}
} else {
log::error!("unsupported fd: {fd}");
-1
}
} else {
log::error!("ptr not writeable");
-1
}
}
fn open(&self, _caller: Caller, path: usize, flags: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if let Some(ptr) = current.address_space.translate(VAddr::new(path), READABLE) {
let mut string = String::new();
let mut raw_ptr: *mut u8 = ptr.as_ptr();
loop {
unsafe {
let ch = *raw_ptr;
if ch == 0 {
break;
}
string.push(ch as char);
raw_ptr = (raw_ptr as usize + 1) as *mut u8;
}
}
if let Some(file_handle) =
FS.open(string.as_str(), OpenFlags::from_bits(flags as u32).unwrap())
{
let new_fd = current.fd_table.len();
current
.fd_table
.push(Some(Mutex::new(Fd::File((*file_handle).clone()))));
new_fd as isize
} else {
-1
}
} else {
log::error!("ptr not writeable");
-1
}
}
#[inline]
fn close(&self, _caller: Caller, fd: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if fd >= current.fd_table.len() || current.fd_table[fd].is_none() {
return -1;
}
current.fd_table[fd].take();
0
}
fn pipe(&self, _caller: Caller, pipe: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
let (read_end, write_end) = make_pipe();
let read_fd = current.fd_table.len();
let write_fd = read_fd + 1;
if let Some(mut ptr) = current
.address_space
.translate::<usize>(VAddr::new(pipe), WRITEABLE)
{
unsafe { *ptr.as_mut() = read_fd };
} else {
return -1;
}
if let Some(mut ptr) = current
.address_space
.translate::<usize>(VAddr::new(pipe + core::mem::size_of::<usize>()), WRITEABLE)
{
unsafe { *ptr.as_mut() = write_fd };
} else {
return -1;
}
current
.fd_table
.push(Some(Mutex::new(Fd::PipeRead(read_end))));
current
.fd_table
.push(Some(Mutex::new(Fd::PipeWrite(write_end))));
0
}
}
impl Process for SyscallContext {
#[inline]
fn exit(&self, _caller: Caller, exit_code: usize) -> isize {
exit_code as isize
}
fn fork(&self, _caller: Caller) -> isize {
let processor: *mut PManager<ProcStruct, ProcManager> = PROCESSOR.get_mut() as *mut _;
let current = unsafe { (*processor).current().unwrap() };
let parent_pid = current.pid;
let mut child_proc = current.fork().unwrap();
let pid = child_proc.pid;
let context = &mut child_proc.context.context;
*context.a_mut(0) = 0 as _;
unsafe {
(*processor).add(pid, child_proc, parent_pid);
}
pid.get_usize() as isize
}
fn exec(&self, _caller: Caller, path: usize, count: usize) -> isize {
const READABLE: VmFlags<Sv39> = build_flags("RV");
let current = PROCESSOR.get_mut().current().unwrap();
current
.address_space
.translate(VAddr::new(path), READABLE)
.map(|ptr| unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr.as_ptr(), count))
})
.and_then(|name| FS.open(name, OpenFlags::RDONLY))
.map_or_else(
|| {
log::error!("unknown app, select one in the list: ");
FS.readdir("")
.unwrap()
.into_iter()
.for_each(|app| println!("{app}"));
println!();
-1
},
|fd| {
current.exec(ElfFile::new(&read_all(fd)).unwrap());
0
},
)
}
fn wait(&self, _caller: Caller, pid: isize, exit_code_ptr: usize) -> isize {
let processor: *mut PManager<ProcStruct, ProcManager> = PROCESSOR.get_mut() as *mut _;
let current = unsafe { (*processor).current().unwrap() };
const WRITABLE: VmFlags<Sv39> = build_flags("W_V");
if let Some((dead_pid, exit_code)) =
unsafe { (*processor).wait(ProcId::from_usize(pid as usize)) }
{
if let Some(mut ptr) = current
.address_space
.translate::<i32>(VAddr::new(exit_code_ptr), WRITABLE)
{
unsafe { *ptr.as_mut() = exit_code as i32 };
}
return dead_pid.get_usize() as isize;
} else {
return -1;
}
}
fn getpid(&self, _caller: Caller) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
current.pid.get_usize() as _
}
fn sbrk(&self, _caller: Caller, size: i32) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if let Some(old_brk) = current.change_program_brk(size as isize) {
old_brk as isize
} else {
-1
}
}
}
impl Scheduling for SyscallContext {
#[inline]
fn sched_yield(&self, _caller: Caller) -> isize {
0
}
}
impl Clock for SyscallContext {
#[inline]
fn clock_gettime(&self, _caller: Caller, clock_id: ClockId, tp: usize) -> isize {
const WRITABLE: VmFlags<Sv39> = build_flags("W_V");
match clock_id {
ClockId::CLOCK_MONOTONIC => {
if let Some(mut ptr) = PROCESSOR
.get_mut()
.current()
.unwrap()
.address_space
.translate(VAddr::new(tp), WRITABLE)
{
let time = riscv::register::time::read() * 10000 / 125;
*unsafe { ptr.as_mut() } = TimeSpec {
tv_sec: time / 1_000_000_000,
tv_nsec: time % 1_000_000_000,
};
0
} else {
log::error!("ptr not readable");
-1
}
}
_ => -1,
}
}
}
impl Signal for SyscallContext {
fn kill(&self, _caller: Caller, pid: isize, signum: u8) -> isize {
if let Some(target_task) = PROCESSOR
.get_mut()
.get_task(ProcId::from_usize(pid as usize))
{
if let Ok(signal_no) = SignalNo::try_from(signum) {
if signal_no != SignalNo::ERR {
target_task.signal.add_signal(signal_no);
return 0;
}
}
}
-1
}
fn sigaction(
&self,
_caller: Caller,
signum: u8,
action: usize,
old_action: usize,
) -> isize {
if signum as usize > tg_signal::MAX_SIG {
return -1;
}
let current = PROCESSOR.get_mut().current().unwrap();
if let Ok(signal_no) = SignalNo::try_from(signum) {
if signal_no == SignalNo::ERR {
return -1;
}
if old_action as usize != 0 {
if let Some(mut ptr) = current
.address_space
.translate(VAddr::new(old_action), WRITEABLE)
{
if let Some(signal_action) = current.signal.get_action_ref(signal_no) {
*unsafe { ptr.as_mut() } = signal_action;
} else {
return -1;
}
} else {
return -1;
}
}
if action as usize != 0 {
if let Some(ptr) = current
.address_space
.translate(VAddr::new(action), READABLE)
{
if !current
.signal
.set_action(signal_no, &unsafe { *ptr.as_ptr() })
{
return -1;
}
} else {
return -1;
}
}
return 0;
}
-1
}
fn sigprocmask(&self, _caller: Caller, mask: usize) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
current.signal.update_mask(mask) as isize
}
fn sigreturn(&self, _caller: Caller) -> isize {
let current = PROCESSOR.get_mut().current().unwrap();
if current.signal.sig_return(&mut current.context.context) {
0
} else {
-1
}
}
}
}
#[cfg(not(target_arch = "riscv64"))]
mod stub {
use tg_kernel_vm::page_table::{MmuMeta, VmFlags};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Sv39;
impl MmuMeta for Sv39 {
const P_ADDR_BITS: usize = 56;
const PAGE_BITS: usize = 12;
const LEVEL_BITS: &'static [usize] = &[9, 9, 9];
const PPN_POS: usize = 10;
#[inline]
fn is_leaf(value: usize) -> bool {
value & 0b1110 != 0
}
}
pub const fn build_flags(_s: &str) -> VmFlags<Sv39> {
unsafe { VmFlags::from_raw(0) }
}
pub fn parse_flags(_s: &str) -> Result<VmFlags<Sv39>, ()> {
Ok(unsafe { VmFlags::from_raw(0) })
}
#[unsafe(no_mangle)]
pub extern "C" fn main() -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn __libc_start_main() -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_eh_personality() {}
}