/**
 * @file hv_drv_UsbStorageScsi.c
 * @brief This is usb scsi driver.
 * @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_UsbStorageScsi.h"
#include "hv_drv_UsbStorageUmass.h"
#include "hv_drv_UsbStorageScsiInternal.h"
#include "hv_drv_UsbErrNo.h"


struct scsi_device *create_scsi_device(struct us_data *us) {
    struct scsi_device *dev = NULL;
    struct scsi_extra *extra = NULL;

    dev = (struct scsi_device *) USB_MALLOC(sizeof(*dev));
    extra = (struct scsi_extra *) USB_MALLOC(sizeof(*extra));
    if (dev == NULL || extra == NULL) {
        return NULL;
    }
    HV_MEMSET((void*)dev, 0, sizeof(*dev));
    HV_MEMSET((void*)extra, 0, sizeof(*extra));

    extra->us = us;
    extra->prepare_period = 0;
    extra->error_cnt = 0;
    extra->busy_cnt = 0;
    extra->mount_failed_cnt = 0;
    scsi_set_extra(dev, (void *) extra);

    associate_scsi_device(dev);

    dev->lun = us->max_lun;
    dev->media_type = SCSI_MEDIUM_UNKNOWN;
    dev->device_type = SCSI_DEVICE_UNKNOWN;
    dev->flags.rmb = 1;
    dev->flags.connected = 1;
    dev->flags.medium_present = 0;
    dev->flags.device_removed = 0;
    dev->flags.fsdmounted = 0;
    dev->flags.powered_down = 0;
    dev->flags.open = 0;

    return dev;
}

static int identify_scsi_device(struct scsi_device *dev) {
    int retval;

    retval = dev->ops->inquiry(dev);
    if (retval) {
        return retval;
    }

    retval = dev->ops->test_unit_ready(dev);
    if (retval) {
        USB_MSleep(2000);
    }

    retval = dev->ops->inquiry(dev);
    if (retval) {
    }

    return retval;
}

int reidentify_scsi_device(struct scsi_device *dev) {
    int result;

    result = dev->ops->inquiry(dev);
    if (result) {
        return result;
    }

    result = dev->ops->read_capacity(dev);
    if (result) {
        USB_LOGE("%s, read capacity failed.\n", __FUNCTION__);
    }

    return result;
}

#define MOUNT_FAILED_IGNORE 4
#define BUSY_MAX_BEAR       4
#define DEVICE_MAX_STRIKE   3
static void check_ready_proc(unsigned long data) {
    int result;
    u8  flag;
    struct us_data *us;
    struct scsi_device  *dev = (struct scsi_device *) data;
    struct scsi_extra   *extra = (struct scsi_extra *) scsi_get_extra(dev);
    if (dev == NULL || extra == NULL) {
        return;
    }
    
    us = extra->us;
    if (us == NULL) {
        return;
    }

    result = dev->ops->test_unit_ready(dev);
    if (result == 0) {
        extra->busy_cnt = 0;
        extra->error_cnt = 0;
    } else if (result == -EBUSY) {
        extra->busy_cnt += 1;
        extra->error_cnt = 0;
    } else {
        extra->error_cnt += 1;
    }

    flag = ((extra->busy_cnt > BUSY_MAX_BEAR) << 4)
           | ((extra->error_cnt > DEVICE_MAX_STRIKE) << 5)
           | (extra->busy_cnt ? 1 : 0)
           | (extra->error_cnt ? 1: 0);

    if (flag) {
        if (!dev->flags.fsdmounted) {
            extra->prepare_period += 1;
        } else if (flag & 0x30) {
            extra->prepare_period = 0;
            detach_block_dev(dev);
        }
    } else if (dev->flags.fsdmounted && !dev->flags.medium_present) {
        (void) dev->ops->read_capacity(dev);
        if (!dev->flags.medium_present) {
            detach_block_dev(dev);
        }
    } else if (!dev->flags.fsdmounted) {
        result = reidentify_scsi_device(dev);
        if (result) {
            USB_LOGE("identify scsi device failed before mount. (result is %d)\n", result);
            extra->mount_failed_cnt += 1;
        } else {
            result = attach_block_dev(dev);
            if (result) {
                USB_LOGE("mount failed.\n");
                extra->mount_failed_cnt += 1;
            } else {
                extra->mount_failed_cnt = 0;
                //Detect checking timer for test
            }
        }
    }

    flag |= (extra->mount_failed_cnt > MOUNT_FAILED_IGNORE) << 6;
    if (flag & 0x60) {
        USB_LOGE("Give up this device!\n");
    }

}

int scsi_scan(struct scsi_device *dev) {
    int result;
    struct scsi_extra *extra;
    result = identify_scsi_device(dev);
    if (result) {
        return result;
    }

    extra = (struct scsi_extra *) dev->extra;
    check_ready_proc((unsigned long) dev);

    return 0;
}

void scsi_remove(struct scsi_device *dev) {
    struct scsi_extra *extra = (struct scsi_extra *) scsi_get_extra(dev);
    struct us_data *us = NULL;
    if (extra) {
        us = extra->us;
        if(extra != NULL)
        {
            USB_FREE(extra);
            extra = NULL;
        }
    }
    dev->extra = NULL;
    usb_stor_stop_transport(us);
    detach_block_dev(dev);
    if(dev != NULL)
    {
        USB_FREE(dev);
        dev = NULL;
    }
}
