blob: 82800498dc13760d3506bbea33bf84323a37cb8d [file]
#include "tlbmc/sensors/hwmon_temp_sensor.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <fstream>
#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/str_format.h"
#include "absl/strings/string_view.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>, 7>
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"},
{HWMON_TEMP_SENSOR_TYPE5_TMP461, "lm90"},
{HWMON_TEMP_SENSOR_TYPE6_TMP431, "tmp401"},
{HWMON_TEMP_SENSOR_TYPE7_TMP421, "tmp421"},
// 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;
if (!std::getline(file, label)) {
LOG(WARNING) << "Failed to read label file: " << label_path.string()
<< " with error: " << strerror(errno);
continue;
}
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& label, const std::string& input_dev_path,
const HwmonTempSensorConfig& config,
const SensorInstanceProperties& sensor_instance_properties,
const std::shared_ptr<boost::asio::io_context>& io_context,
std::optional<NotificationCb> on_batch_notify,
const HwmonSysfs& hwmon_sysfs) {
SensorInstanceProperties mutable_instance_properties =
sensor_instance_properties;
mutable_instance_properties.set_name(
absl::StrCat("temperature_", sensor_instance_properties.name()));
mutable_instance_properties.set_unit(UNIT_DEGREE_CELSIUS);
// Populate default related item if not specified.
if (!mutable_instance_properties.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());
}
return std::make_shared<HwmonTempSensor>(
Token(), config.type(), input_dev_path, label, config.hal_common_config(),
config.entity_common_config(), mutable_instance_properties, io_context,
on_batch_notify, hwmon_sysfs);
}
absl::StatusOr<std::shared_ptr<Sensor>> HwmonTempSensor::CreateFailedSensor(
const std::string& label, const HwmonTempSensorConfig& config,
const SensorInstanceProperties& sensor_instance_properties,
const std::shared_ptr<boost::asio::io_context>& io_context,
const HwmonSysfs& hwmon_sysfs,
std::optional<NotificationCb> on_batch_notify,
const std::string& error_message) {
std::shared_ptr<HwmonTempSensor> hwmon_temp_sensor =
CreateHwmonTempSensor(label, "", config, sensor_instance_properties,
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(error_message);
hwmon_temp_sensor->UpdateState(std::move(state));
return hwmon_temp_sensor;
}
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();
}
std::string error_message =
absl::StrFormat("Failed to create HwmonTempSensor (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<Sensor> hwmon_temp_sensor,
CreateFailedSensor(label, config, properties, io_context, hwmon_sysfs,
on_batch_notify, error_message));
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, properties] :
config.label_to_sensor_instance_properties()) {
boost::filesystem::path input_dev_path;
auto it = label_to_input_file.find(label);
if (it == label_to_input_file.end()) {
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure()) {
return absl::InvalidArgumentError(
absl::Substitute("Failed to find $0 in hwmon folder $1", label,
hwmon_path->string()));
}
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> hwmon_temp_sensor,
CreateFailedSensor(
label, config, properties, io_context, hwmon_sysfs,
on_batch_notify,
absl::StrFormat(
"Failed to create HwmonTempSensor (NONFATAL): %s: %s",
absl::StatusCodeToString(absl::StatusCode::kNotFound),
absl::Substitute("Failed to find $0 in hwmon folder $1",
label, hwmon_path->string()))));
sensors.push_back(hwmon_temp_sensor);
continue;
}
input_dev_path = *hwmon_path / it->second;
sensors.push_back(CreateHwmonTempSensor(label, input_dev_path.string(),
config, properties, io_context,
on_batch_notify, hwmon_sysfs));
}
return sensors;
}
absl::Status HwmonTempSensor::ReinitializeInternal() {
absl::string_view sensor_name =
GetSensorAttributesStatic().attributes().key();
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 HalCommonConfig& hal_common_config,
const EntityCommonConfig& entity_common_config,
const SensorInstanceProperties& sensor_instance_properties,
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_instance_properties, hal_common_config,
entity_common_config),
sensor_instance_properties.thresholds(), on_batch_notify,
hwmon_sysfs),
sensor_type_(sensor_type),
sensor_label_(sensor_label) {}
} // namespace milotic_tlbmc