diff --git a/projects/amdsmi/include/amd_smi/impl/amd_smi_test_overrides.h b/projects/amdsmi/include/amd_smi/impl/amd_smi_test_overrides.h index 8b6a1863e94f..280f7badda4f 100644 --- a/projects/amdsmi/include/amd_smi/impl/amd_smi_test_overrides.h +++ b/projects/amdsmi/include/amd_smi/impl/amd_smi_test_overrides.h @@ -5,8 +5,8 @@ #include -// Library-local test seam for the sysfs root smi_amdgpu_get_enabled_blocks() -// (src/amd_smi/amd_smi_utils.cc) reads .../ras/features from. Defaults to the +// Library-local sysfs-root seam for clock ranges and RAS enabled blocks +// (src/amd_smi/amd_smi_utils.cc). Defaults to the // real "/sys/class/drm/" prefix -- e.g. production reads // "/sys/class/drm/card0/device/ras/features". A test that overrides this to // "/tmp/amdsmi_fake_sysfs_XXXXXX/" (see FakeSysfsTree in diff --git a/projects/amdsmi/src/amd_smi/amd_smi_utils.cc b/projects/amdsmi/src/amd_smi/amd_smi_utils.cc index e37406c69bda..bacb9394e4e1 100644 --- a/projects/amdsmi/src/amd_smi/amd_smi_utils.cc +++ b/projects/amdsmi/src/amd_smi/amd_smi_utils.cc @@ -413,7 +413,7 @@ amdsmi_status_t smi_amdgpu_get_ranges(amd::smi::AMDSmiGPUDevice* device, amdsmi_ int* max_freq, int* min_freq, int* num_dpm, int* sleep_state_freq) { SMIGPUDEVICE_MUTEX(device->get_mutex()) - std::string fullpath = "/sys/class/drm/" + device->get_gpu_path() + "/device"; + std::string fullpath = smi_amdgpu_sysfs_drm_root() + device->get_gpu_path() + "/device"; std::string smclk_min_max_fullpath = ""; diff --git a/projects/amdsmi/tests/amd_smi_test/unit/gpu/clock_range_test.cc b/projects/amdsmi/tests/amd_smi_test/unit/gpu/clock_range_test.cc index 29ef18d4ee37..54a8b0aabfef 100644 --- a/projects/amdsmi/tests/amd_smi_test/unit/gpu/clock_range_test.cc +++ b/projects/amdsmi/tests/amd_smi_test/unit/gpu/clock_range_test.cc @@ -1,22 +1,31 @@ // Copyright Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT -// Unit tests for smi_amdgpu_parse_od_clk_range(), the pp_od_clk_voltage parser -// behind amd-smi's per-domain min/max clock. Driven over in-memory streams -- -// no GPU required. Guards the MI45x case where pp_od_clk_voltage carries no -// OD_FCLK section: the parser must report the domain absent so the caller falls -// back to pp_dpm_fclk instead of reporting a max of 0 MHz. +// Hardware-free tests for overdrive/DPM parsing and public clock APIs, +// using in-memory streams and fake sysfs devices. #include #include #include +#include #include "amd_smi/amdsmi.h" #include "amd_smi/impl/amd_smi_clk_testing.h" +#include "amd_smi/impl/amd_smi_drm.h" +#include "amd_smi/impl/amd_smi_gpu_device.h" +#include "amd_smi/impl/amd_smi_test_overrides.h" +#include "rocm_smi/rocm_smi.h" +#include "rocm_smi/rocm_smi_gpu_metrics.h" +#include "test_fixture_utils.h" namespace { +using amd::smi::AMDSmiDrm; +using amd::smi::AMDSmiGPUDevice; +using amd::smi::testing::ScopedOverride; +using amd::smi::testing::ScopedProcessorRegistration; + // pp_od_clk_voltage as seen on MI45x: SCLK and MCLK sections plus an OD_RANGE // footer, but no OD_FCLK section. constexpr char kOdNoFclk[] = @@ -261,6 +270,125 @@ TEST(GpuUnit, DpmRangesEmptyStreamKeepsUnavailableMin) { EXPECT_EQ(smi_amdgpu_parse_dpm_ranges(dpm, od_range, ranges), AMDSMI_STATUS_SUCCESS); EXPECT_EQ(ranges.max_freq, 0); EXPECT_EQ(ranges.min_freq, -1); + EXPECT_EQ(ranges.sleep_state_freq, -1); +} + +TEST(GpuUnit, DpmRangesBlankTablesReportIoError) { + for (const char* contents : {"\n", " \t\r\n", "\n\n"}) { + SCOPED_TRACE(contents); + std::istringstream dpm(contents); + SmiAmdgpuOdClkRange od_range; + SmiAmdgpuClkRanges ranges{1800, 1300, 2, 400}; + EXPECT_EQ(smi_amdgpu_parse_dpm_ranges(dpm, od_range, ranges), AMDSMI_STATUS_IO); + EXPECT_EQ(ranges.max_freq, 1800); + EXPECT_EQ(ranges.min_freq, 1300); + EXPECT_EQ(ranges.num_dpm, 2); + EXPECT_EQ(ranges.sleep_state_freq, 400); + } +} + +TEST(GpuUnit, DpmRangesBlankLinesReportIoWithOdRange) { + for (const char* contents : {"\n", " \t\r\n", "\n0: 1300Mhz\n", "0: 1300Mhz\n\n"}) { + SCOPED_TRACE(contents); + std::istringstream dpm(contents); + SmiAmdgpuOdClkRange od_range{true, 1800u, 1300u}; + SmiAmdgpuClkRanges ranges{1800, 1300, 2, 400}; + EXPECT_EQ(smi_amdgpu_parse_dpm_ranges(dpm, od_range, ranges), AMDSMI_STATUS_IO); + EXPECT_EQ(ranges.max_freq, 1800); + EXPECT_EQ(ranges.min_freq, 1300); + EXPECT_EQ(ranges.num_dpm, 2); + EXPECT_EQ(ranges.sleep_state_freq, 400); + } +} + +TEST(GpuUnit, DpmFrequencyMissingTableNotSupported) { + FakeSysfsTree tree; + ScopedRocmSmiDevice device(tree.root() + "/card0"); + rsmi_frequencies_t frequencies{}; + EXPECT_EQ(rsmi_dev_gpu_clk_freq_get(device.index(), RSMI_CLK_TYPE_DF, &frequencies), + RSMI_STATUS_NOT_SUPPORTED); +} + +TEST(GpuUnit, DpmFrequencyEmptyTablesPreserveDataError) { + FakeSysfsTree tree; + ScopedRocmSmiDevice device(tree.root() + "/card0"); + for (const char* contents : {"", "\n", " \t\r\n", "\n\n"}) { + SCOPED_TRACE(contents); + tree.WriteFile("card0/device/pp_dpm_fclk", "0: 1300Mhz *\n"); + rsmi_frequencies_t frequencies{}; + ASSERT_EQ(rsmi_dev_gpu_clk_freq_get(device.index(), RSMI_CLK_TYPE_DF, &frequencies), + RSMI_STATUS_SUCCESS); + ASSERT_EQ(frequencies.num_supported, 1u); + + tree.WriteFile("card0/device/pp_dpm_fclk", contents); + EXPECT_EQ(rsmi_dev_gpu_clk_freq_get(device.index(), RSMI_CLK_TYPE_DF, &frequencies), + RSMI_STATUS_UNEXPECTED_DATA); + EXPECT_EQ(frequencies.num_supported, 0u); + } +} + +TEST(GpuUnit, DpmFrequencyValidTableStillReportsLevels) { + FakeSysfsTree tree; + tree.WriteFile("card0/device/pp_dpm_fclk", "0: 1300Mhz\n1: 1550Mhz *\n2: 1800Mhz\n"); + ScopedRocmSmiDevice device(tree.root() + "/card0"); + rsmi_frequencies_t frequencies{}; + ASSERT_EQ(rsmi_dev_gpu_clk_freq_get(device.index(), RSMI_CLK_TYPE_DF, &frequencies), + RSMI_STATUS_SUCCESS); + EXPECT_EQ(frequencies.num_supported, 3u); + EXPECT_EQ(frequencies.current, 1u); + EXPECT_EQ(frequencies.frequency[0], 1300000000u); + EXPECT_EQ(frequencies.frequency[2], 1800000000u); +} + +TEST(GpuUnit, DpmMixedDevicesPreservePublicApiErrors) { + ScopedAmdsmiInit smi_init; + FakeSysfsTree tree; + tree.WriteFile("card0/device/pp_dpm_fclk", "0: 1300Mhz\n1: 1550Mhz *\n2: 1800Mhz\n"); + tree.WriteFile("card1/device/pp_dpm_fclk", "\n"); + // amdsmi_get_clock_info reads gpu_metrics even for FCLK. + amd::smi::AMDGpuMetrics_v10_t metrics{}; + metrics.m_common_header = {sizeof(metrics), 1, 0}; + for (const char* card : {"card0", "card1"}) { + const std::string device_path = std::string(card) + "/device/"; + tree.WriteFile(device_path + "gpu_metrics", + std::string(reinterpret_cast(&metrics), sizeof(metrics))); + tree.WriteFile(device_path + "pp_od_clk_voltage", kOdNoFclk); + } + ScopedOverride root_guard( + smi_amdgpu_sysfs_drm_root, smi_amdgpu_set_sysfs_drm_root_for_testing, tree.root() + "/"); + ScopedRocmSmiDevice rocm_dgpu(tree.root() + "/card0"); + ScopedRocmSmiDevice rocm_igpu(tree.root() + "/card1"); + AMDSmiDrm drm; + AMDSmiGPUDevice dgpu(rocm_dgpu.index(), "card0", amdsmi_bdf_t{}, drm); + AMDSmiGPUDevice igpu(rocm_igpu.index(), "card1", amdsmi_bdf_t{}, drm); + ScopedProcessorRegistration dgpu_registration(&dgpu); + ScopedProcessorRegistration igpu_registration(&igpu); + for (const auto handle : {dgpu_registration.handle(), igpu_registration.handle()}) { + amdsmi_gpu_metrics_t result{}; + ASSERT_EQ(amdsmi_get_gpu_metrics_info(handle, &result), AMDSMI_STATUS_SUCCESS); + } + + amdsmi_frequencies_t frequencies{}; + ASSERT_EQ(amdsmi_get_clk_freq(dgpu_registration.handle(), AMDSMI_CLK_TYPE_DF, &frequencies), + AMDSMI_STATUS_SUCCESS); + EXPECT_EQ(frequencies.num_supported, 3u); + EXPECT_EQ(frequencies.current, 1u); + EXPECT_EQ(frequencies.frequency[0], 1300000000u); + EXPECT_EQ(frequencies.frequency[1], 1550000000u); + EXPECT_EQ(frequencies.frequency[2], 1800000000u); + amdsmi_clk_info_t info{}; + ASSERT_EQ(amdsmi_get_clock_info(dgpu_registration.handle(), AMDSMI_CLK_TYPE_DF, &info), + AMDSMI_STATUS_SUCCESS); + EXPECT_EQ(info.min_clk, 1300u); + EXPECT_EQ(info.max_clk, 1800u); + + // These distinct errors capture current newline-only behavior, not a + // prescribed cross-API status contract. + EXPECT_EQ(amdsmi_get_clk_freq(igpu_registration.handle(), AMDSMI_CLK_TYPE_DF, &frequencies), + AMDSMI_STATUS_UNEXPECTED_DATA); + EXPECT_EQ(frequencies.num_supported, 0u); + EXPECT_EQ(amdsmi_get_clock_info(igpu_registration.handle(), AMDSMI_CLK_TYPE_DF, &info), + AMDSMI_STATUS_IO); } // A genuinely out-of-range value (> INT_MAX, not the sentinel) is still