blob: 5645d75cbf9d8c90b2d10d415cd97c9d034f5b19 [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 <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 "hwmon_temp_sensor_config.pb.h"
#include "i2c_common_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 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>, 3>
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"}}
// 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::StatusOr<std::vector<std::shared_ptr<Sensor>>> HwmonTempSensor::Create(
const HwmonTempSensorConfig& config,
const std::shared_ptr<boost::asio::io_context>& io_context,
const I2cSysfs& i2c_sysfs, std::optional<NotificationCb> on_batch_notify) {
DLOG(INFO) << "Creating HwmonTempSensor 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()));
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)) {
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;
}
}
}
std::vector<std::shared_ptr<Sensor>> sensors;
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()));
}
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;
}
std::string hwmon_temp_sensor_key = absl::StrCat("temperature_", name);
sensors.push_back(std::make_shared<HwmonTempSensor>(
Token(), config.type(), input_dev_path.string(), hwmon_temp_sensor_key,
config.i2c_common_config(), threshold_configs, reading_range_configs,
config.entity_common_config(), io_context, on_batch_notify));
}
return sensors;
}
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;
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;
}
double scale = 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) + 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_name,
const I2cCommonConfig& i2c_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)
: I2cHwmonBasedSensor(
input_dev_path, io_context,
CreateStaticAttributes(sensor_name, UNIT_DEGREE_CELSIUS,
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()) {}
} // namespace milotic_tlbmc