blob: 4dc5a5c128007ac1d56d7a6fe00e71e7bf7270ba [file] [log] [blame]
#include "tlbmc/sensors/psu_sensor.h"
#include <algorithm>
#include <array>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <system_error> // NOLINT: system_error is commonly used in BMC
#include <unordered_map>
#include <utility>
#include <vector>
#include "google/protobuf/duration.pb.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/substitute.h"
#include "boost/asio.hpp" // NOLINT: boost::asio is commonly used in BMC
#include "boost/asio/random_access_file.hpp" // NOLINT: boost::asio is commonly used in BMC
#include "boost/filesystem.hpp" // NOLINT: boost::filesystem is commonly used in BMC
#include "boost/filesystem/operations.hpp" // NOLINT: boost::filesystem is commonly used in BMC
#include "boost/system/detail/error_code.hpp" // NOLINT: boost::asio is commonly used in BMC
#include "g3/macros.h"
// NOLINTNEXTLINE: keep this so BUILD file will keep liburing
#include "liburing.h" // IWYU pragma: keep
#include "entity_common_config.pb.h"
#include "i2c_common_config.pb.h"
#include "psu_sensor_config.pb.h"
#include "reading_range_config.pb.h"
#include "threshold_config.pb.h"
#include "tlbmc/hal/sysfs/i2c.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/i2c_hwmon_based_sensor.h"
#include "tlbmc/sensors/sensor.h"
#include "tlbmc/time/time.h"
namespace milotic_tlbmc {
template <class TypeNameFirst, class TypeNameSecond>
struct TypeComparator {
// Compared by the first element which is the type name.
bool operator()(const std::pair<TypeNameFirst, TypeNameSecond>& lhs,
const std::pair<TypeNameFirst, TypeNameSecond>& rhs) const {
return lhs.first < rhs.first;
}
};
constexpr std::array<std::pair<PsuSensorType, std::string_view>, 8>
kSupportedPsuSensorTypes = {
// go/keep-sorted start
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE1_ADM1266, "adm1266"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE2_ADM1272, "adm1272"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE3_PMBUS, "pmbus"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE4_LTC2991, "ltc2991"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE5_RAA228228, "raa228228"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE6_TDA38725, "tda38725"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE7_TDA38740, "tda38740"},
std::pair<PsuSensorType, std::string_view>{
PsuSensorType::PSU_SENSOR_TYPE8_XDPE1A2G5B, "xdpe1a2g5b"},
// go/keep-sorted end
};
// Refer to https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface and
// see OpenBMC definition of PSU Sensor Properties:
// https://github.com/openbmc/dbus-sensors/blob/af1724b84b7558037c665d2106ec44d7362aca6b/src/psu/PSUSensorMain.cpp#L1095
constexpr std::array<std::pair<std::string_view, PsuSensor::ReadingProperties>,
9>
kDefaultPsuSensorProperties = {
// go/keep-sorted start
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"highestpin", // input power
PsuSensor::ReadingProperties{.label_type_name = "Peak Input Power",
.max_reading = 3000.0,
.min_reading = 0.0,
.scale = 0.000001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"iin", // input current
PsuSensor::ReadingProperties{.label_type_name = "Input Current",
.max_reading = 20.0,
.min_reading = 0.0,
.scale = 0.001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"iout", // output current
PsuSensor::ReadingProperties{.label_type_name = "Output Current",
.max_reading = 255.0,
.min_reading = 0.0,
.scale = 0.001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"pin", // input power
PsuSensor::ReadingProperties{.label_type_name = "Input Power",
.max_reading = 3000.0,
.min_reading = 0.0,
.scale = 0.000001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"pout", // output power
PsuSensor::ReadingProperties{.label_type_name = "Output Power",
.max_reading = 3000.0,
.min_reading = 0.0,
.scale = 0.000001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"temp", // temperature
PsuSensor::ReadingProperties{.label_type_name = "Temperature",
.max_reading = 127.0,
.min_reading = -128.0,
.scale = 0.001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"vin", // input voltage
PsuSensor::ReadingProperties{.label_type_name = "Input Voltage",
.max_reading = 300.0,
.min_reading = 0.0,
.scale = 0.001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"voltage", // output voltage
PsuSensor::ReadingProperties{.label_type_name = "Output Voltage",
.max_reading = 255.0,
.min_reading = 0.0,
.scale = 0.001,
.offset = 0.0}},
std::pair<std::string_view, PsuSensor::ReadingProperties>{
"vout", // output voltage
PsuSensor::ReadingProperties{.label_type_name = "Output Voltage",
.max_reading = 255.0,
.min_reading = 0.0,
.scale = 0.001,
.offset = 0.0}}
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, SensorUnit>, 5>
kInputWithoutDigitsToUnit = {
// go/keep-sorted start
std::pair<std::string_view, SensorUnit>{"curr",
SensorUnit::UNIT_AMPERE},
std::pair<std::string_view, SensorUnit>{"in", SensorUnit::UNIT_VOLT},
std::pair<std::string_view, SensorUnit>{"power", SensorUnit::UNIT_WATT},
std::pair<std::string_view, SensorUnit>{
"temp", SensorUnit::UNIT_DEGREE_CELSIUS},
std::pair<std::string_view, SensorUnit>{"voltage",
SensorUnit::UNIT_VOLT},
// go/keep-sorted end
};
absl::StatusOr<std::string_view> PsuSensor::GetDriverName(
PsuSensorType sensor_type) {
const auto* it = std::lower_bound(
kSupportedPsuSensorTypes.begin(), kSupportedPsuSensorTypes.end(),
std::pair<PsuSensorType, std::string_view>{sensor_type, ""},
TypeComparator<PsuSensorType, std::string_view>());
if (it == kSupportedPsuSensorTypes.end() || it->first != sensor_type) {
return absl::InvalidArgumentError(
absl::Substitute("Unsupported sensor type: $0", sensor_type));
}
return it->second;
}
absl::StatusOr<PsuSensor::ReadingProperties> PsuSensor::GetPsuSensorProperties(
std::string_view label) {
std::string_view label_no_digit = label;
std::size_t first_index = label_no_digit.find_first_of("0123456789");
if (first_index != std::string_view::npos) {
label_no_digit = label_no_digit.substr(0, first_index);
}
const auto* it = std::lower_bound(
kDefaultPsuSensorProperties.begin(), kDefaultPsuSensorProperties.end(),
std::pair<std::string_view, PsuSensor::ReadingProperties>{
label_no_digit, PsuSensor::ReadingProperties{}},
TypeComparator<std::string_view, PsuSensor::ReadingProperties>());
if (it == kDefaultPsuSensorProperties.end() || it->first != label_no_digit) {
return absl::InvalidArgumentError(
absl::Substitute("Unsupported sensor label: $0", label_no_digit));
}
return it->second;
}
absl::StatusOr<ReadingRangeConfigs> PsuSensor::GetReadingRangeConfigs(
const ReadingRangeConfigs& configs, const ReadingProperties& properties) {
ReadingRangeConfigs reading_range_configs = configs;
bool has_max_reading_range = false;
bool has_min_reading_range = false;
for (const auto& reading_range_config :
reading_range_configs.reading_range_configs()) {
if (reading_range_config.type() == READING_RANGE_TYPE_MAX) {
has_max_reading_range = true;
} else if (reading_range_config.type() == READING_RANGE_TYPE_MIN) {
has_min_reading_range = true;
}
}
// Add the default reading ranges if they are not specified in the config.
if (!has_max_reading_range) {
ReadingRangeConfig* reading_range =
reading_range_configs.add_reading_range_configs();
reading_range->set_type(READING_RANGE_TYPE_MAX);
reading_range->set_reading(properties.max_reading);
}
if (!has_min_reading_range) {
ReadingRangeConfig* reading_range =
reading_range_configs.add_reading_range_configs();
reading_range->set_type(READING_RANGE_TYPE_MIN);
reading_range->set_reading(properties.min_reading);
}
return reading_range_configs;
}
absl::StatusOr<SensorUnit> PsuSensor::GetSensorUnit(
std::string_view input_file) {
std::string_view input_file_no_digit = input_file;
std::size_t first_index = input_file_no_digit.find_first_of("0123456789");
if (first_index == std::string_view::npos) {
return absl::InvalidArgumentError(absl::Substitute(
"Failed to find the last non-digit character in $0", input_file));
}
input_file_no_digit = input_file_no_digit.substr(0, first_index);
const auto* it = std::lower_bound(
kInputWithoutDigitsToUnit.begin(), kInputWithoutDigitsToUnit.end(),
std::pair<std::string_view, SensorUnit>{input_file_no_digit,
SensorUnit::UNIT_UNKNOWN},
TypeComparator<std::string_view, SensorUnit>());
if (it == kInputWithoutDigitsToUnit.end() ||
it->first != input_file_no_digit) {
return absl::InvalidArgumentError(absl::Substitute(
"Unsupported sensor input file: $0", input_file_no_digit));
}
return it->second;
}
PsuSensorFileInfo PsuSensor::GetPsuSensorFileInfo(std::string_view input_file) {
for (int i = 0; i < kPsuSensorFileSuffixes.size(); ++i) {
std::string_view suffix = kPsuSensorFileSuffixes[i];
if (absl::EndsWith(input_file, suffix)) {
return {.file_prefix = std::string(
input_file.substr(0, input_file.size() - suffix.size())),
.file_type = static_cast<PsuSensorFileType>(i)};
}
}
return {.file_type = PsuSensorFileType::kUnknown};
}
absl::StatusOr<std::string> PsuSensor::ParseLabelFile(
const boost::filesystem::path& hwmon_path, std::string_view file_prefix) {
std::string label_file = absl::StrCat(file_prefix, kLabelSuffix);
// Both input file and label file should exist
boost::filesystem::path label_path = hwmon_path / label_file;
if (!boost::filesystem::exists(hwmon_path / label_file)) {
return absl::InvalidArgumentError(
absl::Substitute("Failed to find label file $0", label_file));
}
std::ifstream file(label_path.string());
std::string label = {std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()};
return std::string(absl::StripTrailingAsciiWhitespace(label));
}
std::string PsuSensor::ApplyLabelModifications(std::string_view label,
PsuSensorFileType file_type) {
// For unknown file type, just return the label.
if (file_type == PsuSensorFileType::kUnknown) {
return std::string(label);
}
return absl::StrCat(kPsuSensorLabelPrefixes[static_cast<int>(file_type)],
label);
}
std::string PsuSensor::GetLabel(const boost::filesystem::path& hwmon_path,
std::string_view file_prefix,
PsuSensorFileType file_type) {
// Try to find a label file and use its contents as the label.
// E.g. if the file prefix is "power1", then the label file should be at
// "power1_label".
// If the label file exists, then use the contents of the
// label file as the label.
// Otherwise, use the file_prefix as the label base.
absl::StatusOr<std::string> label = ParseLabelFile(hwmon_path, file_prefix);
if (!label.ok()) {
return ApplyLabelModifications(file_prefix, file_type);
}
return ApplyLabelModifications(*label, file_type);
}
absl::StatusOr<std::vector<std::shared_ptr<Sensor>>> PsuSensor::Create(
const PsuSensorConfig& config,
const std::shared_ptr<boost::asio::io_context>& io_context,
const I2cSysfs& i2c_sysfs, std::optional<NotificationCb> on_batch_notify) {
DLOG(INFO) << "Creating PsuSensor for device: " << absl::StrCat(config);
const I2cCommonConfig& i2c_common_config = config.i2c_common_config();
ECCLESIA_ASSIGN_OR_RETURN(std::string_view driver_name,
GetDriverName(config.type()));
// E.g., /sys/bus/i2c/devices/20-0040/hwmon/hwmon18/
ECCLESIA_ASSIGN_OR_RETURN(
boost::filesystem::path hwmon_path,
I2cHwmonBasedSensor::CreateDeviceAndReturnsHwmonPath(
i2c_common_config, driver_name, i2c_sysfs));
// Now find all labels and their input files.
std::unordered_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 = GetPsuSensorFileInfo(input_file);
// Ignore sensors that we don't support
if (file_info.file_type == PsuSensorFileType::kUnknown) {
continue;
}
std::string label =
GetLabel(hwmon_path, file_info.file_prefix, file_info.file_type);
label_to_input_file[label] = input_file;
}
}
std::vector<std::shared_ptr<Sensor>> sensors;
for (const auto& [label, name] : config.label_to_name()) {
auto label_to_input_file_it = label_to_input_file.find(label);
if (label_to_input_file_it == label_to_input_file.end()) {
return absl::InvalidArgumentError(absl::Substitute(
"Failed to find $0 in hwmon folder $1", label, hwmon_path.string()));
}
const std::string& input_file = label_to_input_file_it->second;
boost::filesystem::path input_dev_path = hwmon_path / input_file;
ThresholdConfigs threshold_configs;
if (auto it = config.label_to_thresholds().find(label);
it != config.label_to_thresholds().end()) {
threshold_configs = it->second;
}
ReadingRangeConfigs reading_range_configs;
if (auto it = config.label_to_reading_ranges().find(label);
it != config.label_to_reading_ranges().end()) {
reading_range_configs = it->second;
}
ECCLESIA_ASSIGN_OR_RETURN(PsuSensor::ReadingProperties properties,
GetPsuSensorProperties(label));
ECCLESIA_ASSIGN_OR_RETURN(
reading_range_configs,
GetReadingRangeConfigs(reading_range_configs, properties));
ECCLESIA_ASSIGN_OR_RETURN(SensorUnit sensor_unit,
GetSensorUnit(input_file));
std::string sensor_key = name;
if (sensor_key.empty()) {
// If sensor label name is not customized in the config, create key from
// prefix of sensor Name field and suffix of label_type_name
// https://github.com/openbmc/dbus-sensors/blob/556e04b8f374a9eb8cf32bf0e36ac46c14873eba/src/psu/PSUSensorMain.cpp#L934
sensor_key = absl::StrCat(config.name(), "_", properties.label_type_name);
std::replace(sensor_key.begin(), sensor_key.end(), ' ', '_');
}
switch (sensor_unit) {
case SensorUnit::UNIT_AMPERE:
sensor_key = absl::StrCat("current_", sensor_key);
break;
case SensorUnit::UNIT_VOLT:
sensor_key = absl::StrCat("voltage_", sensor_key);
break;
case SensorUnit::UNIT_WATT:
sensor_key = absl::StrCat("power_", sensor_key);
break;
case SensorUnit::UNIT_DEGREE_CELSIUS:
sensor_key = absl::StrCat("temperature_", sensor_key);
break;
default:
break;
}
sensors.push_back(std::make_shared<PsuSensor>(
Token(), config.type(), sensor_unit, input_dev_path.string(),
sensor_key, config.i2c_common_config(), threshold_configs,
reading_range_configs, config.entity_common_config(), properties.scale,
properties.offset, io_context, on_batch_notify));
}
return sensors;
}
void PsuSensor::HandleRefreshResult(const boost::system::error_code& error,
size_t bytes_read) {
if (error) {
State state;
state.set_status(STATUS_STALE);
state.set_status_message(absl::Substitute(
"Failed to read from input device: $0; input device path: $1",
error.message(), GetInputDevicePath()));
UpdateState(std::move(state));
return;
}
const char* buffer_end = GetConstReadBuffer().data() + bytes_read;
uint64_t value = 0;
std::from_chars_result result =
std::from_chars(GetConstReadBuffer().data(), buffer_end, value);
if (result.ec != std::errc()) {
State state;
state.set_status(STATUS_STALE);
state.set_status_message(
absl::StrCat("Read data can't be converted to a number: ",
std::make_error_condition(result.ec).message()));
UpdateState(std::move(state));
return;
}
SensorValue sensor_data;
*sensor_data.mutable_timestamp() = Now();
sensor_data.set_reading((static_cast<double>(value) + offset_) * scale_);
StoreSensorData(std::make_shared<const SensorValue>(std::move(sensor_data)));
State state;
state.set_status(STATUS_READY);
UpdateState(std::move(state));
}
PsuSensor::PsuSensor(Token token, PsuSensorType sensor_type,
SensorUnit sensor_unit, const std::string& input_dev_path,
const std::string& sensor_name,
const I2cCommonConfig& i2c_common_config,
const ThresholdConfigs& threshold_configs,
const ReadingRangeConfigs& reading_range_configs,
const EntityCommonConfig& entity_common_config,
double scale, double offset,
const std::shared_ptr<boost::asio::io_context>& io_context,
std::optional<NotificationCb> on_batch_notify)
: I2cHwmonBasedSensor(
input_dev_path, io_context,
CreateStaticAttributes(sensor_name, sensor_unit, i2c_common_config,
entity_common_config, threshold_configs,
reading_range_configs),
on_batch_notify),
sensor_type_(sensor_type),
sensor_name_(sensor_name),
board_config_name_(entity_common_config.board_config_name()),
scale_(scale),
offset_(offset) {}
} // namespace milotic_tlbmc