Add option to convert from .iic to .bin

The "--convert" option reads in an .iic file and converts it to a .bin
file.  The .bin file is saved to the output file specified with the
--output option.

BUG=chromium-os:23066
TEST=cyapa --convert --output=/tmp/CYTRA.bin  CYTRA.iic

Change-Id: I0ded7079c79552aa9613923015bc4f633966182b
diff --git a/cyapa_fw_update.c b/cyapa_fw_update.c
index 167c023..aca9bcf 100644
--- a/cyapa_fw_update.c
+++ b/cyapa_fw_update.c
@@ -94,7 +94,9 @@
 #define CYAPA_FW_SIZE               (CYAPA_FW_OFFSET_END - \
                                      CYAPA_FW_OFFSET_START + 1)
 #define CYAPA_BAK_READ_BLOCK_LEN    16
-#define CYAPA_UPDATE_BLOCK_LEN      64
+#define CYAPA_FW_BLOCK_LEN          64
+#define CYAPA_FW_BLOCK_COUNT        (CYAPA_FW_SIZE / CYAPA_FW_BLOCK_LEN)
+#define CYAPA_FW_START_BLOCK       (CYAPA_FW_OFFSET_START / CYAPA_FW_BLOCK_LEN)
 
 #define CYAPA_IIC_CMD_OP_NONE	0
 #define CYAPA_IIC_CMD_OP_READ	1
@@ -126,6 +128,7 @@
 	int file_type;
 	bool backup_fw;
 	bool force;
+	bool convert;
 
 	const char *new_fw_image;
 	const char *bak_fw_image;
@@ -162,7 +165,7 @@
 
 
 /* Global buffer for storing firmware image */
-unsigned char fw_bak_buf[CYAPA_FW_SIZE];
+unsigned char fw_buf[CYAPA_FW_SIZE];
 
 
 #if DBG_DUMP_DATA_BLOCK
@@ -209,6 +212,10 @@
 			" path is \"/tmp/cypress\".\n");
 	printf("\t     If -o option is set, use the path specified in"
 			"-o <path>.\n");
+	printf("\t -c, --convert <iic-path>\n");
+	printf("\t     Convert new-firmware-image (.iic) to a .bin file.\n");
+	printf("\t     Does not actually write firmware to the device.\n");
+	printf("\t     Use --output to specify the destination path.\n");
 	printf("\t -o, --output <path>\n");
 	printf("\t     Specifies full path of the output file for where to\n");
 	printf("\t     back up a copy of the current trackpad firmware.\n");
@@ -243,6 +250,7 @@
 
 static struct option options[] = {
 		{"backup", no_argument, NULL, 'b'},
+		{"convert", no_argument, NULL, 'c'},
 		{"force", no_argument, NULL, 'f'},
 		{"help", no_argument, NULL, 'h'},
 		{"output", required_argument, NULL, 'o'},
@@ -274,7 +282,7 @@
 	while (1) {
 		int index = 0;
 
-		c = getopt_long(argc, argv, "bfho:v", options, &index);
+		c = getopt_long(argc, argv, "bcfho:v", options, &index);
 		if (c == -1)
 			break;
 
@@ -282,6 +290,9 @@
 		case 'b':
 			args->backup_fw = true;
 			break;
+		case 'c':
+			args->convert = true;
+			break;
 		case 'f':
 			args->force = true;
 			break;
@@ -948,7 +959,7 @@
 	len = CYAPA_BAK_READ_BLOCK_LEN;
 	for (i = 0; i < total; i += len) {
 		ret = cyapa_read_fw_block(args->fd_dev, offset + i,
-					  &fw_bak_buf[i], len);
+					  &fw_buf[i], len);
 		if (ret < 0) {
 			prt_err("Failed to read firmware image, %d.\n", ret);
 			return -2;
@@ -957,10 +968,10 @@
 		show_progress(100 * (i+1) / total, SHOW_PROGRESS_CONT, NULL);
 	}
 
-	cyapa_fixup_fw_buffer(fw_bak_buf);
+	cyapa_fixup_fw_buffer(fw_buf);
 
 	/* store read data to output file. */
-	ret = write(args->fd_bak_fw, fw_bak_buf, total);
+	ret = write(args->fd_bak_fw, fw_buf, total);
 	if (ret != total) {
 		prt_err("Failed to write backup firmware image.\n");
 		perror(NULL);
@@ -1174,7 +1185,7 @@
 	int fd = args->fd_new_fw;
 	char iic_buf[128];
 	int cmd_op;
-	unsigned char cmd_buf[CYAPA_UPDATE_BLOCK_LEN];
+	unsigned char cmd_buf[CYAPA_FW_BLOCK_LEN];
 	int cmd_data_len;
 	FILE *fp = fdopen(fd, "r");
 	char *retp = NULL;
@@ -1331,7 +1342,7 @@
 	block_buf[8] = 0x06;
 	block_buf[9] = 0x07;
 	/* block index is sent big endian. */
-	block_index = (unsigned short)(offset / CYAPA_UPDATE_BLOCK_LEN);
+	block_index = (unsigned short)(offset / CYAPA_FW_BLOCK_LEN);
 	block_buf[10] = block_index >> 8;
 	block_buf[11] = block_index;
 	memcpy(&block_buf[12], buf, len);
@@ -1398,27 +1409,27 @@
 	int ret = 0;
 	int total_blocks;
 	unsigned short offset;
-	unsigned char buf[CYAPA_UPDATE_BLOCK_LEN];
+	unsigned char buf[CYAPA_FW_BLOCK_LEN];
 
 	total_blocks = (CYAPA_FW_OFFSET_END - CYAPA_FW_OFFSET_START + 1) /
-			CYAPA_UPDATE_BLOCK_LEN;
+			CYAPA_FW_BLOCK_LEN;
 	blocks = 0;
-	offset = start + blocks * CYAPA_UPDATE_BLOCK_LEN;
-	while ((offset + CYAPA_UPDATE_BLOCK_LEN - 1) <= end) {
+	offset = start + blocks * CYAPA_FW_BLOCK_LEN;
+	while ((offset + CYAPA_FW_BLOCK_LEN - 1) <= end) {
 		/* read data block from firmware image file. */
 		memset(buf, 0, sizeof(buf));
-		ret = cyapa_read_bin_fw_image(args, offset,
-				buf, CYAPA_UPDATE_BLOCK_LEN);
+		ret = cyapa_read_bin_fw_image(args, offset, buf,
+					      CYAPA_FW_BLOCK_LEN);
 		if (ret < 0) {
 			prt_err("Failed to read %d bytes block data at 0x%04x"
 				" from .bin firmware image file, %d\n",
-				CYAPA_UPDATE_BLOCK_LEN, offset, ret);
+				CYAPA_FW_BLOCK_LEN, offset, ret);
 			return ret;
 		}
 
 		/* write firmware data block to trackpad device. */
 		ret = cyapa_write_fw_image_block(args, offset,
-				buf, CYAPA_UPDATE_BLOCK_LEN);
+				buf, CYAPA_FW_BLOCK_LEN);
 		if (ret < 0) {
 			prt_err("Failed to write an image block data"
 				" to trackpad device, %d\n", ret);
@@ -1427,7 +1438,7 @@
 
 		/* update pointers. */
 		blocks++;
-		offset = start + blocks * CYAPA_UPDATE_BLOCK_LEN;
+		offset = start + blocks * CYAPA_FW_BLOCK_LEN;
 
 		++(*blocks_written);
 		show_progress(((*blocks_written) * 100 / total_blocks),
@@ -1545,6 +1556,142 @@
 		      "Canceling firmware update.\n");
 }
 
+static size_t line_to_buf(char *line, unsigned char *buf, size_t len)
+{
+	size_t cnt = 0;
+	unsigned char b;
+	char *next;
+
+	do {
+		errno = 0;
+		b = strtoul(line, &next, 16);
+		if (line == next)
+			break;
+		line = next;
+		buf[cnt++] = b;
+	} while (cnt < len);
+
+	return cnt;
+}
+
+const unsigned char wr_cmd[] = { 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04,
+				 0x05, 0x06, 0x07 };
+
+/*
+ * Process a 'w' line from an iic file.
+ *
+ * 5 'w' lines, when taken together, form a 78-byte write fw block command,
+ * with a 64-byte payload.
+ */
+static void proc_iic_w_line(const unsigned char *buf, size_t len)
+{
+	static unsigned char cmd[14 + CYAPA_FW_BLOCK_LEN] = { 0 };
+	size_t offset;
+	size_t cmd_len;
+	ssize_t block;
+	unsigned char csum;
+
+	offset = buf[0];
+	cmd_len = offset + len - 1;
+	if (cmd_len > sizeof(cmd) || len == 0)
+		return;
+
+	memcpy(&cmd[offset], &buf[1], len - 1);
+
+	/* Start processing only if command is a full 78 bytes. */
+	if (cmd_len !=  sizeof(cmd))
+		return;
+
+	if (memcmp(cmd, wr_cmd, sizeof(wr_cmd)))
+		return;
+
+	/* Penultimate byte is crc of payload */
+	csum = cyapa_calculate_checksum(&cmd[12], CYAPA_FW_BLOCK_LEN);
+	if (csum != cmd[sizeof(cmd)-2])
+		return;
+	/* Last byte is crc of entire command */
+	csum = cyapa_calculate_checksum(cmd, sizeof(cmd)-1);
+	if (csum != cmd[sizeof(cmd)-1])
+		return;
+
+	block = ((cmd[10] << 8) | cmd[11]) - CYAPA_FW_START_BLOCK;
+	if (block >= 0 && block < CYAPA_FW_BLOCK_COUNT)
+		memcpy(&fw_buf[block * CYAPA_FW_BLOCK_LEN], &cmd[12],
+		       CYAPA_FW_BLOCK_LEN);
+}
+
+/*
+ * An iic file consists of a sequence of commands, each on a single text line.
+ * The commands are 'r', 'w', or '[delay=X]'.
+ *
+ * Each write command has the following format:
+ * w <addr> <line_offset>
+ */
+static int convert_iic(struct args *args)
+{
+	int ret;
+	FILE *f_iic;
+	FILE *f_bin;
+	char line[256];
+	unsigned char buf[20];
+	size_t n;
+
+	if (!args->new_fw_image || !args->bak_fw_image)
+		return -EINVAL;
+	f_iic = fopen(args->new_fw_image, "r");
+	if (f_iic == NULL) {
+		perror(NULL);
+		return -EIO;
+	}
+
+	while (!feof(f_iic)) {
+		char *r = fgets(line, sizeof(line), f_iic);
+		if (r == NULL)
+			break;
+
+		if (ferror(f_iic)) {
+			perror(NULL);
+			ret = -1;
+			goto close_iic;
+		}
+
+		/* Ignore lines that aren't part of a write command */
+		if (strncmp(line, "w 67 00 ", 8))
+			continue;
+
+		/* Extract remaining hex-encoded bytes on line into buf */
+		n = line_to_buf(&line[8], buf, sizeof(buf));
+
+		/* Ignore "w 67 00 p" */
+		if (n == 0)
+			continue;
+
+		proc_iic_w_line(buf, n);
+	}
+
+	f_bin = fopen(args->bak_fw_image, "wb");
+	if (f_bin == NULL) {
+		perror(NULL);
+		ret = -EIO;
+		goto close_iic;
+	}
+
+	printf("Writing %d bytes\n", CYAPA_FW_SIZE);
+	ret = fwrite(fw_buf, 1, CYAPA_FW_SIZE, f_bin);
+	if (ret != CYAPA_FW_SIZE) {
+		perror(NULL);
+		ret = -EIO;
+		goto close_bin;
+	}
+
+	ret = 0;
+close_bin:
+	fclose(f_bin);
+close_iic:
+	fclose(f_iic);
+	return ret;
+}
+
 int main(int argc, char **argv)
 {
 	int err_code = 0;
@@ -1568,6 +1715,9 @@
 		exit(0);
 	}
 
+	if (args.convert)
+		return convert_iic(&args);
+
 	/* open device and firmware image files. */
 	if (open_trackpad_dev(&args)) {
 		prt_err("unable to open trackpad device.\n");