3

如何不使用SBI在OS中关闭QEMU

 3 years ago
source link: https://blog.kuangjux.top/2021/04/14/shutdown-without-SBI-in-OS/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
KuangjuX(狂且)

如何不使用SBI在OS中关闭QEMU

Created2021-04-14|Updated2021-04-14
Post View:166

之前在做rCore的时候发现里面的panic()会直接调用sbi_call直接实现shutdown()。由于这次我自己写的OS自己写的bootloader没有使用SBI,于是想去自己去实现一个shutdown的功能。

由于我用的是QEMU来做硬件模拟,于是我去看了一下RustSBI关于shutdown的处理部分:

pub fn handle_ecall(extension: usize, function: usize, param: [usize; 5]) -> SbiRet {
match extension {
EXTENSION_RFENCE => rfence::handle_ecall_rfence(function, param[0], param[1], param[2], param[3], param[4]),
EXTENSION_TIMER => match () {
#[cfg(target_pointer_width = "64")]
() => timer::handle_ecall_timer_64(function, param[0]),
#[cfg(target_pointer_width = "32")]
() => timer::handle_ecall_timer_32(function, param[0], param[1]),
},
EXTENSION_IPI => ipi::handle_ecall_ipi(function, param[0], param[1]),
EXTENSION_BASE => base::handle_ecall_base(function, param[0]),
EXTENSION_HSM => hsm::handle_ecall_hsm(function, param[0], param[1], param[2]),
EXTENSION_SRST => srst::handle_ecall_srst(function, param[0], param[1]),
LEGACY_SET_TIMER => match () {
#[cfg(target_pointer_width = "64")]
() => legacy::set_timer_64(param[0]),
#[cfg(target_pointer_width = "32")]
() => legacy::set_timer_32(param[0], param[1]),
}
.legacy_void(param[0], param[1]),
LEGACY_CONSOLE_PUTCHAR => legacy::console_putchar(param[0]).legacy_void(param[0], param[1]),
LEGACY_CONSOLE_GETCHAR => legacy::console_getchar().legacy_return(param[1]),
LEGACY_SEND_IPI => legacy::send_ipi(param[0]).legacy_void(param[0], param[1]),
LEGACY_SHUTDOWN => legacy::shutdown().legacy_void(param[0], param[1]),
_ => SbiRet::not_supported(),
}
#[inline]
pub fn shutdown() -> SbiRet {
crate::reset::legacy_reset()
}
pub(crate) fn system_reset(reset_type: usize, reset_reason: usize) -> SbiRet {
if let Some(obj) = &*RESET.lock() {
return obj.system_reset(reset_type, reset_reason);
}
SbiRet::not_supported()
}

pub(crate) fn legacy_reset() -> ! {
if let Some(obj) = &*RESET.lock() {
obj.legacy_reset()
}
unreachable!("no reset handler available; this is okay if your platform didn't declare a legacy reset handler")
}

根据源码追溯我们可以看出来RustSBI使用system_reset()来处理关机,而不同平台对应不同的system_reset()功能。

由于我使用的是QEMU,所以我们可以从平台里面找到对应的函数接口:

impl rustsbi::Reset for Reset {
fn system_reset(&self, reset_type: usize, reset_reason: usize) -> rustsbi::SbiRet {
// todo: only exit after all harts finished
// loop {}
const VIRT_TEST: *mut u32 = 0x10_0000 as *mut u32;
// Fail = 0x3333,
// Pass = 0x5555,
// Reset = 0x7777,
let mut value = match reset_type {
rustsbi::reset::RESET_TYPE_SHUTDOWN => TEST_PASS,
rustsbi::reset::RESET_TYPE_COLD_REBOOT => TEST_RESET,
rustsbi::reset::RESET_TYPE_WARM_REBOOT => TEST_RESET,
_ => TEST_FAIL,
};
if reset_reason == rustsbi::reset::RESET_REASON_SYSTEM_FAILURE {
value = TEST_FAIL;
};
unsafe {
core::ptr::write_volatile(VIRT_TEST, value);
}
unreachable!()
}
}

可以看到,我们只需要向VIRT_TEST这个物理地址里面写一些值即可实现关机。

明白了RustSBI实现关机的原理,然后回到我们OS中来。

由于实现shutdown()这必须要求我们使用ecall从S Mode切换到M Mode。但幸运的是我们可以通过设置midelegmedeleg寄存器来将M态的中断代理到S态:

// delegate all interrupts and exceptions to supervisor mode.
medeleg::write(0xffff);
mideleg::write(0xffff);
sie::write(sie::read() | sie::SIE::SEIE as usize | sie::SIE::STIE as usize | sie::SIE::SSIE as usize);

然后我们就可以在S态的中断来处理Kernel syscall了。

Trap::Exception(Exception::KernelEnvCall) => handler_kernel_syscall(arg7),

在中断时我们可以匹配到KernelEnvCall来对内核的调用进行处理,这里我们遵循SBI的标准,arg7即表示中断的类型。

pub fn handler_kernel_syscall(result: usize) {
unsafe{
satp::write(0);
}
match result {
SHUTDOWN => {
println!("\x1b[1;31mshutdown! Bye~ \x1b[0m");
system_reset(
RESET_TYPE_SHUTDOWN,
RESET_REASON_NO_REASON
);
// panic!("sutdown");
}

_ => {
println!("Unresolved Kernel Syscall");
}
}
}

首先我们必须把satp寄存器置0,否则当我们写入物理地址的时候会直接发生Page Fault

以上就是内核的处理流程。

而在panic的具体实现中,我们需要去ecall来传入参数:

#[panic_handler]
fn panic(info: &PanicInfo<'_>) -> ! {
println!("\x1b[1;31mpanic: '{}'\x1b[0m", info);
kernel_syscall(SHUTDOWN, 0, 0, 0);
loop {}
}
#[inline]
pub fn kernel_syscall(
which: usize,
arg0: usize,
arg1: usize,
arg2: usize,
) -> usize {
let mut ret;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
: "memory"
: "volatile"
);
}
ret
}

这样我们即可在内核中实现关机。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK