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