[PATCH] kernel: add system_energy_efficiency syscall (nr 337)
From: 林濬哲
Date: Sun Sep 20 2026 - 06:08:56 EST
Add a new syscall that reports GPU presence and system-wide
energy information:
- mode 0: returns the number of display devices found on the PCI bus
- mode 1: returns an energy efficiency metric; prefers the real GPU
power reported through hwmon, falls back to runtime PM status
weights, plus the online CPU count
- mode 2: returns the real-time GPU power in milliwatts read from
the hwmon power1_input channel exposed by GPU drivers
(amdgpu/nouveau/radeon/nvidia); returns 0 if not reported
Signed-off-by: 林濬哲 <m18667909625@xxxxxxx>
Assisted-by: AI coding assistant (disclosed per kernel AI guidelines)
---
arch/x86/entry/syscalls/syscall_64.tbl | 1 +
include/linux/syscalls.h | 2 +
kernel/sys.c | 138 +++++++++++++++++++++++++
3 files changed, 141 insertions(+)
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index d5b6045b0090..f99649dfaa38 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -346,6 +346,7 @@
334 common rseq sys_rseq
335 common uretprobe sys_uretprobe
336 common uprobe sys_uprobe
+337 common system_energy_efficiency sys_system_energy_efficiency
# don't use numbers 387 through 423, add new calls after the last
# 'common' entry
424 common pidfd_send_signal sys_pidfd_send_signal
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 8413b624ad47..dfa9d31c20cd 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1017,6 +1017,8 @@ asmlinkage long sys_uretprobe(void);
asmlinkage long sys_uprobe(void);
+asmlinkage long sys_system_energy_efficiency(int __user *mode, int __user *value);
+
/* pciconfig: alpha, arm, arm64, ia64, sparc */
asmlinkage long sys_pciconfig_read(unsigned long bus, unsigned long dfn,
unsigned long off, unsigned long len,
diff --git a/kernel/sys.c b/kernel/sys.c
index 35b538ba843c..bb1106085d7e 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -22,6 +22,7 @@
#include <linux/workqueue.h>
#include <linux/capability.h>
#include <linux/device.h>
+#include <linux/pci.h>
#include <linux/key.h>
#include <linux/times.h>
#include <linux/posix-timers.h>
@@ -181,6 +182,143 @@ int fs_overflowgid = DEFAULT_FS_OVERFLOWGID;
EXPORT_SYMBOL(fs_overflowuid);
EXPORT_SYMBOL(fs_overflowgid);
+
+/*
+ * Read the real power consumption reported by GPU drivers via hwmon,
+ * in milliwatts. The hwmon ABI exposes power1_input in microwatts;
+ * amdgpu/nouveau/radeon/nvidia provide this channel on supported
+ * hardware. Returns 0 if no driver reports a power reading.
+ */
+static long gpu_hwmon_power_mw(void)
+{
+ static const char * const gpu_drivers[] = {
+ "amdgpu", "nouveau", "radeon", "nvidia",
+ };
+ char buf[32];
+ char path[64];
+ loff_t pos;
+ long total_uw = 0;
+ long val;
+ int i, j, ret;
+
+ for (i = 0; i < 32; i++) {
+ bool is_gpu = false;
+ struct file *fp;
+
+ snprintf(path, sizeof(path), "/sys/class/hwmon/hwmon%d/name", i);
+ fp = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(fp))
+ continue;
+
+ pos = 0;
+ ret = kernel_read(fp, buf, sizeof(buf) - 1, &pos);
+ filp_close(fp, NULL);
+ if (ret <= 0)
+ continue;
+ buf[ret] = '\0';
+
+ for (j = 0; j < ARRAY_SIZE(gpu_drivers); j++) {
+ if (!strncmp(buf, gpu_drivers[j], strlen(gpu_drivers[j]))) {
+ is_gpu = true;
+ break;
+ }
+ }
+ if (!is_gpu)
+ continue;
+
+ snprintf(path, sizeof(path), "/sys/class/hwmon/hwmon%d/power1_input", i);
+ fp = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(fp))
+ continue;
+
+ pos = 0;
+ ret = kernel_read(fp, buf, sizeof(buf) - 1, &pos);
+ filp_close(fp, NULL);
+ if (ret <= 0)
+ continue;
+ buf[ret] = '\0';
+
+ if (kstrtol(buf, 10, &val))
+ continue;
+
+ total_uw += val;
+ }
+
+ return total_uw / 1000; /* microwatts -> milliwatts */
+}
+
+static int gpu_energy_estimate(int *gpus)
+{
+ static const int class_list[] = {
+ 0x030000, /* VGA compatible controller */
+ 0x030200, /* 3D controller (modern NVIDIA/AMD GPUs) */
+ 0x030100, /* XGA */
+ 0x030080, /* other display controller */
+ };
+ struct pci_dev *pdev;
+ int i, count = 0, energy = 0;
+
+ for (i = 0; i < ARRAY_SIZE(class_list); i++) {
+ pdev = NULL;
+ while ((pdev = pci_get_class(class_list[i], pdev)) != NULL) {
+ count++;
+ if (pdev->dev.power.runtime_status == RPM_ACTIVE)
+ energy += 100;
+ else
+ energy += 5;
+ }
+ }
+
+ if (gpus)
+ *gpus = count;
+
+ return energy;
+}
+
+SYSCALL_DEFINE2(system_energy_efficiency, int __user *, mode, int __user *, value)
+{
+ int mode_v, ret;
+ int gpu_num, gpu_energy;
+ long gpu_power_mw;
+
+ if (get_user(mode_v, mode))
+ return -EFAULT;
+
+ if (mode_v < 0 || mode_v > 2)
+ return -EINVAL;
+
+ gpu_energy = gpu_energy_estimate(&gpu_num);
+ gpu_power_mw = gpu_hwmon_power_mw();
+
+ switch (mode_v) {
+ case 0:
+ /* number of GPUs, 0 if the system has none */
+ ret = gpu_num;
+ break;
+ case 1:
+ /*
+ * Energy efficiency metric: prefer the real GPU power
+ * (mW) reported by the driver, fall back to runtime PM
+ * status weights if no power channel exists, then add
+ * the CPU part (online CPUs x 10)
+ */
+ ret = (int)(gpu_power_mw ? gpu_power_mw : gpu_energy) +
+ num_online_cpus() * 10;
+ break;
+ case 2:
+ /* real-time GPU power in mW, 0 if not reported */
+ ret = (int)gpu_power_mw;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (put_user(ret, value))
+ return -EFAULT;
+
+ return 0;
+}
+
static const struct ctl_table overflow_sysctl_table[] = {
{
.procname = "overflowuid",
--
2.54.0.windows.1