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