diff options
Diffstat (limited to 'drivers/gpu/nova-core')
-rw-r--r-- | drivers/gpu/nova-core/Kconfig | 15 | ||||
-rw-r--r-- | drivers/gpu/nova-core/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/nova-core/driver.rs | 54 | ||||
-rw-r--r-- | drivers/gpu/nova-core/firmware.rs | 81 | ||||
-rw-r--r-- | drivers/gpu/nova-core/gpu.rs | 191 | ||||
-rw-r--r-- | drivers/gpu/nova-core/nova_core.rs | 22 | ||||
-rw-r--r-- | drivers/gpu/nova-core/regs.rs | 39 | ||||
-rw-r--r-- | drivers/gpu/nova-core/regs/macros.rs | 380 | ||||
-rw-r--r-- | drivers/gpu/nova-core/util.rs | 21 |
9 files changed, 806 insertions, 0 deletions
diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig new file mode 100644 index 000000000000..8726d80d6ba4 --- /dev/null +++ b/drivers/gpu/nova-core/Kconfig @@ -0,0 +1,15 @@ +config NOVA_CORE + tristate "Nova Core GPU driver" + depends on PCI + depends on RUST + depends on RUST_FW_LOADER_ABSTRACTIONS + select AUXILIARY_BUS + default n + help + Choose this if you want to build the Nova Core driver for Nvidia + GPUs based on the GPU System Processor (GSP). This is true for Turing + and later GPUs. + + This driver is work in progress and may not be functional. + + If M is selected, the module will be called nova_core. diff --git a/drivers/gpu/nova-core/Makefile b/drivers/gpu/nova-core/Makefile new file mode 100644 index 000000000000..2d78c50126e1 --- /dev/null +++ b/drivers/gpu/nova-core/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_NOVA_CORE) += nova_core.o diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs new file mode 100644 index 000000000000..8c86101c26cb --- /dev/null +++ b/drivers/gpu/nova-core/driver.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 + +use kernel::{auxiliary, bindings, c_str, device::Core, pci, prelude::*}; + +use crate::gpu::Gpu; + +#[pin_data] +pub(crate) struct NovaCore { + #[pin] + pub(crate) gpu: Gpu, + _reg: auxiliary::Registration, +} + +const BAR0_SIZE: usize = 8; +pub(crate) type Bar0 = pci::Bar<BAR0_SIZE>; + +kernel::pci_device_table!( + PCI_TABLE, + MODULE_PCI_TABLE, + <NovaCore as pci::Driver>::IdInfo, + [( + pci::DeviceId::from_id(bindings::PCI_VENDOR_ID_NVIDIA, bindings::PCI_ANY_ID as _), + () + )] +); + +impl pci::Driver for NovaCore { + type IdInfo = (); + const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE; + + fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> { + dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n"); + + pdev.enable_device_mem()?; + pdev.set_master(); + + let bar = pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?; + + let this = KBox::pin_init( + try_pin_init!(Self { + gpu <- Gpu::new(pdev, bar)?, + _reg: auxiliary::Registration::new( + pdev.as_ref(), + c_str!("nova-drm"), + 0, // TODO: Once it lands, use XArray; for now we don't use the ID. + crate::MODULE_NAME + )?, + }), + GFP_KERNEL, + )?; + + Ok(this) + } +} diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs new file mode 100644 index 000000000000..4b8a38358a4f --- /dev/null +++ b/drivers/gpu/nova-core/firmware.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Contains structures and functions dedicated to the parsing, building and patching of firmwares +//! to be loaded into a given execution unit. + +use kernel::device; +use kernel::firmware; +use kernel::prelude::*; +use kernel::str::CString; + +use crate::gpu; +use crate::gpu::Chipset; + +pub(crate) const FIRMWARE_VERSION: &str = "535.113.01"; + +/// 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, +} + +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 request = |name_| { + CString::try_from_fmt(fmt!("nvidia/{}/gsp/{}-{}.bin", &*chip_name, name_, ver)) + .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")?, + }) + } +} + +pub(crate) struct ModInfoBuilder<const N: usize>(firmware::ModInfoBuilder<N>); + +impl<const N: usize> ModInfoBuilder<N> { + const fn make_entry_file(self, chipset: &str, fw: &str) -> Self { + ModInfoBuilder( + self.0 + .new_entry() + .push("nvidia/") + .push(chipset) + .push("/gsp/") + .push(fw) + .push("-") + .push(FIRMWARE_VERSION) + .push(".bin"), + ) + } + + const fn make_entry_chipset(self, chipset: &str) -> Self { + self.make_entry_file(chipset, "booter_load") + .make_entry_file(chipset, "booter_unload") + .make_entry_file(chipset, "bootloader") + .make_entry_file(chipset, "gsp") + } + + pub(crate) const fn create( + module_name: &'static kernel::str::CStr, + ) -> firmware::ModInfoBuilder<N> { + 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]); + i += 1; + } + + this.0 + } +} diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs new file mode 100644 index 000000000000..60b86f370284 --- /dev/null +++ b/drivers/gpu/nova-core/gpu.rs @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 + +use kernel::{device, devres::Devres, error::code::*, pci, prelude::*}; + +use crate::driver::Bar0; +use crate::firmware::{Firmware, FIRMWARE_VERSION}; +use crate::regs; +use crate::util; +use core::fmt; + +macro_rules! define_chipset { + ({ $($variant:ident = $value:expr),* $(,)* }) => + { + /// Enum representation of the GPU chipset. + #[derive(fmt::Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] + pub(crate) enum Chipset { + $($variant = $value),*, + } + + impl Chipset { + pub(crate) const ALL: &'static [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() + ), )* + ]; + } + + // TODO replace with something like derive(FromPrimitive) + impl TryFrom<u32> for Chipset { + type Error = kernel::error::Error; + + fn try_from(value: u32) -> Result<Self, Self::Error> { + match value { + $( $value => Ok(Chipset::$variant), )* + _ => Err(ENODEV), + } + } + } + } +} + +define_chipset!({ + // Turing + TU102 = 0x162, + TU104 = 0x164, + TU106 = 0x166, + TU117 = 0x167, + TU116 = 0x168, + // Ampere + GA100 = 0x170, + GA102 = 0x172, + GA103 = 0x173, + GA104 = 0x174, + GA106 = 0x176, + GA107 = 0x177, + // Ada + AD102 = 0x192, + AD103 = 0x193, + AD104 = 0x194, + AD106 = 0x196, + AD107 = 0x197, +}); + +impl Chipset { + pub(crate) fn arch(&self) -> Architecture { + match self { + Self::TU102 | Self::TU104 | Self::TU106 | Self::TU117 | Self::TU116 => { + Architecture::Turing + } + Self::GA100 | Self::GA102 | Self::GA103 | Self::GA104 | Self::GA106 | Self::GA107 => { + Architecture::Ampere + } + Self::AD102 | Self::AD103 | Self::AD104 | Self::AD106 | Self::AD107 => { + Architecture::Ada + } + } + } +} + +// TODO +// +// The resulting strings are used to generate firmware paths, hence the +// generated strings have to be stable. +// +// Hence, replace with something like strum_macros derive(Display). +// +// For now, redirect to fmt::Debug for convenience. +impl fmt::Display for Chipset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +/// Enum representation of the GPU generation. +#[derive(fmt::Debug)] +pub(crate) enum Architecture { + Turing = 0x16, + Ampere = 0x17, + Ada = 0x19, +} + +impl TryFrom<u8> for Architecture { + type Error = Error; + + fn try_from(value: u8) -> Result<Self> { + match value { + 0x16 => Ok(Self::Turing), + 0x17 => Ok(Self::Ampere), + 0x19 => Ok(Self::Ada), + _ => Err(ENODEV), + } + } +} + +pub(crate) struct Revision { + major: u8, + minor: u8, +} + +impl Revision { + fn from_boot0(boot0: regs::NV_PMC_BOOT_0) -> Self { + Self { + major: boot0.major_revision(), + minor: boot0.minor_revision(), + } + } +} + +impl fmt::Display for Revision { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x}.{:x}", self.major, self.minor) + } +} + +/// Structure holding the metadata of the GPU. +pub(crate) struct Spec { + chipset: Chipset, + /// The revision of the chipset. + revision: Revision, +} + +impl Spec { + fn new(bar: &Bar0) -> Result<Spec> { + let boot0 = regs::NV_PMC_BOOT_0::read(bar); + + Ok(Self { + chipset: boot0.chipset()?, + revision: Revision::from_boot0(boot0), + }) + } +} + +/// Structure holding the resources required to operate the GPU. +#[pin_data] +pub(crate) struct Gpu { + spec: Spec, + /// MMIO mapping of PCI BAR 0 + bar: Devres<Bar0>, + fw: Firmware, +} + +impl Gpu { + pub(crate) fn new( + pdev: &pci::Device<device::Bound>, + devres_bar: 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 + ); + + Ok(pin_init!(Self { + spec, + bar: devres_bar, + fw + })) + } +} diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs new file mode 100644 index 000000000000..618632f0abcc --- /dev/null +++ b/drivers/gpu/nova-core/nova_core.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Nova Core GPU Driver + +mod driver; +mod firmware; +mod gpu; +mod regs; +mod util; + +pub(crate) const MODULE_NAME: &kernel::str::CStr = <LocalModule as kernel::ModuleMetadata>::NAME; + +kernel::module_pci_driver! { + type: driver::NovaCore, + name: "NovaCore", + author: "Danilo Krummrich", + description: "Nova Core GPU driver", + license: "GPL v2", + firmware: [], +} + +kernel::module_firmware!(firmware::ModInfoBuilder); diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs new file mode 100644 index 000000000000..5a1273230306 --- /dev/null +++ b/drivers/gpu/nova-core/regs.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Required to retain the original register names used by OpenRM, which are all capital snake case +// but are mapped to types. +#![allow(non_camel_case_types)] + +#[macro_use] +mod macros; + +use crate::gpu::{Architecture, Chipset}; +use kernel::prelude::*; + +/* PMC */ + +register!(NV_PMC_BOOT_0 @ 0x00000000, "Basic revision information about the GPU" { + 3:0 minor_revision as u8, "Minor revision of the chip"; + 7:4 major_revision as u8, "Major revision of the chip"; + 8:8 architecture_1 as u8, "MSB of the architecture"; + 23:20 implementation as u8, "Implementation version of the architecture"; + 28:24 architecture_0 as u8, "Lower bits of the architecture"; +}); + +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()), + ) + } + + /// Combines `architecture` and `implementation` to obtain a code unique to the chipset. + pub(crate) fn chipset(self) -> Result<Chipset> { + self.architecture() + .map(|arch| { + ((arch as u32) << Self::IMPLEMENTATION.len()) | self.implementation() as u32 + }) + .and_then(Chipset::try_from) + } +} diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs new file mode 100644 index 000000000000..7ecc70efb3cd --- /dev/null +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! 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. + +/// 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. +/// +/// Example: +/// +/// ```no_run +/// register!(BOOT_0 @ 0x00000100, "Basic revision information about the GPU" { +/// 3:0 minor_revision as u8, "Minor revision of the chip"; +/// 7:4 major_revision as u8, "Major revision of the chip"; +/// 28:20 chipset as u32 ?=> Chipset, "Chipset model"; +/// }); +/// ``` +/// +/// 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 +/// significant bits of the register. Each field can be accessed and modified using accessor +/// methods: +/// +/// ```no_run +/// // Read from the register's defined offset (0x100). +/// 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. +/// 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: +/// BOOT_0::alter(&bar, |r| r.set_major_revision(3).set_minor_revision(10)); +/// ``` +/// +/// Fields can be 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> => <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. +/// +/// 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); +/// ``` +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 $(, $comment)?); + register!(@field_accessors $name { $($fields)* }); + register!(@io $name @ $offset); + }; + + // Creates a register at a relative offset from a base address. + ( + $name:ident @ + $offset:literal $(, $comment:literal)? { + $($fields:tt)* + } + ) => { + register!(@common $name $(, $comment)?); + register!(@field_accessors $name { $($fields)* }); + register!(@io$name @ + $offset); + }; + + // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`, + // and conversion to regular `u32`). + (@common $name:ident $(, $comment:literal)?) => { + $( + #[doc=$comment] + )? + #[repr(transparent)] + #[derive(Clone, Copy, Default)] + pub(crate) struct $name(u32); + + // TODO: 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; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + + impl ::core::convert::From<$name> for u32 { + fn from(reg: $name) -> u32 { + reg.0 + } + } + }; + + // Defines all the field getter/methods methods for `$name`. + ( + @field_accessors $name:ident { + $($hi:tt:$lo:tt $field:ident as $type:tt + $(?=> $try_into_type:ty)? + $(=> $into_type:ty)? + $(, $comment:literal)? + ; + )* + } + ) => { + $( + register!(@check_field_bounds $hi:$lo $field as $type); + )* + + #[allow(dead_code)] + impl $name { + $( + register!(@field_accessor $name $hi:$lo $field as $type + $(?=> $try_into_type)? + $(=> $into_type)? + $(, $comment)? + ; + ); + )* + } + }; + + // Boolean fields must have `$hi == $lo`. + (@check_field_bounds $hi:tt:$lo:tt $field:ident as bool) => { + #[allow(clippy::eq_op)] + const _: () = { + kernel::build_assert!( + $hi == $lo, + concat!("boolean field `", stringify!($field), "` covers more than one bit") + ); + }; + }; + + // Non-boolean fields must have `$hi >= $lo`. + (@check_field_bounds $hi:tt:$lo:tt $field:ident as $type:tt) => { + #[allow(clippy::eq_op)] + const _: () = { + kernel::build_assert!( + $hi >= $lo, + concat!("field `", stringify!($field), "`'s MSB is smaller than its LSB") + ); + }; + }; + + // Catches fields defined as `bool` and convert them into a boolean value. + ( + @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool => $into_type:ty + $(, $comment:literal)?; + ) => { + register!( + @leaf_accessor $name $hi:$lo $field as bool + { |f| <$into_type>::from(if f != 0 { true } else { false }) } + $into_type => $into_type $(, $comment)?; + ); + }; + + // Shortcut for fields defined as `bool` without the `=>` syntax. + ( + @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool $(, $comment:literal)?; + ) => { + register!(@field_accessor $name $hi:$lo $field as bool => bool $(, $comment)?;); + }; + + // Catches the `?=>` syntax for non-boolean fields. + ( + @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 + { |f| <$try_into_type>::try_from(f as $type) } $try_into_type => + ::core::result::Result< + $try_into_type, + <$try_into_type as ::core::convert::TryFrom<$type>>::Error + > + $(, $comment)?;); + }; + + // Catches the `=>` syntax for non-boolean fields. + ( + @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 + { |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;); + }; + + // Shortcut for fields defined as non-`bool` without the `=>` or `?=>` syntax. + ( + @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt + $(, $comment:literal)?; + ) => { + register!(@field_accessor $name $hi:$lo $field as $type => $type $(, $comment)?;); + }; + + // Generates the accessor methods for a single field. + ( + @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:ty + { $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 _MASK>]: u32 = ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1); + const [<$field:upper _SHIFT>]: u32 = Self::[<$field:upper _MASK>].trailing_zeros(); + ); + + $( + #[doc="Returns the value of this field:"] + #[doc=$comment] + )? + #[inline] + pub(crate) fn $field(self) -> $res_type { + kernel::macros::paste!( + const MASK: u32 = $name::[<$field:upper _MASK>]; + const SHIFT: u32 = $name::[<$field:upper _SHIFT>]; + ); + let field = ((self.0 & MASK) >> SHIFT); + + $process(field) + } + + kernel::macros::paste!( + $( + #[doc="Sets the value of this field:"] + #[doc=$comment] + )? + #[inline] + 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>]; + let value = ((value as u32) << SHIFT) & MASK; + self.0 = (self.0 & !MASK) | value; + + self + } + ); + }; + + // Creates the IO accessors for a fixed offset register. + (@io $name:ident @ $offset:literal) => { + #[allow(dead_code)] + impl $name { + #[inline] + 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] + 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] + pub(crate) fn alter<const SIZE: usize, T, F>( + io: &T, + f: F, + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io)); + reg.write(io); + } + } + }; + + // Create the IO accessors for a relative offset register. + (@io $name:ident @ + $offset:literal) => { + #[allow(dead_code)] + impl $name { + #[inline] + pub(crate) fn read<const SIZE: usize, T>( + io: &T, + base: usize, + ) -> Self where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + Self(io.read32(base + $offset)) + } + + #[inline] + pub(crate) fn write<const SIZE: usize, T>( + self, + io: &T, + base: usize, + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + io.write32(self.0, base + $offset) + } + + #[inline] + pub(crate) fn alter<const SIZE: usize, T, F>( + io: &T, + base: usize, + f: F, + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + 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>( + io: &T, + base: usize, + ) -> ::kernel::error::Result<Self> where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + io.try_read32(base + $offset).map(Self) + } + + #[inline] + pub(crate) fn try_write<const SIZE: usize, T>( + self, + io: &T, + base: usize, + ) -> ::kernel::error::Result<()> where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + io.try_write32(self.0, base + $offset) + } + + #[inline] + pub(crate) fn try_alter<const SIZE: usize, T, F>( + io: &T, + base: usize, + f: F, + ) -> ::kernel::error::Result<()> 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) + } + } + }; +} diff --git a/drivers/gpu/nova-core/util.rs b/drivers/gpu/nova-core/util.rs new file mode 100644 index 000000000000..332a64cfc6a9 --- /dev/null +++ b/drivers/gpu/nova-core/util.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +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."), + } +} |