/*
    i2ctransfer.c - A user-space program to send concatenated i2c messages
    Copyright (C) 2015-17 Wolfram Sang <wsa@sang-engineering.com>
    Copyright (C) 2015-17 Renesas Electronics Corporation

    Based on i2cget.c:
    Copyright (C) 2005-2012  Jean Delvare <jdelvare@suse.de>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
*/

#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include "i2cbusses.h"
#include "util.h"
#include "../version.h"

enum parse_state {
	PARSE_GET_DESC,
	PARSE_GET_DATA,
};

#define PRINT_STDERR	(1 << 0)
#define PRINT_READ_BUF	(1 << 1)
#define PRINT_WRITE_BUF	(1 << 2)
#define PRINT_HEADER	(1 << 3)

static void help(void)
{
	fprintf(stderr,
		"Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...\n"
		"  I2CBUS is an integer or an I2C bus name\n"
		"  DESC describes the transfer in the form: {r|w}LENGTH[@address]\n"
		"    1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)\n"
		"  DATA are LENGTH bytes for a write message. They can be shortened by a suffix:\n"
		"    = (keep value constant until LENGTH)\n"
		"    + (increase value by 1 until LENGTH)\n"
		"    - (decrease value by 1 until LENGTH)\n"
		"    p (use pseudo random generator until LENGTH with value as seed)\n\n"
		"Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):\n"
		"  # i2ctransfer 0 w1@0x50 0x64 r8\n"
		"Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):\n"
		"  # i2ctransfer 0 w17@0x50 0x42 0xff-\n");
}

static int check_funcs(int file)
{
	unsigned long funcs;

	/* check adapter functionality */
	if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
		fprintf(stderr, "Error: Could not get the adapter "
			"functionality matrix: %s\n", strerror(errno));
		return -1;
	}

	if (!(funcs & I2C_FUNC_I2C)) {
		fprintf(stderr, MISSING_FUNC_FMT, "I2C transfers");
		return -1;
	}

	return 0;
}

static void print_msgs(struct i2c_msg *msgs, __u32 nmsgs, unsigned flags)
{
	FILE *output = flags & PRINT_STDERR ? stderr : stdout;
	unsigned i;
	__u16 j;

	for (i = 0; i < nmsgs; i++) {
		int read = msgs[i].flags & I2C_M_RD;
		int print_buf = (read && (flags & PRINT_READ_BUF)) ||
				(!read && (flags & PRINT_WRITE_BUF));

		if (flags & PRINT_HEADER)
			fprintf(output, "msg %u: addr 0x%02x, %s, len %u",
				i, msgs[i].addr, read ? "read" : "write", msgs[i].len);

		if (msgs[i].len && print_buf) {
			if (flags & PRINT_HEADER)
				fprintf(output, ", buf ");
			for (j = 0; j < msgs[i].len - 1; j++)
				fprintf(output, "0x%02x ", msgs[i].buf[j]);
			/* Print final byte with newline */
			fprintf(output, "0x%02x\n", msgs[i].buf[j]);
		} else if (flags & PRINT_HEADER) {
			fprintf(output, "\n");
		}
	}
}

static int confirm(const char *filename, struct i2c_msg *msgs, __u32 nmsgs)
{
	fprintf(stderr, "WARNING! This program can confuse your I2C bus, cause data loss and worse!\n");
	fprintf(stderr, "I will send the following messages to device file %s:\n", filename);
	print_msgs(msgs, nmsgs, PRINT_STDERR | PRINT_HEADER | PRINT_WRITE_BUF);

	fprintf(stderr, "Continue? [y/N] ");
	fflush(stderr);
	if (!user_ack(0)) {
		fprintf(stderr, "Aborting on user request.\n");
		return 0;
	}

	return 1;
}

int main(int argc, char *argv[])
{
	char filename[20];
	int i2cbus, address = -1, file, arg_idx = 1, nmsgs = 0, nmsgs_sent, i;
	int force = 0, yes = 0, version = 0, verbose = 0, all_addrs = 0;
	struct i2c_msg msgs[I2C_RDRW_IOCTL_MAX_MSGS];
	enum parse_state state = PARSE_GET_DESC;
	unsigned buf_idx = 0;

	for (i = 0; i < I2C_RDRW_IOCTL_MAX_MSGS; i++)
		msgs[i].buf = NULL;

	/* handle (optional) arg_idx first */
	while (arg_idx < argc && argv[arg_idx][0] == '-') {
		switch (argv[arg_idx][1]) {
		case 'V': version = 1; break;
		case 'v': verbose = 1; break;
		case 'f': force = 1; break;
		case 'y': yes = 1; break;
		case 'a': all_addrs = 1; break;
		default:
			fprintf(stderr, "Error: Unsupported option \"%s\"!\n",
				argv[arg_idx]);
			help();
			exit(1);
		}
		arg_idx++;
	}

	if (version) {
		fprintf(stderr, "i2ctransfer version %s\n", VERSION);
		exit(0);
	}

	if (arg_idx == argc) {
		help();
		exit(1);
	}

	i2cbus = lookup_i2c_bus(argv[arg_idx++]);
	if (i2cbus < 0)
		exit(1);

	file = open_i2c_dev(i2cbus, filename, sizeof(filename), 0);
	if (file < 0 || check_funcs(file))
		exit(1);

	while (arg_idx < argc) {
		char *arg_ptr = argv[arg_idx];
		unsigned long len, raw_data;
		__u16 flags;
		__u8 data, *buf;
		char *end;

		if (nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) {
			fprintf(stderr, "Error: Too many messages (max: %d)\n",
				I2C_RDRW_IOCTL_MAX_MSGS);
			goto err_out;
		}

		switch (state) {
		case PARSE_GET_DESC:
			flags = 0;

			switch (*arg_ptr++) {
			case 'r': flags |= I2C_M_RD; break;
			case 'w': break;
			default:
				fprintf(stderr, "Error: Invalid direction\n");
				goto err_out_with_arg;
			}

			len = strtoul(arg_ptr, &end, 0);
			if (len > 0xffff || arg_ptr == end) {
				fprintf(stderr, "Error: Length invalid\n");
				goto err_out_with_arg;
			}

			arg_ptr = end;
			if (*arg_ptr) {
				if (*arg_ptr++ != '@') {
					fprintf(stderr, "Error: Unknown separator after length\n");
					goto err_out_with_arg;
				}

				/* We skip 10-bit support for now. If we want it,
				 * it should be marked with a 't' flag before
				 * the address here.
				 */

				address = parse_i2c_address(arg_ptr, all_addrs);
				if (address < 0)
					goto err_out_with_arg;

				/* Ensure address is not busy */
				if (!force && set_slave_addr(file, address, 0))
					goto err_out_with_arg;
			} else {
				/* Reuse last address if possible */
				if (address < 0) {
					fprintf(stderr, "Error: No address given\n");
					goto err_out_with_arg;
				}
			}

			msgs[nmsgs].addr = address;
			msgs[nmsgs].flags = flags;
			msgs[nmsgs].len = len;

			if (len) {
				buf = malloc(len);
				if (!buf) {
					fprintf(stderr, "Error: No memory for buffer\n");
					goto err_out_with_arg;
				}
				memset(buf, 0, len);
				msgs[nmsgs].buf = buf;
			}

			if (flags & I2C_M_RD || len == 0) {
				nmsgs++;
			} else {
				buf_idx = 0;
				state = PARSE_GET_DATA;
			}

			break;

		case PARSE_GET_DATA:
			raw_data = strtoul(arg_ptr, &end, 0);
			if (raw_data > 0xff || arg_ptr == end) {
				fprintf(stderr, "Error: Invalid data byte\n");
				goto err_out_with_arg;
			}
			data = raw_data;
			len = msgs[nmsgs].len;

			while (buf_idx < len) {
				msgs[nmsgs].buf[buf_idx++] = data;

				if (!*end)
					break;

				switch (*end) {
				/* Pseudo randomness (8 bit AXR with a=13 and b=27) */
				case 'p':
					data = (data ^ 27) + 13;
					data = (data << 1) | (data >> 7);
					break;
				case '+': data++; break;
				case '-': data--; break;
				case '=': break;
				default:
					fprintf(stderr, "Error: Invalid data byte suffix\n");
					goto err_out_with_arg;
				}
			}

			if (buf_idx == len) {
				nmsgs++;
				state = PARSE_GET_DESC;
			}

			break;

		default:
			/* Should never happen */
			fprintf(stderr, "Internal Error: Unknown state in state machine!\n");
			goto err_out;
		}

		arg_idx++;
	}

	if (state != PARSE_GET_DESC || nmsgs == 0) {
		fprintf(stderr, "Error: Incomplete message\n");
		goto err_out;
	}

	if (yes || confirm(filename, msgs, nmsgs)) {
		struct i2c_rdwr_ioctl_data rdwr;

		rdwr.msgs = msgs;
		rdwr.nmsgs = nmsgs;
		nmsgs_sent = ioctl(file, I2C_RDWR, &rdwr);
		if (nmsgs_sent < 0) {
			fprintf(stderr, "Error: Sending messages failed: %s\n", strerror(errno));
			goto err_out;
		} else if (nmsgs_sent < nmsgs) {
			fprintf(stderr, "Warning: only %d/%d messages were sent\n", nmsgs_sent, nmsgs);
		}

		print_msgs(msgs, nmsgs_sent, PRINT_READ_BUF | (verbose ? PRINT_HEADER | PRINT_WRITE_BUF : 0));
	}

	close(file);

	for (i = 0; i < nmsgs; i++)
		free(msgs[i].buf);

	exit(0);

 err_out_with_arg:
	fprintf(stderr, "Error: faulty argument is '%s'\n", argv[arg_idx]);
 err_out:
	close(file);

	for (i = 0; i <= nmsgs; i++)
		free(msgs[i].buf);

	exit(1);
}
