/* * Copyright (c) 2013, Peter Rutenbar * 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 #include #include #include #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; }