Project RC

/**
 * Filename:    stdrc.cc
 * Description: 一只腊鸡的技术成长。
 * Author:      Richard Chien
 */

#include <stdrc.hh>

在 QEMU 上使用 U-Boot 启动自制内核

创建于
分类:Dev
标签:QEMUU-BootOSARMAArch64

为了简单了解 U-Boot 的使用,花了些时间尝试在 QEMU 的 arm64 virt 虚拟平台上使用 U-Boot 启动自制 OS 内核,这里记录一下过程,以便以后查阅。

准备 OS 内核

要想运行,肯定得先有个内核,于是搞了个极简的 helloworld:qemu-virt-hello,能够启用 ARM timer,然后打印 tick。写这个 helloworld 的时候是直接用 QEMU 的 -kernel 参数传入 ELF 格式的内核来测试运行的,所以其实这次用 U-Boot 运行纯属学习目的,本身并没有简化什么。

上述内核编译完成后得到 ELF 格式的 build/kernel.img 和 objcopy 后的纯二进制的 build/kernel.bin

然后需要使用 mkimage 命令(Ubuntu 上需安装 u-boot-tools 包)生成 U-Boot 能够识别的 image 文件:

mkimage -A arm64 -C none -T kernel -a 0x40000000 -e 0x40000000 -n qemu-virt-hello -d build/kernel.bin uImage

生成的 uImage 文件即所需的 image。

编译 U-Boot

使用下面命令下载和编译 U-Boot:

git clone git@github.com:u-boot/u-boot.git --depth 1
cd u-boot

make qemu_arm64_defconfig # 生成针对 QEMU virt 的 config
make -j16 CROSS_COMPILE=aarch64-linux-gnu- # 使用 CROSS_COMPILE 指定的工具链构建

完成之后 u-boot 目录下会生成一个 u-boot.bin 文件,这是 U-Boot 的可直接执行的纯二进制格式,使用下面命令检查是否可以正常进入 U-Boot:

qemu-system-aarch64 -machine virt -cpu cortex-a57 -bios u-boot.bin -nographic

进去之后会有个 autoboot 倒计时,按任意键之后会结束倒计时,到 U-Boot 命令行。

准备 Device Tree Blob

虽然按理说 U-Boot 可以不指定设备树直接启动不需要设备树的 kernel,但我这边一直不能成功,还没搞懂为什么,所以还是先准备一个设备树。

前面编写测试内核时针对的 QEMU 虚拟平台参数是 -machine virt -cpu cortex-a57 -smp 1 -m 2G,所以这里可以使用下面命令来 dump 出设备树:

qemu-system-aarch64 -machine virt,dumpdtb=virt.dtb -cpu cortex-a57 -smp 1 -m 2G -nographic

这会在当前文件夹生成 virt.dtb 文件。

构造 Flash Image

QEMU virt 平台有两个 flash 区域,分别是 0x0000_0000~0x0400_0000 和 0x0400_0000~0x0800_0000,U-Boot 本身被放在前一个 flash,我们可以通过 QEMU 参数 -drive if=pflash,format=raw,index=1,file=/path/to/flash.img 参数传入一个原始二进制格式的 image 文件来作为后一个 flash。

这里为了简单起见,使用 fallocate 和 cat 简单地把前面得到的 uImagevirt.dtb 拼在一起:

# 把 uImage 和 virt.dtb 分别扩展到 32M
fallocate -l 32M uImage
fallocate -l 32M virt.dtb

# 拼接
cat uImage virt.dtb > flash.img

运行

使用下面命令运行 QEMU 并进入 U-Boot 命令行:

qemu-system-aarch64 -nographic \
    -machine virt -cpu cortex-a57 -smp 1 -m 2G \
    -bios u-boot.bin \
    -drive if=pflash,format=raw,index=1,file=flash.img

使用 flinfo 命令可以查看 flash 信息。由于前面在制作 flash.img 时简单的拼接了 uImagevirt.dtb,因此现在 uImage 在 0x0400_0000 位置,virt.dtb 在 0x0600_0000 位置。

使用 iminfo 0x04000000 可以显示位于 0x0400_0000 的 uImage 信息,大致如下:

=> iminfo 0x04000000

## Checking Image at 04000000 ...
   Legacy image found
   Image Name:   qemu-virt-hello
   Created:      2021-02-22  15:54:06 UTC
   Image Type:   AArch64 Linux Kernel Image (uncompressed)
   Data Size:    12416 Bytes = 12.1 KiB
   Load Address: 40000000
   Entry Point:  40000000
   Verifying Checksum ... OK

使用 fdt addr 0x06000000fdt print / 可以检查设备树是否正确,大致输出如下:

=> fdt addr 0x06000000
=> fdt print /
/ {
    interrupt-parent = <0x00008001>;
    #size-cells = <0x00000002>;
...

确认无误之后,使用 bootm 0x04000000 - 0x06000000 命令即可运行内核,如下:

=> bootm 0x04000000 - 0x06000000
## Booting kernel from Legacy Image at 04000000 ...
   Image Name:   qemu-virt-hello
   Created:      2021-02-22  15:54:06 UTC
   Image Type:   AArch64 Linux Kernel Image (uncompressed)
   Data Size:    12416 Bytes = 12.1 KiB
   Load Address: 40000000
   Entry Point:  40000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 06000000
   Booting using the fdt blob at 0x6000000
   Loading Kernel Image
   Loading Device Tree to 00000000bede5000, end 00000000bede9cdb ... OK

Starting kernel ...

Booting...
...

参考资料

在 QEMU 上使用 U-Boot 启动自制内核

创建有多个分区的 img 文件并格式化

创建于
分类:Dev
标签:文件系统分区OS

在 QEMU 中运行 OS 并需要模拟从 SD 卡读取文件时,可以通过 -drive file=/path/to/sdcard.img,if=sd,format=raw 参数向虚拟机提供 SD 卡,这里记录一下制作 sdcard.img 的过程。

首先需要创建一个空的映像文件,可以使用 ddfallocate,这里以 fallocate 为例:

fallocate -l 200M sdcard.img

这里 200M 指的是这个映像文件占 200 MB,也就相当于是一个 200 MB 的 SD 卡。

然后使用 fdisk 对其进行分区,直接 fdisk sdcard.img 之后操作即可,比如这里分了两个分区:

Command (m for help): p
Disk sdcard.img: 200 MiB, 209715200 bytes, 409600 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb11e63d6

Device      Boot  Start    End Sectors  Size Id Type
sdcard.img1        2048 206847  204800  100M  c W95 FAT32 (LBA)
sdcard.img2      206848 409599  202752   99M 83 Linux

下一步就是要格式化成期望的文件系统,在这之前需要先把 sdcard.img 虚拟成一个块设备,通过如下命令做到:

losetup --partscan --show --find sdcard.img

成功后会输出一个设备路径,比如 /dev/loop0,与此同时还有两个分区 /dev/loop0p1/dev/loop0p2。之后使用形如下面的命令格式化分区:

mkfs.fat -F32 /dev/loop0p1

然后移除 loop 设备:

losetup -d /dev/loop0

到这里 sdcard.img 就制作完成了。

参考资料

创建有多个分区的 img 文件并格式化

2020 → 2021

创建于
分类:Misc
标签:年度总结

虽然现在已经是 2021 年的第二个月中旬了,却还是觉得需要补一个年度总结。而且越来越觉得,农历新年比元旦更适合用来总结过去的一年,因为往往这时候结束了过去一年的学习和工作,吃了年夜饭,看完了春晚,才觉得好像过去一年才算真的过去,身心也终于得到真正的放松。相比起来,元旦短短的假期,最多能匆匆忙忙出去吃一两顿好的,总还是缺了点“新年”的味道。

无聊的本科生活的落幕

回首 2020 年,最显然的一件事就是大学毕业,糊完了极其坑爹的杰普公司校企合作培训和毕业设计,终于可以离开那个浪费了我四年最好时光的学校。无聊的课堂教学、敷衍的课程实验、落后的课本,真的令人失望,当然了,这只能怪我自己初中和高中没有努力接受应试教育。

不过,大学期间也算是做了些积极的尝试,比如试图搞开源软件协会来促进计算机社团水平的发展,也尝试给大一新生做了 Python 相关的培训,都是一些有趣的经历。

另一方面,如果说我的大学还有什么值得怀念的话,那就只剩下自由的课余时间了吧。那个时候,可以随便翘课,整天待在宿舍学自己想学的东西,写自己想写的项目,几乎一切时间安排都是自由的,现在那样的状态已经一去不复返了。

研究生之梦的实现和趋于平淡

去年的另一件大事就是考研上岸,经历了出分前的紧张、出分后的激动、机试的完美感觉、面试前的浪、面试后的自我怀疑,最后听到海波老师在电话里的“欢迎加入 IPADS”,证明了为此付出的十个月没有白费。最终的初试+复试总成绩排名第一,似乎是很值得吹的了。

但对研究生生活的热情却逐渐变淡了。开学前的暑期培训时候还是非常有激情的,提前加入项目后更是晚上常常肝到 12 点之后,刚开学的一段时间也每天晚上基本都 11 点才从实验室回宿舍。可是渐渐地开始怀疑了,觉得好像实验室的项目也没有那么有意思,虽然对操作系统真的很感兴趣,但被分配任务去按部就班地完成好像终究缺了点乐趣。总结来说给的补贴太少,占用的时间太多,写的是屎山代码,又没有什么自由发挥的空间,有点憋屈。于是当初那个幻想着“为中国操作系统事业添砖加瓦、为中华民族伟大复兴尽一份力”的自己,逐渐开始变得现实了,现在只想在实验室规定的工作时间干活,其它时间都用来做自己的事,或者写写个人项目,或者出去耍一耍放松,总之是不想碰实验室的事情了。

曾经希望能搞点科研,现在也觉得好像很无趣,其实现在只能打廉价劳动力的工,并没有机会参与科研,但也并不期待。现在只想安安静静地混个毕业,期间能多点时间学自己想学的东西,最后找个还算不错的工作,也就足够了。我终究不是一个能够无私奉献自己的人。谁又是呢。

QQ 机器人

暑假的时候还有一件算蛮大的事,就是 CKYU 关闭,起因是晨风机器人作者据传被公安局喝茶,然后 CKYU 作者为了避免遭殃,主动撤了。其实这说起来非常好笑,本来就是灰色产业,为了避险,不再做了,明明很正常的事情,网上居然有人称之为“8.2 事件”,然后各种讲情怀,说得好像是什么不得了的情况一样。

CKYU 关了之后,CQHTTP 也就没用了,其实本来也没什么精力维护了,想着趁机也就不再管了。结果后来广大用户们在其它机器人平台发明了各种轮子,有的兼容 CQHTTP API,有的甚至可以直接加载 CQHTTP 插件。于是最后还是决定再做一件事情,就是把 CQHTTP 的文档重新整理了一下,做成了“OneBot 聊天机器人接口标准”,整理到最后甚至来了兴致,觉得把这玩意就作为接口标准继续维护也不错,可以推动各兼容项目长久保持兼容。可是这个兴致终究抵不过实验室各种事情的忙碌,最后也就没时间顾及了。

现在 OneBot 标准依然是许多项目开发时的参考文档,但未来何去何从,已经没有明确的方向了。

操作系统

虽然说前面吐槽了有些时候对实验室项目感到无趣,但写操作系统这件事本身一直是很有意思的,当不小心把精力投入到实验室的 OS 项目里之后,也常常会忘记自己已经说过要“堕落”。其实以前就一直梦想着以后写一个自己的 OS,去年经过 μCore 和 ChCore 实验的练习,以及翻阅了些 Linux 源码,似乎已经具备了写 OS 的能力,再加上对实验室的 OS 不太满意,没有发挥空间,于是年末开了个新坑,打算尝试写一个叫做 rcOS 的简易操作系统,算是满足自己的心愿和完美主义,希望 2021 年能写到自己满意的程度。

新的一年?

其实经过半年的研究生生活,对新的一年已经没有多少激动人心的期待,只希望能更好地调整好 work-life balance,多抽出些时间陪女朋友,以及写自己的 OS 和其它项目,多运动保持健康,就够了。

2020 → 2021

用 Rust 写操作系统的踩坑记录

创建于 更新于
分类:Dev
标签:RustOSSystem

持续更新中……

最近在尝试用 Rust 写一个简单的 OS,过程中遇到了不少问题,在这里记录下,以便自己以后查阅,也给其他写 OS 的朋友们提供参考。

编译 core crate 发生 segmentation fault

使用自定义 target 的时候需要指定 -Z build-std=core,alloc --target=targets/foo.json,这会为指定的 target 编译 corealloc crate,但是遇到如下报错:

$ make
    Updating crates.io index
   Compiling compiler_builtins v0.1.36 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/vendor/compiler_builtins)
   Compiling core v0.0.0 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core)
   Compiling kernel v0.1.0 (/Users/richard/Projects/rcos/kernel)
   Compiling rustc-std-workspace-core v1.99.0 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/rustc-std-workspace-core)
error: could not compile `core`

Caused by:
  process didn't exit successfully: `rustc --crate-name core --edition=2018 /Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C panic=abort -C embed-bitcode=no -C debuginfo=2 -C metadata=d1c28a1a3b0e7456 -C extra-filename=-d1c28a1a3b0e7456 --out-dir /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps --target /Users/richard/Projects/rcos/kernel/targets/aarch64.json -Z force-unstable-if-unmarked -L dependency=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps -L dependency=/Users/richard/Projects/rcos/kernel/target/debug/deps --cap-lints allow` (signal: 11, SIGSEGV: invalid memory reference)
warning: build failed, waiting for other jobs to finish...
error: build failed
make: *** [target/aarch64/debug/kernel] Error 101

报错信息具体就是 rustc 在编译 core 的时候发生了 segmentation fault,直接运行报错的那句命令也是一样的效果,后来发现使用 release 编译就不会报错,于是发现问题跟 -C opt-level= 编译选项有关,opt-level 等于 0 就会报错,大于等于 1 就没问题,可能是 rustc 的 bug。具体解决方法是在 Cargo.toml 中针对 core 包修改 opt-level,如下:

[profile.dev.package.core]
opt-level = 1

内核链接错误,报 undefined symbol: memcpy

报错信息如下:

$ make
...
error: linking with `rust-lld` failed: exit code: 1
  |
  = note: "rust-lld" "-flavor" "gnu" "-Ttarget/aarch64/linker.ld" "--eh-frame-hdr" "-L" "/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/aarch64/lib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.1fogdgfwiaz79eo9.rcgu.o" ... "-o" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.27nsv6g895dy2t94.rcgu.o" "--gc-sections" "-O1" "-L" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps" "-L" "/Users/richard/Projects/rcos/kernel/target/debug/deps" "-L" "/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/aarch64/lib" "-Bstatic" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libbuddy_system_allocator-6c2e94bd40abd09c.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-452aa8ee04b5ffb7.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-453077918cb4bcee.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/liballoc-359e1687c65b650d.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/librustc_std_workspace_core-9cbf353238b20cd5.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcompiler_builtins-482ad68e57d9fb9c.rlib" "-Bdynamic"
  = note: rust-lld: error: undefined symbol: memcpy
          >>> referenced by mod.rs:182 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:182)
          >>>               /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.2ec5pxgnruw0e9q7.rcgu.o:(_$LT$$RF$mut$u20$W$u20$as$u20$core..fmt..Write$GT$::write_fmt::h14b1afbd1a65c84b (.llvm.3212942262376902672))
          >>> referenced by mod.rs:447 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:447)
          >>>               core-81456d7491ea4ea4.core.2iv2qs8o-cgu.9.rcgu.o:(_$LT$core..fmt..Arguments$u20$as$u20$core..fmt..Display$GT$::fmt::haadaeb7738e71625) in archive /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib


error: aborting due to previous error; 35 warnings emitted

error: could not compile `kernel`

Caused by:
  process didn't exit successfully: `rustc --crate-name kernel --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C opt-level=2 -C embed-bitcode=no -C debuginfo=2 -C debug-assertions=on -C metadata=ff049f1d4f391e89 -C extra-filename=-ff049f1d4f391e89 --out-dir /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps --target /Users/richard/Projects/rcos/kernel/targets/aarch64.json -C incremental=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/incremental -L dependency=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps -L dependency=/Users/richard/Projects/rcos/kernel/target/debug/deps --extern 'noprelude:alloc=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/liballoc-359e1687c65b650d.rlib' --extern buddy_system_allocator=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libbuddy_system_allocator-6c2e94bd40abd09c.rlib --extern 'noprelude:compiler_builtins=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcompiler_builtins-482ad68e57d9fb9c.rlib' --extern 'noprelude:core=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib' --extern spin=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-453077918cb4bcee.rlib -Z unstable-options --cfg 'arch="aarch64"' --cfg 'machine="virt"'` (exit code: 1)
make: *** [target/aarch64/debug/kernel] Error 101

意思是 core::fmt 包里面引用了 memcpy,然而 no_std 的情况下没有 memcpy 这符号。

后来在 Writing an OS in Rust (First Edition) 找到解决方案,方法就是链接一个 rlibc,这提供了 memcpymemmove 等函数的实现。

但事情并没有这么简单,虽然上面的方案可以用,但 rlibc 已经是弃用状态了,作者推荐使用 compiler_builtin 替代,这个 crate 的 README 里让添加 dependency,但其实这玩意在使用 -Z build-std=core,alloc 编译的情况下会自动编译,不需要手动添加 dependency,但是自动编译的情况下不会加上 mem feature,而我们正是要它的 mem feature 才能解决链接问题,找了一圈发现这个问题已经在几个 issue 里讨论过了,最后有一个 PR 解决了这问题,现在只需要再加上 -Z build-std-features=compiler-builtins-mem 参数就可以把 mem feature 传给 compiler_builtins crate。

相关链接:

build.rs 中自动选择 linker script

由于不同 arch 和 machine 可能需要不同的 linker script,因此 linker.ld 可能放在 src/arch/<arch>/ 也可能在 src/arch/<arch>/machine/<machine>/,于是想在 build.rs 中根据传入的环境变量来自动选择对应的 linker.ld

但 cargo 只支持在 build.rs 中输出 rustc-cdylib-link-arg,是针对编译动态库的,于是一开始选择在自定义 target 的 JSON 或在 .cargo/config.toml 中写死 linker script 路径为 target 目录中的某个地方,然后在 build.rs 中把对应的 linker.ld 拷到那个地方。

后来翻了半天 issue 找到了同类问题,然后发现有一个 PR 已经提供了 unstable 支持,允许在 build.rs 中生成 rustc-link-arg(虽然 PR 中修改了 unstable.md 文档,但奇怪的是 master 分支文档却没有),可以用在任何目标类型,于是这件事情就简化成了在 build.rs 中找到需要的 linker.ld,然后输出 cargo:rustc-link-arg=-T<linker_ld_path>,完美解决问题。

相关链接:

生成位置无关代码

在还没有启动 MMU 的时候,PC 寄存器首先会是物理地址,但内核最终需要使用 0xffff 开头的高地址,通常在 linker script 中可进行配置,使代码中使用的绝对寻址拿到的都是高地址,然后启用编译器的相关选项使生成的代码在访问静态变量、调用函数时都采用位置无关的方式。这样就可以在内核的 boot 阶段首先使用低地址,同时该阶段的代码可以随意访问内核其它部分的函数或静态变量,然后在配置好页表、启用 MMU 之后,使用绝对寻址跳转到高地址。

要让 Rust 生成位置无关代码,在自定义 target 中添加 "position-independent-executables": true 即可。

相关链接:

用 Rust 写操作系统的踩坑记录

移植树莓派驱动框架 Circle 到自制操作系统

创建于
分类:Dev
标签:树莓派驱动OS

0. 前言

什么是 Circle?

Circle 是一个叫 rsta2 的大佬用 C++ 写的 bare-metal 的树莓派驱动框架,同时支持现存的几乎所有版本树莓派,能够驱动树莓派上的大部分设备,包括 SD 卡控制器、有线和无线网卡、GPIO、USB 控制器及一些常用 USB 设备等。

这些设备驱动中有一些是 rsta2 参考 Linux 或其它 bare-metal 的实现自己写的,还有一些是他直接从其它系统移植过来的。各驱动对外封装成了 C++ 类的形式,可以按需对其实例化和使用。对于在树莓派上编写自制操作系统并且希望尽快驱动一些设备的场景,将 Circle 整个移植过来是非常值得考虑的选项。当然,需要注意的是,Circle 的开源协议是 GPLv3,具有传染性,应该把它作为系统的非必要组件来使用。

本文干了什么?

本文将简要记述将 Circle 驱动框架移植到一个自制微内核操作系统(实际上是实验室项目),作为用户态驱动进程的过程。

由于只是简要地记录移植过程和经验总结,本文不会深入到每一个具体的细节,如果你恰好有相似的需求,除了参考本文,还需要具体地去研究 Circle 的代码,并根据具体情况具体分析。

1. 确定需求

首先一开始不要盲目移植,先确定一下自己需要 Circle 中的哪些设备驱动,比如说,如果你只是需要 USB 相关驱动,那么在移植过程中可以不关心 WLAN 那块是否有不兼容。

然后跑一些 Circle 提供的 sample,确定 Circle 确实可以满足需求。

2. 移植基本部分

这部分包括一些非常必要的组件,比如 mailbox 访问,许多驱动都需要通过 mailbox 获取信息或申请资源等。这部分移植完成后,将可以在启动时获取机器信息,并点亮 LED 灯。

具体地,主要需要做下面这些事情:

  1. 修改 Rules.mkMakefile,去掉任何 boot 相关的代码
  2. 重新实现一些模块,去除需要特权指令的地方,并使用用户态 lib 提供的功能代替:
    • assert:assert 失败后不要真的关机或重启,可以 exit
    • interrupt:一开始可简单打印点内容,不用真的实现
    • logger:改为 printf 输出
    • new:使用 malloc 分配
    • sysinit:提供假实现
    • timer:使用 nanosleep 实现 SimpleusDelay
    • memory:CMemorySystem 类可以直接去掉,一开始会有地方用到 CMemorySystem::GetCoherentPage,改用系统提供的分配物理上连续的 non-cacheable 内存的接口
  3. Circle 进程启动时将物理地址的外设区域直接对等映射到当前进程的虚拟地址空间,这样将不需要改动 Circle 中通过 MMIO 访问外设时使用的地址

经过一些调试后,可以点亮 LED 灯,输出日志到 stdout,然后退出(需要编写适当的 kernel.cpp,可参考 sample),这意味着简单的 MMIO 已经可以了。

3. 驱动屏幕

这一步同样需要重新实现一些模块:

  • synchronize:主要是刷 cache 相关操作
  • bcmframebuffer:向 GPU 申请 frame buffer 后需要将其映射到当前进程的虚拟地址

经过一些调试后,可以通过 HDMI 输出内容到屏幕。

4. 模拟实现 Timer

对于一些稍复杂的驱动,例如 USB 和 WLAN,会依赖 timer 获取当前 tick,因此需要重新实现 timer 模块。

具体地,可以在 CTimer::Initialize 中创建一个新的线程,每隔 10 ms(利用 nanosleep 等函数)调用一次 CTimer::InterruptHandler,其它代码几乎不用改动。此外,还需要实现 CTimer::GetClockTicks 以获得当前 tick 数。

经过一些调试后,输出的日志中能够包含当前时间,此时说明 timer 基本实现对了。

5. 解决内存相关的一系列问题

许多驱动在运行的过程中需要分配内存以供外设进行 DMA,同时又需要在进程内访问这块内存以读写跟外设交互的数据。因此,这块内存既需要能通过虚拟地址访问,又需要能获取到物理地址,同时在物理地址上连续且 non-cacheable。malloc 是不能满足这个需求的,因为 malloc 只保证分配出的内存虚拟地址连续,不能保证物理地址连续,也无法配置成 non-cacheable。

要解决这个问题,需要内核提供分配物理上连续且 non-cacheable 的内存并映射到虚拟地址空间的相关系统调用,然后再在 Circle 中利用这些系统调用重新实现内存相关模块。其实在前面已经粗略地实现了,但在这一步需要确保实现的正确性。对 Circle 的修改主要涉及 CMemorySystem::GetCoherentPageDMA_BUFFERBUS_ADDRESSnew (HEAP_DMA) 的定义及使用它们的地方。

6. 用户态处理中断

对于像 USB 和 WLAN 这些需要利用中断通知操作系统发生了特定事件的设备,还需要把之前虚假实现的 interrupt 模块实现对。

首先要求内核提供让特定用户态进程处理特定 IRQ 的能力,具体来说就是驱动进程要能够通过系统调用注册一个函数作为特定编号的 IRQ 的用户态处理函数,然后内核在收到 IRQ 后调用此函数来处理。

接着重新实现 interrupt 模块,把 CInterruptSystem::ConnectIRQ 改为使用上述注册 IRQ 处理函数的系统调用,暂时用不到的函数可以不实现,比如与 FIQ 相关的。

7. 驱动 USB

Timer、DMA buffer、中断这几个重要的部分移植完成后,比较容易就可以驱动 USB 控制器,进而可以检测并驱动 USB 键盘、鼠标、存储、串口转换器等设备,对于树莓派 3,还可以驱动有线网卡(LAN7800)。

8. 其它驱动

到目前为止已经移植了大部分驱动所需的运行环境,之后的移植工作主要看具体的需求了,比如如果需要网络协议栈,还要重新实现 sched 模块,里面包括线程抽象、调度、线程同步机制等。

移植树莓派驱动框架 Circle 到自制操作系统