252 lines
7.2 KiB
Rust

use super::{asm, mem::make_satp, SSTATUS_SPIE, SSTATUS_SPP};
use crate::{
hal::{
arch::{
interrupt,
mem::{PAGE_SIZE, TRAMPOLINE},
},
platform::VIRTIO0_IRQ,
},
println,
proc::{
cpu::Cpu,
process::{Process, ProcessState},
scheduler::{r#yield, wakeup},
},
sync::mutex::Mutex,
syscall::syscall,
};
use core::ptr::addr_of;
extern "C" {
pub fn kernelvec();
// pub fn usertrap();
// pub fn usertrapret();
// fn syscall();
// pub fn userret(satp: u64);
fn virtio_disk_intr();
pub static mut trampoline: [u8; 0];
pub static mut uservec: [u8; 0];
pub static mut userret: [u8; 0];
}
pub static CLOCK_TICKS: Mutex<usize> = Mutex::new(0);
/// Set up to take exceptions and traps while in the kernel.
pub unsafe fn trapinithart() {
asm::w_stvec(kernelvec as usize as u64);
}
pub fn clockintr() {
let mut ticks = CLOCK_TICKS.lock_spinning();
*ticks += 1;
unsafe {
wakeup(addr_of!(CLOCK_TICKS).cast_mut().cast());
}
}
/// Check if it's an external interrupt or software interrupt and handle it.
///
/// Returns 2 if timer interrupt, 1 if other device, 0 if not recognized.
pub unsafe fn devintr() -> i32 {
let scause = asm::r_scause();
if (scause & 0x8000000000000000 > 0) && (scause & 0xff) == 9 {
// This is a supervisor external interrupt, via PLIC.
// IRQ indicates which device interrupted.
let irq = interrupt::handle_interrupt();
let mut uart_interrupt = false;
for (uart_irq, uart) in &crate::hal::platform::UARTS {
if irq == *uart_irq {
uart_interrupt = true;
uart.interrupt();
}
}
if !uart_interrupt {
if irq == VIRTIO0_IRQ {
virtio_disk_intr();
} else if irq > 0 {
println!("unexpected interrupt irq={}", irq);
}
}
// The PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if irq > 0 {
interrupt::complete_interrupt(irq);
}
1
} else if scause == 0x8000000000000001 {
// Software interrupt from a machine-mode timer interrupt,
// forwarded by timervec in kernelvec.S.
if Cpu::current_id() == 0 {
clockintr();
}
// Acknowledge the software interrupt by
// clearing the SSIP bit in sip.
asm::w_sip(asm::r_sip() & !2);
2
} else {
0
}
}
/// Return to user space
#[no_mangle]
pub unsafe extern "C" fn usertrapret() -> ! {
let proc = Process::current().unwrap();
// We're about to switch the destination of traps from
// kerneltrap() to usertrap(), so turn off interrupts until
// we're back in user space, where usertrap() is correct.
interrupt::disable_interrupts();
// Send syscalls, interrupts, and exceptions to uservec in trampoline.S
let trampoline_uservec =
TRAMPOLINE + (addr_of!(uservec) as usize) - (addr_of!(trampoline) as usize);
asm::w_stvec(trampoline_uservec as u64);
// Set up trapframe values that uservec will need when
// the process next traps into the kernel.
// kernel page table
(*proc.trapframe).kernel_satp = asm::r_satp();
// process's kernel stack
(*proc.trapframe).kernel_sp = proc.kernel_stack + PAGE_SIZE as u64;
(*proc.trapframe).kernel_trap = usertrap as usize as u64;
// hartid for Cpu::current_id()
(*proc.trapframe).kernel_hartid = asm::r_tp();
// Set up the registers that trampoline.S's
// sret will use to get to user space.
// Set S Previous Privelege mode to User.
let mut x = asm::r_sstatus();
// Clear SPP to 0 for user mode.
x &= !SSTATUS_SPP;
// Enable interrupts in user mode.
x |= SSTATUS_SPIE;
asm::w_sstatus(x);
// Set S Exception Program Counter to the saved user pc.
asm::w_sepc((*proc.trapframe).epc);
// Tell trampoline.S the user page table to switch to.
let satp = make_satp(proc.pagetable);
// Jump to userret in trampoline.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
let trampoline_userret =
TRAMPOLINE + (addr_of!(userret) as usize) - (addr_of!(trampoline) as usize);
let trampoline_userret = trampoline_userret as *const ();
// Rust's most dangerous function: core::mem::transmute
let trampoline_userret = core::mem::transmute::<*const (), fn(u64) -> !>(trampoline_userret);
trampoline_userret(satp)
}
/// Interrupts and exceptions from kernel code go here via kernelvec,
/// on whatever the current kernel stack is.
#[no_mangle]
pub unsafe extern "C" fn kerneltrap() {
let sepc = asm::r_sepc();
let sstatus = asm::r_sstatus();
let scause = asm::r_scause();
if sstatus & SSTATUS_SPP == 0 {
panic!("kerneltrap: not from supervisor mode");
} else if interrupt::interrupts_enabled() != 0 {
panic!("kerneltrap: interrupts enabled");
}
let which_dev = devintr();
if which_dev == 0 {
println!(
"scause {}\nsepc={} stval={}",
scause,
asm::r_sepc(),
asm::r_stval()
);
panic!("kerneltrap");
} else if which_dev == 2
&& Process::current().is_some()
&& Process::current().unwrap().state == ProcessState::Running
{
// Give up the CPU if this is a timer interrupt.
r#yield();
}
// The yield() may have caused some traps to occur,
// so restore trap registers for use by kernelvec.S's sepc instruction.
asm::w_sepc(sepc);
asm::w_sstatus(sstatus);
}
/// Handle an interrupt, exception, or system call from userspace.
///
/// Called from trampoline.S
#[no_mangle]
pub unsafe extern "C" fn usertrap() {
if asm::r_sstatus() & SSTATUS_SPP != 0 {
panic!("usertrap: not from user mode");
}
// Send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
asm::w_stvec(kernelvec as usize as u64);
let proc = Process::current().unwrap();
// Save user program counter.
(*proc.trapframe).epc = asm::r_sepc();
if asm::r_scause() == 8 {
// System call
if proc.is_killed() {
proc.exit(-1);
}
// sepc points to the ecall instruction, but
// we want to return to the next instruction.
(*proc.trapframe).epc += 4;
// An interrupt will change sepc, scause, and sstatus,
// so enable only now that we're done with those registers.
interrupt::enable_interrupts();
syscall();
}
let which_dev = devintr();
if asm::r_scause() != 8 && which_dev == 0 {
println!(
"usertrap(): unexpected scause {} {}\n\tsepc={} stval={}",
asm::r_scause(),
proc.pid,
asm::r_sepc(),
asm::r_stval()
);
proc.set_killed(true);
}
if proc.is_killed() {
proc.exit(-1);
}
// Give up the CPU if this is a timer interrupt.
if which_dev == 2 {
r#yield();
}
usertrapret();
}