diff --git a/src/cdrom/cdrom.c b/src/cdrom/cdrom.c index 00262bcf5..2b68ff09f 100644 --- a/src/cdrom/cdrom.c +++ b/src/cdrom/cdrom.c @@ -8,7 +8,7 @@ * * Generic CD-ROM drive core. * - * Version: @(#)cdrom.c 1.0.9 2019/12/13 + * Version: @(#)cdrom.c 1.0.10 2020/03/23 * * Author: Miran Grca, * @@ -41,6 +41,8 @@ #define MIN_SEEK 2000 #define MAX_SEEK 333333 +#define CD_BCD(x) (((x) % 10) | (((x) / 10) << 4)) +#define CD_DCB(x) ((((x) & 0xf0) >> 4) * 10 + ((x) & 0x0f)) #pragma pack(push,1) typedef struct { @@ -345,6 +347,72 @@ cdrom_audio_play(cdrom_t *dev, uint32_t pos, uint32_t len, int ismsf) return 1; } +uint8_t +cdrom_audio_track_search(cdrom_t *dev, uint32_t pos, int type, uint8_t playbit) +{ + int m = 0, s = 0, f = 0; + + if (dev->cd_status == CD_STATUS_DATA_ONLY) + return 0; + + switch (type) { + case 0x40: + cdrom_log("Audio Track Search: MSF = %06x, type = %02x\n", pos, type); + m = CD_DCB((pos >> 24) & 0xff); + s = CD_DCB((pos >> 16) & 0xff); + f = CD_DCB((pos >> 8) & 0xff); + pos = MSFtoLBA(m, s, f) - 150; + break; + } + + /* Do this at this point, since it's at this point that we know the + actual LBA position to start playing from. */ + if (!(dev->ops->track_type(dev, pos) & CD_TRACK_AUDIO)) { + cdrom_log("CD-ROM %i: LBA %08X not on an audio track\n", dev->id, pos); + cdrom_stop(dev); + return 0; + } + + dev->seek_pos = pos; + dev->noplay = !playbit; + dev->cd_status = playbit ? CD_STATUS_PLAYING : CD_STATUS_PAUSED; + return 1; +} + +uint8_t +cdrom_toshiba_audio_play(cdrom_t *dev, uint32_t pos, int type) +{ + int m = 0, s = 0, f = 0; + + if (dev->cd_status == CD_STATUS_DATA_ONLY) + return 0; + + if (dev->cd_status == CD_STATUS_STOPPED || dev->cd_status == CD_STATUS_PAUSED) + dev->cd_status = CD_STATUS_PLAYING; + + /*Preliminary support, revert if too incomplete*/ + switch (type) { + case 0x40: + cdrom_log("Toshiba Play Audio: MSF = %06x, type = %02x\n", pos, type); + m = CD_DCB((pos >> 24) & 0xff); + s = CD_DCB((pos >> 16) & 0xff); + f = CD_DCB((pos >> 8) & 0xff); + pos = MSFtoLBA(m, s, f) - 150; + break; + } + + /* Do this at this point, since it's at this point that we know the + actual LBA position to start playing from. */ + if (!(dev->ops->track_type(dev, pos) & CD_TRACK_AUDIO)) { + cdrom_log("CD-ROM %i: LBA %08X not on an audio track\n", dev->id, pos); + cdrom_stop(dev); + return 0; + } + + dev->cd_end = pos; + dev->cd_buflen = 0; + return 1; +} void cdrom_audio_pause_resume(cdrom_t *dev, uint8_t resume) @@ -409,6 +477,38 @@ cdrom_get_current_subchannel(cdrom_t *dev, uint8_t *b, int msf) return ret; } +uint8_t +cdrom_get_current_subcodeq_playstatus(cdrom_t *dev, uint8_t *b) +{ + uint8_t ret; + subchannel_t subc; + + dev->ops->get_subchannel(dev, dev->seek_pos, &subc); + + if (dev->cd_status == CD_STATUS_PLAYING) + ret = 0x00; + else if (dev->cd_status == CD_STATUS_PAUSED) { + if (dev->noplay) + ret = 0x02; + else + ret = 0x01; + } + else + ret = 0x03; + + b[0] = subc.attr; + b[1] = CD_BCD(subc.track); + b[2] = CD_BCD(subc.index); + b[3] = CD_BCD(subc.rel_m); + b[4] = CD_BCD(subc.rel_s); + b[5] = CD_BCD(subc.rel_f); + b[6] = CD_BCD(subc.abs_m); + b[7] = CD_BCD(subc.abs_s); + b[8] = CD_BCD(subc.abs_f); + cdrom_log("CD-ROM %i: Returned subcode-q at %02i:%02i.%02i, track=%02x\n", dev->id, b[3], b[4], b[5], b[1]); + + return ret; +} static int read_toc_normal(cdrom_t *dev, unsigned char *b, unsigned char start_track, int msf) @@ -557,6 +657,44 @@ cdrom_read_toc(cdrom_t *dev, unsigned char *b, int type, unsigned char start_tra return len; } +void +cdrom_read_disc_info_toc(cdrom_t *dev, unsigned char *b, unsigned char track, int type) +{ + track_info_t ti; + int first_track, last_track; + + dev->ops->get_tracks(dev, &first_track, &last_track); + + switch (type) { + case 0: + b[0] = CD_BCD(first_track); + b[1] = CD_BCD(last_track); + b[2] = 0; + b[3] = 0; + break; + case 1: + dev->ops->get_track_info(dev, 0xAA, 0, &ti); + b[0] = CD_BCD(ti.m); + b[1] = CD_BCD(ti.s); + b[2] = CD_BCD(ti.f); + b[3] = 0; + break; + case 2: + dev->ops->get_track_info(dev, CD_DCB(track), 0, &ti); + b[0] = CD_BCD(ti.m); + b[1] = CD_BCD(ti.s); + b[2] = CD_BCD(ti.f); + b[3] = ti.attr; + cdrom_log("CD-ROM %i: Returned Toshiba disc information at %02i:%02i.%02i, track=%d\n", dev->id, b[0], b[1], b[2], CD_DCB(track)); + break; + case 3: + b[0] = 0x00; /*TODO: correct it further, mark it as CD-Audio/CD-ROM disc for now*/ + b[1] = 0; + b[2] = 0; + b[3] = 0; + break; + } +} static int track_type_is_valid(uint8_t id, int type, int flags, int audio, int mode2) diff --git a/src/cdrom/cdrom.h b/src/cdrom/cdrom.h index ec141a5f9..e5b3a948f 100644 --- a/src/cdrom/cdrom.h +++ b/src/cdrom/cdrom.h @@ -112,7 +112,7 @@ typedef struct cdrom { seek_diff, cd_end; int host_drive, prev_host_drive, - cd_buflen; + cd_buflen, noplay; const cdrom_ops_t *ops; @@ -134,12 +134,17 @@ extern double cdrom_seek_time(cdrom_t *dev); extern void cdrom_stop(cdrom_t *dev); extern int cdrom_audio_callback(cdrom_t *dev, int16_t *output, int len); extern uint8_t cdrom_audio_play(cdrom_t *dev, uint32_t pos, uint32_t len, int ismsf); +extern uint8_t cdrom_audio_track_search(cdrom_t *dev, uint32_t pos, int type, uint8_t playbit); +extern uint8_t cdrom_toshiba_audio_play(cdrom_t *dev, uint32_t pos, int type); extern void cdrom_audio_pause_resume(cdrom_t *dev, uint8_t resume); extern uint8_t cdrom_get_current_subchannel(cdrom_t *dev, uint8_t *b, int msf); +extern uint8_t cdrom_get_current_subcodeq_playstatus(cdrom_t *dev, uint8_t *b); extern int cdrom_read_toc(cdrom_t *dev, unsigned char *b, int type, unsigned char start_track, int msf, int max_len); extern int cdrom_readsector_raw(cdrom_t *dev, uint8_t *buffer, int sector, int ismsf, int cdrom_sector_type, int cdrom_sector_flags, int *len); +extern void cdrom_read_disc_info_toc(cdrom_t *dev, unsigned char *b, unsigned char track, int type); + extern void cdrom_seek(cdrom_t *dev, uint32_t pos); extern void cdrom_close_handler(uint8_t id); diff --git a/src/scsi/scsi.c b/src/scsi/scsi.c index fb1bdec79..a5a509494 100644 --- a/src/scsi/scsi.c +++ b/src/scsi/scsi.c @@ -38,6 +38,7 @@ #include "scsi_buslogic.h" #include "scsi_ncr5380.h" #include "scsi_ncr53c8xx.h" +#include "scsi_spock.h" #ifdef WALTJE # include "scsi_wd33c93.h" #endif @@ -60,7 +61,7 @@ static SCSI_CARD scsi_cards[] = { { "[ISA] Adaptec AHA-154xB", "aha154xb", &aha154xb_device, }, { "[ISA] Adaptec AHA-154xC", "aha154xc", &aha154xc_device, }, { "[ISA] Adaptec AHA-154xCF", "aha154xcf", &aha154xcf_device, }, - { "[ISA] BusLogic BT-542B", "bt542b", &buslogic_542b_1991_device, }, + { "[ISA] BusLogic BT-542B", "bt542b", &buslogic_542b_1991_device, }, { "[ISA] BusLogic BT-542BH", "bt542bh", &buslogic_device, }, { "[ISA] BusLogic BT-545S", "bt545s", &buslogic_545s_device, }, { "[ISA] Longshine LCS-6821N", "lcs6821n", &scsi_lcs6821n_device, }, @@ -72,6 +73,7 @@ static SCSI_CARD scsi_cards[] = { #endif { "[MCA] Adaptec AHA-1640", "aha1640", &aha1640_device, }, { "[MCA] BusLogic BT-640A", "bt640a", &buslogic_640a_device, }, + { "[MCA] IBM PS/2 SCSI", "spock", &spock_device, }, { "[PCI] BusLogic BT-958D", "bt958d", &buslogic_pci_device, }, { "[PCI] NCR 53C810", "ncr53c810", &ncr53c810_pci_device, }, { "[PCI] NCR 53C825A", "ncr53c825a", &ncr53c825a_pci_device, }, diff --git a/src/scsi/scsi_cdrom.c b/src/scsi/scsi_cdrom.c index 1c9c1cab4..6527340f9 100644 --- a/src/scsi/scsi_cdrom.c +++ b/src/scsi/scsi_cdrom.c @@ -9,7 +9,7 @@ * Implementation of the CD-ROM drive with SCSI(-like) * commands, for both ATAPI and SCSI usage. * - * Version: @(#)scsi_cdrom.c 1.0.74 2020/01/17 + * Version: @(#)scsi_cdrom.c 1.0.75 2020/03/23 * * Author: Miran Grca, * @@ -139,9 +139,15 @@ const uint8_t scsi_cdrom_command_flags[0x100] = IMPLEMENTED, /* 0xBD */ IMPLEMENTED | CHECK_READY, /* 0xBE */ IMPLEMENTED | CHECK_READY, /* 0xBF */ - 0, 0, /* 0xC0-0xC1 */ + IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC0 */ + IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC1 */ IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC2 */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC3-0xCC */ + 0, /* 0xC3 */ + IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC4 */ + 0, /* 0xC5 */ + IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC6 */ + IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xC7 */ + 0, 0, 0, 0, 0, /* 0xC8-0xCC */ IMPLEMENTED | CHECK_READY | SCSI_ONLY, /* 0xCD */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xCE-0xD9 */ IMPLEMENTED | SCSI_ONLY, /* 0xDA */ @@ -151,6 +157,7 @@ const uint8_t scsi_cdrom_command_flags[0x100] = }; static uint64_t scsi_cdrom_mode_sense_page_flags = (GPMODEP_R_W_ERROR_PAGE | + GPMODEP_DISCONNECT_PAGE | GPMODEP_CDROM_PAGE | GPMODEP_CDROM_AUDIO_PAGE | GPMODEP_CAPABILITIES_PAGE | @@ -207,7 +214,7 @@ static const mode_sense_pages_t scsi_cdrom_mode_sense_pages_default_scsi = { { { 0, 0 }, { GPMODE_R_W_ERROR_PAGE, 6, 0, 5, 0, 0, 0, 0 }, - { 0, 0 }, + { GPMODE_DISCONNECT_PAGE, 0x0e, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, @@ -254,7 +261,7 @@ static const mode_sense_pages_t scsi_cdrom_mode_sense_pages_changeable = { { { 0, 0 }, { GPMODE_R_W_ERROR_PAGE, 6, 0xFF, 0xFF, 0, 0, 0, 0 }, - { 0, 0 }, + { GPMODE_DISCONNECT_PAGE, 0x0e, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, @@ -672,6 +679,8 @@ scsi_cdrom_command_common(scsi_cdrom_t *dev) case 0xb8: case 0xb9: case 0xbe: + case 0xc6: + case 0xc7: if (dev->current_cdb[0] == 0x42) dev->callback += 40.0; /* Account for seek time. */ @@ -1540,6 +1549,26 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) toc_format, ide->cylinder, dev->buffer[1]); */ return; + case GPCMD_READ_DISC_INFORMATION_TOSHIBA: + scsi_cdrom_set_phase(dev, SCSI_PHASE_DATA_IN); + + scsi_cdrom_buf_alloc(dev, 65536); + + if ((!dev->drv->ops) && ((cdb[1] & 3) == 2)) { + scsi_cdrom_not_ready(dev); + return; + } + + memset(dev->buffer, 0, 4); + + cdrom_read_disc_info_toc(dev->drv, dev->buffer, cdb[2], cdb[1] & 3); + + len = 4; + scsi_cdrom_set_buf_len(dev, BufLen, &len); + + scsi_cdrom_data_command_finish(dev, len, len, len, 0); + return; + case GPCMD_READ_CD_OLD: /* IMPORTANT: Convert the command to new read CD for pass through purposes. */ @@ -1980,6 +2009,36 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) scsi_cdrom_data_command_finish(dev, len, len, max_len, 0); break; + case GPCMD_AUDIO_TRACK_SEARCH: + scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); + if ((dev->drv->host_drive < 1) || (dev->drv->cd_status <= CD_STATUS_DATA_ONLY)) { + scsi_cdrom_illegal_mode(dev); + break; + } + pos = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + ret = cdrom_audio_track_search(dev->drv, pos, cdb[9], cdb[1] & 1); + + if (ret) + scsi_cdrom_command_complete(dev); + else + scsi_cdrom_illegal_mode(dev); + break; + + case GPCMD_TOSHIBA_PLAY_AUDIO: + scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); + if ((dev->drv->host_drive < 1) || (dev->drv->cd_status <= CD_STATUS_DATA_ONLY)) { + scsi_cdrom_illegal_mode(dev); + break; + } + pos = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + ret = cdrom_toshiba_audio_play(dev->drv, pos, cdb[9]); + + if (ret) + scsi_cdrom_command_complete(dev); + else + scsi_cdrom_illegal_mode(dev); + break; + case GPCMD_PLAY_AUDIO_10: case GPCMD_PLAY_AUDIO_12: case GPCMD_PLAY_AUDIO_MSF: @@ -1997,13 +2056,13 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) len = (cdb[7] << 8) | cdb[8]; break; case GPCMD_PLAY_AUDIO_12: + /* This is apparently deprecated in the ATAPI spec, and apparently + has been since 1995 (!). Hence I'm having to guess most of it. */ msf = 0; pos = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; len = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; break; case GPCMD_PLAY_AUDIO_MSF: - /* This is apparently deprecated in the ATAPI spec, and apparently - has been since 1995 (!). Hence I'm having to guess most of it. */ msf = 1; pos = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; len = (cdb[6] << 16) | (cdb[7] << 8) | cdb[8]; @@ -2118,6 +2177,37 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) scsi_cdrom_data_command_finish(dev, len, len, len, 0); break; + case GPCMD_READ_SUBCODEQ_PLAYING_STATUS: + scsi_cdrom_set_phase(dev, SCSI_PHASE_DATA_IN); + + alloc_length = cdb[1] & 0x1f; + + scsi_cdrom_buf_alloc(dev, alloc_length); + + if (!dev->drv->ops) { + scsi_cdrom_not_ready(dev); + return; + } + + if (!alloc_length) { + scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); + scsi_cdrom_log("CD-ROM %i: All done - callback set\n", dev->id); + dev->packet_status = PHASE_COMPLETE; + dev->callback = 20.0 * CDROM_TIME; + scsi_cdrom_set_callback(dev); + break; + } + + len = alloc_length; + + memset(dev->buffer, 0, len); + dev->buffer[0] = cdrom_get_current_subcodeq_playstatus(dev->drv, &dev->buffer[1]); + scsi_cdrom_log("Audio Status = %02x\n", dev->buffer[0]); + + scsi_cdrom_set_buf_len(dev, BufLen, &alloc_length); + scsi_cdrom_data_command_finish(dev, len, len, len, 0); + break; + case GPCMD_READ_DVD_STRUCTURE: scsi_cdrom_set_phase(dev, SCSI_PHASE_DATA_IN); @@ -2176,6 +2266,13 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) scsi_cdrom_command_complete(dev); break; + + case GPCMD_CADDY_EJECT: + scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); + scsi_cdrom_stop(sc); + cdrom_eject(dev->id); + scsi_cdrom_command_complete(dev); + break; case GPCMD_INQUIRY: scsi_cdrom_set_phase(dev, SCSI_PHASE_DATA_IN); @@ -2221,10 +2318,15 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) dev->buffer[idx++] = 0x01; dev->buffer[idx++] = 0x00; dev->buffer[idx++] = 68; - ide_padstr8(dev->buffer + idx, 8, EMU_NAME); /* Vendor */ + if (dev->drv->bus_type == CDROM_BUS_SCSI) + ide_padstr8(dev->buffer + idx, 8, "TOSHIBA"); /* Vendor */ + else + ide_padstr8(dev->buffer + idx, 8, EMU_NAME); /* Vendor */ idx += 8; - ide_padstr8(dev->buffer + idx, 40, device_identify_ex); /* Product */ - + if (dev->drv->bus_type == CDROM_BUS_SCSI) + ide_padstr8(dev->buffer + idx, 40, "XM6201TASUN32XCD1103"); /* Product */ + else + ide_padstr8(dev->buffer + idx, 40, device_identify_ex); /* Product */ idx += 40; ide_padstr8(dev->buffer + idx, 20, "53R141"); /* Product */ idx += 20; @@ -2257,10 +2359,16 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) dev->buffer[6] = 1; /* 16-bit transfers supported */ dev->buffer[7] = 0x20; /* Wide bus supported */ } - - ide_padstr8(dev->buffer + 8, 8, EMU_NAME); /* Vendor */ - ide_padstr8(dev->buffer + 16, 16, device_identify); /* Product */ - ide_padstr8(dev->buffer + 32, 4, EMU_VERSION); /* Revision */ + + if (dev->drv->bus_type == CDROM_BUS_SCSI) { + ide_padstr8(dev->buffer + 8, 8, "TOSHIBA"); /* Vendor */ + ide_padstr8(dev->buffer + 16, 16, "XM6201TASUN32XCD"); /* Product */ + ide_padstr8(dev->buffer + 32, 4, "1103"); /* Revision */ + } else { + ide_padstr8(dev->buffer + 8, 8, EMU_NAME); /* Vendor */ + ide_padstr8(dev->buffer + 16, 16, device_identify); /* Product */ + ide_padstr8(dev->buffer + 32, 4, EMU_VERSION); /* Revision */ + } idx = 36; @@ -2285,13 +2393,21 @@ atapi_out: scsi_cdrom_command_complete(dev); break; +#if 0 case GPCMD_PAUSE_RESUME_ALT: +#endif case GPCMD_PAUSE_RESUME: scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); cdrom_audio_pause_resume(dev->drv, cdb[8] & 0x01); scsi_cdrom_command_complete(dev); break; + case GPCMD_STILL: + scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); + dev->drv->cd_status = CD_STATUS_PAUSED; + scsi_cdrom_command_complete(dev); + break; + case GPCMD_SEEK_6: case GPCMD_SEEK_10: scsi_cdrom_set_phase(dev, SCSI_PHASE_STATUS); diff --git a/src/scsi/scsi_device.h b/src/scsi/scsi_device.h index 5527dae0e..181e4ea7e 100644 --- a/src/scsi/scsi_device.h +++ b/src/scsi/scsi_device.h @@ -8,7 +8,7 @@ * * Definitions for the generic SCSI device command handler. * - * Version: @(#)scsi_device.h 1.0.17 2019/09/26 + * Version: @(#)scsi_device.h 1.0.18 2020/03/23 * * Authors: Miran Grca, * Fred N. van Kempen, @@ -102,8 +102,14 @@ #define GPCMD_PLAY_CD 0xbc #define GPCMD_MECHANISM_STATUS 0xbd #define GPCMD_READ_CD 0xbe -#define GPCMD_SEND_DVD_STRUCTURE 0xbf /* This is for writing only, irrelevant to PCem. */ +#define GPCMD_SEND_DVD_STRUCTURE 0xbf /* This is for writing only, irrelevant to 86Box. */ +#define GPCMD_AUDIO_TRACK_SEARCH 0xc0 /* Toshiba Vendor Unique command */ +#define GPCMD_TOSHIBA_PLAY_AUDIO 0xc1 /* Toshiba Vendor Unique command */ #define GPCMD_PAUSE_RESUME_ALT 0xc2 +#define GPCMD_STILL 0xc2 /* Toshiba Vendor Unique command */ +#define GPCMD_CADDY_EJECT 0xc4 /* Toshiba Vendor Unique command */ +#define GPCMD_READ_SUBCODEQ_PLAYING_STATUS 0xc6 /* Toshiba Vendor Unique command */ +#define GPCMD_READ_DISC_INFORMATION_TOSHIBA 0xc7 /* Toshiba Vendor Unique command */ #define GPCMD_SCAN_ALT 0xcd /* Should be equivalent to 0xba */ #define GPCMD_SET_SPEED_ALT 0xda /* Should be equivalent to 0xbb */ diff --git a/src/scsi/scsi_spock.c b/src/scsi/scsi_spock.c new file mode 100644 index 000000000..d1e9a179e --- /dev/null +++ b/src/scsi/scsi_spock.c @@ -0,0 +1,1159 @@ +/* + * 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 IBM PS/2 SCSI controller with + * cache for MCA only. + * + * Version: @(#)scsi_spock.c 1.0.1 2020/03/23 + * + * Authors: Sarah Walker, + * TheCollector1995, + * + * Copyright 2020 Sarah Walker. + * Copyright 2020 TheCollector1995. + */ +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include "../86box.h" +#include "../io.h" +#include "../timer.h" +#include "../dma.h" +#include "../pic.h" +#include "../mca.h" +#include "../mem.h" +#include "../rom.h" +#include "../device.h" +#include "../nvr.h" +#include "../plat.h" +#include "scsi.h" +#include "scsi_device.h" +#include "scsi_spock.h" + +#define SPOCK_U68_1990_ROM L"roms/scsi/ibm/64f4376.bin" +#define SPOCK_U69_1990_ROM L"roms/scsi/ibm/64f4377.bin" + +#define SPOCK_U68_1991_ROM L"roms/scsi/ibm/92F2244.U68" +#define SPOCK_U69_1991_ROM L"roms/scsi/ibm/92F2245.U69" + +#define SPOCK_TIME (20) + +typedef enum +{ + SCSI_STATE_IDLE, + SCSI_STATE_SELECT, + SCSI_STATE_SELECT_FAILED, + SCSI_STATE_SEND_COMMAND, + SCSI_STATE_END_PHASE +} scsi_state_t; + +#pragma pack(push,1) +typedef struct { + uint16_t pos; + uint16_t pos2; + uint16_t pos3; + uint16_t pos4; + uint16_t pos5; + uint16_t pos6; + uint16_t pos7; + uint16_t pos8; +} get_pos_info_t; + +typedef struct { + uint16_t scb_status; + uint16_t retry_count; + uint32_t residual_byte_count; + uint32_t sg_list_element_addr; + uint16_t device_dep_status_len; + uint16_t cmd_status; + uint16_t error; + uint16_t reserved; + uint16_t cache_info_status; + uint32_t scb_addr; +} get_complete_stat_t; + +typedef struct { + uint32_t sys_buf_addr; + uint32_t sys_buf_byte_count; +} SGE; + +typedef struct { + uint16_t command; + uint16_t enable; + uint32_t lba_addr; + SGE sge; + uint32_t term_status_block_addr; + uint32_t scb_chain_addr; + uint16_t block_count; + uint16_t block_length; +} scb_t; +#pragma pack(pop) + +typedef struct { + rom_t bios_rom; + + int bios_ver; + int irq, irq_inactive; + + uint8_t pos_regs[8]; + + uint8_t basic_ctrl; + uint32_t command; + + uint8_t attention, + attention_pending; + int attention_wait; + + uint8_t cir[4], + cir_pending[4]; + + uint8_t irq_status; + + uint32_t scb_addr; + + uint8_t status; + + get_complete_stat_t get_complete_stat; + get_pos_info_t get_pos_info; + + scb_t scb; + int scb_id; + + int cmd_status; + int cir_status; + + uint8_t pacing; + + uint8_t buf[0x600]; + + struct { + int phys_id; + int lun_id; + } dev_id[SCSI_ID_MAX]; + + uint8_t last_status; + uint8_t cdb[12]; + int cdb_len; + int cdb_id; + uint32_t data_ptr, data_len; + uint8_t temp_cdb[12]; + + int irq_requests[SCSI_ID_MAX]; + + pc_timer_t callback_timer; + + int cmd_timer; + + int scb_state; + int in_reset; + int in_invalid; + + uint64_t temp_period; + double media_period; + + scsi_state_t scsi_state; +} spock_t; + +#define CTRL_RESET (1 << 7) +#define CTRL_DMA_ENA (1 << 1) +#define CTRL_IRQ_ENA (1 << 0) + +#define STATUS_CMD_FULL (1 << 3) +#define STATUS_CMD_EMPTY (1 << 2) +#define STATUS_IRQ (1 << 1) +#define STATUS_BUSY (1 << 0) + +#define ENABLE_PT (1 << 12) + +#define CMD_MASK 0xff3f +#define CMD_ASSIGN 0x040e +#define CMD_DEVICE_INQUIRY 0x1c0b +#define CMD_DMA_PACING_CONTROL 0x040d +#define CMD_FEATURE_CONTROL 0x040c +#define CMD_GET_POS_INFO 0x1c0a +#define CMD_INVALID_412 0x0412 +#define CMD_GET_COMPLETE_STATUS 0x1c07 +#define CMD_FORMAT_UNIT 0x1c16 +#define CMD_READ_DATA 0x1c01 +#define CMD_READ_DEVICE_CAPACITY 0x1c09 +#define CMD_REQUEST_SENSE 0x1c08 +#define CMD_RESET 0x0400 +#define CMD_SEND_OTHER_SCSI 0x241f +#define CMD_UNKNOWN_1C10 0x1c10 +#define CMD_UNKNOWN_1C11 0x1c11 +#define CMD_WRITE_DATA 0x1c02 +#define CMD_VERIFY 0x1c03 + +#define IRQ_TYPE_NONE 0x0 +#define IRQ_TYPE_SCB_COMPLETE 0x1 +#define IRQ_TYPE_SCB_COMPLETE_RETRY 0x5 +#define IRQ_TYPE_ADAPTER_HW_FAILURE 0x7 +#define IRQ_TYPE_IMM_CMD_COMPLETE 0xa +#define IRQ_TYPE_COMMAND_FAIL 0xc +#define IRQ_TYPE_COMMAND_ERROR 0xe +#define IRQ_TYPE_SW_SEQ_ERROR 0xf +#define IRQ_TYPE_RESET_COMPLETE 0x10 + + +#ifdef ENABLE_SPOCK_LOG +int spock_do_log = ENABLE_SPOCK_LOG; + + +static void +spock_log(const char *fmt, ...) +{ + va_list ap; + + if (spock_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define spock_log(fmt, ...) +#endif + +static void +spock_rethink_irqs(spock_t *scsi) +{ + int irq_pending = 0; + int c; + + if (!scsi->irq_status) { + for (c = 0; c < SCSI_ID_MAX; c++) { + if (scsi->irq_requests[c] != IRQ_TYPE_NONE) { + /* Found IRQ */ + scsi->irq_status = c | (scsi->irq_requests[c] << 4); + spock_log("Found IRQ: status = %02x\n", scsi->irq_status); + scsi->status |= STATUS_IRQ; + irq_pending = 1; + break; + } + } + } else + irq_pending = 1; + + if (scsi->basic_ctrl & CTRL_IRQ_ENA) { + if (irq_pending) { + spock_log("IRQ issued\n"); + scsi->irq_inactive = 0; + picint(1 << scsi->irq); + } else { + /* No IRQs pending, clear IRQ state */ + spock_log("IRQ cleared\n"); + scsi->irq_status = 0; + scsi->irq_inactive = 1; + scsi->status &= ~STATUS_IRQ; + picintc(1 << scsi->irq); + } + } else { + spock_log("IRQ disabled\n"); + picintc(1 << scsi->irq); + } +} + +static __inline void +spock_set_irq(spock_t *scsi, int id, int type) +{ + spock_log("spock_set_irq id=%i type=%x %02x\n", id, type, scsi->irq_status); + scsi->irq_requests[id] = type; + if (!scsi->irq_status) /* Don't change IRQ status if one is currently being processed */ + spock_rethink_irqs(scsi); +} + +static __inline void +spock_clear_irq(spock_t *scsi, int id) +{ + spock_log("spock_clear_irq id=%i\n", id); + scsi->irq_requests[id] = IRQ_TYPE_NONE; + spock_rethink_irqs(scsi); +} + +static void +spock_add_to_period(spock_t *scsi, int TransferLength) +{ + scsi->temp_period += (uint64_t) TransferLength; +} + +static void +spock_write(uint16_t port, uint8_t val, void *p) +{ + spock_t *scsi = (spock_t *)p; + + spock_log("spock_write: port=%04x val=%02x %04x:%04x\n", port, val, CS, cpu_state.pc); + + switch (port & 7) { + case 0: case 1: case 2: case 3: /*Command Interface Register*/ + scsi->cir_pending[port & 3] = val; + if (port & 2) + scsi->cir_status |= 2; + else + scsi->cir_status |= 1; + break; + + case 4: /*Attention Register*/ + scsi->attention_pending = val; + scsi->attention_wait = 2; + scsi->status |= STATUS_BUSY; + break; + + case 5: /*Basic Control Register*/ + if ((scsi->basic_ctrl & CTRL_RESET) && !(val & CTRL_RESET)) { + spock_log("Spock: SCSI reset and busy\n"); + scsi->in_reset = 1; + scsi->cmd_timer = SPOCK_TIME * 2; + scsi->status |= STATUS_BUSY; + } + scsi->basic_ctrl = val; + spock_rethink_irqs(scsi); + break; + } +} + +static void +spock_writew(uint16_t port, uint16_t val, void *p) +{ + spock_t *scsi = (spock_t *)p; + + switch (port & 7) { + case 0: /*Command Interface Register*/ + scsi->cir_pending[0] = val & 0xff; + scsi->cir_pending[1] = val >> 8; + scsi->cir_status |= 1; + break; + case 2: /*Command Interface Register*/ + scsi->cir_pending[2] = val & 0xff; + scsi->cir_pending[3] = val >> 8; + scsi->cir_status |= 2; + break; + } + + spock_log("spock_writew: port=%04x val=%04x\n", port, val); +} + + +static uint8_t +spock_read(uint16_t port, void *p) +{ + spock_t *scsi = (spock_t *)p; + uint8_t temp = 0xff; + + switch (port & 7) { + case 0: case 1: case 2: case 3: /*Command Interface Register*/ + temp = scsi->cir_pending[port & 3]; + break; + + case 4: /*Attention Register*/ + temp = scsi->attention_pending; + break; + case 5: /*Basic Control Register*/ + temp = scsi->basic_ctrl; + break; + case 6: /*IRQ status*/ + temp = scsi->irq_status; + break; + case 7: /*Basic Status Register*/ + temp = scsi->status; + if (scsi->cir_status == 0) { + spock_log("Status Cmd Empty\n"); + temp |= STATUS_CMD_EMPTY; + } + if (scsi->cir_status == 3) { + spock_log("Status Cmd Full\n"); + temp |= STATUS_CMD_FULL; + } + break; + } + + spock_log("spock_read: port=%04x val=%02x %04x(%05x):%04x %02x\n", port, temp, CS, cs, cpu_state.pc, BH); + return temp; +} + +static uint16_t +spock_readw(uint16_t port, void *p) +{ + spock_t *scsi = (spock_t *)p; + uint16_t temp = 0xffff; + + switch (port & 7) { + case 0: /*Command Interface Register*/ + temp = scsi->cir_pending[0] | (scsi->cir_pending[1] << 8); + break; + case 2: /*Command Interface Register*/ + temp = scsi->cir_pending[2] | (scsi->cir_pending[3] << 8); + break; + } + + spock_log("spock_readw: port=%04x val=%04x\n", port, temp); + return temp; +} + +static void +spock_rd_sge(spock_t *scsi, uint32_t Address, SGE *SG) +{ + DMAPageRead(Address, (uint8_t *)SG, sizeof(SGE)); + spock_add_to_period(scsi, sizeof(SGE)); +} + +static int +spock_get_len(spock_t *scsi, scb_t *scb) +{ + uint32_t DataToTransfer = 0, i = 0; + + spock_log("Data Buffer write: length %d, pointer 0x%04X\n", + scsi->data_len, scsi->data_ptr); + + if (!scsi->data_len) + return(0); + + if (scb->enable & ENABLE_PT) { + for (i = 0; i < scsi->data_len; i += 8) { + spock_rd_sge(scsi, scsi->data_ptr + i, &scb->sge); + + DataToTransfer += scb->sge.sys_buf_byte_count; + } + return(DataToTransfer); + } else { + return(scsi->data_len); + } +} + +static void +spock_process_imm_cmd(spock_t *scsi) +{ + int i; + int adapter_id, phys_id, lun_id; + + switch (scsi->command & CMD_MASK) { + case CMD_ASSIGN: + adapter_id = (scsi->command >> 16) & 15; + phys_id = (scsi->command >> 20) & 7; + lun_id = (scsi->command >> 24) & 7; + + if (adapter_id == 15) { + if (phys_id == 7) /*Device 15 always adapter*/ + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + else /*Can not re-assign device 15 (always adapter)*/ + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_FAIL); + } else { + if (scsi->command & (1 << 23)) { + spock_log("Physical Device Number -1\n"); + scsi->dev_id[adapter_id].phys_id = -1; + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + } else if (phys_id == 7) { /*Can not assign adapter*/ + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_FAIL); + } else { + scsi->dev_id[adapter_id].phys_id = phys_id; + scsi->dev_id[adapter_id].lun_id = lun_id; + spock_log("Assign: adapter dev=%x scsi ID=%i LUN=%i\n", adapter_id, scsi->dev_id[adapter_id].phys_id, scsi->dev_id[adapter_id].lun_id); + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + } + } + break; + + case CMD_DMA_PACING_CONTROL: + scsi->pacing = scsi->cir[2]; + spock_log("Pacing control: %i\n", scsi->pacing); + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + break; + + case CMD_FEATURE_CONTROL: + spock_log("Feature control: timeout=%is d-rate=%i\n", (scsi->command >> 16) & 0x1fff, scsi->command >> 29); + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + break; + + case CMD_INVALID_412: + spock_log("Invalid 412\n"); + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + break; + + case CMD_RESET: + spock_log("Reset Command\n"); + if ((scsi->attention & 15) == 15) { /*Adapter reset*/ + for (i = 0; i < 7; i++) + scsi_device_reset(&scsi_devices[i]); + spock_log("Adapter Reset\n"); + + scsi->scb_state = 0; + } + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE); + break; + + default: + fatal("scsi_callback: Bad command %02x\n", scsi->command); + break; + } +} + +static void +spock_execute_cmd(spock_t *scsi, scb_t *scb) +{ + int c; + int old_scb_state; + + if (scsi->in_reset) { + spock_log("Reset type = %d\n", scsi->in_reset); + + scsi->status &= ~STATUS_BUSY; + scsi->irq_status = 0; + + for (c = 0; c < SCSI_ID_MAX; c++) + spock_clear_irq(scsi, c); + + if (scsi->in_reset == 1) { + scsi->basic_ctrl |= CTRL_IRQ_ENA; + spock_set_irq(scsi, 0x0f, IRQ_TYPE_RESET_COMPLETE); + } else + spock_set_irq(scsi, 0x0f, IRQ_TYPE_RESET_COMPLETE); + + /*Reset device mappings*/ + for (c = 0; c < 7; c++) { + scsi->dev_id[c].phys_id = c; + scsi->dev_id[c].lun_id = 0; + } + for (; c < (SCSI_ID_MAX-1); c++) + scsi->dev_id[c].phys_id = -1; + + scsi->in_reset = 0; + return; + } + + if (scsi->in_invalid) { + spock_log("Invalid command\n"); + spock_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_ERROR); + scsi->in_invalid = 0; + return; + } + + do + { + old_scb_state = scsi->scb_state; + + switch (scsi->scb_state) { + case 0: /* Idle */ + break; + + case 1: /* Select */ + if (scsi->dev_id[scsi->scb_id].phys_id == -1) { + uint16_t term_stat_block_addr7 = (0xe << 8) | 0; + uint16_t term_stat_block_addr8 = (0xa << 8) | 0; + + spock_log("Start failed, SCB ID = %d\n", scsi->scb_id); + spock_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL); + scsi->scb_state = 0; + DMAPageWrite(scb->term_status_block_addr + 0x7*2, (uint8_t *)&term_stat_block_addr7, 2); + DMAPageWrite(scb->term_status_block_addr + 0x8*2, (uint8_t *)&term_stat_block_addr8, 2); + break; + } + + DMAPageRead(scsi->scb_addr, (uint8_t *)&scb->command, 2); + DMAPageRead(scsi->scb_addr + 2, (uint8_t *)&scb->enable, 2); + DMAPageRead(scsi->scb_addr + 4, (uint8_t *)&scb->lba_addr, 4); + DMAPageRead(scsi->scb_addr + 8, (uint8_t *)&scb->sge.sys_buf_addr, 4); + DMAPageRead(scsi->scb_addr + 12, (uint8_t *)&scb->sge.sys_buf_byte_count, 4); + DMAPageRead(scsi->scb_addr + 16, (uint8_t *)&scb->term_status_block_addr, 4); + DMAPageRead(scsi->scb_addr + 20, (uint8_t *)&scb->scb_chain_addr, 4); + DMAPageRead(scsi->scb_addr + 24, (uint8_t *)&scb->block_count, 2); + DMAPageRead(scsi->scb_addr + 26, (uint8_t *)&scb->block_length, 2); + + spock_log("SCB : \n" + " Command = %04x\n" + " Enable = %04x\n" + " LBA addr = %08x\n" + " System buffer addr = %08x\n" + " System buffer byte count = %08x\n" + " Terminate status block addr = %08x\n" + " SCB chain address = %08x\n" + " Block count = %04x\n" + " Block length = %04x\n", + scb->command, scb->enable, scb->lba_addr, + scb->sge.sys_buf_addr, scb->sge.sys_buf_byte_count, + scb->term_status_block_addr, scb->scb_chain_addr, + scb->block_count, scb->block_length); + + if (scb->command == 0x245f) { /*Issued by NT October 1991 and December 1991 (Undocumented version of Send Other SCSI?)*/ + spock_log("Send Other SCSI (BIOS?)\n"); + scb->command = CMD_SEND_OTHER_SCSI; + } + + switch (scb->command & CMD_MASK) { + case CMD_GET_COMPLETE_STATUS: + { + spock_log("Get Complete Status\n"); + get_complete_stat_t *get_complete_stat = &scsi->get_complete_stat; + + get_complete_stat->scb_status = 0x201; + get_complete_stat->retry_count = 0; + get_complete_stat->residual_byte_count = 0; + get_complete_stat->sg_list_element_addr = 0; + get_complete_stat->device_dep_status_len = 0x0c; + get_complete_stat->cmd_status = scsi->cmd_status << 8; + get_complete_stat->error = 0; + get_complete_stat->reserved = 0; + get_complete_stat->cache_info_status = 0; + get_complete_stat->scb_addr = scsi->scb_addr; + + DMAPageWrite(scb->sge.sys_buf_addr, (uint8_t *)&get_complete_stat->scb_status, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 2, (uint8_t *)&get_complete_stat->retry_count, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 4, (uint8_t *)&get_complete_stat->residual_byte_count, 4); + DMAPageWrite(scb->sge.sys_buf_addr + 8, (uint8_t *)&get_complete_stat->sg_list_element_addr, 4); + DMAPageWrite(scb->sge.sys_buf_addr + 12, (uint8_t *)&get_complete_stat->device_dep_status_len, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 14, (uint8_t *)&get_complete_stat->cmd_status, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 16, (uint8_t *)&get_complete_stat->error, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 18, (uint8_t *)&get_complete_stat->reserved, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 20, (uint8_t *)&get_complete_stat->cache_info_status, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 22, (uint8_t *)&get_complete_stat->scb_addr, 4); + scsi->scb_state = 3; + } + break; + + case CMD_UNKNOWN_1C10: + spock_log("Unknown 1C10\n"); + DMAPageRead(scb->sge.sys_buf_addr, scsi->buf, scb->sge.sys_buf_byte_count); + scsi->scb_state = 3; + break; + + case CMD_UNKNOWN_1C11: + spock_log("Unknown 1C11\n"); + DMAPageWrite(scb->sge.sys_buf_addr, scsi->buf, scb->sge.sys_buf_byte_count); + scsi->scb_state = 3; + break; + + case CMD_GET_POS_INFO: + { + spock_log("Get POS Info\n"); + get_pos_info_t *get_pos_info = &scsi->get_pos_info; + + get_pos_info->pos = 0x8eff; + get_pos_info->pos2 = scsi->pos_regs[3] | (scsi->pos_regs[2] << 8); + get_pos_info->pos3 = 0x0e | (scsi->pos_regs[4] << 8); + get_pos_info->pos4 = 1 << 12; + get_pos_info->pos4 = (7 << 8) | 8; + get_pos_info->pos5 = (16 << 8) | scsi->pacing; + get_pos_info->pos6 = (30 << 8) | 1; + get_pos_info->pos7 = 0; + get_pos_info->pos8 = 0; + + DMAPageWrite(scb->sge.sys_buf_addr, (uint8_t *)&get_pos_info->pos, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 2, (uint8_t *)&get_pos_info->pos2, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 4, (uint8_t *)&get_pos_info->pos3, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 6, (uint8_t *)&get_pos_info->pos4, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 8, (uint8_t *)&get_pos_info->pos5, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 10, (uint8_t *)&get_pos_info->pos6, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 12, (uint8_t *)&get_pos_info->pos7, 2); + DMAPageWrite(scb->sge.sys_buf_addr + 14, (uint8_t *)&get_pos_info->pos8, 2); + scsi->scb_state = 3; + } + break; + + case CMD_DEVICE_INQUIRY: + spock_log("Device Inquiry\n"); + scsi->cdb[0] = GPCMD_INQUIRY; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = 0; /*Page code*/ + scsi->cdb[3] = 0; + scsi->cdb[4] = scb->sge.sys_buf_byte_count; /*Allocation length*/ + scsi->cdb[5] = 0; /*Control*/ + scsi->cdb_len = 6; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->data_ptr = scb->sge.sys_buf_addr; + scsi->data_len = scb->sge.sys_buf_byte_count; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_SEND_OTHER_SCSI: + spock_log("Send Other SCSI\n"); + DMAPageRead(scsi->scb_addr + 0x18, scsi->cdb, 12); + scsi->cdb[1] = (scsi->cdb[1] & 0x1f) | (scsi->dev_id[scsi->scb_id].lun_id << 5); /*Patch correct LUN into command*/ + scsi->cdb_len = (scb->lba_addr & 0xff) ? (scb->lba_addr & 0xff) : 6; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_READ_DEVICE_CAPACITY: + spock_log("Device Capacity\n"); + scsi->cdb[0] = GPCMD_READ_CDROM_CAPACITY; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = 0; /*LBA*/ + scsi->cdb[3] = 0; + scsi->cdb[4] = 0; + scsi->cdb[5] = 0; + scsi->cdb[6] = 0; /*Reserved*/ + scsi->cdb[7] = 0; + scsi->cdb[8] = 0; + scsi->cdb[9] = 0; /*Control*/ + scsi->cdb_len = 10; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_READ_DATA: + spock_log("Device Read Data\n"); + scsi->cdb[0] = GPCMD_READ_10; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/ + scsi->cdb[3] = (scb->lba_addr >> 16) & 0xff; + scsi->cdb[4] = (scb->lba_addr >> 8) & 0xff; + scsi->cdb[5] = scb->lba_addr & 0xff; + scsi->cdb[6] = 0; /*Reserved*/ + scsi->cdb[7] = (scb->block_count >> 8) & 0xff; + scsi->cdb[8] = scb->block_count & 0xff; + scsi->cdb[9] = 0; /*Control*/ + scsi->cdb_len = 10; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_WRITE_DATA: + spock_log("Device Write Data\n"); + scsi->cdb[0] = GPCMD_WRITE_10; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/ + scsi->cdb[3] = (scb->lba_addr >> 16) & 0xff; + scsi->cdb[4] = (scb->lba_addr >> 8) & 0xff; + scsi->cdb[5] = scb->lba_addr & 0xff; + scsi->cdb[6] = 0; /*Reserved*/ + scsi->cdb[7] = (scb->block_count >> 8) & 0xff; + scsi->cdb[8] = scb->block_count & 0xff; + scsi->cdb[9] = 0; /*Control*/ + scsi->cdb_len = 10; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_VERIFY: + spock_log("Device Verify\n"); + scsi->cdb[0] = GPCMD_VERIFY_10; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/ + scsi->cdb[3] = (scb->lba_addr >> 16) & 0xff; + scsi->cdb[4] = (scb->lba_addr >> 8) & 0xff; + scsi->cdb[5] = scb->lba_addr & 0xff; + scsi->cdb[6] = 0; /*Reserved*/ + scsi->cdb[7] = (scb->block_count >> 8) & 0xff; + scsi->cdb[8] = scb->block_count & 0xff; + scsi->cdb[9] = 0; /*Control*/ + scsi->cdb_len = 10; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->data_len = 0; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + + case CMD_REQUEST_SENSE: + spock_log("Device Request Sense\n"); + scsi->cdb[0] = GPCMD_REQUEST_SENSE; + scsi->cdb[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/ + scsi->cdb[2] = 0; + scsi->cdb[3] = 0; + scsi->cdb[4] = scb->sge.sys_buf_byte_count; /*Allocation length*/ + scsi->cdb[5] = 0; + scsi->cdb_len = 6; + scsi->cdb_id = scsi->dev_id[scsi->scb_id].phys_id; + scsi->scsi_state = SCSI_STATE_SELECT; + scsi->scb_state = 2; + return; + } + break; + + case 2: /* Wait */ + if (scsi->scsi_state == SCSI_STATE_IDLE) { + if (scsi->last_status == SCSI_STATUS_OK) { + scsi->scb_state = 3; + spock_log("Status is Good on device ID %d\n", scsi->cdb_id); + } else if (scsi->last_status == SCSI_STATUS_CHECK_CONDITION) { + uint16_t term_stat_block_addr7 = (0xc << 8) | 2; + uint16_t term_stat_block_addr8 = 0x20; + uint16_t term_stat_block_addrb = scsi->scb_addr & 0xffff; + uint16_t term_stat_block_addrc = scsi->scb_addr >> 16; + + spock_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL); + scsi->scb_state = 0; + spock_log("Status Check Condition on device ID %d\n", scsi->cdb_id); + DMAPageWrite(scb->term_status_block_addr + 0x7*2, (uint8_t *)&term_stat_block_addr7, 2); + DMAPageWrite(scb->term_status_block_addr + 0x8*2, (uint8_t *)&term_stat_block_addr8, 2); + DMAPageWrite(scb->term_status_block_addr + 0xb*2, (uint8_t *)&term_stat_block_addrb, 2); + DMAPageWrite(scb->term_status_block_addr + 0xc*2, (uint8_t *)&term_stat_block_addrc, 2); + } + } else if (scsi->scsi_state == SCSI_STATE_SELECT_FAILED) { + uint16_t term_stat_block_addr7 = (0xc << 8) | 2; + uint16_t term_stat_block_addr8 = 0x10; + spock_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL); + scsi->scb_state = 0; + DMAPageWrite(scb->term_status_block_addr + 0x7*2, (uint8_t *)&term_stat_block_addr7, 2); + DMAPageWrite(scb->term_status_block_addr + 0x8*2, (uint8_t *)&term_stat_block_addr8, 2); + } + break; + + case 3: /* Complete */ + if (scb->enable & 1) { + scsi->scb_state = 1; + scsi->scb_addr = scb->scb_chain_addr; + spock_log("Next SCB - %08x\n", scsi->scb_addr); + } else { + spock_set_irq(scsi, scsi->scb_id, IRQ_TYPE_SCB_COMPLETE); + scsi->scb_state = 0; + spock_log("Complete SCB\n"); + } + break; + } + } while (scsi->scb_state != old_scb_state); +} + +static void +spock_process_scsi(spock_t *scsi, scb_t *scb) +{ + int c; + double p; + scsi_device_t *sd; + + switch (scsi->scsi_state) { + case SCSI_STATE_IDLE: + break; + + case SCSI_STATE_SELECT: + if ((scsi->cdb_id != (uint8_t)-1) && scsi_device_present(&scsi_devices[scsi->cdb_id])) { + scsi->scsi_state = SCSI_STATE_SEND_COMMAND; + spock_log("Device selected at ID %i\n", scsi->cdb_id); + break; + } else { + spock_log("Device selection failed at ID %i\n", scsi->cdb_id); + scsi->scsi_state = SCSI_STATE_SELECT_FAILED; + if (!scsi->cmd_timer) { + spock_log("Callback to reset\n"); + scsi->cmd_timer = 1; + } + spock_add_to_period(scsi, 1); + } + break; + + case SCSI_STATE_SELECT_FAILED: + break; + + case SCSI_STATE_SEND_COMMAND: + sd = &scsi_devices[scsi->cdb_id]; + memset(scsi->temp_cdb, 0x00, 12); + + if (scsi->cdb_len < 12) { + memcpy(scsi->temp_cdb, scsi->cdb, + scsi->cdb_len); + spock_add_to_period(scsi, scsi->cdb_len); + } else { + memcpy(scsi->temp_cdb, scsi->cdb, + 12); + spock_add_to_period(scsi, 12); + } + + scsi->data_ptr = scb->sge.sys_buf_addr; + scsi->data_len = scb->sge.sys_buf_byte_count; + + sd->buffer_length = spock_get_len(scsi, scb); + + scsi_device_command_phase0(sd, scsi->temp_cdb); + spock_log("SCSI ID %i: Command %02X: CDB1 = %02x, LUN = %i, Buffer Length %i, SCSI Phase %02X\n", scsi->cdb_id, scsi->temp_cdb[0], scsi->temp_cdb[1], scsi->dev_id[scsi->scb_id].lun_id, sd->buffer_length, sd->phase); + + if (sd->phase != SCSI_PHASE_STATUS && sd->buffer_length > 0) { + p = scsi_device_get_callback(sd); + if (p <= 0.0) + spock_add_to_period(scsi, sd->buffer_length); + else + scsi->media_period += p; + + if (scb->enable & ENABLE_PT) { + int32_t buflen = sd->buffer_length; + int sg_pos = 0; + uint32_t DataTx = 0; + uint32_t Address; + + if (scb->sge.sys_buf_byte_count > 0) { + for (c = 0; c < scsi->data_len; c += 8) { + spock_rd_sge(scsi, scsi->data_ptr + c, &scb->sge); + + Address = scb->sge.sys_buf_addr; + DataTx = MIN((int) scb->sge.sys_buf_byte_count, buflen); + + if ((sd->phase == SCSI_PHASE_DATA_IN) && DataTx) { + spock_log("Writing S/G segment %i: length %i, pointer %08X\n", c, DataTx, Address); + DMAPageWrite(Address, &sd->sc->temp_buffer[sg_pos], DataTx); + } else if ((sd->phase == SCSI_PHASE_DATA_OUT) && DataTx) { + spock_log("Reading S/G segment %i: length %i, pointer %08X\n", c, DataTx, Address); + DMAPageRead(Address, &sd->sc->temp_buffer[sg_pos], DataTx); + } + + sg_pos += scb->sge.sys_buf_byte_count; + buflen -= scb->sge.sys_buf_byte_count; + + if (buflen < 0) + buflen = 0; + } + } + } else { + if (sd->phase == SCSI_PHASE_DATA_IN) { + DMAPageWrite(scsi->data_ptr, sd->sc->temp_buffer, MIN(sd->buffer_length, (int)scsi->data_len)); + } else if (sd->phase == SCSI_PHASE_DATA_OUT) + DMAPageRead(scsi->data_ptr, sd->sc->temp_buffer, MIN(sd->buffer_length, (int)scsi->data_len)); + } + + scsi_device_command_phase1(sd); + } + scsi->last_status = sd->status; + scsi->scsi_state = SCSI_STATE_END_PHASE; + break; + + case SCSI_STATE_END_PHASE: + scsi->scsi_state = SCSI_STATE_IDLE; + + spock_log("State to idle\n"); + if (!scsi->cmd_timer) { + scsi->cmd_timer = 1; + } + spock_add_to_period(scsi, 1); + break; + } +} + +static void +spock_callback(void *priv) +{ + double period; + spock_t *scsi = (spock_t *)priv; + scb_t *scb = &scsi->scb; + + scsi->temp_period = 0; + scsi->media_period = 0.0; + + if (scsi->cmd_timer) { + scsi->cmd_timer--; + if (!scsi->cmd_timer) { + spock_execute_cmd(scsi, scb); + } + } + + if (scsi->attention_wait && + (scsi->scb_state == 0 || (scsi->attention_pending & 0xf0) == 0xe0)) { + scsi->attention_wait--; + if (!scsi->attention_wait) { + scsi->attention = scsi->attention_pending; + scsi->status &= ~STATUS_BUSY; + scsi->cir[0] = scsi->cir_pending[0]; + scsi->cir[1] = scsi->cir_pending[1]; + scsi->cir[2] = scsi->cir_pending[2]; + scsi->cir[3] = scsi->cir_pending[3]; + scsi->cir_status = 0; + + switch (scsi->attention >> 4) { + case 1: /*Immediate command*/ + scsi->cmd_status = 0x0a; + scsi->command = scsi->cir[0] | (scsi->cir[1] << 8) | (scsi->cir[2] << 16) | (scsi->cir[3] << 24); + switch (scsi->command & CMD_MASK) { + case CMD_ASSIGN: + case CMD_DMA_PACING_CONTROL: + case CMD_FEATURE_CONTROL: + case CMD_INVALID_412: + case CMD_RESET: + spock_process_imm_cmd(scsi); + break; + } + break; + + case 3: case 4: case 0x0f: /*Start SCB*/ + scsi->cmd_status = 1; + scsi->scb_addr = scsi->cir[0] | (scsi->cir[1] << 8) | (scsi->cir[2] << 16) | (scsi->cir[3] << 24); + scsi->scb_id = scsi->attention & 0x0f; + scsi->cmd_timer = SPOCK_TIME * 2; + spock_log("Start SCB at %08x\n", scsi->scb_addr); + scsi->scb_state = 1; + break; + + case 5: /*Invalid*/ + case 0x0a: /*Invalid*/ + scsi->in_invalid = 1; + scsi->cmd_timer = SPOCK_TIME * 2; + break; + + case 0x0e: /*EOI*/ + scsi->irq_status = 0; + spock_clear_irq(scsi, scsi->attention & 0xf); + break; + } + } + } + + spock_process_scsi(scsi, scb); + + period = 0.2 * ((double) scsi->temp_period); + timer_on(&scsi->callback_timer, (scsi->media_period + period + 10.0), 0); + spock_log("Temporary period: %lf us (%" PRIi64 " periods)\n", scsi->callback_timer.period, scsi->temp_period); +} + +static void +spock_mca_write(int port, uint8_t val, void *priv) +{ + spock_t *scsi = (spock_t *)priv; + + if (port < 0x102) + return; + + spock_log("spock_mca_write: port=%04x val=%02x %04x:%04x\n", port, val, CS, cpu_state.pc); + + io_removehandler((((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540, 0x0008, spock_read, spock_readw, NULL, spock_write, spock_writew, NULL, scsi); + mem_mapping_disable(&scsi->bios_rom.mapping); + + scsi->pos_regs[port & 7] = val; + + spock_log("SCSI pos reg write POS2 = %02x, POS3 = %02x, POS4 = %02x\n", scsi->pos_regs[2], scsi->pos_regs[3], scsi->pos_regs[4]); + + if (scsi->pos_regs[2] & 1) { + spock_log("spock scsi io = %04x\n", (((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540); + io_sethandler((((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540, 0x0008, spock_read, spock_readw, NULL, spock_write, spock_writew, NULL, scsi); + if ((scsi->pos_regs[2] >> 4) == 0x0f) + mem_mapping_disable(&scsi->bios_rom.mapping); + else { + spock_log("Spock BIOS segment select (hex val) = %02x, enabled bios area is %s\n", scsi->pos_regs[2] >> 4, (scsi->pos_regs[4] & 0x80) ? "32KB" : "16KB"); + mem_mapping_set_addr(&scsi->bios_rom.mapping, ((scsi->pos_regs[2] >> 4) * 0x2000) + 0xc0000, 0x8000); + } + } +} + +static uint8_t +spock_mca_read(int port, void *priv) +{ + spock_t *scsi = (spock_t *)priv; + + spock_log("spock_mca_read: port=%04x %02x %04x:%04x\n", port, scsi->pos_regs[port & 7], CS,cpu_state.pc); + + return scsi->pos_regs[port & 7]; +} + +static uint8_t +spock_mca_feedb(void *priv) +{ + spock_t *scsi = (spock_t *)priv; + + return (scsi->pos_regs[2] & 0x01); +} + +static void +spock_mca_reset(void *priv) +{ + spock_t *scsi = (spock_t *)priv; + int i; + + scsi->in_reset = 2; + scsi->cmd_timer = SPOCK_TIME * 50; + scsi->status = STATUS_BUSY; + scsi->scsi_state = SCSI_STATE_IDLE; + scsi->scb_state = 0; + scsi->in_invalid = 0; + scsi->attention_wait = 0; + scsi->basic_ctrl = 0; + + /* Reset all devices on controller reset. */ + for (i = 0; i < 7; i++) + scsi_device_reset(&scsi_devices[i]); +} + +static void * +spock_init(const device_t *info) +{ + int c; + spock_t *scsi = malloc(sizeof(spock_t)); + memset(scsi, 0x00, sizeof(spock_t)); + + scsi->irq = 14; + + scsi->bios_ver = device_get_config_int("bios_ver"); + + if (scsi->bios_ver) + rom_init_interleaved(&scsi->bios_rom, SPOCK_U68_1991_ROM, SPOCK_U69_1991_ROM, + 0xc8000, 0x8000, 0x7fff, 0x4000, MEM_MAPPING_EXTERNAL); + else + rom_init_interleaved(&scsi->bios_rom, SPOCK_U68_1990_ROM, SPOCK_U69_1990_ROM, + 0xc8000, 0x8000, 0x7fff, 0x4000, MEM_MAPPING_EXTERNAL); + + mem_mapping_disable(&scsi->bios_rom.mapping); + + scsi->pos_regs[0] = 0xff; + scsi->pos_regs[1] = 0x8e; + mca_add(spock_mca_read, spock_mca_write, spock_mca_feedb, spock_mca_reset, scsi); + + scsi->in_reset = 2; + scsi->cmd_timer = SPOCK_TIME * 50; + scsi->status = STATUS_BUSY; + + for (c = 0; c < (SCSI_ID_MAX-1); c++) + scsi->dev_id[c].phys_id = -1; + + scsi->dev_id[SCSI_ID_MAX-1].phys_id = 7; + + timer_add(&scsi->callback_timer, spock_callback, scsi, 1); + scsi->callback_timer.period = 10.0; + timer_set_delay_u64(&scsi->callback_timer, (uint64_t) (scsi->callback_timer.period * ((double) TIMER_USEC))); + + return scsi; +} + +static void +spock_close(void *p) +{ + spock_t *scsi = (spock_t *)p; + + if (scsi) { + free(scsi); + scsi = NULL; + } +} + +static int +spock_available(void) +{ + return rom_present(SPOCK_U68_1991_ROM) && rom_present(SPOCK_U69_1991_ROM) && + rom_present(SPOCK_U68_1990_ROM) && rom_present(SPOCK_U69_1990_ROM); +} + +static const device_config_t spock_rom_config[] = { + { + "bios_ver", "BIOS Version", CONFIG_SELECTION, "", 1, + { + { + "1991 BIOS (>1GB)", 1 + }, + { + "1990 BIOS", 0 + }, + { + "" + } + }, + }, + { + "", "", -1 + } +}; + +const device_t spock_device = +{ + "IBM PS/2 SCSI Adapter (Spock)", + DEVICE_MCA, + 0, + spock_init, spock_close, NULL, + spock_available, + NULL, NULL, + spock_rom_config +}; diff --git a/src/scsi/scsi_spock.h b/src/scsi/scsi_spock.h new file mode 100644 index 000000000..bf064421c --- /dev/null +++ b/src/scsi/scsi_spock.h @@ -0,0 +1,25 @@ +/* + * 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 IBM PS/2 SCSI controller with + * cache for MCA only. + * + * Version: @(#)scsi_spock.h 1.0.0 2020/03/10 + * + * Authors: Sarah Walker, + * TheCollector1995, + * + * Copyright 2020 Sarah Walker. + * Copyright 2020 TheCollector1995. + */ +#ifndef SCSI_SPOCK_H +# define SCSI_SPOCK_H + +extern const device_t spock_device; + +#endif /*SCSI_SPOCK_H*/ \ No newline at end of file diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 4c9b413ce..bcc10bbd2 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -642,7 +642,8 @@ SCSIOBJ := scsi.o scsi_device.o \ scsi_cdrom.o scsi_disk.o \ scsi_x54x.o \ scsi_aha154x.o scsi_buslogic.o \ - scsi_ncr5380.o scsi_ncr53c8xx.o + scsi_ncr5380.o scsi_ncr53c8xx.o \ + scsi_spock.o NETOBJ := network.o \ net_pcap.o \ diff --git a/src/win/Makefile_ndr.mingw b/src/win/Makefile_ndr.mingw index 5b565407e..23466a8f6 100644 --- a/src/win/Makefile_ndr.mingw +++ b/src/win/Makefile_ndr.mingw @@ -647,7 +647,8 @@ SCSIOBJ := scsi.o scsi_device.o \ scsi_cdrom.o scsi_disk.o \ scsi_x54x.o \ scsi_aha154x.o scsi_buslogic.o \ - scsi_ncr5380.o scsi_ncr53c8xx.o + scsi_ncr5380.o scsi_ncr53c8xx.o \ + scsi_spock.o NETOBJ := network.o \ net_pcap.o \