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