/**
 * @file hv_drv_UsbStorageScsiDisk.c
 * @brief This is usb scsi disk 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_UsbPortingOsDisk.h"
#include "hv_drv_UsbStorageScsiInternal.h"
#include "hv_drv_UsbPortingOsDevtab.h"

typedef struct _scsi_disk_info_t {
    struct scsi_device  *dev;
    int num;
    u32 cylinders_num;
    u32 heads_num;
    u32 sectors_num;
    u32 bytes_per_sector;
    u32 size;
} scsi_disk_info_t;

typedef struct _scsi_controller_t {
    int dummy;
} scsi_controller_t;

static boolean scsi_disk_init(struct os_devtab_entry *tab);

static os_errno scsi_disk_read(disk_channel *chan,
                               void         *buf,
                               u32    len,
                               u32    block_num);

static os_errno scsi_disk_write(disk_channel *chan,
                                const void   *buf,
                                u32    len,
                                u32    block_num);

static os_errno scsi_disk_get_config(disk_channel *chan,
                                     u32    key,
                                     const void   *xbuf,
                                     u32   *len);

static os_errno scsi_disk_set_config(disk_channel *chan,
                                     u32    key,
                                     const void   *xbuf,
                                     u32   *len);

static os_errno scsi_disk_lookup(struct os_devtab_entry  **tab,
                                 struct os_devtab_entry   *sub_tab,
                                 const char                *name);


DISK_FUNS(scsi_disk_funs,
          scsi_disk_read,
          scsi_disk_write,
          scsi_disk_get_config,
          scsi_disk_set_config
         );

static scsi_disk_info_t scsi_disk_info0;

static scsi_controller_t scsi_controller_0;

DISK_CONTROLLER(scsi_disk_controller_0, scsi_controller_0);

DISK_CHANNEL(scsi_disk_channel0,
             scsi_disk_funs,
             scsi_disk_info0,
             scsi_disk_controller_0,
             true,
             4
            );
#define SCSI_DISK_DEVNAME "/dev/scsi0/"

BLOCK_DEVTAB_ENTRY(scsi_disk_io0,
                   SCSI_DISK_DEVNAME,    //name
                   0,                    //dep_name
                   NULL,                 //os_devio_table_t handlers
                   scsi_disk_init,       //init
                   scsi_disk_lookup,     //lookup
                   &scsi_disk_channel0   //priv
                  );

static boolean scsi_disk_init(struct os_devtab_entry *tab) {
    disk_channel *chan = (disk_channel *) tab->priv;
    scsi_disk_info_t *scsi_info = (scsi_disk_info_t *) chan->dev_priv;
    boolean result = true;
    if (chan->init)
        return true;
    if (!(chan->callbacks->disk_init)(tab))
        return false;
    return result;
}


static os_errno scsi_disk_lookup(struct os_devtab_entry  **tab,
                                 struct os_devtab_entry  *sub_tab,
                                 const char               *name) {
    os_errno res;
    disk_channel *chan = (disk_channel *) (*tab)->priv;
    scsi_disk_info_t *scsi_info = (scsi_disk_info_t *) chan->dev_priv;
    os_disk_identify_t ident;

    ident.serial[0]       = '\0';
    ident.firmware_rev[0] = '\0';
    ident.model_num[0]    = '\0';
    ident.lba_sectors_num = scsi_info->size / scsi_info->bytes_per_sector;
    ident.cylinders_num   = scsi_info->cylinders_num;
    ident.heads_num       = scsi_info->heads_num;
    ident.sectors_num     = scsi_info->sectors_num;
    ident.phys_block_size = 1;
    ident.max_transfer    = 2048;
    res = (chan->callbacks->disk_connected)(*tab, &ident);

    if( res == ENOERR )
        res = (chan->callbacks->disk_lookup(tab, sub_tab, name));

    return res;
}



static os_errno scsi_disk_read(disk_channel *chan,
                               void         *buf,
                               u32    len,
                               u32    block_num) {
    u32 rscsi;
    int ret;
    scsi_disk_info_t *info = (scsi_disk_info_t *)chan->dev_priv;
    struct scsi_device *scsi = info->dev;
    int block_size = info->bytes_per_sector;
    ret = scsi->ops->read_10(scsi, (u8*)buf, block_num, block_size * len, &rscsi);

    if (ret == 0) {
        return ENOERR;
    } else {
        return -EIO;
    }
}


static os_errno scsi_disk_write(disk_channel *chan,
                                const void   *buf,
                                u32    len,
                                u32    block_num) {
    u32 rscsi;
    int ret;
    scsi_disk_info_t *info = (scsi_disk_info_t *)chan->dev_priv;
    struct scsi_device *scsi = info->dev;
    int block_size = info->bytes_per_sector;
    ret = scsi->ops->write_10(scsi, (u8*)buf, block_num, block_size * len, &rscsi);

    if (ret == 0) {
        return ENOERR;
    } else {
        return -EIO;
    }
}


static os_errno scsi_disk_get_config(disk_channel *chan,
                                     u32    key,
                                     const void   *xbuf,
                                     u32   *len) {

    return -EINVAL;
}


static os_errno scsi_disk_set_config(disk_channel *chan,
                                     u32    key,
                                     const void   *xbuf,
                                     u32   *len) {
    os_errno res = ENOERR;
    switch ( key ) {
    case OS_IO_SET_CONFIG_DISK_MOUNT:
        // We have nothing to do here for this option.
        break;

    case OS_IO_SET_CONFIG_DISK_UMOUNT:
        if( chan->info->mounts == 0 ) {
            // If this is the last unmount of this disk, then disconnect it from
            // the driver system so the user can swap it out if he wants.
            res = (chan->callbacks->disk_disconnected)(chan);
        }
        break;
    default:
        res = -EINVAL;
        break;
    }
    return res;
}

int attach_block_dev(struct scsi_device *dev) {
    scsi_disk_info0.dev = dev;
    scsi_disk_info0.cylinders_num = dev->disk_info.di_cylinders;
    scsi_disk_info0.heads_num = dev->disk_info.di_heads;
    scsi_disk_info0.bytes_per_sector = dev->disk_info.di_bytes_per_sect;
    scsi_disk_info0.size = (u32)dev->disk_info.di_total_sectors * dev->disk_info.di_bytes_per_sect;
    scsi_disk_info0.sectors_num = (u32)dev->disk_info.di_total_sectors;

    os_dev_register(&scsi_disk_io0);
    dev->flags.fsdmounted = 1;
    dev->flags.device_removed = 0;
    return 0;
}
void detach_block_dev(struct scsi_device *dev) {
    dev->flags.fsdmounted = 0;
    dev->flags.device_removed = 1;
    os_dev_unregister(&scsi_disk_io0);
}
void os_dev_register(os_devtab_entry_t *dev){};
void os_dev_unregister(os_devtab_entry_t *dev){};

struct disk_channel* get_scsi_disk_channel(void)
{
    return &scsi_disk_channel0;
}

