Skip to main content

kernel/io/
mem.rs

1// SPDX-License-Identifier: GPL-2.0
2
3//! Generic memory-mapped IO.
4
5use crate::{
6    device::{
7        Bound,
8        Device, //
9    },
10    devres::DevresLt,
11    io::{
12        self,
13        resource::{
14            Region,
15            Resource, //
16        },
17        IoBase,
18        Mmio,
19        MmioBackend,
20        MmioRaw, //
21    },
22    prelude::*,
23    types::{
24        CovariantForLt,
25        ForLt, //
26    },
27};
28
29/// An IO request for a specific device and resource.
30pub struct IoRequest<'a> {
31    device: &'a Device<Bound>,
32    resource: &'a Resource,
33}
34
35impl<'a> IoRequest<'a> {
36    /// Creates a new [`IoRequest`] instance.
37    ///
38    /// # Safety
39    ///
40    /// Callers must ensure that `resource` is valid for `device` during the
41    /// lifetime `'a`.
42    pub(crate) unsafe fn new(device: &'a Device<Bound>, resource: &'a Resource) -> Self {
43        IoRequest { device, resource }
44    }
45
46    /// Maps an [`IoRequest`] where the size is known at compile time.
47    ///
48    /// This uses the [`ioremap()`] C API.
49    ///
50    /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
51    ///
52    /// # Examples
53    ///
54    /// The following example uses a [`kernel::platform::Device`] for
55    /// illustration purposes.
56    ///
57    /// ```no_run
58    /// use kernel::{
59    ///     bindings,
60    ///     device::Core,
61    ///     io::Io,
62    ///     of,
63    ///     platform,
64    /// };
65    /// struct SampleDriver;
66    ///
67    /// impl platform::Driver for SampleDriver {
68    ///    # type IdInfo = ();
69    ///    # type Data<'bound> = Self;
70    ///
71    ///    fn probe<'bound>(
72    ///       pdev: &'bound platform::Device<Core<'_>>,
73    ///       info: Option<&'bound Self::IdInfo>,
74    ///    ) -> impl PinInit<Self, Error> + 'bound {
75    ///       let offset = 0; // Some offset.
76    ///
77    ///       // If the size is known at compile time, use [`Self::iomap_sized`].
78    ///       //
79    ///       // No runtime checks will apply when reading and writing.
80    ///       let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
81    ///       let iomem = request.iomap_sized::<42>()?;
82    ///
83    ///       // Read and write a 32-bit value at `offset`.
84    ///       let data = iomem.read32(offset);
85    ///
86    ///       iomem.write32(data, offset);
87    ///
88    ///       # Ok(SampleDriver)
89    ///     }
90    /// }
91    /// ```
92    pub fn iomap_sized<const SIZE: usize>(self) -> Result<IoMem<'a, SIZE>> {
93        IoMem::ioremap(self.device, self.resource)
94    }
95
96    /// Same as [`Self::iomap_sized`] but with exclusive access to the
97    /// underlying region.
98    ///
99    /// This uses the [`ioremap()`] C API.
100    ///
101    /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
102    pub fn iomap_exclusive_sized<const SIZE: usize>(self) -> Result<ExclusiveIoMem<'a, SIZE>> {
103        ExclusiveIoMem::ioremap(self.device, self.resource)
104    }
105
106    /// Maps an [`IoRequest`] where the size is not known at compile time,
107    ///
108    /// This uses the [`ioremap()`] C API.
109    ///
110    /// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
111    ///
112    /// # Examples
113    ///
114    /// The following example uses a [`kernel::platform::Device`] for
115    /// illustration purposes.
116    ///
117    /// ```no_run
118    /// use kernel::{
119    ///     bindings,
120    ///     device::Core,
121    ///     io::Io,
122    ///     of,
123    ///     platform,
124    /// };
125    /// struct SampleDriver;
126    ///
127    /// impl platform::Driver for SampleDriver {
128    ///    # type IdInfo = ();
129    ///    # type Data<'bound> = Self;
130    ///
131    ///    fn probe<'bound>(
132    ///       pdev: &'bound platform::Device<Core<'_>>,
133    ///       info: Option<&'bound Self::IdInfo>,
134    ///    ) -> impl PinInit<Self, Error> + 'bound {
135    ///       let offset = 0; // Some offset.
136    ///
137    ///       // Unlike [`Self::iomap_sized`], here the size of the memory region
138    ///       // is not known at compile time, so only the `try_read*` and `try_write*`
139    ///       // family of functions should be used, leading to runtime checks on every
140    ///       // access.
141    ///       let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
142    ///       let iomem = request.iomap()?;
143    ///
144    ///       let data = iomem.try_read32(offset)?;
145    ///
146    ///       iomem.try_write32(data, offset)?;
147    ///
148    ///       # Ok(SampleDriver)
149    ///     }
150    /// }
151    /// ```
152    pub fn iomap(self) -> Result<IoMem<'a>> {
153        self.iomap_sized::<0>()
154    }
155
156    /// Same as [`Self::iomap`] but with exclusive access to the underlying
157    /// region.
158    pub fn iomap_exclusive(self) -> Result<ExclusiveIoMem<'a, 0>> {
159        self.iomap_exclusive_sized::<0>()
160    }
161}
162
163/// An exclusive memory-mapped IO region.
164///
165/// # Invariants
166///
167/// - [`ExclusiveIoMem`] has exclusive access to the underlying [`IoMem`].
168pub struct ExclusiveIoMem<'a, const SIZE: usize> {
169    /// The underlying `IoMem` instance.
170    iomem: IoMem<'a, SIZE>,
171
172    /// The region abstraction. This represents exclusive access to the
173    /// range represented by the underlying `iomem`.
174    ///
175    /// This field is needed for ownership of the region.
176    _region: Region,
177}
178
179impl<const SIZE: usize> ForLt for ExclusiveIoMem<'static, SIZE> {
180    type Of<'a> = ExclusiveIoMem<'a, SIZE>;
181}
182
183// SAFETY: `ExclusiveIoMem<'a, SIZE>` is covariant over `'a`; it holds an `IoMem<'a, SIZE>`,
184// which holds `&'a Device<Bound>`, which is covariant.
185unsafe impl<const SIZE: usize> CovariantForLt for ExclusiveIoMem<'static, SIZE> {}
186
187/// A device-managed exclusive I/O memory region.
188///
189/// See [`ExclusiveIoMem::into_devres`].
190pub type DevresExclusiveIoMem<const SIZE: usize> = DevresLt<ExclusiveIoMem<'static, SIZE>>;
191
192impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> {
193    /// Creates a new `ExclusiveIoMem` instance.
194    fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> {
195        let start = resource.start();
196        let size = resource.size();
197        let name = resource.name().unwrap_or_default();
198
199        let region = resource
200            .request_region(
201                start,
202                size,
203                name.to_cstring()?,
204                io::resource::Flags::IORESOURCE_MEM,
205            )
206            .ok_or(EBUSY)?;
207
208        let iomem = IoMem::ioremap(dev, resource)?;
209
210        Ok(ExclusiveIoMem {
211            iomem,
212            _region: region,
213        })
214    }
215
216    /// Consume the `ExclusiveIoMem` and register it as a device-managed resource.
217    ///
218    /// The returned [`DevresExclusiveIoMem`] can outlive the original borrow and be stored in
219    /// driver data. Access to the I/O memory is revoked automatically when the device is unbound.
220    pub fn into_devres(self) -> Result<DevresExclusiveIoMem<SIZE>> {
221        let dev = self.iomem.dev;
222        // SAFETY: `ExclusiveIoMem` only holds a device reference and an I/O mapping, both of
223        // which remain valid for the device's full bound scope, not just for `'a`.
224        unsafe { DevresLt::new(dev, self) }
225    }
226}
227
228impl<'a, const SIZE: usize> IoBase<'a> for &'a ExclusiveIoMem<'_, SIZE> {
229    type Backend = MmioBackend;
230    type Target = super::Region<SIZE>;
231
232    #[inline]
233    fn as_view(self) -> Mmio<'a, Self::Target> {
234        self.iomem.as_view()
235    }
236}
237
238/// A generic memory-mapped IO region.
239///
240/// Accesses to the underlying region is checked either at compile time, if the
241/// region's size is known at that point, or at runtime otherwise.
242///
243/// # Invariants
244///
245/// [`IoMem`] always holds an [`MmioRaw`] instance that holds a valid pointer to the
246/// start of the I/O memory mapped region.
247pub struct IoMem<'a, const SIZE: usize = 0> {
248    dev: &'a Device<Bound>,
249    io: MmioRaw<super::Region<SIZE>>,
250}
251
252impl<const SIZE: usize> ForLt for IoMem<'static, SIZE> {
253    type Of<'a> = IoMem<'a, SIZE>;
254}
255
256// SAFETY: `IoMem<'a, SIZE>` is covariant over `'a`; it holds `&'a Device<Bound>`,
257// which is covariant.
258unsafe impl<const SIZE: usize> CovariantForLt for IoMem<'static, SIZE> {}
259
260/// A device-managed I/O memory region.
261///
262/// See [`IoMem::into_devres`].
263pub type DevresIoMem<const SIZE: usize = 0> = DevresLt<IoMem<'static, SIZE>>;
264
265impl<'a, const SIZE: usize> IoMem<'a, SIZE> {
266    fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> {
267        // Note: Some ioremap() implementations use types that depend on the CPU
268        // word width rather than the bus address width.
269        //
270        // TODO: Properly address this in the C code to avoid this `try_into`.
271        let size = resource.size().try_into()?;
272        if size == 0 {
273            return Err(EINVAL);
274        }
275
276        let res_start = resource.start();
277
278        let addr = if resource
279            .flags()
280            .contains(io::resource::Flags::IORESOURCE_MEM_NONPOSTED)
281        {
282            // SAFETY:
283            // - `res_start` and `size` are read from a presumably valid `struct resource`.
284            // - `size` is known not to be zero at this point.
285            unsafe { bindings::ioremap_np(res_start, size) }
286        } else {
287            // SAFETY:
288            // - `res_start` and `size` are read from a presumably valid `struct resource`.
289            // - `size` is known not to be zero at this point.
290            unsafe { bindings::ioremap(res_start, size) }
291        };
292
293        if addr.is_null() {
294            return Err(ENOMEM);
295        }
296
297        let io = MmioRaw::new_region(addr as usize, size)?;
298        Ok(IoMem { dev, io })
299    }
300
301    /// Consume the `IoMem` and register it as a device-managed resource.
302    ///
303    /// The returned [`DevresIoMem`] can outlive the original borrow and be stored in driver data.
304    /// Access to the I/O memory is revoked automatically when the device is unbound.
305    pub fn into_devres(self) -> Result<DevresIoMem<SIZE>> {
306        let dev = self.dev;
307        // SAFETY: `IoMem` only holds a device reference and an I/O mapping, both of which
308        // remain valid for the device's full bound scope, not just for `'a`.
309        unsafe { DevresLt::new(dev, self) }
310    }
311}
312
313impl<const SIZE: usize> Drop for IoMem<'_, SIZE> {
314    fn drop(&mut self) {
315        // SAFETY: Safe as by the invariant of `Io`.
316        unsafe { bindings::iounmap(self.io.addr() as *mut c_void) }
317    }
318}
319
320impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<'_, SIZE> {
321    type Backend = MmioBackend;
322    type Target = super::Region<SIZE>;
323
324    #[inline]
325    fn as_view(self) -> Mmio<'a, Self::Target> {
326        // SAFETY: Safe as by the invariant of `IoMem`.
327        unsafe { Mmio::from_raw(self.io) }
328    }
329}