1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <errno.h>
6#include <fcntl.h>
7#include <zircon/syscalls.h>
8#include <zircon/types.h>
9#include <zircon/device/i2c.h>
10#include <lib/fdio/util.h>
11#include <stddef.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <unistd.h>
16
17const char* prog_name;
18
19void print_usage(void) {
20    printf("Usage:\n");
21    printf("\n");
22    printf("%s DEVICE COMMAND [command arguments]\n", prog_name);
23    printf("DEVICE is either the i2c bus or i2c slave COMMAND applies to.\n");
24    printf("COMMAND is one of the following commands, optionally followed \n");
25    printf("arguments which are specific to each command.\n");
26    printf("\n");
27    printf("read LENGTH: Read data from the target slave device.\n");
28    printf("LENGTH is the number of bytes to read in decimal.\n");
29    printf("\n");
30    printf("write [data]: Write data to the target slave device.\n");
31    printf("data is a sequence of hex values which each represent one byte\n");
32    printf("of data to write to the target device.\n");
33    printf("\n");
34    printf("transfer [segments]: Perform a tranfer to/from the i2c slave.\n");
35    printf("segments is a series of segment descriptions which are a\n");
36    printf("direction, a length, and then (for writes) a series of bytes\n");
37    printf("in hexidecimal.\n");
38    printf("\n");
39    printf("The direction is specified as either \"w\" for writes, or\n");
40    printf("\"r\" for reads.\n");
41    printf("\n");
42    printf("For example, to perform a write of one byte and then a read\n");
43    printf("of one byte without giving up the bus:\n");
44    printf("%s [dev] transfer w 1 00 r 1\n", prog_name);
45}
46
47int cmd_read(int fd, int argc, const char** argv) {
48    if (argc < 1) {
49        print_usage();
50        return 1;
51    }
52
53    errno = 0;
54    long int length = strtol(argv[0], NULL, 10);
55    if (errno) {
56        print_usage();
57        return errno;
58    }
59
60    uint8_t* buf = malloc(length);
61    if (!buf) {
62        printf("Failed to allocate buffer.\n");
63        return 1;
64    }
65
66    int ret = read(fd, buf, length);
67    if (ret < 0) {
68        printf("Error reading from slave. (%d)\n", ret);
69        goto cmd_read_finish;
70    }
71
72    for (int i = 0; i < length; i++) {
73        printf(" %02x", buf[i]);
74        if (i % 32 == 31) printf("\n");
75    }
76    printf("\n");
77
78cmd_read_finish:
79    free(buf);
80    return ret;
81}
82
83int cmd_write(int fd, int argc, const char** argv) {
84    if (argc < 1) {
85        print_usage();
86        return 1;
87    }
88
89    uint8_t* buf = malloc(argc);
90    if (!buf) {
91        printf("Failed to allocate buffer.\n");
92        return 1;
93    }
94
95    int ret = 0;
96
97    errno = 0;
98    for (int i = 0; i < argc; i++) {
99        buf[i] = strtol(argv[i], NULL, 16);
100        if (errno) {
101            ret = errno;
102            print_usage();
103            goto cmd_write_finish;
104        }
105    }
106
107    ret = write(fd, buf, argc);
108    if (ret < 0)
109        printf("Error writing to slave. (%d)\n", ret);
110
111cmd_write_finish:
112    free(buf);
113    return ret;
114}
115
116int cmd_transfer(int fd, int argc, const char** argv) {
117    const size_t base_size = sizeof(i2c_slave_ioctl_segment_t);
118    int ret = ZX_OK;
119
120    // Figure out how big our buffers need to be.
121    // Start the counters with enough space for the I2C_SEGMENT_TYPE_END
122    // segment.
123    size_t in_len = base_size;
124    size_t out_len = 0;
125    int segments = 1;
126    int count = argc;
127    const char** arg = argv;
128    errno = 0;
129    while (count) {
130        if (count < 2) {
131            print_usage();
132            goto cmd_transfer_finish_2;
133        }
134
135        in_len += base_size;
136
137        int read;
138        if (!strcmp(arg[0], "r")) {
139            read = 1;
140        } else if (!strcmp(arg[0], "w")) {
141            read = 0;
142        } else {
143            print_usage();
144            goto cmd_transfer_finish_2;
145        }
146        segments++;
147
148        long int length = strtol(arg[1], NULL, 10);
149        if (errno) {
150            print_usage();
151            return errno;
152        }
153        arg += 2;
154        count -= 2;
155        if (read) {
156            out_len += length;
157        } else {
158            in_len += length;
159            if (length > count) {
160                print_usage();
161                goto cmd_transfer_finish_2;
162            }
163            arg += length;
164            count -= length;
165        }
166    }
167
168    // Allocate the input and output buffers.
169    void* in_buf = malloc(in_len);
170    void* out_buf = malloc(out_len);
171    if (!in_buf || !out_buf) {
172        ret = 1;
173        goto cmd_transfer_finish_1;
174    }
175    uint8_t* data_addr = (uint8_t*)in_buf + segments * base_size;
176    uint8_t* data_buf = data_addr;
177
178    // Fill the "input" buffer which is sent to the ioctl.
179    uintptr_t in_addr = (uintptr_t)in_buf;
180    int i = 0;
181    i2c_slave_ioctl_segment_t* ioctl_segment = (i2c_slave_ioctl_segment_t*)in_addr;
182    while (i < argc) {
183        if (!strcmp(argv[i++], "r")) {
184            ioctl_segment->type = I2C_SEGMENT_TYPE_READ;
185            ioctl_segment->len = strtol(argv[i++], NULL, 10);
186            if (errno) {
187                print_usage();
188                return errno;
189            }
190        } else {
191            ioctl_segment->type = I2C_SEGMENT_TYPE_WRITE;
192            ioctl_segment->len = strtol(argv[i++], NULL, 10);
193            if (errno) {
194                print_usage();
195                return errno;
196            }
197
198            for (int seg = 0; seg < ioctl_segment->len; seg++) {
199                *data_buf++ = strtol(argv[i++], NULL, 16);
200                if (errno) {
201                    print_usage();
202                    return errno;
203                }
204            }
205        }
206        ioctl_segment++;
207    }
208    ioctl_segment->type = I2C_SEGMENT_TYPE_END;
209    ioctl_segment->len = 0;
210    ioctl_segment++;
211    // We should be at the start of the data section now.
212    if ((uint8_t*)ioctl_segment != data_addr) {
213        ret = 1;
214        goto cmd_transfer_finish_1;
215    }
216
217    ret = ioctl_i2c_slave_transfer(fd, in_buf, in_len, out_buf, out_len);
218    if (ret < 0)
219        goto cmd_transfer_finish_1;
220
221    for (size_t i = 0; i < out_len; i++) {
222        printf(" %02x", ((uint8_t*)out_buf)[i]);
223        if (i % 32 == 31) printf("\n");
224    }
225    printf("\n");
226
227    ret = 0;
228
229cmd_transfer_finish_1:
230    free(in_buf);
231    free(out_buf);
232cmd_transfer_finish_2:
233    return ret;
234}
235
236int main(int argc, const char** argv) {
237    if (argc < 1)
238        return 1;
239
240    prog_name = argv[0];
241
242    if (argc < 3) {
243        print_usage();
244        return 1;
245    }
246
247    const char* dev = argv[1];
248    const char* cmd = argv[2];
249
250    argc -= 3;
251    argv += 3;
252
253    int fd = open(dev, O_RDWR);
254    if (fd < 0) {
255        printf("Error opening I2C device.\n");
256        return 1;
257    }
258
259    if (!strcmp("read", cmd)) {
260        return cmd_read(fd, argc, argv);
261    } else if (!strcmp("write", cmd)) {
262        return cmd_write(fd, argc, argv);
263    } else if (!strcmp("transfer", cmd)) {
264        return cmd_transfer(fd, argc, argv);
265    } else {
266        printf("Unrecognized command %s.\n", cmd);
267        print_usage();
268        return 1;
269    }
270
271    printf("We should never get here!.\n");
272    return 1;
273}
274