/*
* Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
* Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
* Copyright(c) 2019-2023, Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files(the "Software"),
*to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
*and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions :
*
* The above copyright notice and this permission notice(including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL
* PRECISION INSIGHT AND / OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
*ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

#ifndef DRM_DEVICE_H_
#define DRM_DEVICE_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <dirent.h>
#include <stddef.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <time.h>
#include <sys/sysmacros.h>   //<sys/types.h>
#include <sys/stat.h>
#define stat_t struct stat
#include <sys/ioctl.h>
#include <sys/time.h>
#include <stdarg.h>
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif
#include <math.h>
#include <string>
#include <cstring>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

/* Not all systems have MAP_FAILED defined */
#ifndef MAP_FAILED
#define MAP_FAILED ((void *)-1)
#endif

#define DRM_DEV_UID  0
#define DRM_DEV_GID  0
/* Default /dev/dri directory permissions 0755 */
#define DRM_DEV_DIRMODE  (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#define DRM_DEV_MODE     (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
#define DRM_DEVICE_GET_PCI_REVISION (1 << 0)

#ifdef __OpenBSD__
#define DRM_DIR_NAME  "/dev"
#define DRM_DEV_NAME  "%s/drm%d"
#define DRM_CONTROL_DEV_NAME  "%s/drmC%d"
#define DRM_RENDER_DEV_NAME  "%s/drmR%d"
#else
#define DRM_DIR_NAME  "/dev/dri"
#define DRM_DEV_NAME  "%s/card%d"
#define DRM_CONTROL_DEV_NAME  "%s/controlD%d"
#define DRM_RENDER_DEV_NAME  "%s/renderD%d"
#define DRM_PROC_NAME "/proc/dri/" /* For backward Linux compatibility */
#endif

#ifdef __OpenBSD__
#define DRM_PRIMARY_MINOR_NAME  "drm"
#define DRM_CONTROL_MINOR_NAME  "drmC"
#define DRM_RENDER_MINOR_NAME   "drmR"
#else
#define DRM_PRIMARY_MINOR_NAME  "card"
#define DRM_CONTROL_MINOR_NAME  "controlD"
#define DRM_RENDER_MINOR_NAME   "renderD"
#endif

#define DRM_ERR_NO_DEVICE  (-1001)
#define DRM_ERR_NO_ACCESS  (-1002)
#define DRM_ERR_NOT_ROOT   (-1003)
#define DRM_ERR_INVALID    (-1004)
#define DRM_ERR_NO_FD      (-1005)

#define DRM_AGP_NO_HANDLE 0

#define DRM_BUS_PCI       0
#define DRM_BUS_USB       1
#define DRM_BUS_PLATFORM  2
#define DRM_BUS_HOST1X    3
#define DRM_BUS_VIRTIO 0x10

#define DRM_NODE_PRIMARY 0
#define DRM_NODE_CONTROL 1
#define DRM_NODE_RENDER  2
#define DRM_NODE_MAX     3

typedef unsigned int  drmSize, *drmSizePtr;         /**< For mapped regions */
typedef void          *drmAddress, **drmAddressPtr; /**< For mapped regions */

#if (__GNUC__ >= 3)
#define DRM_PRINTFLIKE(f, a) __attribute__ ((format(__printf__, f, a)))
#else
#define DRM_PRINTFLIKE(f, a)
#endif

#define MIN2( A, B )   ( (A)<(B) ? (A) : (B) )
#define MAX2( A, B )   ( (A)>(B) ? (A) : (B) )
#define MAX3( A, B, C ) ((A) > (B) ? MAX2(A, C) : MAX2(B, C))

#define __align_mask(value, mask)  (((value) + (mask)) & ~(mask))
#define ALIGN_CEIL(value, alignment)    __align_mask(value, (__typeof__(value))((alignment) - 1))
#define DRM_PLATFORM_DEVICE_NAME_LEN 512

typedef struct _drmPciBusInfo {
    uint16_t domain;
    uint8_t bus;
    uint8_t dev;
    uint8_t func;
} drmPciBusInfo, *drmPciBusInfoPtr;

typedef struct _drmPciDeviceInfo {
    uint32_t vendor_id;
    uint32_t device_id;
    uint32_t subvendor_id;
    uint32_t subdevice_id;
    uint32_t revision_id;
    char     driverInfo[1024];
    uint64_t videoMem[4] = {};
    uint64_t systemMem[4] = {};
    uint64_t sharedMem[4] = {};
    uint64_t ioportMem[4] = {};
    uint64_t ioMem[4] = {};
} drmPciDeviceInfo, *drmPciDeviceInfoPtr;


typedef struct _drmUsbBusInfo {
    uint8_t bus;
    uint8_t dev;
} drmUsbBusInfo, *drmUsbBusInfoPtr;

typedef struct _drmUsbDeviceInfo {
    uint16_t vendor;
    uint16_t product;
} drmUsbDeviceInfo, *drmUsbDeviceInfoPtr;



typedef struct _drmPlatformBusInfo {
    char fullname[DRM_PLATFORM_DEVICE_NAME_LEN + 1];
} drmPlatformBusInfo, *drmPlatformBusInfoPtr;

typedef struct _drmPlatformDeviceInfo {
    char **compatible; /* NULL terminated list of compatible strings */
} drmPlatformDeviceInfo, *drmPlatformDeviceInfoPtr;

#define DRM_HOST1X_DEVICE_NAME_LEN 512

typedef struct _drmHost1xBusInfo {
    char fullname[DRM_HOST1X_DEVICE_NAME_LEN + 1];
} drmHost1xBusInfo, *drmHost1xBusInfoPtr;

typedef struct _drmHost1xDeviceInfo {
    char **compatible; /* NULL terminated list of compatible strings */
} drmHost1xDeviceInfo, *drmHost1xDeviceInfoPtr;

typedef struct _drmDevice {
    char **nodes; /* DRM_NODE_MAX sized array */
    int available_nodes; /* DRM_NODE_* bitmask */
    int bustype;
    union {
        drmPciBusInfoPtr pci;
        drmUsbBusInfoPtr usb;
        drmPlatformBusInfoPtr platform;
        drmHost1xBusInfoPtr host1x;
    } businfo;
    union {
        drmPciDeviceInfoPtr pci;
        drmUsbDeviceInfoPtr usb;
        drmPlatformDeviceInfoPtr platform;
        drmHost1xDeviceInfoPtr host1x;
    } deviceinfo;

    unsigned int MaxThread = 0;
    unsigned int EuNumber = 0;
    unsigned int TileNumber = 0;
    unsigned int reserved[16];
} drmDevice, *drmDevicePtr;

/*
 * The kernel drm core has a number of places that assume maximum of
 * 3x64 devices nodes. That's 64 for each of primary, control and
 * render nodes. Rounded it up to 256 for simplicity.
 */
#define MAX_DRM_NODES 256

inline int memcpy_s(void *dst, size_t numberOfElements, const void *src, size_t count)
{
    if ((dst == nullptr) || (src == nullptr))
    {
        return EINVAL;
    }
    if (numberOfElements < count)
    {
        return ERANGE;
    }
    std::memcpy(dst, src, count);
    return 0;
}

 /* Check that the given flags are valid returning 0 on success */
static int
drm_device_validate_flags(uint32_t flags)
{
    return (flags & ~DRM_DEVICE_GET_PCI_REVISION);
}

static bool
drm_device_has_rdev(drmDevicePtr device, dev_t find_rdev)
{
    struct stat sbuf;

    for (int i = 0; i < DRM_NODE_MAX; i++) {
        if (device->available_nodes & 1 << i) {
            if (stat(device->nodes[i], &sbuf) == 0 &&
                sbuf.st_rdev == find_rdev)
                return true;
        }
    }
    return false;
}

static int drmGetMaxNodeName(void)
{
    return sizeof(DRM_DIR_NAME) +
        MAX3(sizeof(DRM_PRIMARY_MINOR_NAME),
            sizeof(DRM_CONTROL_MINOR_NAME),
            sizeof(DRM_RENDER_MINOR_NAME)) +
        3 /* length of the node number */;
}

static bool drmNodeIsDRM(int maj, int min)
{
#ifdef __linux__
    char path[64];
    struct stat sbuf;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device/drm",
        maj, min);
    return stat(path, &sbuf) == 0;
#else
    return maj == DRM_MAJOR;
#endif
}

static int drmDevicesEqual(drmDevicePtr a, drmDevicePtr b)
{
    if (a == NULL || b == NULL)
        return 0;

    if (a->bustype != b->bustype)
        return 0;

    switch (a->bustype) {
    case DRM_BUS_PCI:
        return memcmp(a->businfo.pci, b->businfo.pci, sizeof(drmPciBusInfo)) == 0;

    case DRM_BUS_USB:
        return memcmp(a->businfo.usb, b->businfo.usb, sizeof(drmUsbBusInfo)) == 0;

    case DRM_BUS_PLATFORM:
        return memcmp(a->businfo.platform, b->businfo.platform, sizeof(drmPlatformBusInfo)) == 0;

    case DRM_BUS_HOST1X:
        return memcmp(a->businfo.host1x, b->businfo.host1x, sizeof(drmHost1xBusInfo)) == 0;

    default:
        break;
    }
    return 0;
}

static drmDevicePtr drmDeviceAlloc(unsigned int type, const char *node,
    size_t bus_size, size_t device_size,
    char **ptrp)
{
    size_t max_node_length, extra, size;
    drmDevicePtr device;
    unsigned int i;
    char *ptr;

    max_node_length = ALIGN_CEIL(drmGetMaxNodeName(), sizeof(void *));

    extra = DRM_NODE_MAX * (sizeof(void *) + max_node_length);

     size = sizeof(*device) + extra + bus_size + device_size;

    device = (drmDevicePtr)calloc(1, size);
    if (!device)
        return NULL;

    device->available_nodes = 1 << type;

    ptr = (char *)device + sizeof(*device);
    device->nodes = (char **)ptr;

    ptr += DRM_NODE_MAX * sizeof(void *);

    for (i = 0; i < DRM_NODE_MAX; i++) {
        device->nodes[i] = ptr;
        ptr += max_node_length;
    }

    memcpy(device->nodes[type], node, max_node_length);
    *ptrp = ptr;

    return device;
}

static char * DRM_PRINTFLIKE(2, 3)
sysfs_uevent_get(const char *path, const char *fmt, ...)
{
    char filename[PATH_MAX + 1], *key, *line = NULL, *value = NULL;
    size_t size = 0, len;
    ssize_t num;
    va_list ap;
    FILE *fp;

    va_start(ap, fmt);
    num = vasprintf(&key, fmt, ap);
    va_end(ap);
    len = num;

    snprintf(filename, sizeof(filename), "%s/uevent", path);

    fp = fopen(filename, "r");
    if (!fp) {
        free(key);
        return NULL;
    }

    while ((num = getline(&line, &size, fp)) >= 0) {
        if ((strncmp(line, key, len) == 0) && (line[len] == '=')) {
            char *start = line + len + 1, *end = line + num - 1;

            if (*end != '\n')
                end++;

            value = strndup(start, end - start);
            break;
        }
    }

    free(line);
    fclose(fp);

    free(key);

    return value;
}

static int drmParseHost1xDeviceInfo(int maj,
                                    int min,
                                    drmHost1xDeviceInfoPtr info)
{
    char path[PATH_MAX + 1];
    int err = 0;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);

    char *value = sysfs_uevent_get(path, "OF_COMPATIBLE_N");
    if (!value)
    {
        return -ENOENT;
    }
    unsigned int count = 0;
    int scanned_value_count = sscanf(value, "%u", &count);
    free(value);
    if (scanned_value_count <= 0 || 0 == count)
    {
        return -ENOENT;
    }

    info->compatible = (char**)calloc(count + 1, sizeof(*info->compatible));
    if (!info->compatible)
    {
        return -ENOMEM;
    }

    unsigned int i = 0;
    for (; i < count; i++)
    {
        value = sysfs_uevent_get(path, "OF_COMPATIBLE_%u", i);
        if (!value)
        {
            err = -ENOENT;
            goto free;
        }
        info->compatible[i] = value;
    }
    return 0;

free:
    while (i--)
    {
        free(info->compatible[i]);
    }
    free(info->compatible);
    return err;
}

static int drmParseHost1xBusInfo(int maj, int min, drmHost1xBusInfoPtr info)
{
    char path[PATH_MAX + 1], *name;
    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    name = sysfs_uevent_get(path, "OF_FULLNAME");
    if (!name)
        return -ENOENT;
    strncpy(info->fullname, name, DRM_HOST1X_DEVICE_NAME_LEN);
    info->fullname[DRM_HOST1X_DEVICE_NAME_LEN - 1] = '\0';
    free(name);
    return 0;
}

static int drmProcessHost1xDevice(drmDevicePtr *device,
    const char *node, int node_type,
    int maj, int min, bool fetch_deviceinfo,
    uint32_t flags)
{
    drmDevicePtr dev;
    char *ptr;
    int ret;

    dev = drmDeviceAlloc(node_type, node, sizeof(drmHost1xBusInfo),
        sizeof(drmHost1xDeviceInfo), &ptr);
    if (!dev)
        return -ENOMEM;

    dev->bustype = DRM_BUS_HOST1X;

    dev->businfo.host1x = (drmHost1xBusInfoPtr)ptr;

    ret = drmParseHost1xBusInfo(maj, min, dev->businfo.host1x);
    if (ret < 0)
        goto free_device;

    if (fetch_deviceinfo) {
        ptr += sizeof(drmHost1xBusInfo);
        dev->deviceinfo.host1x = (drmHost1xDeviceInfoPtr)ptr;

        ret = drmParseHost1xDeviceInfo(maj, min, dev->deviceinfo.host1x);
        if (ret < 0)
            goto free_device;
    }

    *device = dev;

    return 0;

free_device:
    free(dev);
    return ret;
}

static int drmParsePlatformBusInfo(int maj, int min, drmPlatformBusInfoPtr info)
{
    char path[PATH_MAX + 1], *name;
    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    name = sysfs_uevent_get(path, "OF_FULLNAME");
    if (!name)
        return -ENOENT;
    strncpy(info->fullname, name, DRM_PLATFORM_DEVICE_NAME_LEN);
    info->fullname[DRM_PLATFORM_DEVICE_NAME_LEN - 1] = '\0';
    free(name);
    return 0;
}

static int drmParsePlatformDeviceInfo(int maj, int min,
    drmPlatformDeviceInfoPtr info)
{
    char path[PATH_MAX + 1], *value;
    unsigned int count, i;
    int err;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    value = sysfs_uevent_get(path, "OF_COMPATIBLE_N");
    if (!value)
        return -ENOENT;

    int scanned_value_count = sscanf(value, "%u", &count);
    free(value);
    if (scanned_value_count <= 0 || 0 == count)
    {
        return -ENOENT;
    }

    if (count <= MAX_DRM_NODES)
    {
        info->compatible = (char**)calloc(count + 1, sizeof(*info->compatible));
    }else
        return -ENOENT;

    if (!info->compatible)
        return -ENOMEM;
    for (i = 0; i < count; i++) {
        value = sysfs_uevent_get(path, "OF_COMPATIBLE_%u", i);
        if (!value) {
            err = -ENOENT;
            goto free;
        }
        info->compatible[i] = value;
    }
    return 0;
free:
    while (i--)
        free(info->compatible[i]);
    free(info->compatible);
    return err;
}

static void
get_pci_path(int maj, int min, char *pci_path)
{
    char path[PATH_MAX + 1], *term;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    if (!realpath(path, pci_path)) {
        strcpy(pci_path, path);
        return;
    }

    term = strrchr(pci_path, '/');
    if (term && strncmp(term, "/virtio", 7) == 0)
        *term = 0;
}

static int parse_separate_sysfs_files(int maj, int min,
    drmPciDeviceInfoPtr device,
    bool ignore_revision)
{
    static const char *attrs[] = {
        "revision", /* Older kernels are missing the file, so check for it first */
        "vendor",
        "device",
        "subsystem_vendor",
        "subsystem_device",
    };
    char path[PATH_MAX + 128], pci_path[PATH_MAX];
    char resourcename[PATH_MAX + 64], driverpath[PATH_MAX + 64], drivername[PATH_MAX + 64], irqpath[PATH_MAX + 64];

    unsigned int data[ARRAY_SIZE(attrs)];
    FILE *fp;
    int ret;
    char *dev_path;
    char *driver_name;

    get_pci_path(maj, min, pci_path);
    dev_path = strrchr(pci_path, '/');
    dev_path++;

    snprintf(driverpath, sizeof(driverpath), "%s/driver", pci_path);
    snprintf(irqpath, sizeof(irqpath), "%s/irq", pci_path);
    int fd = open(irqpath, O_RDONLY);

    char buffer[512] ;
    memset(buffer, 0, sizeof(buffer));
    if (fd >= 0)
    {
        int count = 0;
        count = read(fd, buffer, sizeof(buffer));
        close(fd);
    }

    snprintf(resourcename, sizeof(resourcename), "%s/resource", pci_path);

    memset(drivername, 0, sizeof(drivername));
    if (readlink(driverpath, drivername, PATH_MAX) < 0)
    {
        /* Some devices might not be bound to a driver */
        if (errno == ENOENT)
            return -errno;
        else
            printf("   readlink -errno %d\n", errno);
    }

    driver_name = strrchr(drivername, '/');
    driver_name++;
    snprintf(device->driverInfo, sizeof(device->driverInfo), "Driver Module %s  Bus %s IRQ %s", driver_name, dev_path, buffer);

    FILE*resource = fopen(resourcename, "r");

    if (resource)
    {
        while (!feof(resource))
        {
            unsigned long long start, end, flags;

            start = end = flags = 0;

            if (fscanf(resource, "%llx %llx %llx", &start, &end, &flags) != 3)
                break;

            if (flags & 0x101)
            {
                device->ioportMem[0] = end - start + 1;
            }
            else
                if (flags & 0x100)
                {
                    device->ioMem[0] = end - start + 1;
                }
                else
                    if (flags & 0x200)
                    {
                        int i;
                        for ( i = 0; i < 4; i++)
                        {
                            if (device->videoMem[i] == 0)
                            {
                                device->videoMem[i] = end - start + 1;
                                break;
                            }
                        }

                    }
        }
        fclose(resource);
    }


    for (unsigned i = ignore_revision ? 1 : 0; i < ARRAY_SIZE(attrs); i++) {
        snprintf(path, PATH_MAX + 128, "%s/%s", pci_path, attrs[i]);

        fp = fopen(path, "r");
        if (!fp)
            return -errno;

        ret = fscanf(fp, "%x", &data[i]);
        fclose(fp);
        if (ret != 1)
            return -errno;
    }

    device->revision_id = ignore_revision ? 0xff : data[0] & 0xff;
    device->vendor_id = data[1] & 0xffff;
    device->device_id = data[2] & 0xffff;
    device->subvendor_id = data[3] & 0xffff;
    device->subdevice_id = data[4] & 0xffff;
    return 0;
}

static int parse_config_sysfs_file(int maj, int min,
    drmPciDeviceInfoPtr device)
{
    char path[PATH_MAX + 128], pci_path[PATH_MAX + 1];
    unsigned char config[64];
    int fd, ret;

    get_pci_path(maj, min, pci_path);

    snprintf(path, PATH_MAX + 128, "%s/config", pci_path);

    fd = open(path, O_RDONLY);
    if (fd < 0)
        return -errno;

    ret = read(fd, config, sizeof(config));
    close(fd);
    if (ret < 0)
        return -errno;

    device->vendor_id = config[0] | (config[1] << 8);
    device->device_id = config[2] | (config[3] << 8);
    device->revision_id = config[8];
    device->subvendor_id = config[44] | (config[45] << 8);
    device->subdevice_id = config[46] | (config[47] << 8);

    return 0;
}

static int drmParsePciDeviceInfo(int maj, int min,
    drmPciDeviceInfoPtr device,
    uint32_t flags)
{
//#ifdef __linux__
    if (!(flags & DRM_DEVICE_GET_PCI_REVISION))
        return parse_separate_sysfs_files(maj, min, device, true);

    if (parse_separate_sysfs_files(maj, min, device, false))
        return parse_config_sysfs_file(maj, min, device);
    return 0;
/*
#elif defined(__OpenBSD__) || defined(__DragonFly__)
    struct drm_pciinfo pinfo;
    int fd, type;

    type = drmGetMinorType(min);
    if (type == -1)
        return -ENODEV;

    fd = drmOpenMinor(min, 0, type);
    if (fd < 0)
        return -errno;

    if (drmIoctl(fd, DRM_IOCTL_GET_PCIINFO, &pinfo)) {
        close(fd);
        return -errno;
    }
    close(fd);

    device->vendor_id = pinfo.vendor_id;
    device->device_id = pinfo.device_id;
    device->revision_id = pinfo.revision_id;
    device->subvendor_id = pinfo.subvendor_id;
    device->subdevice_id = pinfo.subdevice_id;

    return 0;
#else
    #warning "Missing implementation of drmParsePciDeviceInfo"
        return -EINVAL;
#endif
*/
}

static int drmParsePciBusInfo(int maj, int min, drmPciBusInfoPtr info)
{
    unsigned int domain, bus, dev, func;
    char pci_path[PATH_MAX + 1], *value;
    int num;
    get_pci_path(maj, min, pci_path);
    value = sysfs_uevent_get(pci_path, "PCI_SLOT_NAME");
    if (!value)
        return -ENOENT;

    num = sscanf(value, "%04x:%02x:%02x.%1u", &domain, &bus, &dev, &func);
    free(value);
    if (num != 4)
        return -EINVAL;
    info->domain = domain;
    info->bus = bus;
    info->dev = dev;
    info->func = func;
    return 0;
}

static int drmProcessPlatformDevice(drmDevicePtr *device,
    const char *node, int node_type,
    int maj, int min, bool fetch_deviceinfo,
    uint32_t flags)
{
    drmDevicePtr dev;
    char *ptr;
    int ret;

    dev = drmDeviceAlloc(node_type, node, sizeof(drmPlatformBusInfo),
        sizeof(drmPlatformDeviceInfo), &ptr);
    if (!dev)
        return -ENOMEM;

    dev->bustype = DRM_BUS_PLATFORM;

    dev->businfo.platform = (drmPlatformBusInfoPtr)ptr;

    ret = drmParsePlatformBusInfo(maj, min, dev->businfo.platform);
    if (ret < 0)
        goto free_device;

    if (fetch_deviceinfo) {
        ptr += sizeof(drmPlatformBusInfo);
        dev->deviceinfo.platform = (drmPlatformDeviceInfoPtr)ptr;

        ret = drmParsePlatformDeviceInfo(maj, min, dev->deviceinfo.platform);
        if (ret < 0)
            goto free_device;
    }

    *device = dev;

    return 0;

free_device:
    free(dev);
    return ret;
}

static int drmProcessPciDevice(drmDevicePtr *device,
    const char *node, int node_type,
    int maj, int min, bool fetch_deviceinfo,
    uint32_t flags)
{
    drmDevicePtr dev;
    char *addr;
    int ret;

    dev = drmDeviceAlloc(node_type, node, sizeof(drmPciBusInfo),
        sizeof(drmPciDeviceInfo), &addr);

    if (!dev)
        return -ENOMEM;

    dev->bustype = DRM_BUS_PCI;

    dev->businfo.pci = (drmPciBusInfoPtr)addr;

    ret = drmParsePciBusInfo(maj, min, dev->businfo.pci);
    if (ret)
        goto free_device;

    // Fetch the device info if the user has requested it
    if (fetch_deviceinfo) {
        addr += sizeof(drmPciBusInfo);
        dev->deviceinfo.pci = (drmPciDeviceInfoPtr)addr;
        ret = drmParsePciDeviceInfo(maj, min, dev->deviceinfo.pci, flags);

        if (ret)
            goto free_device;
    }

    *device = dev;

    return 0;

free_device:
    free(dev);
    return ret;
}

static int drmParseUsbBusInfo(int maj, int min, drmUsbBusInfoPtr info)
{
    char path[PATH_MAX + 1], *value;
    unsigned int bus, dev;
    int ret;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    value = sysfs_uevent_get(path, "BUSNUM");
    if (!value)
        return -ENOENT;

    ret = sscanf(value, "%03u", &bus);
    free(value);

    if (ret <= 0)
        return -errno;

    value = sysfs_uevent_get(path, "DEVNUM");
    if (!value)
        return -ENOENT;

    ret = sscanf(value, "%03u", &dev);
    free(value);

    if (ret <= 0)
        return -errno;

    info->bus = bus;
    info->dev = dev;
    return 0;
}

static int drmParseUsbDeviceInfo(int maj, int min, drmUsbDeviceInfoPtr info)
{
    char path[PATH_MAX + 1], *value;
    unsigned int vendor, product;
    int ret;

    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
    value = sysfs_uevent_get(path, "PRODUCT");
    if (!value)
        return -ENOENT;

    ret = sscanf(value, "%x/%x", &vendor, &product);
    free(value);

    if (ret <= 0)
        return -errno;

    info->vendor = vendor;
    info->product = product;
    return 0;
}

static int drmProcessUsbDevice(drmDevicePtr *device, const char *node,
    int node_type, int maj, int min,
    bool fetch_deviceinfo, uint32_t flags)
{
    drmDevicePtr dev;
    char *ptr;
    int ret;

    dev = drmDeviceAlloc(node_type, node, sizeof(drmUsbBusInfo),
        sizeof(drmUsbDeviceInfo), &ptr);
    if (!dev)
        return -ENOMEM;

    dev->bustype = DRM_BUS_USB;

    dev->businfo.usb = (drmUsbBusInfoPtr)ptr;

    ret = drmParseUsbBusInfo(maj, min, dev->businfo.usb);
    if (ret < 0)
        goto free_device;

    if (fetch_deviceinfo) {
        ptr += sizeof(drmUsbBusInfo);
        dev->deviceinfo.usb = (drmUsbDeviceInfoPtr)ptr;

        ret = drmParseUsbDeviceInfo(maj, min, dev->deviceinfo.usb);
        if (ret < 0)
            goto free_device;
    }

    *device = dev;

    return 0;

free_device:
    free(dev);
    return ret;
}


static int drmParseSubsystemType(int maj, int min)
{
#ifdef __linux__
    char path[PATH_MAX + 1];
    char link[PATH_MAX + 1] = "";
    char *name;
    struct {
        const char *name;
        int bus_type;
    } bus_types[] = {
        { "/pci", DRM_BUS_PCI },
    { "/usb", DRM_BUS_USB },
    { "/platform", DRM_BUS_PLATFORM },
    { "/spi", DRM_BUS_PLATFORM },
    { "/host1x", DRM_BUS_HOST1X },
    { "/virtio", DRM_BUS_VIRTIO },
    };

    snprintf(path, PATH_MAX, "/sys/dev/char/%d:%d/device/subsystem",
        maj, min);

    memset(link, 0, sizeof(link));
    if (readlink(path, link, PATH_MAX) < 0)
        return -errno;

    name = strrchr(link, '/');
    if (!name)
        return -EINVAL;

    for (unsigned i = 0; i < ARRAY_SIZE(bus_types); i++) {
        if (strncmp(name, bus_types[i].name, strlen(bus_types[i].name)) == 0)
            return bus_types[i].bus_type;
    }

    return -EINVAL;
#elif defined(__OpenBSD__) || defined(__DragonFly__)
    return DRM_BUS_PCI;
#else
    #warning "Missing implementation of drmParseSubsystemType"
        return -EINVAL;
#endif
}

static void drmFreePlatformDevice(drmDevicePtr device)
{
    if (device->deviceinfo.platform) {
        if (device->deviceinfo.platform->compatible) {
            char **compatible = device->deviceinfo.platform->compatible;

            while (*compatible) {
                free(*compatible);
                compatible++;
            }

            free(device->deviceinfo.platform->compatible);
        }
    }
}

static void drmFreeHost1xDevice(drmDevicePtr device)
{
    if (device->deviceinfo.host1x) {
        if (device->deviceinfo.host1x->compatible) {
            char **compatible = device->deviceinfo.host1x->compatible;

            while (*compatible) {
                free(*compatible);
                compatible++;
            }

            free(device->deviceinfo.host1x->compatible);
        }
    }
}

static int drmGetNodeType(const char *name)
{
    if (strncmp(name, DRM_PRIMARY_MINOR_NAME,
        sizeof(DRM_PRIMARY_MINOR_NAME) - 1) == 0)
        return DRM_NODE_PRIMARY;

    if (strncmp(name, DRM_CONTROL_MINOR_NAME,
        sizeof(DRM_CONTROL_MINOR_NAME) - 1) == 0)
        return DRM_NODE_CONTROL;

    if (strncmp(name, DRM_RENDER_MINOR_NAME,
        sizeof(DRM_RENDER_MINOR_NAME) - 1) == 0)
        return DRM_NODE_RENDER;

    return -EINVAL;
}

void drmFreeDevice(drmDevicePtr *device)
{
    if (device == NULL)
        return;

    if (*device) {
        switch ((*device)->bustype) {
        case DRM_BUS_PLATFORM:
            drmFreePlatformDevice(*device);
            break;

        case DRM_BUS_HOST1X:
            drmFreeHost1xDevice(*device);
            break;
        }
    }

    free(*device);
    *device = NULL;
}

static int
process_device(drmDevicePtr *device, const char *d_name,
    int req_subsystem_type,
    bool fetch_deviceinfo, uint32_t flags)
{
    struct stat sbuf;
    char node[PATH_MAX + 1];
    int node_type, subsystem_type;
    unsigned int maj, min;

    node_type = drmGetNodeType(d_name);
    if (node_type < 0)
        return -1;

    snprintf(node, PATH_MAX, "%s/%s", DRM_DIR_NAME, d_name);

    if (stat(node, &sbuf))
        return -1;

    maj = major(sbuf.st_rdev);
    min = minor(sbuf.st_rdev);

    if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode))
    {
        return -1;
    }

    subsystem_type = drmParseSubsystemType(maj, min);

    if (req_subsystem_type != -1 && req_subsystem_type != subsystem_type)
        return -1;

    switch (subsystem_type) {
    case DRM_BUS_PCI:
    case DRM_BUS_VIRTIO:
        return drmProcessPciDevice(device, node, node_type, maj, min,
            fetch_deviceinfo, flags);
    case DRM_BUS_USB:
        return drmProcessUsbDevice(device, node, node_type, maj, min,
            fetch_deviceinfo, flags);
    case DRM_BUS_PLATFORM:
        return drmProcessPlatformDevice(device, node, node_type, maj, min,
            fetch_deviceinfo, flags);
    case DRM_BUS_HOST1X:
        return drmProcessHost1xDevice(device, node, node_type, maj, min,
            fetch_deviceinfo, flags);
    default:
        return -1;
    }
}

/* Consider devices located on the same bus as duplicate and fold the respective
* entries into a single one.
*
* Note: this leaves "gaps" in the array, while preserving the length.
*/
static void drmFoldDuplicatedDevices(drmDevicePtr local_devices[], int count)
{
    int node_type, i, j;

    for (i = 0; i < count; i++) {
        for (j = i + 1; j < count; j++) {
            if (drmDevicesEqual(local_devices[i], local_devices[j])) {
                local_devices[i]->available_nodes |= local_devices[j]->available_nodes;
                node_type = log2(local_devices[j]->available_nodes);
                memcpy(local_devices[i]->nodes[node_type],
                    local_devices[j]->nodes[node_type], drmGetMaxNodeName());
                drmFreeDevice(&local_devices[j]);
            }
        }
    }
}

/**
 * Get drm devices on the system
 *
 * \param flags feature/behaviour bitmask
 * \param devices the array of devices with drmDevicePtr elements
 *                can be NULL to get the device number first
 * \param max_devices the maximum number of devices for the array
 *
 * \return on error - negative error code,
 *         if devices is NULL - total number of devices available on the system,
 *         alternatively the number of devices stored in devices[], which is
 *         capped by the max_devices.
 *
 * \note Unlike drmGetDevices it does not retrieve the pci device revision field
 * unless the DRM_DEVICE_GET_PCI_REVISION \p flag is set.
 */
int drmGetDevices2(uint32_t flags, drmDevicePtr devices[],
                              int max_devices)
{
    drmDevicePtr local_devices[MAX_DRM_NODES];
    drmDevicePtr device;
    DIR *sysdir;
    struct dirent *dent;
    int ret, i, node_count, device_count;

    if (drm_device_validate_flags(flags))
        return -EINVAL;

    sysdir = opendir(DRM_DIR_NAME);
    if (!sysdir)
        return -errno;

    i = 0;
    while ((dent = readdir(sysdir))) {
        ret = process_device(&device, dent->d_name, -1, devices != NULL, flags);
        if (ret)
            continue;

        if (i >= MAX_DRM_NODES) {
            fprintf(stderr, "More than %d drm nodes detected. "
                    "Please report  - that should not happen.\n"
                    "Skipping extra nodes\n", MAX_DRM_NODES);
            break;
        }
        local_devices[i] = device;
        i++;
    }
    node_count = i;
    drmFoldDuplicatedDevices(local_devices, node_count);

    device_count = 0;
    for (i = 0; i < node_count; i++) {
        if (!local_devices[i])
            continue;

        if ((devices != NULL) && (device_count < max_devices))
            devices[device_count] = local_devices[i];
        else
            drmFreeDevice(&local_devices[i]);

        device_count++;
    }

    closedir(sysdir);
    return device_count;
}

/**
 * Get drm devices on the system
 *
 * \param devices the array of devices with drmDevicePtr elements
 *                can be NULL to get the device number first
 * \param max_devices the maximum number of devices for the array
 *
 * \return on error - negative error code,
 *         if devices is NULL - total number of devices available on the system,
 *         alternatively the number of devices stored in devices[], which is
 *         capped by the max_devices.
 */
int drmGetDevices(drmDevicePtr devices[], int max_devices)
{
    return drmGetDevices2(DRM_DEVICE_GET_PCI_REVISION, devices, max_devices);
}

/**
 * Get information about the opened drm device
 *
 * \param fd file descriptor of the drm device
 * \param flags feature/behaviour bitmask
 * \param device the address of a drmDevicePtr where the information
 *               will be allocated in stored
 *
 * \return zero on success, negative error code otherwise.
 *
 * \note Unlike drmGetDevice it does not retrieve the pci device revision field
 * unless the DRM_DEVICE_GET_PCI_REVISION \p flag is set.
 */
int drmGetDevice2(int fd, uint32_t flags, drmDevicePtr *device)
{
    drmDevicePtr local_devices[MAX_DRM_NODES];
    drmDevicePtr d;
    DIR *sysdir;
    struct dirent *dent;
    struct stat sbuf;
    int subsystem_type;
    int maj, min;
    int ret, i, node_count;
    dev_t find_rdev;

    if (drm_device_validate_flags(flags))
        return -EINVAL;

    if (fd == -1 || device == NULL)
        return -EINVAL;

    if (fstat(fd, &sbuf))
        return -errno;

    find_rdev = sbuf.st_rdev;
    maj = major(sbuf.st_rdev);
    min = minor(sbuf.st_rdev);

    if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode))
        return -EINVAL;

    subsystem_type = drmParseSubsystemType(maj, min);
    if (subsystem_type < 0)
        return subsystem_type;

    sysdir = opendir(DRM_DIR_NAME);
    if (!sysdir)
        return -errno;

    i = 0;
    while ((dent = readdir(sysdir))) {
        ret = process_device(&d, dent->d_name, subsystem_type, true, flags);
        if (ret)
            continue;

        if (i >= MAX_DRM_NODES) {
            fprintf(stderr, "More than %d drm nodes detected. "
                    "Please report a bug - that should not happen.\n"
                    "Skipping extra nodes\n", MAX_DRM_NODES);
            break;
        }
        local_devices[i] = d;
        i++;
    }
    node_count = i;

    drmFoldDuplicatedDevices(local_devices, node_count);

    *device = NULL;

    for (i = 0; i < node_count; i++) {
        if (!local_devices[i])
            continue;

        if (drm_device_has_rdev(local_devices[i], find_rdev))
            *device = local_devices[i];
        else
            drmFreeDevice(&local_devices[i]);
    }

    closedir(sysdir);
    if (*device == NULL)
        return -ENODEV;
    return 0;
}

/**
 * Get information about the opened drm device
 *
 * \param fd file descriptor of the drm device
 * \param device the address of a drmDevicePtr where the information
 *               will be allocated in stored
 *
 * \return zero on success, negative error code otherwise.
 */
int drmGetDevice(int fd, drmDevicePtr *device)
{
    return drmGetDevice2(fd, DRM_DEVICE_GET_PCI_REVISION, device);
}

static int32_t GetRendererFileDescriptor(char * drm_node)
{
    int32_t driFileDescriptor = -1;

    driFileDescriptor = open(drm_node, O_RDWR);
    return driFileDescriptor;
}

#endif  // #ifndef DRM_DEVICE_H_
