├── .gitignore ├── LICENSE ├── README.md ├── doc └── BCM2837-ARM-Peripherals-V2-1.pdf ├── src ├── .cargo │ └── config ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── link.ld ├── mirror │ ├── kernel │ └── kernel.img ├── raspi3_boot │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ ├── delay.rs │ ├── gpio.rs │ ├── main.rs │ ├── mbox.rs │ ├── power.rs │ └── uart.rs └── tutorials ├── 00_preparation ├── README.md └── pic │ ├── gpio_info.png │ ├── gpios.png │ ├── ls_dev.png │ └── uart.jpg └── 01_helloworld └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | 4 | src/xbuild_sysroot 5 | **/target/* 6 | 7 | *~ 8 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jorge Aparicio 4 | Copyright (c) 2018-2019 Andre Richter 5 | Copyright (c) 2019 Sergey Cheung 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OS-tutorials 2 | 3 | 📚 使用 Rust 和树莓派 3 进行裸机与操作系统开发教程。本仓库基于[rust-raspi3-OS-tutorials](https://github.com/rust-embedded/rust-raspi3-OS-tutorials)。 4 | 5 | >本教程是 [斯坦福操作系统教程公开课](https://web.stanford.edu/class/cs140e/) 的一个示例,本仓库记录📝尽量做到每章节都图文并茂。 6 | 7 | > 拖更了:笔者发现自己基础知识有点跟不上了,在充电中(2019.4.26)QAQ 8 | 9 | ``` 10 | 00000000000000000000100011000100000000000000000000 11 | 00000000000000011001111111111110011000000000000000 12 | 00000000000110011111111100111111111001100000000000 13 | 00000000000111111111111100111111111111100000000000 14 | 00000000111111110000000111000000001111111100000000 15 | 00000111111110000000000000000000000001111111100000 16 | 00000111111111111111111111111111000000011111100000 17 | 00011111111111111111111111111111111100001111111000 18 | 00011111111111111111111111111111111110001111111000 19 | 00011100011001111111100000000111111110011000111000 20 | 00111111111001111111100000000111111110011111111100 21 | 00111110000001111111111111111111111000000001111100 22 | 01111110000001111111111111111111111000000001111110 23 | 00011110000001111111100000011111111100000111111000 24 | 01111111000001111111100000001111111110001111111110 25 | 00011111111111111111111111000111111111111111111000 26 | 00011111111111111111111111000111111111111111111000 27 | 00000111111111111111111110000001111111111111100000 28 | 00000111111111110000000000000000011111111111100000 29 | 00000000111110011000000000000000011001111100000000 30 | 00000000111111011100000000000000111011111100000000 31 | 00000000000111111111111111111111111111100000000000 32 | 00000000000100011111111111111111111000100000000000 33 | 00000000000000011001110111101110011000000000000000 34 | 00000000000000000000000000000000000000000000000000 35 | ``` 36 | 37 | ## 如何阅读 38 | - 所有教程文档、含注释的代码都合并到了 `master` 分支上,教程文档都在 [tutorials](tutorials/) 目录上。 39 | - 教程中的所有记录,请切换相应的 git 分支上,读者可以按照自己的学习路线进行阅读。(所以也避免不了拖更QAQ) 40 | 41 | ### 相关阅读 42 | 洛佳 (@luojia65)同学主要负责翻译的[《使用Rust编写操作系统》](https://github.com/rustcc/writing-an-os-in-rust)简体中文翻译:https://github.com/rustcc/writing-an-os-in-rust 43 | 44 | ## 准备与开发环境 45 | 46 | [开发指南](tutorials/00_preparation/README.md) 47 | -------------------------------------------------------------------------------- /doc/BCM2837-ARM-Peripherals-V2-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/doc/BCM2837-ARM-Peripherals-V2-1.pdf -------------------------------------------------------------------------------- /src/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-none] 2 | rustflags = [ 3 | "-C", "link-arg=-Tlink.ld", 4 | "-C", "target-feature=-fp-armv8", 5 | "-C", "target-cpu=cortex-a53", 6 | ] 7 | -------------------------------------------------------------------------------- /src/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cortex-a" 5 | version = "2.3.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "kernel" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "cortex-a 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "raspi3_boot 0.1.0", 17 | "register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "panic-abort" 22 | version = "0.3.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "r0" 27 | version = "0.2.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "raspi3_boot" 32 | version = "0.1.0" 33 | dependencies = [ 34 | "cortex-a 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "register" 41 | version = "0.3.2" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "tock-registers" 49 | version = "0.3.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [metadata] 53 | "checksum cortex-a 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12425c4491f31f28f539c74382ade69ee9db4f1f597aa177f43e072595562e46" 54 | "checksum panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c14a66511ed17b6a8b4256b868d7fd207836d891db15eea5195dbcaf87e630f" 55 | "checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 56 | "checksum register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a0f44a6dc9a98359515541a0c46ef4e3630a30879c1d7a4038f31dd533570bfb" 57 | "checksum tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c758f5195a2e0df9d9fecf6f506506b2766ff74cf64db1e995c87e2761a5c3e2" 58 | -------------------------------------------------------------------------------- /src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel" 3 | version = "0.1.0" 4 | authors = ["Sergey Cheung "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | raspi3_boot = { path = "raspi3_boot" } 9 | cortex-a = "2.3.1" 10 | register = "0.3.2" 11 | 12 | [package.metadata.cargo-xbuild] 13 | sysroot_path = "xbuild_sysroot" 14 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | 4 | TARGET = aarch64-unknown-none 5 | 6 | SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) link.ld 7 | 8 | 9 | XRUSTC_CMD = cargo xrustc --target=$(TARGET) --release 10 | CARGO_OUTPUT = target/$(TARGET)/release/kernel 11 | 12 | OBJCOPY = cargo objcopy -- 13 | OBJCOPY_PARAMS = --strip-all -O binary 14 | 15 | CONTAINER_UTILS = andrerichter/raspi3-utils 16 | 17 | DOCKER_CMD = docker run -it --rm 18 | DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work 19 | DOCKER_ARG_TTY = --privileged -v /dev:/dev 20 | 21 | DOCKER_EXEC_QEMU = qemu-system-aarch64 -M raspi3 -kernel kernel.img 22 | DOCKER_EXEC_RASPBOOT = raspbootcom /dev/ttyUSB0 23 | 24 | .PHONY: all qemu raspboot clippy clean objdump nm 25 | 26 | all: clean kernel.img move 27 | 28 | $(CARGO_OUTPUT): $(SOURCES) 29 | $(XRUSTC_CMD) 30 | 31 | kernel.img: $(CARGO_OUTPUT) 32 | cp $< . 33 | $(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel.img 34 | 35 | qemu: all 36 | $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \ 37 | $(DOCKER_EXEC_QEMU) -serial stdio 38 | 39 | raspboot: all 40 | $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_TTY) \ 41 | $(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) kernel.img 42 | 43 | clippy: 44 | cargo xclippy --target=$(TARGET) 45 | 46 | clean: 47 | cargo clean 48 | 49 | objdump: 50 | cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel 51 | 52 | nm: 53 | cargo nm --target $(TARGET) -- kernel | sort 54 | 55 | move: 56 | mv kernel mirror/ 57 | mv kernel.img mirror/ -------------------------------------------------------------------------------- /src/link.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_boot_cores); 2 | 3 | SECTIONS 4 | { 5 | . = 0x80000; 6 | 7 | .text : 8 | { 9 | KEEP(*(.text.boot)) *(.text .text.*) 10 | } 11 | 12 | .rodata : 13 | { 14 | *(.rodata .rodata.*) 15 | } 16 | 17 | .data : 18 | { 19 | *(.data .data.*) 20 | } 21 | 22 | .bss ALIGN(8): 23 | { 24 | __bss_start = .; 25 | *(.bss .bss.*) 26 | *(COMMON) 27 | __bss_end = .; 28 | } 29 | 30 | /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } 31 | } 32 | -------------------------------------------------------------------------------- /src/mirror/kernel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/src/mirror/kernel -------------------------------------------------------------------------------- /src/mirror/kernel.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/src/mirror/kernel.img -------------------------------------------------------------------------------- /src/raspi3_boot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raspi3_boot" 3 | version = "0.1.0" 4 | authors = ["Sergey Cheung "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cortex-a = "2.3.1" 9 | panic-abort = "0.3.1" 10 | r0 = "0.2.2" 11 | -------------------------------------------------------------------------------- /src/raspi3_boot/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![deny(warnings)] 3 | #![no_std] 4 | 5 | //! Low-level boot of the Raspberry's processor 6 | 7 | extern crate panic_abort; 8 | 9 | use cortex_a::{asm, regs::*}; 10 | 11 | /// 处理器启动入口 12 | /// 13 | /// 只启动第一个核心 core0, 并且启动成功后执行复位函数:`reset()` 14 | #[link_section = ".text.boot"] 15 | #[no_mangle] 16 | pub unsafe extern "C" fn _boot_cores() -> ! { 17 | const CORE_0: u64 = 0; 18 | const CORE_MASK: u64 = 0x3; 19 | const STACK_START: u64 = 0x80_000; 20 | 21 | if CORE_0 == MPIDR_EL1.get() & CORE_MASK { 22 | SP.set(STACK_START); 23 | reset() 24 | } else { 25 | // if not core0, infinitely wait for events 26 | loop { 27 | asm::wfe(); 28 | } 29 | } 30 | } 31 | 32 | 33 | /// 复位函数 34 | /// 35 | /// 在执行用户的 `main()` 函数之前,初始化 bss 段 36 | unsafe fn reset() -> ! { 37 | extern "C" { 38 | // .bss 段的起止, 由链接器脚本提供 39 | static mut __bss_start: u64; 40 | static mut __bss_end: u64; 41 | } 42 | 43 | // .bss 段置零 44 | r0::zero_bss(&mut __bss_start, &mut __bss_end); 45 | 46 | extern "Rust" { 47 | fn main() -> !; 48 | } 49 | 50 | main(); 51 | } 52 | 53 | /// 用户提供的入口函数(`main()`)的类型检查 54 | #[macro_export] 55 | macro_rules! entry { 56 | ($path:path) => { 57 | #[export_name = "main"] 58 | pub unsafe fn __main() -> ! { 59 | let f: fn() -> ! = $path; 60 | 61 | f() 62 | } 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/src/delay.rs: -------------------------------------------------------------------------------- 1 | use cortex_a::asm; 2 | 3 | /// 等待 N 个机器周期 4 | /// 此函数仅限于 arm cpu 5 | pub fn wait_cycles(cycle: u32) { 6 | for _ in 0..cycle { 7 | asm::nop(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/src/gpio.rs: -------------------------------------------------------------------------------- 1 | use core::ops; 2 | 3 | use register::{mmio::ReadWrite, register_bitfields}; 4 | 5 | use super::MMIO_BASE; 6 | 7 | const GPIO_BASE: u32 = MMIO_BASE + 0x200_000; 8 | 9 | // 寄存器文档请参考 10 | // https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf 11 | // TODO 12 | register_bitfields! { 13 | u32, 14 | 15 | /// GPIO Function Select 1 16 | GPFSEL1 [ 17 | /// Pin 15 18 | FSEL15 OFFSET(15) NUMBITS(3) [ 19 | Input = 0b000, 20 | Output = 0b001, 21 | RXD0 = 0b100, // UART0 - Alternate function 0 22 | RXD1 = 0b010 // Mini UART - Alternate function 5 23 | 24 | ], 25 | 26 | /// Pin 14 27 | FSEL14 OFFSET(12) NUMBITS(3) [ 28 | Input = 0b000, 29 | Output = 0b001, 30 | TXD0 = 0b100, // UART0 - Alternate function 0 31 | TXD1 = 0b010 // Mini UART - Alternate function 5 32 | ] 33 | ], 34 | 35 | /// GPIO Pull-up/down Clock Register 0 36 | GPPUDCLK0 [ 37 | /// Pin 15 38 | PUDCLK15 OFFSET(15) NUMBITS(1) [ 39 | NoEffect = 0, 40 | AssertClock = 1 41 | ], 42 | 43 | /// Pin 14 44 | PUDCLK14 OFFSET(14) NUMBITS(1) [ 45 | NoEffect = 0, 46 | AssertClock = 1 47 | ] 48 | ] 49 | } 50 | 51 | #[allow(non_snake_case)] 52 | #[repr(C)] 53 | pub struct RegisterBlock { 54 | pub GPFSEL0: ReadWrite, 55 | // 0x00 56 | pub GPFSEL1: ReadWrite, 57 | // 0x04 58 | pub GPFSEL2: ReadWrite, 59 | // 0x08 60 | pub GPFSEL3: ReadWrite, 61 | // 0x0C 62 | pub GPFSEL4: ReadWrite, 63 | // 0x10 64 | pub GPFSEL5: ReadWrite, 65 | // 0x14 66 | __reserved_0: u32, 67 | // 0x18 68 | GPSET0: ReadWrite, 69 | // 0x1C 70 | GPSET1: ReadWrite, 71 | // 0x20 72 | __reserved_1: u32, 73 | // 74 | GPCLR0: ReadWrite, 75 | // 0x28 76 | __reserved_2: [u32; 2], 77 | // 78 | GPLEV0: ReadWrite, 79 | // 0x34 80 | GPLEV1: ReadWrite, 81 | // 0x38 82 | __reserved_3: u32, 83 | // 84 | GPEDS0: ReadWrite, 85 | // 0x40 86 | GPEDS1: ReadWrite, 87 | // 0x44 88 | __reserved_4: [u32; 7], 89 | // 90 | GPHEN0: ReadWrite, 91 | // 0x64 92 | GPHEN1: ReadWrite, 93 | // 0x68 94 | __reserved_5: [u32; 10], 95 | // 96 | pub GPPUD: ReadWrite, 97 | // 0x94 98 | pub GPPUDCLK0: ReadWrite, 99 | // 0x98 100 | pub GPPUDCLK1: ReadWrite, // 0x9C 101 | } 102 | 103 | 104 | /// GPIO MMIO 公共接口 105 | pub struct GPIO; 106 | 107 | impl ops::Deref for GPIO { 108 | type Target = RegisterBlock; 109 | 110 | fn deref(&self) -> &Self::Target { 111 | unsafe { &*Self::ptr() } 112 | } 113 | } 114 | 115 | 116 | impl GPIO { 117 | pub fn new() -> GPIO { 118 | GPIO 119 | } 120 | 121 | /// 返回指向寄存器模块的指针 122 | fn ptr() -> *const RegisterBlock { 123 | GPIO_BASE as *const _ 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | /// 很多其他 io 设备基于这个基地址 5 | /// Memory mapping I/O 6 | const MMIO_BASE: u32 = 0x3F00_0000; 7 | 8 | mod delay; 9 | mod gpio; 10 | mod mbox; 11 | mod power; 12 | mod uart; 13 | 14 | fn kernel_entry() -> ! { 15 | let mut mbox = mbox::Mbox::new(); 16 | let gpio = gpio::GPIO::new(); 17 | let uart = uart::Uart::new(); 18 | let power = power::Power::new(); 19 | 20 | // 设置串口输出 21 | match uart.init(&mut mbox, &gpio) { 22 | Ok(_) => uart.puts("\n uart is in good working order.\n"), 23 | Err(_) => loop { 24 | cortex_a::asm::wfe(); 25 | }, 26 | } 27 | 28 | uart.puts("Press a key to continue booting\n"); 29 | uart.getc(); 30 | uart.puts("\nWelcome to the new OS !\n"); 31 | 32 | loop { 33 | uart.puts("*** sergeychang@gmail.com ***\n"); 34 | uart.puts(">>> 1 - power off\n"); 35 | uart.puts(">>> 2 - reset\n"); 36 | 37 | let input = uart.getc(); 38 | match input { 39 | '1' => { 40 | if power.off(&mut mbox, &gpio).is_err() { 41 | uart.puts("MailBox error in Power::off()!\n"); 42 | uart.puts("Power off failed.\n"); 43 | } 44 | } 45 | '2' => { 46 | power.reset(); 47 | } 48 | _ => {} 49 | } 50 | } 51 | } 52 | 53 | raspi3_boot::entry!(kernel_entry); -------------------------------------------------------------------------------- /src/src/mbox.rs: -------------------------------------------------------------------------------- 1 | use super::MMIO_BASE; 2 | use core::ops; 3 | use cortex_a::asm; 4 | use register::{ 5 | mmio::{ReadOnly, WriteOnly}, 6 | register_bitfields, 7 | }; 8 | 9 | register_bitfields! { 10 | u32, 11 | 12 | STATUS [ 13 | FULL OFFSET(31) NUMBITS(1) [], 14 | EMPTY OFFSET(30) NUMBITS(1) [] 15 | ] 16 | } 17 | 18 | const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880; 19 | 20 | #[allow(non_snake_case)] 21 | #[repr(C)] 22 | pub struct RegisterBlock { 23 | READ: ReadOnly, // 0x00 24 | __reserved_0: [u32; 5], // 0x04 25 | STATUS: ReadOnly, // 0x18 26 | __reserved_1: u32, // 0x1C 27 | WRITE: WriteOnly, // 0x20 28 | } 29 | 30 | // Custom errors 31 | pub enum MboxError { 32 | ResponseError, 33 | UnknownError, 34 | } 35 | pub type Result = ::core::result::Result; 36 | 37 | // Channels 38 | pub mod channel { 39 | pub const PROP: u32 = 8; 40 | } 41 | 42 | // Tags 43 | pub mod tag { 44 | pub const SETPOWER: u32 = 0x28001; 45 | pub const SETCLKRATE: u32 = 0x38002; 46 | pub const LAST: u32 = 0; 47 | } 48 | 49 | // Clocks 50 | pub mod clock { 51 | pub const UART: u32 = 0x0_0000_0002; 52 | } 53 | 54 | // Responses 55 | mod response { 56 | pub const SUCCESS: u32 = 0x8000_0000; 57 | pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response) 58 | } 59 | 60 | pub const REQUEST: u32 = 0; 61 | 62 | // Public interface to the mailbox 63 | #[repr(C)] 64 | #[repr(align(16))] 65 | pub struct Mbox { 66 | // The address for buffer needs to be 16-byte aligned so that the 67 | // Videcore can handle it properly. 68 | pub buffer: [u32; 36], 69 | } 70 | 71 | /// Deref to RegisterBlock 72 | /// 73 | /// Allows writing 74 | /// ``` 75 | /// self.STATUS.read() 76 | /// ``` 77 | /// instead of something along the lines of 78 | /// ``` 79 | /// unsafe { (*Mbox::ptr()).STATUS.read() } 80 | /// ``` 81 | impl ops::Deref for Mbox { 82 | type Target = RegisterBlock; 83 | 84 | fn deref(&self) -> &Self::Target { 85 | unsafe { &*Self::ptr() } 86 | } 87 | } 88 | 89 | impl Mbox { 90 | pub fn new() -> Mbox { 91 | Mbox { buffer: [0; 36] } 92 | } 93 | 94 | /// Returns a pointer to the register block 95 | fn ptr() -> *const RegisterBlock { 96 | VIDEOCORE_MBOX as *const _ 97 | } 98 | 99 | /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success 100 | pub fn call(&self, channel: u32) -> Result<()> { 101 | // wait until we can write to the mailbox 102 | loop { 103 | if !self.STATUS.is_set(STATUS::FULL) { 104 | break; 105 | } 106 | 107 | asm::nop(); 108 | } 109 | 110 | let buf_ptr = self.buffer.as_ptr() as u32; 111 | 112 | // write the address of our message to the mailbox with channel identifier 113 | self.WRITE.set((buf_ptr & !0xF) | (channel & 0xF)); 114 | 115 | // now wait for the response 116 | loop { 117 | // is there a response? 118 | loop { 119 | if !self.STATUS.is_set(STATUS::EMPTY) { 120 | break; 121 | } 122 | 123 | asm::nop(); 124 | } 125 | 126 | let resp: u32 = self.READ.get(); 127 | 128 | // is it a response to our message? 129 | if ((resp & 0xF) == channel) && ((resp & !0xF) == buf_ptr) { 130 | // is it a valid successful response? 131 | return match self.buffer[1] { 132 | response::SUCCESS => Ok(()), 133 | response::ERROR => Err(MboxError::ResponseError), 134 | _ => Err(MboxError::UnknownError), 135 | }; 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/src/power.rs: -------------------------------------------------------------------------------- 1 | use super::MMIO_BASE; 2 | use crate::delay; 3 | use crate::gpio; 4 | use crate::mbox; 5 | use core::ops; 6 | use core::sync::atomic::{compiler_fence, Ordering}; 7 | use register::mmio::*; 8 | 9 | const POWER_BASE: u32 = MMIO_BASE + 0x100_01C; 10 | 11 | #[allow(non_snake_case)] 12 | #[repr(C)] 13 | pub struct RegisterBlock { 14 | PM_RSTC: ReadWrite, // 0x1C 15 | PM_RSTS: ReadWrite, // 0x20 16 | PM_WDOG: ReadWrite, // 0x24 17 | } 18 | 19 | const PM_PASSWORD: u32 = 0x5a_000_000; 20 | const PM_RSTC_WRCFG_CLR: u32 = 0xffff_ffcf; 21 | const PM_RSTC_WRCFG_FULL_RESET: u32 = 0x0000_0020; 22 | 23 | // The Raspberry Pi firmware uses the RSTS register to know which 24 | // partition to boot from. The partition value is spread into bits 0, 2, 25 | // 4, 6, 8, 10. Partition 63 is a special partition used by the 26 | // firmware to indicate halt. 27 | const PM_RSTS_RASPBERRYPI_HALT: u32 = 0x555; 28 | 29 | pub enum PowerError { 30 | MailboxError, 31 | } 32 | pub type Result = ::core::result::Result; 33 | 34 | /// Public interface to the Power subsystem 35 | pub struct Power; 36 | 37 | impl ops::Deref for Power { 38 | type Target = RegisterBlock; 39 | 40 | fn deref(&self) -> &Self::Target { 41 | unsafe { &*Self::ptr() } 42 | } 43 | } 44 | 45 | impl Power { 46 | pub fn new() -> Power { 47 | Power 48 | } 49 | 50 | /// Returns a pointer to the register block 51 | fn ptr() -> *const RegisterBlock { 52 | POWER_BASE as *const _ 53 | } 54 | 55 | /// Shutdown the board 56 | pub fn off(&self, mbox: &mut mbox::Mbox, gpio: &gpio::GPIO) -> Result<()> { 57 | // power off devices one by one 58 | for dev_id in 0..16 { 59 | mbox.buffer[0] = 8 * 4; 60 | mbox.buffer[1] = mbox::REQUEST; 61 | mbox.buffer[2] = mbox::tag::SETPOWER; 62 | mbox.buffer[3] = 8; 63 | mbox.buffer[4] = 8; 64 | mbox.buffer[5] = dev_id; // device id 65 | mbox.buffer[6] = 0; // bit 0: off, bit 1: no wait 66 | mbox.buffer[7] = mbox::tag::LAST; 67 | 68 | // Insert a compiler fence that ensures that all stores to the 69 | // mbox buffer are finished before the GPU is signaled (which 70 | // is done by a store operation as well). 71 | compiler_fence(Ordering::Release); 72 | 73 | if mbox.call(mbox::channel::PROP).is_err() { 74 | return Err(PowerError::MailboxError); 75 | }; 76 | } 77 | 78 | // power off gpio pins (but not VCC pins) 79 | gpio.GPFSEL0.set(0); 80 | gpio.GPFSEL1.set(0); 81 | gpio.GPFSEL2.set(0); 82 | gpio.GPFSEL3.set(0); 83 | gpio.GPFSEL4.set(0); 84 | gpio.GPFSEL5.set(0); 85 | 86 | gpio.GPPUD.set(0); 87 | delay::wait_cycles(150); 88 | 89 | gpio.GPPUDCLK0.set(0xffff_ffff); 90 | gpio.GPPUDCLK1.set(0xffff_ffff); 91 | delay::wait_cycles(150); 92 | 93 | // flush GPIO setup 94 | gpio.GPPUDCLK0.set(0); 95 | gpio.GPPUDCLK1.set(0); 96 | 97 | // We set the watchdog hard reset bit here to distinguish this 98 | // reset from the normal (full) reset. bootcode.bin will not 99 | // reboot after a hard reset. 100 | let mut val = self.PM_RSTS.get(); 101 | val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT; 102 | self.PM_RSTS.set(val); 103 | 104 | // Continue with normal reset mechanism 105 | self.reset(); 106 | } 107 | 108 | /// Reboot 109 | pub fn reset(&self) -> ! { 110 | // use a timeout of 10 ticks (~150us) 111 | self.PM_WDOG.set(PM_PASSWORD | 10); 112 | let mut val = self.PM_RSTC.get(); 113 | val &= PM_RSTC_WRCFG_CLR; 114 | val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET; 115 | self.PM_RSTC.set(val); 116 | 117 | loop {} 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/src/uart.rs: -------------------------------------------------------------------------------- 1 | use super::MMIO_BASE; 2 | use crate::delay; 3 | use crate::gpio; 4 | use crate::mbox; 5 | use core::{ 6 | ops, 7 | sync::atomic::{compiler_fence, Ordering}, 8 | }; 9 | use cortex_a::asm; 10 | use register::{mmio::*, register_bitfields}; 11 | 12 | // PL011 UART registers. 13 | // 14 | // Descriptions taken from 15 | // https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf 16 | register_bitfields! { 17 | u32, 18 | 19 | /// Flag Register 20 | FR [ 21 | /// Transmit FIFO full. The meaning of this bit depends on the 22 | /// state of the FEN bit in the UARTLCR_ LCRH Register. If the 23 | /// FIFO is disabled, this bit is set when the transmit 24 | /// holding register is full. If the FIFO is enabled, the TXFF 25 | /// bit is set when the transmit FIFO is full. 26 | TXFF OFFSET(5) NUMBITS(1) [], 27 | 28 | /// Receive FIFO empty. The meaning of this bit depends on the 29 | /// state of the FEN bit in the UARTLCR_H Register. If the 30 | /// FIFO is disabled, this bit is set when the receive holding 31 | /// register is empty. If the FIFO is enabled, the RXFE bit is 32 | /// set when the receive FIFO is empty. 33 | RXFE OFFSET(4) NUMBITS(1) [] 34 | ], 35 | 36 | /// Integer Baud rate divisor 37 | IBRD [ 38 | /// Integer Baud rate divisor 39 | IBRD OFFSET(0) NUMBITS(16) [] 40 | ], 41 | 42 | /// Fractional Baud rate divisor 43 | FBRD [ 44 | /// Fractional Baud rate divisor 45 | FBRD OFFSET(0) NUMBITS(6) [] 46 | ], 47 | 48 | /// Line Control register 49 | LCRH [ 50 | /// Word length. These bits indicate the number of data bits 51 | /// transmitted or received in a frame. 52 | WLEN OFFSET(5) NUMBITS(2) [ 53 | FiveBit = 0b00, 54 | SixBit = 0b01, 55 | SevenBit = 0b10, 56 | EightBit = 0b11 57 | ] 58 | ], 59 | 60 | /// Control Register 61 | CR [ 62 | /// Receive enable. If this bit is set to 1, the receive 63 | /// section of the UART is enabled. Data reception occurs for 64 | /// UART signals. When the UART is disabled in the middle of 65 | /// reception, it completes the current character before 66 | /// stopping. 67 | RXE OFFSET(9) NUMBITS(1) [ 68 | Disabled = 0, 69 | Enabled = 1 70 | ], 71 | 72 | /// Transmit enable. If this bit is set to 1, the transmit 73 | /// section of the UART is enabled. Data transmission occurs 74 | /// for UART signals. When the UART is disabled in the middle 75 | /// of transmission, it completes the current character before 76 | /// stopping. 77 | TXE OFFSET(8) NUMBITS(1) [ 78 | Disabled = 0, 79 | Enabled = 1 80 | ], 81 | 82 | /// UART enable 83 | UARTEN OFFSET(0) NUMBITS(1) [ 84 | /// If the UART is disabled in the middle of transmission 85 | /// or reception, it completes the current character 86 | /// before stopping. 87 | Disabled = 0, 88 | Enabled = 1 89 | ] 90 | ], 91 | 92 | /// Interupt Clear Register 93 | ICR [ 94 | /// Meta field for all pending interrupts 95 | ALL OFFSET(0) NUMBITS(11) [] 96 | ] 97 | } 98 | 99 | const UART_BASE: u32 = MMIO_BASE + 0x20_1000; 100 | 101 | #[allow(non_snake_case)] 102 | #[repr(C)] 103 | pub struct RegisterBlock { 104 | DR: ReadWrite, // 0x00 105 | __reserved_0: [u32; 5], // 0x04 106 | FR: ReadOnly, // 0x18 107 | __reserved_1: [u32; 2], // 0x1c 108 | IBRD: WriteOnly, // 0x24 109 | FBRD: WriteOnly, // 0x28 110 | LCRH: WriteOnly, // 0x2C 111 | CR: WriteOnly, // 0x30 112 | __reserved_2: [u32; 4], // 0x34 113 | ICR: WriteOnly, // 0x44 114 | } 115 | 116 | pub enum UartError { 117 | MailboxError, 118 | } 119 | pub type Result = ::core::result::Result; 120 | 121 | pub struct Uart; 122 | 123 | impl ops::Deref for Uart { 124 | type Target = RegisterBlock; 125 | 126 | fn deref(&self) -> &Self::Target { 127 | unsafe { &*Self::ptr() } 128 | } 129 | } 130 | 131 | impl Uart { 132 | pub fn new() -> Uart { 133 | Uart 134 | } 135 | 136 | /// Returns a pointer to the register block 137 | fn ptr() -> *const RegisterBlock { 138 | UART_BASE as *const _ 139 | } 140 | 141 | ///Set baud rate and characteristics (115200 8N1) and map to GPIO 142 | pub fn init(&self, mbox: &mut mbox::Mbox, gpio: &gpio::GPIO) -> Result<()> { 143 | // turn off UART0 144 | self.CR.set(0); 145 | 146 | // set up clock for consistent divisor values 147 | mbox.buffer[0] = 9 * 4; 148 | mbox.buffer[1] = mbox::REQUEST; 149 | mbox.buffer[2] = mbox::tag::SETCLKRATE; 150 | mbox.buffer[3] = 12; 151 | mbox.buffer[4] = 8; 152 | mbox.buffer[5] = mbox::clock::UART; // UART clock 153 | mbox.buffer[6] = 4_000_000; // 4Mhz 154 | mbox.buffer[7] = 0; // skip turbo setting 155 | mbox.buffer[8] = mbox::tag::LAST; 156 | 157 | // Insert a compiler fence that ensures that all stores to the 158 | // mbox buffer are finished before the GPU is signaled (which 159 | // is done by a store operation as well). 160 | compiler_fence(Ordering::Release); 161 | 162 | if mbox.call(mbox::channel::PROP).is_err() { 163 | return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set 164 | }; 165 | 166 | // map UART0 to GPIO pins 167 | gpio.GPFSEL1 168 | .modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0); 169 | 170 | gpio.GPPUD.set(0); // enable pins 14 and 15 171 | delay::wait_cycles(150); 172 | 173 | gpio.GPPUDCLK0.modify( 174 | gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock, 175 | ); 176 | delay::wait_cycles(150); 177 | 178 | gpio.GPPUDCLK0.set(0); 179 | 180 | self.ICR.write(ICR::ALL::CLEAR); 181 | self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud 182 | self.FBRD.write(FBRD::FBRD.val(0xB)); 183 | self.LCRH.write(LCRH::WLEN::EightBit); // 8N1 184 | self.CR 185 | .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); 186 | 187 | Ok(()) 188 | } 189 | 190 | /// Send a character 191 | pub fn send(&self, c: char) { 192 | // wait until we can send 193 | loop { 194 | if !self.FR.is_set(FR::TXFF) { 195 | break; 196 | } 197 | 198 | asm::nop(); 199 | } 200 | 201 | // write the character to the buffer 202 | self.DR.set(c as u32); 203 | } 204 | 205 | /// Receive a character 206 | pub fn getc(&self) -> char { 207 | // wait until something is in the buffer 208 | loop { 209 | if !self.FR.is_set(FR::RXFE) { 210 | break; 211 | } 212 | 213 | asm::nop(); 214 | } 215 | 216 | // read it and return 217 | let mut ret = self.DR.get() as u8 as char; 218 | 219 | // convert carrige return to newline 220 | if ret == '\r' { 221 | ret = '\n' 222 | } 223 | 224 | ret 225 | } 226 | 227 | /// Display a string 228 | pub fn puts(&self, string: &str) { 229 | for c in string.chars() { 230 | // convert newline to carrige return + newline 231 | if c == '\n' { 232 | self.send('\r') 233 | } 234 | 235 | self.send(c); 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /tutorials/00_preparation/README.md: -------------------------------------------------------------------------------- 1 | ## 准备与开发环境 2 | 3 | 因为是在裸机上直接跑系统,会涉及到硬件,所以还需要读者对硬件有一点了解,同时需要对 Rust 也有一点了解。 4 | 5 | ### 树莓派 6 | 7 | 首先心仪 💓的树莓派到手了,你需要熟悉一下它,最简单的办法就是安装树莓派官方的系统。为此,你需要: 8 | 9 | 1. 一个 8G 以上的 micro SD 卡 10 | 2. 此外,你需要一个读卡器 11 | 12 | 这些在大天朝某宝上都能买到,实用兼实惠。 13 | 14 | 网上有很多相关的安装教程,此处不赘述,[官方教程可以看这里](https://www.raspberrypi.org/documentation/installation/installing-images/) 15 | 16 | ### Rust 17 | 18 | 首先你需要一个合适的 rust 工具链,你需要安装 nightly 版本的 rust 以及 llvm 等等: 19 | 20 | ```bash 21 | $ curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly 22 | $ rustup component add rust-src llvm-tools-preview 23 | $ cargo install cargo-xbuild cargo-binutils 24 | $ rustup component add clippy-preview --toolchain=nightly 25 | ``` 26 | 27 | ⚠️⚠️⚠️ 读者需确保自己的编译环境搭建好 ⚠️⚠️⚠️ 28 | 29 | ### Micro SD卡相关 30 | 31 | 当你安装了树莓派的提供的系统并且让它很好的运行起来后,你的 micro SD 卡已经是 FAT 文件系统,并且已经建好了 MSR 分区。 32 | 33 | 当你使用读卡器读取 micro SD 卡的内容时,你会发现里面的内容与树莓派官方提供的 [固件文件](https://github.com/raspberrypi/firmware/tree/master/boot) 是一致的。当你需要更新系统时,可以直接更换树莓派官方提供的 [固件文件](https://github.com/raspberrypi/firmware/tree/master/boot) 。 34 | 35 | 当我们进行实验的时候,我们需要将原来的 `kernel`,`.img` 镜像文件删除,并换成我们自己编译出来文件。 36 | 37 | >为什么要这么做,以后尽量补上。 38 | 39 | ### Usb 转 ttl 串口线 40 | 41 | 英文翻译过来是 USB 串行调试线,不知道买什么样的同学可以直接某宝搜索“树莓派USB串口线”。 42 | 43 | #### 连接方式 44 | 45 | 电源线 🔌与 5v 连接,GND 互相连接,TX (GPIO14)、RX (GPIO15)交错连接。有关树莓派的 GPIO 更多信息,请看[官方描述](https://www.raspberrypi.org/documentation/usage/gpio/). 46 | 47 | ![GPIO对照](pic/gpios.png) 48 | 49 | ![GPIO](pic/gpio_info.png) 50 | 51 | >题外话,可能过程中会遇到一些问题,读者需细心检查。我在连接的时候,自己坑了自己一把,就是杜邦线有松动导致电源不稳定,常常弄着弄着就断电了。 52 | 53 | 连接参考: 54 | ![调试线连接](pic/uart.jpg) 55 | 56 | 连接上你的电脑后(*nix系统,window的童鞋可以使用串口连接工具),首先查看你的树莓派 uart 设备是哪个: 57 | 58 | ```bash 59 | $ ls /dev | grep cu 60 | ``` 61 | 62 | 比方说这样: 63 | 64 | ![](pic/ls_dev.png) 65 | 66 | 67 | ```bash 68 | sudo screen /dev/usbserial-1420 115200 69 | ``` 70 | 71 | `115200` 是串口波特率,树莓派默认是这个这样你就可以用过串口线与 pi 通信了,如需退出,可以按 `ctrl-a`, `ctrl-d`。 -------------------------------------------------------------------------------- /tutorials/00_preparation/pic/gpio_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/tutorials/00_preparation/pic/gpio_info.png -------------------------------------------------------------------------------- /tutorials/00_preparation/pic/gpios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/tutorials/00_preparation/pic/gpios.png -------------------------------------------------------------------------------- /tutorials/00_preparation/pic/ls_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/tutorials/00_preparation/pic/ls_dev.png -------------------------------------------------------------------------------- /tutorials/00_preparation/pic/uart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jancd/OS-tutorials/5816ee63f9a14e16dcf3afd0dde6630d93dc7462/tutorials/00_preparation/pic/uart.jpg -------------------------------------------------------------------------------- /tutorials/01_helloworld/README.md: -------------------------------------------------------------------------------- 1 | # HelloWorld 2 | 3 | 4 | ## GPIO初始化 5 | 6 | ## 电源 7 | 8 | ## mbox 9 | 10 | ## delay 11 | --------------------------------------------------------------------------------