#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>

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

#include "esdi_at.h"

#define IDE_TIME (TIMER_USEC * 10) //(5 * 100 * (1 << TIMER_SHIFT))

#define STAT_ERR 0x01
#define STAT_INDEX 0x02
#define STAT_CORRECTED_DATA 0x04
#define STAT_DRQ 0x08 /* Data request */
#define STAT_DSC 0x10
#define STAT_SEEK_COMPLETE 0x20
#define STAT_READY 0x40
#define STAT_BUSY 0x80

#define ERR_DAM_NOT_FOUND 0x01 /*Data Address Mark not found*/
#define ERR_TR000 0x02         /*Track 0 not found*/
#define ERR_ABRT 0x04          /*Command aborted*/
#define ERR_ID_NOT_FOUND 0x10  /*ID not found*/
#define ERR_DATA_CRC 0x40      /*Data CRC error*/
#define ERR_BAD_BLOCK 0x80     /*Bad Block detected*/

#define CMD_NOP 0x00
#define CMD_RESTORE 0x10
#define CMD_READ 0x20
#define CMD_WRITE 0x30
#define CMD_VERIFY 0x40
#define CMD_FORMAT 0x50
#define CMD_SEEK 0x70
#define CMD_DIAGNOSE 0x90
#define CMD_SET_PARAMETERS 0x91
#define CMD_READ_PARAMETERS 0xec

extern char ide_fn[7][512];

typedef struct esdi_drive_t {
        int cfg_spt;
        int cfg_hpc;
        int current_cylinder;
        hdd_file_t hdd_file;
} esdi_drive_t;

typedef struct esdi_t {
        uint8_t status;
        uint8_t error;
        int secount, sector, cylinder, head, cylprecomp;
        uint8_t command;
        uint8_t fdisk;
        int pos;

        int drive_sel;
        int reset;
        uint16_t buffer[256];
        int irqstat;

        pc_timer_t callback_timer;

        esdi_drive_t drives[2];

        rom_t bios_rom;
} esdi_t;

uint16_t esdi_readw(uint16_t port, void *p);
void esdi_writew(uint16_t port, uint16_t val, void *p);

static inline void esdi_irq_raise(esdi_t *esdi) {
        //        pclog("IDE_IRQ_RAISE\n");
        if (!(esdi->fdisk & 2))
                picint(1 << 14);

        esdi->irqstat = 1;
}

static inline void esdi_irq_lower(esdi_t *esdi) { picintc(1 << 14); }

void esdi_irq_update(esdi_t *esdi) {
        if (esdi->irqstat && !((pic2.pend | pic2.ins) & 0x40) && !(esdi->fdisk & 2))
                picint(1 << 14);
}

/*
 * Return the sector offset for the current register values
 */
int esdi_get_sector(esdi_t *esdi, off_t *addr) {
        esdi_drive_t *drive = &esdi->drives[esdi->drive_sel];
        int heads = drive->cfg_hpc;
        int sectors = drive->cfg_spt;

        if (esdi->head > heads) {
                pclog("esdi_get_sector: past end of configured heads\n");
                return 1;
        }
        if (esdi->sector >= sectors + 1) {
                pclog("esdi_get_sector: past end of configured sectors\n");
                return 1;
        }

        if (drive->cfg_spt == drive->hdd_file.spt && drive->cfg_hpc == drive->hdd_file.hpc) {
                *addr = ((((off_t)esdi->cylinder * heads) + esdi->head) * sectors) + (esdi->sector - 1);
        } else {
                /*When performing translation, the firmware seems to leave 1
                  sector per track inaccessible (spare sector)*/
                int c, h, s;
                *addr = ((((off_t)esdi->cylinder * heads) + esdi->head) * sectors) + (esdi->sector - 1);

                s = *addr % (drive->hdd_file.spt - 1);
                h = (*addr / (drive->hdd_file.spt - 1)) % drive->hdd_file.hpc;
                c = (*addr / (drive->hdd_file.spt - 1)) / drive->hdd_file.hpc;

                *addr = ((((off_t)c * drive->hdd_file.hpc) + h) * drive->hdd_file.spt) + s;
        }

        return 0;
}

/**
 * Move to the next sector using CHS addressing
 */
void esdi_next_sector(esdi_t *esdi) {
        esdi_drive_t *drive = &esdi->drives[esdi->drive_sel];

        esdi->sector++;
        if (esdi->sector == (drive->cfg_spt + 1)) {
                esdi->sector = 1;
                esdi->head++;
                if (esdi->head == drive->cfg_hpc) {
                        esdi->head = 0;
                        esdi->cylinder++;
                        if (drive->current_cylinder < drive->hdd_file.tracks)
                                drive->current_cylinder++;
                }
        }
}

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

        //        pclog("esdi_write: addr=%04x val=%02x\n", port, val);
        switch (port) {
        case 0x1F0: /* Data */
                esdi_writew(port, val | (val << 8), p);
                return;

        case 0x1F1: /* Write precompenstation */
                esdi->cylprecomp = val;
                return;

        case 0x1F2: /* Sector count */
                esdi->secount = val;
                return;

        case 0x1F3: /* Sector */
                esdi->sector = val;
                return;

        case 0x1F4: /* Cylinder low */
                esdi->cylinder = (esdi->cylinder & 0xFF00) | val;
                return;

        case 0x1F5: /* Cylinder high */
                esdi->cylinder = (esdi->cylinder & 0xFF) | (val << 8);
                return;

        case 0x1F6: /* Drive/Head */
                esdi->head = val & 0xF;
                esdi->drive_sel = (val & 0x10) ? 1 : 0;
                if (esdi->drives[esdi->drive_sel].hdd_file.f == NULL)
                        esdi->status = 0;
                else
                        esdi->status = STAT_READY | STAT_DSC;
                return;

        case 0x1F7: /* Command register */
                    //                if (esdi->drives[esdi->drive_sel].hdd_file.f == NULL && val != CMD_SET_PARAMETERS && val !=
                //                CMD_NOP && val != 0xe0 && (val & ~3) != 0x20 && (val & ~3) != 0x30 && (val & ~3) != 0x40 && val
                //                != 0x90 && val != CMD_READ_PARAMETERS)
                //                        fatal("Command %02x on non-present drive\n", val);

                esdi_irq_lower(esdi);
                esdi->command = val;
                esdi->error = 0;

                switch (val & 0xf0) {
                case CMD_RESTORE:
                        //                        pclog("Restore\n");
                        esdi->command &= ~0x0f; /*Mask off step rate*/
                        esdi->status = STAT_BUSY;
                        timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                        break;

                case CMD_SEEK:
                        //                        pclog("Seek to cylinder %i\n", esdi->cylinder);
                        esdi->command &= ~0x0f; /*Mask off step rate*/
                        esdi->status = STAT_BUSY;
                        timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                        break;

                default:
                        switch (val) {
                        case CMD_NOP:
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;

                        case CMD_READ:
                        case CMD_READ + 1:
                        case CMD_READ + 2:
                        case CMD_READ + 3:
                                //                                pclog("Read %i sectors from sector %i cylinder %i head %i
                                //                                %i\n",esdi->secount,esdi->sector,esdi->cylinder,esdi->head,ins);
                                esdi->command &= ~3;
                                if (val & 2)
                                        fatal("Read with ECC\n");
                        case 0xa0:
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;

                        case CMD_WRITE:
                        case CMD_WRITE + 1:
                        case CMD_WRITE + 2:
                        case CMD_WRITE + 3:
                                //                                pclog("Write %i sectors to sector %i cylinder %i head
                                //                                %i\n",esdi->secount,esdi->sector,esdi->cylinder,esdi->head);
                                esdi->command &= ~3;
                                if (val & 2)
                                        fatal("Write with ECC\n");
                                esdi->status = STAT_DRQ | STAT_DSC; // | STAT_BUSY;
                                esdi->pos = 0;
                                break;

                        case CMD_VERIFY:
                        case CMD_VERIFY + 1:
                                //                                pclog("Read verify %i sectors from sector %i cylinder %i head
                                //                                %i\n",esdi->secount,esdi->sector,esdi->cylinder,esdi->head);
                                esdi->command &= ~1;
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;

                        case CMD_FORMAT:
                                //                                pclog("Format track %i head %i\n", esdi->cylinder, esdi->head);
                                esdi->status = STAT_DRQ; // | STAT_BUSY;
                                esdi->pos = 0;
                                break;

                        case CMD_SET_PARAMETERS: /* Initialize Drive Parameters */
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 30 * IDE_TIME);
                                break;

                        case CMD_DIAGNOSE: /* Execute Drive Diagnostics */
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;

                        case 0xe0: /*???*/
                        case CMD_READ_PARAMETERS:
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;

                        default:
                                pclog("Bad esdi command %02X\n", val);
                        case 0xe8: /*???*/
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 200 * IDE_TIME);
                                break;
                        }
                }
                break;

        case 0x3F6: /* Device control */
                if ((esdi->fdisk & 4) && !(val & 4)) {
                        timer_set_delay_u64(&esdi->callback_timer, 500 * IDE_TIME);
                        esdi->reset = 1;
                        esdi->status = STAT_BUSY;
                        //                        pclog("esdi Reset\n");
                }
                if (val & 4) {
                        /*Drive held in reset*/
                        timer_disable(&esdi->callback_timer);
                        esdi->status = STAT_BUSY;
                }
                esdi->fdisk = val;
                esdi_irq_update(esdi);
                return;
        }
        //        fatal("Bad ESDI write %04X %02X\n", port, val);
}

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

        //        pclog("Write ESDIw %04X\n",val);
        esdi->buffer[esdi->pos >> 1] = val;
        esdi->pos += 2;

        if (esdi->pos >= 512) {
                esdi->pos = 0;
                esdi->status = STAT_BUSY;
                timer_set_delay_u64(&esdi->callback_timer, 6 * IDE_TIME);
        }
}

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

        switch (port) {
        case 0x1F0: /* Data */
                temp = esdi_readw(port, esdi) & 0xff;
                break;

        case 0x1F1: /* Error */
                temp = esdi->error;
                break;

        case 0x1F2: /* Sector count */
                temp = (uint8_t)esdi->secount;
                break;

        case 0x1F3: /* Sector */
                temp = (uint8_t)esdi->sector;
                break;

        case 0x1F4: /* Cylinder low */
                temp = (uint8_t)(esdi->cylinder & 0xFF);
                break;

        case 0x1F5: /* Cylinder high */
                temp = (uint8_t)(esdi->cylinder >> 8);
                break;

        case 0x1F6: /* Drive/Head */
                temp = (uint8_t)(esdi->head | (esdi->drive_sel ? 0x10 : 0) | 0xa0);
                break;

        case 0x1F7: /* Status */
                esdi_irq_lower(esdi);
                temp = esdi->status;
                break;
        }

        //        if (port != 0x1f7) pclog("esdi_read: addr=%04x val=%02x %04X:%04x\n", port, temp, CS, cpu_state.pc);
        return temp;
}

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

        temp = esdi->buffer[esdi->pos >> 1];
        esdi->pos += 2;

        if (esdi->pos >= 512) {
                //                pclog("Over! packlen %i %i\n",ide->packlen,ide->pos);
                esdi->pos = 0;
                esdi->status = STAT_READY | STAT_DSC;
                if (esdi->command == CMD_READ || esdi->command == 0xa0) {
                        esdi->secount = (esdi->secount - 1) & 0xff;
                        if (esdi->secount) {
                                esdi_next_sector(esdi);
                                esdi->status = STAT_BUSY;
                                timer_set_delay_u64(&esdi->callback_timer, 6 * IDE_TIME);
                        }
                }
        }

        //        pclog("esdi_readw: temp=%04x %i\n", temp, esdi->pos);
        return temp;
}

void esdi_callback(void *p) {
        esdi_t *esdi = (esdi_t *)p;
        esdi_drive_t *drive = &esdi->drives[esdi->drive_sel];
        off_t addr;

        //        pclog("esdi_callback: command=%02x reset=%i\n", esdi->command, esdi->reset);

        if (esdi->reset) {
                esdi->status = STAT_READY | STAT_DSC;
                esdi->error = 1;
                esdi->secount = 1;
                esdi->sector = 1;
                esdi->head = 0;
                esdi->cylinder = 0;
                esdi->reset = 0;
                //                pclog("Reset callback\n");
                return;
        }
        switch (esdi->command) {
        case CMD_RESTORE:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        drive->current_cylinder = 0;
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        case CMD_SEEK:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        case CMD_READ:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        if (esdi_get_sector(esdi, &addr)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        if (hdd_read_sectors(&drive->hdd_file, addr, 1, esdi->buffer)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        esdi->pos = 0;
                        esdi->status = STAT_DRQ | STAT_READY | STAT_DSC;
                        //                pclog("Read sector callback %i %i %i offset %08X %i left %i
                        //                %02X\n",ide.sector,ide.cylinder,ide.head,addr,ide.secount,ide.spt,ide.atastat[ide.board]);
                        //                if (addr) output=3;
                        esdi_irq_raise(esdi);
                        readflash_set(READFLASH_HDC, esdi->drive_sel);
                }
                break;

        case CMD_WRITE:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        if (esdi_get_sector(esdi, &addr)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        if (hdd_write_sectors(&drive->hdd_file, addr, 1, esdi->buffer)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        esdi_irq_raise(esdi);
                        esdi->secount = (esdi->secount - 1) & 0xff;
                        if (esdi->secount) {
                                esdi->status = STAT_DRQ | STAT_READY | STAT_DSC;
                                esdi->pos = 0;
                                esdi_next_sector(esdi);
                        } else
                                esdi->status = STAT_READY | STAT_DSC;
                        readflash_set(READFLASH_HDC, esdi->drive_sel);
                }
                break;

        case CMD_VERIFY:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        if (esdi_get_sector(esdi, &addr)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        if (hdd_read_sectors(&drive->hdd_file, addr, 1, esdi->buffer)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        readflash_set(READFLASH_HDC, esdi->drive_sel);
                        esdi_next_sector(esdi);
                        esdi->secount = (esdi->secount - 1) & 0xff;
                        if (esdi->secount)
                                timer_set_delay_u64(&esdi->callback_timer, 6 * IDE_TIME);
                        else {
                                esdi->pos = 0;
                                esdi->status = STAT_READY | STAT_DSC;
                                //                pclog("Read verify callback %i %i %i offset %08X %i
                                //                left\n",ide.sector,ide.cylinder,ide.head,addr,ide.secount);
                                esdi_irq_raise(esdi);
                        }
                }
                break;

        case CMD_FORMAT:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        if (esdi_get_sector(esdi, &addr)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        if (hdd_format_sectors(&drive->hdd_file, addr, esdi->secount)) {
                                esdi->error = ERR_ID_NOT_FOUND;
                                esdi->status = STAT_READY | STAT_DSC | STAT_ERR;
                                esdi_irq_raise(esdi);
                                break;
                        }
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                        readflash_set(READFLASH_HDC, esdi->drive_sel);
                }
                break;

        case CMD_DIAGNOSE:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        esdi->error = 1; /*No error detected*/
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        case CMD_SET_PARAMETERS: /* Initialize Drive Parameters */
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        drive->cfg_spt = esdi->secount;
                        drive->cfg_hpc = esdi->head + 1;
                        pclog("Parameters: spt=%i hpc=%i\n", drive->cfg_spt, drive->cfg_hpc);
                        if (!esdi->secount)
                                fatal("secount=0\n");
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        case CMD_NOP:
                esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                esdi->error = ERR_ABRT;
                esdi_irq_raise(esdi);
                break;

        case 0xe0:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        switch (esdi->cylinder >> 8) {
                        case 0x31:
                                esdi->cylinder = drive->hdd_file.tracks;
                                break;
                        case 0x33:
                                esdi->cylinder = drive->hdd_file.hpc;
                                break;
                        case 0x35:
                                esdi->cylinder = 0x200;
                                break;
                        case 0x36:
                                esdi->cylinder = drive->hdd_file.spt;
                                break;
                        default:
                                pclog("EDSI Bad read config %02x\n", esdi->cylinder >> 8);
                        }
                        esdi->status = STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        case 0xa0:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        memset(esdi->buffer, 0, 512);
                        memset(&esdi->buffer[3], 0xff, 512 - 6);
                        esdi->pos = 0;
                        esdi->status = STAT_DRQ | STAT_READY | STAT_DSC;
                        //                pclog("Read sector callback %i %i %i offset %08X %i left %i
                        //                %02X\n",ide.sector,ide.cylinder,ide.head,addr,ide.secount,ide.spt,ide.atastat[ide.board]);
                        //                if (addr) output=3;
                        esdi_irq_raise(esdi);
                }
                break;

        case CMD_READ_PARAMETERS:
                if (!drive->hdd_file.f) {
                        esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                        esdi->error = ERR_ABRT;
                        esdi_irq_raise(esdi);
                } else {
                        memset(esdi->buffer, 0, 512);

                        esdi->buffer[0] = 0x44;                                  /* general configuration */
                        esdi->buffer[1] = drive->hdd_file.tracks;                /* number of non-removable cylinders */
                        esdi->buffer[2] = 0;                                     /* number of removable cylinders */
                        esdi->buffer[3] = drive->hdd_file.hpc;                   /* number of heads */
                        esdi->buffer[5] = esdi->buffer[4] * drive->hdd_file.spt; /* number of unformatted bytes/sector */
                        esdi->buffer[4] = 600;                                   /* number of unformatted bytes/track */
                        esdi->buffer[6] = drive->hdd_file.spt;                   /* number of sectors */
                        esdi->buffer[7] = 0;                                     /*minimum bytes in inter-sector gap*/
                        esdi->buffer[8] = 0;                                     /* minimum bytes in postamble */
                        esdi->buffer[9] = 0;                                     /* number of words of vendor status */
                        /* controller info */
                        //	char	wdp_cnsn[20];		/* controller serial number */
                        esdi->buffer[20] = 2; /* controller type */
                        esdi->buffer[21] = 1; /* sector buffer size, in sectors */
                        esdi->buffer[22] = 0; /* ecc bytes appended */
                                              //	char	wdp_rev[8];		/* firmware revision */
                        // 27
                        esdi->buffer[27] = 'W' | ('D' << 8);
                        esdi->buffer[28] = '1' | ('0' << 8);
                        esdi->buffer[29] = '0' | ('7' << 8);
                        esdi->buffer[30] = 'V' | ('-' << 8);
                        esdi->buffer[31] = 'S' | ('E' << 8);
                        esdi->buffer[32] = '1';
                        esdi->buffer[47] = 0; /* sectors per interrupt */
                        esdi->buffer[48] = 0; /* can use double word read/write? */
                        esdi->pos = 0;
                        esdi->status = STAT_DRQ | STAT_READY | STAT_DSC;
                        esdi_irq_raise(esdi);
                }
                break;

        default:
                pclog("ESDI Callback on unknown command %02x\n", esdi->command);
        case 0xe8:
                esdi->status = STAT_READY | STAT_ERR | STAT_DSC;
                esdi->error = ERR_ABRT;
                esdi_irq_raise(esdi);
                break;
        }
}

static void esdi_rom_write(uint32_t addr, uint8_t val, void *p) {
        rom_t *rom = (rom_t *)p;

        addr &= rom->mask;

        if (addr >= 0x1f00 && addr < 0x2000)
                rom->rom[addr] = val;
}

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

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

        esdi->drives[0].cfg_spt = esdi->drives[0].hdd_file.spt;
        esdi->drives[0].cfg_hpc = esdi->drives[0].hdd_file.hpc;
        esdi->drives[1].cfg_spt = esdi->drives[1].hdd_file.spt;
        esdi->drives[1].cfg_hpc = esdi->drives[1].hdd_file.hpc;

        esdi->status = STAT_READY | STAT_DSC;
        esdi->error = 1; /*No errors*/

        rom_init(&esdi->bios_rom, "62-000279-061.bin", 0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

        mem_mapping_set_handler(&esdi->bios_rom.mapping, rom_read, rom_readw, rom_readl, esdi_rom_write, NULL, NULL);

        io_sethandler(0x01f0, 0x0001, esdi_read, esdi_readw, NULL, esdi_write, esdi_writew, NULL, esdi);
        io_sethandler(0x01f1, 0x0007, esdi_read, NULL, NULL, esdi_write, NULL, NULL, esdi);
        io_sethandler(0x03f6, 0x0001, NULL, NULL, NULL, esdi_write, NULL, NULL, esdi);

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

        return esdi;
}

void wd1007vse1_close(void *p) {
        esdi_t *esdi = (esdi_t *)p;

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

        free(esdi);
}

static int wd1007vse1_available() { return rom_present("62-000279-061.bin"); }

device_t wd1007vse1_device = {"Western Digital WD1007V-SE1 (ESDI)",
                              DEVICE_AT,
                              wd1007vse1_init,
                              wd1007vse1_close,
                              wd1007vse1_available,
                              NULL,
                              NULL,
                              NULL,
                              NULL};
