From: Gregory Etelson <getelson@nvidia.com>
To: <getelson@nvidia.com>
Cc: <bruce.richardson@intel.com>, <dev@dpdk.org>,
<mkashani@nvidia.com>, <thomas@monjalon.net>,
<harry.van.haaren@intel.com>, <igootorov@gmail.com>
Subject: [PATCH v3] rust: support raw DPDK API
Date: Fri, 14 Mar 2025 20:38:15 +0200 [thread overview]
Message-ID: <20250314183815.246612-1-getelson@nvidia.com> (raw)
In-Reply-To: <20250306133713.393057-1-getelson@nvidia.com>
The patch converts include files with DPDK API to RUST and binds new
RUST API files into raw module under dpdk crate.
The RUST files and DPDK libraries build from C sources
allow creation of DPDK application in RUST.
RUST DPDK application must specify the `dpdk` crate as
dependency in Cargo.toml file.
RUST `dpdk` crate is installed into
$MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust directory.
Software requirements:
- clang
- RUST installation
- bindgen-cli crate
RUST dpdk installation instructions:
1. Configure DPDK with `-Deanble_rust=true`
2. Build and install DPDK. The installation procedure will create
$MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust crate.
3. Update PKG_CONFIG_PATH to point to DPDK installation.
Signed-off-by: Gregory Etelson <getelson@nvidia.com>
---
v2:
Change rust crate name from dpdklib to dpdk.
Add raw module for to link with C API.
Add "cargo:rerun-if-changed=build.rs".
v3:
Move init_port_config() to Port.
Move start_port() to Port.
Remove Cargo.lock from git repository
Reformat code.
---
buildtools/meson.build | 4 +
buildtools/rust-env.sh | 81 ++++++++++
examples/rust/helloworld/Cargo.toml | 7 +
examples/rust/helloworld/build.rs | 24 +++
examples/rust/helloworld/src/main.rs | 219 +++++++++++++++++++++++++++
meson_options.txt | 2 +
6 files changed, 337 insertions(+)
create mode 100755 buildtools/rust-env.sh
create mode 100644 examples/rust/helloworld/Cargo.toml
create mode 100644 examples/rust/helloworld/build.rs
create mode 100644 examples/rust/helloworld/src/main.rs
diff --git a/buildtools/meson.build b/buildtools/meson.build
index 4e2c1217a2..b9d0092f07 100644
--- a/buildtools/meson.build
+++ b/buildtools/meson.build
@@ -50,3 +50,7 @@ else
pmdinfo += 'ar'
pmdinfogen += 'elf'
endif
+
+if get_option('enable_rust')
+ meson.add_install_script(['rust-env.sh', get_option('libdir')])
+endif
diff --git a/buildtools/rust-env.sh b/buildtools/rust-env.sh
new file mode 100755
index 0000000000..fe0877643b
--- /dev/null
+++ b/buildtools/rust-env.sh
@@ -0,0 +1,81 @@
+#! /bin/sh
+
+# Convert DPDK API files into RUST.
+# DPDK files selection is on demand.
+#
+# The coversion is done in 4 stages:
+# 1. Preparation [Optional]
+# Due to the bindgen conversion utility limitations source file may need
+# manual adjustment.
+# 2. Preprocessing [Mandatory]
+# Run preprocessor on a source file before conversion.
+# 3. Conversion [Mandatory]
+# Convert preprocessed C file into RUST file
+# 4. Post translation [Optional]
+# Manually fix translation.
+
+# DPDK files list
+files='
+rte_build_config.h
+rte_eal.h
+rte_ethdev.h
+rte_mbuf.h
+rte_mbuf_core.h
+rte_mempool.h
+'
+libdir="$1"
+rust_dir="${MESON_INSTALL_DESTDIR_PREFIX}/$libdir/rust"
+include_dir="${MESON_INSTALL_DESTDIR_PREFIX}/include"
+
+if test -d "$rust_dir"; then
+ rm -rf "$rust_dir"
+fi
+
+mkdir -p "$rust_dir/src/raw"
+if ! test -d "$rust_dir"; then
+ echo "failed to create Rust library $rust_dir"
+ exit 255
+fi
+
+bindgen_opt='--no-layout-tests --no-derive-debug'
+bindgen_clang_opt='-Wno-unused-command-line-argument'
+
+create_rust_lib ()
+{
+ base=$1
+
+ cp $include_dir/${base}.h /tmp/${base}.h
+
+# bindgen cannot process complex macro definitions
+# manually simplify macros before conversion
+ sed -i -e 's/RTE_BIT64(\([0-9]*\))/(1UL << \1)/g' /tmp/${base}.h
+ sed -i -e 's/RTE_BIT32(\([0-9]*\))/(1U << \1)/g' /tmp/${base}.h
+ sed -i -e 's/UINT64_C(\([0-9]*\))/\1/g' /tmp/${base}.h
+
+ # clang output has better integration with bindgen than GCC
+ clang -E -dD -I$include_dir /tmp/${base}.h > /tmp/$base.i
+ bindgen $bindgen_opt --output $rust_dir/src/raw/$base.rs /tmp/$base.i -- $bindgen_clang_opt
+ rm -f /tmp/$base.i /tmp/$base.h
+}
+
+echo 'pub mod raw;' > "$rust_dir/src/lib.rs"
+
+touch "$rust_dir/src/raw/mod.rs"
+for file in $files; do
+ base=$(basename $file | cut -d. -f 1)
+ create_rust_lib $base
+ echo "pub mod $base;" >> "$rust_dir/src/raw/mod.rs"
+done
+
+cat > "$rust_dir/Cargo.toml" <<EOF
+[package]
+name = "dpdk"
+version = "$(cat ${MESON_SOURCE_ROOT}/VERSION | sed 's/\.0\([1-9]\)/\.\1/')"
+EOF
+
+# post conversion updates
+# RUST does not accept aligned structures into packed structure.
+# TODO: fix DPDK definitions.
+sed -i 's/repr(align(2))/repr(packed(2))/g' "$rust_dir/src/raw/rte_ethdev.rs"
+
+echo "Install RUST DPDK crate in $rust_dir"
diff --git a/examples/rust/helloworld/Cargo.toml b/examples/rust/helloworld/Cargo.toml
new file mode 100644
index 0000000000..ceeecf958d
--- /dev/null
+++ b/examples/rust/helloworld/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "helloworld"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+dpdk = {path = $MESON_INSTALL_DESTDIR_PREFIX/$libdir/rust }
diff --git a/examples/rust/helloworld/build.rs b/examples/rust/helloworld/build.rs
new file mode 100644
index 0000000000..bd5737d209
--- /dev/null
+++ b/examples/rust/helloworld/build.rs
@@ -0,0 +1,24 @@
+use std::process::Command;
+
+pub fn main() {
+ let mut pkgconfig = Command::new("pkg-config");
+
+ match pkgconfig.args(["--libs", "libdpdk"]).output() {
+ Ok(output) => {
+ let stdout = String::from_utf8_lossy(&output.stdout)
+ .trim_end()
+ .to_string();
+ for token in stdout.split_ascii_whitespace().filter(|s| !s.is_empty()) {
+ if token.starts_with("-L") {
+ println!("cargo::rustc-link-search=native={}", &token[2..]);
+ } else if token.starts_with("-l") {
+ println!("cargo::rustc-link-lib={}", &token[2..]);
+ }
+ }
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ Err(error) => {
+ panic!("failed to read libdpdk package: {:?}", error);
+ }
+ }
+}
diff --git a/examples/rust/helloworld/src/main.rs b/examples/rust/helloworld/src/main.rs
new file mode 100644
index 0000000000..0c057cb39f
--- /dev/null
+++ b/examples/rust/helloworld/src/main.rs
@@ -0,0 +1,219 @@
+/// Usage: helloworld -a <port 1 params> -a <port 2 params> ...
+use std::env;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::os::raw::{c_char, c_int};
+
+use dpdk::raw::rte_eal::{
+ rte_eal_cleanup,
+ // Functions
+ rte_eal_init,
+};
+
+use dpdk::raw::rte_ethdev::{
+ RTE_ETH_DEV_NO_OWNER,
+ RTE_ETH_NAME_MAX_LEN,
+ RTE_ETH_RSS_IP,
+ rte_eth_conf,
+ rte_eth_dev_configure,
+ // Functions
+ rte_eth_dev_get_name_by_port,
+ // Structures
+ rte_eth_dev_info,
+ rte_eth_dev_info_get,
+ rte_eth_dev_start,
+ rte_eth_find_next_owned_by,
+ rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_RSS,
+ rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_VMDQ_DCB_RSS,
+
+ rte_eth_rx_queue_setup,
+ rte_eth_rxconf,
+
+ rte_eth_tx_queue_setup,
+ rte_eth_txconf,
+};
+
+use dpdk::raw::rte_build_config::RTE_MAX_ETHPORTS;
+
+use dpdk::raw::rte_mbuf::rte_pktmbuf_pool_create;
+
+use dpdk::raw::rte_mbuf_core::RTE_MBUF_DEFAULT_BUF_SIZE;
+
+pub type DpdkPort = u16;
+pub struct Port {
+ pub port_id: DpdkPort,
+ pub dev_info: rte_eth_dev_info,
+ pub dev_conf: rte_eth_conf,
+ pub rxq_num: u16,
+ pub txq_num: u16,
+}
+
+impl Port {
+ unsafe fn new(id: DpdkPort) -> Self {
+ Port {
+ port_id: id,
+ dev_info: unsafe {
+ let uninit: ::std::mem::MaybeUninit<rte_eth_dev_info> =
+ ::std::mem::MaybeUninit::zeroed().assume_init();
+ *uninit.as_ptr()
+ },
+ dev_conf: unsafe {
+ let uninit: ::std::mem::MaybeUninit<rte_eth_conf> =
+ ::std::mem::MaybeUninit::zeroed().assume_init();
+ *uninit.as_ptr()
+ },
+ rxq_num: 1,
+ txq_num: 1,
+ }
+ }
+
+ pub unsafe fn init_port_config(&mut self) {
+ let ret = unsafe {
+ rte_eth_dev_info_get(self.port_id, &mut self.dev_info as *mut rte_eth_dev_info)
+ };
+ if ret != 0 {
+ panic!("self-{}: failed to get dev info {ret}", self.port_id);
+ }
+
+ self.dev_conf.rx_adv_conf.rss_conf.rss_key = std::ptr::null_mut();
+ self.dev_conf.rx_adv_conf.rss_conf.rss_hf = if self.rxq_num > 1 {
+ RTE_ETH_RSS_IP as u64 & self.dev_info.flow_type_rss_offloads
+ } else {
+ 0
+ };
+
+ if self.dev_conf.rx_adv_conf.rss_conf.rss_hf != 0 {
+ self.dev_conf.rxmode.mq_mode = rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_VMDQ_DCB_RSS
+ & rte_eth_rx_mq_mode_RTE_ETH_MQ_RX_RSS;
+ }
+ }
+
+ unsafe fn start_port(&mut self) {
+ let mut rc = unsafe {
+ rte_eth_dev_configure(
+ self.port_id,
+ self.rxq_num,
+ self.txq_num,
+ &self.dev_conf as *const rte_eth_conf,
+ )
+ };
+ if rc != 0 {
+ panic!("failed to configure self-{}: {rc}", self.port_id)
+ }
+ println!("self-{} configured", self.port_id);
+
+ rc = unsafe { rte_eth_tx_queue_setup(self.port_id, 0, 64, 0, 0 as *const rte_eth_txconf) };
+ if rc != 0 {
+ panic!("self-{}: failed to configure TX queue 0 {rc}", self.port_id)
+ }
+ println!("self-{} configured TX queue 0", self.port_id);
+
+ let mbuf_pool_name = CString::new(format!("mbuf pool self-{}", self.port_id)).unwrap();
+ let mbuf_pool: *mut dpdk::raw::rte_mbuf::rte_mempool = unsafe {
+ rte_pktmbuf_pool_create(
+ mbuf_pool_name.as_ptr(),
+ 1024,
+ 0,
+ 0,
+ RTE_MBUF_DEFAULT_BUF_SIZE as u16,
+ 0,
+ )
+ };
+ if mbuf_pool == 0 as *mut dpdk::raw::rte_mbuf::rte_mempool {
+ panic!("self-{}: failed to allocate mempool {rc}", self.port_id)
+ }
+ println!("self-{} mempool ready", self.port_id);
+
+ let mut rxq_conf: rte_eth_rxconf = self.dev_info.default_rxconf.clone();
+ rxq_conf.offloads = 0;
+ rc = unsafe {
+ rte_eth_rx_queue_setup(
+ self.port_id,
+ 0,
+ 64,
+ 0,
+ &mut rxq_conf as *mut rte_eth_rxconf,
+ mbuf_pool as *mut dpdk::raw::rte_ethdev::rte_mempool,
+ )
+ };
+ if rc != 0 {
+ panic!("self-{}: failed to configure RX queue 0 {rc}", self.port_id)
+ }
+ println!("self-{} configured RX queue 0", self.port_id);
+ rc = unsafe { rte_eth_dev_start(self.port_id) };
+ if rc != 0 {
+ panic!("failed to start self-{}: {rc}", self.port_id)
+ }
+ println!("self-{} started", self.port_id);
+ }
+}
+
+pub unsafe fn iter_rte_eth_dev_owned_by(owner_id: u64) -> impl Iterator<Item = DpdkPort> {
+ let mut port_id: DpdkPort = 0 as DpdkPort;
+ std::iter::from_fn(move || {
+ let cur = port_id;
+ port_id = unsafe { rte_eth_find_next_owned_by(cur, owner_id) as DpdkPort };
+ if port_id == RTE_MAX_ETHPORTS as DpdkPort {
+ return None;
+ }
+ if cur == port_id {
+ port_id += 1
+ }
+ Some(cur)
+ })
+}
+
+pub unsafe fn iter_rte_eth_dev() -> impl Iterator<Item = DpdkPort> {
+ unsafe { iter_rte_eth_dev_owned_by(RTE_ETH_DEV_NO_OWNER as u64) }
+}
+
+pub unsafe fn show_ports_summary(ports: &Vec<Port>) {
+ let mut name_buf: [c_char; RTE_ETH_NAME_MAX_LEN as usize] =
+ [0 as c_char; RTE_ETH_NAME_MAX_LEN as usize];
+ let title = format!("{:<4} {:<32} {:<14}", "Port", "Name", "Driver");
+ println!("{title}");
+ ports.iter().for_each(|p| unsafe {
+ let _rc = rte_eth_dev_get_name_by_port(p.port_id, name_buf.as_mut_ptr());
+ let name = CStr::from_ptr(name_buf.as_ptr());
+ let drv = CStr::from_ptr(p.dev_info.driver_name);
+ let summary = format!(
+ "{:<4} {:<32} {:<14}",
+ p.port_id,
+ name.to_str().unwrap(),
+ drv.to_str().unwrap()
+ );
+ println!("{summary}");
+ });
+}
+
+fn main() {
+ let mut argv: Vec<*mut c_char> = env::args()
+ .map(|arg| CString::new(arg).unwrap().into_raw())
+ .collect();
+
+ let rc = unsafe { rte_eal_init(env::args().len() as c_int, argv.as_mut_ptr()) };
+ if rc == -1 {
+ unsafe {
+ rte_eal_cleanup();
+ }
+ }
+
+ let mut ports: Vec<Port> = vec![];
+ unsafe {
+ for port_id in
+ iter_rte_eth_dev().take(dpdk::raw::rte_build_config::RTE_MAX_ETHPORTS as usize)
+ {
+ let mut port = Port::new(port_id);
+ port.init_port_config();
+ println!("init port {port_id}");
+ port.start_port();
+ ports.push(port);
+ }
+ }
+
+ unsafe {
+ show_ports_summary(&ports);
+ }
+
+ println!("Hello, world!");
+}
diff --git a/meson_options.txt b/meson_options.txt
index e49b2fc089..d37b9ba1dc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -60,3 +60,5 @@ option('tests', type: 'boolean', value: true, description:
'build unit tests')
option('use_hpet', type: 'boolean', value: false, description:
'use HPET timer in EAL')
+option('enable_rust', type: 'boolean', value: false, description:
+ 'enable RUST')
--
2.45.2
prev parent reply other threads:[~2025-03-14 18:38 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-06 13:37 [PATCH] rust: support " Gregory Etelson
2025-03-06 19:26 ` Van Haaren, Harry
2025-03-07 16:56 ` Etelson, Gregory
2025-03-07 15:54 ` Van Haaren, Harry
2025-03-07 16:20 ` Bruce Richardson
2025-03-07 18:15 ` Etelson, Gregory
2025-03-07 18:00 ` Etelson, Gregory
2025-03-08 14:28 ` Igor Gutorov
2025-03-08 19:14 ` Etelson, Gregory
2025-03-10 15:31 ` Stephen Hemminger
2025-03-12 5:21 ` Etelson, Gregory
2025-03-08 18:50 ` [PATCH v2] rust: support raw " Gregory Etelson
2025-03-10 16:13 ` Van Haaren, Harry
2025-03-10 16:25 ` Bruce Richardson
2025-03-12 17:19 ` Thomas Monjalon
2025-03-14 19:12 ` Etelson, Gregory
2025-03-10 15:00 ` [PATCH] rust: support " Stephen Hemminger
2025-03-12 5:12 ` Etelson, Gregory
2025-03-10 16:18 ` Stephen Hemminger
2025-03-10 16:30 ` Bruce Richardson
2025-03-12 14:30 ` Etelson, Gregory
2025-03-13 7:56 ` Igor Gutorov
2025-03-12 15:29 ` Igor Gutorov
2025-03-12 17:24 ` Thomas Monjalon
2025-03-14 18:38 ` Gregory Etelson [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250314183815.246612-1-getelson@nvidia.com \
--to=getelson@nvidia.com \
--cc=bruce.richardson@intel.com \
--cc=dev@dpdk.org \
--cc=harry.van.haaren@intel.com \
--cc=igootorov@gmail.com \
--cc=mkashani@nvidia.com \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).