/*
    JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.4

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007-2010 The University of Oxford

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    jpc.sourceforge.net
    or the developer website
    sourceforge.net/projects/jpc/

    Conceived and Developed by:
    Rhys Newman, Ian Preston, Chris Dennis

    End of licence header
*/

package org.jpc.emulator.pci.peripheral;

import org.jpc.emulator.motherboard.*;
import org.jpc.support.BlockDevice;
import org.jpc.emulator.*;

import java.io.*;
import java.util.logging.*;

/**
 * 
 * @author Chris Dennis
 */
class IDEChannel extends AbstractHardwareComponent implements IODevice {

    private static final Logger LOGGING = Logger.getLogger(IDEChannel.class.getName());
    private IDEState[] devices;
    private IDEState currentDevice;
    private int ioBase,  ioBaseTwo,  irq;
    private InterruptController irqDevice;
    private int nextDriveSerial;
    public static final String CDLABEL = "CDROM";//"JPC CD-ROM";

    public void saveState(DataOutput output) throws IOException {
        output.writeInt(ioBase);
        output.writeInt(ioBaseTwo);
        output.writeInt(irq);
        output.writeInt(nextDriveSerial);
        for (IDEState device : devices) {
            device.saveState(output);
        }
        int currentDeviceIndex = -1;
        for (int i = 0; i < devices.length; i++) {
            if (currentDevice == devices[i]) {
                currentDeviceIndex = i;
            }
        }
        output.writeInt(currentDeviceIndex);
    }

    public void loadState(DataInput input) throws IOException {
        ioBase = input.readInt();
        ioBaseTwo = input.readInt();
        irq = input.readInt();
        nextDriveSerial = input.readInt();
        for (int i = 0; i < devices.length; i++) {
            devices[i].loadState(input);
        }
        int currentDeviceIndex = input.readInt();
        currentDevice = devices[currentDeviceIndex];
    }

    private static void shortToBigEndianBytes(byte[] buffer, int offset, short val) {
        buffer[offset + 0] = (byte) (val >> 8);
        buffer[offset + 1] = (byte) (val);
    }

    private static void intToBigEndianBytes(byte[] buffer, int offset, int val) {
        buffer[offset + 0] = (byte) (val >> 24);
        buffer[offset + 1] = (byte) (val >> 16);
        buffer[offset + 2] = (byte) (val >> 8);
        buffer[offset + 3] = (byte) (val);
    }

    private static int bigEndianBytesToInt(byte[] buffer, int offset) {
        int val = 0;
        val |= ((buffer[offset + 0] << 24) & 0xff000000);
        val |= ((buffer[offset + 1] << 16) & 0x00ff0000);
        val |= ((buffer[offset + 2] << 8) & 0x0000ff00);
        val |= ((buffer[offset + 3] << 0) & 0x000000ff);
        return val;
    }

    private static short bigEndianBytesToShort(byte[] buffer, int offset) {
        short val = 0;
        val |= ((buffer[offset + 0] << 8) & 0xff00);
        val |= ((buffer[offset + 1] << 0) & 0x00ff);
        return val;
    }

    private static void lbaToMSF(byte[] buffer, int offset, int lba) {
        lba += 150;
        buffer[offset + 0] = (byte) ((lba / 75) / 60);
        buffer[offset + 1] = (byte) ((lba / 75) % 60);
        buffer[offset + 2] = (byte) (lba % 75);
    }

    private static void putLE16InByte(byte[] dest, int offset, int data) {
        dest[offset + 0] = (byte) data;
        dest[offset + 1] = (byte) (data >>> 8);
    }

    private static void stringToBytes(String text, byte[] dest, int start, int length) {
        byte[] temp = text.getBytes();
        int i = 0;
        for (; i < Math.min(temp.length, length); i++) {
            dest[(start + i) ^ 1] = temp[i];
        }
        for (; i < length; i++) {
            dest[(start + i) ^ 1] = 0x20;
        }
    }

    public IDEChannel(int irq, InterruptController irqDevice, int ioBase, int ioBaseTwo, BlockDevice[] drives, BMDMAIORegion bmdma) {
        this.irq = irq;
        this.irqDevice = irqDevice;
        this.ioBase = ioBase;
        this.ioBaseTwo = ioBaseTwo;
        this.nextDriveSerial = 1;

        devices = new IDEState[2];
        devices[0] = new IDEState(drives[0]); // master
        devices[1] = new IDEState(drives[1]); // slave

        devices[0].bmdma = bmdma;
        devices[1].bmdma = bmdma;

        currentDevice = devices[0];
    }

    public void setDrives(BlockDevice[] drives) {
        devices[0].setDrive(drives[0]);
        devices[1].setDrive(drives[1]);
    }

    public void ioPortWrite8(int address, int data) {
        if (address == ioBaseTwo) {
            writeCommand(data);
            return;
        } else {
            writeIDE(address, data);
            return;
        }
    }

    public void ioPortWrite16(int address, int data) {
        switch (address - ioBase) {
            case 0:
            case 1:
                writeDataWord(data);
                break;
            default:
                ioPortWrite8(address, data);
                ioPortWrite8(address + 1, data >>> 8);
                break;
        }
    }

    public void ioPortWrite32(int address, int data) {
        switch (address - ioBase) {
            case 0:
            case 1:
            case 2:
            case 3:
                writeDataLong(data);
                break;
            default:
                ioPortWrite16(address, data);
                ioPortWrite16(address + 2, data >>> 16);
                break;
        }
    }

    public int ioPortRead8(int address) {
        if (address == ioBaseTwo) {
            return readStatus();
        } else {
            return readIDE(address);
        }
    }

    public int ioPortRead16(int address) {
        switch (address - ioBase) {
            case 0:
            case 1:
                return readDataWord();
            default:
                return (0xff & ioPortRead8(address)) |
                        (0xff00 & (ioPortRead8(address + 1) << 8));
        }
    }

    public int ioPortRead32(int address) {
        switch (address - ioBase) {
            case 0:
            case 1:
            case 2:
            case 3:
                return readDataLong();
            default:
                return (0xffff & ioPortRead16(address)) |
                        (0xffff0000 & (ioPortRead16(address + 2) << 16));
        }
    }

    public int[] ioPortsRequested() {
        if (ioBaseTwo == 0) {
            return new int[]{ioBase, ioBase + 1,
                        ioBase + 2, ioBase + 3,
                        ioBase + 4, ioBase + 5,
                        ioBase + 6, ioBase + 7
                    };
        } else {
            return new int[]{ioBase, ioBase + 1,
                        ioBase + 2, ioBase + 3,
                        ioBase + 4, ioBase + 5,
                        ioBase + 6, ioBase + 7,
                        ioBaseTwo
                    };
        }
    }

    private void writeCommand(int data) {
        /* common for both drives */
        if (((devices[0].command & IDEState.IDE_CMD_RESET) == 0) &&
                ((data & IDEState.IDE_CMD_RESET) != 0)) {
            /* reset low to high */
            devices[0].status = (byte) (IDEState.BUSY_STAT | IDEState.SEEK_STAT);
            devices[0].error = 0x01;
            devices[1].status = (byte) (IDEState.BUSY_STAT | IDEState.SEEK_STAT);
            devices[1].error = 0x01;
        } else if (((devices[0].command & IDEState.IDE_CMD_RESET) != 0) &&
                ((data & IDEState.IDE_CMD_RESET) == 0)) {
            /* reset high to low */
            for (int i = 0; i < 2; i++) {
                if (devices[i].isCDROM) {
                    devices[i].status = 0x00; /* NOTE: READY is not set */

                } else {
                    devices[i].status = (byte) (IDEState.READY_STAT | IDEState.SEEK_STAT);
                }
                devices[i].setSignature();
            }
        }

        devices[0].command = (byte) data;
        devices[1].command = (byte) data;
    }

    private int readStatus() {
        if (((devices[0].drive == null) && (devices[1].drive == null)) ||
                ((currentDevice != devices[0]) && (currentDevice.drive == null))) {
            return 0;
        } else {
            return currentDevice.status;
        }
    }

    private void writeIDE(int address, int data) {
        
        boolean lba48 = false;
        address &= 7;
        switch (address) {
            case 0:
                break;
            case 1:
                clearHob();
                /* NOTE: data is written to the two drives */
                devices[0].hobFeature = devices[0].feature;
                devices[1].hobFeature = devices[1].feature;
                devices[0].feature = (byte) data;
                devices[1].feature = (byte) data;
                break;
            case 2:
                clearHob();
                devices[0].hobNSector = (byte) devices[0].nSector;
                devices[1].hobNSector = (byte) devices[1].nSector;
                devices[0].nSector = 0xff & data;
                devices[1].nSector = 0xff & data;
                break;
            case 3:
                clearHob();
                devices[0].hobSector = devices[0].sector;
                devices[1].hobSector = devices[1].sector;
                devices[0].sector = (byte) data;
                devices[1].sector = (byte) data;
                break;
            case 4:
                clearHob();
                devices[0].hobLCyl = devices[0].lcyl;
                devices[1].hobLCyl = devices[1].lcyl;
                devices[0].lcyl = (byte) data;
                devices[1].lcyl = (byte) data;
                break;
            case 5:
                clearHob();
                devices[0].hobHCyl = devices[0].hcyl;
                devices[1].hobHCyl = devices[1].hcyl;
                devices[0].hcyl = (byte) data;
                devices[1].hcyl = (byte) data;
                break;
            case 6:
                // FIXME: HOB readback uses bit 7
                devices[0].select = (byte) ((data & ~0x10) | 0xa0);
                devices[1].select = (byte) (data | 0x10 | 0xa0);
                /* select drive */
                currentDevice = devices[(data >> 4) & 1];
                break;
            default:
            case 7:

                /* ignore commands to non existant slave */
                if (currentDevice != devices[0] && currentDevice.drive == null) {
                    break;
                }
                switch (data) {
                    case IDEState.WIN_IDENTIFY:
                        if ((currentDevice.drive != null) && !currentDevice.isCDROM) {
                            currentDevice.identify();
                            currentDevice.status = (byte) (IDEState.READY_STAT | IDEState.SEEK_STAT);
                            currentDevice.transferStart(currentDevice.ioBuffer, 0, 512, IDEState.ETF_TRANSFER_STOP);
                        } else {
                            if (currentDevice.isCDROM) {
                                currentDevice.setSignature();
                            }
                            currentDevice.abortCommand();
                        }
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_SPECIFY:
                    case IDEState.WIN_RECAL:
                        currentDevice.error = 0;
                        currentDevice.status = (byte) (IDEState.READY_STAT | IDEState.SEEK_STAT);
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_SETMULT:
                        if (currentDevice.nSector > IDEState.MAX_MULT_SECTORS ||
                                currentDevice.nSector == 0 ||
                                (currentDevice.nSector & (currentDevice.nSector - 1)) != 0) {
                            currentDevice.abortCommand();
                        } else {
                            currentDevice.multSectors = currentDevice.nSector;
                            currentDevice.status = IDEState.READY_STAT;
                        }
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_VERIFY_EXT:
                        lba48 = true;
                    case IDEState.WIN_VERIFY:
                    case IDEState.WIN_VERIFY_ONCE:
                        currentDevice.commandLBA48Transform(lba48);
                        /* do sector number check ? */
                        currentDevice.status = IDEState.READY_STAT;
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_READ_EXT:
                        lba48 = true;
                    case IDEState.WIN_READ:
                    case IDEState.WIN_READ_ONCE:
                        if (currentDevice.drive == null) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.requiredNumberOfSectors = 1;
                        currentDevice.sectorRead();
                        break;
                    case IDEState.WIN_WRITE_EXT:
                        lba48 = true;
                    case IDEState.WIN_WRITE:
                    case IDEState.WIN_WRITE_ONCE:
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.error = 0;
                        currentDevice.status = IDEState.SEEK_STAT | IDEState.READY_STAT;
                        currentDevice.requiredNumberOfSectors = 1;
                        currentDevice.transferStart(currentDevice.ioBuffer, 0, 512, IDEState.ETF_SECTOR_WRITE);
                        break;
                    case IDEState.WIN_MULTREAD_EXT:
                        lba48 = true;
                    case IDEState.WIN_MULTREAD:
                        if (currentDevice.multSectors == 0) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.requiredNumberOfSectors = currentDevice.multSectors;
                        currentDevice.sectorRead();
                        break;
                    case IDEState.WIN_MULTWRITE_EXT:
                        lba48 = true;
                    case IDEState.WIN_MULTWRITE:
                        if (currentDevice.multSectors == 0) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.error = 0;
                        currentDevice.status = IDEState.SEEK_STAT | IDEState.READY_STAT;
                        currentDevice.requiredNumberOfSectors = currentDevice.multSectors;
                        int n = currentDevice.nSector;
                        if (n > currentDevice.requiredNumberOfSectors) {
                            n = currentDevice.requiredNumberOfSectors;
                        }
                        currentDevice.transferStart(currentDevice.ioBuffer, 0, 512 * n, IDEState.ETF_SECTOR_WRITE);
                        break;
                    case IDEState.WIN_READDMA_EXT:
                        lba48 = true;
                    case IDEState.WIN_READDMA:
                    case IDEState.WIN_READDMA_ONCE:
                        if (currentDevice.drive == null) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.sectorReadDMA();
                        break;
                    case IDEState.WIN_WRITEDMA_EXT:
                        lba48 = true;
                    case IDEState.WIN_WRITEDMA:
                    case IDEState.WIN_WRITEDMA_ONCE:
                        if (currentDevice.drive == null) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.sectorWriteDMA();
                        break;
                    case IDEState.WIN_READ_NATIVE_MAX_EXT:
                        lba48 = true;
                    case IDEState.WIN_READ_NATIVE_MAX:
                        currentDevice.commandLBA48Transform(lba48);
                        currentDevice.setSector(currentDevice.drive.getTotalSectors() - 1);
                        currentDevice.status = IDEState.READY_STAT;
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_CHECKPOWERMODE1:
                        currentDevice.nSector = 0xff; /* device active or idle */
                        currentDevice.status = IDEState.READY_STAT;
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_SETFEATURES:
                        if (currentDevice.drive == null) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        /* XXX: valid for CDROM ? */
                        switch (currentDevice.feature) {
                            case (byte) 0x02: /* write cache enable */
                            case (byte) 0x82: /* write cache disable */
                            case (byte) 0xaa: /* read look-ahead enable */
                            case (byte) 0x55: /* read look-ahead disable */
                                currentDevice.status = IDEState.READY_STAT | IDEState.SEEK_STAT;
                                currentDevice.setIRQ();
                                break;
                            case (byte) 0x03: /* set transfer mode */
                                int val = currentDevice.nSector & 0x07;
                                switch (currentDevice.nSector >>> 3) {
                                    case 0x00: // pio default
                                    case 0x01: // pio mode
                                        putLE16InByte(currentDevice.identifyData, 126, 0x07);
                                        putLE16InByte(currentDevice.identifyData, 176, 0x3f);
                                        break;
                                    case 0x04: /* mdma mode */
                                        putLE16InByte(currentDevice.identifyData, 126, 0x07 | (1 << (val + 8)));
                                        putLE16InByte(currentDevice.identifyData, 176, 0x3f);
                                        break;
                                    case 0x08: /* udma mode */
                                        putLE16InByte(currentDevice.identifyData, 126, 0x07);
                                        putLE16InByte(currentDevice.identifyData, 176, 0x3f | (1 << (val + 8)));
                                        break;
                                    default:
                                        currentDevice.abortCommand();
                                        currentDevice.setIRQ();
                                        return;
                                }
                                currentDevice.status = IDEState.READY_STAT | IDEState.SEEK_STAT;
                                currentDevice.setIRQ();
                                break;
                            default:
                                currentDevice.abortCommand();
                                currentDevice.setIRQ();
                                return;
                        }
                        break;
                    case IDEState.WIN_FLUSH_CACHE:
                    case IDEState.WIN_FLUSH_CACHE_EXT:
                        if (currentDevice.drive != null) {
                            LOGGING.log(Level.INFO, "Should flush {0}", currentDevice.drive);
                        }
                        currentDevice.status = IDEState.READY_STAT;
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_STANDBYNOW1:
                    case IDEState.WIN_IDLEIMMEDIATE:
                        currentDevice.status = IDEState.READY_STAT;
                        currentDevice.setIRQ();
                        break;
                    /* ATAPI commands */
                    case IDEState.WIN_PIDENTIFY:
                        if (currentDevice.isCDROM) {
                            currentDevice.atapiIdentify();
                            currentDevice.status = IDEState.READY_STAT | IDEState.SEEK_STAT;
                            currentDevice.transferStart(currentDevice.ioBuffer, 0, 512, IDEState.ETF_TRANSFER_STOP);
                        } else {
                            currentDevice.abortCommand();
                        }
                        currentDevice.setIRQ();
                        break;
                    case IDEState.WIN_DIAGNOSE:
                        currentDevice.setSignature();
                        currentDevice.status = 0x00;
                        currentDevice.error = 0x01;
                        break;
                    case IDEState.WIN_SRST:
                        if (!currentDevice.isCDROM) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.setSignature();
                        currentDevice.status = 0x00; /* NOTE: READY is _not_ set */
                        currentDevice.error = 0x01;
                        break;
                    case IDEState.WIN_PACKETCMD:
                        if (!currentDevice.isCDROM) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        /* overlapping commands not supported */
                        if ((currentDevice.feature & 0x02) != 0) {
                            currentDevice.abortCommand();
                            currentDevice.setIRQ();
                            return;
                        }
                        currentDevice.atapiDMA = ((currentDevice.feature & 1) == 1);
                        currentDevice.nSector = 1;
                        currentDevice.transferStart(currentDevice.ioBuffer, 0, IDEState.ATAPI_PACKET_SIZE, IDEState.ETF_ATAPI_COMMAND);
                        break;
                    default:
                        currentDevice.abortCommand();
                        currentDevice.setIRQ();
                        return;
                }

        }
    }

    private int readIDE(int address) {
        address &= 0x7;
        //boolean hob = (currentDevice.select & (1 << 7)) != 0;
        boolean hob = false;
        switch (address) {
            case 0:
                return 0xff;
            case 1:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else if (!hob) {
                    return currentDevice.error;
                } else {
                    return currentDevice.hobFeature;
                }
            case 2:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else if (!hob) {
                    return currentDevice.nSector & 0xff;
                } else {
                    return currentDevice.hobNSector;
                }
            case 3:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else if (!hob) {
                    return currentDevice.sector;
                } else {
                    return currentDevice.hobSector;
                }
            case 4:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else if (!hob) {
                    return currentDevice.lcyl;
                } else {
                    return currentDevice.hobLCyl;
                }
            case 5:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else if (!hob) {
                    return currentDevice.hcyl;
                } else {
                    return currentDevice.hobHCyl;
                }
            case 6:
                if (devices[0].drive == null && devices[1].drive == null) {
                    return 0;
                } else {
                    return currentDevice.select;
                }
            default:
            case 7:
                if ((devices[0].drive == null && devices[1].drive == null) ||
                        (currentDevice != devices[0] && currentDevice.drive == null)) {
                    irqDevice.setIRQ(irq, 0);
                    return 0;
                } else {
                    irqDevice.setIRQ(irq, 0);
                    return currentDevice.status;
                }
        }
    }

    private int readDataWord() {
        int data = 0;
        data |= 0xff & currentDevice.dataBuffer[currentDevice.dataBufferOffset++];
        data |= 0xff00 & (currentDevice.dataBuffer[currentDevice.dataBufferOffset++] << 8);

        if (currentDevice.dataBufferOffset >= currentDevice.dataBufferEnd) {
            currentDevice.endTransfer(currentDevice.endTransferFunction);
        }
        return data;
    }

    private int readDataLong() {
        int data = 0;
        data |= 0xff & currentDevice.dataBuffer[currentDevice.dataBufferOffset++];
        data |= 0xff00 & (currentDevice.dataBuffer[currentDevice.dataBufferOffset++] << 8);
        data |= 0xff0000 & (currentDevice.dataBuffer[currentDevice.dataBufferOffset++] << 16);
        data |= 0xff000000 & (currentDevice.dataBuffer[currentDevice.dataBufferOffset++] << 24);

        if (currentDevice.dataBufferOffset >= currentDevice.dataBufferEnd) {
            currentDevice.endTransfer(currentDevice.endTransferFunction);
        }
        return data;
    }

    private void writeDataWord(int data) {
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data);
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data >> 8);

        if (currentDevice.dataBufferOffset >= currentDevice.dataBufferEnd) {
            currentDevice.endTransfer(currentDevice.endTransferFunction);
        }
    }

    private void writeDataLong(int data) {
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data);
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data >> 8);
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data >> 16);
        currentDevice.dataBuffer[currentDevice.dataBufferOffset++] = (byte) (data >> 24);

        if (currentDevice.dataBufferOffset >= currentDevice.dataBufferEnd) {
            currentDevice.endTransfer(currentDevice.endTransferFunction);
        }
    }

    private void clearHob() {
        /* any write clears HOB high bit of device control register */
        devices[0].select &= ~(1 << 7);
        devices[1].select &= ~(1 << 7);
    }

    class IDEState implements Hibernatable {
        /* Bits of HD_STATUS */

        public static final int ERR_STAT = 0x01;
        public static final int INDEX_STAT = 0x02;
        public static final int ECC_STAT = 0x04;/* Corrected error */

        public static final int DRQ_STAT = 0x08;
        public static final int SEEK_STAT = 0x10;
        public static final int SRV_STAT = 0x10;
        public static final int WRERR_STAT = 0x20;
        public static final int READY_STAT = 0x40;
        public static final int BUSY_STAT = 0x80;

        /* Bits for HD_ERROR */
        private static final int MARK_ERR = 0x01; /* Bad address mark */

        private static final int TRK0_ERR = 0x02; /* couldn't find track 0 */

        private static final int ABRT_ERR = 0x04; /* Command aborted */

        private static final int MCR_ERR = 0x08; /* media change request */

        private static final int ID_ERR = 0x10; /* ID field not found */

        private static final int MC_ERR = 0x20; /* media changed */

        private static final int ECC_ERR = 0x40; /* Uncorrectable ECC error */

        private static final int BBD_ERR = 0x80; /* pre-EIDE meaning:  block marked bad */

        private static final int ICRC_ERR = 0x80; /* new meaning:  CRC error during transfer */

        public static final int IDE_CMD_RESET = 0x04;
        public static final int IDE_CMD_DISABLE_IRQ = 0x02;

        /* ATA/ATAPI Commands pre T13 Spec */
        public static final int WIN_NOP = 0x00;
        /*
         *      0x01->0x02 Reserved
         */
        public static final int CFA_REQ_EXT_ERROR_CODE = 0x03; /* CFA Request Extended Error Code
         */
        /*
         *      0x04->0x07 Reserved
         */

        public static final int WIN_SRST = 0x08; /* ATAPI soft reset command */

        public static final int WIN_DEVICE_RESET = 0x08;
        /*
         *      0x09->0x0F Reserved
         */
        public static final int WIN_RECAL = 0x10;
        public static final int WIN_RESTORE = WIN_RECAL;
        /*
         *      0x10->0x1F Reserved
         */
        public static final int WIN_READ = 0x20; /* 28-Bit */

        public static final int WIN_READ_ONCE = 0x21; /* 28-Bit without retries */

        public static final int WIN_READ_LONG = 0x22; /* 28-Bit */

        public static final int WIN_READ_LONG_ONCE = 0x23; /* 28-Bit without retries */

        public static final int WIN_READ_EXT = 0x24; /* 48-Bit */

        public static final int WIN_READDMA_EXT = 0x25; /* 48-Bit */

        public static final int WIN_READDMA_QUEUED_EXT = 0x26; /* 48-Bit */

        public static final int WIN_READ_NATIVE_MAX_EXT = 0x27; /* 48-Bit */
        /*
         *      0x28
         */

        public static final int WIN_MULTREAD_EXT = 0x29; /* 48-Bit */
        /*
         *      0x2A->0x2F Reserved
         */

        public static final int WIN_WRITE = 0x30; /* 28-Bit */

        public static final int WIN_WRITE_ONCE = 0x31; /* 28-Bit without retries */

        public static final int WIN_WRITE_LONG = 0x32; /* 28-Bit */

        public static final int WIN_WRITE_LONG_ONCE = 0x33; /* 28-Bit without retries */

        public static final int WIN_WRITE_EXT = 0x34; /* 48-Bit */

        public static final int WIN_WRITEDMA_EXT = 0x35; /* 48-Bit */

        public static final int WIN_WRITEDMA_QUEUED_EXT = 0x36; /* 48-Bit */

        public static final int WIN_SET_MAX_EXT = 0x37; /* 48-Bit */

        public static final int CFA_WRITE_SECT_WO_ERASE = 0x38; /* CFA Write Sectors without erase
         */

        public static final int WIN_MULTWRITE_EXT = 0x39; /* 48-Bit */
        /*
         *      0x3A->0x3B Reserved
         */

        public static final int WIN_WRITE_VERIFY = 0x3C; /* 28-Bit */
        /*
         *      0x3D->0x3F Reserved
         */

        public static final int WIN_VERIFY = 0x40; /* 28-Bit - Read Verify Sectors */

        public static final int WIN_VERIFY_ONCE = 0x41; /* 28-Bit - without retries */

        public static final int WIN_VERIFY_EXT = 0x42; /* 48-Bit */
        /*
         *      0x43->0x4F Reserved
         */

        public static final int WIN_FORMAT = 0x50;
        /*
         *      0x51->0x5F Reserved
         */
        public static final int WIN_INIT = 0x60;
        /*
         *      0x61->0x5F Reserved
         */
        public static final int WIN_SEEK = 0x70; /* 0x70-0x7F Reserved */

        public static final int CFA_TRANSLATE_SECTOR = 0x87; /* CFA Translate Sector */

        public static final int WIN_DIAGNOSE = 0x90;
        public static final int WIN_SPECIFY = 0x91; /* set drive geometry translation */

        public static final int WIN_DOWNLOAD_MICROCODE = 0x92;
        public static final int WIN_STANDBYNOW2 = 0x94;
        public static final int WIN_STANDBY2 = 0x96;
        public static final int WIN_SETIDLE2 = 0x97;
        public static final int WIN_CHECKPOWERMODE2 = 0x98;
        public static final int WIN_SLEEPNOW2 = 0x99;
        /*
         *      0x9A VENDOR
         */
        public static final int WIN_PACKETCMD = 0xA0; /* Send a packet command. */

        public static final int WIN_PIDENTIFY = 0xA1; /* identify ATAPI device   */

        public static final int WIN_QUEUED_SERVICE = 0xA2;
        public static final int WIN_SMART = 0xB0; /* self-monitoring and reporting */

        public static final int CFA_ERASE_SECTORS = 0xC0;
        public static final int WIN_MULTREAD = 0xC4; /* read sectors using multiple mode
         */

        public static final int WIN_MULTWRITE = 0xC5; /* write sectors using multiple mod
        e */

        public static final int WIN_SETMULT = 0xC6; /* enable/disable multiple mode */

        public static final int WIN_READDMA_QUEUED = 0xC7; /* read sectors using Queued DMA tr
        ansfers */

        public static final int WIN_READDMA = 0xC8; /* read sectors using DMA transfers
         */

        public static final int WIN_READDMA_ONCE = 0xC9; /* 28-Bit - without retries */

        public static final int WIN_WRITEDMA = 0xCA; /* write sectors using DMA transfer
        s */

        public static final int WIN_WRITEDMA_ONCE = 0xCB; /* 28-Bit - without retries */

        public static final int WIN_WRITEDMA_QUEUED = 0xCC; /* write sectors using Queued DMA t
        ransfers */

        public static final int CFA_WRITE_MULTI_WO_ERASE = 0xCD; /* CFA Write multiple without erase
         */

        public static final int WIN_GETMEDIASTATUS = 0xDA;
        public static final int WIN_ACKMEDIACHANGE = 0xDB; /* ATA-1, ATA-2 vendor */

        public static final int WIN_POSTBOOT = 0xDC;
        public static final int WIN_PREBOOT = 0xDD;
        public static final int WIN_DOORLOCK = 0xDE; /* lock door on removable drives */

        public static final int WIN_DOORUNLOCK = 0xDF; /* unlock door on removable drives
         */

        public static final int WIN_STANDBYNOW1 = 0xE0;
        public static final int WIN_IDLEIMMEDIATE = 0xE1; /* force drive to become "ready" */

        public static final int WIN_STANDBY = 0xE2; /* Set device in Standby Mode */

        public static final int WIN_SETIDLE1 = 0xE3;
        public static final int WIN_READ_BUFFER = 0xE4; /* force read only 1 sector */

        public static final int WIN_CHECKPOWERMODE1 = 0xE5;
        public static final int WIN_SLEEPNOW1 = 0xE6;
        public static final int WIN_FLUSH_CACHE = 0xE7;
        public static final int WIN_WRITE_BUFFER = 0xE8; /* force write only 1 sector */

        public static final int WIN_WRITE_SAME = 0xE9; /* read ata-2 to use */
        /* SET_FEATURES 0x22 or 0xDD */

        public static final int WIN_FLUSH_CACHE_EXT = 0xEA; /* 48-Bit */

        public static final int WIN_IDENTIFY = 0xEC; /* ask drive to identify itself
         */

        public static final int WIN_MEDIAEJECT = 0xED;
        public static final int WIN_IDENTIFY_DMA = 0xEE; /* same as WIN_IDENTIFY, but DMA */

        public static final int WIN_SETFEATURES = 0xEF; /* set special drive features */

        public static final int EXABYTE_ENABLE_NEST = 0xF0;
        public static final int WIN_SECURITY_SET_PASS = 0xF1;
        public static final int WIN_SECURITY_UNLOCK = 0xF2;
        public static final int WIN_SECURITY_ERASE_PREPARE = 0xF3;
        public static final int WIN_SECURITY_ERASE_UNIT = 0xF4;
        public static final int WIN_SECURITY_FREEZE_LOCK = 0xF5;
        public static final int WIN_SECURITY_DISABLE = 0xF6;
        public static final int WIN_READ_NATIVE_MAX = 0xF8; /* return the native maximum addres
        s */

        public static final int WIN_SET_MAX = 0xF9;
        public static final int DISABLE_SEAGATE = 0xFB;

        /* set to 1 set disable mult support */
        public static final int MAX_MULT_SECTORS = 16;

        /* ATAPI defines */
        public static final int ATAPI_PACKET_SIZE = 12;

        /* The generic packet command opcodes for CD/DVD Logical Units,
         * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
        public static final int GPCMD_BLANK = 0xa1;
        public static final int GPCMD_CLOSE_TRACK = 0x5b;
        public static final int GPCMD_FLUSH_CACHE = 0x35;
        public static final int GPCMD_FORMAT_UNIT = 0x04;
        public static final int GPCMD_GET_CONFIGURATION = 0x46;
        public static final int GPCMD_GET_EVENT_STATUS_NOTIFICATION = 0x4a;
        public static final int GPCMD_GET_PERFORMANCE = 0xac;
        public static final int GPCMD_INQUIRY = 0x12;
        public static final int GPCMD_LOAD_UNLOAD = 0xa6;
        public static final int GPCMD_MECHANISM_STATUS = 0xbd;
        public static final int GPCMD_MODE_SELECT_10 = 0x55;
        public static final int GPCMD_MODE_SENSE_10 = 0x5a;
        public static final int GPCMD_PAUSE_RESUME = 0x4b;
        public static final int GPCMD_PLAY_AUDIO_10 = 0x45;
        public static final int GPCMD_PLAY_AUDIO_MSF = 0x47;
        public static final int GPCMD_PLAY_AUDIO_TI = 0x48;
        public static final int GPCMD_PLAY_CD = 0xbc;
        public static final int GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e;
        public static final int GPCMD_READ_10 = 0x28;
        public static final int GPCMD_READ_12 = 0xa8;
        public static final int GPCMD_READ_CDVD_CAPACITY = 0x25;
        public static final int GPCMD_READ_CD = 0xbe;
        public static final int GPCMD_READ_CD_MSF = 0xb9;
        public static final int GPCMD_READ_DISC_INFO = 0x51;
        public static final int GPCMD_READ_DVD_STRUCTURE = 0xad;
        public static final int GPCMD_READ_FORMAT_CAPACITIES = 0x23;
        public static final int GPCMD_READ_HEADER = 0x44;
        public static final int GPCMD_READ_TRACK_RZONE_INFO = 0x52;
        public static final int GPCMD_READ_SUBCHANNEL = 0x42;
        public static final int GPCMD_READ_TOC_PMA_ATIP = 0x43;
        ;
        public static final int GPCMD_REPAIR_RZONE_TRACK = 0x58;
        public static final int GPCMD_REPORT_KEY = 0xa4;
        public static final int GPCMD_REQUEST_SENSE = 0x03;
        public static final int GPCMD_RESERVE_RZONE_TRACK = 0x53;
        public static final int GPCMD_SCAN = 0xba;
        public static final int GPCMD_SEEK = 0x2b;
        public static final int GPCMD_SEND_DVD_STRUCTURE = 0xad;
        public static final int GPCMD_SEND_EVENT = 0xa2;
        public static final int GPCMD_SEND_KEY = 0xa3;
        public static final int GPCMD_SEND_OPC = 0x54;
        public static final int GPCMD_SET_READ_AHEAD = 0xa7;
        public static final int GPCMD_SET_STREAMING = 0xb6;
        public static final int GPCMD_START_STOP_UNIT = 0x1b;
        public static final int GPCMD_STOP_PLAY_SCAN = 0x4e;
        public static final int GPCMD_TEST_UNIT_READY = 0x00;
        public static final int GPCMD_VERIFY_10 = 0x2f;
        public static final int GPCMD_WRITE_10 = 0x2a;
        public static final int GPCMD_WRITE_AND_VERIFY_10 = 0x2e;
        /* This is listed as optional in ATAPI 2.6, but is (curiously)
         * missing from Mt. Fuji, Table 57.  It _is_ mentioned in Mt. Fuji
         * Table 377 as an MMC command for SCSi devices though...  Most ATAPI
         * drives support it. */
        public static final int GPCMD_SET_SPEED = 0xbb;
        /* This seems to be a SCSI specific CD-ROM opcode
         * to play data at track/index */
        public static final int GPCMD_PLAYAUDIO_TI = 0x48;
        /*
         * From MS Media Status Notification Support Specification. For
         * older drives only.
         */
        public static final int GPCMD_GET_MEDIA_STATUS = 0xda;

        /* Mode page codes for mode sense/set */
        public static final int GPMODE_R_W_ERROR_PAGE = 0x01;
        public static final int GPMODE_WRITE_PARMS_PAGE = 0x05;
        public static final int GPMODE_AUDIO_CTL_PAGE = 0x0e;
        public static final int GPMODE_POWER_PAGE = 0x1a;
        public static final int GPMODE_FAULT_FAIL_PAGE = 0x1c;
        public static final int GPMODE_TO_PROTECT_PAGE = 0x1d;
        public static final int GPMODE_CAPABILITIES_PAGE = 0x2a;
        public static final int GPMODE_ALL_PAGES = 0x3f;
        /* Not in Mt. Fuji, but in ATAPI 2.6 -- depricated now in favor
         * of MODE_SENSE_POWER_PAGE */
        public static final int GPMODE_CDROM_PAGE = 0x0d;
        public static final int ATAPI_INT_REASON_CD = 0x01; /* 0 = data transfer */

        public static final int ATAPI_INT_REASON_IO = 0x02; /* 1 = transfer to the host */

        public static final int ATAPI_INT_REASON_REL = 0x04;
        public static final int ATAPI_INT_REASON_TAG = 0xf8;

        /* same constants as bochs */
        public static final int ASC_ILLEGAL_OPCODE = 0x20;
        public static final int ASC_LOGICAL_BLOCK_OOR = 0x21;
        public static final int ASC_INV_FIELD_IN_CMD_PACKET = 0x24;
        public static final int ASC_MEDIUM_NOT_PRESENT = 0x3a;
        public static final int ASC_SAVING_PARAMETERS_NOT_SUPPORTED = 0x39;
        public static final int SENSE_NONE = 0;
        public static final int SENSE_NOT_READY = 2;
        public static final int SENSE_ILLEGAL_REQUEST = 5;
        public static final int SENSE_UNIT_ATTENTION = 6;
        public static final int ETF_TRANSFER_STOP = 1;
        public static final int ETF_SECTOR_WRITE = 2;
        public static final int ETF_SECTOR_READ = 3;
        public static final int ETF_ATAPI_COMMAND = 4;
        public static final int ETF_ATAPI_COMMAND_REPLY_END = 5;
        public static final int ETF_DUMMY_TRANSFER_STOP = 6;
        public static final int IDF_NONE = 0;
        public static final int IDF_WRITE_DMA_CB = 1;
        public static final int IDF_READ_DMA_CB = 2;
        public static final int IDF_ATAPI_READ_DMA_CB = 3;
        public static final String HD_VERSION = "0.01";
        private int cylinders,  heads,  sectors;
        public byte status,  command,  error,  feature,  select;
        public byte hcyl,  lcyl;
        public byte sector;
        public int nSector;
        public int endTransferFunction;
        public boolean isCDROM;
        public boolean atapiDMA;
        public int requiredNumberOfSectors;
        public int multSectors;
        public int driveSerial;        //lba48 support
        public byte hobFeature;
        public byte hobNSector;
        public byte hobSector;
        public byte hobLCyl;
        public byte hobHCyl;
        public boolean lba48;
        public byte[] ioBuffer;
        private int ioBufferSize;
        private int ioBufferIndex;
        public byte[] dataBuffer;
        public int dataBufferOffset;
        public int dataBufferEnd;
        public boolean identifySet;
        public byte[] identifyData;
        public byte senseKey;
        public byte asc;
        public int elementaryTransferSize;
        public int packetTransferSize;
        public int lba;
        public int cdSectorSize;
        //public long numberOfSectors; //forwarded through to blockdevice, prevents need for cdrom callback
        public BlockDevice drive;
        public BMDMAIORegion bmdma;

        public IDEState(BlockDevice drive) {
            this.drive = drive;
            if (drive != null) {
                //this.numberOfSectors = drive.getTotalSectors();
                this.cylinders = drive.getCylinders();
                this.heads = drive.getHeads();
                this.sectors = drive.getSectors();
                if (drive.getType() == BlockDevice.Type.CDROM) {
                    this.isCDROM = true;
                }
            }
            ioBuffer = new byte[MAX_MULT_SECTORS * 512 + 4];
            identifyData = new byte[512];
            identifySet = false;
            lba48 = false;
            this.driveSerial = nextDriveSerial++;
            reset();
        }

        public void setDrive(BlockDevice drive) {
            this.drive = drive;
        }

        public void saveState(DataOutput output) throws IOException {
            output.writeInt(cylinders);
            output.writeInt(heads);
            output.writeInt(sectors);
            output.writeByte(status);
            output.writeByte(command);
            output.writeByte(error);
            output.writeByte(feature);
            output.writeByte(select);
            output.writeByte(hcyl);
            output.writeByte(lcyl);
            output.writeByte(sector);
            output.writeInt(nSector);
            output.writeInt(endTransferFunction);
            output.writeBoolean(isCDROM);
            output.writeBoolean(atapiDMA);
            output.writeInt(requiredNumberOfSectors);
            output.writeInt(multSectors);
            output.writeInt(driveSerial);
            output.writeByte(hobFeature);
            output.writeByte(hobNSector);
            output.writeByte(hobSector);
            output.writeByte(hobLCyl);
            output.writeByte(hobHCyl);
            output.writeBoolean(lba48);

            output.writeInt(ioBuffer.length);
            output.write(ioBuffer);
            output.writeInt(ioBufferSize);
            output.writeInt(ioBufferIndex);

            output.writeInt(dataBuffer.length);
            output.write(dataBuffer);
            output.writeInt(dataBufferOffset);
            output.writeInt(dataBufferEnd);
            output.writeBoolean(identifySet);

            output.writeInt(identifyData.length);
            output.write(identifyData);
            output.writeByte(senseKey);
            output.writeByte(asc);
            output.writeInt(elementaryTransferSize);
            output.writeInt(packetTransferSize);
            output.writeInt(lba);
            output.writeInt(cdSectorSize);
        }

        public void loadState(DataInput input) throws IOException {
            cylinders = input.readInt();
            heads = input.readInt();
            sectors = input.readInt();
            status = input.readByte();
            command = input.readByte();
            error = input.readByte();
            feature = input.readByte();
            select = input.readByte();
            hcyl = input.readByte();
            lcyl = input.readByte();
            sector = input.readByte();
            nSector = input.readInt();
            endTransferFunction = input.readInt();
            isCDROM = input.readBoolean();
            atapiDMA = input.readBoolean();
            requiredNumberOfSectors = input.readInt();
            multSectors = input.readInt();
            driveSerial = input.readInt();
            hobFeature = input.readByte();
            hobNSector = input.readByte();
            hobSector = input.readByte();
            hobLCyl = input.readByte();
            hobHCyl = input.readByte();
            lba48 = input.readBoolean();
            int len = input.readInt();
            ioBuffer = new byte[len];
            input.readFully(ioBuffer, 0, len);
            ioBufferSize = input.readInt();
            ioBufferIndex = input.readInt();
            len = input.readInt();
            dataBuffer = new byte[len];
            input.readFully(dataBuffer, 0, len);
            dataBufferOffset = input.readInt();
            dataBufferEnd = input.readInt();
            identifySet = input.readBoolean();
            len = input.readInt();
            identifyData = new byte[len];
            input.readFully(identifyData, 0, len);
            senseKey = input.readByte();
            asc = input.readByte();
            elementaryTransferSize = input.readInt();
            packetTransferSize = input.readInt();
            lba = input.readInt();
            cdSectorSize = input.readInt();
            bmdma.setIDEDevice(this);
        }

        public int dmaCallback(int ideDMAFunction, int address, int size) {
            switch (ideDMAFunction) {
                case IDF_ATAPI_READ_DMA_CB:
                    return atapiCommandReadDMACallback(address, size);
//                case IDF_READ_DMA_CB:
//                    return readDMACallback(address, size);
                default:
                    LOGGING.log(Level.WARNING, "Need DMA callback function {0,number,integer}", Integer.valueOf(ideDMAFunction));
                    return 0;
            }
        }

        public void setSignature() {
            select &= 0xf0; /* clear head */
            /* put signature */
            nSector = 1;
            sector = 1;
            if (isCDROM) {
                lcyl = (byte) 0x14;
                hcyl = (byte) 0xeb;
            } else if (drive != null) {
                lcyl = 0;
                hcyl = 0;
            } else {
                lcyl = (byte) 0xff;
                hcyl = (byte) 0xff;
            }
        }

        public void setIRQ() {
            if ((command & IDE_CMD_DISABLE_IRQ) == 0) {
                if (bmdma != null) {
                    bmdma.setIRQ();
                }
                irqDevice.setIRQ(irq, 1);
            }
        }

        public void abortCommand() {
            status = READY_STAT | ERR_STAT;
            error = ABRT_ERR;
        }

        public void atapiIdentify() {
            if (identifySet) {
                System.arraycopy(identifyData, 0, ioBuffer, 0, identifyData.length);
                return;
            }

            for (int i = 0; i < 512; i++) {
                ioBuffer[i] = (byte) 0;
            }
            putLE16InByte(ioBuffer, 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0));
            stringToBytes("JPC" + driveSerial, ioBuffer, 20, 20);
            putLE16InByte(ioBuffer, 40, 3); /* XXX: retired, remove ? */
            putLE16InByte(ioBuffer, 42, 512); /* cache size in sectors */
            putLE16InByte(ioBuffer, 44, 4); /* ecc bytes */
            stringToBytes(HD_VERSION, ioBuffer, 46, 8);
            stringToBytes(CDLABEL, ioBuffer, 54, 40);
            putLE16InByte(ioBuffer, 96, 1); /* dword I/O */
            putLE16InByte(ioBuffer, 98, (1 << 9)); /* DMA and LBA supported */
            putLE16InByte(ioBuffer, 106, 3); /* words 54-58, 64-70 are valid */
            putLE16InByte(ioBuffer, 126, 0x103);
            putLE16InByte(ioBuffer, 128, 1);
            putLE16InByte(ioBuffer, 130, 0xb4);
            putLE16InByte(ioBuffer, 132, 0xb4);
            putLE16InByte(ioBuffer, 134, 0x12c);
            putLE16InByte(ioBuffer, 136, 0xb4);
            putLE16InByte(ioBuffer, 142, 30);
            putLE16InByte(ioBuffer, 144, 30);
            putLE16InByte(ioBuffer, 160, 0x1e);

            System.arraycopy(ioBuffer, 0, identifyData, 0, identifyData.length);
            identifySet = true;
        }

        public void setSector(long sectorNumber) {
            if ((select & 0x40) != 0) {
                if (!lba48) {
                    select = (byte) ((select & 0xf0) | (sectorNumber >>> 24));
                    hcyl = (byte) (sectorNumber >>> 16);
                    lcyl = (byte) (sectorNumber >>> 8);
                    sector = (byte) sectorNumber;
                } else {
                    sector = (byte) sectorNumber;
                    lcyl = (byte) (sectorNumber >>> 8);
                    hcyl = (byte) (sectorNumber >>> 16);
                    hobSector = (byte) (sectorNumber >>> 24);
                    hobLCyl = (byte) (sectorNumber >>> 32);
                    hobHCyl = (byte) (sectorNumber >>> 40);
                }
            } else {
                int cyl = (int) (sectorNumber / (heads * sectors));
                int r = (int) (sectorNumber % (heads * sectors));
                hcyl = (byte) (cyl >>> 8);
                lcyl = (byte) (cyl);
                select = (byte) ((select & 0xf0) | ((r / sectors) & 0x0f));
                sector = (byte) ((r % sectors) + 1);
            }
        }

        public void sectorWriteDMA() {
            status = READY_STAT | SEEK_STAT | DRQ_STAT;
            int n = nSector;
            if (n > MAX_MULT_SECTORS) {
                n = MAX_MULT_SECTORS;
            }
            ioBufferIndex = 0;
            ioBufferSize = n * 512;
            dmaStart(IDF_WRITE_DMA_CB);
        }

        public void sectorReadDMA() {
            status = READY_STAT | SEEK_STAT | DRQ_STAT;
            ioBufferIndex = 0;
            ioBufferSize = 0;
            dmaStart(IDF_READ_DMA_CB);
        }

        public void sectorWrite() {
            status = READY_STAT | SEEK_STAT;
            long sectorNumber = getSector();
            int n = nSector;
            if (n > requiredNumberOfSectors) {
                n = requiredNumberOfSectors;
            }
            drive.write(sectorNumber, ioBuffer, n);
            nSector -= n;
            if (nSector == 0) {
                transferStop();
            } else {
                int n1 = nSector;
                if (n1 > requiredNumberOfSectors) {
                    n1 = requiredNumberOfSectors;
                }
                transferStart(ioBuffer, 0, 512 * n1, ETF_SECTOR_WRITE);
            }
            setSector(sectorNumber + n);
            setIRQ();
        }

        public void sectorRead() {
            status = READY_STAT | SEEK_STAT;
            error = 0; /* not needed by IDE spec, but needed by Windows */
            long sectorNumber = getSector();
            int n = nSector;
            if (n == 0) // no more sectors to read from disk
            {
                transferStop();
            } else {
                n = Math.min(n, requiredNumberOfSectors);
                drive.read(sectorNumber, ioBuffer, n);
                transferStart(ioBuffer, 0, 512 * n, ETF_SECTOR_READ);
                setIRQ();
                setSector(sectorNumber + n);
                nSector -= n;
            }
        }

        public void identify() {
            if (identifySet) {
                System.arraycopy(identifyData, 0, ioBuffer, 0, identifyData.length);
                return;
            }

            for (int i = 0; i < 512; i++) {
                ioBuffer[i] = (byte) 0;
            }
            putLE16InByte(ioBuffer, 0, 0x0040);
            putLE16InByte(ioBuffer, 2, cylinders);
            putLE16InByte(ioBuffer, 6, heads);
            putLE16InByte(ioBuffer, 8, 512 * sectors); /* XXX: retired, remove ? */
            putLE16InByte(ioBuffer, 10, 512); /* XXX: retired, remove ? */
            putLE16InByte(ioBuffer, 12, sectors);
            stringToBytes("JPC" + driveSerial, ioBuffer, 20, 20);
            putLE16InByte(ioBuffer, 40, 3); /* XXX: retired, remove ? */
            putLE16InByte(ioBuffer, 42, 512); /* cache size in sectors */
            putLE16InByte(ioBuffer, 44, 4); /* ecc bytes */
            stringToBytes(HD_VERSION, ioBuffer, 46, 8);
            stringToBytes("Generic 1234", ioBuffer, 54, 40); //TODO revert to "JPC HARDDISK"
            putLE16InByte(ioBuffer, 94, 0x8000 | MAX_MULT_SECTORS);
            putLE16InByte(ioBuffer, 96, 1); /* dword I/O */
            putLE16InByte(ioBuffer, 98, (1 << 11) | (1 << 9) | (1 << 8)); /* DMA and LBA supported */
            putLE16InByte(ioBuffer, 102, 0x200); /* PIO transfer cycle */
            putLE16InByte(ioBuffer, 104, 0x200); /* DMA transfer cycle */
            putLE16InByte(ioBuffer, 106, 1 | (1 << 1) | (1 << 2)); /* words 54-58, 64-70, 88 are valid */
            putLE16InByte(ioBuffer, 108, cylinders);
            putLE16InByte(ioBuffer, 110, heads);
            putLE16InByte(ioBuffer, 112, sectors);
            int oldsize = cylinders * heads * sectors;
            putLE16InByte(ioBuffer, 114, oldsize);
            putLE16InByte(ioBuffer, 116, oldsize >>> 16);
            if (multSectors != 0) {
                putLE16InByte(ioBuffer, 118, 0x100 | multSectors);
            }
            putLE16InByte(ioBuffer, 120, (short) drive.getTotalSectors());
            putLE16InByte(ioBuffer, 122, (short) (drive.getTotalSectors() >>> 16));
            putLE16InByte(ioBuffer, 126, 0x07); // mdma0-2 supported
            putLE16InByte(ioBuffer, 130, 120);
            putLE16InByte(ioBuffer, 132, 120);
            putLE16InByte(ioBuffer, 134, 120);
            putLE16InByte(ioBuffer, 136, 120);
            putLE16InByte(ioBuffer, 160, 0xf0); // ata3 -> ata6 supported
            putLE16InByte(ioBuffer, 162, 0x16); // conforms to ata5
            putLE16InByte(ioBuffer, 164, 1 << 14);
            putLE16InByte(ioBuffer, 166, (1 << 14) | (1 << 13) | (1 << 12));
            //putLE16InByte(ioBuffer, 166, (1 << 14) | (1 << 13) | (1 << 12) | (1 << 10));
            putLE16InByte(ioBuffer, 168, 1 << 14);
            putLE16InByte(ioBuffer, 170, 1 << 14);
            putLE16InByte(ioBuffer, 172, (1 << 14) | (1 << 13) | (1 << 12));
            //putLE16InByte(ioBuffer, 172, (1 << 14) | (1 << 13) | (1 << 12) | (1 << 10));
            putLE16InByte(ioBuffer, 174, 1 << 14);
            putLE16InByte(ioBuffer, 176, 0x3f | (1 << 13));
            putLE16InByte(ioBuffer, 186, 1 | (1 << 14) | 0x2000);
            putLE16InByte(ioBuffer, 200, nSector);
            putLE16InByte(ioBuffer, 202, nSector >>> 16);
            putLE16InByte(ioBuffer, 204, /*nSector >>> 32*/ 0);
            putLE16InByte(ioBuffer, 206, /*nSector >>> 48*/ 0);

            System.arraycopy(ioBuffer, 0, identifyData, 0, identifyData.length);
            identifySet = true;
        }

        public void transferStart(byte[] buffer, int offset, int size, int endTransferFunction) {
            this.endTransferFunction = endTransferFunction;
            dataBuffer = buffer;
            dataBufferOffset = offset;
            dataBufferEnd = size + offset;
            status |= DRQ_STAT;
        }

        private void transferStop() {
            endTransferFunction = ETF_TRANSFER_STOP;
            dataBuffer = ioBuffer;
            dataBufferEnd = 0;
            dataBufferOffset = 0;
            status &= ~DRQ_STAT;
        }

        private void commandLBA48Transform(boolean lba48) {

            this.lba48 = lba48;

            if (!this.lba48) {
                if (nSector == 0) {
                    nSector = 256;
                }
            } else {
                if ((nSector == 0) && (hobNSector == 0)) {
                    nSector = 65536;
                } else {
                    int lo = 0xff & nSector;
                    int hi = 0xff & hobNSector;
                    nSector = (hi << 8) | lo;
                }
            }
        }

        private long getSector() {
            if ((select & 0x40) != 0) { /* lba */
                if (!lba48) {
                    return ((select & 0x0fl) << 24) |
                            ((0xffl & hcyl) << 16) |
                            ((0xffl & lcyl) << 8) |
                            (0xffl & sector);
                } else {
                    return ((0xffl & hobHCyl) << 40) |
                            ((0xffl & hobLCyl) << 32) |
                            ((0xffl & hobSector) << 24) |
                            ((0xffl & hcyl) << 16) |
                            ((0xffl & lcyl) << 16) |
                            (0xffl & sector);
                }
            } else {
                return ((((0xffl & hcyl) << 8) | (0xffl & lcyl)) * heads * sectors) + ((select & 0x0fl) * sectors) + ((0xffl & sector) - 1);
            }
        }

        private void dmaStart(int ideDMAFunction) {
            if (bmdma == null) {
                return;
            }
            bmdma.setIDEDevice(this);
            bmdma.setDMAFunction(ideDMAFunction);

            if ((bmdma.getStatus() & BMDMAIORegion.BM_STATUS_DMAING) != 0) {
                bmdma.ideDMALoop();
            }
        }

        public void endTransfer(int mode) {
            switch (mode) {
                case ETF_TRANSFER_STOP:
                    transferStop();
                    break;
                case ETF_SECTOR_WRITE:
                    sectorWrite();
                    break;
                case ETF_SECTOR_READ:
                    sectorRead();
                    break;
                case ETF_ATAPI_COMMAND:
                    atapiCommand();
                    break;
                case ETF_ATAPI_COMMAND_REPLY_END:
                    atapiCommandReplyEnd();
                    break;
                case ETF_DUMMY_TRANSFER_STOP:
                    dummyTransferStop();
                    break;
            }
        }

        public void reset() {
            multSectors = MAX_MULT_SECTORS;
            select = (byte) 0xa0;
            status = READY_STAT;
            setSignature();
            endTransferFunction = ETF_DUMMY_TRANSFER_STOP;
            endTransfer(ETF_DUMMY_TRANSFER_STOP);
        }

        private void atapiCommand() {
            switch (0xff & ioBuffer[0]) {
                case GPCMD_TEST_UNIT_READY:
                    if (drive.isInserted()) {
                        atapiCommandOk();
                    } else {
                        atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                    }
                    break;
                case GPCMD_MODE_SENSE_10:
                     {
                        int maxLength = bigEndianBytesToShort(ioBuffer, 7);
                        int action = (0xff & ioBuffer[2]) >>> 6;
                        int code = ioBuffer[2] & 0x3f;
                        switch (action) {
                            case 0: /* current values */
                                switch (code) {
                                    case 0x01: /* error recovery */
                                        shortToBigEndianBytes(ioBuffer, 0, (short) (16 + 6));
                                        ioBuffer[2] = 0x70;
                                        ioBuffer[3] = 0;
                                        ioBuffer[4] = 0;
                                        ioBuffer[5] = 0;
                                        ioBuffer[6] = 0;
                                        ioBuffer[7] = 0;

                                        ioBuffer[8] = 0x01;
                                        ioBuffer[9] = 0x06;
                                        ioBuffer[10] = 0x00;
                                        ioBuffer[11] = 0x05;
                                        ioBuffer[12] = 0x00;
                                        ioBuffer[13] = 0x00;
                                        ioBuffer[14] = 0x00;
                                        ioBuffer[15] = 0x00;
                                        atapiCommandReply(16, maxLength);
                                        break;
                                    case 0x2a:
                                        shortToBigEndianBytes(ioBuffer, 0, (short) (28 + 6));
                                        ioBuffer[2] = 0x70;
                                        ioBuffer[3] = 0;
                                        ioBuffer[4] = 0;
                                        ioBuffer[5] = 0;
                                        ioBuffer[6] = 0;
                                        ioBuffer[7] = 0;

                                        ioBuffer[8] = 0x2a;
                                        ioBuffer[9] = 0x12;
                                        ioBuffer[10] = 0x00;
                                        ioBuffer[11] = 0x00;

                                        ioBuffer[12] = 0x70;
                                        ioBuffer[13] = 3 << 5;
                                        ioBuffer[14] = (1 << 0) | (1 << 3) | (1 << 5);
                                        if (drive.isLocked()) {
                                            ioBuffer[6] |= 1 << 1;
                                        }
                                        ioBuffer[15] = 0x00;
                                        shortToBigEndianBytes(ioBuffer, 16, (short) 706);
                                        ioBuffer[18] = 0;
                                        ioBuffer[19] = 2;
                                        shortToBigEndianBytes(ioBuffer, 20, (short) 512);
                                        shortToBigEndianBytes(ioBuffer, 22, (short) 706);
                                        ioBuffer[24] = 0;
                                        ioBuffer[25] = 0;
                                        ioBuffer[26] = 0;
                                        ioBuffer[27] = 0;
                                        atapiCommandReply(28, maxLength);
                                        break;
                                    default:
                                        atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                }
                                break;
                            case 1: /* changeable values */
                                atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                break;
                            case 2: /* default values */
                                atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                break;
                            default:
                            case 3: /* saved values */
                                atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
                                break;
                        }
                    }
                    break;
                case GPCMD_REQUEST_SENSE:
                     {
                        int maxLength = 0xff & ioBuffer[4];
                        for (int i = 0; i < 18; i++) {
                            ioBuffer[i] = 0;
                        }
                        ioBuffer[0] = (byte) (0x70 | (1 << 7));
                        ioBuffer[2] = senseKey;
                        ioBuffer[7] = 10;
                        ioBuffer[12] = asc;
                        atapiCommandReply(18, maxLength);
                    }
                    break;
                case GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
                    if (drive.isInserted()) {
                        drive.setLock((ioBuffer[4] & 1) != 0);
                        atapiCommandOk();
                    } else {
                        atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                    }
                    break;
                case GPCMD_READ_10:
                case GPCMD_READ_12:
                     {
                        if (!drive.isInserted()) {
                            atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                            break;
                        }

                        int numSectors;
                        if (ioBuffer[0] == GPCMD_READ_10) {
                            numSectors = bigEndianBytesToShort(ioBuffer, 7);
                        } else {
                            numSectors = bigEndianBytesToInt(ioBuffer, 6);
                        }
                        int lba = bigEndianBytesToInt(ioBuffer, 2);
                        if (numSectors == 0) {
                            atapiCommandOk();
                            break;
                        }
                        if ((((0xffffffffl & lba) + (0xffffffffl & numSectors)) << 2) > drive.getTotalSectors()) {
                            atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
                            break;
                        }
                        atapiCommandRead(lba, numSectors, 2048);
                    }
                    break;
                case GPCMD_READ_CD:
                     {
                        if (!drive.isInserted()) {
                            atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                            break;
                        }
                        int numSectors = ((0xff & ioBuffer[6]) << 16) | ((0xff & ioBuffer[7]) << 8) | (0xff & ioBuffer[8]);
                        int lba = bigEndianBytesToInt(ioBuffer, 2);
                        if (numSectors == 0) {
                            atapiCommandOk();
                            break;
                        }
                        if ((((0xffffffffl & lba) + (0xffffffffl & numSectors)) << 2) > drive.getTotalSectors()) {
                            atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
                            break;
                        }
                        int transferRequest = 0xff & ioBuffer[9];
                        switch (transferRequest & 0xf8) {
                            case 0x00:
                                /* nothing */
                                atapiCommandOk();
                                break;
                            case 0x10:
                                /* normal read */
                                atapiCommandRead(lba, numSectors, 2048);
                                break;
                            case 0xf8:
                                /* read all data */
                                atapiCommandRead(lba, numSectors, 2352);
                                break;
                            default:
                                atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                break;
                        }
                    }
                    break;
                case GPCMD_SEEK:
                     {
                        if (!drive.isInserted()) {
                            atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                            break;
                        }
                        int lba = bigEndianBytesToInt(ioBuffer, 2);
                        if (((0xffffffffl & lba) << 2) > drive.getTotalSectors()) {
                            atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
                            break;
                        }
                        atapiCommandOk();
                    }
                    break;
                case GPCMD_START_STOP_UNIT:
                     {
                        boolean start = ((ioBuffer[4] & 1) != 0);
                        boolean eject = ((ioBuffer[4] & 2) != 0);

                        if (eject && !start) {
                            /* eject the disk */
                            drive.close();
                        }
                        atapiCommandOk();
                    }
                    break;
                case GPCMD_MECHANISM_STATUS:
                     {
                        int maxLength = bigEndianBytesToShort(ioBuffer, 8);
                        shortToBigEndianBytes(ioBuffer, 0, (short) 0);
                        /* no current LBA */
                        ioBuffer[2] = 0;
                        ioBuffer[3] = 0;
                        ioBuffer[4] = 0;
                        ioBuffer[5] = 1;
                        shortToBigEndianBytes(ioBuffer, 6, (short) 0);
                        atapiCommandReply(8, maxLength);
                    }
                    break;
                case GPCMD_READ_TOC_PMA_ATIP:
                     {
                        if (!drive.isInserted()) {
                            atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                            break;
                        }
                        int maxLength = bigEndianBytesToShort(ioBuffer, 7);
                        int format = (0xff & ioBuffer[9]) >>> 6;
                        int msf = (ioBuffer[1] >>> 1) & 1;
                        int startTrack = 0xff & ioBuffer[6];
                        switch (format) {
                            case 0:
                                int length = cdromReadTOC((int) (drive.getTotalSectors() >>> 2), ioBuffer, msf, startTrack);
                                if (length < 0) {
                                    atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                    break;
                                }
                                atapiCommandReply(length, maxLength);
                                break;
                            case 1:
                                /* multi session : only a single session defined */
                                for (int i = 0; i < 12; i++) {
                                    ioBuffer[i] = 0;
                                }
                                ioBuffer[1] = 0x0a;
                                ioBuffer[2] = 0x01;
                                ioBuffer[3] = 0x01;
                                atapiCommandReply(12, maxLength);
                                break;
                            case 2:
                                length = cdromReadTOCRaw((int) (drive.getTotalSectors() >>> 2), ioBuffer, msf, startTrack);
                                if (length < 0) {
                                    atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                    break;
                                }
                                atapiCommandReply(length, maxLength);
                                break;
                            default:
                                atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
                                break;
                        }
                    }
                    break;
                case GPCMD_READ_CDVD_CAPACITY:
                    if (!drive.isInserted()) {
                        atapiCommandError(SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
                        break;
                    }
                    /* NOTE: it is really the number of sectors minus 1 */
                    intToBigEndianBytes(ioBuffer, 0, (int) ((drive.getTotalSectors() >>> 2) - 1));
                    intToBigEndianBytes(ioBuffer, 4, 2048);
                    atapiCommandReply(8, 8);
                    break;
                case GPCMD_INQUIRY:
                    int maxLength = 0xff & ioBuffer[4];
                    ioBuffer[0] = 0x05; /* CD-ROM */
                    ioBuffer[1] = (byte) 0x80; /* removable */
                    ioBuffer[2] = 0x00; /* ISO */
                    ioBuffer[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
                    ioBuffer[4] = 31; /* additionnal length */
                    ioBuffer[5] = 0; /* reserved */
                    ioBuffer[6] = 0; /* reserved */
                    ioBuffer[7] = 0; /* reserved */
                     {
                        byte[] temp = "JPC".getBytes();
                        int i = 8;
                        for (int j = 0; j < temp.length; i++, j++) {
                            ioBuffer[i] = temp[j];
                        }
                        for (; i < 16; i++) {
                            ioBuffer[i] = 0;
                        }
                    }
                     {
                        byte[] temp = CDLABEL.getBytes();
                        int i = 16;
                        for (int j = 0; j < temp.length; i++, j++) {
                            ioBuffer[i] = temp[j];
                        }
                        for (; i < 32; i++) {
                            ioBuffer[i] = 0;
                        }
                    }
                     {
                        byte[] temp = "1.0".getBytes();
                        int i = 32;
                        for (int j = 0; j < temp.length; i++, j++) {
                            ioBuffer[i] = temp[j];
                        }
                        for (; i < 36; i++) {
                            ioBuffer[i] = 0;
                        }
                    }
                    atapiCommandReply(36, maxLength);
                    break;
                default:
                    atapiCommandError(SENSE_ILLEGAL_REQUEST, ASC_ILLEGAL_OPCODE);
                    break;
            }
        }

        private void dummyTransferStop() {
            dataBuffer = ioBuffer;
            dataBufferEnd = 0;
            ioBuffer[0] = (byte) 0xff;
            ioBuffer[1] = (byte) 0xff;
            ioBuffer[2] = (byte) 0xff;
            ioBuffer[3] = (byte) 0xff;
        }

        private void atapiCommandOk() {
            error = 0;
            status = READY_STAT;
            nSector = (nSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
            setIRQ();
        }

        private void atapiCommandError(int senseKey, int asc) {
            error = (byte) (this.senseKey << 4);
            status = READY_STAT | ERR_STAT;
            nSector = (nSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
            this.senseKey = (byte) senseKey;
            this.asc = (byte) asc;
            setIRQ();
        }

        private void atapiCommandReply(int size, int maxSize) {
            size = Math.min(size, maxSize);
            lba = -1;
            packetTransferSize = size;
            elementaryTransferSize = 0;
            ioBufferIndex = 0;

            status = READY_STAT;
            atapiCommandReplyEnd();
        }

        private void atapiCommandRead(int lba, int numSectors, int sectorSize) {
            if (atapiDMA) {
                atapiCommandReadDMA(lba, numSectors, sectorSize);
            } else {
                atapiCommandReadPIO(lba, numSectors, sectorSize);
            }
        }

        private void atapiCommandReadDMA(int lba, int numSectors, int sectorSize) {
            this.lba = lba;
            packetTransferSize = numSectors * sectorSize;
            ioBufferIndex = sectorSize;
            cdSectorSize = sectorSize;

            status = READY_STAT | DRQ_STAT;
            dmaStart(IDF_ATAPI_READ_DMA_CB);
        }

        private void atapiCommandReadPIO(int lba, int numSectors, int sectorSize) {
            this.lba = lba;
            packetTransferSize = numSectors * sectorSize;
            elementaryTransferSize = 0;
            ioBufferIndex = sectorSize;
            cdSectorSize = sectorSize;

            status = READY_STAT;
            atapiCommandReplyEnd();
        }

        private void atapiCommandReplyEnd() {
            if (packetTransferSize <= 0) { //end of transfer
                transferStop();
                status = READY_STAT;
                nSector = (nSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
                setIRQ();
            } else {
                /* see if a new sector must be read */
                if (lba != -1 && ioBufferIndex >= cdSectorSize) {
                    cdReadSector(lba, ioBuffer, cdSectorSize);
                    lba++;
                    ioBufferIndex = 0;
                }
                if (elementaryTransferSize > 0) { // there is some data left to transmit in this elementary transfer
                    int size = Math.min(cdSectorSize - ioBufferIndex, elementaryTransferSize);
                    transferStart(ioBuffer, ioBufferIndex, size, ETF_ATAPI_COMMAND_REPLY_END);
                    packetTransferSize -= size;
                    elementaryTransferSize -= size;
                    ioBufferIndex += size;
                } else {
                    /* a new transfer is needed */
                    nSector = (nSector & ~7) | ATAPI_INT_REASON_IO;
                    int byteCountLimit = (0xff & lcyl) | (0xff00 & (hcyl << 8));
                    if (byteCountLimit == 0xffff) {
                        byteCountLimit--;
                    }
                    int size = packetTransferSize;
                    if (size > byteCountLimit) {
                        /* byte count limit must be even if this case */
                        if ((byteCountLimit & 1) != 0) {
                            byteCountLimit--;
                        }
                        size = byteCountLimit;
                    }
                    lcyl = (byte) size;
                    hcyl = (byte) (size >>> 8);
                    elementaryTransferSize = size;
                    /* we cannot transmit more than one sector at a time */
                    if (lba != -1) {
                        size = Math.min(cdSectorSize - ioBufferIndex, size);
                    }
                    transferStart(ioBuffer, ioBufferIndex, size, ETF_ATAPI_COMMAND_REPLY_END);
                    packetTransferSize -= size;
                    elementaryTransferSize -= size;
                    ioBufferIndex += size;
                    setIRQ();
                }
            }
        }

        private int readDMACallback(int address, int size) {
            int originalSize = size;
            packetTransferSize = size;
	    while (size > 0) {
		if (packetTransferSize <= 0)
		    break;
		int length = drive.SECTOR_SIZE - ioBufferIndex;
		if (length <= 0) {
		    ioBufferIndex = 0;
		    length = drive.SECTOR_SIZE;
		}
		if (length > size)
		    length = size;
                int start = nSector;
                sectorRead();
                int end = nSector;
		bmdma.writeMemory(address, ioBuffer, ioBufferIndex, 512*(start-end));
		packetTransferSize -= length;
		ioBufferIndex += length;
		size -= length;
		address += length;
	    }

	    if (packetTransferSize <= 0) {
		status = READY_STAT | SEEK_STAT;
		nSector = (nSector & ~0x7);
		setIRQ();
		return 0;
	    }

	    return originalSize - size;
        }

        private int atapiCommandReadDMACallback(int address, int size) {
            System.out.println("CD DMA callback read");
            int originalSize = size;
            while (size > 0) {
                if (packetTransferSize <= 0) {
                    break;
                }
                int length = cdSectorSize - ioBufferIndex;
                if (length <= 0) {
                    lba++;
                    ioBufferIndex = 0;
                    length = cdSectorSize;
                }
                if (length > size) {
                    length = size;
                }
                cdReadSector(lba, ioBuffer, length);
                bmdma.writeMemory(address, ioBuffer, ioBufferIndex, length);
                packetTransferSize -= length;
                ioBufferIndex += length;
                size -= length;
                address += length;
            }

            if (packetTransferSize <= 0) {
                status = READY_STAT;
                nSector = (nSector & ~0x7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
                setIRQ();
                return 0;
            }

            return originalSize - size;
        }

        private int cdromReadTOC(int nbSectors, byte[] buffer, int msf, int startTrack) {
            if ((startTrack > 1) && (startTrack != 0xaa)) {
                return -1;
            }
            int bufferOffset = 2;
            buffer[bufferOffset++] = 1; // first session 
            buffer[bufferOffset++] = 1; // last session

            if (startTrack <= 1) {
                buffer[bufferOffset++] = 0; // reserved
                buffer[bufferOffset++] = 0x14; // ADR, control
                buffer[bufferOffset++] = 1; // track number
                buffer[bufferOffset++] = 0; // reserved
                if (msf != 0) {
                    buffer[bufferOffset++] = 0; // reserved
                    lbaToMSF(buffer, bufferOffset, 0);
                    bufferOffset += 3;
                } else {
                    // sector 0
                    intToBigEndianBytes(buffer, bufferOffset, 0);
                    bufferOffset += 4;
                }
            }
            // lead out track
            buffer[bufferOffset++] = 0; // reserved
            buffer[bufferOffset++] = 0x16; // ADR, control
            buffer[bufferOffset++] = (byte) 0xaa; // track number
            buffer[bufferOffset++] = 0; // reserved
            if (msf != 0) {
                buffer[bufferOffset++] = 0; // reserved
                lbaToMSF(buffer, bufferOffset, nbSectors);
                bufferOffset += 3;
            } else {
                intToBigEndianBytes(buffer, bufferOffset, nbSectors);
                bufferOffset += 4;
            }

            shortToBigEndianBytes(buffer, bufferOffset, (short) (bufferOffset - 2));
            return bufferOffset;
        }

        private int cdromReadTOCRaw(int nbSectors, byte[] buffer, int msf, int sessionNumber) {
            int bufferOffset = 2;
            buffer[bufferOffset++] = 1; // first session 
            buffer[bufferOffset++] = 1; // last session

            buffer[bufferOffset++] = 1; /* session number */
            buffer[bufferOffset++] = 0x14; /* data track */
            buffer[bufferOffset++] = 0; /* track number */
            buffer[bufferOffset++] = (byte) 0xa0; /* lead-in */
            buffer[bufferOffset++] = 0; /* min */
            buffer[bufferOffset++] = 0; /* sec */
            buffer[bufferOffset++] = 0; /* frame */
            buffer[bufferOffset++] = 0;
            buffer[bufferOffset++] = 1; /* first track */
            buffer[bufferOffset++] = 0x00; /* disk type */
            buffer[bufferOffset++] = 0x00;

            buffer[bufferOffset++] = 1; /* session number */
            buffer[bufferOffset++] = 0x14; /* data track */
            buffer[bufferOffset++] = 0; /* track number */
            buffer[bufferOffset++] = (byte) 0xa1;
            buffer[bufferOffset++] = 0; /* min */
            buffer[bufferOffset++] = 0; /* sec */
            buffer[bufferOffset++] = 0; /* frame */
            buffer[bufferOffset++] = 0;
            buffer[bufferOffset++] = 1; /* last track */
            buffer[bufferOffset++] = 0x00;
            buffer[bufferOffset++] = 0x00;

            buffer[bufferOffset++] = 1; /* session number */
            buffer[bufferOffset++] = 0x14; /* data track */
            buffer[bufferOffset++] = 0; /* track number */
            buffer[bufferOffset++] = (byte) 0xa2; /* lead-out */
            buffer[bufferOffset++] = 0; /* min */
            buffer[bufferOffset++] = 0; /* sec */
            buffer[bufferOffset++] = 0; /* frame */
            if (msf != 0) {
                buffer[bufferOffset++] = 0; /* reserved */
                lbaToMSF(buffer, bufferOffset, nbSectors);
                bufferOffset += 3;
            } else {
                intToBigEndianBytes(buffer, bufferOffset, nbSectors);
                bufferOffset += 4;
            }

            buffer[bufferOffset++] = 1; /* session number */
            buffer[bufferOffset++] = 0x14; /* ADR, control */
            buffer[bufferOffset++] = 0;    /* track number */
            buffer[bufferOffset++] = 1;    /* point */
            buffer[bufferOffset++] = 0; /* min */
            buffer[bufferOffset++] = 0; /* sec */
            buffer[bufferOffset++] = 0; /* frame */
            if (msf != 0) {
                buffer[bufferOffset++] = 0;
                lbaToMSF(buffer, bufferOffset, 0);
                bufferOffset += 3;
            } else {
                buffer[bufferOffset++] = 0;
                buffer[bufferOffset++] = 0;
                buffer[bufferOffset++] = 0;
                buffer[bufferOffset++] = 0;
            }

            shortToBigEndianBytes(buffer, bufferOffset, (short) (bufferOffset - 2));
            return bufferOffset;
        }

        private void hdReadSector(long sector, byte[] buffer, int sectorSize) {
            drive.read(sector, buffer, 1);
            System.out.println("DMA reading sector: " + sector);
        }

        private void cdReadSector(int lba, byte[] buffer, int sectorSize) {
            switch (sectorSize) {
                case 2048:
                    drive.read((0xffffffffl & lba) << 2, buffer, 4);
                    break;
                case 2352:
                    drive.read((0xffffffffl & lba) << 2, buffer, 4);
                    System.arraycopy(buffer, 0, buffer, 16, 2048);

                    /* sync bytes */
                    buffer[0] = 0x00;
                    for (int i = 1; i < 11; i++) {
                        buffer[i] = (byte) 0xff;
                    }
                    buffer[11] = 0x00;
                    lbaToMSF(buffer, 12, lba); // MSF
                    buffer[12 + 3] = 0x01; // mode 1 data

                    for (int i = 2064; i < 2352; i++) {
                        buffer[i] = 0;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    public String toString() {
        if (ioBaseTwo == 0) {
            return "IDE Channel @ 0x" + Integer.toHexString(ioBase) + "-0x" + Integer.toHexString(ioBase + 7) + " on irq " + irq;
        } else {
            return "IDE Channel @ 0x" + Integer.toHexString(ioBase) + "-0x" + Integer.toHexString(ioBase + 7) + ", 0x" + Integer.toHexString(ioBaseTwo) + " on irq " + irq;
        }
    }
}
