/*
 * Copyright (c) 2007, 2008 University of Tsukuba
 * 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 the University of Tsukuba 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.
 */

/**
 * @file	drivers/pci.c
 * @brief	PCI driver (core)
 * @author	T. Shinagawa
 */

/*
 * Copyright (c) 2010-2013 Yuichi Watanabe
 */

#include <common/common.h>
#include <core/printf.h>
#include <core/spinlock.h>
#include <core/vm.h>
#include <io/pci.h>
#include "pci_internal.h"

drvdata_hdl_t		pci_handle;
struct resource		mmio_resource;
struct resource		io_resource;

LIST2_DEFINE_HEAD(struct pci_device, pci_device_on_root_bus);
LIST_DEFINE_HEAD(pci_device_list);
LIST_DEFINE_HEAD(pci_driver_list);

void
pci_init_root_resources(void)
{
	rm_init_resource(&mmio_resource, 0, 0xffffffffffffffffLL,
			 RESOURCE_TYPE_MMIO, 0, "mmio_resource");
	rm_init_resource(&io_resource, 0, 0x0000ffff,
			 RESOURCE_TYPE_IO, 0, "io_resource");

}

void
pci_dump_resources(void)
{
	rm_dump_resources(&mmio_resource);
	rm_dump_resources(&io_resource);
}

static void
pci_insert_recouces(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->resource + i;
		if (resource->type == RESOURCE_TYPE_INVALID) {
			continue;
		}

		if (parent == NULL) {
			switch (resource->type) {
			case RESOURCE_TYPE_MMIO:
				rm_insert_resource(&mmio_resource, resource);
				break;
			case RESOURCE_TYPE_IO:
				rm_insert_resource(&io_resource, resource);
				break;
			}
		} else {
			for (j = 0; j < PCI_RESOURCE_NUM; j++) {
				presource = parent->resource + 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);
		}
	}
}

vmmerr_t
pci_alloc_resource(struct pci_device *dev, struct resource *resource,
		  size_t size, phys_t align, u32 type, u32 data)
{
	struct pci_device *parent;
	struct resource *presource;
	vmmerr_t err;
	int j;

	parent = dev->parent;
	if (parent == NULL) {
		return VMMERR_NODEV;
	}

	for (j = 0; j < PCI_RESOURCE_NUM; j++) {
		presource = parent->resource + j;
		if ((presource->data & PCI_RESOURCE_WINDOW) == 0) {
			continue;
		}
		if (presource->type != type) {
			continue;
		}
		if ((presource->data & PCI_RESOURCE_PREFETCHABLE) &&
		    (data & PCI_RESOURCE_PREFETCHABLE) == 0) {
			continue;
		}
		err = rm_alloc_resource(presource, resource, size,
					align, type, data, dev->name);
		if (err == VMMERR_SUCCESS) {
			break;
		}
	}
	if (resource->parent == NULL) {
		return VMMERR_BUSY;
	}
	return VMMERR_SUCCESS;
}

void
pci_register_device(struct pci_device *parent, struct pci_device *dev)
{
#ifdef PRINT_PCI_DEVICE
	printf("%s %04x:%04x %06x %02x\n",
	       dev->name,
	       dev->vendor_id, dev->device_id,
	       dev->class_code, dev->header_type);
#endif
	LIST_APPEND(pci_device_list, dev);
	if (parent) {
		dev->parent = parent;
		LIST2_ADD(parent->children, sibling, dev);
	} else {
		dev->parent = NULL;
		LIST2_ADD(pci_device_on_root_bus, sibling, dev);
	}
	pci_insert_recouces(dev);
}

struct pci_device *
pci_next_device_on_root_bus(struct pci_device *cur)
{
	return LIST2_NEXT(pci_device_on_root_bus, sibling, cur);
}

/**
 * @brief		PCI driver registration function
 * @param  driver	pointer to struct pci_driver
 */
void pci_register_driver(struct pci_driver *driver)
{
	struct pci_device *dev;

	LIST_APPEND(pci_driver_list, driver);

	LIST_FOREACH (pci_device_list, dev) {
		u32 id = dev->id;
		u32 class = dev->class_code;

		if (idmask_match(id, driver->id) &&
		    idmask_match(class, driver->class)) {
			dev->driver = driver;
			driver->new(dev);
		}
	}
	return;
}
