pcem/src/scsi_ibm.c

1278 lines
54 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include "ibm.h"
#include "device.h"
#include "dma.h"
#include "io.h"
#include "mca.h"
#include "mem.h"
#include "pic.h"
#include "rom.h"
#include "scsi.h"
#include "timer.h"
#include "scsi_ibm.h"
#define SCSI_TIME (20)
#define POLL_TIME (TIMER_USEC * 10)
#define MAX_BYTES_TRANSFERRED_PER_POLL 50
extern char ide_fn[4][512];
typedef enum
{
SCB_STATE_IDLE,
SCB_STATE_START,
SCB_STATE_WAIT,
SCB_STATE_COMPLETE
} sbc_state_t;
typedef enum
{
SCSI_STATE_IDLE,
SCSI_STATE_SELECT,
SCSI_STATE_SELECT_FAILED,
SCSI_STATE_SEND_COMMAND,
SCSI_STATE_NEXT_PHASE,
SCSI_STATE_END_PHASE,
SCSI_STATE_READ_DATA,
SCSI_STATE_WRITE_DATA,
SCSI_STATE_READ_STATUS,
SCSI_STATE_READ_MESSAGE
} scsi_state_t;
typedef struct scb_t
{
uint16_t command;
uint16_t enable;
uint32_t lba_addr;
uint32_t sys_buf_addr;
uint32_t sys_buf_byte_count;
uint32_t term_status_block_addr;
uint32_t scb_chain_addr;
uint16_t block_count;
uint16_t block_length;
} scb_t;
typedef struct scsi_ibm_t
{
rom_t bios_rom;
uint8_t pos_regs[8];
uint8_t basic_ctrl;
uint32_t command;
uint8_t attention;
uint8_t attention_pending;
int attention_waiting;
uint8_t cir[4];
uint8_t cir_pending[4];
uint8_t irq_status;
uint32_t scb_addr;
uint8_t status;
int in_invalid;
int in_reset;
sbc_state_t scb_state;
scb_t scb;
int scb_id;
int cmd_status;
int cir_status;
uint8_t pacing;
uint8_t buf[0x600];
uint8_t int_buffer[512];
struct
{
int phys_id;
int lun_id;
} dev_id[16];
struct
{
int idx, len;
uint8_t data[256];
int data_idx, data_len;
uint32_t data_pointer;
uint8_t last_status;
int bytes_transferred;
int target_id;
} cdb;
int irq_requests[16];
int cmd_timer;
pc_timer_t callback_timer;
scsi_state_t scsi_state;
scsi_bus_t bus;
} scsi_ibm_t;
#define CTRL_RESET (1 << 7)
#define CTRL_DMA_ENA (1 << 1)
#define CTRL_IRQ_ENA (1 << 0)
#define STATUS_CMD_FULL (1 << 3)
#define STATUS_CMD_EMPTY (1 << 2)
#define STATUS_IRQ (1 << 1)
#define STATUS_BUSY (1 << 0)
#define ENABLE_PT (1 << 12)
#define CMD_MASK 0xff3f
#define CMD_ASSIGN 0x040e
#define CMD_DEVICE_INQUIRY 0x1c0b
#define CMD_DMA_PACING_CONTROL 0x040d
#define CMD_FEATURE_CONTROL 0x040c
#define CMD_GET_POS_INFO 0x1c0a
#define CMD_INVALID_412 0x0412
#define CMD_GET_COMPLETE_STATUS 0x1c07
#define CMD_READ_DATA 0x1c01
#define CMD_READ_DEVICE_CAPACITY 0x1c09
#define CMD_REQUEST_SENSE 0x1c08
#define CMD_RESET 0x0400
#define CMD_SEND_OTHER_SCSI 0x241f
#define CMD_UNKNOWN_1c10 0x1c10
#define CMD_UNKNOWN_1c11 0x1c11
#define CMD_WRITE_DATA 0x1c02
#define CMD_VERIFY 0x1c03
#define IRQ_TYPE_NONE 0x0
#define IRQ_TYPE_SCB_COMPLETE 0x1
#define IRQ_TYPE_SCB_COMPLETE_RETRY 0x5
#define IRQ_TYPE_ADAPTER_HW_FAILURE 0x7
#define IRQ_TYPE_IMM_CMD_COMPLETE 0xa
#define IRQ_TYPE_COMMAND_FAIL 0xc
#define IRQ_TYPE_COMMAND_ERROR 0xe
#define IRQ_TYPE_SW_SEQ_ERROR 0xf
#define IRQ_TYPE_RESET_COMPLETE 0x10
static void scsi_rethink_irqs(scsi_ibm_t *scsi)
{
int irq_pending = 0;
int c;
/* pclog("scsi_rethink_irqs: basic_ctrl=%02x status=%02x\n", scsi->basic_ctrl, scsi->irq_status);
pclog(" ");
for (c = 0; c < 16; c++)
pclog(" %02x", scsi->irq_requests[c]);
pclog("\n");*/
if (!scsi->irq_status)
{
for (c = 0; c < 16; c++)
{
if (scsi->irq_requests[c] != IRQ_TYPE_NONE)
{
// pclog(" Found IRQ on %i\n", c);
/*Found IRQ*/
scsi->irq_status = c | (scsi->irq_requests[c] << 4);
scsi->status |= STATUS_IRQ;
irq_pending = 1;
break;
}
}
}
else
irq_pending = 1;
if (scsi->basic_ctrl & CTRL_IRQ_ENA)
{
if (irq_pending)
{
picintlevel(1 << 14);
// pclog(" SCSI assert INT\n");
}
else
{
/*No IRQs pending, clear IRQ state*/
scsi->irq_status = 0;
scsi->status &= ~STATUS_IRQ;
picintc(1 << 14);
// pclog(" SCSI clear INT - no IRQ\n");
}
}
else
{
picintc(1 << 14);
// pclog(" SCSI clear INT - IRQs disabled\n");
}
}
static inline void scsi_ibm_set_irq(scsi_ibm_t *scsi, int id, int type)
{
// pclog("scsi_ibm_set_irq id=%i type=%x %02x\n", id, type, scsi->irq_status);
scsi->irq_requests[id] = type;
if (!scsi->irq_status) /*Don't change IRQ status if one is currently being processed*/
scsi_rethink_irqs(scsi);
}
static inline void scsi_clear_irq(scsi_ibm_t *scsi, int id)
{
scsi->irq_requests[id] = IRQ_TYPE_NONE;
scsi_rethink_irqs(scsi);
}
static uint8_t scsi_read(uint16_t port, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
uint8_t temp = 0xff;
switch (port & 7)
{
case 0: case 1: case 2: case 3: /*Command Interface Register*/
temp = scsi->cir_pending[port & 3];
break;
case 4: /*Attention Register*/
temp = scsi->attention_pending;
break;
case 5: /*Basic Control Register*/
temp = scsi->basic_ctrl;
break;
case 6: /*IRQ status*/
temp = scsi->irq_status;
break;
case 7: /*Status*/
temp = scsi->status;
if (scsi->cir_status == 0)
temp |= STATUS_CMD_EMPTY;
if (scsi->cir_status == 3)
temp |= STATUS_CMD_FULL;
break;
}
// pclog("scsi_read: port=%04x val=%02x %04x(%05x):%04x %i %02x\n", port, temp,CS,cs,cpu_state.pc, 0/*scsi->callback*/, BH);
return temp;
}
static void scsi_write(uint16_t port, uint8_t val, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
// pclog("scsi_write: port=%04x val=%02x %04x:%04x\n", port, val,CS,cpu_state.pc);
switch (port & 7)
{
case 0: case 1: case 2: case 3: /*Command Interface Register*/
scsi->cir_pending[port & 3] = val;
if (port & 2)
scsi->cir_status |= 2;
else
scsi->cir_status |= 1;
break;
case 4: /*Attention Register*/
scsi->attention_pending = val;
scsi->attention_waiting = 2;
scsi->status |= STATUS_BUSY;
break;
case 5: /*Basic Control Register*/
if ((scsi->basic_ctrl & CTRL_RESET) && !(val & CTRL_RESET))
{
// pclog("SCSI reset\n");
scsi->in_reset = 1;
scsi->cmd_timer = SCSI_TIME*2;
scsi->status |= STATUS_BUSY;
}
scsi->basic_ctrl = val;
scsi_rethink_irqs(scsi);
break;
// default:
// fatal("scsi_write port=%04x val=%02x\n", port, val);
}
}
static uint16_t scsi_readw(uint16_t port, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
uint16_t temp = 0xffff;
switch (port & 7)
{
case 0: /*Command Interface Register*/
temp = scsi->cir_pending[0] | (scsi->cir_pending[1] << 8);
break;
case 2: /*Command Interface Register*/
temp = scsi->cir_pending[2] | (scsi->cir_pending[3] << 8);
break;
// default:
// fatal("scsi_readw port=%04x\n", port);
}
// pclog("scsi_readw: port=%04x val=%04x\n", port, temp);
return temp;
}
static void scsi_writew(uint16_t port, uint16_t val, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
switch (port & 7)
{
case 0: /*Command Interface Register*/
scsi->cir_pending[0] = val & 0xff;
scsi->cir_pending[1] = val >> 8;
scsi->cir_status |= 1;
break;
case 2: /*Command Interface Register*/
scsi->cir_pending[2] = val & 0xff;
scsi->cir_pending[3] = val >> 8;
scsi->cir_status |= 2;
break;
// default:
// fatal("scsi_writew port=%04x val=%04x\n", port, val);
}
// pclog("scsi_writew: port=%04x val=%04x\n", port, val);
}
static void sg_fetch_next(scsi_ibm_t *scsi)
{
if (scsi->scb.sys_buf_byte_count > 0)
{
scsi->cdb.data_idx = 0;
scsi->cdb.data_pointer = mem_readl_phys(scsi->scb.sys_buf_addr);
scsi->cdb.data_len = mem_readl_phys(scsi->scb.sys_buf_addr + 4);
// pclog("SG list: from %08x addr=%08x len=%08x\n", scsi->scb.sys_buf_addr, scsi->cdb.data_pointer, scsi->cdb.data_len);
scsi->scb.sys_buf_addr += 8;
scsi->scb.sys_buf_byte_count -= 8;
}
}
static void process_ibm_cmd(void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
scb_t *scb = &scsi->scb;
int c;
int old_scb_state;
// pclog("scsi_callback: in_reset=%i command=%x\n", scsi->in_reset, scsi->command);
if (scsi->in_reset)
{
scsi->status &= ~STATUS_BUSY;
scsi->irq_status = 0;
for (c = 0; c < 16; c++)
scsi_clear_irq(scsi, c);
if (scsi->in_reset == 1)
{
scsi->basic_ctrl |= CTRL_IRQ_ENA;
scsi_ibm_set_irq(scsi, 0xf, IRQ_TYPE_RESET_COMPLETE);
}
else
scsi_ibm_set_irq(scsi, 0xf, IRQ_TYPE_RESET_COMPLETE);
/*Reset device mappings*/
for (c = 0; c < 7; c++)
{
scsi->dev_id[c].phys_id = c;
scsi->dev_id[c].lun_id = 0;
}
for (; c < 15; c++)
scsi->dev_id[c].phys_id = -1;
scsi->in_reset = 0;
// pclog("scsi reset complete\n");
return;
}
if (scsi->in_invalid)
{
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_ERROR);
scsi->in_invalid = 0;
return;
}
do
{
old_scb_state = scsi->scb_state;
// pclog("scb_state=%i\n", scsi->scb_state);
switch (scsi->scb_state)
{
case SCB_STATE_IDLE:
break;
case SCB_STATE_START:
if (scsi->dev_id[scsi->scb_id].phys_id == -1)
{
scsi_ibm_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL);
scsi->scb_state = SCB_STATE_IDLE;
mem_writew_phys(scb->term_status_block_addr + 0x7*2, (0xe << 8) | 0);
mem_writew_phys(scb->term_status_block_addr + 0x8*2, (0xa << 8) | 0);
break;
}
scb->command = mem_readw_phys(scsi->scb_addr);
scb->enable = mem_readw_phys(scsi->scb_addr + 0x02);
scb->lba_addr = mem_readl_phys(scsi->scb_addr + 0x04);
scb->sys_buf_addr = mem_readl_phys(scsi->scb_addr + 0x08);
scb->sys_buf_byte_count = mem_readl_phys(scsi->scb_addr + 0x0c);
scb->term_status_block_addr = mem_readl_phys(scsi->scb_addr + 0x10);
scb->scb_chain_addr = mem_readl_phys(scsi->scb_addr + 0x14);
scb->block_count = mem_readw_phys(scsi->scb_addr + 0x18);
scb->block_length = mem_readw_phys(scsi->scb_addr + 0x1a);
/* pclog("SCB : \n"
" Command = %04x\n"
" Enable = %04x\n"
" LBA addr = %08x\n"
" System buffer addr = %08x\n"
" System buffer byte count = %08x\n"
" Terminate status block addr = %08x\n"
" SCB chain address = %08x\n"
" Block count = %04x\n"
" Block length = %04x\n",
scb->command, scb->enable, scb->lba_addr,
scb->sys_buf_addr, scb->sys_buf_byte_count,
scb->term_status_block_addr, scb->scb_chain_addr,
scb->block_count, scb->block_length);*/
if (scb->enable & ENABLE_PT)
sg_fetch_next(scsi);
else
{
scsi->cdb.data_idx = 0;
scsi->cdb.data_pointer = scb->sys_buf_addr;
scsi->cdb.data_len = scb->sys_buf_byte_count;
}
switch (scb->command & CMD_MASK)
{
case CMD_GET_COMPLETE_STATUS:
mem_writew_phys(scb->sys_buf_addr + 0x0*2, 0x201); /*SCB status*/
mem_writew_phys(scb->sys_buf_addr + 0x1*2, 0); /*Retry counts*/
mem_writel_phys(scb->sys_buf_addr + 0x2*2, 0); /*Residual byte count*/
mem_writel_phys(scb->sys_buf_addr + 0x4*2, 0); /*Scatter/gather list element address*/
mem_writew_phys(scb->sys_buf_addr + 0x6*2, 0xc); /*Device dependent status length*/
mem_writew_phys(scb->sys_buf_addr + 0x7*2, scsi->cmd_status << 8);
mem_writew_phys(scb->sys_buf_addr + 0x8*2, 0); /*Error*/
mem_writew_phys(scb->sys_buf_addr + 0x9*2, 0); /*Reserved*/
mem_writew_phys(scb->sys_buf_addr + 0xa*2, 0); /*Cache information status*/
mem_writel_phys(scb->sys_buf_addr + 0xb*2, scsi->scb_addr);
scsi->scb_state = SCB_STATE_COMPLETE;
break;
case CMD_UNKNOWN_1c10:
for (c = 0; c < 0x600 && c < scb->sys_buf_byte_count; c++)
scsi->buf[c] = mem_readb_phys(scb->sys_buf_addr + c);
scsi->scb_state = SCB_STATE_COMPLETE;
break;
case CMD_UNKNOWN_1c11:
for (c = 0; c < 0x600 && c < scb->sys_buf_byte_count; c++)
mem_writeb_phys(scb->sys_buf_addr + c, scsi->buf[c]);
scsi->scb_state = SCB_STATE_COMPLETE;
break;
case CMD_GET_POS_INFO:
mem_writew_phys(scb->sys_buf_addr + 0x0*2, 0x8eff);
mem_writew_phys(scb->sys_buf_addr + 0x1*2, scsi->pos_regs[3] | (scsi->pos_regs[2] << 8));
mem_writew_phys(scb->sys_buf_addr + 0x2*2, 0x0e | (scsi->pos_regs[4] << 8));
mem_writew_phys(scb->sys_buf_addr + 0x3*2, 1 << 12);
mem_writew_phys(scb->sys_buf_addr + 0x4*2, (7 << 8) | 8);
mem_writew_phys(scb->sys_buf_addr + 0x5*2, (16 << 8) | scsi->pacing);
mem_writew_phys(scb->sys_buf_addr + 0x6*2, (30 << 8) | 1);
mem_writew_phys(scb->sys_buf_addr + 0x7*2, 0);
mem_writew_phys(scb->sys_buf_addr + 0x8*2, 0);
scsi->scb_state = SCB_STATE_COMPLETE;
break;
case CMD_DEVICE_INQUIRY:
scsi->cdb.data[0] = SCSI_INQUIRY;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = 0; /*Page code*/
scsi->cdb.data[3] = 0;
scsi->cdb.data[4] = scb->sys_buf_byte_count; /*Allocation length*/
scsi->cdb.data[5] = 0; /*Control*/
scsi->cdb.idx = 0;
scsi->cdb.len = 6;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->cdb.data_pointer = scb->sys_buf_addr;
scsi->cdb.data_idx = 0;
scsi->cdb.data_len = scb->sys_buf_byte_count;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_SEND_OTHER_SCSI:
// pclog("SEND_OTHER_SCSI:");
for (c = 0; c < 12; c++)
{
scsi->cdb.data[c] = mem_readb_phys(scsi->scb_addr + 0x18 + c);
// pclog(" %02x", scsi->cdb.data[c]);
}
// pclog("\n");
scsi->cdb.data[1] = (scsi->cdb.data[1] & 0x1f) | (scsi->dev_id[scsi->scb_id].lun_id << 5); /*Patch correct LUN into command*/
scsi->cdb.idx = 0;
scsi->cdb.len = (scb->lba_addr & 0xff) ? (scb->lba_addr & 0xff) : 6;
// pclog("SEND_OTHER_SCSI: %04x\n", scb->lba_addr & 0xffff);
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_READ_DEVICE_CAPACITY:
scsi->cdb.data[0] = SCSI_READ_CAPACITY_10;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = 0; /*LBA*/
scsi->cdb.data[3] = 0;
scsi->cdb.data[4] = 0;
scsi->cdb.data[5] = 0;
scsi->cdb.data[6] = 0; /*Reserved*/
scsi->cdb.data[7] = 0;
scsi->cdb.data[8] = 0;
scsi->cdb.data[9] = 0; /*Control*/
scsi->cdb.idx = 0;
scsi->cdb.len = 10;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_READ_DATA:
scsi->cdb.data[0] = SCSI_READ_10;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/
scsi->cdb.data[3] = (scb->lba_addr >> 16) & 0xff;
scsi->cdb.data[4] = (scb->lba_addr >> 8) & 0xff;
scsi->cdb.data[5] = scb->lba_addr & 0xff;
scsi->cdb.data[6] = 0; /*Reserved*/
scsi->cdb.data[7] = (scb->block_count >> 8) & 0xff;
scsi->cdb.data[8] = scb->block_count & 0xff;
scsi->cdb.data[9] = 0; /*Control*/
scsi->cdb.idx = 0;
scsi->cdb.len = 10;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_REQUEST_SENSE:
scsi->cdb.data[0] = SCSI_REQUEST_SENSE;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = 0;
scsi->cdb.data[3] = 0;
scsi->cdb.data[4] = scb->block_count & 0xff; /*Allocation length*/
scsi->cdb.data[5] = 0;
scsi->cdb.idx = 0;
scsi->cdb.len = 6;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_WRITE_DATA:
scsi->cdb.data[0] = SCSI_WRITE_10;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/
scsi->cdb.data[3] = (scb->lba_addr >> 16) & 0xff;
scsi->cdb.data[4] = (scb->lba_addr >> 8) & 0xff;
scsi->cdb.data[5] = scb->lba_addr & 0xff;
scsi->cdb.data[6] = 0; /*Reserved*/
scsi->cdb.data[7] = (scb->block_count >> 8) & 0xff;
scsi->cdb.data[8] = scb->block_count & 0xff;
scsi->cdb.data[9] = 0; /*Control*/
scsi->cdb.idx = 0;
scsi->cdb.len = 10;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
case CMD_VERIFY:
scsi->cdb.data[0] = SCSI_VERIFY_10;
scsi->cdb.data[1] = scsi->dev_id[scsi->scb_id].lun_id << 5; /*LUN*/
scsi->cdb.data[2] = (scb->lba_addr >> 24) & 0xff; /*LBA*/
scsi->cdb.data[3] = (scb->lba_addr >> 16) & 0xff;
scsi->cdb.data[4] = (scb->lba_addr >> 8) & 0xff;
scsi->cdb.data[5] = scb->lba_addr & 0xff;
scsi->cdb.data[6] = 0; /*Reserved*/
scsi->cdb.data[7] = (scb->block_count >> 8) & 0xff;
scsi->cdb.data[8] = scb->block_count & 0xff;
scsi->cdb.data[9] = 0; /*Control*/
scsi->cdb.idx = 0;
scsi->cdb.len = 10;
scsi->cdb.target_id = scsi->dev_id[scsi->scb_id].phys_id;
scsi->cdb.data_len = 0;
scsi->scsi_state = SCSI_STATE_SELECT;
scsi->scb_state = SCB_STATE_WAIT;
return;
#ifndef RELEASE_BUILD
default:
fatal("SCB command %04x\n", scb->command);
#endif
}
break;
case SCB_STATE_WAIT:
if (scsi->scsi_state == SCSI_STATE_IDLE)
{
if (scsi->cdb.last_status == STATUS_GOOD)
scsi->scb_state = SCB_STATE_COMPLETE;
else if (scsi->cdb.last_status == STATUS_CHECK_CONDITION)
{
scsi_ibm_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL);
scsi->scb_state = SCB_STATE_IDLE;
mem_writew_phys(scb->term_status_block_addr + 0x7*2, (0xc << 8) | 2);
mem_writew_phys(scb->term_status_block_addr + 0x8*2, 0x20);
mem_writew_phys(scb->term_status_block_addr + 0xb*2, scsi->scb_addr & 0xffff);
mem_writew_phys(scb->term_status_block_addr + 0xc*2, scsi->scb_addr >> 16);
}
#ifndef RELEASE_BUILD
else
fatal("SCB unknown result %02x\n", scsi->cdb.last_status);
#endif
}
else if (scsi->scsi_state == SCSI_STATE_SELECT_FAILED)
{
// pclog("SCB_STATE_WAIT SELECT_FAILED\n");
scsi_ibm_set_irq(scsi, scsi->scb_id, IRQ_TYPE_COMMAND_FAIL);
scsi->scb_state = SCB_STATE_IDLE;
mem_writew_phys(scb->term_status_block_addr + 0x7*2, (0xc << 8) | 2);
mem_writew_phys(scb->term_status_block_addr + 0x8*2, 0x10);
}
#ifndef RELEASE_BUILD
else
fatal("SCB_STATE_WAIT other %i\n", scsi->scsi_state);
#endif
break;
case SCB_STATE_COMPLETE:
if (scb->enable & 1)
{
scsi->scb_state = SCB_STATE_START;
scsi->scb_addr = scb->scb_chain_addr;
// pclog("Next SCB - %08x\n", scsi->scb_addr);
}
else
{
scsi_ibm_set_irq(scsi, scsi->scb_id, IRQ_TYPE_SCB_COMPLETE);
scsi->scb_state = SCB_STATE_IDLE;
}
break;
}
} while (scsi->scb_state != old_scb_state);
}
static void process_immediate_command(scsi_ibm_t *scsi)
{
switch (scsi->command & CMD_MASK)
{
case CMD_ASSIGN:
// pclog("Assign: adapter dev=%x scsi ID=%i LUN=%i\n", (scsi->command >> 16) & 0xf, (scsi->command >> 20) & 7, (scsi->command >> 24) & 7);
if (((scsi->command >> 16) & 0xf) == 0xf && ((scsi->command >> 20) & 7) == 7) /*Device 15 always adapter*/
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
else if (((scsi->command >> 16) & 0xf) == 0xf) /*Can not re-assign device 15 (always adapter)*/
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_FAIL);
else if (scsi->command & (1 << 23))
{
scsi->dev_id[(scsi->command >> 16) & 0xf].phys_id = -1;
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
}
else if (((scsi->command >> 20) & 7) == 7) /*Can not assign adapter*/
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_COMMAND_FAIL);
else
{
scsi->dev_id[(scsi->command >> 16) & 0xf].phys_id = (scsi->command >> 20) & 7;
scsi->dev_id[(scsi->command >> 16) & 0xf].lun_id = (scsi->command >> 24) & 7;
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
}
break;
case CMD_DMA_PACING_CONTROL:
scsi->pacing = scsi->cir[2];
// pclog("Pacing control: %i\n", scsi->pacing);
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
break;
case CMD_FEATURE_CONTROL:
// pclog("Feature control: timeout=%is d-rate=%i\n", (scsi->command >> 16) & 0x1fff, scsi->command >> 29);
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
break;
case CMD_INVALID_412:
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
break;
case CMD_RESET:
if ((scsi->attention & 0xf) == 0xf) /*Adapter reset*/
{
scsi_bus_reset(&scsi->bus);
scsi->scb_state = SCB_STATE_IDLE;
}
else /*Device reset*/
{
}
scsi_ibm_set_irq(scsi, scsi->attention & 0x0f, IRQ_TYPE_IMM_CMD_COMPLETE);
break;
default:
fatal("scsi_callback: Bad command %02x\n", scsi->command);
}
}
static int wait_for_bus(scsi_bus_t *bus, int state, int req_needed)
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(bus);
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) == state && (bus_state & BUS_BSY))
{
if (!req_needed || (bus_state & BUS_REQ))
return 1;
}
}
return 0;
}
static void process_scsi(scsi_ibm_t *scsi)
{
int c;
int bytes_transferred = 0;
switch (scsi->scsi_state)
{
case SCSI_STATE_IDLE:
break;
case SCSI_STATE_SELECT:
scsi->cdb.last_status = 0;
// pclog("Select target ID %i\n", scsi->cdb.target_id);
scsi_bus_update(&scsi->bus, BUS_SEL | BUS_SETDATA(1 << scsi->cdb.target_id));
if (!(scsi_bus_read(&scsi->bus) & BUS_BSY) || scsi->cdb.target_id > 6)
{
// pclog("STATE_SCSI_SELECT failed to select target %i\n", scsi->cdb.target_id);
scsi->scsi_state = SCSI_STATE_SELECT_FAILED;
if (!scsi->cmd_timer)
scsi->cmd_timer = 1;
break;
}
scsi_bus_update(&scsi->bus, 0);
if (!(scsi_bus_read(&scsi->bus) & BUS_BSY))
{
// pclog("STATE_SCSI_SELECT failed to select target %i 2\n", scsi->cdb.target_id);
scsi->scsi_state = SCSI_STATE_SELECT_FAILED;
if (!scsi->cmd_timer)
scsi->cmd_timer = 1;
break;
}
/*Device should now be selected*/
if (!wait_for_bus(&scsi->bus, BUS_CD, 1))
{
// pclog("Device failed to request command\n");
scsi->scsi_state = SCSI_STATE_SELECT_FAILED;
if (!scsi->cmd_timer)
scsi->cmd_timer = 1;
break;
}
scsi->scsi_state = SCSI_STATE_SEND_COMMAND;
break;
case SCSI_STATE_SELECT_FAILED:
break;
case SCSI_STATE_SEND_COMMAND:
while (scsi->cdb.idx < scsi->cdb.len && bytes_transferred < MAX_BYTES_TRANSFERRED_PER_POLL)
{
int bus_out;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("SEND_COMMAND - dropped BSY\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_CD)
fatal("SEND_COMMAND - bus phase incorrect\n");
#endif
if (bus_state & BUS_REQ)
break;
}
if (c == 20)
{
// pclog("SEND_COMMAND timed out\n");
break;
}
bus_out = BUS_SETDATA(scsi->cdb.data[scsi->cdb.idx]);
// pclog(" Command send %02x %i\n", scsi->cdb.data[scsi->cdb.idx], scsi->cdb.len);
scsi->cdb.idx++;
bytes_transferred++;
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
}
if (scsi->cdb.idx == scsi->cdb.len)
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
case SCSI_STATE_NEXT_PHASE:
/*Wait for SCSI command to move to next phase*/
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("NEXT_PHASE - dropped BSY waiting\n");
#endif
if (bus_state & BUS_REQ)
{
int bus_out;
// pclog("SCSI next phase - %x\n", bus_state);
switch (bus_state & (BUS_IO | BUS_CD | BUS_MSG))
{
case 0:
// pclog("Move to write data\n");
scsi->scsi_state = SCSI_STATE_WRITE_DATA;
break;
case BUS_IO:
// pclog("Move to read data\n");
scsi->scsi_state = SCSI_STATE_READ_DATA;
break;
case (BUS_CD | BUS_IO):
// pclog("Move to read status\n");
scsi->scsi_state = SCSI_STATE_READ_STATUS;
break;
case (BUS_CD | BUS_IO | BUS_MSG):
// pclog("Move to read message\n");
scsi->scsi_state = SCSI_STATE_READ_MESSAGE;
break;
case BUS_CD:
bus_out = BUS_SETDATA(0);
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
break;
#ifndef RELEASE_BUILD
default:
fatal(" Bad new phase %x\n", bus_state);
#endif
}
break;
}
}
break;
case SCSI_STATE_END_PHASE:
/*Wait for SCSI command to move to next phase*/
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&scsi->bus);
if (!(bus_state & BUS_BSY))
{
// pclog("END_PHASE - dropped BSY waiting\n");
// if (scsi->ccb.req_sense_len == CCB_SENSE_NONE)
// fatal("No sense data\n");
scsi->scsi_state = SCSI_STATE_IDLE;
// pclog("End of command\n");
if (!scsi->cmd_timer)
scsi->cmd_timer = 1;
break;
}
#ifndef RELEASE_BUILD
if (bus_state & BUS_REQ)
fatal("END_PHASE - unexpected REQ\n");
#endif
}
break;
case SCSI_STATE_READ_DATA:
while (scsi->scsi_state == SCSI_STATE_READ_DATA && bytes_transferred < MAX_BYTES_TRANSFERRED_PER_POLL)
{
int d;
for (d = 0; d < 20; d++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("READ_DATA - dropped BSY waiting\n");
#endif
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_IO)
{
// pclog("READ_DATA - changed phase\n");
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t data = BUS_GETDATA(bus_state);
int bus_out = 0;
if (scsi->cdb.data_idx < scsi->cdb.data_len)
{
if (scsi->cdb.data_pointer == -1)
scsi->int_buffer[scsi->cdb.data_idx] = data;
else
mem_writeb_phys(scsi->cdb.data_pointer + scsi->cdb.data_idx, data);
scsi->cdb.data_idx++;
if ((scsi->scb.enable & ENABLE_PT) && scsi->cdb.data_idx == scsi->cdb.data_len)
sg_fetch_next(scsi);
}
scsi->cdb.bytes_transferred++;
// pclog("Read data %02x %i %06x\n", data, scsi->cdb.data_idx, scsi->cdb.data_pointer + scsi->cdb.data_idx);
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
break;
}
}
bytes_transferred++;
}
break;
case SCSI_STATE_WRITE_DATA:
while (scsi->scsi_state == SCSI_STATE_WRITE_DATA && bytes_transferred < MAX_BYTES_TRANSFERRED_PER_POLL)
{
int d;
for (d = 0; d < 20; d++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("WRITE_DATA - dropped BSY waiting\n");
#endif
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != 0)
{
// pclog("WRITE_DATA - changed phase\n");
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t data;// = BUS_GETDATA(bus_state);
int bus_out;
if (scsi->cdb.data_idx < scsi->cdb.data_len)
{
if (scsi->cdb.data_pointer == -1)
data = scsi->int_buffer[scsi->cdb.data_idx];
else
data = mem_readb_phys(scsi->cdb.data_pointer + scsi->cdb.data_idx);
scsi->cdb.data_idx++;
if ((scsi->scb.enable & ENABLE_PT) && scsi->cdb.data_idx == scsi->cdb.data_len)
sg_fetch_next(scsi);
}
else
data = 0xff;
scsi->cdb.bytes_transferred++;
// pclog("Write data %02x %i\n", data, scsi->cdb.data_idx);
bus_out = BUS_SETDATA(data);
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
break;
}
}
bytes_transferred++;
}
// if (scsi->cdb.data_idx == scsi->cdb.data_len)
// scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
case SCSI_STATE_READ_STATUS:
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("READ_STATUS - dropped BSY waiting\n");
#endif
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != (BUS_CD | BUS_IO))
{
// pclog("READ_STATUS - changed phase\n");
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t status = BUS_GETDATA(bus_state);
int bus_out = 0;
// pclog("Read status %02x\n", status);
scsi->cdb.last_status = status;
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
}
}
break;
case SCSI_STATE_READ_MESSAGE:
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&scsi->bus);
#ifndef RELEASE_BUILD
if (!(bus_state & BUS_BSY))
fatal("READ_MESSAGE - dropped BSY waiting\n");
#endif
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != (BUS_CD | BUS_IO | BUS_MSG))
{
// pclog("READ_MESSAGE - changed phase\n");
scsi->scsi_state = SCSI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t msg = BUS_GETDATA(bus_state);
int bus_out = 0;
// pclog("Read message %02x\n", msg);
scsi_bus_update(&scsi->bus, bus_out | BUS_ACK);
scsi_bus_update(&scsi->bus, bus_out & ~BUS_ACK);
switch (msg)
{
case MSG_COMMAND_COMPLETE:
scsi->scsi_state = SCSI_STATE_END_PHASE;
break;
#ifndef RELEASE_BUILD
default:
fatal("READ_MESSAGE - unknown message %02x\n", msg);
#endif
}
break;
}
}
break;
#ifndef RELEASE_BUILD
default:
fatal("Unknown SCSI_state %d\n", scsi->scsi_state);
#endif
}
}
static void scsi_callback(void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
timer_advance_u64(&scsi->callback_timer, POLL_TIME);
// pclog("poll %i\n", scsi->cmd_state);
if (scsi->cmd_timer)
{
scsi->cmd_timer--;
if (!scsi->cmd_timer)
process_ibm_cmd(scsi);
}
if (scsi->attention_waiting &&
(scsi->scb_state == SCB_STATE_IDLE || (scsi->attention_pending & 0xf0) == 0xe0))
{
scsi->attention_waiting--;
if (!scsi->attention_waiting)
{
// pclog("Process attention request\n");
scsi->attention = scsi->attention_pending;
scsi->status &= ~STATUS_BUSY;
scsi->cir[0] = scsi->cir_pending[0];
scsi->cir[1] = scsi->cir_pending[1];
scsi->cir[2] = scsi->cir_pending[2];
scsi->cir[3] = scsi->cir_pending[3];
scsi->cir_status = 0;
switch (scsi->attention >> 4)
{
case 0x1: /*Immediate command*/
scsi->cmd_status = 0xa;
scsi->command = scsi->cir[0] | (scsi->cir[1] << 8) | (scsi->cir[2] << 16) | (scsi->cir[3] << 24);
switch (scsi->command & CMD_MASK)
{
case CMD_ASSIGN:
case CMD_DMA_PACING_CONTROL:
case CMD_FEATURE_CONTROL:
case CMD_INVALID_412:
case CMD_RESET:
process_immediate_command(scsi);
break;
#ifndef RELEASE_BUILD
default:
fatal("Unknown immediate command %08x\n", scsi->command);
#endif
}
break;
case 0x3: case 0x4: case 0xf: /*Start SCB*/
scsi->cmd_status = 0x1;
scsi->scb_addr = scsi->cir[0] | (scsi->cir[1] << 8) | (scsi->cir[2] << 16) | (scsi->cir[3] << 24);
scsi->scb_state = SCB_STATE_START;
scsi->scb_id = scsi->attention & 0xf;
scsi->cmd_timer = SCSI_TIME*2;
// pclog("Start SCB at %08x\n", scsi->scb_addr);
break;
case 5: /*Invalid*/
case 0xa: /*Invalid*/
scsi->in_invalid = 1;
scsi->cmd_timer = SCSI_TIME*2;
break;
case 0xe: /*EOI*/
scsi->irq_status = 0;
scsi_clear_irq(scsi, scsi->attention & 0xf);
break;
#ifndef RELEASE_BUILD
default:
fatal("Attention Register 5 %02x\n", scsi->attention);
#endif
}
}
}
process_scsi(scsi);
}
static uint8_t scsi_ibm_mca_read(int port, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
// pclog("scsi_mca_read: port=%04x %02x %04x:%04x\n", port, scsi->pos_regs[port & 7], CS,cpu_state.pc);
return scsi->pos_regs[port & 7];
}
static void scsi_ibm_mca_write(int port, uint8_t val, void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
if (port < 0x102)
return;
// pclog("scsi_mca_write: port=%04x val=%02x %04x:%04x\n", port, val, CS, cpu_state.pc);
io_removehandler((((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540, 0x0008, scsi_read, scsi_readw, NULL, scsi_write, scsi_writew, NULL, scsi);
mem_mapping_disable(&scsi->bios_rom.mapping);
scsi->pos_regs[port & 7] = val;
if (scsi->pos_regs[2] & 1)
{
// pclog(" scsi io = %04x\n", (((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540);
io_sethandler((((scsi->pos_regs[2] >> 1) & 7) * 8) + 0x3540, 0x0008, scsi_read, scsi_readw, NULL, scsi_write, scsi_writew, NULL, scsi);
if ((scsi->pos_regs[2] & 0xf0) != 0xf0)
{
mem_mapping_enable(&scsi->bios_rom.mapping);
mem_mapping_set_addr(&scsi->bios_rom.mapping, ((scsi->pos_regs[2] >> 4) * 0x2000) + 0xc0000, 0x8000);
// pclog("SCSI BIOS ROM at %05x %02x\n", ((scsi->pos_regs[2] >> 4) * 0x2000) + 0xc0000, scsi->pos_regs[2]);
}
}
}
static void scsi_ibm_reset(void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
// pclog("scsi_ibm_reset\n");
scsi->in_reset = 2;
scsi->cmd_timer = SCSI_TIME * 50;
scsi->status = STATUS_BUSY;
scsi->scsi_state = SCSI_STATE_IDLE;
scsi->scb_state = SCB_STATE_IDLE;
scsi->in_invalid = 0;
scsi->attention_waiting = 0;
scsi->basic_ctrl = 0;
scsi_bus_reset(&scsi->bus);
}
static void *scsi_ibm_init()
{
int c;
scsi_ibm_t *scsi = malloc(sizeof(scsi_ibm_t));
memset(scsi, 0, sizeof(scsi_ibm_t));
rom_init_interleaved(&scsi->bios_rom, "92F2244.U68", "92F2245.U69", 0xc8000, 0x8000, 0x7fff, 0x4000, MEM_MAPPING_EXTERNAL);
mem_mapping_disable(&scsi->bios_rom.mapping);
timer_add(&scsi->callback_timer, scsi_callback, scsi, 1);
mca_add(scsi_ibm_mca_read, scsi_ibm_mca_write, scsi_ibm_reset, scsi);
scsi->pos_regs[0] = 0xff;
scsi->pos_regs[1] = 0x8e;
scsi->in_reset = 2;
scsi->cmd_timer = SCSI_TIME * 50;
scsi->status = STATUS_BUSY;
scsi_bus_init(&scsi->bus);
/* for (c = 0; c < 7; c++)
{
scsi->dev_id[c].phys_id = c;
scsi->dev_id[c].lun_id = 0;
}*/
for (c = 0; c < 15; c++)
scsi->dev_id[c].phys_id = -1;
scsi->dev_id[15].phys_id = 7;
return scsi;
}
static void scsi_ibm_close(void *p)
{
scsi_ibm_t *scsi = (scsi_ibm_t *)p;
free(scsi);
}
static int scsi_ibm_available()
{
return rom_present("92F2244.U68") && rom_present("92F2245.U69");
}
device_t scsi_ibm_device =
{
"IBM SCSI Adapter with Cache (MCA)",
DEVICE_MCA,
scsi_ibm_init,
scsi_ibm_close,
scsi_ibm_available,
NULL,
NULL,
NULL,
NULL
};