Skip to content

Commit aa7413f

Browse files
authored
Translation Chapter 09 (#191)
1 parent 2dfb9e7 commit aa7413f

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

09_privilege_level/README.CN.md

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# 教程 09 - 特权级别
2+
3+
## tl;dr
4+
5+
- 在早期引导代码中,我们从`Hypervisor`特权级别(AArch64中的`EL2`)过渡到`Kernel``EL1`)特权级别。
6+
7+
## 目录
8+
9+
- [介绍](#介绍)
10+
- [本教程的范围](#本教程的范围)
11+
- [在入口点检查EL2](#在入口点检查EL2)
12+
- [过渡准备](#过渡准备)
13+
- [从未发生的异常中返回](#从未发生的异常中返回)
14+
- [测试](#测试)
15+
- [相比之前的变化(diff)](#相比之前的变化(diff))
16+
17+
## 介绍
18+
19+
应用级别的CPU具有所谓的`privilege levels`,它们具有不同的目的:
20+
21+
| Typically used for | AArch64 | RISC-V | x86 |
22+
| ------------- | ------------- | ------------- | ------------- |
23+
| Userspace applications | EL0 | U/VU | Ring 3 |
24+
| OS Kernel | EL1 | S/VS | Ring 0 |
25+
| Hypervisor | EL2 | HS | Ring -1 |
26+
| Low-Level Firmware | EL3 | M | |
27+
28+
在AArch64中,`EL`代表`Exception Level`(异常级别)。如果您想获取有关其他体系结构的更多信息,请查看以下链接:
29+
- [x86 privilege rings](https://en.wikipedia.org/wiki/Protection_ring).
30+
- [RISC-V privilege modes](https://content.riscv.org/wp-content/uploads/2017/12/Tue0942-riscv-hypervisor-waterman.pdf).
31+
32+
在继续之前,我强烈建议您先浏览一下[Programmer’s Guide for ARMv8-A]`的第3章`。它提供了关于该主题的简明概述。
33+
34+
[Programmer’s Guide for ARMv8-A]: http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf
35+
36+
## 本教程的范围
37+
38+
默认情况下,树莓派将始终在`EL2`中开始执行。由于我们正在编写一个传统的`Kernel`,我们需要过渡到更合适的`EL1`
39+
40+
## 在入口点检查EL2
41+
42+
首先,我们需要确保我们实际上是在`EL2`中执行,然后才能调用相应的代码过渡到`EL1`
43+
因此,我们在`boot.s`的顶部添加了一个新的检查,如果CPU核心不在`EL2`中,则将其停止。
44+
45+
```
46+
// Only proceed if the core executes in EL2. Park it otherwise.
47+
mrs x0, CurrentEL
48+
cmp x0, {CONST_CURRENTEL_EL2}
49+
b.ne .L_parking_loop
50+
```
51+
52+
接下来,在`boot.rs`中继续准备从`EL2``EL1`的过渡,通过调用`prepare_el2_to_el1_transition()`函数。
53+
54+
```rust
55+
#[no_mangle]
56+
pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! {
57+
prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr);
58+
59+
// Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1.
60+
asm::eret()
61+
}
62+
```
63+
64+
## 过渡准备
65+
66+
由于`EL2``EL1`更具特权,它可以控制各种处理器功能,并允许或禁止`EL1`代码使用它们。
67+
其中一个例子是访问计时器和计数器寄存器。我们已经在[tutorial 07](../07_timestamps/)中使用了它们,所以当然我们希望保留它们。
68+
因此,我们在[Counter-timer Hypervisor Control register]中设置相应的标志,并将虚拟偏移量设置为零,以获取真实的物理值。
69+
70+
[Counter-timer Hypervisor Control register]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/cnthctl_el2.rs.html
71+
72+
```rust
73+
// Enable timer counter registers for EL1.
74+
CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
75+
76+
// No offset for reading the counters.
77+
CNTVOFF_EL2.set(0);
78+
```
79+
80+
接下来,我们配置[Hypervisor Configuration Register],使`EL1``AArch64`模式下运行,而不是在`AArch32`模式下运行,这也是可能的。
81+
82+
[Hypervisor Configuration Register]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/hcr_el2.rs.html
83+
84+
```rust
85+
// Set EL1 execution state to AArch64.
86+
HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64);
87+
```
88+
89+
## 从未发生的异常中返回
90+
91+
实际上,从较高的EL过渡到较低的EL只有一种方式,即通过执行[ERET]指令。
92+
93+
[ERET]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/asm.rs.html#92-101
94+
95+
在这个指令中,它将会将[Saved Program Status Register - EL2]的内容复制到`Current Program Status Register - EL1`,并跳转到存储在[Exception Link Register - EL2]
96+
97+
这基本上是在发生异常时所发生的相反过程。您将在即将发布的教程中了解更多相关内容。
98+
99+
[Saved Program Status Register - EL2]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/spsr_el2.rs.html
100+
[Exception Link Register - EL2]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/elr_el2.rs.html
101+
102+
```rust
103+
// Set up a simulated exception return.
104+
//
105+
// First, fake a saved program status where all interrupts were masked and SP_EL1 was used as a
106+
// stack pointer.
107+
SPSR_EL2.write(
108+
SPSR_EL2::D::Masked
109+
+ SPSR_EL2::A::Masked
110+
+ SPSR_EL2::I::Masked
111+
+ SPSR_EL2::F::Masked
112+
+ SPSR_EL2::M::EL1h,
113+
);
114+
115+
// Second, let the link register point to kernel_init().
116+
ELR_EL2.set(crate::kernel_init as *const () as u64);
117+
118+
// Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. Since there
119+
// are no plans to ever return to EL2, just re-use the same stack.
120+
SP_EL1.set(phys_boot_core_stack_end_exclusive_addr);
121+
```
122+
123+
正如您所看到的,我们将`ELR_EL2`的值设置为之前直接从入口点调用的`kernel_init()`函数的地址。最后,我们设置了`SP_EL1`的堆栈指针。
124+
125+
您可能已经注意到,堆栈的地址作为函数参数进行了传递。正如您可能记得的,在`boot.s``_start()`函数中,
126+
我们已经为`EL2`设置了堆栈。由于没有计划返回到`EL2`,我们可以直接重用相同的堆栈作为`EL1`的堆栈,
127+
因此使用函数参数将其地址传递。
128+
129+
最后,在`_start_rust()`函数中调用了`ERET`指令。
130+
131+
```rust
132+
#[no_mangle]
133+
pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! {
134+
prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr);
135+
136+
// Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1.
137+
asm::eret()
138+
}
139+
```
140+
141+
## 测试
142+
143+
`main.rs`中,我们打印`current privilege level`,并额外检查`SPSR_EL2`中的掩码位是否传递到了`EL1`
144+
145+
```console
146+
$ make chainboot
147+
[...]
148+
Minipush 1.0
149+
150+
[MP] ⏳ Waiting for /dev/ttyUSB0
151+
[MP] ✅ Serial connected
152+
[MP] 🔌 Please power the target now
153+
154+
__ __ _ _ _ _
155+
| \/ (_)_ _ (_) | ___ __ _ __| |
156+
| |\/| | | ' \| | |__/ _ \/ _` / _` |
157+
|_| |_|_|_||_|_|____\___/\__,_\__,_|
158+
159+
Raspberry Pi 3
160+
161+
[ML] Requesting binary
162+
[MP] ⏩ Pushing 14 KiB =========================================🦀 100% 0 KiB/s Time: 00:00:00
163+
[ML] Loaded! Executing the payload now
164+
165+
[ 0.162546] mingo version 0.9.0
166+
[ 0.162745] Booting on: Raspberry Pi 3
167+
[ 0.163201] Current privilege level: EL1
168+
[ 0.163677] Exception handling state:
169+
[ 0.164122] Debug: Masked
170+
[ 0.164511] SError: Masked
171+
[ 0.164901] IRQ: Masked
172+
[ 0.165291] FIQ: Masked
173+
[ 0.165681] Architectural timer resolution: 52 ns
174+
[ 0.166255] Drivers loaded:
175+
[ 0.166592] 1. BCM PL011 UART
176+
[ 0.167014] 2. BCM GPIO
177+
[ 0.167371] Timer test, spinning for 1 second
178+
[ 1.167904] Echoing input now
179+
```
180+
181+
## 相比之前的变化(diff)
182+
请检查[英文版本](README.md#diff-to-previous),这是最新的。

0 commit comments

Comments
 (0)