xref: /aosp_15_r20/external/pigweed/pw_digital_io_linux/notifier.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2024 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker 
15*61c4878aSAndroid Build Coastguard Worker #include "pw_digital_io_linux/notifier.h"
16*61c4878aSAndroid Build Coastguard Worker 
17*61c4878aSAndroid Build Coastguard Worker #include <sys/epoll.h>
18*61c4878aSAndroid Build Coastguard Worker #include <sys/eventfd.h>
19*61c4878aSAndroid Build Coastguard Worker #include <unistd.h>
20*61c4878aSAndroid Build Coastguard Worker 
21*61c4878aSAndroid Build Coastguard Worker #include <array>
22*61c4878aSAndroid Build Coastguard Worker #include <cerrno>
23*61c4878aSAndroid Build Coastguard Worker #include <cstring>
24*61c4878aSAndroid Build Coastguard Worker #include <mutex>
25*61c4878aSAndroid Build Coastguard Worker #include <utility>
26*61c4878aSAndroid Build Coastguard Worker 
27*61c4878aSAndroid Build Coastguard Worker #include "log_errno.h"
28*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
29*61c4878aSAndroid Build Coastguard Worker #include "pw_log/log.h"
30*61c4878aSAndroid Build Coastguard Worker #include "pw_status/status.h"
31*61c4878aSAndroid Build Coastguard Worker 
32*61c4878aSAndroid Build Coastguard Worker namespace pw::digital_io {
33*61c4878aSAndroid Build Coastguard Worker 
34*61c4878aSAndroid Build Coastguard Worker namespace {
35*61c4878aSAndroid Build Coastguard Worker 
36*61c4878aSAndroid Build Coastguard Worker using pw::OkStatus;
37*61c4878aSAndroid Build Coastguard Worker 
38*61c4878aSAndroid Build Coastguard Worker constexpr void* kCancelToken = nullptr;
39*61c4878aSAndroid Build Coastguard Worker 
40*61c4878aSAndroid Build Coastguard Worker // The max number of that the notifier will process in a single iteration.
41*61c4878aSAndroid Build Coastguard Worker inline constexpr auto kMaxEventsPerWake = 16;
42*61c4878aSAndroid Build Coastguard Worker 
43*61c4878aSAndroid Build Coastguard Worker }  // namespace
44*61c4878aSAndroid Build Coastguard Worker 
Create()45*61c4878aSAndroid Build Coastguard Worker pw::Result<std::shared_ptr<LinuxGpioNotifier>> LinuxGpioNotifier::Create() {
46*61c4878aSAndroid Build Coastguard Worker   // Create file descriptors
47*61c4878aSAndroid Build Coastguard Worker   int raw_epoll_fd = epoll_create1(0);
48*61c4878aSAndroid Build Coastguard Worker   if (raw_epoll_fd < 0) {
49*61c4878aSAndroid Build Coastguard Worker     LOG_ERROR_WITH_ERRNO("Failed to initialize epoll descriptor:", errno);
50*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
51*61c4878aSAndroid Build Coastguard Worker   }
52*61c4878aSAndroid Build Coastguard Worker   auto epoll_fd = OwnedFd(raw_epoll_fd);
53*61c4878aSAndroid Build Coastguard Worker 
54*61c4878aSAndroid Build Coastguard Worker   int raw_event_fd = eventfd(0, 0);
55*61c4878aSAndroid Build Coastguard Worker   if (raw_event_fd < 0) {
56*61c4878aSAndroid Build Coastguard Worker     LOG_ERROR_WITH_ERRNO("Failed to initialize event descriptor:", errno);
57*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
58*61c4878aSAndroid Build Coastguard Worker   }
59*61c4878aSAndroid Build Coastguard Worker   auto event_fd = OwnedFd(raw_event_fd);
60*61c4878aSAndroid Build Coastguard Worker 
61*61c4878aSAndroid Build Coastguard Worker   // Attempt to register event_fd with epoll_fd
62*61c4878aSAndroid Build Coastguard Worker   epoll_event event = {
63*61c4878aSAndroid Build Coastguard Worker       .events = EPOLLIN,
64*61c4878aSAndroid Build Coastguard Worker       .data = {.ptr = kCancelToken},
65*61c4878aSAndroid Build Coastguard Worker   };
66*61c4878aSAndroid Build Coastguard Worker   if (epoll_ctl(epoll_fd.fd(), EPOLL_CTL_ADD, event_fd.fd(), &event) != 0) {
67*61c4878aSAndroid Build Coastguard Worker     // There is no reason this should ever fail, except for a bug!
68*61c4878aSAndroid Build Coastguard Worker     LOG_ERROR_WITH_ERRNO("Failed to add cancel event to epoll descriptor:",
69*61c4878aSAndroid Build Coastguard Worker                          errno);
70*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
71*61c4878aSAndroid Build Coastguard Worker   }
72*61c4878aSAndroid Build Coastguard Worker 
73*61c4878aSAndroid Build Coastguard Worker   // Initialization succeeded - create the object.
74*61c4878aSAndroid Build Coastguard Worker   return std::shared_ptr<LinuxGpioNotifier>(
75*61c4878aSAndroid Build Coastguard Worker       new LinuxGpioNotifier(std::move(epoll_fd), std::move(event_fd)));
76*61c4878aSAndroid Build Coastguard Worker }
77*61c4878aSAndroid Build Coastguard Worker 
~LinuxGpioNotifier()78*61c4878aSAndroid Build Coastguard Worker LinuxGpioNotifier::~LinuxGpioNotifier() {
79*61c4878aSAndroid Build Coastguard Worker   PW_CHECK_INT_EQ(  // Crash OK: prevent use-after-free via registered lines.
80*61c4878aSAndroid Build Coastguard Worker       registered_line_count_,
81*61c4878aSAndroid Build Coastguard Worker       0,
82*61c4878aSAndroid Build Coastguard Worker       "Destroying notifier with registered lines");
83*61c4878aSAndroid Build Coastguard Worker   // fds closed automatically
84*61c4878aSAndroid Build Coastguard Worker }
85*61c4878aSAndroid Build Coastguard Worker 
RegisterLine(int fd,LinuxGpioNotifier::Handler & handler)86*61c4878aSAndroid Build Coastguard Worker pw::Status LinuxGpioNotifier::RegisterLine(
87*61c4878aSAndroid Build Coastguard Worker     int fd, LinuxGpioNotifier::Handler& handler) {
88*61c4878aSAndroid Build Coastguard Worker   // Register for event notifications. Note that it's not clear from the
89*61c4878aSAndroid Build Coastguard Worker   // documentation if EPOLLIN or EPOLLPRI is needed here, but EPOLLPRI shows up
90*61c4878aSAndroid Build Coastguard Worker   // in all the examples online, and EPOLLIN is useful for testing.
91*61c4878aSAndroid Build Coastguard Worker   epoll_event event = {
92*61c4878aSAndroid Build Coastguard Worker       .events = EPOLLIN | EPOLLPRI,
93*61c4878aSAndroid Build Coastguard Worker       .data = {.ptr = &handler},
94*61c4878aSAndroid Build Coastguard Worker   };
95*61c4878aSAndroid Build Coastguard Worker   if (epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_ADD, fd, &event) != 0) {
96*61c4878aSAndroid Build Coastguard Worker     switch (errno) {
97*61c4878aSAndroid Build Coastguard Worker       case EBADF:
98*61c4878aSAndroid Build Coastguard Worker         PW_LOG_WARN("The fd [%d] is invalid", fd);
99*61c4878aSAndroid Build Coastguard Worker         return pw::Status::InvalidArgument();
100*61c4878aSAndroid Build Coastguard Worker       case EEXIST:
101*61c4878aSAndroid Build Coastguard Worker         PW_LOG_WARN(
102*61c4878aSAndroid Build Coastguard Worker             "The fd [%d] is already registered with epoll descriptor [%d]",
103*61c4878aSAndroid Build Coastguard Worker             fd,
104*61c4878aSAndroid Build Coastguard Worker             epoll_fd_.fd());
105*61c4878aSAndroid Build Coastguard Worker         return pw::Status::FailedPrecondition();
106*61c4878aSAndroid Build Coastguard Worker       case ENOSPC:
107*61c4878aSAndroid Build Coastguard Worker         PW_LOG_WARN("No space to add fd [%d] to epoll descriptor [%d]",
108*61c4878aSAndroid Build Coastguard Worker                     fd,
109*61c4878aSAndroid Build Coastguard Worker                     epoll_fd_.fd());
110*61c4878aSAndroid Build Coastguard Worker         return pw::Status::ResourceExhausted();
111*61c4878aSAndroid Build Coastguard Worker     }
112*61c4878aSAndroid Build Coastguard Worker     // Other errors are likely the result of bugs and should never happen.
113*61c4878aSAndroid Build Coastguard Worker     LOG_ERROR_WITH_ERRNO("Failed to add fd [%d] to epoll descriptor [%d]:",
114*61c4878aSAndroid Build Coastguard Worker                          errno,
115*61c4878aSAndroid Build Coastguard Worker                          fd,
116*61c4878aSAndroid Build Coastguard Worker                          epoll_fd_.fd());
117*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
118*61c4878aSAndroid Build Coastguard Worker   }
119*61c4878aSAndroid Build Coastguard Worker   ++registered_line_count_;
120*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
121*61c4878aSAndroid Build Coastguard Worker }
122*61c4878aSAndroid Build Coastguard Worker 
UnregisterLine(int fd)123*61c4878aSAndroid Build Coastguard Worker pw::Status LinuxGpioNotifier::UnregisterLine(int fd) {
124*61c4878aSAndroid Build Coastguard Worker   // Unregister from event notifications.
125*61c4878aSAndroid Build Coastguard Worker   epoll_event unused{};  // See BUGS under epoll_ctl(2).
126*61c4878aSAndroid Build Coastguard Worker   if (epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_DEL, fd, &unused) != 0) {
127*61c4878aSAndroid Build Coastguard Worker     switch (errno) {
128*61c4878aSAndroid Build Coastguard Worker       case ENOENT:
129*61c4878aSAndroid Build Coastguard Worker         // The file descriptor was not registered.
130*61c4878aSAndroid Build Coastguard Worker         return pw::Status::NotFound();
131*61c4878aSAndroid Build Coastguard Worker       case EBADF:
132*61c4878aSAndroid Build Coastguard Worker         PW_LOG_WARN("The fd [%d] is invalid", fd);
133*61c4878aSAndroid Build Coastguard Worker         return pw::Status::InvalidArgument();
134*61c4878aSAndroid Build Coastguard Worker     }
135*61c4878aSAndroid Build Coastguard Worker     // Other errors are likely the result of bugs and should never happen.
136*61c4878aSAndroid Build Coastguard Worker     LOG_ERROR_WITH_ERRNO("Failed to remove fd [%d] from epoll descriptor [%d]:",
137*61c4878aSAndroid Build Coastguard Worker                          errno,
138*61c4878aSAndroid Build Coastguard Worker                          fd,
139*61c4878aSAndroid Build Coastguard Worker                          epoll_fd_.fd());
140*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
141*61c4878aSAndroid Build Coastguard Worker   }
142*61c4878aSAndroid Build Coastguard Worker   --registered_line_count_;
143*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
144*61c4878aSAndroid Build Coastguard Worker }
145*61c4878aSAndroid Build Coastguard Worker 
CancelWait()146*61c4878aSAndroid Build Coastguard Worker void LinuxGpioNotifier::CancelWait() {
147*61c4878aSAndroid Build Coastguard Worker   uint64_t value = 1;
148*61c4878aSAndroid Build Coastguard Worker   // Note this is used in tests only, and failure to cancel will hang the test
149*61c4878aSAndroid Build Coastguard Worker   // or leak a thread - depending on if the test tries to join the thread.
150*61c4878aSAndroid Build Coastguard Worker   ssize_t result = cancel_event_fd_.write(&value, sizeof(value));
151*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK_INT_EQ(result,
152*61c4878aSAndroid Build Coastguard Worker                    sizeof(value),
153*61c4878aSAndroid Build Coastguard Worker                    "Failed to write cancel event: " ERRNO_FORMAT_STRING,
154*61c4878aSAndroid Build Coastguard Worker                    ERRNO_FORMAT_ARGS(errno));
155*61c4878aSAndroid Build Coastguard Worker }
156*61c4878aSAndroid Build Coastguard Worker 
WaitForEvents(int timeout_ms)157*61c4878aSAndroid Build Coastguard Worker pw::Result<unsigned int> LinuxGpioNotifier::WaitForEvents(int timeout_ms) {
158*61c4878aSAndroid Build Coastguard Worker   // Block until there is at least 1 file descriptor with an event.
159*61c4878aSAndroid Build Coastguard Worker   std::array<epoll_event, kMaxEventsPerWake> events{};
160*61c4878aSAndroid Build Coastguard Worker   int event_count;
161*61c4878aSAndroid Build Coastguard Worker   for (;;) {
162*61c4878aSAndroid Build Coastguard Worker     errno = 0;
163*61c4878aSAndroid Build Coastguard Worker     event_count =
164*61c4878aSAndroid Build Coastguard Worker         epoll_wait(epoll_fd_.fd(), events.data(), events.size(), timeout_ms);
165*61c4878aSAndroid Build Coastguard Worker     if (event_count > 0) {
166*61c4878aSAndroid Build Coastguard Worker       break;
167*61c4878aSAndroid Build Coastguard Worker     }
168*61c4878aSAndroid Build Coastguard Worker     if (event_count == 0) {
169*61c4878aSAndroid Build Coastguard Worker       return pw::Status::DeadlineExceeded();
170*61c4878aSAndroid Build Coastguard Worker     }
171*61c4878aSAndroid Build Coastguard Worker     if (errno == EINTR) {
172*61c4878aSAndroid Build Coastguard Worker       // Call was interrupted by a signal to the thread. Restart it.
173*61c4878aSAndroid Build Coastguard Worker       // NOTE: We don't attempt to update timeout_ms.
174*61c4878aSAndroid Build Coastguard Worker       continue;
175*61c4878aSAndroid Build Coastguard Worker     }
176*61c4878aSAndroid Build Coastguard Worker     LOG_CRITICAL_WITH_ERRNO("Failed to wait on epoll descriptor:", errno);
177*61c4878aSAndroid Build Coastguard Worker     return pw::Status::Internal();
178*61c4878aSAndroid Build Coastguard Worker   }
179*61c4878aSAndroid Build Coastguard Worker 
180*61c4878aSAndroid Build Coastguard Worker   // Process any lines that have events. Note that if event_count =
181*61c4878aSAndroid Build Coastguard Worker   // kMaxEvents and there are more events waiting, we will get them on the
182*61c4878aSAndroid Build Coastguard Worker   // next loop.
183*61c4878aSAndroid Build Coastguard Worker   for (int i = 0; i < event_count; i++) {
184*61c4878aSAndroid Build Coastguard Worker     if (events[i].data.ptr == kCancelToken) {
185*61c4878aSAndroid Build Coastguard Worker       return pw::Status::Cancelled();
186*61c4878aSAndroid Build Coastguard Worker     }
187*61c4878aSAndroid Build Coastguard Worker     static_cast<Handler*>(events[i].data.ptr)->HandleEvents();
188*61c4878aSAndroid Build Coastguard Worker   }
189*61c4878aSAndroid Build Coastguard Worker 
190*61c4878aSAndroid Build Coastguard Worker   // Must be positive due to (event_count > 0) check above.
191*61c4878aSAndroid Build Coastguard Worker   return event_count;
192*61c4878aSAndroid Build Coastguard Worker }
193*61c4878aSAndroid Build Coastguard Worker 
Run()194*61c4878aSAndroid Build Coastguard Worker void LinuxGpioNotifier::Run() {
195*61c4878aSAndroid Build Coastguard Worker   for (;;) {
196*61c4878aSAndroid Build Coastguard Worker     auto status = WaitForEvents(-1);
197*61c4878aSAndroid Build Coastguard Worker     if (!status.ok()) {
198*61c4878aSAndroid Build Coastguard Worker       break;
199*61c4878aSAndroid Build Coastguard Worker     }
200*61c4878aSAndroid Build Coastguard Worker   }
201*61c4878aSAndroid Build Coastguard Worker }
202*61c4878aSAndroid Build Coastguard Worker 
203*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::digital_io
204