|
5 | 5 | //! C header: [`include/linux/pci.h`](srctree/include/linux/pci.h)
|
6 | 6 |
|
7 | 7 | use crate::{
|
| 8 | + alloc::flags::*, |
8 | 9 | bindings, container_of, device,
|
9 | 10 | device_id::RawDeviceId,
|
| 11 | + devres::Devres, |
10 | 12 | driver,
|
11 | 13 | error::{to_result, Result},
|
| 14 | + io::Io, |
12 | 15 | str::CStr,
|
13 | 16 | types::{ARef, ForeignOwnable},
|
14 | 17 | ThisModule,
|
15 | 18 | };
|
| 19 | +use core::ops::Deref; |
16 | 20 | use kernel::prelude::*;
|
17 | 21 |
|
18 | 22 | /// An adapter for the registration of PCI drivers.
|
@@ -220,9 +224,116 @@ pub trait Driver {
|
220 | 224 | ///
|
221 | 225 | /// A PCI device is based on an always reference counted `device:Device` instance. Cloning a PCI
|
222 | 226 | /// device, hence, also increments the base device' reference count.
|
| 227 | +/// |
| 228 | +/// # Invariants |
| 229 | +/// |
| 230 | +/// `Device` hold a valid reference of `ARef<device::Device>` whose underlying `struct device` is a |
| 231 | +/// member of a `struct pci_dev`. |
223 | 232 | #[derive(Clone)]
|
224 | 233 | pub struct Device(ARef<device::Device>);
|
225 | 234 |
|
| 235 | +/// A PCI BAR to perform I/O-Operations on. |
| 236 | +/// |
| 237 | +/// # Invariants |
| 238 | +/// |
| 239 | +/// `Bar` always holds an `Io` inststance that holds a valid pointer to the start of the I/O memory |
| 240 | +/// mapped PCI bar and its size. |
| 241 | +pub struct Bar<const SIZE: usize = 0> { |
| 242 | + pdev: Device, |
| 243 | + io: Io<SIZE>, |
| 244 | + num: i32, |
| 245 | +} |
| 246 | + |
| 247 | +impl<const SIZE: usize> Bar<SIZE> { |
| 248 | + fn new(pdev: Device, num: u32, name: &CStr) -> Result<Self> { |
| 249 | + let len = pdev.resource_len(num)?; |
| 250 | + if len == 0 { |
| 251 | + return Err(ENOMEM); |
| 252 | + } |
| 253 | + |
| 254 | + // Convert to `i32`, since that's what all the C bindings use. |
| 255 | + let num = i32::try_from(num)?; |
| 256 | + |
| 257 | + // SAFETY: |
| 258 | + // `pdev` is valid by the invariants of `Device`. |
| 259 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 260 | + // `name` is always valid. |
| 261 | + let ret = unsafe { bindings::pci_request_region(pdev.as_raw(), num, name.as_char_ptr()) }; |
| 262 | + if ret != 0 { |
| 263 | + return Err(EBUSY); |
| 264 | + } |
| 265 | + |
| 266 | + // SAFETY: |
| 267 | + // `pdev` is valid by the invariants of `Device`. |
| 268 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 269 | + // `name` is always valid. |
| 270 | + let ioptr: usize = unsafe { bindings::pci_iomap(pdev.as_raw(), num, 0) } as usize; |
| 271 | + if ioptr == 0 { |
| 272 | + // SAFETY: |
| 273 | + // `pdev` valid by the invariants of `Device`. |
| 274 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 275 | + unsafe { bindings::pci_release_region(pdev.as_raw(), num) }; |
| 276 | + return Err(ENOMEM); |
| 277 | + } |
| 278 | + |
| 279 | + // SAFETY: `ioptr` is guaranteed to be the start of a valid I/O mapped memory region of size |
| 280 | + // `len`. |
| 281 | + let io = match unsafe { Io::new(ioptr, len as usize) } { |
| 282 | + Ok(io) => io, |
| 283 | + Err(err) => { |
| 284 | + // SAFETY: |
| 285 | + // `pdev` is valid by the invariants of `Device`. |
| 286 | + // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region. |
| 287 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 288 | + unsafe { Self::do_release(&pdev, ioptr, num) }; |
| 289 | + return Err(err); |
| 290 | + } |
| 291 | + }; |
| 292 | + |
| 293 | + Ok(Bar { pdev, io, num }) |
| 294 | + } |
| 295 | + |
| 296 | + /// # Safety |
| 297 | + /// |
| 298 | + /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`. |
| 299 | + unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) { |
| 300 | + // SAFETY: |
| 301 | + // `pdev` is valid by the invariants of `Device`. |
| 302 | + // `ioptr` is valid by the safety requirements. |
| 303 | + // `num` is valid by the safety requirements. |
| 304 | + unsafe { |
| 305 | + bindings::pci_iounmap(pdev.as_raw(), ioptr as _); |
| 306 | + bindings::pci_release_region(pdev.as_raw(), num); |
| 307 | + } |
| 308 | + } |
| 309 | + |
| 310 | + fn release(&self) { |
| 311 | + // SAFETY: Safe by the invariants of `Device` and `Bar`. |
| 312 | + unsafe { Self::do_release(&self.pdev, self.io.base_addr(), self.num) }; |
| 313 | + } |
| 314 | +} |
| 315 | + |
| 316 | +impl Bar { |
| 317 | + fn index_is_valid(index: u32) -> bool { |
| 318 | + // A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries. |
| 319 | + index < bindings::PCI_NUM_RESOURCES |
| 320 | + } |
| 321 | +} |
| 322 | + |
| 323 | +impl<const SIZE: usize> Drop for Bar<SIZE> { |
| 324 | + fn drop(&mut self) { |
| 325 | + self.release(); |
| 326 | + } |
| 327 | +} |
| 328 | + |
| 329 | +impl<const SIZE: usize> Deref for Bar<SIZE> { |
| 330 | + type Target = Io<SIZE>; |
| 331 | + |
| 332 | + fn deref(&self) -> &Self::Target { |
| 333 | + &self.io |
| 334 | + } |
| 335 | +} |
| 336 | + |
226 | 337 | impl Device {
|
227 | 338 | /// Create a PCI Device instance from an existing `device::Device`.
|
228 | 339 | ///
|
@@ -256,6 +367,39 @@ impl Device {
|
256 | 367 | // SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid `struct pci_dev`.
|
257 | 368 | unsafe { bindings::pci_set_master(self.as_raw()) };
|
258 | 369 | }
|
| 370 | + |
| 371 | + /// Returns the size of the given PCI bar resource. |
| 372 | + pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> { |
| 373 | + if !Bar::index_is_valid(bar) { |
| 374 | + return Err(EINVAL); |
| 375 | + } |
| 376 | + |
| 377 | + // SAFETY: Safe by the type invariant. |
| 378 | + Ok(unsafe { bindings::pci_resource_len(self.as_raw(), bar.try_into()?) }) |
| 379 | + } |
| 380 | + |
| 381 | + /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks |
| 382 | + /// can be performed on compile time for offsets (plus the requested type size) < SIZE. |
| 383 | + pub fn iomap_region_sized<const SIZE: usize>( |
| 384 | + &self, |
| 385 | + bar: u32, |
| 386 | + name: &CStr, |
| 387 | + ) -> Result<Devres<Bar<SIZE>>> { |
| 388 | + let bar = Bar::<SIZE>::new(self.clone(), bar, name)?; |
| 389 | + let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?; |
| 390 | + |
| 391 | + Ok(devres) |
| 392 | + } |
| 393 | + |
| 394 | + /// Mapps an entire PCI-BAR after performing a region-request on it. |
| 395 | + pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<Bar>> { |
| 396 | + self.iomap_region_sized::<0>(bar, name) |
| 397 | + } |
| 398 | + |
| 399 | + /// Returns a new `ARef` of the base `device::Device`. |
| 400 | + pub fn as_dev(&self) -> ARef<device::Device> { |
| 401 | + self.0.clone() |
| 402 | + } |
259 | 403 | }
|
260 | 404 |
|
261 | 405 | impl AsRef<device::Device> for Device {
|
|
0 commit comments