xref: /aosp_15_r20/external/pigweed/pw_digital_io_linux/digital_io_cli.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 
15 // Usage: pw_digital_io_linux_cli COMMAND ...
16 //  Commands:
17 //    get   [-i] CHIP LINE
18 //      Configure the GPIO as an input and read its value.
19 //
20 //    set   [-i] CHIP LINE VALUE
21 //      Configure the GPIO as an output and set its value.
22 //
23 //    watch [-i] [{-ta,-tb,-td}] CHIP LINE
24 //      Configure the GPIO as an input and watch for interrupt events.
25 //
26 //      Options:
27 //        -t  Trigger for an interrupt:
28 //            -ta - activating edge
29 //            -tb - both edges (default)
30 //            -td - deactivating edge
31 //
32 //  Args:
33 //   CHIP:  gpiochip path (e.g. /dev/gpiochip0)
34 //   LINE:  line number (e.g. 1)
35 //   VALUE: the value to set (0 or 1)
36 //
37 // Options:
38 //   -i    Invert; configure as active-low.
39 
40 #include <fcntl.h>
41 #include <unistd.h>
42 
43 #include <cerrno>
44 #include <charconv>
45 #include <cstdint>
46 #include <cstring>
47 #include <iostream>
48 #include <list>
49 #include <optional>
50 #include <string>
51 
52 #include "pw_digital_io_linux/digital_io.h"
53 #include "pw_log/log.h"
54 #include "pw_status/try.h"
55 
56 using pw::digital_io::InterruptTrigger;
57 using pw::digital_io::LinuxDigitalIoChip;
58 using pw::digital_io::LinuxGpioNotifier;
59 using pw::digital_io::LinuxInputConfig;
60 using pw::digital_io::LinuxOutputConfig;
61 using pw::digital_io::Polarity;
62 using pw::digital_io::State;
63 
64 namespace {
65 
SetOutput(LinuxDigitalIoChip & chip,const LinuxOutputConfig & config)66 pw::Status SetOutput(LinuxDigitalIoChip& chip,
67                      const LinuxOutputConfig& config) {
68   auto maybe_output = chip.GetOutputLine(config);
69   if (!maybe_output.ok()) {
70     PW_LOG_ERROR("Failed to get output line: %s", maybe_output.status().str());
71     return pw::Status::Unavailable();
72   }
73   auto output = std::move(maybe_output.value());
74 
75   if (auto status = output.Enable(); !status.ok()) {
76     PW_LOG_ERROR("Failed to enable output line: %s", status.str());
77     return status;
78   }
79 
80   // Nothing to do... default value applied.
81   PW_LOG_INFO("Set line %u to %s\n",
82               config.index,
83               config.default_state == State::kActive ? "active" : "inactive");
84 
85   // NOTE: When this function returns and `output` goes out of scope, its
86   // file descriptor is closed. Depending on the GPIO driver, this could
87   // result in the pin being immediately returned to its default state.
88   // See https://manpages.debian.org/bookworm/gpiod/gpioset.1.en.html.
89 
90   return pw::OkStatus();
91 }
92 
InterruptTriggerStr(InterruptTrigger trigger)93 const char* InterruptTriggerStr(InterruptTrigger trigger) {
94   switch (trigger) {
95     case InterruptTrigger::kActivatingEdge:
96       return "activating edge";
97     case InterruptTrigger::kBothEdges:
98       return "both edges";
99     case InterruptTrigger::kDeactivatingEdge:
100       return "deactivating edge";
101     default:
102       return "?";
103   }
104 }
105 
WatchInput(LinuxDigitalIoChip & chip,const LinuxInputConfig & config,InterruptTrigger trigger)106 pw::Status WatchInput(LinuxDigitalIoChip& chip,
107                       const LinuxInputConfig& config,
108                       InterruptTrigger trigger) {
109   PW_TRY_ASSIGN(auto notifier, LinuxGpioNotifier::Create());
110 
111   auto maybe_input = chip.GetInterruptLine(config, notifier);
112   if (!maybe_input.ok()) {
113     PW_LOG_ERROR("Failed to get input line: %s", maybe_input.status().str());
114     return pw::Status::Unavailable();
115   }
116   auto input = std::move(maybe_input.value());
117 
118   auto handler = [](State state) {
119     if (state == State::kActive) {
120       std::cout << "Activated" << std::endl;
121     } else {
122       std::cout << "Deactivated" << std::endl;
123     }
124   };
125 
126   PW_TRY(input.SetInterruptHandler(trigger, handler));
127 
128   if (auto status = input.EnableInterruptHandler(); !status.ok()) {
129     PW_LOG_ERROR("Failed to enable input interrupt: %s", status.str());
130     return status;
131   }
132 
133   if (auto status = input.Enable(); !status.ok()) {
134     PW_LOG_ERROR("Failed to enable input line: %s", status.str());
135     return status;
136   }
137 
138   PW_LOG_INFO("Watching for events (%s)", InterruptTriggerStr(trigger));
139 
140   // Process events
141   notifier->Run();
142 
143   return pw::OkStatus();
144 }
145 
GetInput(LinuxDigitalIoChip & chip,const LinuxInputConfig & config)146 pw::Status GetInput(LinuxDigitalIoChip& chip, const LinuxInputConfig& config) {
147   auto maybe_input = chip.GetInputLine(config);
148   if (!maybe_input.ok()) {
149     PW_LOG_ERROR("Failed to get input line: %s", maybe_input.status().str());
150     return pw::Status::Unavailable();
151   }
152   auto input = std::move(maybe_input.value());
153 
154   if (auto status = input.Enable(); !status.ok()) {
155     PW_LOG_ERROR("Failed to enable input line: %s", status.str());
156     return status;
157   }
158 
159   auto maybe_state = input.GetState();
160   if (!maybe_state.ok()) {
161     PW_LOG_ERROR("Failed to get input line state: %s",
162                  maybe_state.status().str());
163     return pw::Status::Unavailable();
164   }
165   std::cout << (maybe_state.value() == State::kActive ? "active" : "inactive")
166             << std::endl;
167   return pw::OkStatus();
168 }
169 
UsageError(const std::string & error)170 void UsageError(const std::string& error) {
171   PW_LOG_ERROR("%s", error.c_str());
172   std::cerr << "Usage: pw_digital_io_linux_cli COMMAND ..." << std::endl;
173   std::cerr << std::endl;
174   std::cerr << "  Commands:" << std::endl;
175   std::cerr << "    get   [-i] CHIP LINE" << std::endl;
176   std::cerr << "    set   [-i] CHIP LINE VALUE" << std::endl;
177   std::cerr << "    watch [-i] [{-ta,-tb,-td}] CHIP LINE" << std::endl;
178   std::cerr << "        Options:" << std::endl;
179   std::cerr << "          -t  Trigger for an interrupt:" << std::endl;
180   std::cerr << "              -ta - activating edge" << std::endl;
181   std::cerr << "              -tb - both edges (default)" << std::endl;
182   std::cerr << "              -td - deactivating edge" << std::endl;
183   std::cerr << std::endl;
184   std::cerr << "  Common Options:" << std::endl;
185   std::cerr << "    -i    Invert; configure as active-low." << std::endl;
186 }
187 
188 }  // namespace
189 
main(int argc,char * argv[])190 int main(int argc, char* argv[]) {
191   std::list<std::string> args;
192   for (int i = 1; i < argc; ++i) {
193     args.emplace_back(argv[i]);
194   }
195 
196   // The first argument is the command name.
197   if (args.empty()) {
198     UsageError("Missing command");
199     return 1;
200   }
201   std::string command = args.front();
202   args.pop_front();
203 
204   // These are currently the only commands, and they take the same options (-i)
205   // and first two arguments (chip and line).
206   if (!(command == "get" || command == "set" || command == "watch")) {
207     UsageError("Invalid command: \"" + command + "\"");
208     return 1;
209   }
210 
211   // Process options
212   Polarity polarity = Polarity::kActiveHigh;
213   InterruptTrigger trigger = InterruptTrigger::kBothEdges;
214   for (auto argi = args.begin(); argi != args.end(); /* Advance in body. */) {
215     std::string option = *argi;
216     if (!(option.size() >= 2 && option[0] == '-')) {
217       // Not an option.
218       ++argi;  // Advance.
219       continue;
220     }
221     option = option.substr(1);
222     argi = args.erase(argi);  // Advance.
223 
224     if (option == "i") {
225       polarity = Polarity::kActiveLow;
226       continue;
227     }
228     if (command == "watch") {
229       if (option == "ta") {
230         trigger = InterruptTrigger::kActivatingEdge;
231         continue;
232       } else if (option == "tb") {
233         trigger = InterruptTrigger::kBothEdges;
234         continue;
235       } else if (option == "td") {
236         trigger = InterruptTrigger::kDeactivatingEdge;
237         continue;
238       }
239     }
240     UsageError("Invalid option: \"-" + option + "\"");
241     return 1;
242   }
243 
244   // Process args
245   if (args.size() < 2) {
246     UsageError("Missing arguments: CHIP, LINE");
247     return 1;
248   }
249   std::string path = args.front();
250   args.pop_front();
251   uint32_t index = std::stoi(args.front());
252   args.pop_front();
253 
254   // "set" also takes a value argument.
255   std::optional<State> set_value = std::nullopt;
256   if (command == "set") {
257     if (args.empty()) {
258       UsageError("Missing argument: VALUE");
259       return 1;
260     }
261     set_value =
262         (std::stoi(args.front()) > 0) ? State::kActive : State::kInactive;
263     args.pop_front();
264   }
265 
266   if (!args.empty()) {
267     UsageError("Unexpected argument: \"" + args.front() + "\"");
268     return 1;
269   }
270 
271   // Open the chip.
272   auto maybe_chip = LinuxDigitalIoChip::Open(path.c_str());
273   if (!maybe_chip.ok()) {
274     PW_LOG_ERROR("Failed to open %s: %s", path.c_str(), std::strerror(errno));
275     return 2;
276   }
277   auto chip = std::move(maybe_chip.value());
278   PW_LOG_INFO("Opened %s", path.c_str());
279 
280   // Handle the get or set.
281   pw::Status status;
282   if (command == "get") {
283     LinuxInputConfig config(
284         /* gpio_index= */ index,
285         /* gpio_polarity= */ polarity);
286     status = GetInput(chip, config);
287   } else if (command == "set") {
288     LinuxOutputConfig config(
289         /* gpio_index= */ index,
290         /* gpio_polarity= */ polarity,
291         /* gpio_default_state= */ *set_value);
292     status = SetOutput(chip, config);
293   } else if (command == "watch") {
294     LinuxInputConfig config(
295         /* gpio_index= */ index,
296         /* gpio_polarity= */ polarity);
297     status = WatchInput(chip, config, trigger);
298   }
299 
300   // Handle the return status accordingly.
301   return status.ok() ? 0 : 2;
302 }
303