/**
 * @file hv_drv_UsbCoreBus.c
 * @brief usb bus driver
 * @details This module provide interfaces for usb bus.
 * @author HiView SoC Software Team
 * @version 0.0.1
 * @date 20230417
 * @copyright Copyright(c),2022-9, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author                         <th>Date             <th>Details
 * <tr><td>HiView SoC Software Team   <td>20230417       <td>Create and first Version.
 * </table>
 **/

#include "hv_drv_UsbCoreDevice.h"
#include "hv_drv_UsbErrNo.h"
#include "hv_drv_UsbCoreCommonUsb.h"
#include "hv_drv_UsbCoreUsb.h"

/* for usb subsystem. **NOT** a bus instance */
static struct bus_type *usb_subsys_bus = NULL;

/* for external read access to <usb_subsys_bus> */
struct bus_type *udrv_bus(void) {
    return usb_subsys_bus;
}

int bus_register(struct bus_type *bus) {
    INIT_LIST_HEAD(&bus->bus_drivers);
    INIT_LIST_HEAD(&bus->bus_devices);

    usb_subsys_bus = bus;
    return 0;
}

int driver_register(struct device_driver *drv) {
    struct bus_type *bus = usb_subsys_bus;
    USB_ASSERT(bus != NULL);
    /* FIXME: add some additional checks */
    list_add_tail(&drv->bus_drv, &bus->bus_drivers);
    return 0;
}

/**
 * bus_for_each_drv - iterate bus's drivers
 *
 * @bus: which bus that we prepare to iterate
 * @data: extra data for fn
 * @fn: proform fn when iterate each driver
 *      if fn returns a non-negative number break the iterate
 *
 * Returns fn's return or -1
 */
static int bus_for_each_drv(struct bus_type *bus, void *data,
                            int (*fn) (struct device_driver *, void *)) {
    struct device_driver *__drv = NULL;
    int ret = -1;

    list_for_each_entry(__drv, &bus->bus_drivers, bus_drv) {
        if (__drv != NULL) {
            ret = fn(__drv, data);
            if (ret >= 0) {
                break;
            }
        }
    }
    return ret;
}

/**
 * bus_for_each_dev - iterate bus's drivers
 *
 * @bus: which bus that we prepare to iterate
 * @data: extra data for fn
 * @fn: proform fn when iterate each driver
 *      if fn returns a non-negative number break the iterate
 *
 * Returns fn's return or -1
 */
static int bus_for_each_dev(struct bus_type *bus, void *data,
                            int (*fn) (void *, struct device *)) {
    struct device *__dev = NULL;
    int ret = -1;

    list_for_each_entry(__dev, &bus->bus_devices, bus_dev) {
        if (__dev != NULL) {
            ret = fn(data, __dev);
            if (ret >= 0) {
                break;
            }
        }
    }

    return ret;
}

/*
 * Clusore in <bus_for_each_dev>
 * Compare the address, judge if the same instance
 * Returns 1 for same, -1 for different
 */
INLINE int
__device_comparision(void *data, struct device *dev) {
    return data == dev ? 1 : -1;
}

void device_release_driver(struct device *dev) {
    struct device_driver *drv;

    drv = dev->driver;
    if (drv) {
        if (drv->remove) {
            drv->remove(dev);
        }

        dev->driver = NULL;
        dev_set_drvdata(dev, NULL);
    }
}

/* Returns 0 is not registered, 1 if is registered */
int device_is_registered(struct device *dev) {
    struct bus_type *bus;
    if (dev == NULL) {
        return 0;
    }

    bus = dev->bus;
    if (bus == NULL) {
        return 0;
    }

    return bus_for_each_dev(bus, (void *)dev,
                            __device_comparision) > 0 ?  1: 0;
}

static int driver_probe_device(
    struct device_driver *drv,
    struct device *dev) {
    int ret = 0;

    if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            return -ENODEV;
    }
    return 0;
}

/**
 * closure in <bus_probe_device()>
 * match the device with driver
 *
 * Returns -1 if unmatch
 * otherwise driver probe device then return probe's return
 */
INLINE
int __device_attach(
    struct device_driver *drv,
    void *data) {
    struct device *dev = (struct device *)data;
    struct bus_type *bus = dev->bus;

    if (!bus->match(dev, drv)) {
        return -1;
    } else {
        dev->driver = drv;
        return driver_probe_device(drv, dev);
    }
}

/**
 * closure in <bus_probe_device()>
 * match the device with driver
 *
 * Returns -1 if unmatch, 0 if matched
 */
INLINE
int __device_match(
    struct device_driver *drv,
    void *data) {
    struct device *dev = (struct device *)data;
    struct bus_type *bus = dev->bus;

    if (!bus->match(dev, drv)) {
        return -1;
    } else {
        dev->driver = drv;
        return 0;
    }
}

static int bus_probe_device(struct device *dev, int probe) {
    struct bus_type *bus;
    int ret = 0;
    if (!device_is_registered(dev)) {
        return -ENODEV;
    }

    bus = dev->bus;
    if (probe) {
        if (unlikely(dev->driver)) {
            return driver_probe_device(dev->driver, dev);
        } else {
            ret = bus_for_each_drv(bus, dev, __device_attach);
        }
    } else if (likely(!dev->driver)) {
        ret = bus_for_each_drv(bus, dev, __device_match);
    }

    return ret;
}

int device_add(struct device *dev) {
    struct bus_type *bus;
    if (dev == NULL) {
        return -ENODEV;
    }
    
    bus = dev->bus;
    if (!device_is_registered(dev)) {
        /* FIXME: add some additional checks */
        list_add_tail(&dev->bus_dev, &bus->bus_devices);
    }
    return is_usb_port(dev) ? 0 : bus_probe_device(dev, 1);
}

int device_attach(struct device *dev) {
    return bus_probe_device(dev, 1);
}

void device_del(struct device *dev) {
    if (dev == NULL) {
        return;
    }

    list_del_init(&dev->bus_dev);
}

void device_initialize(struct device *dev) {
    atomic_set(&dev->ref, 1);
}

int device_register(struct device *dev) {
    device_initialize(dev);
    return device_add(dev);
}

void device_unregister(struct device *dev) {
    if (device_is_registered(dev)) {
        device_del(dev);
        put_device(dev);
    }
}

struct device *get_device(struct device *dev) {
    if (!dev)
        return NULL;

    atomic_inc(&dev->ref);
    return dev;
}

void put_device(struct device *dev) {
    if (!dev)
        return;

    if (dev->driver && dev->driver->remove) {
        dev->driver->remove(dev);
    }

    if (dev->type && dev->type->release) {
        dev->type->release(dev);
    }
}
