blob: c0dcf4fadfb3c97d38101423af84347453f71dc4 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "args.h"
#include <zircon/assert.h>
#include <iostream>
#include <limits>
#include <map>
#include <optional>
#include <sstream>
namespace {
namespace PositionalArgs {
constexpr size_t kCommand = 1; // First argument is the subcommand
constexpr size_t kPath = 2; // Second argument is the device name or path
constexpr size_t kVargs = 3; // Variable arguments (data / parameters follow)
} // namespace PositionalArgs
const std::map<char, i2cutil::I2cOp> kOrdinalToOperation = {
{'r', i2cutil::I2cOp::Read}, {'w', i2cutil::I2cOp::Write}, {'t', i2cutil::I2cOp::Transact},
{'d', i2cutil::I2cOp::Dump}, {'l', i2cutil::I2cOp::List}, {'p', i2cutil::I2cOp::Ping},
{'h', i2cutil::I2cOp::Help},
};
const std::map<i2cutil::I2cOp, int> kOperationToMinArgCount = {
{i2cutil::I2cOp::Read, 4}, {i2cutil::I2cOp::Write, 4}, {i2cutil::I2cOp::Transact, 5},
{i2cutil::I2cOp::Dump, 5}, {i2cutil::I2cOp::List, 2}, {i2cutil::I2cOp::Ping, 2},
{i2cutil::I2cOp::Help, 2},
};
std::string inferPath(const char* arg) {
/// i2cutil accepts arguments in the form of full paths or simply the device
/// number in /dev/class/i2c/...
/// This function guesses which format is being provided and infers the path
/// accordingly. If a number N is passed the path is inferred as
/// /dev/class/i2c/NNN otherwise `arg` is returned unchanged.
/// Note that the path returned by this function is not guaranteed to be a
/// valid i2c device.
std::string result = arg;
char buffer[32];
int id = -1;
if (sscanf(arg, "%u", &id) == 1) {
if (snprintf(buffer, std::size(buffer), i2cutil::kI2cPathFormat, id) >= 0) {
result = buffer;
}
}
return result;
}
constexpr int64_t kBadParse = -1;
int64_t parse_positive_long(const char* number) {
char* end;
uint64_t result = strtoul(number, &end, 0);
if (end == number || *end != '\0' || result > std::numeric_limits<int64_t>::max()) {
return kBadParse;
}
return static_cast<int64_t>(result);
}
zx_status_t readArgs(const char* argv[], size_t count, std::vector<uint8_t>& data) {
for (size_t i = 0; i < count; i++) {
int64_t result = parse_positive_long(argv[i]);
if (result < 0 || result > 0xff) {
return ZX_ERR_INVALID_ARGS;
}
uint8_t byte = static_cast<uint8_t>(result);
data.push_back(byte);
}
return ZX_OK;
}
zx_status_t parseTransactionData(const char* argv[], size_t count,
std::vector<i2cutil::TransactionData>& transactions) {
enum class State { Start, ScanningWrite, ScanningRead };
State s = State::Start;
i2cutil::TransactionData current;
for (size_t i = 0; i < count; i++) {
switch (s) {
case State::Start:
// Only 'r' and 'w' are valid.
if (argv[i][0] == 'r') {
s = State::ScanningRead;
current.Reset();
current.type = i2cutil::TransactionType::Read;
} else if (argv[i][0] == 'w') {
s = State::ScanningWrite;
current.Reset();
current.type = i2cutil::TransactionType::Write;
} else {
// Parse error, stream must start with either 'w' or 'r'
return ZX_ERR_INVALID_ARGS;
}
break;
case State::ScanningRead: {
// Scan one integer that represents the number of bytes to read then
// return to the State::Start state.
int64_t result = parse_positive_long(argv[i]);
if (result < 0 || result > 0xff)
return ZX_ERR_INVALID_ARGS;
current.count = result;
transactions.push_back(current);
s = State::Start;
break;
}
case State::ScanningWrite:
// Scan bytes to write until a new transaction is started with either
// 'r' or 'w'
if (argv[i][0] == 'r') {
transactions.push_back(current);
current.Reset();
current.type = i2cutil::TransactionType::Read;
s = State::ScanningRead;
} else if (argv[i][0] == 'w') {
transactions.push_back(current);
current.Reset();
current.type = i2cutil::TransactionType::Write;
s = State::ScanningWrite;
} else {
int64_t result = parse_positive_long(argv[i]);
if (result < 0 || result > 0xff)
return ZX_ERR_INVALID_ARGS;
current.bytes.push_back(static_cast<uint8_t>(result));
}
break;
}
}
// TODO(gkalsi): Filter out empty transactions? Make sure there's at least
// one transaction available to run?
// Push the last transaction onto the result list as well.
if (s == State::ScanningWrite) {
transactions.push_back(current);
}
return ZX_OK;
}
} // namespace
namespace i2cutil {
zx::result<Args> Args::FromArgv(const int argc, const char* argv[]) {
if (argc < 2) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
Args result;
// Step 1: Parse which command the user is trying to invoke.
auto op_it = kOrdinalToOperation.find(argv[PositionalArgs::kCommand][0]);
if (op_it != kOrdinalToOperation.end()) {
result.op_ = op_it->second;
} else {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Step 2: Figure out how many args this command expects, and ensure that the
// user has supplied enough args.
auto minArgCountIt = kOperationToMinArgCount.find(result.op_);
ZX_ASSERT(minArgCountIt != kOperationToMinArgCount.end());
if (argc < minArgCountIt->second) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Step 3: If the command expects a path, parse the path; otherwise we're done.
if (minArgCountIt->second >= 3) {
result.path_ = inferPath(argv[PositionalArgs::kPath]);
} else {
return zx::ok(result);
}
// Step 4: If the command expects additional data, parse the additional data.
switch (result.op_) {
case I2cOp::Read:
case I2cOp::Write: {
TransactionData write_data;
zx_status_t status;
// Parse a single row of bytes
write_data.type = TransactionType::Write;
status =
readArgs(argv + PositionalArgs::kVargs, argc - PositionalArgs::kVargs, write_data.bytes);
if (status != ZX_OK) {
return zx::error(status);
}
result.transactions_.push_back(write_data);
// A Read is just a write followed by 1 byte read.
if (result.op_ == I2cOp::Read) {
TransactionData read_data;
read_data.count = 1;
read_data.type = TransactionType::Read;
result.transactions_.push_back(read_data);
}
break;
}
case I2cOp::Transact: {
// Parse a sequence of transactions.
zx_status_t status = parseTransactionData(
argv + PositionalArgs::kVargs, argc - PositionalArgs::kVargs, result.transactions_);
if (status != ZX_OK) {
return zx::error(status);
}
break;
}
case I2cOp::Dump: {
int64_t start_address = parse_positive_long(argv[3]);
int64_t count = parse_positive_long(argv[4]);
// Only do up to 8-bit addressing for now.
if (start_address == kBadParse || count == kBadParse || count > 255 || start_address > 255 ||
(start_address + count) > 255) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
for (int64_t i = 0; i < count; i++) {
TransactionData write;
TransactionData read;
write.type = TransactionType::Write;
write.bytes.push_back(static_cast<uint8_t>(i + start_address));
result.transactions_.push_back(write);
read.type = TransactionType::Read;
read.count = 1;
result.transactions_.push_back(read);
}
break;
}
default:
// No additional data needed, should this be an error?
break;
}
return zx::ok(result);
}
} // namespace i2cutil