* [RFC PATCH v1 1/2] rust: pci: add PCIe bus error handler support
2025-11-08 16:55 [RFC PATCH v1 0/2] rust: pci: Introduce PCIe error handler support and sample usage Guangbo Cui
@ 2025-11-08 16:55 ` Guangbo Cui
2025-11-08 16:55 ` [RFC PATCH v1 2/2] sample: rust: pci: implement dummy error handlers to demonstrate usage Guangbo Cui
2025-11-11 8:26 ` [RFC PATCH v1 0/2] rust: pci: Introduce PCIe error handler support and sample usage Danilo Krummrich
2 siblings, 0 replies; 5+ messages in thread
From: Guangbo Cui @ 2025-11-08 16:55 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Danilo Krummrich
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Bjorn Helgaas, Krzysztof Wilczyński, rust-for-linux,
linux-pci, Guangbo Cui
This patch introduces Rust abstractions for PCIe Advanced Error Reporting
(AER) and general PCI bus error handling. It provides safe bindings for
the struct pci_error_handlers callbacks, allowing Rust PCI drivers to
implement device-specific recovery logic for events such as MMIO
restoration, slot reset, and fatal/correctable error notifications.
The new ErrorHandler trait defines the equivalent of the C
pci_error_handlers interface, including all callback methods, along
with Rust enums ErsResult and ChannelState that mirror the kernel's
pci_ers_result_t and pci_channel_state_t.
This enables fully type-safe integration between Rust PCI drivers and
the Linux PCI error recovery infrastructure.
Existing Rust PCI sample drivers are updated to specify a default
ErrorHandler = () until they implement custom error handling.
Signed-off-by: Guangbo Cui <jckeep.cuiguangbo@gmail.com>
---
rust/kernel/pci.rs | 11 ++
rust/kernel/pci/err.rs | 273 ++++++++++++++++++++++++++
samples/rust/rust_dma.rs | 1 +
samples/rust/rust_driver_auxiliary.rs | 2 +
samples/rust/rust_driver_pci.rs | 2 +
5 files changed, 289 insertions(+)
create mode 100644 rust/kernel/pci/err.rs
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 7fcc5f6022c1..24c82305f195 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -24,8 +24,10 @@
};
use kernel::prelude::*;
+mod err;
mod id;
+pub use self::err::{ChannelState, ErrorHandler, ErsResult};
pub use self::id::{Class, ClassMask, Vendor};
/// An adapter for the registration of PCI drivers.
@@ -47,6 +49,7 @@ unsafe fn register(
(*pdrv.get()).probe = Some(Self::probe_callback);
(*pdrv.get()).remove = Some(Self::remove_callback);
(*pdrv.get()).id_table = T::ID_TABLE.as_ptr();
+ (*pdrv.get()).err_handler = err::ErrorHandlerVTable::<T::ErrorHandler>::vtable_ptr();
}
// SAFETY: `pdrv` is guaranteed to be a valid `RegType`.
@@ -265,6 +268,14 @@ pub trait Driver: Send {
// ```
type IdInfo: 'static;
+ /// The PCI error handler implementation for this driver.
+ // TODO: Use `associated_type_defaults` once stabilized:
+ //
+ // ```
+ // type ErrorHandler: err::ErrorHandler = ();
+ // ```
+ type ErrorHandler: err::ErrorHandler;
+
/// The table of device ids supported by the driver.
const ID_TABLE: IdTable<Self::IdInfo>;
diff --git a/rust/kernel/pci/err.rs b/rust/kernel/pci/err.rs
new file mode 100644
index 000000000000..d61520563bc3
--- /dev/null
+++ b/rust/kernel/pci/err.rs
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! PCI error handling abstractions.
+//!
+//! This module provides traits and types to handle PCI bus errors in Rust PCI drivers.
+
+use core::marker::PhantomData;
+
+use kernel::prelude::*;
+
+use crate::{
+ device,
+ error::VTABLE_DEFAULT_ERROR, //
+};
+
+use super::Device;
+
+/// Result type for PCI error handling operations.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum ErsResult {
+ /// No result/none/not supported in device driver
+ None = bindings::pci_ers_result_PCI_ERS_RESULT_NONE,
+ /// Device driver can recover without slot reset
+ CanRecover = bindings::pci_ers_result_PCI_ERS_RESULT_CAN_RECOVER,
+ /// Device driver wants slot to be reset
+ NeedReset = bindings::pci_ers_result_PCI_ERS_RESULT_NEED_RESET,
+ /// Device has completely failed, is unrecoverable
+ Disconnect = bindings::pci_ers_result_PCI_ERS_RESULT_DISCONNECT,
+ /// Device driver is fully recovered and operational
+ Recovered = bindings::pci_ers_result_PCI_ERS_RESULT_RECOVERED,
+ /// No AER capabilities registered for the driver
+ NoAerDriver = bindings::pci_ers_result_PCI_ERS_RESULT_NO_AER_DRIVER,
+}
+
+impl ErsResult {
+ fn into_c(self) -> bindings::pci_ers_result_t {
+ self as bindings::pci_ers_result_t
+ }
+}
+
+/// PCI channel state representation.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum ChannelState {
+ /// I/O channel is in normal state
+ Normal = bindings::pci_channel_io_normal,
+ /// I/O to channel is blocked
+ Frozen = bindings::pci_channel_io_frozen,
+ /// PCI card is dead
+ PermanentFailure = bindings::pci_channel_io_perm_failure,
+}
+
+impl TryFrom<u32> for ChannelState {
+ type Error = kernel::error::Error;
+
+ fn try_from(value: u32) -> Result<Self> {
+ match value {
+ bindings::pci_channel_io_normal => Ok(ChannelState::Normal),
+ bindings::pci_channel_io_frozen => Ok(ChannelState::Frozen),
+ bindings::pci_channel_io_perm_failure => Ok(ChannelState::PermanentFailure),
+ _ => Err(kernel::error::code::EINVAL),
+ }
+ }
+}
+
+/// PCI bus error handler trait.
+#[vtable]
+pub trait ErrorHandler {
+ /// The driver type associated with this error handler.
+ type Driver;
+
+ /// PCI bus error detected on this device
+ fn error_detected(
+ _dev: &Device<device::Bound>,
+ _error: ChannelState,
+ _this: Pin<&Self::Driver>,
+ ) -> ErsResult {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// MMIO has been re-enabled, but not DMA
+ fn mmio_enabled(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) -> ErsResult {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// PCI slot has been reset
+ fn slot_reset(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) -> ErsResult {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// PCI function reset prepare
+ fn reset_prepare(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// PCI function reset completed
+ fn reset_done(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Device driver may resume normal operations
+ fn resume(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Allow device driver to record more details of a correctable error
+ fn cor_error_detected(_dev: &Device<device::Bound>, _this: Pin<&Self::Driver>) {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+#[vtable]
+impl ErrorHandler for () {
+ type Driver = ();
+}
+
+/// A vtable for the error handler trait.
+pub(super) struct ErrorHandlerVTable<T: ErrorHandler>(PhantomData<T>);
+
+impl<T: ErrorHandler + 'static> ErrorHandlerVTable<T> {
+ extern "C" fn error_detected(
+ pdev: *mut bindings::pci_dev,
+ error: bindings::pci_channel_state_t,
+ ) -> bindings::pci_ers_result_t {
+ // SAFETY: The PCI bus only ever calls the error_detected callback with a valid pointer
+ // to a `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `error_detected()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `error_detected` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ let error = ChannelState::try_from(error).unwrap_or(ChannelState::PermanentFailure);
+
+ T::error_detected(pdev, error, data).into_c()
+ }
+
+ extern "C" fn mmio_enabled(pdev: *mut bindings::pci_dev) -> bindings::pci_ers_result_t {
+ // SAFETY: The PCI bus only ever calls the mmio_enabled callback with a valid pointer
+ // to a `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `mmio_enabled()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `mmio_enabled` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::mmio_enabled(pdev, data).into_c()
+ }
+
+ extern "C" fn slot_reset(pdev: *mut bindings::pci_dev) -> bindings::pci_ers_result_t {
+ // SAFETY: The PCI bus only ever calls the slot_reset callback with a valid pointer to a
+ // `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `slot_reset()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `slot_reset` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::slot_reset(pdev, data).into_c()
+ }
+
+ extern "C" fn reset_prepare(pdev: *mut bindings::pci_dev) {
+ // SAFETY: The PCI bus only ever calls the reset_prepare callback with a valid pointer to a
+ // `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `reset_prepare()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `reset_prepare` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::reset_prepare(pdev, data)
+ }
+
+ extern "C" fn reset_done(pdev: *mut bindings::pci_dev) {
+ // SAFETY: The PCI bus only ever calls the reset_done callback with a valid pointer to a
+ // `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `reset_done()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `reset_done` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::reset_done(pdev, data)
+ }
+
+ extern "C" fn resume(pdev: *mut bindings::pci_dev) {
+ // SAFETY: The PCI bus only ever calls the resume callback with a valid pointer to a
+ // `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `resume()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `resume` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::resume(pdev, data)
+ }
+
+ extern "C" fn cor_error_detected(pdev: *mut bindings::pci_dev) {
+ // SAFETY: The PCI bus only ever calls the cor_error_detected callback with a valid pointer
+ // to a `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `cor_error_detected()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `cor_error_detected` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<Pin<KBox<T::Driver>>>() };
+
+ T::cor_error_detected(pdev, data)
+ }
+
+ const VTABLE: bindings::pci_error_handlers = bindings::pci_error_handlers {
+ error_detected: if T::HAS_ERROR_DETECTED {
+ Some(Self::error_detected)
+ } else {
+ None
+ },
+ mmio_enabled: if T::HAS_MMIO_ENABLED {
+ Some(Self::mmio_enabled)
+ } else {
+ None
+ },
+ slot_reset: if T::HAS_SLOT_RESET {
+ Some(Self::slot_reset)
+ } else {
+ None
+ },
+ reset_prepare: if T::HAS_RESET_PREPARE {
+ Some(Self::reset_prepare)
+ } else {
+ None
+ },
+ reset_done: if T::HAS_RESET_DONE {
+ Some(Self::reset_done)
+ } else {
+ None
+ },
+ resume: if T::HAS_RESUME {
+ Some(Self::resume)
+ } else {
+ None
+ },
+ cor_error_detected: if T::HAS_COR_ERROR_DETECTED {
+ Some(Self::cor_error_detected)
+ } else {
+ None
+ },
+ };
+
+ pub(super) const fn vtable_ptr() -> *const bindings::pci_error_handlers {
+ core::ptr::from_ref(&Self::VTABLE)
+ }
+}
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs
index 4d324f06cc2a..ea0113a42446 100644
--- a/samples/rust/rust_dma.rs
+++ b/samples/rust/rust_dma.rs
@@ -53,6 +53,7 @@ unsafe impl kernel::transmute::FromBytes for MyStruct {}
impl pci::Driver for DmaSampleDriver {
type IdInfo = ();
+ type ErrorHandler = ();
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs
index 55ece336ee45..b714be978fdf 100644
--- a/samples/rust/rust_driver_auxiliary.rs
+++ b/samples/rust/rust_driver_auxiliary.rs
@@ -56,6 +56,8 @@ struct ParentDriver {
impl pci::Driver for ParentDriver {
type IdInfo = ();
+ type ErrorHandler = ();
+
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 55a683c39ed9..11ed48dd9fbb 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -63,6 +63,8 @@ fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> {
impl pci::Driver for SampleDriver {
type IdInfo = TestIndex;
+ type ErrorHandler = ();
+
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v1 2/2] sample: rust: pci: implement dummy error handlers to demonstrate usage
2025-11-08 16:55 [RFC PATCH v1 0/2] rust: pci: Introduce PCIe error handler support and sample usage Guangbo Cui
2025-11-08 16:55 ` [RFC PATCH v1 1/2] rust: pci: add PCIe bus error handler support Guangbo Cui
@ 2025-11-08 16:55 ` Guangbo Cui
2025-11-11 8:26 ` [RFC PATCH v1 0/2] rust: pci: Introduce PCIe error handler support and sample usage Danilo Krummrich
2 siblings, 0 replies; 5+ messages in thread
From: Guangbo Cui @ 2025-11-08 16:55 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Danilo Krummrich
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Bjorn Helgaas, Krzysztof Wilczyński, rust-for-linux,
linux-pci, Guangbo Cui
This patch adds a dummy implementation of PCIe error handlers to the
Rust sample PCI driver. It demonstrates how to implement the
`pci::ErrorHandler` trait.
Signed-off-by: Guangbo Cui <jckeep.cuiguangbo@gmail.com>
---
samples/rust/rust_driver_pci.rs | 47 +++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 11ed48dd9fbb..d575f84fde7b 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -4,7 +4,17 @@
//!
//! To make this driver probe, QEMU must be run with `-device pci-testdev`.
-use kernel::{c_str, device::Core, devres::Devres, pci, prelude::*, sync::aref::ARef};
+use kernel::{
+ c_str,
+ device::{
+ Bound,
+ Core, //
+ },
+ devres::Devres,
+ pci,
+ prelude::*,
+ sync::aref::ARef,
+};
struct Regs;
@@ -63,7 +73,7 @@ fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> {
impl pci::Driver for SampleDriver {
type IdInfo = TestIndex;
- type ErrorHandler = ();
+ type ErrorHandler = Self;
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
@@ -106,6 +116,39 @@ fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) {
}
}
+#[vtable]
+impl pci::ErrorHandler for SampleDriver {
+ type Driver = Self;
+
+ fn error_detected(
+ pdev: &pci::Device<Bound>,
+ error: pci::ChannelState,
+ _this: Pin<&Self::Driver>,
+ ) -> pci::ErsResult {
+ dev_info!(pdev.as_ref(), "error detected.\n");
+
+ match error {
+ pci::ChannelState::PermanentFailure => {
+ dev_err!(pdev.as_ref(), "Permanent failure detected.\n");
+ pci::ErsResult::Disconnect
+ }
+ _ => {
+ dev_info!(pdev.as_ref(), "Attempting recovery from error.\n");
+ pci::ErsResult::NeedReset
+ }
+ }
+ }
+
+ fn slot_reset(pdev: &pci::Device<Bound>, _this: Pin<&Self::Driver>) -> pci::ErsResult {
+ dev_info!(pdev.as_ref(), "slot reset.\n");
+ pci::ErsResult::Recovered
+ }
+
+ fn resume(pdev: &pci::Device<Bound>, _this: Pin<&Self::Driver>) {
+ dev_info!(pdev.as_ref(), "resume.\n");
+ }
+}
+
#[pinned_drop]
impl PinnedDrop for SampleDriver {
fn drop(self: Pin<&mut Self>) {
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread