#include "kvm/devices.h"
#include "kvm/kvm.h"
#include "kvm/pci.h"
#include "kvm/virtio-mmio.h"

#include <linux/err.h>
#include <linux/rbtree.h>

struct device_bus {
	struct rb_root	root;
	int		dev_num;
};

static struct device_bus device_trees[DEVICE_BUS_MAX] = {
	[0 ... (DEVICE_BUS_MAX - 1)] = { RB_ROOT, 0 },
};

int device__register(struct device_header *dev)
{
	struct device_bus *bus;
	struct rb_node **node, *parent = NULL;

	if (dev->bus_type >= DEVICE_BUS_MAX) {
		pr_warning("Ignoring device registration on unknown bus %d\n",
			   dev->bus_type);
		return -EINVAL;
	}

	bus = &device_trees[dev->bus_type];
	dev->dev_num = bus->dev_num++;

	switch (dev->bus_type) {
	case DEVICE_BUS_PCI:
		pci__assign_irq(dev);
		break;
	case DEVICE_BUS_MMIO:
		virtio_mmio_assign_irq(dev);
		break;
	default:
		break;
	}

	node = &bus->root.rb_node;
	while (*node) {
		int num = rb_entry(*node, struct device_header, node)->dev_num;
		int result = dev->dev_num - num;

		if (result < 0)
			node = &((*node)->rb_left);
		else if (result > 0)
			node = &((*node)->rb_right);
		else
			return -EEXIST;
	}

	rb_link_node(&dev->node, parent, node);
	rb_insert_color(&dev->node, &bus->root);
	return 0;
}

void device__unregister(struct device_header *dev)
{
	struct device_bus *bus = &device_trees[dev->bus_type];
	rb_erase(&dev->node, &bus->root);
}

struct device_header *device__find_dev(enum device_bus_type bus_type, u8 dev_num)
{
	struct rb_node *node;

	if (bus_type >= DEVICE_BUS_MAX)
		return ERR_PTR(-EINVAL);

	node = device_trees[bus_type].root.rb_node;
	while (node) {
		struct device_header *dev = rb_entry(node, struct device_header,
						     node);
		if (dev_num < dev->dev_num) {
			node = node->rb_left;
		} else if (dev_num > dev->dev_num) {
			node = node->rb_right;
		} else {
			return dev;
		}
	}

	return NULL;
}

struct device_header *device__first_dev(enum device_bus_type bus_type)
{
	struct rb_node *node;

	if (bus_type >= DEVICE_BUS_MAX)
		return NULL;

	node = rb_first(&device_trees[bus_type].root);
	return node ? rb_entry(node, struct device_header, node) : NULL;
}

struct device_header *device__next_dev(struct device_header *dev)
{
	struct rb_node *node = rb_next(&dev->node);
	return node ? rb_entry(node, struct device_header, node) : NULL;
}
