| // SPDX-License-Identifier: GPL-2.0 | |
| //! Generic memory-mapped IO. | |
| use crate::{ | |
| device::{ | |
| Bound, | |
| Device, // | |
| }, | |
| devres::DevresLt, | |
| io::{ | |
| self, | |
| resource::{ | |
| Region, | |
| Resource, // | |
| }, | |
| IoBase, | |
| Mmio, | |
| MmioBackend, | |
| MmioRaw, // | |
| }, | |
| prelude::*, | |
| types::{ | |
| CovariantForLt, | |
| ForLt, // | |
| }, | |
| }; | |
| /// An IO request for a specific device and resource. | |
| pub struct IoRequest<'a> { | |
| device: &'a Device<Bound>, | |
| resource: &'a Resource, | |
| } | |
| impl<'a> IoRequest<'a> { | |
| /// Creates a new [`IoRequest`] instance. | |
| /// | |
| /// # Safety | |
| /// | |
| /// Callers must ensure that `resource` is valid for `device` during the | |
| /// lifetime `'a`. | |
| pub(crate) unsafe fn new(device: &'a Device<Bound>, resource: &'a Resource) -> Self { | |
| IoRequest { device, resource } | |
| } | |
| /// Maps an [`IoRequest`] where the size is known at compile time. | |
| /// | |
| /// This uses the [`ioremap()`] C API. | |
| /// | |
| /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device | |
| /// | |
| /// # Examples | |
| /// | |
| /// The following example uses a [`kernel::platform::Device`] for | |
| /// illustration purposes. | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// bindings, | |
| /// device::Core, | |
| /// io::Io, | |
| /// of, | |
| /// platform, | |
| /// }; | |
| /// struct SampleDriver; | |
| /// | |
| /// impl platform::Driver for SampleDriver { | |
| /// # type IdInfo = (); | |
| /// # type Data<'bound> = Self; | |
| /// | |
| /// fn probe<'bound>( | |
| /// pdev: &'bound platform::Device<Core<'_>>, | |
| /// info: Option<&'bound Self::IdInfo>, | |
| /// ) -> impl PinInit<Self, Error> + 'bound { | |
| /// let offset = 0; // Some offset. | |
| /// | |
| /// // If the size is known at compile time, use [`Self::iomap_sized`]. | |
| /// // | |
| /// // No runtime checks will apply when reading and writing. | |
| /// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; | |
| /// let iomem = request.iomap_sized::<42>()?; | |
| /// | |
| /// // Read and write a 32-bit value at `offset`. | |
| /// let data = iomem.read32(offset); | |
| /// | |
| /// iomem.write32(data, offset); | |
| /// | |
| /// # Ok(SampleDriver) | |
| /// } | |
| /// } | |
| /// ``` | |
| pub fn iomap_sized<const SIZE: usize>(self) -> Result<IoMem<'a, SIZE>> { | |
| IoMem::ioremap(self.device, self.resource) | |
| } | |
| /// Same as [`Self::iomap_sized`] but with exclusive access to the | |
| /// underlying region. | |
| /// | |
| /// This uses the [`ioremap()`] C API. | |
| /// | |
| /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device | |
| pub fn iomap_exclusive_sized<const SIZE: usize>(self) -> Result<ExclusiveIoMem<'a, SIZE>> { | |
| ExclusiveIoMem::ioremap(self.device, self.resource) | |
| } | |
| /// Maps an [`IoRequest`] where the size is not known at compile time, | |
| /// | |
| /// This uses the [`ioremap()`] C API. | |
| /// | |
| /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device | |
| /// | |
| /// # Examples | |
| /// | |
| /// The following example uses a [`kernel::platform::Device`] for | |
| /// illustration purposes. | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// bindings, | |
| /// device::Core, | |
| /// io::Io, | |
| /// of, | |
| /// platform, | |
| /// }; | |
| /// struct SampleDriver; | |
| /// | |
| /// impl platform::Driver for SampleDriver { | |
| /// # type IdInfo = (); | |
| /// # type Data<'bound> = Self; | |
| /// | |
| /// fn probe<'bound>( | |
| /// pdev: &'bound platform::Device<Core<'_>>, | |
| /// info: Option<&'bound Self::IdInfo>, | |
| /// ) -> impl PinInit<Self, Error> + 'bound { | |
| /// let offset = 0; // Some offset. | |
| /// | |
| /// // Unlike [`Self::iomap_sized`], here the size of the memory region | |
| /// // is not known at compile time, so only the `try_read*` and `try_write*` | |
| /// // family of functions should be used, leading to runtime checks on every | |
| /// // access. | |
| /// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; | |
| /// let iomem = request.iomap()?; | |
| /// | |
| /// let data = iomem.try_read32(offset)?; | |
| /// | |
| /// iomem.try_write32(data, offset)?; | |
| /// | |
| /// # Ok(SampleDriver) | |
| /// } | |
| /// } | |
| /// ``` | |
| pub fn iomap(self) -> Result<IoMem<'a>> { | |
| self.iomap_sized::<0>() | |
| } | |
| /// Same as [`Self::iomap`] but with exclusive access to the underlying | |
| /// region. | |
| pub fn iomap_exclusive(self) -> Result<ExclusiveIoMem<'a, 0>> { | |
| self.iomap_exclusive_sized::<0>() | |
| } | |
| } | |
| /// An exclusive memory-mapped IO region. | |
| /// | |
| /// # Invariants | |
| /// | |
| /// - [`ExclusiveIoMem`] has exclusive access to the underlying [`IoMem`]. | |
| pub struct ExclusiveIoMem<'a, const SIZE: usize> { | |
| /// The underlying `IoMem` instance. | |
| iomem: IoMem<'a, SIZE>, | |
| /// The region abstraction. This represents exclusive access to the | |
| /// range represented by the underlying `iomem`. | |
| /// | |
| /// This field is needed for ownership of the region. | |
| _region: Region, | |
| } | |
| impl<const SIZE: usize> ForLt for ExclusiveIoMem<'static, SIZE> { | |
| type Of<'a> = ExclusiveIoMem<'a, SIZE>; | |
| } | |
| // SAFETY: `ExclusiveIoMem<'a, SIZE>` is covariant over `'a`; it holds an `IoMem<'a, SIZE>`, | |
| // which holds `&'a Device<Bound>`, which is covariant. | |
| unsafe impl<const SIZE: usize> CovariantForLt for ExclusiveIoMem<'static, SIZE> {} | |
| /// A device-managed exclusive I/O memory region. | |
| /// | |
| /// See [`ExclusiveIoMem::into_devres`]. | |
| pub type DevresExclusiveIoMem<const SIZE: usize> = DevresLt<ExclusiveIoMem<'static, SIZE>>; | |
| impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { | |
| /// Creates a new `ExclusiveIoMem` instance. | |
| fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> { | |
| let start = resource.start(); | |
| let size = resource.size(); | |
| let name = resource.name().unwrap_or_default(); | |
| let region = resource | |
| .request_region( | |
| start, | |
| size, | |
| name.to_cstring()?, | |
| io::resource::Flags::IORESOURCE_MEM, | |
| ) | |
| .ok_or(EBUSY)?; | |
| let iomem = IoMem::ioremap(dev, resource)?; | |
| Ok(ExclusiveIoMem { | |
| iomem, | |
| _region: region, | |
| }) | |
| } | |
| /// Consume the `ExclusiveIoMem` and register it as a device-managed resource. | |
| /// | |
| /// The returned [`DevresExclusiveIoMem`] can outlive the original borrow and be stored in | |
| /// driver data. Access to the I/O memory is revoked automatically when the device is unbound. | |
| pub fn into_devres(self) -> Result<DevresExclusiveIoMem<SIZE>> { | |
| let dev = self.iomem.dev; | |
| // SAFETY: `ExclusiveIoMem` only holds a device reference and an I/O mapping, both of | |
| // which remain valid for the device's full bound scope, not just for `'a`. | |
| unsafe { DevresLt::new(dev, self) } | |
| } | |
| } | |
| impl<'a, const SIZE: usize> IoBase<'a> for &'a ExclusiveIoMem<'_, SIZE> { | |
| type Backend = MmioBackend; | |
| type Target = super::Region<SIZE>; | |
| #[inline] | |
| fn as_view(self) -> Mmio<'a, Self::Target> { | |
| self.iomem.as_view() | |
| } | |
| } | |
| /// A generic memory-mapped IO region. | |
| /// | |
| /// Accesses to the underlying region is checked either at compile time, if the | |
| /// region's size is known at that point, or at runtime otherwise. | |
| /// | |
| /// # Invariants | |
| /// | |
| /// [`IoMem`] always holds an [`MmioRaw`] instance that holds a valid pointer to the | |
| /// start of the I/O memory mapped region. | |
| pub struct IoMem<'a, const SIZE: usize = 0> { | |
| dev: &'a Device<Bound>, | |
| io: MmioRaw<super::Region<SIZE>>, | |
| } | |
| impl<const SIZE: usize> ForLt for IoMem<'static, SIZE> { | |
| type Of<'a> = IoMem<'a, SIZE>; | |
| } | |
| // SAFETY: `IoMem<'a, SIZE>` is covariant over `'a`; it holds `&'a Device<Bound>`, | |
| // which is covariant. | |
| unsafe impl<const SIZE: usize> CovariantForLt for IoMem<'static, SIZE> {} | |
| /// A device-managed I/O memory region. | |
| /// | |
| /// See [`IoMem::into_devres`]. | |
| pub type DevresIoMem<const SIZE: usize = 0> = DevresLt<IoMem<'static, SIZE>>; | |
| impl<'a, const SIZE: usize> IoMem<'a, SIZE> { | |
| fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> { | |
| // Note: Some ioremap() implementations use types that depend on the CPU | |
| // word width rather than the bus address width. | |
| // | |
| // TODO: Properly address this in the C code to avoid this `try_into`. | |
| let size = resource.size().try_into()?; | |
| if size == 0 { | |
| return Err(EINVAL); | |
| } | |
| let res_start = resource.start(); | |
| let addr = if resource | |
| .flags() | |
| .contains(io::resource::Flags::IORESOURCE_MEM_NONPOSTED) | |
| { | |
| // SAFETY: | |
| // - `res_start` and `size` are read from a presumably valid `struct resource`. | |
| // - `size` is known not to be zero at this point. | |
| unsafe { bindings::ioremap_np(res_start, size) } | |
| } else { | |
| // SAFETY: | |
| // - `res_start` and `size` are read from a presumably valid `struct resource`. | |
| // - `size` is known not to be zero at this point. | |
| unsafe { bindings::ioremap(res_start, size) } | |
| }; | |
| if addr.is_null() { | |
| return Err(ENOMEM); | |
| } | |
| let io = MmioRaw::new_region(addr as usize, size)?; | |
| Ok(IoMem { dev, io }) | |
| } | |
| /// Consume the `IoMem` and register it as a device-managed resource. | |
| /// | |
| /// The returned [`DevresIoMem`] can outlive the original borrow and be stored in driver data. | |
| /// Access to the I/O memory is revoked automatically when the device is unbound. | |
| pub fn into_devres(self) -> Result<DevresIoMem<SIZE>> { | |
| let dev = self.dev; | |
| // SAFETY: `IoMem` only holds a device reference and an I/O mapping, both of which | |
| // remain valid for the device's full bound scope, not just for `'a`. | |
| unsafe { DevresLt::new(dev, self) } | |
| } | |
| } | |
| impl<const SIZE: usize> Drop for IoMem<'_, SIZE> { | |
| fn drop(&mut self) { | |
| // SAFETY: Safe as by the invariant of `Io`. | |
| unsafe { bindings::iounmap(self.io.addr() as *mut c_void) } | |
| } | |
| } | |
| impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<'_, SIZE> { | |
| type Backend = MmioBackend; | |
| type Target = super::Region<SIZE>; | |
| #[inline] | |
| fn as_view(self) -> Mmio<'a, Self::Target> { | |
| // SAFETY: Safe as by the invariant of `IoMem`. | |
| unsafe { Mmio::from_raw(self.io) } | |
| } | |
| } |