blob: 64066f5c3c24f1a42b57158ea34cee81935f1ac2 [file]
#include "tlbmc/sensors/adc_sensor.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <chrono> // NOLINT: chrono is commonly used in BMC
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#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/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#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 "g3/macros.h"
#include "tlbmc/central_config/config.h"
#include "adc_sensor_config.pb.h"
#include "entity_common_config.pb.h"
#include "hal_common_config.pb.h"
#include "reading_range_config.pb.h"
#include "reading_transform_config.pb.h"
#include "threshold_config.pb.h"
#include "tlbmc/hal/sysfs/i2c.h"
#include "tlbmc/hal/sysfs/iio.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/polling_base_sensor.h"
#include "tlbmc/time/time.h"
namespace milotic_tlbmc {
namespace {
constexpr auto kSupportedAdcSensorTypes =
std::to_array<std::pair<AdcSensorType, std::string_view>>({
// go/keep-sorted start numeric=yes
std::pair<AdcSensorType, std::string_view>{
AdcSensorType::ADC_SENSOR_TYPE1_TLA2024, "tla2024"},
std::pair<AdcSensorType, std::string_view>{
AdcSensorType::ADC_SENSOR_TYPE2_MAX11611, "max1363"},
std::pair<AdcSensorType, std::string_view>{
AdcSensorType::ADC_SENSOR_TYPE3_ADS1015, "ads1015"},
std::pair<AdcSensorType, std::string_view>{
AdcSensorType::ADC_SENSOR_TYPE4_MAX1363, "max1363"},
std::pair<AdcSensorType, std::string_view>{
AdcSensorType::ADC_SENSOR_TYPE5_ADS7142, "ads7142"},
// go/keep-sorted end
});
} // namespace
absl::StatusOr<std::shared_ptr<AdcSensor>> AdcSensor::Create(
const AdcSensorParameters& params, const IioSysfs& iio_sysfs,
const I2cSysfs& i2c_sysfs, std::optional<NotificationCb> on_batch_notify) {
SensorInstanceProperties mutable_instance_properties =
params.channel_config.instance_properties();
if (!params.channel_config.instance_properties().has_related_item()) {
mutable_instance_properties.mutable_related_item()->set_type(
RESOURCE_TYPE_BOARD);
mutable_instance_properties.mutable_related_item()->set_id(
params.entity_common_config.board_config_key());
}
mutable_instance_properties.set_name(absl::StrCat(
"voltage_", params.channel_config.instance_properties().name()));
std::string full_path;
absl::Status status;
if (params.hal_common_config.has_bus() &&
params.hal_common_config.has_address()) {
ECCLESIA_ASSIGN_OR_RETURN(std::string_view driver_name,
GetDriverName(params.type));
auto device_base_path = CreateAdcDeviceAndReturnsHwmonPath(
params.hal_common_config, driver_name, i2c_sysfs);
if (!device_base_path.ok()) {
status = device_base_path.status();
} else {
auto full_path_or = FindFullPathViaI2c(params, *device_base_path);
if (!full_path_or.ok()) {
status = full_path_or.status();
} else {
full_path = *std::move(full_path_or);
}
}
} else {
auto full_path_or = FindFullPathViaIio(params, iio_sysfs);
if (!full_path_or.ok()) {
status = full_path_or.status();
} else {
full_path = *std::move(full_path_or);
}
}
if (!status.ok()) {
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure() &&
params.entity_common_config.config_detected()) {
return status;
}
std::string error_message = absl::StrFormat(
"Failed to create AdcSensor (NONFATAL): %s: %s",
absl::StatusCodeToString(status.code()), status.message());
return CreateFailedSensor(params, mutable_instance_properties,
on_batch_notify, error_message);
}
auto sensor = std::make_shared<AdcSensor>(params, mutable_instance_properties,
full_path, on_batch_notify);
sensor->Init();
return sensor;
}
absl::StatusOr<std::shared_ptr<AdcSensor>> AdcSensor::CreateFailedSensor(
const AdcSensorParameters& params,
const SensorInstanceProperties& instance_properties,
std::optional<NotificationCb> on_batch_notify,
std::string_view error_message) {
auto sensor = std::make_shared<AdcSensor>(params, instance_properties, "",
on_batch_notify);
State state;
if (!params.entity_common_config.config_detected()) {
state.set_status(STATUS_CREATION_PENDING);
} else {
state.set_status(STATUS_CREATION_FAILED);
}
state.set_status_message(error_message);
sensor->UpdateState(std::move(state));
return sensor;
}
AdcSensor::AdcSensor(const AdcSensorParameters& params,
const SensorInstanceProperties& instance_properties,
const std::string& full_path,
std::optional<NotificationCb> on_batch_notify)
: PollingBaseSensor(
CreateStaticAttributes(instance_properties, params.hal_common_config,
params.entity_common_config),
instance_properties.thresholds(), on_batch_notify),
device_name_(params.device_name),
file_name_(params.channel_config.file_name()),
gpio_threshold_millivolts_(
params.channel_config.has_gpio_threshold_millivolts()
? std::make_optional(
params.channel_config.gpio_threshold_millivolts())
: std::nullopt),
io_context_(params.io_context),
sd_input_device_(*io_context_),
full_path_(full_path) {
absl::StatusOr<double> scale_value = GetFileScaleValue();
if (scale_value.ok()) {
scale_value_ = *scale_value;
} else {
LOG(WARNING) << "Failed to get scale value for " << device_name_ << ": "
<< scale_value.status().message()
<< ". Using default scale 1.0.";
}
if (params.channel_config.instance_properties()
.has_reading_transform_config() &&
params.channel_config.instance_properties()
.reading_transform_config()
.has_scale()) {
scale_value_ = params.channel_config.instance_properties()
.reading_transform_config()
.scale();
}
if (scale_value_ == 0) {
LOG(ERROR) << "Scale value is 0 for " << device_name_
<< "from either file or config. Resetting scale value to 1.0.";
scale_value_ = 1.0;
}
}
void AdcSensor::Init() {
if (sd_input_device_.is_open()) {
LOG(INFO) << "ADC device already opened: " << full_path_;
return;
}
if (full_path_.empty()) {
return;
}
int fd = open(full_path_.c_str(), O_RDONLY);
if (fd >= 0) {
sd_input_device_.assign(fd);
} else {
LOG(ERROR) << "Failed to open ADC device: " << full_path_ << " (fd: " << fd
<< ")";
}
}
AdcSensor::~AdcSensor() {
boost::system::error_code ec;
if (sd_input_device_.is_open()) {
boost::system::error_code ec_close = sd_input_device_.close(ec);
if (ec_close) {
LOG(WARNING) << "Failed to close sd_input_device_: "
<< ec_close.message();
}
}
}
void AdcSensor::HandleReadDone(
const boost::system::error_code& error, size_t bytes_read,
absl::AnyInvocable<void(const std::shared_ptr<const SensorValue>&)>
callback) {
if (error) {
if (error == boost::asio::error::no_such_device ||
error == boost::system::errc::no_such_file_or_directory ||
error == boost::system::errc::no_such_device_or_address ||
error == boost::system::errc::bad_file_descriptor) {
boost::system::error_code ec_close;
if (sd_input_device_.is_open()) {
ec_close = sd_input_device_.close(ec_close);
if (ec_close) {
LOG(WARNING) << "Failed to close stale sd_input_device_: "
<< ec_close.message();
}
}
}
State state;
state.set_status(STATUS_STALE);
state.set_status_message(absl::StrCat(
"Failed to read sensor value with error: ", error.message()));
UpdateState(std::move(state));
if (callback) {
callback(GetSensorData());
}
return;
}
double reading = 0;
const std::string_view str_value(read_buffer_.data(), bytes_read);
if (!absl::SimpleAtod(str_value, &reading)) {
LOG(ERROR) << "Failed to parse sensor reading: " << str_value;
State state;
state.set_status(STATUS_STALE);
state.set_status_message("Failed to parse sensor reading");
UpdateState(std::move(state));
if (callback) {
callback(GetSensorData());
}
return;
}
// The scale value is already validated in the constructor to be
// non-zero.
reading /= scale_value_;
if (gpio_threshold_millivolts_.has_value()) {
LOG(INFO) << "GPIO threshold exists, inferring virtual gpio value "
"based on the threshold and store it.";
auto processed_sensor_value = std::make_shared<SensorValue>();
*processed_sensor_value->mutable_timestamp() = Now();
processed_sensor_value->set_reading(
reading > static_cast<double>(*gpio_threshold_millivolts_) ? 1.0 : 0.0);
StoreSensorData(processed_sensor_value);
} else {
LOG(INFO) << "GPIO threshold does not exist, storing raw sensor value.";
auto sensor_value = std::make_shared<SensorValue>();
*sensor_value->mutable_timestamp() = Now();
sensor_value->set_reading(reading);
StoreSensorData(sensor_value);
}
State state;
state.set_status(STATUS_READY);
UpdateState(std::move(state));
if (callback) {
LOG(INFO) << "Calling callback to return sensor data";
callback(GetSensorData());
}
}
void AdcSensor::RefreshOnceAsync(
absl::AnyInvocable<void(const std::shared_ptr<const SensorValue>&)>
callback) {
if (IsOverridden()) {
if (callback) {
callback(GetSensorData());
}
return;
}
std::weak_ptr<AdcSensor> weak_self = shared_from_this();
auto on_read_done = [weak_self, callback = std::move(callback)](
const boost::system::error_code& error,
size_t bytes_read) mutable {
std::shared_ptr<AdcSensor> self = weak_self.lock();
if (!self) {
LOG(WARNING) << "Sensor is destroyed; cancel the refresh.";
return;
}
self->HandleReadDone(error, bytes_read, std::move(callback));
std::chrono::steady_clock::time_point last_refresh_end_time =
self->GetLastRefreshEndTime();
std::chrono::steady_clock::time_point current_time_point_2 =
std::chrono::steady_clock::now();
if (last_refresh_end_time != std::chrono::steady_clock::time_point::min()) {
self->UpdateSoftwarePollingEndMetrics(current_time_point_2 -
last_refresh_end_time);
}
self->SetLastRefreshEndTime(current_time_point_2);
};
std::shared_ptr<AdcSensor> self = weak_self.lock();
if (!self) {
LOG(WARNING) << "Sensor is destroyed; cancel the refresh.";
return;
}
std::chrono::steady_clock::time_point last_refresh_start_time =
self->GetLastRefreshStartTime();
std::chrono::steady_clock::time_point current_time_point_1 =
std::chrono::steady_clock::now();
if (last_refresh_start_time != std::chrono::steady_clock::time_point::min()) {
self->UpdateSoftwarePollingStartMetrics(current_time_point_1 -
last_refresh_start_time);
}
self->SetLastRefreshStartTime(current_time_point_1);
if (!self->sd_input_device_.is_open()) {
self->Init();
}
if (self->sd_input_device_.is_open()) {
if (lseek(self->sd_input_device_.native_handle(), 0, SEEK_SET) == -1) {
int err = errno;
LOG(WARNING) << "lseek failed on " << self->full_path_
<< " with error: " << strerror(err);
if (err == ENODEV || err == EBADF || err == ENOENT || err == ENXIO) {
boost::system::error_code ec_close;
ec_close = self->sd_input_device_.close(ec_close);
if (ec_close) {
LOG(WARNING) << "Failed to close stale sd_input_device_: "
<< ec_close.message();
}
}
const boost::system::error_code ec = boost::system::errc::make_error_code(
static_cast<boost::system::errc::errc_t>(err));
constexpr size_t bytes_read = 0;
boost::asio::post(*self->io_context_,
[on_read_done = std::move(on_read_done), ec]() mutable {
on_read_done(ec, bytes_read);
});
return;
}
self->sd_input_device_.async_read_some(
boost::asio::buffer(self->read_buffer_),
[on_read_done = std::move(on_read_done)](
const boost::system::error_code& ec,
std::size_t bytes_transferred) mutable {
if (ec == boost::system::errc::bad_file_descriptor) {
LOG(INFO) << "AdcSensor: sd_input_device_ is closed.";
return;
}
on_read_done(ec, bytes_transferred);
});
return;
}
const boost::system::error_code ec = boost::system::errc::make_error_code(
boost::system::errc::no_such_file_or_directory);
constexpr size_t bytes_read = 0;
boost::asio::post(*self->io_context_,
[on_read_done = std::move(on_read_done), ec]() mutable {
on_read_done(ec, bytes_read);
});
}
absl::StatusOr<double> AdcSensor::GetFileScaleValue() const {
auto scale_file_path = GetFileScalePath();
if (!scale_file_path.ok()) {
return scale_file_path.status();
}
std::ifstream scale_file(*scale_file_path);
if (!scale_file.is_open()) {
return absl::UnavailableError(
absl::StrCat("Could not open scale file: ", *scale_file_path));
}
std::string content;
std::getline(scale_file, content);
absl::StripTrailingAsciiWhitespace(&content);
double scale_value;
if (!absl::SimpleAtod(content, &scale_value)) {
return absl::InvalidArgumentError(
absl::StrCat("Could not parse scale value: ", content));
}
return scale_value;
}
absl::StatusOr<std::string> AdcSensor::GetFileScalePath() const {
std::string scale_file_name = "in_voltage_scale";
if (absl::StrContains(file_name_, "-voltage")) {
scale_file_name = "in_voltage-voltage_scale";
}
return (boost::filesystem::path(full_path_).parent_path() / scale_file_name)
.string();
}
absl::StatusOr<std::string_view> AdcSensor::GetDriverName(
AdcSensorType sensor_type) {
const auto* it = std::lower_bound(
kSupportedAdcSensorTypes.begin(), kSupportedAdcSensorTypes.end(),
std::pair<AdcSensorType, std::string_view>{sensor_type, ""},
[](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
if (it == kSupportedAdcSensorTypes.end() || it->first != sensor_type) {
return absl::InvalidArgumentError(absl::StrCat(
"Unsupported sensor type: ", static_cast<int>(sensor_type)));
}
return it->second;
}
absl::StatusOr<boost::filesystem::path>
AdcSensor::CreateAdcDeviceAndReturnsHwmonPath(const HalCommonConfig& hal_config,
std::string_view driver_name,
const I2cSysfs& i2c_sysfs) {
if (absl::Status status = i2c_sysfs.NewDevice(hal_config, driver_name);
!status.ok() && status.code() != absl::StatusCode::kAlreadyExists) {
return status;
}
if (!i2c_sysfs.IsDevicePresent(hal_config)) {
return absl::NotFoundError(
absl::StrCat("Failed to find device ", hal_config.bus(), "-",
hal_config.address(), " after creating it"));
}
boost::filesystem::path device_path =
i2c_sysfs.GetBusPath(hal_config.bus()) /
i2c_sysfs.GetDeviceDirectoryName(hal_config);
return device_path;
}
absl::StatusOr<std::string> AdcSensor::FindFullPathViaI2c(
const AdcSensorParameters& params,
const boost::filesystem::path& device_base_path) {
boost::system::error_code ec;
if (!boost::filesystem::is_directory(device_base_path, ec) || ec) {
LOG(ERROR) << "Failed to find I2C device directory: " << device_base_path;
return absl::NotFoundError(absl::StrCat("I2C device directory not found: ",
device_base_path.string()));
}
boost::filesystem::path sensor_dir;
bool found = false;
for (auto iter = boost::filesystem::directory_iterator(device_base_path, ec);
iter != boost::filesystem::directory_iterator(); iter.increment(ec)) {
const auto& entry = *iter;
if (absl::StartsWith(entry.path().filename().string(), "iio")) {
sensor_dir = entry.path();
found = true;
break;
}
}
if (ec) {
LOG(ERROR) << "Failed to iterate IIO directory under " << device_base_path
<< ": " << ec.message();
return absl::InternalError(
absl::StrCat("Failed to iterate IIO directory: ", ec.message()));
}
if (!found) {
LOG(ERROR) << "Failed to find IIO directory under " << device_base_path;
return absl::NotFoundError(absl::StrCat("IIO directory not found under ",
device_base_path.string()));
}
boost::filesystem::path sensor_path =
sensor_dir / params.channel_config.file_name();
if (!boost::filesystem::exists(sensor_path, ec) || ec) {
LOG(ERROR) << "Failed to find sensor file: " << sensor_path;
return absl::NotFoundError(
absl::StrCat("Sensor file not found: ", sensor_path.string()));
}
return sensor_path.string();
}
absl::StatusOr<std::string> AdcSensor::FindFullPathViaIio(
const AdcSensorParameters& params, const IioSysfs& iio_sysfs) {
const absl::StatusOr<boost::filesystem::path> sensor_path =
iio_sysfs.GetSensorPath(params.device_name,
params.channel_config.file_name());
if (!sensor_path.ok()) {
LOG(ERROR) << "Failed to get sensor path for " << params.device_name << " "
<< params.channel_config.file_name();
return sensor_path.status();
}
return sensor_path->string();
}
} // namespace milotic_tlbmc