diff --git a/src/86box.c b/src/86box.c index cf992df4c..69ab487c6 100644 --- a/src/86box.c +++ b/src/86box.c @@ -91,6 +91,7 @@ #include <86box/cdrom.h> #include <86box/cdrom_interface.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/scsi_disk.h> #include <86box/cdrom_image.h> @@ -1292,6 +1293,7 @@ usage: mouse_init(); cdrom_global_init(); rdisk_global_init(); + superdisk_global_init(); mo_global_init(); /* Initialize the keyboard accelerator list with default values */ @@ -1651,6 +1653,8 @@ pc_reset_hard_close(void) rdisk_close(); + superdisk_close(); + mo_close(); scsi_disk_close(); @@ -1767,7 +1771,7 @@ pc_reset_hard_init(void) mo_hard_reset(); rdisk_hard_reset(); - + superdisk_hard_reset(); /* Reset any ISA ROM cards. */ isarom_reset(); @@ -1937,6 +1941,8 @@ pc_close(UNUSED(thread_t *ptr)) rdisk_close(); + superdisk_close(); + mo_close(); scsi_disk_close(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b50879eb..e039d1c08 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,6 +127,7 @@ target_link_libraries(86Box game cdrom rdisk + superdisk mo hdd net diff --git a/src/config.c b/src/config.c index edd713755..a8998e989 100644 --- a/src/config.c +++ b/src/config.c @@ -74,6 +74,7 @@ #include <86box/cdrom.h> #include <86box/cdrom_interface.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/sound.h> #include <86box/midi.h> @@ -1956,6 +1957,118 @@ load_other_removable_devices(void) } go_to_mo: + memset(temp, 0x00, sizeof(temp)); + for (c = 0; c < SUPERDISK_NUM; c++) { + sprintf(temp, "superdisk_%02i_parameters", c + 1); + p = ini_section_get_string(cat, temp, NULL); + if (p != NULL) + sscanf(p, "%01u, %s", &superdisk_drives[c].is_240, s); + else + sscanf("0, none", "%01u, %s", &superdisk_drives[c].is_240, s); + superdisk_drives[c].bus_type = hdd_string_to_bus(s, 1); + + /* Default values, needed for proper operation of the Settings dialog. */ + superdisk_drives[c].ide_channel = superdisk_drives[c].scsi_device_id = c + 2; + + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_ATAPI) { + sprintf(temp, "superdisk_%02i_ide_channel", c + 1); + sprintf(tmp2, "%01u:%01u", (c + 2) >> 1, (c + 2) & 1); + p = ini_section_get_string(cat, temp, tmp2); + sscanf(p, "%01u:%01u", &board, &dev); + board &= 3; + dev &= 1; + superdisk_drives[c].ide_channel = (board << 1) + dev; + + if (superdisk_drives[c].ide_channel > 7) + superdisk_drives[c].ide_channel = 7; + } else if (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI) { + sprintf(temp, "superdisk_%02i_scsi_location", c + 1); + sprintf(tmp2, "%01u:%02u", SCSI_BUS_MAX, c + 2); + p = ini_section_get_string(cat, temp, tmp2); + sscanf(p, "%01u:%02u", &board, &dev); + if (board >= SCSI_BUS_MAX) { + /* Invalid bus - check legacy ID */ + sprintf(temp, "superdisk_%02i_scsi_id", c + 1); + superdisk_drives[c].scsi_device_id = ini_section_get_int(cat, temp, c + 2); + + if (superdisk_drives[c].scsi_device_id > 15) + superdisk_drives[c].scsi_device_id = 15; + } else { + board %= SCSI_BUS_MAX; + dev &= 15; + superdisk_drives[c].scsi_device_id = (board << 4) + dev; + } + } + + if (superdisk_drives[c].bus_type != SUPERDISK_BUS_ATAPI) { + sprintf(temp, "superdisk_%02i_ide_channel", c + 1); + ini_section_delete_var(cat, temp); + } + + if (superdisk_drives[c].bus_type != SUPERDISK_BUS_SCSI) { + sprintf(temp, "superdisk_%02i_scsi_location", c + 1); + ini_section_delete_var(cat, temp); + } + + sprintf(temp, "superdisk_%02i_scsi_id", c + 1); + ini_section_delete_var(cat, temp); + + sprintf(temp, "superdisk_%02i_image_path", c + 1); + p = ini_section_get_string(cat, temp, ""); + + if (!strcmp(p, usr_path)) + p[0] = 0x00; + + if (p[0] != 0x00) { + if (path_abs(p)) { + if (strlen(p) > 511) + fatal("Configuration: Length of superdisk_%02i_image_path is more than 511\n", c + 1); + else + strncpy(superdisk_drives[c].image_path, p, 511); + } else + path_append_filename(superdisk_drives[c].image_path, usr_path, p); + path_normalize(superdisk_drives[c].image_path); + } + + for (int i = 0; i < MAX_PREV_IMAGES; i++) { + superdisk_drives[c].image_history[i] = (char *) calloc((MAX_IMAGE_PATH_LEN + 1) << 1, sizeof(char)); + sprintf(temp, "superdisk_%02i_image_history_%02i", c + 1, i + 1); + p = ini_section_get_string(cat, temp, NULL); + if (p) { + if (path_abs(p)) { + if (strlen(p) > (MAX_IMAGE_PATH_LEN - 1)) + fatal("Configuration: Length of superdisk_%02i_image_history_%02i is more than %i\n", + c + 1, i + 1, MAX_IMAGE_PATH_LEN - 1); + else + snprintf(superdisk_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s", p); + } else + snprintf(superdisk_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, + path_get_slash(usr_path), p); + path_normalize(superdisk_drives[c].image_history[i]); + } + } + + /* If the SuperDisk drive is disabled, delete all its variables. */ + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_DISABLED) { + sprintf(temp, "superdisk_%02i_parameters", c + 1); + ini_section_delete_var(cat, temp); + + sprintf(temp, "superdisk_%02i_ide_channel", c + 1); + ini_section_delete_var(cat, temp); + + sprintf(temp, "superdisk_%02i_scsi_location", c + 1); + ini_section_delete_var(cat, temp); + + sprintf(temp, "superdisk_%02i_image_path", c + 1); + ini_section_delete_var(cat, temp); + + for (int i = 0; i < MAX_PREV_IMAGES; i++) { + sprintf(temp, "superdisk_%02i_image_history_%02i", c + 1, i + 1); + ini_section_delete_var(cat, temp); + } + } + } + memset(temp, 0x00, sizeof(temp)); for (c = 0; c < MO_NUM; c++) { sprintf(temp, "mo_%02i_parameters", c + 1); @@ -2231,6 +2344,7 @@ config_load(void) memset(cdrom_ioctl, 0, sizeof(cdrom_ioctl_t) * CDROM_NUM); #endif memset(rdisk_drives, 0, sizeof(rdisk_drive_t)); + memset(superdisk_drives, 0, sizeof(superdisk_drive_t)); for (int i = 0; i < 768; i++) scancode_config_map[i] = i; @@ -3720,6 +3834,49 @@ save_other_removable_devices(void) } } + for (c = 0; c < SUPERDISK_NUM; c++) { + sprintf(temp, "superdisk_%02i_parameters", c + 1); + if (superdisk_drives[c].bus_type == 0) { + ini_section_delete_var(cat, temp); + } else { + sprintf(tmp2, "%u, %s", superdisk_drives[c].is_240, + hdd_bus_to_string(superdisk_drives[c].bus_type, 1)); + ini_section_set_string(cat, temp, tmp2); + } + + sprintf(temp, "superdisk_%02i_ide_channel", c + 1); + if (superdisk_drives[c].bus_type != SUPERDISK_BUS_ATAPI) + ini_section_delete_var(cat, temp); + else { + sprintf(tmp2, "%01u:%01u", superdisk_drives[c].ide_channel >> 1, + superdisk_drives[c].ide_channel & 1); + ini_section_set_string(cat, temp, tmp2); + } + + sprintf(temp, "superdisk_%02i_scsi_id", c + 1); + ini_section_delete_var(cat, temp); + + sprintf(temp, "superdisk_%02i_scsi_location", c + 1); + if (superdisk_drives[c].bus_type != SUPERDISK_BUS_SCSI) + ini_section_delete_var(cat, temp); + else { + sprintf(tmp2, "%01u:%02u", superdisk_drives[c].scsi_device_id >> 4, + superdisk_drives[c].scsi_device_id & 15); + ini_section_set_string(cat, temp, tmp2); + } + + sprintf(temp, "superdisk_%02i_image_path", c + 1); + if ((superdisk_drives[c].bus_type == 0) || (strlen(superdisk_drives[c].image_path) == 0)) + ini_section_delete_var(cat, temp); + else { + path_normalize(superdisk_drives[c].image_path); + if (!strnicmp(superdisk_drives[c].image_path, usr_path, strlen(usr_path))) + ini_section_set_string(cat, temp, &superdisk_drives[c].image_path[strlen(usr_path)]); + else + ini_section_set_string(cat, temp, superdisk_drives[c].image_path); + } + } + for (c = 0; c < MO_NUM; c++) { sprintf(temp, "mo_%02i_parameters", c + 1); if (mo_drives[c].bus_type == 0) { diff --git a/src/disk/CMakeLists.txt b/src/disk/CMakeLists.txt index fff17a5e4..b4a34859d 100644 --- a/src/disk/CMakeLists.txt +++ b/src/disk/CMakeLists.txt @@ -43,5 +43,7 @@ add_library(rdisk OBJECT rdisk.c) add_library(mo OBJECT mo.c) +add_library(superdisk OBJECT superdisk.c) + add_subdirectory(minivhd) target_link_libraries(86Box minivhd) diff --git a/src/disk/hdc_ide_cmd640.c b/src/disk/hdc_ide_cmd640.c index 44e488df4..50d22763c 100644 --- a/src/disk/hdc_ide_cmd640.c +++ b/src/disk/hdc_ide_cmd640.c @@ -36,6 +36,7 @@ #include <86box/hdc_ide.h> #include <86box/hdc_ide_sff8038i.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> typedef struct cmd640_t { @@ -418,11 +419,19 @@ cmd640_reset(void *priv) (cdrom[i].ide_channel <= max_channel) && cdrom[i].priv) scsi_cdrom_reset((scsi_common_t *) cdrom[i].priv); } + for (i = 0; i < RDISK_NUM; i++) { if ((rdisk_drives[i].bus_type == RDISK_BUS_ATAPI) && (rdisk_drives[i].ide_channel >= min_channel) && (rdisk_drives[i].ide_channel <= max_channel) && rdisk_drives[i].priv) rdisk_reset((scsi_common_t *) rdisk_drives[i].priv); } + + for (i = 0; i < SUPERDISK_NUM; i++) { + if ((superdisk_drives[i].bus_type == SUPERDISK_BUS_ATAPI) && (superdisk_drives[i].ide_channel >= min_channel) && + (superdisk_drives[i].ide_channel <= max_channel) && superdisk_drives[i].priv) + superdisk_reset((scsi_common_t *) superdisk_drives[i].priv); + } + for (i = 0; i < MO_NUM; i++) { if ((mo_drives[i].bus_type == MO_BUS_ATAPI) && (mo_drives[i].ide_channel >= min_channel) && (mo_drives[i].ide_channel <= max_channel) && mo_drives[i].priv) diff --git a/src/disk/hdc_ide_cmd646.c b/src/disk/hdc_ide_cmd646.c index 9cd50e871..104ad86eb 100644 --- a/src/disk/hdc_ide_cmd646.c +++ b/src/disk/hdc_ide_cmd646.c @@ -36,6 +36,7 @@ #include <86box/hdc_ide.h> #include <86box/hdc_ide_sff8038i.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/rom.h> #include <86box/hdd.h> #include <86box/scsi_disk.h> @@ -542,10 +543,17 @@ cmd646_reset(void *priv) if ((cdrom[i].bus_type == CDROM_BUS_ATAPI) && check_ch(dev, cdrom[i].ide_channel) && cdrom[i].priv) scsi_cdrom_reset((scsi_common_t *) cdrom[i].priv); } + for (i = 0; i < RDISK_NUM; i++) { if ((rdisk_drives[i].bus_type == RDISK_BUS_ATAPI) && check_ch(dev, rdisk_drives[i].ide_channel) && rdisk_drives[i].priv) rdisk_reset((scsi_common_t *) rdisk_drives[i].priv); } + + for (i = 0; i < SUPERDISK_NUM; i++) { + if ((superdisk_drives[i].bus_type == SUPERDISK_BUS_ATAPI) && (superdisk_drives[i].ide_channel < 4) && superdisk_drives[i].priv) + superdisk_reset((scsi_common_t *) superdisk_drives[i].priv); + } + for (i = 0; i < MO_NUM; i++) { if ((mo_drives[i].bus_type == MO_BUS_ATAPI) && check_ch(dev, mo_drives[i].ide_channel) && mo_drives[i].priv) mo_reset((scsi_common_t *) mo_drives[i].priv); diff --git a/src/disk/hdc_ide_sff8038i.c b/src/disk/hdc_ide_sff8038i.c index 15dd4c515..67d737bff 100644 --- a/src/disk/hdc_ide_sff8038i.c +++ b/src/disk/hdc_ide_sff8038i.c @@ -41,6 +41,7 @@ #include <86box/hdc_ide.h> #include <86box/hdc_ide_sff8038i.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/plat_unused.h> @@ -488,6 +489,7 @@ sff_reset(void *priv) if ((hdd[i].bus_type == HDD_BUS_ATAPI) && (hdd[i].ide_channel < 4) && hdd[i].priv) scsi_disk_reset((scsi_common_t *) hdd[i].priv); } + for (uint8_t i = 0; i < CDROM_NUM; i++) { if ((cdrom[i].bus_type == CDROM_BUS_ATAPI) && (cdrom[i].ide_channel < 4) && cdrom[i].priv) @@ -498,6 +500,12 @@ sff_reset(void *priv) rdisk_drives[i].priv) rdisk_reset((scsi_common_t *) rdisk_drives[i].priv); } + + for (uint8_t i = 0; i < SUPERDISK_NUM; i++) { + if ((superdisk_drives[i].bus_type == SUPERDISK_BUS_ATAPI) && (superdisk_drives[i].ide_channel < 4) && superdisk_drives[i].priv) + superdisk_reset((scsi_common_t *) superdisk_drives[i].priv); + } + for (uint8_t i = 0; i < MO_NUM; i++) { if ((mo_drives[i].bus_type == MO_BUS_ATAPI) && (mo_drives[i].ide_channel < 4) && mo_drives[i].priv) diff --git a/src/disk/rdisk.c b/src/disk/rdisk.c index 87e9eb5e4..9596ea572 100644 --- a/src/disk/rdisk.c +++ b/src/disk/rdisk.c @@ -172,7 +172,7 @@ rdisk_load_abort(const rdisk_t *dev) return 0; } -int +static int image_is_zdi(const char *s) { return !strcasecmp(path_get_extension((char *) s), "ZDI"); diff --git a/src/disk/superdisk.c b/src/disk/superdisk.c new file mode 100644 index 000000000..689e54e53 --- /dev/null +++ b/src/disk/superdisk.c @@ -0,0 +1,2334 @@ +/* + * 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 the Imation superdisk drive with SCSI(-like) + * commands, for both ATAPI and SCSI usage. + * + * Authors: Miran Grca, + * Jasmine Iwanek, + * + * Copyright 2018-2025 Miran Grca. + * Copyright 2022-2025 Jasmine Iwanek. + */ +#ifdef ENABLE_SUPERDISK_LOG +#include +#endif +#include +#include +#include +#include +#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/superdisk.h> + +#define IDE_ATAPI_IS_EARLY id->sc->pad0 + +superdisk_drive_t superdisk_drives[SUPERDISK_NUM]; + +// clang-format off +/* + Table of all SCSI commands and their flags, needed for the new disc change / + not ready handler. + */ +const uint8_t superdisk_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 superdisk_mode_sense_page_flags = (GPMODEP_R_W_ERROR_PAGE | GPMODEP_DISCONNECT_PAGE | + GPMODEP_ALL_PAGES); +static uint64_t superdisk_240_mode_sense_page_flags = (GPMODEP_R_W_ERROR_PAGE | GPMODEP_FLEXIBLE_DISK_PAGE | + GPMODEP_CACHING_PAGE | + GPMODEP_ALL_PAGES); + +static const mode_sense_pages_t superdisk_mode_sense_pages_default = { + [0x01] = { GPMODE_R_W_ERROR_PAGE, 0x0a, // Page Length + 0x80, // Automatic Write Retry Enabled = 1 + 0x01, // Read Retry Count = 1 + 0x00, // Correction Span + 0x00, // Head Offset Count + 0x00, // Data Strobe Offset Count + 0x00, // Reserved + 0x00, // Write Retry Count (Low Byte) + 0x02, // Write Retry Count = 2 + 0x00, // Recovery Time Limit MSB + 0x00 }, // Recovery Time Limit LSB + [0x02] = { GPMODE_DISCONNECT_PAGE, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +}; + +static const mode_sense_pages_t superdisk_240_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 }, +}; + +static const mode_sense_pages_t superdisk_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 }, +}; + +static const mode_sense_pages_t superdisk_240_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 }, +}; + +static const mode_sense_pages_t superdisk_mode_sense_pages_changeable = { + [0x01] = { GPMODE_R_W_ERROR_PAGE, 0x0a, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff }, + [0x02] = { GPMODE_DISCONNECT_PAGE, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +}; + +static const mode_sense_pages_t superdisk_240_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 }, +}; +// clang-format on + +static void superdisk_command_complete(superdisk_t *dev); +static void superdisk_init(superdisk_t *dev); + +#ifdef ENABLE_SUPERDISK_LOG +int superdisk_do_log = ENABLE_SUPERDISK_LOG; + +static void +superdisk_log(const char *fmt, ...) +{ + va_list ap; + + if (superdisk_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define superdisk_log(fmt, ...) +#endif + +static int +superdisk_load_abort(const superdisk_t *dev) +{ + if (dev->drv->fp) + fclose(dev->drv->fp); + dev->drv->fp = NULL; + dev->drv->medium_size = 0; + superdisk_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"); +} + +int +superdisk_is_empty(const uint8_t id) +{ + const superdisk_t *dev = (const superdisk_t *) superdisk_drives[id].priv; + int ret = 0; + + if ((dev->drv == NULL) || (dev->drv->fp == NULL)) + ret = 1; + + return ret; +} + +void +superdisk_load(const superdisk_t *dev, const char *fn, const int skip_insert) +{ + const int was_empty = superdisk_is_empty(dev->id); + int ret = 0; + + if (dev->drv == NULL) + superdisk_eject(dev->id); + else { + const int is_sdi = image_is_sdi(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 = superdisk_load_abort(dev); + else + dev->drv->read_only = 1; + } else + ret = superdisk_load_abort(dev); + } + + if (ret) { + fseek(dev->drv->fp, 0, SEEK_END); + int size = ftell(dev->drv->fp); + + if (is_sdi) { + /* This is a SDI image. */ + size -= 0x1000; + dev->drv->base = 0x1000; + } else + dev->drv->base = 0; + + if (dev->drv->is_240) { + if ((size != (SUPERDISK_240_SECTORS << 9)) && (size != (SUPERDISK_SECTORS << 9))) { + superdisk_log("File is incorrect size for a LS-120/LS-240 image\n"); + superdisk_log("Must be exactly %i or %i bytes\n", + SUPERDISK_240_SECTORS << 9, SUPERDISK_SECTORS << 9); + ret = superdisk_load_abort(dev); + } + } else if (size != (SUPERDISK_SECTORS << 9)) { + superdisk_log("File is incorrect size for a LS-120/LS-240 image\n"); + superdisk_log("Must be exactly %i bytes\n", SUPERDISK_SECTORS << 9); + ret = superdisk_load_abort(dev); + } + + if (ret) + dev->drv->medium_size = size >> 9; + } + + if (ret) { + if (fseek(dev->drv->fp, dev->drv->base, SEEK_SET) == -1) + fatal("superdisk_load(): Error seeking to the beginning of " + "the file\n"); + + strncpy(dev->drv->image_path, fn, 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. */ + superdisk_insert((superdisk_t *) dev); + + /* The drive was previously empty, transition directly to UNIT ATTENTION. */ + if (was_empty) + superdisk_insert((superdisk_t *) dev); + } +} + +void +superdisk_disk_reload(const superdisk_t *dev) +{ + if (strlen(dev->drv->prev_image_path) != 0) + (void) superdisk_load(dev, dev->drv->prev_image_path, 0); +} + +static void +superdisk_disk_unload(const superdisk_t *dev) +{ + if ((dev->drv != NULL) && (dev->drv->fp != NULL)) { + fclose(dev->drv->fp); + dev->drv->fp = NULL; + } +} + +void +superdisk_disk_close(const superdisk_t *dev) +{ + if ((dev->drv != NULL) && (dev->drv->fp != NULL)) { + superdisk_disk_unload(dev); + + memcpy(dev->drv->prev_image_path, dev->drv->image_path, + sizeof(dev->drv->prev_image_path)); + memset(dev->drv->image_path, 0, sizeof(dev->drv->image_path)); + + dev->drv->medium_size = 0; + + superdisk_insert((superdisk_t *) dev); + } +} + +static void +superdisk_set_callback(const superdisk_t *dev) +{ + if (dev->drv->bus_type != SUPERDISK_BUS_SCSI) + ide_set_callback(ide_drives[dev->drv->ide_channel], dev->callback); +} + +static void +superdisk_init(superdisk_t *dev) +{ + if (dev->id < SUPERDISK_NUM) { + dev->requested_blocks = 1; + dev->sense[0] = 0xf0; + dev->sense[7] = 10; + dev->drv->bus_mode = 0; + if (dev->drv->bus_type >= SUPERDISK_BUS_ATAPI) + dev->drv->bus_mode |= 2; + if (dev->drv->bus_type < SUPERDISK_BUS_SCSI) + dev->drv->bus_mode |= 1; + superdisk_log("SuperDisk %i: Bus type %i, bus mode %i\n", dev->id, dev->drv->bus_type, dev->drv->bus_mode); + if (dev->drv->bus_type < SUPERDISK_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; + superdisk_sense_key = superdisk_asc = superdisk_ascq = dev->unit_attention = dev->transition = 0; + superdisk_info = 0x00000000; + } +} + +static int +superdisk_supports_pio(const superdisk_t *dev) +{ + return (dev->drv->bus_mode & 1); +} + +static int +superdisk_supports_dma(const superdisk_t *dev) +{ + return (dev->drv->bus_mode & 2); +} + +/* Returns: 0 for none, 1 for PIO, 2 for DMA. */ +static int +superdisk_current_mode(const superdisk_t *dev) +{ + if (!superdisk_supports_pio(dev) && !superdisk_supports_dma(dev)) + return 0; + if (superdisk_supports_pio(dev) && !superdisk_supports_dma(dev)) { + superdisk_log("SuperDisk %i: Drive does not support DMA, setting to PIO\n", dev->id); + return 1; + } + if (!superdisk_supports_pio(dev) && superdisk_supports_dma(dev)) + return 2; + if (superdisk_supports_pio(dev) && superdisk_supports_dma(dev)) { + superdisk_log("SuperDisk %i: Drive supports both, setting to %s\n", dev->id, + (dev->tf->features & 1) ? "DMA" : "PIO"); + return (dev->tf->features & 1) ? 2 : 1; + } + + return 0; +} + +static void +superdisk_mode_sense_load(superdisk_t *dev) +{ + char fn[512] = { 0 }; + + memset(&dev->ms_pages_saved, 0, sizeof(mode_sense_pages_t)); + if (dev->drv->is_240) { + if (superdisk_drives[dev->id].bus_type == SUPERDISK_BUS_SCSI) + memcpy(&dev->ms_pages_saved, &superdisk_240_mode_sense_pages_default_scsi, sizeof(mode_sense_pages_t)); + else + memcpy(&dev->ms_pages_saved, &superdisk_240_mode_sense_pages_default, sizeof(mode_sense_pages_t)); + } else { + if (superdisk_drives[dev->id].bus_type == SUPERDISK_BUS_SCSI) + memcpy(&dev->ms_pages_saved, &superdisk_mode_sense_pages_default_scsi, sizeof(mode_sense_pages_t)); + else + memcpy(&dev->ms_pages_saved, &superdisk_mode_sense_pages_default, sizeof(mode_sense_pages_t)); + } + + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + sprintf(fn, "scsi_superdisk_%02i_mode_sense_bin", dev->id); + else + sprintf(fn, "superdisk_%02i_mode_sense_bin", dev->id); + FILE *fp = plat_fopen(nvr_path(fn), "rb"); + if (fp) { + /* Nothing to read, not used by SuperDisk. */ + fclose(fp); + } +} + +static void +superdisk_mode_sense_save(const superdisk_t *dev) +{ + char fn[512] = { 0 }; + + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + sprintf(fn, "scsi_superdisk_%02i_mode_sense_bin", dev->id); + else + sprintf(fn, "superdisk_%02i_mode_sense_bin", dev->id); + FILE *fp = plat_fopen(nvr_path(fn), "wb"); + if (fp) { + /* Nothing to write, not used by SuperDisk. */ + fclose(fp); + } +} + +/* SCSI Mode Sense 6/10. */ +static uint8_t +superdisk_mode_sense_read(const superdisk_t *dev, const uint8_t pgctl, + const uint8_t page, const uint8_t pos) +{ + switch (pgctl) { + case 0: + case 3: + if (dev->drv->is_240 && (page == 5) && (pos == 9) && + (dev->drv->medium_size == SUPERDISK_SECTORS)) + return 0x60; + return dev->ms_pages_saved.pages[page][pos]; + case 1: + if (dev->drv->is_240) + return superdisk_240_mode_sense_pages_changeable.pages[page][pos]; + else + return superdisk_mode_sense_pages_changeable.pages[page][pos]; + case 2: + if (dev->drv->is_240) { + if ((page == 5) && (pos == 9) && (dev->drv->medium_size == SUPERDISK_SECTORS)) + return 0x60; + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + return superdisk_240_mode_sense_pages_default_scsi.pages[page][pos]; + else + return superdisk_240_mode_sense_pages_default.pages[page][pos]; + } else { + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + return superdisk_mode_sense_pages_default_scsi.pages[page][pos]; + else + return superdisk_mode_sense_pages_default.pages[page][pos]; + } + + default: + break; + } + + return 0; +} + +static uint32_t +superdisk_mode_sense(const superdisk_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->is_240) + pf = superdisk_240_mode_sense_page_flags; + else + pf = superdisk_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 = superdisk_mode_sense_read(dev, pgctl, i, 1); + buf[pos++] = superdisk_mode_sense_read(dev, pgctl, i, 0); + buf[pos++] = msplen; + superdisk_log("SuperDisk %i: MODE SENSE: Page [%02X] length %i\n", dev->id, i, msplen); + for (uint8_t j = 0; j < msplen; j++) + buf[pos++] = superdisk_mode_sense_read(dev, pgctl, i, 2 + j); + } + } + } + + return pos; +} + +static void +superdisk_update_request_length(superdisk_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 +superdisk_bus_speed(superdisk_t *dev) +{ + double ret = -1.0; + + if (dev && dev->drv) + 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 +superdisk_command_common(superdisk_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 == SUPERDISK_BUS_SCSI) + dev->callback = -1.0; /* Speed depends on SCSI controller */ + else + dev->callback = superdisk_bus_speed(dev) * (double) (dev->packet_len); + + superdisk_set_callback(dev); +} + +static void +superdisk_command_complete(superdisk_t *dev) +{ + dev->packet_status = PHASE_COMPLETE; + superdisk_command_common(dev); +} + +static void +superdisk_command_read(superdisk_t *dev) +{ + dev->packet_status = PHASE_DATA_IN; + superdisk_command_common(dev); +} + +static void +superdisk_command_read_dma(superdisk_t *dev) +{ + dev->packet_status = PHASE_DATA_IN_DMA; + superdisk_command_common(dev); +} + +static void +superdisk_command_write(superdisk_t *dev) +{ + dev->packet_status = PHASE_DATA_OUT; + superdisk_command_common(dev); +} + +static void +superdisk_command_write_dma(superdisk_t *dev) +{ + dev->packet_status = PHASE_DATA_OUT_DMA; + superdisk_command_common(dev); +} + +/* + dev = Pointer to current SuperDisk 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 +superdisk_data_command_finish(superdisk_t *dev, int len, const int block_len, + const int alloc_len, const int direction) +{ + superdisk_log("SuperDisk %i: Finishing command (%02X): %i, %i, %i, %i, %i\n", dev->id, + 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) || (superdisk_current_mode(dev) == 0)) { + if (dev->drv->bus_type != SUPERDISK_BUS_SCSI) + dev->packet_len = 0; + + superdisk_command_complete(dev); + } else { + if (superdisk_current_mode(dev) == 2) { + if (dev->drv->bus_type != SUPERDISK_BUS_SCSI) + dev->packet_len = alloc_len; + + if (direction == 0) + superdisk_command_read_dma(dev); + else + superdisk_command_write_dma(dev); + } else { + superdisk_update_request_length(dev, len, block_len); + if ((dev->drv->bus_type != SUPERDISK_BUS_SCSI) && + (dev->tf->request_length == 0)) + superdisk_command_complete(dev); + else if (direction == 0) + superdisk_command_read(dev); + else + superdisk_command_write(dev); + } + } + + superdisk_log("SuperDisk %i: Status: %i, cylinder %i, packet length: %i, position: %i, phase: %i\n", dev->id, + dev->packet_status, dev->tf->request_length, dev->packet_len, + dev->tf->pos, dev->tf->phase); +} + +static void +superdisk_sense_clear(superdisk_t *dev, UNUSED(int command)) +{ + superdisk_sense_key = superdisk_asc = superdisk_ascq = 0; + superdisk_info = 0x00000000; +} + +static void +superdisk_set_phase(const superdisk_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 == SUPERDISK_BUS_SCSI) + scsi_devices[scsi_bus][scsi_id].phase = phase; +} + +static void +superdisk_cmd_error(superdisk_t *dev) +{ + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + dev->tf->error = ((superdisk_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 * SUPERDISK_TIME; + superdisk_set_callback(dev); + ui_sb_update_icon(SB_SUPERDISK | dev->id, 0); + ui_sb_update_icon_write(SB_SUPERDISK | dev->id, 0); + superdisk_log("SuperDisk %i: [%02X] ERROR: %02X/%02X/%02X\n", dev->id, dev->current_cdb[0], superdisk_sense_key, + superdisk_asc, superdisk_ascq); +} + +static void +superdisk_unit_attention(superdisk_t *dev) +{ + superdisk_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 * SUPERDISK_TIME; + superdisk_set_callback(dev); + ui_sb_update_icon(SB_SUPERDISK | dev->id, 0); + ui_sb_update_icon_write(SB_SUPERDISK | dev->id, 0); + superdisk_log("SuperDisk %i: UNIT ATTENTION\n", dev->id); +} + +static void +superdisk_buf_alloc(superdisk_t *dev, const uint32_t len) +{ + superdisk_log("SuperDisk %i: Allocated buffer length: %i\n", dev->id, len); + if (dev->buffer == NULL) + dev->buffer = (uint8_t *) malloc(len); +} + +static void +superdisk_buf_free(superdisk_t *dev) +{ + if (dev->buffer != NULL) { + superdisk_log("SuperDisk %i: Freeing buffer...\n", dev->id); + free(dev->buffer); + dev->buffer = NULL; + } +} + +static void +superdisk_bus_master_error(scsi_common_t *sc) +{ + superdisk_t *dev = (superdisk_t *) sc; + + superdisk_buf_free(dev); + superdisk_sense_key = superdisk_asc = superdisk_ascq = 0; + superdisk_info = (dev->sector_pos >> 24) | + ((dev->sector_pos >> 16) << 8) | + ((dev->sector_pos >> 8) << 16) | + ( dev->sector_pos << 24); + superdisk_cmd_error(dev); +} + +static void +superdisk_not_ready(superdisk_t *dev) +{ + superdisk_sense_key = SENSE_NOT_READY; + superdisk_asc = ASC_MEDIUM_NOT_PRESENT; + superdisk_ascq = 0; + superdisk_info = 0x00000000; + superdisk_cmd_error(dev); +} + +static void +superdisk_write_protected(superdisk_t *dev) +{ + superdisk_sense_key = SENSE_UNIT_ATTENTION; + superdisk_asc = ASC_WRITE_PROTECTED; + superdisk_ascq = 0; + superdisk_info = (dev->sector_pos >> 24) | + ((dev->sector_pos >> 16) << 8) | + ((dev->sector_pos >> 8) << 16) | + ( dev->sector_pos << 24); + superdisk_cmd_error(dev); +} + +static void +superdisk_write_error(superdisk_t *dev) +{ + superdisk_sense_key = SENSE_MEDIUM_ERROR; + superdisk_asc = ASC_WRITE_ERROR; + superdisk_ascq = 0; + superdisk_info = (dev->sector_pos >> 24) | + ((dev->sector_pos >> 16) << 8) | + ((dev->sector_pos >> 8) << 16) | + ( dev->sector_pos << 24); + superdisk_cmd_error(dev); +} + +static void +superdisk_read_error(superdisk_t *dev) +{ + superdisk_sense_key = SENSE_MEDIUM_ERROR; + superdisk_asc = ASC_UNRECOVERED_READ_ERROR; + superdisk_ascq = 0; + superdisk_info = (dev->sector_pos >> 24) | + ((dev->sector_pos >> 16) << 8) | + ((dev->sector_pos >> 8) << 16) | + ( dev->sector_pos << 24); + superdisk_cmd_error(dev); +} + +static void +superdisk_invalid_lun(superdisk_t *dev, const uint8_t lun) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_INV_LUN; + superdisk_ascq = 0; + superdisk_info = lun << 24; + superdisk_cmd_error(dev); +} + +static void +superdisk_illegal_opcode(superdisk_t *dev, const uint8_t opcode) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_ILLEGAL_OPCODE; + superdisk_ascq = 0; + superdisk_info = opcode << 24; + superdisk_cmd_error(dev); +} + +static void +superdisk_lba_out_of_range(superdisk_t *dev) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_LBA_OUT_OF_RANGE; + superdisk_ascq = 0; + superdisk_info = (dev->sector_pos >> 24) | + ((dev->sector_pos >> 16) << 8) | + ((dev->sector_pos >> 8) << 16) | + ( dev->sector_pos << 24); + superdisk_cmd_error(dev); +} + +static void +superdisk_invalid_field(superdisk_t *dev, const uint32_t field) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_INV_FIELD_IN_CMD_PACKET; + superdisk_ascq = 0; + superdisk_info = (field >> 24) | + ((field >> 16) << 8) | + ((field >> 8) << 16) | + ( field << 24); + superdisk_cmd_error(dev); + dev->tf->status = 0x53; +} + +static void +superdisk_invalid_field_pl(superdisk_t *dev, const uint32_t field) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_INV_FIELD_IN_PARAMETER_LIST; + superdisk_ascq = 0; + superdisk_info = (field >> 24) | + ((field >> 16) << 8) | + ((field >> 8) << 16) | + ( field << 24); + superdisk_cmd_error(dev); + dev->tf->status = 0x53; +} + +static void +superdisk_data_phase_error(superdisk_t *dev, const uint32_t info) +{ + superdisk_sense_key = SENSE_ILLEGAL_REQUEST; + superdisk_asc = ASC_DATA_PHASE_ERROR; + superdisk_ascq = 0; + superdisk_info = (info >> 24) | + ((info >> 16) << 8) | + ((info >> 8) << 16) | + ( info << 24); + superdisk_cmd_error(dev); +} + +static int +superdisk_blocks(superdisk_t *dev, int32_t *len, const int out) +{ + int ret = 1; + *len = 0; + + if (!dev->sector_len) + superdisk_command_complete(dev); + else { + superdisk_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) { + superdisk_log("SuperDisk %i: Trying to %s beyond the end of disk\n", dev->id, + out ? "write" : "read"); + superdisk_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) + + (i << 9), SEEK_SET) == -1) { + if (out) + superdisk_write_error(dev); + else + superdisk_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) { + superdisk_log("superdisk_blocks(): Error writing data\n"); + superdisk_write_error(dev); + ret = -1; + } else + fflush(dev->drv->fp); + } else if (fread(dev->buffer + (i << 9), 1, + 512, dev->drv->fp) != 512) { + superdisk_log("superdisk_blocks(): Error reading data\n"); + superdisk_read_error(dev); + ret = -1; + } + } + + if (ret == -1) + break; + + dev->sector_pos++; + } + + if (ret == 1) { + superdisk_log("%s %i bytes of blocks...\n", out ? "Written" : + "Read", *len); + + dev->sector_len -= dev->requested_blocks; + } + } + } else { + superdisk_command_complete(dev); + ret = 0; + } + + return ret; +} + +void +superdisk_insert(superdisk_t *dev) +{ + if ((dev != NULL) && (dev->drv != NULL)) { + if (dev->drv->fp == NULL) { + dev->unit_attention = 0; + dev->transition = 0; + superdisk_log("Media removal\n"); + } else if (dev->transition) { + dev->unit_attention = 1; + /* Turn off the medium changed status. */ + dev->transition = 0; + superdisk_log("Media insert\n"); + } else { + dev->unit_attention = 0; + dev->transition = 1; + superdisk_log("Media transition\n"); + } + } +} + +static int +superdisk_pre_execution_check(superdisk_t *dev, const uint8_t *cdb) +{ + int ready; + + if ((cdb[0] != GPCMD_REQUEST_SENSE) && (dev->cur_lun == SCSI_LUN_USE_CDB) && + (cdb[1] & 0xe0)) { + superdisk_log("SuperDisk %i: Attempting to execute a unknown command targeted at SCSI LUN %i\n", dev->id, + ((dev->tf->request_length >> 5) & 7)); + superdisk_invalid_lun(dev, cdb[1] >> 5); + return 0; + } + + if (!(superdisk_command_flags[cdb[0]] & IMPLEMENTED)) { + superdisk_log("SuperDisk %i: Attempting to execute unknown command %02X over %s\n", dev->id, + cdb[0], (dev->drv->bus_type == SUPERDISK_BUS_SCSI) ? + "SCSI" : "ATAPI"); + + superdisk_illegal_opcode(dev, cdb[0]); + return 0; + } + + if ((dev->drv->bus_type < SUPERDISK_BUS_SCSI) && + (superdisk_command_flags[cdb[0]] & SCSI_ONLY)) { + superdisk_log("SuperDisk %i: Attempting to execute SCSI-only command %02X " + "over ATAPI\n",dev->id, cdb[0]); + superdisk_illegal_opcode(dev, cdb[0]); + return 0; + } + + if ((dev->drv->bus_type == SUPERDISK_BUS_SCSI) && + (superdisk_command_flags[cdb[0]] & ATAPI_ONLY)) { + superdisk_log("SuperDisk %i: Attempting to execute ATAPI-only command %02X " + "over SCSI\n", dev->id, cdb[0]); + superdisk_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 (!(superdisk_command_flags[cdb[0]] & ALLOW_UA)) { + superdisk_log("(ext_medium_changed != 0): superdisk_insert()\n"); + superdisk_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 (!(superdisk_command_flags[cdb[0]] & ALLOW_UA)) { + superdisk_log("SuperDisk %i: Unit attention now 2\n", dev->id); + dev->unit_attention++; + superdisk_log("SuperDisk %i: UNIT ATTENTION: Command %02X not allowed to pass through\n", dev->id, + cdb[0]); + superdisk_unit_attention(dev); + return 0; + } + } else if (dev->unit_attention == 2) { + if (cdb[0] != GPCMD_REQUEST_SENSE) { + superdisk_log("SuperDisk %i: Unit attention now 0\n", dev->id); + 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) + superdisk_sense_clear(dev, cdb[0]); + + if (!ready && (superdisk_command_flags[cdb[0]] & CHECK_READY)) { + superdisk_log("SuperDisk %i: Not ready (%02X)\n", dev->id, cdb[0]); + superdisk_not_ready(dev); + return 0; + } + + superdisk_log("SuperDisk %i: Continuing with command %02X\n", dev->id, cdb[0]); + return 1; +} + +static void +superdisk_seek(superdisk_t *dev, const uint32_t pos) +{ + dev->sector_pos = pos; +} + +static void +superdisk_rezero(superdisk_t *dev) +{ + dev->sector_pos = dev->sector_len = 0; + superdisk_seek(dev, 0); +} + +void +superdisk_reset(scsi_common_t *sc) +{ + superdisk_t *dev = (superdisk_t *) sc; + + superdisk_rezero(dev); + dev->tf->status = 0; + dev->callback = 0.0; + superdisk_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; + superdisk_sense_key = superdisk_asc = superdisk_ascq = dev->unit_attention = dev->transition = 0; + superdisk_info = 0x00000000; +} + +static void +superdisk_request_sense(superdisk_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] = superdisk_sense_key; + buffer[2] = superdisk_asc; + buffer[3] = superdisk_ascq; + } + } + + buffer[0] = desc ? 0x72 : 0x70; + if (!desc) + buffer[7] = 10; + + if (dev->unit_attention && (superdisk_sense_key == 0)) { + buffer[desc ? 1 : 2] = SENSE_UNIT_ATTENTION; + buffer[desc ? 2 : 12] = ASC_MEDIUM_MAY_HAVE_CHANGED; + buffer[desc ? 3 : 13] = 0; + } + + superdisk_log("SuperDisk %i: Reporting sense: %02X %02X %02X\n", dev->id, 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. */ + superdisk_sense_clear(dev, GPCMD_REQUEST_SENSE); + + if (dev->transition) { + superdisk_log("SUPERDISK_TRANSITION: superdisk_insert()\n"); + superdisk_insert((void *) dev); + } +} + +static void +superdisk_request_sense_for_scsi(scsi_common_t *sc, uint8_t *buffer, const uint8_t alloc_length) +{ + superdisk_t *dev = (superdisk_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. */ + superdisk_request_sense(dev, buffer, alloc_length, 0); +} + +static void +superdisk_set_buf_len(const superdisk_t *dev, int32_t *BufLen, int32_t *src_len) +{ + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) { + if (*BufLen == -1) + *BufLen = *src_len; + else { + *BufLen = MIN(*src_len, *BufLen); + *src_len = *BufLen; + } + superdisk_log("SuperDisk %i: Actual transfer length: %i\n", dev->id, *BufLen); + } +} + +static void +superdisk_command(scsi_common_t *sc, const uint8_t *cdb) +{ + superdisk_t *dev = (superdisk_t *) sc; + 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 == SUPERDISK_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; + + memcpy(dev->current_cdb, cdb, 12); + + if (cdb[0] != 0) { + superdisk_log("SuperDisk %i: Command 0x%02X, Sense Key %02X, Asc %02X, Ascq %02X, " + "Unit attention: %i\n", dev->id, + cdb[0], superdisk_sense_key, superdisk_asc, superdisk_ascq, dev->unit_attention); + superdisk_log("SuperDisk %i: Request length: %04X\n", dev->id, dev->tf->request_length); + + superdisk_log("SuperDisk %i: CDB: %02X %02X %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X %02X\n", dev->id, + 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; + + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + + /* + This handles the Not Ready/Unit Attention check if it has to be handled at + this point. + */ + if (superdisk_pre_execution_check(dev, cdb) == 0) + return; + + switch (cdb[0]) { + case GPCMD_SEND_DIAGNOSTIC: + if (!(cdb[1] & (1 << 2))) { + superdisk_invalid_field(dev, cdb[1]); + return; + } + fallthrough; + case GPCMD_SCSI_RESERVE: + case GPCMD_SCSI_RELEASE: + case GPCMD_TEST_UNIT_READY: + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + superdisk_command_complete(dev); + break; + + case GPCMD_FORMAT_UNIT: + if (dev->drv->read_only) + superdisk_write_protected(dev); + else { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + superdisk_command_complete(dev); + } + break; + + case GPCMD_IMATION_SENSE: + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + max_len = cdb[4]; + superdisk_buf_alloc(dev, 256); + superdisk_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 { + superdisk_invalid_field(dev, cdb[2]); + superdisk_buf_free(dev); + return; + } + superdisk_data_command_finish(dev, 18, 18, cdb[4], 0); + break; + + case GPCMD_REZERO_UNIT: + dev->sector_pos = dev->sector_len = 0; + superdisk_seek(dev, 0); + superdisk_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. + */ + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + max_len = cdb[4]; + + if (!max_len) { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + dev->packet_status = PHASE_COMPLETE; + dev->callback = 20.0 * SUPERDISK_TIME; + superdisk_set_callback(dev); + break; + } + + superdisk_buf_alloc(dev, 256); + superdisk_set_buf_len(dev, BufLen, &max_len); + len = (cdb[1] & 1) ? 8 : 18; + superdisk_request_sense(dev, dev->buffer, max_len, cdb[1] & 1); + superdisk_data_command_finish(dev, len, len, cdb[4], 0); + break; + + case GPCMD_MECHANISM_STATUS: + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + len = (cdb[8] << 8) | cdb[9]; + + superdisk_buf_alloc(dev, 8); + superdisk_set_buf_len(dev, BufLen, &len); + + memset(dev->buffer, 0, 8); + dev->buffer[5] = 1; + + superdisk_data_command_finish(dev, 8, 8, len, 0); + break; + + case GPCMD_READ_6: + case GPCMD_READ_10: + case GPCMD_READ_12: + superdisk_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]); + superdisk_log("SuperDisk %i: Length: %i, LBA: %i\n", dev->id, 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]; + superdisk_log("SuperDisk %i: Length: %i, LBA: %i\n", dev->id, 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) + superdisk_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; + superdisk_buf_alloc(dev, dev->packet_len); + + int ret = 0; + + if (dev->sector_len > 0) + ret = superdisk_blocks(dev, &alloc_length, 0); + + if (ret > 0) { + dev->requested_blocks = max_len; + dev->packet_len = alloc_length; + + superdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len); + + superdisk_data_command_finish(dev, alloc_length, 512, + alloc_length, 0); + + ui_sb_update_icon(SB_ZIP | dev->id, + dev->packet_status != PHASE_COMPLETE); + } else { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + dev->packet_status = (ret < 0) ? PHASE_ERROR : PHASE_COMPLETE; + dev->callback = 20.0 * SUPERDISK_TIME; + superdisk_set_callback(dev); + superdisk_buf_free(dev); + } + } else { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + /* superdisk_log("All done - callback set\n"); */ + dev->packet_status = PHASE_COMPLETE; + dev->callback = 20.0 * SUPERDISK_TIME; + superdisk_set_callback(dev); + break; + } + break; + + case GPCMD_VERIFY_6: + case GPCMD_VERIFY_10: + case GPCMD_VERIFY_12: + if (!(cdb[1] & 2)) { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + superdisk_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: + superdisk_set_phase(dev, SCSI_PHASE_DATA_OUT); + alloc_length = 512; + + if (dev->drv->read_only) { + superdisk_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]; + superdisk_log("SuperDisk %i: Length: %i, LBA: %i\n", dev->id, + 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) + superdisk_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; + superdisk_buf_alloc(dev, dev->packet_len); + + dev->requested_blocks = max_len; + dev->packet_len = max_len << 9; + + superdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len); + + superdisk_data_command_finish(dev, dev->packet_len, 512, + dev->packet_len, 1); + + ui_sb_update_icon_write(SB_ZIP | dev->id, + dev->packet_status != PHASE_COMPLETE); + } else { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); +#if 0 + superdisk_log("SuperDisk %i: All done - callback set\n", dev->id); +#endif + dev->packet_status = PHASE_COMPLETE; + dev->callback = 20.0 * SUPERDISK_TIME; + superdisk_set_callback(dev); + } + break; + + case GPCMD_WRITE_SAME_10: + superdisk_set_phase(dev, SCSI_PHASE_DATA_OUT); + alloc_length = 512; + + if ((cdb[1] & 6) == 6) + superdisk_invalid_field(dev, cdb[1]); + else { + if (dev->drv->read_only) + superdisk_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) + superdisk_lba_out_of_range(dev); + else if (dev->sector_len) { + superdisk_buf_alloc(dev, alloc_length); + superdisk_set_buf_len(dev, BufLen, (int32_t *) &dev->packet_len); + + max_len = 1; + dev->requested_blocks = 1; + + dev->packet_len = alloc_length; + + superdisk_set_phase(dev, SCSI_PHASE_DATA_OUT); + + superdisk_data_command_finish(dev, 512, 512, + alloc_length, 1); + + ui_sb_update_icon_write(SB_ZIP | dev->id, + dev->packet_status != PHASE_COMPLETE); + } else { + superdisk_set_phase(dev, SCSI_PHASE_STATUS); +#if 0 + superdisk_log("All done - callback set\n"); +#endif + dev->packet_status = PHASE_COMPLETE; + dev->callback = 20.0 * SUPERDISK_TIME; + superdisk_set_callback(dev); + } + } + } + break; + + case GPCMD_MODE_SENSE_6: + case GPCMD_MODE_SENSE_10: + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + block_desc = ((cdb[1] >> 3) & 1) ? 0 : 1; + else + block_desc = 0; + + if (cdb[0] == GPCMD_MODE_SENSE_6) { + len = cdb[4]; + superdisk_buf_alloc(dev, 256); + } else { + len = (cdb[8] | (cdb[7] << 8)); + superdisk_buf_alloc(dev, 65536); + } + + if (superdisk_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 = superdisk_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 = superdisk_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; + } + } + + superdisk_set_buf_len(dev, BufLen, &len); + + superdisk_log("SuperDisk %i: Reading mode page: %02X...\n", dev->id, cdb[2]); + + superdisk_data_command_finish(dev, len, len, alloc_length, 0); + } else { + superdisk_invalid_field(dev, cdb[2]); + superdisk_buf_free(dev); + } + break; + + case GPCMD_MODE_SELECT_6: + case GPCMD_MODE_SELECT_10: + superdisk_set_phase(dev, SCSI_PHASE_DATA_OUT); + + if (cdb[0] == GPCMD_MODE_SELECT_6) { + len = cdb[4]; + superdisk_buf_alloc(dev, 256); + } else { + len = (cdb[7] << 8) | cdb[8]; + superdisk_buf_alloc(dev, 65536); + } + + superdisk_set_buf_len(dev, BufLen, &len); + + dev->total_length = len; + dev->do_page_save = cdb[1] & 1; + + superdisk_data_command_finish(dev, len, len, len, 1); + return; + + case GPCMD_START_STOP_UNIT: + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + + switch (cdb[4] & 3) { + case 0: /* Stop the disc. */ + superdisk_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 + superdisk_eject(dev->id); +#endif + break; + case 3: /* Load the disc (close tray). */ + superdisk_reload(dev->id); + break; + + default: + break; + } + + superdisk_command_complete(dev); + break; + + case GPCMD_INQUIRY: + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + + max_len = cdb[3]; + max_len <<= 8; + max_len |= cdb[4]; + + superdisk_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) { + superdisk_data_phase_error(dev, cdb[2]); + superdisk_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 */ + ide_padstr8(dev->buffer + idx, 8, "MATSHITA "); + idx += 8; + /* Product */ + if (dev->drv->is_240) + ide_padstr8(dev->buffer + idx, 40, "LS-240_COSM "); + else + ide_padstr8(dev->buffer + idx, 40, "LS-120_COSM "); + idx += 40; + ide_padstr8(dev->buffer + idx, 20, " 04"); + idx += 20; + break; + default: + superdisk_log("INQUIRY: Invalid page: %02X\n", cdb[2]); + superdisk_invalid_field(dev, cdb[2]); + superdisk_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 == SUPERDISK_BUS_SCSI) ? 0x02 : 0x00; + dev->buffer[3] = (dev->drv->bus_type == SUPERDISK_BUS_SCSI) ? 0x02 : 0x21; +#if 0 + dev->buffer[4] = 31; +#endif + dev->buffer[4] = 0; + if (dev->drv->bus_type == SUPERDISK_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, "MATSHITA "); /* Vendor */ + if (dev->drv->is_240) { + /* Product */ + ide_padstr8(dev->buffer + 16, 16, "LS-240_COSM "); + /* Revision */ + ide_padstr8(dev->buffer + 32, 4, " 04"); + /* Date? */ + if (max_len >= 44) + ide_padstr8(dev->buffer + 36, 8, "07/01/01"); + if (max_len >= 122) + ide_padstr8(dev->buffer + 96, 26, "(c) Copyright Matshita 2001"); /* Copyright string */ + } else { + /* Product */ + ide_padstr8(dev->buffer + 16, 16, "LS-120_COSM "); + /* Revision */ + ide_padstr8(dev->buffer + 32, 4, " 04"); + } + 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); + superdisk_set_buf_len(dev, BufLen, &len); + + superdisk_data_command_finish(dev, len, len, max_len, 0); + break; + + case GPCMD_PREVENT_REMOVAL: + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + superdisk_command_complete(dev); + break; + + case GPCMD_SEEK_6: + case GPCMD_SEEK_10: + superdisk_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; + } + superdisk_seek(dev, pos); + superdisk_command_complete(dev); + break; + + case GPCMD_READ_CDROM_CAPACITY: + superdisk_set_phase(dev, SCSI_PHASE_DATA_IN); + + superdisk_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; + + superdisk_set_buf_len(dev, BufLen, &len); + + superdisk_data_command_finish(dev, len, len, len, 0); + break; + + case GPCMD_IMATION_EJECT: + superdisk_set_phase(dev, SCSI_PHASE_STATUS); + superdisk_eject(dev->id); + superdisk_command_complete(dev); + break; + + case GPCMD_READ_FORMAT_CAPACITIES: + len = (cdb[7] << 8) | cdb[8]; + + superdisk_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->is_240) { + /* SuperDisk 240 also supports LS-120 media, so if the medium is inserted, + we return the inserted medium's size, otherwise, the LS-240 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++] = (SUPERDISK_240_SECTORS >> 24) & 0xff; + dev->buffer[pos++] = (SUPERDISK_240_SECTORS >> 16) & 0xff; + dev->buffer[pos++] = (SUPERDISK_240_SECTORS >> 8) & 0xff; + dev->buffer[pos++] = SUPERDISK_240_SECTORS & 0xff; + dev->buffer[pos++] = 3; /* Maximum medium capacity */ + } + } else { + /* SuperDisk 120 only supports LS-120 media, so we always return + the LS-120 size. */ + dev->buffer[pos++] = (SUPERDISK_SECTORS >> 24) & 0xff; + dev->buffer[pos++] = (SUPERDISK_SECTORS >> 16) & 0xff; + dev->buffer[pos++] = (SUPERDISK_SECTORS >> 8) & 0xff; + dev->buffer[pos++] = SUPERDISK_SECTORS & 0xff; + if (dev->drv->fp != NULL) + dev->buffer[pos++] = 2; + else + dev->buffer[pos++] = 3; + } + + 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; + } + + superdisk_set_buf_len(dev, BufLen, &len); + + superdisk_data_command_finish(dev, len, len, len, 0); + break; + + default: + superdisk_illegal_opcode(dev, cdb[0]); + break; + } + +#if 0 + superdisk_log("SuperDisk %i: Phase: %02X, request length: %i\n", dev->id, + dev->tf->phase, dev->tf->request_length); +#endif + + if ((dev->packet_status == PHASE_COMPLETE) || (dev->packet_status == PHASE_ERROR)) + superdisk_buf_free(dev); +} + +static void +superdisk_command_stop(scsi_common_t *sc) +{ + superdisk_t *dev = (superdisk_t *) sc; + + superdisk_command_complete(dev); + superdisk_buf_free(dev); +} + +/* The command second phase function, needed for Mode Select. */ +static uint8_t +superdisk_phase_data_out(scsi_common_t *sc) +{ + superdisk_t *dev = (superdisk_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) + superdisk_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 963, 2, 32 (LS-120) and 262, 2, 56 (LS-240) */ + 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) + fatal("superdisk_phase_data_out(): Error seeking\n"); + if (fwrite(dev->buffer, 1, 512, dev->drv->fp) != 512) + fatal("superdisk_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 == SUPERDISK_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) { + superdisk_log("SuperDisk %i: Buffer has only block descriptor\n", dev->id); + break; + } + + const uint8_t page = dev->buffer[pos] & 0x3f; + const uint8_t page_len = dev->buffer[pos + 1]; + + pos += 2; + + if (!(superdisk_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 = superdisk_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; + superdisk_invalid_field_pl(dev, val); + } + } + } + + pos += page_len; + + if (dev->drv->bus_type == SUPERDISK_BUS_SCSI) + val = superdisk_mode_sense_pages_default_scsi.pages[page][0] & 0x80; + else + val = superdisk_mode_sense_pages_default.pages[page][0] & 0x80; + if (dev->do_page_save && val) + superdisk_mode_sense_save(dev); + + if (pos >= dev->total_length) + break; + } + + if (error) { + superdisk_buf_free(dev); + return 0; + } + break; + + default: + break; + } + + superdisk_command_stop((scsi_common_t *) dev); + return 1; +} + +/* Peform a master init on the entire module. */ +void +superdisk_global_init(void) +{ + /* Clear the global data. */ + memset(superdisk_drives, 0x00, sizeof(superdisk_drives)); +} + +static int +superdisk_get_max(UNUSED(const ide_t *ide), const int ide_has_dma, const int type) +{ + int ret; + + switch (type) { + case TYPE_PIO: + ret = ide_has_dma ? 3 : 0; + 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 +superdisk_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 = ide_has_dma ? 0xb4 : 0; + break; + case TIMINGS_PIO_FC: + ret = ide_has_dma ? 0xb4 : 0; + break; + default: + ret = 0; + break; + } + + return ret; +} + +static void +superdisk_120_identify(const ide_t *ide) +{ + ide_padstr((char *) (ide->buffer + 23), " 04", 8); /* Firmware */ + ide_padstr((char *) (ide->buffer + 27), "IMATION SUPERDISK 120 ATAPI", 40); /* Model */ +} + +static void +superdisk_240_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), "IMATION SUPERDISK 240 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 +superdisk_identify(const ide_t *ide, const int ide_has_dma) +{ + const superdisk_t *superdisk = (superdisk_t *) ide->sc; + + /* + ATAPI device, direct-access device, removable media, interrupt DRQ: + + Using (1 << 5) below makes the ASUS P/I-P54TP4XE misdentify the SuperDisk drive + as a ZIP drive. + */ + ide->buffer[0] = 0x8000 | (0 << 8) | 0x80 | (2 << 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 (superdisk_drives[superdisk->id].is_240) + superdisk_240_identify(ide, ide_has_dma); + else + superdisk_120_identify(ide); +} + +static void +superdisk_drive_reset(const int c) +{ + const uint8_t scsi_bus = (superdisk_drives[c].scsi_device_id >> 4) & 0x0f; + const uint8_t scsi_id = superdisk_drives[c].scsi_device_id & 0x0f; + + if (superdisk_drives[c].priv == NULL) { + superdisk_drives[c].priv = (superdisk_t *) calloc(1, sizeof(superdisk_t)); + + } + + superdisk_t *dev = (superdisk_t *) superdisk_drives[c].priv; + + dev->id = c; + dev->cur_lun = SCSI_LUN_USE_CDB; + + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI) { + if (dev->tf == NULL) + dev->tf = (ide_tf_t *) calloc(1, sizeof(ide_tf_t)); + + /* SCSI SuperDisk, attach to the SCSI bus. */ + scsi_device_t *sd = &scsi_devices[scsi_bus][scsi_id]; + + sd->sc = (scsi_common_t *) dev; + sd->command = superdisk_command; + sd->request_sense = superdisk_request_sense_for_scsi; + sd->reset = superdisk_reset; + sd->phase_data_out = superdisk_phase_data_out; + sd->command_stop = superdisk_command_stop; + sd->type = SCSI_REMOVABLE_DISK; + } else if (superdisk_drives[c].bus_type == SUPERDISK_BUS_ATAPI) { + /* ATAPI CD-ROM, attach to the IDE bus. */ + ide_t *id = ide_get_drive(superdisk_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 = superdisk_get_max; + id->get_timings = superdisk_get_timings; + id->identify = superdisk_identify; + id->stop = NULL; + id->packet_command = superdisk_command; + id->device_reset = superdisk_reset; + id->phase_data_out = superdisk_phase_data_out; + id->command_stop = superdisk_command_stop; + id->bus_master_error = superdisk_bus_master_error; + id->interrupt_drq = 1; + + ide_atapi_attach(id); + } + } +} + +void +superdisk_hard_reset(void) +{ + for (uint8_t c = 0; c < SUPERDISK_NUM; c++) { + if ((superdisk_drives[c].bus_type == SUPERDISK_BUS_ATAPI) || (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI)) { + + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI) { + const uint8_t scsi_bus = (superdisk_drives[c].scsi_device_id >> 4) & 0x0f; + const uint8_t scsi_id = superdisk_drives[c].scsi_device_id & 0x0f; + + /* Make sure to ignore any SCSI SuperDisk drive that has an out of range SCSI bus. */ + if (scsi_bus >= SCSI_BUS_MAX) + continue; + + /* Make sure to ignore any SCSI SuperDisk drive that has an out of range ID. */ + if (scsi_id >= SCSI_ID_MAX) + continue; + } + + /* Make sure to ignore any ATAPI SuperDisk drive that has an out of range IDE channel. */ + if ((superdisk_drives[c].bus_type == SUPERDISK_BUS_ATAPI) && (superdisk_drives[c].ide_channel > 7)) + continue; + + superdisk_drive_reset(c); + + superdisk_t *dev = (superdisk_t *) superdisk_drives[c].priv; + + superdisk_log("SuperDisk hard_reset drive=%d\n", c); + + if (dev->tf == NULL) + continue; + + dev->id = c; + dev->drv = &superdisk_drives[c]; + + superdisk_init(dev); + + if (strlen(superdisk_drives[c].image_path)) + superdisk_load(dev, superdisk_drives[c].image_path, 0); + + superdisk_mode_sense_load(dev); + + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI) + superdisk_log("SCSI SuperDisk drive %i attached to SCSI ID %i\n", + c, superdisk_drives[c].scsi_device_id); + else if (superdisk_drives[c].bus_type == SUPERDISK_BUS_ATAPI) + superdisk_log("ATAPI SuperDisk drive %i attached to IDE channel %i\n", + c, superdisk_drives[c].ide_channel); + } + } +} + +void +superdisk_close(void) +{ + for (uint8_t c = 0; c < SUPERDISK_NUM; c++) { + if (superdisk_drives[c].bus_type == SUPERDISK_BUS_SCSI) { + const uint8_t scsi_bus = (superdisk_drives[c].scsi_device_id >> 4) & 0x0f; + const uint8_t scsi_id = superdisk_drives[c].scsi_device_id & 0x0f; + + memset(&scsi_devices[scsi_bus][scsi_id], 0x00, sizeof(scsi_device_t)); + } + + superdisk_t *dev = (superdisk_t *) superdisk_drives[c].priv; + + if (dev) { + superdisk_disk_unload(dev); + + if (dev->tf) + free(dev->tf); + + free(dev); + superdisk_drives[c].priv = NULL; + } + } +} diff --git a/src/include/86box/disksizes.h b/src/include/86box/disksizes.h index 642d099fc..18a135421 100644 --- a/src/include/86box/disksizes.h +++ b/src/include/86box/disksizes.h @@ -35,7 +35,7 @@ typedef struct disk_size_t { int root_dir_entries; } disk_size_t; -static const disk_size_t disk_sizes[14] = { +static const disk_size_t disk_sizes[16] = { // clang-format off #if 0 { 1, 1, 2, 1, 1, 77, 26, 0, 0, 4, 2, 6, 68 }, /* 250k 8" */ @@ -57,10 +57,8 @@ static const disk_size_t disk_sizes[14] = { { 2, 2, 3, 1, 0, 80, 36, 2, 0xf0, 2, 2, 9, 240 }, /* 2.88M */ { 0, 64, 0, 0, 0, 96, 32, 2, 0, 0, 0, 0, 0 }, /* ZIP 100 */ { 0, 64, 0, 0, 0, 239, 32, 2, 0, 0, 0, 0, 0 }, /* ZIP 250 */ -#if 0 { 0, 8, 0, 0, 0, 963, 32, 2, 0, 0, 0, 0, 0 }, /* LS-120 */ { 0, 32, 0, 0, 0, 262, 56, 2, 0, 0, 0, 0, 0 } /* LS-240 */ -#endif // clang-format on }; diff --git a/src/include/86box/machine_status.h b/src/include/86box/machine_status.h index a3d3bdcd7..a44ebc465 100644 --- a/src/include/86box/machine_status.h +++ b/src/include/86box/machine_status.h @@ -21,6 +21,7 @@ typedef struct machine_status_t { dev_status_empty_active_t fdd[FDD_NUM]; dev_status_empty_active_t cdrom[CDROM_NUM]; dev_status_empty_active_t rdisk[RDISK_NUM]; + dev_status_empty_active_t superdisk[SUPERDISK_NUM]; dev_status_empty_active_t mo[MO_NUM]; dev_status_empty_active_t cassette; dev_status_active_t hdd[HDD_BUS_USB]; diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index efbd06898..a719fdc8f 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -183,6 +183,9 @@ extern void rdisk_reload(uint8_t id); extern void mo_eject(uint8_t id); extern void mo_mount(uint8_t id, char *fn, uint8_t wp); extern void mo_reload(uint8_t id); +extern void superdisk_eject(uint8_t id); +extern void superdisk_mount(uint8_t id, char *fn, uint8_t wp); +extern void superdisk_reload(uint8_t id); /* Other stuff. */ extern void startblit(void); diff --git a/src/include/86box/scsi_device.h b/src/include/86box/scsi_device.h index f4c37e56c..67db6cb11 100644 --- a/src/include/86box/scsi_device.h +++ b/src/include/86box/scsi_device.h @@ -46,11 +46,14 @@ #define GPCMD_REQUEST_SENSE 0x03 #define GPCMD_FORMAT_UNIT 0x04 #define GPCMD_IOMEGA_SENSE 0x06 +#define GPCMD_IMATION_SENSE 0x06 #define GPCMD_READ_6 0x08 #define GPCMD_WRITE_6 0x0a #define GPCMD_SEEK_6 0x0b #define GPCMD_IOMEGA_SET_PROTECTION_MODE 0x0c +#define GPCMD_IMATION_SET_PROTECTION_MODE 0x0c #define GPCMD_IOMEGA_EJECT 0x0d /* ATAPI only? */ +#define GPCMD_IMATION_EJECT 0x0d /* ATAPI only? */ #define GPCMD_NO_OPERATION_TOSHIBA 0x0d /* Toshiba Vendor Unique command. */ #define GPCMD_NO_OPERATION_NEC 0x0d /* NEC Vendor Unique command. */ #define GPCMD_INQUIRY 0x12 @@ -160,21 +163,23 @@ #define GPCMD_PLAY_AUDIO_TRACK_RELATIVE_12_MATSUSHITA 0xe9 /* Matsushita Vendor Unique command */ /* Mode page codes for mode sense/set */ -#define GPMODE_UNIT_ATN_PAGE 0x00 -#define GPMODE_R_W_ERROR_PAGE 0x01 +#define GPMODE_UNIT_ATN_PAGE 0x00 /* Unit Attention page */ +#define GPMODE_R_W_ERROR_PAGE 0x01 /* Read-Write Error Recovery page */ #define GPMODE_DISCONNECT_PAGE 0x02 /* Disconnect/reconnect page */ -#define GPMODE_FORMAT_DEVICE_PAGE 0x03 +#define GPMODE_FORMAT_DEVICE_PAGE 0x03 /* Format device page */ #define GPMODE_RIGID_DISK_PAGE 0x04 /* Rigid disk geometry page */ -#define GPMODE_FLEXIBLE_DISK_PAGE 0x05 -#define GPMODE_CACHING_PAGE 0x08 -#define GPMODE_CDROM_PAGE_SONY 0x08 -#define GPMODE_CDROM_AUDIO_PAGE_SONY 0x09 -#define GPMODE_CDROM_PAGE 0x0d -#define GPMODE_CDROM_AUDIO_PAGE 0x0e -#define GPMODE_CAPABILITIES_PAGE 0x2a -#define GPMODE_IOMEGA_PAGE 0x2f -#define GPMODE_UNK_VENDOR_PAGE 0x30 -#define GPMODE_ALL_PAGES 0x3f +#define GPMODE_FLEXIBLE_DISK_PAGE 0x05 /* Flexible disk page */ +#define GPMODE_CACHING_PAGE 0x08 /* Caching page */ +#define GPMODE_CDROM_PAGE_SONY 0x08 /* Sony-specific CD-ROM page */ +#define GPMODE_CDROM_AUDIO_PAGE_SONY 0x09 /* Sony-specific CD-ROM audio page */ +#define GPMODE_MEDIUM_TYPES_PAGE 0x0b /* Medium types supported page */ +#define GPMODE_CDROM_PAGE 0x0d /* General CD-ROM page */ +#define GPMODE_CDROM_AUDIO_PAGE 0x0e /* General CD-ROM audio page */ +#define GPMODE_REMOVABLE_BLOCK_ACCESS_PAGE 0x1b /* Removable block access capabilities page */ +#define GPMODE_CAPABILITIES_PAGE 0x2a /* Capabilities page */ +#define GPMODE_IOMEGA_PAGE 0x2f /* Iomega-specific page */ +#define GPMODE_UNK_VENDOR_PAGE 0x30 /* Unknown vendor-specific page */ +#define GPMODE_ALL_PAGES 0x3f /* All pages supported */ /* Mode page codes for presence */ #define GPMODEP_UNIT_ATN_PAGE 0x0000000000000001LL diff --git a/src/include/86box/superdisk.h b/src/include/86box/superdisk.h new file mode 100644 index 000000000..de16f6547 --- /dev/null +++ b/src/include/86box/superdisk.h @@ -0,0 +1,148 @@ +/* + * 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 the Imation SuperDisk drive with SCSI(-like) + * commands, for both ATAPI and SCSI usage. + * + * + * + * Authors: Miran Grca, + * Jasmine Iwanek, + * + * Copyright 2018-2025 Miran Grca. + * Copyright 2022-2025 Jasmine Iwanek. + */ + +#ifndef EMU_SUPERDISK_H +#define EMU_SUPERDISK_H + +#define SUPERDISK_NUM 4 + +#define BUF_SIZE 32768 + +#define SUPERDISK_TIME 10.0 + +#define SUPERDISK_SECTORS (963 * 256) + +#define SUPERDISK_240_SECTORS (469504) + +#define SUPERDISK_IMAGE_HISTORY 10 + +enum { + SUPERDISK_BUS_DISABLED = 0, + SUPERDISK_BUS_ATAPI = 5, + SUPERDISK_BUS_SCSI = 6, + SUPERDISK_BUS_USB = 7 +}; + +typedef struct superdisk_drive_t { + uint8_t id; + + union { + uint8_t res; + /* Reserved for other ID's. */ + uint8_t res0; + uint8_t res1; + uint8_t ide_channel; + uint8_t scsi_device_id; + }; + + uint8_t bus_type; /* 0 = ATAPI, 1 = SCSI */ + uint8_t bus_mode; /* Bit 0 = PIO suported; + Bit 1 = DMA supportd. */ + uint8_t read_only; /* Struct variable reserved for + media status. */ + uint8_t pad; + uint8_t pad0; + + FILE *fp; + void *priv; + + char image_path[1024]; + char prev_image_path[1024]; + + char *image_history[SUPERDISK_IMAGE_HISTORY]; + + uint32_t is_240; + uint32_t medium_size; + uint32_t base; +} superdisk_drive_t; + +typedef struct superdisk_t { + mode_sense_pages_t ms_pages_saved; + + superdisk_drive_t *drv; +#ifdef EMU_IDE_H + ide_tf_t *tf; +#else + void *tf; +#endif + + uint8_t *buffer; + uint8_t atapi_cdb[16]; + uint8_t current_cdb[16]; + uint8_t sense[256]; + + uint8_t id; + uint8_t cur_lun; + uint8_t pad0; + uint8_t pad1; + + uint16_t max_transfer_len; + uint16_t pad2; + + int requested_blocks; + int packet_status; + int total_length; + int do_page_save; + int unit_attention; + int request_pos; + int old_len; + int transition; + + uint32_t sector_pos; + uint32_t sector_len; + uint32_t packet_len; + + double callback; + + uint8_t (*ven_cmd)(void *sc, uint8_t *cdb, int32_t *BufLen); +} superdisk_t; + +extern superdisk_t *superdisk[SUPERDISK_NUM]; +extern superdisk_drive_t superdisk_drives[SUPERDISK_NUM]; +extern uint8_t atapi_superdisk_drives[8]; +extern uint8_t scsi_superdisk_drives[16]; + +#define superdisk_sense_error dev->sense[0] +#define superdisk_sense_key dev->sense[2] +#define superdisk_info *(uint32_t *) &(dev->sense[3]) +#define superdisk_asc dev->sense[12] +#define superdisk_ascq dev->sense[13] + +#ifdef __cplusplus +extern "C" { +#endif + +extern void superdisk_disk_close(const superdisk_t *dev); +extern void superdisk_disk_reload(const superdisk_t *dev); +extern void superdisk_insert(superdisk_t *dev); + +extern void superdisk_global_init(void); +extern void superdisk_hard_reset(void); + +extern void superdisk_reset(scsi_common_t *sc); +extern int superdisk_is_empty(const uint8_t id); +extern void superdisk_load(const superdisk_t *dev, const char *fn, const int skip_insert); +extern void superdisk_close(void); + +#ifdef __cplusplus +} +#endif + +#endif /*EMU_SUPERDISK_H*/ diff --git a/src/include/86box/ui.h b/src/include/86box/ui.h index 08719cc2b..b7b3dba47 100644 --- a/src/include/86box/ui.h +++ b/src/include/86box/ui.h @@ -49,10 +49,11 @@ extern int ui_msgbox_header(int flags, void *header, void *message); #define SB_CDROM 0x30 #define SB_RDISK 0x40 #define SB_MO 0x50 -#define SB_HDD 0x60 -#define SB_NETWORK 0x70 -#define SB_SOUND 0x80 -#define SB_TEXT 0x90 +#define SB_SUPERDISK 0x60 +#define SB_HDD 0x70 +#define SB_NETWORK 0x80 +#define SB_SOUND 0x90 +#define SB_TEXT 0x100 extern wchar_t *ui_window_title(wchar_t *s); extern void ui_hard_reset_completed(void); diff --git a/src/machine_status.c b/src/machine_status.c index c1396975d..8bc6ac6ee 100644 --- a/src/machine_status.c +++ b/src/machine_status.c @@ -17,6 +17,7 @@ #include <86box/cassette.h> #include <86box/cdrom.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/hdd.h> #include <86box/thread.h> @@ -32,14 +33,22 @@ machine_status_init(void) machine_status.fdd[i].empty = (strlen(floppyfns[i]) == 0); machine_status.fdd[i].active = false; } + for (size_t i = 0; i < CDROM_NUM; ++i) { machine_status.cdrom[i].empty = (strlen(cdrom[i].image_path) == 0); machine_status.cdrom[i].active = false; } + for (size_t i = 0; i < RDISK_NUM; i++) { machine_status.rdisk[i].empty = (strlen(rdisk_drives[i].image_path) == 0); machine_status.rdisk[i].active = false; } + + for (size_t i = 0; i < SUPERDISK_NUM; i++) { + machine_status.superdisk[i].empty = (strlen(superdisk_drives[i].image_path) == 0); + machine_status.superdisk[i].active = false; + } + for (size_t i = 0; i < MO_NUM; i++) { machine_status.mo[i].empty = (strlen(mo_drives[i].image_path) == 0); machine_status.mo[i].active = false; diff --git a/src/qt/icons/superdisk_active.ico b/src/qt/icons/superdisk_active.ico new file mode 100644 index 000000000..5932f3fee Binary files /dev/null and b/src/qt/icons/superdisk_active.ico differ diff --git a/src/qt/icons/superdisk_empty.ico b/src/qt/icons/superdisk_empty.ico new file mode 100644 index 000000000..c0a5ec39a Binary files /dev/null and b/src/qt/icons/superdisk_empty.ico differ diff --git a/src/qt/icons/superdisk_empty_active.ico b/src/qt/icons/superdisk_empty_active.ico new file mode 100644 index 000000000..bba1e580f Binary files /dev/null and b/src/qt/icons/superdisk_empty_active.ico differ diff --git a/src/qt/languages/ca-ES.po b/src/qt/languages/ca-ES.po index 813b82dcb..f9a7ac3e5 100644 --- a/src/qt/languages/ca-ES.po +++ b/src/qt/languages/ca-ES.po @@ -648,6 +648,12 @@ msgstr "Unitats de disc extraïble:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/cs-CZ.po b/src/qt/languages/cs-CZ.po index f27ac9d3c..fdd52bdf8 100644 --- a/src/qt/languages/cs-CZ.po +++ b/src/qt/languages/cs-CZ.po @@ -648,6 +648,12 @@ msgstr "Mechaniky vyměnitelných disků:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA hodiny:" diff --git a/src/qt/languages/de-DE.po b/src/qt/languages/de-DE.po index fc201c303..65ef78b42 100644 --- a/src/qt/languages/de-DE.po +++ b/src/qt/languages/de-DE.po @@ -648,6 +648,12 @@ msgstr "Wechseldatenträger:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA-Echtzeituhr:" diff --git a/src/qt/languages/es-ES.po b/src/qt/languages/es-ES.po index 54e3d2079..f216a7b3f 100644 --- a/src/qt/languages/es-ES.po +++ b/src/qt/languages/es-ES.po @@ -648,6 +648,12 @@ msgstr "Unidades de disco removible:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/fi-FI.po b/src/qt/languages/fi-FI.po index 9d4e84f39..f8f973297 100644 --- a/src/qt/languages/fi-FI.po +++ b/src/qt/languages/fi-FI.po @@ -648,6 +648,12 @@ msgstr "Irrotettavat levyasemat:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA-RTC (kello):" diff --git a/src/qt/languages/fr-FR.po b/src/qt/languages/fr-FR.po index 453b52de4..05820a9bc 100644 --- a/src/qt/languages/fr-FR.po +++ b/src/qt/languages/fr-FR.po @@ -648,6 +648,12 @@ msgstr "Lecteurs de disques amovibles :" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "Horloge temps réel ISA :" diff --git a/src/qt/languages/hr-HR.po b/src/qt/languages/hr-HR.po index f1e8e3688..228938c5d 100644 --- a/src/qt/languages/hr-HR.po +++ b/src/qt/languages/hr-HR.po @@ -650,6 +650,12 @@ msgstr "Pogoni izmjenjivih diskova:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "Sat stvarnog vremena (RTC):" diff --git a/src/qt/languages/it-IT.po b/src/qt/languages/it-IT.po index 71fc69bce..2b9778654 100644 --- a/src/qt/languages/it-IT.po +++ b/src/qt/languages/it-IT.po @@ -648,6 +648,12 @@ msgstr "Unità disco rimovibili:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "RTC ISA:" diff --git a/src/qt/languages/ja-JP.po b/src/qt/languages/ja-JP.po index 487e5e297..07190acbb 100644 --- a/src/qt/languages/ja-JP.po +++ b/src/qt/languages/ja-JP.po @@ -649,6 +649,12 @@ msgstr "取り外し可能なディスクドライブ:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTCカード:" diff --git a/src/qt/languages/ko-KR.po b/src/qt/languages/ko-KR.po index cd85aec57..0a5aa7c59 100644 --- a/src/qt/languages/ko-KR.po +++ b/src/qt/languages/ko-KR.po @@ -648,6 +648,12 @@ msgstr "제거 가능한 디스크 드라이브:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC 카드:" diff --git a/src/qt/languages/pl-PL.po b/src/qt/languages/pl-PL.po index e235bf995..6af86a439 100644 --- a/src/qt/languages/pl-PL.po +++ b/src/qt/languages/pl-PL.po @@ -649,6 +649,12 @@ msgstr "Stacje dysków wymiennych:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/pt-BR.po b/src/qt/languages/pt-BR.po index bd95c22ce..f90872015 100644 --- a/src/qt/languages/pt-BR.po +++ b/src/qt/languages/pt-BR.po @@ -642,6 +642,12 @@ msgstr "Unidades de disco removível:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "RTC ISA:" diff --git a/src/qt/languages/pt-PT.po b/src/qt/languages/pt-PT.po index 123376ee1..1e272cecb 100644 --- a/src/qt/languages/pt-PT.po +++ b/src/qt/languages/pt-PT.po @@ -649,6 +649,12 @@ msgstr "Unidades de disco amovível:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/ru-RU.po b/src/qt/languages/ru-RU.po index 2f272b262..f7bf513d6 100644 --- a/src/qt/languages/ru-RU.po +++ b/src/qt/languages/ru-RU.po @@ -649,6 +649,12 @@ msgstr "Дисководы съёмных дисков:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/sk-SK.po b/src/qt/languages/sk-SK.po index 2de580e32..4e8d3dd04 100644 --- a/src/qt/languages/sk-SK.po +++ b/src/qt/languages/sk-SK.po @@ -648,6 +648,12 @@ msgstr "Mechaniky vymeniteľných diskov:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA hodiny:" diff --git a/src/qt/languages/sl-SI.po b/src/qt/languages/sl-SI.po index bbddd9a7e..65f9a7a4b 100644 --- a/src/qt/languages/sl-SI.po +++ b/src/qt/languages/sl-SI.po @@ -650,6 +650,12 @@ msgstr "Odstranljivi diskovni pogoni:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "Ura realnega časa ISA:" diff --git a/src/qt/languages/tr-TR.po b/src/qt/languages/tr-TR.po index d113c92b8..c47f88758 100644 --- a/src/qt/languages/tr-TR.po +++ b/src/qt/languages/tr-TR.po @@ -648,6 +648,12 @@ msgstr "Çıkarılabilir disk sürücüleri:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/uk-UA.po b/src/qt/languages/uk-UA.po index 3e6b695e1..0e66dfb71 100644 --- a/src/qt/languages/uk-UA.po +++ b/src/qt/languages/uk-UA.po @@ -650,6 +650,12 @@ msgstr "Дисководи знімних дисків:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA RTC:" diff --git a/src/qt/languages/zh-CN.po b/src/qt/languages/zh-CN.po index 706c7f8ad..ae29659b9 100644 --- a/src/qt/languages/zh-CN.po +++ b/src/qt/languages/zh-CN.po @@ -649,6 +649,12 @@ msgstr "可移动磁盘 (ZIP) 驱动器:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA 实时时钟:" diff --git a/src/qt/languages/zh-TW.po b/src/qt/languages/zh-TW.po index 2b2189f2c..41092b301 100644 --- a/src/qt/languages/zh-TW.po +++ b/src/qt/languages/zh-TW.po @@ -649,6 +649,12 @@ msgstr "抽取式磁碟機:" msgid "ZIP 250" msgstr "ZIP 250" +msgid "Superdrives:" +msgstr "Superdrives:" + +msgid "LS-240" +msgstr "LS-240" + msgid "ISA RTC:" msgstr "ISA 實時時鐘:" diff --git a/src/qt/qt_machinestatus.cpp b/src/qt/qt_machinestatus.cpp index e6f6c824e..1d29d3524 100644 --- a/src/qt/qt_machinestatus.cpp +++ b/src/qt/qt_machinestatus.cpp @@ -30,6 +30,7 @@ extern "C" { #include <86box/scsi.h> #include <86box/scsi_device.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/plat.h> #include <86box/machine.h> @@ -107,6 +108,7 @@ struct Pixmaps { PixmapSetEmptyActive floppy_35; PixmapSetEmptyActive cdrom; PixmapSetEmptyActive rdisk; + PixmapSetEmptyActive superdisk; PixmapSetEmptyActive mo; PixmapSetActive hd; PixmapSetEmptyActive net; @@ -328,6 +330,7 @@ struct MachineStatus::States { pixmaps.floppy_35.load(QIcon(":/settings/qt/icons/floppy_35.ico")); pixmaps.cdrom.load(QIcon(":/settings/qt/icons/cdrom.ico")); pixmaps.rdisk.load(QIcon(":/settings/qt/icons/rdisk.ico")); + pixmaps.superdisk.load(QIcon(":/settings/qt/icons/superdisk.ico")); pixmaps.mo.load(QIcon(":/settings/qt/icons/mo.ico")); pixmaps.hd.load(QIcon(":/settings/qt/icons/hard_disk.ico")); pixmaps.net.load(QIcon(":/settings/qt/icons/network.ico")); @@ -345,6 +348,9 @@ struct MachineStatus::States { for (auto &z : rdisk) { z.pixmaps = &pixmaps.rdisk; } + for (auto &z : superdisk) { + z.pixmaps = &pixmaps.superdisk; + } for (auto &m : mo) { m.pixmaps = &pixmaps.mo; } @@ -361,6 +367,7 @@ struct MachineStatus::States { std::array fdd; std::array cdrom; std::array rdisk; + std::array superdisk; std::array mo; std::array hdds; std::array net; @@ -461,6 +468,27 @@ MachineStatus::iterateRDisk(const std::function &cb) } } +void +MachineStatus::iterateSuperdisk(const std::function &cb) +{ + auto hdc_name = QString(hdc_get_internal_name(hdc_current[0])); + for (size_t i = 0; i < SUPERDISK_NUM; i++) { + /* Could be Internal or External IDE.. */ + if ((superdisk_drives[i].bus_type == SUPERDISK_BUS_ATAPI) && !hasIDE() && + (hdc_name.left(3) != QStringLiteral("ide")) && + (hdc_name.left(5) != QStringLiteral("xtide")) && + (hdc_name.left(5) != QStringLiteral("mcide"))) + continue; + if ((superdisk_drives[i].bus_type == SUPERDISK_BUS_SCSI) && !hasSCSI() && + (scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) && + (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) + continue; + if (superdisk_drives[i].bus_type != 0) { + cb(i); + } + } +} + void MachineStatus::iterateMO(const std::function &cb) { @@ -580,6 +608,10 @@ MachineStatus::refreshIcons() if (machine_status.rdisk[i].write_active) ui_sb_update_icon_write(SB_RDISK | i, 0); } + for (size_t i = 0; i < SUPERDISK_NUM; i++) { + d->superdisk[i].setActive(machine_status.superdisk[i].active); + d->superdisk[i].setEmpty(machine_status.superdisk[i].empty); + } for (size_t i = 0; i < MO_NUM; i++) { d->mo[i].setActive(machine_status.mo[i].active); d->mo[i].setWriteActive(machine_status.mo[i].write_active); @@ -619,6 +651,10 @@ MachineStatus::clearActivity() rdisk.setActive(false); rdisk.setWriteActive(false); } + for (auto &superdisk : d->superdisk) { + superdisk.setActive(false); + superdisk.setWriteActive(false); + } for (auto &mo : d->mo) { mo.setActive(false); mo.setWriteActive(false); @@ -660,6 +696,9 @@ MachineStatus::refresh(QStatusBar *sbar) for (size_t i = 0; i < RDISK_NUM; i++) { sbar->removeWidget(d->rdisk[i].label.get()); } + for (size_t i = 0; i < SUPERDISK_NUM; i++) { + sbar->removeWidget(d->superdisk[i].label.get()); + } for (size_t i = 0; i < MO_NUM; i++) { sbar->removeWidget(d->mo[i].label.get()); } @@ -780,6 +819,22 @@ MachineStatus::refresh(QStatusBar *sbar) sbar->addWidget(d->rdisk[i].label.get()); }); + iterateSuperdisk([this, sbar](int i) { + d->superdisk[i].label = std::make_unique(); + d->superdisk[i].setEmpty(QString(superdisk_drives[i].image_path).isEmpty()); + d->superdisk[i].setActive(false); + d->superdisk[i].refresh(); + connect((ClickableLabel *) d->superdisk[i].label.get(), &ClickableLabel::clicked, [i](QPoint pos) { + MediaMenu::ptr->superdiskMenus[i]->popup(pos - QPoint(0, MediaMenu::ptr->superdiskMenus[i]->sizeHint().height())); + }); + connect((ClickableLabel *) d->superdisk[i].label.get(), &ClickableLabel::dropped, [i](QString str) { + MediaMenu::ptr->superdiskMount(i, str, false); + }); + d->superdisk[i].label->setToolTip(MediaMenu::ptr->superdiskMenus[i]->title()); + d->superdisk[i].label->setAcceptDrops(true); + sbar->addWidget(d->superdisk[i].label.get()); + }); + iterateMO([this, sbar](int i) { d->mo[i].label = std::make_unique(); d->mo[i].setEmpty(QString(mo_drives[i].image_path).isEmpty()); @@ -985,6 +1040,10 @@ MachineStatus::updateTip(int tag) if (d->rdisk[item].label && MediaMenu::ptr->rdiskMenus[item]) d->rdisk[item].label->setToolTip(MediaMenu::ptr->rdiskMenus[item]->toolTip()); break; + case SB_SUPERDISK: + if (d->superdisk[item].label && MediaMenu::ptr->superdiskMenus[item]) + d->superdisk[item].label->setToolTip(MediaMenu::ptr->superdiskMenus[item]->title()); + break; case SB_MO: if (d->mo[item].label && MediaMenu::ptr->moMenus[item]) d->mo[item].label->setToolTip(MediaMenu::ptr->moMenus[item]->toolTip()); diff --git a/src/qt/qt_machinestatus.hpp b/src/qt/qt_machinestatus.hpp index 2e270ec84..d929d856c 100644 --- a/src/qt/qt_machinestatus.hpp +++ b/src/qt/qt_machinestatus.hpp @@ -73,6 +73,7 @@ public: static void iterateFDD(const std::function &cb); static void iterateCDROM(const std::function &cb); static void iterateRDisk(const std::function &cb); + static void iterateSuperdisk(const std::function &cb); static void iterateMO(const std::function &cb); static void iterateNIC(const std::function &cb); diff --git a/src/qt/qt_mediahistorymanager.hpp b/src/qt/qt_mediahistorymanager.hpp index fcde55fa8..f40e1bfdd 100644 --- a/src/qt/qt_mediahistorymanager.hpp +++ b/src/qt/qt_mediahistorymanager.hpp @@ -44,6 +44,7 @@ enum class MediaType { Optical, RDisk, Mo, + SuperDisk, Cassette, Cartridge }; @@ -57,10 +58,10 @@ typedef QHash master_list_t; // Used to iterate over all supported types when preparing data structures // Also useful to indicate which types support history static const MediaType AllSupportedMediaHistoryTypes[] = { - MediaType::Optical, MediaType::Floppy, MediaType::RDisk, MediaType::Mo, + MediaType::SuperDisk, MediaType::Cassette, MediaType::Cartridge }; diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index c9250f44c..e42c54da7 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -54,6 +54,7 @@ extern "C" { #include <86box/cdrom.h> #include <86box/scsi_device.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/sound.h> #include <86box/ui.h> @@ -1136,6 +1137,141 @@ MediaMenu::moReload(int index, int slot) ui_sb_update_tip(SB_MO | index); } +void +MediaMenu::superdiskNewImage(int i) +{ + NewFloppyDialog dialog(NewFloppyDialog::MediaType::SuperDisk, parentWidget); + switch (dialog.exec()) { + default: + break; + case QDialog::Accepted: + QByteArray filename = dialog.fileName().toUtf8(); + superdiskMount(i, filename, false); + break; + } +} + +void +MediaMenu::superdiskSelectImage(int i, bool wp) +{ + const auto filename = QFileDialog::getOpenFileName( + parentWidget, + QString(), + QString(), + tr("SuperDisk images") % util::DlgFilter({ "im?", "sdi" }) % tr("All files") % util::DlgFilter({ "*" }, true)); + + if (!filename.isEmpty()) + superdiskMount(i, filename, wp); +} + +void +MediaMenu::superdiskMount(int i, const QString &filename, bool wp) +{ + const auto dev = static_cast(superdisk_drives[i].priv); + int was_empty = superdisk_is_empty(i); + + superdisk_disk_close(dev); + superdisk_drives[i].read_only = wp; + if (!filename.isEmpty()) { + QByteArray filenameBytes = filename.toUtf8(); + superdisk_load(dev, filenameBytes.data(), 1); + + /* Signal media change to the emulated machine. */ + superdisk_insert(dev); + + /* The drive was previously empty, transition directly to UNIT ATTENTION. */ + if (was_empty) + superdisk_insert(dev); + } + mhm.addImageToHistory(i, ui::MediaType::SuperDisk, superdisk_drives[i].prev_image_path, superdisk_drives[i].image_path); + + ui_sb_update_icon_state(SB_SUPERDISK | i, filename.isEmpty() ? 1 : 0); + superdiskUpdateMenu(i); + ui_sb_update_tip(SB_SUPERDISK | i); + + config_save(); +} + +void +MediaMenu::superdiskEject(int i) +{ + const auto dev = static_cast(superdisk_drives[i].priv); + + mhm.addImageToHistory(i, ui::MediaType::SuperDisk, superdisk_drives[i].image_path, QString()); + superdisk_disk_close(dev); + superdisk_drives[i].image_path[0] = 0; + if (superdisk_drives[i].bus_type) { + /* Signal disk change to the emulated machine. */ + superdisk_insert(dev); + } + + ui_sb_update_icon_state(SB_SUPERDISK | i, 1); + superdiskUpdateMenu(i); + ui_sb_update_tip(SB_SUPERDISK | i); + config_save(); +} + +void +MediaMenu::superdiskReloadPrev(int i) +{ + const auto dev = static_cast(superdisk_drives[i].priv); + + superdisk_disk_reload(dev); + if (strlen(superdisk_drives[i].image_path) == 0) { + ui_sb_update_icon_state(SB_SUPERDISK | i, 1); + } else { + ui_sb_update_icon_state(SB_SUPERDISK | i, 0); + } + + superdiskUpdateMenu(i); + ui_sb_update_tip(SB_SUPERDISK | i); + + config_save(); +} + +void +MediaMenu::superdiskReload(int index, int slot) +{ + const QString filename = mhm.getImageForSlot(index, slot, ui::MediaType::SuperDisk); + superdiskMount(index, filename, false); + superdiskUpdateMenu(index); + ui_sb_update_tip(SB_SUPERDISK | index); +} + +void +MediaMenu::superdiskUpdateMenu(int i) +{ + const QString name = superdisk_drives[i].image_path; + const QString prev_name = superdisk_drives[i].prev_image_path; + if (!superdiskMenus.contains(i)) + return; + auto *menu = superdiskMenus[i]; + auto childs = menu->children(); + + auto *ejectMenu = dynamic_cast(childs[superdiskEjectPos]); + ejectMenu->setEnabled(!name.isEmpty()); + + QString busName = tr("Unknown Bus"); + switch (superdisk_drives[i].bus_type) { + default: + break; + case SUPERDISK_BUS_ATAPI: + busName = "ATAPI"; + break; + case SUPERDISK_BUS_SCSI: + busName = "SCSI"; + break; + } + +#if 0 + menu->setTitle(tr("SuperDisk %1 %2 (%3): %4").arg((superdisk_drives[i].is_240 > 0) ? "240" : "120", QString::number(i+1), busName, name.isEmpty() ? tr("(empty)") : name)); +#endif + menu->setTitle(QString::asprintf(tr("SuperDisk %03i %i (%s): %ls").toUtf8().constData(), (superdisk_drives[i].is_240 > 0) ? 240 : 120, i + 1, busName.toUtf8().data(), name.isEmpty() ? tr("(empty)").toStdU16String().data() : name.toStdU16String().data())); + + for (int slot = 0; slot < MAX_PREV_IMAGES; slot++) + updateImageHistory(i, slot, ui::MediaType::SuperDisk); +} + void MediaMenu::nicConnect(int i) { @@ -1295,4 +1431,23 @@ mo_reload(uint8_t id) { MediaMenu::ptr->moReloadPrev(id); } + +void +superdisk_eject(uint8_t id) +{ + MediaMenu::ptr->superdiskEject(id); +} + +void +superdisk_mount(uint8_t id, char *fn, uint8_t wp) +{ + MediaMenu::ptr->superdiskMount(id, QString(fn), wp); +} + +void +superdisk_reload(uint8_t id) +{ + MediaMenu::ptr->superdiskReloadPrev(id); +} + } diff --git a/src/qt/qt_mediamenu.hpp b/src/qt/qt_mediamenu.hpp index fb24d24e0..c8f7be657 100644 --- a/src/qt/qt_mediamenu.hpp +++ b/src/qt/qt_mediamenu.hpp @@ -67,6 +67,14 @@ public: void moReload(int index, int slot); void moUpdateMenu(int i); + void superdiskNewImage(int i); + void superdiskSelectImage(int i, bool wp); + void superdiskMount(int i, const QString &filename, bool wp); + void superdiskEject(int i); + void superdiskReloadPrev(int i); + void superdiskReload(int index, int slot); + void superdiskUpdateMenu(int i); + void nicConnect(int i); void nicDisconnect(int i); void nicUpdateMenu(int i); @@ -86,6 +94,7 @@ private: QMap cdromMenus; QMap rdiskMenus; QMap moMenus; + QMap superdiskMenus; QMap netMenus; QString getMediaOpenDirectory(); @@ -117,6 +126,9 @@ private: int moEjectPos; int moImageHistoryPos[MAX_PREV_IMAGES]; + int superdiskEjectPos; + int superdiskReloadPos; + int netDisconnPos; friend class MachineStatus; diff --git a/src/qt/qt_newfloppydialog.cpp b/src/qt/qt_newfloppydialog.cpp index 98078004a..1b09eec4c 100644 --- a/src/qt/qt_newfloppydialog.cpp +++ b/src/qt/qt_newfloppydialog.cpp @@ -28,6 +28,7 @@ extern "C" { #include <86box/random.h> #include <86box/scsi_device.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> } @@ -57,7 +58,7 @@ struct disk_size_t { int root_dir_entries; }; -static const disk_size_t disk_sizes[14] = { +static const disk_size_t disk_sizes[16] = { // clang-format off #if 0 { 1, 1, 2, 1, 1, 77, 26, 0, 0, 4, 2, 6, 68 }, /* 250k 8" */ @@ -79,11 +80,9 @@ static const disk_size_t disk_sizes[14] = { { 2, 2, 3, 1, 0, 80, 36, 2, 0xf0, 2, 2, 9, 240 }, /* 2.88M */ { 0, 64, 0, 0, 0, 96, 32, 2, 0, 0, 0, 0, 0 }, /* ZIP 100 */ { 0, 64, 0, 0, 0, 239, 32, 2, 0, 0, 0, 0, 0 }, /* ZIP 250 */ -#if 0 { 0, 8, 0, 0, 0, 963, 32, 2, 0, 0, 0, 0, 0 }, /* LS-120 */ { 0, 32, 0, 0, 0, 262, 56, 2, 0, 0, 0, 0, 0 } /* LS-240 */ -#endif - // clang-format on +// clang-format on }; static const QStringList rpmModes = { @@ -126,6 +125,11 @@ static const QStringList moTypes = { "5.25\" 1.3 GB", }; +static const QStringList superDiskTypes = { + "LS-120", + "LS-240", +}; + NewFloppyDialog::NewFloppyDialog(MediaType type, QWidget *parent) : QDialog(parent) , ui(new Ui::NewFloppyDialog) @@ -150,6 +154,12 @@ NewFloppyDialog::NewFloppyDialog(MediaType type, QWidget *parent) } ui->fileField->setFilter(tr("Removable disk images") % util::DlgFilter({ "im?", "img", "rdi", "zdi" }, true)); break; + case MediaType::SuperDisk: + for (int i = 0; i < superDiskTypes.size(); ++i) { + Models::AddEntry(model, tr(superDiskTypes[i].toUtf8().data()), i); + } + ui->fileField->setFilter(tr("SuperDisk images") % util::DlgFilter({ "im?", "img", "sdi" }, true)); + break; case MediaType::Mo: for (int i = 0; i < moTypes.size(); ++i) { Models::AddEntry(model, tr(moTypes[i].toUtf8().data()), i); @@ -232,6 +242,22 @@ NewFloppyDialog::onCreate() } } break; + case MediaType::SuperDisk: + { + fileType = fi.suffix().toLower() == QStringLiteral("sdi") ? FileType::Sdi: FileType::Img; + + std::atomic_bool res; + std::thread t([this, &res, filename, fileType, &progress] { + res = createSuperDiskSectorImage(filename, disk_sizes[ui->comboBoxSize->currentIndex() + 12], fileType, progress); + }); + progress.exec(); + t.join(); + + if (res) { + return; + } + } + break; case MediaType::Mo: { fileType = fi.suffix().toLower() == QStringLiteral("mdi") ? FileType::Mdi : FileType::Img; @@ -708,3 +734,186 @@ NewFloppyDialog::createMoSectorImage(const QString &filename, int8_t disk_size, return true; } + +bool +NewFloppyDialog::createSuperDiskSectorImage(const QString &filename, const disk_size_t& disk_size, FileType type, QProgressDialog& pbar) +{ + uint32_t total_size = 0; + uint32_t total_sectors = 0; + uint32_t sector_bytes = 0; + uint16_t base = 0x1000; + uint32_t pbar_max = 0; + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + + sector_bytes = (128 << disk_size.sector_len); + total_sectors = disk_size.sides * disk_size.tracks * disk_size.sectors; + if (total_sectors > SUPERDISK_SECTORS) + total_sectors = SUPERDISK_240_SECTORS; + total_size = total_sectors * sector_bytes; + + pbar_max = total_size; + if (type == FileType::Zdi) { + pbar_max += base; + } + pbar_max >>= 11; + + if (type == FileType::Zdi) { + QByteArray data(base, 0); + auto empty = data.data(); + + *(uint32_t *) &(empty[0x08]) = (uint32_t) base; + *(uint32_t *) &(empty[0x0C]) = total_size; + *(uint16_t *) &(empty[0x10]) = (uint16_t) sector_bytes; + *(uint8_t *) &(empty[0x14]) = (uint8_t) disk_size.sectors; + *(uint8_t *) &(empty[0x18]) = (uint8_t) disk_size.sides; + *(uint8_t *) &(empty[0x1C]) = (uint8_t) disk_size.tracks; + + stream.writeRawData(empty, base); + pbar_max -= 2; + } + + QByteArray bytes(total_size, 0); + auto empty = bytes.data(); + + if (total_sectors == SUPERDISK_SECTORS) { + /* LS-120 */ + /* MBR */ + *(uint64_t *) &(empty[0x0000]) = 0x2054524150492EEBLL; + *(uint64_t *) &(empty[0x0008]) = 0x3930302065646F63LL; + *(uint64_t *) &(empty[0x0010]) = 0x67656D6F49202D20LL; + *(uint64_t *) &(empty[0x0018]) = 0x726F70726F432061LL; + *(uint64_t *) &(empty[0x0020]) = 0x202D206E6F697461LL; + *(uint64_t *) &(empty[0x0028]) = 0x30392F33322F3131LL; + + *(uint64_t *) &(empty[0x01AE]) = 0x0116010100E90644LL; + *(uint64_t *) &(empty[0x01B6]) = 0xED08BBE5014E0135LL; + *(uint64_t *) &(empty[0x01BE]) = 0xFFFFFE06FFFFFE80LL; + *(uint64_t *) &(empty[0x01C6]) = 0x0002FFE000000020LL; + + *(uint16_t *) &(empty[0x01FE]) = 0xAA55; + + /* 31 sectors filled with 0x48 */ + memset(&(empty[0x0200]), 0x48, 0x3E00); + + /* Boot sector */ + *(uint64_t *) &(empty[0x4000]) = 0x584F4236389058EBLL; + *(uint64_t *) &(empty[0x4008]) = 0x0008040200302E35LL; + *(uint64_t *) &(empty[0x4010]) = 0x00C0F80000020002LL; + *(uint64_t *) &(empty[0x4018]) = 0x0000002000FF003FLL; + *(uint32_t *) &(empty[0x4020]) = 0x0002FFE0; + *(uint16_t *) &(empty[0x4024]) = 0x0080; + + empty[0x4026] = 0x29; /* ')' followed by randomly-generated volume serial number. */ + empty[0x4027] = random_generate(); + empty[0x4028] = random_generate(); + empty[0x4029] = random_generate(); + empty[0x402A] = random_generate(); + + memset(&(empty[0x402B]), 0x00, 0x000B); + memset(&(empty[0x4036]), 0x20, 0x0008); + + empty[0x4036] = 'F'; + empty[0x4037] = 'A'; + empty[0x4038] = 'T'; + empty[0x4039] = '1'; + empty[0x403A] = '6'; + memset(&(empty[0x403B]), 0x20, 0x0003); + + empty[0x41FE] = 0x55; + empty[0x41FF] = 0xAA; + + empty[0x5000] = empty[0x1D000] = empty[0x4015]; + empty[0x5001] = empty[0x1D001] = 0xFF; + empty[0x5002] = empty[0x1D002] = 0xFF; + empty[0x5003] = empty[0x1D003] = 0xFF; + + /* Root directory = 0x35000 + Data = 0x39000 */ + } else { + /* LS-240 */ + /* MBR */ + *(uint64_t *) &(empty[0x0000]) = 0x2054524150492EEBLL; + *(uint64_t *) &(empty[0x0008]) = 0x3930302065646F63LL; + *(uint64_t *) &(empty[0x0010]) = 0x67656D6F49202D20LL; + *(uint64_t *) &(empty[0x0018]) = 0x726F70726F432061LL; + *(uint64_t *) &(empty[0x0020]) = 0x202D206E6F697461LL; + *(uint64_t *) &(empty[0x0028]) = 0x30392F33322F3131LL; + + *(uint64_t *) &(empty[0x01AE]) = 0x0116010100E900E9LL; + *(uint64_t *) &(empty[0x01B6]) = 0x2E32A7AC014E0135LL; + + *(uint64_t *) &(empty[0x01EE]) = 0xEE203F0600010180LL; + *(uint64_t *) &(empty[0x01F6]) = 0x000777E000000020LL; + *(uint16_t *) &(empty[0x01FE]) = 0xAA55; + + /* 31 sectors filled with 0x48 */ + memset(&(empty[0x0200]), 0x48, 0x3E00); + + /* The second sector begins with some strange data + in my reference image. */ + *(uint64_t *) &(empty[0x0200]) = 0x3831393230334409LL; + *(uint64_t *) &(empty[0x0208]) = 0x6A57766964483130LL; + *(uint64_t *) &(empty[0x0210]) = 0x3C3A34676063653FLL; + *(uint64_t *) &(empty[0x0218]) = 0x586A56A8502C4161LL; + *(uint64_t *) &(empty[0x0220]) = 0x6F2D702535673D6CLL; + *(uint64_t *) &(empty[0x0228]) = 0x255421B8602D3456LL; + *(uint64_t *) &(empty[0x0230]) = 0x577B22447B52603ELL; + *(uint64_t *) &(empty[0x0238]) = 0x46412CC871396170LL; + *(uint64_t *) &(empty[0x0240]) = 0x704F55237C5E2626LL; + *(uint64_t *) &(empty[0x0248]) = 0x6C7932C87D5C3C20LL; + *(uint64_t *) &(empty[0x0250]) = 0x2C50503E47543D6ELL; + *(uint64_t *) &(empty[0x0258]) = 0x46394E807721536ALL; + *(uint64_t *) &(empty[0x0260]) = 0x505823223F245325LL; + *(uint64_t *) &(empty[0x0268]) = 0x365C79B0393B5B6ELL; + + /* Boot sector */ + *(uint64_t *) &(empty[0x4000]) = 0x584F4236389058EBLL; + *(uint64_t *) &(empty[0x4008]) = 0x0001080200302E35LL; + *(uint64_t *) &(empty[0x4010]) = 0x00EFF80000020002LL; + *(uint64_t *) &(empty[0x4018]) = 0x0000002000400020LL; + *(uint32_t *) &(empty[0x4020]) = 0x000777E0; + *(uint16_t *) &(empty[0x4024]) = 0x0080; + + empty[0x4026] = 0x29; /* ')' followed by randomly-generated volume serial number. */ + empty[0x4027] = random_generate(); + empty[0x4028] = random_generate(); + empty[0x4029] = random_generate(); + empty[0x402A] = random_generate(); + + memset(&(empty[0x402B]), 0x00, 0x000B); + memset(&(empty[0x4036]), 0x20, 0x0008); + + empty[0x4036] = 'F'; + empty[0x4037] = 'A'; + empty[0x4038] = 'T'; + empty[0x4039] = '1'; + empty[0x403A] = '6'; + memset(&(empty[0x403B]), 0x20, 0x0003); + + empty[0x41FE] = 0x55; + empty[0x41FF] = 0xAA; + + empty[0x4200] = empty[0x22000] = empty[0x4015]; + empty[0x4201] = empty[0x22001] = 0xFF; + empty[0x4202] = empty[0x22002] = 0xFF; + empty[0x4203] = empty[0x22003] = 0xFF; + + /* Root directory = 0x3FE00 + Data = 0x38200 */ + } + + pbar.setMaximum(pbar_max); + for (uint32_t i = 0; i < pbar_max; i++) { + stream.writeRawData(&empty[i << 11], 2048); + fileProgress(i); + } + fileProgress(pbar_max); + + return true; +} diff --git a/src/qt/qt_newfloppydialog.hpp b/src/qt/qt_newfloppydialog.hpp index 7429fc186..597b9d028 100644 --- a/src/qt/qt_newfloppydialog.hpp +++ b/src/qt/qt_newfloppydialog.hpp @@ -18,11 +18,13 @@ public: Floppy, RDisk, Mo, + SuperDisk, }; enum class FileType { Img, Fdi, Zdi, + Sdi, Mdi, }; explicit NewFloppyDialog(MediaType type, QWidget *parent = nullptr); @@ -44,6 +46,7 @@ private: bool createSectorImage(const QString &filename, const disk_size_t &disk_size, FileType type); bool createRDiskSectorImage(const QString &filename, const disk_size_t &disk_size, FileType type, QProgressDialog &pbar); bool createMoSectorImage(const QString &filename, int8_t disk_size, FileType type, QProgressDialog &pbar); + bool createSuperDiskSectorImage(const QString &filename, const disk_size_t &disk_size, FileType type, QProgressDialog &pbar); }; #endif // QT_NEWFLOPPYDIALOG_HPP diff --git a/src/qt/qt_settingsotherremovable.cpp b/src/qt/qt_settingsotherremovable.cpp index 567eaa3e2..e2889d55b 100644 --- a/src/qt/qt_settingsotherremovable.cpp +++ b/src/qt/qt_settingsotherremovable.cpp @@ -23,6 +23,7 @@ extern "C" { #include <86box/scsi_device.h> #include <86box/mo.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> } #include "qt_models_common.hpp" diff --git a/src/qt/qt_settingsotherremovable.hpp b/src/qt/qt_settingsotherremovable.hpp index 794939589..d02034dc4 100644 --- a/src/qt/qt_settingsotherremovable.hpp +++ b/src/qt/qt_settingsotherremovable.hpp @@ -35,6 +35,9 @@ private slots: void on_comboBoxRDiskBus_activated(int index); void on_comboBoxRDiskChannel_activated(int index); void on_comboBoxRDiskType_activated(int index); +#if 0 + void onSuperDiskRowChanged(const QModelIndex ¤t); +#endif private: Ui::SettingsOtherRemovable *ui; diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp index 59204de56..d7607e6b3 100644 --- a/src/qt/qt_ui.cpp +++ b/src/qt/qt_ui.cpp @@ -49,6 +49,7 @@ extern "C" { #include <86box/cassette.h> #include <86box/cdrom.h> #include <86box/rdisk.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/hdd.h> #include <86box/thread.h> @@ -307,6 +308,9 @@ ui_sb_update_icon_state(int tag, int state) case SB_RDISK: machine_status.rdisk[item].empty = state > 0 ? true : false; break; + case SB_SUPERDISK: + machine_status.superdisk[item].empty = state > 0 ? true : false; + break; case SB_MO: machine_status.mo[item].empty = state > 0 ? true : false; break; @@ -345,6 +349,9 @@ ui_sb_update_icon(int tag, int active) case SB_RDISK: machine_status.rdisk[item].active = active > 0 ? true : false; break; + case SB_SUPERDISK: + machine_status.superdisk[item].active = active > 0 ? true : false; + break; case SB_MO: machine_status.mo[item].active = active > 0 ? true : false; break; diff --git a/src/qt_resources.qrc b/src/qt_resources.qrc index 4b069cb3f..9f7ea92d3 100644 --- a/src/qt_resources.qrc +++ b/src/qt_resources.qrc @@ -35,6 +35,11 @@ qt/icons/record.ico qt/icons/rewind.ico qt/icons/sound.ico + qt/icons/superdisk.ico + qt/icons/superdisk_active.ico + qt/icons/superdisk_disabled.ico + qt/icons/superdisk_empty.ico + qt/icons/superdisk_empty_active.ico qt/icons/storage_controllers.ico qt/icons/superdisk.ico qt/icons/superdisk_image.ico diff --git a/src/unix/unix_cdrom.c b/src/unix/unix_cdrom.c index 35ce92a9f..a95e5d698 100644 --- a/src/unix/unix_cdrom.c +++ b/src/unix/unix_cdrom.c @@ -6,7 +6,7 @@ * * This file is part of the 86Box distribution. * - * Handle the platform-side of CDROM/RDisk/MO drives. + * Handle the platform-side of CDROM/RDisk/MO/SuperDisk drives. * * Authors: Miran Grca, * Fred N. van Kempen, @@ -30,6 +30,7 @@ #include <86box/scsi_device.h> #include <86box/cdrom.h> #include <86box/cdrom_image.h> +#include <86box/superdisk.h> #include <86box/mo.h> #include <86box/rdisk.h> #include <86box/scsi_disk.h> @@ -142,6 +143,63 @@ cdrom_mount(uint8_t id, char *fn) config_save(); } +void +superdisk_eject(uint8_t id) +{ + superdisk_t *dev = (superdisk_t *) superdisk_drives[id].priv; + + superdisk_disk_close(dev); + if (superdisk_drives[id].bus_type) { + /* Signal disk change to the emulated machine. */ + superdisk_insert(dev); + } + + ui_sb_update_icon_state(SB_SUPERDISK | id, 1); +#if 0 + media_menu_update_superdisk(id); +#endif + ui_sb_update_tip(SB_SUPERDISK | id); + config_save(); +} + +void +superdisk_mount(uint8_t id, char *fn, uint8_t wp) +{ + superdisk_t *dev = (superdisk_t *) superdisk_drives[id].priv; + + superdisk_disk_close(dev); + superdisk_drives[id].read_only = wp; + superdisk_load(dev, fn, 0); + + ui_sb_update_icon_state(SB_SUPERDISK | id, strlen(superdisk_drives[id].image_path) ? 0 : 1); +#if 0 + media_menu_update_superdisk(id); +#endif + ui_sb_update_tip(SB_SUPERDISK | id); + + config_save(); +} + +void +superdisk_reload(uint8_t id) +{ + superdisk_t *dev = (superdisk_t *) superdisk_drives[id].priv; + + superdisk_disk_reload(dev); + if (strlen(superdisk_drives[id].image_path) == 0) { + ui_sb_update_icon_state(SB_SUPERDISK | id, 1); + } else { + ui_sb_update_icon_state(SB_SUPERDISK | id, 0); + } + +#if 0 + media_menu_update_superdisk(id); +#endif + ui_sb_update_tip(SB_SUPERDISK | id); + + config_save(); +} + void mo_eject(uint8_t id) {