[PATCH v6 07/13] drm: nova: Add an info ioctl

From: Alistair Popple

Date: Wed Sep 09 2026 - 02:53:01 EST


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.

Signed-off-by: Alistair Popple <apopple@xxxxxxxxxx>

---

Changes since v5:

- Report an opaque chipid alongside the architecture instead of the
implementation
- Rename struct drm_nova_gpu_info to drm_nova_info_gpu for consistency
with the DRM_NOVA_INFO_<type> identifiers
- Expose a Spec accessor from NovaCoreApi rather than forwarding
methods, as suggested by Danilo
- Handle the size query in write_info() and pass the value by value
- Document the NULL data pointer size query in the uAPI header
- Drop the redundant GpuInfo invariants, as suggested by Danilo

Changes since v4:

- Add a new info ioctl interface as suggested by Danilo

Changes since v3:

- New for v4 - chipid was previously returned as a GETPARAM parameter
---
drivers/gpu/drm/nova/driver.rs | 2 +-
drivers/gpu/drm/nova/file.rs | 60 ++++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/api.rs | 8 ++++-
drivers/gpu/nova-core/gpu.rs | 24 ++++++++++----
include/uapi/drm/nova_drm.h | 53 ++++++++++++++++++++++++++++++
5 files changed, 138 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs
index 52550e694729..0acdd2eb45ca 100644
--- a/drivers/gpu/drm/nova/driver.rs
+++ b/drivers/gpu/drm/nova/driver.rs
@@ -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),
}
}
diff --git a/drivers/gpu/drm/nova/file.rs b/drivers/gpu/drm/nova/file.rs
index 1156df51c533..4a65bec0fbce 100644
--- a/drivers/gpu/drm/nova/file.rs
+++ b/drivers/gpu/drm/nova/file.rs
@@ -17,11 +17,56 @@
},
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() as u32,
+ chipid: spec.chipset as u32,
+ };
+ Ok(Self(info))
+ }
+}
+
+// SAFETY: `GpuInfo` has no implicit padding, kernel pointers, or interior
+// mutability, and all of its fields are initialized before it is written to
+// userspace.
+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>() as u32;
+ return Ok(());
+ }
+
+ let mut writer =
+ UserSlice::new(UserPtr::from_addr(info.data as usize), info.size as usize).writer();
+
+ info.size = writer.write_truncated(&value)? as u32;
+
+ Ok(())
+}
+
impl drm::file::DriverFile for File {
type Driver = NovaDriver;

@@ -78,4 +123,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)
+ }
}
diff --git a/drivers/gpu/nova-core/api.rs b/drivers/gpu/nova-core/api.rs
index ae023242594e..876451dcc051 100644
--- a/drivers/gpu/nova-core/api.rs
+++ b/drivers/gpu/nova-core/api.rs
@@ -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
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 933bd3657db1..91db64dcaea7 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -43,12 +43,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, )*
];

@@ -122,7 +124,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
@@ -165,13 +168,19 @@ 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,
}
@@ -200,8 +209,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,
}

@@ -289,7 +299,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,
/// GSP and its resources.
diff --git a/include/uapi/drm/nova_drm.h b/include/uapi/drm/nova_drm.h
index e35f09a1ebe8..26a5e7ca5425 100644
--- a/include/uapi/drm/nova_drm.h
+++ b/include/uapi/drm/nova_drm.h
@@ -132,9 +132,60 @@ 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 +195,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)
--
2.54.0