The patch converts include files with DPDK API to RUST and binds new RUST API files into raw module under dpdk crate.
The RUST files and DPDK libraries build from C sources allow creation of DPDK application in RUST. RUST DPDK application must specify the `dpdk` crate as dependency in Cargo.toml file. RUST `dpdk` crate is installed into $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust directory. Software requirements: - clang - RUST installation - bindgen-cli crate RUST dpdk installation instructions: 1. Configure DPDK with `-Deanble_rust=true` 2. Build and install DPDK. The installation procedure will create $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust crate. 3. Update PKG_CONFIG_PATH to point to DPDK installation. Signed-off-by: Gregory Etelson <getel...@nvidia.com> --- v2: Change rust crate name from dpdklib to dpdk. Add raw module for to link with C API. Add "cargo:rerun-if-changed=build.rs". v3: Move init_port_config() to Port. Move start_port() to Port. Remove Cargo.lock from git repository Reformat code. --- buildtools/meson.build | 4 + buildtools/rust-env.sh | 81 ++++++++++ examples/rust/helloworld/Cargo.toml | 7 + examples/rust/helloworld/build.rs | 24 +++ examples/rust/helloworld/src/main.rs | 219 +++++++++++++++++++++++++++ meson_options.txt | 2 + 6 files changed, 337 insertions(+) create mode 100755 buildtools/rust-env.sh create mode 100644 examples/rust/helloworld/Cargo.toml create mode 100644 examples/rust/helloworld/build.rs create mode 100644 examples/rust/helloworld/src/main.rs diff --git a/buildtools/meson.build b/buildtools/meson.build index 4e2c1217a2..b9d0092f07 100644 --- a/buildtools/meson.build +++ b/buildtools/meson.build @@ -50,3 +50,7 @@ else pmdinfo += 'ar' pmdinfogen += 'elf' endif + +if get_option('enable_rust') + meson.add_install_script(['rust-env.sh', get_option('libdir')]) +endif diff --git a/buildtools/rust-env.sh b/buildtools/rust-env.sh new file mode 100755 index 0000000000..fe0877643b --- /dev/null +++ b/buildtools/rust-env.sh @@ -0,0 +1,81 @@ +#! /bin/sh + +# Convert DPDK API files into RUST. +# DPDK files selection is on demand. +# +# The coversion is done in 4 stages: +# 1. Preparation [Optional] +# Due to the bindgen conversion utility limitations source file may need +# manual adjustment. +# 2. Preprocessing [Mandatory] +# Run preprocessor on a source file before conversion. +# 3. Conversion [Mandatory] +# Convert preprocessed C file into RUST file +# 4. Post translation [Optional] +# Manually fix translation. + +# DPDK files list +files=' +rte_build_config.h +rte_eal.h +rte_ethdev.h +rte_mbuf.h +rte_mbuf_core.h +rte_mempool.h +' +libdir="$1" +rust_dir="${MESON_INSTALL_DESTDIR_PREFIX}/$libdir/rust" +include_dir="${MESON_INSTALL_DESTDIR_PREFIX}/include" + +if test -d "$rust_dir"; then + rm -rf "$rust_dir" +fi + +mkdir -p "$rust_dir/src/raw" +if ! test -d "$rust_dir"; then + echo "failed to create Rust library $rust_dir" + exit 255 +fi + +bindgen_opt='--no-layout-tests --no-derive-debug' +bindgen_clang_opt='-Wno-unused-command-line-argument' + +create_rust_lib () +{ + base=$1 + + cp $include_dir/${base}.h /tmp/${base}.h + +# bindgen cannot process complex macro definitions +# manually simplify macros before conversion + sed -i -e 's/RTE_BIT64(\([0-9]*\))/(1UL << \1)/g' /tmp/${base}.h + sed -i -e 's/RTE_BIT32(\([0-9]*\))/(1U << \1)/g' /tmp/${base}.h + sed -i -e 's/UINT64_C(\([0-9]*\))/\1/g' /tmp/${base}.h + + # clang output has better integration with bindgen than GCC + clang -E -dD -I$include_dir /tmp/${base}.h > /tmp/$base.i + bindgen $bindgen_opt --output $rust_dir/src/raw/$base.rs /tmp/$base.i -- $bindgen_clang_opt + rm -f /tmp/$base.i /tmp/$base.h +} + +echo 'pub mod raw;' > "$rust_dir/src/lib.rs" + +touch "$rust_dir/src/raw/mod.rs" +for file in $files; do + base=$(basename $file | cut -d. -f 1) + create_rust_lib $base + echo "pub mod $base;" >> "$rust_dir/src/raw/mod.rs" +done + +cat > "$rust_dir/Cargo.toml" <<EOF +[package] +name = "dpdk" +version = "$(cat ${MESON_SOURCE_ROOT}/VERSION | sed 's/\.0\([1-9]\)/\.\1/')" +EOF + +# post conversion updates +# RUST does not accept aligned structures into packed structure. +# TODO: fix DPDK definitions. +sed -i 's/repr(align(2))/repr(packed(2))/g' "$rust_dir/src/raw/rte_ethdev.rs" + +echo "Install RUST DPDK crate in $rust_dir" diff --git a/examples/rust/helloworld/Cargo.toml b/examples/rust/helloworld/Cargo.toml new file mode 100644 index 0000000000..ceeecf958d --- /dev/null +++ b/examples/rust/helloworld/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "helloworld" +version = "0.1.0" +edition = "2024" + +[dependencies] +dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust } diff --git a/examples/rust/helloworld/build.rs b/examples/rust/helloworld/build.rs new file mode 100644 index 0000000000..bd5737d209 --- /dev/null +++ b/examples/rust/helloworld/build.rs @@ -0,0 +1,24 @@ +use std::process::Command; + +pub fn main() { + let mut pkgconfig = Command::new("pkg-config"); + + match pkgconfig.args(["--libs", "libdpdk"]).output() { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout) + .trim_end() + .to_string(); + for token in stdout.split_ascii_whitespace().filter(|s| !s.is_empty()) { + if token.starts_with("-L") { + println!("cargo::rustc-link-search=native={}", &token[2..]); + } else if token.starts_with("-l") { + println!("cargo::rustc-link-lib={}", &token[2..]); + } + } + println!("cargo:rerun-if-changed=build.rs"); + } + Err(error) => { + panic!("failed to read libdpdk package: {:?}", error); + } + } +} diff --git a/examples/rust/helloworld/src/main.rs b/examples/rust/helloworld/src/main.rs new file mode 100644 index 0000000000..0c057cb39f --- /dev/null +++ b/examples/rust/helloworld/src/main.rs @@ -0,0 +1,219 @@ +/// Usage: helloworld -a <port 1 params> -a <port 2 params> ... +use std::env; +use std::ffi::CStr; +use std::ffi::CString; +use std::os::raw::{c_char, c_int}; + +use dpdk::raw::rte_eal::{ + rte_eal_cleanup, + // Functions + rte_eal_init, +}; + +use dpdk::raw::rte_ethdev::{ + RTE_ETH_DEV_NO_OWNER, + RTE_ETH_NAME_MAX_LEN, + RTE_ETH_RSS_IP, + rte_eth_conf, + rte_eth_dev_configure, + // Functions + rte_eth_dev_get_name_by_port, + // Structures + rte_eth_dev_info, + rte_eth_dev_info_get, + rte_eth_dev_start, + rte_eth_find_next_owned_by, + rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_RSS, + rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_VMDQ_DCB_RSS, + + rte_eth_rx_queue_setup, + rte_eth_rxconf, + + rte_eth_tx_queue_setup, + rte_eth_txconf, +}; + +use dpdk::raw::rte_build_config::RTE_MAX_ETHPORTS; + +use dpdk::raw::rte_mbuf::rte_pktmbuf_pool_create; + +use dpdk::raw::rte_mbuf_core::RTE_MBUF_DEFAULT_BUF_SIZE; + +pub type DpdkPort = u16; +pub struct Port { + pub port_id: DpdkPort, + pub dev_info: rte_eth_dev_info, + pub dev_conf: rte_eth_conf, + pub rxq_num: u16, + pub txq_num: u16, +} + +impl Port { + unsafe fn new(id: DpdkPort) -> Self { + Port { + port_id: id, + dev_info: unsafe { + let uninit: ::std::mem::MaybeUninit<rte_eth_dev_info> = + ::std::mem::MaybeUninit::zeroed().assume_init(); + *uninit.as_ptr() + }, + dev_conf: unsafe { + let uninit: ::std::mem::MaybeUninit<rte_eth_conf> = + ::std::mem::MaybeUninit::zeroed().assume_init(); + *uninit.as_ptr() + }, + rxq_num: 1, + txq_num: 1, + } + } + + pub unsafe fn init_port_config(&mut self) { + let ret = unsafe { + rte_eth_dev_info_get(self.port_id, &mut self.dev_info as *mut rte_eth_dev_info) + }; + if ret != 0 { + panic!("self-{}: failed to get dev info {ret}", self.port_id); + } + + self.dev_conf.rx_adv_conf.rss_conf.rss_key = std::ptr::null_mut(); + self.dev_conf.rx_adv_conf.rss_conf.rss_hf = if self.rxq_num > 1 { + RTE_ETH_RSS_IP as u64 & self.dev_info.flow_type_rss_offloads + } else { + 0 + }; + + if self.dev_conf.rx_adv_conf.rss_conf.rss_hf != 0 { + self.dev_conf.rxmode.mq_mode = rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_VMDQ_DCB_RSS + & rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_RSS; + } + } + + unsafe fn start_port(&mut self) { + let mut rc = unsafe { + rte_eth_dev_configure( + self.port_id, + self.rxq_num, + self.txq_num, + &self.dev_conf as *const rte_eth_conf, + ) + }; + if rc != 0 { + panic!("failed to configure self-{}: {rc}", self.port_id) + } + println!("self-{} configured", self.port_id); + + rc = unsafe { rte_eth_tx_queue_setup(self.port_id, 0, 64, 0, 0 as *const rte_eth_txconf) }; + if rc != 0 { + panic!("self-{}: failed to configure TX queue 0 {rc}", self.port_id) + } + println!("self-{} configured TX queue 0", self.port_id); + + let mbuf_pool_name = CString::new(format!("mbuf pool self-{}", self.port_id)).unwrap(); + let mbuf_pool: *mut dpdk::raw::rte_mbuf::rte_mempool = unsafe { + rte_pktmbuf_pool_create( + mbuf_pool_name.as_ptr(), + 1024, + 0, + 0, + RTE_MBUF_DEFAULT_BUF_SIZE as u16, + 0, + ) + }; + if mbuf_pool == 0 as *mut dpdk::raw::rte_mbuf::rte_mempool { + panic!("self-{}: failed to allocate mempool {rc}", self.port_id) + } + println!("self-{} mempool ready", self.port_id); + + let mut rxq_conf: rte_eth_rxconf = self.dev_info.default_rxconf.clone(); + rxq_conf.offloads = 0; + rc = unsafe { + rte_eth_rx_queue_setup( + self.port_id, + 0, + 64, + 0, + &mut rxq_conf as *mut rte_eth_rxconf, + mbuf_pool as *mut dpdk::raw::rte_ethdev::rte_mempool, + ) + }; + if rc != 0 { + panic!("self-{}: failed to configure RX queue 0 {rc}", self.port_id) + } + println!("self-{} configured RX queue 0", self.port_id); + rc = unsafe { rte_eth_dev_start(self.port_id) }; + if rc != 0 { + panic!("failed to start self-{}: {rc}", self.port_id) + } + println!("self-{} started", self.port_id); + } +} + +pub unsafe fn iter_rte_eth_dev_owned_by(owner_id: u64) -> impl Iterator<Item = DpdkPort> { + let mut port_id: DpdkPort = 0 as DpdkPort; + std::iter::from_fn(move || { + let cur = port_id; + port_id = unsafe { rte_eth_find_next_owned_by(cur, owner_id) as DpdkPort }; + if port_id == RTE_MAX_ETHPORTS as DpdkPort { + return None; + } + if cur == port_id { + port_id += 1 + } + Some(cur) + }) +} + +pub unsafe fn iter_rte_eth_dev() -> impl Iterator<Item = DpdkPort> { + unsafe { iter_rte_eth_dev_owned_by(RTE_ETH_DEV_NO_OWNER as u64) } +} + +pub unsafe fn show_ports_summary(ports: &Vec<Port>) { + let mut name_buf: [c_char; RTE_ETH_NAME_MAX_LEN as usize] = + [0 as c_char; RTE_ETH_NAME_MAX_LEN as usize]; + let title = format!("{:<4} {:<32} {:<14}", "Port", "Name", "Driver"); + println!("{title}"); + ports.iter().for_each(|p| unsafe { + let _rc = rte_eth_dev_get_name_by_port(p.port_id, name_buf.as_mut_ptr()); + let name = CStr::from_ptr(name_buf.as_ptr()); + let drv = CStr::from_ptr(p.dev_info.driver_name); + let summary = format!( + "{:<4} {:<32} {:<14}", + p.port_id, + name.to_str().unwrap(), + drv.to_str().unwrap() + ); + println!("{summary}"); + }); +} + +fn main() { + let mut argv: Vec<*mut c_char> = env::args() + .map(|arg| CString::new(arg).unwrap().into_raw()) + .collect(); + + let rc = unsafe { rte_eal_init(env::args().len() as c_int, argv.as_mut_ptr()) }; + if rc == -1 { + unsafe { + rte_eal_cleanup(); + } + } + + let mut ports: Vec<Port> = vec![]; + unsafe { + for port_id in + iter_rte_eth_dev().take(dpdk::raw::rte_build_config::RTE_MAX_ETHPORTS as usize) + { + let mut port = Port::new(port_id); + port.init_port_config(); + println!("init port {port_id}"); + port.start_port(); + ports.push(port); + } + } + + unsafe { + show_ports_summary(&ports); + } + + println!("Hello, world!"); +} diff --git a/meson_options.txt b/meson_options.txt index e49b2fc089..d37b9ba1dc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -60,3 +60,5 @@ option('tests', type: 'boolean', value: true, description: 'build unit tests') option('use_hpet', type: 'boolean', value: false, description: 'use HPET timer in EAL') +option('enable_rust', type: 'boolean', value: false, description: + 'enable RUST') -- 2.45.2