/*
 * Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
 *
 * Licensed under GPLv2
 */

#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <linux/types.h>

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <szedata2.h>

#define SZETEST2_VERSION	"0.1"

#define CHARS_PER_LINE		16

static long PAGE_SIZE;

static void print_hex_dump_bytes(const char *prefix_str, const void *vbuf,
		size_t len)
{
	const __u8 *buf = vbuf;
	unsigned int a, b;
	char lbuf[10 + CHARS_PER_LINE * 2 + 5 + CHARS_PER_LINE + 1];
	char *pos;

	if (strlen(prefix_str) > 10)
		return;

	for (a = 0; a < len / CHARS_PER_LINE; a++) {
		pos = lbuf;
		for (b = 0; b < CHARS_PER_LINE; b++) {
			pos += sprintf(pos, "%.2x ", buf[b]);
			if (!((b + 1) % 8) && b != CHARS_PER_LINE - 1)
				*pos++ = ' ';
		}
		for (b = 0; b < CHARS_PER_LINE; b++, buf++)
			*pos++ = isascii(*buf) && isprint(*buf) ? *buf : '.';
		*pos = '\0';

		printf("%s%s\n", prefix_str ? : "", lbuf);
	}
}

static void usage(const char *app)
{
	printf("%s v. " SZETEST2_VERSION "\n", app);
}

static int test_space(int fd, struct sze2_instance_info *a, unsigned int i)
{
	char *b;
	unsigned int x;

	b = mmap(NULL, a->sizes[i] + 1, PROT_READ, MAP_SHARED, fd,
			a->offsets[i]);
	if (b != MAP_FAILED) {
		puts("mmap b 1 didn't fail!");
		munmap(b, a->sizes[i] + 1);
	}
	b = mmap(NULL, a->sizes[i], PROT_READ, MAP_SHARED, fd,
			a->offsets[i] + PAGE_SIZE);
	if (b != MAP_FAILED) {
		puts("mmap b 2 didn't fail!");
		munmap(b, a->sizes[i]);
	}
	b = mmap(NULL, a->sizes[i], PROT_READ, MAP_SHARED, fd,
			a->offsets[i]);
	if (b == MAP_FAILED) {
		warn("mmap b %u failed", i);
		return -1;
	}
	if (i == SZE2_MMIO_RX) {
		for (x = 0; x < PAGE_SIZE * 10; x++) {
			if (!(x % PAGE_SIZE))
				putchar('|');
			putchar(b[x + a->sizes[i] / 4]);
		}
		for (x = 0; x < PAGE_SIZE * 10; x++) {
			if (!(x % PAGE_SIZE))
				putchar('|');
			putchar(b[x + a->sizes[i] / 2 - PAGE_SIZE * 10]);
		}
	}

/*	puts(b);
	puts(b + PAGE_SIZE);
	puts(b + PAGE_SIZE * 2);
	puts(b + a->sizes[i] - PAGE_SIZE);*/
	munmap(b, a->sizes[i]);

	return 0;
}

static int test_mmap(int fd)
{
	struct sze2_instance_info *a;
	unsigned int i;

	a = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (a != MAP_FAILED) {
		puts("mmap a didn't fail!");
		munmap(a, PAGE_SIZE);
	}

	a = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, 0);
	if (a == MAP_FAILED) {
		warn("mmap a failed");
		goto err;
	}

	puts("spaces:");
	for (i = 0; i < SZE2_MMIO_MAX; i++)
		printf("\t%u: %llu %llu\n", i, a->offsets[i], a->sizes[i]);

	for (i = 0; i < SZE2_MMIO_MAX; i++)
		if (a->sizes[i])
			if (test_space(fd, a, i))
				goto err_unmap_a;

	munmap(a, PAGE_SIZE);
	return 0;
err_unmap_a:
	munmap(a, PAGE_SIZE);
err:
	return 100;
}

int main(int argc, char *argv[])
{
	struct sze2_instance_info *info;
	struct szedata2_packet *pkt;
	struct sze2_subscribe_area sub;
	void *rx;
	unsigned int mmap_test = 0;
	__u32 u32;
	int fd;

	while ((fd = getopt(argc, argv, "hm")) >= 0)
		switch (fd) {
		case 'm':
			mmap_test = 1;
			break;
		case '?':
		case 'h':
			usage(*argv);
			exit(1);
			break;
		}

	PAGE_SIZE = sysconf(_SC_PAGE_SIZE);
	if (PAGE_SIZE <= 0)
		errx(2, "sysconf(_SC_PAGE_SIZE) failed");

	fd = open("/dev/szedataII0", O_RDWR);
	if (fd < 0)
		err(3, "open");

	if (mmap_test)
		exit(test_mmap(fd));

	info = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, 0);
	if (info == MAP_FAILED)
		err(4, "mmap info failed");

	rx = mmap(NULL, info->sizes[SZE2_MMIO_RX], PROT_READ, MAP_SHARED, fd,
			info->offsets[SZE2_MMIO_RX]);
	if (rx == MAP_FAILED)
		err(5, "rx mmap failed");

	print_hex_dump_bytes("RX1:", rx + info->sizes[SZE2_MMIO_RX] / 4, 160);

	sub.poll_thresh[SZE2_MMIO_RX] = sub.poll_thresh[SZE2_MMIO_TX] = 10;
	sub.areas[SZE2_MMIO_RX] = 0xff;
	sub.areas[SZE2_MMIO_TX] = 0;
	if (ioctl(fd, SZE2_IOC_SUBSCRIBE_AREA, &sub))
		err(6, "ioctl SZE2_IOC_SUBSCRIBE_AREA");

	if (!(info->areas[SZE2_MMIO_RX] & 0x1))
		warnx("RX iface 1 not subscribed");

	if (ioctl(fd, SZE2_IOC_START))
		err(7, "ioctl SZE2_IOC_START 1");

	if (!ioctl(fd, SZE2_IOC_START))
		warnx("ioctl SZE2_IOC_START didn't fail");

	sub.areas[SZE2_MMIO_TX] = 0xff;
	if (ioctl(fd, SZE2_IOC_SUBSCRIBE_AREA, &sub))
		err(8, "ioctl SZE2_IOC_SUBSCRIBE_AREA");

	if (ioctl(fd, SZE2_IOC_START))
		warn("ioctl SZE2_IOC_START 2 (this is OK for non TX HW)");

	printf("subscribed: %x %x\n", info->areas[SZE2_MMIO_RX],
			info->areas[SZE2_MMIO_TX]);

	while (1) {
		u32 = ~0U;
		if (ioctl(fd, SZE2_IOC_RXLOCKDATA, &u32))
			err(9, "ioctl SZE2_IOC_RXLOCKDATA");
		printf("%8llu %5u\n",
				info->adesc[SZE2_MMIO_RX][u32].offset %
				(5*1024*1024),
				info->adesc[SZE2_MMIO_RX][u32].size);
		if (info->adesc[SZE2_MMIO_RX][u32].size) {
			pkt = rx + info->adesc[SZE2_MMIO_RX][u32].offset;
			printf("\t%5u %5u\n", pkt->seg_size, pkt->hw_size);
			if (ioctl(fd, SZE2_IOC_RXUNLOCKDATA))
				err(20, "ioctl SZE2_IOC_RXUNLOCKDATA");
		} else
			sleep(3);
	}

	munmap(rx, info->sizes[SZE2_MMIO_RX]);
	munmap(info, PAGE_SIZE);

	close(fd);
	return 0;
}