blob: d068e7eedafcf159d24501aafa416709c2eddfa4 [file]
#include "tlbmc/sensors/amd_cpu_sensor.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <fstream>
#include <limits>
#include <memory>
#include <optional>
#include <regex> // NOLINT
#include <string>
#include <utility>
#include <vector>
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "boost/filesystem.hpp" // NOLINT
#include "g3/macros.h"
#include "tlbmc/central_config/config.h"
#include "entity_common_config.pb.h"
#include "psu_sensor_config.pb.h"
#include "sensor_instance_properties.pb.h"
#include "tlbmc/hal/sysfs/i3c.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/ixc_hwmon_based_sensor.h"
#include "tlbmc/sensors/psu_sensor.h"
#include "tlbmc/sensors/sensor.h"
#include "tlbmc/time/time.h"
namespace milotic_tlbmc {
constexpr absl::Duration kApmlDriverReloadInterval = absl::Seconds(5);
constexpr auto kSupportedAmdCpuSensorTypes = std::to_array<PsuSensorType>({
// go/keep-sorted start numeric=yes
PsuSensorType::PSU_SENSOR_TYPE15_SBTSI_I3C,
PsuSensorType::PSU_SENSOR_TYPE22_SBRMI_I3C,
// go/keep-sorted end
});
absl::StatusOr<std::vector<std::shared_ptr<Sensor>>> AmdCpuSensor::Create(
const PsuSensorConfig& config,
const std::shared_ptr<boost::asio::io_context>& io_context,
I3cSysfs& i3c_sysfs, std::optional<NotificationCb> on_batch_notify,
AmdCpuSensorSysfsPaths paths) {
DLOG(INFO) << "Creating AmdCpuSensor for device: " << absl::StrCat(config);
const HalCommonConfig& hal_common_config = config.hal_common_config();
ECCLESIA_ASSIGN_OR_RETURN(std::string_view driver_name,
PsuSensor::GetDriverName(config.type()));
std::vector<std::shared_ptr<Sensor>> sensors;
auto make_amd_sensor = [&config, &io_context, &on_batch_notify, &i3c_sysfs,
&paths](const std::string& label,
const std::string& input_dev_path,
const SensorInstanceProperties& props)
-> absl::StatusOr<std::shared_ptr<AmdCpuSensor>> {
ECCLESIA_ASSIGN_OR_RETURN(PsuSensor::ReadingProperties reading_properties,
PsuSensor::GetPsuSensorProperties(label));
SensorInstanceProperties mutable_instance_properties = props;
mutable_instance_properties.set_unit(reading_properties.sensor_unit);
mutable_instance_properties.set_name(PsuSensor::GetPsuSensorKey(
config.entity_common_config(), props, reading_properties));
*mutable_instance_properties.mutable_reading_ranges() =
PsuSensor::GetReadingRangeConfigs(props.reading_ranges(),
reading_properties);
if (!props.reading_transform_config().has_scale()) {
mutable_instance_properties.mutable_reading_transform_config()->set_scale(
std::pow(10, reading_properties.scale));
}
if (!props.has_related_item()) {
mutable_instance_properties.mutable_related_item()->set_type(
RESOURCE_TYPE_BOARD);
mutable_instance_properties.mutable_related_item()->set_id(
config.entity_common_config().board_config_key());
}
if (!props.reading_transform_config().has_offset()) {
mutable_instance_properties.mutable_reading_transform_config()
->set_offset(reading_properties.offset);
}
return std::make_shared<AmdCpuSensor>(
Token(), config.type(), input_dev_path, label,
config.hal_common_config(), config.entity_common_config(),
mutable_instance_properties, io_context, on_batch_notify, i3c_sysfs,
paths);
};
absl::StatusOr<boost::filesystem::path> hwmon_path;
hwmon_path = IXcHwmonBasedSensor::CreateIXcDeviceAndReturnsHwmonPath(
hal_common_config, driver_name, i3c_sysfs);
if (!hwmon_path.ok()) {
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure() &&
config.entity_common_config().config_detected()) {
return hwmon_path.status();
}
std::string error_message =
absl::StrFormat("Failed to create AmdCpuSensor (NONFATAL): %s: %s",
absl::StatusCodeToString(hwmon_path.status().code()),
hwmon_path.status().message());
for (const auto& [label, properties] :
config.label_to_sensor_instance_properties()) {
ECCLESIA_ASSIGN_OR_RETURN(std::shared_ptr<AmdCpuSensor> amd_sensor,
make_amd_sensor(label, "", properties));
State state;
// Initialize with STATUS_STALE so that the sensor is added to the
// polling queue when it becomes ready.
state.set_status(STATUS_STALE);
state.set_status_message(error_message);
amd_sensor->UpdateState(std::move(state));
sensors.push_back(amd_sensor);
}
return sensors;
}
// Now find all labels and their input files.
// Example hwmon folder contents:
// SBTSI:
// # /sys/bus/i3c/devices/6-118/hwmon/hwmon51/
// device name of_node power subsystem
// temp1_input temp1_max temp1_min uevent
// SBRMI:
// # ls /sys/bus/i3c/devices/6-1118/hwmon/hwmon53/
// device of_node power1_cap power1_input
// temp10_input temp12_input temp14_input temp16_input
// temp2_input temp4_input temp6_input temp8_input uevent
// name power power1_cap_max subsystem
// temp11_input temp13_input temp15_input temp1_input
// temp3_input temp5_input temp7_input temp9_input
absl::flat_hash_map<std::string, std::string> label_to_input_file;
for (const auto& entry : boost::filesystem::directory_iterator(*hwmon_path)) {
if (entry.is_regular_file()) {
std::string input_file = entry.path().filename().string();
PsuSensorFileInfo file_info = PsuSensor::GetPsuSensorFileInfo(input_file);
// Ignore sensors that we don't support
if (file_info.file_type == PsuSensorFileType::kUnknown) {
continue;
}
std::string label = PsuSensor::ApplyLabelModifications(
file_info.file_prefix, file_info.file_type);
label_to_input_file[label] = input_file;
}
}
for (const auto& [label, properties] :
config.label_to_sensor_instance_properties()) {
auto label_to_input_file_it = label_to_input_file.find(label);
if (label_to_input_file_it == label_to_input_file.end()) {
std::string error_message = absl::Substitute(
"Failed to find $0 in hwmon folder $1", label, hwmon_path->string());
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure()) {
return absl::InvalidArgumentError(error_message);
}
ECCLESIA_ASSIGN_OR_RETURN(std::shared_ptr<AmdCpuSensor> amd_sensor,
make_amd_sensor(label, "", properties));
State state;
if (!config.entity_common_config().config_detected()) {
state.set_status(STATUS_CREATION_PENDING);
} else {
state.set_status(STATUS_CREATION_FAILED);
}
state.set_status_message(
absl::StrFormat("Failed to create AmdCpuSensor (NONFATAL): %s: %s",
absl::StatusCodeToString(absl::StatusCode::kNotFound),
error_message));
amd_sensor->UpdateState(std::move(state));
sensors.push_back(amd_sensor);
continue;
}
const std::string& input_file = label_to_input_file_it->second;
boost::filesystem::path input_dev_path = *hwmon_path / input_file;
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<AmdCpuSensor> amd_sensor,
make_amd_sensor(label, input_dev_path.string(), properties));
sensors.push_back(amd_sensor);
}
return sensors;
}
bool AmdCpuSensor::IsAmdCpuSensor(PsuSensorType psu_sensor_type) {
const auto* it =
std::lower_bound(kSupportedAmdCpuSensorTypes.begin(),
kSupportedAmdCpuSensorTypes.end(), psu_sensor_type);
return it != kSupportedAmdCpuSensorTypes.end() && (*it == psu_sensor_type);
}
void AmdCpuSensor::RefreshHwmonDevpath() {
absl::StatusOr<std::string_view> driver_name =
PsuSensor::GetDriverName(sensor_type_);
if (!driver_name.ok()) {
LOG(ERROR) << "AmdCpuSensor (" << label_
<< ") failed to get driver name: " << driver_name.status();
return;
}
if (i3c_sysfs_ == nullptr) {
LOG(ERROR) << "AmdCpuSensor (" << label_ << ") i3c_sysfs_ is null";
return;
}
absl::StatusOr<boost::filesystem::path> hwmon =
IXcHwmonBasedSensor::CreateIXcDeviceAndReturnsHwmonPath(
sensor_attributes_static_.hal_common_config(), *driver_name,
*i3c_sysfs_);
if (!hwmon.ok()) {
LOG(ERROR) << "AmdCpuSensor (" << label_
<< ") failed to create/find hwmon path: " << hwmon.status();
return;
}
LOG(INFO) << "AmdCpuSensor (" << label_
<< ") successfully found/created hwmon path: " << hwmon->string();
bool found_match = false;
for (const auto& entry : boost::filesystem::directory_iterator(*hwmon)) {
if (!entry.is_regular_file()) {
continue;
}
std::string input_file = entry.path().filename().string();
PsuSensorFileInfo file_info = PsuSensor::GetPsuSensorFileInfo(input_file);
if (file_info.file_type == PsuSensorFileType::kUnknown) {
continue;
}
std::string file_label = PsuSensor::ApplyLabelModifications(
file_info.file_prefix, file_info.file_type);
LOG(INFO) << "AmdCpuSensor (" << label_
<< "): Check hwmon path: " << hwmon->string()
<< ", checking file: " << input_file
<< ", computed label: " << file_label;
if (file_label == label_) {
std::string new_input_path = (*hwmon / input_file).string();
LOG(INFO) << "AmdCpuSensor (" << label_
<< "): Found match! Setting input dev path to: "
<< new_input_path;
SetDevpath(new_input_path).IgnoreError();
SetInputDevicePath(new_input_path);
SensorValue initial_value;
*initial_value.mutable_timestamp() = Now();
initial_value.set_reading(std::numeric_limits<double>::quiet_NaN());
StoreSensorData(
std::make_shared<const SensorValue>(std::move(initial_value)));
State state;
state.set_status(STATUS_READY);
UpdateState(std::move(state));
found_match = true;
break;
}
}
if (!found_match) {
LOG(ERROR)
<< "AmdCpuSensor (" << label_
<< ") could not find a matching input file for label in hwmon path: "
<< hwmon->string();
}
}
void AmdCpuSensor::PowerOffToOnCallback(bool setup_run) {
if (setup_run) {
ready_for_read_ = true;
return;
}
absl::Status status =
ReloadDriver(sensor_attributes_static_.hal_common_config().bus());
if (!status.ok()) {
LOG(WARNING) << "Failed to reload APML driver: " << status;
} else {
RefreshHwmonDevpath();
ready_for_read_ = true;
}
}
void AmdCpuSensor::PowerOnToOffCallback(bool setup_run) {
if (setup_run) {
ready_for_read_ = false;
return;
}
absl::Status status =
UnbindI3cBusDevices(sensor_attributes_static_.hal_common_config().bus());
if (!status.ok()) {
LOG(WARNING) << "Failed to unbind APML devices: " << status;
}
ready_for_read_ = false;
}
void AmdCpuSensor::OSInactiveToStandbyCallback(bool setup_run) {
if (setup_run) {
ready_for_read_ = true;
return;
}
absl::Status status =
ReloadDriver(sensor_attributes_static_.hal_common_config().bus());
if (!status.ok()) {
LOG(WARNING) << "Failed to reload APML driver: " << status;
} else {
ready_for_read_ = true;
}
}
void AmdCpuSensor::OSStandbyToInactiveCallback(bool setup_run) {
if (setup_run) {
ready_for_read_ = false;
return;
}
absl::Status status =
UnbindI3cBusDevices(sensor_attributes_static_.hal_common_config().bus());
if (!status.ok()) {
LOG(WARNING) << "Failed to unbind APML devices: " << status;
}
ready_for_read_ = false;
}
AmdCpuSensor::SharedState& AmdCpuSensor::GetSharedState() {
static absl::NoDestructor<SharedState> state;
return *state;
}
void AmdCpuSensor::ResetSharedStateForTest() {
auto& shared_state = GetSharedState();
absl::MutexLock lock(shared_state.global_mutex);
shared_state.last_reload.clear();
shared_state.bus_mutexes.clear();
}
absl::Status AmdCpuSensor::ReloadDriver(uint64_t bus) {
auto& shared_state = GetSharedState();
absl::Mutex* bus_mutex = nullptr;
// Step 1: Check cooldown cache under global lock, and get/create per-bus
// mutex.
{
absl::MutexLock lock(shared_state.global_mutex);
auto it = shared_state.last_reload.find(bus);
if (it != shared_state.last_reload.end()) {
absl::Duration elapsed = absl::Now() - it->second.time;
if (elapsed < kApmlDriverReloadInterval) {
LOG(INFO) << "Skipping redundant APML driver reload for bus " << bus
<< " (last reload was " << elapsed << " ago)";
return it->second.status;
}
}
auto mutex_it = shared_state.bus_mutexes.find(bus);
if (mutex_it == shared_state.bus_mutexes.end()) {
mutex_it = shared_state.bus_mutexes
.try_emplace(bus, std::make_unique<absl::Mutex>())
.first;
}
bus_mutex = mutex_it->second.get();
}
// Step 2: Acquire per-bus lock to serialize reload for this specific bus
// only.
absl::MutexLock bus_lock(*bus_mutex);
// Step 3: Re-validate under global lock in case another thread on the same
// bus finished while we were waiting for bus_lock.
{
absl::MutexLock lock(shared_state.global_mutex);
auto it = shared_state.last_reload.find(bus);
if (it != shared_state.last_reload.end()) {
absl::Duration elapsed = absl::Now() - it->second.time;
if (elapsed < kApmlDriverReloadInterval) {
LOG(INFO) << "Skipping redundant APML driver reload for bus " << bus
<< " (last reload was " << elapsed << " ago)";
return it->second.status;
}
}
}
// Step 4: Perform blocking sysfs reload operations (different buses can run
// in parallel).
absl::Status status = UnbindI3cBusDevices(bus);
if (!status.ok()) {
LOG(ERROR) << "Failed to unbind I3C devices on bus " << bus
<< ". Warning: This may potentially cause a BMC crash. Status: "
<< status;
}
if (status.ok()) {
status = UnbindI3cBusController(bus);
if (!status.ok()) {
LOG(ERROR) << "Failed to unbind I3C controller on bus " << bus
<< ". Status: " << status;
}
}
if (status.ok()) {
status = BindI3cBusController(bus);
if (!status.ok()) {
LOG(ERROR) << "Failed to bind I3C controller on bus " << bus
<< ". Status: " << status;
}
}
// Step 5: Update cooldown cache under global lock.
{
absl::MutexLock lock(shared_state.global_mutex);
auto it = shared_state.last_reload.find(bus);
if (it != shared_state.last_reload.end()) {
it->second = {absl::Now(), status};
} else {
shared_state.last_reload.try_emplace(bus, absl::Now(), status);
}
}
return status;
}
absl::Status AmdCpuSensor::UnbindI3cBusDevices(uint64_t bus) {
LOG(INFO) << "UnbindI3cBusDevices called for bus " << bus;
std::vector<boost::filesystem::path> driver_dirs;
boost::system::error_code ec;
if (!boost::filesystem::exists(i3c_sysfs_drivers_path_, ec)) {
if (ec && ec != boost::system::errc::no_such_file_or_directory) {
return absl::InternalError(
absl::StrCat("Error checking exists for path '",
i3c_sysfs_drivers_path_, "': ", ec.message()));
}
LOG(INFO) << "I3C driver path does not exist: " << i3c_sysfs_drivers_path_;
return absl::OkStatus();
}
boost::filesystem::directory_iterator driver_filepath_iterator(
i3c_sysfs_drivers_path_, ec);
if (ec) {
LOG(ERROR) << "Error opening directory '" << i3c_sysfs_drivers_path_
<< "': " << ec.message();
}
for (const auto& entry : driver_filepath_iterator) {
if (boost::filesystem::is_directory(entry.path(), ec)) {
driver_dirs.push_back(entry.path());
}
if (ec) {
LOG(ERROR) << "Error checking is_directory for path '"
<< entry.path().string() << "': " << ec.message();
break;
}
}
std::string device_pattern = absl::StrCat(bus, "-[0-9a-f]+");
std::regex re(device_pattern);
for (const auto& driver_dir : driver_dirs) {
std::vector<std::string> devices_to_unbind;
boost::filesystem::path unbind_file_path = driver_dir / "unbind";
DLOG(INFO) << "Searching devices under: " << driver_dir.string();
boost::filesystem::directory_iterator device_filepath_iterator(driver_dir,
ec);
if (ec) {
LOG(ERROR) << "Error opening directory '" << driver_dir.string()
<< "': " << ec.message();
}
for (const auto& entry : device_filepath_iterator) {
std::string name = entry.path().filename().string();
if (std::regex_match(name, re)) {
devices_to_unbind.push_back(name);
DLOG(INFO) << "Matched device: " << name;
}
}
if (devices_to_unbind.empty()) {
DLOG(INFO) << "No i3c" << bus << " device found in "
<< driver_dir.filename().string();
continue;
}
for (const auto& device : devices_to_unbind) {
std::ofstream unbind_file(unbind_file_path.string());
if (!unbind_file.good()) {
return absl::InternalError(
absl::StrCat("Failed to open ", unbind_file_path.string()));
}
unbind_file << device << "\n";
unbind_file.flush();
if (!unbind_file.good()) {
return absl::InternalError(
absl::StrCat("Failed to write to ", unbind_file_path.string()));
}
unbind_file.close();
LOG(INFO) << "Unbound device " << device << " from driver "
<< driver_dir.filename().string();
}
absl::SleepFor(absl::Milliseconds(100));
boost::filesystem::directory_iterator remaining_device_filepath_iterator(
driver_dir, ec);
if (ec) {
LOG(ERROR) << "Error opening directory '" << driver_dir.string()
<< "': " << ec.message();
}
for (const auto& entry : remaining_device_filepath_iterator) {
std::string remain = entry.path().filename().string();
if (std::regex_match(remain, re)) {
LOG(ERROR) << "Device " << remain << " on I3C bus " << bus
<< " still remains under driver "
<< driver_dir.filename().string();
return absl::InternalError("Failed to unbind all devices");
}
}
}
LOG(INFO) << "UnbindI3cBusDevices succeeded for bus " << bus;
return absl::OkStatus();
}
absl::Status AmdCpuSensor::UnbindI3cBusController(uint64_t bus) {
absl::MutexLock lock(controller_mutex_);
// If the controller for this bus is already recorded as unbound, return OK.
auto it = unbound_i3c_controllers_.find(bus);
if (it != unbound_i3c_controllers_.end() && !it->second.empty()) {
LOG(INFO) << "Controller for I3C bus " << bus << " is already unbound.";
return absl::OkStatus();
}
std::string controller;
std::string controller_pattern = absl::StrCat("[0-9a-f]+\\.i3c", bus);
std::regex re(controller_pattern);
DLOG(INFO) << "Searching controller under: " << i3c_controller_driver_path_;
boost::system::error_code ec;
if (!boost::filesystem::exists(i3c_controller_driver_path_, ec)) {
if (ec && ec != boost::system::errc::no_such_file_or_directory) {
return absl::InternalError(
absl::StrCat("Error checking exists for path '",
i3c_controller_driver_path_, "': ", ec.message()));
}
LOG(INFO) << "I3C controller driver path does not exist: "
<< i3c_controller_driver_path_;
return absl::OkStatus();
}
boost::filesystem::directory_iterator controller_filepath_iterator(
i3c_controller_driver_path_, ec);
if (ec) {
LOG(ERROR) << "Error opening directory '" << i3c_controller_driver_path_
<< "': " << ec.message();
}
for (const auto& p : controller_filepath_iterator) {
std::string name = p.path().filename().string();
if (std::regex_match(name, re)) {
controller = name;
DLOG(INFO) << "Matched controller: " << controller;
break;
}
}
if (controller.empty()) {
return absl::NotFoundError(
absl::StrCat("No controller found for I3C bus ", bus));
}
boost::filesystem::path unbind_file_path =
absl::StrCat(i3c_controller_driver_path_, "unbind");
std::ofstream unbind_file(unbind_file_path.string());
if (!unbind_file.good()) {
return absl::InternalError(absl::StrCat("Failed to open unbind file: ",
unbind_file_path.string()));
}
unbind_file << controller << "\n";
unbind_file.flush();
if (!unbind_file.good()) {
return absl::InternalError(absl::StrCat("Failed to write to unbind file: ",
unbind_file_path.string()));
}
unbind_file.close();
unbound_i3c_controllers_[bus] = {controller};
LOG(INFO) << "Unbound controller " << controller;
absl::SleepFor(absl::Milliseconds(100));
boost::filesystem::directory_iterator remaining_controller_filepath_iterator(
i3c_controller_driver_path_, ec);
if (ec) {
LOG(ERROR) << "Error opening directory '" << i3c_controller_driver_path_
<< "': " << ec.message();
}
for (const auto& p : remaining_controller_filepath_iterator) {
std::string remain = p.path().filename().string();
if (std::regex_match(remain, re)) {
LOG(ERROR) << "I3C Controller " << remain << " on bus " << bus
<< " failed to unbind from " << i3c_controller_driver_path_;
return absl::InternalError("Failed to unbind controller");
}
}
return absl::OkStatus();
}
absl::Status AmdCpuSensor::BindI3cBusController(uint64_t bus) {
std::vector<std::string> controllers;
absl::MutexLock lock(controller_mutex_);
auto it = unbound_i3c_controllers_.find(bus);
if (it == unbound_i3c_controllers_.end() || it->second.empty()) {
return absl::NotFoundError(
absl::StrCat("No unbound controllers recorded for bus ", bus));
}
controllers = it->second;
boost::filesystem::path bind_file_path =
absl::StrCat(i3c_controller_driver_path_, "bind");
for (const auto& controller : controllers) {
std::ofstream bind_file(bind_file_path.string());
if (!bind_file.good()) {
return absl::InternalError(
absl::StrCat("Failed to open bind file for controller ", controller));
}
bind_file << controller << "\n";
bind_file.flush();
if (!bind_file.good()) {
return absl::InternalError(absl::StrCat(
"Failed to write to bind file for controller ", controller));
}
bind_file.close();
LOG(INFO) << "Bound controller " << controller << " for bus " << bus;
}
unbound_i3c_controllers_.erase(bus);
absl::SleepFor(absl::Milliseconds(100));
return absl::OkStatus();
}
} // namespace milotic_tlbmc