diff --git a/05_drivers_gpio_uart/README.CN.md b/05_drivers_gpio_uart/README.CN.md
new file mode 100644
index 000000000..7999da7b3
--- /dev/null
+++ b/05_drivers_gpio_uart/README.CN.md
@@ -0,0 +1,138 @@
+# 教程 05 - 驱动程序: GPIO和UART
+
+## tl;dr
+
+- 添加了用于真实`UART`和`GPIO`控制器的驱动程序。
+- **我们将首次能够在真实硬件上运行代码** (请向下滚动查看说明)。
+
+## 简介
+
+在上一篇教程中,我们启用了全局安全变量,为添加第一个真实设备驱动程序奠定了基础。
+我们放弃了神奇的QEMU控制台,并引入了一个`驱动程序管理器`,允许`BSP`将设备驱动程序注册到`内核`中。
+
+## 驱动程序管理器
+
+第一步是向内核添加一个`driver subsystem`。相应的代码将位于`src/driver.rs`中。
+该子系统引入了`interface::DeviceDriver`,这是每个设备驱动程序都需要实现的通用特征,并为内核所知。
+在同一文件中实例化的全局`DRIVER_MANAGER`实例(类型为`DriverManager`)作为一个中央实体,可以被调用来管理内核中的所有设备驱动程序。
+例如,通过使用全局可访问的`crate::driver::driver_manager().register_driver(...)`,任何代码都可以注册一个实现了`interface::DeviceDriver`特征的具有静态生命周期的对象。
+
+在内核初始化期间,调用`crate::driver::driver_manager().init_drivers(...)`将使驱动程序管理器遍历所有已注册的驱动程序,
+并启动它们的初始化,并执行可选的`post-init callback`,该回调可以与驱动程序一起注册。
+例如,此机制用于在`UART`驱动程序初始化后将其切换为主系统控制台的驱动程序。
+
+## BSP驱动程序实现
+
+在`src/bsp/raspberrypi/driver.rs`中,函数`init()`负责注册`UART`和`GPIO`驱动程序。
+因此,在内核初始化期间,按照以下来自`main.rs`的代码,正确的顺序是:
+(i)首先初始化BSP驱动程序子系统,然后(ii)调用`driver_manager()`。
+
+```rust
+unsafe fn kernel_init() -> ! {
+ // Initialize the BSP driver subsystem.
+ if let Err(x) = bsp::driver::init() {
+ panic!("Error initializing BSP driver subsystem: {}", x);
+ }
+
+ // Initialize all device drivers.
+ driver::driver_manager().init_drivers();
+ // println! is usable from here on.
+```
+
+
+
+驱动程序本身存储在`src/bsp/device_driver`中,并且可以在不同的`BSP`之间重复使用
+在这些教程中添加的第一个驱动程序是`PL011Uart`驱动程序:它实现了`console::interface::*`特征,并且从现在开始用作主系统控制台。
+第二个驱动程序是`GPIO`驱动程序,它根据需要将`RPii's`的`UART`映射(即将来自`SoC`内部的信号路由到实际的硬件引脚)。
+请注意,`GPIO`驱动程序区分**RPi 3**和**RPi 4**。它们的硬件不同,因此我们必须在软件中进行适配。
+
+现在,`BSP`还包含了一个内存映射表,位于`src/bsp/raspberrypi/memory.rs`中。它提供了树莓派的`MMIO`地址,
+`BSP`使用这些地址来实例化相应的设备驱动程序,以便驱动程序代码知道在内存中找到设备的寄存器的位置。
+
+## SD卡启动
+
+由于我们现在有了真实的`UART`输出,我们可以在真实的硬件上运行代码。
+由于前面提到的`GPIO`驱动程序的差异,构建过程在**RPi 3**和**RPi 4**之间有所区别。
+默认情况下,所有的`Makefile`目标都将为**RPi 3**构建。
+为了**RPi 4**构建,需要在每个目标前加上`BSP=rpi4`。例如:
+
+```console
+$ BSP=rpi4 make
+$ BSP=rpi4 make doc
+```
+
+不幸的是,QEMU目前还不支持**RPi 4**,因此`BSP=rpi4 make qemu`无法工作。
+
+**准备SD卡的一些步骤在RPi3和RPi4之间有所不同,请在以下操作中小心。**
+
+### 通用步骤
+
+1. 创建一个名为`boot`的`FAT32`分区。
+2. 在SD卡上生成一个名为`config.txt`的文件,并将以下内容写入其中:
+
+```txt
+arm_64bit=1
+init_uart_clock=48000000
+```
+### RPi 3
+
+3. 从[Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/master/boot)中将以下文件复制到SD卡上:
+ - [bootcode.bin](https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin)
+ - [fixup.dat](https://github.com/raspberrypi/firmware/raw/master/boot/fixup.dat)
+ - [start.elf](https://github.com/raspberrypi/firmware/raw/master/boot/start.elf)
+4. 运行`make`命令。
+
+### RPi 4
+
+3. 从[Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/master/boot)中将以下文件复制到SD卡上:
+ - [fixup4.dat](https://github.com/raspberrypi/firmware/raw/master/boot/fixup4.dat)
+ - [start4.elf](https://github.com/raspberrypi/firmware/raw/master/boot/start4.elf)
+ - [bcm2711-rpi-4-b.dtb](https://github.com/raspberrypi/firmware/raw/master/boot/bcm2711-rpi-4-b.dtb)
+4. 运行`BSP=rpi4 make`命令。
+
+
+_**注意**: 如果在您的RPi4上无法正常工作,请尝试将`start4.elf`重命名为`start.elf` (不带4)
+并复制到SD卡上。_
+
+### 再次通用步骤
+
+5. 将`kernel8.img`复制到SD卡上,并将SD卡插入RPi。
+6. 运行`miniterm` target,在主机上打开UART设备:
+
+```console
+$ make miniterm
+```
+
+> ❗ **注意**: `Miniterm`假设默认的串行设备名称为`/dev/ttyUSB0`。Depending on your
+> 根据您的主机操作系统,设备名称可能会有所不同。例如,在`macOS`上,它可能是
+> `/dev/tty.usbserial-0001`之类的。在这种情况下,请明确提供设备名称:
+
+
+```console
+$ DEV_SERIAL=/dev/tty.usbserial-0001 make miniterm
+```
+
+7. 将USB串口连接到主机PC。
+ - 请参考[top-level README](../README.md#-usb-serial-output)中的接线图。
+ - **注意**: TX(发送)线连接到RX(接收)引脚。
+ - 确保您**没有**连接USB串口的电源引脚,只连接RX/TX和GND引脚。
+8. 将RPi连接到(USB)电源线,并观察输出。
+
+```console
+Miniterm 1.0
+
+[MT] ⏳ Waiting for /dev/ttyUSB0
+[MT] ✅ Serial connected
+[0] mingo version 0.5.0
+[1] Booting on: Raspberry Pi 3
+[2] Drivers loaded:
+ 1. BCM PL011 UART
+ 2. BCM GPIO
+[3] Chars written: 117
+[4] Echoing input now
+```
+
+8. 通过按下ctrl-c退出。
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
diff --git a/05_drivers_gpio_uart/README.md b/05_drivers_gpio_uart/README.md
index e6e5dd648..5e96d40a0 100644
--- a/05_drivers_gpio_uart/README.md
+++ b/05_drivers_gpio_uart/README.md
@@ -54,8 +54,8 @@ The drivers themselves are stored in `src/bsp/device_driver`, and can be reused
first driver added in these tutorials is the `PL011Uart` driver: It implements the
`console::interface::*` traits and is from now on used as the main system console. The second driver
is the `GPIO` driver, which pinmuxes (that is, routing signals from inside the `SoC` to actual HW
-pins) the RPi's PL011 UART accordingly. Note how the `GPIO` driver differentiates between **RPi3**
-and **RPi4**. Their HW is different, so we have to account for it in SW.
+pins) the RPi's PL011 UART accordingly. Note how the `GPIO` driver differentiates between **RPi 3**
+and **RPi 4**. Their HW is different, so we have to account for it in SW.
The `BSP`s now also contain a memory map in `src/bsp/raspberrypi/memory.rs`. It provides the
Raspberry's `MMIO` addresses which are used by the `BSP` to instantiate the respective device
@@ -64,18 +64,18 @@ drivers, so that the driver code knows where to find the device's registers in m
## Boot it from SD card
Since we have real `UART` output now, we can run the code on the real hardware. Building is
-differentiated between the **RPi 3** and the **RPi4** due to before mentioned differences in the
+differentiated between the **RPi 3** and the **RPi 4** due to before mentioned differences in the
`GPIO` driver. By default, all `Makefile` targets will build for the **RPi 3**. In order to build
-for the the **RPi4**, prepend `BSP=rpi4` to each target. For example:
+for the the **RPi 4**, prepend `BSP=rpi4` to each target. For example:
```console
$ BSP=rpi4 make
$ BSP=rpi4 make doc
```
-Unfortunately, QEMU does not yet support the **RPi4**, so `BSP=rpi4 make qemu` won't work.
+Unfortunately, QEMU does not yet support the **RPi 4**, so `BSP=rpi4 make qemu` won't work.
-**Some steps for preparing the SD card differ between RPi3 and RPi4, so be careful in the
+**Some steps for preparing the SD card differ between RPi 3 and RPi 4, so be careful in the
following.**
### Common for both
@@ -87,7 +87,7 @@ following.**
arm_64bit=1
init_uart_clock=48000000
```
-### Pi 3
+### RPi 3
3. Copy the following files from the [Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/master/boot) onto the SD card:
- [bootcode.bin](https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin)
@@ -95,7 +95,7 @@ init_uart_clock=48000000
- [start.elf](https://github.com/raspberrypi/firmware/raw/master/boot/start.elf)
4. Run `make`.
-### Pi 4
+### RPi 4
3. Copy the following files from the [Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/master/boot) onto the SD card:
- [fixup4.dat](https://github.com/raspberrypi/firmware/raw/master/boot/fixup4.dat)
@@ -104,7 +104,7 @@ init_uart_clock=48000000
4. Run `BSP=rpi4 make`.
-_**Note**: Should it not work on your RPi4, try renaming `start4.elf` to `start.elf` (without the 4)
+_**Note**: Should it not work on your RPi 4, try renaming `start4.elf` to `start.elf` (without the 4)
on the SD card._
### Common again
diff --git a/06_uart_chainloader/README.CN.md b/06_uart_chainloader/README.CN.md
new file mode 100644
index 000000000..de7f51114
--- /dev/null
+++ b/06_uart_chainloader/README.CN.md
@@ -0,0 +1,116 @@
+# 教程06 - UART链加载器
+
+## tl;dr
+
+- 从SD卡上运行是一次不错的体验,但是每次都为每个新的二进制文件这样做将非常繁琐。
+ 因此,让我们编写一个[chainloader]。
+- 这将是您需要放在SD卡上的最后一个二进制文件。
+ 每个后续的教程都将在`Makefile`中提供一个`chainboot`,让您方便地通过`UART`加载内核。
+
+[chainloader]: https://en.wikipedia.org/wiki/Chain_loading
+
+
+## 注意
+
+请注意,这个教程中有一些内容仅通过查看源代码很难理解。
+
+大致的意思是,在`boot.s`中,我们编写了一段[position independent code]代码,
+它会自动确定固件加载二进制文件的位置(`0x8_0000`),以及链接到的位置(`0x200_0000`,参见 `kernel.ld`)。
+然后,二进制文件将自身从加载地址复制到链接地址(也就是"重定位"自身),然后跳转到`_start_rust()`的重定位版本。
+
+由于链加载程序现在已经"脱离了路径",它现在可以从`UART`接收另一个内核二进制文件,并将其复制到RPi固件的标准加载地址`0x8_0000`。
+最后,它跳转到`0x8_0000`,新加载的二进制文件会透明地执行,就好像它一直从SD卡加载一样。
+
+在我有时间详细写下这些内容之前,请耐心等待。目前,请将这个教程视为一种便利功能的启用程序,它允许快速启动以下教程。
+_对于那些渴望深入了解的人,可以直接跳到第[15章](../15_virtual_mem_part3_precomputed_tables),阅读README的前半部分,
+其中讨论了`Load Address != Link Address`的问题_。
+
+[position independent code]: https://en.wikipedia.org/wiki/Position-independent_code
+
+## 安装并测试它
+
+我们的链加载程序称为`MiniLoad`,受到了[raspbootin]的启发。
+
+您可以按照以下教程尝试它:
+1. 根据您的目标硬件运行命令:`make`或`BSP=rpi4 make`。
+1. 将`kernel8.img`复制到SD卡中,并将SD卡重新插入您的RPi。
+1. 运行命令`make chainboot`或`BSP=rpi4 make chainboot`。
+1. 将USB串口连接到您的主机PC上。
+ - 请参考[top-level README](../README.md#-usb-serial-output)中的接线图。
+ - 确保您**没有**连接USB串口的电源引脚,只连接RX/TX和GND。
+1. 将RPi连接到(USB)电源线。
+1. 观察加载程序通过`UART`获取内核:
+
+> ❗ **注意**: `make chainboot`假设默认的串行设备名称为`/dev/ttyUSB0`。根据您的主机操作系统,设备名称可能会有所不同。
+> 例如,在`macOS`上,它可能是类似于`/dev/tty.usbserial-0001`的名称。
+> 在这种情况下,请明确给出设备名称:
+
+
+```console
+$ DEV_SERIAL=/dev/tty.usbserial-0001 make chainboot
+```
+
+[raspbootin]: https://github.com/mrvn/raspbootin
+
+```console
+$ make chainboot
+[...]
+Minipush 1.0
+
+[MP] ⏳ Waiting for /dev/ttyUSB0
+[MP] ✅ Serial connected
+[MP] 🔌 Please power the target now
+
+ __ __ _ _ _ _
+| \/ (_)_ _ (_) | ___ __ _ __| |
+| |\/| | | ' \| | |__/ _ \/ _` / _` |
+|_| |_|_|_||_|_|____\___/\__,_\__,_|
+
+ Raspberry Pi 3
+
+[ML] Requesting binary
+[MP] ⏩ Pushing 7 KiB ==========================================🦀 100% 0 KiB/s Time: 00:00:00
+[ML] Loaded! Executing the payload now
+
+[0] mingo version 0.5.0
+[1] Booting on: Raspberry Pi 3
+[2] Drivers loaded:
+ 1. BCM PL011 UART
+ 2. BCM GPIO
+[3] Chars written: 117
+[4] Echoing input now
+```
+
+在这个教程中,为了演示目的,加载了上一个教程中的内核版本。在后续的教程中,将使用工作目录的内核。
+
+## 测试它
+
+这个教程中的`Makefile`有一个额外的目标`qemuasm`,它可以让你很好地观察到内核在重新定位后如何从加载地址区域(`0x80_XXX`)
+跳转到重新定位的代码(`0x0200_0XXX`):
+
+```console
+$ make qemuasm
+[...]
+N:
+0x00080030: 58000140 ldr x0, #0x80058
+0x00080034: 9100001f mov sp, x0
+0x00080038: 58000141 ldr x1, #0x80060
+0x0008003c: d61f0020 br x1
+
+----------------
+IN:
+0x02000070: 9400044c bl #0x20011a0
+
+----------------
+IN:
+0x020011a0: 90000008 adrp x8, #0x2001000
+0x020011a4: 90000009 adrp x9, #0x2001000
+0x020011a8: f9446508 ldr x8, [x8, #0x8c8]
+0x020011ac: f9446929 ldr x9, [x9, #0x8d0]
+0x020011b0: eb08013f cmp x9, x8
+0x020011b4: 54000109 b.ls #0x20011d4
+[...]
+```
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
\ No newline at end of file
diff --git a/07_timestamps/README.CN.md b/07_timestamps/README.CN.md
new file mode 100644
index 000000000..1192996d3
--- /dev/null
+++ b/07_timestamps/README.CN.md
@@ -0,0 +1,45 @@
+# 教程 07 - 时间戳
+
+## tl;dr
+
+- 我们为计时器硬件添加了抽象,并在`_arch/aarch64`中实现了ARM架构计时器。
+- 新的计时器函数用于给UART打印添加时间戳,并且用于消除`GPIO`设备驱动中基于周期的延迟,从而提高准确性。
+- 添加了`warn!()`宏。
+
+## 测试它
+
+请通过 chainboot 进行检查(在上一个教程中添加)。
+```console
+$ make chainboot
+[...]
+Minipush 1.0
+
+[MP] ⏳ Waiting for /dev/ttyUSB0
+[MP] ✅ Serial connected
+[MP] 🔌 Please power the target now
+
+ __ __ _ _ _ _
+| \/ (_)_ _ (_) | ___ __ _ __| |
+| |\/| | | ' \| | |__/ _ \/ _` / _` |
+|_| |_|_|_||_|_|____\___/\__,_\__,_|
+
+ Raspberry Pi 3
+
+[ML] Requesting binary
+[MP] ⏩ Pushing 12 KiB =========================================🦀 100% 0 KiB/s Time: 00:00:00
+[ML] Loaded! Executing the payload now
+
+[ 0.143123] mingo version 0.7.0
+[ 0.143323] Booting on: Raspberry Pi 3
+[ 0.143778] Architectural timer resolution: 52 ns
+[ 0.144352] Drivers loaded:
+[ 0.144688] 1. BCM PL011 UART
+[ 0.145110] 2. BCM GPIO
+[W 0.145469] Spin duration smaller than architecturally supported, skipping
+[ 0.146313] Spinning for 1 second
+[ 1.146715] Spinning for 1 second
+[ 2.146938] Spinning for 1 second
+```
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
diff --git a/08_hw_debug_JTAG/README.CN.md b/08_hw_debug_JTAG/README.CN.md
new file mode 100644
index 000000000..b77032101
--- /dev/null
+++ b/08_hw_debug_JTAG/README.CN.md
@@ -0,0 +1,288 @@
+# 教程 08 - 使用JTAG进行硬件调试
+
+## tl;dr
+
+按照以下顺序进行操作:
+
+1. 运行`make jtagboot`并保持终端打开。
+2. 连接USB串行设备。
+3. 连接`JTAG`调试器的USB设备。
+4. 在新的终端中,运行`make openocd`并保持终端打开。
+5. 在新的终端中,运行`make gdb`或者运行`make gdb-opt0`。
+
+
+
+## 目录
+
+- [简介](#简介)
+- [大纲](#大纲)
+- [软件设置](#软件设置)
+- [硬件设置](#硬件设置)
+ * [线路](#线路)
+- [准备连接](#准备连接)
+- [OpenOCD](#openocd)
+- [GDB](#gdb)
+ * [备注](#备注)
+ + [优化](#优化)
+ + [GDB控制](#GDB控制)
+- [关于USB连接限制的注意事项](#关于USB连接限制的注意事项)
+- [额外资料](#额外资料)
+- [致谢](#致谢)
+- [相比之前的变化(diff)](#相比之前的变化(diff))
+
+## 简介
+
+在即将到来的教程中,我们将涉及RPi的SoC(系统芯片)的敏感区域,这可能会让我们的调试工作变得非常困难。
+例如,改变处理器的`Privilege Level`或引入`Virtual Memory`。
+
+硬件调试器有时可以成为寻找棘手错误的最后手段。特别是对于调试复杂的、与体系结构相关的硬件问题,它将非常有用,
+因为在这个领域,`QEMU`有时无法提供帮助,因为它对硬件的某些特性进行了抽象,并没有模拟到最后一位。
+
+那么,让我们介绍一下`JTAG`调试。一旦设置好,它将允许我们在真实的硬件上逐步执行我们的内核。这是多么酷啊!
+
+## 大纲
+
+从内核的角度来看,这个教程与之前的教程相同。我们只是在其周围添加了用于JTAG调试的基础设施。
+
+## 软件设置
+
+我们需要在SD卡的`config.txt`文件中添加另一行:
+
+```toml
+arm_64bit=1
+init_uart_clock=48000000
+enable_jtag_gpio=1
+```
+
+## 硬件设置
+
+与我们WG的[Embedded Rust Book]书籍中使用的`STM32F3DISCOVERY`等微控制器板不同,RPi没有在其板上内置调试器。
+因此,您需要购买一个。
+
+在本教程中,我们将使用OLIMEX的[ARM-USB-TINY-H]。它具有标准的[ARM JTAG 20 connector]。
+不幸的是,RPi没有这个连接器,所以我们必须通过跳线连接它。
+
+[Embedded Rust Book]: https://rust-embedded.github.io/book/start/hardware.html
+[ARM-USB-TINY-H]: https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H
+[ARM JTAG 20 connector]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0499dj/BEHEIHCE.html
+
+### 线路
+
+
+
+
+ | GPIO # |
+ Name |
+ JTAG # |
+ Note |
+ Diagram |
+
+
+
+
+ |
+ VTREF |
+ 1 |
+ to 3.3V |
+  |
+
+
+ |
+ GND |
+ 4 |
+ to GND |
+
+
+ | 22 |
+ TRST |
+ 3 |
+ |
+
+
+ | 26 |
+ TDI |
+ 5 |
+ |
+
+
+ | 27 |
+ TMS |
+ 7 |
+ |
+
+
+ | 25 |
+ TCK |
+ 9 |
+ |
+
+
+ | 23 |
+ RTCK |
+ 11 |
+ |
+
+
+ | 24 |
+ TDO |
+ 13 |
+ |
+
+
+
+
+
+
+## 准备连接
+
+在启动时,由于我们对`config.txt`进行的更改,RPi的固件将配置相应的GPIO引脚以实现`JTAG`功能。
+
+现在剩下的要做的就是暂停RPi的执行,然后通过`JTAG`进行连接。因此,我们添加了一个新的`Makefile` target,
+`make jtagboot`,它使用`chainboot`方法将一个小型辅助二进制文件加载到RPi上,
+该文件只是将执行核心置于等待状态。
+
+文件夹中单独[X1_JTAG_boot]文件夹中单独维护,并且是我们迄今为止在教程中使用的内核的修改版本。
+
+[X1_JTAG_boot]: ../X1_JTAG_boot
+
+```console
+$ make jtagboot
+Minipush 1.0
+
+[MP] ⏳ Waiting for /dev/ttyUSB0
+[MP] ✅ Serial connected
+[MP] 🔌 Please power the target now
+ __ __ _ _ _ _
+| \/ (_)_ _ (_) | ___ __ _ __| |
+| |\/| | | ' \| | |__/ _ \/ _` / _` |
+|_| |_|_|_||_|_|____\___/\__,_\__,_|
+
+ Raspberry Pi 3
+
+[ML] Requesting binary
+[MP] ⏩ Pushing 7 KiB ==========================================🦀 100% 0 KiB/s Time: 00:00:00
+[ML] Loaded! Executing the payload now
+
+[ 0.394532] Parking CPU core. Please connect over JTAG now.
+```
+
+保持USB串口连接和打开运行`jtagboot`的终端非常重要。当我们稍后加载实际的内核时,`UART`输出将显示在这里。
+
+## OpenOCD
+
+接下来,我们需要启动开放式片上调试器 [Open On-Chip Debugger],也称为`OpenOCD`,以实际连接`JTAG`。
+
+[Open On-Chip Debugger]: http://openocd.org
+
+一如既往,我们的教程力求使开发工具的使用尽可能简单,
+这就是为什么我们将所有内容打包到了[dedicated Docker container]中,该容器已经用于链式引导和`QEMU`。
+
+[dedicated Docker container]: ../docker/rustembedded-osdev-utils
+
+连接Olimex USB JTAG调试器,在同一个文件夹中打开一个新的终端窗口,然后按顺序输入
+`make openocd`命令。你将会看到一些初始输出:
+
+```console
+$ make openocd
+[...]
+Open On-Chip Debugger 0.10.0
+[...]
+Info : Listening on port 6666 for tcl connections
+Info : Listening on port 4444 for telnet connections
+Info : clock speed 1000 kHz
+Info : JTAG tap: rpi3.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4)
+Info : rpi3.core0: hardware has 6 breakpoints, 4 watchpoints
+Info : rpi3.core1: hardware has 6 breakpoints, 4 watchpoints
+Info : rpi3.core2: hardware has 6 breakpoints, 4 watchpoints
+Info : rpi3.core3: hardware has 6 breakpoints, 4 watchpoints
+Info : Listening on port 3333 for gdb connections
+Info : Listening on port 3334 for gdb connections
+Info : Listening on port 3335 for gdb connections
+Info : Listening on port 3336 for gdb connections
+```
+
+`OpenOCD`已检测到RPi的四个核心,并打开了四个网络端口,`gdb`现在可以连接到这些端口来调试各自的核心。
+
+## GDB
+
+最后,我们需要一个支持`AArch64`的`gdb`版本。你猜对了,它已经打包在osdev容器中。
+可以通过`make gdb`命令启动它。
+
+实际上,这个Makefile target做了更多的事情。它构建了一个包含调试信息的特殊版本的内核。
+这使得`gdb`能够显示我们当前正在调试的`Rust`源代码行。
+它还启动了`gdb`,以便它已经加载了这个调试构建(`kernel_for_jtag`)。
+
+现在我们可以使用`gdb`命令行来进行以下操作:
+ 1. 在我们的内核中设置断点。
+ 2. 通过JTAG将内核加载到内存中(请记住,当前RPi仍在执行最小的JTAG引导二进制文件)。
+ 3. 操纵RPi的程序计数器,使其从我们内核的入口点开始执行。
+ 4. 逐步执行内核的执行过程。
+
+```console
+$ make gdb
+[...]
+>>> target remote :3333 # Connect to OpenOCD, core0
+>>> load # Load the kernel into the RPi's DRAM over JTAG.
+Loading section .text, size 0x2454 lma 0x80000
+Loading section .rodata, size 0xa1d lma 0x82460
+Loading section .got, size 0x10 lma 0x82e80
+Loading section .data, size 0x20 lma 0x82e90
+Start address 0x0000000000080000, load size 11937
+Transfer rate: 63 KB/sec, 2984 bytes/write.
+>>> set $pc = 0x80000 # Set RPI's program counter to the start of the
+ # kernel binary.
+>>> break main.rs:158
+Breakpoint 1 at 0x8025c: file src/main.rs, line 158.
+>>> cont
+>>> step # Single-step through the kernel
+>>> step
+>>> ...
+```
+
+### 备注
+
+#### 优化
+
+在调试操作系统二进制文件时,您需要在可以逐步执行源代码粒度和生成的二进制文件的优化级别之间进行权衡。
+`make`和`make gdb`targets生成一个`--release`二进制文件,其中包含优化级别为3(`-opt-level=3`)。
+然而,在这种情况下,编译器会非常积极地进行内联,并尽可能地将读取和写入操作打包在一起。
+因此,不总是能够在源代码文件的特定行上准确命中断点。
+
+因此,Makefile还提供了`make gdb-opt0` target,它使用了`-opt-level=0`。
+因此,它将允许您拥有更精细的调试粒度。然而,请记住,当调试与硬件密切相关的代码时,
+编译器对易失性寄存器的读取或写入进行压缩的优化可能会对执行产生重大影响。
+请注意,上面的演示GIF是使用`gdb-opt0`录制的。
+
+#### GDB控制
+
+在某些情况下,您可能会遇到延迟循环或等待串行输入的代码。在这种情况下,
+逐步执行可能不可行或无法正常工作。您可以通过在这些区域之外设置其他断点,从而跳过这些障碍。
+并使用`cont`命令到达它们。
+
+在`gdb`中按下`ctrl+c`将再次停止RPi的执行,以防止您在没有进一步断点的情况下继续执行。
+
+## 关于USB连接限制的注意事项
+
+如果您按照教程从头到尾进行操作,关于USB连接的一切应该都没问题。
+
+但是,请注意,根据当前的形式,我们的`Makefile`对连接的USB设备的命名做出了隐含的假设。
+它期望`/dev/ttyUSB0`是`UART`设备。
+
+因此,请确保按照以下顺序将设备连接到您的计算机:
+ 1. 首先连接USB串行设备。
+ 2. 然后连接Olimex调试器。
+
+这样,主机操作系统会相应地枚举这些设备。这只需要做一次即可。
+可以多次断开和连接串行设备,例如在保持调试器连接的情况下启动不同的`make jtagboot`运行。
+
+## 额外资料
+
+- https://metebalci.com/blog/bare-metal-raspberry-pi-3b-jtag
+- https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag
+
+## 致谢
+
+感谢[@naotaco](https://github.com/naotaco)为本教程奠定了基础。
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
diff --git a/09_privilege_level/README.CN.md b/09_privilege_level/README.CN.md
new file mode 100644
index 000000000..80773a690
--- /dev/null
+++ b/09_privilege_level/README.CN.md
@@ -0,0 +1,182 @@
+# 教程 09 - 特权级别
+
+## tl;dr
+
+- 在早期引导代码中,我们从`Hypervisor`特权级别(AArch64中的`EL2`)过渡到`Kernel` (`EL1`)特权级别。
+
+## 目录
+
+- [介绍](#介绍)
+- [本教程的范围](#本教程的范围)
+- [在入口点检查EL2](#在入口点检查EL2)
+- [过渡准备](#过渡准备)
+- [从未发生的异常中返回](#从未发生的异常中返回)
+- [测试](#测试)
+- [相比之前的变化(diff)](#相比之前的变化(diff))
+
+## 介绍
+
+应用级别的CPU具有所谓的`privilege levels`,它们具有不同的目的:
+
+| Typically used for | AArch64 | RISC-V | x86 |
+| ------------- | ------------- | ------------- | ------------- |
+| Userspace applications | EL0 | U/VU | Ring 3 |
+| OS Kernel | EL1 | S/VS | Ring 0 |
+| Hypervisor | EL2 | HS | Ring -1 |
+| Low-Level Firmware | EL3 | M | |
+
+在AArch64中,`EL`代表`Exception Level`(异常级别)。如果您想获取有关其他体系结构的更多信息,请查看以下链接:
+- [x86 privilege rings](https://en.wikipedia.org/wiki/Protection_ring).
+- [RISC-V privilege modes](https://content.riscv.org/wp-content/uploads/2017/12/Tue0942-riscv-hypervisor-waterman.pdf).
+
+在继续之前,我强烈建议您先浏览一下[Programmer’s Guide for ARMv8-A]`的第3章`。它提供了关于该主题的简明概述。
+
+[Programmer’s Guide for ARMv8-A]: http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf
+
+## 本教程的范围
+
+默认情况下,树莓派将始终在`EL2`中开始执行。由于我们正在编写一个传统的`Kernel`,我们需要过渡到更合适的`EL1`。
+
+## 在入口点检查EL2
+
+首先,我们需要确保我们实际上是在`EL2`中执行,然后才能调用相应的代码过渡到`EL1`。
+因此,我们在`boot.s`的顶部添加了一个新的检查,如果CPU核心不在`EL2`中,则将其停止。
+
+```
+// Only proceed if the core executes in EL2. Park it otherwise.
+mrs x0, CurrentEL
+cmp x0, {CONST_CURRENTEL_EL2}
+b.ne .L_parking_loop
+```
+
+接下来,在`boot.rs`中继续准备从`EL2`到`EL1`的过渡,通过调用`prepare_el2_to_el1_transition()`函数。
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! {
+ prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr);
+
+ // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1.
+ asm::eret()
+}
+```
+
+## 过渡准备
+
+由于`EL2`比`EL1`更具特权,它可以控制各种处理器功能,并允许或禁止`EL1`代码使用它们。
+其中一个例子是访问计时器和计数器寄存器。我们已经在[tutorial 07](../07_timestamps/)中使用了它们,所以当然我们希望保留它们。
+因此,我们在[Counter-timer Hypervisor Control register]中设置相应的标志,并将虚拟偏移量设置为零,以获取真实的物理值。
+
+[Counter-timer Hypervisor Control register]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/cnthctl_el2.rs.html
+
+```rust
+// Enable timer counter registers for EL1.
+CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
+
+// No offset for reading the counters.
+CNTVOFF_EL2.set(0);
+```
+
+接下来,我们配置[Hypervisor Configuration Register],使`EL1`在`AArch64`模式下运行,而不是在`AArch32`模式下运行,这也是可能的。
+
+[Hypervisor Configuration Register]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/hcr_el2.rs.html
+
+```rust
+// Set EL1 execution state to AArch64.
+HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64);
+```
+
+## 从未发生的异常中返回
+
+实际上,从较高的EL过渡到较低的EL只有一种方式,即通过执行[ERET]指令。
+
+[ERET]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/asm.rs.html#92-101
+
+在这个指令中,它将会将[Saved Program Status Register - EL2]的内容复制到`Current Program Status Register - EL1`,并跳转到存储在[Exception Link Register - EL2]。
+
+这基本上是在发生异常时所发生的相反过程。您将在即将发布的教程中了解更多相关内容。
+
+[Saved Program Status Register - EL2]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/spsr_el2.rs.html
+[Exception Link Register - EL2]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/elr_el2.rs.html
+
+```rust
+// Set up a simulated exception return.
+//
+// First, fake a saved program status where all interrupts were masked and SP_EL1 was used as a
+// stack pointer.
+SPSR_EL2.write(
+ SPSR_EL2::D::Masked
+ + SPSR_EL2::A::Masked
+ + SPSR_EL2::I::Masked
+ + SPSR_EL2::F::Masked
+ + SPSR_EL2::M::EL1h,
+);
+
+// Second, let the link register point to kernel_init().
+ELR_EL2.set(crate::kernel_init as *const () as u64);
+
+// Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. Since there
+// are no plans to ever return to EL2, just re-use the same stack.
+SP_EL1.set(phys_boot_core_stack_end_exclusive_addr);
+```
+
+正如您所看到的,我们将`ELR_EL2`的值设置为之前直接从入口点调用的`kernel_init()`函数的地址。最后,我们设置了`SP_EL1`的堆栈指针。
+
+您可能已经注意到,堆栈的地址作为函数参数进行了传递。正如您可能记得的,在`boot.s`的`_start()`函数中,
+我们已经为`EL2`设置了堆栈。由于没有计划返回到`EL2`,我们可以直接重用相同的堆栈作为`EL1`的堆栈,
+因此使用函数参数将其地址传递。
+
+最后,在`_start_rust()`函数中调用了`ERET`指令。
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! {
+ prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr);
+
+ // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1.
+ asm::eret()
+}
+```
+
+## 测试
+
+在`main.rs`中,我们打印`current privilege level`,并额外检查`SPSR_EL2`中的掩码位是否传递到了`EL1`:
+
+```console
+$ make chainboot
+[...]
+Minipush 1.0
+
+[MP] ⏳ Waiting for /dev/ttyUSB0
+[MP] ✅ Serial connected
+[MP] 🔌 Please power the target now
+
+ __ __ _ _ _ _
+| \/ (_)_ _ (_) | ___ __ _ __| |
+| |\/| | | ' \| | |__/ _ \/ _` / _` |
+|_| |_|_|_||_|_|____\___/\__,_\__,_|
+
+ Raspberry Pi 3
+
+[ML] Requesting binary
+[MP] ⏩ Pushing 14 KiB =========================================🦀 100% 0 KiB/s Time: 00:00:00
+[ML] Loaded! Executing the payload now
+
+[ 0.162546] mingo version 0.9.0
+[ 0.162745] Booting on: Raspberry Pi 3
+[ 0.163201] Current privilege level: EL1
+[ 0.163677] Exception handling state:
+[ 0.164122] Debug: Masked
+[ 0.164511] SError: Masked
+[ 0.164901] IRQ: Masked
+[ 0.165291] FIQ: Masked
+[ 0.165681] Architectural timer resolution: 52 ns
+[ 0.166255] Drivers loaded:
+[ 0.166592] 1. BCM PL011 UART
+[ 0.167014] 2. BCM GPIO
+[ 0.167371] Timer test, spinning for 1 second
+[ 1.167904] Echoing input now
+```
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
diff --git a/10_virtual_mem_part1_identity_mapping/README.CN.md b/10_virtual_mem_part1_identity_mapping/README.CN.md
new file mode 100644
index 000000000..2b840e28c
--- /dev/null
+++ b/10_virtual_mem_part1_identity_mapping/README.CN.md
@@ -0,0 +1,319 @@
+# 教程10 - 虚拟内存第一部分:将所有内容进行身份映射!
+
+## tl;dr
+
+- 打开`MMU`。
+- 使用简单的方案:静态的`64 KiB`转换表。
+- 为了教学目的,我们将数据写入重新映射的`UART`,并对其他所有内容进行`identity map`。
+
+## 目录
+
+- [介绍](#introduction)
+- [MMU和分页理论](#MMU和分页理论)
+- [方法](#方法)
+ * [通用内核代码:`memory/mmu.rs`](#通用内核代码:`memory/mmu.rs`)
+ * [BSP:`bsp/raspberrypi/memory/mmu.rs`](#bsp-bspraspberrypimemorymmurs)
+ * [AArch64:`_arch/aarch64/memory/*`](#aarch64-_archaarch64memory)
+ * [`kernel.ld`](#kernelld)
+- [地址转换示例](#地址转换示例)
+ * [使用64 KiB页描述符进行地址转换](#使用64KiB页描述符进行地址转换)
+- [零成本抽象](#零成本抽象)
+- [测试](#测试)
+- [相比之前的变化(diff)](#相比之前的变化(diff))
+
+## 介绍
+
+虚拟内存是一个非常复杂但重要且强大的主题。在本教程中,我们从简单易懂的方式开始,
+通过打开`MMU`,使用静态转换表和一次性进行`identity-map`
+(除了为教育目的而重新映射的`UART`之外;在下一个教程中,这将被取消)。
+
+## MMU和分页理论
+
+在这一点上,我们不会重新发明轮子并详细描述现代应用级处理器中分页的工作原理。
+互联网上有很多关于这个主题的优秀资源,我们鼓励您阅读其中一些以获得对该主题的高层理解。
+
+继续阅读本`AArch64`特定的教程,我强烈建议您在此处停下来,首先阅读[ARM Cortex-A Series Programmer's Guide for ARMv8-A]的`第12章`,
+以便在继续之前获得所有所需的`AArch64`特定知识。
+
+已经阅读完`第12章`了吗?做得好 :+1:!
+
+[ARM Cortex-A Series Programmer's Guide for ARMv8-A]: http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf
+
+## 方法
+
+1. 通用的`kernel`部分:`src/memory/mmu.rs`及其子模块提供了与体系结构无关的描述符类型,
+ 用于组合一个高级数据结构,描述内核的虚拟内存布局:`memory::mmu::KernelVirtualLayout`。
+2. `BSP`部分:`src/bsp/raspberrypi/memory/mmu.rs`包含一个`KernelVirtualLayout`的静态实例,并通过函数
+ `bsp::memory::mmu::virt_mem_layout()`使其可访问。
+3. `aarch64`部分:`src/_arch/aarch64/memory/mmu.rs`及其子模块包含实际的`MMU`驱动程序。它使用`64 KiB`粒度获取
+ `BSP`的高级`KernelVirtualLayout`并进行映射。
+
+### 通用内核代码:`memory/mmu.rs`
+
+在这个文件中提供的描述符类型是构建块,用于描述不同内存区域的属性。
+例如,`R/W`(读/写)、`no-execute`(不执行)、`cached/uncached`(缓存/非缓存)等等。
+
+这些描述符与硬件`MMU`的实际描述符无关。不同的`BSP`可以使用这些类型来生成内核虚拟内存布局的高级描述。
+真实硬件的实际`MMU`驱动程序将使用这些类型作为输入。
+
+通过这种方式,我们在`BSP`和`_arch`代码之间实现了清晰的抽象,这样可以在不需要调整另一个的情况下进行交换。
+
+### BSP: `bsp/raspberrypi/memory/mmu.rs`
+
+这个文件包含了一个`KernelVirtualLayout`的实例,用于存储先前提到的描述符。
+将其放在`BSP`中是正确的位置,因为它具有目标板的内存映射知识。
+
+策略是只描述**不是**普通的、可缓存的DRAM的区域。然而,如果您希望,也可以定义这些区域。
+这里是一个设备MMIO区域的示例:
+
+```rust
+TranslationDescriptor {
+ name: "Device MMIO",
+ virtual_range: mmio_range_inclusive,
+ physical_range_translation: Translation::Identity,
+ attribute_fields: AttributeFields {
+ mem_attributes: MemAttributes::Device,
+ acc_perms: AccessPermissions::ReadWrite,
+ execute_never: true,
+ },
+},
+```
+
+`KernelVirtualLayout`本身实现了以下方法:
+
+```rust
+pub fn virt_addr_properties(
+ &self,
+ virt_addr: usize,
+) -> Result<(usize, AttributeFields), &'static str>
+```
+
+它将被`_arch/aarch64`的`MMU`代码使用,用于请求虚拟地址和转换的属性,该转换提供物理输出地址
+(返回元组中的`usize`)。该函数扫描包含查询地址的描述符,并返回第一个匹配的条目的相应结果。
+如果找不到条目,则返回普通可缓存DRAM的默认属性和输入地址,从而告诉`MMU`代码请求的地址应该是`identity mapped`。
+
+由于这种默认行为,不需要定义普通可缓存DRAM区域。
+
+### AArch64: `_arch/aarch64/memory/*`
+
+这些模块包含了`AArch64`的`MMU`驱动程序。粒度在这里被硬编码为(`64 KiB`页描述符)。
+
+在`translation_table.rs`中,有一个实际的转换表结构的定义,它对`LVL2`表的数量进行了泛化。
+后者取决于目标板的内存大小。自然地,`BSP`了解目标板的这些细节,并通过常量
+`bsp::memory::mmu::KernelAddrSpace::SIZE`提供大小信息。
+
+`translation_table.rs`使用这些信息来计算所需的`LVL2`表的数量。由于在`64 KiB`配置中,
+一个`LVL2`表可以覆盖`512 MiB`,所以只需要将`KernelAddrSpace::SIZE`除以`512 MiB`
+(有几个编译时检查确保`KernelAddrSpace::SIZE`是`512 MiB`的倍数)。
+
+最终的表类型被导出为`KernelTranslationTable`。以下是来自`translation_table.rs`的相关代码:
+
+```rust
+/// A table descriptor for 64 KiB aperture.
+///
+/// The output points to the next table.
+#[derive(Copy, Clone)]
+#[repr(C)]
+struct TableDescriptor {
+ value: u64,
+}
+
+/// A page descriptor with 64 KiB aperture.
+///
+/// The output points to physical memory.
+#[derive(Copy, Clone)]
+#[repr(C)]
+struct PageDescriptor {
+ value: u64,
+}
+
+const NUM_LVL2_TABLES: usize = bsp::memory::mmu::KernelAddrSpace::SIZE >> Granule512MiB::SHIFT;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Big monolithic struct for storing the translation tables. Individual levels must be 64 KiB
+/// aligned, hence the "reverse" order of appearance.
+#[repr(C)]
+#[repr(align(65536))]
+pub struct FixedSizeTranslationTable {
+ /// Page descriptors, covering 64 KiB windows per entry.
+ lvl3: [[PageDescriptor; 8192]; NUM_TABLES],
+
+ /// Table descriptors, covering 512 MiB windows.
+ lvl2: [TableDescriptor; NUM_TABLES],
+}
+
+/// A translation table type for the kernel space.
+pub type KernelTranslationTable = FixedSizeTranslationTable;
+```
+
+在`mmu.rs`中,`KernelTranslationTable`用于创建内核表的最终实例:
+
+```rust
+//--------------------------------------------------------------------------------------------------
+// Global instances
+//--------------------------------------------------------------------------------------------------
+
+/// The kernel translation tables.
+static mut KERNEL_TABLES: KernelTranslationTable = KernelTranslationTable::new();
+```
+
+它们在`MMU::init()`期间通过调用`KERNEL_TABLES.populate_tt_entries()`进行填充,
+该函数利用`bsp::memory::mmu::virt_mem_layout().virt_addr_properties()`和一系列实用函数,将内核通用描述符转换为
+`AArch64 MMU`硬件所需的实际`64 bit`整数条目,用于填充转换表数组。
+
+一个值得注意的事情是,每个页描述符都有一个索引(`AttrIndex`),它索引到[MAIR_EL1]寄存器,
+该寄存器保存了有关相应页面的缓存属性的信息。我们目前定义了普通可缓存内存和设备内存(不被缓存)。
+
+[MAIR_EL1]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500d/CIHDHJBB.html
+
+```rust
+impl MemoryManagementUnit {
+ /// Setup function for the MAIR_EL1 register.
+ fn set_up_mair(&self) {
+ // Define the memory types being mapped.
+ MAIR_EL1.write(
+ // Attribute 1 - Cacheable normal DRAM.
+ MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc +
+ MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc +
+
+ // Attribute 0 - Device.
+ MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck,
+ );
+ }
+```
+
+然后,[Translation Table Base Register 0 - EL1]使用`lvl2`表的基地址进行设置,同时配置[Translation Control Register - EL1]:
+
+```rust
+// Set the "Translation Table Base Register".
+TTBR0_EL1.set_baddr(KERNEL_TABLES.phys_base_address());
+
+self.configure_translation_control();
+```
+
+最后,通过[System Control Register - EL1]打开`MMU`。最后一步还启用了数据和指令的缓存。
+
+[Translation Table Base Register 0 - EL1]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/ttbr0_el1.rs.html
+[Translation Control Register - EL1]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/tcr_el1.rs.html
+[System Control Register - EL1]: https://docs.rs/aarch64-cpu/9.0.0/src/aarch64_cpu/registers/sctlr_el1.rs.html
+
+### `kernel.ld`
+
+我们需要将`code`段对齐到`64 KiB`,这样它就不会与下一个需要读/写属性而不是读/执行属性的部分重叠。
+
+```ld.s
+. = ALIGN(PAGE_SIZE);
+__code_end_exclusive = .;
+```
+
+这会增加二进制文件的大小,但考虑到与传统的`4 KiB`粒度相比,它显著减少了静态分页条目的数量,这是一个小小的代价。
+
+## 地址转换示例
+
+出于教育目的,定义了一个布局,允许通过两个不同的虚拟地址访问`UART`
+- 由于我们对整个`Device MMIO`区域进行了身份映射,所以在`MMU`打开后,可以通过断言其物理基地址
+ (`0x3F20_1000`或`0xFA20_1000`,取决于使用的是哪个RPi版本)来访问它。
+- 此外,它还映射到第一个`512 MiB`中的最后一个`64 KiB`槽位,使其可以通过基地址`0x1FFF_1000`访问。
+
+以下块图可视化了第二个映射的底层转换。
+
+### 使用64KiB页描述符进行地址转换
+
+
+
+## 零成本抽象
+
+初始化代码再次是展示Rust零成本抽象在嵌入式编程中巨大潜力的一个很好的例子[[1]][[2]]。
+
+让我们再次看一下使用[aarch64-cpu]crate设置`MAIR_EL1`寄存器的代码片段:
+
+[1]: https://blog.rust-lang.org/2015/05/11/traits.html
+[2]: https://ruudvanasseldonk.com/2016/11/30/zero-cost-abstractions
+[aarch64-cpu]: https://crates.io/crates/aarch64-cpu
+
+```rust
+/// Setup function for the MAIR_EL1 register.
+fn set_up_mair(&self) {
+ // Define the memory types being mapped.
+ MAIR_EL1.write(
+ // Attribute 1 - Cacheable normal DRAM.
+ MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc +
+ MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc +
+
+ // Attribute 0 - Device.
+ MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck,
+ );
+}
+```
+
+这段代码具有超强的表达能力,它利用`traits`,不同的`types`和`constants`来提供类型安全的寄存器操作。
+
+最后,此代码根据数据表将寄存器的前四个字节设置为特定值。查看生成的代码,
+我们可以看到,尽管有所有的类型安全和抽象,但它可以归结为两条汇编指令:
+
+```text
+ 800a8: 529fe089 mov w9, #0xff04 // #65284
+ 800ac: d518a209 msr mair_el1, x9
+```
+
+## 测试
+
+打开虚拟内存现在是我们在内核初始化过程中要做的第一件事:
+
+```rust
+unsafe fn kernel_init() -> ! {
+ use memory::mmu::interface::MMU;
+
+ if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() {
+ panic!("MMU: {}", string);
+ }
+```
+
+稍后在引导过程中,可以观察到有关映射的打印:
+
+```console
+$ make chainboot
+[...]
+Minipush 1.0
+
+[MP] ⏳ Waiting for /dev/ttyUSB0
+[MP] ✅ Serial connected
+[MP] 🔌 Please power the target now
+
+ __ __ _ _ _ _
+| \/ (_)_ _ (_) | ___ __ _ __| |
+| |\/| | | ' \| | |__/ _ \/ _` / _` |
+|_| |_|_|_||_|_|____\___/\__,_\__,_|
+
+ Raspberry Pi 3
+
+[ML] Requesting binary
+[MP] ⏩ Pushing 64 KiB =========================================🦀 100% 0 KiB/s Time: 00:00:00
+[ML] Loaded! Executing the payload now
+
+[ 0.811167] mingo version 0.10.0
+[ 0.811374] Booting on: Raspberry Pi 3
+[ 0.811829] MMU online. Special regions:
+[ 0.812306] 0x00080000 - 0x0008ffff | 64 KiB | C RO PX | Kernel code and RO data
+[ 0.813324] 0x1fff0000 - 0x1fffffff | 64 KiB | Dev RW PXN | Remapped Device MMIO
+[ 0.814310] 0x3f000000 - 0x4000ffff | 17 MiB | Dev RW PXN | Device MMIO
+[ 0.815198] Current privilege level: EL1
+[ 0.815675] Exception handling state:
+[ 0.816119] Debug: Masked
+[ 0.816509] SError: Masked
+[ 0.816899] IRQ: Masked
+[ 0.817289] FIQ: Masked
+[ 0.817679] Architectural timer resolution: 52 ns
+[ 0.818253] Drivers loaded:
+[ 0.818589] 1. BCM PL011 UART
+[ 0.819011] 2. BCM GPIO
+[ 0.819369] Timer test, spinning for 1 second
+[ !!! ] Writing through the remapped UART at 0x1FFF_1000
+[ 1.820409] Echoing input now
+```
+
+## 相比之前的变化(diff)
+请检查[英文版本](README.md#diff-to-previous),这是最新的。
diff --git a/11_exceptions_part1_groundwork/README.md b/11_exceptions_part1_groundwork/README.md
index 7de4e306c..a9c9b3179 100644
--- a/11_exceptions_part1_groundwork/README.md
+++ b/11_exceptions_part1_groundwork/README.md
@@ -55,7 +55,7 @@ In `AArch64`, it is differentiated between four types of exceptions. These are:
## Exception entry
-I recommend to read pages 1874-1876 of the [ARMv8 Architecture Reference Manual][ARMv8_Manual] to
+I recommend to read pages D1-5355 of the [ARMv8 Architecture Reference Manual][ARMv8_Manual Ja] to
understand the mechanisms of taking an exception.
Here's an excerpt of important features for this tutorial:
@@ -65,8 +65,8 @@ Here's an excerpt of important features for this tutorial:
- The preferred return address is saved in the `ELR_ELx` register.
- "Preferred" here means that `ELR_ELx` may hold the instruction address of the instructions that
caused the exception (`synchronous case`) or the first instruction that did not complete due to
- an `asynchronous` exception. Details in Chapter D1.10.1 of the [ARMv8 Architecture Reference
- Manual][ARMv8_Manual].
+ an `asynchronous` exception. Details in pages D1-5357 of the [ARMv8 Architecture Reference
+ Manual][ARMv8_Manual Ja].
- All kinds of exceptions are turned off upon taking an exception, so that by default, exception
handlers can not get interrupted themselves.
- Taking an exception will select the dedicated stack pointer of the target `EL`.
@@ -81,10 +81,10 @@ Here's an excerpt of important features for this tutorial:
introduced already, and additionally, it is taken into account _where_ the exception was taken from
and what the circumstances were.
-Here is a copy of the decision table as shown in Chapter D1.10.2 of the [ARMv8 Architecture
-Reference Manual][ARMv8_Manual]:
+Here is a copy of the decision table as shown in pages D1-5358 of the [ARMv8 Architecture
+Reference Manual][ARMv8_Manual Ja]:
-[ARMv8_Manual]: https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile
+[ARMv8_Manual Ja]: https://developer.arm.com/documentation/ddi0487/ja/
diff --git a/15_virtual_mem_part3_precomputed_tables/README.md b/15_virtual_mem_part3_precomputed_tables/README.md
index 3fdc23854..989301e0e 100644
--- a/15_virtual_mem_part3_precomputed_tables/README.md
+++ b/15_virtual_mem_part3_precomputed_tables/README.md
@@ -2006,7 +2006,7 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/tools/translation_table_tool/arch.rb 1
+ attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
+
+ def next_level_table_addr=(addr)
-+ addr = addr >> Granule64KiB::SHIFT
++ addr >>= Granule64KiB::SHIFT
+
+ self.__next_level_table_addr = addr
+ end
@@ -2092,7 +2092,7 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/tools/translation_table_tool/arch.rb 1
+ attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
+
+ def output_addr=(addr)
-+ addr = addr >> Granule64KiB::SHIFT
++ addr >>= Granule64KiB::SHIFT
+
+ self.__output_addr = addr
+ end
diff --git a/15_virtual_mem_part3_precomputed_tables/tools/translation_table_tool/arch.rb b/15_virtual_mem_part3_precomputed_tables/tools/translation_table_tool/arch.rb
index 44b8531eb..d8b5d04a1 100644
--- a/15_virtual_mem_part3_precomputed_tables/tools/translation_table_tool/arch.rb
+++ b/15_virtual_mem_part3_precomputed_tables/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/16_virtual_mem_part4_higher_half_kernel/tools/translation_table_tool/arch.rb b/16_virtual_mem_part4_higher_half_kernel/tools/translation_table_tool/arch.rb
index 61a6d6ca6..053cac194 100644
--- a/16_virtual_mem_part4_higher_half_kernel/tools/translation_table_tool/arch.rb
+++ b/16_virtual_mem_part4_higher_half_kernel/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/17_kernel_symbols/tools/translation_table_tool/arch.rb b/17_kernel_symbols/tools/translation_table_tool/arch.rb
index 61a6d6ca6..053cac194 100644
--- a/17_kernel_symbols/tools/translation_table_tool/arch.rb
+++ b/17_kernel_symbols/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/18_backtrace/tools/translation_table_tool/arch.rb b/18_backtrace/tools/translation_table_tool/arch.rb
index 61a6d6ca6..053cac194 100644
--- a/18_backtrace/tools/translation_table_tool/arch.rb
+++ b/18_backtrace/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/19_kernel_heap/tools/translation_table_tool/arch.rb b/19_kernel_heap/tools/translation_table_tool/arch.rb
index 61a6d6ca6..053cac194 100644
--- a/19_kernel_heap/tools/translation_table_tool/arch.rb
+++ b/19_kernel_heap/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/20_timer_callbacks/tools/translation_table_tool/arch.rb b/20_timer_callbacks/tools/translation_table_tool/arch.rb
index 61a6d6ca6..053cac194 100644
--- a/20_timer_callbacks/tools/translation_table_tool/arch.rb
+++ b/20_timer_callbacks/tools/translation_table_tool/arch.rb
@@ -84,7 +84,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def next_level_table_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__next_level_table_addr = addr
end
@@ -170,7 +170,7 @@ module Valid
attr_bitfield(:valid, Valid::OFFSET, Valid::NUMBITS)
def output_addr=(addr)
- addr = addr >> Granule64KiB::SHIFT
+ addr >>= Granule64KiB::SHIFT
self.__output_addr = addr
end
diff --git a/README.CN.md b/README.CN.md
index b8cba52aa..1b44067b6 100644
--- a/README.CN.md
+++ b/README.CN.md
@@ -48,8 +48,8 @@ _带上我最诚挚的问候,
Andre ([@andre-richter])_
### 🚀 tl;dr 版本
1. [安装 Docker][install_docker]。
-2. 确保你的用户在 [docker group] 中。
-3. 安装正确的`Rust`工具链:
+2. **(仅限Linux)** 确保您的用户帐户在 [docker group] 中。
+3. 准备Rust工具链。其中大部分将在首次使用时通过[rust-toolchain.toml](rust-toolchain.toml)文件进行处理。我们要做的是:
1. 如果你已经安装了一个版本的Rust:
```bash
cargo install cargo-binutils rustfilt
@@ -63,12 +63,17 @@ _带上我最诚挚的问候,
Andre ([@andre-richter])_
cargo install cargo-binutils rustfilt
```
-1. 如果你使用 `Visual Studio Code`,我强烈推荐你安装[Rust Analyzer 扩展]。
-1. 如果你使用的**不是**Linux,那么你还需要安装一些`Ruby` gems。
+4. 如果你使用 `Visual Studio Code`,我强烈推荐你安装[Rust Analyzer 扩展]。
+5. **(仅限macOS)** 安装一些`Ruby` gems。
+
+这是作者最后一次在`macOS Monterey`上用`Ruby 3.0.2`版本进行测试。如果您正在使用`rbenv`,那么相应的`.ruby-version`文件已经就位。
+如果你从未听说过`rbenv`,请尝试使用[这个指南](https://stackoverflow.com/a/68118750)。
+
+在存储库根文件夹中运行此操作:
```bash
-sudo gem install bundler
-bundle config set path '.vendor/bundle'
+bundle config set --local path '.vendor/bundle'
+bundle config set --local without 'development'
bundle install
```
@@ -78,9 +83,10 @@ bundle install
## 🧰 长期版本: 消除工具链烦恼
-这个系列的教程会着重关注用户体验的友好性。因此,我尽量消除嵌入式开发中的最大痛点:工具链的问题。
+这个系列的教程会着重关注用户体验的友好性。因此,我尽量消除嵌入式开发中的最大痛点:`Toolchain hassle`。
Rust内置的交叉编译支持在这方面帮了我们大忙。我们只需要使用`rustup`安装目标工具链就可以在`x86`宿主机上交叉编译支持树莓派的目标文件。然而,除了Rust编译器,我们还需要更多的工具。例如:
+Rust本身在这方面已经起到了很大的作用,因为它内置了对交叉编译的支持。从`x86`宿主机到树莓派的`AArch64`架构的交叉编译所需的一切都将由`rustup`自动安装。然而,除了Rust编译器,我们还将使用更多的工具。例如:
- 用于在我们的宿主系统上模拟我们内核运行环境的`QEMU`。
- 一个叫`Minipush`的自制工具,可以通过`UART`将内核加载到树莓派上。
@@ -96,14 +102,18 @@ Rust内置的交叉编译支持在这方面帮了我们大忙。我们只需要
由于教程中开发的内核是在真实的硬件上运行的,因此强烈建议您使用 USB 串行调试线来进行试验。连接调试线后,树莓派需要通过额外电源供电。
-- 淘宝搜索"USB 转串口"
-- 如下图连接 GPIO 串口的 14/15 号引脚
-- [第六章](06_drivers_gpio_uart) 是这个设备第一次需要使用的地方。找到如何准备 SD 卡来引导你自制的内核的说明。
-- [第七章](07_uart_chainloader)开始,在树莓派上启动内核变得非常舒适。在这章,会开发出一个叫`chainloader`的文件。
- 这将是您暂时需要在 SD 卡上手动复制的最后一个文件。这将使您能够在通过 UART 按需引导期间加载教程内核。
+- 您可以在[\[1\]] [\[2\]]中或者[淘宝]上找到USB转串口线,但许多其他线材也可以工作。理想情况下,您的线材应基于`CP2102`芯片。
+- 您将其连接到`GND`和GPIO引脚`14/15`,如下所示。
+- [教程5](05_drivers_gpio_uart/README.CN.md)是这个设备第一次需要使用的地方。查看它了解如何准备SD卡以从中启动自制内核的说明。
+- 从[教程6](06_uart_chainloader/README.CN.md)开始,在树莓派上启动内核变得非常舒适。在本教程中开发了一个所谓的`chainloader`,。
+ 这将是您暂时需要在SD卡上手动复制的最后一个文件。这将使您能够在通过`UART`按需引导期间加载教程内核。

+[\[1\]]: https://www.amazon.de/dp/B0757FQ5CX/ref=cm_sw_r_tw_dp_U_x_ozGRDbVTJAG4Q
+[\[2\]]: https://www.adafruit.com/product/954
+[淘宝]: https://www.taobao.com/
+
## 🙌 致谢
这个教程最初是由[Zoltan Baldaszti](https://github.com/bztsrc)的[项目](https://github.com/bztsrc/raspi3-tutorial)衍生出来的,感谢它给我开了一个头。
diff --git a/README.ES.md b/README.ES.md
index 0ed0634f6..16619dd0c 100644
--- a/README.ES.md
+++ b/README.ES.md
@@ -64,7 +64,7 @@ Muchas de las cosas vistas aquí también funcionan en **macOS**, pero esto solo
2. (**Solo para Linux**) Asegúrate de que la cuenta de tu usuario está en el [grupo `docker`][docker group].
-3. Prepara la `Rust` toolchain. La mayor parte se hará automáticamente durante el primer uso del archivo [rust-toolchain](rust-toolchain).
+3. Prepara la `Rust` toolchain. La mayor parte se hará automáticamente durante el primer uso del archivo [rust-toolchain.toml](rust-toolchain.toml).
Todo lo que nos queda hacer a nosotros es:
i. Si ya tienes una versión de Rust instalada:
diff --git a/README.md b/README.md
index 7267f4405..c31b549e8 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ The tutorials are primarily targeted at **Linux**-based distributions. Most stuf
1. [Install Docker Engine][install_docker].
1. (**Linux only**) Ensure your user account is in the [docker group].
1. Prepare the `Rust` toolchain. Most of it will be handled on first use through the
- [rust-toolchain](rust-toolchain) file. What's left for us to do is:
+ [rust-toolchain.toml](rust-toolchain.toml) file. What's left for us to do is:
1. If you already have a version of Rust installed:
```bash
cargo install cargo-binutils rustfilt
@@ -164,8 +164,8 @@ RPi3](https://github.com/bztsrc/raspi3-tutorial) in `C`. Thanks for giving me a
Licensed under either of
-- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or )
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.