Skip to content
Closed
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: test
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.10.0"
fdt = "0.1.3"
99 changes: 88 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

use core::fmt;
use core::mem::size_of;

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Expand All @@ -52,10 +53,17 @@ pub enum FwCfgError {

/// A struct for accessing QEMU fw_cfg.
#[derive(Debug)]
pub struct FwCfg(());
pub struct FwCfg(Mode);

#[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 +72,35 @@ 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> {
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);

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 an iterator of all files in the fw_cfg directory
Expand Down Expand Up @@ -163,22 +190,26 @@ impl FwCfg {
}

fn select(&mut self, key: u16) {
unsafe {
Self::write_selector(key);
match &mut self.0 {
#[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.0 {
#[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 +256,49 @@ 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()]);
}
}
}
16 changes: 7 additions & 9 deletions src/x86.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::FwCfg;
use core::arch::asm;

// https://gitlab.com/qemu-project/qemu/-/blob/v7.0.0/docs/specs/fw_cfg.txt#L79
const IO_PORT_SELECTOR: u16 = 0x510;
const IO_PORT_DATA: u16 = 0x511;

Expand All @@ -22,14 +22,12 @@ unsafe fn out_u16(address: u16, data: u16) {
);
}

impl FwCfg {
pub(crate) unsafe fn write_selector(key: u16) {
out_u16(IO_PORT_SELECTOR, key);
}
pub(crate) unsafe fn write_selector(key: u16) {
out_u16(IO_PORT_SELECTOR, key);
}

pub(crate) unsafe fn read_data(buffer: &mut [u8]) {
for i in buffer {
*i = in_u8(IO_PORT_DATA);
}
pub(crate) unsafe fn read_data(buffer: &mut [u8]) {
for i in buffer {
*i = in_u8(IO_PORT_DATA);
}
}
8 changes: 5 additions & 3 deletions tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
#![no_main]
#![cfg_attr(feature = "alloc", feature(default_alloc_error_handler))]

use qemu_fw_cfg::FwCfg;
use core::fmt::Write;

mod shared;

const DATA_INPUT_TXT: &'static [u8] = include_bytes!("input.txt");

#[no_mangle]
#[cfg_attr(not(target_arch = "riscv32"), no_mangle)]
fn main() {
let mut fw_cfg = unsafe { FwCfg::new().unwrap() };
let mut fw_cfg = unsafe { shared::fw_cfg() };

// File exist
let file_input_txt = fw_cfg.find_file("opt/input.txt").unwrap();
Expand Down Expand Up @@ -54,4 +54,6 @@ fn main() {
let mut buffer = [0u8; DATA_INPUT_TXT.len() / 2];
fw_cfg.read_file_to_buffer(&file_input_txt, &mut buffer);
assert_eq!(DATA_INPUT_TXT[..buffer.len()], buffer);

writeln!(shared::Writer, "✅ Test sucessful").unwrap();
}
37 changes: 22 additions & 15 deletions tests/runner.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
#!/bin/sh
#!/bin/bash

TARGET=$(echo $1 | sed 's/.*target\/\([^\/]*\).*/\1/')
ARCH=$(echo $TARGET | awk -F '-' '{ print $1 }')

if [ "$ARCH" = "i686" ]; then
qemu-system-i386 \
-kernel $1 \
-m 32M \
-display none \
-fw_cfg opt/input.txt,file=tests/input.txt \
-fw_cfg opt/567890123456789012345678901234567890123456789012345,file=tests/input.txt \
-device isa-debug-exit \
-serial stdio

status=$(($? >> 1))
fi
QEMU_OPTS=" -m 32M"
QEMU_OPTS+=" -display none"
QEMU_OPTS+=" -fw_cfg opt/input.txt,file=tests/input.txt"
QEMU_OPTS+=" -fw_cfg opt/567890123456789012345678901234567890123456789012345,file=tests/input.txt"
QEMU_OPTS+=" -serial stdio"

if [ $status -gt 0 ]; then
exit $status
if [[ "$ARCH" == i686 ]]; then
exec qemu-system-i386 $QEMU_OPTS \
-device isa-debug-exit \
-kernel "$@"
elif [[ "$ARCH" == riscv32* ]]; then
if [[ -z "$GDB" ]]; then
exec qemu-system-riscv32 $QEMU_OPTS \
-machine virt \
-bios none \
-kernel "$@"
else
exec riscv32-elf-gdb -ex 'target remote :1234' "$@"
fi
else
echo Unsupported TARGET=$TARGET
exit 1
fi
5 changes: 5 additions & 0 deletions tests/shared/i686/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::arch::{asm, global_asm};
use qemu_fw_cfg::FwCfg;

global_asm!(include_str!("boot.asm"));

Expand Down Expand Up @@ -31,3 +32,7 @@ impl core::fmt::Write for Writer {
Ok(())
}
}

pub unsafe fn fw_cfg() -> FwCfg {
FwCfg::new_for_x86().unwrap()
}
4 changes: 4 additions & 0 deletions tests/shared/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#[path = "i686/mod.rs"]
mod arch;

#[cfg(target_arch = "riscv32")]
#[path = "riscv32/mod.rs"]
mod arch;

pub use arch::*;

use core::{
Expand Down
12 changes: 12 additions & 0 deletions tests/shared/riscv32/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
MEMORY
{
RAM : ORIGIN = 0x80000000, LENGTH = 16M
}

REGION_ALIAS("REGION_TEXT", RAM);
REGION_ALIAS("REGION_RODATA", RAM);

REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);
57 changes: 57 additions & 0 deletions tests/shared/riscv32/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use core::ptr::null_mut;
use core::sync::atomic::{AtomicPtr, Ordering};
use qemu_fw_cfg::FwCfg;

static EXIT: AtomicPtr<u32> = AtomicPtr::new(null_mut());
static UART: AtomicPtr<u8> = AtomicPtr::new(null_mut());
static FW_CFG: AtomicPtr<()> = AtomicPtr::new(null_mut());

#[riscv_rt::entry]
fn main(_hart_id: usize, fdt_address: usize) -> ! {
let fdt = unsafe { fdt::Fdt::from_ptr(fdt_address as _).unwrap() };
find_compatible_reg(&fdt, "sifive,test1", &EXIT);
find_compatible_reg(&fdt, "ns16550a", &UART);
find_compatible_reg(&fdt, "qemu,fw-cfg-mmio", &FW_CFG);
crate::main();
exit(0)
}

fn find_compatible_reg<T>(fdt: &fdt::Fdt, with: &str, ptr: &AtomicPtr<T>) {
ptr.store(
fdt.find_compatible(&[with])
.unwrap()
.reg()
.unwrap()
.next()
.unwrap()
.starting_address as _,
Ordering::Release,
)
}

#[no_mangle]
pub extern "C" fn exit(status: u8) -> ! {
unsafe {
let ptr = EXIT.load(Ordering::Acquire);
ptr.write_volatile((status as u32) << 16 | 0x3333);
}
loop {}
}

pub struct Writer;

impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let uart = UART.load(Ordering::Acquire);
for b in s.bytes() {
unsafe {
uart.write_volatile(b);
}
}
Ok(())
}
}

pub unsafe fn fw_cfg() -> FwCfg {
FwCfg::new_memory_mapped(FW_CFG.load(Ordering::Acquire)).unwrap()
}