| #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 |