/*
 * \brief  Generic and simple virtio device
 * \author Sebastian Sumpf
 * \date   2019-10-10
 */

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

#ifndef _VIRTIO_DEVICE_H_
#define _VIRTIO_DEVICE_H_

#include <util/mmio.h>
#include <util/reconstructible.h>

#include <gic.h>
#include <ram.h>
#include <mmio.h>

namespace Vmm {
	class  Virtio_device;
	class  Virtio_queue;
	struct Virtio_queue_data;
	class  Virtio_descriptor;
	class  Virtio_avail;
	class  Virtio_used;
}

using uint16_t = Genode::uint16_t;
using uint32_t = Genode::uint32_t;
using uint64_t = Genode::uint64_t;
using addr_t   = Genode::addr_t;
using size_t   = Genode::size_t;

struct Vmm::Virtio_queue_data
{
	uint32_t descr_low     { 0 };
	uint32_t descr_high    { 0 };
	uint32_t driver_low    { 0 };
	uint32_t driver_high   { 0 };
	uint32_t device_low    { 0 };
	uint32_t device_high   { 0 };
	uint32_t num           { 0 };
	uint32_t ready         { 0 };
	bool     tx            { false };

	uint64_t descr()  const {
		return ((uint64_t)descr_high  << 32) | descr_low; }
	uint64_t driver() const {
		return ((uint64_t)driver_high << 32) | driver_low; }
	uint64_t device() const {
		return ((uint64_t)device_high << 32) | device_low; }

	enum { MAX_QUEUE_SIZE = 1 << 15 };
};


class Vmm::Virtio_descriptor : Genode::Mmio
{
	public:

		Virtio_descriptor(addr_t base)
		: Mmio(base) {  }


		struct Address : Register<0x0, 64> { };
		struct Length  : Register<0x8, 32> { };

		struct Flags : Register<0xc, 16>
		{
			struct Next     : Bitfield<0, 1> { };
			struct Write    : Bitfield<1, 1> { };
			struct Indirect : Bitfield<2, 1> { };
		};

		struct Next  : Register<0xe, 16> { };

		static constexpr size_t size() { return 16; }

		Virtio_descriptor index(unsigned idx)
		{
			return Virtio_descriptor(base() + (size() * idx));
		}

		uint64_t address() const { return read<Address>(); }
		size_t   length () const { return read<Length>(); }
		uint16_t flags()   const { return read<Flags>(); }
		uint16_t next()    const { return read<Next>(); }
};


class Vmm::Virtio_avail : public Genode::Mmio
{
	public:

		Virtio_avail(addr_t base)
		: Mmio(base) { };

		struct Flags : Register<0x0, 16> { };
		struct Idx   : Register<0x2, 16> { };
		struct Ring  : Register_array<0x4, 16, Virtio_queue_data::MAX_QUEUE_SIZE, 16> { };

		bool inject_irq() { return (read<Flags>() & 1) == 0; }
};


class Vmm::Virtio_used : public Genode::Mmio
{
	public:

		Virtio_used(addr_t base)
		: Mmio(base) { };

		struct Flags : Register<0x0, 16> { };
		struct Idx   : Register<0x2, 16> { };

		struct Elem : Register_array<0x4, 64, Virtio_queue_data::MAX_QUEUE_SIZE, 64>
		{
			struct Id     : Bitfield<0, 32> { };
			struct Length : Bitfield<32,32> { };
		};
};

/** 
 * Split queue implementation
 */
class Vmm::Virtio_queue
{
	private:

		Virtio_queue_data &_data;
		Ram               &_ram;

		Virtio_descriptor _descr { _ram.local_address(_data.descr(),
			Virtio_descriptor::size() * _data.num ) };
		Virtio_avail      _avail { _ram.local_address(_data.driver(), 6 + 2 * _data.num) };
		Virtio_used       _used  { _ram.local_address(_data.device(), 6 + 8 * _data.num) };

		uint16_t   _idx     { 0 };
		uint32_t   _length  { _data.num };
		bool       _tx      { _data.tx  };
		bool const _verbose { false };

	public:

		Virtio_queue(Virtio_queue_data &data, Ram &ram)
		: _data(data), _ram(ram) { }


	bool verbose() const { return _verbose; }

	template <typename FUNC>
	bool notify(FUNC func)
	{
		asm volatile ("dmb ish" : : : "memory");
		uint16_t used_idx  = _used.read<Virtio_used::Idx>();
		uint16_t avail_idx = _avail.read<Virtio_avail::Idx>();

		uint16_t written = 0;

		if (_verbose)
			Genode::log(_length > 64 ? "net $ " : "console $ ",
			            "[", _tx ? "tx] " : "rx] ",
			            "idx: ", _idx, " avail_idx: ", avail_idx, " used_idx: ", used_idx,
			             " queue length: ", _length, " avail flags: ", _avail.read<Virtio_avail::Flags>());

		while (_idx != avail_idx && written < _length)  {

			uint16_t id = _avail.read<Virtio_avail::Ring>(_idx % _length);

			/* make sure id stays in ring */
			id %= _length;

			Virtio_descriptor descr = _descr.index(id);
			uint64_t        address = descr.address();
			size_t           length = descr.length();
			if (!address || !length) break;

			addr_t data = 0;
			try {
				data   = _ram.local_address(address, length);
				length = func(data, length);
			} catch (...) { break; }

			if (length == 0) break;

			_used.write<Virtio_used::Flags>(0);
			Virtio_used::Elem::access_t elem = 0;
			Virtio_used::Elem::Id::set(elem,  id);
			Virtio_used::Elem::Length::set(elem, length);
			_used.write<Virtio_used::Elem>(elem, _idx % _length);
			written++; _idx++;

			if (used_idx + written == avail_idx)  break;
		}

		if (written) {
			used_idx += written;
			_used.write<Virtio_used::Idx>(used_idx);
			asm volatile ("dmb ish" : : : "memory");
		}

		if (written && _verbose)
			Genode::log(_length > 64 ? "net $ " : "console $ ",
			            "[", _tx ? "tx] " : "rx] ", "updated used_idx: ", used_idx,
			            " written: ", written);

		return written > 0 && _avail.inject_irq();
	}
};


class Vmm::Virtio_device : public Vmm::Mmio_device
{
	protected:

		enum { RX = 0, TX = 1, NUM = 2 };
		Virtio_queue_data _data[NUM];
		uint32_t          _current { RX };

		Genode::Constructible<Virtio_queue> _queue[NUM];
		Gic::Irq                           &_irq;
		Ram                                &_ram;
		Genode::Mutex                       _mutex;

		struct Dummy {
			Mmio_register regs[7];
		} _reg_container { .regs = {
			{ "MagicValue", Mmio_register::RO, 0x0, 4, 0x74726976            },
			{ "Version",    Mmio_register::RO, 0x4, 4, 0x2                   },
			{ "DeviceID",   Mmio_register::RO, 0x8, 4, 0x0                   },
			{ "VendorID",   Mmio_register::RO, 0xc, 4, 0x554d4551 /* QEMU */ },
			{ "DeviceFeatureSel", Mmio_register::RW, 0x14, 4, 0 },
			{ "DriverFeatureSel", Mmio_register::RW, 0x24, 4, 0 },
			{ "QueueNumMax", Mmio_register::RO, 0x34, 4, 8 }
		}};


		void _device_id(unsigned id)
		{
			_reg_container.regs[2].set(id);
		}

		void               _queue_select(uint32_t sel) { _current = sel; }
		Virtio_queue_data &_queue_data() { return _data[_current]; }

		void _queue_state(bool const construct)
		{
			if (construct && !_queue[_current].constructed() && _queue_data().num > 0) {
				_queue_data().tx = (_current == TX);
				_queue[_current].construct(_queue_data(), _ram);
			}

			if (!construct && _queue[_current].constructed())
				_queue[_current].destruct();
		}

		void _assert_irq()
		{
			_interrupt_status.set(0x1);
			_irq.assert();
		}

		void _deassert_irq()
		{
			_interrupt_status.set(0);
			_irq.deassert();
		}

		virtual void _notify(unsigned idx) = 0;
		virtual Register _device_specific_features() = 0;

		/***************
		 ** Registers **
		 ***************/

		struct DeviceFeatures : Mmio_register
		{
			enum {
				VIRTIO_F_VERSION_1 = 1,
			};

			Virtio_device &_device;
			Mmio_register &_selector;

			Register read(Address_range&,  Cpu&) override
			{
				/* lower 32 bit */
				if (_selector.value() == 0) return _device._device_specific_features();

				/* upper 32 bit */
				return VIRTIO_F_VERSION_1;
			}

			DeviceFeatures(Virtio_device &device, Mmio_register &selector)
			: Mmio_register("DeviceFeatures", Mmio_register::RO, 0x10, 4),
			  _device(device), _selector(selector)
			{ }
		} _device_features { *this, _reg_container.regs[4] };

		struct DriverFeatures : Mmio_register
		{
			Mmio_register    &_selector;
			uint32_t  _lower { 0 };
			uint32_t  _upper { 0 };

			void write(Address_range&, Cpu&, Register reg) override
			{
				if (_selector.value() == 0) {
					_lower = reg;
				}
				_upper = reg;
			}

			DriverFeatures(Mmio_register &selector)
			: Mmio_register("DriverFeatures", Mmio_register::WO, 0x20, 4),
			  _selector(selector)
			{ }
		} _driver_features { _reg_container.regs[5] };

		struct Status : Mmio_register
		{
			Register read(Address_range&,  Cpu&) override
			{
				return value();
			}

			void write(Address_range&, Cpu&, Register reg) override
			{
				set(reg);
			}

			Status()
			: Mmio_register("Status", Mmio_register::RW, 0x70, 4, 0)
			{ }
		} _status;

		struct QueueSel : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				if (reg >= device.NUM) return;
				device._queue_select(reg);
			}

			QueueSel(Virtio_device &device)
			: Mmio_register("QueueSel", Mmio_register::WO, 0x30, 4),
			  device(device) { }
		} _queue_sel { *this };

		struct QueueNum : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().num = Genode::min(reg,
					device._reg_container.regs[6].value());
			}

			QueueNum(Virtio_device &device)
			: Mmio_register("QueueNum", Mmio_register::WO, 0x38, 4),
			  device(device) { }
		} _queue_num { *this };

		struct QueueReady : Mmio_register
		{
			Virtio_device &device;

			Register read(Address_range&,  Cpu&) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				return device._queue_data().ready; 
			}

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				bool construct = reg == 1 ? true : false;
				device._queue_data().ready = reg;
				device._queue_state(construct);
			}

			QueueReady(Virtio_device &device)
			: Mmio_register("QueueReady", Mmio_register::RW, 0x44, 4),
			  device(device) { }
		} _queue_ready { *this };

		struct QueueNotify : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				if (!device._queue[reg].constructed()) return;

				device._notify(reg);
			}

			QueueNotify(Virtio_device &device)
			: Mmio_register("QueueNotify", Mmio_register::WO, 0x50, 4),
			  device(device) { }
		} _queue_notify { *this };

		struct QueueDescrLow : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().descr_low = reg;
			}

			QueueDescrLow(Virtio_device &device)
			: Mmio_register("QueuDescrLow", Mmio_register::WO, 0x80, 4),
			  device(device) { }
		} _queue_descr_low { *this };

		struct QueueDescrHigh : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().descr_high = reg;
			}

			QueueDescrHigh(Virtio_device &device)
			: Mmio_register("QueuDescrHigh", Mmio_register::WO, 0x84, 4),
			  device(device) { }
		} _queue_descr_high { *this };

		struct QueueDriverLow : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().driver_low = reg;
			}

			QueueDriverLow(Virtio_device &device)
			: Mmio_register("QueuDriverLow", Mmio_register::WO, 0x90, 4),
			  device(device) { }
		} _queue_driver_low { *this };

		struct QueueDriverHigh : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().driver_high = reg;
			}

			QueueDriverHigh(Virtio_device &device)
			: Mmio_register("QueuDriverHigh", Mmio_register::WO, 0x94, 4),
			  device(device) { }
		} _queue_driver_high { *this };

		struct QueueDeviceLow : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().device_low = reg;
			}

			QueueDeviceLow(Virtio_device &device)
			: Mmio_register("QueuDeviceLow", Mmio_register::WO, 0xa0, 4),
			  device(device) { }
		} _queue_device_low { *this };

		struct QueueDeviceHigh : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._queue_data().device_high = reg;
			}

			QueueDeviceHigh(Virtio_device &device)
			: Mmio_register("QueuDeviceHigh", Mmio_register::WO, 0xa4, 4),
			  device(device) { }
		} _queue_device_high { *this };

		struct InterruptStatus : Mmio_register
		{
			Register read(Address_range&,  Cpu&) override
			{
				return value();
			}

			InterruptStatus()
			: Mmio_register("InterruptStatus", Mmio_register::RO, 0x60, 4)
			{ }
		} _interrupt_status;

		struct InterruptAck : Mmio_register
		{
			Virtio_device &device;

			void write(Address_range&, Cpu&, Register reg) override
			{
				Genode::Mutex::Guard guard(device.mutex());
				device._deassert_irq();
			}

			InterruptAck(Virtio_device &device)
			: Mmio_register("InterruptAck", Mmio_register::WO, 0x64, 4),
			  device(device) { }
		} _interrupt_ack { *this };

	public:

		Virtio_device(const char * const name,
		              const uint64_t addr,
		              const uint64_t size,
		              unsigned irq,
		              Cpu      &cpu,
		              Mmio_bus &bus,
		              Ram      &ram,
		              unsigned queue_size = 8);

		Genode::Mutex & mutex() { return _mutex; }
};

#endif /* _VIRTIO_DEVICE_H_ */
