DPDK patches and discussions
 help / color / mirror / Atom feed
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


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