// Copyright 2016 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 #include #include #include #include #include #include #include #include #include #include #include const char* prog_name; void print_usage(void) { printf("Usage:\n"); printf("\n"); printf("%s DEVICE COMMAND [command arguments]\n", prog_name); printf("DEVICE is either the i2c bus or i2c slave COMMAND applies to.\n"); printf("COMMAND is one of the following commands, optionally followed \n"); printf("arguments which are specific to each command.\n"); printf("\n"); printf("read LENGTH: Read data from the target slave device.\n"); printf("LENGTH is the number of bytes to read in decimal.\n"); printf("\n"); printf("write [data]: Write data to the target slave device.\n"); printf("data is a sequence of hex values which each represent one byte\n"); printf("of data to write to the target device.\n"); printf("\n"); printf("transfer [segments]: Perform a transfer to/from the i2c slave.\n"); printf("segments is a series of segment descriptions which are a\n"); printf("direction, a length, and then (for writes) a series of bytes\n"); printf("in hexadecimal.\n"); printf("\n"); printf("The direction is specified as either \"w\" for writes, or\n"); printf("\"r\" for reads.\n"); printf("\n"); printf("For example, to perform a write of one byte and then a read\n"); printf("of one byte without giving up the bus:\n"); printf("%s [dev] transfer w 1 00 r 1\n", prog_name); } int cmd_read(int fd, int argc, const char** argv) { if (argc < 1) { print_usage(); return 1; } errno = 0; long int length = strtol(argv[0], NULL, 10); if (errno) { print_usage(); return errno; } uint8_t* buf = malloc(length); if (!buf) { printf("Failed to allocate buffer.\n"); return 1; } int ret = read(fd, buf, length); if (ret < 0) { printf("Error reading from slave. (%d)\n", ret); goto cmd_read_finish; } for (int i = 0; i < length; i++) { printf(" %02x", buf[i]); if (i % 32 == 31) printf("\n"); } printf("\n"); cmd_read_finish: free(buf); return ret; } int cmd_write(int fd, int argc, const char** argv) { if (argc < 1) { print_usage(); return 1; } uint8_t* buf = malloc(argc); if (!buf) { printf("Failed to allocate buffer.\n"); return 1; } int ret = 0; errno = 0; for (int i = 0; i < argc; i++) { buf[i] = strtol(argv[i], NULL, 16); if (errno) { ret = errno; print_usage(); goto cmd_write_finish; } } ret = write(fd, buf, argc); if (ret < 0) printf("Error writing to slave. (%d)\n", ret); cmd_write_finish: free(buf); return ret; } int cmd_transfer(int fd, int argc, const char** argv) { const size_t base_size = sizeof(fuchsia_hardware_i2c_Segment); int ret = ZX_OK; // Figure out how big our buffers need to be. // Start the counters with enough space for the fuchsia_hardware_i2c_SegmentType_END // segment. size_t in_len = base_size; size_t out_len = 0; int segments = 1; int count = argc; const char** arg = argv; errno = 0; while (count) { if (count < 2) { print_usage(); goto cmd_transfer_finish_2; } in_len += base_size; int read; if (!strcmp(arg[0], "r")) { read = 1; } else if (!strcmp(arg[0], "w")) { read = 0; } else { print_usage(); goto cmd_transfer_finish_2; } segments++; long int length = strtol(arg[1], NULL, 10); if (errno) { print_usage(); return errno; } arg += 2; count -= 2; if (read) { out_len += length; } else { in_len += length; if (length > count) { print_usage(); goto cmd_transfer_finish_2; } arg += length; count -= length; } } // Allocate the input and output buffers. void* in_buf = malloc(in_len); void* out_buf = malloc(out_len); if (!in_buf || !out_buf) { ret = 1; goto cmd_transfer_finish_1; } uint8_t* data_addr = (uint8_t*)in_buf + segments * base_size; uint8_t* data_buf = data_addr; // Fill the "input" buffer which is sent via FIDL. uintptr_t in_addr = (uintptr_t)in_buf; int i = 0; fuchsia_hardware_i2c_Segment* segment = (fuchsia_hardware_i2c_Segment*)in_addr; while (i < argc) { if (!strcmp(argv[i++], "r")) { segment->type = fuchsia_hardware_i2c_SegmentType_READ; segment->len = strtol(argv[i++], NULL, 10); if (errno) { print_usage(); return errno; } } else { segment->type = fuchsia_hardware_i2c_SegmentType_WRITE; segment->len = strtol(argv[i++], NULL, 10); if (errno) { print_usage(); return errno; } for (uint32_t seg = 0; seg < segment->len; seg++) { *data_buf++ = strtol(argv[i++], NULL, 16); if (errno) { print_usage(); return errno; } } } segment++; } segment->type = fuchsia_hardware_i2c_SegmentType_END; segment->len = 0; segment++; // We should be at the start of the data section now. if ((uint8_t*)segment != data_addr) { ret = 1; goto cmd_transfer_finish_1; } fdio_t* io = fdio_unsafe_fd_to_io(fd); // Get a fdio channel from the fd. if (io == NULL) return ZX_ERR_INVALID_ARGS; zx_status_t status; size_t out_actual; zx_status_t res = fuchsia_hardware_i2c_DeviceSlaveTransfer( fdio_unsafe_borrow_channel(io), in_buf, in_len, &status, out_buf, out_len, &out_actual); fdio_unsafe_release(io); // Release the channel manually. if (out_actual < out_len) { status = ZX_ERR_BUFFER_TOO_SMALL; } if (res == ZX_OK) { res = status; } if (res != ZX_OK) { printf("Error in transfer to/from slave. (%d)\n", res); goto cmd_transfer_finish_1; } for (size_t i = 0; i < out_len; i++) { printf(" %02x", ((uint8_t*)out_buf)[i]); if (i % 32 == 31) printf("\n"); } printf("\n"); ret = 0; cmd_transfer_finish_1: free(in_buf); free(out_buf); cmd_transfer_finish_2: return ret; } int main(int argc, const char** argv) { if (argc < 1) return 1; prog_name = argv[0]; if (argc < 3) { print_usage(); return 1; } const char* dev = argv[1]; const char* cmd = argv[2]; argc -= 3; argv += 3; int fd = open(dev, O_RDWR); if (fd < 0) { printf("Error opening I2C device.\n"); return 1; } if (!strcmp("read", cmd)) { return cmd_read(fd, argc, argv); } else if (!strcmp("write", cmd)) { return cmd_write(fd, argc, argv); } else if (!strcmp("transfer", cmd)) { return cmd_transfer(fd, argc, argv); } else { printf("Unrecognized command %s.\n", cmd); print_usage(); return 1; } printf("We should never get here!.\n"); return 1; }