| #include "tlbmc/sensors/hwmon_based_sensor.h" |
| |
| #include <chrono> // NOLINT |
| #include <cstddef> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "google/protobuf/duration.pb.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/strings/match.h" |
| #include "boost/asio.hpp" // NOLINT: boost::asio is commonly used in BMC |
| #include "boost/asio/error.hpp" // NOLINT |
| #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 |
| #include "boost/system/detail/error_code.hpp" // NOLINT: boost::asio is commonly used in BMC |
| // NOLINTNEXTLINE: keep this so BUILD file will keep liburing |
| #include "liburing.h" // IWYU pragma: keep |
| #include "hal_common_config.pb.h" |
| #include "hwmon_temp_sensor_config.pb.h" |
| #include "resource.pb.h" |
| #include "sensor.pb.h" |
| #include "tlbmc/sensors/hwmon_input_device.h" |
| #include "tlbmc/sensors/sensor.h" |
| |
| namespace milotic_tlbmc { |
| |
| void HwmonBasedSensor::SetUpInput() { |
| // If the input device is already usable, it means the sensor is already in |
| // a valid state. We should not be modifying any of its internals. |
| if (IsInputDeviceUsable()) { |
| LOG(WARNING) << "Input device is already usable for " << input_dev_path_ |
| << ". Skipping setup."; |
| return; |
| } |
| // TODO(nanzhou): This logic should be eventually become a power signal based |
| // device deletion and recreation. E.g., |
| // /sys/bus/i2c/devices/i2c-35/35-005c/hwmon/hwmon0/temp1_input |
| // This also assumes the label and device mapping does not changes after |
| // recreation. |
| boost::filesystem::path input_dev_path(input_dev_path_); |
| bool found_hwmon = false; |
| boost::filesystem::path hwmon_folder = |
| input_dev_path.parent_path().parent_path(); |
| boost::system::error_code error; |
| for (const auto& entry : |
| boost::filesystem::directory_iterator(hwmon_folder, error)) { |
| if (error) { |
| break; |
| } |
| // Look for something like hwmon10 |
| if (absl::StartsWith(entry.path().filename().string(), "hwmon")) { |
| SetInputDevicePath((entry.path() / input_dev_path.filename()).string()); |
| found_hwmon = true; |
| break; |
| } |
| } |
| if (!found_hwmon) { |
| SetInputDeviceUsable(false); |
| return; |
| } |
| |
| input_device_ = |
| std::make_shared<HwmonInputDevice>(io_context_, input_dev_path_); |
| SetInputDeviceUsable(true); |
| } |
| |
| HwmonBasedSensor::HwmonBasedSensor( |
| const std::string& input_dev_path, |
| const std::shared_ptr<boost::asio::io_context>& io_context, |
| SensorAttributesStatic&& sensor_attributes_static, |
| const ThresholdConfigs& threshold_configs, |
| std::optional<NotificationCb> on_batch_notify) |
| : Sensor(std::move(sensor_attributes_static), threshold_configs, |
| on_batch_notify), |
| input_dev_path_(input_dev_path), |
| io_context_(io_context), |
| input_device_usable_(false) { |
| if (!input_dev_path.empty()) { |
| input_device_ = |
| std::make_shared<HwmonInputDevice>(io_context_, input_dev_path_); |
| SetInputDeviceUsable(true); |
| } |
| } |
| |
| HwmonBasedSensor::~HwmonBasedSensor() { input_device_usable_ = false; } |
| |
| void HwmonBasedSensor::RefreshOnceAsync( |
| absl::AnyInvocable<void(const std::shared_ptr<const SensorValue>&)> |
| callback) { |
| std::weak_ptr<HwmonBasedSensor> self = weak_from_this(); |
| boost::asio::post(*io_context_, [self{std::move(self)}, |
| callback = std::move(callback)]() mutable { |
| std::shared_ptr<HwmonBasedSensor> sensor = self.lock(); |
| if (!sensor) { |
| LOG(WARNING) << "Sensor is already destroyed; cancel the refresh"; |
| return; |
| } |
| |
| std::chrono::steady_clock::time_point last_refresh_start_time = |
| sensor->GetLastRefreshStartTime(); |
| if (last_refresh_start_time != |
| std::chrono::steady_clock::time_point::min()) { |
| sensor->UpdateSoftwarePollingStartMetrics( |
| std::chrono::steady_clock::now() - last_refresh_start_time); |
| } |
| sensor->SetLastRefreshStartTime(std::chrono::steady_clock::now()); |
| // The device might be deleted. E.g., the endpoint device lost |
| // power. In this case, we will try to set up the device again and |
| // bypass the refresh. |
| if (!sensor->IsInputDeviceUsable() || |
| !boost::filesystem::exists(sensor->GetInputDevicePath())) { |
| // Make the input device unusable to allow SetUpInput to be called. |
| sensor->SetInputDeviceUsable(false); |
| sensor->SetUpInput(); |
| sensor->HandleRefreshResult(boost::asio::error::no_such_device, 0); |
| if (callback) { |
| callback(sensor->GetSensorData()); |
| } |
| return; |
| } |
| std::chrono::time_point<std::chrono::steady_clock> start_time = |
| std::chrono::steady_clock::now(); |
| |
| sensor->GetInputDevice()->ReadAndProcess( |
| [self, start_time, callback = std::move(callback)]( |
| const boost::system::error_code& error, size_t bytes_read) mutable { |
| std::shared_ptr<HwmonBasedSensor> sensor = self.lock(); |
| if (!sensor) { |
| return; |
| } |
| std::chrono::steady_clock::duration latency = |
| std::chrono::steady_clock::now() - start_time; |
| sensor->UpdateHardwarePollingMetrics(latency); |
| // If the device is deleted, we will try to set up the device again |
| if (error == boost::asio::error::no_such_device) { |
| sensor->SetInputDeviceUsable(false); |
| sensor->SetUpInput(); |
| } |
| sensor->HandleRefreshResult(error, bytes_read); |
| if (callback) { |
| callback(sensor->GetSensorData()); |
| } |
| |
| std::chrono::steady_clock::time_point last_refresh_end_time = |
| sensor->GetLastRefreshEndTime(); |
| if (last_refresh_end_time != |
| std::chrono::steady_clock::time_point::min()) { |
| sensor->UpdateSoftwarePollingEndMetrics( |
| std::chrono::steady_clock::now() - last_refresh_end_time); |
| } |
| sensor->SetLastRefreshEndTime(std::chrono::steady_clock::now()); |
| }, |
| sensor->GetMutableReadBuffer()); |
| }); |
| } |
| |
| } // namespace milotic_tlbmc |