drm: nova: Add an info ioctl

Add an extensible info ioctl and use it to report basic GPU information.
One of the first things userspace needs to know about a GPU is its
architecture and which chip it is, so add those as the first fields in
the GPU info result. The chip identifier is opaque to userspace and is
only meaningful when compared against the values of enum
drm_nova_chipid.

The ioctl selects an information type by ID and writes the result through
a sized userspace buffer. The kernel truncates results to the supplied
size, allowing information structures to grow and new logical information
groups to be added without introducing new ioctls. Passing a NULL data
pointer returns the size of the requested information structure instead.

Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Alistair Popple <apopple@nvidia.com>
Link: https://patch.msgid.link/20260909064506.910162-8-apopple@nvidia.com
[ Avoid 'as' casts (add From<Chipset> for u32, From<Architecture> for
  u32 and use IntoSafeCast, IntoSafeCastArch); improve / simplify a few
  comments. - Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
Alistair Popple 2026-09-09 16:45:00 +10:00 committed by Danilo Krummrich
parent b40f5ed85c
commit ce6e59897b
5 changed files with 162 additions and 10 deletions

View File

@ -33,7 +33,6 @@ pub(crate) struct Nova<'bound> {
/// DRM registration data, accessible from ioctl handlers via the registration guard.
pub(crate) struct DrmRegData<'bound> {
#[expect(unused)]
pub(crate) api: NovaCoreApiHandle<'bound>,
}
@ -98,5 +97,6 @@ impl drm::Driver for NovaDriver {
(NOVA_GETPARAM, drm_nova_getparam, ioctl::RENDER_ALLOW, File::get_param),
(NOVA_GEM_CREATE, drm_nova_gem_create, ioctl::AUTH | ioctl::RENDER_ALLOW, File::gem_create),
(NOVA_GEM_INFO, drm_nova_gem_info, ioctl::AUTH | ioctl::RENDER_ALLOW, File::gem_info),
(NOVA_INFO, drm_nova_info, ioctl::RENDER_ALLOW, File::info),
}
}

View File

@ -15,13 +15,63 @@
gem::BaseObject,
Registered, //
},
num::casts::{
arch::IntoSafeCastArch,
IntoSafeCast, //
},
pci,
prelude::*,
transmute::AsBytes,
uaccess::UserSlice,
uapi,
};
pub(crate) struct File;
/// GPU information returned to userspace.
#[repr(transparent)]
struct GpuInfo(uapi::drm_nova_info_gpu);
impl GpuInfo {
/// Collects the GPU information reported to userspace.
///
/// This is fallible so that unexpected GSP behaviour, such as a malformed name string, is
/// reported to userspace instead of being silently replaced with a made up value. When adding
/// a field, take care that a value the GSP simply does not provide, for example because the
/// firmware predates it, does not cause a failure. Such fields must fall back to zero or
/// another documented default instead.
fn new(reg_data: &DrmRegData<'_>) -> Result<Self> {
let spec = reg_data.api.with(|api| api.get_ref().spec());
let info = uapi::drm_nova_info_gpu {
architecture: spec.chipset.arch().into(),
chipid: spec.chipset.into(),
};
Ok(Self(info))
}
}
// SAFETY: `GpuInfo` has no interior mutability, and there are no uninitialized bytes.
unsafe impl AsBytes for GpuInfo {}
fn write_info<T: AsBytes>(info: &mut uapi::drm_nova_info, value: T) -> Result {
// A NULL data pointer requests the size of the information structure.
if info.data == 0 {
info.size = size_of::<T>().try_into()?;
return Ok(());
}
let mut writer = UserSlice::new(
UserPtr::from_addr(info.data.into_safe_cast_arch()),
info.size.into_safe_cast(),
)
.writer();
info.size = writer.write_truncated(&value)?.try_into()?;
Ok(())
}
impl drm::file::DriverFile for File {
type Driver = NovaDriver;
@ -41,7 +91,7 @@ pub(crate) fn get_param(
let adev: &auxiliary::Device<Bound> = dev.as_ref();
let pdev: &pci::Device<Bound> = adev.parent().try_into()?;
let value = match getparam.param as u32 {
let value = match getparam.param.try_into()? {
uapi::NOVA_GETPARAM_VRAM_BAR_SIZE => pdev.resource_len(1)?,
_ => return Err(EINVAL),
};
@ -78,4 +128,19 @@ pub(crate) fn gem_info(
Ok(0)
}
/// IOCTL: info: Query device information.
pub(crate) fn info(
_dev: &NovaDevice<Registered>,
reg_data: &DrmRegData<'_>,
info: &mut uapi::drm_nova_info,
_file: &drm::File<File>,
) -> Result<u32> {
match info.id {
uapi::DRM_NOVA_INFO_GPU => write_info(info, GpuInfo::new(reg_data)?)?,
_ => return Err(EINVAL),
}
Ok(0)
}
}

View File

@ -12,11 +12,12 @@
types::ForLt, //
};
pub use crate::gpu::Spec;
use crate::gpu::Gpu;
/// API handle for the auxiliary bus child drivers to interact with nova-core.
pub struct NovaCoreApi<'bound> {
#[expect(unused)]
pub(crate) gpu: Pin<&'bound Gpu<'bound>>,
}
@ -26,6 +27,11 @@ impl NovaCoreApi<'_> {
pub fn of(adev: &auxiliary::Device<Bound>) -> Result<NovaCoreApiHandle<'_>> {
NovaCoreApiHandle::of(adev)
}
/// Returns the GPU [`Spec`].
pub fn spec(&self) -> &Spec {
&self.gpu.spec
}
}
/// Expose a handle to nova-core API

View File

@ -57,12 +57,14 @@ macro_rules! define_chipset {
/// Enum representation of the GPU chipset.
#[derive(fmt::Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[repr(u32)]
pub(crate) enum Chipset {
#[allow(missing_docs)]
pub enum Chipset {
$($variant = uapi::[<drm_nova_chipid_NOVA_DRM_CHIPID_ $variant:upper>]),*,
}
impl Chipset {
pub(crate) const ALL: &'static [Chipset] = &[
/// All chipsets known to the driver.
pub const ALL: &'static [Chipset] = &[
$( Chipset::$variant, )*
];
@ -136,7 +138,8 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
});
impl Chipset {
pub(crate) const fn arch(self) -> Architecture {
/// Returns the [`Architecture`] generation of this chipset.
pub const fn arch(self) -> Architecture {
match self {
Self::TU102 | Self::TU104 | Self::TU106 | Self::TU117 | Self::TU116 => {
Architecture::Turing
@ -166,6 +169,14 @@ pub(crate) fn mmu_version(self) -> MmuVersion {
}
}
impl From<Chipset> for u32 {
#[inline]
fn from(value: Chipset) -> Self {
// CAST: `Chipset` is `repr(u32)` and can thus be cast losslessly.
value as u32
}
}
// TODO
//
// The resulting strings are used to generate firmware paths, hence the
@ -184,18 +195,32 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// Enum representation of the GPU generation.
#[derive(fmt::Debug, Copy, Clone)]
#[repr(u32)]
pub(crate) enum Architecture with TryFrom<Bounded<u32, 6>> {
pub enum Architecture with TryFrom<Bounded<u32, 6>> {
/// Turing (TU1xx).
Turing = uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_TURING,
/// Ampere (GA10x).
Ampere = uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_AMPERE,
/// Hopper (GH100).
Hopper = uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_HOPPER,
/// Ada Lovelace (AD10x).
Ada = uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_ADA,
/// Blackwell (GB10x).
BlackwellGB10x =
uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_BLACKWELL_GB10X,
/// Blackwell (GB20x).
BlackwellGB20x =
uapi::drm_nova_architecture_NOVA_DRM_ARCHITECTURE_BLACKWELL_GB20X,
}
}
impl From<Architecture> for u32 {
#[inline]
fn from(value: Architecture) -> Self {
// CAST: `Architecture` is `repr(u32)` and can thus be cast losslessly.
value as u32
}
}
#[derive(Clone, Copy)]
pub(crate) struct Revision {
major: Bounded<u8, 4>,
@ -219,8 +244,9 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// Structure holding a basic description of the GPU: `Chipset` and `Revision`.
#[derive(Clone, Copy)]
pub(crate) struct Spec {
chipset: Chipset,
pub struct Spec {
/// The GPU chipset.
pub chipset: Chipset,
revision: Revision,
}
@ -308,7 +334,7 @@ struct GspResources<'gpu> {
/// Structure holding the resources required to operate the GPU.
#[pin_data]
pub(crate) struct Gpu<'gpu> {
spec: Spec,
pub(crate) spec: Spec,
/// Static GPU information as provided by the GSP.
gsp_static_info: GetGspStaticInfoReply,
/// GPU memory manager owning memory management resources.

View File

@ -132,9 +132,62 @@ struct drm_nova_gem_info {
__u64 size;
};
/**
* struct drm_nova_info - query device information
*/
struct drm_nova_info {
/**
* @id: The identifier of the information to query.
*/
__u32 id;
/**
* @size: The amount of space allocated by userspace at @data. The kernel
* will return the number of bytes it wrote, or the size of the queried
* information structure if @data is NULL.
*/
__u32 size;
/**
* @data: Pointer to the userspace buffer into which the queried
* information will be written. May be NULL, in which case nothing is
* written and @size is set to the size of the information structure so
* userspace can allocate a suitably sized buffer.
*/
__u64 data;
};
/**
* DRM_NOVA_INFO_GPU
*
* Query GPU information. The result is returned in a
* &struct drm_nova_info_gpu.
*/
#define DRM_NOVA_INFO_GPU 0x00
/**
* struct drm_nova_info_gpu - GPU information
*/
struct drm_nova_info_gpu {
/**
* @architecture: GPU architecture identifier.
*
* See &enum drm_nova_architecture for currently known architectures.
*/
__u32 architecture;
/**
* @chipid: Opaque GPU chip identifier.
*
* See &enum drm_nova_chipid for currently known chips.
*/
__u32 chipid;
};
#define DRM_NOVA_GETPARAM 0x00
#define DRM_NOVA_GEM_CREATE 0x01
#define DRM_NOVA_GEM_INFO 0x02
#define DRM_NOVA_INFO 0x03
/* Note: this is an enum so that it can be resolved by Rust bindgen. */
enum {
@ -144,6 +197,8 @@ enum {
struct drm_nova_gem_create),
DRM_IOCTL_NOVA_GEM_INFO = DRM_IOWR(DRM_COMMAND_BASE + DRM_NOVA_GEM_INFO,
struct drm_nova_gem_info),
DRM_IOCTL_NOVA_INFO = DRM_IOWR(DRM_COMMAND_BASE + DRM_NOVA_INFO,
struct drm_nova_info),
};
#if defined(__cplusplus)