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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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(),
}
1
2
3
4
#[inline]
pub fn shutdown() -> SbiRet {
crate::reset::legacy_reset()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
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,所以我们可以从平台里面找到对应的函数接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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态:

1
2
3
4
// 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了。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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来传入参数:

1
2
3
4
5
6
#[panic_handler]
fn panic(info: &PanicInfo<'_>) -> ! {
println!("\x1b[1;31mpanic: '{}'\x1b[0m", info);
kernel_syscall(SHUTDOWN, 0, 0, 0);
loop {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#[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
}

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