blob: 4abea6e7fe3ffda5aefe6f44a1d8825f102043b9 [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "pw_assert/check.h"
#include "pw_i2c_linux/initiator.h"
#include "pw_log/log.h"
#include "pw_preprocessor/util.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
namespace pw::i2c {
namespace {
const struct option kLongOptions[] = {
{"addr10", required_argument, nullptr, 'A'},
{"address", required_argument, nullptr, 'a'},
{"device", required_argument, nullptr, 'D'},
{"human", no_argument, nullptr, 'h'},
{"input", required_argument, nullptr, 'i'},
{"lsb", no_argument, nullptr, 'l'},
{"output", required_argument, nullptr, 'o'},
{"rx-count", required_argument, nullptr, 'r'},
{}, // terminator
};
// Starts with ':' to skip printing errors and return ':' for a missing option
// argument.
// const char* kShortOptions = ":b:D:F:hi:lm:o:r:";
const char* kShortOptions = ":a:A:D:hi:lm:o:r:";
void Usage() {
std::cerr << "Usage: pw_i2c_linux_cli -D DEVICE -A|-a ADDR [flags]"
<< std::endl;
std::cerr << "Required flags:" << std::endl;
std::cerr << " -A/--addr10 Target address, 0x prefix allowed (10-bit i2c "
"extension)"
<< std::endl;
std::cerr << " -a/--address Target address, 0x prefix allowed (7-bit "
"standard i2c)"
<< std::endl;
std::cerr << " -D/--device I2C device path (e.g. /dev/i2c-0" << std::endl;
std::cerr << std::endl;
std::cerr << "Optional flags:" << std::endl;
std::cerr << " -h/--human Human-readable output (default: binary, "
"unless output to stdout tty)"
<< std::endl;
std::cerr << " -i/--input Input file, or - for stdin" << std::endl;
std::cerr << " If not given, no data is sent." << std::endl;
std::cerr << " -l/--lsb LSB first (default: MSB first)" << std::endl;
std::cerr << " -o/--output Output file (default: stdout)" << std::endl;
std::cerr << " -r/--rx-count Number of bytes to receive (defaults to size "
"of input)"
<< std::endl;
}
struct Args {
std::string device;
std::optional<std::string> input_path;
std::string output_path = "-";
bool human_readable = false;
std::optional<unsigned int> rx_count;
std::optional<Address> address;
bool lsb_first = false;
};
template <class T>
std::optional<T> ParseNumber(std::string_view str) {
std::string _str(str);
errno = 0;
long value = std::strtol(_str.c_str(), nullptr, 0);
if (errno != 0) {
PW_LOG_ERROR("Unable to parse param: [%s] error %d", _str.c_str(), errno);
return std::nullopt;
}
if (value >= std::numeric_limits<T>::min() &&
value <= std::numeric_limits<T>::max()) {
return value;
}
PW_LOG_ERROR("Value is out of range: %ld", value);
return std::nullopt;
}
Result<Args> ParseArgs(int argc, char* argv[]) {
Args args;
bool human_readable_given;
while (true) {
int current_optind = optind;
int c = getopt_long(argc, argv, kShortOptions, kLongOptions, nullptr);
if (c == -1) {
break;
}
switch (c) {
case 'A': {
if (args.address.has_value()) {
PW_LOG_ERROR("Must specify exactly one of -A|-a");
return Status::InvalidArgument();
}
std::optional<uint16_t> addr10 = ParseNumber<uint16_t>(optarg);
if (!addr10) {
PW_LOG_ERROR("Invalid 10-bit i2c address: %s", optarg);
return Status::InvalidArgument();
}
args.address = Address::TenBit(addr10.value());
break;
}
case 'a': {
if (args.address.has_value()) {
PW_LOG_ERROR("Must specify exactly one of -A|-a");
return Status::InvalidArgument();
}
std::optional<uint8_t> addr = ParseNumber<uint8_t>(optarg);
if (!addr) {
PW_LOG_ERROR("Invalid 7-bit i2c address: %s", optarg);
return Status::InvalidArgument();
}
args.address = Address::SevenBit(addr.value());
break;
}
case 'D':
args.device = optarg;
break;
case 'h':
human_readable_given = true;
break;
case 'i':
args.input_path = optarg;
break;
case 'l':
args.lsb_first = true;
break;
case 'o':
args.output_path = optarg;
break;
case 'r': {
auto count = ParseNumber<unsigned int>(optarg);
if (!count) {
PW_LOG_ERROR("Invalid count: %s", optarg);
return Status::InvalidArgument();
}
args.rx_count = count;
break;
}
case '?':
if (optopt) {
PW_LOG_ERROR("Invalid flag: -%c", optopt);
} else {
PW_LOG_ERROR("Invalid flag: %s", argv[current_optind]);
}
Usage();
return Status::InvalidArgument();
case ':':
PW_LOG_ERROR("Missing argument to %s", argv[current_optind]);
return Status::InvalidArgument();
}
}
args.human_readable = human_readable_given ||
(args.output_path == "-" && isatty(STDOUT_FILENO));
// Check for required flags
if (args.device.empty()) {
PW_LOG_ERROR("Missing required flag: -D/--device");
Usage();
return Status::InvalidArgument();
}
// Check for required flags
if (!args.address.has_value()) {
PW_LOG_ERROR("Missing required flag: -A|-a ADDR");
Usage();
return Status::InvalidArgument();
}
// Either input file or rx count must be provided
if (!args.input_path && !args.rx_count) {
PW_LOG_ERROR("Either -i/--input or -r/--rx must be provided.");
return Status::InvalidArgument();
}
return args;
}
std::vector<std::byte> ReadInput(const std::string& path, size_t limit) {
std::ifstream input_file;
if (path != "-") {
input_file.open(path, std::ifstream::in);
if (!input_file.is_open()) {
PW_LOG_ERROR("Failed to open %s", path.c_str());
exit(2);
}
}
std::istream& instream = input_file.is_open() ? input_file : std::cin;
std::vector<std::byte> result;
for (size_t i = 0; i < limit; i++) {
int b = instream.get();
if (b == EOF) {
break;
}
result.push_back(static_cast<std::byte>(b));
}
return result;
}
void WriteOutput(const std::string& path,
std::vector<std::byte> data,
bool human_readable) {
std::ofstream output_file;
if (path != "-") {
output_file.open(path, std::ifstream::out);
if (!output_file.is_open()) {
PW_LOG_ERROR("Failed to open %s", path.c_str());
exit(2);
}
}
std::ostream& out = output_file.is_open() ? output_file : std::cout;
if (human_readable) {
out << '"';
}
for (std::byte b : data) {
char c = static_cast<char>(b);
if (!human_readable || std::isprint(c)) {
out.put(c);
} else if (c == '\0') {
out << "\\0";
} else if (c == '\n') {
out << "\\n";
} else {
out << "\\x" << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned int>(c);
}
}
if (human_readable) {
out << '"' << std::endl;
}
}
int MainInNamespace(int argc, char* argv[]) {
auto maybe_args = ParseArgs(argc, argv);
if (!maybe_args.ok()) {
return 1;
}
auto args = std::move(maybe_args.value());
pw::Result<int> fd = LinuxInitiator::OpenI2cBus(args.device.c_str());
if (!fd.ok()) {
PW_LOG_ERROR("Failed to open %s: %s", args.device.c_str(), strerror(errno));
return 1;
}
PW_LOG_DEBUG("Opened %s", args.device.c_str());
LinuxInitiator initiator(fd.value());
// Read input data for transmit.
std::vector<std::byte> tx_data;
if (args.input_path) {
tx_data = ReadInput(args.input_path.value(), 1024);
}
// Set up receive buffer.
std::vector<std::byte> rx_data(args.rx_count ? args.rx_count.value() : 0);
// Perform a transfer!
PW_LOG_DEBUG(
"Ready to send %zu, receive %zu bytes", tx_data.size(), rx_data.size());
PW_CHECK(args.address.has_value());
std::vector<Message> messages;
if (!tx_data.empty()) {
messages.push_back(Message::WriteMessage(args.address.value(), tx_data));
}
if (!rx_data.empty()) {
messages.push_back(Message::ReadMessage(args.address.value(), rx_data));
}
constexpr auto kTimeout =
pw::chrono::SystemClock::for_at_least(std::chrono::milliseconds(500));
if (auto status = initiator.TransferFor(messages, kTimeout); !status.ok()) {
PW_LOG_ERROR("Failed to transfer data: %s", status.str());
return 2;
}
PW_LOG_DEBUG("Transfer successful! tx_bytes=%zu rx_bytes=%zu",
tx_data.size(),
rx_data.size());
WriteOutput(args.output_path, rx_data, args.human_readable);
close(fd.value());
return 0;
}
} // namespace
} // namespace pw::i2c
int main(int argc, char* argv[]) {
return pw::i2c::MainInNamespace(argc, argv);
}