├── ci ├── asm │ ├── rt2 │ │ ├── src │ │ ├── asm.s │ │ ├── link.x │ │ ├── librt.a │ │ ├── Cargo.toml │ │ ├── librt.objdump │ │ └── build.rs │ ├── app2 │ │ ├── src │ │ ├── .cargo │ │ ├── release.objdump │ │ ├── release.vector_table │ │ └── Cargo.toml │ ├── app │ │ ├── .cargo │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── main.rs │ │ ├── release.vector_table │ │ └── release.objdump │ └── rt │ │ ├── asm.s │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── link.x │ │ └── src │ │ └── lib.rs ├── singleton │ ├── rt │ ├── app │ │ ├── dev.out │ │ ├── release.objdump │ │ ├── .cargo │ │ ├── dev.objdump │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── log │ │ ├── log.x │ │ ├── build.rs │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── logging │ ├── app2 │ │ ├── dev.out │ │ ├── log.x │ │ ├── dev.objdump │ │ ├── Cargo.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ └── src │ │ │ └── main.rs │ ├── app3 │ │ ├── dev.out │ │ ├── .cargo │ │ ├── dev.objdump │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── app4 │ │ ├── dev.out │ │ ├── .cargo │ │ ├── dev.objdump │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── rt │ ├── log2 │ │ ├── build.rs │ │ ├── log.x │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── app │ │ ├── dev.out │ │ ├── release.out │ │ ├── dev.objdump │ │ ├── release.objdump │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── log │ │ ├── log.x │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ └── lib.rs ├── main │ ├── rt2 │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── link.x │ │ └── src │ │ │ └── lib.rs │ ├── app2 │ │ ├── Cargo.toml │ │ ├── .cargo │ │ └── src │ │ │ └── main.rs │ ├── app3 │ │ ├── Cargo.toml │ │ ├── .cargo │ │ └── src │ │ │ └── main.rs │ ├── rt-unsound │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── link.x │ │ └── src │ │ │ └── lib.rs │ ├── app │ │ ├── .cargo │ │ ├── src │ │ │ └── main.rs │ │ ├── Cargo.toml │ │ └── app.objdump │ ├── app4 │ │ ├── .cargo │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── app-unsound │ │ ├── .cargo │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── rt │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── src │ │ └── lib.rs │ │ └── link.x ├── exceptions │ ├── rt │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── link.x │ │ └── src │ │ │ └── lib.rs │ ├── app │ │ ├── .cargo │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── main.rs │ │ └── app.vector_table.objdump │ └── app2 │ │ ├── .cargo │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── dma │ ├── .gitignore │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── examples │ │ ├── two.rs │ │ ├── seven.rs │ │ ├── one.rs │ │ ├── four.rs │ │ ├── three.rs │ │ ├── five.rs │ │ ├── six.rs │ │ └── eight.rs ├── memory-layout │ ├── Cargo.toml │ ├── .cargo │ │ └── config.toml │ ├── app.vector_table.objdump │ ├── app.text.objdump │ ├── src │ │ └── main.rs │ └── link.x ├── smallest-no-std │ ├── app.o.nm │ ├── .cargo │ │ └── config.toml │ ├── app.size │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── after-success.sh └── script.sh ├── CNAME ├── .github ├── CODEOWNERS └── workflows │ └── ci.yaml ├── .gitignore ├── book.toml ├── src ├── SUMMARY.md ├── preface.md ├── sections-in-rust.md ├── smallest-no-std.md ├── asm.md ├── singleton.md ├── main.md ├── exceptions.md ├── custom-target.md ├── memory-layout.md ├── soc-support.md └── compiler-support.md ├── LICENSE-MIT ├── README.md ├── CODE_OF_CONDUCT.md └── LICENSE-APACHE /ci/asm/rt2/src: -------------------------------------------------------------------------------- 1 | ../rt/src -------------------------------------------------------------------------------- /ci/asm/app2/src: -------------------------------------------------------------------------------- 1 | ../app/src -------------------------------------------------------------------------------- /ci/asm/rt2/asm.s: -------------------------------------------------------------------------------- 1 | ../rt/asm.s -------------------------------------------------------------------------------- /ci/singleton/rt: -------------------------------------------------------------------------------- 1 | ../logging/rt -------------------------------------------------------------------------------- /ci/asm/rt2/link.x: -------------------------------------------------------------------------------- 1 | ../rt/link.x -------------------------------------------------------------------------------- /ci/logging/app2/dev.out: -------------------------------------------------------------------------------- 1 | 0001 2 | -------------------------------------------------------------------------------- /ci/logging/app3/dev.out: -------------------------------------------------------------------------------- 1 | 0001 2 | -------------------------------------------------------------------------------- /ci/logging/app4/dev.out: -------------------------------------------------------------------------------- 1 | 0100 2 | -------------------------------------------------------------------------------- /ci/logging/rt: -------------------------------------------------------------------------------- 1 | ../exceptions/rt -------------------------------------------------------------------------------- /ci/singleton/app/dev.out: -------------------------------------------------------------------------------- 1 | 0001 2 | -------------------------------------------------------------------------------- /ci/singleton/app/release.objdump: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ci/logging/app3/.cargo: -------------------------------------------------------------------------------- 1 | ../app2/.cargo -------------------------------------------------------------------------------- /ci/logging/app4/.cargo: -------------------------------------------------------------------------------- 1 | ../app2/.cargo -------------------------------------------------------------------------------- /ci/main/rt2/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../rt/Cargo.toml -------------------------------------------------------------------------------- /ci/main/rt2/build.rs: -------------------------------------------------------------------------------- 1 | ../rt/build.rs -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | embedonomicon.rust-embedded.org 2 | -------------------------------------------------------------------------------- /ci/asm/app/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/logging/log2/build.rs: -------------------------------------------------------------------------------- 1 | ../log/build.rs -------------------------------------------------------------------------------- /ci/main/app2/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../app/Cargo.toml -------------------------------------------------------------------------------- /ci/main/app3/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../app/Cargo.toml -------------------------------------------------------------------------------- /ci/main/rt-unsound/build.rs: -------------------------------------------------------------------------------- 1 | ../rt/build.rs -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rust-embedded/resources -------------------------------------------------------------------------------- /ci/asm/app2/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/exceptions/rt/build.rs: -------------------------------------------------------------------------------- 1 | ../../main/rt/build.rs -------------------------------------------------------------------------------- /ci/logging/app/dev.out: -------------------------------------------------------------------------------- 1 | 0x1fe0 2 | 0x1fe1 3 | -------------------------------------------------------------------------------- /ci/logging/app/release.out: -------------------------------------------------------------------------------- 1 | 0xb9c 2 | 0xb9d 3 | -------------------------------------------------------------------------------- /ci/main/app/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/main/app2/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/main/app3/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/main/app4/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/main/rt-unsound/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../rt/Cargo.toml -------------------------------------------------------------------------------- /ci/singleton/app/.cargo: -------------------------------------------------------------------------------- 1 | ../../logging/app2/.cargo -------------------------------------------------------------------------------- /ci/singleton/log/log.x: -------------------------------------------------------------------------------- 1 | ../../logging/log/log.x -------------------------------------------------------------------------------- /ci/asm/app2/release.objdump: -------------------------------------------------------------------------------- 1 | ../app/release.objdump -------------------------------------------------------------------------------- /ci/dma/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .#* 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /ci/exceptions/app/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/exceptions/app2/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/exceptions/rt/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../../main/rt/Cargo.toml -------------------------------------------------------------------------------- /ci/main/app-unsound/.cargo: -------------------------------------------------------------------------------- 1 | ../../memory-layout/.cargo -------------------------------------------------------------------------------- /ci/singleton/log/build.rs: -------------------------------------------------------------------------------- 1 | ../../logging/log/build.rs -------------------------------------------------------------------------------- /ci/exceptions/app2/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../../main/app/Cargo.toml -------------------------------------------------------------------------------- /ci/memory-layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | ../smallest-no-std/Cargo.toml -------------------------------------------------------------------------------- /ci/asm/app2/release.vector_table: -------------------------------------------------------------------------------- 1 | ../app/release.vector_table -------------------------------------------------------------------------------- /ci/smallest-no-std/app.o.nm: -------------------------------------------------------------------------------- 1 | 00000000 T __rustc::rust_begin_unwind 2 | -------------------------------------------------------------------------------- /ci/smallest-no-std/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7m-none-eabi" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .stderr 3 | .stdout 4 | Cargo.lock 5 | book 6 | target 7 | **/.idea/ -------------------------------------------------------------------------------- /ci/logging/app2/log.x: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .log 0 (INFO) : { 4 | *(.log); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ci/logging/log/log.x: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .log 0 (INFO) : { 4 | *(.log); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ci/asm/rt2/librt.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/embedonomicon/HEAD/ci/asm/rt2/librt.a -------------------------------------------------------------------------------- /ci/logging/app2/dev.objdump: -------------------------------------------------------------------------------- 1 | 00000001 g O .log 00000001 Goodbye 2 | 00000000 g O .log 00000001 Hello, world! 3 | -------------------------------------------------------------------------------- /ci/logging/app3/dev.objdump: -------------------------------------------------------------------------------- 1 | 00000001 g O .log 00000001 Goodbye 2 | 00000000 g O .log 00000001 Hello, world! 3 | -------------------------------------------------------------------------------- /ci/singleton/app/dev.objdump: -------------------------------------------------------------------------------- 1 | 00000001 g O .log 00000001 Goodbye 2 | 00000000 g O .log 00000001 Hello, world! 3 | -------------------------------------------------------------------------------- /ci/logging/app/dev.objdump: -------------------------------------------------------------------------------- 1 | 00001fe1 g .rodata 00000001 Goodbye 2 | 00001fe0 g .rodata 00000001 Hello, world! 3 | -------------------------------------------------------------------------------- /ci/logging/app/release.objdump: -------------------------------------------------------------------------------- 1 | 00000b9d g O .rodata 00000001 Goodbye 2 | 00000b9c g O .rodata 00000001 Hello, world! 3 | -------------------------------------------------------------------------------- /ci/smallest-no-std/app.size: -------------------------------------------------------------------------------- 1 | text data bss dec hex filename 2 | 0 0 0 0 0 app 3 | -------------------------------------------------------------------------------- /ci/asm/rt2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "rt" 4 | version = "0.1.0" 5 | authors = ["Jorge Aparicio "] 6 | -------------------------------------------------------------------------------- /ci/logging/log2/log.x: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .log 0 (INFO) : { 4 | *(.log.error); 5 | __log_warning_start__ = .; 6 | *(.log.warning); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ci/memory-layout/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | rustflags = ["-C", "link-arg=-Tlink.x"] 3 | 4 | [build] 5 | target = "thumbv7m-none-eabi" 6 | -------------------------------------------------------------------------------- /ci/main/app-unsound/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "app" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | rt = { path = "../rt-unsound" } 8 | -------------------------------------------------------------------------------- /ci/asm/rt/asm.s: -------------------------------------------------------------------------------- 1 | .section .text.HardFaultTrampoline 2 | .global HardFaultTrampoline 3 | .thumb_func 4 | HardFaultTrampoline: 5 | mrs r0, MSP 6 | b HardFault 7 | -------------------------------------------------------------------------------- /ci/logging/app4/dev.objdump: -------------------------------------------------------------------------------- 1 | 00000000 g O .log 00000001 Goodbye 2 | 00000001 g O .log 00000001 Hello, world! 3 | 00000001 g .log 00000000 __log_warning_start__ 4 | -------------------------------------------------------------------------------- /ci/logging/log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "log" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2024" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /ci/logging/log2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "log" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2024" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /ci/main/rt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "rt" # <- 4 | version = "0.1.0" 5 | authors = ["Jorge Aparicio "] 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /ci/memory-layout/app.vector_table.objdump: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | Contents of section .vector_table: 4 | 0000 00000120 09000000 ... .... 5 | -------------------------------------------------------------------------------- /ci/singleton/log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "log" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2024" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /ci/smallest-no-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "app" 4 | authors = ["Jorge Aparicio "] 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /ci/asm/rt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "rt" 4 | version = "0.1.0" 5 | authors = ["Jorge Aparicio "] 6 | 7 | [build-dependencies] 8 | cc = "1.0.25" 9 | -------------------------------------------------------------------------------- /ci/main/app2/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use rt::entry; 5 | 6 | entry!(main); 7 | 8 | fn main() -> ! { 9 | let _x = 42; 10 | 11 | loop {} 12 | } 13 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jorge Aparicio"] 3 | src = "src" 4 | title = "The Embedonomicon" 5 | 6 | [output.html] 7 | git-repository-url = "https://github.com/rust-embedded/embedonomicon" 8 | -------------------------------------------------------------------------------- /ci/main/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate rt; 5 | 6 | #[unsafe(no_mangle)] 7 | pub fn main() -> ! { 8 | let _x = 42; 9 | 10 | loop {} 11 | } 12 | -------------------------------------------------------------------------------- /ci/asm/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | rt = { path = "../rt" } 9 | -------------------------------------------------------------------------------- /ci/asm/app2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | rt = { path = "../rt2" } 9 | -------------------------------------------------------------------------------- /ci/main/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2024" 3 | name = "app" 4 | version = "0.1.0" 5 | authors = ["Jorge Aparicio "] 6 | 7 | [dependencies] 8 | rt = { path = "../rt" } 9 | -------------------------------------------------------------------------------- /ci/main/app4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | rt = { path = "../rt2" } 9 | -------------------------------------------------------------------------------- /ci/exceptions/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | rt = { path = "../rt" } 9 | -------------------------------------------------------------------------------- /ci/smallest-no-std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::panic::PanicInfo; 5 | 6 | #[panic_handler] 7 | #[inline(never)] 8 | fn panic(_panic: &PanicInfo<'_>) -> ! { 9 | loop {} 10 | } 11 | -------------------------------------------------------------------------------- /ci/dma/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "shared" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | as-slice = "0.1.0" 9 | pin-utils = "0.1.0-alpha.4" 10 | -------------------------------------------------------------------------------- /ci/logging/app3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m-semihosting = "0.3.1" 9 | log = { path = "../log" } 10 | rt = { path = "../rt" } 11 | -------------------------------------------------------------------------------- /ci/logging/app4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m-semihosting = "0.3.1" 9 | log = { path = "../log2" } 10 | rt = { path = "../rt" } 11 | -------------------------------------------------------------------------------- /ci/asm/rt2/librt.objdump: -------------------------------------------------------------------------------- 1 | In archive librt.a: 2 | 3 | asm.o: file format elf32-littlearm 4 | 5 | 6 | Disassembly of section .text.HardFaultTrampoline: 7 | 8 | 00000000 : 9 | 0: f3ef 8008 mrs r0, MSP 10 | 4: e7fe b.n 0 11 | -------------------------------------------------------------------------------- /ci/asm/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use rt::entry; 5 | 6 | entry!(main); 7 | 8 | fn main() -> ! { 9 | loop {} 10 | } 11 | 12 | #[allow(non_snake_case)] 13 | #[unsafe(no_mangle)] 14 | pub fn HardFault(_ef: *const u32) -> ! { 15 | loop {} 16 | } 17 | -------------------------------------------------------------------------------- /ci/logging/app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" 3 | rustflags = ["-C", "link-arg=-Tlink.x"] 4 | 5 | [build] 6 | target = "thumbv7m-none-eabi" 7 | -------------------------------------------------------------------------------- /ci/logging/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m-semihosting = "0.3.1" 9 | rt = { path = "../rt" } 10 | 11 | [profile.release] 12 | codegen-units = 1 13 | lto = true -------------------------------------------------------------------------------- /ci/logging/app2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m-semihosting = "0.3.1" 9 | rt = { path = "../rt" } 10 | 11 | [profile.release] 12 | codegen-units = 1 13 | lto = true -------------------------------------------------------------------------------- /ci/exceptions/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::arch::asm; 5 | use rt::entry; 6 | 7 | entry!(main); 8 | 9 | fn main() -> ! { 10 | // this executes the undefined instruction (UDF) and causes a HardFault exception 11 | unsafe { asm!("udf #0", options(noreturn)) }; 12 | } 13 | -------------------------------------------------------------------------------- /ci/logging/app2/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | "-C", "link-arg=-Tlog.x", # <- NEW! 6 | ] 7 | 8 | [build] 9 | target = "thumbv7m-none-eabi" 10 | -------------------------------------------------------------------------------- /ci/asm/app/release.vector_table: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | Contents of section .vector_table: 4 | 0000 00000120 4d000000 55000000 5b000000 ... M...U...[... 5 | 0010 55000000 55000000 55000000 00000000 U...U...U....... 6 | 0020 00000000 00000000 00000000 55000000 ............U... 7 | 0030 00000000 00000000 55000000 55000000 ........U...U... 8 | -------------------------------------------------------------------------------- /ci/singleton/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2024" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | [profile.release] 8 | codegen-units = 1 9 | lto = true 10 | 11 | [dependencies] 12 | cortex-m = "0.7.7" 13 | cortex-m-semihosting = "0.5.0" 14 | log = { path = "../log" } 15 | rt = { path = "../rt" } 16 | -------------------------------------------------------------------------------- /ci/exceptions/app2/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::arch::asm; 5 | 6 | use rt::entry; 7 | 8 | entry!(main); 9 | 10 | fn main() -> ! { 11 | unsafe { asm!("udf #0", options(noreturn)) }; 12 | } 13 | 14 | #[unsafe(no_mangle)] 15 | pub extern "C" fn HardFault() -> ! { 16 | // do something interesting here 17 | loop {} 18 | } 19 | -------------------------------------------------------------------------------- /ci/exceptions/app/app.vector_table.objdump: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | Contents of section .vector_table: 4 | 0000 00000120 49000000 85000000 85000000 ... I........... 5 | 0010 85000000 85000000 85000000 00000000 ................ 6 | 0020 00000000 00000000 00000000 85000000 ................ 7 | 0030 00000000 00000000 85000000 85000000 ................ 8 | -------------------------------------------------------------------------------- /ci/main/app3/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use rt::entry; 5 | 6 | entry!(main); 7 | 8 | static RODATA: &[u8] = b"Hello, world!"; 9 | static mut BSS: u8 = 0; 10 | static mut DATA: u16 = 1; 11 | 12 | #[allow(static_mut_refs)] 13 | fn main() -> ! { 14 | let _x = RODATA; 15 | let _y = unsafe { &BSS }; 16 | let _z = unsafe { &DATA }; 17 | 18 | loop {} 19 | } 20 | -------------------------------------------------------------------------------- /ci/memory-layout/app.text.objdump: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | 4 | Disassembly of section .text: 5 | 6 | : 7 | push {r7, lr} 8 | mov r7, sp 9 | sub sp, #0x4 10 | movs r0, #0x2a 11 | str r0, [sp] 12 | b 0x14 @ imm = #-0x2 13 | b 0x14 @ imm = #-0x4 14 | -------------------------------------------------------------------------------- /ci/logging/log/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs::File, io::Write, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | // Put the linker script somewhere the linker can find it 5 | let out = PathBuf::from(env::var("OUT_DIR")?); 6 | 7 | File::create(out.join("log.x"))?.write_all(include_bytes!("log.x"))?; 8 | 9 | println!("cargo:rustc-link-search={}", out.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /ci/main/app4/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::{arch::asm, ptr}; 5 | 6 | use rt::entry; 7 | 8 | entry!(main); 9 | 10 | static mut DATA: i32 = 1; 11 | 12 | #[allow(static_mut_refs)] 13 | fn main() -> ! { 14 | unsafe { 15 | // check that DATA is properly initialized 16 | if ptr::read_volatile(&DATA) != 1 { 17 | // this makes QEMU crash 18 | asm!("BKPT"); 19 | } 20 | } 21 | 22 | loop {} 23 | } 24 | -------------------------------------------------------------------------------- /ci/logging/log/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub trait Log { 4 | type Error; 5 | 6 | fn log(&mut self, address: u8) -> Result<(), Self::Error>; 7 | } 8 | 9 | #[macro_export] 10 | macro_rules! log { 11 | ($logger:expr, $string:expr) => {{ 12 | #[unsafe(export_name = $string)] 13 | #[unsafe(link_section = ".log")] 14 | static SYMBOL: u8 = 0; 15 | 16 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 17 | }}; 18 | } 19 | -------------------------------------------------------------------------------- /ci/main/app-unsound/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::{arch::asm, ptr}; 5 | 6 | use rt::entry; 7 | 8 | entry!(main); 9 | 10 | static mut DATA: i32 = 1; 11 | 12 | #[allow(static_mut_refs)] 13 | fn main() -> ! { 14 | unsafe { 15 | // check that DATA is properly initialized 16 | if ptr::read_volatile(&DATA) != 1 { 17 | // this makes QEMU crash 18 | asm!("BKPT"); 19 | } 20 | } 21 | 22 | loop {} 23 | } 24 | -------------------------------------------------------------------------------- /ci/main/rt/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs::File, io::Write, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | // build directory for this crate 5 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 6 | 7 | // extend the library search path 8 | println!("cargo:rustc-link-search={}", out_dir.display()); 9 | 10 | // put `link.x` in the build directory 11 | File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?; 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /ci/after-success.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | mkdir ghp-import 5 | curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz | 6 | tar --strip-components 1 -C ghp-import -xz 7 | 8 | ./ghp-import/ghp_import.py book 9 | 10 | # NOTE(+x) don't print $GH_TOKEN to the console! 11 | set +x 12 | git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK 13 | } 14 | 15 | if [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ]; then 16 | main 17 | fi 18 | -------------------------------------------------------------------------------- /ci/main/app/app.objdump: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | 4 | Disassembly of section .text: 5 | 6 |
: 7 | push {r7, lr} 8 | mov r7, sp 9 | sub sp, #0x4 10 | movs r0, #0x2a 11 | str r0, [sp] 12 | b 0x14 @ imm = #-0x2 13 | b 0x14 @ imm = #-0x4 14 | 15 | : 16 | push {r7, lr} 17 | mov r7, sp 18 | bl 0x8
@ imm = #-0x16 19 | -------------------------------------------------------------------------------- /ci/memory-layout/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::panic::PanicInfo; 5 | 6 | // The reset handler 7 | #[unsafe(no_mangle)] 8 | pub unsafe extern "C" fn Reset() -> ! { 9 | let _x = 42; 10 | 11 | // can't return so we go into an infinite loop here 12 | loop {} 13 | } 14 | 15 | // The reset vector, a pointer into the reset handler 16 | #[unsafe(link_section = ".vector_table.reset_vector")] 17 | #[unsafe(no_mangle)] 18 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 19 | 20 | #[panic_handler] 21 | fn panic(_panic: &PanicInfo<'_>) -> ! { 22 | loop {} 23 | } 24 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Preface](./preface.md) 4 | - [The smallest `#![no_std]` program](./smallest-no-std.md) 5 | - [Memory layout](./memory-layout.md) 6 | - [A `main` interface](./main.md) 7 | - [Why don't we initialize `.data` and `.bss` using Rust](./sections-in-rust.md) 8 | - [Exception handling](./exceptions.md) 9 | - [Assembly on stable](./asm.md) 10 | - [Logging with symbols](./logging.md) 11 | - [Global singletons](./singleton.md) 12 | - [DMA](./dma.md) 13 | --- 14 | [A note on compiler support](./compiler-support.md) 15 | [Creating a custom target](./custom-target.md) 16 | [Supporting a new SoC](./soc-support.md) 17 | -------------------------------------------------------------------------------- /ci/logging/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::fmt::Write; 5 | use cortex_m_semihosting::{debug, hio}; 6 | 7 | use rt::entry; 8 | 9 | entry!(main); 10 | 11 | fn main() -> ! { 12 | let mut hstdout = hio::hstdout().unwrap(); 13 | 14 | #[unsafe(export_name = "Hello, world!")] 15 | static A: u8 = 0; 16 | 17 | let _ = writeln!(hstdout, "{:#x}", &A as *const u8 as usize); 18 | 19 | #[unsafe(export_name = "Goodbye")] 20 | static B: u8 = 0; 21 | 22 | let _ = writeln!(hstdout, "{:#x}", &B as *const u8 as usize); 23 | 24 | debug::exit(debug::EXIT_SUCCESS); 25 | 26 | loop {} 27 | } 28 | -------------------------------------------------------------------------------- /ci/asm/rt/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs::File, io::Write, path::PathBuf}; 2 | 3 | use cc::Build; 4 | 5 | fn main() -> Result<(), Box> { 6 | // build directory for this crate 7 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 8 | 9 | // extend the library search path 10 | println!("cargo:rustc-link-search={}", out_dir.display()); 11 | 12 | // put `link.x` in the build directory 13 | File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?; 14 | 15 | // assemble the `asm.s` file 16 | Build::new().file("asm.s").compile("asm"); // <- NEW! 17 | 18 | // rebuild if `asm.s` changed 19 | println!("cargo:rerun-if-changed=asm.s"); // <- NEW! 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /ci/logging/app3/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_semihosting::{ 5 | debug, 6 | hio::{self, HStdout}, 7 | }; 8 | 9 | use log::{log, Log}; 10 | use rt::entry; 11 | 12 | struct Logger { 13 | hstdout: HStdout, 14 | } 15 | 16 | impl Log for Logger { 17 | type Error = (); 18 | 19 | fn log(&mut self, address: u8) -> Result<(), ()> { 20 | self.hstdout.write_all(&[address]) 21 | } 22 | } 23 | 24 | entry!(main); 25 | 26 | fn main() -> ! { 27 | let hstdout = hio::hstdout().unwrap(); 28 | let mut logger = Logger { hstdout }; 29 | 30 | let _ = log!(logger, "Hello, world!"); 31 | 32 | let _ = log!(logger, "Goodbye"); 33 | 34 | debug::exit(debug::EXIT_SUCCESS); 35 | 36 | loop {} 37 | } 38 | -------------------------------------------------------------------------------- /ci/memory-layout/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | 14 | SECTIONS 15 | { 16 | .vector_table ORIGIN(FLASH) : 17 | { 18 | /* First entry: initial Stack Pointer value */ 19 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 20 | 21 | /* Second entry: reset vector */ 22 | KEEP(*(.vector_table.reset_vector)); 23 | } > FLASH 24 | 25 | .text : 26 | { 27 | *(.text .text.*); 28 | } > FLASH 29 | 30 | /DISCARD/ : 31 | { 32 | *(.ARM.exidx .ARM.exidx.*); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ci/logging/app4/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_semihosting::{ 5 | debug, 6 | hio::{self, HStdout}, 7 | }; 8 | 9 | use log::{error, warn, Log}; 10 | use rt::entry; 11 | 12 | entry!(main); 13 | 14 | fn main() -> ! { 15 | let hstdout = hio::hstdout().unwrap(); 16 | let mut logger = Logger { hstdout }; 17 | 18 | let _ = warn!(logger, "Hello, world!"); // <- CHANGED! 19 | 20 | let _ = error!(logger, "Goodbye"); // <- CHANGED! 21 | 22 | debug::exit(debug::EXIT_SUCCESS); 23 | 24 | loop {} 25 | } 26 | 27 | struct Logger { 28 | hstdout: HStdout, 29 | } 30 | 31 | impl Log for Logger { 32 | type Error = (); 33 | 34 | fn log(&mut self, address: u8) -> Result<(), ()> { 35 | self.hstdout.write_all(&[address]) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ci/logging/app2/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_semihosting::{debug, hio}; 5 | 6 | use rt::entry; 7 | 8 | entry!(main); 9 | 10 | fn main() -> ! { 11 | let mut hstdout = hio::hstdout().unwrap(); 12 | 13 | #[unsafe(export_name = "Hello, world!")] 14 | #[unsafe(link_section = ".log")] // <- NEW! 15 | static A: u8 = 0; 16 | 17 | let address = &A as *const u8 as usize as u8; 18 | hstdout.write_all(&[address]).unwrap(); // <- CHANGED! 19 | 20 | #[unsafe(export_name = "Goodbye")] 21 | #[unsafe(link_section = ".log")] // <- NEW! 22 | static B: u8 = 0; 23 | 24 | let address = &B as *const u8 as usize as u8; 25 | hstdout.write_all(&[address]).unwrap(); // <- CHANGED! 26 | 27 | debug::exit(debug::EXIT_SUCCESS); 28 | 29 | loop {} 30 | } 31 | -------------------------------------------------------------------------------- /ci/asm/rt2/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | error::Error, 4 | fs::{self, File}, 5 | io::Write, 6 | path::PathBuf, 7 | }; 8 | 9 | fn main() -> Result<(), Box> { 10 | // build directory for this crate 11 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 12 | 13 | // extend the library search path 14 | println!("cargo:rustc-link-search={}", out_dir.display()); 15 | 16 | // put `link.x` in the build directory 17 | File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?; 18 | 19 | // link to `librt.a` 20 | fs::copy("librt.a", out_dir.join("librt.a"))?; // <- NEW! 21 | println!("cargo:rustc-link-lib=static=rt"); // <- NEW! 22 | 23 | // rebuild if `librt.a` changed 24 | println!("cargo:rerun-if-changed=librt.a"); // <- NEW! 25 | 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /ci/asm/app/release.objdump: -------------------------------------------------------------------------------- 1 | 2 | app: file format elf32-littlearm 3 | 4 | Disassembly of section .text: 5 | 6 | 00000040 : 7 | 40: push {r7, lr} 8 | 42: mov r7, sp 9 | 44: b 0x44 @ imm = #-0x4 10 | 11 | 00000046
: 12 | 46: push {r7, lr} 13 | 48: mov r7, sp 14 | 4a: b 0x4a @ imm = #-0x4 15 | 16 | 0000004c : 17 | 4c: push {r7, lr} 18 | 4e: mov r7, sp 19 | 50: bl 0x46
@ imm = #-0xe 20 | 21 | 00000054 : 22 | 54: push {r7, lr} 23 | 56: mov r7, sp 24 | 58: b 0x58 @ imm = #-0x4 25 | 26 | 0000005a : 27 | 5a: mrs r0, msp 28 | 5e: b 0x40 @ imm = #-0x22 29 | -------------------------------------------------------------------------------- /ci/main/rt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | 5 | // CHANGED! 6 | #[unsafe(no_mangle)] 7 | pub unsafe extern "C" fn Reset() -> ! { 8 | unsafe extern "Rust" { 9 | safe fn main() -> !; 10 | } 11 | 12 | main() 13 | } 14 | 15 | // The reset vector, a pointer into the reset handler 16 | #[unsafe(link_section = ".vector_table.reset_vector")] 17 | #[unsafe(no_mangle)] 18 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 19 | 20 | #[panic_handler] 21 | fn panic(_panic: &PanicInfo<'_>) -> ! { 22 | loop {} 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! entry { 27 | ($path:path) => { 28 | #[unsafe(export_name = "main")] 29 | pub unsafe fn __main() -> ! { 30 | // type check the given path 31 | let f: fn() -> ! = $path; 32 | 33 | f() 34 | } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /ci/logging/log2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub trait Log { 4 | type Error; 5 | 6 | fn log(&mut self, address: u8) -> Result<(), Self::Error>; 7 | } 8 | 9 | /// Logs messages at the ERROR log level 10 | #[macro_export] 11 | macro_rules! error { 12 | ($logger:expr, $string:expr) => {{ 13 | #[unsafe(export_name = $string)] 14 | #[unsafe(link_section = ".log.error")] // <- CHANGED! 15 | static SYMBOL: u8 = 0; 16 | 17 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 18 | }}; 19 | } 20 | 21 | /// Logs messages at the WARNING log level 22 | #[macro_export] 23 | macro_rules! warn { 24 | ($logger:expr, $string:expr) => {{ 25 | #[unsafe(export_name = $string)] 26 | #[unsafe(link_section = ".log.warning")] // <- CHANGED! 27 | static SYMBOL: u8 = 0; 28 | 29 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 30 | }}; 31 | } 32 | -------------------------------------------------------------------------------- /ci/main/rt/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | 14 | SECTIONS 15 | { 16 | .vector_table ORIGIN(FLASH) : 17 | { 18 | /* First entry: initial Stack Pointer value */ 19 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 20 | 21 | /* Second entry: reset vector */ 22 | KEEP(*(.vector_table.reset_vector)); 23 | } > FLASH 24 | 25 | .text : 26 | { 27 | *(.text .text.*); 28 | } > FLASH 29 | 30 | /* NEW! */ 31 | .rodata : 32 | { 33 | *(.rodata .rodata.*); 34 | } > FLASH 35 | 36 | .bss : 37 | { 38 | *(.bss .bss.*); 39 | } > RAM 40 | 41 | .data : 42 | { 43 | *(.data .data.*); 44 | } > RAM 45 | 46 | /DISCARD/ : 47 | { 48 | *(.ARM.exidx .ARM.exidx.*); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ci/main/rt2/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | 14 | SECTIONS 15 | { 16 | .vector_table ORIGIN(FLASH) : 17 | { 18 | /* First entry: initial Stack Pointer value */ 19 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 20 | 21 | /* Second entry: reset vector */ 22 | KEEP(*(.vector_table.reset_vector)); 23 | } > FLASH 24 | 25 | .text : 26 | { 27 | *(.text .text.*); 28 | } > FLASH 29 | 30 | /* CHANGED! */ 31 | .rodata : 32 | { 33 | *(.rodata .rodata.*); 34 | } > FLASH 35 | 36 | .bss : 37 | { 38 | _sbss = .; 39 | *(.bss .bss.*); 40 | _ebss = .; 41 | } > RAM 42 | 43 | .data : AT(ADDR(.rodata) + SIZEOF(.rodata)) 44 | { 45 | _sdata = .; 46 | *(.data .data.*); 47 | _edata = .; 48 | } > RAM 49 | 50 | _sidata = LOADADDR(.data); 51 | 52 | /DISCARD/ : 53 | { 54 | *(.ARM.exidx .ARM.exidx.*); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ci/main/rt-unsound/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | 14 | SECTIONS 15 | { 16 | .vector_table ORIGIN(FLASH) : 17 | { 18 | /* First entry: initial Stack Pointer value */ 19 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 20 | 21 | /* Second entry: reset vector */ 22 | KEEP(*(.vector_table.reset_vector)); 23 | } > FLASH 24 | 25 | .text : 26 | { 27 | *(.text .text.*); 28 | } > FLASH 29 | 30 | /* CHANGED! */ 31 | .rodata : 32 | { 33 | *(.rodata .rodata.*); 34 | } > FLASH 35 | 36 | .bss : 37 | { 38 | _sbss = .; 39 | *(.bss .bss.*); 40 | _ebss = .; 41 | } > RAM 42 | 43 | .data : AT(ADDR(.rodata) + SIZEOF(.rodata)) 44 | { 45 | _sdata = .; 46 | *(.data .data.*); 47 | _edata = .; 48 | } > RAM 49 | 50 | _sidata = LOADADDR(.data); 51 | 52 | /DISCARD/ : 53 | { 54 | *(.ARM.exidx .ARM.exidx.*); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ci/singleton/log/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | // NEW! 4 | pub trait GlobalLog: Sync { 5 | fn log(&self, address: u8); 6 | } 7 | 8 | pub trait Log { 9 | type Error; 10 | 11 | fn log(&mut self, address: u8) -> Result<(), Self::Error>; 12 | } 13 | 14 | #[macro_export] 15 | macro_rules! log { 16 | // NEW! 17 | ($string:expr) => { 18 | unsafe { 19 | unsafe extern "Rust" { 20 | static LOGGER: &'static dyn $crate::GlobalLog; 21 | } 22 | 23 | #[unsafe(export_name = $string)] 24 | #[unsafe(link_section = ".log")] 25 | static SYMBOL: u8 = 0; 26 | 27 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 28 | } 29 | }; 30 | 31 | ($logger:expr, $string:expr) => {{ 32 | #[unsafe(export_name = $string)] 33 | #[unsafe(link_section = ".log")] 34 | static SYMBOL: u8 = 0; 35 | 36 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 37 | }}; 38 | } 39 | 40 | // NEW! 41 | #[macro_export] 42 | macro_rules! global_logger { 43 | ($logger:expr) => { 44 | #[unsafe(no_mangle)] 45 | pub static LOGGER: &dyn $crate::GlobalLog = &$logger; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /ci/singleton/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::cell::RefCell; 5 | use cortex_m::interrupt; 6 | use cortex_m::interrupt::Mutex; 7 | use cortex_m_semihosting::{ 8 | debug, 9 | hio::{self, HostStream}, 10 | }; 11 | 12 | use log::{GlobalLog, global_logger, log}; 13 | use rt::entry; 14 | 15 | struct Logger; 16 | 17 | global_logger!(Logger); 18 | 19 | entry!(main); 20 | 21 | fn main() -> ! { 22 | log!("Hello, world!"); 23 | 24 | log!("Goodbye"); 25 | 26 | debug::exit(debug::EXIT_SUCCESS); 27 | 28 | loop {} 29 | } 30 | 31 | impl GlobalLog for Logger { 32 | fn log(&self, address: u8) { 33 | // we use a critical section (`interrupt::free`) to make the access to the 34 | // `HSTDOUT` variable interrupt-safe which is required for memory safety 35 | interrupt::free(|cs| { 36 | static HSTDOUT: Mutex>> = Mutex::new(RefCell::new(None)); 37 | let mut hstdout = HSTDOUT.borrow(cs).borrow_mut(); 38 | 39 | // lazy initialization 40 | if hstdout.is_none() { 41 | hstdout.replace(hio::hstdout()?); 42 | } 43 | 44 | let hstdout = hstdout.as_mut().unwrap(); 45 | 46 | hstdout.write_all(&[address]) 47 | }) 48 | .ok(); // `.ok()` = ignore errors 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ci/main/rt-unsound/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | use core::ptr; 5 | 6 | #[unsafe(no_mangle)] 7 | #[allow(static_mut_refs)] 8 | pub unsafe extern "C" fn Reset() -> ! { 9 | // NEW! 10 | // Initialize RAM 11 | unsafe extern "C" { 12 | static mut _sbss: u8; 13 | static mut _ebss: u8; 14 | 15 | static mut _sdata: u8; 16 | static mut _edata: u8; 17 | static _sidata: u8; 18 | } 19 | 20 | let count = unsafe { &_ebss as *const u8 as usize - &_sbss as *const u8 as usize }; 21 | unsafe { ptr::write_bytes(&mut _sbss as *mut u8, 0, count) }; 22 | 23 | let count = unsafe { &_edata as *const u8 as usize - &_sdata as *const u8 as usize }; 24 | unsafe { ptr::copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count) }; 25 | 26 | // Call user entry point 27 | unsafe extern "Rust" { 28 | safe fn main() -> !; 29 | } 30 | 31 | main() 32 | } 33 | 34 | // The reset vector, a pointer into the reset handler 35 | #[unsafe(link_section = ".vector_table.reset_vector")] 36 | #[unsafe(no_mangle)] 37 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 38 | 39 | #[panic_handler] 40 | fn panic(_panic: &PanicInfo<'_>) -> ! { 41 | loop {} 42 | } 43 | 44 | #[macro_export] 45 | macro_rules! entry { 46 | ($path:path) => { 47 | #[unsafe(export_name = "main")] 48 | pub unsafe fn __main() -> ! { 49 | // type check the given path 50 | let f: fn() -> ! = $path; 51 | 52 | f() 53 | } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /ci/asm/rt/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | EXTERN(EXCEPTIONS); 14 | 15 | SECTIONS 16 | { 17 | .vector_table ORIGIN(FLASH) : 18 | { 19 | /* First entry: initial Stack Pointer value */ 20 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 21 | 22 | /* Second entry: reset vector */ 23 | KEEP(*(.vector_table.reset_vector)); 24 | 25 | /* The next 14 entries are exception vectors */ 26 | KEEP(*(.vector_table.exceptions)); /* <- NEW */ 27 | } > FLASH 28 | 29 | .text : 30 | { 31 | *(.text .text.*); 32 | } > FLASH 33 | 34 | /* CHANGED! */ 35 | .rodata : 36 | { 37 | *(.rodata .rodata.*); 38 | } > FLASH 39 | 40 | .bss : 41 | { 42 | _sbss = .; 43 | *(.bss .bss.*); 44 | _ebss = .; 45 | } > RAM 46 | 47 | .data : AT(ADDR(.rodata) + SIZEOF(.rodata)) 48 | { 49 | _sdata = .; 50 | *(.data .data.*); 51 | _edata = .; 52 | } > RAM 53 | 54 | _sidata = LOADADDR(.data); 55 | 56 | /DISCARD/ : 57 | { 58 | *(.ARM.exidx .ARM.exidx.*); 59 | } 60 | } 61 | 62 | PROVIDE(NMI = DefaultExceptionHandler); 63 | PROVIDE(HardFault = DefaultExceptionHandler); 64 | PROVIDE(MemManage = DefaultExceptionHandler); 65 | PROVIDE(BusFault = DefaultExceptionHandler); 66 | PROVIDE(UsageFault = DefaultExceptionHandler); 67 | PROVIDE(SVCall = DefaultExceptionHandler); 68 | PROVIDE(PendSV = DefaultExceptionHandler); 69 | PROVIDE(SysTick = DefaultExceptionHandler); 70 | -------------------------------------------------------------------------------- /ci/exceptions/rt/link.x: -------------------------------------------------------------------------------- 1 | /* Memory layout of the LM3S6965 microcontroller */ 2 | /* 1K = 1 KiBi = 1024 bytes */ 3 | MEMORY 4 | { 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | 9 | /* The entry point is the reset handler */ 10 | ENTRY(Reset); 11 | 12 | EXTERN(RESET_VECTOR); 13 | EXTERN(EXCEPTIONS); /* <- NEW */ 14 | 15 | SECTIONS 16 | { 17 | .vector_table ORIGIN(FLASH) : 18 | { 19 | /* First entry: initial Stack Pointer value */ 20 | LONG(ORIGIN(RAM) + LENGTH(RAM)); 21 | 22 | /* Second entry: reset vector */ 23 | KEEP(*(.vector_table.reset_vector)); 24 | 25 | /* The next 14 entries are exception vectors */ 26 | KEEP(*(.vector_table.exceptions)); /* <- NEW */ 27 | } > FLASH 28 | 29 | .text : 30 | { 31 | *(.text .text.*); 32 | } > FLASH 33 | 34 | /* CHANGED! */ 35 | .rodata : 36 | { 37 | *(.rodata .rodata.*); 38 | } > FLASH 39 | 40 | .bss : 41 | { 42 | _sbss = .; 43 | *(.bss .bss.*); 44 | _ebss = .; 45 | } > RAM 46 | 47 | .data : AT(ADDR(.rodata) + SIZEOF(.rodata)) 48 | { 49 | _sdata = .; 50 | *(.data .data.*); 51 | _edata = .; 52 | } > RAM 53 | 54 | _sidata = LOADADDR(.data); 55 | 56 | /DISCARD/ : 57 | { 58 | *(.ARM.exidx .ARM.exidx.*); 59 | } 60 | } 61 | 62 | PROVIDE(NMI = DefaultExceptionHandler); 63 | PROVIDE(HardFault = DefaultExceptionHandler); 64 | PROVIDE(MemManage = DefaultExceptionHandler); 65 | PROVIDE(BusFault = DefaultExceptionHandler); 66 | PROVIDE(UsageFault = DefaultExceptionHandler); 67 | PROVIDE(SVCall = DefaultExceptionHandler); 68 | PROVIDE(PendSV = DefaultExceptionHandler); 69 | PROVIDE(SysTick = DefaultExceptionHandler); 70 | -------------------------------------------------------------------------------- /ci/main/rt2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | 5 | use core::arch::global_asm; 6 | 7 | global_asm!( 8 | ".text 9 | 10 | .syntax unified 11 | .global _sbss 12 | .global _ebss 13 | 14 | .global _sdata 15 | .global _edata 16 | .global _sidata 17 | 18 | .global main 19 | .global Reset 20 | 21 | .type Reset,%function 22 | .thumb_func 23 | Reset: 24 | 25 | _init_bss: 26 | movs r2, #0 27 | ldr r0, =_sbss 28 | ldr r1, =_ebss 29 | 30 | 1: 31 | cmp r1, r0 32 | beq _init_data 33 | strb r2, [r0] 34 | add r0, #1 35 | b 1b 36 | 37 | _init_data: 38 | ldr r0, =_sdata 39 | ldr r1, =_edata 40 | ldr r2, =_sidata 41 | 42 | 1: 43 | cmp r0, r1 44 | beq _main_trampoline 45 | ldrb r3, [r2] 46 | strb r3, [r0] 47 | add r0, #1 48 | add r2, #1 49 | b 1b 50 | _main_trampoline: 51 | ldr r0, =main 52 | bx r0" 53 | ); 54 | 55 | unsafe extern "C" { 56 | pub safe fn Reset() -> !; 57 | } 58 | 59 | // The reset vector, a pointer into the reset handler 60 | #[unsafe(link_section = ".vector_table.reset_vector")] 61 | #[unsafe(no_mangle)] 62 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 63 | 64 | #[panic_handler] 65 | fn panic(_panic: &PanicInfo<'_>) -> ! { 66 | loop {} 67 | } 68 | 69 | #[macro_export] 70 | macro_rules! entry { 71 | ($path:path) => { 72 | #[unsafe(export_name = "main")] 73 | pub unsafe fn __main() -> ! { 74 | // type check the given path 75 | let f: fn() -> ! = $path; 76 | 77 | f() 78 | } 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Embedonomicon 2 | 3 | > How to bootstrap support for a no_std target 4 | 5 | This book is hosted at https://docs.rust-embedded.org/embedonomicon/ 6 | 7 | This project is developed and maintained by the [Resources team][team]. 8 | 9 | ## License 10 | 11 | The Embedonomicon (this project) is distributed under the following licenses: 12 | 13 | * The code samples and free-standing Cargo projects contained within this book are licensed under 14 | the terms of both the [MIT License] and the [Apache License v2.0]. 15 | * The written prose contained within this book is licensed under the terms of the Creative Commons 16 | [CC-BY-SA v4.0] license. 17 | 18 | Copies of the licenses used by this project may also be found here: 19 | 20 | * [MIT License Hosted] 21 | * [Apache License v2.0 Hosted] 22 | * [CC-BY-SA v4.0 Hosted] 23 | 24 | [MIT License]: ./LICENSE-MIT 25 | [Apache License v2.0]: ./LICENSE-APACHE 26 | [CC-BY-SA v4.0]: ./LICENSE-CC-BY-SA 27 | [MIT License Hosted]: https://opensource.org/licenses/MIT 28 | [Apache License v2.0 Hosted]: http://www.apache.org/licenses/LICENSE-2.0 29 | [CC-BY-SA v4.0 Hosted]: https://creativecommons.org/licenses/by-sa/4.0/legalcode 30 | 31 | ### Contribution 32 | 33 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 34 | work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any 35 | additional terms or conditions. 36 | 37 | ## Code of Conduct 38 | 39 | Contribution to this crate is organized under the terms of the [Rust Code of 40 | Conduct][CoC], the maintainer of this crate, the [Resources team][team], promises 41 | to intervene to uphold that code of conduct. 42 | 43 | [CoC]: CODE_OF_CONDUCT.md 44 | [team]: https://github.com/rust-embedded/wg#the-resources-team 45 | -------------------------------------------------------------------------------- /ci/dma/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | pub const USART1_TX: usize = 0x4000_0000; 4 | pub const USART1_RX: usize = 0x4000_0004; 5 | 6 | /// A singleton that represents a single DMA channel (channel 1 in this case) 7 | /// 8 | /// This singleton has exclusive access to the registers of the DMA channel 1 9 | pub struct Dma1Channel1 { 10 | // .. 11 | } 12 | 13 | impl Dma1Channel1 { 14 | /// Data will be written to this `address` 15 | /// 16 | /// `inc` indicates whether the address will be incremented after every byte 17 | /// transfer 18 | /// 19 | /// NOTE this performs a volatile write 20 | pub fn set_destination_address(&mut self, address: usize, inc: bool) { 21 | // .. 22 | } 23 | 24 | /// Data will be read from this `address` 25 | /// 26 | /// `inc` indicates whether the address will be incremented after every byte 27 | /// transfer 28 | /// 29 | /// NOTE this performs a volatile write 30 | pub fn set_source_address(&mut self, address: usize, inc: bool) { 31 | // .. 32 | } 33 | 34 | /// Number of bytes to transfer 35 | /// 36 | /// NOTE this performs a volatile write 37 | pub fn set_transfer_length(&mut self, len: usize) { 38 | // .. 39 | } 40 | 41 | /// Starts the DMA transfer 42 | /// 43 | /// NOTE this performs a volatile write 44 | pub fn start(&mut self) { 45 | // .. 46 | } 47 | 48 | /// Stops the DMA transfer 49 | /// 50 | /// NOTE this performs a volatile write 51 | pub fn stop(&mut self) { 52 | // .. 53 | } 54 | 55 | /// Returns `true` if there's a transfer in progress 56 | /// 57 | /// NOTE this performs a volatile read 58 | pub fn in_progress() -> bool { 59 | // .. 60 | false 61 | } 62 | } 63 | 64 | /// A singleton that represents serial port #1 65 | pub struct Serial1 { 66 | // .. 67 | } 68 | 69 | impl Serial1 { 70 | /// Reads out a single byte 71 | /// 72 | /// NOTE: blocks if no byte is available to be read 73 | pub fn read(&mut self) -> Result { 74 | // .. 75 | Ok(0) 76 | } 77 | 78 | /// Sends out a single byte 79 | /// 80 | /// NOTE: blocks if the output FIFO buffer is full 81 | pub fn write(&mut self, byte: u8) -> Result<(), Error> { 82 | // .. 83 | Ok(()) 84 | } 85 | } 86 | 87 | pub enum Error {} 88 | -------------------------------------------------------------------------------- /ci/dma/examples/two.rs: -------------------------------------------------------------------------------- 1 | //! `mem::forget` 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 6 | 7 | impl Serial1 { 8 | /// Receives data into the given `buffer` until it's filled 9 | /// 10 | /// Returns a value that represents the in-progress DMA transfer 11 | pub fn read_exact(&mut self, buffer: &'static mut [u8]) -> Transfer<&'static mut [u8]> { 12 | // .. same as before .. 13 | self.dma.set_source_address(USART1_RX, false); 14 | self.dma 15 | .set_destination_address(buffer.as_mut_ptr() as usize, true); 16 | self.dma.set_transfer_length(buffer.len()); 17 | 18 | self.dma.start(); 19 | 20 | Transfer { buffer } 21 | } 22 | 23 | /// Sends out the given `buffer` 24 | /// 25 | /// Returns a value that represents the in-progress DMA transfer 26 | pub fn write_all(mut self, buffer: &'static [u8]) -> Transfer<&'static [u8]> { 27 | // .. same as before .. 28 | self.dma.set_destination_address(USART1_TX, false); 29 | self.dma.set_source_address(buffer.as_ptr() as usize, true); 30 | self.dma.set_transfer_length(buffer.len()); 31 | 32 | self.dma.start(); 33 | 34 | Transfer { buffer } 35 | } 36 | } 37 | 38 | use core::mem; 39 | 40 | #[allow(dead_code)] 41 | fn sound(mut serial: Serial1, buf: &'static mut [u8; 16]) { 42 | // NOTE `buf` is moved into `foo` 43 | foo(&mut serial, buf); 44 | bar(); 45 | } 46 | 47 | #[inline(never)] 48 | fn foo(serial: &mut Serial1, buf: &'static mut [u8]) { 49 | // start a DMA transfer and forget the returned `Transfer` value 50 | mem::forget(serial.read_exact(buf)); 51 | } 52 | 53 | #[allow(unused_mut, unused_variables)] 54 | #[inline(never)] 55 | fn bar() { 56 | // stack variables 57 | let mut x = 0; 58 | let mut y = 0; 59 | 60 | // use `x` and `y` 61 | } 62 | 63 | // UNCHANGED 64 | fn main() {} 65 | 66 | /// A singleton that represents serial port #1 67 | pub struct Serial1 { 68 | dma: Dma1Channel1, 69 | // .. 70 | } 71 | 72 | /// A DMA transfer 73 | pub struct Transfer { 74 | buffer: B, 75 | } 76 | 77 | impl Transfer { 78 | /// Returns `true` if the DMA transfer has finished 79 | pub fn is_done(&self) -> bool { 80 | !Dma1Channel1::in_progress() 81 | } 82 | 83 | /// Blocks until the transfer is done and returns the buffer 84 | pub fn wait(self) -> B { 85 | // Busy wait until the transfer is done 86 | while !self.is_done() {} 87 | 88 | self.buffer 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ci/exceptions/rt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | use core::ptr; 5 | 6 | #[unsafe(no_mangle)] 7 | #[allow(static_mut_refs)] 8 | pub unsafe extern "C" fn Reset() -> ! { 9 | // NEW! 10 | // Initialize RAM 11 | unsafe extern "C" { 12 | static mut _sbss: u8; 13 | static mut _ebss: u8; 14 | 15 | static mut _sdata: u8; 16 | static mut _edata: u8; 17 | static _sidata: u8; 18 | } 19 | 20 | let count = unsafe { &_ebss as *const u8 as usize - &_sbss as *const u8 as usize }; 21 | unsafe { ptr::write_bytes(&mut _sbss as *mut u8, 0, count) }; 22 | 23 | let count = unsafe { &_edata as *const u8 as usize - &_sdata as *const u8 as usize }; 24 | unsafe { ptr::copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count) }; 25 | 26 | // Call user entry point 27 | unsafe extern "Rust" { 28 | safe fn main() -> !; 29 | } 30 | 31 | main() 32 | } 33 | 34 | // The reset vector, a pointer into the reset handler 35 | #[unsafe(link_section = ".vector_table.reset_vector")] 36 | #[unsafe(no_mangle)] 37 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 38 | 39 | #[panic_handler] 40 | fn panic(_panic: &PanicInfo<'_>) -> ! { 41 | loop {} 42 | } 43 | 44 | #[macro_export] 45 | macro_rules! entry { 46 | ($path:path) => { 47 | #[unsafe(export_name = "main")] 48 | pub unsafe fn __main() -> ! { 49 | // type check the given path 50 | let f: fn() -> ! = $path; 51 | 52 | f() 53 | } 54 | }; 55 | } 56 | 57 | pub union Vector { 58 | reserved: u32, 59 | handler: unsafe extern "C" fn(), 60 | } 61 | 62 | unsafe extern "C" { 63 | fn NMI(); 64 | fn HardFault(); 65 | fn MemManage(); 66 | fn BusFault(); 67 | fn UsageFault(); 68 | fn SVCall(); 69 | fn PendSV(); 70 | fn SysTick(); 71 | } 72 | 73 | #[unsafe(link_section = ".vector_table.exceptions")] 74 | #[unsafe(no_mangle)] 75 | pub static EXCEPTIONS: [Vector; 14] = [ 76 | Vector { handler: NMI }, 77 | Vector { handler: HardFault }, 78 | Vector { handler: MemManage }, 79 | Vector { handler: BusFault }, 80 | Vector { 81 | handler: UsageFault, 82 | }, 83 | Vector { reserved: 0 }, 84 | Vector { reserved: 0 }, 85 | Vector { reserved: 0 }, 86 | Vector { reserved: 0 }, 87 | Vector { handler: SVCall }, 88 | Vector { reserved: 0 }, 89 | Vector { reserved: 0 }, 90 | Vector { handler: PendSV }, 91 | Vector { handler: SysTick }, 92 | ]; 93 | 94 | #[unsafe(no_mangle)] 95 | pub extern "C" fn DefaultExceptionHandler() { 96 | loop {} 97 | } 98 | -------------------------------------------------------------------------------- /ci/asm/rt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | // use core::ptr; 5 | 6 | #[unsafe(no_mangle)] 7 | pub unsafe extern "C" fn Reset() -> ! { 8 | // Omitted to simplify the `objdump` output 9 | // Initialize RAM 10 | unsafe extern "C" { 11 | // static mut _sbss: u8; 12 | // static mut _ebss: u8; 13 | 14 | // static mut _sdata: u8; 15 | // static mut _edata: u8; 16 | // static _sidata: u8; 17 | } 18 | 19 | // let count = &_ebss as *const u8 as usize - &_sbss as *const u8 as usize; 20 | // ptr::write_bytes(&mut _sbss as *mut u8, 0, count); 21 | 22 | // let count = &_edata as *const u8 as usize - &_sdata as *const u8 as usize; 23 | // ptr::copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count); 24 | 25 | // Call user entry point 26 | unsafe extern "Rust" { 27 | safe fn main() -> !; 28 | } 29 | 30 | main() 31 | } 32 | 33 | // The reset vector, a pointer into the reset handler 34 | #[unsafe(link_section = ".vector_table.reset_vector")] 35 | #[unsafe(no_mangle)] 36 | pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; 37 | 38 | #[panic_handler] 39 | fn panic(_panic: &PanicInfo<'_>) -> ! { 40 | loop {} 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! entry { 45 | ($path:path) => { 46 | #[unsafe(export_name = "main")] 47 | pub unsafe fn __main() -> ! { 48 | // type check the given path 49 | let f: fn() -> ! = $path; 50 | 51 | f() 52 | } 53 | }; 54 | } 55 | 56 | pub union Vector { 57 | reserved: u32, 58 | handler: unsafe extern "C" fn(), 59 | } 60 | 61 | unsafe extern "C" { 62 | fn NMI(); 63 | fn HardFaultTrampoline(); // <- CHANGED! 64 | fn MemManage(); 65 | fn BusFault(); 66 | fn UsageFault(); 67 | fn SVCall(); 68 | fn PendSV(); 69 | fn SysTick(); 70 | } 71 | 72 | #[unsafe(link_section = ".vector_table.exceptions")] 73 | #[unsafe(no_mangle)] 74 | pub static EXCEPTIONS: [Vector; 14] = [ 75 | Vector { handler: NMI }, 76 | Vector { handler: HardFaultTrampoline }, // <- CHANGED! 77 | Vector { handler: MemManage }, 78 | Vector { handler: BusFault }, 79 | Vector { handler: UsageFault }, 80 | Vector { reserved: 0 }, 81 | Vector { reserved: 0 }, 82 | Vector { reserved: 0 }, 83 | Vector { reserved: 0 }, 84 | Vector { handler: SVCall }, 85 | Vector { reserved: 0 }, 86 | Vector { reserved: 0 }, 87 | Vector { handler: PendSV }, 88 | Vector { handler: SysTick }, 89 | ]; 90 | 91 | #[unsafe(no_mangle)] 92 | pub extern "C" fn DefaultExceptionHandler() { 93 | loop {} 94 | } 95 | -------------------------------------------------------------------------------- /ci/dma/examples/seven.rs: -------------------------------------------------------------------------------- 1 | //! `'static` bound 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use core::{ 6 | marker::Unpin, 7 | ops::{Deref, DerefMut}, 8 | pin::Pin, 9 | sync::atomic::{self, Ordering}, 10 | }; 11 | 12 | use as_slice::{AsMutSlice, AsSlice}; 13 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 14 | 15 | impl Serial1 { 16 | /// Receives data into the given `buffer` until it's filled 17 | /// 18 | /// Returns a value that represents the in-progress DMA transfer 19 | pub fn read_exact(mut self, mut buffer: Pin) -> Transfer 20 | where 21 | // NOTE: added 'static bound 22 | B: DerefMut + 'static, 23 | B::Target: AsMutSlice + Unpin, 24 | { 25 | // .. same as before .. 26 | let slice = buffer.as_mut_slice(); 27 | let (ptr, len) = (slice.as_mut_ptr(), slice.len()); 28 | 29 | self.dma.set_source_address(USART1_RX, false); 30 | self.dma.set_destination_address(ptr as usize, true); 31 | self.dma.set_transfer_length(len); 32 | 33 | atomic::compiler_fence(Ordering::Release); 34 | self.dma.start(); 35 | 36 | Transfer { 37 | buffer, 38 | serial: self, 39 | } 40 | } 41 | 42 | /// Sends out the given `buffer` 43 | /// 44 | /// Returns a value that represents the in-progress DMA transfer 45 | pub fn write_all(mut self, buffer: Pin) -> Transfer 46 | where 47 | // NOTE: added 'static bound 48 | B: Deref + 'static, 49 | B::Target: AsSlice, 50 | { 51 | // .. same as before .. 52 | let slice = buffer.as_slice(); 53 | let (ptr, len) = (slice.as_ptr(), slice.len()); 54 | 55 | self.dma.set_destination_address(USART1_TX, false); 56 | self.dma.set_source_address(ptr as usize, true); 57 | self.dma.set_transfer_length(len); 58 | 59 | atomic::compiler_fence(Ordering::Release); 60 | self.dma.start(); 61 | 62 | Transfer { 63 | buffer, 64 | serial: self, 65 | } 66 | } 67 | } 68 | 69 | // UNCHANGED 70 | 71 | fn main() {} 72 | 73 | /// A singleton that represents serial port #1 74 | pub struct Serial1 { 75 | dma: Dma1Channel1, 76 | // .. 77 | } 78 | 79 | /// A DMA transfer 80 | pub struct Transfer { 81 | buffer: Pin, 82 | serial: Serial1, 83 | } 84 | 85 | impl Transfer { 86 | /// Returns `true` if the DMA transfer has finished 87 | pub fn is_done(&self) -> bool { 88 | !Dma1Channel1::in_progress() 89 | } 90 | 91 | /// Blocks until the transfer is done and returns the buffer 92 | pub fn wait(self) -> (Pin, Serial1) { 93 | while !self.is_done() {} 94 | 95 | atomic::compiler_fence(Ordering::Acquire); 96 | 97 | (self.buffer, self.serial) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ci/dma/examples/one.rs: -------------------------------------------------------------------------------- 1 | //! A first stab 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 6 | 7 | /// A singleton that represents serial port #1 8 | pub struct Serial1 { 9 | // NOTE: we extend this struct by adding the DMA channel singleton 10 | dma: Dma1Channel1, 11 | // .. 12 | } 13 | 14 | impl Serial1 { 15 | /// Sends out the given `buffer` 16 | /// 17 | /// Returns a value that represents the in-progress DMA transfer 18 | pub fn write_all<'a>(mut self, buffer: &'a [u8]) -> Transfer<&'a [u8]> { 19 | self.dma.set_destination_address(USART1_TX, false); 20 | self.dma.set_source_address(buffer.as_ptr() as usize, true); 21 | self.dma.set_transfer_length(buffer.len()); 22 | 23 | self.dma.start(); 24 | 25 | Transfer { buffer } 26 | } 27 | } 28 | 29 | /// A DMA transfer 30 | pub struct Transfer { 31 | buffer: B, 32 | } 33 | 34 | impl Transfer { 35 | /// Returns `true` if the DMA transfer has finished 36 | pub fn is_done(&self) -> bool { 37 | !Dma1Channel1::in_progress() 38 | } 39 | 40 | /// Blocks until the transfer is done and returns the buffer 41 | pub fn wait(self) -> B { 42 | // Busy wait until the transfer is done 43 | while !self.is_done() {} 44 | 45 | self.buffer 46 | } 47 | } 48 | 49 | impl Serial1 { 50 | /// Receives data into the given `buffer` until it's filled 51 | /// 52 | /// Returns a value that represents the in-progress DMA transfer 53 | pub fn read_exact<'a>(&mut self, buffer: &'a mut [u8]) -> Transfer<&'a mut [u8]> { 54 | self.dma.set_source_address(USART1_RX, false); 55 | self.dma 56 | .set_destination_address(buffer.as_mut_ptr() as usize, true); 57 | self.dma.set_transfer_length(buffer.len()); 58 | 59 | self.dma.start(); 60 | 61 | Transfer { buffer } 62 | } 63 | } 64 | 65 | #[allow(dead_code)] 66 | fn write(serial: Serial1) { 67 | // fire and forget 68 | serial.write_all(b"Hello, world!\n"); 69 | 70 | // do other stuff 71 | } 72 | 73 | #[allow(dead_code)] 74 | fn read(mut serial: Serial1) { 75 | let mut buf = [0; 16]; 76 | let t = serial.read_exact(&mut buf); 77 | 78 | // do other stuff 79 | 80 | t.wait(); 81 | 82 | match buf.split(|b| *b == b'\n').next() { 83 | Some(b"some-command") => { /* do something */ } 84 | _ => { /* do something else */ } 85 | } 86 | } 87 | 88 | use core::mem; 89 | 90 | #[allow(dead_code)] 91 | fn unsound(mut serial: Serial1) { 92 | start(&mut serial); 93 | bar(); 94 | } 95 | 96 | #[inline(never)] 97 | fn start(serial: &mut Serial1) { 98 | let mut buf = [0; 16]; 99 | 100 | // start a DMA transfer and forget the returned `Transfer` value 101 | mem::forget(serial.read_exact(&mut buf)); 102 | } 103 | 104 | #[allow(unused_variables, unused_mut)] 105 | #[inline(never)] 106 | fn bar() { 107 | // stack variables 108 | let mut x = 0; 109 | let mut y = 0; 110 | 111 | // use `x` and `y` 112 | } 113 | 114 | fn main() {} 115 | -------------------------------------------------------------------------------- /ci/dma/examples/four.rs: -------------------------------------------------------------------------------- 1 | //! Compiler (mis)optimizations 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use core::sync::atomic::{self, Ordering}; 6 | 7 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 8 | 9 | impl Serial1 { 10 | /// Receives data into the given `buffer` until it's filled 11 | /// 12 | /// Returns a value that represents the in-progress DMA transfer 13 | pub fn read_exact(mut self, buffer: &'static mut [u8]) -> Transfer<&'static mut [u8]> { 14 | self.dma.set_source_address(USART1_RX, false); 15 | self.dma 16 | .set_destination_address(buffer.as_mut_ptr() as usize, true); 17 | self.dma.set_transfer_length(buffer.len()); 18 | 19 | // NOTE: added 20 | atomic::compiler_fence(Ordering::Release); 21 | 22 | // NOTE: this is a volatile *write* 23 | self.dma.start(); 24 | 25 | Transfer { 26 | buffer, 27 | serial: self, 28 | } 29 | } 30 | 31 | /// Sends out the given `buffer` 32 | /// 33 | /// Returns a value that represents the in-progress DMA transfer 34 | pub fn write_all(mut self, buffer: &'static [u8]) -> Transfer<&'static [u8]> { 35 | self.dma.set_destination_address(USART1_TX, false); 36 | self.dma.set_source_address(buffer.as_ptr() as usize, true); 37 | self.dma.set_transfer_length(buffer.len()); 38 | 39 | // NOTE: added 40 | atomic::compiler_fence(Ordering::Release); 41 | 42 | // NOTE: this is a volatile *write* 43 | self.dma.start(); 44 | 45 | Transfer { 46 | buffer, 47 | serial: self, 48 | } 49 | } 50 | } 51 | 52 | impl Transfer { 53 | /// Blocks until the transfer is done and returns the buffer 54 | pub fn wait(self) -> (B, Serial1) { 55 | // NOTE: this is a volatile *read* 56 | while !self.is_done() {} 57 | 58 | // NOTE: added 59 | atomic::compiler_fence(Ordering::Acquire); 60 | 61 | (self.buffer, self.serial) 62 | } 63 | 64 | // .. 65 | } 66 | 67 | #[allow(dead_code, unused_variables)] 68 | fn reorder(serial: Serial1, buf: &'static mut [u8], x: &mut u32) { 69 | // zero the buffer (for no particular reason) 70 | buf.iter_mut().for_each(|byte| *byte = 0); 71 | 72 | *x += 1; 73 | 74 | let t = serial.read_exact(buf); // compiler_fence(Ordering::Release) ▲ 75 | 76 | // NOTE: the processor can't access `buf` between the fences 77 | // ... do other stuff .. 78 | *x += 2; 79 | 80 | let (buf, serial) = t.wait(); // compiler_fence(Ordering::Acquire) ▼ 81 | 82 | *x += 3; 83 | 84 | buf.reverse(); 85 | 86 | // .. do stuff with `buf` .. 87 | } 88 | 89 | // UNCHANGED 90 | 91 | fn main() {} 92 | 93 | /// A DMA transfer 94 | pub struct Transfer { 95 | buffer: B, 96 | serial: Serial1, 97 | } 98 | 99 | /// A singleton that represents serial port #1 100 | pub struct Serial1 { 101 | dma: Dma1Channel1, 102 | // .. 103 | } 104 | 105 | impl Transfer { 106 | /// Returns `true` if the DMA transfer has finished 107 | pub fn is_done(&self) -> bool { 108 | !Dma1Channel1::in_progress() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ci/dma/examples/three.rs: -------------------------------------------------------------------------------- 1 | //! Overlapping use 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 6 | 7 | /// A DMA transfer 8 | pub struct Transfer { 9 | buffer: B, 10 | // NOTE: added 11 | serial: Serial1, 12 | } 13 | 14 | impl Transfer { 15 | /// Blocks until the transfer is done and returns the buffer 16 | // NOTE: the return value has changed 17 | pub fn wait(self) -> (B, Serial1) { 18 | // Busy wait until the transfer is done 19 | while !self.is_done() {} 20 | 21 | (self.buffer, self.serial) 22 | } 23 | 24 | // .. 25 | } 26 | 27 | impl Serial1 { 28 | /// Receives data into the given `buffer` until it's filled 29 | /// 30 | /// Returns a value that represents the in-progress DMA transfer 31 | // NOTE we now take `self` by value 32 | pub fn read_exact(mut self, buffer: &'static mut [u8]) -> Transfer<&'static mut [u8]> { 33 | self.dma.set_source_address(USART1_RX, false); 34 | self.dma 35 | .set_destination_address(buffer.as_mut_ptr() as usize, true); 36 | self.dma.set_transfer_length(buffer.len()); 37 | 38 | self.dma.start(); 39 | 40 | // .. same as before .. 41 | 42 | Transfer { 43 | buffer, 44 | // NOTE: added 45 | serial: self, 46 | } 47 | } 48 | 49 | /// Sends out the given `buffer` 50 | /// 51 | /// Returns a value that represents the in-progress DMA transfer 52 | // NOTE we now take `self` by value 53 | pub fn write_all(mut self, buffer: &'static [u8]) -> Transfer<&'static [u8]> { 54 | self.dma.set_destination_address(USART1_TX, false); 55 | self.dma.set_source_address(buffer.as_ptr() as usize, true); 56 | self.dma.set_transfer_length(buffer.len()); 57 | 58 | self.dma.start(); 59 | 60 | // .. same as before .. 61 | 62 | Transfer { 63 | buffer, 64 | // NOTE: added 65 | serial: self, 66 | } 67 | } 68 | } 69 | 70 | #[allow(dead_code, unused_variables)] 71 | fn read(serial: Serial1, buf: &'static mut [u8; 16]) { 72 | let t = serial.read_exact(buf); 73 | 74 | // let byte = serial.read(); //~ ERROR: `serial` has been moved 75 | 76 | // .. do stuff .. 77 | 78 | let (serial, buf) = t.wait(); 79 | 80 | // .. do more stuff .. 81 | } 82 | 83 | #[allow(dead_code, unused_variables)] 84 | fn reorder(serial: Serial1, buf: &'static mut [u8]) { 85 | // zero the buffer (for no particular reason) 86 | buf.iter_mut().for_each(|byte| *byte = 0); 87 | 88 | let t = serial.read_exact(buf); 89 | 90 | // ... do other stuff .. 91 | 92 | let (buf, serial) = t.wait(); 93 | 94 | buf.reverse(); 95 | 96 | // .. do stuff with `buf` .. 97 | } 98 | 99 | // UNCHANGED 100 | 101 | fn main() {} 102 | 103 | /// A singleton that represents serial port #1 104 | pub struct Serial1 { 105 | dma: Dma1Channel1, 106 | // .. 107 | } 108 | 109 | impl Transfer { 110 | /// Returns `true` if the DMA transfer has finished 111 | pub fn is_done(&self) -> bool { 112 | !Dma1Channel1::in_progress() 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: #Enables manual run of this workflow 5 | push: 6 | branches: [master, staging, trying] 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | 17 | - name: Install Python dependencies 18 | run: | 19 | pip3 install --user python-dateutil linkchecker 20 | 21 | - name: Cache installed binaries 22 | uses: actions/cache@v4 23 | id: cache-bin 24 | with: 25 | path: ~/cache-bin 26 | key: cache-bin 27 | 28 | - name: Install mdbook 29 | if: steps.cache-bin.outputs.cache-hit != 'true' 30 | uses: actions-rs/install@v0.1 31 | with: 32 | crate: mdbook 33 | version: latest 34 | 35 | 36 | - name: Install cargo-binutils 37 | if: steps.cache-bin.outputs.cache-hit != 'true' 38 | uses: actions-rs/install@v0.1 39 | with: 40 | crate: cargo-binutils 41 | version: latest 42 | 43 | #Moved until after installing mdbook and cargo-binutils because otherwise installing them fails 44 | #(note all GitHub runners come with the latest stable version of Rust pre-installed, and it is that version we want to install these). 45 | - name: Install Rust 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | profile: minimal 49 | toolchain: 1.89.0 50 | override: true 51 | components: rustfmt, clippy, llvm-tools 52 | target: thumbv7m-none-eabi 53 | 54 | - name: Install arm-none-eabi-gcc and qemu 55 | if: steps.cache-bin.outputs.cache-hit != 'true' 56 | run: | 57 | mkdir -p ~/cache-bin/arm_gcc 58 | curl -L https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.07/gcc-arm-none-eabi-10.3-2021.07-x86_64-linux.tar.bz2 \ 59 | | tar --strip-components=1 -C ~/cache-bin/arm_gcc -xj 60 | 61 | curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 \ 62 | > ~/cache-bin/qemu-system-arm 63 | chmod a+x ~/cache-bin/qemu-system-arm 64 | 65 | - name: Copy installed binaries to cache directory 66 | if: steps.cache-bin.outputs.cache-hit != 'true' 67 | run: | 68 | cp ~/.cargo/bin/* ~/cache-bin 69 | 70 | - name: Put new bin directory into path 71 | run: | 72 | echo "$HOME/cache-bin" >> $GITHUB_PATH 73 | echo "$HOME/cache-bin/arm_gcc/bin" >> $GITHUB_PATH 74 | 75 | - name: Test 76 | run: bash ci/script.sh 77 | 78 | deploy: 79 | runs-on: ubuntu-latest 80 | 81 | needs: [build] 82 | 83 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 84 | 85 | steps: 86 | - uses: actions/checkout@v2 87 | 88 | - name: Cache installed binaries 89 | uses: actions/cache@v4 90 | id: cache-bin 91 | with: 92 | path: ~/cache-bin 93 | key: cache-bin 94 | 95 | - name: Put new bin directory into path 96 | run: echo "$HOME/cache-bin" >> $GITHUB_PATH 97 | 98 | - name: Build the book 99 | run: mdbook build 100 | 101 | - name: Deploy book 102 | uses: peaceiris/actions-gh-pages@v3 103 | with: 104 | github_token: ${{ secrets.GITHUB_TOKEN }} 105 | publish_dir: book 106 | force_orphan: true 107 | -------------------------------------------------------------------------------- /ci/dma/examples/five.rs: -------------------------------------------------------------------------------- 1 | //! Generic buffer 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use core::sync::atomic::{self, Ordering}; 6 | 7 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 8 | 9 | // as-slice = "0.1.0" 10 | use as_slice::{AsMutSlice, AsSlice}; 11 | 12 | impl Serial1 { 13 | /// Receives data into the given `buffer` until it's filled 14 | /// 15 | /// Returns a value that represents the in-progress DMA transfer 16 | pub fn read_exact(mut self, mut buffer: B) -> Transfer 17 | where 18 | B: AsMutSlice, 19 | { 20 | // NOTE: added 21 | let slice = buffer.as_mut_slice(); 22 | let (ptr, len) = (slice.as_mut_ptr(), slice.len()); 23 | 24 | self.dma.set_source_address(USART1_RX, false); 25 | 26 | // NOTE: tweaked 27 | self.dma.set_destination_address(ptr as usize, true); 28 | self.dma.set_transfer_length(len); 29 | 30 | atomic::compiler_fence(Ordering::Release); 31 | self.dma.start(); 32 | 33 | Transfer { 34 | buffer, 35 | serial: self, 36 | } 37 | } 38 | 39 | /// Sends out the given `buffer` 40 | /// 41 | /// Returns a value that represents the in-progress DMA transfer 42 | fn write_all(mut self, buffer: B) -> Transfer 43 | where 44 | B: AsSlice, 45 | { 46 | // NOTE: added 47 | let slice = buffer.as_slice(); 48 | let (ptr, len) = (slice.as_ptr(), slice.len()); 49 | 50 | self.dma.set_destination_address(USART1_TX, false); 51 | 52 | // NOTE: tweaked 53 | self.dma.set_source_address(ptr as usize, true); 54 | self.dma.set_transfer_length(len); 55 | 56 | atomic::compiler_fence(Ordering::Release); 57 | self.dma.start(); 58 | 59 | Transfer { 60 | buffer, 61 | serial: self, 62 | } 63 | } 64 | } 65 | 66 | #[allow(dead_code, unused_variables)] 67 | fn reuse(serial: Serial1, msg: &'static mut [u8]) { 68 | // send a message 69 | let t1 = serial.write_all(msg); 70 | 71 | // .. 72 | 73 | let (msg, serial) = t1.wait(); // `msg` is now `&'static [u8]` 74 | 75 | msg.reverse(); 76 | 77 | // now send it in reverse 78 | let t2 = serial.write_all(msg); 79 | 80 | // .. 81 | 82 | let (buf, serial) = t2.wait(); 83 | 84 | // .. 85 | } 86 | 87 | #[allow(dead_code, unused_variables)] 88 | fn invalidate(serial: Serial1) { 89 | let t = start(serial); 90 | 91 | bar(); 92 | 93 | let (buf, serial) = t.wait(); 94 | } 95 | 96 | #[inline(never)] 97 | fn start(serial: Serial1) -> Transfer<[u8; 16]> { 98 | // array allocated in this frame 99 | let buffer = [0; 16]; 100 | 101 | serial.read_exact(buffer) 102 | } 103 | 104 | #[allow(unused_mut, unused_variables)] 105 | #[inline(never)] 106 | fn bar() { 107 | // stack variables 108 | let mut x = 0; 109 | let mut y = 0; 110 | 111 | // use `x` and `y` 112 | } 113 | 114 | // UNCHANGED 115 | 116 | fn main() {} 117 | 118 | /// A DMA transfer 119 | pub struct Transfer { 120 | buffer: B, 121 | serial: Serial1, 122 | } 123 | 124 | /// A singleton that represents serial port #1 125 | pub struct Serial1 { 126 | dma: Dma1Channel1, 127 | // .. 128 | } 129 | 130 | impl Transfer { 131 | /// Returns `true` if the DMA transfer has finished 132 | pub fn is_done(&self) -> bool { 133 | !Dma1Channel1::in_progress() 134 | } 135 | 136 | /// Blocks until the transfer is done and returns the buffer 137 | pub fn wait(self) -> (B, Serial1) { 138 | while !self.is_done() {} 139 | 140 | atomic::compiler_fence(Ordering::Acquire); 141 | 142 | (self.buffer, self.serial) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /ci/dma/examples/six.rs: -------------------------------------------------------------------------------- 1 | //! Immovable buffers 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use core::{ 6 | marker::Unpin, 7 | mem, 8 | ops::{Deref, DerefMut}, 9 | pin::Pin, 10 | sync::atomic::{self, Ordering}, 11 | }; 12 | 13 | use as_slice::{AsMutSlice, AsSlice}; 14 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 15 | 16 | /// A DMA transfer 17 | pub struct Transfer { 18 | // NOTE: changed 19 | buffer: Pin, 20 | serial: Serial1, 21 | } 22 | 23 | impl Serial1 { 24 | /// Receives data into the given `buffer` until it's filled 25 | /// 26 | /// Returns a value that represents the in-progress DMA transfer 27 | pub fn read_exact(mut self, mut buffer: Pin) -> Transfer 28 | where 29 | // NOTE: bounds changed 30 | B: DerefMut, 31 | B::Target: AsMutSlice + Unpin, 32 | { 33 | // .. same as before .. 34 | let slice = buffer.as_mut_slice(); 35 | let (ptr, len) = (slice.as_mut_ptr(), slice.len()); 36 | 37 | self.dma.set_source_address(USART1_RX, false); 38 | self.dma.set_destination_address(ptr as usize, true); 39 | self.dma.set_transfer_length(len); 40 | 41 | atomic::compiler_fence(Ordering::Release); 42 | self.dma.start(); 43 | 44 | Transfer { 45 | buffer, 46 | serial: self, 47 | } 48 | } 49 | 50 | /// Sends out the given `buffer` 51 | /// 52 | /// Returns a value that represents the in-progress DMA transfer 53 | pub fn write_all(mut self, buffer: Pin) -> Transfer 54 | where 55 | // NOTE: bounds changed 56 | B: Deref, 57 | B::Target: AsSlice, 58 | { 59 | // .. same as before .. 60 | let slice = buffer.as_slice(); 61 | let (ptr, len) = (slice.as_ptr(), slice.len()); 62 | 63 | self.dma.set_destination_address(USART1_TX, false); 64 | self.dma.set_source_address(ptr as usize, true); 65 | self.dma.set_transfer_length(len); 66 | 67 | atomic::compiler_fence(Ordering::Release); 68 | self.dma.start(); 69 | 70 | Transfer { 71 | buffer, 72 | serial: self, 73 | } 74 | } 75 | } 76 | 77 | #[allow(dead_code, unused_variables)] 78 | fn static_mut(serial: Serial1, buf: &'static mut [u8]) { 79 | let buf = Pin::new(buf); 80 | 81 | let t = serial.read_exact(buf); 82 | 83 | // .. 84 | 85 | let (buf, serial) = t.wait(); 86 | 87 | // .. 88 | } 89 | 90 | #[allow(dead_code, unused_variables)] 91 | fn boxed(serial: Serial1, buf: Box<[u8]>) { 92 | let buf = Pin::new(buf); 93 | 94 | let t = serial.read_exact(buf); 95 | 96 | // .. 97 | 98 | let (buf, serial) = t.wait(); 99 | 100 | // .. 101 | } 102 | 103 | #[allow(dead_code)] 104 | fn unsound(serial: Serial1) { 105 | start(serial); 106 | 107 | bar(); 108 | } 109 | 110 | // pin-utils = "0.1.0-alpha.4" 111 | use pin_utils::pin_mut; 112 | 113 | #[inline(never)] 114 | fn start(serial: Serial1) { 115 | let buffer = [0; 16]; 116 | 117 | // pin the `buffer` to this stack frame 118 | // `buffer` now has type `Pin<&mut [u8; 16]>` 119 | pin_mut!(buffer); 120 | 121 | mem::forget(serial.read_exact(buffer)); 122 | } 123 | 124 | #[allow(unused_mut, unused_variables)] 125 | #[inline(never)] 126 | fn bar() { 127 | // stack variables 128 | let mut x = 0; 129 | let mut y = 0; 130 | 131 | // use `x` and `y` 132 | } 133 | 134 | // UNCHANGED 135 | 136 | fn main() {} 137 | 138 | /// A singleton that represents serial port #1 139 | pub struct Serial1 { 140 | dma: Dma1Channel1, 141 | // .. 142 | } 143 | 144 | impl Transfer { 145 | /// Returns `true` if the DMA transfer has finished 146 | pub fn is_done(&self) -> bool { 147 | !Dma1Channel1::in_progress() 148 | } 149 | 150 | /// Blocks until the transfer is done and returns the buffer 151 | pub fn wait(self) -> (Pin, Serial1) { 152 | while !self.is_done() {} 153 | 154 | atomic::compiler_fence(Ordering::Acquire); 155 | 156 | (self.buffer, self.serial) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ci/dma/examples/eight.rs: -------------------------------------------------------------------------------- 1 | //! Destructors 2 | 3 | #![deny(missing_docs, warnings)] 4 | 5 | use core::{ 6 | hint, 7 | marker::Unpin, 8 | mem, 9 | ops::{Deref, DerefMut}, 10 | pin::Pin, 11 | ptr, 12 | sync::atomic::{self, Ordering}, 13 | }; 14 | 15 | use as_slice::{AsMutSlice, AsSlice}; 16 | use shared::{Dma1Channel1, USART1_RX, USART1_TX}; 17 | 18 | /// A DMA transfer 19 | pub struct Transfer { 20 | // NOTE: always `Some` variant 21 | inner: Option>, 22 | } 23 | 24 | // NOTE: previously named `Transfer` 25 | struct Inner { 26 | buffer: Pin, 27 | serial: Serial1, 28 | } 29 | 30 | impl Transfer { 31 | /// Blocks until the transfer is done and returns the buffer 32 | pub fn wait(mut self) -> (Pin, Serial1) { 33 | while !self.is_done() {} 34 | 35 | atomic::compiler_fence(Ordering::Acquire); 36 | 37 | let inner = self 38 | .inner 39 | .take() 40 | .unwrap_or_else(|| unsafe { hint::unreachable_unchecked() }); 41 | (inner.buffer, inner.serial) 42 | } 43 | } 44 | 45 | impl Drop for Transfer { 46 | fn drop(&mut self) { 47 | if let Some(inner) = self.inner.as_mut() { 48 | // NOTE: this is a volatile write 49 | inner.serial.dma.stop(); 50 | 51 | // we need a read here to make the Acquire fence effective 52 | // we do *not* need this if `dma.stop` does a RMW operation 53 | unsafe { 54 | ptr::read_volatile(&0); 55 | } 56 | 57 | // we need a fence here for the same reason we need one in `Transfer.wait` 58 | atomic::compiler_fence(Ordering::Acquire); 59 | } 60 | } 61 | } 62 | 63 | impl Serial1 { 64 | /// Receives data into the given `buffer` until it's filled 65 | /// 66 | /// Returns a value that represents the in-progress DMA transfer 67 | pub fn read_exact(mut self, mut buffer: Pin) -> Transfer 68 | where 69 | B: DerefMut + 'static, 70 | B::Target: AsMutSlice + Unpin, 71 | { 72 | // .. same as before .. 73 | let slice = buffer.as_mut_slice(); 74 | let (ptr, len) = (slice.as_mut_ptr(), slice.len()); 75 | 76 | self.dma.set_source_address(USART1_RX, false); 77 | self.dma.set_destination_address(ptr as usize, true); 78 | self.dma.set_transfer_length(len); 79 | 80 | atomic::compiler_fence(Ordering::Release); 81 | self.dma.start(); 82 | 83 | Transfer { 84 | inner: Some(Inner { 85 | buffer, 86 | serial: self, 87 | }), 88 | } 89 | } 90 | 91 | /// Sends out the given `buffer` 92 | /// 93 | /// Returns a value that represents the in-progress DMA transfer 94 | pub fn write_all(mut self, buffer: Pin) -> Transfer 95 | where 96 | B: Deref + 'static, 97 | B::Target: AsSlice, 98 | { 99 | // .. same as before .. 100 | let slice = buffer.as_slice(); 101 | let (ptr, len) = (slice.as_ptr(), slice.len()); 102 | 103 | self.dma.set_destination_address(USART1_TX, false); 104 | self.dma.set_source_address(ptr as usize, true); 105 | self.dma.set_transfer_length(len); 106 | 107 | atomic::compiler_fence(Ordering::Release); 108 | self.dma.start(); 109 | 110 | Transfer { 111 | inner: Some(Inner { 112 | buffer, 113 | serial: self, 114 | }), 115 | } 116 | } 117 | } 118 | 119 | #[allow(dead_code, unused_mut, unused_variables)] 120 | fn reuse(serial: Serial1) { 121 | let buf = Pin::new(Box::new([0; 16])); 122 | 123 | let t = serial.read_exact(buf); // compiler_fence(Ordering::Release) ▲ 124 | 125 | // .. 126 | 127 | // this stops the DMA transfer and frees memory 128 | mem::drop(t); // compiler_fence(Ordering::Acquire) ▼ 129 | 130 | // this likely reuses the previous memory allocation 131 | let mut buf = Box::new([0; 16]); 132 | 133 | // .. do stuff with `buf` .. 134 | } 135 | 136 | // UNCHANGED 137 | 138 | fn main() {} 139 | 140 | /// A singleton that represents serial port #1 141 | pub struct Serial1 { 142 | dma: Dma1Channel1, 143 | // .. 144 | } 145 | 146 | impl Transfer { 147 | /// Returns `true` if the DMA transfer has finished 148 | pub fn is_done(&self) -> bool { 149 | !Dma1Channel1::in_progress() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The Rust Code of Conduct 2 | 3 | ## Conduct 4 | 5 | **Contact**: [Resources team][team] 6 | 7 | * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. 8 | * On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. 9 | * Please be kind and courteous. There's no need to be mean or rude. 10 | * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. 11 | * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. 12 | * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. 13 | * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Resources team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. 14 | * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. 15 | 16 | ## Moderation 17 | 18 | These are the policies for upholding our community's standards of conduct. 19 | 20 | 1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) 21 | 2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. 22 | 3. Moderators will first respond to such remarks with a warning. 23 | 4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. 24 | 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. 25 | 6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. 26 | 7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. 27 | 8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. 28 | 29 | In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. 30 | 31 | And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. 32 | 33 | The enforcement policies listed above apply to all official embedded WG venues; including official IRC channels (#rust-embedded); GitHub repositories under rust-embedded; and all forums under rust-embedded.org (forum.rust-embedded.org). 34 | 35 | *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* 36 | 37 | [team]: https://github.com/rust-embedded/wg#the-resources-team 38 | -------------------------------------------------------------------------------- /src/preface.md: -------------------------------------------------------------------------------- 1 | # The embedonomicon 2 | 3 | The embedonomicon walks you through the process of creating a `#![no_std]` application from scratch 4 | and through the iterative process of building architecture-specific functionality for Cortex-M 5 | microcontrollers. 6 | 7 | ## Objectives 8 | 9 | By reading this book you will learn 10 | 11 | - How to build a `#![no_std]` application. This is much more complex than building a `#![no_std]` 12 | library because the target system may not be running an OS (or you could be aiming to build an 13 | OS!) and the program could be the only process running in the target (or the first one). 14 | In that case, the program may need to be customized for the target system. 15 | 16 | - Tricks to finely control the memory layout of a Rust program. You'll learn about linkers, linker 17 | scripts and about the Rust features that let you control a bit of the ABI of Rust programs. 18 | 19 | - A trick to implement default functionality that can be statically overridden (no runtime cost). 20 | 21 | ## Target audience 22 | 23 | This book mainly targets to two audiences: 24 | 25 | - People that wish to bootstrap bare metal support for an architecture that the ecosystem doesn't 26 | yet support (e.g. Cortex-R as of Rust 1.28), or for an architecture that Rust just gained support 27 | for (e.g. maybe Xtensa some time in the future). 28 | 29 | - People that are curious about the unusual implementation of *runtime* crates like [`cortex-m-rt`], 30 | [`msp430-rt`] and [`riscv-rt`]. 31 | 32 | [`cortex-m-rt`]: https://crates.io/crates/cortex-m-rt 33 | [`msp430-rt`]: https://crates.io/crates/msp430-rt 34 | [`riscv-rt`]: https://crates.io/crates/riscv-rt 35 | 36 | ## Translations 37 | 38 | This book has been translated by generous volunteers. If you would like your 39 | translation listed here, please open a PR to add it. 40 | 41 | * [Japanese](https://tomoyuki-nakabayashi.github.io/embedonomicon/) 42 | ([repository](https://github.com/tomoyuki-nakabayashi/embedonomicon)) 43 | 44 | * [Chinese](https://xxchang.github.io/embedonomicon/) 45 | ([repository](https://github.com/xxchang/embedonomicon)) 46 | 47 | ## Requirements 48 | 49 | This book is self contained. The reader doesn't need to be familiar with the 50 | Cortex-M architecture, nor is access to a Cortex-M microcontroller needed -- all 51 | the examples included in this book can be tested in QEMU. You will, however, 52 | need to install the following tools to run and inspect the examples in this 53 | book: 54 | 55 | - All the code in this book uses the 2024 edition. If you are not familiar with 56 | the 2024 features and idioms check the [`edition guide`]. 57 | 58 | - Rust 1.89 or a newer toolchain with ARM Cortex-M compilation support. 59 | 60 | - [`cargo-binutils`](https://github.com/japaric/cargo-binutils). v0.1.4 or newer. 61 | 62 | - [`cargo-edit`](https://crates.io/crates/cargo-edit). 63 | 64 | - QEMU with support for ARM emulation. The `qemu-system-arm` program must be 65 | installed on your computer. 66 | 67 | - GDB with ARM support. 68 | 69 | [`edition guide`]: https://doc.rust-lang.org/edition-guide/ 70 | 71 | ### Example setup 72 | 73 | Instructions common to all OSes 74 | 75 | ``` console 76 | $ # Rust toolchain 77 | $ # If you start from scratch, get rustup from https://rustup.rs/ 78 | $ rustup default stable 79 | 80 | $ # toolchain should be newer than this one 81 | $ rustc -V 82 | rustc 1.89.0 (29483883e 2025-08-04) 83 | 84 | $ rustup target add thumbv7m-none-eabi 85 | 86 | $ # cargo-binutils 87 | $ cargo install cargo-binutils 88 | 89 | $ rustup component add llvm-tools 90 | 91 | ``` 92 | 93 | #### macOS 94 | 95 | ``` console 96 | $ # arm-none-eabi-gdb 97 | $ # you may need to run `brew tap Caskroom/tap` first 98 | $ brew install --cask gcc-arm-embedded 99 | 100 | $ # QEMU 101 | $ brew install qemu 102 | ``` 103 | 104 | #### Ubuntu 16.04 105 | 106 | ``` console 107 | $ # arm-none-eabi-gdb 108 | $ sudo apt install gdb-arm-none-eabi 109 | 110 | $ # QEMU 111 | $ sudo apt install qemu-system-arm 112 | ``` 113 | 114 | #### Ubuntu 18.04 (or newer) or Debian 115 | 116 | ``` console 117 | $ # gdb-multiarch -- use `gdb-multiarch` when you wish to invoke gdb 118 | $ sudo apt install gdb-multiarch 119 | 120 | $ # QEMU 121 | $ sudo apt install qemu-system-arm 122 | ``` 123 | 124 | #### Windows 125 | 126 | - [arm-none-eabi-gdb](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads). 127 | The GNU Arm Embedded Toolchain includes GDB. 128 | 129 | - [QEMU](https://www.qemu.org/download/#windows) 130 | 131 | ## Installing a toolchain bundle from ARM (optional step) (tested on Ubuntu 18.04) 132 | - With the late 2018 switch from 133 | [GCC's linker to LLD](https://rust-embedded.github.io/blog/2018-08-2x-psa-cortex-m-breakage/) for Cortex-M 134 | microcontrollers, [gcc-arm-none-eabi][1] is no longer 135 | required. But for those wishing to use the toolchain 136 | anyway, install from [here][1] and follow the steps outlined below: 137 | ``` console 138 | $ tar xvjf gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2 139 | $ mv gcc-arm-none-eabi- # optional 140 | $ export PATH=${PATH}:/bin # add this line to .bashrc to make persistent 141 | ``` 142 | [1]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads 143 | -------------------------------------------------------------------------------- /src/sections-in-rust.md: -------------------------------------------------------------------------------- 1 | # Why don't we initialize `.data` and `.bss` using Rust 2 | 3 | Earlier versions of this book initialized the `.data` and `.bss` sections using Rust code. 4 | This has proven to have questionable soundness, and the recommended method of 5 | performing the initialization of these sections nowadays relies on assembly. 6 | 7 | This chapter discusses the reasons that led to the decision of various crates like 8 | [cortex-m-rt](https://crates.io/crates/cortex-m-rt) and [riscv-rt](https://crates.io/crates/riscv-rt) 9 | to migrate to performing assembly initialization of these sections. There are 10 | [a](https://github.com/rust-embedded/cortex-m-rt/issues/300) 11 | [decent](https://github.com/rust-embedded/embedonomicon/issues/69) 12 | [number](https://rust-lang.zulipchat.com/#narrow/stream/136281-t-lang.2Fwg-unsafe-code-guidelines/topic/The.20least.20incorrect.20init.20code.20.3A\)) 13 | [of](https://github.com/rust-lang/unsafe-code-guidelines/issues/259) 14 | [threads](https://github.com/rust-embedded/wg/issues/771) 15 | where the soundness of such code has been questioned. We will summarize 16 | them in this chapter. 17 | 18 | The original code used for global data initialization in Rust in this book is listed 19 | as follows: 20 | 21 | ``` rust 22 | {{#include ../ci/main/rt-unsound/src/lib.rs:1:32}} 23 | ``` 24 | 25 | Five `extern "C"` variables are declared to reference specific memory locations. 26 | Our linker script defines each symbol, so we do not need to worry about their 27 | exact placement. 28 | 29 | ## Pointer proventace 30 | 31 | To initialize the `.bss` section, we take the address of `_sbss` `u8` variable, 32 | which points to the start of the `.bss` section. Then we write an arbitrary 33 | amount of data to its location. `_sbss` is declared as an `u8` variables, and 34 | the pointer provenance rules only allow us to write an amount of data that fits 35 | within the allocation of our `_sbss` variable. Despite that, we are writing past 36 | the single byte (as far as Rust is aware, a single byte is allocated at this 37 | address) up until we hit the location of the `_ebss`. 38 | 39 | There is a separate issue in which we actually have an `_ebss` variable that is 40 | pointing one byte outside of the `.bss` section. In specific implementations, 41 | accessing this byte might not even be possible if the `.bss` section exhausted 42 | the available memory. Ideally `_ebss` needs to be declared as a ZST. And by 43 | extension, because the `.bss` section can be empty, `_sbss` should also be a 44 | ZST, because in this case `_sbss` would also fall outside of the region reserved 45 | for the `.bss`. 46 | 47 | ## Aliasing 48 | 49 | Another potential problem with the code above is aliasing. Consider our linker 50 | script. 51 | 52 | ``` text 53 | {{#include ../ci/main/rt-unsound/link.x:36:48}} 54 | ``` 55 | 56 | The following situations can occur: 57 | - `_sbss` might be located at the same address as the first variable in the `.bss` 58 | section, assuming that the section is not empty. 59 | - `_ebss` will be located at the same address as `_sdata`, and by extension, it 60 | will also be located at the same address as the first variable in the `.data` 61 | section. 62 | - If the `.bss` section is empty, both `_sbss` and `_ebss` will alias each other. 63 | - If the `.data` section is empty, both `_sdata` and `_edata` will alias each other. 64 | 65 | Rust does not allow to have more than one variable to be located at the same address 66 | (with ZSTs being a key exception). But even if it did, we are using these variables 67 | to write the whole global memory area, which effectively is mutably aliasing all 68 | global data defined in the program. 69 | 70 | ## Abstract machine initialization 71 | 72 | Another question is whether it is safe to enter any Rust code before the Rust 73 | abstract machine has been fully initialized. Can we rely on Rust not using any 74 | of the global memory while it is not yet initialized? The answer to this question 75 | is not clear (or does not seem clear to the author of the section at the time of 76 | this writing). 77 | 78 | ## More potential provenance issues 79 | 80 | A clever reader might have seen how we compute the offset between `_ebss` and `_sbss` and thought, 81 | couldn't we instad use the [`offset_from`](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset_from) 82 | method of a pointer? 83 | 84 | The problem with this approach, however, is that, as we mentioned above, both `_ebss` 85 | and `_sbss` belong to different allocations, so they do not share the same pointer 86 | provenance. This is true even if they both are aliased and happen to fall at the 87 | same address (i.e. when the `.bss` section is empty). 88 | 89 | Running Miri on this [Rust Playground Snippet](https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=3225a585752704d9c58b1842e0fc5307) 90 | shows the undefined behavior. 91 | 92 | ## Ok, but it works, doesn't it? 93 | 94 | Yes. While the code provided at the beginning of this chapter does produce the 95 | right behavior as of Rust 1.89, the problem is that **we cannot rely on this behavior 96 | being preserved in future releases**, or even in the optimizer doing something 97 | funky in the future. 98 | 99 | That is why, overall, the recommendation of this books is to **not** perform the initialization 100 | using Rust code for this purpose. 101 | -------------------------------------------------------------------------------- /src/smallest-no-std.md: -------------------------------------------------------------------------------- 1 | # The smallest `#![no_std]` program 2 | 3 | In this section we'll write the smallest `#![no_std]` program that *compiles*. 4 | 5 | ## What does `#![no_std]` mean? 6 | 7 | `#![no_std]` is a crate level attribute that indicates that the crate will link to the [`core`] 8 | crate instead of the [`std`] crate, but what does this mean for applications? 9 | 10 | [`core`]: https://doc.rust-lang.org/core/ 11 | [`std`]: https://doc.rust-lang.org/std/ 12 | 13 | The `std` crate is Rust's standard library. It contains functionality that assumes that the program 14 | will run on an operating system rather than [*directly on the metal*]. `std` also assumes that the 15 | operating system is a general purpose operating system, like the ones one would find in servers and 16 | desktops. For this reason, `std` provides a standard API over functionality one usually finds in 17 | such operating systems: Threads, files, sockets, a filesystem, processes, etc. 18 | 19 | [*directly on the metal*]: https://en.wikipedia.org/wiki/Bare_machine 20 | 21 | On the other hand, the `core` crate is a subset of the `std` crate that makes zero assumptions about 22 | the system the program will run on. As such, it provides APIs for language primitives like floats, 23 | strings and slices, as well as APIs that expose processor features like atomic operations and SIMD 24 | instructions. However it lacks APIs for anything that involves heap memory allocations and I/O. 25 | 26 | For an application, `std` does more than just providing a way to access OS abstractions. `std` also 27 | takes care of, among other things, setting up stack overflow protection, processing command line 28 | arguments and spawning the main thread before a program's `main` function is invoked. A `#![no_std]` 29 | application lacks all that standard runtime, so it must initialize its own runtime, if any is 30 | required. 31 | 32 | Because of these properties, a `#![no_std]` application can be the first and / or the only code that 33 | runs on a system. It can be many things that a standard Rust application can never be, for example: 34 | 35 | - The kernel of an OS. 36 | - Firmware. 37 | - A bootloader. 38 | 39 | ## The code 40 | 41 | With that out of the way, we can move on to the smallest `#![no_std]` program that compiles: 42 | 43 | ``` console 44 | $ cargo new --edition 2024 --bin app 45 | 46 | $ cd app 47 | ``` 48 | 49 | ``` console 50 | $ # modify main.rs so it has these contents 51 | $ cat src/main.rs 52 | ``` 53 | 54 | ``` rust 55 | {{#include ../ci/smallest-no-std/src/main.rs}} 56 | ``` 57 | 58 | This program contains some things that you won't see in standard Rust programs: 59 | 60 | The `#![no_std]` attribute which we have already extensively covered. 61 | 62 | The `#![no_main]` attribute which means that the program won't use the standard `main` function as 63 | its entry point. At the time of writing, Rust's `main` interface makes some assumptions about the 64 | environment the program executes in: For example, it assumes the existence of command line 65 | arguments, so in general, it's not appropriate for `#![no_std]` programs. 66 | 67 | The `#[panic_handler]` attribute. The function marked with this attribute defines the behavior of 68 | panics, both library level panics (`core::panic!`) and language level panics (out of bounds 69 | indexing). 70 | 71 | This program doesn't produce anything useful. In fact, it will produce an empty binary. 72 | 73 | ``` console 74 | $ # equivalent to `size target/thumbv7m-none-eabi/debug/app` 75 | $ cargo size --target thumbv7m-none-eabi --bin app 76 | ``` 77 | 78 | ``` text 79 | {{#include ../ci/smallest-no-std/app.size}} 80 | ``` 81 | 82 | Before linking, the crate contains the panicking symbol. 83 | 84 | ``` console 85 | $ cargo rustc --target thumbv7m-none-eabi -- --emit=obj 86 | 87 | $ cargo nm -- -C $(pwd)/target/thumbv7m-none-eabi/debug/deps/app-*.o | grep '[0-9]* [^N] ' 88 | ``` 89 | 90 | ``` text 91 | {{#include ../ci/smallest-no-std/app.o.nm}} 92 | ``` 93 | 94 | However, it's our starting point. In the next section, we'll build something useful. But before 95 | continuing, let's set a default build target to avoid having to pass the `--target` flag to every 96 | Cargo invocation. 97 | 98 | ``` console 99 | $ mkdir .cargo 100 | 101 | $ # modify .cargo/config.toml so it has these contents 102 | $ cat .cargo/config.toml 103 | ``` 104 | 105 | ``` toml 106 | {{#include ../ci/smallest-no-std/.cargo/config.toml}} 107 | ``` 108 | 109 | ## eh_personality 110 | 111 | If your configuration does not unconditionally abort on panic, which most targets for full operating 112 | systems don't (or if your [custom target][custom-target] does not contain 113 | `"panic-strategy": "abort"`), then you must tell Cargo to do so or add an `eh_personality` function, 114 | which requires a nightly compiler. [Here is Rust's documentation about it][more-about-lang-items], 115 | and [here is some discussion about it][til-why-eh-personality]. 116 | 117 | In your Cargo.toml, add: 118 | 119 | ``` toml 120 | [profile.dev] 121 | panic = "abort" 122 | 123 | [profile.release] 124 | panic = "abort" 125 | ``` 126 | 127 | Alternatively, declare the `eh_personality` function. A simple implementation that does not do 128 | anything special when unwinding is as follows: 129 | 130 | ``` rust 131 | #![feature(lang_items)] 132 | 133 | #[lang = "eh_personality"] 134 | extern "C" fn eh_personality() {} 135 | ``` 136 | 137 | You will receive the error `language item required, but not found: 'eh_personality'` if not 138 | included. 139 | 140 | [custom-target]: ./custom-target.md 141 | [more-about-lang-items]: 142 | https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#more-about-the-language-items 143 | [til-why-eh-personality]: 144 | https://www.reddit.com/r/rust/comments/estvau/til_why_the_eh_personality_language_item_is/ 145 | -------------------------------------------------------------------------------- /src/asm.md: -------------------------------------------------------------------------------- 1 | # Assembly on stable 2 | 3 | > Note: Since Rust 1.59, both *inline* assembly (`asm!`) and *free form* assembly 4 | > (`global_asm!`) become stable. But since it will take some time for the 5 | > existing crates to catchup the change, and since it's good for us to know the 6 | > other ways in history we used to deal with assembly, we will keep this chapter 7 | > here. 8 | 9 | So far we have managed to boot the device and handle interrupts without a single 10 | line of assembly. That's quite a feat! But depending on the architecture you are 11 | targeting you may need some assembly to get to this point. There are also some 12 | operations, for example context switching, that require assembly. 13 | 14 | Both *inline* assembly (`asm!`) and *free form* assembly (`global_asm!`) were 15 | unstable before Rust 1.59. Normally, you will want to use `global_asm!` and 16 | `asm!` in your crates. However, this chapter describes an alternative approach 17 | you may also use. 18 | 19 | To motivate this section we'll tweak the `HardFault` handler to provide 20 | information about the stack frame that generated the exception. 21 | 22 | Here's what we want to do: 23 | 24 | Instead of letting the user directly put their `HardFault` handler in the vector 25 | table we'll make the `rt` crate put a trampoline to the user-defined `HardFault` 26 | handler in the vector table. 27 | 28 | ``` console 29 | $ tail -n36 ../rt/src/lib.rs 30 | ``` 31 | 32 | ``` rust 33 | {{#include ../ci/asm/rt/src/lib.rs:61:96}} 34 | ``` 35 | 36 | This trampoline will read the stack pointer and then call the user `HardFault` 37 | handler. The trampoline will have to be written in assembly: 38 | 39 | ``` armasm 40 | {{#include ../ci/asm/rt/asm.s:5:6}} 41 | ``` 42 | 43 | Due to how the ARM ABI works this sets the Main Stack Pointer (MSP) as the first 44 | argument of the `HardFault` function / routine. This MSP value also happens to 45 | be a pointer to the registers pushed to the stack by the exception. With these 46 | changes the user `HardFault` handler must now have signature 47 | `fn(&StackedRegisters) -> !`. 48 | 49 | ## `.s` files 50 | 51 | One approach to stable assembly is to write the assembly in an external file: 52 | 53 | ``` console 54 | $ cat ../rt/asm.s 55 | ``` 56 | 57 | ``` armasm 58 | {{#include ../ci/asm/rt/asm.s}} 59 | ``` 60 | 61 | And use the `cc` crate in the build script of the `rt` crate to assemble that 62 | file into an object file (`.o`) and then into an archive (`.a`). 63 | 64 | ``` console 65 | $ cat ../rt/build.rs 66 | ``` 67 | 68 | ``` rust 69 | {{#include ../ci/asm/rt/build.rs}} 70 | ``` 71 | 72 | ``` console 73 | $ tail -n2 ../rt/Cargo.toml 74 | ``` 75 | 76 | ``` toml 77 | {{#include ../ci/asm/rt/Cargo.toml:7:8}} 78 | ``` 79 | 80 | And that's it! 81 | 82 | We can confirm that the vector table contains a pointer to `HardFaultTrampoline` 83 | by writing a very simple program. 84 | 85 | ``` rust 86 | {{#include ../ci/asm/app/src/main.rs}} 87 | ``` 88 | 89 | Here's the disassembly. Look at the address of `HardFaultTrampoline`. 90 | 91 | ``` console 92 | $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex 93 | ``` 94 | 95 | ``` text 96 | {{#include ../ci/asm/app/release.objdump}} 97 | ``` 98 | 99 | > **NOTE:** To make this disassembly smaller I commented out the initialization 100 | > of RAM. 101 | 102 | Now look at the vector table. The 4th entry should be the address of 103 | `HardFaultTrampoline` plus one. 104 | 105 | ``` console 106 | $ cargo objdump --bin app --release -- -s -j .vector_table 107 | ``` 108 | 109 | ``` text 110 | {{#include ../ci/asm/app/release.vector_table}} 111 | ``` 112 | 113 | ## `.o` / `.a` files 114 | 115 | The downside of using the `cc` crate is that it requires some assembler program 116 | on the build machine. For example when targeting ARM Cortex-M the `cc` crate 117 | uses `arm-none-eabi-gcc` as the assembler. 118 | 119 | Instead of assembling the file on the build machine we can ship a pre-assembled 120 | file with the `rt` crate. That way no assembler program is required on the build 121 | machine. However, you would still need an assembler on the machine that packages 122 | and publishes the crate. 123 | 124 | There's not much difference between an assembly (`.s`) file and its *compiled* 125 | version: the object (`.o`) file. The assembler doesn't do any optimization; it 126 | simply chooses the right object file format for the target architecture. 127 | 128 | Cargo provides support for bundling archives (`.a`) with crates. We can package 129 | object files into an archive using the `ar` command and then bundle the archive 130 | with the crate. In fact, this what the `cc` crate does; you can see the commands 131 | it invoked by searching for a file named `output` in the `target` directory. 132 | 133 | ``` console 134 | $ grep running $(find target -name output) 135 | ``` 136 | 137 | ``` text 138 | running: "arm-none-eabi-gcc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-mthumb" "-march=armv7-m" "-Wall" "-Wextra" "-o" "/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/asm.o" "-c" "asm.s" 139 | running: "ar" "crs" "/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/libasm.a" "/home/japaric/rust-embedded/embedonomicon/ci/asm/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/asm.o" 140 | ``` 141 | 142 | ``` console 143 | $ grep cargo $(find target -name output) 144 | ``` 145 | 146 | ``` tetx 147 | cargo:rustc-link-search=/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out 148 | cargo:rustc-link-lib=static=asm 149 | cargo:rustc-link-search=native=/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out 150 | ``` 151 | 152 | We'll do something similar to produce an archive. 153 | 154 | ``` console 155 | $ # most of flags `cc` uses have no effect when assembling so we drop them 156 | $ arm-none-eabi-as -march=armv7-m asm.s -o asm.o 157 | 158 | $ ar crs librt.a asm.o 159 | 160 | $ arm-none-eabi-objdump -Cd librt.a 161 | ``` 162 | 163 | ``` text 164 | {{#include ../ci/asm/rt2/librt.objdump}} 165 | ``` 166 | 167 | Next we modify the build script to bundle this archive with the `rt` rlib. 168 | 169 | ``` console 170 | $ cat ../rt/build.rs 171 | ``` 172 | 173 | ``` rust 174 | {{#include ../ci/asm/rt2/build.rs}} 175 | ``` 176 | 177 | Now we can test this new version against the simple program from before and 178 | we'll get the same output. 179 | 180 | ``` console 181 | $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex 182 | ``` 183 | 184 | ``` text 185 | {{#include ../ci/asm/app2/release.objdump}} 186 | ``` 187 | 188 | > **NOTE**: As before I have commented out the RAM initialization to make the 189 | > disassembly smaller. 190 | 191 | ``` console 192 | $ cargo objdump --bin app --release -- -s -j .vector_table 193 | ``` 194 | 195 | ``` text 196 | {{#include ../ci/asm/app2/release.vector_table}} 197 | ``` 198 | 199 | The downside of shipping pre-assembled archives is that, in the worst case 200 | scenario, you'll need to ship one build artifact for each compilation target 201 | your library supports. 202 | -------------------------------------------------------------------------------- /src/singleton.md: -------------------------------------------------------------------------------- 1 | # Global singletons 2 | 3 | In this section we'll cover how to implement a global, shared singleton. The 4 | embedded Rust book covered local, owned singletons which are pretty much unique 5 | to Rust. Global singletons are essentially the singleton pattern you see in C 6 | and C++; they are not specific to embedded development but since they involve 7 | symbols they seemed a good fit for the Embedonomicon. The embedded Rust book 8 | also has a chapter on [singletons]. 9 | 10 | To illustrate this section we'll extend the logger we developed in the last 11 | section to support global logging. The result will be very similar to the 12 | `#[global_allocator]` feature covered in the [collections] chapter of the 13 | embedded Rust book. 14 | 15 | Here's the summary of what we want to do: 16 | 17 | In the last section we created a `log!` macro to log messages through a specific 18 | logger, a value that implements the `Log` trait. The syntax of the `log!` macro 19 | is `log!(logger, "String")`. We want to extend the macro such that 20 | `log!("String")` also works. Using the `logger`-less version should log the 21 | message through a global logger; this is how `std::println!` works. We'll also 22 | need a mechanism to declare what the global logger is; this is the part that's 23 | similar to `#[global_allocator]`. 24 | 25 | It could be that the global logger is declared in the top crate, and it could 26 | also be that the type of the global logger is defined in the top crate. In this 27 | scenario the dependencies *cannot* know the exact type of the global logger. To 28 | support this scenario we'll need some indirection. 29 | 30 | Instead of hardcoding the type of the global logger in the `log` crate we'll 31 | declare only the *interface* of the global logger in that crate. That is we'll 32 | add a new trait, `GlobalLog`, to the `log` crate. The `log!` macro will also 33 | have to make use of that trait. 34 | 35 | ``` console 36 | $ cat ../log/src/lib.rs 37 | ``` 38 | 39 | ``` rust 40 | {{#include ../ci/singleton/log/src/lib.rs}} 41 | ``` 42 | 43 | There's quite a bit to unpack here. 44 | 45 | Let's start with the trait. 46 | 47 | ``` rust 48 | {{#include ../ci/singleton/log/src/lib.rs:4:6}} 49 | ``` 50 | 51 | Both `GlobalLog` and `Log` have a `log` method. The difference is that 52 | `GlobalLog.log` takes a shared reference to the receiver (`&self`). This is 53 | necessary because the global logger will be a `static` variable. More on that 54 | later. 55 | 56 | The other difference is that `GlobalLog.log` doesn't return a `Result`. This 57 | means that it can *not* report errors to the caller. This is not a strict 58 | requirement for traits used to implement global singletons. Error handling in 59 | global singletons is fine but then all users of the global version of the `log!` 60 | macro have to agree on the error type. Here we are simplifying the interface a 61 | bit by having the `GlobalLog` implementer deal with the errors. 62 | 63 | Yet another difference is that `GlobalLog` requires that the implementer is 64 | `Sync`, that is that it can be shared between threads. This is a requirement for 65 | values placed in `static` variables; their types must implement the `Sync` 66 | trait. 67 | 68 | At this point it may not be entirely clear why the interface has to look this 69 | way. The other parts of the crate will make this clearer so keep reading. 70 | 71 | Next up is the `log!` macro: 72 | 73 | ``` rust 74 | {{#include ../ci/singleton/log/src/lib.rs:17:29}} 75 | ``` 76 | 77 | When called without a specific `$logger` the macros uses an `extern` `static` 78 | variable called `LOGGER` to log the message. This variable *is* the global 79 | logger that's defined somewhere else; that's why we use the `extern` block. We 80 | saw this pattern in the [main interface] chapter. 81 | 82 | [main interface]: main.html 83 | 84 | We need to declare a type for `LOGGER` or the code won't type check. We don't 85 | know the concrete type of `LOGGER` at this point but we know, or rather require, 86 | that it implements the `GlobalLog` trait so we can use a trait object here. 87 | 88 | The rest of the macro expansion looks very similar to the expansion of the local 89 | version of the `log!` macro so I won't explain it here as it's explained in the 90 | [previous] chapter. 91 | 92 | [previous]: logging.html 93 | 94 | Now that we know that `LOGGER` has to be a trait object it's clearer why we 95 | omitted the associated `Error` type in `GlobalLog`. If we had not omitted then 96 | we would have need to pick a type for `Error` in the type signature of `LOGGER`. 97 | This is what I earlier meant by "all users of `log!` would need to agree on the 98 | error type". 99 | 100 | Now the final piece: the `global_logger!` macro. It could have been a proc macro 101 | attribute but it's easier to write a `macro_rules!` macro. 102 | 103 | ``` rust 104 | {{#include ../ci/singleton/log/src/lib.rs:41:47}} 105 | ``` 106 | 107 | This macro creates the `LOGGER` variable that `log!` uses. Because we need a 108 | stable ABI interface we use the `no_mangle` attribute. This way the symbol name 109 | of `LOGGER` will be "LOGGER" which is what the `log!` macro expects. 110 | 111 | The other important bit is that the type of this static variable must exactly 112 | match the type used in the expansion of the `log!` macro. If they don't match 113 | Bad Stuff will happen due to ABI mismatch. 114 | 115 | Let's write an example that uses this new global logger functionality. 116 | 117 | ``` console 118 | $ cat src/main.rs 119 | ``` 120 | 121 | ``` rust 122 | {{#include ../ci/singleton/app/src/main.rs}} 123 | ``` 124 | 125 | We had to add `cortex-m` to the dependencies. 126 | 127 | ``` console 128 | $ tail -n5 Cargo.toml 129 | ``` 130 | 131 | ``` text 132 | {{#include ../ci/singleton/app/Cargo.toml:11:15}} 133 | ``` 134 | 135 | This is a port of one of the examples written in the [previous] section. The 136 | output is the same as what we got back there. 137 | 138 | ``` console 139 | $ cargo run | xxd -p 140 | ``` 141 | 142 | ``` text 143 | {{#include ../ci/singleton/app/dev.out}} 144 | ``` 145 | 146 | ``` console 147 | $ cargo objdump --bin app -- -t | grep '\.log' 148 | ``` 149 | 150 | ``` text 151 | {{#include ../ci/singleton/app/dev.objdump}} 152 | ``` 153 | 154 | --- 155 | 156 | Some readers may be concerned about this implementation of global singletons not 157 | being zero cost because it uses trait objects which involve dynamic dispatch, 158 | that is method calls are performed through a vtable lookup. 159 | 160 | However, it appears that LLVM is smart enough to eliminate the dynamic dispatch 161 | when compiling with optimizations / LTO. This can be confirmed by searching for 162 | `LOGGER` in the symbol table. 163 | 164 | ``` console 165 | $ cargo objdump --bin app --release -- -t | grep LOGGER 166 | ``` 167 | 168 | ``` text 169 | {{#include ../ci/singleton/app/release.objdump}} 170 | ``` 171 | 172 | If the `static` is missing that means that there is no vtable and that LLVM was 173 | capable of transforming all the `LOGGER.log` calls into `Logger.log` calls. 174 | 175 | [singletons]: https://docs.rust-embedded.org/book/peripherals/singletons.html 176 | [collections]: https://docs.rust-embedded.org/book/collections/index.html#using-alloc 177 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | # All objdumps are compared to their expected output using the `-b` flag, which 4 | # ignores whitespace changes within lines. This is good to avoid the necessity 5 | # of regenerating blessed outputs each time `cargo objdump` makes minor 6 | # formatting changes. 7 | # 8 | # Note that `diff -b` does _not_ allow whitespace changes to span multiple 9 | # lines (i.e. the addition or removal of whitespace-only lines). This is a good 10 | # thing, since some files are included in the book based on line number. 11 | 12 | main() { 13 | # build the book and check that it has no dead links 14 | mdbook build 15 | 16 | linkchecker book 17 | 18 | # now check this as a directory of the bookshelf 19 | rm -rf shelf 20 | mkdir shelf 21 | mv book shelf 22 | linkchecker shelf 23 | 24 | mv shelf/book . 25 | rmdir shelf 26 | 27 | # test the instructions at different stages 28 | cd ci 29 | 30 | # # smallest-no-std 31 | pushd smallest-no-std 32 | 33 | # check that it builds 34 | cargo rustc -- --emit=obj 35 | 36 | # check that the output is an empty binary 37 | # NOTE(-b) ignore change in whitespace 38 | diff -b app.size \ 39 | <(cargo size --bin app) 40 | 41 | # check presence of the `rust_begin_unwind` symbol 42 | diff app.o.nm \ 43 | <(cargo nm -- -C $(pwd)/target/thumbv7m-none-eabi/debug/deps/app-*.o | grep '[0-9]* [^N] ') 44 | 45 | 46 | edition_check 47 | 48 | popd 49 | 50 | # # memory-layout 51 | pushd memory-layout 52 | 53 | # check that the Reset symbol is there 54 | diff -b app.text.objdump \ 55 | <(cargo objdump --bin app -- -d --no-show-raw-insn --no-leading-addr) 56 | 57 | # check that the reset vector is there and has the right address 58 | diff -b app.vector_table.objdump \ 59 | <(cargo objdump --bin app -- -s --section .vector_table) 60 | 61 | qemu_check target/thumbv7m-none-eabi/debug/app 62 | 63 | edition_check 64 | 65 | popd 66 | 67 | # # main 68 | pushd main 69 | 70 | # check that the disassembly matches 71 | pushd app 72 | diff -b app.objdump \ 73 | <(cargo objdump --bin app -- -d --no-show-raw-insn --no-leading-addr) 74 | # disabled because of rust-lang/rust#53964 75 | # edition_check 76 | popd 77 | 78 | # check that it builds 79 | pushd app2 80 | cargo build 81 | edition_check 82 | popd 83 | 84 | pushd app3 85 | cargo build 86 | edition_check 87 | popd 88 | 89 | pushd app4 90 | cargo build 91 | qemu_check target/thumbv7m-none-eabi/debug/app 92 | edition_check 93 | popd 94 | 95 | pushd app-unsound 96 | cargo build 97 | qemu_check target/thumbv7m-none-eabi/debug/app 98 | edition_check 99 | popd 100 | 101 | popd 102 | 103 | 104 | pushd exceptions 105 | 106 | # check that the disassembly matches 107 | pushd app 108 | diff -b app.objdump \ 109 | <(cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex) 110 | diff -b app.vector_table.objdump \ 111 | <(cargo objdump --bin app --release -- -s -j .vector_table) 112 | edition_check 113 | popd 114 | 115 | # check that it builds 116 | pushd app2 117 | cargo build 118 | edition_check 119 | popd 120 | 121 | popd 122 | 123 | # # Assembly on stable 124 | pushd asm 125 | 126 | # check that the disassembly matches 127 | pushd app 128 | diff -b release.objdump \ 129 | <(cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex) 130 | diff release.vector_table \ 131 | <(cargo objdump --bin app --release -- -s -j .vector_table) 132 | edition_check 133 | popd 134 | 135 | # check that the binary blob is up to date 136 | pushd rt2 137 | arm-none-eabi-as -march=armv7-m asm.s -o asm.o 138 | ar crs librt.a asm.o 139 | diff -b librt.objdump \ 140 | <(arm-none-eabi-objdump -Cd librt.a) 141 | popd 142 | 143 | # check that the disassembly matches 144 | pushd app2 145 | diff -b release.objdump \ 146 | <(cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex) 147 | diff release.vector_table \ 148 | <(cargo objdump --bin app --release -- -s -j .vector_table) 149 | edition_check 150 | popd 151 | 152 | popd 153 | 154 | # # Logging with symbols 155 | pushd logging 156 | 157 | # check that the ~output~ and disassembly matches 158 | # the output won't exactly match because addresses of static variables won't 159 | # remain the same when the toolchain is updated. Instead we'll that the 160 | # printed address is contained in the output of `cargo objdump -- -t` 161 | pushd app 162 | cargo run > dev.out 163 | cargo objdump --bin app -- -t | grep '\.rodata\s*0*1\b' > dev.objdump 164 | for address in $(cat dev.out); do 165 | grep ${address#0x} dev.objdump 166 | done 167 | 168 | cargo run --release > release.out 169 | cargo objdump --bin app --release -- -t | grep '\.rodata\s*0*1\b' > release.objdump 170 | for address in $(cat release.out); do 171 | grep ${address#0x} release.objdump 172 | done 173 | 174 | # sanity check the committed files 175 | git checkout dev.out 176 | git checkout dev.objdump 177 | for address in $(cat dev.out); do 178 | grep ${address#0x} dev.objdump 179 | done 180 | 181 | git checkout release.out 182 | git checkout release.objdump 183 | for address in $(cat release.out); do 184 | grep ${address#0x} release.objdump 185 | done 186 | edition_check 187 | popd 188 | 189 | # check that the output and disassembly matches 190 | pushd app2 191 | diff dev.out \ 192 | <(cargo run | xxd -p) 193 | diff -b dev.objdump \ 194 | <(cargo objdump --bin app -- -t | grep '\.log') 195 | edition_check 196 | popd 197 | 198 | # check that the output and disassembly matches 199 | pushd app3 200 | diff dev.out \ 201 | <(cargo run | xxd -p) 202 | diff -b dev.objdump \ 203 | <(cargo objdump --bin app -- -t | grep '\.log') 204 | edition_check 205 | popd 206 | 207 | # check that the output and disassembly matches 208 | pushd app4 209 | diff dev.out \ 210 | <(cargo run | xxd -p) 211 | diff -b dev.objdump \ 212 | <(cargo objdump --bin app -- -t | grep '\.log') 213 | edition_check 214 | popd 215 | 216 | popd 217 | 218 | # # Logging with symbols 219 | pushd singleton 220 | 221 | pushd app 222 | diff dev.out \ 223 | <(cargo run | xxd -p) 224 | diff -b dev.objdump \ 225 | <(cargo objdump --bin app -- -t | grep '\.log') 226 | diff -b release.objdump \ 227 | <(cargo objdump --bin app --release -- -t | grep LOGGER) 228 | edition_check 229 | popd 230 | 231 | popd 232 | 233 | # DMA 234 | pushd dma 235 | cargo build --examples 236 | popd 237 | } 238 | 239 | # checks that 2024 idioms are being used 240 | edition_check() { 241 | RUSTFLAGS="-D rust_2024_compatibility" cargo check 242 | } 243 | 244 | # checks that QEMU doesn't crash and that it produces no error messages 245 | qemu_check() { 246 | qemu-system-arm \ 247 | -cpu cortex-m3 \ 248 | -machine lm3s6965evb \ 249 | -nographic \ 250 | -kernel $1 \ 251 | >.stdout 2>.stderr & 252 | 253 | local pid=$! 254 | sleep 3 255 | # check that: process is still running && stdout is empty && stderr is empty 256 | kill -9 $pid && ! [ -s .stdout ] && ! [ -s .stderr ] || \ 257 | ( cat .stdout && cat .stderr && exit 1) 258 | rm .stdout .stderr 259 | } 260 | 261 | main 262 | -------------------------------------------------------------------------------- /src/main.md: -------------------------------------------------------------------------------- 1 | # A `main` interface 2 | 3 | We have a minimal working program now, but we need to package it in a way that the end user can build 4 | safe programs on top of it. In this section, we'll implement a `main` interface like the one standard 5 | Rust programs use. 6 | 7 | First, we'll convert our binary crate into a library crate: 8 | 9 | ``` console 10 | $ mv src/main.rs src/lib.rs 11 | ``` 12 | 13 | And then rename it to `rt` which stands for "runtime". 14 | 15 | ``` console 16 | $ sed -i s/app/rt/ Cargo.toml 17 | 18 | $ head -n4 Cargo.toml 19 | ``` 20 | 21 | ``` toml 22 | {{#include ../ci/main/rt/Cargo.toml:1:4}} 23 | ``` 24 | 25 | The first change is to have the reset handler call an external `main` function: 26 | 27 | ``` console 28 | $ head -n13 src/lib.rs 29 | ``` 30 | 31 | ``` rust 32 | {{#include ../ci/main/rt/src/lib.rs:1:13}} 33 | ``` 34 | 35 | We also drop the `#![no_main]` attribute as it has no effect on library crates. 36 | 37 | > There's an orthogonal question that arises at this stage: Should the `rt` 38 | > library provide a standard panicking behavior, or should it *not* provide a 39 | > `#[panic_handler]` function and leave the end user to choose the panicking 40 | > behavior? This document won't delve into that question and for simplicity will 41 | > leave the dummy `#[panic_handler]` function in the `rt` crate. However, we 42 | > wanted to inform the reader that there are other options. 43 | 44 | The second change involves providing the linker script we wrote before to the application crate. The linker will search for linker scripts in the library search path (`-L`) and in the directory 45 | from which it's invoked. The application crate shouldn't need to carry around a copy of `link.x` so 46 | we'll have the `rt` crate put the linker script in the library search path using a [build script]. 47 | 48 | [build script]: https://doc.rust-lang.org/cargo/reference/build-scripts.html 49 | 50 | ``` console 51 | $ # create a build.rs file in the root of `rt` with these contents 52 | $ cat build.rs 53 | ``` 54 | 55 | ``` rust 56 | {{#include ../ci/main/rt/build.rs}} 57 | ``` 58 | 59 | Now the user can write an application that exposes the `main` symbol and link it to the `rt` crate. 60 | The `rt` will take care of giving the program the right memory layout. 61 | 62 | ``` console 63 | $ cd .. 64 | 65 | $ cargo new --edition 2024 --bin app 66 | 67 | $ cd app 68 | 69 | $ # modify Cargo.toml to include the `rt` crate as a dependency 70 | $ tail -n2 Cargo.toml 71 | ``` 72 | 73 | ``` toml 74 | {{#include ../ci/main/app/Cargo.toml:7:8}} 75 | ``` 76 | 77 | ``` console 78 | $ # copy over the config file that sets a default target and tweaks the linker invocation 79 | $ cp -r ../rt/.cargo . 80 | 81 | $ # change the contents of `main.rs` to 82 | $ cat src/main.rs 83 | ``` 84 | 85 | ``` rust 86 | {{#include ../ci/main/app/src/main.rs}} 87 | ``` 88 | 89 | The disassembly will be similar but will now include the user `main` function. 90 | 91 | ``` console 92 | $ cargo objdump --bin app -- -d --no-show-raw-insn 93 | ``` 94 | 95 | ``` text 96 | {{#include ../ci/main/app/app.objdump}} 97 | ``` 98 | 99 | ## Making it type safe 100 | 101 | The `main` interface works, but it's easy to get it wrong. For example, the user could write `main` 102 | as a non-divergent function, and they would get no compile time error and undefined behavior (the 103 | compiler will misoptimize the program). 104 | 105 | We can add type safety by exposing a macro to the user instead of the symbol interface. In the 106 | `rt` crate, we can write this macro: 107 | 108 | ``` console 109 | $ tail -n12 ../rt/src/lib.rs 110 | ``` 111 | 112 | ``` rust 113 | {{#include ../ci/main/rt/src/lib.rs:25:37}} 114 | ``` 115 | 116 | Then the application writers can invoke it like this: 117 | 118 | ``` console 119 | $ cat src/main.rs 120 | ``` 121 | 122 | ``` rust 123 | {{#include ../ci/main/app2/src/main.rs}} 124 | ``` 125 | 126 | Now the author will get an error if they change the signature of `main` to be 127 | non divergent function, e.g. `fn()`. 128 | 129 | ## Life before main 130 | 131 | `rt` is looking good but it's not feature complete! Applications written against it can't use 132 | `static` variables or string literals because `rt`'s linker script doesn't define the standard 133 | `.bss`, `.data` and `.rodata` sections. Let's fix that! 134 | 135 | The first step is to define these sections in the linker script: 136 | 137 | ``` console 138 | $ # showing just a fragment of the file 139 | $ sed -n 25,46p ../rt/link.x 140 | ``` 141 | 142 | ``` text 143 | {{#include ../ci/main/rt/link.x:25:46}} 144 | ``` 145 | 146 | They just re-export the input sections and specify in which memory region each output section will 147 | go. 148 | 149 | With these changes, the following program will compile: 150 | 151 | ``` rust 152 | {{#include ../ci/main/app3/src/main.rs}} 153 | ``` 154 | 155 | However if you run this program on real hardware and debug it, you'll observe that the `static` 156 | variables `BSS` and `DATA` don't have the values `0` and `1` by the time `main` has been reached. 157 | Instead, these variables will have junk values. The problem is that the contents of RAM are 158 | random after powering up the device. You won't be able to observe this effect if you run the 159 | program in QEMU. 160 | 161 | As things stand if your program reads any `static` variable before performing a write to it then 162 | your program has undefined behavior. Let's fix that by initializing all `static` variables before 163 | calling `main`. 164 | 165 | We'll need to tweak the linker script a bit more to do the RAM initialization: 166 | 167 | ``` console 168 | $ # showing just a fragment of the file 169 | $ sed -n 25,52p ../rt/link.x 170 | ``` 171 | 172 | ``` text 173 | {{#include ../ci/main/rt2/link.x:25:52}} 174 | ``` 175 | 176 | Let's go into the details of these changes: 177 | 178 | ``` text 179 | {{#include ../ci/main/rt2/link.x:38}} 180 | ``` 181 | 182 | ``` text 183 | {{#include ../ci/main/rt2/link.x:40}} 184 | ``` 185 | 186 | ``` text 187 | {{#include ../ci/main/rt2/link.x:45}} 188 | ``` 189 | 190 | ``` text 191 | {{#include ../ci/main/rt2/link.x:47}} 192 | ``` 193 | 194 | We associate symbols to the start and end addresses of the `.bss` and `.data` sections, which we'll 195 | later use to initialize them. 196 | 197 | ``` text 198 | {{#include ../ci/main/rt2/link.x:43}} 199 | ``` 200 | 201 | We set the Load Memory Address (LMA) of the `.data` section to the end of the `.rodata` 202 | section. The `.data` contains `static` variables with a non-zero initial value; the Virtual Memory 203 | Address (VMA) of the `.data` section is somewhere in RAM -- this is where the `static` variables are 204 | located. The initial values of those `static` variables, however, must be allocated in non volatile 205 | memory (Flash); the LMA is where in Flash those initial values are stored. 206 | 207 | ``` text 208 | {{#include ../ci/main/rt2/link.x:50}} 209 | ``` 210 | 211 | Finally, we associate a symbol to the LMA of `.data`. 212 | 213 | Using our initialization code, we zero the `.bss` section and initialize the `.data` section. We can reference 214 | the symbols we created in the linker script from the code. The *addresses*[^1] of these symbols are 215 | the boundaries of the `.bss` and `.data` sections. 216 | 217 | We could write the initialization `.bss` and `.data` section code in pure Rust code. In fact, earlier 218 | versions of this book did so. However, several soundness questions have been raised over time, 219 | and it is no longer considered good practice to initialize them in Rust code. See the 220 | [Why don't we initialize .data and .bss using Rust](./sections-in-rust.md) section of the book for more details. 221 | We will write the initialization code using the `global_asm!` macro to define our reset handler. 222 | 223 | The updated reset handler, now written in `Thumb-2` assembly, is shown below: 224 | 225 | ``` console 226 | $ head -n53 ../rt/src/lib.rs 227 | ``` 228 | 229 | ``` rust 230 | {{#include ../ci/main/rt2/src/lib.rs:1:53}} 231 | ``` 232 | 233 | Now end users can directly and indirectly make use of `static` variables without running into 234 | undefined behavior! 235 | 236 | > In the code above we performed the memory initialization in a bytewise fashion. It's possible to 237 | > force the `.bss` and `.data` sections to be aligned to, say, 4 bytes. This fact can then be used 238 | > in the Rust code to perform the initialization wordwise while omitting alignment checks. If you 239 | > are interested in learning how this can be achieved check the [`cortex-m-rt`] crate. 240 | 241 | [`cortex-m-rt`]: https://github.com/japaric/cortex-m-rt/tree/v0.5.1 242 | 243 | [^1]: The fact that the addresses of the linker script symbols must be used here can be confusing and 244 | unintuitive. An elaborate explanation for this oddity can be found [here](https://stackoverflow.com/a/40392131). 245 | -------------------------------------------------------------------------------- /src/exceptions.md: -------------------------------------------------------------------------------- 1 | # Exception handling 2 | 3 | During the "Memory layout" section, we decided to start out simple and leave out handling of 4 | exceptions. In this section, we'll add support for handling them; this serves as an example of 5 | how to achieve compile time overridable behavior in stable Rust (i.e. without relying on the 6 | unstable `#[linkage = "weak"]` attribute, which makes a symbol weak). 7 | 8 | ## Background information 9 | 10 | In a nutshell, *exceptions* are a mechanism the Cortex-M and other architectures provide to let 11 | applications respond to asynchronous, usually external, events. The most prominent type of exception, 12 | that most people will know, is the classical (hardware) interrupt. 13 | 14 | The Cortex-M exception mechanism works like this: 15 | When the processor receives a signal or event associated to a type of exception, it suspends 16 | the execution of the current subroutine (by stashing the state in the call stack) and then proceeds 17 | to execute the corresponding exception handler, another subroutine, in a new stack frame. After 18 | finishing the execution of the exception handler (i.e. returning from it), the processor resumes the 19 | execution of the suspended subroutine. 20 | 21 | The processor uses the vector table to decide what handler to execute. Each entry in the table 22 | contains a pointer to a handler, and each entry corresponds to a different exception type. For 23 | example, the second entry is the reset handler, the third entry is the NMI (Non Maskable Interrupt) 24 | handler, and so on. 25 | 26 | As mentioned before, the processor expects the vector table to be at some specific location in memory, 27 | and each entry in it can potentially be used by the processor at runtime. Hence, the entries must always 28 | contain valid values. Furthermore, we want the `rt` crate to be flexible so the end user can customize the 29 | behavior of each exception handler. Finally, the vector table resides in read only memory, or rather in not 30 | easily modified memory, so the user has to register the handler statically, rather than at runtime. 31 | 32 | To satisfy all these constraints, we'll assign a *default* value to all the entries of the vector 33 | table in the `rt` crate, but make these values kind of *weak* to let the end user override them 34 | at compile time. 35 | 36 | ## Rust side 37 | 38 | Let's see how all this can be implemented. For simplicity, we'll only work with the first 16 entries 39 | of the vector table; these entries are not device specific so they have the same function on any 40 | kind of Cortex-M microcontroller. 41 | 42 | The first thing we'll do is create an array of vectors (pointers to exception handlers) in the 43 | `rt` crate's code: 44 | 45 | ``` console 46 | $ sed -n 56,91p ../rt/src/lib.rs 47 | ``` 48 | 49 | ``` rust 50 | {{#include ../ci/exceptions/rt/src/lib.rs:56:91}} 51 | ``` 52 | 53 | Some of the entries in the vector table are *reserved*; the ARM documentation states that they 54 | should be assigned the value `0` so we use a union to do exactly that. The entries that must point 55 | to a handler make use of *external* functions; this is important because it lets the end user 56 | *provide* the actual function definition. 57 | 58 | Next, we define a default exception handler in the Rust code. Exceptions that have not been assigned 59 | a handler by the end user will make use of this default handler. 60 | 61 | ``` console 62 | $ tail -n4 ../rt/src/lib.rs 63 | ``` 64 | 65 | ``` rust 66 | {{#include ../ci/exceptions/rt/src/lib.rs:93:97}} 67 | ``` 68 | 69 | ## Linker script side 70 | 71 | On the linker script side, we place these new exception vectors right after the reset vector. 72 | 73 | ``` console 74 | $ sed -n 12,25p ../rt/link.x 75 | ``` 76 | 77 | ``` text 78 | {{#include ../ci/exceptions/rt/link.x:12:27}} 79 | ``` 80 | 81 | And we use `PROVIDE` to give a default value to the handlers that we left undefined in `rt` (`NMI` 82 | and the others above): 83 | 84 | ``` console 85 | $ tail -n8 ../rt/link.x 86 | ``` 87 | 88 | ``` text 89 | PROVIDE(NMI = DefaultExceptionHandler); 90 | PROVIDE(HardFault = DefaultExceptionHandler); 91 | PROVIDE(MemManage = DefaultExceptionHandler); 92 | PROVIDE(BusFault = DefaultExceptionHandler); 93 | PROVIDE(UsageFault = DefaultExceptionHandler); 94 | PROVIDE(SVCall = DefaultExceptionHandler); 95 | PROVIDE(PendSV = DefaultExceptionHandler); 96 | PROVIDE(SysTick = DefaultExceptionHandler); 97 | ``` 98 | 99 | `PROVIDE` only takes effect when the symbol to the left of the equal sign is still undefined after 100 | inspecting all the input object files. This is the scenario where the user didn't implement the 101 | handler for the respective exception. 102 | 103 | ## Testing it 104 | 105 | That's it! The `rt` crate now has support for exception handlers. We can test it out with following 106 | application: 107 | 108 | > **NOTE**: Turns out it's hard to generate an exception in QEMU. On real 109 | > hardware a read to an invalid memory address (i.e. outside of the Flash and 110 | > RAM regions) would be enough but QEMU happily accepts the operation and 111 | > returns zero. A trap instruction works on both QEMU and hardware but 112 | > unfortunately it's not available as pure Rust code. We will use inline assembly 113 | > to generate the trap instruction. Later chapters describe the use of assembly 114 | > in more detail. 115 | 116 | ``` rust 117 | {{#include ../ci/exceptions/app/src/main.rs}} 118 | ``` 119 | 120 | ``` console 121 | (gdb) target remote :3333 122 | Remote debugging using :3333 123 | Reset () at ../rt/src/lib.rs:7 124 | 7 pub unsafe extern "C" fn Reset() -> ! { 125 | 126 | (gdb) b DefaultExceptionHandler 127 | Breakpoint 1 at 0xec: file ../rt/src/lib.rs, line 95. 128 | 129 | (gdb) continue 130 | Continuing. 131 | 132 | Breakpoint 1, DefaultExceptionHandler () 133 | at ../rt/src/lib.rs:95 134 | 95 loop {} 135 | 136 | (gdb) list 137 | 90 Vector { handler: SysTick }, 138 | 91 ]; 139 | 92 140 | 93 #[no_mangle] 141 | 94 pub extern "C" fn DefaultExceptionHandler() { 142 | 95 loop {} 143 | 96 } 144 | ``` 145 | 146 | And for completeness, here's the disassembly of the optimized version of the program: 147 | 148 | ``` console 149 | $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex 150 | ``` 151 | 152 | ``` text 153 | {{#include ../ci/exceptions/app/app.objdump:1:34}} 154 | ``` 155 | 156 | ``` console 157 | $ cargo objdump --bin app --release -- -s -j .vector_table 158 | ``` 159 | 160 | ``` text 161 | {{#include ../ci/exceptions/app/app.vector_table.objdump}} 162 | ``` 163 | 164 | The vector table now resembles the results of all the code snippets in this book 165 | so far. To summarize: 166 | - In the [_Inspecting it_] section of the earlier memory chapter, we learned 167 | that: 168 | - The first entry in the vector table contains the initial value of the 169 | stack pointer. 170 | - Objdump prints in `little endian` format, so the stack starts at 171 | `0x2001_0000`. 172 | - The second entry points to address `0x0000_0049`, the Reset handler. 173 | - The address of the Reset handler can be seen in the disassembly above, 174 | being `0x48`. 175 | - The first bit being set to 1 does not alter the address due to 176 | alignment requirements. Instead, it causes the function to be executed 177 | in _thumb mode_. 178 | - Afterwards, a pattern of addresses alternating between `0x85` and `0x00` is 179 | visible. 180 | - Looking at the disassembly above, it is clear that `0x85` refers to the 181 | `UsageFault` (`0x84` executed in thumb mode). 182 | - Cross referencing the pattern to the vector table that was set up earlier 183 | in this chapter (see the definition of `pub static EXCEPTIONS`) with [the 184 | vector table layout for the Cortex-M], it is clear that the address of the 185 | `UsageFault` is present each time a respective handler entry 186 | is present in the table. 187 | - But we did not explicitly insert `UsageFault` in each entry. Instead, we 188 | aliased each handler to `DefaultExceptionHandler`. Our disassembler found 189 | multiple symbols at the same location, and chose one of them for the 190 | function name. 191 | - In turn, it is also visible that the layout of the vector table data 192 | structure in the Rust code is aligned with all the reserved slots in the 193 | Cortex-M vector table. Hence, all reserved slots are correctly set to a 194 | value of zero. 195 | 196 | [_Inspecting it_]: https://docs.rust-embedded.org/embedonomicon/memory-layout.html#inspecting-it 197 | [the vector table layout for the Cortex-M]: https://developer.arm.com/docs/dui0552/latest/the-cortex-m3-processor/exception-model/vector-table 198 | 199 | ## Overriding a handler 200 | 201 | To override an exception handler, the user has to provide a function whose symbol name exactly 202 | matches the name we used in `EXCEPTIONS`. 203 | 204 | ``` rust 205 | {{#include ../ci/exceptions/app2/src/main.rs}} 206 | ``` 207 | 208 | You can test it in QEMU: 209 | 210 | ``` console 211 | (gdb) target remote :3333 212 | Remote debugging using :3333 213 | Reset () at /home/japaric/rust/embedonomicon/ci/exceptions/rt/src/lib.rs:7 214 | 7 pub unsafe extern "C" fn Reset() -> ! { 215 | 216 | (gdb) b HardFault 217 | Breakpoint 1 at 0x44: file src/main.rs, line 18. 218 | 219 | (gdb) continue 220 | Continuing. 221 | 222 | Breakpoint 1, HardFault () at src/main.rs:18 223 | 18 loop {} 224 | 225 | (gdb) list 226 | 13 } 227 | 14 228 | 15 #[no_mangle] 229 | 16 pub extern "C" fn HardFault() -> ! { 230 | 17 // do something interesting here 231 | 18 loop {} 232 | 19 } 233 | ``` 234 | 235 | The program now executes the user defined `HardFault` function instead of the 236 | `DefaultExceptionHandler` in the `rt` crate. 237 | 238 | Like our first attempt at a `main` interface, this first implementation has the problem of having no 239 | type safety. It's also easy to mistype the name of the exception, but that doesn't produce an error 240 | or warning. Instead the user defined handler is simply ignored. Those problems can be fixed using a 241 | macro like the [`exception!`] macro defined in `cortex-m-rt` v0.5.x or the 242 | [`exception`] attribute in `cortex-m-rt` v0.6.x. 243 | 244 | [`exception!`]: https://github.com/japaric/cortex-m-rt/blob/v0.5.1/src/lib.rs#L792 245 | [`exception`]: https://github.com/rust-embedded/cortex-m-rt/blob/v0.6.3/macros/src/lib.rs#L254 246 | -------------------------------------------------------------------------------- /src/custom-target.md: -------------------------------------------------------------------------------- 1 | # Creating a custom target 2 | 3 | If a custom target triple is not available for your platform, you must create a custom target file 4 | that describes your target to `rustc`. 5 | 6 | Keep in mind that it is required to use a nightly compiler to build the core library, which must be 7 | done for a target unknown to `rustc`. 8 | 9 | ## Deciding on a target triple 10 | 11 | Many targets already have a known triple used to describe them, typically in the form 12 | ARCH-VENDOR-SYS-ABI. You should aim to use the same triple that [LLVM uses][llvm-target-triple]; 13 | however, it may differ if you need to specify additional information to Rust that LLVM does not know 14 | about. Although the triple is technically only for human use, it's important for it to be unique and 15 | descriptive especially if the target will be upstreamed in the future. 16 | 17 | The ARCH part is typically just the architecture name, except in the case of 32-bit ARM. For 18 | example, you would probably use x86_64 for those processors, but specify the exact ARM architecture 19 | version. Typical values might be `armv7`, `armv5te`, or `thumbv7neon`. Take a look at the names of 20 | the [built-in targets][built-in-target] for inspiration. 21 | 22 | The VENDOR part is optional and describes the manufacturer. Omitting this field is the same as 23 | using `unknown`. 24 | 25 | The SYS part describes the OS that is used. Typical values include `win32`, `linux`, and `darwin` 26 | for desktop platforms. `none` is used for bare-metal usage. 27 | 28 | The ABI part describes how the process starts up. `eabi` is used for bare metal, while `gnu` is used 29 | for glibc, `musl` for musl, etc. 30 | 31 | Now that you have a target triple, create a file with the name of the triple and a `.json` 32 | extension. For example, a file describing `armv7a-none-eabi` would have the filename 33 | `armv7a-none-eabi.json`. 34 | 35 | [llvm-target-triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple 36 | 37 | ## Fill the target file 38 | 39 | The target file must be valid JSON. There are two places where its contents are described: 40 | [`Target`], where every field is mandatory, and [`TargetOptions`], where every field is optional. 41 | **All underscores are replaced with hyphens**. 42 | 43 | The recommended way is to base your target file on the specification of a built-in target that's 44 | similar to your target system, then tweak it to match the properties of your target system. To do 45 | so, use the command 46 | `rustc +nightly -Z unstable-options --print target-spec-json --target $SOME_SIMILAR_TARGET`, using 47 | [a target that's already built into the compiler][built-in-target]. 48 | 49 | You can pretty much copy that output into your file. Start with a few modifications: 50 | 51 | - Remove `"is-builtin": true` 52 | - Fill `llvm-target` with [the triple that LLVM expects][llvm-target-triple] 53 | - Decide on a panicking strategy. A bare metal implementation will likely use 54 | `"panic-strategy": "abort"`. If you decide not to `abort` on panicking, unless you [tell Cargo 55 | to][eh_personality] per-project, you must define an [eh_personality] function. 56 | - Configure atomics. Pick the first option that describes your target: 57 | - I have a single-core processor, no threads, [**no interrupts**][interrupts-note], or any way for 58 | multiple things to be happening in parallel: if you are **sure** that is the case, such as WASM 59 | (for now), you may set `"singlethread": true`. This will configure LLVM to convert all atomic 60 | operations to use their single threaded counterparts. Incorrectly using this option may result 61 | in UB if using threads or interrupts. 62 | - I have native atomic operations: set `max-atomic-width` to the biggest type in bits that your 63 | target can operate on atomically. For example, many ARM cores have 32-bit atomic operations. You 64 | may set `"max-atomic-width": 32` in that case. 65 | - I have no native atomic operations, but I can emulate them myself: set `max-atomic-width` to the 66 | highest number of bits that you can emulate up to 128, then implement all of the 67 | [atomic][libcalls-atomic] and [sync][libcalls-sync] functions expected by LLVM as 68 | `#[no_mangle] unsafe extern "C"`. These functions have been standardized by GCC, so the [GCC 69 | documentation][gcc-sync] may have more notes. Missing functions will cause a linker error, while 70 | incorrectly implemented functions will possibly cause UB. For example, if you have a 71 | single-core, single-thread processor with interrupts, you can implement these functions to 72 | disable interrupts, perform the regular operation, and then re-enable them. 73 | - I have no native atomic operations: you'll have to do some unsafe work to manually ensure 74 | synchronization in your code. You must set `"max-atomic-width": 0`. 75 | - Change the linker if integrating with an existing toolchain. For example, if you're using a 76 | toolchain that uses a custom build of GCC, set `"linker-flavor": "gcc"` and `linker` to the 77 | command name of your linker. If you require additional linker arguments, use `pre-link-args` and 78 | `post-link-args` as so: 79 | ``` json 80 | "pre-link-args": { 81 | "gcc": [ 82 | "-Wl,--as-needed", 83 | "-Wl,-z,noexecstack", 84 | "-m64" 85 | ] 86 | }, 87 | "post-link-args": { 88 | "gcc": [ 89 | "-Wl,--allow-multiple-definition", 90 | "-Wl,--start-group,-lc,-lm,-lgcc,-lstdc++,-lsupc++,--end-group" 91 | ] 92 | } 93 | ``` 94 | Ensure that the linker type is the key within `link-args`. 95 | - Configure LLVM features. Run `llc -march=ARCH -mattr=help` where ARCH is the base architecture 96 | (not including the version in the case of ARM) to list the available features and their 97 | descriptions. **If your target requires strict memory alignment access (e.g. `armv5te`), make sure 98 | that you enable `strict-align`**. To enable a feature, place a plus before it. Likewise, to 99 | disable a feature, place a minus before it. Features should be comma-separated like so: 100 | `"features": "+soft-float,+neon"`. Note that this may not be necessary if LLVM knows enough about 101 | your target based on the provided triple and CPU. 102 | - Configure the CPU that LLVM uses if you know it. This will enable CPU-specific optimizations and 103 | features. At the top of the output of the command in the last step, there is a list of known CPUs. 104 | If you know that you will be targeting a specific CPU, you may set it in the `cpu` field in the 105 | JSON target file. 106 | 107 | [`target`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.Target.html 108 | [`targetoptions`]: 109 | https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html 110 | [aborting-on-panic]: 111 | https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/aborting-on-panic.html 112 | [built-in-target]: ./compiler-support.md#built-in-target 113 | [eh_personality]: ./smallest-no-std.md#eh_personality 114 | [interrupts-note]: https://github.com/rust-lang/rust/issues/58500#issuecomment-654341233 115 | [libcalls-atomic]: http://llvm.org/docs/Atomics.html#libcalls-atomic 116 | [libcalls-sync]: http://llvm.org/docs/Atomics.html#libcalls-sync 117 | [gcc-sync]: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html 118 | 119 | ## Use the target file 120 | 121 | Once you have a target specification file, you may refer to it by its path or by its name (i.e. 122 | excluding `.json`) if it is in the current directory or in `$RUST_TARGET_PATH`. 123 | 124 | Verify that it is readable by `rustc`: 125 | 126 | ``` sh 127 | ❱ rustc --print cfg --target foo.json # or just foo if in the current directory 128 | debug_assertions 129 | target_arch="arm" 130 | target_endian="little" 131 | target_env="" 132 | target_feature="mclass" 133 | target_feature="v7" 134 | target_has_atomic="16" 135 | target_has_atomic="32" 136 | target_has_atomic="8" 137 | target_has_atomic="cas" 138 | target_has_atomic="ptr" 139 | target_os="none" 140 | target_pointer_width="32" 141 | target_vendor="" 142 | ``` 143 | 144 | Now, you finally get to use it! Many resources have been recommending [`xargo`] or [`cargo-xbuild`]. 145 | However, its successor, cargo's `build-std` feature, has received a lot of work recently and has 146 | quickly reached feature parity with the other options. As such, this guide will only cover that 147 | option. 148 | 149 | Start with a bare minimum [`no_std` program][no_std-program]. Now, run 150 | `cargo build -Z build-std=core --target foo.json`, again using the above rules about referencing the 151 | path. Hopefully, you should now have a binary in the target directory. 152 | 153 | You may optionally configure cargo to always use your target. See the recommendations at the end of 154 | the page about [the smallest `no_std` program][no_std-program]. However, you'll currently have to 155 | use the flag `-Z build-std=core` as that option is unstable. 156 | 157 | [`xargo`]: https://github.com/japaric/xargo 158 | [`cargo-xbuild`]: https://github.com/rust-osdev/cargo-xbuild 159 | [no_std-program]: ./smallest-no-std.md 160 | 161 | ### Build additional built-in crates 162 | 163 | When using cargo's `build-std` feature, you can choose which crates to compile in. By default, when 164 | only passing `-Z build-std`, `std`, `core`, and `alloc` are compiled. However, you may want to 165 | exclude `std` when compiling for bare-metal. To do so, specify the crated you'd like after 166 | `build-std`. For example, to include `core` and `alloc`, pass `-Z build-std=core,alloc`. 167 | 168 | ## Troubleshooting 169 | 170 | ### language item required, but not found: `eh_personality` 171 | 172 | Either add `"panic-strategy": "abort"` to your target file, or define an [eh_personality] function. 173 | Alternatively, [tell Cargo to ignore it][eh_personality]. 174 | 175 | ### undefined reference to `__sync_val_compare_and_swap_#` 176 | 177 | Rust thinks that your target has atomic instructions, but LLVM doesn't. Go back to the step about 178 | [configuring atomics][fill-target-file]. You will need to reduce the number in `max-atomic-width`. 179 | See [#58500] for more details. 180 | 181 | [fill-target-file]: #fill-the-target-file 182 | [#58500]: https://github.com/rust-lang/rust/issues/58500 183 | 184 | ### could not find `sync` in `alloc` 185 | 186 | Similar to the above case, Rust doesn't think that you have atomics. You must implement them 187 | yourself or [tell Rust that you have atomic instructions][fill-target-file]. 188 | 189 | ### multiple definition of `__(something)` 190 | 191 | You're likely linking your Rust program with code built from another language, and the other 192 | language includes compiler built-ins that Rust also creates. To fix this, you'll need to tell your 193 | linker to allow multiple definitions. If using gcc, you may add: 194 | 195 | ``` json 196 | "post-link-args": { 197 | "gcc": [ 198 | "-Wl,--allow-multiple-definition" 199 | ] 200 | } 201 | ``` 202 | 203 | ### error adding symbols: file format not recognized 204 | 205 | Switch to cargo's `build-std` feature and update your compiler. This [was a bug][#8239] introduced 206 | for a few compiler builds that tried to pass in internal Rust object to an external linker. 207 | 208 | [#8239]: https://github.com/rust-lang/cargo/issues/8239 209 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/memory-layout.md: -------------------------------------------------------------------------------- 1 | # Memory layout 2 | 3 | The next step is to ensure the program has the right memory layout so that the target system will be 4 | able to execute it. In our example, we'll be working with a virtual Cortex-M3 microcontroller: the 5 | [LM3S6965]. Our program will be the only process running on the device so it must also take care of 6 | initializing the device. 7 | 8 | ## Background information 9 | 10 | [LM3S6965]: http://www.ti.com/product/LM3S6965 11 | 12 | Cortex-M devices require a [vector table] to be present at the start of their [code memory region]. 13 | The vector table is an array of pointers; the first two pointers are required to boot the device, 14 | the rest of the pointers are related to exceptions. We'll ignore them for now. 15 | 16 | [code memory region]: https://developer.arm.com/docs/dui0552/latest/the-cortex-m3-processor/memory-model 17 | [vector table]: https://developer.arm.com/docs/dui0552/latest/the-cortex-m3-processor/exception-model/vector-table 18 | 19 | Linkers decide the final memory layout of programs, but we can use [linker scripts] to have some 20 | control over it. The control granularity that linker scripts give us over the layout 21 | is at the level of *sections*. A section is a collection of *symbols* laid out in contiguous memory. 22 | Symbols, in turn, can be data (a static variable), or instructions (a Rust function). 23 | 24 | [linker scripts]: https://sourceware.org/binutils/docs/ld/Scripts.html 25 | 26 | Every symbol has a name assigned by the compiler. As of Rust 1.28 , the names that the Rust compiler 27 | assigns to symbols are of the form: `_ZN5krate6module8function17he1dfc17c86fe16daE`, which demangles to 28 | `krate::module::function::he1dfc17c86fe16da` where `krate::module::function` is the path of the 29 | function or variable and `he1dfc17c86fe16da` is some sort of hash. The Rust compiler will place each 30 | symbol into its own unique section; for example the symbol mentioned before will be placed in a 31 | section named `.text._ZN5krate6module8function17he1dfc17c86fe16daE`. 32 | 33 | These compiler generated symbol and section names are not guaranteed to remain constant across 34 | different releases of the Rust compiler. However, the language lets us control symbol names and 35 | section placement via these attributes: 36 | 37 | - `#[unsafe(export_name = "foo")]` sets the symbol name to `foo`. 38 | - `#[unsafe(no_mangle)]` means: use the function or variable name (not its full path) as its symbol name. 39 | `#[unsafe(no_mangle)] fn bar()` will produce a symbol named `bar`. 40 | - `#[unsafe(link_section = ".bar")]` places the symbol in a section named `.bar`. 41 | 42 | With these attributes, we can expose a stable ABI of the program and use it in the linker script. 43 | Each of these attributes has safety guarantees that must be upheld in your program, namely: 44 | - `#[unsafe(no_mangle)]` fixes the symbol name, regardless of the crate, module, variable or 45 | function name and arguments. Using this attribute, it is possible to create multiple symbols with 46 | the same name, leading to undefined behavior. The user must guarantee that the symbol does not collide 47 | with others. 48 | - `#[unsafe(export_name = "foo")]` also fixes the symbol name. The user must guarantee that 49 | the chosen symbol name (in this case `foo`) does not collide with any other symbol in the program. 50 | - `#[unsafe(link_section = ".bar")]` may place data and code into sections of memory not expecting them, 51 | such as mutable data into read-only areas, or memory regions that do not exist on the target device. 52 | 53 | ## The Rust side 54 | 55 | As mentioned above, for Cortex-M devices, we need to populate the first two entries of the 56 | vector table. The first one, the initial value for the stack pointer, can be populated using 57 | only the linker script. The second one, the reset vector, needs to be created in Rust code 58 | and placed correctly using the linker script. 59 | 60 | The reset vector is a pointer into the reset handler. The reset handler is the function that the 61 | device will execute after a system reset, or after it powers up for the first time. The reset 62 | handler is always the first stack frame in the hardware call stack; returning from it is undefined 63 | behavior as there's no other stack frame to return to. We can enforce that the reset handler never 64 | returns by making it a divergent function, which is a function with signature `fn(/* .. */) -> !`. 65 | 66 | ``` rust 67 | {{#include ../ci/memory-layout/src/main.rs:7:19}} 68 | ``` 69 | 70 | The hardware expects a certain format here, to which we adhere by using `extern "C"` to tell the 71 | compiler to lower the function using the C ABI, instead of the Rust ABI, which is unstable. 72 | 73 | To refer to the reset handler and reset vector from the linker script, we need them to have a stable 74 | symbol name so we use `#[unsafe(no_mangle)]`. We need fine control over the location of `RESET_VECTOR`, so we 75 | place it in a known section, `.vector_table.reset_vector`. The exact location of the reset handler 76 | itself, `Reset`, is not important. We just stick to the default compiler generated section. 77 | 78 | The linker will ignore symbols with internal linkage (also known as internal symbols) while traversing 79 | the list of input object files, so we need our two symbols to have external linkage. The only way to 80 | make a symbol external in Rust is to make its corresponding item public (`pub`) and *reachable* (no 81 | private module between the item and the root of the crate). 82 | 83 | ## The linker script side 84 | 85 | A minimal linker script that places the vector table in the correct location is shown below. Let's 86 | walk through it. 87 | 88 | ``` console 89 | $ cat link.x 90 | ``` 91 | 92 | ``` text 93 | {{#include ../ci/memory-layout/link.x}} 94 | ``` 95 | 96 | ### `MEMORY` 97 | 98 | This section of the linker script describes the location and size of blocks of memory in the target. 99 | Two memory blocks are defined: `FLASH` and `RAM`; they correspond to the physical memory available 100 | in the target. The values used here correspond to the LM3S6965 microcontroller. 101 | 102 | ### `ENTRY` 103 | 104 | Here we indicate to the linker that the reset handler, whose symbol name is `Reset`, is the 105 | *entry point* of the program. Linkers aggressively discard unused sections. Linkers consider the 106 | entry point and functions called from it as *used* so they won't discard them. Without this line, 107 | the linker would discard the `Reset` function and all subsequent functions called from it. 108 | 109 | ### `EXTERN` 110 | 111 | Linkers are lazy; they will stop looking into the input object files once they have found all the 112 | symbols that are recursively referenced from the entry point. `EXTERN` forces the linker to look 113 | for `EXTERN`'s argument even after all other referenced symbols have been found. As a rule of thumb, 114 | if you need a symbol that's not called from the entry point to always be present in the output binary, 115 | you should use `EXTERN` in conjunction with `KEEP`. 116 | 117 | ### `SECTIONS` 118 | 119 | This part describes how sections in the input object files (also known as *input sections*) are to be arranged 120 | in the sections of the output object file (also known as output sections) or if they should be discarded. Here 121 | we define two output sections: 122 | 123 | ``` text 124 | .vector_table ORIGIN(FLASH) : { /* .. */ } > FLASH 125 | ``` 126 | 127 | `.vector_table` contains the vector table and is located at the start of `FLASH` memory. 128 | 129 | ``` text 130 | .text : { /* .. */ } > FLASH 131 | ``` 132 | 133 | `.text` contains the program subroutines and is located somewhere in `FLASH`. Its start 134 | address is not specified, but the linker will place it after the previous output section, 135 | `.vector_table`. 136 | 137 | The output `.vector_table` section contains: 138 | 139 | ``` text 140 | {{#include ../ci/memory-layout/link.x:18:19}} 141 | ``` 142 | 143 | We'll place the (call) stack at the end of RAM (the stack is *full descending*; it grows towards 144 | smaller addresses) so the end address of RAM will be used as the initial Stack Pointer (SP) value. 145 | That address is computed in the linker script itself using the information we entered for the `RAM` 146 | memory block. 147 | 148 | ``` 149 | {{#include ../ci/memory-layout/link.x:21:22}} 150 | ``` 151 | 152 | Next, we use `KEEP` to force the linker to insert all input sections named 153 | `.vector_table.reset_vector` right after the initial SP value. The only symbol located in that 154 | section is `RESET_VECTOR`, so this will effectively place `RESET_VECTOR` second in the vector table. 155 | 156 | The output `.text` section contains: 157 | 158 | ``` text 159 | {{#include ../ci/memory-layout/link.x:27}} 160 | ``` 161 | 162 | This includes all the input sections named `.text` and `.text.*`. Note that we don't use `KEEP` 163 | here to let the linker discard unused sections. 164 | 165 | Finally, we use the special `/DISCARD/` section to discard 166 | 167 | ``` text 168 | {{#include ../ci/memory-layout/link.x:32}} 169 | ``` 170 | 171 | input sections named `.ARM.exidx.*`. These sections are related to exception handling but we are not 172 | doing stack unwinding on panics and they take up space in Flash memory, so we just discard them. 173 | 174 | ## Putting it all together 175 | 176 | Now we can link the application. For reference, here's the complete Rust program: 177 | 178 | ``` rust 179 | {{#include ../ci/memory-layout/src/main.rs}} 180 | ``` 181 | 182 | We have to tweak the linker process to make it use our linker script. This is done 183 | passing the `-C link-arg` flag to `rustc`. This can be done with `cargo-rustc` or 184 | `cargo-build`. 185 | 186 | **IMPORTANT**: Make sure you have the `.cargo/config.toml` file that was added at the 187 | end of the last section before running this command. 188 | 189 | Using the `cargo-rustc` subcommand: 190 | 191 | ``` console 192 | $ cargo rustc -- -C link-arg=-Tlink.x 193 | ``` 194 | 195 | Or you can set the rustflags in `.cargo/config.toml` and continue using the 196 | `cargo-build` subcommand. We'll do the latter because it better integrates with 197 | `cargo-binutils`. 198 | 199 | ``` console 200 | # modify .cargo/config.toml so it has these contents 201 | $ cat .cargo/config.toml 202 | ``` 203 | 204 | ``` toml 205 | {{#include ../ci/memory-layout/.cargo/config.toml}} 206 | ``` 207 | 208 | The `[target.thumbv7m-none-eabi]` part says that these flags will only be used 209 | when cross compiling to that target. 210 | 211 | ## Inspecting it 212 | 213 | Now let's inspect the output binary to confirm the memory layout looks the way we want 214 | (this requires [`cargo-binutils`](https://github.com/rust-embedded/cargo-binutils#readme)): 215 | 216 | ``` console 217 | $ cargo objdump --bin app -- -d --no-show-raw-insn 218 | ``` 219 | 220 | ``` text 221 | {{#include ../ci/memory-layout/app.text.objdump}} 222 | ``` 223 | 224 | This is the disassembly of the `.text` section. We see that the reset handler, named `Reset`, is 225 | located at address `0x8`. 226 | 227 | ``` console 228 | $ cargo objdump --bin app -- -s --section .vector_table 229 | ``` 230 | 231 | ``` text 232 | {{#include ../ci/memory-layout/app.vector_table.objdump}} 233 | ``` 234 | 235 | This shows the contents of the `.vector_table` section. We can see that the section starts at 236 | address `0x0` and that the first word of the section is `0x2001_0000` (the `objdump` output is in 237 | little endian format). This is the initial SP value and matches the end address of RAM. The second 238 | word is `0x9`; this is the *thumb mode* address of the reset handler. When a function is to be 239 | executed in thumb mode the first bit of its address is set to 1. 240 | 241 | ## Testing it 242 | 243 | This program is a valid LM3S6965 program; we can execute it in a virtual microcontroller (QEMU) to 244 | test it out. 245 | 246 | ``` console 247 | $ # this program will block 248 | $ qemu-system-arm \ 249 | -cpu cortex-m3 \ 250 | -machine lm3s6965evb \ 251 | -gdb tcp::3333 \ 252 | -S \ 253 | -nographic \ 254 | -kernel target/thumbv7m-none-eabi/debug/app 255 | ``` 256 | 257 | ``` console 258 | $ # on a different terminal 259 | $ arm-none-eabi-gdb -q target/thumbv7m-none-eabi/debug/app 260 | Reading symbols from target/thumbv7m-none-eabi/debug/app...done. 261 | 262 | (gdb) target remote :3333 263 | Remote debugging using :3333 264 | Reset () at src/main.rs:8 265 | 8 pub unsafe extern "C" fn Reset() -> ! { 266 | 267 | (gdb) # the SP has the initial value we programmed in the vector table 268 | (gdb) print/x $sp 269 | $1 = 0x20010000 270 | 271 | (gdb) step 272 | 9 let _x = 42; 273 | 274 | (gdb) step 275 | 12 loop {} 276 | 277 | (gdb) # next we inspect the stack variable `_x` 278 | (gdb) print _x 279 | $2 = 42 280 | 281 | (gdb) print &_x 282 | $3 = (i32 *) 0x2000fff4 283 | 284 | (gdb) quit 285 | ``` 286 | -------------------------------------------------------------------------------- /src/soc-support.md: -------------------------------------------------------------------------------- 1 | # Guide for silicon vendors to enable Rust support for their SoCs 2 | 3 | ## Introduction 4 | 5 | Rust has emerged as a powerful and safety-focused programming language, gaining 6 | traction among embedded developers. Silicon vendors who wish to enable Rust 7 | support for their System-on-Chip (SoC) products can benefit from this trend by 8 | attracting a growing community of Rust developers. 9 | 10 | This guide aims to help silicon vendors enable Rust support, either 11 | independently or by empowering third-party developers. It outlines the 12 | essential resources, tasks, and priorities required to foster a robust Rust 13 | ecosystem around their System-on-Chip (SoC). 14 | 15 | **Note:** For assistance with strategy in engaging with the community, we 16 | recommend reaching out to the Rust Embedded Working Group (REWG) leads. They 17 | can provide valuable insights and support to help you navigate the process 18 | effectively. 19 | 20 | ## Essential resources 21 | 22 | ### Documentation 23 | 24 | Detailed documentation is essential for effective development and debugging. It 25 | enables developers to comprehend the System-on-Chip (SoC), including its memory 26 | map, peripherals, interrupt handling, low-power modes, etc. Ensure that the 27 | documentation covers all hardware aspects comprehensively, from register-level 28 | details to system-level interactions. The documentation should be publicly 29 | available; in cases where public availability is not feasible, any 30 | non-disclosure agreement (NDA) must permit the publication of open-source code 31 | derived from it. 32 | 33 | ### Register description files 34 | 35 | Register description files are used to generate Peripheral Access Crates 36 | (PACs). The most common format for these files is SVD 37 | ([System View Description](https://open-cmsis-pack.github.io/svd-spec)). Rust 38 | developers have often encountered issues with SVD files, so it is crucial to 39 | provide clear contact information for reporting any discrepancies or problems. 40 | Up-to-date SVD files ensure that the community can collaborate effectively to 41 | resolve issues and improve the quality of the PACs. 42 | 43 | ### Flash Algorithms 44 | 45 | [Flash Algorithms](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/flashAlgorithm.html) 46 | are integrated with debugging tools like [probe-rs](https://probe.rs). They 47 | facilitate and speed up firmware programming and debugging, streamlining 48 | development workflows. Providing well-supported FlashAlgos will enhance the 49 | integration with these tools and improve the overall developer experience. 50 | Flash Algorithms can be authored in Rust (see 51 | [flash-algorithm-template](https://github.com/probe-rs/flash-algorithm-template) 52 | for an template to write one). 53 | 54 | ### Vendor tooling 55 | 56 | Some System-on-Chip (SoC) devices require custom tools for generating images or 57 | flashing them onto the device. It is beneficial to provide these tools in an 58 | open-source manner, fostering community contributions and accelerating 59 | ecosystem growth. Open-sourcing vendor tooling enables third-party developers 60 | to extend and enhance the toolchain, ensuring improved compatibility with the 61 | broader Embedded Rust ecosystem. 62 | 63 | ### Contact information 64 | 65 | Providing contact information is vital for addressing maintainer queries and 66 | issues related to register description files or other resources. The use of a 67 | public issue tracking system (like GitHub Issues) for reporting and tracking 68 | problems might help. Actively engage with the community through forums, 69 | discussions, and updates to build trust and collaboration. 70 | 71 | ## Maintaining PAC and HAL crates 72 | 73 | Peripheral Access Crates (PACs) and Hardware Abstraction Layer (HAL) crates are 74 | at the core of enabling Rust support. 75 | 76 | ### Generate and maintain PACs 77 | 78 | Multiple tools such as [svd2rust](https://crates.io/crates/svd2rust), 79 | [chiptool](https://github.com/embassy-rs/chiptool), 80 | [raltool](https://github.com/imxrt-rs/imxrt-ral/tree/master/raltool), and 81 | [svd2pac](https://github.com/Infineon/svd2pac) automate the generation of PACs 82 | from register description files. Each tool has its strengths, and selecting the 83 | right one depends on the requirements and the complexity of the hardware. 84 | 85 | ### Develop and maintain HAL crates 86 | 87 | Implement [embedded-hal](https://crates.io/crates/embedded-hal), 88 | [embedded-hal-async](https://crates.io/crates/embedded-hal-async), and 89 | [embedded-io](https://crates.io/crates/embedded-io) traits in your HAL crates. 90 | Adhering to these traits ensures compatibility across the Embedded Rust 91 | ecosystem, enhancing interoperability. It is an essential goal that HALs use 92 | Rust code rather than wrapping existing C code. An incremental porting 93 | strategy, where all core functionality is implemented in Rust, but C with Rust 94 | bindings is used for complex drivers, is acceptable, allowing for gradual 95 | adoption and community contributions. 96 | 97 | Start with essential peripherals (clock, timer, GPIO) and expand progressively 98 | (I2C, SPI, UART, etc.) based on community feedback. Release early and often to 99 | engage the community and gather valuable insights for further development. 100 | 101 | ### Common recommendations 102 | 103 | - Ensure that crates are compatible with `no_std` environments, which are 104 | common in embedded systems without an operating system. Functionality that 105 | needs `alloc` or `std` can be included when gated with Cargo 106 | [features](https://doc.rust-lang.org/cargo/reference/features.html). 107 | - Make your crates available on [crates.io](https://crates.io) to maximize 108 | visibility and ease of use for developers. 109 | - Use [semantic versioning](https://semver.org) to maintain consistency and 110 | predictability in your releases. 111 | - Prefer licenses like Apache 2.0 and MIT for their permissive nature, which 112 | encourages broader adoption and collaboration. 113 | 114 | ### Issue tracking 115 | 116 | Effective issue tracking is crucial for maintaining a healthy and collaborative 117 | ecosystem. Discuss triaging, labeling, and community involvement in issue 118 | resolution. Implement transparent processes for: 119 | 120 | - Triage and prioritize issues based on severity and impact. 121 | - Use labels to categorize issues (e.g., bugs, feature requests). 122 | - Encourage community members to contribute to resolving issues by providing 123 | feedback or submitting pull requests (PRs). 124 | 125 | ### Facilitate debugging and testing 126 | 127 | The Embedded Rust ecosystem offers various tools used for debugging 128 | and testing, with [probe-rs](https://probe.rs) being one of the most widely 129 | used. [probe-rs](https://probe.rs) supports a wide range 130 | of target architectures, debug interfaces, and debug probe protocols. 131 | Combined with debug-based facilities like 132 | [defmt-rtt](https://crates.io/crates/defmt-rtt), which provide logging 133 | capabilities for embedded systems, these tools form a robust foundation for 134 | development. 135 | 136 | Thorough testing ensures hardware-software reliability, and leveraging these 137 | tools can significantly enhance development workflows. 138 | 139 | ## Nice-to-have features for enhanced ecosystem support 140 | 141 | ### Examples 142 | 143 | Including some basic examples as part of the HAL is essential for helping 144 | developers get started. These examples should demonstrate key functionalities, 145 | such as initializing peripherals or handling interrupts. They serve as 146 | practical starting points and learning aids. 147 | 148 | ### BSP (Board Support Package) crates 149 | 150 | BSP crates are relevant when you need to provide board-specific configurations 151 | and initializations. Unlike HALs, which focus on hardware abstraction, BSPs 152 | handle the integration of multiple components for a specific board. Separation 153 | in BSP and HAL crates offers a layered approach, making it easier for developers 154 | to build applications targeting a particular hardware board. 155 | 156 | ### Project templates 157 | 158 | Project templates are boilerplate code structures that provide a starting point 159 | for new projects. They include prevalent configurations, dependencies, and 160 | setup steps, saving developers time and reducing the learning curve. Examples 161 | of project templates include bare-metal (using the HAL without any framework), 162 | Embassy, RTIC, and others. 163 | 164 | ### Integration with popular IDEs and tools 165 | 166 | Offer guides on setting up development environments for Embedded Rust projects 167 | with popular tools such as: 168 | 169 | - [rust-analyzer](https://rust-analyzer.github.io): for Rust syntax 170 | highlighting and error checking. 171 | - [probe-rs](https://probe.rs): for flashing and debugging firmware. 172 | - [defmt](https://crates.io/crates/defmt): a logging framework optimized for 173 | embedded systems, including a test harness called 174 | [defmt-test](https://crates.io/crates/defmt-test). 175 | 176 | Providing setup instructions for these tools will help developers integrate 177 | them into their workflows, enhancing productivity and collaboration. 178 | 179 | ## Suggested flow for adding SoC Support 180 | 181 | - A preliminary requirement of this flow is that the Rust toolchain includes 182 | a [target](https://doc.rust-lang.org/rustc/platform-support.html) that 183 | matches the System-on-Chip (SoC). If this not the case the solution can be as 184 | simple as adding a 185 | [custom target](https://doc.rust-lang.org/rustc/targets/custom.html) or as 186 | difficult as adding support for the underlying architecture to 187 | [LLVM](https://llvm.org). 188 | - Before starting from scratch, check if any existing community efforts for 189 | already exist (e.g. checking on 190 | [awesome-embedded-rust](https://github.com/rust-embedded/awesome-embedded-rust) 191 | or joining the 192 | [Rust Embedded Matrix room](https://matrix.to/#/#rust-embedded:matrix.org)). 193 | This could save significant development time. 194 | - Ensure that your target is supported by [probe-rs](https://probe.rs). The 195 | ability to debug using SWD or JTAG is highly beneficial. Support for flashing 196 | programming can be added with a Flash Algorithm (e.g. from a CMSIS-Pack or 197 | [writing one in Rust](https://github.com/probe-rs/flash-algorithm-template)). 198 | - Generate Peripheral Access Crates (PACs) from register description files, 199 | with SVD (System View Description) being the most common and preferred 200 | format. Alternatives include extracting the register descriptions from PDF 201 | datasheets or C header files, but this can be much more labor-intensive. 202 | - Create a minimal project containing the PAC and/or an empty Hardware 203 | Abstraction Layer (HAL). The goal is to get a minimal working binary that 204 | either blinks an LED or sends messages through 205 | [defmt-rtt](https://crates.io/crates/defmt-rtt) using only the PAC crate or 206 | with a minimal HAL. This will require a linker script and exercise the 207 | availability to flash and debug programs. Additional crates for core 208 | registers and peripheral, or startup code and interrupt handling will also be 209 | required (see [Cortex-M](https://github.com/rust-embedded/cortex-m) or 210 | [RISC-V](https://github.com/rust-embedded/riscv)). 211 | - Add core functionality in HAL: clocks, timers, interrupts. Verify the 212 | accuracy of timers and interrupts with external tools like a logic analyzer 213 | or an oscilloscope. 214 | - Progressively add drivers for other peripherals (GPIO, I2C, SPI, UART, etc.) 215 | implementing standard Rust Embedded traits 216 | ([embedded-hal](https://crates.io/crates/embedded-hal), 217 | [embedded-hal-async](https://crates.io/crates/embedded-hal-async), 218 | [embedded-io](https://crates.io/crates/embedded-io)). 219 | - Release early and often in the beginning, engage with the community to get 220 | feedback. 221 | 222 | ## Conclusion 223 | 224 | Enabling Rust support for your SoC opens the door to a vibrant community of 225 | developers who value safety, performance, and reliability. By providing 226 | essential resources, maintaining high-quality PACs and HAL crates, and 227 | fostering a supportive ecosystem, you empower both internal teams and 228 | third-party developers to unlock the full potential of your hardware. 229 | 230 | As the Rust embedded ecosystem continues to grow, embracing these practices 231 | positions your company at the forefront of this movement, attracting developers 232 | passionate about building robust and innovative systems. Encourage ongoing 233 | engagement with the Rust community to stay updated on best practices and tools, 234 | ensuring your System-on-Chip (SoC) remains a preferred choice for Rust 235 | developers. 236 | 237 | By following this guide, you can create a comprehensive and supportive 238 | environment that not only enables Rust support but also nurtures a thriving 239 | developer ecosystem around your products. 240 | -------------------------------------------------------------------------------- /src/compiler-support.md: -------------------------------------------------------------------------------- 1 | # A note on compiler support 2 | 3 | This book makes use of a built-in *compiler* target, the `thumbv7m-none-eabi`, for which the Rust 4 | team distributes a `rust-std` component, which is a pre-compiled collection of crates like [`core`] 5 | and [`std`]. 6 | 7 | [`core`]: https://doc.rust-lang.org/core/index.html 8 | [`std`]: https://doc.rust-lang.org/std/index.html 9 | 10 | If you want to attempt replicating the contents of this book for a different target architecture, 11 | you need to take into account the different levels of support that Rust provides for (compilation) 12 | targets. 13 | 14 | ## LLVM support 15 | 16 | As of Rust 1.28, the official Rust compiler, `rustc`, uses LLVM for (machine) code generation. The 17 | minimal level of support Rust provides for an architecture is having its LLVM backend enabled in 18 | `rustc`. You can see all the architectures that `rustc` supports, through LLVM, by running the 19 | following command: 20 | 21 | ``` console 22 | $ # you need to have `cargo-binutils` installed to run this command 23 | $ cargo objdump -- --version 24 | LLVM (http://llvm.org/): 25 | LLVM version 7.0.0svn 26 | Optimized build. 27 | Default target: x86_64-unknown-linux-gnu 28 | Host CPU: skylake 29 | 30 | Registered Targets: 31 | aarch64 - AArch64 (little endian) 32 | aarch64_be - AArch64 (big endian) 33 | arm - ARM 34 | arm64 - ARM64 (little endian) 35 | armeb - ARM (big endian) 36 | hexagon - Hexagon 37 | mips - Mips 38 | mips64 - Mips64 [experimental] 39 | mips64el - Mips64el [experimental] 40 | mipsel - Mipsel 41 | msp430 - MSP430 [experimental] 42 | nvptx - NVIDIA PTX 32-bit 43 | nvptx64 - NVIDIA PTX 64-bit 44 | ppc32 - PowerPC 32 45 | ppc64 - PowerPC 64 46 | ppc64le - PowerPC 64 LE 47 | sparc - Sparc 48 | sparcel - Sparc LE 49 | sparcv9 - Sparc V9 50 | systemz - SystemZ 51 | thumb - Thumb 52 | thumbeb - Thumb (big endian) 53 | wasm32 - WebAssembly 32-bit 54 | wasm64 - WebAssembly 64-bit 55 | x86 - 32-bit X86: Pentium-Pro and above 56 | x86-64 - 64-bit X86: EM64T and AMD64 57 | ``` 58 | 59 | If LLVM supports the architecture you are interested in, but `rustc` is built with the backend 60 | disabled (which is the case of AVR as of Rust 1.28), then you will need to modify the Rust source 61 | enabling it. The first two commits of PR [rust-lang/rust#52787] give you an idea of the required 62 | changes. 63 | 64 | [rust-lang/rust#52787]: https://github.com/rust-lang/rust/pull/52787 65 | 66 | On the other hand, if LLVM doesn't support the architecture, but a fork of LLVM does, you will have 67 | to replace the original version of LLVM with the fork before building `rustc`. The Rust build system 68 | allows this and in principle it should just require changing the `llvm` submodule to point to the 69 | fork. 70 | 71 | If your target architecture is only supported by some vendor-provided GCC, you have the option of 72 | using [`mrustc`], an unofficial Rust compiler, to translate your Rust program into C code and then 73 | compile that using GCC. 74 | 75 | [`mrustc`]: https://github.com/thepowersgang/mrustc 76 | 77 | ## Built-in target 78 | 79 | A compilation target is more than just its architecture. Each target has a [specification] 80 | associated to it that describes, among other things, its architecture, its operating system and the 81 | default linker. 82 | 83 | [specification]: https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md 84 | 85 | The Rust compiler knows about several targets. These are *built into* the compiler and can be listed 86 | by running the following command: 87 | 88 | ``` console 89 | $ rustc --print target-list | column 90 | aarch64-fuchsia mipsisa32r6el-unknown-linux-gnu 91 | aarch64-linux-android mipsisa64r6-unknown-linux-gnuabi64 92 | aarch64-pc-windows-msvc mipsisa64r6el-unknown-linux-gnuabi64 93 | aarch64-unknown-cloudabi msp430-none-elf 94 | aarch64-unknown-freebsd nvptx64-nvidia-cuda 95 | aarch64-unknown-hermit powerpc-unknown-linux-gnu 96 | aarch64-unknown-linux-gnu powerpc-unknown-linux-gnuspe 97 | aarch64-unknown-linux-musl powerpc-unknown-linux-musl 98 | aarch64-unknown-netbsd powerpc-unknown-netbsd 99 | aarch64-unknown-none powerpc-wrs-vxworks 100 | aarch64-unknown-none-softfloat powerpc-wrs-vxworks-spe 101 | aarch64-unknown-openbsd powerpc64-unknown-freebsd 102 | aarch64-unknown-redox powerpc64-unknown-linux-gnu 103 | aarch64-uwp-windows-msvc powerpc64-unknown-linux-musl 104 | aarch64-wrs-vxworks powerpc64-wrs-vxworks 105 | arm-linux-androideabi powerpc64le-unknown-linux-gnu 106 | arm-unknown-linux-gnueabi powerpc64le-unknown-linux-musl 107 | arm-unknown-linux-gnueabihf riscv32i-unknown-none-elf 108 | arm-unknown-linux-musleabi riscv32imac-unknown-none-elf 109 | arm-unknown-linux-musleabihf riscv32imc-unknown-none-elf 110 | armebv7r-none-eabi riscv64gc-unknown-linux-gnu 111 | armebv7r-none-eabihf riscv64gc-unknown-none-elf 112 | armv4t-unknown-linux-gnueabi riscv64imac-unknown-none-elf 113 | armv5te-unknown-linux-gnueabi s390x-unknown-linux-gnu 114 | armv5te-unknown-linux-musleabi sparc-unknown-linux-gnu 115 | armv6-unknown-freebsd sparc64-unknown-linux-gnu 116 | armv6-unknown-netbsd-eabihf sparc64-unknown-netbsd 117 | armv7-linux-androideabi sparc64-unknown-openbsd 118 | armv7-unknown-cloudabi-eabihf sparcv9-sun-solaris 119 | armv7-unknown-freebsd thumbv6m-none-eabi 120 | armv7-unknown-linux-gnueabi thumbv7a-pc-windows-msvc 121 | armv7-unknown-linux-gnueabihf thumbv7em-none-eabi 122 | armv7-unknown-linux-musleabi thumbv7em-none-eabihf 123 | armv7-unknown-linux-musleabihf thumbv7m-none-eabi 124 | armv7-unknown-netbsd-eabihf thumbv7neon-linux-androideabi 125 | armv7-wrs-vxworks-eabihf thumbv7neon-unknown-linux-gnueabihf 126 | armv7a-none-eabi thumbv7neon-unknown-linux-musleabihf 127 | armv7a-none-eabihf thumbv8m.base-none-eabi 128 | armv7r-none-eabi thumbv8m.main-none-eabi 129 | armv7r-none-eabihf thumbv8m.main-none-eabihf 130 | asmjs-unknown-emscripten wasm32-unknown-emscripten 131 | hexagon-unknown-linux-musl wasm32-unknown-unknown 132 | i586-pc-windows-msvc wasm32-wasi 133 | i586-unknown-linux-gnu x86_64-apple-darwin 134 | i586-unknown-linux-musl x86_64-fortanix-unknown-sgx 135 | i686-apple-darwin x86_64-fuchsia 136 | i686-linux-android x86_64-linux-android 137 | i686-pc-windows-gnu x86_64-linux-kernel 138 | i686-pc-windows-msvc x86_64-pc-solaris 139 | i686-unknown-cloudabi x86_64-pc-windows-gnu 140 | i686-unknown-freebsd x86_64-pc-windows-msvc 141 | i686-unknown-haiku x86_64-rumprun-netbsd 142 | i686-unknown-linux-gnu x86_64-sun-solaris 143 | i686-unknown-linux-musl x86_64-unknown-cloudabi 144 | i686-unknown-netbsd x86_64-unknown-dragonfly 145 | i686-unknown-openbsd x86_64-unknown-freebsd 146 | i686-unknown-uefi x86_64-unknown-haiku 147 | i686-uwp-windows-gnu x86_64-unknown-hermit 148 | i686-uwp-windows-msvc x86_64-unknown-hermit-kernel 149 | i686-wrs-vxworks x86_64-unknown-illumos 150 | mips-unknown-linux-gnu x86_64-unknown-l4re-uclibc 151 | mips-unknown-linux-musl x86_64-unknown-linux-gnu 152 | mips-unknown-linux-uclibc x86_64-unknown-linux-gnux32 153 | mips64-unknown-linux-gnuabi64 x86_64-unknown-linux-musl 154 | mips64-unknown-linux-muslabi64 x86_64-unknown-netbsd 155 | mips64el-unknown-linux-gnuabi64 x86_64-unknown-openbsd 156 | mips64el-unknown-linux-muslabi64 x86_64-unknown-redox 157 | mipsel-unknown-linux-gnu x86_64-unknown-uefi 158 | mipsel-unknown-linux-musl x86_64-uwp-windows-gnu 159 | mipsel-unknown-linux-uclibc x86_64-uwp-windows-msvc 160 | mipsisa32r6-unknown-linux-gnu x86_64-wrs-vxworks 161 | ``` 162 | 163 | You can print the specification of one of these targets using the following command: 164 | 165 | ``` console 166 | $ rustc +nightly -Z unstable-options --print target-spec-json --target thumbv7m-none-eabi 167 | { 168 | "abi-blacklist": [ 169 | "stdcall", 170 | "fastcall", 171 | "vectorcall", 172 | "thiscall", 173 | "win64", 174 | "sysv64" 175 | ], 176 | "arch": "arm", 177 | "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", 178 | "emit-debug-gdb-scripts": false, 179 | "env": "", 180 | "executables": true, 181 | "is-builtin": true, 182 | "linker": "arm-none-eabi-gcc", 183 | "linker-flavor": "gcc", 184 | "llvm-target": "thumbv7m-none-eabi", 185 | "max-atomic-width": 32, 186 | "os": "none", 187 | "panic-strategy": "abort", 188 | "relocation-model": "static", 189 | "target-c-int-width": "32", 190 | "target-endian": "little", 191 | "target-pointer-width": "32", 192 | "vendor": "" 193 | } 194 | ``` 195 | 196 | If none of these built-in targets seems appropriate for your target system, you'll have to create a 197 | custom target by writing your own target specification file in JSON format which is described in the 198 | [next section][custom-target]. 199 | 200 | [custom-target]: ./custom-target.md 201 | 202 | ## `rust-std` component 203 | 204 | For some of the built-in target the Rust team distributes `rust-std` components via `rustup`. This 205 | component is a collection of pre-compiled crates like `core` and `std`, and it's required for cross 206 | compilation. 207 | 208 | You can find the list of targets that have a `rust-std` component available via `rustup` by running 209 | the following command: 210 | 211 | ``` console 212 | $ rustup target list | column 213 | aarch64-apple-ios mipsel-unknown-linux-musl 214 | aarch64-fuchsia nvptx64-nvidia-cuda 215 | aarch64-linux-android powerpc-unknown-linux-gnu 216 | aarch64-pc-windows-msvc powerpc64-unknown-linux-gnu 217 | aarch64-unknown-linux-gnu powerpc64le-unknown-linux-gnu 218 | aarch64-unknown-linux-musl riscv32i-unknown-none-elf 219 | aarch64-unknown-none riscv32imac-unknown-none-elf 220 | aarch64-unknown-none-softfloat riscv32imc-unknown-none-elf 221 | arm-linux-androideabi riscv64gc-unknown-linux-gnu 222 | arm-unknown-linux-gnueabi riscv64gc-unknown-none-elf 223 | arm-unknown-linux-gnueabihf riscv64imac-unknown-none-elf 224 | arm-unknown-linux-musleabi s390x-unknown-linux-gnu 225 | arm-unknown-linux-musleabihf sparc64-unknown-linux-gnu 226 | armebv7r-none-eabi sparcv9-sun-solaris 227 | armebv7r-none-eabihf thumbv6m-none-eabi 228 | armv5te-unknown-linux-gnueabi thumbv7em-none-eabi 229 | armv5te-unknown-linux-musleabi thumbv7em-none-eabihf 230 | armv7-linux-androideabi thumbv7m-none-eabi 231 | armv7-unknown-linux-gnueabi thumbv7neon-linux-androideabi 232 | armv7-unknown-linux-gnueabihf thumbv7neon-unknown-linux-gnueabihf 233 | armv7-unknown-linux-musleabi thumbv8m.base-none-eabi 234 | armv7-unknown-linux-musleabihf thumbv8m.main-none-eabi 235 | armv7a-none-eabi thumbv8m.main-none-eabihf 236 | armv7r-none-eabi wasm32-unknown-emscripten 237 | armv7r-none-eabihf wasm32-unknown-unknown 238 | asmjs-unknown-emscripten wasm32-wasi 239 | i586-pc-windows-msvc x86_64-apple-darwin 240 | i586-unknown-linux-gnu x86_64-apple-ios 241 | i586-unknown-linux-musl x86_64-fortanix-unknown-sgx 242 | i686-linux-android x86_64-fuchsia 243 | i686-pc-windows-gnu x86_64-linux-android 244 | i686-pc-windows-msvc x86_64-pc-windows-gnu 245 | i686-unknown-freebsd x86_64-pc-windows-msvc 246 | i686-unknown-linux-gnu x86_64-rumprun-netbsd 247 | i686-unknown-linux-musl x86_64-sun-solaris 248 | mips-unknown-linux-gnu x86_64-unknown-cloudabi 249 | mips-unknown-linux-musl x86_64-unknown-freebsd 250 | mips64-unknown-linux-gnuabi64 x86_64-unknown-linux-gnu (default) 251 | mips64-unknown-linux-muslabi64 x86_64-unknown-linux-gnux32 252 | mips64el-unknown-linux-gnuabi64 x86_64-unknown-linux-musl 253 | mips64el-unknown-linux-muslabi64 x86_64-unknown-netbsd 254 | mipsel-unknown-linux-gnu x86_64-unknown-redox 255 | ``` 256 | 257 | If there's no `rust-std` component for your target, or you are using a custom target, then you'll 258 | have to use a nightly toolchain to build the standard library. See the next page about [building for 259 | custom targets][use-target-file]. 260 | 261 | [use-target-file]: ./custom-target.md#use-the-target-file 262 | [xargo]: https://github.com/japaric/xargo 263 | --------------------------------------------------------------------------------