mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	DRM Rust changes for v6.18
Alloc
   - Add BorrowedPage type and AsPageIter trait
   - Implement Vmalloc::to_page() and VmallocPageIter
   - Implement AsPageIter for VBox and VVec
 
 DMA & Scatterlist
   - Add dma::DataDirection and type alias for dma_addr_t
   - Abstraction for struct scatterlist and struct sg_table
 
 DRM
   - In the DRM GEM module, simplify overall use of generics, add
     DriverFile type alias and drop Object::SIZE.
 
 Nova (Core)
   - Various register!() macro improvements (paving the way for lifting
     it to common driver infrastructure)
   - Minor VBios fixes and refactoring
   - Minor firmware request refactoring
   - Advance firmware boot stages; process Booter and patch its
     signature, process GSP and GSP bootloader
   - Switch development fimrware version to r570.144
   - Add basic firmware bindings for r570.144
   - Move GSP boot code to its own module
   - Clean up and take advantage of pin-init features to store most of
     the driver's private data within a single allocation
   - Update ARef import from sync::aref
   - Add website to MAINTAINERS entry
 
 Nova (DRM)
   - Update ARef import from sync::aref
   - Add website to MAINTAINERS entry
 
 Pin-Init
   - Merge pin-init PR from Benno
     - `#[pin_data]` now generates a `*Projection` struct similar to the
       `pin-project` crate.
 
     - Add initializer code blocks to `[try_][pin_]init!` macros: make
       initializer macros accept any number of `_: {/* arbitrary code
       */},` & make them run the code at that point.
 
     - Make the `[try_][pin_]init!` macros expose initialized fields via
       a `let` binding as `&mut T` or `Pin<&mut T>` for later fields.
 
 Rust
   - Various methods for AsBytes and FromBytes traits
 
 Tyr
   - Initial Rust driver skeleton for ARM Mali GPUs.
     - It can power up the GPU, query for GPU metatdata through MMIO and
       provide the metadata to userspace via DRM device IOCTL (struct
       drm_panthor_dev_query).
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQS2q/xV6QjXAdC7k+1FlHeO1qrKLgUCaMlv1gAKCRBFlHeO1qrK
 Lu8uAQDTJvYuAvSh9MyeSWhOl6H+7u4CpRb3FeatQsApnn7mRQD9Ft1RJyB7keRm
 vDUsGZi4P9f5BDwXOLq6aRRzuxWIvQc=
 =fvs6
 -----END PGP SIGNATURE-----
Merge tag 'drm-rust-next-2025-09-16' of https://gitlab.freedesktop.org/drm/rust/kernel into drm-next
DRM Rust changes for v6.18
Alloc
  - Add BorrowedPage type and AsPageIter trait
  - Implement Vmalloc::to_page() and VmallocPageIter
  - Implement AsPageIter for VBox and VVec
DMA & Scatterlist
  - Add dma::DataDirection and type alias for dma_addr_t
  - Abstraction for struct scatterlist and struct sg_table
DRM
  - In the DRM GEM module, simplify overall use of generics, add
    DriverFile type alias and drop Object::SIZE.
Nova (Core)
  - Various register!() macro improvements (paving the way for lifting
    it to common driver infrastructure)
  - Minor VBios fixes and refactoring
  - Minor firmware request refactoring
  - Advance firmware boot stages; process Booter and patch its
    signature, process GSP and GSP bootloader
  - Switch development fimrware version to r570.144
  - Add basic firmware bindings for r570.144
  - Move GSP boot code to its own module
  - Clean up and take advantage of pin-init features to store most of
    the driver's private data within a single allocation
  - Update ARef import from sync::aref
  - Add website to MAINTAINERS entry
Nova (DRM)
  - Update ARef import from sync::aref
  - Add website to MAINTAINERS entry
Pin-Init
  - Merge pin-init PR from Benno
    - `#[pin_data]` now generates a `*Projection` struct similar to the
      `pin-project` crate.
    - Add initializer code blocks to `[try_][pin_]init!` macros: make
      initializer macros accept any number of `_: {/* arbitrary code
      */},` & make them run the code at that point.
    - Make the `[try_][pin_]init!` macros expose initialized fields via
      a `let` binding as `&mut T` or `Pin<&mut T>` for later fields.
Rust
  - Various methods for AsBytes and FromBytes traits
Tyr
  - Initial Rust driver skeleton for ARM Mali GPUs.
    - It can power up the GPU, query for GPU metatdata through MMIO and
      provide the metadata to userspace via DRM device IOCTL (struct
      drm_panthor_dev_query).
Signed-off-by: Dave Airlie <airlied@redhat.com>
From: "Danilo Krummrich" <dakr@kernel.org>
Link: https://lore.kernel.org/r/DCUC4SY6SRBD.1ZLHAIQZOC6KG@kernel.org
			
			
This commit is contained in:
		
						commit
						6f17ab9a63
					
				
					 62 changed files with 4022 additions and 687 deletions
				
			
		|  | @ -131,8 +131,6 @@ crate so it can be used by other components as well. | |||
| 
 | ||||
| Features desired before this happens: | ||||
| 
 | ||||
| * Relative register with build-time base address validation, | ||||
| * Arrays of registers with build-time index validation, | ||||
| * Make I/O optional I/O (for field values that are not registers), | ||||
| * Support other sizes than `u32`, | ||||
| * Allow visibility control for registers and individual fields, | ||||
|  | @ -232,23 +230,6 @@ Rust abstraction for debugfs APIs. | |||
| GPU (general) | ||||
| ============= | ||||
| 
 | ||||
| Parse firmware headers | ||||
| ---------------------- | ||||
| 
 | ||||
| Parse ELF headers from the firmware files loaded from the filesystem. | ||||
| 
 | ||||
| | Reference: ELF utils | ||||
| | Complexity: Beginner | ||||
| | Contact: Abdiel Janulgue | ||||
| 
 | ||||
| Build radix3 page table | ||||
| ----------------------- | ||||
| 
 | ||||
| Build the radix3 page table to map the firmware. | ||||
| 
 | ||||
| | Complexity: Intermediate | ||||
| | Contact: Abdiel Janulgue | ||||
| 
 | ||||
| Initial Devinit support | ||||
| ----------------------- | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								MAINTAINERS
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								MAINTAINERS
									
									
									
									
									
								
							|  | @ -2086,6 +2086,19 @@ F:	Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | |||
| F:	drivers/gpu/drm/panthor/ | ||||
| F:	include/uapi/drm/panthor_drm.h | ||||
| 
 | ||||
| ARM MALI TYR DRM DRIVER | ||||
| M:	Daniel Almeida <daniel.almeida@collabora.com> | ||||
| M:	Alice Ryhl <aliceryhl@google.com> | ||||
| L:	dri-devel@lists.freedesktop.org | ||||
| S:	Supported | ||||
| W:	https://rust-for-linux.com/tyr-gpu-driver | ||||
| W	https://drm.pages.freedesktop.org/maintainer-tools/drm-rust.html | ||||
| B:	https://gitlab.freedesktop.org/panfrost/linux/-/issues | ||||
| T:	git https://gitlab.freedesktop.org/drm/rust/kernel.git | ||||
| F:	Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | ||||
| F:	drivers/gpu/drm/tyr/ | ||||
| F:	include/uapi/drm/panthor_drm.h | ||||
| 
 | ||||
| ARM MALI-DP DRM DRIVER | ||||
| M:	Liviu Dudau <liviu.dudau@arm.com> | ||||
| S:	Supported | ||||
|  | @ -7237,7 +7250,7 @@ F:	include/linux/dma-mapping.h | |||
| F:	include/linux/swiotlb.h | ||||
| F:	kernel/dma/ | ||||
| 
 | ||||
| DMA MAPPING HELPERS DEVICE DRIVER API [RUST] | ||||
| DMA MAPPING & SCATTERLIST API [RUST] | ||||
| M:	Danilo Krummrich <dakr@kernel.org> | ||||
| R:	Abdiel Janulgue <abdiel.janulgue@gmail.com> | ||||
| R:	Daniel Almeida <daniel.almeida@collabora.com> | ||||
|  | @ -7248,7 +7261,9 @@ S:	Supported | |||
| W:	https://rust-for-linux.com | ||||
| T:	git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git | ||||
| F:	rust/helpers/dma.c | ||||
| F:	rust/helpers/scatterlist.c | ||||
| F:	rust/kernel/dma.rs | ||||
| F:	rust/kernel/scatterlist.rs | ||||
| F:	samples/rust/rust_dma.rs | ||||
| 
 | ||||
| DMA-BUF HEAPS FRAMEWORK | ||||
|  | @ -7838,6 +7853,7 @@ M:	Danilo Krummrich <dakr@kernel.org> | |||
| M:	Alexandre Courbot <acourbot@nvidia.com> | ||||
| L:	nouveau@lists.freedesktop.org | ||||
| S:	Supported | ||||
| W:	https://rust-for-linux.com/nova-gpu-driver | ||||
| Q:	https://patchwork.freedesktop.org/project/nouveau/ | ||||
| B:	https://gitlab.freedesktop.org/drm/nova/-/issues | ||||
| C:	irc://irc.oftc.net/nouveau | ||||
|  | @ -7849,6 +7865,7 @@ DRM DRIVER FOR NVIDIA GPUS [RUST] | |||
| M:	Danilo Krummrich <dakr@kernel.org> | ||||
| L:	nouveau@lists.freedesktop.org | ||||
| S:	Supported | ||||
| W:	https://rust-for-linux.com/nova-gpu-driver | ||||
| Q:	https://patchwork.freedesktop.org/project/nouveau/ | ||||
| B:	https://gitlab.freedesktop.org/drm/nova/-/issues | ||||
| C:	irc://irc.oftc.net/nouveau | ||||
|  |  | |||
|  | @ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig" | |||
| 
 | ||||
| source "drivers/gpu/drm/imagination/Kconfig" | ||||
| 
 | ||||
| source "drivers/gpu/drm/tyr/Kconfig" | ||||
| 
 | ||||
| config DRM_HYPERV | ||||
| 	tristate "DRM Support for Hyper-V synthetic video device" | ||||
| 	depends on DRM && PCI && HYPERV | ||||
|  |  | |||
|  | @ -220,6 +220,7 @@ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/ | |||
| obj-$(CONFIG_DRM_LIMA)  += lima/ | ||||
| obj-$(CONFIG_DRM_PANFROST) += panfrost/ | ||||
| obj-$(CONFIG_DRM_PANTHOR) += panthor/ | ||||
| obj-$(CONFIG_DRM_TYR) += tyr/ | ||||
| obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ | ||||
| obj-$(CONFIG_DRM_MCDE) += mcde/ | ||||
| obj-$(CONFIG_DRM_TIDSS) += tidss/ | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| use kernel::{auxiliary, c_str, device::Core, drm, drm::gem, drm::ioctl, prelude::*, types::ARef}; | ||||
| use kernel::{ | ||||
|     auxiliary, c_str, device::Core, drm, drm::gem, drm::ioctl, prelude::*, sync::aref::ARef, | ||||
| }; | ||||
| 
 | ||||
| use crate::file::File; | ||||
| use crate::gem::NovaObject; | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
|     drm, | ||||
|     drm::{gem, gem::BaseObject}, | ||||
|     prelude::*, | ||||
|     types::ARef, | ||||
|     sync::aref::ARef, | ||||
| }; | ||||
| 
 | ||||
| use crate::{ | ||||
|  | @ -16,16 +16,14 @@ | |||
| #[pin_data] | ||||
| pub(crate) struct NovaObject {} | ||||
| 
 | ||||
| impl gem::BaseDriverObject<gem::Object<NovaObject>> for NovaObject { | ||||
| impl gem::DriverObject for NovaObject { | ||||
|     type Driver = NovaDriver; | ||||
| 
 | ||||
|     fn new(_dev: &NovaDevice, _size: usize) -> impl PinInit<Self, Error> { | ||||
|         try_pin_init!(NovaObject {}) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl gem::DriverObject for NovaObject { | ||||
|     type Driver = NovaDriver; | ||||
| } | ||||
| 
 | ||||
| impl NovaObject { | ||||
|     /// Create a new DRM GEM object.
 | ||||
|     pub(crate) fn new(dev: &NovaDevice, size: usize) -> Result<ARef<gem::Object<Self>>> { | ||||
|  |  | |||
							
								
								
									
										19
									
								
								drivers/gpu/drm/tyr/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								drivers/gpu/drm/tyr/Kconfig
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| # SPDX-License-Identifier: GPL-2.0 or MIT | ||||
| 
 | ||||
| config DRM_TYR | ||||
| 	tristate "Tyr (Rust DRM support for ARM Mali CSF-based GPUs)" | ||||
| 	depends on DRM=y | ||||
| 	depends on RUST | ||||
| 	depends on ARM || ARM64 || COMPILE_TEST | ||||
| 	depends on !GENERIC_ATOMIC64  # for IOMMU_IO_PGTABLE_LPAE | ||||
| 	default n | ||||
| 	help | ||||
| 	  Rust DRM driver for ARM Mali CSF-based GPUs. | ||||
| 
 | ||||
| 	  This driver is for Mali (or Immortalis) Valhall Gxxx GPUs. | ||||
| 
 | ||||
| 	  Note that the Mali-G68 and Mali-G78, while Valhall architecture, will | ||||
| 	  be supported with the panfrost driver as they are not CSF GPUs. | ||||
| 
 | ||||
| 	  if M is selected, the module will be called tyr. This driver is work | ||||
| 	  in progress and may not be functional. | ||||
							
								
								
									
										3
									
								
								drivers/gpu/drm/tyr/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								drivers/gpu/drm/tyr/Makefile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| # SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| obj-$(CONFIG_DRM_TYR) += tyr.o | ||||
							
								
								
									
										205
									
								
								drivers/gpu/drm/tyr/driver.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								drivers/gpu/drm/tyr/driver.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,205 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| use kernel::c_str; | ||||
| use kernel::clk::Clk; | ||||
| use kernel::clk::OptionalClk; | ||||
| use kernel::device::Bound; | ||||
| use kernel::device::Core; | ||||
| use kernel::device::Device; | ||||
| use kernel::devres::Devres; | ||||
| use kernel::drm; | ||||
| use kernel::drm::ioctl; | ||||
| use kernel::new_mutex; | ||||
| use kernel::of; | ||||
| use kernel::platform; | ||||
| use kernel::prelude::*; | ||||
| use kernel::regulator; | ||||
| use kernel::regulator::Regulator; | ||||
| use kernel::sizes::SZ_2M; | ||||
| use kernel::sync::Arc; | ||||
| use kernel::sync::Mutex; | ||||
| use kernel::time; | ||||
| use kernel::types::ARef; | ||||
| 
 | ||||
| use crate::file::File; | ||||
| use crate::gem::TyrObject; | ||||
| use crate::gpu; | ||||
| use crate::gpu::GpuInfo; | ||||
| use crate::regs; | ||||
| 
 | ||||
| pub(crate) type IoMem = kernel::io::mem::IoMem<SZ_2M>; | ||||
| 
 | ||||
| /// Convenience type alias for the DRM device type for this driver.
 | ||||
| pub(crate) type TyrDevice = drm::Device<TyrDriver>; | ||||
| 
 | ||||
| #[pin_data(PinnedDrop)] | ||||
| pub(crate) struct TyrDriver { | ||||
|     device: ARef<TyrDevice>, | ||||
| } | ||||
| 
 | ||||
| #[pin_data(PinnedDrop)] | ||||
| pub(crate) struct TyrData { | ||||
|     pub(crate) pdev: ARef<platform::Device>, | ||||
| 
 | ||||
|     #[pin] | ||||
|     clks: Mutex<Clocks>, | ||||
| 
 | ||||
|     #[pin] | ||||
|     regulators: Mutex<Regulators>, | ||||
| 
 | ||||
|     /// Some information on the GPU.
 | ||||
|     ///
 | ||||
|     /// This is mainly queried by userspace, i.e.: Mesa.
 | ||||
|     pub(crate) gpu_info: GpuInfo, | ||||
| } | ||||
| 
 | ||||
| // Both `Clk` and `Regulator` do not implement `Send` or `Sync`, but they
 | ||||
| // should. There are patches on the mailing list to address this, but they have
 | ||||
| // not landed yet.
 | ||||
| //
 | ||||
| // For now, add this workaround so that this patch compiles with the promise
 | ||||
| // that it will be removed in a future patch.
 | ||||
| //
 | ||||
| // SAFETY: This will be removed in a future patch.
 | ||||
| unsafe impl Send for TyrData {} | ||||
| // SAFETY: This will be removed in a future patch.
 | ||||
| unsafe impl Sync for TyrData {} | ||||
| 
 | ||||
| fn issue_soft_reset(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result { | ||||
|     regs::GPU_CMD.write(dev, iomem, regs::GPU_CMD_SOFT_RESET)?; | ||||
| 
 | ||||
|     // TODO: We cannot poll, as there is no support in Rust currently, so we
 | ||||
|     // sleep. Change this when read_poll_timeout() is implemented in Rust.
 | ||||
|     kernel::time::delay::fsleep(time::Delta::from_millis(100)); | ||||
| 
 | ||||
|     if regs::GPU_IRQ_RAWSTAT.read(dev, iomem)? & regs::GPU_IRQ_RAWSTAT_RESET_COMPLETED == 0 { | ||||
|         dev_err!(dev, "GPU reset failed with errno\n"); | ||||
|         dev_err!( | ||||
|             dev, | ||||
|             "GPU_INT_RAWSTAT is {}\n", | ||||
|             regs::GPU_IRQ_RAWSTAT.read(dev, iomem)? | ||||
|         ); | ||||
| 
 | ||||
|         return Err(EIO); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| kernel::of_device_table!( | ||||
|     OF_TABLE, | ||||
|     MODULE_OF_TABLE, | ||||
|     <TyrDriver as platform::Driver>::IdInfo, | ||||
|     [ | ||||
|         (of::DeviceId::new(c_str!("rockchip,rk3588-mali")), ()), | ||||
|         (of::DeviceId::new(c_str!("arm,mali-valhall-csf")), ()) | ||||
|     ] | ||||
| ); | ||||
| 
 | ||||
| impl platform::Driver for TyrDriver { | ||||
|     type IdInfo = (); | ||||
|     const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); | ||||
| 
 | ||||
|     fn probe( | ||||
|         pdev: &platform::Device<Core>, | ||||
|         _info: Option<&Self::IdInfo>, | ||||
|     ) -> Result<Pin<KBox<Self>>> { | ||||
|         let core_clk = Clk::get(pdev.as_ref(), Some(c_str!("core")))?; | ||||
|         let stacks_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("stacks")))?; | ||||
|         let coregroup_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("coregroup")))?; | ||||
| 
 | ||||
|         core_clk.prepare_enable()?; | ||||
|         stacks_clk.prepare_enable()?; | ||||
|         coregroup_clk.prepare_enable()?; | ||||
| 
 | ||||
|         let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c_str!("mali"))?; | ||||
|         let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c_str!("sram"))?; | ||||
| 
 | ||||
|         let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; | ||||
|         let iomem = Arc::pin_init(request.iomap_sized::<SZ_2M>(), GFP_KERNEL)?; | ||||
| 
 | ||||
|         issue_soft_reset(pdev.as_ref(), &iomem)?; | ||||
|         gpu::l2_power_on(pdev.as_ref(), &iomem)?; | ||||
| 
 | ||||
|         let gpu_info = GpuInfo::new(pdev.as_ref(), &iomem)?; | ||||
|         gpu_info.log(pdev); | ||||
| 
 | ||||
|         let platform: ARef<platform::Device> = pdev.into(); | ||||
| 
 | ||||
|         let data = try_pin_init!(TyrData { | ||||
|                 pdev: platform.clone(), | ||||
|                 clks <- new_mutex!(Clocks { | ||||
|                     core: core_clk, | ||||
|                     stacks: stacks_clk, | ||||
|                     coregroup: coregroup_clk, | ||||
|                 }), | ||||
|                 regulators <- new_mutex!(Regulators { | ||||
|                     mali: mali_regulator, | ||||
|                     sram: sram_regulator, | ||||
|                 }), | ||||
|                 gpu_info, | ||||
|         }); | ||||
| 
 | ||||
|         let tdev: ARef<TyrDevice> = drm::Device::new(pdev.as_ref(), data)?; | ||||
|         drm::driver::Registration::new_foreign_owned(&tdev, pdev.as_ref(), 0)?; | ||||
| 
 | ||||
|         let driver = KBox::pin_init(try_pin_init!(TyrDriver { device: tdev }), GFP_KERNEL)?; | ||||
| 
 | ||||
|         // We need this to be dev_info!() because dev_dbg!() does not work at
 | ||||
|         // all in Rust for now, and we need to see whether probe succeeded.
 | ||||
|         dev_info!(pdev.as_ref(), "Tyr initialized correctly.\n"); | ||||
|         Ok(driver) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[pinned_drop] | ||||
| impl PinnedDrop for TyrDriver { | ||||
|     fn drop(self: Pin<&mut Self>) {} | ||||
| } | ||||
| 
 | ||||
| #[pinned_drop] | ||||
| impl PinnedDrop for TyrData { | ||||
|     fn drop(self: Pin<&mut Self>) { | ||||
|         // TODO: the type-state pattern for Clks will fix this.
 | ||||
|         let clks = self.clks.lock(); | ||||
|         clks.core.disable_unprepare(); | ||||
|         clks.stacks.disable_unprepare(); | ||||
|         clks.coregroup.disable_unprepare(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // We need to retain the name "panthor" to achieve drop-in compatibility with
 | ||||
| // the C driver in the userspace stack.
 | ||||
| const INFO: drm::DriverInfo = drm::DriverInfo { | ||||
|     major: 1, | ||||
|     minor: 5, | ||||
|     patchlevel: 0, | ||||
|     name: c_str!("panthor"), | ||||
|     desc: c_str!("ARM Mali Tyr DRM driver"), | ||||
| }; | ||||
| 
 | ||||
| #[vtable] | ||||
| impl drm::Driver for TyrDriver { | ||||
|     type Data = TyrData; | ||||
|     type File = File; | ||||
|     type Object = drm::gem::Object<TyrObject>; | ||||
| 
 | ||||
|     const INFO: drm::DriverInfo = INFO; | ||||
| 
 | ||||
|     kernel::declare_drm_ioctls! { | ||||
|         (PANTHOR_DEV_QUERY, drm_panthor_dev_query, ioctl::RENDER_ALLOW, File::dev_query), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[pin_data] | ||||
| struct Clocks { | ||||
|     core: Clk, | ||||
|     stacks: OptionalClk, | ||||
|     coregroup: OptionalClk, | ||||
| } | ||||
| 
 | ||||
| #[pin_data] | ||||
| struct Regulators { | ||||
|     mali: Regulator<regulator::Enabled>, | ||||
|     sram: Regulator<regulator::Enabled>, | ||||
| } | ||||
							
								
								
									
										56
									
								
								drivers/gpu/drm/tyr/file.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								drivers/gpu/drm/tyr/file.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| use kernel::drm; | ||||
| use kernel::prelude::*; | ||||
| use kernel::uaccess::UserSlice; | ||||
| use kernel::uapi; | ||||
| 
 | ||||
| use crate::driver::TyrDevice; | ||||
| use crate::TyrDriver; | ||||
| 
 | ||||
| #[pin_data] | ||||
| pub(crate) struct File {} | ||||
| 
 | ||||
| /// Convenience type alias for our DRM `File` type
 | ||||
| pub(crate) type DrmFile = drm::file::File<File>; | ||||
| 
 | ||||
| impl drm::file::DriverFile for File { | ||||
|     type Driver = TyrDriver; | ||||
| 
 | ||||
|     fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> { | ||||
|         KBox::try_pin_init(try_pin_init!(Self {}), GFP_KERNEL) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl File { | ||||
|     pub(crate) fn dev_query( | ||||
|         tdev: &TyrDevice, | ||||
|         devquery: &mut uapi::drm_panthor_dev_query, | ||||
|         _file: &DrmFile, | ||||
|     ) -> Result<u32> { | ||||
|         if devquery.pointer == 0 { | ||||
|             match devquery.type_ { | ||||
|                 uapi::drm_panthor_dev_query_type_DRM_PANTHOR_DEV_QUERY_GPU_INFO => { | ||||
|                     devquery.size = core::mem::size_of_val(&tdev.gpu_info) as u32; | ||||
|                     Ok(0) | ||||
|                 } | ||||
|                 _ => Err(EINVAL), | ||||
|             } | ||||
|         } else { | ||||
|             match devquery.type_ { | ||||
|                 uapi::drm_panthor_dev_query_type_DRM_PANTHOR_DEV_QUERY_GPU_INFO => { | ||||
|                     let mut writer = UserSlice::new( | ||||
|                         UserPtr::from_addr(devquery.pointer as usize), | ||||
|                         devquery.size as usize, | ||||
|                     ) | ||||
|                     .writer(); | ||||
| 
 | ||||
|                     writer.write(&tdev.gpu_info)?; | ||||
| 
 | ||||
|                     Ok(0) | ||||
|                 } | ||||
|                 _ => Err(EINVAL), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								drivers/gpu/drm/tyr/gem.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								drivers/gpu/drm/tyr/gem.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| use crate::driver::TyrDevice; | ||||
| use crate::driver::TyrDriver; | ||||
| use kernel::drm::gem; | ||||
| use kernel::prelude::*; | ||||
| 
 | ||||
| /// GEM Object inner driver data
 | ||||
| #[pin_data] | ||||
| pub(crate) struct TyrObject {} | ||||
| 
 | ||||
| impl gem::DriverObject for TyrObject { | ||||
|     type Driver = TyrDriver; | ||||
| 
 | ||||
|     fn new(_dev: &TyrDevice, _size: usize) -> impl PinInit<Self, Error> { | ||||
|         try_pin_init!(TyrObject {}) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										219
									
								
								drivers/gpu/drm/tyr/gpu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								drivers/gpu/drm/tyr/gpu.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,219 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| use kernel::bits::genmask_u32; | ||||
| use kernel::device::Bound; | ||||
| use kernel::device::Device; | ||||
| use kernel::devres::Devres; | ||||
| use kernel::platform; | ||||
| use kernel::prelude::*; | ||||
| use kernel::time; | ||||
| use kernel::transmute::AsBytes; | ||||
| 
 | ||||
| use crate::driver::IoMem; | ||||
| use crate::regs; | ||||
| 
 | ||||
| /// Struct containing information that can be queried by userspace. This is read from
 | ||||
| /// the GPU's registers.
 | ||||
| ///
 | ||||
| /// # Invariants
 | ||||
| ///
 | ||||
| /// - The layout of this struct identical to the C `struct drm_panthor_gpu_info`.
 | ||||
| #[repr(C)] | ||||
| pub(crate) struct GpuInfo { | ||||
|     pub(crate) gpu_id: u32, | ||||
|     pub(crate) gpu_rev: u32, | ||||
|     pub(crate) csf_id: u32, | ||||
|     pub(crate) l2_features: u32, | ||||
|     pub(crate) tiler_features: u32, | ||||
|     pub(crate) mem_features: u32, | ||||
|     pub(crate) mmu_features: u32, | ||||
|     pub(crate) thread_features: u32, | ||||
|     pub(crate) max_threads: u32, | ||||
|     pub(crate) thread_max_workgroup_size: u32, | ||||
|     pub(crate) thread_max_barrier_size: u32, | ||||
|     pub(crate) coherency_features: u32, | ||||
|     pub(crate) texture_features: [u32; 4], | ||||
|     pub(crate) as_present: u32, | ||||
|     pub(crate) pad0: u32, | ||||
|     pub(crate) shader_present: u64, | ||||
|     pub(crate) l2_present: u64, | ||||
|     pub(crate) tiler_present: u64, | ||||
|     pub(crate) core_features: u32, | ||||
|     pub(crate) pad: u32, | ||||
| } | ||||
| 
 | ||||
| impl GpuInfo { | ||||
|     pub(crate) fn new(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result<Self> { | ||||
|         let gpu_id = regs::GPU_ID.read(dev, iomem)?; | ||||
|         let csf_id = regs::GPU_CSF_ID.read(dev, iomem)?; | ||||
|         let gpu_rev = regs::GPU_REVID.read(dev, iomem)?; | ||||
|         let core_features = regs::GPU_CORE_FEATURES.read(dev, iomem)?; | ||||
|         let l2_features = regs::GPU_L2_FEATURES.read(dev, iomem)?; | ||||
|         let tiler_features = regs::GPU_TILER_FEATURES.read(dev, iomem)?; | ||||
|         let mem_features = regs::GPU_MEM_FEATURES.read(dev, iomem)?; | ||||
|         let mmu_features = regs::GPU_MMU_FEATURES.read(dev, iomem)?; | ||||
|         let thread_features = regs::GPU_THREAD_FEATURES.read(dev, iomem)?; | ||||
|         let max_threads = regs::GPU_THREAD_MAX_THREADS.read(dev, iomem)?; | ||||
|         let thread_max_workgroup_size = regs::GPU_THREAD_MAX_WORKGROUP_SIZE.read(dev, iomem)?; | ||||
|         let thread_max_barrier_size = regs::GPU_THREAD_MAX_BARRIER_SIZE.read(dev, iomem)?; | ||||
|         let coherency_features = regs::GPU_COHERENCY_FEATURES.read(dev, iomem)?; | ||||
| 
 | ||||
|         let texture_features = regs::GPU_TEXTURE_FEATURES0.read(dev, iomem)?; | ||||
| 
 | ||||
|         let as_present = regs::GPU_AS_PRESENT.read(dev, iomem)?; | ||||
| 
 | ||||
|         let shader_present = u64::from(regs::GPU_SHADER_PRESENT_LO.read(dev, iomem)?); | ||||
|         let shader_present = | ||||
|             shader_present | u64::from(regs::GPU_SHADER_PRESENT_HI.read(dev, iomem)?) << 32; | ||||
| 
 | ||||
|         let tiler_present = u64::from(regs::GPU_TILER_PRESENT_LO.read(dev, iomem)?); | ||||
|         let tiler_present = | ||||
|             tiler_present | u64::from(regs::GPU_TILER_PRESENT_HI.read(dev, iomem)?) << 32; | ||||
| 
 | ||||
|         let l2_present = u64::from(regs::GPU_L2_PRESENT_LO.read(dev, iomem)?); | ||||
|         let l2_present = l2_present | u64::from(regs::GPU_L2_PRESENT_HI.read(dev, iomem)?) << 32; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|             gpu_id, | ||||
|             gpu_rev, | ||||
|             csf_id, | ||||
|             l2_features, | ||||
|             tiler_features, | ||||
|             mem_features, | ||||
|             mmu_features, | ||||
|             thread_features, | ||||
|             max_threads, | ||||
|             thread_max_workgroup_size, | ||||
|             thread_max_barrier_size, | ||||
|             coherency_features, | ||||
|             // TODO: Add texture_features_{1,2,3}.
 | ||||
|             texture_features: [texture_features, 0, 0, 0], | ||||
|             as_present, | ||||
|             pad0: 0, | ||||
|             shader_present, | ||||
|             l2_present, | ||||
|             tiler_present, | ||||
|             core_features, | ||||
|             pad: 0, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn log(&self, pdev: &platform::Device) { | ||||
|         let major = (self.gpu_id >> 16) & 0xff; | ||||
|         let minor = (self.gpu_id >> 8) & 0xff; | ||||
|         let status = self.gpu_id & 0xff; | ||||
| 
 | ||||
|         let model_name = if let Some(model) = GPU_MODELS | ||||
|             .iter() | ||||
|             .find(|&f| f.major == major && f.minor == minor) | ||||
|         { | ||||
|             model.name | ||||
|         } else { | ||||
|             "unknown" | ||||
|         }; | ||||
| 
 | ||||
|         dev_info!( | ||||
|             pdev.as_ref(), | ||||
|             "mali-{} id 0x{:x} major 0x{:x} minor 0x{:x} status 0x{:x}", | ||||
|             model_name, | ||||
|             self.gpu_id >> 16, | ||||
|             major, | ||||
|             minor, | ||||
|             status | ||||
|         ); | ||||
| 
 | ||||
|         dev_info!( | ||||
|             pdev.as_ref(), | ||||
|             "Features: L2:{:#x} Tiler:{:#x} Mem:{:#x} MMU:{:#x} AS:{:#x}", | ||||
|             self.l2_features, | ||||
|             self.tiler_features, | ||||
|             self.mem_features, | ||||
|             self.mmu_features, | ||||
|             self.as_present | ||||
|         ); | ||||
| 
 | ||||
|         dev_info!( | ||||
|             pdev.as_ref(), | ||||
|             "shader_present=0x{:016x} l2_present=0x{:016x} tiler_present=0x{:016x}", | ||||
|             self.shader_present, | ||||
|             self.l2_present, | ||||
|             self.tiler_present | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the number of virtual address bits supported by the GPU.
 | ||||
|     #[expect(dead_code)] | ||||
|     pub(crate) fn va_bits(&self) -> u32 { | ||||
|         self.mmu_features & genmask_u32(0..=7) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the number of physical address bits supported by the GPU.
 | ||||
|     #[expect(dead_code)] | ||||
|     pub(crate) fn pa_bits(&self) -> u32 { | ||||
|         (self.mmu_features >> 8) & genmask_u32(0..=7) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // SAFETY: `GpuInfo`'s invariant guarantees that it is the same type that is
 | ||||
| // already exposed to userspace by the C driver. This implies that it fulfills
 | ||||
| // the requirements for `AsBytes`.
 | ||||
| //
 | ||||
| // This means:
 | ||||
| //
 | ||||
| // - No implicit padding,
 | ||||
| // - No kernel pointers,
 | ||||
| // - No interior mutability.
 | ||||
| unsafe impl AsBytes for GpuInfo {} | ||||
| 
 | ||||
| struct GpuModels { | ||||
|     name: &'static str, | ||||
|     major: u32, | ||||
|     minor: u32, | ||||
| } | ||||
| 
 | ||||
| const GPU_MODELS: [GpuModels; 1] = [GpuModels { | ||||
|     name: "g610", | ||||
|     major: 10, | ||||
|     minor: 7, | ||||
| }]; | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| pub(crate) struct GpuId { | ||||
|     pub(crate) arch_major: u32, | ||||
|     pub(crate) arch_minor: u32, | ||||
|     pub(crate) arch_rev: u32, | ||||
|     pub(crate) prod_major: u32, | ||||
|     pub(crate) ver_major: u32, | ||||
|     pub(crate) ver_minor: u32, | ||||
|     pub(crate) ver_status: u32, | ||||
| } | ||||
| 
 | ||||
| impl From<u32> for GpuId { | ||||
|     fn from(value: u32) -> Self { | ||||
|         GpuId { | ||||
|             arch_major: (value & genmask_u32(28..=31)) >> 28, | ||||
|             arch_minor: (value & genmask_u32(24..=27)) >> 24, | ||||
|             arch_rev: (value & genmask_u32(20..=23)) >> 20, | ||||
|             prod_major: (value & genmask_u32(16..=19)) >> 16, | ||||
|             ver_major: (value & genmask_u32(12..=15)) >> 12, | ||||
|             ver_minor: (value & genmask_u32(4..=11)) >> 4, | ||||
|             ver_status: value & genmask_u32(0..=3), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Powers on the l2 block.
 | ||||
| pub(crate) fn l2_power_on(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result { | ||||
|     regs::L2_PWRON_LO.write(dev, iomem, 1)?; | ||||
| 
 | ||||
|     // TODO: We cannot poll, as there is no support in Rust currently, so we
 | ||||
|     // sleep. Change this when read_poll_timeout() is implemented in Rust.
 | ||||
|     kernel::time::delay::fsleep(time::Delta::from_millis(100)); | ||||
| 
 | ||||
|     if regs::L2_READY_LO.read(dev, iomem)? != 1 { | ||||
|         dev_err!(dev, "Failed to power on the GPU\n"); | ||||
|         return Err(EIO); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										108
									
								
								drivers/gpu/drm/tyr/regs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								drivers/gpu/drm/tyr/regs.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| // We don't expect that all the registers and fields will be used, even in the
 | ||||
| // future.
 | ||||
| //
 | ||||
| // Nevertheless, it is useful to have most of them defined, like the C driver
 | ||||
| // does.
 | ||||
| #![allow(dead_code)] | ||||
| 
 | ||||
| use kernel::bits::bit_u32; | ||||
| use kernel::device::Bound; | ||||
| use kernel::device::Device; | ||||
| use kernel::devres::Devres; | ||||
| use kernel::prelude::*; | ||||
| 
 | ||||
| use crate::driver::IoMem; | ||||
| 
 | ||||
| /// Represents a register in the Register Set
 | ||||
| ///
 | ||||
| /// TODO: Replace this with the Nova `register!()` macro when it is available.
 | ||||
| /// In particular, this will automatically give us 64bit register reads and
 | ||||
| /// writes.
 | ||||
| pub(crate) struct Register<const OFFSET: usize>; | ||||
| 
 | ||||
| impl<const OFFSET: usize> Register<OFFSET> { | ||||
|     #[inline] | ||||
|     pub(crate) fn read(&self, dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result<u32> { | ||||
|         let value = (*iomem).access(dev)?.read32(OFFSET); | ||||
|         Ok(value) | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub(crate) fn write(&self, dev: &Device<Bound>, iomem: &Devres<IoMem>, value: u32) -> Result { | ||||
|         (*iomem).access(dev)?.write32(value, OFFSET); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) const GPU_ID: Register<0x0> = Register; | ||||
| pub(crate) const GPU_L2_FEATURES: Register<0x4> = Register; | ||||
| pub(crate) const GPU_CORE_FEATURES: Register<0x8> = Register; | ||||
| pub(crate) const GPU_CSF_ID: Register<0x1c> = Register; | ||||
| pub(crate) const GPU_REVID: Register<0x280> = Register; | ||||
| pub(crate) const GPU_TILER_FEATURES: Register<0xc> = Register; | ||||
| pub(crate) const GPU_MEM_FEATURES: Register<0x10> = Register; | ||||
| pub(crate) const GPU_MMU_FEATURES: Register<0x14> = Register; | ||||
| pub(crate) const GPU_AS_PRESENT: Register<0x18> = Register; | ||||
| pub(crate) const GPU_IRQ_RAWSTAT: Register<0x20> = Register; | ||||
| 
 | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_FAULT: u32 = bit_u32(0); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_PROTECTED_FAULT: u32 = bit_u32(1); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_RESET_COMPLETED: u32 = bit_u32(8); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_POWER_CHANGED_SINGLE: u32 = bit_u32(9); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_POWER_CHANGED_ALL: u32 = bit_u32(10); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_CLEAN_CACHES_COMPLETED: u32 = bit_u32(17); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_DOORBELL_STATUS: u32 = bit_u32(18); | ||||
| pub(crate) const GPU_IRQ_RAWSTAT_MCU_STATUS: u32 = bit_u32(19); | ||||
| 
 | ||||
| pub(crate) const GPU_IRQ_CLEAR: Register<0x24> = Register; | ||||
| pub(crate) const GPU_IRQ_MASK: Register<0x28> = Register; | ||||
| pub(crate) const GPU_IRQ_STAT: Register<0x2c> = Register; | ||||
| pub(crate) const GPU_CMD: Register<0x30> = Register; | ||||
| pub(crate) const GPU_CMD_SOFT_RESET: u32 = 1 | (1 << 8); | ||||
| pub(crate) const GPU_CMD_HARD_RESET: u32 = 1 | (2 << 8); | ||||
| pub(crate) const GPU_THREAD_FEATURES: Register<0xac> = Register; | ||||
| pub(crate) const GPU_THREAD_MAX_THREADS: Register<0xa0> = Register; | ||||
| pub(crate) const GPU_THREAD_MAX_WORKGROUP_SIZE: Register<0xa4> = Register; | ||||
| pub(crate) const GPU_THREAD_MAX_BARRIER_SIZE: Register<0xa8> = Register; | ||||
| pub(crate) const GPU_TEXTURE_FEATURES0: Register<0xb0> = Register; | ||||
| pub(crate) const GPU_SHADER_PRESENT_LO: Register<0x100> = Register; | ||||
| pub(crate) const GPU_SHADER_PRESENT_HI: Register<0x104> = Register; | ||||
| pub(crate) const GPU_TILER_PRESENT_LO: Register<0x110> = Register; | ||||
| pub(crate) const GPU_TILER_PRESENT_HI: Register<0x114> = Register; | ||||
| pub(crate) const GPU_L2_PRESENT_LO: Register<0x120> = Register; | ||||
| pub(crate) const GPU_L2_PRESENT_HI: Register<0x124> = Register; | ||||
| pub(crate) const L2_READY_LO: Register<0x160> = Register; | ||||
| pub(crate) const L2_READY_HI: Register<0x164> = Register; | ||||
| pub(crate) const L2_PWRON_LO: Register<0x1a0> = Register; | ||||
| pub(crate) const L2_PWRON_HI: Register<0x1a4> = Register; | ||||
| pub(crate) const L2_PWRTRANS_LO: Register<0x220> = Register; | ||||
| pub(crate) const L2_PWRTRANS_HI: Register<0x204> = Register; | ||||
| pub(crate) const L2_PWRACTIVE_LO: Register<0x260> = Register; | ||||
| pub(crate) const L2_PWRACTIVE_HI: Register<0x264> = Register; | ||||
| 
 | ||||
| pub(crate) const MCU_CONTROL: Register<0x700> = Register; | ||||
| pub(crate) const MCU_CONTROL_ENABLE: u32 = 1; | ||||
| pub(crate) const MCU_CONTROL_AUTO: u32 = 2; | ||||
| pub(crate) const MCU_CONTROL_DISABLE: u32 = 0; | ||||
| 
 | ||||
| pub(crate) const MCU_STATUS: Register<0x704> = Register; | ||||
| pub(crate) const MCU_STATUS_DISABLED: u32 = 0; | ||||
| pub(crate) const MCU_STATUS_ENABLED: u32 = 1; | ||||
| pub(crate) const MCU_STATUS_HALT: u32 = 2; | ||||
| pub(crate) const MCU_STATUS_FATAL: u32 = 3; | ||||
| 
 | ||||
| pub(crate) const GPU_COHERENCY_FEATURES: Register<0x300> = Register; | ||||
| 
 | ||||
| pub(crate) const JOB_IRQ_RAWSTAT: Register<0x1000> = Register; | ||||
| pub(crate) const JOB_IRQ_CLEAR: Register<0x1004> = Register; | ||||
| pub(crate) const JOB_IRQ_MASK: Register<0x1008> = Register; | ||||
| pub(crate) const JOB_IRQ_STAT: Register<0x100c> = Register; | ||||
| 
 | ||||
| pub(crate) const JOB_IRQ_GLOBAL_IF: u32 = bit_u32(31); | ||||
| 
 | ||||
| pub(crate) const MMU_IRQ_RAWSTAT: Register<0x2000> = Register; | ||||
| pub(crate) const MMU_IRQ_CLEAR: Register<0x2004> = Register; | ||||
| pub(crate) const MMU_IRQ_MASK: Register<0x2008> = Register; | ||||
| pub(crate) const MMU_IRQ_STAT: Register<0x200c> = Register; | ||||
							
								
								
									
										22
									
								
								drivers/gpu/drm/tyr/tyr.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								drivers/gpu/drm/tyr/tyr.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| // SPDX-License-Identifier: GPL-2.0 or MIT
 | ||||
| 
 | ||||
| //! Arm Mali Tyr DRM driver.
 | ||||
| //!
 | ||||
| //! The name "Tyr" is inspired by Norse mythology, reflecting Arm's tradition of
 | ||||
| //! naming their GPUs after Nordic mythological figures and places.
 | ||||
| 
 | ||||
| use crate::driver::TyrDriver; | ||||
| 
 | ||||
| mod driver; | ||||
| mod file; | ||||
| mod gem; | ||||
| mod gpu; | ||||
| mod regs; | ||||
| 
 | ||||
| kernel::module_platform_driver! { | ||||
|     type: TyrDriver, | ||||
|     name: "tyr", | ||||
|     authors: ["The Tyr driver authors"], | ||||
|     description: "Arm Mali Tyr DRM driver", | ||||
|     license: "Dual MIT/GPL", | ||||
| } | ||||
|  | @ -34,14 +34,19 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self | |||
|         pdev.enable_device_mem()?; | ||||
|         pdev.set_master(); | ||||
| 
 | ||||
|         let bar = Arc::pin_init( | ||||
|         let devres_bar = Arc::pin_init( | ||||
|             pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0")), | ||||
|             GFP_KERNEL, | ||||
|         )?; | ||||
| 
 | ||||
|         // Used to provided a `&Bar0` to `Gpu::new` without tying it to the lifetime of
 | ||||
|         // `devres_bar`.
 | ||||
|         let bar_clone = Arc::clone(&devres_bar); | ||||
|         let bar = bar_clone.access(pdev.as_ref())?; | ||||
| 
 | ||||
|         let this = KBox::pin_init( | ||||
|             try_pin_init!(Self { | ||||
|                 gpu <- Gpu::new(pdev, bar)?, | ||||
|                 gpu <- Gpu::new(pdev, devres_bar, bar), | ||||
|                 _reg: auxiliary::Registration::new( | ||||
|                     pdev.as_ref(), | ||||
|                     c_str!("nova-drm"), | ||||
|  | @ -54,4 +59,8 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self | |||
| 
 | ||||
|         Ok(this) | ||||
|     } | ||||
| 
 | ||||
|     fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) { | ||||
|         this.gpu.unbind(pdev.as_ref()); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,16 +4,17 @@ | |||
| 
 | ||||
| use core::ops::Deref; | ||||
| use hal::FalconHal; | ||||
| use kernel::bindings; | ||||
| use kernel::device; | ||||
| use kernel::dma::DmaAddress; | ||||
| use kernel::prelude::*; | ||||
| use kernel::sync::aref::ARef; | ||||
| use kernel::time::Delta; | ||||
| use kernel::types::ARef; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
| use crate::driver::Bar0; | ||||
| use crate::gpu::Chipset; | ||||
| use crate::regs; | ||||
| use crate::regs::macros::RegisterBase; | ||||
| use crate::util; | ||||
| 
 | ||||
| pub(crate) mod gsp; | ||||
|  | @ -274,14 +275,25 @@ fn from(value: bool) -> Self { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Trait defining the parameters of a given Falcon instance.
 | ||||
| pub(crate) trait FalconEngine: Sync { | ||||
|     /// Base I/O address for the falcon, relative from which its registers are accessed.
 | ||||
|     const BASE: usize; | ||||
| /// Type used to represent the `PFALCON` registers address base for a given falcon engine.
 | ||||
| pub(crate) struct PFalconBase(()); | ||||
| 
 | ||||
| /// Type used to represent the `PFALCON2` registers address base for a given falcon engine.
 | ||||
| pub(crate) struct PFalcon2Base(()); | ||||
| 
 | ||||
| /// Trait defining the parameters of a given Falcon engine.
 | ||||
| ///
 | ||||
| /// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used
 | ||||
| /// to identify a given Falcon instance with register I/O methods.
 | ||||
| pub(crate) trait FalconEngine: | ||||
|     Send + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized | ||||
| { | ||||
|     /// Singleton of the engine, used to identify it with register I/O methods.
 | ||||
|     const ID: Self; | ||||
| } | ||||
| 
 | ||||
| /// Represents a portion of the firmware to be loaded into a particular memory (e.g. IMEM or DMEM).
 | ||||
| #[derive(Debug)] | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct FalconLoadTarget { | ||||
|     /// Offset from the start of the source object to copy from.
 | ||||
|     pub(crate) src_start: u32, | ||||
|  | @ -292,7 +304,7 @@ pub(crate) struct FalconLoadTarget { | |||
| } | ||||
| 
 | ||||
| /// Parameters for the falcon boot ROM.
 | ||||
| #[derive(Debug)] | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct FalconBromParams { | ||||
|     /// Offset in `DMEM`` of the firmware's signature.
 | ||||
|     pub(crate) pkc_data_offset: u32, | ||||
|  | @ -343,13 +355,13 @@ pub(crate) fn new( | |||
|         bar: &Bar0, | ||||
|         need_riscv: bool, | ||||
|     ) -> Result<Self> { | ||||
|         let hwcfg1 = regs::NV_PFALCON_FALCON_HWCFG1::read(bar, E::BASE); | ||||
|         let hwcfg1 = regs::NV_PFALCON_FALCON_HWCFG1::read(bar, &E::ID); | ||||
|         // Check that the revision and security model contain valid values.
 | ||||
|         let _ = hwcfg1.core_rev()?; | ||||
|         let _ = hwcfg1.security_model()?; | ||||
| 
 | ||||
|         if need_riscv { | ||||
|             let hwcfg2 = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); | ||||
|             let hwcfg2 = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); | ||||
|             if !hwcfg2.riscv() { | ||||
|                 dev_err!( | ||||
|                     dev, | ||||
|  | @ -369,7 +381,7 @@ pub(crate) fn new( | |||
|     fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { | ||||
|         // TIMEOUT: memory scrubbing should complete in less than 20ms.
 | ||||
|         util::wait_on(Delta::from_millis(20), || { | ||||
|             if regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE).mem_scrubbing_done() { | ||||
|             if regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID).mem_scrubbing_done() { | ||||
|                 Some(()) | ||||
|             } else { | ||||
|                 None | ||||
|  | @ -379,12 +391,12 @@ fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { | |||
| 
 | ||||
|     /// Reset the falcon engine.
 | ||||
|     fn reset_eng(&self, bar: &Bar0) -> Result { | ||||
|         let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); | ||||
|         let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); | ||||
| 
 | ||||
|         // According to OpenRM's `kflcnPreResetWait_GA102` documentation, HW sometimes does not set
 | ||||
|         // RESET_READY so a non-failing timeout is used.
 | ||||
|         let _ = util::wait_on(Delta::from_micros(150), || { | ||||
|             let r = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); | ||||
|             let r = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); | ||||
|             if r.reset_ready() { | ||||
|                 Some(()) | ||||
|             } else { | ||||
|  | @ -392,13 +404,13 @@ fn reset_eng(&self, bar: &Bar0) -> Result { | |||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         regs::NV_PFALCON_FALCON_ENGINE::alter(bar, E::BASE, |v| v.set_reset(true)); | ||||
|         regs::NV_PFALCON_FALCON_ENGINE::alter(bar, &E::ID, |v| v.set_reset(true)); | ||||
| 
 | ||||
|         // TODO[DLAY]: replace with udelay() or equivalent once available.
 | ||||
|         // TIMEOUT: falcon engine should not take more than 10us to reset.
 | ||||
|         let _: Result = util::wait_on(Delta::from_micros(10), || None); | ||||
| 
 | ||||
|         regs::NV_PFALCON_FALCON_ENGINE::alter(bar, E::BASE, |v| v.set_reset(false)); | ||||
|         regs::NV_PFALCON_FALCON_ENGINE::alter(bar, &E::ID, |v| v.set_reset(false)); | ||||
| 
 | ||||
|         self.reset_wait_mem_scrubbing(bar)?; | ||||
| 
 | ||||
|  | @ -413,7 +425,7 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result { | |||
| 
 | ||||
|         regs::NV_PFALCON_FALCON_RM::default() | ||||
|             .set_value(regs::NV_PMC_BOOT_0::read(bar).into()) | ||||
|             .write(bar, E::BASE); | ||||
|             .write(bar, &E::ID); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
|  | @ -443,7 +455,7 @@ fn dma_wr<F: FalconFirmware<Target = E>>( | |||
|                 fw.dma_handle_with_offset(load_offsets.src_start as usize)?, | ||||
|             ), | ||||
|         }; | ||||
|         if dma_start % bindings::dma_addr_t::from(DMA_LEN) > 0 { | ||||
|         if dma_start % DmaAddress::from(DMA_LEN) > 0 { | ||||
|             dev_err!( | ||||
|                 self.dev, | ||||
|                 "DMA transfer start addresses must be a multiple of {}", | ||||
|  | @ -451,44 +463,57 @@ fn dma_wr<F: FalconFirmware<Target = E>>( | |||
|             ); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
|         if load_offsets.len % DMA_LEN > 0 { | ||||
|             dev_err!( | ||||
|                 self.dev, | ||||
|                 "DMA transfer length must be a multiple of {}", | ||||
|                 DMA_LEN | ||||
|             ); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|         // DMA transfers can only be done in units of 256 bytes. Compute how many such transfers we
 | ||||
|         // need to perform.
 | ||||
|         let num_transfers = load_offsets.len.div_ceil(DMA_LEN); | ||||
| 
 | ||||
|         // Check that the area we are about to transfer is within the bounds of the DMA object.
 | ||||
|         // Upper limit of transfer is `(num_transfers * DMA_LEN) + load_offsets.src_start`.
 | ||||
|         match num_transfers | ||||
|             .checked_mul(DMA_LEN) | ||||
|             .and_then(|size| size.checked_add(load_offsets.src_start)) | ||||
|         { | ||||
|             None => { | ||||
|                 dev_err!(self.dev, "DMA transfer length overflow"); | ||||
|                 return Err(EOVERFLOW); | ||||
|             } | ||||
|             Some(upper_bound) if upper_bound as usize > fw.size() => { | ||||
|                 dev_err!(self.dev, "DMA transfer goes beyond range of DMA object"); | ||||
|                 return Err(EINVAL); | ||||
|             } | ||||
|             Some(_) => (), | ||||
|         }; | ||||
| 
 | ||||
|         // Set up the base source DMA address.
 | ||||
| 
 | ||||
|         regs::NV_PFALCON_FALCON_DMATRFBASE::default() | ||||
|             .set_base((dma_start >> 8) as u32) | ||||
|             .write(bar, E::BASE); | ||||
|             .write(bar, &E::ID); | ||||
|         regs::NV_PFALCON_FALCON_DMATRFBASE1::default() | ||||
|             .set_base((dma_start >> 40) as u16) | ||||
|             .write(bar, E::BASE); | ||||
|             .write(bar, &E::ID); | ||||
| 
 | ||||
|         let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default() | ||||
|             .set_size(DmaTrfCmdSize::Size256B) | ||||
|             .set_imem(target_mem == FalconMem::Imem) | ||||
|             .set_sec(if sec { 1 } else { 0 }); | ||||
| 
 | ||||
|         for pos in (0..load_offsets.len).step_by(DMA_LEN as usize) { | ||||
|         for pos in (0..num_transfers).map(|i| i * DMA_LEN) { | ||||
|             // Perform a transfer of size `DMA_LEN`.
 | ||||
|             regs::NV_PFALCON_FALCON_DMATRFMOFFS::default() | ||||
|                 .set_offs(load_offsets.dst_start + pos) | ||||
|                 .write(bar, E::BASE); | ||||
|                 .write(bar, &E::ID); | ||||
|             regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default() | ||||
|                 .set_offs(src_start + pos) | ||||
|                 .write(bar, E::BASE); | ||||
|             cmd.write(bar, E::BASE); | ||||
|                 .write(bar, &E::ID); | ||||
|             cmd.write(bar, &E::ID); | ||||
| 
 | ||||
|             // Wait for the transfer to complete.
 | ||||
|             // TIMEOUT: arbitrarily large value, no DMA transfer to the falcon's small memories
 | ||||
|             // should ever take that long.
 | ||||
|             util::wait_on(Delta::from_secs(2), || { | ||||
|                 let r = regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, E::BASE); | ||||
|                 let r = regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, &E::ID); | ||||
|                 if r.idle() { | ||||
|                     Some(()) | ||||
|                 } else { | ||||
|  | @ -502,9 +527,9 @@ fn dma_wr<F: FalconFirmware<Target = E>>( | |||
| 
 | ||||
|     /// Perform a DMA load into `IMEM` and `DMEM` of `fw`, and prepare the falcon to run it.
 | ||||
|     pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result { | ||||
|         regs::NV_PFALCON_FBIF_CTL::alter(bar, E::BASE, |v| v.set_allow_phys_no_ctx(true)); | ||||
|         regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, E::BASE); | ||||
|         regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, E::BASE, |v| { | ||||
|         regs::NV_PFALCON_FBIF_CTL::alter(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true)); | ||||
|         regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID); | ||||
|         regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, &E::ID, 0, |v| { | ||||
|             v.set_target(FalconFbifTarget::CoherentSysmem) | ||||
|                 .set_mem_type(FalconFbifMemType::Physical) | ||||
|         }); | ||||
|  | @ -517,7 +542,7 @@ pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) | |||
|         // Set `BootVec` to start of non-secure code.
 | ||||
|         regs::NV_PFALCON_FALCON_BOOTVEC::default() | ||||
|             .set_value(fw.boot_addr()) | ||||
|             .write(bar, E::BASE); | ||||
|             .write(bar, &E::ID); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
|  | @ -538,27 +563,27 @@ pub(crate) fn boot( | |||
|         if let Some(mbox0) = mbox0 { | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX0::default() | ||||
|                 .set_value(mbox0) | ||||
|                 .write(bar, E::BASE); | ||||
|                 .write(bar, &E::ID); | ||||
|         } | ||||
| 
 | ||||
|         if let Some(mbox1) = mbox1 { | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX1::default() | ||||
|                 .set_value(mbox1) | ||||
|                 .write(bar, E::BASE); | ||||
|                 .write(bar, &E::ID); | ||||
|         } | ||||
| 
 | ||||
|         match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, E::BASE).alias_en() { | ||||
|         match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en() { | ||||
|             true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default() | ||||
|                 .set_startcpu(true) | ||||
|                 .write(bar, E::BASE), | ||||
|                 .write(bar, &E::ID), | ||||
|             false => regs::NV_PFALCON_FALCON_CPUCTL::default() | ||||
|                 .set_startcpu(true) | ||||
|                 .write(bar, E::BASE), | ||||
|                 .write(bar, &E::ID), | ||||
|         } | ||||
| 
 | ||||
|         // TIMEOUT: arbitrarily large value, firmwares should complete in less than 2 seconds.
 | ||||
|         util::wait_on(Delta::from_secs(2), || { | ||||
|             let r = regs::NV_PFALCON_FALCON_CPUCTL::read(bar, E::BASE); | ||||
|             let r = regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID); | ||||
|             if r.halted() { | ||||
|                 Some(()) | ||||
|             } else { | ||||
|  | @ -567,8 +592,8 @@ pub(crate) fn boot( | |||
|         })?; | ||||
| 
 | ||||
|         let (mbox0, mbox1) = ( | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, E::BASE).value(), | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, E::BASE).value(), | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value(), | ||||
|             regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value(), | ||||
|         ); | ||||
| 
 | ||||
|         Ok((mbox0, mbox1)) | ||||
|  |  | |||
|  | @ -2,23 +2,31 @@ | |||
| 
 | ||||
| use crate::{ | ||||
|     driver::Bar0, | ||||
|     falcon::{Falcon, FalconEngine}, | ||||
|     regs, | ||||
|     falcon::{Falcon, FalconEngine, PFalcon2Base, PFalconBase}, | ||||
|     regs::{self, macros::RegisterBase}, | ||||
| }; | ||||
| 
 | ||||
| /// Type specifying the `Gsp` falcon engine. Cannot be instantiated.
 | ||||
| pub(crate) struct Gsp(()); | ||||
| 
 | ||||
| impl FalconEngine for Gsp { | ||||
| impl RegisterBase<PFalconBase> for Gsp { | ||||
|     const BASE: usize = 0x00110000; | ||||
| } | ||||
| 
 | ||||
| impl RegisterBase<PFalcon2Base> for Gsp { | ||||
|     const BASE: usize = 0x00111000; | ||||
| } | ||||
| 
 | ||||
| impl FalconEngine for Gsp { | ||||
|     const ID: Self = Gsp(()); | ||||
| } | ||||
| 
 | ||||
| impl Falcon<Gsp> { | ||||
|     /// Clears the SWGEN0 bit in the Falcon's IRQ status clear register to
 | ||||
|     /// allow GSP to signal CPU for processing new messages in message queue.
 | ||||
|     pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) { | ||||
|         regs::NV_PFALCON_FALCON_IRQSCLR::default() | ||||
|             .set_swgen0(true) | ||||
|             .write(bar, Gsp::BASE); | ||||
|             .write(bar, &Gsp::ID); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| /// Implements chipset-specific low-level operations. The trait is generic against [`FalconEngine`]
 | ||||
| /// so its `BASE` parameter can be used in order to avoid runtime bound checks when accessing
 | ||||
| /// registers.
 | ||||
| pub(crate) trait FalconHal<E: FalconEngine>: Sync { | ||||
| pub(crate) trait FalconHal<E: FalconEngine>: Send + Sync { | ||||
|     /// Activates the Falcon core if the engine is a risvc/falcon dual engine.
 | ||||
|     fn select_core(&self, _falcon: &Falcon<E>, _bar: &Bar0) -> Result { | ||||
|         Ok(()) | ||||
|  |  | |||
|  | @ -16,15 +16,15 @@ | |||
| use super::FalconHal; | ||||
| 
 | ||||
| fn select_core_ga102<E: FalconEngine>(bar: &Bar0) -> Result { | ||||
|     let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, E::BASE); | ||||
|     let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); | ||||
|     if bcr_ctrl.core_select() != PeregrineCoreSelect::Falcon { | ||||
|         regs::NV_PRISCV_RISCV_BCR_CTRL::default() | ||||
|             .set_core_select(PeregrineCoreSelect::Falcon) | ||||
|             .write(bar, E::BASE); | ||||
|             .write(bar, &E::ID); | ||||
| 
 | ||||
|         // TIMEOUT: falcon core should take less than 10ms to report being enabled.
 | ||||
|         util::wait_on(Delta::from_millis(10), || { | ||||
|             let r = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, E::BASE); | ||||
|             let r = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); | ||||
|             if r.valid() { | ||||
|                 Some(()) | ||||
|             } else { | ||||
|  | @ -42,50 +42,47 @@ fn signature_reg_fuse_version_ga102( | |||
|     engine_id_mask: u16, | ||||
|     ucode_id: u8, | ||||
| ) -> Result<u32> { | ||||
|     // TODO[REGA]: The ucode fuse versions are contained in the
 | ||||
|     // FUSE_OPT_FPF_<ENGINE>_UCODE<X>_VERSION registers, which are an array. Our register
 | ||||
|     // definition macros do not allow us to manage them properly, so we need to hardcode their
 | ||||
|     // addresses for now. Clean this up once we support register arrays.
 | ||||
|     const NV_FUSE_OPT_FPF_SIZE: u8 = regs::NV_FUSE_OPT_FPF_SIZE as u8; | ||||
| 
 | ||||
|     // Each engine has 16 ucode version registers numbered from 1 to 16.
 | ||||
|     if ucode_id == 0 || ucode_id > 16 { | ||||
|         dev_err!(dev, "invalid ucode id {:#x}", ucode_id); | ||||
|         return Err(EINVAL); | ||||
|     } | ||||
|     let ucode_idx = match ucode_id { | ||||
|         1..=NV_FUSE_OPT_FPF_SIZE => (ucode_id - 1) as usize, | ||||
|         _ => { | ||||
|             dev_err!(dev, "invalid ucode id {:#x}", ucode_id); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Base address of the FUSE registers array corresponding to the engine.
 | ||||
|     let reg_fuse_base = if engine_id_mask & 0x0001 != 0 { | ||||
|         regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::OFFSET | ||||
|     // `ucode_idx` is guaranteed to be in the range [0..15], making the `read` calls provable valid
 | ||||
|     // at build-time.
 | ||||
|     let reg_fuse_version = if engine_id_mask & 0x0001 != 0 { | ||||
|         regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data() | ||||
|     } else if engine_id_mask & 0x0004 != 0 { | ||||
|         regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::OFFSET | ||||
|         regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data() | ||||
|     } else if engine_id_mask & 0x0400 != 0 { | ||||
|         regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::OFFSET | ||||
|         regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::read(bar, ucode_idx).data() | ||||
|     } else { | ||||
|         dev_err!(dev, "unexpected engine_id_mask {:#x}", engine_id_mask); | ||||
|         return Err(EINVAL); | ||||
|     }; | ||||
| 
 | ||||
|     // Read `reg_fuse_base[ucode_id - 1]`.
 | ||||
|     let reg_fuse_version = | ||||
|         bar.read32(reg_fuse_base + ((ucode_id - 1) as usize * core::mem::size_of::<u32>())); | ||||
| 
 | ||||
|     // TODO[NUMM]: replace with `last_set_bit` once it lands.
 | ||||
|     Ok(u32::BITS - reg_fuse_version.leading_zeros()) | ||||
|     Ok(u16::BITS - reg_fuse_version.leading_zeros()) | ||||
| } | ||||
| 
 | ||||
| fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result { | ||||
|     regs::NV_PFALCON2_FALCON_BROM_PARAADDR::default() | ||||
|         .set_value(params.pkc_data_offset) | ||||
|         .write(bar, E::BASE); | ||||
|         .write(bar, &E::ID, 0); | ||||
|     regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default() | ||||
|         .set_value(u32::from(params.engine_id_mask)) | ||||
|         .write(bar, E::BASE); | ||||
|         .write(bar, &E::ID); | ||||
|     regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::default() | ||||
|         .set_ucode_id(params.ucode_id) | ||||
|         .write(bar, E::BASE); | ||||
|         .write(bar, &E::ID); | ||||
|     regs::NV_PFALCON2_FALCON_MOD_SEL::default() | ||||
|         .set_algo(FalconModSelAlgo::Rsa3k) | ||||
|         .write(bar, E::BASE); | ||||
|         .write(bar, &E::ID); | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,19 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| use crate::falcon::FalconEngine; | ||||
| use crate::falcon::{FalconEngine, PFalcon2Base, PFalconBase}; | ||||
| use crate::regs::macros::RegisterBase; | ||||
| 
 | ||||
| /// Type specifying the `Sec2` falcon engine. Cannot be instantiated.
 | ||||
| pub(crate) struct Sec2(()); | ||||
| 
 | ||||
| impl FalconEngine for Sec2 { | ||||
| impl RegisterBase<PFalconBase> for Sec2 { | ||||
|     const BASE: usize = 0x00840000; | ||||
| } | ||||
| 
 | ||||
| impl RegisterBase<PFalcon2Base> for Sec2 { | ||||
|     const BASE: usize = 0x00841000; | ||||
| } | ||||
| 
 | ||||
| impl FalconEngine for Sec2 { | ||||
|     const ID: Self = Sec2(()); | ||||
| } | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 
 | ||||
| use kernel::prelude::*; | ||||
| use kernel::sizes::*; | ||||
| use kernel::types::ARef; | ||||
| use kernel::sync::aref::ARef; | ||||
| use kernel::{dev_warn, device}; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
|  |  | |||
|  | @ -4,48 +4,36 @@ | |||
| //! to be loaded into a given execution unit.
 | ||||
| 
 | ||||
| use core::marker::PhantomData; | ||||
| use core::mem::size_of; | ||||
| 
 | ||||
| use kernel::device; | ||||
| use kernel::firmware; | ||||
| use kernel::prelude::*; | ||||
| use kernel::str::CString; | ||||
| use kernel::transmute::FromBytes; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
| use crate::falcon::FalconFirmware; | ||||
| use crate::gpu; | ||||
| use crate::gpu::Chipset; | ||||
| 
 | ||||
| pub(crate) mod booter; | ||||
| pub(crate) mod fwsec; | ||||
| pub(crate) mod gsp; | ||||
| pub(crate) mod riscv; | ||||
| 
 | ||||
| pub(crate) const FIRMWARE_VERSION: &str = "535.113.01"; | ||||
| pub(crate) const FIRMWARE_VERSION: &str = "570.144"; | ||||
| 
 | ||||
| /// Structure encapsulating the firmware blobs required for the GPU to operate.
 | ||||
| #[expect(dead_code)] | ||||
| pub(crate) struct Firmware { | ||||
|     booter_load: firmware::Firmware, | ||||
|     booter_unload: firmware::Firmware, | ||||
|     bootloader: firmware::Firmware, | ||||
|     gsp: firmware::Firmware, | ||||
| } | ||||
| /// Requests the GPU firmware `name` suitable for `chipset`, with version `ver`.
 | ||||
| fn request_firmware( | ||||
|     dev: &device::Device, | ||||
|     chipset: gpu::Chipset, | ||||
|     name: &str, | ||||
|     ver: &str, | ||||
| ) -> Result<firmware::Firmware> { | ||||
|     let chip_name = chipset.name(); | ||||
| 
 | ||||
| impl Firmware { | ||||
|     pub(crate) fn new(dev: &device::Device, chipset: Chipset, ver: &str) -> Result<Firmware> { | ||||
|         let mut chip_name = CString::try_from_fmt(fmt!("{chipset}"))?; | ||||
|         chip_name.make_ascii_lowercase(); | ||||
|         let chip_name = &*chip_name; | ||||
| 
 | ||||
|         let request = |name_| { | ||||
|             CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{name_}-{ver}.bin")) | ||||
|                 .and_then(|path| firmware::Firmware::request(&path, dev)) | ||||
|         }; | ||||
| 
 | ||||
|         Ok(Firmware { | ||||
|             booter_load: request("booter_load")?, | ||||
|             booter_unload: request("booter_unload")?, | ||||
|             bootloader: request("bootloader")?, | ||||
|             gsp: request("gsp")?, | ||||
|         }) | ||||
|     } | ||||
|     CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{name}-{ver}.bin")) | ||||
|         .and_then(|path| firmware::Firmware::request(&path, dev)) | ||||
| } | ||||
| 
 | ||||
| /// Structure used to describe some firmwares, notably FWSEC-FRTS.
 | ||||
|  | @ -150,6 +138,65 @@ fn no_patch_signature(self) -> FirmwareDmaObject<F, Signed> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Header common to most firmware files.
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug, Clone)] | ||||
| struct BinHdr { | ||||
|     /// Magic number, must be `0x10de`.
 | ||||
|     bin_magic: u32, | ||||
|     /// Version of the header.
 | ||||
|     bin_ver: u32, | ||||
|     /// Size in bytes of the binary (to be ignored).
 | ||||
|     bin_size: u32, | ||||
|     /// Offset of the start of the application-specific header.
 | ||||
|     header_offset: u32, | ||||
|     /// Offset of the start of the data payload.
 | ||||
|     data_offset: u32, | ||||
|     /// Size in bytes of the data payload.
 | ||||
|     data_size: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for BinHdr {} | ||||
| 
 | ||||
| // A firmware blob starting with a `BinHdr`.
 | ||||
| struct BinFirmware<'a> { | ||||
|     hdr: BinHdr, | ||||
|     fw: &'a [u8], | ||||
| } | ||||
| 
 | ||||
| impl<'a> BinFirmware<'a> { | ||||
|     /// Interpret `fw` as a firmware image starting with a [`BinHdr`], and returns the
 | ||||
|     /// corresponding [`BinFirmware`] that can be used to extract its payload.
 | ||||
|     fn new(fw: &'a firmware::Firmware) -> Result<Self> { | ||||
|         const BIN_MAGIC: u32 = 0x10de; | ||||
|         let fw = fw.data(); | ||||
| 
 | ||||
|         fw.get(0..size_of::<BinHdr>()) | ||||
|             // Extract header.
 | ||||
|             .and_then(BinHdr::from_bytes_copy) | ||||
|             // Validate header.
 | ||||
|             .and_then(|hdr| { | ||||
|                 if hdr.bin_magic == BIN_MAGIC { | ||||
|                     Some(hdr) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             }) | ||||
|             .map(|hdr| Self { hdr, fw }) | ||||
|             .ok_or(EINVAL) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the data payload of the firmware, or `None` if the data range is out of bounds of
 | ||||
|     /// the firmware image.
 | ||||
|     fn data(&self) -> Option<&[u8]> { | ||||
|         let fw_start = self.hdr.data_offset as usize; | ||||
|         let fw_size = self.hdr.data_size as usize; | ||||
| 
 | ||||
|         self.fw.get(fw_start..fw_start + fw_size) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) struct ModInfoBuilder<const N: usize>(firmware::ModInfoBuilder<N>); | ||||
| 
 | ||||
| impl<const N: usize> ModInfoBuilder<N> { | ||||
|  | @ -180,8 +227,8 @@ pub(crate) const fn create( | |||
|         let mut this = Self(firmware::ModInfoBuilder::new(module_name)); | ||||
|         let mut i = 0; | ||||
| 
 | ||||
|         while i < gpu::Chipset::NAMES.len() { | ||||
|             this = this.make_entry_chipset(gpu::Chipset::NAMES[i]); | ||||
|         while i < gpu::Chipset::ALL.len() { | ||||
|             this = this.make_entry_chipset(gpu::Chipset::ALL[i].name()); | ||||
|             i += 1; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										375
									
								
								drivers/gpu/nova-core/firmware/booter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								drivers/gpu/nova-core/firmware/booter.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,375 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| //! Support for loading and patching the `Booter` firmware. `Booter` is a Heavy Secured firmware
 | ||||
| //! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon
 | ||||
| //! (and optionally unload it through a separate firmware image).
 | ||||
| 
 | ||||
| use core::marker::PhantomData; | ||||
| use core::mem::size_of; | ||||
| use core::ops::Deref; | ||||
| 
 | ||||
| use kernel::device; | ||||
| use kernel::prelude::*; | ||||
| use kernel::transmute::FromBytes; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
| use crate::driver::Bar0; | ||||
| use crate::falcon::sec2::Sec2; | ||||
| use crate::falcon::{Falcon, FalconBromParams, FalconFirmware, FalconLoadParams, FalconLoadTarget}; | ||||
| use crate::firmware::{BinFirmware, FirmwareDmaObject, FirmwareSignature, Signed, Unsigned}; | ||||
| use crate::gpu::Chipset; | ||||
| 
 | ||||
| /// Local convenience function to return a copy of `S` by reinterpreting the bytes starting at
 | ||||
| /// `offset` in `slice`.
 | ||||
| fn frombytes_at<S: FromBytes + Sized>(slice: &[u8], offset: usize) -> Result<S> { | ||||
|     slice | ||||
|         .get(offset..offset + size_of::<S>()) | ||||
|         .and_then(S::from_bytes_copy) | ||||
|         .ok_or(EINVAL) | ||||
| } | ||||
| 
 | ||||
| /// Heavy-Secured firmware header.
 | ||||
| ///
 | ||||
| /// Such firmwares have an application-specific payload that needs to be patched with a given
 | ||||
| /// signature.
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug, Clone)] | ||||
| struct HsHeaderV2 { | ||||
|     /// Offset to the start of the signatures.
 | ||||
|     sig_prod_offset: u32, | ||||
|     /// Size in bytes of the signatures.
 | ||||
|     sig_prod_size: u32, | ||||
|     /// Offset to a `u32` containing the location at which to patch the signature in the microcode
 | ||||
|     /// image.
 | ||||
|     patch_loc_offset: u32, | ||||
|     /// Offset to a `u32` containing the index of the signature to patch.
 | ||||
|     patch_sig_offset: u32, | ||||
|     /// Start offset to the signature metadata.
 | ||||
|     meta_data_offset: u32, | ||||
|     /// Size in bytes of the signature metadata.
 | ||||
|     meta_data_size: u32, | ||||
|     /// Offset to a `u32` containing the number of signatures in the signatures section.
 | ||||
|     num_sig_offset: u32, | ||||
|     /// Offset of the application-specific header.
 | ||||
|     header_offset: u32, | ||||
|     /// Size in bytes of the application-specific header.
 | ||||
|     header_size: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for HsHeaderV2 {} | ||||
| 
 | ||||
| /// Heavy-Secured Firmware image container.
 | ||||
| ///
 | ||||
| /// This provides convenient access to the fields of [`HsHeaderV2`] that are actually indices to
 | ||||
| /// read from in the firmware data.
 | ||||
| struct HsFirmwareV2<'a> { | ||||
|     hdr: HsHeaderV2, | ||||
|     fw: &'a [u8], | ||||
| } | ||||
| 
 | ||||
| impl<'a> HsFirmwareV2<'a> { | ||||
|     /// Interprets the header of `bin_fw` as a [`HsHeaderV2`] and returns an instance of
 | ||||
|     /// `HsFirmwareV2` for further parsing.
 | ||||
|     ///
 | ||||
|     /// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
 | ||||
|     fn new(bin_fw: &BinFirmware<'a>) -> Result<Self> { | ||||
|         frombytes_at::<HsHeaderV2>(bin_fw.fw, bin_fw.hdr.header_offset as usize) | ||||
|             .map(|hdr| Self { hdr, fw: bin_fw.fw }) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the location at which the signatures should be patched in the microcode image.
 | ||||
|     ///
 | ||||
|     /// Fails if the offset of the patch location is outside the bounds of the firmware
 | ||||
|     /// image.
 | ||||
|     fn patch_location(&self) -> Result<u32> { | ||||
|         frombytes_at::<u32>(self.fw, self.hdr.patch_loc_offset as usize) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns an iterator to the signatures of the firmware. The iterator can be empty if the
 | ||||
|     /// firmware is unsigned.
 | ||||
|     ///
 | ||||
|     /// Fails if the pointed signatures are outside the bounds of the firmware image.
 | ||||
|     fn signatures_iter(&'a self) -> Result<impl Iterator<Item = BooterSignature<'a>>> { | ||||
|         let num_sig = frombytes_at::<u32>(self.fw, self.hdr.num_sig_offset as usize)?; | ||||
|         let iter = match self.hdr.sig_prod_size.checked_div(num_sig) { | ||||
|             // If there are no signatures, return an iterator that will yield zero elements.
 | ||||
|             None => (&[] as &[u8]).chunks_exact(1), | ||||
|             Some(sig_size) => { | ||||
|                 let patch_sig = frombytes_at::<u32>(self.fw, self.hdr.patch_sig_offset as usize)?; | ||||
|                 let signatures_start = (self.hdr.sig_prod_offset + patch_sig) as usize; | ||||
| 
 | ||||
|                 self.fw | ||||
|                     // Get signatures range.
 | ||||
|                     .get(signatures_start..signatures_start + self.hdr.sig_prod_size as usize) | ||||
|                     .ok_or(EINVAL)? | ||||
|                     .chunks_exact(sig_size as usize) | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         // Map the byte slices into signatures.
 | ||||
|         Ok(iter.map(BooterSignature)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Signature parameters, as defined in the firmware.
 | ||||
| #[repr(C)] | ||||
| struct HsSignatureParams { | ||||
|     /// Fuse version to use.
 | ||||
|     fuse_ver: u32, | ||||
|     /// Mask of engine IDs this firmware applies to.
 | ||||
|     engine_id_mask: u32, | ||||
|     /// ID of the microcode.
 | ||||
|     ucode_id: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for HsSignatureParams {} | ||||
| 
 | ||||
| impl HsSignatureParams { | ||||
|     /// Returns the signature parameters contained in `hs_fw`.
 | ||||
|     ///
 | ||||
|     /// Fails if the meta data parameter of `hs_fw` is outside the bounds of the firmware image, or
 | ||||
|     /// if its size doesn't match that of [`HsSignatureParams`].
 | ||||
|     fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> { | ||||
|         let start = hs_fw.hdr.meta_data_offset as usize; | ||||
|         let end = start | ||||
|             .checked_add(hs_fw.hdr.meta_data_size as usize) | ||||
|             .ok_or(EINVAL)?; | ||||
| 
 | ||||
|         hs_fw | ||||
|             .fw | ||||
|             .get(start..end) | ||||
|             .and_then(Self::from_bytes_copy) | ||||
|             .ok_or(EINVAL) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Header for code and data load offsets.
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug, Clone)] | ||||
| struct HsLoadHeaderV2 { | ||||
|     // Offset at which the code starts.
 | ||||
|     os_code_offset: u32, | ||||
|     // Total size of the code, for all apps.
 | ||||
|     os_code_size: u32, | ||||
|     // Offset at which the data starts.
 | ||||
|     os_data_offset: u32, | ||||
|     // Size of the data.
 | ||||
|     os_data_size: u32, | ||||
|     // Number of apps following this header. Each app is described by a [`HsLoadHeaderV2App`].
 | ||||
|     num_apps: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for HsLoadHeaderV2 {} | ||||
| 
 | ||||
| impl HsLoadHeaderV2 { | ||||
|     /// Returns the load header contained in `hs_fw`.
 | ||||
|     ///
 | ||||
|     /// Fails if the header pointed at by `hs_fw` is not within the bounds of the firmware image.
 | ||||
|     fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> { | ||||
|         frombytes_at::<Self>(hs_fw.fw, hs_fw.hdr.header_offset as usize) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Header for app code loader.
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug, Clone)] | ||||
| struct HsLoadHeaderV2App { | ||||
|     /// Offset at which to load the app code.
 | ||||
|     offset: u32, | ||||
|     /// Length in bytes of the app code.
 | ||||
|     len: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for HsLoadHeaderV2App {} | ||||
| 
 | ||||
| impl HsLoadHeaderV2App { | ||||
|     /// Returns the [`HsLoadHeaderV2App`] for app `idx` of `hs_fw`.
 | ||||
|     ///
 | ||||
|     /// Fails if `idx` is larger than the number of apps declared in `hs_fw`, or if the header is
 | ||||
|     /// not within the bounds of the firmware image.
 | ||||
|     fn new(hs_fw: &HsFirmwareV2<'_>, idx: u32) -> Result<Self> { | ||||
|         let load_hdr = HsLoadHeaderV2::new(hs_fw)?; | ||||
|         if idx >= load_hdr.num_apps { | ||||
|             Err(EINVAL) | ||||
|         } else { | ||||
|             frombytes_at::<Self>( | ||||
|                 hs_fw.fw, | ||||
|                 (hs_fw.hdr.header_offset as usize) | ||||
|                     // Skip the load header...
 | ||||
|                     .checked_add(size_of::<HsLoadHeaderV2>()) | ||||
|                     // ... and jump to app header `idx`.
 | ||||
|                     .and_then(|offset| { | ||||
|                         offset.checked_add((idx as usize).checked_mul(size_of::<Self>())?) | ||||
|                     }) | ||||
|                     .ok_or(EINVAL)?, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Signature for Booter firmware. Their size is encoded into the header and not known a compile
 | ||||
| /// time, so we just wrap a byte slices on which we can implement [`FirmwareSignature`].
 | ||||
| struct BooterSignature<'a>(&'a [u8]); | ||||
| 
 | ||||
| impl<'a> AsRef<[u8]> for BooterSignature<'a> { | ||||
|     fn as_ref(&self) -> &[u8] { | ||||
|         self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> FirmwareSignature<BooterFirmware> for BooterSignature<'a> {} | ||||
| 
 | ||||
| /// The `Booter` loader firmware, responsible for loading the GSP.
 | ||||
| pub(crate) struct BooterFirmware { | ||||
|     // Load parameters for `IMEM` falcon memory.
 | ||||
|     imem_load_target: FalconLoadTarget, | ||||
|     // Load parameters for `DMEM` falcon memory.
 | ||||
|     dmem_load_target: FalconLoadTarget, | ||||
|     // BROM falcon parameters.
 | ||||
|     brom_params: FalconBromParams, | ||||
|     // Device-mapped firmware image.
 | ||||
|     ucode: FirmwareDmaObject<Self, Signed>, | ||||
| } | ||||
| 
 | ||||
| impl FirmwareDmaObject<BooterFirmware, Unsigned> { | ||||
|     fn new_booter(dev: &device::Device<device::Bound>, data: &[u8]) -> Result<Self> { | ||||
|         DmaObject::from_data(dev, data).map(|ucode| Self(ucode, PhantomData)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug, PartialEq)] | ||||
| pub(crate) enum BooterKind { | ||||
|     Loader, | ||||
|     #[expect(unused)] | ||||
|     Unloader, | ||||
| } | ||||
| 
 | ||||
| impl BooterFirmware { | ||||
|     /// Parses the Booter firmware contained in `fw`, and patches the correct signature so it is
 | ||||
|     /// ready to be loaded and run on `falcon`.
 | ||||
|     pub(crate) fn new( | ||||
|         dev: &device::Device<device::Bound>, | ||||
|         kind: BooterKind, | ||||
|         chipset: Chipset, | ||||
|         ver: &str, | ||||
|         falcon: &Falcon<<Self as FalconFirmware>::Target>, | ||||
|         bar: &Bar0, | ||||
|     ) -> Result<Self> { | ||||
|         let fw_name = match kind { | ||||
|             BooterKind::Loader => "booter_load", | ||||
|             BooterKind::Unloader => "booter_unload", | ||||
|         }; | ||||
|         let fw = super::request_firmware(dev, chipset, fw_name, ver)?; | ||||
|         let bin_fw = BinFirmware::new(&fw)?; | ||||
| 
 | ||||
|         // The binary firmware embeds a Heavy-Secured firmware.
 | ||||
|         let hs_fw = HsFirmwareV2::new(&bin_fw)?; | ||||
| 
 | ||||
|         // The Heavy-Secured firmware embeds a firmware load descriptor.
 | ||||
|         let load_hdr = HsLoadHeaderV2::new(&hs_fw)?; | ||||
| 
 | ||||
|         // Offset in `ucode` where to patch the signature.
 | ||||
|         let patch_loc = hs_fw.patch_location()?; | ||||
| 
 | ||||
|         let sig_params = HsSignatureParams::new(&hs_fw)?; | ||||
|         let brom_params = FalconBromParams { | ||||
|             // `load_hdr.os_data_offset` is an absolute index, but `pkc_data_offset` is from the
 | ||||
|             // signature patch location.
 | ||||
|             pkc_data_offset: patch_loc | ||||
|                 .checked_sub(load_hdr.os_data_offset) | ||||
|                 .ok_or(EINVAL)?, | ||||
|             engine_id_mask: u16::try_from(sig_params.engine_id_mask).map_err(|_| EINVAL)?, | ||||
|             ucode_id: u8::try_from(sig_params.ucode_id).map_err(|_| EINVAL)?, | ||||
|         }; | ||||
|         let app0 = HsLoadHeaderV2App::new(&hs_fw, 0)?; | ||||
| 
 | ||||
|         // Object containing the firmware microcode to be signature-patched.
 | ||||
|         let ucode = bin_fw | ||||
|             .data() | ||||
|             .ok_or(EINVAL) | ||||
|             .and_then(|data| FirmwareDmaObject::<Self, _>::new_booter(dev, data))?; | ||||
| 
 | ||||
|         let ucode_signed = { | ||||
|             let mut signatures = hs_fw.signatures_iter()?.peekable(); | ||||
| 
 | ||||
|             if signatures.peek().is_none() { | ||||
|                 // If there are no signatures, then the firmware is unsigned.
 | ||||
|                 ucode.no_patch_signature() | ||||
|             } else { | ||||
|                 // Obtain the version from the fuse register, and extract the corresponding
 | ||||
|                 // signature.
 | ||||
|                 let reg_fuse_version = falcon.signature_reg_fuse_version( | ||||
|                     bar, | ||||
|                     brom_params.engine_id_mask, | ||||
|                     brom_params.ucode_id, | ||||
|                 )?; | ||||
| 
 | ||||
|                 // `0` means the last signature should be used.
 | ||||
|                 const FUSE_VERSION_USE_LAST_SIG: u32 = 0; | ||||
|                 let signature = match reg_fuse_version { | ||||
|                     FUSE_VERSION_USE_LAST_SIG => signatures.last(), | ||||
|                     // Otherwise hardware fuse version needs to be subtracted to obtain the index.
 | ||||
|                     reg_fuse_version => { | ||||
|                         let Some(idx) = sig_params.fuse_ver.checked_sub(reg_fuse_version) else { | ||||
|                             dev_err!(dev, "invalid fuse version for Booter firmware\n"); | ||||
|                             return Err(EINVAL); | ||||
|                         }; | ||||
|                         signatures.nth(idx as usize) | ||||
|                     } | ||||
|                 } | ||||
|                 .ok_or(EINVAL)?; | ||||
| 
 | ||||
|                 ucode.patch_signature(&signature, patch_loc as usize)? | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|             imem_load_target: FalconLoadTarget { | ||||
|                 src_start: app0.offset, | ||||
|                 dst_start: 0, | ||||
|                 len: app0.len, | ||||
|             }, | ||||
|             dmem_load_target: FalconLoadTarget { | ||||
|                 src_start: load_hdr.os_data_offset, | ||||
|                 dst_start: 0, | ||||
|                 len: load_hdr.os_data_size, | ||||
|             }, | ||||
|             brom_params, | ||||
|             ucode: ucode_signed, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FalconLoadParams for BooterFirmware { | ||||
|     fn imem_load_params(&self) -> FalconLoadTarget { | ||||
|         self.imem_load_target.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn dmem_load_params(&self) -> FalconLoadTarget { | ||||
|         self.dmem_load_target.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn brom_params(&self) -> FalconBromParams { | ||||
|         self.brom_params.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn boot_addr(&self) -> u32 { | ||||
|         self.imem_load_target.src_start | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Deref for BooterFirmware { | ||||
|     type Target = DmaObject; | ||||
| 
 | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.ucode.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FalconFirmware for BooterFirmware { | ||||
|     type Target = Sec2; | ||||
| } | ||||
|  | @ -202,9 +202,6 @@ pub(crate) struct FwsecFirmware { | |||
|     ucode: FirmwareDmaObject<Self, Signed>, | ||||
| } | ||||
| 
 | ||||
| // We need to load full DMEM pages.
 | ||||
| const DMEM_LOAD_SIZE_ALIGN: u32 = 256; | ||||
| 
 | ||||
| impl FalconLoadParams for FwsecFirmware { | ||||
|     fn imem_load_params(&self) -> FalconLoadTarget { | ||||
|         FalconLoadTarget { | ||||
|  | @ -218,11 +215,7 @@ fn dmem_load_params(&self) -> FalconLoadTarget { | |||
|         FalconLoadTarget { | ||||
|             src_start: self.desc.imem_load_size, | ||||
|             dst_start: self.desc.dmem_phys_base, | ||||
|             // TODO[NUMM]: replace with `align_up` once it lands.
 | ||||
|             len: self | ||||
|                 .desc | ||||
|                 .dmem_load_size | ||||
|                 .next_multiple_of(DMEM_LOAD_SIZE_ALIGN), | ||||
|             len: self.desc.dmem_load_size, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -253,8 +246,8 @@ impl FalconFirmware for FwsecFirmware { | |||
| 
 | ||||
| impl FirmwareDmaObject<FwsecFirmware, Unsigned> { | ||||
|     fn new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self> { | ||||
|         let desc = bios.fwsec_image().header(dev)?; | ||||
|         let ucode = bios.fwsec_image().ucode(dev, desc)?; | ||||
|         let desc = bios.fwsec_image().header()?; | ||||
|         let ucode = bios.fwsec_image().ucode(desc)?; | ||||
|         let mut dma_object = DmaObject::from_data(dev, ucode)?; | ||||
| 
 | ||||
|         let hdr_offset = (desc.imem_load_size + desc.interface_offset) as usize; | ||||
|  | @ -343,7 +336,7 @@ pub(crate) fn new( | |||
|         let ucode_dma = FirmwareDmaObject::<Self, _>::new_fwsec(dev, bios, cmd)?; | ||||
| 
 | ||||
|         // Patch signature if needed.
 | ||||
|         let desc = bios.fwsec_image().header(dev)?; | ||||
|         let desc = bios.fwsec_image().header()?; | ||||
|         let ucode_signed = if desc.signature_count != 0 { | ||||
|             let sig_base_img = (desc.imem_load_size + desc.pkc_data_offset) as usize; | ||||
|             let desc_sig_versions = u32::from(desc.signature_versions); | ||||
|  | @ -382,7 +375,7 @@ pub(crate) fn new( | |||
|             dev_dbg!(dev, "patching signature with index {}\n", signature_idx); | ||||
|             let signature = bios | ||||
|                 .fwsec_image() | ||||
|                 .sigs(dev, desc) | ||||
|                 .sigs(desc) | ||||
|                 .and_then(|sigs| sigs.get(signature_idx).ok_or(EINVAL))?; | ||||
| 
 | ||||
|             ucode_dma.patch_signature(signature, sig_base_img)? | ||||
|  |  | |||
							
								
								
									
										243
									
								
								drivers/gpu/nova-core/firmware/gsp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								drivers/gpu/nova-core/firmware/gsp.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,243 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| use core::mem::size_of_val; | ||||
| 
 | ||||
| use kernel::device; | ||||
| use kernel::dma::{DataDirection, DmaAddress}; | ||||
| use kernel::kvec; | ||||
| use kernel::prelude::*; | ||||
| use kernel::scatterlist::{Owned, SGTable}; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
| use crate::firmware::riscv::RiscvFirmware; | ||||
| use crate::gpu::{Architecture, Chipset}; | ||||
| use crate::gsp::GSP_PAGE_SIZE; | ||||
| 
 | ||||
| /// Ad-hoc and temporary module to extract sections from ELF images.
 | ||||
| ///
 | ||||
| /// Some firmware images are currently packaged as ELF files, where sections names are used as keys
 | ||||
| /// to specific and related bits of data. Future firmware versions are scheduled to move away from
 | ||||
| /// that scheme before nova-core becomes stable, which means this module will eventually be
 | ||||
| /// removed.
 | ||||
| mod elf { | ||||
|     use core::mem::size_of; | ||||
| 
 | ||||
|     use kernel::bindings; | ||||
|     use kernel::str::CStr; | ||||
|     use kernel::transmute::FromBytes; | ||||
| 
 | ||||
|     /// Newtype to provide a [`FromBytes`] implementation.
 | ||||
|     #[repr(transparent)] | ||||
|     struct Elf64Hdr(bindings::elf64_hdr); | ||||
|     // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
|     unsafe impl FromBytes for Elf64Hdr {} | ||||
| 
 | ||||
|     #[repr(transparent)] | ||||
|     struct Elf64SHdr(bindings::elf64_shdr); | ||||
|     // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
|     unsafe impl FromBytes for Elf64SHdr {} | ||||
| 
 | ||||
|     /// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
 | ||||
|     pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> { | ||||
|         let hdr = &elf | ||||
|             .get(0..size_of::<bindings::elf64_hdr>()) | ||||
|             .and_then(Elf64Hdr::from_bytes)? | ||||
|             .0; | ||||
| 
 | ||||
|         // Get all the section headers.
 | ||||
|         let mut shdr = { | ||||
|             let shdr_num = usize::from(hdr.e_shnum); | ||||
|             let shdr_start = usize::try_from(hdr.e_shoff).ok()?; | ||||
|             let shdr_end = shdr_num | ||||
|                 .checked_mul(size_of::<Elf64SHdr>()) | ||||
|                 .and_then(|v| v.checked_add(shdr_start))?; | ||||
| 
 | ||||
|             elf.get(shdr_start..shdr_end) | ||||
|                 .map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))? | ||||
|         }; | ||||
| 
 | ||||
|         // Get the strings table.
 | ||||
|         let strhdr = shdr | ||||
|             .clone() | ||||
|             .nth(usize::from(hdr.e_shstrndx)) | ||||
|             .and_then(Elf64SHdr::from_bytes)?; | ||||
| 
 | ||||
|         // Find the section which name matches `name` and return it.
 | ||||
|         shdr.find(|&sh| { | ||||
|             let Some(hdr) = Elf64SHdr::from_bytes(sh) else { | ||||
|                 return false; | ||||
|             }; | ||||
| 
 | ||||
|             let Some(name_idx) = strhdr | ||||
|                 .0 | ||||
|                 .sh_offset | ||||
|                 .checked_add(u64::from(hdr.0.sh_name)) | ||||
|                 .and_then(|idx| usize::try_from(idx).ok()) | ||||
|             else { | ||||
|                 return false; | ||||
|             }; | ||||
| 
 | ||||
|             // Get the start of the name.
 | ||||
|             elf.get(name_idx..) | ||||
|                 // Stop at the first `0`.
 | ||||
|                 .and_then(|nstr| nstr.get(0..=nstr.iter().position(|b| *b == 0)?)) | ||||
|                 // Convert into CStr. This should never fail because of the line above.
 | ||||
|                 .and_then(|nstr| CStr::from_bytes_with_nul(nstr).ok()) | ||||
|                 // Convert into str.
 | ||||
|                 .and_then(|c_str| c_str.to_str().ok()) | ||||
|                 // Check that the name matches.
 | ||||
|                 .map(|str| str == name) | ||||
|                 .unwrap_or(false) | ||||
|         }) | ||||
|         // Return the slice containing the section.
 | ||||
|         .and_then(|sh| { | ||||
|             let hdr = Elf64SHdr::from_bytes(sh)?; | ||||
|             let start = usize::try_from(hdr.0.sh_offset).ok()?; | ||||
|             let end = usize::try_from(hdr.0.sh_size) | ||||
|                 .ok() | ||||
|                 .and_then(|sh_size| start.checked_add(sh_size))?; | ||||
| 
 | ||||
|             elf.get(start..end) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// GSP firmware with 3-level radix page tables for the GSP bootloader.
 | ||||
| ///
 | ||||
| /// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
 | ||||
| /// space:
 | ||||
| ///
 | ||||
| /// ```text
 | ||||
| /// Level 0:  1 page, 1 entry         -> points to first level 1 page
 | ||||
| /// Level 1:  Multiple pages/entries  -> each entry points to a level 2 page
 | ||||
| /// Level 2:  Multiple pages/entries  -> each entry points to a firmware page
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
 | ||||
| /// Also known as "Radix3" firmware.
 | ||||
| #[pin_data] | ||||
| pub(crate) struct GspFirmware { | ||||
|     /// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
 | ||||
|     #[pin] | ||||
|     fw: SGTable<Owned<VVec<u8>>>, | ||||
|     /// Level 2 page table whose entries contain DMA addresses of firmware pages.
 | ||||
|     #[pin] | ||||
|     level2: SGTable<Owned<VVec<u8>>>, | ||||
|     /// Level 1 page table whose entries contain DMA addresses of level 2 pages.
 | ||||
|     #[pin] | ||||
|     level1: SGTable<Owned<VVec<u8>>>, | ||||
|     /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
 | ||||
|     level0: DmaObject, | ||||
|     /// Size in bytes of the firmware contained in [`Self::fw`].
 | ||||
|     size: usize, | ||||
|     /// Device-mapped GSP signatures matching the GPU's [`Chipset`].
 | ||||
|     signatures: DmaObject, | ||||
|     /// GSP bootloader, verifies the GSP firmware before loading and running it.
 | ||||
|     bootloader: RiscvFirmware, | ||||
| } | ||||
| 
 | ||||
| impl GspFirmware { | ||||
|     /// Loads the GSP firmware binaries, map them into `dev`'s address-space, and creates the page
 | ||||
|     /// tables expected by the GSP bootloader to load it.
 | ||||
|     pub(crate) fn new<'a, 'b>( | ||||
|         dev: &'a device::Device<device::Bound>, | ||||
|         chipset: Chipset, | ||||
|         ver: &'b str, | ||||
|     ) -> Result<impl PinInit<Self, Error> + 'a> { | ||||
|         let fw = super::request_firmware(dev, chipset, "gsp", ver)?; | ||||
| 
 | ||||
|         let fw_section = elf::elf64_section(fw.data(), ".fwimage").ok_or(EINVAL)?; | ||||
| 
 | ||||
|         let sigs_section = match chipset.arch() { | ||||
|             Architecture::Ampere => ".fwsignature_ga10x", | ||||
|             _ => return Err(ENOTSUPP), | ||||
|         }; | ||||
|         let signatures = elf::elf64_section(fw.data(), sigs_section) | ||||
|             .ok_or(EINVAL) | ||||
|             .and_then(|data| DmaObject::from_data(dev, data))?; | ||||
| 
 | ||||
|         let size = fw_section.len(); | ||||
| 
 | ||||
|         // Move the firmware into a vmalloc'd vector and map it into the device address
 | ||||
|         // space.
 | ||||
|         let fw_vvec = VVec::with_capacity(fw_section.len(), GFP_KERNEL) | ||||
|             .and_then(|mut v| { | ||||
|                 v.extend_from_slice(fw_section, GFP_KERNEL)?; | ||||
|                 Ok(v) | ||||
|             }) | ||||
|             .map_err(|_| ENOMEM)?; | ||||
| 
 | ||||
|         let bl = super::request_firmware(dev, chipset, "bootloader", ver)?; | ||||
|         let bootloader = RiscvFirmware::new(dev, &bl)?; | ||||
| 
 | ||||
|         Ok(try_pin_init!(Self { | ||||
|             fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL), | ||||
|             level2 <- { | ||||
|                 // Allocate the level 2 page table, map the firmware onto it, and map it into the
 | ||||
|                 // device address space.
 | ||||
|                 VVec::<u8>::with_capacity( | ||||
|                     fw.iter().count() * core::mem::size_of::<u64>(), | ||||
|                     GFP_KERNEL, | ||||
|                 ) | ||||
|                 .map_err(|_| ENOMEM) | ||||
|                 .and_then(|level2| map_into_lvl(&fw, level2)) | ||||
|                 .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))? | ||||
|             }, | ||||
|             level1 <- { | ||||
|                 // Allocate the level 1 page table, map the level 2 page table onto it, and map it
 | ||||
|                 // into the device address space.
 | ||||
|                 VVec::<u8>::with_capacity( | ||||
|                     level2.iter().count() * core::mem::size_of::<u64>(), | ||||
|                     GFP_KERNEL, | ||||
|                 ) | ||||
|                 .map_err(|_| ENOMEM) | ||||
|                 .and_then(|level1| map_into_lvl(&level2, level1)) | ||||
|                 .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))? | ||||
|             }, | ||||
|             level0: { | ||||
|                 // Allocate the level 0 page table as a device-visible DMA object, and map the
 | ||||
|                 // level 1 page table onto it.
 | ||||
| 
 | ||||
|                 // Level 0 page table data.
 | ||||
|                 let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?; | ||||
| 
 | ||||
|                 // Fill level 1 page entry.
 | ||||
|                 #[allow(clippy::useless_conversion)] | ||||
|                 let level1_entry = u64::from(level1.iter().next().unwrap().dma_address()); | ||||
|                 let dst = &mut level0_data[..size_of_val(&level1_entry)]; | ||||
|                 dst.copy_from_slice(&level1_entry.to_le_bytes()); | ||||
| 
 | ||||
|                 // Turn the level0 page table into a [`DmaObject`].
 | ||||
|                 DmaObject::from_data(dev, &level0_data)? | ||||
|             }, | ||||
|             size, | ||||
|             signatures, | ||||
|             bootloader, | ||||
|         })) | ||||
|     } | ||||
| 
 | ||||
|     #[expect(unused)] | ||||
|     /// Returns the DMA handle of the radix3 level 0 page table.
 | ||||
|     pub(crate) fn radix3_dma_handle(&self) -> DmaAddress { | ||||
|         self.level0.dma_handle() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Build a page table from a scatter-gather list.
 | ||||
| ///
 | ||||
| /// Takes each DMA-mapped region from `sg_table` and writes page table entries
 | ||||
| /// for all 4KB pages within that region. For example, a 16KB SG entry becomes
 | ||||
| /// 4 consecutive page table entries.
 | ||||
| fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> { | ||||
|     for sg_entry in sg_table.iter() { | ||||
|         // Number of pages we need to map.
 | ||||
|         let num_pages = (sg_entry.dma_len() as usize).div_ceil(GSP_PAGE_SIZE); | ||||
| 
 | ||||
|         for i in 0..num_pages { | ||||
|             let entry = sg_entry.dma_address() + (i as u64 * GSP_PAGE_SIZE as u64); | ||||
|             dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Ok(dst) | ||||
| } | ||||
							
								
								
									
										91
									
								
								drivers/gpu/nova-core/firmware/riscv.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								drivers/gpu/nova-core/firmware/riscv.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,91 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| //! Support for firmware binaries designed to run on a RISC-V core. Such firmwares files have a
 | ||||
| //! dedicated header.
 | ||||
| 
 | ||||
| use core::mem::size_of; | ||||
| 
 | ||||
| use kernel::device; | ||||
| use kernel::firmware::Firmware; | ||||
| use kernel::prelude::*; | ||||
| use kernel::transmute::FromBytes; | ||||
| 
 | ||||
| use crate::dma::DmaObject; | ||||
| use crate::firmware::BinFirmware; | ||||
| 
 | ||||
| /// Descriptor for microcode running on a RISC-V core.
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug)] | ||||
| struct RmRiscvUCodeDesc { | ||||
|     version: u32, | ||||
|     bootloader_offset: u32, | ||||
|     bootloader_size: u32, | ||||
|     bootloader_param_offset: u32, | ||||
|     bootloader_param_size: u32, | ||||
|     riscv_elf_offset: u32, | ||||
|     riscv_elf_size: u32, | ||||
|     app_version: u32, | ||||
|     manifest_offset: u32, | ||||
|     manifest_size: u32, | ||||
|     monitor_data_offset: u32, | ||||
|     monitor_data_size: u32, | ||||
|     monitor_code_offset: u32, | ||||
|     monitor_code_size: u32, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
 | ||||
| unsafe impl FromBytes for RmRiscvUCodeDesc {} | ||||
| 
 | ||||
| impl RmRiscvUCodeDesc { | ||||
|     /// Interprets the header of `bin_fw` as a [`RmRiscvUCodeDesc`] and returns it.
 | ||||
|     ///
 | ||||
|     /// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
 | ||||
|     fn new(bin_fw: &BinFirmware<'_>) -> Result<Self> { | ||||
|         let offset = bin_fw.hdr.header_offset as usize; | ||||
| 
 | ||||
|         bin_fw | ||||
|             .fw | ||||
|             .get(offset..offset + size_of::<Self>()) | ||||
|             .and_then(Self::from_bytes_copy) | ||||
|             .ok_or(EINVAL) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A parsed firmware for a RISC-V core, ready to be loaded and run.
 | ||||
| #[expect(unused)] | ||||
| pub(crate) struct RiscvFirmware { | ||||
|     /// Offset at which the code starts in the firmware image.
 | ||||
|     code_offset: u32, | ||||
|     /// Offset at which the data starts in the firmware image.
 | ||||
|     data_offset: u32, | ||||
|     /// Offset at which the manifest starts in the firmware image.
 | ||||
|     manifest_offset: u32, | ||||
|     /// Application version.
 | ||||
|     app_version: u32, | ||||
|     /// Device-mapped firmware image.
 | ||||
|     ucode: DmaObject, | ||||
| } | ||||
| 
 | ||||
| impl RiscvFirmware { | ||||
|     /// Parses the RISC-V firmware image contained in `fw`.
 | ||||
|     pub(crate) fn new(dev: &device::Device<device::Bound>, fw: &Firmware) -> Result<Self> { | ||||
|         let bin_fw = BinFirmware::new(fw)?; | ||||
| 
 | ||||
|         let riscv_desc = RmRiscvUCodeDesc::new(&bin_fw)?; | ||||
| 
 | ||||
|         let ucode = { | ||||
|             let start = bin_fw.hdr.data_offset as usize; | ||||
|             let len = bin_fw.hdr.data_size as usize; | ||||
| 
 | ||||
|             DmaObject::from_data(dev, fw.data().get(start..start + len).ok_or(EINVAL)?)? | ||||
|         }; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|             ucode, | ||||
|             code_offset: riscv_desc.monitor_code_offset, | ||||
|             data_offset: riscv_desc.monitor_data_offset, | ||||
|             manifest_offset: riscv_desc.manifest_offset, | ||||
|             app_version: riscv_desc.app_version, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -3,15 +3,11 @@ | |||
| use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, sync::Arc}; | ||||
| 
 | ||||
| use crate::driver::Bar0; | ||||
| use crate::falcon::{gsp::Gsp, sec2::Sec2, Falcon}; | ||||
| use crate::fb::FbLayout; | ||||
| use crate::falcon::{gsp::Gsp as GspFalcon, sec2::Sec2 as Sec2Falcon, Falcon}; | ||||
| use crate::fb::SysmemFlush; | ||||
| use crate::firmware::fwsec::{FwsecCommand, FwsecFirmware}; | ||||
| use crate::firmware::{Firmware, FIRMWARE_VERSION}; | ||||
| use crate::gfw; | ||||
| use crate::gsp::Gsp; | ||||
| use crate::regs; | ||||
| use crate::util; | ||||
| use crate::vbios::Vbios; | ||||
| use core::fmt; | ||||
| 
 | ||||
| macro_rules! define_chipset { | ||||
|  | @ -28,13 +24,23 @@ impl Chipset { | |||
|                 $( Chipset::$variant, )* | ||||
|             ]; | ||||
| 
 | ||||
|             pub(crate) const NAMES: [&'static str; Self::ALL.len()] = [ | ||||
|                 $( util::const_bytes_to_str( | ||||
|                         util::to_lowercase_bytes::<{ stringify!($variant).len() }>( | ||||
|                             stringify!($variant) | ||||
|                         ).as_slice() | ||||
|                 ), )* | ||||
|             ]; | ||||
|             ::kernel::macros::paste!( | ||||
|             /// Returns the name of this chipset, in lowercase.
 | ||||
|             ///
 | ||||
|             /// # Examples
 | ||||
|             ///
 | ||||
|             /// ```
 | ||||
|             /// let chipset = Chipset::GA102;
 | ||||
|             /// assert_eq!(chipset.name(), "ga102");
 | ||||
|             /// ```
 | ||||
|             pub(crate) const fn name(&self) -> &'static str { | ||||
|                 match *self { | ||||
|                 $( | ||||
|                     Chipset::$variant => stringify!([<$variant:lower>]), | ||||
|                 )* | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         // TODO[FPRI]: replace with something like derive(FromPrimitive)
 | ||||
|  | @ -163,150 +169,74 @@ fn new(bar: &Bar0) -> Result<Spec> { | |||
| } | ||||
| 
 | ||||
| /// Structure holding the resources required to operate the GPU.
 | ||||
| #[pin_data(PinnedDrop)] | ||||
| #[pin_data] | ||||
| pub(crate) struct Gpu { | ||||
|     spec: Spec, | ||||
|     /// MMIO mapping of PCI BAR 0
 | ||||
|     bar: Arc<Devres<Bar0>>, | ||||
|     fw: Firmware, | ||||
|     /// System memory page required for flushing all pending GPU-side memory writes done through
 | ||||
|     /// PCIE into system memory, via sysmembar (A GPU-initiated HW memory-barrier operation).
 | ||||
|     sysmem_flush: SysmemFlush, | ||||
| } | ||||
| 
 | ||||
| #[pinned_drop] | ||||
| impl PinnedDrop for Gpu { | ||||
|     fn drop(self: Pin<&mut Self>) { | ||||
|         // Unregister the sysmem flush page before we release it.
 | ||||
|         self.bar | ||||
|             .try_access_with(|b| self.sysmem_flush.unregister(b)); | ||||
|     } | ||||
|     /// GSP falcon instance, used for GSP boot up and cleanup.
 | ||||
|     gsp_falcon: Falcon<GspFalcon>, | ||||
|     /// SEC2 falcon instance, used for GSP boot up and cleanup.
 | ||||
|     sec2_falcon: Falcon<Sec2Falcon>, | ||||
|     /// GSP runtime data. Temporarily an empty placeholder.
 | ||||
|     #[pin] | ||||
|     gsp: Gsp, | ||||
| } | ||||
| 
 | ||||
| impl Gpu { | ||||
|     /// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
 | ||||
|     /// created the WPR2 region.
 | ||||
|     ///
 | ||||
|     /// TODO: this needs to be moved into a larger type responsible for booting the whole GSP
 | ||||
|     /// (`GspBooter`?).
 | ||||
|     fn run_fwsec_frts( | ||||
|         dev: &device::Device<device::Bound>, | ||||
|         falcon: &Falcon<Gsp>, | ||||
|         bar: &Bar0, | ||||
|         bios: &Vbios, | ||||
|         fb_layout: &FbLayout, | ||||
|     ) -> Result<()> { | ||||
|         // Check that the WPR2 region does not already exists - if it does, we cannot run
 | ||||
|         // FWSEC-FRTS until the GPU is reset.
 | ||||
|         if regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound() != 0 { | ||||
|             dev_err!( | ||||
|                 dev, | ||||
|                 "WPR2 region already exists - GPU needs to be reset to proceed\n" | ||||
|             ); | ||||
|             return Err(EBUSY); | ||||
|         } | ||||
| 
 | ||||
|         let fwsec_frts = FwsecFirmware::new( | ||||
|             dev, | ||||
|             falcon, | ||||
|             bar, | ||||
|             bios, | ||||
|             FwsecCommand::Frts { | ||||
|                 frts_addr: fb_layout.frts.start, | ||||
|                 frts_size: fb_layout.frts.end - fb_layout.frts.start, | ||||
|             }, | ||||
|         )?; | ||||
| 
 | ||||
|         // Run FWSEC-FRTS to create the WPR2 region.
 | ||||
|         fwsec_frts.run(dev, falcon, bar)?; | ||||
| 
 | ||||
|         // SCRATCH_E contains the error code for FWSEC-FRTS.
 | ||||
|         let frts_status = regs::NV_PBUS_SW_SCRATCH_0E::read(bar).frts_err_code(); | ||||
|         if frts_status != 0 { | ||||
|             dev_err!( | ||||
|                 dev, | ||||
|                 "FWSEC-FRTS returned with error code {:#x}", | ||||
|                 frts_status | ||||
|             ); | ||||
| 
 | ||||
|             return Err(EIO); | ||||
|         } | ||||
| 
 | ||||
|         // Check that the WPR2 region has been created as we requested.
 | ||||
|         let (wpr2_lo, wpr2_hi) = ( | ||||
|             regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO::read(bar).lower_bound(), | ||||
|             regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound(), | ||||
|         ); | ||||
| 
 | ||||
|         match (wpr2_lo, wpr2_hi) { | ||||
|             (_, 0) => { | ||||
|                 dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n"); | ||||
| 
 | ||||
|                 Err(EIO) | ||||
|             } | ||||
|             (wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => { | ||||
|                 dev_err!( | ||||
|                     dev, | ||||
|                     "WPR2 region created at unexpected address {:#x}; expected {:#x}\n", | ||||
|                     wpr2_lo, | ||||
|                     fb_layout.frts.start, | ||||
|     pub(crate) fn new<'a>( | ||||
|         pdev: &'a pci::Device<device::Bound>, | ||||
|         devres_bar: Arc<Devres<Bar0>>, | ||||
|         bar: &'a Bar0, | ||||
|     ) -> impl PinInit<Self, Error> + 'a { | ||||
|         try_pin_init!(Self { | ||||
|             spec: Spec::new(bar).inspect(|spec| { | ||||
|                 dev_info!( | ||||
|                     pdev.as_ref(), | ||||
|                     "NVIDIA (Chipset: {}, Architecture: {:?}, Revision: {})\n", | ||||
|                     spec.chipset, | ||||
|                     spec.chipset.arch(), | ||||
|                     spec.revision | ||||
|                 ); | ||||
|             })?, | ||||
| 
 | ||||
|                 Err(EIO) | ||||
|             } | ||||
|             (wpr2_lo, wpr2_hi) => { | ||||
|                 dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_lo, wpr2_hi); | ||||
|                 dev_dbg!(dev, "GPU instance built\n"); | ||||
|             // We must wait for GFW_BOOT completion before doing any significant setup on the GPU.
 | ||||
|             _: { | ||||
|                 gfw::wait_gfw_boot_completion(bar) | ||||
|                     .inspect_err(|_| dev_err!(pdev.as_ref(), "GFW boot did not complete"))?; | ||||
|             }, | ||||
| 
 | ||||
|                 Ok(()) | ||||
|             } | ||||
|         } | ||||
|             sysmem_flush: SysmemFlush::register(pdev.as_ref(), bar, spec.chipset)?, | ||||
| 
 | ||||
|             gsp_falcon: Falcon::new( | ||||
|                 pdev.as_ref(), | ||||
|                 spec.chipset, | ||||
|                 bar, | ||||
|                 spec.chipset > Chipset::GA100, | ||||
|             ) | ||||
|             .inspect(|falcon| falcon.clear_swgen0_intr(bar))?, | ||||
| 
 | ||||
|             sec2_falcon: Falcon::new(pdev.as_ref(), spec.chipset, bar, true)?, | ||||
| 
 | ||||
|             gsp <- Gsp::new(), | ||||
| 
 | ||||
|             _: { gsp.boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)? }, | ||||
| 
 | ||||
|             bar: devres_bar, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn new( | ||||
|         pdev: &pci::Device<device::Bound>, | ||||
|         devres_bar: Arc<Devres<Bar0>>, | ||||
|     ) -> Result<impl PinInit<Self>> { | ||||
|         let bar = devres_bar.access(pdev.as_ref())?; | ||||
|         let spec = Spec::new(bar)?; | ||||
|         let fw = Firmware::new(pdev.as_ref(), spec.chipset, FIRMWARE_VERSION)?; | ||||
| 
 | ||||
|         dev_info!( | ||||
|             pdev.as_ref(), | ||||
|             "NVIDIA (Chipset: {}, Architecture: {:?}, Revision: {})\n", | ||||
|             spec.chipset, | ||||
|             spec.chipset.arch(), | ||||
|             spec.revision | ||||
|         ); | ||||
| 
 | ||||
|         // We must wait for GFW_BOOT completion before doing any significant setup on the GPU.
 | ||||
|         gfw::wait_gfw_boot_completion(bar) | ||||
|             .inspect_err(|_| dev_err!(pdev.as_ref(), "GFW boot did not complete"))?; | ||||
| 
 | ||||
|         let sysmem_flush = SysmemFlush::register(pdev.as_ref(), bar, spec.chipset)?; | ||||
| 
 | ||||
|         let gsp_falcon = Falcon::<Gsp>::new( | ||||
|             pdev.as_ref(), | ||||
|             spec.chipset, | ||||
|             bar, | ||||
|             spec.chipset > Chipset::GA100, | ||||
|         )?; | ||||
|         gsp_falcon.clear_swgen0_intr(bar); | ||||
| 
 | ||||
|         let _sec2_falcon = Falcon::<Sec2>::new(pdev.as_ref(), spec.chipset, bar, true)?; | ||||
| 
 | ||||
|         let fb_layout = FbLayout::new(spec.chipset, bar)?; | ||||
|         dev_dbg!(pdev.as_ref(), "{:#x?}\n", fb_layout); | ||||
| 
 | ||||
|         let bios = Vbios::new(pdev, bar)?; | ||||
| 
 | ||||
|         Self::run_fwsec_frts(pdev.as_ref(), &gsp_falcon, bar, &bios, &fb_layout)?; | ||||
| 
 | ||||
|         Ok(pin_init!(Self { | ||||
|             spec, | ||||
|             bar: devres_bar, | ||||
|             fw, | ||||
|             sysmem_flush, | ||||
|         })) | ||||
|     /// Called when the corresponding [`Device`](device::Device) is unbound.
 | ||||
|     ///
 | ||||
|     /// Note: This method must only be called from `Driver::unbind`.
 | ||||
|     pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) { | ||||
|         kernel::warn_on!(self | ||||
|             .bar | ||||
|             .access(dev) | ||||
|             .inspect(|bar| self.sysmem_flush.unregister(bar)) | ||||
|             .is_err()); | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										22
									
								
								drivers/gpu/nova-core/gsp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								drivers/gpu/nova-core/gsp.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| mod boot; | ||||
| 
 | ||||
| use kernel::prelude::*; | ||||
| 
 | ||||
| mod fw; | ||||
| 
 | ||||
| pub(crate) const GSP_PAGE_SHIFT: usize = 12; | ||||
| pub(crate) const GSP_PAGE_SIZE: usize = 1 << GSP_PAGE_SHIFT; | ||||
| 
 | ||||
| /// GSP runtime data.
 | ||||
| ///
 | ||||
| /// This is an empty pinned placeholder for now.
 | ||||
| #[pin_data] | ||||
| pub(crate) struct Gsp {} | ||||
| 
 | ||||
| impl Gsp { | ||||
|     pub(crate) fn new() -> impl PinInit<Self> { | ||||
|         pin_init!(Self {}) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										137
									
								
								drivers/gpu/nova-core/gsp/boot.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								drivers/gpu/nova-core/gsp/boot.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| use kernel::device; | ||||
| use kernel::pci; | ||||
| use kernel::prelude::*; | ||||
| 
 | ||||
| use crate::driver::Bar0; | ||||
| use crate::falcon::{gsp::Gsp, sec2::Sec2, Falcon}; | ||||
| use crate::fb::FbLayout; | ||||
| use crate::firmware::{ | ||||
|     booter::{BooterFirmware, BooterKind}, | ||||
|     fwsec::{FwsecCommand, FwsecFirmware}, | ||||
|     gsp::GspFirmware, | ||||
|     FIRMWARE_VERSION, | ||||
| }; | ||||
| use crate::gpu::Chipset; | ||||
| use crate::regs; | ||||
| use crate::vbios::Vbios; | ||||
| 
 | ||||
| impl super::Gsp { | ||||
|     /// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
 | ||||
|     /// created the WPR2 region.
 | ||||
|     fn run_fwsec_frts( | ||||
|         dev: &device::Device<device::Bound>, | ||||
|         falcon: &Falcon<Gsp>, | ||||
|         bar: &Bar0, | ||||
|         bios: &Vbios, | ||||
|         fb_layout: &FbLayout, | ||||
|     ) -> Result<()> { | ||||
|         // Check that the WPR2 region does not already exists - if it does, we cannot run
 | ||||
|         // FWSEC-FRTS until the GPU is reset.
 | ||||
|         if regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound() != 0 { | ||||
|             dev_err!( | ||||
|                 dev, | ||||
|                 "WPR2 region already exists - GPU needs to be reset to proceed\n" | ||||
|             ); | ||||
|             return Err(EBUSY); | ||||
|         } | ||||
| 
 | ||||
|         let fwsec_frts = FwsecFirmware::new( | ||||
|             dev, | ||||
|             falcon, | ||||
|             bar, | ||||
|             bios, | ||||
|             FwsecCommand::Frts { | ||||
|                 frts_addr: fb_layout.frts.start, | ||||
|                 frts_size: fb_layout.frts.end - fb_layout.frts.start, | ||||
|             }, | ||||
|         )?; | ||||
| 
 | ||||
|         // Run FWSEC-FRTS to create the WPR2 region.
 | ||||
|         fwsec_frts.run(dev, falcon, bar)?; | ||||
| 
 | ||||
|         // SCRATCH_E contains the error code for FWSEC-FRTS.
 | ||||
|         let frts_status = regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR::read(bar).frts_err_code(); | ||||
|         if frts_status != 0 { | ||||
|             dev_err!( | ||||
|                 dev, | ||||
|                 "FWSEC-FRTS returned with error code {:#x}", | ||||
|                 frts_status | ||||
|             ); | ||||
| 
 | ||||
|             return Err(EIO); | ||||
|         } | ||||
| 
 | ||||
|         // Check that the WPR2 region has been created as we requested.
 | ||||
|         let (wpr2_lo, wpr2_hi) = ( | ||||
|             regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO::read(bar).lower_bound(), | ||||
|             regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound(), | ||||
|         ); | ||||
| 
 | ||||
|         match (wpr2_lo, wpr2_hi) { | ||||
|             (_, 0) => { | ||||
|                 dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n"); | ||||
| 
 | ||||
|                 Err(EIO) | ||||
|             } | ||||
|             (wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => { | ||||
|                 dev_err!( | ||||
|                     dev, | ||||
|                     "WPR2 region created at unexpected address {:#x}; expected {:#x}\n", | ||||
|                     wpr2_lo, | ||||
|                     fb_layout.frts.start, | ||||
|                 ); | ||||
| 
 | ||||
|                 Err(EIO) | ||||
|             } | ||||
|             (wpr2_lo, wpr2_hi) => { | ||||
|                 dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_lo, wpr2_hi); | ||||
|                 dev_dbg!(dev, "GPU instance built\n"); | ||||
| 
 | ||||
|                 Ok(()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Attempt to boot the GSP.
 | ||||
|     ///
 | ||||
|     /// This is a GPU-dependent and complex procedure that involves loading firmware files from
 | ||||
|     /// user-space, patching them with signatures, and building firmware-specific intricate data
 | ||||
|     /// structures that the GSP will use at runtime.
 | ||||
|     ///
 | ||||
|     /// Upon return, the GSP is up and running, and its runtime object given as return value.
 | ||||
|     pub(crate) fn boot( | ||||
|         self: Pin<&mut Self>, | ||||
|         pdev: &pci::Device<device::Bound>, | ||||
|         bar: &Bar0, | ||||
|         chipset: Chipset, | ||||
|         gsp_falcon: &Falcon<Gsp>, | ||||
|         sec2_falcon: &Falcon<Sec2>, | ||||
|     ) -> Result { | ||||
|         let dev = pdev.as_ref(); | ||||
| 
 | ||||
|         let bios = Vbios::new(dev, bar)?; | ||||
| 
 | ||||
|         let _gsp_fw = KBox::pin_init( | ||||
|             GspFirmware::new(dev, chipset, FIRMWARE_VERSION)?, | ||||
|             GFP_KERNEL, | ||||
|         )?; | ||||
| 
 | ||||
|         let fb_layout = FbLayout::new(chipset, bar)?; | ||||
|         dev_dbg!(dev, "{:#x?}\n", fb_layout); | ||||
| 
 | ||||
|         Self::run_fwsec_frts(dev, gsp_falcon, bar, &bios, &fb_layout)?; | ||||
| 
 | ||||
|         let _booter_loader = BooterFirmware::new( | ||||
|             dev, | ||||
|             BooterKind::Loader, | ||||
|             chipset, | ||||
|             FIRMWARE_VERSION, | ||||
|             sec2_falcon, | ||||
|             bar, | ||||
|         )?; | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								drivers/gpu/nova-core/gsp/fw.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								drivers/gpu/nova-core/gsp/fw.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| mod r570_144; | ||||
| 
 | ||||
| // Alias to avoid repeating the version number with every use.
 | ||||
| #[expect(unused)] | ||||
| use r570_144 as bindings; | ||||
							
								
								
									
										29
									
								
								drivers/gpu/nova-core/gsp/fw/r570_144.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								drivers/gpu/nova-core/gsp/fw/r570_144.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| //! Firmware bindings.
 | ||||
| //!
 | ||||
| //! Imports the generated bindings by `bindgen`.
 | ||||
| //!
 | ||||
| //! This module may not be directly used. Please abstract or re-export the needed symbols in the
 | ||||
| //! parent module instead.
 | ||||
| 
 | ||||
| #![cfg_attr(test, allow(deref_nullptr))] | ||||
| #![cfg_attr(test, allow(unaligned_references))] | ||||
| #![cfg_attr(test, allow(unsafe_op_in_unsafe_fn))] | ||||
| #![allow(
 | ||||
|     dead_code, | ||||
|     unused_imports, | ||||
|     clippy::all, | ||||
|     clippy::undocumented_unsafe_blocks, | ||||
|     clippy::ptr_as_ptr, | ||||
|     clippy::ref_as_ptr, | ||||
|     missing_docs, | ||||
|     non_camel_case_types, | ||||
|     non_upper_case_globals, | ||||
|     non_snake_case, | ||||
|     improper_ctypes, | ||||
|     unreachable_pub, | ||||
|     unsafe_op_in_unsafe_fn | ||||
| )] | ||||
| use kernel::ffi; | ||||
| include!("r570_144/bindings.rs"); | ||||
							
								
								
									
										1
									
								
								drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
|  | @ -9,6 +9,7 @@ | |||
| mod firmware; | ||||
| mod gfw; | ||||
| mod gpu; | ||||
| mod gsp; | ||||
| mod regs; | ||||
| mod util; | ||||
| mod vbios; | ||||
|  |  | |||
|  | @ -5,11 +5,11 @@ | |||
| #![allow(non_camel_case_types)] | ||||
| 
 | ||||
| #[macro_use] | ||||
| mod macros; | ||||
| pub(crate) mod macros; | ||||
| 
 | ||||
| use crate::falcon::{ | ||||
|     DmaTrfCmdSize, FalconCoreRev, FalconCoreRevSubversion, FalconFbifMemType, FalconFbifTarget, | ||||
|     FalconModSelAlgo, FalconSecurityModel, PeregrineCoreSelect, | ||||
|     FalconModSelAlgo, FalconSecurityModel, PFalcon2Base, PFalconBase, PeregrineCoreSelect, | ||||
| }; | ||||
| use crate::gpu::{Architecture, Chipset}; | ||||
| use kernel::prelude::*; | ||||
|  | @ -28,7 +28,7 @@ impl NV_PMC_BOOT_0 { | |||
|     /// Combines `architecture_0` and `architecture_1` to obtain the architecture of the chip.
 | ||||
|     pub(crate) fn architecture(self) -> Result<Architecture> { | ||||
|         Architecture::try_from( | ||||
|             self.architecture_0() | (self.architecture_1() << Self::ARCHITECTURE_0.len()), | ||||
|             self.architecture_0() | (self.architecture_1() << Self::ARCHITECTURE_0_RANGE.len()), | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|  | @ -36,7 +36,8 @@ pub(crate) fn architecture(self) -> Result<Architecture> { | |||
|     pub(crate) fn chipset(self) -> Result<Chipset> { | ||||
|         self.architecture() | ||||
|             .map(|arch| { | ||||
|                 ((arch as u32) << Self::IMPLEMENTATION.len()) | u32::from(self.implementation()) | ||||
|                 ((arch as u32) << Self::IMPLEMENTATION_RANGE.len()) | ||||
|                     | u32::from(self.implementation()) | ||||
|             }) | ||||
|             .and_then(Chipset::try_from) | ||||
|     } | ||||
|  | @ -44,8 +45,10 @@ pub(crate) fn chipset(self) -> Result<Chipset> { | |||
| 
 | ||||
| // PBUS
 | ||||
| 
 | ||||
| // TODO[REGA]: this is an array of registers.
 | ||||
| register!(NV_PBUS_SW_SCRATCH_0E@0x00001438  { | ||||
| register!(NV_PBUS_SW_SCRATCH @ 0x00001400[64]  {}); | ||||
| 
 | ||||
| register!(NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe], | ||||
|     "scratch register 0xe used as FRTS firmware error code" { | ||||
|     31:16   frts_err_code as u16; | ||||
| }); | ||||
| 
 | ||||
|  | @ -123,13 +126,12 @@ pub(crate) fn higher_bound(self) -> u64 { | |||
|     0:0     read_protection_level0 as bool, "Set after FWSEC lowers its protection level"; | ||||
| }); | ||||
| 
 | ||||
| // TODO[REGA]: This is an array of registers.
 | ||||
| register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234 { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| // OpenRM defines this as a register array, but doesn't specify its size and only uses its first
 | ||||
| // element. Be conservative until we know the actual size or need to use more registers.
 | ||||
| register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234[1] {}); | ||||
| 
 | ||||
| register!( | ||||
|     NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05, | ||||
|     NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0], | ||||
|     "Scratch group 05 register 0 used as GFW boot progress indicator" { | ||||
|         7:0    progress as u8, "Progress of GFW boot (0xff means completed)"; | ||||
|     } | ||||
|  | @ -180,38 +182,40 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> { | |||
| 
 | ||||
| // FUSE
 | ||||
| 
 | ||||
| register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100 { | ||||
| pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16; | ||||
| 
 | ||||
| register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] { | ||||
|     15:0    data as u16; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140 { | ||||
| register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] { | ||||
|     15:0    data as u16; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0 { | ||||
| register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] { | ||||
|     15:0    data as u16; | ||||
| }); | ||||
| 
 | ||||
| // PFALCON
 | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_IRQSCLR @ +0x00000004 { | ||||
| register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] { | ||||
|     4:4     halt as bool; | ||||
|     6:6     swgen0 as bool; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_MAILBOX0 @ +0x00000040 { | ||||
| register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_MAILBOX1 @ +0x00000044 { | ||||
| register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_RM @ +0x00000084 { | ||||
| register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_HWCFG2 @ +0x000000f4 { | ||||
| register!(NV_PFALCON_FALCON_HWCFG2 @ PFalconBase[0x000000f4] { | ||||
|     10:10   riscv as bool; | ||||
|     12:12   mem_scrubbing as bool, "Set to 0 after memory scrubbing is completed"; | ||||
|     31:31   reset_ready as bool, "Signal indicating that reset is completed (GA102+)"; | ||||
|  | @ -224,17 +228,17 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_CPUCTL @ +0x00000100 { | ||||
| register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] { | ||||
|     1:1     startcpu as bool; | ||||
|     4:4     halted as bool; | ||||
|     6:6     alias_en as bool; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_BOOTVEC @ +0x00000104 { | ||||
| register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMACTL @ +0x0000010c { | ||||
| register!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] { | ||||
|     0:0     require_ctx as bool; | ||||
|     1:1     dmem_scrubbing as bool; | ||||
|     2:2     imem_scrubbing as bool; | ||||
|  | @ -242,15 +246,15 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { | |||
|     7:7     secure_stat as bool; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMATRFBASE @ +0x00000110 { | ||||
| register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] { | ||||
|     31:0    base as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMATRFMOFFS @ +0x00000114 { | ||||
| register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] { | ||||
|     23:0    offs as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMATRFCMD @ +0x00000118 { | ||||
| register!(NV_PFALCON_FALCON_DMATRFCMD @ PFalconBase[0x00000118] { | ||||
|     0:0     full as bool; | ||||
|     1:1     idle as bool; | ||||
|     3:2     sec as u8; | ||||
|  | @ -261,60 +265,62 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { | |||
|     16:16   set_dmtag as u8; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ +0x0000011c { | ||||
| register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] { | ||||
|     31:0    offs as u32; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_DMATRFBASE1 @ +0x00000128 { | ||||
| register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] { | ||||
|     8:0     base as u16; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_HWCFG1 @ +0x0000012c { | ||||
| register!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] { | ||||
|     3:0     core_rev as u8 ?=> FalconCoreRev, "Core revision"; | ||||
|     5:4     security_model as u8 ?=> FalconSecurityModel, "Security model"; | ||||
|     7:6     core_rev_subversion as u8 ?=> FalconCoreRevSubversion, "Core revision subversion"; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ +0x00000130 { | ||||
| register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] { | ||||
|     1:1     startcpu as bool; | ||||
| }); | ||||
| 
 | ||||
| // Actually known as `NV_PSEC_FALCON_ENGINE` and `NV_PGSP_FALCON_ENGINE` depending on the falcon
 | ||||
| // instance.
 | ||||
| register!(NV_PFALCON_FALCON_ENGINE @ +0x000003c0 { | ||||
| register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] { | ||||
|     0:0     reset as bool; | ||||
| }); | ||||
| 
 | ||||
| // TODO[REGA]: this is an array of registers.
 | ||||
| register!(NV_PFALCON_FBIF_TRANSCFG @ +0x00000600 { | ||||
| register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] { | ||||
|     1:0     target as u8 ?=> FalconFbifTarget; | ||||
|     2:2     mem_type as bool => FalconFbifMemType; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON_FBIF_CTL @ +0x00000624 { | ||||
| register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] { | ||||
|     7:7     allow_phys_no_ctx as bool; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON2_FALCON_MOD_SEL @ +0x00001180 { | ||||
| /* PFALCON2 */ | ||||
| 
 | ||||
| register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] { | ||||
|     7:0     algo as u8 ?=> FalconModSelAlgo; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ +0x00001198 { | ||||
| register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] { | ||||
|     7:0    ucode_id as u8; | ||||
| }); | ||||
| 
 | ||||
| register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ +0x0000119c { | ||||
| register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| // TODO[REGA]: this is an array of registers.
 | ||||
| register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ +0x00001210 { | ||||
| // OpenRM defines this as a register array, but doesn't specify its size and only uses its first
 | ||||
| // element. Be conservative until we know the actual size or need to use more registers.
 | ||||
| register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210[1]] { | ||||
|     31:0    value as u32; | ||||
| }); | ||||
| 
 | ||||
| // PRISCV
 | ||||
| 
 | ||||
| register!(NV_PRISCV_RISCV_BCR_CTRL @ +0x00001668 { | ||||
| register!(NV_PRISCV_RISCV_BCR_CTRL @ PFalconBase[0x00001668] { | ||||
|     0:0     valid as bool; | ||||
|     4:4     core_select as bool => PeregrineCoreSelect; | ||||
|     8:8     br_fetch as bool; | ||||
|  |  | |||
|  | @ -1,17 +1,27 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| //! Macro to define register layout and accessors.
 | ||||
| //! `register!` macro to define register layout and accessors.
 | ||||
| //!
 | ||||
| //! A single register typically includes several fields, which are accessed through a combination
 | ||||
| //! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
 | ||||
| //! not all possible field values are necessarily valid.
 | ||||
| //!
 | ||||
| //! The macro in this module allow to define, using an intruitive and readable syntax, a dedicated
 | ||||
| //! type for each register with its own field accessors that can return an error is a field's value
 | ||||
| //! is invalid.
 | ||||
| //! The `register!` macro in this module provides an intuitive and readable syntax for defining a
 | ||||
| //! dedicated type for each register. Each such type comes with its own field accessors that can
 | ||||
| //! return an error if a field's value is invalid.
 | ||||
| 
 | ||||
| /// Defines a dedicated type for a register with an absolute offset, alongside with getter and
 | ||||
| /// setter methods for its fields and methods to read and write it from an `Io` region.
 | ||||
| /// Trait providing a base address to be added to the offset of a relative register to obtain
 | ||||
| /// its actual offset.
 | ||||
| ///
 | ||||
| /// The `T` generic argument is used to distinguish which base to use, in case a type provides
 | ||||
| /// several bases. It is given to the `register!` macro to restrict the use of the register to
 | ||||
| /// implementors of this particular variant.
 | ||||
| pub(crate) trait RegisterBase<T> { | ||||
|     const BASE: usize; | ||||
| } | ||||
| 
 | ||||
| /// Defines a dedicated type for a register with an absolute offset, including getter and setter
 | ||||
| /// methods for its fields and methods to read and write it from an `Io` region.
 | ||||
| ///
 | ||||
| /// Example:
 | ||||
| ///
 | ||||
|  | @ -24,7 +34,7 @@ | |||
| /// ```
 | ||||
| ///
 | ||||
| /// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
 | ||||
| /// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 less
 | ||||
| /// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 least
 | ||||
| /// significant bits of the register. Each field can be accessed and modified using accessor
 | ||||
| /// methods:
 | ||||
| ///
 | ||||
|  | @ -33,130 +43,344 @@ | |||
| /// let boot0 = BOOT_0::read(&bar);
 | ||||
| /// pr_info!("chip revision: {}.{}", boot0.major_revision(), boot0.minor_revision());
 | ||||
| ///
 | ||||
| /// // `Chipset::try_from` will be called with the value of the field and returns an error if the
 | ||||
| /// // value is invalid.
 | ||||
| /// // `Chipset::try_from` is called with the value of the `chipset` field and returns an
 | ||||
| /// // error if it is invalid.
 | ||||
| /// let chipset = boot0.chipset()?;
 | ||||
| ///
 | ||||
| /// // Update some fields and write the value back.
 | ||||
| /// boot0.set_major_revision(3).set_minor_revision(10).write(&bar);
 | ||||
| ///
 | ||||
| /// // Or just read and update the register in a single step:
 | ||||
| /// // Or, just read and update the register in a single step:
 | ||||
| /// BOOT_0::alter(&bar, |r| r.set_major_revision(3).set_minor_revision(10));
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// Fields can be defined as follows:
 | ||||
| /// Fields are defined as follows:
 | ||||
| ///
 | ||||
| /// - `as <type>` simply returns the field value casted as the requested integer type, typically
 | ||||
| ///   `u32`, `u16`, `u8` or `bool`. Note that `bool` fields must have a range of 1 bit.
 | ||||
| /// - `as <type>` simply returns the field value casted to <type>, typically `u32`, `u16`, `u8` or
 | ||||
| ///   `bool`. Note that `bool` fields must have a range of 1 bit.
 | ||||
| /// - `as <type> => <into_type>` calls `<into_type>`'s `From::<<type>>` implementation and returns
 | ||||
| ///   the result.
 | ||||
| /// - `as <type> ?=> <try_into_type>` calls `<try_into_type>`'s `TryFrom::<<type>>` implementation
 | ||||
| ///   and returns the result. This is useful on fields for which not all values are value.
 | ||||
| ///   and returns the result. This is useful with fields for which not all values are valid.
 | ||||
| ///
 | ||||
| /// The documentation strings are optional. If present, they will be added to the type's
 | ||||
| /// definition, or the field getter and setter methods they are attached to.
 | ||||
| ///
 | ||||
| /// Putting a `+` before the address of the register makes it relative to a base: the `read` and
 | ||||
| /// `write` methods take a `base` argument that is added to the specified address before access,
 | ||||
| /// and `try_read` and `try_write` methods are also created, allowing access with offsets unknown
 | ||||
| /// at compile-time:
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// register!(CPU_CTL @ +0x0000010, "CPU core control" {
 | ||||
| ///    0:0     start as bool, "Start the CPU core";
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// // Flip the `start` switch for the CPU core which base address is at `CPU_BASE`.
 | ||||
| /// let cpuctl = CPU_CTL::read(&bar, CPU_BASE);
 | ||||
| /// pr_info!("CPU CTL: {:#x}", cpuctl);
 | ||||
| /// cpuctl.set_start(true).write(&bar, CPU_BASE);
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// It is also possible to create a alias register by using the `=> ALIAS` syntax. This is useful
 | ||||
| /// for cases where a register's interpretation depends on the context:
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// register!(SCRATCH_0 @ 0x0000100, "Scratch register 0" {
 | ||||
| /// register!(SCRATCH @ 0x00000200, "Scratch register" {
 | ||||
| ///    31:0     value as u32, "Raw value";
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// register!(SCRATCH_0_BOOT_STATUS => SCRATCH_0, "Boot status of the firmware" {
 | ||||
| /// register!(SCRATCH_BOOT_STATUS => SCRATCH, "Boot status of the firmware" {
 | ||||
| ///     0:0     completed as bool, "Whether the firmware has completed booting";
 | ||||
| /// });
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// In this example, `SCRATCH_0_BOOT_STATUS` uses the same I/O address as `SCRATCH_0`, while also
 | ||||
| /// providing its own `completed` method.
 | ||||
| /// In this example, `SCRATCH_0_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
 | ||||
| /// providing its own `completed` field.
 | ||||
| ///
 | ||||
| /// ## Relative registers
 | ||||
| ///
 | ||||
| /// A register can be defined as being accessible from a fixed offset of a provided base. For
 | ||||
| /// instance, imagine the following I/O space:
 | ||||
| ///
 | ||||
| /// ```text
 | ||||
| ///           +-----------------------------+
 | ||||
| ///           |             ...             |
 | ||||
| ///           |                             |
 | ||||
| ///  0x100--->+------------CPU0-------------+
 | ||||
| ///           |                             |
 | ||||
| ///  0x110--->+-----------------------------+
 | ||||
| ///           |           CPU_CTL           |
 | ||||
| ///           +-----------------------------+
 | ||||
| ///           |             ...             |
 | ||||
| ///           |                             |
 | ||||
| ///           |                             |
 | ||||
| ///  0x200--->+------------CPU1-------------+
 | ||||
| ///           |                             |
 | ||||
| ///  0x210--->+-----------------------------+
 | ||||
| ///           |           CPU_CTL           |
 | ||||
| ///           +-----------------------------+
 | ||||
| ///           |             ...             |
 | ||||
| ///           +-----------------------------+
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O
 | ||||
| /// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define
 | ||||
| /// them twice and would prefer a way to select which one to use from a single definition
 | ||||
| ///
 | ||||
| /// This can be done using the `Base[Offset]` syntax when specifying the register's address.
 | ||||
| ///
 | ||||
| /// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
 | ||||
| /// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
 | ||||
| /// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
 | ||||
| /// into code:
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// // Type used to identify the base.
 | ||||
| /// pub(crate) struct CpuCtlBase;
 | ||||
| ///
 | ||||
| /// // ZST describing `CPU0`.
 | ||||
| /// struct Cpu0;
 | ||||
| /// impl RegisterBase<CpuCtlBase> for Cpu0 {
 | ||||
| ///     const BASE: usize = 0x100;
 | ||||
| /// }
 | ||||
| /// // Singleton of `CPU0` used to identify it.
 | ||||
| /// const CPU0: Cpu0 = Cpu0;
 | ||||
| ///
 | ||||
| /// // ZST describing `CPU1`.
 | ||||
| /// struct Cpu1;
 | ||||
| /// impl RegisterBase<CpuCtlBase> for Cpu1 {
 | ||||
| ///     const BASE: usize = 0x200;
 | ||||
| /// }
 | ||||
| /// // Singleton of `CPU1` used to identify it.
 | ||||
| /// const CPU1: Cpu1 = Cpu1;
 | ||||
| ///
 | ||||
| /// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
 | ||||
| /// register!(CPU_CTL @ CpuCtlBase[0x10], "CPU core control" {
 | ||||
| ///     0:0     start as bool, "Start the CPU core";
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// // The `read`, `write` and `alter` methods of relative registers take an extra `base` argument
 | ||||
| /// // that is used to resolve its final address by adding its `BASE` to the offset of the
 | ||||
| /// // register.
 | ||||
| ///
 | ||||
| /// // Start `CPU0`.
 | ||||
| /// CPU_CTL::alter(bar, &CPU0, |r| r.set_start(true));
 | ||||
| ///
 | ||||
| /// // Start `CPU1`.
 | ||||
| /// CPU_CTL::alter(bar, &CPU1, |r| r.set_start(true));
 | ||||
| ///
 | ||||
| /// // Aliases can also be defined for relative register.
 | ||||
| /// register!(CPU_CTL_ALIAS => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
 | ||||
| ///     1:1     alias_start as bool, "Start the aliased CPU core";
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// // Start the aliased `CPU0`.
 | ||||
| /// CPU_CTL_ALIAS::alter(bar, &CPU0, |r| r.set_alias_start(true));
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// ## Arrays of registers
 | ||||
| ///
 | ||||
| /// Some I/O areas contain consecutive values that can be interpreted in the same way. These areas
 | ||||
| /// can be defined as an array of identical registers, allowing them to be accessed by index with
 | ||||
| /// compile-time or runtime bound checking. Simply define their address as `Address[Size]`, and add
 | ||||
| /// an `idx` parameter to their `read`, `write` and `alter` methods:
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// # fn no_run() -> Result<(), Error> {
 | ||||
| /// # fn get_scratch_idx() -> usize {
 | ||||
| /// #   0x15
 | ||||
| /// # }
 | ||||
| /// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
 | ||||
| /// register!(SCRATCH @ 0x00000080[64], "Scratch registers" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// // Read scratch register 0, i.e. I/O address `0x80`.
 | ||||
| /// let scratch_0 = SCRATCH::read(bar, 0).value();
 | ||||
| /// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
 | ||||
| /// let scratch_15 = SCRATCH::read(bar, 15).value();
 | ||||
| ///
 | ||||
| /// // This is out of bounds and won't build.
 | ||||
| /// // let scratch_128 = SCRATCH::read(bar, 128).value();
 | ||||
| ///
 | ||||
| /// // Runtime-obtained array index.
 | ||||
| /// let scratch_idx = get_scratch_idx();
 | ||||
| /// // Access on a runtime index returns an error if it is out-of-bounds.
 | ||||
| /// let some_scratch = SCRATCH::try_read(bar, scratch_idx)?.value();
 | ||||
| ///
 | ||||
| /// // Alias to a particular register in an array.
 | ||||
| /// // Here `SCRATCH[8]` is used to convey the firmware exit code.
 | ||||
| /// register!(FIRMWARE_STATUS => SCRATCH[8], "Firmware exit status code" {
 | ||||
| ///     7:0     status as u8;
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// let status = FIRMWARE_STATUS::read(bar).status();
 | ||||
| ///
 | ||||
| /// // Non-contiguous register arrays can be defined by adding a stride parameter.
 | ||||
| /// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
 | ||||
| /// // registers of the two declarations below are interleaved.
 | ||||
| /// register!(SCRATCH_INTERLEAVED_0 @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| /// register!(SCRATCH_INTERLEAVED_1 @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| /// # Ok(())
 | ||||
| /// # }
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// ## Relative arrays of registers
 | ||||
| ///
 | ||||
| /// Combining the two features described in the sections above, arrays of registers accessible from
 | ||||
| /// a base can also be defined:
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// # fn no_run() -> Result<(), Error> {
 | ||||
| /// # fn get_scratch_idx() -> usize {
 | ||||
| /// #   0x15
 | ||||
| /// # }
 | ||||
| /// // Type used as parameter of `RegisterBase` to specify the base.
 | ||||
| /// pub(crate) struct CpuCtlBase;
 | ||||
| ///
 | ||||
| /// // ZST describing `CPU0`.
 | ||||
| /// struct Cpu0;
 | ||||
| /// impl RegisterBase<CpuCtlBase> for Cpu0 {
 | ||||
| ///     const BASE: usize = 0x100;
 | ||||
| /// }
 | ||||
| /// // Singleton of `CPU0` used to identify it.
 | ||||
| /// const CPU0: Cpu0 = Cpu0;
 | ||||
| ///
 | ||||
| /// // ZST describing `CPU1`.
 | ||||
| /// struct Cpu1;
 | ||||
| /// impl RegisterBase<CpuCtlBase> for Cpu1 {
 | ||||
| ///     const BASE: usize = 0x200;
 | ||||
| /// }
 | ||||
| /// // Singleton of `CPU1` used to identify it.
 | ||||
| /// const CPU1: Cpu1 = Cpu1;
 | ||||
| ///
 | ||||
| /// // 64 per-cpu scratch registers, arranged as an contiguous array.
 | ||||
| /// register!(CPU_SCRATCH @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// let cpu0_scratch_0 = CPU_SCRATCH::read(bar, &Cpu0, 0).value();
 | ||||
| /// let cpu1_scratch_15 = CPU_SCRATCH::read(bar, &Cpu1, 15).value();
 | ||||
| ///
 | ||||
| /// // This won't build.
 | ||||
| /// // let cpu0_scratch_128 = CPU_SCRATCH::read(bar, &Cpu0, 128).value();
 | ||||
| ///
 | ||||
| /// // Runtime-obtained array index.
 | ||||
| /// let scratch_idx = get_scratch_idx();
 | ||||
| /// // Access on a runtime value returns an error if it is out-of-bounds.
 | ||||
| /// let cpu0_some_scratch = CPU_SCRATCH::try_read(bar, &Cpu0, scratch_idx)?.value();
 | ||||
| ///
 | ||||
| /// // `SCRATCH[8]` is used to convey the firmware exit code.
 | ||||
| /// register!(CPU_FIRMWARE_STATUS => CpuCtlBase[CPU_SCRATCH[8]],
 | ||||
| ///     "Per-CPU firmware exit status code" {
 | ||||
| ///     7:0     status as u8;
 | ||||
| /// });
 | ||||
| ///
 | ||||
| /// let cpu0_status = CPU_FIRMWARE_STATUS::read(bar, &Cpu0).status();
 | ||||
| ///
 | ||||
| /// // Non-contiguous register arrays can be defined by adding a stride parameter.
 | ||||
| /// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
 | ||||
| /// // registers of the two declarations below are interleaved.
 | ||||
| /// register!(CPU_SCRATCH_INTERLEAVED_0 @ CpuCtlBase[0x00000d00[16 ; 8]],
 | ||||
| ///           "Scratch registers bank 0" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| /// register!(CPU_SCRATCH_INTERLEAVED_1 @ CpuCtlBase[0x00000d04[16 ; 8]],
 | ||||
| ///           "Scratch registers bank 1" {
 | ||||
| ///     31:0    value as u32;
 | ||||
| /// });
 | ||||
| /// # Ok(())
 | ||||
| /// # }
 | ||||
| /// ```
 | ||||
| macro_rules! register { | ||||
|     // Creates a register at a fixed offset of the MMIO space.
 | ||||
|     ( | ||||
|         $name:ident @ $offset:literal $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!(@common $name @ $offset $(, $comment)?); | ||||
|         register!(@field_accessors $name { $($fields)* }); | ||||
|         register!(@io $name @ $offset); | ||||
|     ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_fixed $name @ $offset); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates a alias register of fixed offset register `alias` with its own fields.
 | ||||
|     ( | ||||
|         $name:ident => $alias:ident $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!(@common $name @ $alias::OFFSET $(, $comment)?); | ||||
|         register!(@field_accessors $name { $($fields)* }); | ||||
|         register!(@io $name @ $alias::OFFSET); | ||||
|     // Creates an alias register of fixed offset register `alias` with its own fields.
 | ||||
|     ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_fixed $name @ $alias::OFFSET); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates a register at a relative offset from a base address.
 | ||||
|     ( | ||||
|         $name:ident @ + $offset:literal $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!(@common $name @ $offset $(, $comment)?); | ||||
|         register!(@field_accessors $name { $($fields)* }); | ||||
|         register!(@io$name @ + $offset); | ||||
|     // Creates a register at a relative offset from a base address provider.
 | ||||
|     ($name:ident @ $base:ty [ $offset:literal ] $(, $comment:literal)? { $($fields:tt)* } ) => { | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_relative $name @ $base [ $offset ]); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates a alias register of relative offset register `alias` with its own fields.
 | ||||
|     // Creates an alias register of relative offset register `alias` with its own fields.
 | ||||
|     ($name:ident => $base:ty [ $alias:ident ] $(, $comment:literal)? { $($fields:tt)* }) => { | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_relative $name @ $base [ $alias::OFFSET ]); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates an array of registers at a fixed offset of the MMIO space.
 | ||||
|     ( | ||||
|         $name:ident => + $alias:ident $(, $comment:literal)? { | ||||
|         $name:ident @ $offset:literal [ $size:expr ; $stride:expr ] $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!(@common $name @ $alias::OFFSET $(, $comment)?); | ||||
|         register!(@field_accessors $name { $($fields)* }); | ||||
|         register!(@io $name @ + $alias::OFFSET); | ||||
|         static_assert!(::core::mem::size_of::<u32>() <= $stride); | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_array $name @ $offset [ $size ; $stride ]); | ||||
|     }; | ||||
| 
 | ||||
|     // Shortcut for contiguous array of registers (stride == size of element).
 | ||||
|     ( | ||||
|         $name:ident @ $offset:literal [ $size:expr ] $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!($name @ $offset [ $size ; ::core::mem::size_of::<u32>() ] $(, $comment)? { | ||||
|             $($fields)* | ||||
|         } ); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates an array of registers at a relative offset from a base address provider.
 | ||||
|     ( | ||||
|         $name:ident @ $base:ty [ $offset:literal [ $size:expr ; $stride:expr ] ] | ||||
|             $(, $comment:literal)? { $($fields:tt)* } | ||||
|     ) => { | ||||
|         static_assert!(::core::mem::size_of::<u32>() <= $stride); | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_relative_array $name @ $base [ $offset [ $size ; $stride ] ]); | ||||
|     }; | ||||
| 
 | ||||
|     // Shortcut for contiguous array of relative registers (stride == size of element).
 | ||||
|     ( | ||||
|         $name:ident @ $base:ty [ $offset:literal [ $size:expr ] ] $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         register!($name @ $base [ $offset [ $size ; ::core::mem::size_of::<u32>() ] ] | ||||
|             $(, $comment)? { $($fields)* } ); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates an alias of register `idx` of relative array of registers `alias` with its own
 | ||||
|     // fields.
 | ||||
|     ( | ||||
|         $name:ident => $base:ty [ $alias:ident [ $idx:expr ] ] $(, $comment:literal)? { | ||||
|             $($fields:tt)* | ||||
|         } | ||||
|     ) => { | ||||
|         static_assert!($idx < $alias::SIZE); | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_relative $name @ $base [ $alias::OFFSET + $idx * $alias::STRIDE ] ); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates an alias of register `idx` of array of registers `alias` with its own fields.
 | ||||
|     // This rule belongs to the (non-relative) register arrays set, but needs to be put last
 | ||||
|     // to avoid it being interpreted in place of the relative register array alias rule.
 | ||||
|     ($name:ident => $alias:ident [ $idx:expr ] $(, $comment:literal)? { $($fields:tt)* }) => { | ||||
|         static_assert!($idx < $alias::SIZE); | ||||
|         register!(@core $name $(, $comment)? { $($fields)* } ); | ||||
|         register!(@io_fixed $name @ $alias::OFFSET + $idx * $alias::STRIDE ); | ||||
|     }; | ||||
| 
 | ||||
|     // All rules below are helpers.
 | ||||
| 
 | ||||
|     // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`,
 | ||||
|     // and conversion to regular `u32`).
 | ||||
|     (@common $name:ident @ $offset:expr $(, $comment:literal)?) => { | ||||
|     // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`,
 | ||||
|     // `Default`, `BitOr`, and conversion to the value type) and field accessor methods.
 | ||||
|     (@core $name:ident $(, $comment:literal)? { $($fields:tt)* }) => { | ||||
|         $( | ||||
|         #[doc=$comment] | ||||
|         )? | ||||
|         #[repr(transparent)] | ||||
|         #[derive(Clone, Copy, Default)] | ||||
|         #[derive(Clone, Copy)] | ||||
|         pub(crate) struct $name(u32); | ||||
| 
 | ||||
|         #[allow(dead_code)] | ||||
|         impl $name { | ||||
|             pub(crate) const OFFSET: usize = $offset; | ||||
|         } | ||||
| 
 | ||||
|         // TODO[REGA]: display the raw hex value, then the value of all the fields. This requires
 | ||||
|         // matching the fields, which will complexify the syntax considerably...
 | ||||
|         impl ::core::fmt::Debug for $name { | ||||
|             fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | ||||
|                 f.debug_tuple(stringify!($name)) | ||||
|                     .field(&format_args!("0x{0:x}", &self.0)) | ||||
|                     .finish() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         impl ::core::ops::BitOr for $name { | ||||
|             type Output = Self; | ||||
| 
 | ||||
|  | @ -170,6 +394,34 @@ fn from(reg: $name) -> u32 { | |||
|                 reg.0 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         register!(@fields_dispatcher $name { $($fields)* }); | ||||
|     }; | ||||
| 
 | ||||
|     // Captures the fields and passes them to all the implementers that require field information.
 | ||||
|     //
 | ||||
|     // Used to simplify the matching rules for implementers, so they don't need to match the entire
 | ||||
|     // complex fields rule even though they only make use of part of it.
 | ||||
|     (@fields_dispatcher $name:ident { | ||||
|         $($hi:tt:$lo:tt $field:ident as $type:tt | ||||
|             $(?=> $try_into_type:ty)? | ||||
|             $(=> $into_type:ty)? | ||||
|             $(, $comment:literal)? | ||||
|         ; | ||||
|         )* | ||||
|     } | ||||
|     ) => { | ||||
|         register!(@field_accessors $name { | ||||
|             $( | ||||
|                 $hi:$lo $field as $type | ||||
|                 $(?=> $try_into_type)? | ||||
|                 $(=> $into_type)? | ||||
|                 $(, $comment)? | ||||
|             ; | ||||
|             )* | ||||
|         }); | ||||
|         register!(@debug $name { $($field;)* }); | ||||
|         register!(@default $name { $($field;)* }); | ||||
|     }; | ||||
| 
 | ||||
|     // Defines all the field getter/methods methods for `$name`.
 | ||||
|  | @ -228,7 +480,7 @@ impl $name { | |||
|             $(, $comment:literal)?; | ||||
|     ) => { | ||||
|         register!( | ||||
|             @leaf_accessor $name $hi:$lo $field as bool | ||||
|             @leaf_accessor $name $hi:$lo $field | ||||
|             { |f| <$into_type>::from(if f != 0 { true } else { false }) } | ||||
|             $into_type => $into_type $(, $comment)?; | ||||
|         ); | ||||
|  | @ -246,7 +498,7 @@ impl $name { | |||
|         @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt ?=> $try_into_type:ty | ||||
|             $(, $comment:literal)?; | ||||
|     ) => { | ||||
|         register!(@leaf_accessor $name $hi:$lo $field as $type | ||||
|         register!(@leaf_accessor $name $hi:$lo $field | ||||
|             { |f| <$try_into_type>::try_from(f as $type) } $try_into_type => | ||||
|             ::core::result::Result< | ||||
|                 $try_into_type, | ||||
|  | @ -260,11 +512,11 @@ impl $name { | |||
|         @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt => $into_type:ty | ||||
|             $(, $comment:literal)?; | ||||
|     ) => { | ||||
|         register!(@leaf_accessor $name $hi:$lo $field as $type | ||||
|         register!(@leaf_accessor $name $hi:$lo $field | ||||
|             { |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;); | ||||
|     }; | ||||
| 
 | ||||
|     // Shortcut for fields defined as non-`bool` without the `=>` or `?=>` syntax.
 | ||||
|     // Shortcut for non-boolean fields defined without the `=>` or `?=>` syntax.
 | ||||
|     ( | ||||
|         @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt | ||||
|             $(, $comment:literal)?; | ||||
|  | @ -274,11 +526,11 @@ impl $name { | |||
| 
 | ||||
|     // Generates the accessor methods for a single field.
 | ||||
|     ( | ||||
|         @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:ty | ||||
|         @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident | ||||
|             { $process:expr } $to_type:ty => $res_type:ty $(, $comment:literal)?; | ||||
|     ) => { | ||||
|         ::kernel::macros::paste!( | ||||
|         const [<$field:upper>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; | ||||
|         const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; | ||||
|         const [<$field:upper _MASK>]: u32 = ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1); | ||||
|         const [<$field:upper _SHIFT>]: u32 = Self::[<$field:upper _MASK>].trailing_zeros(); | ||||
|         ); | ||||
|  | @ -287,7 +539,7 @@ impl $name { | |||
|         #[doc="Returns the value of this field:"] | ||||
|         #[doc=$comment] | ||||
|         )? | ||||
|         #[inline] | ||||
|         #[inline(always)] | ||||
|         pub(crate) fn $field(self) -> $res_type { | ||||
|             ::kernel::macros::paste!( | ||||
|             const MASK: u32 = $name::[<$field:upper _MASK>]; | ||||
|  | @ -303,7 +555,7 @@ pub(crate) fn $field(self) -> $res_type { | |||
|         #[doc="Sets the value of this field:"] | ||||
|         #[doc=$comment] | ||||
|         )? | ||||
|         #[inline] | ||||
|         #[inline(always)] | ||||
|         pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { | ||||
|             const MASK: u32 = $name::[<$field:upper _MASK>]; | ||||
|             const SHIFT: u32 = $name::[<$field:upper _SHIFT>]; | ||||
|  | @ -315,25 +567,64 @@ pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { | |||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     // Creates the IO accessors for a fixed offset register.
 | ||||
|     (@io $name:ident @ $offset:expr) => { | ||||
|     // Generates the `Debug` implementation for `$name`.
 | ||||
|     (@debug $name:ident { $($field:ident;)* }) => { | ||||
|         impl ::core::fmt::Debug for $name { | ||||
|             fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | ||||
|                 f.debug_struct(stringify!($name)) | ||||
|                     .field("<raw>", &format_args!("{:#x}", &self.0)) | ||||
|                 $( | ||||
|                     .field(stringify!($field), &self.$field()) | ||||
|                 )* | ||||
|                     .finish() | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Generates the `Default` implementation for `$name`.
 | ||||
|     (@default $name:ident { $($field:ident;)* }) => { | ||||
|         /// Returns a value for the register where all fields are set to their default value.
 | ||||
|         impl ::core::default::Default for $name { | ||||
|             fn default() -> Self { | ||||
|                 #[allow(unused_mut)] | ||||
|                 let mut value = Self(Default::default()); | ||||
| 
 | ||||
|                 ::kernel::macros::paste!( | ||||
|                 $( | ||||
|                 value.[<set_ $field>](Default::default()); | ||||
|                 )* | ||||
|                 ); | ||||
| 
 | ||||
|                 value | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Generates the IO accessors for a fixed offset register.
 | ||||
|     (@io_fixed $name:ident @ $offset:expr) => { | ||||
|         #[allow(dead_code)] | ||||
|         impl $name { | ||||
|             #[inline] | ||||
|             pub(crate) const OFFSET: usize = $offset; | ||||
| 
 | ||||
|             /// Read the register from its address in `io`.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 Self(io.read32($offset)) | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             /// Write the value contained in `self` to the register address in `io`.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 io.write32(self.0, $offset) | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             /// Read the register from its address in `io` and run `f` on its value to obtain a new
 | ||||
|             /// value to write back.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn alter<const SIZE: usize, T, F>( | ||||
|                 io: &T, | ||||
|                 f: F, | ||||
|  | @ -347,76 +638,322 @@ pub(crate) fn alter<const SIZE: usize, T, F>( | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Create the IO accessors for a relative offset register.
 | ||||
|     (@io $name:ident @ + $offset:literal) => { | ||||
|     // Generates the IO accessors for a relative offset register.
 | ||||
|     (@io_relative $name:ident @ $base:ty [ $offset:expr ]) => { | ||||
|         #[allow(dead_code)] | ||||
|         impl $name { | ||||
|             #[inline] | ||||
|             pub(crate) fn read<const SIZE: usize, T>( | ||||
|             pub(crate) const OFFSET: usize = $offset; | ||||
| 
 | ||||
|             /// Read the register from `io`, using the base address provided by `base` and adding
 | ||||
|             /// the register's offset to it.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn read<const SIZE: usize, T, B>( | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|                 #[allow(unused_variables)] | ||||
|                 base: &B, | ||||
|             ) -> Self where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 Self(io.read32(base + $offset)) | ||||
|                 const OFFSET: usize = $name::OFFSET; | ||||
| 
 | ||||
|                 let value = io.read32( | ||||
|                     <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET | ||||
|                 ); | ||||
| 
 | ||||
|                 Self(value) | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             pub(crate) fn write<const SIZE: usize, T>( | ||||
|             /// Write the value contained in `self` to `io`, using the base address provided by
 | ||||
|             /// `base` and adding the register's offset to it.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn write<const SIZE: usize, T, B>( | ||||
|                 self, | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|                 #[allow(unused_variables)] | ||||
|                 base: &B, | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 io.write32(self.0, base + $offset) | ||||
|                 const OFFSET: usize = $name::OFFSET; | ||||
| 
 | ||||
|                 io.write32( | ||||
|                     self.0, | ||||
|                     <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             pub(crate) fn alter<const SIZE: usize, T, F>( | ||||
|             /// Read the register from `io`, using the base address provided by `base` and adding
 | ||||
|             /// the register's offset to it, then run `f` on its value to obtain a new value to
 | ||||
|             /// write back.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn alter<const SIZE: usize, T, B, F>( | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|                 base: &B, | ||||
|                 f: F, | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|                 F: ::core::ops::FnOnce(Self) -> Self, | ||||
|             { | ||||
|                 let reg = f(Self::read(io, base)); | ||||
|                 reg.write(io, base); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|             #[inline] | ||||
|             pub(crate) fn try_read<const SIZE: usize, T>( | ||||
|     // Generates the IO accessors for an array of registers.
 | ||||
|     (@io_array $name:ident @ $offset:literal [ $size:expr ; $stride:expr ]) => { | ||||
|         #[allow(dead_code)] | ||||
|         impl $name { | ||||
|             pub(crate) const OFFSET: usize = $offset; | ||||
|             pub(crate) const SIZE: usize = $size; | ||||
|             pub(crate) const STRIDE: usize = $stride; | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from its address in `io`.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn read<const SIZE: usize, T>( | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|             ) -> ::kernel::error::Result<Self> where | ||||
|                 idx: usize, | ||||
|             ) -> Self where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 io.try_read32(base + $offset).map(Self) | ||||
|                 build_assert!(idx < Self::SIZE); | ||||
| 
 | ||||
|                 let offset = Self::OFFSET + (idx * Self::STRIDE); | ||||
|                 let value = io.read32(offset); | ||||
| 
 | ||||
|                 Self(value) | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             pub(crate) fn try_write<const SIZE: usize, T>( | ||||
|             /// Write the value contained in `self` to the array register with index `idx` in `io`.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn write<const SIZE: usize, T>( | ||||
|                 self, | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|             ) -> ::kernel::error::Result<()> where | ||||
|                 idx: usize | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 io.try_write32(self.0, base + $offset) | ||||
|                 build_assert!(idx < Self::SIZE); | ||||
| 
 | ||||
|                 let offset = Self::OFFSET + (idx * Self::STRIDE); | ||||
| 
 | ||||
|                 io.write32(self.0, offset); | ||||
|             } | ||||
| 
 | ||||
|             #[inline] | ||||
|             pub(crate) fn try_alter<const SIZE: usize, T, F>( | ||||
|             /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
 | ||||
|             /// new value to write back.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn alter<const SIZE: usize, T, F>( | ||||
|                 io: &T, | ||||
|                 base: usize, | ||||
|                 idx: usize, | ||||
|                 f: F, | ||||
|             ) -> ::kernel::error::Result<()> where | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 F: ::core::ops::FnOnce(Self) -> Self, | ||||
|             { | ||||
|                 let reg = f(Self::try_read(io, base)?); | ||||
|                 reg.try_write(io, base) | ||||
|                 let reg = f(Self::read(io, idx)); | ||||
|                 reg.write(io, idx); | ||||
|             } | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from its address in `io`.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_read<const SIZE: usize, T>( | ||||
|                 io: &T, | ||||
|                 idx: usize, | ||||
|             ) -> ::kernel::error::Result<Self> where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(Self::read(io, idx)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// Write the value contained in `self` to the array register with index `idx` in `io`.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_write<const SIZE: usize, T>( | ||||
|                 self, | ||||
|                 io: &T, | ||||
|                 idx: usize, | ||||
|             ) -> ::kernel::error::Result where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(self.write(io, idx)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
 | ||||
|             /// new value to write back.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_alter<const SIZE: usize, T, F>( | ||||
|                 io: &T, | ||||
|                 idx: usize, | ||||
|                 f: F, | ||||
|             ) -> ::kernel::error::Result where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 F: ::core::ops::FnOnce(Self) -> Self, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(Self::alter(io, idx, f)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Generates the IO accessors for an array of relative registers.
 | ||||
|     ( | ||||
|         @io_relative_array $name:ident @ $base:ty | ||||
|             [ $offset:literal [ $size:expr ; $stride:expr ] ] | ||||
|     ) => { | ||||
|         #[allow(dead_code)] | ||||
|         impl $name { | ||||
|             pub(crate) const OFFSET: usize = $offset; | ||||
|             pub(crate) const SIZE: usize = $size; | ||||
|             pub(crate) const STRIDE: usize = $stride; | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from `io`, using the base address provided
 | ||||
|             /// by `base` and adding the register's offset to it.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn read<const SIZE: usize, T, B>( | ||||
|                 io: &T, | ||||
|                 #[allow(unused_variables)] | ||||
|                 base: &B, | ||||
|                 idx: usize, | ||||
|             ) -> Self where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 build_assert!(idx < Self::SIZE); | ||||
| 
 | ||||
|                 let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE + | ||||
|                     Self::OFFSET + (idx * Self::STRIDE); | ||||
|                 let value = io.read32(offset); | ||||
| 
 | ||||
|                 Self(value) | ||||
|             } | ||||
| 
 | ||||
|             /// Write the value contained in `self` to `io`, using the base address provided by
 | ||||
|             /// `base` and adding the offset of array register `idx` to it.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn write<const SIZE: usize, T, B>( | ||||
|                 self, | ||||
|                 io: &T, | ||||
|                 #[allow(unused_variables)] | ||||
|                 base: &B, | ||||
|                 idx: usize | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 build_assert!(idx < Self::SIZE); | ||||
| 
 | ||||
|                 let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE + | ||||
|                     Self::OFFSET + (idx * Self::STRIDE); | ||||
| 
 | ||||
|                 io.write32(self.0, offset); | ||||
|             } | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from `io`, using the base address provided
 | ||||
|             /// by `base` and adding the register's offset to it, then run `f` on its value to
 | ||||
|             /// obtain a new value to write back.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn alter<const SIZE: usize, T, B, F>( | ||||
|                 io: &T, | ||||
|                 base: &B, | ||||
|                 idx: usize, | ||||
|                 f: F, | ||||
|             ) where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|                 F: ::core::ops::FnOnce(Self) -> Self, | ||||
|             { | ||||
|                 let reg = f(Self::read(io, base, idx)); | ||||
|                 reg.write(io, base, idx); | ||||
|             } | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from `io`, using the base address provided
 | ||||
|             /// by `base` and adding the register's offset to it.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_read<const SIZE: usize, T, B>( | ||||
|                 io: &T, | ||||
|                 base: &B, | ||||
|                 idx: usize, | ||||
|             ) -> ::kernel::error::Result<Self> where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(Self::read(io, base, idx)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// Write the value contained in `self` to `io`, using the base address provided by
 | ||||
|             /// `base` and adding the offset of array register `idx` to it.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_write<const SIZE: usize, T, B>( | ||||
|                 self, | ||||
|                 io: &T, | ||||
|                 base: &B, | ||||
|                 idx: usize, | ||||
|             ) -> ::kernel::error::Result where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(self.write(io, base, idx)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /// Read the array register at index `idx` from `io`, using the base address provided
 | ||||
|             /// by `base` and adding the register's offset to it, then run `f` on its value to
 | ||||
|             /// obtain a new value to write back.
 | ||||
|             ///
 | ||||
|             /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
 | ||||
|             /// access was out-of-bounds.
 | ||||
|             #[inline(always)] | ||||
|             pub(crate) fn try_alter<const SIZE: usize, T, B, F>( | ||||
|                 io: &T, | ||||
|                 base: &B, | ||||
|                 idx: usize, | ||||
|                 f: F, | ||||
|             ) -> ::kernel::error::Result where | ||||
|                 T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, | ||||
|                 B: crate::regs::macros::RegisterBase<$base>, | ||||
|                 F: ::core::ops::FnOnce(Self) -> Self, | ||||
|             { | ||||
|                 if idx < Self::SIZE { | ||||
|                     Ok(Self::alter(io, base, idx, f)) | ||||
|                 } else { | ||||
|                     Err(EINVAL) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  |  | |||
|  | @ -3,26 +3,6 @@ | |||
| use kernel::prelude::*; | ||||
| use kernel::time::{Delta, Instant, Monotonic}; | ||||
| 
 | ||||
| pub(crate) const fn to_lowercase_bytes<const N: usize>(s: &str) -> [u8; N] { | ||||
|     let src = s.as_bytes(); | ||||
|     let mut dst = [0; N]; | ||||
|     let mut i = 0; | ||||
| 
 | ||||
|     while i < src.len() && i < N { | ||||
|         dst[i] = (src[i] as char).to_ascii_lowercase() as u8; | ||||
|         i += 1; | ||||
|     } | ||||
| 
 | ||||
|     dst | ||||
| } | ||||
| 
 | ||||
| pub(crate) const fn const_bytes_to_str(bytes: &[u8]) -> &str { | ||||
|     match core::str::from_utf8(bytes) { | ||||
|         Ok(string) => string, | ||||
|         Err(_) => kernel::build_error!("Bytes are not valid UTF-8."), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Wait until `cond` is true or `timeout` elapsed.
 | ||||
| ///
 | ||||
| /// When `cond` evaluates to `Some`, its return value is returned.
 | ||||
|  |  | |||
|  | @ -8,8 +8,8 @@ | |||
| use core::convert::TryFrom; | ||||
| use kernel::device; | ||||
| use kernel::error::Result; | ||||
| use kernel::pci; | ||||
| use kernel::prelude::*; | ||||
| use kernel::types::ARef; | ||||
| 
 | ||||
| /// The offset of the VBIOS ROM in the BAR0 space.
 | ||||
| const ROM_OFFSET: usize = 0x300000; | ||||
|  | @ -31,7 +31,7 @@ | |||
| 
 | ||||
| /// Vbios Reader for constructing the VBIOS data.
 | ||||
| struct VbiosIterator<'a> { | ||||
|     pdev: &'a pci::Device, | ||||
|     dev: &'a device::Device, | ||||
|     bar0: &'a Bar0, | ||||
|     /// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference
 | ||||
|     /// or copying into other data structures. It is the entire scanned contents of the VBIOS which
 | ||||
|  | @ -46,9 +46,9 @@ struct VbiosIterator<'a> { | |||
| } | ||||
| 
 | ||||
| impl<'a> VbiosIterator<'a> { | ||||
|     fn new(pdev: &'a pci::Device, bar0: &'a Bar0) -> Result<Self> { | ||||
|     fn new(dev: &'a device::Device, bar0: &'a Bar0) -> Result<Self> { | ||||
|         Ok(Self { | ||||
|             pdev, | ||||
|             dev, | ||||
|             bar0, | ||||
|             data: KVec::new(), | ||||
|             current_offset: 0, | ||||
|  | @ -64,7 +64,7 @@ fn read_more(&mut self, len: usize) -> Result { | |||
|         // Ensure length is a multiple of 4 for 32-bit reads
 | ||||
|         if len % core::mem::size_of::<u32>() != 0 { | ||||
|             dev_err!( | ||||
|                 self.pdev.as_ref(), | ||||
|                 self.dev, | ||||
|                 "VBIOS read length {} is not a multiple of 4\n", | ||||
|                 len | ||||
|             ); | ||||
|  | @ -89,7 +89,7 @@ fn read_more(&mut self, len: usize) -> Result { | |||
|     /// Read bytes at a specific offset, filling any gap.
 | ||||
|     fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result { | ||||
|         if offset > BIOS_MAX_SCAN_LEN { | ||||
|             dev_err!(self.pdev.as_ref(), "Error: exceeded BIOS scan limit.\n"); | ||||
|             dev_err!(self.dev, "Error: exceeded BIOS scan limit.\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -115,7 +115,7 @@ fn read_bios_image_at_offset( | |||
|         if offset + len > data_len { | ||||
|             self.read_more_at_offset(offset, len).inspect_err(|e| { | ||||
|                 dev_err!( | ||||
|                     self.pdev.as_ref(), | ||||
|                     self.dev, | ||||
|                     "Failed to read more at offset {:#x}: {:?}\n", | ||||
|                     offset, | ||||
|                     e | ||||
|  | @ -123,9 +123,9 @@ fn read_bios_image_at_offset( | |||
|             })?; | ||||
|         } | ||||
| 
 | ||||
|         BiosImage::new(self.pdev, &self.data[offset..offset + len]).inspect_err(|err| { | ||||
|         BiosImage::new(self.dev, &self.data[offset..offset + len]).inspect_err(|err| { | ||||
|             dev_err!( | ||||
|                 self.pdev.as_ref(), | ||||
|                 self.dev, | ||||
|                 "Failed to {} at offset {:#x}: {:?}\n", | ||||
|                 context, | ||||
|                 offset, | ||||
|  | @ -146,10 +146,7 @@ fn next(&mut self) -> Option<Self::Item> { | |||
|         } | ||||
| 
 | ||||
|         if self.current_offset > BIOS_MAX_SCAN_LEN { | ||||
|             dev_err!( | ||||
|                 self.pdev.as_ref(), | ||||
|                 "Error: exceeded BIOS scan limit, stopping scan\n" | ||||
|             ); | ||||
|             dev_err!(self.dev, "Error: exceeded BIOS scan limit, stopping scan\n"); | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|  | @ -192,18 +189,18 @@ impl Vbios { | |||
|     /// Probe for VBIOS extraction.
 | ||||
|     ///
 | ||||
|     /// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore.
 | ||||
|     pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> { | ||||
|     pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { | ||||
|         // Images to extract from iteration
 | ||||
|         let mut pci_at_image: Option<PciAtBiosImage> = None; | ||||
|         let mut first_fwsec_image: Option<FwSecBiosBuilder> = None; | ||||
|         let mut second_fwsec_image: Option<FwSecBiosBuilder> = None; | ||||
| 
 | ||||
|         // Parse all VBIOS images in the ROM
 | ||||
|         for image_result in VbiosIterator::new(pdev, bar0)? { | ||||
|         for image_result in VbiosIterator::new(dev, bar0)? { | ||||
|             let full_image = image_result?; | ||||
| 
 | ||||
|             dev_dbg!( | ||||
|                 pdev.as_ref(), | ||||
|                 dev, | ||||
|                 "Found BIOS image: size: {:#x}, type: {}, last: {}\n", | ||||
|                 full_image.image_size_bytes(), | ||||
|                 full_image.image_type_str(), | ||||
|  | @ -234,14 +231,14 @@ pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> { | |||
|             (second_fwsec_image, first_fwsec_image, pci_at_image) | ||||
|         { | ||||
|             second | ||||
|                 .setup_falcon_data(pdev, &pci_at, &first) | ||||
|                 .inspect_err(|e| dev_err!(pdev.as_ref(), "Falcon data setup failed: {:?}\n", e))?; | ||||
|                 .setup_falcon_data(&pci_at, &first) | ||||
|                 .inspect_err(|e| dev_err!(dev, "Falcon data setup failed: {:?}\n", e))?; | ||||
|             Ok(Vbios { | ||||
|                 fwsec_image: second.build(pdev)?, | ||||
|                 fwsec_image: second.build()?, | ||||
|             }) | ||||
|         } else { | ||||
|             dev_err!( | ||||
|                 pdev.as_ref(), | ||||
|                 dev, | ||||
|                 "Missing required images for falcon data setup, skipping\n" | ||||
|             ); | ||||
|             Err(EINVAL) | ||||
|  | @ -284,9 +281,9 @@ struct PcirStruct { | |||
| } | ||||
| 
 | ||||
| impl PcirStruct { | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { | ||||
|         if data.len() < core::mem::size_of::<PcirStruct>() { | ||||
|             dev_err!(pdev.as_ref(), "Not enough data for PcirStruct\n"); | ||||
|             dev_err!(dev, "Not enough data for PcirStruct\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -295,11 +292,7 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
| 
 | ||||
|         // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
 | ||||
|         if &signature != b"PCIR" && &signature != b"NPDS" { | ||||
|             dev_err!( | ||||
|                 pdev.as_ref(), | ||||
|                 "Invalid signature for PcirStruct: {:?}\n", | ||||
|                 signature | ||||
|             ); | ||||
|             dev_err!(dev, "Invalid signature for PcirStruct: {:?}\n", signature); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -308,7 +301,7 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
| 
 | ||||
|         let image_len = u16::from_le_bytes([data[16], data[17]]); | ||||
|         if image_len == 0 { | ||||
|             dev_err!(pdev.as_ref(), "Invalid image length: 0\n"); | ||||
|             dev_err!(dev, "Invalid image length: 0\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -345,7 +338,7 @@ fn image_size_bytes(&self) -> usize { | |||
| /// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the
 | ||||
| /// [`FwSecBiosImage`].
 | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[expect(dead_code)] | ||||
| #[repr(C)] | ||||
| struct BitHeader { | ||||
|     /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
 | ||||
|     id: u16, | ||||
|  | @ -365,7 +358,7 @@ struct BitHeader { | |||
| 
 | ||||
| impl BitHeader { | ||||
|     fn new(data: &[u8]) -> Result<Self> { | ||||
|         if data.len() < 12 { | ||||
|         if data.len() < core::mem::size_of::<Self>() { | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -467,7 +460,7 @@ struct PciRomHeader { | |||
| } | ||||
| 
 | ||||
| impl PciRomHeader { | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { | ||||
|         if data.len() < 26 { | ||||
|             // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock.
 | ||||
|             return Err(EINVAL); | ||||
|  | @ -479,7 +472,7 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
|         match signature { | ||||
|             0xAA55 | 0xBB77 | 0x4E56 => {} | ||||
|             _ => { | ||||
|                 dev_err!(pdev.as_ref(), "ROM signature unknown {:#x}\n", signature); | ||||
|                 dev_err!(dev, "ROM signature unknown {:#x}\n", signature); | ||||
|                 return Err(EINVAL); | ||||
|             } | ||||
|         } | ||||
|  | @ -538,9 +531,9 @@ struct NpdeStruct { | |||
| } | ||||
| 
 | ||||
| impl NpdeStruct { | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Option<Self> { | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Option<Self> { | ||||
|         if data.len() < core::mem::size_of::<Self>() { | ||||
|             dev_dbg!(pdev.as_ref(), "Not enough data for NpdeStruct\n"); | ||||
|             dev_dbg!(dev, "Not enough data for NpdeStruct\n"); | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|  | @ -549,17 +542,13 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Option<Self> { | |||
| 
 | ||||
|         // Signature should be "NPDE" (0x4544504E).
 | ||||
|         if &signature != b"NPDE" { | ||||
|             dev_dbg!( | ||||
|                 pdev.as_ref(), | ||||
|                 "Invalid signature for NpdeStruct: {:?}\n", | ||||
|                 signature | ||||
|             ); | ||||
|             dev_dbg!(dev, "Invalid signature for NpdeStruct: {:?}\n", signature); | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let subimage_len = u16::from_le_bytes([data[8], data[9]]); | ||||
|         if subimage_len == 0 { | ||||
|             dev_dbg!(pdev.as_ref(), "Invalid subimage length: 0\n"); | ||||
|             dev_dbg!(dev, "Invalid subimage length: 0\n"); | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|  | @ -584,7 +573,7 @@ fn image_size_bytes(&self) -> usize { | |||
| 
 | ||||
|     /// Try to find NPDE in the data, the NPDE is right after the PCIR.
 | ||||
|     fn find_in_data( | ||||
|         pdev: &pci::Device, | ||||
|         dev: &device::Device, | ||||
|         data: &[u8], | ||||
|         rom_header: &PciRomHeader, | ||||
|         pcir: &PcirStruct, | ||||
|  | @ -596,12 +585,12 @@ fn find_in_data( | |||
| 
 | ||||
|         // Check if we have enough data
 | ||||
|         if npde_start + core::mem::size_of::<Self>() > data.len() { | ||||
|             dev_dbg!(pdev.as_ref(), "Not enough data for NPDE\n"); | ||||
|             dev_dbg!(dev, "Not enough data for NPDE\n"); | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         // Try to create NPDE from the data
 | ||||
|         NpdeStruct::new(pdev, &data[npde_start..]) | ||||
|         NpdeStruct::new(dev, &data[npde_start..]) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -669,10 +658,10 @@ fn image_size_bytes(&self) -> usize { | |||
| 
 | ||||
|     /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which
 | ||||
|     /// triggers the constructor of the specific BiosImage enum variant.
 | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | ||||
|         let base = BiosImageBase::new(pdev, data)?; | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { | ||||
|         let base = BiosImageBase::new(dev, data)?; | ||||
|         let image = base.into_image().inspect_err(|e| { | ||||
|             dev_err!(pdev.as_ref(), "Failed to create BiosImage: {:?}\n", e); | ||||
|             dev_err!(dev, "Failed to create BiosImage: {:?}\n", e); | ||||
|         })?; | ||||
| 
 | ||||
|         Ok(image) | ||||
|  | @ -754,9 +743,10 @@ fn try_from(base: BiosImageBase) -> Result<Self> { | |||
| ///
 | ||||
| /// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that
 | ||||
| /// Rust favors composition of types over inheritance.
 | ||||
| #[derive(Debug)] | ||||
| #[expect(dead_code)] | ||||
| struct BiosImageBase { | ||||
|     /// Used for logging.
 | ||||
|     dev: ARef<device::Device>, | ||||
|     /// PCI ROM Expansion Header
 | ||||
|     rom_header: PciRomHeader, | ||||
|     /// PCI Data Structure
 | ||||
|  | @ -773,16 +763,16 @@ fn into_image(self) -> Result<BiosImage> { | |||
|     } | ||||
| 
 | ||||
|     /// Creates a new BiosImageBase from raw byte data.
 | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { | ||||
|         // Ensure we have enough data for the ROM header.
 | ||||
|         if data.len() < 26 { | ||||
|             dev_err!(pdev.as_ref(), "Not enough data for ROM header\n"); | ||||
|             dev_err!(dev, "Not enough data for ROM header\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|         // Parse the ROM header.
 | ||||
|         let rom_header = PciRomHeader::new(pdev, &data[0..26]) | ||||
|             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PciRomHeader: {:?}\n", e))?; | ||||
|         let rom_header = PciRomHeader::new(dev, &data[0..26]) | ||||
|             .inspect_err(|e| dev_err!(dev, "Failed to create PciRomHeader: {:?}\n", e))?; | ||||
| 
 | ||||
|         // Get the PCI Data Structure using the pointer from the ROM header.
 | ||||
|         let pcir_offset = rom_header.pci_data_struct_offset as usize; | ||||
|  | @ -791,28 +781,29 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
|             .ok_or(EINVAL) | ||||
|             .inspect_err(|_| { | ||||
|                 dev_err!( | ||||
|                     pdev.as_ref(), | ||||
|                     dev, | ||||
|                     "PCIR offset {:#x} out of bounds (data length: {})\n", | ||||
|                     pcir_offset, | ||||
|                     data.len() | ||||
|                 ); | ||||
|                 dev_err!( | ||||
|                     pdev.as_ref(), | ||||
|                     dev, | ||||
|                     "Consider reading more data for construction of BiosImage\n" | ||||
|                 ); | ||||
|             })?; | ||||
| 
 | ||||
|         let pcir = PcirStruct::new(pdev, pcir_data) | ||||
|             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PcirStruct: {:?}\n", e))?; | ||||
|         let pcir = PcirStruct::new(dev, pcir_data) | ||||
|             .inspect_err(|e| dev_err!(dev, "Failed to create PcirStruct: {:?}\n", e))?; | ||||
| 
 | ||||
|         // Look for NPDE structure if this is not an NBSI image (type != 0x70).
 | ||||
|         let npde = NpdeStruct::find_in_data(pdev, data, &rom_header, &pcir); | ||||
|         let npde = NpdeStruct::find_in_data(dev, data, &rom_header, &pcir); | ||||
| 
 | ||||
|         // Create a copy of the data.
 | ||||
|         let mut data_copy = KVec::new(); | ||||
|         data_copy.extend_from_slice(data, GFP_KERNEL)?; | ||||
| 
 | ||||
|         Ok(BiosImageBase { | ||||
|             dev: dev.into(), | ||||
|             rom_header, | ||||
|             pcir, | ||||
|             npde, | ||||
|  | @ -848,7 +839,7 @@ fn get_bit_token(&self, token_id: u8) -> Result<BitToken> { | |||
|     ///
 | ||||
|     /// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC
 | ||||
|     /// image.
 | ||||
|     fn falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32> { | ||||
|     fn falcon_data_ptr(&self) -> Result<u32> { | ||||
|         let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?; | ||||
| 
 | ||||
|         // Make sure we don't go out of bounds
 | ||||
|  | @ -859,14 +850,14 @@ fn falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32> { | |||
|         // read the 4 bytes at the offset specified in the token
 | ||||
|         let offset = token.data_offset as usize; | ||||
|         let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| { | ||||
|             dev_err!(pdev.as_ref(), "Failed to convert data slice to array"); | ||||
|             dev_err!(self.base.dev, "Failed to convert data slice to array"); | ||||
|             EINVAL | ||||
|         })?; | ||||
| 
 | ||||
|         let data_ptr = u32::from_le_bytes(bytes); | ||||
| 
 | ||||
|         if (data_ptr as usize) < self.base.data.len() { | ||||
|             dev_err!(pdev.as_ref(), "Falcon data pointer out of bounds\n"); | ||||
|             dev_err!(self.base.dev, "Falcon data pointer out of bounds\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -892,7 +883,7 @@ fn try_from(base: BiosImageBase) -> Result<Self> { | |||
| /// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`].
 | ||||
| ///
 | ||||
| /// See the [`PmuLookupTable`] description for more information.
 | ||||
| #[expect(dead_code)] | ||||
| #[repr(C, packed)] | ||||
| struct PmuLookupTableEntry { | ||||
|     application_id: u8, | ||||
|     target_id: u8, | ||||
|  | @ -901,7 +892,7 @@ struct PmuLookupTableEntry { | |||
| 
 | ||||
| impl PmuLookupTableEntry { | ||||
|     fn new(data: &[u8]) -> Result<Self> { | ||||
|         if data.len() < 6 { | ||||
|         if data.len() < core::mem::size_of::<Self>() { | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -928,7 +919,7 @@ struct PmuLookupTable { | |||
| } | ||||
| 
 | ||||
| impl PmuLookupTable { | ||||
|     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | ||||
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { | ||||
|         if data.len() < 4 { | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
|  | @ -940,10 +931,7 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
|         let required_bytes = header_len + (entry_count * entry_len); | ||||
| 
 | ||||
|         if data.len() < required_bytes { | ||||
|             dev_err!( | ||||
|                 pdev.as_ref(), | ||||
|                 "PmuLookupTable data length less than required\n" | ||||
|             ); | ||||
|             dev_err!(dev, "PmuLookupTable data length less than required\n"); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -956,11 +944,7 @@ fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { | |||
| 
 | ||||
|         // Debug logging of entries (dumps the table data to dmesg)
 | ||||
|         for i in (header_len..required_bytes).step_by(entry_len) { | ||||
|             dev_dbg!( | ||||
|                 pdev.as_ref(), | ||||
|                 "PMU entry: {:02x?}\n", | ||||
|                 &data[i..][..entry_len] | ||||
|             ); | ||||
|             dev_dbg!(dev, "PMU entry: {:02x?}\n", &data[i..][..entry_len]); | ||||
|         } | ||||
| 
 | ||||
|         Ok(PmuLookupTable { | ||||
|  | @ -997,11 +981,10 @@ fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> { | |||
| impl FwSecBiosBuilder { | ||||
|     fn setup_falcon_data( | ||||
|         &mut self, | ||||
|         pdev: &pci::Device, | ||||
|         pci_at_image: &PciAtBiosImage, | ||||
|         first_fwsec: &FwSecBiosBuilder, | ||||
|     ) -> Result { | ||||
|         let mut offset = pci_at_image.falcon_data_ptr(pdev)? as usize; | ||||
|         let mut offset = pci_at_image.falcon_data_ptr()? as usize; | ||||
|         let mut pmu_in_first_fwsec = false; | ||||
| 
 | ||||
|         // The falcon data pointer assumes that the PciAt and FWSEC images
 | ||||
|  | @ -1024,10 +1007,15 @@ fn setup_falcon_data( | |||
|         self.falcon_data_offset = Some(offset); | ||||
| 
 | ||||
|         if pmu_in_first_fwsec { | ||||
|             self.pmu_lookup_table = | ||||
|                 Some(PmuLookupTable::new(pdev, &first_fwsec.base.data[offset..])?); | ||||
|             self.pmu_lookup_table = Some(PmuLookupTable::new( | ||||
|                 &self.base.dev, | ||||
|                 &first_fwsec.base.data[offset..], | ||||
|             )?); | ||||
|         } else { | ||||
|             self.pmu_lookup_table = Some(PmuLookupTable::new(pdev, &self.base.data[offset..])?); | ||||
|             self.pmu_lookup_table = Some(PmuLookupTable::new( | ||||
|                 &self.base.dev, | ||||
|                 &self.base.data[offset..], | ||||
|             )?); | ||||
|         } | ||||
| 
 | ||||
|         match self | ||||
|  | @ -1040,7 +1028,7 @@ fn setup_falcon_data( | |||
|                 let mut ucode_offset = entry.data as usize; | ||||
|                 ucode_offset -= pci_at_image.base.data.len(); | ||||
|                 if ucode_offset < first_fwsec.base.data.len() { | ||||
|                     dev_err!(pdev.as_ref(), "Falcon Ucode offset not in second Fwsec.\n"); | ||||
|                     dev_err!(self.base.dev, "Falcon Ucode offset not in second Fwsec.\n"); | ||||
|                     return Err(EINVAL); | ||||
|                 } | ||||
|                 ucode_offset -= first_fwsec.base.data.len(); | ||||
|  | @ -1048,7 +1036,7 @@ fn setup_falcon_data( | |||
|             } | ||||
|             Err(e) => { | ||||
|                 dev_err!( | ||||
|                     pdev.as_ref(), | ||||
|                     self.base.dev, | ||||
|                     "PmuLookupTableEntry not found, error: {:?}\n", | ||||
|                     e | ||||
|                 ); | ||||
|  | @ -1059,7 +1047,7 @@ fn setup_falcon_data( | |||
|     } | ||||
| 
 | ||||
|     /// Build the final FwSecBiosImage from this builder
 | ||||
|     fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> { | ||||
|     fn build(self) -> Result<FwSecBiosImage> { | ||||
|         let ret = FwSecBiosImage { | ||||
|             base: self.base, | ||||
|             falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?, | ||||
|  | @ -1067,8 +1055,8 @@ fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> { | |||
| 
 | ||||
|         if cfg!(debug_assertions) { | ||||
|             // Print the desc header for debugging
 | ||||
|             let desc = ret.header(pdev.as_ref())?; | ||||
|             dev_dbg!(pdev.as_ref(), "PmuLookupTableEntry desc: {:#?}\n", desc); | ||||
|             let desc = ret.header()?; | ||||
|             dev_dbg!(ret.base.dev, "PmuLookupTableEntry desc: {:#?}\n", desc); | ||||
|         } | ||||
| 
 | ||||
|         Ok(ret) | ||||
|  | @ -1077,13 +1065,16 @@ fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> { | |||
| 
 | ||||
| impl FwSecBiosImage { | ||||
|     /// Get the FwSec header ([`FalconUCodeDescV3`]).
 | ||||
|     pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> { | ||||
|     pub(crate) fn header(&self) -> Result<&FalconUCodeDescV3> { | ||||
|         // Get the falcon ucode offset that was found in setup_falcon_data.
 | ||||
|         let falcon_ucode_offset = self.falcon_ucode_offset; | ||||
| 
 | ||||
|         // Make sure the offset is within the data bounds.
 | ||||
|         if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() { | ||||
|             dev_err!(dev, "fwsec-frts header not contained within BIOS bounds\n"); | ||||
|             dev_err!( | ||||
|                 self.base.dev, | ||||
|                 "fwsec-frts header not contained within BIOS bounds\n" | ||||
|             ); | ||||
|             return Err(ERANGE); | ||||
|         } | ||||
| 
 | ||||
|  | @ -1095,7 +1086,7 @@ pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> | |||
|         let ver = (hdr & 0xff00) >> 8; | ||||
| 
 | ||||
|         if ver != 3 { | ||||
|             dev_err!(dev, "invalid fwsec firmware version: {:?}\n", ver); | ||||
|             dev_err!(self.base.dev, "invalid fwsec firmware version: {:?}\n", ver); | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|  | @ -1115,7 +1106,7 @@ pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> | |||
|     } | ||||
| 
 | ||||
|     /// Get the ucode data as a byte slice
 | ||||
|     pub(crate) fn ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> { | ||||
|     pub(crate) fn ucode(&self, desc: &FalconUCodeDescV3) -> Result<&[u8]> { | ||||
|         let falcon_ucode_offset = self.falcon_ucode_offset; | ||||
| 
 | ||||
|         // The ucode data follows the descriptor.
 | ||||
|  | @ -1127,15 +1118,16 @@ pub(crate) fn ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Re | |||
|             .data | ||||
|             .get(ucode_data_offset..ucode_data_offset + size) | ||||
|             .ok_or(ERANGE) | ||||
|             .inspect_err(|_| dev_err!(dev, "fwsec ucode data not contained within BIOS bounds\n")) | ||||
|             .inspect_err(|_| { | ||||
|                 dev_err!( | ||||
|                     self.base.dev, | ||||
|                     "fwsec ucode data not contained within BIOS bounds\n" | ||||
|                 ) | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     /// Get the signatures as a byte slice
 | ||||
|     pub(crate) fn sigs( | ||||
|         &self, | ||||
|         dev: &device::Device, | ||||
|         desc: &FalconUCodeDescV3, | ||||
|     ) -> Result<&[Bcrt30Rsa3kSignature]> { | ||||
|     pub(crate) fn sigs(&self, desc: &FalconUCodeDescV3) -> Result<&[Bcrt30Rsa3kSignature]> { | ||||
|         // The signatures data follows the descriptor.
 | ||||
|         let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>(); | ||||
|         let sigs_size = | ||||
|  | @ -1144,7 +1136,7 @@ pub(crate) fn sigs( | |||
|         // Make sure the data is within bounds.
 | ||||
|         if sigs_data_offset + sigs_size > self.base.data.len() { | ||||
|             dev_err!( | ||||
|                 dev, | ||||
|                 self.base.dev, | ||||
|                 "fwsec signatures data not contained within BIOS bounds\n" | ||||
|             ); | ||||
|             return Err(ERANGE); | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ | |||
| #include <linux/cpumask.h> | ||||
| #include <linux/cred.h> | ||||
| #include <linux/device/faux.h> | ||||
| #include <linux/dma-direction.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include <linux/errname.h> | ||||
| #include <linux/ethtool.h> | ||||
|  | @ -57,6 +58,7 @@ | |||
| #include <linux/jiffies.h> | ||||
| #include <linux/jump_label.h> | ||||
| #include <linux/mdio.h> | ||||
| #include <linux/mm.h> | ||||
| #include <linux/miscdevice.h> | ||||
| #include <linux/of_device.h> | ||||
| #include <linux/pci.h> | ||||
|  |  | |||
|  | @ -39,6 +39,7 @@ | |||
| #include "rcu.c" | ||||
| #include "refcount.c" | ||||
| #include "regulator.c" | ||||
| #include "scatterlist.c" | ||||
| #include "security.c" | ||||
| #include "signal.c" | ||||
| #include "slab.c" | ||||
|  |  | |||
							
								
								
									
										24
									
								
								rust/helpers/scatterlist.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								rust/helpers/scatterlist.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| #include <linux/dma-direction.h> | ||||
| 
 | ||||
| dma_addr_t rust_helper_sg_dma_address(struct scatterlist *sg) | ||||
| { | ||||
| 	return sg_dma_address(sg); | ||||
| } | ||||
| 
 | ||||
| unsigned int rust_helper_sg_dma_len(struct scatterlist *sg) | ||||
| { | ||||
| 	return sg_dma_len(sg); | ||||
| } | ||||
| 
 | ||||
| struct scatterlist *rust_helper_sg_next(struct scatterlist *sg) | ||||
| { | ||||
| 	return sg_next(sg); | ||||
| } | ||||
| 
 | ||||
| void rust_helper_dma_unmap_sgtable(struct device *dev, struct sg_table *sgt, | ||||
| 				   enum dma_data_direction dir, unsigned long attrs) | ||||
| { | ||||
| 	return dma_unmap_sgtable(dev, sgt, dir, attrs); | ||||
| } | ||||
|  | @ -15,8 +15,12 @@ | |||
| 
 | ||||
| use crate::alloc::{AllocError, Allocator}; | ||||
| use crate::bindings; | ||||
| use crate::page; | ||||
| use crate::pr_warn; | ||||
| 
 | ||||
| mod iter; | ||||
| pub use self::iter::VmallocPageIter; | ||||
| 
 | ||||
| /// The contiguous kernel allocator.
 | ||||
| ///
 | ||||
| /// `Kmalloc` is typically used for physically contiguous allocations up to page size, but also
 | ||||
|  | @ -142,6 +146,54 @@ unsafe fn realloc( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Vmalloc { | ||||
|     /// Convert a pointer to a [`Vmalloc`] allocation to a [`page::BorrowedPage`].
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     ///
 | ||||
|     /// ```
 | ||||
|     /// # use core::ptr::{NonNull, from_mut};
 | ||||
|     /// # use kernel::{page, prelude::*};
 | ||||
|     /// use kernel::alloc::allocator::Vmalloc;
 | ||||
|     ///
 | ||||
|     /// let mut vbox = VBox::<[u8; page::PAGE_SIZE]>::new_uninit(GFP_KERNEL)?;
 | ||||
|     ///
 | ||||
|     /// {
 | ||||
|     ///     // SAFETY: By the type invariant of `Box` the inner pointer of `vbox` is non-null.
 | ||||
|     ///     let ptr = unsafe { NonNull::new_unchecked(from_mut(&mut *vbox)) };
 | ||||
|     ///
 | ||||
|     ///     // SAFETY:
 | ||||
|     ///     // `ptr` is a valid pointer to a `Vmalloc` allocation.
 | ||||
|     ///     // `ptr` is valid for the entire lifetime of `page`.
 | ||||
|     ///     let page = unsafe { Vmalloc::to_page(ptr.cast()) };
 | ||||
|     ///
 | ||||
|     ///     // SAFETY: There is no concurrent read or write to the same page.
 | ||||
|     ///     unsafe { page.fill_zero_raw(0, page::PAGE_SIZE)? };
 | ||||
|     /// }
 | ||||
|     /// # Ok::<(), Error>(())
 | ||||
|     /// ```
 | ||||
|     ///
 | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// - `ptr` must be a valid pointer to a [`Vmalloc`] allocation.
 | ||||
|     /// - `ptr` must remain valid for the entire duration of `'a`.
 | ||||
|     pub unsafe fn to_page<'a>(ptr: NonNull<u8>) -> page::BorrowedPage<'a> { | ||||
|         // SAFETY: `ptr` is a valid pointer to `Vmalloc` memory.
 | ||||
|         let page = unsafe { bindings::vmalloc_to_page(ptr.as_ptr().cast()) }; | ||||
| 
 | ||||
|         // SAFETY: `vmalloc_to_page` returns a valid pointer to a `struct page` for a valid pointer
 | ||||
|         // to `Vmalloc` memory.
 | ||||
|         let page = unsafe { NonNull::new_unchecked(page) }; | ||||
| 
 | ||||
|         // SAFETY:
 | ||||
|         // - `page` is a valid pointer to a `struct page`, given that by the safety requirements of
 | ||||
|         //   this function `ptr` is a valid pointer to a `Vmalloc` allocation.
 | ||||
|         // - By the safety requirements of this function `ptr` is valid for the entire lifetime of
 | ||||
|         //   `'a`.
 | ||||
|         unsafe { page::BorrowedPage::from_raw(page) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that
 | ||||
| // - memory remains valid until it is explicitly freed,
 | ||||
| // - passing a pointer to a valid memory allocation is OK,
 | ||||
|  |  | |||
							
								
								
									
										102
									
								
								rust/kernel/alloc/allocator/iter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								rust/kernel/alloc/allocator/iter.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| use super::Vmalloc; | ||||
| use crate::page; | ||||
| use core::marker::PhantomData; | ||||
| use core::ptr::NonNull; | ||||
| 
 | ||||
| /// An [`Iterator`] of [`page::BorrowedPage`] items owned by a [`Vmalloc`] allocation.
 | ||||
| ///
 | ||||
| /// # Guarantees
 | ||||
| ///
 | ||||
| /// The pages iterated by the [`Iterator`] appear in the order as they are mapped in the CPU's
 | ||||
| /// virtual address space ascendingly.
 | ||||
| ///
 | ||||
| /// # Invariants
 | ||||
| ///
 | ||||
| /// - `buf` is a valid and [`page::PAGE_SIZE`] aligned pointer into a [`Vmalloc`] allocation.
 | ||||
| /// - `size` is the number of bytes from `buf` until the end of the [`Vmalloc`] allocation `buf`
 | ||||
| ///   points to.
 | ||||
| pub struct VmallocPageIter<'a> { | ||||
|     /// The base address of the [`Vmalloc`] buffer.
 | ||||
|     buf: NonNull<u8>, | ||||
|     /// The size of the buffer pointed to by `buf` in bytes.
 | ||||
|     size: usize, | ||||
|     /// The current page index of the [`Iterator`].
 | ||||
|     index: usize, | ||||
|     _p: PhantomData<page::BorrowedPage<'a>>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> Iterator for VmallocPageIter<'a> { | ||||
|     type Item = page::BorrowedPage<'a>; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         let offset = self.index.checked_mul(page::PAGE_SIZE)?; | ||||
| 
 | ||||
|         // Even though `self.size()` may be smaller than `Self::page_count() * page::PAGE_SIZE`, it
 | ||||
|         // is always a number between `(Self::page_count() - 1) * page::PAGE_SIZE` and
 | ||||
|         // `Self::page_count() * page::PAGE_SIZE`, hence the check below is sufficient.
 | ||||
|         if offset < self.size() { | ||||
|             self.index += 1; | ||||
|         } else { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         // TODO: Use `NonNull::add()` instead, once the minimum supported compiler version is
 | ||||
|         // bumped to 1.80 or later.
 | ||||
|         //
 | ||||
|         // SAFETY: `offset` is in the interval `[0, (self.page_count() - 1) * page::PAGE_SIZE]`,
 | ||||
|         // hence the resulting pointer is guaranteed to be within the same allocation.
 | ||||
|         let ptr = unsafe { self.buf.as_ptr().add(offset) }; | ||||
| 
 | ||||
|         // SAFETY: `ptr` is guaranteed to be non-null given that it is derived from `self.buf`.
 | ||||
|         let ptr = unsafe { NonNull::new_unchecked(ptr) }; | ||||
| 
 | ||||
|         // SAFETY:
 | ||||
|         // - `ptr` is a valid pointer to a `Vmalloc` allocation.
 | ||||
|         // - `ptr` is valid for the duration of `'a`.
 | ||||
|         Some(unsafe { Vmalloc::to_page(ptr) }) | ||||
|     } | ||||
| 
 | ||||
|     fn size_hint(&self) -> (usize, Option<usize>) { | ||||
|         let remaining = self.page_count().saturating_sub(self.index); | ||||
| 
 | ||||
|         (remaining, Some(remaining)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> VmallocPageIter<'a> { | ||||
|     /// Creates a new [`VmallocPageIter`] instance.
 | ||||
|     ///
 | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// - `buf` must be a [`page::PAGE_SIZE`] aligned pointer into a [`Vmalloc`] allocation.
 | ||||
|     /// - `buf` must be valid for at least the lifetime of `'a`.
 | ||||
|     /// - `size` must be the number of bytes from `buf` until the end of the [`Vmalloc`] allocation
 | ||||
|     ///   `buf` points to.
 | ||||
|     pub unsafe fn new(buf: NonNull<u8>, size: usize) -> Self { | ||||
|         // INVARIANT: By the safety requirements, `buf` is a valid and `page::PAGE_SIZE` aligned
 | ||||
|         // pointer into a [`Vmalloc`] allocation.
 | ||||
|         Self { | ||||
|             buf, | ||||
|             size, | ||||
|             index: 0, | ||||
|             _p: PhantomData, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the size of the backing [`Vmalloc`] allocation in bytes.
 | ||||
|     ///
 | ||||
|     /// Note that this is the size the [`Vmalloc`] allocation has been allocated with. Hence, this
 | ||||
|     /// number may be smaller than `[`Self::page_count`] * [`page::PAGE_SIZE`]`.
 | ||||
|     #[inline] | ||||
|     pub fn size(&self) -> usize { | ||||
|         self.size | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the number of pages owned by the backing [`Vmalloc`] allocation.
 | ||||
|     #[inline] | ||||
|     pub fn page_count(&self) -> usize { | ||||
|         self.size().div_ceil(page::PAGE_SIZE) | ||||
|     } | ||||
| } | ||||
|  | @ -12,8 +12,10 @@ | |||
| use super::{flags::*, AllocError, Allocator, Flags}; | ||||
| use core::alloc::Layout; | ||||
| use core::cmp; | ||||
| use core::marker::PhantomData; | ||||
| use core::ptr; | ||||
| use core::ptr::NonNull; | ||||
| use kernel::page; | ||||
| 
 | ||||
| /// The userspace allocator based on libc.
 | ||||
| pub struct Cmalloc; | ||||
|  | @ -33,6 +35,33 @@ pub fn aligned_layout(layout: Layout) -> Layout { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct VmallocPageIter<'a> { | ||||
|     _p: PhantomData<page::BorrowedPage<'a>>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> Iterator for VmallocPageIter<'a> { | ||||
|     type Item = page::BorrowedPage<'a>; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         None | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> VmallocPageIter<'a> { | ||||
|     #[allow(clippy::missing_safety_doc)] | ||||
|     pub unsafe fn new(_buf: NonNull<u8>, _size: usize) -> Self { | ||||
|         Self { _p: PhantomData } | ||||
|     } | ||||
| 
 | ||||
|     pub fn size(&self) -> usize { | ||||
|         0 | ||||
|     } | ||||
| 
 | ||||
|     pub fn page_count(&self) -> usize { | ||||
|         0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" { | ||||
|     #[link_name = "aligned_alloc"] | ||||
|     fn libc_aligned_alloc(align: usize, size: usize) -> *mut crate::ffi::c_void; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| //! Implementation of [`Box`].
 | ||||
| 
 | ||||
| #[allow(unused_imports)] // Used in doc comments.
 | ||||
| use super::allocator::{KVmalloc, Kmalloc, Vmalloc}; | ||||
| use super::allocator::{KVmalloc, Kmalloc, Vmalloc, VmallocPageIter}; | ||||
| use super::{AllocError, Allocator, Flags}; | ||||
| use core::alloc::Layout; | ||||
| use core::borrow::{Borrow, BorrowMut}; | ||||
|  | @ -18,6 +18,7 @@ | |||
| 
 | ||||
| use crate::ffi::c_void; | ||||
| use crate::init::InPlaceInit; | ||||
| use crate::page::AsPageIter; | ||||
| use crate::types::ForeignOwnable; | ||||
| use pin_init::{InPlaceWrite, Init, PinInit, ZeroableOption}; | ||||
| 
 | ||||
|  | @ -598,3 +599,40 @@ fn drop(&mut self) { | |||
|         unsafe { A::free(self.0.cast(), layout) }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// # Examples
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// # use kernel::prelude::*;
 | ||||
| /// use kernel::alloc::allocator::VmallocPageIter;
 | ||||
| /// use kernel::page::{AsPageIter, PAGE_SIZE};
 | ||||
| ///
 | ||||
| /// let mut vbox = VBox::new((), GFP_KERNEL)?;
 | ||||
| ///
 | ||||
| /// assert!(vbox.page_iter().next().is_none());
 | ||||
| ///
 | ||||
| /// let mut vbox = VBox::<[u8; PAGE_SIZE]>::new_uninit(GFP_KERNEL)?;
 | ||||
| ///
 | ||||
| /// let page = vbox.page_iter().next().expect("At least one page should be available.\n");
 | ||||
| ///
 | ||||
| /// // SAFETY: There is no concurrent read or write to the same page.
 | ||||
| /// unsafe { page.fill_zero_raw(0, PAGE_SIZE)? };
 | ||||
| /// # Ok::<(), Error>(())
 | ||||
| /// ```
 | ||||
| impl<T> AsPageIter for VBox<T> { | ||||
|     type Iter<'a> | ||||
|         = VmallocPageIter<'a> | ||||
|     where | ||||
|         T: 'a; | ||||
| 
 | ||||
|     fn page_iter(&mut self) -> Self::Iter<'_> { | ||||
|         let ptr = self.0.cast(); | ||||
|         let size = core::mem::size_of::<T>(); | ||||
| 
 | ||||
|         // SAFETY:
 | ||||
|         // - `ptr` is a valid pointer to the beginning of a `Vmalloc` allocation.
 | ||||
|         // - `ptr` is guaranteed to be valid for the lifetime of `'a`.
 | ||||
|         // - `size` is the size of the `Vmalloc` allocation `ptr` points to.
 | ||||
|         unsafe { VmallocPageIter::new(ptr, size) } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -3,10 +3,11 @@ | |||
| //! Implementation of [`Vec`].
 | ||||
| 
 | ||||
| use super::{ | ||||
|     allocator::{KVmalloc, Kmalloc, Vmalloc}, | ||||
|     allocator::{KVmalloc, Kmalloc, Vmalloc, VmallocPageIter}, | ||||
|     layout::ArrayLayout, | ||||
|     AllocError, Allocator, Box, Flags, | ||||
| }; | ||||
| use crate::page::AsPageIter; | ||||
| use core::{ | ||||
|     borrow::{Borrow, BorrowMut}, | ||||
|     fmt, | ||||
|  | @ -1017,6 +1018,43 @@ fn into_iter(self) -> Self::IntoIter { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// # Examples
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// # use kernel::prelude::*;
 | ||||
| /// use kernel::alloc::allocator::VmallocPageIter;
 | ||||
| /// use kernel::page::{AsPageIter, PAGE_SIZE};
 | ||||
| ///
 | ||||
| /// let mut vec = VVec::<u8>::new();
 | ||||
| ///
 | ||||
| /// assert!(vec.page_iter().next().is_none());
 | ||||
| ///
 | ||||
| /// vec.reserve(PAGE_SIZE, GFP_KERNEL)?;
 | ||||
| ///
 | ||||
| /// let page = vec.page_iter().next().expect("At least one page should be available.\n");
 | ||||
| ///
 | ||||
| /// // SAFETY: There is no concurrent read or write to the same page.
 | ||||
| /// unsafe { page.fill_zero_raw(0, PAGE_SIZE)? };
 | ||||
| /// # Ok::<(), Error>(())
 | ||||
| /// ```
 | ||||
| impl<T> AsPageIter for VVec<T> { | ||||
|     type Iter<'a> | ||||
|         = VmallocPageIter<'a> | ||||
|     where | ||||
|         T: 'a; | ||||
| 
 | ||||
|     fn page_iter(&mut self) -> Self::Iter<'_> { | ||||
|         let ptr = self.ptr.cast(); | ||||
|         let size = self.layout.size(); | ||||
| 
 | ||||
|         // SAFETY:
 | ||||
|         // - `ptr` is a valid pointer to the beginning of a `Vmalloc` allocation.
 | ||||
|         // - `ptr` is guaranteed to be valid for the lifetime of `'a`.
 | ||||
|         // - `size` is the size of the `Vmalloc` allocation `ptr` points to.
 | ||||
|         unsafe { VmallocPageIter::new(ptr, size) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// An [`Iterator`] implementation for [`Vec`] that moves elements out of a vector.
 | ||||
| ///
 | ||||
| /// This structure is created by the [`Vec::into_iter`] method on [`Vec`] (provided by the
 | ||||
|  |  | |||
|  | @ -98,6 +98,11 @@ pub const fn len(&self) -> usize { | |||
|     pub const fn is_empty(&self) -> bool { | ||||
|         self.len == 0 | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the size of the [`ArrayLayout`] in bytes.
 | ||||
|     pub const fn size(&self) -> usize { | ||||
|         self.len() * core::mem::size_of::<T>() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> From<ArrayLayout<T>> for Layout { | ||||
|  |  | |||
|  | @ -135,11 +135,9 @@ pub fn new<'a, E>( | |||
|         T: 'a, | ||||
|         Error: From<E>, | ||||
|     { | ||||
|         let callback = Self::devres_callback; | ||||
| 
 | ||||
|         try_pin_init!(&this in Self { | ||||
|             dev: dev.into(), | ||||
|             callback, | ||||
|             callback: Self::devres_callback, | ||||
|             // INVARIANT: `inner` is properly initialized.
 | ||||
|             inner <- Opaque::pin_init(try_pin_init!(Inner { | ||||
|                     devm <- Completion::new(), | ||||
|  | @ -160,7 +158,7 @@ pub fn new<'a, E>( | |||
|                 //    properly initialized, because we require `dev` (i.e. the *bound* device) to
 | ||||
|                 //    live at least as long as the returned `impl PinInit<Self, Error>`.
 | ||||
|                 to_result(unsafe { | ||||
|                     bindings::devm_add_action(dev.as_raw(), Some(callback), inner.cast()) | ||||
|                     bindings::devm_add_action(dev.as_raw(), Some(*callback), inner.cast()) | ||||
|                 }).inspect_err(|_| { | ||||
|                     let inner = Opaque::cast_into(inner); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,16 @@ | |||
|     types::ARef, | ||||
| }; | ||||
| 
 | ||||
| /// DMA address type.
 | ||||
| ///
 | ||||
| /// Represents a bus address used for Direct Memory Access (DMA) operations.
 | ||||
| ///
 | ||||
| /// This is an alias of the kernel's `dma_addr_t`, which may be `u32` or `u64` depending on
 | ||||
| /// `CONFIG_ARCH_DMA_ADDR_T_64BIT`.
 | ||||
| ///
 | ||||
| /// Note that this may be `u64` even on 32-bit architectures.
 | ||||
| pub type DmaAddress = bindings::dma_addr_t; | ||||
| 
 | ||||
| /// Trait to be implemented by DMA capable bus devices.
 | ||||
| ///
 | ||||
| /// The [`dma::Device`](Device) trait should be implemented by bus specific device representations,
 | ||||
|  | @ -244,6 +254,74 @@ pub mod attrs { | |||
|     pub const DMA_ATTR_PRIVILEGED: Attrs = Attrs(bindings::DMA_ATTR_PRIVILEGED); | ||||
| } | ||||
| 
 | ||||
| /// DMA data direction.
 | ||||
| ///
 | ||||
| /// Corresponds to the C [`enum dma_data_direction`].
 | ||||
| ///
 | ||||
| /// [`enum dma_data_direction`]: srctree/include/linux/dma-direction.h
 | ||||
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||||
| #[repr(u32)] | ||||
| pub enum DataDirection { | ||||
|     /// The DMA mapping is for bidirectional data transfer.
 | ||||
|     ///
 | ||||
|     /// This is used when the buffer can be both read from and written to by the device.
 | ||||
|     /// The cache for the corresponding memory region is both flushed and invalidated.
 | ||||
|     Bidirectional = Self::const_cast(bindings::dma_data_direction_DMA_BIDIRECTIONAL), | ||||
| 
 | ||||
|     /// The DMA mapping is for data transfer from memory to the device (write).
 | ||||
|     ///
 | ||||
|     /// The CPU has prepared data in the buffer, and the device will read it.
 | ||||
|     /// The cache for the corresponding memory region is flushed before device access.
 | ||||
|     ToDevice = Self::const_cast(bindings::dma_data_direction_DMA_TO_DEVICE), | ||||
| 
 | ||||
|     /// The DMA mapping is for data transfer from the device to memory (read).
 | ||||
|     ///
 | ||||
|     /// The device will write data into the buffer for the CPU to read.
 | ||||
|     /// The cache for the corresponding memory region is invalidated before CPU access.
 | ||||
|     FromDevice = Self::const_cast(bindings::dma_data_direction_DMA_FROM_DEVICE), | ||||
| 
 | ||||
|     /// The DMA mapping is not for data transfer.
 | ||||
|     ///
 | ||||
|     /// This is primarily for debugging purposes. With this direction, the DMA mapping API
 | ||||
|     /// will not perform any cache coherency operations.
 | ||||
|     None = Self::const_cast(bindings::dma_data_direction_DMA_NONE), | ||||
| } | ||||
| 
 | ||||
| impl DataDirection { | ||||
|     /// Casts the bindgen-generated enum type to a `u32` at compile time.
 | ||||
|     ///
 | ||||
|     /// This function will cause a compile-time error if the underlying value of the
 | ||||
|     /// C enum is out of bounds for `u32`.
 | ||||
|     const fn const_cast(val: bindings::dma_data_direction) -> u32 { | ||||
|         // CAST: The C standard allows compilers to choose different integer types for enums.
 | ||||
|         // To safely check the value, we cast it to a wide signed integer type (`i128`)
 | ||||
|         // which can hold any standard C integer enum type without truncation.
 | ||||
|         let wide_val = val as i128; | ||||
| 
 | ||||
|         // Check if the value is outside the valid range for the target type `u32`.
 | ||||
|         // CAST: `u32::MAX` is cast to `i128` to match the type of `wide_val` for the comparison.
 | ||||
|         if wide_val < 0 || wide_val > u32::MAX as i128 { | ||||
|             // Trigger a compile-time error in a const context.
 | ||||
|             build_error!("C enum value is out of bounds for the target type `u32`."); | ||||
|         } | ||||
| 
 | ||||
|         // CAST: This cast is valid because the check above guarantees that `wide_val`
 | ||||
|         // is within the representable range of `u32`.
 | ||||
|         wide_val as u32 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<DataDirection> for bindings::dma_data_direction { | ||||
|     /// Returns the raw representation of [`enum dma_data_direction`].
 | ||||
|     fn from(direction: DataDirection) -> Self { | ||||
|         // CAST: `direction as u32` gets the underlying representation of our `#[repr(u32)]` enum.
 | ||||
|         // The subsequent cast to `Self` (the bindgen type) assumes the C enum is compatible
 | ||||
|         // with the enum variants of `DataDirection`, which is a valid assumption given our
 | ||||
|         // compile-time checks.
 | ||||
|         direction as u32 as Self | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// An abstraction of the `dma_alloc_coherent` API.
 | ||||
| ///
 | ||||
| /// This is an abstraction around the `dma_alloc_coherent` API which is used to allocate and map
 | ||||
|  | @ -275,7 +353,7 @@ pub mod attrs { | |||
| // entire `CoherentAllocation` including the allocated memory itself.
 | ||||
| pub struct CoherentAllocation<T: AsBytes + FromBytes> { | ||||
|     dev: ARef<device::Device>, | ||||
|     dma_handle: bindings::dma_addr_t, | ||||
|     dma_handle: DmaAddress, | ||||
|     count: usize, | ||||
|     cpu_addr: *mut T, | ||||
|     dma_attrs: Attrs, | ||||
|  | @ -376,7 +454,7 @@ pub fn start_ptr_mut(&mut self) -> *mut T { | |||
| 
 | ||||
|     /// Returns a DMA handle which may be given to the device as the DMA address base of
 | ||||
|     /// the region.
 | ||||
|     pub fn dma_handle(&self) -> bindings::dma_addr_t { | ||||
|     pub fn dma_handle(&self) -> DmaAddress { | ||||
|         self.dma_handle | ||||
|     } | ||||
| 
 | ||||
|  | @ -384,13 +462,13 @@ pub fn dma_handle(&self) -> bindings::dma_addr_t { | |||
|     /// device as the DMA address base of the region.
 | ||||
|     ///
 | ||||
|     /// Returns `EINVAL` if `offset` is not within the bounds of the allocation.
 | ||||
|     pub fn dma_handle_with_offset(&self, offset: usize) -> Result<bindings::dma_addr_t> { | ||||
|     pub fn dma_handle_with_offset(&self, offset: usize) -> Result<DmaAddress> { | ||||
|         if offset >= self.count { | ||||
|             Err(EINVAL) | ||||
|         } else { | ||||
|             // INVARIANT: The type invariant of `Self` guarantees that `size_of::<T> * count` fits
 | ||||
|             // into a `usize`, and `offset` is inferior to `count`.
 | ||||
|             Ok(self.dma_handle + (offset * core::mem::size_of::<T>()) as bindings::dma_addr_t) | ||||
|             Ok(self.dma_handle + (offset * core::mem::size_of::<T>()) as DmaAddress) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -86,6 +86,9 @@ pub struct AllocOps { | |||
| 
 | ||||
| /// Trait for memory manager implementations. Implemented internally.
 | ||||
| pub trait AllocImpl: super::private::Sealed + drm::gem::IntoGEMObject { | ||||
|     /// The [`Driver`] implementation for this [`AllocImpl`].
 | ||||
|     type Driver: drm::Driver; | ||||
| 
 | ||||
|     /// The C callback operations for this memory manager.
 | ||||
|     const ALLOC_OPS: AllocOps; | ||||
| } | ||||
|  |  | |||
|  | @ -13,34 +13,34 @@ | |||
|     sync::aref::{ARef, AlwaysRefCounted}, | ||||
|     types::Opaque, | ||||
| }; | ||||
| use core::{mem, ops::Deref, ptr::NonNull}; | ||||
| use core::{ops::Deref, ptr::NonNull}; | ||||
| 
 | ||||
| /// A type alias for retrieving a [`Driver`]s [`DriverFile`] implementation from its
 | ||||
| /// [`DriverObject`] implementation.
 | ||||
| ///
 | ||||
| /// [`Driver`]: drm::Driver
 | ||||
| /// [`DriverFile`]: drm::file::DriverFile
 | ||||
| pub type DriverFile<T> = drm::File<<<T as DriverObject>::Driver as drm::Driver>::File>; | ||||
| 
 | ||||
| /// GEM object functions, which must be implemented by drivers.
 | ||||
| pub trait BaseDriverObject<T: BaseObject>: Sync + Send + Sized { | ||||
| pub trait DriverObject: Sync + Send + Sized { | ||||
|     /// Parent `Driver` for this object.
 | ||||
|     type Driver: drm::Driver; | ||||
| 
 | ||||
|     /// Create a new driver data object for a GEM object of a given size.
 | ||||
|     fn new(dev: &drm::Device<T::Driver>, size: usize) -> impl PinInit<Self, Error>; | ||||
|     fn new(dev: &drm::Device<Self::Driver>, size: usize) -> impl PinInit<Self, Error>; | ||||
| 
 | ||||
|     /// Open a new handle to an existing object, associated with a File.
 | ||||
|     fn open( | ||||
|         _obj: &<<T as IntoGEMObject>::Driver as drm::Driver>::Object, | ||||
|         _file: &drm::File<<<T as IntoGEMObject>::Driver as drm::Driver>::File>, | ||||
|     ) -> Result { | ||||
|     fn open(_obj: &<Self::Driver as drm::Driver>::Object, _file: &DriverFile<Self>) -> Result { | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     /// Close a handle to an existing object, associated with a File.
 | ||||
|     fn close( | ||||
|         _obj: &<<T as IntoGEMObject>::Driver as drm::Driver>::Object, | ||||
|         _file: &drm::File<<<T as IntoGEMObject>::Driver as drm::Driver>::File>, | ||||
|     ) { | ||||
|     } | ||||
|     fn close(_obj: &<Self::Driver as drm::Driver>::Object, _file: &DriverFile<Self>) {} | ||||
| } | ||||
| 
 | ||||
| /// Trait that represents a GEM object subtype
 | ||||
| pub trait IntoGEMObject: Sized + super::private::Sealed + AlwaysRefCounted { | ||||
|     /// Owning driver for this type
 | ||||
|     type Driver: drm::Driver; | ||||
| 
 | ||||
|     /// Returns a reference to the raw `drm_gem_object` structure, which must be valid as long as
 | ||||
|     /// this owning object is valid.
 | ||||
|     fn as_raw(&self) -> *mut bindings::drm_gem_object; | ||||
|  | @ -75,25 +75,16 @@ unsafe fn dec_ref(obj: NonNull<Self>) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Trait which must be implemented by drivers using base GEM objects.
 | ||||
| pub trait DriverObject: BaseDriverObject<Object<Self>> { | ||||
|     /// Parent `Driver` for this object.
 | ||||
|     type Driver: drm::Driver; | ||||
| } | ||||
| 
 | ||||
| extern "C" fn open_callback<T: BaseDriverObject<U>, U: BaseObject>( | ||||
| extern "C" fn open_callback<T: DriverObject>( | ||||
|     raw_obj: *mut bindings::drm_gem_object, | ||||
|     raw_file: *mut bindings::drm_file, | ||||
| ) -> core::ffi::c_int { | ||||
|     // SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`.
 | ||||
|     let file = unsafe { | ||||
|         drm::File::<<<U as IntoGEMObject>::Driver as drm::Driver>::File>::from_raw(raw_file) | ||||
|     }; | ||||
|     // SAFETY: `open_callback` is specified in the AllocOps structure for `Object<T>`, ensuring that
 | ||||
|     // `raw_obj` is indeed contained within a `Object<T>`.
 | ||||
|     let obj = unsafe { | ||||
|         <<<U as IntoGEMObject>::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) | ||||
|     }; | ||||
|     let file = unsafe { DriverFile::<T>::from_raw(raw_file) }; | ||||
| 
 | ||||
|     // SAFETY: `open_callback` is specified in the AllocOps structure for `DriverObject<T>`,
 | ||||
|     // ensuring that `raw_obj` is contained within a `DriverObject<T>`
 | ||||
|     let obj = unsafe { <<T::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) }; | ||||
| 
 | ||||
|     match T::open(obj, file) { | ||||
|         Err(e) => e.to_errno(), | ||||
|  | @ -101,26 +92,21 @@ extern "C" fn open_callback<T: BaseDriverObject<U>, U: BaseObject>( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" fn close_callback<T: BaseDriverObject<U>, U: BaseObject>( | ||||
| extern "C" fn close_callback<T: DriverObject>( | ||||
|     raw_obj: *mut bindings::drm_gem_object, | ||||
|     raw_file: *mut bindings::drm_file, | ||||
| ) { | ||||
|     // SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`.
 | ||||
|     let file = unsafe { | ||||
|         drm::File::<<<U as IntoGEMObject>::Driver as drm::Driver>::File>::from_raw(raw_file) | ||||
|     }; | ||||
|     let file = unsafe { DriverFile::<T>::from_raw(raw_file) }; | ||||
| 
 | ||||
|     // SAFETY: `close_callback` is specified in the AllocOps structure for `Object<T>`, ensuring
 | ||||
|     // that `raw_obj` is indeed contained within a `Object<T>`.
 | ||||
|     let obj = unsafe { | ||||
|         <<<U as IntoGEMObject>::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) | ||||
|     }; | ||||
|     let obj = unsafe { <<T::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) }; | ||||
| 
 | ||||
|     T::close(obj, file); | ||||
| } | ||||
| 
 | ||||
| impl<T: DriverObject> IntoGEMObject for Object<T> { | ||||
|     type Driver = T::Driver; | ||||
| 
 | ||||
|     fn as_raw(&self) -> *mut bindings::drm_gem_object { | ||||
|         self.obj.get() | ||||
|     } | ||||
|  | @ -142,10 +128,12 @@ fn size(&self) -> usize { | |||
| 
 | ||||
|     /// Creates a new handle for the object associated with a given `File`
 | ||||
|     /// (or returns an existing one).
 | ||||
|     fn create_handle( | ||||
|         &self, | ||||
|         file: &drm::File<<<Self as IntoGEMObject>::Driver as drm::Driver>::File>, | ||||
|     ) -> Result<u32> { | ||||
|     fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32> | ||||
|     where | ||||
|         Self: AllocImpl<Driver = D>, | ||||
|         D: drm::Driver<Object = Self, File = F>, | ||||
|         F: drm::file::DriverFile<Driver = D>, | ||||
|     { | ||||
|         let mut handle: u32 = 0; | ||||
|         // SAFETY: The arguments are all valid per the type invariants.
 | ||||
|         to_result(unsafe { | ||||
|  | @ -155,10 +143,12 @@ fn create_handle( | |||
|     } | ||||
| 
 | ||||
|     /// Looks up an object by its handle for a given `File`.
 | ||||
|     fn lookup_handle( | ||||
|         file: &drm::File<<<Self as IntoGEMObject>::Driver as drm::Driver>::File>, | ||||
|         handle: u32, | ||||
|     ) -> Result<ARef<Self>> { | ||||
|     fn lookup_handle<D, F>(file: &drm::File<F>, handle: u32) -> Result<ARef<Self>> | ||||
|     where | ||||
|         Self: AllocImpl<Driver = D>, | ||||
|         D: drm::Driver<Object = Self, File = F>, | ||||
|         F: drm::file::DriverFile<Driver = D>, | ||||
|     { | ||||
|         // SAFETY: The arguments are all valid per the type invariants.
 | ||||
|         let ptr = unsafe { bindings::drm_gem_object_lookup(file.as_raw().cast(), handle) }; | ||||
|         if ptr.is_null() { | ||||
|  | @ -208,13 +198,10 @@ pub struct Object<T: DriverObject + Send + Sync> { | |||
| } | ||||
| 
 | ||||
| impl<T: DriverObject> Object<T> { | ||||
|     /// The size of this object's structure.
 | ||||
|     pub const SIZE: usize = mem::size_of::<Self>(); | ||||
| 
 | ||||
|     const OBJECT_FUNCS: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs { | ||||
|         free: Some(Self::free_callback), | ||||
|         open: Some(open_callback::<T, Object<T>>), | ||||
|         close: Some(close_callback::<T, Object<T>>), | ||||
|         open: Some(open_callback::<T>), | ||||
|         close: Some(close_callback::<T>), | ||||
|         print_info: None, | ||||
|         export: None, | ||||
|         pin: None, | ||||
|  | @ -297,6 +284,8 @@ fn deref(&self) -> &Self::Target { | |||
| } | ||||
| 
 | ||||
| impl<T: DriverObject> AllocImpl for Object<T> { | ||||
|     type Driver = T::Driver; | ||||
| 
 | ||||
|     const ALLOC_OPS: AllocOps = AllocOps { | ||||
|         gem_create_object: None, | ||||
|         prime_handle_to_fd: None, | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| //
 | ||||
| // Stable since Rust 1.79.0.
 | ||||
| #![feature(inline_const)] | ||||
| #![feature(pointer_is_aligned)] | ||||
| //
 | ||||
| // Stable since Rust 1.81.0.
 | ||||
| #![feature(lint_reasons)] | ||||
|  | @ -113,6 +114,7 @@ | |||
| pub mod rbtree; | ||||
| pub mod regulator; | ||||
| pub mod revocable; | ||||
| pub mod scatterlist; | ||||
| pub mod security; | ||||
| pub mod seq_file; | ||||
| pub mod sizes; | ||||
|  |  | |||
|  | @ -9,7 +9,12 @@ | |||
|     error::Result, | ||||
|     uaccess::UserSliceReader, | ||||
| }; | ||||
| use core::ptr::{self, NonNull}; | ||||
| use core::{ | ||||
|     marker::PhantomData, | ||||
|     mem::ManuallyDrop, | ||||
|     ops::Deref, | ||||
|     ptr::{self, NonNull}, | ||||
| }; | ||||
| 
 | ||||
| /// A bitwise shift for the page size.
 | ||||
| pub const PAGE_SHIFT: usize = bindings::PAGE_SHIFT as usize; | ||||
|  | @ -30,6 +35,86 @@ pub const fn page_align(addr: usize) -> usize { | |||
|     (addr + (PAGE_SIZE - 1)) & PAGE_MASK | ||||
| } | ||||
| 
 | ||||
| /// Representation of a non-owning reference to a [`Page`].
 | ||||
| ///
 | ||||
| /// This type provides a borrowed version of a [`Page`] that is owned by some other entity, e.g. a
 | ||||
| /// [`Vmalloc`] allocation such as [`VBox`].
 | ||||
| ///
 | ||||
| /// # Example
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// # use kernel::{bindings, prelude::*};
 | ||||
| /// use kernel::page::{BorrowedPage, Page, PAGE_SIZE};
 | ||||
| /// # use core::{mem::MaybeUninit, ptr, ptr::NonNull };
 | ||||
| ///
 | ||||
| /// fn borrow_page<'a>(vbox: &'a mut VBox<MaybeUninit<[u8; PAGE_SIZE]>>) -> BorrowedPage<'a> {
 | ||||
| ///     let ptr = ptr::from_ref(&**vbox);
 | ||||
| ///
 | ||||
| ///     // SAFETY: `ptr` is a valid pointer to `Vmalloc` memory.
 | ||||
| ///     let page = unsafe { bindings::vmalloc_to_page(ptr.cast()) };
 | ||||
| ///
 | ||||
| ///     // SAFETY: `vmalloc_to_page` returns a valid pointer to a `struct page` for a valid
 | ||||
| ///     // pointer to `Vmalloc` memory.
 | ||||
| ///     let page = unsafe { NonNull::new_unchecked(page) };
 | ||||
| ///
 | ||||
| ///     // SAFETY:
 | ||||
| ///     // - `self.0` is a valid pointer to a `struct page`.
 | ||||
| ///     // - `self.0` is valid for the entire lifetime of `self`.
 | ||||
| ///     unsafe { BorrowedPage::from_raw(page) }
 | ||||
| /// }
 | ||||
| ///
 | ||||
| /// let mut vbox = VBox::<[u8; PAGE_SIZE]>::new_uninit(GFP_KERNEL)?;
 | ||||
| /// let page = borrow_page(&mut vbox);
 | ||||
| ///
 | ||||
| /// // SAFETY: There is no concurrent read or write to this page.
 | ||||
| /// unsafe { page.fill_zero_raw(0, PAGE_SIZE)? };
 | ||||
| /// # Ok::<(), Error>(())
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// # Invariants
 | ||||
| ///
 | ||||
| /// The borrowed underlying pointer to a `struct page` is valid for the entire lifetime `'a`.
 | ||||
| ///
 | ||||
| /// [`VBox`]: kernel::alloc::VBox
 | ||||
| /// [`Vmalloc`]: kernel::alloc::allocator::Vmalloc
 | ||||
| pub struct BorrowedPage<'a>(ManuallyDrop<Page>, PhantomData<&'a Page>); | ||||
| 
 | ||||
| impl<'a> BorrowedPage<'a> { | ||||
|     /// Constructs a [`BorrowedPage`] from a raw pointer to a `struct page`.
 | ||||
|     ///
 | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// - `ptr` must point to a valid `bindings::page`.
 | ||||
|     /// - `ptr` must remain valid for the entire lifetime `'a`.
 | ||||
|     pub unsafe fn from_raw(ptr: NonNull<bindings::page>) -> Self { | ||||
|         let page = Page { page: ptr }; | ||||
| 
 | ||||
|         // INVARIANT: The safety requirements guarantee that `ptr` is valid for the entire lifetime
 | ||||
|         // `'a`.
 | ||||
|         Self(ManuallyDrop::new(page), PhantomData) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> Deref for BorrowedPage<'a> { | ||||
|     type Target = Page; | ||||
| 
 | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Trait to be implemented by types which provide an [`Iterator`] implementation of
 | ||||
| /// [`BorrowedPage`] items, such as [`VmallocPageIter`](kernel::alloc::allocator::VmallocPageIter).
 | ||||
| pub trait AsPageIter { | ||||
|     /// The [`Iterator`] type, e.g. [`VmallocPageIter`](kernel::alloc::allocator::VmallocPageIter).
 | ||||
|     type Iter<'a>: Iterator<Item = BorrowedPage<'a>> | ||||
|     where | ||||
|         Self: 'a; | ||||
| 
 | ||||
|     /// Returns an [`Iterator`] of [`BorrowedPage`] items over all pages owned by `self`.
 | ||||
|     fn page_iter(&mut self) -> Self::Iter<'_>; | ||||
| } | ||||
| 
 | ||||
| /// A pointer to a page that owns the page allocation.
 | ||||
| ///
 | ||||
| /// # Invariants
 | ||||
|  |  | |||
							
								
								
									
										491
									
								
								rust/kernel/scatterlist.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								rust/kernel/scatterlist.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,491 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| 
 | ||||
| //! Abstractions for scatter-gather lists.
 | ||||
| //!
 | ||||
| //! C header: [`include/linux/scatterlist.h`](srctree/include/linux/scatterlist.h)
 | ||||
| //!
 | ||||
| //! Scatter-gather (SG) I/O is a memory access technique that allows devices to perform DMA
 | ||||
| //! operations on data buffers that are not physically contiguous in memory. It works by creating a
 | ||||
| //! "scatter-gather list", an array where each entry specifies the address and length of a
 | ||||
| //! physically contiguous memory segment.
 | ||||
| //!
 | ||||
| //! The device's DMA controller can then read this list and process the segments sequentially as
 | ||||
| //! part of one logical I/O request. This avoids the need for a single, large, physically contiguous
 | ||||
| //! memory buffer, which can be difficult or impossible to allocate.
 | ||||
| //!
 | ||||
| //! This module provides safe Rust abstractions over the kernel's `struct scatterlist` and
 | ||||
| //! `struct sg_table` types.
 | ||||
| //!
 | ||||
| //! The main entry point is the [`SGTable`] type, which represents a complete scatter-gather table.
 | ||||
| //! It can be either:
 | ||||
| //!
 | ||||
| //! - An owned table ([`SGTable<Owned<P>>`]), created from a Rust memory buffer (e.g., [`VVec`]).
 | ||||
| //!   This type manages the allocation of the `struct sg_table`, the DMA mapping of the buffer, and
 | ||||
| //!   the automatic cleanup of all resources.
 | ||||
| //! - A borrowed reference (&[`SGTable`]), which provides safe, read-only access to a table that was
 | ||||
| //!   allocated by other (e.g., C) code.
 | ||||
| //!
 | ||||
| //! Individual entries in the table are represented by [`SGEntry`], which can be accessed by
 | ||||
| //! iterating over an [`SGTable`].
 | ||||
| 
 | ||||
| use crate::{ | ||||
|     alloc, | ||||
|     alloc::allocator::VmallocPageIter, | ||||
|     bindings, | ||||
|     device::{Bound, Device}, | ||||
|     devres::Devres, | ||||
|     dma, error, | ||||
|     io::resource::ResourceSize, | ||||
|     page, | ||||
|     prelude::*, | ||||
|     types::{ARef, Opaque}, | ||||
| }; | ||||
| use core::{ops::Deref, ptr::NonNull}; | ||||
| 
 | ||||
| /// A single entry in a scatter-gather list.
 | ||||
| ///
 | ||||
| /// An `SGEntry` represents a single, physically contiguous segment of memory that has been mapped
 | ||||
| /// for DMA.
 | ||||
| ///
 | ||||
| /// Instances of this struct are obtained by iterating over an [`SGTable`]. Drivers do not create
 | ||||
| /// or own [`SGEntry`] objects directly.
 | ||||
| #[repr(transparent)] | ||||
| pub struct SGEntry(Opaque<bindings::scatterlist>); | ||||
| 
 | ||||
| // SAFETY: `SGEntry` can be sent to any task.
 | ||||
| unsafe impl Send for SGEntry {} | ||||
| 
 | ||||
| // SAFETY: `SGEntry` has no interior mutability and can be accessed concurrently.
 | ||||
| unsafe impl Sync for SGEntry {} | ||||
| 
 | ||||
| impl SGEntry { | ||||
|     /// Convert a raw `struct scatterlist *` to a `&'a SGEntry`.
 | ||||
|     ///
 | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// Callers must ensure that the `struct scatterlist` pointed to by `ptr` is valid for the
 | ||||
|     /// lifetime `'a`.
 | ||||
|     #[inline] | ||||
|     unsafe fn from_raw<'a>(ptr: *mut bindings::scatterlist) -> &'a Self { | ||||
|         // SAFETY: The safety requirements of this function guarantee that `ptr` is a valid pointer
 | ||||
|         // to a `struct scatterlist` for the duration of `'a`.
 | ||||
|         unsafe { &*ptr.cast() } | ||||
|     } | ||||
| 
 | ||||
|     /// Obtain the raw `struct scatterlist *`.
 | ||||
|     #[inline] | ||||
|     fn as_raw(&self) -> *mut bindings::scatterlist { | ||||
|         self.0.get() | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the DMA address of this SG entry.
 | ||||
|     ///
 | ||||
|     /// This is the address that the device should use to access the memory segment.
 | ||||
|     #[inline] | ||||
|     pub fn dma_address(&self) -> dma::DmaAddress { | ||||
|         // SAFETY: `self.as_raw()` is a valid pointer to a `struct scatterlist`.
 | ||||
|         unsafe { bindings::sg_dma_address(self.as_raw()) } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the length of this SG entry in bytes.
 | ||||
|     #[inline] | ||||
|     pub fn dma_len(&self) -> ResourceSize { | ||||
|         #[allow(clippy::useless_conversion)] | ||||
|         // SAFETY: `self.as_raw()` is a valid pointer to a `struct scatterlist`.
 | ||||
|         unsafe { bindings::sg_dma_len(self.as_raw()) }.into() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The borrowed generic type of an [`SGTable`], representing a borrowed or externally managed
 | ||||
| /// table.
 | ||||
| #[repr(transparent)] | ||||
| pub struct Borrowed(Opaque<bindings::sg_table>); | ||||
| 
 | ||||
| // SAFETY: `Borrowed` can be sent to any task.
 | ||||
| unsafe impl Send for Borrowed {} | ||||
| 
 | ||||
| // SAFETY: `Borrowed` has no interior mutability and can be accessed concurrently.
 | ||||
| unsafe impl Sync for Borrowed {} | ||||
| 
 | ||||
| /// A scatter-gather table.
 | ||||
| ///
 | ||||
| /// This struct is a wrapper around the kernel's `struct sg_table`. It manages a list of DMA-mapped
 | ||||
| /// memory segments that can be passed to a device for I/O operations.
 | ||||
| ///
 | ||||
| /// The generic parameter `T` is used as a generic type to distinguish between owned and borrowed
 | ||||
| /// tables.
 | ||||
| ///
 | ||||
| ///  - [`SGTable<Owned>`]: An owned table created and managed entirely by Rust code. It handles
 | ||||
| ///    allocation, DMA mapping, and cleanup of all associated resources. See [`SGTable::new`].
 | ||||
| ///  - [`SGTable<Borrowed>`} (or simply [`SGTable`]): Represents a table whose lifetime is managed
 | ||||
| ///    externally. It can be used safely via a borrowed reference `&'a SGTable`, where `'a` is the
 | ||||
| ///    external lifetime.
 | ||||
| ///
 | ||||
| /// All [`SGTable`] variants can be iterated over the individual [`SGEntry`]s.
 | ||||
| #[repr(transparent)] | ||||
| #[pin_data] | ||||
| pub struct SGTable<T: private::Sealed = Borrowed> { | ||||
|     #[pin] | ||||
|     inner: T, | ||||
| } | ||||
| 
 | ||||
| impl SGTable { | ||||
|     /// Creates a borrowed `&'a SGTable` from a raw `struct sg_table` pointer.
 | ||||
|     ///
 | ||||
|     /// This allows safe access to an `sg_table` that is managed elsewhere (for example, in C code).
 | ||||
|     ///
 | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// Callers must ensure that:
 | ||||
|     ///
 | ||||
|     /// - the `struct sg_table` pointed to by `ptr` is valid for the entire lifetime of `'a`,
 | ||||
|     /// - the data behind `ptr` is not modified concurrently for the duration of `'a`.
 | ||||
|     #[inline] | ||||
|     pub unsafe fn from_raw<'a>(ptr: *mut bindings::sg_table) -> &'a Self { | ||||
|         // SAFETY: The safety requirements of this function guarantee that `ptr` is a valid pointer
 | ||||
|         // to a `struct sg_table` for the duration of `'a`.
 | ||||
|         unsafe { &*ptr.cast() } | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     fn as_raw(&self) -> *mut bindings::sg_table { | ||||
|         self.inner.0.get() | ||||
|     } | ||||
| 
 | ||||
|     /// Returns an [`SGTableIter`] bound to the lifetime of `self`.
 | ||||
|     pub fn iter(&self) -> SGTableIter<'_> { | ||||
|         // SAFETY: `self.as_raw()` is a valid pointer to a `struct sg_table`.
 | ||||
|         let nents = unsafe { (*self.as_raw()).nents }; | ||||
| 
 | ||||
|         let pos = if nents > 0 { | ||||
|             // SAFETY: `self.as_raw()` is a valid pointer to a `struct sg_table`.
 | ||||
|             let ptr = unsafe { (*self.as_raw()).sgl }; | ||||
| 
 | ||||
|             // SAFETY: `ptr` is guaranteed to be a valid pointer to a `struct scatterlist`.
 | ||||
|             Some(unsafe { SGEntry::from_raw(ptr) }) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         SGTableIter { pos, nents } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Represents the DMA mapping state of a `struct sg_table`.
 | ||||
| ///
 | ||||
| /// This is used as an inner type of [`Owned`] to manage the DMA mapping lifecycle.
 | ||||
| ///
 | ||||
| /// # Invariants
 | ||||
| ///
 | ||||
| /// - `sgt` is a valid pointer to a `struct sg_table` for the entire lifetime of the
 | ||||
| ///   [`DmaMappedSgt`].
 | ||||
| /// - `sgt` is always DMA mapped.
 | ||||
| struct DmaMappedSgt { | ||||
|     sgt: NonNull<bindings::sg_table>, | ||||
|     dev: ARef<Device>, | ||||
|     dir: dma::DataDirection, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: `DmaMappedSgt` can be sent to any task.
 | ||||
| unsafe impl Send for DmaMappedSgt {} | ||||
| 
 | ||||
| // SAFETY: `DmaMappedSgt` has no interior mutability and can be accessed concurrently.
 | ||||
| unsafe impl Sync for DmaMappedSgt {} | ||||
| 
 | ||||
| impl DmaMappedSgt { | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// - `sgt` must be a valid pointer to a `struct sg_table` for the entire lifetime of the
 | ||||
|     ///   returned [`DmaMappedSgt`].
 | ||||
|     /// - The caller must guarantee that `sgt` remains DMA mapped for the entire lifetime of
 | ||||
|     ///   [`DmaMappedSgt`].
 | ||||
|     unsafe fn new( | ||||
|         sgt: NonNull<bindings::sg_table>, | ||||
|         dev: &Device<Bound>, | ||||
|         dir: dma::DataDirection, | ||||
|     ) -> Result<Self> { | ||||
|         // SAFETY:
 | ||||
|         // - `dev.as_raw()` is a valid pointer to a `struct device`, which is guaranteed to be
 | ||||
|         //   bound to a driver for the duration of this call.
 | ||||
|         // - `sgt` is a valid pointer to a `struct sg_table`.
 | ||||
|         error::to_result(unsafe { | ||||
|             bindings::dma_map_sgtable(dev.as_raw(), sgt.as_ptr(), dir.into(), 0) | ||||
|         })?; | ||||
| 
 | ||||
|         // INVARIANT: By the safety requirements of this function it is guaranteed that `sgt` is
 | ||||
|         // valid for the entire lifetime of this object instance.
 | ||||
|         Ok(Self { | ||||
|             sgt, | ||||
|             dev: dev.into(), | ||||
|             dir, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Drop for DmaMappedSgt { | ||||
|     #[inline] | ||||
|     fn drop(&mut self) { | ||||
|         // SAFETY:
 | ||||
|         // - `self.dev.as_raw()` is a pointer to a valid `struct device`.
 | ||||
|         // - `self.dev` is the same device the mapping has been created for in `Self::new()`.
 | ||||
|         // - `self.sgt.as_ptr()` is a valid pointer to a `struct sg_table` by the type invariants
 | ||||
|         //   of `Self`.
 | ||||
|         // - `self.dir` is the same `dma::DataDirection` the mapping has been created with in
 | ||||
|         //   `Self::new()`.
 | ||||
|         unsafe { | ||||
|             bindings::dma_unmap_sgtable(self.dev.as_raw(), self.sgt.as_ptr(), self.dir.into(), 0) | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A transparent wrapper around a `struct sg_table`.
 | ||||
| ///
 | ||||
| /// While we could also create the `struct sg_table` in the constructor of [`Owned`], we can't tear
 | ||||
| /// down the `struct sg_table` in [`Owned::drop`]; the drop order in [`Owned`] matters.
 | ||||
| #[repr(transparent)] | ||||
| struct RawSGTable(Opaque<bindings::sg_table>); | ||||
| 
 | ||||
| // SAFETY: `RawSGTable` can be sent to any task.
 | ||||
| unsafe impl Send for RawSGTable {} | ||||
| 
 | ||||
| // SAFETY: `RawSGTable` has no interior mutability and can be accessed concurrently.
 | ||||
| unsafe impl Sync for RawSGTable {} | ||||
| 
 | ||||
| impl RawSGTable { | ||||
|     /// # Safety
 | ||||
|     ///
 | ||||
|     /// - `pages` must be a slice of valid `struct page *`.
 | ||||
|     /// - The pages pointed to by `pages` must remain valid for the entire lifetime of the returned
 | ||||
|     ///   [`RawSGTable`].
 | ||||
|     unsafe fn new( | ||||
|         pages: &mut [*mut bindings::page], | ||||
|         size: usize, | ||||
|         max_segment: u32, | ||||
|         flags: alloc::Flags, | ||||
|     ) -> Result<Self> { | ||||
|         // `sg_alloc_table_from_pages_segment()` expects at least one page, otherwise it
 | ||||
|         // produces a NPE.
 | ||||
|         if pages.is_empty() { | ||||
|             return Err(EINVAL); | ||||
|         } | ||||
| 
 | ||||
|         let sgt = Opaque::zeroed(); | ||||
|         // SAFETY:
 | ||||
|         // - `sgt.get()` is a valid pointer to uninitialized memory.
 | ||||
|         // - As by the check above, `pages` is not empty.
 | ||||
|         error::to_result(unsafe { | ||||
|             bindings::sg_alloc_table_from_pages_segment( | ||||
|                 sgt.get(), | ||||
|                 pages.as_mut_ptr(), | ||||
|                 pages.len().try_into()?, | ||||
|                 0, | ||||
|                 size, | ||||
|                 max_segment, | ||||
|                 flags.as_raw(), | ||||
|             ) | ||||
|         })?; | ||||
| 
 | ||||
|         Ok(Self(sgt)) | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     fn as_raw(&self) -> *mut bindings::sg_table { | ||||
|         self.0.get() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Drop for RawSGTable { | ||||
|     #[inline] | ||||
|     fn drop(&mut self) { | ||||
|         // SAFETY: `sgt` is a valid and initialized `struct sg_table`.
 | ||||
|         unsafe { bindings::sg_free_table(self.0.get()) }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The [`Owned`] generic type of an [`SGTable`].
 | ||||
| ///
 | ||||
| /// A [`SGTable<Owned>`] signifies that the [`SGTable`] owns all associated resources:
 | ||||
| ///
 | ||||
| /// - The backing memory pages.
 | ||||
| /// - The `struct sg_table` allocation (`sgt`).
 | ||||
| /// - The DMA mapping, managed through a [`Devres`]-managed `DmaMappedSgt`.
 | ||||
| ///
 | ||||
| /// Users interact with this type through the [`SGTable`] handle and do not need to manage
 | ||||
| /// [`Owned`] directly.
 | ||||
| #[pin_data] | ||||
| pub struct Owned<P> { | ||||
|     // Note: The drop order is relevant; we first have to unmap the `struct sg_table`, then free the
 | ||||
|     // `struct sg_table` and finally free the backing pages.
 | ||||
|     #[pin] | ||||
|     dma: Devres<DmaMappedSgt>, | ||||
|     sgt: RawSGTable, | ||||
|     _pages: P, | ||||
| } | ||||
| 
 | ||||
| // SAFETY: `Owned` can be sent to any task if `P` can be send to any task.
 | ||||
| unsafe impl<P: Send> Send for Owned<P> {} | ||||
| 
 | ||||
| // SAFETY: `Owned` has no interior mutability and can be accessed concurrently if `P` can be
 | ||||
| // accessed concurrently.
 | ||||
| unsafe impl<P: Sync> Sync for Owned<P> {} | ||||
| 
 | ||||
| impl<P> Owned<P> | ||||
| where | ||||
|     for<'a> P: page::AsPageIter<Iter<'a> = VmallocPageIter<'a>> + 'static, | ||||
| { | ||||
|     fn new( | ||||
|         dev: &Device<Bound>, | ||||
|         mut pages: P, | ||||
|         dir: dma::DataDirection, | ||||
|         flags: alloc::Flags, | ||||
|     ) -> Result<impl PinInit<Self, Error> + '_> { | ||||
|         let page_iter = pages.page_iter(); | ||||
|         let size = page_iter.size(); | ||||
| 
 | ||||
|         let mut page_vec: KVec<*mut bindings::page> = | ||||
|             KVec::with_capacity(page_iter.page_count(), flags)?; | ||||
| 
 | ||||
|         for page in page_iter { | ||||
|             page_vec.push(page.as_ptr(), flags)?; | ||||
|         } | ||||
| 
 | ||||
|         // `dma_max_mapping_size` returns `size_t`, but `sg_alloc_table_from_pages_segment()` takes
 | ||||
|         // an `unsigned int`.
 | ||||
|         //
 | ||||
|         // SAFETY: `dev.as_raw()` is a valid pointer to a `struct device`.
 | ||||
|         let max_segment = match unsafe { bindings::dma_max_mapping_size(dev.as_raw()) } { | ||||
|             0 => u32::MAX, | ||||
|             max_segment => u32::try_from(max_segment).unwrap_or(u32::MAX), | ||||
|         }; | ||||
| 
 | ||||
|         Ok(try_pin_init!(&this in Self { | ||||
|             // SAFETY:
 | ||||
|             // - `page_vec` is a `KVec` of valid `struct page *` obtained from `pages`.
 | ||||
|             // - The pages contained in `pages` remain valid for the entire lifetime of the
 | ||||
|             //   `RawSGTable`.
 | ||||
|             sgt: unsafe { RawSGTable::new(&mut page_vec, size, max_segment, flags) }?, | ||||
|             dma <- { | ||||
|                 // SAFETY: `this` is a valid pointer to uninitialized memory.
 | ||||
|                 let sgt = unsafe { &raw mut (*this.as_ptr()).sgt }.cast(); | ||||
| 
 | ||||
|                 // SAFETY: `sgt` is guaranteed to be non-null.
 | ||||
|                 let sgt = unsafe { NonNull::new_unchecked(sgt) }; | ||||
| 
 | ||||
|                 // SAFETY:
 | ||||
|                 // - It is guaranteed that the object returned by `DmaMappedSgt::new` won't out-live
 | ||||
|                 //   `sgt`.
 | ||||
|                 // - `sgt` is never DMA unmapped manually.
 | ||||
|                 Devres::new(dev, unsafe { DmaMappedSgt::new(sgt, dev, dir) }) | ||||
|             }, | ||||
|             _pages: pages, | ||||
|         })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<P> SGTable<Owned<P>> | ||||
| where | ||||
|     for<'a> P: page::AsPageIter<Iter<'a> = VmallocPageIter<'a>> + 'static, | ||||
| { | ||||
|     /// Allocates a new scatter-gather table from the given pages and maps it for DMA.
 | ||||
|     ///
 | ||||
|     /// This constructor creates a new [`SGTable<Owned>`] that takes ownership of `P`.
 | ||||
|     /// It allocates a `struct sg_table`, populates it with entries corresponding to the physical
 | ||||
|     /// pages of `P`, and maps the table for DMA with the specified [`Device`] and
 | ||||
|     /// [`dma::DataDirection`].
 | ||||
|     ///
 | ||||
|     /// The DMA mapping is managed through [`Devres`], ensuring that the DMA mapping is unmapped
 | ||||
|     /// once the associated [`Device`] is unbound, or when the [`SGTable<Owned>`] is dropped.
 | ||||
|     ///
 | ||||
|     /// # Parameters
 | ||||
|     ///
 | ||||
|     /// * `dev`: The [`Device`] that will be performing the DMA.
 | ||||
|     /// * `pages`: The entity providing the backing pages. It must implement [`page::AsPageIter`].
 | ||||
|     ///   The ownership of this entity is moved into the new [`SGTable<Owned>`].
 | ||||
|     /// * `dir`: The [`dma::DataDirection`] of the DMA transfer.
 | ||||
|     /// * `flags`: Allocation flags for internal allocations (e.g., [`GFP_KERNEL`]).
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     ///
 | ||||
|     /// ```
 | ||||
|     /// use kernel::{
 | ||||
|     ///     device::{Bound, Device},
 | ||||
|     ///     dma, page,
 | ||||
|     ///     prelude::*,
 | ||||
|     ///     scatterlist::{SGTable, Owned},
 | ||||
|     /// };
 | ||||
|     ///
 | ||||
|     /// fn test(dev: &Device<Bound>) -> Result {
 | ||||
|     ///     let size = 4 * page::PAGE_SIZE;
 | ||||
|     ///     let pages = VVec::<u8>::with_capacity(size, GFP_KERNEL)?;
 | ||||
|     ///
 | ||||
|     ///     let sgt = KBox::pin_init(SGTable::new(
 | ||||
|     ///         dev,
 | ||||
|     ///         pages,
 | ||||
|     ///         dma::DataDirection::ToDevice,
 | ||||
|     ///         GFP_KERNEL,
 | ||||
|     ///     ), GFP_KERNEL)?;
 | ||||
|     ///
 | ||||
|     ///     Ok(())
 | ||||
|     /// }
 | ||||
|     /// ```
 | ||||
|     pub fn new( | ||||
|         dev: &Device<Bound>, | ||||
|         pages: P, | ||||
|         dir: dma::DataDirection, | ||||
|         flags: alloc::Flags, | ||||
|     ) -> impl PinInit<Self, Error> + '_ { | ||||
|         try_pin_init!(Self { | ||||
|             inner <- Owned::new(dev, pages, dir, flags)? | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<P> Deref for SGTable<Owned<P>> { | ||||
|     type Target = SGTable; | ||||
| 
 | ||||
|     #[inline] | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         // SAFETY:
 | ||||
|         // - `self.inner.sgt.as_raw()` is a valid pointer to a `struct sg_table` for the entire
 | ||||
|         //   lifetime of `self`.
 | ||||
|         // - The backing `struct sg_table` is not modified for the entire lifetime of `self`.
 | ||||
|         unsafe { SGTable::from_raw(self.inner.sgt.as_raw()) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| mod private { | ||||
|     pub trait Sealed {} | ||||
| 
 | ||||
|     impl Sealed for super::Borrowed {} | ||||
|     impl<P> Sealed for super::Owned<P> {} | ||||
| } | ||||
| 
 | ||||
| /// An [`Iterator`] over the DMA mapped [`SGEntry`] items of an [`SGTable`].
 | ||||
| ///
 | ||||
| /// Note that the existence of an [`SGTableIter`] does not guarantee that the [`SGEntry`] items
 | ||||
| /// actually remain DMA mapped; they are prone to be unmapped on device unbind.
 | ||||
| pub struct SGTableIter<'a> { | ||||
|     pos: Option<&'a SGEntry>, | ||||
|     /// The number of DMA mapped entries in a `struct sg_table`.
 | ||||
|     nents: c_uint, | ||||
| } | ||||
| 
 | ||||
| impl<'a> Iterator for SGTableIter<'a> { | ||||
|     type Item = &'a SGEntry; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         let entry = self.pos?; | ||||
|         self.nents = self.nents.saturating_sub(1); | ||||
| 
 | ||||
|         // SAFETY: `entry.as_raw()` is a valid pointer to a `struct scatterlist`.
 | ||||
|         let next = unsafe { bindings::sg_next(entry.as_raw()) }; | ||||
| 
 | ||||
|         self.pos = (!next.is_null() && self.nents > 0).then(|| { | ||||
|             // SAFETY: If `next` is not NULL, `sg_next()` guarantees to return a valid pointer to
 | ||||
|             // the next `struct scatterlist`.
 | ||||
|             unsafe { SGEntry::from_raw(next) } | ||||
|         }); | ||||
| 
 | ||||
|         Some(entry) | ||||
|     } | ||||
| } | ||||
|  | @ -2,6 +2,8 @@ | |||
| 
 | ||||
| //! Traits for transmuting types.
 | ||||
| 
 | ||||
| use core::mem::size_of; | ||||
| 
 | ||||
| /// Types for which any bit pattern is valid.
 | ||||
| ///
 | ||||
| /// Not all types are valid for all values. For example, a `bool` must be either zero or one, so
 | ||||
|  | @ -9,10 +11,93 @@ | |||
| ///
 | ||||
| /// It's okay for the type to have padding, as initializing those bytes has no effect.
 | ||||
| ///
 | ||||
| /// # Examples
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// use kernel::transmute::FromBytes;
 | ||||
| ///
 | ||||
| /// # fn test() -> Option<()> {
 | ||||
| /// let raw = [1, 2, 3, 4];
 | ||||
| ///
 | ||||
| /// let result = u32::from_bytes(&raw)?;
 | ||||
| ///
 | ||||
| /// #[cfg(target_endian = "little")]
 | ||||
| /// assert_eq!(*result, 0x4030201);
 | ||||
| ///
 | ||||
| /// #[cfg(target_endian = "big")]
 | ||||
| /// assert_eq!(*result, 0x1020304);
 | ||||
| ///
 | ||||
| /// # Some(()) }
 | ||||
| /// # test().ok_or(EINVAL)?;
 | ||||
| /// # Ok::<(), Error>(())
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// # Safety
 | ||||
| ///
 | ||||
| /// All bit-patterns must be valid for this type. This type must not have interior mutability.
 | ||||
| pub unsafe trait FromBytes {} | ||||
| pub unsafe trait FromBytes { | ||||
|     /// Converts a slice of bytes to a reference to `Self`.
 | ||||
|     ///
 | ||||
|     /// Succeeds if the reference is properly aligned, and the size of `bytes` is equal to that of
 | ||||
|     /// `T` and different from zero.
 | ||||
|     ///
 | ||||
|     /// Otherwise, returns [`None`].
 | ||||
|     fn from_bytes(bytes: &[u8]) -> Option<&Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|     { | ||||
|         let slice_ptr = bytes.as_ptr().cast::<Self>(); | ||||
|         let size = size_of::<Self>(); | ||||
| 
 | ||||
|         #[allow(clippy::incompatible_msrv)] | ||||
|         if bytes.len() == size && slice_ptr.is_aligned() { | ||||
|             // SAFETY: Size and alignment were just checked.
 | ||||
|             unsafe { Some(&*slice_ptr) } | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Converts a mutable slice of bytes to a reference to `Self`.
 | ||||
|     ///
 | ||||
|     /// Succeeds if the reference is properly aligned, and the size of `bytes` is equal to that of
 | ||||
|     /// `T` and different from zero.
 | ||||
|     ///
 | ||||
|     /// Otherwise, returns [`None`].
 | ||||
|     fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self> | ||||
|     where | ||||
|         Self: AsBytes + Sized, | ||||
|     { | ||||
|         let slice_ptr = bytes.as_mut_ptr().cast::<Self>(); | ||||
|         let size = size_of::<Self>(); | ||||
| 
 | ||||
|         #[allow(clippy::incompatible_msrv)] | ||||
|         if bytes.len() == size && slice_ptr.is_aligned() { | ||||
|             // SAFETY: Size and alignment were just checked.
 | ||||
|             unsafe { Some(&mut *slice_ptr) } | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Creates an owned instance of `Self` by copying `bytes`.
 | ||||
|     ///
 | ||||
|     /// Unlike [`FromBytes::from_bytes`], which requires aligned input, this method can be used on
 | ||||
|     /// non-aligned data at the cost of a copy.
 | ||||
|     fn from_bytes_copy(bytes: &[u8]) -> Option<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|     { | ||||
|         if bytes.len() == size_of::<Self>() { | ||||
|             // SAFETY: we just verified that `bytes` has the same size as `Self`, and per the
 | ||||
|             // invariants of `FromBytes`, any byte sequence of the correct length is a valid value
 | ||||
|             // for `Self`.
 | ||||
|             Some(unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::<Self>()) }) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| macro_rules! impl_frombytes { | ||||
|     ($($({$($generics:tt)*})? $t:ty, )*) => { | ||||
|  | @ -47,7 +132,32 @@ macro_rules! impl_frombytes { | |||
| ///
 | ||||
| /// Values of this type may not contain any uninitialized bytes. This type must not have interior
 | ||||
| /// mutability.
 | ||||
| pub unsafe trait AsBytes {} | ||||
| pub unsafe trait AsBytes { | ||||
|     /// Returns `self` as a slice of bytes.
 | ||||
|     fn as_bytes(&self) -> &[u8] { | ||||
|         // CAST: `Self` implements `AsBytes` thus all bytes of `self` are initialized.
 | ||||
|         let data = core::ptr::from_ref(self).cast::<u8>(); | ||||
|         let len = core::mem::size_of_val(self); | ||||
| 
 | ||||
|         // SAFETY: `data` is non-null and valid for reads of `len * sizeof::<u8>()` bytes.
 | ||||
|         unsafe { core::slice::from_raw_parts(data, len) } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns `self` as a mutable slice of bytes.
 | ||||
|     fn as_bytes_mut(&mut self) -> &mut [u8] | ||||
|     where | ||||
|         Self: FromBytes, | ||||
|     { | ||||
|         // CAST: `Self` implements both `AsBytes` and `FromBytes` thus making `Self`
 | ||||
|         // bi-directionally transmutable to `[u8; size_of_val(self)]`.
 | ||||
|         let data = core::ptr::from_mut(self).cast::<u8>(); | ||||
|         let len = core::mem::size_of_val(self); | ||||
| 
 | ||||
|         // SAFETY: `data` is non-null and valid for read and writes of `len * sizeof::<u8>()`
 | ||||
|         // bytes.
 | ||||
|         unsafe { core::slice::from_raw_parts_mut(data, len) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| macro_rules! impl_asbytes { | ||||
|     ($($({$($generics:tt)*})? $t:ty, )*) => { | ||||
|  |  | |||
|  | @ -356,18 +356,11 @@ struct ClosureWork<T> { | |||
|     func: Option<T>, | ||||
| } | ||||
| 
 | ||||
| impl<T> ClosureWork<T> { | ||||
|     fn project(self: Pin<&mut Self>) -> &mut Option<T> { | ||||
|         // SAFETY: The `func` field is not structurally pinned.
 | ||||
|         unsafe { &mut self.get_unchecked_mut().func } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: FnOnce()> WorkItem for ClosureWork<T> { | ||||
|     type Pointer = Pin<KBox<Self>>; | ||||
| 
 | ||||
|     fn run(mut this: Pin<KBox<Self>>) { | ||||
|         if let Some(func) = this.as_mut().project().take() { | ||||
|         if let Some(func) = this.as_mut().project().func.take() { | ||||
|             (func)() | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -6,6 +6,18 @@ | |||
|  | ||||
| # `pin-init` | ||||
| 
 | ||||
| > [!NOTE] | ||||
| >  | ||||
| > This crate was originally named [`pinned-init`], but the migration to | ||||
| > `pin-init` is not yet complete. The `legcay` branch contains the current | ||||
| > version of the `pinned-init` crate & the `main` branch already incorporates | ||||
| > the rename to `pin-init`. | ||||
| > | ||||
| > There are still some changes needed on the kernel side before the migration | ||||
| > can be completed. | ||||
| 
 | ||||
| [`pinned-init`]: https://crates.io/crates/pinned-init | ||||
| 
 | ||||
| <!-- cargo-rdme start --> | ||||
| 
 | ||||
| Library to safely and fallibly initialize pinned `struct`s using in-place constructors. | ||||
|  |  | |||
|  | @ -24,4 +24,6 @@ fn from(_: AllocError) -> Self { | |||
| } | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| fn main() {} | ||||
| fn main() { | ||||
|     let _ = Error; | ||||
| } | ||||
|  |  | |||
|  | @ -740,6 +740,8 @@ macro_rules! stack_try_pin_init { | |||
| /// As already mentioned in the examples above, inside of `pin_init!` a `struct` initializer with
 | ||||
| /// the following modifications is expected:
 | ||||
| /// - Fields that you want to initialize in-place have to use `<-` instead of `:`.
 | ||||
| /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in
 | ||||
| ///   order to run arbitrary code.
 | ||||
| /// - In front of the initializer you can write `&this in` to have access to a [`NonNull<Self>`]
 | ||||
| ///   pointer named `this` inside of the initializer.
 | ||||
| /// - Using struct update syntax one can place `..Zeroable::init_zeroed()` at the very end of the
 | ||||
|  | @ -994,7 +996,7 @@ macro_rules! try_init { | |||
| /// }
 | ||||
| ///
 | ||||
| /// impl<T> Foo<T> {
 | ||||
| ///     fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
 | ||||
| ///     fn project_this(self: Pin<&mut Self>) -> Pin<&mut T> {
 | ||||
| ///         assert_pinned!(Foo<T>, elem, T, inline);
 | ||||
| ///
 | ||||
| ///         // SAFETY: The field is structurally pinned.
 | ||||
|  |  | |||
|  | @ -831,6 +831,17 @@ macro_rules! __pin_data { | |||
|             $($fields)* | ||||
|         } | ||||
| 
 | ||||
|         $crate::__pin_data!(make_pin_projections: | ||||
|             @vis($vis), | ||||
|             @name($name), | ||||
|             @impl_generics($($impl_generics)*), | ||||
|             @ty_generics($($ty_generics)*), | ||||
|             @decl_generics($($decl_generics)*), | ||||
|             @where($($whr)*), | ||||
|             @pinned($($pinned)*), | ||||
|             @not_pinned($($not_pinned)*), | ||||
|         ); | ||||
| 
 | ||||
|         // We put the rest into this const item, because it then will not be accessible to anything
 | ||||
|         // outside.
 | ||||
|         const _: () = { | ||||
|  | @ -980,6 +991,56 @@ fn drop(&mut self) { | |||
|             stringify!($($rest)*), | ||||
|         ); | ||||
|     }; | ||||
|     (make_pin_projections: | ||||
|         @vis($vis:vis), | ||||
|         @name($name:ident), | ||||
|         @impl_generics($($impl_generics:tt)*), | ||||
|         @ty_generics($($ty_generics:tt)*), | ||||
|         @decl_generics($($decl_generics:tt)*), | ||||
|         @where($($whr:tt)*), | ||||
|         @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type:ty),* $(,)?), | ||||
|         @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:ty),* $(,)?), | ||||
|     ) => { | ||||
|         $crate::macros::paste! { | ||||
|             #[doc(hidden)] | ||||
|             $vis struct [< $name Projection >] <'__pin, $($decl_generics)*> { | ||||
|                 $($(#[$($p_attr)*])* $pvis $p_field : ::core::pin::Pin<&'__pin mut $p_type>,)* | ||||
|                 $($(#[$($attr)*])* $fvis $field : &'__pin mut $type,)* | ||||
|                 ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, | ||||
|             } | ||||
| 
 | ||||
|             impl<$($impl_generics)*> $name<$($ty_generics)*> | ||||
|             where $($whr)* | ||||
|             { | ||||
|                 /// Pin-projects all fields of `Self`.
 | ||||
|                 ///
 | ||||
|                 /// These fields are structurally pinned:
 | ||||
|                 $(#[doc = ::core::concat!(" - `", ::core::stringify!($p_field), "`")])* | ||||
|                 ///
 | ||||
|                 /// These fields are **not** structurally pinned:
 | ||||
|                 $(#[doc = ::core::concat!(" - `", ::core::stringify!($field), "`")])* | ||||
|                 #[inline] | ||||
|                 $vis fn project<'__pin>( | ||||
|                     self: ::core::pin::Pin<&'__pin mut Self>, | ||||
|                 ) -> [< $name Projection >] <'__pin, $($ty_generics)*> { | ||||
|                     // SAFETY: we only give access to `&mut` for fields not structurally pinned.
 | ||||
|                     let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; | ||||
|                     [< $name Projection >] { | ||||
|                         $( | ||||
|                             // SAFETY: `$p_field` is structurally pinned.
 | ||||
|                             $(#[$($p_attr)*])* | ||||
|                             $p_field : unsafe { ::core::pin::Pin::new_unchecked(&mut this.$p_field) }, | ||||
|                         )* | ||||
|                         $( | ||||
|                             $(#[$($attr)*])* | ||||
|                             $field : &mut this.$field, | ||||
|                         )* | ||||
|                         ___pin_phantom_data: ::core::marker::PhantomData, | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     (make_pin_data: | ||||
|         @pin_data($pin_data:ident), | ||||
|         @impl_generics($($impl_generics:tt)*), | ||||
|  | @ -988,38 +1049,56 @@ fn drop(&mut self) { | |||
|         @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type:ty),* $(,)?), | ||||
|         @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:ty),* $(,)?), | ||||
|     ) => { | ||||
|         // For every field, we create a projection function according to its projection type. If a
 | ||||
|         // field is structurally pinned, then it must be initialized via `PinInit`, if it is not
 | ||||
|         // structurally pinned, then it can be initialized via `Init`.
 | ||||
|         //
 | ||||
|         // The functions are `unsafe` to prevent accidentally calling them.
 | ||||
|         #[allow(dead_code)] | ||||
|         #[expect(clippy::missing_safety_doc)] | ||||
|         impl<$($impl_generics)*> $pin_data<$($ty_generics)*> | ||||
|         where $($whr)* | ||||
|         { | ||||
|             $( | ||||
|                 $(#[$($p_attr)*])* | ||||
|                 $pvis unsafe fn $p_field<E>( | ||||
|                     self, | ||||
|                     slot: *mut $p_type, | ||||
|                     init: impl $crate::PinInit<$p_type, E>, | ||||
|                 ) -> ::core::result::Result<(), E> { | ||||
|                     // SAFETY: TODO.
 | ||||
|                     unsafe { $crate::PinInit::__pinned_init(init, slot) } | ||||
|                 } | ||||
|             )* | ||||
|             $( | ||||
|                 $(#[$($attr)*])* | ||||
|                 $fvis unsafe fn $field<E>( | ||||
|                     self, | ||||
|                     slot: *mut $type, | ||||
|                     init: impl $crate::Init<$type, E>, | ||||
|                 ) -> ::core::result::Result<(), E> { | ||||
|                     // SAFETY: TODO.
 | ||||
|                     unsafe { $crate::Init::__init(init, slot) } | ||||
|                 } | ||||
|             )* | ||||
|         $crate::macros::paste! { | ||||
|             // For every field, we create a projection function according to its projection type. If a
 | ||||
|             // field is structurally pinned, then it must be initialized via `PinInit`, if it is not
 | ||||
|             // structurally pinned, then it can be initialized via `Init`.
 | ||||
|             //
 | ||||
|             // The functions are `unsafe` to prevent accidentally calling them.
 | ||||
|             #[allow(dead_code)] | ||||
|             #[expect(clippy::missing_safety_doc)] | ||||
|             impl<$($impl_generics)*> $pin_data<$($ty_generics)*> | ||||
|             where $($whr)* | ||||
|             { | ||||
|                 $( | ||||
|                     $(#[$($p_attr)*])* | ||||
|                     $pvis unsafe fn $p_field<E>( | ||||
|                         self, | ||||
|                         slot: *mut $p_type, | ||||
|                         init: impl $crate::PinInit<$p_type, E>, | ||||
|                     ) -> ::core::result::Result<(), E> { | ||||
|                         // SAFETY: TODO.
 | ||||
|                         unsafe { $crate::PinInit::__pinned_init(init, slot) } | ||||
|                     } | ||||
| 
 | ||||
|                     $(#[$($p_attr)*])* | ||||
|                     $pvis unsafe fn [<__project_ $p_field>]<'__slot>( | ||||
|                         self, | ||||
|                         slot: &'__slot mut $p_type, | ||||
|                     ) -> ::core::pin::Pin<&'__slot mut $p_type> { | ||||
|                         ::core::pin::Pin::new_unchecked(slot) | ||||
|                     } | ||||
|                 )* | ||||
|                 $( | ||||
|                     $(#[$($attr)*])* | ||||
|                     $fvis unsafe fn $field<E>( | ||||
|                         self, | ||||
|                         slot: *mut $type, | ||||
|                         init: impl $crate::Init<$type, E>, | ||||
|                     ) -> ::core::result::Result<(), E> { | ||||
|                         // SAFETY: TODO.
 | ||||
|                         unsafe { $crate::Init::__init(init, slot) } | ||||
|                     } | ||||
| 
 | ||||
|                     $(#[$($attr)*])* | ||||
|                     $fvis unsafe fn [<__project_ $field>]<'__slot>( | ||||
|                         self, | ||||
|                         slot: &'__slot mut $type, | ||||
|                     ) -> &'__slot mut $type { | ||||
|                         slot | ||||
|                     } | ||||
|                 )* | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | @ -1202,6 +1281,21 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|         // have been initialized. Therefore we can now dismiss the guards by forgetting them.
 | ||||
|         $(::core::mem::forget($guards);)* | ||||
|     }; | ||||
|     (init_slot($($use_data:ident)?): | ||||
|         @data($data:ident), | ||||
|         @slot($slot:ident), | ||||
|         @guards($($guards:ident,)*), | ||||
|         // arbitrary code block
 | ||||
|         @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), | ||||
|     ) => { | ||||
|         { $($code)* } | ||||
|         $crate::__init_internal!(init_slot($($use_data)?): | ||||
|             @data($data), | ||||
|             @slot($slot), | ||||
|             @guards($($guards,)*), | ||||
|             @munch_fields($($rest)*), | ||||
|         ); | ||||
|     }; | ||||
|     (init_slot($use_data:ident): // `use_data` is present, so we use the `data` to init fields.
 | ||||
|         @data($data:ident), | ||||
|         @slot($slot:ident), | ||||
|  | @ -1216,6 +1310,13 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|         // return when an error/panic occurs.
 | ||||
|         // We also use the `data` to require the correct trait (`Init` or `PinInit`) for `$field`.
 | ||||
|         unsafe { $data.$field(::core::ptr::addr_of_mut!((*$slot).$field), init)? }; | ||||
|         // SAFETY:
 | ||||
|         // - the project function does the correct field projection,
 | ||||
|         // - the field has been initialized,
 | ||||
|         // - the reference is only valid until the end of the initializer.
 | ||||
|         #[allow(unused_variables)] | ||||
|         let $field = $crate::macros::paste!(unsafe { $data.[< __project_ $field >](&mut (*$slot).$field) }); | ||||
| 
 | ||||
|         // Create the drop guard:
 | ||||
|         //
 | ||||
|         // We rely on macro hygiene to make it impossible for users to access this local variable.
 | ||||
|  | @ -1247,6 +1348,14 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|         // SAFETY: `slot` is valid, because we are inside of an initializer closure, we
 | ||||
|         // return when an error/panic occurs.
 | ||||
|         unsafe { $crate::Init::__init(init, ::core::ptr::addr_of_mut!((*$slot).$field))? }; | ||||
| 
 | ||||
|         // SAFETY:
 | ||||
|         // - the field is not structurally pinned, since the line above must compile,
 | ||||
|         // - the field has been initialized,
 | ||||
|         // - the reference is only valid until the end of the initializer.
 | ||||
|         #[allow(unused_variables)] | ||||
|         let $field = unsafe { &mut (*$slot).$field }; | ||||
| 
 | ||||
|         // Create the drop guard:
 | ||||
|         //
 | ||||
|         // We rely on macro hygiene to make it impossible for users to access this local variable.
 | ||||
|  | @ -1265,7 +1374,7 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|             ); | ||||
|         } | ||||
|     }; | ||||
|     (init_slot($($use_data:ident)?): | ||||
|     (init_slot(): // No `use_data`, so all fields are not structurally pinned
 | ||||
|         @data($data:ident), | ||||
|         @slot($slot:ident), | ||||
|         @guards($($guards:ident,)*), | ||||
|  | @ -1279,6 +1388,15 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|             // SAFETY: The memory at `slot` is uninitialized.
 | ||||
|             unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; | ||||
|         } | ||||
| 
 | ||||
|         #[allow(unused_variables)] | ||||
|         // SAFETY:
 | ||||
|         // - the field is not structurally pinned, since no `use_data` was required to create this
 | ||||
|         //   initializer,
 | ||||
|         // - the field has been initialized,
 | ||||
|         // - the reference is only valid until the end of the initializer.
 | ||||
|         let $field = unsafe { &mut (*$slot).$field }; | ||||
| 
 | ||||
|         // Create the drop guard:
 | ||||
|         //
 | ||||
|         // We rely on macro hygiene to make it impossible for users to access this local variable.
 | ||||
|  | @ -1289,7 +1407,7 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|                 $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) | ||||
|             }; | ||||
| 
 | ||||
|             $crate::__init_internal!(init_slot($($use_data)?): | ||||
|             $crate::__init_internal!(init_slot(): | ||||
|                 @data($data), | ||||
|                 @slot($slot), | ||||
|                 @guards([< __ $field _guard >], $($guards,)*), | ||||
|  | @ -1297,6 +1415,59 @@ fn assert_zeroable<T: $crate::Zeroable>(_: *mut T) {} | |||
|             ); | ||||
|         } | ||||
|     }; | ||||
|     (init_slot($use_data:ident): | ||||
|         @data($data:ident), | ||||
|         @slot($slot:ident), | ||||
|         @guards($($guards:ident,)*), | ||||
|         // Init by-value.
 | ||||
|         @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), | ||||
|     ) => { | ||||
|         { | ||||
|             $(let $field = $val;)? | ||||
|             // Initialize the field.
 | ||||
|             //
 | ||||
|             // SAFETY: The memory at `slot` is uninitialized.
 | ||||
|             unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; | ||||
|         } | ||||
|         // SAFETY:
 | ||||
|         // - the project function does the correct field projection,
 | ||||
|         // - the field has been initialized,
 | ||||
|         // - the reference is only valid until the end of the initializer.
 | ||||
|         #[allow(unused_variables)] | ||||
|         let $field = $crate::macros::paste!(unsafe { $data.[< __project_ $field >](&mut (*$slot).$field) }); | ||||
| 
 | ||||
|         // Create the drop guard:
 | ||||
|         //
 | ||||
|         // We rely on macro hygiene to make it impossible for users to access this local variable.
 | ||||
|         // We use `paste!` to create new hygiene for `$field`.
 | ||||
|         $crate::macros::paste! { | ||||
|             // SAFETY: We forget the guard later when initialization has succeeded.
 | ||||
|             let [< __ $field _guard >] = unsafe { | ||||
|                 $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) | ||||
|             }; | ||||
| 
 | ||||
|             $crate::__init_internal!(init_slot($use_data): | ||||
|                 @data($data), | ||||
|                 @slot($slot), | ||||
|                 @guards([< __ $field _guard >], $($guards,)*), | ||||
|                 @munch_fields($($rest)*), | ||||
|             ); | ||||
|         } | ||||
|     }; | ||||
|     (make_initializer: | ||||
|         @slot($slot:ident), | ||||
|         @type_name($t:path), | ||||
|         @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), | ||||
|         @acc($($acc:tt)*), | ||||
|     ) => { | ||||
|         // code blocks are ignored for the initializer check
 | ||||
|         $crate::__init_internal!(make_initializer: | ||||
|             @slot($slot), | ||||
|             @type_name($t), | ||||
|             @munch_fields($($rest)*), | ||||
|             @acc($($acc)*), | ||||
|         ); | ||||
|     }; | ||||
|     (make_initializer: | ||||
|         @slot($slot:ident), | ||||
|         @type_name($t:path), | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <uapi/asm-generic/ioctl.h> | ||||
| #include <uapi/drm/drm.h> | ||||
| #include <uapi/drm/nova_drm.h> | ||||
| #include <uapi/drm/panthor_drm.h> | ||||
| #include <uapi/linux/mdio.h> | ||||
| #include <uapi/linux/mii.h> | ||||
| #include <uapi/linux/ethtool.h> | ||||
|  |  | |||
|  | @ -7,15 +7,19 @@ | |||
| use kernel::{ | ||||
|     bindings, | ||||
|     device::Core, | ||||
|     dma::{CoherentAllocation, Device, DmaMask}, | ||||
|     pci, | ||||
|     dma::{CoherentAllocation, DataDirection, Device, DmaMask}, | ||||
|     page, pci, | ||||
|     prelude::*, | ||||
|     scatterlist::{Owned, SGTable}, | ||||
|     types::ARef, | ||||
| }; | ||||
| 
 | ||||
| #[pin_data(PinnedDrop)] | ||||
| struct DmaSampleDriver { | ||||
|     pdev: ARef<pci::Device>, | ||||
|     ca: CoherentAllocation<MyStruct>, | ||||
|     #[pin] | ||||
|     sgt: SGTable<Owned<VVec<u8>>>, | ||||
| } | ||||
| 
 | ||||
| const TEST_VALUES: [(u32, u32); 5] = [ | ||||
|  | @ -70,21 +74,30 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self | |||
|             kernel::dma_write!(ca[i] = MyStruct::new(value.0, value.1))?; | ||||
|         } | ||||
| 
 | ||||
|         let drvdata = KBox::new( | ||||
|             Self { | ||||
|         let size = 4 * page::PAGE_SIZE; | ||||
|         let pages = VVec::with_capacity(size, GFP_KERNEL)?; | ||||
| 
 | ||||
|         let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL); | ||||
| 
 | ||||
|         let drvdata = KBox::pin_init( | ||||
|             try_pin_init!(Self { | ||||
|                 pdev: pdev.into(), | ||||
|                 ca, | ||||
|             }, | ||||
|                 sgt <- sgt, | ||||
|             }), | ||||
|             GFP_KERNEL, | ||||
|         )?; | ||||
| 
 | ||||
|         Ok(drvdata.into()) | ||||
|         Ok(drvdata) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Drop for DmaSampleDriver { | ||||
|     fn drop(&mut self) { | ||||
|         dev_info!(self.pdev.as_ref(), "Unload DMA test driver.\n"); | ||||
| #[pinned_drop] | ||||
| impl PinnedDrop for DmaSampleDriver { | ||||
|     fn drop(self: Pin<&mut Self>) { | ||||
|         let dev = self.pdev.as_ref(); | ||||
| 
 | ||||
|         dev_info!(dev, "Unload DMA test driver.\n"); | ||||
| 
 | ||||
|         for (i, value) in TEST_VALUES.into_iter().enumerate() { | ||||
|             let val0 = kernel::dma_read!(self.ca[i].h); | ||||
|  | @ -99,6 +112,10 @@ fn drop(&mut self) { | |||
|                 assert_eq!(val1, value.1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (i, entry) in self.sgt.iter().enumerate() { | ||||
|             dev_info!(dev, "Entry[{}]: DMA address: {:#x}", i, entry.dma_address()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -78,8 +78,8 @@ fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> Result<Pin<KBox<Self> | |||
| 
 | ||||
|         let drvdata = KBox::pin_init( | ||||
|             try_pin_init!(Self { | ||||
|                 pdev: pdev.into(), | ||||
|                 bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci")), | ||||
|                 pdev: pdev.into(), | ||||
|                 index: *info, | ||||
|             }), | ||||
|             GFP_KERNEL, | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Dave Airlie
						Dave Airlie