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}