/**
 * @file hv_drv_UsbCoreConfig.c
 * @brief usb core config
 * @details This module provide interfaces for usb configuration.
 * @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_UsbCoreUsb.h"
#include "hv_drv_UsbCoreQuirks.h"
#include "hv_drv_UsbPortingTypes.h"
#include "hv_drv_UsbCoreUtils.h"

#define USB_MAXALTSETTING       128 /* Hard limit */
#define USB_MAXENDPOINTS        30  /* Hard limit */

#define USB_MAXCONFIG           8   /* Arbitrary limit */

INLINE const char *plural(int n) {
    return (n == 1 ? "" : "s");
}

static int find_next_descriptor(unsigned char *buffer, int size,
                                int dt1, int dt2, int *num_skipped) {
    struct usb_descriptor_header *h;
    int n = 0;
    unsigned char *buffer0 = buffer;

    /* Find the next descriptor of type dt1 or dt2 */
    while (size > 0) {
        h = (struct usb_descriptor_header *) buffer;
        if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2)
            break;
        buffer += h->bLength;
        size -= h->bLength;
        ++n;
    }

    /* Store the number of descriptors skipped and return the
     * number of bytes skipped */
    if (num_skipped)
        *num_skipped = n;
    return buffer - buffer0;
}

static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
                              int asnum, struct usb_host_interface *ifp, int num_ep,
                              unsigned char *buffer, int size) {
    unsigned char *buffer0 = buffer;
    struct usb_endpoint_descriptor *d;
    struct usb_host_endpoint *endpoint;
    int n, i, j, retval;

    d = (struct usb_endpoint_descriptor *) buffer;
    buffer += d->bLength;
    size -= d->bLength;

    if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)
        n = USB_DT_ENDPOINT_AUDIO_SIZE;
    else if (d->bLength >= USB_DT_ENDPOINT_SIZE)
        n = USB_DT_ENDPOINT_SIZE;
    else {
        goto skip_to_next_endpoint_or_interface_descriptor;
    }

    i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK;
    if (i >= 16 || i == 0) {
        goto skip_to_next_endpoint_or_interface_descriptor;
    }

    /* Only store as many endpoints as we have room for */
    if (ifp->desc.bNumEndpoints >= num_ep)
        goto skip_to_next_endpoint_or_interface_descriptor;

    endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints];
    ++ifp->desc.bNumEndpoints;

    USB_MEMCPY(&endpoint->desc, d, n);

    USB_LOGI("Dev[%d], Config[%d], Interface[%d], EP[%d] addr:0x%x, type:%d, wMaxPacketSize:%d, Interval:%d",
            to_usb_device(ddev)->devnum,
            cfgno,
            inum,
            ifp->desc.bNumEndpoints - 1,
            endpoint->desc.bEndpointAddress,
            endpoint->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK,
            endpoint->desc.wMaxPacketSize,
            endpoint->desc.bInterval
           );
    /* Fix up bInterval values outside the legal range. Use 32 ms if no
     * proper value can be guessed. */
    i = 0;        /* i = min, j = max, n = default */
    j = 255;
    if (usb_endpoint_xfer_int(d)) {
        i = 1;
        switch (to_usb_device(ddev)->speed) {
        case USB_SPEED_SUPER:
        case USB_SPEED_HIGH:
            /* Many device manufacturers are using full-speed
             * bInterval values in high-speed interrupt endpoint
             * descriptors. Try to fix those and fall back to a
             * 32 ms default value otherwise. */
            n = fls(d->bInterval*8);
            if (n == 0)
                n = 9;    /* 32 ms = 2^(9-1) uframes */
            j = 16;

            /*
             * Adjust bInterval for quirked devices.
             * This quirk fixes bIntervals reported in
             * linear microframes.
             */
            if (to_usb_device(ddev)->quirks &
                    USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL) {
                n = USB_CLAMP(fls(d->bInterval), i, j);
                i = j = n;
            }
            break;
        default:        /* USB_SPEED_FULL or _LOW */
            /* For low-speed, 10 ms is the official minimum.
             * But some "overclocked" devices might want faster
             * polling so we'll allow it. */
            n = 32;
            break;
        }
    } else if (usb_endpoint_xfer_isoc(d)) {
        i = 1;
        j = 16;
        switch (to_usb_device(ddev)->speed) {
        case USB_SPEED_HIGH:
            n = 9;        /* 32 ms = 2^(9-1) uframes */
            break;
        default:        /* USB_SPEED_FULL */
            n = 6;        /* 32 ms = 2^(6-1) frames */
            break;
        }
    }
    if (d->bInterval < i || d->bInterval > j) {
        endpoint->desc.bInterval = n;
    }

    /* Some buggy low-speed devices have Bulk endpoints, which is
     * explicitly forbidden by the USB spec.  In an attempt to make
     * them usable, we will try treating them as Interrupt endpoints.
     */
    if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&
            usb_endpoint_xfer_bulk(d)) {
        endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;
        endpoint->desc.bInterval = 1;
        if (usb_endpoint_maxp(&endpoint->desc) > 8)
            endpoint->desc.wMaxPacketSize = 8;
    }

    /*
     * Some buggy high speed devices have bulk endpoints using
     * maxpacket sizes other than 512.  High speed HCDs may not
     * be able to handle that particular bug, so let's warn...
     */
    if (to_usb_device(ddev)->speed == USB_SPEED_HIGH
            && usb_endpoint_xfer_bulk(d)) {
        unsigned maxp;

        maxp = usb_endpoint_maxp(&endpoint->desc) & 0x07ff;
        if (maxp != 512) {
            USB_LOGV("config %d interface %d altsetting %d "
                    "bulk endpoint 0x%X has invalid maxpacket %d\n",
                    cfgno, inum, asnum, d->bEndpointAddress,
                    maxp);
        }
    }

    /* Parse a possible SuperSpeed endpoint companion descriptor */
    if (to_usb_device(ddev)->speed == USB_SPEED_SUPER) {
        /* usb_parse_ss_endpoint_companion(ddev, cfgno,
                inum, asnum, endpoint, buffer, size);
           not support now.
        */
    }

    /* Skip over any Class Specific or Vendor Specific descriptors;
     * find the next endpoint or interface descriptor */
    endpoint->extra = buffer;
    i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
                             USB_DT_INTERFACE, &n);
    endpoint->extralen = i;
    retval = buffer - buffer0 + i;
    if (n > 0) {
        USB_LOGV("skipped %d descriptor%s after %s\n",
                n, plural(n), "endpoint");
    }

    return retval;

skip_to_next_endpoint_or_interface_descriptor:
    i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
                             USB_DT_INTERFACE, NULL);
    return buffer - buffer0 + i;
}

void usb_release_interface_cache(struct usb_interface_cache *intfc) {
    unsigned j;
    for (j = 0; j < intfc->num_altsetting; j++) {
        struct usb_host_interface *alt = &intfc->altsetting[j];

        if(alt->endpoint != NULL)
        {
            USB_FREE(alt->endpoint);
            alt->endpoint = NULL;
        }
        if(alt->string != NULL)
        {
            USB_FREE(alt->string);
            alt->string = NULL;
        }
    }
    if(intfc != NULL)
    {
        USB_FREE(intfc);
        intfc = NULL;
    }
}

static int usb_parse_interface(struct device *ddev, int cfgno,
                               struct usb_host_config *config, unsigned char *buffer, int size,
                               u8 inums[], u8 nalts[]) {
    unsigned char *buffer0 = buffer;
    struct usb_interface_descriptor    *d;
    int inum, asnum;
    struct usb_interface_cache *intfc;
    struct usb_host_interface *alt;
    unsigned i;
    int n;
    int len, retval;
    int num_ep, num_ep_orig;

    d = (struct usb_interface_descriptor *) buffer;

    USB_LOGI("Dev[%d], Config[%d], Interface[%d], bNumEndpoints:%d, Alt:%d, Class:%d, SubClass:%d, Protocol:%d",
            to_usb_device(ddev)->devnum,
            cfgno,
            d->bInterfaceNumber,
            d->bNumEndpoints,
            d->bAlternateSetting,
            d->bInterfaceClass,
            d->bInterfaceSubClass,
            d->bInterfaceProtocol);

    //当没有内接hub时，在这里设置接入设备的标识符，非U盘则后续不执行操作
    Hv_Drv_USB_SetDeviceType(d->bInterfaceClass, d->bInterfaceSubClass, d->bInterfaceProtocol);

    buffer += d->bLength;
    size -= d->bLength;

    if (d->bLength < USB_DT_INTERFACE_SIZE)
        goto skip_to_next_interface_descriptor;

    /* Which interface entry is this? */
    intfc = NULL;
    inum = d->bInterfaceNumber;
    for (i = 0; i < config->desc.bNumInterfaces; ++i) {
        if (inums[i] == inum) {
            intfc = config->intf_cache[i];
            break;
        }
    }
    if (!intfc || intfc->num_altsetting >= nalts[i])
        goto skip_to_next_interface_descriptor;

    /* Check for duplicate altsetting entries */
    asnum = d->bAlternateSetting;
    for ((i = 0, alt = &intfc->altsetting[0]);
            i < intfc->num_altsetting;
            (++i, ++alt)) {
        if (alt->desc.bAlternateSetting == asnum) {
            goto skip_to_next_interface_descriptor;
        }
    }

    ++intfc->num_altsetting;
    USB_MEMCPY(&alt->desc, d, USB_DT_INTERFACE_SIZE);

    /* Skip over any Class Specific or Vendor Specific descriptors;
     * find the first endpoint or interface descriptor */
    alt->extra = buffer;
    i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
                             USB_DT_INTERFACE, &n);
    alt->extralen = i;
    if (n > 0) {
        USB_LOGV("skipped %d descriptor after %s\n", n, "interface");
    }
    buffer += i;
    size -= i;

    /* Allocate space for the right(?) number of endpoints */
    num_ep = num_ep_orig = alt->desc.bNumEndpoints;
    alt->desc.bNumEndpoints = 0;        /* Use as a counter */
    if (num_ep > USB_MAXENDPOINTS) {
        num_ep = USB_MAXENDPOINTS;
    }

    if (num_ep > 0) {
        /* Can't allocate 0 bytes */
        len = sizeof(struct usb_host_endpoint) * num_ep;
        alt->endpoint = USB_MALLOC(len);
        if (!alt->endpoint)
            return -ENOMEM;
        HV_MEMSET((void*)(alt->endpoint), 0, len);
    }

    /* Parse all the endpoint descriptors */
    n = 0;
    while (size > 0) {
        if (((struct usb_descriptor_header *) buffer)->bDescriptorType
                == USB_DT_INTERFACE)
            break;
        retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt,
                                    num_ep, buffer, size);
        if (retval < 0)
            return retval;
        ++n;

        buffer += retval;
        size -= retval;
    }

    if (n != num_ep_orig)
        USB_LOGI("config %d interface %d altsetting %d has %d "
                "endpoint descriptor%s, different from the interface "
                "descriptor's value: %d\n",
                cfgno, inum, asnum, n, plural(n), num_ep_orig);
    return buffer - buffer0;

skip_to_next_interface_descriptor:
    i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
                             USB_DT_INTERFACE, NULL);
    return buffer - buffer0 + i;
}

static int usb_parse_configuration(struct usb_device *udev, int cfgidx,
                                   struct usb_host_config *config, unsigned char *buffer, int size) {
    struct device *ddev = &udev->dev;
    unsigned char *buffer0 = buffer;
    int cfgno;
    int nintf, nintf_orig;
    int i, j, n;
    struct usb_interface_cache *intfc;
    unsigned char *buffer2;
    int size2;
    struct usb_descriptor_header *header;
    int len, retval;
    u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
    unsigned iad_num = 0;

    USB_MEMCPY(&config->desc, buffer, USB_DT_CONFIG_SIZE);
    if (config->desc.bDescriptorType != USB_DT_CONFIG ||
            config->desc.bLength < USB_DT_CONFIG_SIZE ||
            config->desc.bLength > size) {
        USB_LOGE("invalid descriptor for config index %d: "
                "type = 0x%X, length = %d\n", cfgidx,
                config->desc.bDescriptorType, config->desc.bLength);
        return -EINVAL;
    }
    cfgno = config->desc.bConfigurationValue;

    buffer += config->desc.bLength;
    size -= config->desc.bLength;

    nintf = nintf_orig = config->desc.bNumInterfaces;
    if (nintf > USB_MAXINTERFACES) {
        nintf = USB_MAXINTERFACES;
    }

    /* Go through the descriptors, checking their length and counting the
     * number of altsettings for each interface */
    n = 0;
    for (buffer2 = buffer, size2 = size;
            size2 > 0;
            buffer2 += header->bLength, size2 -= header->bLength) {
        if (size2 < sizeof(struct usb_descriptor_header)) {
            break;
        }

        header = (struct usb_descriptor_header *) buffer2;
        if ((header->bLength > size2) || (header->bLength < 2)) {
            break;
        }

        if (header->bDescriptorType == USB_DT_INTERFACE) {
            struct usb_interface_descriptor *d;
            int inum;

            d = (struct usb_interface_descriptor *) header;
            if (d->bLength < USB_DT_INTERFACE_SIZE) {
                continue;
            }

            inum = d->bInterfaceNumber;
            if ((udev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) &&
                    n >= nintf_orig) {
                continue;
            }

            if (inum >= nintf_orig)
                USB_LOGV("config %d has an invalid "
                        "interface number: %d but max is %d\n",
                        cfgno, inum, nintf_orig - 1);

            /* Have we already encountered this interface?
             * Count its altsettings */
            for (i = 0; i < n; ++i) {
                if (inums[i] == inum)
                    break;
            }
            if (i < n) {
                if (nalts[i] < 255)
                    ++nalts[i];
            } else if (n < USB_MAXINTERFACES) {
                inums[n] = inum;
                nalts[n] = 1;
                ++n;
            }

        } else if (header->bDescriptorType ==
                   USB_DT_INTERFACE_ASSOCIATION) {
            if (iad_num == USB_MAXIADS) {
            } else {
                config->intf_assoc[iad_num] =
                    (struct usb_interface_assoc_descriptor
                     *)header;
                iad_num++;
            }

        } else if (header->bDescriptorType == USB_DT_DEVICE ||
                   header->bDescriptorType == USB_DT_CONFIG)
            USB_LOGV("config %d contains an unexpected "
                    "descriptor of type 0x%X, skipping\n",
                    cfgno, header->bDescriptorType);

    }    /* for ((buffer2 = buffer, size2 = size); ...) */
    size = buffer2 - buffer;
    config->desc.wTotalLength = buffer2 - buffer0;

    if (n != nintf)
        USB_LOGV("config %d has %d interface%s, different from "
                "the descriptor's value: %d\n",
                cfgno, n, plural(n), nintf_orig);
    else if (n == 0)
        USB_LOGV("config %d has no interfaces?\n", cfgno);
    config->desc.bNumInterfaces = nintf = n;

    /* Check for missing interface numbers */
    for (i = 0; i < nintf; ++i) {
        for (j = 0; j < nintf; ++j) {
            if (inums[j] == i)
                break;
        }
        if (j >= nintf)
            USB_LOGV("config %d has no interface number "
                    "%d\n", cfgno, i);
    }

    /* Allocate the usb_interface_caches and altsetting arrays */
    for (i = 0; i < nintf; ++i) {
        j = nalts[i];
        if (j > USB_MAXALTSETTING) {
            USB_LOGV("too many alternate settings for "
                    "config %d interface %d: %d, "
                    "using maximum allowed: %d\n",
                    cfgno, inums[i], j, USB_MAXALTSETTING);
            nalts[i] = j = USB_MAXALTSETTING;
        }

        len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;
        config->intf_cache[i] = intfc = USB_MALLOC(len);
        if (!intfc)
            return -ENOMEM;
        HV_MEMSET((void*)(intfc), 0, len);
    }

    /* FIXME: parse the BOS descriptor */

    /* Skip over any Class Specific or Vendor Specific descriptors;
     * find the first interface descriptor */
    config->extra = buffer;
    i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
                             USB_DT_INTERFACE, &n);
    config->extralen = i;
    if (n > 0)
        USB_LOGV("skipped %d descriptor%s after %s\n",
                n, plural(n), "configuration");
    buffer += i;
    size -= i;

    /* Parse all the interface/altsetting descriptors */
    while (size > 0) {
        retval = usb_parse_interface(ddev, cfgno, config,
                                     buffer, size, inums, nalts);
        if (retval < 0)
            return retval;

        buffer += retval;
        size -= retval;
    }

    /* Check for missing altsettings */
    for (i = 0; i < nintf; ++i) {
        intfc = config->intf_cache[i];
        for (j = 0; j < (int)intfc->num_altsetting; ++j) {
            for (n = 0; n < (int)intfc->num_altsetting; ++n) {
                if (intfc->altsetting[n].desc.
                        bAlternateSetting == j)
                    break;
            }
            if (n >= (int)intfc->num_altsetting) {
                USB_LOGV("config %d interface %d has no "
                        "altsetting %d\n", cfgno, inums[i], j);
            }
        }
    }

    return 0;
}

void usb_destroy_configuration(struct usb_device *udev) {
    int c, i;

    if (!udev->config)
        return;

    if (udev->rawdescriptors) {
        for (i = 0; i < udev->descriptor.bNumConfigurations; i++)
            if(udev->rawdescriptors[i] != NULL)
            {
                USB_FREE(udev->rawdescriptors[i]);
                udev->rawdescriptors[i] = NULL;
            }

        if(udev->rawdescriptors != NULL)
        {
            USB_FREE(udev->rawdescriptors);
            udev->rawdescriptors = NULL;
        }
    }

    for (c = 0; c < udev->descriptor.bNumConfigurations; c++) {
        struct usb_host_config *cf = &udev->config[c];

        if (cf) {
            if(cf->string != NULL)
            {
                USB_FREE(cf->string);
                cf->string = NULL;
            }

            for (i = 0; i < cf->desc.bNumInterfaces; i++) {
                if (cf->intf_cache[i])
                    usb_release_interface_cache(cf->intf_cache[i]);
            }
        }
    }

    if(udev->config != NULL)
    {
        USB_FREE(udev->config);
        udev->config = NULL;
    }
}

/*
 * Get the USB config descriptors, cache and parse'em
 *
 * hub-only!! ... and only in reset path, or usb_new_device()
 * (used by real hubs and virtual root hubs)
 */
int usb_get_configuration(struct usb_device *udev) {
    int ncfg = udev->descriptor.bNumConfigurations;
    int result = 0;
    //unsigned int cfgno, length;
    unsigned int cfgno;
    int length; //modify for tscancode
    unsigned char *bigbuffer;
    struct usb_config_descriptor *desc;

    cfgno = 0;
    result = -ENOMEM;
    if (ncfg > USB_MAXCONFIG) {
        udev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
    }

    if (ncfg < 1) {
        return -EINVAL;
    }

    length = ncfg * sizeof(struct usb_host_config);
    udev->config = (struct usb_host_config *)USB_MALLOC(length);
    if (!udev->config)
        goto err2;
    HV_MEMSET((void*)(udev->config), 0, length);

    length = ncfg * sizeof(char *);
    udev->rawdescriptors = (char **)USB_MALLOC(length);
    if (!udev->rawdescriptors) {
        if(udev->config != NULL)
        {
            USB_FREE(udev->config);
            udev->config = NULL;
        }
        goto err2;
    }
    HV_MEMSET((void*)(udev->rawdescriptors), 0, length);

    desc = (struct usb_config_descriptor *)USB_MALLOC(USB_DT_CONFIG_SIZE);
    if (!desc) {
        if(udev->config != NULL)
        {
            USB_FREE(udev->config);
            udev->config = NULL;
        }
        if(udev->rawdescriptors != NULL)
        {
            USB_FREE(udev->rawdescriptors);
            udev->rawdescriptors = NULL;
        }

        goto err2;
    }
    HV_MEMSET((void*)(desc), 0, USB_DT_CONFIG_SIZE);

    result = 0;
    for (; (int)cfgno < ncfg; cfgno++) {

        /* We grab just the first descriptor so we know how long
        * the whole configuration is */
        result = usb_get_descriptor(udev, USB_DT_CONFIG, cfgno,
                                    desc, USB_DT_CONFIG_SIZE);
        if (result < 0) {
            if (result != -EPIPE)
                goto err;
            udev->descriptor.bNumConfigurations = cfgno;
            break;
        } else if (result < 4) {
            result = -EINVAL;
            goto err;
        }

        length = USB_MAX((int) (desc->wTotalLength), USB_DT_CONFIG_SIZE);

        /* Now that we know the length, get the whole thing */
        bigbuffer = (unsigned char *)USB_MALLOC(length);
        if (!bigbuffer) {
            result = -ENOMEM;
            goto err;
        }
        HV_MEMSET((void*)(bigbuffer), 0, length);

        if (udev->quirks & USB_QUIRK_DELAY_INIT) {
            ;   /* FIXME: delay 100 ms */
        }

        result = usb_get_descriptor(udev, USB_DT_CONFIG, cfgno, bigbuffer, length);

        if (result < 0) {
            if(bigbuffer != NULL)
            {
                USB_FREE(bigbuffer);
                bigbuffer = NULL;
            }
            goto err;
        }

        if (result < (int)length) {
            length = result;
        }

        udev->rawdescriptors[cfgno] = (char*)bigbuffer;

        result = usb_parse_configuration(udev, cfgno,
                                         &udev->config[cfgno], bigbuffer, length);
        if (result < 0) {
            ++cfgno;
            goto err;
        }
    }

    result = 0;

err:
    if(desc != NULL)
    {
        USB_FREE(desc);
        desc = NULL;
    }
    udev->descriptor.bNumConfigurations = cfgno;
err2:
    if (result == -ENOMEM)
        USB_LOGE("out of memory\n");
    return result;
}
