/*
 * \brief  OS specific backend for ACPICA library
 * \author Alexander Boettcher
 * \date   2016-11-14
 */

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

#include <base/log.h>
#include <base/sleep.h>
#include <util/misc_math.h>

#include <io_port_session/connection.h>
#include <timer_session/connection.h>

#include <acpica/acpica.h>

#include "env.h"

extern "C" {
#include "acpi.h"
#include "acpiosxf.h"
}


#define FAIL(retval) \
	{ \
		Genode::error(__func__, ":", __LINE__, " called - dead"); \
		while (1) Genode::sleep_forever(); \
		return retval; \
	}

ACPI_STATUS AcpiOsPredefinedOverride (const ACPI_PREDEFINED_NAMES *pre,
                                      ACPI_STRING *newobj)
{
	*newobj = nullptr;
	return AE_OK;
}


void * AcpiOsAllocate (ACPI_SIZE size) { return Acpica::heap().alloc(size); }


void AcpiOsFree (void *ptr)
{
	if (Acpica::heap().need_size_for_free())
		Genode::warning(__func__, " called - warning - ptr=", ptr);

	Acpica::heap().free(ptr, 0);
}

ACPI_STATUS AcpiOsCreateLock (ACPI_SPINLOCK *spin_lock)
{
	*spin_lock = new (Acpica::heap()) Genode::Mutex();
	return AE_OK;
}

ACPI_CPU_FLAGS AcpiOsAcquireLock (ACPI_SPINLOCK h)
{
	Genode::Mutex *mutex = reinterpret_cast<Genode::Mutex *>(h);
	mutex->acquire();

	return AE_OK;
}

void AcpiOsReleaseLock (ACPI_SPINLOCK h, ACPI_CPU_FLAGS flags)
{
	Genode::Mutex *mutex = reinterpret_cast<Genode::Mutex *>(h);

	if (flags != AE_OK)
		Genode::warning("warning - unknown flags in ", __func__);

	mutex->release();

	return;
}

ACPI_STATUS AcpiOsCreateSemaphore (UINT32 max, UINT32 initial,
                                   ACPI_SEMAPHORE *sem)
{
	*sem = new (Acpica::heap()) Genode::Semaphore(initial);
	return AE_OK;
}

ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE h, UINT32 units,
                                UINT16 timeout_ms)
{
	Genode::Semaphore *sem = reinterpret_cast<Genode::Semaphore *>(h);

	if (!units)
		FAIL(AE_BAD_PARAMETER)

	/**
	 * Timeouts not supported yet ...
	 * == 0     means - try and don't block - we're single threaded - ignore
	 * == 0xfff means - wait endless - fine
	 */
	if (0 < timeout_ms && timeout_ms < 0xffff)
		FAIL(AE_BAD_PARAMETER)

	/* timeout == forever case */
	while (units) {
		sem->down();
		units--;
	}

	return AE_OK;
}

ACPI_STATUS AcpiOsSignalSemaphore (ACPI_SEMAPHORE h, UINT32 units)
{
	Genode::Semaphore *sem = reinterpret_cast<Genode::Semaphore *>(h);

	while (units) {
		sem->up();
		units--;
	}

	return AE_OK;
}

ACPI_STATUS AcpiOsDeleteSemaphore (ACPI_SEMAPHORE h)
{
	Genode::Semaphore *sem = reinterpret_cast<Genode::Semaphore *>(h);

	if (!sem)
		return AE_BAD_PARAMETER;

	Genode::destroy(Acpica::heap(), sem);
	return AE_OK;
}

ACPI_THREAD_ID AcpiOsGetThreadId (void) {
	return reinterpret_cast<Genode::addr_t>(Genode::Thread::myself()); }

ACPI_STATUS AcpiOsTableOverride (ACPI_TABLE_HEADER *x, ACPI_TABLE_HEADER **y)
{
	*y = nullptr;
	return AE_OK;
}

ACPI_STATUS AcpiOsPhysicalTableOverride (ACPI_TABLE_HEADER *x,
                                         ACPI_PHYSICAL_ADDRESS *y, UINT32 *z)
{
	*y = 0;
	return AE_OK;
}

ACPI_STATUS AcpiOsReadPort (ACPI_IO_ADDRESS port, UINT32 *value, UINT32 width)
{
	if (width % 8 != 0)
		FAIL(AE_BAD_PARAMETER)

	/* the I/O port may be owned by drivers, which will cause exceptions */
	try {
		unsigned const bytes = width / 8;
		Genode::Io_port_connection io_port(Acpica::env(), port, bytes);

		switch (bytes) {
		case 1 :
			*value = io_port.inb(port);
			break;
		case 2 :
			*value = io_port.inw(port);
			break;
		case 4 :
			*value = io_port.inl(port);
			break;
		default:
			FAIL(AE_BAD_PARAMETER)
		}
	}
	catch (Genode::Service_denied) { return AE_BAD_PARAMETER; }

	return AE_OK;
}

ACPI_STATUS AcpiOsWritePort (ACPI_IO_ADDRESS port, UINT32 value, UINT32 width)
{
	if (width % 8 != 0)
		FAIL(AE_BAD_PARAMETER)

	/* the I/O port may be owned by drivers, which will cause exceptions */
	try {
		unsigned const bytes = width / 8;
		Genode::Io_port_connection io_port(Acpica::env(), port, bytes);

		switch (bytes) {
		case 1 :
			io_port.outb(port, value);
			break;
		case 2 :
			io_port.outw(port, value);
			break;
		case 4 :
			io_port.outl(port, value);
			break;
		default:
			FAIL(AE_BAD_PARAMETER)
		}
	}
	catch (Genode::Service_denied) { return AE_BAD_PARAMETER; }

	return AE_OK;
}

static struct
{
	ACPI_EXECUTE_TYPE type;
	ACPI_OSD_EXEC_CALLBACK func;
	void *context;
} deferred[8];

ACPI_STATUS AcpiOsExecute(ACPI_EXECUTE_TYPE type, ACPI_OSD_EXEC_CALLBACK func,
                         void *context)
{
	if (type == OSL_GPE_HANDLER) {
		func(context);
		return AE_OK;
	}

	if (type != OSL_NOTIFY_HANDLER)
		FAIL(AE_BAD_PARAMETER)

	for (unsigned i = 0; i < sizeof(deferred) / sizeof (deferred[0]); i++) {
		if (deferred[i].func)
			continue;

		deferred[i].type = type;
		deferred[i].func = func;
		deferred[i].context = context;
		return AE_OK;
	}
	Genode::error("queue full for deferred handlers");
	return AE_BAD_PARAMETER;
}

void AcpiOsWaitEventsComplete()
{
	for (unsigned i = 0; i < sizeof(deferred) / sizeof (deferred[0]); i++) {
		if (!deferred[i].func)
			continue;

		ACPI_OSD_EXEC_CALLBACK func = deferred[i].func;
		deferred[i].func = nullptr;

		func(deferred[i].context);
	}
}

static Timer::Connection &timer_connection()
{
	static Timer::Connection connection(Acpica::env());
	return connection;
}

void AcpiOsSleep (UINT64 sleep_ms)
{
	Genode::log(__func__, " ", sleep_ms, " ms");

	timer_connection().msleep(sleep_ms);
}

void AcpiOsStall (UINT32 stall_us)
{
	Genode::log(__func__, " ", stall_us, " us");

	timer_connection().usleep(stall_us);
}

ACPI_STATUS AcpiOsEnterSleep (UINT8, UINT32, UINT32) {
	/* unused notification hook for OS */
	return (AE_OK); }

/********************************
 * unsupported/unused functions *
 ********************************/

ACPI_STATUS AcpiOsSignal (UINT32, void *)
	FAIL(AE_BAD_PARAMETER)

UINT64 AcpiOsGetTimer (void)
{
	return timer_connection().elapsed_us() * 10;
}

ACPI_STATUS AcpiOsReadMemory (ACPI_PHYSICAL_ADDRESS, UINT64 *, UINT32)
	FAIL(AE_BAD_PARAMETER)

ACPI_STATUS AcpiOsWriteMemory (ACPI_PHYSICAL_ADDRESS, UINT64, UINT32)
	FAIL(AE_BAD_PARAMETER)

ACPI_STATUS AcpiOsRemoveInterruptHandler (UINT32, ACPI_OSD_HANDLER)
	FAIL(AE_BAD_PARAMETER)

ACPI_STATUS AcpiOsGetLine (char *, UINT32, UINT32 *)
	FAIL(AE_BAD_PARAMETER)

ACPI_STATUS
AcpiOsWaitCommandReady (
    void)
{
	FAIL(AE_BAD_PARAMETER)
}

ACPI_STATUS
AcpiOsNotifyCommandComplete (
    void)
{
	FAIL(AE_BAD_PARAMETER)
}


extern "C"
{
void AcpiAhMatchUuid() FAIL()
void AcpiAhMatchHardwareId() FAIL()
void AcpiDbCommandDispatch() FAIL()
void AcpiDbSetOutputDestination() FAIL()
void MpSaveSerialInfo() FAIL()
void MpSaveGpioInfo() FAIL()
}
