#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
	};
