blob: 2388dcaf596842903fbe32ad77f172d1443a07a4 [file] [log] [blame]
#include "tlbmc/sensors/fan_pwm.h"
#include <array>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <ios>
#include <memory>
#include <optional>
#include <string>
#include <system_error> // NOLINT: system_error is commonly used in BMC
#include <utility>
#include "google/protobuf/duration.pb.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.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 "tlbmc/central_config/config.h"
#include "entity_common_config.pb.h"
#include "fan_pwm_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/hwmon.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/fan_controller.h"
#include "tlbmc/sensors/ixc_hwmon_based_sensor.h"
#include "tlbmc/sensors/sensor.h"
#include "tlbmc/time/time.h"
namespace milotic_tlbmc {
constexpr int kMaxPwmValue = 255;
absl::Status FanPwm::EnablePwm(const boost::filesystem::path& pwm_enable_path) {
std::fstream file_in(pwm_enable_path.string(), std::ios::in);
if (!file_in.good()) {
return absl::InternalError(
absl::StrCat("Error open ", pwm_enable_path.string()));
}
std::string mode;
std::getline(file_in, mode);
if (mode == "0") {
DLOG(INFO) << "Enabling fan PWM for " << pwm_enable_path.string();
std::ofstream file_out(pwm_enable_path.string(), std::ios::out);
file_out.clear();
file_out.seekp(0, std::ios::beg);
file_out << 1;
file_out.flush();
}
return absl::OkStatus();
}
absl::StatusOr<std::shared_ptr<Sensor>> FanPwm::Create(
const FanPwmConfig& config, const FanController& fan_controller,
const std::shared_ptr<boost::asio::io_context>& io_context,
const HwmonSysfs& hwmon_sysfs,
std::optional<NotificationCb> on_batch_notify) {
DLOG(INFO) << "Creating fan PWM for device: " << absl::StrCat(config);
const HalCommonConfig& hal_common_config = config.hal_common_config();
if (!fan_controller.ControllerHasSensor(hal_common_config)) {
return absl::InvalidArgumentError(
absl::Substitute("Fan controller $0 does not have sensor $1",
absl::StrCat(fan_controller.GetHalCommonConfig()),
absl::StrCat(hal_common_config)));
}
auto it = fan_controller.GetIndexToPwms().find(config.index());
if (it == fan_controller.GetIndexToPwms().end()) {
// 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 absl::InvalidArgumentError(absl::Substitute(
"Fan controller $0 does not have a PWM file at index $1",
absl::StrCat(fan_controller.GetHalCommonConfig()), config.index()));
}
LOG(ERROR) << "Failed to create FanPwm (NONFATAL): "
<< absl::Substitute(
"Fan controller $0 does not have a PWM file at index $1",
absl::StrCat(fan_controller.GetHalCommonConfig()),
config.index());
std::shared_ptr<FanPwm> fan_pwm = std::make_shared<FanPwm>(
Token(), config.type(), "", absl::StrCat("fanpwm_", config.name()),
config.hal_common_config(), config.thresholds(),
config.reading_ranges(), ReadingTransformConfig(),
config.entity_common_config(), 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("Failed to create FanPwm Sensor (NONFATAL)");
fan_pwm->UpdateState(std::move(state));
return fan_pwm;
}
// Enable PWM
ECCLESIA_RETURN_IF_ERROR(EnablePwm(it->second.pwm_enable));
std::string fan_pwm_key = absl::StrCat("fanpwm_", config.name());
// Default reading scale for FanPwm is 100.0.
ReadingTransformConfig reading_transform_config;
reading_transform_config.set_scale(100.0);
return std::make_shared<FanPwm>(
Token(), config.type(), it->second.pwm.string(), fan_pwm_key,
config.hal_common_config(), config.thresholds(), config.reading_ranges(),
reading_transform_config, config.entity_common_config(), io_context,
on_batch_notify, hwmon_sysfs);
}
void FanPwm::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;
}
SensorValue sensor_data;
*sensor_data.mutable_timestamp() = Now();
sensor_data.set_reading(
((static_cast<double>(value) / kMaxPwmValue) *
sensor_attributes_static_.reading_transform().scale()));
StoreSensorData(std::make_shared<const SensorValue>(std::move(sensor_data)));
State state;
state.set_status(STATUS_READY);
UpdateState(std::move(state));
}
absl::Status FanPwm::WriteReading(const SensorValue& value) {
if (value.reading() < 0 || value.reading() > 100) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid reading: ", value.reading()));
}
std::shared_ptr<const SensorValue> last_reading = GetSensorData();
if (last_reading != nullptr && last_reading->reading() == value.reading()) {
// No need to write the same reading.
return absl::OkStatus();
}
std::string pwm_path = GetInputDevicePath();
// Transform the reading from [0, 100] to [0, 255] range.
int reading = static_cast<int>(value.reading() * kMaxPwmValue / 100);
std::ofstream file_out(pwm_path);
file_out << reading;
file_out.flush();
if (!file_out.good()) {
return absl::InternalError(
absl::StrCat("Error writing to ", pwm_path));
}
return absl::OkStatus();
}
FanPwm::FanPwm(Token token, FanPwmType sensor_type,
const std::string& input_dev_path,
const std::string& sensor_name,
const HalCommonConfig& hal_common_config,
const ThresholdConfigs& threshold_configs,
const ReadingRangeConfigs& reading_range_configs,
const ReadingTransformConfig& reading_transform_config,
const EntityCommonConfig& entity_common_config,
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_name, UNIT_PERCENT, hal_common_config,
entity_common_config, reading_range_configs,
reading_transform_config),
threshold_configs, on_batch_notify, hwmon_sysfs),
sensor_type_(sensor_type),
sensor_name_(sensor_name) {}
} // namespace milotic_tlbmc