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