Files
86Box/src/scsi/scsi_device.c
TC1995 4008010131 Big SCSI bus update of the day, NCR 5380 too (January 20th, 2025)
1. Separate the SCSI bus functions from NCR 5380 into true general purpose SCSI bus functions, allowing use of future legacy scsi controllers.
2. Corrected NCR 5380 chip period for the SCSI controllers based on that chip so that CD-ROM speed is correct enough per speed tests and no more breakage (I hope, report if they are still there, please!) on desyncs.
3. A NCR 5380 software reset involves asserting an IRQ.
2025-01-20 19:55:18 +01:00

524 lines
18 KiB
C

/*
* 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.
*
* The generic SCSI device command handler.
*
*
*
* Authors: Miran Grca, <mgrca8@gmail.com>
* Fred N. van Kempen, <decwiz@yahoo.com>
*
* Copyright 2016-2018 Miran Grca.
* Copyright 2017-2018 Fred N. van Kempen.
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/hdd.h>
#include <86box/hdc_ide.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/plat_unused.h>
scsi_device_t scsi_devices[SCSI_BUS_MAX][SCSI_ID_MAX];
int scsi_command_length[8] = { 6, 10, 10, 6, 16, 12, 10, 6 };
uint8_t scsi_null_device_sense[18] = { 0x70, 0, SENSE_ILLEGAL_REQUEST, 0, 0, 0, 0, 0, 0, 0, 0, 0, ASC_INV_LUN, 0, 0, 0, 0, 0 };
#define SET_BUS_STATE(scsi_bus, state) scsi_bus->bus_out = (scsi_bus->bus_out & ~(SCSI_PHASE_MESSAGE_IN)) | (state & (SCSI_PHASE_MESSAGE_IN))
#ifdef ENABLE_SCSI_DEVICE_LOG
int scsi_device_do_log = ENABLE_SCSI_DEVICE_LOG;
static void
scsi_device_log(const char *fmt, ...)
{
va_list ap;
if (scsi_device_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define scsi_device_log(fmt, ...)
#endif
static uint8_t
scsi_device_target_command(scsi_device_t *dev, uint8_t *cdb)
{
if (dev->command) {
dev->command(dev->sc, cdb);
if (dev->sc->tf->status & ERR_STAT)
return SCSI_STATUS_CHECK_CONDITION;
else
return SCSI_STATUS_OK;
} else
return SCSI_STATUS_CHECK_CONDITION;
}
double
scsi_device_get_callback(scsi_device_t *dev)
{
if (dev->sc)
return dev->sc->callback;
else
return -1.0;
}
uint8_t *
scsi_device_sense(scsi_device_t *dev)
{
if (dev->sc)
return dev->sc->sense;
else
return scsi_null_device_sense;
}
void
scsi_device_request_sense(scsi_device_t *dev, uint8_t *buffer, uint8_t alloc_length)
{
if (dev->request_sense)
dev->request_sense(dev->sc, buffer, alloc_length);
else
memcpy(buffer, scsi_null_device_sense, alloc_length);
}
void
scsi_device_reset(scsi_device_t *dev)
{
if (dev->reset)
dev->reset(dev->sc);
}
int
scsi_device_present(scsi_device_t *dev)
{
if (dev->type == SCSI_NONE)
return 0;
else
return 1;
}
int
scsi_device_valid(scsi_device_t *dev)
{
if (dev->sc)
return 1;
else
return 0;
}
int
scsi_device_cdb_length(UNUSED(scsi_device_t *dev))
{
/* Right now, it's 12 for all devices. */
return 12;
}
void
scsi_device_command_phase0(scsi_device_t *dev, uint8_t *cdb)
{
if (!dev->sc) {
dev->phase = SCSI_PHASE_STATUS;
dev->status = SCSI_STATUS_CHECK_CONDITION;
return;
}
/* Finally, execute the SCSI command immediately and get the transfer length. */
dev->phase = SCSI_PHASE_COMMAND;
dev->status = scsi_device_target_command(dev, cdb);
}
void
scsi_device_command_stop(scsi_device_t *dev)
{
if (dev->command_stop) {
dev->command_stop(dev->sc);
dev->status = SCSI_STATUS_OK;
}
}
void
scsi_device_command_phase1(scsi_device_t *dev)
{
if (!dev->sc)
return;
/* Call the second phase. */
if (dev->phase == SCSI_PHASE_DATA_OUT) {
if (dev->phase_data_out)
dev->phase_data_out(dev->sc);
} else
scsi_device_command_stop(dev);
if (dev->sc->tf->status & ERR_STAT)
dev->status = SCSI_STATUS_CHECK_CONDITION;
else
dev->status = SCSI_STATUS_OK;
}
/* When LUN is FF, there has been no IDENTIFY message, otherwise
there has been one. */
void
scsi_device_identify(scsi_device_t *dev, uint8_t lun)
{
if ((dev == NULL) || (dev->type == SCSI_NONE) || !dev->sc)
return;
dev->sc->cur_lun = lun;
/* TODO: This should return a value, should IDENTIFY fail due to a
a LUN not supported by the target. */
}
void
scsi_device_close_all(void)
{
scsi_device_t *dev;
for (uint8_t i = 0; i < SCSI_BUS_MAX; i++) {
for (uint8_t j = 0; j < SCSI_ID_MAX; j++) {
dev = &(scsi_devices[i][j]);
if (dev->command_stop && dev->sc)
dev->command_stop(dev->sc);
}
}
}
void
scsi_device_init(void)
{
scsi_device_t *dev;
for (uint8_t i = 0; i < SCSI_BUS_MAX; i++) {
for (uint8_t j = 0; j < SCSI_ID_MAX; j++) {
dev = &(scsi_devices[i][j]);
memset(dev, 0, sizeof(scsi_device_t));
dev->type = SCSI_NONE;
}
}
}
int
scsi_device_get_id(uint8_t data)
{
for (uint8_t c = 0; c < SCSI_ID_MAX; c++) {
if (data & (1 << c))
return c;
}
return -1;
}
static int
scsi_device_get_msg(uint8_t *msgp, int len)
{
uint8_t msg = msgp[0];
if ((msg == 0) || ((msg >= 0x02) && (msg <= 0x1f)) || (msg >= 0x80))
return 1;
if ((msg >= 0x20) && (msg <= 0x2f))
return 2;
if (len < 2)
return 3;
return msgp[1];
}
int
scsi_bus_read(scsi_bus_t *scsi_bus)
{
scsi_device_t *dev;
int phase;
/*Wait processes to handle bus requests*/
if (scsi_bus->clear_req) {
scsi_bus->clear_req--;
if (!scsi_bus->clear_req) {
scsi_device_log("Prelude to command data\n");
SET_BUS_STATE(scsi_bus, scsi_bus->bus_phase);
scsi_bus->bus_out |= BUS_REQ;
}
}
if (scsi_bus->wait_data) {
scsi_bus->wait_data--;
if (!scsi_bus->wait_data) {
dev = &scsi_devices[scsi_bus->bus_device][scsi_bus->target_id];
SET_BUS_STATE(scsi_bus, scsi_bus->bus_phase);
phase = scsi_bus->bus_out & SCSI_PHASE_MESSAGE_IN;
switch (phase) {
case SCSI_PHASE_DATA_IN:
scsi_device_log("DataIn.\n");
scsi_bus->state = STATE_DATAIN;
if ((dev->sc != NULL) && (dev->sc->temp_buffer != NULL))
scsi_bus->data = dev->sc->temp_buffer[scsi_bus->data_pos++];
scsi_bus->bus_out = (scsi_bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(scsi_bus->data) | BUS_DBP;
break;
case SCSI_PHASE_DATA_OUT:
if (scsi_bus->bus_phase & BUS_IDLE) {
scsi_device_log("Bus Idle.\n");
scsi_bus->state = STATE_IDLE;
scsi_bus->bus_out &= ~BUS_BSY;
scsi_bus->timer(scsi_bus->priv, 0.0);
} else {
scsi_device_log("DataOut.\n");
scsi_bus->state = STATE_DATAOUT;
}
break;
case SCSI_PHASE_STATUS:
scsi_device_log("Status.\n");
scsi_bus->bus_out |= BUS_REQ;
scsi_bus->state = STATE_STATUS;
scsi_bus->bus_out = (scsi_bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(dev->status) | BUS_DBP;
break;
case SCSI_PHASE_MESSAGE_IN:
scsi_device_log("Message In.\n");
scsi_bus->state = STATE_MESSAGEIN;
scsi_bus->bus_out = (scsi_bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(0) | BUS_DBP;
break;
case SCSI_PHASE_MESSAGE_OUT:
scsi_device_log("Message Out.\n");
scsi_bus->bus_out |= BUS_REQ;
scsi_bus->state = STATE_MESSAGEOUT;
scsi_bus->bus_out = (scsi_bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(scsi_bus->target_id >> 5) | BUS_DBP;
break;
default:
break;
}
}
}
if (scsi_bus->wait_complete) {
scsi_bus->wait_complete--;
if (!scsi_bus->wait_complete)
scsi_bus->bus_out |= BUS_REQ;
}
return scsi_bus->bus_out;
}
void
scsi_bus_update(scsi_bus_t *scsi_bus, int bus)
{
scsi_device_t *dev = &scsi_devices[scsi_bus->bus_device][scsi_bus->target_id];
double p;
uint8_t sel_data;
int msglen;
/*Start the SCSI command layer, which will also make the timings*/
if (bus & BUS_ARB)
scsi_bus->state = STATE_IDLE;
scsi_device_log("State = %i\n", scsi_bus->state);
switch (scsi_bus->state) {
case STATE_IDLE:
scsi_bus->clear_req = scsi_bus->wait_data = scsi_bus->wait_complete = 0;
if ((bus & BUS_SEL) && !(bus & BUS_BSY)) {
sel_data = BUS_GETDATA(bus);
scsi_bus->target_id = scsi_device_get_id(sel_data);
/*Once the device has been found and selected, mark it as busy*/
if ((scsi_bus->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[scsi_bus->bus_device][scsi_bus->target_id])) {
scsi_bus->bus_out |= BUS_BSY;
scsi_bus->state = STATE_SELECT;
scsi_device_log("Select - target ID = %i, moving to state = %d.\n", scsi_bus->target_id, scsi_bus->state);
} else {
scsi_device_log("Device not found at ID %i, Current Bus BSY=%02x\n", scsi_bus->target_id, scsi_bus->bus_out);
scsi_bus->bus_out = 0;
}
}
break;
case STATE_SELECT:
if (!(bus & BUS_SEL)) {
if (!(bus & BUS_ATN)) {
if ((scsi_bus->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[scsi_bus->bus_device][scsi_bus->target_id])) {
scsi_device_log("Device found at ID %i, Current Bus BSY=%02x\n", scsi_bus->target_id, scsi_bus->bus_out);
scsi_bus->state = STATE_COMMAND;
scsi_bus->bus_out = BUS_BSY | BUS_REQ;
scsi_bus->command_pos = 0;
SET_BUS_STATE(scsi_bus, SCSI_PHASE_COMMAND);
} else {
scsi_device_log("Device not found at ID %i again.\n", scsi_bus->target_id);
scsi_bus->state = STATE_IDLE;
scsi_bus->bus_out = 0;
}
} else {
scsi_device_log("Set to SCSI Message Out\n");
scsi_bus->bus_phase = SCSI_PHASE_MESSAGE_OUT;
scsi_bus->wait_data = 4;
scsi_bus->msgout_pos = 0;
scsi_bus->is_msgout = 1;
}
}
break;
case STATE_COMMAND:
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
/*Write command byte to the output data register*/
scsi_bus->command[scsi_bus->command_pos++] = BUS_GETDATA(bus);
scsi_bus->clear_req = 3;
scsi_bus->bus_phase = scsi_bus->bus_out & SCSI_PHASE_MESSAGE_IN;
scsi_bus->bus_out &= ~BUS_REQ;
scsi_device_log("Command pos=%i, output data=%02x\n", scsi_bus->command_pos, BUS_GETDATA(bus));
if (scsi_bus->command_pos == scsi_command_length[(scsi_bus->command[0] >> 5) & 7]) {
if (scsi_bus->is_msgout) {
scsi_bus->is_msgout = 0;
#if 0
scsi_bus->command[1] = (scsi_bus->command[1] & 0x1f) | (scsi_bus->msglun << 5);
#endif
}
/*Reset data position to default*/
scsi_bus->data_pos = 0;
dev = &scsi_devices[scsi_bus->bus_device][scsi_bus->target_id];
scsi_device_log("SCSI Command 0x%02X for ID %d, status code=%02x\n", scsi_bus->command[0], scsi_bus->target_id, dev->status);
dev->buffer_length = -1;
scsi_device_command_phase0(dev, scsi_bus->command);
scsi_device_log("SCSI ID %i: Command %02X: Buffer Length %i, SCSI Phase %02X\n", scsi_bus->target_id, scsi_bus->command[0], dev->buffer_length, dev->phase);
scsi_bus->period = 1.0;
scsi_bus->wait_data = 4;
scsi_bus->data_wait = 0;
scsi_bus->command_issued = 1;
if (dev->status == SCSI_STATUS_OK) {
/*If the SCSI phase is Data In or Data Out, allocate the SCSI buffer based on the transfer length of the command*/
if (dev->buffer_length && ((dev->phase == SCSI_PHASE_DATA_IN) || (dev->phase == SCSI_PHASE_DATA_OUT))) {
p = scsi_device_get_callback(dev);
scsi_bus->period = (p > 0.0) ? ((p / scsi_bus->divider) * scsi_bus->multi) : (((double) dev->buffer_length) * scsi_bus->speed);
scsi_device_log("SCSI ID %i: command 0x%02x for p = %lf, update = %lf, len = %i, dmamode = %x\n", scsi_bus->target_id, scsi_bus->command[0], scsi_device_get_callback(dev), scsi_bus->period, dev->buffer_length, scsi_bus->tx_mode);
}
}
scsi_bus->bus_phase = dev->phase;
}
}
break;
case STATE_DATAIN:
dev = &scsi_devices[scsi_bus->bus_device][scsi_bus->target_id];
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
if (scsi_bus->data_pos >= dev->buffer_length) {
scsi_bus->bus_out &= ~BUS_REQ;
scsi_device_command_phase1(dev);
scsi_bus->bus_phase = SCSI_PHASE_STATUS;
scsi_bus->wait_data = 4;
scsi_bus->wait_complete = 8;
} else {
if ((dev->sc != NULL) && (dev->sc->temp_buffer != NULL))
scsi_bus->data = dev->sc->temp_buffer[scsi_bus->data_pos++];
scsi_device_log("TXMode DataIn=%x, cmd=%02x.\n", scsi_bus->tx_mode, scsi_bus->command[0]);
scsi_bus->bus_out = (scsi_bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(scsi_bus->data) | BUS_DBP | BUS_REQ;
if (scsi_bus->tx_mode == PIO_TX_BUS) { /*If a data in command that is not read 6/10 has been issued*/
scsi_device_log("DMA mode idle IN=%d.\n", scsi_bus->data_pos);
scsi_bus->data_wait |= 1;
scsi_bus->timer(scsi_bus->priv, scsi_bus->period);
} else {
scsi_device_log("DMA mode IN=%d.\n", scsi_bus->data_pos);
scsi_bus->clear_req = 3;
}
scsi_bus->bus_out &= ~BUS_REQ;
scsi_bus->bus_phase = SCSI_PHASE_DATA_IN;
}
}
break;
case STATE_DATAOUT:
dev = &scsi_devices[scsi_bus->bus_device][scsi_bus->target_id];
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
if ((dev->sc != NULL) && (dev->sc->temp_buffer != NULL))
dev->sc->temp_buffer[scsi_bus->data_pos++] = BUS_GETDATA(bus);
if (scsi_bus->data_pos >= dev->buffer_length) {
scsi_bus->bus_out &= ~BUS_REQ;
scsi_device_command_phase1(dev);
scsi_bus->bus_phase = SCSI_PHASE_STATUS;
scsi_bus->wait_data = 4;
scsi_bus->wait_complete = 8;
} else {
/*More data is to be transferred, place a request*/
if (scsi_bus->tx_mode == PIO_TX_BUS) { /*If a data in command that is not write 6/10 has been issued*/
scsi_device_log("DMA mode idle OUT=%d.\n", scsi_bus->data_pos);
scsi_bus->data_wait |= 1;
scsi_bus->timer(scsi_bus->priv, scsi_bus->period);
scsi_bus->bus_out &= ~BUS_REQ;
} else {
scsi_device_log("DMA mode OUT=%d.\n", scsi_bus->data_pos);
scsi_bus->bus_out |= BUS_REQ;
}
}
}
break;
case STATE_STATUS:
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
/*All transfers done, wait until next transfer*/
scsi_device_identify(&scsi_devices[scsi_bus->bus_device][scsi_bus->target_id], SCSI_LUN_USE_CDB);
scsi_bus->bus_out &= ~BUS_REQ;
scsi_bus->bus_phase = SCSI_PHASE_MESSAGE_IN;
scsi_bus->wait_data = 4;
scsi_bus->wait_complete = 8;
scsi_bus->command_issued = 0;
}
break;
case STATE_MESSAGEIN:
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
scsi_bus->bus_out &= ~BUS_REQ;
scsi_bus->bus_phase = BUS_IDLE;
scsi_bus->wait_data = 4;
}
break;
case STATE_MESSAGEOUT:
if ((bus & BUS_ACK) && !(scsi_bus->bus_in & BUS_ACK)) {
scsi_bus->msgout[scsi_bus->msgout_pos++] = BUS_GETDATA(bus);
msglen = scsi_device_get_msg(scsi_bus->msgout, scsi_bus->msgout_pos);
if (scsi_bus->msgout_pos >= msglen) {
if ((scsi_bus->msgout[0] & (0x80 | 0x20)) == 0x80)
scsi_bus->msglun = scsi_bus->msgout[0] & 7;
scsi_bus->bus_out &= ~BUS_REQ;
scsi_bus->state = STATE_MESSAGE_ID;
}
}
break;
case STATE_MESSAGE_ID:
if ((scsi_bus->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[scsi_bus->bus_device][scsi_bus->target_id])) {
scsi_device_log("Device found at ID %i on MSGOUT, Current Bus BSY=%02x\n", scsi_bus->target_id, scsi_bus->bus_out);
scsi_device_identify(&scsi_devices[scsi_bus->bus_device][scsi_bus->target_id], scsi_bus->msglun);
scsi_bus->state = STATE_COMMAND;
scsi_bus->bus_out = BUS_BSY | BUS_REQ;
scsi_bus->command_pos = 0;
SET_BUS_STATE(scsi_bus, SCSI_PHASE_COMMAND);
}
break;
default:
break;
}
scsi_bus->bus_in = bus;
}