757 lines
26 KiB
C
757 lines
26 KiB
C
/*
|
|
* Copyright (c) 2013, Peter Rutenbar <pruten@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include "shoebill.h"
|
|
|
|
// Target command register bits
|
|
#define TARGET_COMM_LAST_BYTE_SENT (1<<7)
|
|
#define TARGET_COMM_ASSERT_REQ (1<<3)
|
|
#define TARGET_COMM_ASSERT_MSG (1<<2)
|
|
#define TARGET_COMM_ASSERT_CD (1<<1)
|
|
#define TARGET_COMM_ASSERT_IO (1<<0)
|
|
|
|
// Initiator command register bits
|
|
#define INIT_COMM_ASSERT_RST (1<<7)
|
|
#define INIT_COMM_TEST_MODE (1<<6) // write-only
|
|
#define INIT_COMM_ARBITRATION_IN_PROGRESS (1<<6) // read-only
|
|
#define INIT_COMM_LOST_ARBITRATION (1<<5) // read-only
|
|
#define INIT_COMM_ASSERT_ACK (1<<4)
|
|
#define INIT_COMM_ASSERT_BSY (1<<3)
|
|
#define INIT_COMM_ASSERT_SEL (1<<2)
|
|
#define INIT_COMM_ASSERT_ATN (1<<1)
|
|
#define INIT_COMM_ASSERT_DATA_BUS (1<<0)
|
|
|
|
// Current scsi control register bits
|
|
#define CURR_SCSI_CONTROL_RST (1<<7)
|
|
#define CURR_SCSI_CONTROL_BSY (1<<6)
|
|
#define CURR_SCSI_CONTROL_REQ (1<<5)
|
|
#define CURR_SCSI_CONTROL_MSG (1<<4)
|
|
#define CURR_SCSI_CONTROL_CD (1<<3)
|
|
#define CURR_SCSI_CONTROL_IO (1<<2)
|
|
#define CURR_SCSI_CONTROL_SEL (1<<1)
|
|
#define CURR_SCSI_CONTROL_PARITY (1<<0)
|
|
|
|
// Bus and status register bits
|
|
#define BUS_STATUS_ACK (1<<0)
|
|
#define BUS_STATUS_ATN (1<<1)
|
|
#define BUS_STATUS_BUSY_ERROR (1<<2)
|
|
#define BUS_STATUS_PHASE_MATCH (1<<3)
|
|
#define BUS_STATUS_INTERRUPT_REQUEST_ACTIVE (1<<4)
|
|
#define BUS_STATUS_PARITY_ERROR (1<<5)
|
|
#define BUS_STATUS_DMA_REQUEST (1<<6)
|
|
#define BUS_STATUS_END_OF_DMA (1<<7)
|
|
|
|
// Mode register bits
|
|
#define MODE_BLOCK_MODE_DMA (1<<7)
|
|
#define MODE_TARGET_MODE (1<<6)
|
|
#define MODE_ENABLE_PARITY_CHECKING (1<<5)
|
|
#define MODE_ENABLE_PARITY_INTERRUPT (1<<4)
|
|
#define MODE_ENABLE_EOP_INTERRUPT (1<<3)
|
|
#define MODE_MONITOR_BUSY (1<<2)
|
|
#define MODE_DMA_MODE (1<<1)
|
|
#define MODE_ARBITRATE (1<<0)
|
|
|
|
// Invalid scsi device ID
|
|
#define INVALID_ID 8
|
|
|
|
const char *scsi_read_reg_str[8] = {
|
|
"current_scsi_data_bus",
|
|
"initiator_command",
|
|
"mode",
|
|
"target_command",
|
|
"current_scsi_control",
|
|
"bus_and_status",
|
|
"input_data",
|
|
"reset_interrupt"
|
|
};
|
|
|
|
const char *scsi_write_reg_str[8] = {
|
|
"output_data",
|
|
"initiator_command",
|
|
"mode",
|
|
"target_command",
|
|
"id_select",
|
|
"start_dma_send",
|
|
"start_dma_target_receive",
|
|
"start_dma_initiator_receive"
|
|
};
|
|
|
|
static void scsi_raise_irq() {via_raise_interrupt(2, IFR_CB2);}
|
|
static void scsi_raise_drq() {via_raise_interrupt(2, IFR_CA2);}
|
|
|
|
static _Bool phase_match (void)
|
|
{
|
|
uint8_t phase_tmp = shoe.scsi.msg;
|
|
phase_tmp = (phase_tmp << 1) | shoe.scsi.cd;
|
|
phase_tmp = (phase_tmp << 1) | shoe.scsi.io;
|
|
return (phase_tmp == (shoe.scsi.target_command & 7));
|
|
}
|
|
|
|
static void switch_status_phase (uint8_t status_byte)
|
|
{
|
|
slog("scsi_reg_something: switching to STATUS phase\n");
|
|
|
|
shoe.scsi.phase = STATUS;
|
|
shoe.scsi.status_byte = status_byte;
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.cd = 1;
|
|
shoe.scsi.io = 1;
|
|
|
|
shoe.scsi.bufi = 0;
|
|
|
|
// Phase mismatch (I think)
|
|
scsi_raise_drq();
|
|
}
|
|
|
|
static void switch_command_phase (void)
|
|
{
|
|
slog("scsi_reg_something: switching to COMMAND phase\n");
|
|
shoe.scsi.phase = COMMAND;
|
|
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.cd = 1;
|
|
shoe.scsi.io = 0;
|
|
|
|
shoe.scsi.bufi = 0;
|
|
|
|
// Phase mismatch, probably
|
|
scsi_raise_drq();
|
|
}
|
|
|
|
static void switch_message_in_phase (uint8_t message_byte)
|
|
{
|
|
slog("scsi_reg_something: switching to MESSAGE_IN phase\n");
|
|
|
|
shoe.scsi.phase = MESSAGE_IN;
|
|
shoe.scsi.msg = 1;
|
|
shoe.scsi.cd = 1;
|
|
shoe.scsi.io = 1;
|
|
|
|
shoe.scsi.message_byte = message_byte; // only one-byte messages supported for now
|
|
|
|
// Phase mismatch, probably
|
|
scsi_raise_drq();
|
|
}
|
|
|
|
static void switch_bus_free_phase (void)
|
|
{
|
|
slog("scsi_reg_something: switching to BUS_FREE phase\n");
|
|
|
|
shoe.scsi.phase = BUS_FREE;
|
|
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.cd = 0;
|
|
shoe.scsi.io = 0;
|
|
|
|
shoe.scsi.target_bsy = 0;
|
|
shoe.scsi.req = 0;
|
|
|
|
shoe.scsi.bufi = 0;
|
|
// Phase mismatch not possible here.
|
|
}
|
|
|
|
static void switch_data_in_phase (void)
|
|
{
|
|
slog("scsi_reg_something: switching to DATA_IN phase\n");
|
|
|
|
shoe.scsi.phase = DATA_IN;
|
|
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.cd = 0;
|
|
shoe.scsi.io = 1;
|
|
|
|
// Phase mismatch, probably
|
|
scsi_raise_drq();
|
|
}
|
|
|
|
static void switch_data_out_phase (void)
|
|
{
|
|
slog("scsi_reg_something: switching to DATA_OUT phase\n");
|
|
|
|
shoe.scsi.phase = DATA_OUT;
|
|
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.cd = 0;
|
|
shoe.scsi.io = 0;
|
|
|
|
scsi_raise_drq();
|
|
}
|
|
|
|
struct inquiry_response_t {
|
|
uint8_t periph_device_type:5;
|
|
uint8_t periph_qualifier:3;
|
|
|
|
uint8_t device_type_modifier:7;
|
|
uint8_t rmb:1;
|
|
|
|
uint8_t ansi_vers:3;
|
|
uint8_t ecma_vers:3;
|
|
uint8_t iso_vers:2;
|
|
|
|
uint8_t resp_format:4;
|
|
uint8_t unused_1:2;
|
|
uint8_t trmiop:1;
|
|
uint8_t anec:1;
|
|
|
|
uint8_t additional_length;
|
|
|
|
uint8_t unused_2;
|
|
uint8_t unused_3;
|
|
|
|
uint8_t sftre:1;
|
|
uint8_t cmdque:1;
|
|
uint8_t unused_4:1;
|
|
uint8_t linked:1;
|
|
uint8_t sync:1;
|
|
uint8_t wbus16:1;
|
|
uint8_t wbus32:1;
|
|
uint8_t reladr:1;
|
|
|
|
uint8_t vendor_id[8];
|
|
uint8_t product_id[16];
|
|
uint8_t product_rev[4];
|
|
};
|
|
|
|
static void scsi_handle_inquiry_command(const uint8_t alloc_len)
|
|
{
|
|
struct inquiry_response_t resp;
|
|
|
|
assert(alloc_len >= 6);
|
|
|
|
memset(&resp, 0, sizeof(struct inquiry_response_t));
|
|
|
|
resp.periph_qualifier = 0; // I'm connected to the specified LUN (sure, whatever)
|
|
resp.periph_device_type = 0; // 0 -> direct access device (hard disk)
|
|
|
|
resp.rmb = 0; // not removable
|
|
resp.device_type_modifier = 0; // Vendor-specific
|
|
|
|
resp.ansi_vers = 1; // complies with ANSI X3.131-1986 (SCSI-1)
|
|
resp.ecma_vers = 1; // whatever
|
|
resp.iso_vers = 1; // whatever
|
|
|
|
resp.anec = 0; // only applies to "processor" devices
|
|
resp.trmiop = 0; // we don't support TERMINATE I/O PROCESS, whatever that is
|
|
resp.resp_format = 0; // SCSI-1 INQUIRY response format
|
|
|
|
resp.additional_length = 0; // no additional parameters
|
|
|
|
resp.reladr = 0; // don't support relative addressing
|
|
resp.wbus16 = 0; // these must be SCSI-2 things. (I'm looking at the ANSI SCSI-2 docs)
|
|
resp.wbus32 = 0;
|
|
resp.sync = 0; // don't support synchronous transfer
|
|
resp.cmdque = 0; // don't support tagged command queuing
|
|
resp.linked = 0; // don't support linked commands
|
|
resp.sftre = 0; // don't support soft reset
|
|
|
|
memcpy(resp.vendor_id, "Shoebill", 8);
|
|
strcpy((char*)resp.product_id, "Phony SCSI disk");
|
|
memcpy(resp.product_rev, "fded", 4);
|
|
|
|
// XXX: added this because A/UX 3.0.1 requsts 6 bytes of the the inquiry response (sometimes?) I think it's polling for all attached scsi devices.
|
|
// Fixme: figure out how to respond "not attached"
|
|
if (alloc_len > sizeof(resp))
|
|
shoe.scsi.in_len = sizeof(resp);
|
|
else
|
|
shoe.scsi.in_len = alloc_len;
|
|
memcpy(shoe.scsi.buf, &resp, shoe.scsi.in_len);
|
|
shoe.scsi.in_i = 0;
|
|
|
|
switch_data_in_phase();
|
|
}
|
|
|
|
static void scsi_buf_set (uint8_t byte)
|
|
{
|
|
assert(shoe.scsi.bufi <= sizeof(shoe.scsi.buf));
|
|
shoe.scsi.buf[shoe.scsi.bufi++] = byte;
|
|
|
|
if (shoe.scsi.phase == COMMAND) {
|
|
const uint32_t cmd_len = (shoe.scsi.buf[0] >= 0x20) ? 10 : 6; // 10 or 6 byte command?
|
|
|
|
assert(shoe.scsi.target_id < 8);
|
|
scsi_device_t *dev = &shoe.scsi_devices[shoe.scsi.target_id];
|
|
|
|
// If we need more data for this command, keep driving REQ
|
|
if (shoe.scsi.bufi < cmd_len) {
|
|
// shoe.scsi.req = 1;
|
|
// FIXME: keep driving DMA_REQUEST too
|
|
return ;
|
|
}
|
|
|
|
switch (shoe.scsi.buf[0]) {
|
|
case 0: // test unit ready (6)
|
|
slog("scsi_buf_set: responding to test-unit-ready\n");
|
|
switch_status_phase(0); // switch to the status phase, with a status byte of 0
|
|
break;
|
|
|
|
case 0x3: { // request sense (6)
|
|
const uint8_t alloc_len = shoe.scsi.buf[4];
|
|
const uint8_t control = shoe.scsi.buf[5];
|
|
const _Bool desc = shoe.scsi.buf[1] & 1;
|
|
|
|
switch_status_phase(2); // CHECK_CONDITION
|
|
|
|
break;
|
|
}
|
|
|
|
case 0x8: { // read (6)
|
|
const uint32_t offset =
|
|
(shoe.scsi.buf[1] << 16) |
|
|
(shoe.scsi.buf[2] << 8 ) |
|
|
(shoe.scsi.buf[3]);
|
|
const uint16_t len = (shoe.scsi.buf[4]==0) ? 0x100 : shoe.scsi.buf[4]; // len==0 -> 256 sectors
|
|
|
|
assert(dev->f);
|
|
|
|
slog("scsi_buf_set: Responding to read at off=%u len=%u\n", offset, len);
|
|
|
|
//assert(len <= 64);
|
|
|
|
assert(dev->num_blocks > offset);
|
|
|
|
if (len == 0) {
|
|
switch_status_phase(0);
|
|
break;
|
|
}
|
|
|
|
assert(0 == fseeko(dev->f, 512 * offset, SEEK_SET));
|
|
assert(fread(shoe.scsi.buf, len * 512, 1, dev->f) == 1);
|
|
|
|
|
|
shoe.scsi.in_len = len * 512;
|
|
shoe.scsi.in_i = 0;
|
|
|
|
switch_data_in_phase();
|
|
break;
|
|
}
|
|
|
|
case 0xa: { // write (6)
|
|
const uint32_t offset =
|
|
(shoe.scsi.buf[1] << 16) |
|
|
(shoe.scsi.buf[2] << 8 ) |
|
|
(shoe.scsi.buf[3]);
|
|
const uint16_t len = (shoe.scsi.buf[4]==0) ? 0x100 : shoe.scsi.buf[4]; // len==0 -> 256 sectors
|
|
|
|
slog("scsi_buf_set: Responding to write at off=%u len=%u\n", offset, len);
|
|
|
|
//assert(len <= 64);
|
|
|
|
shoe.scsi.write_offset = offset;
|
|
shoe.scsi.out_len = len * 512;
|
|
shoe.scsi.out_i = 0;
|
|
|
|
shoe.scsi.dma_send_written = 0; // reset here. The real data will come in after start_dma_send is written to.
|
|
switch_data_out_phase();
|
|
break;
|
|
}
|
|
|
|
case 0x12: { // inquiry command (6)
|
|
slog("scsi_buf_set: responding to inquiry\n");
|
|
const uint8_t alloc_len = shoe.scsi.buf[4];
|
|
|
|
scsi_handle_inquiry_command(alloc_len);
|
|
break;
|
|
}
|
|
|
|
case 0x15: // mode select (6)
|
|
slog("scsi_buf_set: responding to mode-select\n");
|
|
switch_status_phase(0);
|
|
break;
|
|
|
|
case 0x1a: { // mode sense (6)
|
|
const _Bool dbd = (shoe.scsi.buf[1] >> 3) & 1;
|
|
const uint8_t pc = (shoe.scsi.buf[2] >> 6) & 3;
|
|
const uint8_t page_code = shoe.scsi.buf[2] & 0x3f;
|
|
const uint8_t subpage_code = shoe.scsi.buf[3];
|
|
const uint8_t alloc_len = shoe.scsi.buf[4];
|
|
const uint8_t control = shoe.scsi.buf[6];
|
|
|
|
slog("scsi_bug_set: responding to mode-sense\n");
|
|
slog("dbd=%u pc=%u page_code=%u subpage_code=%u alloc_len=%u control=%u\n",
|
|
dbd, pc, page_code, subpage_code, alloc_len, control);
|
|
|
|
|
|
// FIXME: set sense code!
|
|
switch_status_phase(2); // CHECK_CONDITION
|
|
}
|
|
|
|
case 0x25: // read capacity (10)
|
|
slog("scsi_buf_set: responding to read-capacity\n");
|
|
// bytes [0,3] -> BE number of blocks
|
|
shoe.scsi.buf[0] = (dev->num_blocks >> 24) & 0xff;
|
|
shoe.scsi.buf[1] = (dev->num_blocks >> 16) & 0xff;
|
|
shoe.scsi.buf[2] = (dev->num_blocks >> 8) & 0xff;
|
|
shoe.scsi.buf[3] = (dev->num_blocks) & 0xff;
|
|
|
|
// bytes [4,7] -> BE block size (needs to be 512)
|
|
|
|
shoe.scsi.buf[4] = (dev->block_size >> 24) & 0xff;
|
|
shoe.scsi.buf[5] = (dev->block_size >> 16) & 0xff;
|
|
shoe.scsi.buf[6] = (dev->block_size >> 8) & 0xff;
|
|
shoe.scsi.buf[7] = (dev->block_size) & 0xff;
|
|
|
|
shoe.scsi.in_i = 0;
|
|
shoe.scsi.in_len = 8;
|
|
|
|
switch_data_in_phase();
|
|
break;
|
|
|
|
case 0x28: { // read (10)
|
|
|
|
// FIXME: set sense code!
|
|
switch_status_phase(2); // CHECK_CONDITION
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
printf("unknown scsi command (0x%02x)\n", shoe.scsi.buf[0]);
|
|
// FIXME: set sense code
|
|
switch_status_phase(2); // CHECK_CONDITION
|
|
break;
|
|
}
|
|
|
|
shoe.scsi.bufi = 0;
|
|
}
|
|
}
|
|
|
|
void init_scsi_bus_state ()
|
|
{
|
|
memset(&shoe.scsi, 0, sizeof(scsi_bus_state_t));
|
|
|
|
shoe.scsi.phase = BUS_FREE;
|
|
}
|
|
|
|
void reset_scsi_bus_state ()
|
|
{
|
|
memset(&shoe.scsi, 0, sizeof(scsi_bus_state_t));
|
|
|
|
shoe.scsi.phase = BUS_FREE;
|
|
}
|
|
|
|
void scsi_reg_read ()
|
|
{
|
|
const uint32_t reg = ((shoe.physical_addr & 0xffff) >> 4) & 0xf;
|
|
|
|
//slog("\nscsi_reg_read: reading from register %s(%u) ", scsi_read_reg_str[reg], reg);
|
|
|
|
switch (reg) {
|
|
case 0: // Current scsi data bus register
|
|
if (shoe.scsi.phase == ARBITRATION)
|
|
shoe.physical_dat = 0; // I don't know why A/UX expects 0 here. It should be the initiator's ID, I think
|
|
else if (shoe.scsi.phase == MESSAGE_IN) {
|
|
shoe.physical_dat = shoe.scsi.message_byte; // one-byte messages supported for now
|
|
}
|
|
else if (shoe.scsi.phase == STATUS) {
|
|
shoe.physical_dat = shoe.scsi.status_byte;
|
|
shoe.scsi.sent_status_byte_via_reg0 = 1;
|
|
}
|
|
else
|
|
assert(!"scsi_reg_read: reading data reg (0) from unknown phase\n");
|
|
|
|
break;
|
|
|
|
case 1: // Initiator command register
|
|
|
|
if (shoe.scsi.phase == ARBITRATION &&
|
|
(shoe.scsi.initiator_command & INIT_COMM_ARBITRATION_IN_PROGRESS)) {
|
|
|
|
shoe.physical_dat = shoe.scsi.initiator_command;
|
|
|
|
// the INIT_COMM_ARBITRATION_IN_PROGRESS bit is transient. Just clear
|
|
// it after the first access (it needs to go hi, then later low)
|
|
shoe.scsi.initiator_command &= ~INIT_COMM_ARBITRATION_IN_PROGRESS;
|
|
|
|
}
|
|
else
|
|
shoe.physical_dat = shoe.scsi.initiator_command;
|
|
|
|
break;
|
|
|
|
case 2: // Mode register
|
|
shoe.physical_dat = shoe.scsi.mode;
|
|
break;
|
|
|
|
case 3: // Target command register
|
|
shoe.physical_dat = shoe.scsi.target_command & 0xf; // only the low 4 bits are significant
|
|
break;
|
|
|
|
case 4: { // Current SCSI control register
|
|
uint8_t tmp = 0;
|
|
tmp |= (shoe.scsi.sel * CURR_SCSI_CONTROL_SEL);
|
|
tmp |= (shoe.scsi.io * CURR_SCSI_CONTROL_IO);
|
|
tmp |= (shoe.scsi.cd * CURR_SCSI_CONTROL_CD);
|
|
tmp |= (shoe.scsi.msg * CURR_SCSI_CONTROL_MSG);
|
|
tmp |= (shoe.scsi.req * CURR_SCSI_CONTROL_REQ);
|
|
tmp |= ((shoe.scsi.target_bsy || shoe.scsi.init_bsy) ? CURR_SCSI_CONTROL_BSY : 0);
|
|
tmp |= (shoe.scsi.rst * CURR_SCSI_CONTROL_RST);
|
|
shoe.physical_dat = tmp;
|
|
break;
|
|
}
|
|
|
|
case 5: { // Bus and status register
|
|
uint8_t tmp = 0;
|
|
|
|
// Compute phase match (IO, CD, MSG match the assertions in target_command register)
|
|
uint8_t phase_tmp = phase_match();
|
|
|
|
tmp |= (shoe.scsi.ack * BUS_STATUS_ACK);
|
|
tmp |= (shoe.scsi.atn * BUS_STATUS_ATN);
|
|
tmp |= (phase_tmp * BUS_STATUS_PHASE_MATCH);
|
|
|
|
// let's just say BUS_ERROR is always false (fixme: wrong)
|
|
// let's just say INTERRUPT_REQUEST_ACTIVE is always false (fixme: wrong)
|
|
// let's just say PARITY_ERROR is always false
|
|
tmp |= BUS_STATUS_DMA_REQUEST; // let's just say DMA_REQUEST is always true (fixme: wrong)
|
|
shoe.physical_dat = tmp;
|
|
break;
|
|
}
|
|
|
|
case 6: // Input data register
|
|
shoe.physical_dat = 0;
|
|
break;
|
|
|
|
case 7: // Reset error / Interrupt register
|
|
shoe.physical_dat = 0;
|
|
break;
|
|
}
|
|
|
|
//slog("(set to 0x%02x)\n\n", (uint32_t)shoe.physical_dat);
|
|
}
|
|
|
|
void scsi_reg_write ()
|
|
{
|
|
const uint32_t reg = ((shoe.physical_addr & 0xffff) >> 4) & 0xf;
|
|
const uint8_t dat = shoe.physical_dat & 0xff;
|
|
|
|
switch (reg) {
|
|
case 0: // Output data register
|
|
shoe.scsi.data = dat;
|
|
break;
|
|
|
|
case 1: { // Initiator command register
|
|
shoe.scsi.initiator_command = dat;
|
|
|
|
shoe.scsi.ack = ((shoe.scsi.initiator_command & INIT_COMM_ASSERT_ACK) != 0);
|
|
shoe.scsi.rst = ((shoe.scsi.initiator_command & INIT_COMM_ASSERT_RST) != 0);
|
|
shoe.scsi.init_bsy = ((shoe.scsi.initiator_command & INIT_COMM_ASSERT_BSY) != 0);
|
|
shoe.scsi.sel = ((shoe.scsi.initiator_command & INIT_COMM_ASSERT_SEL) != 0);
|
|
shoe.scsi.atn = ((shoe.scsi.initiator_command & INIT_COMM_ASSERT_ATN) != 0);
|
|
|
|
/*
|
|
// --- Arbitration ---
|
|
// Check whether to switch from ARBITRATION to SELECTION
|
|
if (shoe.scsi.sel && shoe.scsi.phase == ARBITRATION) {
|
|
// Asserting SEL in arbitration phase means we switch to selection phase :)
|
|
shoe.scsi.phase = SELECTION;
|
|
shoe.scsi.target_id = INVALID_ID; // invalid ID
|
|
slog("scsi_reg_write: selection phase\n");
|
|
break;
|
|
}
|
|
*/
|
|
// --- Selection ---
|
|
// If we're in SELECTION, receive the target_id from shoe.scsi.data
|
|
if (shoe.scsi.sel && (shoe.scsi.initiator_command & INIT_COMM_ASSERT_DATA_BUS) &&
|
|
((shoe.scsi.phase == ARBITRATION) || (shoe.scsi.phase == BUS_FREE)))
|
|
{
|
|
uint8_t id;
|
|
for (id=0; (id < 8) && !(shoe.scsi.data & (1 << id)); id++) ;
|
|
assert(id != 8);
|
|
shoe.scsi.target_id = id;
|
|
slog("scsi_reg_write: selected target id %u\n", id);
|
|
|
|
if (shoe.scsi_devices[shoe.scsi.target_id].f == NULL) {
|
|
shoe.scsi.phase = BUS_FREE;
|
|
}
|
|
else {
|
|
shoe.scsi.target_bsy = 1; // target asserts BSY to acknowledge being selected
|
|
shoe.scsi.phase = SELECTION;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// SELECTION ends when SEL gets unset
|
|
if (!shoe.scsi.sel && shoe.scsi.phase == SELECTION) {
|
|
slog("scsi_reg_write: switch to COMMAND phase\n"); // what's next?
|
|
|
|
shoe.scsi.req = 1; // target asserts REQ after initiator deasserts SEL
|
|
|
|
// Switch to COMMAND phase
|
|
shoe.scsi.cd = 1;
|
|
shoe.scsi.io = 0;
|
|
shoe.scsi.msg = 0;
|
|
shoe.scsi.phase = COMMAND;
|
|
break;
|
|
}
|
|
|
|
// --- Information transfer ---
|
|
// If initiator asserts ACK, then target needs to deassert REQ
|
|
// (I think this only makes sense for non-arbitration/selection/busfree situations
|
|
if ((shoe.scsi.phase != BUS_FREE) && (shoe.scsi.phase != ARBITRATION) && (shoe.scsi.phase != SELECTION)) {
|
|
|
|
// If this is the message_in phase, use the unsetting-ACK portion of the REQ/ACK handshake
|
|
// to go to BUS_FREE.
|
|
// Don't bother asserting REQ here. Also, switch_bus_free_phase() will deassert target_BSY.
|
|
if (shoe.scsi.phase == MESSAGE_IN && !shoe.scsi.ack && !shoe.scsi.req) {
|
|
switch_bus_free_phase();
|
|
break ;
|
|
}
|
|
// If the status byte was read through register 0, then we need to manually switch to
|
|
// message_in phase when the initiator sets ACK
|
|
// Do this when the OS deasserts ACK. We know that ACK was previously asserted if !REQ.
|
|
// (This is kinda hacky - maybe I can detect if ACK is deasserted by looking at the
|
|
// previous value of reg1)
|
|
else if (shoe.scsi.phase == STATUS && !shoe.scsi.ack && !shoe.scsi.req && shoe.scsi.sent_status_byte_via_reg0) {
|
|
shoe.scsi.req = 1;
|
|
switch_message_in_phase(0);
|
|
}
|
|
else {
|
|
shoe.scsi.req = !shoe.scsi.ack;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 2: { // Mode register
|
|
shoe.scsi.mode = dat;
|
|
|
|
if (shoe.scsi.mode & MODE_ARBITRATE) {
|
|
slog("scsi_reg_write: arbitration phase\n");
|
|
shoe.scsi.phase = ARBITRATION;
|
|
shoe.scsi.initiator_command |= INIT_COMM_ARBITRATION_IN_PROGRESS;
|
|
}
|
|
else {
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 3: // Target command register
|
|
shoe.scsi.target_command = dat & 0xf; // only the bottom 4 bits are writable
|
|
break;
|
|
|
|
case 4: // ID select register
|
|
shoe.scsi.select_enable = dat;
|
|
break;
|
|
|
|
case 5: // Start DMA send
|
|
shoe.scsi.dma_send_written = 1;
|
|
scsi_raise_drq();
|
|
break;
|
|
|
|
case 6: // Start DMA target receive
|
|
break;
|
|
|
|
case 7: // Start DMA initiator receive
|
|
scsi_raise_drq();
|
|
break;
|
|
}
|
|
|
|
slog("\nscsi_reg_write: writing to register %s(%u) (0x%x)\n\n", scsi_write_reg_str[reg], reg, dat);
|
|
}
|
|
|
|
void scsi_dma_write_long(const uint32_t dat)
|
|
{
|
|
scsi_dma_write((dat >> 24) & 0xff);
|
|
scsi_dma_write((dat >> 16) & 0xff);
|
|
scsi_dma_write((dat >> 8 ) & 0xff);
|
|
scsi_dma_write(dat & 0xff);
|
|
}
|
|
|
|
void scsi_dma_write (const uint8_t byte)
|
|
{
|
|
if (shoe.scsi.phase == COMMAND) {
|
|
slog("scsi_reg_dma_write: writing COMMAND byte 0x%02x\n", byte);
|
|
scsi_buf_set(byte);
|
|
}
|
|
else if (shoe.scsi.phase == DATA_OUT && shoe.scsi.dma_send_written) {
|
|
shoe.scsi.buf[shoe.scsi.out_i++] = byte;
|
|
|
|
//slog("scsi_reg_dma_write: writing DATA_OUT byte 0x%02x (%c)\n", byte, isprint(byte)?byte:'.');
|
|
|
|
if (shoe.scsi.out_i >= shoe.scsi.out_len) {
|
|
assert(shoe.scsi.target_id < 8);
|
|
scsi_device_t *dev = &shoe.scsi_devices[shoe.scsi.target_id];
|
|
assert(dev->f);
|
|
|
|
assert(0 == fseeko(dev->f, 512 * shoe.scsi.write_offset, SEEK_SET));
|
|
assert(fwrite(shoe.scsi.buf, shoe.scsi.out_len, 1, dev->f) == 1);
|
|
fflush(dev->f);
|
|
|
|
shoe.scsi.out_i = 0;
|
|
shoe.scsi.out_len = 0;
|
|
switch_status_phase(0);
|
|
}
|
|
}
|
|
else if (shoe.scsi.phase == DATA_OUT) {
|
|
slog("scsi_reg_dma_write: writing DATA_OUT byte (without shoe.scsi.dma_send_written) 0x%02x\n", byte);
|
|
}
|
|
else {
|
|
slog("scsi_reg_dma_write: writing 0x%02x in UNKNOWN PHASE!\n", byte);
|
|
}
|
|
|
|
}
|
|
|
|
uint32_t scsi_dma_read_long()
|
|
{
|
|
uint32_t i, result = 0;
|
|
|
|
for (i=0; i<4; i++) {
|
|
result = (result << 8) + scsi_dma_read();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
uint8_t scsi_dma_read ()
|
|
{
|
|
uint8_t result = 0;
|
|
|
|
if (shoe.scsi.phase == STATUS) {
|
|
// If in the STATUS phase, return the status byte and switch back to COMMAND phase
|
|
result = shoe.scsi.status_byte;
|
|
switch_message_in_phase(0);
|
|
}
|
|
else if (shoe.scsi.phase == DATA_IN) {
|
|
assert(shoe.scsi.in_len > 0);
|
|
result = shoe.scsi.buf[shoe.scsi.in_i++];
|
|
if (shoe.scsi.in_i >= shoe.scsi.in_len) {
|
|
shoe.scsi.in_i = 0;
|
|
shoe.scsi.in_len = 0;
|
|
|
|
switch_status_phase(0);
|
|
}
|
|
}
|
|
|
|
//slog("scsi_reg_dma_read: called, returning 0x%02x\n", (uint8_t)result);
|
|
|
|
return result;
|
|
}
|