Skip to content

Commit

Permalink
Provide C++ FFI interface, bindings, meson integration
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianFreudiger committed Sep 11, 2023
1 parent 5889b99 commit 2dfa194
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ version = "0.0.1"
edition = "2021"
license = "MIT"

[lib]
crate-type = ["lib", "staticlib"]

[[bin]]
name = "nic-emu"
required-features = ["build-binary"]

[features]
default = ["build-binary"]
build-binary = ["pretty_env_logger", "tun-tap", "polling", "libvfio-user"]
generate-bindings = ["cbindgen"]

[dependencies]
# Lib dependencies:
Expand All @@ -36,3 +40,6 @@ rev = "667916a90288883af6fb782d1a74ed0590b7c58f"

# Local development version, with libvfio-user-rs being in the same parent directory
#path = "../libvfio-user-rs/libvfio-user"

[build-dependencies]
cbindgen = { version = "0.25.0", optional = true }
23 changes: 23 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[cfg(feature = "generate-bindings")]
use {std::env, std::path::PathBuf};

fn main() {
#[cfg(feature = "generate-bindings")]
{
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());

// Not ideal to navigate by .. but there isn't a good alternative until cargo provides some
// sort of artifacts dir, see https://github.com/rust-lang/cargo/issues/9096
let bindings_path = out_path.join("../../../include/nic-emu.hpp");

cbindgen::Builder::new()
.with_crate(crate_dir)
.with_pragma_once(true)
.with_include_version(true)
.exclude_item("DESCRIPTOR_BUFFER_SIZE")
.generate()
.expect("Unable to generate bindings")
.write_to_file(bindings_path);
}
}
55 changes: 55 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Not meant for standalone usage! The crate is still compiled via cargo!
# Only provided for easing integration of the library in meson parent projects.
# Exposing cargo's output as a prebuilt binary dependency

# Example subproject integration:
# nic_emu = subproject('nic-emu')
# nic_emu_dep = nic_emu.get_variable('nic_emu_dep')
# executable(..., dependencies : [nic_emu_dep, ...])

project(
'nic-emu',
version: '0.0.1',
license: 'MIT',
)

if not meson.is_subproject()
warning('nic-emu is not a standalone meson project!')
endif

cargo = find_program('cargo')
cargo_command = [
cargo,
'build',
'--lib',
'--no-default-features',
'--features',
'generate-bindings'
]

buildtype = get_option('buildtype')
rust_buildtype = 'debug'
if buildtype == 'release' or buildtype == 'minsize'
rust_buildtype = 'release'
cargo_command += '--release'
endif

message('Building nic-emu using cargo')
run_command(
cargo_command,
check: true
)

nic_emu_build_path = 'target' / rust_buildtype

cc = meson.get_compiler('cpp')
nic_emu_lib = cc.find_library(
'nic_emu',
dirs: meson.current_source_dir() / nic_emu_build_path,
)

# Include this dependency in your executable
nic_emu_dep = declare_dependency(
dependencies: [nic_emu_lib],
include_directories: include_directories(nic_emu_build_path / 'include'),
)
98 changes: 98 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::e1000::E1000;
use crate::NicContext;
use log::error;
use std::slice::from_raw_parts_mut;

// General FFI interface

type SendCallback = unsafe extern "C" fn(buffer: *const u8, len: usize);
type DmaReadCallback = unsafe extern "C" fn(dma_address: usize, buffer: *mut u8, len: usize);
type DmaWriteCallback = unsafe extern "C" fn(dma_address: usize, buffer: *const u8, len: usize);
type IssueInterruptCallback = unsafe extern "C" fn();

#[repr(C)]
struct FfiCallbacks {
send_cb: SendCallback,
dma_read_cb: DmaReadCallback,
dma_write_cb: DmaWriteCallback,
issue_interrupt_cb: IssueInterruptCallback,
}

impl NicContext for FfiCallbacks {
fn send(&mut self, buffer: &[u8]) -> anyhow::Result<usize> {
unsafe {
(self.send_cb)(buffer.as_ptr(), buffer.len());
}

// Assume everything went well...
Ok(buffer.len())
}

fn dma_read(&mut self, address: usize, buffer: &mut [u8]) {
unsafe {
(self.dma_read_cb)(address, buffer.as_mut_ptr(), buffer.len());
}
}

fn dma_write(&mut self, address: usize, buffer: &[u8]) {
unsafe {
(self.dma_write_cb)(address, buffer.as_ptr(), buffer.len());
}
}

fn trigger_interrupt(&mut self) {
unsafe { (self.issue_interrupt_cb)() }
}
}

// E1000 FFI Interface

struct E1000FFI {
e1000: E1000<FfiCallbacks>,
}

impl E1000FFI {
#[no_mangle]
pub extern "C" fn new_e1000(callbacks: FfiCallbacks) -> *mut E1000FFI {
let e1000_ffi = E1000FFI {
e1000: E1000::new(callbacks),
};
Box::into_raw(Box::new(e1000_ffi))
}

#[no_mangle]
pub extern "C" fn drop_e1000(e1000_ffi: *mut E1000FFI) {
unsafe {
// Box will free on drop
let _ = Box::from_raw(e1000_ffi);
}
}

#[no_mangle]
pub extern "C" fn e1000_region_access(
&mut self, bar: u8, offset: usize, data_ptr: *const u8, data_len: usize, write: bool,
) -> bool {
let data = unsafe { from_raw_parts_mut(data_ptr as *mut u8, data_len) };

let result = match bar {
0 => self.e1000.region_access_bar0(offset, data, write),
1 => self.e1000.region_access_bar1(offset, data, write),
_ => {
error!("Unknown bar {}", bar);
return false;
}
};

if let Err(e) = result {
error!("Error accessing bar {}: {}", bar, e);
false
} else {
true
}
}

#[no_mangle]
pub extern "C" fn e1000_reset(&mut self) {
self.e1000.reset_e1000();
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Result;

pub mod e1000;
mod ffi;
mod util;

pub trait NicContext {
Expand Down

0 comments on commit 2dfa194

Please sign in to comment.