/* * 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, * Fred N. van Kempen, * * Copyright 2016-2018 Miran Grca. * Copyright 2017-2018 Fred N. van Kempen. */ #include #include #include #include #include #include #include #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; }