/******************************************************************************
 * Copyright (c) 2011 IBM Corporation
 * All rights reserved.
 * This program and the accompanying materials
 * are made available under the terms of the BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/

#include <stdint.h>
#include "netdriver_int.h"
#include "libhvcall.h"

static snk_kernel_t     *snk_kernel_interface;
static snk_module_t	*snk_module_interface;
static unsigned int	g_reg;

#define printk(fmt...)  do { snk_kernel_interface->print(fmt); } while(0)
#define malloc(args...) snk_kernel_interface->k_malloc(args)
#define malloc_aligned(args...) snk_kernel_interface->k_malloc_aligned(args)
#define free(args...)  do { snk_kernel_interface->k_free(args); } while(0)

#define dprintk(fmt...)
//#define dprintk(fmt...)	printk(fmt)

/* *** WARNING: We pass our addresses as-is as DMA addresses,
 *     we -do- rely on the forth code to have enabled TCE bypass
 *     on our device !
 */
#define vaddr_to_dma(vaddr)	((uint64_t)vaddr)

struct ibmveth_buf_desc_fields {
	uint32_t flags_len;
#define IBMVETH_BUF_VALID	0x80000000
#define IBMVETH_BUF_TOGGLE	0x40000000
#define IBMVETH_BUF_NO_CSUM	0x02000000
#define IBMVETH_BUF_CSUM_GOOD	0x01000000
#define IBMVETH_BUF_LEN_MASK	0x00FFFFFF
	uint32_t address;
};

union ibmveth_buf_desc {
	uint64_t desc;
	struct ibmveth_buf_desc_fields fields;
};

struct ibmveth_rx_q_entry {
	uint32_t flags_off;
#define IBMVETH_RXQ_TOGGLE		0x80000000
#define IBMVETH_RXQ_TOGGLE_SHIFT	31
#define IBMVETH_RXQ_VALID		0x40000000
#define IBMVETH_RXQ_NO_CSUM		0x02000000
#define IBMVETH_RXQ_CSUM_GOOD		0x01000000
#define IBMVETH_RXQ_OFF_MASK		0x0000FFFF

	uint32_t length;
	uint64_t correlator;
};

static void *buffer_list; 
static void *filter_list; 
static uint64_t *rx_bufs;
static uint64_t *rx_bufs_aligned;
static uint32_t cur_rx_toggle;
static uint32_t cur_rx_index;

#define RX_QUEUE_SIZE	16
#define RX_BUF_SIZE	2048
#define RX_BUF_MULT	(RX_BUF_SIZE >> 3)

static struct ibmveth_rx_q_entry *rx_queue;

static char * memcpy( char *dest, const char *src, size_t n )
{
        char *ret = dest;
        while( n-- ) {
                *dest++ = *src++;
        }

        return( ret );
}

static inline uint64_t *veth_get_rx_buf(unsigned int i)
{
	return &rx_bufs_aligned[i * RX_BUF_MULT];
}

static int veth_init(void)
{
	char *mac_addr = snk_module_interface->mac_addr;
	union ibmveth_buf_desc rxq_desc;
	unsigned long rx_queue_len = sizeof(struct ibmveth_rx_q_entry) *
		RX_QUEUE_SIZE;
	unsigned int i;
	long rc;

	dprintk("veth_init(%02x:%02x:%02x:%02x:%02x:%02x)\n",
		mac_addr[0], mac_addr[1], mac_addr[2],
		mac_addr[3], mac_addr[4], mac_addr[5]);

	if (snk_module_interface->running != 0)
		return 0;

	cur_rx_toggle = IBMVETH_RXQ_TOGGLE;
	cur_rx_index = 0;
	buffer_list = malloc_aligned(8192, 4096);
	filter_list = buffer_list + 4096;
	rx_queue = malloc_aligned(rx_queue_len, 16);
	rx_bufs = malloc(2048 * RX_QUEUE_SIZE + 4);
	if (!buffer_list || !filter_list || !rx_queue || !rx_bufs) {
		printk("veth: Failed to allocate memory !\n");
		goto fail;
	}
	rx_bufs_aligned = (uint64_t *)(((uint64_t)rx_bufs | 3) + 1);
	rxq_desc.fields.address = vaddr_to_dma(rx_queue);
	rxq_desc.fields.flags_len = IBMVETH_BUF_VALID | rx_queue_len;

	rc = h_register_logical_lan(g_reg,
				    vaddr_to_dma(buffer_list),
				    rxq_desc.desc,
				    vaddr_to_dma(filter_list),
				    (*(uint64_t *)mac_addr) >> 16);
	if (rc != H_SUCCESS) {
		printk("veth: Error %ld registering interface !\n", rc);
		goto fail;
	}
	for (i = 0; i < RX_QUEUE_SIZE; i++) {
		uint64_t *buf = veth_get_rx_buf(i);
		union ibmveth_buf_desc desc;
		*buf = (uint64_t)buf;
		desc.fields.address = vaddr_to_dma(buf);
		desc.fields.flags_len = IBMVETH_BUF_VALID | RX_BUF_SIZE;
		h_add_logical_lan_buffer(g_reg, desc.desc);
	}

	snk_module_interface->running = 1;

	return 0;
 fail:
	if (filter_list)
		free(filter_list);
	if (buffer_list)
		free(buffer_list);
	if (rx_queue)
		free(rx_queue);
	if (rx_bufs)
		free(rx_bufs);
	return -1;
}

static int veth_term(void)
{
	dprintk("veth_term()\n");

	if (snk_module_interface->running == 0)
		return 0;

	h_free_logical_lan(g_reg);

	if (filter_list)
		free(filter_list);
	if (buffer_list)
		free(buffer_list);
	if (rx_queue)
		free(rx_queue);
	if (rx_bufs)
		free(rx_bufs);

	snk_module_interface->running = 0;

	return 0;
}

static int veth_xmit(char *f_buffer_pc, int f_len_i)
{
	union ibmveth_buf_desc tx_desc;
	long rc;

	dprintk("veth_xmit(packet at %p, %d bytes)\n", f_buffer_pc, f_len_i);

	tx_desc.fields.address = vaddr_to_dma(f_buffer_pc);
	tx_desc.fields.flags_len = IBMVETH_BUF_VALID | f_len_i;

	rc = hv_send_logical_lan(g_reg, tx_desc.desc, 0, 0, 0, 0, 0);
	if (rc != H_SUCCESS) {
		printk("veth: Error %ld sending packet !\n", rc);
		return -1;
	}

	return f_len_i;
}

static int veth_receive(char *f_buffer_pc, int f_len_i)
{
	int packet = 0;

	dprintk("veth_receive()\n");

	while(!packet) {
		struct ibmveth_rx_q_entry *desc = &rx_queue[cur_rx_index];
		union ibmveth_buf_desc bdesc;
		void *buf;

		buf = (void *)desc->correlator;

		if ((desc->flags_off & IBMVETH_RXQ_TOGGLE) != cur_rx_toggle)
			break;

		if (!(desc->flags_off & IBMVETH_RXQ_VALID))
			goto recycle;
		if (desc->length > f_len_i) {
			printk("veth: Dropping too big packet [%d bytes]\n",
			       desc->length);
			goto recycle;
		}

		packet = desc->length;
		memcpy(f_buffer_pc,
		       buf + (desc->flags_off & IBMVETH_RXQ_OFF_MASK), packet);
	recycle:
		bdesc.fields.address = vaddr_to_dma(buf);
		bdesc.fields.flags_len = IBMVETH_BUF_VALID | RX_BUF_SIZE;
		h_add_logical_lan_buffer(g_reg, bdesc.desc);

		cur_rx_index = (cur_rx_index + 1) % RX_QUEUE_SIZE;
		if (cur_rx_index == 0)
			cur_rx_toggle ^= IBMVETH_RXQ_TOGGLE;
	}

	return packet;
}

static int veth_ioctl(int request, void* data)
{
	dprintk("veth_ioctl()\n");

	return 0;
}

static snk_module_t veth_interface = {
	.version = 1,
	.type    = MOD_TYPE_NETWORK,
	.running = 0,
	.init    = veth_init,
	.term    = veth_term,
	.write   = veth_xmit,
	.read    = veth_receive,
	.ioctl   = veth_ioctl
};


static int check_driver(vio_config_t *conf)
{
	if (snk_kernel_interface->strcmp(conf->compat, "IBM,l-lan")) {
		dprintk( "veth: netdevice not supported\n" );
		return -1;
	}

	g_reg = conf->reg[0];

	return 0;
}

snk_module_t* veth_module_init(snk_kernel_t *snk_kernel_int, vio_config_t *conf)
{
	snk_kernel_interface = snk_kernel_int;
	snk_module_interface = &veth_interface;

	if (snk_kernel_int->version != snk_module_interface->version)
		return 0;

	/* Check if this is the right driver */
	if (check_driver(conf) < 0)
		return 0;

	snk_module_interface->link_addr = module_init;
	return snk_module_interface;
}
