hal/temperature: correct sysfs-hwmon temperature fallback for Arc Pro… - #149
hal/temperature: correct sysfs-hwmon temperature fallback for Arc Pro…#149thevisad wants to merge 1 commit into
Conversation
|
Related to #146 |
cf3f931 to
4d8a702
Compare
savery42
left a comment
There was a problem hiding this comment.
Apologies for the wait - got busy prior to a code-freeze and couldn't get a multi-tile until today. Overall looks good, though one general note:
The oal/ dir exists for OS-specific (or, potentially, platform specific) behavior
Some of the methods such as getHwmonLabelPaths that does a traversal within sysfs can be placed into that area to follow the project architecture
No issues with the code on Windows as you handled everything clearly, just a note on the general code structure. I also try and avoid the OAL as it's.. unique - though we do have a ticket to clean it up and make it testable (if we can ever get to a refactor ticket)
| * - A matching sensor exists but its state read failed (device loss, | ||
| * permission, invalid state, ...) -> that underlying error is returned. |
There was a problem hiding this comment.
Due to a pre-existing issue in getTemp, in this case retval == ZE_RESULT_SUCCESS and param[out] temp == 0.0
This is because if the call to getState() (line 378) fails it just logs and continues without modifying result as well as the return statement not returning result if a "sensor is found" (property type matches the expected one)
Pedantic - apologies - I would've missed this if I hadn't been looking into another issue with garbage data after a DEVICE_LOST event from a GPU crash :)
There was a problem hiding this comment.
Double note: the DEVICE_LOST issue is larger as smDeviceInit doesn't propagate errors but that's a story for a later fix..
| static const std::string kPrefix = "temp"; | ||
| static const std::string kSuffix = "_label"; |
There was a problem hiding this comment.
// c++20 style / nit
| static const std::string kPrefix = "temp"; | |
| static const std::string kSuffix = "_label"; | |
| constexpr std::string_view kPrefix = "temp"; | |
| constexpr std::string_view kSuffix = "_label"; |
Avoids heap allocation, follows idiomatic c++20
| // Need prefix + at least one digit + suffix. | ||
| if (filename.size() <= kPrefix.size() + kSuffix.size()) { | ||
| return false; | ||
| } | ||
| if (filename.compare(0, kPrefix.size(), kPrefix) != 0) { | ||
| return false; | ||
| } | ||
| if (filename.compare(filename.size() - kSuffix.size(), kSuffix.size(), kSuffix) != 0) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
// c++20 style
idiomatic c++20 has filename.starts_with(prefix), filename.ends_with(suffix), filename
| const size_t digitsBegin = kPrefix.size(); | ||
| const size_t digitsEnd = filename.size() - kSuffix.size(); | ||
| if (digitsEnd <= digitsBegin) { | ||
| return false; // no digits between prefix and suffix | ||
| } | ||
| unsigned long value = 0; | ||
| for (size_t i = digitsBegin; i < digitsEnd; ++i) { | ||
| const char c = filename[i]; | ||
| if (c < '0' || c > '9') { | ||
| return false; | ||
| } | ||
| value = value * 10UL + (unsigned long)(c - '0'); | ||
| } | ||
| if (idx != nullptr) { | ||
| *idx = (unsigned int)value; | ||
| } |
There was a problem hiding this comment.
// c++20 / no overflow risk on the unsigned long -> unsigned conversion
Can parse using std::from_chars
| const size_t digitsBegin = kPrefix.size(); | |
| const size_t digitsEnd = filename.size() - kSuffix.size(); | |
| if (digitsEnd <= digitsBegin) { | |
| return false; // no digits between prefix and suffix | |
| } | |
| unsigned long value = 0; | |
| for (size_t i = digitsBegin; i < digitsEnd; ++i) { | |
| const char c = filename[i]; | |
| if (c < '0' || c > '9') { | |
| return false; | |
| } | |
| value = value * 10UL + (unsigned long)(c - '0'); | |
| } | |
| if (idx != nullptr) { | |
| *idx = (unsigned int)value; | |
| } | |
| unsigned int parsed = 0; | |
| const auto [ptr, ec] = std::from_chars(digits.data(), digits.data() + digits.size(), parsed); | |
| if (ec != std::errc{} || ptr != digits.data() + digits.size()) { | |
| return false; | |
| } | |
| if (idx != nullptr) { | |
| *idx = parsed; | |
| } |
| if (!parseTempLabelIndex(entryName, &idx)) { | ||
| continue; | ||
| } | ||
| const std::string labelPath = hwmonDir + "/" + entryName; |
There was a problem hiding this comment.
// nit
This is the same as entry.path(). Using that directly avoids allocation
| continue; | ||
| } | ||
| ++visited; | ||
| scanHwmonForLabels(hwmonBase + "/" + name, out); |
There was a problem hiding this comment.
// nit
| scanHwmonForLabels(hwmonBase + "/" + name, out); | |
| scanHwmonForLabels(entry.path().string(), out); |
| // lives in the internal xpum::hwmon utilities (hwmon_temperature_utils.h) so | ||
| // it is shared with the unit tests without expanding this exported class. | ||
| // Scope and non-goals are documented on resolveSysfsHwmon() in the .cpp. | ||
| std::string sysfsHwmonBase; // /sys/bus/pci/devices/<BDF>/hwmon |
There was a problem hiding this comment.
This doesn't seem to need to be a member and only passed as a function param after clearing -> setting
Reco removing
| : (type == ZES_TEMP_SENSORS_MEMORY) ? "vram" | ||
| : nullptr; | ||
| double sysfsTemp = 0.0; | ||
| if (sysfsLabel != nullptr && readSysfsLabel(sysfsLabel, &sysfsTemp) == ZE_RESULT_SUCCESS) { |
There was a problem hiding this comment.
if readSysfsLabel() != ZE_RESULT_SUCCESS the error gets dropped and a ZE_RESULT_SUCCESS is returned
Report GPU package and VRAM temperatures on the tested Arc Pro B70 when Level Zero Sysman exposes no matching temperature sensors. Move hwmon traversal into the OS abstraction layer, keep temperature parsing/validation outside platform-specific discovery, preserve Level Zero error propagation, and add Linux OAL/unit coverage plus Windows compile validation. Signed-off-by: thevisad <373694+thevisad@users.noreply.github.com>
4d8a702 to
df3741f
Compare
|
@savery42 Thanks for the review. Reworked along the lines you asked. OAL move. The hwmon sysfs traversal now lives in the OS abstraction layer: oal/os.h declares getHwmonLabelPaths(bdf, prefix), implemented in oal/lin/hwmon.cpp (a std::filesystem walk of the PCI device's hwmon* nodes with strict temp_label matching) and stubbed in oal/win/hwmon.cpp. The temperature interpretation (parse, milli-to-Celsius, physical-bounds validation, and the Level-Zero-vs-sysfs decision) stays portable in hal (hwmon_temperature_utils). OAL discovers the filesystem objects; HAL reads and validates them. Error contract. decideTempSource falls back to the hwmon node when Level Zero reports the sensor unsupported or denies access to an unprivileged process (the "only works with sudo" symptom in #146), and propagates device-loss / other runtime errors so a real driver failure is never masked. While in here I also fixed getTempPerTile, which returned ZE_RESULT_SUCCESS after a failed sysfs read instead of propagating the error, and dropped the sysfsHwmonBase member (the resolved label-to-path map is cached instead). Tests. New OAL unit suite oal/lin/test/hwmon_tests (7 cases / 26 assertions) covers discovery; temperature_tests (8 / 41) covers the decision matrix and bounds. meson test: 10/10 suites green. Hardware: on an Arc Pro B70 (xe, Level Zero 1.27.0) xpu-smi stats reports Core 51 / Mem 52 matching raw sysfs pkg / vram. Windows. Linux is fully tested and hardware-validated; the touched Windows compilation surface (oal/win/hwmon.cpp, hwmon_temperature_utils.cpp) is verified clean with MSVC 19.44 at /std:c++20 /WX, which is a compile check of the modified translation units, not a full Windows product build. Single DCO-signed commit; the PR description is updated to match. Relates to #146. |
Description
xpu-smireports GPU Core and Memory Temperature asN/Aon the Intel Arc Pro B70 (Battlemage,xedriver): Level Zero sysman enumerates no GPU/Memory temperature sensor on this device, even though the
readings are published on the PCI device's hwmon node (labels
pkg/vram). This PR adds a correct,validated sysfs-hwmon fallback so temperature is reported on such devices, while leaving behaviour
byte-identical where Level Zero already provides a sensor.
What it does
getCoreTemp/getMemoryTempfall back to sysfs only when Level Zero reports the sensor typeunsupported (no matching sensor was enumerated); any other Level Zero result is propagated, so
device-loss / permission / state errors are never masked. The routing decision is a pure, unit-tested
helper (
decideTempSource).getTempPerTileuses the fallback only when no matching-type sensor was enumerated, not merely when thetile map is empty (
perTileShouldFallback).resolveSysfsHwmonscans allhwmon*subdirs and caches the exacttempN_inputpath per label;strict
temp<N>_labelfilename matching; every stream open is verified.milli→Celsius → physical-validity bounds). The lowerbound is absolute zero, not a device-specific
0 Cfloor, so a legitimate subzero reading on anothermatching system is not wrongly rejected.
xpum::hwmonutility (hwmon_temperature_utils.{h,cpp}) shared bytemperature.cppand the tests, without expanding the exportedtemperatureclass / DLL surface.Testing
SUCCESS→L0 /UNSUPPORTED→sysfs /DEVICE_LOST,PERMISSIONS,UNKNOWN→propagate; strict filename parse; value bounds; multi-hwmondiscovery incl. labelled node not first; two roots don't cross-resolve). Full
meson test: 9/9 suites.xe, kernel7.0.0-28-generic, Level Zero1.27.0):xpu-smi statsreportsCore 51 / Mem 52, matching raw sysfs
pkg=51000/vram=52000; VR staysN/A.Scope / limitations
xe. The fallback is condition-based, notPCI-ID-gated: it activates when Level Zero exposes no matching sensor and the hwmon node provides the
expected label. Not generalized to multiple tiles/subdevices; VR temperature is not in sysfs and stays
N/A.std::filesystem).Windows was not build-tested. The doctest test targets require a
doctestpackage in CI.Fixes:
Relates-To:
Type of change
Affected components
hal— Hardware Abstraction Layerial— Interface / Application Layeroal— OS Abstraction Layerxpumd— XPU Manager Daemoncli/smici/ build systemChecklist
Signed-off-by(DCO)