/*
 * Copyright (c) 2010-2013 Yuichi Watanabe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Yuichi Watanabe nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <common/list.h>
#include <core/assert.h>
#include <core/initfunc.h>
#include <core/param.h>
#include <core/printf.h>
#include <core/spinlock.h>
#include <core/mm.h>
#include <core/mmio.h>
#include <core/rm.h>
#include <core/string.h>
#include <core/vm.h>
#include <core/sleep.h>
#include <core/vmmerr.h>
#include <io/io.h>
#include <io/pci.h>
#include <io/vmrm.h>
#include "pci_internal.h"


#define PCI_DUMMY_FUNC0_VENDOR_ID 0x0001
#define PCI_DUMMY_FUNC0_DEVICE_ID 0x0001

/* #define NO_CLEAR_IOBITMAP */

static void
pci_save_config(struct pci_device *dev)
{
	int i;

	for (i = 0; i < pci_config_size(); i += sizeof(u32)) {
		dev->saved_config[i / sizeof(u32)] = pci_read_config32(dev, i);
	}
}

static void
pci_save_configs(struct pci_device *dev)
{
	struct pci_device *child;

	pci_save_config(dev);

	LIST2_FOREACH(dev->children, sibling, child) {
		pci_save_configs(child);
	}
}

static void
pci_setup_assigned_device(struct pci_device *dev)
{
	struct pci_device *child;
	struct resource *resource;
	u32 reg_val, mask;
	size_t size;
	vmmerr_t err;

	/*
	 * Disable IRQ for assigned devices.
	 */
	pci_write_config8(dev,
			   PCI_CONFIG_INTERRUPT_LINE,
			   PCI_ASSIGNED_DEVICE_IRQ);

	/*
	 * Disable INTx interrupt.
	 */
	reg_val = pci_read_config16(dev, PCI_CONFIG_COMMAND);
	reg_val |= PCI_CONFIG_COMMAND_INTERRUPT_DISABLE;
	pci_write_config16(dev, PCI_CONFIG_COMMAND, reg_val);

	/*
	 * Assign a memory region to ROM.
	 */
	resource = dev->resource + PCI_RESOURCE_EXPANTION_ROM_INDEX;
	mask = pci_get_base_address_mask(dev, PCI_CONFIG_EXPANSION_ROM);
	if (resource->type == RESOURCE_TYPE_INVALID &&
	    mask != 0x00000000 && mask != 0xFFFFFFFF) {
		size = ~(mask & PCI_CONFIG_ROM_MEMMASK) + 1;
		err = pci_alloc_resource(dev, resource, size, size,
					 RESOURCE_TYPE_MMIO,
					 PCI_RESOURCE_PREFETCHABLE);
		if (err == VMMERR_SUCCESS) {
			reg_val = resource->start & PCI_CONFIG_ROM_MEMMASK;
			pci_write_config32(dev,
					   PCI_CONFIG_EXPANSION_ROM,
					   reg_val);
		} else {
			printf("Failed to allocate a memory region for ROM. "
			       "pci %s size 0x%llx err 0x%x\n",
			       dev->name, size, err);
		}
	}

	LIST2_FOREACH(dev->children, sibling, child) {
		pci_setup_assigned_device(child);
	}
}

static void
pci_insert_vmresources(struct pci_device *dev)
{
	struct pci_device *parent;
	struct resource *resource, *presource;
	vmmerr_t err;
	int i, j;

	parent = dev->parent;

	for (i = 0; i < PCI_RESOURCE_NUM; i++) {
		resource = dev->vmresource + i;

		if (resource->type == RESOURCE_TYPE_INVALID) {
			continue;
		}

		if (parent == NULL) {
			switch (resource->type) {
			case RESOURCE_TYPE_MMIO:
				rm_insert_resource(vmrm_mmio_resource(),
						   resource);
				break;
			case RESOURCE_TYPE_IO:
				rm_insert_resource(vmrm_io_resource(),
						   resource);
				break;
			}
		} else {
			for (j = 0; j < PCI_RESOURCE_NUM; j++) {
				presource = parent->vmresource + j;
				if ((presource->data & PCI_RESOURCE_WINDOW)
				    == 0) {
					continue;
				}
				if (resource->type != presource->type) {
					continue;
				}
				if ((presource->data &
				     PCI_RESOURCE_PREFETCHABLE) &&
				    (resource->data &
				     PCI_RESOURCE_PREFETCHABLE) == 0) {
					continue;
				}
				err = rm_insert_resource(presource, resource);
				if (err == VMMERR_SUCCESS) {
					break;
				}
			}
		}
		if (resource->parent == NULL) {
			printf("Fail to insert resource. %s"
			       " %016llx-%016llx type %x data %x\n",
			       dev->name, resource->start,
			       resource->end, resource->type, resource->data);
		}
	}
}

static void
pci_setup_vmresource(struct pci_device *dev)
{
	struct pci_device *child;
	struct resource *resource;
	int i;

	for (i = 0; i < PCI_RESOURCE_NUM; i++) {
		resource = dev->resource + i;
		if (resource->type == RESOURCE_TYPE_INVALID) {
			continue;
		}
		rm_init_resource(dev->vmresource + i, resource->start,
				 resource->end, resource->type,
				 resource->data, resource->name);
	}
	pci_insert_vmresources(dev);

	LIST2_FOREACH(dev->children, sibling, child) {
		pci_setup_vmresource(child);
	}
}

static void
pci_assign_device(struct pci_device *dev)
{
	struct pci_vm_data *pci_data;
#ifndef NO_CLEAR_IOBITMAP
	struct resource *resource;
	int i, j;
#endif

	pci_data = vm_get_driver_data(pci_handle);
	LIST2_ADD(pci_data->vm_pci_device_list, vm_pci_device_list, dev);

	if (vm_get_id() != 0) {
		pci_setup_assigned_device(dev);
	}
	pci_setup_vmresource(dev);

	pci_save_configs(dev);

#ifndef NO_CLEAR_IOBITMAP
	/*
	 * Set do_io_pass to clear io bitmap so that vm-exits on io
	 * port access will be reduced.
	 */
	for (i = 0; i < PCI_RESOURCE_NUM; i++) {
		resource = dev->resource + i;
		if (resource->type != RESOURCE_TYPE_IO) {
			continue;
		}
		for (j = resource->start; j <= (int)resource->end - 3; j++) {
			set_iofunc(j, do_io_pass);
		}
	}
#endif

	dev->assigned = true;
}

static void
pci_dump_device(struct pci_device *dev, int level)
{
	struct pci_device *child;
	char buf[17] = {0};

	memset(buf, ' ', level <= 8 ? level * 2 : 16);
	printf("    %s%s %04x:%04x %02x %02x\n",
	       buf, dev->name,
	       dev->vendor_id, dev->device_id,
	       dev->class_code, dev->header_type);
	LIST2_FOREACH(dev->children, sibling, child) {
		pci_dump_device(child, level + 1);
	}
}

void
pci_dump_assignment(void)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev = NULL;
	static spinlock_t lock = SPINLOCK_INITIALIZER;

	pci_data = vm_get_driver_data(pci_handle);

	spinlock_lock(&lock);
	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		pci_dump_device(dev, 0);
	}
	spinlock_unlock(&lock);
}

static void
pci_reserve_unused_legacy_ioport(void)
{
	struct resource *resource;
	vmmerr_t err;

	for (;;) {
		resource = alloc(sizeof(struct resource));
		if (resource == NULL) {
			panic("Failed to reserve unused legacy ioport");
		}
		err = rm_alloc_avail_resource(vmrm_io_resource(), resource,
					      0x0, 0xfff, 1,
					      RESOURCE_TYPE_IO, 0, "legacy");
		if (err != VMMERR_SUCCESS) {
			free(resource);
			break;
		}
	}
}

void
pci_assign_devices(void)
{
	struct pci_device *dev = NULL;
	u8 bus_no, dev_no, func_no;
	char param_name[VM_NAME_BUF_SIZE + 4];
	int index;
	vmmerr_t err;
	
	snprintf(param_name, VM_NAME_BUF_SIZE + 4, "%s.pci", vm_get_name());
	while ((dev = pci_next_device_on_root_bus(dev)) != NULL) {
		index = 0;
		while ((err = param_get_bdf(param_name, index++, &bus_no, &dev_no,
					    &func_no))
		       != VMMERR_NOTEXIST) {
			if (err != VMMERR_SUCCESS) {
				printf("Failed to get %s(%d) paramater. err 0x%x\n",
				       param_name, index - 1,  err);
				continue;
			}
			if (dev->bus_no == bus_no &&
			    PCI_DEV_NO(dev->devfn) == dev_no &&
			    PCI_FUNC_NO(dev->devfn) == func_no) {
				pci_assign_device(dev);
			}
		}
	}
	pci_reserve_unused_legacy_ioport();
}

static void
pci_restrict_access(struct pci_device *dev)
{
	u32 port;
	struct resource *resource;
	int i;

	printf("pci_restrict_access %s\n", dev->name);
	for (i = 0; i < PCI_RESOURCE_NUM; i++) {
		resource = dev->resource + i;
		switch (resource->type) {
		case RESOURCE_TYPE_MMIO:
			mmio_register(resource->start,
				      resource->end - resource->start + 1,
				      mmio_do_nothing, NULL);
			break;
		case RESOURCE_TYPE_IO:
			for (port = resource->start; port <= resource->end;
			     port++) {
				set_iofunc(i, do_io_nothing);
			}
			break;
		}
	}
}

void
pci_assign_devices_to_vm0(void)
{
	struct pci_device *dev = NULL;

	/*
	 * This function is called for vm0 after all assignments except
	 * vm0 are completed.
	 */
	while ((dev = pci_next_device_on_root_bus(dev)) != NULL) {
		if (dev->assigned) {
			pci_restrict_access(dev);
			continue;
		}
		pci_assign_device(dev);
	}
	pci_reserve_unused_legacy_ioport();
}

int
pci_check_assignment(u8 bus_no, u8 devfn)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev;

	pci_data = (struct pci_vm_data *)vm_get_driver_data(pci_handle);

	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		if (bus_no == dev->bus_no && devfn == dev->devfn) {
			return PCI_ASSIGNED;
		}
		if (dev->type == PCI_CONFIG_HEADER_TYPE_1 &&
		    bus_no >= dev->sec_bus && bus_no <= dev->sub_bus) {
			return PCI_ASSIGNED;
		}
		if (bus_no == dev->bus_no &&
		    PCI_DEV_NO(devfn) == PCI_DEV_NO(dev->devfn) &&
		    PCI_FUNC_NO(devfn) == 0) {
			return PCI_DUMMY_FUNC0;
		}
	}
	return PCI_NOT_ASSIGNED;
}

struct pci_device *
pci_next_assgined_pci_device(struct pci_device *cur)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev;

	pci_data = (struct pci_vm_data *)vm_get_driver_data(pci_handle);

	dev = LIST2_NEXT(pci_data->vm_pci_device_list,
			 vm_pci_device_list, cur);
	return dev;
}

int
pci_dummy_func0(pci_off_t offset, pci_off_t size, bool wr, void *data)
{
	static char header[0x10 + 3] = {PCI_DUMMY_FUNC0_VENDOR_ID,
					PCI_DUMMY_FUNC0_VENDOR_ID >> 8,
					PCI_DUMMY_FUNC0_DEVICE_ID,
					PCI_DUMMY_FUNC0_DEVICE_ID >> 8,
					0x0, 0x0, 0x0, 0x0,
					0x0, 0x0, 0x0, 0x0,
					0x0, 0x0,
					PCI_CONFIG_HEADER_TYPE_MULTI_FUNCTION |
					PCI_CONFIG_HEADER_TYPE_0,
					0x0,
					0x0, 0x0, 0x0};

	if (wr) {
		return 1; /* no pass */
	}
	if (offset < sizeof(header) - 3) {
		memcpy(data, header + offset, size);
	} else {
		memset(data, 0, size);
	}
	return 1; /* no pass */
}

int
pci_assigned_mmio_addr(phys_t addr, size_t *len)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev;
	struct resource *resource;
	int i;

	pci_data = (struct pci_vm_data *)vm_get_driver_data(pci_handle);

	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		for (i = 0; i < PCI_RESOURCE_NUM; i++) {
			resource = dev->resource + i;
			if (resource->type != RESOURCE_TYPE_MMIO) {
				continue;
			}
			if (resource->start <= addr && resource->end >= addr) {
				if (len) {
					*len = resource->end - addr + 1;
				}
				return 1;
			}
		}
	}
	return 0;
}

int
pci_assigned_ioport(ioport_t port, ioport_t *len)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev;
	struct resource *resource;
	int i;

	pci_data = (struct pci_vm_data *)vm_get_driver_data(pci_handle);

	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		for (i = 0; i < PCI_RESOURCE_NUM; i++) {
			resource = dev->resource + i;
			if (resource->type != RESOURCE_TYPE_IO) {
				continue;
			}
			if (resource->start <= port && resource->end >= port) {
				if (len) {
					*len = resource->end - port + 1;
				}
				return 1;
			}
		}
	}
	return 0;
}

static void
pci_reprogram_device(struct pci_device *dev)
{
	int i;

	printf("Reprogram %s\n", dev->name);
	for (i = 0; i < pci_config_size(); i += sizeof(u32)) {
		pci_write_config32(dev, i, dev->saved_config[i / sizeof(u32)]);
	}
}

static void
pci_reprogram_devices(struct pci_device *dev)
{
	struct pci_device *child;
	u16 vendor_id;
	u16 device_id;

	vendor_id = pci_read_config16(dev, PCI_CONFIG_VENDOR_ID);
	device_id = pci_read_config16(dev, PCI_CONFIG_DEVICE_ID);

	if (dev->vendor_id != vendor_id || dev->device_id != device_id) {
		/* A device might be replaced */
		return;
	}

	pci_reprogram_device(dev);

	LIST2_FOREACH(dev->children, sibling, child) {
		pci_reprogram_devices(child);
	}

}

static void
pci_reset_assigned_device(void)
{
	struct pci_vm_data *pci_data;
	struct pci_device *dev;
	struct pci_device *child;
	u16 reg;

	pci_data = (struct pci_vm_data *)vm_get_driver_data(pci_handle);

	/* Secondary bus reset */
	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		if (dev->type != PCI_CONFIG_HEADER_TYPE_1) {
			continue;
		}
		printf("Execute secondary bus reset on %s\n", dev->name);
		reg = pci_read_config16(dev, PCI_CONFIG_BRIDGE_CONTROL);
		reg |= PCI_CONFIG_SECONDARY_BUS_RESET;
		pci_write_config16(dev, PCI_CONFIG_BRIDGE_CONTROL, reg);

		/* wait 100 ms */
		usleep(100 * 1000);

		reg &= ~PCI_CONFIG_SECONDARY_BUS_RESET;
		pci_write_config16(dev, PCI_CONFIG_BRIDGE_CONTROL, reg);
	}

	/* wait 2 sec */
	usleep(2 * 1000 * 1000);

	/* Reprogram children */
	LIST2_FOREACH(pci_data->vm_pci_device_list, vm_pci_device_list, dev) {
		LIST2_FOREACH(dev->children, sibling, child) {
			pci_reprogram_devices(child);
		}
	}
}

static void
pci_setup_vm(void)
{
	struct pci_vm_data *pci_data;

	pci_data = vm_get_driver_data(pci_handle);
	LIST2_HEAD_INIT(pci_data->vm_pci_device_list);

	if (vm_get_id() == 0) {
		pci_assign_devices_to_vm0();
	} else {
		pci_assign_devices();
	}
}

BUS_SETUPVM(pci_setup_vm);
BUS_RESET(pci_reset_assigned_device);
