[PATCH 12/14] samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver

From: Zhi Wang

Date: Tue Sep 15 2026 - 17:02:35 EST


C drivers need a type-checked way to invoke functionality implemented by
Rust PF drivers without receiving the PF driver's complete private data.
The Rust SR-IOV sample currently demonstrates only a Rust VF consumer.

Add an optional C VF module that borrows the sample's published PF data
through a `struct rust_ffi` descriptor. Validate the ABI token, version,
and operations-table size before calling an automatically generated Rust
trampoline.

Route Rust and C VF requests through the same PF handler. The generated C
callback forwards the requester's PCI ID and translates the Rust `Result`
into the C errno convention. The C driver uses the borrow only while its
VF remains bound, so all users must be drained before its remove callback
returns.

Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
MAINTAINERS | 2 +-
rust/bindings/bindings_helper.h | 4 ++
samples/rust/Kconfig | 17 +++++++-
samples/rust/Makefile | 1 +
samples/rust/rust_driver_sriov.h | 27 ++++++++++++
samples/rust/rust_driver_sriov.rs | 43 ++++++++++++++++---
samples/rust/rust_driver_sriov_c_vf.c | 61 +++++++++++++++++++++++++++
7 files changed, 146 insertions(+), 9 deletions(-)
create mode 100644 samples/rust/rust_driver_sriov.h
create mode 100644 samples/rust/rust_driver_sriov_c_vf.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9f70dc14bf78..e03ebe44c341 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21141,7 +21141,7 @@ F: rust/helpers/pci.c
F: rust/kernel/pci.rs
F: rust/kernel/pci/
F: samples/rust/rust_driver_pci.rs
-F: samples/rust/rust_driver_sriov.rs
+F: samples/rust/rust_driver_sriov*

PCIE BANDWIDTH CONTROLLER
M: Ilpo Järvinen <ilpo.jarvinen@xxxxxxxxxxxxxxx>
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6a30455768b4..2467305c4842 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -112,6 +112,10 @@
#include <drm/drm_panic.h>
#endif

+#if IS_ENABLED(CONFIG_SAMPLE_RUST_DRIVER_SRIOV)
+#include "../../samples/rust/rust_driver_sriov.h"
+#endif
+
/* `bindgen` gets confused at certain things. */
const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN;
const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN;
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 737670fd68f8..3747f3fe23dd 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -132,13 +132,28 @@ config SAMPLE_RUST_DRIVER_SRIOV
tristate "SR-IOV Driver"
depends on PCI_IOV
help
- This option builds the Rust SR-IOV driver sample.
+ This option builds the Rust SR-IOV PF and VF driver sample.

To compile this as a module, choose M here:
the module will be called rust_driver_sriov.

If unsure, say N.

+config SAMPLE_RUST_DRIVER_SRIOV_C_VF
+ tristate "C VF consumer for the Rust SR-IOV driver"
+ depends on SAMPLE_RUST_DRIVER_SRIOV
+ help
+ This option builds a C VF driver that accesses PF data through FFI
+ published by the Rust SR-IOV PF sample.
+
+ To compile this as a module, choose M here: the module will be called
+ rust_driver_sriov_c_vf. Load it before rust_driver_sriov and enable
+ VFs only after both drivers are registered. Ensure that no other VF
+ driver has claimed the device; driver_override may be used for a
+ deterministic test.
+
+ If unsure, say N.
+
config SAMPLE_RUST_DRIVER_USB
tristate "USB Driver"
depends on USB = y
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 238a11d5ec39..b52f07df52de 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C) += rust_driver_i2c.o
obj-$(CONFIG_SAMPLE_RUST_I2C_CLIENT) += rust_i2c_client.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_SRIOV_C_VF) += rust_driver_sriov_c_vf.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_SRIOV) += rust_driver_sriov.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o
diff --git a/samples/rust/rust_driver_sriov.h b/samples/rust/rust_driver_sriov.h
new file mode 100644
index 000000000000..1d4dff4e3414
--- /dev/null
+++ b/samples/rust/rust_driver_sriov.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SAMPLES_RUST_DRIVER_SRIOV_H
+#define _SAMPLES_RUST_DRIVER_SRIOV_H
+
+#include <linux/rust_ffi.h>
+
+#define RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH 0x6c8686c04f7a4ba1ULL
+#define RUST_DRIVER_SRIOV_FFI_TOKEN_LOW 0x77ab9a3c4d3cfb34ULL
+#define RUST_DRIVER_SRIOV_FFI_ABI_MAJOR 1U
+#define RUST_DRIVER_SRIOV_FFI_ABI_MINOR 0U
+
+/**
+ * struct rust_driver_sriov_ops - Operations published by the Rust PF sample
+ * @submit: Submit one request for a requester ID and return 0 or a negative
+ * errno
+ *
+ * The context passed to each operation must be the context from the borrowed
+ * struct rust_ffi. It remains valid until the VF driver is fully unbound,
+ * including the return of its remove() callback when present.
+ *
+ * @submit may sleep and must not be called from atomic context.
+ */
+struct rust_driver_sriov_ops {
+ int (*submit)(const void *context, u16 requester_id);
+};
+
+#endif /* _SAMPLES_RUST_DRIVER_SRIOV_H */
diff --git a/samples/rust/rust_driver_sriov.rs b/samples/rust/rust_driver_sriov.rs
index ae1e1babb66b..f3e37586b0f4 100644
--- a/samples/rust/rust_driver_sriov.rs
+++ b/samples/rust/rust_driver_sriov.rs
@@ -10,15 +10,24 @@
//!
//! and append `intel_iommu=on` to the guest kernel arguments.
//!
+//! The optional `rust_driver_sriov_c_vf` module demonstrates a C VF calling the same PF data
+//! through an FFI operations table.
+//! Load that module before this one and enable VFs only after both drivers are registered.
+//!
//! [igb]: https://www.qemu.org/docs/master/system/devices/igb.html
//! [vIOMMU]: https://wiki.qemu.org/Features/VT-d

use kernel::{
+ bindings,
device::{
Bound,
Core, //
},
driver,
+ interop::ffi::{
+ Abi,
+ Token, //
+ },
new_mutex,
pci,
prelude::*,
@@ -45,8 +54,9 @@ struct PfApi<'bound> {

type PfApiForLt = CovariantForLt!(PfApi<'_>);

+#[kernel::macros::ffi_vtable(SAMPLE_FFI_OPS: bindings::rust_driver_sriov_ops)]
impl PfApi<'_> {
- fn submit(self: Pin<&Self>, vf: &pci::Device<Bound>) -> Result<u64> {
+ fn submit(self: Pin<&Self>, requester_id: u16) -> Result {
let mut requests = self.requests.lock();
let request = (*requests).checked_add(1).ok_or(EOVERFLOW)?;
*requests = request;
@@ -54,15 +64,34 @@ fn submit(self: Pin<&Self>, vf: &pci::Device<Bound>) -> Result<u64> {

dev_info!(
self.pdev,
- "Handle PF request {} from VF devfn {:#x}.\n",
+ "Handle PF request {} from VF requester ID {:#06x}.\n",
request,
- vf.dev_id()
+ requester_id
);

- Ok(request)
+ Ok(())
}
}

+struct SampleFfiAbi;
+
+// SAFETY:
+// - `RawOps` and the token/version constants come from the C header shared with consumers;
+// - `SAMPLE_FFI_OPS` is initialized as that raw type by `ffi_vtable`; and
+// - every callback recovers the context as the pinned `PfApi` published below.
+unsafe impl Abi for SampleFfiAbi {
+ type Context = PfApiForLt;
+ type RawOps = bindings::rust_driver_sriov_ops;
+
+ const OPS: &'static Self::RawOps = &SAMPLE_FFI_OPS;
+ const TOKEN: Token = Token::new(
+ bindings::RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH,
+ bindings::RUST_DRIVER_SRIOV_FFI_TOKEN_LOW,
+ );
+ const ABI_MAJOR: u16 = bindings::RUST_DRIVER_SRIOV_FFI_ABI_MAJOR as u16;
+ const ABI_MINOR: u16 = bindings::RUST_DRIVER_SRIOV_FFI_ABI_MINOR as u16;
+}
+
#[pin_data(PinnedDrop)]
struct PfDriverData<'bound> {
// Keep the device alive until the registration stops exposing `PfApi::pdev`.
@@ -125,7 +154,7 @@ fn probe<'bound>(
// - no other registration is created for this PF; and
// - VFs are enabled only after probe by `sriov_configure`.
_registration <- unsafe {
- pci::VfRegistration::new(
+ pci::VfRegistration::new_ffi::<SampleFfiAbi, _>(
pdev,
try_pin_init!(PfApi {
pdev,
@@ -191,8 +220,8 @@ fn probe<'bound>(
pdev.enable_device_mem()?;
pdev.set_master();

- let request = pf_api.submit(pdev)?;
- dev_info!(pdev, "Submitted request {} through PF data.\n", request);
+ pf_api.submit(pdev.dev_id())?;
+ dev_info!(pdev, "Submitted request through PF data.\n");

Ok(try_pin_init!(VfDriverData { pdev: pdev.into() }))
})
diff --git a/samples/rust/rust_driver_sriov_c_vf.c b/samples/rust/rust_driver_sriov_c_vf.c
new file mode 100644
index 000000000000..50df98867361
--- /dev/null
+++ b/samples/rust/rust_driver_sriov_c_vf.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "rust_driver_sriov.h"
+
+#define E1000_DEV_ID_82576_VF 0x10ca
+
+static const struct rust_ffi_token ffi_token = {
+ .high = RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH,
+ .low = RUST_DRIVER_SRIOV_FFI_TOKEN_LOW,
+};
+
+static int rust_driver_sriov_c_vf_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ const struct rust_driver_sriov_ops *ops;
+ const struct rust_ffi *ffi;
+ int ret;
+
+ ffi = pci_iov_borrow_rust_pf_data(pdev, &ffi_token,
+ RUST_DRIVER_SRIOV_FFI_ABI_MAJOR,
+ RUST_DRIVER_SRIOV_FFI_ABI_MINOR,
+ sizeof(*ops));
+ if (IS_ERR(ffi))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ffi),
+ "failed to borrow PF FFI\n");
+
+ ops = ffi->ops;
+ if (!ops->submit)
+ return dev_err_probe(&pdev->dev, -EOPNOTSUPP,
+ "PF FFI does not implement submit\n");
+
+ ret = ops->submit(ffi->context, pci_dev_id(pdev));
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to submit through PF FFI\n");
+
+ pci_info(pdev, "submitted request through Rust PF FFI\n");
+
+ return 0;
+}
+
+static const struct pci_device_id rust_driver_sriov_c_vf_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_82576_VF) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, rust_driver_sriov_c_vf_id_table);
+
+static struct pci_driver rust_driver_sriov_c_vf_driver = {
+ .name = "rust_driver_sriov_c_vf",
+ .id_table = rust_driver_sriov_c_vf_id_table,
+ .probe = rust_driver_sriov_c_vf_probe,
+};
+module_pci_driver(rust_driver_sriov_c_vf_driver);
+
+MODULE_AUTHOR("Rust for Linux Contributors");
+MODULE_DESCRIPTION("C VF consumer for the Rust SR-IOV driver sample");
+MODULE_LICENSE("GPL");