blob: 3c292721674112c0a1b7294559c558970aa11dae [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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/hardware/i2c/llcpp/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/unsafe.h>
#include <stdio.h>
#include <zircon/status.h>
#include <filesystem>
#include <fbl/auto_call.h>
#include <fbl/span.h>
#include <fbl/unique_fd.h>
static void usage(char* prog) {
printf("Usage:\n");
printf(" (DATA and ADDRESS are a list of space separated bytes BYTE_0 BYTE_1...BYTE_N)\n");
printf(" %s w[rite] DEVICE DATA... Write bytes\n",
prog);
printf(" %s r[ead] DEVICE ADDRESS Reads one byte\n",
prog);
printf(" %s t[ransact] DEVICE [w|r] [DATA...|LENGTH] [w|r] [DATA...|LENGTH]... Transaction\n",
prog);
printf(" %s p[ing] Ping devices\n",
prog);
}
static zx_status_t convert_args(char** argv, size_t length, uint8_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<uint8_t>(value);
}
return ZX_OK;
}
static zx_status_t write_bytes(llcpp::fuchsia::hardware::i2c::Device2::SyncClient client,
fbl::Span<uint8_t> write_buffer) {
bool is_write[] = {true};
fidl::VectorView<bool> segments_is_write(fidl::unowned_ptr(is_write), countof(is_write));
fidl::VectorView<uint8_t> write_segment(fidl::unowned_ptr(write_buffer.data()),
write_buffer.size());
auto read = client.Transfer(std::move(segments_is_write),
fidl::VectorView<fidl::VectorView<uint8_t>>(
fidl::unowned_ptr(&write_segment), 1), // One write.
fidl::VectorView<uint8_t>(nullptr, 0)); // No reads.
auto status = read.status();
if (status == ZX_OK && read->result.is_err()) {
status = ZX_ERR_INTERNAL;
}
return status;
}
static zx_status_t read_byte(llcpp::fuchsia::hardware::i2c::Device2::SyncClient client,
fbl::Span<uint8_t> address, uint8_t* out_byte) {
bool is_write[] = {true, false};
fidl::VectorView<bool> segments_is_write(fidl::unowned_ptr(is_write), countof(is_write));
fidl::VectorView<uint8_t> write_segment(fidl::unowned_ptr(address.data()), address.size());
uint8_t read_length = 1;
auto read =
client.Transfer(std::move(segments_is_write),
fidl::VectorView<fidl::VectorView<uint8_t>>(fidl::unowned_ptr(&write_segment),
1), // One write.
fidl::VectorView<uint8_t>(fidl::unowned_ptr(&read_length), 1)); // One read.
auto status = read.status();
if (status == ZX_OK) {
if (read->result.is_err()) {
status = ZX_ERR_INTERNAL;
} else {
*out_byte = read->result.response().read_segments_data[0].data()[0];
}
}
return status;
}
static zx_status_t transact(llcpp::fuchsia::hardware::i2c::Device2::SyncClient 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(argv[0]);
return -1;
}
if (n_segments > llcpp::fuchsia::hardware::i2c::MAX_COUNT_SEGMENTS) {
printf("No more than %u segments allowed\n", llcpp::fuchsia::hardware::i2c::MAX_COUNT_SEGMENTS);
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<uint8_t[]>(n_segments - n_writes);
auto is_write = std::make_unique<bool[]>(n_segments);
fidl::VectorView<bool> segments_is_write(fidl::unowned_ptr(is_write.get()), 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(argv[0]);
return status;
}
write_data[write_cnt].set_data(fidl::unowned_ptr(write_buffer_pos));
write_data[write_cnt].set_count(write_len);
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(argv[0]);
return status;
}
read_cnt++;
element_cnt++;
}
segment_cnt++;
}
}
if (write_cnt != n_writes || read_cnt + write_cnt != segment_cnt) {
usage(argv[0]);
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(
std::move(segments_is_write),
fidl::VectorView<fidl::VectorView<uint8_t>>(fidl::unowned_ptr(write_data.get()), n_writes),
fidl::VectorView<uint8_t>(fidl::unowned_ptr(read_lengths.get()), n_segments - n_writes));
auto status = read.status();
if (status == ZX_OK) {
if (read->result.is_err()) {
return ZX_ERR_INTERNAL;
} else {
auto& read_data = read->result.response().read_segments_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(argv[0]);
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;
}
}
fbl::unique_fd fd(open(path, O_RDWR));
if (!fd) {
printf("%s: %s\n", argv[2], strerror(errno));
usage(argv[0]);
return -1;
}
zx_handle_t svc;
if ((fdio_get_service_handle(fd.release(), &svc) != ZX_OK)) {
printf("%s: get service handle failed\n", argv[2]);
usage(argv[0]);
return -1;
}
zx::channel channel(svc);
llcpp::fuchsia::hardware::i2c::Device2::SyncClient client(std::move(channel));
zx_status_t status = ZX_OK;
switch (argv[1][0]) {
case 'w': {
if (argc < 4) {
usage(argv[0]);
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(argv[0]);
return status;
}
status =
write_bytes(std::move(client), fbl::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(argv[0]);
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(argv[0]);
return status;
}
uint8_t out_byte = 0;
status = read_byte(std::move(client), fbl::Span<uint8_t>(write_buffer.get(), n_write_bytes),
&out_byte);
if (status == ZX_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", out_byte);
}
break;
}
case 't': {
if (argc < 5) {
usage(argv[0]);
return -1;
}
status = transact(std::move(client), argc, argv);
break;
}
default:
printf("%c: unrecognized command\n", argv[2][0]);
usage(argv[0]);
return -1;
}
if (status != ZX_OK) {
printf("Error %s\n", zx_status_get_string(status));
}
return status;
}
static int ping_cmd() {
const char* c_dir = "/dev/class/i2c";
DIR* dir = opendir(c_dir);
if (!dir) {
printf("Directory %s not found\n", c_dir);
return -1;
}
std::filesystem::path dir_path(c_dir);
struct dirent* de;
while ((de = readdir(dir))) {
std::filesystem::path dev_path = dir_path;
dev_path /= std::filesystem::path(de->d_name);
const char* argv[] = {"i2cutil_ping", "r", dev_path.c_str(), "0x00"};
char** argv_main = (char**)(&argv);
auto status = device_cmd(countof(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(argv[0]);
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;
default:
usage(argv[0]);
return -1;
}
return 0;
}