mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

So far Devres uses an inner memory allocation and reference count, i.e. an inner Arc, in order to ensure that the devres callback can't run into a use-after-free in case where the Devres object is dropped while the devres callback runs concurrently. Instead, use a completion in order to avoid a potential UAF: In Devres::drop(), if we detect that we can't remove the devres action anymore, we wait for the completion that is completed from the devres callback. If, in turn, we were able to successfully remove the devres action, we can just go ahead. This, again, allows us to get rid of the internal Arc, and instead let Devres consume an `impl PinInit<T, E>` in order to return an `impl PinInit<Devres<T>, E>`, which enables us to get away with less memory allocations. Additionally, having the resulting explicit synchronization in Devres::drop() prevents potential subtle undesired side effects of the devres callback dropping the final Arc reference asynchronously within the devres callback. Reviewed-by: Benno Lossin <lossin@kernel.org> Reviewed-by: Boqun Feng <boqun.feng@gmail.com> Link: https://lore.kernel.org/r/20250626200054.243480-4-dakr@kernel.org [ Move '# Invariants' below '# Examples'. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
111 lines
2.7 KiB
Rust
111 lines
2.7 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//! Rust PCI driver sample (based on QEMU's `pci-testdev`).
|
|
//!
|
|
//! To make this driver probe, QEMU must be run with `-device pci-testdev`.
|
|
|
|
use kernel::{bindings, c_str, device::Core, devres::Devres, pci, prelude::*, types::ARef};
|
|
|
|
struct Regs;
|
|
|
|
impl Regs {
|
|
const TEST: usize = 0x0;
|
|
const OFFSET: usize = 0x4;
|
|
const DATA: usize = 0x8;
|
|
const COUNT: usize = 0xC;
|
|
const END: usize = 0x10;
|
|
}
|
|
|
|
type Bar0 = pci::Bar<{ Regs::END }>;
|
|
|
|
#[derive(Debug)]
|
|
struct TestIndex(u8);
|
|
|
|
impl TestIndex {
|
|
const NO_EVENTFD: Self = Self(0);
|
|
}
|
|
|
|
#[pin_data(PinnedDrop)]
|
|
struct SampleDriver {
|
|
pdev: ARef<pci::Device>,
|
|
#[pin]
|
|
bar: Devres<Bar0>,
|
|
}
|
|
|
|
kernel::pci_device_table!(
|
|
PCI_TABLE,
|
|
MODULE_PCI_TABLE,
|
|
<SampleDriver as pci::Driver>::IdInfo,
|
|
[(
|
|
pci::DeviceId::from_id(bindings::PCI_VENDOR_ID_REDHAT, 0x5),
|
|
TestIndex::NO_EVENTFD
|
|
)]
|
|
);
|
|
|
|
impl SampleDriver {
|
|
fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> {
|
|
// Select the test.
|
|
bar.write8(index.0, Regs::TEST);
|
|
|
|
let offset = u32::from_le(bar.read32(Regs::OFFSET)) as usize;
|
|
let data = bar.read8(Regs::DATA);
|
|
|
|
// Write `data` to `offset` to increase `count` by one.
|
|
//
|
|
// Note that we need `try_write8`, since `offset` can't be checked at compile-time.
|
|
bar.try_write8(data, offset)?;
|
|
|
|
Ok(bar.read32(Regs::COUNT))
|
|
}
|
|
}
|
|
|
|
impl pci::Driver for SampleDriver {
|
|
type IdInfo = TestIndex;
|
|
|
|
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
|
|
|
|
fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
|
|
dev_dbg!(
|
|
pdev.as_ref(),
|
|
"Probe Rust PCI driver sample (PCI ID: 0x{:x}, 0x{:x}).\n",
|
|
pdev.vendor_id(),
|
|
pdev.device_id()
|
|
);
|
|
|
|
pdev.enable_device_mem()?;
|
|
pdev.set_master();
|
|
|
|
let drvdata = KBox::pin_init(
|
|
try_pin_init!(Self {
|
|
pdev: pdev.into(),
|
|
bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci")),
|
|
}),
|
|
GFP_KERNEL,
|
|
)?;
|
|
|
|
let bar = drvdata.bar.access(pdev.as_ref())?;
|
|
dev_info!(
|
|
pdev.as_ref(),
|
|
"pci-testdev data-match count: {}\n",
|
|
Self::testdev(info, bar)?
|
|
);
|
|
|
|
Ok(drvdata)
|
|
}
|
|
}
|
|
|
|
#[pinned_drop]
|
|
impl PinnedDrop for SampleDriver {
|
|
fn drop(self: Pin<&mut Self>) {
|
|
dev_dbg!(self.pdev.as_ref(), "Remove Rust PCI driver sample.\n");
|
|
}
|
|
}
|
|
|
|
kernel::module_pci_driver! {
|
|
type: SampleDriver,
|
|
name: "rust_driver_pci",
|
|
authors: ["Danilo Krummrich"],
|
|
description: "Rust PCI driver",
|
|
license: "GPL v2",
|
|
}
|