这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .cargo/config
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@ target = "i686-unknown-none.json"

[target.i686-unknown-none]
rustflags = ["-C", "link-args=-T tests/shared/i686/link.ld"]
runner = "sh tests/runner.sh"
runner = "tests/runner.sh"

[target.riscv32imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Ttests/shared/riscv32/memory.x",
"-C", "link-arg=-Tlink.x",
]
runner = "tests/runner.sh"
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ jobs:
toolchain: ${{ matrix.rust }}
override: true
components: rust-src, rustfmt, clippy
target: riscv32imac-unknown-none-elf

- name: Setup QEMU
- name: Setup QEMU for RISC-V
run: sudo apt-get update && sudo apt-get install -y qemu-system-misc

- name: Setup QEMU for i686
if: ${{ matrix.rust == 'nightly' }}
run: sudo apt-get update && sudo apt-get install -y qemu-system-x86
run: sudo apt-get install -y qemu-system-x86

- name: Build (without testing) as x86_64
if: ${{ matrix.rust == 'nightly' }} # Tier 2 (precompiled libcore in rustup) since 1.62
Expand All @@ -44,6 +48,12 @@ jobs:
command: test
args: --target i686-unknown-none.json

- name: Build and test as RISC-V
uses: actions-rs/cargo@v1
with:
command: build
args: --target riscv32imac-unknown-none-elf --no-default-features

- name: Rustfmt
if: ${{ matrix.rust == 'nightly' }}
uses: actions-rs/cargo@v1
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ test = false
[[test]]
name = "main"
harness = false

[target.'cfg(target_arch = "riscv32")'.dev-dependencies]
riscv-rt = "0.9.0"
fdt = "0.1.3"
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
[![License-Apache](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE-APACHE)
[![docs.rs](https://img.shields.io/docsrs/qemu-fw-cfg)](https://docs.rs/qemu-fw-cfg)

A Rust library for reading fw_cfg from QEMU.
A Rust library for reading [fw_cfg] from QEMU.

[fw_cfg]: https://www.qemu.org/docs/master/specs/fw_cfg.html

## Usage

Expand Down
225 changes: 212 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! A Rust library for reading fw_cfg from QEMU.
//! A Rust library for reading [fw_cfg] from QEMU.
//!
//! [fw_cfg]: https://www.qemu.org/docs/master/specs/fw_cfg.html
//!
//! # Supported architectures
//!
Expand Down Expand Up @@ -29,33 +31,67 @@ extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

use core::cell::UnsafeCell;
use core::convert::TryFrom;
use core::fmt;
use core::mem::size_of;
use core::sync::atomic::{compiler_fence, Ordering};

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "x86.rs"]
mod arch;

mod selector_keys {
pub const SIGNATURE: u16 = 0x0000;
pub const FEATURE_BITMAP: u16 = 0x0001;
pub const DIR: u16 = 0x0019;
}

const SIGNATURE_DATA: &[u8] = b"QEMU";

mod feature_bitmasks {
pub const _HAS_TRADITIONAL_INTERFACE: u32 = 1 << 0;
pub const HAS_DMA: u32 = 1 << 1;
}

/// An enum type for [`FwCfg`] errors.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FwCfgError {
/// Invalid signature returned from QEMU fw_cfg I/O port
InvalidSignature,
}

/// An enum type for [`FwCfg::write_file`] errors.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FwCfgWriteError {
/// This fw_cfg device does not support DMA access,
/// which is necessary for writing since QEMU v2.4.
///
/// Note: writing through the data register for older QEMU versions
/// is not supported by this crate.
DmaNotAvailable,
/// Something went wrong during a DMA write
DmaFailed,
}

/// A struct for accessing QEMU fw_cfg.
#[derive(Debug)]
pub struct FwCfg(());
pub struct FwCfg {
mode: Mode,
feature_bitmap: Option<u32>,
}

#[derive(Debug)]
enum Mode {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
IOPort,
MemoryMapped(MemoryMappedDevice),
}

impl FwCfg {
/// Build `FwCfg` from the builder.
/// Build `FwCfg` for the x86/x86-64 I/O port.
///
/// # Safety
///
Expand All @@ -64,16 +100,51 @@ impl FwCfg {
///
/// Only one `FwCfg` value may exist at the same time
/// since it accesses a global shared stateful resource.
pub unsafe fn new() -> Result<FwCfg, FwCfgError> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub unsafe fn new_for_x86() -> Result<FwCfg, FwCfgError> {
Self::new_for_mode(Mode::IOPort)
}

/// Build `FwCfg` for the device memory-mapped at the give base pointer.
///
/// # Safety
///
/// The pointer must point to a valid fw_cfg device.
///
/// Only one `FwCfg` value may exist at the same time for that pointer.
pub unsafe fn new_memory_mapped(base_ptr: *mut ()) -> Result<FwCfg, FwCfgError> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this also works on x86/x86_64?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qemu-system-i386 and qemu-system-x86_64 don’t expose a memory-mapped fw-cfg device, so there is no valid address that could be passed to this constructor. But if you somehow had an x86/x86_64 with such a device, this should work.

let device = MemoryMappedDevice::new(base_ptr);
Self::new_for_mode(Mode::MemoryMapped(device))
}

unsafe fn new_for_mode(mode: Mode) -> Result<FwCfg, FwCfgError> {
let mut fw_cfg = FwCfg {
mode,
feature_bitmap: None,
};

let mut signature = [0u8; SIGNATURE_DATA.len()];
Self::write_selector(selector_keys::SIGNATURE);
Self::read_data(&mut signature);
fw_cfg.select(selector_keys::SIGNATURE);
fw_cfg.read(&mut signature);

if signature != SIGNATURE_DATA {
return Err(FwCfgError::InvalidSignature);
}

Ok(FwCfg(()))
Ok(fw_cfg)
}

/// Return the "feature" configuration item,
/// reading it from the device if necessary and caching it.
fn feature_bitmap(&mut self) -> u32 {
self.feature_bitmap.unwrap_or_else(|| {
let mut buffer = [0u8; 4];
self.select(selector_keys::FEATURE_BITMAP);
self.read(&mut buffer);
let value = u32::from_le_bytes(buffer);
self.feature_bitmap = Some(value);
value
})
}

/// Return an iterator of all files in the fw_cfg directory
Expand Down Expand Up @@ -162,23 +233,55 @@ impl FwCfg {
buf
}

/// Write provided `data` into a file, starting at file offset 0.
///
/// This requires the DMA interface, which QEMU supports since version 2.9.
pub fn write_to_file(&mut self, file: &FwCfgFile, data: &[u8]) -> Result<(), FwCfgWriteError> {
let has_dma = (self.feature_bitmap() & feature_bitmasks::HAS_DMA) != 0;
if !has_dma {
return Err(FwCfgWriteError::DmaNotAvailable);
}
let control = (file.key() as u32) << 16 | FwCfgDmaAccess::WRITE | FwCfgDmaAccess::SELECT;
let access = FwCfgDmaAccess::new(control, data.as_ptr() as _, data.len());
// `data` and `access` initialization must not be reordered to after this:
compiler_fence(Ordering::Release);
match &mut self.mode {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Mode::IOPort => unsafe { arch::start_dma(&access) },
Mode::MemoryMapped(device) => device.start_dma(&access),
}
loop {
let control = access.read_control();
if (control & FwCfgDmaAccess::ERROR) != 0 {
return Err(FwCfgWriteError::DmaFailed);
}
if control == 0 {
return Ok(());
}
}
}

fn select(&mut self, key: u16) {
unsafe {
Self::write_selector(key);
match &mut self.mode {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Mode::IOPort => unsafe { arch::write_selector(key) },
Mode::MemoryMapped(device) => device.write_selector(key),
}
}

fn read(&mut self, buffer: &mut [u8]) {
unsafe {
Self::read_data(buffer);
match &mut self.mode {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Mode::IOPort => unsafe { arch::read_data(buffer) },
Mode::MemoryMapped(device) => device.read_data(buffer),
}
}
}

const _: () = assert!(size_of::<FwCfgFile>() == 64);

/// A struct that contains information of a fw_cfg file.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
// NOTE: The memory layout of this struct must match this exactly:
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L132-137
#[repr(C)]
Expand Down Expand Up @@ -225,3 +328,99 @@ impl FwCfgFile {
unsafe { &mut *ptr }
}
}

impl fmt::Debug for FwCfgFile {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("FwCfgFile")
.field("key", &self.key())
.field("size", &self.size())
.field("name", &self.name())
.finish()
}
}

#[derive(Debug)]
struct MemoryMappedDevice {
base_ptr: *mut (),
}

impl MemoryMappedDevice {
unsafe fn new(base_ptr: *mut ()) -> Self {
Self { base_ptr }
}

fn register<T>(&self, offset_in_bytes: usize) -> *mut T {
let offset = offset_in_bytes / size_of::<T>();
unsafe { self.base_ptr.cast::<T>().add(offset) }
}

fn write_selector(&mut self, key: u16) {
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L87
let selector_offset = 8;
let selector_ptr = self.register::<u16>(selector_offset);
unsafe { selector_ptr.write_volatile(key.to_be()) }
}

fn read_data(&mut self, data: &mut [u8]) {
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L88
let data_offset = 0;
let data_ptr = self.register::<usize>(data_offset);
for chunk in data.chunks_mut(size_of::<usize>()) {
let word = unsafe { data_ptr.read_volatile() };
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L53
// "string-preserving" means native-endian
let bytes = word.to_ne_bytes();
chunk.copy_from_slice(&bytes[..chunk.len()]);
}
}

fn start_dma(&self, access: &FwCfgDmaAccess) {
let address = access as *const FwCfgDmaAccess as u64;
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L89
let offset = 16;
let dma_address_register: *mut u32 = self.register(offset);
unsafe {
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L167
// The DMA address register is 64-bit and big-endian.
// Writing its lower half is what triggers DMA,
// so write these half separately to control their order:
let register_high = dma_address_register;
let register_low = dma_address_register.add(1); // One u32
let address_high = (address >> 32) as u32;
let address_low = address as u32;
register_high.write_volatile(address_high.to_be());
compiler_fence(Ordering::AcqRel);
register_low.write_volatile(address_low.to_be());
}
}
}

#[derive(Debug)]
// NOTE: The memory layout of this struct must match this exactly:
// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L177-181
#[repr(C)]
struct FwCfgDmaAccess {
control_be: UnsafeCell<u32>,
length_be: u32,
address_be: u64,
}

impl FwCfgDmaAccess {
const ERROR: u32 = 1 << 0;
const _READ: u32 = 1 << 1;
const _SKIP: u32 = 1 << 2;
const SELECT: u32 = 1 << 3;
const WRITE: u32 = 1 << 4;

fn new(control: u32, ptr: *mut (), length: usize) -> Self {
Self {
control_be: UnsafeCell::new(control.to_be()),
length_be: u32::try_from(length).unwrap().to_be(),
address_be: u64::try_from(ptr as usize).unwrap().to_be(),
}
}

fn read_control(&self) -> u32 {
u32::from_be(unsafe { self.control_be.get().read_volatile() })
}
}
Loading