/**
 * @file hv_drv_UsbCoreUrb.c
 * @brief usb core urb driver
 * @details This module provide interfaces for usb urb.
 * @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_UsbErrNo.h"
#include "hv_drv_UsbCoreHcd.h"
#include "hv_drv_UsbCoreUsb.h"
#include "hv_comm_DataType.h"

int usb_submit_urb(struct urb *urb) {
    static int  pipetypes[4] = {
        PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT
    };
    int     xfertype, max;
    struct usb_device   *dev;
    struct usb_host_endpoint    *ep;
    int     is_out;
    unsigned int    allowed;

    if (!(urb && urb->complete)) {
        return -EINVAL;
    } else if (urb->hcpriv) {  
        return -EBUSY;
    }

    dev = urb->dev;
    if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED)) {
        return -ENODEV;
    }

    ep = usb_pipe_endpoint(dev, urb->pipe);
    if (!ep || ep->hcpriv) {
        return -ENOENT;
    }
    
    if (ep->hcpriv) {
        USB_ASSERT(0);  /* should NEVER happen */
    }

    dev->ep0.hcpriv = NULL;
    ep->hcpriv = NULL;

    urb->ep = ep;
    urb->status = -EINPROGRESS;
    urb->actual_length = 0;

    /* Lots of sanity checks, so HCDs can rely on clean datas */
    xfertype = usb_endpoint_type(&ep->desc);
    if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
        struct usb_ctrlrequest *setup =
            (struct usb_ctrlrequest *) urb->setup_packet;

        if (!setup)
            return -ENOEXEC;
        is_out = !(setup->bRequestType & URB_DIR_IN) || !setup->wLength;
    } else {
        is_out = usb_endpoint_dir_out(&ep->desc);
    }

    urb->transfer_flags &= ~(URB_DIR_MASK | URB_DMA_MAP_SINGLE |
                             URB_DMA_MAP_PAGE | URB_DMA_MAP_SG | URB_MAP_LOCAL |
                             URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL |
                             URB_DMA_SG_COMBINED);
    urb->transfer_flags |= (is_out ? URB_DIR_OUT : URB_DIR_IN);

    if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
            dev->state < USB_STATE_CONFIGURED) {
        return -ENODEV;
    }

    max = usb_endpoint_maxp(&ep->desc);
    if (max <= 0) {
        return -EMSGSIZE;
    }
    /* periodic transfers limit size per frame/uframe,
     * but drivers only control those sizes for ISO.
     * while we're checking, initialize return status.
     */
    if (xfertype == USB_ENDPOINT_XFER_ISOC) {
        int    n, len;

        /* SuperSpeed isoc endpoints have up to 16 bursts of up to
         * 3 packets each
         */

        if (urb->number_of_packets <= 0)
            return -EINVAL;
        for (n = 0; n < urb->number_of_packets; n++) {
            len = urb->iso_frame_desc[n].length;
            if (len < 0 || len > max)
                return -EMSGSIZE;
            urb->iso_frame_desc[n].status = -EXDEV;
            urb->iso_frame_desc[n].actual_length = 0;
        }
    }
    if (urb->transfer_buffer_length > INT_MAX) {
        return -EMSGSIZE;
    }

    if (usb_pipetype(urb->pipe) != pipetypes[xfertype]) {
        USB_LOGV("BOGUS urb xfer, pipe %x != type %x\n",
                usb_pipetype(urb->pipe), pipetypes[xfertype]);
    }

    allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT |
               URB_DIR_MASK | URB_FREE_BUFFER);
    switch (xfertype) {
    case USB_ENDPOINT_XFER_BULK:
    case USB_ENDPOINT_XFER_INT:
        if (is_out)
            allowed |= URB_ZERO_PACKET;
    case USB_ENDPOINT_XFER_CONTROL:
        allowed |= URB_NO_FSBR; /* for UHCI, no use */
    default:
        if (!is_out)
            allowed |= URB_SHORT_NOT_OK;
        break;
    case USB_ENDPOINT_XFER_ISOC:
        allowed |= URB_ISO_ASAP;
        break;
    }
    allowed &= urb->transfer_flags;

    if (allowed != urb->transfer_flags) {
        USB_LOGV("BOGUS urb flags, %x --> %x\n",
                urb->transfer_flags, allowed);
    }
    switch (xfertype) {
    case USB_ENDPOINT_XFER_ISOC:
    case USB_ENDPOINT_XFER_INT:
        /* too small? */
        if (dev->speed == USB_SPEED_WIRELESS) {
            if ((urb->interval < 6)
                    && (xfertype == USB_ENDPOINT_XFER_INT)) {
                return -EINVAL;
            }
        } else {
            if (urb->interval < 0)
                return -EINVAL;
        }

        /* too big? */
        switch (dev->speed) {
        case USB_SPEED_SUPER:
            if (urb->interval > (1 << 15)) {
                return -EINVAL;
            }
            max = 1 << 15;
            break;
        case USB_SPEED_WIRELESS:
            if (urb->interval > 16) {
                return -EINVAL;
            }
            break;
        case USB_SPEED_HIGH:    /* units are microframes */
            /* NOTE: usb handles 2^15 */
            if (urb->interval > (1024 * 8))
                urb->interval = (1024 * 8);
            max = (1024 * 8);
            break;
        case USB_SPEED_FULL:
        case USB_SPEED_LOW:
            if (xfertype == USB_ENDPOINT_XFER_INT) {
                if (urb->interval > 255) {
                    return -EINVAL;
                }
                max = 128;
            } else {
                if (urb->interval > 1024) {
                    urb->interval = 1024;
                }
                max = 1024;
            }
            break;
        default:
            return -EINVAL;
        }
        if (dev->speed != USB_SPEED_WIRELESS) {
            urb->interval = USB_MIN(max, urb->interval);
        }
    }

    urb->transfer_dma = (dma_addr_t) urb->transfer_buffer;
    urb->transfer_dma &=0x0fffffff;

    return usb_hcd_submit_urb(urb);
}

void usb_free_urb(struct urb *urb) {
    if (unlikely(!urb)) {
        return;
    } else {
        usb_hcd_free_urb(urb);
        if (urb->transfer_flags & URB_FREE_BUFFER) {
            if(urb->transfer_buffer != NULL)
            {
                USB_FREE(urb->transfer_buffer);
                urb->transfer_buffer = NULL;
            }
        }
    }

    if(urb != NULL)
    {
        USB_FREE(urb);
        urb = NULL;
    }
}

int usb_unlink_urb(struct urb *urb) {
    if (!urb)
        return -EINVAL;
    if (!urb->dev)
        return -ENODEV;
    if (!urb->ep)
        return -EIDRM;
    return usb_hcd_unlink_urb(urb, -ECONNRESET);
}

int usb_kill_urb(struct urb *urb) {
    int retval;
    if (!(urb && urb->dev)) {
        return -ENODEV;
    }

    if (!(urb->ep)) {
        return -ENOENT;
    }
    atomic_inc(&urb->reject);
    retval = usb_hcd_unlink_urb(urb, -ENOENT);
    atomic_dec(&urb->reject);
    return retval;
}

struct urb *usb_alloc_urb(void) {
    struct urb *urb;
    urb = (struct urb *) USB_MALLOC(sizeof(*urb));
    if (!urb)
        return NULL;
    urb->preferable = 0;
    HV_MEMSET((void*)urb, 0, sizeof(struct urb));

    return urb;
}
