xref: /aosp_15_r20/external/pigweed/pw_spi_linux/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 #include <errno.h>
16 #include <fcntl.h>
17 #include <getopt.h>
18 #include <string.h>
19 #include <unistd.h>
20 
21 #include <cctype>
22 #include <charconv>
23 #include <fstream>
24 #include <iomanip>
25 #include <iostream>
26 #include <optional>
27 #include <string>
28 #include <string_view>
29 #include <vector>
30 
31 #include "pw_log/log.h"
32 #include "pw_preprocessor/util.h"
33 #include "pw_result/result.h"
34 #include "pw_spi_linux/spi.h"
35 #include "pw_status/status.h"
36 
37 namespace pw::spi {
38 namespace {
39 
40 constexpr unsigned int kDefaultMode = 0;
41 constexpr unsigned int kDefaultBits = 8;
42 
43 const struct option kLongOptions[] = {
44     {"bits", required_argument, nullptr, 'b'},
45     {"device", required_argument, nullptr, 'D'},
46     {"freq", required_argument, nullptr, 'F'},
47     {"human", no_argument, nullptr, 'h'},
48     {"input", required_argument, nullptr, 'i'},
49     {"lsb", no_argument, nullptr, 'l'},
50     {"mode", required_argument, nullptr, 'm'},
51     {"output", required_argument, nullptr, 'o'},
52     {"rx-count", required_argument, nullptr, 'r'},
53     {},  // terminator
54 };
55 
56 // Starts with ':' to skip printing errors and return ':' for a missing option
57 // argument.
58 const char* kShortOptions = ":b:D:F:hi:lm:o:r:";
59 
Usage()60 void Usage() {
61   std::cerr << "Usage: pw_spi_linux_cli -D DEVICE -F FREQ [flags]" << std::endl;
62   std::cerr << "Required flags:" << std::endl;
63   std::cerr << "  -D/--device   SPI device path (e.g. /dev/spidev0.0"
64             << std::endl;
65   std::cerr << "  -F/--freq     SPI clock frequency in Hz (e.g. 24000000)"
66             << std::endl;
67   std::cerr << std::endl;
68   std::cerr << "Optional flags:" << std::endl;
69   std::cerr << "  -b/--bits     Bits per word, default: " << kDefaultBits
70             << std::endl;
71   std::cerr << "  -h/--human    Human-readable output (default: binary, "
72                "unless output to stdout tty)"
73             << std::endl;
74   std::cerr << "  -i/--input    Input file, or - for stdin" << std::endl;
75   std::cerr << "                If not given, no data is sent." << std::endl;
76   std::cerr << "  -l/--lsb      LSB first (default: MSB first)" << std::endl;
77   std::cerr << "  -m/--mode     SPI mode (0-3), default: " << kDefaultMode
78             << std::endl;
79   std::cerr << "  -o/--output   Output file (default: stdout)" << std::endl;
80   std::cerr << "  -r/--rx-count Number of bytes to receive (defaults to size "
81                "of input)"
82             << std::endl;
83 }
84 
85 struct Args {
86   std::string device;
87   unsigned int frequency = 0;
88   std::optional<std::string> input_path;
89   std::string output_path = "-";
90   bool human_readable = false;
91   std::optional<unsigned int> rx_count;
92 
93   unsigned int mode = kDefaultMode;
94   unsigned int bits = kDefaultBits;
95   bool lsb_first = false;
96 
GetSpiConfigpw::spi::__anona050ae370111::Args97   Config GetSpiConfig() const {
98     return {
99         .polarity = (mode & 0b10) ? ClockPolarity::kActiveLow
100                                   : ClockPolarity::kActiveHigh,
101         .phase =
102             (mode & 0b01) ? ClockPhase::kFallingEdge : ClockPhase::kRisingEdge,
103         .bits_per_word = bits,
104         .bit_order = lsb_first ? BitOrder::kLsbFirst : BitOrder::kMsbFirst,
105     };
106   }
107 };
108 
109 template <class T>
ParseNumber(std::string_view str)110 std::optional<T> ParseNumber(std::string_view str) {
111   T value{};
112   const auto* str_end = str.data() + str.size();
113   auto [ptr, ec] = std::from_chars(str.data(), str_end, value);
114   if (ec == std::errc() && ptr == str_end) {
115     return value;
116   }
117   return std::nullopt;
118 }
119 
ParseArgs(int argc,char * argv[])120 Result<Args> ParseArgs(int argc, char* argv[]) {
121   Args args;
122   bool human_readable_given;
123 
124   while (true) {
125     int current_optind = optind;
126     int c = getopt_long(argc, argv, kShortOptions, kLongOptions, nullptr);
127     if (c == -1) {
128       break;
129     }
130 
131     switch (c) {
132       case 'b': {
133         auto bits = ParseNumber<unsigned int>(optarg);
134         if (bits > 32) {
135           PW_LOG_ERROR("Invalid bits : %s", optarg);
136           return Status::InvalidArgument();
137         }
138         args.bits = bits.value();
139         break;
140       }
141       case 'D':
142         args.device = optarg;
143         break;
144       case 'F': {
145         auto freq = ParseNumber<unsigned int>(optarg);
146         if (!freq || freq.value() == 0) {
147           PW_LOG_ERROR("Invalid frequency: %s", optarg);
148           return Status::InvalidArgument();
149         }
150         args.frequency = freq.value();
151         break;
152       }
153       case 'h':
154         human_readable_given = true;
155         break;
156       case 'i':
157         args.input_path = optarg;
158         break;
159       case 'l':
160         args.lsb_first = true;
161         break;
162       case 'm': {
163         auto mode = ParseNumber<unsigned int>(optarg);
164         if (!mode || mode.value() > 3) {
165           PW_LOG_ERROR("Invalid mode: %s", optarg);
166           return Status::InvalidArgument();
167         }
168         args.mode = mode.value();
169         break;
170       }
171       case 'o':
172         args.output_path = optarg;
173         break;
174       case 'r': {
175         auto count = ParseNumber<unsigned int>(optarg);
176         if (!count) {
177           PW_LOG_ERROR("Invalid count: %s", optarg);
178           return Status::InvalidArgument();
179         }
180         args.rx_count = count;
181         break;
182       }
183       case '?':
184         if (optopt) {
185           PW_LOG_ERROR("Invalid flag: -%c", optopt);
186         } else {
187           PW_LOG_ERROR("Invalid flag: %s", argv[current_optind]);
188         }
189         Usage();
190         return Status::InvalidArgument();
191       case ':':
192         PW_LOG_ERROR("Missing argument to %s", argv[current_optind]);
193         return Status::InvalidArgument();
194     }
195   }
196 
197   args.human_readable = human_readable_given ||
198                         (args.output_path == "-" && isatty(STDOUT_FILENO));
199 
200   // Check for required flags
201   if (args.device.empty()) {
202     PW_LOG_ERROR("Missing required flag: -D/--device");
203     Usage();
204     return Status::InvalidArgument();
205   }
206   if (!args.frequency) {
207     PW_LOG_ERROR("Missing required flag: -F/--frequency");
208     Usage();
209     return Status::InvalidArgument();
210   }
211 
212   // Either input file or rx count must be provided
213   if (!args.input_path && !args.rx_count) {
214     PW_LOG_ERROR("Either -i/--input or -r/--rx must be provided.");
215     return Status::InvalidArgument();
216   }
217 
218   return args;
219 }
220 
ReadInput(const std::string & path,size_t limit)221 std::vector<std::byte> ReadInput(const std::string& path, size_t limit) {
222   std::ifstream input_file;
223   if (path != "-") {
224     input_file.open(path, std::ifstream::in);
225     if (!input_file.is_open()) {
226       PW_LOG_ERROR("Failed to open %s", path.c_str());
227       exit(2);
228     }
229   }
230   std::istream& instream = input_file.is_open() ? input_file : std::cin;
231 
232   std::vector<std::byte> result;
233   for (size_t i = 0; i < limit; i++) {
234     int b = instream.get();
235     if (b == EOF) {
236       break;
237     }
238     result.push_back(static_cast<std::byte>(b));
239   }
240 
241   return result;
242 }
243 
WriteOutput(const std::string & path,std::vector<std::byte> data,bool human_readable)244 void WriteOutput(const std::string& path,
245                  std::vector<std::byte> data,
246                  bool human_readable) {
247   std::ofstream output_file;
248   if (path != "-") {
249     output_file.open(path, std::ifstream::out);
250     if (!output_file.is_open()) {
251       PW_LOG_ERROR("Failed to open %s", path.c_str());
252       exit(2);
253     }
254   }
255   std::ostream& out = output_file.is_open() ? output_file : std::cout;
256 
257   if (human_readable) {
258     out << '"';
259   }
260 
261   for (std::byte b : data) {
262     char c = static_cast<char>(b);
263     if (!human_readable || std::isprint(c)) {
264       out.put(c);
265     } else if (c == '\0') {
266       out << "\\0";
267     } else if (c == '\n') {
268       out << "\\n";
269     } else {
270       out << "\\x" << std::hex << std::setfill('0') << std::setw(2)
271           << static_cast<unsigned int>(c);
272     }
273   }
274 
275   if (human_readable) {
276     out << '"' << std::endl;
277   }
278 }
279 
MainInNamespace(int argc,char * argv[])280 int MainInNamespace(int argc, char* argv[]) {
281   auto maybe_args = ParseArgs(argc, argv);
282   if (!maybe_args.ok()) {
283     return 1;
284   }
285   auto args = std::move(maybe_args.value());
286 
287   int fd = open(args.device.c_str(), O_RDWR);
288   if (fd < 0) {
289     PW_LOG_ERROR("Failed to open %s: %s", args.device.c_str(), strerror(errno));
290     return 1;
291   }
292   PW_LOG_DEBUG("Opened %s", args.device.c_str());
293 
294   // Set up SPI Initiator.
295   LinuxInitiator initiator(fd, args.frequency);
296   if (auto status = initiator.Configure(args.GetSpiConfig()); !status.ok()) {
297     PW_LOG_ERROR(
298         "Failed to configure %s: %s", args.device.c_str(), status.str());
299     return 2;
300   }
301   PW_LOG_DEBUG("Configured %s", args.device.c_str());
302 
303   // Read input data for transmit.
304   std::vector<std::byte> tx_data;
305   if (args.input_path) {
306     tx_data = ReadInput(args.input_path.value(), 1024);
307   }
308 
309   // Set up receive buffer.
310   std::vector<std::byte> rx_data(args.rx_count ? args.rx_count.value()
311                                                : tx_data.size());
312 
313   // Perform a transfer!
314   PW_LOG_DEBUG(
315       "Ready to send %zu, receive %zu bytes", tx_data.size(), rx_data.size());
316   if (auto status = initiator.WriteRead(tx_data, rx_data); !status.ok()) {
317     PW_LOG_ERROR("Failed to send/recv data: %s", status.str());
318     return 2;
319   }
320   PW_LOG_DEBUG("Transfer successful! (%zu bytes)", rx_data.size());
321 
322   WriteOutput(args.output_path, rx_data, args.human_readable);
323 
324   return 0;
325 }
326 
327 }  // namespace
328 }  // namespace pw::spi
329 
main(int argc,char * argv[])330 int main(int argc, char* argv[]) {
331   return pw::spi::MainInNamespace(argc, argv);
332 }
333