/* * scsi.cpp - SCSI Manager * * Basilisk II (C) 1997-2008 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * SEE ALSO * Inside Macintosh: Devices, chapter 3 "SCSI Manager" * Technote DV 24: "Fear No SCSI" */ #include #include #include "sysdeps.h" #include "cpu_emulation.h" #include "main.h" #include "user_strings.h" #include "scsi.h" #define DEBUG 0 #include "debug.h" // Error codes enum { scCommErr = 2, scArbNBErr, scBadParmsErr, scPhaseErr, scCompareErr, scMgrBusyErr, scSequenceErr, scBusTOErr, scComplPhaseErr }; // TIB opcodes enum { scInc = 1, scNoInc, scAdd, scMove, scLoop, scNop, scStop, scComp }; // Logical SCSI phases enum { PH_FREE, // Bus free PH_ARBITRATED, // Bus arbitrated (after SCSIGet()) PH_SELECTED, // Target selected (after SCSISelect()) PH_TRANSFER // Command sent (after SCSICmd()) }; // Global variables static int target_id; // ID of active target static int phase; // Logical SCSI phase static uint16 fake_status; // Faked 5830 status word static bool reading; // Flag: reading from device const int SG_TABLE_SIZE = 1024; static int sg_index; // Index of first unused entry in S/G table static uint8 *sg_ptr[SG_TABLE_SIZE]; // Scatter/gather table data pointer (host address space) static uint32 sg_len[SG_TABLE_SIZE]; // Scatter/gather table data length static uint32 sg_total_length; // Total data length /* * Execute TIB, constructing S/G table */ static int16 exec_tib(uint32 tib) { for (;;) { // Read next opcode and parameters uint16 cmd = ReadMacInt16(tib); tib += 2; uint32 ptr = ReadMacInt32(tib); tib += 4; uint32 len = ReadMacInt32(tib); tib += 4; #if DEBUG const char *cmd_str; switch (cmd) { case scInc: cmd_str = "INC "; break; case scNoInc: cmd_str = "NOINC"; break; case scAdd: cmd_str = "ADD "; break; case scMove: cmd_str = "MOVE "; break; case scLoop: cmd_str = "LOOP "; break; case scNop: cmd_str = "NOP "; break; case scStop: cmd_str = "STOP "; break; case scComp: cmd_str = "COMP "; break; default: cmd_str = "??? "; break; } D(bug(" %s(%d) %08x %d\n", cmd_str, cmd, ptr, len)); #endif switch (cmd) { case scInc: WriteMacInt32(tib - 8, ptr + len); // fall through to scNoInc case scNoInc: if ((sg_index > 0) && (Mac2HostAddr(ptr) == sg_ptr[sg_index-1] + sg_len[sg_index-1])) { sg_len[sg_index-1] += len; // Merge to previous entry } else { if (sg_index == SG_TABLE_SIZE) { ErrorAlert(GetString(STR_SCSI_SG_FULL_ERR)); return -108; } sg_ptr[sg_index] = Mac2HostAddr(ptr); // Create new entry sg_len[sg_index] = len; sg_index++; } sg_total_length += len; break; case scAdd: WriteMacInt32(ptr, ReadMacInt32(ptr) + len); break; case scMove: WriteMacInt32(len, ReadMacInt32(ptr)); break; case scLoop: WriteMacInt32(tib - 4, len - 1); if (len - 1 > 0) tib += (int32)ptr - 10; break; case scNop: break; case scStop: return 0; case scComp: printf("WARNING: Unimplemented scComp opcode\n"); return scCompareErr; default: printf("FATAL: Unrecognized TIB opcode %d\n", cmd); return scBadParmsErr; } } } /* * Reset SCSI bus */ int16 SCSIReset(void) { D(bug("SCSIReset\n")); phase = PH_FREE; fake_status = 0x0000; // Bus free sg_index = 0; target_id = 8; return 0; } /* * Arbitrate bus */ int16 SCSIGet(void) { D(bug("SCSIGet\n")); if (phase != PH_FREE) return scMgrBusyErr; phase = PH_ARBITRATED; fake_status = 0x0040; // Bus busy reading = false; sg_index = 0; // Flush S/G table sg_total_length = 0; return 0; } /* * Select SCSI device */ int16 SCSISelect(int id) { D(bug("SCSISelect %d\n", id)); if (phase != PH_ARBITRATED) return scSequenceErr; // ID valid? if (id < 0 || id > 7) return scBadParmsErr; // Target present? target_id = id; if (!scsi_is_target_present(target_id)) { phase = PH_FREE; fake_status = 0x0000; // Bus free return scCommErr; } phase = PH_SELECTED; fake_status = 0x006a; // Target selected, command phase return 0; } /* * Send SCSI command */ int16 SCSICmd(int cmd_length, uint8 *cmd) { #if DEBUG switch (cmd_length) { case 6: D(bug("SCSICmd len 6, cmd %02x %02x %02x %02x %02x %02x\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5])); break; case 10: D(bug("SCSICmd len 10, cmd %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9])); break; case 12: D(bug("SCSICmd len 12, cmd %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9], cmd[10], cmd[11])); break; default: D(bug("SCSICmd bogus length %d\n", cmd_length)); break; } #endif if (phase != PH_SELECTED) return scPhaseErr; // Commdn length valid? if (cmd_length != 6 && cmd_length != 10 && cmd_length != 12) return scBadParmsErr; // Set command, extract LUN scsi_set_cmd(cmd_length, cmd); // Extract LUN, set target if (!scsi_set_target(target_id, (cmd[1] >> 5) & 7)) { phase = PH_FREE; fake_status = 0x0000; // Bus free return scCommErr; } phase = PH_TRANSFER; fake_status = 0x006e; // Target selected, data phase return 0; } /* * Read data */ int16 SCSIRead(uint32 tib) { D(bug("SCSIRead TIB %08lx\n", tib)); if (phase != PH_TRANSFER) return scPhaseErr; // Execute TIB, fill S/G table reading = true; return exec_tib(tib); } /* * Write data */ int16 SCSIWrite(uint32 tib) { D(bug("SCSIWrite TIB %08lx\n", tib)); if (phase != PH_TRANSFER) return scPhaseErr; // Execute TIB, fill S/G table return exec_tib(tib); } /* * Wait for command completion (we're actually doing everything in here...) */ int16 SCSIComplete(uint32 timeout, uint32 message, uint32 stat) { D(bug("SCSIComplete wait %d, msg %08lx, stat %08lx\n", timeout, message, stat)); WriteMacInt16(message, 0); if (phase != PH_TRANSFER) return scPhaseErr; // Send command, process S/G table uint16 scsi_stat = 0; bool success = scsi_send_cmd(sg_total_length, reading, sg_index, sg_ptr, sg_len, &scsi_stat, timeout); WriteMacInt16(stat, scsi_stat); // Complete command phase = PH_FREE; fake_status = 0x0000; // Bus free return success ? 0 : scCommErr; } /* * Get bus status */ uint16 SCSIStat(void) { D(bug("SCSIStat returns %04x\n", fake_status)); return fake_status; } /* * SCSI Manager busy? */ int16 SCSIMgrBusy(void) { // D(bug("SCSIMgrBusy\n")); return phase != PH_FREE; }