| Imre Kis | 2883ea1 | 2025-06-10 13:28:53 +0200 | [diff] [blame^] | 1 | # Firmware Development Guide |
| 2 | |
| 3 | The goal of this document is to provide a set of best practices, design guidelines, and tooling |
| 4 | recommendations for developing firmware in Rust, particularly in `no_std` environments. |
| 5 | |
| 6 | ## Set `no_std` attribute |
| 7 | |
| 8 | Rust's standard library relies on the presence of a rich OS. As a result, it isn’t available in |
| 9 | firmware environments. Firmware projects or crates should therefore be marked with `no_std` to |
| 10 | indicate that they do not use the std library. |
| 11 | |
| 12 | Insert the following line at the top of `main.rs`/`lib.rs`. |
| 13 | |
| 14 | ```rust,ignore |
| 15 | #![no_std] |
| 16 | ``` |
| 17 | |
| 18 | Tests might rely on the standard library since they are typically executed in a host environment. |
| 19 | Therefore, the `no_std` attribute should be conditionally applied when the build is not intended for |
| 20 | testing. |
| 21 | |
| 22 | ```rust,ignore |
| 23 | #![cfg_attr(not(test), no_std)] |
| 24 | ``` |
| 25 | |
| 26 | ## Avoid using `alloc` |
| 27 | |
| 28 | Using a heap in firmware components can lead to non-deterministic behavior and potential |
| 29 | fragmentation issues. As a result, many firmware projects choose to avoid using a heap entirely. |
| 30 | |
| 31 | Since a heap may not be available, relying on types from the alloc crate - such as `Vec` - can |
| 32 | restrict the crate's applicability. Instead, prefer using slices for function inputs and outputs |
| 33 | whenever possible. This approach allows the caller to manage memory allocation using the most |
| 34 | appropriate storage mechanism for their context. |
| 35 | |
| 36 | ```rust |
| 37 | fn numbers_with_alloc(n: usize) -> Vec<usize> { |
| 38 | let mut result = Vec::new(); |
| 39 | for i in 0..n { |
| 40 | result.push(i); |
| 41 | } |
| 42 | result |
| 43 | } |
| 44 | |
| 45 | fn numbers_without_alloc(n: usize, result: &mut [usize]) { |
| 46 | for i in 0..n { |
| 47 | result[i] = i; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | let result: Vec<usize> = numbers_with_alloc(10); |
| 52 | |
| 53 | let mut result: [usize; 10] = [0; 10]; |
| 54 | numbers_without_alloc(10, &mut result); |
| 55 | |
| 56 | // But now it still works with Vec too! |
| 57 | let mut result: Vec<usize> = vec![0; 10]; |
| 58 | numbers_without_alloc(10, result.as_mut_slice()); |
| 59 | ``` |
| 60 | |
| 61 | ## Derive or implement common traits for custom types |
| 62 | |
| 63 | Rust provides a wide range of traits that allow custom types to integrate seamlessly with the |
| 64 | core and standard library. For instance, a slice can be sorted if its element type implements the |
| 65 | `Ord` trait, and if the element type implements `PartialEq`, it’s possible to check whether a slice |
| 66 | contains a specific item. Implementing these traits for your custom types greatly increases their |
| 67 | usability. Avoid creating ad-hoc methods for common operations, prefer standard traits like `From` |
| 68 | or `TryFrom` for type conversions. |
| 69 | |
| 70 | There are two main ways to implement a trait: by deriving it or by writing an explicit |
| 71 | implementation. When a type's fields already implement a trait, using `#[derive]` is often enough |
| 72 | to generate the trait implementation automatically. However, if the default behavior isn’t |
| 73 | suitable, you can manually implement the trait to customize its logic. |
| 74 | |
| 75 | ### Common traits |
| 76 | |
| 77 | * `Debug` - Enables printing the value using the `"{:?}"` formatter. |
| 78 | * `Display` - Enables printing the value using the `"{}"` formatter. `Display` is similar to |
| 79 | `Debug`, but `Display` is for user-facing output, and so cannot be derived. |
| 80 | * `Default` - Provides a `default()` function for creating default instance. |
| 81 | * `Clone` - Enables explicit `clone()`-ing of the type. |
| 82 | * `Copy` - Enable implicit copying of the type. Do not use it for large types to avoid unnecessary |
| 83 | copying. |
| 84 | * `PartialEq` - Provides `==` and `!=` operators. |
| 85 | * `Eq` - The implementor promises that `a == a`, i.e. the type is reflexive. This is not true for |
| 86 | floating point numbers where `NaN != Nan`. |
| 87 | * `PartialOrd` - Provides `<`, `<=`, `>`, and `>=` operators. |
| 88 | * `Ord` - Trait for types that form a total order. |
| 89 | * `From<T>` - Used to do value-to-value conversions while consuming the input value. It is the |
| 90 | reciprocal of `Into`. Implementing `From` over `Into` is preferred, because `From` automatically |
| 91 | provides an `Into` implementation as well. |
| 92 | * `TryFrom<T>`- Simple and safe type conversions that may fail in a controlled way under some |
| 93 | circumstances. It is the reciprocal of `TryInto`. |
| 94 | |
| 95 | ### Example |
| 96 | |
| 97 | ```rust |
| 98 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] |
| 99 | struct Address { |
| 100 | city: String, |
| 101 | street: String, |
| 102 | house_number: u32 |
| 103 | } |
| 104 | |
| 105 | fn get_addresses() -> Vec<Address> { |
| 106 | vec![Address { |
| 107 | city: "Cambridge".to_string(), |
| 108 | street: "Fulbourn Rd".to_string(), |
| 109 | house_number: 110, |
| 110 | }] |
| 111 | } |
| 112 | |
| 113 | let mut addresses: Vec<Address> = get_addresses(); |
| 114 | |
| 115 | // Debug trait |
| 116 | println!("{:?}", addresses[0]); |
| 117 | |
| 118 | // Default trait |
| 119 | let empty_address = Address::default(); |
| 120 | |
| 121 | // Clone trait |
| 122 | let empty_clone = empty_address.clone(); |
| 123 | |
| 124 | // PartialEq trait |
| 125 | let has_arm_address = addresses.contains(&Address { |
| 126 | city: "Cambridge".to_string(), |
| 127 | street: "Fulbourn Rd".to_string(), |
| 128 | house_number: 110, |
| 129 | }); |
| 130 | |
| 131 | // Ord trait |
| 132 | addresses.sort(); |
| 133 | ``` |
| 134 | |
| 135 | ## Minimize and document unsafe code |
| 136 | |
| 137 | Maintaining and reviewing unsafe code demands additional effort, so it's best to keep its usage to |
| 138 | a minimum. In most cases unsafe code can be avoided by using an existing crate that provides a safe |
| 139 | wrapper around the unsafe logic. For example, `zerocopy` enables the developer to convert between |
| 140 | types, along all the necessary size and alignment checks. |
| 141 | |
| 142 | During firmware development, writing some unsafe code is often unavoidable. Whenever you use unsafe |
| 143 | features, clearly document how the necessary conditions for safety are satisfied to justify the use |
| 144 | of unsafe blocks. |
| 145 | |
| 146 | When defining an unsafe function, always specify the safety requirements that callers must uphold to |
| 147 | use the function correctly. |
| 148 | |
| 149 | ```rust |
| 150 | # struct BootInfo(u32); |
| 151 | |
| 152 | // SAFETY: The address is mapped statically and nothing else references it. |
| 153 | let boot_info = unsafe { &*(0x8000_0000 as *const BootInfo) }; |
| 154 | |
| 155 | /// # Safety |
| 156 | /// |
| 157 | /// Before calling this function, ensure that all pending writes have been |
| 158 | /// flushed and there are no references to the address. |
| 159 | unsafe fn unmap_memory(address: u64) { |
| 160 | // [...] |
| 161 | } |
| 162 | ``` |
| 163 | |
| 164 | Add the following lines to `Cargo.toml` in order to enforce these rules by Clippy. |
| 165 | |
| 166 | ```toml |
| 167 | [lints.clippy] |
| 168 | missing_safety_doc = "deny" |
| 169 | undocumented_unsafe_blocks = "deny" |
| 170 | ``` |
| 171 | |
| 172 | ## Do not reinvent the ~~wheel~~ crate |
| 173 | |
| 174 | Use popular crates instead of reimplementing a functionality. `cargo` provides great ways of reusing |
| 175 | code and `crates.io` provides high quality crates. On the other hand, try to minimize the number of |
| 176 | dependencies for supply chain, code size and build time reasons. |
| 177 | |
| 178 | [npm left-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident) |
| 179 | |
| 180 | Recommended crates to use in firmware project: |
| 181 | |
| 182 | ### bitflags |
| 183 | |
| 184 | 🔗 [bitflags](https://crates.io/crates/bitflags) |
| 185 | |
| 186 | Provides a macro to define typesafe bitflags for managing sets of binary flags (single-bit). Not |
| 187 | suitable for multi-bit fields. |
| 188 | |
| 189 | ```rust |
| 190 | bitflags::bitflags! { |
| 191 | struct Permissions: u32 { |
| 192 | const READ = 0b001; |
| 193 | const WRITE = 0b010; |
| 194 | const EXECUTE = 0b100; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | let perms = Permissions::READ | Permissions::WRITE; |
| 199 | let is_executable = perms.contains(Permissions::EXECUTE); |
| 200 | ``` |
| 201 | |
| 202 | ### embedded-hal |
| 203 | |
| 204 | 🔗 [embedded-hal](https://crates.io/crates/embedded-hal) |
| 205 | |
| 206 | Provides hardware abstraction layers for embedded system as a family of crates |
| 207 | (`embedded-hal`, `embedded-hal-async`, `embedded-hal-nb`, `embedded-io`, etc.). Peripheral drivers |
| 208 | should implement the suitable traits of these crates in order to increase interoperability. |
| 209 | |
| 210 | ### num_enum |
| 211 | |
| 212 | 🔗 [num_enum](https://crates.io/crates/num_enum) |
| 213 | |
| 214 | Rust does not have a built-in way of converting from integers to enums. The `num_enum` crate |
| 215 | provides utilities for defining enums with explicit integer representations and conversions between |
| 216 | them safely. |
| 217 | |
| 218 | ```rust |
| 219 | use num_enum::{TryFromPrimitive, TryFromPrimitiveError}; |
| 220 | use core::convert::TryFrom; |
| 221 | |
| 222 | #[derive(Debug, TryFromPrimitive, PartialEq, Eq)] |
| 223 | #[repr(u8)] |
| 224 | enum Command { |
| 225 | Start = 1, |
| 226 | Stop = 2, |
| 227 | } |
| 228 | |
| 229 | assert_eq!(Ok(Command::Start), Command::try_from(1u8)); |
| 230 | assert_eq!(Ok(Command::Stop), Command::try_from(2u8)); |
| 231 | assert!(Command::try_from(3u8).is_err()); |
| 232 | ``` |
| 233 | |
| 234 | ### safe-mmio |
| 235 | |
| 236 | 🔗 [safe-mmio](https://crates.io/crates/safe-mmio) |
| 237 | |
| 238 | For MMIO peripheral access use the `safe-mmio` crate. This crates provides primitives for tracking |
| 239 | the lifetime and ownership of an MMIO peripheral and for defining different types of peripherals |
| 240 | registers. |
| 241 | |
| 242 | ```rust,no_run |
| 243 | use core::ptr::NonNull; |
| 244 | use safe_mmio::{ |
| 245 | field, |
| 246 | fields::{ReadOnly, ReadPure, ReadWrite, WriteOnly}, |
| 247 | UniqueMmioPointer, |
| 248 | }; |
| 249 | |
| 250 | #[repr(C)] |
| 251 | struct UartRegisters { |
| 252 | data: ReadWrite<u8>, |
| 253 | status: ReadPure<u8>, |
| 254 | pending_interrupt: ReadOnly<u8>, |
| 255 | } |
| 256 | |
| 257 | // SAFETY: |
| 258 | // The UART peripherals at 0x900_0000 is statically mapped for the lifetime of |
| 259 | // the application and nothing else references it. |
| 260 | let mut uart_registers: UniqueMmioPointer<UartRegisters> = |
| 261 | unsafe { UniqueMmioPointer::new(NonNull::new(0x900_0000 as _).unwrap()) }; |
| 262 | |
| 263 | field!(uart_registers, data).write(b'x'); |
| 264 | ``` |
| 265 | |
| 266 | ### spin |
| 267 | |
| 268 | 🔗 [spin](https://crates.io/crates/spin) |
| 269 | |
| 270 | Implements simple spinlocks for `no_std` environments without relying on OS primitives. Use |
| 271 | `spin::Once` for global/static variable lazy initialization. |
| 272 | |
| 273 | ```rust |
| 274 | use spin::Mutex; |
| 275 | |
| 276 | static DATA: Mutex<u32> = Mutex::new(0); |
| 277 | |
| 278 | fn increment() { |
| 279 | let mut data = DATA.lock(); |
| 280 | *data += 1; |
| 281 | } |
| 282 | ``` |
| 283 | |
| 284 | ### thiserror |
| 285 | |
| 286 | 🔗 [thiserror](https://crates.io/crates/thiserror) |
| 287 | |
| 288 | This library provides a convenient derive macro for the standard library’s `core::error::Error` |
| 289 | trait. It makes creating custom error types easier by |
| 290 | |
| 291 | ```rust |
| 292 | use thiserror::Error; |
| 293 | |
| 294 | #[derive(Debug, Error)] |
| 295 | enum CustomError { |
| 296 | #[error("generic error")] |
| 297 | GenericError, |
| 298 | #[error("invalid capacity: {0}")] |
| 299 | InvalidCapacity(usize), |
| 300 | #[error("unknown command index: {0}")] |
| 301 | UnknownCommandIndex(u32), |
| 302 | } |
| 303 | ``` |
| 304 | |
| 305 | ### uuid |
| 306 | |
| 307 | 🔗 [uuid](https://crates.io/crates/uuid) |
| 308 | |
| 309 | Use the `uuid` crate for parsing/handling UUID/GUID values. It provides functions for converting |
| 310 | between the `Uuid` type and strings, bytes and other representations. |
| 311 | |
| 312 | ```rust |
| 313 | use uuid::{uuid, Uuid}; |
| 314 | |
| 315 | const ID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); |
| 316 | let my_uuid = |
| 317 | Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").expect("Failed to parse UUID"); |
| 318 | |
| 319 | let bytes: &[u8; 16] = my_uuid.as_bytes(); |
| 320 | ``` |
| 321 | |
| 322 | ### zerocopy |
| 323 | |
| 324 | 🔗 [zerocopy](https://crates.io/crates/zerocopy) |
| 325 | |
| 326 | Enables zero-copy parsing and serialization of fixed-layout types using safe abstractions. Zerocopy |
| 327 | ensures that the source and destination types are suitable for conversion and they have a matching |
| 328 | size and alignment. |
| 329 | |
| 330 | ```rust |
| 331 | use zerocopy::{FromBytes, IntoBytes, KnownLayout, Immutable, transmute}; |
| 332 | |
| 333 | #[derive(FromBytes, IntoBytes, KnownLayout, Immutable)] |
| 334 | #[repr(C, packed)] |
| 335 | struct Packet { |
| 336 | header: u16, |
| 337 | payload: u32, |
| 338 | } |
| 339 | |
| 340 | let raw: [u8; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; |
| 341 | let packet = Packet::read_from(&raw[..]).unwrap(); |
| 342 | let raw_serialized = packet.as_bytes(); |
| 343 | assert_eq!(raw_serialized, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); |
| 344 | |
| 345 | let one_dimensional: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; |
| 346 | let two_dimensional: [[u8; 4]; 2] = transmute!(one_dimensional); |
| 347 | assert_eq!(two_dimensional, [[0, 1, 2, 3], [4, 5, 6, 7]]); |
| 348 | ``` |
| 349 | |
| 350 | ## Recommended tools |
| 351 | |
| 352 | ### rustfmt |
| 353 | |
| 354 | 🔗 [rustfmt](https://github.com/rust-lang/rustfmt) |
| 355 | |
| 356 | Rust has an official style guide that ensures consistent formatting across the Rust ecosystem. |
| 357 | `cargo fmt` automatically formats the code according to the official style guide. Most editors can |
| 358 | integrate rustfmt to format on save. |
| 359 | |
| 360 | ### Clippy |
| 361 | |
| 362 | 🔗 [Clippy](https://github.com/rust-lang/rust-clippy) |
| 363 | |
| 364 | Clippy is the official Rust linter tool. It can emit various style, performance, etc. issues. |
| 365 | Always address the issues pointed out by Clippy to improve code quality. |
| 366 | |
| 367 | ### Cargo Vet |
| 368 | |
| 369 | 🔗 [Cargo Vet](https://github.com/mozilla/cargo-vet) |
| 370 | |
| 371 | Cargo provides an easy way for using third party crates, however this introduces the possibility of |
| 372 | supply chain attacks. An attacker can take over a dependency or push a malicious change into it. |
| 373 | Cargo Vet is a tool for supply chain auditing and it only allows the use of audited crate versions. |
| 374 | |
| 375 | ## Pre-release checklist |
| 376 | |
| 377 | Before releasing the crate/project go thought the following action items. |
| 378 | |
| 379 | * Update version number in `Cargo.toml` |
| 380 | * Update readme/changelog |
| 381 | * Run the following commands |
| 382 | |
| 383 | ```sh |
| 384 | cargo update # Update dependencies |
| 385 | cargo vet # Run audit check |
| 386 | cargo build # Build project |
| 387 | cargo test # Build and run tests |
| 388 | cargo clippy # Run linter |
| 389 | ``` |
| 390 | |
| 391 | ## General advice |
| 392 | |
| 393 | * `use` only what's needed. |
| 394 | * Create `const` variables for magic numbers. |
| 395 | * Try to limit the visibility of types and functions using `pub`, `pub(crate)`, etc. modifiers. |
| 396 | * The fields of enum variants inherit the visibility of the type, however this is not true for |
| 397 | struct field. Make sure that struct fields have the correct visibility if they are intended to be |
| 398 | accessed outside of the defining module. |
| 399 | |
| 400 | -------------- |
| 401 | |
| 402 | *Copyright 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>* |
| 403 | |
| 404 | *Arm is a registered trademark of Arm Limited (or its subsidiaries or affiliates).* |