| #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 |