/*
 * \brief  Virtual PCI bus tree for DDE kit
 * \author Christian Helmuth
 * \date   2008-10-22
 */

/*
 * Copyright (C) 2008-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.
 */

#ifndef _PCI_TREE_H_
#define _PCI_TREE_H_

#include <base/stdint.h>
#include <base/printf.h>
#include <base/lock.h>

#include <util/avl_tree.h>

#include <pci_session/connection.h>
#include <pci_device/client.h>

namespace Dde_kit {

	using namespace Genode;

	class Pci_device : public Avl_node<Pci_device>
	{
		public:

			static inline unsigned short knit_bdf(int bus, int dev, int fun)
			{
				return ((bus & 0xff) << 8)
				     | ((dev & 0x1f) << 3)
				     | (fun  & 0x07);
			}

		private:

			Pci::Device_client _device;
			unsigned short     _bdf;    /* bus:device:function */

		public:

			Pci_device(Pci::Device_capability device_cap) : _device(device_cap)
			{
				unsigned char bus = ~0, dev = ~0, fun = ~0;

				_device.bus_address(&bus, &dev, &fun);
				_bdf = knit_bdf(bus, dev, fun);
			}

			/***************
			 ** Accessors **
			 ***************/

			unsigned short bdf() const { return  _bdf; }
			unsigned char  bus() const { return (_bdf >> 8) & 0xff; }
			unsigned char  dev() const { return (_bdf >> 3) & 0x1f; }
			unsigned char  fun() const { return  _bdf       & 0x07; }


			/********************************
			 ** Configuration space access **
			 ********************************/

			uint32_t config_read(unsigned char address, Pci::Device::Access_size size);

			void config_write(unsigned char address, uint32_t val,
			                  Pci::Device::Access_size size);

			/** AVL node comparison */
			bool higher(Pci_device *device) {
				return (_bdf < device->_bdf); }

			Pci_device *next(Pci_device *root, Side direction)
			{
				/*
				 * The predecessor of a node is the right-most node of the left
				 * subtree or the parent right after the first "left turn" up to
				 * the root of the tree. Symmetrically, the successor of a node is
				 * the left-most node of the right subtree or the parent right
				 * after the first "right turn" up to the root of the tree.
				 */
				if (child(direction)) {
					Pci_device *n = child(direction);
					while (true) {
						if (!n->child(!direction))
							return n;
						else
							n = n->child(!direction);
					}
				} else {
					Pci_device *n = this;
					for (Pci_device *parent = static_cast<Pci_device *>(n->_parent);
					     n != root;
					     n = parent, parent = static_cast<Pci_device *>(n->_parent)) {
						if (n == parent->child(!direction))
							return parent;
					}

					return 0;
				}
			}

			void show()
			{
				if (child(LEFT)) child(LEFT)->show();

				unsigned char ht = config_read(0x0e, Pci::Device::ACCESS_8BIT) & 0xff;

				PINF("%02x:%02x.%x %04x:%04x (%x) ht=%02x",
				     bus(), dev(), fun(),
				     _device.vendor_id(), _device.device_id(), _device.base_class(), ht);

				if (child(RIGHT)) child(RIGHT)->show();
			}
	};

	class Pci_tree
	{
		public:

			class Not_found : public Exception { };

		private:

			int                  _current_dev_num;
			Pci::Connection      _pci_drv;
			Avl_tree<Pci_device> _devices;

			Lock                 _lock;

			/* Lookup device for given BDF */
			Pci_device *_lookup(unsigned short bdf)
			{
				if (!_devices.first())
					throw Not_found();

				Pci_device *d = _devices.first();

				do {
					if (bdf == d->bdf())
						return d;

					if (bdf < d->bdf())
						d = d->child(Pci_device::LEFT);
					else
						d = d->child(Pci_device::RIGHT);
				} while (d);

				throw Not_found();
			}

			/** Find lowest BDF */
			void _first_bdf(int *bus, int *dev, int *fun)
			{
				if (!_devices.first())
					throw Not_found();

				Pci_device *first, *prev, *root;
				first = prev = root = _devices.first();

				do {
					first = prev;
					prev = prev->next(root, Pci_device::LEFT);
				} while (prev);

				*bus = first->bus();
				*dev = first->dev();
				*fun = first->fun();
			}

			/** Find next BDF */
			void _next_bdf(Pci_device *prev, int *bus, int *dev, int *fun)
			{
				if (!_devices.first())
					throw Not_found();

				Pci_device *next = prev->next(_devices.first(), Pci_device::RIGHT);

				if (!next)
					throw Not_found();

				*bus = next->bus();
				*dev = next->dev();
				*fun = next->fun();
			}

			void _show_devices()
			{
				if (_devices.first())
					_devices.first()->show();
			}

		public:

			Pci_tree();

			uint32_t config_read(int bus, int dev, int fun, unsigned char address,
			                     Pci::Device::Access_size size)
			{
				Lock::Guard lock_guard(_lock);

				unsigned short bdf = Pci_device::knit_bdf(bus, dev, fun);

				return _lookup(bdf)->config_read(address, size);
			}

			void config_write(int bus, int dev, int fun, unsigned char address,
			                  uint32_t val, Pci::Device::Access_size size)
			{
				Lock::Guard lock_guard(_lock);

				unsigned short bdf = Pci_device::knit_bdf(bus, dev, fun);

				_lookup(bdf)->config_write(address, val, size);
			}

			/**
			 * Lookup first device
			 */
			void first_device(int *bus, int *dev, int *fun)
			{
				Lock::Guard lock_guard(_lock);

				_first_bdf(bus, dev, fun);
			}

			/**
			 * Lookup next device
			 */
			void next_device(int *bus, int *dev, int *fun)
			{
				Lock::Guard lock_guard(_lock);

				Pci_device *d = _lookup(Pci_device::knit_bdf(*bus, *dev, *fun));

				_next_bdf(d, bus, dev, fun);
			}
	};
}

#endif /* _PCI_TREE_H_ */
