diff options
Diffstat (limited to 'src/devices/bus/scsi/scsihle.c')
-rw-r--r-- | src/devices/bus/scsi/scsihle.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/devices/bus/scsi/scsihle.c b/src/devices/bus/scsi/scsihle.c new file mode 100644 index 00000000000..033e1f66bae --- /dev/null +++ b/src/devices/bus/scsi/scsihle.c @@ -0,0 +1,591 @@ +// license:BSD-3-Clause +// copyright-holders:smf +/* + +scsihle.c + +Base class for HLE'd SCSI devices. + +*/ + +#include "scsihle.h" + +scsihle_device::scsihle_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) : + device_t(mconfig, type, name, tag, owner, clock, shortname, source), + scsi_port_interface(mconfig, *this), + m_scsi_id(*this, "SCSI_ID"), + m_input_data(0) +{ +} + +INPUT_PORTS_START(scsihle) + PORT_START("SCSI_ID") + PORT_CONFNAME(0x07, 0x07, "SCSI ID") + PORT_CONFSETTING( 0x00, "0") + PORT_CONFSETTING( 0x01, "1") + PORT_CONFSETTING( 0x02, "2") + PORT_CONFSETTING( 0x03, "3") + PORT_CONFSETTING( 0x04, "4") + PORT_CONFSETTING( 0x05, "5") + PORT_CONFSETTING( 0x06, "6") + PORT_CONFSETTING( 0x07, "7") +INPUT_PORTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_0 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 0 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_1 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 1 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_2 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 2 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_3 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 3 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_4 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 4 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_5 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 5 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_6 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 6 ) +DEVICE_INPUT_DEFAULTS_END + +DEVICE_INPUT_DEFAULTS_START( SCSI_ID_7 ) + DEVICE_INPUT_DEFAULTS( "SCSI_ID", 7, 7 ) +DEVICE_INPUT_DEFAULTS_END + +ioport_constructor scsihle_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(scsihle); +} + +void scsihle_device::device_start() +{ + t10_start(*this); + + req_timer = timer_alloc(0); + sel_timer = timer_alloc(1); + dataout_timer = timer_alloc(2); +} + +void scsihle_device::device_reset() +{ + scsiID = m_scsi_id->read(); + t10_reset(); +} + +int scsihle_device::GetDeviceID() +{ + return scsiID; +} + +#define BSY_DELAY_NS 50 +#define REQ_DELAY_NS 90 + +static const char *const phasenames[] = +{ + "data out", "data in", "command", "status", "none", "none", "message out", "message in", "bus free","select" +}; + +// scsihle +#define SCSI_CMD_BUFFER_WRITE ( 0x3b ) +#define SCSI_CMD_BUFFER_READ ( 0x3c ) + +// scsihd +#define SCSI_CMD_FORMAT_UNIT 0x04 +#define SCSI_CMD_SEARCH_DATA_EQUAL 0x31 +#define SCSI_CMD_READ_DEFECT 0x37 + + +#define IS_COMMAND(cmd) (command[0]==cmd) + +#define FORMAT_UNIT_TIMEOUT 5 + +/* + LOGLEVEL + 0 no logging, + 1 just commands + 2 1 + data + 3 2 + line changes +*/ + +#define LOGLEVEL 0 + +#define LOG(level, ...) if(LOGLEVEL>=level) logerror(__VA_ARGS__) + +void scsihle_device::data_out(UINT8 data) +{ +// printf( "%s data out %02x\n", tag(), data ); + output_data0(BIT(data, 0)); + output_data1(BIT(data, 1)); + output_data2(BIT(data, 2)); + output_data3(BIT(data, 3)); + output_data4(BIT(data, 4)); + output_data5(BIT(data, 5)); + output_data6(BIT(data, 6)); + output_data7(BIT(data, 7)); +} + +void scsihle_device::scsi_out_req_delay(UINT8 state) +{ + req_timer->adjust(attotime::from_nsec(REQ_DELAY_NS),state); +} + +void scsihle_device::dump_bytes(UINT8 *buff, int count) +{ + int byteno; + + for(byteno=0; byteno<count; byteno++) + { + logerror("%02X ",buff[byteno]); + } +} + +void scsihle_device::dump_command_bytes() +{ + logerror("sending command 0x%02X to ScsiID %d\n",command[0],scsiID); + dump_bytes(command,cmd_idx); + logerror("\n\n"); +} + +void scsihle_device::dump_data_bytes(int count) +{ + logerror("Data buffer[0..%d]\n",count); + dump_bytes(buffer,count); + logerror("\n\n"); +} + +void scsihle_device::scsibus_read_data() +{ + data_last = (bytes_left >= m_sector_bytes) ? m_sector_bytes : bytes_left; + + LOG(2,"SCSIBUS:scsibus_read_data bytes_left=%04X, data_last=%04X\n",bytes_left,data_last); + + data_idx=0; + + if (data_last > 0) + { + ReadData(buffer, data_last); + bytes_left-=data_last; + + data_out(buffer[ data_idx++ ]); + } +} + +void scsihle_device::scsibus_write_data() +{ + if (data_last > 0) + { + WriteData(buffer, data_last); + bytes_left -= data_last; + } + + data_idx=0; +} + +void scsihle_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr) +{ + switch (tid) + { + case 0: + output_req(param); + break; + + case 1: + output_bsy(param); + break; + + case 2: + // Some drives, notably the ST225N and ST125N, accept fromat unit commands + // with flags set indicating that bad block data should be transfered but + // don't then implemnt a data in phase, this timeout it to catch these ! + if (IS_COMMAND(SCSI_CMD_FORMAT_UNIT) && (data_idx==0)) + { + scsi_change_phase(SCSI_PHASE_STATUS); + } + break; + } +} + +void scsihle_device::scsibus_exec_command() +{ + int command_local = 0; + + if (LOGLEVEL) + dump_command_bytes(); + + //is_linked=command[cmd_idx-1] & 0x01; + is_linked=0; + + // Check for locally executed commands, and if found execute them + switch (command[0]) + { + // Format unit + case SCSI_CMD_FORMAT_UNIT: + LOG(1,"SCSIBUS: format unit command[1]=%02X & 0x10\n",(command[1] & 0x10)); + command_local=1; + if ((command[1] & 0x10)==0x10) + m_phase = SCSI_PHASE_DATAOUT; + else + m_phase = SCSI_PHASE_STATUS; + + m_status_code = SCSI_STATUS_CODE_GOOD; + bytes_left=4; + dataout_timer->adjust(attotime::from_seconds(FORMAT_UNIT_TIMEOUT)); + break; + + case SCSI_CMD_SEARCH_DATA_EQUAL: + LOG(1,"SCSIBUS: Search_data_equaln"); + command_local=1; + bytes_left=0; + m_phase = SCSI_PHASE_STATUS; + m_status_code = SCSI_STATUS_CODE_GOOD; + break; + + case SCSI_CMD_READ_DEFECT: + LOG(1,"SCSIBUS: read defect list\n"); + command_local=1; + + buffer[0] = 0x00; + buffer[1] = command[2]; + buffer[3] = 0x00; // defect list len msb + buffer[4] = 0x00; // defect list len lsb + + bytes_left=4; + m_phase = SCSI_PHASE_DATAIN; + m_status_code = SCSI_STATUS_CODE_GOOD; + break; + + // write buffer + case SCSI_CMD_BUFFER_WRITE: + LOG(1,"SCSIBUS: write_buffer\n"); + command_local=1; + bytes_left=(command[7]<<8)+command[8]; + m_phase = SCSI_PHASE_DATAOUT; + m_status_code = SCSI_STATUS_CODE_GOOD; + break; + + // read buffer + case SCSI_CMD_BUFFER_READ: + LOG(1,"SCSIBUS: read_buffer\n"); + command_local=1; + bytes_left = (command[7]<<8) + command[8]; + m_phase = SCSI_PHASE_DATAIN; + m_status_code = SCSI_STATUS_CODE_GOOD; + break; + } + + + // Check for locally executed command, if not then pass it on + // to the disk driver + if (!command_local) + { + SetCommand(command, cmd_idx); + ExecCommand(); + GetLength(&bytes_left); + data_idx=0; + } + + scsi_change_phase(m_phase); + + LOG(1,"SCSIBUS:bytes_left=%02X data_idx=%02X\n",bytes_left,data_idx); + + // This is correct as we need to read from disk for commands other than just read data + if ((m_phase == SCSI_PHASE_DATAIN) && (!command_local)) + scsibus_read_data(); +} + +UINT8 scsihle_device::scsibus_driveno(UINT8 drivesel) +{ + switch (drivesel) + { + case 0x01: return 0; + case 0x02: return 1; + case 0x04: return 2; + case 0x08: return 3; + case 0x10: return 4; + case 0x20: return 5; + case 0x40: return 6; + case 0x80: return 7; + default: return 0; + } +} + +void scsihle_device::scsi_change_phase(UINT8 newphase) +{ + LOG(1,"scsi_change_phase() from=%s, to=%s\n",phasenames[m_phase],phasenames[newphase]); + + m_phase=newphase; + cmd_idx=0; + data_idx=0; + + switch(m_phase) + { + case SCSI_PHASE_BUS_FREE: + output_bsy(0); + // sel + output_cd(0); + output_io(0); + output_msg(0); + output_req(0); + // ack + // atn + // rst + data_out(0); + LOG(1,"SCSIBUS: done\n\n"); + break; + + case SCSI_PHASE_COMMAND: + output_cd(1); + output_io(0); + output_msg(0); + scsi_out_req_delay(1); + data_out(0); + LOG(1,"\nSCSIBUS: Command begin\n"); + break; + + case SCSI_PHASE_DATAOUT: + output_cd(0); + output_io(0); + output_msg(0); + scsi_out_req_delay(1); + data_out(0); + break; + + case SCSI_PHASE_DATAIN: + output_cd(0); + output_io(1); + output_msg(0); + scsi_out_req_delay(1); + break; + + case SCSI_PHASE_STATUS: + output_cd(1); + output_io(1); + output_msg(0); + scsi_out_req_delay(1); + data_out(m_status_code); + break; + + case SCSI_PHASE_MESSAGE_OUT: + output_cd(1); + output_io(0); + output_msg(1); + scsi_out_req_delay(1); + data_out(0); + break; + + case SCSI_PHASE_MESSAGE_IN: + output_cd(1); + output_io(1); + output_msg(1); + scsi_out_req_delay(1); + data_out(0); // no errors for the time being ! + break; + } +} + +WRITE_LINE_MEMBER( scsihle_device::input_sel ) +{ +// printf( "sel %d %d %02x\n", state, m_phase, m_input_data ); + switch (m_phase) + { + case SCSI_PHASE_BUS_FREE: + // Note this assumes we only have one initiator and therefore + // only one line active. + if (scsibus_driveno(m_input_data) == scsiID) + { + void *hdfile = NULL; + // Check to see if device had image file mounted, if not, do not set busy, + // and stay busfree. + GetDevice(&hdfile); + if (hdfile != NULL) + { + if (!state) + { + scsi_change_phase(SCSI_PHASE_COMMAND); + } + else + { + sel_timer->adjust(attotime::from_nsec(BSY_DELAY_NS),1); + } + } + } + break; + } +} + +WRITE_LINE_MEMBER( scsihle_device::input_ack ) +{ + switch (m_phase) + { + case SCSI_PHASE_COMMAND: + if (!state) + { + command[ cmd_idx++ ] = m_input_data; + + // If the command is ready go and execute it + if (cmd_idx == get_scsi_cmd_len(command[0])) + { + scsibus_exec_command(); + } + else + { + scsi_out_req_delay(1); + } + } + else + { + scsi_out_req_delay(0); + } + break; + + case SCSI_PHASE_DATAIN: + if (!state) + { + // check to see if we have reached the end of the block buffer + // and that there is more data to read from the scsi disk + if (data_idx == m_sector_bytes && bytes_left > 0) + { + scsibus_read_data(); + scsi_out_req_delay(1); + } + else if (data_idx == data_last && bytes_left == 0) + { + scsi_change_phase(SCSI_PHASE_STATUS); + } + else + { + data_out(buffer[data_idx++]); + scsi_out_req_delay(1); + } + } + else + { + scsi_out_req_delay(0); + } + break; + + case SCSI_PHASE_DATAOUT: + if (!state) + { + //LOG(1,"SCSIBUS:bytes_left=%02X data_idx=%02X\n",bytes_left,data_idx); + buffer[data_idx++] = m_input_data; + + if (IS_COMMAND(SCSI_CMD_FORMAT_UNIT)) + { + // If we have the first byte, then cancel the dataout timout + if (data_idx == 1) + dataout_timer->adjust(attotime::never); + + // When we have the first 3 bytes, calculate how many more are in the + // bad block list. + if (data_idx == 3) + { + bytes_left += ((buffer[2]<<8) + buffer[3]); + LOG(1, "format_unit reading an extra %d bytes\n", bytes_left - 4); + dump_data_bytes(4); + } + } + + // If the data buffer is full flush it to the SCSI disk + + data_last = (bytes_left >= m_sector_bytes) ? m_sector_bytes : bytes_left; + + if (data_idx == data_last) + scsibus_write_data(); + + if (data_idx == 0 && bytes_left == 0) + { + scsi_change_phase(SCSI_PHASE_STATUS); + } + else + { + scsi_out_req_delay(1); + } + } + else + { + scsi_out_req_delay(0); + } + break; + + case SCSI_PHASE_STATUS: + if (!state) + { + if (cmd_idx > 0) + { + scsi_change_phase(SCSI_PHASE_MESSAGE_IN); + } + else + { + scsi_out_req_delay(1); + } + } + else + { + cmd_idx++; + scsi_out_req_delay(0); + } + break; + + case SCSI_PHASE_MESSAGE_IN: + if (!state) + { + if (cmd_idx > 0) + { + if (is_linked) + scsi_change_phase(SCSI_PHASE_COMMAND); + else + scsi_change_phase(SCSI_PHASE_BUS_FREE); + } + else + { + scsi_out_req_delay(1); + } + } + else + { + cmd_idx++; + scsi_out_req_delay(0); + } + break; + } +} + +WRITE_LINE_MEMBER( scsihle_device::input_rst ) +{ + if (state) + { + scsi_change_phase(SCSI_PHASE_BUS_FREE); + cmd_idx = 0; + data_idx = 0; + is_linked = 0; + } +} + +// get the length of a SCSI command based on it's command byte type +int scsihle_device::get_scsi_cmd_len(int cbyte) +{ + int group; + + group = (cbyte>>5) & 7; + + if (group == 0 || group == 3 || group == 6 || group == 7) return 6; + if (group == 1 || group == 2) return 10; + if (group == 5) return 12; + + fatalerror("scsihle: Unknown SCSI command group %d, command byte=%02X\n", group,cbyte); + + // never executed + //return 6; +} |