All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] drm/nouveau: GSP telemetry via RUSD, and fdinfo telemetry exposure
@ 2026-07-14 21:14 ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

On the GSP firmware, nouveau reports nothing about the GPU. The GSP owns
the sensors, so the legacy nvkm therm/clk/volt hwmon paths never run, and
monitoring tools have no data to read.

GSP on later firmware does publish all of it through a stable interface.
It maintains a shared-memory region called "RM User Shared Data" (RUSD),
which the firmware fills with all the relevant sensor readings like power,
temperatures, clocks, and many others. Consuming it needs only two
internal subdevice controls: INIT_USER_SHARED_DATA to register the buffer,
and SET_DATA_POLL to tell the GSP which data groups to poll and how often.

This series makes nouveau a RUSD client and exposes the data through two
main surfaces:

  - hwmon, for the values that map onto standard channels: GPU and memory
    temperature, GPU/board/VRAM power, power cap. 

  - sysfs, under /sys/class/drm/cardN/device/rusd/, for everything hwmon
    has no channel type for: clocks, utilisation, pstate, throttle reason,
    ECC counters, PCIe error counters, and everything exposed to hwmon as
    well. One value per file according to sysfs convention.

Polling is demand-driven. GSP polls a group only while a client is reading
it, and a group idles off after 10s after the last read. Polling costs
power, so nothing is polled unless someone is looking. A section that has
not been polled yet, or that the board does not support, reads -ENODATA.

The sysfs interface is documented in
Documentation/ABI/testing/sysfs-driver-nouveau.

While the original scope was RUSD support only, monitoring tools needed
two extra additions to complete the picture:

  - VRAM size and usage on sysfs (patch 5): since this series adds a sysfs
    interface for nouveau, it's better to expose VRAM metrics there rather
    than force monitoring apps to pull in libdrm for the GETPARAM memory
    ioctls.

  - fdinfo (patch 6): nouveau implemented none at all, and monitoring apps
    could not show per-process GPU and memory usage. It now reports
    per-client memory and per-engine busy time, accounted from the DRM
    scheduler that the EXEC/VM_BIND path already uses.

Limitations, caveats, and additional considerations or questions:

  - RUSD needs r570 or newer GSP firmware; r535 does not have the needed
    controls. Pre-GSP and r535 behavior is identical to what it was: the
    rusd/ group is not created, and hwmon keeps its current behavior.

  - This is all read-only telemetry. Clock, voltage, or power control is
    through other interfaces out of scope of this series.

  - The sysfs interface exposed here should be stable and valid for nova
    as well. RUSD is stable and only grows more fields with newer GSP
    firmware (e.g., fan telemetry gets added in r580), so it is possible
    to keep the same interface as we use newer firmware or as we move to
    nova and any potential considerations should be ironed out before this
    gets merged and the interface gets frozen.

  - amdgpu offers a single binary blob with all the telemetry embedded in
    addition to the sysfs sensor files. This series currently only exposes
    sensor files, but it is possible to add a similar binary for a v2.
    This mainly offers a more efficient way to read the telemetry as
    clients would read only a few files instead of reading all of them.

  - The last two patches don't depend on RUSD and work on every GPU. But
    they are here because they complete the sensor telemetry story. I am
    happy to split them into their own series if that is preferred.

Userspace WIP work built on this sysfs surface exists in form of nouveau
backends for nvtop, LACT, and a NVK HUD:

  - nvtop: https://github.com/mohamexiety/nvtop/tree/nvtop-nouvtop

  - LACT: https://github.com/mohamexiety/LACT/tree/lnouvct

  - NVK HUD:
    https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39898

None of these are merged yet and exist for testing only. Upstreaming will
begin when this series gets merged and the interface is stable.

Mohamed Ahmed (6):
  drm/nouveau/gsp: vendor the RUSD header
  drm/nouveau/gsp: add RUSD telemetry support
  drm/nouveau: add GSP hwmon support
  drm/nouveau: expose RUSD telemetry via sysfs
  drm/nouveau: expose global VRAM size and usage via sysfs
  drm/nouveau: expose per-client GPU usage via fdinfo

 .../ABI/testing/sysfs-driver-nouveau          | 167 ++++++
 MAINTAINERS                                   |   1 +
 drivers/gpu/drm/nouveau/Kbuild                |   1 +
 .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h |  33 ++
 .../drm/nouveau/include/nvkm/subdev/rusd.h    | 114 ++++
 .../gpu/drm/nouveau/include/nvrm/nvtypes.h    |   3 +
 drivers/gpu/drm/nouveau/nouveau_abi16.c       |  16 +
 drivers/gpu/drm/nouveau/nouveau_chan.h        |   2 +
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  22 +
 drivers/gpu/drm/nouveau/nouveau_drv.h         |  29 +
 drivers/gpu/drm/nouveau/nouveau_exec.c        |   3 +
 drivers/gpu/drm/nouveau/nouveau_gem.c         |  16 +
 drivers/gpu/drm/nouveau/nouveau_hwmon.c       | 375 ++++++++++---
 drivers/gpu/drm/nouveau/nouveau_sched.c       |  87 +++
 drivers/gpu/drm/nouveau/nouveau_sched.h       |  11 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 311 ++++++++++
 drivers/gpu/drm/nouveau/nouveau_sysfs.h       |  10 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c |  26 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/Kbuild    |   1 +
 .../nvkm/subdev/gsp/rm/r570/nvrm/rusd.h       | 303 ++++++++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/rusd.c    | 531 ++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   7 +
 24 files changed, 1990 insertions(+), 81 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-nouveau
 create mode 100644 drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.c
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 0/6] drm/nouveau: GSP telemetry via RUSD, and fdinfo telemetry exposure
@ 2026-07-14 21:14 ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

On the GSP firmware, nouveau reports nothing about the GPU. The GSP owns
the sensors, so the legacy nvkm therm/clk/volt hwmon paths never run, and
monitoring tools have no data to read.

GSP on later firmware does publish all of it through a stable interface.
It maintains a shared-memory region called "RM User Shared Data" (RUSD),
which the firmware fills with all the relevant sensor readings like power,
temperatures, clocks, and many others. Consuming it needs only two
internal subdevice controls: INIT_USER_SHARED_DATA to register the buffer,
and SET_DATA_POLL to tell the GSP which data groups to poll and how often.

This series makes nouveau a RUSD client and exposes the data through two
main surfaces:

  - hwmon, for the values that map onto standard channels: GPU and memory
    temperature, GPU/board/VRAM power, power cap. 

  - sysfs, under /sys/class/drm/cardN/device/rusd/, for everything hwmon
    has no channel type for: clocks, utilisation, pstate, throttle reason,
    ECC counters, PCIe error counters, and everything exposed to hwmon as
    well. One value per file according to sysfs convention.

Polling is demand-driven. GSP polls a group only while a client is reading
it, and a group idles off after 10s after the last read. Polling costs
power, so nothing is polled unless someone is looking. A section that has
not been polled yet, or that the board does not support, reads -ENODATA.

The sysfs interface is documented in
Documentation/ABI/testing/sysfs-driver-nouveau.

While the original scope was RUSD support only, monitoring tools needed
two extra additions to complete the picture:

  - VRAM size and usage on sysfs (patch 5): since this series adds a sysfs
    interface for nouveau, it's better to expose VRAM metrics there rather
    than force monitoring apps to pull in libdrm for the GETPARAM memory
    ioctls.

  - fdinfo (patch 6): nouveau implemented none at all, and monitoring apps
    could not show per-process GPU and memory usage. It now reports
    per-client memory and per-engine busy time, accounted from the DRM
    scheduler that the EXEC/VM_BIND path already uses.

Limitations, caveats, and additional considerations or questions:

  - RUSD needs r570 or newer GSP firmware; r535 does not have the needed
    controls. Pre-GSP and r535 behavior is identical to what it was: the
    rusd/ group is not created, and hwmon keeps its current behavior.

  - This is all read-only telemetry. Clock, voltage, or power control is
    through other interfaces out of scope of this series.

  - The sysfs interface exposed here should be stable and valid for nova
    as well. RUSD is stable and only grows more fields with newer GSP
    firmware (e.g., fan telemetry gets added in r580), so it is possible
    to keep the same interface as we use newer firmware or as we move to
    nova and any potential considerations should be ironed out before this
    gets merged and the interface gets frozen.

  - amdgpu offers a single binary blob with all the telemetry embedded in
    addition to the sysfs sensor files. This series currently only exposes
    sensor files, but it is possible to add a similar binary for a v2.
    This mainly offers a more efficient way to read the telemetry as
    clients would read only a few files instead of reading all of them.

  - The last two patches don't depend on RUSD and work on every GPU. But
    they are here because they complete the sensor telemetry story. I am
    happy to split them into their own series if that is preferred.

Userspace WIP work built on this sysfs surface exists in form of nouveau
backends for nvtop, LACT, and a NVK HUD:

  - nvtop: https://github.com/mohamexiety/nvtop/tree/nvtop-nouvtop

  - LACT: https://github.com/mohamexiety/LACT/tree/lnouvct

  - NVK HUD:
    https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39898

None of these are merged yet and exist for testing only. Upstreaming will
begin when this series gets merged and the interface is stable.

Mohamed Ahmed (6):
  drm/nouveau/gsp: vendor the RUSD header
  drm/nouveau/gsp: add RUSD telemetry support
  drm/nouveau: add GSP hwmon support
  drm/nouveau: expose RUSD telemetry via sysfs
  drm/nouveau: expose global VRAM size and usage via sysfs
  drm/nouveau: expose per-client GPU usage via fdinfo

 .../ABI/testing/sysfs-driver-nouveau          | 167 ++++++
 MAINTAINERS                                   |   1 +
 drivers/gpu/drm/nouveau/Kbuild                |   1 +
 .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h |  33 ++
 .../drm/nouveau/include/nvkm/subdev/rusd.h    | 114 ++++
 .../gpu/drm/nouveau/include/nvrm/nvtypes.h    |   3 +
 drivers/gpu/drm/nouveau/nouveau_abi16.c       |  16 +
 drivers/gpu/drm/nouveau/nouveau_chan.h        |   2 +
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  22 +
 drivers/gpu/drm/nouveau/nouveau_drv.h         |  29 +
 drivers/gpu/drm/nouveau/nouveau_exec.c        |   3 +
 drivers/gpu/drm/nouveau/nouveau_gem.c         |  16 +
 drivers/gpu/drm/nouveau/nouveau_hwmon.c       | 375 ++++++++++---
 drivers/gpu/drm/nouveau/nouveau_sched.c       |  87 +++
 drivers/gpu/drm/nouveau/nouveau_sched.h       |  11 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 311 ++++++++++
 drivers/gpu/drm/nouveau/nouveau_sysfs.h       |  10 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c |  26 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/Kbuild    |   1 +
 .../nvkm/subdev/gsp/rm/r570/nvrm/rusd.h       | 303 ++++++++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/rusd.c    | 531 ++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   7 +
 24 files changed, 1990 insertions(+), 81 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-nouveau
 create mode 100644 drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.c
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 1/6] drm/nouveau/gsp: vendor the RUSD header
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

RM User Shared Data (RUSD) is GSP-RM's telemetry channel: the kernel
hands the firmware a buffer and the firmware fills it with telemetry
data (temperatures, power draw, power limits, clocks, utilizations,
etc) on its own timer, with a per-section timestamp/seqlock protocol
for lock-free reads.

Vendor the ABI needed to use it from the 570.144 tag of NVIDIA's
open-gpu-kernel-modules, matching the r570 firmware nouveau ships:

 - NV00DE_SHARED_DATA and its section structs, the RUSD_TIMESTAMP_*
   read-protocol values and the NV00DE_RUSD_POLL_* group mask bits
   (src/common/sdk/nvidia/inc/class/cl00de.h),
 - the two internal subdevice controls that drive it,
   NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA (0x20800afe) and
   NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL (0x20800aff)
   (src/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080internal.h),
 - the NvTemp signed 24.8 fixed-point temperature typedef chain
   (src/common/sdk/nvidia/inc/nvfixedtypes.h), with NvS32 added to
   nvtypes.h to support it.

There is no version, magic or size field anywhere in the buffer or the
INIT params and the layout is pinned solely by the cl00de.h the firmware
binary was built with, which does change (more items get added) between
firmware branches. So this header must be re-derived from the exact tag
of any future firmware bump.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/include/nvrm/nvtypes.h    |   3 +
 .../nvkm/subdev/gsp/rm/r570/nvrm/rusd.h       | 303 ++++++++++++++++++
 2 files changed, 306 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h

diff --git a/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h b/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
index af11648ad9c8..ea232b765b7e 100644
--- a/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
+++ b/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
@@ -4,6 +4,7 @@
 
 #define NV_ALIGN_BYTES(a) __attribute__ ((__aligned__(a)))
 #define NV_DECLARE_ALIGNED(f,a) f __attribute__ ((__aligned__(a)))
+#define NVBIT(b) (1U << (b))
 
 typedef u32 NvV32;
 
@@ -12,6 +13,8 @@ typedef u16 NvU16;
 typedef u32 NvU32;
 typedef u64 NvU64;
 
+typedef s32 NvS32;
+
 typedef void* NvP64;
 
 typedef NvU8 NvBool;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
new file mode 100644
index 000000000000..9f1099a06430
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: MIT */
+/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. */
+
+#ifndef __NVRM_RUSD_H__
+#define __NVRM_RUSD_H__
+
+#include <nvif/os.h>
+#include <nvrm/nvtypes.h>
+
+/* Excerpt of RM headers from https://github.com/NVIDIA/open-gpu-kernel-modules/tree/570.144.
+ * Note that in case of GSP upgrades, we will need a different rusd.h as the struct changes.
+ */
+
+typedef NvS32 NvSFXP32;
+typedef NvSFXP32 NvSFXP24_8;
+typedef NvSFXP24_8 NvTemp;
+
+#define RM_USER_SHARED_DATA (0x000000de)
+
+#define RUSD_TIMESTAMP_WRITE_IN_PROGRESS (0xFFFFFFFFFFFFFFFFULL)
+#define RUSD_TIMESTAMP_INVALID 0
+
+#define RUSD_SEQ_START (0xFF00000000000000LLU)
+
+enum {
+   RUSD_CLK_PUBLIC_DOMAIN_GRAPHICS = 0,
+   RUSD_CLK_PUBLIC_DOMAIN_MEMORY,
+   RUSD_CLK_PUBLIC_DOMAIN_VIDEO,
+
+   // Put at the end. See bug 1000230 NVML doesn't report SM frequency on Kepler
+   RUSD_CLK_PUBLIC_DOMAIN_SM,
+   RUSD_CLK_PUBLIC_DOMAIN_MAX_TYPE,
+};
+
+enum {
+   RUSD_CLK_THROTTLE_REASON_GPU_IDLE                         = NVBIT(0),
+   RUSD_CLK_THROTTLE_REASON_APPLICATION_CLOCK_SETTING        = NVBIT(1),
+   RUSD_CLK_THROTTLE_REASON_SW_POWER_CAP                     = NVBIT(2),
+   RUSD_CLK_THROTTLE_REASON_HW_SLOWDOWN                      = NVBIT(3),
+   RUSD_CLK_THROTTLE_REASON_SYNC_BOOST                       = NVBIT(4),
+   RUSD_CLK_THROTTLE_REASON_SW_THERMAL_SLOWDOWN              = NVBIT(5),
+   RUSD_CLK_THROTTLE_REASON_HW_THERMAL_SLOWDOWN              = NVBIT(6),
+   RUSD_CLK_THROTTLE_REASON_HW_POWER_BRAKES_SLOWDOWN         = NVBIT(7),
+   RUSD_CLK_THROTTLE_REASON_DISPLAY_CLOCK_SETTING            = NVBIT(8),
+};
+
+typedef struct RUSD_BAR1_MEMORY_INFO {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Total size and available memory in Bar1
+   //
+   NvU32 bar1Size;
+   NvU32 bar1AvailSize;
+} RUSD_BAR1_MEMORY_INFO;
+
+typedef struct RUSD_PMA_MEMORY_INFO {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Total size and available memory in PMA
+   //
+   NvU64 totalPmaMemory;
+   NvU64 freePmaMemory;
+} RUSD_PMA_MEMORY_INFO;
+
+typedef struct RUSD_CLK_PUBLIC_DOMAIN_INFO {
+   NvU32 targetClkMHz;
+} RUSD_CLK_PUBLIC_DOMAIN_INFO;
+
+typedef struct RUSD_CLK_PUBLIC_DOMAIN_INFOS {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_CLK_PUBLIC_DOMAIN_INFO info[RUSD_CLK_PUBLIC_DOMAIN_MAX_TYPE];
+} RUSD_CLK_PUBLIC_DOMAIN_INFOS;
+
+typedef struct RUSD_ENG_UTILIZATION {
+   NvU32 clkPercentBusy;
+   NvU32 samplingPeriodUs;
+} RUSD_ENG_UTILIZATION;
+
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVENC 0
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVDEC 1
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVJPG 2
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVOFA 3
+#define RUSD_ENG_UTILIZATION_COUNT 4
+
+typedef struct RUSD_PERF_DEVICE_UTILIZATION_INFO {
+   NvU32 gpuPercentBusy;
+   NvU32 memoryPercentBusy;
+   RUSD_ENG_UTILIZATION engUtil[RUSD_ENG_UTILIZATION_COUNT];
+} RUSD_PERF_DEVICE_UTILIZATION_INFO;
+
+typedef struct RUSD_PERF_DEVICE_UTILIZATION {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_PERF_DEVICE_UTILIZATION_INFO info;
+} RUSD_PERF_DEVICE_UTILIZATION;
+
+typedef struct RUSD_PERF_CURRENT_PSTATE {
+   volatile NvU64 lastModifiedTimestamp;
+   // Provided from NV2080_CTRL_CMD_PERF_GET_CURRENT_PSTATE
+   NvU32 currentPstate;
+} RUSD_PERF_CURRENT_PSTATE;
+
+typedef struct RUSD_CLK_THROTTLE_REASON {
+   volatile NvU64 lastModifiedTimestamp;
+   NvU32 reasonMask; // Bitmask of RUSD_CLK_THROTTLE_REASON_*
+} RUSD_CLK_THROTTLE_REASON;
+
+typedef struct RUSD_MEM_ERROR_COUNTS {
+   NvU64 correctedVolatile;
+   NvU64 correctedAggregate;
+   NvU64 uncorrectedVolatile;
+   NvU64 uncorrectedAggregate;
+} RUSD_MEM_ERROR_COUNTS;
+
+#define RUSD_MEMORY_ERROR_TYPE_TOTAL 0
+#define RUSD_MEMORY_ERROR_TYPE_DRAM  1
+#define RUSD_MEMORY_ERROR_TYPE_SRAM  2
+#define RUSD_MEMORY_ERROR_TYPE_COUNT 3
+
+typedef struct RUSD_MEM_ECC {
+   volatile NvU64 lastModifiedTimestamp;
+   // Provided from NV2080_CTRL_CMD_GPU_QUERY_ECC_STATUS
+   RUSD_MEM_ERROR_COUNTS count[RUSD_MEMORY_ERROR_TYPE_COUNT];
+} RUSD_MEM_ECC;
+
+typedef struct RUSD_POWER_LIMIT_INFO {
+   NvU32 requestedmW;
+   NvU32 enforcedmW;
+} RUSD_POWER_LIMIT_INFO;
+
+typedef struct RUSD_POWER_LIMITS {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_POWER_LIMIT_INFO info;
+} RUSD_POWER_LIMITS;
+
+typedef enum RUSD_TEMPERATURE_TYPE {
+   RUSD_TEMPERATURE_TYPE_GPU,
+   RUSD_TEMPERATURE_TYPE_HBM,
+   RUSD_TEMPERATURE_TYPE_MAX
+} RUSD_TEMPERATURE_TYPE;
+
+typedef struct RUSD_TEMPERATURE {
+   volatile NvU64 lastModifiedTimestamp;
+   NvTemp temperature;
+} RUSD_TEMPERATURE;
+
+typedef struct RUSD_MEM_ROW_REMAP_INFO {
+   // Provided from NV2080_CTRL_CMD_FB_GET_ROW_REMAPPER_HISTOGRAM
+   NvU32 histogramMax;     // No remapped row is used.
+   NvU32 histogramHigh;    // One remapped row is used.
+   NvU32 histogramPartial; // More than one remapped rows are used.
+   NvU32 histogramLow;     // One remapped row is available.
+   NvU32 histogramNone;    // All remapped rows are used.
+
+   NvU32 correctableRows;
+   NvU32 uncorrectableRows;
+   NvBool isPending;
+   NvBool hasFailureOccurred;
+} RUSD_MEM_ROW_REMAP_INFO;
+
+typedef struct RUSD_MEM_ROW_REMAP {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_MEM_ROW_REMAP_INFO info;
+} RUSD_MEM_ROW_REMAP;
+
+typedef struct RUSD_AVG_POWER_INFO {
+   NvU32 averageGpuPower;      // mW
+   NvU32 averageModulePower;   // mW
+   NvU32 averageMemoryPower;   // mW
+} RUSD_AVG_POWER_INFO;
+
+typedef struct RUSD_AVG_POWER_USAGE {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_AVG_POWER_INFO info;
+} RUSD_AVG_POWER_USAGE;
+
+typedef struct RUSD_INST_POWER_INFO {
+   NvU32 instGpuPower;         // mW
+   NvU32 instModulePower;      // mW
+   NvU32 instCpuPower;         // mW
+} RUSD_INST_POWER_INFO;
+
+typedef struct RUSD_INST_POWER_USAGE {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_INST_POWER_INFO info;
+} RUSD_INST_POWER_USAGE;
+
+typedef struct RUSD_SHADOW_ERR_CONT {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Shadowed ERR_CONT register value
+   //
+   NvU32 shadowErrContVal;
+} RUSD_SHADOW_ERR_CONT;
+
+// Each RUSD_BUS_DATA_* define corresponds to the equivalent NV2080_CTRL_BUS_INFO_INDEX_*
+#define RUSD_BUS_DATA_PCIE_GEN_INFO                              0
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_LINECODE_ERRORS              1
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_CRC_ERRORS                   2
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_NAKS_RECEIVED                3
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_FAILED_L0S_EXITS             4
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_CORRECTABLE_ERRORS           5
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_NONFATAL_ERRORS              6
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_FATAL_ERRORS                 7
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_UNSUPPORTED_REQUESTS         8
+#define RUSD_BUS_DATA_COUNT                                      9
+
+typedef struct RUSD_PCIE_DATA_INFO {
+   // Provided from NV2080_CTRL_CMD_BUS_GET_INFO_V2
+   NvU32 data[RUSD_BUS_DATA_COUNT];
+} RUSD_PCIE_DATA_INFO;
+
+typedef struct RUSD_PCIE_DATA {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_PCIE_DATA_INFO info;
+} RUSD_PCIE_DATA;
+
+typedef struct RUSD_GR_INFO
+{
+   volatile NvU64 lastModifiedTimestamp;
+   NvBool bCtxswLoggingEnabled;
+} RUSD_GR_INFO;
+
+typedef struct NV00DE_SHARED_DATA {
+   NV_DECLARE_ALIGNED(RUSD_BAR1_MEMORY_INFO bar1MemoryInfo, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_PMA_MEMORY_INFO pmaMemoryInfo, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_SHADOW_ERR_CONT shadowErrCont, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_GR_INFO grInfo, 8);
+
+   // gpuUpdateUserSharedData is sensitive to these two sections being contiguous
+
+   //
+   // Polled data section
+   // All data structs are a volatile NvU64 timestamp followed by data contents.
+   // Access by reading timestamp, then copying the struct contents, then reading the timestamp again.
+   // If time0 matches time1, data has not changed during the read, and contents are valid.
+   // If timestamp is RUSD_TIMESTAMP_WRITE_IN_PROGRESS, data was edited during the read, retry.
+   // If timestamp is RUSD_TIMESTAMP_INVALID, data is not available or not supported on this platform.
+   //
+
+   // POLL_CLOCK
+   NV_DECLARE_ALIGNED(RUSD_CLK_PUBLIC_DOMAIN_INFOS clkPublicDomainInfos, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_CLK_THROTTLE_REASON clkThrottleReason, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_PERF_DEVICE_UTILIZATION perfDevUtil, 8);
+
+   // POLL_MEMORY
+   NV_DECLARE_ALIGNED(RUSD_MEM_ECC memEcc, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_PERF_CURRENT_PSTATE perfCurrentPstate, 8);
+
+   // POLL_POWER
+   // Module Limit is not supported on Ampere/Hopper
+   NV_DECLARE_ALIGNED(RUSD_POWER_LIMITS powerLimitGpu, 8);
+
+   // POLL_THERMAL
+   NV_DECLARE_ALIGNED(RUSD_TEMPERATURE temperatures[RUSD_TEMPERATURE_TYPE_MAX], 8);
+
+   // POLL_MEMORY
+   NV_DECLARE_ALIGNED(RUSD_MEM_ROW_REMAP memRowRemap, 8);
+
+   // POLL_POWER
+   NV_DECLARE_ALIGNED(RUSD_AVG_POWER_USAGE avgPowerUsage, 8);
+
+   // POLL_POWER
+   NV_DECLARE_ALIGNED(RUSD_INST_POWER_USAGE instPowerUsage, 8);
+
+   // POLL_PCI
+   NV_DECLARE_ALIGNED(RUSD_PCIE_DATA pciBusData, 8);
+} NV00DE_SHARED_DATA;
+
+//
+// Polling mask bits, pass into ALLOC_PARAMETERS or NV00DE_CTRL_REQEUSET_DATA_POLL
+// to request above polled data to be provided
+//
+#define NV00DE_RUSD_POLL_CLOCK     0x1
+#define NV00DE_RUSD_POLL_PERF      0x2
+#define NV00DE_RUSD_POLL_MEMORY    0x4
+#define NV00DE_RUSD_POLL_POWER     0x8
+#define NV00DE_RUSD_POLL_THERMAL   0x10
+#define NV00DE_RUSD_POLL_PCI       0x20
+
+typedef struct NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS {
+   NV_DECLARE_ALIGNED(NvU64 physAddr, 8);
+} NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS;
+#define NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA (0x20800afe) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS_MESSAGE_ID" */
+
+typedef struct NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS {
+   NV_DECLARE_ALIGNED(NvU64 polledDataMask, 8);
+   NvU32 pollFrequencyMs;
+} NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS;
+#define NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL (0x20800aff) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS_MESSAGE_ID" */
+
+#endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 1/6] drm/nouveau/gsp: vendor the RUSD header
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

RM User Shared Data (RUSD) is GSP-RM's telemetry channel: the kernel
hands the firmware a buffer and the firmware fills it with telemetry
data (temperatures, power draw, power limits, clocks, utilizations,
etc) on its own timer, with a per-section timestamp/seqlock protocol
for lock-free reads.

Vendor the ABI needed to use it from the 570.144 tag of NVIDIA's
open-gpu-kernel-modules, matching the r570 firmware nouveau ships:

 - NV00DE_SHARED_DATA and its section structs, the RUSD_TIMESTAMP_*
   read-protocol values and the NV00DE_RUSD_POLL_* group mask bits
   (src/common/sdk/nvidia/inc/class/cl00de.h),
 - the two internal subdevice controls that drive it,
   NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA (0x20800afe) and
   NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL (0x20800aff)
   (src/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080internal.h),
 - the NvTemp signed 24.8 fixed-point temperature typedef chain
   (src/common/sdk/nvidia/inc/nvfixedtypes.h), with NvS32 added to
   nvtypes.h to support it.

There is no version, magic or size field anywhere in the buffer or the
INIT params and the layout is pinned solely by the cl00de.h the firmware
binary was built with, which does change (more items get added) between
firmware branches. So this header must be re-derived from the exact tag
of any future firmware bump.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/include/nvrm/nvtypes.h    |   3 +
 .../nvkm/subdev/gsp/rm/r570/nvrm/rusd.h       | 303 ++++++++++++++++++
 2 files changed, 306 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h

diff --git a/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h b/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
index af11648ad9c8..ea232b765b7e 100644
--- a/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
+++ b/drivers/gpu/drm/nouveau/include/nvrm/nvtypes.h
@@ -4,6 +4,7 @@
 
 #define NV_ALIGN_BYTES(a) __attribute__ ((__aligned__(a)))
 #define NV_DECLARE_ALIGNED(f,a) f __attribute__ ((__aligned__(a)))
+#define NVBIT(b) (1U << (b))
 
 typedef u32 NvV32;
 
@@ -12,6 +13,8 @@ typedef u16 NvU16;
 typedef u32 NvU32;
 typedef u64 NvU64;
 
+typedef s32 NvS32;
+
 typedef void* NvP64;
 
 typedef NvU8 NvBool;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
new file mode 100644
index 000000000000..9f1099a06430
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: MIT */
+/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. */
+
+#ifndef __NVRM_RUSD_H__
+#define __NVRM_RUSD_H__
+
+#include <nvif/os.h>
+#include <nvrm/nvtypes.h>
+
+/* Excerpt of RM headers from https://github.com/NVIDIA/open-gpu-kernel-modules/tree/570.144.
+ * Note that in case of GSP upgrades, we will need a different rusd.h as the struct changes.
+ */
+
+typedef NvS32 NvSFXP32;
+typedef NvSFXP32 NvSFXP24_8;
+typedef NvSFXP24_8 NvTemp;
+
+#define RM_USER_SHARED_DATA (0x000000de)
+
+#define RUSD_TIMESTAMP_WRITE_IN_PROGRESS (0xFFFFFFFFFFFFFFFFULL)
+#define RUSD_TIMESTAMP_INVALID 0
+
+#define RUSD_SEQ_START (0xFF00000000000000LLU)
+
+enum {
+   RUSD_CLK_PUBLIC_DOMAIN_GRAPHICS = 0,
+   RUSD_CLK_PUBLIC_DOMAIN_MEMORY,
+   RUSD_CLK_PUBLIC_DOMAIN_VIDEO,
+
+   // Put at the end. See bug 1000230 NVML doesn't report SM frequency on Kepler
+   RUSD_CLK_PUBLIC_DOMAIN_SM,
+   RUSD_CLK_PUBLIC_DOMAIN_MAX_TYPE,
+};
+
+enum {
+   RUSD_CLK_THROTTLE_REASON_GPU_IDLE                         = NVBIT(0),
+   RUSD_CLK_THROTTLE_REASON_APPLICATION_CLOCK_SETTING        = NVBIT(1),
+   RUSD_CLK_THROTTLE_REASON_SW_POWER_CAP                     = NVBIT(2),
+   RUSD_CLK_THROTTLE_REASON_HW_SLOWDOWN                      = NVBIT(3),
+   RUSD_CLK_THROTTLE_REASON_SYNC_BOOST                       = NVBIT(4),
+   RUSD_CLK_THROTTLE_REASON_SW_THERMAL_SLOWDOWN              = NVBIT(5),
+   RUSD_CLK_THROTTLE_REASON_HW_THERMAL_SLOWDOWN              = NVBIT(6),
+   RUSD_CLK_THROTTLE_REASON_HW_POWER_BRAKES_SLOWDOWN         = NVBIT(7),
+   RUSD_CLK_THROTTLE_REASON_DISPLAY_CLOCK_SETTING            = NVBIT(8),
+};
+
+typedef struct RUSD_BAR1_MEMORY_INFO {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Total size and available memory in Bar1
+   //
+   NvU32 bar1Size;
+   NvU32 bar1AvailSize;
+} RUSD_BAR1_MEMORY_INFO;
+
+typedef struct RUSD_PMA_MEMORY_INFO {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Total size and available memory in PMA
+   //
+   NvU64 totalPmaMemory;
+   NvU64 freePmaMemory;
+} RUSD_PMA_MEMORY_INFO;
+
+typedef struct RUSD_CLK_PUBLIC_DOMAIN_INFO {
+   NvU32 targetClkMHz;
+} RUSD_CLK_PUBLIC_DOMAIN_INFO;
+
+typedef struct RUSD_CLK_PUBLIC_DOMAIN_INFOS {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_CLK_PUBLIC_DOMAIN_INFO info[RUSD_CLK_PUBLIC_DOMAIN_MAX_TYPE];
+} RUSD_CLK_PUBLIC_DOMAIN_INFOS;
+
+typedef struct RUSD_ENG_UTILIZATION {
+   NvU32 clkPercentBusy;
+   NvU32 samplingPeriodUs;
+} RUSD_ENG_UTILIZATION;
+
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVENC 0
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVDEC 1
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVJPG 2
+#define RUSD_ENG_UTILIZATION_VID_ENG_NVOFA 3
+#define RUSD_ENG_UTILIZATION_COUNT 4
+
+typedef struct RUSD_PERF_DEVICE_UTILIZATION_INFO {
+   NvU32 gpuPercentBusy;
+   NvU32 memoryPercentBusy;
+   RUSD_ENG_UTILIZATION engUtil[RUSD_ENG_UTILIZATION_COUNT];
+} RUSD_PERF_DEVICE_UTILIZATION_INFO;
+
+typedef struct RUSD_PERF_DEVICE_UTILIZATION {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_PERF_DEVICE_UTILIZATION_INFO info;
+} RUSD_PERF_DEVICE_UTILIZATION;
+
+typedef struct RUSD_PERF_CURRENT_PSTATE {
+   volatile NvU64 lastModifiedTimestamp;
+   // Provided from NV2080_CTRL_CMD_PERF_GET_CURRENT_PSTATE
+   NvU32 currentPstate;
+} RUSD_PERF_CURRENT_PSTATE;
+
+typedef struct RUSD_CLK_THROTTLE_REASON {
+   volatile NvU64 lastModifiedTimestamp;
+   NvU32 reasonMask; // Bitmask of RUSD_CLK_THROTTLE_REASON_*
+} RUSD_CLK_THROTTLE_REASON;
+
+typedef struct RUSD_MEM_ERROR_COUNTS {
+   NvU64 correctedVolatile;
+   NvU64 correctedAggregate;
+   NvU64 uncorrectedVolatile;
+   NvU64 uncorrectedAggregate;
+} RUSD_MEM_ERROR_COUNTS;
+
+#define RUSD_MEMORY_ERROR_TYPE_TOTAL 0
+#define RUSD_MEMORY_ERROR_TYPE_DRAM  1
+#define RUSD_MEMORY_ERROR_TYPE_SRAM  2
+#define RUSD_MEMORY_ERROR_TYPE_COUNT 3
+
+typedef struct RUSD_MEM_ECC {
+   volatile NvU64 lastModifiedTimestamp;
+   // Provided from NV2080_CTRL_CMD_GPU_QUERY_ECC_STATUS
+   RUSD_MEM_ERROR_COUNTS count[RUSD_MEMORY_ERROR_TYPE_COUNT];
+} RUSD_MEM_ECC;
+
+typedef struct RUSD_POWER_LIMIT_INFO {
+   NvU32 requestedmW;
+   NvU32 enforcedmW;
+} RUSD_POWER_LIMIT_INFO;
+
+typedef struct RUSD_POWER_LIMITS {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_POWER_LIMIT_INFO info;
+} RUSD_POWER_LIMITS;
+
+typedef enum RUSD_TEMPERATURE_TYPE {
+   RUSD_TEMPERATURE_TYPE_GPU,
+   RUSD_TEMPERATURE_TYPE_HBM,
+   RUSD_TEMPERATURE_TYPE_MAX
+} RUSD_TEMPERATURE_TYPE;
+
+typedef struct RUSD_TEMPERATURE {
+   volatile NvU64 lastModifiedTimestamp;
+   NvTemp temperature;
+} RUSD_TEMPERATURE;
+
+typedef struct RUSD_MEM_ROW_REMAP_INFO {
+   // Provided from NV2080_CTRL_CMD_FB_GET_ROW_REMAPPER_HISTOGRAM
+   NvU32 histogramMax;     // No remapped row is used.
+   NvU32 histogramHigh;    // One remapped row is used.
+   NvU32 histogramPartial; // More than one remapped rows are used.
+   NvU32 histogramLow;     // One remapped row is available.
+   NvU32 histogramNone;    // All remapped rows are used.
+
+   NvU32 correctableRows;
+   NvU32 uncorrectableRows;
+   NvBool isPending;
+   NvBool hasFailureOccurred;
+} RUSD_MEM_ROW_REMAP_INFO;
+
+typedef struct RUSD_MEM_ROW_REMAP {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_MEM_ROW_REMAP_INFO info;
+} RUSD_MEM_ROW_REMAP;
+
+typedef struct RUSD_AVG_POWER_INFO {
+   NvU32 averageGpuPower;      // mW
+   NvU32 averageModulePower;   // mW
+   NvU32 averageMemoryPower;   // mW
+} RUSD_AVG_POWER_INFO;
+
+typedef struct RUSD_AVG_POWER_USAGE {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_AVG_POWER_INFO info;
+} RUSD_AVG_POWER_USAGE;
+
+typedef struct RUSD_INST_POWER_INFO {
+   NvU32 instGpuPower;         // mW
+   NvU32 instModulePower;      // mW
+   NvU32 instCpuPower;         // mW
+} RUSD_INST_POWER_INFO;
+
+typedef struct RUSD_INST_POWER_USAGE {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_INST_POWER_INFO info;
+} RUSD_INST_POWER_USAGE;
+
+typedef struct RUSD_SHADOW_ERR_CONT {
+   volatile NvU64 lastModifiedTimestamp;
+   //
+   // Non-polled data, not tied to any specific RM API
+   // Shadowed ERR_CONT register value
+   //
+   NvU32 shadowErrContVal;
+} RUSD_SHADOW_ERR_CONT;
+
+// Each RUSD_BUS_DATA_* define corresponds to the equivalent NV2080_CTRL_BUS_INFO_INDEX_*
+#define RUSD_BUS_DATA_PCIE_GEN_INFO                              0
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_LINECODE_ERRORS              1
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_CRC_ERRORS                   2
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_NAKS_RECEIVED                3
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_FAILED_L0S_EXITS             4
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_CORRECTABLE_ERRORS           5
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_NONFATAL_ERRORS              6
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_FATAL_ERRORS                 7
+#define RUSD_BUS_DATA_PCIE_GPU_LINK_UNSUPPORTED_REQUESTS         8
+#define RUSD_BUS_DATA_COUNT                                      9
+
+typedef struct RUSD_PCIE_DATA_INFO {
+   // Provided from NV2080_CTRL_CMD_BUS_GET_INFO_V2
+   NvU32 data[RUSD_BUS_DATA_COUNT];
+} RUSD_PCIE_DATA_INFO;
+
+typedef struct RUSD_PCIE_DATA {
+   volatile NvU64 lastModifiedTimestamp;
+   RUSD_PCIE_DATA_INFO info;
+} RUSD_PCIE_DATA;
+
+typedef struct RUSD_GR_INFO
+{
+   volatile NvU64 lastModifiedTimestamp;
+   NvBool bCtxswLoggingEnabled;
+} RUSD_GR_INFO;
+
+typedef struct NV00DE_SHARED_DATA {
+   NV_DECLARE_ALIGNED(RUSD_BAR1_MEMORY_INFO bar1MemoryInfo, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_PMA_MEMORY_INFO pmaMemoryInfo, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_SHADOW_ERR_CONT shadowErrCont, 8);
+
+   NV_DECLARE_ALIGNED(RUSD_GR_INFO grInfo, 8);
+
+   // gpuUpdateUserSharedData is sensitive to these two sections being contiguous
+
+   //
+   // Polled data section
+   // All data structs are a volatile NvU64 timestamp followed by data contents.
+   // Access by reading timestamp, then copying the struct contents, then reading the timestamp again.
+   // If time0 matches time1, data has not changed during the read, and contents are valid.
+   // If timestamp is RUSD_TIMESTAMP_WRITE_IN_PROGRESS, data was edited during the read, retry.
+   // If timestamp is RUSD_TIMESTAMP_INVALID, data is not available or not supported on this platform.
+   //
+
+   // POLL_CLOCK
+   NV_DECLARE_ALIGNED(RUSD_CLK_PUBLIC_DOMAIN_INFOS clkPublicDomainInfos, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_CLK_THROTTLE_REASON clkThrottleReason, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_PERF_DEVICE_UTILIZATION perfDevUtil, 8);
+
+   // POLL_MEMORY
+   NV_DECLARE_ALIGNED(RUSD_MEM_ECC memEcc, 8);
+
+   // POLL_PERF
+   NV_DECLARE_ALIGNED(RUSD_PERF_CURRENT_PSTATE perfCurrentPstate, 8);
+
+   // POLL_POWER
+   // Module Limit is not supported on Ampere/Hopper
+   NV_DECLARE_ALIGNED(RUSD_POWER_LIMITS powerLimitGpu, 8);
+
+   // POLL_THERMAL
+   NV_DECLARE_ALIGNED(RUSD_TEMPERATURE temperatures[RUSD_TEMPERATURE_TYPE_MAX], 8);
+
+   // POLL_MEMORY
+   NV_DECLARE_ALIGNED(RUSD_MEM_ROW_REMAP memRowRemap, 8);
+
+   // POLL_POWER
+   NV_DECLARE_ALIGNED(RUSD_AVG_POWER_USAGE avgPowerUsage, 8);
+
+   // POLL_POWER
+   NV_DECLARE_ALIGNED(RUSD_INST_POWER_USAGE instPowerUsage, 8);
+
+   // POLL_PCI
+   NV_DECLARE_ALIGNED(RUSD_PCIE_DATA pciBusData, 8);
+} NV00DE_SHARED_DATA;
+
+//
+// Polling mask bits, pass into ALLOC_PARAMETERS or NV00DE_CTRL_REQEUSET_DATA_POLL
+// to request above polled data to be provided
+//
+#define NV00DE_RUSD_POLL_CLOCK     0x1
+#define NV00DE_RUSD_POLL_PERF      0x2
+#define NV00DE_RUSD_POLL_MEMORY    0x4
+#define NV00DE_RUSD_POLL_POWER     0x8
+#define NV00DE_RUSD_POLL_THERMAL   0x10
+#define NV00DE_RUSD_POLL_PCI       0x20
+
+typedef struct NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS {
+   NV_DECLARE_ALIGNED(NvU64 physAddr, 8);
+} NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS;
+#define NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA (0x20800afe) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS_MESSAGE_ID" */
+
+typedef struct NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS {
+   NV_DECLARE_ALIGNED(NvU64 polledDataMask, 8);
+   NvU32 pollFrequencyMs;
+} NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS;
+#define NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL (0x20800aff) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS_MESSAGE_ID" */
+
+#endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 2/6] drm/nouveau/gsp: add RUSD telemetry support
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

Allocate the RUSD shared-data buffer and ask GSP-RM to poll telemetry
into it, following OpenRM's call sequence
(gpuCreateRusdMemory_IMPL, 570.144): allocate a zero-filled, physically
contiguous, coherent sysmem buffer, hand its bus address to the
firmware with INIT_USER_SHARED_DATA, then program the poll group mask
and interval with USER_SHARED_DATA_SET_DATA_POLL. Both controls go
over the existing internal subdevice object.

Readers copy sections out under the RUSD timestamp protocol: a
section's leading u64 is 0 while invalid/unsupported, ~0 while the
firmware is mid-write, and a timestamp otherwise. Reads fail with
-ENODATA for sections the firmware never fills (e.g. HBM temperature on
GDDR boards).

Polling is completely demand driven, each read enables its data group's
poll bit on demand, and an idle worker drops a group's bit once it has
gone unread for R570_RUSD_IDLE_MS. This idle backoff is what stops
polling when nothing reads the sensors. The window is kept above a
typical monitoring cadence so "sensors" in a loop doesn't switch
the poll on and off.

Tracking is per group and each group has independent deadlines, so reading
only temperatures doesn't keep the power section polled for example.
A single delayed_work recomputes the wanted mask from the per-group
last-access timestamps and issues one SET_DATA_POLL.

The vendored layout only matches the firmware's on 64-bit (32-bit u64
alignment shrinks segments and shifts every section), so init fails
closed with -ENODEV on 32-bit kernels rather than misinterpreting
telemetry or leading to GSP OOB writes.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h |  33 ++
 .../drm/nouveau/include/nvkm/subdev/rusd.h    | 114 ++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c |  26 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/Kbuild    |   1 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/rusd.c    | 531 ++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   7 +
 8 files changed, 714 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c

diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
index 64fed208e4cf..6202be91125f 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
@@ -4,6 +4,7 @@
 #include <core/subdev.h>
 #include <core/falcon.h>
 #include <core/firmware.h>
+#include <subdev/rusd.h>
 
 #include <linux/debugfs.h>
 
@@ -233,6 +234,23 @@ struct nvkm_gsp {
 		u8 tpcs;
 	} gr;
 
+	/* RM User Shared Data telemetry buffer, polled by GSP-RM (r570+).
+	 *
+	 * GSP only polls the groups nouveau currently wants: a sensor read
+	 * enables that group's poll bit on demand, and @work disables a group
+	 * again once it has gone unread for a while.  @mutex serialises @mask
+	 * changes (and the SET_DATA_POLL RPC that carries them) against @work.
+	 */
+	struct {
+		struct nvkm_gsp_mem mem;
+		u32 poll_ms;
+		bool valid;
+		struct mutex mutex;
+		u64 mask;
+		unsigned long group_access[NVKM_GSP_RUSD_GROUP_COUNT];
+		struct delayed_work work;
+	} rusd;
+
 	struct nvkm_rm *rm;
 
 	struct {
@@ -275,6 +293,21 @@ nvkm_gsp_rm(struct nvkm_gsp *gsp)
 
 #include <rm/rm.h>
 
+static inline bool
+nvkm_gsp_rusd(struct nvkm_gsp *gsp)
+{
+	return nvkm_gsp_rm(gsp) && gsp->rusd.valid;
+}
+
+static inline int
+nvkm_gsp_rusd_read(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item, s64 *val)
+{
+	if (!nvkm_gsp_rusd(gsp) || !gsp->rm->api->rusd)
+		return -ENODEV;
+
+	return gsp->rm->api->rusd->read(gsp, item, val);
+}
+
 static inline void *
 nvkm_gsp_rpc_get(struct nvkm_gsp *gsp, u32 fn, u32 argc)
 {
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
new file mode 100644
index 000000000000..20fe5b94b1ca
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef __NVKM_SUBDEV_GSP_RUSD_H__
+#define __NVKM_SUBDEV_GSP_RUSD_H__
+
+/*
+ * Nouveau NVKM API/ABI for the RUSD interface. This is the core our RUSD
+ * design rests on: layering such that the firmware ABI doesn't leak into the
+ * rest of DRM.
+ */
+
+/* RUSD poll groups nouveau drives on demand; each maps to one NV00DE_RUSD_POLL_* bit. */
+enum nvkm_gsp_rusd_group {
+	NVKM_GSP_RUSD_GROUP_THERMAL,
+	NVKM_GSP_RUSD_GROUP_POWER,
+	NVKM_GSP_RUSD_GROUP_CLOCK,
+	NVKM_GSP_RUSD_GROUP_PERF,
+	NVKM_GSP_RUSD_GROUP_MEMORY,
+	NVKM_GSP_RUSD_GROUP_PCI,
+	NVKM_GSP_RUSD_GROUP_COUNT
+};
+
+/*
+ * Telemetry items read from the RM User Shared Data (RUSD) buffer. Each is a
+ * single decoded scalar; nvkm owns the units/conversions, callers stay dumb.
+ *
+ * The clock, video-engine and PCIe items are read with index arithmetic in
+ * rusd.c, so the marked runs must stay contiguous and in the noted order.
+ */
+enum nvkm_gsp_rusd_item {
+	NVKM_GSP_RUSD_TEMP_GPU,
+	NVKM_GSP_RUSD_TEMP_HBM,
+
+	NVKM_GSP_RUSD_POWER_GPU,
+	NVKM_GSP_RUSD_POWER_GPU_AVG,
+	NVKM_GSP_RUSD_POWER_BOARD,
+	NVKM_GSP_RUSD_POWER_BOARD_AVG,
+	NVKM_GSP_RUSD_POWER_VRAM_AVG,
+	NVKM_GSP_RUSD_POWER_CAP,
+	NVKM_GSP_RUSD_POWER_CPU,
+	NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED,
+
+	/* contiguous, order = RUSD_CLK_PUBLIC_DOMAIN_* */
+	NVKM_GSP_RUSD_CLOCK_GRAPHICS,	/* MHz */
+	NVKM_GSP_RUSD_CLOCK_MEMORY,
+	NVKM_GSP_RUSD_CLOCK_VIDEO,
+	NVKM_GSP_RUSD_CLOCK_SM,
+
+	NVKM_GSP_RUSD_UTIL_GPU,		/* percent */
+	NVKM_GSP_RUSD_UTIL_MEMORY,
+	/* contiguous, order = RUSD_ENG_UTILIZATION_VID_ENG_* */
+	NVKM_GSP_RUSD_UTIL_NVENC,	/* percent */
+	NVKM_GSP_RUSD_UTIL_NVDEC,
+	NVKM_GSP_RUSD_UTIL_NVJPG,
+	NVKM_GSP_RUSD_UTIL_NVOFA,
+	/* contiguous, same engine order */
+	NVKM_GSP_RUSD_UTIL_NVENC_PERIOD,	/* microseconds */
+	NVKM_GSP_RUSD_UTIL_NVDEC_PERIOD,
+	NVKM_GSP_RUSD_UTIL_NVJPG_PERIOD,
+	NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD,
+
+	NVKM_GSP_RUSD_PSTATE,		/* P-state index */
+
+	NVKM_GSP_RUSD_THROTTLE_MASK,	/* RUSD_CLK_THROTTLE_REASON_* bitmask */
+	NVKM_GSP_RUSD_THROTTLE_GPU_IDLE,	/* the rest: 0/1 */
+	NVKM_GSP_RUSD_THROTTLE_APP_CLOCK,
+	NVKM_GSP_RUSD_THROTTLE_SW_POWER_CAP,
+	NVKM_GSP_RUSD_THROTTLE_HW_SLOWDOWN,
+	NVKM_GSP_RUSD_THROTTLE_SYNC_BOOST,
+	NVKM_GSP_RUSD_THROTTLE_SW_THERMAL,
+	NVKM_GSP_RUSD_THROTTLE_HW_THERMAL,
+	NVKM_GSP_RUSD_THROTTLE_HW_POWER_BRAKE,
+	NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK,
+
+	/*
+	 * contiguous, order = {TOTAL,DRAM,SRAM} x {ce,ue} x {volatile,aggregate};
+	 * ce = correctable error, ue = uncorrectable error.
+	 */
+	NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL,		/* error count */
+	NVKM_GSP_RUSD_ECC_TOTAL_CE_AGG,
+	NVKM_GSP_RUSD_ECC_TOTAL_UE_VOL,
+	NVKM_GSP_RUSD_ECC_TOTAL_UE_AGG,
+	NVKM_GSP_RUSD_ECC_DRAM_CE_VOL,
+	NVKM_GSP_RUSD_ECC_DRAM_CE_AGG,
+	NVKM_GSP_RUSD_ECC_DRAM_UE_VOL,
+	NVKM_GSP_RUSD_ECC_DRAM_UE_AGG,
+	NVKM_GSP_RUSD_ECC_SRAM_CE_VOL,
+	NVKM_GSP_RUSD_ECC_SRAM_CE_AGG,
+	NVKM_GSP_RUSD_ECC_SRAM_UE_VOL,
+	NVKM_GSP_RUSD_ECC_SRAM_UE_AGG,
+
+	NVKM_GSP_RUSD_ROWREMAP_HIST_MAX,	/* bank count */
+	NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_LOW,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_NONE,
+	NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE,	/* row count */
+	NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE,
+	NVKM_GSP_RUSD_ROWREMAP_PENDING,		/* 0/1 */
+	NVKM_GSP_RUSD_ROWREMAP_FAILURE,		/* 0/1 */
+
+	/* contiguous, order = RUSD_BUS_DATA_* */
+	NVKM_GSP_RUSD_PCIE_GEN,			/* packed gen/width bitfield */
+	NVKM_GSP_RUSD_PCIE_LINECODE_ERRORS,	/* the rest: counters */
+	NVKM_GSP_RUSD_PCIE_CRC_ERRORS,
+	NVKM_GSP_RUSD_PCIE_NAKS_RECEIVED,
+	NVKM_GSP_RUSD_PCIE_FAILED_L0S_EXITS,
+	NVKM_GSP_RUSD_PCIE_CORRECTABLE_ERRORS,
+	NVKM_GSP_RUSD_PCIE_NONFATAL_ERRORS,
+	NVKM_GSP_RUSD_PCIE_FATAL_ERRORS,
+	NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS,
+};
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
index f544afa12b6b..c75652b39f81 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
@@ -322,6 +322,9 @@ r535_gsp_postinit(struct nvkm_gsp *gsp)
 	nvkm_gsp_mem_dtor(&gsp->boot.fw);
 	nvkm_gsp_mem_dtor(&gsp->libos);
 
+	if (rmapi->rusd)
+		rmapi->rusd->init(gsp);
+
 	return ret;
 }
 
@@ -1727,6 +1730,19 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend)
 	struct nvkm_rm *rm = gsp->rm;
 	int ret;
 
+	/* Stop demand-poll management before tearing the firmware down. Mark
+	 * RUSD inactive under the lock first: gsp->running is still true
+	 * throughout fini, so the flag is what stops an in-flight request
+	 * from enabling a group (and issuing a SET_DATA_POLL) once teardown
+	 * has begun. Then cancel the worker.
+	 */
+	if (gsp->rusd.mem.data) {
+		mutex_lock(&gsp->rusd.mutex);
+		gsp->rusd.valid = false;
+		mutex_unlock(&gsp->rusd.mutex);
+		cancel_delayed_work_sync(&gsp->rusd.work);
+	}
+
 	if (suspend) {
 		u32 len = rm->api->gsp->sr_data_size(gsp);
 		GspFwSRMeta *sr;
@@ -1801,6 +1817,9 @@ r535_gsp_init(struct nvkm_gsp *gsp)
 		nvkm_gsp_mem_dtor(&gsp->sr.meta);
 		nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3);
 		nvkm_gsp_sg_free(gsp->subdev.device, &gsp->sr.sgt);
+
+		if (ret == 0 && gsp->rm->api->rusd)
+			gsp->rm->api->rusd->resume(gsp);
 		return ret;
 	}
 
@@ -2120,6 +2139,13 @@ r535_gsp_dtor(struct nvkm_gsp *gsp)
 	nvkm_gsp_mem_dtor(&gsp->loginit);
 	nvkm_gsp_mem_dtor(&gsp->logintr);
 	nvkm_gsp_mem_dtor(&gsp->logrm);
+
+	gsp->rusd.valid = false;
+	if (gsp->rusd.mem.data) {
+		cancel_delayed_work_sync(&gsp->rusd.work);
+		mutex_destroy(&gsp->rusd.mutex);
+	}
+	nvkm_gsp_mem_dtor(&gsp->rusd.mem);
 }
 
 static void
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
index a4190676e1ad..babddeeee065 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
@@ -37,6 +37,7 @@ r535_api = {
 	.nvenc = &r535_nvenc,
 	.nvjpg = &r535_nvjpg,
 	.ofa = &r535_ofa,
+	.rusd = NULL,
 };
 
 const struct nvkm_rm_impl
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
index 5db0e7009e1f..5bd3c6538010 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
@@ -7,3 +7,4 @@ nvkm-y += nvkm/subdev/gsp/rm/r570/disp.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/fifo.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/gr.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/ofa.o
+nvkm-y += nvkm/subdev/gsp/rm/r570/rusd.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
index 498658d0c60c..5c091e80cd56 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
@@ -66,6 +66,7 @@ r570_api = {
 	.nvenc = &r535_nvenc,
 	.nvjpg = &r535_nvjpg,
 	.ofa = &r570_ofa,
+	.rusd = &r570_rusd,
 };
 
 const struct nvkm_rm_impl
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
new file mode 100644
index 000000000000..d20dda07596a
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
@@ -0,0 +1,531 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2026 Valve Corp.
+ */
+
+#include <rm/rm.h>
+
+#include "nvrm/rusd.h"
+
+/*
+ * The period the GSP polls the telemetry in. Less frequent is better for power
+ * efficiency; OpenRM sets it to 500ms for GeForce and 100ms for the enterprise
+ * parts, so we mirror that by setting to 500ms.
+ */
+#define R570_RUSD_POLL_MS 500
+/*
+ * Keep polling a group for this long after its last read. This idle backoff
+ * is the primary thing that stops polling when nothing reads the sensors.
+ * This should be kept above a typical monitoring cadence (~1-2s) so monitoring
+ * tools that query the telemetry in a loop don't flip the poll on and off.
+ * Beyond that the value just trades idle power against SET_DATA_POLL churn.
+ */
+#define R570_RUSD_IDLE_MS 10000
+
+/* Each group nouveau manages corresponds to one NV00DE_RUSD_POLL_* bit. */
+static const u64 r570_rusd_group_mask[NVKM_GSP_RUSD_GROUP_COUNT] = {
+	[NVKM_GSP_RUSD_GROUP_THERMAL] = NV00DE_RUSD_POLL_THERMAL,
+	[NVKM_GSP_RUSD_GROUP_POWER]   = NV00DE_RUSD_POLL_POWER,
+	[NVKM_GSP_RUSD_GROUP_CLOCK]   = NV00DE_RUSD_POLL_CLOCK,
+	[NVKM_GSP_RUSD_GROUP_PERF]    = NV00DE_RUSD_POLL_PERF,
+	[NVKM_GSP_RUSD_GROUP_MEMORY]  = NV00DE_RUSD_POLL_MEMORY,
+	[NVKM_GSP_RUSD_GROUP_PCI]     = NV00DE_RUSD_POLL_PCI,
+};
+
+static enum nvkm_gsp_rusd_group
+r570_rusd_item_group(enum nvkm_gsp_rusd_item item)
+{
+	switch (item) {
+	case NVKM_GSP_RUSD_TEMP_GPU:
+	case NVKM_GSP_RUSD_TEMP_HBM:
+		return NVKM_GSP_RUSD_GROUP_THERMAL;
+	case NVKM_GSP_RUSD_CLOCK_GRAPHICS ... NVKM_GSP_RUSD_CLOCK_SM:
+		return NVKM_GSP_RUSD_GROUP_CLOCK;
+	case NVKM_GSP_RUSD_UTIL_GPU ... NVKM_GSP_RUSD_PSTATE:
+	case NVKM_GSP_RUSD_THROTTLE_MASK ... NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK:
+		return NVKM_GSP_RUSD_GROUP_PERF;
+	case NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL ... NVKM_GSP_RUSD_ROWREMAP_FAILURE:
+		return NVKM_GSP_RUSD_GROUP_MEMORY;
+	case NVKM_GSP_RUSD_PCIE_GEN ... NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS:
+		return NVKM_GSP_RUSD_GROUP_PCI;
+	default:
+		return NVKM_GSP_RUSD_GROUP_POWER;
+	}
+}
+
+/*
+ * Program GSP's poll mask. Caller holds gsp->rusd.mutex.
+ *
+ * Mirrors OpenRM: the poll interval is established once at init with an empty
+ * mask (_gpushareddataInitPollingFrequency), and every following mask change
+ * is sent with pollFrequencyMs == 0, which signals to keep the current
+ * interval (_gpushareddataSendDataPollRpc), deduped against the last mask
+ * sent.
+ */
+static int
+r570_rusd_set_mask(struct nvkm_gsp *gsp, u64 mask)
+{
+	NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS *poll;
+	struct nvkm_gsp_object *subdevice = &gsp->internal.device.subdevice;
+	int ret;
+
+	if (mask == gsp->rusd.mask)
+		return 0;
+
+	poll = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL,
+				    sizeof(*poll));
+	if (IS_ERR(poll))
+		return PTR_ERR(poll);
+
+	poll->polledDataMask = mask;
+	poll->pollFrequencyMs = 0;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, poll);
+	if (ret)
+		return ret;
+
+	gsp->rusd.mask = mask;
+	return 0;
+}
+
+/*
+ * Mark a group as consumed: refresh its idle deadline, make sure GSP is
+ * polling it, and keep the idle worker armed. Best-effort; if the enable
+ * RPC fails the worker retries from the refreshed deadline.
+ */
+static void
+r570_rusd_poke(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_group group)
+{
+	mutex_lock(&gsp->rusd.mutex);
+	/* @valid (not gsp->running) is the teardown fence: it is cleared under
+	 * this mutex at the very start of fini, while gsp->running is still
+	 * true, so checking it here stops a read from enabling a group and
+	 * issuing a SET_DATA_POLL to a firmware that is being unloaded.
+	 */
+	if (gsp->rusd.valid) {
+		gsp->rusd.group_access[group] = jiffies;
+		r570_rusd_set_mask(gsp, gsp->rusd.mask | r570_rusd_group_mask[group]);
+		if (gsp->rusd.mask)
+			schedule_delayed_work(&gsp->rusd.work,
+					      msecs_to_jiffies(R570_RUSD_IDLE_MS));
+	}
+	mutex_unlock(&gsp->rusd.mutex);
+}
+
+/* Drop the groups that haven't been read within the idle window. */
+static void
+r570_rusd_work_drop_idle(struct work_struct *work)
+{
+	struct nvkm_gsp *gsp = container_of(to_delayed_work(work),
+					    struct nvkm_gsp, rusd.work);
+	unsigned long idle = msecs_to_jiffies(R570_RUSD_IDLE_MS);
+	u64 mask = 0;
+	int g;
+
+	mutex_lock(&gsp->rusd.mutex);
+	if (gsp->rusd.valid) {
+		for (g = 0; g < NVKM_GSP_RUSD_GROUP_COUNT; g++) {
+			if (gsp->rusd.group_access[g] &&
+			    time_before(jiffies, gsp->rusd.group_access[g] + idle))
+				mask |= r570_rusd_group_mask[g];
+		}
+
+		r570_rusd_set_mask(gsp, mask);
+
+		/* Re-check until every group has gone idle. */
+		if (mask)
+			schedule_delayed_work(&gsp->rusd.work, idle);
+	}
+	mutex_unlock(&gsp->rusd.mutex);
+}
+
+static int
+r570_rusd_rpc_init(struct nvkm_gsp *gsp)
+{
+	NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS *poll;
+	NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS *init;
+	struct nvkm_gsp_object *subdevice = &gsp->internal.device.subdevice;
+	int ret;
+
+	init = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA,
+				    sizeof(*init));
+	if (IS_ERR(init))
+		return PTR_ERR(init);
+
+	init->physAddr = gsp->rusd.mem.addr;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, init);
+	if (ret)
+		return ret;
+
+	/* Polling costs power, so we establish the poll interval up front with an
+	 * empty mask and groups are added later on demand.
+	 */
+	poll = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL,
+				    sizeof(*poll));
+	if (IS_ERR(poll))
+		return PTR_ERR(poll);
+
+	poll->polledDataMask = 0;
+	poll->pollFrequencyMs = gsp->rusd.poll_ms;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, poll);
+	if (ret)
+		return ret;
+
+	/* Nothing is being polled yet; reads will enable groups as needed.
+	 * Runs single-threaded (cold init, or resume with @work cancelled and
+	 * reads gated), so it needs no lock.
+	 */
+	memset(gsp->rusd.group_access, 0, sizeof(gsp->rusd.group_access));
+	gsp->rusd.mask = 0;
+	return 0;
+}
+
+static int
+r570_rusd_init(struct nvkm_gsp *gsp)
+{
+	int g, ret;
+
+	/* The RUSD struct doesn't have a size field, and the GSP writes at its
+	 * firmware-defined offsets. With 32-bit u64 alignment, our copy of the
+	 * layout shrinks which would leave GSP writing past the end of the
+	 * buffer we hand it so fail closed on 32-bit builds.
+	 */
+	if (!IS_ENABLED(CONFIG_64BIT))
+		return -ENODEV;
+
+	if (!gsp->rusd.mem.data) {
+		ret = nvkm_gsp_mem_ctor(gsp, sizeof(NV00DE_SHARED_DATA),
+					&gsp->rusd.mem);
+		if (ret)
+			return ret;
+
+		mutex_init(&gsp->rusd.mutex);
+		INIT_DELAYED_WORK(&gsp->rusd.work, r570_rusd_work_drop_idle);
+	}
+
+	gsp->rusd.poll_ms = R570_RUSD_POLL_MS;
+
+	ret = r570_rusd_rpc_init(gsp);
+	if (ret) {
+		nvkm_warn(&gsp->subdev, "rusd: init failed, %d\n", ret);
+		return ret;
+	}
+
+	gsp->rusd.valid = true;
+
+	/* Warm every group up once so hwmon registration can probe which
+	 * sensors the GPU actually supports without stalling a poll cycle,
+	 * and the first userspace read is instant.
+	 */
+	for (g = 0; g < NVKM_GSP_RUSD_GROUP_COUNT; g++)
+		r570_rusd_poke(gsp, g);
+
+	return 0;
+}
+
+/* Re-register the buffer and re-establish the idle interval on resume. */
+static void
+r570_rusd_resume(struct nvkm_gsp *gsp)
+{
+	if (!gsp->rusd.mem.data)
+		return;
+
+	gsp->rusd.valid = r570_rusd_rpc_init(gsp) == 0;
+	if (!gsp->rusd.valid)
+		nvkm_warn(&gsp->subdev, "rusd: resume failed\n");
+}
+
+/*
+ * Copy one section out of the shared buffer, untorn. Every section starts
+ * with a u64 timestamp GSP-RM sets to WRITE_IN_PROGRESS while updating it.
+ */
+static int
+r570_rusd_read_section(struct nvkm_gsp *gsp, size_t offset, size_t size, void *data)
+{
+	const void *sect = gsp->rusd.mem.data + offset;
+	u64 seq0, seq1;
+	int retries;
+
+	for (retries = 10; retries; retries--) {
+		seq0 = READ_ONCE(*(const u64 *)sect);
+		if (seq0 == RUSD_TIMESTAMP_INVALID)
+			return -ENODATA;
+		if (seq0 == RUSD_TIMESTAMP_WRITE_IN_PROGRESS)
+			continue;
+		/* Sequence counters appear only in sections written by the kernel and
+		 * never in GSP-polled ones.
+		 */
+		if (seq0 >= RUSD_SEQ_START)
+			return -ENODATA;
+
+		dma_rmb();
+		memcpy(data, sect, size);
+		dma_rmb();
+
+		seq1 = READ_ONCE(*(const u64 *)sect);
+		if (seq1 == seq0)
+			return 0;
+	}
+
+	return -EAGAIN;
+}
+
+static int
+r570_rusd_read(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item, s64 *val)
+{
+	size_t offset;
+	u32 mw;
+	int ret;
+
+	if (!gsp->running)
+		return -EINVAL;
+
+	/* Demand the item's poll group. If it was idle this enables polling
+	 * but the section won't be filled for up to one poll interval, so the
+	 * first read after idle returns -ENODATA; the next one has data.
+	 */
+	r570_rusd_poke(gsp, r570_rusd_item_group(item));
+
+	switch (item) {
+	case NVKM_GSP_RUSD_TEMP_GPU:
+	case NVKM_GSP_RUSD_TEMP_HBM: {
+		RUSD_TEMPERATURE temp;
+
+		offset = offsetof(NV00DE_SHARED_DATA, temperatures);
+		if (item == NVKM_GSP_RUSD_TEMP_HBM)
+			offset += RUSD_TEMPERATURE_TYPE_HBM * sizeof(temp);
+
+		ret = r570_rusd_read_section(gsp, offset, sizeof(temp), &temp);
+		if (ret)
+			return ret;
+
+		/* NvTemp is signed 24.8 fixed-point degrees Celsius. */
+		*val = ((s64)temp.temperature * 1000) >> 8;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_POWER_GPU:
+	case NVKM_GSP_RUSD_POWER_BOARD:
+	case NVKM_GSP_RUSD_POWER_CPU: {
+		RUSD_INST_POWER_USAGE inst;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, instPowerUsage),
+					     sizeof(inst), &inst);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_POWER_GPU)
+			mw = inst.info.instGpuPower;
+		else if (item == NVKM_GSP_RUSD_POWER_BOARD)
+			mw = inst.info.instModulePower;
+		else
+			mw = inst.info.instCpuPower;
+		break;
+	}
+	case NVKM_GSP_RUSD_POWER_GPU_AVG:
+	case NVKM_GSP_RUSD_POWER_BOARD_AVG:
+	case NVKM_GSP_RUSD_POWER_VRAM_AVG: {
+		RUSD_AVG_POWER_USAGE avg;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, avgPowerUsage),
+					     sizeof(avg), &avg);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_POWER_GPU_AVG)
+			mw = avg.info.averageGpuPower;
+		else if (item == NVKM_GSP_RUSD_POWER_BOARD_AVG)
+			mw = avg.info.averageModulePower;
+		else
+			mw = avg.info.averageMemoryPower;
+		break;
+	}
+	case NVKM_GSP_RUSD_POWER_CAP:
+	case NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED: {
+		RUSD_POWER_LIMITS limits;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, powerLimitGpu),
+					     sizeof(limits), &limits);
+		if (ret)
+			return ret;
+
+		mw = item == NVKM_GSP_RUSD_POWER_CAP ? limits.info.enforcedmW :
+						       limits.info.requestedmW;
+		break;
+	}
+	case NVKM_GSP_RUSD_CLOCK_GRAPHICS ... NVKM_GSP_RUSD_CLOCK_SM: {
+		RUSD_CLK_PUBLIC_DOMAIN_INFOS clk;
+
+		int idx = item - NVKM_GSP_RUSD_CLOCK_GRAPHICS;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, clkPublicDomainInfos),
+					     sizeof(clk), &clk);
+		if (ret)
+			return ret;
+
+		*val = clk.info[idx].targetClkMHz;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_UTIL_GPU:
+	case NVKM_GSP_RUSD_UTIL_MEMORY:
+	case NVKM_GSP_RUSD_UTIL_NVENC ... NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD: {
+		RUSD_PERF_DEVICE_UTILIZATION util;
+		RUSD_PERF_DEVICE_UTILIZATION_INFO *u = &util.info;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, perfDevUtil),
+					     sizeof(util), &util);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_UTIL_GPU)
+			*val = u->gpuPercentBusy;
+		else if (item == NVKM_GSP_RUSD_UTIL_MEMORY)
+			*val = u->memoryPercentBusy;
+		else if (item <= NVKM_GSP_RUSD_UTIL_NVOFA)
+			*val = u->engUtil[item - NVKM_GSP_RUSD_UTIL_NVENC].clkPercentBusy;
+		else
+			*val = u->engUtil[item - NVKM_GSP_RUSD_UTIL_NVENC_PERIOD].samplingPeriodUs;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_PSTATE: {
+		RUSD_PERF_CURRENT_PSTATE pstate;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, perfCurrentPstate),
+					     sizeof(pstate), &pstate);
+		if (ret)
+			return ret;
+
+		*val = pstate.currentPstate;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_THROTTLE_MASK ... NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK: {
+		static const u32 bits[] = {
+			RUSD_CLK_THROTTLE_REASON_GPU_IDLE,
+			RUSD_CLK_THROTTLE_REASON_APPLICATION_CLOCK_SETTING,
+			RUSD_CLK_THROTTLE_REASON_SW_POWER_CAP,
+			RUSD_CLK_THROTTLE_REASON_HW_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_SYNC_BOOST,
+			RUSD_CLK_THROTTLE_REASON_SW_THERMAL_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_HW_THERMAL_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_HW_POWER_BRAKES_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_DISPLAY_CLOCK_SETTING,
+		};
+
+		RUSD_CLK_THROTTLE_REASON thr;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, clkThrottleReason),
+					     sizeof(thr), &thr);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_THROTTLE_MASK)
+			*val = thr.reasonMask;
+		else
+			*val = !!(thr.reasonMask &
+				  bits[item - NVKM_GSP_RUSD_THROTTLE_GPU_IDLE]);
+		return 0;
+	}
+	case NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL ... NVKM_GSP_RUSD_ECC_SRAM_UE_AGG: {
+		int n = item - NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL;
+
+		RUSD_MEM_ECC ecc;
+		u64 counts[4];
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, memEcc),
+					     sizeof(ecc), &ecc);
+		if (ret)
+			return ret;
+
+		counts[0] = ecc.count[n / 4].correctedVolatile;
+		counts[1] = ecc.count[n / 4].correctedAggregate;
+		counts[2] = ecc.count[n / 4].uncorrectedVolatile;
+		counts[3] = ecc.count[n / 4].uncorrectedAggregate;
+		*val = counts[n % 4];
+		return 0;
+	}
+	case NVKM_GSP_RUSD_ROWREMAP_HIST_MAX ... NVKM_GSP_RUSD_ROWREMAP_FAILURE: {
+		RUSD_MEM_ROW_REMAP remap;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, memRowRemap),
+					     sizeof(remap), &remap);
+		if (ret)
+			return ret;
+
+		switch (item) {
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_MAX:
+			*val = remap.info.histogramMax;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH:
+			*val = remap.info.histogramHigh;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL:
+			*val = remap.info.histogramPartial;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_LOW:
+			*val = remap.info.histogramLow;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_NONE:
+			*val = remap.info.histogramNone;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE:
+			*val = remap.info.correctableRows;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE:
+			*val = remap.info.uncorrectableRows;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_PENDING:
+			*val = remap.info.isPending;
+			break;
+		default: /* NVKM_GSP_RUSD_ROWREMAP_FAILURE */
+			*val = remap.info.hasFailureOccurred;
+			break;
+		}
+		return 0;
+	}
+	case NVKM_GSP_RUSD_PCIE_GEN ... NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS: {
+		RUSD_PCIE_DATA pcie;
+
+		int idx = item - NVKM_GSP_RUSD_PCIE_GEN;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, pciBusData),
+					     sizeof(pcie), &pcie);
+		if (ret)
+			return ret;
+
+		*val = pcie.info.data[idx];
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
+
+	/* A reading of 0 mW is indistinguishable from "not supported on this
+	 * board" (e.g. module power on Ampere/Hopper).
+	 */
+	if (!mw)
+		return -ENODATA;
+
+	*val = (s64)mw * 1000;
+	return 0;
+}
+
+const struct nvkm_rm_api_rusd
+r570_rusd = {
+	.init = r570_rusd_init,
+	.resume = r570_rusd_resume,
+	.read = r570_rusd_read,
+};
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
index a9af94adf9ef..1338b4a88f95 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
@@ -130,6 +130,12 @@ struct nvkm_rm_api {
 			void (*fini)(struct r535_gr *);
 		} scrubber;
 	} *gr;
+
+	const struct nvkm_rm_api_rusd {
+		int (*init)(struct nvkm_gsp *);
+		void (*resume)(struct nvkm_gsp *);
+		int (*read)(struct nvkm_gsp *, enum nvkm_gsp_rusd_item, s64 *);
+	} *rusd;
 };
 
 extern const struct nvkm_rm_impl r535_rm_tu102;
@@ -188,4 +194,5 @@ extern const struct nvkm_rm_api_gr r570_gr;
 int r570_gr_gpc_mask(struct nvkm_gsp *, u32 *mask);
 int r570_gr_tpc_mask(struct nvkm_gsp *, int gpc, u32 *mask);
 extern const struct nvkm_rm_api_engine r570_ofa;
+extern const struct nvkm_rm_api_rusd r570_rusd;
 #endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 2/6] drm/nouveau/gsp: add RUSD telemetry support
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

Allocate the RUSD shared-data buffer and ask GSP-RM to poll telemetry
into it, following OpenRM's call sequence
(gpuCreateRusdMemory_IMPL, 570.144): allocate a zero-filled, physically
contiguous, coherent sysmem buffer, hand its bus address to the
firmware with INIT_USER_SHARED_DATA, then program the poll group mask
and interval with USER_SHARED_DATA_SET_DATA_POLL. Both controls go
over the existing internal subdevice object.

Readers copy sections out under the RUSD timestamp protocol: a
section's leading u64 is 0 while invalid/unsupported, ~0 while the
firmware is mid-write, and a timestamp otherwise. Reads fail with
-ENODATA for sections the firmware never fills (e.g. HBM temperature on
GDDR boards).

Polling is completely demand driven, each read enables its data group's
poll bit on demand, and an idle worker drops a group's bit once it has
gone unread for R570_RUSD_IDLE_MS. This idle backoff is what stops
polling when nothing reads the sensors. The window is kept above a
typical monitoring cadence so "sensors" in a loop doesn't switch
the poll on and off.

Tracking is per group and each group has independent deadlines, so reading
only temperatures doesn't keep the power section polled for example.
A single delayed_work recomputes the wanted mask from the per-group
last-access timestamps and issues one SET_DATA_POLL.

The vendored layout only matches the firmware's on 64-bit (32-bit u64
alignment shrinks segments and shifts every section), so init fails
closed with -ENODEV on 32-bit kernels rather than misinterpreting
telemetry or leading to GSP OOB writes.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h |  33 ++
 .../drm/nouveau/include/nvkm/subdev/rusd.h    | 114 ++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c |  26 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/Kbuild    |   1 +
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c  |   1 +
 .../nouveau/nvkm/subdev/gsp/rm/r570/rusd.c    | 531 ++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   7 +
 8 files changed, 714 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c

diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
index 64fed208e4cf..6202be91125f 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h
@@ -4,6 +4,7 @@
 #include <core/subdev.h>
 #include <core/falcon.h>
 #include <core/firmware.h>
+#include <subdev/rusd.h>
 
 #include <linux/debugfs.h>
 
@@ -233,6 +234,23 @@ struct nvkm_gsp {
 		u8 tpcs;
 	} gr;
 
+	/* RM User Shared Data telemetry buffer, polled by GSP-RM (r570+).
+	 *
+	 * GSP only polls the groups nouveau currently wants: a sensor read
+	 * enables that group's poll bit on demand, and @work disables a group
+	 * again once it has gone unread for a while.  @mutex serialises @mask
+	 * changes (and the SET_DATA_POLL RPC that carries them) against @work.
+	 */
+	struct {
+		struct nvkm_gsp_mem mem;
+		u32 poll_ms;
+		bool valid;
+		struct mutex mutex;
+		u64 mask;
+		unsigned long group_access[NVKM_GSP_RUSD_GROUP_COUNT];
+		struct delayed_work work;
+	} rusd;
+
 	struct nvkm_rm *rm;
 
 	struct {
@@ -275,6 +293,21 @@ nvkm_gsp_rm(struct nvkm_gsp *gsp)
 
 #include <rm/rm.h>
 
+static inline bool
+nvkm_gsp_rusd(struct nvkm_gsp *gsp)
+{
+	return nvkm_gsp_rm(gsp) && gsp->rusd.valid;
+}
+
+static inline int
+nvkm_gsp_rusd_read(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item, s64 *val)
+{
+	if (!nvkm_gsp_rusd(gsp) || !gsp->rm->api->rusd)
+		return -ENODEV;
+
+	return gsp->rm->api->rusd->read(gsp, item, val);
+}
+
 static inline void *
 nvkm_gsp_rpc_get(struct nvkm_gsp *gsp, u32 fn, u32 argc)
 {
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
new file mode 100644
index 000000000000..20fe5b94b1ca
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/rusd.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef __NVKM_SUBDEV_GSP_RUSD_H__
+#define __NVKM_SUBDEV_GSP_RUSD_H__
+
+/*
+ * Nouveau NVKM API/ABI for the RUSD interface. This is the core our RUSD
+ * design rests on: layering such that the firmware ABI doesn't leak into the
+ * rest of DRM.
+ */
+
+/* RUSD poll groups nouveau drives on demand; each maps to one NV00DE_RUSD_POLL_* bit. */
+enum nvkm_gsp_rusd_group {
+	NVKM_GSP_RUSD_GROUP_THERMAL,
+	NVKM_GSP_RUSD_GROUP_POWER,
+	NVKM_GSP_RUSD_GROUP_CLOCK,
+	NVKM_GSP_RUSD_GROUP_PERF,
+	NVKM_GSP_RUSD_GROUP_MEMORY,
+	NVKM_GSP_RUSD_GROUP_PCI,
+	NVKM_GSP_RUSD_GROUP_COUNT
+};
+
+/*
+ * Telemetry items read from the RM User Shared Data (RUSD) buffer. Each is a
+ * single decoded scalar; nvkm owns the units/conversions, callers stay dumb.
+ *
+ * The clock, video-engine and PCIe items are read with index arithmetic in
+ * rusd.c, so the marked runs must stay contiguous and in the noted order.
+ */
+enum nvkm_gsp_rusd_item {
+	NVKM_GSP_RUSD_TEMP_GPU,
+	NVKM_GSP_RUSD_TEMP_HBM,
+
+	NVKM_GSP_RUSD_POWER_GPU,
+	NVKM_GSP_RUSD_POWER_GPU_AVG,
+	NVKM_GSP_RUSD_POWER_BOARD,
+	NVKM_GSP_RUSD_POWER_BOARD_AVG,
+	NVKM_GSP_RUSD_POWER_VRAM_AVG,
+	NVKM_GSP_RUSD_POWER_CAP,
+	NVKM_GSP_RUSD_POWER_CPU,
+	NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED,
+
+	/* contiguous, order = RUSD_CLK_PUBLIC_DOMAIN_* */
+	NVKM_GSP_RUSD_CLOCK_GRAPHICS,	/* MHz */
+	NVKM_GSP_RUSD_CLOCK_MEMORY,
+	NVKM_GSP_RUSD_CLOCK_VIDEO,
+	NVKM_GSP_RUSD_CLOCK_SM,
+
+	NVKM_GSP_RUSD_UTIL_GPU,		/* percent */
+	NVKM_GSP_RUSD_UTIL_MEMORY,
+	/* contiguous, order = RUSD_ENG_UTILIZATION_VID_ENG_* */
+	NVKM_GSP_RUSD_UTIL_NVENC,	/* percent */
+	NVKM_GSP_RUSD_UTIL_NVDEC,
+	NVKM_GSP_RUSD_UTIL_NVJPG,
+	NVKM_GSP_RUSD_UTIL_NVOFA,
+	/* contiguous, same engine order */
+	NVKM_GSP_RUSD_UTIL_NVENC_PERIOD,	/* microseconds */
+	NVKM_GSP_RUSD_UTIL_NVDEC_PERIOD,
+	NVKM_GSP_RUSD_UTIL_NVJPG_PERIOD,
+	NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD,
+
+	NVKM_GSP_RUSD_PSTATE,		/* P-state index */
+
+	NVKM_GSP_RUSD_THROTTLE_MASK,	/* RUSD_CLK_THROTTLE_REASON_* bitmask */
+	NVKM_GSP_RUSD_THROTTLE_GPU_IDLE,	/* the rest: 0/1 */
+	NVKM_GSP_RUSD_THROTTLE_APP_CLOCK,
+	NVKM_GSP_RUSD_THROTTLE_SW_POWER_CAP,
+	NVKM_GSP_RUSD_THROTTLE_HW_SLOWDOWN,
+	NVKM_GSP_RUSD_THROTTLE_SYNC_BOOST,
+	NVKM_GSP_RUSD_THROTTLE_SW_THERMAL,
+	NVKM_GSP_RUSD_THROTTLE_HW_THERMAL,
+	NVKM_GSP_RUSD_THROTTLE_HW_POWER_BRAKE,
+	NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK,
+
+	/*
+	 * contiguous, order = {TOTAL,DRAM,SRAM} x {ce,ue} x {volatile,aggregate};
+	 * ce = correctable error, ue = uncorrectable error.
+	 */
+	NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL,		/* error count */
+	NVKM_GSP_RUSD_ECC_TOTAL_CE_AGG,
+	NVKM_GSP_RUSD_ECC_TOTAL_UE_VOL,
+	NVKM_GSP_RUSD_ECC_TOTAL_UE_AGG,
+	NVKM_GSP_RUSD_ECC_DRAM_CE_VOL,
+	NVKM_GSP_RUSD_ECC_DRAM_CE_AGG,
+	NVKM_GSP_RUSD_ECC_DRAM_UE_VOL,
+	NVKM_GSP_RUSD_ECC_DRAM_UE_AGG,
+	NVKM_GSP_RUSD_ECC_SRAM_CE_VOL,
+	NVKM_GSP_RUSD_ECC_SRAM_CE_AGG,
+	NVKM_GSP_RUSD_ECC_SRAM_UE_VOL,
+	NVKM_GSP_RUSD_ECC_SRAM_UE_AGG,
+
+	NVKM_GSP_RUSD_ROWREMAP_HIST_MAX,	/* bank count */
+	NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_LOW,
+	NVKM_GSP_RUSD_ROWREMAP_HIST_NONE,
+	NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE,	/* row count */
+	NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE,
+	NVKM_GSP_RUSD_ROWREMAP_PENDING,		/* 0/1 */
+	NVKM_GSP_RUSD_ROWREMAP_FAILURE,		/* 0/1 */
+
+	/* contiguous, order = RUSD_BUS_DATA_* */
+	NVKM_GSP_RUSD_PCIE_GEN,			/* packed gen/width bitfield */
+	NVKM_GSP_RUSD_PCIE_LINECODE_ERRORS,	/* the rest: counters */
+	NVKM_GSP_RUSD_PCIE_CRC_ERRORS,
+	NVKM_GSP_RUSD_PCIE_NAKS_RECEIVED,
+	NVKM_GSP_RUSD_PCIE_FAILED_L0S_EXITS,
+	NVKM_GSP_RUSD_PCIE_CORRECTABLE_ERRORS,
+	NVKM_GSP_RUSD_PCIE_NONFATAL_ERRORS,
+	NVKM_GSP_RUSD_PCIE_FATAL_ERRORS,
+	NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS,
+};
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
index f544afa12b6b..c75652b39f81 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c
@@ -322,6 +322,9 @@ r535_gsp_postinit(struct nvkm_gsp *gsp)
 	nvkm_gsp_mem_dtor(&gsp->boot.fw);
 	nvkm_gsp_mem_dtor(&gsp->libos);
 
+	if (rmapi->rusd)
+		rmapi->rusd->init(gsp);
+
 	return ret;
 }
 
@@ -1727,6 +1730,19 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend)
 	struct nvkm_rm *rm = gsp->rm;
 	int ret;
 
+	/* Stop demand-poll management before tearing the firmware down. Mark
+	 * RUSD inactive under the lock first: gsp->running is still true
+	 * throughout fini, so the flag is what stops an in-flight request
+	 * from enabling a group (and issuing a SET_DATA_POLL) once teardown
+	 * has begun. Then cancel the worker.
+	 */
+	if (gsp->rusd.mem.data) {
+		mutex_lock(&gsp->rusd.mutex);
+		gsp->rusd.valid = false;
+		mutex_unlock(&gsp->rusd.mutex);
+		cancel_delayed_work_sync(&gsp->rusd.work);
+	}
+
 	if (suspend) {
 		u32 len = rm->api->gsp->sr_data_size(gsp);
 		GspFwSRMeta *sr;
@@ -1801,6 +1817,9 @@ r535_gsp_init(struct nvkm_gsp *gsp)
 		nvkm_gsp_mem_dtor(&gsp->sr.meta);
 		nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3);
 		nvkm_gsp_sg_free(gsp->subdev.device, &gsp->sr.sgt);
+
+		if (ret == 0 && gsp->rm->api->rusd)
+			gsp->rm->api->rusd->resume(gsp);
 		return ret;
 	}
 
@@ -2120,6 +2139,13 @@ r535_gsp_dtor(struct nvkm_gsp *gsp)
 	nvkm_gsp_mem_dtor(&gsp->loginit);
 	nvkm_gsp_mem_dtor(&gsp->logintr);
 	nvkm_gsp_mem_dtor(&gsp->logrm);
+
+	gsp->rusd.valid = false;
+	if (gsp->rusd.mem.data) {
+		cancel_delayed_work_sync(&gsp->rusd.work);
+		mutex_destroy(&gsp->rusd.mutex);
+	}
+	nvkm_gsp_mem_dtor(&gsp->rusd.mem);
 }
 
 static void
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
index a4190676e1ad..babddeeee065 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rm.c
@@ -37,6 +37,7 @@ r535_api = {
 	.nvenc = &r535_nvenc,
 	.nvjpg = &r535_nvjpg,
 	.ofa = &r535_ofa,
+	.rusd = NULL,
 };
 
 const struct nvkm_rm_impl
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
index 5db0e7009e1f..5bd3c6538010 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/Kbuild
@@ -7,3 +7,4 @@ nvkm-y += nvkm/subdev/gsp/rm/r570/disp.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/fifo.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/gr.o
 nvkm-y += nvkm/subdev/gsp/rm/r570/ofa.o
+nvkm-y += nvkm/subdev/gsp/rm/r570/rusd.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
index 498658d0c60c..5c091e80cd56 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rm.c
@@ -66,6 +66,7 @@ r570_api = {
 	.nvenc = &r535_nvenc,
 	.nvjpg = &r535_nvjpg,
 	.ofa = &r570_ofa,
+	.rusd = &r570_rusd,
 };
 
 const struct nvkm_rm_impl
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
new file mode 100644
index 000000000000..d20dda07596a
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
@@ -0,0 +1,531 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2026 Valve Corp.
+ */
+
+#include <rm/rm.h>
+
+#include "nvrm/rusd.h"
+
+/*
+ * The period the GSP polls the telemetry in. Less frequent is better for power
+ * efficiency; OpenRM sets it to 500ms for GeForce and 100ms for the enterprise
+ * parts, so we mirror that by setting to 500ms.
+ */
+#define R570_RUSD_POLL_MS 500
+/*
+ * Keep polling a group for this long after its last read. This idle backoff
+ * is the primary thing that stops polling when nothing reads the sensors.
+ * This should be kept above a typical monitoring cadence (~1-2s) so monitoring
+ * tools that query the telemetry in a loop don't flip the poll on and off.
+ * Beyond that the value just trades idle power against SET_DATA_POLL churn.
+ */
+#define R570_RUSD_IDLE_MS 10000
+
+/* Each group nouveau manages corresponds to one NV00DE_RUSD_POLL_* bit. */
+static const u64 r570_rusd_group_mask[NVKM_GSP_RUSD_GROUP_COUNT] = {
+	[NVKM_GSP_RUSD_GROUP_THERMAL] = NV00DE_RUSD_POLL_THERMAL,
+	[NVKM_GSP_RUSD_GROUP_POWER]   = NV00DE_RUSD_POLL_POWER,
+	[NVKM_GSP_RUSD_GROUP_CLOCK]   = NV00DE_RUSD_POLL_CLOCK,
+	[NVKM_GSP_RUSD_GROUP_PERF]    = NV00DE_RUSD_POLL_PERF,
+	[NVKM_GSP_RUSD_GROUP_MEMORY]  = NV00DE_RUSD_POLL_MEMORY,
+	[NVKM_GSP_RUSD_GROUP_PCI]     = NV00DE_RUSD_POLL_PCI,
+};
+
+static enum nvkm_gsp_rusd_group
+r570_rusd_item_group(enum nvkm_gsp_rusd_item item)
+{
+	switch (item) {
+	case NVKM_GSP_RUSD_TEMP_GPU:
+	case NVKM_GSP_RUSD_TEMP_HBM:
+		return NVKM_GSP_RUSD_GROUP_THERMAL;
+	case NVKM_GSP_RUSD_CLOCK_GRAPHICS ... NVKM_GSP_RUSD_CLOCK_SM:
+		return NVKM_GSP_RUSD_GROUP_CLOCK;
+	case NVKM_GSP_RUSD_UTIL_GPU ... NVKM_GSP_RUSD_PSTATE:
+	case NVKM_GSP_RUSD_THROTTLE_MASK ... NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK:
+		return NVKM_GSP_RUSD_GROUP_PERF;
+	case NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL ... NVKM_GSP_RUSD_ROWREMAP_FAILURE:
+		return NVKM_GSP_RUSD_GROUP_MEMORY;
+	case NVKM_GSP_RUSD_PCIE_GEN ... NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS:
+		return NVKM_GSP_RUSD_GROUP_PCI;
+	default:
+		return NVKM_GSP_RUSD_GROUP_POWER;
+	}
+}
+
+/*
+ * Program GSP's poll mask. Caller holds gsp->rusd.mutex.
+ *
+ * Mirrors OpenRM: the poll interval is established once at init with an empty
+ * mask (_gpushareddataInitPollingFrequency), and every following mask change
+ * is sent with pollFrequencyMs == 0, which signals to keep the current
+ * interval (_gpushareddataSendDataPollRpc), deduped against the last mask
+ * sent.
+ */
+static int
+r570_rusd_set_mask(struct nvkm_gsp *gsp, u64 mask)
+{
+	NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS *poll;
+	struct nvkm_gsp_object *subdevice = &gsp->internal.device.subdevice;
+	int ret;
+
+	if (mask == gsp->rusd.mask)
+		return 0;
+
+	poll = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL,
+				    sizeof(*poll));
+	if (IS_ERR(poll))
+		return PTR_ERR(poll);
+
+	poll->polledDataMask = mask;
+	poll->pollFrequencyMs = 0;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, poll);
+	if (ret)
+		return ret;
+
+	gsp->rusd.mask = mask;
+	return 0;
+}
+
+/*
+ * Mark a group as consumed: refresh its idle deadline, make sure GSP is
+ * polling it, and keep the idle worker armed. Best-effort; if the enable
+ * RPC fails the worker retries from the refreshed deadline.
+ */
+static void
+r570_rusd_poke(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_group group)
+{
+	mutex_lock(&gsp->rusd.mutex);
+	/* @valid (not gsp->running) is the teardown fence: it is cleared under
+	 * this mutex at the very start of fini, while gsp->running is still
+	 * true, so checking it here stops a read from enabling a group and
+	 * issuing a SET_DATA_POLL to a firmware that is being unloaded.
+	 */
+	if (gsp->rusd.valid) {
+		gsp->rusd.group_access[group] = jiffies;
+		r570_rusd_set_mask(gsp, gsp->rusd.mask | r570_rusd_group_mask[group]);
+		if (gsp->rusd.mask)
+			schedule_delayed_work(&gsp->rusd.work,
+					      msecs_to_jiffies(R570_RUSD_IDLE_MS));
+	}
+	mutex_unlock(&gsp->rusd.mutex);
+}
+
+/* Drop the groups that haven't been read within the idle window. */
+static void
+r570_rusd_work_drop_idle(struct work_struct *work)
+{
+	struct nvkm_gsp *gsp = container_of(to_delayed_work(work),
+					    struct nvkm_gsp, rusd.work);
+	unsigned long idle = msecs_to_jiffies(R570_RUSD_IDLE_MS);
+	u64 mask = 0;
+	int g;
+
+	mutex_lock(&gsp->rusd.mutex);
+	if (gsp->rusd.valid) {
+		for (g = 0; g < NVKM_GSP_RUSD_GROUP_COUNT; g++) {
+			if (gsp->rusd.group_access[g] &&
+			    time_before(jiffies, gsp->rusd.group_access[g] + idle))
+				mask |= r570_rusd_group_mask[g];
+		}
+
+		r570_rusd_set_mask(gsp, mask);
+
+		/* Re-check until every group has gone idle. */
+		if (mask)
+			schedule_delayed_work(&gsp->rusd.work, idle);
+	}
+	mutex_unlock(&gsp->rusd.mutex);
+}
+
+static int
+r570_rusd_rpc_init(struct nvkm_gsp *gsp)
+{
+	NV2080_CTRL_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL_PARAMS *poll;
+	NV2080_CTRL_INTERNAL_INIT_USER_SHARED_DATA_PARAMS *init;
+	struct nvkm_gsp_object *subdevice = &gsp->internal.device.subdevice;
+	int ret;
+
+	init = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA,
+				    sizeof(*init));
+	if (IS_ERR(init))
+		return PTR_ERR(init);
+
+	init->physAddr = gsp->rusd.mem.addr;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, init);
+	if (ret)
+		return ret;
+
+	/* Polling costs power, so we establish the poll interval up front with an
+	 * empty mask and groups are added later on demand.
+	 */
+	poll = nvkm_gsp_rm_ctrl_get(subdevice,
+				    NV2080_CTRL_CMD_INTERNAL_USER_SHARED_DATA_SET_DATA_POLL,
+				    sizeof(*poll));
+	if (IS_ERR(poll))
+		return PTR_ERR(poll);
+
+	poll->polledDataMask = 0;
+	poll->pollFrequencyMs = gsp->rusd.poll_ms;
+
+	ret = nvkm_gsp_rm_ctrl_wr(subdevice, poll);
+	if (ret)
+		return ret;
+
+	/* Nothing is being polled yet; reads will enable groups as needed.
+	 * Runs single-threaded (cold init, or resume with @work cancelled and
+	 * reads gated), so it needs no lock.
+	 */
+	memset(gsp->rusd.group_access, 0, sizeof(gsp->rusd.group_access));
+	gsp->rusd.mask = 0;
+	return 0;
+}
+
+static int
+r570_rusd_init(struct nvkm_gsp *gsp)
+{
+	int g, ret;
+
+	/* The RUSD struct doesn't have a size field, and the GSP writes at its
+	 * firmware-defined offsets. With 32-bit u64 alignment, our copy of the
+	 * layout shrinks which would leave GSP writing past the end of the
+	 * buffer we hand it so fail closed on 32-bit builds.
+	 */
+	if (!IS_ENABLED(CONFIG_64BIT))
+		return -ENODEV;
+
+	if (!gsp->rusd.mem.data) {
+		ret = nvkm_gsp_mem_ctor(gsp, sizeof(NV00DE_SHARED_DATA),
+					&gsp->rusd.mem);
+		if (ret)
+			return ret;
+
+		mutex_init(&gsp->rusd.mutex);
+		INIT_DELAYED_WORK(&gsp->rusd.work, r570_rusd_work_drop_idle);
+	}
+
+	gsp->rusd.poll_ms = R570_RUSD_POLL_MS;
+
+	ret = r570_rusd_rpc_init(gsp);
+	if (ret) {
+		nvkm_warn(&gsp->subdev, "rusd: init failed, %d\n", ret);
+		return ret;
+	}
+
+	gsp->rusd.valid = true;
+
+	/* Warm every group up once so hwmon registration can probe which
+	 * sensors the GPU actually supports without stalling a poll cycle,
+	 * and the first userspace read is instant.
+	 */
+	for (g = 0; g < NVKM_GSP_RUSD_GROUP_COUNT; g++)
+		r570_rusd_poke(gsp, g);
+
+	return 0;
+}
+
+/* Re-register the buffer and re-establish the idle interval on resume. */
+static void
+r570_rusd_resume(struct nvkm_gsp *gsp)
+{
+	if (!gsp->rusd.mem.data)
+		return;
+
+	gsp->rusd.valid = r570_rusd_rpc_init(gsp) == 0;
+	if (!gsp->rusd.valid)
+		nvkm_warn(&gsp->subdev, "rusd: resume failed\n");
+}
+
+/*
+ * Copy one section out of the shared buffer, untorn. Every section starts
+ * with a u64 timestamp GSP-RM sets to WRITE_IN_PROGRESS while updating it.
+ */
+static int
+r570_rusd_read_section(struct nvkm_gsp *gsp, size_t offset, size_t size, void *data)
+{
+	const void *sect = gsp->rusd.mem.data + offset;
+	u64 seq0, seq1;
+	int retries;
+
+	for (retries = 10; retries; retries--) {
+		seq0 = READ_ONCE(*(const u64 *)sect);
+		if (seq0 == RUSD_TIMESTAMP_INVALID)
+			return -ENODATA;
+		if (seq0 == RUSD_TIMESTAMP_WRITE_IN_PROGRESS)
+			continue;
+		/* Sequence counters appear only in sections written by the kernel and
+		 * never in GSP-polled ones.
+		 */
+		if (seq0 >= RUSD_SEQ_START)
+			return -ENODATA;
+
+		dma_rmb();
+		memcpy(data, sect, size);
+		dma_rmb();
+
+		seq1 = READ_ONCE(*(const u64 *)sect);
+		if (seq1 == seq0)
+			return 0;
+	}
+
+	return -EAGAIN;
+}
+
+static int
+r570_rusd_read(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item, s64 *val)
+{
+	size_t offset;
+	u32 mw;
+	int ret;
+
+	if (!gsp->running)
+		return -EINVAL;
+
+	/* Demand the item's poll group. If it was idle this enables polling
+	 * but the section won't be filled for up to one poll interval, so the
+	 * first read after idle returns -ENODATA; the next one has data.
+	 */
+	r570_rusd_poke(gsp, r570_rusd_item_group(item));
+
+	switch (item) {
+	case NVKM_GSP_RUSD_TEMP_GPU:
+	case NVKM_GSP_RUSD_TEMP_HBM: {
+		RUSD_TEMPERATURE temp;
+
+		offset = offsetof(NV00DE_SHARED_DATA, temperatures);
+		if (item == NVKM_GSP_RUSD_TEMP_HBM)
+			offset += RUSD_TEMPERATURE_TYPE_HBM * sizeof(temp);
+
+		ret = r570_rusd_read_section(gsp, offset, sizeof(temp), &temp);
+		if (ret)
+			return ret;
+
+		/* NvTemp is signed 24.8 fixed-point degrees Celsius. */
+		*val = ((s64)temp.temperature * 1000) >> 8;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_POWER_GPU:
+	case NVKM_GSP_RUSD_POWER_BOARD:
+	case NVKM_GSP_RUSD_POWER_CPU: {
+		RUSD_INST_POWER_USAGE inst;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, instPowerUsage),
+					     sizeof(inst), &inst);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_POWER_GPU)
+			mw = inst.info.instGpuPower;
+		else if (item == NVKM_GSP_RUSD_POWER_BOARD)
+			mw = inst.info.instModulePower;
+		else
+			mw = inst.info.instCpuPower;
+		break;
+	}
+	case NVKM_GSP_RUSD_POWER_GPU_AVG:
+	case NVKM_GSP_RUSD_POWER_BOARD_AVG:
+	case NVKM_GSP_RUSD_POWER_VRAM_AVG: {
+		RUSD_AVG_POWER_USAGE avg;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, avgPowerUsage),
+					     sizeof(avg), &avg);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_POWER_GPU_AVG)
+			mw = avg.info.averageGpuPower;
+		else if (item == NVKM_GSP_RUSD_POWER_BOARD_AVG)
+			mw = avg.info.averageModulePower;
+		else
+			mw = avg.info.averageMemoryPower;
+		break;
+	}
+	case NVKM_GSP_RUSD_POWER_CAP:
+	case NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED: {
+		RUSD_POWER_LIMITS limits;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, powerLimitGpu),
+					     sizeof(limits), &limits);
+		if (ret)
+			return ret;
+
+		mw = item == NVKM_GSP_RUSD_POWER_CAP ? limits.info.enforcedmW :
+						       limits.info.requestedmW;
+		break;
+	}
+	case NVKM_GSP_RUSD_CLOCK_GRAPHICS ... NVKM_GSP_RUSD_CLOCK_SM: {
+		RUSD_CLK_PUBLIC_DOMAIN_INFOS clk;
+
+		int idx = item - NVKM_GSP_RUSD_CLOCK_GRAPHICS;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, clkPublicDomainInfos),
+					     sizeof(clk), &clk);
+		if (ret)
+			return ret;
+
+		*val = clk.info[idx].targetClkMHz;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_UTIL_GPU:
+	case NVKM_GSP_RUSD_UTIL_MEMORY:
+	case NVKM_GSP_RUSD_UTIL_NVENC ... NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD: {
+		RUSD_PERF_DEVICE_UTILIZATION util;
+		RUSD_PERF_DEVICE_UTILIZATION_INFO *u = &util.info;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, perfDevUtil),
+					     sizeof(util), &util);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_UTIL_GPU)
+			*val = u->gpuPercentBusy;
+		else if (item == NVKM_GSP_RUSD_UTIL_MEMORY)
+			*val = u->memoryPercentBusy;
+		else if (item <= NVKM_GSP_RUSD_UTIL_NVOFA)
+			*val = u->engUtil[item - NVKM_GSP_RUSD_UTIL_NVENC].clkPercentBusy;
+		else
+			*val = u->engUtil[item - NVKM_GSP_RUSD_UTIL_NVENC_PERIOD].samplingPeriodUs;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_PSTATE: {
+		RUSD_PERF_CURRENT_PSTATE pstate;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, perfCurrentPstate),
+					     sizeof(pstate), &pstate);
+		if (ret)
+			return ret;
+
+		*val = pstate.currentPstate;
+		return 0;
+	}
+	case NVKM_GSP_RUSD_THROTTLE_MASK ... NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK: {
+		static const u32 bits[] = {
+			RUSD_CLK_THROTTLE_REASON_GPU_IDLE,
+			RUSD_CLK_THROTTLE_REASON_APPLICATION_CLOCK_SETTING,
+			RUSD_CLK_THROTTLE_REASON_SW_POWER_CAP,
+			RUSD_CLK_THROTTLE_REASON_HW_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_SYNC_BOOST,
+			RUSD_CLK_THROTTLE_REASON_SW_THERMAL_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_HW_THERMAL_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_HW_POWER_BRAKES_SLOWDOWN,
+			RUSD_CLK_THROTTLE_REASON_DISPLAY_CLOCK_SETTING,
+		};
+
+		RUSD_CLK_THROTTLE_REASON thr;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, clkThrottleReason),
+					     sizeof(thr), &thr);
+		if (ret)
+			return ret;
+
+		if (item == NVKM_GSP_RUSD_THROTTLE_MASK)
+			*val = thr.reasonMask;
+		else
+			*val = !!(thr.reasonMask &
+				  bits[item - NVKM_GSP_RUSD_THROTTLE_GPU_IDLE]);
+		return 0;
+	}
+	case NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL ... NVKM_GSP_RUSD_ECC_SRAM_UE_AGG: {
+		int n = item - NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL;
+
+		RUSD_MEM_ECC ecc;
+		u64 counts[4];
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, memEcc),
+					     sizeof(ecc), &ecc);
+		if (ret)
+			return ret;
+
+		counts[0] = ecc.count[n / 4].correctedVolatile;
+		counts[1] = ecc.count[n / 4].correctedAggregate;
+		counts[2] = ecc.count[n / 4].uncorrectedVolatile;
+		counts[3] = ecc.count[n / 4].uncorrectedAggregate;
+		*val = counts[n % 4];
+		return 0;
+	}
+	case NVKM_GSP_RUSD_ROWREMAP_HIST_MAX ... NVKM_GSP_RUSD_ROWREMAP_FAILURE: {
+		RUSD_MEM_ROW_REMAP remap;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, memRowRemap),
+					     sizeof(remap), &remap);
+		if (ret)
+			return ret;
+
+		switch (item) {
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_MAX:
+			*val = remap.info.histogramMax;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH:
+			*val = remap.info.histogramHigh;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL:
+			*val = remap.info.histogramPartial;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_LOW:
+			*val = remap.info.histogramLow;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_HIST_NONE:
+			*val = remap.info.histogramNone;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE:
+			*val = remap.info.correctableRows;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE:
+			*val = remap.info.uncorrectableRows;
+			break;
+		case NVKM_GSP_RUSD_ROWREMAP_PENDING:
+			*val = remap.info.isPending;
+			break;
+		default: /* NVKM_GSP_RUSD_ROWREMAP_FAILURE */
+			*val = remap.info.hasFailureOccurred;
+			break;
+		}
+		return 0;
+	}
+	case NVKM_GSP_RUSD_PCIE_GEN ... NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS: {
+		RUSD_PCIE_DATA pcie;
+
+		int idx = item - NVKM_GSP_RUSD_PCIE_GEN;
+
+		ret = r570_rusd_read_section(gsp,
+					     offsetof(NV00DE_SHARED_DATA, pciBusData),
+					     sizeof(pcie), &pcie);
+		if (ret)
+			return ret;
+
+		*val = pcie.info.data[idx];
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
+
+	/* A reading of 0 mW is indistinguishable from "not supported on this
+	 * board" (e.g. module power on Ampere/Hopper).
+	 */
+	if (!mw)
+		return -ENODATA;
+
+	*val = (s64)mw * 1000;
+	return 0;
+}
+
+const struct nvkm_rm_api_rusd
+r570_rusd = {
+	.init = r570_rusd_init,
+	.resume = r570_rusd_resume,
+	.read = r570_rusd_read,
+};
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
index a9af94adf9ef..1338b4a88f95 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
@@ -130,6 +130,12 @@ struct nvkm_rm_api {
 			void (*fini)(struct r535_gr *);
 		} scrubber;
 	} *gr;
+
+	const struct nvkm_rm_api_rusd {
+		int (*init)(struct nvkm_gsp *);
+		void (*resume)(struct nvkm_gsp *);
+		int (*read)(struct nvkm_gsp *, enum nvkm_gsp_rusd_item, s64 *);
+	} *rusd;
 };
 
 extern const struct nvkm_rm_impl r535_rm_tu102;
@@ -188,4 +194,5 @@ extern const struct nvkm_rm_api_gr r570_gr;
 int r570_gr_gpc_mask(struct nvkm_gsp *, u32 *mask);
 int r570_gr_tpc_mask(struct nvkm_gsp *, int gpc, u32 *mask);
 extern const struct nvkm_rm_api_engine r570_ofa;
+extern const struct nvkm_rm_api_rusd r570_rusd;
 #endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 3/6] drm/nouveau: add GSP hwmon support
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

GSP boards currently register no hwmon device at all, so every Turing
through Blackwell user has been running without any temperature or power
readout. Wire the RUSD telemetry up as the first one:

  temp1_input/_label   "gpu"    temperatures[GPU]
  temp2_input/_label   "vram"   temperatures[HBM], HBM boards only
  power1_input/average "gpu"    inst/avg GPU power
  power1_cap                    enforced power limit (read-only)
  power2_input/average "board"  module power, where supported
  power3_average       "vram"   memory rail average, where supported
  update_interval               the programmed RUSD poll interval

All channels are standard hwmon ABI, per-channel visibility is decided
at registration time from live section validity, since support varies
per SKU at runtime. If nothing becomes valid, no hwmon device is
registered, matching today's behaviour on GSP boards.

The legacy therm/volt/iccsense paths are untouched and the new
channels and attributes are gated on RUSD, so pre-GSP boards see no
change.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/nouveau_hwmon.c | 375 +++++++++++++++++++-----
 1 file changed, 294 insertions(+), 81 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_hwmon.c b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
index 726397ab035d..d449431d4a40 100644
--- a/drivers/gpu/drm/nouveau/nouveau_hwmon.c
+++ b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
@@ -25,6 +25,8 @@
 #ifdef CONFIG_ACPI
 #include <linux/acpi.h>
 #endif
+#include <linux/delay.h>
+#include <linux/jiffies.h>
 #include <linux/power_supply.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
@@ -32,6 +34,7 @@
 #include "nouveau_drv.h"
 #include "nouveau_hwmon.h"
 
+#include <nvkm/subdev/gsp.h>
 #include <nvkm/subdev/iccsense.h>
 #include <nvkm/subdev/volt.h>
 
@@ -215,10 +218,11 @@ static const struct hwmon_channel_info * const nouveau_info[] = {
 	HWMON_CHANNEL_INFO(chip,
 			   HWMON_C_UPDATE_INTERVAL),
 	HWMON_CHANNEL_INFO(temp,
-			   HWMON_T_INPUT |
+			   HWMON_T_INPUT | HWMON_T_LABEL |
 			   HWMON_T_MAX | HWMON_T_MAX_HYST |
 			   HWMON_T_CRIT | HWMON_T_CRIT_HYST |
-			   HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST),
+			   HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL),
 	HWMON_CHANNEL_INFO(fan,
 			   HWMON_F_INPUT),
 	HWMON_CHANNEL_INFO(in,
@@ -228,7 +232,10 @@ static const struct hwmon_channel_info * const nouveau_info[] = {
 	HWMON_CHANNEL_INFO(pwm,
 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
 	HWMON_CHANNEL_INFO(power,
-			   HWMON_P_INPUT | HWMON_P_CAP_MAX | HWMON_P_CRIT),
+			   HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_CRIT |
+			   HWMON_P_AVERAGE | HWMON_P_CAP | HWMON_P_LABEL,
+			   HWMON_P_INPUT | HWMON_P_AVERAGE | HWMON_P_LABEL,
+			   HWMON_P_AVERAGE | HWMON_P_LABEL),
 	NULL
 };
 
@@ -243,52 +250,144 @@ nouveau_chip_is_visible(const void *data, u32 attr, int channel)
 	}
 }
 
-static umode_t
-nouveau_power_is_visible(const void *data, u32 attr, int channel)
+static bool
+nouveau_rusd_ok(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item)
 {
-	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	int retries = 3;
+	s64 val;
+	int ret;
 
-	if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
-		return 0;
+	/* Visibility decisions are made only once at registration. So retry
+	 * to avoid letting racing a GSP write window (-EAGAIN) hide a channel
+	 * for good.
+	 */
+	do {
+		ret = nvkm_gsp_rusd_read(gsp, item, &val);
+		if (ret != -EAGAIN)
+			break;
+		usleep_range(100, 200);
+	} while (--retries);
+
+	return ret == 0;
+}
 
+static umode_t
+nouveau_rusd_power_is_visible(struct nvkm_gsp *gsp, u32 attr, int channel)
+{
 	switch (attr) {
 	case hwmon_power_input:
-		return 0444;
-	case hwmon_power_max:
-		if (iccsense->power_w_max)
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU))
+			return 0444;
+		if (channel == 1 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD))
+			return 0444;
+		return 0;
+	case hwmon_power_average:
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG))
+			return 0444;
+		if (channel == 1 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD_AVG))
+			return 0444;
+		if (channel == 2 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_VRAM_AVG))
 			return 0444;
 		return 0;
-	case hwmon_power_crit:
-		if (iccsense->power_w_crit)
+	case hwmon_power_cap:
+		/* The enforced limit is only telemetry here. Setting it needs ctrls nouveau
+		 * doesn't implement.
+		 */
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP))
 			return 0444;
 		return 0;
+	case hwmon_power_label:
+		switch (channel) {
+		case 0:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP))
+				return 0444;
+			break;
+		case 1:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD_AVG))
+				return 0444;
+			break;
+		case 2:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_VRAM_AVG))
+				return 0444;
+			break;
+		}
+		return 0;
 	default:
 		return 0;
 	}
 }
 
 static umode_t
-nouveau_temp_is_visible(const void *data, u32 attr, int channel)
+nouveau_power_is_visible(const void *data, u32 attr, int channel)
 {
 	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
-	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 
-	if (!therm || !therm->attr_get || nvkm_therm_temp_get(therm) < 0)
-		return 0;
+	if (nvkm_gsp_rusd(gsp))
+		return nouveau_rusd_power_is_visible(gsp, attr, channel);
+	else {
+		if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
+			return 0;
 
-	switch (attr) {
-	case hwmon_temp_input:
-		return 0444;
-	case hwmon_temp_max:
-	case hwmon_temp_max_hyst:
-	case hwmon_temp_crit:
-	case hwmon_temp_crit_hyst:
-	case hwmon_temp_emergency:
-	case hwmon_temp_emergency_hyst:
-		return 0644;
-	default:
-		return 0;
+		switch (attr) {
+		case hwmon_power_input:
+			return 0444;
+		case hwmon_power_max:
+			if (iccsense->power_w_max)
+				return 0444;
+			return 0;
+		case hwmon_power_crit:
+			if (iccsense->power_w_crit)
+				return 0444;
+			return 0;
+		default:
+			return 0;
+		}
+	}
+}
+
+static umode_t
+nouveau_temp_is_visible(const void *data, u32 attr, int channel)
+{
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
+	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
+	if (nvkm_gsp_rusd(gsp)) {
+		switch (attr) {
+		case hwmon_temp_input:
+		case hwmon_temp_label:
+			if (nouveau_rusd_ok(gsp, channel ? NVKM_GSP_RUSD_TEMP_HBM :
+							   NVKM_GSP_RUSD_TEMP_GPU))
+				return 0444;
+			return 0;
+		default:
+			/* No threshold data in the RUSD buffer. */
+			return 0;
+		}
+	} else {
+		if (!therm || !therm->attr_get || nvkm_therm_temp_get(therm) < 0)
+			return 0;
+
+		switch (attr) {
+		case hwmon_temp_input:
+			return 0444;
+		case hwmon_temp_max:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+		case hwmon_temp_emergency:
+		case hwmon_temp_emergency_hyst:
+			return 0644;
+		default:
+			return 0;
+		}
 	}
 }
 
@@ -351,9 +450,13 @@ nouveau_fan_is_visible(const void *data, u32 attr, int channel)
 static int
 nouveau_chip_read(struct device *dev, u32 attr, int channel, long *val)
 {
+	struct nouveau_drm *drm = nouveau_drm(dev_get_drvdata(dev));
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
 	switch (attr) {
 	case hwmon_chip_update_interval:
-		*val = 1000;
+		*val = nvkm_gsp_rusd(gsp) ? gsp->rusd.poll_ms : 1000;
 		break;
 	default:
 		return -EOPNOTSUPP;
@@ -368,47 +471,67 @@ nouveau_temp_read(struct device *dev, u32 attr, int channel, long *val)
 	struct drm_device *drm_dev = dev_get_drvdata(dev);
 	struct nouveau_drm *drm = nouveau_drm(drm_dev);
 	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 	int ret;
 
-	if (!therm || !therm->attr_get)
-		return -EOPNOTSUPP;
+	if (nvkm_gsp_rusd(gsp)) {
+		s64 value;
+
+		if (attr != hwmon_temp_input)
+			return -EOPNOTSUPP;
 
-	switch (attr) {
-	case hwmon_temp_input:
 		if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
 			return -EINVAL;
-		ret = nvkm_therm_temp_get(therm);
-		*val = ret < 0 ? ret : (ret * 1000);
-		break;
-	case hwmon_temp_max:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
-					* 1000;
-		break;
-	case hwmon_temp_max_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
-					* 1000;
-		break;
-	case hwmon_temp_crit:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
-					* 1000;
-		break;
-	case hwmon_temp_crit_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
-					* 1000;
-		break;
-	case hwmon_temp_emergency:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
-					* 1000;
-		break;
-	case hwmon_temp_emergency_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
-					* 1000;
-		break;
-	default:
-		return -EOPNOTSUPP;
-	}
 
-	return 0;
+		ret = nvkm_gsp_rusd_read(gsp, channel ? NVKM_GSP_RUSD_TEMP_HBM :
+							NVKM_GSP_RUSD_TEMP_GPU, &value);
+		if (ret)
+			return ret;
+
+		*val = value;
+		return 0;
+	} else {
+		if (!therm || !therm->attr_get)
+			return -EOPNOTSUPP;
+
+		switch (attr) {
+		case hwmon_temp_input:
+			if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+				return -EINVAL;
+			ret = nvkm_therm_temp_get(therm);
+			*val = ret < 0 ? ret : (ret * 1000);
+			break;
+		case hwmon_temp_max:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
+						* 1000;
+			break;
+		case hwmon_temp_max_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
+						* 1000;
+			break;
+		case hwmon_temp_crit:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
+						* 1000;
+			break;
+		case hwmon_temp_crit_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
+						* 1000;
+			break;
+		case hwmon_temp_emergency:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
+						* 1000;
+			break;
+		case hwmon_temp_emergency_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
+						* 1000;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+
+		return 0;
+	}
 }
 
 static int
@@ -492,34 +615,84 @@ nouveau_pwm_read(struct device *dev, u32 attr, int channel, long *val)
 }
 
 static int
-nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
+nouveau_rusd_power_read(struct drm_device *drm_dev, struct nvkm_gsp *gsp,
+			u32 attr, int channel, long *val)
 {
-	struct drm_device *drm_dev = dev_get_drvdata(dev);
-	struct nouveau_drm *drm = nouveau_drm(drm_dev);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
-
-	if (!iccsense)
-		return -EOPNOTSUPP;
+	enum nvkm_gsp_rusd_item item;
+	s64 value;
+	int ret;
 
 	switch (attr) {
 	case hwmon_power_input:
-		if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
-			return -EINVAL;
-		*val = nvkm_iccsense_read_all(iccsense);
+		if (channel == 2)
+			return -EOPNOTSUPP;
+		item = channel ? NVKM_GSP_RUSD_POWER_BOARD :
+				 NVKM_GSP_RUSD_POWER_GPU;
 		break;
-	case hwmon_power_max:
-		*val = iccsense->power_w_max;
+	case hwmon_power_average:
+		if (channel == 0)
+			item = NVKM_GSP_RUSD_POWER_GPU_AVG;
+		else if (channel == 1)
+			item = NVKM_GSP_RUSD_POWER_BOARD_AVG;
+		else
+			item = NVKM_GSP_RUSD_POWER_VRAM_AVG;
 		break;
-	case hwmon_power_crit:
-		*val = iccsense->power_w_crit;
+	case hwmon_power_cap:
+		if (channel)
+			return -EOPNOTSUPP;
+		item = NVKM_GSP_RUSD_POWER_CAP;
 		break;
 	default:
 		return -EOPNOTSUPP;
 	}
 
+	if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+		return -EINVAL;
+
+	ret = nvkm_gsp_rusd_read(gsp, item, &value);
+	if (ret)
+		return ret;
+
+	*val = value;
+
 	return 0;
 }
 
+static int
+nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
+{
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
+	if (nvkm_gsp_rusd(gsp))
+		return nouveau_rusd_power_read(drm_dev, gsp, attr, channel, val);
+	else {
+		if (!iccsense)
+			return -EOPNOTSUPP;
+
+		switch (attr) {
+		case hwmon_power_input:
+			if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+				return -EINVAL;
+			*val = nvkm_iccsense_read_all(iccsense);
+			break;
+		case hwmon_power_max:
+			*val = iccsense->power_w_max;
+			break;
+		case hwmon_power_crit:
+			*val = iccsense->power_w_crit;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+
+		return 0;
+	}
+}
+
 static int
 nouveau_temp_write(struct device *dev, u32 attr, int channel, long val)
 {
@@ -597,6 +770,8 @@ nouveau_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
 }
 
 static const char input_label[] = "GPU core";
+static const char * const nouveau_temp_labels[] = { "gpu", "vram" };
+static const char * const nouveau_power_labels[] = { "gpu", "board", "vram" };
 
 static int
 nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
@@ -607,6 +782,18 @@ nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 		return 0;
 	}
 
+	if (type == hwmon_temp && attr == hwmon_temp_label &&
+	    channel < ARRAY_SIZE(nouveau_temp_labels)) {
+		*buf = nouveau_temp_labels[channel];
+		return 0;
+	}
+
+	if (type == hwmon_power && attr == hwmon_power_label &&
+	    channel < ARRAY_SIZE(nouveau_power_labels)) {
+		*buf = nouveau_power_labels[channel];
+		return 0;
+	}
+
 	return -EOPNOTSUPP;
 }
 
@@ -667,13 +854,39 @@ nouveau_hwmon_init(struct drm_device *dev)
 	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
 	struct nvkm_therm *therm = nvxx_therm(drm);
 	struct nvkm_volt *volt = nvxx_volt(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 	const struct attribute_group *special_groups[N_ATTR_GROUPS];
 	struct nouveau_hwmon *hwmon;
 	struct device *hwmon_dev;
+	bool rusd_active = false;
 	int ret = 0;
 	int i = 0;
 
-	if (!iccsense && !therm && !volt) {
+	if (nvkm_gsp_rusd(gsp)) {
+		/* Per-channel visibility is latched below from live RUSD
+		 * section validity, so wait for GSP-RM's first poll of both
+		 * requested groups (thermal, power). It is normally completed
+		 * long before we get here and this is a last resort.
+		 */
+		unsigned long timeout = jiffies +
+			msecs_to_jiffies(2 * gsp->rusd.poll_ms);
+		bool thermal, power;
+
+		for (;;) {
+			thermal = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_TEMP_GPU);
+			power = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU) ||
+				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG) ||
+				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP);
+			if ((thermal && power) || !time_before(jiffies, timeout))
+				break;
+			msleep(20);
+		}
+
+		rusd_active = thermal || power;
+	}
+
+	if (!iccsense && !therm && !volt && !rusd_active) {
 		NV_DEBUG(drm, "Skipping hwmon registration\n");
 		return 0;
 	}
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 3/6] drm/nouveau: add GSP hwmon support
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

GSP boards currently register no hwmon device at all, so every Turing
through Blackwell user has been running without any temperature or power
readout. Wire the RUSD telemetry up as the first one:

  temp1_input/_label   "gpu"    temperatures[GPU]
  temp2_input/_label   "vram"   temperatures[HBM], HBM boards only
  power1_input/average "gpu"    inst/avg GPU power
  power1_cap                    enforced power limit (read-only)
  power2_input/average "board"  module power, where supported
  power3_average       "vram"   memory rail average, where supported
  update_interval               the programmed RUSD poll interval

All channels are standard hwmon ABI, per-channel visibility is decided
at registration time from live section validity, since support varies
per SKU at runtime. If nothing becomes valid, no hwmon device is
registered, matching today's behaviour on GSP boards.

The legacy therm/volt/iccsense paths are untouched and the new
channels and attributes are gated on RUSD, so pre-GSP boards see no
change.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/nouveau_hwmon.c | 375 +++++++++++++++++++-----
 1 file changed, 294 insertions(+), 81 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_hwmon.c b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
index 726397ab035d..d449431d4a40 100644
--- a/drivers/gpu/drm/nouveau/nouveau_hwmon.c
+++ b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
@@ -25,6 +25,8 @@
 #ifdef CONFIG_ACPI
 #include <linux/acpi.h>
 #endif
+#include <linux/delay.h>
+#include <linux/jiffies.h>
 #include <linux/power_supply.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
@@ -32,6 +34,7 @@
 #include "nouveau_drv.h"
 #include "nouveau_hwmon.h"
 
+#include <nvkm/subdev/gsp.h>
 #include <nvkm/subdev/iccsense.h>
 #include <nvkm/subdev/volt.h>
 
@@ -215,10 +218,11 @@ static const struct hwmon_channel_info * const nouveau_info[] = {
 	HWMON_CHANNEL_INFO(chip,
 			   HWMON_C_UPDATE_INTERVAL),
 	HWMON_CHANNEL_INFO(temp,
-			   HWMON_T_INPUT |
+			   HWMON_T_INPUT | HWMON_T_LABEL |
 			   HWMON_T_MAX | HWMON_T_MAX_HYST |
 			   HWMON_T_CRIT | HWMON_T_CRIT_HYST |
-			   HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST),
+			   HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL),
 	HWMON_CHANNEL_INFO(fan,
 			   HWMON_F_INPUT),
 	HWMON_CHANNEL_INFO(in,
@@ -228,7 +232,10 @@ static const struct hwmon_channel_info * const nouveau_info[] = {
 	HWMON_CHANNEL_INFO(pwm,
 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
 	HWMON_CHANNEL_INFO(power,
-			   HWMON_P_INPUT | HWMON_P_CAP_MAX | HWMON_P_CRIT),
+			   HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_CRIT |
+			   HWMON_P_AVERAGE | HWMON_P_CAP | HWMON_P_LABEL,
+			   HWMON_P_INPUT | HWMON_P_AVERAGE | HWMON_P_LABEL,
+			   HWMON_P_AVERAGE | HWMON_P_LABEL),
 	NULL
 };
 
@@ -243,52 +250,144 @@ nouveau_chip_is_visible(const void *data, u32 attr, int channel)
 	}
 }
 
-static umode_t
-nouveau_power_is_visible(const void *data, u32 attr, int channel)
+static bool
+nouveau_rusd_ok(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_item item)
 {
-	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	int retries = 3;
+	s64 val;
+	int ret;
 
-	if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
-		return 0;
+	/* Visibility decisions are made only once at registration. So retry
+	 * to avoid letting racing a GSP write window (-EAGAIN) hide a channel
+	 * for good.
+	 */
+	do {
+		ret = nvkm_gsp_rusd_read(gsp, item, &val);
+		if (ret != -EAGAIN)
+			break;
+		usleep_range(100, 200);
+	} while (--retries);
+
+	return ret == 0;
+}
 
+static umode_t
+nouveau_rusd_power_is_visible(struct nvkm_gsp *gsp, u32 attr, int channel)
+{
 	switch (attr) {
 	case hwmon_power_input:
-		return 0444;
-	case hwmon_power_max:
-		if (iccsense->power_w_max)
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU))
+			return 0444;
+		if (channel == 1 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD))
+			return 0444;
+		return 0;
+	case hwmon_power_average:
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG))
+			return 0444;
+		if (channel == 1 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD_AVG))
+			return 0444;
+		if (channel == 2 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_VRAM_AVG))
 			return 0444;
 		return 0;
-	case hwmon_power_crit:
-		if (iccsense->power_w_crit)
+	case hwmon_power_cap:
+		/* The enforced limit is only telemetry here. Setting it needs ctrls nouveau
+		 * doesn't implement.
+		 */
+		if (channel == 0 && nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP))
 			return 0444;
 		return 0;
+	case hwmon_power_label:
+		switch (channel) {
+		case 0:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP))
+				return 0444;
+			break;
+		case 1:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD) ||
+			    nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_BOARD_AVG))
+				return 0444;
+			break;
+		case 2:
+			if (nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_VRAM_AVG))
+				return 0444;
+			break;
+		}
+		return 0;
 	default:
 		return 0;
 	}
 }
 
 static umode_t
-nouveau_temp_is_visible(const void *data, u32 attr, int channel)
+nouveau_power_is_visible(const void *data, u32 attr, int channel)
 {
 	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
-	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 
-	if (!therm || !therm->attr_get || nvkm_therm_temp_get(therm) < 0)
-		return 0;
+	if (nvkm_gsp_rusd(gsp))
+		return nouveau_rusd_power_is_visible(gsp, attr, channel);
+	else {
+		if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
+			return 0;
 
-	switch (attr) {
-	case hwmon_temp_input:
-		return 0444;
-	case hwmon_temp_max:
-	case hwmon_temp_max_hyst:
-	case hwmon_temp_crit:
-	case hwmon_temp_crit_hyst:
-	case hwmon_temp_emergency:
-	case hwmon_temp_emergency_hyst:
-		return 0644;
-	default:
-		return 0;
+		switch (attr) {
+		case hwmon_power_input:
+			return 0444;
+		case hwmon_power_max:
+			if (iccsense->power_w_max)
+				return 0444;
+			return 0;
+		case hwmon_power_crit:
+			if (iccsense->power_w_crit)
+				return 0444;
+			return 0;
+		default:
+			return 0;
+		}
+	}
+}
+
+static umode_t
+nouveau_temp_is_visible(const void *data, u32 attr, int channel)
+{
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
+	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
+	if (nvkm_gsp_rusd(gsp)) {
+		switch (attr) {
+		case hwmon_temp_input:
+		case hwmon_temp_label:
+			if (nouveau_rusd_ok(gsp, channel ? NVKM_GSP_RUSD_TEMP_HBM :
+							   NVKM_GSP_RUSD_TEMP_GPU))
+				return 0444;
+			return 0;
+		default:
+			/* No threshold data in the RUSD buffer. */
+			return 0;
+		}
+	} else {
+		if (!therm || !therm->attr_get || nvkm_therm_temp_get(therm) < 0)
+			return 0;
+
+		switch (attr) {
+		case hwmon_temp_input:
+			return 0444;
+		case hwmon_temp_max:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+		case hwmon_temp_emergency:
+		case hwmon_temp_emergency_hyst:
+			return 0644;
+		default:
+			return 0;
+		}
 	}
 }
 
@@ -351,9 +450,13 @@ nouveau_fan_is_visible(const void *data, u32 attr, int channel)
 static int
 nouveau_chip_read(struct device *dev, u32 attr, int channel, long *val)
 {
+	struct nouveau_drm *drm = nouveau_drm(dev_get_drvdata(dev));
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
 	switch (attr) {
 	case hwmon_chip_update_interval:
-		*val = 1000;
+		*val = nvkm_gsp_rusd(gsp) ? gsp->rusd.poll_ms : 1000;
 		break;
 	default:
 		return -EOPNOTSUPP;
@@ -368,47 +471,67 @@ nouveau_temp_read(struct device *dev, u32 attr, int channel, long *val)
 	struct drm_device *drm_dev = dev_get_drvdata(dev);
 	struct nouveau_drm *drm = nouveau_drm(drm_dev);
 	struct nvkm_therm *therm = nvxx_therm(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 	int ret;
 
-	if (!therm || !therm->attr_get)
-		return -EOPNOTSUPP;
+	if (nvkm_gsp_rusd(gsp)) {
+		s64 value;
+
+		if (attr != hwmon_temp_input)
+			return -EOPNOTSUPP;
 
-	switch (attr) {
-	case hwmon_temp_input:
 		if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
 			return -EINVAL;
-		ret = nvkm_therm_temp_get(therm);
-		*val = ret < 0 ? ret : (ret * 1000);
-		break;
-	case hwmon_temp_max:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
-					* 1000;
-		break;
-	case hwmon_temp_max_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
-					* 1000;
-		break;
-	case hwmon_temp_crit:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
-					* 1000;
-		break;
-	case hwmon_temp_crit_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
-					* 1000;
-		break;
-	case hwmon_temp_emergency:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
-					* 1000;
-		break;
-	case hwmon_temp_emergency_hyst:
-		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
-					* 1000;
-		break;
-	default:
-		return -EOPNOTSUPP;
-	}
 
-	return 0;
+		ret = nvkm_gsp_rusd_read(gsp, channel ? NVKM_GSP_RUSD_TEMP_HBM :
+							NVKM_GSP_RUSD_TEMP_GPU, &value);
+		if (ret)
+			return ret;
+
+		*val = value;
+		return 0;
+	} else {
+		if (!therm || !therm->attr_get)
+			return -EOPNOTSUPP;
+
+		switch (attr) {
+		case hwmon_temp_input:
+			if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+				return -EINVAL;
+			ret = nvkm_therm_temp_get(therm);
+			*val = ret < 0 ? ret : (ret * 1000);
+			break;
+		case hwmon_temp_max:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
+						* 1000;
+			break;
+		case hwmon_temp_max_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
+						* 1000;
+			break;
+		case hwmon_temp_crit:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
+						* 1000;
+			break;
+		case hwmon_temp_crit_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
+						* 1000;
+			break;
+		case hwmon_temp_emergency:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
+						* 1000;
+			break;
+		case hwmon_temp_emergency_hyst:
+			*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
+						* 1000;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+
+		return 0;
+	}
 }
 
 static int
@@ -492,34 +615,84 @@ nouveau_pwm_read(struct device *dev, u32 attr, int channel, long *val)
 }
 
 static int
-nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
+nouveau_rusd_power_read(struct drm_device *drm_dev, struct nvkm_gsp *gsp,
+			u32 attr, int channel, long *val)
 {
-	struct drm_device *drm_dev = dev_get_drvdata(dev);
-	struct nouveau_drm *drm = nouveau_drm(drm_dev);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
-
-	if (!iccsense)
-		return -EOPNOTSUPP;
+	enum nvkm_gsp_rusd_item item;
+	s64 value;
+	int ret;
 
 	switch (attr) {
 	case hwmon_power_input:
-		if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
-			return -EINVAL;
-		*val = nvkm_iccsense_read_all(iccsense);
+		if (channel == 2)
+			return -EOPNOTSUPP;
+		item = channel ? NVKM_GSP_RUSD_POWER_BOARD :
+				 NVKM_GSP_RUSD_POWER_GPU;
 		break;
-	case hwmon_power_max:
-		*val = iccsense->power_w_max;
+	case hwmon_power_average:
+		if (channel == 0)
+			item = NVKM_GSP_RUSD_POWER_GPU_AVG;
+		else if (channel == 1)
+			item = NVKM_GSP_RUSD_POWER_BOARD_AVG;
+		else
+			item = NVKM_GSP_RUSD_POWER_VRAM_AVG;
 		break;
-	case hwmon_power_crit:
-		*val = iccsense->power_w_crit;
+	case hwmon_power_cap:
+		if (channel)
+			return -EOPNOTSUPP;
+		item = NVKM_GSP_RUSD_POWER_CAP;
 		break;
 	default:
 		return -EOPNOTSUPP;
 	}
 
+	if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+		return -EINVAL;
+
+	ret = nvkm_gsp_rusd_read(gsp, item, &value);
+	if (ret)
+		return ret;
+
+	*val = value;
+
 	return 0;
 }
 
+static int
+nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
+{
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
+
+	if (nvkm_gsp_rusd(gsp))
+		return nouveau_rusd_power_read(drm_dev, gsp, attr, channel, val);
+	else {
+		if (!iccsense)
+			return -EOPNOTSUPP;
+
+		switch (attr) {
+		case hwmon_power_input:
+			if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
+				return -EINVAL;
+			*val = nvkm_iccsense_read_all(iccsense);
+			break;
+		case hwmon_power_max:
+			*val = iccsense->power_w_max;
+			break;
+		case hwmon_power_crit:
+			*val = iccsense->power_w_crit;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+
+		return 0;
+	}
+}
+
 static int
 nouveau_temp_write(struct device *dev, u32 attr, int channel, long val)
 {
@@ -597,6 +770,8 @@ nouveau_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
 }
 
 static const char input_label[] = "GPU core";
+static const char * const nouveau_temp_labels[] = { "gpu", "vram" };
+static const char * const nouveau_power_labels[] = { "gpu", "board", "vram" };
 
 static int
 nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
@@ -607,6 +782,18 @@ nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 		return 0;
 	}
 
+	if (type == hwmon_temp && attr == hwmon_temp_label &&
+	    channel < ARRAY_SIZE(nouveau_temp_labels)) {
+		*buf = nouveau_temp_labels[channel];
+		return 0;
+	}
+
+	if (type == hwmon_power && attr == hwmon_power_label &&
+	    channel < ARRAY_SIZE(nouveau_power_labels)) {
+		*buf = nouveau_power_labels[channel];
+		return 0;
+	}
+
 	return -EOPNOTSUPP;
 }
 
@@ -667,13 +854,39 @@ nouveau_hwmon_init(struct drm_device *dev)
 	struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
 	struct nvkm_therm *therm = nvxx_therm(drm);
 	struct nvkm_volt *volt = nvxx_volt(drm);
+	struct nvkm_device *device = drm->nvkm;
+	struct nvkm_gsp *gsp = device->gsp;
 	const struct attribute_group *special_groups[N_ATTR_GROUPS];
 	struct nouveau_hwmon *hwmon;
 	struct device *hwmon_dev;
+	bool rusd_active = false;
 	int ret = 0;
 	int i = 0;
 
-	if (!iccsense && !therm && !volt) {
+	if (nvkm_gsp_rusd(gsp)) {
+		/* Per-channel visibility is latched below from live RUSD
+		 * section validity, so wait for GSP-RM's first poll of both
+		 * requested groups (thermal, power). It is normally completed
+		 * long before we get here and this is a last resort.
+		 */
+		unsigned long timeout = jiffies +
+			msecs_to_jiffies(2 * gsp->rusd.poll_ms);
+		bool thermal, power;
+
+		for (;;) {
+			thermal = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_TEMP_GPU);
+			power = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU) ||
+				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG) ||
+				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP);
+			if ((thermal && power) || !time_before(jiffies, timeout))
+				break;
+			msleep(20);
+		}
+
+		rusd_active = thermal || power;
+	}
+
+	if (!iccsense && !therm && !volt && !rusd_active) {
 		NV_DEBUG(drm, "Skipping hwmon registration\n");
 		return 0;
 	}
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 4/6] drm/nouveau: expose RUSD telemetry via sysfs
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

Add a "rusd" attribute group under the DRM device
(/sys/class/drm/cardN/device/rusd/) exposing every GSP-polled RUSD
telemetry value as its own world-readable file: temperatures, the full
set of power readings, clocks, GPU/memory/engine utilization, current
pstate, clock throttle reasons, ECC error counts, memory row-remap
state and PCIe link counters.

The data that fits hwmon (temperature, power) is already published
there, this interface is for the rest, which has no hwmon channel type,
and for tools that want a complete, self-contained view. Each file reads
one decoded value through nvkm_gsp_rusd_read(). nvkm owns the units and
conversions, so the show() is a thin wrapper.

The group is created only on GSP-RM boards whose firmware provides RUSD
(r570+); all files appear when RUSD is active, and a section that is
unsupported on the board (e.g. ECC on a consumer part) or has not been
polled yet reads -ENODATA rather than being raced out of existence at
probe time. Because reads are demand-driven, GSP only polls a value's
group while its files are being read.

Files are documented in Documentation/ABI/testing/sysfs-driver-nouveau.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../ABI/testing/sysfs-driver-nouveau          | 152 +++++++++++
 MAINTAINERS                                   |   1 +
 drivers/gpu/drm/nouveau/Kbuild                |   1 +
 drivers/gpu/drm/nouveau/nouveau_drm.c         |   3 +
 drivers/gpu/drm/nouveau/nouveau_drv.h         |   1 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 250 ++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_sysfs.h       |  10 +
 7 files changed, 418 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-nouveau
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.c
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.h

diff --git a/Documentation/ABI/testing/sysfs-driver-nouveau b/Documentation/ABI/testing/sysfs-driver-nouveau
new file mode 100644
index 000000000000..947474b9955d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-nouveau
@@ -0,0 +1,152 @@
+What:		/sys/class/drm/card<n>/device/rusd/
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		GPU telemetry read from NVIDIA GSP-RM's "RM User Shared Data"
+		(RUSD) buffer. Present only on GSP-RM boards whose firmware
+		supports RUSD (r570 and later); absent otherwise. Each file is
+		a single value, world-readable. GSP polls a value's data group
+		only while files in that group are being read, so the first read
+		after an idle period, and reads of a section not supported on the
+		board, return -ENODATA; a subsequent read returns data once the
+		group has been polled. All files are read-only.
+
+What:		/sys/class/drm/card<n>/device/rusd/temp_gpu
+What:		/sys/class/drm/card<n>/device/rusd/temp_hbm
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		GPU core and HBM/VRAM temperatures, in millidegrees Celsius.
+		temp_hbm is meaningful only on boards with on-package memory
+		temperature sensing.
+
+What:		/sys/class/drm/card<n>/device/rusd/power_gpu
+What:		/sys/class/drm/card<n>/device/rusd/power_gpu_average
+What:		/sys/class/drm/card<n>/device/rusd/power_board
+What:		/sys/class/drm/card<n>/device/rusd/power_board_average
+What:		/sys/class/drm/card<n>/device/rusd/power_vram_average
+What:		/sys/class/drm/card<n>/device/rusd/power_cpu
+What:		/sys/class/drm/card<n>/device/rusd/power_cap
+What:		/sys/class/drm/card<n>/device/rusd/power_limit_requested
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Power readings, in microwatts. power_gpu / power_board /
+		power_cpu are instantaneous GPU, board (module) and CPU power;
+		the *_average variants are time-averaged. power_cap is the
+		enforced power limit and power_limit_requested the requested one
+		(read-only; RUSD does not set limits). Board/module and CPU
+		values are absent on form factors that do not report them (e.g.
+		module power on Ampere/Hopper add-in boards).
+
+What:		/sys/class/drm/card<n>/device/rusd/clock_graphics
+What:		/sys/class/drm/card<n>/device/rusd/clock_memory
+What:		/sys/class/drm/card<n>/device/rusd/clock_video
+What:		/sys/class/drm/card<n>/device/rusd/clock_sm
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Target clock frequencies of the graphics, memory, video and SM
+		domains, in MHz. A domain a board does not implement reads 0.
+
+What:		/sys/class/drm/card<n>/device/rusd/util_gpu
+What:		/sys/class/drm/card<n>/device/rusd/util_memory
+What:		/sys/class/drm/card<n>/device/rusd/util_nvenc
+What:		/sys/class/drm/card<n>/device/rusd/util_nvdec
+What:		/sys/class/drm/card<n>/device/rusd/util_nvjpg
+What:		/sys/class/drm/card<n>/device/rusd/util_nvofa
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Utilization of the GPU, the memory controller and the NVENC,
+		NVDEC, NVJPG and NVOFA engines, as a percentage (0-100). These
+		are device-global, not per-process.
+
+What:		/sys/class/drm/card<n>/device/rusd/util_nvenc_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvdec_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvjpg_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvofa_period
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Sampling period, in microseconds, over which the matching
+		engine utilization above was measured.
+
+What:		/sys/class/drm/card<n>/device/rusd/pstate
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Current performance state (P-state) index; lower is higher
+		performance (P0 is maximum).
+
+What:		/sys/class/drm/card<n>/device/rusd/throttle_status
+What:		/sys/class/drm/card<n>/device/rusd/throttle_gpu_idle
+What:		/sys/class/drm/card<n>/device/rusd/throttle_app_clock
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sw_power_cap
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_slowdown
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sync_boost
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sw_thermal
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_thermal
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_power_brake
+What:		/sys/class/drm/card<n>/device/rusd/throttle_display_clock
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Clock throttling reasons. throttle_status is the raw bitmask of
+		all active reasons; the remaining files each read 0 or 1 for one
+		reason (GPU idle, application clock setting, software power cap,
+		hardware slowdown, sync boost, software/hardware thermal
+		slowdown, hardware power brake, and display clock setting).
+
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ue_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ue_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ue_aggregate
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		ECC error counts for total, DRAM and SRAM, split into
+		correctable (ce) and uncorrectable (ue) errors, each as a
+		volatile (since boot) and an aggregate (lifetime) count.
+		Present only on boards with ECC.
+
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_max
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_high
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_partial
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_low
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_none
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_correctable
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_uncorrectable
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_pending
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_failure
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Memory row-remapping state. The histogram_* files count memory
+		banks by remap-resource availability (max/high/partial/low/none);
+		rowremap_correctable and rowremap_uncorrectable count remapped
+		rows; rowremap_pending and rowremap_failure read 0 or 1. Present
+		only on boards with row remapping (datacenter parts).
+
+What:		/sys/class/drm/card<n>/device/rusd/pcie_gen
+What:		/sys/class/drm/card<n>/device/rusd/pcie_linecode_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_crc_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_naks_received
+What:		/sys/class/drm/card<n>/device/rusd/pcie_failed_l0s_exits
+What:		/sys/class/drm/card<n>/device/rusd/pcie_correctable_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_nonfatal_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_fatal_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_unsupported_requests
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		PCIe link telemetry. pcie_gen is a packed gen/width bitfield;
+		the remaining files are link error and event counters.
\ No newline at end of file
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..8ae43f0e83c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8289,6 +8289,7 @@ Q:	https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests
 B:	https://gitlab.freedesktop.org/drm/nouveau/-/issues
 C:	irc://irc.oftc.net/nouveau
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/ABI/testing/sysfs-driver-nouveau
 F:	drivers/gpu/drm/nouveau/
 F:	include/uapi/drm/nouveau_drm.h
 
diff --git a/drivers/gpu/drm/nouveau/Kbuild b/drivers/gpu/drm/nouveau/Kbuild
index 385d24530d1e..00d81133533d 100644
--- a/drivers/gpu/drm/nouveau/Kbuild
+++ b/drivers/gpu/drm/nouveau/Kbuild
@@ -26,6 +26,7 @@ nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o
 nouveau-$(CONFIG_LEDS_CLASS) += nouveau_led.o
 nouveau-y += nouveau_nvif.o
 nouveau-$(CONFIG_NOUVEAU_PLATFORM_DRIVER) += nouveau_platform.o
+nouveau-y += nouveau_sysfs.o
 nouveau-y += nouveau_vga.o
 
 # DRM - memory management
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index e16f59b00f6f..49f73f295664 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -60,6 +60,7 @@
 #include "nouveau_vga.h"
 #include "nouveau_led.h"
 #include "nouveau_hwmon.h"
+#include "nouveau_sysfs.h"
 #include "nouveau_acpi.h"
 #include "nouveau_bios.h"
 #include "nouveau_ioctl.h"
@@ -591,6 +592,7 @@ nouveau_drm_device_fini(struct nouveau_drm *drm)
 	nouveau_dmem_fini(drm);
 	nouveau_svm_fini(drm);
 	nouveau_hwmon_fini(dev);
+	nouveau_sysfs_fini(drm);
 	nouveau_debugfs_fini(drm);
 
 	if (dev->mode_config.num_crtc)
@@ -675,6 +677,7 @@ nouveau_drm_device_init(struct nouveau_drm *drm)
 	}
 
 	nouveau_debugfs_init(drm);
+	nouveau_sysfs_init(drm);
 	nouveau_hwmon_init(dev);
 	nouveau_svm_init(drm);
 	nouveau_dmem_init(drm);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 1c2523e2f92e..a3e343085cc1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -296,6 +296,7 @@ struct nouveau_drm {
 	/* power management */
 	struct nouveau_hwmon *hwmon;
 	struct nouveau_debugfs *debugfs;
+	bool rusd_sysfs;
 
 	/* led management */
 	struct nouveau_led *led;
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.c b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
new file mode 100644
index 000000000000..f3d9462deaba
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2026 Valve Corp.
+ *
+ * Expose the GPU's RM User Shared Data (RUSD) telemetry, polled by GSP-RM,
+ * as world-readable sysfs files under the DRM device's "rusd" group. Each
+ * file is one decoded value; nvkm owns the units and conversions. A section
+ * that is not supported on this board, or has not been polled yet, reads
+ * -ENODATA. See Documentation/ABI/testing/sysfs-driver-nouveau for units.
+ */
+#include <linux/device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/sysfs.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_sysfs.h"
+
+#include <nvkm/subdev/gsp.h>
+
+static ssize_t
+rusd_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+	int item = to_sensor_dev_attr(attr)->index;
+	s64 val;
+	int ret;
+
+	ret = nvkm_gsp_rusd_read(drm->nvkm->gsp, item, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%lld\n", val);
+}
+
+/* One read-only file per RUSD item; nvkm owns units and conversions. */
+static SENSOR_DEVICE_ATTR(temp_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_TEMP_GPU);
+static SENSOR_DEVICE_ATTR(temp_hbm, 0444, rusd_show, NULL, NVKM_GSP_RUSD_TEMP_HBM);
+static SENSOR_DEVICE_ATTR(power_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_GPU);
+static SENSOR_DEVICE_ATTR(power_gpu_average, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_GPU_AVG);
+static SENSOR_DEVICE_ATTR(power_board, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_BOARD);
+static SENSOR_DEVICE_ATTR(power_board_average, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_POWER_BOARD_AVG);
+static SENSOR_DEVICE_ATTR(power_vram_average, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_VRAM_AVG);
+static SENSOR_DEVICE_ATTR(power_cap, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_CAP);
+static SENSOR_DEVICE_ATTR(power_cpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_CPU);
+static SENSOR_DEVICE_ATTR(power_limit_requested, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED);
+static SENSOR_DEVICE_ATTR(clock_graphics, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_GRAPHICS);
+static SENSOR_DEVICE_ATTR(clock_memory, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_MEMORY);
+static SENSOR_DEVICE_ATTR(clock_video, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_VIDEO);
+static SENSOR_DEVICE_ATTR(clock_sm, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_SM);
+static SENSOR_DEVICE_ATTR(util_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_GPU);
+static SENSOR_DEVICE_ATTR(util_memory, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_MEMORY);
+static SENSOR_DEVICE_ATTR(util_nvenc, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVENC);
+static SENSOR_DEVICE_ATTR(util_nvdec, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVDEC);
+static SENSOR_DEVICE_ATTR(util_nvjpg, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVJPG);
+static SENSOR_DEVICE_ATTR(util_nvofa, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVOFA);
+static SENSOR_DEVICE_ATTR(util_nvenc_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVENC_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvdec_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVDEC_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvjpg_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVJPG_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvofa_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD);
+static SENSOR_DEVICE_ATTR(pstate, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PSTATE);
+static SENSOR_DEVICE_ATTR(throttle_status, 0444, rusd_show, NULL, NVKM_GSP_RUSD_THROTTLE_MASK);
+static SENSOR_DEVICE_ATTR(throttle_gpu_idle, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_GPU_IDLE);
+static SENSOR_DEVICE_ATTR(throttle_app_clock, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_APP_CLOCK);
+static SENSOR_DEVICE_ATTR(throttle_sw_power_cap, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SW_POWER_CAP);
+static SENSOR_DEVICE_ATTR(throttle_hw_slowdown, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_SLOWDOWN);
+static SENSOR_DEVICE_ATTR(throttle_sync_boost, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SYNC_BOOST);
+static SENSOR_DEVICE_ATTR(throttle_sw_thermal, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SW_THERMAL);
+static SENSOR_DEVICE_ATTR(throttle_hw_thermal, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_THERMAL);
+static SENSOR_DEVICE_ATTR(throttle_hw_power_brake, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_POWER_BRAKE);
+static SENSOR_DEVICE_ATTR(throttle_display_clock, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK);
+static SENSOR_DEVICE_ATTR(ecc_total_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_total_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_total_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_total_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_UE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_dram_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_dram_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_dram_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_dram_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_UE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_sram_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_sram_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_sram_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_sram_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_UE_AGG);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_max, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_MAX);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_high, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_partial, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_low, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_LOW);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_none, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_NONE);
+static SENSOR_DEVICE_ATTR(rowremap_correctable, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE);
+static SENSOR_DEVICE_ATTR(rowremap_uncorrectable, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE);
+static SENSOR_DEVICE_ATTR(rowremap_pending, 0444, rusd_show, NULL, NVKM_GSP_RUSD_ROWREMAP_PENDING);
+static SENSOR_DEVICE_ATTR(rowremap_failure, 0444, rusd_show, NULL, NVKM_GSP_RUSD_ROWREMAP_FAILURE);
+static SENSOR_DEVICE_ATTR(pcie_gen, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PCIE_GEN);
+static SENSOR_DEVICE_ATTR(pcie_linecode_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_LINECODE_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_crc_errors, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PCIE_CRC_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_naks_received, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_NAKS_RECEIVED);
+static SENSOR_DEVICE_ATTR(pcie_failed_l0s_exits, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_FAILED_L0S_EXITS);
+static SENSOR_DEVICE_ATTR(pcie_correctable_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_CORRECTABLE_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_nonfatal_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_NONFATAL_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_fatal_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_FATAL_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_unsupported_requests, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS);
+
+static struct attribute *rusd_attrs[] = {
+	&sensor_dev_attr_temp_gpu.dev_attr.attr,
+	&sensor_dev_attr_temp_hbm.dev_attr.attr,
+	&sensor_dev_attr_power_gpu.dev_attr.attr,
+	&sensor_dev_attr_power_gpu_average.dev_attr.attr,
+	&sensor_dev_attr_power_board.dev_attr.attr,
+	&sensor_dev_attr_power_board_average.dev_attr.attr,
+	&sensor_dev_attr_power_vram_average.dev_attr.attr,
+	&sensor_dev_attr_power_cap.dev_attr.attr,
+	&sensor_dev_attr_power_cpu.dev_attr.attr,
+	&sensor_dev_attr_power_limit_requested.dev_attr.attr,
+	&sensor_dev_attr_clock_graphics.dev_attr.attr,
+	&sensor_dev_attr_clock_memory.dev_attr.attr,
+	&sensor_dev_attr_clock_video.dev_attr.attr,
+	&sensor_dev_attr_clock_sm.dev_attr.attr,
+	&sensor_dev_attr_util_gpu.dev_attr.attr,
+	&sensor_dev_attr_util_memory.dev_attr.attr,
+	&sensor_dev_attr_util_nvenc.dev_attr.attr,
+	&sensor_dev_attr_util_nvdec.dev_attr.attr,
+	&sensor_dev_attr_util_nvjpg.dev_attr.attr,
+	&sensor_dev_attr_util_nvofa.dev_attr.attr,
+	&sensor_dev_attr_util_nvenc_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvdec_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvjpg_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvofa_period.dev_attr.attr,
+	&sensor_dev_attr_pstate.dev_attr.attr,
+	&sensor_dev_attr_throttle_status.dev_attr.attr,
+	&sensor_dev_attr_throttle_gpu_idle.dev_attr.attr,
+	&sensor_dev_attr_throttle_app_clock.dev_attr.attr,
+	&sensor_dev_attr_throttle_sw_power_cap.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_slowdown.dev_attr.attr,
+	&sensor_dev_attr_throttle_sync_boost.dev_attr.attr,
+	&sensor_dev_attr_throttle_sw_thermal.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_thermal.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_power_brake.dev_attr.attr,
+	&sensor_dev_attr_throttle_display_clock.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_max.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_high.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_partial.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_low.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_none.dev_attr.attr,
+	&sensor_dev_attr_rowremap_correctable.dev_attr.attr,
+	&sensor_dev_attr_rowremap_uncorrectable.dev_attr.attr,
+	&sensor_dev_attr_rowremap_pending.dev_attr.attr,
+	&sensor_dev_attr_rowremap_failure.dev_attr.attr,
+	&sensor_dev_attr_pcie_gen.dev_attr.attr,
+	&sensor_dev_attr_pcie_linecode_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_crc_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_naks_received.dev_attr.attr,
+	&sensor_dev_attr_pcie_failed_l0s_exits.dev_attr.attr,
+	&sensor_dev_attr_pcie_correctable_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_nonfatal_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_fatal_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_unsupported_requests.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group rusd_group = {
+	.name = "rusd",
+	.attrs = rusd_attrs,
+};
+
+int
+nouveau_sysfs_init(struct nouveau_drm *drm)
+{
+	int ret;
+
+	/*
+	 * Only GPUs running GSP with RUSD registered (r570+) get the group. On
+	 * everything else there is nothing to expose. All files appear when RUSD
+	 * is active. Sections unsupported on a given GPU read -ENODATA.
+	 *
+	 * The group is NOT devm-managed on drm->dev->dev: that device's devres
+	 * outlives nouveau's remove path (which frees the drm and nvkm/gsp
+	 * objects the files read), so it is torn down explicitly in
+	 * nouveau_sysfs_fini() before those objects go away.
+	 */
+	if (!nvkm_gsp_rusd(drm->nvkm->gsp))
+		return 0;
+
+	ret = device_add_group(drm->dev->dev, &rusd_group);
+	if (ret)
+		return ret;
+
+	drm->rusd_sysfs = true;
+	return 0;
+}
+
+void
+nouveau_sysfs_fini(struct nouveau_drm *drm)
+{
+	if (drm->rusd_sysfs) {
+		device_remove_group(drm->dev->dev, &rusd_group);
+		drm->rusd_sysfs = false;
+	}
+}
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.h b/drivers/gpu/drm/nouveau/nouveau_sysfs.h
new file mode 100644
index 000000000000..4acd1ea7d3f4
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: MIT */
+#ifndef __NOUVEAU_SYSFS_H__
+#define __NOUVEAU_SYSFS_H__
+
+struct nouveau_drm;
+
+int nouveau_sysfs_init(struct nouveau_drm *drm);
+void nouveau_sysfs_fini(struct nouveau_drm *drm);
+
+#endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 4/6] drm/nouveau: expose RUSD telemetry via sysfs
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

Add a "rusd" attribute group under the DRM device
(/sys/class/drm/cardN/device/rusd/) exposing every GSP-polled RUSD
telemetry value as its own world-readable file: temperatures, the full
set of power readings, clocks, GPU/memory/engine utilization, current
pstate, clock throttle reasons, ECC error counts, memory row-remap
state and PCIe link counters.

The data that fits hwmon (temperature, power) is already published
there, this interface is for the rest, which has no hwmon channel type,
and for tools that want a complete, self-contained view. Each file reads
one decoded value through nvkm_gsp_rusd_read(). nvkm owns the units and
conversions, so the show() is a thin wrapper.

The group is created only on GSP-RM boards whose firmware provides RUSD
(r570+); all files appear when RUSD is active, and a section that is
unsupported on the board (e.g. ECC on a consumer part) or has not been
polled yet reads -ENODATA rather than being raced out of existence at
probe time. Because reads are demand-driven, GSP only polls a value's
group while its files are being read.

Files are documented in Documentation/ABI/testing/sysfs-driver-nouveau.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../ABI/testing/sysfs-driver-nouveau          | 152 +++++++++++
 MAINTAINERS                                   |   1 +
 drivers/gpu/drm/nouveau/Kbuild                |   1 +
 drivers/gpu/drm/nouveau/nouveau_drm.c         |   3 +
 drivers/gpu/drm/nouveau/nouveau_drv.h         |   1 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 250 ++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_sysfs.h       |  10 +
 7 files changed, 418 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-nouveau
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.c
 create mode 100644 drivers/gpu/drm/nouveau/nouveau_sysfs.h

diff --git a/Documentation/ABI/testing/sysfs-driver-nouveau b/Documentation/ABI/testing/sysfs-driver-nouveau
new file mode 100644
index 000000000000..947474b9955d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-nouveau
@@ -0,0 +1,152 @@
+What:		/sys/class/drm/card<n>/device/rusd/
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		GPU telemetry read from NVIDIA GSP-RM's "RM User Shared Data"
+		(RUSD) buffer. Present only on GSP-RM boards whose firmware
+		supports RUSD (r570 and later); absent otherwise. Each file is
+		a single value, world-readable. GSP polls a value's data group
+		only while files in that group are being read, so the first read
+		after an idle period, and reads of a section not supported on the
+		board, return -ENODATA; a subsequent read returns data once the
+		group has been polled. All files are read-only.
+
+What:		/sys/class/drm/card<n>/device/rusd/temp_gpu
+What:		/sys/class/drm/card<n>/device/rusd/temp_hbm
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		GPU core and HBM/VRAM temperatures, in millidegrees Celsius.
+		temp_hbm is meaningful only on boards with on-package memory
+		temperature sensing.
+
+What:		/sys/class/drm/card<n>/device/rusd/power_gpu
+What:		/sys/class/drm/card<n>/device/rusd/power_gpu_average
+What:		/sys/class/drm/card<n>/device/rusd/power_board
+What:		/sys/class/drm/card<n>/device/rusd/power_board_average
+What:		/sys/class/drm/card<n>/device/rusd/power_vram_average
+What:		/sys/class/drm/card<n>/device/rusd/power_cpu
+What:		/sys/class/drm/card<n>/device/rusd/power_cap
+What:		/sys/class/drm/card<n>/device/rusd/power_limit_requested
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Power readings, in microwatts. power_gpu / power_board /
+		power_cpu are instantaneous GPU, board (module) and CPU power;
+		the *_average variants are time-averaged. power_cap is the
+		enforced power limit and power_limit_requested the requested one
+		(read-only; RUSD does not set limits). Board/module and CPU
+		values are absent on form factors that do not report them (e.g.
+		module power on Ampere/Hopper add-in boards).
+
+What:		/sys/class/drm/card<n>/device/rusd/clock_graphics
+What:		/sys/class/drm/card<n>/device/rusd/clock_memory
+What:		/sys/class/drm/card<n>/device/rusd/clock_video
+What:		/sys/class/drm/card<n>/device/rusd/clock_sm
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Target clock frequencies of the graphics, memory, video and SM
+		domains, in MHz. A domain a board does not implement reads 0.
+
+What:		/sys/class/drm/card<n>/device/rusd/util_gpu
+What:		/sys/class/drm/card<n>/device/rusd/util_memory
+What:		/sys/class/drm/card<n>/device/rusd/util_nvenc
+What:		/sys/class/drm/card<n>/device/rusd/util_nvdec
+What:		/sys/class/drm/card<n>/device/rusd/util_nvjpg
+What:		/sys/class/drm/card<n>/device/rusd/util_nvofa
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Utilization of the GPU, the memory controller and the NVENC,
+		NVDEC, NVJPG and NVOFA engines, as a percentage (0-100). These
+		are device-global, not per-process.
+
+What:		/sys/class/drm/card<n>/device/rusd/util_nvenc_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvdec_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvjpg_period
+What:		/sys/class/drm/card<n>/device/rusd/util_nvofa_period
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Sampling period, in microseconds, over which the matching
+		engine utilization above was measured.
+
+What:		/sys/class/drm/card<n>/device/rusd/pstate
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Current performance state (P-state) index; lower is higher
+		performance (P0 is maximum).
+
+What:		/sys/class/drm/card<n>/device/rusd/throttle_status
+What:		/sys/class/drm/card<n>/device/rusd/throttle_gpu_idle
+What:		/sys/class/drm/card<n>/device/rusd/throttle_app_clock
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sw_power_cap
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_slowdown
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sync_boost
+What:		/sys/class/drm/card<n>/device/rusd/throttle_sw_thermal
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_thermal
+What:		/sys/class/drm/card<n>/device/rusd/throttle_hw_power_brake
+What:		/sys/class/drm/card<n>/device/rusd/throttle_display_clock
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Clock throttling reasons. throttle_status is the raw bitmask of
+		all active reasons; the remaining files each read 0 or 1 for one
+		reason (GPU idle, application clock setting, software power cap,
+		hardware slowdown, sync boost, software/hardware thermal
+		slowdown, hardware power brake, and display clock setting).
+
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_total_ue_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_dram_ue_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ce_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ce_aggregate
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ue_volatile
+What:		/sys/class/drm/card<n>/device/rusd/ecc_sram_ue_aggregate
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		ECC error counts for total, DRAM and SRAM, split into
+		correctable (ce) and uncorrectable (ue) errors, each as a
+		volatile (since boot) and an aggregate (lifetime) count.
+		Present only on boards with ECC.
+
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_max
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_high
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_partial
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_low
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_histogram_none
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_correctable
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_uncorrectable
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_pending
+What:		/sys/class/drm/card<n>/device/rusd/rowremap_failure
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		Memory row-remapping state. The histogram_* files count memory
+		banks by remap-resource availability (max/high/partial/low/none);
+		rowremap_correctable and rowremap_uncorrectable count remapped
+		rows; rowremap_pending and rowremap_failure read 0 or 1. Present
+		only on boards with row remapping (datacenter parts).
+
+What:		/sys/class/drm/card<n>/device/rusd/pcie_gen
+What:		/sys/class/drm/card<n>/device/rusd/pcie_linecode_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_crc_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_naks_received
+What:		/sys/class/drm/card<n>/device/rusd/pcie_failed_l0s_exits
+What:		/sys/class/drm/card<n>/device/rusd/pcie_correctable_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_nonfatal_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_fatal_errors
+What:		/sys/class/drm/card<n>/device/rusd/pcie_unsupported_requests
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		PCIe link telemetry. pcie_gen is a packed gen/width bitfield;
+		the remaining files are link error and event counters.
\ No newline at end of file
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..8ae43f0e83c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8289,6 +8289,7 @@ Q:	https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests
 B:	https://gitlab.freedesktop.org/drm/nouveau/-/issues
 C:	irc://irc.oftc.net/nouveau
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/ABI/testing/sysfs-driver-nouveau
 F:	drivers/gpu/drm/nouveau/
 F:	include/uapi/drm/nouveau_drm.h
 
diff --git a/drivers/gpu/drm/nouveau/Kbuild b/drivers/gpu/drm/nouveau/Kbuild
index 385d24530d1e..00d81133533d 100644
--- a/drivers/gpu/drm/nouveau/Kbuild
+++ b/drivers/gpu/drm/nouveau/Kbuild
@@ -26,6 +26,7 @@ nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o
 nouveau-$(CONFIG_LEDS_CLASS) += nouveau_led.o
 nouveau-y += nouveau_nvif.o
 nouveau-$(CONFIG_NOUVEAU_PLATFORM_DRIVER) += nouveau_platform.o
+nouveau-y += nouveau_sysfs.o
 nouveau-y += nouveau_vga.o
 
 # DRM - memory management
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index e16f59b00f6f..49f73f295664 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -60,6 +60,7 @@
 #include "nouveau_vga.h"
 #include "nouveau_led.h"
 #include "nouveau_hwmon.h"
+#include "nouveau_sysfs.h"
 #include "nouveau_acpi.h"
 #include "nouveau_bios.h"
 #include "nouveau_ioctl.h"
@@ -591,6 +592,7 @@ nouveau_drm_device_fini(struct nouveau_drm *drm)
 	nouveau_dmem_fini(drm);
 	nouveau_svm_fini(drm);
 	nouveau_hwmon_fini(dev);
+	nouveau_sysfs_fini(drm);
 	nouveau_debugfs_fini(drm);
 
 	if (dev->mode_config.num_crtc)
@@ -675,6 +677,7 @@ nouveau_drm_device_init(struct nouveau_drm *drm)
 	}
 
 	nouveau_debugfs_init(drm);
+	nouveau_sysfs_init(drm);
 	nouveau_hwmon_init(dev);
 	nouveau_svm_init(drm);
 	nouveau_dmem_init(drm);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 1c2523e2f92e..a3e343085cc1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -296,6 +296,7 @@ struct nouveau_drm {
 	/* power management */
 	struct nouveau_hwmon *hwmon;
 	struct nouveau_debugfs *debugfs;
+	bool rusd_sysfs;
 
 	/* led management */
 	struct nouveau_led *led;
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.c b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
new file mode 100644
index 000000000000..f3d9462deaba
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2026 Valve Corp.
+ *
+ * Expose the GPU's RM User Shared Data (RUSD) telemetry, polled by GSP-RM,
+ * as world-readable sysfs files under the DRM device's "rusd" group. Each
+ * file is one decoded value; nvkm owns the units and conversions. A section
+ * that is not supported on this board, or has not been polled yet, reads
+ * -ENODATA. See Documentation/ABI/testing/sysfs-driver-nouveau for units.
+ */
+#include <linux/device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/sysfs.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_sysfs.h"
+
+#include <nvkm/subdev/gsp.h>
+
+static ssize_t
+rusd_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+	int item = to_sensor_dev_attr(attr)->index;
+	s64 val;
+	int ret;
+
+	ret = nvkm_gsp_rusd_read(drm->nvkm->gsp, item, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%lld\n", val);
+}
+
+/* One read-only file per RUSD item; nvkm owns units and conversions. */
+static SENSOR_DEVICE_ATTR(temp_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_TEMP_GPU);
+static SENSOR_DEVICE_ATTR(temp_hbm, 0444, rusd_show, NULL, NVKM_GSP_RUSD_TEMP_HBM);
+static SENSOR_DEVICE_ATTR(power_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_GPU);
+static SENSOR_DEVICE_ATTR(power_gpu_average, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_GPU_AVG);
+static SENSOR_DEVICE_ATTR(power_board, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_BOARD);
+static SENSOR_DEVICE_ATTR(power_board_average, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_POWER_BOARD_AVG);
+static SENSOR_DEVICE_ATTR(power_vram_average, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_VRAM_AVG);
+static SENSOR_DEVICE_ATTR(power_cap, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_CAP);
+static SENSOR_DEVICE_ATTR(power_cpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_POWER_CPU);
+static SENSOR_DEVICE_ATTR(power_limit_requested, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_POWER_LIMIT_REQUESTED);
+static SENSOR_DEVICE_ATTR(clock_graphics, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_GRAPHICS);
+static SENSOR_DEVICE_ATTR(clock_memory, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_MEMORY);
+static SENSOR_DEVICE_ATTR(clock_video, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_VIDEO);
+static SENSOR_DEVICE_ATTR(clock_sm, 0444, rusd_show, NULL, NVKM_GSP_RUSD_CLOCK_SM);
+static SENSOR_DEVICE_ATTR(util_gpu, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_GPU);
+static SENSOR_DEVICE_ATTR(util_memory, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_MEMORY);
+static SENSOR_DEVICE_ATTR(util_nvenc, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVENC);
+static SENSOR_DEVICE_ATTR(util_nvdec, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVDEC);
+static SENSOR_DEVICE_ATTR(util_nvjpg, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVJPG);
+static SENSOR_DEVICE_ATTR(util_nvofa, 0444, rusd_show, NULL, NVKM_GSP_RUSD_UTIL_NVOFA);
+static SENSOR_DEVICE_ATTR(util_nvenc_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVENC_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvdec_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVDEC_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvjpg_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVJPG_PERIOD);
+static SENSOR_DEVICE_ATTR(util_nvofa_period, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_UTIL_NVOFA_PERIOD);
+static SENSOR_DEVICE_ATTR(pstate, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PSTATE);
+static SENSOR_DEVICE_ATTR(throttle_status, 0444, rusd_show, NULL, NVKM_GSP_RUSD_THROTTLE_MASK);
+static SENSOR_DEVICE_ATTR(throttle_gpu_idle, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_GPU_IDLE);
+static SENSOR_DEVICE_ATTR(throttle_app_clock, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_APP_CLOCK);
+static SENSOR_DEVICE_ATTR(throttle_sw_power_cap, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SW_POWER_CAP);
+static SENSOR_DEVICE_ATTR(throttle_hw_slowdown, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_SLOWDOWN);
+static SENSOR_DEVICE_ATTR(throttle_sync_boost, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SYNC_BOOST);
+static SENSOR_DEVICE_ATTR(throttle_sw_thermal, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_SW_THERMAL);
+static SENSOR_DEVICE_ATTR(throttle_hw_thermal, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_THERMAL);
+static SENSOR_DEVICE_ATTR(throttle_hw_power_brake, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_HW_POWER_BRAKE);
+static SENSOR_DEVICE_ATTR(throttle_display_clock, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_THROTTLE_DISPLAY_CLOCK);
+static SENSOR_DEVICE_ATTR(ecc_total_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_total_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_total_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_total_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_TOTAL_UE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_dram_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_dram_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_dram_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_dram_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_DRAM_UE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_sram_ce_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_CE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_sram_ce_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_CE_AGG);
+static SENSOR_DEVICE_ATTR(ecc_sram_ue_volatile, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_UE_VOL);
+static SENSOR_DEVICE_ATTR(ecc_sram_ue_aggregate, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ECC_SRAM_UE_AGG);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_max, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_MAX);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_high, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_HIGH);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_partial, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_PARTIAL);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_low, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_LOW);
+static SENSOR_DEVICE_ATTR(rowremap_histogram_none, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_HIST_NONE);
+static SENSOR_DEVICE_ATTR(rowremap_correctable, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_CORRECTABLE);
+static SENSOR_DEVICE_ATTR(rowremap_uncorrectable, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_ROWREMAP_UNCORRECTABLE);
+static SENSOR_DEVICE_ATTR(rowremap_pending, 0444, rusd_show, NULL, NVKM_GSP_RUSD_ROWREMAP_PENDING);
+static SENSOR_DEVICE_ATTR(rowremap_failure, 0444, rusd_show, NULL, NVKM_GSP_RUSD_ROWREMAP_FAILURE);
+static SENSOR_DEVICE_ATTR(pcie_gen, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PCIE_GEN);
+static SENSOR_DEVICE_ATTR(pcie_linecode_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_LINECODE_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_crc_errors, 0444, rusd_show, NULL, NVKM_GSP_RUSD_PCIE_CRC_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_naks_received, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_NAKS_RECEIVED);
+static SENSOR_DEVICE_ATTR(pcie_failed_l0s_exits, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_FAILED_L0S_EXITS);
+static SENSOR_DEVICE_ATTR(pcie_correctable_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_CORRECTABLE_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_nonfatal_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_NONFATAL_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_fatal_errors, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_FATAL_ERRORS);
+static SENSOR_DEVICE_ATTR(pcie_unsupported_requests, 0444, rusd_show, NULL,
+			  NVKM_GSP_RUSD_PCIE_UNSUPPORTED_REQUESTS);
+
+static struct attribute *rusd_attrs[] = {
+	&sensor_dev_attr_temp_gpu.dev_attr.attr,
+	&sensor_dev_attr_temp_hbm.dev_attr.attr,
+	&sensor_dev_attr_power_gpu.dev_attr.attr,
+	&sensor_dev_attr_power_gpu_average.dev_attr.attr,
+	&sensor_dev_attr_power_board.dev_attr.attr,
+	&sensor_dev_attr_power_board_average.dev_attr.attr,
+	&sensor_dev_attr_power_vram_average.dev_attr.attr,
+	&sensor_dev_attr_power_cap.dev_attr.attr,
+	&sensor_dev_attr_power_cpu.dev_attr.attr,
+	&sensor_dev_attr_power_limit_requested.dev_attr.attr,
+	&sensor_dev_attr_clock_graphics.dev_attr.attr,
+	&sensor_dev_attr_clock_memory.dev_attr.attr,
+	&sensor_dev_attr_clock_video.dev_attr.attr,
+	&sensor_dev_attr_clock_sm.dev_attr.attr,
+	&sensor_dev_attr_util_gpu.dev_attr.attr,
+	&sensor_dev_attr_util_memory.dev_attr.attr,
+	&sensor_dev_attr_util_nvenc.dev_attr.attr,
+	&sensor_dev_attr_util_nvdec.dev_attr.attr,
+	&sensor_dev_attr_util_nvjpg.dev_attr.attr,
+	&sensor_dev_attr_util_nvofa.dev_attr.attr,
+	&sensor_dev_attr_util_nvenc_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvdec_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvjpg_period.dev_attr.attr,
+	&sensor_dev_attr_util_nvofa_period.dev_attr.attr,
+	&sensor_dev_attr_pstate.dev_attr.attr,
+	&sensor_dev_attr_throttle_status.dev_attr.attr,
+	&sensor_dev_attr_throttle_gpu_idle.dev_attr.attr,
+	&sensor_dev_attr_throttle_app_clock.dev_attr.attr,
+	&sensor_dev_attr_throttle_sw_power_cap.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_slowdown.dev_attr.attr,
+	&sensor_dev_attr_throttle_sync_boost.dev_attr.attr,
+	&sensor_dev_attr_throttle_sw_thermal.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_thermal.dev_attr.attr,
+	&sensor_dev_attr_throttle_hw_power_brake.dev_attr.attr,
+	&sensor_dev_attr_throttle_display_clock.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_total_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_dram_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ce_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ce_aggregate.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ue_volatile.dev_attr.attr,
+	&sensor_dev_attr_ecc_sram_ue_aggregate.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_max.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_high.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_partial.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_low.dev_attr.attr,
+	&sensor_dev_attr_rowremap_histogram_none.dev_attr.attr,
+	&sensor_dev_attr_rowremap_correctable.dev_attr.attr,
+	&sensor_dev_attr_rowremap_uncorrectable.dev_attr.attr,
+	&sensor_dev_attr_rowremap_pending.dev_attr.attr,
+	&sensor_dev_attr_rowremap_failure.dev_attr.attr,
+	&sensor_dev_attr_pcie_gen.dev_attr.attr,
+	&sensor_dev_attr_pcie_linecode_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_crc_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_naks_received.dev_attr.attr,
+	&sensor_dev_attr_pcie_failed_l0s_exits.dev_attr.attr,
+	&sensor_dev_attr_pcie_correctable_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_nonfatal_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_fatal_errors.dev_attr.attr,
+	&sensor_dev_attr_pcie_unsupported_requests.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group rusd_group = {
+	.name = "rusd",
+	.attrs = rusd_attrs,
+};
+
+int
+nouveau_sysfs_init(struct nouveau_drm *drm)
+{
+	int ret;
+
+	/*
+	 * Only GPUs running GSP with RUSD registered (r570+) get the group. On
+	 * everything else there is nothing to expose. All files appear when RUSD
+	 * is active. Sections unsupported on a given GPU read -ENODATA.
+	 *
+	 * The group is NOT devm-managed on drm->dev->dev: that device's devres
+	 * outlives nouveau's remove path (which frees the drm and nvkm/gsp
+	 * objects the files read), so it is torn down explicitly in
+	 * nouveau_sysfs_fini() before those objects go away.
+	 */
+	if (!nvkm_gsp_rusd(drm->nvkm->gsp))
+		return 0;
+
+	ret = device_add_group(drm->dev->dev, &rusd_group);
+	if (ret)
+		return ret;
+
+	drm->rusd_sysfs = true;
+	return 0;
+}
+
+void
+nouveau_sysfs_fini(struct nouveau_drm *drm)
+{
+	if (drm->rusd_sysfs) {
+		device_remove_group(drm->dev->dev, &rusd_group);
+		drm->rusd_sysfs = false;
+	}
+}
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.h b/drivers/gpu/drm/nouveau/nouveau_sysfs.h
new file mode 100644
index 000000000000..4acd1ea7d3f4
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: MIT */
+#ifndef __NOUVEAU_SYSFS_H__
+#define __NOUVEAU_SYSFS_H__
+
+struct nouveau_drm;
+
+int nouveau_sysfs_init(struct nouveau_drm *drm);
+void nouveau_sysfs_fini(struct nouveau_drm *drm);
+
+#endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 5/6] drm/nouveau: expose global VRAM size and usage via sysfs
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

Monitoring tools want a device-wide VRAM total and current usage, but
nouveau exposed neither outside of NOUVEAU_GETPARAM ioctls, which are
awkward to use for some tools as they rely on pulling libdrm.

Add two read-only files in a device attribute group:

  mem_info_vram_total  user-accessible VRAM, i.e. NOUVEAU_GETPARAM_FB_SIZE
  mem_info_vram_used   bytes currently allocated from the VRAM TTM manager

Both read nouveau's own framebuffer and TTM accounting, the exact source
behind the FB_SIZE/VRAM_USED getparams, so they need no GSP and are
present on every board, unlike the GSP-only RUSD group. total is the size
of the pool used draws from, so used never exceeds total. used counts only
memory nouveau itself allocated and can read below firmware-reserved usage.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../ABI/testing/sysfs-driver-nouveau          | 17 +++++-
 drivers/gpu/drm/nouveau/nouveau_drv.h         |  1 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 61 +++++++++++++++++++
 3 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-driver-nouveau b/Documentation/ABI/testing/sysfs-driver-nouveau
index 947474b9955d..968c3ac0c464 100644
--- a/Documentation/ABI/testing/sysfs-driver-nouveau
+++ b/Documentation/ABI/testing/sysfs-driver-nouveau
@@ -149,4 +149,19 @@ Date:		June 2026
 Contact:	nouveau <nouveau@lists.freedesktop.org>
 Description:
 		PCIe link telemetry. pcie_gen is a packed gen/width bitfield;
-		the remaining files are link error and event counters.
\ No newline at end of file
+		the remaining files are link error and event counters.
+
+What:		/sys/class/drm/card<n>/device/mem_info_vram_total
+What:		/sys/class/drm/card<n>/device/mem_info_vram_used
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		The amount of VRAM usable for allocations and the amount currently
+		in use, in bytes, world-readable and read-only. mem_info_vram_total
+		is the installed VRAM minus the region firmware reserves. This is the
+		same value as NOUVEAU_GETPARAM_FB_SIZE, so it can read slightly below
+		the card's marketed size, and it is the size of the pool that
+		mem_info_vram_used draws from (so used never exceeds total). Both
+		come from nouveau's own framebuffer and TTM accounting (the same
+		source as the FB_SIZE/VRAM_USED ioctls) and are present on every
+		nouveau board, independent of GSP-RM.
\ No newline at end of file
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index a3e343085cc1..a8ca86556e2f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -297,6 +297,7 @@ struct nouveau_drm {
 	struct nouveau_hwmon *hwmon;
 	struct nouveau_debugfs *debugfs;
 	bool rusd_sysfs;
+	bool mem_sysfs;
 
 	/* led management */
 	struct nouveau_led *led;
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.c b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
index f3d9462deaba..789b3781bedc 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sysfs.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
@@ -7,11 +7,18 @@
  * file is one decoded value; nvkm owns the units and conversions. A section
  * that is not supported on this board, or has not been polled yet, reads
  * -ENODATA. See Documentation/ABI/testing/sysfs-driver-nouveau for units.
+ *
+ * Also exposes global VRAM size and usage (mem_info_vram_total/used, bytes)
+ * from nouveau's own FB/TTM accounting. Unlike RUSD these do not need GSP and
+ * are present on every board.
  */
 #include <linux/device.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/sysfs.h>
 
+#include <drm/ttm/ttm_placement.h>
+#include <drm/ttm/ttm_resource.h>
+
 #include "nouveau_drv.h"
 #include "nouveau_sysfs.h"
 
@@ -214,11 +221,60 @@ static const struct attribute_group rusd_group = {
 	.attrs = rusd_attrs,
 };
 
+/*
+ * Global VRAM size and current usage, in bytes. These use the same source as the
+ * NOUVEAU_GETPARAM_FB_SIZE/VRAM_USED ioctls, and are exposed here as a more
+ * convenient way for monitoring tools to access the data.
+ *
+ * Unlike the RUSD group these need no GSP and are present on every board.
+ */
+static ssize_t
+mem_info_vram_total_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+
+	return sysfs_emit(buf, "%llu\n", drm->client.device.info.ram_user);
+}
+static DEVICE_ATTR_RO(mem_info_vram_total);
+
+static ssize_t
+mem_info_vram_used_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+	struct ttm_resource_manager *vram = ttm_manager_type(&drm->ttm.bdev, TTM_PL_VRAM);
+
+	return sysfs_emit(buf, "%llu\n", ttm_resource_manager_usage(vram));
+}
+static DEVICE_ATTR_RO(mem_info_vram_used);
+
+static struct attribute *nouveau_mem_attrs[] = {
+	&dev_attr_mem_info_vram_total.attr,
+	&dev_attr_mem_info_vram_used.attr,
+	NULL,
+};
+
+static const struct attribute_group nouveau_mem_group = {
+	.attrs = nouveau_mem_attrs,
+};
+
 int
 nouveau_sysfs_init(struct nouveau_drm *drm)
 {
 	int ret;
 
+	/*
+	 * VRAM size/usage comes from nouveau's own accounting, so this group is
+	 * present on every board (GSP or legacy).
+	 *
+	 * The group is NOT devm-managed on drm->dev->dev: that device's devres
+	 * outlives nouveau's remove path (which frees the drm objects the files
+	 * read), so it is torn down explicitly in nouveau_sysfs_fini() before that.
+	 */
+	ret = device_add_group(drm->dev->dev, &nouveau_mem_group);
+	if (ret)
+		return ret;
+	drm->mem_sysfs = true;
+
 	/*
 	 * Only GPUs running GSP with RUSD registered (r570+) get the group. On
 	 * everything else there is nothing to expose. All files appear when RUSD
@@ -247,4 +303,9 @@ nouveau_sysfs_fini(struct nouveau_drm *drm)
 		device_remove_group(drm->dev->dev, &rusd_group);
 		drm->rusd_sysfs = false;
 	}
+
+	if (drm->mem_sysfs) {
+		device_remove_group(drm->dev->dev, &nouveau_mem_group);
+		drm->mem_sysfs = false;
+	}
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 5/6] drm/nouveau: expose global VRAM size and usage via sysfs
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

Monitoring tools want a device-wide VRAM total and current usage, but
nouveau exposed neither outside of NOUVEAU_GETPARAM ioctls, which are
awkward to use for some tools as they rely on pulling libdrm.

Add two read-only files in a device attribute group:

  mem_info_vram_total  user-accessible VRAM, i.e. NOUVEAU_GETPARAM_FB_SIZE
  mem_info_vram_used   bytes currently allocated from the VRAM TTM manager

Both read nouveau's own framebuffer and TTM accounting, the exact source
behind the FB_SIZE/VRAM_USED getparams, so they need no GSP and are
present on every board, unlike the GSP-only RUSD group. total is the size
of the pool used draws from, so used never exceeds total. used counts only
memory nouveau itself allocated and can read below firmware-reserved usage.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../ABI/testing/sysfs-driver-nouveau          | 17 +++++-
 drivers/gpu/drm/nouveau/nouveau_drv.h         |  1 +
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 61 +++++++++++++++++++
 3 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-driver-nouveau b/Documentation/ABI/testing/sysfs-driver-nouveau
index 947474b9955d..968c3ac0c464 100644
--- a/Documentation/ABI/testing/sysfs-driver-nouveau
+++ b/Documentation/ABI/testing/sysfs-driver-nouveau
@@ -149,4 +149,19 @@ Date:		June 2026
 Contact:	nouveau <nouveau@lists.freedesktop.org>
 Description:
 		PCIe link telemetry. pcie_gen is a packed gen/width bitfield;
-		the remaining files are link error and event counters.
\ No newline at end of file
+		the remaining files are link error and event counters.
+
+What:		/sys/class/drm/card<n>/device/mem_info_vram_total
+What:		/sys/class/drm/card<n>/device/mem_info_vram_used
+Date:		June 2026
+Contact:	nouveau <nouveau@lists.freedesktop.org>
+Description:
+		The amount of VRAM usable for allocations and the amount currently
+		in use, in bytes, world-readable and read-only. mem_info_vram_total
+		is the installed VRAM minus the region firmware reserves. This is the
+		same value as NOUVEAU_GETPARAM_FB_SIZE, so it can read slightly below
+		the card's marketed size, and it is the size of the pool that
+		mem_info_vram_used draws from (so used never exceeds total). Both
+		come from nouveau's own framebuffer and TTM accounting (the same
+		source as the FB_SIZE/VRAM_USED ioctls) and are present on every
+		nouveau board, independent of GSP-RM.
\ No newline at end of file
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index a3e343085cc1..a8ca86556e2f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -297,6 +297,7 @@ struct nouveau_drm {
 	struct nouveau_hwmon *hwmon;
 	struct nouveau_debugfs *debugfs;
 	bool rusd_sysfs;
+	bool mem_sysfs;
 
 	/* led management */
 	struct nouveau_led *led;
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.c b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
index f3d9462deaba..789b3781bedc 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sysfs.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
@@ -7,11 +7,18 @@
  * file is one decoded value; nvkm owns the units and conversions. A section
  * that is not supported on this board, or has not been polled yet, reads
  * -ENODATA. See Documentation/ABI/testing/sysfs-driver-nouveau for units.
+ *
+ * Also exposes global VRAM size and usage (mem_info_vram_total/used, bytes)
+ * from nouveau's own FB/TTM accounting. Unlike RUSD these do not need GSP and
+ * are present on every board.
  */
 #include <linux/device.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/sysfs.h>
 
+#include <drm/ttm/ttm_placement.h>
+#include <drm/ttm/ttm_resource.h>
+
 #include "nouveau_drv.h"
 #include "nouveau_sysfs.h"
 
@@ -214,11 +221,60 @@ static const struct attribute_group rusd_group = {
 	.attrs = rusd_attrs,
 };
 
+/*
+ * Global VRAM size and current usage, in bytes. These use the same source as the
+ * NOUVEAU_GETPARAM_FB_SIZE/VRAM_USED ioctls, and are exposed here as a more
+ * convenient way for monitoring tools to access the data.
+ *
+ * Unlike the RUSD group these need no GSP and are present on every board.
+ */
+static ssize_t
+mem_info_vram_total_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+
+	return sysfs_emit(buf, "%llu\n", drm->client.device.info.ram_user);
+}
+static DEVICE_ATTR_RO(mem_info_vram_total);
+
+static ssize_t
+mem_info_vram_used_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nouveau_drm *drm = dev_get_drvdata(dev);
+	struct ttm_resource_manager *vram = ttm_manager_type(&drm->ttm.bdev, TTM_PL_VRAM);
+
+	return sysfs_emit(buf, "%llu\n", ttm_resource_manager_usage(vram));
+}
+static DEVICE_ATTR_RO(mem_info_vram_used);
+
+static struct attribute *nouveau_mem_attrs[] = {
+	&dev_attr_mem_info_vram_total.attr,
+	&dev_attr_mem_info_vram_used.attr,
+	NULL,
+};
+
+static const struct attribute_group nouveau_mem_group = {
+	.attrs = nouveau_mem_attrs,
+};
+
 int
 nouveau_sysfs_init(struct nouveau_drm *drm)
 {
 	int ret;
 
+	/*
+	 * VRAM size/usage comes from nouveau's own accounting, so this group is
+	 * present on every board (GSP or legacy).
+	 *
+	 * The group is NOT devm-managed on drm->dev->dev: that device's devres
+	 * outlives nouveau's remove path (which frees the drm objects the files
+	 * read), so it is torn down explicitly in nouveau_sysfs_fini() before that.
+	 */
+	ret = device_add_group(drm->dev->dev, &nouveau_mem_group);
+	if (ret)
+		return ret;
+	drm->mem_sysfs = true;
+
 	/*
 	 * Only GPUs running GSP with RUSD registered (r570+) get the group. On
 	 * everything else there is nothing to expose. All files appear when RUSD
@@ -247,4 +303,9 @@ nouveau_sysfs_fini(struct nouveau_drm *drm)
 		device_remove_group(drm->dev->dev, &rusd_group);
 		drm->rusd_sysfs = false;
 	}
+
+	if (drm->mem_sysfs) {
+		device_remove_group(drm->dev->dev, &nouveau_mem_group);
+		drm->mem_sysfs = false;
+	}
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 6/6] drm/nouveau: expose per-client GPU usage via fdinfo
  2026-07-14 21:14 ` Mohamed Ahmed
@ 2026-07-14 21:14   ` Mohamed Ahmed
  -1 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, Milos Tijanic, nouveau, Mohamed Ahmed

nouveau implemented no DRM fdinfo. Wire it up and report per-client memory
and per-engine busy time, which monitoring tools consume generically.

Memory: add a GEM .status callback reporting residency and emit the
standard stats via drm_show_memory_stats(). This works for every client
regardless of submission path.

Engine busy time: account it from the DRM scheduler the EXEC/VM_BIND path
already uses, similar to amdgpu. Each job's drm_sched fence carries
scheduled/finished timestamps; (finished - scheduled) is folded into a
per-client per-engine counter when the job retires (nouveau_sched_free_
job, the per-job hook the scheduler always calls). In-flight jobs are
tracked on a per-client list and walked at fdinfo read time, adding
(now - scheduled) so a currently-running client is accounted without
waiting for its jobs to retire; the resulting non-monotonic correction
on retire is permitted by drm-usage-stats. The drm_sched core back-fills
the scheduled timestamp of in-order retiring jobs, so summing these
intervals approximates the channel's busy wall-clock.

The engine a job is accounted against is recorded on its channel at
allocation time. Only clients using the newer EXEC uAPI produce
drm-engine-* values, legacy GEM_PUSHBUF submissions bypass the scheduler
and are not accounted (memory still is). Of the engine classes the current
uAPI can select, gfx/ce/dec are reachable. enc/jpg/ofa are defined for
when per-engine channels become selectable.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/nouveau_abi16.c | 16 +++++
 drivers/gpu/drm/nouveau/nouveau_chan.h  |  2 +
 drivers/gpu/drm/nouveau/nouveau_drm.c   | 19 ++++++
 drivers/gpu/drm/nouveau/nouveau_drv.h   | 27 ++++++++
 drivers/gpu/drm/nouveau/nouveau_exec.c  |  3 +
 drivers/gpu/drm/nouveau/nouveau_gem.c   | 16 +++++
 drivers/gpu/drm/nouveau/nouveau_sched.c | 87 +++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_sched.h | 11 ++++
 8 files changed, 181 insertions(+)

diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c
index 291203121f0c..63966444d45e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_abi16.c
+++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c
@@ -439,6 +439,22 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS)
 	if (ret)
 		goto done;
 
+	/* Record the channel's engine class for DRM fdinfo accounting. */
+	switch (engine) {
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_CE:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_CE;
+		break;
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSPDEC:
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSVLD:
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSPPP:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_DEC;
+		break;
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_GR:
+	default:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_GFX;
+		break;
+	}
+
 	/* If we're not using the VM_BIND uAPI, we don't need a scheduler.
 	 *
 	 * The client lock is already acquired by nouveau_abi16_get().
diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.h b/drivers/gpu/drm/nouveau/nouveau_chan.h
index bb34b0a6082d..22c484dd0bbe 100644
--- a/drivers/gpu/drm/nouveau/nouveau_chan.h
+++ b/drivers/gpu/drm/nouveau/nouveau_chan.h
@@ -20,6 +20,8 @@ struct nouveau_channel {
 	u64 inst;
 	u32 token;
 
+	u8 fdinfo_engine;
+
 	struct nvif_object vram;
 	struct nvif_object gart;
 	struct nvif_object nvsw;
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 49f73f295664..a0264496a7d6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -35,8 +35,10 @@
 #include <drm/clients/drm_client_setup.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_fbdev_ttm.h>
+#include <drm/drm_file.h>
 #include <drm/drm_gem_ttm_helper.h>
 #include <drm/drm_ioctl.h>
+#include <drm/drm_print.h>
 #include <drm/drm_vblank.h>
 
 #include <core/gpuobj.h>
@@ -262,6 +264,9 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
 	INIT_LIST_HEAD(&cli->worker);
 	mutex_init(&cli->lock);
 
+	spin_lock_init(&cli->fdinfo.lock);
+	INIT_LIST_HEAD(&cli->fdinfo.jobs);
+
 	mutex_lock(&drm->client_mutex);
 	ret = nvif_client_ctor(&drm->_client, cli->name, &cli->base);
 	mutex_unlock(&drm->client_mutex);
@@ -1347,6 +1352,18 @@ nouveau_drm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	return ret;
 }
 
+static void
+nouveau_show_fdinfo(struct drm_printer *p, struct drm_file *file)
+{
+	struct nouveau_cli *cli = nouveau_cli(file);
+
+	/* Per-engine busy time of this client's EXEC jobs, then the standard
+	 * per-region memory stats
+	 */
+	nouveau_sched_show_fdinfo(cli, p);
+	drm_show_memory_stats(p, file);
+}
+
 static const struct file_operations
 nouveau_driver_fops = {
 	.owner = THIS_MODULE,
@@ -1360,6 +1377,7 @@ nouveau_driver_fops = {
 	.compat_ioctl = nouveau_compat_ioctl,
 #endif
 	.llseek = noop_llseek,
+	.show_fdinfo = drm_show_fdinfo,
 	.fop_flags = FOP_UNSIGNED_OFFSET,
 };
 
@@ -1372,6 +1390,7 @@ driver_stub = {
 			   DRIVER_RENDER,
 	.open = nouveau_drm_open,
 	.postclose = nouveau_drm_postclose,
+	.show_fdinfo = PTR_IF(IS_ENABLED(CONFIG_PROC_FS), nouveau_show_fdinfo),
 
 #if defined(CONFIG_DEBUG_FS)
 	.debugfs_init = nouveau_drm_debugfs_init,
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index a8ca86556e2f..ced15ee1aa65 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -65,6 +65,19 @@
 struct nouveau_channel;
 struct platform_device;
 
+/* Engine classes tracked for DRM fdinfo per-client utilisation. The value
+ * NOUVEAU_FDINFO_ENGINE_COUNT doubles as "not accounted"
+ */
+enum nouveau_fdinfo_engine {
+	NOUVEAU_FDINFO_ENGINE_GFX,	/* graphics/compute (GR) */
+	NOUVEAU_FDINFO_ENGINE_CE,	/* copy */
+	NOUVEAU_FDINFO_ENGINE_DEC,	/* video decode */
+	NOUVEAU_FDINFO_ENGINE_ENC,	/* video encode */
+	NOUVEAU_FDINFO_ENGINE_JPG,	/* jpeg */
+	NOUVEAU_FDINFO_ENGINE_OFA,	/* optical flow */
+	NOUVEAU_FDINFO_ENGINE_COUNT,
+};
+
 #include "nouveau_fence.h"
 #include "nouveau_bios.h"
 #include "nouveau_sched.h"
@@ -114,6 +127,20 @@ struct nouveau_cli {
 	struct work_struct work;
 	struct list_head worker;
 	struct mutex lock;
+
+	/* Per-client engine busy-time accounting for DRM fdinfo.
+	 *
+	 * @engine_ns holds the accumulated busy time of retired jobs per
+	 * engine; @jobs is the list of in-flight jobs, walked at fdinfo read
+	 * time to additionally account currently-running work. Both are
+	 * protected by @lock (a leaf spinlock taken from the job free path and
+	 * from the fdinfo show callback)
+	 */
+	struct {
+		spinlock_t lock;
+		struct list_head jobs;
+		u64 engine_ns[NOUVEAU_FDINFO_ENGINE_COUNT];
+	} fdinfo;
 };
 
 struct nouveau_cli_work {
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index a08ab1cfea9b..1215f0669d0d 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -255,6 +255,9 @@ nouveau_exec_job_init(struct nouveau_exec_job **pjob,
 	if (ret)
 		goto err_free_pushs;
 
+	/* Account this job's busy time against the channel's engine in fdinfo */
+	job->base.engine = job->chan->fdinfo_engine;
+
 	return 0;
 
 err_free_pushs:
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index 20dba02d6175..76fbde516722 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -214,10 +214,26 @@ nouveau_gem_object_close(struct drm_gem_object *gem, struct drm_file *file_priv)
 	ttm_bo_unreserve(&nvbo->bo);
 }
 
+static enum drm_gem_object_status
+nouveau_gem_object_status(struct drm_gem_object *gem)
+{
+	struct nouveau_bo *nvbo = nouveau_gem_object(gem);
+	enum drm_gem_object_status status = 0;
+
+	/* nouveau has no userspace madvise/purgeable concept, so only
+	 * residency is reported
+	 */
+	if (nvbo->bo.resource)
+		status |= DRM_GEM_OBJECT_RESIDENT;
+
+	return status;
+}
+
 const struct drm_gem_object_funcs nouveau_gem_object_funcs = {
 	.free = nouveau_gem_object_del,
 	.open = nouveau_gem_object_open,
 	.close = nouveau_gem_object_close,
+	.status = nouveau_gem_object_status,
 	.export = nouveau_gem_prime_export,
 	.pin = nouveau_gem_prime_pin,
 	.unpin = nouveau_gem_prime_unpin,
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c
index 2cbae003d6de..219cc8f621ce 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: MIT
 
+#include <linux/ktime.h>
 #include <linux/slab.h>
+#include <drm/drm_print.h>
 #include <drm/gpu_scheduler.h>
 #include <drm/drm_syncobj.h>
 
@@ -22,6 +24,64 @@ enum nouveau_sched_priority {
 	NOUVEAU_SCHED_PRIORITY_COUNT,
 };
 
+/* Busy time of a single job for fdinfo accounting: (finished - scheduled)
+ * once the job has retired, or (now - scheduled) while it is still running
+ *
+ * The drm_sched core back-fills the scheduled timestamp of an in-order
+ * retiring job with the previous job's finished timestamp, so summing these
+ * intervals over a channel's jobs approximates its busy wall-clock without
+ * double counting work that overlapped in the pipe
+ */
+static u64
+nouveau_job_busy_ns(struct nouveau_job *job)
+{
+	struct drm_sched_fence *s_fence = job->base.s_fence;
+
+	if (!s_fence ||
+	    !test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &s_fence->scheduled.flags))
+		return 0;
+
+	if (!test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &s_fence->finished.flags))
+		return ktime_to_ns(ktime_sub(ktime_get(),
+					     s_fence->scheduled.timestamp));
+
+	return ktime_to_ns(ktime_sub(s_fence->finished.timestamp,
+				     s_fence->scheduled.timestamp));
+}
+
+static const char * const
+nouveau_fdinfo_engine_name[NOUVEAU_FDINFO_ENGINE_COUNT] = {
+	[NOUVEAU_FDINFO_ENGINE_GFX] = "gfx",
+	[NOUVEAU_FDINFO_ENGINE_CE]  = "ce",
+	[NOUVEAU_FDINFO_ENGINE_DEC] = "dec",
+	[NOUVEAU_FDINFO_ENGINE_ENC] = "enc",
+	[NOUVEAU_FDINFO_ENGINE_JPG] = "jpg",
+	[NOUVEAU_FDINFO_ENGINE_OFA] = "ofa",
+};
+
+void
+nouveau_sched_show_fdinfo(struct nouveau_cli *cli, struct drm_printer *p)
+{
+	u64 engine_ns[NOUVEAU_FDINFO_ENGINE_COUNT];
+	struct nouveau_job *job;
+	unsigned int i;
+
+	spin_lock(&cli->fdinfo.lock);
+	for (i = 0; i < NOUVEAU_FDINFO_ENGINE_COUNT; i++)
+		engine_ns[i] = cli->fdinfo.engine_ns[i];
+
+	/* Add the busy time of currently in-flight jobs so a running client is
+	 * accounted without waiting for its jobs to retire
+	 */
+	list_for_each_entry(job, &cli->fdinfo.jobs, fdinfo_entry)
+		engine_ns[job->engine] += nouveau_job_busy_ns(job);
+	spin_unlock(&cli->fdinfo.lock);
+
+	for (i = 0; i < NOUVEAU_FDINFO_ENGINE_COUNT; i++)
+		drm_printf(p, "drm-engine-%s:\t%llu ns\n",
+			   nouveau_fdinfo_engine_name[i], engine_ns[i]);
+}
+
 int
 nouveau_job_init(struct nouveau_job *job,
 		 struct nouveau_job_args *args)
@@ -30,6 +90,8 @@ nouveau_job_init(struct nouveau_job *job,
 	int ret;
 
 	INIT_LIST_HEAD(&job->entry);
+	INIT_LIST_HEAD(&job->fdinfo_entry);
+	job->engine = NOUVEAU_FDINFO_ENGINE_COUNT;
 
 	job->file_priv = args->file_priv;
 	job->cli = nouveau_cli(args->file_priv);
@@ -308,6 +370,16 @@ nouveau_job_submit(struct nouveau_job *job)
 	list_add(&job->entry, &sched->job.list.head);
 	spin_unlock(&sched->job.list.lock);
 
+	/* Track in-flight jobs for fdinfo per-engine accounting. The job is
+	 * removed (and its busy time folded in) from nouveau_sched_free_job(),
+	 * which the scheduler guarantees to call once for every pushed job.
+	 */
+	if (job->engine < NOUVEAU_FDINFO_ENGINE_COUNT) {
+		spin_lock(&job->cli->fdinfo.lock);
+		list_add_tail(&job->fdinfo_entry, &job->cli->fdinfo.jobs);
+		spin_unlock(&job->cli->fdinfo.lock);
+	}
+
 	drm_sched_job_arm(&job->base);
 	job->done_fence = dma_fence_get(&job->base.s_fence->finished);
 	if (job->sync)
@@ -388,6 +460,21 @@ nouveau_sched_free_job(struct drm_sched_job *sched_job)
 {
 	struct nouveau_job *job = to_nouveau_job(sched_job);
 
+	/* Fold the retired job's busy time into the per-client per-engine
+	 * counter and stop tracking it as in-flight. Done before
+	 * nouveau_job_fini() since that releases the scheduler fences this
+	 * reads. The job's finished fence has signalled by now, so
+	 * nouveau_job_busy_ns() returns (finished - scheduled)
+	 */
+	if (job->engine < NOUVEAU_FDINFO_ENGINE_COUNT) {
+		struct nouveau_cli *cli = job->cli;
+
+		spin_lock(&cli->fdinfo.lock);
+		cli->fdinfo.engine_ns[job->engine] += nouveau_job_busy_ns(job);
+		list_del(&job->fdinfo_entry);
+		spin_unlock(&cli->fdinfo.lock);
+	}
+
 	nouveau_job_fini(job);
 }
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 20cd1da8db73..fa80f9b996b1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -56,6 +56,14 @@ struct nouveau_job {
 	struct drm_file *file_priv;
 	struct nouveau_cli *cli;
 
+	/* fdinfo per-engine busy-time accounting: @engine is the engine class
+	 * this job runs on (NOUVEAU_FDINFO_ENGINE_COUNT == not accounted)
+	 * @fdinfo_entry links the job into cli->fdinfo.jobs while it is in
+	 * flight.
+	 */
+	enum nouveau_fdinfo_engine engine;
+	struct list_head fdinfo_entry;
+
 	enum dma_resv_usage resv_usage;
 	struct dma_fence *done_fence;
 
@@ -96,6 +104,9 @@ int nouveau_job_submit(struct nouveau_job *job);
 void nouveau_job_done(struct nouveau_job *job);
 void nouveau_job_free(struct nouveau_job *job);
 
+struct drm_printer;
+void nouveau_sched_show_fdinfo(struct nouveau_cli *cli, struct drm_printer *p);
+
 struct nouveau_sched {
 	struct drm_gpu_scheduler base;
 	struct drm_sched_entity entity;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 6/6] drm/nouveau: expose per-client GPU usage via fdinfo
@ 2026-07-14 21:14   ` Mohamed Ahmed
  0 siblings, 0 replies; 17+ messages in thread
From: Mohamed Ahmed @ 2026-07-14 21:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

nouveau implemented no DRM fdinfo. Wire it up and report per-client memory
and per-engine busy time, which monitoring tools consume generically.

Memory: add a GEM .status callback reporting residency and emit the
standard stats via drm_show_memory_stats(). This works for every client
regardless of submission path.

Engine busy time: account it from the DRM scheduler the EXEC/VM_BIND path
already uses, similar to amdgpu. Each job's drm_sched fence carries
scheduled/finished timestamps; (finished - scheduled) is folded into a
per-client per-engine counter when the job retires (nouveau_sched_free_
job, the per-job hook the scheduler always calls). In-flight jobs are
tracked on a per-client list and walked at fdinfo read time, adding
(now - scheduled) so a currently-running client is accounted without
waiting for its jobs to retire; the resulting non-monotonic correction
on retire is permitted by drm-usage-stats. The drm_sched core back-fills
the scheduled timestamp of in-order retiring jobs, so summing these
intervals approximates the channel's busy wall-clock.

The engine a job is accounted against is recorded on its channel at
allocation time. Only clients using the newer EXEC uAPI produce
drm-engine-* values, legacy GEM_PUSHBUF submissions bypass the scheduler
and are not accounted (memory still is). Of the engine classes the current
uAPI can select, gfx/ce/dec are reachable. enc/jpg/ofa are defined for
when per-engine channels become selectable.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/nouveau_abi16.c | 16 +++++
 drivers/gpu/drm/nouveau/nouveau_chan.h  |  2 +
 drivers/gpu/drm/nouveau/nouveau_drm.c   | 19 ++++++
 drivers/gpu/drm/nouveau/nouveau_drv.h   | 27 ++++++++
 drivers/gpu/drm/nouveau/nouveau_exec.c  |  3 +
 drivers/gpu/drm/nouveau/nouveau_gem.c   | 16 +++++
 drivers/gpu/drm/nouveau/nouveau_sched.c | 87 +++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_sched.h | 11 ++++
 8 files changed, 181 insertions(+)

diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c
index 291203121f0c..63966444d45e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_abi16.c
+++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c
@@ -439,6 +439,22 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS)
 	if (ret)
 		goto done;
 
+	/* Record the channel's engine class for DRM fdinfo accounting. */
+	switch (engine) {
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_CE:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_CE;
+		break;
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSPDEC:
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSVLD:
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_MSPPP:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_DEC;
+		break;
+	case NV_DEVICE_HOST_RUNLIST_ENGINES_GR:
+	default:
+		chan->chan->fdinfo_engine = NOUVEAU_FDINFO_ENGINE_GFX;
+		break;
+	}
+
 	/* If we're not using the VM_BIND uAPI, we don't need a scheduler.
 	 *
 	 * The client lock is already acquired by nouveau_abi16_get().
diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.h b/drivers/gpu/drm/nouveau/nouveau_chan.h
index bb34b0a6082d..22c484dd0bbe 100644
--- a/drivers/gpu/drm/nouveau/nouveau_chan.h
+++ b/drivers/gpu/drm/nouveau/nouveau_chan.h
@@ -20,6 +20,8 @@ struct nouveau_channel {
 	u64 inst;
 	u32 token;
 
+	u8 fdinfo_engine;
+
 	struct nvif_object vram;
 	struct nvif_object gart;
 	struct nvif_object nvsw;
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 49f73f295664..a0264496a7d6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -35,8 +35,10 @@
 #include <drm/clients/drm_client_setup.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_fbdev_ttm.h>
+#include <drm/drm_file.h>
 #include <drm/drm_gem_ttm_helper.h>
 #include <drm/drm_ioctl.h>
+#include <drm/drm_print.h>
 #include <drm/drm_vblank.h>
 
 #include <core/gpuobj.h>
@@ -262,6 +264,9 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
 	INIT_LIST_HEAD(&cli->worker);
 	mutex_init(&cli->lock);
 
+	spin_lock_init(&cli->fdinfo.lock);
+	INIT_LIST_HEAD(&cli->fdinfo.jobs);
+
 	mutex_lock(&drm->client_mutex);
 	ret = nvif_client_ctor(&drm->_client, cli->name, &cli->base);
 	mutex_unlock(&drm->client_mutex);
@@ -1347,6 +1352,18 @@ nouveau_drm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	return ret;
 }
 
+static void
+nouveau_show_fdinfo(struct drm_printer *p, struct drm_file *file)
+{
+	struct nouveau_cli *cli = nouveau_cli(file);
+
+	/* Per-engine busy time of this client's EXEC jobs, then the standard
+	 * per-region memory stats
+	 */
+	nouveau_sched_show_fdinfo(cli, p);
+	drm_show_memory_stats(p, file);
+}
+
 static const struct file_operations
 nouveau_driver_fops = {
 	.owner = THIS_MODULE,
@@ -1360,6 +1377,7 @@ nouveau_driver_fops = {
 	.compat_ioctl = nouveau_compat_ioctl,
 #endif
 	.llseek = noop_llseek,
+	.show_fdinfo = drm_show_fdinfo,
 	.fop_flags = FOP_UNSIGNED_OFFSET,
 };
 
@@ -1372,6 +1390,7 @@ driver_stub = {
 			   DRIVER_RENDER,
 	.open = nouveau_drm_open,
 	.postclose = nouveau_drm_postclose,
+	.show_fdinfo = PTR_IF(IS_ENABLED(CONFIG_PROC_FS), nouveau_show_fdinfo),
 
 #if defined(CONFIG_DEBUG_FS)
 	.debugfs_init = nouveau_drm_debugfs_init,
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index a8ca86556e2f..ced15ee1aa65 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -65,6 +65,19 @@
 struct nouveau_channel;
 struct platform_device;
 
+/* Engine classes tracked for DRM fdinfo per-client utilisation. The value
+ * NOUVEAU_FDINFO_ENGINE_COUNT doubles as "not accounted"
+ */
+enum nouveau_fdinfo_engine {
+	NOUVEAU_FDINFO_ENGINE_GFX,	/* graphics/compute (GR) */
+	NOUVEAU_FDINFO_ENGINE_CE,	/* copy */
+	NOUVEAU_FDINFO_ENGINE_DEC,	/* video decode */
+	NOUVEAU_FDINFO_ENGINE_ENC,	/* video encode */
+	NOUVEAU_FDINFO_ENGINE_JPG,	/* jpeg */
+	NOUVEAU_FDINFO_ENGINE_OFA,	/* optical flow */
+	NOUVEAU_FDINFO_ENGINE_COUNT,
+};
+
 #include "nouveau_fence.h"
 #include "nouveau_bios.h"
 #include "nouveau_sched.h"
@@ -114,6 +127,20 @@ struct nouveau_cli {
 	struct work_struct work;
 	struct list_head worker;
 	struct mutex lock;
+
+	/* Per-client engine busy-time accounting for DRM fdinfo.
+	 *
+	 * @engine_ns holds the accumulated busy time of retired jobs per
+	 * engine; @jobs is the list of in-flight jobs, walked at fdinfo read
+	 * time to additionally account currently-running work. Both are
+	 * protected by @lock (a leaf spinlock taken from the job free path and
+	 * from the fdinfo show callback)
+	 */
+	struct {
+		spinlock_t lock;
+		struct list_head jobs;
+		u64 engine_ns[NOUVEAU_FDINFO_ENGINE_COUNT];
+	} fdinfo;
 };
 
 struct nouveau_cli_work {
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index a08ab1cfea9b..1215f0669d0d 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -255,6 +255,9 @@ nouveau_exec_job_init(struct nouveau_exec_job **pjob,
 	if (ret)
 		goto err_free_pushs;
 
+	/* Account this job's busy time against the channel's engine in fdinfo */
+	job->base.engine = job->chan->fdinfo_engine;
+
 	return 0;
 
 err_free_pushs:
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index 20dba02d6175..76fbde516722 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -214,10 +214,26 @@ nouveau_gem_object_close(struct drm_gem_object *gem, struct drm_file *file_priv)
 	ttm_bo_unreserve(&nvbo->bo);
 }
 
+static enum drm_gem_object_status
+nouveau_gem_object_status(struct drm_gem_object *gem)
+{
+	struct nouveau_bo *nvbo = nouveau_gem_object(gem);
+	enum drm_gem_object_status status = 0;
+
+	/* nouveau has no userspace madvise/purgeable concept, so only
+	 * residency is reported
+	 */
+	if (nvbo->bo.resource)
+		status |= DRM_GEM_OBJECT_RESIDENT;
+
+	return status;
+}
+
 const struct drm_gem_object_funcs nouveau_gem_object_funcs = {
 	.free = nouveau_gem_object_del,
 	.open = nouveau_gem_object_open,
 	.close = nouveau_gem_object_close,
+	.status = nouveau_gem_object_status,
 	.export = nouveau_gem_prime_export,
 	.pin = nouveau_gem_prime_pin,
 	.unpin = nouveau_gem_prime_unpin,
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c
index 2cbae003d6de..219cc8f621ce 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: MIT
 
+#include <linux/ktime.h>
 #include <linux/slab.h>
+#include <drm/drm_print.h>
 #include <drm/gpu_scheduler.h>
 #include <drm/drm_syncobj.h>
 
@@ -22,6 +24,64 @@ enum nouveau_sched_priority {
 	NOUVEAU_SCHED_PRIORITY_COUNT,
 };
 
+/* Busy time of a single job for fdinfo accounting: (finished - scheduled)
+ * once the job has retired, or (now - scheduled) while it is still running
+ *
+ * The drm_sched core back-fills the scheduled timestamp of an in-order
+ * retiring job with the previous job's finished timestamp, so summing these
+ * intervals over a channel's jobs approximates its busy wall-clock without
+ * double counting work that overlapped in the pipe
+ */
+static u64
+nouveau_job_busy_ns(struct nouveau_job *job)
+{
+	struct drm_sched_fence *s_fence = job->base.s_fence;
+
+	if (!s_fence ||
+	    !test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &s_fence->scheduled.flags))
+		return 0;
+
+	if (!test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &s_fence->finished.flags))
+		return ktime_to_ns(ktime_sub(ktime_get(),
+					     s_fence->scheduled.timestamp));
+
+	return ktime_to_ns(ktime_sub(s_fence->finished.timestamp,
+				     s_fence->scheduled.timestamp));
+}
+
+static const char * const
+nouveau_fdinfo_engine_name[NOUVEAU_FDINFO_ENGINE_COUNT] = {
+	[NOUVEAU_FDINFO_ENGINE_GFX] = "gfx",
+	[NOUVEAU_FDINFO_ENGINE_CE]  = "ce",
+	[NOUVEAU_FDINFO_ENGINE_DEC] = "dec",
+	[NOUVEAU_FDINFO_ENGINE_ENC] = "enc",
+	[NOUVEAU_FDINFO_ENGINE_JPG] = "jpg",
+	[NOUVEAU_FDINFO_ENGINE_OFA] = "ofa",
+};
+
+void
+nouveau_sched_show_fdinfo(struct nouveau_cli *cli, struct drm_printer *p)
+{
+	u64 engine_ns[NOUVEAU_FDINFO_ENGINE_COUNT];
+	struct nouveau_job *job;
+	unsigned int i;
+
+	spin_lock(&cli->fdinfo.lock);
+	for (i = 0; i < NOUVEAU_FDINFO_ENGINE_COUNT; i++)
+		engine_ns[i] = cli->fdinfo.engine_ns[i];
+
+	/* Add the busy time of currently in-flight jobs so a running client is
+	 * accounted without waiting for its jobs to retire
+	 */
+	list_for_each_entry(job, &cli->fdinfo.jobs, fdinfo_entry)
+		engine_ns[job->engine] += nouveau_job_busy_ns(job);
+	spin_unlock(&cli->fdinfo.lock);
+
+	for (i = 0; i < NOUVEAU_FDINFO_ENGINE_COUNT; i++)
+		drm_printf(p, "drm-engine-%s:\t%llu ns\n",
+			   nouveau_fdinfo_engine_name[i], engine_ns[i]);
+}
+
 int
 nouveau_job_init(struct nouveau_job *job,
 		 struct nouveau_job_args *args)
@@ -30,6 +90,8 @@ nouveau_job_init(struct nouveau_job *job,
 	int ret;
 
 	INIT_LIST_HEAD(&job->entry);
+	INIT_LIST_HEAD(&job->fdinfo_entry);
+	job->engine = NOUVEAU_FDINFO_ENGINE_COUNT;
 
 	job->file_priv = args->file_priv;
 	job->cli = nouveau_cli(args->file_priv);
@@ -308,6 +370,16 @@ nouveau_job_submit(struct nouveau_job *job)
 	list_add(&job->entry, &sched->job.list.head);
 	spin_unlock(&sched->job.list.lock);
 
+	/* Track in-flight jobs for fdinfo per-engine accounting. The job is
+	 * removed (and its busy time folded in) from nouveau_sched_free_job(),
+	 * which the scheduler guarantees to call once for every pushed job.
+	 */
+	if (job->engine < NOUVEAU_FDINFO_ENGINE_COUNT) {
+		spin_lock(&job->cli->fdinfo.lock);
+		list_add_tail(&job->fdinfo_entry, &job->cli->fdinfo.jobs);
+		spin_unlock(&job->cli->fdinfo.lock);
+	}
+
 	drm_sched_job_arm(&job->base);
 	job->done_fence = dma_fence_get(&job->base.s_fence->finished);
 	if (job->sync)
@@ -388,6 +460,21 @@ nouveau_sched_free_job(struct drm_sched_job *sched_job)
 {
 	struct nouveau_job *job = to_nouveau_job(sched_job);
 
+	/* Fold the retired job's busy time into the per-client per-engine
+	 * counter and stop tracking it as in-flight. Done before
+	 * nouveau_job_fini() since that releases the scheduler fences this
+	 * reads. The job's finished fence has signalled by now, so
+	 * nouveau_job_busy_ns() returns (finished - scheduled)
+	 */
+	if (job->engine < NOUVEAU_FDINFO_ENGINE_COUNT) {
+		struct nouveau_cli *cli = job->cli;
+
+		spin_lock(&cli->fdinfo.lock);
+		cli->fdinfo.engine_ns[job->engine] += nouveau_job_busy_ns(job);
+		list_del(&job->fdinfo_entry);
+		spin_unlock(&cli->fdinfo.lock);
+	}
+
 	nouveau_job_fini(job);
 }
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 20cd1da8db73..fa80f9b996b1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -56,6 +56,14 @@ struct nouveau_job {
 	struct drm_file *file_priv;
 	struct nouveau_cli *cli;
 
+	/* fdinfo per-engine busy-time accounting: @engine is the engine class
+	 * this job runs on (NOUVEAU_FDINFO_ENGINE_COUNT == not accounted)
+	 * @fdinfo_entry links the job into cli->fdinfo.jobs while it is in
+	 * flight.
+	 */
+	enum nouveau_fdinfo_engine engine;
+	struct list_head fdinfo_entry;
+
 	enum dma_resv_usage resv_usage;
 	struct dma_fence *done_fence;
 
@@ -96,6 +104,9 @@ int nouveau_job_submit(struct nouveau_job *job);
 void nouveau_job_done(struct nouveau_job *job);
 void nouveau_job_free(struct nouveau_job *job);
 
+struct drm_printer;
+void nouveau_sched_show_fdinfo(struct nouveau_cli *cli, struct drm_printer *p);
+
 struct nouveau_sched {
 	struct drm_gpu_scheduler base;
 	struct drm_sched_entity entity;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/6] drm/nouveau/gsp: vendor the RUSD header
  2026-07-14 21:14   ` Mohamed Ahmed
  (?)
@ 2026-07-14 21:23   ` sashiko-bot
  -1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 21:23 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] 32-bit platform ABI mismatch in shared telemetry buffer layout due to missing struct padding, leading to memory corruption.
- [Medium] RUSD telemetry structures use native integer types, masking endianness mismatches on big-endian architectures.
--

--- Patch [1]: [PATCH 1/6] drm/nouveau/gsp: vendor the RUSD header ---
commit c1a3b8ad153588da7ba08ef117defa77f7ba5f2d
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

drm/nouveau/gsp: vendor the RUSD header

RM User Shared Data (RUSD) is GSP-RM's telemetry channel: the kernel
hands the firmware a buffer and the firmware fills it with telemetry
data (temperatures, power draw, power limits, clocks, utilizations,
etc) on its own timer. This commit vendors the ABI needed to use it
from the 570.144 tag of NVIDIA's open-gpu-kernel-modules.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
> new file mode 100644
> index 0000000000000..9f1099a064301
> --- /dev/null
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/rusd.h
[ ... ]
> +typedef enum RUSD_TEMPERATURE_TYPE {
> +   RUSD_TEMPERATURE_TYPE_GPU,
> +   RUSD_TEMPERATURE_TYPE_HBM,
> +   RUSD_TEMPERATURE_TYPE_MAX
> +} RUSD_TEMPERATURE_TYPE;
> +
> +typedef struct RUSD_TEMPERATURE {
> +   volatile NvU64 lastModifiedTimestamp;
> +   NvTemp temperature;
> +} RUSD_TEMPERATURE;

[Severity: High]
Does this struct lack explicit padding, causing an ABI mismatch on 32-bit
platforms?

On 32-bit platforms (like x86-32), NvU64 has a 4-byte natural alignment,
making sizeof(RUSD_TEMPERATURE) exactly 12 bytes. On 64-bit systems and the
64-bit RISC-V GSP firmware, it requires padding and is 16 bytes.

[ ... ]
> +typedef struct NV00DE_SHARED_DATA {
> +   NV_DECLARE_ALIGNED(RUSD_BAR1_MEMORY_INFO bar1MemoryInfo, 8);

[Severity: Medium]
Will these fields be read incorrectly on big-endian architectures?

The shared data structures define fields with native integer types (NvU32,
NvU64) instead of explicit little-endian types (__le32, __le64). On a
big-endian host, the natively-read values will be byte-swapped versions of
the little-endian data written by the RISC-V GSP firmware.

[ ... ]
> +   // POLL_POWER
> +   // Module Limit is not supported on Ampere/Hopper
> +   NV_DECLARE_ALIGNED(RUSD_POWER_LIMITS powerLimitGpu, 8);
> +
> +   // POLL_THERMAL
> +   NV_DECLARE_ALIGNED(RUSD_TEMPERATURE temperatures[RUSD_TEMPERATURE_TYPE_MAX], 8);

[Severity: High]
Will the missing padding in RUSD_TEMPERATURE cause memory corruption here?

Because temperatures is declared as an array of 3 elements, the array occupies
36 bytes on 32-bit versus 48 bytes on 64-bit. This difference in array stride
shifts all subsequent fields by 8 bytes.

The kernel will allocate a smaller buffer than the firmware expects, causing
the 64-bit firmware to write past the end of the allocation when writing
telemetry data.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714211429.238355-1-mohamedahmedegypt2001@gmail.com?part=1

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 2/6] drm/nouveau/gsp: add RUSD telemetry support
  2026-07-14 21:14   ` Mohamed Ahmed
  (?)
@ 2026-07-14 21:27   ` sashiko-bot
  -1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 21:27 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing delay or CPU relax in bounded retry loop causes spurious -EAGAIN on GSP-RM telemetry reads.
- [Medium] Jiffies wrap-around edge case causes telemetry group to be prematurely dropped.
--

--- Patch [2]: [PATCH 2/6] drm/nouveau/gsp: add RUSD telemetry support ---
commit e2f85d24749e78bb60d3f7f4d694be38a59b92af
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

drm/nouveau/gsp: add RUSD telemetry support

This commit adds RUSD telemetry support for GSP-RM by allocating a shared-data
buffer and enabling demand-driven polling of sensor data. It tracks group
access and disables polling for groups that have been idle for a timeout period.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
> new file mode 100644
> index 0000000000000..d20dda07596a0
> --- /dev/null
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/rusd.c
> @@ -0,0 +1,531 @@
[ ... ]
> +static void
> +r570_rusd_poke(struct nvkm_gsp *gsp, enum nvkm_gsp_rusd_group group)
> +{
> +	mutex_lock(&gsp->rusd.mutex);
> +	/* @valid (not gsp->running) is the teardown fence: it is cleared under
> +	 * this mutex at the very start of fini, while gsp->running is still
> +	 * true, so checking it here stops a read from enabling a group and
> +	 * issuing a SET_DATA_POLL to a firmware that is being unloaded.
> +	 */
> +	if (gsp->rusd.valid) {
> +		gsp->rusd.group_access[group] = jiffies;
> +		r570_rusd_set_mask(gsp, gsp->rusd.mask | r570_rusd_group_mask[group]);

[Severity: Medium]
If jiffies happens to be exactly 0 during this assignment, could it cause issues
in the idle worker below?

> +		if (gsp->rusd.mask)
> +			schedule_delayed_work(&gsp->rusd.work,
> +					      msecs_to_jiffies(R570_RUSD_IDLE_MS));
> +	}
> +	mutex_unlock(&gsp->rusd.mutex);
> +}
> +
> +/* Drop the groups that haven't been read within the idle window. */
> +static void
> +r570_rusd_work_drop_idle(struct work_struct *work)
> +{
> +	struct nvkm_gsp *gsp = container_of(to_delayed_work(work),
> +					    struct nvkm_gsp, rusd.work);
> +	unsigned long idle = msecs_to_jiffies(R570_RUSD_IDLE_MS);
> +	u64 mask = 0;
> +	int g;
> +
> +	mutex_lock(&gsp->rusd.mutex);
> +	if (gsp->rusd.valid) {
> +		for (g = 0; g < NVKM_GSP_RUSD_GROUP_COUNT; g++) {
> +			if (gsp->rusd.group_access[g] &&

[Severity: Medium]
When jiffies wraps to 0, this check evaluates to false. 

Will this cause the telemetry group to be prematurely dropped from the poll mask
until the next read cycle?

> +			    time_before(jiffies, gsp->rusd.group_access[g] + idle))
> +				mask |= r570_rusd_group_mask[g];
> +		}
[ ... ]
> +/*
> + * Copy one section out of the shared buffer, untorn. Every section starts
> + * with a u64 timestamp GSP-RM sets to WRITE_IN_PROGRESS while updating it.
> + */
> +static int
> +r570_rusd_read_section(struct nvkm_gsp *gsp, size_t offset, size_t size, void *data)
> +{
> +	const void *sect = gsp->rusd.mem.data + offset;
> +	u64 seq0, seq1;
> +	int retries;
> +
> +	for (retries = 10; retries; retries--) {
> +		seq0 = READ_ONCE(*(const u64 *)sect);
> +		if (seq0 == RUSD_TIMESTAMP_INVALID)
> +			return -ENODATA;
> +		if (seq0 == RUSD_TIMESTAMP_WRITE_IN_PROGRESS)
> +			continue;

[Severity: Medium]
Without a delay or cpu_relax() here, the 10 iterations will exhaust extremely
fast (in tens of nanoseconds). 

Could this cause spurious -EAGAIN errors if the CPU spins through all retries
before the GSP firmware has time to finish its memory write?

> +		/* Sequence counters appear only in sections written by the kernel and
> +		 * never in GSP-polled ones.
> +		 */
> +		if (seq0 >= RUSD_SEQ_START)
> +			return -ENODATA;
> +

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714211429.238355-1-mohamedahmedegypt2001@gmail.com?part=2

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 3/6] drm/nouveau: add GSP hwmon support
  2026-07-14 21:14   ` Mohamed Ahmed
  (?)
@ 2026-07-14 21:28   ` sashiko-bot
  -1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 21:28 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Legacy (pre-GSP) hardware exposes duplicate dummy temperature and power sensor attributes.
- [High] Unconditional 2-second timeout delay during driver initialization if the GPU supports only thermal or only power telemetry.
- [Medium] The hwmon sysfs read callbacks fail intermittently with -EAGAIN when racing with GSP telemetry updates.
- [Medium] The GSP 'temp1' label incorrectly reports 'GPU core' instead of 'gpu', leaving the new label definition as dead code.
--

--- Patch [3]: [PATCH 3/6] drm/nouveau: add GSP hwmon support ---
commit ceccedcbd9d07d5becab288795d358c7f8e9d71b
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

drm/nouveau: add GSP hwmon support

This commit wires up the RUSD telemetry to provide hwmon device support
for Turing through Blackwell boards. It adds temperature and power readout
channels, aiming to leave legacy therm/volt/iccsense paths unchanged.

> diff --git a/drivers/gpu/drm/nouveau/nouveau_hwmon.c b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
> --- a/drivers/gpu/drm/nouveau/nouveau_hwmon.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_hwmon.c

[ ... ]

> @@ -243,52 +250,144 @@ nouveau_chip_is_visible(const void *data, u32 attr, int channel)

[ ... ]

> +static umode_t
> +nouveau_power_is_visible(const void *data, u32 attr, int channel)
> +{

[ ... ]

> +	if (nvkm_gsp_rusd(gsp))
> +		return nouveau_rusd_power_is_visible(gsp, attr, channel);
> +	else {
> +		if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
> +			return 0;
> +
> +		switch (attr) {
> +		case hwmon_power_input:
> +			return 0444;

[Severity: High]
Does this inadvertently expose dummy power sensors on pre-GSP hardware?

The hwmon core iterates over the newly added channels in nouveau_info, but
this legacy fallback branch in nouveau_power_is_visible() does not check the
channel index. Because it unconditionally returns 0444 for hwmon_power_input,
it advertises support for channels 1 and 2 on legacy boards, duplicating
channel 0.

A similar issue appears in nouveau_temp_is_visible() which returns 0444 for
hwmon_temp_input regardless of the channel parameter.

> @@ -368,47 +471,67 @@ nouveau_temp_read(struct device *dev, u32 attr, int channel, long *val)

[ ... ]

> +	if (nvkm_gsp_rusd(gsp)) {

[ ... ]

> +		ret = nvkm_gsp_rusd_read(gsp, channel ? NVKM_GSP_RUSD_TEMP_HBM :
> +							NVKM_GSP_RUSD_TEMP_GPU, &value);
> +		if (ret)
> +			return ret;

[Severity: Medium]
Could this cause sporadic sensor read failures in userspace?

When userspace reads a sysfs attribute like temp1_input during a GSP write
window, nvkm_gsp_rusd_read() returns -EAGAIN. Since there is no retry loop
here in nouveau_temp_read() (unlike the one implemented in nouveau_rusd_ok()
for initialization), does this directly fail the userspace read and cause
monitoring tools to report errors?

This pattern also appears in nouveau_rusd_power_read().

> @@ -597,6 +770,8 @@ nouveau_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
>  }
>  
>  static const char input_label[] = "GPU core";
> +static const char * const nouveau_temp_labels[] = { "gpu", "vram" };
> +static const char * const nouveau_power_labels[] = { "gpu", "board", "vram" };
>  
>  static int
>  nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> @@ -607,6 +782,18 @@ nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>  		return 0;
>  	}
>  
> +	if (type == hwmon_temp && attr == hwmon_temp_label &&
> +	    channel < ARRAY_SIZE(nouveau_temp_labels)) {
> +		*buf = nouveau_temp_labels[channel];
> +		return 0;
> +	}

[Severity: Medium]
Is the new label for temp1 unreachable for GSP boards?

Earlier in nouveau_read_string(), there is a check for channel == 0:

	if (type == hwmon_temp && attr == hwmon_temp_label && channel == 0) {
		*buf = input_label;
		return 0;
	}

Because that check intercepts channel 0 and returns "GPU core", does it
prevent the execution of this new block and leave nouveau_temp_labels[0]
("gpu") as dead code?

> @@ -667,13 +854,39 @@ nouveau_hwmon_init(struct drm_device *dev)

[ ... ]

> +		for (;;) {
> +			thermal = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_TEMP_GPU);
> +			power = nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU) ||
> +				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_GPU_AVG) ||
> +				nouveau_rusd_ok(gsp, NVKM_GSP_RUSD_POWER_CAP);
> +			if ((thermal && power) || !time_before(jiffies, timeout))
> +				break;
> +			msleep(20);
> +		}

[Severity: High]
Will this loop introduce a noticeable boot delay on configurations that only
support one of these telemetry features?

The loop condition requires both thermal and power telemetry to become valid
to break early. If a GPU SKU natively supports only thermal or only power
sensors, the missing feature will constantly fail the check. Will this
unnecessarily block driver probe for the full timeout duration (typically
2 seconds) before proceeding?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714211429.238355-1-mohamedahmedegypt2001@gmail.com?part=3

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-07-14 21:28 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 21:14 [PATCH 0/6] drm/nouveau: GSP telemetry via RUSD, and fdinfo telemetry exposure Mohamed Ahmed
2026-07-14 21:14 ` Mohamed Ahmed
2026-07-14 21:14 ` [PATCH 1/6] drm/nouveau/gsp: vendor the RUSD header Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed
2026-07-14 21:23   ` sashiko-bot
2026-07-14 21:14 ` [PATCH 2/6] drm/nouveau/gsp: add RUSD telemetry support Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed
2026-07-14 21:27   ` sashiko-bot
2026-07-14 21:14 ` [PATCH 3/6] drm/nouveau: add GSP hwmon support Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed
2026-07-14 21:28   ` sashiko-bot
2026-07-14 21:14 ` [PATCH 4/6] drm/nouveau: expose RUSD telemetry via sysfs Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed
2026-07-14 21:14 ` [PATCH 5/6] drm/nouveau: expose global VRAM size and usage " Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed
2026-07-14 21:14 ` [PATCH 6/6] drm/nouveau: expose per-client GPU usage via fdinfo Mohamed Ahmed
2026-07-14 21:14   ` Mohamed Ahmed

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.