blob: 7b582b89917df8458ae2fc6d5eee0cbf2be18643 [file] [log] [blame]
#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