/*
 * \brief  DDE iPXE NIC API implementation
 * \author Christian Helmuth
 * \date   2010-09-13
 */

/*
 * Copyright (C) 2010-2012 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* DDE kit */
#include <dde_kit/pci.h>
#include <dde_kit/lock.h>
#include <dde_kit/semaphore.h>
#include <dde_kit/timer.h>
#include <dde_kit/interrupt.h>
#include <dde_kit/dde_kit.h>

/* iPXE */
#include <stdlib.h>
#include <ipxe/netdevice.h>
#include <ipxe/pci.h>
#include <ipxe/iobuf.h>

#include <dde_ipxe/nic.h>
#include "local.h"

/**
 * DDE iPXE mutual exclusion lock
 */
static struct dde_kit_lock *ipxe_lock;

#define ENTER dde_kit_lock_lock(ipxe_lock)
#define LEAVE dde_kit_lock_unlock(ipxe_lock)

/**
 * Bottom-half activation semaphore
 */
static struct dde_kit_sem *bh_sema;

/**
 * Network device driven by iPXE
 */
static struct net_device *net_dev;

/**
 * RX callback function pointer
 */
static dde_ipxe_nic_rx_cb rx_callback;

/**
 * Known iPXE driver structures (located in the driver binaries)
 */
extern struct pci_driver
	e1000_82540_driver, e1000_82541_driver, e1000_82542_driver, e1000_82543_driver,
	e1000e_80003es2lan_driver, e1000e_82571_driver, e1000e_ich8lan_driver,
//	ifec_driver,
//	rtl8139_driver,
//	nepci_driver,
	pcnet32_driver;

/**
 * Driver database (used for probing)
 */
static struct pci_driver *pci_drivers[] = {
	&e1000_82540_driver, &e1000_82541_driver, &e1000_82542_driver, &e1000_82543_driver,
	&e1000e_80003es2lan_driver, &e1000e_82571_driver, &e1000e_ich8lan_driver,
//	&ifec_driver,
//	&rtl8139_driver,
//	&nepci_driver,
	&pcnet32_driver
};


/**
 * Update BARs of PCI device
 */
static void pci_read_bases(struct pci_device *pci_dev)
{
	uint32_t bar;
	int reg;

	for (reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4) {
		pci_read_config_dword(pci_dev, reg, &bar);
		if (bar & PCI_BASE_ADDRESS_SPACE_IO) {
			if (!pci_dev->ioaddr) {
				pci_dev->ioaddr = bar & PCI_BASE_ADDRESS_IO_MASK;

				dde_kit_addr_t base = bar & PCI_BASE_ADDRESS_IO_MASK;
				dde_kit_size_t size = pci_bar_size(pci_dev, reg);
				dde_kit_request_io(base, size);
			}
		} else {
			if (!pci_dev->membase)
				pci_dev->membase = bar & PCI_BASE_ADDRESS_MEM_MASK;
			/* Skip next BAR if 64-bit */
			if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64)
				reg += 4;
		}
	}
}


/**
 * Probe one PCI device
 */
static int probe_pci_device(struct pci_device *pci_dev)
{
	int j;
	for (j = 0; j < sizeof(pci_drivers)/sizeof(*pci_drivers); j++) {
		struct pci_driver *driver = pci_drivers[j];

		int i;
		for (i = 0; i < driver->id_count; i++) {
			struct pci_device_id *id = &driver->ids[i];
			if ((id->vendor != PCI_ANY_ID) && (id->vendor != pci_dev->vendor))
				continue;
			if ((id->device != PCI_ANY_ID) && (id->device != pci_dev->device))
				continue;
			pci_set_driver(pci_dev, driver, id);

			LOG("using driver %s", pci_dev->id->name);
			int ret = driver->probe(pci_dev);
			if (ret != 0) {
				LOG("probe failed for %s", pci_dev->id->name);
				continue;
			}
			return 0;
		}
	}

	LOG("no driver found");
	return -1;
}


enum { NO_DEVICE_FOUND = ~0U };

/**
 * Scan the PCI bus
 *
 * \return PCI location of NIC found; NO_DEVICE_FOUND otherwise
 */
static unsigned scan_pci(void)
{
	int ret, bus = 0, dev = 0, fun = 0; 
	for (ret = dde_kit_pci_first_device(&bus, &dev, &fun);
	     ret == 0;
	     ret = dde_kit_pci_next_device(&bus, &dev, &fun)) {

		dde_kit_uint32_t class_code;
		dde_kit_pci_readl(bus, dev, fun, PCI_CLASS_REVISION, &class_code);
		class_code >>= 8;
		if (PCI_BASE_CLASS(class_code) != PCI_BASE_CLASS_NETWORK)
			continue;

		dde_kit_uint16_t vendor, device;
		dde_kit_pci_readw(bus, dev, fun, PCI_VENDOR_ID, &vendor);
		dde_kit_pci_readw(bus, dev, fun, PCI_DEVICE_ID, &device);
		dde_kit_uint8_t rev, irq;
		dde_kit_pci_readb(bus, dev, fun, PCI_REVISION_ID, &rev);
		dde_kit_pci_readb(bus, dev, fun, PCI_INTERRUPT_LINE, &irq);
		LOG("Found: " FMT_BUSDEVFN " %04x:%04x (rev %02x) IRQ %02x",
		            bus, dev, fun, vendor, device, rev, irq);

		struct pci_device *pci_dev = zalloc(sizeof(*pci_dev));
		ASSERT(pci_dev != 0);

		pci_dev->busdevfn = PCI_BUSDEVFN(bus, dev, fun);
		pci_dev->vendor   = vendor;
		pci_dev->device   = device;
		pci_dev->class    = class_code;
		pci_dev->irq      = irq;

		pci_read_bases(pci_dev);

		pci_dev->dev.desc.bus_type = BUS_TYPE_PCI;
		pci_dev->dev.desc.location = pci_dev->busdevfn;
		pci_dev->dev.desc.vendor   = pci_dev->vendor;
		pci_dev->dev.desc.device   = pci_dev->device;
		pci_dev->dev.desc.class    = pci_dev->class;
		pci_dev->dev.desc.ioaddr   = pci_dev->ioaddr;
		pci_dev->dev.desc.irq      = pci_dev->irq;

		/* we found our device -> break loop */
		if (!probe_pci_device(pci_dev))
			return pci_dev->dev.desc.location;

		/* free device if no driver was found */
		free(pci_dev);
	}

	return NO_DEVICE_FOUND;
}


/**
 * IRQ handler registered at DDE kit
 */
static void irq_handler(void *p)
{
	ENTER;

	netdev_poll(net_dev);
	dde_kit_sem_up(bh_sema);

	LEAVE;
}


/**
 * Bottom-half handler executed in separate thread
 *
 * Calls RX callback if appropriate.
 */
static void bh_handler(void *p)
{
	while (1) {
		dde_kit_sem_down(bh_sema);

		ENTER;

		struct io_buffer *iobuf;
		while ((iobuf = netdev_rx_dequeue(net_dev))) {
			LEAVE;
			if (rx_callback)
				rx_callback(1, iobuf->data, iob_len(iobuf));
			ENTER;
			free_iob(iobuf);
		}

		LEAVE;
	}
}


/************************
 ** API implementation **
 ************************/

dde_ipxe_nic_rx_cb dde_ipxe_nic_register_rx_callback(dde_ipxe_nic_rx_cb cb)
{
	ENTER;

	dde_ipxe_nic_rx_cb old = rx_callback;
	rx_callback            = cb;

	LEAVE;
	return old;
}


int dde_ipxe_nic_tx(unsigned if_index, const char *packet, unsigned packet_len)
{
	if (if_index != 1)
		return -1;

	ENTER;

	struct io_buffer *iobuf = alloc_iob(packet_len);

	LEAVE;
	memcpy(iob_put(iobuf, packet_len), packet, packet_len);
	ENTER;

	netdev_poll(net_dev);
	netdev_tx(net_dev, iob_disown(iobuf));

	LEAVE;
	return 0;
}


int dde_ipxe_nic_get_mac_addr(unsigned if_index, char *out_mac_addr)
{
	if (if_index != 1)
		return -1;

	ENTER;

	out_mac_addr[0] = net_dev->hw_addr[0];
	out_mac_addr[1] = net_dev->hw_addr[1];
	out_mac_addr[2] = net_dev->hw_addr[2];
	out_mac_addr[3] = net_dev->hw_addr[3];
	out_mac_addr[4] = net_dev->hw_addr[4];
	out_mac_addr[5] = net_dev->hw_addr[5];

	LEAVE;
	return 0;
}


int dde_ipxe_nic_init(void)
{
	dde_kit_init();
	dde_kit_timer_init(0, 0);
	dde_kit_pci_init();
	dde_kit_lock_init(&ipxe_lock);

	slab_init();

	ENTER;

	/* scan all pci devices and drivers */
	unsigned location = scan_pci();
	if (location == NO_DEVICE_FOUND)
		return 0;

	/* find and open iPXE NIC device */
	net_dev = find_netdev_by_location(BUS_TYPE_PCI, location);
	if (netdev_open(net_dev)) {
		LOG("opening device " FMT_BUSDEVFN " failed",
		    PCI_BUS(net_dev->dev->desc.location),
		    PCI_SLOT(net_dev->dev->desc.location),
		    PCI_FUNC(net_dev->dev->desc.location));
		return 0;
	}

	/* initialize IRQ handler and enable interrupt/bottom-half handling */
	bh_sema = dde_kit_sem_init(0);
	dde_kit_thread_create(bh_handler, 0, "bh_handler");
	int err = dde_kit_interrupt_attach(net_dev->dev->desc.irq, 0,
	                                   0, irq_handler, 0);
	if (err) {
		LOG("attaching to IRQ %02x failed", net_dev->dev->desc.irq);
		return 0;
	}
	netdev_irq(net_dev, 1);

	LEAVE;

	/* always report 1 device was found */
	return 1;
}
