/**
 * @file hv_drv_UsbCoreDriver.c
 * @brief usb core driver
 * @details This module provide interfaces for usb core driver.
 * @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_UsbCoreCommonUsb.h"
#include "hv_drv_UsbCoreDevice.h"
#include "hv_drv_UsbCoreUsb.h"

static int usb_match_one_id_intf(
    struct usb_device *dev,
    struct usb_host_interface *intf,
    const struct usb_device_id *id) {
    /* The interface class, subclass, protocol and number should never be
     * checked for a match if the device class is Vendor Specific,
     * unless the match record specifies the Vendor ID. */
    if (dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC &&
            !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
            (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS |
                                USB_DEVICE_ID_MATCH_INT_SUBCLASS |
                                USB_DEVICE_ID_MATCH_INT_PROTOCOL |
                                USB_DEVICE_ID_MATCH_INT_NUMBER)))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
            (id->bInterfaceClass != intf->desc.bInterfaceClass))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
            (id->bInterfaceSubClass != intf->desc.bInterfaceSubClass))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
            (id->bInterfaceProtocol != intf->desc.bInterfaceProtocol))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER) &&
            (id->bInterfaceNumber != intf->desc.bInterfaceNumber))
        return 0;

    return 1;
}

static int usb_match_device(struct usb_device *dev,
                            const struct usb_device_id *id) {
    if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
            id->idVendor != (dev->descriptor.idVendor))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
            id->idProduct != (dev->descriptor.idProduct))
        return 0;

    /* No need to test id->bcdDevice_lo != 0, since 0 is never
     *     greater than any unsigned number. */
    if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
            (id->bcdDevice_lo > (dev->descriptor.bcdDevice)))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
            (id->bcdDevice_hi < (dev->descriptor.bcdDevice)))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
            (id->bDeviceClass != dev->descriptor.bDeviceClass))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
            (id->bDeviceSubClass != dev->descriptor.bDeviceSubClass))
        return 0;

    if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
            (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
        return 0;

    return 1;
}

/* returns 0 if no match, 1 if match */
static int usb_match_one_id(struct usb_interface *interface,
                            const struct usb_device_id *id) {
    struct usb_host_interface *intf;
    struct usb_device *udev;
    if (id == NULL) {
        return 0;
    }

    intf = interface->cur_altsetting;
    udev = interface_to_usbdev(interface);

    if (!usb_match_device(udev, id)) {
        return 0;
    }

    return usb_match_one_id_intf(udev, intf, id);
}

static const struct usb_device_id *usb_match_dynamic_id(
    struct usb_interface *intf, struct usb_driver *drv) {
    struct usb_dynid *dynid;

    list_for_each_entry(dynid, &drv->dynids.list, node) {
        if (usb_match_one_id(intf, &dynid->id)) {
            return &dynid->id;
        }
    }

    return NULL;
}

static const struct usb_device_id *usb_match_id(
    struct usb_interface *interface,
    const struct usb_device_id *id) {
    if (id == NULL) {
        return NULL;
    }

    for (; id->idVendor || id->idProduct || id->bDeviceClass ||
            id->bInterfaceClass || id->driver_info; id++) {
        if (usb_match_one_id(interface, id)) {
            return id;
        }
    }

    return NULL;
}

static int usb_probe_interface(struct device *dev) {
    struct usb_driver *driver = to_usb_driver(dev->driver);
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_device *udev = interface_to_usbdev(intf);
    const struct usb_device_id *id;
    int error = -ENODEV;

    intf->needs_binding = 0;

    if (udev->authorized == 0) {
        return error;
    }

    id = usb_match_dynamic_id(intf, driver);
    if (!id)
        id = usb_match_id(intf, driver->id_table);
    if (!id)
        return error;

    intf->condition = USB_INTERFACE_BINDING;

    /* Carry out a differed switch to altsetting 0 */
    if (intf->needs_altsetting0) {
        error = usb_set_interface(udev,
                                  intf->altsetting[0].desc.bInterfaceNumber, 0);
        if (error < 0) {
            goto err;
        }
        intf->needs_altsetting0 = 0;
    }

    error = driver->probe(intf, id);
    if (error)
        goto err;

    intf->condition = USB_INTERFACE_BOUND;

    return error;
err:
    usb_set_intfdata(intf, NULL);
    intf->condition = USB_INTERFACE_UNBOUND;

    return error;
}

static int usb_unbind_interface(struct device *dev) {
    struct usb_driver *driver = to_usb_driver(dev->driver);
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_device *udev = interface_to_usbdev(intf);

    intf->condition = USB_INTERFACE_UNBINDING;

    if (!driver->soft_unbind || udev->state == USB_STATE_NOTATTACHED) {
        usb_disable_interface(udev, intf, false);
    }

    USB_ASSERT(driver->disconnect);
    driver->disconnect(intf);

    if (intf->cur_altsetting->desc.bAlternateSetting == 0) {
        usb_enable_interface(udev, intf, false);
    } else {
        intf->needs_altsetting0 = 1;
    }
    usb_set_intfdata(intf, NULL);

    intf->condition = USB_INTERFACE_UNBOUND;
    return 0;
}

void usb_driver_release_interface(struct usb_driver *driver,
                                  struct usb_interface *iface) {
    struct device *dev = &iface->dev;

    /* this should never happen, don't release something that's not ours */
    if (!dev->driver || dev->driver != &driver->drvwrap.driver)
        return;

    /* don't release from within disconnect() */
    if (iface->condition != USB_INTERFACE_BOUND)
        return;
    iface->condition = USB_INTERFACE_UNBINDING;

    /* Release via the driver core only if the interface
     * has already been registered
     */
    if (device_is_registered(dev)) {
        device_release_driver(dev);
    } else {
        usb_unbind_interface(dev);
        dev->driver = NULL;
    }
}

void usb_forced_unbind_intf(struct usb_interface *intf) {
    struct usb_driver *driver = to_usb_driver(intf->dev.driver);

    usb_driver_release_interface(driver, intf);

    /* Mark the interface for later rebinding */
    intf->needs_binding = 1;
}


static void unbind_marked_interfaces(struct usb_device *udev) {
    struct usb_host_config *config;
    int i;
    struct usb_interface *intf;

    config = udev->actconfig;
    if (config) {
        for (i = 0; i < config->desc.bNumInterfaces; ++i) {
            intf = config->interface[i];
            if (intf->dev.driver && intf->needs_binding) {
                usb_forced_unbind_intf(intf);
            }
        }
    }
}

static void usb_rebind_intf(struct usb_interface *intf) {
    int rc;

    if (intf->dev.driver) {
        usb_forced_unbind_intf(intf);
    }

    intf->needs_binding = 0;
    rc = device_attach(&intf->dev);
    if (rc) {
        USB_LOGE("rebind failed. (rc = %d)\n", rc);
    }
}

static void rebind_marked_interfaces(struct usb_device *udev) {
    struct usb_host_config *config;
    int i;
    struct usb_interface *intf;

    config = udev->actconfig;
    if (config) {
        for (i = 0; i < config->desc.bNumInterfaces; ++i) {
            intf = config->interface[i];
            if (intf->dev.driver && intf->needs_binding) {
                usb_rebind_intf(intf);
            }
        }
    }
}

void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev) {
    unbind_marked_interfaces(udev);
    rebind_marked_interfaces(udev);
}

static int usb_probe_device(struct device *dev) {
    struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);
    struct usb_device *udev = to_usb_device(dev);
    int retval = 0;

    USB_ASSERT(udriver->probe != NULL);
    retval = udriver->probe(udev);
    return retval;
}

static int usb_unbind_device(struct device *dev) {
    struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);
    struct usb_device *udev = to_usb_device(dev);

    USB_ASSERT(udriver->disconnect != NULL);
    udriver->disconnect(udev);
    return 0;
}


/* for interface device */
int usb_register_driver(struct usb_driver *new_udriver) {
    int retval = 0;

    if (usb_disabled())
        return -ENODEV;

    new_udriver->drvwrap.for_devices = 0;
    new_udriver->drvwrap.driver.name = new_udriver->name;
    new_udriver->drvwrap.driver.bus = &usb_bus_type;
    new_udriver->drvwrap.driver.probe = usb_probe_interface;
    new_udriver->drvwrap.driver.remove = usb_unbind_interface;
    INIT_LIST_HEAD(&new_udriver->drvwrap.driver.bus_drv);
    INIT_LIST_HEAD(&new_udriver->dynids.list);

    driver_register(&new_udriver->drvwrap.driver);

    return retval;
}

/* for usb device */
int usb_register_device_driver(struct usb_device_driver *new_udriver) {
    int retval = 0;
    if (usb_disabled())
        return -ENODEV;

    new_udriver->drvwrap.for_devices = 1;
    new_udriver->drvwrap.driver.name = new_udriver->name;
    new_udriver->drvwrap.driver.bus = &usb_bus_type;
    new_udriver->drvwrap.driver.probe = usb_probe_device;
    new_udriver->drvwrap.driver.remove = usb_unbind_device;

    INIT_LIST_HEAD(&new_udriver->drvwrap.driver.bus_drv);

    driver_register(&new_udriver->drvwrap.driver);

    return retval;
}

int usb_unregister_device_driver(struct usb_device_driver *udriver) {
    if (usb_disabled())
        return -ENODEV;
    return 0;
}

/* polling bus to match the driver */
int usb_device_match(struct device *dev, const struct device_driver *drv) {
    struct usb_interface *intf;
    struct usb_driver *udrv;
    const struct usb_device_id *id;
    if (is_usb_device(dev)) {
        if (!is_usb_device_driver(drv)) {
            return 0;
        } else {
            return 1;
        }
    } else if (is_usb_interface(dev)) {
        if (is_usb_device_driver(drv)) {
            return 0;
        }

        intf = to_usb_interface(dev);
        udrv = to_usb_driver(drv);
        /* TODO here */
        id = usb_match_id(intf, udrv->id_table);
        if (id) {
            return 1;
        }

        id = usb_match_dynamic_id(intf, udrv);
        if (id) {
            return 1;
        }
    }
    return 0;
}

struct bus_type usb_bus_type = {
    "usb",              //name
    1,                  //auto_probe
    usb_device_match,   //match
};
