root/src/add-ons/kernel/drivers/input/i2c_hid/Driver.cpp
/*
 * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
 * Copyright 2008-2011 Michael Lotz <mmlr@mlotz.ch>
 * Distributed under the terms of the MIT license.
 */


//!     Driver for I2C Human Interface Devices.


#include <ACPI.h>
#include <device_manager.h>
#include <i2c.h>

#include "DeviceList.h"
#include "Driver.h"
#include "HIDDevice.h"
#include "ProtocolHandler.h"

#include <lock.h>
#include <util/AutoLock.h>

#include <new>
#include <stdio.h>
#include <string.h>



struct hid_driver_cookie {
        device_node*                    node;
        i2c_device_interface*   i2c;
        i2c_device                              i2c_cookie;
        uint32                                  descriptorAddress;
        HIDDevice*                              hidDevice;
};

struct device_cookie {
        ProtocolHandler*        handler;
        uint32                          cookie;
        hid_driver_cookie*      driver_cookie;
};


#define I2C_HID_DRIVER_NAME "drivers/input/i2c_hid/driver_v1"
#define I2C_HID_DEVICE_NAME "drivers/input/i2c_hid/device_v1"

/* Base Namespace devices are published to */
#define I2C_HID_BASENAME "input/i2c_hid/%d"

// name of pnp generator of path ids
#define I2C_HID_PATHID_GENERATOR "i2c_hid/path_id"

#define ACPI_NAME_HID_DEVICE "PNP0C50"

static device_manager_info *sDeviceManager;
static acpi_module_info* gACPI;

DeviceList *gDeviceList = NULL;
static mutex sDriverLock;


static acpi_object_type*
acpi_evaluate_dsm(acpi_handle handle, const uint8 *guid, uint64 revision, uint64 function)
{
        acpi_data buffer;
        buffer.pointer = NULL;
        buffer.length = ACPI_ALLOCATE_BUFFER;

        acpi_object_type array[4];
        acpi_objects acpi_objects;
        acpi_objects.count = 4;
        acpi_objects.pointer = array;

        array[0].object_type = ACPI_TYPE_BUFFER;
        array[0].buffer.buffer = (void*)guid;
        array[0].buffer.length = 16;

        array[1].object_type = ACPI_TYPE_INTEGER;
        array[1].integer.integer = revision;

        array[2].object_type = ACPI_TYPE_INTEGER;
        array[2].integer.integer = function;

        array[3].object_type = ACPI_TYPE_PACKAGE;
        array[3].package.objects = NULL;
        array[3].package.count = 0;

        if (gACPI->evaluate_method(handle, "_DSM", &acpi_objects, &buffer) == B_OK)
                return (acpi_object_type*)buffer.pointer;
        return NULL;
}


// #pragma mark - notify hooks


/*
status_t
i2c_hid_device_removed(void *cookie)
{
        mutex_lock(&sDriverLock);
        int32 parentCookie = (int32)(addr_t)cookie;
        TRACE("device_removed(%" B_PRId32 ")\n", parentCookie);

        for (int32 i = 0; i < gDeviceList->CountDevices(); i++) {
                ProtocolHandler *handler = (ProtocolHandler *)gDeviceList->DeviceAt(i);
                if (!handler)
                        continue;

                HIDDevice *device = handler->Device();
                if (device->ParentCookie() != parentCookie)
                        continue;

                // remove all the handlers
                for (uint32 i = 0;; i++) {
                        handler = device->ProtocolHandlerAt(i);
                        if (handler == NULL)
                                break;

                        gDeviceList->RemoveDevice(NULL, handler);
                }

                // this handler's device belongs to the one removed
                if (device->IsOpen()) {
                        // the device and it's handlers will be deleted in the free hook
                        device->Removed();
                } else
                        delete device;

                break;
        }

        mutex_unlock(&sDriverLock);
        return B_OK;
}*/


// #pragma mark - driver hooks


static status_t
i2c_hid_init_device(void *driverCookie, void **cookie)
{
        *cookie = driverCookie;
        return B_OK;
}


static void
i2c_hid_uninit_device(void *_cookie)
{

}


static status_t
i2c_hid_open(void *initCookie, const char *path, int flags, void **_cookie)
{
        TRACE("open(%s, %" B_PRIu32 ", %p)\n", path, flags, _cookie);

        device_cookie *cookie = new(std::nothrow) device_cookie();
        if (cookie == NULL)
                return B_NO_MEMORY;
        cookie->driver_cookie = (hid_driver_cookie*)initCookie;

        MutexLocker locker(sDriverLock);

        ProtocolHandler *handler = (ProtocolHandler *)gDeviceList->FindDevice(path);
        TRACE("  path %s: handler %p\n", path, handler);

        cookie->handler = handler;
        cookie->cookie = 0;

        status_t result = handler == NULL ? B_ENTRY_NOT_FOUND : B_OK;
        if (result == B_OK)
                result = handler->Open(flags, &cookie->cookie);

        if (result != B_OK) {
                delete cookie;
                return result;
        }

        *_cookie = cookie;

        return B_OK;
}


static status_t
i2c_hid_read(void *_cookie, off_t position, void *buffer, size_t *numBytes)
{
        device_cookie *cookie = (device_cookie *)_cookie;

        TRACE("read(%p, %" B_PRIu64 ", %p, %p (%" B_PRIuSIZE ")\n", cookie, position, buffer, numBytes,
                numBytes != NULL ? *numBytes : 0);
        return cookie->handler->Read(&cookie->cookie, position, buffer, numBytes);
}


static status_t
i2c_hid_write(void *_cookie, off_t position, const void *buffer,
        size_t *numBytes)
{
        device_cookie *cookie = (device_cookie *)_cookie;

        TRACE("write(%p, %" B_PRIu64 ", %p, %p (%" B_PRIuSIZE ")\n", cookie, position, buffer, numBytes,
                numBytes != NULL ? *numBytes : 0);
        return cookie->handler->Write(&cookie->cookie, position, buffer, numBytes);
}


static status_t
i2c_hid_control(void *_cookie, uint32 op, void *buffer, size_t length)
{
        device_cookie *cookie = (device_cookie *)_cookie;

        TRACE("control(%p, %" B_PRIu32 ", %p, %" B_PRIuSIZE ")\n", cookie, op, buffer, length);
        return cookie->handler->Control(&cookie->cookie, op, buffer, length);
}


static status_t
i2c_hid_close(void *_cookie)
{
        device_cookie *cookie = (device_cookie *)_cookie;

        TRACE("close(%p)\n", cookie);
        return cookie->handler->Close(&cookie->cookie);
}


static status_t
i2c_hid_free(void *_cookie)
{
        device_cookie *cookie = (device_cookie *)_cookie;
        TRACE("free(%p)\n", cookie);

        mutex_lock(&sDriverLock);

        HIDDevice *device = cookie->handler->Device();
        if (device->IsOpen()) {
                // another handler of this device is still open so we can't free it
        } else if (device->IsRemoved()) {
                // the parent device is removed already and none of its handlers are
                // open anymore so we can free it here
                delete device;
        }

        mutex_unlock(&sDriverLock);

        delete cookie;
        return B_OK;
}


//      #pragma mark - driver module API


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

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

        if (strcmp(bus, "i2c"))
                return 0.0;
        TRACE("i2c_hid_support found an i2c device %p\n", parent);

        // check whether it's an HID device
        uint64 handlePointer;
        if (sDeviceManager->get_attr_uint64(parent, ACPI_DEVICE_HANDLE_ITEM,
                        &handlePointer, false) != B_OK) {
                TRACE("i2c_hid_support found an i2c device without acpi handle\n");
                return B_ERROR;
        }

        const char *name;
        if (sDeviceManager->get_attr_string(parent, ACPI_DEVICE_HID_ITEM, &name,
                false) == B_OK && strcmp(name, ACPI_NAME_HID_DEVICE) == 0) {
                TRACE("i2c_hid_support found an hid i2c device\n");
                return 0.6;
        }

        if (sDeviceManager->get_attr_string(parent, ACPI_DEVICE_CID_ITEM, &name,
                false) == B_OK && strcmp(name, ACPI_NAME_HID_DEVICE) == 0) {
                TRACE("i2c_hid_support found a compatible hid i2c device\n");
                return 0.6;
        }

        uint16 slaveAddress;
        if (sDeviceManager->get_attr_uint16(parent, I2C_DEVICE_SLAVE_ADDR_ITEM,
                &slaveAddress, false) != B_OK) {
                TRACE("i2c_hid_support found a non hid without addr i2c device\n");
                return B_ERROR;
        }

        TRACE("i2c_hid_support found a non hid i2c device\n");

        return 0.0;
}


static status_t
i2c_hid_register_device(device_node *node)
{
        CALLED();

        acpi_handle handle;
        if (sDeviceManager->get_attr_uint64(node, ACPI_DEVICE_HANDLE_ITEM,
                (uint64*)&handle, false) != B_OK) {
                return B_DEVICE_NOT_FOUND;
        }

        static uint8_t acpiHidGuid[] = { 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55,
                0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE };
        acpi_object_type* object = acpi_evaluate_dsm(handle, acpiHidGuid, 1, 1);
        if (object == NULL)
                return B_DEVICE_NOT_FOUND;
        if (object->object_type != ACPI_TYPE_INTEGER) {
                free(object);
                return B_DEVICE_NOT_FOUND;
        }

        uint32 descriptorAddress = object->integer.integer;
        free(object);
        device_attr attrs[] = {
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "I2C HID Device" }},
                { "descriptorAddress", B_UINT32_TYPE, { .ui32 = descriptorAddress }},
                { NULL }
        };

        return sDeviceManager->register_node(node, I2C_HID_DRIVER_NAME, attrs,
                NULL, NULL);
}


static status_t
i2c_hid_init_driver(device_node *node, void **driverCookie)
{
        CALLED();

        uint32 descriptorAddress;
        if (sDeviceManager->get_attr_uint32(node, "descriptorAddress",
                &descriptorAddress, false) != B_OK) {
                return B_DEVICE_NOT_FOUND;
        }

        hid_driver_cookie *device
                = (hid_driver_cookie *)calloc(1, sizeof(hid_driver_cookie));
        if (device == NULL)
                return B_NO_MEMORY;

        *driverCookie = device;

        device->node = node;
        device->descriptorAddress = descriptorAddress;

        device_node *parent;
        parent = sDeviceManager->get_parent_node(node);
        sDeviceManager->get_driver(parent, (driver_module_info **)&device->i2c,
                (void **)&device->i2c_cookie);
        sDeviceManager->put_node(parent);

        mutex_lock(&sDriverLock);
        HIDDevice *hidDevice
                = new(std::nothrow) HIDDevice(descriptorAddress, device->i2c,
                        device->i2c_cookie);

        if (hidDevice != NULL && hidDevice->InitCheck() == B_OK) {
                device->hidDevice = hidDevice;
        } else
                delete hidDevice;

        mutex_unlock(&sDriverLock);

        return device->hidDevice != NULL ? B_OK : B_IO_ERROR;
}


static void
i2c_hid_uninit_driver(void *driverCookie)
{
        CALLED();
        hid_driver_cookie *device = (hid_driver_cookie*)driverCookie;

        free(device);
}


static status_t
i2c_hid_register_child_devices(void *cookie)
{
        CALLED();
        hid_driver_cookie *device = (hid_driver_cookie*)cookie;
        HIDDevice* hidDevice = device->hidDevice;
        if (hidDevice == NULL)
                return B_OK;
        for (uint32 i = 0;; i++) {
                ProtocolHandler *handler = hidDevice->ProtocolHandlerAt(i);
                if (handler == NULL)
                        break;

                // As devices can be un- and replugged at will, we cannot
                // simply rely on a device count. If there is just one
                // keyboard, this does not mean that it uses the 0 name.
                // There might have been two keyboards and the one using 0
                // might have been unplugged. So we just generate names
                // until we find one that is not currently in use.
                int32 index = 0;
                char pathBuffer[B_DEV_NAME_LENGTH];
                const char *basePath = handler->BasePath();
                while (true) {
                        sprintf(pathBuffer, "%s%" B_PRId32, basePath, index++);
                        if (gDeviceList->FindDevice(pathBuffer) == NULL) {
                                // this name is still free, use it
                                handler->SetPublishPath(strdup(pathBuffer));
                                break;
                        }
                }

                gDeviceList->AddDevice(handler->PublishPath(), handler);
        
                sDeviceManager->publish_device(device->node, pathBuffer,
                        I2C_HID_DEVICE_NAME);
        }


/*      int pathID = sDeviceManager->create_id(I2C_HID_PATHID_GENERATOR);
        if (pathID < 0) {
                ERROR("register_child_devices: couldn't create a path_id\n");
                return B_ERROR;
        }*/
        return B_OK;
}


static status_t
std_ops(int32 op, ...)
{
        switch (op) {
                case B_MODULE_INIT:
                        gDeviceList = new(std::nothrow) DeviceList();
                        if (gDeviceList == NULL) {
                                return B_NO_MEMORY;
                        }
                        mutex_init(&sDriverLock, "i2c hid driver lock");

                        return B_OK;
                case B_MODULE_UNINIT:
                        delete gDeviceList;
                        gDeviceList = NULL;
                        mutex_destroy(&sDriverLock);
                        return B_OK;

                default:
                        break;
        }

        return B_ERROR;
}


//      #pragma mark -


driver_module_info i2c_hid_driver_module = {
        {
                I2C_HID_DRIVER_NAME,
                0,
                &std_ops
        },

        i2c_hid_support,
        i2c_hid_register_device,
        i2c_hid_init_driver,
        i2c_hid_uninit_driver,
        i2c_hid_register_child_devices,
        NULL,   // rescan
        NULL,   // removed
};


struct device_module_info i2c_hid_device_module = {
        {
                I2C_HID_DEVICE_NAME,
                0,
                NULL
        },

        i2c_hid_init_device,
        i2c_hid_uninit_device,
        NULL,

        i2c_hid_open,
        i2c_hid_close,
        i2c_hid_free,
        i2c_hid_read,
        i2c_hid_write,
        NULL,
        i2c_hid_control,

        NULL,
        NULL
};


module_dependency module_dependencies[] = {
        { B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager },
        { B_ACPI_MODULE_NAME, (module_info**)&gACPI },
        {}
};


module_info *modules[] = {
        (module_info *)&i2c_hid_driver_module,
        (module_info *)&i2c_hid_device_module,
        NULL
};