/*
 * Copyright (C) 2010 by David Brownell
 *
 * 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 3 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Simple utility to parse and dump ARM Cortex-M3 SWO trace output.  Once the
 * mechanisms work right, this information can be used for various purposes
 * including profiling (particularly easy for flat PC-sample profiles) and
 * for debugging.
 *
 * SWO is the Single Wire Output found on some ARM cores, most notably on the
 * Cortex-M3.  It combines data from several sources:
 *
 *  - Software trace (ITM):  so-called "printf-style" application messaging
 *    using "ITM stimulus ports"; and differential timestamps.
 *  - Hardware trace (DWT):  for profiling counters and comparator matches.
 *  - TPIU may issue sync packets.
 *
 * The trace data format is defined in Appendix E, "Debug ITM and DWT packet
 * protocol", of the ARMv7-M Architecture Reference Manual (DDI 0403C).  It
 * is a superset of the ITM data format from the Coresight TRM.
 *
 * The trace data has two encodings.  The working assumption is that data
 * gets into this program using the UART encoding.
 */

#include <errno.h>
#include <libgen.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>


/* Example ITM trace word (0xWWXXYYZZ) parsing for task events, sent
 * on port 31 (Reserved for "the" RTOS in CMSIS v1.30)
 *   WWXX: event code (0..3 pre-assigned, 4..15 reserved)
 *   YY:   task priority
 *   ZZ:   task number
 *
 * NOTE that this specific encoding could be space-optimized; and that
 * trace data streams could also be history-sensitive.
 */
static void show_task(int port, unsigned data)
{
	unsigned code = data >> 16;
	char buf[16];

	switch (code) {
	case 0:
		strcpy(buf, "run");
		break;
	case 1:
		strcpy(buf, "block");
		break;
	case 2:
		strcpy(buf, "create");
		break;
	case 3:
		strcpy(buf, "destroy");
		break;
	/* 4..15 reserved for other infrastructure ops */
	default:
		sprintf(buf, "code %d", code);
		break;
	}
	printf("TASK %d, pri %d: %s",
		(data >> 0) & 0xff,
		(data >> 8) & 0xff,
		buf);
}

static void show_reserved(FILE *f, char *label, int c)
{
	unsigned i;

	printf("%s - %#02x", label, c);

	for (i = 0; (c & 0x80) && i < 4; i++) {
		c = fgetc(f);
		if (c == EOF) {
			printf("(ERROR %d - %s) ", errno, strerror(errno));
			break;
		}
		printf(" %#02x", c);
	}

	printf("\n");
}

static bool read_varlen(FILE *f, int c, unsigned *value)
{
	unsigned size;
	unsigned char buf[4];
	unsigned i;

	*value = 0;

	switch (c & 3) {
	case 3:
		size = 4;
		break;
	case 2:
		size = 2;
		break;
	case 1:
		size = 1;
		break;
	default:
		printf("INVALID SIZE\n");
		return false;
	}

	memset(buf, 0, sizeof buf);
	if (fread(buf, 1, size, f) != size)
		goto err;

	*value =  (buf[3] << 24)
		+ (buf[2] << 16)
		+ (buf[2] << 8)
		+ (buf[0] << 0);
	return true;

err:
	printf("(ERROR %d - %s)\n", errno, strerror(errno));
	return;
}

static void show_hard(FILE *f, int c)
{
	unsigned type = c >> 3;
	unsigned value;
	unsigned size;
	char *label;

	printf("DWT - ", type);

	if (!read_varlen(f, c, &value))
		return;
	printf("%#x", value);

	switch (type) {
	case 0:				/* event counter wrapping */
		printf("overflow %s%s%s%s%s%s",
			(value & (1 << 5)) ? "cyc " : "",
			(value & (1 << 4)) ? "fold " : "",
			(value & (1 << 3)) ? "lsu " : "",
			(value & (1 << 2)) ? "slp " : "",
			(value & (1 << 1)) ? "exc " : "",
			(value & (1 << 0)) ? "cpi " : "");
		break;
	case 1:				/* exception tracing */
		switch (value >> 12) {
		case 1:
			label = "entry to";
			break;
		case 2:
			label = "exit from";
			break;
		case 3:
			label = "return to";
			break;
		default:
			label = "?";
			break;
		}
		printf("%s exception %d", label, value & 0x1ff);
		break;
	case 2:				/* PC sampling */
		if (c == 0x15)
			printf("PC - sleep");
		else
			printf("PC - %#08x", value);
		break;
	case 8:				/* data tracing, pc value */
	case 10:
	case 12:
	case 14:
		printf("Data trace %d, PC %#08x", (c >> 4) & 3, value);
		/* optionally followed by data value */
		break;
	case 9:				/* data tracing, address offset */
	case 11:
	case 13:
	case 15:
		printf("Data trace %d, address offset %#04x",
				(c >> 4) & 3, value);
		/* always followed by data value */
		break;
	case 16 ... 23:			/* data tracing, data value */
		printf("Data trace %d, ", (c >> 4) & 3);
		label = (c & 0x8) ? "write" : "read";
		switch (c & 3) {
		case 3:
			printf("word %s, value %#08x", label, value);
			break;
		case 2:
			printf("halfword %s, value %#04x", label, value);
			break;
		case 1:
			printf("byte %s, value %#02x", label, value);
			break;
		}
		break;
	default:
		printf("UNDEFINED");
		break;
	}

	printf("\n");
	return;
}

/*
 * Table of SWIT (SoftWare InstrumentTation) message dump formats, for
 * ITM port 0..31 application data.
 *
 * Eventually this should be customizable; all usage is application defined.
 *
 * REVISIT there can be up to 256 trace ports, via "ITM Extension" packets
 */
struct {
	int port;
	void (*show)(int port, unsigned data);
} format[] = {
	{ .port = 31,  .show = show_task, },
};

static void show_swit(FILE *f, int c)
{
	unsigned size;
	unsigned port = c >> 3;
	unsigned char buf[4];
	unsigned value = 0;
	unsigned i;

	printf("SWIT %u - ", port);

	if (!read_varlen(f, c, &value))
		return;
	printf("%#08x", value);

	for (i = 0; i <= sizeof(format) / sizeof(format[0]); i++) {
		if (format[i].port == port) {
			printf(", ");
			format[i].show(port, value);
			break;
		}
	}

	printf("\n");
	return;

err:
	printf("(ERROR %d - %s)\n", errno, strerror(errno));
	return;
}

static void show_timestamp(FILE *f, int c)
{
	unsigned counter = 0;
	char *label = "";
	bool delayed = false;

	printf("TIMESTAMP - ");

	/* Format 2: header only */
	if (!(c & 0x80)) {
		switch (c) {
		case 0:		/* sync packet -- coding error! */
		case 0x70:	/* overflow -- ditto! */
			printf("ERROR - %#02x\n", c);
			break;
		default:
			/* synchronous to ITM */
			counter = c >> 4;
			goto done;
		}
		return;
	}

	/* Format 1:  one to four bytes of data too */
	switch (c) {
	default:
		label = ", reserved control\n";
		break;
	case 0xc:
		/* synchronous to ITM */
		break;
	case 0xd:
		label = ", timestamp delayed";
		delayed = true;
		break;
	case 0xe:
		label = ", packet delayed";
		delayed = true;
		break;
	case 0xf:
		label = ", packet and timetamp delayed";
		delayed = true;
		break;
	}

	c = fgetc(f);
	if (c == EOF)
		goto err;
	counter = c & 0x7f;
	if (!(c & 0x80))
		goto done;

	c = fgetc(f);
	if (c == EOF)
		goto err;
	counter |= (c & 0x7f) << 7;
	if (!(c & 0x80))
		goto done;

	c = fgetc(f);
	if (c == EOF)
		goto err;
	counter |= (c & 0x7f) << 14;
	if (!(c & 0x80))
		goto done;

	c = fgetc(f);
	if (c == EOF)
		goto err;
	counter |= (c & 0x7f) << 21;

done:
	/* REVISIT should we try to convert from delta values?  */
	printf("+%u%s\n", counter, label);
	return;

err:
	printf("(ERROR %d - %s) ", errno, strerror(errno));
	goto done;
}

int main(int argc, char **argv)
{
	FILE *f = stdin;
	int c;

	/* parse arguments */
	while ((c = getopt(argc, argv, "f:")) != EOF) {
		switch (c) {
		case 'f':
			/* e.g. from UART connected to /dev/ttyUSB0 */
			f = fopen(optarg, "r");
			if (!f) {
				perror(optarg);
				return 1;
			}
			break;
		default:
usage:
			fprintf(stderr, "usage: %s [-f input]",
				basename(argv[0]));
			return 1;
		}
	}

	/* Parse data ... records have a header then data bytes.
	 * NOTE: we assume getc() deals in 8-bit bytes.
	 */
	bool overflow = false;

	while ((c = getc(f)) != EOF) {

		/* Sync packet ... 7 zeroes, 0x80 */
		if (c == 0) {
			int i;

			for (i = 0; i < 6; i++) {
				c = fgetc(f);
				if (c == EOF)
					break;
				if (c != 0)
					goto bad_sync;
			}
			c = fgetc(f);
			if (c == 0x80) {
				printf("SYNC\n");
				continue;
			}
bad_sync:
			printf("BAD SYNC\n");
			continue;
		}

		/* Overflow packet */
		if (c == 0x70) {
			/* REVISIT later, report just what overflowed!
			 * Timestamp and SWIT can happen.  Non-ITM too?
			 */
			overflow = true;
			printf("OVERFLOW ...\n");
			continue;
		}
		overflow = false;

		switch (c & 0x0f) {
		case 0x00:		/* Timestamp */
			show_timestamp(f, c);
			break;
		case 0x04:		/* "Reserved" */
			show_reserved(f, "RESERVED", c);
			break;
		case 0x08:		/* ITM Extension */
			/* FIXME someday, handle these ...  */
			show_reserved(f, "ITM EXT", c);
			break;
		case 0x0c:		/* DWT Extension */
			show_reserved(f, "DWT EXT", c);
			break;
		default:
			if (c & 4)
				show_hard(f, c);
			else
				show_swit(f, c);
			break;
		}

	}

	return 0;
}
