#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include "ibm.h"

#include "device.h"
#include "dma.h"
#include "hdd_file.h"
#include "io.h"
#include "mca.h"
#include "mem.h"
#include "pic.h"
#include "rom.h"
#include "timer.h"

#include "hdd_esdi.h"

#define ESDI_TIME (500 * TIMER_USEC)

#define CMD_ADAPTER 0

extern char ide_fn[7][512];

typedef struct esdi_t {
	rom_t bios_rom;

	uint8_t basic_ctrl;
	uint8_t status;
	uint8_t irq_status;

	int irq_in_progress;
	int cmd_req_in_progress;

	int cmd_pos;
	uint16_t cmd_data[4];
	int cmd_dev;

	int status_pos, status_len;
	uint16_t status_data[256];

	int data_pos;
	uint16_t data[256];

	uint16_t sector_buffer[256][256];

	int sector_pos;
	int sector_count;

	int command;
	int cmd_state;

	int in_reset;
	pc_timer_t callback_timer;

	uint32_t rba;

	struct {
		int req_in_progress;
	} cmds[3];

	hdd_file_t hdd_file[2];

	uint8_t pos_regs[8];
} esdi_t;

#define STATUS_DMA_ENA         (1 << 7)
#define STATUS_IRQ_PENDING     (1 << 6)
#define STATUS_CMD_IN_PROGRESS (1 << 5)
#define STATUS_BUSY            (1 << 4)
#define STATUS_STATUS_OUT_FULL (1 << 3)
#define STATUS_CMD_IR_FULL     (1 << 2)
#define STATUS_TRANSFER_REQ    (1 << 1)
#define STATUS_IRQ             (1 << 0)

#define CTRL_RESET   (1 << 7)
#define CTRL_DMA_ENA (1 << 1)
#define CTRL_IRQ_ENA (1 << 0)

#define IRQ_HOST_ADAPTER (7 << 5)
#define IRQ_DEVICE_0     (0 << 5)
#define IRQ_CMD_COMPLETE_SUCCESS 0x1
#define IRQ_RESET_COMPLETE       0xa
#define IRQ_DATA_TRANSFER_READY  0xb
#define IRQ_CMD_COMPLETE_FAILURE 0xc

#define ATTN_DEVICE_SEL   (7 << 5)
#define ATTN_HOST_ADAPTER (7 << 5)
#define ATTN_DEVICE_0     (0 << 5)
#define ATTN_DEVICE_1     (1 << 5)
#define ATTN_REQ_MASK     0xf
#define ATTN_CMD_REQ      1
#define ATTN_EOI          2
#define ATTN_RESET        4

#define CMD_SIZE_4 (1 << 14)

#define CMD_DEVICE_SEL     (7 << 5)
#define CMD_MASK           0x1f
#define CMD_READ           0x01
#define CMD_WRITE          0x02
#define CMD_READ_VERIFY    0x03
#define CMD_WRITE_VERIFY   0x04
#define CMD_SEEK           0x05
#define CMD_GET_DEV_STATUS 0x08
#define CMD_GET_DEV_CONFIG 0x09
#define CMD_GET_POS_INFO   0x0a

#define STATUS_LEN(x) ((x) << 8)
#define STATUS_DEVICE(x) ((x) << 5)
#define STATUS_DEVICE_HOST_ADAPTER (7 << 5)

static inline void esdi_set_irq(esdi_t *esdi) {
	if (esdi->basic_ctrl & CTRL_IRQ_ENA)
		picint(1 << 14);
}
static inline void esdi_clear_irq() {
	picintc(1 << 14);
}

static uint8_t esdi_read(uint16_t port, void *p) {
	esdi_t *esdi = (esdi_t *)p;
	uint8_t temp = 0xff;

	switch (port) {
	case 0x3512: /*Basic status register*/
		temp = esdi->status;
		break;
	case 0x3513: /*IRQ status*/
		esdi->status &= ~STATUS_IRQ;
		temp = esdi->irq_status;
		break;

	default:fatal("esdi_read port=%04x\n", port);
	}

//        pclog("esdi_read: port=%04x val=%02x %04x(%05x):%04x %02x %02x\n", port, temp,CS,cs,cpu_state.pc, BL, BH);
	return temp;
}

static void esdi_write(uint16_t port, uint8_t val, void *p) {
	esdi_t *esdi = (esdi_t *)p;

//        pclog("esdi_write: port=%04x val=%02x %04x:%04x %02x %i\n", port, val,CS,cpu_state.pc, esdi->status, esdi->irq_in_progress);
	switch (port) {
	case 0x3512: /*Basic control register*/
		if ((esdi->basic_ctrl & CTRL_RESET) && !(val & CTRL_RESET)) {
//                        pclog("ESDI reset\n");
			esdi->in_reset = 1;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME * 50);
			esdi->status = STATUS_BUSY;
		}
		esdi->basic_ctrl = val;
		if (!(esdi->basic_ctrl & CTRL_IRQ_ENA))
			picintc(1 << 14);
		break;
	case 0x3513: /*Attention register*/
		switch (val & ATTN_DEVICE_SEL) {
		case ATTN_HOST_ADAPTER:
			switch (val & ATTN_REQ_MASK) {
			case ATTN_CMD_REQ:
				if (esdi->cmd_req_in_progress)
					fatal("Try to start command on in_progress adapter\n");
				esdi->cmd_req_in_progress = 1;
				esdi->cmd_dev = ATTN_HOST_ADAPTER;
				esdi->status |= STATUS_BUSY;
				esdi->cmd_pos = 0;
				esdi->status_pos = 0;
				break;

			case ATTN_EOI:esdi->irq_in_progress = 0;
				esdi->status &= ~STATUS_IRQ;
				esdi_clear_irq();
				break;

			case ATTN_RESET:
//                                pclog("ESDI reset\n");
				esdi->in_reset = 1;
				timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME * 50);
				esdi->status = STATUS_BUSY;
				break;

			default:fatal("Bad attention request %02x\n", val);
			}
			break;

		case ATTN_DEVICE_0:
			switch (val & ATTN_REQ_MASK) {
			case ATTN_CMD_REQ:
				if (esdi->cmd_req_in_progress)
					fatal("Try to start command on in_progress device0\n");
				esdi->cmd_req_in_progress = 1;
				esdi->cmd_dev = ATTN_DEVICE_0;
				esdi->status |= STATUS_BUSY;
				esdi->cmd_pos = 0;
				esdi->status_pos = 0;
				break;

			case ATTN_EOI:esdi->irq_in_progress = 0;
				esdi->status &= ~STATUS_IRQ;
				esdi_clear_irq();
				break;

			default:fatal("Bad attention request %02x\n", val);
			}
			break;

		case ATTN_DEVICE_1:
			switch (val & ATTN_REQ_MASK) {
			case ATTN_CMD_REQ:
				if (esdi->cmd_req_in_progress)
					fatal("Try to start command on in_progress device0\n");
				esdi->cmd_req_in_progress = 1;
				esdi->cmd_dev = ATTN_DEVICE_1;
				esdi->status |= STATUS_BUSY;
				esdi->cmd_pos = 0;
				esdi->status_pos = 0;
				break;

			case ATTN_EOI:esdi->irq_in_progress = 0;
				esdi->status &= ~STATUS_IRQ;
				esdi_clear_irq();
				break;

			default:fatal("Bad attention request %02x\n", val);
			}
			break;

		default:fatal("Attention to unknown device %02x\n", val);
		}
		break;

	default:fatal("esdi_write port=%04x val=%02x\n", port, val);
	}
}

static uint16_t esdi_readw(uint16_t port, void *p) {
	esdi_t *esdi = (esdi_t *)p;
	uint16_t temp = 0xffff;

	switch (port) {
	case 0x3510: /*Status Interface Register*/
		if (esdi->status_pos >= esdi->status_len)
			return 0;
		temp = esdi->status_data[esdi->status_pos++];
		if (esdi->status_pos >= esdi->status_len) {
			esdi->status &= ~STATUS_STATUS_OUT_FULL;
			esdi->status_pos = esdi->status_len = 0;
		}
		break;

	default:fatal("esdi_readw port=%04x\n", port);
	}

//        pclog("esdi_readw: port=%04x val=%04x\n", port, temp);
	return temp;
}

static void esdi_writew(uint16_t port, uint16_t val, void *p) {
	esdi_t *esdi = (esdi_t *)p;

//        pclog("esdi_writew: port=%04x val=%04x\n", port, val);
	switch (port) {
	case 0x3510: /*Command Interface Register*/
		if (esdi->cmd_pos >= 4)
			fatal("CIR pos 4\n");
		esdi->cmd_data[esdi->cmd_pos++] = val;
		if (((esdi->cmd_data[0] & CMD_SIZE_4) && esdi->cmd_pos == 4) ||
			(!(esdi->cmd_data[0] & CMD_SIZE_4) && esdi->cmd_pos == 2)) {
//                        pclog("Received command - %04x %04x %04x %04x\n", esdi->cmd_data[0], esdi->cmd_data[1], esdi->cmd_data[2], esdi->cmd_data[3]);

			esdi->cmd_pos = 0;
			esdi->cmd_req_in_progress = 0;
			esdi->cmd_state = 0;

			if ((esdi->cmd_data[0] & CMD_DEVICE_SEL) != esdi->cmd_dev)
				fatal("Command device mismatch with attn\n");
			esdi->command = esdi->cmd_data[0] & CMD_MASK;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			esdi->status = STATUS_BUSY;
			esdi->data_pos = 0;
		}
		break;

	default:fatal("esdi_writew port=%04x val=%04x\n", port, val);
	}
}

static void cmd_unsupported(esdi_t *esdi) {
	esdi->status_len = 9;
	esdi->status_data[0] = esdi->command | STATUS_LEN(9) | esdi->cmd_dev;
	esdi->status_data[1] = 0x0f03; /*Attention error, command not supported*/
	esdi->status_data[2] = 0x0002; /*Interface fault*/
	esdi->status_data[3] = 0;
	esdi->status_data[4] = 0;
	esdi->status_data[5] = 0;
	esdi->status_data[6] = 0;
	esdi->status_data[7] = 0;
	esdi->status_data[8] = 0;

	esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
	esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_FAILURE;
	esdi->irq_in_progress = 1;
	esdi_set_irq(esdi);
}

static void device_not_present(esdi_t *esdi) {
	esdi->status_len = 9;
	esdi->status_data[0] = esdi->command | STATUS_LEN(9) | esdi->cmd_dev;
	esdi->status_data[1] = 0x0c11; /*Command failed, internal hardware error*/
	esdi->status_data[2] = 0x000b; /*Selection error*/
	esdi->status_data[3] = 0;
	esdi->status_data[4] = 0;
	esdi->status_data[5] = 0;
	esdi->status_data[6] = 0;
	esdi->status_data[7] = 0;
	esdi->status_data[8] = 0;

	esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
	esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_FAILURE;
	esdi->irq_in_progress = 1;
	esdi_set_irq(esdi);
}

static void rba_out_of_range(esdi_t *esdi) {
	esdi->status_len = 9;
	esdi->status_data[0] = esdi->command | STATUS_LEN(9) | esdi->cmd_dev;
	esdi->status_data[1] = 0x0e01; /*Command block error, invalid parameter*/
	esdi->status_data[2] = 0x0007; /*RBA out of range*/
	esdi->status_data[3] = 0;
	esdi->status_data[4] = 0;
	esdi->status_data[5] = 0;
	esdi->status_data[6] = 0;
	esdi->status_data[7] = 0;
	esdi->status_data[8] = 0;

	esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
	esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_FAILURE;
	esdi->irq_in_progress = 1;
	esdi_set_irq(esdi);
}

static void complete_command_status(esdi_t *esdi) {
	esdi->status_len = 7;
	if (esdi->cmd_dev == ATTN_DEVICE_0)
		esdi->status_data[0] = CMD_READ | STATUS_LEN(7) | STATUS_DEVICE(0);
	else
		esdi->status_data[0] = CMD_READ | STATUS_LEN(7) | STATUS_DEVICE(1);
	esdi->status_data[1] = 0x0000; /*Error bits*/
	esdi->status_data[2] = 0x1900; /*Device status*/
	esdi->status_data[3] = 0; /*Number of blocks left to do*/
	esdi->status_data[4] = (esdi->rba - 1) & 0xffff; /*Last RBA processed*/
	esdi->status_data[5] = (esdi->rba - 1) >> 8;
	esdi->status_data[6] = 0; /*Number of blocks requiring error recovery*/
}

#define ESDI_ADAPTER_ONLY() do \
        {                                                 \
                if (esdi->cmd_dev != ATTN_HOST_ADAPTER)   \
                {                                         \
                        cmd_unsupported(esdi);            \
                        return;                           \
                }                                         \
        } while (0)

#define ESDI_DRIVE_ONLY() do \
        {                                                                               \
                if (esdi->cmd_dev != ATTN_DEVICE_0 && esdi->cmd_dev != ATTN_DEVICE_1)   \
                {                                                                       \
                        cmd_unsupported(esdi);                                          \
                        return;                                                         \
                }                                                                       \
                if (esdi->cmd_dev == ATTN_DEVICE_0)                                     \
                        hdd_file = &esdi->hdd_file[0];                                  \
                else                                                                    \
                        hdd_file = &esdi->hdd_file[1];                                  \
        } while (0)

static void esdi_callback(void *p) {
	esdi_t *esdi = (esdi_t *)p;
	hdd_file_t *hdd_file;

//        pclog("esdi_callback: in_reset=%i command=%x\n", esdi->in_reset, esdi->command);
	if (esdi->in_reset) {
		esdi->in_reset = 0;
		esdi->status = STATUS_IRQ;
		esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_RESET_COMPLETE;

//                pclog("esdi reset complete\n");
		return;
	}
	switch (esdi->command) {
	case CMD_READ:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		switch (esdi->cmd_state) {
		case 0:esdi->rba = (esdi->cmd_data[2] | (esdi->cmd_data[3] << 16)) & 0x0fffffff;

			esdi->sector_pos = 0;
			esdi->sector_count = esdi->cmd_data[1];

			if ((esdi->rba + esdi->sector_count) >= hdd_file->sectors) {
				rba_out_of_range(esdi);
				return;
			}

			esdi->status = STATUS_IRQ | STATUS_CMD_IN_PROGRESS | STATUS_TRANSFER_REQ;
			esdi->irq_status = esdi->cmd_dev | IRQ_DATA_TRANSFER_READY;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);

			esdi->cmd_state = 1;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			esdi->data_pos = 0;
			break;

		case 1:
			if (!(esdi->basic_ctrl & CTRL_DMA_ENA)) {
//                                pclog("DMA not enabled\n");
				timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
				return;
			}
			while (esdi->sector_pos < esdi->sector_count) {
				if (!esdi->data_pos) {
					if (esdi->rba >= hdd_file->sectors)
						fatal("Read past end of drive\n");
					hdd_read_sectors(hdd_file, esdi->rba, 1, esdi->data);
					readflash_set(READFLASH_HDC, esdi->cmd_dev == ATTN_DEVICE_0 ? 0 : 1);
				}
//                                pclog("Read sector %i %i %08x\n", esdi->sector_pos, esdi->data_pos, esdi->rba*512);
				while (esdi->data_pos < 256) {
					int val = dma_channel_write(5, esdi->data[esdi->data_pos]);

					if (val == DMA_NODATA) {
//                                                pclog("DMA out of data\n");
						timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
						return;
					}

					esdi->data_pos++;
				}

				esdi->data_pos = 0;
				esdi->sector_pos++;
				esdi->rba++;
			}

			esdi->status = STATUS_CMD_IN_PROGRESS;
			esdi->cmd_state = 2;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			break;

		case 2:
//                        pclog("Read sector complete\n");
			complete_command_status(esdi);

			esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
			esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);
			break;
		}
		break;

	case CMD_WRITE:
	case CMD_WRITE_VERIFY:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		switch (esdi->cmd_state) {
		case 0:esdi->rba = (esdi->cmd_data[2] | (esdi->cmd_data[3] << 16)) & 0x0fffffff;

			esdi->sector_pos = 0;
			esdi->sector_count = esdi->cmd_data[1];

			if ((esdi->rba + esdi->sector_count) >= hdd_file->sectors) {
				rba_out_of_range(esdi);
				return;
			}

			esdi->status = STATUS_IRQ | STATUS_CMD_IN_PROGRESS | STATUS_TRANSFER_REQ;
			esdi->irq_status = esdi->cmd_dev | IRQ_DATA_TRANSFER_READY;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);

			esdi->cmd_state = 1;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			esdi->data_pos = 0;
			break;

		case 1:
			if (!(esdi->basic_ctrl & CTRL_DMA_ENA)) {
//                                pclog("DMA not enabled\n");
				timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
				return;
			}
			while (esdi->sector_pos < esdi->sector_count) {
//                                pclog("Write sector %i %08x\n", esdi->sector_pos, esdi->rba*512);
				while (esdi->data_pos < 256) {
					int val = dma_channel_read(5);

					if (val == DMA_NODATA) {
//                                                pclog("DMA out of data\n");
						timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
						return;
					}

					esdi->data[esdi->data_pos++] = val & 0xffff;
				}

				if (esdi->rba >= hdd_file->sectors)
					fatal("Write past end of drive\n");
				hdd_write_sectors(hdd_file, esdi->rba, 1, esdi->data);
				esdi->rba++;
				esdi->sector_pos++;
				readflash_set(READFLASH_HDC, esdi->cmd_dev == ATTN_DEVICE_0 ? 0 : 1);

				esdi->data_pos = 0;
			}

			esdi->status = STATUS_CMD_IN_PROGRESS;
			esdi->cmd_state = 2;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			break;

		case 2:
//                        pclog("Write sector complete\n");
			complete_command_status(esdi);

			esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
			esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);
			break;
		}
		break;

	case CMD_READ_VERIFY:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		if ((esdi->rba + esdi->sector_count) >= hdd_file->sectors) {
			rba_out_of_range(esdi);
			return;
		}
		esdi->rba += esdi->sector_count;
		complete_command_status(esdi);
		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	case CMD_SEEK:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		complete_command_status(esdi);
		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	case CMD_GET_DEV_STATUS:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		if ((esdi->status & STATUS_IRQ) || esdi->irq_in_progress)
			fatal("IRQ in progress %02x %i\n", esdi->status, esdi->irq_in_progress);

		esdi->status_len = 9;
		esdi->status_data[0] = CMD_GET_DEV_STATUS | STATUS_LEN(9) | STATUS_DEVICE_HOST_ADAPTER;
		esdi->status_data[1] = 0x0000; /*Error bits*/
		esdi->status_data[2] = 0x1900; /*Device status*/
		esdi->status_data[3] = 0; /*ESDI Standard Status*/
		esdi->status_data[4] = 0; /*ESDI Vendor Unique Status*/
		esdi->status_data[5] = 0;
		esdi->status_data[6] = 0;
		esdi->status_data[7] = 0;
		esdi->status_data[8] = 0;

		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	case CMD_GET_DEV_CONFIG:ESDI_DRIVE_ONLY();

		if (!hdd_file->f) {
			device_not_present(esdi);
			return;
		}

		if ((esdi->status & STATUS_IRQ) || esdi->irq_in_progress)
			fatal("IRQ in progress %02x %i\n", esdi->status, esdi->irq_in_progress);

		esdi->status_len = 6;
		esdi->status_data[0] = CMD_GET_DEV_CONFIG | STATUS_LEN(6) | STATUS_DEVICE_HOST_ADAPTER;
		esdi->status_data[1] = 0x10; /*Zero defect*/
		esdi->status_data[2] = hdd_file->sectors & 0xffff;
		esdi->status_data[3] = hdd_file->sectors >> 16;
		esdi->status_data[4] = hdd_file->tracks;
		esdi->status_data[5] = hdd_file->hpc | (hdd_file->spt << 16);

/*                pclog("CMD_GET_DEV_CONFIG %i  %04x %04x %04x %04x %04x %04x\n", drive->sectors,
                                esdi->status_data[0], esdi->status_data[1],
                                esdi->status_data[2], esdi->status_data[3],
                                esdi->status_data[4], esdi->status_data[5]);*/

		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = esdi->cmd_dev | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	case CMD_GET_POS_INFO:ESDI_ADAPTER_ONLY();

		if ((esdi->status & STATUS_IRQ) || esdi->irq_in_progress)
			fatal("IRQ in progress %02x %i\n", esdi->status, esdi->irq_in_progress);

		esdi->status_len = 5;
		esdi->status_data[0] = CMD_GET_POS_INFO | STATUS_LEN(5) | STATUS_DEVICE_HOST_ADAPTER;
		esdi->status_data[1] = 0xffdd; /*MCA ID*/
		esdi->status_data[2] = esdi->pos_regs[3] | (esdi->pos_regs[2] << 8);
		esdi->status_data[3] = 0xff;
		esdi->status_data[4] = 0xff;

		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	case 0x11:ESDI_ADAPTER_ONLY();
		switch (esdi->cmd_state) {
		case 0:esdi->sector_pos = 0;
			esdi->sector_count = esdi->cmd_data[1];
			if (esdi->sector_count > 256)
				fatal("Read sector buffer count %04x\n", esdi->cmd_data[1]);

			esdi->status = STATUS_IRQ | STATUS_CMD_IN_PROGRESS | STATUS_TRANSFER_REQ;
			esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_DATA_TRANSFER_READY;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);

			esdi->cmd_state = 1;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			esdi->data_pos = 0;
			break;

		case 1:
			if (!(esdi->basic_ctrl & CTRL_DMA_ENA)) {
//                                pclog("DMA not enabled\n");
				timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
				return;
			}
			while (esdi->sector_pos < esdi->sector_count) {
				if (!esdi->data_pos)
					memcpy(esdi->data, esdi->sector_buffer[esdi->sector_pos++], 512);
//                                pclog("Transfer sector %i\n", esdi->sector_pos);
				while (esdi->data_pos < 256) {
					int val = dma_channel_write(5, esdi->data[esdi->data_pos]);

					if (val == DMA_NODATA) {
//                                                pclog("DMA out of data\n");
						timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
						return;
					}

					esdi->data_pos++;
				}

				esdi->data_pos = 0;
			}

			esdi->status = STATUS_CMD_IN_PROGRESS;
			esdi->cmd_state = 2;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			break;

		case 2:
//                        pclog("Read sector buffer complete\n");
			esdi->status = STATUS_IRQ;
			esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_CMD_COMPLETE_SUCCESS;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);
			break;
		}
		break;

	case 0x10:ESDI_ADAPTER_ONLY();
		switch (esdi->cmd_state) {
		case 0:esdi->sector_pos = 0;
			esdi->sector_count = esdi->cmd_data[1];
			if (esdi->sector_count > 256)
				fatal("Write sector buffer count %04x\n", esdi->cmd_data[1]);

			esdi->status = STATUS_IRQ | STATUS_CMD_IN_PROGRESS | STATUS_TRANSFER_REQ;
			esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_DATA_TRANSFER_READY;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);

			esdi->cmd_state = 1;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			esdi->data_pos = 0;
			break;

		case 1:
			if (!(esdi->basic_ctrl & CTRL_DMA_ENA)) {
//                                pclog("DMA not enabled\n");
				timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
				return;
			}
			while (esdi->sector_pos < esdi->sector_count) {
//                                pclog("Transfer sector %i\n", esdi->sector_pos);
				while (esdi->data_pos < 256) {
					int val = dma_channel_read(5);

					if (val == DMA_NODATA) {
						//                                              pclog("DMA out of data\n");
						timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
						return;
					}

					esdi->data[esdi->data_pos++] = val & 0xffff;;
				}

				memcpy(esdi->sector_buffer[esdi->sector_pos++], esdi->data, 512);
				esdi->data_pos = 0;
			}

			esdi->status = STATUS_CMD_IN_PROGRESS;
			esdi->cmd_state = 2;
			timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME);
			break;

		case 2:
//                        pclog("Write sector buffer complete\n");
			esdi->status = STATUS_IRQ;
			esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_CMD_COMPLETE_SUCCESS;
			esdi->irq_in_progress = 1;
			esdi_set_irq(esdi);
			break;
		}
		break;

	case 0x12:ESDI_ADAPTER_ONLY();

		if ((esdi->status & STATUS_IRQ) || esdi->irq_in_progress)
			fatal("IRQ in progress %02x %i\n", esdi->status, esdi->irq_in_progress);

		esdi->status_len = 2;
		esdi->status_data[0] = 0x12 | STATUS_LEN(5) | STATUS_DEVICE_HOST_ADAPTER;
		esdi->status_data[1] = 0;

		esdi->status = STATUS_IRQ | STATUS_STATUS_OUT_FULL;
		esdi->irq_status = IRQ_HOST_ADAPTER | IRQ_CMD_COMPLETE_SUCCESS;
		esdi->irq_in_progress = 1;
		esdi_set_irq(esdi);
		break;

	default:fatal("Bad command %02x %i\n", esdi->command, esdi->cmd_dev);

	}
}

static uint8_t esdi_mca_read(int port, void *p) {
	esdi_t *esdi = (esdi_t *)p;

//        pclog("esdi_mca_read: port=%04x\n", port);

	return esdi->pos_regs[port & 7];
}

static void esdi_mca_write(int port, uint8_t val, void *p) {
	esdi_t *esdi = (esdi_t *)p;

	if (port < 0x102)
		return;

//        pclog("esdi_mca_write: port=%04x val=%02x %04x:%04x\n", port, val, CS, cpu_state.pc);

	esdi->pos_regs[port & 7] = val;

	io_removehandler(0x3510, 0x0008, esdi_read, esdi_readw, NULL, esdi_write, esdi_writew, NULL, esdi);
	mem_mapping_disable(&esdi->bios_rom.mapping);
	if (esdi->pos_regs[2] & 1) {
		io_sethandler(0x3510, 0x0008, esdi_read, esdi_readw, NULL, esdi_write, esdi_writew, NULL, esdi);
		if (!(esdi->pos_regs[3] & 8)) {
			mem_mapping_enable(&esdi->bios_rom.mapping);
			mem_mapping_set_addr(&esdi->bios_rom.mapping, ((esdi->pos_regs[3] & 7) * 0x4000) + 0xc0000, 0x4000);
//                        pclog("ESDI BIOS ROM at %05x %02x\n", ((esdi->pos_regs[3] & 7) * 0x4000) + 0xc0000, esdi->pos_regs[3]);
		}
	}
}

static void *esdi_init() {
	esdi_t *esdi = malloc(sizeof(esdi_t));
	memset(esdi, 0, sizeof(esdi_t));

	rom_init_interleaved(&esdi->bios_rom, "90x8970.bin", "90x8969.bin", 0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);
	mem_mapping_disable(&esdi->bios_rom.mapping);

	hdd_load(&esdi->hdd_file[0], 0, ide_fn[0]);
	hdd_load(&esdi->hdd_file[1], 1, ide_fn[1]);

	timer_add(&esdi->callback_timer, esdi_callback, esdi, 0);

	mca_add(esdi_mca_read, esdi_mca_write, NULL, esdi);

	esdi->pos_regs[0] = 0xff;
	esdi->pos_regs[1] = 0xdd;

	esdi->in_reset = 1;
	timer_set_delay_u64(&esdi->callback_timer, ESDI_TIME * 50);
	esdi->status = STATUS_BUSY;

	return esdi;
}

static void esdi_close(void *p) {
	esdi_t *esdi = (esdi_t *)p;

	hdd_close(&esdi->hdd_file[0]);
	hdd_close(&esdi->hdd_file[1]);

	free(esdi);
}

static int esdi_available() {
	return rom_present("90x8969.bin") && rom_present("90x8970.bin");
}

device_t hdd_esdi_device =
	{
		"IBM ESDI Fixed Disk Adapter (MCA)",
		DEVICE_MCA,
		esdi_init,
		esdi_close,
		esdi_available,
		NULL,
		NULL,
		NULL,
		NULL
	};
