|
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, |
| 15 | + io::IoRaw, |
12 | 16 | str::CStr,
|
13 | 17 | types::{ARef, ForeignOwnable, Opaque},
|
14 | 18 | ThisModule,
|
@@ -241,9 +245,115 @@ pub trait Driver {
|
241 | 245 | ///
|
242 | 246 | /// A PCI device is based on an always reference counted `device:Device` instance. Cloning a PCI
|
243 | 247 | /// device, hence, also increments the base device' reference count.
|
| 248 | +/// |
| 249 | +/// # Invariants |
| 250 | +/// |
| 251 | +/// `Device` hold a valid reference of `ARef<device::Device>` whose underlying `struct device` is a |
| 252 | +/// member of a `struct pci_dev`. |
244 | 253 | #[derive(Clone)]
|
245 | 254 | pub struct Device(ARef<device::Device>);
|
246 | 255 |
|
| 256 | +/// A PCI BAR to perform I/O-Operations on. |
| 257 | +/// |
| 258 | +/// # Invariants |
| 259 | +/// |
| 260 | +/// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O |
| 261 | +/// memory mapped PCI bar and its size. |
| 262 | +pub struct Bar<const SIZE: usize = 0> { |
| 263 | + pdev: Device, |
| 264 | + io: IoRaw<SIZE>, |
| 265 | + num: i32, |
| 266 | +} |
| 267 | + |
| 268 | +impl<const SIZE: usize> Bar<SIZE> { |
| 269 | + fn new(pdev: Device, num: u32, name: &CStr) -> Result<Self> { |
| 270 | + let len = pdev.resource_len(num)?; |
| 271 | + if len == 0 { |
| 272 | + return Err(ENOMEM); |
| 273 | + } |
| 274 | + |
| 275 | + // Convert to `i32`, since that's what all the C bindings use. |
| 276 | + let num = i32::try_from(num)?; |
| 277 | + |
| 278 | + // SAFETY: |
| 279 | + // `pdev` is valid by the invariants of `Device`. |
| 280 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 281 | + // `name` is always valid. |
| 282 | + let ret = unsafe { bindings::pci_request_region(pdev.as_raw(), num, name.as_char_ptr()) }; |
| 283 | + if ret != 0 { |
| 284 | + return Err(EBUSY); |
| 285 | + } |
| 286 | + |
| 287 | + // SAFETY: |
| 288 | + // `pdev` is valid by the invariants of `Device`. |
| 289 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 290 | + // `name` is always valid. |
| 291 | + let ioptr: usize = unsafe { bindings::pci_iomap(pdev.as_raw(), num, 0) } as usize; |
| 292 | + if ioptr == 0 { |
| 293 | + // SAFETY: |
| 294 | + // `pdev` valid by the invariants of `Device`. |
| 295 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 296 | + unsafe { bindings::pci_release_region(pdev.as_raw(), num) }; |
| 297 | + return Err(ENOMEM); |
| 298 | + } |
| 299 | + |
| 300 | + let io = match IoRaw::new(ioptr, len as usize) { |
| 301 | + Ok(io) => io, |
| 302 | + Err(err) => { |
| 303 | + // SAFETY: |
| 304 | + // `pdev` is valid by the invariants of `Device`. |
| 305 | + // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region. |
| 306 | + // `num` is checked for validity by a previous call to `Device::resource_len`. |
| 307 | + unsafe { Self::do_release(&pdev, ioptr, num) }; |
| 308 | + return Err(err); |
| 309 | + } |
| 310 | + }; |
| 311 | + |
| 312 | + Ok(Bar { pdev, io, num }) |
| 313 | + } |
| 314 | + |
| 315 | + /// # Safety |
| 316 | + /// |
| 317 | + /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`. |
| 318 | + unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) { |
| 319 | + // SAFETY: |
| 320 | + // `pdev` is valid by the invariants of `Device`. |
| 321 | + // `ioptr` is valid by the safety requirements. |
| 322 | + // `num` is valid by the safety requirements. |
| 323 | + unsafe { |
| 324 | + bindings::pci_iounmap(pdev.as_raw(), ioptr as _); |
| 325 | + bindings::pci_release_region(pdev.as_raw(), num); |
| 326 | + } |
| 327 | + } |
| 328 | + |
| 329 | + fn release(&self) { |
| 330 | + // SAFETY: The safety requirements are guaranteed by the type invariant of `self.pdev`. |
| 331 | + unsafe { Self::do_release(&self.pdev, self.io.addr(), self.num) }; |
| 332 | + } |
| 333 | +} |
| 334 | + |
| 335 | +impl Bar { |
| 336 | + fn index_is_valid(index: u32) -> bool { |
| 337 | + // A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries. |
| 338 | + index < bindings::PCI_NUM_RESOURCES |
| 339 | + } |
| 340 | +} |
| 341 | + |
| 342 | +impl<const SIZE: usize> Drop for Bar<SIZE> { |
| 343 | + fn drop(&mut self) { |
| 344 | + self.release(); |
| 345 | + } |
| 346 | +} |
| 347 | + |
| 348 | +impl<const SIZE: usize> Deref for Bar<SIZE> { |
| 349 | + type Target = Io<SIZE>; |
| 350 | + |
| 351 | + fn deref(&self) -> &Self::Target { |
| 352 | + // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. |
| 353 | + unsafe { Io::from_raw(&self.io) } |
| 354 | + } |
| 355 | +} |
| 356 | + |
247 | 357 | impl Device {
|
248 | 358 | /// Create a PCI Device instance from an existing `device::Device`.
|
249 | 359 | ///
|
@@ -289,6 +399,36 @@ impl Device {
|
289 | 399 | // SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid `struct pci_dev`.
|
290 | 400 | unsafe { bindings::pci_set_master(self.as_raw()) };
|
291 | 401 | }
|
| 402 | + |
| 403 | + /// Returns the size of the given PCI bar resource. |
| 404 | + pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> { |
| 405 | + if !Bar::index_is_valid(bar) { |
| 406 | + return Err(EINVAL); |
| 407 | + } |
| 408 | + |
| 409 | + // SAFETY: |
| 410 | + // - `bar` is a valid bar number, as guaranteed by the above call to `Bar::index_is_valid`, |
| 411 | + // - by its type invariant `self.as_raw` is always a valid pointer to a `struct pci_dev`. |
| 412 | + Ok(unsafe { bindings::pci_resource_len(self.as_raw(), bar.try_into()?) }) |
| 413 | + } |
| 414 | + |
| 415 | + /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks |
| 416 | + /// can be performed on compile time for offsets (plus the requested type size) < SIZE. |
| 417 | + pub fn iomap_region_sized<const SIZE: usize>( |
| 418 | + &self, |
| 419 | + bar: u32, |
| 420 | + name: &CStr, |
| 421 | + ) -> Result<Devres<Bar<SIZE>>> { |
| 422 | + let bar = Bar::<SIZE>::new(self.clone(), bar, name)?; |
| 423 | + let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?; |
| 424 | + |
| 425 | + Ok(devres) |
| 426 | + } |
| 427 | + |
| 428 | + /// Mapps an entire PCI-BAR after performing a region-request on it. |
| 429 | + pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<Bar>> { |
| 430 | + self.iomap_region_sized::<0>(bar, name) |
| 431 | + } |
292 | 432 | }
|
293 | 433 |
|
294 | 434 | impl AsRef<device::Device> for Device {
|
|
0 commit comments