/*
 * osmlxf.c (Monolithium ACPICA OSL)
 *
 * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <common.h>
#include <log.h>
#include <heap.h>
#include <memory.h>
#include <thread.h>
#include <cpu.h>
#include <timer.h>
#include <interrupt.h>
#include <acpi.h>
#include <stdio.h>
#include <power.h>
#include <semaphore.h>

const char driver_name[] = "acpica";

static ACPI_OSD_HANDLER acpica_int_handlers[256] = { NULL };
static void *acpica_int_contexts[256] = { NULL };

static void acpica_osl_handler(registers_t *regs, byte_t int_num)
{
    acpica_int_handlers[int_num](acpica_int_contexts[int_num]);
}

ACPI_STATUS AcpiOsInitialize(void)
{
    return AE_OK;
}

ACPI_STATUS AcpiOsTerminate(void)
{
    return AE_OK;
}

ACPI_PHYSICAL_ADDRESS AcpiOsGetRootPointer(void)
{
    ACPI_PHYSICAL_ADDRESS Address;
    AcpiFindRootPointer(&Address);
    return Address;
}

ACPI_STATUS AcpiOsPredefinedOverride(const ACPI_PREDEFINED_NAMES *PredefinedObject, ACPI_STRING *NewValue)
{
    *NewValue = NULL;
    return AE_OK;
}

ACPI_STATUS AcpiOsTableOverride(ACPI_TABLE_HEADER *ExistingTable, ACPI_TABLE_HEADER **NewTable)
{
    *NewTable = NULL;
    return AE_OK;
}

ACPI_STATUS AcpiOsPhysicalTableOverride(ACPI_TABLE_HEADER *ExistingTable, ACPI_PHYSICAL_ADDRESS *NewAddress, UINT32 *NewTableLength)
{
    *NewAddress = 0;
    return AE_OK;
}

ACPI_STATUS AcpiOsSignal(UINT32 Function, void *Info)
{
    return AE_SUPPORT; // TODO
}

void AcpiOsVprintf(const char *Format, va_list Args)
{
    char buffer[MAX_LOG_MESSAGE_SIZE];
    vsnprintf(buffer, MAX_LOG_MESSAGE_SIZE, Format, Args);
    log_write(LOG_NORMAL, buffer);
}

void AcpiOsPrintf(const char *Format, ...)
{
    va_list args;
    va_start(args, Format);
    AcpiOsVprintf(Format, args);
    va_end(args);
}

ACPI_STATUS AcpiOsReadPort(ACPI_IO_ADDRESS Address, UINT32 *Value, UINT32 Width)
{
    switch (Width)
    {
    case 8:
        *Value = cpu_read_port_byte(Address);
        break;
    case 16:
        *Value = cpu_read_port_word(Address);
        break;
    case 32:
        *Value = cpu_read_port_dword(Address);
        break;
    default:
        return AE_SUPPORT;
    }

    return AE_OK;
}

ACPI_STATUS AcpiOsWritePort(ACPI_IO_ADDRESS Address, UINT32 Value, UINT32 Width)
{
    switch (Width)
    {
    case 8:
        cpu_write_port_byte(Address, Value);
        break;
    case 16:
        cpu_write_port_word(Address, Value);
        break;
    case 32:
        cpu_write_port_dword(Address, Value);
        break;
    default:
        return AE_SUPPORT;
    }

    return AE_OK;
}

void *AcpiOsAllocate(ACPI_SIZE Size)
{
    return malloc(Size);
}

void AcpiOsFree(void *Memory)
{
    free(Memory);
}

ACPI_STATUS AcpiOsReadMemory(ACPI_PHYSICAL_ADDRESS Address, UINT64 *Value, UINT32 Width)
{
    *Value = 0;
    dword_t ret = read_physical((void*)((uintptr_t)Address), Value, Width / 8);

    if (ret == ERR_SUCCESS) return AE_OK;
    if (ret == ERR_NOMEMORY) return AE_NO_MEMORY;
    return AE_ERROR;
}

ACPI_STATUS AcpiOsWriteMemory(ACPI_PHYSICAL_ADDRESS Address, UINT64 Value, UINT32 Width)
{
    dword_t ret = write_physical((void*)((uintptr_t)Address), &Value, Width / 8);

    if (ret == ERR_SUCCESS) return AE_OK;
    if (ret == ERR_NOMEMORY) return AE_NO_MEMORY;
    return AE_ERROR;
}

void *AcpiOsMapMemory(ACPI_PHYSICAL_ADDRESS Address, ACPI_SIZE Length)
{
    void *virtual = NULL;

    if (map_memory((void*)PAGE_ALIGN((uintptr_t)Address),
                   &virtual,
                   PAGE_ALIGN_UP(PAGE_OFFSET((uintptr_t)Address) + Length),
                   MEMORY_BLOCK_ACCESSIBLE | MEMORY_BLOCK_WRITABLE) == ERR_SUCCESS)
    {
        return (void*)((uintptr_t)virtual + PAGE_OFFSET((uintptr_t)Address));
    }
    else
    {
        return NULL;
    }
}

void AcpiOsUnmapMemory(void *Address, ACPI_SIZE Length)
{
    UNUSED_PARAMETER(Length);
    unmap_memory(Address);
}

ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK *OutHandle)
{
    *OutHandle = malloc(sizeof(critical_t));
    if (*OutHandle == NULL) return AE_NO_MEMORY;
    return AE_OK;
}

void AcpiOsDeleteLock(ACPI_SPINLOCK Handle)
{
    free(Handle);
}

ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK Handle)
{
    enter_critical(Handle);
    return *(critical_t*)Handle;
}

void AcpiOsReleaseLock(ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags)
{
    UNUSED_PARAMETER(Flags);
    leave_critical(Handle);
}

ACPI_STATUS AcpiOsCreateSemaphore(UINT32 MaxUnits, UINT32 InitialUnits, ACPI_SEMAPHORE *OutHandle)
{
    semaphore_t *semaphore = malloc(sizeof(semaphore_t));
    if (semaphore == NULL) return AE_NO_MEMORY;

    init_semaphore(semaphore, InitialUnits, MaxUnits);
    *OutHandle = (ACPI_SEMAPHORE)semaphore;
    return AE_OK;
}

ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE Handle)
{
    free(Handle);
    return AE_OK;
}

ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units, UINT16 Timeout)
{
    dword_t ret = wait_semaphore((semaphore_t*)Handle, Units, (int32_t)((int16_t)Timeout));

    if (ret == ERR_SUCCESS) return AE_OK;
    else if (ret == ERR_TIMEOUT) return AE_TIME;
    else return AE_ERROR;
}

ACPI_STATUS AcpiOsSignalSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units)
{
    release_semaphore((semaphore_t*)Handle, Units);
    return AE_OK;
}

UINT64 AcpiOsGetTimer(void)
{
    return syscall_get_nanoseconds() / 100ULL;
}

void AcpiOsSleep(UINT64 Milliseconds)
{
    syscall_sleep(Milliseconds);
}

void AcpiOsStall(UINT32 Microseconds)
{
    qword_t end_time = syscall_get_nanoseconds() + (qword_t)Microseconds * 1000ULL;
    while (syscall_get_nanoseconds() < end_time) continue;
}

ACPI_THREAD_ID AcpiOsGetThreadId(void)
{
    return get_current_thread()->tid + 1;
}

ACPI_STATUS AcpiOsInstallInterruptHandler(UINT32 InterruptLevel, ACPI_OSD_HANDLER Handler, void *Context)
{
    if (InterruptLevel >= 256) return AE_BAD_PARAMETER;

    acpica_int_handlers[InterruptLevel] = Handler;
    acpica_int_contexts[InterruptLevel] = Context;
    return AE_OK;
}

ACPI_STATUS AcpiOsRemoveInterruptHandler(UINT32 InterruptLevel, ACPI_OSD_HANDLER Handler)
{
    acpica_int_handlers[InterruptLevel] = NULL;
    acpica_int_contexts[InterruptLevel] = NULL;
    return AE_OK;
}

ACPI_STATUS AcpiOsExecute(ACPI_EXECUTE_TYPE Type, ACPI_OSD_EXEC_CALLBACK Function, void *Context)
{
    thread_t *thread;
    dword_t ret = create_system_thread((thread_procedure_t)Function, 0, THREAD_PRIORITY_MID, 0, Context, &thread);

    if (ret == ERR_SUCCESS)
    {
        dereference(&thread->header);
        return AE_OK;
    }
    else if (ret == ERR_INVALID)
    {
        return AE_BAD_PARAMETER;
    }
    else
    {
        return AE_ERROR;
    }
}

void AcpiOsWaitEventsComplete(void)
{
    // TODO
}

ACPI_STATUS AcpiOsReadPciConfiguration(ACPI_PCI_ID *PciId, UINT32 Register, UINT64 *Value, UINT32 Width)
{
    return AE_SUPPORT; // TODO
}

ACPI_STATUS AcpiOsWritePciConfiguration(ACPI_PCI_ID *PciId, UINT32 Register, UINT64 Value, UINT32 Width)
{
     return AE_SUPPORT; // TODO
}

dword_t acpica_set_state(power_state_t state)
{
    switch (state)
    {
    case POWER_STATE_STANDBY:
    case POWER_STATE_SUSPEND:
        /* Not implemented */
        return ERR_NOSYSCALL;

    case POWER_STATE_OFF:
        AcpiEnterSleepStatePrep(ACPI_STATE_S5);
        cpu_disable_interrupts();
        AcpiEnterSleepState(ACPI_STATE_S5);
        break;

    default:
        return ERR_INVALID;
    }

    return ERR_SUCCESS;
}

dword_t driver_load(const char *parameters)
{
    static power_callbacks_t callbacks = {
        .set_state = acpica_set_state,
    };

    dword_t ret = register_power_callbacks(&callbacks);
    if (ret != ERR_SUCCESS) return ret;

    if (ACPI_FAILURE(AcpiInitializeSubsystem())
        || ACPI_FAILURE(AcpiInitializeTables(NULL, 16, FALSE))
        || ACPI_FAILURE(AcpiLoadTables())
        || ACPI_FAILURE(AcpiEnableSubsystem(ACPI_FULL_INITIALIZATION)))
    {
        register_power_callbacks(NULL);
        return ERR_HARDWARE;
    }

    return ERR_SUCCESS;
}
