Files
linux/samples/rust/rust_dma.rs
Gary Guo e7219e53c5 rust: io: add copying methods
One feature that was lost from the old `dma_read!` and `dma_write!` when
moving to `io_read!` and `io_write!` was the ability to read/write a large
structs. However, the semantics was unclear to begin with, as there was no
guarantee about their atomicity even for structs that were small enough to
fit in u32. Re-introduce the capability in the form of copying methods.

    dma_read!(foo, bar) -> io_project!(foo, bar).copy_read()
    dma_write!(foo, bar, baz) -> io_project!(foo, bar).copy_write(baz)

Model these semantics after memcpy so user has clear expectation of lack of
atomicity. As an additional benefit of this change, this now works for MMIO
as well by mapping them to `memcpy_{from,to}io`.

For slices which is DST so the `copy_read` and `copy_write` API above can't
work, add `copy_from_slice` and `copy_to_slice` to copy from/to normal
memory.

Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://patch.msgid.link/20260706-io_projection-v6-19-72cd5d055d54@garyguo.net
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2026-07-11 18:09:04 +02:00

140 lines
3.4 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
//! Rust DMA api test (based on QEMU's `pci-testdev`).
//!
//! To make this driver probe, QEMU must be run with `-device pci-testdev`.
use kernel::{
device::Core,
dma::{
Coherent,
DataDirection,
Device,
DmaMask, //
},
io::{
io_project,
io_read,
Io, //
},
page, pci,
prelude::*,
scatterlist::{Owned, SGTable},
sync::aref::ARef,
};
#[pin_data(PinnedDrop)]
struct DmaSampleDriver {
pdev: ARef<pci::Device>,
ca: Coherent<[MyStruct]>,
#[pin]
sgt: SGTable<Owned<VVec<u8>>>,
}
const TEST_VALUES: [(u32, u32); 5] = [
(0xa, 0xb),
(0xc, 0xd),
(0xe, 0xf),
(0xab, 0xba),
(0xcd, 0xef),
];
#[derive(FromBytes, IntoBytes)]
struct MyStruct {
h: u32,
b: u32,
}
impl MyStruct {
fn new(h: u32, b: u32) -> Self {
Self { h, b }
}
}
// SAFETY: All bit patterns are acceptable values for `MyStruct`.
unsafe impl kernel::transmute::AsBytes for MyStruct {}
// SAFETY: Instances of `MyStruct` have no uninitialized portions.
unsafe impl kernel::transmute::FromBytes for MyStruct {}
kernel::pci_device_table!(
PCI_TABLE,
MODULE_PCI_TABLE,
<DmaSampleDriver as pci::Driver>::IdInfo,
[(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())]
);
impl pci::Driver for DmaSampleDriver {
type IdInfo = ();
type Data<'bound> = Self;
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
fn probe<'bound>(
pdev: &'bound pci::Device<Core<'_>>,
_info: &'bound Self::IdInfo,
) -> impl PinInit<Self, Error> + 'bound {
pin_init::pin_init_scope(move || {
dev_info!(pdev, "Probe DMA test driver.\n");
let mask = DmaMask::new::<64>();
// SAFETY: There are no concurrent calls to DMA allocation and mapping primitives.
unsafe { pdev.dma_set_mask_and_coherent(mask)? };
let ca: Coherent<[MyStruct]> =
Coherent::zeroed_slice(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?;
for (i, value) in TEST_VALUES.into_iter().enumerate() {
io_project!(ca, [panic: i]).copy_write(MyStruct::new(value.0, value.1));
}
let size = 4 * page::PAGE_SIZE;
let pages = VVec::with_capacity(size, GFP_KERNEL)?;
let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL);
Ok(try_pin_init!(Self {
pdev: pdev.into(),
ca,
sgt <- sgt,
}))
})
}
}
impl DmaSampleDriver {
fn check_dma(&self) {
for (i, value) in TEST_VALUES.into_iter().enumerate() {
let val0 = io_read!(self.ca, [panic: i].h);
let val1 = io_read!(self.ca, [panic: i].b);
assert_eq!(val0, value.0);
assert_eq!(val1, value.1);
}
}
}
#[pinned_drop]
impl PinnedDrop for DmaSampleDriver {
fn drop(self: Pin<&mut Self>) {
dev_info!(self.pdev, "Unload DMA test driver.\n");
self.check_dma();
for (i, entry) in self.sgt.iter().enumerate() {
dev_info!(
self.pdev,
"Entry[{}]: DMA address: {:#x}",
i,
entry.dma_address(),
);
}
}
}
kernel::module_pci_driver! {
type: DmaSampleDriver,
name: "rust_dma",
authors: ["Abdiel Janulgue"],
description: "Rust DMA test",
license: "GPL v2",
}