Skip to contentSkip to conversation

rust: support DPDK API

PATCH Superseded Gregory Etelson 10 participants 51 messages Mar 6, 2025 – Apr 17, 2025 Arm/Amlogic Meson SoC support

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 rust: support DPDK API

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.

---
 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
buildtools/meson.build +4 -0
index 4e2c1217a2..dd16567f51 100644
@@ -50,3 +50,7 @@ else
     pmdinfo += 'ar'
     pmdinfogen += 'elf'
 endif
+
+if get_option('enable_rust')
+    meson.add_install_script('rust-env.sh')
+endif
buildtools/rust-env.sh new file +78 -0

78 lines not on this page · raw message

examples/rust/helloworld/Cargo.lock new file +14 -0
new file mode 100644
index 0000000000..d18af80c66
@@ -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",
+]
examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..7f9a77968a
@@ -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
examples/rust/helloworld/build.rs new file +21 -0
new file mode 100644
index 0000000000..5c76188e18
@@ -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);
+        }
+    }
+}
examples/rust/helloworld/src/main.rs new file +197 -0

197 lines not on this page · raw message

meson_options.txt +2 -0
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
Signature · 1 line
2.45.2
#1 lore raw
Quoted 5 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

Cool, I like this subject, great!

Quoted 17 lines

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

Quoted 5 lines

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()

Quoted 1 line

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>

#2 lore raw

Hello Harry,

Quoted 38 lines
Quoted 6 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

Cool, I like this subject, great!

Quoted 17 lines

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

Quoted 5 lines

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.

Quoted 4 lines

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.

My reason for adding the crate directly into upstream DPDK branch was to produce a single centralized RUST version of DPDK API. If DPDK API files are converted externally, there is no control over the result.
Internal crate can help smoothing bindgen translation - DPDK files can be adjusted to reduce manual translation corrections. The crate content will be automatically updated after DPDK API change.
Also, internal crate will increate overall RUST awareness in DPDK comunity.

Quoted 3 lines

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()

The crate exports existing DPDK API to RUST application as it is.
This way RUST application receives stable DPDK API - the same one C applications have.

Quoted 3 lines

to
dpdk::eal::init()

That is not current DPDK API.

Quoted 11 lines
Quoted 1 line

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?

The goal was to create RUST application over existing DPDK API. The patch converts DPDK API files required for the helloworld example. Additional API files will be added on demand.

Quoted 8 lines

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>

Regards,
Gregory

#3 lore raw
In reply to Gregory Etelson
Quoted 5 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

More in-depth review in this reply..

Quoted 17 lines

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

Quoted 5 lines

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.

I've tested these instructions, using the default prefix = "/usr/local" This puts the rust files in "/usr/local/rust", so some tidy-up required. Debugging (or "searching for the files" was difficult - perhaps a log of the bindgen output from "rust-env.sh" would be helpful for 1st time setup?)

Quoted 51 lines
---
 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
buildtools/meson.build +4 -0
index 4e2c1217a2..dd16567f51 100644
@@ -50,3 +50,7 @@ else
     pmdinfo += 'ar'
     pmdinfogen += 'elf'
 endif
+
+if get_option('enable_rust')
+    meson.add_install_script('rust-env.sh')
+endif
buildtools/rust-env.sh new file +17 -0
new file mode 100755
index 0000000000..705bc0d95b
@@ -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

buildtools/rust-env.sh line 27

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+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"

Here "rust_dir"="/usr/local/rust" if the default prefix is used.
At least, a "dpdk" must be in there somewhere (we're not Rust itself after all!) but also different systems put files in e.g: "/usr/local/lib/x86_64-linux-gnu/dpdk"

Appending a "/rust" to the end of that is probably what was intended?

Quoted 20 lines
buildtools/rust-env.sh new file +20 -0
+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
+

buildtools/rust-env.sh line 57

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+  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

# Adding this helps debug the header conversion & output file locations echo "writing Rust converted header to ${rust_dir}/src/${base}.rs"

Quoted 5 lines
buildtools/rust-env.sh new file +5 -0
+  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

buildtools/rust-env.sh line 72

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+  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/')"

Missing {} brackets around MESON_SOURCE_ROOT perhaps? Got an error on "/VERSION" not found.

buildtools/rust-env.sh line 78

Quoted 6 lines
buildtools/rust-env.sh new file +6 -0
+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"

Hmm, interesting, lets keep a TODO here, but as this changes the "allowable" or valid binary layouts, it is potentially going to cause differences between C and Rust's interpretation of the memory backing the structs.
TODO: investigate each struct individually, and determine if/what is really required/correct.

Quoted 26 lines
examples/rust/helloworld/Cargo.lock new file +14 -0
new file mode 100644
index 0000000000..d18af80c66
@@ -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",
+]
examples/rust/helloworld/Cargo.toml new file +0 -0
new file mode 100644
index 0000000000..7f9a77968a

examples/rust/helloworld/Cargo.toml line 7

Quoted 8 lines
examples/rust/helloworld/Cargo.toml new file +7 -0
@@ -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

Above Cargo.toml looks normal. Perhaps we could suggest/provide a method where DPDK does not need to be "ninja install"-ed, but instead process a local directory? That would likely make it easier to locally build/test Rust bindings.

Quoted 17 lines
examples/rust/helloworld/build.rs new file +11 -0
new file mode 100644
index 0000000000..5c76188e18
@@ -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..]);

examples/rust/helloworld/build.rs line 21

Quoted 10 lines
examples/rust/helloworld/build.rs new file +10 -0
+                } else if token.starts_with("-l") {
+                    println!("cargo::rustc-link-lib={}", &token[2..]);
+                }
+            }
+        }
+        Err(error) => {
+            panic!("failed to read libdpdk package: {:?}", error);
+        }
+    }
+}

Could a simple "probe()" like below replace the more complex logic? I've linked DPDK in the past using this more boring method.. or is there a reason that the link-search-native and link-lib parts have to be handled manually? Doing a Command::new() is hacky, nice to remove it if possible!

 if let Err(e) = pkg_config::Config::new().probe("libdpdk") {
    panic!("libdpdk probe failed: {e:?}");

}

The build.rs file itself also needs a "rebuild if self changed" line:
println!("cargo:rerun-if-changed=build.rs");

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +4 -0
new file mode 100644
index 0000000000..cf666aece6
@@ -0,0 +1,197 @@
+/// Usage: helloworld -a <port 1 params> -a <port 2 params> ...
+
+use std::env;
+use std::ffi::{CString};

examples/rust/helloworld/src/main.rs line 14

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+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::{

As per first reply, I'd like to see the end-result safe-Rust namespacing be shortened.

This can be easily done by renaming the name= in Cargo.toml, and removing the "rte_" prefix at bindgen output stage for each file. dpdk::ethdev:: { RTE_ETH_NAME_MAX_LEN, ... };

Perhaps its a good method to put the generated (unsafe, raw-C-bindings) in this namespace, and build a safe crate (with the dpdk::ethdev::* namespacing) over it. Thoughts?

Quoted 35 lines
examples/rust/helloworld/src/main.rs new file +35 -0
+    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;

examples/rust/helloworld/src/main.rs line 59

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+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 {

This is where the first manual code is wrapping C concepts into Rust. And the approach taken here is quite "C thinking", where it is possible to misuse an API, and hence "unsafe" is used for many functions (more below) and also for the C FFI function calls.

I believe this part can be improved, and that there is big value in those improvements:
Today the code is "C but compiled by the Rust compiler", the programmer has to use the APIs correctly.

I'll send a patch in reply to this one for review, suggesting ways to improve.
Future code goal: "Rust checks code: invalid code will not compile, or provide a clean error at runtime."

Quoted 26 lines
examples/rust/helloworld/src/main.rs new file +26 -0
+        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
+        }

examples/rust/helloworld/src/main.rs line 95

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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)
+    }
+}

Cool - I like the egonomics idea of providing Iterator<DpdkPort>. The implementation here is unsafe, which is unfortunate, we'd like to ensure that all "end-user visible" Rust code is "safe Rust".

Quoted 23 lines
examples/rust/helloworld/src/main.rs new file +23 -0
+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}");

examples/rust/helloworld/src/main.rs line 129

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+    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}");
+    });
+
+}

There are some interesting changes of thoughts in Rust vs C, in how applications actually allocate or "own" data. The above show_ports_summary() function requires a &Vec<Port>. That demands that the backing storage for Port is a Vec<Port>, and a (immutable) reference is passed into this function. Hence the type is:
summary(ports: &Vec<Port>)

There is no reason to "demand" that the application stores Ports in a Vec. While common to store things in Vec<T>, we have now demanded "more than required" from the application. In some situations (perhaps not 100% relevant here, but..) like embedded systems, where dynamic allocation isn't available there IS no Vec<T> available. In other application scenarios, perhaps a HashMap is better.

The best way to do this in Rust is to use the Iterator trait, which doesn't require any specific "backing storage" (e.g. linear-data for 'slice' or Vec, or HashMap per-item allocated data, or ...). Instead the Iterator trait allows the function itself to iterate of the "Item". Example:

pub fn show_ports_summary_iterator<'a>(ports: impl Iterator<Item = &'a Port>) {
    ports.for_each(|p| { /* p is a &Port */);
}

There should be no unsafe required for the above debug print, and no statically sized C-string allocations. Instead of fetching the port name via C function at print-time, the Port::new() function can cache the value and convert it to a Rust String type - avoids unsafe {}, reduces scope of unsafe variables in the program.

Quoted 29 lines
examples/rust/helloworld/src/main.rs new file +29 -0
+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,

examples/rust/helloworld/src/main.rs line 168

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+                               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);
+}

Similar concepts around reworking "unsafe" away as above, future discussion.

Quoted 18 lines
examples/rust/helloworld/src/main.rs new file +18 -0
+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);

examples/rust/helloworld/src/main.rs line 197

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+            println!("init port {port_id}");
+            start_port(&mut port);
+            ports.push(port);
+        }
+    }
+
+    unsafe { show_ports_summary(&ports); }
+
+    println!("Hello, world!");
+}

Overall, I'm very happy to see this patch, I want to ensure folks on list hear that! The unsafe {} around each block of "do something" is not idiomatic Rust - we can improve that. The summary_iterator() function above can be called like a normal function:

show_ports_summary_iterator(ports.iter());

Quoted 11 lines
meson_options.txt +2 -1
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
--
Quoted 1 line

2.45.2

Great work, and looking forward to hearing about next-steps, and discussion on the above feedback.

Regards! (& thanks for reading till the end.. this turned into a long response :) -Harry

#4 lore raw
Quoted 6 lines · Harry
Quoted 5 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

<snip>

Quoted 5 lines

+

Quoted 1 line

+use dpdklib::rte_ethdev::{

As per first reply, I'd like to see the end-result safe-Rust namespacing be shortened.

Quoted 8 lines

This can be easily done by renaming the name= in Cargo.toml, and removing the "rte_" prefix at bindgen output stage for each file. dpdk::ethdev:: { RTE_ETH_NAME_MAX_LEN, ... };

Perhaps its a good method to put the generated (unsafe, raw-C-bindings) in this namespace, and build a safe crate (with the dpdk::ethdev::* namespacing) over it. Thoughts?

I love naming discussions :-)

+1 to separate namespaces for low-level DPDK wraps, and more rustic higher-level APIs. However, that doesn't mean that the names get to be ugly! Let's definitely remove the "lib" in dpdklib (or libdpdk), and the "rte_" prefix from libs.

How about having "dpdk::raw::*" e.g. "dpdk::raw::ethdev" for low-level wraps and "dpdk::*" e.g. "dpdk::ethdev" for higher level apis?

/Bruce

#5 lore raw
Quoted 28 lines · Bruce Richardson

External email: Use caution opening links or attachments

Quoted 6 lines · Harry
Quoted 5 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

<snip>

Quoted 5 lines

+

Quoted 1 line

+use dpdklib::rte_ethdev::{

As per first reply, I'd like to see the end-result safe-Rust namespacing be shortened.

Quoted 8 lines

This can be easily done by renaming the name= in Cargo.toml, and removing the "rte_" prefix at bindgen output stage for each file. dpdk::ethdev:: { RTE_ETH_NAME_MAX_LEN, ... };

Perhaps its a good method to put the generated (unsafe, raw-C-bindings) in this namespace, and build a safe crate (with the dpdk::ethdev::* namespacing) over it. Thoughts?

I love naming discussions :-)

Quoted 8 lines

+1 to separate namespaces for low-level DPDK wraps, and more rustic higher-level APIs. However, that doesn't mean that the names get to be ugly! Let's definitely remove the "lib" in dpdklib (or libdpdk), and the "rte_" prefix from libs.

How about having "dpdk::raw::*" e.g. "dpdk::raw::ethdev" for low-level wraps and "dpdk::*" e.g. "dpdk::ethdev" for higher level apis?

I think that is good idea.
Application can use ether dpdk::raw::* or dpdk::* API depending on internal logic.

Quoted 2 lines

/Bruce

#6 lore raw
In reply to Van Haaren, Harry
Quoted 34 lines · Harry

External email: Use caution opening links or attachments

Quoted 5 lines

From: Gregory Etelson <[email protected]>
Sent: Thursday, March 6, 2025 1:37 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Richardson, Bruce <[email protected]>
Subject: [PATCH] rust: support DPDK API

More in-depth review in this reply..

Quoted 17 lines

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

Quoted 5 lines

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.

Quoted 4 lines

I've tested these instructions, using the default prefix = "/usr/local" This puts the rust files in "/usr/local/rust", so some tidy-up required. Debugging (or "searching for the files" was difficult - perhaps a log of the bindgen output from "rust-env.sh" would be helpful for 1st time setup?)

RUST installation directory is kind of open issue.
The current patch installs RUST crate during DPDK installation. RUST environment can be undefined at that time.
Maybe it's better to install the crate with a dedicated meson command.

Quoted 68 lines
Quoted 53 lines
---
 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
buildtools/meson.build +4 -0
index 4e2c1217a2..dd16567f51 100644
@@ -50,3 +50,7 @@ else
     pmdinfo += 'ar'
     pmdinfogen += 'elf'
 endif
+
+if get_option('enable_rust')
+    meson.add_install_script('rust-env.sh')
+endif
buildtools/rust-env.sh new file +17 -0
new file mode 100755
index 0000000000..705bc0d95b
@@ -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

buildtools/rust-env.sh line 27

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+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"

Here "rust_dir"="/usr/local/rust" if the default prefix is used.
At least, a "dpdk" must be in there somewhere (we're not Rust itself after all!) but also different systems put files in e.g: "/usr/local/lib/x86_64-linux-gnu/dpdk"

Quoted 1 line

Appending a "/rust" to the end of that is probably what was intended?

Same as above.

Quoted 27 lines
Quoted 27 lines
buildtools/rust-env.sh new file +25 -0
+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
Quoted 8 lines

buildtools/rust-env.sh line 57

Quoted 5 lines
buildtools/rust-env.sh new file +5 -0
+  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

# Adding this helps debug the header conversion & output file locations echo "writing Rust converted header to ${rust_dir}/src/${base}.rs"

Meson add_install_script() dumps shell output.

Quoted 10 lines
Quoted 10 lines
buildtools/rust-env.sh new file +9 -0
+  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
Quoted 9 lines

buildtools/rust-env.sh line 72

Quoted 6 lines
buildtools/rust-env.sh new file +6 -0
+
+
+cat > "$rust_dir/Cargo.toml" <<EOF
+[package]
+name = "dpdklib"
+version = "$(cat $MESON_SOURCE_ROOT/VERSION | sed 's/\.0\([1-9]\)/\.\1/')"

Missing {} brackets around MESON_SOURCE_ROOT perhaps? Got an error on "/VERSION" not found.

I'll fix that.

Quoted 11 lines

buildtools/rust-env.sh line 78

Quoted 6 lines
buildtools/rust-env.sh new file +6 -0
+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"

Hmm, interesting, lets keep a TODO here, but as this changes the "allowable" or valid binary layouts, it is potentially going to cause differences between C and Rust's interpretation of the memory backing the structs.
TODO: investigate each struct individually, and determine if/what is really required/correct.

There was no actual violation in this case because network headers definitions were properly aligned. C compiler ether ignored it or managed to figure it out.
RUST does not allow to mix align() and packed() in general.
Adjusting C file will not change structures layout and reduce post-bindgen corrections. Hence the TODO.

Quoted 31 lines
Quoted 31 lines
examples/rust/helloworld/Cargo.lock new file +14 -0
new file mode 100644
index 0000000000..d18af80c66
@@ -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",
+]
examples/rust/helloworld/Cargo.toml new file +4 -0
new file mode 100644
index 0000000000..7f9a77968a
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
Quoted 9 lines

examples/rust/helloworld/Cargo.toml line 7

Quoted 4 lines
examples/rust/helloworld/Cargo.toml new file +3 -0
+
+[dependencies]
+dpdklib = {path = "MESON_INSTALL_DESTDIR_PREFIX/rust"}
\ No newline at end of file

Above Cargo.toml looks normal. Perhaps we could suggest/provide a method where DPDK does not need to be "ninja install"-ed, but instead process a local directory? That would likely make it easier to locally build/test Rust bindings.

Agree. RUST crate should have a dedicated installation command.

Quoted 32 lines
Quoted 17 lines
examples/rust/helloworld/build.rs new file +11 -0
new file mode 100644
index 0000000000..5c76188e18
@@ -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..]);

examples/rust/helloworld/build.rs line 21

Quoted 10 lines
examples/rust/helloworld/build.rs new file +10 -0
+                } else if token.starts_with("-l") {
+                    println!("cargo::rustc-link-lib={}", &token[2..]);
+                }
+            }
+        }
+        Err(error) => {
+            panic!("failed to read libdpdk package: {:?}", error);
+        }
+    }
+}

Could a simple "probe()" like below replace the more complex logic? I've linked DPDK in the past using this more boring method.. or is there a reason that the link-search-native and link-lib parts have to be handled manually? Doing a Command::new() is hacky, nice to remove it if possible!

Quoted 7 lines
if let Err(e) = pkg_config::Config::new().probe("libdpdk") {
   panic!("libdpdk probe failed: {e:?}");
}

The build.rs file itself also needs a "rebuild if self changed" line:
println!("cargo:rerun-if-changed=build.rs");

I'll fix that.

Quoted 23 lines
Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +4 -0
new file mode 100644
index 0000000000..cf666aece6
@@ -0,0 +1,197 @@
+/// Usage: helloworld -a <port 1 params> -a <port 2 params> ...
+
+use std::env;
+use std::ffi::{CString};

examples/rust/helloworld/src/main.rs line 14

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+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::{

As per first reply, I'd like to see the end-result safe-Rust namespacing be shortened.

Quoted 8 lines

This can be easily done by renaming the name= in Cargo.toml, and removing the "rte_" prefix at bindgen output stage for each file. dpdk::ethdev:: { RTE_ETH_NAME_MAX_LEN, ... };

Perhaps its a good method to put the generated (unsafe, raw-C-bindings) in this namespace, and build a safe crate (with the dpdk::ethdev::* namespacing) over it. Thoughts?

The idea was to provide generic DPDK API and let application to resolve unsafe interfaces.

Quoted 52 lines
Quoted 36 lines
examples/rust/helloworld/src/main.rs new file +35 -0
+    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;

examples/rust/helloworld/src/main.rs line 59

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+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 {

This is where the first manual code is wrapping C concepts into Rust. And the approach taken here is quite "C thinking", where it is possible to misuse an API, and hence "unsafe" is used for many functions (more below) and also for the C FFI function calls.

Quoted 7 lines

I believe this part can be improved, and that there is big value in those improvements:
Today the code is "C but compiled by the Rust compiler", the programmer has to use the APIs correctly.

I'll send a patch in reply to this one for review, suggesting ways to improve.
Future code goal: "Rust checks code: invalid code will not compile, or provide a clean error at runtime."

Agree. The RUST part is pretty dull here.
The idea was to show how DPDK API can be used from RUST application.

Quoted 194 lines
Quoted 27 lines
examples/rust/helloworld/src/main.rs new file +26 -0
+        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
+        }

examples/rust/helloworld/src/main.rs line 95

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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)
+    }
+}

Cool - I like the egonomics idea of providing Iterator<DpdkPort>. The implementation here is unsafe, which is unfortunate, we'd like to ensure that all "end-user visible" Rust code is "safe Rust".

Quoted 23 lines
examples/rust/helloworld/src/main.rs new file +23 -0
+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}");

examples/rust/helloworld/src/main.rs line 129

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+    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}");
+    });
+
+}

There are some interesting changes of thoughts in Rust vs C, in how applications actually allocate or "own" data. The above show_ports_summary() function requires a &Vec<Port>. That demands that the backing storage for Port is a Vec<Port>, and a (immutable) reference is passed into this function. Hence the type is:
summary(ports: &Vec<Port>)

There is no reason to "demand" that the application stores Ports in a Vec. While common to store things in Vec<T>, we have now demanded "more than required" from the application. In some situations (perhaps not 100% relevant here, but..) like embedded systems, where dynamic allocation isn't available there IS no Vec<T> available. In other application scenarios, perhaps a HashMap is better.

The best way to do this in Rust is to use the Iterator trait, which doesn't require any specific "backing storage" (e.g. linear-data for 'slice' or Vec, or HashMap per-item allocated data, or ...). Instead the Iterator trait allows the function itself to iterate of the "Item". Example:

pub fn show_ports_summary_iterator<'a>(ports: impl Iterator<Item = &'a Port>) {
   ports.for_each(|p| { /* p is a &Port */);
}

There should be no unsafe required for the above debug print, and no statically sized C-string allocations. Instead of fetching the port name via C function at print-time, the Port::new() function can cache the value and convert it to a Rust String type - avoids unsafe {}, reduces scope of unsafe variables in the program.

Quoted 29 lines
examples/rust/helloworld/src/main.rs new file +29 -0
+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,

examples/rust/helloworld/src/main.rs line 168

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+                               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);
+}

Similar concepts around reworking "unsafe" away as above, future discussion.

Quoted 18 lines
examples/rust/helloworld/src/main.rs new file +18 -0
+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);

examples/rust/helloworld/src/main.rs line 197

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+            println!("init port {port_id}");
+            start_port(&mut port);
+            ports.push(port);
+        }
+    }
+
+    unsafe { show_ports_summary(&ports); }
+
+    println!("Hello, world!");
+}

Overall, I'm very happy to see this patch, I want to ensure folks on list hear that! The unsafe {} around each block of "do something" is not idiomatic Rust - we can improve that. The summary_iterator() function above can be called like a normal function:

show_ports_summary_iterator(ports.iter());

Quoted 11 lines
meson_options.txt +2 -1
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
--
Quoted 1 line

2.45.2

Great work, and looking forward to hearing about next-steps, and discussion on the above feedback.

Quoted 1 line

Regards! (& thanks for reading till the end.. this turned into a long response :) -Harry

:thumbs up:

#7 lore raw
In reply to Gregory Etelson

Hi Gregory!

As a DPDK as well as a Rust user, I'm quite excited about this patch. I'm wondering though, is DPDK getting an official Rust API? I'm subscribed to dev and user mailing lists, and haven't seen any announcements - did I miss something?

Bellow are some stylistic suggestions

Quoted 58 lines · Gregory Etelson

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.

---
 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
buildtools/meson.build +4 -0
index 4e2c1217a2..dd16567f51 100644
@@ -50,3 +50,7 @@ else
     pmdinfo += 'ar'
     pmdinfogen += 'elf'
 endif
+
+if get_option('enable_rust')
+    meson.add_install_script('rust-env.sh')
+endif
buildtools/rust-env.sh new file +0 -0
new file mode 100755
index 0000000000..705bc0d95b

buildtools/rust-env.sh line 6

Quoted 6 lines
buildtools/rust-env.sh new file +6 -0
@@ -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:

nit: typo (coversion -> conversion)

Quoted 224 lines
buildtools/rust-env.sh new file +72 -0
+# 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"
examples/rust/helloworld/Cargo.lock new file +14 -0
new file mode 100644
index 0000000000..d18af80c66
@@ -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",
+]
examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..7f9a77968a
@@ -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
examples/rust/helloworld/build.rs new file +21 -0
new file mode 100644
index 0000000000..5c76188e18
@@ -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);
+        }
+    }
+}
examples/rust/helloworld/src/main.rs new file +85 -0
new file mode 100644
index 0000000000..cf666aece6
@@ -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
+        }

examples/rust/helloworld/src/main.rs line 95

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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)
+    }
+}

It seems to me that it is expected to avoid iterating more than RTE_MAX_ETHPORTS times.
I'd suggest to add a .take() call here:

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)
}
.take(dpdklib::rte_build_config::RTE_MAX_ETHPORTS as usize)
}

Then the users of this function don't need to know about this detail. Also, would this make iter_rte_eth_dev() safe?

Quoted 11 lines
examples/rust/helloworld/src/main.rs new file +11 -0
+
+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 {

examples/rust/helloworld/src/main.rs line 116

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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>) {

Is it safe to remove unsafe here? Since Port is already initialized, all of the calls inside this function should be valid, unless I'm missing something.

Quoted 4 lines
examples/rust/helloworld/src/main.rs new file +4 -0
+    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 {

examples/rust/helloworld/src/main.rs line 130

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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) {

Same here with unsafe as with show_ports_summary. It seems all error conditions are checked, and the application terminates if something goes wrong.

Quoted 9 lines
examples/rust/helloworld/src/main.rs new file +9 -0
+    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)

examples/rust/helloworld/src/main.rs line 149

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+    };
+    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 {

nit: I'd suggest

if mbuf_pool.is_null() {
    ...
}

examples/rust/helloworld/src/main.rs line 159

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+        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)

This is quite pedantic, but it is often recommended to use .cast() in raw pointer casting:
mbuf_pool.cast::<dpdklib::rte_ethdev::rte_mempool>() or even simply mbuf_pool.cast(). [1].

Quoted 23 lines
examples/rust/helloworld/src/main.rs new file +23 -0
+    };
+    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![];

examples/rust/helloworld/src/main.rs line 192

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+    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);
+        }
+    }

If you prefer, this can be written like so:

let ports: Vec<Port> = unsafe {
    iter_rte_eth_dev()
        .take(dpdklib::rte_build_config::RTE_MAX_ETHPORTS as usize)
        .map(|port_id| {
            let mut port = Port::new(port_id);
            init_port_config(&mut port);
            println!("init port {port_id}");
            start_port(&mut port);
            port
        })
        .collect()
}
Quoted 16 lines
examples/rust/helloworld/src/main.rs new file +5 -0
+
+    unsafe { show_ports_summary(&ports); }
+
+    println!("Hello, world!");
+}
meson_options.txt +2 -1
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
--
Quoted 2 lines

2.45.2

Some other thoughts/suggestions:
- Indentation/styling is a bit off or inconsistent. I'd suggest to run cargo fmt to format the code. It is possible to configure the output to match DPDK's needs [2]. Also, cargo fmt --check can check if rustfmt would make any formatting changes. This could be a part of the checks in devtools in the future.
- I haven't checked, but I think some of my suggestions would pop up by cargo clippy analysis. It may be worth having this tool be a part of the devtools checks as well in the future.
- I'd make init_port_config and start_port a method on Port, rather than a free function, but that's just my preference.

[1] https://github.com/rust-lang/rust-clippy/issues/8017
[2] https://rust-lang.github.io/rustfmt/?version=master&search=

--
Regards,
Igor Gutorov

#8 lore raw

Hello Igor,

Quoted 9 lines

Hi Gregory!

As a DPDK as well as a Rust user, I'm quite excited about this patch. I'm wondering though, is DPDK getting an official Rust API? I'm subscribed to dev and user mailing lists, and haven't seen any announcements - did I miss something?

Bellow are some stylistic suggestions

There is no DPDK API for RUST yet.
My patch allows RUST application to use native DPDK API. I'll adopt you coding style suggestions in the next patch release.

Regards,
Gregory

#9 lore raw
Quoted 12 lines · Gregory

Hello Igor,

Quoted 9 lines

Hi Gregory!

As a DPDK as well as a Rust user, I'm quite excited about this patch. I'm wondering though, is DPDK getting an official Rust API? I'm subscribed to dev and user mailing lists, and haven't seen any announcements - did I miss something?

Bellow are some stylistic suggestions

Quoted 6 lines

There is no DPDK API for RUST yet.
My patch allows RUST application to use native DPDK API. I'll adopt you coding style suggestions in the next patch release.

Regards,
Gregory

I have same concerns about rust in DPDK as the Linux kernel. What API's are we going to be covered?
Will new API's have to have rust support?
What about API stability?

Good to see this mainstreamed, baby steps first. The build system issues seem like a lot of heavy lifting.

#10 lore raw
Quoted 7 lines
Quoted 4 lines

There is no DPDK API for RUST yet.
My patch allows RUST application to use native DPDK API. I'll adopt you coding style suggestions in the next patch release.

I have same concerns about rust in DPDK as the Linux kernel. What API's are we going to be covered?

The plan is to convert API in demand -
according to RUST applications coverage.

Quoted 1 line

Will new API's have to have rust support?

The patch converts selected files during DPDK installation. All API in the selected files are converted. That procedure automatically covers all API changes in selected files.

Quoted 2 lines

What about API stability?

The raw module works directly with DPDK API.
It's stable.

#11 lore raw

PATCH v2 1/1 rust: support raw DPDK API

In reply to Gregory Etelson

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.

---
 buildtools/meson.build               |   4 +
 buildtools/rust-env.sh               |  81 +++++++++++
 examples/rust/helloworld/Cargo.lock  |  14 ++
 examples/rust/helloworld/Cargo.toml  |   7 +
 examples/rust/helloworld/build.rs    |  22 +++
 examples/rust/helloworld/src/main.rs | 197 +++++++++++++++++++++++++++
 meson_options.txt                    |   2 +
 7 files changed, 327 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
buildtools/meson.build +4 -0
index 4e2c1217a2..b9d0092f07 100644
@@ -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
buildtools/rust-env.sh new file +81 -0

81 lines not on this page · raw message

examples/rust/helloworld/Cargo.lock new file +14 -0
new file mode 100644
index 0000000000..d18af80c66
@@ -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",
+]
examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..ceeecf958d
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust }
examples/rust/helloworld/build.rs new file +22 -0
new file mode 100644
index 0000000000..f4cef36499
@@ -0,0 +1,22 @@
+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);
+        }
+    }
+}
examples/rust/helloworld/src/main.rs new file +197 -0

197 lines not on this page · raw message

meson_options.txt +2 -0
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
Signature · 1 line
2.45.2
#12 lore raw

Re: [PATCH v2] rust: support raw DPDK API

Quoted 23 lines

From: Gregory Etelson <[email protected]>
Sent: Saturday, March 8, 2025 6:50 PM
To: Van Haaren, Harry <[email protected]>; Richardson, Bruce <[email protected]>; [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>
Subject: [PATCH v2] rust: support raw DPDK API

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

Quoted 7 lines

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.

Please include a "revision change" summary, this helps reviewers understand what changes you have addressed, and what changes are not present. This usually is formatted below a --- line, so Git doesn't include it in commit notes.

Also add to CC any participants of the wider email discussion. (+CC Igor and Stephen here now)

---
v2:
- update rust crate name to "dpdk" from "dpdklib" (Bruce)
- update binding generation to use dpdk::raw::* namespace (Bruce)
- <were other comments/notes fixed?>
--- e.g. "rte_ethdev" -> "ethdev" namespace change?
--- e.g. "cargo fmt"?
--- e.g. "build.rs" simplification & fix?

Pulling comments from "detailed review" email thread (see v1 on patchwork)
- Gregory: "The idea was to provide generic DPDK API and let application to resolve unsafe interfaces."

For this patch, OK. But we (DPDK project) should take the next steps, and provide a "Safe Rust" API. The main reasons are that:
1) DPDK APIs have many rules around multithreading; (lcore concepts, rx_burst() threading requirements, etc).
2) Cross-language bindings are hard, FFI is hard (even though C/Rust is one of the "easier" ones)

Overall, I'd like to set the "goal" of Rust in DPDK to be "if it compiles, it is correct".
For example, if compiler allows "rxq.rx_burst()" to be called, it must ensure that a &mut reference is valid at that point. That gives a Rust bindings *actual value*, as opposed to "allowing DPDK usage from Rust (with all threading/unsafety just like in C)".

The bindings are the required base for that: making it available. But it is important state that these raw bindings are NOT ready for Rust folks to go build applications in "safe Rust".

- Igor "Indentation/styling is a bit off or inconsistent. I'd suggest to run cargo fmt to format the code."

Big +1. Rust has a default formatting engine, and using it means everybody wins (no checkpatch, no manual style rework). By reworking (and auto-saving..) my updates of the first patch, it reformatted a bunch of code, losing my own changes. Small things like this really hinder productivity. (Off topic: I'd like to see a DPDK auto-formatted for C code too!)

- Igor "I'd make init_port_config and start_port a method on Port, rather than a free function, but that's just my preference."

+1 here too. There is a logical "ownership" of functions to structs. For value added Rust APIs, we must
not copy the existing DPDK namespacing (it does not capture "ownership" in the way that Rust API should).
Instead, logically understanding the thread-requirements and boundaries of concepts like an "Rxq" struct instance, and ensuring appropriate Rust "Send" and "Sync" bounds are applied is key to a good Rust API (as per https://youtu.be/lb6xn2xQ-NQ?t=702).

<snip>

Quoted 2 lines
examples/rust/helloworld/src/main.rs new file +0 -0
new file mode 100644

<snip>

Quoted 18 lines
examples/rust/helloworld/src/main.rs new file +18 -0
+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 {

examples/rust/helloworld/src/main.rs line 76

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+                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> {

Looking at the above parts of "Port abstraction in Rust", I'm not sure it really adds anything. It feels a bit "middle of the road" (aka, adding some "abstraction", but not going far enough).

What if we took an even smaller step: remove the Rust struct concepts, and just call the "dpdk::raw::*" unsafe functions directly, as if they were C code. No "struct Port, impl Port", and no "Iterator<DpdkPort" concepts at all. That would show "correct" usage of the raw generated bindings, and provide raw API unit-tests.

In a future patch, we can start to build "Safe Rust" essential-basics of EAL, and Ethdev. This is to abstract over the "dpdk::raw::*" APIs, "compiles == correct", end-user documentation, etc all as is normal in Rust ecosystem. Example:

Today: V2 patch (note the "Port" struct in Rust):
pub unsafe fn init_port_config(port: &mut Port) { ... }

Future Raw API (note the raw u16 like C APIs):
unsafe fn init_port_config(port: u16) { ... }

Future Safe API (mockup, this needs thought):
let ports = eal.get_eth_ports();

  for p in &mut ports {
     p.initialize(rxqs, txqs, ...)?;

}

Thoughts?

Regards, -Harry

#13 lore raw

Re: [PATCH v2] rust: support raw DPDK API

Quoted 115 lines · Harry
Quoted 23 lines

From: Gregory Etelson <[email protected]>
Sent: Saturday, March 8, 2025 6:50 PM
To: Van Haaren, Harry <[email protected]>; Richardson, Bruce <[email protected]>; [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>
Subject: [PATCH v2] rust: support raw DPDK API

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

Quoted 7 lines

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.

Please include a "revision change" summary, this helps reviewers understand what changes you have addressed, and what changes are not present. This usually is formatted below a --- line, so Git doesn't include it in commit notes.

Also add to CC any participants of the wider email discussion. (+CC Igor and Stephen here now)

---
v2:
- update rust crate name to "dpdk" from "dpdklib" (Bruce)
- update binding generation to use dpdk::raw::* namespace (Bruce)
- <were other comments/notes fixed?>
--- e.g. "rte_ethdev" -> "ethdev" namespace change?
--- e.g. "cargo fmt"?
--- e.g. "build.rs" simplification & fix?

Pulling comments from "detailed review" email thread (see v1 on patchwork)
- Gregory: "The idea was to provide generic DPDK API and let application to resolve unsafe interfaces."

For this patch, OK. But we (DPDK project) should take the next steps, and provide a "Safe Rust" API. The main reasons are that:
1) DPDK APIs have many rules around multithreading; (lcore concepts, rx_burst() threading requirements, etc).
2) Cross-language bindings are hard, FFI is hard (even though C/Rust is one of the "easier" ones)

Overall, I'd like to set the "goal" of Rust in DPDK to be "if it compiles, it is correct".
For example, if compiler allows "rxq.rx_burst()" to be called, it must ensure that a &mut reference is valid at that point. That gives a Rust bindings *actual value*, as opposed to "allowing DPDK usage from Rust (with all threading/unsafety just like in C)".

The bindings are the required base for that: making it available. But it is important state that these raw bindings are NOT ready for Rust folks to go build applications in "safe Rust".

- Igor "Indentation/styling is a bit off or inconsistent. I'd suggest to run cargo fmt to format the code."

Big +1. Rust has a default formatting engine, and using it means everybody wins (no checkpatch, no manual style rework). By reworking (and auto-saving..) my updates of the first patch, it reformatted a bunch of code, losing my own changes. Small things like this really hinder productivity. (Off topic: I'd like to see a DPDK auto-formatted for C code too!)

- Igor "I'd make init_port_config and start_port a method on Port, rather than a free function, but that's just my preference."

+1 here too. There is a logical "ownership" of functions to structs. For value added Rust APIs, we must
not copy the existing DPDK namespacing (it does not capture "ownership" in the way that Rust API should).
Instead, logically understanding the thread-requirements and boundaries of concepts like an "Rxq" struct instance, and ensuring appropriate Rust "Send" and "Sync" bounds are applied is key to a good Rust API (as per https://youtu.be/lb6xn2xQ-NQ?t=702).

<snip>

Quoted 2 lines
examples/rust/helloworld/src/main.rs new file +0 -0
new file mode 100644

<snip>

Quoted 18 lines
examples/rust/helloworld/src/main.rs new file +18 -0
+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 {

examples/rust/helloworld/src/main.rs line 76

Quoted 10 lines
examples/rust/helloworld/src/main.rs new file +10 -0
+                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> {

Looking at the above parts of "Port abstraction in Rust", I'm not sure it really adds anything. It feels a bit "middle of the road" (aka, adding some "abstraction", but not going far enough).

Quoted 4 lines

What if we took an even smaller step: remove the Rust struct concepts, and just call the "dpdk::raw::*" unsafe functions directly, as if they were C code. No "struct Port, impl Port", and no "Iterator<DpdkPort" concepts at all. That would show "correct" usage of the raw generated bindings, and provide raw API unit-tests.

Yes, definite +1 for this. Let's not try and have a half-C, half-Rust set of APIs, but instead let's have a clean low-level mapping directly to C functions, and then build a proper, safe API on top of that.

/Bruce

#14 lore raw

Re: [PATCH v2] rust: support raw DPDK API

10/03/2025 17:25, Bruce Richardson:

Quoted 12 lines · Harry
Quoted 7 lines

Looking at the above parts of "Port abstraction in Rust", I'm not sure it really adds anything. It feels a bit "middle of the road" (aka, adding some "abstraction", but not going far enough).

What if we took an even smaller step: remove the Rust struct concepts, and just call the "dpdk::raw::*" unsafe functions directly, as if they were C code. No "struct Port, impl Port", and no "Iterator<DpdkPort" concepts at all. That would show "correct" usage of the raw generated bindings, and provide raw API unit-tests.

Yes, definite +1 for this. Let's not try and have a half-C, half-Rust set of APIs, but instead let's have a clean low-level mapping directly to C functions, and then build a proper, safe API on top of that.

+1 about limiting this patch to a low-level unsafe binding, while allowing a safe high-level API to be created later.

#15 lore raw

Re: [PATCH v2] rust: support raw DPDK API

In reply to Van Haaren, Harry

<snip>

Quoted 4 lines

Looking at the above parts of "Port abstraction in Rust", I'm not sure it really adds anything. It feels a bit "middle of the road" (aka, adding some "abstraction", but not going far enough).

Current helloword example is a POC for the raw module usage. It content can (should) be updated once the API will be more stable.

Quoted 3 lines

What if we took an even smaller step: remove the Rust struct concepts, and just call the "dpdk::raw::*" unsafe functions directly, as if they were C code. No "struct Port, impl Port", and no "Iterator<DpdkPort" concepts at all. That would show "correct" usage of the raw generated bindings, and provide raw API unit-tests.

In cases when helloworld is activated with more than 1 port, dedicated structure helps to keep track of resources. Maybe that example was not the best choice for "helloworld" introduction.

Quoted 10 lines

In a future patch, we can start to build "Safe Rust" essential-basics of EAL, and Ethdev. This is to abstract over the "dpdk::raw::*" APIs, "compiles == correct", end-user documentation, etc all as is normal in Rust ecosystem. Example:

Today: V2 patch (note the "Port" struct in Rust):
pub unsafe fn init_port_config(port: &mut Port) { ... }

Future Raw API (note the raw u16 like C APIs):
unsafe fn init_port_config(port: u16) { ... }

Quoted 7 lines

Future Safe API (mockup, this needs thought):
let ports = eal.get_eth_ports();

 for p in &mut ports {
    p.initialize(rxqs, txqs, ...)?;

}

Thoughts?

The raw API links rust application directly with DPDK libraries. Such application can achieve the same performance as C application.

DPDK can have additional rust module - dpdk::native, for genuine rust interfaces.

With 2 rust modules, DPDK users will have time to build application logic while DPDK developers can safely experiment with new API.

I see the raw module as a temporal solution before DPDK will have native rust API.

Regards,
Gregory

#16 lore raw
In reply to Gregory Etelson
Quoted 6 lines · On Thu, 6 Mar 2025 15:37:13 +0200 Gregory Etelson <[email protected]> wrote:
examples/rust/helloworld/Cargo.toml new file +0 -0
new file mode 100644
index 0000000000..7f9a77968a

examples/rust/helloworld/Cargo.toml line 7

Quoted 8 lines
examples/rust/helloworld/Cargo.toml new file +7 -0
@@ -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

Bad practice.
Don't forget EOL

#17 lore raw
Quoted 6 lines
Quoted 3 lines

+[dependencies]
+dpdklib = {path = "MESON_INSTALL_DESTDIR_PREFIX/rust"} \ No newline at end of file

Bad practice.
Don't forget EOL

I'll fix that

#18 lore raw
In reply to Gregory Etelson
Quoted 17 lines · On Thu, 6 Mar 2025 15:37:13 +0200 Gregory Etelson <[email protected]> wrote:

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

Quoted 8 lines

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.

---

Re-implementing test-pmd, l3fwd, etc in Rust is OK but not huge leap. The real benefit would the ability to support PMD's built in Rust. That would be much more invasive

#19 lore raw
Quoted 26 lines · Stephen Hemminger
Quoted 23 lines · On Thu, 6 Mar 2025 15:37:13 +0200 Gregory Etelson <[email protected]> wrote:

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.

Quoted 7 lines
Quoted 2 lines

---

Re-implementing test-pmd, l3fwd, etc in Rust is OK but not huge leap. The real benefit would the ability to support PMD's built in Rust. That would be much more invasive

I'd actually disagree here. I think there is far more value in providing a good, safe set of DPDK APIs and structs for apps written in rust. For drivers, there probably is some value, but reading/writing registers and descriptors will by its nature be unsafe code anyway. I'd also worry about the complexity of trying to mix in rust into our existing build systems etc. I've actually written some test vdevs in rust to interface DPDK, and the amount of crossing the C-rust boundary that was necessary was very painful. There were also huge difficulties around linking and run-time usage, symbol conflicts etc. etc., due to building rust separate from DPDK itself. All solvable, but complex and of limited value to the end-user.

So therefore, I'd concentrate on starting at the app layer, and worry about lower level things only thereafter if we need it.

/Bruce

#20 lore raw
In reply to Stephen Hemminger
Quoted 2 lines

Re-implementing test-pmd, l3fwd, etc in Rust is OK but not huge leap.

DPDK examples and applications are part of inner infrastructure. The patch allows external RUST application as well.

Quoted 3 lines

The real benefit would the ability to support PMD's built in Rust. That would be much more invasive

It's not sure when and even if vendors decide to work on RUST PMD. Before that happens, there must be native RUST API with RUST-DPDK library functions.

In the meanwhile, RUST application can use existing C API. Also, having RUST inside DPDK can shape existing API for future conversion.

#21 lore raw
Quoted 2 lines · Gregory

It's not sure when and even if vendors decide to work on RUST PMD.

A PMD could be software based (e.g. net_pcap). For such PMDs, Rust can be a good option.

#22 lore raw
In reply to Stephen Hemminger
Quoted 2 lines · Stephen Hemminger

The real benefit would the ability to support PMD's built in Rust.

Having DPDK libraries as Rust crates would be beneficial as well. The mempool library in particular is something I've wanted to use in some Rust projects. I've previously considered either porting this library or writing a safe API around bindings, but never actually got to implementing it yet. IMO, the benefit to DPDK would be more users, more contributors, etc.

#23 lore raw

12/03/2025 16:29, Igor Gutorov:

Quoted 11 lines
Quoted 2 lines · Stephen Hemminger

The real benefit would the ability to support PMD's built in Rust.

Having DPDK libraries as Rust crates would be beneficial as well. The mempool library in particular is something I've wanted to use in some Rust projects. I've previously considered either porting this library or writing a safe API around bindings, but never actually got to implementing it yet. IMO, the benefit to DPDK would be more users, more contributors, etc.

That's all what we want: being helpful :)

#24 lore raw

PATCH v3 1/1 rust: support raw DPDK API

In reply to Gregory Etelson

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.

---
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
buildtools/meson.build +4 -0
index 4e2c1217a2..b9d0092f07 100644
@@ -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
buildtools/rust-env.sh new file +81 -0

81 lines not on this page · raw message

examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..ceeecf958d
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust }
examples/rust/helloworld/build.rs new file +24 -0
new file mode 100644
index 0000000000..bd5737d209
@@ -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);
+        }
+    }
+}
examples/rust/helloworld/src/main.rs new file +219 -0

219 lines not on this page · raw message

meson_options.txt +2 -0
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
Signature · 1 line
2.45.2
#25 lore raw

Re: [PATCH v3] rust: support raw DPDK API

Hi Gregory,

I have been playing around with the patch and I have a few suggestions which I hope would improve the experience with using the generated bindings. Please let me know what you think.

First of all, during compilation, there is a lot of warnings regarding naming style. Basically there is a difference between naming style in C and Rust. For example:

	warning: type `rte_mempool_mem_cb_t` should have an upper camel case name
	    --> /home/ds/obj/dpdk/lib/rust/src/raw/rte_mempool.rs:6292:10
	     |
	6292 | pub type rte_mempool_mem_cb_t = ::std::option::Option<
	     |          ^^^^^^^^^^^^^^^^^^^^ help: convert the identifier to upper camel case: `RteMempoolMemCbT`

In the end, I don't think these are important for bindings and they can be safely ignored. I think the following should be added to "src/raw/lib.rs":

	#[allow(non_camel_case_types)]
	#[allow(non_upper_case_globals)]
	#[allow(non_snake_case)]

There are also some warnings regarding the use of u128:

	warning: `extern` block uses type `u128`, which is not FFI-safe
	    --> /home/ds/obj/dpdk/lib/rust/src/raw/rte_mempool.rs:4170:18
	     |
	4170 |         __value: u128,
	     |                  ^^^^ not FFI-safe
	     |
	     = note: 128-bit integers don't currently have a known stable ABI

There's an open issue for that with more information and discussion:
https://github.com/rust-lang/rust/issues/78473

These warnings are caused by declaration of the following functions in stdlib.h (which is transitively included by DPDK headers):

qecvt_r
qfcvt_r
qecvt
qfcvt
qgcvt
strtold

These are not part of DPDK public API and removing their bindings should not be an issue in my opinion.
I think we can just remove them from bindings with '--blocklist-function' argument. What do you think?

Quoted 80 lines · Gregory Etelson

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.

---
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
buildtools/meson.build +4 -0
index 4e2c1217a2..b9d0092f07 100644
@@ -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
buildtools/rust-env.sh new file +15 -0
new file mode 100755
index 0000000000..fe0877643b
@@ -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.

buildtools/rust-env.sh line 25

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+
+# DPDK files list
+files='
+rte_build_config.h
+rte_eal.h
+rte_ethdev.h
+rte_mbuf.h
+rte_mbuf_core.h
+rte_mempool.h
+'

There is a lot of warnings generated for types redeclaration, for example:

	warning: `rte_mempool_ops_get_info` redeclared with a different signature
	    --> /home/ds/obj/dpdk/lib/rust/src/raw/rte_mbuf.rs:6352:5
	     |
	6352 | /     pub fn rte_mempool_ops_get_info(
	6353 | |         mp: *const rte_mempool,
	6354 | |         info: *mut rte_mempool_info,
	6355 | |     ) -> ::std::os::raw::c_int;
	     | |_______________________________^ this signature doesn't match the previous declaration
	     |
	    ::: /home/ds/obj/dpdk/lib/rust/src/raw/rte_ethdev.rs:8957:5
	     |
	8957 | /     pub fn rte_mempool_ops_get_info(
	8958 | |         mp: *const rte_mempool,
	8959 | |         info: *mut rte_mempool_info,
	8960 | |     ) -> ::std::os::raw::c_int;
	     | |_______________________________- `rte_mempool_ops_get_info` previously declared here
	     |
	     = note: expected `unsafe extern "C" fn(*const rte_ethdev::rte_mempool, *mut rte_ethdev::rte_mempool_info) -> i32`
			found `unsafe extern "C" fn(*const raw::rte_mbuf::rte_mempool, *mut raw::rte_mbuf::rte_mempool_info) -> i32`

These come from the fact that bindings for each header file are generated separately, but certain types and functions are defined "transitively" in more than one. rte_mempool for example is defined in rte_mempool.h which is included by both rte_ethdev.h and rte_mbuf.h
It's ok in C, but in Rust, resulting bindings these types are different (dpdk::raw::rte_ethdev::rte_mempool != dpdk::raw::rte_mbuf::rte_mempool).
They could be transmuted between one another, but it is an additional obstacle in using the bindings.

What do you think about using a wrapper header?
This is a solution used in Rust for Linux
(https://github.com/torvalds/linux/blob/master/rust/bindings/bindings_helper.h).
Basically, a new header file is added which includes all headers we want to translate. bindgen is only run on this wrapper header.
This removes the duplication of all definitions.

This comes at a price of having a flat namespace in dpdk::raw::*, but I'm not sure it's that much important for raw bindings. Proper namespacing can be introduced by higher level wrappers.

Quoted 44 lines
buildtools/rust-env.sh new file +44 -0
+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
+

buildtools/rust-env.sh line 79

Quoted 10 lines
buildtools/rust-env.sh new file +10 -0
+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"

I have taken a look at specific types with that issue and there is only a few of them. The following are affected:

rte_l2tpv2_combined_msg_hdr
rte_flow_item_l2tpv2
rte_arp_ipv4
rte_arp_hdr
rte_flow_item_arp_eth_ipv4

Before changing DPDK definitions (if possible), we could make bindings for them opaque. This would basically generate, for each type, a struct with byte array inside, which would be "naturally" packed (no need for 'repr(packed)'). Reading/modifying specific fields could be written as a higher level getter/setter.

What do you think about making these types opaque through '--opaque-type' bindgen parameter? This would:

- remove the need for manually replacing align/packed (which removes required alignment on rte_ether_addr),
- remove the need to disable layout tests (which are very good to have),
- "document" the list of problematic types.

Quoted 35 lines
buildtools/rust-env.sh new file +2 -0
+
+echo "Install RUST DPDK crate in $rust_dir"
examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..ceeecf958d
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust }
examples/rust/helloworld/build.rs new file +14 -0
new file mode 100644
index 0000000000..bd5737d209
@@ -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") {

examples/rust/helloworld/build.rs line 24

Quoted 10 lines
examples/rust/helloworld/build.rs new file +10 -0
+                    println!("cargo::rustc-link-lib={}", &token[2..]);
+                }
+            }
+            println!("cargo:rerun-if-changed=build.rs");
+        }
+        Err(error) => {
+            panic!("failed to read libdpdk package: {:?}", error);
+        }
+    }
+}

What do you think about using pkg_config crate in build scripts?
(https://crates.io/crates/pkg-config)

This would allow to remove manual command execution and output parsing with the following:

	fn main() {
	  pkg_config::probe_library("libdpdk").expect("Unable to find libdpdk");
	}

Also, what do you think about moving this logic to dpdk crate? This way dependency resolution would be shared by all examples/applications which use the dpdk crate.

Quoted 238 lines
examples/rust/helloworld/src/main.rs new file +219 -0
new file mode 100644
index 0000000000..0c057cb39f
@@ -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!");
+}
meson_options.txt +2 -0
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
Signature · 1 line
2.45.2

Best regards,
Dariusz Sosnowski

#26 lore raw

Re: [PATCH v3] rust: support raw DPDK API

Quoted 31 lines · Dariusz Sosnowski
Quoted 14 lines
examples/rust/helloworld/build.rs new file +14 -0
+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") {

examples/rust/helloworld/build.rs line 24

Quoted 10 lines
examples/rust/helloworld/build.rs new file +10 -0
+                    println!("cargo::rustc-link-lib={}", &token[2..]);
+                }
+            }
+            println!("cargo:rerun-if-changed=build.rs");
+        }
+        Err(error) => {
+            panic!("failed to read libdpdk package: {:?}", error);
+        }
+    }
+}

What do you think about using pkg_config crate in build scripts?
(https://crates.io/crates/pkg-config)

This would allow to remove manual command execution and output parsing with the following:

Quoted 7 lines
	fn main() {
	  pkg_config::probe_library("libdpdk").expect("Unable to find libdpdk");
	}

Also, what do you think about moving this logic to dpdk crate? This way dependency resolution would be shared by all examples/applications which use the dpdk crate.

I just noticed that similar proposal was raised by Harry previously. Sorry for the duplication :)

#27 lore raw

PATCH v4 1/1 rust: support raw DPDK API

In reply to Gregory Etelson

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.

---
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.
v4:
Blocked bindgen to convert stdlib functions with u128 type. Allow compilation of C style symbols.

---
 buildtools/meson.build               |   4 +
 buildtools/rust-env.sh               |  96 ++++++++++++
 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, 352 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
buildtools/meson.build +4 -0
index 4e2c1217a2..b9d0092f07 100644
@@ -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
buildtools/rust-env.sh new file +96 -0

96 lines not on this page · raw message

examples/rust/helloworld/Cargo.toml new file +7 -0
new file mode 100644
index 0000000000..ceeecf958d
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust }
examples/rust/helloworld/build.rs new file +24 -0
new file mode 100644
index 0000000000..bd5737d209
@@ -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);
+        }
+    }
+}
examples/rust/helloworld/src/main.rs new file +219 -0

219 lines not on this page · raw message

meson_options.txt +2 -0
index e49b2fc089..d37b9ba1dc 100644
@@ -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')
Signature · 1 line
2.45.2
#28 lore raw

Re: [PATCH v4] rust: support raw DPDK API

Quoted 37 lines · Gregory Etelson

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.

---
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.
v4:
Blocked bindgen to convert stdlib functions with u128 type. Allow compilation of C style symbols.

Quoted 13 lines
---
 buildtools/meson.build               |   4 +
 buildtools/rust-env.sh               |  96 ++++++++++++
 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, 352 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

Hi Gregory, some high-level feedback:

* I'd suggest for future revisions splitting this into two patches. The rust example should be separate from a patch adding the basic rust infrastructure.

* For the example, I'd suggest that the helloworld rust example should correspond to the regular "C" helloworld example, i.e just init DPDK and print helloworld. If we want a basic packet handling example, it should be an l2fwd one - again to align with the "C" examples.

* However, in terms of the examples, if the idea is to provide higher level "rustic" APIs for DPDK, the examples should be written to use that. Any use of the low-level C APIs should be in a testing app only, rather than an example app. IMHO We want to encourage proper rust usage, not C-code just rewritten to use a different compiler.

Regards,
/Bruce

#29 lore raw

Re: [PATCH v4] rust: support raw DPDK API

Hello Bruce,

Quoted 5 lines

Hi Gregory, some high-level feedback:

* I'd suggest for future revisions splitting this into two patches. The rust example should be separate from a patch adding the basic rust infrastructure.

In progress.

Quoted 5 lines

* For the example, I'd suggest that the helloworld rust example should correspond to the regular "C" helloworld example, i.e just init DPDK and print helloworld. If we want a basic packet handling example, it should be an l2fwd one - again to align with the "C" examples.

The next patch update will not include that example.

Quoted 6 lines

* However, in terms of the examples, if the idea is to provide higher level "rustic" APIs for DPDK, the examples should be written to use that. Any use of the low-level C APIs should be in a testing app only, rather than an example app. IMHO We want to encourage proper rust usage, not C-code just rewritten to use a different compiler.

This project, in its current stage, is a bit of an experiment.
I'm trying to figure out how much effort it will take to provide DPDK API for Rust application. Rust API is the final goal.

Regards,
Gregory

#30 lore raw

Re: [PATCH v4] rust: support raw DPDK API

In reply to Gregory Etelson
Quoted 17 lines · Gregory Etelson

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

Quoted 7 lines

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.

Hi Gregory,

some additional thoughts and comments inline below.

/Bruce

Quoted 5 lines

---
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".

Quoted 8 lines

v3:
Move init_port_config() to Port.
Move start_port() to Port.
Remove Cargo.lock from git repository
Reformat code.
v4:
Blocked bindgen to convert stdlib functions with u128 type. Allow compilation of C style symbols.

When using bindgen, are we better to take the approach (as in this patch) of running it on everything in the headers and just excluding some things, or taking the opposite conservative approach of just listing the functions and defines we actually do want exposed (with wildcarding as necessary)? When playing with rust apps on top of DPDK myself, I've tended toward the latter scheme, but maybe for this effort we may want the former.

Quoted 18 lines
---
 buildtools/meson.build               |   4 +
 buildtools/rust-env.sh               |  96 ++++++++++++
 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, 352 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
buildtools/meson.build +0 -0
index 4e2c1217a2..b9d0092f07 100644

buildtools/meson.build else · line 56

Quoted 7 lines
buildtools/meson.build +4 -0
@@ -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

I'm not convinced by having this done as a post-install script. Instead I'd tend towards having a rust crate hosted somewhere that does the bindgen as part of the rust build.

Quoted 11 lines
buildtools/rust-env.sh new file +5 -0
new file mode 100755
index 0000000000..a239432a28
@@ -0,0 +1,96 @@
+#! /bin/sh
+
+# Convert DPDK API files into RUST.
+# DPDK files selection is on demand.
+#

<snip>

examples/rust/helloworld/build.rs line 6

Quoted 12 lines
examples/rust/helloworld/build.rs new file +6 -0
new file mode 100644
index 0000000000..bd5737d209
@@ -0,0 +1,24 @@
+use std::process::Command;
+
+pub fn main() {
+    let mut pkgconfig = Command::new("pkg-config");
+
+    match pkgconfig.args(["--libs", "libdpdk"]).output() {

This work of using pkgconfig should not be necessary in the application build.rs file. If we switch to actually producing a proper crate, the pkgconfig handling and linking should be covered there (I would hope, anyway).

Quoted 18 lines
examples/rust/helloworld/build.rs new file +18 -0
+        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);
+        }
+    }
+}

<snip>

#31 lore raw

Re: [PATCH v4] rust: support raw DPDK API

Hello Bruce,

Quoted 7 lines

When using bindgen, are we better to take the approach (as in this patch) of running it on everything in the headers and just excluding some things, or taking the opposite conservative approach of just listing the functions and defines we actually do want exposed (with wildcarding as necessary)? When playing with rust apps on top of DPDK myself, I've tended toward the latter scheme, but maybe for this effort we may want the former.

The current library produced a lot of warnings during compilation. Many of these warnings were result of symbol duplication.
It looks like removing unnecessery duplicated symbols is the right way. However, I could not activate regex symbol match in bindgen-0.71.1 cli utility. At this stage I translate files from a shell script. This way I can translate a single file on demand.

Quoted 4 lines

I'm not convinced by having this done as a post-install script. Instead I'd tend towards having a rust crate hosted somewhere that does the bindgen as part of the rust build.

I'm already implementing that approach.

Quoted 11 lines

<snip>

Review

Quoted 2 lines
(quoted diff) +2 -0
+
+    match pkgconfig.args(["--libs", "libdpdk"]).output() {

This work of using pkgconfig should not be necessary in the application build.rs file. If we switch to actually producing a proper crate, the pkgconfig handling and linking should be covered there (I would hope, anyway).

I need pkgconfig for dynamic linker.

Regards,
Gregory

#32 lore raw

DPDK for rust

In reply to Gregory Etelson
Quoted 9 lines

From: Gregory Etelson [mailto:[email protected]]
Sent: Thursday, 6 March 2025 14.37
Subject: [PATCH] rust: support DPDK API

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.

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

I don't object to supporting rust, quite the opposite. I'm only trying to think ahead...

I would hate to see a DPDK patch being rejected because it doesn't support rust, or needs additional work to not break DPDK for rust.

And how about the CI - do we want it to test "DPDK for rust", and how many resources are we going to put into this?

-Morten

#33 lore raw

Re: DPDK for rust

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

Quoted 6 lines

I don't object to supporting rust, quite the opposite. I'm only trying to think ahead...

I would hate to see a DPDK patch being rejected because it doesn't support rust, or needs additional work to not break DPDK for rust.

And how about the CI - do we want it to test "DPDK for rust", and how many resources are we going to put into this?

Regards,
Gregory

#34 lore raw

Re: DPDK for rust

Quoted 5 lines · Gregory

Hello Morten,

Thank you for raising these questions !

Quoted 1 line
Quoted 7 lines
Quoted 2 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

#35 lore raw

Re: DPDK for rust

Quoted 8 lines
Quoted 8 lines

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Quoted 7 lines
Quoted 4 lines

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

That's correct - cargo registry is the native way. Also we may consider splitting the code between several crates. That approach can provide more flexible way to arrange files.

#36 lore raw

Re: DPDK for rust

Quoted 13 lines · Gregory
Quoted 13 lines
Quoted 12 lines

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

Quoted 6 lines
Quoted 2 lines

For ease of use, that would mean hosting a cargo registry, no?

That's correct - cargo registry is the native way. Also we may consider splitting the code between several crates. That approach can provide more flexible way to arrange files.

Talk to Luca. The current rust cargo registry model is causing lots of grief for distro's such as Debian, Redhat, Ubuntu and SUSE.

Packaging is always a pain

#37 lore raw

Re: DPDK for rust

Quoted 19 lines · Stephen Hemminger
Quoted 16 lines · Gregory
Quoted 15 lines
Quoted 5 lines

Hello Morten,

Thank you for raising these questions !

Quoted 1 line
Quoted 7 lines
Quoted 2 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

Quoted 8 lines
Quoted 3 lines

That's correct - cargo registry is the native way. Also we may consider splitting the code between several crates. That approach can provide more flexible way to arrange files.

Talk to Luca. The current rust cargo registry model is causing lots of grief for distro's such as Debian, Redhat, Ubuntu and SUSE.

Packaging is always a pain

Yes, but cargo is just the way the rust ecosystem works right now. If we want to have DPDK available for rust developers it needs to be accessible via cargo.

/Bruce

#38 lore raw

Re: DPDK for rust

Quoted 25 lines · Bruce Richardson
Quoted 23 lines · Stephen Hemminger
Quoted 19 lines · Gregory
Quoted 8 lines
Quoted 8 lines

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Quoted 7 lines
Quoted 4 lines

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

That's correct - cargo registry is the native way. Also we may consider splitting the code between several crates. That approach can provide more flexible way to arrange files.

Quoted 8 lines
Quoted 4 lines

Talk to Luca. The current rust cargo registry model is causing lots of grief for distro's such as Debian, Redhat, Ubuntu and SUSE.

Packaging is always a pain

Yes, but cargo is just the way the rust ecosystem works right now. If we want to have DPDK available for rust developers it needs to be accessible via cargo.

Making DPDK available on it is not an issue - the problems start popping up if it is _used by_ DPDK to pull in dozens of dependencies, as it becomes way, way too hard to manage. If you only use the compiler and the standard library then it's doable

#39 lore raw

Re: DPDK for rust

Hello,

I pulled the raw library bindings out of DPDK source into a dedicated crate and cleaned up compilation warnings.

Also I started a testpmd like application - runpmd.
At this stage it can receive buffers and insert flows with very limited items and actions coverage.

The total patches are big - over 69000 lines.
Is there another way to share these patches, except the mailing list ?

Regards,
Gregory

#40 lore raw

Re: DPDK for rust

In reply to Bruce Richardson

27/03/2025 17:17, Bruce Richardson:

Quoted 9 lines · Gregory
Quoted 8 lines

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Quoted 6 lines
Quoted 4 lines

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

Please could you explain why it is easier to have a cargo registry?

#41 lore raw

Re: DPDK for rust

Quoted 10 lines · Thomas Monjalon

27/03/2025 17:17, Bruce Richardson:

Quoted 9 lines · Gregory
Quoted 8 lines

Hello Morten,

Thank you for raising these questions !

Quoted 3 lines

Do we want the DPDK project itself to support rust? Or should parts of this be a DPDK hosted project, like grout?

Quoted 9 lines
Quoted 6 lines
Quoted 4 lines

Rust packages management is different.
Also DPDK Rust code will eventually provide a different API. At this stage, DPDK hosted project looks like a good idea.

For ease of use, that would mean hosting a cargo registry, no?

Please could you explain why it is easier to have a cargo registry?

Because cargo is the tool used by all rust developers to pull down and use their dependent libraries. I don't think any work we do will have much traction unless it's available via cargo.

/Bruce

#42 lore raw

RFC PATCH 1/1 add rust binding support to DPDK

In reply to Gregory Etelson

Add a Cargo.toml file in the root folder and a number of other scripts and rust-related files into buildtools/rust, which then enables DPDK to be cloned and built as a rust crate - all-be-it one with only two
functions: rte_eal_init and rte_eal_cleanup.

---

This RFC is proposed as an alternative approach to enabling rust support in DPDK. The key difference vs previous is that we are taking the whole DPDK project here as a rust "crate", which can then be used by other higher-level crates as a dependency. Building the crate does a (minimal) build of DPDK and statically links it in, so there is no "install" step or anything needed - the Rust app just adds DPDK to their Cargo.toml file and then should have everything they need.

---
 Cargo.toml                                | 16 +++++++
 buildtools/meson.build                    |  1 +
 buildtools/rust/build.rs                  | 45 ++++++++++++++++++++
 buildtools/rust/gen-cargo-bindgen-info.py | 52 +++++++++++++++++++++++
 buildtools/rust/lib.rs                    | 25 +++++++++++
 buildtools/rust/wrapper.h                 |  5 +++
 meson.build                               |  3 ++
 7 files changed, 147 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 buildtools/rust/build.rs
 create mode 100644 buildtools/rust/gen-cargo-bindgen-info.py
 create mode 100644 buildtools/rust/lib.rs
 create mode 100644 buildtools/rust/wrapper.h
Cargo.toml new file +16 -0
new file mode 100644
index 0000000000..fe0843975a
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2025 Intel Corporation
+
+[package]
+name = "dpdk-c"
+version = "25.0.7"
+build = "buildtools/rust/build.rs"
+edition = "2021"
+
+[lib]
+path = "buildtools/rust/lib.rs"
+doctest = false
+
+[build-dependencies]
+bindgen = "0.71"
+meson-next = "1"
buildtools/meson.build +1 -0
index 4e2c1217a2..a13b5189c1 100644
@@ -26,6 +26,7 @@ header_gen_cmd = py3 + files('gen-header.py')
 has_hugepages_cmd = py3 + files('has-hugepages.py')
 cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py')
 check_dts_requirements = py3 + files('check-dts-requirements.py')
+gen_cargo_bindgen_info = py3 + files('rust/gen-cargo-bindgen-info.py')
 
 # install any build tools that end-users might want also
 install_data([
buildtools/rust/build.rs new file +45 -0

45 lines not on this page · raw message

buildtools/rust/gen-cargo-bindgen-info.py new file +52 -0

52 lines not on this page · raw message

buildtools/rust/lib.rs new file +25 -0
new file mode 100644
index 0000000000..f99c54ac42
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_helloworld() {
+	let appname = std::ffi::CString::new("test-rs").unwrap();
+	let mut argv = [appname.into_raw()];
+	let ret = unsafe {
+		rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
+	};
+	assert!(ret >= 0, "rte_eal_init failed");
+        unsafe { rte_eal_cleanup() };
+    }
+}
buildtools/rust/wrapper.h new file +5 -0
new file mode 100644
index 0000000000..25f5bf94e2
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#include <rte_eal.h>
meson.build +3 -0
index 8436d1dff8..72c82178e1 100644
@@ -114,6 +114,9 @@ configure_file(output: build_cfg,
 # build pkg-config files for dpdk
 subdir('buildtools/pkg-config')
 
+# output a list of cargo parameters if DPDK is being built for rust use
+run_command([gen_cargo_bindgen_info, pkgconf], check: true)
+
 if meson.is_subproject()
     subdir('buildtools/subproject')
 endif

--
2.45.2

#43 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Quoted 31 lines · Bruce Richardson

Add a Cargo.toml file in the root folder and a number of other scripts and rust-related files into buildtools/rust, which then enables DPDK to be cloned and built as a rust crate - all-be-it one with only two
functions: rte_eal_init and rte_eal_cleanup.

---

This RFC is proposed as an alternative approach to enabling rust support in DPDK. The key difference vs previous is that we are taking the whole DPDK project here as a rust "crate", which can then be used by other higher-level crates as a dependency. Building the crate does a (minimal) build of DPDK and statically links it in, so there is no "install" step or anything needed - the Rust app just adds DPDK to their Cargo.toml file and then should have everything they need.

---
 Cargo.toml                                | 16 +++++++
 buildtools/meson.build                    |  1 +
 buildtools/rust/build.rs                  | 45 ++++++++++++++++++++
 buildtools/rust/gen-cargo-bindgen-info.py | 52 +++++++++++++++++++++++
 buildtools/rust/lib.rs                    | 25 +++++++++++
 buildtools/rust/wrapper.h                 |  5 +++
 meson.build                               |  3 ++
 7 files changed, 147 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 buildtools/rust/build.rs
 create mode 100644 buildtools/rust/gen-cargo-bindgen-info.py
 create mode 100644 buildtools/rust/lib.rs
 create mode 100644 buildtools/rust/wrapper.h

<snip>

Quoted 21 lines
buildtools/rust/lib.rs new file +15 -0
new file mode 100644
index 0000000000..f99c54ac42
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]

buildtools/rust/lib.rs line 25

Quoted 10 lines
buildtools/rust/lib.rs new file +10 -0
+    fn test_helloworld() {
+	let appname = std::ffi::CString::new("test-rs").unwrap();
+	let mut argv = [appname.into_raw()];
+	let ret = unsafe {
+		rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
+	};
+	assert!(ret >= 0, "rte_eal_init failed");
+        unsafe { rte_eal_cleanup() };
+    }
+}

This test code is included in the patch to make it easy to verify that DPDK can be built and run with rust - at least doing EAL init and cleanup.

To test, once rust is available on your system (e.g. using rustup), apply this patch and then just do "cargo test" in the root of your DPDK directory. After waiting a minute for things to build you should see the test run output of DPDK initialization.

The whole approach for now I'm deliberately keeping extremely minimal so as
a) not to invest too much effort before we know what way we want to go and
b) to make it easier to review and comment upon.

For anyone wanting to try this out in a standalone app or play with developing a higher-level crate, I've pushed this patch to my github account. This is the "Cargo.toml" file entry I've used to pull this down when investigating wrapping eal_init into a "higher-level" rust-style init API:

[dependencies]
dpdk-c = { git = "https://github.com/bruce-richardson/dpdk", branch = "cargo-build" }

Regards,

/Bruce

#44 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

In reply to Bruce Richardson

Hello Bruce,

Quoted 8 lines

Add a Cargo.toml file in the root folder and a number of other scripts and rust-related files into buildtools/rust, which then enables DPDK to be cloned and built as a rust crate - all-be-it one with only two
functions: rte_eal_init and rte_eal_cleanup.

---

Quoted 8 lines

This RFC is proposed as an alternative approach to enabling rust support in DPDK. The key difference vs previous is that we are taking the whole DPDK project here as a rust "crate", which can then be used by other higher-level crates as a dependency. Building the crate does a (minimal) build of DPDK and statically links it in, so there is no "install" step or anything needed - the Rust app just adds DPDK to their Cargo.toml file and then should have everything they need.

Having a shared source directory for both for C and Rust DPDK infrastructure is the correct approach. My concern is how to properly maintain Rust crate once DPDK starts to implement
it's own API.
Rust files may need a separate directories to host libraries, applications and PMD.

Quoted 65 lines
Cargo.toml new file +16 -0
new file mode 100644
index 0000000000..fe0843975a
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2025 Intel Corporation
+
+[package]
+name = "dpdk-c"
+version = "25.0.7"
+build = "buildtools/rust/build.rs"
+edition = "2021"
+
+[lib]
+path = "buildtools/rust/lib.rs"
+doctest = false
+
+[build-dependencies]
+bindgen = "0.71"
+meson-next = "1"
buildtools/meson.build +1 -0
index 4e2c1217a2..a13b5189c1 100644
@@ -26,6 +26,7 @@ header_gen_cmd = py3 + files('gen-header.py')
 has_hugepages_cmd = py3 + files('has-hugepages.py')
 cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py')
 check_dts_requirements = py3 + files('check-dts-requirements.py')
+gen_cargo_bindgen_info = py3 + files('rust/gen-cargo-bindgen-info.py')
 
 # install any build tools that end-users might want also

install_data([

buildtools/rust/build.rs new file +25 -0
new file mode 100644
index 0000000000..1ffdf03d2f
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+extern crate meson_next as meson;
+use std::collections::HashMap;
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    let build_dir = out_path.join("build");
+
+    let meson_cfg = meson::Config::new().options(HashMap::from([
+        ("enable_libs", "eal"),
+        ("enable_drivers", "net/*,net/intel/*"),
+        ("enable_apps", "test")
+    ]));
+    meson::build(".", build_dir.to_str().unwrap(), meson_cfg);
+
+    /* open and print file 'cargo_rules.txt' from build_dir */
+    let cargo_ldflags_file = build_dir.join("cargo_ldflags.txt");
+    println!("cargo:rerun-if-changed={}", cargo_ldflags_file.display());
+    print!("{}", std::fs::read_to_string(cargo_ldflags_file).unwrap());
+

buildtools/rust/build.rs line 35

Quoted 10 lines
buildtools/rust/build.rs new file +10 -0
+    let bindgen_include_file = build_dir.join("bindgen_cflags.txt");
+    let mut bindings = bindgen::Builder::default();
+    for line in std::fs::read_to_string(bindgen_include_file).unwrap().lines() {
+            bindings = bindings.clang_arg(line);
+    }
+
+    let bindings = bindings.header("buildtools/rust/wrapper.h")
+        .derive_default(true)
+        .allowlist_function("rte_eal_init")
+        .allowlist_function("rte_eal_cleanup")

Calling the allowlist_function() method generates well-maintained target bindings.
That approach requires to specify each DPDK symbol for Rust library API explicitly. There are way too many symbols to bind even for a simple network application.

buildtools/rust/build.rs line 45

Quoted 10 lines
buildtools/rust/build.rs new file +10 -0
+        // Tell cargo to invalidate the built crate whenever any of the
+        // included header files changed.
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+        .generate()
+        .expect("Unable to generate bindings");
+
+    bindings
+        .write_to_file(out_path.join("bindings.rs"))
+        .expect("Couldn't write bindings!");
+}

Bindings file or directory for RTE library API is not enough. Each PMD has it's own symbols set and will need a separate bindings library.

Quoted 74 lines
buildtools/rust/gen-cargo-bindgen-info.py new file +52 -0
new file mode 100644
index 0000000000..bea432ead3
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2025 Intel Corporation
+
+# Script to generate cargo rules for static linking in rust builds
+# outputs one line per library, for drivers and libraries
+
+import os
+import os.path
+import sys
+import subprocess
+
+if 'MESON_BUILD_ROOT' not in os.environ:
+    print('This script must be called from a meson build environment')
+    sys.exit(1)
+
+pkgconf = sys.argv[1]
+os.environ['PKG_CONFIG_PATH'] = os.path.join(os.environ['MESON_BUILD_ROOT'], 'meson-uninstalled')
+linker_flags = subprocess.check_output([pkgconf, '--libs', '--static', 'libdpdk']).decode('utf-8')
+cflags = subprocess.check_output([pkgconf, '--cflags', 'libdpdk']).decode('utf-8')
+
+whole_archive = False
+with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'cargo_ldflags.txt'), 'w') as dst:
+    for flag in linker_flags.split():
+        if flag == '-pthread':
+            continue
+        elif flag == '-Wl,--whole-archive':
+            whole_archive = True
+        elif flag == '-Wl,--no-whole-archive':
+            whole_archive = False
+        elif flag.startswith('-L'):
+            dst.write(f'cargo:rustc-link-search=native={flag[2:]}\n')
+        elif flag.startswith('-l:'):
+            libname = flag[3:]
+            if libname.startswith('lib'):
+                libname = libname[3:]
+            if libname.endswith('.a'):
+                libname = libname[:-2]
+            if whole_archive:
+                dst.write(f'cargo:rustc-link-lib=static:+whole-archive={libname}\n')
+            else:
+                dst.write(f'cargo:rustc-link-lib=static={libname}\n')
+        elif flag.startswith('-lrte_'):
+            # skip any other DPDK lib flags, we already have them above
+            continue
+        elif flag.startswith('-l'):
+            dst.write(f'cargo:rustc-link-lib={flag[2:]}\n')
+        else:
+            print(f'Warning: Unknown flag: {flag}', file=sys.stderr)
+
+with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'bindgen_cflags.txt'), 'w') as dst:
+    for flag in cflags.split():
+        dst.write(f'{flag}\n')
buildtools/rust/lib.rs new file +10 -0
new file mode 100644
index 0000000000..f99c54ac42
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+

buildtools/rust/lib.rs line 20

Quoted 10 lines
buildtools/rust/lib.rs new file +10 -0
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_helloworld() {
+       let appname = std::ffi::CString::new("test-rs").unwrap();
+       let mut argv = [appname.into_raw()];
+       let ret = unsafe {
+               rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())

Activating rte_eal_init() proves that Rust crate has properly linked with DPDK libraries. That is more like infrastructure test.
An active network port with IO capabilities is a real Rust-DPDK POC.

Quoted 29 lines
buildtools/rust/lib.rs new file +5 -0
+       };
+       assert!(ret >= 0, "rte_eal_init failed");
+        unsafe { rte_eal_cleanup() };
+    }
+}
buildtools/rust/wrapper.h new file +5 -0
new file mode 100644
index 0000000000..25f5bf94e2
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#include <rte_eal.h>
meson.build +3 -0
index 8436d1dff8..72c82178e1 100644
@@ -114,6 +114,9 @@ configure_file(output: build_cfg,
 # build pkg-config files for dpdk
 subdir('buildtools/pkg-config')
 
+# output a list of cargo parameters if DPDK is being built for rust use
+run_command([gen_cargo_bindgen_info, pkgconf], check: true)
+
 if meson.is_subproject()
    subdir('buildtools/subproject')
Quoted 5 lines

endif
--
2.45.2

Regards,
Gregory

#45 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Quoted 7 lines

From: Etelson, Gregory
Sent: Thursday, April 10, 2025 6:28 AM
To: Richardson, Bruce
Cc: [email protected]
Subject: Re: [RFC PATCH] add rust binding support to DPDK

Hello Bruce,

Hi Bruce & Gregory,

Quoted 17 lines
Quoted 8 lines

Add a Cargo.toml file in the root folder and a number of other scripts and rust-related files into buildtools/rust, which then enables DPDK to be cloned and built as a rust crate - all-be-it one with only two
functions: rte_eal_init and rte_eal_cleanup.

---

Quoted 8 lines

This RFC is proposed as an alternative approach to enabling rust support in DPDK. The key difference vs previous is that we are taking the whole DPDK project here as a rust "crate", which can then be used by other higher-level crates as a dependency. Building the crate does a (minimal) build of DPDK and statically links it in, so there is no "install" step or anything needed - the Rust app just adds DPDK to their Cargo.toml file and then should have everything they need.

Quoted 2 lines

Having a shared source directory for both for C and Rust DPDK infrastructure is the correct approach.

It seems so, with the various approaches so far, agreed.

I've tested Bruce's repo, and Cargo.toml. The initial build takes some time.. but it works! The experience of "Adding DPDK to a Rust project" is very similar to adding any other Rust crate. I like this - it makes the "strangeness" of depending on DPDK near-zero :)

Quoted 2 lines

My concern is how to properly maintain Rust crate once DPDK starts to implement it's own API.

I'm not really sure what is meant here. I don't understand what "own" word refers to?

I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)

So DPDK changing APIs is API/ABI breaking for *all* consumers of DPDK, not just the Rust bindings. No special treatment required, just identify API/ABI changes when they occur (as per ABI policy) and fix.

Some approaches to how CI could help, and when to identify/fix Rust bindings took place on the last TechBoard call (minutes not yet available on https://core.dpdk.org/techboard/minutes/).
No decision made on how - we're still at prototyping phase anyway.

Quoted 1 line

Rust files may need a separate directories to host libraries, applications and PMD.

I'm not sure this is strictly required - perhaps "nice to have". Bruce's approach has been to minimize changes in the "DPDK C repo", doing just enough to enable building "Safe Rust" wrappers on top in a seperate "DPDK Rust" repo.

I like this approach - use what bindgen gives access to - and incrementally export/expose more DPDK APIs *as they are consumed*. This ensures we don't accidentally over-export, or have mistakes go unnoticed because nobody tested/used the APIs yet. More on this below in the context of bindgen usage.

<snip>

Quoted 5 lines
Quoted 5 lines
buildtools/rust/build.rs new file +0 -0
new file mode 100644
index 0000000000..1ffdf03d2f

<snip>

Quoted 10 lines

buildtools/rust/build.rs line 35

Quoted 4 lines
buildtools/rust/build.rs new file +4 -0
+    let bindings = bindings.header("buildtools/rust/wrapper.h")
+        .derive_default(true)
+        .allowlist_function("rte_eal_init")
+        .allowlist_function("rte_eal_cleanup")

Calling the allowlist_function() method generates well-maintained target bindings.
That approach requires to specify each DPDK symbol for Rust library API explicitly. There are way too many symbols to bind even for a simple network application.

The "allowlist" approach ensures that DPDK C functions, bindgen-converted in Rust are used, and reviewed.

If all DPDK C APIs are exported, there is additional and unknown risk of bugs, that the users of the DPDK-rust-bindings will hit: providing bad user-experience. I do not believe it is a good idea to export all API structs & symbols without any tests, reviews, and quality control.

If it is really desired by a end user to export all C API as "unsafe Rust" bindings, there are many crates existing already doing this (and your patch Gregory also provides that method?). I do not feel this adds value for DPDK community to maintain: the end-goal must be an ergonomic and safe Rust API.

My vote is to incrementally "allowlist" new APIs, and having a "Safe Rust" (or "idiomatic Rust APIs") wrapping them to export public functions in the "end-user facing" DPDK Rust crate. This ensures that any public function in the "DPDK Rust" crate has been built, reviewed and tested.

This will cause the initial versions of the "DPDK Rust" to be very minimal, and to gradually export more functionality and APIs in a safe way. From my perspective, this is the only way to give a consistent high-quality user-experience that is the norm for the Rust ecosystem.

Quoted 2 lines

Bindings file or directory for RTE library API is not enough. Each PMD has it's own symbols set and will need a separate bindings library.

As per above, I recommend we focus on minimal functionality, exported and exposed in an ergonomic way. That means initial focus on EAL, Lcore management, Mempools, Ethdev, and Mbufs. Or from a "use-case" perspective, enough to get L2-macswap to work using "Safe Rust" bindings.

This means that things like "PMD specific symbols" are not going to be high on the priority list initially. Once the above L2 forward use-case is satisfied, I'm happy to provide input on designing an API which is "PMD specific" in a generic way (initial technical thoughts, a "dyn Trait" with downcast to a PMD specific trait).

<snip>

Quoted 7 lines
Quoted 7 lines
buildtools/rust/build.rs new file +7 -0
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_helloworld() {
+       let appname = std::ffi::CString::new("test-rs").unwrap();
Quoted 8 lines

buildtools/rust/build.rs

Quoted 3 lines
buildtools/rust/build.rs new file +3 -0
+       let mut argv = [appname.into_raw()];
+       let ret = unsafe {
+               rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())

Activating rte_eal_init() proves that Rust crate has properly linked with DPDK libraries.
That is more like infrastructure test.
An active network port with IO capabilities is a real Rust-DPDK POC.

Agreed - as you can see, this is a #[test]. It is intended only to show that eal_init() and cleanup() work.

Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports, lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?

I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.

Thanks for the inputs! Regards, -Harry

#46 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Hello Bruce & Harry,

<snip>

Quoted 10 lines
Quoted 3 lines

My concern is how to properly maintain Rust crate once DPDK starts to implement it's own API.

I'm not really sure what is meant here. I don't understand what "own" word refers to?

I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)

Bindgen cannot provide access to all DPDK public API.

A good example here is rte_eth_rx_burst().
That function is defined as inline and bindgen does not translate it.
Also, the function definition references rte_eth_fp_ops array that is not part of the public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and the solution can require extensions to existing DPDK API.

I added a new public API that exports rte_eth_fp_ops for a given port Id.

Rust implementation of rte_eth_rx_burst() does not have to follow the original approach. Usage of rte_eth_fp_ops is good for C, but Rust has different methods.

For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs to provide native implementation for some public DPDK API. It can be easier to maintain Rust files separately.

You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk

<snip>

Quoted 4 lines

Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports, lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?

I suggest to set the first goal to initiate a port and run simple Rx/Tx functions.

Quoted 4 lines

I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.

Thanks for the inputs! Regards, -Harry

Regards,
Gregory

#47 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Quoted 14 lines

From: Etelson, Gregory
Sent: Friday, April 11, 2025 4:39 PM
To: Van Haaren, Harry
Cc: Richardson, Bruce; [email protected]
Subject: Re: [RFC PATCH] add rust binding support to DPDK

Hello Bruce & Harry,

<snip>

Quoted 4 lines
Quoted 3 lines

My concern is how to properly maintain Rust crate once DPDK starts to implement it's own API.

Quoted 8 lines
Quoted 6 lines

I'm not really sure what is meant here. I don't understand what "own" word refers to?

I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)

Bindgen cannot provide access to all DPDK public API.

Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap. Correct - thanks - I understand your point now.

Quoted 11 lines

A good example here is rte_eth_rx_burst().
That function is defined as inline and bindgen does not translate it. Also, the function definition references rte_eth_fp_ops array that is not part of the public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and the solution can require extensions to existing DPDK API.

I added a new public API that exports rte_eth_fp_ops for a given port Id.

Rust implementation of rte_eth_rx_burst() does not have to follow the original approach.
Usage of rte_eth_fp_ops is good for C, but Rust has different methods.

Agreed there is a certain "mismatch" sometimes, if functions aren't in the actually "C ABI" then they can't be called via bindgen.

Agree that elegant solutions (clean, maintainable, and high performance) will have to be found here. Many existing solutions just wrap the "static inline" function into a "non-static" function, and export it as a public symbol. That allows calling into it from Rust (via bindgen-generated header) however causes an actual function call.. (LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)

As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons, it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call. We have work to do to find/propose the best solution.

Quoted 3 lines

For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs to provide native implementation for some public DPDK API.
It can be easier to maintain Rust files separately.

OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above. I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.

Quoted 1 line

You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk

Thanks for the link - I see you've been pushing code actively! Good to see opportunities, and compare approaches and concepts. Have you investigated wrapping the various pointers into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.

The code in the above repo feels like "DPDK C code written in Rust". It is a great step towards better understanding, and having something that works is very valuable; thanks for sharing it.

A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.

Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?

Quoted 5 lines
Quoted 3 lines

Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports, lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?

I suggest to set the first goal to initiate a port and run simple Rx/Tx functions.

Agreed, we need some API to "work towards" (Top down) as well as "work from" (DPDK C APIs, and your repo above).

Quoted 1 line
Quoted 1 line

I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.

Hopefully this Safe-Rust API proposal will be a candidate for "Top Down" API design approach.

Quoted 4 lines
Quoted 1 line

Thanks for the inputs! Regards, -Harry

Regards,
Gregory

Thanks! -Harry

#48 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Hello Harry,

Quoted 27 lines
Quoted 11 lines
Quoted 9 lines
Quoted 2 lines

My concern is how to properly maintain Rust crate once DPDK starts to implement it's own API.

I'm not really sure what is meant here. I don't understand what "own" word refers to?

I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)

Bindgen cannot provide access to all DPDK public API.

Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap. Correct - thanks - I understand your point now.

Quoted 11 lines

A good example here is rte_eth_rx_burst().
That function is defined as inline and bindgen does not translate it. Also, the function definition references rte_eth_fp_ops array that is not part of the public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and the solution can require extensions to existing DPDK API.

I added a new public API that exports rte_eth_fp_ops for a given port Id.

Rust implementation of rte_eth_rx_burst() does not have to follow the original approach.
Usage of rte_eth_fp_ops is good for C, but Rust has different methods.

Quoted 9 lines

Agreed there is a certain "mismatch" sometimes, if functions aren't in the actually "C ABI" then they can't be called via bindgen.

Agree that elegant solutions (clean, maintainable, and high performance) will have to be found here. Many existing solutions just wrap the "static inline" function into a "non-static" function, and export it as a public symbol. That allows calling into it from Rust (via bindgen-generated header) however causes an actual function call.. (LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)

The core DPDK code is maintained as a self-contained pure C project.
What about extending that model and provide direct access to DPDK resources that are needed for Rust API ? That can be arranged as part of "native" DPDK API or as extension for Rust-enabled DPDK only. I'm currently experimenting with the latter in https://github.com/getelson-at-mellanox/rdpdk/tree/main/dpdk-patches

Quoted 3 lines

As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons, it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call. We have work to do to find/propose the best solution.

What exactly is a small amount of performance degradation ?
For some existing performance oriented projects loosing performance to eliminate the unsafe {} construct is not an option.

Quoted 9 lines
Quoted 4 lines

For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs to provide native implementation for some public DPDK API.
It can be easier to maintain Rust files separately.

OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above. I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.

Quoted 7 lines
Quoted 1 line

You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk

Thanks for the link - I see you've been pushing code actively! Good to see opportunities, and compare approaches and concepts. Have you investigated wrapping the various pointers into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.

This construct can work:

struct PktsBuf<const SIZE: usize> {
buffer: [*mut rte_mbuf; SIZE]
}

Quoted 1 line

The code in the above repo feels like "DPDK C code written in Rust".

Agree.
At this stage there is no native Rust DPDK API.

Quoted 8 lines

It is a great step towards better
understanding, and having something that works is very valuable; thanks for sharing it.

A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.

Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?

I think we need to conclude what the safe API means in terms of DPDK project. Because it's possible to provide native Rust API for DPDK what will use FFI. Specially, if Rust PMD is not in plans and performance is one of the main goals.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports, lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?

check out for Rx, L2 addr swap, Tx sequence here:
https://github.com/getelson-at-mellanox/rdpdk/blob/253fde7c16a5514bf99d3823725f825262e244c8/app/runpmd/runpmd.rs#L156

Regards,
Gregory

#49 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Hello all,

I wanted to chime in as someone who uses DPDK from Rust in quite a few projects.

No snips so the conversation can continue past me for things I don't comment on. Hello Harry,

Quoted 27 lines
Quoted 11 lines
Quoted 9 lines
Quoted 2 lines

My concern is how to properly maintain Rust crate once DPDK starts to implement it's own API.

I'm not really sure what is meant here. I don't understand what "own" word refers to?

I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)

Bindgen cannot provide access to all DPDK public API.

Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap. Correct - thanks - I understand your point now.

Quoted 11 lines

A good example here is rte_eth_rx_burst().
That function is defined as inline and bindgen does not translate it. Also, the function definition references rte_eth_fp_ops array that is not part of the public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and the solution can require extensions to existing DPDK API.

I added a new public API that exports rte_eth_fp_ops for a given port Id.

Rust implementation of rte_eth_rx_burst() does not have to follow the original approach.
Usage of rte_eth_fp_ops is good for C, but Rust has different methods.

Quoted 9 lines

Agreed there is a certain "mismatch" sometimes, if functions aren't in the actually "C ABI" then they can't be called via bindgen.

Agree that elegant solutions (clean, maintainable, and high performance) will have to be found here. Many existing solutions just wrap the "static inline" function into a "non-static" function, and export it as a public symbol. That allows calling into it from Rust (via bindgen-generated header) however causes an actual function call.. (LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)

The core DPDK code is maintained as a self-contained pure C project.
What about extending that model and provide direct access to DPDK resources that
are needed for Rust API ?
That can be arranged as part of "native" DPDK API or as extension for
Rust-enabled DPDK only.
I'm currently experimenting with the latter in
https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk%2Ftree%2Fmain%2Fdpdk-patches&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429834568%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=3YrOkklWRWlwKyfg9BblBnLsNqnCEf7RdAxdUW3JGgM%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk/tree/main/dpdk-patches>
There are some performance wins to be found along this path. It's not a lot, but you can "devirtualize" a lot of DPDK if you use Rust's generics to specialize on particular hardware combinations, at the cost of multiplying code size by the number of supported NICs. I can do this in my bindings since I tend to only need to test a few software PMDs and the hardware I have, with a fallback to "dyn dpdk::EthDev" which pulls data from info structs all of the time, instead of only pulling runtime-only information (ex: mac addr, rss key). This is paired with a top-level function which does dispatch for different hardware to "lift" the runtime information about what NIC is used to compile time. I think the main win here is from inlining driver functions, but I haven't done a detailed analysis of the "why". These are marginal improvements, around a half percent for nic_single_core_perf on my hardware, but there may be more wins for less heavily optimized paths through DPDK where the compiler can do more heavy lifting.

Quoted 3 lines

As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons, it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call. We have work to do to find/propose the best solution.

What exactly is a small amount of performance degradation ?
For some existing performance oriented projects loosing performance to
eliminate the unsafe {} construct is not an option.
If you are willing to compile with clang and use LTO, both rustc and clang will emit LLVM IR, which gets inlined at link time. I haven't been able to measure a performance difference vs C code even when calling "static inline" hot path function wrappers across the FFI boundary. This does increase compile times by quite a bit, more than LTO with pure C would increase them due to the amount of LLVM IR that rustc emits, but it means that the performance difference barely exists if it does at all.

Quoted 9 lines
Quoted 4 lines

For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs to provide native implementation for some public DPDK API.
It can be easier to maintain Rust files separately.

OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above. I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.

Quoted 7 lines
Quoted 1 line

You can check out my Rust DPDK version here: https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429852869%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=Qxiozd%2B2%2BnlqsKeuzzVdsU1egsfukfgNEyQyZASpLr8%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk>

Thanks for the link - I see you've been pushing code actively! Good to see opportunities, and compare approaches and concepts. Have you investigated wrapping the various pointers into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.

This construct can work:

struct PktsBuf<const SIZE: usize> {
buffer: [*mut rte_mbuf; SIZE]
}

I think that they mean a construct that wraps a *mut rte_mbuf and provides a safe interface. Another option, depending on how invasive the bindings are allowed to be, is to make them references with a lifetime tied to the mempool they are allocated from. For many usecases, mempools exist for the entire lifetime of the program and have static lifetimes, meaning that the references have static lifetimes which greatly simplifies a lot of the code. This also gets rid of unsafe accesses. However, it would require a wrapper around the buffer with the valid length, something like a fixed-capacity vector. Another option is to make the pointers Option<NonNull<rte_mbuf>>, which has the same ABI as a C *rte_mbuf but forces null checks. This will be annoying to program against, but for "one packet at a time to completion" processing it's equivalent to "if (buffer[i] != null) { ... }" at the top of the loop. For the pipeline or graph libraries, this latter option will incur a lot of potentially extra null checks, but the upside is that it's ABI compatible with the burst functions from the C API.

Quoted 1 line

The code in the above repo feels like "DPDK C code written in Rust".

Agree.
At this stage there is no native Rust DPDK API.

Quoted 8 lines

It is a great step towards better
understanding, and having something that works is very valuable; thanks for sharing it.

A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.

Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?

I think we need to conclude what the safe API means in terms of DPDK project.
Because it's possible to provide native Rust API for DPDK what will use FFI.
Specially, if Rust PMD is not in plans and performance is one of the main goals.
I think most of the value of Rust can be gotten by making a good Rust API available to consumers of DPDK. There is value in having Rust for PMDs, but there is a lot more code written which consumes DPDK than code in new PMDs. It might be better to hold that conversation until someone wants to write a Rust PMD. I think that there is also value in a version which is "maximum safety" that takes performance hits where necessary. DPDK is far enough ahead of the performance of most other options that "DPDK at 80%" is still going to be fast enough for many purposes and the extra safety is valuable there from an ease-of-use perspective. There are, of course, applications which need everything DPDK has to offer from a performance perspective, and those should be served as well, but I think DPDK can offer a spectrum where users get steadily closer to "zero overhead abstraction over the DPDK C API" the more performance they need, possibly sacrificing some safety along the way.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports, lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?

check out for Rx, L2 addr swap, Tx sequence here:
https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk%2Fblob%2F253fde7c16a5514bf99d3823725f825262e244c8%2Fapp%2Frunpmd%2Frunpmd.rs%23L156&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429865170%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=DKvEoYTKjKEx75AKr16Ge7wN7vigNrv75O1p%2BwTzdH0%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk/blob/253fde7c16a5514bf99d3823725f825262e244c8/app/runpmd/runpmd.rs#L156>

Regards,
Gregory

#50 lore raw

Re: [RFC PATCH] add rust binding support to DPDK

Quoted 9 lines

From: Owen Hilyard
Sent: Sunday, April 13, 2025 6:09 PM
To: Etelson, Gregory; Van Haaren, Harry
Cc: Richardson, Bruce; [email protected]
Subject: Re: [RFC PATCH] add rust binding support to DPDK

Hello all,

I wanted to chime in as someone who uses DPDK from Rust in quite a few projects.

Great - thanks - more input from different perspectives is good!

<big snip>

Quoted 1 line

I think most of the value of Rust can be gotten by making a good Rust API available to consumers of DPDK.

Yes - agree here! Adoption of DPDK in Rust ecosystem must "feel normal" just like depending on any other Rust crate.

Quoted 1 line

There is value in having Rust for PMDs, but there is a lot more code written which consumes DPDK than code in new PMDs. It might be better to hold that conversation until someone wants to write a Rust PMD.

Building PMDs in Rust (while interesting) is not the primary goal here; correct. "End user" of DPDK having a safe and high-performance Rust API is the goal.

Quoted 1 line

I think that there is also value in a version which is "maximum safety" that takes performance hits where necessary. DPDK is far enough ahead of the performance of most other options that "DPDK at 80%" is still going to be fast enough for many purposes and the extra safety is valuable there from an ease-of-use perspective. There are, of course, applications which need everything DPDK has to offer from a performance perspective, and those should be served as well, but I think DPDK can offer a spectrum where users get steadily closer to "zero overhead abstraction over the DPDK C API" the more performance they need, possibly sacrificing some safety along the way.

I like this perspective - as hardware vendors its easy to get hyper focused on maximum performance. To be clear; I want to ensure DPDK+Rust can hit max performance too, but lets focus on Safety/Ergonomics first and then optimize, not the other way around.

I see (Gregory) you posted some more "Rust like abstraction" code for DPDK: https://mails.dpdk.org/archives/dev/2025-April/317088.html
I'll reply there shortly, and give some thoughts on the details as next steps towards a safe/abstracted (but high performance!) API.

Regards, -Harry