qemu_fw_cfg/
lib.rs

1//! A Rust library for reading [fw_cfg] from QEMU.
2//!
3//! [fw_cfg]: https://www.qemu.org/docs/master/specs/fw_cfg.html
4//!
5//! # Supported architectures
6//!
7//! As of today, this crate only supported x86 and x86_64. However, it is possible
8//! to add support for other platforms, such as ARM.
9//!
10//! # Examples
11//! ```
12//! use qemu_fw_cfg::FwCfg;
13//!
14//! // Verify that we are inside QEMU.
15//! if running_in_qemu() {
16//!     // Create a new `FwCfg` instance.
17//!     let fw_cfg = unsafe { FwCfg::new().unwrap() };
18//!     // Retrieve information of a file.
19//!     let file = fw_cfg.find_file("etc/igd-opregion").unwrap();
20//!     // Read data from the file.
21//!     let data = fw_cfg.read_file(&file);
22//! }
23//! ```
24
25#![no_std]
26
27#[cfg(feature = "alloc")]
28#[macro_use]
29extern crate alloc;
30
31#[cfg(feature = "alloc")]
32use alloc::vec::Vec;
33
34use core::cell::UnsafeCell;
35use core::convert::TryFrom;
36use core::fmt;
37use core::mem::size_of;
38use core::sync::atomic::{compiler_fence, Ordering};
39
40#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41#[path = "x86.rs"]
42mod arch;
43
44mod selector_keys {
45    pub const SIGNATURE: u16 = 0x0000;
46    pub const FEATURE_BITMAP: u16 = 0x0001;
47    pub const DIR: u16 = 0x0019;
48}
49
50const SIGNATURE_DATA: &[u8] = b"QEMU";
51
52mod feature_bitmasks {
53    pub const _HAS_TRADITIONAL_INTERFACE: u32 = 1 << 0;
54    pub const HAS_DMA: u32 = 1 << 1;
55}
56
57/// An enum type for [`FwCfg`] errors.
58#[derive(Debug, PartialEq, Eq)]
59#[non_exhaustive]
60pub enum FwCfgError {
61    /// Invalid signature returned from QEMU fw_cfg I/O port
62    InvalidSignature,
63}
64
65/// An enum type for [`FwCfg::write_file`] errors.
66#[derive(Debug, PartialEq, Eq)]
67#[non_exhaustive]
68pub enum FwCfgWriteError {
69    /// This fw_cfg device does not support DMA access,
70    /// which is necessary for writing since QEMU v2.4.
71    ///
72    /// Note: writing through the data register for older QEMU versions
73    /// is not supported by this crate.
74    DmaNotAvailable,
75    /// Something went wrong during a DMA write
76    DmaFailed,
77}
78
79/// A struct for accessing QEMU fw_cfg.
80#[derive(Debug)]
81pub struct FwCfg {
82    mode: Mode,
83    feature_bitmap: Option<u32>,
84}
85
86#[derive(Debug)]
87enum Mode {
88    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
89    IOPort,
90    MemoryMapped(MemoryMappedDevice),
91}
92
93impl FwCfg {
94    /// Build `FwCfg` for the x86/x86-64 I/O port.
95    ///
96    /// # Safety
97    ///
98    /// This may only be called when running inside QEMU
99    /// since I/O ports are accessed without additional checks.
100    ///
101    /// Only one `FwCfg` value may exist at the same time
102    /// since it accesses a global shared stateful resource.
103    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
104    pub unsafe fn new_for_x86() -> Result<FwCfg, FwCfgError> {
105        Self::new_for_mode(Mode::IOPort)
106    }
107
108    /// Build `FwCfg` for the device memory-mapped at the give base pointer.
109    ///
110    /// # Safety
111    ///
112    /// The pointer must point to a valid fw_cfg device.
113    ///
114    /// Only one `FwCfg` value may exist at the same time for that pointer.
115    pub unsafe fn new_memory_mapped(base_ptr: *mut ()) -> Result<FwCfg, FwCfgError> {
116        let device = MemoryMappedDevice::new(base_ptr);
117        Self::new_for_mode(Mode::MemoryMapped(device))
118    }
119
120    unsafe fn new_for_mode(mode: Mode) -> Result<FwCfg, FwCfgError> {
121        let mut fw_cfg = FwCfg {
122            mode,
123            feature_bitmap: None,
124        };
125
126        let mut signature = [0u8; SIGNATURE_DATA.len()];
127        fw_cfg.select(selector_keys::SIGNATURE);
128        fw_cfg.read(&mut signature);
129
130        if signature != SIGNATURE_DATA {
131            return Err(FwCfgError::InvalidSignature);
132        }
133
134        Ok(fw_cfg)
135    }
136
137    /// Return the "feature" configuration item,
138    /// reading it from the device if necessary and caching it.
139    fn feature_bitmap(&mut self) -> u32 {
140        self.feature_bitmap.unwrap_or_else(|| {
141            let mut buffer = [0u8; 4];
142            self.select(selector_keys::FEATURE_BITMAP);
143            self.read(&mut buffer);
144            let value = u32::from_le_bytes(buffer);
145            self.feature_bitmap = Some(value);
146            value
147        })
148    }
149
150    /// Return an iterator of all files in the fw_cfg directory
151    pub fn iter_files(&mut self) -> impl Iterator<Item = FwCfgFile> + '_ {
152        self.select(selector_keys::DIR);
153
154        let count = {
155            let mut buf = [0u8; size_of::<u32>()];
156            self.read(&mut buf);
157            u32::from_be_bytes(buf)
158        };
159        (0..count).map(move |_| {
160            let mut file = FwCfgFile::default();
161            self.read(file.as_mut_bytes());
162            file
163        })
164    }
165
166    /// Find one or more files by their name.
167    ///
168    /// Each tuple in `entries` must consisted of file name and a space for
169    /// `Option<FwCfgFile>`. If a file is found, the result will be stored by
170    /// replacing the value in `Option<FwCfgFile>` of the corresponding tuple,
171    /// otherwise it will retained the same value as before.
172    ///
173    /// # Examples
174    /// ```
175    /// use qemu_fw_cfg::FwCfg;
176    ///
177    /// let fw_cfg = unsafe { FwCfg::new().unwrap() };
178    /// let mut files = [
179    ///     ("etc/igd-opregion", None),
180    ///     ("opt/another/file.txt", None),
181    /// ];
182    /// fw_cfg.find_files(&mut files);
183    /// ```
184    pub fn find_files(&mut self, entries: &mut [(&str, Option<FwCfgFile>)]) {
185        for file in self.iter_files() {
186            let mut changed = false;
187
188            for (name, ret) in entries.iter_mut() {
189                if file.name() == *name {
190                    *ret = Some(file.clone());
191                    changed = true;
192                }
193            }
194
195            if changed && entries.iter().all(|entry| entry.1.is_some()) {
196                return;
197            }
198        }
199    }
200
201    /// Find a single file by its name. Returns `None` if the file is missing.
202    ///
203    /// # Examples
204    /// ```
205    /// use qemu_fw_cfg::FwCfg;
206    ///
207    /// let fw_cfg = unsafe { FwCfg::new().unwrap() };
208    /// let file = fw_cfg.find_file("etc/igd-opregion").unwrap();
209    /// ```
210    pub fn find_file(&mut self, name: &str) -> Option<FwCfgFile> {
211        let mut entries = [(name, None)];
212        self.find_files(&mut entries);
213        entries[0].1.take()
214    }
215
216    /// Read a file and fill its data in `buffer`.
217    ///
218    /// If the size of `buffer` is greater or equals to the size of the file,
219    /// then it will fill the entire data in `buffer[0..file.size()]`, otherwise
220    /// it will only fill up to `buffer.len()`.
221    pub fn read_file_to_buffer(&mut self, file: &FwCfgFile, buffer: &mut [u8]) {
222        let len = file.size().min(buffer.len());
223        self.select(file.key());
224        self.read(&mut buffer[..len]);
225    }
226
227    /// Read a file and return the data in `Vec<u8>`.
228    #[cfg(feature = "alloc")]
229    pub fn read_file(&mut self, file: &FwCfgFile) -> Vec<u8> {
230        let mut buf = vec![0u8; file.size()];
231        self.select(file.key());
232        self.read(&mut buf);
233        buf
234    }
235
236    /// Write provided `data` into a file, starting at file offset 0.
237    ///
238    /// This requires the DMA interface, which QEMU supports since version 2.9.
239    pub fn write_to_file(&mut self, file: &FwCfgFile, data: &[u8]) -> Result<(), FwCfgWriteError> {
240        let has_dma = (self.feature_bitmap() & feature_bitmasks::HAS_DMA) != 0;
241        if !has_dma {
242            return Err(FwCfgWriteError::DmaNotAvailable);
243        }
244        let control = (file.key() as u32) << 16 | FwCfgDmaAccess::WRITE | FwCfgDmaAccess::SELECT;
245        let access = FwCfgDmaAccess::new(control, data.as_ptr() as _, data.len());
246        // `data` and `access` initialization must not be reordered to after this:
247        compiler_fence(Ordering::Release);
248        match &mut self.mode {
249            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
250            Mode::IOPort => unsafe { arch::start_dma(&access) },
251            Mode::MemoryMapped(device) => device.start_dma(&access),
252        }
253        loop {
254            let control = access.read_control();
255            if (control & FwCfgDmaAccess::ERROR) != 0 {
256                return Err(FwCfgWriteError::DmaFailed);
257            }
258            if control == 0 {
259                return Ok(());
260            }
261        }
262    }
263
264    fn select(&mut self, key: u16) {
265        match &mut self.mode {
266            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
267            Mode::IOPort => unsafe { arch::write_selector(key) },
268            Mode::MemoryMapped(device) => device.write_selector(key),
269        }
270    }
271
272    fn read(&mut self, buffer: &mut [u8]) {
273        match &mut self.mode {
274            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
275            Mode::IOPort => unsafe { arch::read_data(buffer) },
276            Mode::MemoryMapped(device) => device.read_data(buffer),
277        }
278    }
279}
280
281const _: () = assert!(size_of::<FwCfgFile>() == 64);
282
283/// A struct that contains information of a fw_cfg file.
284#[derive(Clone, PartialEq, Eq)]
285// NOTE: The memory layout of this struct must match this exactly:
286// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L132-137
287#[repr(C)]
288pub struct FwCfgFile {
289    size_be: u32,
290    key_be: u16,
291    _reserved: u16,
292    name_bytes: [u8; 56],
293}
294
295// Can’t be derived because of:
296// https://github.com/rust-lang/rust/issues/88744
297// https://github.com/rust-lang/rust/issues/61415
298impl Default for FwCfgFile {
299    fn default() -> Self {
300        Self {
301            size_be: 0,
302            key_be: 0,
303            _reserved: 0,
304            name_bytes: [0; 56],
305        }
306    }
307}
308
309impl FwCfgFile {
310    /// The size of this file.
311    pub fn size(&self) -> usize {
312        u32::from_be(self.size_be) as usize
313    }
314
315    fn key(&self) -> u16 {
316        u16::from_be(self.key_be)
317    }
318
319    /// The name of this file.
320    pub fn name(&self) -> &str {
321        let bytes = self.name_bytes.split(|&b| b == b'\x00').next().unwrap();
322        core::str::from_utf8(bytes).unwrap()
323    }
324
325    fn as_mut_bytes(&mut self) -> &mut [u8; size_of::<Self>()] {
326        let ptr: *mut Self = self;
327        let ptr: *mut [u8; size_of::<Self>()] = ptr.cast();
328        unsafe { &mut *ptr }
329    }
330}
331
332impl fmt::Debug for FwCfgFile {
333    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
334        fmt.debug_struct("FwCfgFile")
335            .field("key", &self.key())
336            .field("size", &self.size())
337            .field("name", &self.name())
338            .finish()
339    }
340}
341
342#[derive(Debug)]
343struct MemoryMappedDevice {
344    base_ptr: *mut (),
345}
346
347impl MemoryMappedDevice {
348    unsafe fn new(base_ptr: *mut ()) -> Self {
349        Self { base_ptr }
350    }
351
352    fn register<T>(&self, offset_in_bytes: usize) -> *mut T {
353        let offset = offset_in_bytes / size_of::<T>();
354        unsafe { self.base_ptr.cast::<T>().add(offset) }
355    }
356
357    fn write_selector(&mut self, key: u16) {
358        // https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L87
359        let selector_offset = 8;
360        let selector_ptr = self.register::<u16>(selector_offset);
361        unsafe { selector_ptr.write_volatile(key.to_be()) }
362    }
363
364    fn read_data(&mut self, data: &mut [u8]) {
365        // https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L88
366        let data_offset = 0;
367        let data_ptr = self.register::<usize>(data_offset);
368        for chunk in data.chunks_mut(size_of::<usize>()) {
369            let word = unsafe { data_ptr.read_volatile() };
370            // https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L53
371            // "string-preserving" means native-endian
372            let bytes = word.to_ne_bytes();
373            chunk.copy_from_slice(&bytes[..chunk.len()]);
374        }
375    }
376
377    fn start_dma(&self, access: &FwCfgDmaAccess) {
378        let address = access as *const FwCfgDmaAccess as u64;
379        // https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L89
380        let offset = 16;
381        let dma_address_register: *mut u32 = self.register(offset);
382        unsafe {
383            // https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L167
384            // The DMA address register is 64-bit and big-endian.
385            // Writing its lower half is what triggers DMA,
386            // so write these half separately to control their order:
387            let register_high = dma_address_register;
388            let register_low = dma_address_register.add(1); // One u32
389            let address_high = (address >> 32) as u32;
390            let address_low = address as u32;
391            register_high.write_volatile(address_high.to_be());
392            compiler_fence(Ordering::AcqRel);
393            register_low.write_volatile(address_low.to_be());
394        }
395    }
396}
397
398#[derive(Debug)]
399// NOTE: The memory layout of this struct must match this exactly:
400// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L177-181
401#[repr(C)]
402struct FwCfgDmaAccess {
403    control_be: UnsafeCell<u32>,
404    length_be: u32,
405    address_be: u64,
406}
407
408impl FwCfgDmaAccess {
409    const ERROR: u32 = 1 << 0;
410    const _READ: u32 = 1 << 1;
411    const _SKIP: u32 = 1 << 2;
412    const SELECT: u32 = 1 << 3;
413    const WRITE: u32 = 1 << 4;
414
415    fn new(control: u32, ptr: *mut (), length: usize) -> Self {
416        Self {
417            control_be: UnsafeCell::new(control.to_be()),
418            length_be: u32::try_from(length).unwrap().to_be(),
419            address_be: u64::try_from(ptr as usize).unwrap().to_be(),
420        }
421    }
422
423    fn read_control(&self) -> u32 {
424        u32::from_be(unsafe { self.control_be.get().read_volatile() })
425    }
426}