blob: e6d436dfd931867370866eb0d4565cb077c1f36f [file] [log] [blame]
#include "tlbmc/sensors/hwmon_temp_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 <utility>
#include <vector>
#include "google/protobuf/duration.pb.h"
#include "absl/container/flat_hash_map.h"
#include "absl/functional/any_invocable.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 "tlbmc/central_config/config.h"
#include "entity_common_config.pb.h"
#include "hal_common_config.pb.h"
#include "hwmon_temp_sensor_config.pb.h"
#include "reading_range_config.pb.h"
#include "threshold_config.pb.h"
#include "tlbmc/hal/sysfs/hwmon.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/ixc_hwmon_based_sensor.h"
#include "tlbmc/sensors/sensor.h"
#include "tlbmc/time/time.h"
namespace milotic_tlbmc {
template <class TypeName>
struct TypeComparator {
// Compared by the first element which is the type name.
bool operator()(const std::pair<HwmonTempSensorType, TypeName>& lhs,
const std::pair<HwmonTempSensorType, TypeName>& rhs) const {
return lhs.first < rhs.first;
}
};
constexpr std::array<std::pair<HwmonTempSensorType, std::string_view>, 4>
kSupportedHwmonTempSensorTypes = {{
// go/keep-sorted start numeric=yes
{HWMON_TEMP_SENSOR_TYPE1_MAX31725, "max31725"},
{HWMON_TEMP_SENSOR_TYPE2_TMP75, "tmp75"},
{HWMON_TEMP_SENSOR_TYPE3_MAX31732, "max31732"},
{HWMON_TEMP_SENSOR_TYPE4_SA56004, "sa56004"},
// go/keep-sorted end
}};
absl::StatusOr<std::string_view> HwmonTempSensor::GetDriverName(
HwmonTempSensorType sensor_type) {
const auto* it = std::lower_bound(
kSupportedHwmonTempSensorTypes.begin(),
kSupportedHwmonTempSensorTypes.end(),
std::pair<HwmonTempSensorType, std::string_view>{sensor_type, ""},
TypeComparator<std::string_view>());
if (it == kSupportedHwmonTempSensorTypes.end() || it->first != sensor_type) {
return absl::InvalidArgumentError(
absl::Substitute("Unsupported sensor type: $0", sensor_type));
}
return it->second;
}
absl::flat_hash_map<std::string, std::string>
HwmonTempSensor::CreateLabelToInputFileMap(
const boost::filesystem::path& hwmon_path) {
absl::flat_hash_map<std::string, std::string> label_to_input_file;
for (const auto& entry : boost::filesystem::directory_iterator(hwmon_path)) {
constexpr std::string_view kInputFileSuffix = "_input";
constexpr std::string_view kLabelSuffix = "_label";
if (entry.is_regular_file() &&
absl::EndsWith(entry.path().filename().string(), kInputFileSuffix)) {
// e.g., /sys/bus/i2c/devices/i2c-21/21-0040/hwmon/hwmon15/temp100_input
std::string input_file = entry.path().filename().string();
std::string file_prefix =
input_file.substr(0, input_file.size() - kInputFileSuffix.size());
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(label_path)) {
std::ifstream file(label_path.string());
std::string label = {std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()};
label = std::string(absl::StripTrailingAsciiWhitespace(label));
label_to_input_file[label] = input_file;
} else if (boost::filesystem::exists(hwmon_path / input_file)) {
// If label file does not exist, use input file name prefix as label.
label_to_input_file[file_prefix] = input_file;
}
}
}
return label_to_input_file;
}
std::shared_ptr<HwmonTempSensor> HwmonTempSensor::CreateHwmonTempSensor(
const std::string& name, const std::string& label,
const std::string& input_dev_path, const HwmonTempSensorConfig& config,
const std::shared_ptr<boost::asio::io_context>& io_context,
std::optional<NotificationCb> on_batch_notify,
const HwmonSysfs& hwmon_sysfs) {
std::string hwmon_temp_sensor_key = absl::StrCat("temperature_", name);
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;
}
return std::make_shared<HwmonTempSensor>(
Token(), config.type(), input_dev_path, label, hwmon_temp_sensor_key,
config.hal_common_config(), threshold_configs, reading_range_configs,
config.entity_common_config(), io_context, on_batch_notify, hwmon_sysfs);
}
absl::StatusOr<std::vector<std::shared_ptr<Sensor>>> HwmonTempSensor::Create(
const HwmonTempSensorConfig& config,
const std::shared_ptr<boost::asio::io_context>& io_context,
const HwmonSysfs& hwmon_sysfs,
std::optional<NotificationCb> on_batch_notify) {
DLOG(INFO) << "Creating HwmonTempSensor for device: " << absl::StrCat(config);
const HalCommonConfig& hal_common_config = config.hal_common_config();
ECCLESIA_ASSIGN_OR_RETURN(std::string_view driver_name,
GetDriverName(config.type()));
std::vector<std::shared_ptr<Sensor>> sensors;
absl::StatusOr<boost::filesystem::path> hwmon_path =
IXcHwmonBasedSensor::CreateIXcDeviceAndReturnsHwmonPath(
hal_common_config, driver_name, hwmon_sysfs);
if (!hwmon_path.ok()) {
// Only fail if the sensor is detected and the config is not allowed to
// fail.
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure() &&
config.entity_common_config().config_detected()) {
return hwmon_path.status();
}
LOG(ERROR) << "Failed to create HwmonTempSensor (NONFATAL): "
<< hwmon_path.status();
for (const auto& [label, name] : config.label_to_name()) {
std::shared_ptr<HwmonTempSensor> hwmon_temp_sensor =
CreateHwmonTempSensor(name, label, "", config, io_context,
on_batch_notify, hwmon_sysfs);
State state;
// If the sensor is not detected, we set its creation as pending otherwise
// set it to failed.
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::StrCat("Failed to create HwmonTempSensor (NONFATAL): ",
hwmon_path.status()));
hwmon_temp_sensor->UpdateState(std::move(state));
sensors.push_back(hwmon_temp_sensor);
}
return sensors;
}
// Now find all labels and their input files.
absl::flat_hash_map<std::string, std::string> label_to_input_file =
CreateLabelToInputFileMap(*hwmon_path);
for (const auto& [label, name] : config.label_to_name()) {
boost::filesystem::path input_dev_path;
if (auto it = label_to_input_file.find(label);
it != label_to_input_file.end()) {
input_dev_path = *hwmon_path / it->second;
} else {
return absl::InvalidArgumentError(absl::Substitute(
"Failed to find $0 in hwmon folder $1", label, hwmon_path->string()));
}
sensors.push_back(
CreateHwmonTempSensor(name, label, input_dev_path.string(), config,
io_context, on_batch_notify, hwmon_sysfs));
}
return sensors;
}
absl::Status HwmonTempSensor::ReinitializeInternal() {
LOG(INFO) << "Reinitializing hwmon temp sensor: " << sensor_name_;
if (IsInputDeviceUsable()) {
return absl::FailedPreconditionError(
absl::StrCat("Attempted to reinitialize usable sensor: ", sensor_name_,
". Ignoring reinitialization."));
}
ECCLESIA_ASSIGN_OR_RETURN(std::string_view driver_name,
GetDriverName(sensor_type_));
ECCLESIA_ASSIGN_OR_RETURN(
boost::filesystem::path hwmon_path,
IXcHwmonBasedSensor::CreateIXcDeviceAndReturnsHwmonPath(
sensor_attributes_static_.hal_common_config(), driver_name,
*hwmon_sysfs_));
absl::flat_hash_map<std::string, std::string> label_to_input_file =
CreateLabelToInputFileMap(hwmon_path);
auto it = label_to_input_file.find(sensor_label_);
if (it == label_to_input_file.end()) {
return absl::NotFoundError(
absl::Substitute("Failed to find $0 in hwmon folder $1", sensor_label_,
hwmon_path.string()));
}
SetInputDevicePath((hwmon_path / it->second).string());
// Set to Status UNKNOWN to simulate fresh creation of Sensor object.
State state;
state.set_status(STATUS_UNKNOWN);
UpdateState(std::move(state));
// Set up the input device file for reading. This must be done last as right
// after this function is done, the sensor is readable.
SetUpInput();
LOG(WARNING) << "Successfully reinitialized hwmon temp sensor: "
<< sensor_name_;
return absl::OkStatus();
}
void HwmonTempSensor::Reinitialize(
absl::AnyInvocable<void(absl::Status)> callback) {
boost::asio::post(*io_context_,
[this, callback = std::move(callback)]() mutable {
callback(ReinitializeInternal());
});
}
void HwmonTempSensor::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;
int64_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;
}
double scale = sensor_attributes_static_.reading_transform().scale();
if (GetSensorAttributesStatic().unit() == UNIT_DEGREE_CELSIUS) {
// Temperatures are read in milli degrees Celsius, we need
// degrees Celsius.
// https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
scale *= 0.001;
}
SensorValue sensor_data;
*sensor_data.mutable_timestamp() = Now();
sensor_data.set_reading(
((static_cast<double>(value) +
sensor_attributes_static_.reading_transform().offset()) *
scale));
StoreSensorData(std::make_shared<const SensorValue>(std::move(sensor_data)));
State state;
state.set_status(STATUS_READY);
UpdateState(std::move(state));
}
HwmonTempSensor::HwmonTempSensor(
Token token, HwmonTempSensorType sensor_type,
const std::string& input_dev_path, const std::string& sensor_label,
const std::string& sensor_name, const HalCommonConfig& hal_common_config,
const ThresholdConfigs& threshold_configs,
const ReadingRangeConfigs& reading_range_configs,
const EntityCommonConfig& entity_common_config,
const std::shared_ptr<boost::asio::io_context>& io_context,
std::optional<NotificationCb> on_batch_notify,
const HwmonSysfs& hwmon_sysfs)
: IXcHwmonBasedSensor(input_dev_path, io_context,
CreateStaticAttributes(
sensor_name, UNIT_DEGREE_CELSIUS,
hal_common_config, entity_common_config,
reading_range_configs, ReadingTransformConfig()),
threshold_configs, on_batch_notify, hwmon_sysfs),
sensor_type_(sensor_type),
sensor_label_(sensor_label),
sensor_name_(sensor_name){}
} // namespace milotic_tlbmc