diff --git a/doc/amd-pptables/rx7000-fancurve b/doc/amd-pptables/rx7000-fancurve new file mode 100644 index 0000000..30c8f64 --- /dev/null +++ b/doc/amd-pptables/rx7000-fancurve @@ -0,0 +1,9 @@ +OD_FAN_CURVE: +0: 0C 0% +1: 45C 15% +2: 50C 30% +3: 55C 70% +4: 65C 100% +OD_RANGE: +FAN_CURVE(hotspot temp): 25C 100C +FAN_CURVE(fan speed): 15% 100% diff --git a/src/plugins/AMD.cpp b/src/plugins/AMD.cpp index 90417f4..8a889c0 100644 --- a/src/plugins/AMD.cpp +++ b/src/plugins/AMD.cpp @@ -368,6 +368,13 @@ std::vector> getFanMode(AMDGPUData data) { } std::vector> getFanSpeedWrite(AMDGPUData data) { + // Don't try to use pre-6.7 interface on RX 7000 + // TODO: potential regression, if this exists on older cards, but doesn't function + char fanCurvePath[128]; + snprintf(fanCurvePath, 128, "%s/gpu_od/fan_ctrl/fan_curve", data.devPath.c_str()); + if (std::ifstream{fanCurvePath}.good()) + return {}; + char path[96]; snprintf(path, 96, "%s/pwm1", data.hwmonPath.c_str()); if (!std::ifstream{path}.good()) @@ -410,6 +417,71 @@ std::vector> getFanSpeedWrite(AMDGPUData data) { }}; } +std::vector> getFanSpeedWriteRX7000(AMDGPUData data) { + char fanCurvePath[128]; + snprintf(fanCurvePath, 128, "%s/gpu_od/fan_ctrl/fan_curve", data.devPath.c_str()); + if (!std::ifstream{fanCurvePath}.good()) + return {}; + + auto contentsRaw = fileContents(fanCurvePath); + if (!contentsRaw.has_value()) + return {}; + + // Replace the space in 'fan speed' with underscore, see doc/amd-pptables/rx7000-fancurve + // to fit the format parsePstateRangeLine expects + // Little cursed, but this allows us to use the same algorithm + auto contents = + fplus::replace_tokens(std::string{"fan speed"}, std::string{"fan_speed"}, *contentsRaw); + + // We don't care acout the temps, since we set all points to desired speed + auto speedRange = parsePstateRangeLine("FAN_CURVE(fan_speed)", contents); + if (!speedRange.has_value()) + return {}; + + // Doesn't make sense with what we do + auto getFunc = [] { return std::nullopt; }; + + auto setFunc = [=](AssignmentArgument a) -> std::optional { + if (!std::holds_alternative(a)) + return AssignmentError::InvalidType; + + auto target = std::get(a); + if (speedRange->min > target || speedRange->max < target) + return AssignmentError::OutOfRange; + + // TODO: do we even need to care about setting same temperature? + // Might be slow to do this every assignment + auto lines = pstateSectionLines("OD_FAN_CURVE", contents); + std::ofstream file{fanCurvePath}; + if (lines.empty() || !file.good()) + return AssignmentError::UnknownError; + + for (int i = 0; i < lines.size(); i++) { + // Write all curve points to same value + auto words = fplus::split_one_of(std::string{" "}, false, lines[i]); + if (words.size() < 3) + return AssignmentError::UnknownError; + + // Essentially cut off the 'C' in '65C' + auto temp = std::atoi(words[1].c_str()); + char cmdString[32]; + // TODO: docs say PWM but internet says percentage + snprintf(cmdString, 32, "%i %i %i", i, temp, target); + if (!(file << cmdString)) + return AssignmentError::UnknownError; + } + if (file << "c") + return std::nullopt; + return AssignmentError::UnknownError; + }; + + return {DeviceNode{ + .name = _("Fan Speed"), + .interface = Assignable{setFunc, *speedRange, getFunc, _("%")}, + .hash = md5(data.identifier + "RX7000 Fan Speed"), + }}; +} + std::vector> getFanSpeedRead(AMDGPUData data) { char path[96]; @@ -1330,6 +1402,7 @@ auto gpuTree = TreeConstructor{ {getFanRoot, { {getFanMode, {}}, {getFanSpeedWrite, {}}, + {getFanSpeedWriteRX7000, {}}, {getFanSpeedRead, {}} }}, {getPowerRoot, {