blob: a25f1342a037e8a1bd54780cd27cab50174d5c3e [file] [log] [blame] [edit]
#include "tlbmc/sensors/i2c_hwmon_based_sensor.h"
#include <array>
#include <cstddef>
#include <exception>
#include <fstream>
#include <ios>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "google/protobuf/duration.pb.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/match.h"
#include "absl/strings/substitute.h"
#include "boost/asio.hpp" // NOLINT: boost::asio is commonly used in BMC
#include "boost/asio/error.hpp"
#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"
#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 "hwmon_temp_sensor_config.pb.h"
#include "i2c_common_config.pb.h"
#include "tlbmc/hal/sysfs/i2c.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/sensor.h"
namespace milotic_tlbmc {
void I2cHwmonBasedSensor::SetUpInputDeviceFile() {
// Prod kernel doesn't support io_uring yet. To make continuous build and test
// happy, we disable io_uring and use synchronous file read.
// On BMC kernel, io_uring will be supported by default.
// On cloudtop or workstation, add `--test_strategy=local` to your blaze
// command to enable io_uring.
try {
// Explicitly specify the O_NONBLOCK flag to avoid blocking the input file
// reading, needed for some kernel versions. (b/421908876)
input_device_ = std::make_shared<boost::asio::random_access_file>(
*io_context_, input_dev_path_,
boost::asio::random_access_file::read_only |
static_cast<boost::asio::random_access_file::flags>(O_NONBLOCK));
} catch (const std::exception& e) {
input_device_ = nullptr;
LOG(WARNING) << "Failed to create io_uring based random_access_file for "
<< input_dev_path_ << ": " << e.what()
<< " . Fall back to ifstream.";
input_file_ = std::ifstream(input_dev_path_);
}
}
void I2cHwmonBasedSensor::SetUpInput() {
// 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")) {
input_dev_path_ = (entry.path() / input_dev_path.filename()).string();
found_hwmon = true;
break;
}
}
if (!found_hwmon) {
return;
}
SetUpInputDeviceFile();
}
I2cHwmonBasedSensor::I2cHwmonBasedSensor(
const std::string& input_dev_path,
const std::shared_ptr<boost::asio::io_context>& io_context,
SensorAttributesStatic&& sensor_attributes_static,
std::optional<NotificationCb> on_batch_notify)
: Sensor(std::move(sensor_attributes_static), on_batch_notify),
input_dev_path_(input_dev_path),
io_context_(io_context) {
SetUpInputDeviceFile();
input_device_usable_ = true;
}
I2cHwmonBasedSensor::~I2cHwmonBasedSensor() {
input_device_usable_ = false;
// We need to copy the input device and extend its lifetime because
// `close()` will be called on it asynchronously.
if (io_context_ && input_device_) {
boost::asio::post(*io_context_, [input_device = input_device_] {
input_device->close();
});
}
}
absl::StatusOr<boost::filesystem::path>
I2cHwmonBasedSensor::CreateDeviceAndReturnsHwmonPath(
const I2cCommonConfig& i2c_config, std::string_view driver_name,
const I2cSysfs& i2c_sysfs) {
// If the device is already created, in the current implementation we don't
// create it again.
if (absl::Status status = i2c_sysfs.NewDevice(i2c_config, driver_name);
!status.ok() && status.code() != absl::StatusCode::kAlreadyExists) {
return status;
}
if (!i2c_sysfs.IsDevicePresent(i2c_config)) {
return absl::InternalError(absl::Substitute(
"Failed to find device $0 after creating it", i2c_config));
}
// Now the device is created, e.g.,
// /sys/bus/i2c/devices/i2c-24/24-005c/
boost::filesystem::path device_path =
i2c_sysfs.Geti2cBusPath(i2c_config.bus()) /
I2cSysfs::GetDeviceDirectoryName(i2c_config);
if (!boost::filesystem::is_directory(device_path / "hwmon")) {
return absl::InternalError(absl::Substitute("Failed to find hwmon under $0",
device_path.string()));
}
boost::filesystem::path hwmon_path;
bool found_hwmon = false;
for (const auto& entry :
boost::filesystem::directory_iterator(device_path / "hwmon")) {
// Look for something like hwmon10
if (absl::StartsWith(entry.path().filename().string(), "hwmon")) {
found_hwmon = true;
hwmon_path = entry.path();
break;
}
}
if (!found_hwmon) {
return absl::InternalError(
absl::Substitute("Failed to find hwmon for $0", device_path.string()));
}
return hwmon_path;
}
void I2cHwmonBasedSensor::RefreshOnceAsync(
absl::AnyInvocable<void(const std::shared_ptr<const SensorValue>&)>
callback) {
std::weak_ptr<I2cHwmonBasedSensor> self = weak_from_this();
boost::asio::post(*io_context_, [self{std::move(self)},
callback = std::move(callback)]() mutable {
std::shared_ptr<I2cHwmonBasedSensor> sensor = self.lock();
if (!sensor || !sensor->IsInputDeviceUsable()) {
LOG(WARNING) << "Sensor is already destroyed or input device is not "
"usable; cancel the refresh";
return;
}
// 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 (!boost::filesystem::exists(sensor->GetInputDevicePath())) {
sensor->SetUpInput();
sensor->HandleRefreshResult(boost::asio::error::no_such_device, 0);
if (callback) {
callback(sensor->GetSensorData());
}
return;
}
if (sensor->GetInputDevice() != nullptr) {
// These lines is covered on machines that have io_uring support, e.g.,
// workstation. Add "--test_strategy=local" to your blaze command.
// Example output with IO_URING:
// http://sponge2/cc89fcd1-d734-4123-8652-8f3484a895e9
sensor->GetInputDevice()->async_read_some_at(
0, boost::asio::buffer(sensor->GetMutableReadBuffer()),
[self, callback = std::move(callback)](
const boost::system::error_code& error,
size_t bytes_read) mutable {
std::shared_ptr<I2cHwmonBasedSensor> sensor = self.lock();
if (!sensor) {
return;
}
// If the device is deleted, we will try to set up the device again
if (error == boost::asio::error::no_such_device) {
sensor->SetUpInput();
}
sensor->HandleRefreshResult(error, bytes_read);
if (callback) {
callback(sensor->GetSensorData());
}
});
return;
}
sensor->GetInputFile().read(
sensor->GetMutableReadBuffer().data(),
static_cast<std::streamsize>(sensor->GetMutableReadBuffer().size()));
boost::system::error_code error;
sensor->HandleRefreshResult(error, sensor->GetConstReadBuffer().size());
if (callback) {
callback(sensor->GetSensorData());
}
});
}
} // namespace milotic_tlbmc