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 #include "pw_digital_io/digital_io.h"
15
16 #include <linux/gpio.h>
17
18 #include <algorithm>
19 #include <functional>
20 #include <memory>
21 #include <mutex>
22 #include <queue>
23 #include <vector>
24
25 #include "mock_vfs.h"
26 #include "pw_digital_io_linux/digital_io.h"
27 #include "pw_log/log.h"
28 #include "pw_result/result.h"
29 #include "pw_sync/mutex.h"
30 #include "pw_sync/timed_thread_notification.h"
31 #include "pw_thread/thread.h"
32 #include "pw_thread_stl/options.h"
33 #include "pw_unit_test/framework.h"
34
35 namespace pw::digital_io {
36 namespace {
37
38 using namespace std::chrono_literals;
39
40 class DigitalIoTest;
41 class LineHandleFile;
42 class LineEventFile;
43
44 // Represents a mocked in-kernel GPIO line object.
45 class Line {
46 public:
47 //
48 // Harness-side interface: Intended for use by DigitalIoTest and the
49 // MockFile subclasses.
50 //
51
Line(uint32_t index)52 explicit Line(uint32_t index) : index_(index) {}
53
54 // Get the logical value of the line, respecting active_low.
GetValue() const55 Result<bool> GetValue() const {
56 // Linux lets you read the value of an output.
57 if (requested_ == RequestedState::kNone) {
58 PW_LOG_ERROR("Cannot get value of unrequested line");
59 return Status::FailedPrecondition();
60 }
61 return physical_state_ ^ active_low_;
62 }
63
64 // Set the logical value of the line, respecting active_low.
65 // Returns OK on success; FAILED_PRECONDITION if not requested as output.
SetValue(bool value)66 Status SetValue(bool value) {
67 if (requested_ != RequestedState::kOutput) {
68 PW_LOG_ERROR("Cannot set value of line not requested as output");
69 return Status::FailedPrecondition();
70 }
71
72 physical_state_ = value ^ active_low_;
73
74 PW_LOG_DEBUG("Set line %u to physical %u", index_, physical_state_);
75 return OkStatus();
76 }
77
RequestInput(LineHandleFile * handle,bool active_low)78 Status RequestInput(LineHandleFile* handle, bool active_low) {
79 PW_TRY(DoRequest(RequestedState::kInput, active_low));
80 current_line_handle_ = handle;
81 return OkStatus();
82 }
83
RequestInputInterrupt(LineEventFile * handle,bool active_low)84 Status RequestInputInterrupt(LineEventFile* handle, bool active_low) {
85 PW_TRY(DoRequest(RequestedState::kInputInterrupt, active_low));
86 current_event_handle_ = handle;
87 return OkStatus();
88 }
89
RequestOutput(LineHandleFile * handle,bool active_low)90 Status RequestOutput(LineHandleFile* handle, bool active_low) {
91 PW_TRY(DoRequest(RequestedState::kOutput, active_low));
92 current_line_handle_ = handle;
93 return OkStatus();
94 }
95
ClearRequest()96 void ClearRequest() {
97 requested_ = RequestedState::kNone;
98 current_line_handle_ = nullptr;
99 current_event_handle_ = nullptr;
100 }
101
102 //
103 // Test-side interface: Intended for use by the tests themselves.
104 //
105
106 enum class RequestedState {
107 kNone, // Not requested by "userspace"
108 kInput, // Requested by "userspace" as an input
109 kInputInterrupt, // Requested by "userspace" as an interrupt (event)
110 kOutput, // Requested by "userspace" as an output
111 };
112
requested() const113 RequestedState requested() const { return requested_; }
current_line_handle() const114 LineHandleFile* current_line_handle() const { return current_line_handle_; }
current_event_handle() const115 LineEventFile* current_event_handle() const { return current_event_handle_; }
116
ForcePhysicalState(bool state)117 void ForcePhysicalState(bool state) { physical_state_ = state; }
118
physical_state() const119 bool physical_state() const { return physical_state_; }
120
121 private:
122 const uint32_t index_;
123 bool physical_state_ = false;
124
125 RequestedState requested_ = RequestedState::kNone;
126 bool active_low_ = false;
127
128 LineHandleFile* current_line_handle_ = nullptr;
129 LineEventFile* current_event_handle_ = nullptr;
130
DoRequest(RequestedState request,bool active_low)131 Status DoRequest(RequestedState request, bool active_low) {
132 if (requested_ != RequestedState::kNone) {
133 PW_LOG_ERROR("Cannot request already-requested line");
134 return Status::FailedPrecondition();
135 }
136 requested_ = request;
137 active_low_ = active_low;
138 return OkStatus();
139 }
140 };
141
142 #define EXPECT_LINE_NOT_REQUESTED(line) \
143 EXPECT_EQ(line.requested(), Line::RequestedState::kNone)
144 #define EXPECT_LINE_REQUESTED_OUTPUT(line) \
145 EXPECT_EQ(line.requested(), Line::RequestedState::kOutput)
146 #define EXPECT_LINE_REQUESTED_INPUT(line) \
147 EXPECT_EQ(line.requested(), Line::RequestedState::kInput)
148 #define EXPECT_LINE_REQUESTED_INPUT_INTERRUPT(line) \
149 EXPECT_EQ(line.requested(), Line::RequestedState::kInputInterrupt)
150
151 // Represents a GPIO line handle, the result of issuing
152 // GPIO_GET_LINEHANDLE_IOCTL to an open chip file.
153 class LineHandleFile : public MockFile {
154 public:
LineHandleFile(MockVfs & vfs,int eventfd,const std::string & name,Line & line)155 LineHandleFile(MockVfs& vfs, int eventfd, const std::string& name, Line& line)
156 : MockFile(vfs, eventfd, name), line_(line) {}
157
158 private:
159 Line& line_;
160
161 //
162 // MockFile impl.
163 //
164
DoClose()165 int DoClose() override {
166 line_.ClearRequest();
167 return 0;
168 }
169
DoIoctl(unsigned long request,void * arg)170 int DoIoctl(unsigned long request, void* arg) override {
171 switch (request) {
172 case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
173 return DoIoctlGetValues(static_cast<struct gpiohandle_data*>(arg));
174 case GPIOHANDLE_SET_LINE_VALUES_IOCTL:
175 return DoIoctlSetValues(static_cast<struct gpiohandle_data*>(arg));
176 default:
177 PW_LOG_ERROR("%s: Unhandled request=0x%lX", __FUNCTION__, request);
178 return -1;
179 }
180 }
181
182 // Handle GPIOHANDLE_GET_LINE_VALUES_IOCTL
DoIoctlGetValues(struct gpiohandle_data * data)183 int DoIoctlGetValues(struct gpiohandle_data* data) {
184 auto result = line_.GetValue();
185 if (!result.ok()) {
186 return -1;
187 }
188
189 data->values[0] = *result;
190 return 0;
191 }
192
193 // Handle GPIOHANDLE_SET_LINE_VALUES_IOCTL
DoIoctlSetValues(struct gpiohandle_data * data)194 int DoIoctlSetValues(struct gpiohandle_data* data) {
195 auto status = line_.SetValue(data->values[0]);
196 if (!status.ok()) {
197 return -1;
198 }
199
200 return 0;
201 }
202 };
203
204 // Represents a GPIO line event handle, the result of issuing
205 // GPIO_GET_LINEEVENT_IOCTL to an open chip file.
206 class LineEventFile final : public MockFile {
207 public:
LineEventFile(MockVfs & vfs,int eventfd,const std::string & name,Line & line,uint32_t event_flags)208 LineEventFile(MockVfs& vfs,
209 int eventfd,
210 const std::string& name,
211 Line& line,
212 uint32_t event_flags)
213 : MockFile(vfs, eventfd, name), line_(line), event_flags_(event_flags) {}
214
EnqueueEvent(const struct gpioevent_data & event)215 void EnqueueEvent(const struct gpioevent_data& event) {
216 static_assert(GPIOEVENT_REQUEST_RISING_EDGE == GPIOEVENT_EVENT_RISING_EDGE);
217 static_assert(GPIOEVENT_REQUEST_FALLING_EDGE ==
218 GPIOEVENT_EVENT_FALLING_EDGE);
219 if ((event.id & event_flags_) == 0) {
220 return;
221 }
222
223 {
224 std::lock_guard lock(mutex_);
225 event_queue_.push(event);
226 }
227
228 // Make this file's fd readable (one token).
229 WriteEventfd();
230 }
231
232 private:
233 Line& line_;
234 uint32_t const event_flags_;
235 std::queue<struct gpioevent_data> event_queue_;
236 pw::sync::Mutex mutex_;
237
238 // Hide these
239 using MockFile::ReadEventfd;
240 using MockFile::WriteEventfd;
241
242 //
243 // MockFile impl.
244 //
245
DoClose()246 int DoClose() override {
247 line_.ClearRequest();
248 return 0;
249 }
250
DoIoctl(unsigned long request,void * arg)251 int DoIoctl(unsigned long request, void* arg) override {
252 switch (request) {
253 case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
254 return DoIoctlGetValues(static_cast<struct gpiohandle_data*>(arg));
255 // Unlinke LineHandleFile, this only supports "get", as it is only for
256 // inputs.
257 default:
258 PW_LOG_ERROR("%s: Unhandled request=0x%lX", __FUNCTION__, request);
259 return -1;
260 }
261 }
262
DoIoctlGetValues(struct gpiohandle_data * data)263 int DoIoctlGetValues(struct gpiohandle_data* data) {
264 auto result = line_.GetValue();
265 if (!result.ok()) {
266 return -1;
267 }
268
269 data->values[0] = *result;
270 return 0;
271 }
272
DoRead(void * buf,size_t count)273 ssize_t DoRead(void* buf, size_t count) override {
274 // Consume the readable state of the eventfd (one token).
275 PW_CHECK_INT_EQ(ReadEventfd(), 1); // EFD_SEMAPHORE
276
277 std::lock_guard lock(mutex_);
278
279 // Pop the event from the queue.
280 PW_CHECK(!event_queue_.empty());
281 struct gpioevent_data event = event_queue_.front();
282 if (count < sizeof(event)) {
283 return -1;
284 }
285 event_queue_.pop();
286
287 memcpy(buf, &event, sizeof(event));
288 return sizeof(event);
289 }
290 };
291
292 // Represents an open GPIO chip file, the result of opening /dev/gpiochip*.
293 class ChipFile : public MockFile {
294 public:
ChipFile(MockVfs & vfs,int eventfd,const std::string & name,std::vector<Line> & lines)295 ChipFile(MockVfs& vfs,
296 int eventfd,
297 const std::string& name,
298 std::vector<Line>& lines)
299 : MockFile(vfs, eventfd, name), lines_(lines) {}
300
301 private:
302 std::vector<Line>& lines_;
303
304 //
305 // MockFile impl.
306 //
307
DoIoctl(unsigned long request,void * arg)308 int DoIoctl(unsigned long request, void* arg) override {
309 switch (request) {
310 case GPIO_GET_LINEHANDLE_IOCTL:
311 return DoLinehandleIoctl(static_cast<struct gpiohandle_request*>(arg));
312 case GPIO_GET_LINEEVENT_IOCTL:
313 return DoLineeventIoctl(static_cast<struct gpioevent_request*>(arg));
314 default:
315 PW_LOG_ERROR("%s: Unhandled request=0x%lX", __FUNCTION__, request);
316 return -1;
317 }
318 }
319
320 // Handle GPIO_GET_LINEHANDLE_IOCTL
DoLinehandleIoctl(struct gpiohandle_request * req)321 int DoLinehandleIoctl(struct gpiohandle_request* req) {
322 uint32_t const direction =
323 req->flags & (GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_INPUT);
324
325 // Validate flags.
326 if (direction == (GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_INPUT)) {
327 PW_LOG_ERROR("%s: OUTPUT and INPUT are mutually exclusive", __FUNCTION__);
328 return -1;
329 }
330
331 // Only support requesting one line at at time.
332 if (req->lines != 1) {
333 PW_LOG_ERROR("%s: Unsupported req->lines=%u", __FUNCTION__, req->lines);
334 return -1;
335 }
336
337 uint32_t const offset = req->lineoffsets[0];
338 uint8_t const default_value = req->default_values[0];
339 bool const active_low = req->flags & GPIOHANDLE_REQUEST_ACTIVE_LOW;
340
341 if (offset >= lines_.size()) {
342 PW_LOG_ERROR("%s: Invalid line offset: %u", __FUNCTION__, offset);
343 return -1;
344 }
345 Line& line = lines_[offset];
346
347 auto file = vfs().MakeFile<LineHandleFile>("line-handle", line);
348 // Ownership: The vfs owns this file, but the line borrows a reference to
349 // it. This is safe because the file's Close() method undoes that borrow.
350
351 Status status = OkStatus();
352 switch (direction) {
353 case GPIOHANDLE_REQUEST_OUTPUT:
354 status.Update(line.RequestOutput(file.get(), active_low));
355 status.Update(line.SetValue(default_value));
356 break;
357 case GPIOHANDLE_REQUEST_INPUT:
358 status.Update(line.RequestInput(file.get(), active_low));
359 break;
360 }
361 if (!status.ok()) {
362 return -1;
363 }
364
365 req->fd = vfs().InstallFile(std::move(file));
366 return 0;
367 }
368
DoLineeventIoctl(struct gpioevent_request * req)369 int DoLineeventIoctl(struct gpioevent_request* req) {
370 uint32_t const direction = req->handleflags & (GPIOHANDLE_REQUEST_OUTPUT |
371 GPIOHANDLE_REQUEST_INPUT);
372 bool const active_low = req->handleflags & GPIOHANDLE_REQUEST_ACTIVE_LOW;
373 uint32_t const offset = req->lineoffset;
374
375 if (direction != GPIOHANDLE_REQUEST_INPUT) {
376 PW_LOG_ERROR("%s: Only input is supported by this ioctl", __FUNCTION__);
377 return -1;
378 }
379
380 if (offset >= lines_.size()) {
381 PW_LOG_ERROR("%s: Invalid line offset: %u", __FUNCTION__, offset);
382 return -1;
383 }
384 Line& line = lines_[offset];
385
386 auto file =
387 vfs().MakeFile<LineEventFile>("line-event", line, req->eventflags);
388 // Ownership: The vfs() owns this file, but the line borrows a reference to
389 // it. This is safe because the file's Close() method undoes that borrow.
390
391 Status status = line.RequestInputInterrupt(file.get(), active_low);
392 if (!status.ok()) {
393 return -1;
394 }
395
396 req->fd = vfs().InstallFile(std::move(file));
397 return 0;
398 }
399 };
400
401 // Test fixture for all digtal io tests.
402 class DigitalIoTest : public ::testing::Test {
403 protected:
SetUp()404 void SetUp() override { GetMockVfs().Reset(); }
405
TearDown()406 void TearDown() override { EXPECT_TRUE(GetMockVfs().AllFdsClosed()); }
407
OpenChip()408 LinuxDigitalIoChip OpenChip() {
409 int fd = GetMockVfs().InstallNewFile<ChipFile>("chip", lines_);
410 return LinuxDigitalIoChip(fd);
411 }
412
line0()413 Line& line0() { return lines_[0]; }
line1()414 Line& line1() { return lines_[1]; }
415
416 private:
417 std::vector<Line> lines_ = std::vector<Line>{
418 Line(0), // Input
419 Line(1), // Output
420 };
421 };
422
423 //
424 // Tests
425 //
426
TEST_F(DigitalIoTest,DoInput)427 TEST_F(DigitalIoTest, DoInput) {
428 LinuxDigitalIoChip chip = OpenChip();
429
430 auto& line = line0();
431 LinuxInputConfig config(
432 /* gpio_index= */ 0,
433 /* gpio_polarity= */ Polarity::kActiveHigh);
434
435 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input, chip.GetInputLine(config));
436
437 // Enable the input, and ensure it is requested.
438 EXPECT_LINE_NOT_REQUESTED(line);
439 PW_TEST_ASSERT_OK(input.Enable());
440 EXPECT_LINE_REQUESTED_INPUT(line);
441
442 Result<State> state;
443
444 // Force the line high and assert it is seen as active (active high).
445 line.ForcePhysicalState(true);
446 state = input.GetState();
447 PW_TEST_ASSERT_OK(state.status());
448 ASSERT_EQ(State::kActive, state.value());
449
450 // Force the line low and assert it is seen as inactive (active high).
451 line.ForcePhysicalState(false);
452 state = input.GetState();
453 PW_TEST_ASSERT_OK(state.status());
454 ASSERT_EQ(State::kInactive, state.value());
455
456 // Disable the line and ensure it is no longer requested.
457 PW_TEST_ASSERT_OK(input.Disable());
458 EXPECT_LINE_NOT_REQUESTED(line);
459 }
460
TEST_F(DigitalIoTest,DoInputInvert)461 TEST_F(DigitalIoTest, DoInputInvert) {
462 LinuxDigitalIoChip chip = OpenChip();
463
464 auto& line = line0();
465 LinuxInputConfig config(
466 /* gpio_index= */ 0,
467 /* gpio_polarity= */ Polarity::kActiveLow);
468
469 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input, chip.GetInputLine(config));
470
471 // Enable the input, and ensure it is requested.
472 EXPECT_LINE_NOT_REQUESTED(line);
473 PW_TEST_ASSERT_OK(input.Enable());
474 EXPECT_LINE_REQUESTED_INPUT(line);
475
476 Result<State> state;
477
478 // Force the line high and assert it is seen as inactive (active low).
479 line.ForcePhysicalState(true);
480 state = input.GetState();
481 PW_TEST_ASSERT_OK(state.status());
482 ASSERT_EQ(State::kInactive, state.value());
483
484 // Force the line low and assert it is seen as active (active low).
485 line.ForcePhysicalState(false);
486 state = input.GetState();
487 PW_TEST_ASSERT_OK(state.status());
488 ASSERT_EQ(State::kActive, state.value());
489
490 // Disable the line and ensure it is no longer requested.
491 PW_TEST_ASSERT_OK(input.Disable());
492 EXPECT_LINE_NOT_REQUESTED(line);
493 }
494
TEST_F(DigitalIoTest,DoOutput)495 TEST_F(DigitalIoTest, DoOutput) {
496 LinuxDigitalIoChip chip = OpenChip();
497
498 auto& line = line1();
499 LinuxOutputConfig config(
500 /* gpio_index= */ 1,
501 /* gpio_polarity= */ Polarity::kActiveHigh,
502 /* gpio_default_state== */ State::kActive);
503
504 PW_TEST_ASSERT_OK_AND_ASSIGN(auto output, chip.GetOutputLine(config));
505
506 // Enable the output, and ensure it is requested.
507 EXPECT_LINE_NOT_REQUESTED(line);
508 PW_TEST_ASSERT_OK(output.Enable());
509 EXPECT_LINE_REQUESTED_OUTPUT(line);
510
511 // Expect the line to go high, due to default_state=kActive (active high).
512 ASSERT_TRUE(line.physical_state());
513
514 // Set the output's state to inactive, and assert it goes low (active high).
515 PW_TEST_ASSERT_OK(output.SetStateInactive());
516 ASSERT_FALSE(line.physical_state());
517
518 // Set the output's state to active, and assert it goes high (active high).
519 PW_TEST_ASSERT_OK(output.SetStateActive());
520 ASSERT_TRUE(line.physical_state());
521
522 // Disable the line and ensure it is no longer requested.
523 PW_TEST_ASSERT_OK(output.Disable());
524 EXPECT_LINE_NOT_REQUESTED(line);
525 // NOTE: We do not assert line.physical_state() here.
526 // See the warning on LinuxDigitalOut in docs.rst.
527 }
528
TEST_F(DigitalIoTest,DoOutputInvert)529 TEST_F(DigitalIoTest, DoOutputInvert) {
530 LinuxDigitalIoChip chip = OpenChip();
531
532 auto& line = line1();
533 LinuxOutputConfig config(
534 /* gpio_index= */ 1,
535 /* gpio_polarity= */ Polarity::kActiveLow,
536 /* gpio_default_state== */ State::kActive);
537
538 PW_TEST_ASSERT_OK_AND_ASSIGN(auto output, chip.GetOutputLine(config));
539
540 // Enable the output, and ensure it is requested.
541 EXPECT_LINE_NOT_REQUESTED(line);
542 PW_TEST_ASSERT_OK(output.Enable());
543 EXPECT_LINE_REQUESTED_OUTPUT(line);
544
545 // Expect the line to stay low, due to default_state=kActive (active low).
546 ASSERT_FALSE(line.physical_state());
547
548 // Set the output's state to inactive, and assert it goes high (active low).
549 PW_TEST_ASSERT_OK(output.SetStateInactive());
550 ASSERT_TRUE(line.physical_state());
551
552 // Set the output's state to active, and assert it goes low (active low).
553 PW_TEST_ASSERT_OK(output.SetStateActive());
554 ASSERT_FALSE(line.physical_state());
555
556 // Disable the line and ensure it is no longer requested.
557 PW_TEST_ASSERT_OK(output.Disable());
558 EXPECT_LINE_NOT_REQUESTED(line);
559 // NOTE: We do not assert line.physical_state() here.
560 // See the warning on LinuxDigitalOut in docs.rst.
561 }
562
563 // Verify we can get the state of an output.
TEST_F(DigitalIoTest,OutputGetState)564 TEST_F(DigitalIoTest, OutputGetState) {
565 LinuxDigitalIoChip chip = OpenChip();
566
567 auto& line = line1();
568 LinuxOutputConfig config(
569 /* gpio_index= */ 1,
570 /* gpio_polarity= */ Polarity::kActiveHigh,
571 /* gpio_default_state== */ State::kInactive);
572
573 PW_TEST_ASSERT_OK_AND_ASSIGN(auto output, chip.GetOutputLine(config));
574
575 PW_TEST_ASSERT_OK(output.Enable());
576
577 // Expect the line to stay low, due to default_state=kInactive (active high).
578 ASSERT_FALSE(line.physical_state());
579
580 Result<State> state;
581
582 // Verify GetState() returns the expected state: inactive (default_state).
583 state = output.GetState();
584 PW_TEST_ASSERT_OK(state.status());
585 ASSERT_EQ(State::kInactive, state.value());
586
587 // Set the output's state to active, then verify GetState() returns the
588 // new expected state.
589 PW_TEST_ASSERT_OK(output.SetStateActive());
590 state = output.GetState();
591 PW_TEST_ASSERT_OK(state.status());
592 ASSERT_EQ(State::kActive, state.value());
593 }
594
595 //
596 // Input interrupts
597 //
598
TEST_F(DigitalIoTest,DoInputInterruptsEnabledBefore)599 TEST_F(DigitalIoTest, DoInputInterruptsEnabledBefore) {
600 LinuxDigitalIoChip chip = OpenChip();
601 PW_TEST_ASSERT_OK_AND_ASSIGN(auto notifier, LinuxGpioNotifier::Create());
602
603 auto& line = line0();
604 LinuxInputConfig config(
605 /* gpio_index= */ 0,
606 /* gpio_polarity= */ Polarity::kActiveHigh);
607
608 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input,
609 chip.GetInterruptLine(config, notifier));
610
611 EXPECT_LINE_NOT_REQUESTED(line);
612
613 // Have to set a handler before we can enable interrupts.
614 PW_TEST_ASSERT_OK(input.SetInterruptHandler(InterruptTrigger::kActivatingEdge,
615 [](State) {}));
616
617 // pw_digital_io says the line should be enabled before calling
618 // EnableInterruptHandler(), but we explicitly support it being called with
619 // the line disabled to avoid an unnecessary file close/reopen.
620 PW_TEST_ASSERT_OK(input.EnableInterruptHandler());
621 PW_TEST_ASSERT_OK(input.Enable());
622
623 // Interrupts requested; should be a line event handle.
624 EXPECT_LINE_REQUESTED_INPUT_INTERRUPT(line);
625
626 // Disable; nothing should be requested.
627 PW_TEST_ASSERT_OK(input.Disable());
628 EXPECT_LINE_NOT_REQUESTED(line);
629 }
630
TEST_F(DigitalIoTest,DoInputInterruptsEnabledAfter)631 TEST_F(DigitalIoTest, DoInputInterruptsEnabledAfter) {
632 LinuxDigitalIoChip chip = OpenChip();
633 PW_TEST_ASSERT_OK_AND_ASSIGN(auto notifier, LinuxGpioNotifier::Create());
634
635 auto& line = line0();
636 LinuxInputConfig config(
637 /* gpio_index= */ 0,
638 /* gpio_polarity= */ Polarity::kActiveHigh);
639
640 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input,
641 chip.GetInterruptLine(config, notifier));
642
643 EXPECT_LINE_NOT_REQUESTED(line);
644
645 PW_TEST_ASSERT_OK(input.Enable());
646
647 // No interrupts requested; should be a normal line handle.
648 EXPECT_LINE_REQUESTED_INPUT(line);
649
650 // Interrupts requested while enabled; should be a line event handle.
651 // Have to set a handler before we can enable interrupts.
652 PW_TEST_ASSERT_OK(input.SetInterruptHandler(InterruptTrigger::kActivatingEdge,
653 [](State) {}));
654 PW_TEST_ASSERT_OK(input.EnableInterruptHandler());
655 EXPECT_LINE_REQUESTED_INPUT_INTERRUPT(line);
656
657 // Interrupts disabled while enabled; should revert to a normal line handle.
658 PW_TEST_ASSERT_OK(input.DisableInterruptHandler());
659 EXPECT_LINE_REQUESTED_INPUT(line);
660
661 // Disable; nothing should be requested.
662 PW_TEST_ASSERT_OK(input.Disable());
663 EXPECT_LINE_NOT_REQUESTED(line);
664 }
665
TEST_F(DigitalIoTest,DoInputInterruptsReadOne)666 TEST_F(DigitalIoTest, DoInputInterruptsReadOne) {
667 LinuxDigitalIoChip chip = OpenChip();
668 PW_TEST_ASSERT_OK_AND_ASSIGN(auto notifier, LinuxGpioNotifier::Create());
669
670 auto& line = line0();
671 LinuxInputConfig config(
672 /* gpio_index= */ 0,
673 /* gpio_polarity= */ Polarity::kActiveHigh);
674
675 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input,
676 chip.GetInterruptLine(config, notifier));
677
678 std::vector<State> interrupts;
679 auto handler = [&interrupts](State state) {
680 PW_LOG_DEBUG("Interrupt handler fired with state=%s",
681 state == State::kActive ? "active" : "inactive");
682 interrupts.push_back(state);
683 };
684
685 PW_TEST_ASSERT_OK(
686 input.SetInterruptHandler(InterruptTrigger::kActivatingEdge, handler));
687
688 // pw_digital_io says the line should be enabled before calling
689 // EnableInterruptHandler(), but we explicitly support it being called with
690 // the line disabled to avoid an unnecessary file close/reopen.
691 PW_TEST_ASSERT_OK(input.EnableInterruptHandler());
692 PW_TEST_ASSERT_OK(input.Enable());
693
694 EXPECT_LINE_REQUESTED_INPUT_INTERRUPT(line);
695 LineEventFile* evt = line.current_event_handle();
696 ASSERT_NE(evt, nullptr);
697
698 evt->EnqueueEvent({
699 .timestamp = 1122334455667788,
700 .id = GPIOEVENT_EVENT_RISING_EDGE,
701 });
702
703 constexpr int timeout = 0; // Don't block
704 PW_LOG_DEBUG("WaitForEvents(%d)", timeout);
705 PW_TEST_ASSERT_OK_AND_ASSIGN(unsigned int count,
706 notifier->WaitForEvents(timeout));
707 EXPECT_EQ(count, 1u);
708
709 EXPECT_EQ(interrupts,
710 std::vector<State>({
711 State::kActive,
712 }));
713 }
714
TEST_F(DigitalIoTest,DoInputInterruptsThread)715 TEST_F(DigitalIoTest, DoInputInterruptsThread) {
716 LinuxDigitalIoChip chip = OpenChip();
717 PW_TEST_ASSERT_OK_AND_ASSIGN(auto notifier, LinuxGpioNotifier::Create());
718
719 auto& line = line0();
720 LinuxInputConfig config(
721 /* gpio_index= */ 0,
722 /* gpio_polarity= */ Polarity::kActiveHigh);
723
724 PW_TEST_ASSERT_OK_AND_ASSIGN(auto input,
725 chip.GetInterruptLine(config, notifier));
726
727 constexpr unsigned int kCount = 10;
728 struct {
729 sync::TimedThreadNotification done;
730 std::vector<State> interrupts;
731
732 void HandleInterrupt(State state) {
733 interrupts.push_back(state);
734 if (interrupts.size() == kCount) {
735 done.release();
736 }
737 }
738 } context;
739
740 auto handler = [&context](State state) {
741 PW_LOG_DEBUG("Interrupt handler fired with state=%s",
742 state == State::kActive ? "active" : "inactive");
743 context.HandleInterrupt(state);
744 };
745
746 PW_TEST_ASSERT_OK(
747 input.SetInterruptHandler(InterruptTrigger::kBothEdges, handler));
748
749 // pw_digital_io says the line should be enabled before calling
750 // EnableInterruptHandler(), but we explicitly support it being called with
751 // the line disabled to avoid an unnecessary file close/reopen.
752 PW_TEST_ASSERT_OK(input.EnableInterruptHandler());
753 PW_TEST_ASSERT_OK(input.Enable());
754
755 // Run a notifier thread.
756 pw::Thread notif_thread(pw::thread::stl::Options(), *notifier);
757
758 EXPECT_LINE_REQUESTED_INPUT_INTERRUPT(line);
759 LineEventFile* evt = line.current_event_handle();
760 ASSERT_NE(evt, nullptr);
761
762 // Feed the line with events.
763 auto nth_event = [](unsigned int i) -> uint32_t {
764 return (i % 2) ? GPIOEVENT_EVENT_FALLING_EDGE : GPIOEVENT_EVENT_RISING_EDGE;
765 };
766 auto nth_state = [](unsigned int i) -> State {
767 return (i % 2) ? State::kInactive : State::kActive;
768 };
769
770 for (unsigned int i = 0; i < kCount; i++) {
771 evt->EnqueueEvent({
772 .timestamp = 1122334400000000u + i,
773 .id = nth_event(i),
774 });
775 }
776
777 // Wait for the notifier to pick them all up.
778 constexpr auto kWaitForDataTimeout = 1000ms;
779 ASSERT_TRUE(context.done.try_acquire_for(kWaitForDataTimeout));
780
781 // Stop the notifier thread.
782 notifier->CancelWait();
783 notif_thread.join();
784
785 // Verify we received all of the expected callbacks.
786 EXPECT_EQ(context.interrupts.size(), kCount);
787 for (unsigned int i = 0; i < kCount; i++) {
788 EXPECT_EQ(context.interrupts[i], nth_state(i));
789 }
790 }
791
792 } // namespace
793 } // namespace pw::digital_io
794