mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge patch series "rust: I/O type generalization and projection"
Gary Guo <gary@garyguo.net> says: This series presents a major rework of I/O types, as a summary: - Make I/O regions typed. The existing untyped region still exists with a dynamically sized `Region` type. - Create I/O view types to represent subregion of a full I/O region mapped. A projection macro is added to allow safely create such subviews. - Split I/O traits, make I/O views play a central role, avoid duplicate monomorphization and less `unsafe` code. - Add a `SysMem` backend, and make `Coherent` implement `Io`. - Add copying methods (memcpy_{from,to}io and friends). This series generalize `Mmio` type from just an untyped region to typed representations (so `MmioRaw<T>` is `__iomem *T`). This allows us to remove the `IoKnownSize` trait; the information is sourced from just the pointer from the `KnownSize` trait instead. Building on top of that, `Mmio` and `ConfigSpace` have been converted to typed views of I/O regions rather than just a big chunk of untyped I/O memory. These changes made it possible to implement `Io` trait for `Coherent<T>`. Shared system memory, `SysMem` is also added to the series, given it similarity in implementation compared to `Coherent`. In fact, the series use `SysMem` to implement `Coherent`'s I/O methods. Built on these generalization, this series add `io_project!()`. `io_project!()` performs a safe way to project a bigger view to a small subviews, and some Nova code has been converted in this series to demonstrate cleanups possible with this addition. New `io_read!()`, `io_write!()` has been added that supersedes `dma_read!()`, `dma_write!()` macro. Although, they work for primitives only (to be exact, types that the backend is `IoCapable` of). One feature that was lost from the old `dma_read!()` and `dma_write!()` series 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. Suggested-by: Danilo Krummrich <dakr@kernel.org> Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/near/571198078 Link: https://patch.msgid.link/20260706-io_projection-v6-0-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
@@ -9,14 +9,16 @@ use kernel::{
|
||||
dma::{
|
||||
Coherent,
|
||||
CoherentBox,
|
||||
CoherentView,
|
||||
DmaAddress, //
|
||||
},
|
||||
io::{
|
||||
io_project,
|
||||
io_write,
|
||||
Io, //
|
||||
},
|
||||
pci,
|
||||
prelude::*,
|
||||
transmute::{
|
||||
AsBytes,
|
||||
FromBytes, //
|
||||
}, //
|
||||
prelude::*, //
|
||||
};
|
||||
|
||||
pub(crate) mod cmdq;
|
||||
@@ -48,21 +50,21 @@ const LOG_BUFFER_SIZE: usize = RM_LOG_BUFFER_NUM_PAGES * GSP_PAGE_SIZE;
|
||||
|
||||
/// Array of page table entries, as understood by the GSP bootloader.
|
||||
#[repr(C)]
|
||||
#[derive(FromBytes, IntoBytes)]
|
||||
struct PteArray<const NUM_ENTRIES: usize>([u64; NUM_ENTRIES]);
|
||||
|
||||
/// SAFETY: arrays of `u64` implement `FromBytes` and we are but a wrapper around one.
|
||||
unsafe impl<const NUM_ENTRIES: usize> FromBytes for PteArray<NUM_ENTRIES> {}
|
||||
|
||||
/// SAFETY: arrays of `u64` implement `AsBytes` and we are but a wrapper around one.
|
||||
unsafe impl<const NUM_ENTRIES: usize> AsBytes for PteArray<NUM_ENTRIES> {}
|
||||
|
||||
impl<const NUM_PAGES: usize> PteArray<NUM_PAGES> {
|
||||
/// Returns the page table entry for `index`, for a mapping starting at `start`.
|
||||
// TODO: Replace with `IoView` projection once available.
|
||||
fn entry(start: DmaAddress, index: usize) -> Result<u64> {
|
||||
start
|
||||
.checked_add(num::usize_as_u64(index) << GSP_PAGE_SHIFT)
|
||||
.ok_or(EOVERFLOW)
|
||||
/// Initialize a new page table array mapping `NUM_PAGES` GSP pages starting at address `start`.
|
||||
fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
|
||||
for i in 0..NUM_PAGES {
|
||||
io_write!(view, .0[build: i],
|
||||
start
|
||||
.checked_add(num::usize_as_u64(i) << GSP_PAGE_SHIFT)
|
||||
.ok_or(EOVERFLOW)?
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,17 +91,12 @@ impl LogBuffer {
|
||||
|
||||
let start_addr = obj.0.dma_handle();
|
||||
|
||||
// SAFETY: `obj` has just been created and we are its sole user.
|
||||
let pte_region = unsafe {
|
||||
&mut obj.0.as_mut()[size_of::<u64>()..][..RM_LOG_BUFFER_NUM_PAGES * size_of::<u64>()]
|
||||
};
|
||||
|
||||
// Write values one by one to avoid an on-stack instance of `PteArray`.
|
||||
for (i, chunk) in pte_region.chunks_exact_mut(size_of::<u64>()).enumerate() {
|
||||
let pte_value = PteArray::<0>::entry(start_addr, i)?;
|
||||
|
||||
chunk.copy_from_slice(&pte_value.to_ne_bytes());
|
||||
}
|
||||
let pte_view = io_project!(
|
||||
obj.0,
|
||||
[build: size_of::<u64>()..][build: ..RM_LOG_BUFFER_NUM_PAGES * size_of::<u64>()]
|
||||
)
|
||||
.try_cast::<PteArray<RM_LOG_BUFFER_NUM_PAGES>>()?;
|
||||
PteArray::init(pte_view, start_addr)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
@@ -2,16 +2,23 @@
|
||||
|
||||
mod continuation;
|
||||
|
||||
use core::mem;
|
||||
use core::{
|
||||
mem,
|
||||
sync::atomic::{
|
||||
fence,
|
||||
Ordering, //
|
||||
},
|
||||
};
|
||||
|
||||
use kernel::{
|
||||
device,
|
||||
dma::{
|
||||
Coherent,
|
||||
CoherentBox,
|
||||
DmaAddress, //
|
||||
},
|
||||
dma_write,
|
||||
io::{
|
||||
io_project,
|
||||
poll::read_poll_timeout,
|
||||
Io, //
|
||||
},
|
||||
@@ -171,20 +178,18 @@ static_assert!(align_of::<MsgqData>() == GSP_PAGE_SIZE);
|
||||
#[repr(C)]
|
||||
// There is no struct defined for this in the open-gpu-kernel-source headers.
|
||||
// Instead it is defined by code in `GspMsgQueuesInit()`.
|
||||
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
|
||||
pub(super) struct Msgq {
|
||||
struct Msgq {
|
||||
/// Header for sending messages, including the write pointer.
|
||||
pub(super) tx: MsgqTxHeader,
|
||||
tx: MsgqTxHeader,
|
||||
/// Header for receiving messages, including the read pointer.
|
||||
pub(super) rx: MsgqRxHeader,
|
||||
rx: MsgqRxHeader,
|
||||
/// The message queue proper.
|
||||
msgq: MsgqData,
|
||||
}
|
||||
|
||||
/// Structure shared between the driver and the GSP and containing the command and message queues.
|
||||
#[repr(C)]
|
||||
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
|
||||
pub(super) struct GspMem {
|
||||
struct GspMem {
|
||||
/// Self-mapping page table entries.
|
||||
ptes: PteArray<{ Self::PTE_ARRAY_SIZE }>,
|
||||
/// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
|
||||
@@ -192,13 +197,13 @@ pub(super) struct GspMem {
|
||||
/// index into the GSP queue.
|
||||
///
|
||||
/// This member is read-only for the GSP.
|
||||
pub(super) cpuq: Msgq,
|
||||
cpuq: Msgq,
|
||||
/// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
|
||||
/// write and read pointers that the GSP updates. This means that the read pointer here is an
|
||||
/// index into the CPU queue.
|
||||
///
|
||||
/// This member is read-only for the driver.
|
||||
pub(super) gspq: Msgq,
|
||||
gspq: Msgq,
|
||||
}
|
||||
|
||||
impl GspMem {
|
||||
@@ -232,20 +237,12 @@ impl DmaGspMem {
|
||||
const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::<Msgq>() }>();
|
||||
const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();
|
||||
|
||||
let gsp_mem = Coherent::<GspMem>::zeroed(dev, GFP_KERNEL)?;
|
||||
let mut gsp_mem = CoherentBox::<GspMem>::zeroed(dev, GFP_KERNEL)?;
|
||||
gsp_mem.cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES);
|
||||
gsp_mem.cpuq.rx = MsgqRxHeader::new();
|
||||
|
||||
let start = gsp_mem.dma_handle();
|
||||
// Write values one by one to avoid an on-stack instance of `PteArray`.
|
||||
for i in 0..GspMem::PTE_ARRAY_SIZE {
|
||||
dma_write!(gsp_mem, .ptes.0[build: i], PteArray::<0>::entry(start, i)?);
|
||||
}
|
||||
|
||||
dma_write!(
|
||||
gsp_mem,
|
||||
.cpuq.tx,
|
||||
MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES)
|
||||
);
|
||||
dma_write!(gsp_mem, .cpuq.rx, MsgqRxHeader::new());
|
||||
let gsp_mem: Coherent<_> = gsp_mem.into();
|
||||
PteArray::init(io_project!(gsp_mem, .ptes), gsp_mem.dma_handle())?;
|
||||
|
||||
Ok(Self(gsp_mem))
|
||||
}
|
||||
@@ -406,7 +403,7 @@ impl DmaGspMem {
|
||||
//
|
||||
// - The returned value is within `0..MSGQ_NUM_PAGES`.
|
||||
fn gsp_write_ptr(&self) -> u32 {
|
||||
super::fw::gsp_mem::gsp_write_ptr(&self.0)
|
||||
MsgqTxHeader::write_ptr(io_project!(self.0, .gspq.tx)) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
// Returns the index of the memory page the GSP will read the next command from.
|
||||
@@ -415,7 +412,7 @@ impl DmaGspMem {
|
||||
//
|
||||
// - The returned value is within `0..MSGQ_NUM_PAGES`.
|
||||
fn gsp_read_ptr(&self) -> u32 {
|
||||
super::fw::gsp_mem::gsp_read_ptr(&self.0)
|
||||
MsgqRxHeader::read_ptr(io_project!(self.0, .gspq.rx)) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
// Returns the index of the memory page the CPU can read the next message from.
|
||||
@@ -424,12 +421,18 @@ impl DmaGspMem {
|
||||
//
|
||||
// - The returned value is within `0..MSGQ_NUM_PAGES`.
|
||||
fn cpu_read_ptr(&self) -> u32 {
|
||||
super::fw::gsp_mem::cpu_read_ptr(&self.0)
|
||||
MsgqRxHeader::read_ptr(io_project!(self.0, .cpuq.rx)) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
// Informs the GSP that it can send `elem_count` new pages into the message queue.
|
||||
fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
|
||||
super::fw::gsp_mem::advance_cpu_read_ptr(&self.0, elem_count)
|
||||
let rx = io_project!(self.0, .cpuq.rx);
|
||||
let rptr = MsgqRxHeader::read_ptr(rx).wrapping_add(elem_count) % MSGQ_NUM_PAGES;
|
||||
|
||||
// Ensure read pointer is properly ordered.
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
MsgqRxHeader::set_read_ptr(rx, rptr)
|
||||
}
|
||||
|
||||
// Returns the index of the memory page the CPU can write the next command to.
|
||||
@@ -438,12 +441,17 @@ impl DmaGspMem {
|
||||
//
|
||||
// - The returned value is within `0..MSGQ_NUM_PAGES`.
|
||||
fn cpu_write_ptr(&self) -> u32 {
|
||||
super::fw::gsp_mem::cpu_write_ptr(&self.0)
|
||||
MsgqTxHeader::write_ptr(io_project!(self.0, .cpuq.tx)) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
// Informs the GSP that it can process `elem_count` new pages from the command queue.
|
||||
fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
|
||||
super::fw::gsp_mem::advance_cpu_write_ptr(&self.0, elem_count)
|
||||
let tx = io_project!(self.0, .cpuq.tx);
|
||||
let wptr = MsgqTxHeader::write_ptr(tx).wrapping_add(elem_count) % MSGQ_NUM_PAGES;
|
||||
MsgqTxHeader::set_write_ptr(tx, wptr);
|
||||
|
||||
// Ensure all command data is visible before triggering the GSP read.
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,14 @@ use r570_144 as bindings;
|
||||
use core::ops::Range;
|
||||
|
||||
use kernel::{
|
||||
dma::Coherent,
|
||||
dma::{
|
||||
Coherent,
|
||||
CoherentView, //
|
||||
},
|
||||
io::{
|
||||
io_read,
|
||||
io_write, //
|
||||
},
|
||||
prelude::*,
|
||||
ptr::{
|
||||
Alignable,
|
||||
@@ -44,59 +51,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Replace with `IoView` projections once available.
|
||||
pub(super) mod gsp_mem {
|
||||
use core::sync::atomic::{
|
||||
fence,
|
||||
Ordering, //
|
||||
};
|
||||
|
||||
use kernel::{
|
||||
dma::Coherent,
|
||||
dma_read,
|
||||
dma_write, //
|
||||
};
|
||||
|
||||
use crate::gsp::cmdq::{
|
||||
GspMem,
|
||||
MSGQ_NUM_PAGES, //
|
||||
};
|
||||
|
||||
pub(in crate::gsp) fn gsp_write_ptr(qs: &Coherent<GspMem>) -> u32 {
|
||||
dma_read!(qs, .gspq.tx.0.writePtr) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
pub(in crate::gsp) fn gsp_read_ptr(qs: &Coherent<GspMem>) -> u32 {
|
||||
dma_read!(qs, .gspq.rx.0.readPtr) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
pub(in crate::gsp) fn cpu_read_ptr(qs: &Coherent<GspMem>) -> u32 {
|
||||
dma_read!(qs, .cpuq.rx.0.readPtr) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
pub(in crate::gsp) fn advance_cpu_read_ptr(qs: &Coherent<GspMem>, count: u32) {
|
||||
let rptr = cpu_read_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES;
|
||||
|
||||
// Ensure read pointer is properly ordered.
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
dma_write!(qs, .cpuq.rx.0.readPtr, rptr);
|
||||
}
|
||||
|
||||
pub(in crate::gsp) fn cpu_write_ptr(qs: &Coherent<GspMem>) -> u32 {
|
||||
dma_read!(qs, .cpuq.tx.0.writePtr) % MSGQ_NUM_PAGES
|
||||
}
|
||||
|
||||
pub(in crate::gsp) fn advance_cpu_write_ptr(qs: &Coherent<GspMem>, count: u32) {
|
||||
let wptr = cpu_write_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES;
|
||||
|
||||
dma_write!(qs, .cpuq.tx.0.writePtr, wptr);
|
||||
|
||||
// Ensure all command data is visible before triggering the GSP read.
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum size of a single GSP message queue element in bytes.
|
||||
pub(crate) const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: usize =
|
||||
num::u32_as_usize(bindings::GSP_MSG_QUEUE_ELEMENT_SIZE_MAX);
|
||||
@@ -720,6 +674,16 @@ impl MsgqTxHeader {
|
||||
entryOff: num::usize_into_u32::<GSP_PAGE_SIZE>(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the value of the write pointer for this queue.
|
||||
pub(crate) fn write_ptr(this: CoherentView<'_, Self>) -> u32 {
|
||||
io_read!(this, .0.writePtr)
|
||||
}
|
||||
|
||||
/// Sets the value of the write pointer for this queue.
|
||||
pub(crate) fn set_write_ptr(this: CoherentView<'_, Self>, val: u32) {
|
||||
io_write!(this, .0.writePtr, val)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Padding is explicit and does not contain uninitialized data.
|
||||
@@ -735,6 +699,16 @@ impl MsgqRxHeader {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
/// Returns the value of the read pointer for this queue.
|
||||
pub(crate) fn read_ptr(this: CoherentView<'_, Self>) -> u32 {
|
||||
io_read!(this, .0.readPtr)
|
||||
}
|
||||
|
||||
/// Sets the value of the read pointer for this queue.
|
||||
pub(crate) fn set_read_ptr(this: CoherentView<'_, Self>, val: u32) {
|
||||
io_write!(this, .0.readPtr, val)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Padding is explicit and does not contain uninitialized data.
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
//! this method is not used in this driver.
|
||||
//!
|
||||
|
||||
use core::ops::Deref;
|
||||
use kernel::{
|
||||
clk::Clk,
|
||||
device::{Bound, Core, Device},
|
||||
@@ -212,8 +211,7 @@ impl pwm::PwmOps for Th1520PwmDriverData {
|
||||
) -> Result<Self::WfHw> {
|
||||
let data = chip.drvdata();
|
||||
let hwpwm = pwm.hwpwm();
|
||||
let iomem_accessor = data.iomem.access(parent_dev)?;
|
||||
let iomap = iomem_accessor.deref();
|
||||
let iomap = data.iomem.access(parent_dev)?;
|
||||
|
||||
let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
|
||||
let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
|
||||
@@ -247,8 +245,7 @@ impl pwm::PwmOps for Th1520PwmDriverData {
|
||||
) -> Result {
|
||||
let data = chip.drvdata();
|
||||
let hwpwm = pwm.hwpwm();
|
||||
let iomem_accessor = data.iomem.access(parent_dev)?;
|
||||
let iomap = iomem_accessor.deref();
|
||||
let iomap = data.iomem.access(parent_dev)?;
|
||||
let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
|
||||
let was_enabled = duty_cycles != 0;
|
||||
|
||||
|
||||
@@ -19,6 +19,19 @@ __rust_helper void rust_helper_iounmap(void __iomem *addr)
|
||||
iounmap(addr);
|
||||
}
|
||||
|
||||
__rust_helper void rust_helper_memcpy_fromio(void *dst,
|
||||
const volatile void __iomem *src,
|
||||
size_t count)
|
||||
{
|
||||
memcpy_fromio(dst, src, count);
|
||||
}
|
||||
|
||||
__rust_helper void rust_helper_memcpy_toio(volatile void __iomem *dst,
|
||||
const void *src, size_t count)
|
||||
{
|
||||
memcpy_toio(dst, src, count);
|
||||
}
|
||||
|
||||
__rust_helper u8 rust_helper_readb(const void __iomem *addr)
|
||||
{
|
||||
return readb(addr);
|
||||
|
||||
+12
-12
@@ -70,17 +70,19 @@ struct Inner<T> {
|
||||
/// devres::Devres,
|
||||
/// io::{
|
||||
/// Io,
|
||||
/// IoKnownSize,
|
||||
/// IoBase,
|
||||
/// Mmio,
|
||||
/// MmioRaw,
|
||||
/// PhysAddr, //
|
||||
/// MmioBackend,
|
||||
/// PhysAddr,
|
||||
/// Region, //
|
||||
/// },
|
||||
/// prelude::*,
|
||||
/// };
|
||||
/// use core::ops::Deref;
|
||||
///
|
||||
/// // See also [`pci::Bar`] for a real example.
|
||||
/// struct IoMem<const SIZE: usize>(MmioRaw<SIZE>);
|
||||
/// struct IoMem<const SIZE: usize>(MmioRaw<Region<SIZE>>);
|
||||
///
|
||||
/// impl<const SIZE: usize> IoMem<SIZE> {
|
||||
/// /// # Safety
|
||||
@@ -95,7 +97,7 @@ struct Inner<T> {
|
||||
/// return Err(ENOMEM);
|
||||
/// }
|
||||
///
|
||||
/// Ok(IoMem(MmioRaw::new(addr as usize, SIZE)?))
|
||||
/// Ok(IoMem(MmioRaw::new_region(addr as usize, SIZE)?))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -106,12 +108,13 @@ struct Inner<T> {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
|
||||
/// type Target = Mmio<SIZE>;
|
||||
/// impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<SIZE> {
|
||||
/// type Backend = MmioBackend;
|
||||
/// type Target = Region<SIZE>;
|
||||
///
|
||||
/// fn deref(&self) -> &Self::Target {
|
||||
/// fn as_view(self) -> Mmio<'a, Region<SIZE>> {
|
||||
/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
|
||||
/// unsafe { Mmio::from_raw(&self.0) }
|
||||
/// unsafe { Mmio::from_raw(self.0) }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn no_run(dev: &Device<Bound>) -> Result<(), Error> {
|
||||
@@ -299,10 +302,7 @@ impl<T: Send + 'static> Devres<T> {
|
||||
/// use kernel::{
|
||||
/// device::Core,
|
||||
/// devres::Devres,
|
||||
/// io::{
|
||||
/// Io,
|
||||
/// IoKnownSize, //
|
||||
/// },
|
||||
/// io::Io,
|
||||
/// pci, //
|
||||
/// };
|
||||
///
|
||||
|
||||
+156
-125
@@ -14,14 +14,22 @@ use crate::{
|
||||
},
|
||||
error::to_result,
|
||||
fs::file,
|
||||
io::{
|
||||
IoBackend,
|
||||
IoBase,
|
||||
IoCapable,
|
||||
IoCopyable,
|
||||
SysMem,
|
||||
SysMemBackend, //
|
||||
},
|
||||
prelude::*,
|
||||
ptr::KnownSize,
|
||||
sync::aref::ARef,
|
||||
transmute::{
|
||||
AsBytes,
|
||||
FromBytes, //
|
||||
}, //
|
||||
uaccess::UserSliceWriter,
|
||||
},
|
||||
uaccess::UserSliceWriter, //
|
||||
};
|
||||
use core::{
|
||||
ops::{
|
||||
@@ -654,52 +662,6 @@ impl<T: KnownSize + ?Sized> Coherent<T> {
|
||||
// SAFETY: per safety requirement.
|
||||
unsafe { &mut *self.as_mut_ptr() }
|
||||
}
|
||||
|
||||
/// Reads the value of `field` and ensures that its type is [`FromBytes`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must be called from the [`dma_read`] macro which ensures that the `field` pointer is
|
||||
/// validated beforehand.
|
||||
///
|
||||
/// Public but hidden since it should only be used from [`dma_read`] macro.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn field_read<F: FromBytes>(&self, field: *const F) -> F {
|
||||
// SAFETY:
|
||||
// - By the safety requirements field is valid.
|
||||
// - Using read_volatile() here is not sound as per the usual rules, the usage here is
|
||||
// a special exception with the following notes in place. When dealing with a potential
|
||||
// race from a hardware or code outside kernel (e.g. user-space program), we need that
|
||||
// read on a valid memory is not UB. Currently read_volatile() is used for this, and the
|
||||
// rationale behind is that it should generate the same code as READ_ONCE() which the
|
||||
// kernel already relies on to avoid UB on data races. Note that the usage of
|
||||
// read_volatile() is limited to this particular case, it cannot be used to prevent
|
||||
// the UB caused by racing between two kernel functions nor do they provide atomicity.
|
||||
unsafe { field.read_volatile() }
|
||||
}
|
||||
|
||||
/// Writes a value to `field` and ensures that its type is [`AsBytes`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must be called from the [`dma_write`] macro which ensures that the `field` pointer is
|
||||
/// validated beforehand.
|
||||
///
|
||||
/// Public but hidden since it should only be used from [`dma_write`] macro.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn field_write<F: AsBytes>(&self, field: *mut F, val: F) {
|
||||
// SAFETY:
|
||||
// - By the safety requirements field is valid.
|
||||
// - Using write_volatile() here is not sound as per the usual rules, the usage here is
|
||||
// a special exception with the following notes in place. When dealing with a potential
|
||||
// race from a hardware or code outside kernel (e.g. user-space program), we need that
|
||||
// write on a valid memory is not UB. Currently write_volatile() is used for this, and the
|
||||
// rationale behind is that it should generate the same code as WRITE_ONCE() which the
|
||||
// kernel already relies on to avoid UB on data races. Note that the usage of
|
||||
// write_volatile() is limited to this particular case, it cannot be used to prevent
|
||||
// the UB caused by racing between two kernel functions nor do they provide atomicity.
|
||||
unsafe { field.write_volatile(val) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsBytes + FromBytes> Coherent<T> {
|
||||
@@ -1133,84 +1095,153 @@ unsafe impl Send for CoherentHandle {}
|
||||
// plain `Copy` values.
|
||||
unsafe impl Sync for CoherentHandle {}
|
||||
|
||||
/// Reads a field of an item from an allocated region of structs.
|
||||
/// View type for `Coherent`.
|
||||
///
|
||||
/// The syntax is of the form `kernel::dma_read!(dma, proj)` where `dma` is an expression evaluating
|
||||
/// to a [`Coherent`] and `proj` is a [projection specification](kernel::ptr::project!).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kernel::device::Device;
|
||||
/// use kernel::dma::{attrs::*, Coherent};
|
||||
///
|
||||
/// struct MyStruct { field: u32, }
|
||||
///
|
||||
/// // SAFETY: All bit patterns are acceptable values for `MyStruct`.
|
||||
/// unsafe impl kernel::transmute::FromBytes for MyStruct{};
|
||||
/// // SAFETY: Instances of `MyStruct` have no uninitialized portions.
|
||||
/// unsafe impl kernel::transmute::AsBytes for MyStruct{};
|
||||
///
|
||||
/// # fn test(alloc: &kernel::dma::Coherent<[MyStruct]>) -> Result {
|
||||
/// let whole = kernel::dma_read!(alloc, [try: 2]);
|
||||
/// let field = kernel::dma_read!(alloc, [panic: 1].field);
|
||||
/// # Ok::<(), Error>(()) }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! dma_read {
|
||||
($dma:expr, $($proj:tt)*) => {{
|
||||
let dma = &$dma;
|
||||
let ptr = $crate::ptr::project!(
|
||||
$crate::dma::Coherent::as_ptr(dma), $($proj)*
|
||||
);
|
||||
// SAFETY: The pointer created by the projection is within the DMA region.
|
||||
unsafe { $crate::dma::Coherent::field_read(dma, ptr) }
|
||||
}};
|
||||
/// This is same as [`SysMem`] but with additional information that allows handing out a DMA handle.
|
||||
pub struct CoherentView<'a, T: ?Sized> {
|
||||
cpu_addr: SysMem<'a, T>,
|
||||
dma_handle: DmaAddress,
|
||||
}
|
||||
|
||||
/// Writes to a field of an item from an allocated region of structs.
|
||||
///
|
||||
/// The syntax is of the form `kernel::dma_write!(dma, proj, val)` where `dma` is an expression
|
||||
/// evaluating to a [`Coherent`], `proj` is a
|
||||
/// [projection specification](kernel::ptr::project!), and `val` is the value to be written to the
|
||||
/// projected location.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kernel::device::Device;
|
||||
/// use kernel::dma::{attrs::*, Coherent};
|
||||
///
|
||||
/// struct MyStruct { member: u32, }
|
||||
///
|
||||
/// // SAFETY: All bit patterns are acceptable values for `MyStruct`.
|
||||
/// unsafe impl kernel::transmute::FromBytes for MyStruct{};
|
||||
/// // SAFETY: Instances of `MyStruct` have no uninitialized portions.
|
||||
/// unsafe impl kernel::transmute::AsBytes for MyStruct{};
|
||||
///
|
||||
/// # fn test(alloc: &kernel::dma::Coherent<[MyStruct]>) -> Result {
|
||||
/// kernel::dma_write!(alloc, [try: 2].member, 0xf);
|
||||
/// kernel::dma_write!(alloc, [panic: 1], MyStruct { member: 0xf });
|
||||
/// # Ok::<(), Error>(()) }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! dma_write {
|
||||
(@parse [$dma:expr] [$($proj:tt)*] [, $val:expr]) => {{
|
||||
let dma = &$dma;
|
||||
let ptr = $crate::ptr::project!(
|
||||
mut $crate::dma::Coherent::as_mut_ptr(dma), $($proj)*
|
||||
);
|
||||
let val = $val;
|
||||
// SAFETY: The pointer created by the projection is within the DMA region.
|
||||
unsafe { $crate::dma::Coherent::field_write(dma, ptr, val) }
|
||||
}};
|
||||
(@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => {
|
||||
$crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*])
|
||||
};
|
||||
(@parse [$dma:expr] [$($proj:tt)*] [[$flavor:ident: $index:expr] $($rest:tt)*]) => {
|
||||
$crate::dma_write!(@parse [$dma] [$($proj)* [$flavor: $index]] [$($rest)*])
|
||||
};
|
||||
($dma:expr, $($rest:tt)*) => {
|
||||
$crate::dma_write!(@parse [$dma] [] [$($rest)*])
|
||||
};
|
||||
impl<T: ?Sized> Copy for CoherentView<'_, T> {}
|
||||
impl<T: ?Sized> Clone for CoherentView<'_, T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> CoherentView<'a, T> {
|
||||
/// Erase the DMA handle information and obtain a [`SysMem`] view of the same memory region.
|
||||
#[inline]
|
||||
pub fn as_sys_mem(self) -> SysMem<'a, T> {
|
||||
self.cpu_addr
|
||||
}
|
||||
|
||||
/// Returns a DMA handle which may be given to the device as the DMA address base of the region.
|
||||
#[inline]
|
||||
pub fn dma_handle(self) -> DmaAddress {
|
||||
self.dma_handle
|
||||
}
|
||||
|
||||
/// Returns a reference to the data in the region.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * Callers must ensure that the device does not read/write to/from memory while the returned
|
||||
/// reference is live.
|
||||
/// * Callers must ensure that this call does not race with a write (including call to `as_mut`)
|
||||
/// to the same region while the returned reference is live.
|
||||
#[inline]
|
||||
pub unsafe fn as_ref(self) -> &'a T {
|
||||
// SAFETY: pointer is aligned and valid per type invariant. Aliasing rule is satisfied per
|
||||
// safety requirement.
|
||||
unsafe { &*self.cpu_addr.as_ptr() }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the data in the region.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * Callers must ensure that the device does not read/write to/from memory while the returned
|
||||
/// reference is live.
|
||||
/// * Callers must ensure that this call does not race with a read (including call to `as_ref`)
|
||||
/// or write (including call to `as_mut`) to the same region while the returned reference is
|
||||
/// live.
|
||||
#[inline]
|
||||
pub unsafe fn as_mut(self) -> &'a mut T {
|
||||
// SAFETY: pointer is aligned and valid per type invariant. Aliasing rule is satisfied per
|
||||
// safety requirement.
|
||||
unsafe { &mut *self.cpu_addr.as_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
/// `IoBackend` implementation for `Coherent`.
|
||||
pub struct CoherentIoBackend;
|
||||
|
||||
impl IoBackend for CoherentIoBackend {
|
||||
type View<'a, T: ?Sized + KnownSize> = CoherentView<'a, T>;
|
||||
|
||||
#[inline]
|
||||
fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
|
||||
SysMemBackend::as_ptr(view.cpu_addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
|
||||
view: Self::View<'a, T>,
|
||||
ptr: *mut U,
|
||||
) -> Self::View<'a, U> {
|
||||
let offset = ptr.addr() - view.cpu_addr.as_ptr().addr();
|
||||
// CAST: The offset DMA address can never overflow.
|
||||
let dma_handle = view.dma_handle + offset as DmaAddress;
|
||||
CoherentView {
|
||||
dma_handle,
|
||||
// SAFETY: Per safety requirement.
|
||||
cpu_addr: unsafe { SysMemBackend::project_view(view.cpu_addr, ptr) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IoCapable<T> for CoherentIoBackend
|
||||
where
|
||||
SysMemBackend: IoCapable<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn io_read<'a>(view: Self::View<'a, T>) -> T {
|
||||
SysMemBackend::io_read(view.cpu_addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn io_write<'a>(view: Self::View<'a, T>, value: T) {
|
||||
SysMemBackend::io_write(view.cpu_addr, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IoCopyable for CoherentIoBackend {
|
||||
#[inline]
|
||||
unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
|
||||
// SAFETY: Per safety requirement.
|
||||
unsafe { SysMemBackend::copy_from_io(view.cpu_addr, buffer) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
|
||||
// SAFETY: Per safety requirement.
|
||||
unsafe { SysMemBackend::copy_to_io(view.cpu_addr, buffer) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn copy_read<T: zerocopy::FromBytes>(view: Self::View<'_, T>) -> T {
|
||||
SysMemBackend::copy_read(view.cpu_addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn copy_write<T: zerocopy::IntoBytes>(view: Self::View<'_, T>, value: T) {
|
||||
SysMemBackend::copy_write(view.cpu_addr, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + KnownSize> IoBase<'a> for CoherentView<'a, T> {
|
||||
type Backend = CoherentIoBackend;
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn as_view(self) -> CoherentView<'a, Self::Target> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + KnownSize> IoBase<'a> for &'a Coherent<T> {
|
||||
type Backend = CoherentIoBackend;
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn as_view(self) -> CoherentView<'a, Self::Target> {
|
||||
CoherentView {
|
||||
// SAFETY: `cpu_addr` is valid and aligned kernel accessible memory.
|
||||
cpu_addr: unsafe { SysMem::new(self.cpu_addr.as_ptr()) },
|
||||
dma_handle: self.dma_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1204
-322
File diff suppressed because it is too large
Load Diff
+16
-13
@@ -2,8 +2,6 @@
|
||||
|
||||
//! Generic memory-mapped IO.
|
||||
|
||||
use core::ops::Deref;
|
||||
|
||||
use crate::{
|
||||
device::{
|
||||
Bound,
|
||||
@@ -16,7 +14,9 @@ use crate::{
|
||||
Region,
|
||||
Resource, //
|
||||
},
|
||||
IoBase,
|
||||
Mmio,
|
||||
MmioBackend,
|
||||
MmioRaw, //
|
||||
},
|
||||
prelude::*,
|
||||
@@ -225,11 +225,13 @@ impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> Deref for ExclusiveIoMem<'_, SIZE> {
|
||||
type Target = Mmio<SIZE>;
|
||||
impl<'a, const SIZE: usize> IoBase<'a> for &'a ExclusiveIoMem<'_, SIZE> {
|
||||
type Backend = MmioBackend;
|
||||
type Target = super::Region<SIZE>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.iomem
|
||||
#[inline]
|
||||
fn as_view(self) -> Mmio<'a, Self::Target> {
|
||||
self.iomem.as_view()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +246,7 @@ impl<const SIZE: usize> Deref for ExclusiveIoMem<'_, SIZE> {
|
||||
/// start of the I/O memory mapped region.
|
||||
pub struct IoMem<'a, const SIZE: usize = 0> {
|
||||
dev: &'a Device<Bound>,
|
||||
io: MmioRaw<SIZE>,
|
||||
io: MmioRaw<super::Region<SIZE>>,
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> ForLt for IoMem<'static, SIZE> {
|
||||
@@ -292,8 +294,7 @@ impl<'a, const SIZE: usize> IoMem<'a, SIZE> {
|
||||
return Err(ENOMEM);
|
||||
}
|
||||
|
||||
let io = MmioRaw::new(addr as usize, size)?;
|
||||
|
||||
let io = MmioRaw::new_region(addr as usize, size)?;
|
||||
Ok(IoMem { dev, io })
|
||||
}
|
||||
|
||||
@@ -316,11 +317,13 @@ impl<const SIZE: usize> Drop for IoMem<'_, SIZE> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> Deref for IoMem<'_, SIZE> {
|
||||
type Target = Mmio<SIZE>;
|
||||
impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<'_, SIZE> {
|
||||
type Backend = MmioBackend;
|
||||
type Target = super::Region<SIZE>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[inline]
|
||||
fn as_view(self) -> Mmio<'a, Self::Target> {
|
||||
// SAFETY: Safe as by the invariant of `IoMem`.
|
||||
unsafe { Mmio::from_raw(&self.io) }
|
||||
unsafe { Mmio::from_raw(self.io) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,14 @@ use crate::{
|
||||
/// use kernel::io::{
|
||||
/// Io,
|
||||
/// Mmio,
|
||||
/// Region,
|
||||
/// poll::read_poll_timeout, //
|
||||
/// };
|
||||
/// use kernel::time::Delta;
|
||||
///
|
||||
/// const HW_READY: u16 = 0x01;
|
||||
///
|
||||
/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result {
|
||||
/// fn wait_for_hardware<const SIZE: usize>(io: Mmio<'_, Region<SIZE>>) -> Result {
|
||||
/// read_poll_timeout(
|
||||
/// // The `op` closure reads the value of a specific status register.
|
||||
/// || io.try_read16(0x1000),
|
||||
@@ -135,13 +136,14 @@ where
|
||||
/// use kernel::io::{
|
||||
/// Io,
|
||||
/// Mmio,
|
||||
/// Region,
|
||||
/// poll::read_poll_timeout_atomic, //
|
||||
/// };
|
||||
/// use kernel::time::Delta;
|
||||
///
|
||||
/// const HW_READY: u16 = 0x01;
|
||||
///
|
||||
/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result {
|
||||
/// fn wait_for_hardware<const SIZE: usize>(io: Mmio<'_, Region<SIZE>>) -> Result {
|
||||
/// read_poll_timeout_atomic(
|
||||
/// // The `op` closure reads the value of a specific status register.
|
||||
/// || io.try_read16(0x1000),
|
||||
|
||||
+23
-21
@@ -58,7 +58,7 @@
|
||||
//! },
|
||||
//! num::Bounded,
|
||||
//! };
|
||||
//! # use kernel::io::Mmio;
|
||||
//! # use kernel::io::{Mmio, Region};
|
||||
//! # register! {
|
||||
//! # pub BOOT_0(u32) @ 0x00000100 {
|
||||
//! # 15:8 vendor_id;
|
||||
@@ -66,7 +66,7 @@
|
||||
//! # 3:0 minor_revision;
|
||||
//! # }
|
||||
//! # }
|
||||
//! # fn test(io: &Mmio<0x1000>) {
|
||||
//! # fn test(io: Mmio<'_, Region<0x1000>>) {
|
||||
//! # fn obtain_vendor_id() -> u8 { 0xff }
|
||||
//!
|
||||
//! // Read from the register's defined offset (0x100).
|
||||
@@ -113,6 +113,8 @@ use crate::{
|
||||
io::IoLoc, //
|
||||
};
|
||||
|
||||
use super::Region;
|
||||
|
||||
/// Trait implemented by all registers.
|
||||
pub trait Register: Sized {
|
||||
/// Backing primitive type of the register.
|
||||
@@ -129,7 +131,7 @@ pub trait FixedRegister: Register {}
|
||||
|
||||
/// Allows `()` to be used as the `location` parameter of [`Io::write`](super::Io::write) when
|
||||
/// passing a [`FixedRegister`] value.
|
||||
impl<T> IoLoc<T> for ()
|
||||
impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for ()
|
||||
where
|
||||
T: FixedRegister,
|
||||
{
|
||||
@@ -143,7 +145,7 @@ where
|
||||
|
||||
/// A [`FixedRegister`] carries its location in its type. Thus `FixedRegister` values can be used
|
||||
/// as an [`IoLoc`].
|
||||
impl<T> IoLoc<T> for T
|
||||
impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for T
|
||||
where
|
||||
T: FixedRegister,
|
||||
{
|
||||
@@ -168,7 +170,7 @@ impl<T: FixedRegister> FixedRegisterLoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IoLoc<T> for FixedRegisterLoc<T>
|
||||
impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for FixedRegisterLoc<T>
|
||||
where
|
||||
T: FixedRegister,
|
||||
{
|
||||
@@ -239,7 +241,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> IoLoc<T> for RelativeRegisterLoc<T, B>
|
||||
impl<const SIZE: usize, T, B> IoLoc<Region<SIZE>, T> for RelativeRegisterLoc<T, B>
|
||||
where
|
||||
T: RelativeRegister,
|
||||
B: RegisterBase<T::BaseFamily> + ?Sized,
|
||||
@@ -283,7 +285,7 @@ impl<T: RegisterArray> RegisterArrayLoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IoLoc<T> for RegisterArrayLoc<T>
|
||||
impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for RegisterArrayLoc<T>
|
||||
where
|
||||
T: RegisterArray,
|
||||
{
|
||||
@@ -370,7 +372,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> IoLoc<T> for RelativeRegisterArrayLoc<T, B>
|
||||
impl<const SIZE: usize, T, B> IoLoc<Region<SIZE>, T> for RelativeRegisterArrayLoc<T, B>
|
||||
where
|
||||
T: RelativeRegisterArray,
|
||||
B: RegisterBase<T::BaseFamily> + ?Sized,
|
||||
@@ -387,18 +389,18 @@ where
|
||||
/// which to write it.
|
||||
///
|
||||
/// Implementors can be used with [`Io::write_reg`](super::Io::write_reg).
|
||||
pub trait LocatedRegister {
|
||||
pub trait LocatedRegister<Base: ?Sized> {
|
||||
/// Register value to write.
|
||||
type Value: Register;
|
||||
/// Full location information at which to write the value.
|
||||
type Location: IoLoc<Self::Value>;
|
||||
type Location: IoLoc<Base, Self::Value>;
|
||||
|
||||
/// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write
|
||||
/// operation.
|
||||
fn into_io_op(self) -> (Self::Location, Self::Value);
|
||||
}
|
||||
|
||||
impl<T> LocatedRegister for T
|
||||
impl<const SIZE: usize, T> LocatedRegister<Region<SIZE>> for T
|
||||
where
|
||||
T: FixedRegister,
|
||||
{
|
||||
@@ -444,7 +446,7 @@ where
|
||||
/// Io,
|
||||
/// },
|
||||
/// };
|
||||
/// # use kernel::io::Mmio;
|
||||
/// # use kernel::io::{Mmio, Region};
|
||||
///
|
||||
/// register! {
|
||||
/// FIXED_REG(u32) @ 0x100 {
|
||||
@@ -453,7 +455,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test(io: &Mmio<0x1000>) {
|
||||
/// # fn test(io: Mmio<'_, Region<0x1000>>) {
|
||||
/// let val = io.read(FIXED_REG);
|
||||
///
|
||||
/// // Write from an already-existing value.
|
||||
@@ -557,7 +559,7 @@ where
|
||||
/// Io,
|
||||
/// },
|
||||
/// };
|
||||
/// # use kernel::io::Mmio;
|
||||
/// # use kernel::io::{Mmio, Region};
|
||||
///
|
||||
/// // Type used to identify the base.
|
||||
/// pub struct CpuCtlBase;
|
||||
@@ -582,7 +584,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test(io: Mmio<0x1000>) {
|
||||
/// # fn test(io: Mmio<'_, Region<0x1000>>) {
|
||||
/// // Read the status of `Cpu0`.
|
||||
/// let cpu0_started = io.read(CPU_CTL::of::<Cpu0>());
|
||||
///
|
||||
@@ -599,7 +601,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test2(io: Mmio<0x1000>) {
|
||||
/// # fn test2(io: Mmio<'_, Region<0x1000>>) {
|
||||
/// // Start the aliased `CPU0`, leaving its other fields untouched.
|
||||
/// io.update(CPU_CTL_ALIAS::of::<Cpu0>(), |r| r.with_alias_start(true));
|
||||
/// # }
|
||||
@@ -636,7 +638,7 @@ where
|
||||
/// Io,
|
||||
/// },
|
||||
/// };
|
||||
/// # use kernel::io::Mmio;
|
||||
/// # use kernel::io::{Mmio, Region};
|
||||
/// # fn get_scratch_idx() -> usize {
|
||||
/// # 0x15
|
||||
/// # }
|
||||
@@ -649,7 +651,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test(io: &Mmio<0x1000>)
|
||||
/// # fn test(io: Mmio<'_, Region<0x1000>>)
|
||||
/// # -> Result<(), Error>{
|
||||
/// // Read scratch register 0, i.e. I/O address `0x80`.
|
||||
/// let scratch_0 = io.read(SCRATCH::at(0)).value();
|
||||
@@ -722,7 +724,7 @@ where
|
||||
/// Io,
|
||||
/// },
|
||||
/// };
|
||||
/// # use kernel::io::Mmio;
|
||||
/// # use kernel::io::{Mmio, Region};
|
||||
/// # fn get_scratch_idx() -> usize {
|
||||
/// # 0x15
|
||||
/// # }
|
||||
@@ -750,7 +752,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test(io: &Mmio<0x1000>) -> Result<(), Error> {
|
||||
/// # fn test(io: Mmio<'_, Region<0x1000>>) -> Result<(), Error> {
|
||||
/// // Read scratch register 0 of CPU0.
|
||||
/// let scratch = io.read(CPU_SCRATCH::of::<Cpu0>().at(0));
|
||||
///
|
||||
@@ -792,7 +794,7 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn test2(io: &Mmio<0x1000>) -> Result<(), Error> {
|
||||
/// # fn test2(io: Mmio<'_, Region<0x1000>>) -> Result<(), Error> {
|
||||
/// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::<Cpu0>()).status();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
// Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on
|
||||
// the unstable features in use.
|
||||
//
|
||||
// Stable since Rust 1.87.0.
|
||||
#![feature(unsigned_is_multiple_of)]
|
||||
//
|
||||
// Stable since Rust 1.89.0.
|
||||
#![feature(generic_arg_infer)]
|
||||
//
|
||||
|
||||
@@ -43,7 +43,6 @@ pub use self::id::{
|
||||
pub use self::io::{
|
||||
Bar,
|
||||
ConfigSpace,
|
||||
ConfigSpaceKind,
|
||||
ConfigSpaceSize,
|
||||
DevresBar,
|
||||
Extended,
|
||||
|
||||
+95
-73
@@ -8,22 +8,21 @@ use crate::{
|
||||
device,
|
||||
devres::DevresLt,
|
||||
io::{
|
||||
Io,
|
||||
IoBackend,
|
||||
IoBase,
|
||||
IoCapable,
|
||||
IoKnownSize,
|
||||
Mmio,
|
||||
MmioRaw, //
|
||||
MmioBackend,
|
||||
MmioRaw,
|
||||
Region, //
|
||||
},
|
||||
prelude::*,
|
||||
ptr::KnownSize,
|
||||
types::{
|
||||
CovariantForLt,
|
||||
ForLt, //
|
||||
}, //
|
||||
};
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
ops::Deref, //
|
||||
};
|
||||
|
||||
/// Represents the size of a PCI configuration space.
|
||||
///
|
||||
@@ -50,68 +49,95 @@ impl ConfigSpaceSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker type for normal (256-byte) PCI configuration space.
|
||||
pub struct Normal;
|
||||
/// Alias for normal (256-byte) PCI configuration space.
|
||||
pub type Normal = Region<256>;
|
||||
|
||||
/// Marker type for extended (4096-byte) PCIe configuration space.
|
||||
pub struct Extended;
|
||||
/// Alias for extended (4096-byte) PCIe configuration space.
|
||||
pub type Extended = Region<4096>;
|
||||
|
||||
/// Trait for PCI configuration space size markers.
|
||||
///
|
||||
/// This trait is implemented by [`Normal`] and [`Extended`] to provide
|
||||
/// compile-time knowledge of the configuration space size.
|
||||
pub trait ConfigSpaceKind {
|
||||
/// The size of this configuration space in bytes.
|
||||
const SIZE: usize;
|
||||
}
|
||||
|
||||
impl ConfigSpaceKind for Normal {
|
||||
const SIZE: usize = 256;
|
||||
}
|
||||
|
||||
impl ConfigSpaceKind for Extended {
|
||||
const SIZE: usize = 4096;
|
||||
}
|
||||
|
||||
/// The PCI configuration space of a device.
|
||||
/// A view of PCI configuration space of a device.
|
||||
///
|
||||
/// Provides typed read and write accessors for configuration registers
|
||||
/// using the standard `pci_read_config_*` and `pci_write_config_*` helpers.
|
||||
///
|
||||
/// The generic parameter `S` indicates the maximum size of the configuration space.
|
||||
/// Use [`Normal`] for 256-byte legacy configuration space or [`Extended`] for
|
||||
/// 4096-byte PCIe extended configuration space (default).
|
||||
pub struct ConfigSpace<'a, S: ConfigSpaceKind = Extended> {
|
||||
/// The generic parameter `T` is the type of the view. The full configuration space is also a
|
||||
/// special type of view; in such cases, `T` can be [`Normal`] for 256-byte legacy configuration
|
||||
/// space or [`Extended`] for 4096-byte PCIe extended configuration space (default).
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// `ptr` is aligned and range `ptr..ptr + KnownSize::size(ptr)` is within
|
||||
/// `0..pdev.cfg_size().into_raw()`.
|
||||
pub struct ConfigSpace<'a, T: ?Sized = Extended> {
|
||||
pub(crate) pdev: &'a Device<device::Bound>,
|
||||
_marker: PhantomData<S>,
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Copy for ConfigSpace<'_, T> {}
|
||||
impl<T: ?Sized> Clone for ConfigSpace<'_, T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory.
|
||||
unsafe impl<T: ?Sized + Sync> Send for ConfigSpace<'_, T> {}
|
||||
|
||||
// SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory.
|
||||
unsafe impl<T: ?Sized + Sync> Sync for ConfigSpace<'_, T> {}
|
||||
|
||||
/// I/O Backend for PCI configuration space.
|
||||
pub struct ConfigSpaceBackend;
|
||||
|
||||
impl IoBackend for ConfigSpaceBackend {
|
||||
type View<'a, T: ?Sized + KnownSize> = ConfigSpace<'a, T>;
|
||||
|
||||
#[inline]
|
||||
fn as_ptr<'a, T: ?Sized + KnownSize>(view: ConfigSpace<'a, T>) -> *mut T {
|
||||
view.ptr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
|
||||
view: Self::View<'a, T>,
|
||||
ptr: *mut U,
|
||||
) -> Self::View<'a, U> {
|
||||
// INVARIANT: Per safety requirement.
|
||||
ConfigSpace {
|
||||
pdev: view.pdev,
|
||||
ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`.
|
||||
macro_rules! impl_config_space_io_capable {
|
||||
($ty:ty, $read_fn:ident, $write_fn:ident) => {
|
||||
impl<'a, S: ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> {
|
||||
unsafe fn io_read(&self, address: usize) -> $ty {
|
||||
impl IoCapable<$ty> for ConfigSpaceBackend {
|
||||
fn io_read(view: ConfigSpace<'_, $ty>) -> $ty {
|
||||
// CAST: The offset is cast to `i32` because the C functions expect a 32-bit
|
||||
// signed offset parameter. PCI configuration space size is at most 4096 bytes,
|
||||
// so the value always fits within `i32` without truncation or sign change.
|
||||
let addr = view.ptr.addr() as i32;
|
||||
|
||||
let mut val: $ty = 0;
|
||||
|
||||
// Return value from C function is ignored in infallible accessors.
|
||||
let _ret =
|
||||
// SAFETY: By the type invariant `self.pdev` is a valid address.
|
||||
// CAST: The offset is cast to `i32` because the C functions expect a 32-bit
|
||||
// signed offset parameter. PCI configuration space size is at most 4096 bytes,
|
||||
// so the value always fits within `i32` without truncation or sign change.
|
||||
unsafe { bindings::$read_fn(self.pdev.as_raw(), address as i32, &mut val) };
|
||||
|
||||
// SAFETY: By the type invariant `pdev` is a valid address.
|
||||
let _ = unsafe { bindings::$read_fn(view.pdev.as_raw(), addr, &mut val) };
|
||||
val
|
||||
}
|
||||
|
||||
unsafe fn io_write(&self, value: $ty, address: usize) {
|
||||
fn io_write(view: ConfigSpace<'_, $ty>, value: $ty) {
|
||||
// CAST: The offset is cast to `i32` because the C functions expect a 32-bit
|
||||
// signed offset parameter. PCI configuration space size is at most 4096 bytes,
|
||||
// so the value always fits within `i32` without truncation or sign change.
|
||||
let addr = view.ptr.addr() as i32;
|
||||
|
||||
// Return value from C function is ignored in infallible accessors.
|
||||
let _ret =
|
||||
// SAFETY: By the type invariant `self.pdev` is a valid address.
|
||||
// CAST: The offset is cast to `i32` because the C functions expect a 32-bit
|
||||
// signed offset parameter. PCI configuration space size is at most 4096 bytes,
|
||||
// so the value always fits within `i32` without truncation or sign change.
|
||||
unsafe { bindings::$write_fn(self.pdev.as_raw(), address as i32, value) };
|
||||
// SAFETY: By the type invariant `pdev` is a valid address.
|
||||
let _ = unsafe { bindings::$write_fn(view.pdev.as_raw(), addr, value) };
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -122,24 +148,16 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte);
|
||||
impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word);
|
||||
impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword);
|
||||
|
||||
impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> {
|
||||
/// Returns the base address of the I/O region. It is always 0 for configuration space.
|
||||
#[inline]
|
||||
fn addr(&self) -> usize {
|
||||
0
|
||||
}
|
||||
impl<'a, T: ?Sized + KnownSize> IoBase<'a> for ConfigSpace<'a, T> {
|
||||
type Backend = ConfigSpaceBackend;
|
||||
type Target = T;
|
||||
|
||||
/// Returns the maximum size of the configuration space.
|
||||
#[inline]
|
||||
fn maxsize(&self) -> usize {
|
||||
self.pdev.cfg_size().into_raw()
|
||||
fn as_view(self) -> ConfigSpace<'a, T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> {
|
||||
const MIN_SIZE: usize = S::SIZE;
|
||||
}
|
||||
|
||||
/// A PCI BAR to perform I/O-Operations on.
|
||||
///
|
||||
/// I/O backend assumes that the device is little-endian and will automatically
|
||||
@@ -151,7 +169,7 @@ impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> {
|
||||
/// memory mapped PCI BAR and its size.
|
||||
pub struct Bar<'a, const SIZE: usize = 0> {
|
||||
pdev: &'a Device<device::Bound>,
|
||||
io: MmioRaw<SIZE>,
|
||||
io: MmioRaw<crate::io::Region<SIZE>>,
|
||||
num: i32,
|
||||
}
|
||||
|
||||
@@ -204,7 +222,7 @@ impl<'a, const SIZE: usize> Bar<'a, SIZE> {
|
||||
return Err(ENOMEM);
|
||||
}
|
||||
|
||||
let io = match MmioRaw::new(ioptr, len as usize) {
|
||||
let io = match MmioRaw::new_region(ioptr, len as usize) {
|
||||
Ok(io) => io,
|
||||
Err(err) => {
|
||||
// SAFETY:
|
||||
@@ -264,12 +282,14 @@ impl<const SIZE: usize> Drop for Bar<'_, SIZE> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> Deref for Bar<'_, SIZE> {
|
||||
type Target = Mmio<SIZE>;
|
||||
impl<'a, const SIZE: usize> IoBase<'a> for &'a Bar<'_, SIZE> {
|
||||
type Backend = MmioBackend;
|
||||
type Target = crate::io::Region<SIZE>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[inline]
|
||||
fn as_view(self) -> Mmio<'a, Self::Target> {
|
||||
// SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped.
|
||||
unsafe { Mmio::from_raw(&self.io) }
|
||||
unsafe { Mmio::from_raw(self.io) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,23 +324,25 @@ impl Device<device::Bound> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an initialized normal (256-byte) config space object.
|
||||
/// Return a view of the normal (256-byte) config space.
|
||||
pub fn config_space<'a>(&'a self) -> ConfigSpace<'a, Normal> {
|
||||
// INVARIANT: null is aligned and the range is within config space.
|
||||
ConfigSpace {
|
||||
pdev: self,
|
||||
_marker: PhantomData,
|
||||
ptr: Normal::ptr_from_raw_parts_mut(core::ptr::null_mut(), self.cfg_size().into_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an initialized extended (4096-byte) config space object.
|
||||
/// Return a view of the extended (4096-byte) config space.
|
||||
pub fn config_space_extended<'a>(&'a self) -> Result<ConfigSpace<'a, Extended>> {
|
||||
if self.cfg_size() != ConfigSpaceSize::Extended {
|
||||
return Err(EINVAL);
|
||||
}
|
||||
|
||||
// INVARIANT: null is aligned and we just checked the `cfg_size`.
|
||||
Ok(ConfigSpace {
|
||||
pdev: self,
|
||||
_marker: PhantomData,
|
||||
ptr: Extended::ptr_from_raw_parts_mut(core::ptr::null_mut(), 4096),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,11 +235,20 @@ impl_alignable_uint!(u8, u16, u32, u64, usize);
|
||||
///
|
||||
/// This is a generalization of [`size_of`] that works for dynamically sized types.
|
||||
pub trait KnownSize {
|
||||
/// Minimum size of this type known at compile-time.
|
||||
const MIN_SIZE: usize;
|
||||
|
||||
/// Minimum alignment of this type known at compile-time.
|
||||
const MIN_ALIGN: Alignment;
|
||||
|
||||
/// Get the size of an object of this type in bytes, with the metadata of the given pointer.
|
||||
fn size(p: *const Self) -> usize;
|
||||
}
|
||||
|
||||
impl<T> KnownSize for T {
|
||||
const MIN_SIZE: usize = size_of::<T>();
|
||||
const MIN_ALIGN: Alignment = Alignment::of::<T>();
|
||||
|
||||
#[inline(always)]
|
||||
fn size(_: *const Self) -> usize {
|
||||
size_of::<T>()
|
||||
@@ -247,6 +256,9 @@ impl<T> KnownSize for T {
|
||||
}
|
||||
|
||||
impl<T> KnownSize for [T] {
|
||||
const MIN_SIZE: usize = 0;
|
||||
const MIN_ALIGN: Alignment = Alignment::of::<T>();
|
||||
|
||||
#[inline(always)]
|
||||
fn size(p: *const Self) -> usize {
|
||||
p.len() * size_of::<T>()
|
||||
|
||||
@@ -12,6 +12,11 @@ use kernel::{
|
||||
Device,
|
||||
DmaMask, //
|
||||
},
|
||||
io::{
|
||||
io_project,
|
||||
io_read,
|
||||
Io, //
|
||||
},
|
||||
page, pci,
|
||||
prelude::*,
|
||||
scatterlist::{Owned, SGTable},
|
||||
@@ -34,6 +39,7 @@ const TEST_VALUES: [(u32, u32); 5] = [
|
||||
(0xcd, 0xef),
|
||||
];
|
||||
|
||||
#[derive(FromBytes, IntoBytes)]
|
||||
struct MyStruct {
|
||||
h: u32,
|
||||
b: u32,
|
||||
@@ -77,7 +83,7 @@ impl pci::Driver for DmaSampleDriver {
|
||||
Coherent::zeroed_slice(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?;
|
||||
|
||||
for (i, value) in TEST_VALUES.into_iter().enumerate() {
|
||||
kernel::dma_write!(ca, [try: i], MyStruct::new(value.0, value.1));
|
||||
io_project!(ca, [panic: i]).copy_write(MyStruct::new(value.0, value.1));
|
||||
}
|
||||
|
||||
let size = 4 * page::PAGE_SIZE;
|
||||
@@ -97,8 +103,8 @@ impl pci::Driver for DmaSampleDriver {
|
||||
impl DmaSampleDriver {
|
||||
fn check_dma(&self) {
|
||||
for (i, value) in TEST_VALUES.into_iter().enumerate() {
|
||||
let val0 = kernel::dma_read!(self.ca, [panic: i].h);
|
||||
let val1 = kernel::dma_read!(self.ca, [panic: i].b);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user