blob: 4d24722ef4cdc5d998a4cb6741ce4b4146dedbd9 [file] [log] [blame]
// Copyright 2020 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 <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <stdio.h>
#include <zircon/status.h>
#include <filesystem>
// LINT.IfChange
constexpr char kUsageSummary[] = R"""(
Usage:
i2cutil read <device> <address> [<address>...]
i2cutil write <device> <address> [<address>...] <data> [<data>...]
i2cutil transact <device> (r <bytes>|w <address> [<address>...] [<data>...])...
i2cutil ping
i2cutil help
)""";
constexpr char kUsageDetails[] = R"""(
Commands:
read | r Read one byte from an I2C device. Use `i2cutil transact`
to read multiple bytes. <device> can be the full path of
a devfs node (example: `/dev/class/i2c/031`) or only the
devfs node's index (example: `31`). Use `i2cutil ping` to
get devfs node paths and indexes. <address> is the internal
register of <device> to read from. Use multiple <address>
values to access a multi-byte (little-endian) register address.
For example `i2cutil read 4 0x20 0x3D` to read the register at
`0x203D`.
write | w Write one or more bytes (<data>) to an I2C device. See the
`i2cutil read` description for explanations of <device>
and <address>.
transact | t Perform a transaction with multiple segments. Each segment
can be a write (`w`) or a read (`r`).
ping | p Ping all I2C devices under devfs path `/dev/class/i2c` by
reading from each device's 0x00 address.
help | h Print this help text.
Examples:
Read one byte from the register at `0x20` of the I2C device represented by
devfs node index `4`:
$ i2cutil read 4 0x20
Read three bytes from the same I2C device and register as the last example:
$ i2cutil transact 4 w 0x20 r 3
Read one byte from the register at the multi-byte address `0x203D` of the
I2C device represented by devfs node index `4`:
$ i2cutil read 4 0x20 0x3D
Same as the last example but represent the I2C device with a devfs node path:
$ i2cutil read /dev/class/i2c/004 0x20 0x3D
Write byte `0x12` to the register at `0x2C` of the I2C device represented by
devfs node index `3`:
$ i2cutil write 3 0x2C 0x12
Write byte `0x121B` to the same device and register as the last example:
$ i2cutil write 3 0x2C 0x12 0x1B
Write byte `0x1B to register `0x2C12` of a different device (note that
this is the exact same command as the last example; the meaning of the
arguments depends on the I2C device):
$ i2cutil write 3 0x2C 0x12 0x1B
Ping all I2C devices:
$ i2cutil ping
/dev/class/i2c/821: OK
/dev/class/i2c/822: OK
/dev/class/i2c/823: OK
/dev/class/i2c/824: OK
Error ZX_ERR_TIMED_OUT
/dev/class/i2c/825: ERROR
Notes:
Source code for `i2cutil`: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/devices/i2c/bin/i2cutil.cc
)""";
// LINT.ThenChange(//docs/reference/tools/hardware/i2cutil.md)
static void usage(bool show_details) {
printf(kUsageSummary);
if (!show_details) {
printf("\nUse `i2cutil help` to see full help text\n");
} else {
printf(kUsageDetails);
}
}
template <typename T>
static zx_status_t convert_args(char** argv, size_t length, T* buffer) {
for (size_t i = 0; i < length; i++) {
char* end = nullptr;
unsigned long value = strtoul(argv[i], &end, 0);
if (value > 0xFF || *end != '\0') {
return ZX_ERR_INVALID_ARGS;
}
buffer[i] = static_cast<T>(value);
}
return ZX_OK;
}
static zx_status_t write_bytes(fidl::WireSyncClient<fuchsia_hardware_i2c::Device> client,
cpp20::span<uint8_t> write_buffer) {
auto write_data =
fidl::VectorView<uint8_t>::FromExternal(write_buffer.data(), write_buffer.size());
fidl::Arena arena;
auto transactions = fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>(arena, 1);
transactions[0] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(arena, write_data))
.Build();
auto read = client->Transfer(transactions);
auto status = read.status();
if (status == ZX_OK && read->is_error()) {
status = ZX_ERR_INTERNAL;
}
return status;
}
static zx::result<uint8_t> read_byte(fidl::WireSyncClient<fuchsia_hardware_i2c::Device> client,
cpp20::span<uint8_t> address) {
auto write_data = fidl::VectorView<uint8_t>::FromExternal(address.data(), address.size());
fidl::Arena arena;
auto transactions = fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>(arena, 2);
transactions[0] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(arena, write_data))
.Build();
transactions[1] = fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(fuchsia_hardware_i2c::wire::DataTransfer::WithReadSize(1))
.Build();
auto read = client->Transfer(transactions);
auto status = read.status();
if (status != ZX_OK) {
return zx::error(read.status());
}
if (read->is_error()) {
return zx::error(read->error_value());
}
return zx::ok(read->value()->read_data[0].data()[0]);
}
static zx_status_t transact(fidl::WireSyncClient<fuchsia_hardware_i2c::Device> client, int argc,
char** argv) {
size_t n_elements = argc - 3;
size_t n_segments = 0;
size_t n_writes = 0;
// We know n_segments and total data will be smaller than n_elements, so we use it as max.
auto segment_start = std::make_unique<size_t[]>(n_elements);
auto writes_start = std::make_unique<size_t[]>(n_elements);
auto write_buffer = std::make_unique<uint8_t[]>(n_elements);
// Find n_segments, segment starts and writes starts.
for (size_t i = 0; i < n_elements; ++i) {
if (argv[3 + i][0] == 'r') {
segment_start[n_segments++] = i + 1;
} else if (argv[3 + i][0] == 'w') {
segment_start[n_segments++] = i + 1;
writes_start[n_writes++] = i + 1;
}
}
// Must have at least one segment and start with a w or r.
if (n_segments == 0 || (argv[3][0] != 'r' && argv[3][0] != 'w')) {
usage(false);
return -1;
}
if (n_segments > fuchsia_hardware_i2c::wire::kMaxCountTransactions) {
printf("No more than %u segments allowed\n", fuchsia_hardware_i2c::wire::kMaxCountTransactions);
return -1;
}
// For the last segment we pretend that data starts after a pretend w/r, this makes
// calculations below consistent for the last actual segment without a segment to follow.
segment_start[n_segments] = n_elements + 1;
auto write_data = std::make_unique<fidl::VectorView<uint8_t>[]>(n_writes);
auto read_lengths = std::make_unique<uint32_t[]>(n_segments - n_writes);
auto is_write = std::make_unique<bool[]>(n_segments);
fidl::Arena arena;
auto transactions = fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>(arena, n_segments);
size_t element_cnt = 0;
size_t segment_cnt = 0;
size_t read_cnt = 0;
size_t write_cnt = 0;
uint8_t* write_buffer_pos = write_buffer.get();
while (element_cnt < n_elements) {
if (argv[3 + element_cnt][0] == 'w') {
is_write[segment_cnt] = true;
element_cnt++;
} else if (argv[3 + element_cnt][0] == 'r') {
is_write[segment_cnt] = false;
element_cnt++;
} else {
if (is_write[segment_cnt]) {
auto write_len = segment_start[segment_cnt + 1] - segment_start[segment_cnt] - 1;
auto status = convert_args(&argv[3 + element_cnt], write_len, write_buffer_pos);
if (status != ZX_OK) {
usage(false);
return status;
}
write_data[write_cnt] =
fidl::VectorView<uint8_t>::FromExternal(write_buffer_pos, write_len);
transactions[segment_cnt] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(
arena, write_data[write_cnt]))
.Build();
write_buffer_pos += write_len;
write_cnt++;
element_cnt += write_len;
} else {
auto status = convert_args(&argv[3 + element_cnt], 1, &read_lengths[read_cnt]);
if (status != ZX_OK) {
usage(false);
return status;
}
transactions[segment_cnt] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(
fuchsia_hardware_i2c::wire::DataTransfer::WithReadSize(read_lengths[read_cnt]))
.Build();
read_cnt++;
element_cnt++;
}
segment_cnt++;
}
}
if (write_cnt != n_writes || read_cnt + write_cnt != segment_cnt) {
usage(false);
return ZX_ERR_INVALID_ARGS;
}
if (n_writes != 0) {
printf("Writes:");
for (size_t i = 0; i < n_writes; ++i) {
printf(" ");
for (size_t j = 0; j < write_data[i].count(); ++j) {
printf("0x%02X ", write_data[i].data()[j]);
}
}
printf("\n");
}
auto read = client->Transfer(transactions);
auto status = read.status();
if (status == ZX_OK) {
if (read->is_error()) {
return ZX_ERR_INTERNAL;
} else {
auto& read_data = read->value()->read_data;
if (read_data.count() != 0) {
printf("Reads:");
for (auto& i : read_data) {
printf(" ");
for (size_t j = 0; j < i.count(); ++j) {
printf("0x%02X ", i.data()[j]);
}
}
printf("\n");
}
}
}
return status;
}
static int device_cmd(int argc, char** argv, bool print_out) {
if (argc < 3) {
usage(false);
return -1;
}
const char* path = argv[2];
char new_path[32];
int id = -1;
if (sscanf(path, "%u", &id) == 1) {
if (snprintf(new_path, sizeof(new_path), "/dev/class/i2c/%03u", id) >= 0) {
path = new_path;
}
}
zx::result client_end = component::Connect<fuchsia_hardware_i2c::Device>(path);
if (client_end.is_error()) {
printf("%s: connect failed: %s\n", argv[2], client_end.status_string());
usage(false);
return -1;
}
fidl::WireSyncClient client{std::move(client_end.value())};
zx_status_t status = ZX_OK;
switch (argv[1][0]) {
case 'w': {
if (argc < 4) {
usage(false);
return -1;
}
size_t n_write_bytes = argc - 3;
auto write_buffer = std::make_unique<uint8_t[]>(n_write_bytes);
status = convert_args(&argv[3], n_write_bytes, write_buffer.get());
if (status != ZX_OK) {
usage(false);
return status;
}
status =
write_bytes(std::move(client), cpp20::span<uint8_t>(write_buffer.get(), n_write_bytes));
if (status == ZX_OK && print_out) {
printf("Write: ");
for (size_t i = 0; i < n_write_bytes; ++i) {
printf("0x%02X ", write_buffer[i]);
}
printf("\n");
}
break;
}
case 'r': {
if (argc < 4) {
usage(false);
return -1;
}
size_t n_write_bytes = argc - 3;
auto write_buffer = std::make_unique<uint8_t[]>(n_write_bytes);
status = convert_args(&argv[3], n_write_bytes, write_buffer.get());
if (status != ZX_OK) {
usage(false);
return status;
}
auto byte =
read_byte(std::move(client), cpp20::span<uint8_t>(write_buffer.get(), n_write_bytes));
status = byte.status_value();
if (byte.is_ok() && print_out) {
printf("Read from");
for (size_t i = 0; i < n_write_bytes; ++i) {
printf(" 0x%02X", write_buffer[i]);
}
printf(": 0x%02X\n", byte.value());
}
break;
}
case 't': {
if (argc < 5) {
usage(false);
return -1;
}
status = transact(std::move(client), argc, argv);
break;
}
default:
printf("%c: unrecognized command\n", argv[2][0]);
usage(false);
return -1;
}
if (status != ZX_OK) {
printf("Error %s\n", zx_status_get_string(status));
}
return status;
}
static int ping_cmd() {
constexpr char kDir[] = "/dev/class/i2c";
for (auto const& dir_entry : std::filesystem::directory_iterator{kDir}) {
const std::filesystem::path& dev_path = dir_entry.path();
const char* argv[] = {"i2cutil_ping", "r", dev_path.c_str(), "0x00"};
char** argv_main = (char**)(&argv);
auto status = device_cmd(std::size(argv), argv_main, false);
printf("%s: %s\n", dev_path.c_str(), status == ZX_OK ? "OK" : "ERROR");
}
return 0;
}
int main(int argc, char** argv) {
if (argc < 2) {
usage(false);
return -1;
}
switch (argv[1][0]) {
case 'w':
case 'r':
case 't':
return device_cmd(argc, argv, true);
break;
case 'p':
return ping_cmd();
break;
case 'h':
usage(true);
break;
default:
usage(false);
return -1;
}
return 0;
}