Merge pull request #1660 from Sky1-Linux/thermal-zone-fallback

SystemStat: add thermal_zone fallback for CPU and GPU temperature
This commit is contained in:
Lemmy
2026-02-06 09:46:26 -05:00
committed by GitHub
+185 -3
View File
@@ -242,6 +242,15 @@ Singleton {
property int intelTempFilesChecked: 0
property int intelTempMaxFiles: 20 // Will test up to temp20_input
// Thermal zone fallback (for ARM SoCs with SCMI sensors, etc.)
// Matches thermal zone types containing "cpu" and picks the hottest big-core zone.
// Also provides GPU temp via zones like "gpu-avg-thermal" or "gpu0-thermal".
readonly property var thermalZoneCpuPatterns: ["cpu-b", "cpu-m", "cpu"]
readonly property var thermalZoneGpuPatterns: ["gpu-avg", "gpu0", "gpu"]
property string cpuThermalZonePath: ""
property var cpuThermalZonePaths: [] // All matching CPU zones for averaging
property string gpuThermalZonePath: ""
// GPU temperature detection
// On dual-GPU systems, we prioritize discrete GPUs over integrated GPUs
// Priority: NVIDIA (opt-in) > AMD dGPU > Intel Arc > AMD iGPU
@@ -549,8 +558,8 @@ Singleton {
function checkNext() {
if (currentIndex >= 16) {
// Check up to hwmon10
Logger.w("No supported temperature sensor found");
// No hwmon sensor found, try thermal_zone fallback (ARM SoCs, SCMI, etc.)
thermalZoneScanner.startScan();
return;
}
@@ -614,6 +623,159 @@ Singleton {
}
}
// --------------------------------------------
// Thermal zone fallback for CPU and GPU temperature
// Used on ARM SoCs (e.g., SCMI sensors) where hwmon doesn't expose
// coretemp/k10temp/zenpower. Scans /sys/class/thermal/thermal_zoneN/type
// for CPU and GPU zone names, then reads temp from all matching zones.
//
// CPU: reads all cpu-*-thermal zones and reports the hottest core.
// GPU: prefers gpu-avg-thermal (firmware average), falls back to max of gpu[0-9]-thermal.
FileView {
id: thermalZoneScanner
property int currentIndex: 0
property var cpuZones: []
property var gpuZones: []
property string gpuAvgZonePath: ""
printErrors: false
function startScan() {
currentIndex = 0;
cpuZones = [];
gpuZones = [];
gpuAvgZonePath = "";
checkNext();
}
function checkNext() {
if (currentIndex >= 20) {
finishScan();
return;
}
thermalZoneScanner.path = `/sys/class/thermal/thermal_zone${currentIndex}/type`;
thermalZoneScanner.reload();
}
onLoaded: {
const name = text().trim();
const zonePath = `/sys/class/thermal/thermal_zone${currentIndex}`;
if (name.startsWith("cpu") && name.endsWith("thermal")) {
cpuZones.push({"type": name, "path": zonePath + "/temp"});
} else if (name === "gpu-avg-thermal") {
gpuAvgZonePath = zonePath + "/temp";
} else if (/^gpu[0-9]+-?thermal$/.test(name)) {
gpuZones.push({"type": name, "path": zonePath + "/temp"});
}
currentIndex++;
Qt.callLater(() => { checkNext(); });
}
onLoadFailed: function (error) {
currentIndex++;
Qt.callLater(() => { checkNext(); });
}
function finishScan() {
// CPU thermal zones
if (cpuZones.length > 0) {
root.cpuTempSensorName = "thermal_zone";
root.cpuThermalZonePaths = cpuZones.map(z => z.path);
const types = cpuZones.map(z => z.type).join(", ");
Logger.i("SystemStat", `Found ${cpuZones.length} CPU thermal zone(s): ${types}`);
} else {
Logger.w("No supported temperature sensor found");
}
// GPU thermal zones
if (gpuAvgZonePath !== "") {
root.gpuThermalZonePath = gpuAvgZonePath;
root.gpuAvailable = true;
root.gpuType = "thermal_zone";
Logger.i("SystemStat", `Found GPU thermal zone: gpu-avg-thermal`);
} else if (gpuZones.length > 0) {
root.gpuThermalZonePaths = gpuZones.map(z => z.path);
root.gpuThermalZonePath = gpuZones[0].path; // fallback single path
root.gpuAvailable = true;
root.gpuType = "thermal_zone";
const types = gpuZones.map(z => z.type).join(", ");
Logger.i("SystemStat", `Found ${gpuZones.length} GPU thermal zone(s): ${types} (using max)`);
}
}
}
// Thermal zone reader for CPU: reads all zones, reports max (hottest core)
FileView {
id: cpuThermalZoneReader
property int currentZoneIndex: 0
property var collectedTemps: []
printErrors: false
onLoaded: {
const temp = parseInt(text().trim()) / 1000.0;
if (!isNaN(temp) && temp > 0)
collectedTemps.push(temp);
currentZoneIndex++;
Qt.callLater(() => { readNextCpuThermalZone(); });
}
onLoadFailed: function (error) {
currentZoneIndex++;
Qt.callLater(() => { readNextCpuThermalZone(); });
}
}
function readNextCpuThermalZone() {
if (cpuThermalZoneReader.currentZoneIndex >= root.cpuThermalZonePaths.length) {
if (cpuThermalZoneReader.collectedTemps.length > 0) {
root.cpuTemp = Math.round(Math.max(...cpuThermalZoneReader.collectedTemps));
} else {
root.cpuTemp = 0;
}
root.pushCpuTempHistory();
return;
}
cpuThermalZoneReader.path = root.cpuThermalZonePaths[cpuThermalZoneReader.currentZoneIndex];
cpuThermalZoneReader.reload();
}
// Thermal zone reader for GPU: reads single zone (gpu-avg-thermal) or max of gpu[N] zones
FileView {
id: gpuThermalZoneReader
property int currentZoneIndex: 0
property var collectedTemps: []
printErrors: false
onLoaded: {
const temp = parseInt(text().trim()) / 1000.0;
if (!isNaN(temp) && temp > 0)
collectedTemps.push(temp);
// If we have multiple GPU zones (no gpu-avg), iterate and take max
if (root.gpuThermalZonePaths && root.gpuThermalZonePaths.length > 0) {
currentZoneIndex++;
if (currentZoneIndex < root.gpuThermalZonePaths.length) {
Qt.callLater(() => { readNextGpuThermalZone(); });
return;
}
// All zones read, take max
root.gpuTemp = Math.round(Math.max(...collectedTemps));
} else {
// Single gpu-avg-thermal zone
root.gpuTemp = Math.round(temp);
}
root.pushGpuHistory();
}
}
function readNextGpuThermalZone() {
gpuThermalZoneReader.path = root.gpuThermalZonePaths[gpuThermalZoneReader.currentZoneIndex];
gpuThermalZoneReader.reload();
}
// Property to store multiple GPU thermal zone paths (when no gpu-avg is available)
property var gpuThermalZonePaths: []
// --------------------------------------------
// --------------------------------------------
// GPU Temperature
@@ -1056,6 +1218,11 @@ Singleton {
root.intelTempValues = [];
root.intelTempFilesChecked = 0;
checkNextIntelTemp();
} // For thermal_zone fallback (ARM SoCs, SCMI, etc.), read all CPU zones and take max
else if (root.cpuTempSensorName === "thermal_zone") {
cpuThermalZoneReader.currentZoneIndex = 0;
cpuThermalZoneReader.collectedTemps = [];
readNextCpuThermalZone();
}
}
@@ -1112,7 +1279,11 @@ Singleton {
// Priority (when dGPU monitoring disabled): AMD iGPU only (discrete GPUs skipped to preserve D3cold)
function selectBestGpu() {
if (root.foundGpuSensors.length === 0) {
Logger.d("SystemStat", "No GPU temperature sensor found");
// No hwmon GPU sensors found, try thermal_zone fallback
if (root.gpuThermalZonePath === "" && root.gpuThermalZonePaths.length === 0) {
// Thermal zone scanner hasn't found GPU zones yet; start a scan
thermalZoneScanner.startScan();
}
return;
}
@@ -1174,6 +1345,17 @@ Singleton {
} else if (root.gpuType === "amd" || root.gpuType === "intel") {
gpuTempReader.path = `${root.gpuTempHwmonPath}/temp1_input`;
gpuTempReader.reload();
} else if (root.gpuType === "thermal_zone") {
if (root.gpuThermalZonePaths && root.gpuThermalZonePaths.length > 0) {
// Multiple GPU zones (no gpu-avg), read all and take max
gpuThermalZoneReader.currentZoneIndex = 0;
gpuThermalZoneReader.collectedTemps = [];
readNextGpuThermalZone();
} else {
// Single gpu-avg-thermal zone
gpuThermalZoneReader.path = root.gpuThermalZonePath;
gpuThermalZoneReader.reload();
}
}
}
}