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