root/src/add-ons/kernel/drivers/wmi/WMIACPI.cpp
/*
 * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT license.
 */


#define DRIVER_NAME "wmi_acpi"
#include "WMIPrivate.h"


#define ACPI_NAME_ACPI_WMI "PNP0C14"

#define ACPI_WMI_REGFLAG_EXPENSIVE      (1 << 0)
#define ACPI_WMI_REGFLAG_METHOD         (1 << 1)
#define ACPI_WMI_REGFLAG_STRING         (1 << 2)
#define ACPI_WMI_REGFLAG_EVENT          (1 << 3)


device_manager_info *gDeviceManager;
smbios_module_info *gSMBios;


acpi_status wmi_acpi_adr_space_handler(uint32 function,
        acpi_physical_address address, uint32 bitWidth, int *value,
        void *handlerContext, void *regionContext)
{
        return B_OK;
}


WMIACPI::WMIACPI(device_node *node)
        :
        fNode(node)
{
        CALLED();

        device_node *parent;
        parent = gDeviceManager->get_parent_node(node);
        gDeviceManager->get_driver(parent, (driver_module_info **)&acpi,
                (void **)&acpi_cookie);
        gDeviceManager->get_attr_string(parent, ACPI_DEVICE_UID_ITEM, &fUid,
                false);
        gDeviceManager->put_node(parent);

        // install notify handler
        fStatus = acpi->install_notify_handler(acpi_cookie,
                ACPI_ALL_NOTIFY, _NotifyHandler, this);
        if (fStatus != B_OK) {
                ERROR("install_notify_handler failed\n");
                return;
        }

        fStatus = acpi->install_address_space_handler(acpi_cookie,
                ACPI_ADR_SPACE_EC, wmi_acpi_adr_space_handler, NULL, this);
        if (fStatus != B_OK) {
                ERROR("wmi_acpi_adr_space_handler failed\n");
                return;
        }

        acpi_data buffer = {ACPI_ALLOCATE_BUFFER, NULL};
        fStatus = acpi->evaluate_method(acpi_cookie, "_WDG", NULL, &buffer);
        if (fStatus != B_OK) {
                ERROR("Method call _WDG failed\n");
                return;
        }

        acpi_object_type* object = (acpi_object_type*)buffer.pointer;
        fWMIInfoCount = object->buffer.length / sizeof(guid_info);
        guid_info *info = (guid_info*)object->buffer.buffer;
        fWMIInfos = (wmi_info *)calloc(fWMIInfoCount, sizeof(wmi_info));
        TRACE("found %" B_PRIu32 " objects\n", fWMIInfoCount);
        for (uint32 i = 0; i < fWMIInfoCount; i++, info++) {
                wmi_info *wmi = &fWMIInfos[i];
                wmi->guid = *info;
                fList.Add(wmi);
        }
        free(object);
}


WMIACPI::~WMIACPI()
{
        free(fWMIInfos);

        acpi->remove_notify_handler(acpi_cookie,
                ACPI_ALL_NOTIFY, _NotifyHandler);
}


status_t
WMIACPI::InitCheck()
{
        return fStatus;
}


status_t
WMIACPI::Scan()
{
        CALLED();
        status_t status;
        wmi_info* wmiInfo = NULL;
        uint32 index = 0;
        for (WMIInfoList::Iterator it = fList.GetIterator();
                        (wmiInfo = it.Next()) != NULL; index++) {
                uint8* guid = wmiInfo->guid.guid;
                char guidString[37] = {};
                _GuidToGuidString(guid, guidString);
                device_attr attrs[] = {
                        // connection
                        { WMI_GUID_STRING_ITEM, B_STRING_TYPE, { .string = guidString }},

                        { WMI_BUS_COOKIE, B_UINT32_TYPE, { .ui32 = index }},

                        // description of peripheral drivers
                        { B_DEVICE_BUS, B_STRING_TYPE, { .string = "wmi" }},

                        { B_DEVICE_FLAGS, B_UINT32_TYPE,
                                { .ui32 = B_FIND_MULTIPLE_CHILDREN }},

                        { NULL }
                };

                status = gDeviceManager->register_node(fNode, WMI_DEVICE_MODULE_NAME,
                        attrs, NULL, NULL);
                if (status != B_OK)
                        return status;
        }

        return B_OK;

}


status_t
WMIACPI::GetBlock(uint32 busCookie, uint8 instance, uint32 methodId,
        acpi_data* out)
{
        CALLED();
        if (busCookie >= fWMIInfoCount)
                return B_BAD_VALUE;
        wmi_info* info = &fWMIInfos[busCookie];
        if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) != 0
                || (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
                return B_BAD_VALUE;
        } else if (instance > info->guid.max_instance)
                return B_BAD_VALUE;

        char method[5] = "WQ";
        strncat(method, info->guid.oid, 2);
        char wcMethod[5] = "WC";
        strncat(wcMethod, info->guid.oid, 2);
        status_t wcStatus = B_OK;
        status_t status = B_OK;

        if ((info->guid.flags & ACPI_WMI_REGFLAG_EXPENSIVE) != 0)
                 wcStatus = _EvaluateMethodSimple(wcMethod, 1);

        acpi_object_type object;
        object.object_type = ACPI_TYPE_INTEGER;
        object.integer.integer = instance;
        acpi_objects objects = { 1, &object};
        TRACE("GetBlock calling %s\n", method);
        status = acpi->evaluate_method(acpi_cookie, method, &objects, out);

        if ((info->guid.flags & ACPI_WMI_REGFLAG_EXPENSIVE) != 0
                && wcStatus == B_OK) {
                 _EvaluateMethodSimple(wcMethod, 0);
        }

        return status;
}


status_t
WMIACPI::SetBlock(uint32 busCookie, uint8 instance, uint32 methodId,
        const acpi_data* in)
{
        CALLED();
        if (busCookie >= fWMIInfoCount)
                return B_BAD_VALUE;
        wmi_info* info = &fWMIInfos[busCookie];
        if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) != 0
                || (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
                return B_BAD_VALUE;
        } else if (instance > info->guid.max_instance)
                return B_BAD_VALUE;

        char method[5] = "WS";
        strncat(method, info->guid.oid, 2);

        acpi_object_type object[2];
        object[0].object_type = ACPI_TYPE_INTEGER;
        object[0].integer.integer = instance;
        object[1].object_type = ACPI_TYPE_BUFFER;
        if ((info->guid.flags & ACPI_WMI_REGFLAG_STRING) != 0)
                object[1].object_type = ACPI_TYPE_STRING;
        object[1].buffer.buffer = in->pointer;
        object[1].buffer.length = in->length;
        acpi_objects objects = { 2, object};
        TRACE("SetBlock calling %s\n", method);
        return acpi->evaluate_method(acpi_cookie, method, &objects, NULL);
}


status_t
WMIACPI::EvaluateMethod(uint32 busCookie, uint8 instance, uint32 methodId,
        const acpi_data* in, acpi_data* out)
{
        CALLED();
        if (busCookie >= fWMIInfoCount)
                return B_BAD_VALUE;
        wmi_info* info = &fWMIInfos[busCookie];
        if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) == 0)
                return B_BAD_VALUE;

        char method[5] = "WM";
        strncat(method, info->guid.oid, 2);

        acpi_object_type object[3];
        object[0].object_type = ACPI_TYPE_INTEGER;
        object[0].integer.integer = instance;
        object[1].object_type = ACPI_TYPE_INTEGER;
        object[1].integer.integer = methodId;
        uint32 count = 2;
        if (in != NULL) {
                object[2].object_type = ACPI_TYPE_BUFFER;
                if ((info->guid.flags & ACPI_WMI_REGFLAG_STRING) != 0)
                        object[2].object_type = ACPI_TYPE_STRING;
                object[2].buffer.buffer = in->pointer;
                object[2].buffer.length = in->length;
                count++;
        }
        acpi_objects objects = { count, object};
        TRACE("EvaluateMethod calling %s\n", method);
        return acpi->evaluate_method(acpi_cookie, method, &objects, out);
}


status_t
WMIACPI::InstallEventHandler(const char* guidString,
        acpi_notify_handler handler, void* context)
{
        CALLED();
        char string[37] = {};
        for (uint32 i = 0; i < fWMIInfoCount; i++) {
                wmi_info* info = &fWMIInfos[i];
                _GuidToGuidString(info->guid.guid, string);
                if (strcmp(guidString, string) == 0) {
                        status_t status = B_OK;
                        if (info->handler == NULL)
                                status = _SetEventGeneration(info, true);
                        if (status == B_OK) {
                                info->handler = handler;
                                info->handler_context = context;
                        }
                        return status;
                }
        }
        return B_ENTRY_NOT_FOUND;
}


status_t
WMIACPI::RemoveEventHandler(const char* guidString)
{
        CALLED();
        char string[37] = {};
        for (uint32 i = 0; i < fWMIInfoCount; i++) {
                wmi_info* info = &fWMIInfos[i];
                _GuidToGuidString(info->guid.guid, string);
                if (strcmp(guidString, string) == 0) {
                        status_t status = _SetEventGeneration(info, false);
                        info->handler = NULL;
                        info->handler_context = NULL;
                        return status;
                }
        }
        return B_ENTRY_NOT_FOUND;
}


status_t
WMIACPI::GetEventData(uint32 notify, acpi_data* out)
{
        CALLED();

        acpi_object_type object;
        object.object_type = ACPI_TYPE_INTEGER;
        object.integer.integer = notify;
        acpi_objects objects = { 1, &object };

        for (uint32 i = 0; i < fWMIInfoCount; i++) {
                wmi_info* info = &fWMIInfos[i];
                if (info->guid.notify_id == notify
                        && (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
                        return acpi->evaluate_method(acpi_cookie, "_WED", &objects, out);
                }
        }
        return B_ENTRY_NOT_FOUND;
}


const char*
WMIACPI::GetUid(uint32 busCookie)
{
        return fUid;
}


status_t
WMIACPI::_SetEventGeneration(wmi_info* info, bool enabled)
{
        char method[5];
        sprintf(method, "WE%02X", info->guid.notify_id);
        TRACE("_SetEventGeneration calling %s\n", method);
        status_t status = _EvaluateMethodSimple(method, enabled ? 1 : 0);
        // the method is allowed not to exist
        if (status == B_ERROR)
                status = B_OK;
        return status;
}


status_t
WMIACPI::_EvaluateMethodSimple(const char* method, uint64 integer)
{
        acpi_object_type object;
        object.object_type = ACPI_TYPE_INTEGER;
        object.integer.integer = integer;
        acpi_objects objects = { 1, &object};
        return acpi->evaluate_method(acpi_cookie, method, &objects, NULL);
}


void
WMIACPI::_NotifyHandler(acpi_handle device, uint32 value, void *context)
{
        WMIACPI* bus = (WMIACPI*)context;
        bus->_Notify(device, value);
}


void
WMIACPI::_Notify(acpi_handle device, uint32 value)
{
        for (uint32 i = 0; i < fWMIInfoCount; i++) {
                wmi_info* wmi = &fWMIInfos[i];
                if (wmi->guid.notify_id == value) {
                        TRACE("_Notify found event 0x%" B_PRIx32 "\n", value);
                        if (wmi->handler != NULL) {
                                TRACE("_Notify found handler for event 0x%" B_PRIx32 "\n",
                                        value);
                                wmi->handler(device, value, wmi->handler_context);
                        }
                        break;
                }
        }
}


void
WMIACPI::_GuidToGuidString(uint8 guid[16], char* guidString)
{
        sprintf(guidString,
                "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6],
                guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14],
                guid[15]);
}


//      #pragma mark - driver module API


static float
wmi_acpi_support(device_node *parent)
{
        CALLED();

        // make sure parent is really the ACPI bus manager
        const char *bus;
        if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
                return -1;

        if (strcmp(bus, "acpi"))
                return 0.0;

        // check whether it's really a device
        uint32 device_type;
        if (gDeviceManager->get_attr_uint32(parent, ACPI_DEVICE_TYPE_ITEM,
                        &device_type, false) != B_OK
                || device_type != ACPI_TYPE_DEVICE) {
                return 0.0;
        }

        // check whether it's an acpi wmi device
        const char *name;
        if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_HID_ITEM, &name,
                false) != B_OK || strcmp(name, ACPI_NAME_ACPI_WMI) != 0) {
                return 0.0;
        }

        TRACE("found an acpi wmi device\n");

        return 0.6;
}


static status_t
wmi_acpi_register_device(device_node *node)
{
        CALLED();
        device_attr attrs[] = {
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "WMI ACPI" }},
                { NULL }
        };

        return gDeviceManager->register_node(node, WMI_ACPI_DRIVER_NAME, attrs,
                NULL, NULL);
}


static status_t
wmi_acpi_init_driver(device_node *node, void **driverCookie)
{
        CALLED();
        WMIACPI* device = new(std::nothrow) WMIACPI(node);
        if (device == NULL)
                return B_NO_MEMORY;

        *driverCookie = device;

        return B_OK;
}


static void
wmi_acpi_uninit_driver(void *driverCookie)
{
        CALLED();
        WMIACPI *device = (WMIACPI*)driverCookie;

        delete device;
}


static status_t
wmi_acpi_register_child_devices(void *cookie)
{
        CALLED();
        WMIACPI *device = (WMIACPI*)cookie;
        return device->Scan();
}


module_dependency module_dependencies[] = {
        { B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&gDeviceManager },
        { SMBIOS_MODULE_NAME, (module_info**)&gSMBios },
        {}
};


static driver_module_info sWMIACPIDriverModule = {
        {
                WMI_ACPI_DRIVER_NAME,
                0,
                NULL
        },

        wmi_acpi_support,
        wmi_acpi_register_device,
        wmi_acpi_init_driver,
        wmi_acpi_uninit_driver,
        wmi_acpi_register_child_devices,
        NULL,   // rescan
        NULL,   // removed
};


module_info *modules[] = {
        (module_info *)&sWMIACPIDriverModule,
        (module_info *)&gWMIAsusDriverModule,
        (module_info *)&gWMIDeviceModule,
        NULL
};