* [PATCH] rust: support DPDK API
@ 2025-03-06 13:37 Gregory Etelson
2025-03-06 19:26 ` Van Haaren, Harry
0 siblings, 1 reply; 2+ messages in thread
From: Gregory Etelson @ 2025-03-06 13:37 UTC (permalink / raw)
To: dev; +Cc: getelson, thomas, mkashani, Bruce Richardson
The patch converts include files with DPDK API to RUST and binds new
RUST API files info dpdklib package.
The RUST dpdklib files and DPDK libraries build from C sources
allow creation of DPDK application in RUST.
RUST DPDK application must specify the `dpdklib` package as
dependency in Cargo.toml file.
RUST `dpdklib` package is installed into
MESON_INSTALL_DESTDIR_PREFIX/rust directory.
Software requirements:
- clang
- RUST installation
- bindgen-cli crate
RUST dpdklib installation instructions:
1. Configure DPDK with `-Deanble_rust=true`
2. Build and install DPDK. The installation procedure will create
MESON_INSTALL_DESTDIR_PREFIX/rust directory.
3. Update PKG_CONFIG_PATH to point to DPDK installation.
Signed-off-by: Gregory Etelson <getelson@nvidia.com>
---
buildtools/meson.build | 4 +
buildtools/rust-env.sh | 78 +++++++++++
examples/rust/helloworld/Cargo.lock | 14 ++
examples/rust/helloworld/Cargo.toml | 7 +
examples/rust/helloworld/build.rs | 21 +++
examples/rust/helloworld/src/main.rs | 197 +++++++++++++++++++++++++++
meson_options.txt | 2 +
7 files changed, 323 insertions(+)
create mode 100755 buildtools/rust-env.sh
create mode 100644 examples/rust/helloworld/Cargo.lock
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..dd16567f51 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')
+endif
diff --git a/buildtools/rust-env.sh b/buildtools/rust-env.sh
new file mode 100755
index 0000000000..705bc0d95b
--- /dev/null
+++ b/buildtools/rust-env.sh
@@ -0,0 +1,78 @@
+#! /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
+'
+
+rust_dir="${MESON_INSTALL_DESTDIR_PREFIX}/rust"
+include_dir="${MESON_INSTALL_DESTDIR_PREFIX}/include"
+
+if test -d "$rust_dir"; then
+ rm -rf "$rust_dir"
+fi
+
+mkdir -p "$rust_dir/src"
+if ! test -d "$rust_dir"; then
+ echo "failed to create Rust library $rust_dir"
+ exit 255
+fi
+touch "$rust_dir/src/lib.rs"
+
+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/$base.rs /tmp/$base.i -- $bindgen_clang_opt
+ rm -f /tmp/$base.i /tmp/$base.h
+}
+
+for file in $files; do
+ base=$(basename $file | cut -d. -f 1)
+ create_rust_lib $base
+ echo "pub mod $base;" >> "$rust_dir/src/lib.rs"
+done
+
+
+cat > "$rust_dir/Cargo.toml" <<EOF
+[package]
+name = "dpdklib"
+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/rte_ethdev.rs"
diff --git a/examples/rust/helloworld/Cargo.lock b/examples/rust/helloworld/Cargo.lock
new file mode 100644
index 0000000000..d18af80c66
--- /dev/null
+++ b/examples/rust/helloworld/Cargo.lock
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "dpdklib"
+version = "25.3.0-rc1"
+
+[[package]]
+name = "helloworld"
+version = "0.1.0"
+dependencies = [
+ "dpdklib",
+]
diff --git a/examples/rust/helloworld/Cargo.toml b/examples/rust/helloworld/Cargo.toml
new file mode 100644
index 0000000000..7f9a77968a
--- /dev/null
+++ b/examples/rust/helloworld/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdklib = {path = "MESON_INSTALL_DESTDIR_PREFIX/rust"}
\ No newline at end of file
diff --git a/examples/rust/helloworld/build.rs b/examples/rust/helloworld/build.rs
new file mode 100644
index 0000000000..5c76188e18
--- /dev/null
+++ b/examples/rust/helloworld/build.rs
@@ -0,0 +1,21 @@
+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..]);
+ }
+ }
+ }
+ 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..cf666aece6
--- /dev/null
+++ b/examples/rust/helloworld/src/main.rs
@@ -0,0 +1,197 @@
+/// Usage: helloworld -a <port 1 params> -a <port 2 params> ...
+
+use std::env;
+use std::ffi::{CString};
+use std::os::raw::{c_int, c_char};
+use std::ffi::CStr;
+
+use dpdklib::rte_eal::{
+ // Functions
+ rte_eal_init,
+ rte_eal_cleanup,
+};
+
+use dpdklib::rte_ethdev::{
+ RTE_ETH_NAME_MAX_LEN,
+ RTE_ETH_DEV_NO_OWNER,
+ RTE_ETH_RSS_IP,
+ rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_RSS,
+ rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_VMDQ_DCB_RSS,
+
+ // Structures
+ rte_eth_dev_info,
+ rte_eth_conf,
+ rte_eth_txconf,
+ rte_eth_rxconf,
+
+ // Functions
+ rte_eth_dev_get_name_by_port,
+ rte_eth_find_next_owned_by,
+ rte_eth_dev_info_get,
+ rte_eth_dev_configure,
+ rte_eth_tx_queue_setup,
+ rte_eth_rx_queue_setup,
+ rte_eth_dev_start,
+};
+
+use dpdklib::rte_build_config::{
+ RTE_MAX_ETHPORTS,
+};
+
+use dpdklib::rte_mbuf::{
+ rte_pktmbuf_pool_create,
+};
+
+use dpdklib::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 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 init_port_config(port: &mut Port) {
+ let ret = unsafe {
+ rte_eth_dev_info_get(port.port_id, &mut port.dev_info as *mut rte_eth_dev_info)
+ };
+ if ret != 0 {
+ panic!("port-{}: failed to get dev info {ret}", port.port_id);
+ }
+
+ port.dev_conf.rx_adv_conf.rss_conf.rss_key = std::ptr::null_mut();
+ port.dev_conf.rx_adv_conf.rss_conf.rss_hf = if port.rxq_num > 1 {
+ RTE_ETH_RSS_IP as u64 & port.dev_info.flow_type_rss_offloads
+ } else {0};
+
+ if port.dev_conf.rx_adv_conf.rss_conf.rss_hf != 0 {
+ port.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;
+ }
+}
+
+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}");
+ });
+
+}
+unsafe fn start_port(port:&mut Port) {
+ let mut rc = unsafe {
+ rte_eth_dev_configure(port.port_id, port.rxq_num, port.txq_num,
+ &port.dev_conf as *const rte_eth_conf)
+ };
+ if rc != 0 { panic!("failed to configure port-{}: {rc}", port.port_id)}
+ println!("port-{} configured", port.port_id);
+
+ rc = unsafe {
+ rte_eth_tx_queue_setup(port.port_id, 0, 64, 0, 0 as *const rte_eth_txconf)
+ };
+ if rc != 0 { panic!("port-{}: failed to configure TX queue 0 {rc}", port.port_id)}
+ println!("port-{} configured TX queue 0", port.port_id);
+
+ let mbuf_pool_name = CString::new(format!("mbuf pool port-{}", port.port_id)).unwrap();
+ let mbuf_pool : *mut dpdklib::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 dpdklib::rte_mbuf::rte_mempool {
+ panic!("port-{}: failed to allocate mempool {rc}", port.port_id)
+ }
+ println!("port-{} mempool ready", port.port_id);
+
+ let mut rxq_conf:rte_eth_rxconf = port.dev_info.default_rxconf.clone();
+ rxq_conf.offloads = 0;
+ rc = unsafe {
+ rte_eth_rx_queue_setup(port.port_id, 0, 64, 0,
+ &mut rxq_conf as *mut rte_eth_rxconf,
+ mbuf_pool as *mut dpdklib::rte_ethdev::rte_mempool)
+ };
+ if rc != 0 { panic!("port-{}: failed to configure RX queue 0 {rc}", port.port_id)}
+ println!("port-{} configured RX queue 0", port.port_id);
+ rc = unsafe {
+ rte_eth_dev_start(port.port_id)
+ };
+ if rc != 0 { panic!("failed to start port-{}: {rc}", port.port_id)}
+ println!("port-{} started", port.port_id);
+}
+
+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(dpdklib::rte_build_config::RTE_MAX_ETHPORTS as usize) {
+ let mut port = Port::new(port_id);
+ init_port_config(&mut port);
+ println!("init port {port_id}");
+ start_port(&mut 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
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH] rust: support DPDK API
2025-03-06 13:37 [PATCH] rust: support DPDK API Gregory Etelson
@ 2025-03-06 19:26 ` Van Haaren, Harry
0 siblings, 0 replies; 2+ messages in thread
From: Van Haaren, Harry @ 2025-03-06 19:26 UTC (permalink / raw)
To: Gregory Etelson, dev; +Cc: thomas, mkashani, Richardson, Bruce
> From: Gregory Etelson <getelson@nvidia.com>
> Sent: Thursday, March 6, 2025 1:37 PM
> To: dev@dpdk.org <dev@dpdk.org>
> Cc: getelson@nvidia.com <getelson@nvidia.com>; thomas@monjalon.net <thomas@monjalon.net>; mkashani@nvidia.com <mkashani@nvidia.com>; Richardson, Bruce <bruce.richardson@intel.com>
> Subject: [PATCH] rust: support DPDK API
Cool, I like this subject, great!
> The patch converts include files with DPDK API to RUST and binds new
> RUST API files info dpdklib package.
>
> The RUST dpdklib files and DPDK libraries build from C sources
> allow creation of DPDK application in RUST.
>
> RUST DPDK application must specify the `dpdklib` package as
> dependency in Cargo.toml file.
>
> RUST `dpdklib` package is installed into
> MESON_INSTALL_DESTDIR_PREFIX/rust directory.
>
> Software requirements:
> - clang
> - RUST installation
> - bindgen-cli crate
>
> RUST dpdklib installation instructions:
> 1. Configure DPDK with `-Deanble_rust=true`
> 2. Build and install DPDK. The installation procedure will create
> MESON_INSTALL_DESTDIR_PREFIX/rust directory.
> 3. Update PKG_CONFIG_PATH to point to DPDK installation.
Interesting approach to automate it; are there specific reasons that this approach was taken,
or did this just seem the easiest/best way to include "Rust support" into upstream DPDK?
Alternatives could be (*not* suggesting to rework the patch!) a dpdk-sys
crate where the bindgen etc is done externally to DPDK itself. Many existing
approaches (some examples: https://youtu.be/lb6xn2xQ-NQ?t=130) use that method.
I kind of like (this approach) of having the binding generation upstream with DPDK itself;
it makes the Rust support upstream and keeps it close to DPDK... however it
means that it is "the one" official/DPDK-community-approved library/crate.
There's some nice tidy-ups to cleanup the namespaces possible if this is "the crate".
Perhaps (sorry, borderline bikeshed-topics..) renames for clarity & readability, e.g.:
dpdklib::rte_eal::rte_eal_init()
to
dpdk::eal::init()
> Signed-off-by: Gregory Etelson <getelson@nvidia.com>
I'll get to a testing & review in the next days, however I'd like to ask some bigger picture
questions, to understand/provide input on approach, and how big an effor to expect here!
See above linked youtube video - I had sketched out some API concepts for exposing a
"safe Rust API" wrapper around the DPDK C API, which also encodes threading requirements.
Some questions:
- Is there an overaching "we're trying to achieve X with Rust", or specifically "safe Rust"?
- Is this patch the "get DPDK + Rust working", with follow up patches for "safe wrappers" the intention?
- Or is this patch all to expect for now?
Again, thanks for the patch, I think Rust is important for DPDK & infrastructure software,
and will try make time to help test/review/discuss this patch, and the wider Rust effort!
Regards, -Harry
<snip code itself>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2025-03-06 19:26 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-03-06 13:37 [PATCH] rust: support DPDK API Gregory Etelson
2025-03-06 19:26 ` Van Haaren, Harry
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).