/**
 * @file hv_drv_UsbStorageUmass.c
 * @brief This is usb driver for umass.
 * @details 
 * @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_UsbStorageUmass.h"
#include "hv_drv_UsbStorageScsi.h"
#include "hv_drv_UsbCoreUsb.h"
#include "hv_drv_UsbStorage.h"
#include "hv_drv_UsbStorageTransport.h"
#include "hv_drv_UsbStorageProtocol.h"
#include "hv_drv_UsbBitWise.h"


static const struct usb_device_id usb_storage_id_table[] = {
    /* Control/Bulk transport for all SubClass values */
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_RBC, USB_PR_CB) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8020, USB_PR_CB) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_QIC, USB_PR_CB) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_UFI, USB_PR_CB) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8070, USB_PR_CB) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_CB) },

    /* Control/Bulk/Interrupt transport for all SubClass values */
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_RBC, USB_PR_CBI) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8020, USB_PR_CBI) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_QIC, USB_PR_CBI) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_UFI, USB_PR_CBI) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8070, USB_PR_CBI) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_CBI) },

    /* Bulk-only transport for all SubClass values */
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_RBC, USB_PR_BULK) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8020, USB_PR_BULK) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_QIC, USB_PR_BULK) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_UFI, USB_PR_BULK) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_8070, USB_PR_BULK) },
    { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) },

    /* Terminating entry */
    { 0, }
};

/* Get the transport settings */
static void get_transport(struct us_data *us) {
    switch (us->protocol) {
    case USB_PR_CB:
        break;
    case USB_PR_CBI:
        break;
    case USB_PR_BULK:
        us->transport_name = "Bulk";
        us->transport = usb_stor_bulk_transport;
        us->transport_reset = usb_stor_bulk_reset;
        break;
    }
}

/* Get the protocol settings */
static void get_protocol(struct us_data *us) {
    switch (us->subclass) {
    case USB_SC_RBC:
    case USB_SC_8020:
    case USB_SC_QIC:
    case USB_SC_UFI:
    default:
        USB_LOGE("%s, do not support %d protocol.\n", __FUNCTION__, us->subclass);
        break;
    case USB_SC_8070:
        us->protocol_name = "8087i";
        us->proto_handler = usb_stor_pad12_command;
        break;
    case USB_SC_SCSI:
        us->protocol_name = "Transparent SCSI";
        us->proto_handler = usb_stor_transparent_scsi_command;
        break;
    }
}

static int associate_dev(struct us_data *us, struct usb_interface *intf) {
    us->pusb_dev = interface_to_usbdev(intf);
    us->pusb_intf = intf;
    us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber;

    usb_set_intfdata(intf, us);
    return 0;
}

static int get_device_info(struct us_data *us,
                           const struct usb_device_id *id,
                           struct us_unusual_dev *unusual_dev) {
    struct usb_device *udev = us->pusb_dev;
    struct usb_interface_descriptor *idesc =
            &us->pusb_intf->cur_altsetting->desc;

    if (unusual_dev != NULL) {
        ;   /* TODO */
    }

    us->subclass = idesc->bInterfaceSubClass;
    us->protocol = idesc->bInterfaceProtocol;
    us->fflags = id->driver_info;
//      lsh del high speed
    if (udev->speed != USB_SPEED_HIGH) {
        us->fflags &= ~US_FL_GO_SLOW;
    }

    return 0;
}

static int usb_stor_scan(struct us_data *us) {
    int result = -ENODEV;
    struct scsi_device *scsi_dev = us_to_scsi_dev(us);
    if (us->protocol == USB_PR_BULK) {
        us->max_lun = usb_stor_bulk_max_lun(us);
    }
    scsi_dev->lun = us->max_lun + (us->max_lun >= 8 ? 1 : 0);
    result = scsi_scan(scsi_dev);
    if (result) {
        USB_LOGE("can not recognize the scsi device,"
                "(error = %d)\n", result);
    }

    return result;
}

static void dissociate_dev(struct us_data *us) {
    usb_set_intfdata(us->pusb_intf, NULL);
}

static void quiesce_and_remove_scsi(struct us_data *us) {
    struct scsi_device *sdev = us_to_scsi_dev(us);

    set_bit(US_FLIDX_DISCONNECTING, &us->dflags);
    set_bit(US_FLIDX_ABORTING, &us->dflags);
    if (sdev) {
        scsi_remove(sdev);
    }
}

static void release_everything(struct us_data *us) {
    dissociate_dev(us);
    usb_free_urb(us->current_urb);
    if(us != NULL)
    {
        USB_FREE(us);
        us = NULL;
    }
}

static int usb_stor_probe1(struct us_data *us,
                           struct usb_interface *intf,
                           const struct usb_device_id *id,
                           struct us_unusual_dev *unusual_dev) {
    struct scsi_device *scsi_dev = NULL;
    int result;
    scsi_dev = create_scsi_device(us);
    if (scsi_dev == NULL) {
        return -ENOMEM;
    }

    result = -ENODEV;
    result = associate_dev(us, intf);
    if (result) {
        goto BadDevice;
    }

    result = get_device_info(us, id, unusual_dev);
    if (result) {
        goto BadDevice;
    }

    get_transport(us);
    get_protocol(us);
    us->scsi = scsi_dev;

    return 0;

BadDevice:
    quiesce_and_remove_scsi(us);
    release_everything(us);
    return result;
}

static int get_pipes(struct us_data *us) {
    int i;
    struct usb_host_interface *altsetting =
            us->pusb_intf->cur_altsetting;
    struct usb_endpoint_descriptor *ep;
    struct usb_endpoint_descriptor *ep_in = NULL;
    struct usb_endpoint_descriptor *ep_out = NULL;
    struct usb_endpoint_descriptor *ep_int = NULL;

    for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {
        ep = &altsetting->endpoint[i].desc;

        if (usb_endpoint_xfer_bulk(ep)) {
            if (usb_endpoint_dir_in(ep)) {
                if (!ep_in)
                    ep_in = ep;
            } else {
                if (!ep_out)
                    ep_out = ep;
            }
        } else if (usb_endpoint_is_int_in(ep)) {
            if (!ep_int)
                ep_int = ep;
        }
    }

    if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) {
        return -EIO;
    }
    us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0);
    us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0);
    us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev, usb_endpoint_num(ep_out));
    us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev, usb_endpoint_num(ep_in));
    if (ep_int) {
        us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev, usb_endpoint_num(ep_int));
        us->ep_bInterval = ep_int->bInterval;
    }

    return 0;
}

static int usb_stor_probe2(struct us_data *us) {
    int result;

    if (!us->transport || !us->proto_handler) {
        result = -ENXIO;
        goto BadDevice;
    }

    result = get_pipes(us);
    if (result) {
        goto BadDevice;
    }

    if (us->fflags & US_FL_INITIAL_READ10) {
        set_bit(US_FLIDX_REDO_READ10, &us->dflags);
    }

    us->current_urb = usb_alloc_urb();
    if (!us->current_urb) {
        result = -ENOMEM;
        goto BadDevice;
    }

    set_bit(US_FLIDX_SCAN_PENDING, &us->dflags);
    result = usb_stor_scan(us);
    clear_bit(US_FLIDX_SCAN_PENDING, &us->dflags);
    if (result) {
        goto BadDevice;
    }

    return 0;

BadDevice:
    quiesce_and_remove_scsi(us);
    release_everything(us);
    return result;
}

static int storage_probe(struct usb_interface *intf,
                         const struct usb_device_id *id) {
    struct us_unusual_dev *unusual_dev = NULL;
    struct us_data *us;
    int result;

    us = (struct us_data*)USB_MALLOC(sizeof (*us));

    HV_MEMSET((void*)us, 0, sizeof(struct us_data));
    us->tag = 0x01;
    result = usb_stor_probe1(us, intf, id, unusual_dev);
    if (result)
        return result;

    result = usb_stor_probe2(us);
    return result;
}

static void usb_stor_disconnect(struct usb_interface *intf) {
    struct us_data *us = (struct us_data *) usb_get_intfdata(intf);
    if (!us) {
        return;
    }

    quiesce_and_remove_scsi(us);
    release_everything(us);
}

static int usb_stor_pre_reset(struct usb_interface *intf) {
    struct us_data *us = (struct us_data *)usb_get_intfdata(intf);
    //us = us;    /* remember lock this device */

    return 0;
}

static int usb_stor_post_reset(struct usb_interface *intf) {
    struct us_data *us = (struct us_data *)usb_get_intfdata(intf);
    //us = us; /* remember unlock this device */

    return 0;
}

static struct usb_driver usb_storage_driver = {
    "usb-storage",       //name
    storage_probe,       //probe
    usb_stor_disconnect, //disconnect
    usb_stor_pre_reset,  //pre_reset
    usb_stor_post_reset, //post_reset
    usb_storage_id_table,//id_table
    {{NULL, NULL}},      //dynids
    {{0, 0}},            //drvwrap
    1,                   //soft_unbind
};

int usb_mass_init(void) {
    usb_register_driver(&usb_storage_driver);
    return 0;
}

void usb_mass_exit(void) {
}
