xref: /aosp_15_r20/external/pigweed/pw_digital_io_linux/digital_io_test.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 #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