#include #include #include #include #include #include /* maybe have a read pp_table function */ /* would be neat to wrap it in a class but eh namespace is good enough */ namespace gpu { const std::string rel_metrics_path = "/../../gpu_metrics"; // gpu_metrics path relative to hwmon path constexpr uint16_t max_metrics_length = 512; // length of the metrics buffer /* takes pointer to a target gpu::sensors struct and updates its hwmon path and detects gpu_metrics */ int detect(gpuSensors* target, const std::string& pci) { try { target->hwmon = utils::getFirstDir("/sys/bus/pci/devices/" + pci + "/hwmon"); } catch (...) { applog(LOG::ERR, "Couldn't find GPU hwmon"); return -1; } applog(LOG::INF, "Detected path \"{}\"", target->hwmon); if (utils::fileExists(target->hwmon + rel_metrics_path)) { applog(LOG::INF, "Found gpu_metrics"); target->hasGpuMetrics = true; } return 0; } /* some metrics are missing in gpu_metrics, we always fill them from fs, unused metrics are commented out */ void fillMissingMetrics(gpuSensors* g) { static std::array buf; utils::fastReadNum<1000>(g->hwmon + "/in0_input", buf, g->vcore); // g->fan_pwm = std::stoi(readFile(g->hwmon + "/pwm1", 4)) * 100 / 256; utils::fastReadNum(g->hwmon + "/fan1_input", buf, g->fan_speed); utils::fastReadNum<1000000>(g->hwmon + "/power1_cap", buf, g->power_cap); // utils::fastReadNum(g->hwmon + "/power1_cap_max", g->power_cap_max) / 1000000; utils::fastReadNum(g->hwmon + "/../../mem_info_vram_used", buf, g->vram_used); // utils::fastReadNum(g->hwmon + "/../../mem_info_vram_total", g->vram_total); } /* get sensors for pre-vega cards (rx480/580) */ void updateFromFs(gpuSensors* g) { static std::array buf; utils::fastReadNum<1000000>(g->hwmon + "/freq1_input", buf, g->clock_core); utils::fastReadNum<1000000>(g->hwmon + "/freq2_input", buf, g->clock_mem); utils::fastReadNum<1000000>(g->hwmon + "/power1_input", buf, g->power_avg); utils::fastReadNum<1000>(g->hwmon + "/temp1_input", buf, g->temp_edge); utils::fastReadNum(g->hwmon + "/../../gpu_busy_percent", buf, g->usage_core); } /* get sensors for vega and newer (rx5700xt, rx7800xt [*]) */ void updateFromMetrics(gpuSensors* g) { std::array raw_metrics{}; if (!utils::readFile(g->hwmon + rel_metrics_path, raw_metrics)) return; switch (const auto header = reinterpret_cast(raw_metrics.data()); header->format_revision * 10 + header->content_revision) { /* rx5700xt, rx7800xt */ case 13: { const auto metrics = reinterpret_cast(raw_metrics.data()); g->usage_core = metrics->average_gfx_activity; g->usage_umc = metrics->average_umc_activity; g->usage_media = metrics->average_mm_activity; g->temp_edge = metrics->temperature_edge; g->temp_hotspot = metrics->temperature_hotspot; g->temp_mem = metrics->temperature_mem; g->temp_vrm_gfx = metrics->temperature_vrgfx; g->temp_vrm_soc = metrics->temperature_vrsoc; g->temp_vrm_mem = metrics->temperature_vrmem; g->power_avg = metrics->average_socket_power; g->fan_speed = metrics->current_fan_speed; // hacks for rx7800xt // g->clock_core = metrics->average_vclk1_frequency; // why not gfxclock? // g->clock_mem = metrics->average_gfxclk_frequency; // why gfxclock? min mem clock seems to be 96mhz, this is probably including time sleeping // hacks for rx5700xt g->clock_core = metrics->average_gfxclk_frequency; g->clock_mem = metrics->average_uclk_frequency * 2; // mem controller clock, actual mem is 2x that break; } /* ryzen 5 5600g */ case 22: { const auto metrics = reinterpret_cast(raw_metrics.data()); g->temp_edge = metrics->temperature_gfx / 100; break; } /* werid hardware i probably dont like, maybe fallback to reading from fs? */ default: { applog(LOG::WRN, "Unsupported gpu_metrics_v{}_{}", header->format_revision, header->content_revision); break; } } } /* reads values from system and updates the target gpu::sensors struct */ void update(gpuSensors* g) { fillMissingMetrics(g); if (g->hasGpuMetrics) updateFromMetrics(g); else updateFromFs(g); if (g->fan_speed > 9999) g->fan_speed = 9999; // sanity check fan speed } } // namespace gpu