mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

So far Devres uses an inner memory allocation and reference count, i.e. an inner Arc, in order to ensure that the devres callback can't run into a use-after-free in case where the Devres object is dropped while the devres callback runs concurrently. Instead, use a completion in order to avoid a potential UAF: In Devres::drop(), if we detect that we can't remove the devres action anymore, we wait for the completion that is completed from the devres callback. If, in turn, we were able to successfully remove the devres action, we can just go ahead. This, again, allows us to get rid of the internal Arc, and instead let Devres consume an `impl PinInit<T, E>` in order to return an `impl PinInit<Devres<T>, E>`, which enables us to get away with less memory allocations. Additionally, having the resulting explicit synchronization in Devres::drop() prevents potential subtle undesired side effects of the devres callback dropping the final Arc reference asynchronously within the devres callback. Reviewed-by: Benno Lossin <lossin@kernel.org> Reviewed-by: Boqun Feng <boqun.feng@gmail.com> Link: https://lore.kernel.org/r/20250626200054.243480-4-dakr@kernel.org [ Move '# Invariants' below '# Examples'. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
192 lines
4.7 KiB
Rust
192 lines
4.7 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, sync::Arc};
|
|
|
|
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: Arc<Devres<Bar0>>,
|
|
fw: Firmware,
|
|
}
|
|
|
|
impl Gpu {
|
|
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
|
|
);
|
|
|
|
Ok(pin_init!(Self {
|
|
spec,
|
|
bar: devres_bar,
|
|
fw
|
|
}))
|
|
}
|
|
}
|