blob: 752dea28c45da059af0dcfa1fb10b185d1a1f158 [file] [log] [blame] [view]
Imre Kis2883ea12025-06-10 13:28:53 +02001# Firmware Development Guide
2
3The goal of this document is to provide a set of best practices, design guidelines, and tooling
4recommendations for developing firmware in Rust, particularly in `no_std` environments.
5
6## Set `no_std` attribute
7
8Rust's standard library relies on the presence of a rich OS. As a result, it isn’t available in
9firmware environments. Firmware projects or crates should therefore be marked with `no_std` to
10indicate that they do not use the std library.
11
12Insert the following line at the top of `main.rs`/`lib.rs`.
13
14```rust,ignore
15#![no_std]
16```
17
18Tests might rely on the standard library since they are typically executed in a host environment.
19Therefore, the `no_std` attribute should be conditionally applied when the build is not intended for
20testing.
21
22```rust,ignore
23#![cfg_attr(not(test), no_std)]
24```
25
26## Avoid using `alloc`
27
28Using a heap in firmware components can lead to non-deterministic behavior and potential
29fragmentation issues. As a result, many firmware projects choose to avoid using a heap entirely.
30
31Since a heap may not be available, relying on types from the alloc crate - such as `Vec` - can
32restrict the crate's applicability. Instead, prefer using slices for function inputs and outputs
33whenever possible. This approach allows the caller to manage memory allocation using the most
34appropriate storage mechanism for their context.
35
36```rust
37fn 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
45fn numbers_without_alloc(n: usize, result: &mut [usize]) {
46 for i in 0..n {
47 result[i] = i;
48 }
49}
50
51let result: Vec<usize> = numbers_with_alloc(10);
52
53let mut result: [usize; 10] = [0; 10];
54numbers_without_alloc(10, &mut result);
55
56// But now it still works with Vec too!
57let mut result: Vec<usize> = vec![0; 10];
58numbers_without_alloc(10, result.as_mut_slice());
59```
60
61## Derive or implement common traits for custom types
62
63Rust provides a wide range of traits that allow custom types to integrate seamlessly with the
64core 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`, its possible to check whether a slice
66contains a specific item. Implementing these traits for your custom types greatly increases their
67usability. Avoid creating ad-hoc methods for common operations, prefer standard traits like `From`
68or `TryFrom` for type conversions.
69
70There are two main ways to implement a trait: by deriving it or by writing an explicit
71implementation. When a type's fields already implement a trait, using `#[derive]` is often enough
72to generate the trait implementation automatically. However, if the default behavior isn’t
73suitable, 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)]
99struct Address {
100 city: String,
101 street: String,
102 house_number: u32
103}
104
105fn 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
113let mut addresses: Vec<Address> = get_addresses();
114
115// Debug trait
116println!("{:?}", addresses[0]);
117
118// Default trait
119let empty_address = Address::default();
120
121// Clone trait
122let empty_clone = empty_address.clone();
123
124// PartialEq trait
125let 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
132addresses.sort();
133```
134
135## Minimize and document unsafe code
136
137Maintaining and reviewing unsafe code demands additional effort, so it's best to keep its usage to
138a minimum. In most cases unsafe code can be avoided by using an existing crate that provides a safe
139wrapper around the unsafe logic. For example, `zerocopy` enables the developer to convert between
140types, along all the necessary size and alignment checks.
141
142During firmware development, writing some unsafe code is often unavoidable. Whenever you use unsafe
143features, clearly document how the necessary conditions for safety are satisfied to justify the use
144of unsafe blocks.
145
146When defining an unsafe function, always specify the safety requirements that callers must uphold to
147use the function correctly.
148
149```rust
150# struct BootInfo(u32);
151
152// SAFETY: The address is mapped statically and nothing else references it.
153let 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.
159unsafe fn unmap_memory(address: u64) {
160 // [...]
161}
162```
163
164Add the following lines to `Cargo.toml` in order to enforce these rules by Clippy.
165
166```toml
167[lints.clippy]
168missing_safety_doc = "deny"
169undocumented_unsafe_blocks = "deny"
170```
171
172## Do not reinvent the ~~wheel~~ crate
173
174Use popular crates instead of reimplementing a functionality. `cargo` provides great ways of reusing
175code and `crates.io` provides high quality crates. On the other hand, try to minimize the number of
176dependencies 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
180Recommended crates to use in firmware project:
181
182### bitflags
183
184&#128279; [bitflags](https://crates.io/crates/bitflags)
185
186Provides a macro to define typesafe bitflags for managing sets of binary flags (single-bit). Not
187suitable for multi-bit fields.
188
189```rust
190bitflags::bitflags! {
191 struct Permissions: u32 {
192 const READ = 0b001;
193 const WRITE = 0b010;
194 const EXECUTE = 0b100;
195 }
196}
197
198let perms = Permissions::READ | Permissions::WRITE;
199let is_executable = perms.contains(Permissions::EXECUTE);
200```
201
202### embedded-hal
203
204&#128279; [embedded-hal](https://crates.io/crates/embedded-hal)
205
206Provides 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
208should implement the suitable traits of these crates in order to increase interoperability.
209
210### num_enum
211
212&#128279; [num_enum](https://crates.io/crates/num_enum)
213
214Rust does not have a built-in way of converting from integers to enums. The `num_enum` crate
215provides utilities for defining enums with explicit integer representations and conversions between
216them safely.
217
218```rust
219use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
220use core::convert::TryFrom;
221
222#[derive(Debug, TryFromPrimitive, PartialEq, Eq)]
223#[repr(u8)]
224enum Command {
225 Start = 1,
226 Stop = 2,
227}
228
229assert_eq!(Ok(Command::Start), Command::try_from(1u8));
230assert_eq!(Ok(Command::Stop), Command::try_from(2u8));
231assert!(Command::try_from(3u8).is_err());
232```
233
234### safe-mmio
235
236&#128279; [safe-mmio](https://crates.io/crates/safe-mmio)
237
238For MMIO peripheral access use the `safe-mmio` crate. This crates provides primitives for tracking
239the lifetime and ownership of an MMIO peripheral and for defining different types of peripherals
240registers.
241
242```rust,no_run
243use core::ptr::NonNull;
244use safe_mmio::{
245 field,
246 fields::{ReadOnly, ReadPure, ReadWrite, WriteOnly},
247 UniqueMmioPointer,
248};
249
250#[repr(C)]
251struct 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.
260let mut uart_registers: UniqueMmioPointer<UartRegisters> =
261 unsafe { UniqueMmioPointer::new(NonNull::new(0x900_0000 as _).unwrap()) };
262
263field!(uart_registers, data).write(b'x');
264```
265
266### spin
267
268&#128279; [spin](https://crates.io/crates/spin)
269
270Implements simple spinlocks for `no_std` environments without relying on OS primitives. Use
271`spin::Once` for global/static variable lazy initialization.
272
273```rust
274use spin::Mutex;
275
276static DATA: Mutex<u32> = Mutex::new(0);
277
278fn increment() {
279 let mut data = DATA.lock();
280 *data += 1;
281}
282```
283
284### thiserror
285
286&#128279; [thiserror](https://crates.io/crates/thiserror)
287
288This library provides a convenient derive macro for the standard librarys `core::error::Error`
289trait. It makes creating custom error types easier by
290
291```rust
292use thiserror::Error;
293
294#[derive(Debug, Error)]
295enum 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&#128279; [uuid](https://crates.io/crates/uuid)
308
309Use the `uuid` crate for parsing/handling UUID/GUID values. It provides functions for converting
310between the `Uuid` type and strings, bytes and other representations.
311
312```rust
313use uuid::{uuid, Uuid};
314
315const ID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
316let my_uuid =
317 Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").expect("Failed to parse UUID");
318
319let bytes: &[u8; 16] = my_uuid.as_bytes();
320```
321
322### zerocopy
323
324&#128279; [zerocopy](https://crates.io/crates/zerocopy)
325
326Enables zero-copy parsing and serialization of fixed-layout types using safe abstractions. Zerocopy
327ensures that the source and destination types are suitable for conversion and they have a matching
328size and alignment.
329
330```rust
331use zerocopy::{FromBytes, IntoBytes, KnownLayout, Immutable, transmute};
332
333#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
334#[repr(C, packed)]
335struct Packet {
336 header: u16,
337 payload: u32,
338}
339
340let raw: [u8; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
341let packet = Packet::read_from(&raw[..]).unwrap();
342let raw_serialized = packet.as_bytes();
343assert_eq!(raw_serialized, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
344
345let one_dimensional: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
346let two_dimensional: [[u8; 4]; 2] = transmute!(one_dimensional);
347assert_eq!(two_dimensional, [[0, 1, 2, 3], [4, 5, 6, 7]]);
348```
349
350## Recommended tools
351
352### rustfmt
353
354&#128279; [rustfmt](https://github.com/rust-lang/rustfmt)
355
356Rust 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
358integrate rustfmt to format on save.
359
360### Clippy
361
362&#128279; [Clippy](https://github.com/rust-lang/rust-clippy)
363
364Clippy is the official Rust linter tool. It can emit various style, performance, etc. issues.
365Always address the issues pointed out by Clippy to improve code quality.
366
367### Cargo Vet
368
369&#128279; [Cargo Vet](https://github.com/mozilla/cargo-vet)
370
371Cargo provides an easy way for using third party crates, however this introduces the possibility of
372supply chain attacks. An attacker can take over a dependency or push a malicious change into it.
373Cargo Vet is a tool for supply chain auditing and it only allows the use of audited crate versions.
374
375## Pre-release checklist
376
377Before 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
384cargo update # Update dependencies
385cargo vet # Run audit check
386cargo build # Build project
387cargo test # Build and run tests
388cargo 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).*