| #include "tlbmc/hal/gpio/gpio_monitor.h" |
| |
| #include <gpiod.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "boost/asio/io_context.hpp" // NOLINT: boost is commonly used in BMC codebase |
| #include "boost/asio/posix/stream_descriptor.hpp" // NOLINT: boost is commonly used in BMC codebase |
| #include "boost/asio/post.hpp" // NOLINT: boost is commonly used in BMC codebase |
| #include "g3/macros.h" |
| #include "gpio_config.pb.h" |
| #include "tlbmc/hal/gpio/gpio.h" |
| |
| namespace milotic_tlbmc { |
| |
| GpioMonitor::GpioMonitor( |
| std::unique_ptr<milotic_tlbmc::Gpio> gpio, bool is_one_time_request, |
| std::shared_ptr<boost::asio::io_context> io_context, |
| std::unique_ptr<boost::asio::posix::stream_descriptor> event_descriptor) |
| : gpio_(std::move(gpio)), |
| is_one_time_request_(is_one_time_request), |
| io_context_(std::move(io_context)), |
| event_descriptor_(std::move(event_descriptor)) { |
| ScheduleEventHandler(); |
| } |
| |
| GpioMonitor::~GpioMonitor() { |
| // Stop the io_context before closing the event descriptor. Otherwise, the |
| // event handler may be triggered after the event descriptor is closed. |
| io_context_->stop(); |
| event_descriptor_.reset(); |
| } |
| |
| void GpioMonitor::ScheduleEventHandler() { |
| event_descriptor_->async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [this](const boost::system::error_code& ec) { |
| if (ec) { |
| LOG(ERROR) << "GPIO event handler error: " << ec.message(); |
| return; |
| } |
| EventHandler(); |
| }); |
| } |
| |
| void GpioMonitor::RegisterCallback( |
| absl::AnyInvocable<void(GpioEventType event)>&& callback) { |
| boost::asio::post(*io_context_, [this, cb = std::move(callback)]() mutable { |
| callbacks_.push_back(std::move(cb)); |
| }); |
| } |
| |
| void GpioMonitor::EventHandler() { |
| gpiod_line_event gpio_line_event; |
| |
| // Need to consume the event. Otherwise, the event will be stuck in the |
| // queue and the descriptor will always be ready for read. |
| if (gpiod_line_event_read_fd(event_descriptor_->native_handle(), |
| &gpio_line_event) < 0) { |
| LOG(ERROR) << ("Failed to read from GPIO event descriptor"); |
| return; |
| } |
| |
| if (is_one_time_request_) { |
| event_descriptor_->close(); |
| } else { |
| event_descriptor_->async_wait( |
| boost::asio::posix::stream_descriptor::wait_read, |
| [this](const boost::system::error_code& ec) { |
| if (ec) { |
| LOG(ERROR) << "GPIO event handler error: " << ec.message(); |
| return; |
| } |
| EventHandler(); |
| }); |
| } |
| |
| for (auto& callback : callbacks_) { |
| if (gpio_line_event.event_type == GPIO_EVENT_RISING) { |
| callback(GPIO_EVENT_RISING); |
| } else { |
| callback(GPIO_EVENT_FALLING); |
| } |
| } |
| } |
| |
| absl::StatusOr<std::unique_ptr<GpioMonitor>> GpioMonitor::Create( |
| const GpioConfig& gpio_config, |
| std::shared_ptr<boost::asio::io_context> io_context) { |
| ECCLESIA_ASSIGN_OR_RETURN(std::unique_ptr<milotic_tlbmc::Gpio> gpio, |
| milotic_tlbmc::Gpio::Create(gpio_config)); |
| auto event_descriptor = |
| std::make_unique<boost::asio::posix::stream_descriptor>(*io_context); |
| event_descriptor->assign(gpio->GetGpioLineEventFd()); |
| |
| return absl::WrapUnique(new GpioMonitor( |
| std::move(gpio), gpio_config.gpio_event_config().one_time_request(), |
| io_context, std::move(event_descriptor))); |
| } |
| |
| } // namespace milotic_tlbmc |