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