xref: /aosp_15_r20/external/pigweed/pw_digital_io_linux/public/pw_digital_io_linux/digital_io.h (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 
15 #pragma once
16 
17 #include <cstdint>
18 #include <memory>
19 
20 #include "pw_digital_io/digital_io.h"
21 #include "pw_digital_io/polarity.h"
22 #include "pw_digital_io_linux/internal/owned_fd.h"
23 #include "pw_digital_io_linux/notifier.h"
24 #include "pw_result/result.h"
25 #include "pw_sync/lock_annotations.h"
26 #include "pw_sync/mutex.h"
27 
28 namespace pw::digital_io {
29 
30 struct LinuxConfig {
31   uint32_t index;
32   Polarity polarity;
33 
LinuxConfigLinuxConfig34   LinuxConfig(uint32_t gpio_index, Polarity gpio_polarity)
35       : index(gpio_index), polarity(gpio_polarity) {}
36   uint32_t GetFlags() const;
37 };
38 
39 struct LinuxInputConfig final : LinuxConfig {
LinuxInputConfigfinal40   LinuxInputConfig(uint32_t gpio_index, Polarity gpio_polarity)
41       : LinuxConfig(gpio_index, gpio_polarity) {}
42   uint32_t GetFlags() const;
43 };
44 
45 struct LinuxOutputConfig final : LinuxConfig {
46   State default_state;
47 
LinuxOutputConfigfinal48   LinuxOutputConfig(uint32_t gpio_index,
49                     Polarity gpio_polarity,
50                     State gpio_default_state)
51       : LinuxConfig(gpio_index, gpio_polarity),
52         default_state(gpio_default_state) {}
53   uint32_t GetFlags() const;
54 };
55 
56 class LinuxDigitalInInterrupt;
57 class LinuxDigitalIn;
58 class LinuxDigitalOut;
59 
60 /// Represents an open handle to a Linux GPIO chip (e.g. /dev/gpiochip0).
61 class LinuxDigitalIoChip final {
62   friend class LinuxDigitalInInterrupt;
63   friend class LinuxDigitalIn;
64   friend class LinuxDigitalOut;
65   using OwnedFd = internal::OwnedFd;
66 
67  private:
68   // Implementation
69   class Impl {
70    public:
Impl(int fd)71     Impl(int fd) : fd_(fd) {}
72 
73     Result<OwnedFd> GetLineHandle(uint32_t offset,
74                                   uint32_t flags,
75                                   uint8_t default_value = 0);
76 
77     Result<OwnedFd> GetLineEventHandle(uint32_t offset,
78                                        uint32_t handle_flags,
79                                        uint32_t event_flags);
80 
81    private:
82     OwnedFd fd_;
83   };
84 
85   std::shared_ptr<Impl> impl_;
86 
87  public:
LinuxDigitalIoChip(int fd)88   explicit LinuxDigitalIoChip(int fd) : impl_(std::make_shared<Impl>(fd)) {}
89 
90   static Result<LinuxDigitalIoChip> Open(const char* path);
91 
Close()92   void Close() { impl_ = nullptr; }
93 
94   Result<LinuxDigitalInInterrupt> GetInterruptLine(
95       const LinuxInputConfig& config,
96       std::shared_ptr<LinuxGpioNotifier> notifier);
97 
98   Result<LinuxDigitalIn> GetInputLine(const LinuxInputConfig& config);
99 
100   Result<LinuxDigitalOut> GetOutputLine(const LinuxOutputConfig& config);
101 };
102 
103 class LinuxDigitalInInterrupt final : public DigitalInInterrupt {
104   friend class LinuxDigitalIoChip;
105 
106  private:
LinuxDigitalInInterrupt(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,const LinuxInputConfig & config,std::shared_ptr<LinuxGpioNotifier> notifier)107   explicit LinuxDigitalInInterrupt(
108       std::shared_ptr<LinuxDigitalIoChip::Impl> chip,
109       const LinuxInputConfig& config,
110       std::shared_ptr<LinuxGpioNotifier> notifier)
111       : impl_(std::make_shared<Impl>(chip, config, notifier)) {}
112 
113   // DigitalInInterrupt impl.
114   // These simply forward to the private impl class.
DoEnable(bool enable)115   Status DoEnable(bool enable) override { return impl_->DoEnable(enable); }
116 
DoGetState()117   Result<State> DoGetState() override { return impl_->DoGetState(); }
118 
DoSetInterruptHandler(InterruptTrigger trigger,InterruptHandler && handler)119   Status DoSetInterruptHandler(InterruptTrigger trigger,
120                                InterruptHandler&& handler) override {
121     return impl_->DoSetInterruptHandler(trigger, std::move(handler));
122   }
123 
DoEnableInterruptHandler(bool enable)124   Status DoEnableInterruptHandler(bool enable) override {
125     return impl_->DoEnableInterruptHandler(enable);
126   }
127 
128   // Internal impl for shared_ptr
129   // As a nested class, this doesn't really need to implement
130   // DigitalInInterrupt, but it makes things clearer and easy to forward calls
131   // from the containing class.
132   class Impl final : public DigitalInInterrupt,
133                      public LinuxGpioNotifier::Handler {
134    public:
Impl(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,const LinuxInputConfig & config,std::shared_ptr<LinuxGpioNotifier> notifier)135     explicit Impl(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,
136                   const LinuxInputConfig& config,
137                   std::shared_ptr<LinuxGpioNotifier> notifier)
138         : chip_(std::move(chip)),
139           config_(config),
140           notifier_(std::move(notifier)) {}
141 
142     ~Impl() override;
143 
144     // The notifier holds a reference to this object, so disable
145     // the ability to copy or move it.
146     Impl(const Impl&) = delete;
147     Impl& operator=(const Impl&) = delete;
148     Impl(Impl&&) = delete;
149     Impl& operator=(Impl&&) = delete;
150 
151     // DigitalInInterrupt impl.
152     Status DoEnable(bool enable) override;
153     Result<State> DoGetState() override;
154     Status DoSetInterruptHandler(InterruptTrigger trigger,
155                                  InterruptHandler&& handler) override;
156     Status DoEnableInterruptHandler(bool enable) override;
157 
158    private:
159     // The parent chip object.
160     const std::shared_ptr<LinuxDigitalIoChip::Impl> chip_;
161 
162     // The desired configuration of this line.
163     LinuxInputConfig const config_;
164 
165     // The notifier is inherently thread-safe.
166     const std::shared_ptr<LinuxGpioNotifier> notifier_;
167 
168     // Line handle or line event fd, depending on fd_is_event_handle_.
169     internal::OwnedFd fd_;
170 
171     // The type of file currently in fd_:
172     //   true: fd_ is a "lineevent" file (from GPIO_GET_LINEEVENT_IOCTL).
173     //   false: fd_ is a "linehandle" file (from GPIO_GET_LINEHANDLE_IOCTL).
174     bool fd_is_event_handle_ = false;
175 
176     // Interrupts have been requested by user via DoEnableInterruptHandler().
177     bool interrupts_desired_ = false;
178 
179     // The handler and trigger configured by DoSetInterruptHandler().
180     InterruptHandler handler_ = nullptr;
181     InterruptTrigger trigger_ = {};
182     uint32_t handler_generation_ = 0;
183 
184     // Guards access to line state, primarily for synchronizing with
185     // interrupt callbacks.
186     sync::Mutex mutex_;
187 
188     //
189     // Methods
190     //
191 
192     // LinuxGpioNotifier::Handler impl.
193     void HandleEvents() override PW_LOCKS_EXCLUDED(mutex_);
194 
195     // Private methods
196     Status OpenHandle() PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
197     void CloseHandle() PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
198     Status SubscribeEvents() PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
199     Status UnsubscribeEvents() PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
200     uint32_t GetEventFlags() const PW_SHARED_LOCKS_REQUIRED(mutex_);
enabled()201     bool enabled() const PW_SHARED_LOCKS_REQUIRED(mutex_) {
202       return fd_.valid();
203     }
interrupts_enabled()204     bool interrupts_enabled() const PW_SHARED_LOCKS_REQUIRED(mutex_) {
205       return enabled() && interrupts_desired_;
206     }
207   };  // class Impl
208 
209   const std::shared_ptr<Impl> impl_;
210 };
211 
212 class LinuxDigitalIn final : public DigitalIn {
213   friend class LinuxDigitalIoChip;
214 
215  private:
LinuxDigitalIn(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,const LinuxInputConfig & config)216   explicit LinuxDigitalIn(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,
217                           const LinuxInputConfig& config)
218       : chip_(std::move(chip)), config_(config) {}
219 
220   Status DoEnable(bool enable) override;
221   Result<State> DoGetState() override;
222 
enabled()223   bool enabled() { return fd_.valid(); }
224 
225   std::shared_ptr<LinuxDigitalIoChip::Impl> chip_;
226   LinuxInputConfig const config_;
227   internal::OwnedFd fd_;
228 };
229 
230 class LinuxDigitalOut final : public DigitalInOut {
231   friend class LinuxDigitalIoChip;
232 
233  private:
LinuxDigitalOut(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,const LinuxOutputConfig & config)234   explicit LinuxDigitalOut(std::shared_ptr<LinuxDigitalIoChip::Impl> chip,
235                            const LinuxOutputConfig& config)
236       : chip_(std::move(chip)), config_(config) {}
237 
238   Status DoEnable(bool enable) override;
239   Result<State> DoGetState() override;
240   Status DoSetState(State level) override;
241 
enabled()242   bool enabled() { return fd_.valid(); }
243 
244   std::shared_ptr<LinuxDigitalIoChip::Impl> chip_;
245   LinuxOutputConfig const config_;
246   internal::OwnedFd fd_;
247 };
248 
249 }  // namespace pw::digital_io
250