@@ -43,5 +43,973 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rocm_smi/rocm_smi.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
|
||||
#include "rocm_smi/rocm_smi.h"
|
||||
#include "rocm_smi/rocm_smi_main.h"
|
||||
#include "rocm_smi/rocm_smi_device.h"
|
||||
#include "rocm_smi/rocm_smi_utils.h"
|
||||
|
||||
static const uint32_t kMaxOverdriveLevel = 20;
|
||||
|
||||
static rsmi_status_t handleException() {
|
||||
try {
|
||||
throw;
|
||||
} catch (const std::bad_alloc& e) {
|
||||
debug_print("RSMI exception: BadAlloc\n");
|
||||
return RSMI_STATUS_OUT_OF_RESOURCES;
|
||||
} catch (const std::exception& e) {
|
||||
debug_print("Unhandled exception: %s\n", e.what());
|
||||
assert(false && "Unhandled exception.");
|
||||
return RSMI_STATUS_INTERNAL_EXCEPTION;
|
||||
} catch (const std::nested_exception& e) {
|
||||
debug_print("Callback threw, forwarding.\n");
|
||||
e.rethrow_nested();
|
||||
return RSMI_STATUS_INTERNAL_EXCEPTION;
|
||||
} catch (...) {
|
||||
assert(false && "Unhandled exception.");
|
||||
abort();
|
||||
return RSMI_STATUS_INTERNAL_EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
#define TRY try {
|
||||
#define CATCH } catch (...) {return handleException();}
|
||||
#define GET_DEV_FROM_INDX \
|
||||
amd::smi::RocmSMI smi = amd::smi::RocmSMI::getInstance(); \
|
||||
if (dv_ind >= smi.monitor_devices().size()) { \
|
||||
return RSMI_STATUS_INVALID_ARGS; \
|
||||
} \
|
||||
std::shared_ptr<amd::smi::Device> dev = smi.monitor_devices()[dv_ind]; \
|
||||
assert(dev != nullptr);
|
||||
|
||||
static rsmi_status_t errno_to_rsmi_status(uint32_t err) {
|
||||
switch (err) {
|
||||
case 0: return RSMI_STATUS_SUCCESS;
|
||||
case EACCES: return RSMI_STATUS_PERMISSION;
|
||||
case EPERM: return RSMI_STATUS_NOT_SUPPORTED;
|
||||
case ENOENT: return RSMI_STATUS_FILE_ERROR;
|
||||
default: return RSMI_STATUS_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse a string of the form "<int index>: <int freq><freq. unit string> <|*>"
|
||||
*/
|
||||
static uint32_t freq_string_to_int(std::string freq_line, bool *is_curr) {
|
||||
assert(is_curr != nullptr);
|
||||
|
||||
std::istringstream fs(freq_line);
|
||||
|
||||
uint32_t ind;
|
||||
uint32_t freq;
|
||||
std::string junk;
|
||||
std::string units_str;
|
||||
std::string star_str;
|
||||
|
||||
fs >> ind;
|
||||
fs >> junk; // colon
|
||||
fs >> freq;
|
||||
fs >> units_str;
|
||||
fs >> star_str;
|
||||
|
||||
if (is_curr != nullptr) {
|
||||
if (freq_line.find("*") != std::string::npos) {
|
||||
*is_curr = true;
|
||||
} else {
|
||||
*is_curr = false;
|
||||
}
|
||||
}
|
||||
uint32_t multiplier = 0;
|
||||
|
||||
if (units_str == "Mhz") {
|
||||
multiplier = 1000000;
|
||||
} else if (units_str == "Ghz") {
|
||||
multiplier = 1000000000;
|
||||
} else if (units_str == "Khz") {
|
||||
multiplier = 1000;
|
||||
} else if (units_str == "Hz") {
|
||||
multiplier = 1;
|
||||
} else {
|
||||
assert(!"Unexpected units for frequency");
|
||||
}
|
||||
|
||||
return freq*multiplier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a string of the form "<int index> <mode name string> <|*>"
|
||||
*/
|
||||
static rsmi_power_profile_preset_masks
|
||||
power_prof_string_to_int(std::string pow_prof_line, bool *is_curr) {
|
||||
std::istringstream fs(pow_prof_line);
|
||||
uint32_t ind;
|
||||
std::string mode;
|
||||
size_t tmp;
|
||||
|
||||
rsmi_power_profile_preset_masks ret = RSMI_PWR_PROF_PRST_INVALID;
|
||||
|
||||
fs >> ind;
|
||||
fs >> mode;
|
||||
|
||||
while (1) {
|
||||
tmp = mode.find_last_of("* :");
|
||||
if (tmp == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
mode = mode.substr(0, tmp);
|
||||
}
|
||||
|
||||
if (is_curr != nullptr) {
|
||||
if (pow_prof_line.find("*") != std::string::npos) {
|
||||
*is_curr = true;
|
||||
} else {
|
||||
*is_curr = false;
|
||||
}
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::function<void()>> mode_map {
|
||||
{"3D_FULL_SCREEN", [&](){ ret = RSMI_PWR_PROF_PRST_3D_FULL_SCR_MASK; }},
|
||||
{"POWER_SAVING", [&](){ ret = RSMI_PWR_PROF_PRST_POWER_SAVING_MASK; }},
|
||||
{"VIDEO", [&](){ ret = RSMI_PWR_PROF_PRST_VIDEO_MASK; }},
|
||||
{"VR", [&](){ ret = RSMI_PWR_PROF_PRST_VR_MASK; }},
|
||||
{"COMPUTE", [&](){ ret = RSMI_PWR_PROF_PRST_COMPUTE_MASK; }},
|
||||
{"CUSTOM", [&](){ ret = RSMI_PWR_PROF_PRST_CUSTOM_MASK; }},
|
||||
};
|
||||
auto mode_iter = mode_map.find(mode);
|
||||
|
||||
if (mode_iter != mode_map.end()) {
|
||||
mode_iter->second();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static rsmi_status_t get_dev_value_str(amd::smi::DevInfoTypes type,
|
||||
uint32_t dv_ind, std::string *val_str) {
|
||||
GET_DEV_FROM_INDX
|
||||
int ret = dev->readDevInfo(type, val_str);
|
||||
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
static rsmi_status_t set_dev_value(amd::smi::DevInfoTypes type,
|
||||
uint32_t dv_ind, uint64_t val) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
int ret = dev->writeDevInfo(type, val);
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
static rsmi_status_t get_dev_mon_value(amd::smi::MonitorTypes type,
|
||||
uint32_t dv_ind, uint32_t sensor_ind, int64_t *val) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
assert(dev->monitor() != nullptr);
|
||||
|
||||
std::string val_str;
|
||||
|
||||
int ret = dev->monitor()->readMonitor(type, sensor_ind, &val_str);
|
||||
if (ret) {
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
*val = std::stoi(val_str);
|
||||
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static rsmi_status_t get_dev_mon_value(amd::smi::MonitorTypes type,
|
||||
uint32_t dv_ind, uint32_t sensor_ind, uint64_t *val) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
assert(dev->monitor() != nullptr);
|
||||
|
||||
std::string val_str;
|
||||
|
||||
int ret = dev->monitor()->readMonitor(type, sensor_ind, &val_str);
|
||||
if (ret) {
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
*val = std::stoul(val_str);
|
||||
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static rsmi_status_t set_dev_mon_value(amd::smi::MonitorTypes type,
|
||||
uint32_t dv_ind, int32_t sensor_ind, T val) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
assert(dev->monitor() != nullptr);
|
||||
|
||||
int ret = dev->monitor()->writeMonitor(type, sensor_ind,
|
||||
std::to_string(val));
|
||||
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
static rsmi_status_t get_power_mon_value(amd::smi::PowerMonTypes type,
|
||||
uint32_t dv_ind, uint64_t *val) {
|
||||
amd::smi::RocmSMI smi = amd::smi::RocmSMI::getInstance();
|
||||
|
||||
if (dv_ind >= smi.monitor_devices().size() || val == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
uint32_t ret = smi.DiscoverAMDPowerMonitors();
|
||||
if (ret == EACCES) {
|
||||
return RSMI_STATUS_PERMISSION;
|
||||
} else if (ret != 0) {
|
||||
return RSMI_STATUS_FILE_ERROR;
|
||||
}
|
||||
|
||||
std::shared_ptr<amd::smi::Device> dev = smi.monitor_devices()[dv_ind];
|
||||
assert(dev != nullptr);
|
||||
assert(dev->monitor() != nullptr);
|
||||
|
||||
ret = dev->power_monitor()->readPowerValue(type, val);
|
||||
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
static rsmi_status_t get_dev_mon_value_str(amd::smi::MonitorTypes type,
|
||||
uint32_t dv_ind, int32_t sensor_ind, std::string *val_str) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
assert(dev->monitor() != nullptr);
|
||||
|
||||
int ret = dev->monitor()->readMonitor(type, sensor_ind, val_str);
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
|
||||
static rsmi_status_t get_dev_value_vec(amd::smi::DevInfoTypes type,
|
||||
uint32_t dv_ind, std::vector<std::string> *val_vec) {
|
||||
GET_DEV_FROM_INDX
|
||||
|
||||
int ret = dev->readDevInfo(type, val_vec);
|
||||
return errno_to_rsmi_status(ret);
|
||||
}
|
||||
|
||||
// A call to rsmi_init is not technically necessary at this time, but may be
|
||||
// in the future.
|
||||
rsmi_status_t
|
||||
rsmi_init(uint64_t init_flags) {
|
||||
TRY
|
||||
(void)init_flags; // unused for now; for future use
|
||||
|
||||
amd::smi::RocmSMI smi = amd::smi::RocmSMI::getInstance();
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
// A call to rsmi_shut_down is not technically necessary at this time,
|
||||
// but may be in the future.
|
||||
rsmi_status_t
|
||||
rsmi_shut_down(void) {
|
||||
TRY
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_num_monitor_devices(uint32_t *num_devices) {
|
||||
TRY
|
||||
if (num_devices == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
amd::smi::RocmSMI smi = amd::smi::RocmSMI::getInstance();
|
||||
|
||||
*num_devices = smi.monitor_devices().size();
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_id_get(uint32_t dv_ind, uint64_t *id) {
|
||||
TRY
|
||||
std::string val_str;
|
||||
rsmi_status_t ret = get_dev_value_str(amd::smi::kDevDevID, dv_ind, &val_str);
|
||||
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
*id = strtoul(val_str.c_str(), nullptr, 16);
|
||||
assert(errno == 0);
|
||||
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_perf_level_get(uint32_t dv_ind, rsmi_dev_perf_level *perf) {
|
||||
TRY
|
||||
std::string val_str;
|
||||
rsmi_status_t ret = get_dev_value_str(amd::smi::kDevPerfLevel, dv_ind,
|
||||
&val_str);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (val_str == "auto") {
|
||||
*perf = RSMI_DEV_PERF_LEVEL_AUTO;
|
||||
} else if (val_str == "low") {
|
||||
*perf = RSMI_DEV_PERF_LEVEL_LOW;
|
||||
} else if (val_str == "high") {
|
||||
*perf = RSMI_DEV_PERF_LEVEL_HIGH;
|
||||
} else if (val_str == "manual") {
|
||||
*perf = RSMI_DEV_PERF_LEVEL_MANUAL;
|
||||
} else {
|
||||
*perf = RSMI_DEV_PERF_LEVEL_UNKNOWN;
|
||||
}
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_overdrive_level_get(uint32_t dv_ind, uint32_t *od) {
|
||||
TRY
|
||||
std::string val_str;
|
||||
rsmi_status_t ret = get_dev_value_str(amd::smi::kDevOverDriveLevel, dv_ind,
|
||||
&val_str);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
*od = strtoul(val_str.c_str(), nullptr, 10);
|
||||
assert(errno == 0);
|
||||
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_overdrive_level_set(int32_t dv_ind, uint32_t od) {
|
||||
TRY
|
||||
if (od > kMaxOverdriveLevel) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
return set_dev_value(amd::smi::kDevOverDriveLevel, dv_ind, od);
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_perf_level_set(int32_t dv_ind, rsmi_dev_perf_level perf_level) {
|
||||
TRY
|
||||
if (perf_level > RSMI_DEV_PERF_LEVEL_LAST) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
return set_dev_value(amd::smi::kDevPerfLevel, dv_ind, perf_level);
|
||||
CATCH
|
||||
}
|
||||
|
||||
static rsmi_status_t get_frequencies(amd::smi::DevInfoTypes type,
|
||||
uint32_t dv_ind, rsmi_frequencies *f) {
|
||||
TRY
|
||||
std::vector<std::string> val_vec;
|
||||
rsmi_status_t ret;
|
||||
|
||||
if (f == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ret = get_dev_value_vec(type, dv_ind, &val_vec);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
assert(val_vec.size() <= RSMI_MAX_NUM_FREQUENCIES);
|
||||
|
||||
f->num_supported = val_vec.size();
|
||||
bool current = false;
|
||||
f->current = RSMI_MAX_NUM_FREQUENCIES + 1; // init to an invalid value
|
||||
|
||||
for (uint32_t i = 0; i < f->num_supported; ++i) {
|
||||
f->frequency[i] = freq_string_to_int(val_vec[i], ¤t);
|
||||
if (current) {
|
||||
// Should only be 1 current frequency
|
||||
assert(f->current == RSMI_MAX_NUM_FREQUENCIES + 1);
|
||||
f->current = i;
|
||||
}
|
||||
}
|
||||
|
||||
assert(f->current < f->num_supported);
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
static rsmi_status_t get_power_profiles(uint32_t dv_ind,
|
||||
rsmi_power_profile_status *p,
|
||||
std::map<rsmi_power_profile_preset_masks, uint32_t> *ind_map) {
|
||||
TRY
|
||||
std::vector<std::string> val_vec;
|
||||
rsmi_status_t ret;
|
||||
|
||||
if (p == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ret = get_dev_value_vec(amd::smi::kDevPowerProfileMode, dv_ind, &val_vec);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
assert(val_vec.size() <= RSMI_MAX_NUM_POWER_PROFILES);
|
||||
|
||||
p->num_profiles = val_vec.size() - 1; // -1 for the header line
|
||||
bool current = false;
|
||||
p->current = RSMI_PWR_PROF_PRST_INVALID; // init to an invalid value
|
||||
p->available_profiles = 0;
|
||||
|
||||
rsmi_power_profile_preset_masks prof;
|
||||
|
||||
for (uint32_t i = 1; i < val_vec.size(); ++i) {
|
||||
prof = power_prof_string_to_int(val_vec[i], ¤t);
|
||||
|
||||
if (prof == RSMI_PWR_PROF_PRST_INVALID) {
|
||||
return RSMI_STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (ind_map != nullptr) {
|
||||
(*ind_map)[prof] = i-1;
|
||||
}
|
||||
|
||||
p->available_profiles |= prof;
|
||||
if (current) {
|
||||
// Should only be 1 current profile
|
||||
assert(p->current == RSMI_PWR_PROF_PRST_INVALID);
|
||||
p->current = prof;
|
||||
}
|
||||
}
|
||||
|
||||
assert(p->current != RSMI_PWR_PROF_PRST_INVALID);
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
static bool is_power_of_2(uint64_t n) {
|
||||
return n && !(n & (n - 1));
|
||||
}
|
||||
static rsmi_status_t set_power_profile(uint32_t dv_ind,
|
||||
rsmi_power_profile_preset_masks profile) {
|
||||
TRY
|
||||
|
||||
rsmi_status_t ret;
|
||||
rsmi_power_profile_status avail_profiles = {0, RSMI_PWR_PROF_PRST_INVALID, 0};
|
||||
|
||||
// TODO(cf): test if it is valid to OR profiles; if not the following is
|
||||
// not necessary:
|
||||
// Determine if the provided profile is valid
|
||||
if (!is_power_of_2(profile)) {
|
||||
return RSMI_STATUS_INPUT_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
std::map<rsmi_power_profile_preset_masks, uint32_t> ind_map;
|
||||
ret = get_power_profiles(dv_ind, &avail_profiles, &ind_map);
|
||||
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!(profile & avail_profiles.available_profiles)) {
|
||||
return RSMI_STATUS_INPUT_OUT_OF_BOUNDS;
|
||||
}
|
||||
assert(ind_map.find(profile) != ind_map.end());
|
||||
|
||||
// Set perf. level to manual so that we can then set the power profile
|
||||
ret = rsmi_dev_perf_level_set(dv_ind, RSMI_DEV_PERF_LEVEL_MANUAL);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Write the new profile
|
||||
ret = set_dev_value(amd::smi::kDevPowerProfileMode, dv_ind,
|
||||
ind_map[profile]);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_gpu_clk_freq_get(uint32_t dv_ind, rsmi_clk_type clk_type,
|
||||
rsmi_frequencies *f) {
|
||||
TRY
|
||||
switch (clk_type) {
|
||||
case RSMI_CLK_TYPE_SYS:
|
||||
return get_frequencies(amd::smi::kDevGPUSClk, dv_ind, f);
|
||||
break;
|
||||
case RSMI_CLK_TYPE_MEM:
|
||||
return get_frequencies(amd::smi::kDevGPUMClk, dv_ind, f);
|
||||
break;
|
||||
default:
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
CATCH
|
||||
}
|
||||
|
||||
static std::string bitfield_to_freq_string(uint64_t bitf,
|
||||
uint32_t num_supported) {
|
||||
std::string bf_str("");
|
||||
std::bitset<RSMI_MAX_NUM_FREQUENCIES> bs(bitf);
|
||||
|
||||
for (uint32_t i = 0; i < num_supported; ++i) {
|
||||
if (bs[i]) {
|
||||
bf_str += std::to_string(i);
|
||||
bf_str += " ";
|
||||
}
|
||||
}
|
||||
return bf_str;
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_gpu_clk_freq_set(uint32_t dv_ind,
|
||||
rsmi_clk_type clk_type, uint64_t freq_bitmask) {
|
||||
rsmi_status_t ret;
|
||||
rsmi_frequencies freqs;
|
||||
|
||||
TRY
|
||||
ret = rsmi_dev_gpu_clk_freq_get(dv_ind, clk_type, &freqs);
|
||||
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
assert(freqs.num_supported <= RSMI_MAX_NUM_FREQUENCIES);
|
||||
|
||||
amd::smi::RocmSMI smi = amd::smi::RocmSMI::getInstance();
|
||||
|
||||
// Above call to rsmi_dev_get_gpu_clk_freq should have emitted an error if
|
||||
// assert below is not true
|
||||
assert(dv_ind < smi.monitor_devices().size());
|
||||
|
||||
std::string freq_enable_str =
|
||||
bitfield_to_freq_string(freq_bitmask, freqs.num_supported);
|
||||
|
||||
|
||||
std::shared_ptr<amd::smi::Device> dev = smi.monitor_devices()[dv_ind];
|
||||
assert(dev != nullptr);
|
||||
|
||||
ret = rsmi_dev_perf_level_set(dv_ind, RSMI_DEV_PERF_LEVEL_MANUAL);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ret_i;
|
||||
switch (clk_type) {
|
||||
case RSMI_CLK_TYPE_SYS:
|
||||
ret_i = dev->writeDevInfo(amd::smi::kDevGPUSClk, freq_enable_str);
|
||||
return errno_to_rsmi_status(ret_i);
|
||||
break;
|
||||
|
||||
case RSMI_CLK_TYPE_MEM:
|
||||
ret_i = dev->writeDevInfo(amd::smi::kDevGPUMClk, freq_enable_str);
|
||||
return errno_to_rsmi_status(ret_i);
|
||||
break;
|
||||
|
||||
default:
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_name_get(uint32_t dv_ind, char *name, size_t len) {
|
||||
TRY
|
||||
if (name == nullptr || len == 0) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
std::string val_str;
|
||||
rsmi_status_t ret;
|
||||
|
||||
ret = get_dev_mon_value_str(amd::smi::kMonName, dv_ind, -1, &val_str);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ln = val_str.copy(name, len);
|
||||
|
||||
name[std::min(len - 1, ln)] = '\0';
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_temp_metric_get(uint32_t dv_ind, uint32_t sensor_ind,
|
||||
rsmi_temperature_metric metric, int64_t *temperature) {
|
||||
TRY
|
||||
|
||||
if (temperature == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
rsmi_status_t ret;
|
||||
amd::smi::MonitorTypes mon_type;
|
||||
|
||||
|
||||
// Make any adjustments to sensor_ind here, if index is not a 0 based. For
|
||||
// rocm_smi we are using a 0-based index. However, most of the Linux sysfs
|
||||
// monitor files are 1-based, so we will increment by 1 and make adjustments
|
||||
// for exceptions later.
|
||||
// See https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
|
||||
++sensor_ind;
|
||||
|
||||
switch (metric) {
|
||||
case RSMI_TEMP_CURRENT:
|
||||
mon_type = amd::smi::kMonTemp;
|
||||
break;
|
||||
case RSMI_TEMP_MAX:
|
||||
mon_type = amd::smi::kMonTempMax;
|
||||
break;
|
||||
case RSMI_TEMP_MIN:
|
||||
mon_type = amd::smi::kMonTempMin;
|
||||
break;
|
||||
case RSMI_TEMP_MAX_HYST:
|
||||
mon_type = amd::smi::kMonTempMaxHyst;
|
||||
break;
|
||||
case RSMI_TEMP_MIN_HYST:
|
||||
mon_type = amd::smi::kMonTempMinHyst;
|
||||
break;
|
||||
case RSMI_TEMP_CRITICAL:
|
||||
mon_type = amd::smi::kMonTempCritical;
|
||||
break;
|
||||
case RSMI_TEMP_CRITICAL_HYST:
|
||||
mon_type = amd::smi::kMonTempCriticalHyst;
|
||||
break;
|
||||
case RSMI_TEMP_EMERGENCY:
|
||||
mon_type = amd::smi::kMonTempEmergency;
|
||||
break;
|
||||
case RSMI_TEMP_EMERGENCY_HYST:
|
||||
mon_type = amd::smi::kMonTempEmergencyHyst;
|
||||
break;
|
||||
case RSMI_TEMP_CRIT_MIN:
|
||||
mon_type = amd::smi::kMonTempCritMin;
|
||||
break;
|
||||
case RSMI_TEMP_CRIT_MIN_HYST:
|
||||
mon_type = amd::smi::kMonTempCritMinHyst;
|
||||
break;
|
||||
case RSMI_TEMP_OFFSET:
|
||||
mon_type = amd::smi::kMonTempOffset;
|
||||
break;
|
||||
case RSMI_TEMP_LOWEST:
|
||||
mon_type = amd::smi::kMonTempLowest;
|
||||
break;
|
||||
case RSMI_TEMP_HIGHEST:
|
||||
mon_type = amd::smi::kMonTempHighest;
|
||||
break;
|
||||
default:
|
||||
mon_type = amd::smi::kMonInvalid;
|
||||
}
|
||||
|
||||
ret = get_dev_mon_value(mon_type, dv_ind, sensor_ind, temperature);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_fan_speed_get(uint32_t dv_ind, uint32_t sensor_ind, int64_t *speed) {
|
||||
TRY
|
||||
|
||||
if (speed == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
rsmi_status_t ret;
|
||||
|
||||
++sensor_ind; // fan sysfs files have 1-based indices
|
||||
|
||||
ret = get_dev_mon_value(amd::smi::kMonFanSpeed, dv_ind, sensor_ind, speed);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_fan_rpms_get(uint32_t dv_ind, uint32_t sensor_ind, int64_t *speed) {
|
||||
TRY
|
||||
|
||||
if (speed == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
++sensor_ind; // fan sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
|
||||
ret = get_dev_mon_value(amd::smi::kMonFanRPMs, dv_ind, sensor_ind, speed);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_fan_reset(uint32_t dv_ind, uint32_t sensor_ind) {
|
||||
TRY
|
||||
|
||||
rsmi_status_t ret;
|
||||
|
||||
++sensor_ind; // fan sysfs files have 1-based indices
|
||||
|
||||
ret = set_dev_mon_value<uint64_t>(amd::smi::kMonFanCntrlEnable,
|
||||
dv_ind, sensor_ind, 2);
|
||||
|
||||
return ret;
|
||||
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_fan_speed_set(uint32_t dv_ind, uint32_t sensor_ind, uint64_t speed) {
|
||||
TRY
|
||||
|
||||
rsmi_status_t ret;
|
||||
uint64_t max_speed;
|
||||
|
||||
|
||||
ret = rsmi_dev_fan_speed_max_get(dv_ind, sensor_ind, &max_speed);
|
||||
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (speed > max_speed) {
|
||||
return RSMI_STATUS_INPUT_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
++sensor_ind; // fan sysfs files have 1-based indices
|
||||
|
||||
// First need to set fan mode (pwm1_enable) to 1 (aka, "manual")
|
||||
ret = set_dev_mon_value<uint64_t>(amd::smi::kMonFanCntrlEnable, dv_ind,
|
||||
sensor_ind, 1);
|
||||
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = set_dev_mon_value<uint64_t>(amd::smi::kMonFanSpeed, dv_ind,
|
||||
sensor_ind, speed);
|
||||
|
||||
return ret;
|
||||
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_fan_speed_max_get(uint32_t dv_ind, uint32_t sensor_ind,
|
||||
uint64_t *max_speed) {
|
||||
TRY
|
||||
|
||||
if (max_speed == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
++sensor_ind; // fan sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
|
||||
ret = get_dev_mon_value(amd::smi::kMonMaxFanSpeed, dv_ind, sensor_ind,
|
||||
reinterpret_cast<int64_t *>(max_speed));
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_max_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *power) {
|
||||
TRY
|
||||
|
||||
if (power == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
(void)sensor_ind; // Not used yet
|
||||
// ++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
ret = get_power_mon_value(amd::smi::kPowerMaxGPUPower, dv_ind, power);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_ave_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *power) {
|
||||
TRY
|
||||
|
||||
if (power == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
(void)sensor_ind; // Not used yet
|
||||
// ++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
ret = get_power_mon_value(amd::smi::kPowerAveGPUPower, dv_ind, power);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_cap_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *cap) {
|
||||
TRY
|
||||
|
||||
if (cap == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
ret = get_dev_mon_value(amd::smi::kMonPowerCap, dv_ind, sensor_ind, cap);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_cap_range_get(uint32_t dv_ind, uint32_t sensor_ind,
|
||||
uint64_t *max, uint64_t *min) {
|
||||
TRY
|
||||
|
||||
if (max == nullptr || min == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret;
|
||||
ret = get_dev_mon_value(amd::smi::kMonPowerCapMax, dv_ind, sensor_ind, max);
|
||||
|
||||
if (ret == RSMI_STATUS_SUCCESS) {
|
||||
ret = get_dev_mon_value(amd::smi::kMonPowerCapMin, dv_ind,
|
||||
sensor_ind, min);
|
||||
}
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_cap_set(uint32_t dv_ind, uint32_t sensor_ind, uint64_t cap) {
|
||||
TRY
|
||||
rsmi_status_t ret;
|
||||
uint64_t min, max;
|
||||
|
||||
|
||||
ret = rsmi_dev_power_cap_range_get(dv_ind, sensor_ind, &max, &min);
|
||||
if (ret != RSMI_STATUS_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// All rsmi_* calls that use sensor_ind should use the 0-based value,
|
||||
// so increment this after the call above.
|
||||
++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
if (cap > max || cap < min) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ret = set_dev_mon_value<uint64_t>(amd::smi::kMonPowerCap, dv_ind,
|
||||
sensor_ind, cap);
|
||||
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_profile_presets_get(uint32_t dv_ind, uint32_t sensor_ind,
|
||||
rsmi_power_profile_status *status) {
|
||||
TRY
|
||||
|
||||
++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret = get_power_profiles(dv_ind, status, nullptr);
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_dev_power_profile_set(uint32_t dv_ind, uint32_t sensor_ind,
|
||||
rsmi_power_profile_preset_masks profile) {
|
||||
TRY
|
||||
++sensor_ind; // power sysfs files have 1-based indices
|
||||
|
||||
rsmi_status_t ret = set_power_profile(dv_ind, profile);
|
||||
return ret;
|
||||
CATCH
|
||||
}
|
||||
|
||||
rsmi_status_t
|
||||
rsmi_status_string(rsmi_status_t status, const char **status_string) {
|
||||
TRY
|
||||
if (status_string == nullptr) {
|
||||
return RSMI_STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
const size_t status_u = static_cast<size_t>(status);
|
||||
switch (status_u) {
|
||||
case RSMI_STATUS_SUCCESS:
|
||||
*status_string = "RSMI_STATUS_SUCCESS: The function has been executed"
|
||||
" successfully.";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_INVALID_ARGS:
|
||||
*status_string =
|
||||
"RSMI_STATUS_INVALID_ARGS: The provided arguments do not"
|
||||
" meet the preconditions required for calling this function.";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_NOT_SUPPORTED:
|
||||
*status_string = "RSMI_STATUS_NOT_SUPPORTED: This function is not"
|
||||
" supported in the current environment.";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_FILE_ERROR:
|
||||
*status_string =
|
||||
"RSMI_STATUS_FILE_ERROR: There was an error in finding or"
|
||||
" opening a file or directory. The operation may not be supported by "
|
||||
"this Linux kernel version.";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_PERMISSION:
|
||||
*status_string = "RSMI_STATUS_PERMISSION: The user ID of the calling"
|
||||
" process does not have sufficient permission to execute a command."
|
||||
" Often this is fixed by running as root (sudo).";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_OUT_OF_RESOURCES:
|
||||
*status_string = "Unable to acquire memory or other resource";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_INTERNAL_EXCEPTION:
|
||||
*status_string = "An internal exception was caught";
|
||||
break;
|
||||
|
||||
case RSMI_STATUS_INPUT_OUT_OF_BOUNDS:
|
||||
*status_string = "The provided input is out of allowable or safe range";
|
||||
break;
|
||||
|
||||
default:
|
||||
*status_string = "An unknown error occurred";
|
||||
return RSMI_STATUS_UNKNOWN_ERROR;
|
||||
}
|
||||
return RSMI_STATUS_SUCCESS;
|
||||
CATCH
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <fstream>
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
#include "rocm_smi/rocm_smi_main.h"
|
||||
#include "rocm_smi/rocm_smi_device.h"
|
||||
#include "rocm_smi/rocm_smi.h"
|
||||
|
||||
namespace amd {
|
||||
namespace smi {
|
||||
@@ -63,6 +64,12 @@ static const char *kDevDevIDFName = "device";
|
||||
static const char *kDevOverDriveLevelFName = "pp_sclk_od";
|
||||
static const char *kDevGPUSClkFName = "pp_dpm_sclk";
|
||||
static const char *kDevGPUMClkFName = "pp_dpm_mclk";
|
||||
static const char *kDevPowerProfileModeName = "pp_power_profile_mode";
|
||||
static const char *kDevPerfLevelAutoStr = "auto";
|
||||
static const char *kDevPerfLevelLowStr = "low";
|
||||
static const char *kDevPerfLevelHighStr = "high";
|
||||
static const char *kDevPerfLevelManualStr = "manual";
|
||||
static const char *kDevPerfLevelUnknownStr = "unknown";
|
||||
|
||||
static const std::map<DevInfoTypes, const char *> kDevAttribNameMap = {
|
||||
{kDevPerfLevel, kDevPerfLevelFName},
|
||||
@@ -70,6 +77,15 @@ static const std::map<DevInfoTypes, const char *> kDevAttribNameMap = {
|
||||
{kDevDevID, kDevDevIDFName},
|
||||
{kDevGPUMClk, kDevGPUMClkFName},
|
||||
{kDevGPUSClk, kDevGPUSClkFName},
|
||||
{kDevPowerProfileMode, kDevPowerProfileModeName},
|
||||
};
|
||||
|
||||
static const std::map<rsmi_dev_perf_level, const char *> kDevPerfLvlMap = {
|
||||
{RSMI_DEV_PERF_LEVEL_AUTO, kDevPerfLevelAutoStr},
|
||||
{RSMI_DEV_PERF_LEVEL_LOW, kDevPerfLevelLowStr},
|
||||
{RSMI_DEV_PERF_LEVEL_HIGH, kDevPerfLevelHighStr},
|
||||
{RSMI_DEV_PERF_LEVEL_MANUAL, kDevPerfLevelManualStr},
|
||||
{RSMI_DEV_PERF_LEVEL_UNKNOWN, kDevPerfLevelUnknownStr},
|
||||
};
|
||||
|
||||
static bool isRegularFile(std::string fname) {
|
||||
@@ -78,6 +94,10 @@ static bool isRegularFile(std::string fname) {
|
||||
return S_ISREG(file_stat.st_mode);
|
||||
}
|
||||
|
||||
#define RET_IF_NONZERO(X) { \
|
||||
if (X) return X; \
|
||||
}
|
||||
|
||||
Device::Device(std::string p) : path_(p) {
|
||||
monitor_ = nullptr;
|
||||
}
|
||||
@@ -94,18 +114,84 @@ int Device::readDevInfoStr(DevInfoTypes type, std::string *retStr) {
|
||||
tempPath += "/device/";
|
||||
tempPath += kDevAttribNameMap.at(type);
|
||||
|
||||
if (!isRegularFile(tempPath)) {
|
||||
return EISDIR;
|
||||
}
|
||||
|
||||
std::ifstream fs;
|
||||
fs.open(tempPath);
|
||||
|
||||
if (!fs.is_open() || !isRegularFile(tempPath)) {
|
||||
return -1;
|
||||
if (!fs.is_open()) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
fs >> *retStr;
|
||||
fs.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::writeDevInfoStr(DevInfoTypes type, std::string valStr) {
|
||||
auto tempPath = path_;
|
||||
tempPath += "/device/";
|
||||
tempPath += kDevAttribNameMap.at(type);
|
||||
|
||||
std::ofstream fs;
|
||||
fs.open(tempPath);
|
||||
|
||||
if (!isRegularFile(tempPath)) {
|
||||
return EISDIR;
|
||||
}
|
||||
|
||||
if (!fs.is_open()) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
fs << valStr;
|
||||
fs.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::writeDevInfo(DevInfoTypes type, uint64_t val) {
|
||||
switch (type) {
|
||||
// The caller is responsible for making sure "val" is within a valid range
|
||||
case kDevOverDriveLevel: // integer between 0 and 20
|
||||
case kDevPowerProfileMode:
|
||||
return writeDevInfoStr(type, std::to_string(val));
|
||||
break;
|
||||
|
||||
case kDevPerfLevel: // string: "auto", "low", "high", "manual"
|
||||
return writeDevInfoStr(type,
|
||||
kDevPerfLvlMap.at((rsmi_dev_perf_level)val));
|
||||
break;
|
||||
|
||||
case kDevGPUMClk: // integer (index within num-freq range)
|
||||
case kDevGPUSClk: // integer (index within num-freq range)
|
||||
case kDevDevID: // string (read-only)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Device::writeDevInfo(DevInfoTypes type, std::string val) {
|
||||
switch (type) {
|
||||
case kDevGPUMClk:
|
||||
case kDevGPUSClk:
|
||||
return writeDevInfoStr(type, val);
|
||||
|
||||
case kDevOverDriveLevel:
|
||||
case kDevPerfLevel:
|
||||
case kDevDevID:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Device::readDevInfoMultiLineStr(DevInfoTypes type,
|
||||
std::vector<std::string> *retVec) {
|
||||
auto tempPath = path_;
|
||||
@@ -121,7 +207,7 @@ int Device::readDevInfoMultiLineStr(DevInfoTypes type,
|
||||
|
||||
|
||||
if (!isRegularFile(tempPath)) {
|
||||
return -1;
|
||||
return EISDIR;
|
||||
}
|
||||
|
||||
while (std::getline(fs, line)) {
|
||||
@@ -134,19 +220,18 @@ int Device::readDevInfo(DevInfoTypes type, uint32_t *val) {
|
||||
assert(val != nullptr);
|
||||
|
||||
std::string tempStr;
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case kDevDevID:
|
||||
if (readDevInfoStr(type, &tempStr)) {
|
||||
return -1;
|
||||
}
|
||||
ret = readDevInfoStr(type, &tempStr);
|
||||
RET_IF_NONZERO(ret);
|
||||
*val = std::stoi(tempStr, 0, 16);
|
||||
break;
|
||||
|
||||
case kDevOverDriveLevel:
|
||||
if (readDevInfoStr(type, &tempStr)) {
|
||||
return -1;
|
||||
}
|
||||
ret = readDevInfoStr(type, &tempStr);
|
||||
RET_IF_NONZERO(ret);
|
||||
*val = std::stoi(tempStr, 0);
|
||||
break;
|
||||
|
||||
@@ -162,9 +247,8 @@ int Device::readDevInfo(DevInfoTypes type, std::vector<std::string> *val) {
|
||||
switch (type) {
|
||||
case kDevGPUMClk:
|
||||
case kDevGPUSClk:
|
||||
if (readDevInfoMultiLineStr(type, val)) {
|
||||
return -1;
|
||||
}
|
||||
case kDevPowerProfileMode:
|
||||
return readDevInfoMultiLineStr(type, val);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -181,9 +265,7 @@ int Device::readDevInfo(DevInfoTypes type, std::string *val) {
|
||||
case kDevPerfLevel:
|
||||
case kDevOverDriveLevel:
|
||||
case kDevDevID:
|
||||
if (readDevInfoStr(type, val)) {
|
||||
return -1;
|
||||
}
|
||||
return readDevInfoStr(type, val);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -192,5 +274,6 @@ int Device::readDevInfo(DevInfoTypes type, std::string *val) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef RET_IF_NONZERO
|
||||
} // namespace smi
|
||||
} // namespace amd
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
#include <dirent.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
@@ -55,12 +57,15 @@
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <cerrno>
|
||||
|
||||
#include "rocm_smi/rocm_smi.h"
|
||||
#include "rocm_smi/rocm_smi_main.h"
|
||||
|
||||
static const char *kPathDRMRoot = "/sys/class/drm";
|
||||
static const char *kPathHWMonRoot = "/sys/class/hwmon";
|
||||
static const char *kPathPowerRoot = "/sys/kernel/debug/dri";
|
||||
|
||||
static const char *kDeviceNamePrefix = "card";
|
||||
|
||||
static const char *kAMDMonitorTypes[] = {"radeon", "amdgpu", ""};
|
||||
@@ -73,6 +78,14 @@ static bool FileExists(char const *filename) {
|
||||
return (stat(filename, &buf) == 0);
|
||||
}
|
||||
|
||||
static uint32_t GetDeviceIndex(const std::string s) {
|
||||
std::string t = s;
|
||||
size_t tmp = t.find_last_not_of("0123456789");
|
||||
t.erase(0, tmp+1);
|
||||
|
||||
return stoi(t);
|
||||
}
|
||||
|
||||
// Return 0 if same file, 1 if not, and -1 for error
|
||||
static int SameFile(const std::string fileA, const std::string fileB) {
|
||||
struct stat aStat;
|
||||
@@ -104,15 +117,40 @@ static int SameDevice(const std::string fileA, const std::string fileB) {
|
||||
return SameFile(fileA + "/device", fileB + "/device");
|
||||
}
|
||||
|
||||
void ShowAllTemperatures();
|
||||
// Call-back function to append to a vector of Devices
|
||||
static bool GetMonitorDevices(const std::shared_ptr<amd::smi::Device> &d,
|
||||
void *p) {
|
||||
std::string val_str;
|
||||
|
||||
RocmSMI::RocmSMI() {
|
||||
assert(p != nullptr);
|
||||
|
||||
std::vector<std::shared_ptr<amd::smi::Device>> *device_list =
|
||||
reinterpret_cast<std::vector<std::shared_ptr<amd::smi::Device>> *>(p);
|
||||
|
||||
if (d->monitor() != nullptr) {
|
||||
device_list->push_back(d);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<amd::smi::Device>> RocmSMI::s_monitor_devices;
|
||||
|
||||
RocmSMI::RocmSMI(void) {
|
||||
auto i = 0;
|
||||
|
||||
while (std::string(kAMDMonitorTypes[i]) != "") {
|
||||
amd_monitor_types_.insert(kAMDMonitorTypes[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
// DiscoverDevices() will seach for devices and monitors and update internal
|
||||
// data structures.
|
||||
DiscoverDevices();
|
||||
|
||||
// IterateSMIDevices will iterate through all the known devices and apply
|
||||
// the provided call-back to each device found.
|
||||
IterateSMIDevices(GetMonitorDevices,
|
||||
reinterpret_cast<void *>(&s_monitor_devices));
|
||||
}
|
||||
|
||||
RocmSMI::~RocmSMI() {
|
||||
@@ -120,6 +158,13 @@ RocmSMI::~RocmSMI() {
|
||||
monitors_.clear();
|
||||
}
|
||||
|
||||
RocmSMI& RocmSMI::getInstance(void) {
|
||||
// Assume c++11 or greater. static objects will be created by only 1 thread
|
||||
// and creation will be thread-safe.
|
||||
static RocmSMI singleton;
|
||||
return singleton;
|
||||
}
|
||||
|
||||
void
|
||||
RocmSMI::AddToDeviceList(std::string dev_name) {
|
||||
auto ret = 0;
|
||||
@@ -137,22 +182,31 @@ RocmSMI::AddToDeviceList(std::string dev_name) {
|
||||
|
||||
if (ret == 0) {
|
||||
dev->set_monitor(*m);
|
||||
|
||||
m = monitors_.erase(m);
|
||||
break;
|
||||
} else {
|
||||
assert(ret == 1);
|
||||
++m;
|
||||
}
|
||||
}
|
||||
|
||||
std::string d_name = dev_name;
|
||||
uint32_t d_index = GetDeviceIndex(d_name);
|
||||
dev->set_index(d_index);
|
||||
|
||||
devices_.push_back(dev);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint32_t RocmSMI::DiscoverDevices(void) {
|
||||
auto ret = 0;
|
||||
|
||||
// If this gets called more than once, clear previous findings.
|
||||
devices_.clear();
|
||||
monitors_.clear();
|
||||
|
||||
ret = DiscoverAMDMonitors();
|
||||
|
||||
if (ret) {
|
||||
@@ -219,9 +273,71 @@ uint32_t RocmSMI::DiscoverAMDMonitors(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since these sysfs files require sudo access, we won't discover them
|
||||
// with rsmi_init() (and thus always require the user to use "sudo".
|
||||
// Instead, we will discover() all the power monitors the first time
|
||||
// they are needed and then check for previous discovery on each subsequent
|
||||
// call.
|
||||
uint32_t RocmSMI::DiscoverAMDPowerMonitors(bool force_update) {
|
||||
if (force_update) {
|
||||
power_mons_.clear();
|
||||
}
|
||||
|
||||
if (power_mons_.size() != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
auto dri_dir = opendir(kPathPowerRoot);
|
||||
|
||||
if (dri_dir == nullptr) {
|
||||
return errno;
|
||||
}
|
||||
auto dentry = readdir(dri_dir);
|
||||
|
||||
std::string mon_name;
|
||||
std::string tmp;
|
||||
|
||||
while (dentry != nullptr) {
|
||||
if (dentry->d_name[0] == '.') {
|
||||
dentry = readdir(dri_dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
mon_name = kPathPowerRoot;
|
||||
mon_name += "/";
|
||||
mon_name += dentry->d_name;
|
||||
tmp = mon_name + "/amdgpu_pm_info";
|
||||
|
||||
if (FileExists(tmp.c_str())) {
|
||||
std::shared_ptr<PowerMon> mon =
|
||||
std::shared_ptr<PowerMon>(new PowerMon(mon_name));
|
||||
power_mons_.push_back(mon);
|
||||
mon->set_dev_index(GetDeviceIndex(dentry->d_name));
|
||||
}
|
||||
dentry = readdir(dri_dir);
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
if (closedir(dri_dir)) {
|
||||
power_mons_.clear();
|
||||
return errno;
|
||||
}
|
||||
|
||||
for (auto m : power_mons_) {
|
||||
for (auto d : devices_) {
|
||||
if (m->dev_index() == d->index()) {
|
||||
d->set_power_monitor(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RocmSMI::IterateSMIDevices(
|
||||
std::function<bool(std::shared_ptr<Device>&, void *)> func, void *p) {
|
||||
|
||||
if (func == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,9 +50,11 @@
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "rocm_smi/rocm_smi_main.h"
|
||||
#include "rocm_smi/rocm_smi_monitor.h"
|
||||
#include "rocm_smi/rocm_smi_utils.h"
|
||||
|
||||
namespace amd {
|
||||
namespace smi {
|
||||
@@ -63,16 +65,53 @@ struct MonitorNameEntry {
|
||||
};
|
||||
|
||||
|
||||
static const char *kMonTempFName = "temp1_input";
|
||||
static const char *kMonFanSpeedFName = "pwm1";
|
||||
static const char *kMonMaxFanSpeedFName = "pwm1_max";
|
||||
static const char *kMonTempFName = "temp#_input";
|
||||
static const char *kMonFanSpeedFName = "pwm#";
|
||||
static const char *kMonMaxFanSpeedFName = "pwm#_max";
|
||||
static const char *kMonFanRPMsName = "fan#_input";
|
||||
static const char *kMonFanControlEnableName = "pwm#_enable";
|
||||
static const char *kMonNameFName = "name";
|
||||
static const char *kMonPowerCapName = "power#_cap";
|
||||
static const char *kMonPowerCapMaxName = "power#_cap_max";
|
||||
static const char *kMonPowerCapMinName = "power#_cap_min";
|
||||
|
||||
static const char *kMonTempMaxName = "temp#_max";
|
||||
static const char *kMonTempMinName = "temp#_min";
|
||||
static const char *kMonTempMaxHystName = "temp#_max_hyst";
|
||||
static const char *kMonTempMinHystName = "temp#_min_hyst";
|
||||
static const char *kMonTempCriticalName = "temp#_crit";
|
||||
static const char *kMonTempCriticalHystName = "temp#_crit_hyst";
|
||||
static const char *kMonTempEmergencyName = "temp#_emergency";
|
||||
static const char *kMonTempEmergencyHystName = "temp#_emergency_hyst";
|
||||
static const char *kMonTempCritMinName = "temp#_lcrit";
|
||||
static const char *kMonTempCritMinHystName = "temp#_lcrit_hyst";
|
||||
static const char *kMonTempOffsetName = "temp#_offset";
|
||||
static const char *kMonTempLowestName = "temp#_lowest";
|
||||
static const char *kMonTempHighestName = "temp#_highest";
|
||||
|
||||
static const std::map<MonitorTypes, const char *> kMonitorNameMap = {
|
||||
{kMonName, kMonNameFName},
|
||||
{kMonTemp, kMonTempFName},
|
||||
{kMonFanSpeed, kMonFanSpeedFName},
|
||||
{kMonMaxFanSpeed, kMonMaxFanSpeedFName}
|
||||
{kMonFanCntrlEnable, kMonFanControlEnableName},
|
||||
{kMonMaxFanSpeed, kMonMaxFanSpeedFName},
|
||||
{kMonFanRPMs, kMonFanRPMsName},
|
||||
{kMonPowerCap, kMonPowerCapName},
|
||||
{kMonPowerCapMax, kMonPowerCapMaxName},
|
||||
{kMonPowerCapMin, kMonPowerCapMinName},
|
||||
{kMonTempMax, kMonTempMaxName},
|
||||
{kMonTempMin, kMonTempMinName},
|
||||
{kMonTempMaxHyst, kMonTempMaxHystName},
|
||||
{kMonTempMinHyst, kMonTempMinHystName},
|
||||
{kMonTempCritical, kMonTempCriticalName},
|
||||
{kMonTempCriticalHyst, kMonTempCriticalHystName},
|
||||
{kMonTempEmergency, kMonTempEmergencyName},
|
||||
{kMonTempEmergencyHyst, kMonTempEmergencyHystName},
|
||||
{kMonTempCritMin, kMonTempCritMinName},
|
||||
{kMonTempCritMinHyst, kMonTempCritMinHystName},
|
||||
{kMonTempOffset, kMonTempOffsetName},
|
||||
{kMonTempLowest, kMonTempLowestName},
|
||||
{kMonTempHighest, kMonTempHighestName},
|
||||
};
|
||||
|
||||
Monitor::Monitor(std::string path) : path_(path) {
|
||||
@@ -80,55 +119,34 @@ Monitor::Monitor(std::string path) : path_(path) {
|
||||
Monitor::~Monitor(void) {
|
||||
}
|
||||
|
||||
int Monitor::readMonitorStr(MonitorTypes type, std::string *retStr) {
|
||||
auto tempPath = path_;
|
||||
std::string
|
||||
Monitor::MakeMonitorPath(MonitorTypes type, int32_t sensor_id) {
|
||||
std::string tempPath = path_;
|
||||
std::string fn = kMonitorNameMap.at(type);
|
||||
|
||||
assert(retStr != nullptr);
|
||||
std::replace(fn.begin(), fn.end(), '#', static_cast<char>('0' + sensor_id));
|
||||
|
||||
tempPath += "/";
|
||||
tempPath += kMonitorNameMap.at(type);
|
||||
tempPath += fn;
|
||||
|
||||
std::ifstream fs;
|
||||
fs.open(tempPath);
|
||||
|
||||
if (!fs.is_open()) {
|
||||
return -1;
|
||||
}
|
||||
fs >> *retStr;
|
||||
fs.close();
|
||||
|
||||
return 0;
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
int Monitor::readMonitor(MonitorTypes type, uint32_t *val) {
|
||||
assert(val != nullptr);
|
||||
|
||||
std::string tempStr;
|
||||
|
||||
switch (type) {
|
||||
case kMonTemp: // Temperature in millidegrees
|
||||
case kMonFanSpeed:
|
||||
case kMonMaxFanSpeed:
|
||||
if (readMonitorStr(type, &tempStr)) {
|
||||
return -1;
|
||||
}
|
||||
*val = std::stoi(tempStr);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
int Monitor::writeMonitor(MonitorTypes type, uint32_t sensor_id,
|
||||
std::string val) {
|
||||
std::string sysfs_path = MakeMonitorPath(type, sensor_id);
|
||||
return WriteSysfsStr(sysfs_path, val);
|
||||
}
|
||||
|
||||
// This string version should work for all valid monitor types
|
||||
int Monitor::readMonitor(MonitorTypes type, std::string *val) {
|
||||
int Monitor::readMonitor(MonitorTypes type, uint32_t sensor_id,
|
||||
std::string *val) {
|
||||
assert(val != nullptr);
|
||||
|
||||
if (readMonitorStr(type, val)) {
|
||||
return -1;
|
||||
}
|
||||
std::string temp_str;
|
||||
std::string sysfs_path = MakeMonitorPath(type, sensor_id);
|
||||
|
||||
return 0;
|
||||
return ReadSysfsStr(sysfs_path, val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* =============================================================================
|
||||
* ROC Runtime Conformance Release License
|
||||
* =============================================================================
|
||||
* The University of Illinois/NCSA
|
||||
* Open Source License (NCSA)
|
||||
*
|
||||
* Copyright (c) 2017, Advanced Micro Devices, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Developed by:
|
||||
*
|
||||
* AMD Research and AMD ROC Software Development
|
||||
*
|
||||
* Advanced Micro Devices, Inc.
|
||||
*
|
||||
* www.amd.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal with the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimers.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimers in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the names of <Name of Development Group, Name of Institution>,
|
||||
* nor the names of its contributors may be used to endorse or promote
|
||||
* products derived from this Software without specific prior written
|
||||
* permission.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS WITH THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "rocm_smi/rocm_smi_main.h"
|
||||
#include "rocm_smi/rocm_smi_monitor.h"
|
||||
#include "rocm_smi/rocm_smi_utils.h"
|
||||
|
||||
namespace amd {
|
||||
namespace smi {
|
||||
|
||||
|
||||
static const char *kPowerMonPMName = "amdgpu_pm_info";
|
||||
|
||||
// Using this map in case we add other files from dri directory to parse.
|
||||
static const std::map<PowerMonTypes, const char *> kMonitorNameMap = {
|
||||
{kPowerMaxGPUPower, kPowerMonPMName},
|
||||
{kPowerAveGPUPower, kPowerMonPMName},
|
||||
};
|
||||
|
||||
PowerMon::PowerMon(std::string path) : path_(path) {
|
||||
}
|
||||
PowerMon::~PowerMon(void) {
|
||||
}
|
||||
|
||||
static int parse_power_str(std::string s, PowerMonTypes type, uint64_t *val) {
|
||||
std::stringstream ss(s);
|
||||
std::string ln;
|
||||
std::string search_str;
|
||||
|
||||
assert(val != nullptr);
|
||||
|
||||
switch (type) {
|
||||
case kPowerMaxGPUPower:
|
||||
search_str = "(max GPU)";
|
||||
break;
|
||||
|
||||
case kPowerAveGPUPower:
|
||||
search_str = "(average GPU)";
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"Invalid search Power type requested");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
while (std::getline(ss, ln)) {
|
||||
if (ln.rfind(search_str) != std::string::npos) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
ss.clear();
|
||||
std::stringstream l_ss;
|
||||
|
||||
l_ss << ln;
|
||||
|
||||
double num_units;
|
||||
std::string sz;
|
||||
|
||||
switch (type) {
|
||||
case kPowerMaxGPUPower:
|
||||
case kPowerAveGPUPower:
|
||||
l_ss >> num_units;
|
||||
l_ss >> sz;
|
||||
assert(sz == "W"); // We only expect Watts at this time
|
||||
*val = num_units * 1000; // Convert Watts to milliwatts
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"Invalid search Power type requested");
|
||||
return EINVAL;
|
||||
}
|
||||
ss.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PowerMon::readPowerValue(PowerMonTypes type, uint64_t *power) {
|
||||
auto tempPath = path_;
|
||||
std::string fstr;
|
||||
|
||||
assert(power != nullptr);
|
||||
|
||||
tempPath += "/";
|
||||
tempPath += kMonitorNameMap.at(type);
|
||||
int ret = ReadSysfsStr(tempPath, &fstr);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return parse_power_str(fstr, type, power);
|
||||
}
|
||||
|
||||
} // namespace smi
|
||||
} // namespace amd
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* =============================================================================
|
||||
* ROC Runtime Conformance Release License
|
||||
* =============================================================================
|
||||
* The University of Illinois/NCSA
|
||||
* Open Source License (NCSA)
|
||||
*
|
||||
* Copyright (c) 2018, Advanced Micro Devices, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Developed by:
|
||||
*
|
||||
* AMD Research and AMD ROC Software Development
|
||||
*
|
||||
* Advanced Micro Devices, Inc.
|
||||
*
|
||||
* www.amd.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal with the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimers.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimers in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the names of <Name of Development Group, Name of Institution>,
|
||||
* nor the names of its contributors may be used to endorse or promote
|
||||
* products derived from this Software without specific prior written
|
||||
* permission.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS WITH THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace amd {
|
||||
namespace smi {
|
||||
|
||||
int WriteSysfsStr(std::string path, std::string val) {
|
||||
std::ofstream fs;
|
||||
int ret = 0;
|
||||
|
||||
fs.open(path);
|
||||
if (!fs.is_open()) {
|
||||
ret = errno;
|
||||
errno = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
fs << val;
|
||||
fs.close();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ReadSysfsStr(std::string path, std::string *retStr) {
|
||||
std::stringstream ss;
|
||||
int ret = 0;
|
||||
|
||||
assert(retStr != nullptr);
|
||||
|
||||
std::ifstream fs;
|
||||
fs.open(path);
|
||||
|
||||
if (!fs.is_open()) {
|
||||
ret = errno;
|
||||
errno = 0;
|
||||
return ret;
|
||||
}
|
||||
ss << fs.rdbuf();
|
||||
fs.close();
|
||||
|
||||
*retStr = ss.str();
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace smi
|
||||
} // namespace amd
|
||||
Reference in New Issue
Block a user