|
|
@@ -1,1371 +1,1371 @@
|
|
|
-// Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
|
|
|
-//
|
|
|
-// This file is part of SCSI2SD.
|
|
|
-//
|
|
|
-// SCSI2SD 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 3 of the License, or
|
|
|
-// (at your option) any later version.
|
|
|
-//
|
|
|
-// SCSI2SD 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 SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
-
|
|
|
-#include "scsi.h"
|
|
|
-#include "scsiPhy.h"
|
|
|
-#include "config.h"
|
|
|
-#include "diagnostic.h"
|
|
|
-#include "disk.h"
|
|
|
-#include "inquiry.h"
|
|
|
-#include "led.h"
|
|
|
-#include "mode.h"
|
|
|
-#include "scsi2sd_time.h"
|
|
|
-#include "bsp.h"
|
|
|
-#include "cdrom.h"
|
|
|
-//#include "debug.h"
|
|
|
-#include "tape.h"
|
|
|
-#include "mo.h"
|
|
|
-#include "vendor.h"
|
|
|
-
|
|
|
-#include <string.h>
|
|
|
-
|
|
|
-// Global SCSI device state.
|
|
|
-ScsiDevice scsiDev S2S_DMA_ALIGN;
|
|
|
-
|
|
|
-static void enter_SelectionPhase(void);
|
|
|
-static void process_SelectionPhase(void);
|
|
|
-static void enter_MessageIn(uint8_t message);
|
|
|
-static void enter_Status(uint8_t status);
|
|
|
-static void enter_DataIn(int len);
|
|
|
-static void process_DataIn(void);
|
|
|
-static void process_DataOut(void);
|
|
|
-static void process_Command(void);
|
|
|
-
|
|
|
-static void doReserveRelease(void);
|
|
|
-
|
|
|
-void enter_BusFree()
|
|
|
-{
|
|
|
- // This delay probably isn't needed for most SCSI hosts, but it won't
|
|
|
- // hurt either. It's possible some of the samplers needed this delay.
|
|
|
- if (scsiDev.compatMode < COMPAT_SCSI2)
|
|
|
- {
|
|
|
- s2s_delay_us(2);
|
|
|
- }
|
|
|
-
|
|
|
-#if 0
|
|
|
- if (scsiDev.status != GOOD)// && isDebugEnabled())
|
|
|
- {
|
|
|
- // We want to capture debug information for failure cases.
|
|
|
- s2s_delay_ms(80);
|
|
|
- }
|
|
|
-#endif
|
|
|
-
|
|
|
-
|
|
|
- scsiEnterBusFree();
|
|
|
-
|
|
|
- // Wait for the initiator to cease driving signals
|
|
|
- // Bus settle delay + bus clear delay = 1200ns
|
|
|
- // Just waiting the clear delay is sufficient.
|
|
|
- s2s_delay_ns(800);
|
|
|
-
|
|
|
- s2s_ledOff();
|
|
|
- scsiDev.phase = BUS_FREE;
|
|
|
- scsiDev.selFlag = 0;
|
|
|
-}
|
|
|
-
|
|
|
-static void enter_MessageIn(uint8_t message)
|
|
|
-{
|
|
|
- scsiDev.msgIn = message;
|
|
|
- scsiDev.phase = MESSAGE_IN;
|
|
|
-}
|
|
|
-
|
|
|
-int process_MessageIn(int releaseBusFree)
|
|
|
-{
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
- scsiWriteByte(scsiDev.msgIn);
|
|
|
-
|
|
|
- if (unlikely(scsiDev.atnFlag))
|
|
|
- {
|
|
|
- // If there was a parity error, we go
|
|
|
- // back to MESSAGE_OUT first, get out parity error message, then come
|
|
|
- // back here.
|
|
|
- return 0;
|
|
|
- }
|
|
|
- else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) ||
|
|
|
- (scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG))
|
|
|
- {
|
|
|
- // Go back to the command phase and start again.
|
|
|
- scsiDev.phase = COMMAND;
|
|
|
- scsiDev.dataPtr = 0;
|
|
|
- scsiDev.savedDataPtr = 0;
|
|
|
- scsiDev.dataLen = 0;
|
|
|
- scsiDev.status = GOOD;
|
|
|
- transfer.blocks = 0;
|
|
|
- transfer.currentBlock = 0;
|
|
|
- return 0;
|
|
|
- }
|
|
|
- else if (releaseBusFree) /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/
|
|
|
- {
|
|
|
- enter_BusFree();
|
|
|
- return 1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return 1;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static void messageReject()
|
|
|
-{
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
- scsiWriteByte(MSG_REJECT);
|
|
|
-}
|
|
|
-
|
|
|
-static void enter_Status(uint8_t status)
|
|
|
-{
|
|
|
- scsiDev.status = status;
|
|
|
- scsiDev.phase = STATUS;
|
|
|
-
|
|
|
- scsiDev.lastStatus = scsiDev.status;
|
|
|
- scsiDev.lastSense = scsiDev.target->sense.code;
|
|
|
- scsiDev.lastSenseASC = scsiDev.target->sense.asc;
|
|
|
-}
|
|
|
-
|
|
|
-void process_Status()
|
|
|
-{
|
|
|
- scsiEnterPhase(STATUS);
|
|
|
-
|
|
|
- uint8_t message;
|
|
|
-
|
|
|
- uint8_t control = scsiDev.cdb[scsiDev.cdbLen - 1];
|
|
|
-
|
|
|
- if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
- {
|
|
|
- // All commands have a control byte, except 0xC0
|
|
|
- if (scsiDev.cdb[0] == 0xC0)
|
|
|
- {
|
|
|
- control = 0;
|
|
|
- }
|
|
|
-
|
|
|
- // OMTI non-standard LINK control
|
|
|
- if (control & 0x01)
|
|
|
- {
|
|
|
- scsiDev.phase = COMMAND;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ((scsiDev.status == GOOD) && (control & 0x01) &&
|
|
|
- scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- // Linked command.
|
|
|
- scsiDev.status = INTERMEDIATE;
|
|
|
- if (control & 0x02)
|
|
|
- {
|
|
|
- message = MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- message = MSG_LINKED_COMMAND_COMPLETE;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- message = MSG_COMMAND_COMPLETE;
|
|
|
- }
|
|
|
-
|
|
|
- if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- // More non-standardness. Expects 2 status bytes (really status + msg)
|
|
|
- // 00 d 000 err 0
|
|
|
- // d == disk number
|
|
|
- // ERR = 1 if error.
|
|
|
- if (scsiDev.status == GOOD)
|
|
|
- {
|
|
|
- scsiWriteByte(scsiDev.cdb[1] & 0x20);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiWriteByte((scsiDev.cdb[1] & 0x20) | 0x2);
|
|
|
- }
|
|
|
- s2s_delay_us(10); // Seems to need a delay before changing phase bits.
|
|
|
- }
|
|
|
- else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
- {
|
|
|
- scsiDev.status |= (scsiDev.target->targetId & 0x03) << 5;
|
|
|
- scsiWriteByte(scsiDev.status);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiWriteByte(scsiDev.status);
|
|
|
- }
|
|
|
-
|
|
|
- scsiDev.lastStatus = scsiDev.status;
|
|
|
- scsiDev.lastSense = scsiDev.target->sense.code;
|
|
|
- scsiDev.lastSenseASC = scsiDev.target->sense.asc;
|
|
|
-
|
|
|
- // Command Complete occurs AFTER a valid status has been
|
|
|
- // sent. then we go bus-free.
|
|
|
- enter_MessageIn(message);
|
|
|
-}
|
|
|
-
|
|
|
-static void enter_DataIn(int len)
|
|
|
-{
|
|
|
- scsiDev.dataLen = len;
|
|
|
- scsiDev.phase = DATA_IN;
|
|
|
-}
|
|
|
-
|
|
|
-static void process_DataIn()
|
|
|
-{
|
|
|
- uint32_t len;
|
|
|
-
|
|
|
- if (scsiDev.dataLen > sizeof(scsiDev.data))
|
|
|
- {
|
|
|
- scsiDev.dataLen = sizeof(scsiDev.data);
|
|
|
- }
|
|
|
-
|
|
|
- len = scsiDev.dataLen - scsiDev.dataPtr;
|
|
|
- if (len > 0)
|
|
|
- {
|
|
|
- scsiEnterPhase(DATA_IN);
|
|
|
- scsiWrite(scsiDev.data + scsiDev.dataPtr, len);
|
|
|
- scsiDev.dataPtr += len;
|
|
|
- }
|
|
|
-
|
|
|
- if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
|
|
|
- (transfer.currentBlock == transfer.blocks))
|
|
|
- {
|
|
|
- enter_Status(GOOD);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static void process_DataOut()
|
|
|
-{
|
|
|
- uint32_t len;
|
|
|
-
|
|
|
- if (scsiDev.dataLen > sizeof(scsiDev.data))
|
|
|
- {
|
|
|
- scsiDev.dataLen = sizeof(scsiDev.data);
|
|
|
- }
|
|
|
-
|
|
|
- len = scsiDev.dataLen - scsiDev.dataPtr;
|
|
|
- if (len > 0)
|
|
|
- {
|
|
|
- scsiEnterPhase(DATA_OUT);
|
|
|
-
|
|
|
- int parityError = 0;
|
|
|
- scsiRead(scsiDev.data + scsiDev.dataPtr, len, &parityError);
|
|
|
- scsiDev.dataPtr += len;
|
|
|
-
|
|
|
- if (parityError &&
|
|
|
- (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
- {
|
|
|
- scsiDev.target->sense.code = ABORTED_COMMAND;
|
|
|
- scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
|
|
|
- (transfer.currentBlock == transfer.blocks))
|
|
|
- {
|
|
|
- if (scsiDev.postDataOutHook != NULL)
|
|
|
- {
|
|
|
- scsiDev.postDataOutHook();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- enter_Status(GOOD);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static const uint8_t CmdGroupBytes[] = {
|
|
|
- 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,16,16,16,16,16,16,16,16,
|
|
|
- 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
|
|
- 16,16,16,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
|
|
|
- 12,12,12,12,12,12,12,12,12,12,12,12,12,12,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
- 10,10,10,10,10,10,10,10,10,10,10,10,10,10
|
|
|
-};
|
|
|
-
|
|
|
-static void process_Command()
|
|
|
-{
|
|
|
- uint8_t command;
|
|
|
- uint8_t control;
|
|
|
-
|
|
|
- scsiEnterPhase(COMMAND);
|
|
|
-
|
|
|
- memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);
|
|
|
- int parityError = 0;
|
|
|
- scsiRead(scsiDev.cdb, 6, &parityError);
|
|
|
-
|
|
|
- // Handle Atari ST ICD extended commands
|
|
|
- if (scsiDev.cdb[0] == 0x1F)
|
|
|
- {
|
|
|
- scsiDev.cdb[0] = scsiDev.cdb[1];
|
|
|
- scsiDev.cdb[1] = scsiDev.cdb[2];
|
|
|
- scsiDev.cdb[2] = scsiDev.cdb[3];
|
|
|
- scsiDev.cdb[3] = scsiDev.cdb[4];
|
|
|
- scsiDev.cdb[4] = scsiDev.cdb[5];
|
|
|
- scsiDev.cdb[5] = scsiReadByte();
|
|
|
- }
|
|
|
-
|
|
|
- scsiDev.cdbLen = CmdGroupBytes[scsiDev.cdb[0]];
|
|
|
- if (parityError &&
|
|
|
- (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
- {
|
|
|
- // Don't try and read more bytes, as we cannot be sure what group
|
|
|
- // the command should be.
|
|
|
- }
|
|
|
- else if (scsiDev.cdbLen - 6 > 0)
|
|
|
- {
|
|
|
- scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6, &parityError);
|
|
|
- }
|
|
|
- command = scsiDev.cdb[0];
|
|
|
-
|
|
|
- // Prefer LUN's set by IDENTIFY messages for newer hosts.
|
|
|
- if (scsiDev.lun < 0)
|
|
|
- {
|
|
|
- if (command == 0xE0 || command == 0xE4) // XEBEC s1410
|
|
|
- {
|
|
|
- scsiDev.lun = 0;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.lun = scsiDev.cdb[1] >> 5;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // For Philips P2000C with Xebec S1410 SASI/MFM adapter
|
|
|
- // http://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf
|
|
|
- if ((scsiDev.lun > 0) && (scsiDev.boardCfg.flags & S2S_CFG_MAP_LUNS_TO_IDS))
|
|
|
- {
|
|
|
- int tgtIndex;
|
|
|
- for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
|
|
|
- {
|
|
|
- if (scsiDev.targets[tgtIndex].targetId == scsiDev.lun)
|
|
|
- {
|
|
|
- scsiDev.target = &scsiDev.targets[tgtIndex];
|
|
|
- scsiDev.lun = 0;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- control = scsiDev.cdb[scsiDev.cdbLen - 1];
|
|
|
-
|
|
|
- scsiDev.cmdCount++;
|
|
|
- const S2S_TargetCfg* cfg = scsiDev.target->cfg;
|
|
|
-
|
|
|
- if (unlikely(scsiDev.resetFlag))
|
|
|
- {
|
|
|
- // Don't log bogus commands
|
|
|
- scsiDev.cmdCount--;
|
|
|
- memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));
|
|
|
- return;
|
|
|
- }
|
|
|
- else if (parityError &&
|
|
|
- (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
- {
|
|
|
- scsiDev.target->sense.code = ABORTED_COMMAND;
|
|
|
- scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- else if ((control & 0x02) && ((control & 0x01) == 0) &&
|
|
|
- // used for head step options on xebec.
|
|
|
- likely(scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC))
|
|
|
- {
|
|
|
- // FLAG set without LINK flag.
|
|
|
- scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
- scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- else if (command == 0x12)
|
|
|
- {
|
|
|
- s2s_scsiInquiry();
|
|
|
- }
|
|
|
- else if (command == 0x03)
|
|
|
- {
|
|
|
- // REQUEST SENSE
|
|
|
- uint32_t allocLength = scsiDev.cdb[4];
|
|
|
-
|
|
|
- if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- // Completely non-standard
|
|
|
- allocLength = 4;
|
|
|
- if (scsiDev.target->sense.code == NO_SENSE)
|
|
|
- scsiDev.data[0] = 0;
|
|
|
- else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
|
|
|
- scsiDev.data[0] = 0x20; // Illegal command
|
|
|
- else if (scsiDev.target->sense.code == NOT_READY)
|
|
|
- scsiDev.data[0] = 0x04; // Drive not ready
|
|
|
- else
|
|
|
- scsiDev.data[0] = 0x11; // Uncorrectable data error
|
|
|
-
|
|
|
- scsiDev.data[1] = (scsiDev.cdb[1] & 0x20) | ((transfer.lba >> 16) & 0x1F);
|
|
|
- scsiDev.data[2] = transfer.lba >> 8;
|
|
|
- scsiDev.data[3] = transfer.lba;
|
|
|
- }
|
|
|
- else if (cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
- {
|
|
|
- // The response is completely non-standard.
|
|
|
- if (likely(allocLength > 12))
|
|
|
- allocLength = 12;
|
|
|
- else if (unlikely(allocLength < 4))
|
|
|
- allocLength = 4;
|
|
|
- if (cfg->deviceType != S2S_CFG_SEQUENTIAL)
|
|
|
- allocLength = 4;
|
|
|
- memset(scsiDev.data, 0, allocLength);
|
|
|
- if (scsiDev.target->sense.code == NO_SENSE)
|
|
|
- {
|
|
|
- // Nothing to report.
|
|
|
- }
|
|
|
- else if (scsiDev.target->sense.code == UNIT_ATTENTION &&
|
|
|
- cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
- {
|
|
|
- scsiDev.data[0] = 0x10; // Tape exception
|
|
|
- }
|
|
|
- else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
|
|
|
- {
|
|
|
- if (scsiDev.target->sense.asc == LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE)
|
|
|
- {
|
|
|
- if (cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
- scsiDev.data[0] = 0x10; // Tape exception
|
|
|
- else
|
|
|
- scsiDev.data[0] = 0x21; // Illegal Parameters
|
|
|
- }
|
|
|
- else if (scsiDev.target->sense.asc == INVALID_COMMAND_OPERATION_CODE)
|
|
|
- {
|
|
|
- scsiDev.data[0] = 0x20; // Invalid Command
|
|
|
- }
|
|
|
- }
|
|
|
- else if (scsiDev.target->sense.code == NOT_READY)
|
|
|
- {
|
|
|
- scsiDev.data[0] = 0x04; // Drive not ready
|
|
|
- }
|
|
|
- else if (scsiDev.target->sense.code == BLANK_CHECK)
|
|
|
- {
|
|
|
- scsiDev.data[0] = 0x10; // Tape exception
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.data[0] = 0x11; // Uncorrectable data error
|
|
|
- }
|
|
|
- scsiDev.data[1] = (scsiDev.cdb[1] & 0x60) | ((transfer.lba >> 16) & 0x1F);
|
|
|
- scsiDev.data[2] = transfer.lba >> 8;
|
|
|
- scsiDev.data[3] = transfer.lba;
|
|
|
- if (cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
- {
|
|
|
- // For the tape drive there are 8 extra sense bytes.
|
|
|
- if (scsiDev.target->sense.code == BLANK_CHECK)
|
|
|
- scsiDev.data[11] = 0x88; // End of data recorded on the tape
|
|
|
- else if (scsiDev.target->sense.code == UNIT_ATTENTION)
|
|
|
- scsiDev.data[5] = 0x81; // Power On Reset occurred
|
|
|
- else if (scsiDev.target->sense.code == ILLEGAL_REQUEST &&
|
|
|
- scsiDev.target->sense.asc == LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE)
|
|
|
- scsiDev.data[4] = 0x81; // File Mark detected
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // As specified by the SASI and SCSI1 standard.
|
|
|
- // Newer initiators won't be specifying 0 anyway.
|
|
|
- if (allocLength == 0) allocLength = 4;
|
|
|
-
|
|
|
- memset(scsiDev.data, 0, 256); // Max possible alloc length
|
|
|
- scsiDev.data[0] = 0xF0;
|
|
|
- scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;
|
|
|
-
|
|
|
- scsiDev.data[3] = transfer.lba >> 24;
|
|
|
- scsiDev.data[4] = transfer.lba >> 16;
|
|
|
- scsiDev.data[5] = transfer.lba >> 8;
|
|
|
- scsiDev.data[6] = transfer.lba;
|
|
|
-
|
|
|
- // Additional bytes if there are errors to report
|
|
|
- scsiDev.data[7] = 10; // additional length
|
|
|
- scsiDev.data[12] = scsiDev.target->sense.asc >> 8;
|
|
|
- scsiDev.data[13] = scsiDev.target->sense.asc;
|
|
|
- }
|
|
|
-
|
|
|
- // Silently truncate results. SCSI-2 spec 8.2.14.
|
|
|
- enter_DataIn(allocLength);
|
|
|
-
|
|
|
- // This is a good time to clear out old sense information.
|
|
|
- scsiDev.target->sense.code = NO_SENSE;
|
|
|
- scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
- }
|
|
|
- // Some old SCSI drivers do NOT properly support
|
|
|
- // unitAttention. eg. the Mac Plus would trigger a SCSI reset
|
|
|
- // on receiving the unit attention response on boot, thus
|
|
|
- // triggering another unit attention condition.
|
|
|
- else if (scsiDev.target->unitAttention &&
|
|
|
- (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))
|
|
|
- {
|
|
|
- scsiDev.target->sense.code = UNIT_ATTENTION;
|
|
|
- scsiDev.target->sense.asc = scsiDev.target->unitAttention;
|
|
|
-
|
|
|
- // If initiator doesn't do REQUEST SENSE for the next command, then
|
|
|
- // data is lost.
|
|
|
- scsiDev.target->unitAttention = 0;
|
|
|
-
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- else if (scsiDev.lun)
|
|
|
- {
|
|
|
- scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
- scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- else if (command == 0x17 || command == 0x16)
|
|
|
- {
|
|
|
- doReserveRelease();
|
|
|
- }
|
|
|
- else if ((scsiDev.target->reservedId >= 0) &&
|
|
|
- (scsiDev.target->reservedId != scsiDev.initiatorId))
|
|
|
- {
|
|
|
- enter_Status(CONFLICT);
|
|
|
- }
|
|
|
- // Handle odd device types first that may override basic read and
|
|
|
- // write commands. Will fall-through to generic disk handling.
|
|
|
- else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||
|
|
|
- ((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||
|
|
|
- ((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
|
|
|
- {
|
|
|
- // Already handled.
|
|
|
- }
|
|
|
- else if (scsiDiskCommand())
|
|
|
- {
|
|
|
- // Already handled.
|
|
|
- // check for the performance-critical read/write
|
|
|
- // commands ASAP.
|
|
|
- }
|
|
|
- else if (command == 0x1C)
|
|
|
- {
|
|
|
- scsiReceiveDiagnostic();
|
|
|
- }
|
|
|
- else if (command == 0x1D)
|
|
|
- {
|
|
|
- scsiSendDiagnostic();
|
|
|
- }
|
|
|
- else if (command == 0x3B)
|
|
|
- {
|
|
|
- scsiWriteBuffer();
|
|
|
- }
|
|
|
- else if (command == 0x0f &&
|
|
|
- scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- scsiWriteSectorBuffer();
|
|
|
- }
|
|
|
- else if (command == 0x3C)
|
|
|
- {
|
|
|
- scsiReadBuffer();
|
|
|
- }
|
|
|
- else if (!scsiModeCommand() && !scsiVendorCommand())
|
|
|
- {
|
|
|
- scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
- scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
-
|
|
|
- // Successful
|
|
|
- if (scsiDev.phase == COMMAND) // No status set, and not in DATA_IN
|
|
|
- {
|
|
|
- enter_Status(GOOD);
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-static void doReserveRelease()
|
|
|
-{
|
|
|
- int extentReservation = scsiDev.cdb[1] & 1;
|
|
|
- int thirdPty = scsiDev.cdb[1] & 0x10;
|
|
|
- int thirdPtyId = (scsiDev.cdb[1] >> 1) & 0x7;
|
|
|
- uint8_t command = scsiDev.cdb[0];
|
|
|
-
|
|
|
- int canRelease =
|
|
|
- (!thirdPty && (scsiDev.initiatorId == scsiDev.target->reservedId)) ||
|
|
|
- (thirdPty &&
|
|
|
- (scsiDev.target->reserverId == scsiDev.initiatorId) &&
|
|
|
- (scsiDev.target->reservedId == thirdPtyId)
|
|
|
- );
|
|
|
-
|
|
|
- if (extentReservation)
|
|
|
- {
|
|
|
- // Not supported.
|
|
|
- scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
- scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
|
- enter_Status(CHECK_CONDITION);
|
|
|
- }
|
|
|
- else if (command == 0x17) // release
|
|
|
- {
|
|
|
- if ((scsiDev.target->reservedId < 0) || canRelease)
|
|
|
- {
|
|
|
- scsiDev.target->reservedId = -1;
|
|
|
- scsiDev.target->reserverId = -1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- enter_Status(CONFLICT);
|
|
|
- }
|
|
|
- }
|
|
|
- else // assume reserve.
|
|
|
- {
|
|
|
- if ((scsiDev.target->reservedId < 0) || canRelease)
|
|
|
- {
|
|
|
- scsiDev.target->reserverId = scsiDev.initiatorId;
|
|
|
- if (thirdPty)
|
|
|
- {
|
|
|
- scsiDev.target->reservedId = thirdPtyId;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.target->reservedId = scsiDev.initiatorId;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Already reserved by someone else!
|
|
|
- enter_Status(CONFLICT);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static uint32_t resetUntil = 0;
|
|
|
-
|
|
|
-static void scsiReset()
|
|
|
-{
|
|
|
- scsiDev.rstCount++;
|
|
|
- s2s_ledOff();
|
|
|
-
|
|
|
- scsiPhyReset();
|
|
|
-
|
|
|
- scsiDev.phase = BUS_FREE;
|
|
|
- scsiDev.atnFlag = 0;
|
|
|
- scsiDev.resetFlag = 0;
|
|
|
- scsiDev.selFlag = 0;
|
|
|
- scsiDev.lun = -1;
|
|
|
- scsiDev.compatMode = COMPAT_UNKNOWN;
|
|
|
-
|
|
|
- if (scsiDev.target)
|
|
|
- {
|
|
|
- if (scsiDev.target->unitAttention != POWER_ON_RESET)
|
|
|
- {
|
|
|
- scsiDev.target->unitAttention = SCSI_BUS_RESET;
|
|
|
- }
|
|
|
- scsiDev.target->reservedId = -1;
|
|
|
- scsiDev.target->reserverId = -1;
|
|
|
- scsiDev.target->sense.code = NO_SENSE;
|
|
|
- scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
- }
|
|
|
- scsiDev.target = NULL;
|
|
|
-
|
|
|
- for (int i = 0; i < S2S_MAX_TARGETS; ++i)
|
|
|
- {
|
|
|
- scsiDev.targets[i].syncOffset = 0;
|
|
|
- scsiDev.targets[i].syncPeriod = 0;
|
|
|
- }
|
|
|
- scsiDev.minSyncPeriod = 0;
|
|
|
-
|
|
|
- scsiDiskReset();
|
|
|
-
|
|
|
- scsiDev.postDataOutHook = NULL;
|
|
|
-
|
|
|
- scsiDev.sdUnderrunCount = 0;
|
|
|
-
|
|
|
- // Sleep to allow the bus to settle down a bit.
|
|
|
- // We must be ready again within the "Reset to selection time" of
|
|
|
- // 250ms.
|
|
|
- // There is no guarantee that the RST line will be negated by then.
|
|
|
- // NOTE: We could be connected and powered by USB for configuration,
|
|
|
- // in which case TERMPWR cannot be supplied, and reset will ALWAYS
|
|
|
- // be true. Therefore, the sleep here must be slow to avoid slowing
|
|
|
- // USB comms
|
|
|
- resetUntil = s2s_getTime_ms() + 2; // At least 1ms.
|
|
|
-}
|
|
|
-
|
|
|
-static void enter_SelectionPhase()
|
|
|
-{
|
|
|
- // Ignore stale versions of this flag, but ensure we know the
|
|
|
- // current value if the flag is still set.
|
|
|
- scsiDev.atnFlag = 0;
|
|
|
- scsiDev.dataPtr = 0;
|
|
|
- scsiDev.savedDataPtr = 0;
|
|
|
- scsiDev.dataLen = 0;
|
|
|
- scsiDev.status = GOOD;
|
|
|
- scsiDev.phase = SELECTION;
|
|
|
- scsiDev.lun = -1;
|
|
|
- scsiDev.discPriv = 0;
|
|
|
-
|
|
|
- scsiDev.initiatorId = -1;
|
|
|
- scsiDev.target = NULL;
|
|
|
-
|
|
|
- transfer.blocks = 0;
|
|
|
- transfer.currentBlock = 0;
|
|
|
-
|
|
|
- scsiDev.postDataOutHook = NULL;
|
|
|
-
|
|
|
- scsiDev.needSyncNegotiationAck = 0;
|
|
|
-}
|
|
|
-
|
|
|
-static void process_SelectionPhase()
|
|
|
-{
|
|
|
- // Selection delays.
|
|
|
- // Many SCSI1 samplers that use a 5380 chip need a delay of at least 1ms.
|
|
|
- // The Mac Plus boot-time (ie. rom code) selection abort time
|
|
|
- // is < 1ms and must have no delay (standard suggests 250ms abort time)
|
|
|
- // Most newer SCSI2 hosts don't care either way.
|
|
|
- if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- s2s_delay_ms(1); // Simply won't work if set to 0.
|
|
|
- }
|
|
|
- else if (scsiDev.boardCfg.selectionDelay == 255) // auto
|
|
|
- {
|
|
|
- if (scsiDev.compatMode < COMPAT_SCSI2)
|
|
|
- {
|
|
|
- s2s_delay_ms(1);
|
|
|
- }
|
|
|
- }
|
|
|
- else if (scsiDev.boardCfg.selectionDelay != 0)
|
|
|
- {
|
|
|
- s2s_delay_ms(scsiDev.boardCfg.selectionDelay);
|
|
|
- }
|
|
|
-
|
|
|
- uint8_t selStatus = *SCSI_STS_SELECTED;
|
|
|
- if ((selStatus == 0) && (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SEL_LATCH))
|
|
|
- {
|
|
|
- selStatus = scsiDev.selFlag;
|
|
|
- }
|
|
|
-
|
|
|
- int tgtIndex;
|
|
|
- TargetState* target = NULL;
|
|
|
- for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
|
|
|
- {
|
|
|
- if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))
|
|
|
- {
|
|
|
- target = &scsiDev.targets[tgtIndex];
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if ((target != NULL) && (selStatus & 0x40))
|
|
|
- {
|
|
|
- // We've been selected!
|
|
|
- // Assert BSY - Selection success!
|
|
|
- // must happen within 200us (Selection abort time) of seeing our
|
|
|
- // ID + SEL.
|
|
|
- // (Note: the initiator will be waiting the "Selection time-out delay"
|
|
|
- // for our BSY response, which is actually a very generous 250ms)
|
|
|
- *SCSI_CTRL_BSY = 1;
|
|
|
- s2s_ledOn();
|
|
|
-
|
|
|
- scsiDev.target = target;
|
|
|
-
|
|
|
- // Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says
|
|
|
- // move to MESSAGE OUT if ATN is true before we assert BSY.
|
|
|
- // The initiator should assert ATN with SEL.
|
|
|
- scsiDev.atnFlag = selStatus & 0x80;
|
|
|
-
|
|
|
-
|
|
|
- // Unit attention breaks many older SCSI hosts. Disable it completely
|
|
|
- // for SCSI-1 (and older) hosts, regardless of our configured setting.
|
|
|
- // Enable the compatability mode also as many SASI and SCSI1
|
|
|
- // controllers don't generate parity bits.
|
|
|
- if (!scsiDev.atnFlag)
|
|
|
- {
|
|
|
- target->unitAttention = 0;
|
|
|
- scsiDev.compatMode = COMPAT_SCSI1;
|
|
|
- }
|
|
|
- else if (!(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SCSI2))
|
|
|
- {
|
|
|
- scsiDev.compatMode = COMPAT_SCSI2_DISABLED;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.compatMode = COMPAT_SCSI2;
|
|
|
- }
|
|
|
-
|
|
|
- scsiDev.selCount++;
|
|
|
-
|
|
|
-
|
|
|
- // Save our initiator now that we're no longer in a time-critical
|
|
|
- // section.
|
|
|
- // SCSI1/SASI initiators may not set their own ID.
|
|
|
- scsiDev.initiatorId = (selStatus >> 3) & 0x7;
|
|
|
-
|
|
|
- // Wait until the end of the selection phase.
|
|
|
- uint32_t selTimerBegin = s2s_getTime_ms();
|
|
|
- while (likely(!scsiDev.resetFlag))
|
|
|
- {
|
|
|
- if (!scsiStatusSEL())
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
- else if (s2s_elapsedTime_ms(selTimerBegin) >= 10 &&
|
|
|
- scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
- {
|
|
|
- // XEBEC hosts may not bother releasing SEL at all until
|
|
|
- // just before the command ends.
|
|
|
- break;
|
|
|
- }
|
|
|
- else if (s2s_elapsedTime_ms(selTimerBegin) >= 250)
|
|
|
- {
|
|
|
- *SCSI_CTRL_BSY = 0;
|
|
|
- scsiDev.resetFlag = 1;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- scsiDev.phase = COMMAND;
|
|
|
- }
|
|
|
- else if (!selStatus)
|
|
|
- {
|
|
|
- scsiDev.phase = BUS_BUSY;
|
|
|
- }
|
|
|
- scsiDev.selFlag = 0;
|
|
|
-}
|
|
|
-
|
|
|
-static void process_MessageOut()
|
|
|
-{
|
|
|
- int wasNeedSyncNegotiationAck = scsiDev.needSyncNegotiationAck;
|
|
|
- scsiDev.needSyncNegotiationAck = 0; // Successful on -most- messages.
|
|
|
-
|
|
|
- scsiEnterPhase(MESSAGE_OUT);
|
|
|
-
|
|
|
- scsiDev.atnFlag = 0;
|
|
|
- scsiDev.msgOut = scsiReadByte();
|
|
|
- scsiDev.msgCount++;
|
|
|
-
|
|
|
- if (scsiParityError() &&
|
|
|
- (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
- {
|
|
|
- // Skip the remaining message bytes, and then start the MESSAGE_OUT
|
|
|
- // phase again from the start. The initiator will re-send the
|
|
|
- // same set of messages.
|
|
|
- while (scsiStatusATN() && !scsiDev.resetFlag)
|
|
|
- {
|
|
|
- scsiReadByte();
|
|
|
- }
|
|
|
-
|
|
|
- // Go-back and try the message again.
|
|
|
- scsiDev.atnFlag = 1;
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x00)
|
|
|
- {
|
|
|
- // COMMAND COMPLETE. but why would the target be receiving this ? nfi.
|
|
|
- enter_BusFree();
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x06)
|
|
|
- {
|
|
|
- // ABORT
|
|
|
- scsiDiskReset();
|
|
|
- enter_BusFree();
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x0C)
|
|
|
- {
|
|
|
- // BUS DEVICE RESET
|
|
|
-
|
|
|
- scsiDiskReset();
|
|
|
-
|
|
|
- scsiDev.target->unitAttention = SCSI_BUS_RESET;
|
|
|
-
|
|
|
- // ANY initiator can reset the reservation state via this message.
|
|
|
- scsiDev.target->reservedId = -1;
|
|
|
- scsiDev.target->reserverId = -1;
|
|
|
-
|
|
|
- // Cancel any sync negotiation
|
|
|
- scsiDev.target->syncOffset = 0;
|
|
|
- scsiDev.target->syncPeriod = 0;
|
|
|
-
|
|
|
- enter_BusFree();
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x05)
|
|
|
- {
|
|
|
- // Initiate Detected Error
|
|
|
- // Ignore for now
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x0F)
|
|
|
- {
|
|
|
- // INITIATE RECOVERY
|
|
|
- // Ignore for now
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x10)
|
|
|
- {
|
|
|
- // RELEASE RECOVERY
|
|
|
- // Ignore for now
|
|
|
- enter_BusFree();
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == MSG_REJECT)
|
|
|
- {
|
|
|
- // Message Reject
|
|
|
- // Oh well.
|
|
|
-
|
|
|
- if (wasNeedSyncNegotiationAck)
|
|
|
- {
|
|
|
- scsiDev.target->syncOffset = 0;
|
|
|
- scsiDev.target->syncPeriod = 0;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x08)
|
|
|
- {
|
|
|
- // NOP
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x09)
|
|
|
- {
|
|
|
- // Message Parity Error
|
|
|
- // Go back and re-send the last message.
|
|
|
- scsiDev.phase = MESSAGE_IN;
|
|
|
-
|
|
|
- if (wasNeedSyncNegotiationAck)
|
|
|
- {
|
|
|
- scsiDev.target->syncOffset = 0;
|
|
|
- scsiDev.target->syncPeriod = 0;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF
|
|
|
- {
|
|
|
- // IDENTIFY
|
|
|
- if ((scsiDev.msgOut & 0x18) || // Reserved bits set.
|
|
|
- (scsiDev.msgOut & 0x20)) // We don't have any target routines!
|
|
|
- {
|
|
|
- messageReject();
|
|
|
- }
|
|
|
-
|
|
|
- scsiDev.lun = scsiDev.msgOut & 0x7;
|
|
|
- scsiDev.discPriv =
|
|
|
- ((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))
|
|
|
- ? 1 : 0;
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)
|
|
|
- {
|
|
|
- // Two byte message. We don't support these. read and discard.
|
|
|
- scsiReadByte();
|
|
|
-
|
|
|
- if (scsiDev.msgOut == 0x23) {
|
|
|
- // Ignore Wide Residue. We're only 8 bit anyway.
|
|
|
- } else {
|
|
|
- messageReject();
|
|
|
- }
|
|
|
- }
|
|
|
- else if (scsiDev.msgOut == 0x01)
|
|
|
- {
|
|
|
- int i;
|
|
|
-
|
|
|
- // Extended message.
|
|
|
- int msgLen = scsiReadByte();
|
|
|
- if (msgLen == 0) msgLen = 256;
|
|
|
- uint8_t extmsg[256];
|
|
|
- for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)
|
|
|
- {
|
|
|
- // Discard bytes.
|
|
|
- extmsg[i] = scsiReadByte();
|
|
|
- }
|
|
|
-
|
|
|
- if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request
|
|
|
- {
|
|
|
- // Negotiate down to 8bit
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
- static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};
|
|
|
- scsiWrite(WDTR, sizeof(WDTR));
|
|
|
-
|
|
|
- // SDTR becomes invalidated.
|
|
|
- scsiDev.target->syncOffset = 0;
|
|
|
- scsiDev.target->syncPeriod = 0;
|
|
|
- }
|
|
|
- else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request
|
|
|
- {
|
|
|
- int oldPeriod = scsiDev.target->syncPeriod;
|
|
|
- int oldOffset = scsiDev.target->syncOffset;
|
|
|
-
|
|
|
- int transferPeriod = extmsg[1];
|
|
|
- int offset = extmsg[2];
|
|
|
-
|
|
|
- if ((
|
|
|
- (transferPeriod > 0) &&
|
|
|
- (transferPeriod < scsiDev.minSyncPeriod)) ||
|
|
|
- (scsiDev.minSyncPeriod == 0))
|
|
|
- {
|
|
|
- scsiDev.minSyncPeriod = transferPeriod;
|
|
|
- }
|
|
|
-
|
|
|
- if ((transferPeriod > 80) || // 320ns, 3.125MB/s
|
|
|
- // Amiga A590 (WD33C93 chip) only does 3.5MB/s sync
|
|
|
- // After 80 we start to run out of bits in the fpga timing
|
|
|
- // register.
|
|
|
- (transferPeriod == 0) ||
|
|
|
- (offset == 0) ||
|
|
|
- ((scsiDev.boardCfg.scsiSpeed != S2S_CFG_SPEED_NoLimit) &&
|
|
|
- (scsiDev.boardCfg.scsiSpeed <= S2S_CFG_SPEED_ASYNC_50)))
|
|
|
- {
|
|
|
- scsiDev.target->syncOffset = 0;
|
|
|
- scsiDev.target->syncPeriod = 0;
|
|
|
- } else {
|
|
|
- scsiDev.target->syncOffset = offset <= 15 ? offset : 15;
|
|
|
- // FAST20 / 50ns / 20MHz is disabled for now due to
|
|
|
- // data corruption while reading data. We can count the
|
|
|
- // ACK's correctly, but can't save the data to a register
|
|
|
- // before it changes. (ie. transferPeriod == 12)
|
|
|
- if ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO) &&
|
|
|
- (transferPeriod <= 16))
|
|
|
- {
|
|
|
- scsiDev.target->syncPeriod = 16; // 15.6MB/s
|
|
|
- }
|
|
|
- else if (scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO)
|
|
|
- {
|
|
|
- scsiDev.target->syncPeriod = transferPeriod;
|
|
|
- }
|
|
|
- else if (transferPeriod <= 25 &&
|
|
|
- ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
|
|
|
- (scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
|
|
|
- {
|
|
|
- scsiDev.target->syncPeriod = 25; // 100ns, 10MB/s
|
|
|
-
|
|
|
- } else if (transferPeriod < 50 &&
|
|
|
- ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
|
|
|
- (scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
|
|
|
- {
|
|
|
- scsiDev.target->syncPeriod = transferPeriod;
|
|
|
- } else if (transferPeriod >= 50)
|
|
|
- {
|
|
|
- scsiDev.target->syncPeriod = transferPeriod;
|
|
|
- } else {
|
|
|
- scsiDev.target->syncPeriod = 50;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (transferPeriod != oldPeriod ||
|
|
|
- scsiDev.target->syncPeriod != oldPeriod ||
|
|
|
- offset != oldOffset ||
|
|
|
- scsiDev.target->syncOffset != oldOffset ||
|
|
|
- !wasNeedSyncNegotiationAck) // Don't get into infinite loops negotiating.
|
|
|
- {
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
- uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset};
|
|
|
- scsiWrite(SDTR, sizeof(SDTR));
|
|
|
- scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected.
|
|
|
- scsiDev.sdUnderrunCount = 0; // reset counter, may work now.
|
|
|
-
|
|
|
- // Set to the theoretical speed, then adjust if we measure lower
|
|
|
- // actual speeds.
|
|
|
- scsiDev.hostSpeedKBs = s2s_getScsiRateKBs();
|
|
|
- scsiDev.hostSpeedMeasured = 0;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Not supported
|
|
|
- messageReject();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- messageReject();
|
|
|
- }
|
|
|
-
|
|
|
- // Re-check the ATN flag in case it stays asserted.
|
|
|
- scsiDev.atnFlag |= scsiStatusATN();
|
|
|
-
|
|
|
- if (!scsiDev.atnFlag)
|
|
|
- {
|
|
|
- // Message wasn't rejected!
|
|
|
- scsiDev.needSyncNegotiationAck = 0;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void scsiPoll(void)
|
|
|
-{
|
|
|
- if (resetUntil != 0 && resetUntil > s2s_getTime_ms())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- resetUntil = 0;
|
|
|
-
|
|
|
- if (unlikely(scsiDev.resetFlag))
|
|
|
- {
|
|
|
- scsiReset();
|
|
|
- // Still in reset phase for a few ms.
|
|
|
- // Do not try and process any commands.
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- switch (scsiDev.phase)
|
|
|
- {
|
|
|
- case BUS_FREE:
|
|
|
- if (scsiStatusBSY())
|
|
|
- {
|
|
|
- scsiDev.phase = BUS_BUSY;
|
|
|
- }
|
|
|
- // The Arbitration phase is optional for SCSI1/SASI hosts if there is only
|
|
|
- // one initiator in the chain. Support this by moving
|
|
|
- // straight to selection if SEL is asserted.
|
|
|
- // ie. the initiator won't assert BSY and it's own ID before moving to selection.
|
|
|
- else if (scsiDev.selFlag || *SCSI_STS_SELECTED)
|
|
|
- {
|
|
|
- enter_SelectionPhase();
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case BUS_BUSY:
|
|
|
- // Someone is using the bus. Perhaps they are trying to
|
|
|
- // select us.
|
|
|
- if (scsiDev.selFlag || *SCSI_STS_SELECTED)
|
|
|
- {
|
|
|
- enter_SelectionPhase();
|
|
|
- }
|
|
|
- else if (!scsiStatusBSY())
|
|
|
- {
|
|
|
- scsiDev.phase = BUS_FREE;
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case ARBITRATION:
|
|
|
- // TODO Support reselection.
|
|
|
- break;
|
|
|
-
|
|
|
- case SELECTION:
|
|
|
- process_SelectionPhase();
|
|
|
- break;
|
|
|
-
|
|
|
- case RESELECTION:
|
|
|
- // Not currently supported!
|
|
|
- break;
|
|
|
-
|
|
|
- case COMMAND:
|
|
|
- // Do not check ATN here. SCSI 1 & 2 initiators must set ATN
|
|
|
- // and SEL together upon entering the selection phase if they
|
|
|
- // want to send a message (IDENTIFY) immediately.
|
|
|
- if (scsiDev.atnFlag)
|
|
|
- {
|
|
|
- process_MessageOut();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- process_Command();
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case DATA_IN:
|
|
|
- scsiDev.atnFlag |= scsiStatusATN();
|
|
|
- if (scsiDev.atnFlag)
|
|
|
- {
|
|
|
- process_MessageOut();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- process_DataIn();
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case DATA_OUT:
|
|
|
- scsiDev.atnFlag |= scsiStatusATN();
|
|
|
- if (scsiDev.atnFlag)
|
|
|
- {
|
|
|
- process_MessageOut();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- process_DataOut();
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case STATUS:
|
|
|
- scsiDev.atnFlag |= scsiStatusATN();
|
|
|
- if (scsiDev.atnFlag)
|
|
|
- {
|
|
|
- process_MessageOut();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- process_Status();
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case MESSAGE_IN:
|
|
|
- scsiDev.atnFlag |= scsiStatusATN();
|
|
|
- if (scsiDev.atnFlag)
|
|
|
- {
|
|
|
- process_MessageOut();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- process_MessageIn(1);
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case MESSAGE_OUT:
|
|
|
- process_MessageOut();
|
|
|
- break;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void scsiInit()
|
|
|
-{
|
|
|
- static int firstInit = 1;
|
|
|
-
|
|
|
- scsiDev.atnFlag = 0;
|
|
|
- scsiDev.resetFlag = 1;
|
|
|
- scsiDev.selFlag = 0;
|
|
|
- scsiDev.phase = BUS_FREE;
|
|
|
- scsiDev.target = NULL;
|
|
|
- scsiDev.compatMode = COMPAT_UNKNOWN;
|
|
|
- scsiDev.hostSpeedKBs = 0;
|
|
|
- scsiDev.hostSpeedMeasured = 0;
|
|
|
-
|
|
|
- int i;
|
|
|
- for (i = 0; i < S2S_MAX_TARGETS; ++i)
|
|
|
- {
|
|
|
- const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);
|
|
|
- if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
|
|
|
- {
|
|
|
- scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;
|
|
|
- scsiDev.targets[i].cfg = cfg;
|
|
|
-
|
|
|
- scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.targets[i].targetId = 0xff;
|
|
|
- scsiDev.targets[i].cfg = NULL;
|
|
|
- }
|
|
|
- scsiDev.targets[i].reservedId = -1;
|
|
|
- scsiDev.targets[i].reserverId = -1;
|
|
|
- if (firstInit)
|
|
|
- {
|
|
|
- scsiDev.targets[i].unitAttention = POWER_ON_RESET;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;
|
|
|
- }
|
|
|
- scsiDev.targets[i].sense.code = NO_SENSE;
|
|
|
- scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
-
|
|
|
- scsiDev.targets[i].syncOffset = 0;
|
|
|
- scsiDev.targets[i].syncPeriod = 0;
|
|
|
-
|
|
|
- // Always "start" the device. Many systems (eg. Apple System 7)
|
|
|
- // won't respond properly to
|
|
|
- // LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED sense
|
|
|
- // code
|
|
|
- scsiDev.targets[i].started = 1;
|
|
|
- }
|
|
|
- firstInit = 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* TODO REENABLE
|
|
|
-void scsiDisconnect()
|
|
|
-{
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
- scsiWriteByte(0x02); // save data pointer
|
|
|
- scsiWriteByte(0x04); // disconnect msg.
|
|
|
-
|
|
|
- // For now, the caller is responsible for tracking the disconnected
|
|
|
- // state, and calling scsiReconnect.
|
|
|
- // Ideally the client would exit their loop and we'd implement this
|
|
|
- // as part of scsiPoll
|
|
|
- int phase = scsiDev.phase;
|
|
|
- enter_BusFree();
|
|
|
- scsiDev.phase = phase;
|
|
|
-}
|
|
|
-*/
|
|
|
-
|
|
|
-/* TODO REENABLE
|
|
|
-int scsiReconnect()
|
|
|
-{
|
|
|
- int reconnected = 0;
|
|
|
-
|
|
|
- int sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
- int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
- if (!sel && !bsy)
|
|
|
- {
|
|
|
- s2s_delay_us(1);
|
|
|
- sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
- bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
- }
|
|
|
-
|
|
|
- if (!sel && !bsy)
|
|
|
- {
|
|
|
- // Arbitrate.
|
|
|
- s2s_ledOn();
|
|
|
- uint8_t scsiIdMask = 1 << scsiDev.target->targetId;
|
|
|
- SCSI_Out_Bits_Write(scsiIdMask);
|
|
|
- SCSI_Out_Ctl_Write(1); // Write bits manually.
|
|
|
- SCSI_SetPin(SCSI_Out_BSY);
|
|
|
-
|
|
|
- s2s_delay_us(3); // arbitrate delay. 2.4us.
|
|
|
-
|
|
|
- uint8_t dbx = scsiReadDBxPins();
|
|
|
- sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
- if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))
|
|
|
- {
|
|
|
- // Lost arbitration.
|
|
|
- SCSI_Out_Ctl_Write(0);
|
|
|
- SCSI_ClearPin(SCSI_Out_BSY);
|
|
|
- s2s_ledOff();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Won arbitration
|
|
|
- SCSI_SetPin(SCSI_Out_SEL);
|
|
|
- s2s_delay_us(1); // Bus clear + Bus settle.
|
|
|
-
|
|
|
- // Reselection phase
|
|
|
- SCSI_CTL_PHASE_Write(__scsiphase_io);
|
|
|
- SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));
|
|
|
- scsiDeskewDelay(); // 2 deskew delays
|
|
|
- scsiDeskewDelay(); // 2 deskew delays
|
|
|
- SCSI_ClearPin(SCSI_Out_BSY);
|
|
|
- s2s_delay_us(1); // Bus Settle Delay
|
|
|
-
|
|
|
- uint32_t waitStart_ms = getTime_ms();
|
|
|
- bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
- // Wait for initiator.
|
|
|
- while (
|
|
|
- !bsy &&
|
|
|
- !scsiDev.resetFlag &&
|
|
|
- (elapsedTime_ms(waitStart_ms) < 250))
|
|
|
- {
|
|
|
- bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
- }
|
|
|
-
|
|
|
- if (bsy)
|
|
|
- {
|
|
|
- SCSI_SetPin(SCSI_Out_BSY);
|
|
|
- scsiDeskewDelay(); // 2 deskew delays
|
|
|
- scsiDeskewDelay(); // 2 deskew delays
|
|
|
- SCSI_ClearPin(SCSI_Out_SEL);
|
|
|
-
|
|
|
- // Prepare for the initial IDENTIFY message.
|
|
|
- SCSI_Out_Ctl_Write(0);
|
|
|
- scsiEnterPhase(MESSAGE_IN);
|
|
|
-
|
|
|
- // Send identify command
|
|
|
- scsiWriteByte(0x80);
|
|
|
-
|
|
|
- scsiEnterPhase(scsiDev.phase);
|
|
|
- reconnected = 1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // reselect timeout.
|
|
|
- SCSI_Out_Ctl_Write(0);
|
|
|
- SCSI_ClearPin(SCSI_Out_SEL);
|
|
|
- SCSI_CTL_PHASE_Write(0);
|
|
|
- s2s_ledOff();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return reconnected;
|
|
|
-}
|
|
|
-*/
|
|
|
-
|
|
|
+// Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
|
|
|
+//
|
|
|
+// This file is part of SCSI2SD.
|
|
|
+//
|
|
|
+// SCSI2SD 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 3 of the License, or
|
|
|
+// (at your option) any later version.
|
|
|
+//
|
|
|
+// SCSI2SD 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 SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
+
|
|
|
+#include "scsi.h"
|
|
|
+#include "scsiPhy.h"
|
|
|
+#include "config.h"
|
|
|
+#include "diagnostic.h"
|
|
|
+#include "disk.h"
|
|
|
+#include "inquiry.h"
|
|
|
+#include "led.h"
|
|
|
+#include "mode.h"
|
|
|
+#include "scsi2sd_time.h"
|
|
|
+#include "bsp.h"
|
|
|
+#include "cdrom.h"
|
|
|
+//#include "debug.h"
|
|
|
+#include "tape.h"
|
|
|
+#include "mo.h"
|
|
|
+#include "vendor.h"
|
|
|
+
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+// Global SCSI device state.
|
|
|
+ScsiDevice scsiDev S2S_DMA_ALIGN;
|
|
|
+
|
|
|
+static void enter_SelectionPhase(void);
|
|
|
+static void process_SelectionPhase(void);
|
|
|
+static void enter_MessageIn(uint8_t message);
|
|
|
+static void enter_Status(uint8_t status);
|
|
|
+static void enter_DataIn(int len);
|
|
|
+static void process_DataIn(void);
|
|
|
+static void process_DataOut(void);
|
|
|
+static void process_Command(void);
|
|
|
+
|
|
|
+static void doReserveRelease(void);
|
|
|
+
|
|
|
+void enter_BusFree()
|
|
|
+{
|
|
|
+ // This delay probably isn't needed for most SCSI hosts, but it won't
|
|
|
+ // hurt either. It's possible some of the samplers needed this delay.
|
|
|
+ if (scsiDev.compatMode < COMPAT_SCSI2)
|
|
|
+ {
|
|
|
+ s2s_delay_us(2);
|
|
|
+ }
|
|
|
+
|
|
|
+#if 0
|
|
|
+ if (scsiDev.status != GOOD)// && isDebugEnabled())
|
|
|
+ {
|
|
|
+ // We want to capture debug information for failure cases.
|
|
|
+ s2s_delay_ms(80);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+ scsiEnterBusFree();
|
|
|
+
|
|
|
+ // Wait for the initiator to cease driving signals
|
|
|
+ // Bus settle delay + bus clear delay = 1200ns
|
|
|
+ // Just waiting the clear delay is sufficient.
|
|
|
+ s2s_delay_ns(800);
|
|
|
+
|
|
|
+ s2s_ledOff();
|
|
|
+ scsiDev.phase = BUS_FREE;
|
|
|
+ scsiDev.selFlag = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void enter_MessageIn(uint8_t message)
|
|
|
+{
|
|
|
+ scsiDev.msgIn = message;
|
|
|
+ scsiDev.phase = MESSAGE_IN;
|
|
|
+}
|
|
|
+
|
|
|
+int process_MessageIn(int releaseBusFree)
|
|
|
+{
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+ scsiWriteByte(scsiDev.msgIn);
|
|
|
+
|
|
|
+ if (unlikely(scsiDev.atnFlag))
|
|
|
+ {
|
|
|
+ // If there was a parity error, we go
|
|
|
+ // back to MESSAGE_OUT first, get out parity error message, then come
|
|
|
+ // back here.
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) ||
|
|
|
+ (scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG))
|
|
|
+ {
|
|
|
+ // Go back to the command phase and start again.
|
|
|
+ scsiDev.phase = COMMAND;
|
|
|
+ scsiDev.dataPtr = 0;
|
|
|
+ scsiDev.savedDataPtr = 0;
|
|
|
+ scsiDev.dataLen = 0;
|
|
|
+ scsiDev.status = GOOD;
|
|
|
+ transfer.blocks = 0;
|
|
|
+ transfer.currentBlock = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else if (releaseBusFree) /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/
|
|
|
+ {
|
|
|
+ enter_BusFree();
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void messageReject()
|
|
|
+{
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+ scsiWriteByte(MSG_REJECT);
|
|
|
+}
|
|
|
+
|
|
|
+static void enter_Status(uint8_t status)
|
|
|
+{
|
|
|
+ scsiDev.status = status;
|
|
|
+ scsiDev.phase = STATUS;
|
|
|
+
|
|
|
+ scsiDev.lastStatus = scsiDev.status;
|
|
|
+ scsiDev.lastSense = scsiDev.target->sense.code;
|
|
|
+ scsiDev.lastSenseASC = scsiDev.target->sense.asc;
|
|
|
+}
|
|
|
+
|
|
|
+void process_Status()
|
|
|
+{
|
|
|
+ scsiEnterPhase(STATUS);
|
|
|
+
|
|
|
+ uint8_t message;
|
|
|
+
|
|
|
+ uint8_t control = scsiDev.cdb[scsiDev.cdbLen - 1];
|
|
|
+
|
|
|
+ if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
+ {
|
|
|
+ // All commands have a control byte, except 0xC0
|
|
|
+ if (scsiDev.cdb[0] == 0xC0)
|
|
|
+ {
|
|
|
+ control = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // OMTI non-standard LINK control
|
|
|
+ if (control & 0x01)
|
|
|
+ {
|
|
|
+ scsiDev.phase = COMMAND;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((scsiDev.status == GOOD) && (control & 0x01) &&
|
|
|
+ scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ // Linked command.
|
|
|
+ scsiDev.status = INTERMEDIATE;
|
|
|
+ if (control & 0x02)
|
|
|
+ {
|
|
|
+ message = MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ message = MSG_LINKED_COMMAND_COMPLETE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ message = MSG_COMMAND_COMPLETE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ // More non-standardness. Expects 2 status bytes (really status + msg)
|
|
|
+ // 00 d 000 err 0
|
|
|
+ // d == disk number
|
|
|
+ // ERR = 1 if error.
|
|
|
+ if (scsiDev.status == GOOD)
|
|
|
+ {
|
|
|
+ scsiWriteByte(scsiDev.cdb[1] & 0x20);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiWriteByte((scsiDev.cdb[1] & 0x20) | 0x2);
|
|
|
+ }
|
|
|
+ s2s_delay_us(10); // Seems to need a delay before changing phase bits.
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
+ {
|
|
|
+ scsiDev.status |= (scsiDev.target->targetId & 0x03) << 5;
|
|
|
+ scsiWriteByte(scsiDev.status);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiWriteByte(scsiDev.status);
|
|
|
+ }
|
|
|
+
|
|
|
+ scsiDev.lastStatus = scsiDev.status;
|
|
|
+ scsiDev.lastSense = scsiDev.target->sense.code;
|
|
|
+ scsiDev.lastSenseASC = scsiDev.target->sense.asc;
|
|
|
+
|
|
|
+ // Command Complete occurs AFTER a valid status has been
|
|
|
+ // sent. then we go bus-free.
|
|
|
+ enter_MessageIn(message);
|
|
|
+}
|
|
|
+
|
|
|
+static void enter_DataIn(int len)
|
|
|
+{
|
|
|
+ scsiDev.dataLen = len;
|
|
|
+ scsiDev.phase = DATA_IN;
|
|
|
+}
|
|
|
+
|
|
|
+static void process_DataIn()
|
|
|
+{
|
|
|
+ uint32_t len;
|
|
|
+
|
|
|
+ if (scsiDev.dataLen > sizeof(scsiDev.data))
|
|
|
+ {
|
|
|
+ scsiDev.dataLen = sizeof(scsiDev.data);
|
|
|
+ }
|
|
|
+
|
|
|
+ len = scsiDev.dataLen - scsiDev.dataPtr;
|
|
|
+ if (len > 0)
|
|
|
+ {
|
|
|
+ scsiEnterPhase(DATA_IN);
|
|
|
+ scsiWrite(scsiDev.data + scsiDev.dataPtr, len);
|
|
|
+ scsiDev.dataPtr += len;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
|
|
|
+ (transfer.currentBlock == transfer.blocks))
|
|
|
+ {
|
|
|
+ enter_Status(GOOD);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void process_DataOut()
|
|
|
+{
|
|
|
+ uint32_t len;
|
|
|
+
|
|
|
+ if (scsiDev.dataLen > sizeof(scsiDev.data))
|
|
|
+ {
|
|
|
+ scsiDev.dataLen = sizeof(scsiDev.data);
|
|
|
+ }
|
|
|
+
|
|
|
+ len = scsiDev.dataLen - scsiDev.dataPtr;
|
|
|
+ if (len > 0)
|
|
|
+ {
|
|
|
+ scsiEnterPhase(DATA_OUT);
|
|
|
+
|
|
|
+ int parityError = 0;
|
|
|
+ scsiRead(scsiDev.data + scsiDev.dataPtr, len, &parityError);
|
|
|
+ scsiDev.dataPtr += len;
|
|
|
+
|
|
|
+ if (parityError &&
|
|
|
+ (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
+ {
|
|
|
+ scsiDev.target->sense.code = ABORTED_COMMAND;
|
|
|
+ scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
|
|
|
+ (transfer.currentBlock == transfer.blocks))
|
|
|
+ {
|
|
|
+ if (scsiDev.postDataOutHook != NULL)
|
|
|
+ {
|
|
|
+ scsiDev.postDataOutHook();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ enter_Status(GOOD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static const uint8_t CmdGroupBytes[] = {
|
|
|
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,16,16,16,16,16,16,16,16,
|
|
|
+ 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
|
|
+ 16,16,16,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
|
|
|
+ 12,12,12,12,12,12,12,12,12,12,12,12,12,12,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
|
|
|
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10
|
|
|
+};
|
|
|
+
|
|
|
+static void process_Command()
|
|
|
+{
|
|
|
+ uint8_t command;
|
|
|
+ uint8_t control;
|
|
|
+
|
|
|
+ scsiEnterPhase(COMMAND);
|
|
|
+
|
|
|
+ memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);
|
|
|
+ int parityError = 0;
|
|
|
+ scsiRead(scsiDev.cdb, 6, &parityError);
|
|
|
+
|
|
|
+ // Handle Atari ST ICD extended commands
|
|
|
+ if (scsiDev.cdb[0] == 0x1F)
|
|
|
+ {
|
|
|
+ scsiDev.cdb[0] = scsiDev.cdb[1];
|
|
|
+ scsiDev.cdb[1] = scsiDev.cdb[2];
|
|
|
+ scsiDev.cdb[2] = scsiDev.cdb[3];
|
|
|
+ scsiDev.cdb[3] = scsiDev.cdb[4];
|
|
|
+ scsiDev.cdb[4] = scsiDev.cdb[5];
|
|
|
+ scsiDev.cdb[5] = scsiReadByte();
|
|
|
+ }
|
|
|
+
|
|
|
+ scsiDev.cdbLen = CmdGroupBytes[scsiDev.cdb[0]];
|
|
|
+ if (parityError &&
|
|
|
+ (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
+ {
|
|
|
+ // Don't try and read more bytes, as we cannot be sure what group
|
|
|
+ // the command should be.
|
|
|
+ }
|
|
|
+ else if (scsiDev.cdbLen - 6 > 0)
|
|
|
+ {
|
|
|
+ scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6, &parityError);
|
|
|
+ }
|
|
|
+ command = scsiDev.cdb[0];
|
|
|
+
|
|
|
+ // Prefer LUN's set by IDENTIFY messages for newer hosts.
|
|
|
+ if (scsiDev.lun < 0)
|
|
|
+ {
|
|
|
+ if (command == 0xE0 || command == 0xE4) // XEBEC s1410
|
|
|
+ {
|
|
|
+ scsiDev.lun = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.lun = scsiDev.cdb[1] >> 5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // For Philips P2000C with Xebec S1410 SASI/MFM adapter
|
|
|
+ // http://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf
|
|
|
+ if ((scsiDev.lun > 0) && (scsiDev.boardCfg.flags & S2S_CFG_MAP_LUNS_TO_IDS))
|
|
|
+ {
|
|
|
+ int tgtIndex;
|
|
|
+ for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
|
|
|
+ {
|
|
|
+ if (scsiDev.targets[tgtIndex].targetId == scsiDev.lun)
|
|
|
+ {
|
|
|
+ scsiDev.target = &scsiDev.targets[tgtIndex];
|
|
|
+ scsiDev.lun = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ control = scsiDev.cdb[scsiDev.cdbLen - 1];
|
|
|
+
|
|
|
+ scsiDev.cmdCount++;
|
|
|
+ const S2S_TargetCfg* cfg = scsiDev.target->cfg;
|
|
|
+
|
|
|
+ if (unlikely(scsiDev.resetFlag))
|
|
|
+ {
|
|
|
+ // Don't log bogus commands
|
|
|
+ scsiDev.cmdCount--;
|
|
|
+ memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (parityError &&
|
|
|
+ (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
+ {
|
|
|
+ scsiDev.target->sense.code = ABORTED_COMMAND;
|
|
|
+ scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ else if ((control & 0x02) && ((control & 0x01) == 0) &&
|
|
|
+ // used for head step options on xebec.
|
|
|
+ likely(scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC))
|
|
|
+ {
|
|
|
+ // FLAG set without LINK flag.
|
|
|
+ scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
+ scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ else if (command == 0x12)
|
|
|
+ {
|
|
|
+ s2s_scsiInquiry();
|
|
|
+ }
|
|
|
+ else if (command == 0x03)
|
|
|
+ {
|
|
|
+ // REQUEST SENSE
|
|
|
+ uint32_t allocLength = scsiDev.cdb[4];
|
|
|
+
|
|
|
+ if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ // Completely non-standard
|
|
|
+ allocLength = 4;
|
|
|
+ if (scsiDev.target->sense.code == NO_SENSE)
|
|
|
+ scsiDev.data[0] = 0;
|
|
|
+ else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
|
|
|
+ scsiDev.data[0] = 0x20; // Illegal command
|
|
|
+ else if (scsiDev.target->sense.code == NOT_READY)
|
|
|
+ scsiDev.data[0] = 0x04; // Drive not ready
|
|
|
+ else
|
|
|
+ scsiDev.data[0] = 0x11; // Uncorrectable data error
|
|
|
+
|
|
|
+ scsiDev.data[1] = (scsiDev.cdb[1] & 0x20) | ((transfer.lba >> 16) & 0x1F);
|
|
|
+ scsiDev.data[2] = transfer.lba >> 8;
|
|
|
+ scsiDev.data[3] = transfer.lba;
|
|
|
+ }
|
|
|
+ else if (cfg->quirks == S2S_CFG_QUIRKS_OMTI)
|
|
|
+ {
|
|
|
+ // The response is completely non-standard.
|
|
|
+ if (likely(allocLength > 12))
|
|
|
+ allocLength = 12;
|
|
|
+ else if (unlikely(allocLength < 4))
|
|
|
+ allocLength = 4;
|
|
|
+ if (cfg->deviceType != S2S_CFG_SEQUENTIAL)
|
|
|
+ allocLength = 4;
|
|
|
+ memset(scsiDev.data, 0, allocLength);
|
|
|
+ if (scsiDev.target->sense.code == NO_SENSE)
|
|
|
+ {
|
|
|
+ // Nothing to report.
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->sense.code == UNIT_ATTENTION &&
|
|
|
+ cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
+ {
|
|
|
+ scsiDev.data[0] = 0x10; // Tape exception
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
|
|
|
+ {
|
|
|
+ if (scsiDev.target->sense.asc == LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE)
|
|
|
+ {
|
|
|
+ if (cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
+ scsiDev.data[0] = 0x10; // Tape exception
|
|
|
+ else
|
|
|
+ scsiDev.data[0] = 0x21; // Illegal Parameters
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->sense.asc == INVALID_COMMAND_OPERATION_CODE)
|
|
|
+ {
|
|
|
+ scsiDev.data[0] = 0x20; // Invalid Command
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->sense.code == NOT_READY)
|
|
|
+ {
|
|
|
+ scsiDev.data[0] = 0x04; // Drive not ready
|
|
|
+ }
|
|
|
+ else if (scsiDev.target->sense.code == BLANK_CHECK)
|
|
|
+ {
|
|
|
+ scsiDev.data[0] = 0x10; // Tape exception
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.data[0] = 0x11; // Uncorrectable data error
|
|
|
+ }
|
|
|
+ scsiDev.data[1] = (scsiDev.cdb[1] & 0x60) | ((transfer.lba >> 16) & 0x1F);
|
|
|
+ scsiDev.data[2] = transfer.lba >> 8;
|
|
|
+ scsiDev.data[3] = transfer.lba;
|
|
|
+ if (cfg->deviceType == S2S_CFG_SEQUENTIAL)
|
|
|
+ {
|
|
|
+ // For the tape drive there are 8 extra sense bytes.
|
|
|
+ if (scsiDev.target->sense.code == BLANK_CHECK)
|
|
|
+ scsiDev.data[11] = 0x88; // End of data recorded on the tape
|
|
|
+ else if (scsiDev.target->sense.code == UNIT_ATTENTION)
|
|
|
+ scsiDev.data[5] = 0x81; // Power On Reset occurred
|
|
|
+ else if (scsiDev.target->sense.code == ILLEGAL_REQUEST &&
|
|
|
+ scsiDev.target->sense.asc == LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE)
|
|
|
+ scsiDev.data[4] = 0x81; // File Mark detected
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // As specified by the SASI and SCSI1 standard.
|
|
|
+ // Newer initiators won't be specifying 0 anyway.
|
|
|
+ if (allocLength == 0) allocLength = 4;
|
|
|
+
|
|
|
+ memset(scsiDev.data, 0, 256); // Max possible alloc length
|
|
|
+ scsiDev.data[0] = 0xF0;
|
|
|
+ scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;
|
|
|
+
|
|
|
+ scsiDev.data[3] = transfer.lba >> 24;
|
|
|
+ scsiDev.data[4] = transfer.lba >> 16;
|
|
|
+ scsiDev.data[5] = transfer.lba >> 8;
|
|
|
+ scsiDev.data[6] = transfer.lba;
|
|
|
+
|
|
|
+ // Additional bytes if there are errors to report
|
|
|
+ scsiDev.data[7] = 10; // additional length
|
|
|
+ scsiDev.data[12] = scsiDev.target->sense.asc >> 8;
|
|
|
+ scsiDev.data[13] = scsiDev.target->sense.asc;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Silently truncate results. SCSI-2 spec 8.2.14.
|
|
|
+ enter_DataIn(allocLength);
|
|
|
+
|
|
|
+ // This is a good time to clear out old sense information.
|
|
|
+ scsiDev.target->sense.code = NO_SENSE;
|
|
|
+ scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
+ }
|
|
|
+ // Some old SCSI drivers do NOT properly support
|
|
|
+ // unitAttention. eg. the Mac Plus would trigger a SCSI reset
|
|
|
+ // on receiving the unit attention response on boot, thus
|
|
|
+ // triggering another unit attention condition.
|
|
|
+ else if (scsiDev.target->unitAttention &&
|
|
|
+ (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))
|
|
|
+ {
|
|
|
+ scsiDev.target->sense.code = UNIT_ATTENTION;
|
|
|
+ scsiDev.target->sense.asc = scsiDev.target->unitAttention;
|
|
|
+
|
|
|
+ // If initiator doesn't do REQUEST SENSE for the next command, then
|
|
|
+ // data is lost.
|
|
|
+ scsiDev.target->unitAttention = 0;
|
|
|
+
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ else if (scsiDev.lun)
|
|
|
+ {
|
|
|
+ scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
+ scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ else if (command == 0x17 || command == 0x16)
|
|
|
+ {
|
|
|
+ doReserveRelease();
|
|
|
+ }
|
|
|
+ else if ((scsiDev.target->reservedId >= 0) &&
|
|
|
+ (scsiDev.target->reservedId != scsiDev.initiatorId))
|
|
|
+ {
|
|
|
+ enter_Status(CONFLICT);
|
|
|
+ }
|
|
|
+ // Handle odd device types first that may override basic read and
|
|
|
+ // write commands. Will fall-through to generic disk handling.
|
|
|
+ else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||
|
|
|
+ ((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||
|
|
|
+ ((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
|
|
|
+ {
|
|
|
+ // Already handled.
|
|
|
+ }
|
|
|
+ else if (scsiDiskCommand())
|
|
|
+ {
|
|
|
+ // Already handled.
|
|
|
+ // check for the performance-critical read/write
|
|
|
+ // commands ASAP.
|
|
|
+ }
|
|
|
+ else if (command == 0x1C)
|
|
|
+ {
|
|
|
+ scsiReceiveDiagnostic();
|
|
|
+ }
|
|
|
+ else if (command == 0x1D)
|
|
|
+ {
|
|
|
+ scsiSendDiagnostic();
|
|
|
+ }
|
|
|
+ else if (command == 0x3B)
|
|
|
+ {
|
|
|
+ scsiWriteBuffer();
|
|
|
+ }
|
|
|
+ else if (command == 0x0f &&
|
|
|
+ scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ scsiWriteSectorBuffer();
|
|
|
+ }
|
|
|
+ else if (command == 0x3C)
|
|
|
+ {
|
|
|
+ scsiReadBuffer();
|
|
|
+ }
|
|
|
+ else if (!scsiModeCommand() && !scsiVendorCommand())
|
|
|
+ {
|
|
|
+ scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
+ scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Successful
|
|
|
+ if (scsiDev.phase == COMMAND) // No status set, and not in DATA_IN
|
|
|
+ {
|
|
|
+ enter_Status(GOOD);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+static void doReserveRelease()
|
|
|
+{
|
|
|
+ int extentReservation = scsiDev.cdb[1] & 1;
|
|
|
+ int thirdPty = scsiDev.cdb[1] & 0x10;
|
|
|
+ int thirdPtyId = (scsiDev.cdb[1] >> 1) & 0x7;
|
|
|
+ uint8_t command = scsiDev.cdb[0];
|
|
|
+
|
|
|
+ int canRelease =
|
|
|
+ (!thirdPty && (scsiDev.initiatorId == scsiDev.target->reservedId)) ||
|
|
|
+ (thirdPty &&
|
|
|
+ (scsiDev.target->reserverId == scsiDev.initiatorId) &&
|
|
|
+ (scsiDev.target->reservedId == thirdPtyId)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (extentReservation)
|
|
|
+ {
|
|
|
+ // Not supported.
|
|
|
+ scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
|
+ scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
|
+ enter_Status(CHECK_CONDITION);
|
|
|
+ }
|
|
|
+ else if (command == 0x17) // release
|
|
|
+ {
|
|
|
+ if ((scsiDev.target->reservedId < 0) || canRelease)
|
|
|
+ {
|
|
|
+ scsiDev.target->reservedId = -1;
|
|
|
+ scsiDev.target->reserverId = -1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ enter_Status(CONFLICT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else // assume reserve.
|
|
|
+ {
|
|
|
+ if ((scsiDev.target->reservedId < 0) || canRelease)
|
|
|
+ {
|
|
|
+ scsiDev.target->reserverId = scsiDev.initiatorId;
|
|
|
+ if (thirdPty)
|
|
|
+ {
|
|
|
+ scsiDev.target->reservedId = thirdPtyId;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.target->reservedId = scsiDev.initiatorId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Already reserved by someone else!
|
|
|
+ enter_Status(CONFLICT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static uint32_t resetUntil = 0;
|
|
|
+
|
|
|
+static void scsiReset()
|
|
|
+{
|
|
|
+ scsiDev.rstCount++;
|
|
|
+ s2s_ledOff();
|
|
|
+
|
|
|
+ scsiPhyReset();
|
|
|
+
|
|
|
+ scsiDev.phase = BUS_FREE;
|
|
|
+ scsiDev.atnFlag = 0;
|
|
|
+ scsiDev.resetFlag = 0;
|
|
|
+ scsiDev.selFlag = 0;
|
|
|
+ scsiDev.lun = -1;
|
|
|
+ scsiDev.compatMode = COMPAT_UNKNOWN;
|
|
|
+
|
|
|
+ if (scsiDev.target)
|
|
|
+ {
|
|
|
+ if (scsiDev.target->unitAttention != POWER_ON_RESET)
|
|
|
+ {
|
|
|
+ scsiDev.target->unitAttention = SCSI_BUS_RESET;
|
|
|
+ }
|
|
|
+ scsiDev.target->reservedId = -1;
|
|
|
+ scsiDev.target->reserverId = -1;
|
|
|
+ scsiDev.target->sense.code = NO_SENSE;
|
|
|
+ scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
+ }
|
|
|
+ scsiDev.target = NULL;
|
|
|
+
|
|
|
+ for (int i = 0; i < S2S_MAX_TARGETS; ++i)
|
|
|
+ {
|
|
|
+ scsiDev.targets[i].syncOffset = 0;
|
|
|
+ scsiDev.targets[i].syncPeriod = 0;
|
|
|
+ }
|
|
|
+ scsiDev.minSyncPeriod = 0;
|
|
|
+
|
|
|
+ scsiDiskReset();
|
|
|
+
|
|
|
+ scsiDev.postDataOutHook = NULL;
|
|
|
+
|
|
|
+ scsiDev.sdUnderrunCount = 0;
|
|
|
+
|
|
|
+ // Sleep to allow the bus to settle down a bit.
|
|
|
+ // We must be ready again within the "Reset to selection time" of
|
|
|
+ // 250ms.
|
|
|
+ // There is no guarantee that the RST line will be negated by then.
|
|
|
+ // NOTE: We could be connected and powered by USB for configuration,
|
|
|
+ // in which case TERMPWR cannot be supplied, and reset will ALWAYS
|
|
|
+ // be true. Therefore, the sleep here must be slow to avoid slowing
|
|
|
+ // USB comms
|
|
|
+ resetUntil = s2s_getTime_ms() + 2; // At least 1ms.
|
|
|
+}
|
|
|
+
|
|
|
+static void enter_SelectionPhase()
|
|
|
+{
|
|
|
+ // Ignore stale versions of this flag, but ensure we know the
|
|
|
+ // current value if the flag is still set.
|
|
|
+ scsiDev.atnFlag = 0;
|
|
|
+ scsiDev.dataPtr = 0;
|
|
|
+ scsiDev.savedDataPtr = 0;
|
|
|
+ scsiDev.dataLen = 0;
|
|
|
+ scsiDev.status = GOOD;
|
|
|
+ scsiDev.phase = SELECTION;
|
|
|
+ scsiDev.lun = -1;
|
|
|
+ scsiDev.discPriv = 0;
|
|
|
+
|
|
|
+ scsiDev.initiatorId = -1;
|
|
|
+ scsiDev.target = NULL;
|
|
|
+
|
|
|
+ transfer.blocks = 0;
|
|
|
+ transfer.currentBlock = 0;
|
|
|
+
|
|
|
+ scsiDev.postDataOutHook = NULL;
|
|
|
+
|
|
|
+ scsiDev.needSyncNegotiationAck = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void process_SelectionPhase()
|
|
|
+{
|
|
|
+ // Selection delays.
|
|
|
+ // Many SCSI1 samplers that use a 5380 chip need a delay of at least 1ms.
|
|
|
+ // The Mac Plus boot-time (ie. rom code) selection abort time
|
|
|
+ // is < 1ms and must have no delay (standard suggests 250ms abort time)
|
|
|
+ // Most newer SCSI2 hosts don't care either way.
|
|
|
+ if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ s2s_delay_ms(1); // Simply won't work if set to 0.
|
|
|
+ }
|
|
|
+ else if (scsiDev.boardCfg.selectionDelay == 255) // auto
|
|
|
+ {
|
|
|
+ if (scsiDev.compatMode < COMPAT_SCSI2)
|
|
|
+ {
|
|
|
+ s2s_delay_ms(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (scsiDev.boardCfg.selectionDelay != 0)
|
|
|
+ {
|
|
|
+ s2s_delay_ms(scsiDev.boardCfg.selectionDelay);
|
|
|
+ }
|
|
|
+
|
|
|
+ uint8_t selStatus = *SCSI_STS_SELECTED;
|
|
|
+ if ((selStatus == 0) && (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SEL_LATCH))
|
|
|
+ {
|
|
|
+ selStatus = scsiDev.selFlag;
|
|
|
+ }
|
|
|
+
|
|
|
+ int tgtIndex;
|
|
|
+ TargetState* target = NULL;
|
|
|
+ for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
|
|
|
+ {
|
|
|
+ if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))
|
|
|
+ {
|
|
|
+ target = &scsiDev.targets[tgtIndex];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ((target != NULL) && (selStatus & 0x40))
|
|
|
+ {
|
|
|
+ // We've been selected!
|
|
|
+ // Assert BSY - Selection success!
|
|
|
+ // must happen within 200us (Selection abort time) of seeing our
|
|
|
+ // ID + SEL.
|
|
|
+ // (Note: the initiator will be waiting the "Selection time-out delay"
|
|
|
+ // for our BSY response, which is actually a very generous 250ms)
|
|
|
+ *SCSI_CTRL_BSY = 1;
|
|
|
+ s2s_ledOn();
|
|
|
+
|
|
|
+ scsiDev.target = target;
|
|
|
+
|
|
|
+ // Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says
|
|
|
+ // move to MESSAGE OUT if ATN is true before we assert BSY.
|
|
|
+ // The initiator should assert ATN with SEL.
|
|
|
+ scsiDev.atnFlag = selStatus & 0x80;
|
|
|
+
|
|
|
+
|
|
|
+ // Unit attention breaks many older SCSI hosts. Disable it completely
|
|
|
+ // for SCSI-1 (and older) hosts, regardless of our configured setting.
|
|
|
+ // Enable the compatability mode also as many SASI and SCSI1
|
|
|
+ // controllers don't generate parity bits.
|
|
|
+ if (!scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ target->unitAttention = 0;
|
|
|
+ scsiDev.compatMode = COMPAT_SCSI1;
|
|
|
+ }
|
|
|
+ else if (!(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SCSI2))
|
|
|
+ {
|
|
|
+ scsiDev.compatMode = COMPAT_SCSI2_DISABLED;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.compatMode = COMPAT_SCSI2;
|
|
|
+ }
|
|
|
+
|
|
|
+ scsiDev.selCount++;
|
|
|
+
|
|
|
+
|
|
|
+ // Save our initiator now that we're no longer in a time-critical
|
|
|
+ // section.
|
|
|
+ // SCSI1/SASI initiators may not set their own ID.
|
|
|
+ scsiDev.initiatorId = (selStatus >> 3) & 0x7;
|
|
|
+
|
|
|
+ // Wait until the end of the selection phase.
|
|
|
+ uint32_t selTimerBegin = s2s_getTime_ms();
|
|
|
+ while (likely(!scsiDev.resetFlag))
|
|
|
+ {
|
|
|
+ if (!scsiStatusSEL())
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (s2s_elapsedTime_ms(selTimerBegin) >= 10 &&
|
|
|
+ scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
|
|
|
+ {
|
|
|
+ // XEBEC hosts may not bother releasing SEL at all until
|
|
|
+ // just before the command ends.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (s2s_elapsedTime_ms(selTimerBegin) >= 250)
|
|
|
+ {
|
|
|
+ *SCSI_CTRL_BSY = 0;
|
|
|
+ scsiDev.resetFlag = 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ scsiDev.phase = COMMAND;
|
|
|
+ }
|
|
|
+ else if (!selStatus)
|
|
|
+ {
|
|
|
+ scsiDev.phase = BUS_BUSY;
|
|
|
+ }
|
|
|
+ scsiDev.selFlag = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void process_MessageOut()
|
|
|
+{
|
|
|
+ int wasNeedSyncNegotiationAck = scsiDev.needSyncNegotiationAck;
|
|
|
+ scsiDev.needSyncNegotiationAck = 0; // Successful on -most- messages.
|
|
|
+
|
|
|
+ scsiEnterPhase(MESSAGE_OUT);
|
|
|
+
|
|
|
+ scsiDev.atnFlag = 0;
|
|
|
+ scsiDev.msgOut = scsiReadByte();
|
|
|
+ scsiDev.msgCount++;
|
|
|
+
|
|
|
+ if (scsiParityError() &&
|
|
|
+ (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
|
|
|
+ {
|
|
|
+ // Skip the remaining message bytes, and then start the MESSAGE_OUT
|
|
|
+ // phase again from the start. The initiator will re-send the
|
|
|
+ // same set of messages.
|
|
|
+ while (scsiStatusATN() && !scsiDev.resetFlag)
|
|
|
+ {
|
|
|
+ scsiReadByte();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Go-back and try the message again.
|
|
|
+ scsiDev.atnFlag = 1;
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x00)
|
|
|
+ {
|
|
|
+ // COMMAND COMPLETE. but why would the target be receiving this ? nfi.
|
|
|
+ enter_BusFree();
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x06)
|
|
|
+ {
|
|
|
+ // ABORT
|
|
|
+ scsiDiskReset();
|
|
|
+ enter_BusFree();
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x0C)
|
|
|
+ {
|
|
|
+ // BUS DEVICE RESET
|
|
|
+
|
|
|
+ scsiDiskReset();
|
|
|
+
|
|
|
+ scsiDev.target->unitAttention = SCSI_BUS_RESET;
|
|
|
+
|
|
|
+ // ANY initiator can reset the reservation state via this message.
|
|
|
+ scsiDev.target->reservedId = -1;
|
|
|
+ scsiDev.target->reserverId = -1;
|
|
|
+
|
|
|
+ // Cancel any sync negotiation
|
|
|
+ scsiDev.target->syncOffset = 0;
|
|
|
+ scsiDev.target->syncPeriod = 0;
|
|
|
+
|
|
|
+ enter_BusFree();
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x05)
|
|
|
+ {
|
|
|
+ // Initiate Detected Error
|
|
|
+ // Ignore for now
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x0F)
|
|
|
+ {
|
|
|
+ // INITIATE RECOVERY
|
|
|
+ // Ignore for now
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x10)
|
|
|
+ {
|
|
|
+ // RELEASE RECOVERY
|
|
|
+ // Ignore for now
|
|
|
+ enter_BusFree();
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == MSG_REJECT)
|
|
|
+ {
|
|
|
+ // Message Reject
|
|
|
+ // Oh well.
|
|
|
+
|
|
|
+ if (wasNeedSyncNegotiationAck)
|
|
|
+ {
|
|
|
+ scsiDev.target->syncOffset = 0;
|
|
|
+ scsiDev.target->syncPeriod = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x08)
|
|
|
+ {
|
|
|
+ // NOP
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x09)
|
|
|
+ {
|
|
|
+ // Message Parity Error
|
|
|
+ // Go back and re-send the last message.
|
|
|
+ scsiDev.phase = MESSAGE_IN;
|
|
|
+
|
|
|
+ if (wasNeedSyncNegotiationAck)
|
|
|
+ {
|
|
|
+ scsiDev.target->syncOffset = 0;
|
|
|
+ scsiDev.target->syncPeriod = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF
|
|
|
+ {
|
|
|
+ // IDENTIFY
|
|
|
+ if ((scsiDev.msgOut & 0x18) || // Reserved bits set.
|
|
|
+ (scsiDev.msgOut & 0x20)) // We don't have any target routines!
|
|
|
+ {
|
|
|
+ messageReject();
|
|
|
+ }
|
|
|
+
|
|
|
+ scsiDev.lun = scsiDev.msgOut & 0x7;
|
|
|
+ scsiDev.discPriv =
|
|
|
+ ((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))
|
|
|
+ ? 1 : 0;
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)
|
|
|
+ {
|
|
|
+ // Two byte message. We don't support these. read and discard.
|
|
|
+ scsiReadByte();
|
|
|
+
|
|
|
+ if (scsiDev.msgOut == 0x23) {
|
|
|
+ // Ignore Wide Residue. We're only 8 bit anyway.
|
|
|
+ } else {
|
|
|
+ messageReject();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (scsiDev.msgOut == 0x01)
|
|
|
+ {
|
|
|
+ int i;
|
|
|
+
|
|
|
+ // Extended message.
|
|
|
+ int msgLen = scsiReadByte();
|
|
|
+ if (msgLen == 0) msgLen = 256;
|
|
|
+ uint8_t extmsg[256];
|
|
|
+ for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)
|
|
|
+ {
|
|
|
+ // Discard bytes.
|
|
|
+ extmsg[i] = scsiReadByte();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request
|
|
|
+ {
|
|
|
+ // Negotiate down to 8bit
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+ static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};
|
|
|
+ scsiWrite(WDTR, sizeof(WDTR));
|
|
|
+
|
|
|
+ // SDTR becomes invalidated.
|
|
|
+ scsiDev.target->syncOffset = 0;
|
|
|
+ scsiDev.target->syncPeriod = 0;
|
|
|
+ }
|
|
|
+ else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request
|
|
|
+ {
|
|
|
+ int oldPeriod = scsiDev.target->syncPeriod;
|
|
|
+ int oldOffset = scsiDev.target->syncOffset;
|
|
|
+
|
|
|
+ int transferPeriod = extmsg[1];
|
|
|
+ int offset = extmsg[2];
|
|
|
+
|
|
|
+ if ((
|
|
|
+ (transferPeriod > 0) &&
|
|
|
+ (transferPeriod < scsiDev.minSyncPeriod)) ||
|
|
|
+ (scsiDev.minSyncPeriod == 0))
|
|
|
+ {
|
|
|
+ scsiDev.minSyncPeriod = transferPeriod;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((transferPeriod > 80) || // 320ns, 3.125MB/s
|
|
|
+ // Amiga A590 (WD33C93 chip) only does 3.5MB/s sync
|
|
|
+ // After 80 we start to run out of bits in the fpga timing
|
|
|
+ // register.
|
|
|
+ (transferPeriod == 0) ||
|
|
|
+ (offset == 0) ||
|
|
|
+ ((scsiDev.boardCfg.scsiSpeed != S2S_CFG_SPEED_NoLimit) &&
|
|
|
+ (scsiDev.boardCfg.scsiSpeed <= S2S_CFG_SPEED_ASYNC_50)))
|
|
|
+ {
|
|
|
+ scsiDev.target->syncOffset = 0;
|
|
|
+ scsiDev.target->syncPeriod = 0;
|
|
|
+ } else {
|
|
|
+ scsiDev.target->syncOffset = offset <= 15 ? offset : 15;
|
|
|
+ // FAST20 / 50ns / 20MHz is disabled for now due to
|
|
|
+ // data corruption while reading data. We can count the
|
|
|
+ // ACK's correctly, but can't save the data to a register
|
|
|
+ // before it changes. (ie. transferPeriod == 12)
|
|
|
+ if ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO) &&
|
|
|
+ (transferPeriod <= 16))
|
|
|
+ {
|
|
|
+ scsiDev.target->syncPeriod = 16; // 15.6MB/s
|
|
|
+ }
|
|
|
+ else if (scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO)
|
|
|
+ {
|
|
|
+ scsiDev.target->syncPeriod = transferPeriod;
|
|
|
+ }
|
|
|
+ else if (transferPeriod <= 25 &&
|
|
|
+ ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
|
|
|
+ (scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
|
|
|
+ {
|
|
|
+ scsiDev.target->syncPeriod = 25; // 100ns, 10MB/s
|
|
|
+
|
|
|
+ } else if (transferPeriod < 50 &&
|
|
|
+ ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
|
|
|
+ (scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
|
|
|
+ {
|
|
|
+ scsiDev.target->syncPeriod = transferPeriod;
|
|
|
+ } else if (transferPeriod >= 50)
|
|
|
+ {
|
|
|
+ scsiDev.target->syncPeriod = transferPeriod;
|
|
|
+ } else {
|
|
|
+ scsiDev.target->syncPeriod = 50;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (transferPeriod != oldPeriod ||
|
|
|
+ scsiDev.target->syncPeriod != oldPeriod ||
|
|
|
+ offset != oldOffset ||
|
|
|
+ scsiDev.target->syncOffset != oldOffset ||
|
|
|
+ !wasNeedSyncNegotiationAck) // Don't get into infinite loops negotiating.
|
|
|
+ {
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+ uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset};
|
|
|
+ scsiWrite(SDTR, sizeof(SDTR));
|
|
|
+ scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected.
|
|
|
+ scsiDev.sdUnderrunCount = 0; // reset counter, may work now.
|
|
|
+
|
|
|
+ // Set to the theoretical speed, then adjust if we measure lower
|
|
|
+ // actual speeds.
|
|
|
+ scsiDev.hostSpeedKBs = s2s_getScsiRateKBs();
|
|
|
+ scsiDev.hostSpeedMeasured = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Not supported
|
|
|
+ messageReject();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ messageReject();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Re-check the ATN flag in case it stays asserted.
|
|
|
+ scsiDev.atnFlag |= scsiStatusATN();
|
|
|
+
|
|
|
+ if (!scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ // Message wasn't rejected!
|
|
|
+ scsiDev.needSyncNegotiationAck = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void scsiPoll(void)
|
|
|
+{
|
|
|
+ if (resetUntil != 0 && resetUntil > s2s_getTime_ms())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ resetUntil = 0;
|
|
|
+
|
|
|
+ if (unlikely(scsiDev.resetFlag))
|
|
|
+ {
|
|
|
+ scsiReset();
|
|
|
+ // Still in reset phase for a few ms.
|
|
|
+ // Do not try and process any commands.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (scsiDev.phase)
|
|
|
+ {
|
|
|
+ case BUS_FREE:
|
|
|
+ if (scsiStatusBSY())
|
|
|
+ {
|
|
|
+ scsiDev.phase = BUS_BUSY;
|
|
|
+ }
|
|
|
+ // The Arbitration phase is optional for SCSI1/SASI hosts if there is only
|
|
|
+ // one initiator in the chain. Support this by moving
|
|
|
+ // straight to selection if SEL is asserted.
|
|
|
+ // ie. the initiator won't assert BSY and it's own ID before moving to selection.
|
|
|
+ else if (scsiDev.selFlag || *SCSI_STS_SELECTED)
|
|
|
+ {
|
|
|
+ enter_SelectionPhase();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case BUS_BUSY:
|
|
|
+ // Someone is using the bus. Perhaps they are trying to
|
|
|
+ // select us.
|
|
|
+ if (scsiDev.selFlag || *SCSI_STS_SELECTED)
|
|
|
+ {
|
|
|
+ enter_SelectionPhase();
|
|
|
+ }
|
|
|
+ else if (!scsiStatusBSY())
|
|
|
+ {
|
|
|
+ scsiDev.phase = BUS_FREE;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ARBITRATION:
|
|
|
+ // TODO Support reselection.
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SELECTION:
|
|
|
+ process_SelectionPhase();
|
|
|
+ break;
|
|
|
+
|
|
|
+ case RESELECTION:
|
|
|
+ // Not currently supported!
|
|
|
+ break;
|
|
|
+
|
|
|
+ case COMMAND:
|
|
|
+ // Do not check ATN here. SCSI 1 & 2 initiators must set ATN
|
|
|
+ // and SEL together upon entering the selection phase if they
|
|
|
+ // want to send a message (IDENTIFY) immediately.
|
|
|
+ if (scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ process_MessageOut();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ process_Command();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case DATA_IN:
|
|
|
+ scsiDev.atnFlag |= scsiStatusATN();
|
|
|
+ if (scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ process_MessageOut();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ process_DataIn();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case DATA_OUT:
|
|
|
+ scsiDev.atnFlag |= scsiStatusATN();
|
|
|
+ if (scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ process_MessageOut();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ process_DataOut();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case STATUS:
|
|
|
+ scsiDev.atnFlag |= scsiStatusATN();
|
|
|
+ if (scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ process_MessageOut();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ process_Status();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case MESSAGE_IN:
|
|
|
+ scsiDev.atnFlag |= scsiStatusATN();
|
|
|
+ if (scsiDev.atnFlag)
|
|
|
+ {
|
|
|
+ process_MessageOut();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ process_MessageIn(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+
|
|
|
+ case MESSAGE_OUT:
|
|
|
+ process_MessageOut();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void scsiInit()
|
|
|
+{
|
|
|
+ static int firstInit = 1;
|
|
|
+
|
|
|
+ scsiDev.atnFlag = 0;
|
|
|
+ scsiDev.resetFlag = 1;
|
|
|
+ scsiDev.selFlag = 0;
|
|
|
+ scsiDev.phase = BUS_FREE;
|
|
|
+ scsiDev.target = NULL;
|
|
|
+ scsiDev.compatMode = COMPAT_UNKNOWN;
|
|
|
+ scsiDev.hostSpeedKBs = 0;
|
|
|
+ scsiDev.hostSpeedMeasured = 0;
|
|
|
+
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < S2S_MAX_TARGETS; ++i)
|
|
|
+ {
|
|
|
+ const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);
|
|
|
+ if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
|
|
|
+ {
|
|
|
+ scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;
|
|
|
+ scsiDev.targets[i].cfg = cfg;
|
|
|
+
|
|
|
+ scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.targets[i].targetId = 0xff;
|
|
|
+ scsiDev.targets[i].cfg = NULL;
|
|
|
+ }
|
|
|
+ scsiDev.targets[i].reservedId = -1;
|
|
|
+ scsiDev.targets[i].reserverId = -1;
|
|
|
+ if (firstInit)
|
|
|
+ {
|
|
|
+ scsiDev.targets[i].unitAttention = POWER_ON_RESET;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;
|
|
|
+ }
|
|
|
+ scsiDev.targets[i].sense.code = NO_SENSE;
|
|
|
+ scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
|
|
+
|
|
|
+ scsiDev.targets[i].syncOffset = 0;
|
|
|
+ scsiDev.targets[i].syncPeriod = 0;
|
|
|
+
|
|
|
+ // Always "start" the device. Many systems (eg. Apple System 7)
|
|
|
+ // won't respond properly to
|
|
|
+ // LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED sense
|
|
|
+ // code
|
|
|
+ scsiDev.targets[i].started = 1;
|
|
|
+ }
|
|
|
+ firstInit = 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* TODO REENABLE
|
|
|
+void scsiDisconnect()
|
|
|
+{
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+ scsiWriteByte(0x02); // save data pointer
|
|
|
+ scsiWriteByte(0x04); // disconnect msg.
|
|
|
+
|
|
|
+ // For now, the caller is responsible for tracking the disconnected
|
|
|
+ // state, and calling scsiReconnect.
|
|
|
+ // Ideally the client would exit their loop and we'd implement this
|
|
|
+ // as part of scsiPoll
|
|
|
+ int phase = scsiDev.phase;
|
|
|
+ enter_BusFree();
|
|
|
+ scsiDev.phase = phase;
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+/* TODO REENABLE
|
|
|
+int scsiReconnect()
|
|
|
+{
|
|
|
+ int reconnected = 0;
|
|
|
+
|
|
|
+ int sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
+ int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
+ if (!sel && !bsy)
|
|
|
+ {
|
|
|
+ s2s_delay_us(1);
|
|
|
+ sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
+ bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!sel && !bsy)
|
|
|
+ {
|
|
|
+ // Arbitrate.
|
|
|
+ s2s_ledOn();
|
|
|
+ uint8_t scsiIdMask = 1 << scsiDev.target->targetId;
|
|
|
+ SCSI_Out_Bits_Write(scsiIdMask);
|
|
|
+ SCSI_Out_Ctl_Write(1); // Write bits manually.
|
|
|
+ SCSI_SetPin(SCSI_Out_BSY);
|
|
|
+
|
|
|
+ s2s_delay_us(3); // arbitrate delay. 2.4us.
|
|
|
+
|
|
|
+ uint8_t dbx = scsiReadDBxPins();
|
|
|
+ sel = SCSI_ReadFilt(SCSI_Filt_SEL);
|
|
|
+ if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))
|
|
|
+ {
|
|
|
+ // Lost arbitration.
|
|
|
+ SCSI_Out_Ctl_Write(0);
|
|
|
+ SCSI_ClearPin(SCSI_Out_BSY);
|
|
|
+ s2s_ledOff();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Won arbitration
|
|
|
+ SCSI_SetPin(SCSI_Out_SEL);
|
|
|
+ s2s_delay_us(1); // Bus clear + Bus settle.
|
|
|
+
|
|
|
+ // Reselection phase
|
|
|
+ SCSI_CTL_PHASE_Write(__scsiphase_io);
|
|
|
+ SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));
|
|
|
+ scsiDeskewDelay(); // 2 deskew delays
|
|
|
+ scsiDeskewDelay(); // 2 deskew delays
|
|
|
+ SCSI_ClearPin(SCSI_Out_BSY);
|
|
|
+ s2s_delay_us(1); // Bus Settle Delay
|
|
|
+
|
|
|
+ uint32_t waitStart_ms = getTime_ms();
|
|
|
+ bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
+ // Wait for initiator.
|
|
|
+ while (
|
|
|
+ !bsy &&
|
|
|
+ !scsiDev.resetFlag &&
|
|
|
+ (elapsedTime_ms(waitStart_ms) < 250))
|
|
|
+ {
|
|
|
+ bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bsy)
|
|
|
+ {
|
|
|
+ SCSI_SetPin(SCSI_Out_BSY);
|
|
|
+ scsiDeskewDelay(); // 2 deskew delays
|
|
|
+ scsiDeskewDelay(); // 2 deskew delays
|
|
|
+ SCSI_ClearPin(SCSI_Out_SEL);
|
|
|
+
|
|
|
+ // Prepare for the initial IDENTIFY message.
|
|
|
+ SCSI_Out_Ctl_Write(0);
|
|
|
+ scsiEnterPhase(MESSAGE_IN);
|
|
|
+
|
|
|
+ // Send identify command
|
|
|
+ scsiWriteByte(0x80);
|
|
|
+
|
|
|
+ scsiEnterPhase(scsiDev.phase);
|
|
|
+ reconnected = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // reselect timeout.
|
|
|
+ SCSI_Out_Ctl_Write(0);
|
|
|
+ SCSI_ClearPin(SCSI_Out_SEL);
|
|
|
+ SCSI_CTL_PHASE_Write(0);
|
|
|
+ s2s_ledOff();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return reconnected;
|
|
|
+}
|
|
|
+*/
|
|
|
+
|