/*
 * \brief  Gallium EGL driver for Genode
 * \author Norman Feske
 * \date   2010-07-05
 */

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

/* Genode includes */
#include <base/printf.h>
#include <base/snprintf.h>
#include <base/env.h>
#include <util/misc_math.h>  /* for 'max' */
#include <framebuffer_session/connection.h>
#include <timer_session/connection.h>
#include <blit/blit.h>

/* libc includes */
#include <dlfcn.h> /* for 'dlopen', 'dlsym', and the like */
#include <fcntl.h> /* for 'open' */

/* includes from 'egl/main' */
extern "C" {
#include <egllog.h>
}

/* includes from 'gallium/state_trackers/egl' */
extern "C" {
#include <common/native.h>
#include <common/native_modeset.h>
}

/* Gallium includes */
extern "C" {
#include <auxiliary/util/u_simple_screen.h> /* for 'struct pipe_winsys' */
#include <auxiliary/util/u_inlines.h>       /* for 'pipe_reference_init' */
#include <auxiliary/util/u_memory.h>        /* for 'align_malloc' */
#include <auxiliary/util/u_format.h>        /* for 'util_format_get_nblocksy' */
#include <auxiliary/util/u_math.h>          /* for 'align' */
#include <drivers/softpipe/sp_winsys.h>     /* for 'softpipe_create_screen' */
#include <state_tracker/drm_api.h>          /* for 'drm_api_create' */
}

/* local includes */
#include "select_driver.h"


static bool do_clflush = true;


/********************************
 ** Genode framebuffer backend **
 ********************************/

class Genode_framebuffer
{
	private:

		Framebuffer::Connection            _framebuffer;
		Framebuffer::Mode            const _mode;
		Genode::Dataspace_capability const _ds_cap;
		void                       * const _local_addr;

	public:

		Genode_framebuffer()
		:
			_mode(_framebuffer.mode()),
			_ds_cap(_framebuffer.dataspace()),
			_local_addr(Genode::env()->rm_session()->attach(_ds_cap))
		{ }

		~Genode_framebuffer()
		{
			Genode::env()->rm_session()->detach(_local_addr);
		}

		void *local_addr() const { return _local_addr; }

		void flush()
		{
			_framebuffer.refresh(0, 0, _mode.width(), _mode.height());
		}

		int width()  const { return _mode.width();  }
		int height() const { return _mode.height(); }
};


static Genode_framebuffer *genode_framebuffer()
{
	static Genode_framebuffer genode_framebuffer_inst;
	return &genode_framebuffer_inst;
}


/************
 ** Winsys **
 ************/

class Pipe_buffer : public pipe_buffer
{
	void *_data;

	public:

		/**
		 * Constructor
		 */
		Pipe_buffer(unsigned alignment,
		            unsigned usage,
		            unsigned size)
		{
			pipe_reference_init(&reference, 1);
			pipe_buffer::alignment = alignment;
			pipe_buffer::usage     = usage;
			pipe_buffer::size      = size;

			/* align to 16-byte multiple for Cell */
			_data = align_malloc(size, Genode::max(alignment, 16U));
		}

		/**
		 * Destructor
		 */
		~Pipe_buffer() { free(_data); }

		void *data() const { return _data; }
};


class Winsys : public pipe_winsys
{
	/*
	 * For documentation of the winsys functions, refer to
	 * 'auxiliary/util/u_simple_screen.h'.
	 */

	static void
	_destroy(struct pipe_winsys *ws)
	{
		Genode::destroy(Genode::env()->heap(), static_cast<Winsys *>(ws));
	}

	static const char *
	_get_name(struct pipe_winsys *ws)
	{
		return "Genode-winsys";
	}

	static void
	_update_buffer(struct pipe_winsys *ws,
	               void *context_private)
	{
		PDBG("not implemented");
	}

	static void
	_flush_frontbuffer(struct pipe_winsys *ws,
	                   struct pipe_surface *surf,
	                   void *context_private)
	{
		PDBG("not implemented");
	}

	static struct pipe_buffer *
	_buffer_create(struct pipe_winsys *ws,
	               unsigned alignment,
	               unsigned usage,
	               unsigned size)
	{
		return new (Genode::env()->heap()) Pipe_buffer(alignment, usage, size);
	}

	static struct pipe_buffer *
	_user_buffer_create(struct pipe_winsys *ws,
	                    void *ptr,
	                    unsigned bytes)
	{
		PDBG("not implemented"); return 0;
	}

	/*
	 * Called when using the softpipe driver
	 */
	static struct pipe_buffer *
	_surface_buffer_create(struct pipe_winsys *winsys,
	                       unsigned width, unsigned height,
	                       enum pipe_format format,
	                       unsigned usage,
	                       unsigned tex_usage,
	                       unsigned *stride)
	{
		Genode::printf("Winsys::_surface_buffer_create: format=%d, stride=%d, usage=%d, tex_usage=%x)\n",
		               format, *stride, usage, tex_usage);

		unsigned nblocksy = util_format_get_nblocksy(format, height);

		enum { ALIGNMENT = 64 };
		*stride = align(util_format_get_stride(format, width), ALIGNMENT);

		return new (Genode::env()->heap()) Pipe_buffer(ALIGNMENT, usage, *stride * nblocksy);
	}

	static void *
	_buffer_map(struct pipe_winsys *ws,
	            struct pipe_buffer *buf,
	            unsigned usage)
	{
		return buf ? static_cast<Pipe_buffer *>(buf)->data() : 0;
	}

	static void
	_buffer_unmap(struct pipe_winsys *ws,
	              struct pipe_buffer *buf)
	{
	}

	static void
	_buffer_destroy(struct pipe_buffer *buf)
	{
		Genode::destroy(Genode::env()->heap(), static_cast<Pipe_buffer *>(buf));
	}

	static void
	_fence_reference(struct pipe_winsys *ws,
	                 struct pipe_fence_handle **ptr,
	                 struct pipe_fence_handle *fence)
	{
		PDBG("not implemented");
	}

	static int
	_fence_signalled(struct pipe_winsys *ws,
	                 struct pipe_fence_handle *fence,
	                 unsigned flag )
	{
		PDBG("not implemented"); return 0;
	}

	static int
	_fence_finish(struct pipe_winsys *ws,
	              struct pipe_fence_handle *fence,
	              unsigned flag )
	{
		PDBG("not implemented"); return 0;
	}

	public:

		/**
		 * Constructor
		 */
		Winsys()
		{
			/* initialize members of 'struct pipe_winsys' */
			destroy               = _destroy;
			get_name              = _get_name;
			update_buffer         = _update_buffer;
			flush_frontbuffer     = _flush_frontbuffer;
			buffer_create         = _buffer_create;
			user_buffer_create    = _user_buffer_create;
			surface_buffer_create = _surface_buffer_create;
			buffer_map            = _buffer_map;
			buffer_unmap          = _buffer_unmap;
			buffer_destroy        = _buffer_destroy;
			fence_reference       = _fence_reference;
			fence_signalled       = _fence_signalled;
			fence_finish          = _fence_finish;
		}
};


/**************************
 ** EGL driver functions **
 **************************/

using namespace Genode;

/*
 * For documentation of the 'native_surface' and 'native_display' functions,
 * refer to 'gallium/state_trackers/egl/common/native.h'.
 * For documentation of the 'native_display_modeset' functions, refer to
 * 'gallium/state_trackers/egl/common/native_modeset.h'.
 */
class Display;

class Surface : public native_surface
{
	public:

		enum Surface_type { TYPE_SCANOUT };

	private:

		enum pipe_format     _color_format;
		native_display      *_display;
		Surface_type         _type;
		const native_config *_config;
		int                  _width, _height;
		struct pipe_texture *_textures[NUM_NATIVE_ATTACHMENTS];
		unsigned int         _sequence_number;

		static void
		_destroy(struct native_surface *nsurf)
		{
			Genode::destroy(Genode::env()->heap(), static_cast<Surface *>(nsurf));
		}

		static boolean
		_swap_buffers(struct native_surface *nsurf);

		static boolean
		_flush_frontbuffer(struct native_surface *nsurf)
		{
			PDBG("not implemented"); return 0;
		}

		static boolean
		_validate(struct native_surface *nsurf, uint attachment_mask,
		          unsigned int *seq_num, struct pipe_texture **textures,
		          int *width, int *height)
		{
			Surface *this_surface = static_cast<Surface *>(nsurf);

			struct pipe_texture templ;
			if (attachment_mask) {
				::memset(&templ, 0, sizeof(templ));
				templ.target = PIPE_TEXTURE_2D;
				templ.last_level = 0;
				templ.width0 = this_surface->_width;
				templ.height0 = this_surface->_height;
				templ.depth0 = 1;
				templ.format = this_surface->_color_format;
				templ.tex_usage = PIPE_TEXTURE_USAGE_RENDER_TARGET;
				if (this_surface->_type == TYPE_SCANOUT)
					templ.tex_usage |= PIPE_TEXTURE_USAGE_PRIMARY;
			}

			/* create textures */
			for (int i = 0; i < NUM_NATIVE_ATTACHMENTS; i++) {

				/* delay the allocation */
				if (!native_attachment_mask_test(attachment_mask, (native_attachment)i))
					continue;

				struct pipe_texture *ptex = this_surface->_textures[i];
				if (!ptex) {

					struct pipe_screen *screen = this_surface->_display->screen;
					/*
					 * The softpipe driver implements 'texture_create' by
					 * calling 'surface_buffer_create' if the
					 * 'PIPE_TEXTURE_USAGE_PRIMARY' bit in tex_usage is set (see
					 * 'softpipe_texture_create').
					 * The call then ends up in 'Winsys::_surface_buffer_create'.
					 *
					 * The i915 driver, however, translates 'PIPE_TEXTURE_USAGE_PRIMARY'
					 * to an 'INTEL_NEW_SCANOUT' argument passed to 'buffer_create'
					 * (ending up in 'intel_drm_buffer_create'.
					 */
					ptex = screen->texture_create(screen, &templ);
					this_surface->_textures[i] = ptex;
				}

				if (textures) {
					textures[i] = 0;
					pipe_texture_reference(&textures[i], ptex);
				}
			}

			if (seq_num) *seq_num = this_surface->_sequence_number;
			if (width)   *width   = this_surface->_width;
			if (height)  *height  = this_surface->_height;

			return TRUE;
		}

		static void
		_wait(struct native_surface *nsurf)
		{
			PDBG("not implemented");
		}

	public:

		Surface(native_display *display, Surface_type t, const native_config *config,
		        int width, int height)
		:
			_color_format(config->color_format), _display(display), _type(t),
			_config(config), _width(width), _height(height), _sequence_number(0)
		{
			for (int i = 0; i < NUM_NATIVE_ATTACHMENTS; i++)
				_textures[i] = 0;

			/* setup members of 'struct native_surface' */
			destroy           = _destroy;
			swap_buffers      = _swap_buffers;
			flush_frontbuffer = _flush_frontbuffer;
			validate          = _validate;
			wait              = _wait;
		}

		/**
		 * Return texture used as backing store for the surface
		 */
		pipe_texture *texture()
		{
			for (int i = 0; i < NUM_NATIVE_ATTACHMENTS; i++)
				if (_textures[i])
					return _textures[i];
			return 0;
		}

		int width()  const { return _width; }
		int height() const { return _height; }
};


class Display : public native_display
{
	drm_api *_api;
	Winsys _winsys;

	enum { NUM_MODES = 1 };
	struct native_mode _mode;
	const struct native_mode *_mode_list[NUM_MODES];

	native_display_modeset _modeset;

	/*
	 * We only support one configuration.
	 */
	struct native_config _native_config;

	boolean
	_is_format_supported(enum pipe_format fmt, boolean is_color)
	{
		return screen->is_format_supported(screen,
			fmt, PIPE_TEXTURE_2D,
			(is_color) ? PIPE_TEXTURE_USAGE_RENDER_TARGET :
			PIPE_TEXTURE_USAGE_DEPTH_STENCIL, 0);
	}

	/***********************
	 ** Modeset functions **
	 ***********************/

	static const struct native_connector **
	_get_connectors(struct native_display *ndpy,
	                int *num_connectors, int *num_crtcs)
	{
		static struct native_connector conn;
		static const struct native_connector *conn_list[1] = { &conn };
		printf("called, return 1 connector\n");

		if (num_connectors) *num_connectors = 1;
		if (num_crtcs)      *num_crtcs = 1;
		return conn_list;
	}

	static const struct native_mode **
	_get_modes(struct native_display *ndpy,
	           const struct native_connector *nconn,
	           int *num_modes)
	{
		*num_modes = 1;
		return static_cast<Display *>(ndpy)->_mode_list;
	}

	static struct native_surface *
	_create_scanout_surface(struct native_display *ndpy,
	                        const struct native_config *nconf,
	                        uint width, uint height)
	{
		return new (Genode::env()->heap())
			Surface(ndpy,
			        Surface::TYPE_SCANOUT, nconf,
			        width, height);
	}

	static boolean
	_program(struct native_display *ndpy, int crtc_idx,
	         struct native_surface *nsurf, uint x, uint y,
	         const struct native_connector **nconns, int num_nconns,
	         const struct native_mode *nmode)
	{
		return TRUE;
	}


	/***********************
	 ** Display functions **
	 ***********************/

	static void
	_destroy(struct native_display *ndpy)
	{
		Genode::destroy(Genode::env()->heap(), static_cast<Display *>(ndpy));
	}

	static int
	_get_param(struct native_display *ndpy, enum native_param_type param)
	{
		switch (param) {
		case NATIVE_PARAM_USE_NATIVE_BUFFER:
			return TRUE;
			break;
		default:
			return FALSE;
			break;
		}
	}

	static const struct native_config **
	_get_configs(struct native_display *ndpy, int *num_configs)
	{
		Display *display = static_cast<Display *>(ndpy);

		enum { NUM_CONFIGS = 1 };
		const struct native_config **configs =
			(const struct native_config **)calloc(1, sizeof(struct native_config *));
		configs[0] = &display->_native_config;

		struct native_config *config = &display->_native_config;
		config->mode.drawableType    = GLX_PBUFFER_BIT;

		int r = 5, g = 6, b = 5, a = 0;
		config->mode.swapMethod       = GLX_SWAP_EXCHANGE_OML;
		config->mode.visualID         = 0;
		config->mode.visualType       = EGL_NONE;
		config->mode.renderType       = GLX_RGBA_BIT;
		config->mode.rgbMode          = TRUE;
		config->mode.xRenderable      = FALSE;
		config->mode.maxPbufferWidth  = 4096;
		config->mode.maxPbufferHeight = 4096;
		config->mode.maxPbufferPixels = 4096*4096/256+3;
		config->mode.doubleBufferMode = TRUE;
		config->mode.rgbBits          = r + g + b + a;
		config->mode.redBits          = r;
		config->mode.greenBits        = g;
		config->mode.blueBits         = b;
		config->mode.alphaBits        = a;

		config->depth_format   = PIPE_FORMAT_NONE;
		config->stencil_format = PIPE_FORMAT_NONE;

		enum pipe_format format = PIPE_FORMAT_Z24S8_UNORM;
		if (!display->_is_format_supported(format, FALSE)) {
			format = PIPE_FORMAT_S8Z24_UNORM;
			if (!display->_is_format_supported(format, FALSE))
				format = PIPE_FORMAT_NONE;
		}

		if (format != PIPE_FORMAT_NONE) {
			printf("support depth and stencil buffer\n");
			config->depth_format           = format;
			config->stencil_format         = format;
			config->mode.depthBits         = 24;
			config->mode.stencilBits       = 8;
			config->mode.haveDepthBuffer   = TRUE;
			config->mode.haveStencilBuffer = TRUE;
		}

		config->color_format = PIPE_FORMAT_B8G8R8A8_UNORM;
		config->color_format = PIPE_FORMAT_B5G6R5_UNORM;

		config->scanout_bit = TRUE;

		printf("returning 1 config at %p\n", config);

		*num_configs = NUM_CONFIGS;
		return configs;
	}

	static boolean
	_is_pixmap_supported(struct native_display *ndpy,
	                     EGLNativePixmapType pix,
	                     const struct native_config *nconf)
	{
		PDBG("not implemented"); return 0;
	}

	static struct native_surface *
	_create_window_surface(struct native_display *ndpy,
	                       EGLNativeWindowType win,
	                       const struct native_config *nconf)
	{
		PDBG("not implemented"); return 0;
	}

	static struct native_surface *
	_create_pixmap_surface(struct native_display *ndpy,
	                       EGLNativePixmapType pix,
	                       const struct native_config *nconf)
	{
		PDBG("not implemented"); return 0;
	}

	static struct native_surface *
	_create_pbuffer_surface(struct native_display *ndpy,
	                        const struct native_config *nconf,
	                        uint width, uint height)
	{
		PDBG("not implemented"); return 0;
	}

	public:

		/**
		 * Constructor
		 */
		Display(drm_api *api) : _api(api)
		{
			::memset(&_mode,          0, sizeof(_mode));
			::memset(&_modeset,       0, sizeof(_modeset));
			::memset(&_native_config, 0, sizeof(_native_config));

			/* setup mode list */
			_mode.desc         = "Mode-genode";
			_mode.width        = genode_framebuffer()->width();
			_mode.height       = genode_framebuffer()->height();
			_mode.refresh_rate = 100;
			_mode_list[0]      = &_mode;

			/* setup members of 'struct native_display_modeset' */
			_modeset.get_connectors         = _get_connectors;
			_modeset.get_modes              = _get_modes;
			_modeset.create_scanout_surface = _create_scanout_surface;
			_modeset.program                = _program;

			/* setup members of 'struct native_display' */
			if (api) {
				struct drm_create_screen_arg arg;
				::memset(&arg, 0, sizeof(arg));
				arg.mode = DRM_CREATE_NORMAL;
				int drm_fd = open("/dev/drm", O_RDWR);
				screen = api->create_screen(api, drm_fd, &arg);
			} else {
				screen = softpipe_create_screen(&_winsys);
			}

			PWRN("returned from init display->screen");

			destroy                = _destroy;
			get_param              = _get_param;
			get_configs            = _get_configs;
			is_pixmap_supported    = _is_pixmap_supported;
			create_window_surface  = _create_window_surface;
			create_pixmap_surface  = _create_pixmap_surface;
			create_pbuffer_surface = _create_pbuffer_surface;
			modeset                = &_modeset;
		}
};


boolean Surface::_swap_buffers(struct native_surface *nsurf)
{
	Surface *this_surface = static_cast<Surface *>(nsurf);
	Display *display = static_cast<Display *>(this_surface->_display);
	pipe_screen *screen = display->screen;
	pipe_texture *texture = this_surface->texture();
	pipe_transfer *transfer = 0;

	static Timer::Connection timer;

	timer.msleep(5);

	if (!texture) {
		PERR("surface has no texture");
		return FALSE;
	}

	enum { FACE = 0, LEVEL = 0, ZSLICE = 0 };
	transfer = screen->get_tex_transfer(screen, texture, FACE, LEVEL, ZSLICE,
	                                    PIPE_TRANSFER_READ, 0, 0,
	                                    this_surface->width(),
	                                    this_surface->height());

	if (!transfer) {
		PERR("could not create transfer object");
		return FALSE;
	}

	void *data = screen->transfer_map(screen, transfer);
	if (!data) {
		PERR("transfer failed");
		screen->tex_transfer_destroy(transfer);
		return FALSE;
	}

#ifdef __i386__
	/* flush cache */
	if (do_clflush) {
		volatile char *virt_addr = (volatile char *)data;
		long num_bytes = transfer->stride*transfer->height;
		asm volatile ("": : :"memory");
		enum { CACHE_LINE_SIZE = 16 /* hard-coded for now */ };
		for (long i = 0; i < num_bytes; i += CACHE_LINE_SIZE, virt_addr += CACHE_LINE_SIZE)
			asm volatile("clflush %0" : "+m" (*((volatile char *)virt_addr)));
		asm volatile ("": : :"memory");
	}
#endif

	blit(data, transfer->stride, genode_framebuffer()->local_addr(),
	     transfer->stride, transfer->stride, transfer->height);

	screen->transfer_unmap(screen, transfer);
	screen->tex_transfer_destroy(transfer);

	this_surface->_sequence_number++;

	genode_framebuffer()->flush();
	return TRUE;
}


extern "C" const char *native_get_name(void)
{
	/*
	 * Among the 'native_' functions, this one is called first - a good
	 * opportunity to define the 'eglLog' debug level. For maximum verbosity,
	 * use '_EGL_DEBUG'.
	 */
	_eglSetLogLevel(_EGL_DEBUG);
	return "Genode-EGL";
}


extern "C" struct native_probe *
native_create_probe(EGLNativeDisplayType dpy)
{
	PDBG("not yet implemented dpy=%d", dpy);
	return 0;
}


extern "C" enum native_probe_result
native_get_probe_result(struct native_probe *nprobe)
{
	PDBG("not yet implemented");
	return NATIVE_PROBE_UNKNOWN;
}


extern "C" struct native_display *
native_create_display(EGLNativeDisplayType dpy,
                      struct native_event_handler *event_handler)
{
	/*
	 * Request API by dynamically loading the driver module. Each driver
	 * module has an entry-point function called 'drm_api_create'. For the
	 * i915 driver, this function resides in
	 * 'gallium/winsys/drm/intel/gem/intel_drm_api.c'.
	 */
	drm_api *api = 0;

	const char *driver_filename = probe_gpu_and_select_driver();
	void *driver_so_handle = driver_filename ? dlopen(driver_filename, 0) : 0;
	if (driver_so_handle) {

		/* query entry point into driver module */
		drm_api *(*drm_api_create) (void) = 0;
		drm_api_create = (drm_api *(*)(void))dlsym(driver_so_handle, "drm_api_create");
		if (drm_api_create)
			api = drm_api_create();
		else
			PWRN("could not obtain symbol \"drm_api_create\" in driver \"%s\"",
			     driver_filename);
	}

	if (!api) {
		PWRN("falling back to softpipe driver");

		/*
		 * Performing clflush is not needed when using software rendering.
		 * Furthermore, on qemu with the default cpu, 'cflush' is an illegal
		 * instruction.
		 */
		do_clflush = false;
	}

	return new (env()->heap()) Display(api);
}
