/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of removable disk drives with SCSI(-like)
 *          commands, for both ATAPI and SCSI usage.
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *          Jasmine Iwanek, <jriwanek@gmail.com>
 *
 *          Copyright 2018-2026 Miran Grca.
 *          Copyright 2022-2026 Jasmine Iwanek.
 */
#ifdef ENABLE_RDISK_LOG
#include <stdarg.h>
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <86box/86box.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/log.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/nvr.h>
#include <86box/path.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/hdc_ide.h>
#include <86box/rdisk.h>
#include <86box/version.h>

#define IDE_ATAPI_IS_EARLY             id->sc->pad0

rdisk_drive_t rdisk_drives[RDISK_NUM];

// clang-format off
/*
   Table of all SCSI commands and their flags, needed for the new disc change /
   not ready handler.
 */
const uint8_t rdisk_command_flags[0x100] = {
    [0x00]          = IMPLEMENTED | CHECK_READY,
    [0x01]          = IMPLEMENTED | ALLOW_UA | SCSI_ONLY,
    [0x03]          = IMPLEMENTED | ALLOW_UA,
    [0x04]          = IMPLEMENTED | CHECK_READY | ALLOW_UA | SCSI_ONLY,
    [0x06]          = IMPLEMENTED,
    [0x08]          = IMPLEMENTED | CHECK_READY,
    [0x0a ... 0x0b] = IMPLEMENTED | CHECK_READY,
    [0x0c]          = IMPLEMENTED,
    [0x0d]          = IMPLEMENTED | ATAPI_ONLY,
    [0x12]          = IMPLEMENTED | ALLOW_UA,
    [0x13]          = IMPLEMENTED | CHECK_READY,
    [0x15]          = IMPLEMENTED,
    [0x16 ... 0x17] = IMPLEMENTED | SCSI_ONLY,
    [0x1a]          = IMPLEMENTED,
    [0x1b]          = IMPLEMENTED | CHECK_READY,
    [0x1d]          = IMPLEMENTED,
    [0x1e]          = IMPLEMENTED | CHECK_READY,
    [0x23]          = IMPLEMENTED | ATAPI_ONLY,
    [0x25]          = IMPLEMENTED | CHECK_READY,
    [0x28]          = IMPLEMENTED | CHECK_READY,
    [0x2a ... 0x2b] = IMPLEMENTED | CHECK_READY,
    [0x2e ... 0x2f] = IMPLEMENTED | CHECK_READY,
    [0x41]          = IMPLEMENTED | CHECK_READY,
    [0x55]          = IMPLEMENTED,
    [0x5a]          = IMPLEMENTED,
    [0xa8]          = IMPLEMENTED | CHECK_READY,
    [0xaa]          = IMPLEMENTED | CHECK_READY,
    [0xae]          = IMPLEMENTED | CHECK_READY,
    [0xaf]          = IMPLEMENTED | CHECK_READY | SCSI_ONLY,
    [0xbd]          = IMPLEMENTED
};

static uint64_t zip_100_mode_sense_page_flags = (GPMODEP_R_W_ERROR_PAGE | GPMODEP_DISCONNECT_PAGE |
                                                 GPMODEP_IOMEGA_PAGE | GPMODEP_ALL_PAGES);
static uint64_t zip_250_mode_sense_page_flags = (GPMODEP_R_W_ERROR_PAGE | GPMODEP_FLEXIBLE_DISK_PAGE |
                                                 GPMODEP_CACHING_PAGE | GPMODEP_IOMEGA_PAGE |
                                                 GPMODEP_ALL_PAGES);

static const mode_sense_pages_t zip_100_mode_sense_pages_default = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x0a, 0xc8, 0x16, 0x00, 0x00, 0x00, 0x00,
                 0x5a,                                0x00, 0x50, 0x20                         },
      [0x02] = { GPMODE_DISCONNECT_PAGE,              0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0x5c, 0x0f, 0xff, 0x0f             } }
};

static const mode_sense_pages_t zip_250_mode_sense_pages_default = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x06, 0xc8, 0x64, 0x00, 0x00, 0x00, 0x00 },
      [0x05] = { GPMODE_FLEXIBLE_DISK_PAGE,           0x1e, 0x80, 0x00, 0x40, 0x20, 0x02, 0x00,
                 0x00,                                0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x0b, 0x7d, 0x00, 0x00 },
      [0x08] = { GPMODE_CACHING_PAGE,                 0x0a, 0x04, 0x00, 0xff, 0xff, 0x00, 0x00,
                 0xff,                                0xff, 0xff, 0xff                         },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0x5c, 0x0f, 0x3c, 0x0f             } }
};

static const mode_sense_pages_t zip_100_mode_sense_pages_default_scsi = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x0a, 0xc8, 0x16, 0x00, 0x00, 0x00, 0x00,
                 0x5a,                                0x00, 0x50, 0x20                         },
      [0x02] = { GPMODE_DISCONNECT_PAGE,              0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0x5c, 0x0f, 0xff, 0x0f             } }
};

static const mode_sense_pages_t zip_250_mode_sense_pages_default_scsi = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x06, 0xc8, 0x64, 0x00, 0x00, 0x00, 0x00 },
      [0x05] = { GPMODE_FLEXIBLE_DISK_PAGE,           0x1e, 0x80, 0x00, 0x40, 0x20, 0x02, 0x00,
                 0x00,                                0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x0b, 0x7d, 0x00, 0x00 },
      [0x08] = { GPMODE_CACHING_PAGE,                 0x0a, 0x04, 0x00, 0xff, 0xff, 0x00, 0x00,
                 0xff,                                0xff, 0xff, 0xff                         },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0x5c, 0x0f, 0x3c, 0x0f             } }
};

static const mode_sense_pages_t zip_100_mode_sense_pages_changeable = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x0a, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
                 0x5a,                                0xff, 0xff, 0xff                         },
      [0x02] = { GPMODE_DISCONNECT_PAGE,              0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00,                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0xff, 0xff, 0xff, 0xff             } }
};

static const mode_sense_pages_t zip_250_mode_sense_pages_changeable = {
    { [0x01] = { GPMODE_R_W_ERROR_PAGE,               0x06, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 },
      [0x05] = { GPMODE_FLEXIBLE_DISK_PAGE,           0x1e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                 0xff,                                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                 0xff,                                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                 0xff,                                0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 },
      [0x08] = { GPMODE_CACHING_PAGE,                 0x0a, 0x04, 0x00, 0xff, 0xff, 0x00, 0x00,
                 0xff,                                0xff, 0xff, 0xff                         },
      [0x2f] = { GPMODE_IOMEGA_PAGE,                  0x04, 0xff, 0xff, 0xff, 0xff             } }
};
// clang-format on

static void rdisk_command_complete(rdisk_t *dev);
static void rdisk_init(rdisk_t *dev);

#ifdef ENABLE_RDISK_LOG
int rdisk_do_log = ENABLE_RDISK_LOG;

static void
rdisk_log(void *priv, const char *fmt, ...)
{
    va_list ap;

    if (rdisk_do_log) {
        va_start(ap, fmt);
        log_out(priv, fmt, ap);
        va_end(ap);
    }
}
#else
#    define rdisk_log(priv, fmt, ...)
#endif

static int
rdisk_load_abort(const rdisk_t *dev)
{
    if (dev->drv->fp)
        fclose(dev->drv->fp);
    dev->drv->fp           = NULL;
    dev->drv->medium_size = 0;
    rdisk_eject(dev->id); /* Make sure the host OS knows we've rejected (and ejected) the image. */
    return 0;
}

static int
image_is_sdi(const char *s)
{
    return !strcasecmp(path_get_extension((char *) s), "SDI");
}

static int
image_is_zdi(const char *s)
{
    return !strcasecmp(path_get_extension((char *) s), "ZDI");
}

int
rdisk_is_empty(const uint8_t id)
{
    const rdisk_t *dev = (const rdisk_t *) rdisk_drives[id].priv;
    int          ret = 0;

    if ((dev->drv == NULL) || (dev->drv->fp == NULL))
        ret = 1;

    return ret;
}

void
rdisk_load(const rdisk_t *dev, const char *fn, const int skip_insert)
{
    const int was_empty = rdisk_is_empty(dev->id);
    int       ret       = 0;
    int       offs      = 0;

    if (strstr(fn, "wp://") == fn) {
        offs                = 5;
        dev->drv->read_only = 1;
    }

    fn += offs;

    if (dev->drv == NULL)
        rdisk_eject(dev->id);
    else {
        const int is_sdi = image_is_sdi(fn);
        const int is_zdi = image_is_zdi(fn);

        dev->drv->fp     = plat_fopen(fn, dev->drv->read_only ? "rb" : "rb+");
        ret              = 1;

        if (dev->drv->fp == NULL) {
            if (!dev->drv->read_only) {
                dev->drv->fp = plat_fopen(fn, "rb");
                if (dev->drv->fp == NULL)
                    ret = rdisk_load_abort(dev);
                else
                    dev->drv->read_only = 1;
            } else
                ret = rdisk_load_abort(dev);
        }

        if (ret) {
            fseek(dev->drv->fp, 0, SEEK_END);
            int size = ftell(dev->drv->fp);

            if ((is_sdi) || (is_zdi)) {
                /* This is a SDI or ZDI image. */
                size -= 0x1000;
                dev->drv->base = 0x1000;
            } else
                dev->drv->base = 0;

            if (dev->drv->type != RDISK_TYPE_ZIP_100) {
                if ((size != (ZIP_250_SECTORS << 9)) && (size != (ZIP_SECTORS << 9))) {
                    rdisk_log(dev->log, "File is incorrect size for a RDISK image\n");
                    rdisk_log(dev->log, "Must be exactly %i or %i bytes\n",
                            ZIP_250_SECTORS << 9, ZIP_SECTORS << 9);
                    ret = rdisk_load_abort(dev);
                }
            } else if (size != (ZIP_SECTORS << 9)) {
                rdisk_log(dev->log, "File is incorrect size for a RDISK image\n");
                rdisk_log(dev->log, "Must be exactly %i bytes\n", ZIP_SECTORS << 9);
                ret = rdisk_load_abort(dev);
            }

            if (ret)
                dev->drv->medium_size = size >> 9;
        }

         if (ret) {
             if (fseek(dev->drv->fp, dev->drv->base, SEEK_SET) == -1)
                 log_fatal(dev->log, "rdisk_load(): Error seeking to the beginning of "
                           "the file\n");

             strncpy(dev->drv->image_path, fn - offs, sizeof(dev->drv->image_path) - 1);
             /*
                After using strncpy, dev->drv->image_path needs to be explicitly null
                terminated to make gcc happy.
                In the event strlen(dev->drv->image_path) == sizeof(dev->drv->image_path)
                (no null terminator) it is placed at the very end. Otherwise, it is placed
                right after the string.
              */
             const size_t term = strlen(dev->drv->image_path) ==
                 sizeof(dev->drv->image_path) ? sizeof(dev->drv->image_path) - 1 :
                                                strlen(dev->drv->image_path);
             dev->drv->image_path[term] = '\0';
        }
    }

    if (ret && !skip_insert) {
        /* Signal media change to the emulated machine. */
        rdisk_insert((rdisk_t *) dev);

        /* The drive was previously empty, transition directly to UNIT ATTENTION. */
        if (was_empty)
            rdisk_insert((rdisk_t *) dev);
    }

    if (ret)
        ui_sb_update_icon_wp(SB_RDISK | dev->id, dev->drv->read_only);
}

void
rdisk_disk_reload(const rdisk_t *dev)
{
    if (strlen(dev->drv->prev_image_path) != 0)
        (void) rdisk_load(dev, dev->drv->prev_image_path, 0);
}

static void
rdisk_disk_unload(const rdisk_t *dev)
{
    if ((dev->drv != NULL) && (dev->drv->fp != NULL)) {
        fclose(dev->drv->fp);
        dev->drv->fp = NULL;
    }
}

void
rdisk_disk_close(const rdisk_t *dev)
{
    if ((dev->drv != NULL) && (dev->drv->fp != NULL)) {
        rdisk_disk_unload(dev);

        memcpy(dev->drv->prev_image_path, dev->drv->image_path,
               sizeof(dev->drv->image_path));
        memset(dev->drv->image_path, 0, sizeof(dev->drv->image_path));

        dev->drv->medium_size = 0;

        rdisk_insert((rdisk_t *) dev);
    }
}

static void
rdisk_set_callback(const rdisk_t *dev)
{
    if (dev->drv->bus_type != RDISK_BUS_SCSI)
        ide_set_callback(ide_drives[dev->drv->ide_channel], dev->callback);
}

static void
rdisk_init(rdisk_t *dev)
{
    if (dev->id < RDISK_NUM) {
        dev->requested_blocks = 1;
        dev->sense[0]         = 0xf0;
        dev->sense[7]         = 10;
        dev->drv->bus_mode    = 0;
        if (dev->drv->bus_type >= RDISK_BUS_ATAPI)
            dev->drv->bus_mode |= 2;
        if (dev->drv->bus_type < RDISK_BUS_SCSI)
            dev->drv->bus_mode |= 1;
        rdisk_log(dev->log, "Bus type %i, bus mode %i\n", dev->drv->bus_type, dev->drv->bus_mode);
        if (dev->drv->bus_type < RDISK_BUS_SCSI) {
            dev->tf->phase          = 1;
            dev->tf->request_length = 0xEB14;
        }
        dev->tf->status    = READY_STAT | DSC_STAT;
        dev->tf->pos       = 0;
        dev->packet_status = PHASE_NONE;
        rdisk_sense_key = rdisk_asc = rdisk_ascq = dev->unit_attention = dev->transition = 0;
        rdisk_info      = 0x00000000;
    }
}

static int
rdisk_supports_pio(const rdisk_t *dev)
{
    return (dev->drv->bus_mode & 1);
}

static int
rdisk_supports_dma(const rdisk_t *dev)
{
    return (dev->drv->bus_mode & 2);
}

/* Returns: 0 for none, 1 for PIO, 2 for DMA. */
static int
rdisk_current_mode(const rdisk_t *dev)
{
    if (!rdisk_supports_pio(dev) && !rdisk_supports_dma(dev))
        return 0;
    if (rdisk_supports_pio(dev) && !rdisk_supports_dma(dev)) {
        rdisk_log(dev->log, "Drive does not support DMA, setting to PIO\n");
        return 1;
    }
    if (!rdisk_supports_pio(dev) && rdisk_supports_dma(dev))
        return 2;
    if (rdisk_supports_pio(dev) && rdisk_supports_dma(dev)) {
        rdisk_log(dev->log, "Drive supports both, setting to %s\n",
                (dev->tf->features & 1) ? "DMA" : "PIO");
        return (dev->tf->features & 1) ? 2 : 1;
    }

    return 0;
}

static void
rdisk_mode_sense_load(rdisk_t *dev)
{
    char  fn[512] = { 0 };

    memset(&dev->ms_pages_saved, 0, sizeof(mode_sense_pages_t));

    if (dev->drv->type == RDISK_TYPE_ZIP_100) {
        if (rdisk_drives[dev->id].bus_type == RDISK_BUS_SCSI)
            memcpy(&dev->ms_pages_saved, &zip_100_mode_sense_pages_default_scsi, sizeof(mode_sense_pages_t));
        else
            memcpy(&dev->ms_pages_saved, &zip_100_mode_sense_pages_default, sizeof(mode_sense_pages_t));
    } else {
        if (rdisk_drives[dev->id].bus_type == RDISK_BUS_SCSI)
            memcpy(&dev->ms_pages_saved, &zip_250_mode_sense_pages_default_scsi, sizeof(mode_sense_pages_t));
        else
            memcpy(&dev->ms_pages_saved, &zip_250_mode_sense_pages_default, sizeof(mode_sense_pages_t));
    }

    if (dev->drv->bus_type == RDISK_BUS_SCSI)
        sprintf(fn, "scsi_rdisk_%02i_mode_sense_bin", dev->id);
    else
        sprintf(fn, "rdisk_%02i_mode_sense_bin", dev->id);

    FILE *fp = plat_fopen(nvr_path(fn), "rb");
    if (fp) {
        /* Nothing to read, not used by RDISK. */
        fclose(fp);
    }
}

static void
rdisk_mode_sense_save(const rdisk_t *dev)
{
    char  fn[512] = { 0 };

    if (dev->drv->bus_type == RDISK_BUS_SCSI)
        sprintf(fn, "scsi_rdisk_%02i_mode_sense_bin", dev->id);
    else
        sprintf(fn, "rdisk_%02i_mode_sense_bin", dev->id);
    FILE *fp = plat_fopen(nvr_path(fn), "wb");
    if (fp) {
        /* Nothing to write, not used by RDISK. */
        fclose(fp);
    }
}

/* SCSI Mode Sense 6/10. */
static uint8_t
zip_mode_sense_read(const rdisk_t *dev, const uint8_t pgctl,
                    const uint8_t page, const uint8_t pos)
{
    switch (pgctl) {
        case 0:
        case 3:
            if ((dev->drv->type != RDISK_TYPE_ZIP_100) && (page == 5) && (pos == 9) &&
                (dev->drv->medium_size == ZIP_SECTORS))
                return 0x60;
            return dev->ms_pages_saved.pages[page][pos];
        case 1:
            if (dev->drv->type == RDISK_TYPE_ZIP_100)
                return zip_100_mode_sense_pages_changeable.pages[page][pos];
            else
                return zip_250_mode_sense_pages_changeable.pages[page][pos];
        case 2:
            if (dev->drv->type == RDISK_TYPE_ZIP_100) {
                if (dev->drv->bus_type == RDISK_BUS_SCSI)
                    return zip_100_mode_sense_pages_default_scsi.pages[page][pos];
                else
                    return zip_100_mode_sense_pages_default.pages[page][pos];
            } else {
                if ((page == 5) && (pos == 9) && (dev->drv->medium_size == ZIP_SECTORS))
                    return 0x60;
                if (dev->drv->bus_type == RDISK_BUS_SCSI)
                    return zip_250_mode_sense_pages_default_scsi.pages[page][pos];
                else
                    return zip_250_mode_sense_pages_default.pages[page][pos];
            }

        default:
            break;
    }

    return 0;
}

static uint32_t
rdisk_mode_sense(const rdisk_t *dev, uint8_t *buf, uint32_t pos,
               uint8_t page, const uint8_t block_descriptor_len)
{
    uint64_t       pf;
    const uint8_t  pgctl = (page >> 6) & 3;

    if (dev->drv->type == RDISK_TYPE_ZIP_100)
        pf = zip_100_mode_sense_page_flags;
    else
        pf = zip_250_mode_sense_page_flags;

    page &= 0x3f;

    if (block_descriptor_len) {
        buf[pos++] = ((dev->drv->medium_size >> 24) & 0xff);
        buf[pos++] = ((dev->drv->medium_size >> 16) & 0xff);
        buf[pos++] = ((dev->drv->medium_size >> 8) & 0xff);
        buf[pos++] = (dev->drv->medium_size & 0xff);
        buf[pos++] = 0; /* Reserved. */
        buf[pos++] = 0; /* Block length (0x200 = 512 bytes). */
        buf[pos++] = 2;
        buf[pos++] = 0;
    }

    for (uint8_t i = 0; i < 0x40; i++) {
        if ((page == GPMODE_ALL_PAGES) || (page == i)) {
            if (pf & (1LL << ((uint64_t) page))) {
                const uint8_t msplen = zip_mode_sense_read(dev, pgctl, i, 1);
                buf[pos++]           = zip_mode_sense_read(dev, pgctl, i, 0);
                buf[pos++]           = msplen;
                rdisk_log(dev->log, "MODE SENSE: Page [%02X] length %i\n", i, msplen);
                for (uint8_t j = 0; j < msplen; j++)
                    buf[pos++] = zip_mode_sense_read(dev, pgctl, i, 2 + j);
            }
        }
    }

    return pos;
}

static void
rdisk_update_request_length(rdisk_t *dev, int len, int block_len)
{
    int bt;
    int min_len = 0;

    dev->max_transfer_len = dev->tf->request_length;

    /*
       For media access commands, make sure the requested DRQ length matches the
       block length.
     */
    switch (dev->current_cdb[0]) {
        case 0x08:
        case 0x0a:
        case 0x28:
        case 0x2a:
        case 0xa8:
        case 0xaa:
            /* Round it to the nearest 2048 bytes. */
            dev->max_transfer_len = (dev->max_transfer_len >> 9) << 9;

            /*
               Make sure total length is not bigger than sum of the lengths of
               all the requested blocks.
             */
            bt = (dev->requested_blocks * block_len);
            if (len > bt)
                len = bt;

            min_len = block_len;

            if (len <= block_len) {
                /* Total length is less or equal to block length. */
                if (dev->max_transfer_len < block_len) {
                    /* Transfer a minimum of (block size) bytes. */
                    dev->max_transfer_len = block_len;
                    dev->packet_len       = block_len;
                    break;
                }
            }
            fallthrough;

        default:
            dev->packet_len = len;
            break;
    }
    /*
       If the DRQ length is odd, and the total remaining length is bigger,
       make sure it's even.
     */
    if ((dev->max_transfer_len & 1) && (dev->max_transfer_len < len))
        dev->max_transfer_len &= 0xfffe;
    /*
       If the DRQ length is smaller or equal in size to the total remaining length,
       set it to that.
     */
    if (!dev->max_transfer_len)
        dev->max_transfer_len = 65534;

    if ((len <= dev->max_transfer_len) && (len >= min_len))
        dev->tf->request_length = dev->max_transfer_len = len;
    else if (len > dev->max_transfer_len)
        dev->tf->request_length = dev->max_transfer_len;

    return;
}

static double
rdisk_bus_speed(rdisk_t *dev)
{
    double ret = -1.0;

    if (dev && dev->drv && (dev->drv->bus_type == RDISK_BUS_ATAPI))
        ret = ide_atapi_get_period(dev->drv->ide_channel);

    if (ret == -1.0) {
        if (dev)
            dev->callback = -1.0;
        ret = 0.0;
    }

    return ret;
}

static void
rdisk_command_common(rdisk_t *dev)
{
    dev->tf->status = BUSY_STAT;
    dev->tf->phase  = 1;
    dev->tf->pos    = 0;
    if (dev->packet_status == PHASE_COMPLETE)
        dev->callback = 0.0;
    else if (dev->drv->bus_type == RDISK_BUS_SCSI)
        dev->callback = -1.0; /* Speed depends on SCSI controller */
    else
        dev->callback = rdisk_bus_speed(dev) * (double) (dev->packet_len);

    rdisk_set_callback(dev);
}

static void
rdisk_command_complete(rdisk_t *dev)
{
    dev->packet_status = PHASE_COMPLETE;
    rdisk_command_common(dev);
}

static void
rdisk_command_read(rdisk_t *dev)
{
    dev->packet_status = PHASE_DATA_IN;
    rdisk_command_common(dev);
}

static void
rdisk_command_read_dma(rdisk_t *dev)
{
    dev->packet_status = PHASE_DATA_IN_DMA;
    rdisk_command_common(dev);
}

static void
rdisk_command_write(rdisk_t *dev)
{
    dev->packet_status = PHASE_DATA_OUT;
    rdisk_command_common(dev);
}

static void
rdisk_command_write_dma(rdisk_t *dev)
{
    dev->packet_status = PHASE_DATA_OUT_DMA;
    rdisk_command_common(dev);
}

/*
   dev = Pointer to current RDISK device;
   len = Total transfer length;
   block_len = Length of a single block (why does it matter?!);
   alloc_len = Allocated transfer length;
   direction = Transfer direction (0 = read from host, 1 = write to host).
 */
static void
rdisk_data_command_finish(rdisk_t *dev, int len, const int block_len,
                        const int alloc_len, const int direction)
{
    rdisk_log(dev->log, "Finishing command (%02X): %i, %i, %i, %i, %i\n",
            dev->current_cdb[0], len, block_len, alloc_len,
            direction, dev->tf->request_length);
    dev->tf->pos = 0;
    if (alloc_len >= 0) {
        if (alloc_len < len)
            len = alloc_len;
    }
    if ((len == 0) || (rdisk_current_mode(dev) == 0)) {
        if (dev->drv->bus_type != RDISK_BUS_SCSI)
            dev->packet_len = 0;

        rdisk_command_complete(dev);
    } else {
        if (rdisk_current_mode(dev) == 2) {
            if (dev->drv->bus_type != RDISK_BUS_SCSI)
                dev->packet_len = alloc_len;

            if (direction == 0)
                rdisk_command_read_dma(dev);
            else
                rdisk_command_write_dma(dev);
        } else {
            rdisk_update_request_length(dev, len, block_len);
            if ((dev->drv->bus_type != RDISK_BUS_SCSI) &&
                (dev->tf->request_length == 0))
                rdisk_command_complete(dev);
            else if (direction == 0)
                rdisk_command_read(dev);
            else
                rdisk_command_write(dev);
        }
    }

    rdisk_log(dev->log, "Status: %i, cylinder %i, packet length: %i, position: %i, phase: %i\n",
            dev->packet_status, dev->tf->request_length, dev->packet_len,
            dev->tf->pos, dev->tf->phase);
}

static void
rdisk_sense_clear(rdisk_t *dev, UNUSED(int command))
{
    rdisk_sense_key = rdisk_asc = rdisk_ascq = 0;
    rdisk_info      = 0x00000000;
}

static void
rdisk_set_phase(const rdisk_t *dev, const uint8_t phase)
{
    const uint8_t scsi_bus = (dev->drv->scsi_device_id >> 4) & 0x0f;
    const uint8_t scsi_id  = dev->drv->scsi_device_id & 0x0f;

    if (dev->drv->bus_type == RDISK_BUS_SCSI)
        scsi_devices[scsi_bus][scsi_id].phase = phase;
}

static void
rdisk_cmd_error(rdisk_t *dev)
{
    rdisk_set_phase(dev, SCSI_PHASE_STATUS);
    dev->tf->error = ((rdisk_sense_key & 0xf) << 4) | ABRT_ERR;
    dev->tf->status    = READY_STAT | ERR_STAT;
    dev->tf->phase     = 3;
    dev->tf->pos       = 0;
    dev->packet_status = PHASE_ERROR;
    dev->callback      = 50.0 * RDISK_TIME;
    rdisk_set_callback(dev);
    ui_sb_update_icon(SB_RDISK | dev->id, 0);
    ui_sb_update_icon_write(SB_RDISK | dev->id, 0);
    rdisk_log(dev->log, "[%02X] ERROR: %02X/%02X/%02X\n", dev->current_cdb[0], rdisk_sense_key,
            rdisk_asc, rdisk_ascq);
}

static void
rdisk_unit_attention(rdisk_t *dev)
{
    rdisk_set_phase(dev, SCSI_PHASE_STATUS);
    dev->tf->error = (SENSE_UNIT_ATTENTION << 4) | ABRT_ERR;
    dev->tf->status    = READY_STAT | ERR_STAT;
    dev->tf->phase     = 3;
    dev->tf->pos       = 0;
    dev->packet_status = PHASE_ERROR;
    dev->callback      = 50.0 * RDISK_TIME;
    rdisk_set_callback(dev);
    ui_sb_update_icon(SB_RDISK | dev->id, 0);
    ui_sb_update_icon_write(SB_RDISK | dev->id, 0);
    rdisk_log(dev->log, "UNIT ATTENTION\n", dev->id);
}

static void
rdisk_buf_alloc(rdisk_t *dev, const uint32_t len)
{
    rdisk_log(dev->log, "Allocated buffer length: %i\n", len);

    if (dev->buffer == NULL) {
        dev->buffer = (uint8_t *) calloc(1, len);
        dev->buffer_sz = len;
    }

    if (len > dev->buffer_sz) {
        uint8_t *buf = (uint8_t *) realloc(dev->buffer, len);
        dev->buffer = buf;
        dev->buffer_sz = len;
    }
}

static void
rdisk_buf_free(rdisk_t *dev)
{
    if (dev->buffer != NULL) {
        rdisk_log(dev->log, "Removable Disk %i: Freeing buffer...\n");
        free(dev->buffer);
        dev->buffer = NULL;
    }
}

static void
rdisk_bus_master_error(scsi_common_t *sc)
{
    rdisk_t *dev = (rdisk_t *) sc;

    rdisk_buf_free(dev);
    rdisk_sense_key = rdisk_asc = rdisk_ascq = 0;
    rdisk_info      = (dev->sector_pos >> 24)        |
                     ((dev->sector_pos >> 16) <<  8) |
                     ((dev->sector_pos >> 8)  << 16) |
                     ( dev->sector_pos        << 24);
    rdisk_cmd_error(dev);
}

static void
rdisk_not_ready(rdisk_t *dev)
{
    rdisk_sense_key = SENSE_NOT_READY;
    rdisk_asc       = ASC_MEDIUM_NOT_PRESENT;
    rdisk_ascq      = 0;
    rdisk_info      = 0x00000000;
    rdisk_cmd_error(dev);
}

static void
rdisk_write_protected(rdisk_t *dev)
{
    rdisk_sense_key = SENSE_UNIT_ATTENTION;
    rdisk_asc       = ASC_WRITE_PROTECTED;
    rdisk_ascq      = 0;
    rdisk_info      = (dev->sector_pos >> 24)        |
                     ((dev->sector_pos >> 16) <<  8) |
                     ((dev->sector_pos >> 8)  << 16) |
                     ( dev->sector_pos        << 24);
    rdisk_cmd_error(dev);
}

static void
rdisk_write_error(rdisk_t *dev)
{
    rdisk_sense_key = SENSE_MEDIUM_ERROR;
    rdisk_asc       = ASC_WRITE_ERROR;
    rdisk_ascq      = 0;
    rdisk_info      = (dev->sector_pos >> 24)        |
                     ((dev->sector_pos >> 16) <<  8) |
                     ((dev->sector_pos >> 8)  << 16) |
                     ( dev->sector_pos        << 24);
    rdisk_cmd_error(dev);
}

static void
rdisk_read_error(rdisk_t *dev)
{
    rdisk_sense_key = SENSE_MEDIUM_ERROR;
    rdisk_asc       = ASC_UNRECOVERED_READ_ERROR;
    rdisk_ascq      = 0;
    rdisk_info      = (dev->sector_pos >> 24)        |
                     ((dev->sector_pos >> 16) <<  8) |
                     ((dev->sector_pos >> 8)  << 16) |
                     ( dev->sector_pos        << 24);
    rdisk_cmd_error(dev);
}

static void
rdisk_invalid_lun(rdisk_t *dev, const uint8_t lun)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_INV_LUN;
    rdisk_ascq      = 0;
    rdisk_info      = lun << 24;
    rdisk_cmd_error(dev);
}

static void
rdisk_illegal_opcode(rdisk_t *dev, const uint8_t opcode)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_ILLEGAL_OPCODE;
    rdisk_ascq      = 0;
    rdisk_info      = opcode << 24;
    rdisk_cmd_error(dev);
}

static void
rdisk_lba_out_of_range(rdisk_t *dev)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_LBA_OUT_OF_RANGE;
    rdisk_ascq      = 0;
    rdisk_info      = (dev->sector_pos >> 24)        |
                     ((dev->sector_pos >> 16) <<  8) |
                     ((dev->sector_pos >> 8)  << 16) |
                     ( dev->sector_pos        << 24);
    rdisk_cmd_error(dev);
}

static void
rdisk_invalid_field(rdisk_t *dev, const uint32_t field)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_INV_FIELD_IN_CMD_PACKET;
    rdisk_ascq      = 0;
    rdisk_info      = (field >> 24)        |
                     ((field >> 16) <<  8) |
                     ((field >> 8)  << 16) |
                     ( field        << 24);
    rdisk_cmd_error(dev);
    dev->tf->status = 0x53;
}

static void
rdisk_invalid_field_pl(rdisk_t *dev, const uint32_t field)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_INV_FIELD_IN_PARAMETER_LIST;
    rdisk_ascq      = 0;
    rdisk_info      = (field >> 24)        |
                     ((field >> 16) <<  8) |
                     ((field >> 8)  << 16) |
                     ( field        << 24);
    rdisk_cmd_error(dev);
    dev->tf->status = 0x53;
}

static void
rdisk_data_phase_error(rdisk_t *dev, const uint32_t info)
{
    rdisk_sense_key = SENSE_ILLEGAL_REQUEST;
    rdisk_asc       = ASC_DATA_PHASE_ERROR;
    rdisk_ascq      = 0;
    rdisk_info      = (info >> 24)        |
                     ((info >> 16) <<  8) |
                     ((info >> 8)  << 16) |
                     ( info        << 24);
    rdisk_cmd_error(dev);
}

static int
rdisk_blocks(rdisk_t *dev, int32_t *len, const int out)
{
    int ret = 1;
    *len    = 0;

    if (dev->sector_len > 0) {
        rdisk_log(dev->log, "%sing %i blocks starting from %i...\n", out ? "Writ" : "Read",
                dev->requested_blocks, dev->sector_pos);

        if (dev->sector_pos >= dev->drv->medium_size) {
            rdisk_log(dev->log, "Trying to %s beyond the end of disk\n",
                    out ? "write" : "read");
            rdisk_lba_out_of_range(dev);
            ret = 0;
        } else {
            *len    = dev->requested_blocks << 9;

            for (int i = 0; i < dev->requested_blocks; i++) {
                if (fseek(dev->drv->fp, dev->drv->base + (dev->sector_pos << 9),
                    SEEK_SET) == -1) {
                    if (out)
                        rdisk_write_error(dev);
                    else
                        rdisk_read_error(dev);
                    ret = -1;
                } else {
                    if (feof(dev->drv->fp))
                        break;

                    if (out) {
                        if (fwrite(dev->buffer + (i << 9), 1,
                                   512, dev->drv->fp) != 512) {
                            rdisk_log(dev->log, "rdisk_blocks(): Error writing data\n");
                            rdisk_write_error(dev);
                            ret = -1;
                        } else
                            fflush(dev->drv->fp);
                    } else if (fread(dev->buffer + (i << 9), 1,
                                     512, dev->drv->fp) != 512) {
                        rdisk_log(dev->log, "rdisk_blocks(): Error reading data\n");
                        rdisk_read_error(dev);
                        ret = -1;
                    }
                }

                if (ret == -1)
                    break;

                dev->sector_pos++;
            }

            if (ret == 1) {
                rdisk_log(dev->log, "%s %i bytes of blocks...\n", out ? "Written" :
                        "Read", *len);

                dev->sector_len -= dev->requested_blocks;
            }
        }
    } else {
        rdisk_command_complete(dev);
        ret = 0;
    }

    return ret;
}

void
rdisk_insert(rdisk_t *dev)
{
    if ((dev != NULL) && (dev->drv != NULL)) {
        if (dev->drv->fp == NULL) {
            dev->unit_attention = 0;
            dev->transition     = 0;
            rdisk_log(dev->log, "Media removal\n");
        } else if (dev->transition) {
            dev->unit_attention = 1;
            /* Turn off the medium changed status. */
            dev->transition     = 0;
            rdisk_log(dev->log, "Media insert\n");
        } else {
            dev->unit_attention = 0;
            dev->transition     = 1;
            rdisk_log(dev->log, "Media transition\n");
        }
    }
}

static int
rdisk_pre_execution_check(rdisk_t *dev, const uint8_t *cdb)
{
    int ready;

    if ((cdb[0] != GPCMD_REQUEST_SENSE) && (dev->cur_lun == SCSI_LUN_USE_CDB) &&
        (cdb[1] & 0xe0)) {
        rdisk_log(dev->log, "Attempting to execute a unknown command targeted at SCSI LUN %i\n",
                ((dev->tf->request_length >> 5) & 7));
        rdisk_invalid_lun(dev, cdb[1] >> 5);
        return 0;
    }

    if (!(rdisk_command_flags[cdb[0]] & IMPLEMENTED)) {
        rdisk_log(dev->log, "Attempting to execute unknown command %02X over %s\n",
                  cdb[0], (dev->drv->bus_type == RDISK_BUS_SCSI) ?
                  "SCSI" : "ATAPI");

        rdisk_illegal_opcode(dev, cdb[0]);
        return 0;
    }

    if ((dev->drv->bus_type < RDISK_BUS_SCSI) &&
        (rdisk_command_flags[cdb[0]] & SCSI_ONLY)) {
        rdisk_log(dev->log, "Attempting to execute SCSI-only command %02X "
                  "over ATAPI\n", cdb[0]);
        rdisk_illegal_opcode(dev, cdb[0]);
        return 0;
    }

    if ((dev->drv->bus_type == RDISK_BUS_SCSI) &&
        (rdisk_command_flags[cdb[0]] & ATAPI_ONLY)) {
        rdisk_log(dev->log, "Attempting to execute ATAPI-only command %02X "
                  "over SCSI\n", cdb[0]);
        rdisk_illegal_opcode(dev, cdb[0]);
        return 0;
    }

    if (dev->transition) {
        if ((cdb[0] == GPCMD_TEST_UNIT_READY) || (cdb[0] == GPCMD_REQUEST_SENSE))
            ready = 0;
        else {
            if (!(rdisk_command_flags[cdb[0]] & ALLOW_UA)) {
                rdisk_log(dev->log, "(ext_medium_changed != 0): rdisk_insert()\n");
                rdisk_insert((void *) dev);
            }

            ready = (dev->drv->fp != NULL);
        }
    } else
        ready = (dev->drv->fp != NULL);

    /*
       If the drive is not ready, there is no reason to keep the
       UNIT ATTENTION condition present, as we only use it to mark
       disc changes.
     */
    if (!ready && (dev->unit_attention > 0))
        dev->unit_attention = 0;

    /*
       If the UNIT ATTENTION condition is set and the command does not allow
       execution under it, error out and report the condition.
     */
    if (dev->unit_attention == 1) {
        /*
           Only increment the unit attention phase if the command can
           not pass through it.
         */
        if (!(rdisk_command_flags[cdb[0]] & ALLOW_UA)) {
            rdisk_log(dev->log, "Unit attention now 2\n");
            dev->unit_attention++;
            rdisk_log(dev->log, "UNIT ATTENTION: Command %02X not allowed to pass through\n",
                      cdb[0]);
            rdisk_unit_attention(dev);
            return 0;
        }
    } else if (dev->unit_attention == 2) {
        if (cdb[0] != GPCMD_REQUEST_SENSE) {
            rdisk_log(dev->log, "Unit attention now 0\n");
            dev->unit_attention = 0;
        }
    }

    /*
       Unless the command is REQUEST SENSE, clear the sense. This will *NOT* clear
       the UNIT ATTENTION condition if it's set.
     */
    if (cdb[0] != GPCMD_REQUEST_SENSE)
        rdisk_sense_clear(dev, cdb[0]);

    if (!ready && (rdisk_command_flags[cdb[0]] & CHECK_READY)) {
        rdisk_log(dev->log, "Not ready (%02X)\n", cdb[0]);
        rdisk_not_ready(dev);
        return 0;
    }

    rdisk_log(dev->log, "Continuing with command %02X\n", cdb[0]);
    return 1;
}

static void
rdisk_seek(rdisk_t *dev, const uint32_t pos)
{
    dev->sector_pos = pos;
}

static void
rdisk_rezero(rdisk_t *dev)
{
    dev->sector_pos = dev->sector_len = 0;
    rdisk_seek(dev, 0);
}

void
rdisk_reset(scsi_common_t *sc)
{
    rdisk_t *dev = (rdisk_t *) sc;

    rdisk_rezero(dev);
    dev->tf->status         = 0;
    dev->callback           = 0.0;
    rdisk_set_callback(dev);
    dev->tf->phase          = 1;
    dev->tf->request_length = 0xEB14;
    dev->packet_status      = PHASE_NONE;
    dev->unit_attention     = 0;
    dev->cur_lun            = SCSI_LUN_USE_CDB;
    rdisk_sense_key         = rdisk_asc = rdisk_ascq = dev->unit_attention = dev->transition = 0;
    rdisk_info              = 0x00000000;
}

static void
rdisk_request_sense(rdisk_t *dev, uint8_t *buffer, const uint8_t alloc_length, const int desc)
{
    /*Will return 18 bytes of 0*/
    if (alloc_length != 0) {
        memset(buffer, 0, alloc_length);
        if (!desc)
            memcpy(buffer, dev->sense, alloc_length);
        else {
            buffer[1] = rdisk_sense_key;
            buffer[2] = rdisk_asc;
            buffer[3] = rdisk_ascq;
        }
    }

    buffer[0] = desc ? 0x72 : 0xf0;
    if (!desc)
        buffer[7] = 10;

    if (dev->unit_attention && (rdisk_sense_key == 0)) {
        buffer[desc ? 1 : 2]  = SENSE_UNIT_ATTENTION;
        buffer[desc ? 2 : 12] = ASC_MEDIUM_MAY_HAVE_CHANGED;
        buffer[desc ? 3 : 13] = 0;
    }

    rdisk_log(dev->log, "Reporting sense: %02X %02X %02X\n", buffer[2],
            buffer[12], buffer[13]);

    if (buffer[desc ? 1 : 2] == SENSE_UNIT_ATTENTION) {
        /* If the last remaining sense is unit attention, clear
           that condition. */
        dev->unit_attention = 0;
    }

    /* Clear the sense stuff as per the spec. */
    rdisk_sense_clear(dev, GPCMD_REQUEST_SENSE);

    if (dev->transition) {
        rdisk_log(dev->log, "Removable Disk_TRANSITION: rdisk_insert()\n");
        rdisk_insert((void *) dev);
    }
}

static void
rdisk_request_sense_for_scsi(scsi_common_t *sc, uint8_t *buffer, const uint8_t alloc_length)
{
    rdisk_t  *dev   = (rdisk_t *) sc;
    const int ready = (dev->drv->fp != NULL);

    if (!ready && dev->unit_attention) {
        /*
           If the drive is not ready, there is no reason to keep the UNIT ATTENTION
           condition present, as we only use it to mark disc changes.
         */
        dev->unit_attention = 0;
    }

    /* Do *NOT* advance the unit attention phase. */
    rdisk_request_sense(dev, buffer, alloc_length, 0);
}

static void
rdisk_set_buf_len(const rdisk_t *dev, int32_t *BufLen, int32_t *src_len)
{
    if (dev->drv->bus_type == RDISK_BUS_SCSI) {
        if (*BufLen == -1)
            *BufLen = *src_len;
        else {
            *BufLen  = MIN(*src_len, *BufLen);
            *src_len = *BufLen;
        }
        rdisk_log(dev->log, "Actual transfer length: %i\n", *BufLen);
    }
}

static void
rdisk_command(scsi_common_t *sc, const uint8_t *cdb)
{
    rdisk_t       *dev                = (rdisk_t *) sc;
    char           device_identify[9] = { '8', '6', 'B', '_', 'R', 'D', '0', '0', 0 };
    const uint8_t  scsi_bus           = (dev->drv->scsi_device_id >> 4) & 0x0f;
    const uint8_t  scsi_id            = dev->drv->scsi_device_id & 0x0f;
    int            pos                = 0;
    int            idx                = 0;
    int32_t        blen               = 0;
    uint32_t       i;
    unsigned       preamble_len;
    int32_t        len;
    int32_t        max_len;
    int32_t        alloc_length;
    int            block_desc;
    int            size_idx;
    int32_t *      BufLen;

    if (dev->drv->bus_type == RDISK_BUS_SCSI) {
        BufLen          = &scsi_devices[scsi_bus][scsi_id].buffer_length;
        dev->tf->status &= ~ERR_STAT;
    } else {
        BufLen         = &blen;
        dev->tf->error = 0;
    }

    dev->packet_len  = 0;
    dev->request_pos = 0;

    device_identify[7] = dev->id + 0x30;

    memcpy(dev->current_cdb, cdb, 12);

    if (cdb[0] != 0) {
        rdisk_log(dev->log, "Command 0x%02X, Sense Key %02X, Asc %02X, Ascq %02X, "
                  "Unit attention: %i\n",
                  cdb[0], rdisk_sense_key, rdisk_asc, rdisk_ascq, dev->unit_attention);
        rdisk_log(dev->log, "Request length: %04X\n", dev->tf->request_length);

        rdisk_log(dev->log, "CDB: %02X %02X %02X %02X %02X %02X %02X %02X "
                  "%02X %02X %02X %02X\n",
                  cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6], cdb[7],
                  cdb[8], cdb[9], cdb[10], cdb[11]);
    }

    dev->sector_len = 0;

    rdisk_set_phase(dev, SCSI_PHASE_STATUS);

    /*
       This handles the Not Ready/Unit Attention check if it has to be handled at
       this point.
     */
    if (rdisk_pre_execution_check(dev, cdb) == 0)
        return;

    switch (cdb[0]) {
        case GPCMD_SEND_DIAGNOSTIC:
            if (!(cdb[1] & (1 << 2))) {
                rdisk_invalid_field(dev, cdb[1]);
                return;
            }
            fallthrough;
        case GPCMD_SCSI_RESERVE:
        case GPCMD_SCSI_RELEASE:
        case GPCMD_TEST_UNIT_READY:
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);
            rdisk_command_complete(dev);
            break;

        case GPCMD_FORMAT_UNIT:
            if (dev->drv->read_only)
                rdisk_write_protected(dev);
            else {
                rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                rdisk_command_complete(dev);
            }
            break;

        case GPCMD_IOMEGA_SENSE:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);
            max_len = cdb[4];
            rdisk_buf_alloc(dev, 256);
            rdisk_set_buf_len(dev, BufLen, &max_len);
            memset(dev->buffer, 0, 256);
            if (cdb[2] == 1) {
                /*
                   This page is related to disk health status - setting
                   this page to 0 makes disk health read as "marginal".
                 */
                dev->buffer[0] = 0x58;
                dev->buffer[1] = 0x00;
                for (i = 0x00; i < 0x58; i++)
                    dev->buffer[i + 0x02] = 0xff;
            } else if (cdb[2] == 2) {
                dev->buffer[0] = 0x3d;
                dev->buffer[1] = 0x00;
                for (i = 0x00; i < 0x13; i++)
                    dev->buffer[i + 0x02] = 0x00;
                dev->buffer[0x15] = 0x00;
                if (dev->drv->read_only)
                    dev->buffer[0x15] |= 0x02;
                for (i = 0x00; i < 0x27; i++)
                    dev->buffer[i + 0x16] = 0x00;
            } else {
                rdisk_invalid_field(dev, cdb[2]);
                rdisk_buf_free(dev);
                return;
            }
            rdisk_data_command_finish(dev, 18, 18, cdb[4], 0);
            break;

        case GPCMD_REZERO_UNIT:
            dev->sector_pos = dev->sector_len = 0;
            rdisk_seek(dev, 0);
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);
            break;

        case GPCMD_REQUEST_SENSE:
            /*
               If there's a unit attention condition and there's a buffered not ready, a
               standalone REQUEST SENSE should forget about the not ready, and report unit
               attention straight away.
             */
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);
            max_len = cdb[4];

            if (!max_len) {
                rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                dev->packet_status = PHASE_COMPLETE;
                dev->callback      = 20.0 * RDISK_TIME;
                rdisk_set_callback(dev);
                break;
            }

            rdisk_buf_alloc(dev, 256);
            rdisk_set_buf_len(dev, BufLen, &max_len);
            len = (cdb[1] & 1) ? 8 : 18;
            rdisk_request_sense(dev, dev->buffer, max_len, cdb[1] & 1);
            rdisk_data_command_finish(dev, len, len, cdb[4], 0);
            break;

        case GPCMD_MECHANISM_STATUS:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);
            len = (cdb[8] << 8) | cdb[9];

            rdisk_buf_alloc(dev, 8);
            rdisk_set_buf_len(dev, BufLen, &len);

            memset(dev->buffer, 0, 8);
            dev->buffer[5] = 1;

            rdisk_data_command_finish(dev, 8, 8, len, 0);
            break;

        case GPCMD_READ_6:
        case GPCMD_READ_10:
        case GPCMD_READ_12:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);
            alloc_length = 512;

            switch (cdb[0]) {
                case GPCMD_READ_6:
                    dev->sector_len = cdb[4];
                    /*
                       For READ (6) and WRITE (6), a length of 0 indicates a
                       transfer of 256 sectors.
                     */
                    if (dev->sector_len == 0)
                        dev->sector_len = 256;
                    dev->sector_pos = ((((uint32_t) cdb[1]) & 0x1f) << 16) |
                                      (((uint32_t) cdb[2]) << 8) | ((uint32_t) cdb[3]);
                    rdisk_log(dev->log, "Length: %i, LBA: %i\n", dev->sector_len, dev->sector_pos);
                    break;
                case GPCMD_READ_10:
                    dev->sector_len = (cdb[7] << 8) | cdb[8];
                    dev->sector_pos = (cdb[2] << 24) | (cdb[3] << 16) |
                                      (cdb[4] << 8) | cdb[5];
                    rdisk_log(dev->log, "Length: %i, LBA: %i\n", dev->sector_len, dev->sector_pos);
                    break;
                case GPCMD_READ_12:
                    dev->sector_len = (((uint32_t) cdb[6]) << 24) |
                                      (((uint32_t) cdb[7]) << 16) |
                                      (((uint32_t) cdb[8]) << 8) | ((uint32_t) cdb[9]);
                    dev->sector_pos = (((uint32_t) cdb[2]) << 24) |
                                      (((uint32_t) cdb[3]) << 16) |
                                      (((uint32_t) cdb[4]) << 8) | ((uint32_t) cdb[5]);
                    break;

                default:
                    break;
            }

            if (dev->sector_pos >= dev->drv->medium_size)
                rdisk_lba_out_of_range(dev);
            else if (dev->sector_len) {
                max_len               = dev->sector_len;
                dev->requested_blocks = max_len;

                dev->packet_len = max_len * alloc_length;
                rdisk_buf_alloc(dev, dev->packet_len);

                const int ret = rdisk_blocks(dev, &alloc_length, 0);
                alloc_length  = dev->requested_blocks * 512;

                if (ret > 0) {
                    dev->requested_blocks = max_len;
                    dev->packet_len       = alloc_length;

                    rdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len);

                    rdisk_data_command_finish(dev, alloc_length, 512,
                                            alloc_length, 0);

                    ui_sb_update_icon(SB_RDISK | dev->id,
                                      dev->packet_status != PHASE_COMPLETE);
                } else {
                    rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                    dev->packet_status = (ret < 0) ? PHASE_ERROR : PHASE_COMPLETE;
                    dev->callback      = 20.0 * RDISK_TIME;
                    rdisk_set_callback(dev);
                    rdisk_buf_free(dev);
                }
            } else {
                rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                /* rdisk_log(dev->log, "All done - callback set\n"); */
                dev->packet_status = PHASE_COMPLETE;
                dev->callback      = 20.0 * RDISK_TIME;
                rdisk_set_callback(dev);
                break;
            }
            break;

        case GPCMD_VERIFY_6:
        case GPCMD_VERIFY_10:
        case GPCMD_VERIFY_12:
            if (!(cdb[1] & 2)) {
                rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                rdisk_command_complete(dev);
                break;
            }
            fallthrough;
        case GPCMD_WRITE_6:
        case GPCMD_WRITE_10:
        case GPCMD_WRITE_AND_VERIFY_10:
        case GPCMD_WRITE_12:
        case GPCMD_WRITE_AND_VERIFY_12:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_OUT);
            alloc_length = 512;

            if (dev->drv->read_only) {
                rdisk_write_protected(dev);
                break;
            }

            switch (cdb[0]) {
                case GPCMD_VERIFY_6:
                case GPCMD_WRITE_6:
                    dev->sector_len = cdb[4];
                    /*
                       For READ (6) and WRITE (6), a length of 0 indicates a
                       transfer of 256 sectors.
                     */
                    if (dev->sector_len == 0)
                        dev->sector_len = 256;
                    dev->sector_pos = ((((uint32_t) cdb[1]) & 0x1f) << 16) |
                                      (((uint32_t) cdb[2]) << 8) | ((uint32_t) cdb[3]);
                    break;
                case GPCMD_VERIFY_10:
                case GPCMD_WRITE_10:
                case GPCMD_WRITE_AND_VERIFY_10:
                    dev->sector_len = (cdb[7] << 8) | cdb[8];
                    dev->sector_pos = (cdb[2] << 24) | (cdb[3] << 16) |
                                      (cdb[4] << 8) | cdb[5];
                    rdisk_log(dev->log, "Length: %i, LBA: %i\n",
                            dev->sector_len, dev->sector_pos);
                    break;
                case GPCMD_VERIFY_12:
                case GPCMD_WRITE_12:
                case GPCMD_WRITE_AND_VERIFY_12:
                    dev->sector_len = (((uint32_t) cdb[6]) << 24) |
                                      (((uint32_t) cdb[7]) << 16) |
                                      (((uint32_t) cdb[8]) << 8) | ((uint32_t) cdb[9]);
                    dev->sector_pos = (((uint32_t) cdb[2]) << 24) |
                                      (((uint32_t) cdb[3]) << 16) |
                                      (((uint32_t) cdb[4]) << 8) | ((uint32_t) cdb[5]);
                    break;

                default:
                    break;
            }

            if (dev->sector_pos >= dev->drv->medium_size)
                rdisk_lba_out_of_range(dev);
            if (dev->sector_len) {
                max_len               = dev->sector_len;
                dev->requested_blocks = max_len;

                dev->packet_len = max_len * alloc_length;
                rdisk_buf_alloc(dev, dev->packet_len);

                dev->requested_blocks = max_len;
                dev->packet_len       = max_len << 9;

                rdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len);

                rdisk_data_command_finish(dev, dev->packet_len, 512,
                                        dev->packet_len, 1);

                ui_sb_update_icon_write(SB_RDISK | dev->id,
                                  dev->packet_status != PHASE_COMPLETE);
            } else {
                rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                /* rdisk_log(dev->log, "All done - callback set\n"); */
                dev->packet_status = PHASE_COMPLETE;
                dev->callback      = 20.0 * RDISK_TIME;
                rdisk_set_callback(dev);
            }
            break;

        case GPCMD_WRITE_SAME_10:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_OUT);
            alloc_length = 512;

            if ((cdb[1] & 6) == 6)
                rdisk_invalid_field(dev, cdb[1]);
            else {
                if (dev->drv->read_only)
                    rdisk_write_protected(dev);
                else {
                    dev->sector_len = (cdb[7] << 8) | cdb[8];
                    dev->sector_pos = (cdb[2] << 24) | (cdb[3] << 16) |
                                      (cdb[4] << 8) | cdb[5];

                    if (dev->sector_pos >= dev->drv->medium_size)
                        rdisk_lba_out_of_range(dev);
                    else if (dev->sector_len) {
                        rdisk_buf_alloc(dev, alloc_length);
                        rdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len);

                        max_len               = 1;
                        dev->requested_blocks = 1;

                        dev->packet_len = alloc_length;

                        rdisk_set_phase(dev, SCSI_PHASE_DATA_OUT);

                        rdisk_data_command_finish(dev, 512, 512,
                                                alloc_length, 1);

                        ui_sb_update_icon_write(SB_RDISK | dev->id,
                                          dev->packet_status != PHASE_COMPLETE);
                    } else {
                        rdisk_set_phase(dev, SCSI_PHASE_STATUS);
                        /* rdisk_log(dev->log, "All done - callback set\n"); */
                        dev->packet_status = PHASE_COMPLETE;
                        dev->callback      = 20.0 * RDISK_TIME;
                        rdisk_set_callback(dev);
                    }
                }
            }
            break;

        case GPCMD_MODE_SENSE_6:
        case GPCMD_MODE_SENSE_10:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);

            if (dev->drv->bus_type == RDISK_BUS_SCSI)
                block_desc = ((cdb[1] >> 3) & 1) ? 0 : 1;
            else
                block_desc = 0;

            if (cdb[0] == GPCMD_MODE_SENSE_6) {
                len = cdb[4];
                rdisk_buf_alloc(dev, 256);
            } else {
                len = (cdb[8] | (cdb[7] << 8));
                rdisk_buf_alloc(dev, 65536);
            }

            if (zip_100_mode_sense_page_flags & (1LL << (uint64_t) (cdb[2] & 0x3f))) {
                memset(dev->buffer, 0, len);
                alloc_length = len;

                if (cdb[0] == GPCMD_MODE_SENSE_6) {
                    len            = rdisk_mode_sense(dev, dev->buffer, 4, cdb[2],
                                                      block_desc);
                    len            = MIN(len, alloc_length);
                    dev->buffer[0] = len - 1;
                    dev->buffer[1] = 0;
                    if (block_desc)
                        dev->buffer[3] = 8;
                } else {
                    len            = rdisk_mode_sense(dev, dev->buffer, 8, cdb[2],
                                                      block_desc);
                    len            = MIN(len, alloc_length);
                    dev->buffer[0] = (len - 2) >> 8;
                    dev->buffer[1] = (len - 2) & 255;
                    dev->buffer[2] = 0;
                    if (block_desc) {
                        dev->buffer[6] = 0;
                        dev->buffer[7] = 8;
                    }
                }

                rdisk_set_buf_len(dev, BufLen, &len);

                rdisk_log(dev->log, "Reading mode page: %02X...\n", cdb[2]);

                rdisk_data_command_finish(dev, len, len, alloc_length, 0);
            } else {
                rdisk_invalid_field(dev, cdb[2]);
                rdisk_buf_free(dev);
            }
            break;

        case GPCMD_MODE_SELECT_6:
        case GPCMD_MODE_SELECT_10:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_OUT);

            if (cdb[0] == GPCMD_MODE_SELECT_6) {
                len = cdb[4];
                rdisk_buf_alloc(dev, 256);
            } else {
                len = (cdb[7] << 8) | cdb[8];
                rdisk_buf_alloc(dev, 65536);
            }

            rdisk_set_buf_len(dev, BufLen, &len);

            dev->total_length = len;
            dev->do_page_save = cdb[1] & 1;

            rdisk_data_command_finish(dev, len, len, len, 1);
            return;

        case GPCMD_START_STOP_UNIT:
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);

            switch (cdb[4] & 3) {
                case 0:                 /* Stop the disc. */
                    rdisk_eject(dev->id); /* The Iomega Windows 9x drivers require this. */
                    break;
                case 1: /* Start the disc and read the TOC. */
                    break;
                case 2: /* Eject the disc if possible. */
#if 0
                    rdisk_eject(dev->id);
#endif
                    break;
                case 3: /* Load the disc (close tray). */
                    rdisk_reload(dev->id);
                    break;

                default:
                    break;
            }

            rdisk_command_complete(dev);
            break;

        case GPCMD_INQUIRY:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);

            max_len = cdb[3];
            max_len <<= 8;
            max_len |= cdb[4];

            rdisk_buf_alloc(dev, 65536);

            if (cdb[1] & 1) {
                preamble_len = 4;
                size_idx     = 3;

                dev->buffer[idx++] = 0;
                dev->buffer[idx++] = cdb[2];
                dev->buffer[idx++] = 0;

                idx++;

                switch (cdb[2]) {
                    case 0x00:
                        dev->buffer[idx++] = 0x00;
                        dev->buffer[idx++] = 0x83;
                        break;
                    case 0x83:
                        if (idx + 24 > max_len) {
                            rdisk_data_phase_error(dev, cdb[2]);
                            rdisk_buf_free(dev);
                            return;
                        }

                        dev->buffer[idx++] = 0x02;
                        dev->buffer[idx++] = 0x00;
                        dev->buffer[idx++] = 0x00;
                        dev->buffer[idx++] = 20;
                        ide_padstr8(dev->buffer + idx, 20, "53R141"); /* Serial */
                        idx += 20;

                        if (idx + 72 > cdb[4])
                            goto atapi_out;
                        dev->buffer[idx++] = 0x02;
                        dev->buffer[idx++] = 0x01;
                        dev->buffer[idx++] = 0x00;
                        dev->buffer[idx++] = 68;
                        /* Vendor */
                        if (dev->drv->type >= RDISK_TYPE_ZIP_100)
                            ide_padstr8(dev->buffer + idx, 8, "IOMEGA  ");
                        else
                            ide_padstr8(dev->buffer + 8, 8, EMU_NAME);          /* Vendor */
                        idx += 8;
                        /* Product */
                        if (dev->drv->type == RDISK_TYPE_ZIP_250)
                            ide_padstr8(dev->buffer + idx, 40, "ZIP 250         ");
                        else if (dev->drv->type == RDISK_TYPE_ZIP_100)
                            ide_padstr8(dev->buffer + idx, 40, "ZIP 100         ");
                        else
                            ide_padstr8(dev->buffer + 16, 40, device_identify);      /* Product */
                        idx += 40;
                        ide_padstr8(dev->buffer + idx, 20, "53R141");
                        idx += 20;
                        break;
                    default:
                        rdisk_log(dev->log, "INQUIRY: Invalid page: %02X\n", cdb[2]);
                        rdisk_invalid_field(dev, cdb[2]);
                        rdisk_buf_free(dev);
                        return;
                }
            } else {
                preamble_len = 5;
                size_idx     = 4;

                memset(dev->buffer, 0, 8);
                if ((cdb[1] & 0xe0) || ((dev->cur_lun > 0x00) && (dev->cur_lun < 0xff)))
                    dev->buffer[0] = 0x7f;    /* No physical device on this LUN */
                else
                    dev->buffer[0] = 0x00;    /* Hard disk */
                dev->buffer[1] = 0x80;        /* Removable */
                /* SCSI-2 compliant */
                dev->buffer[2] = (dev->drv->bus_type == RDISK_BUS_SCSI) ? 0x02 : 0x00;
                dev->buffer[3] = (dev->drv->bus_type == RDISK_BUS_SCSI) ? 0x02 : 0x21;
#if 0
                dev->buffer[4] = 31;
#endif
                dev->buffer[4] = 0;
                if (dev->drv->bus_type == RDISK_BUS_SCSI) {
                    dev->buffer[6] = 1;       /* 16-bit transfers supported */
                    dev->buffer[7] = 0x20;    /* Wide bus supported */
                }
                dev->buffer[7] |= 0x02;

                ide_padstr8(dev->buffer + 8, 8, "IOMEGA  ");    /* Vendor */
                if (dev->drv->type == RDISK_TYPE_ZIP_250) {
                    /* Product */
                    ide_padstr8(dev->buffer + 16, 16, "ZIP 250         ");
                    /* Revision */
                    ide_padstr8(dev->buffer + 32, 4, "42.S");
                    /* Date? */
                    if (max_len >= 44)
                        ide_padstr8(dev->buffer + 36, 8, "08/08/01");
                    if (max_len >= 122)
                        ide_padstr8(dev->buffer + 96, 26, "(c) Copyright IOMEGA 2000 "); /* Copyright string */
                } else if (dev->drv->type == RDISK_TYPE_ZIP_100) {
                    /* Product */
                    ide_padstr8(dev->buffer + 16, 16, "ZIP 100         ");
                    /* Revision */
                    ide_padstr8(dev->buffer + 32, 4, "E.08");
                } else {
                    ide_padstr8(dev->buffer + 8, 8,
                                EMU_NAME);          /* Vendor */
                    ide_padstr8(dev->buffer + 16, 16,
                                device_identify);      /* Product */
                    ide_padstr8(dev->buffer + 32, 4,
                                EMU_VERSION_EX);    /* Revision */
                }
                idx = 36;

                if (max_len == 96) {
                    dev->buffer[4] = 91;
                    idx            = 96;
                } else if (max_len == 128) {
                    dev->buffer[4] = 0x75;
                    idx            = 128;
                }
            }

atapi_out:
            dev->buffer[size_idx] = idx - preamble_len;
            len                   = idx;

            len = MIN(len, max_len);
            rdisk_set_buf_len(dev, BufLen, &len);

            rdisk_data_command_finish(dev, len, len, max_len, 0);
            break;

        case GPCMD_PREVENT_REMOVAL:
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);
            rdisk_command_complete(dev);
            break;

        case GPCMD_SEEK_6:
        case GPCMD_SEEK_10:
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);

            switch (cdb[0]) {
                case GPCMD_SEEK_6:
                    pos = (cdb[2] << 8) | cdb[3];
                    break;
                case GPCMD_SEEK_10:
                    pos = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5];
                    break;

                default:
                    break;
            }
            rdisk_seek(dev, pos);
            rdisk_command_complete(dev);
            break;

        case GPCMD_READ_CDROM_CAPACITY:
            rdisk_set_phase(dev, SCSI_PHASE_DATA_IN);

            rdisk_buf_alloc(dev, 8);

            /* IMPORTANT: What's returned is the last LBA block. */
            max_len = dev->drv->medium_size - 1;
            memset(dev->buffer, 0, 8);
            dev->buffer[0] = (max_len >> 24) & 0xff;
            dev->buffer[1] = (max_len >> 16) & 0xff;
            dev->buffer[2] = (max_len >> 8) & 0xff;
            dev->buffer[3] = max_len & 0xff;
            dev->buffer[6] = 2; /* 512 = 0x0200 */
            len            = 8;

            rdisk_set_buf_len(dev, BufLen, &len);

            rdisk_data_command_finish(dev, len, len, len, 0);
            break;

        case GPCMD_IOMEGA_EJECT:
            rdisk_set_phase(dev, SCSI_PHASE_STATUS);
            rdisk_eject(dev->id);
            rdisk_command_complete(dev);
            break;

        case GPCMD_READ_FORMAT_CAPACITIES:
            len = (cdb[7] << 8) | cdb[8];

            rdisk_buf_alloc(dev, len);
            memset(dev->buffer, 0, len);

            pos = 0;

            /* List header */
            dev->buffer[pos++] = 0;
            dev->buffer[pos++] = 0;
            dev->buffer[pos++] = 0;
            if (dev->drv->fp != NULL)
                dev->buffer[pos++] = 16;
            else
                dev->buffer[pos++] = 8;

            /* Current/Maximum capacity header */
            if (dev->drv->type == RDISK_TYPE_ZIP_100) {
                /* ZIP 100 only supports ZIP 100 media, so we always return the ZIP 100 size. */
                dev->buffer[pos++] = (ZIP_SECTORS >> 24) & 0xff;
                dev->buffer[pos++] = (ZIP_SECTORS >> 16) & 0xff;
                dev->buffer[pos++] = (ZIP_SECTORS >> 8) & 0xff;
                dev->buffer[pos++] = ZIP_SECTORS & 0xff;
                if (dev->drv->fp != NULL)
                    dev->buffer[pos++] = 2;
                else
                    dev->buffer[pos++] = 3;
            } else {
                /* ZIP 250 also supports ZIP 100 media, so if the medium is inserted,
                   we return the inserted medium's size, otherwise, the ZIP 250 size. */
                if (dev->drv->fp != NULL) {
                    dev->buffer[pos++] = (dev->drv->medium_size >> 24) & 0xff;
                    dev->buffer[pos++] = (dev->drv->medium_size >> 16) & 0xff;
                    dev->buffer[pos++] = (dev->drv->medium_size >> 8) & 0xff;
                    dev->buffer[pos++] = dev->drv->medium_size & 0xff;
                    dev->buffer[pos++] = 2; /* Current medium capacity */
                } else {
                    dev->buffer[pos++] = (ZIP_250_SECTORS >> 24) & 0xff;
                    dev->buffer[pos++] = (ZIP_250_SECTORS >> 16) & 0xff;
                    dev->buffer[pos++] = (ZIP_250_SECTORS >> 8) & 0xff;
                    dev->buffer[pos++] = ZIP_250_SECTORS & 0xff;
                    dev->buffer[pos++] = 3; /* Maximum medium capacity */
                }
            }

            dev->buffer[pos++] = 512 >> 16;
            dev->buffer[pos++] = 512 >> 8;
            dev->buffer[pos++] = 512 & 0xff;

            if (dev->drv->fp != NULL) {
                /* Formattable capacity descriptor */
                dev->buffer[pos++] = (dev->drv->medium_size >> 24) & 0xff;
                dev->buffer[pos++] = (dev->drv->medium_size >> 16) & 0xff;
                dev->buffer[pos++] = (dev->drv->medium_size >> 8) & 0xff;
                dev->buffer[pos++] = dev->drv->medium_size & 0xff;
                dev->buffer[pos++] = 0;
                dev->buffer[pos++] = 512 >> 16;
                dev->buffer[pos++] = 512 >> 8;
                dev->buffer[pos++] = 512 & 0xff;
            }

            rdisk_set_buf_len(dev, BufLen, &len);

            rdisk_data_command_finish(dev, len, len, len, 0);
            break;

        default:
            rdisk_illegal_opcode(dev, cdb[0]);
            break;
    }

#if 0
    rdisk_log(dev->log, "Phase: %02X, request length: %i\n",
            dev->tf->phase, dev->tf->request_length);
#endif

    if ((dev->packet_status == PHASE_COMPLETE) || (dev->packet_status == PHASE_ERROR))
        rdisk_buf_free(dev);
}

static void
rdisk_command_stop(scsi_common_t *sc)
{
    rdisk_t *dev = (rdisk_t *) sc;

    rdisk_command_complete(dev);
    rdisk_buf_free(dev);
}

/* The command second phase function, needed for Mode Select. */
static uint8_t
rdisk_phase_data_out(scsi_common_t *sc)
{
    rdisk_t *dev              = (rdisk_t *) sc;
    int      len            = 0;
    uint8_t  error          = 0;
    uint32_t last_to_write;
    uint32_t i;
    uint16_t block_desc_len;
    uint16_t pos;
    uint16_t param_list_len;
    uint8_t  hdr_len;
    uint8_t  val;

    switch (dev->current_cdb[0]) {
        case GPCMD_VERIFY_6:
        case GPCMD_VERIFY_10:
        case GPCMD_VERIFY_12:
            break;
        case GPCMD_WRITE_6:
        case GPCMD_WRITE_10:
        case GPCMD_WRITE_AND_VERIFY_10:
        case GPCMD_WRITE_12:
        case GPCMD_WRITE_AND_VERIFY_12:
            if (dev->requested_blocks > 0)
                rdisk_blocks(dev, &len, 1);
            break;
        case GPCMD_WRITE_SAME_10:
            if (!dev->current_cdb[7] && !dev->current_cdb[8]) {
                last_to_write = (dev->drv->medium_size - 1);
            } else
                last_to_write = dev->sector_pos + dev->sector_len - 1;

            for (i = dev->sector_pos; i <= last_to_write; i++) {
                if (dev->current_cdb[1] & 2) {
                    dev->buffer[0] = (i >> 24) & 0xff;
                    dev->buffer[1] = (i >> 16) & 0xff;
                    dev->buffer[2] = (i >> 8) & 0xff;
                    dev->buffer[3] = i & 0xff;
                } else if (dev->current_cdb[1] & 4) {
                    /* CHS are 96, 1, 2048 (RDISK 100) and 239, 1, 2048 (RDISK 250) */
                    const uint32_t s = (i % 2048);
                    const uint32_t h = ((i - s) / 2048) % 1;
                    const uint32_t c = ((i - s) / 2048) / 1;
                    dev->buffer[0]   = (c >> 16) & 0xff;
                    dev->buffer[1]   = (c >> 8) & 0xff;
                    dev->buffer[2]   = c & 0xff;
                    dev->buffer[3]   = h & 0xff;
                    dev->buffer[4]   = (s >> 24) & 0xff;
                    dev->buffer[5]   = (s >> 16) & 0xff;
                    dev->buffer[6]   = (s >> 8) & 0xff;
                    dev->buffer[7]   = s & 0xff;
                }
                if (fseek(dev->drv->fp, dev->drv->base + (i << 9),
                          SEEK_SET) == -1)
                    log_fatal(dev->log, "rdisk_phase_data_out(): Error seeking\n");
                if (fwrite(dev->buffer, 1, 512, dev->drv->fp) != 512)
                    log_fatal(dev->log, "rdisk_phase_data_out(): Error writing data\n");
            }

            fflush(dev->drv->fp);
            break;
        case GPCMD_MODE_SELECT_6:
        case GPCMD_MODE_SELECT_10:
            if (dev->current_cdb[0] == GPCMD_MODE_SELECT_10) {
                hdr_len        = 8;
                param_list_len = dev->current_cdb[7];
                param_list_len <<= 8;
                param_list_len |= dev->current_cdb[8];
            } else {
                hdr_len        = 4;
                param_list_len = dev->current_cdb[4];
            }

            if (dev->drv->bus_type == RDISK_BUS_SCSI) {
                if (dev->current_cdb[0] == GPCMD_MODE_SELECT_6) {
                    block_desc_len = dev->buffer[2];
                    block_desc_len <<= 8;
                    block_desc_len |= dev->buffer[3];
                } else {
                    block_desc_len = dev->buffer[6];
                    block_desc_len <<= 8;
                    block_desc_len |= dev->buffer[7];
                }
            } else
                block_desc_len = 0;

            pos = hdr_len + block_desc_len;

            while (1) {
                if (pos >= param_list_len) {
                    rdisk_log(dev->log, "Buffer has only block descriptor\n");
                    break;
                }

                const uint8_t page     = dev->buffer[pos] & 0x3f;
                const uint8_t page_len = dev->buffer[pos + 1];

                pos += 2;

                if (!(zip_100_mode_sense_page_flags & (1LL << ((uint64_t) page))))
                    error |= 1;
                else for (i = 0; i < page_len; i++) {
                    const uint8_t old_val = dev->ms_pages_saved.pages[page][i + 2];
                    const uint8_t ch      = zip_100_mode_sense_pages_changeable.pages[page][i + 2];
                    val                   = dev->buffer[pos + i];
                    if (val != old_val) {
                        if (ch)
                            dev->ms_pages_saved.pages[page][i + 2] = val;
                        else {
                            error |= 1;
                            rdisk_invalid_field_pl(dev, val);
                        }
                    }
                }

                pos += page_len;

                if (dev->drv->bus_type == RDISK_BUS_SCSI)
                    val = zip_100_mode_sense_pages_default_scsi.pages[page][0] & 0x80;
                else
                    val = zip_100_mode_sense_pages_default.pages[page][0] & 0x80;
                if (dev->do_page_save && val)
                    rdisk_mode_sense_save(dev);

                if (pos >= dev->total_length)
                    break;
            }

            if (error) {
                rdisk_buf_free(dev);
                return 0;
            }
            break;

        default:
            break;
    }

    rdisk_command_stop((scsi_common_t *) dev);
    return 1;
}

/* Peform a master init on the entire module. */
void
rdisk_global_init(void)
{
    /* Clear the global data. */
    memset(rdisk_drives, 0x00, sizeof(rdisk_drives));
}

static int
rdisk_get_max(UNUSED(const ide_t *ide), const int ide_has_dma, const int type)
{
    int ret;

    switch (type) {
        case TYPE_PIO:
            ret = 3;
            break;
        case TYPE_SDMA:
        default:
            ret = -1;
            break;
        case TYPE_MDMA:
            ret = ide_has_dma ? 1 : -1;
            break;
        case TYPE_UDMA:
            ret = ide_has_dma ? 5 : -1;
            break;
    }

    return ret;
}

static int
rdisk_get_timings(UNUSED(const ide_t *ide), const int ide_has_dma, const int type)
{
    int ret;

    switch (type) {
        case TIMINGS_DMA:
            ret = ide_has_dma ? 0x96 : 0;
            break;
        case TIMINGS_PIO:
            ret = 0xf0;
            break;
        case TIMINGS_PIO_FC:
            ret = 0xb4;
            break;
        default:
            ret = 0;
            break;
    }

    return ret;
}

static void
rdisk_zip_100_identify(const ide_t *ide)
{
    ide_padstr((char *) (ide->buffer + 23), "E.08", 8);                  /* Firmware */
    ide_padstr((char *) (ide->buffer + 27), "IOMEGA ZIP 100 ATAPI", 40); /* Model */
}

static void
rdisk_zip_250_identify(const ide_t *ide, const int ide_has_dma)
{
    /* Firmware */
    ide_padstr((char *) (ide->buffer + 23), "42.S", 8);
    /* Model */
    ide_padstr((char *) (ide->buffer + 27), "IOMEGA  ZIP 250       ATAPI", 40);

    if (ide_has_dma) {
        ide->buffer[80] = 0x70;    /* Supported ATA versions : ATA/ATAPI-4 ATA/ATAPI-6 */
        /* Maximum ATA revision supported : ATA/ATAPI-6 T13 1410D revision 3a */
        ide->buffer[81] = 0x19;
    }
}

static void
rdisk_generic_identify(const ide_t *ide, const int ide_has_dma, const rdisk_t *rdisk)
{
    char model[40];

    memset(model, 0, 40);
    snprintf(model, 40, "%s %s%02i", EMU_NAME, "86B_RD", rdisk->id);
    ide_padstr((char *) (ide->buffer + 23), EMU_VERSION_EX, 8);    /* Firmware */
    ide_padstr((char *) (ide->buffer + 27), model, 40);               /* Model */

    if (ide_has_dma) {
        ide->buffer[80] = 0x70;    /* Supported ATA versions : ATA/ATAPI-4 ATA/ATAPI-6 */
        /* Maximum ATA revision supported : ATA/ATAPI-6 T13 1410D revision 3a */
        ide->buffer[81] = 0x19;
    }
}

static void
rdisk_identify(const ide_t *ide, const int ide_has_dma)
{
    const rdisk_t *rdisk = (rdisk_t *) ide->sc;

    /*
       ATAPI device, direct-access device, removable media, interrupt DRQ:

       Using (2 << 5) below makes the ASUS P/I-P54TP4XE misdentify the RDISK drive
       as a LS-120.
     */
    ide->buffer[0] = 0x8000 | (0 << 8) | 0x80 | (1 << 5);
    ide_padstr((char *) (ide->buffer + 10), "", 20);    /* Serial Number */
    ide->buffer[49]  = 0x200;                                  /* LBA supported */
    /* Interpret zero byte count limit as maximum length */
    ide->buffer[126] = 0xfffe;

    if (rdisk_drives[rdisk->id].type == RDISK_TYPE_ZIP_250)
        rdisk_zip_250_identify(ide, ide_has_dma);
    else if (rdisk_drives[rdisk->id].type == RDISK_TYPE_ZIP_100)
        rdisk_zip_100_identify(ide);
    else
        rdisk_generic_identify(ide, ide_has_dma, rdisk);
}

static void
rdisk_drive_reset(const int c)
{
    const uint8_t scsi_bus = (rdisk_drives[c].scsi_device_id >> 4) & 0x0f;
    const uint8_t scsi_id  = rdisk_drives[c].scsi_device_id & 0x0f;

    if (rdisk_drives[c].priv == NULL) {
        rdisk_drives[c].priv = (rdisk_t *) calloc(1, sizeof(rdisk_t));
        rdisk_t *dev         = (rdisk_t *) rdisk_drives[c].priv;

        char n[1024]       = { 0 };

        sprintf(n, "Removable Disk %i", c + 1);
        dev->log           = log_open(n);
    }

    rdisk_t *dev = (rdisk_t *) rdisk_drives[c].priv;

    dev->id      = c;
    dev->cur_lun = SCSI_LUN_USE_CDB;

    if (rdisk_drives[c].bus_type == RDISK_BUS_SCSI) {
        if (dev->tf == NULL)
            dev->tf        = (ide_tf_t *) calloc(1, sizeof(ide_tf_t));

        /* SCSI RDISK, attach to the SCSI bus. */
        scsi_device_t *sd = &scsi_devices[scsi_bus][scsi_id];

        sd->sc             = (scsi_common_t *) dev;
        sd->command        = rdisk_command;
        sd->request_sense  = rdisk_request_sense_for_scsi;
        sd->reset          = rdisk_reset;
        sd->phase_data_out = rdisk_phase_data_out;
        sd->command_stop   = rdisk_command_stop;
        sd->type           = SCSI_REMOVABLE_DISK;
    } else if (rdisk_drives[c].bus_type == RDISK_BUS_ATAPI) {
        /* ATAPI CD-ROM, attach to the IDE bus. */
        ide_t         *id = ide_get_drive(rdisk_drives[c].ide_channel);
        /* If the IDE channel is initialized, we attach to it,
           otherwise, we do nothing - it's going to be a drive
           that's not attached to anything. */
        if (id) {
            id->sc               = (scsi_common_t *) dev;
            dev->tf              = id->tf;
            IDE_ATAPI_IS_EARLY   = 0;
            id->get_max          = rdisk_get_max;
            id->get_timings      = rdisk_get_timings;
            id->identify         = rdisk_identify;
            id->stop             = NULL;
            id->packet_command   = rdisk_command;
            id->device_reset     = rdisk_reset;
            id->phase_data_out   = rdisk_phase_data_out;
            id->command_stop     = rdisk_command_stop;
            id->bus_master_error = rdisk_bus_master_error;
            id->interrupt_drq    = 1;

            ide_atapi_attach(id);
        }
    }
}

void
rdisk_hard_reset(void)
{
    for (uint8_t c = 0; c < RDISK_NUM; c++) {
        if ((rdisk_drives[c].bus_type == RDISK_BUS_ATAPI) || (rdisk_drives[c].bus_type == RDISK_BUS_SCSI)) {

            if (rdisk_drives[c].bus_type == RDISK_BUS_SCSI) {
                const uint8_t scsi_bus = (rdisk_drives[c].scsi_device_id >> 4) & 0x0f;
                const uint8_t scsi_id  = rdisk_drives[c].scsi_device_id & 0x0f;

                /* Make sure to ignore any SCSI RDISK drive that has an out of range SCSI bus. */
                if (scsi_bus >= SCSI_BUS_MAX)
                    continue;

                /* Make sure to ignore any SCSI RDISK drive that has an out of range ID. */
                if (scsi_id >= SCSI_ID_MAX)
                    continue;
            }

            /* Make sure to ignore any ATAPI RDISK drive that has an out of range IDE channel. */
            if ((rdisk_drives[c].bus_type == RDISK_BUS_ATAPI) && (rdisk_drives[c].ide_channel > 7))
                continue;

            rdisk_drive_reset(c);

            rdisk_t *dev = (rdisk_t *) rdisk_drives[c].priv;

            rdisk_log(dev->log, "Removable Disk hard_reset drive=%d\n", c);

            if (dev->tf == NULL)
                continue;

            dev->id  = c;
            dev->drv = &rdisk_drives[c];

            rdisk_init(dev);

            if (strlen(rdisk_drives[c].image_path))
                rdisk_load(dev, rdisk_drives[c].image_path, 0);

            rdisk_mode_sense_load(dev);

            if (rdisk_drives[c].bus_type == RDISK_BUS_SCSI)
                rdisk_log(dev->log, "SCSI RDISK drive %i attached to SCSI ID %i\n",
                        c, rdisk_drives[c].scsi_device_id);
            else if (rdisk_drives[c].bus_type == RDISK_BUS_ATAPI)
                rdisk_log(dev->log, "ATAPI RDISK drive %i attached to IDE channel %i\n",
                        c, rdisk_drives[c].ide_channel);
        }
    }
}

void
rdisk_close(void)
{
    for (uint8_t c = 0; c < RDISK_NUM; c++) {
        if (rdisk_drives[c].bus_type == RDISK_BUS_SCSI) {
            const uint8_t scsi_bus = (rdisk_drives[c].scsi_device_id >> 4) & 0x0f;
            const uint8_t scsi_id  = rdisk_drives[c].scsi_device_id & 0x0f;

            memset(&scsi_devices[scsi_bus][scsi_id], 0x00, sizeof(scsi_device_t));
        }

        rdisk_t *dev = (rdisk_t *) rdisk_drives[c].priv;

        if (dev) {
            rdisk_disk_unload(dev);

            if (dev->tf)
                free(dev->tf);

            if (dev->log != NULL) {
                rdisk_log(dev->log, "Log closed\n");

                log_close(dev->log);
                dev->log = NULL;
            }

            free(dev);
            rdisk_drives[c].priv = NULL;
        }
    }
}
