• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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