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