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 <[email protected]>
---
 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

Reply via email to