Working with Device Tree in Rust: Calling `libfdt`
This short article introduces how to work with Device Tree in Rust by calling the API of native libfdt
library.
Background
Device Tree is a data structure describing the hardware components of a particular computer so that the operating system’s kernel can use and manage those components, including the CPU or CPUs, the memory, the buses and the integrated peripherals (from Wikipedia).
The core reason for the existence of Device Tree in Linux is to provide a way to describe non-discoverable hardware. This information was previously hard coded in source code. (from eLinux)
To handle the device tree in Rust, you can either:
- Call the API provided by native library
libfdt
- Or use some kind of Rust crate
In this article I will show how to play in the first way. I will compare some different Rust crates in a later story.
Calling libfdt
DTC
is the dominant tool to handle device tree in Linux world. It provides a library libfdt
that offers various APIs to manipulate device tree, and a command line program dtc
which consumes the libfdt
.
Checking the header file libfdt.h
, you can see all the API that libfdt
library provides.
In a Rust program, to handle device tree, it’s a good idea to call the API of libfdt
. After all the libfdt
is powerful, commonly used and very stable.
In Rust, libc
crate helps when you need to call native API provided by a local library that is written in C language (or others).
The following code example illustrates how to call a libfdt
API fdt_create
by leveraging libc
:
use libc::{c_int, c_void};
// Size of the FDT blob
const FDT_MAX_SIZE: u64 = 0x20_000;
// Link to native `libfdt.so` library
#[link(name = "fdt")]
extern "C" {
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
}
fn main() {
// Allocate memory for the holding the blob
let mut fdt = vec![0; FDT_MAX_SIZE as usize];
// Call native C API for creating a FDT
let fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) };
if fdt_ret == 0 {
println!("FDT created");
} else {
panic!("Failed to create FDT")
}
}
Some explanation of the code:
- The library name
libfdt
is specified with#[link(name = "fdt")]
- The symbols to reference must be declared in the
extern "C"
block - The call to the native function must be delimited by the
unsafe
block
In early stage of Cloud Hypervisor, we use this method to build the DTB for the VM. The DTB contains the information of devices that the hypervisor provides. If you are interested in how the VMM was using the libfdt
, you can check the history code before this PR got merged. The PR replaced libfdt
with a pure Rust crate vm-fdt
.
Why we finally replaced libfdt
? Because: (these are also the disadvantages of invoking native API)
- Using a native library makes the Rust program hard to deploy. Because the program depends on something external. To run the Rust program correctly, you have to guarantee the
libfdt.so
has existed in a folder thatLD_LIBRARY_PATH
environment variable covers. - Another trouble in deployment is the possible difference between the building environment and the running environment. API version mismatch can make the program fail.
- Using
unsafe
makes uneasiness, it brings uncertainty in runtime. But runtime certainty is one of the best things of Rust.