Index: trunk/src/VBox/Devices/PC/BIOS/scsi.c
===================================================================
--- trunk/src/VBox/Devices/PC/BIOS/scsi.c	(revision 21321)
+++ trunk/src/VBox/Devices/PC/BIOS/scsi.c	(revision 21321)
@@ -0,0 +1,480 @@
+/* $Id$ */
+/** @file
+ * SCSI host adapter driver to boot from SCSI disks
+ */
+
+/*
+ * Copyright (C) 2004-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/* The I/O port of the BusLogic SCSI adapter. */
+#define BUSLOGIC_ISA_IO_PORT 0x330
+/* The I/O port of the LsiLogic SCSI adapter. */
+#define LSILOGIC_ISA_IO_PORT 0x340
+
+#define VBOXSCSI_REGISTER_STATUS   0
+#define VBOXSCSI_REGISTER_COMMAND  0
+#define VBOXSCSI_REGISTER_DATA_IN  1
+#define VBOXSCSI_REGISTER_IDENTIFY 2
+
+#define VBOXSCSI_MAX_DEVICES 16 /* Maximum number of devices a SCSI device can have. */
+
+/* Command opcodes. */
+#define SCSI_INQUIRY       0x12
+#define SCSI_READ_CAPACITY 0x25
+#define SCSI_READ_10       0x28
+#define SCSI_WRITE_10      0x2a
+
+/* Data transfer direction. */
+#define SCSI_TXDIR_FROM_DEVICE 0
+#define SCSI_TXDIR_TO_DEVICE   1
+
+#define VBOXSCSI_BUSY (1 << 0)
+
+//#define VBOX_SCSI_DEBUG 1 /* temporary */
+
+#ifdef VBOX_SCSI_DEBUG
+# define VBOXSCSI_DEBUG(a...) BX_INFO(a)
+#else
+# define VBOXSCSI_DEBUG(a...)
+#endif
+
+int scsi_cmd_data_in(io_base, device_id, cdb_segment, aCDB, cbCDB, segment, offset, cbBuffer)
+    Bit16u io_base, aCDB, offset, cbBuffer, segment, cdb_segment;
+    Bit8u  device_id, cbCDB;
+{
+    /* Check that the adapter is ready. */
+    Bit8u status;
+    Bit16u i;
+
+    do
+    {
+        status = inb(io_base+VBOXSCSI_REGISTER_STATUS);
+    } while (status & VBOXSCSI_BUSY);
+
+    /* Write target ID. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, device_id);
+    /* Write transfer direction. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, SCSI_TXDIR_FROM_DEVICE);
+    /* Write the CDB size. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, cbCDB);
+    /* Write buffer size. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, cbBuffer);
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, (cbBuffer >> 8));
+    /* Write the CDB. */
+    for (i = 0; i < cbCDB; i++)
+        outb(io_base+VBOXSCSI_REGISTER_COMMAND, read_byte(cdb_segment, aCDB + i));
+
+    /* Now wait for the command to complete. */
+    do
+    {
+        status = inb(io_base+VBOXSCSI_REGISTER_STATUS);
+    } while (status & VBOXSCSI_BUSY);
+
+#if 0
+    /* Get the read data. */
+    for (i = 0; i < cbBuffer; i++)
+    {
+        Bit8u data;
+
+        data = inb(io_base+VBOXSCSI_REGISTER_DATA_IN);
+        write_byte(segment, offset+i, data);
+
+        VBOXSCSI_DEBUG("buffer[%d]=%x\n", i, data);
+    }
+#else
+    io_base = io_base + VBOXSCSI_REGISTER_DATA_IN;
+
+ASM_START
+        push bp
+        mov  bp, sp
+        mov  di, _scsi_cmd_data_in.offset + 2[bp]
+        mov  ax, _scsi_cmd_data_in.segment + 2[bp]
+        mov  cx, _scsi_cmd_data_in.cbBuffer + 2[bp]
+
+        mov   es, ax      ;; segment in es
+        mov   dx, _scsi_cmd_data_in.io_base + 2[bp] ;; SCSI data read port
+
+        rep
+          insb ;; CX dwords transfered from port(DX) to ES:[DI]
+
+        pop  bp
+ASM_END
+#endif
+
+    return 0;
+}
+
+int scsi_cmd_data_out(io_base, device_id, cdb_segment, aCDB, cbCDB, segment, offset, cbBuffer)
+    Bit16u io_base, aCDB, offset, cbBuffer, segment, cdb_segment;
+    Bit8u  device_id, cbCDB;
+{
+    /* Check that the adapter is ready. */
+    Bit8u status;
+    Bit16u i;
+
+    do
+    {
+        status = inb(io_base+VBOXSCSI_REGISTER_STATUS);
+    } while (status & VBOXSCSI_BUSY);
+
+    /* Write target ID. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, device_id);
+    /* Write transfer direction. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, SCSI_TXDIR_TO_DEVICE);
+    /* Write the CDB size. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, cbCDB);
+    /* Write buffer size. */
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, cbBuffer);
+    outb(io_base+VBOXSCSI_REGISTER_COMMAND, (cbBuffer >> 8));
+    /* Write the CDB. */
+    for (i = 0; i < cbCDB; i++)
+        outb(io_base+VBOXSCSI_REGISTER_COMMAND, read_byte(cdb_segment, aCDB + i));
+
+    /* Write data to I/O port. */
+    for (i = 0; i < cbBuffer; i++)
+    {
+        Bit8u data;
+
+        data = read_byte(segment, offset+i);
+        outb(io_base+VBOXSCSI_REGISTER_DATA_IN, data);
+
+        VBOXSCSI_DEBUG("buffer[%d]=%x\n", i, data);
+    }
+
+    /* Now wait for the command to complete. */
+    do
+    {
+        status = inb(io_base+VBOXSCSI_REGISTER_STATUS);
+    } while (status & VBOXSCSI_BUSY);
+
+    return 0;
+}
+
+
+/**
+ * Read sectors from an attached scsi device.
+ *
+ * @returns status code.
+ * @param   device_id   Id of the SCSI device to read from.
+ * @param   count       The number of sectors to read.
+ * @param   lba         The start sector to read from.
+ * @param   segment     The segment the buffer is in.
+ * @param   offset      The offset of the buffer.
+ */
+int scsi_read_sectors(device_id, count, lba, segment, offset)
+    Bit8u device_id;
+    Bit16u count, segment, offset;
+    Bit32u lba;
+{
+    Bit8u rc;
+    Bit8u aCDB[10];
+    Bit16u io_base, ebda_seg;
+    Bit8u  target_id;
+
+    if (device_id > BX_MAX_SCSI_DEVICES)
+        BX_PANIC("scsi_read_sectors: device_id out of range %d\n", device_id);
+
+    // Reset count of transferred data
+    write_word(ebda_seg, &EbdaData->ata.trsfsectors,0);
+    write_dword(ebda_seg, &EbdaData->ata.trsfbytes,0L);
+
+    /* Prepare CDB */
+    write_byte(get_SS(), aCDB + 0, SCSI_READ_10);
+    write_byte(get_SS(), aCDB + 1, 0);
+    write_byte(get_SS(), aCDB + 2, (uint8_t)(lba >> 24));
+    write_byte(get_SS(), aCDB + 3, (uint8_t)(lba >> 16));
+    write_byte(get_SS(), aCDB + 4, (uint8_t)(lba >>  8));
+    write_byte(get_SS(), aCDB + 5, (uint8_t)(lba));
+    write_byte(get_SS(), aCDB + 6, 0);
+    write_byte(get_SS(), aCDB + 7, (uint8_t)(count >> 8));
+    write_byte(get_SS(), aCDB + 8, (uint8_t)(count));
+    write_byte(get_SS(), aCDB + 9, 0);
+
+    ebda_seg = read_word(0x0040, 0x000E);
+    io_base = read_word(ebda_seg, &EbdaData->scsi.devices[device_id].io_base);
+    target_id = read_byte(ebda_seg, &EbdaData->scsi.devices[device_id].target_id);
+
+    rc = scsi_cmd_data_in(io_base, target_id, get_SS(), aCDB, 10, segment, offset, (count * 512));
+
+    if (!rc)
+    {
+        write_word(ebda_seg, &EbdaData->ata.trsfsectors, count);
+        write_dword(ebda_seg, &EbdaData->ata.trsfbytes, (count * 512));
+    }
+
+    return rc;
+}
+
+/**
+ * Write sectors to an attached scsi device.
+ *
+ * @returns status code.
+ * @param   device_id   Id of the SCSI device to write to.
+ * @param   count       The number of sectors to write.
+ * @param   lba         The start sector to write to.
+ * @param   segment     The segment the buffer is in.
+ * @param   offset      The offset of the buffer.
+ */
+int scsi_write_sectors(device_id, count, lba, segment, offset)
+    Bit8u device_id;
+    Bit16u count, segment, offset;
+    Bit32u lba;
+{
+    Bit8u rc;
+    Bit8u aCDB[10];
+    Bit16u io_base, ebda_seg;
+    Bit8u  target_id;
+
+    if (device_id > BX_MAX_SCSI_DEVICES)
+        BX_PANIC("scsi_write_sectors: device_id out of range %d\n", device_id);
+
+    // Reset count of transferred data
+    write_word(ebda_seg, &EbdaData->ata.trsfsectors,0);
+    write_dword(ebda_seg, &EbdaData->ata.trsfbytes,0L);
+
+    /* Prepare CDB */
+    write_byte(get_SS(), aCDB + 0, SCSI_WRITE_10);
+    write_byte(get_SS(), aCDB + 1, 0);
+    write_byte(get_SS(), aCDB + 2, (uint8_t)(lba >> 24));
+    write_byte(get_SS(), aCDB + 3, (uint8_t)(lba >> 16));
+    write_byte(get_SS(), aCDB + 4, (uint8_t)(lba >>  8));
+    write_byte(get_SS(), aCDB + 5, (uint8_t)(lba));
+    write_byte(get_SS(), aCDB + 6, 0);
+    write_byte(get_SS(), aCDB + 7, (uint8_t)(count >> 8));
+    write_byte(get_SS(), aCDB + 8, (uint8_t)(count));
+    write_byte(get_SS(), aCDB + 9, 0);
+
+    ebda_seg = read_word(0x0040, 0x000E);
+    io_base = read_word(ebda_seg, &EbdaData->scsi.devices[device_id].io_base);
+    target_id = read_byte(ebda_seg, &EbdaData->scsi.devices[device_id].target_id);
+
+    rc = scsi_cmd_data_out(io_base, target_id, get_SS(), aCDB, 10, segment, offset, (count * 512));
+
+    if (!rc)
+    {
+        write_word(ebda_seg, &EbdaData->ata.trsfsectors, count);
+        write_dword(ebda_seg, &EbdaData->ata.trsfbytes, (count * 512));
+    }
+
+    return rc;
+}
+
+/**
+ * Enumerate attached devices.
+ *
+ * @returns nothing.
+ * @param   io_base    The I/O base port of the controller.
+ */
+void scsi_enumerate_attached_devices(io_base)
+    Bit16u io_base;
+{
+    Bit16u ebda_seg;
+    Bit8u i;
+    Bit8u buffer[0x0200];
+
+    ebda_seg = read_word(0x0040, 0x000E);
+
+    write_byte(ebda_seg, &EbdaData->scsi.hdcount, 0);
+
+    /* Go through target devices. */
+    for (i = 0; i < VBOXSCSI_MAX_DEVICES; i++)
+    {
+        Bit8u rc;
+        Bit8u z;
+        Bit8u aCDB[10];
+
+        write_byte(get_SS(), aCDB + 0, SCSI_INQUIRY);
+        write_byte(get_SS(), aCDB + 1, 0);
+        write_byte(get_SS(), aCDB + 2, 0);
+        write_byte(get_SS(), aCDB + 3, 0);
+        write_byte(get_SS(), aCDB + 4, 5); /* Allocation length. */
+        write_byte(get_SS(), aCDB + 5, 0);
+
+        rc = scsi_cmd_data_in(io_base, i, get_SS(), aCDB, 6, get_SS(), buffer, 5);
+        if (rc != 0)
+            BX_PANIC("scsi_enumerate_attached_devices: SCSI_INQUIRY failed\n");
+
+        /* Check if there is a disk attached. */
+        if (   ((buffer[0] & 0xe0) == 0)
+            && ((buffer[0] & 0x1f) == 0x00))
+        {
+            VBOXSCSI_DEBUG("scsi_enumerate_attached_devices: Disk detected at %d\n", i);
+
+            /* We add the disk only if the maximum is not reached yet. */
+            if (read_byte(ebda_seg, &EbdaData->scsi.hdcount) < BX_MAX_SCSI_DEVICES)
+            {
+                Bit32u sectors, sector_size, cylinders;
+                Bit16u heads, sectors_per_track;
+                Bit8u  hdcount, hdcount_scsi;
+
+                /* Issue a read capacity command now. */
+                write_byte(get_SS(), aCDB + 0, SCSI_READ_CAPACITY);
+                memsetb(get_SS(), aCDB + 1, 0, 9);
+
+                rc = scsi_cmd_data_in(io_base, i, get_SS(), aCDB, 10, get_SS(), buffer, 8);
+                if (rc != 0)
+                    BX_PANIC("scsi_enumerate_attached_devices: SCSI_READ_CAPACITY failed\n");
+
+                /* Build sector number and size from the buffer. */
+                sectors =   ((uint32_t)read_byte(get_SS(), buffer + 0) << 24)
+                          | ((uint32_t)read_byte(get_SS(), buffer + 1) << 16)
+                          | ((uint32_t)read_byte(get_SS(), buffer + 2) << 8)
+                          | ((uint32_t)read_byte(get_SS(), buffer + 3));
+
+                sector_size =   ((uint32_t)read_byte(get_SS(), buffer + 4) << 24)
+                              | ((uint32_t)read_byte(get_SS(), buffer + 5) << 16)
+                              | ((uint32_t)read_byte(get_SS(), buffer + 6) << 8)
+                              | ((uint32_t)read_byte(get_SS(), buffer + 7));
+
+                /* We only support the disk if sector size is 512 bytes. */
+                if (sector_size != 512)
+                {
+                    /* Leave a log entry. */
+                    BX_INFO("Disk %d has an unsupported sector size of %u\n", i, sector_size);
+                    continue;
+                }
+
+                /* We need to calculate the geometry for the disk. */
+                if (io_base == BUSLOGIC_ISA_IO_PORT)
+                {
+                    /* This is from the BusLogic driver in the Linux kernel. */
+                    if (sectors >= 4 * 1024 * 1024)
+                    {
+                        heads = 255;
+                        sectors_per_track = 63;
+                    }
+                    else if (sectors >= 2 * 1024 * 1024)
+                    {
+                        heads = 128;
+                        sectors_per_track = 32;
+                    }
+                    else
+                    {
+                        heads = 64;
+                        sectors_per_track = 32;
+                    }
+                    cylinders = (uint32_t)(sectors / (heads * sectors_per_track));
+                }
+                else if (io_base == LSILOGIC_ISA_IO_PORT)
+                {
+                    /* This is from the BusLogic driver in the Linux kernel. */
+                    if (sectors >= 4 * 1024 * 1024)
+                    {
+                        heads = 255;
+                        sectors_per_track = 63;
+                    }
+                    else if (sectors >= 2 * 1024 * 1024)
+                    {
+                        heads = 128;
+                        sectors_per_track = 32;
+                    }
+                    else
+                    {
+                        heads = 64;
+                        sectors_per_track = 32;
+                    }
+                    cylinders = (uint32_t)(sectors / (heads * sectors_per_track));
+                }
+                hdcount_scsi = read_byte(ebda_seg, &EbdaData->scsi.hdcount);
+
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].io_base, io_base);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].target_id, i);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.type, ATA_TYPE_SCSI);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.device, ATA_DEVICE_HD);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.removable, 0);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.lock, 0);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.mode, ATA_MODE_PIO16);
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.blksize, sector_size);
+                write_byte(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.translation, ATA_TRANSLATION_LBA);
+
+                /* Write lchs values. */
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.lchs.heads, heads);
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.lchs.spt, sectors_per_track);
+                if (cylinders > 1024)
+                    write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.lchs.cylinders, 1024);
+                else
+                    write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.lchs.cylinders, (Bit16u)cylinders);
+
+                /* Write pchs values. */
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.pchs.heads, heads);
+                write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.pchs.spt, sectors_per_track);
+                if (cylinders > 1024)
+                    write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.pchs.cylinders, 1024);
+                else
+                    write_word(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.pchs.cylinders, (Bit16u)cylinders);
+
+                write_dword(ebda_seg, &EbdaData->scsi.devices[hdcount_scsi].device_info.sectors, sectors);
+
+                /* Store the id of the disk in the ata hdidmap. */
+                hdcount = read_byte(ebda_seg, &EbdaData->ata.hdcount);
+                write_byte(ebda_seg, &EbdaData->ata.hdidmap[hdcount], hdcount_scsi + BX_MAX_ATA_DEVICES);
+                hdcount++;
+                write_byte(ebda_seg, &EbdaData->ata.hdcount, hdcount);
+
+                hdcount_scsi++;
+                write_byte(ebda_seg, &EbdaData->scsi.hdcount, hdcount_scsi);
+            }
+            else
+            {
+                /* We reached the maximum of SCSI disks we can boot from. We can quit detecting. */
+                break;
+            }
+        }
+        else
+            VBOXSCSI_DEBUG("scsi_enumerate_attached_devices: No disk detected at %d\n", i);
+    }
+}
+
+/**
+ * Init the SCSI driver and detect attached disks.
+ */
+void scsi_init( )
+{
+    Bit8u identifier;
+
+    identifier = 0;
+
+    /* Detect BusLogic adapter. */
+    outb(BUSLOGIC_ISA_IO_PORT+VBOXSCSI_REGISTER_IDENTIFY, 0x55);
+    identifier = inb(BUSLOGIC_ISA_IO_PORT+VBOXSCSI_REGISTER_IDENTIFY);
+
+    if (identifier == 0x55)
+    {
+        /* Detected - Enumerate attached devices. */
+        VBOXSCSI_DEBUG("scsi_init: BusLogic SCSI adapter detected\n");
+        scsi_enumerate_attached_devices(BUSLOGIC_ISA_IO_PORT);
+    }
+    else
+    {
+        VBOXSCSI_DEBUG("scsi_init: BusLogic SCSI adapter not detected\n");
+    }
+
+    /* Detect LsiLogic adapter. */
+    outb(LSILOGIC_ISA_IO_PORT+VBOXSCSI_REGISTER_IDENTIFY, 0x55);
+    identifier = inb(LSILOGIC_ISA_IO_PORT+VBOXSCSI_REGISTER_IDENTIFY);
+
+    if (identifier == 0x55)
+    {
+        /* Detected - Enumerate attached devices. */
+        VBOXSCSI_DEBUG("scsi_init: LsiLogic SCSI adapter detected\n");
+        scsi_enumerate_attached_devices(LSILOGIC_ISA_IO_PORT);
+    }
+    else
+    {
+        VBOXSCSI_DEBUG("scsi_init: LsiLogic SCSI adapter not detected\n");
+    }
+}
+
Index: trunk/src/VBox/Devices/Storage/ATAController.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/ATAController.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/ATAController.cpp	(revision 21321)
@@ -0,0 +1,5560 @@
+/* $Id$ */
+/** @file
+ * DevATA, DevAHCI - Shared ATA/ATAPI controller code (disk and cdrom).
+ *
+ * @todo Actually share this code?
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_IDE
+#include <VBox/pdmdev.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/uuid.h>
+# include <iprt/semaphore.h>
+# include <iprt/thread.h>
+# include <iprt/time.h>
+# include <iprt/alloc.h>
+#endif /* IN_RING3 */
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <VBox/stam.h>
+#include <VBox/mm.h>
+#include <VBox/pgm.h>
+
+#include <VBox/scsi.h>
+
+#include "ATAController.h"
+#include "PIIX3ATABmDma.h"
+#include "ide.h"
+
+/**
+ * The SSM saved state version.
+ */
+#define ATA_CTL_SAVED_STATE_VERSION 3
+#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 1
+#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 2
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+DECLINLINE(void) ataSetStatusValue(AHCIATADevState *s, uint8_t stat)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+    /* Freeze status register contents while processing RESET. */
+    if (!pCtl->fReset)
+    {
+        s->uATARegStatus = stat;
+        Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+    }
+}
+
+
+DECLINLINE(void) ataSetStatus(AHCIATADevState *s, uint8_t stat)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+    /* Freeze status register contents while processing RESET. */
+    if (!pCtl->fReset)
+    {
+        s->uATARegStatus |= stat;
+        Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+    }
+}
+
+
+DECLINLINE(void) ataUnsetStatus(AHCIATADevState *s, uint8_t stat)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+    /* Freeze status register contents while processing RESET. */
+    if (!pCtl->fReset)
+    {
+        s->uATARegStatus &= ~stat;
+        Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+    }
+}
+
+#ifdef IN_RING3
+
+typedef void (*PBeginTransferFunc)(AHCIATADevState *);
+typedef bool (*PSourceSinkFunc)(AHCIATADevState *);
+
+static void ataReadWriteSectorsBT(AHCIATADevState *);
+static void ataPacketBT(AHCIATADevState *);
+static void atapiCmdBT(AHCIATADevState *);
+static void atapiPassthroughCmdBT(AHCIATADevState *);
+
+static bool ataIdentifySS(AHCIATADevState *);
+static bool ataFlushSS(AHCIATADevState *);
+static bool ataReadSectorsSS(AHCIATADevState *);
+static bool ataWriteSectorsSS(AHCIATADevState *);
+static bool ataExecuteDeviceDiagnosticSS(AHCIATADevState *);
+static bool ataPacketSS(AHCIATADevState *);
+static bool atapiGetConfigurationSS(AHCIATADevState *);
+static bool atapiGetEventStatusNotificationSS(AHCIATADevState *);
+static bool atapiIdentifySS(AHCIATADevState *);
+static bool atapiInquirySS(AHCIATADevState *);
+static bool atapiMechanismStatusSS(AHCIATADevState *);
+static bool atapiModeSenseErrorRecoverySS(AHCIATADevState *);
+static bool atapiModeSenseCDStatusSS(AHCIATADevState *);
+static bool atapiReadSS(AHCIATADevState *);
+static bool atapiReadCapacitySS(AHCIATADevState *);
+static bool atapiReadDiscInformationSS(AHCIATADevState *);
+static bool atapiReadTOCNormalSS(AHCIATADevState *);
+static bool atapiReadTOCMultiSS(AHCIATADevState *);
+static bool atapiReadTOCRawSS(AHCIATADevState *);
+static bool atapiReadTrackInformationSS(AHCIATADevState *);
+static bool atapiRequestSenseSS(AHCIATADevState *);
+static bool atapiPassthroughSS(AHCIATADevState *);
+
+/**
+ * Begin of transfer function indexes for g_apfnBeginTransFuncs.
+ */
+typedef enum ATAFNBT
+{
+    ATAFN_BT_NULL = 0,
+    ATAFN_BT_READ_WRITE_SECTORS,
+    ATAFN_BT_PACKET,
+    ATAFN_BT_ATAPI_CMD,
+    ATAFN_BT_ATAPI_PASSTHROUGH_CMD,
+    ATAFN_BT_MAX
+} ATAFNBT;
+
+/**
+ * Array of end transfer functions, the index is ATAFNET.
+ * Make sure ATAFNET and this array match!
+ */
+static const PBeginTransferFunc g_apfnBeginTransFuncs[ATAFN_BT_MAX] =
+{
+    NULL,
+    ataReadWriteSectorsBT,
+    ataPacketBT,
+    atapiCmdBT,
+    atapiPassthroughCmdBT,
+};
+
+/**
+ * Source/sink function indexes for g_apfnSourceSinkFuncs.
+ */
+typedef enum ATAFNSS
+{
+    ATAFN_SS_NULL = 0,
+    ATAFN_SS_IDENTIFY,
+    ATAFN_SS_FLUSH,
+    ATAFN_SS_READ_SECTORS,
+    ATAFN_SS_WRITE_SECTORS,
+    ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC,
+    ATAFN_SS_PACKET,
+    ATAFN_SS_ATAPI_GET_CONFIGURATION,
+    ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION,
+    ATAFN_SS_ATAPI_IDENTIFY,
+    ATAFN_SS_ATAPI_INQUIRY,
+    ATAFN_SS_ATAPI_MECHANISM_STATUS,
+    ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
+    ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
+    ATAFN_SS_ATAPI_READ,
+    ATAFN_SS_ATAPI_READ_CAPACITY,
+    ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
+    ATAFN_SS_ATAPI_READ_TOC_NORMAL,
+    ATAFN_SS_ATAPI_READ_TOC_MULTI,
+    ATAFN_SS_ATAPI_READ_TOC_RAW,
+    ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
+    ATAFN_SS_ATAPI_REQUEST_SENSE,
+    ATAFN_SS_ATAPI_PASSTHROUGH,
+    ATAFN_SS_MAX
+} ATAFNSS;
+
+/**
+ * Array of source/sink functions, the index is ATAFNSS.
+ * Make sure ATAFNSS and this array match!
+ */
+static const PSourceSinkFunc g_apfnSourceSinkFuncs[ATAFN_SS_MAX] =
+{
+    NULL,
+    ataIdentifySS,
+    ataFlushSS,
+    ataReadSectorsSS,
+    ataWriteSectorsSS,
+    ataExecuteDeviceDiagnosticSS,
+    ataPacketSS,
+    atapiGetConfigurationSS,
+    atapiGetEventStatusNotificationSS,
+    atapiIdentifySS,
+    atapiInquirySS,
+    atapiMechanismStatusSS,
+    atapiModeSenseErrorRecoverySS,
+    atapiModeSenseCDStatusSS,
+    atapiReadSS,
+    atapiReadCapacitySS,
+    atapiReadDiscInformationSS,
+    atapiReadTOCNormalSS,
+    atapiReadTOCMultiSS,
+    atapiReadTOCRawSS,
+    atapiReadTrackInformationSS,
+    atapiRequestSenseSS,
+    atapiPassthroughSS
+};
+
+
+static const AHCIATARequest ataDMARequest = { AHCIATA_AIO_DMA, };
+static const AHCIATARequest ataPIORequest = { AHCIATA_AIO_PIO, };
+static const AHCIATARequest ataResetARequest = { AHCIATA_AIO_RESET_ASSERTED, };
+static const AHCIATARequest ataResetCRequest = { AHCIATA_AIO_RESET_CLEARED, };
+
+
+static void ataAsyncIOClearRequests(PAHCIATACONTROLLER pCtl)
+{
+    int rc;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    pCtl->AsyncIOReqHead = 0;
+    pCtl->AsyncIOReqTail = 0;
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+}
+
+
+static void ataAsyncIOPutRequest(PAHCIATACONTROLLER pCtl, const AHCIATARequest *pReq)
+{
+    int rc;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    Assert((pCtl->AsyncIOReqHead + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
+    memcpy(&pCtl->aAsyncIORequests[pCtl->AsyncIOReqHead], pReq, sizeof(*pReq));
+    pCtl->AsyncIOReqHead++;
+    pCtl->AsyncIOReqHead %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+    LogBird(("ata: %x: signalling\n", pCtl->IOPortBase1));
+    rc = PDMR3CritSectScheduleExitEvent(&pCtl->lock, pCtl->AsyncIOSem);
+    if (RT_FAILURE(rc))
+    {
+        LogBird(("ata: %x: schedule failed, rc=%Rrc\n", pCtl->IOPortBase1, rc));
+        rc = RTSemEventSignal(pCtl->AsyncIOSem);
+        AssertRC(rc);
+    }
+}
+
+
+static const AHCIATARequest *ataAsyncIOGetCurrentRequest(PAHCIATACONTROLLER pCtl)
+{
+    int rc;
+    const AHCIATARequest *pReq;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
+        pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
+    else
+        pReq = NULL;
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+    return pReq;
+}
+
+
+/**
+ * Remove the request with the given type, as it's finished. The request
+ * is not removed blindly, as this could mean a RESET request that is not
+ * yet processed (but has cleared the request queue) is lost.
+ *
+ * @param pCtl      Controller for which to remove the request.
+ * @param ReqType   Type of the request to remove.
+ */
+static void ataAsyncIORemoveCurrentRequest(PAHCIATACONTROLLER pCtl, AHCIATAAIO ReqType)
+{
+    int rc;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
+    {
+        pCtl->AsyncIOReqTail++;
+        pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+    }
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+}
+
+
+/**
+ * Dump the request queue for a particular controller. First dump the queue
+ * contents, then the already processed entries, as long as they haven't been
+ * overwritten.
+ *
+ * @param pCtl      Controller for which to dump the queue.
+ */
+static void ataAsyncIODumpRequests(PAHCIATACONTROLLER pCtl)
+{
+    int rc;
+    uint8_t curr;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    LogRel(("ATA: Ctl: request queue dump (topmost is current):\n"));
+    curr = pCtl->AsyncIOReqTail;
+    do
+    {
+        if (curr == pCtl->AsyncIOReqHead)
+            LogRel(("ATA: Ctl: processed requests (topmost is oldest):\n"));
+        switch (pCtl->aAsyncIORequests[curr].ReqType)
+        {
+            case AHCIATA_AIO_NEW:
+                LogRel(("new transfer request, iIf=%d iBeginTransfer=%d iSourceSink=%d cbTotalTransfer=%d uTxDir=%d\n", pCtl->aAsyncIORequests[curr].u.t.iIf, pCtl->aAsyncIORequests[curr].u.t.iBeginTransfer, pCtl->aAsyncIORequests[curr].u.t.iSourceSink, pCtl->aAsyncIORequests[curr].u.t.cbTotalTransfer, pCtl->aAsyncIORequests[curr].u.t.uTxDir));
+                break;
+            case AHCIATA_AIO_DMA:
+                LogRel(("dma transfer finished\n"));
+                break;
+            case AHCIATA_AIO_PIO:
+                LogRel(("pio transfer finished\n"));
+                break;
+            case AHCIATA_AIO_RESET_ASSERTED:
+                LogRel(("reset asserted request\n"));
+                break;
+            case AHCIATA_AIO_RESET_CLEARED:
+                LogRel(("reset cleared request\n"));
+                break;
+            case AHCIATA_AIO_ABORT:
+                LogRel(("abort request, iIf=%d fResetDrive=%d\n", pCtl->aAsyncIORequests[curr].u.a.iIf,  pCtl->aAsyncIORequests[curr].u.a.fResetDrive));
+                break;
+            default:
+                LogRel(("unknown request %d\n", pCtl->aAsyncIORequests[curr].ReqType));
+        }
+        curr = (curr + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+    } while (curr != pCtl->AsyncIOReqTail);
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+}
+
+
+/**
+ * Checks whether the request queue for a particular controller is empty
+ * or whether a particular controller is idle.
+ *
+ * @param pCtl      Controller for which to check the queue.
+ * @param fStrict   If set then the controller is checked to be idle.
+ */
+static bool ataAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, bool fStrict)
+{
+    int rc;
+    bool fIdle;
+
+    rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+    AssertRC(rc);
+    fIdle = pCtl->fRedoIdle;
+    if (!fIdle)
+        fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
+    if (fStrict)
+        fIdle &= (pCtl->uAsyncIOState == AHCIATA_AIO_NEW);
+    rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+    return fIdle;
+}
+
+
+/**
+ * Send a transfer request to the async I/O thread.
+ *
+ * @param   s                   Pointer to the ATA device state data.
+ * @param   cbTotalTransfer     Data transfer size.
+ * @param   uTxDir              Data transfer direction.
+ * @param   iBeginTransfer      Index of BeginTransfer callback.
+ * @param   iSourceSink         Index of SourceSink callback.
+ * @param   fChainedTransfer    Whether this is a transfer that is part of the previous command/transfer.
+ */
+static void ataStartTransfer(AHCIATADevState *s, uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer, ATAFNSS iSourceSink, bool fChainedTransfer)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    AHCIATARequest Req;
+
+    Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+    /* Do not issue new requests while the RESET line is asserted. */
+    if (pCtl->fReset)
+    {
+        Log2(("%s: Ctl: suppressed new request as RESET is active\n", __FUNCTION__));
+        return;
+    }
+
+    /* If the controller is already doing something else right now, ignore
+     * the command that is being submitted. Some broken guests issue commands
+     * twice (e.g. the Linux kernel that comes with Acronis True Image 8). */
+    if (!fChainedTransfer && !ataAsyncIOIsIdle(pCtl, true))
+    {
+        Log(("%s: Ctl: ignored command %#04x, controller state %d\n", __FUNCTION__, s->uATARegCommand, pCtl->uAsyncIOState));
+        LogRel(("IDE: guest issued command %#04x while controller busy\n", s->uATARegCommand));
+        return;
+    }
+
+    Req.ReqType = AHCIATA_AIO_NEW;
+    if (fChainedTransfer)
+        Req.u.t.iIf = pCtl->iAIOIf;
+    else
+        Req.u.t.iIf = pCtl->iSelectedIf;
+    Req.u.t.cbTotalTransfer = cbTotalTransfer;
+    Req.u.t.uTxDir = uTxDir;
+    Req.u.t.iBeginTransfer = iBeginTransfer;
+    Req.u.t.iSourceSink = iSourceSink;
+    ataSetStatusValue(s, ATA_STAT_BUSY);
+    pCtl->fChainedTransfer = fChainedTransfer;
+
+    /*
+     * Kick the worker thread into action.
+     */
+    Log2(("%s: Ctl: message to async I/O thread, new request\n", __FUNCTION__));
+    ataAsyncIOPutRequest(pCtl, &Req);
+}
+
+
+/**
+ * Send an abort command request to the async I/O thread.
+ *
+ * @param   s           Pointer to the ATA device state data.
+ * @param   fResetDrive Whether to reset the drive or just abort a command.
+ */
+static void ataAbortCurrentCommand(AHCIATADevState *s, bool fResetDrive)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    AHCIATARequest Req;
+
+    Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+    /* Do not issue new requests while the RESET line is asserted. */
+    if (pCtl->fReset)
+    {
+        Log2(("%s: Ctl: suppressed aborting command as RESET is active\n", __FUNCTION__));
+        return;
+    }
+
+    Req.ReqType = AHCIATA_AIO_ABORT;
+    Req.u.a.iIf = pCtl->iSelectedIf;
+    Req.u.a.fResetDrive = fResetDrive;
+    ataSetStatus(s, ATA_STAT_BUSY);
+    Log2(("%s: Ctl: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, s->iLUN));
+    ataAsyncIOPutRequest(pCtl, &Req);
+}
+
+
+static void ataSetIRQ(AHCIATADevState *s)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+    if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+    {
+        Log2(("%s: LUN#%d asserting IRQ\n", __FUNCTION__, s->iLUN));
+        /* The BMDMA unit unconditionally sets BM_STATUS_INT if the interrupt
+         * line is asserted. It monitors the line for a rising edge. */
+        if (!s->fIrqPending)
+            pCtl->BmDma.u8Status |= BM_STATUS_INT;
+        /* Only actually set the IRQ line if updating the currently selected drive. */
+        if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+            PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
+    }
+    s->fIrqPending = true;
+}
+
+#endif /* IN_RING3 */
+
+static void ataUnsetIRQ(AHCIATADevState *s)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+    if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+    {
+        Log2(("%s: LUN#%d deasserting IRQ\n", __FUNCTION__, s->iLUN));
+        /* Only actually unset the IRQ line if updating the currently selected drive. */
+        if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+            PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
+    }
+    s->fIrqPending = false;
+}
+
+#ifdef IN_RING3
+
+static void ataPIOTransferStart(AHCIATADevState *s, uint32_t start, uint32_t size)
+{
+    Log2(("%s: LUN#%d start %d size %d\n", __FUNCTION__, s->iLUN, start, size));
+    s->iIOBufferPIODataStart = start;
+    s->iIOBufferPIODataEnd = start + size;
+    ataSetStatus(s, ATA_STAT_DRQ);
+}
+
+
+static void ataPIOTransferStop(AHCIATADevState *s)
+{
+    Log2(("%s: LUN#%d\n", __FUNCTION__, s->iLUN));
+    if (s->fATAPITransfer)
+    {
+        s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+        Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+        ataSetIRQ(s);
+        s->fATAPITransfer = false;
+    }
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    s->iIOBufferPIODataStart = 0;
+    s->iIOBufferPIODataEnd = 0;
+    s->iBeginTransfer = ATAFN_BT_NULL;
+    s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static void ataPIOTransferLimitATAPI(AHCIATADevState *s)
+{
+    uint32_t cbLimit, cbTransfer;
+
+    cbLimit = s->uATARegLCyl | (s->uATARegHCyl << 8);
+    /* Use maximum transfer size if the guest requested 0. Avoids a hang. */
+    if (cbLimit == 0)
+        cbLimit = 0xfffe;
+    Log2(("%s: byte count limit=%d\n", __FUNCTION__, cbLimit));
+    if (cbLimit == 0xffff)
+        cbLimit--;
+    cbTransfer = RT_MIN(s->cbTotalTransfer, s->iIOBufferEnd - s->iIOBufferCur);
+    if (cbTransfer > cbLimit)
+    {
+        /* Byte count limit for clipping must be even in this case */
+        if (cbLimit & 1)
+            cbLimit--;
+        cbTransfer = cbLimit;
+    }
+    s->uATARegLCyl = cbTransfer;
+    s->uATARegHCyl = cbTransfer >> 8;
+    s->cbElementaryTransfer = cbTransfer;
+}
+
+
+static uint32_t ataGetNSectors(AHCIATADevState *s)
+{
+    /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+    if (s->fLBA48)
+    {
+        if (!s->uATARegNSector && !s->uATARegNSectorHOB)
+            return 65536;
+        else
+            return s->uATARegNSectorHOB << 8 | s->uATARegNSector;
+    }
+    else
+    {
+        if (!s->uATARegNSector)
+            return 256;
+        else
+            return s->uATARegNSector;
+    }
+}
+
+
+static void ataPadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+    for (uint32_t i = 0; i < cbSize; i++)
+    {
+        if (*pbSrc)
+            pbDst[i ^ 1] = *pbSrc++;
+        else
+            pbDst[i ^ 1] = ' ';
+    }
+}
+
+
+static void ataSCSIPadStr(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+    for (uint32_t i = 0; i < cbSize; i++)
+    {
+        if (*pbSrc)
+            pbDst[i] = *pbSrc++;
+        else
+            pbDst[i] = ' ';
+    }
+}
+
+
+DECLINLINE(void) ataH2BE_U16(uint8_t *pbBuf, uint16_t val)
+{
+    pbBuf[0] = val >> 8;
+    pbBuf[1] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U24(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 16;
+    pbBuf[1] = val >> 8;
+    pbBuf[2] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U32(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 24;
+    pbBuf[1] = val >> 16;
+    pbBuf[2] = val >> 8;
+    pbBuf[3] = val;
+}
+
+
+DECLINLINE(uint16_t) ataBE2H_U16(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 8) | pbBuf[1];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U24(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U32(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
+}
+
+
+DECLINLINE(void) ataLBA2MSF(uint8_t *pbBuf, uint32_t iATAPILBA)
+{
+    iATAPILBA += 150;
+    pbBuf[0] = (iATAPILBA / 75) / 60;
+    pbBuf[1] = (iATAPILBA / 75) % 60;
+    pbBuf[2] = iATAPILBA % 75;
+}
+
+
+DECLINLINE(uint32_t) ataMSF2LBA(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
+}
+
+
+static void ataCmdOK(AHCIATADevState *s, uint8_t status)
+{
+    s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
+    ataSetStatusValue(s, ATA_STAT_READY | status);
+}
+
+
+static void ataCmdError(AHCIATADevState *s, uint8_t uErrorCode)
+{
+    Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
+    s->uATARegError = uErrorCode;
+    ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    s->iIOBufferCur = 0;
+    s->iIOBufferEnd = 0;
+    s->uTxDir = PDMBLOCKTXDIR_NONE;
+    s->iBeginTransfer = ATAFN_BT_NULL;
+    s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static bool ataIdentifySS(AHCIATADevState *s)
+{
+    uint16_t *p;
+    char aSerial[20];
+    int rc;
+    RTUUID Uuid;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer == 512);
+    rc = s->pDrvBlock ? s->pDrvBlock->pfnGetUuid(s->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
+    if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+    {
+        PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+        /* Generate a predictable serial for drives which don't have a UUID. */
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%x-%04x%04x",
+                    s->iLUN + ATADEVSTATE_2_DEVINS(s)->iInstance * 32,
+                    pCtl->IOPortBase1, pCtl->IOPortBase2);
+    }
+    else
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+    p = (uint16_t *)s->CTXALLSUFF(pbIOBuffer);
+    memset(p, 0, 512);
+    p[0] = RT_H2LE_U16(0x0040);
+    p[1] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+    p[3] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+    /* Block size; obsolete, but required for the BIOS. */
+    p[5] = RT_H2LE_U16(512);
+    p[6] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+    ataPadString((uint8_t *)(p + 10), aSerial, 20); /* serial number */
+    p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+    p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+    p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+    ataPadString((uint8_t *)(p + 23), "1.0", 8); /* firmware version */
+    ataPadString((uint8_t *)(p + 27), "VBOX HARDDISK", 40); /* model */
+#if ATA_MAX_MULT_SECTORS > 1
+    p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+#endif
+    p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+    p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+    p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+    p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+    p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+    p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+    p[54] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+    p[55] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+    p[56] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+    p[57] = RT_H2LE_U16(  RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+                        * s->PCHSGeometry.cHeads
+                        * s->PCHSGeometry.cSectors);
+    p[58] = RT_H2LE_U16(  RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+                        * s->PCHSGeometry.cHeads
+                        * s->PCHSGeometry.cSectors >> 16);
+    if (s->cMultSectors)
+        p[59] = RT_H2LE_U16(0x100 | s->cMultSectors);
+    if (s->cTotalSectors <= (1 << 28) - 1)
+    {
+        p[60] = RT_H2LE_U16(s->cTotalSectors);
+        p[61] = RT_H2LE_U16(s->cTotalSectors >> 16);
+    }
+    else
+    {
+        /* Report maximum number of sectors possible with LBA28 */
+        p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+        p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+    }
+    p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+    p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+    p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+    p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+    p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+    p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+    p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+    p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+    p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management,  write cache and look-ahead */
+    if (s->cTotalSectors <= (1 << 28) - 1)
+        p[83] = RT_H2LE_U16(1 << 14 | 1 << 12); /* supports FLUSH CACHE */
+    else
+        p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+    p[84] = RT_H2LE_U16(1 << 14);
+    p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management,  write cache and look-ahead */
+    if (s->cTotalSectors <= (1 << 28) - 1)
+        p[86] = RT_H2LE_U16(1 << 12); /* enabled FLUSH CACHE */
+    else
+        p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+    p[87] = RT_H2LE_U16(1 << 14);
+    p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+    p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+    if (s->cTotalSectors > (1 << 28) - 1)
+    {
+        p[100] = RT_H2LE_U16(s->cTotalSectors);
+        p[101] = RT_H2LE_U16(s->cTotalSectors >> 16);
+        p[102] = RT_H2LE_U16(s->cTotalSectors >> 32);
+        p[103] = RT_H2LE_U16(s->cTotalSectors >> 48);
+    }
+    s->iSourceSink = ATAFN_SS_NULL;
+    ataCmdOK(s, ATA_STAT_SEEK);
+    return false;
+}
+
+
+static bool ataFlushSS(AHCIATADevState *s)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    int rc;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_NONE);
+    Assert(!s->cbElementaryTransfer);
+
+    PDMCritSectLeave(&pCtl->lock);
+
+    STAM_PROFILE_START(&s->StatFlushes, f);
+    rc = s->pDrvBlock->pfnFlush(s->pDrvBlock);
+    AssertRC(rc);
+    STAM_PROFILE_STOP(&s->StatFlushes, f);
+
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+    ataCmdOK(s, 0);
+    return false;
+}
+
+
+static bool atapiIdentifySS(AHCIATADevState *s)
+{
+    uint16_t *p;
+    char aSerial[20];
+    RTUUID Uuid;
+    int rc;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer == 512);
+    rc = s->pDrvBlock ? s->pDrvBlock->pfnGetUuid(s->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
+    if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+    {
+        PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+        /* Generate a predictable serial for drives which don't have a UUID. */
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%x-%04x%04x",
+                    s->iLUN + ATADEVSTATE_2_DEVINS(s)->iInstance * 32,
+                    pCtl->IOPortBase1, pCtl->IOPortBase2);
+    }
+    else
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+    p = (uint16_t *)s->CTXALLSUFF(pbIOBuffer);
+    memset(p, 0, 512);
+    /* Removable CDROM, 50us response, 12 byte packets */
+    p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0);
+    ataPadString((uint8_t *)(p + 10), aSerial, 20); /* serial number */
+    p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+    p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+    ataPadString((uint8_t *)(p + 23), "1.0", 8); /* firmware version */
+    ataPadString((uint8_t *)(p + 27), "VBOX CD-ROM", 40); /* model */
+    p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+    p[50] = RT_H2LE_U16(1 << 14);  /* No drive specific standby timer minimum */
+    p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+    p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+    p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+    p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+    p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+    p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+    p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+    p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+    p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+    p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+    p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+    p[75] = RT_H2LE_U16(1); /* queue depth 1 */
+    p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+    p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+    p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+    p[83] = RT_H2LE_U16(1 << 14);
+    p[84] = RT_H2LE_U16(1 << 14);
+    p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+    p[86] = RT_H2LE_U16(0);
+    p[87] = RT_H2LE_U16(1 << 14);
+    p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+    p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+    s->iSourceSink = ATAFN_SS_NULL;
+    ataCmdOK(s, ATA_STAT_SEEK);
+    return false;
+}
+
+
+static void ataSetSignature(AHCIATADevState *s)
+{
+    s->uATARegSelect &= 0xf0; /* clear head */
+    /* put signature */
+    s->uATARegNSector = 1;
+    s->uATARegSector = 1;
+    if (s->fATAPI)
+    {
+        s->uATARegLCyl = 0x14;
+        s->uATARegHCyl = 0xeb;
+    }
+    else if (s->pDrvBlock)
+    {
+        s->uATARegLCyl = 0;
+        s->uATARegHCyl = 0;
+    }
+    else
+    {
+        s->uATARegLCyl = 0xff;
+        s->uATARegHCyl = 0xff;
+    }
+}
+
+
+static uint64_t ataGetSector(AHCIATADevState *s)
+{
+    uint64_t iLBA;
+    if (s->uATARegSelect & 0x40)
+    {
+        /* any LBA variant */
+        if (s->fLBA48)
+        {
+            /* LBA48 */
+            iLBA = ((uint64_t)s->uATARegHCylHOB << 40) |
+                ((uint64_t)s->uATARegLCylHOB << 32) |
+                ((uint64_t)s->uATARegSectorHOB << 24) |
+                ((uint64_t)s->uATARegHCyl << 16) |
+                ((uint64_t)s->uATARegLCyl << 8) |
+                s->uATARegSector;
+        }
+        else
+        {
+            /* LBA */
+            iLBA = ((s->uATARegSelect & 0x0f) << 24) | (s->uATARegHCyl << 16) |
+                (s->uATARegLCyl << 8) | s->uATARegSector;
+        }
+    }
+    else
+    {
+        /* CHS */
+        iLBA = ((s->uATARegHCyl << 8) | s->uATARegLCyl) * s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors +
+            (s->uATARegSelect & 0x0f) * s->PCHSGeometry.cSectors +
+            (s->uATARegSector - 1);
+    }
+    return iLBA;
+}
+
+static void ataSetSector(AHCIATADevState *s, uint64_t iLBA)
+{
+    uint32_t cyl, r;
+    if (s->uATARegSelect & 0x40)
+    {
+        /* any LBA variant */
+        if (s->fLBA48)
+        {
+            /* LBA48 */
+            s->uATARegHCylHOB = iLBA >> 40;
+            s->uATARegLCylHOB = iLBA >> 32;
+            s->uATARegSectorHOB = iLBA >> 24;
+            s->uATARegHCyl = iLBA >> 16;
+            s->uATARegLCyl = iLBA >> 8;
+            s->uATARegSector = iLBA;
+        }
+        else
+        {
+            /* LBA */
+            s->uATARegSelect = (s->uATARegSelect & 0xf0) | (iLBA >> 24);
+            s->uATARegHCyl = (iLBA >> 16);
+            s->uATARegLCyl = (iLBA >> 8);
+            s->uATARegSector = (iLBA);
+        }
+    }
+    else
+    {
+        /* CHS */
+        cyl = iLBA / (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+        r = iLBA % (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+        s->uATARegHCyl = cyl >> 8;
+        s->uATARegLCyl = cyl;
+        s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->PCHSGeometry.cSectors) & 0x0f);
+        s->uATARegSector = (r % s->PCHSGeometry.cSectors) + 1;
+    }
+}
+
+
+static int ataReadSectors(AHCIATADevState *s, uint64_t u64Sector, void *pvBuf, uint32_t cSectors)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    int rc;
+
+    PDMCritSectLeave(&pCtl->lock);
+
+    STAM_PROFILE_ADV_START(&s->StatReads, r);
+    s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
+    rc = s->pDrvBlock->pfnRead(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
+    s->pLed->Actual.s.fReading = 0;
+    STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+
+    STAM_REL_COUNTER_ADD(s->pStatBytesRead, cSectors * 512);
+
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+    return rc;
+}
+
+
+static int ataWriteSectors(AHCIATADevState *s, uint64_t u64Sector, const void *pvBuf, uint32_t cSectors)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    int rc;
+
+    PDMCritSectLeave(&pCtl->lock);
+
+    STAM_PROFILE_ADV_START(&s->StatWrites, w);
+    s->pLed->Asserted.s.fWriting = s->pLed->Actual.s.fWriting = 1;
+    rc = s->pDrvBlock->pfnWrite(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
+    s->pLed->Actual.s.fWriting = 0;
+    STAM_PROFILE_ADV_STOP(&s->StatWrites, w);
+
+    STAM_REL_COUNTER_ADD(s->pStatBytesWritten, cSectors * 512);
+
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+    return rc;
+}
+
+
+static void ataReadWriteSectorsBT(AHCIATADevState *s)
+{
+    uint32_t cSectors;
+
+    cSectors = s->cbTotalTransfer / 512;
+    if (cSectors > s->cSectorsPerIRQ)
+        s->cbElementaryTransfer = s->cSectorsPerIRQ * 512;
+    else
+        s->cbElementaryTransfer = cSectors * 512;
+    if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+        ataCmdOK(s, 0);
+}
+
+
+static void ataWarningDiskFull(PPDMDEVINS pDevIns)
+{
+    int rc;
+    LogRel(("ATA: Host disk full\n"));
+    rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_DISKFULL",
+                                    N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
+    AssertRC(rc);
+}
+
+
+static void ataWarningFileTooBig(PPDMDEVINS pDevIns)
+{
+    int rc;
+    LogRel(("ATA: File too big\n"));
+    rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_FILETOOBIG",
+                                    N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
+    AssertRC(rc);
+}
+
+
+static void ataWarningISCSI(PPDMDEVINS pDevIns)
+{
+    int rc;
+    LogRel(("ATA: iSCSI target unavailable\n"));
+    rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_ISCSIDOWN",
+                                    N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
+    AssertRC(rc);
+}
+
+
+static bool ataReadSectorsSS(AHCIATADevState *s)
+{
+    int rc;
+    uint32_t cSectors;
+    uint64_t iLBA;
+
+    cSectors = s->cbElementaryTransfer / 512;
+    Assert(cSectors);
+    iLBA = ataGetSector(s);
+    Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+    rc = ataReadSectors(s, iLBA, s->CTXALLSUFF(pbIOBuffer), cSectors);
+    if (RT_SUCCESS(rc))
+    {
+        ataSetSector(s, iLBA + cSectors);
+        if (s->cbElementaryTransfer == s->cbTotalTransfer)
+            s->iSourceSink = ATAFN_SS_NULL;
+        ataCmdOK(s, ATA_STAT_SEEK);
+    }
+    else
+    {
+        if (rc == VERR_DISK_FULL)
+        {
+            ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (rc == VERR_FILE_TOO_BIG)
+        {
+            ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+        {
+            /* iSCSI connection abort (first error) or failure to reestablish
+             * connection (second error). Pause VM. On resume we'll retry. */
+            ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+            LogRel(("AHCI ATA: LUN#%d: disk read error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+                    s->iLUN, rc, iLBA, cSectors));
+        ataCmdError(s, ID_ERR);
+    }
+    /** @todo implement redo for iSCSI */
+    return false;
+}
+
+
+static bool ataWriteSectorsSS(AHCIATADevState *s)
+{
+    int rc;
+    uint32_t cSectors;
+    uint64_t iLBA;
+
+    cSectors = s->cbElementaryTransfer / 512;
+    Assert(cSectors);
+    iLBA = ataGetSector(s);
+    Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+    rc = ataWriteSectors(s, iLBA, s->CTXALLSUFF(pbIOBuffer), cSectors);
+    if (RT_SUCCESS(rc))
+    {
+        ataSetSector(s, iLBA + cSectors);
+        if (!s->cbTotalTransfer)
+            s->iSourceSink = ATAFN_SS_NULL;
+        ataCmdOK(s, ATA_STAT_SEEK);
+    }
+    else
+    {
+        if (rc == VERR_DISK_FULL)
+        {
+            ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (rc == VERR_FILE_TOO_BIG)
+        {
+            ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+        {
+            /* iSCSI connection abort (first error) or failure to reestablish
+             * connection (second error). Pause VM. On resume we'll retry. */
+            ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
+            return true;
+        }
+        if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+            LogRel(("AHCI ATA: LUN#%d: disk write error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+                    s->iLUN, rc, iLBA, cSectors));
+        ataCmdError(s, ID_ERR);
+    }
+    /** @todo implement redo for iSCSI */
+    return false;
+}
+
+
+static void atapiCmdOK(AHCIATADevState *s)
+{
+    s->uATARegError = 0;
+    ataSetStatusValue(s, ATA_STAT_READY);
+    s->uATARegNSector = (s->uATARegNSector & ~7)
+        | ((s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
+        | (!s->cbTotalTransfer ? ATAPI_INT_REASON_CD : 0);
+    Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+    memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+    s->abATAPISense[0] = 0x70 | (1 << 7);
+    s->abATAPISense[7] = 10;
+}
+
+
+static void atapiCmdError(AHCIATADevState *s, const uint8_t *pabATAPISense, size_t cbATAPISense)
+{
+    Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
+             pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
+    s->uATARegError = pabATAPISense[2] << 4;
+    ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+    s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+    Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+    memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+    memcpy(s->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(s->abATAPISense)));
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    s->iIOBufferCur = 0;
+    s->iIOBufferEnd = 0;
+    s->uTxDir = PDMBLOCKTXDIR_NONE;
+    s->iBeginTransfer = ATAFN_BT_NULL;
+    s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/** @todo deprecated function - doesn't provide enough info. Replace by direct
+ * calls to atapiCmdError()  with full data. */
+static void atapiCmdErrorSimple(AHCIATADevState *s, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+    uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+    memset(abATAPISense, '\0', sizeof(abATAPISense));
+    abATAPISense[0] = 0x70 | (1 << 7);
+    abATAPISense[2] = uATAPISenseKey & 0x0f;
+    abATAPISense[7] = 10;
+    abATAPISense[12] = uATAPIASC;
+    atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
+}
+
+
+static void atapiCmdBT(AHCIATADevState *s)
+{
+    s->fATAPITransfer = true;
+    s->cbElementaryTransfer = s->cbTotalTransfer;
+    if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+        atapiCmdOK(s);
+}
+
+
+static void atapiPassthroughCmdBT(AHCIATADevState *s)
+{
+    /* @todo implement an algorithm for correctly determining the read and
+     * write sector size without sending additional commands to the drive.
+     * This should be doable by saving processing the configuration requests
+     * and replies. */
+#if 0
+    if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+    {
+        uint8_t cmd = s->aATAPICmd[0];
+        if (cmd == SCSI_WRITE_10 || cmd == SCSI_WRITE_12 || cmd == SCSI_WRITE_AND_VERIFY_10)
+        {
+            uint8_t aModeSenseCmd[10];
+            uint8_t aModeSenseResult[16];
+            uint8_t uDummySense;
+            uint32_t cbTransfer;
+            int rc;
+
+            cbTransfer = sizeof(aModeSenseResult);
+            aModeSenseCmd[0] = SCSI_MODE_SENSE_10;
+            aModeSenseCmd[1] = 0x08; /* disable block descriptor = 1 */
+            aModeSenseCmd[2] = (SCSI_PAGECONTROL_CURRENT << 6) | SCSI_MODEPAGE_WRITE_PARAMETER;
+            aModeSenseCmd[3] = 0; /* subpage code */
+            aModeSenseCmd[4] = 0; /* reserved */
+            aModeSenseCmd[5] = 0; /* reserved */
+            aModeSenseCmd[6] = 0; /* reserved */
+            aModeSenseCmd[7] = cbTransfer >> 8;
+            aModeSenseCmd[8] = cbTransfer & 0xff;
+            aModeSenseCmd[9] = 0; /* control */
+            rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aModeSenseCmd, PDMBLOCKTXDIR_FROM_DEVICE, aModeSenseResult, &cbTransfer, &uDummySense, 500);
+            if (RT_FAILURE(rc))
+            {
+                atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_NONE);
+                return;
+            }
+            /* Select sector size based on the current data block type. */
+            switch (aModeSenseResult[12] & 0x0f)
+            {
+                case 0:
+                    s->cbATAPISector = 2352;
+                    break;
+                case 1:
+                    s->cbATAPISector = 2368;
+                    break;
+                case 2:
+                case 3:
+                    s->cbATAPISector = 2448;
+                    break;
+                case 8:
+                case 10:
+                    s->cbATAPISector = 2048;
+                    break;
+                case 9:
+                    s->cbATAPISector = 2336;
+                    break;
+                case 11:
+                    s->cbATAPISector = 2056;
+                    break;
+                case 12:
+                    s->cbATAPISector = 2324;
+                    break;
+                case 13:
+                    s->cbATAPISector = 2332;
+                    break;
+                default:
+                    s->cbATAPISector = 0;
+            }
+            Log2(("%s: sector size %d\n", __FUNCTION__, s->cbATAPISector));
+            s->cbTotalTransfer *= s->cbATAPISector;
+            if (s->cbTotalTransfer == 0)
+                s->uTxDir = PDMBLOCKTXDIR_NONE;
+        }
+    }
+#endif
+    atapiCmdBT(s);
+}
+
+
+static bool atapiReadSS(AHCIATADevState *s)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    int rc = VINF_SUCCESS;
+    uint32_t cbTransfer, cSectors;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    cbTransfer = RT_MIN(s->cbTotalTransfer, s->cbIOBuffer);
+    cSectors = cbTransfer / s->cbATAPISector;
+    Assert(cSectors * s->cbATAPISector <= cbTransfer);
+    Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, s->iATAPILBA));
+
+    PDMCritSectLeave(&pCtl->lock);
+
+    STAM_PROFILE_ADV_START(&s->StatReads, r);
+    s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
+    switch (s->cbATAPISector)
+    {
+        case 2048:
+            rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)s->iATAPILBA * s->cbATAPISector, s->CTXALLSUFF(pbIOBuffer), s->cbATAPISector * cSectors);
+            break;
+        case 2352:
+            {
+                uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+                for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
+                {
+                    /* sync bytes */
+                    *pbBuf++ = 0x00;
+                    memset(pbBuf, 0xff, 11);
+                    pbBuf += 11;
+                    /* MSF */
+                    ataLBA2MSF(pbBuf, i);
+                    pbBuf += 3;
+                    *pbBuf++ = 0x01; /* mode 1 data */
+                    /* data */
+                    rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)i * 2048, pbBuf, 2048);
+                    if (RT_FAILURE(rc))
+                        break;
+                    pbBuf += 2048;
+                    /* ECC */
+                    memset(pbBuf, 0, 288);
+                    pbBuf += 288;
+                }
+            }
+            break;
+        default:
+            break;
+    }
+    STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+
+    if (RT_SUCCESS(rc))
+    {
+        s->pLed->Actual.s.fReading = 0;
+        STAM_REL_COUNTER_ADD(s->pStatBytesRead, s->cbATAPISector * cSectors);
+
+        /* The initial buffer end value has been set up based on the total
+         * transfer size. But the I/O buffer size limits what can actually be
+         * done in one transfer, so set the actual value of the buffer end. */
+        s->cbElementaryTransfer = cbTransfer;
+        if (cbTransfer >= s->cbTotalTransfer)
+            s->iSourceSink = ATAFN_SS_NULL;
+        atapiCmdOK(s);
+        s->iATAPILBA += cSectors;
+    }
+    else
+    {
+        if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+            LogRel(("AHCI ATA: LUN#%d: CD-ROM read error, %d sectors at LBA %d\n", s->iLUN, cSectors, s->iATAPILBA));
+        atapiCmdErrorSimple(s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
+    }
+    return false;
+}
+
+
+static bool atapiPassthroughSS(AHCIATADevState *s)
+{
+    PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+    int rc = VINF_SUCCESS;
+    uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+    uint32_t cbTransfer;
+    PSTAMPROFILEADV pProf = NULL;
+
+    cbTransfer = s->cbElementaryTransfer;
+
+    if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+        Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
+
+    /* Simple heuristics: if there is at least one sector of data
+     * to transfer, it's worth updating the LEDs. */
+    if (cbTransfer >= 2048)
+    {
+        if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+        {
+            s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
+            pProf = &s->StatReads;
+        }
+        else
+        {
+            s->pLed->Asserted.s.fWriting = s->pLed->Actual.s.fWriting = 1;
+            pProf = &s->StatWrites;
+        }
+    }
+
+    PDMCritSectLeave(&pCtl->lock);
+
+    memset(abATAPISense, '\0', sizeof(abATAPISense));
+    if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
+    if (cbTransfer > 100 * _1K)
+    {
+        /* Linux accepts commands with up to 100KB of data, but expects
+         * us to handle commands with up to 128KB of data. The usual
+         * imbalance of powers. */
+        uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+        uint32_t iATAPILBA, cSectors, cReqSectors, cbCurrTX;
+        uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+        switch (s->aATAPICmd[0])
+        {
+            case SCSI_READ_10:
+            case SCSI_WRITE_10:
+            case SCSI_WRITE_AND_VERIFY_10:
+                iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+                cSectors = ataBE2H_U16(s->aATAPICmd + 7);
+                break;
+            case SCSI_READ_12:
+            case SCSI_WRITE_12:
+                iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+                cSectors = ataBE2H_U32(s->aATAPICmd + 6);
+                break;
+            case SCSI_READ_CD:
+                iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+                cSectors = ataBE2H_U24(s->aATAPICmd + 6) / s->cbATAPISector;
+                break;
+            case SCSI_READ_CD_MSF:
+                iATAPILBA = ataMSF2LBA(s->aATAPICmd + 3);
+                cSectors = ataMSF2LBA(s->aATAPICmd + 6) - iATAPILBA;
+                break;
+            default:
+                AssertMsgFailed(("Don't know how to split command %#04x\n", s->aATAPICmd[0]));
+                if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+                    LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+                atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+                {
+                STAM_PROFILE_START(&pCtl->StatLockWait, a);
+                PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+                STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+                }
+                return false;
+        }
+        memcpy(aATAPICmd, s->aATAPICmd, ATAPI_PACKET_SIZE);
+        cReqSectors = 0;
+        for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
+        {
+            if (i * s->cbATAPISector > 100 * _1K)
+                cReqSectors = (100 * _1K) / s->cbATAPISector;
+            else
+                cReqSectors = i;
+            cbCurrTX = s->cbATAPISector * cReqSectors;
+            switch (s->aATAPICmd[0])
+            {
+                case SCSI_READ_10:
+                case SCSI_WRITE_10:
+                case SCSI_WRITE_AND_VERIFY_10:
+                    ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
+                    ataH2BE_U16(aATAPICmd + 7, cReqSectors);
+                    break;
+                case SCSI_READ_12:
+                case SCSI_WRITE_12:
+                    ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
+                    ataH2BE_U32(aATAPICmd + 6, cReqSectors);
+                    break;
+                case SCSI_READ_CD:
+                    ataH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
+                    ataH2BE_U24(s->aATAPICmd + 6, cbCurrTX);
+                    break;
+                case SCSI_READ_CD_MSF:
+                    ataLBA2MSF(aATAPICmd + 3, iATAPILBA);
+                    ataLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
+                    break;
+            }
+            rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+            if (rc != VINF_SUCCESS)
+                break;
+            iATAPILBA += cReqSectors;
+            pbBuf += s->cbATAPISector * cReqSectors;
+        }
+    }
+    else
+        rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, s->aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, s->CTXALLSUFF(pbIOBuffer), &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+    if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
+
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+
+    /* Update the LEDs and the read/write statistics. */
+    if (cbTransfer >= 2048)
+    {
+        if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+        {
+            s->pLed->Actual.s.fReading = 0;
+            STAM_REL_COUNTER_ADD(s->pStatBytesRead, cbTransfer);
+        }
+        else
+        {
+            s->pLed->Actual.s.fWriting = 0;
+            STAM_REL_COUNTER_ADD(s->pStatBytesWritten, cbTransfer);
+        }
+    }
+
+    if (RT_SUCCESS(rc))
+    {
+        if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+        {
+            Assert(cbTransfer <= s->cbTotalTransfer);
+            /* Reply with the same amount of data as the real drive. */
+            s->cbTotalTransfer = cbTransfer;
+            /* The initial buffer end value has been set up based on the total
+             * transfer size. But the I/O buffer size limits what can actually be
+             * done in one transfer, so set the actual value of the buffer end. */
+            s->cbElementaryTransfer = cbTransfer;
+            if (s->aATAPICmd[0] == SCSI_INQUIRY)
+            {
+                /* Make sure that the real drive cannot be identified.
+                 * Motivation: changing the VM configuration should be as
+                 * invisible as possible to the guest. */
+                Log3(("ATAPI PT inquiry data before (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
+                ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 8, "VBOX", 8);
+                ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 16, "CD-ROM", 16);
+                ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 32, "1.0", 4);
+            }
+            if (cbTransfer)
+                Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
+        }
+        s->iSourceSink = ATAFN_SS_NULL;
+        atapiCmdOK(s);
+    }
+    else
+    {
+        if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+        {
+            uint8_t u8Cmd = s->aATAPICmd[0];
+            do
+            {
+                /* don't log superflous errors */
+                if (    rc == VERR_DEV_IO_ERROR
+                    && (   u8Cmd == SCSI_TEST_UNIT_READY
+                        || u8Cmd == SCSI_READ_CAPACITY
+                        || u8Cmd == SCSI_READ_TOC_PMA_ATIP))
+                    break;
+                LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough command (%#04x) error %d %Rrc\n", s->iLUN, u8Cmd, abATAPISense[0] & 0x0f, rc));
+            } while (0);
+        }
+        atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
+    }
+    return false;
+}
+
+
+static bool atapiReadSectors(AHCIATADevState *s, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+    Assert(cSectors > 0);
+    s->iATAPILBA = iATAPILBA;
+    s->cbATAPISector = cbSector;
+    ataStartTransfer(s, cSectors * cbSector, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
+    return false;
+}
+
+
+static bool atapiReadCapacitySS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 8);
+    ataH2BE_U32(pbBuf, s->cTotalSectors - 1);
+    ataH2BE_U32(pbBuf + 4, 2048);
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiReadDiscInformationSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 34);
+    memset(pbBuf, '\0', 34);
+    ataH2BE_U16(pbBuf, 32);
+    pbBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+    pbBuf[3] = 1; /* number of first track */
+    pbBuf[4] = 1; /* number of sessions (LSB) */
+    pbBuf[5] = 1; /* first track number in last session (LSB) */
+    pbBuf[6] = 1; /* last track number in last session (LSB) */
+    pbBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+    pbBuf[8] = 0; /* disc type = CD-ROM */
+    pbBuf[9] = 0; /* number of sessions (MSB) */
+    pbBuf[10] = 0; /* number of sessions (MSB) */
+    pbBuf[11] = 0; /* number of sessions (MSB) */
+    ataH2BE_U32(pbBuf + 16, 0x00ffffff); /* last session lead-in start time is not available */
+    ataH2BE_U32(pbBuf + 20, 0x00ffffff); /* last possible start time for lead-out is not available */
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiReadTrackInformationSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 36);
+    /* Accept address/number type of 1 only, and only track 1 exists. */
+    if ((s->aATAPICmd[1] & 0x03) != 1 || ataBE2H_U32(&s->aATAPICmd[2]) != 1)
+    {
+        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return false;
+    }
+    memset(pbBuf, '\0', 36);
+    ataH2BE_U16(pbBuf, 34);
+    pbBuf[2] = 1; /* track number (LSB) */
+    pbBuf[3] = 1; /* session number (LSB) */
+    pbBuf[5] = (0 << 5) | (0 << 4) | (4 << 0); /* not damaged, primary copy, data track */
+    pbBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | (1 << 0); /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
+    pbBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+    ataH2BE_U32(pbBuf + 8, 0); /* track start address is 0 */
+    ataH2BE_U32(pbBuf + 24, s->cTotalSectors); /* track size */
+    pbBuf[32] = 0; /* track number (MSB) */
+    pbBuf[33] = 0; /* session number (MSB) */
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiGetConfigurationSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+    uint16_t u16Sfn = ataBE2H_U16(&s->aATAPICmd[2]);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 32);
+    /* Accept valid request types only, and only starting feature 0. */
+    if ((s->aATAPICmd[1] & 0x03) == 3 || u16Sfn != 0)
+    {
+        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return false;
+    }
+    memset(pbBuf, '\0', 32);
+    ataH2BE_U32(pbBuf, 16);
+    /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+     * way to differentiate them right now is based on the image size). Also
+     * implement signalling "no current profile" if no medium is loaded. */
+    ataH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
+
+    ataH2BE_U16(pbBuf + 8, 0); /* feature 0: list of profiles supported */
+    pbBuf[10] = (0 << 2) | (1 << 1) | (1 || 0); /* version 0, persistent, current */
+    pbBuf[11] = 8; /* additional bytes for profiles */
+    /* The MMC-3 spec says that DVD-ROM read capability should be reported
+     * before CD-ROM read capability. */
+    ataH2BE_U16(pbBuf + 12, 0x10); /* profile: read-only DVD */
+    pbBuf[14] = (0 << 0); /* NOT current profile */
+    ataH2BE_U16(pbBuf + 16, 0x08); /* profile: read only CD */
+    pbBuf[18] = (1 << 0); /* current profile */
+    /* Other profiles we might want to add in the future: 0x40 (BD-ROM) and 0x50 (HDDVD-ROM) */
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiGetEventStatusNotificationSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 8);
+
+    if (!(s->aATAPICmd[1] & 1))
+    {
+        /* no asynchronous operation supported */
+        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return false;
+    }
+
+    uint32_t OldStatus, NewStatus;
+    do
+    {
+        OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+        NewStatus = ATA_EVENT_STATUS_UNCHANGED;
+        switch (OldStatus)
+        {
+            case ATA_EVENT_STATUS_MEDIA_NEW:
+            /* mount */
+                ataH2BE_U16(pbBuf + 0, 6);
+                pbBuf[2] = 0x04;
+                pbBuf[3] = 0x5e;
+                pbBuf[4] = 0x02;
+                pbBuf[5] = 0x02;
+                pbBuf[6] = 0x00;
+                pbBuf[7] = 0x00;
+                break;
+
+            case ATA_EVENT_STATUS_MEDIA_CHANGED:
+            case ATA_EVENT_STATUS_MEDIA_REMOVED:
+                /* umount */
+                ataH2BE_U16(pbBuf + 0, 6);
+                pbBuf[2] = 0x04;
+                pbBuf[3] = 0x5e;
+                pbBuf[4] = 0x03;
+                pbBuf[5] = 0x00;
+                pbBuf[6] = 0x00;
+                pbBuf[7] = 0x00;
+                if (OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED)
+                    NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+                break;
+
+            case ATA_EVENT_STATUS_UNCHANGED:
+            default:
+                ataH2BE_U16(pbBuf + 0, 6);
+                pbBuf[2] = 0x01;
+                pbBuf[3] = 0x5e;
+                pbBuf[4] = 0x00;
+                pbBuf[5] = 0x00;
+                pbBuf[6] = 0x00;
+                pbBuf[7] = 0x00;
+                break;
+        }
+    } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiInquirySS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 36);
+    pbBuf[0] = 0x05; /* CD-ROM */
+    pbBuf[1] = 0x80; /* removable */
+#if 1/*ndef VBOX*/  /** @todo implement MESN + AENC. (async notification on removal and stuff.) */
+    pbBuf[2] = 0x00; /* ISO */
+    pbBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+#else
+    pbBuf[2] = 0x00; /* ISO */
+    pbBuf[3] = 0x91; /* format 1, MESN=1, AENC=9 ??? */
+#endif
+    pbBuf[4] = 31; /* additional length */
+    pbBuf[5] = 0; /* reserved */
+    pbBuf[6] = 0; /* reserved */
+    pbBuf[7] = 0; /* reserved */
+    ataSCSIPadStr(pbBuf + 8, "VBOX", 8);
+    ataSCSIPadStr(pbBuf + 16, "CD-ROM", 16);
+    ataSCSIPadStr(pbBuf + 32, "1.0", 4);
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiModeSenseErrorRecoverySS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 16);
+    ataH2BE_U16(&pbBuf[0], 16 + 6);
+    pbBuf[2] = 0x70;
+    pbBuf[3] = 0;
+    pbBuf[4] = 0;
+    pbBuf[5] = 0;
+    pbBuf[6] = 0;
+    pbBuf[7] = 0;
+
+    pbBuf[8] = 0x01;
+    pbBuf[9] = 0x06;
+    pbBuf[10] = 0x00;
+    pbBuf[11] = 0x05;
+    pbBuf[12] = 0x00;
+    pbBuf[13] = 0x00;
+    pbBuf[14] = 0x00;
+    pbBuf[15] = 0x00;
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiModeSenseCDStatusSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 40);
+    ataH2BE_U16(&pbBuf[0], 38);
+    pbBuf[2] = 0x70;
+    pbBuf[3] = 0;
+    pbBuf[4] = 0;
+    pbBuf[5] = 0;
+    pbBuf[6] = 0;
+    pbBuf[7] = 0;
+
+    pbBuf[8] = 0x2a;
+    pbBuf[9] = 30; /* page length */
+    pbBuf[10] = 0x08; /* DVD-ROM read support */
+    pbBuf[11] = 0x00; /* no write support */
+    /* The following claims we support audio play. This is obviously false,
+     * but the Linux generic CDROM support makes many features depend on this
+     * capability. If it's not set, this causes many things to be disabled. */
+    pbBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+    pbBuf[13] = 0x00; /* no subchannel reads supported */
+    pbBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+    if (s->pDrvMount->pfnIsLocked(s->pDrvMount))
+        pbBuf[14] |= 1 << 1; /* report lock state */
+    pbBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+    ataH2BE_U16(&pbBuf[16], 5632); /* (obsolete) claim 32x speed support */
+    ataH2BE_U16(&pbBuf[18], 2); /* number of audio volume levels */
+    ataH2BE_U16(&pbBuf[20], s->cbIOBuffer / _1K); /* buffer size supported in Kbyte */
+    ataH2BE_U16(&pbBuf[22], 5632); /* (obsolete) current read speed 32x */
+    pbBuf[24] = 0; /* reserved */
+    pbBuf[25] = 0; /* reserved for digital audio (see idx 15) */
+    ataH2BE_U16(&pbBuf[26], 0); /* (obsolete) maximum write speed */
+    ataH2BE_U16(&pbBuf[28], 0); /* (obsolete) current write speed */
+    ataH2BE_U16(&pbBuf[30], 0); /* copy management revision supported 0=no CSS */
+    pbBuf[32] = 0; /* reserved */
+    pbBuf[33] = 0; /* reserved */
+    pbBuf[34] = 0; /* reserved */
+    pbBuf[35] = 1; /* rotation control CAV */
+    ataH2BE_U16(&pbBuf[36], 0); /* current write speed */
+    ataH2BE_U16(&pbBuf[38], 0); /* number of write speed performance descriptors */
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiRequestSenseSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    memset(pbBuf, '\0', s->cbElementaryTransfer);
+    memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiMechanismStatusSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 8);
+    ataH2BE_U16(pbBuf, 0);
+    /* no current LBA */
+    pbBuf[2] = 0;
+    pbBuf[3] = 0;
+    pbBuf[4] = 0;
+    pbBuf[5] = 1;
+    ataH2BE_U16(pbBuf + 6, 0);
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiReadTOCNormalSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer), *q, iStartTrack;
+    bool fMSF;
+    uint32_t cbSize;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    fMSF = (s->aATAPICmd[1] >> 1) & 1;
+    iStartTrack = s->aATAPICmd[6];
+    if (iStartTrack > 1 && iStartTrack != 0xaa)
+    {
+        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return false;
+    }
+    q = pbBuf + 2;
+    *q++ = 1; /* first session */
+    *q++ = 1; /* last session */
+    if (iStartTrack <= 1)
+    {
+        *q++ = 0; /* reserved */
+        *q++ = 0x14; /* ADR, control */
+        *q++ = 1;    /* track number */
+        *q++ = 0; /* reserved */
+        if (fMSF)
+        {
+            *q++ = 0; /* reserved */
+            ataLBA2MSF(q, 0);
+            q += 3;
+        }
+        else
+        {
+            /* sector 0 */
+            ataH2BE_U32(q, 0);
+            q += 4;
+        }
+    }
+    /* lead out track */
+    *q++ = 0; /* reserved */
+    *q++ = 0x14; /* ADR, control */
+    *q++ = 0xaa; /* track number */
+    *q++ = 0; /* reserved */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, s->cTotalSectors);
+        q += 3;
+    }
+    else
+    {
+        ataH2BE_U32(q, s->cTotalSectors);
+        q += 4;
+    }
+    cbSize = q - pbBuf;
+    ataH2BE_U16(pbBuf, cbSize - 2);
+    if (cbSize < s->cbTotalTransfer)
+        s->cbTotalTransfer = cbSize;
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiReadTOCMultiSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
+    bool fMSF;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    Assert(s->cbElementaryTransfer <= 12);
+    fMSF = (s->aATAPICmd[1] >> 1) & 1;
+    /* multi session: only a single session defined */
+/** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R) with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being able to figure out whether numbers are in BCD or hex. */
+    memset(pbBuf, 0, 12);
+    pbBuf[1] = 0x0a;
+    pbBuf[2] = 0x01;
+    pbBuf[3] = 0x01;
+    pbBuf[5] = 0x14; /* ADR, control */
+    pbBuf[6] = 1; /* first track in last complete session */
+    if (fMSF)
+    {
+        pbBuf[8] = 0; /* reserved */
+        ataLBA2MSF(&pbBuf[9], 0);
+    }
+    else
+    {
+        /* sector 0 */
+        ataH2BE_U32(pbBuf + 8, 0);
+    }
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static bool atapiReadTOCRawSS(AHCIATADevState *s)
+{
+    uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer), *q, iStartTrack;
+    bool fMSF;
+    uint32_t cbSize;
+
+    Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+    fMSF = (s->aATAPICmd[1] >> 1) & 1;
+    iStartTrack = s->aATAPICmd[6];
+
+    q = pbBuf + 2;
+    *q++ = 1; /* first session */
+    *q++ = 1; /* last session */
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa0; /* first track in program area */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    *q++ = 0;
+    *q++ = 1; /* first track */
+    *q++ = 0x00; /* disk type CD-DA or CD data */
+    *q++ = 0;
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa1; /* last track in program area */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    *q++ = 0;
+    *q++ = 1; /* last track */
+    *q++ = 0;
+    *q++ = 0;
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa2; /* lead-out */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, s->cTotalSectors);
+        q += 3;
+    }
+    else
+    {
+        ataH2BE_U32(q, s->cTotalSectors);
+        q += 4;
+    }
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* ADR, control */
+    *q++ = 0;    /* track number */
+    *q++ = 1;    /* point */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, 0);
+        q += 3;
+    }
+    else
+    {
+        /* sector 0 */
+        ataH2BE_U32(q, 0);
+        q += 4;
+    }
+
+    cbSize = q - pbBuf;
+    ataH2BE_U16(pbBuf, cbSize - 2);
+    if (cbSize < s->cbTotalTransfer)
+        s->cbTotalTransfer = cbSize;
+    s->iSourceSink = ATAFN_SS_NULL;
+    atapiCmdOK(s);
+    return false;
+}
+
+
+static void atapiParseCmdVirtualATAPI(AHCIATADevState *s)
+{
+    const uint8_t *pbPacket;
+    uint8_t *pbBuf;
+    uint32_t cbMax;
+
+    pbPacket = s->aATAPICmd;
+    pbBuf = s->CTXALLSUFF(pbIOBuffer);
+    switch (pbPacket[0])
+    {
+        case SCSI_TEST_UNIT_READY:
+            if (s->cNotifiedMediaChange > 0)
+            {
+                if (s->cNotifiedMediaChange-- > 2)
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                else
+                    atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+            }
+            else if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+                atapiCmdOK(s);
+            else
+                atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+            break;
+        case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+            break;
+        case SCSI_MODE_SENSE_10:
+            {
+                uint8_t uPageControl, uPageCode;
+                cbMax = ataBE2H_U16(pbPacket + 7);
+                uPageControl = pbPacket[2] >> 6;
+                uPageCode = pbPacket[2] & 0x3f;
+                switch (uPageControl)
+                {
+                    case SCSI_PAGECONTROL_CURRENT:
+                        switch (uPageCode)
+                        {
+                            case SCSI_MODEPAGE_ERROR_RECOVERY:
+                                ataStartTransfer(s, RT_MIN(cbMax, 16), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
+                                break;
+                            case SCSI_MODEPAGE_CD_STATUS:
+                                ataStartTransfer(s, RT_MIN(cbMax, 40), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
+                                break;
+                            default:
+                                goto error_cmd;
+                        }
+                        break;
+                    case SCSI_PAGECONTROL_CHANGEABLE:
+                        goto error_cmd;
+                    case SCSI_PAGECONTROL_DEFAULT:
+                        goto error_cmd;
+                    default:
+                    case SCSI_PAGECONTROL_SAVED:
+                        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+                        break;
+                }
+            }
+            break;
+        case SCSI_REQUEST_SENSE:
+            cbMax = pbPacket[4];
+            ataStartTransfer(s, RT_MIN(cbMax, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+            break;
+        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+            if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+            {
+                if (pbPacket[4] & 1)
+                    s->pDrvMount->pfnLock(s->pDrvMount);
+                else
+                    s->pDrvMount->pfnUnlock(s->pDrvMount);
+                atapiCmdOK(s);
+            }
+            else
+                atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+            break;
+        case SCSI_READ_10:
+        case SCSI_READ_12:
+            {
+                uint32_t cSectors, iATAPILBA;
+
+                if (s->cNotifiedMediaChange > 0)
+                {
+                    s->cNotifiedMediaChange-- ;
+                    atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+                {
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                if (pbPacket[0] == SCSI_READ_10)
+                    cSectors = ataBE2H_U16(pbPacket + 7);
+                else
+                    cSectors = ataBE2H_U32(pbPacket + 6);
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (cSectors == 0)
+                {
+                    atapiCmdOK(s);
+                    break;
+                }
+                if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on reading from places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                atapiReadSectors(s, iATAPILBA, cSectors, 2048);
+            }
+            break;
+        case SCSI_READ_CD:
+            {
+                uint32_t cSectors, iATAPILBA;
+
+                if (s->cNotifiedMediaChange > 0)
+                {
+                    s->cNotifiedMediaChange-- ;
+                    atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+                {
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (cSectors == 0)
+                {
+                    atapiCmdOK(s);
+                    break;
+                }
+                if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on reading from places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                switch (pbPacket[9] & 0xf8)
+                {
+                    case 0x00:
+                        /* nothing */
+                        atapiCmdOK(s);
+                        break;
+                    case 0x10:
+                        /* normal read */
+                        atapiReadSectors(s, iATAPILBA, cSectors, 2048);
+                        break;
+                    case 0xf8:
+                        /* read all data */
+                        atapiReadSectors(s, iATAPILBA, cSectors, 2352);
+                        break;
+                    default:
+                        LogRel(("AHCI ATA: LUN#%d: CD-ROM sector format not supported\n", s->iLUN));
+                        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                        break;
+                }
+            }
+            break;
+        case SCSI_SEEK_10:
+            {
+                uint32_t iATAPILBA;
+                if (s->cNotifiedMediaChange > 0)
+                {
+                    s->cNotifiedMediaChange-- ;
+                    atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+                {
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (iATAPILBA > s->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on seeking to places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", s->iLUN, (uint64_t)iATAPILBA));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                atapiCmdOK(s);
+                ataSetStatus(s, ATA_STAT_SEEK); /* Linux expects this. */
+            }
+            break;
+        case SCSI_START_STOP_UNIT:
+            {
+                int rc = VINF_SUCCESS;
+                switch (pbPacket[4] & 3)
+                {
+                    case 0: /* 00 - Stop motor */
+                    case 1: /* 01 - Start motor */
+                        break;
+                    case 2: /* 10 - Eject media */
+                        /* This must be done from EMT. */
+                        {
+                        PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+                        PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+                        PVMREQ pReq;
+
+                        PDMCritSectLeave(&pCtl->lock);
+                        rc = VMR3ReqCall(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY, &pReq, RT_INDEFINITE_WAIT,
+                                         (PFNRT)s->pDrvMount->pfnUnmount, 2, s->pDrvMount, false);
+                        AssertReleaseRC(rc);
+                        VMR3ReqFree(pReq);
+                        {
+                            STAM_PROFILE_START(&pCtl->StatLockWait, a);
+                            PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+                            STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+                        }
+                        }
+                        break;
+                    case 3: /* 11 - Load media */
+                        /** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
+                        break;
+                }
+                if (RT_SUCCESS(rc))
+                    atapiCmdOK(s);
+                else
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+            }
+            break;
+        case SCSI_MECHANISM_STATUS:
+            {
+                cbMax = ataBE2H_U16(pbPacket + 8);
+                ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MECHANISM_STATUS, true);
+            }
+            break;
+        case SCSI_READ_TOC_PMA_ATIP:
+            {
+                uint8_t format;
+
+                if (s->cNotifiedMediaChange > 0)
+                {
+                    s->cNotifiedMediaChange-- ;
+                    atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+                {
+                    atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                cbMax = ataBE2H_U16(pbPacket + 7);
+                /* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
+                 * but Linux kernel uses offset 9 (topmost 2 bits). Hope that
+                 * the other field is clear... */
+                format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
+                switch (format)
+                {
+                    case 0:
+                        ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
+                        break;
+                    case 1:
+                        ataStartTransfer(s, RT_MIN(cbMax, 12), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
+                        break;
+                    case 2:
+                        ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
+                        break;
+                    default:
+                    error_cmd:
+                        atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                        break;
+                }
+            }
+            break;
+        case SCSI_READ_CAPACITY:
+            if (s->cNotifiedMediaChange > 0)
+            {
+                s->cNotifiedMediaChange-- ;
+                atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+            {
+                atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            ataStartTransfer(s, 8, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_CAPACITY, true);
+            break;
+        case SCSI_READ_DISC_INFORMATION:
+            if (s->cNotifiedMediaChange > 0)
+            {
+                s->cNotifiedMediaChange-- ;
+                atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+            {
+                atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            ataStartTransfer(s, RT_MIN(cbMax, 34), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DISC_INFORMATION, true);
+            break;
+        case SCSI_READ_TRACK_INFORMATION:
+            if (s->cNotifiedMediaChange > 0)
+            {
+                s->cNotifiedMediaChange-- ;
+                atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+            {
+                atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION, true);
+            break;
+        case SCSI_GET_CONFIGURATION:
+            /* No media change stuff here, it can confuse Linux guests. */
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            ataStartTransfer(s, RT_MIN(cbMax, 32), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_CONFIGURATION, true);
+            break;
+        case SCSI_INQUIRY:
+            cbMax = ataBE2H_U16(pbPacket + 3);
+            ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_INQUIRY, true);
+            break;
+        default:
+            atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+    }
+}
+
+
+/*
+ * Parse ATAPI commands, passing them directly to the CD/DVD drive.
+ */
+static void atapiParseCmdPassthrough(AHCIATADevState *s)
+{
+    const uint8_t *pbPacket;
+    uint8_t *pbBuf;
+    uint32_t cSectors, iATAPILBA;
+    uint32_t cbTransfer = 0;
+    PDMBLOCKTXDIR uTxDir = PDMBLOCKTXDIR_NONE;
+
+    pbPacket = s->aATAPICmd;
+    pbBuf = s->CTXALLSUFF(pbIOBuffer);
+    switch (pbPacket[0])
+    {
+        case SCSI_BLANK:
+            goto sendcmd;
+        case SCSI_CLOSE_TRACK_SESSION:
+            goto sendcmd;
+        case SCSI_ERASE_10:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            Log2(("ATAPI PT: lba %d\n", iATAPILBA));
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_FORMAT_UNIT:
+            cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_GET_CONFIGURATION:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            if (ASMAtomicReadU32(&s->MediaEventStatus) != ATA_EVENT_STATUS_UNCHANGED)
+            {
+                ataStartTransfer(s, RT_MIN(cbTransfer, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+                break;
+            }
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_GET_PERFORMANCE:
+            cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_INQUIRY:
+            cbTransfer = ataBE2H_U16(pbPacket + 3);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_LOAD_UNLOAD_MEDIUM:
+            goto sendcmd;
+        case SCSI_MECHANISM_STATUS:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_MODE_SELECT_10:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_MODE_SENSE_10:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_PAUSE_RESUME:
+            goto sendcmd;
+        case SCSI_PLAY_AUDIO_10:
+            goto sendcmd;
+        case SCSI_PLAY_AUDIO_12:
+            goto sendcmd;
+        case SCSI_PLAY_AUDIO_MSF:
+            goto sendcmd;
+        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+            /** @todo do not forget to unlock when a VM is shut down */
+            goto sendcmd;
+        case SCSI_READ_10:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cSectors = ataBE2H_U16(pbPacket + 7);
+            Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = cSectors * s->cbATAPISector;
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_12:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cSectors = ataBE2H_U32(pbPacket + 6);
+            Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = cSectors * s->cbATAPISector;
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_BUFFER:
+            cbTransfer = ataBE2H_U24(pbPacket + 6);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_BUFFER_CAPACITY:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_CAPACITY:
+            cbTransfer = 8;
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_CD:
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = ataBE2H_U24(pbPacket + 6) / s->cbATAPISector * s->cbATAPISector;
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_CD_MSF:
+            cSectors = ataMSF2LBA(pbPacket + 6) - ataMSF2LBA(pbPacket + 3);
+            if (cSectors > 32)
+                cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = cSectors * s->cbATAPISector;
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_DISC_INFORMATION:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_DVD_STRUCTURE:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_FORMAT_CAPACITIES:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_SUBCHANNEL:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_TOC_PMA_ATIP:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_READ_TRACK_INFORMATION:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_REPAIR_TRACK:
+            goto sendcmd;
+        case SCSI_REPORT_KEY:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_REQUEST_SENSE:
+            cbTransfer = pbPacket[4];
+            if ((s->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
+            {
+                ataStartTransfer(s, RT_MIN(cbTransfer, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+                break;
+            }
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_RESERVE_TRACK:
+            goto sendcmd;
+        case SCSI_SCAN:
+            goto sendcmd;
+        case SCSI_SEEK_10:
+            goto sendcmd;
+        case SCSI_SEND_CUE_SHEET:
+            cbTransfer = ataBE2H_U24(pbPacket + 6);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_SEND_DVD_STRUCTURE:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_SEND_EVENT:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_SEND_KEY:
+            cbTransfer = ataBE2H_U16(pbPacket + 8);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_SEND_OPC_INFORMATION:
+            cbTransfer = ataBE2H_U16(pbPacket + 7);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_SET_CD_SPEED:
+            goto sendcmd;
+        case SCSI_SET_READ_AHEAD:
+            goto sendcmd;
+        case SCSI_SET_STREAMING:
+            cbTransfer = ataBE2H_U16(pbPacket + 9);
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_START_STOP_UNIT:
+            goto sendcmd;
+        case SCSI_STOP_PLAY_SCAN:
+            goto sendcmd;
+        case SCSI_SYNCHRONIZE_CACHE:
+            goto sendcmd;
+        case SCSI_TEST_UNIT_READY:
+            goto sendcmd;
+        case SCSI_VERIFY_10:
+            goto sendcmd;
+        case SCSI_WRITE_10:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cSectors = ataBE2H_U16(pbPacket + 7);
+            Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+#if 0
+            /* The sector size is determined by the async I/O thread. */
+            s->cbATAPISector = 0;
+            /* Preliminary, will be corrected once the sector size is known. */
+            cbTransfer = cSectors;
+#else
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = cSectors * s->cbATAPISector;
+#endif
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_WRITE_12:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cSectors = ataBE2H_U32(pbPacket + 6);
+            Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+#if 0
+            /* The sector size is determined by the async I/O thread. */
+            s->cbATAPISector = 0;
+            /* Preliminary, will be corrected once the sector size is known. */
+            cbTransfer = cSectors;
+#else
+            s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+            cbTransfer = cSectors * s->cbATAPISector;
+#endif
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_WRITE_AND_VERIFY_10:
+            iATAPILBA = ataBE2H_U32(pbPacket + 2);
+            cSectors = ataBE2H_U16(pbPacket + 7);
+            Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+            /* The sector size is determined by the async I/O thread. */
+            s->cbATAPISector = 0;
+            /* Preliminary, will be corrected once the sector size is known. */
+            cbTransfer = cSectors;
+            uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+            goto sendcmd;
+        case SCSI_WRITE_BUFFER:
+            switch (pbPacket[1] & 0x1f)
+            {
+                case 0x04: /* download microcode */
+                case 0x05: /* download microcode and save */
+                case 0x06: /* download microcode with offsets */
+                case 0x07: /* download microcode with offsets and save */
+                case 0x0e: /* download microcode with offsets and defer activation */
+                case 0x0f: /* activate deferred microcode */
+                    LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough command attempted to update firmware, blocked\n", s->iLUN));
+                    atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                    break;
+                default:
+                    cbTransfer = ataBE2H_U16(pbPacket + 6);
+                    uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+                    goto sendcmd;
+            }
+            break;
+        case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
+            cbTransfer = ataBE2H_U32(pbPacket + 6);
+            uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+            goto sendcmd;
+        case SCSI_REZERO_UNIT:
+            /* Obsolete command used by cdrecord. What else would one expect?
+             * This command is not sent to the drive, it is handled internally,
+             * as the Linux kernel doesn't like it (message "scsi: unknown
+             * opcode 0x01" in syslog) and replies with a sense code of 0,
+             * which sends cdrecord to an endless loop. */
+            atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+        default:
+            LogRel(("AHCI ATA: LUN#%d: passthrough unimplemented for command %#x\n", s->iLUN, pbPacket[0]));
+            atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+        sendcmd:
+            /* Send a command to the drive, passing data in/out as required. */
+            Log2(("ATAPI PT: max size %d\n", cbTransfer));
+            Assert(cbTransfer <= s->cbIOBuffer);
+            if (cbTransfer == 0)
+                uTxDir = PDMBLOCKTXDIR_NONE;
+            ataStartTransfer(s, cbTransfer, uTxDir, ATAFN_BT_ATAPI_PASSTHROUGH_CMD, ATAFN_SS_ATAPI_PASSTHROUGH, true);
+    }
+}
+
+
+static void atapiParseCmd(AHCIATADevState *s)
+{
+    const uint8_t *pbPacket;
+
+    pbPacket = s->aATAPICmd;
+#ifdef DEBUG
+    Log(("%s: LUN#%d DMA=%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0], SCSICmdText(pbPacket[0])));
+#else /* !DEBUG */
+    Log(("%s: LUN#%d DMA=%d CMD=%#04x\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0]));
+#endif /* !DEBUG */
+    Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, s->uATARegLCyl | (s->uATARegHCyl << 8), ATAPI_PACKET_SIZE, pbPacket));
+
+    if (s->fATAPIPassthrough)
+        atapiParseCmdPassthrough(s);
+    else
+        atapiParseCmdVirtualATAPI(s);
+}
+
+
+static bool ataPacketSS(AHCIATADevState *s)
+{
+    s->fDMA = !!(s->uATARegFeature & 1);
+    memcpy(s->aATAPICmd, s->CTXALLSUFF(pbIOBuffer), ATAPI_PACKET_SIZE);
+    s->uTxDir = PDMBLOCKTXDIR_NONE;
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    atapiParseCmd(s);
+    return false;
+}
+
+#if 0
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
+ * from now on, regardless if there was a medium inserted or not.
+ */
+static void ataMediumRemoved(AHCIATADevState *s)
+{
+    ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_MEDIA_REMOVED);
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
+ * there was already a medium inserted, don't forget to send the "medium
+ * removed" event first.
+ */
+static void ataMediumInserted(AHCIATADevState *s)
+{
+    uint32_t OldStatus, NewStatus;
+    do
+    {
+        OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+        switch (OldStatus)
+        {
+            case ATA_EVENT_STATUS_MEDIA_CHANGED:
+            case ATA_EVENT_STATUS_MEDIA_REMOVED:
+                /* no change, we will send "medium removed" + "medium inserted" */
+                NewStatus = ATA_EVENT_STATUS_MEDIA_CHANGED;
+                break;
+            default:
+                NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+                break;
+        }
+    } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+}
+
+
+/**
+ * Called when a media is mounted.
+ *
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ataMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+    AHCIATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
+    Log(("%s: changing LUN#%d\n", __FUNCTION__, pIf->iLUN));
+
+    /* Ignore the call if we're called while being attached. */
+    if (!pIf->pDrvBlock)
+        return;
+
+    if (pIf->fATAPI)
+        pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
+    else
+        pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
+
+    /* Report media changed in TEST UNIT and other (probably incorrect) places. */
+    if (pIf->cNotifiedMediaChange < 2)
+        pIf->cNotifiedMediaChange = 2;
+    ataMediumInserted(pIf);
+}
+
+/**
+ * Called when a media is unmounted
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ataUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+    AHCIATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
+    Log(("%s:\n", __FUNCTION__));
+    pIf->cTotalSectors = 0;
+
+    /*
+     * Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
+     * However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
+     * So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
+     * present and 2 in which it is changed.
+     */
+    pIf->cNotifiedMediaChange = 4;
+    ataMediumRemoved(pIf);
+}
+#endif
+
+static void ataPacketBT(AHCIATADevState *s)
+{
+    s->cbElementaryTransfer = s->cbTotalTransfer;
+    s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_CD;
+    Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+    ataSetStatusValue(s, ATA_STAT_READY);
+}
+
+
+static void ataResetDevice(AHCIATADevState *s)
+{
+    s->cMultSectors = ATA_MAX_MULT_SECTORS;
+    s->cNotifiedMediaChange = 0;
+    ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
+    ataUnsetIRQ(s);
+
+    s->uATARegSelect = 0x20;
+    ataSetStatusValue(s, ATA_STAT_READY);
+    ataSetSignature(s);
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    s->iIOBufferPIODataStart = 0;
+    s->iIOBufferPIODataEnd = 0;
+    s->iBeginTransfer = ATAFN_BT_NULL;
+    s->iSourceSink = ATAFN_SS_NULL;
+    s->fATAPITransfer = false;
+    s->uATATransferMode = ATA_MODE_UDMA | 2; /* AHCI supports only up to UDMA2 */
+
+    s->uATARegFeature = 0;
+}
+
+
+static bool ataExecuteDeviceDiagnosticSS(AHCIATADevState *s)
+{
+    ataSetSignature(s);
+    if (s->fATAPI)
+        ataSetStatusValue(s, 0); /* NOTE: READY is _not_ set */
+    else
+        ataSetStatusValue(s, ATA_STAT_READY);
+    s->uATARegError = 0x01;
+    return false;
+}
+
+
+static void ataParseCmd(AHCIATADevState *s, uint8_t cmd)
+{
+#ifdef DEBUG
+    Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, cmd, ATACmdText(cmd)));
+#else /* !DEBUG */
+    Log(("%s: LUN#%d CMD=%#04x\n", __FUNCTION__, s->iLUN, cmd));
+#endif /* !DEBUG */
+    s->fLBA48 = false;
+    s->fDMA = false;
+    if (cmd == ATA_IDLE_IMMEDIATE)
+    {
+        /* Detect Linux timeout recovery, first tries IDLE IMMEDIATE (which
+         * would overwrite the failing command unfortunately), then RESET. */
+        int32_t uCmdWait = -1;
+        uint64_t uNow = RTTimeNanoTS();
+        if (s->u64CmdTS)
+            uCmdWait = (uNow - s->u64CmdTS) / 1000;
+        LogRel(("AHCI ATA: LUN#%d: IDLE IMMEDIATE, CmdIf=%#04x (%d usec ago)\n",
+                s->iLUN, s->uATARegCommand, uCmdWait));
+    }
+    s->uATARegCommand = cmd;
+    switch (cmd)
+    {
+        case ATA_IDENTIFY_DEVICE:
+            if (s->pDrvBlock && !s->fATAPI)
+                ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
+            else
+            {
+                if (s->fATAPI)
+                    ataSetSignature(s);
+                ataCmdError(s, ABRT_ERR);
+                ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            }
+            break;
+        case ATA_INITIALIZE_DEVICE_PARAMETERS:
+        case ATA_RECALIBRATE:
+            ataCmdOK(s, ATA_STAT_SEEK);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_SET_MULTIPLE_MODE:
+            if (    s->uATARegNSector != 0
+                &&  (   s->uATARegNSector > ATA_MAX_MULT_SECTORS
+                     || (s->uATARegNSector & (s->uATARegNSector - 1)) != 0))
+            {
+                ataCmdError(s, ABRT_ERR);
+            }
+            else
+            {
+                Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
+                s->cMultSectors = s->uATARegNSector;
+                ataCmdOK(s, 0);
+            }
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_READ_VERIFY_SECTORS_EXT:
+            s->fLBA48 = true;
+        case ATA_READ_VERIFY_SECTORS:
+        case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+            /* do sector number check ? */
+            ataCmdOK(s, 0);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_READ_SECTORS_EXT:
+            s->fLBA48 = true;
+        case ATA_READ_SECTORS:
+        case ATA_READ_SECTORS_WITHOUT_RETRIES:
+            if (!s->pDrvBlock)
+                goto abort_cmd;
+            s->cSectorsPerIRQ = 1;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+            break;
+        case ATA_WRITE_SECTORS_EXT:
+            s->fLBA48 = true;
+        case ATA_WRITE_SECTORS:
+        case ATA_WRITE_SECTORS_WITHOUT_RETRIES:
+            s->cSectorsPerIRQ = 1;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+            break;
+        case ATA_READ_MULTIPLE_EXT:
+            s->fLBA48 = true;
+        case ATA_READ_MULTIPLE:
+            if (!s->cMultSectors)
+                goto abort_cmd;
+            s->cSectorsPerIRQ = s->cMultSectors;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+            break;
+        case ATA_WRITE_MULTIPLE_EXT:
+            s->fLBA48 = true;
+        case ATA_WRITE_MULTIPLE:
+            if (!s->cMultSectors)
+                goto abort_cmd;
+            s->cSectorsPerIRQ = s->cMultSectors;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+            break;
+        case ATA_READ_DMA_EXT:
+            s->fLBA48 = true;
+        case ATA_READ_DMA:
+        case ATA_READ_DMA_WITHOUT_RETRIES:
+            if (!s->pDrvBlock)
+                goto abort_cmd;
+            s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+            s->fDMA = true;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+            break;
+        case ATA_WRITE_DMA_EXT:
+            s->fLBA48 = true;
+        case ATA_WRITE_DMA:
+        case ATA_WRITE_DMA_WITHOUT_RETRIES:
+            if (!s->pDrvBlock)
+                goto abort_cmd;
+            s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+            s->fDMA = true;
+            ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+            break;
+        case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+            s->fLBA48 = true;
+            ataSetSector(s, s->cTotalSectors - 1);
+            ataCmdOK(s, 0);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_READ_NATIVE_MAX_ADDRESS:
+            ataSetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
+            ataCmdOK(s, 0);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_CHECK_POWER_MODE:
+            s->uATARegNSector = 0xff; /* drive active or idle */
+            ataCmdOK(s, 0);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_SET_FEATURES:
+            Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
+            if (!s->pDrvBlock)
+                goto abort_cmd;
+            switch (s->uATARegFeature)
+            {
+                case 0x02: /* write cache enable */
+                    Log2(("%s: write cache enable\n", __FUNCTION__));
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                case 0xaa: /* read look-ahead enable */
+                    Log2(("%s: read look-ahead enable\n", __FUNCTION__));
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                case 0x55: /* read look-ahead disable */
+                    Log2(("%s: read look-ahead disable\n", __FUNCTION__));
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                case 0xcc: /* reverting to power-on defaults enable */
+                    Log2(("%s: revert to power-on defaults enable\n", __FUNCTION__));
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                case 0x66: /* reverting to power-on defaults disable */
+                    Log2(("%s: revert to power-on defaults disable\n", __FUNCTION__));
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                case 0x82: /* write cache disable */
+                    Log2(("%s: write cache disable\n", __FUNCTION__));
+                    /* As per the ATA/ATAPI-6 specs, a write cache disable
+                     * command MUST flush the write buffers to disc. */
+                    ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+                    break;
+                case 0x03: { /* set transfer mode */
+                    Log2(("%s: transfer mode %#04x\n", __FUNCTION__, s->uATARegNSector));
+                    switch (s->uATARegNSector & 0xf8)
+                    {
+                        case 0x00: /* PIO default */
+                        case 0x08: /* PIO mode */
+                            break;
+                        case ATA_MODE_MDMA: /* MDMA mode */
+                            s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_MDMA_MODE_MAX);
+                            break;
+                        case ATA_MODE_UDMA: /* UDMA mode */
+                            s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_UDMA_MODE_MAX);
+                            break;
+                        default:
+                            goto abort_cmd;
+                    }
+                    ataCmdOK(s, ATA_STAT_SEEK);
+                    ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+                    break;
+                }
+                default:
+                    goto abort_cmd;
+            }
+            /*
+             * OS/2 workarond:
+             * The OS/2 IDE driver from MCP2 appears to rely on the feature register being
+             * reset here. According to the specification, this is a driver bug as the register
+             * contents are undefined after the call. This means we can just as well reset it.
+             */
+            s->uATARegFeature = 0;
+            break;
+        case ATA_FLUSH_CACHE_EXT:
+        case ATA_FLUSH_CACHE:
+            if (!s->pDrvBlock || s->fATAPI)
+                goto abort_cmd;
+            ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+            break;
+        case ATA_STANDBY_IMMEDIATE:
+            ataCmdOK(s, 0);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+        case ATA_IDLE_IMMEDIATE:
+            LogRel(("AHCI ATA: LUN#%d: aborting current command\n", s->iLUN));
+            ataAbortCurrentCommand(s, false);
+            break;
+            /* ATAPI commands */
+        case ATA_IDENTIFY_PACKET_DEVICE:
+            if (s->fATAPI)
+                ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
+            else
+            {
+                ataCmdError(s, ABRT_ERR);
+                ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            }
+            break;
+        case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
+            ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC, false);
+            break;
+        case ATA_DEVICE_RESET:
+            if (!s->fATAPI)
+                goto abort_cmd;
+            LogRel(("AHCI ATA: LUN#%d: performing device RESET\n", s->iLUN));
+            ataAbortCurrentCommand(s, true);
+            break;
+        case ATA_PACKET:
+            if (!s->fATAPI)
+                goto abort_cmd;
+            /* overlapping commands not supported */
+            if (s->uATARegFeature & 0x02)
+                goto abort_cmd;
+            ataStartTransfer(s, ATAPI_PACKET_SIZE, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
+            break;
+        default:
+        abort_cmd:
+            ataCmdError(s, ABRT_ERR);
+            ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+            break;
+    }
+}
+
+
+/**
+ * Waits for a particular async I/O thread to complete whatever it
+ * is doing at the moment.
+ *
+ * @returns true on success.
+ * @returns false when the thread is still processing.
+ * @param   pThis       Pointer to the controller data.
+ * @param   cMillies    How long to wait (total).
+ */
+static bool ataWaitForAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, unsigned cMillies)
+{
+    uint64_t        u64Start;
+
+    /*
+     * Wait for any pending async operation to finish
+     */
+    u64Start = RTTimeMilliTS();
+    for (;;)
+    {
+        if (ataAsyncIOIsIdle(pCtl, false))
+            return true;
+        if (RTTimeMilliTS() - u64Start >= cMillies)
+            break;
+
+        /* Sleep for a bit. */
+        RTThreadSleep(100);
+    }
+
+    return false;
+}
+
+#endif /* IN_RING3 */
+
+static int ataIOPortWriteU8(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: write addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+    addr &= 7;
+    switch (addr)
+    {
+        case 0:
+            break;
+        case 1: /* feature register */
+            /* NOTE: data is written to the two drives */
+            pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[0].uATARegFeatureHOB = pCtl->aIfs[0].uATARegFeature;
+            pCtl->aIfs[1].uATARegFeatureHOB = pCtl->aIfs[1].uATARegFeature;
+            pCtl->aIfs[0].uATARegFeature = val;
+            pCtl->aIfs[1].uATARegFeature = val;
+            break;
+        case 2: /* sector count */
+            pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[0].uATARegNSectorHOB = pCtl->aIfs[0].uATARegNSector;
+            pCtl->aIfs[1].uATARegNSectorHOB = pCtl->aIfs[1].uATARegNSector;
+            pCtl->aIfs[0].uATARegNSector = val;
+            pCtl->aIfs[1].uATARegNSector = val;
+            break;
+        case 3: /* sector number */
+            pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[0].uATARegSectorHOB = pCtl->aIfs[0].uATARegSector;
+            pCtl->aIfs[1].uATARegSectorHOB = pCtl->aIfs[1].uATARegSector;
+            pCtl->aIfs[0].uATARegSector = val;
+            pCtl->aIfs[1].uATARegSector = val;
+            break;
+        case 4: /* cylinder low */
+            pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[0].uATARegLCylHOB = pCtl->aIfs[0].uATARegLCyl;
+            pCtl->aIfs[1].uATARegLCylHOB = pCtl->aIfs[1].uATARegLCyl;
+            pCtl->aIfs[0].uATARegLCyl = val;
+            pCtl->aIfs[1].uATARegLCyl = val;
+            break;
+        case 5: /* cylinder high */
+            pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+            pCtl->aIfs[0].uATARegHCylHOB = pCtl->aIfs[0].uATARegHCyl;
+            pCtl->aIfs[1].uATARegHCylHOB = pCtl->aIfs[1].uATARegHCyl;
+            pCtl->aIfs[0].uATARegHCyl = val;
+            pCtl->aIfs[1].uATARegHCyl = val;
+            break;
+        case 6: /* drive/head */
+            pCtl->aIfs[0].uATARegSelect = (val & ~0x10) | 0xa0;
+            pCtl->aIfs[1].uATARegSelect = (val | 0x10) | 0xa0;
+            if (((val >> 4) & 1) != pCtl->iSelectedIf)
+            {
+                PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+
+                /* select another drive */
+                pCtl->iSelectedIf = (val >> 4) & 1;
+                /* The IRQ line is multiplexed between the two drives, so
+                 * update the state when switching to another drive. Only need
+                 * to update interrupt line if it is enabled and there is a
+                 * state change. */
+                if (    !(pCtl->aIfs[pCtl->iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
+                    &&  (   pCtl->aIfs[pCtl->iSelectedIf].fIrqPending
+                         !=  pCtl->aIfs[pCtl->iSelectedIf ^ 1].fIrqPending))
+                {
+                    if (pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+                    {
+                        Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+                        /* The BMDMA unit unconditionally sets BM_STATUS_INT if
+                         * the interrupt line is asserted. It monitors the line
+                         * for a rising edge. */
+                        pCtl->BmDma.u8Status |= BM_STATUS_INT;
+                        if (pCtl->irq == 16)
+                            PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 1);
+                        else
+                            PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
+                    }
+                    else
+                    {
+                        Log2(("%s: LUN#%d deasserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+                        if (pCtl->irq == 16)
+                            PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 0);
+                        else
+                            PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
+                    }
+                }
+            }
+            break;
+        default:
+        case 7: /* command */
+            /* ignore commands to non existant slave */
+            if (pCtl->iSelectedIf && !pCtl->aIfs[pCtl->iSelectedIf].pDrvBlock)
+                break;
+#ifndef IN_RING3
+            /* Don't do anything complicated in GC */
+            return VINF_IOM_HC_IOPORT_WRITE;
+#else /* IN_RING3 */
+            ataParseCmd(&pCtl->aIfs[pCtl->iSelectedIf], val);
+#endif /* !IN_RING3 */
+    }
+    return VINF_SUCCESS;
+}
+
+
+static int ataIOPortReadU8(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
+{
+    AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+    uint32_t val;
+    bool fHOB;
+
+    fHOB = !!(s->uATARegDevCtl & (1 << 7));
+    switch (addr & 7)
+    {
+        case 0: /* data register */
+            val = 0xff;
+            break;
+        case 1: /* error register */
+            /* The ATA specification is very terse when it comes to specifying
+             * the precise effects of reading back the error/feature register.
+             * The error register (read-only) shares the register number with
+             * the feature register (write-only), so it seems that it's not
+             * necessary to support the usual HOB readback here. */
+            if (!s->pDrvBlock)
+                val = 0;
+            else
+                val = s->uATARegError;
+            break;
+        case 2: /* sector count */
+            if (!s->pDrvBlock)
+                val = 0;
+            else if (fHOB)
+                val = s->uATARegNSectorHOB;
+            else
+                val = s->uATARegNSector;
+            break;
+        case 3: /* sector number */
+            if (!s->pDrvBlock)
+                val = 0;
+            else if (fHOB)
+                val = s->uATARegSectorHOB;
+            else
+                val = s->uATARegSector;
+            break;
+        case 4: /* cylinder low */
+            if (!s->pDrvBlock)
+                val = 0;
+            else if (fHOB)
+                val = s->uATARegLCylHOB;
+            else
+                val = s->uATARegLCyl;
+            break;
+        case 5: /* cylinder high */
+            if (!s->pDrvBlock)
+                val = 0;
+            else if (fHOB)
+                val = s->uATARegHCylHOB;
+            else
+                val = s->uATARegHCyl;
+            break;
+        case 6: /* drive/head */
+            /* This register must always work as long as there is at least
+             * one drive attached to the controller. It is common between
+             * both drives anyway (completely identical content). */
+            if (!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock)
+                val = 0;
+            else
+                val = s->uATARegSelect;
+            break;
+        default:
+        case 7: /* primary status */
+        {
+            /* Counter for number of busy status seen in GC in a row. */
+            static unsigned cBusy = 0;
+
+            if (!s->pDrvBlock)
+                val = 0;
+            else
+                val = s->uATARegStatus;
+
+            /* Give the async I/O thread an opportunity to make progress,
+             * don't let it starve by guests polling frequently. EMT has a
+             * lower priority than the async I/O thread, but sometimes the
+             * host OS doesn't care. With some guests we are only allowed to
+             * be busy for about 5 milliseconds in some situations. Note that
+             * this is no guarantee for any other VBox thread getting
+             * scheduled, so this just lowers the CPU load a bit when drives
+             * are busy. It cannot help with timing problems. */
+            if (val & ATA_STAT_BUSY)
+            {
+#ifdef IN_RING3
+                cBusy = 0;
+                PDMCritSectLeave(&pCtl->lock);
+
+                RTThreadYield();
+
+                {
+                    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+                    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+                    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+                }
+
+                val = s->uATARegStatus;
+#else /* !IN_RING3 */
+                /* Cannot yield CPU in guest context. And switching to host
+                 * context for each and every busy status is too costly,
+                 * especially on SMP systems where we don't gain much by
+                 * yielding the CPU to someone else. */
+                if (++cBusy >= 20)
+                {
+                    cBusy = 0;
+                    return VINF_IOM_HC_IOPORT_READ;
+                }
+#endif /* !IN_RING3 */
+            }
+            else
+                cBusy = 0;
+            ataUnsetIRQ(s);
+            break;
+        }
+    }
+    Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+    *pu32 = val;
+    return VINF_SUCCESS;
+}
+
+
+static uint32_t ataStatusRead(PAHCIATACONTROLLER pCtl, uint32_t addr)
+{
+    AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+    uint32_t val;
+
+    if ((!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock) ||
+            (pCtl->iSelectedIf == 1 && !s->pDrvBlock))
+        val = 0;
+    else
+        val = s->uATARegStatus;
+    Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+    return val;
+}
+
+static int ataControlWrite(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+#ifndef IN_RING3
+    if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_RESET)
+        return VINF_IOM_HC_IOPORT_WRITE; /* The RESET stuff is too complicated for GC. */
+#endif /* !IN_RING3 */
+
+    Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+    /* RESET is common for both drives attached to a controller. */
+    if (!(pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
+            (val & ATA_DEVCTL_RESET))
+    {
+#ifdef IN_RING3
+        /* Software RESET low to high */
+        int32_t uCmdWait0 = -1, uCmdWait1 = -1;
+        uint64_t uNow = RTTimeNanoTS();
+        if (pCtl->aIfs[0].u64CmdTS)
+            uCmdWait0 = (uNow - pCtl->aIfs[0].u64CmdTS) / 1000;
+        if (pCtl->aIfs[1].u64CmdTS)
+            uCmdWait1 = (uNow - pCtl->aIfs[1].u64CmdTS) / 1000;
+        LogRel(("ATA: Ctl: RESET, DevSel=%d AIOIf=%d CmdIf0=%#04x (%d usec ago) CmdIf1=%#04x (%d usec ago)\n",
+                    pCtl->iSelectedIf, pCtl->iAIOIf,
+                    pCtl->aIfs[0].uATARegCommand, uCmdWait0,
+                    pCtl->aIfs[1].uATARegCommand, uCmdWait1));
+        pCtl->fReset = true;
+        /* Everything must be done after the reset flag is set, otherwise
+         * there are unavoidable races with the currently executing request
+         * (which might just finish in the mean time). */
+        pCtl->fChainedTransfer = false;
+        for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+        {
+            ataResetDevice(&pCtl->aIfs[i]);
+            /* The following cannot be done using ataSetStatusValue() since the
+             * reset flag is already set, which suppresses all status changes. */
+            pCtl->aIfs[i].uATARegStatus = ATA_STAT_BUSY | ATA_STAT_SEEK;
+            Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, pCtl->aIfs[i].iLUN, pCtl->aIfs[i].uATARegStatus));
+            pCtl->aIfs[i].uATARegError = 0x01;
+        }
+        ataAsyncIOClearRequests(pCtl);
+        Log2(("%s: Ctl: message to async I/O thread, resetA\n", __FUNCTION__));
+        if (val & ATA_DEVCTL_HOB)
+        {
+            val &= ~ATA_DEVCTL_HOB;
+            Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+        }
+        ataAsyncIOPutRequest(pCtl, &ataResetARequest);
+#else /* !IN_RING3 */
+        AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+    }
+    else if ((pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
+               !(val & ATA_DEVCTL_RESET))
+    {
+#ifdef IN_RING3
+        /* Software RESET high to low */
+        Log(("%s: deasserting RESET\n", __FUNCTION__));
+        Log2(("%s: Ctl: message to async I/O thread, resetC\n", __FUNCTION__));
+        if (val & ATA_DEVCTL_HOB)
+        {
+            val &= ~ATA_DEVCTL_HOB;
+            Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+        }
+        ataAsyncIOPutRequest(pCtl, &ataResetCRequest);
+#else /* !IN_RING3 */
+        AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+    }
+
+    /* Change of interrupt disable flag. Update interrupt line if interrupt
+     * is pending on the current interface. */
+    if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_DISABLE_IRQ
+        &&  pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+    {
+        if (!(val & ATA_DEVCTL_DISABLE_IRQ))
+        {
+            Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+            /* The BMDMA unit unconditionally sets BM_STATUS_INT if the
+             * interrupt line is asserted. It monitors the line for a rising
+             * edge. */
+            pCtl->BmDma.u8Status |= BM_STATUS_INT;
+            if (pCtl->irq == 16)
+                PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 1);
+            else
+                PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 1);
+        }
+        else
+        {
+            Log2(("%s: LUN#%d deasserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+            if (pCtl->irq == 16)
+                PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 0);
+            else
+                PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 0);
+        }
+    }
+
+    if (val & ATA_DEVCTL_HOB)
+        Log2(("%s: set HOB\n", __FUNCTION__));
+
+    pCtl->aIfs[0].uATARegDevCtl = val;
+    pCtl->aIfs[1].uATARegDevCtl = val;
+
+    return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+static void ataPIOTransfer(PAHCIATACONTROLLER pCtl)
+{
+    AHCIATADevState *s;
+
+    s = &pCtl->aIfs[pCtl->iAIOIf];
+    Log3(("%s: if=%p\n", __FUNCTION__, s));
+
+    if (s->cbTotalTransfer && s->iIOBufferCur > s->iIOBufferEnd)
+    {
+        LogRel(("AHCI ATA: LUN#%d: %s data in the middle of a PIO transfer - VERY SLOW\n", s->iLUN, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "loading" : "storing"));
+        /* Any guest OS that triggers this case has a pathetic ATA driver.
+         * In a real system it would block the CPU via IORDY, here we do it
+         * very similarly by not continuing with the current instruction
+         * until the transfer to/from the storage medium is completed. */
+        if (s->iSourceSink != ATAFN_SS_NULL)
+        {
+            bool fRedo;
+            uint8_t status = s->uATARegStatus;
+            ataSetStatusValue(s, ATA_STAT_BUSY);
+            Log2(("%s: calling source/sink function\n", __FUNCTION__));
+            fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+            pCtl->fRedo = fRedo;
+            if (RT_UNLIKELY(fRedo))
+                return;
+            ataSetStatusValue(s, status);
+            s->iIOBufferCur = 0;
+            s->iIOBufferEnd = s->cbElementaryTransfer;
+        }
+    }
+    if (s->cbTotalTransfer)
+    {
+        if (s->fATAPITransfer)
+            ataPIOTransferLimitATAPI(s);
+
+        if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+            s->cbElementaryTransfer = s->cbTotalTransfer;
+
+        Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+             __FUNCTION__, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
+             s->cbTotalTransfer, s->cbElementaryTransfer,
+             s->iIOBufferCur, s->iIOBufferEnd));
+        ataPIOTransferStart(s, s->iIOBufferCur, s->cbElementaryTransfer);
+        s->cbTotalTransfer -= s->cbElementaryTransfer;
+        s->iIOBufferCur += s->cbElementaryTransfer;
+
+        if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+            s->cbElementaryTransfer = s->cbTotalTransfer;
+    }
+    else
+        ataPIOTransferStop(s);
+}
+
+
+DECLINLINE(void) ataPIOTransferFinish(PAHCIATACONTROLLER pCtl, AHCIATADevState *s)
+{
+    /* Do not interfere with RESET processing if the PIO transfer finishes
+     * while the RESET line is asserted. */
+    if (pCtl->fReset)
+    {
+        Log2(("%s: Ctl: suppressed continuing PIO transfer as RESET is active\n", __FUNCTION__));
+        return;
+    }
+
+    if (   s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE
+        || (   s->iSourceSink != ATAFN_SS_NULL
+            && s->iIOBufferCur >= s->iIOBufferEnd))
+    {
+        /* Need to continue the transfer in the async I/O thread. This is
+         * the case for write operations or generally for not yet finished
+         * transfers (some data might need to be read). */
+        ataUnsetStatus(s, ATA_STAT_READY | ATA_STAT_DRQ);
+        ataSetStatus(s, ATA_STAT_BUSY);
+
+        Log2(("%s: Ctl: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__));
+        ataAsyncIOPutRequest(pCtl, &ataPIORequest);
+    }
+    else
+    {
+        /* Either everything finished (though some data might still be pending)
+         * or some data is pending before the next read is due. */
+
+        /* Continue a previously started transfer. */
+        ataUnsetStatus(s, ATA_STAT_DRQ);
+        ataSetStatus(s, ATA_STAT_READY);
+
+        if (s->cbTotalTransfer)
+        {
+            /* There is more to transfer, happens usually for large ATAPI
+             * reads - the protocol limits the chunk size to 65534 bytes. */
+            ataPIOTransfer(pCtl);
+            ataSetIRQ(s);
+        }
+        else
+        {
+            Log2(("%s: Ctl: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__));
+            /* Finish PIO transfer. */
+            ataPIOTransfer(pCtl);
+            Assert(!pCtl->fRedo);
+        }
+    }
+}
+
+#endif /* IN_RING3 */
+
+static int ataDataWrite(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, const uint8_t *pbBuf)
+{
+    AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+    uint8_t *p;
+
+    if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+    {
+        Assert(s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE);
+        p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+#ifndef IN_RING3
+        /* All but the last transfer unit is simple enough for GC, but
+         * sending a request to the async IO thread is too complicated. */
+        if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
+        {
+            memcpy(p, pbBuf, cbSize);
+            s->iIOBufferPIODataStart += cbSize;
+        }
+        else
+            return VINF_IOM_HC_IOPORT_WRITE;
+#else /* IN_RING3 */
+        memcpy(p, pbBuf, cbSize);
+        s->iIOBufferPIODataStart += cbSize;
+        if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+            ataPIOTransferFinish(pCtl, s);
+#endif /* !IN_RING3 */
+    }
+    else
+        Log2(("%s: DUMMY data\n", __FUNCTION__));
+    Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
+    return VINF_SUCCESS;
+}
+
+static int ataDataRead(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, uint8_t *pbBuf)
+{
+    AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+    uint8_t *p;
+
+    if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+    {
+        Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+        p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+#ifndef IN_RING3
+        /* All but the last transfer unit is simple enough for GC, but
+         * sending a request to the async IO thread is too complicated. */
+        if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
+        {
+            memcpy(pbBuf, p, cbSize);
+            s->iIOBufferPIODataStart += cbSize;
+        }
+        else
+            return VINF_IOM_HC_IOPORT_READ;
+#else /* IN_RING3 */
+        memcpy(pbBuf, p, cbSize);
+        s->iIOBufferPIODataStart += cbSize;
+        if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+            ataPIOTransferFinish(pCtl, s);
+#endif /* !IN_RING3 */
+    }
+    else
+    {
+        Log2(("%s: DUMMY data\n", __FUNCTION__));
+        memset(pbBuf, '\xff', cbSize);
+    }
+    Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
+    return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+static void ataDMATransferStop(AHCIATADevState *s)
+{
+    s->cbTotalTransfer = 0;
+    s->cbElementaryTransfer = 0;
+    s->iBeginTransfer = ATAFN_BT_NULL;
+    s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/**
+ * Perform the entire DMA transfer in one go (unless a source/sink operation
+ * has to be redone or a RESET comes in between). Unlike the PIO counterpart
+ * this function cannot handle empty transfers.
+ *
+ * @param pCtl      Controller for which to perform the transfer.
+ */
+static void ataDMATransfer(PAHCIATACONTROLLER pCtl)
+{
+    PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+    AHCIATADevState *s = &pCtl->aIfs[pCtl->iAIOIf];
+    bool fRedo;
+    RTGCPHYS32 pDesc;
+    uint32_t cbTotalTransfer, cbElementaryTransfer;
+    uint32_t iIOBufferCur, iIOBufferEnd;
+    uint32_t dmalen;
+    PDMBLOCKTXDIR uTxDir;
+    bool fLastDesc = false;
+
+    Assert(sizeof(BMDMADesc) == 8);
+
+    fRedo = pCtl->fRedo;
+    if (RT_LIKELY(!fRedo))
+        Assert(s->cbTotalTransfer);
+    uTxDir = (PDMBLOCKTXDIR)s->uTxDir;
+    cbTotalTransfer = s->cbTotalTransfer;
+    cbElementaryTransfer = s->cbElementaryTransfer;
+    iIOBufferCur = s->iIOBufferCur;
+    iIOBufferEnd = s->iIOBufferEnd;
+
+    /* The DMA loop is designed to hold the lock only when absolutely
+     * necessary. This avoids long freezes should the guest access the
+     * ATA registers etc. for some reason. */
+    PDMCritSectLeave(&pCtl->lock);
+
+    Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+         __FUNCTION__, uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
+         cbTotalTransfer, cbElementaryTransfer,
+         iIOBufferCur, iIOBufferEnd));
+    for (pDesc = pCtl->pFirstDMADesc; pDesc <= pCtl->pLastDMADesc; pDesc += sizeof(BMDMADesc))
+    {
+        BMDMADesc DMADesc;
+        RTGCPHYS32 pBuffer;
+        uint32_t cbBuffer;
+
+        if (RT_UNLIKELY(fRedo))
+        {
+            pBuffer = pCtl->pRedoDMABuffer;
+            cbBuffer = pCtl->cbRedoDMABuffer;
+            fLastDesc = pCtl->fRedoDMALastDesc;
+        }
+        else
+        {
+            PDMDevHlpPhysRead(pDevIns, pDesc, &DMADesc, sizeof(BMDMADesc));
+            pBuffer = RT_LE2H_U32(DMADesc.pBuffer);
+            cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
+            fLastDesc = !!(cbBuffer & 0x80000000);
+            cbBuffer &= 0xfffe;
+            if (cbBuffer == 0)
+                cbBuffer = 0x10000;
+            if (cbBuffer > cbTotalTransfer)
+                cbBuffer = cbTotalTransfer;
+        }
+
+        while (RT_UNLIKELY(fRedo) || (cbBuffer && cbTotalTransfer))
+        {
+            if (RT_LIKELY(!fRedo))
+            {
+                dmalen = RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur);
+                Log2(("%s: DMA desc %#010x: addr=%#010x size=%#010x\n", __FUNCTION__,
+                       pDesc, pBuffer, cbBuffer));
+                if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+                    PDMDevHlpPhysWrite(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
+                else
+                    PDMDevHlpPhysRead(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
+                iIOBufferCur += dmalen;
+                cbTotalTransfer -= dmalen;
+                cbBuffer -= dmalen;
+                pBuffer += dmalen;
+            }
+            if (    iIOBufferCur == iIOBufferEnd
+                &&  (uTxDir == PDMBLOCKTXDIR_TO_DEVICE || cbTotalTransfer))
+            {
+                if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+                    cbElementaryTransfer = cbTotalTransfer;
+
+                {
+                STAM_PROFILE_START(&pCtl->StatLockWait, a);
+                PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+                STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+                }
+
+                /* The RESET handler could have cleared the DMA transfer
+                 * state (since we didn't hold the lock until just now
+                 * the guest can continue in parallel). If so, the state
+                 * is already set up so the loop is exited immediately. */
+                if (s->iSourceSink != ATAFN_SS_NULL)
+                {
+                    s->iIOBufferCur = iIOBufferCur;
+                    s->iIOBufferEnd = iIOBufferEnd;
+                    s->cbElementaryTransfer = cbElementaryTransfer;
+                    s->cbTotalTransfer = cbTotalTransfer;
+                    Log2(("%s: calling source/sink function\n", __FUNCTION__));
+                    fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+                    if (RT_UNLIKELY(fRedo))
+                    {
+                        pCtl->pFirstDMADesc = pDesc;
+                        pCtl->pRedoDMABuffer = pBuffer;
+                        pCtl->cbRedoDMABuffer = cbBuffer;
+                        pCtl->fRedoDMALastDesc = fLastDesc;
+                    }
+                    else
+                    {
+                        cbTotalTransfer = s->cbTotalTransfer;
+                        cbElementaryTransfer = s->cbElementaryTransfer;
+
+                        if (uTxDir == PDMBLOCKTXDIR_TO_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+                            cbElementaryTransfer = cbTotalTransfer;
+                        iIOBufferCur = 0;
+                        iIOBufferEnd = cbElementaryTransfer;
+                    }
+                    pCtl->fRedo = fRedo;
+                }
+                else
+                {
+                    /* This forces the loop to exit immediately. */
+                    pDesc = pCtl->pLastDMADesc + 1;
+                }
+
+                PDMCritSectLeave(&pCtl->lock);
+                if (RT_UNLIKELY(fRedo))
+                    break;
+            }
+        }
+
+        if (RT_UNLIKELY(fRedo))
+            break;
+
+        /* end of transfer */
+        if (!cbTotalTransfer || fLastDesc)
+            break;
+
+        {
+        STAM_PROFILE_START(&pCtl->StatLockWait, a);
+        PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+        STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+        }
+
+        if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
+        {
+            LogRel(("ATA: Ctl: ABORT DMA%s\n", pCtl->fReset ? " due to RESET" : ""));
+            if (!pCtl->fReset)
+                ataDMATransferStop(s);
+            /* This forces the loop to exit immediately. */
+            pDesc = pCtl->pLastDMADesc + 1;
+        }
+
+        PDMCritSectLeave(&pCtl->lock);
+    }
+
+    {
+    STAM_PROFILE_START(&pCtl->StatLockWait, a);
+    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+    STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+    }
+
+    if (RT_UNLIKELY(fRedo))
+        return;
+
+    if (fLastDesc)
+        pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+    s->cbTotalTransfer = cbTotalTransfer;
+    s->cbElementaryTransfer = cbElementaryTransfer;
+    s->iIOBufferCur = iIOBufferCur;
+    s->iIOBufferEnd = iIOBufferEnd;
+}
+
+
+/**
+ * Suspend I/O operations on a controller. Also suspends EMT, because it's
+ * waiting for I/O to make progress. The next attempt to perform an I/O
+ * operation will be made when EMT is resumed up again (as the resume
+ * callback below restarts I/O).
+ *
+ * @param pCtl      Controller for which to suspend I/O.
+ */
+static void ataSuspendRedo(PAHCIATACONTROLLER pCtl)
+{
+    PPDMDEVINS  pDevIns = CONTROLLER_2_DEVINS(pCtl);
+    PVMREQ      pReq;
+    int         rc;
+
+    pCtl->fRedoIdle = true;
+    rc = VMR3ReqCall(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY, &pReq, RT_INDEFINITE_WAIT,
+                     (PFNRT)PDMDevHlpVMSuspend, 1, pDevIns);
+    AssertReleaseRC(rc);
+    VMR3ReqFree(pReq);
+}
+
+/** Asynch I/O thread for an interface. Once upon a time this was readable
+ * code with several loops and a different semaphore for each purpose. But
+ * then came the "how can one save the state in the middle of a PIO transfer"
+ * question. The solution was to use an ASM, which is what's there now. */
+static DECLCALLBACK(int) ataAsyncIOLoop(RTTHREAD ThreadSelf, void *pvUser)
+{
+    const AHCIATARequest *pReq;
+    uint64_t        u64TS = 0; /* shut up gcc */
+    uint64_t        uWait;
+    int             rc   = VINF_SUCCESS;
+    PAHCIATACONTROLLER  pCtl = (PAHCIATACONTROLLER)pvUser;
+    AHCIATADevState     *s;
+
+    pReq = NULL;
+    pCtl->fChainedTransfer = false;
+    while (!pCtl->fShutdown)
+    {
+        /* Keep this thread from doing anything as long as EMT is suspended. */
+        while (pCtl->fRedoIdle)
+        {
+            rc = RTSemEventWait(pCtl->SuspendIOSem, RT_INDEFINITE_WAIT);
+            if (RT_FAILURE(rc) || pCtl->fShutdown)
+                break;
+
+            pCtl->fRedoIdle = false;
+        }
+
+        /* Wait for work.  */
+        if (pReq == NULL)
+        {
+            LogBird(("ata: %x: going to sleep...\n", pCtl->IOPortBase1));
+            rc = RTSemEventWait(pCtl->AsyncIOSem, RT_INDEFINITE_WAIT);
+            LogBird(("ata: %x: waking up\n", pCtl->IOPortBase1));
+            if (RT_FAILURE(rc) || pCtl->fShutdown)
+                break;
+
+            pReq = ataAsyncIOGetCurrentRequest(pCtl);
+        }
+
+        if (pReq == NULL)
+            continue;
+
+        AHCIATAAIO ReqType = pReq->ReqType;
+
+        Log2(("%s: Ctl: state=%d, req=%d\n", __FUNCTION__, pCtl->uAsyncIOState, ReqType));
+        if (pCtl->uAsyncIOState != ReqType)
+        {
+            /* The new state is not the state that was expected by the normal
+             * state changes. This is either a RESET/ABORT or there's something
+             * really strange going on. */
+            if (    (pCtl->uAsyncIOState == AHCIATA_AIO_PIO || pCtl->uAsyncIOState == AHCIATA_AIO_DMA)
+                &&  (ReqType == AHCIATA_AIO_PIO || ReqType == AHCIATA_AIO_DMA))
+            {
+                /* Incorrect sequence of PIO/DMA states. Dump request queue. */
+                ataAsyncIODumpRequests(pCtl);
+            }
+            AssertReleaseMsg(ReqType == AHCIATA_AIO_RESET_ASSERTED || ReqType == AHCIATA_AIO_RESET_CLEARED || ReqType == AHCIATA_AIO_ABORT || pCtl->uAsyncIOState == ReqType, ("I/O state inconsistent: state=%d request=%d\n", pCtl->uAsyncIOState, ReqType));
+        }
+
+        /* Do our work.  */
+        {
+        STAM_PROFILE_START(&pCtl->StatLockWait, a);
+        LogBird(("ata: %x: entering critsect\n", pCtl->IOPortBase1));
+        PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+        LogBird(("ata: %x: entered\n", pCtl->IOPortBase1));
+        STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+        }
+
+        if (pCtl->uAsyncIOState == AHCIATA_AIO_NEW && !pCtl->fChainedTransfer)
+        {
+            u64TS = RTTimeNanoTS();
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+            STAM_PROFILE_ADV_START(&pCtl->StatAsyncTime, a);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+        }
+
+        switch (ReqType)
+        {
+            case AHCIATA_AIO_NEW:
+
+                pCtl->iAIOIf = pReq->u.t.iIf;
+                s = &pCtl->aIfs[pCtl->iAIOIf];
+                s->cbTotalTransfer = pReq->u.t.cbTotalTransfer;
+                s->uTxDir = pReq->u.t.uTxDir;
+                s->iBeginTransfer = pReq->u.t.iBeginTransfer;
+                s->iSourceSink = pReq->u.t.iSourceSink;
+                s->iIOBufferEnd = 0;
+                s->u64CmdTS = u64TS;
+
+                if (s->fATAPI)
+                {
+                    if (pCtl->fChainedTransfer)
+                    {
+                        /* Only count the actual transfers, not the PIO
+                         * transfer of the ATAPI command bytes. */
+                        if (s->fDMA)
+                            STAM_REL_COUNTER_INC(&s->StatATAPIDMA);
+                        else
+                            STAM_REL_COUNTER_INC(&s->StatATAPIPIO);
+                    }
+                }
+                else
+                {
+                    if (s->fDMA)
+                        STAM_REL_COUNTER_INC(&s->StatATADMA);
+                    else
+                        STAM_REL_COUNTER_INC(&s->StatATAPIO);
+                }
+
+                pCtl->fChainedTransfer = false;
+
+                if (s->iBeginTransfer != ATAFN_BT_NULL)
+                {
+                    Log2(("%s: Ctl: calling begin transfer function\n", __FUNCTION__));
+                    g_apfnBeginTransFuncs[s->iBeginTransfer](s);
+                    s->iBeginTransfer = ATAFN_BT_NULL;
+                    if (s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
+                        s->iIOBufferEnd = s->cbElementaryTransfer;
+                }
+                else
+                {
+                    s->cbElementaryTransfer = s->cbTotalTransfer;
+                    s->iIOBufferEnd = s->cbTotalTransfer;
+                }
+                s->iIOBufferCur = 0;
+
+                if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+                {
+                    if (s->iSourceSink != ATAFN_SS_NULL)
+                    {
+                        bool fRedo;
+                        Log2(("%s: Ctl: calling source/sink function\n", __FUNCTION__));
+                        fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+                        pCtl->fRedo = fRedo;
+                        if (RT_UNLIKELY(fRedo))
+                        {
+                            /* Operation failed at the initial transfer, restart
+                             * everything from scratch by resending the current
+                             * request. Occurs very rarely, not worth optimizing. */
+                            LogRel(("%s: Ctl: redo entire operation\n", __FUNCTION__));
+                            ataAsyncIOPutRequest(pCtl, pReq);
+                            ataSuspendRedo(pCtl);
+                            break;
+                        }
+                    }
+                    else
+                        ataCmdOK(s, 0);
+                    s->iIOBufferEnd = s->cbElementaryTransfer;
+
+                }
+
+                /* Do not go into the transfer phase if RESET is asserted.
+                 * The CritSect is released while waiting for the host OS
+                 * to finish the I/O, thus RESET is possible here. Most
+                 * important: do not change uAsyncIOState. */
+                if (pCtl->fReset)
+                    break;
+
+                if (s->fDMA)
+                {
+                    if (s->cbTotalTransfer)
+                    {
+                        ataSetStatus(s, ATA_STAT_DRQ);
+
+                        pCtl->uAsyncIOState = AHCIATA_AIO_DMA;
+                        /* If BMDMA is already started, do the transfer now. */
+                        if (pCtl->BmDma.u8Cmd & BM_CMD_START)
+                        {
+                            Log2(("%s: Ctl: message to async I/O thread, continuing DMA transfer immediately\n", __FUNCTION__));
+                            ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+                        }
+                    }
+                    else
+                    {
+                        Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+                        /* Finish DMA transfer. */
+                        ataDMATransferStop(s);
+                        ataSetIRQ(s);
+                        pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                    }
+                }
+                else
+                {
+                    if (s->cbTotalTransfer)
+                    {
+                        ataPIOTransfer(pCtl);
+                        Assert(!pCtl->fRedo);
+                        if (s->fATAPITransfer || s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+                            ataSetIRQ(s);
+
+                        if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+                        {
+                            /* Write operations and not yet finished transfers
+                             * must be completed in the async I/O thread. */
+                            pCtl->uAsyncIOState = AHCIATA_AIO_PIO;
+                        }
+                        else
+                        {
+                            /* Finished read operation can be handled inline
+                             * in the end of PIO transfer handling code. Linux
+                             * depends on this, as it waits only briefly for
+                             * devices to become ready after incoming data
+                             * transfer. Cannot find anything in the ATA spec
+                             * that backs this assumption, but as all kernels
+                             * are affected (though most of the time it does
+                             * not cause any harm) this must work. */
+                            pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                        }
+                    }
+                    else
+                    {
+                        Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+                        /* Finish PIO transfer. */
+                        ataPIOTransfer(pCtl);
+                        Assert(!pCtl->fRedo);
+                        if (!s->fATAPITransfer)
+                            ataSetIRQ(s);
+                        pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                    }
+                }
+                break;
+
+            case AHCIATA_AIO_DMA:
+            {
+                BMDMAState *bm = &pCtl->BmDma;
+                s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+                ATAFNSS iOriginalSourceSink = (ATAFNSS)s->iSourceSink; /* Used by the hack below, but gets reset by then. */
+
+                if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+                    AssertRelease(bm->u8Cmd & BM_CMD_WRITE);
+                else
+                    AssertRelease(!(bm->u8Cmd & BM_CMD_WRITE));
+
+                if (RT_LIKELY(!pCtl->fRedo))
+                {
+                    /* The specs say that the descriptor table must not cross a
+                     * 4K boundary. */
+                    pCtl->pFirstDMADesc = bm->pvAddr;
+                    pCtl->pLastDMADesc = RT_ALIGN_32(bm->pvAddr + 1, _4K) - sizeof(BMDMADesc);
+                }
+                ataDMATransfer(pCtl);
+
+                if (RT_UNLIKELY(pCtl->fRedo))
+                {
+                    LogRel(("ATA: Ctl: redo DMA operation\n"));
+                    ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+                    ataSuspendRedo(pCtl);
+                    break;
+                }
+
+                /* The infamous delay IRQ hack. */
+                if (   iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
+                    && s->cbTotalTransfer == 0
+                    && pCtl->DelayIRQMillies)
+                {
+                    /* Delay IRQ for writing. Required to get the Win2K
+                     * installation work reliably (otherwise it crashes,
+                     * usually during component install). So far no better
+                     * solution has been found. */
+                    Log(("%s: delay IRQ hack\n", __FUNCTION__));
+                    PDMCritSectLeave(&pCtl->lock);
+                    RTThreadSleep(pCtl->DelayIRQMillies);
+                    PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+                }
+
+                ataUnsetStatus(s, ATA_STAT_DRQ);
+                Assert(!pCtl->fChainedTransfer);
+                Assert(s->iSourceSink == ATAFN_SS_NULL);
+                if (s->fATAPITransfer)
+                {
+                    s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+                    Log2(("%s: Ctl: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+                    s->fATAPITransfer = false;
+                }
+                ataSetIRQ(s);
+                pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                break;
+            }
+
+            case AHCIATA_AIO_PIO:
+                s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+
+                if (s->iSourceSink != ATAFN_SS_NULL)
+                {
+                    bool fRedo;
+                    Log2(("%s: Ctl: calling source/sink function\n", __FUNCTION__));
+                    fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+                    pCtl->fRedo = fRedo;
+                    if (RT_UNLIKELY(fRedo))
+                    {
+                        LogRel(("ATA: Ctl#%d: redo PIO operation\n"));
+                        ataAsyncIOPutRequest(pCtl, &ataPIORequest);
+                        ataSuspendRedo(pCtl);
+                        break;
+                    }
+                    s->iIOBufferCur = 0;
+                    s->iIOBufferEnd = s->cbElementaryTransfer;
+                }
+                else
+                {
+                    /* Continue a previously started transfer. */
+                    ataUnsetStatus(s, ATA_STAT_BUSY);
+                    ataSetStatus(s, ATA_STAT_READY);
+                }
+
+                /* It is possible that the drives on this controller get RESET
+                 * during the above call to the source/sink function. If that's
+                 * the case, don't restart the transfer and don't finish it the
+                 * usual way. RESET handling took care of all that already.
+                 * Most important: do not change uAsyncIOState. */
+                if (pCtl->fReset)
+                    break;
+
+                if (s->cbTotalTransfer)
+                {
+                    ataPIOTransfer(pCtl);
+                    ataSetIRQ(s);
+
+                    if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+                    {
+                        /* Write operations and not yet finished transfers
+                         * must be completed in the async I/O thread. */
+                        pCtl->uAsyncIOState = AHCIATA_AIO_PIO;
+                    }
+                    else
+                    {
+                        /* Finished read operation can be handled inline
+                         * in the end of PIO transfer handling code. Linux
+                         * depends on this, as it waits only briefly for
+                         * devices to become ready after incoming data
+                         * transfer. Cannot find anything in the ATA spec
+                         * that backs this assumption, but as all kernels
+                         * are affected (though most of the time it does
+                         * not cause any harm) this must work. */
+                        pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                    }
+                }
+                else
+                {
+                    /* Finish PIO transfer. */
+                    ataPIOTransfer(pCtl);
+                    if (    !pCtl->fChainedTransfer
+                        &&  !s->fATAPITransfer
+                        &&  s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
+                    {
+                            ataSetIRQ(s);
+                    }
+                    pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                }
+                break;
+
+            case AHCIATA_AIO_RESET_ASSERTED:
+                pCtl->uAsyncIOState = AHCIATA_AIO_RESET_CLEARED;
+                ataPIOTransferStop(&pCtl->aIfs[0]);
+                ataPIOTransferStop(&pCtl->aIfs[1]);
+                /* Do not change the DMA registers, they are not affected by the
+                 * ATA controller reset logic. It should be sufficient to issue a
+                 * new command, which is now possible as the state is cleared. */
+                break;
+
+            case AHCIATA_AIO_RESET_CLEARED:
+                pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                pCtl->fReset = false;
+                LogRel(("ATA: Ctl: finished processing RESET\n"));
+                for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+                {
+                    if (pCtl->aIfs[i].fATAPI)
+                        ataSetStatusValue(&pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
+                    else
+                        ataSetStatusValue(&pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
+                    ataSetSignature(&pCtl->aIfs[i]);
+                }
+                break;
+
+            case AHCIATA_AIO_ABORT:
+                /* Abort the current command only if it operates on the same interface. */
+                if (pCtl->iAIOIf == pReq->u.a.iIf)
+                {
+                    s = &pCtl->aIfs[pCtl->iAIOIf];
+
+                    pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+                    /* Do not change the DMA registers, they are not affected by the
+                     * ATA controller reset logic. It should be sufficient to issue a
+                     * new command, which is now possible as the state is cleared. */
+                    if (pReq->u.a.fResetDrive)
+                    {
+                        ataResetDevice(s);
+                        ataExecuteDeviceDiagnosticSS(s);
+                    }
+                    else
+                    {
+                        ataPIOTransferStop(s);
+                        ataUnsetStatus(s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
+                        ataSetStatus(s, ATA_STAT_READY);
+                        ataSetIRQ(s);
+                    }
+                }
+                break;
+
+            default:
+                AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
+        }
+
+        ataAsyncIORemoveCurrentRequest(pCtl, ReqType);
+        pReq = ataAsyncIOGetCurrentRequest(pCtl);
+
+        if (pCtl->uAsyncIOState == AHCIATA_AIO_NEW && !pCtl->fChainedTransfer)
+        {
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+            STAM_PROFILE_ADV_STOP(&pCtl->StatAsyncTime, a);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+
+            u64TS = RTTimeNanoTS() - u64TS;
+            uWait = u64TS / 1000;
+            Log(("%s: Ctl: LUN#%d finished I/O transaction in %d microseconds\n", __FUNCTION__, pCtl->aIfs[pCtl->iAIOIf].iLUN, (uint32_t)(uWait)));
+            /* Mark command as finished. */
+            pCtl->aIfs[pCtl->iAIOIf].u64CmdTS = 0;
+
+            /*
+             * Release logging of command execution times depends on the
+             * command type. ATAPI commands often take longer (due to CD/DVD
+             * spin up time etc.) so the threshold is different.
+             */
+            if (pCtl->aIfs[pCtl->iAIOIf].uATARegCommand != ATA_PACKET)
+            {
+                if (uWait > 8 * 1000 * 1000)
+                {
+                    /*
+                     * Command took longer than 8 seconds. This is close
+                     * enough or over the guest's command timeout, so place
+                     * an entry in the release log to allow tracking such
+                     * timing errors (which are often caused by the host).
+                     */
+                    LogRel(("AHCI ATA: execution time for ATA command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].uATARegCommand, uWait / (1000 * 1000)));
+                }
+            }
+            else
+            {
+                if (uWait > 20 * 1000 * 1000)
+                {
+                    /*
+                     * Command took longer than 20 seconds. This is close
+                     * enough or over the guest's command timeout, so place
+                     * an entry in the release log to allow tracking such
+                     * timing errors (which are often caused by the host).
+                     */
+                    LogRel(("AHCI ATA: execution time for ATAPI command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].aATAPICmd[0], uWait / (1000 * 1000)));
+                }
+            }
+
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+            if (uWait < pCtl->StatAsyncMinWait || !pCtl->StatAsyncMinWait)
+                pCtl->StatAsyncMinWait = uWait;
+            if (uWait > pCtl->StatAsyncMaxWait)
+                pCtl->StatAsyncMaxWait = uWait;
+
+            STAM_COUNTER_ADD(&pCtl->StatAsyncTimeUS, uWait);
+            STAM_COUNTER_INC(&pCtl->StatAsyncOps);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+        }
+
+        LogBird(("ata: %x: leaving critsect\n", pCtl->IOPortBase1));
+        PDMCritSectLeave(&pCtl->lock);
+    }
+
+    /* Cleanup the state.  */
+    if (pCtl->AsyncIOSem)
+    {
+        RTSemEventDestroy(pCtl->AsyncIOSem);
+        pCtl->AsyncIOSem = NIL_RTSEMEVENT;
+    }
+    if (pCtl->SuspendIOSem)
+    {
+        RTSemEventDestroy(pCtl->SuspendIOSem);
+        pCtl->SuspendIOSem = NIL_RTSEMEVENT;
+    }
+    /* Do not destroy request mutex yet, still needed for proper shutdown. */
+    pCtl->fShutdown = false;
+    /* This must be last, as it also signals thread exit to EMT. */
+    pCtl->AsyncIOThread = NIL_RTTHREAD;
+
+    Log2(("%s: Ctl: return %Rrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+#endif /* IN_RING3 */
+
+static uint32_t ataBMDMACmdReadB(PAHCIATACONTROLLER pCtl, uint32_t addr)
+{
+    uint32_t val = pCtl->BmDma.u8Cmd;
+    Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+    return val;
+}
+
+
+static void ataBMDMACmdWriteB(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+    if (!(val & BM_CMD_START))
+    {
+        pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+        pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+    }
+    else
+    {
+#ifdef IN_RING3
+        /* Check whether the guest OS wants to change DMA direction in
+         * mid-flight. Not allowed, according to the AHCI specs. */
+        Assert(!(pCtl->BmDma.u8Status & BM_STATUS_DMAING) || !((val ^ pCtl->BmDma.u8Cmd) & 0x04));
+        pCtl->BmDma.u8Status |= BM_STATUS_DMAING;
+        pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+
+        /* Do not continue DMA transfers while the RESET line is asserted. */
+        if (pCtl->fReset)
+        {
+            Log2(("%s: Ctl: suppressed continuing DMA transfer as RESET is active\n", __FUNCTION__));
+            return;
+        }
+
+        /* Do not start DMA transfers if there's a PIO transfer going on. */
+        if (!pCtl->aIfs[pCtl->iSelectedIf].fDMA)
+            return;
+
+        if (pCtl->aIfs[pCtl->iAIOIf].uATARegStatus & ATA_STAT_DRQ)
+        {
+            Log2(("%s: Ctl: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__));
+            ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+        }
+#else /* !IN_RING3 */
+        AssertMsgFailed(("DMA START handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+    }
+}
+
+static uint32_t ataBMDMAStatusReadB(PAHCIATACONTROLLER pCtl, uint32_t addr)
+{
+    uint32_t val = pCtl->BmDma.u8Status;
+    Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+    return val;
+}
+
+static void ataBMDMAStatusWriteB(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+    pCtl->BmDma.u8Status =    (val & (BM_STATUS_D0DMA | BM_STATUS_D1DMA))
+                           |  (pCtl->BmDma.u8Status & BM_STATUS_DMAING)
+                           |  (pCtl->BmDma.u8Status & ~val & (BM_STATUS_ERROR | BM_STATUS_INT));
+}
+
+static uint32_t ataBMDMAAddrReadL(PAHCIATACONTROLLER pCtl, uint32_t addr)
+{
+    uint32_t val = (uint32_t)pCtl->BmDma.pvAddr;
+    Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+    return val;
+}
+
+static void ataBMDMAAddrWriteL(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+    pCtl->BmDma.pvAddr = val & ~3;
+}
+
+static void ataBMDMAAddrWriteLowWord(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+    pCtl->BmDma.pvAddr = (pCtl->BmDma.pvAddr & 0xFFFF0000) | RT_LOWORD(val & ~3);
+
+}
+
+static void ataBMDMAAddrWriteHighWord(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+    Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+    pCtl->BmDma.pvAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.pvAddr);
+}
+
+#define VAL(port, size)   ( ((port) & 7) | ((size) << 3) )
+
+/**
+ * Port I/O Handler for bus master DMA IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+int ataControllerBMDMAIOPortRead(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    int rc;
+
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    switch (VAL(Port, cb))
+    {
+        case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+        case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+        case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+        case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+        case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, Port); break;
+        default:
+            AssertMsgFailed(("%s: Unsupported read from port %x size=%d\n", __FUNCTION__, Port, cb));
+            PDMCritSectLeave(&pCtl->lock);
+            return VERR_IOM_IOPORT_UNUSED;
+    }
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+
+/**
+ * Port I/O Handler for bus master DMA OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+int ataControllerBMDMAIOPortWrite(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    int rc;
+
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    switch (VAL(Port, cb))
+    {
+        case VAL(0, 1):
+#ifndef IN_RING3
+            if (u32 & BM_CMD_START)
+            {
+                rc = VINF_IOM_HC_IOPORT_WRITE;
+                break;
+            }
+#endif /* !IN_RING3 */
+            ataBMDMACmdWriteB(pCtl, Port, u32);
+            break;
+        case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, Port, u32); break;
+        case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, Port, u32); break;
+        case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, Port, u32); break;
+        case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, Port, u32); break;
+        default:        AssertMsgFailed(("%s: Unsupported write to port %x size=%d val=%x\n", __FUNCTION__, Port, cb, u32)); break;
+    }
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+
+#undef VAL
+
+
+#ifdef IN_RING3
+#if 0
+
+/**
+ * Callback function for mapping an PCI I/O region.
+ *
+ * @return VBox status code.
+ * @param   pPciDev         Pointer to PCI device. Use pPciDev->pDevIns to get the device instance.
+ * @param   iRegion         The region number.
+ * @param   GCPhysAddress   Physical address of the region. If iType is PCI_ADDRESS_SPACE_IO, this is an
+ *                          I/O port, else it's a physical address.
+ *                          This address is *NOT* relative to pci_mem_base like earlier!
+ * @param   enmType         One of the PCI_ADDRESS_SPACE_* values.
+ */
+static DECLCALLBACK(int) ataBMDMAIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
+{
+    PCIATAState *pThis = PCIDEV_2_PCIATASTATE(pPciDev);
+    int         rc = VINF_SUCCESS;
+    Assert(enmType == PCI_ADDRESS_SPACE_IO);
+    Assert(iRegion == 4);
+    AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));
+
+    /* Register the port range. */
+    for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+    {
+        int rc2 = PDMDevHlpIOPortRegister(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+                                          (RTHCPTR)i, ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL, NULL, "ATA Bus Master DMA");
+        AssertRC(rc2);
+        if (rc2 < rc)
+            rc = rc2;
+
+        if (pThis->fGCEnabled)
+        {
+            rc2 = PDMDevHlpIOPortRegisterGC(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+                                            (RTGCPTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
+            AssertRC(rc2);
+            if (rc2 < rc)
+                rc = rc2;
+        }
+        if (pThis->fR0Enabled)
+        {
+            rc2 = PDMDevHlpIOPortRegisterR0(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+                                            (RTR0PTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
+            AssertRC(rc2);
+            if (rc2 < rc)
+                rc = rc2;
+        }
+    }
+    return rc;
+}
+#endif
+
+/**
+ * Reset the controller to an intial state.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+void  ataControllerReset(PAHCIATACONTROLLER pCtl)
+{
+    pCtl->iSelectedIf = 0;
+    pCtl->iAIOIf = 0;
+    pCtl->BmDma.u8Cmd = 0;
+    /* Report that both drives present on the bus are in DMA mode. This
+     * pretends that there is a BIOS that has set it up. Normal reset
+     * default is 0x00. */
+    pCtl->BmDma.u8Status =   (pCtl->aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
+                              | (pCtl->aIfs[1].pDrvBase != NULL ? BM_STATUS_D1DMA : 0);
+    pCtl->BmDma.pvAddr = 0;
+
+    pCtl->fReset = true;
+    pCtl->fRedo = false;
+    pCtl->fRedoIdle = false;
+    ataAsyncIOClearRequests(pCtl);
+    Log2(("%s: Ctl: message to async I/O thread, reset controller\n", __FUNCTION__));
+    ataAsyncIOPutRequest(pCtl, &ataResetARequest);
+    ataAsyncIOPutRequest(pCtl, &ataResetCRequest);
+    if (!ataWaitForAsyncIOIsIdle(pCtl, 30000))
+        AssertReleaseMsgFailed(("Async I/O thread busy after reset\n"));
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+        ataResetDevice(&pCtl->aIfs[i]);
+}
+
+#if 0
+/* -=-=-=-=-=- AHCIATADevState::IBase   -=-=-=-=-=- */
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to AHCIATADevState::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *)  ataQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    AHCIATADevState *pIf = PDMIBASE_2_ATASTATE(pInterface);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pIf->IBase;
+        case PDMINTERFACE_BLOCK_PORT:
+            return &pIf->IPort;
+        case PDMINTERFACE_MOUNT_NOTIFY:
+            return &pIf->IMountNotify;
+        default:
+            return NULL;
+    }
+}
+#endif
+#endif /* IN_RING3 */
+
+
+/* -=-=-=-=-=- Wrappers  -=-=-=-=-=- */
+
+/**
+ * Port I/O Handler for primary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+int ataControllerIOPortWrite1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    int rc = VINF_SUCCESS;
+
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    if (cb == 1)
+        rc = ataIOPortWriteU8(pCtl, Port, u32);
+    else if (Port == pCtl->IOPortBase1)
+    {
+        Assert(cb == 2 || cb == 4);
+        rc = ataDataWrite(pCtl, Port, cb, (const uint8_t *)&u32);
+    }
+    else
+        AssertMsgFailed(("ataIOPortWrite1: unsupported write to port %x val=%x size=%d\n", Port, u32, cb));
+    LogBird(("ata: leaving critsect\n"));
+    PDMCritSectLeave(&pCtl->lock);
+    LogBird(("ata: left critsect\n"));
+    return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+int ataControllerIOPortRead1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    int rc = VINF_SUCCESS;
+
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    if (cb == 1)
+    {
+        rc = ataIOPortReadU8(pCtl, Port, pu32);
+    }
+    else if (Port == pCtl->IOPortBase1)
+    {
+        Assert(cb == 2 || cb == 4);
+        rc = ataDataRead(pCtl, Port, cb, (uint8_t *)pu32);
+        if (cb == 2)
+            *pu32 &= 0xffff;
+    }
+    else
+    {
+        AssertMsgFailed(("ataIOPortRead1: unsupported read from port %x size=%d\n", Port, cb));
+        rc = VERR_IOM_IOPORT_UNUSED;
+    }
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+
+#ifndef IN_RING0 /** @todo do this in ring-0 as well. */
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+int ataControllerIOPortReadStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    if (Port == pCtl->IOPortBase1)
+    {
+        uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
+        RTGCPTR GCDst = *pGCPtrDst;
+        AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+        Assert(cb == 2 || cb == 4);
+
+        cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
+#ifndef IN_RING3
+        /* The last transfer unit cannot be handled in GC, as it involves thread communication. */
+        cTransAvailable--;
+#endif /* !IN_RING3 */
+        /* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
+         * They are not performance-critical and generally shouldn't occur at all. */
+        if (cTransAvailable > cTransfer)
+            cTransAvailable = cTransfer;
+        cbTransfer = cTransAvailable * cb;
+
+        PPDMDEVINS pDevIns = pCtl->CTX_SUFF(pDevIns);
+        rc = PGMPhysSimpleDirtyWriteGCPtr(PDMDevHlpGetVMCPU(pDevIns), GCDst, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, cbTransfer);
+        Assert(rc == VINF_SUCCESS);
+
+        if (cbTransfer)
+            Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
+        s->iIOBufferPIODataStart += cbTransfer;
+        *pGCPtrDst = (RTGCPTR)((RTGCUINTPTR)GCDst + cbTransfer);
+        *pcTransfer = cTransfer - cTransAvailable;
+#ifdef IN_RING3
+        if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+            ataPIOTransferFinish(pCtl, s);
+#endif /* IN_RING3 */
+    }
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+int ataControllerIOPortWriteStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    if (Port == pCtl->IOPortBase1)
+    {
+        uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
+        RTGCPTR GCSrc = *pGCPtrSrc;
+        AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+        Assert(cb == 2 || cb == 4);
+
+        cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
+#ifndef IN_RING3
+        /* The last transfer unit cannot be handled in GC, as it involves thread communication. */
+        cTransAvailable--;
+#endif /* !IN_RING3 */
+        /* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
+         * They are not performance-critical and generally shouldn't occur at all. */
+        if (cTransAvailable > cTransfer)
+            cTransAvailable = cTransfer;
+        cbTransfer = cTransAvailable * cb;
+
+        PPDMDEVINS pDevIns = pCtl->CTX_SUFF(pDevIns);
+        rc = PGMPhysSimpleReadGCPtr(PDMDevHlpGetVMCPU(pDevIns), s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, GCSrc, cbTransfer);
+        Assert(rc == VINF_SUCCESS);
+
+        if (cbTransfer)
+            Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
+        s->iIOBufferPIODataStart += cbTransfer;
+        *pGCPtrSrc = (RTGCPTR)((RTGCUINTPTR)GCSrc + cbTransfer);
+        *pcTransfer = cTransfer - cTransAvailable;
+#ifdef IN_RING3
+        if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+            ataPIOTransferFinish(pCtl, s);
+#endif /* IN_RING3 */
+    }
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+#endif /* !IN_RING0 */
+
+/**
+ * Port I/O Handler for secondary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+int ataControllerIOPortWrite2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    int rc;
+
+    if (cb != 1)
+        return VINF_SUCCESS;
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    rc = ataControlWrite(pCtl, Port, u32);
+    PDMCritSectLeave(&pCtl->lock);
+    return rc;
+}
+
+
+/**
+ * Port I/O Handler for secondary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+int ataControllerIOPortRead2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    int            rc;
+
+    if (cb != 1)
+        return VERR_IOM_IOPORT_UNUSED;
+
+    rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+    if (rc != VINF_SUCCESS)
+        return rc;
+    *pu32 = ataStatusRead(pCtl, Port);
+    PDMCritSectLeave(&pCtl->lock);
+    return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Waits for all async I/O threads to complete whatever they
+ * are doing at the moment.
+ *
+ * @returns true on success.
+ * @returns false when one or more threads is still processing.
+ * @param   pThis       Pointer to the instance data.
+ * @param   cMillies    How long to wait (total).
+ */
+static bool ataWaitForAllAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, unsigned cMillies)
+{
+    uint64_t        u64Start;
+    PPDMDEVINS      pDevIns = pCtl->CTXALLSUFF(pDevIns);
+    bool            fAllIdle = false;
+
+    /*
+     * Wait for any pending async operation to finish
+     */
+    u64Start = RTTimeMilliTS();
+    for (;;)
+    {
+        /* Check all async I/O threads. */
+        fAllIdle = true;
+
+        fAllIdle &= ataAsyncIOIsIdle(pCtl, false);
+        if (!fAllIdle)
+            break;
+
+        if (    fAllIdle
+            ||  RTTimeMilliTS() - u64Start >= cMillies)
+            break;
+
+        /* Sleep for a bit. */
+        RTThreadSleep(100);
+    }
+
+    if (!fAllIdle)
+        LogRel(("ATA: Ctl is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x\n",
+                pCtl->iSelectedIf, pCtl->iAIOIf,
+                pCtl->aIfs[0].uATARegCommand, pCtl->aIfs[1].uATARegCommand));
+
+    return fAllIdle;
+}
+
+
+DECLINLINE(void) ataRelocBuffer(PPDMDEVINS pDevIns, AHCIATADevState *s)
+{
+    if (s->pbIOBufferR3)
+        s->pbIOBufferRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), s->pbIOBufferR3);
+}
+
+
+/**
+ * @copydoc FNPDMDEVRELOCATE
+ */
+void ataControllerRelocate(PAHCIATACONTROLLER pCtl, RTGCINTPTR offDelta)
+{
+    PPDMDEVINS pDevIns = pCtl->CTXALLSUFF(pDevIns);
+
+    pCtl->pDevInsRC += offDelta;
+    pCtl->aIfs[0].pDevInsRC += offDelta;
+    pCtl->aIfs[0].pControllerRC += offDelta;
+    ataRelocBuffer(pDevIns, &pCtl->aIfs[0]);
+    pCtl->aIfs[1].pDevInsRC += offDelta;
+    pCtl->aIfs[1].pControllerRC += offDelta;
+    ataRelocBuffer(pDevIns, &pCtl->aIfs[1]);
+}
+
+
+/**
+ * Destroy a controller instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pCtl     The controller instance.
+ */
+int ataControllerDestroy(PAHCIATACONTROLLER pCtl)
+{
+    int rc;
+
+    Log(("%s:\n", __FUNCTION__));
+
+    /*
+     * Terminate all async helper threads
+     */
+    if (pCtl->AsyncIOThread != NIL_RTTHREAD)
+    {
+        ASMAtomicXchgU32(&pCtl->fShutdown, true);
+        rc = RTSemEventSignal(pCtl->AsyncIOSem);
+        AssertRC(rc);
+    }
+
+    if (pCtl->CTX_SUFF(pDevIns))
+    {
+        /*
+         * Wait for them to complete whatever they are doing and then
+         * for them to terminate.
+         */
+        if (ataWaitForAllAsyncIOIsIdle(pCtl, 20000))
+        {
+            uint64_t    u64Start = RTTimeMilliTS();
+            bool        fAllDone;
+            for (;;)
+            {
+                /* check */
+                fAllDone = true;
+                fAllDone &= (pCtl->AsyncIOThread == NIL_RTTHREAD);
+
+                if (    fAllDone
+                    ||  RTTimeMilliTS() - u64Start >= 500)
+                    break;
+
+                /* Sleep for a bit. */
+                RTThreadSleep(100);
+            }
+            AssertMsg(fAllDone, ("Some of the async I/O threads are still running!\n"));
+        }
+        else
+            AssertMsgFailed(("Async I/O is still busy!\n"));
+    }
+
+    /*
+     * Now the request mutexes are no longer needed. Free resources.
+     */
+    if (pCtl->AsyncIORequestMutex)
+    {
+        RTSemMutexDestroy(pCtl->AsyncIORequestMutex);
+        pCtl->AsyncIORequestMutex = NIL_RTSEMEVENT;
+    }
+
+    return VINF_SUCCESS;
+}
+
+#if 0
+/**
+ * Detach notification.
+ *
+ * The DVD drive has been unplugged.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ */
+static DECLCALLBACK(void) ataDetach(PPDMDEVINS pDevIns, unsigned iLUN)
+{
+    PCIATAState    *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+    PAHCIATACONTROLLER  pCtl;
+    AHCIATADevState    *pIf;
+    unsigned        iController;
+    unsigned        iInterface;
+
+    /*
+     * Locate the controller and stuff.
+     */
+    iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+    AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+    pCtl = &pThis->aCts[iController];
+
+    iInterface  = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+    pIf = &pCtl->aIfs[iInterface];
+
+    /*
+     * Zero some important members.
+     */
+    pIf->pDrvBase = NULL;
+    pIf->pDrvBlock = NULL;
+    pIf->pDrvBlockBios = NULL;
+    pIf->pDrvMount = NULL;
+
+    /*
+     * Just in case there was a medium inserted. Only required when attached to a physical drive
+     * in passthrough mode as in virtual ATAPI mode we've got an unmount notification.
+     */
+    if (pIf->fATAPIPassthrough)
+        ataMediumRemoved(pIf);
+
+}
+#endif
+
+/**
+ * Configure a LUN.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   pIf         The ATA unit state.
+ */
+static int ataConfigLun(PPDMDEVINS pDevIns, AHCIATADevState *pIf)
+{
+    int             rc;
+    PDMBLOCKTYPE    enmType;
+
+    /*
+     * Query Block, Bios and Mount interfaces.
+     */
+    pIf->pDrvBlock = (PDMIBLOCK *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK);
+    if (!pIf->pDrvBlock)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+
+    /** @todo implement the BIOS invisible code path. */
+    pIf->pDrvBlockBios = (PDMIBLOCKBIOS *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
+    if (!pIf->pDrvBlockBios)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d hasn't a block BIOS interface!\n", pIf->iLUN));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+    pIf->pDrvMount = (PDMIMOUNT *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_MOUNT);
+
+    /*
+     * Validate type.
+     */
+    enmType = pIf->pDrvBlock->pfnGetType(pIf->pDrvBlock);
+    if (    enmType != PDMBLOCKTYPE_CDROM
+        &&  enmType != PDMBLOCKTYPE_DVD
+        &&  enmType != PDMBLOCKTYPE_HARD_DISK)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd-rom. enmType=%d\n", pIf->iLUN, enmType));
+        return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+    }
+    if (    (   enmType == PDMBLOCKTYPE_DVD
+             || enmType == PDMBLOCKTYPE_CDROM)
+        &&  !pIf->pDrvMount)
+    {
+        AssertMsgFailed(("Internal error: cdrom without a mountable interface, WTF???!\n"));
+        return VERR_INTERNAL_ERROR;
+    }
+    pIf->fATAPI = enmType == PDMBLOCKTYPE_DVD || enmType == PDMBLOCKTYPE_CDROM;
+    pIf->fATAPIPassthrough = pIf->fATAPI ? (pIf->pDrvBlock->pfnSendCmd != NULL) : false;
+
+    /*
+     * Allocate I/O buffer.
+     */
+    PVM pVM = PDMDevHlpGetVM(pDevIns);
+    if (pIf->cbIOBuffer)
+    {
+        /* Buffer is (probably) already allocated. Validate the fields,
+         * because memory corruption can also overwrite pIf->cbIOBuffer. */
+        if (pIf->fATAPI)
+            AssertRelease(pIf->cbIOBuffer == _128K);
+        else
+            AssertRelease(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * 512);
+        Assert(pIf->pbIOBufferR3);
+        Assert(pIf->pbIOBufferR0 == MMHyperR3ToR0(pVM, pIf->pbIOBufferR3));
+        Assert(pIf->pbIOBufferRC == MMHyperR3ToRC(pVM, pIf->pbIOBufferR3));
+    }
+    else
+    {
+        if (pIf->fATAPI)
+            pIf->cbIOBuffer = _128K;
+        else
+            pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * 512;
+        Assert(!pIf->pbIOBufferR3);
+        rc = MMR3HyperAllocOnceNoRel(pVM, pIf->cbIOBuffer, 0, MM_TAG_PDM_DEVICE_USER, (void **)&pIf->pbIOBufferR3);
+        if (RT_FAILURE(rc))
+            return VERR_NO_MEMORY;
+        pIf->pbIOBufferR0 = MMHyperR3ToR0(pVM, pIf->pbIOBufferR3);
+        pIf->pbIOBufferRC = MMHyperR3ToRC(pVM, pIf->pbIOBufferR3);
+    }
+
+    /*
+     * Init geometry (only for non-CD/DVD media).
+     */
+    if (pIf->fATAPI)
+    {
+        pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
+        pIf->PCHSGeometry.cCylinders = 0; /* dummy */
+        pIf->PCHSGeometry.cHeads     = 0; /* dummy */
+        pIf->PCHSGeometry.cSectors   = 0; /* dummy */
+        LogRel(("ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n", pIf->iLUN, pIf->cTotalSectors, (pIf->fATAPIPassthrough ? "enabled" : "disabled")));
+    }
+    else
+    {
+        pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
+        rc = pIf->pDrvBlockBios->pfnGetPCHSGeometry(pIf->pDrvBlockBios,
+                                                    &pIf->PCHSGeometry);
+        if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+        {
+            pIf->PCHSGeometry.cCylinders = 0;
+            pIf->PCHSGeometry.cHeads     = 16; /*??*/
+            pIf->PCHSGeometry.cSectors   = 63; /*??*/
+        }
+        else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+        {
+            pIf->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+            rc = VINF_SUCCESS;
+        }
+        AssertRC(rc);
+
+        if (   pIf->PCHSGeometry.cCylinders == 0
+            || pIf->PCHSGeometry.cHeads == 0
+            || pIf->PCHSGeometry.cSectors == 0
+           )
+        {
+            uint64_t cCylinders = pIf->cTotalSectors / (16 * 63);
+            pIf->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+            pIf->PCHSGeometry.cHeads = 16;
+            pIf->PCHSGeometry.cSectors = 63;
+            /* Set the disk geometry information. */
+            rc = pIf->pDrvBlockBios->pfnSetPCHSGeometry(pIf->pDrvBlockBios,
+                                                        &pIf->PCHSGeometry);
+        }
+        LogRel(("AHCI ATA: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n", pIf->iLUN, pIf->PCHSGeometry.cCylinders, pIf->PCHSGeometry.cHeads, pIf->PCHSGeometry.cSectors, pIf->cTotalSectors));
+    }
+    return VINF_SUCCESS;
+}
+
+#if 0
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for the DVD drive.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ */
+static DECLCALLBACK(int)  ataAttach(PPDMDEVINS pDevIns, unsigned iLUN)
+{
+    PCIATAState    *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+    PAHCIATACONTROLLER  pCtl;
+    AHCIATADevState    *pIf;
+    int             rc;
+    unsigned        iController;
+    unsigned        iInterface;
+
+    /*
+     * Locate the controller and stuff.
+     */
+    iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+    AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+    pCtl = &pThis->aCts[iController];
+
+    iInterface  = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+    pIf = &pCtl->aIfs[iInterface];
+
+    /* the usual paranoia */
+    AssertRelease(!pIf->pDrvBase);
+    AssertRelease(!pIf->pDrvBlock);
+    Assert(ATADEVSTATE_2_CONTROLLER(pIf) == pCtl);
+    Assert(pIf->iLUN == iLUN);
+
+    /*
+     * Try attach the block device and get the interfaces,
+     * required as well as optional.
+     */
+    rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, NULL);
+    if (RT_SUCCESS(rc))
+    {
+        rc = ataConfigLun(pDevIns, pIf);
+        /*
+         * In case there is a new medium inserted. In virtual ATAPI mode we get an mount
+         * notification.
+         */
+        if (pIf->fATAPIPassthrough)
+            ataMediumInserted(pIf);
+    }
+    else
+        AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pIf->iLUN, rc));
+
+    if (RT_FAILURE(rc))
+    {
+        pIf->pDrvBase = NULL;
+        pIf->pDrvBlock = NULL;
+    }
+    return rc;
+}
+#endif
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+void ataControllerSuspend(PAHCIATACONTROLLER pCtl)
+{
+    Log(("%s:\n", __FUNCTION__));
+    if (!ataWaitForAllAsyncIOIsIdle(pCtl, 20000))
+        AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
+    return;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+void ataControllerResume(PAHCIATACONTROLLER pCtl)
+{
+    int rc;
+
+    Log(("%s:\n", __FUNCTION__));
+    if (pCtl->fRedo && pCtl->fRedoIdle)
+    {
+        rc = RTSemEventSignal(pCtl->SuspendIOSem);
+        AssertRC(rc);
+    }
+
+    return;
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @returns VBox status.
+ * @param   pCtl     The controller instance.
+ */
+void ataControllerPowerOff(PAHCIATACONTROLLER pCtl)
+{
+    Log(("%s:\n", __FUNCTION__));
+    if (!ataWaitForAllAsyncIOIsIdle(pCtl, 20000))
+        AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
+    return;
+}
+
+/**
+ * Prepare state save and load operation.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns         Device instance of the device which registered the data unit.
+ * @param   pSSM            SSM operation handle.
+ */
+static int ataSaveLoadPrep(PAHCIATACONTROLLER pCtl)
+{
+    /* sanity - the suspend notification will wait on the async stuff. */
+    Assert(ataAsyncIOIsIdle(pCtl, false));
+    if (!ataAsyncIOIsIdle(pCtl, false))
+        return VERR_SSM_IDE_ASYNC_TIMEOUT;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Prepare state save operation.
+ *
+ * @returns VBox status code.
+ * @param   pCtl    The controller instance.
+ * @param   pSSM    SSM operation handle.
+ */
+DECLCALLBACK(int) ataControllerSavePrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM)
+{
+    return ataSaveLoadPrep(pCtl);
+}
+
+/**
+ * Prepare state load operation.
+ *
+ * @returns VBox status code.
+ * @param   pCtl    The controller instance.
+ * @param   pSSM    SSM operation handle.
+ */
+DECLCALLBACK(int) ataControllerLoadPrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM)
+{
+    return ataSaveLoadPrep(pCtl);
+}
+
+/**
+ * Saves a state of the ATA device.
+ *
+ * @returns VBox status code.
+ * @param   pCtl        Controller instance.
+ * @param   pSSMHandle  The handle to save the state to.
+ */
+int ataControllerSaveExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSMHandle)
+{
+    SSMR3PutU32(pSSMHandle, ATA_CTL_SAVED_STATE_VERSION);
+    SSMR3PutU8(pSSMHandle, pCtl->iSelectedIf);
+    SSMR3PutU8(pSSMHandle, pCtl->iAIOIf);
+    SSMR3PutU8(pSSMHandle, pCtl->uAsyncIOState);
+    SSMR3PutBool(pSSMHandle, pCtl->fChainedTransfer);
+    SSMR3PutBool(pSSMHandle, pCtl->fReset);
+    SSMR3PutBool(pSSMHandle, pCtl->fRedo);
+    SSMR3PutBool(pSSMHandle, pCtl->fRedoIdle);
+    SSMR3PutBool(pSSMHandle, pCtl->fRedoDMALastDesc);
+    SSMR3PutMem(pSSMHandle, &pCtl->BmDma, sizeof(pCtl->BmDma));
+    SSMR3PutGCPhys32(pSSMHandle, pCtl->pFirstDMADesc);
+    SSMR3PutGCPhys32(pSSMHandle, pCtl->pLastDMADesc);
+    SSMR3PutGCPhys32(pSSMHandle, pCtl->pRedoDMABuffer);
+    SSMR3PutU32(pSSMHandle, pCtl->cbRedoDMABuffer);
+
+    for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+    {
+        SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fLBA48);
+        SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fATAPI);
+        SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fIrqPending);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].cMultSectors);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cCylinders);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cHeads);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cSectors);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cSectorsPerIRQ);
+        SSMR3PutU64(pSSMHandle, pCtl->aIfs[j].cTotalSectors);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegFeature);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegFeatureHOB);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegError);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegNSector);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegNSectorHOB);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSector);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSectorHOB);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegLCyl);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegLCylHOB);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegHCyl);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegHCylHOB);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSelect);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegStatus);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegCommand);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegDevCtl);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATATransferMode);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uTxDir);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].iBeginTransfer);
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].iSourceSink);
+        SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fDMA);
+        SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fATAPITransfer);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbTotalTransfer);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbElementaryTransfer);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferCur);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferEnd);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferPIODataStart);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferPIODataEnd);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iATAPILBA);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbATAPISector);
+        SSMR3PutMem(pSSMHandle, &pCtl->aIfs[j].aATAPICmd, sizeof(pCtl->aIfs[j].aATAPICmd));
+        SSMR3PutMem(pSSMHandle, &pCtl->aIfs[j].abATAPISense, sizeof(pCtl->aIfs[j].abATAPISense));
+        SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].cNotifiedMediaChange);
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].MediaEventStatus);
+        SSMR3PutMem(pSSMHandle, pCtl->aIfs[j].pLed, sizeof(PDMLED));
+        SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbIOBuffer);
+        if (pCtl->aIfs[j].cbIOBuffer)
+            SSMR3PutMem(pSSMHandle, pCtl->aIfs[j].CTX_SUFF(pbIOBuffer), pCtl->aIfs[j].cbIOBuffer);
+        else
+            Assert(pCtl->aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+    }
+
+    return SSMR3PutU32(pSSMHandle, ~0); /* sanity/terminator */
+}
+
+
+/**
+ * Loads a saved ATA device state.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   pSSMHandle  The handle to the saved state.
+ */
+int ataControllerLoadExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSMHandle)
+{
+    int             rc;
+    uint32_t        u32Version;
+    uint32_t        u32;
+
+    /* Test for correct version. */
+    rc = SSMR3GetU32(pSSMHandle, &u32Version);
+    AssertRCReturn(rc, rc);
+
+    if (   u32Version != ATA_CTL_SAVED_STATE_VERSION
+        && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
+        && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+    {
+        AssertMsgFailed(("u32Version=%d\n", u32Version));
+        return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+    }
+
+    /* integrity check */
+    if (!ataAsyncIOIsIdle(pCtl, false))
+    {
+        AssertMsgFailed(("Async I/O for controller is active\n"));
+        return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+    }
+
+    SSMR3GetU8(pSSMHandle, &pCtl->iSelectedIf);
+    SSMR3GetU8(pSSMHandle, &pCtl->iAIOIf);
+    SSMR3GetU8(pSSMHandle, &pCtl->uAsyncIOState);
+    SSMR3GetBool(pSSMHandle, &pCtl->fChainedTransfer);
+    SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fReset);
+    SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedo);
+    SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedoIdle);
+    SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedoDMALastDesc);
+    SSMR3GetMem(pSSMHandle, &pCtl->BmDma, sizeof(pCtl->BmDma));
+    SSMR3GetGCPhys32(pSSMHandle, &pCtl->pFirstDMADesc);
+    SSMR3GetGCPhys32(pSSMHandle, &pCtl->pLastDMADesc);
+    SSMR3GetGCPhys32(pSSMHandle, &pCtl->pRedoDMABuffer);
+    SSMR3GetU32(pSSMHandle, &pCtl->cbRedoDMABuffer);
+
+    for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+    {
+        SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fLBA48);
+        SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fATAPI);
+        SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fIrqPending);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].cMultSectors);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cCylinders);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cHeads);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cSectors);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cSectorsPerIRQ);
+        SSMR3GetU64(pSSMHandle, &pCtl->aIfs[j].cTotalSectors);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegFeature);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegFeatureHOB);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegError);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegNSector);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegNSectorHOB);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSector);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSectorHOB);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegLCyl);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegLCylHOB);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegHCyl);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegHCylHOB);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSelect);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegStatus);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegCommand);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegDevCtl);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATATransferMode);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uTxDir);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].iBeginTransfer);
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].iSourceSink);
+        SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fDMA);
+        SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fATAPITransfer);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbTotalTransfer);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbElementaryTransfer);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferCur);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferEnd);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferPIODataStart);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferPIODataEnd);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iATAPILBA);
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbATAPISector);
+        SSMR3GetMem(pSSMHandle, &pCtl->aIfs[j].aATAPICmd, sizeof(pCtl->aIfs[j].aATAPICmd));
+        if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+        {
+            SSMR3GetMem(pSSMHandle, &pCtl->aIfs[j].abATAPISense, sizeof(pCtl->aIfs[j].abATAPISense));
+        }
+        else
+        {
+            uint8_t uATAPISenseKey, uATAPIASC;
+            memset(pCtl->aIfs[j].abATAPISense, '\0', sizeof(pCtl->aIfs[j].abATAPISense));
+            pCtl->aIfs[j].abATAPISense[0] = 0x70 | (1 << 7);
+            pCtl->aIfs[j].abATAPISense[7] = 10;
+            SSMR3GetU8(pSSMHandle, &uATAPISenseKey);
+            SSMR3GetU8(pSSMHandle, &uATAPIASC);
+            pCtl->aIfs[j].abATAPISense[2] = uATAPISenseKey & 0x0f;
+            pCtl->aIfs[j].abATAPISense[12] = uATAPIASC;
+        }
+        /** @todo triple-check this hack after passthrough is working */
+        SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].cNotifiedMediaChange);
+        if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+            SSMR3GetU32(pSSMHandle, (uint32_t*)&pCtl->aIfs[j].MediaEventStatus);
+        else
+            pCtl->aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
+
+        PDMLED Led;
+        SSMR3GetMem(pSSMHandle, &Led, sizeof(PDMLED));
+        SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbIOBuffer);
+        if (pCtl->aIfs[j].cbIOBuffer)
+        {
+            if (pCtl->aIfs[j].CTX_SUFF(pbIOBuffer))
+                SSMR3GetMem(pSSMHandle, pCtl->aIfs[j].CTX_SUFF(pbIOBuffer), pCtl->aIfs[j].cbIOBuffer);
+            else
+            {
+                LogRel(("ATA: No buffer for %d\n", j));
+                if (SSMR3HandleGetAfter(pSSMHandle) != SSMAFTER_DEBUG_IT)
+                    return VERR_SSM_LOAD_CONFIG_MISMATCH;
+
+                /* skip the buffer if we're loading for the debugger / animator. */
+                uint8_t u8Ignored;
+                size_t cbLeft = pCtl->aIfs[j].cbIOBuffer;
+                while (cbLeft-- > 0)
+                    SSMR3GetU8(pSSMHandle, &u8Ignored);
+            }
+        }
+        else
+            Assert(pCtl->aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+    }
+
+    rc = SSMR3GetU32(pSSMHandle, &u32);
+    if (RT_FAILURE(rc))
+        return rc;
+    if (u32 != ~0U)
+    {
+        AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+        rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+        return rc;
+    }
+
+    return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) ataControllerInit(PPDMDEVINS pDevIns, PAHCIATACONTROLLER pCtl, PPDMIBASE pDrvBaseMaster, PPDMIBASE pDrvBaseSlave,
+                                    uint32_t *pcbSSMState, const char *szName, PPDMLED pLed,
+                                    PSTAMCOUNTER pStatBytesRead, PSTAMCOUNTER pStatBytesWritten)
+{
+    int      rc;
+
+    AssertMsg(pcbSSMState, ("pcbSSMState is invalid\n"));
+
+    pCtl->pDevInsR3 = pDevIns;
+    pCtl->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+    pCtl->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+    for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+    {
+        pCtl->aIfs[j].iLUN              = j;
+        pCtl->aIfs[j].pDevInsR3         = pDevIns;
+        pCtl->aIfs[j].pDevInsR0         = PDMDEVINS_2_R0PTR(pDevIns);
+        pCtl->aIfs[j].pDevInsRC         = PDMDEVINS_2_RCPTR(pDevIns);
+        pCtl->aIfs[j].pControllerR3     = pCtl;
+        pCtl->aIfs[j].pControllerR0     = MMHyperR3ToR0(PDMDevHlpGetVM(pDevIns), pCtl);
+        pCtl->aIfs[j].pControllerRC     = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), pCtl);
+        pCtl->aIfs[j].pLed              = pLed;
+        pCtl->aIfs[j].pStatBytesRead    = pStatBytesRead;
+        pCtl->aIfs[j].pStatBytesWritten = pStatBytesWritten;
+    }
+
+    /* Initialize per-controller critical section */
+    rc = PDMDevHlpCritSectInit(pDevIns, &pCtl->lock, szName);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI ATA: cannot initialize critical section"));
+
+    /*
+     * Attach the units.
+     */
+    uint32_t cbTotalBuffer = 0;
+
+    /*
+     * Start the worker thread.
+     */
+    pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
+    rc = RTSemEventCreate(&pCtl->AsyncIOSem);
+    AssertRC(rc);
+    rc = RTSemEventCreate(&pCtl->SuspendIOSem);
+    AssertRC(rc);
+    rc = RTSemMutexCreate(&pCtl->AsyncIORequestMutex);
+    AssertRC(rc);
+    ataAsyncIOClearRequests(pCtl);
+    rc = RTThreadCreate(&pCtl->AsyncIOThread, ataAsyncIOLoop, (void *)pCtl, 128*1024, RTTHREADTYPE_IO, 0, "ATA");
+    AssertRC(rc);
+    Assert(pCtl->AsyncIOThread != NIL_RTTHREAD && pCtl->AsyncIOSem != NIL_RTSEMEVENT && pCtl->SuspendIOSem != NIL_RTSEMEVENT && pCtl->AsyncIORequestMutex != NIL_RTSEMMUTEX);
+    Log(("%s: controller AIO thread id %#x; sem %p susp_sem %p mutex %p\n", __FUNCTION__, pCtl->AsyncIOThread, pCtl->AsyncIOSem, pCtl->SuspendIOSem, pCtl->AsyncIORequestMutex));
+
+    for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+    {
+        /*
+         * Try attach the block device and get the interfaces,
+         * required as well as optional.
+         */
+        AHCIATADevState *pIf = &pCtl->aIfs[j];
+
+        pIf->pDrvBase = (j == 0) ? pDrvBaseMaster : pDrvBaseSlave;
+
+#if 0
+        rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, s_apszDescs[j]);
+        if (RT_SUCCESS(rc))
+#endif
+        if (pIf->pDrvBase)
+            rc = ataConfigLun(pDevIns, pIf);
+        else
+        {
+            pIf->pDrvBase = NULL;
+            pIf->pDrvBlock = NULL;
+            pIf->cbIOBuffer = 0;
+            pIf->pbIOBufferR3 = NULL;
+            pIf->pbIOBufferR0 = NIL_RTR0PTR;
+            pIf->pbIOBufferRC = NIL_RTRCPTR;
+            LogRel(("AHCI ATA: LUN#%d: no unit\n", pIf->iLUN));
+        }
+        cbTotalBuffer += pIf->cbIOBuffer;
+    }
+
+    *pcbSSMState = cbTotalBuffer;
+
+    /*
+     * Initialize the device state.
+     */
+    ataControllerReset(pCtl);
+
+    return VINF_SUCCESS;
+}
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
Index: trunk/src/VBox/Devices/Storage/DevAHCI.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DevAHCI.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/DevAHCI.cpp	(revision 21321)
@@ -0,0 +1,6596 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * AHCI controller device (disk).
+ * Implements the AHCI standard 1.1
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/** @page pg_dev_ahci   AHCI - Advanced Host Controller Interface Emulation.
+ *
+ * This component implements an AHCI SATA controller.
+ * The device is split into two parts. The first part implements the
+ * register interface for the guest and the second one does the data transfer.
+ *
+ * The guest can access the controller in two ways. The first one is the native
+ * way implementing the registers described in the AHCI specification and is
+ * the preferred one.
+ * The second implements the I/O ports used for booting from the hard disk
+ * and for guests which don't have an AHCI SATA driver.
+ *
+ * The data is transfered in an asychronous way using one thread per implemented
+ * port or using the new async completion interface which is still under development.
+ *
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DEV_AHCI
+#include <VBox/pdmdev.h>
+#include <VBox/pdmqueue.h>
+#include <VBox/pdmthread.h>
+#include <VBox/pdmcritsect.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/param.h>
+# include <iprt/thread.h>
+# include <iprt/semaphore.h>
+# include <iprt/alloc.h>
+# include <iprt/uuid.h>
+# include <iprt/time.h>
+#endif
+
+#include "ide.h"
+#include "ATAController.h"
+#include "../Builtins.h"
+
+#define AHCI_MAX_NR_PORTS_IMPL 30
+#define AHCI_NR_COMMAND_SLOTS 32
+#define AHCI_SAVED_STATE_VERSION 2
+#define AHCI_NR_OF_ALLOWED_BIGGER_LISTS 100
+
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/**
+ * Length of the configurable VPD data (without termination)
+ */
+#define AHCI_SERIAL_NUMBER_LENGTH     20
+#define AHCI_FIRMWARE_REVISION_LENGTH  8
+#define AHCI_MODEL_NUMBER_LENGTH      40
+
+/* Command Header. */
+typedef struct
+{
+    /** Description Information. */
+    uint32_t           u32DescInf;
+    /** Command status. */
+    uint32_t           u32PRDBC;
+    /** Command Table Base Address. */
+    uint32_t           u32CmdTblAddr;
+    /** Command Table Base Address - upper 32-bits. */
+    uint32_t           u32CmdTblAddrUp;
+    /** Reserved */
+    uint32_t           u32Reserved[4];
+} CmdHdr;
+AssertCompileSize(CmdHdr, 32);
+
+/* Defines for the command header. */
+#define AHCI_CMDHDR_PRDTL_MASK 0xffff0000
+#define AHCI_CMDHDR_PRDTL_ENTRIES(x) ((x & AHCI_CMDHDR_PRDTL_MASK) >> 16)
+#define AHCI_CMDHDR_C          RT_BIT(10)
+#define AHCI_CMDHDR_B          RT_BIT(9)
+#define AHCI_CMDHDR_R          RT_BIT(8)
+#define AHCI_CMDHDR_P          RT_BIT(7)
+#define AHCI_CMDHDR_W          RT_BIT(6)
+#define AHCI_CMDHDR_A          RT_BIT(5)
+#define AHCI_CMDHDR_CFL_MASK   0x1f
+
+#define AHCI_CMDHDR_PRDT_OFFSET 0x80
+#define AHCI_CMDHDR_ACMD_OFFSET 0x40
+
+/* Defines for the command FIS. */
+/* Defines that are used in the first double word. */
+#define AHCI_CMDFIS_TYPE                  0 /* The first byte. */
+# define AHCI_CMDFIS_TYPE_H2D             0x27 /* Register - Host to Device FIS. */
+# define AHCI_CMDFIS_TYPE_H2D_SIZE        20   /* Five double words. */
+# define AHCI_CMDFIS_TYPE_D2H             0x34 /* Register - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_D2H_SIZE        20   /* Five double words. */
+# define AHCI_CMDFIS_TYPE_SETDEVBITS      0xa1 /* Set Device Bits - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE 8    /* Two double words. */
+# define AHCI_CMDFIS_TYPE_DMAACTD2H       0x39 /* DMA Activate - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE  4    /* One double word. */
+# define AHCI_CMDFIS_TYPE_DMASETUP        0x41 /* DMA Setup - Bidirectional FIS. */
+# define AHCI_CMDFIS_TYPE_DMASETUP_SIZE   28   /* Seven double words. */
+# define AHCI_CMDFIS_TYPE_PIOSETUP        0x5f /* PIO Setup - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_PIOSETUP_SIZE   20   /* Five double words. */
+# define AHCI_CMDFIS_TYPE_DATA            0x46 /* Data - Bidirectional FIS. */
+
+#define AHCI_CMDFIS_BITS                  1 /* Interrupt and Update bit. */
+#define AHCI_CMDFIS_C                     RT_BIT(7) /* Host to device. */
+#define AHCI_CMDFIS_I                     RT_BIT(6) /* Device to Host. */
+
+#define AHCI_CMDFIS_CMD                   2
+#define AHCI_CMDFIS_FET                   3
+
+#define AHCI_CMDFIS_SECTN                 4
+#define AHCI_CMDFIS_CYLL                  5
+#define AHCI_CMDFIS_CYLH                  6
+#define AHCI_CMDFIS_HEAD                  7
+
+#define AHCI_CMDFIS_SECTNEXP              8
+#define AHCI_CMDFIS_CYLLEXP               9
+#define AHCI_CMDFIS_CYLHEXP               10
+#define AHCI_CMDFIS_FETEXP                11
+
+#define AHCI_CMDFIS_SECTC                 12
+#define AHCI_CMDFIS_SECTCEXP              13
+#define AHCI_CMDFIS_CTL                   15
+# define AHCI_CMDFIS_CTL_SRST             RT_BIT(2) /* Reset device. */
+# define AHCI_CMDFIS_CTL_NIEN             RT_BIT(1) /* Assert or clear interrupt. */
+
+/* For D2H FIS */
+#define AHCI_CMDFIS_STS                   2
+#define AHCI_CMDFIS_ERR                   3
+
+/**
+ * Scatter gather list entry data.
+ */
+typedef struct AHCIPORTTASKSTATESGENTRY
+{
+    /** Flag whether the buffer in the list is from the guest or an
+     *  allocated temporary buffer because the segments in the guest
+     *  are not sector aligned.
+     */
+    bool     fGuestMemory;
+    /** Flag dependent data. */
+    union
+    {
+        /** Data to handle direct mappings of guest buffers. */
+        struct
+        {
+            /** The page lock. */
+            PGMPAGEMAPLOCK  PageLock;
+        } direct;
+        /** Data to handle temporary buffers. */
+        struct
+        {
+            /** The first segment in the guest which is not sector aligned. */
+            RTGCPHYS        GCPhysAddrBaseFirstUnaligned;
+            /** Number of unaligned buffers in the guest. */
+            uint32_t        cUnaligned;
+            /** Pointer to the start of the buffer. */
+            void           *pvBuf;
+        } temp;
+    } u;
+} AHCIPORTTASKSTATESGENTRY, *PAHCIPORTTASKSTATESGENTRY;
+
+/** Pointer to a pointer of a scatter gather list entry. */
+typedef PAHCIPORTTASKSTATESGENTRY *PPAHCIPORTTASKSTATESGENTRY;
+
+/**
+ * A task state.
+ */
+typedef struct AHCIPORTTASKSTATE
+{
+    /** Tag of the task. */
+    uint32_t                   uTag;
+    /** Command is queued. */
+    bool                       fQueued;
+    /** The command header for this task. */
+    CmdHdr                     cmdHdr;
+    /** The command Fis for this task. */
+    uint8_t                    cmdFis[AHCI_CMDFIS_TYPE_H2D_SIZE];
+    /** The ATAPI comnmand data. */
+    uint8_t                    aATAPICmd[ATAPI_PACKET_SIZE];
+    /** Physical address of the command header. - GC */
+    RTGCPHYS                   GCPhysCmdHdrAddr;
+    /** Data direction. */
+    uint8_t                    uTxDir;
+    /** Start offset. */
+    uint64_t                   uOffset;
+    /** Number of bytes to transfer. */
+    uint32_t                   cbTransfer;
+    /** ATA error register */
+    uint8_t                    uATARegError;
+    /** ATA status register */
+    uint8_t                    uATARegStatus;
+    /** Number of scatter gather list entries. */
+    uint32_t                   cSGEntries;
+    /** How many entries would fit into the sg list. */
+    uint32_t                   cSGListSize;
+    /** Pointer to the first entry of the scatter gather list. */
+    PPDMDATASEG                pSGListHead;
+    /** Pointer to the first mapping information entry. */
+    PAHCIPORTTASKSTATESGENTRY  paSGEntries;
+    /** Size of the temporary buffer for unaligned guest segments. */
+    uint32_t                   cbBufferUnaligned;
+    /** Pointer to the temporary buffer. */
+    void                      *pvBufferUnaligned;
+    /** Number of times in a row the scatter gather list was too big. */
+    uint32_t                   cSGListTooBig;
+} AHCIPORTTASKSTATE, *PAHCIPORTTASKSTATE;
+
+/**
+ * Notifier queue item.
+ */
+typedef struct DEVPORTNOTIFIERQUEUEITEM
+{
+    /** The core part owned by the queue manager. */
+    PDMQUEUEITEMCORE    Core;
+    /** On which port the async io thread should be put into action. */
+    uint8_t             iPort;
+    /** Which task to process. */
+    uint8_t             iTask;
+    /** Flag whether the task is queued. */
+    uint8_t             fQueued;
+} DEVPORTNOTIFIERQUEUEITEM, *PDEVPORTNOTIFIERQUEUEITEM;
+
+typedef struct AHCIPort
+{
+    /** Pointer to the device instance - HC ptr */
+    PPDMDEVINSR3                    pDevInsR3;
+    /** Pointer to the device instance - R0 ptr */
+    PPDMDEVINSR0                    pDevInsR0;
+    /** Pointer to the device instance - RC ptr. */
+    PPDMDEVINSRC                    pDevInsRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment0;
+#endif
+
+    /** Pointer to the parent AHCI structure - R3 ptr. */
+    R3PTRTYPE(struct AHCI *)        pAhciR3;
+    /** Pointer to the parent AHCI structure - R0 ptr. */
+    R0PTRTYPE(struct AHCI *)        pAhciR0;
+    /** Pointer to the parent AHCI structure - RC ptr. */
+    RCPTRTYPE(struct AHCI *)        pAhciRC;
+    /** Command List Base Address. */
+    uint32_t                        regCLB;
+    /** Command List Base Address upper bits. */
+    uint32_t                        regCLBU;
+    /** FIS Base Address. */
+    uint32_t                        regFB;
+    /** FIS Base Address upper bits. */
+    uint32_t                        regFBU;
+    /** Interrupt Status. */
+    uint32_t                        regIS;
+    /** Interrupt Enable. */
+    uint32_t                        regIE;
+    /** Command. */
+    uint32_t                        regCMD;
+    /** Task File Data. */
+    uint32_t                        regTFD;
+    /** Signature */
+    uint32_t                        regSIG;
+    /** Serial ATA Status. */
+    uint32_t                        regSSTS;
+    /** Serial ATA Control. */
+    uint32_t                        regSCTL;
+    /** Serial ATA Error. */
+    uint32_t                        regSERR;
+    /** Serial ATA Active. */
+    uint32_t                        regSACT;
+    /** Command Issue. */
+    uint32_t                        regCI;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment1;
+#endif
+
+    /** Command List Base Address */
+    volatile RTGCPHYS               GCPhysAddrClb;
+    /** FIS Base Address */
+    volatile RTGCPHYS               GCPhysAddrFb;
+
+    /** If we use the new async interface. */
+    bool                            fAsyncInterface;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment2;
+#endif
+
+    /** Async IO Thread. */
+    PPDMTHREAD                      pAsyncIOThread;
+    /** Request semaphore. */
+    RTSEMEVENT                      AsyncIORequestSem;
+
+    /** Task queue. */
+    volatile uint8_t                ahciIOTasks[2*AHCI_NR_COMMAND_SLOTS];
+    /** Actual write position. */
+    uint8_t                         uActWritePos;
+    /** Actual read position. */
+    uint8_t                         uActReadPos;
+    /** Actual number of active tasks. */
+    volatile uint32_t               uActTasksActive;
+
+    /** Device is powered on. */
+    bool                            fPoweredOn;
+    /** Device has spun up. */
+    bool                            fSpunUp;
+    /** First D2H FIS was send. */
+    bool                            fFirstD2HFisSend;
+    /** Attached device is a CD/DVD drive. */
+    bool                            fATAPI;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment3;
+#endif
+
+    /** Device specific settings. */
+    /** Pointer to the attached driver's base interface. */
+    R3PTRTYPE(PPDMIBASE)            pDrvBase;
+    /** Pointer to the attached driver's block interface. */
+    R3PTRTYPE(PPDMIBLOCK)           pDrvBlock;
+    /** Pointer to the attached driver's async block interface. */
+    R3PTRTYPE(PPDMIBLOCKASYNC)      pDrvBlockAsync;
+    /** Pointer to the attached driver's block bios interface. */
+    R3PTRTYPE(PPDMIBLOCKBIOS)       pDrvBlockBios;
+    /** Pointer to the attached driver's mount interface. */
+    R3PTRTYPE(PPDMIMOUNT)           pDrvMount;
+    /** The base interface. */
+    PDMIBASE                        IBase;
+    /** The block port interface. */
+    PDMIBLOCKPORT                   IPort;
+    /** The optional block async port interface. */
+    PDMIBLOCKASYNCPORT              IPortAsync;
+    /** The mount notify interface. */
+    PDMIMOUNTNOTIFY                 IMountNotify;
+    /** Physical geometry of this image. */
+    PDMMEDIAGEOMETRY                PCHSGeometry;
+    /** The status LED state for this drive. */
+    PDMLED                          Led;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment4;
+#endif
+
+    /** Number of total sectors. */
+    uint64_t                        cTotalSectors;
+    /** Currently configured number of sectors in a multi-sector transfer. */
+    uint32_t                        cMultSectors;
+    /** Currently active transfer mode (MDMA/UDMA) and speed. */
+    uint8_t                         uATATransferMode;
+    /** ATAPI sense key. */
+    uint8_t                         uATAPISenseKey;
+    /** ATAPI additional sens code. */
+    uint8_t                         uATAPIASC;
+    /** HACK: Countdown till we report a newly unmounted drive as mounted. */
+    uint8_t                         cNotifiedMediaChange;
+
+    /** The LUN. */
+    RTUINT                          iLUN;
+    /** Flag if we are in a device reset. */
+    bool                            fResetDevice;
+
+    /** Bitmask for finished tasks. */
+    volatile uint32_t               u32TasksFinished;
+    /** Bitmask for finished queued tasks. */
+    volatile uint32_t               u32QueuedTasksFinished;
+
+    /**
+     * Array of cached tasks. The tag number is the index value.
+     * Only used with the async interface.
+     */
+    R3PTRTYPE(PAHCIPORTTASKSTATE)   aCachedTasks[AHCI_NR_COMMAND_SLOTS];
+
+    /** Release statistics: number of DMA commands. */
+    STAMCOUNTER                     StatDMA;
+    /** Release statistics: number of bytes written. */
+    STAMCOUNTER                     StatBytesWritten;
+    /** Release statistics: number of bytes read. */
+    STAMCOUNTER                     StatBytesRead;
+    /** Release statistics: Number of I/O requests processed per second. */
+    STAMCOUNTER                     StatIORequestsPerSecond;
+#ifdef VBOX_WITH_STATISTICS
+    /** Statistics: Time to complete one request. */
+    STAMPROFILE                     StatProfileProcessTime;
+    /** Statistics: Time to map requests into R3. */
+    STAMPROFILE                     StatProfileMapIntoR3;
+    /** Statistics: Amount of time to read/write data. */
+    STAMPROFILE                     StatProfileReadWrite;
+    /** Statistics: Amount of time to destroy a list. */
+    STAMPROFILE                     StatProfileDestroyScatterGatherList;
+#endif /* VBOX_WITH_STATISTICS */
+    /** Flag whether a notification was already send to R3. */
+    volatile bool                   fNotificationSend;
+    /** Flag whether this port is in a reset state. */
+    volatile bool                   fPortReset;
+    /** Flag whether the I/O thread idles. */
+    volatile bool                   fAsyncIOThreadIdle;
+
+    /** The serial numnber to use for IDENTIFY DEVICE commands. */
+    char                            szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1]; /** < one extra byte for termination */
+    /** The firmware revision to use for IDENTIFY DEVICE commands. */
+    char                            szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1]; /** < one extra byte for termination */
+    /** The model number to use for IDENTIFY DEVICE commands. */
+    char                            szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1]; /** < one extra byte for termination */
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment5;
+#endif
+
+} AHCIPort, *PAHCIPort;
+
+/*
+ * Main AHCI device state.
+ */
+typedef struct AHCI
+{
+    /** The PCI device structure. */
+    PCIDEVICE                       dev;
+    /** Pointer to the device instance - R3 ptr */
+    PPDMDEVINSR3                    pDevInsR3;
+    /** Pointer to the device instance - R0 ptr */
+    PPDMDEVINSR0                    pDevInsR0;
+    /** Pointer to the device instance - RC ptr. */
+    PPDMDEVINSRC                    pDevInsRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment0;
+#endif
+
+    /** The base interface */
+    PDMIBASE                        IBase;
+    /** Status Port - Leds interface. */
+    PDMILEDPORTS                    ILeds;
+    /** Partner of ILeds. */
+    R3PTRTYPE(PPDMILEDCONNECTORS)   pLedsConnector;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment1[2];
+#endif
+
+    /** Base address of the MMIO region. */
+    RTGCPHYS                        MMIOBase;
+
+    /** Global Host Control register of the HBA */
+
+    /** HBA Capabilities - Readonly */
+    uint32_t                        regHbaCap;
+    /** HBA Control */
+    uint32_t                        regHbaCtrl;
+    /** Interrupt Status */
+    uint32_t                        regHbaIs;
+    /** Ports Implemented - Readonly */
+    uint32_t                        regHbaPi;
+    /** AHCI Version - Readonly */
+    uint32_t                        regHbaVs;
+    /** Command completion coalescing control */
+    uint32_t                        regHbaCccCtl;
+    /** Command completion coalescing ports */
+    uint32_t                        regHbaCccPorts;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment3;
+#endif
+
+    /** Countdown timer for command completion coalescing - R3 ptr */
+    PTMTIMERR3                      pHbaCccTimerR3;
+    /** Countdown timer for command completion coalescing - R0 ptr */
+    PTMTIMERR0                      pHbaCccTimerR0;
+    /** Countdown timer for command completion coalescing - RC ptr */
+    PTMTIMERRC                      pHbaCccTimerRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment4;
+#endif
+
+    /** Queue to send tasks to R3. - HC ptr */
+    R3PTRTYPE(PPDMQUEUE)            pNotifierQueueR3;
+    /** Queue to send tasks to R3. - HC ptr */
+    R0PTRTYPE(PPDMQUEUE)            pNotifierQueueR0;
+    /** Queue to send tasks to R3. - RC ptr */
+    RCPTRTYPE(PPDMQUEUE)            pNotifierQueueRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment5;
+#endif
+
+
+    /** Which port number is used to mark an CCC interrupt */
+    uint8_t                         uCccPortNr;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment6;
+#endif
+
+    /** Timeout value */
+    uint64_t                        uCccTimeout;
+    /** Number of completions used to assert an interrupt */
+    uint32_t                        uCccNr;
+    /** Current number of completed commands */
+    uint32_t                        uCccCurrentNr;
+
+    /** Register structure per port */
+    AHCIPort                        ahciPort[AHCI_MAX_NR_PORTS_IMPL];
+
+    /** Needed values for the emulated ide channels. */
+    AHCIATACONTROLLER               aCts[2];
+
+    /** Bitmask of ports which asserted an interrupt. */
+    uint32_t                        u32PortsInterrupted;
+    /** Device is in a reset state. */
+    bool                            fReset;
+    /** Supports 64bit addressing */
+    bool                            f64BitAddr;
+    /** GC enabled. */
+    bool                            fGCEnabled;
+    /** R0 enabled. */
+    bool                            fR0Enabled;
+    /** If the new async interface is used if available. */
+    bool                            fUseAsyncInterfaceIfAvailable;
+    /** The critical section. */
+    PDMCRITSECT                     lock;
+
+    /** Number of usable ports on this controller. */
+    uint32_t                        cPortsImpl;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment7;
+#endif
+
+    /** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */
+    volatile bool                   f8ByteMMIO4BytesWrittenSuccessfully;
+    /** At which number of I/O requests per second we consider having high I/O load. */
+    uint32_t                        cHighIOThreshold;
+    /** How many milliseconds to sleep. */
+    uint32_t                        cMillisToSleep;
+
+} AHCI, *PAHCI;
+
+/* Scatter gather list entry. */
+typedef struct
+{
+    /** Data Base Address. */
+    uint32_t           u32DBA;
+    /** Data Base Address - Upper 32-bits. */
+    uint32_t           u32DBAUp;
+    /** Reserved */
+    uint32_t           u32Reserved;
+    /** Description information. */
+    uint32_t           u32DescInf;
+} SGLEntry;
+AssertCompileSize(SGLEntry, 16);
+
+/** Defines for a scatter gather list entry. */
+#define SGLENTRY_DBA_READONLY     ~(RT_BIT(0))
+#define SGLENTRY_DESCINF_I        RT_BIT(31)
+#define SGLENTRY_DESCINF_DBC      0x3fffff
+#define SGLENTRY_DESCINF_READONLY 0x803fffff
+
+/* Defines for the global host control registers for the HBA. */
+
+#define AHCI_HBA_GLOBAL_SIZE 0x100
+
+/* Defines for the HBA Capabilities - Readonly */
+#define AHCI_HBA_CAP_S64A RT_BIT(31)
+#define AHCI_HBA_CAP_SNCQ RT_BIT(30)
+#define AHCI_HBA_CAP_SIS  RT_BIT(28)
+#define AHCI_HBA_CAP_SSS  RT_BIT(27)
+#define AHCI_HBA_CAP_SALP RT_BIT(26)
+#define AHCI_HBA_CAP_SAL  RT_BIT(25)
+#define AHCI_HBA_CAP_SCLO RT_BIT(24)
+#define AHCI_HBA_CAP_ISS  (RT_BIT(23) | RT_BIT(22) | RT_BIT(21) | RT_BIT(20))
+# define AHCI_HBA_CAP_ISS_SHIFT(x) (((x) << 20) & AHCI_HBA_CAP_ISS)
+# define AHCI_HBA_CAP_ISS_GEN1 RT_BIT(0)
+# define AHCI_HBA_CAP_ISS_GEN2 RT_BIT(1)
+#define AHCI_HBA_CAP_SNZO RT_BIT(19)
+#define AHCI_HBA_CAP_SAM  RT_BIT(18)
+#define AHCI_HBA_CAP_SPM  RT_BIT(17)
+#define AHCI_HBA_CAP_PMD  RT_BIT(15)
+#define AHCI_HBA_CAP_SSC  RT_BIT(14)
+#define AHCI_HBA_CAP_PSC  RT_BIT(13)
+#define AHCI_HBA_CAP_NCS  (RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_HBA_CAP_NCS_SET(x) (((x-1) << 8) & AHCI_HBA_CAP_NCS) /* 0's based */
+#define AHCI_HBA_CAP_CCCS  RT_BIT(7)
+#define AHCI_HBA_CAP_NP   (RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_HBA_CAP_NP_SET(x) ((x-1) & AHCI_HBA_CAP_NP) /* 0's based */
+
+/* Defines for the HBA Control register - Read/Write */
+#define AHCI_HBA_CTRL_AE  RT_BIT(31)
+#define AHCI_HBA_CTRL_IE  RT_BIT(1)
+#define AHCI_HBA_CTRL_HR  RT_BIT(0)
+#define AHCI_HBA_CTRL_RW_MASK (RT_BIT(0) | RT_BIT(1)) /* Mask for the used bits */
+
+/* Defines for the HBA Version register - Readonly (We support AHCI 1.0) */
+#define AHCI_HBA_VS_MJR   (1 << 16)
+#define AHCI_HBA_VS_MNR   0x100
+
+/* Defines for the command completion coalescing control register */
+#define AHCI_HBA_CCC_CTL_TV 0xffff0000
+#define AHCI_HBA_CCC_CTL_TV_SET(x) (x << 16)
+#define AHCI_HBA_CCC_CTL_TV_GET(x) ((x & AHCI_HBA_CCC_CTL_TV) >> 16)
+
+#define AHCI_HBA_CCC_CTL_CC 0xff00
+#define AHCI_HBA_CCC_CTL_CC_SET(x) (x << 8)
+#define AHCI_HBA_CCC_CTL_CC_GET(x) ((x & AHCI_HBA_CCC_CTL_CC) >> 8)
+
+#define AHCI_HBA_CCC_CTL_INT 0xf8
+#define AHCI_HBA_CCC_CTL_INT_SET(x) (x << 3)
+#define AHCI_HBA_CCC_CTL_INT_GET(x) ((x & AHCI_HBA_CCC_CTL_INT) >> 3)
+
+#define AHCI_HBA_CCC_CTL_EN  RT_BIT(0)
+
+/* Defines for the port registers. */
+
+#define AHCI_PORT_REGISTER_SIZE 0x80
+
+#define AHCI_PORT_CLB_RESERVED 0xfffffc00 /* For masking out the reserved bits. */
+
+#define AHCI_PORT_FB_RESERVED  0x7fffff00 /* For masking out the reserved bits. */
+
+#define AHCI_PORT_IS_CPDS      RT_BIT(31)
+#define AHCI_PORT_IS_TFES      RT_BIT(30)
+#define AHCI_PORT_IS_HBFS      RT_BIT(29)
+#define AHCI_PORT_IS_HBDS      RT_BIT(28)
+#define AHCI_PORT_IS_IFS       RT_BIT(27)
+#define AHCI_PORT_IS_INFS      RT_BIT(26)
+#define AHCI_PORT_IS_OFS       RT_BIT(24)
+#define AHCI_PORT_IS_IPMS      RT_BIT(23)
+#define AHCI_PORT_IS_PRCS      RT_BIT(22)
+#define AHCI_PORT_IS_DIS       RT_BIT(7)
+#define AHCI_PORT_IS_PCS       RT_BIT(6)
+#define AHCI_PORT_IS_DPS       RT_BIT(5)
+#define AHCI_PORT_IS_UFS       RT_BIT(4)
+#define AHCI_PORT_IS_SDBS      RT_BIT(3)
+#define AHCI_PORT_IS_DSS       RT_BIT(2)
+#define AHCI_PORT_IS_PSS       RT_BIT(1)
+#define AHCI_PORT_IS_DHRS      RT_BIT(0)
+#define AHCI_PORT_IS_READONLY  0xfd8000af /* Readonly mask including reserved bits. */
+
+#define AHCI_PORT_IE_CPDE      RT_BIT(31)
+#define AHCI_PORT_IE_TFEE      RT_BIT(30)
+#define AHCI_PORT_IE_HBFE      RT_BIT(29)
+#define AHCI_PORT_IE_HBDE      RT_BIT(28)
+#define AHCI_PORT_IE_IFE       RT_BIT(27)
+#define AHCI_PORT_IE_INFE      RT_BIT(26)
+#define AHCI_PORT_IE_OFE       RT_BIT(24)
+#define AHCI_PORT_IE_IPME      RT_BIT(23)
+#define AHCI_PORT_IE_PRCE      RT_BIT(22)
+#define AHCI_PORT_IE_DIE       RT_BIT(7)  /* Not supported for now, readonly. */
+#define AHCI_PORT_IE_PCE       RT_BIT(6)
+#define AHCI_PORT_IE_DPE       RT_BIT(5)
+#define AHCI_PORT_IE_UFE       RT_BIT(4)
+#define AHCI_PORT_IE_SDBE      RT_BIT(3)
+#define AHCI_PORT_IE_DSE       RT_BIT(2)
+#define AHCI_PORT_IE_PSE       RT_BIT(1)
+#define AHCI_PORT_IE_DHRE      RT_BIT(0)
+#define AHCI_PORT_IE_READONLY  (0xfdc000ff) /* Readonly mask including reserved bits. */
+
+#define AHCI_PORT_CMD_ICC      (RT_BIT(28) | RT_BIT(29) | RT_BIT(30) | RT_BIT(31))
+#define AHCI_PORT_CMD_ICC_SHIFT(x) ((x) << 28)
+# define AHCI_PORT_CMD_ICC_IDLE    0x0
+# define AHCI_PORT_CMD_ICC_ACTIVE  0x1
+# define AHCI_PORT_CMD_ICC_PARTIAL 0x2
+# define AHCI_PORT_CMD_ICC_SLUMBER 0x6
+#define AHCI_PORT_CMD_ASP      RT_BIT(27) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_ALPE     RT_BIT(26) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_DLAE     RT_BIT(25)
+#define AHCI_PORT_CMD_ATAPI    RT_BIT(24)
+#define AHCI_PORT_CMD_CPD      RT_BIT(20)
+#define AHCI_PORT_CMD_ISP      RT_BIT(19) /* Readonly */
+#define AHCI_PORT_CMD_HPCP     RT_BIT(18)
+#define AHCI_PORT_CMD_PMA      RT_BIT(17) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_CPS      RT_BIT(16)
+#define AHCI_PORT_CMD_CR       RT_BIT(15) /* Readonly */
+#define AHCI_PORT_CMD_FR       RT_BIT(14) /* Readonly */
+#define AHCI_PORT_CMD_ISS      RT_BIT(13) /* Readonly */
+#define AHCI_PORT_CMD_CCS      (RT_BIT(8) | RT_BIT(9) | RT_BIT(10) | RT_BIT(11) | RT_BIT(12))
+#define AHCI_PORT_CMD_CCS_SHIFT(x) (x << 8) /* Readonly */
+#define AHCI_PORT_CMD_FRE      RT_BIT(4)
+#define AHCI_PORT_CMD_CLO      RT_BIT(3)
+#define AHCI_PORT_CMD_POD      RT_BIT(2)
+#define AHCI_PORT_CMD_SUD      RT_BIT(1)
+#define AHCI_PORT_CMD_ST       RT_BIT(0)
+#define AHCI_PORT_CMD_READONLY (0xff02001f & ~(AHCI_PORT_CMD_ASP | AHCI_PORT_CMD_ALPE | AHCI_PORT_CMD_PMA))
+
+#define AHCI_PORT_SCTL_IPM         (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_PORT_SCTL_IPM_GET(x)  ((x & AHCI_PORT_SCTL_IPM) >> 8)
+#define AHCI_PORT_SCTL_SPD         (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))
+#define AHCI_PORT_SCTL_SPD_GET(x)  ((x & AHCI_PORT_SCTL_SPD) >> 4)
+#define AHCI_PORT_SCTL_DET         (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_PORT_SCTL_DET_GET(x)  (x & AHCI_PORT_SCTL_DET)
+#define AHCI_PORT_SCTL_DET_NINIT   0
+#define AHCI_PORT_SCTL_DET_INIT    1
+#define AHCI_PORT_SCTL_DET_OFFLINE 4
+#define AHCI_PORT_SCTL_READONLY    0xfff
+
+#define AHCI_PORT_SSTS_IPM         (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_PORT_SSTS_IPM_GET(x)  ((x & AHCI_PORT_SCTL_IPM) >> 8)
+#define AHCI_PORT_SSTS_SPD         (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))
+#define AHCI_PORT_SSTS_SPD_GET(x)  ((x & AHCI_PORT_SCTL_SPD) >> 4)
+#define AHCI_PORT_SSTS_DET         (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_PORT_SSTS_DET_GET(x)  (x & AHCI_PORT_SCTL_DET)
+
+#define AHCI_PORT_TFD_BSY          RT_BIT(7)
+#define AHCI_PORT_TFD_DRQ          RT_BIT(3)
+#define AHCI_PORT_TFD_ERR          RT_BIT(0)
+
+#define AHCI_PORT_SERR_X           RT_BIT(26)
+#define AHCI_PORT_SERR_W           RT_BIT(18)
+#define AHCI_PORT_SERR_N           RT_BIT(16)
+
+/* Signatures for attached storage devices. */
+#define AHCI_PORT_SIG_DISK         0x00000101
+#define AHCI_PORT_SIG_ATAPI        0xeb140101
+
+/*
+ * The AHCI spec defines an area of memory where the HBA posts received FIS's from the device.
+ * regFB points to the base of this area.
+ * Every FIS type has an offset where it is posted in this area.
+ */
+#define AHCI_RECFIS_DSFIS_OFFSET  0x00 /* DMA Setup FIS */
+#define AHCI_RECFIS_PSFIS_OFFSET  0x20 /* PIO Setup FIS */
+#define AHCI_RECFIS_RFIS_OFFSET   0x40 /* D2H Register FIS */
+#define AHCI_RECFIS_SDBFIS_OFFSET 0x58 /* Set Device Bits FIS */
+#define AHCI_RECFIS_UFIS_OFFSET   0x60 /* Unknown FIS type */
+
+#define AHCI_TASK_IS_QUEUED(x) ((x) & 0x1)
+#define AHCI_TASK_GET_TAG(x)   ((x) >> 1)
+#define AHCI_TASK_SET(tag, queued) (((tag) << 1) | (queued))
+
+/**
+ * AHCI register operator.
+ */
+typedef struct ahci_opreg
+{
+    const char *pszName;
+    int (*pfnRead )(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value);
+    int (*pfnWrite)(PAHCI ahci, uint32_t iReg, uint32_t u32Value);
+} AHCIOPREG;
+
+/**
+ * AHCI port register operator.
+ */
+typedef struct pAhciPort_opreg
+{
+    const char *pszName;
+    int (*pfnRead )(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value);
+    int (*pfnWrite)(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value);
+} AHCIPORTOPREG;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+RT_C_DECLS_BEGIN
+PDMBOTHCBDECL(int) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+PDMBOTHCBDECL(int) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+static void ahciHBAReset(PAHCI pThis);
+PDMBOTHCBDECL(int) ahciIOPortWrite1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ahciIOPortRead1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) ahciIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ahciIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+#ifdef IN_RING3
+static int ahciPostFisIntoMemory(PAHCIPort pAhciPort, unsigned uFisType, uint8_t *cmdFis);
+static void ahciPostFirstD2HFisIntoMemory(PAHCIPort pAhciPort);
+static int ahciScatterGatherListCopyFromBuffer(PAHCIPORTTASKSTATE pAhciPortTaskState, void *pvBuf, size_t cbBuf);
+static int ahciScatterGatherListCreate(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fReadonly);
+static int ahciScatterGatherListDestroy(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState);
+static void ahciCopyFromBufferIntoSGList(PPDMDEVINS pDevIns, PAHCIPORTTASKSTATESGENTRY pSGInfo);
+static void ahciCopyFromSGListIntoBuffer(PPDMDEVINS pDevIns, PAHCIPORTTASKSTATESGENTRY pSGInfo);
+#endif
+RT_C_DECLS_END
+
+#define PCIDEV_2_PAHCI(pPciDev)                  ( (PAHCI)(pPciDev) )
+#define PDMIMOUNT_2_PAHCIPORT(pInterface)        ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IMount)) )
+#define PDMIMOUNTNOTIFY_2_PAHCIPORT(pInterface)  ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IMountNotify)) )
+#define PDMIBASE_2_PAHCIPORT(pInterface)         ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IBase)) )
+#define PDMIBASE_2_PAHCI(pInterface)             ( (PAHCI)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCI, IBase)) )
+#define PDMILEDPORTS_2_PAHCI(pInterface)         ( (PAHCI)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCI, ILeds)) )
+
+#if 1
+#define AHCI_RTGCPHYS_FROM_U32(Hi, Lo)             ( (RTGCPHYS)RT_MAKE_U64(Lo, Hi) )
+#else
+#define AHCI_RTGCPHYS_FROM_U32(Hi, Lo)             ( (RTGCPHYS)(Lo) )
+#endif
+
+#ifdef IN_RING3
+
+# ifdef LOG_USE_C99
+#  define ahciLog(a) \
+     Log(("R3 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+#  define ahciLog(a) \
+     do { Log(("R3 P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#elif IN_RING0
+
+# ifdef LOG_USE_C99
+#  define ahciLog(a) \
+     Log(("R0 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+#  define ahciLog(a) \
+     do { Log(("R0 P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#elif IN_RC
+
+# ifdef LOG_USE_C99
+#  define ahciLog(a) \
+     Log(("GC P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+#  define ahciLog(a) \
+     do { Log(("GC P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#endif
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ahciHbaClearInterrupt(PAHCI pAhci)
+{
+    Log(("%s: Clearing interrupt\n", __FUNCTION__));
+    PDMDevHlpPCISetIrqNoWait(pAhci->CTX_SUFF(pDevIns), 0, 0);
+}
+
+/**
+ * Updates the IRQ level and sets port bit in the global interrupt status register of the HBA.
+ */
+static void ahciHbaSetInterrupt(PAHCI pAhci, uint8_t iPort)
+{
+    Log(("P%u: %s: Setting interrupt\n", iPort, __FUNCTION__));
+
+    PDMCritSectEnter(&pAhci->lock, VINF_SUCCESS);
+
+    if (pAhci->regHbaCtrl & AHCI_HBA_CTRL_IE)
+    {
+        if ((pAhci->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN) && (pAhci->regHbaCccPorts & (1 << iPort)))
+        {
+            pAhci->uCccCurrentNr++;
+            if (pAhci->uCccCurrentNr >= pAhci->uCccNr)
+            {
+                /* Reset command completion coalescing state. */
+                TMTimerSetMillies(pAhci->CTX_SUFF(pHbaCccTimer), pAhci->uCccTimeout);
+                pAhci->uCccCurrentNr = 0;
+
+                pAhci->u32PortsInterrupted |= (1 << pAhci->uCccPortNr);
+                if (!(pAhci->u32PortsInterrupted & ~(1 << pAhci->uCccPortNr)))
+                {
+                    Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+                    PDMDevHlpPCISetIrqNoWait(pAhci->CTX_SUFF(pDevIns), 0, 1);
+                }
+            }
+        }
+        else
+        {
+            /* If only the bit of the actual port is set assert an interrupt
+             * because the interrupt status register was already read by the guest
+             * and we need to send a new notification.
+             * Otherwise an interrupt is still pending.
+             */
+            ASMAtomicOrU32((volatile uint32_t *)&pAhci->u32PortsInterrupted, (1 << iPort));
+            if (!(pAhci->u32PortsInterrupted & ~(1 << iPort)))
+            {
+                Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+                PDMDevHlpPCISetIrqNoWait(pAhci->CTX_SUFF(pDevIns), 0, 1);
+            }
+        }
+    }
+
+    PDMCritSectLeave(&pAhci->lock);
+}
+
+#ifdef IN_RING3
+/*
+ * Assert irq when an CCC timeout occurs
+ */
+DECLCALLBACK(void) ahciCccTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+    PAHCI pAhci = (PAHCI)pvUser;
+
+    ahciHbaSetInterrupt(pAhci, pAhci->uCccPortNr);
+}
+#endif
+
+static int PortCmdIssue_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    uint32_t uCIValue;
+
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    /* Update the CI register first. */
+    uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0);
+    pAhciPort->regCI &= ~uCIValue;
+
+
+    if ((pAhciPort->regCMD & AHCI_PORT_CMD_ST) && (u32Value > 0))
+    {
+        PDEVPORTNOTIFIERQUEUEITEM pItem;
+
+        /* Mark the tasks set in the value as used. */
+        for (uint8_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
+        {
+            /* Queue task if bit is set in written value and not already in progress. */
+            if (((u32Value >> i) & 0x01) && !(pAhciPort->regCI & (1 << i)))
+            {
+                if (!pAhciPort->fAsyncInterface)
+                {
+                    /* Put the tag number of the task into the FIFO. */
+                    uint8_t uTag = AHCI_TASK_SET(i, ((pAhciPort->regSACT & (1 << i)) ? 1 : 0));
+                    ASMAtomicWriteU8(&pAhciPort->ahciIOTasks[pAhciPort->uActWritePos], uTag);
+                    ahciLog(("%s: Before uActWritePos=%u\n", __FUNCTION__, pAhciPort->uActWritePos));
+                    pAhciPort->uActWritePos++;
+                    pAhciPort->uActWritePos %= RT_ELEMENTS(pAhciPort->ahciIOTasks);
+                    ahciLog(("%s: After uActWritePos=%u\n", __FUNCTION__, pAhciPort->uActWritePos));
+
+                    ASMAtomicIncU32(&pAhciPort->uActTasksActive);
+
+                    bool fNotificationSend = ASMAtomicXchgBool(&pAhciPort->fNotificationSend, true);
+                    if (!fNotificationSend)
+                    {
+                        /* Send new notification. */
+                        pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(ahci->CTX_SUFF(pNotifierQueue));
+                        AssertMsg(pItem, ("Allocating item for queue failed\n"));
+
+                        pItem->iPort = pAhciPort->iLUN;
+                        PDMQueueInsert(ahci->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+                    }
+                }
+                else
+                {
+                    pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(ahci->CTX_SUFF(pNotifierQueue));
+                    AssertMsg(pItem, ("Allocating item for queue failed\n"));
+
+                    pItem->iPort = pAhciPort->iLUN;
+                    pItem->iTask = i;
+                    pItem->fQueued = !!(pAhciPort->regSACT & (1 << i)); /* Mark if the task is queued. */
+                    PDMQueueInsert(ahci->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+                }
+            }
+        }
+    }
+
+    pAhciPort->regCI |= u32Value;
+
+    return VINF_SUCCESS;
+}
+
+static int PortCmdIssue_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    uint32_t uCIValue = 0;
+
+    uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0);
+
+    ahciLog(("%s: read regCI=%#010x uCIValue=%#010x\n", __FUNCTION__, pAhciPort->regCI, uCIValue));
+
+    pAhciPort->regCI &= ~uCIValue;
+
+    *pu32Value = pAhciPort->regCI;
+
+    return VINF_SUCCESS;
+}
+
+static int PortSActive_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    pAhciPort->regSACT |= u32Value;
+
+    return VINF_SUCCESS;
+}
+
+static int PortSActive_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    uint32_t u32TasksFinished = ASMAtomicXchgU32(&pAhciPort->u32QueuedTasksFinished, 0);
+
+    pAhciPort->regSACT &= ~u32TasksFinished;
+
+    ahciLog(("%s: read regSACT=%#010x regCI=%#010x u32TasksFinished=%#010x\n",
+             __FUNCTION__, pAhciPort->regSACT, pAhciPort->regCI, u32TasksFinished));
+
+    *pu32Value = pAhciPort->regSACT;
+
+    return VINF_SUCCESS;
+}
+
+static int PortSError_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+    pAhciPort->regSERR &= ~u32Value;
+    if (u32Value & AHCI_PORT_SERR_X)
+    {
+        pAhciPort->regTFD |= ATA_STAT_ERR;
+        pAhciPort->regTFD &= ~(ATA_STAT_DRQ | ATA_STAT_BUSY);
+    }
+    return VINF_SUCCESS;
+}
+
+static int PortSError_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regSERR=%#010x\n", __FUNCTION__, pAhciPort->regSERR));
+    *pu32Value = pAhciPort->regSERR;
+    return VINF_SUCCESS;
+}
+
+static int PortSControl_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+    ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+             AHCI_PORT_SCTL_IPM_GET(u32Value), AHCI_PORT_SCTL_SPD_GET(u32Value), AHCI_PORT_SCTL_DET_GET(u32Value)));
+
+    if ((u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT)
+    {
+        ASMAtomicXchgBool(&pAhciPort->fPortReset, true);
+        pAhciPort->regSSTS = 0;
+        pAhciPort->regSIG  = ~0;
+        pAhciPort->regTFD  = 0x7f;
+        pAhciPort->fFirstD2HFisSend = false;
+    }
+    else if ((u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_NINIT && pAhciPort->pDrvBase &&
+             (pAhciPort->regSCTL & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT)
+    {
+#ifndef IN_RING3
+        return VINF_IOM_HC_MMIO_WRITE;
+#else
+        if (pAhciPort->pDrvBase)
+        {
+            /* Reset queue. */
+            pAhciPort->uActWritePos = 0;
+            pAhciPort->uActReadPos = 0;
+            ASMAtomicXchgBool(&pAhciPort->fPortReset, false);
+
+            /* Signature for SATA device. */
+            if (pAhciPort->fATAPI)
+                pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+            else
+                pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+
+            pAhciPort->regSSTS = (0x01 << 8)  | /* Interface is active. */
+                                 (0x03 << 0);   /* Device detected and communication established. */
+
+            /*
+             * Use the maximum allowed speed.
+             * (Not that it changes anything really)
+             */
+            switch (AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL))
+            {
+                case 0x01:
+                    pAhciPort->regSSTS |= (0x01 << 4); /* Generation 1 (1.5GBps) speed. */
+                    break;
+                case 0x02:
+                case 0x00:
+                default:
+                    pAhciPort->regSSTS |= (0x02 << 4); /* Generation 2 (3.0GBps) speed. */
+                    break;
+            }
+
+            /* We received a COMINIT from the device. Tell the guest. */
+            pAhciPort->regIS   |= AHCI_PORT_IS_PCS;
+            pAhciPort->regSERR |= AHCI_PORT_SERR_X;
+            pAhciPort->regTFD  |= ATA_STAT_BUSY;
+
+            if ((pAhciPort->regCMD & AHCI_PORT_CMD_FRE) && (!pAhciPort->fFirstD2HFisSend))
+            {
+                ahciPostFirstD2HFisIntoMemory(pAhciPort);
+                pAhciPort->regIS |= AHCI_PORT_IS_DHRS;
+
+                if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+                    ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN);
+            }
+       }
+#endif
+    }
+
+    pAhciPort->regSCTL = u32Value;
+
+    return VINF_SUCCESS;
+}
+
+static int PortSControl_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regSCTL=%#010x\n", __FUNCTION__, pAhciPort->regSCTL));
+    ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+             AHCI_PORT_SCTL_IPM_GET(pAhciPort->regSCTL), AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL),
+             AHCI_PORT_SCTL_DET_GET(pAhciPort->regSCTL)));
+
+    *pu32Value = pAhciPort->regSCTL;
+    return VINF_SUCCESS;
+}
+
+static int PortSStatus_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regSSTS=%#010x\n", __FUNCTION__, pAhciPort->regSSTS));
+    ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+             AHCI_PORT_SSTS_IPM_GET(pAhciPort->regSSTS), AHCI_PORT_SSTS_SPD_GET(pAhciPort->regSSTS),
+             AHCI_PORT_SSTS_DET_GET(pAhciPort->regSSTS)));
+
+    *pu32Value = pAhciPort->regSSTS;
+    return VINF_SUCCESS;
+}
+
+static int PortSignature_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regSIG=%#010x\n", __FUNCTION__, pAhciPort->regSIG));
+    *pu32Value = pAhciPort->regSIG;
+    return VINF_SUCCESS;
+}
+
+static int PortTaskFileData_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regTFD=%#010x\n", __FUNCTION__, pAhciPort->regTFD));
+    ahciLog(("%s: ERR=%x BSY=%d DRQ=%d ERR=%d\n", __FUNCTION__,
+             (pAhciPort->regTFD >> 8), (pAhciPort->regTFD & AHCI_PORT_TFD_BSY) >> 7,
+             (pAhciPort->regTFD & AHCI_PORT_TFD_DRQ) >> 3, (pAhciPort->regTFD & AHCI_PORT_TFD_ERR)));
+    *pu32Value = pAhciPort->regTFD;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command register.
+ */
+static int PortCmd_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regCMD=%#010x\n", __FUNCTION__, pAhciPort->regCMD));
+    ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n",
+             __FUNCTION__, (pAhciPort->regCMD & AHCI_PORT_CMD_ICC) >> 28, (pAhciPort->regCMD & AHCI_PORT_CMD_ASP) >> 27,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_ALPE) >> 26, (pAhciPort->regCMD & AHCI_PORT_CMD_DLAE) >> 25,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_ATAPI) >> 24, (pAhciPort->regCMD & AHCI_PORT_CMD_CPD) >> 20,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_ISP) >> 19, (pAhciPort->regCMD & AHCI_PORT_CMD_HPCP) >> 18,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_PMA) >> 17, (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) >> 16,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_CR) >> 15, (pAhciPort->regCMD & AHCI_PORT_CMD_FR) >> 14,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_ISS) >> 13, (pAhciPort->regCMD & AHCI_PORT_CMD_CCS) >> 8,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) >> 4, (pAhciPort->regCMD & AHCI_PORT_CMD_CLO) >> 3,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_POD) >> 2, (pAhciPort->regCMD & AHCI_PORT_CMD_SUD) >> 1,
+             (pAhciPort->regCMD & AHCI_PORT_CMD_ST)));
+    *pu32Value = pAhciPort->regCMD;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command register.
+ * This is the register where all the data transfer is started
+ */
+static int PortCmd_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    int rc = VINF_SUCCESS;
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+    ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n",
+             __FUNCTION__, (u32Value & AHCI_PORT_CMD_ICC) >> 28, (u32Value & AHCI_PORT_CMD_ASP) >> 27,
+             (u32Value & AHCI_PORT_CMD_ALPE) >> 26, (u32Value & AHCI_PORT_CMD_DLAE) >> 25,
+             (u32Value & AHCI_PORT_CMD_ATAPI) >> 24, (u32Value & AHCI_PORT_CMD_CPD) >> 20,
+             (u32Value & AHCI_PORT_CMD_ISP) >> 19, (u32Value & AHCI_PORT_CMD_HPCP) >> 18,
+             (u32Value & AHCI_PORT_CMD_PMA) >> 17, (u32Value & AHCI_PORT_CMD_CPS) >> 16,
+             (u32Value & AHCI_PORT_CMD_CR) >> 15, (u32Value & AHCI_PORT_CMD_FR) >> 14,
+             (u32Value & AHCI_PORT_CMD_ISS) >> 13, (u32Value & AHCI_PORT_CMD_CCS) >> 8,
+             (u32Value & AHCI_PORT_CMD_FRE) >> 4, (u32Value & AHCI_PORT_CMD_CLO) >> 3,
+             (u32Value & AHCI_PORT_CMD_POD) >> 2, (u32Value & AHCI_PORT_CMD_SUD) >> 1,
+             (u32Value & AHCI_PORT_CMD_ST)));
+
+    if (pAhciPort->fPoweredOn && pAhciPort->fSpunUp)
+    {
+        if (u32Value & AHCI_PORT_CMD_CLO)
+        {
+            ahciLog(("%s: Command list override requested\n", __FUNCTION__));
+            u32Value &= ~(AHCI_PORT_TFD_BSY | AHCI_PORT_TFD_DRQ);
+            /* Clear the CLO bit. */
+            u32Value &= ~(AHCI_PORT_CMD_CLO);
+        }
+
+        if (u32Value & AHCI_PORT_CMD_ST)
+        {
+            ahciLog(("%s: Engine starts\n", __FUNCTION__));
+
+            /** Set engine state to running. */
+            u32Value |= AHCI_PORT_CMD_CR;
+        }
+        else
+        {
+            ahciLog(("%s: Engine stops\n", __FUNCTION__));
+            /* Clear command issue register. */
+            pAhciPort->regCI = 0;
+            /** Clear current command slot. */
+            u32Value &= ~(AHCI_PORT_CMD_CCS_SHIFT(0xff));
+            u32Value &= ~AHCI_PORT_CMD_CR;
+        }
+    }
+    else if (pAhciPort->pDrvBase)
+    {
+        if ((u32Value & AHCI_PORT_CMD_POD) && (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) && !pAhciPort->fPoweredOn)
+        {
+            ahciLog(("%s: Power on the device\n", __FUNCTION__));
+            pAhciPort->fPoweredOn = true;
+
+            /*
+             * Set states in the Port Signature and SStatus registers.
+             */
+            pAhciPort->regSIG  = 0x101; /* Signature for SATA device. */
+            pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+                                 (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
+                                 (0x03 << 0);  /* Device detected and communication established. */
+
+            if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+            {
+#ifndef IN_RING3
+                return VINF_IOM_HC_MMIO_WRITE;
+#else
+                ahciPostFirstD2HFisIntoMemory(pAhciPort);
+                pAhciPort->regIS |= AHCI_PORT_IS_DHRS;
+
+                if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+                    ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN);
+#endif
+            }
+        }
+
+        if ((u32Value & AHCI_PORT_CMD_SUD) && pAhciPort->fPoweredOn && !pAhciPort->fSpunUp)
+        {
+            ahciLog(("%s: Spin up the device\n", __FUNCTION__));
+            pAhciPort->fSpunUp = true;
+        }
+    }
+
+    if (u32Value & AHCI_PORT_CMD_FRE)
+    {
+        ahciLog(("%s: FIS receive enabled\n", __FUNCTION__));
+
+        u32Value |= AHCI_PORT_CMD_FR;
+
+        /* Send the first D2H FIS only if it wasn't already send. */
+        if (!pAhciPort->fFirstD2HFisSend)
+        {
+#ifndef IN_RING3
+            return VINF_IOM_HC_MMIO_WRITE;
+#else
+            ahciPostFirstD2HFisIntoMemory(pAhciPort);
+            pAhciPort->fFirstD2HFisSend = true;
+#endif
+        }
+    }
+    else if (!(u32Value & AHCI_PORT_CMD_FRE))
+    {
+        ahciLog(("%s: FIS receive disabled\n", __FUNCTION__));
+        u32Value &= ~AHCI_PORT_CMD_FR;
+    }
+
+    pAhciPort->regCMD = u32Value;
+
+    return rc;
+}
+
+/**
+ * Read from the port interrupt enable register.
+ */
+static int PortIntrEnable_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regIE=%#010x\n", __FUNCTION__, pAhciPort->regIE));
+    ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n",
+             __FUNCTION__, (pAhciPort->regIE & AHCI_PORT_IE_CPDE) >> 31, (pAhciPort->regIE & AHCI_PORT_IE_TFEE) >> 30,
+             (pAhciPort->regIE & AHCI_PORT_IE_HBFE) >> 29, (pAhciPort->regIE & AHCI_PORT_IE_HBDE) >> 28,
+             (pAhciPort->regIE & AHCI_PORT_IE_IFE) >> 27, (pAhciPort->regIE & AHCI_PORT_IE_INFE) >> 26,
+             (pAhciPort->regIE & AHCI_PORT_IE_OFE) >> 24, (pAhciPort->regIE & AHCI_PORT_IE_IPME) >> 23,
+             (pAhciPort->regIE & AHCI_PORT_IE_PRCE) >> 22, (pAhciPort->regIE & AHCI_PORT_IE_DIE) >> 7,
+             (pAhciPort->regIE & AHCI_PORT_IE_PCE) >> 6, (pAhciPort->regIE & AHCI_PORT_IE_DPE) >> 5,
+             (pAhciPort->regIE & AHCI_PORT_IE_UFE) >> 4, (pAhciPort->regIE & AHCI_PORT_IE_SDBE) >> 3,
+             (pAhciPort->regIE & AHCI_PORT_IE_DSE) >> 2, (pAhciPort->regIE & AHCI_PORT_IE_PSE) >> 1,
+             (pAhciPort->regIE & AHCI_PORT_IE_DHRE)));
+    *pu32Value = pAhciPort->regIE;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port interrupt enable register.
+ */
+static int PortIntrEnable_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+    ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n",
+             __FUNCTION__, (u32Value & AHCI_PORT_IE_CPDE) >> 31, (u32Value & AHCI_PORT_IE_TFEE) >> 30,
+             (u32Value & AHCI_PORT_IE_HBFE) >> 29, (u32Value & AHCI_PORT_IE_HBDE) >> 28,
+             (u32Value & AHCI_PORT_IE_IFE) >> 27, (u32Value & AHCI_PORT_IE_INFE) >> 26,
+             (u32Value & AHCI_PORT_IE_OFE) >> 24, (u32Value & AHCI_PORT_IE_IPME) >> 23,
+             (u32Value & AHCI_PORT_IE_PRCE) >> 22, (u32Value & AHCI_PORT_IE_DIE) >> 7,
+             (u32Value & AHCI_PORT_IE_PCE) >> 6, (u32Value & AHCI_PORT_IE_DPE) >> 5,
+             (u32Value & AHCI_PORT_IE_UFE) >> 4, (u32Value & AHCI_PORT_IE_SDBE) >> 3,
+             (u32Value & AHCI_PORT_IE_DSE) >> 2, (u32Value & AHCI_PORT_IE_PSE) >> 1,
+             (u32Value & AHCI_PORT_IE_DHRE)));
+
+    pAhciPort->regIE = (u32Value & AHCI_PORT_IE_READONLY);
+
+    /* Check if some a interrupt status bit changed*/
+    uint32_t u32IntrStatus = ASMAtomicReadU32(&pAhciPort->regIS);
+
+    if (pAhciPort->regIE & u32IntrStatus)
+        ahciHbaSetInterrupt(ahci, pAhciPort->iLUN);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port interrupt status register.
+ */
+static int PortIntrSts_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regIS=%#010x\n", __FUNCTION__, pAhciPort->regIS));
+    ahciLog(("%s: CPDS=%d TFES=%d HBFS=%d HBDS=%d IFS=%d INFS=%d OFS=%d IPMS=%d PRCS=%d DIS=%d PCS=%d DPS=%d UFS=%d SDBS=%d DSS=%d PSS=%d DHRS=%d\n",
+             __FUNCTION__, (pAhciPort->regIS & AHCI_PORT_IS_CPDS) >> 31, (pAhciPort->regIS & AHCI_PORT_IS_TFES) >> 30,
+             (pAhciPort->regIS & AHCI_PORT_IS_HBFS) >> 29, (pAhciPort->regIS & AHCI_PORT_IS_HBDS) >> 28,
+             (pAhciPort->regIS & AHCI_PORT_IS_IFS) >> 27, (pAhciPort->regIS & AHCI_PORT_IS_INFS) >> 26,
+             (pAhciPort->regIS & AHCI_PORT_IS_OFS) >> 24, (pAhciPort->regIS & AHCI_PORT_IS_IPMS) >> 23,
+             (pAhciPort->regIS & AHCI_PORT_IS_PRCS) >> 22, (pAhciPort->regIS & AHCI_PORT_IS_DIS) >> 7,
+             (pAhciPort->regIS & AHCI_PORT_IS_PCS) >> 6, (pAhciPort->regIS & AHCI_PORT_IS_DPS) >> 5,
+             (pAhciPort->regIS & AHCI_PORT_IS_UFS) >> 4, (pAhciPort->regIS & AHCI_PORT_IS_SDBS) >> 3,
+             (pAhciPort->regIS & AHCI_PORT_IS_DSS) >> 2, (pAhciPort->regIS & AHCI_PORT_IS_PSS) >> 1,
+             (pAhciPort->regIS & AHCI_PORT_IS_DHRS)));
+    *pu32Value = pAhciPort->regIS;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port interrupt status register.
+ */
+static int PortIntrSts_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+    pAhciPort->regIS &= ~u32Value;
+
+    if (u32Value & AHCI_PORT_IS_PCS)
+        pAhciPort->regSERR &= ~AHCI_PORT_SERR_X;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port FIS base address upper 32bit register.
+ */
+static int PortFisAddrUp_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regFBU=%#010x\n", __FUNCTION__, pAhciPort->regFBU));
+    *pu32Value = pAhciPort->regFBU;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port FIS base address upper 32bit register.
+ */
+static int PortFisAddrUp_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    pAhciPort->regFBU = u32Value;
+    pAhciPort->GCPhysAddrFb |= ((RTGCPHYS)pAhciPort->regFBU) << 32;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port FIS base address register.
+ */
+static int PortFisAddr_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regFB=%#010x\n", __FUNCTION__, pAhciPort->regFB));
+    *pu32Value = pAhciPort->regFB;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port FIS base address register.
+ */
+static int PortFisAddr_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    pAhciPort->regFB = (u32Value & AHCI_PORT_FB_RESERVED);
+    pAhciPort->GCPhysAddrFb |= pAhciPort->regFB;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command list base address upper 32bit register.
+ */
+static int PortCmdLstAddrUp_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    pAhciPort->regCLBU = u32Value;
+    pAhciPort->GCPhysAddrClb |= ((RTGCPHYS)pAhciPort->regCLBU) << 32;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command list base address upper 32bit register.
+ */
+static int PortCmdLstAddrUp_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regCLBU=%#010x\n", __FUNCTION__, pAhciPort->regCLBU));
+    *pu32Value = pAhciPort->regCLBU;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command list base address register.
+ */
+static int PortCmdLstAddr_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: read regCLB=%#010x\n", __FUNCTION__, pAhciPort->regCLB));
+    *pu32Value = pAhciPort->regCLB;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command list base address register.
+ */
+static int PortCmdLstAddr_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    pAhciPort->regCLB = (u32Value & AHCI_PORT_CLB_RESERVED);
+    pAhciPort->GCPhysAddrClb |= pAhciPort->regCLB;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global Version register.
+ */
+static int HbaVersion_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaVs=%#010x\n", __FUNCTION__, ahci->regHbaVs));
+    *pu32Value = ahci->regHbaVs;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global Ports implemented register.
+ */
+static int HbaPortsImplemented_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaPi=%#010x\n", __FUNCTION__, ahci->regHbaPi));
+    *pu32Value = ahci->regHbaPi;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global interrupt status register.
+ */
+static int HbaInterruptStatus_w(PAHCI ahci, uint32_t iReg, uint32_t u32Value)
+{
+    int rc;
+    Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    rc = PDMCritSectEnter(&ahci->lock, VINF_IOM_HC_MMIO_WRITE);
+    if (rc != VINF_SUCCESS)
+        return rc;
+
+    if (u32Value > 0)
+    {
+        /*
+         * Clear the interrupt only if no port has signalled
+         * an interrupt and the guest has cleared all set interrupt
+         * notification bits.
+         */
+
+        ahci->regHbaIs &= ~(u32Value);
+
+        if ((!ahci->u32PortsInterrupted) && (!ahci->regHbaIs))
+            ahciHbaClearInterrupt(ahci);
+        else
+        {
+            Log(("%s: Not clearing interrupt: u32PortsInterrupted=%#010x\n", __FUNCTION__, ahci->u32PortsInterrupted));
+            /*
+             * We need to set the interrupt again because the I/O APIC does not set it again even if the
+             * line is still high.
+             * We need to clear it first because the PCI bus only calls the interrupt controller if the state changes.
+             */
+            PDMDevHlpPCISetIrqNoWait(ahci->CTX_SUFF(pDevIns), 0, 0);
+            PDMDevHlpPCISetIrqNoWait(ahci->CTX_SUFF(pDevIns), 0, 1);
+        }
+    }
+
+    PDMCritSectLeave(&ahci->lock);
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global interrupt status register.
+ */
+static int HbaInterruptStatus_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    uint32_t u32PortsInterrupted;
+    int rc;
+
+    rc = PDMCritSectEnter(&ahci->lock, VINF_IOM_HC_MMIO_READ);
+    if (rc != VINF_SUCCESS)
+        return rc;
+
+    u32PortsInterrupted = ASMAtomicXchgU32(&ahci->u32PortsInterrupted, 0);
+
+    PDMCritSectLeave(&ahci->lock);
+    Log(("%s: read regHbaIs=%#010x u32PortsInterrupted=%#010x\n", __FUNCTION__, ahci->regHbaIs, u32PortsInterrupted));
+
+    ahci->regHbaIs |= u32PortsInterrupted;
+
+#ifdef LOG_ENABLED
+    Log(("%s:", __FUNCTION__));
+    unsigned i;
+    for (i = 0; i < ahci->cPortsImpl; i++)
+    {
+        if ((ahci->regHbaIs >> i) & 0x01)
+            Log((" P%d", i));
+    }
+    Log(("\n"));
+#endif
+
+    *pu32Value = ahci->regHbaIs;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global control register.
+ */
+static int HbaControl_w(PAHCI ahci, uint32_t iReg, uint32_t u32Value)
+{
+    Log(("%s: write u32Value=%#010x\n"
+         "%s: AE=%d IE=%d HR=%d\n",
+         __FUNCTION__, u32Value,
+         __FUNCTION__, (u32Value & AHCI_HBA_CTRL_AE) >> 31, (u32Value & AHCI_HBA_CTRL_IE) >> 1,
+         (u32Value & AHCI_HBA_CTRL_HR)));
+
+    ahci->regHbaCtrl = (u32Value & AHCI_HBA_CTRL_RW_MASK) | AHCI_HBA_CTRL_AE;
+    if (ahci->regHbaCtrl & AHCI_HBA_CTRL_HR)
+        ahciHBAReset(ahci);
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read the global control register.
+ */
+static int HbaControl_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaCtrl=%#010x\n"
+         "%s: AE=%d IE=%d HR=%d\n",
+         __FUNCTION__, ahci->regHbaCtrl,
+         __FUNCTION__, (ahci->regHbaCtrl & AHCI_HBA_CTRL_AE) >> 31, (ahci->regHbaCtrl & AHCI_HBA_CTRL_IE) >> 1,
+         (ahci->regHbaCtrl & AHCI_HBA_CTRL_HR)));
+    *pu32Value = ahci->regHbaCtrl;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read the global capabilities register.
+ */
+static int HbaCapabilities_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaCap=%#010x\n"
+         "%s: S64A=%d SNCQ=%d SIS=%d SSS=%d SALP=%d SAL=%d SCLO=%d ISS=%d SNZO=%d SAM=%d SPM=%d PMD=%d SSC=%d PSC=%d NCS=%d NP=%d\n",
+          __FUNCTION__, ahci->regHbaCap,
+          __FUNCTION__, (ahci->regHbaCap & AHCI_HBA_CAP_S64A) >> 31, (ahci->regHbaCap & AHCI_HBA_CAP_SNCQ) >> 30,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SIS) >> 28, (ahci->regHbaCap & AHCI_HBA_CAP_SSS) >> 27,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SALP) >> 26, (ahci->regHbaCap & AHCI_HBA_CAP_SAL) >> 25,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SCLO) >> 24, (ahci->regHbaCap & AHCI_HBA_CAP_ISS) >> 20,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SNZO) >> 19, (ahci->regHbaCap & AHCI_HBA_CAP_SAM) >> 18,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SPM) >> 17, (ahci->regHbaCap & AHCI_HBA_CAP_PMD) >> 15,
+          (ahci->regHbaCap & AHCI_HBA_CAP_SSC) >> 14, (ahci->regHbaCap & AHCI_HBA_CAP_PSC) >> 13,
+          (ahci->regHbaCap & AHCI_HBA_CAP_NCS) >> 8, (ahci->regHbaCap & AHCI_HBA_CAP_NP)));
+    *pu32Value = ahci->regHbaCap;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing control register.
+ */
+static int HbaCccCtl_w(PAHCI ahci, uint32_t iReg, uint32_t u32Value)
+{
+    Log(("%s: write u32Value=%#010x\n"
+         "%s: TV=%d CC=%d INT=%d EN=%d\n",
+         __FUNCTION__, u32Value,
+         __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(u32Value), AHCI_HBA_CCC_CTL_CC_GET(u32Value),
+         AHCI_HBA_CCC_CTL_INT_GET(u32Value), (u32Value & AHCI_HBA_CCC_CTL_EN)));
+
+    ahci->regHbaCccCtl = u32Value;
+    ahci->uCccTimeout  = AHCI_HBA_CCC_CTL_TV_GET(u32Value);
+    ahci->uCccPortNr   = AHCI_HBA_CCC_CTL_INT_GET(u32Value);
+    ahci->uCccNr       = AHCI_HBA_CCC_CTL_CC_GET(u32Value);
+
+    if (u32Value & AHCI_HBA_CCC_CTL_EN)
+    {
+        /* Arm the timer */
+        TMTimerSetMillies(ahci->CTX_SUFF(pHbaCccTimer), ahci->uCccTimeout);
+    }
+    else
+    {
+        TMTimerStop(ahci->CTX_SUFF(pHbaCccTimer));
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing control register.
+ */
+static int HbaCccCtl_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaCccCtl=%#010x\n"
+         "%s: TV=%d CC=%d INT=%d EN=%d\n",
+         __FUNCTION__, ahci->regHbaCccCtl,
+         __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(ahci->regHbaCccCtl), AHCI_HBA_CCC_CTL_CC_GET(ahci->regHbaCccCtl),
+         AHCI_HBA_CCC_CTL_INT_GET(ahci->regHbaCccCtl), (ahci->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)));
+    *pu32Value = ahci->regHbaCccCtl;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing ports register.
+ */
+static int HbaCccPorts_w(PAHCI ahci, uint32_t iReg, uint32_t u32Value)
+{
+    Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+    ahci->regHbaCccPorts = u32Value;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing ports register.
+ */
+static int HbaCccPorts_r(PAHCI ahci, uint32_t iReg, uint32_t *pu32Value)
+{
+    Log(("%s: read regHbaCccPorts=%#010x\n", __FUNCTION__, ahci->regHbaCccPorts));
+
+#ifdef LOG_ENABLED
+    Log(("%s:", __FUNCTION__));
+    unsigned i;
+    for (i = 0; i < ahci->cPortsImpl; i++)
+    {
+        if ((ahci->regHbaCccPorts >> i) & 0x01)
+            Log((" P%d", i));
+    }
+    Log(("\n"));
+#endif
+
+    *pu32Value = ahci->regHbaCccPorts;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Invalid write to global register
+ */
+static int HbaInvalid_w(PAHCI ahci, uint32_t iReg, uint32_t u32Value)
+{
+    Log(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+    return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port write.
+ */
+static int PortInvalid_w(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+    ahciLog(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+    return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port read.
+ */
+static int PortInvalid_r(PAHCI ahci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+    ahciLog(("%s: Read denied!!! iReg=%u\n", __FUNCTION__, iReg));
+    return VINF_SUCCESS;
+}
+
+/**
+ * Register descriptor table for global HBA registers
+ */
+static const AHCIOPREG g_aOpRegs[] =
+{
+    {"HbaCapabilites",      HbaCapabilities_r,     HbaInvalid_w}, /* Readonly */
+    {"HbaControl"    ,      HbaControl_r,          HbaControl_w},
+    {"HbaInterruptStatus",  HbaInterruptStatus_r,  HbaInterruptStatus_w},
+    {"HbaPortsImplemented", HbaPortsImplemented_r, HbaInvalid_w}, /* Readonly */
+    {"HbaVersion",          HbaVersion_r,          HbaInvalid_w}, /* ReadOnly */
+    {"HbaCccCtl",           HbaCccCtl_r,           HbaCccCtl_w},
+    {"HbaCccPorts",         HbaCccPorts_r,         HbaCccPorts_w},
+};
+
+/**
+ * Register descriptor table for port registers
+ */
+static const AHCIPORTOPREG g_aPortOpRegs[] =
+{
+    {"PortCmdLstAddr",   PortCmdLstAddr_r,   PortCmdLstAddr_w},
+    {"PortCmdLstAddrUp", PortCmdLstAddrUp_r, PortCmdLstAddrUp_w},
+    {"PortFisAddr",      PortFisAddr_r,      PortFisAddr_w},
+    {"PortFisAddrUp",    PortFisAddrUp_r,    PortFisAddrUp_w},
+    {"PortIntrSts",      PortIntrSts_r,      PortIntrSts_w},
+    {"PortIntrEnable",   PortIntrEnable_r,   PortIntrEnable_w},
+    {"PortCmd",          PortCmd_r,          PortCmd_w},
+    {"PortReserved1",    PortInvalid_r,      PortInvalid_w}, /* Not used. */
+    {"PortTaskFileData", PortTaskFileData_r, PortInvalid_w}, /* Readonly */
+    {"PortSignature",    PortSignature_r,    PortInvalid_w}, /* Readonly */
+    {"PortSStatus",      PortSStatus_r,      PortInvalid_w}, /* Readonly */
+    {"PortSControl",     PortSControl_r,     PortSControl_w},
+    {"PortSError",       PortSError_r,       PortSError_w},
+    {"PortSActive",      PortSActive_r,      PortSActive_w},
+    {"PortCmdIssue",     PortCmdIssue_r,     PortCmdIssue_w},
+    {"PortReserved2",    PortInvalid_r,      PortInvalid_w}, /* Not used. */
+};
+
+/**
+ * Reset initiated by system software for one port.
+ *
+ * @param pAhciPort     The port to reset.
+ */
+static void ahciPortSwReset(PAHCIPort pAhciPort)
+{
+    pAhciPort->regIS   = 0;
+    pAhciPort->regIE   = 0;
+    pAhciPort->regCMD  = AHCI_PORT_CMD_CPD  | /* Cold presence detection */
+                         AHCI_PORT_CMD_ISP  | /* Interlock switch attached (for hotplug) */
+                         AHCI_PORT_CMD_HPCP | /* Hotplug capable port */
+                         AHCI_PORT_CMD_SUD  | /* Device has spun up. */
+                         AHCI_PORT_CMD_POD;   /* Port is powered on. */
+    pAhciPort->regTFD  = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR;
+    pAhciPort->regSIG  = ~0;
+    pAhciPort->regSSTS = 0;
+    pAhciPort->regSCTL = 0;
+    pAhciPort->regSERR = 0;
+    pAhciPort->regSACT = 0;
+    pAhciPort->regCI   = 0;
+
+    pAhciPort->fResetDevice      = false;
+    pAhciPort->fPoweredOn        = true;
+    pAhciPort->fSpunUp           = true;
+    pAhciPort->fNotificationSend = false;
+    pAhciPort->cMultSectors = ATA_MAX_MULT_SECTORS;
+    pAhciPort->uATATransferMode = ATA_MODE_UDMA | 6;
+
+    pAhciPort->u32TasksFinished = 0;
+    pAhciPort->u32QueuedTasksFinished = 0;
+
+    pAhciPort->uActWritePos = 0;
+    pAhciPort->uActReadPos = 0;
+    pAhciPort->uActTasksActive = 0;
+
+    if (pAhciPort->pDrvBase)
+    {
+        pAhciPort->regCMD |= AHCI_PORT_CMD_CPS; /* Indicate that there is a device on that port */
+        /* We received a COMINIT signal */
+        pAhciPort->regSERR |= AHCI_PORT_SERR_X;
+        pAhciPort->regIS   |= AHCI_PORT_IS_PCS;
+        pAhciPort->regTFD  |= ATA_STAT_BUSY;
+
+        if (pAhciPort->fPoweredOn)
+        {
+            /*
+             * Set states in the Port Signature and SStatus registers.
+             */
+            pAhciPort->regSIG  = 0x101; /* Signature for SATA device. */
+            pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+                                 (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
+                                 (0x03 << 0);  /* Device detected and communication established. */
+        }
+    }
+}
+
+#ifdef IN_RING3
+/**
+ * Hardware reset used for machine power on and reset.
+ *
+ * @param pAhciport     The port to reset.
+ */
+static void ahciPortHwReset(PAHCIPort pAhciPort)
+{
+    /* Reset the address registers. */
+    pAhciPort->regCLB  = 0;
+    pAhciPort->regCLBU = 0;
+    pAhciPort->regFB   = 0;
+    pAhciPort->regFBU  = 0;
+
+    /* Reset calculated addresses. */
+    pAhciPort->GCPhysAddrClb = 0;
+    pAhciPort->GCPhysAddrFb  = 0;
+}
+#endif
+
+/**
+ * Create implemented ports bitmap.
+ *
+ * @returns 32bit bitmask with a bit set for every implemented port.
+ * @param   cPorts    Number of ports.
+ */
+static uint32_t ahciGetPortsImplemented(unsigned cPorts)
+{
+    uint32_t uPortsImplemented = 0;
+
+    for (unsigned i = 0; i < cPorts; i++)
+        uPortsImplemented |= (1 << i);
+
+    return uPortsImplemented;
+}
+
+/**
+ * Reset the entire HBA.
+ *
+ * @param   pThis       The HBA state.
+ */
+static void ahciHBAReset(PAHCI pThis)
+{
+    unsigned i;
+    int rc = VINF_SUCCESS;
+
+    LogFlow(("Reset the HBA controller\n"));
+
+    /* Stop the CCC timer. */
+    if (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)
+    {
+        rc = TMTimerStop(pThis->CTX_SUFF(pHbaCccTimer));
+        if (RT_FAILURE(rc))
+            AssertMsgFailed(("%s: Failed to stop timer!\n", __FUNCTION__));
+    }
+
+    /** Reset every port */
+    for (i = 0; i < pThis->cPortsImpl; i++)
+    {
+        PAHCIPort pAhciPort = &pThis->ahciPort[i];
+
+        pAhciPort->iLUN = i;
+        ahciPortSwReset(pAhciPort);
+    }
+
+    /** Init Global registers */
+    pThis->regHbaCap      = AHCI_HBA_CAP_ISS_SHIFT(AHCI_HBA_CAP_ISS_GEN2) |
+                            AHCI_HBA_CAP_S64A | /* 64bit addressing supported */
+                            AHCI_HBA_CAP_SAM  | /* AHCI mode only */
+                            AHCI_HBA_CAP_SNCQ | /* Support native command queuing */
+                            AHCI_HBA_CAP_SIS  | /* Interlock switch for hotplug operations */
+                            AHCI_HBA_CAP_SSS  | /* Staggered spin up */
+                            AHCI_HBA_CAP_CCCS | /* Support command completion coalescing */
+                            AHCI_HBA_CAP_NCS_SET(AHCI_NR_COMMAND_SLOTS) | /* Number of command slots we support */
+                            AHCI_HBA_CAP_NP_SET(pThis->cPortsImpl); /* Number of supported ports */
+    pThis->regHbaCtrl     = AHCI_HBA_CTRL_AE;
+    pThis->regHbaIs       = 0;
+    pThis->regHbaPi       = ahciGetPortsImplemented(pThis->cPortsImpl);
+    pThis->regHbaVs       = AHCI_HBA_VS_MJR | AHCI_HBA_VS_MNR;
+    pThis->regHbaCccCtl   = 0;
+    pThis->regHbaCccPorts = 0;
+    pThis->uCccTimeout    = 0;
+    pThis->uCccPortNr     = 0;
+    pThis->uCccNr         = 0;
+
+    pThis->f64BitAddr = false;
+    pThis->u32PortsInterrupted = 0;
+    pThis->f8ByteMMIO4BytesWrittenSuccessfully = false;
+    /** Clear the HBA Reset bit */
+    pThis->regHbaCtrl &= ~AHCI_HBA_CTRL_HR;
+}
+
+/**
+ * Memory mapped I/O Handler for read operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   GCPhysAddr  Physical address (in GC) where the read starts.
+ * @param   pv          Where to store the result.
+ * @param   cb          Number of bytes read.
+ */
+PDMBOTHCBDECL(int) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    int   rc = VINF_SUCCESS;
+
+    /* Break up 64 bits reads into two dword reads. */
+    if (cb == 8)
+    {
+        rc = ahciMMIORead(pDevIns, pvUser, GCPhysAddr, pv, 4);
+        if (RT_FAILURE(rc))
+            return rc;
+
+        return ahciMMIORead(pDevIns, pvUser, GCPhysAddr + 4, (uint8_t *)pv + 4, 4);
+    }
+
+    Log2(("#%d ahciMMIORead: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp rc=%Rrc\n",
+          pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr, rc));
+
+    /*
+     * If the access offset is smaller than AHCI_HBA_GLOBAL_SIZE the guest accesses the global registers.
+     * Otherwise it accesses the registers of a port.
+     */
+    uint32_t uOffset = (GCPhysAddr - pAhci->MMIOBase);
+    uint32_t iReg;
+
+    if (uOffset < AHCI_HBA_GLOBAL_SIZE)
+    {
+        iReg = uOffset >> 2;
+        Log3(("%s: Trying to read from global register %u\n", __FUNCTION__, iReg));
+        if (iReg < RT_ELEMENTS(g_aOpRegs))
+        {
+            const AHCIOPREG *pReg = &g_aOpRegs[iReg];
+            rc = pReg->pfnRead(pAhci, iReg, (uint32_t *)pv);
+        }
+        else
+        {
+            Log3(("%s: Trying to read global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
+            *(uint32_t *)pv = 0;
+        }
+    }
+    else
+    {
+        uint32_t iRegOffset;
+        uint32_t iPort;
+
+        /* Calculate accessed port. */
+        uOffset -= AHCI_HBA_GLOBAL_SIZE;
+        iPort = uOffset / AHCI_PORT_REGISTER_SIZE;
+        iRegOffset  = (uOffset % AHCI_PORT_REGISTER_SIZE);
+        iReg = iRegOffset >> 2;
+
+        Log3(("%s: Trying to read from port %u and register %u\n", __FUNCTION__, iPort, iReg));
+
+        if (RT_LIKELY(   iPort < pAhci->cPortsImpl
+                      && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+        {
+            const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+            rc = pPortReg->pfnRead(pAhci, &pAhci->ahciPort[iPort], iReg, (uint32_t *)pv);
+        }
+        else
+        {
+            Log3(("%s: Trying to read port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
+            rc = VINF_IOM_MMIO_UNUSED_00;
+        }
+
+        /*
+         * Windows Vista tries to read one byte from some registers instead of four.
+         * Correct the value according to the read size.
+         */
+        if (RT_SUCCESS(rc) && cb != sizeof(uint32_t))
+        {
+            switch (cb)
+            {
+                case 1:
+                {
+                    uint8_t uNewValue;
+                    uint8_t *p = (uint8_t *)pv;
+
+                    iRegOffset &= 3;
+                    Log3(("%s: iRegOffset=%u\n", __FUNCTION__, iRegOffset));
+                    uNewValue = p[iRegOffset];
+                    /* Clear old value */
+                    *(uint32_t *)pv = 0;
+                    *(uint8_t *)pv = uNewValue;
+                    break;
+                }
+                default:
+                    AssertMsgFailed(("%s: unsupported access width cb=%d uOffset=%x iPort=%x iRegOffset=%x iReg=%x!!!\n", __FUNCTION__, cb, uOffset, iPort, iRegOffset, iReg));
+            }
+        }
+    }
+
+    Log2(("#%d ahciMMIORead: return pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp rc=%Rrc\n",
+          pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr, rc));
+    return rc;
+}
+
+
+/**
+ * Memory mapped I/O Handler for write operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   GCPhysAddr  Physical address (in GC) where the read starts.
+ * @param   pv          Where to fetch the result.
+ * @param   cb          Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    int   rc = VINF_SUCCESS;
+
+    /* Break up 64 bits writes into two dword writes. */
+    if (cb == 8)
+    {
+        /*
+         * Only write the first 4 bytes if they weren't already.
+         * It is possible that the last write to the register caused a world
+         * switch and we entered this function again.
+         * Writing the first 4 bytes again could cause indeterminate behavior
+         * which can cause errors in the guest.
+         */
+        if (!pAhci->f8ByteMMIO4BytesWrittenSuccessfully)
+        {
+            rc = ahciMMIOWrite(pDevIns, pvUser, GCPhysAddr, pv, 4);
+            if (rc != VINF_SUCCESS)
+                return rc;
+
+            pAhci->f8ByteMMIO4BytesWrittenSuccessfully = true;
+        }
+
+        rc = ahciMMIOWrite(pDevIns, pvUser, GCPhysAddr + 4, (uint8_t *)pv + 4, 4);
+        /*
+         * Reset flag again so that the first 4 bytes are written again on the next
+         * 8byte MMIO access.
+         */
+        if (rc == VINF_SUCCESS)
+            pAhci->f8ByteMMIO4BytesWrittenSuccessfully = false;
+
+        return rc;
+    }
+
+    Log2(("#%d ahciMMIOWrite: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n",
+          pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr));
+
+    /* Validate access. */
+    if (cb != sizeof(uint32_t))
+    {
+        Log2(("%s: Bad write size!!! GCPhysAddr=%RGp cb=%d\n", __FUNCTION__, GCPhysAddr, cb));
+        return VINF_SUCCESS;
+    }
+    if (GCPhysAddr & 0x3)
+    {
+        Log2(("%s: Unaligned write!!! GCPhysAddr=%RGp cb=%d\n", __FUNCTION__, GCPhysAddr, cb));
+        return VINF_SUCCESS;
+    }
+
+    /*
+     * If the access offset is smaller than 100h the guest accesses the global registers.
+     * Otherwise it accesses the registers of a port.
+     */
+    uint32_t uOffset = (GCPhysAddr - pAhci->MMIOBase);
+    uint32_t iReg;
+    if (uOffset < AHCI_HBA_GLOBAL_SIZE)
+    {
+        Log3(("Write global HBA register\n"));
+        iReg = uOffset >> 2;
+        if (iReg < RT_ELEMENTS(g_aOpRegs))
+        {
+            const AHCIOPREG *pReg = &g_aOpRegs[iReg];
+            rc = pReg->pfnWrite(pAhci, iReg, *(uint32_t *)pv);
+        }
+        else
+        {
+            Log3(("%s: Trying to write global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
+            rc = VINF_SUCCESS;
+        }
+    }
+    else
+    {
+        uint32_t iPort;
+        Log3(("Write Port register\n"));
+        /* Calculate accessed port. */
+        uOffset -= AHCI_HBA_GLOBAL_SIZE;
+        iPort = uOffset / AHCI_PORT_REGISTER_SIZE;
+        iReg  = (uOffset % AHCI_PORT_REGISTER_SIZE) >> 2;
+        Log3(("%s: Trying to write to port %u and register %u\n", __FUNCTION__, iPort, iReg));
+        if (RT_LIKELY(   iPort < pAhci->cPortsImpl
+                      && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+        {
+            const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+            rc = pPortReg->pfnWrite(pAhci, &pAhci->ahciPort[iPort], iReg, *(uint32_t *)pv);
+        }
+        else
+        {
+            Log3(("%s: Trying to write port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
+            rc = VINF_SUCCESS;
+        }
+    }
+
+    return rc;
+}
+
+PDMBOTHCBDECL(int) ahciIOPortWrite1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    uint32_t iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortWrite1(pCtl, Port, u32, cb);
+}
+
+PDMBOTHCBDECL(int) ahciIOPortRead1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    uint32_t iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortRead1(pCtl, Port, pu32, cb);
+}
+
+PDMBOTHCBDECL(int) ahciIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    uint32_t iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortWrite2(pCtl, Port, u32, cb);
+}
+
+PDMBOTHCBDECL(int) ahciIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    uint32_t iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortRead2(pCtl, Port, pu32, cb);
+}
+
+PDMBOTHCBDECL(int) ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    AssertMsgFailed(("Should not happen\n"));
+    return VINF_SUCCESS;
+}
+
+PDMBOTHCBDECL(int) ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    AssertMsgFailed(("Should not happen\n"));
+    return VINF_SUCCESS;
+}
+
+#ifndef IN_RING0
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+PDMBOTHCBDECL(int) ahciIOPortReadStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    uint32_t       iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI          pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortReadStr1(pCtl, Port, pGCPtrDst, pcTransfer, cb);
+}
+
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+PDMBOTHCBDECL(int) ahciIOPortWriteStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    uint32_t       iChannel = (uint32_t)(uintptr_t)pvUser;
+    PAHCI          pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIATACONTROLLER pCtl = &pAhci->aCts[iChannel];
+
+    Assert(iChannel < 2);
+
+    return ataControllerIOPortReadStr1(pCtl, Port, pGCPtrSrc, pcTransfer, cb);
+}
+#endif /* !IN_RING0 */
+
+#ifdef IN_RING3
+
+static DECLCALLBACK(int) ahciMMIOMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
+{
+    PAHCI pThis = PCIDEV_2_PAHCI(pPciDev);
+    PPDMDEVINS pDevIns = pPciDev->pDevIns;
+    int   rc = VINF_SUCCESS;
+
+    Log2(("%s: registering MMIO area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
+
+    Assert(enmType == PCI_ADDRESS_SPACE_MEM);
+    Assert(cb >= 4352);
+
+    /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+    rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL,
+                               ahciMMIOWrite, ahciMMIORead, NULL, "AHCI");
+    if (RT_FAILURE(rc))
+        return rc;
+
+    if (pThis->fR0Enabled)
+    {
+        rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, 0,
+                                     "ahciMMIOWrite", "ahciMMIORead", NULL);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    if (pThis->fGCEnabled)
+    {
+        rc = PDMDevHlpMMIORegisterGC(pDevIns, GCPhysAddress, cb, 0,
+                                     "ahciMMIOWrite", "ahciMMIORead", NULL);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    pThis->MMIOBase = GCPhysAddress;
+    return rc;
+}
+
+/**
+ * Map the legacy I/O port ranges to make Solaris work with the controller.
+ */
+static DECLCALLBACK(int) ahciLegacyFakeIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
+{
+    PAHCI pThis = PCIDEV_2_PAHCI(pPciDev);
+    PPDMDEVINS pDevIns = pPciDev->pDevIns;
+    int   rc = VINF_SUCCESS;
+
+    Log2(("%s: registering fake I/O area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
+
+    Assert(enmType == PCI_ADDRESS_SPACE_IO);
+
+    /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+    rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, cb, NULL,
+                                 ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL, NULL, "AHCI Fake");
+    if (RT_FAILURE(rc))
+        return rc;
+
+    if (pThis->fR0Enabled)
+    {
+        rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+                                       "ahciLegacyFakeWrite", "ahciLegacyFakeRead", NULL, NULL, "AHCI Fake");
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    if (pThis->fGCEnabled)
+    {
+        rc = PDMDevHlpIOPortRegisterGC(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+                                       "ahciLegacyFakeWrite", "ahciLegacyFakeRead", NULL, NULL, "AHCI Fake");
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    return rc;
+}
+
+/* -=-=-=-=-=- PAHCI::ILeds  -=-=-=-=-=- */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ * @param   iLUN            The unit which status LED we desire.
+ * @param   ppLed           Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ahciStatus_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+    PAHCI pAhci = PDMILEDPORTS_2_PAHCI(pInterface);
+    if (iLUN < AHCI_MAX_NR_PORTS_IMPL)
+    {
+        *ppLed = &pAhci->ahciPort[iLUN].Led;
+        Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+        return VINF_SUCCESS;
+    }
+    return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to ATADevState::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *) ahciStatus_QueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PAHCI pAhci = PDMIBASE_2_PAHCI(pInterface);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pAhci->IBase;
+        case PDMINTERFACE_LED_PORTS:
+            return &pAhci->ILeds;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Query interface method for the AHCI port.
+ */
+static DECLCALLBACK(void *) ahciPortQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PAHCIPort pAhciPort = PDMIBASE_2_PAHCIPORT(pInterface);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pAhciPort->IBase;
+        case PDMINTERFACE_BLOCK_PORT:
+            return &pAhciPort->IPort;
+        case PDMINTERFACE_BLOCK_ASYNC_PORT:
+            return &pAhciPort->IPortAsync;
+        case PDMINTERFACE_MOUNT_NOTIFY:
+            return &pAhciPort->IMountNotify;
+        default:
+            return NULL;
+    }
+}
+
+static DECLCALLBACK(void) ahciRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+    uint32_t i;
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    pAhci->pDevInsRC += offDelta;
+    pAhci->pHbaCccTimerRC = TMTimerRCPtr(pAhci->pHbaCccTimerR3);
+    pAhci->pNotifierQueueRC = PDMQueueRCPtr(pAhci->pNotifierQueueR3);
+
+    /* Relocate every port. */
+    for (i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+    {
+        PAHCIPort pAhciPort = &pAhci->ahciPort[i];
+        pAhciPort->pAhciRC += offDelta;
+        pAhciPort->pDevInsRC += offDelta;
+    }
+
+    /* Relocate emulated ATA controllers. */
+    for (i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+        ataControllerRelocate(&pAhci->aCts[i], offDelta);
+}
+
+#ifdef DEBUG
+
+/**
+ * Dump info about the FIS
+ *
+ * @returns nothing
+ * @param   pAhciPort     The port the command FIS was read from.
+ * @param   cmdFis        The FIS to print info from.
+ */
+static void ahciDumpFisInfo(PAHCIPort pAhciPort, uint8_t *cmdFis)
+{
+    ahciLog(("%s: *** Begin FIS info dump. ***\n", __FUNCTION__));
+    /* Print FIS type. */
+    switch (cmdFis[AHCI_CMDFIS_TYPE])
+    {
+        case AHCI_CMDFIS_TYPE_H2D:
+            {
+                ahciLog(("%s: Command Fis type: H2D\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d bytes\n", __FUNCTION__, AHCI_CMDFIS_TYPE_H2D_SIZE));
+                if (cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C)
+                {
+                    ahciLog(("%s: Command register update\n", __FUNCTION__));
+                }
+                else
+                {
+                    ahciLog(("%s: Control register update\n", __FUNCTION__));
+                }
+                ahciLog(("%s: CMD=%#04x \"%s\"\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CMD], ATACmdText(cmdFis[AHCI_CMDFIS_CMD])));
+                ahciLog(("%s: FEAT=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FET]));
+                ahciLog(("%s: SECTN=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTN]));
+                ahciLog(("%s: CYLL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLL]));
+                ahciLog(("%s: CYLH=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLH]));
+                ahciLog(("%s: HEAD=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_HEAD]));
+
+                ahciLog(("%s: SECTNEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTNEXP]));
+                ahciLog(("%s: CYLLEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLLEXP]));
+                ahciLog(("%s: CYLHEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLHEXP]));
+                ahciLog(("%s: FETEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FETEXP]));
+
+                ahciLog(("%s: SECTC=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTC]));
+                ahciLog(("%s: SECTCEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTCEXP]));
+                ahciLog(("%s: CTL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CTL]));
+                if (cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST)
+                {
+                    ahciLog(("%s: Reset bit is set\n", __FUNCTION__));
+                }
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_D2H:
+            {
+                ahciLog(("%s: Command Fis type D2H\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_D2H_SIZE));
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_SETDEVBITS:
+            {
+                ahciLog(("%s: Command Fis type Set Device Bits\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE));
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_DMAACTD2H:
+            {
+                ahciLog(("%s: Command Fis type DMA Activate H2D\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE));
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_DMASETUP:
+            {
+                ahciLog(("%s: Command Fis type DMA Setup\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMASETUP_SIZE));
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_PIOSETUP:
+            {
+                ahciLog(("%s: Command Fis type PIO Setup\n", __FUNCTION__));
+                ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_PIOSETUP_SIZE));
+            }
+            break;
+        case AHCI_CMDFIS_TYPE_DATA:
+            {
+                ahciLog(("%s: Command Fis type Data\n", __FUNCTION__));
+            }
+            break;
+        default:
+            ahciLog(("%s: ERROR Unknown command FIS type\n", __FUNCTION__));
+            break;
+    }
+    ahciLog(("%s: *** End FIS info dump. ***\n", __FUNCTION__));
+}
+
+/**
+ * Dump info about the command header
+ *
+ * @returns nothing
+ * @param   pAhciPort   Poitner to the port the command header was read from.
+ * @param   pCmdHdr     The command header to print info from.
+ */
+static void ahciDumpCmdHdrInfo(PAHCIPort pAhciPort, CmdHdr *pCmdHdr)
+{
+    ahciLog(("%s: *** Begin command header info dump. ***\n", __FUNCTION__));
+    ahciLog(("%s: Number of Scatter/Gatther List entries: %u\n", __FUNCTION__, AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf)));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_C)
+        ahciLog(("%s: Clear busy upon R_OK\n", __FUNCTION__));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_B)
+        ahciLog(("%s: BIST Fis\n", __FUNCTION__));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_R)
+        ahciLog(("%s: Device Reset Fis\n", __FUNCTION__));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_P)
+        ahciLog(("%s: Command prefetchable\n", __FUNCTION__));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_W)
+        ahciLog(("%s: Device write\n", __FUNCTION__));
+    else
+        ahciLog(("%s: Device read\n", __FUNCTION__));
+    if (pCmdHdr->u32DescInf & AHCI_CMDHDR_A)
+        ahciLog(("%s: ATAPI command\n", __FUNCTION__));
+    else
+        ahciLog(("%s: ATA command\n", __FUNCTION__));
+
+    ahciLog(("%s: Command FIS length %u DW\n", __FUNCTION__, (pCmdHdr->u32DescInf & AHCI_CMDHDR_CFL_MASK)));
+    ahciLog(("%s: *** End command header info dump. ***\n", __FUNCTION__));
+}
+#endif /* DEBUG */
+
+/**
+ * Post the first D2H FIS from the device into guest memory.
+ *
+ * @returns nothing
+ * @param   pAhciPort    Pointer to the port which "receives" the FIS.
+ */
+static void ahciPostFirstD2HFisIntoMemory(PAHCIPort pAhciPort)
+{
+    uint8_t d2hFis[AHCI_CMDFIS_TYPE_D2H_SIZE];
+
+    pAhciPort->fFirstD2HFisSend = true;
+
+    ahciLog(("%s: Sending First D2H FIS from FIFO\n", __FUNCTION__));
+    memset(&d2hFis[0], 0, sizeof(d2hFis));
+    d2hFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_D2H;
+    d2hFis[AHCI_CMDFIS_ERR]  = 0x01;
+
+    d2hFis[AHCI_CMDFIS_STS]  = 0x00;
+
+    /* Set the signature based on the device type. */
+    if (pAhciPort->fATAPI)
+    {
+        d2hFis[AHCI_CMDFIS_CYLL] = 0x14;
+        d2hFis[AHCI_CMDFIS_CYLH] = 0xeb;
+    }
+    else
+    {
+        d2hFis[AHCI_CMDFIS_CYLL]  = 0x00;
+        d2hFis[AHCI_CMDFIS_CYLH]  = 0x00;
+    }
+
+    d2hFis[AHCI_CMDFIS_HEAD]  = 0x00;
+    d2hFis[AHCI_CMDFIS_SECTN] = 0x01;
+    d2hFis[AHCI_CMDFIS_SECTC] = 0x01;
+
+    pAhciPort->regTFD = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR;
+
+    ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis);
+}
+
+/**
+ * Post the FIS in the memory area allocated by the guest and set interrupt if neccessary.
+ *
+ * @returns VBox status code
+ * @param   pAhciPort  The port which "receives" the FIS.
+ * @param   uFisType   The type of the FIS.
+ * @param   pCmdFis    Pointer to the FIS which is to be posted into memory.
+ */
+static int ahciPostFisIntoMemory(PAHCIPort pAhciPort, unsigned uFisType, uint8_t *pCmdFis)
+{
+    int         rc = VINF_SUCCESS;
+    RTGCPHYS    GCPhysAddrRecFis = pAhciPort->GCPhysAddrFb;
+    unsigned    cbFis = 0;
+
+    ahciLog(("%s: pAhciPort=%p uFisType=%u pCmdFis=%p\n", __FUNCTION__, pAhciPort, uFisType, pCmdFis));
+
+    if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+    {
+        AssertMsg(GCPhysAddrRecFis, ("%s: GCPhysAddrRecFis is 0\n", __FUNCTION__));
+
+        /* Determine the offset and size of the FIS based on uFisType. */
+        switch (uFisType)
+        {
+            case AHCI_CMDFIS_TYPE_D2H:
+                {
+                    GCPhysAddrRecFis += AHCI_RECFIS_RFIS_OFFSET;
+                    cbFis = AHCI_CMDFIS_TYPE_D2H_SIZE;
+                }
+                break;
+            case AHCI_CMDFIS_TYPE_SETDEVBITS:
+                {
+                    GCPhysAddrRecFis += AHCI_RECFIS_SDBFIS_OFFSET;
+                    cbFis = AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE;
+                }
+                break;
+            case AHCI_CMDFIS_TYPE_DMASETUP:
+                {
+                    GCPhysAddrRecFis += AHCI_RECFIS_DSFIS_OFFSET;
+                    cbFis = AHCI_CMDFIS_TYPE_DMASETUP_SIZE;
+                }
+                break;
+            case AHCI_CMDFIS_TYPE_PIOSETUP:
+                {
+                    GCPhysAddrRecFis += AHCI_RECFIS_PSFIS_OFFSET;
+                    cbFis = AHCI_CMDFIS_TYPE_PIOSETUP_SIZE;
+                }
+                break;
+            default:
+                /*
+                 * We should post the unknown FIS into memory too but this never happens because
+                 * we know which FIS types we generate. ;)
+                 */
+                AssertMsgFailed(("%s: Unknown FIS type!\n", __FUNCTION__));
+        }
+
+        /* Post the FIS into memory. */
+        ahciLog(("%s: PDMDevHlpPhysWrite GCPhysAddrRecFis=%RGp cbFis=%u\n", __FUNCTION__, GCPhysAddrRecFis, cbFis));
+        PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrRecFis, pCmdFis, cbFis);
+    }
+
+    return rc;
+}
+
+DECLINLINE(void) ataH2BE_U16(uint8_t *pbBuf, uint16_t val)
+{
+    pbBuf[0] = val >> 8;
+    pbBuf[1] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U24(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 16;
+    pbBuf[1] = val >> 8;
+    pbBuf[2] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U32(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 24;
+    pbBuf[1] = val >> 16;
+    pbBuf[2] = val >> 8;
+    pbBuf[3] = val;
+}
+
+
+DECLINLINE(uint16_t) ataBE2H_U16(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 8) | pbBuf[1];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U24(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U32(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
+}
+
+
+DECLINLINE(void) ataLBA2MSF(uint8_t *pbBuf, uint32_t iATAPILBA)
+{
+    iATAPILBA += 150;
+    pbBuf[0] = (iATAPILBA / 75) / 60;
+    pbBuf[1] = (iATAPILBA / 75) % 60;
+    pbBuf[2] = iATAPILBA % 75;
+}
+
+
+DECLINLINE(uint32_t) ataMSF2LBA(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
+}
+
+static void atapiCmdOK(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    pAhciPortTaskState->uATARegError = 0;
+    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+    pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] & ~7)
+        | ((pAhciPortTaskState->uTxDir != PDMBLOCKTXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
+        | (!pAhciPortTaskState->cbTransfer ? ATAPI_INT_REASON_CD : 0);
+    pAhciPort->uATAPISenseKey = SCSI_SENSE_NONE;
+    pAhciPort->uATAPIASC = SCSI_ASC_NONE;
+}
+
+
+static void atapiCmdError(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+    pAhciPortTaskState->uATARegError = uATAPISenseKey << 4;
+    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+    pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] & ~7) |
+                                                     ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+    pAhciPort->uATAPISenseKey = uATAPISenseKey;
+    pAhciPort->uATAPIASC = uATAPIASC;
+}
+
+static void ataSCSIPadStr(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+    for (uint32_t i = 0; i < cbSize; i++)
+    {
+        if (*pbSrc)
+            pbDst[i] = *pbSrc++;
+        else
+            pbDst[i] = ' ';
+    }
+}
+
+static void ataPadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+    for (uint32_t i = 0; i < cbSize; i++)
+    {
+        if (*pbSrc)
+            pbDst[i ^ 1] = *pbSrc++;
+        else
+            pbDst[i ^ 1] = ' ';
+    }
+}
+
+static int ahciIdentifySS(PAHCIPort pAhciPort, void *pvBuf)
+{
+    uint16_t *p;
+    int rc = VINF_SUCCESS;
+
+    p = (uint16_t *)pvBuf;
+    memset(p, 0, 512);
+    p[0] = RT_H2LE_U16(0x0040);
+    p[1] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383));
+    p[3] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads);
+    /* Block size; obsolete, but required for the BIOS. */
+    p[5] = RT_H2LE_U16(512);
+    p[6] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors);
+    ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_SERIAL_NUMBER_LENGTH); /* serial number */
+    p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+    p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+    p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+    ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */
+    ataPadString((uint8_t *)(p + 27), pAhciPort->szModelNumber, AHCI_MODEL_NUMBER_LENGTH); /* model */
+#if ATA_MAX_MULT_SECTORS > 1
+    p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+#endif
+    p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+    p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+    p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+    p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+    p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+    p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+    p[54] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383));
+    p[55] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads);
+    p[56] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors);
+    p[57] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors);
+    p[58] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors >> 16);
+    if (pAhciPort->cMultSectors)
+        p[59] = RT_H2LE_U16(0x100 | pAhciPort->cMultSectors);
+    if (pAhciPort->cTotalSectors <= (1 << 28) - 1)
+    {
+        p[60] = RT_H2LE_U16(pAhciPort->cTotalSectors);
+        p[61] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 16);
+    }
+    else
+    {
+        /* Report maximum number of sectors possible with LBA28 */
+        p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+        p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+    }
+    p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* MDMA modes supported / mode enabled */
+    p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+    p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+    p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+    p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+    p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+    p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+    p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+    p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management,  write cache and look-ahead */
+    p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+    p[84] = RT_H2LE_U16(1 << 14);
+    p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management,  write cache and look-ahead */
+    p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+    p[87] = RT_H2LE_U16(1 << 14);
+    p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
+    p[93] = RT_H2LE_U16(0x00);
+    p[100] = RT_H2LE_U16(pAhciPort->cTotalSectors);
+    p[101] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 16);
+    p[102] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 32);
+    p[103] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 48);
+
+    /* The following are SATA specific */
+    p[75] = RT_H2LE_U16(31); /* We support 32 commands */
+    p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */
+
+    return VINF_SUCCESS;
+}
+
+typedef int (*PAtapiFunc)(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+
+static int atapiGetConfigurationSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiIdentifySS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiInquirySS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiMechanismStatusSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiModeSenseErrorRecoverySS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiModeSenseCDStatusSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadCapacitySS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadDiscInformationSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadTOCNormalSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadTOCMultiSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadTOCRawSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiReadTrackInformationSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+static int atapiRequestSenseSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+//static int atapiPassthroughSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
+
+/**
+ * Source/sink function indexes for g_apfnAtapiFuncs.
+ */
+typedef enum ATAPIFN
+{
+    ATAFN_SS_NULL = 0,
+    ATAFN_SS_ATAPI_GET_CONFIGURATION,
+    ATAFN_SS_ATAPI_IDENTIFY,
+    ATAFN_SS_ATAPI_INQUIRY,
+    ATAFN_SS_ATAPI_MECHANISM_STATUS,
+    ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
+    ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
+    ATAFN_SS_ATAPI_READ_CAPACITY,
+    ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
+    ATAFN_SS_ATAPI_READ_TOC_NORMAL,
+    ATAFN_SS_ATAPI_READ_TOC_MULTI,
+    ATAFN_SS_ATAPI_READ_TOC_RAW,
+    ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
+    ATAFN_SS_ATAPI_REQUEST_SENSE,
+    //ATAFN_SS_ATAPI_PASSTHROUGH,
+    ATAFN_SS_MAX
+} ATAPIFN;
+
+/**
+ * Array of source/sink functions, the index is ATAFNSS.
+ * Make sure ATAFNSS and this array match!
+ */
+static const PAtapiFunc g_apfnAtapiFuncs[ATAFN_SS_MAX] =
+{
+    NULL,
+    atapiGetConfigurationSS,
+    atapiIdentifySS,
+    atapiInquirySS,
+    atapiMechanismStatusSS,
+    atapiModeSenseErrorRecoverySS,
+    atapiModeSenseCDStatusSS,
+    atapiReadCapacitySS,
+    atapiReadDiscInformationSS,
+    atapiReadTOCNormalSS,
+    atapiReadTOCMultiSS,
+    atapiReadTOCRawSS,
+    atapiReadTrackInformationSS,
+    atapiRequestSenseSS
+    //atapiPassthroughSS
+};
+
+static int atapiIdentifySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint16_t p[256];
+    char aSerial[20];
+    RTUUID Uuid;
+    int rc;
+
+    rc = pAhciPort->pDrvBlock ? pAhciPort->pDrvBlock->pfnGetUuid(pAhciPort->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
+    if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+    {
+        /* Generate a predictable serial for drives which don't have a UUID. */
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%x-1a2b3c4d",
+                    pAhciPort->iLUN);
+    }
+    else
+        RTStrPrintf(aSerial, sizeof(aSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+    memset(p, 0, 512);
+    /* Removable CDROM, 50us response, 12 byte packets */
+    p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0);
+    ataPadString((uint8_t *)(p + 10), aSerial, 20); /* serial number */
+    p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+    p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+    ataPadString((uint8_t *)(p + 23), "1.0", 8); /* firmware version */
+    ataPadString((uint8_t *)(p + 27), "VBOX CD-ROM", 40); /* model */
+    p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+    p[50] = RT_H2LE_U16(1 << 14);  /* No drive specific standby timer minimum */
+    p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+    p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+    p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+    p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* MDMA modes supported / mode enabled */
+    p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+    p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+    p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+    p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+    p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+    p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+    p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+    p[75] = RT_H2LE_U16(1); /* queue depth 1 */
+    p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+    p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+    p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+    p[83] = RT_H2LE_U16(1 << 14);
+    p[84] = RT_H2LE_U16(1 << 14);
+    p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+    p[86] = RT_H2LE_U16(0);
+    p[87] = RT_H2LE_U16(1 << 14);
+    p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
+    p[93] = RT_H2LE_U16((1 | 1 << 1) << ((pAhciPort->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+
+    /* The following are SATA specific */
+    p[75] = RT_H2LE_U16(31); /* We support 32 commands */
+    p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&p[0], sizeof(p));
+    *pcbData = sizeof(p);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+static int atapiReadCapacitySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[8];
+
+    ataH2BE_U32(aBuf, pAhciPort->cTotalSectors - 1);
+    ataH2BE_U32(aBuf + 4, 2048);
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiReadDiscInformationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[34];
+
+    memset(aBuf, '\0', 34);
+    ataH2BE_U16(aBuf, 32);
+    aBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+    aBuf[3] = 1; /* number of first track */
+    aBuf[4] = 1; /* number of sessions (LSB) */
+    aBuf[5] = 1; /* first track number in last session (LSB) */
+    aBuf[6] = 1; /* last track number in last session (LSB) */
+    aBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+    aBuf[8] = 0; /* disc type = CD-ROM */
+    aBuf[9] = 0; /* number of sessions (MSB) */
+    aBuf[10] = 0; /* number of sessions (MSB) */
+    aBuf[11] = 0; /* number of sessions (MSB) */
+    ataH2BE_U32(aBuf + 16, 0x00ffffff); /* last session lead-in start time is not available */
+    ataH2BE_U32(aBuf + 20, 0x00ffffff); /* last possible start time for lead-out is not available */
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiReadTrackInformationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[36];
+
+    /* Accept address/number type of 1 only, and only track 1 exists. */
+    if ((pAhciPortTaskState->aATAPICmd[1] & 0x03) != 1 || ataBE2H_U32(&pAhciPortTaskState->aATAPICmd[2]) != 1)
+    {
+        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return VINF_SUCCESS;
+    }
+    memset(aBuf, '\0', 36);
+    ataH2BE_U16(aBuf, 34);
+    aBuf[2] = 1; /* track number (LSB) */
+    aBuf[3] = 1; /* session number (LSB) */
+    aBuf[5] = (0 << 5) | (0 << 4) | (4 << 0); /* not damaged, primary copy, data track */
+    aBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | (1 << 0); /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
+    aBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+    ataH2BE_U32(aBuf + 8, 0); /* track start address is 0 */
+    ataH2BE_U32(aBuf + 24, pAhciPort->cTotalSectors); /* track size */
+    aBuf[32] = 0; /* track number (MSB) */
+    aBuf[33] = 0; /* session number (MSB) */
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiGetConfigurationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[32];
+
+    /* Accept valid request types only, and only starting feature 0. */
+    if ((pAhciPortTaskState->aATAPICmd[1] & 0x03) == 3 || ataBE2H_U16(&pAhciPortTaskState->aATAPICmd[2]) != 0)
+    {
+        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return VINF_SUCCESS;
+    }
+    memset(aBuf, '\0', 32);
+    ataH2BE_U32(aBuf, 16);
+    /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+     * way to differentiate them right now is based on the image size). Also
+     * implement signalling "no current profile" if no medium is loaded. */
+    ataH2BE_U16(aBuf + 6, 0x08); /* current profile: read-only CD */
+
+    ataH2BE_U16(aBuf + 8, 0); /* feature 0: list of profiles supported */
+    aBuf[10] = (0 << 2) | (1 << 1) | (1 || 0); /* version 0, persistent, current */
+    aBuf[11] = 8; /* additional bytes for profiles */
+    /* The MMC-3 spec says that DVD-ROM read capability should be reported
+     * before CD-ROM read capability. */
+    ataH2BE_U16(aBuf + 12, 0x10); /* profile: read-only DVD */
+    aBuf[14] = (0 << 0); /* NOT current profile */
+    ataH2BE_U16(aBuf + 16, 0x08); /* profile: read only CD */
+    aBuf[18] = (1 << 0); /* current profile */
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiInquirySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[36];
+
+    aBuf[0] = 0x05; /* CD-ROM */
+    aBuf[1] = 0x80; /* removable */
+    aBuf[2] = 0x00; /* ISO */
+    aBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+    aBuf[4] = 31; /* additional length */
+    aBuf[5] = 0; /* reserved */
+    aBuf[6] = 0; /* reserved */
+    aBuf[7] = 0; /* reserved */
+    ataSCSIPadStr(aBuf + 8, "VBOX", 8);
+    ataSCSIPadStr(aBuf + 16, "CD-ROM", 16);
+    ataSCSIPadStr(aBuf + 32, "1.0", 4);
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiModeSenseErrorRecoverySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[16];
+
+    ataH2BE_U16(&aBuf[0], 16 + 6);
+    aBuf[2] = 0x70;
+    aBuf[3] = 0;
+    aBuf[4] = 0;
+    aBuf[5] = 0;
+    aBuf[6] = 0;
+    aBuf[7] = 0;
+
+    aBuf[8] = 0x01;
+    aBuf[9] = 0x06;
+    aBuf[10] = 0x00;
+    aBuf[11] = 0x05;
+    aBuf[12] = 0x00;
+    aBuf[13] = 0x00;
+    aBuf[14] = 0x00;
+    aBuf[15] = 0x00;
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiModeSenseCDStatusSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[40];
+
+    ataH2BE_U16(&aBuf[0], 38);
+    aBuf[2] = 0x70;
+    aBuf[3] = 0;
+    aBuf[4] = 0;
+    aBuf[5] = 0;
+    aBuf[6] = 0;
+    aBuf[7] = 0;
+
+    aBuf[8] = 0x2a;
+    aBuf[9] = 30; /* page length */
+    aBuf[10] = 0x08; /* DVD-ROM read support */
+    aBuf[11] = 0x00; /* no write support */
+    /* The following claims we support audio play. This is obviously false,
+     * but the Linux generic CDROM support makes many features depend on this
+     * capability. If it's not set, this causes many things to be disabled. */
+    aBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+    aBuf[13] = 0x00; /* no subchannel reads supported */
+    aBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+    if (pAhciPort->pDrvMount->pfnIsLocked(pAhciPort->pDrvMount))
+        aBuf[14] |= 1 << 1; /* report lock state */
+    aBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+    ataH2BE_U16(&aBuf[16], 5632); /* (obsolete) claim 32x speed support */
+    ataH2BE_U16(&aBuf[18], 2); /* number of audio volume levels */
+    ataH2BE_U16(&aBuf[20], 128); /* buffer size supported in Kbyte - We don't have a buffer because we write directly into guest memory.
+                                    Just write the value DevATA is using. */
+    ataH2BE_U16(&aBuf[22], 5632); /* (obsolete) current read speed 32x */
+    aBuf[24] = 0; /* reserved */
+    aBuf[25] = 0; /* reserved for digital audio (see idx 15) */
+    ataH2BE_U16(&aBuf[26], 0); /* (obsolete) maximum write speed */
+    ataH2BE_U16(&aBuf[28], 0); /* (obsolete) current write speed */
+    ataH2BE_U16(&aBuf[30], 0); /* copy management revision supported 0=no CSS */
+    aBuf[32] = 0; /* reserved */
+    aBuf[33] = 0; /* reserved */
+    aBuf[34] = 0; /* reserved */
+    aBuf[35] = 1; /* rotation control CAV */
+    ataH2BE_U16(&aBuf[36], 0); /* current write speed */
+    ataH2BE_U16(&aBuf[38], 0); /* number of write speed performance descriptors */
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiRequestSenseSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[18];
+
+    memset(&aBuf[0], 0, 18);
+    aBuf[0] = 0x70 | (1 << 7);
+    aBuf[2] = pAhciPort->uATAPISenseKey;
+    aBuf[7] = 10;
+    aBuf[12] = pAhciPort->uATAPIASC;
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiMechanismStatusSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[8];
+
+    ataH2BE_U16(&aBuf[0], 0);
+    /* no current LBA */
+    aBuf[2] = 0;
+    aBuf[3] = 0;
+    aBuf[4] = 0;
+    aBuf[5] = 1;
+    ataH2BE_U16(aBuf + 6, 0);
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiReadTOCNormalSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[20], *q, iStartTrack;
+    bool fMSF;
+    uint32_t cbSize;
+
+    fMSF = (pAhciPortTaskState->aATAPICmd[1] >> 1) & 1;
+    iStartTrack = pAhciPortTaskState->aATAPICmd[6];
+    if (iStartTrack > 1 && iStartTrack != 0xaa)
+    {
+        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+        return VINF_SUCCESS;
+    }
+    q = aBuf + 2;
+    *q++ = 1; /* first session */
+    *q++ = 1; /* last session */
+    if (iStartTrack <= 1)
+    {
+        *q++ = 0; /* reserved */
+        *q++ = 0x14; /* ADR, control */
+        *q++ = 1;    /* track number */
+        *q++ = 0; /* reserved */
+        if (fMSF)
+        {
+            *q++ = 0; /* reserved */
+            ataLBA2MSF(q, 0);
+            q += 3;
+        }
+        else
+        {
+            /* sector 0 */
+            ataH2BE_U32(q, 0);
+            q += 4;
+        }
+    }
+    /* lead out track */
+    *q++ = 0; /* reserved */
+    *q++ = 0x14; /* ADR, control */
+    *q++ = 0xaa; /* track number */
+    *q++ = 0; /* reserved */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, pAhciPort->cTotalSectors);
+        q += 3;
+    }
+    else
+    {
+        ataH2BE_U32(q, pAhciPort->cTotalSectors);
+        q += 4;
+    }
+    cbSize = q - aBuf;
+    ataH2BE_U16(aBuf, cbSize - 2);
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], cbSize);
+    *pcbData = cbSize;
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiReadTOCMultiSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[12];
+    bool fMSF;
+
+    fMSF = (pAhciPortTaskState->aATAPICmd[1] >> 1) & 1;
+    /* multi session: only a single session defined */
+/** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R) with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being able to figure out whether numbers are in BCD or hex. */
+    memset(aBuf, 0, 12);
+    aBuf[1] = 0x0a;
+    aBuf[2] = 0x01;
+    aBuf[3] = 0x01;
+    aBuf[5] = 0x14; /* ADR, control */
+    aBuf[6] = 1; /* first track in last complete session */
+    if (fMSF)
+    {
+        aBuf[8] = 0; /* reserved */
+        ataLBA2MSF(&aBuf[9], 0);
+    }
+    else
+    {
+        /* sector 0 */
+        ataH2BE_U32(aBuf + 8, 0);
+    }
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], sizeof(aBuf));
+    *pcbData = sizeof(aBuf);
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+
+static int atapiReadTOCRawSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
+{
+    uint8_t aBuf[50]; /* Counted a maximum of 45 bytes but better be on the safe side. */
+    uint8_t *q, iStartTrack;
+    bool fMSF;
+    uint32_t cbSize;
+
+    fMSF = (pAhciPortTaskState->aATAPICmd[1] >> 1) & 1;
+    iStartTrack = pAhciPortTaskState->aATAPICmd[6];
+
+    q = aBuf + 2;
+    *q++ = 1; /* first session */
+    *q++ = 1; /* last session */
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa0; /* first track in program area */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    *q++ = 0;
+    *q++ = 1; /* first track */
+    *q++ = 0x00; /* disk type CD-DA or CD data */
+    *q++ = 0;
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa1; /* last track in program area */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    *q++ = 0;
+    *q++ = 1; /* last track */
+    *q++ = 0;
+    *q++ = 0;
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* data track */
+    *q++ = 0; /* track number */
+    *q++ = 0xa2; /* lead-out */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, pAhciPort->cTotalSectors);
+        q += 3;
+    }
+    else
+    {
+        ataH2BE_U32(q, pAhciPort->cTotalSectors);
+        q += 4;
+    }
+
+    *q++ = 1; /* session number */
+    *q++ = 0x14; /* ADR, control */
+    *q++ = 0;    /* track number */
+    *q++ = 1;    /* point */
+    *q++ = 0; /* min */
+    *q++ = 0; /* sec */
+    *q++ = 0; /* frame */
+    if (fMSF)
+    {
+        *q++ = 0; /* reserved */
+        ataLBA2MSF(q, 0);
+        q += 3;
+    }
+    else
+    {
+        /* sector 0 */
+        ataH2BE_U32(q, 0);
+        q += 4;
+    }
+
+    cbSize = q - aBuf;
+    ataH2BE_U16(aBuf, cbSize - 2);
+
+    /* Copy the buffer in to the scatter gather list. */
+    ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&aBuf[0], cbSize);
+    *pcbData = cbSize;
+
+    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+    return VINF_SUCCESS;
+}
+
+static int atapiDoTransfer(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, ATAPIFN iSourceSink)
+{
+    int cbTransfered;
+    int rc, rcSourceSink;
+
+    /* Create scatter gather list. */
+    rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, false);
+    if (RT_FAILURE(rc))
+        AssertMsgFailed(("Getting number of list elements failed rc=%Rrc\n", rc));
+
+    rcSourceSink = g_apfnAtapiFuncs[iSourceSink](pAhciPortTaskState, pAhciPort, &cbTransfered);
+
+    pAhciPortTaskState->cmdHdr.u32PRDBC = cbTransfered;
+
+    rc = ahciScatterGatherListDestroy(pAhciPort, pAhciPortTaskState);
+    if (RT_FAILURE(rc))
+        AssertMsgFailed(("Destroying list failed rc=%Rrc\n", rc));
+
+    /* Write updated command header into memory of the guest. */
+    PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
+
+    return rcSourceSink;
+}
+
+static int atapiReadSectors(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+    Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iATAPILBA));
+
+    switch (cbSector)
+    {
+        case 2048:
+            pAhciPortTaskState->uOffset = iATAPILBA * cbSector;
+            pAhciPortTaskState->cbTransfer = cSectors * cbSector;
+            break;
+        case 2352:
+            {
+                AssertMsgFailed(("2352 read\n"));
+                /* @todo: This is quite difficult as the data transfer is not handled here
+                          We need to add the sync bytes etc. here and modify the pointers
+                          and size of the sg entries.  */
+#if 0
+                uint8_t *pbBuf = s->CTXSUFF(pbIOBuffer);
+
+                for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
+                {
+                    /* sync bytes */
+                    *pbBuf++ = 0x00;
+                    memset(pbBuf, 0xff, 11);
+                    pbBuf += 11;
+                    /* MSF */
+                    ataLBA2MSF(pbBuf, i);
+                    pbBuf += 3;
+                    *pbBuf++ = 0x01; /* mode 1 data */
+                    /* data */
+                    rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)i * 2048, pbBuf, 2048);
+                    if (RT_FAILURE(rc))
+                        break;
+                    pbBuf += 2048;
+                    /* ECC */
+                    memset(pbBuf, 0, 288);
+                    pbBuf += 288;
+                }
+#endif
+                pAhciPortTaskState->uOffset = iATAPILBA * 2048;
+                pAhciPortTaskState->cbTransfer = cSectors * 2048;
+            }
+            break;
+        default:
+            AssertMsgFailed(("Unsupported sectors size\n"));
+            break;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static int atapiParseCmdVirtualATAPI(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    int rc = PDMBLOCKTXDIR_NONE;
+    const uint8_t *pbPacket;
+    uint32_t cbMax;
+
+    pbPacket = pAhciPortTaskState->aATAPICmd;
+
+    ahciLog(("%s: ATAPI CMD=%#04x \"%s\"\n", __FUNCTION__, pbPacket[0], SCSICmdText(pbPacket[0])));
+
+    switch (pbPacket[0])
+    {
+        case SCSI_TEST_UNIT_READY:
+            if (pAhciPort->cNotifiedMediaChange > 0)
+            {
+                if (pAhciPort->cNotifiedMediaChange-- > 2)
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                else
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+            }
+            else if (pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+                atapiCmdOK(pAhciPort, pAhciPortTaskState);
+            else
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+            break;
+        case SCSI_MODE_SENSE_10:
+            {
+                uint8_t uPageControl, uPageCode;
+                cbMax = ataBE2H_U16(pbPacket + 7);
+                uPageControl = pbPacket[2] >> 6;
+                uPageCode = pbPacket[2] & 0x3f;
+                switch (uPageControl)
+                {
+                    case SCSI_PAGECONTROL_CURRENT:
+                        switch (uPageCode)
+                        {
+                            case SCSI_MODEPAGE_ERROR_RECOVERY:
+                                atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY);
+                                break;
+                            case SCSI_MODEPAGE_CD_STATUS:
+                                atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS);
+                                break;
+                            default:
+                                goto error_cmd;
+                        }
+                        break;
+                    case SCSI_PAGECONTROL_CHANGEABLE:
+                        goto error_cmd;
+                    case SCSI_PAGECONTROL_DEFAULT:
+                        goto error_cmd;
+                    default:
+                    case SCSI_PAGECONTROL_SAVED:
+                        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+                        break;
+                }
+            }
+            break;
+        case SCSI_REQUEST_SENSE:
+            cbMax = pbPacket[4];
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_REQUEST_SENSE);
+            break;
+        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+            if (pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+            {
+                if (pbPacket[4] & 1)
+                    pAhciPort->pDrvMount->pfnLock(pAhciPort->pDrvMount);
+                else
+                    pAhciPort->pDrvMount->pfnUnlock(pAhciPort->pDrvMount);
+                atapiCmdOK(pAhciPort, pAhciPortTaskState);
+            }
+            else
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+            break;
+        case SCSI_READ_10:
+        case SCSI_READ_12:
+            {
+                uint32_t cSectors, iATAPILBA;
+
+                if (pAhciPort->cNotifiedMediaChange > 0)
+                {
+                    pAhciPort->cNotifiedMediaChange-- ;
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+                {
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                if (pbPacket[0] == SCSI_READ_10)
+                    cSectors = ataBE2H_U16(pbPacket + 7);
+                else
+                    cSectors = ataBE2H_U32(pbPacket + 6);
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (cSectors == 0)
+                {
+                    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+                    break;
+                }
+                if ((uint64_t)iATAPILBA + cSectors > pAhciPort->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on reading from places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATAPI: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA + cSectors));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                atapiReadSectors(pAhciPort, pAhciPortTaskState, iATAPILBA, cSectors, 2048);
+                rc = PDMBLOCKTXDIR_FROM_DEVICE;
+            }
+            break;
+        case SCSI_READ_CD:
+            {
+                uint32_t cSectors, iATAPILBA;
+
+                if (pAhciPort->cNotifiedMediaChange > 0)
+                {
+                    pAhciPort->cNotifiedMediaChange-- ;
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+                {
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (cSectors == 0)
+                {
+                    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+                    break;
+                }
+                if ((uint64_t)iATAPILBA + cSectors > pAhciPort->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on reading from places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA + cSectors));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                switch (pbPacket[9] & 0xf8)
+                {
+                    case 0x00:
+                        /* nothing */
+                        atapiCmdOK(pAhciPort, pAhciPortTaskState);
+                        break;
+                    case 0x10:
+                        /* normal read */
+                        atapiReadSectors(pAhciPort, pAhciPortTaskState, iATAPILBA, cSectors, 2048);
+                        rc = PDMBLOCKTXDIR_FROM_DEVICE;
+                        break;
+                    case 0xf8:
+                        /* read all data */
+                        atapiReadSectors(pAhciPort, pAhciPortTaskState, iATAPILBA, cSectors, 2352);
+                        rc = PDMBLOCKTXDIR_FROM_DEVICE;
+                        break;
+                    default:
+                        LogRel(("AHCI ATAPI: LUN#%d: CD-ROM sector format not supported\n", pAhciPort->iLUN));
+                        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                        break;
+                }
+            }
+            break;
+        case SCSI_SEEK_10:
+            {
+                uint32_t iATAPILBA;
+                if (pAhciPort->cNotifiedMediaChange > 0)
+                {
+                    pAhciPort->cNotifiedMediaChange-- ;
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+                {
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                iATAPILBA = ataBE2H_U32(pbPacket + 2);
+                if (iATAPILBA > pAhciPort->cTotalSectors)
+                {
+                    /* Rate limited logging, one log line per second. For
+                     * guests that insist on seeking to places outside the
+                     * valid area this often generates too many release log
+                     * entries otherwise. */
+                    static uint64_t uLastLogTS = 0;
+                    if (RTTimeMilliTS() >= uLastLogTS + 1000)
+                    {
+                        LogRel(("AHCI ATAPI: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA));
+                        uLastLogTS = RTTimeMilliTS();
+                    }
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+                    break;
+                }
+                atapiCmdOK(pAhciPort, pAhciPortTaskState);
+                pAhciPortTaskState->uATARegStatus |= ATA_STAT_SEEK; /* Linux expects this. */
+            }
+            break;
+        case SCSI_START_STOP_UNIT:
+            {
+                int rc = VINF_SUCCESS;
+                switch (pbPacket[4] & 3)
+                {
+                    case 0: /* 00 - Stop motor */
+                    case 1: /* 01 - Start motor */
+                        break;
+                    case 2: /* 10 - Eject media */
+                        /* This must be done from EMT. */
+                        {
+                            PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+                            PPDMDEVINS pDevIns = pAhci->CTX_SUFF(pDevIns);
+                            PVMREQ pReq;
+
+                            rc = VMR3ReqCall(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY, &pReq, RT_INDEFINITE_WAIT,
+                                             (PFNRT)pAhciPort->pDrvMount->pfnUnmount, 2, pAhciPort->pDrvMount, false);
+                            AssertReleaseRC(rc);
+                            VMR3ReqFree(pReq);
+                        }
+                        break;
+                    case 3: /* 11 - Load media */
+                        /** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
+                        break;
+                }
+                if (RT_SUCCESS(rc))
+                    atapiCmdOK(pAhciPort, pAhciPortTaskState);
+                else
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+            }
+            break;
+        case SCSI_MECHANISM_STATUS:
+            {
+                cbMax = ataBE2H_U16(pbPacket + 8);
+                atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_MECHANISM_STATUS);
+            }
+            break;
+        case SCSI_READ_TOC_PMA_ATIP:
+            {
+                uint8_t format;
+
+                if (pAhciPort->cNotifiedMediaChange > 0)
+                {
+                    pAhciPort->cNotifiedMediaChange-- ;
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                    break;
+                }
+                else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+                {
+                    atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                    break;
+                }
+                cbMax = ataBE2H_U16(pbPacket + 7);
+                /* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
+                 * but Linux kernel uses offset 9 (topmost 2 bits). Hope that
+                 * the other field is clear... */
+                format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
+                switch (format)
+                {
+                    case 0:
+                        atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_TOC_NORMAL);
+                        break;
+                    case 1:
+                        atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_TOC_MULTI);
+                        break;
+                    case 2:
+                        atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_TOC_RAW);
+                        break;
+                    default:
+                    error_cmd:
+                        atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                        break;
+                }
+            }
+            break;
+        case SCSI_READ_CAPACITY:
+            if (pAhciPort->cNotifiedMediaChange > 0)
+            {
+                pAhciPort->cNotifiedMediaChange-- ;
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+            {
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_CAPACITY);
+            break;
+        case SCSI_READ_DISC_INFORMATION:
+            if (pAhciPort->cNotifiedMediaChange > 0)
+            {
+                pAhciPort->cNotifiedMediaChange-- ;
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+            {
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_DISC_INFORMATION);
+            break;
+        case SCSI_READ_TRACK_INFORMATION:
+            if (pAhciPort->cNotifiedMediaChange > 0)
+            {
+                pAhciPort->cNotifiedMediaChange-- ;
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+                break;
+            }
+            else if (!pAhciPort->pDrvMount->pfnIsMounted(pAhciPort->pDrvMount))
+            {
+                atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+                break;
+            }
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION);
+            break;
+        case SCSI_GET_CONFIGURATION:
+            /* No media change stuff here, it can confuse Linux guests. */
+            cbMax = ataBE2H_U16(pbPacket + 7);
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_GET_CONFIGURATION);
+            break;
+        case SCSI_INQUIRY:
+            cbMax = pbPacket[4];
+            atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_INQUIRY);
+            break;
+        default:
+            atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+    }
+
+    return rc;
+}
+
+/**
+ * Reset all values after a reset of the attached storage device.
+ *
+ * @returns nothing
+ * @param pAhciPort             The port the device is attached to.
+ * @param pAhciPortTaskState    The state to get the tag number from.
+ */
+static void ahciFinishStorageDeviceReset(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    /* Send a status good D2H FIS. */
+    pAhciPort->fResetDevice = false;
+    if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+        ahciPostFirstD2HFisIntoMemory(pAhciPort);
+
+    /* As this is the first D2H FIS after the reset update the signature in the SIG register of the port. */
+    pAhciPort->regSIG  = 0x101;
+    ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciPortTaskState->uTag));
+
+    ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN);
+}
+
+/**
+ * Build a D2H FIS and post into the memory area of the guest.
+ *
+ * @returns Nothing
+ * @param   pAhciPort          The port of the SATA controller.
+ * @param   pAhciPortTaskState The state of the task.
+ * @param   pCmdFis            Pointer to the command FIS from the guest.
+ * @param   fInterrupt         If an interrupt should be send to the guest.
+ */
+static void ahciSendD2HFis(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t *pCmdFis, bool fInterrupt)
+{
+    uint8_t d2hFis[20];
+    bool fAssertIntr = false;
+    PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+
+    ahciLog(("%s: building D2H Fis\n", __FUNCTION__));
+
+    if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+    {
+        memset(&d2hFis[0], 0, sizeof(d2hFis));
+        d2hFis[AHCI_CMDFIS_TYPE]  = AHCI_CMDFIS_TYPE_D2H;
+        d2hFis[AHCI_CMDFIS_BITS]  = (fInterrupt ? AHCI_CMDFIS_I : 0);
+        d2hFis[AHCI_CMDFIS_STS]   = pAhciPortTaskState->uATARegStatus;
+        d2hFis[AHCI_CMDFIS_ERR]   = pAhciPortTaskState->uATARegError;
+        d2hFis[AHCI_CMDFIS_SECTN] = pCmdFis[AHCI_CMDFIS_SECTN];
+        d2hFis[AHCI_CMDFIS_CYLL]  = pCmdFis[AHCI_CMDFIS_CYLL];
+        d2hFis[AHCI_CMDFIS_CYLH]  = pCmdFis[AHCI_CMDFIS_CYLH];
+        d2hFis[AHCI_CMDFIS_HEAD]  = pCmdFis[AHCI_CMDFIS_HEAD];
+        d2hFis[AHCI_CMDFIS_SECTNEXP] = pCmdFis[AHCI_CMDFIS_SECTNEXP];
+        d2hFis[AHCI_CMDFIS_CYLLEXP]  = pCmdFis[AHCI_CMDFIS_CYLLEXP];
+        d2hFis[AHCI_CMDFIS_CYLHEXP]  = pCmdFis[AHCI_CMDFIS_CYLHEXP];
+        d2hFis[AHCI_CMDFIS_SECTC]    = pCmdFis[AHCI_CMDFIS_SECTC];
+        d2hFis[AHCI_CMDFIS_SECTCEXP] = pCmdFis[AHCI_CMDFIS_SECTCEXP];
+
+        /* Update registers. */
+        pAhciPort->regTFD = (pAhciPortTaskState->uATARegError << 8) | pAhciPortTaskState->uATARegStatus;
+
+        ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis);
+
+        if (pAhciPortTaskState->uATARegStatus & ATA_STAT_ERR)
+        {
+            /* Error bit is set. */
+            pAhciPort->regIS |= AHCI_PORT_IS_TFES;
+            if (pAhciPort->regIE & AHCI_PORT_IE_TFEE)
+                fAssertIntr = true;
+        }
+
+        if (fInterrupt)
+        {
+            pAhciPort->regIS |= AHCI_PORT_IS_DHRS;
+            /* Check if we should assert an interrupt */
+            if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+                fAssertIntr = true;
+        }
+
+        ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciPortTaskState->uTag));
+
+        if (fAssertIntr)
+            ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN);
+    }
+}
+
+/**
+ * Build a SDB Fis and post it into the memory area of the guest.
+ *
+ * @returns Nothing
+ * @param   pAhciPort           The port for which the SDB Fis is send.
+ * @param   uFinishedTasks      Bitmask of finished tasks.
+ * @param   pAhciPortTaskState  The state of the last task.
+ * @param   fInterrupt          If an interrupt should be asserted.
+ */
+static void ahciSendSDBFis(PAHCIPort pAhciPort, uint32_t uFinishedTasks, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fInterrupt)
+{
+    uint32_t sdbFis[2];
+    bool fAssertIntr = false;
+    PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+
+    ahciLog(("%s: Building SDB FIS\n", __FUNCTION__));
+
+    if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+    {
+        memset(&sdbFis[0], 0, sizeof(sdbFis));
+        sdbFis[0] = AHCI_CMDFIS_TYPE_SETDEVBITS;
+        sdbFis[0] |= (fInterrupt ? (1 << 14) : 0);
+        sdbFis[0] |= pAhciPortTaskState->uATARegError << 24;
+        sdbFis[0] |= (pAhciPortTaskState->uATARegStatus & 0x77) << 16; /* Some bits are marked as reserved and thus are masked out. */
+        sdbFis[1] = uFinishedTasks;
+
+        ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_SETDEVBITS, (uint8_t *)sdbFis);
+
+        if (pAhciPortTaskState->uATARegStatus & ATA_STAT_ERR)
+        {
+            /* Error bit is set. */
+            pAhciPort->regIS |= AHCI_PORT_IS_TFES;
+            if (pAhciPort->regIE & AHCI_PORT_IE_TFEE)
+                fAssertIntr = true;
+        }
+
+        if (fInterrupt)
+        {
+            pAhciPort->regIS |= AHCI_PORT_IS_SDBS;
+            /* Check if we should assert an interrupt */
+            if (pAhciPort->regIE & AHCI_PORT_IE_SDBE)
+                fAssertIntr = true;
+        }
+
+        /* Update registers. */
+        pAhciPort->regTFD = (pAhciPortTaskState->uATARegError << 8) | pAhciPortTaskState->uATARegStatus;
+
+        ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, uFinishedTasks);
+
+        if (fAssertIntr)
+            ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN);
+    }
+}
+
+static uint32_t ahciGetNSectors(uint8_t *pCmdFis, bool fLBA48)
+{
+    /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+    if (fLBA48)
+    {
+        if (!pCmdFis[AHCI_CMDFIS_SECTC] && !pCmdFis[AHCI_CMDFIS_SECTCEXP])
+            return 65536;
+        else
+            return pCmdFis[AHCI_CMDFIS_SECTCEXP] << 8 | pCmdFis[AHCI_CMDFIS_SECTC];
+    }
+    else
+    {
+        if (!pCmdFis[AHCI_CMDFIS_SECTC])
+            return 256;
+        else
+            return pCmdFis[AHCI_CMDFIS_SECTC];
+    }
+}
+
+static uint64_t ahciGetSector(PAHCIPort pAhciPort, uint8_t *pCmdFis, bool fLBA48)
+{
+    uint64_t iLBA;
+    if (pCmdFis[AHCI_CMDFIS_HEAD] & 0x40)
+    {
+        /* any LBA variant */
+        if (fLBA48)
+        {
+            /* LBA48 */
+            iLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) |
+                ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) |
+                ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) |
+                ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+                ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) |
+                pCmdFis[AHCI_CMDFIS_SECTN];
+        }
+        else
+        {
+            /* LBA */
+            iLBA = ((pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) << 24) | (pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+                (pCmdFis[AHCI_CMDFIS_CYLL] << 8) | pCmdFis[AHCI_CMDFIS_SECTN];
+        }
+    }
+    else
+    {
+        /* CHS */
+        iLBA = ((pCmdFis[AHCI_CMDFIS_CYLH] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors +
+            (pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) * pAhciPort->PCHSGeometry.cSectors +
+            (pCmdFis[AHCI_CMDFIS_SECTN] - 1);
+    }
+    return iLBA;
+}
+
+static uint64_t ahciGetSectorQueued(uint8_t *pCmdFis)
+{
+    uint64_t uLBA;
+
+    uLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) |
+           ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) |
+           ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) |
+           ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+           ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) |
+           pCmdFis[AHCI_CMDFIS_SECTN];
+
+    return uLBA;
+}
+
+DECLINLINE(uint32_t) ahciGetNSectorsQueued(uint8_t *pCmdFis)
+{
+    if (!pCmdFis[AHCI_CMDFIS_FETEXP] && !pCmdFis[AHCI_CMDFIS_FET])
+        return 65536;
+    else
+        return pCmdFis[AHCI_CMDFIS_FETEXP] << 8 | pCmdFis[AHCI_CMDFIS_FET];
+}
+
+DECLINLINE(uint8_t) ahciGetTagQueued(uint8_t *pCmdFis)
+{
+    return pCmdFis[AHCI_CMDFIS_SECTC] >> 3;
+}
+
+static int ahciScatterGatherListAllocate(PAHCIPORTTASKSTATE pAhciPortTaskState, uint32_t cSGList, uint32_t cbUnaligned)
+{
+    if (pAhciPortTaskState->cSGListSize < cSGList)
+    {
+        /* The entries are not allocated yet or the number is too small. */
+        if (pAhciPortTaskState->cSGListSize)
+        {
+            RTMemFree(pAhciPortTaskState->pSGListHead);
+            RTMemFree(pAhciPortTaskState->paSGEntries);
+        }
+
+        /* Allocate R3 scatter gather list. */
+        pAhciPortTaskState->pSGListHead = (PPDMDATASEG)RTMemAllocZ(cSGList * sizeof(PDMDATASEG));
+        if (!pAhciPortTaskState->pSGListHead)
+            return VERR_NO_MEMORY;
+
+        pAhciPortTaskState->paSGEntries = (PAHCIPORTTASKSTATESGENTRY)RTMemAllocZ(cSGList * sizeof(AHCIPORTTASKSTATESGENTRY));
+        if (!pAhciPortTaskState->paSGEntries)
+            return VERR_NO_MEMORY;
+
+        /* Reset usage statistics. */
+        pAhciPortTaskState->cSGListSize = cSGList;
+        pAhciPortTaskState->cSGListTooBig = 0;
+    }
+    else if (pAhciPortTaskState->cSGListSize > cSGList)
+    {
+        /*
+         * The list is too big. Increment counter.
+         * So that the destroying function can free
+         * the list if it is too big too many times
+         * in a row.
+         */
+        pAhciPortTaskState->cSGListTooBig++;
+    }
+    else
+    {
+        /*
+         * Needed entries matches current size.
+         * Reset counter.
+         */
+        pAhciPortTaskState->cSGListTooBig = 0;
+    }
+
+    pAhciPortTaskState->cSGEntries = cSGList;
+
+    if (pAhciPortTaskState->cbBufferUnaligned < cbUnaligned)
+    {
+        if (pAhciPortTaskState->pvBufferUnaligned)
+            RTMemFree(pAhciPortTaskState->pvBufferUnaligned);
+
+        Log(("%s: Allocating buffer for unaligned segments cbUnaligned=%u\n", __FUNCTION__, cbUnaligned));
+
+        pAhciPortTaskState->pvBufferUnaligned = RTMemAllocZ(cbUnaligned);
+        if (!pAhciPortTaskState->pvBufferUnaligned)
+            return VERR_NO_MEMORY;
+
+        pAhciPortTaskState->cbBufferUnaligned = cbUnaligned;
+    }
+
+    /* Make debugging easier. */
+#ifdef DEBUG
+    memset(pAhciPortTaskState->pSGListHead, 0, pAhciPortTaskState->cSGListSize * sizeof(PDMDATASEG));
+    memset(pAhciPortTaskState->paSGEntries, 0, pAhciPortTaskState->cSGListSize * sizeof(AHCIPORTTASKSTATESGENTRY));
+    if (pAhciPortTaskState->pvBufferUnaligned)
+        memset(pAhciPortTaskState->pvBufferUnaligned, 0, pAhciPortTaskState->cbBufferUnaligned);
+#endif
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Create scatter gather list descriptors.
+ *
+ * @returns VBox status code.
+ * @param   pAhciPort              The ahci port.
+ * @param   pAhciPortTaskState     The task state which contains the S/G list entries.
+ * @param   fReadonly              If the mappings should be readonly.
+ * @thread  EMT
+ */
+static int ahciScatterGatherListCreate(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fReadonly)
+{
+    int        rc = VINF_SUCCESS;
+    CmdHdr    *pCmdHdr = &pAhciPortTaskState->cmdHdr;
+    PPDMDEVINS pDevIns = pAhciPort->CTX_SUFF(pDevIns);
+    unsigned   cActualSGEntry;
+    unsigned   cSGEntriesR3   = 0;             /* Needed scatter gather list entries in R3. */
+    SGLEntry   aSGLEntry[32];                  /* Holds read sg entries from guest. Biggest seen number of entries a guest set up. */
+    unsigned   cSGLEntriesGCRead;
+    unsigned   cSGLEntriesGCLeft;              /* Available scatter gather list entries in GC */
+    RTGCPHYS   GCPhysAddrPRDTLEntryStart;      /* Start address to read the entries from. */
+    uint32_t   cbSegment;                      /* Size of the current segments in bytes. */
+    bool       fUnaligned;                     /* Flag whether the current buffer is unaligned. */
+    uint32_t   cbUnaligned;                    /* Size of the unaligned buffers. */
+    uint32_t   cUnaligned;
+    bool       fDoMapping = false;
+    RTGCPHYS   GCPhysAddrPRDTLUnalignedStart = NIL_RTGCPHYS;
+    PAHCIPORTTASKSTATESGENTRY  pSGInfoCurr  = NULL;
+    PAHCIPORTTASKSTATESGENTRY  pSGInfoPrev  = NULL;
+    PPDMDATASEG                pSGEntryCurr = NULL;
+    PPDMDATASEG                pSGEntryPrev = NULL;
+    RTGCPHYS                   GCPhysBufferPageAlignedPrev = NIL_RTGCPHYS;
+    uint8_t                   *pu8BufferUnalignedPos = NULL;
+    uint32_t                   cbUnalignedComplete = 0;
+
+    STAM_PROFILE_START(&pAhciPort->StatProfileMapIntoR3, a);
+
+    /*
+     * We need to calculate the number of SG list entries in R3 first because the data buffers in the guest don't need to be
+     * page aligned. Hence the number of SG list entries in the guest can differ from the ones we need
+     * because PDMDevHlpPhysGCPhys2CCPtr works only on a page base.
+     * In the first pass we calculate the number of segments in R3 and in the second pass we map the guest segments into R3.
+     */
+    for (int i = 0; i < 2; i++)
+    {
+        cSGLEntriesGCLeft = AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf);
+        ahciLog(("%s: cSGEntriesGC=%u\n", __FUNCTION__, cSGLEntriesGCLeft));
+
+        /* Set start address of the entries. */
+        GCPhysAddrPRDTLEntryStart = AHCI_RTGCPHYS_FROM_U32(pCmdHdr->u32CmdTblAddrUp, pCmdHdr->u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
+        fUnaligned  = false;
+        cbUnaligned = 0;
+        cUnaligned  = 0;
+        GCPhysBufferPageAlignedPrev = NIL_RTGCPHYS;
+
+        if (fDoMapping)
+        {
+            ahciLog(("%s: cSGEntriesR3=%u\n", __FUNCTION__, cSGEntriesR3));
+            /* The number of needed SG entries in R3 is known. Allocate needed memory. */
+            rc = ahciScatterGatherListAllocate(pAhciPortTaskState, cSGEntriesR3, cbUnalignedComplete);
+            AssertMsgRC(rc, ("Failed to allocate scatter gather array rc=%Rrc\n", rc));
+
+            /* We are now able to map the pages into R3. */
+            pSGInfoCurr  = pAhciPortTaskState->paSGEntries;
+            pSGEntryCurr = pAhciPortTaskState->pSGListHead;
+            pSGEntryPrev = pSGEntryCurr;
+            pSGInfoPrev  = pSGInfoCurr;
+            /* Initialize first segment to remove the need for additional if checks later in the code. */
+            pSGEntryCurr->pvSeg = NULL;
+            pSGEntryCurr->cbSeg = 0;
+            pSGInfoCurr->fGuestMemory= false;
+            pu8BufferUnalignedPos = (uint8_t *)pAhciPortTaskState->pvBufferUnaligned;
+        }
+
+        do
+        {
+            cSGLEntriesGCRead = (cSGLEntriesGCLeft < RT_ELEMENTS(aSGLEntry)) ? cSGLEntriesGCLeft : RT_ELEMENTS(aSGLEntry);
+            cSGLEntriesGCLeft -= cSGLEntriesGCRead;
+
+            /* Read the SG entries. */
+            PDMDevHlpPhysRead(pDevIns, GCPhysAddrPRDTLEntryStart, &aSGLEntry[0], cSGLEntriesGCRead * sizeof(SGLEntry));
+
+            for (cActualSGEntry = 0; cActualSGEntry < cSGLEntriesGCRead; cActualSGEntry++)
+            {
+                RTGCPHYS    GCPhysAddrDataBase;
+                uint32_t    cbDataToTransfer;
+
+                ahciLog(("%s: cActualSGEntry=%u cSGEntriesR3=%u\n", __FUNCTION__, cActualSGEntry, cSGEntriesR3));
+
+                cbDataToTransfer = (aSGLEntry[cActualSGEntry].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+                ahciLog(("%s: cbDataToTransfer=%u\n", __FUNCTION__, cbDataToTransfer));
+
+                /* Check if the buffer is sector aligned. */
+                if (cbDataToTransfer % 512 != 0)
+                {
+                    if (!fUnaligned)
+                    {
+                        /* We are not in an unaligned buffer but this is the first unaligned one. */
+                        fUnaligned  = true;
+                        cbUnaligned = cbDataToTransfer;
+                        GCPhysAddrPRDTLUnalignedStart = GCPhysAddrPRDTLEntryStart + cActualSGEntry * sizeof(SGLEntry);
+                        cSGEntriesR3++;
+                        cUnaligned = 1;
+                        ahciLog(("%s: Unaligned buffer found cb=%d\n", __FUNCTION__, cbDataToTransfer));
+                    }
+                    else
+                    {
+                        /* We are already in an unaligned buffer and this one is unaligned too. */
+                        cbUnaligned += cbDataToTransfer;
+                        cUnaligned++;
+                    }
+
+                    cbUnalignedComplete += cbDataToTransfer;
+                }
+                else /* Guest segment size is sector aligned. */
+                {
+                    if (fUnaligned)
+                    {
+                        if (cbUnaligned % 512 == 0)
+                        {
+                            /*
+                             * The last buffer started at an offset
+                             * not aligned to a sector boundary but this buffer
+                             * is sector aligned. Check if the current size of all
+                             * unaligned segments is a multiple of a sector.
+                             * If that's the case we can now map the segments again into R3.
+                             */
+                            fUnaligned = false;
+
+                            if (fDoMapping)
+                            {
+                                /* Set up the entry. */
+                                pSGInfoCurr->fGuestMemory = false;
+                                pSGInfoCurr->u.temp.GCPhysAddrBaseFirstUnaligned = GCPhysAddrPRDTLUnalignedStart;
+                                pSGInfoCurr->u.temp.cUnaligned = cUnaligned;
+                                pSGInfoCurr->u.temp.pvBuf      = pu8BufferUnalignedPos;
+
+                                pSGEntryCurr->pvSeg    = pu8BufferUnalignedPos;
+                                pSGEntryCurr->cbSeg    = cbUnaligned;
+                                pu8BufferUnalignedPos += cbUnaligned;
+
+                                /*
+                                 * If the transfer is to the device we need to copy the content of the not mapped guest
+                                 * segments into the temporary buffer.
+                                 */
+                                if (pAhciPortTaskState->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+                                    ahciCopyFromSGListIntoBuffer(pDevIns, pSGInfoCurr);
+
+                                /* Advance to next entry saving the pointers to the current ones. */
+                                pSGEntryPrev = pSGEntryCurr;
+                                pSGInfoPrev  = pSGInfoCurr;
+                                pSGInfoCurr++;
+                                pSGEntryCurr++;
+                            }
+                        }
+                        else
+                        {
+                            cbUnaligned         += cbDataToTransfer;
+                            cbUnalignedComplete += cbDataToTransfer;
+                            cUnaligned++;
+                        }
+                    }
+                    else
+                    {
+                        /*
+                         * The size of the guest segment is sector aligned but it is possible that the segment crosses
+                         * a page boundary in a way splitting the segment into parts which are not sector aligned.
+                         * We have to treat them like unaligned guest segments then.
+                         */
+                        GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntry[cActualSGEntry].u32DBAUp, aSGLEntry[cActualSGEntry].u32DBA);
+
+                        ahciLog(("%s: GCPhysAddrDataBase=%RGp\n", __FUNCTION__, GCPhysAddrDataBase));
+
+                        /*
+                         * Check if the physical address is page aligned.
+                         */
+                        if (GCPhysAddrDataBase & PAGE_OFFSET_MASK)
+                        {
+                            RTGCPHYS    GCPhysAddrDataNextPage = PHYS_PAGE_ADDRESS(GCPhysAddrDataBase) + PAGE_SIZE;
+                            /* Difference from the buffer start to the next page boundary. */
+                            uint32_t    u32GCPhysAddrDiff = GCPhysAddrDataNextPage - GCPhysAddrDataBase;
+
+                            if (u32GCPhysAddrDiff % 512 != 0)
+                            {
+                                if (!fUnaligned)
+                                {
+                                    /* We are not in an unaligned buffer but this is the first unaligned one. */
+                                    fUnaligned  = true;
+                                    cbUnaligned = cbDataToTransfer;
+                                    GCPhysAddrPRDTLUnalignedStart = GCPhysAddrPRDTLEntryStart + cActualSGEntry * sizeof(SGLEntry);
+                                    cSGEntriesR3++;
+                                    cUnaligned = 1;
+                                    ahciLog(("%s: Guest segment is sector aligned but crosses a page boundary cb=%d\n", __FUNCTION__, cbDataToTransfer));
+                                }
+                                else
+                                {
+                                    /* We are already in an unaligned buffer and this one is unaligned too. */
+                                    cbUnaligned += cbDataToTransfer;
+                                    cUnaligned++;
+                                }
+
+                                cbUnalignedComplete += cbDataToTransfer;
+                            }
+                            else
+                            {
+                                ahciLog(("%s: Align page: GCPhysAddrDataBase=%RGp GCPhysAddrDataNextPage=%RGp\n",
+                                         __FUNCTION__, GCPhysAddrDataBase, GCPhysAddrDataNextPage));
+
+                                RTGCPHYS GCPhysBufferPageAligned = PHYS_PAGE_ADDRESS(GCPhysAddrDataBase);
+
+                                /* Check if the mapping ends at the page boundary and set segment size accordingly. */
+                                cbSegment =   (cbDataToTransfer < u32GCPhysAddrDiff)
+                                            ? cbDataToTransfer
+                                            : u32GCPhysAddrDiff;
+                                /* Subtract size of the buffer in the actual page. */
+                                cbDataToTransfer -= cbSegment;
+
+                                if (GCPhysBufferPageAlignedPrev != GCPhysBufferPageAligned)
+                                {
+                                    /* We don't need to map the buffer if it is in the same page as the previous one. */
+                                    if (fDoMapping)
+                                    {
+                                        uint8_t *pbMapping;
+
+                                        pSGInfoCurr->fGuestMemory = true;
+
+                                        /* Create the mapping. */
+                                        if (fReadonly)
+                                            rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, GCPhysBufferPageAligned,
+                                                                                   0, (const void **)&pbMapping,
+                                                                                   &pSGInfoCurr->u.direct.PageLock);
+                                        else
+                                            rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysBufferPageAligned,
+                                                                           0, (void **)&pbMapping,
+                                                                           &pSGInfoCurr->u.direct.PageLock);
+
+                                        if (RT_FAILURE(rc))
+                                            AssertMsgFailed(("Creating mapping failed rc=%Rrc\n", rc));
+
+                                        if ((pbMapping + (GCPhysAddrDataBase - GCPhysBufferPageAligned) == ((uint8_t *)pSGEntryPrev->pvSeg + pSGEntryCurr->cbSeg)))
+                                        {
+                                            pSGEntryPrev->cbSeg += cbSegment;
+                                            ahciLog(("%s: Merged mapping pbMapping=%#p into current segment pvSeg=%#p. New size is cbSeg=%d\n",
+                                                     __FUNCTION__, pbMapping, pSGEntryPrev->pvSeg, pSGEntryPrev->cbSeg));
+                                        }
+                                        else
+                                        {
+                                            pSGEntryCurr->cbSeg = cbSegment;
+
+                                            /* Let pvBuf point to the start of the buffer in the page. */
+                                            pSGEntryCurr->pvSeg =   pbMapping
+                                                                  + (GCPhysAddrDataBase - GCPhysBufferPageAligned);
+
+                                            ahciLog(("%s: pvSegBegin=%#p pvSegEnd=%#p\n", __FUNCTION__,
+                                                     pSGEntryCurr->pvSeg,
+                                                     (uint8_t *)pSGEntryCurr->pvSeg + pSGEntryCurr->cbSeg));
+
+                                            pSGEntryPrev = pSGEntryCurr;
+                                            pSGEntryCurr++;
+                                        }
+
+                                        pSGInfoPrev  = pSGInfoCurr;
+                                        pSGInfoCurr++;
+                                    }
+                                    else
+                                        cSGEntriesR3++;
+                                }
+                                else if (fDoMapping)
+                                {
+                                    pSGEntryPrev->cbSeg += cbSegment;
+                                    ahciLog(("%s: Buffer is already in previous mapping pvSeg=%#p. New size is cbSeg=%d\n",
+                                                __FUNCTION__, pSGEntryPrev->pvSeg, pSGEntryPrev->cbSeg));
+                                }
+
+                                /* Let physical address point to the next page in the buffer. */
+                                GCPhysAddrDataBase = GCPhysAddrDataNextPage;
+                                GCPhysBufferPageAlignedPrev = GCPhysBufferPageAligned;
+                            }
+                        }
+
+                        if (!fUnaligned)
+                        {
+                            /* The address is now page aligned. */
+                            while (cbDataToTransfer)
+                            {
+                                ahciLog(("%s: GCPhysAddrDataBase=%RGp cbDataToTransfer=%u cSGEntriesR3=%u\n",
+                                         __FUNCTION__, GCPhysAddrDataBase, cbDataToTransfer, cSGEntriesR3));
+
+                                /* Check if this is the last page the buffer is in. */
+                                cbSegment = (cbDataToTransfer < PAGE_SIZE) ? cbDataToTransfer : PAGE_SIZE;
+                                cbDataToTransfer -= cbSegment;
+
+                                if (fDoMapping)
+                                {
+                                    void *pvMapping;
+
+                                    pSGInfoCurr->fGuestMemory = true;
+
+                                    /* Create the mapping. */
+                                    if (fReadonly)
+                                        rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, GCPhysAddrDataBase, 0, (const void **)&pvMapping, &pSGInfoCurr->u.direct.PageLock);
+                                    else
+                                        rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrDataBase, 0, &pvMapping, &pSGInfoCurr->u.direct.PageLock);
+
+                                    if (RT_FAILURE(rc))
+                                        AssertMsgFailed(("Creating mapping failed rc=%Rrc\n", rc));
+
+                                    /* Check for adjacent mappings. */
+                                    if (pvMapping == ((uint8_t *)pSGEntryPrev->pvSeg + pSGEntryPrev->cbSeg)
+                                        && (pSGInfoPrev->fGuestMemory == true))
+                                    {
+                                        /* Yes they are adjacent. Just add the size of this mapping to the previous segment. */
+                                        pSGEntryPrev->cbSeg += cbSegment;
+                                        ahciLog(("%s: Merged mapping pvMapping=%#p into current segment pvSeg=%#p. New size is cbSeg=%d\n",
+                                                 __FUNCTION__, pvMapping, pSGEntryPrev->pvSeg, pSGEntryPrev->cbSeg));
+                                    }
+                                    else
+                                    {
+                                        /* No they are not. Use a new sg entry. */
+                                        pSGEntryCurr->cbSeg       = cbSegment;
+                                        pSGEntryCurr->pvSeg       = pvMapping;
+                                        ahciLog(("%s: pvSegBegin=%#p pvSegEnd=%#p\n", __FUNCTION__,
+                                                 pSGEntryCurr->pvSeg,
+                                                 (uint8_t *)pSGEntryCurr->pvSeg + pSGEntryCurr->cbSeg));
+                                        pSGEntryPrev = pSGEntryCurr;
+                                        pSGEntryCurr++;
+                                    }
+
+                                    pSGInfoPrev = pSGInfoCurr;
+                                    pSGInfoCurr++;
+                                }
+                                else
+                                    cSGEntriesR3++;
+
+                                GCPhysBufferPageAlignedPrev = GCPhysAddrDataBase;
+
+                                /* Go to the next page. */
+                                GCPhysAddrDataBase += PAGE_SIZE;
+                            }
+                        } /* if (!fUnaligned) */
+                    } /* if !fUnaligned */
+                } /* if guest segment is sector aligned. */
+            } /* for SGEntries read */
+
+            /* Set address to the next entries to read. */
+            GCPhysAddrPRDTLEntryStart += cSGLEntriesGCRead * sizeof(SGLEntry);
+
+        } while (cSGLEntriesGCLeft);
+
+        fDoMapping = true;
+
+    } /* for passes */
+
+    /* Check if the last processed segment was unaligned. We need to add it now. */
+    if (fUnaligned)
+    {
+        /* Set up the entry. */
+        AssertMsg(!(cbUnaligned % 512), ("Buffer is not sector aligned\n"));
+        pSGInfoCurr->fGuestMemory = false;
+        pSGInfoCurr->u.temp.GCPhysAddrBaseFirstUnaligned = GCPhysAddrPRDTLUnalignedStart;
+        pSGInfoCurr->u.temp.cUnaligned = cUnaligned;
+        pSGInfoCurr->u.temp.pvBuf = pu8BufferUnalignedPos;
+
+        pSGEntryCurr->pvSeg    = pu8BufferUnalignedPos;
+        pSGEntryCurr->cbSeg    = cbUnaligned;
+
+        /*
+         * If the transfer is to the device we need to copy the content of the not mapped guest
+         * segments into the temporary buffer.
+         */
+        if (pAhciPortTaskState->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+            ahciCopyFromSGListIntoBuffer(pDevIns, pSGInfoCurr);
+    }
+
+    STAM_PROFILE_STOP(&pAhciPort->StatProfileMapIntoR3, a);
+
+    return rc;
+}
+
+/**
+ * Destroy a scatter gather list and free all occupied resources (mappings, etc.)
+ *
+ * @returns VBox status code.
+ * @param   pAhciPort   The ahci port.
+ * @param   pAhciPortTaskState    The task state which contains the S/G list entries.
+ */
+static int ahciScatterGatherListDestroy(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    PAHCIPORTTASKSTATESGENTRY  pSGInfoCurr  = pAhciPortTaskState->paSGEntries;
+    PPDMDEVINS                 pDevIns      = pAhciPort->CTX_SUFF(pDevIns);
+
+    STAM_PROFILE_START(&pAhciPort->StatProfileDestroyScatterGatherList, a);
+
+    for (unsigned cActualSGEntry = 0; cActualSGEntry < pAhciPortTaskState->cSGEntries; cActualSGEntry++)
+    {
+        if (pSGInfoCurr->fGuestMemory)
+        {
+            /* Release the lock. */
+            PDMDevHlpPhysReleasePageMappingLock(pDevIns, &pSGInfoCurr->u.direct.PageLock);
+        }
+        else if (pAhciPortTaskState->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+        {
+            /* Copy the data into the guest segments now. */
+            ahciCopyFromBufferIntoSGList(pDevIns, pSGInfoCurr);
+        }
+
+        /* Go to the next entry. */
+        pSGInfoCurr++;
+    }
+
+    /* Free allocated memory if the list was too big too many times. */
+    if (pAhciPortTaskState->cSGListTooBig >= AHCI_NR_OF_ALLOWED_BIGGER_LISTS)
+    {
+        RTMemFree(pAhciPortTaskState->pSGListHead);
+        RTMemFree(pAhciPortTaskState->paSGEntries);
+        if (pAhciPortTaskState->pvBufferUnaligned)
+            RTMemFree(pAhciPortTaskState->pvBufferUnaligned);
+        pAhciPortTaskState->cSGListSize = 0;
+        pAhciPortTaskState->cSGListTooBig = 0;
+        pAhciPortTaskState->pSGListHead = NULL;
+        pAhciPortTaskState->paSGEntries = NULL;
+        pAhciPortTaskState->pvBufferUnaligned = NULL;
+        pAhciPortTaskState->cbBufferUnaligned = 0;
+    }
+
+    STAM_PROFILE_STOP(&pAhciPort->StatProfileDestroyScatterGatherList, a);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Copy a temporary buffer into a part of the guest scatter gather list
+ * described by the given descriptor entry.
+ *
+ * @returns nothing.
+ * @param   pDevIns    Pointer to the device instance data.
+ * @param   pSGInfo    Pointer to the segment info structure which describes the guest segments
+ *                     to write to which are unaligned.
+ */
+static void ahciCopyFromBufferIntoSGList(PPDMDEVINS pDevIns, PAHCIPORTTASKSTATESGENTRY pSGInfo)
+{
+    uint8_t *pu8Buf    = (uint8_t *)pSGInfo->u.temp.pvBuf;
+    SGLEntry aSGLEntries[5];
+    uint32_t cSGEntriesLeft = pSGInfo->u.temp.cUnaligned;
+    RTGCPHYS GCPhysPRDTLStart = pSGInfo->u.temp.GCPhysAddrBaseFirstUnaligned;
+
+    AssertMsg(!pSGInfo->fGuestMemory, ("This is not possible\n"));
+
+    do
+    {
+        uint32_t cSGEntriesRead =   (cSGEntriesLeft < RT_ELEMENTS(aSGLEntries))
+                                  ? cSGEntriesLeft
+                                  : RT_ELEMENTS(aSGLEntries);
+
+        PDMDevHlpPhysRead(pDevIns, GCPhysPRDTLStart, &aSGLEntries[0], cSGEntriesRead * sizeof(SGLEntry));
+
+        for (uint32_t i = 0; i < cSGEntriesRead; i++)
+        {
+            RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntries[i].u32DBAUp, aSGLEntries[i].u32DBA);
+            uint32_t cbCopied = (aSGLEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+            /* Copy into SG entry. */
+            PDMDevHlpPhysWrite(pDevIns, GCPhysAddrDataBase, pu8Buf, cbCopied);
+
+            pu8Buf    += cbCopied;
+        }
+
+        GCPhysPRDTLStart += cSGEntriesRead * sizeof(SGLEntry);
+        cSGEntriesLeft   -= cSGEntriesRead;
+    } while (cSGEntriesLeft);
+}
+
+/**
+ * Copy a part of the guest scatter gather list into a temporary buffer.
+ *
+ * @returns nothing.
+ * @param   pDevIns    Pointer to the device instance data.
+ * @param   pSGInfo    Pointer to the segment info structure which describes the guest segments
+ *                     to read from which are unaligned.
+ */
+static void ahciCopyFromSGListIntoBuffer(PPDMDEVINS pDevIns, PAHCIPORTTASKSTATESGENTRY pSGInfo)
+{
+    uint8_t *pu8Buf    = (uint8_t *)pSGInfo->u.temp.pvBuf;
+    SGLEntry aSGLEntries[5];
+    uint32_t cSGEntriesLeft = pSGInfo->u.temp.cUnaligned;
+    RTGCPHYS GCPhysPRDTLStart = pSGInfo->u.temp.GCPhysAddrBaseFirstUnaligned;
+
+    AssertMsg(!pSGInfo->fGuestMemory, ("This is not possible\n"));
+
+    do
+    {
+        uint32_t cSGEntriesRead =   (cSGEntriesLeft < RT_ELEMENTS(aSGLEntries))
+                                  ? cSGEntriesLeft
+                                  : RT_ELEMENTS(aSGLEntries);
+
+        PDMDevHlpPhysRead(pDevIns, GCPhysPRDTLStart, &aSGLEntries[0], cSGEntriesRead * sizeof(SGLEntry));
+
+        for (uint32_t i = 0; i < cSGEntriesRead; i++)
+        {
+            RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntries[i].u32DBAUp, aSGLEntries[i].u32DBA);
+            uint32_t cbCopied = (aSGLEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+            /* Copy into buffer. */
+            PDMDevHlpPhysRead(pDevIns, GCPhysAddrDataBase, pu8Buf, cbCopied);
+
+            pu8Buf    += cbCopied;
+        }
+
+        GCPhysPRDTLStart += cSGEntriesRead * sizeof(SGLEntry);
+        cSGEntriesLeft   -= cSGEntriesRead;
+    } while (cSGEntriesLeft);
+}
+
+
+/**
+ * Copy the content of a buffer to a scatter gather list.
+ *
+ * @returns VBox status code.
+ * @param   pAhciPortTaskState    The task state which contains the S/G list entries.
+ * @param   pvBuf                 Pointer to the buffer which should be copied.
+ * @param   cbBuf                 Size of the buffer.
+ */
+static int ahciScatterGatherListCopyFromBuffer(PAHCIPORTTASKSTATE pAhciPortTaskState, void *pvBuf, size_t cbBuf)
+{
+    unsigned cSGEntry = 0;
+    PPDMDATASEG pSGEntry = &pAhciPortTaskState->pSGListHead[cSGEntry];
+    uint8_t *pu8Buf = (uint8_t *)pvBuf;
+
+    while (cSGEntry < pAhciPortTaskState->cSGEntries)
+    {
+        size_t cbToCopy = (cbBuf < pSGEntry->cbSeg) ? cbBuf : pSGEntry->cbSeg;
+
+        memcpy(pSGEntry->pvSeg, pu8Buf, cbToCopy);
+
+        cbBuf -= cbToCopy;
+        /* We finished. */
+        if (!cbBuf)
+            break;
+
+        /* Advance the buffer. */
+        pu8Buf += cbToCopy;
+
+        /* Go to the next entry in the list. */
+        pSGEntry++;
+        cSGEntry++;
+    }
+
+#if 0
+    if (!pAhciPort->fATAPI)
+        AssertMsg(!cbBuf, ("There is still data in the buffer\n"));
+#endif
+    return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBlockAsyncPort -=-=-=-=- */
+
+/** Makes a PAHCIPort out of a PPDMIBLOCKASYNCPORT. */
+#define PDMIBLOCKASYNCPORT_2_PAHCIPORT(pInterface)    ( (PAHCIPort)((uintptr_t)pInterface - RT_OFFSETOF(AHCIPort, IPortAsync)) )
+
+/**
+ * Complete a data transfer task by freeing all occupied ressources
+ * and notifying the guest.
+ *
+ * @returns VBox status code
+ *
+ * @param pAhciPort             Pointer to the port where to request completed.
+ * @param pAhciPortTaskState    Pointer to the task which finished.
+ */
+static int ahciTransferComplete(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    /* Free system resources occupied by the scatter gather list. */
+    ahciScatterGatherListDestroy(pAhciPort, pAhciPortTaskState);
+
+    pAhciPortTaskState->cmdHdr.u32PRDBC = pAhciPortTaskState->cbTransfer;
+
+    pAhciPortTaskState->uATARegError = 0;
+    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+    /* Write updated command header into memory of the guest. */
+    PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr,
+                       &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
+
+    if (pAhciPortTaskState->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+    {
+        STAM_REL_COUNTER_ADD(&pAhciPort->StatBytesRead, pAhciPortTaskState->cbTransfer);
+        pAhciPort->Led.Actual.s.fReading = 0;
+    }
+    else
+    {
+        STAM_REL_COUNTER_ADD(&pAhciPort->StatBytesWritten, pAhciPortTaskState->cbTransfer);
+        pAhciPort->Led.Actual.s.fWriting = 0;
+    }
+
+    if (pAhciPortTaskState->fQueued)
+    {
+        uint32_t cOutstandingTasks;
+
+        ahciLog(("%s: Before decrement uActTasksActive=%u\n", __FUNCTION__, pAhciPort->uActTasksActive));
+        cOutstandingTasks = ASMAtomicDecU32(&pAhciPort->uActTasksActive);
+        ahciLog(("%s: After decrement uActTasksActive=%u\n", __FUNCTION__, cOutstandingTasks));
+        ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, (1 << pAhciPortTaskState->uTag));
+
+        if (!cOutstandingTasks)
+            ahciSendSDBFis(pAhciPort, pAhciPort->u32QueuedTasksFinished, pAhciPortTaskState, true);
+    }
+    else
+        ahciSendD2HFis(pAhciPort, pAhciPortTaskState, pAhciPortTaskState->cmdFis, true);
+
+    /* Add the task to the cache. */
+    pAhciPort->aCachedTasks[pAhciPortTaskState->uTag] = pAhciPortTaskState;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Notification callback for a completed transfer.
+ *
+ * @returns VBox status code.
+ * @param   pInterface   Pointer to the interface.
+ * @param   pvUser       User data.
+ */
+static DECLCALLBACK(int) ahciTransferCompleteNotify(PPDMIBLOCKASYNCPORT pInterface, void *pvUser)
+{
+    PAHCIPort pAhciPort = PDMIBLOCKASYNCPORT_2_PAHCIPORT(pInterface);
+    PAHCIPORTTASKSTATE pAhciPortTaskState = (PAHCIPORTTASKSTATE)pvUser;
+
+    ahciLog(("%s: pInterface=%p pvUser=%p uTag=%u\n",
+             __FUNCTION__, pInterface, pvUser, pAhciPortTaskState->uTag));
+
+    return ahciTransferComplete(pAhciPort, pAhciPortTaskState);
+}
+
+/**
+ * Process an non read/write ATA command.
+ *
+ * @returns The direction of the data transfer
+ * @param   pCmdHdr Pointer to the command header.
+ */
+static int ahciProcessCmd(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t *pCmdFis)
+{
+    int rc = PDMBLOCKTXDIR_NONE;
+    bool fLBA48 = false;
+    CmdHdr   *pCmdHdr = &pAhciPortTaskState->cmdHdr;
+
+    AssertMsg(pCmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, ("FIS is not a host to device Fis!!\n"));
+
+    switch (pCmdFis[AHCI_CMDFIS_CMD])
+    {
+        case ATA_IDENTIFY_DEVICE:
+            {
+                if (pAhciPort->pDrvBlock && !pAhciPort->fATAPI)
+                {
+                    uint16_t u16Temp[256];
+
+                    /* Fill the buffer. */
+                    ahciIdentifySS(pAhciPort, u16Temp);
+
+                    /* Create scatter gather list. */
+                    rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, false);
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Creating list failed rc=%Rrc\n", rc));
+
+                    /* Copy the buffer. */
+                    rc = ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, &u16Temp[0], sizeof(u16Temp));
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Copying failed rc=%Rrc\n", rc));
+
+                    /* Destroy list. */
+                    rc = ahciScatterGatherListDestroy(pAhciPort, pAhciPortTaskState);
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Freeing list failed rc=%Rrc\n", rc));
+
+                    pAhciPortTaskState->uATARegError = 0;
+                    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+                    pCmdHdr->u32PRDBC = sizeof(u16Temp);
+
+                    /* Write updated command header into memory of the guest. */
+                    PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, pCmdHdr, sizeof(CmdHdr));
+                }
+                else
+                {
+                    pAhciPortTaskState->uATARegError = ABRT_ERR;
+                    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+                }
+                break;
+            }
+        case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+        case ATA_READ_NATIVE_MAX_ADDRESS:
+                break;
+        case ATA_SET_FEATURES:
+        {
+            switch (pCmdFis[AHCI_CMDFIS_FET])
+            {
+                case 0x02: /* write cache enable */
+                case 0xaa: /* read look-ahead enable */
+                case 0x55: /* read look-ahead disable */
+                case 0xcc: /* reverting to power-on defaults enable */
+                case 0x66: /* reverting to power-on defaults disable */
+                    pAhciPortTaskState->uATARegError = 0;
+                    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+                    break;
+                case 0x82: /* write cache disable */
+                    rc = pAhciPort->pDrvBlock->pfnFlush(pAhciPort->pDrvBlock);
+                    pAhciPortTaskState->uATARegError = 0;
+                    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+                    break;
+                case 0x03:
+                { /* set transfer mode */
+                    Log2(("%s: transfer mode %#04x\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC]));
+                    switch (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8)
+                    {
+                        case 0x00: /* PIO default */
+                        case 0x08: /* PIO mode */
+                            break;
+                        case ATA_MODE_MDMA: /* MDMA mode */
+                            pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_MDMA_MODE_MAX);
+                            break;
+                        case ATA_MODE_UDMA: /* UDMA mode */
+                            pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_UDMA_MODE_MAX);
+                            break;
+                    }
+                    break;
+                }
+                default:
+                    pAhciPortTaskState->uATARegError = ABRT_ERR;
+                    pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+            }
+            break;
+        }
+        case ATA_FLUSH_CACHE_EXT:
+        case ATA_FLUSH_CACHE:
+            rc = pAhciPort->pDrvBlock->pfnFlush(pAhciPort->pDrvBlock);
+            pAhciPortTaskState->uATARegError = 0;
+            pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+            break;
+        case ATA_PACKET:
+            if (!pAhciPort->fATAPI)
+            {
+                pAhciPortTaskState->uATARegError = ABRT_ERR;
+                pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+            }
+            else
+            {
+                rc = atapiParseCmdVirtualATAPI(pAhciPort, pAhciPortTaskState);
+            }
+            break;
+        case ATA_IDENTIFY_PACKET_DEVICE:
+            if (!pAhciPort->fATAPI)
+            {
+                pAhciPortTaskState->uATARegError = ABRT_ERR;
+                pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+            }
+            else
+            {
+                atapiDoTransfer(pAhciPort, pAhciPortTaskState, ATAFN_SS_ATAPI_IDENTIFY);
+
+                pAhciPortTaskState->uATARegError = 0;
+                pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+            }
+            break;
+        case ATA_SET_MULTIPLE_MODE:
+            if (    pCmdFis[AHCI_CMDFIS_SECTC] != 0
+                &&  (   pCmdFis[AHCI_CMDFIS_SECTC] > ATA_MAX_MULT_SECTORS
+                     || (pCmdFis[AHCI_CMDFIS_SECTC] & (pCmdFis[AHCI_CMDFIS_SECTC] - 1)) != 0))
+            {
+                pAhciPortTaskState->uATARegError = ABRT_ERR;
+                pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+            }
+            else
+            {
+                Log2(("%s: set multi sector count to %d\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC]));
+                pAhciPort->cMultSectors = pCmdFis[AHCI_CMDFIS_SECTC];
+                pAhciPortTaskState->uATARegError = 0;
+                pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+            }
+            break;
+        case ATA_STANDBY_IMMEDIATE:
+            break; /* Do nothing. */
+        case ATA_CHECK_POWER_MODE:
+            pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTC] = 0xff; /* drive active or idle */
+            /* fall through */
+        case ATA_INITIALIZE_DEVICE_PARAMETERS:
+        case ATA_IDLE_IMMEDIATE:
+        case ATA_RECALIBRATE:
+        case ATA_NOP:
+        case ATA_READ_VERIFY_SECTORS_EXT:
+        case ATA_READ_VERIFY_SECTORS:
+        case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+            pAhciPortTaskState->uATARegError = 0;
+            pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+            break;
+        case ATA_READ_DMA_EXT:
+            fLBA48 = true;
+        case ATA_READ_DMA:
+        {
+            pAhciPortTaskState->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * 512;
+            pAhciPortTaskState->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * 512;
+            rc = PDMBLOCKTXDIR_FROM_DEVICE;
+            break;
+        }
+        case ATA_WRITE_DMA_EXT:
+            fLBA48 = true;
+        case ATA_WRITE_DMA:
+        {
+            pAhciPortTaskState->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * 512;
+            pAhciPortTaskState->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * 512;
+            rc = PDMBLOCKTXDIR_TO_DEVICE;
+            break;
+        }
+        case ATA_READ_FPDMA_QUEUED:
+        {
+            pAhciPortTaskState->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * 512;
+            pAhciPortTaskState->uOffset = ahciGetSectorQueued(pCmdFis) * 512;
+            rc = PDMBLOCKTXDIR_FROM_DEVICE;
+            break;
+        }
+        case ATA_WRITE_FPDMA_QUEUED:
+        {
+            pAhciPortTaskState->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * 512;
+            pAhciPortTaskState->uOffset = ahciGetSectorQueued(pCmdFis) * 512;
+            rc = PDMBLOCKTXDIR_TO_DEVICE;
+            break;
+        }
+        /* All not implemented commands go below. */
+        case ATA_SECURITY_FREEZE_LOCK:
+        case ATA_SMART:
+        case ATA_NV_CACHE:
+        case ATA_SLEEP: /* Powermanagement not supported. */
+            pAhciPortTaskState->uATARegError = ABRT_ERR;
+            pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+            break;
+        default: /* For debugging purposes. */
+            AssertMsgFailed(("Unknown command issued\n"));
+            pAhciPortTaskState->uATARegError = ABRT_ERR;
+            pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_ERR;
+    }
+
+    return rc;
+}
+
+/**
+ * Retrieve a command FIS from guest memory.
+ *
+ * @returns nothing
+ * @param pAhciPortTaskState The state of the actual task.
+ */
+static void ahciPortTaskGetCommandFis(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
+{
+    RTGCPHYS  GCPhysAddrCmdTbl;
+
+    AssertMsg(pAhciPort->GCPhysAddrClb && pAhciPort->GCPhysAddrFb, ("%s: GCPhysAddrClb and/or GCPhysAddrFb are 0\n", __FUNCTION__));
+
+    /*
+     * First we are reading the command header pointed to by regCLB.
+     * From this we get the address of the command table which we are reading too.
+     * We can process the Command FIS afterwards.
+     */
+    pAhciPortTaskState->GCPhysCmdHdrAddr = pAhciPort->GCPhysAddrClb + pAhciPortTaskState->uTag * sizeof(CmdHdr);
+    LogFlow(("%s: PDMDevHlpPhysRead GCPhysAddrCmdLst=%RGp cbCmdHdr=%u\n", __FUNCTION__,
+             pAhciPortTaskState->GCPhysCmdHdrAddr, sizeof(CmdHdr)));
+    PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
+
+#ifdef DEBUG
+    /* Print some infos about the command header. */
+    ahciDumpCmdHdrInfo(pAhciPort, &pAhciPortTaskState->cmdHdr);
+#endif
+
+    GCPhysAddrCmdTbl = AHCI_RTGCPHYS_FROM_U32(pAhciPortTaskState->cmdHdr.u32CmdTblAddrUp, pAhciPortTaskState->cmdHdr.u32CmdTblAddr);
+
+    AssertMsg((pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_CFL_MASK) * sizeof(uint32_t) == AHCI_CMDFIS_TYPE_H2D_SIZE,
+              ("This is not a command FIS!!\n"));
+
+    /* Read the command Fis. */
+    LogFlow(("%s: PDMDevHlpPhysRead GCPhysAddrCmdTbl=%RGp cbCmdFis=%u\n", __FUNCTION__, GCPhysAddrCmdTbl, AHCI_CMDFIS_TYPE_H2D_SIZE));
+    PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciPortTaskState->cmdFis[0], AHCI_CMDFIS_TYPE_H2D_SIZE);
+
+    /* Set transfer direction. */
+    pAhciPortTaskState->uTxDir = (pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_W) ? PDMBLOCKTXDIR_TO_DEVICE : PDMBLOCKTXDIR_FROM_DEVICE;
+
+    /* If this is an ATAPI command read the atapi command. */
+    if (pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_A)
+    {
+        GCPhysAddrCmdTbl += AHCI_CMDHDR_ACMD_OFFSET;
+        PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciPortTaskState->aATAPICmd[0], ATAPI_PACKET_SIZE);
+    }
+
+    /* We "received" the FIS. Clear the BSY bit in regTFD. */
+    if ((pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_C) && (pAhciPortTaskState->fQueued))
+    {
+        /*
+         * We need to send a FIS which clears the busy bit if this is a queued command so that the guest can queue other commands.
+         * but this FIS does not assert an interrupt
+         */
+        ahciSendD2HFis(pAhciPort, pAhciPortTaskState, pAhciPortTaskState->cmdFis, false);
+        pAhciPort->regTFD &= ~AHCI_PORT_TFD_BSY;
+    }
+
+#ifdef DEBUG
+    /* Print some infos about the FIS. */
+    ahciDumpFisInfo(pAhciPort, &pAhciPortTaskState->cmdFis[0]);
+#endif
+}
+
+/**
+ * Transmit queue consumer
+ * Queue a new async task.
+ *
+ * @returns Success indicator.
+ *          If false the item will not be removed and the flushing will stop.
+ * @param   pDevIns     The device instance.
+ * @param   pItem       The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) ahciNotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+    PDEVPORTNOTIFIERQUEUEITEM pNotifierItem = (PDEVPORTNOTIFIERQUEUEITEM)pItem;
+    PAHCI                     pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIPort                 pAhciPort = &pAhci->ahciPort[pNotifierItem->iPort];
+    int                       rc = VINF_SUCCESS;
+
+    if (!pAhciPort->fAsyncInterface)
+    {
+        ahciLog(("%s: Got notification from GC\n", __FUNCTION__));
+        /* Notify the async IO thread. */
+        rc = RTSemEventSignal(pAhciPort->AsyncIORequestSem);
+        AssertRC(rc);
+    }
+    else
+    {
+        int                iTxDir;
+        PAHCIPORTTASKSTATE pAhciPortTaskState;
+
+        ahciLog(("%s: Processing command at slot %d\n", __FUNCTION__, pNotifierItem->iTask));
+
+        /* Check if there is already an allocated task struct in the cache.
+         * Allocate a new task otherwise.
+         */
+        if (!pAhciPort->aCachedTasks[pNotifierItem->iTask])
+        {
+            pAhciPortTaskState = (PAHCIPORTTASKSTATE)RTMemAllocZ(sizeof(AHCIPORTTASKSTATE));
+            AssertMsg(pAhciPortTaskState, ("%s: Cannot allocate task state memory!\n"));
+        }
+        else
+        {
+            pAhciPortTaskState = pAhciPort->aCachedTasks[pNotifierItem->iTask];
+        }
+
+        /** Set current command slot */
+        pAhciPortTaskState->uTag = pNotifierItem->iTask;
+        pAhciPort->regCMD |= (AHCI_PORT_CMD_CCS_SHIFT(pAhciPortTaskState->uTag));
+
+        ahciPortTaskGetCommandFis(pAhciPort, pAhciPortTaskState);
+
+        /* Mark the task as processed by the HBA if this is a queued task so that it doesn't occur in the CI register anymore. */
+        if (pNotifierItem->fQueued)
+        {
+            pAhciPortTaskState->fQueued = true;
+            ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciPortTaskState->uTag));
+        }
+        else
+            pAhciPortTaskState->fQueued = false;
+
+        if (!(pAhciPortTaskState->cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C))
+        {
+            /* If the reset bit is set put the device into reset state. */
+            if (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST)
+            {
+                ahciLog(("%s: Setting device into reset state\n", __FUNCTION__));
+                pAhciPort->fResetDevice = true;
+                ahciSendD2HFis(pAhciPort, pAhciPortTaskState, pAhciPortTaskState->cmdFis, true);
+                pAhciPort->aCachedTasks[pNotifierItem->iTask] = pAhciPortTaskState;
+                return true;
+            }
+            else if (pAhciPort->fResetDevice) /* The bit is not set and we are in a reset state. */
+            {
+                ahciFinishStorageDeviceReset(pAhciPort, pAhciPortTaskState);
+                pAhciPort->aCachedTasks[pNotifierItem->iTask] = pAhciPortTaskState;
+                return true;
+            }
+            else /* We are not in a reset state update the control registers. */
+            {
+                AssertMsgFailed(("%s: Update the control register\n", __FUNCTION__));
+            }
+        }
+
+        iTxDir = ahciProcessCmd(pAhciPort, pAhciPortTaskState, pAhciPortTaskState->cmdFis);
+
+        if (iTxDir != PDMBLOCKTXDIR_NONE)
+        {
+            if (pAhciPortTaskState->fQueued)
+            {
+                ahciLog(("%s: Before increment uActTasksActive=%u\n", __FUNCTION__, pAhciPort->uActTasksActive));
+                ASMAtomicIncU32(&pAhciPort->uActTasksActive);
+                ahciLog(("%s: After increment uActTasksActive=%u\n", __FUNCTION__, pAhciPort->uActTasksActive));
+            }
+
+            STAM_REL_COUNTER_INC(&pAhciPort->StatDMA);
+
+            rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE) ? false : true);
+            if (RT_FAILURE(rc))
+                AssertMsgFailed(("%s: Failed to process command %Rrc\n", __FUNCTION__, rc));
+
+            if (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+            {
+                pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1;
+                rc = pAhciPort->pDrvBlockAsync->pfnStartRead(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
+                                                             pAhciPortTaskState->pSGListHead, pAhciPortTaskState->cSGEntries,
+                                                             pAhciPortTaskState->cbTransfer,
+                                                             pAhciPortTaskState);
+            }
+            else
+            {
+                pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+                rc = pAhciPort->pDrvBlockAsync->pfnStartWrite(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
+                                                              pAhciPortTaskState->pSGListHead, pAhciPortTaskState->cSGEntries,
+                                                              pAhciPortTaskState->cbTransfer,
+                                                              pAhciPortTaskState);
+            }
+            if (rc == VINF_VD_ASYNC_IO_FINISHED)
+                rc = ahciTransferComplete(pAhciPort, pAhciPortTaskState);
+
+            if (RT_FAILURE(rc))
+                AssertMsgFailed(("%s: Failed to enqueue command %Rrc\n", __FUNCTION__, rc));
+        }
+        else
+        {
+            /* There is nothing left to do. Notify the guest. */
+            ahciSendD2HFis(pAhciPort, pAhciPortTaskState, &pAhciPortTaskState->cmdFis[0], true);
+            /* Add the task to the cache. */
+            pAhciPort->aCachedTasks[pAhciPortTaskState->uTag] = pAhciPortTaskState;
+        }
+    }
+
+    return true;
+}
+
+/* The async IO thread for one port. */
+static DECLCALLBACK(int) ahciAsyncIOLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+    PAHCIPort pAhciPort = (PAHCIPort)pThread->pvUser;
+    PAHCI     pAhci     = pAhciPort->CTX_SUFF(pAhci);
+    PAHCIPORTTASKSTATE pAhciPortTaskState;
+    int rc = VINF_SUCCESS;
+    uint64_t u64StartTime = 0;
+    uint64_t u64StopTime  = 0;
+    uint32_t uIORequestsProcessed = 0;
+    uint32_t uIOsPerSec = 0;
+
+    ahciLog(("%s: Port %d entering async IO loop.\n", __FUNCTION__, pAhciPort->iLUN));
+
+    if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+        return VINF_SUCCESS;
+
+    /* We use only one task structure. */
+    pAhciPortTaskState = (PAHCIPORTTASKSTATE)RTMemAllocZ(sizeof(AHCIPORTTASKSTATE));
+    if (!pAhciPortTaskState)
+    {
+        AssertMsgFailed(("Failed to allocate task state memory\n"));
+        return VERR_NO_MEMORY;
+    }
+
+    while(pThread->enmState == PDMTHREADSTATE_RUNNING)
+    {
+        uint32_t uQueuedTasksFinished = 0;
+
+        /* New run to get number of I/O requests per second?. */
+        if (!u64StartTime)
+            u64StartTime = RTTimeMilliTS();
+
+        ASMAtomicXchgBool(&pAhciPort->fAsyncIOThreadIdle, true);
+
+        rc = RTSemEventWait(pAhciPort->AsyncIORequestSem, 1000);
+        if (rc == VERR_TIMEOUT)
+        {
+            /* No I/O requests inbetween. Reset statistics and wait again. */
+            pAhciPort->StatIORequestsPerSecond.c = 0;
+            rc = RTSemEventWait(pAhciPort->AsyncIORequestSem, RT_INDEFINITE_WAIT);
+        }
+
+        if (RT_FAILURE(rc) || (pThread->enmState != PDMTHREADSTATE_RUNNING))
+            break;
+
+        AssertMsg(pAhciPort->pDrvBase, ("I/O thread without attached device?!\n"));
+
+        ASMAtomicXchgBool(&pAhciPort->fAsyncIOThreadIdle, false);
+
+        /*
+         * To maximize the throughput of the controller we try to minimize the
+         * number of world switches during interrupts by grouping as many
+         * I/O requests together as possible.
+         * On the other side we want to get minimal latency if the I/O load is low.
+         * Thatswhy the number of I/O requests per second is measured and if it is over
+         * a threshold the thread waits for other requests from the guest.
+         */
+        if (uIOsPerSec >= pAhci->cHighIOThreshold)
+        {
+            uint8_t uActWritePosPrev = pAhciPort->uActWritePos;
+
+            Log(("%s: Waiting for more tasks to get queued\n", __FUNCTION__));
+
+            do
+            {
+                /* Sleep some time. */
+                RTThreadSleep(pAhci->cMillisToSleep);
+                /* Check if we got some new requests inbetween. */
+                if (uActWritePosPrev != pAhciPort->uActWritePos)
+                {
+                    uActWritePosPrev = pAhciPort->uActWritePos;
+                    /*
+                     * Check if the queue is full. If that is the case
+                     * there is no point waiting another round.
+                     */
+                    if (   (   (pAhciPort->uActReadPos < uActWritePosPrev)
+                            && (uActWritePosPrev - pAhciPort->uActReadPos) == AHCI_NR_COMMAND_SLOTS)
+                        || (   (pAhciPort->uActReadPos > uActWritePosPrev)
+                            && (RT_ELEMENTS(pAhciPort->ahciIOTasks) - pAhciPort->uActReadPos + uActWritePosPrev) == AHCI_NR_COMMAND_SLOTS)  )
+                    {
+                        Log(("%s: Queue full -> leaving\n", __FUNCTION__));
+                        break;
+                    }
+                    Log(("%s: Another round\n", __FUNCTION__));
+                }
+                else /* No change break out of the loop. */
+                {
+#ifdef DEBUG
+                    uint8_t uQueuedTasks;
+                    if (pAhciPort->uActReadPos < uActWritePosPrev)
+                        uQueuedTasks = uActWritePosPrev - pAhciPort->uActReadPos;
+                    else
+                        uQueuedTasks = RT_ELEMENTS(pAhciPort->ahciIOTasks) - pAhciPort->uActReadPos + uActWritePosPrev;
+
+                    Log(("%s: %u Tasks are queued\n", __FUNCTION__, uQueuedTasks));
+#endif
+                    break;
+                }
+            } while (true);
+        }
+
+        ahciLog(("%s: Processing requests\n", __FUNCTION__));
+        ASMAtomicXchgBool(&pAhciPort->fNotificationSend, false);
+
+        uint32_t cTasksToProcess = ASMAtomicXchgU32(&pAhciPort->uActTasksActive, 0);
+
+        /* Process commands. */
+        while (   (cTasksToProcess > 0)
+               && RT_LIKELY(!pAhciPort->fPortReset))
+        {
+            int       iTxDir;
+            uint8_t   uActTag;
+
+            STAM_PROFILE_START(&pAhciPort->StatProfileProcessTime, a);
+
+            ahciLog(("%s: uActWritePos=%u\n", __FUNCTION__, pAhciPort->uActWritePos));
+            ahciLog(("%s: Before uActReadPos=%u\n", __FUNCTION__, pAhciPort->uActReadPos));
+
+            pAhciPortTaskState->uATARegStatus = 0;
+            pAhciPortTaskState->uATARegError  = 0;
+            uActTag = pAhciPort->ahciIOTasks[pAhciPort->uActReadPos];
+
+            pAhciPortTaskState->uTag = AHCI_TASK_GET_TAG(uActTag);
+            AssertMsg(pAhciPortTaskState->uTag < AHCI_NR_COMMAND_SLOTS, ("%s: Invalid Tag number!!\n", __FUNCTION__));
+
+            /** Set current command slot */
+            pAhciPort->regCMD |= (AHCI_PORT_CMD_CCS_SHIFT(pAhciPortTaskState->uTag));
+
+            /* Mark the task as processed by the HBA if this is a queued task so that it doesn't occur in the CI register anymore. */
+            if (AHCI_TASK_IS_QUEUED(uActTag))
+            {
+                pAhciPortTaskState->fQueued = true;
+                ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciPortTaskState->uTag));
+            }
+            else
+            {
+                pAhciPortTaskState->fQueued = false;
+            }
+
+            ahciPortTaskGetCommandFis(pAhciPort, pAhciPortTaskState);
+
+            ahciLog(("%s: Got command at slot %d\n", __FUNCTION__, pAhciPortTaskState->uTag));
+
+            if (!(pAhciPortTaskState->cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C))
+            {
+                /* If the reset bit is set put the device into reset state. */
+                if (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST)
+                {
+                    ahciLog(("%s: Setting device into reset state\n", __FUNCTION__));
+                    pAhciPort->fResetDevice = true;
+                    ahciSendD2HFis(pAhciPort, pAhciPortTaskState, &pAhciPortTaskState->cmdFis[0], true);
+                }
+                else if (pAhciPort->fResetDevice) /* The bit is not set and we are in a reset state. */
+                {
+                    ahciFinishStorageDeviceReset(pAhciPort, pAhciPortTaskState);
+                }
+                /* TODO: We are not in a reset state update the control registers. */
+            }
+            else
+            {
+                iTxDir = ahciProcessCmd(pAhciPort, pAhciPortTaskState, &pAhciPortTaskState->cmdFis[0]);
+
+                if (iTxDir != PDMBLOCKTXDIR_NONE)
+                {
+                    uint64_t uOffset;
+                    size_t cbTransfer;
+                    PPDMDATASEG pSegCurr;
+                    PAHCIPORTTASKSTATESGENTRY pSGInfoCurr;
+
+                    rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE) ? false : true);
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("%s: Failed to get number of list elments %Rrc\n", __FUNCTION__, rc));
+
+                    STAM_REL_COUNTER_INC(&pAhciPort->StatDMA);
+
+                    /* Initialize all values. */
+                    uOffset     = pAhciPortTaskState->uOffset;
+                    cbTransfer  = pAhciPortTaskState->cbTransfer;
+                    pSegCurr    = &pAhciPortTaskState->pSGListHead[0];
+                    pSGInfoCurr = pAhciPortTaskState->paSGEntries;
+
+                    STAM_PROFILE_START(&pAhciPort->StatProfileReadWrite, a);
+
+                    while(cbTransfer)
+                    {
+                        size_t cbProcess = (cbTransfer < pSegCurr->cbSeg) ? cbTransfer : pSegCurr->cbSeg;
+
+                        AssertMsg(!(pSegCurr->cbSeg % 512), ("Buffer is not sector aligned cbSeg=%d\n", pSegCurr->cbSeg));
+                        AssertMsg(!(uOffset % 512), ("Offset is not sector aligned %llu\n", uOffset));
+                        AssertMsg(!(cbProcess % 512), ("Number of bytes to process is not sector aligned %lu\n", cbProcess));
+
+                        if (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+                        {
+                            pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1;
+                            rc = pAhciPort->pDrvBlock->pfnRead(pAhciPort->pDrvBlock, uOffset,
+                                                               pSegCurr->pvSeg, cbProcess);
+                            pAhciPort->Led.Actual.s.fReading = 0;
+                            if (RT_FAILURE(rc))
+                                AssertMsgFailed(("%s: Failed to read data %Rrc\n", __FUNCTION__, rc));
+
+                            STAM_REL_COUNTER_ADD(&pAhciPort->StatBytesRead, cbProcess);
+                        }
+                        else
+                        {
+                            pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+                            rc = pAhciPort->pDrvBlock->pfnWrite(pAhciPort->pDrvBlock, uOffset,
+                                                                pSegCurr->pvSeg, cbProcess);
+                            pAhciPort->Led.Actual.s.fWriting = 0;
+                            if (RT_FAILURE(rc))
+                                AssertMsgFailed(("%s: Failed to write data %Rrc\n", __FUNCTION__, rc));
+
+                            STAM_REL_COUNTER_ADD(&pAhciPort->StatBytesWritten, cbProcess);
+                        }
+
+                        /* Go to the next entry. */
+                        uOffset    += cbProcess;
+                        cbTransfer -= cbProcess;
+                        pSegCurr++;
+                        pSGInfoCurr++;
+                    }
+
+                    STAM_PROFILE_STOP(&pAhciPort->StatProfileReadWrite, a);
+
+                    /* Cleanup. */
+                    rc = ahciScatterGatherListDestroy(pAhciPort, pAhciPortTaskState);
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Destroying task list failed rc=%Rrc\n", rc));
+
+                    if (RT_LIKELY(!pAhciPort->fPortReset))
+                    {
+                        pAhciPortTaskState->cmdHdr.u32PRDBC = pAhciPortTaskState->cbTransfer;
+                        pAhciPortTaskState->uATARegError = 0;
+                        pAhciPortTaskState->uATARegStatus = ATA_STAT_READY | ATA_STAT_SEEK;
+                        /* Write updated command header into memory of the guest. */
+                        PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr,
+                                           &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
+
+                        if (pAhciPortTaskState->fQueued)
+                            uQueuedTasksFinished |= (1 << pAhciPortTaskState->uTag);
+                        else
+                        {
+                            /* Task is not queued send D2H FIS */
+                            ahciSendD2HFis(pAhciPort, pAhciPortTaskState, &pAhciPortTaskState->cmdFis[0], true);
+                        }
+
+                        uIORequestsProcessed++;
+                    }
+                }
+                else
+                {
+                    /* Nothing left to do. Notify the guest. */
+                    ahciSendD2HFis(pAhciPort, pAhciPortTaskState, &pAhciPortTaskState->cmdFis[0], true);
+                }
+
+                STAM_PROFILE_STOP(&pAhciPort->StatProfileProcessTime, a);
+            }
+
+#ifdef DEBUG
+            /* Be paranoid. */
+            memset(&pAhciPortTaskState->cmdHdr, 0, sizeof(CmdHdr));
+            memset(&pAhciPortTaskState->cmdFis, 0, AHCI_CMDFIS_TYPE_H2D_SIZE);
+            pAhciPortTaskState->GCPhysCmdHdrAddr = 0;
+            pAhciPortTaskState->uOffset = 0;
+            pAhciPortTaskState->cbTransfer = 0;
+            /* Make the port number invalid making it easier to track down bugs. */
+            pAhciPort->ahciIOTasks[pAhciPort->uActReadPos] = 0xff;
+#endif
+
+            pAhciPort->uActReadPos++;
+            pAhciPort->uActReadPos %= RT_ELEMENTS(pAhciPort->ahciIOTasks);
+            ahciLog(("%s: After uActReadPos=%u\n", __FUNCTION__, pAhciPort->uActReadPos));
+            cTasksToProcess--;
+        }
+
+        if (uQueuedTasksFinished && RT_LIKELY(!pAhciPort->fPortReset))
+            ahciSendSDBFis(pAhciPort, uQueuedTasksFinished, pAhciPortTaskState, true);
+
+        uQueuedTasksFinished = 0;
+
+        u64StopTime = RTTimeMilliTS();
+        /* Check if one second has passed. */
+        if (u64StopTime - u64StartTime >= 1000)
+        {
+            /* Calculate number of I/O requests per second. */
+            uIOsPerSec = uIORequestsProcessed / ((u64StopTime - u64StartTime) / 1000);
+            ahciLog(("%s: Processed %u requests in %llu ms -> %u requests/s\n", __FUNCTION__, uIORequestsProcessed, u64StopTime - u64StartTime, uIOsPerSec));
+            u64StartTime = 0;
+            uIORequestsProcessed = 0;
+            /* For the release statistics. There is no macro to set the counter to a specific value. */
+            pAhciPort->StatIORequestsPerSecond.c = uIOsPerSec;
+        }
+    }
+
+    /* Free task state memory */
+    if (pAhciPortTaskState->pSGListHead)
+        RTMemFree(pAhciPortTaskState->pSGListHead);
+    if (pAhciPortTaskState->paSGEntries)
+        RTMemFree(pAhciPortTaskState->paSGEntries);
+    if (pAhciPortTaskState->pvBufferUnaligned)
+        RTMemFree(pAhciPortTaskState->pvBufferUnaligned);
+    RTMemFree(pAhciPortTaskState);
+
+    ahciLog(("%s: Port %d async IO thread exiting rc=%Rrc\n", __FUNCTION__, pAhciPort->iLUN, rc));
+    return rc;
+}
+
+/**
+ * Unblock the async I/O thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The pcnet device instance.
+ * @param   pThread     The send thread.
+ */
+static DECLCALLBACK(int) ahciAsyncIOLoopWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+    PAHCIPort pAhciPort = (PAHCIPort)pThread->pvUser;
+    return RTSemEventSignal(pAhciPort->AsyncIORequestSem);
+}
+
+/**
+ * Called when a media is mounted.
+ *
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ahciMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+    PAHCIPort pAhciPort = PDMIMOUNTNOTIFY_2_PAHCIPORT(pInterface);
+    Log(("%s: changing LUN#%d\n", __FUNCTION__, pAhciPort->iLUN));
+
+    /* Ignore the call if we're called while being attached. */
+    if (!pAhciPort->pDrvBlock)
+        return;
+
+    pAhciPort->cTotalSectors = pAhciPort->pDrvBlock->pfnGetSize(pAhciPort->pDrvBlock) / 512;
+
+    /*
+     * Initialize registers
+     */
+    pAhciPort->regCMD  |= AHCI_PORT_CMD_CPS;
+    pAhciPort->regIS   |= AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS;
+    pAhciPort->regSERR |= AHCI_PORT_SERR_N;
+    if (pAhciPort->regIE & AHCI_PORT_IE_CPDE)
+        ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN);
+}
+
+/**
+ * Called when a media is unmounted
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ahciUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+    PAHCIPort pAhciPort = PDMIMOUNTNOTIFY_2_PAHCIPORT(pInterface);
+    Log(("%s:\n", __FUNCTION__));
+
+    pAhciPort->cTotalSectors = 0;
+
+    /*
+     * Inform the guest about the removed device.
+     */
+    pAhciPort->regSSTS = 0;
+    pAhciPort->regCMD &= ~AHCI_PORT_CMD_CPS;
+    pAhciPort->regIS |= AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS;
+    pAhciPort->regSERR |= AHCI_PORT_SERR_N;
+    if (pAhciPort->regIE & AHCI_PORT_IE_CPDE)
+        ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN);
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(int) ahciDestruct(PPDMDEVINS pDevIns)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    int rc = VINF_SUCCESS;
+    unsigned iActPort = 0;
+
+    /*
+     * At this point the async I/O thread is suspended and will not enter
+     * this module again. So, no coordination is needed here and PDM
+     * will take care of terminating and cleaning up the thread.
+     */
+    if (PDMCritSectIsInitialized(&pAhci->lock))
+    {
+        TMR3TimerDestroy(pAhci->CTX_SUFF(pHbaCccTimer));
+
+        Log(("%s: Destruct every port\n", __FUNCTION__));
+        for (iActPort = 0; iActPort < pAhci->cPortsImpl; iActPort++)
+        {
+            PAHCIPort pAhciPort = &pAhci->ahciPort[iActPort];
+
+            if (pAhciPort->pAsyncIOThread)
+            {
+                /* Destroy the event semaphore. */
+                rc = RTSemEventDestroy(pAhciPort->AsyncIORequestSem);
+                if (RT_FAILURE(rc))
+                {
+                    Log(("%s: Destroying event semaphore for port %d failed rc=%Rrc\n", __FUNCTION__, iActPort, rc));
+                }
+            }
+
+            /* Free all cached tasks. */
+            for (uint32_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
+            {
+                if (pAhciPort->aCachedTasks[i])
+                {
+                    if (pAhciPort->aCachedTasks[i]->pSGListHead)
+                        RTMemFree(pAhciPort->aCachedTasks[i]->pSGListHead);
+                    if (pAhciPort->aCachedTasks[i]->paSGEntries)
+                        RTMemFree(pAhciPort->aCachedTasks[i]->paSGEntries);
+
+                    RTMemFree(pAhciPort->aCachedTasks[i]);
+                }
+            }
+        }
+
+        /* Destroy emulated ATA controllers. */
+        for (unsigned i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+            ataControllerDestroy(&pAhci->aCts[i]);
+
+        PDMR3CritSectDelete(&pAhci->lock);
+    }
+
+    return rc;
+}
+
+/**
+ * Configure the attached device for a port.
+ *
+ * @returns VBox status code
+ * @param   pDevIns     The device instance data.
+ * @param   pAhciPort   The port for which the device is to be configured.
+ */
+static int ahciConfigureLUN(PPDMDEVINS pDevIns, PAHCIPort pAhciPort)
+{
+    int          rc = VINF_SUCCESS;
+    PDMBLOCKTYPE enmType;
+
+    /*
+     * Query the block and blockbios interfaces.
+     */
+    pAhciPort->pDrvBlock = (PDMIBLOCK *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK);
+    if (!pAhciPort->pDrvBlock)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pAhciPort->iLUN));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+    pAhciPort->pDrvBlockBios = (PDMIBLOCKBIOS *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
+    if (!pAhciPort->pDrvBlockBios)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d hasn't a block BIOS interface!\n", pAhciPort->iLUN));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+
+    pAhciPort->pDrvMount = (PDMIMOUNT *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_MOUNT);
+
+    /* Try to get the optional async block interface. */
+    pAhciPort->pDrvBlockAsync = (PDMIBLOCKASYNC *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK_ASYNC);
+
+    /*
+     * Validate type.
+     */
+    enmType = pAhciPort->pDrvBlock->pfnGetType(pAhciPort->pDrvBlock);
+
+    if (   enmType != PDMBLOCKTYPE_HARD_DISK
+        && enmType != PDMBLOCKTYPE_CDROM
+        && enmType != PDMBLOCKTYPE_DVD)
+    {
+        AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd. enmType=%d\n", pAhciPort->iLUN, enmType));
+        return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+    }
+
+    if (   (enmType == PDMBLOCKTYPE_CDROM || enmType == PDMBLOCKTYPE_DVD)
+        && !pAhciPort->pDrvMount)
+    {
+        AssertMsgFailed(("Internal error: CD/DVD-ROM without a mountable interface\n"));
+        return VERR_INTERNAL_ERROR;
+    }
+    pAhciPort->fATAPI = (enmType == PDMBLOCKTYPE_CDROM || enmType == PDMBLOCKTYPE_DVD);
+
+    if (pAhciPort->fATAPI)
+    {
+        pAhciPort->cTotalSectors = pAhciPort->pDrvBlock->pfnGetSize(pAhciPort->pDrvBlock) / 2048;
+        pAhciPort->PCHSGeometry.cCylinders = 0;
+        pAhciPort->PCHSGeometry.cHeads     = 0;
+        pAhciPort->PCHSGeometry.cSectors   = 0;
+        LogRel(("AHCI LUN#%d: CD/DVD, total number of sectors %Ld\n", pAhciPort->iLUN, pAhciPort->cTotalSectors));
+    }
+    else
+    {
+        pAhciPort->cTotalSectors = pAhciPort->pDrvBlock->pfnGetSize(pAhciPort->pDrvBlock) / 512;
+        rc = pAhciPort->pDrvBlockBios->pfnGetPCHSGeometry(pAhciPort->pDrvBlockBios,
+                                                          &pAhciPort->PCHSGeometry);
+        if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+        {
+            pAhciPort->PCHSGeometry.cCylinders = 0;
+            pAhciPort->PCHSGeometry.cHeads     = 16; /*??*/
+            pAhciPort->PCHSGeometry.cSectors   = 63; /*??*/
+        }
+        else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+        {
+            pAhciPort->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+            rc = VINF_SUCCESS;
+        }
+        AssertRC(rc);
+
+        if (   pAhciPort->PCHSGeometry.cCylinders == 0
+            || pAhciPort->PCHSGeometry.cHeads == 0
+            || pAhciPort->PCHSGeometry.cSectors == 0)
+        {
+            uint64_t cCylinders = pAhciPort->cTotalSectors / (16 * 63);
+            pAhciPort->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+            pAhciPort->PCHSGeometry.cHeads = 16;
+            pAhciPort->PCHSGeometry.cSectors = 63;
+            /* Set the disk geometry information. Ignore errors. */
+            pAhciPort->pDrvBlockBios->pfnSetPCHSGeometry(pAhciPort->pDrvBlockBios,
+                                                         &pAhciPort->PCHSGeometry);
+            rc = VINF_SUCCESS;
+        }
+        LogRel(("AHCI: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n",
+                 pAhciPort->iLUN, pAhciPort->PCHSGeometry.cCylinders,
+                 pAhciPort->PCHSGeometry.cHeads, pAhciPort->PCHSGeometry.cSectors,
+                 pAhciPort->cTotalSectors));
+    }
+    return rc;
+}
+
+static bool ahciWaitForAllAsyncIOIsFinished(PPDMDEVINS pDevIns, unsigned cMillies)
+{
+    PAHCI     pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    uint64_t  u64Start;
+    PAHCIPort pAhciPort;
+    bool      fAllFinished;
+
+    u64Start = RTTimeMilliTS();
+    for (;;)
+    {
+        fAllFinished = true;
+        for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+        {
+            pAhciPort = &pAhci->ahciPort[i];
+
+            if (pAhciPort->pDrvBase)
+            {
+                if (pAhciPort->fAsyncInterface)
+                    fAllFinished &= (pAhciPort->uActTasksActive == 0);
+                else
+                    fAllFinished &= ((pAhciPort->uActTasksActive == 0) && (pAhciPort->fAsyncIOThreadIdle));
+
+                if (!fAllFinished)
+                    break;
+            }
+        }
+        if (   fAllFinished
+            || RTTimeMilliTS() - u64Start >= cMillies)
+            break;
+
+        /* Sleep a bit. */
+        RTThreadSleep(100);
+    }
+    return fAllFinished;
+}
+
+static DECLCALLBACK(int) ahciSavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    if (!ahciWaitForAllAsyncIOIsFinished(pDevIns, 20000))
+        AssertMsgFailed(("One port is still active\n"));
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        int rc;
+
+        rc = ataControllerSavePrep(&pAhci->aCts[i], pSSM);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) ahciLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        int rc;
+
+        rc = ataControllerLoadPrep(&pAhci->aCts[i], pSSM);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(void) ahciSuspend(PPDMDEVINS pDevIns)
+{
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    if (!ahciWaitForAllAsyncIOIsFinished(pDevIns, 20000))
+        AssertMsgFailed(("AHCI: One port is still active\n"));
+
+    Log(("%s:\n", __FUNCTION__));
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        ataControllerSuspend(&pAhci->aCts[i]);
+    }
+    return;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(void) ahciResume(PPDMDEVINS pDevIns)
+{
+    PAHCI    pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    Log(("%s:\n", __FUNCTION__));
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        ataControllerResume(&pAhci->aCts[i]);
+    }
+    return;
+}
+
+/**
+ * Saves a state of the AHCI device.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   pSSMHandle  The handle to save the state to.
+ */
+static DECLCALLBACK(int) ahciSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    uint32_t i;
+
+    Assert(!pAhci->f8ByteMMIO4BytesWrittenSuccessfully);
+
+    /* First the main device structure. */
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaCap);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaCtrl);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaIs);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaPi);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaVs);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaCccCtl);
+    SSMR3PutU32(pSSMHandle, pAhci->regHbaCccPorts);
+    SSMR3PutU8(pSSMHandle, pAhci->uCccPortNr);
+    SSMR3PutU64(pSSMHandle, pAhci->uCccTimeout);
+    SSMR3PutU32(pSSMHandle, pAhci->uCccNr);
+    SSMR3PutU32(pSSMHandle, pAhci->uCccCurrentNr);
+    SSMR3PutU32(pSSMHandle, pAhci->u32PortsInterrupted);
+    SSMR3PutBool(pSSMHandle, pAhci->fReset);
+    SSMR3PutBool(pSSMHandle, pAhci->f64BitAddr);
+    SSMR3PutBool(pSSMHandle, pAhci->fR0Enabled);
+    SSMR3PutBool(pSSMHandle, pAhci->fGCEnabled);
+
+    /* Now every port. */
+    for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+    {
+        Assert(pAhci->ahciPort[i].uActTasksActive == 0);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regCLB);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regCLBU);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regFB);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regFBU);
+        SSMR3PutGCPhys(pSSMHandle, pAhci->ahciPort[i].GCPhysAddrClb);
+        SSMR3PutGCPhys(pSSMHandle, pAhci->ahciPort[i].GCPhysAddrFb);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regIS);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regIE);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regCMD);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regTFD);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regSIG);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regSSTS);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regSCTL);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regSERR);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regSACT);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].regCI);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].PCHSGeometry.cCylinders);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].PCHSGeometry.cHeads);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].PCHSGeometry.cSectors);
+        SSMR3PutU64(pSSMHandle, pAhci->ahciPort[i].cTotalSectors);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].cMultSectors);
+        SSMR3PutU8(pSSMHandle, pAhci->ahciPort[i].uATATransferMode);
+        SSMR3PutBool(pSSMHandle, pAhci->ahciPort[i].fResetDevice);
+
+        for (uint8_t uActTask = 0; uActTask < AHCI_NR_COMMAND_SLOTS; uActTask++)
+            SSMR3PutU8(pSSMHandle, pAhci->ahciPort[i].ahciIOTasks[uActTask]);
+
+        SSMR3PutU8(pSSMHandle, pAhci->ahciPort[i].uActWritePos);
+        SSMR3PutU8(pSSMHandle, pAhci->ahciPort[i].uActReadPos);
+        SSMR3PutBool(pSSMHandle, pAhci->ahciPort[i].fPoweredOn);
+        SSMR3PutBool(pSSMHandle, pAhci->ahciPort[i].fSpunUp);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].u32TasksFinished);
+        SSMR3PutU32(pSSMHandle, pAhci->ahciPort[i].u32QueuedTasksFinished);
+    }
+
+    /* Now the emulated ata controllers. */
+    for (i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        int rc;
+
+        rc = ataControllerSaveExec(&pAhci->aCts[i], pSSMHandle);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    return SSMR3PutU32(pSSMHandle, ~0); /* sanity/terminator */
+}
+
+/**
+ * Loads a saved AHCI device state.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   pSSMHandle  The handle to the saved state.
+ * @param   u32Version  The data unit version number.
+ */
+static DECLCALLBACK(int) ahciLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t u32Version)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    uint32_t u32;
+    uint32_t i;
+    int rc = VINF_SUCCESS;
+
+    if (u32Version != AHCI_SAVED_STATE_VERSION)
+        return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+    /* Restore data. */
+
+    /* First the main device structure. */
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaCap);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaCtrl);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaIs);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaPi);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaVs);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaCccCtl);
+    SSMR3GetU32(pSSMHandle, &pAhci->regHbaCccPorts);
+    SSMR3GetU8(pSSMHandle, &pAhci->uCccPortNr);
+    SSMR3GetU64(pSSMHandle, &pAhci->uCccTimeout);
+    SSMR3GetU32(pSSMHandle, &pAhci->uCccNr);
+    SSMR3GetU32(pSSMHandle, &pAhci->uCccCurrentNr);
+
+    SSMR3GetU32(pSSMHandle, &pAhci->u32PortsInterrupted);
+    SSMR3GetBool(pSSMHandle, &pAhci->fReset);
+    SSMR3GetBool(pSSMHandle, &pAhci->f64BitAddr);
+    SSMR3GetBool(pSSMHandle, &pAhci->fR0Enabled);
+    SSMR3GetBool(pSSMHandle, &pAhci->fGCEnabled);
+
+    /* Now every port. */
+    for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+    {
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regCLB);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regCLBU);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regFB);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regFBU);
+        SSMR3GetGCPhys(pSSMHandle, (RTGCPHYS *)&pAhci->ahciPort[i].GCPhysAddrClb);
+        SSMR3GetGCPhys(pSSMHandle, (RTGCPHYS *)&pAhci->ahciPort[i].GCPhysAddrFb);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regIS);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regIE);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regCMD);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regTFD);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regSIG);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regSSTS);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regSCTL);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].regSERR);
+        SSMR3GetU32(pSSMHandle, (uint32_t *)&pAhci->ahciPort[i].regSACT);
+        SSMR3GetU32(pSSMHandle, (uint32_t *)&pAhci->ahciPort[i].regCI);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].PCHSGeometry.cCylinders);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].PCHSGeometry.cHeads);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].PCHSGeometry.cSectors);
+        SSMR3GetU64(pSSMHandle, &pAhci->ahciPort[i].cTotalSectors);
+        SSMR3GetU32(pSSMHandle, &pAhci->ahciPort[i].cMultSectors);
+        SSMR3GetU8(pSSMHandle, &pAhci->ahciPort[i].uATATransferMode);
+        SSMR3GetBool(pSSMHandle, &pAhci->ahciPort[i].fResetDevice);
+
+        for (uint8_t uActTask = 0; uActTask < AHCI_NR_COMMAND_SLOTS; uActTask++)
+            SSMR3GetU8(pSSMHandle, (uint8_t *)&pAhci->ahciPort[i].ahciIOTasks[uActTask]);
+
+        SSMR3GetU8(pSSMHandle, &pAhci->ahciPort[i].uActWritePos);
+        SSMR3GetU8(pSSMHandle, &pAhci->ahciPort[i].uActReadPos);
+        SSMR3GetBool(pSSMHandle, &pAhci->ahciPort[i].fPoweredOn);
+        SSMR3GetBool(pSSMHandle, &pAhci->ahciPort[i].fSpunUp);
+        SSMR3GetU32(pSSMHandle, (uint32_t *)&pAhci->ahciPort[i].u32TasksFinished);
+        SSMR3GetU32(pSSMHandle, (uint32_t *)&pAhci->ahciPort[i].u32QueuedTasksFinished);
+    }
+
+    /* Now the emulated ata controllers. */
+    for (i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+    {
+        int rc;
+
+        rc = ataControllerLoadExec(&pAhci->aCts[i], pSSMHandle);
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
+    rc = SSMR3GetU32(pSSMHandle, &u32);
+    if (RT_FAILURE(rc))
+        return rc;
+    if (u32 != ~0U)
+    {
+        AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+        rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+        return rc;
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Detach notification.
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) ahciDetach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PAHCI           pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIPort       pAhciPort = &pAhci->ahciPort[iLUN];
+    int             rc = VINF_SUCCESS;
+
+    Log(("%s:\n", __FUNCTION__));
+
+    AssertMsg(iLUN < pAhci->cPortsImpl, ("iLUN=%u", iLUN));
+
+    if (!pAhciPort->fAsyncInterface)
+    {
+        int rcThread;
+        /* Destroy the thread. */
+        rc = PDMR3ThreadDestroy(pAhciPort->pAsyncIOThread, &rcThread);
+        if (RT_FAILURE(rc) || RT_FAILURE(rcThread))
+            AssertMsgFailed(("%s Failed to destroy async IO thread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread));
+
+        rc = RTSemEventDestroy(pAhciPort->AsyncIORequestSem);
+        if (RT_FAILURE(rc))
+            AssertMsgFailed(("%s: Failed to destroy the event semaphore rc=%Rrc.\n", __FUNCTION__, rc));
+    }
+
+    /*
+     * Zero some important members.
+     */
+    pAhciPort->pDrvBase = NULL;
+    pAhciPort->pDrvBlock = NULL;
+    pAhciPort->pDrvBlockAsync = NULL;
+    pAhciPort->pDrvBlockBios = NULL;
+}
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for one port.
+ * The VM is suspended at this point.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int)  ahciAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PAHCI       pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+    PAHCIPort   pAhciPort = &pAhci->ahciPort[iLUN];
+    int         rc;
+
+    Log(("%s:\n", __FUNCTION__));
+
+    /* the usual paranoia */
+    AssertMsg(iLUN < pAhci->cPortsImpl, ("iLUN=%u", iLUN));
+    AssertRelease(!pAhciPort->pDrvBase);
+    AssertRelease(!pAhciPort->pDrvBlock);
+    AssertRelease(!pAhciPort->pDrvBlockAsync);
+    Assert(pAhciPort->iLUN == iLUN);
+
+    /*
+     * Try attach the block device and get the interfaces,
+     * required as well as optional.
+     */
+    rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, NULL);
+    if (RT_SUCCESS(rc))
+        rc = ahciConfigureLUN(pDevIns, pAhciPort);
+    else
+        AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pAhciPort->iLUN, rc));
+
+    if (RT_FAILURE(rc))
+    {
+        pAhciPort->pDrvBase = NULL;
+        pAhciPort->pDrvBlock = NULL;
+    }
+    else
+    {
+        char szName[24];
+        RTStrPrintf(szName, sizeof(szName), "Port%d", iLUN);
+
+        if (pAhciPort->pDrvBlockAsync)
+        {
+            pAhciPort->fAsyncInterface = true;
+        }
+        else
+        {
+            pAhciPort->fAsyncInterface = false;
+
+            /* Create event semaphore. */
+            rc = RTSemEventCreate(&pAhciPort->AsyncIORequestSem);
+            if (RT_FAILURE(rc))
+            {
+                Log(("%s: Failed to create event semaphore for %s.\n", __FUNCTION__, szName));
+                return rc;
+            }
+
+            /* Create the async IO thread. */
+            rc = PDMDevHlpPDMThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
+                                          RTTHREADTYPE_IO, szName);
+            if (RT_FAILURE(rc))
+            {
+                AssertMsgFailed(("%s: Async IO Thread creation for %s failed rc=%d\n", __FUNCTION__, szName, rc));
+                return rc;
+            }
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(void) ahciReset(PPDMDEVINS pDevIns)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    if (!ahciWaitForAllAsyncIOIsFinished(pDevIns, 20000))
+        AssertMsgFailed(("AHCI: One port is still active\n"));
+
+    ahciHBAReset(pAhci);
+
+    /* Hardware reset for the ports. */
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+        ahciPortHwReset(&pAhci->ahciPort[i]);
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+        ataControllerReset(&pAhci->aCts[i]);
+}
+
+/**
+ * Poweroff notification.
+ *
+ * @returns nothing
+ * @param   pDevIns Pointer to the device instance
+ */
+static DECLCALLBACK(void) ahciPowerOff(PPDMDEVINS pDevIns)
+{
+    PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+    if (!ahciWaitForAllAsyncIOIsFinished(pDevIns, 20000))
+        AssertMsgFailed(("AHCI: One port is still active\n"));
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->aCts); i++)
+        ataControllerPowerOff(&pAhci->aCts[i]);
+}
+
+/**
+ * Construct a device instance for a VM.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ *                      If the registration structure is needed, pDevIns->pDevReg points to it.
+ * @param   iInstance   Instance number. Use this to figure out which registers and such to use.
+ *                      The device number is also found in pDevIns->iInstance, but since it's
+ *                      likely to be freqently used PDM passes it as parameter.
+ * @param   pCfgHandle  Configuration node handle for the device. Use this to obtain the configuration
+ *                      of the device instance. It's also found in pDevIns->pCfgHandle, but like
+ *                      iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) ahciConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
+{
+    PAHCI      pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+    PPDMIBASE  pBase;
+    int        rc = VINF_SUCCESS;
+    unsigned   i = 0;
+    bool       fGCEnabled = false;
+    bool       fR0Enabled = false;
+    uint32_t   cbTotalBufferSize = 0;
+
+    /*
+     * Validate and read configuration.
+     */
+    rc = CFGMR3AreValuesValid(pCfgHandle, "GCEnabled\0"
+                                          "R0Enabled\0"
+                                          "PrimaryMaster\0"
+                                          "PrimarySlave\0"
+                                          "SecondaryMaster\0"
+                                          "SecondarySlave\0"
+                                          "PortCount\0"
+                                          "UseAsyncInterfaceIfAvailable\0");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+                                N_("AHCI configuration error: unknown option specified"));
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "GCEnabled", &fGCEnabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read GCEnabled as boolean"));
+    Log(("%s: fGCEnabled=%d\n", __FUNCTION__, fGCEnabled));
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "R0Enabled", &fR0Enabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read R0Enabled as boolean"));
+    Log(("%s: fR0Enabled=%d\n", __FUNCTION__, fR0Enabled));
+
+    rc = CFGMR3QueryU32Def(pCfgHandle, "PortCount", &pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read PortCount as integer"));
+    Log(("%s: cPortsImpl=%u\n", __FUNCTION__, pThis->cPortsImpl));
+    if (pThis->cPortsImpl > AHCI_MAX_NR_PORTS_IMPL)
+        return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+                                   N_("AHCI configuration error: PortCount=%u should not exceed %u"),
+                                   pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL);
+    if (pThis->cPortsImpl < 1)
+        return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+                                   N_("AHCI configuration error: PortCount=%u should be at least 1"),
+                                   pThis->cPortsImpl);
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "UseAsyncInterfaceIfAvailable", &pThis->fUseAsyncInterfaceIfAvailable, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read UseAsyncInterfaceIfAvailable as boolean"));
+    rc = CFGMR3QueryU32Def(pCfgHandle, "HighIOThreshold", &pThis->cHighIOThreshold, ~0);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read HighIOThreshold as integer"));
+    rc = CFGMR3QueryU32Def(pCfgHandle, "MillisToSleep", &pThis->cMillisToSleep, 0);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI configuration error: failed to read MillisToSleep as integer"));
+
+    pThis->fR0Enabled = fR0Enabled;
+    pThis->fGCEnabled = fGCEnabled;
+    pThis->pDevInsR3 = pDevIns;
+    pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+    pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+    PCIDevSetVendorId    (&pThis->dev, 0x8086); /* Intel */
+    PCIDevSetDeviceId    (&pThis->dev, 0x2829); /* ICH-8M */
+    PCIDevSetCommand     (&pThis->dev, 0x0000);
+    PCIDevSetRevisionId  (&pThis->dev, 0x02);
+    PCIDevSetClassProg   (&pThis->dev, 0x01);
+    PCIDevSetClassSub    (&pThis->dev, 0x06);
+    PCIDevSetClassBase   (&pThis->dev, 0x01);
+    PCIDevSetBaseAddress (&pThis->dev, 5, false, false, false, 0x00000000);
+
+    pThis->dev.config[0x34] = 0x80; /* Capability pointer. */
+
+    PCIDevSetInterruptLine(&pThis->dev, 0x00);
+    PCIDevSetInterruptPin (&pThis->dev, 0x01);
+
+    pThis->dev.config[0x70] = 0x01; /* Capability ID: PCI Power Management Interface */
+    pThis->dev.config[0x71] = 0x00;
+    pThis->dev.config[0x72] = 0x03;
+
+    pThis->dev.config[0x80] = 0x05; /* Capability ID: Message Signaled Interrupts. Disabled. */
+    pThis->dev.config[0x81] = 0x70; /* next. */
+
+    pThis->dev.config[0x90] = 0x40; /* AHCI mode. */
+    pThis->dev.config[0x92] = 0x3f;
+    pThis->dev.config[0x94] = 0x80;
+    pThis->dev.config[0x95] = 0x01;
+    pThis->dev.config[0x97] = 0x78;
+
+    /*
+     * Register the PCI device, it's I/O regions.
+     */
+    rc = PDMDevHlpPCIRegister (pDevIns, &pThis->dev);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    /*
+     * Solaris 10 U5 fails to map the AHCI register space when the sets (0..5) for the legacy
+     * IDE registers are not available.
+     * We set up "fake" entries in the PCI configuration register.
+     * That means they are available but read and writes from/to them have no effect.
+     * No guest should access them anyway because the controller is marked as AHCI in the Programming interface
+     * and we don't have an option to change to IDE emulation (real hardware provides an option in the BIOS
+     * to switch to it which also changes device Id and other things in the PCI configuration space).
+     */
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 8, PCI_ADDRESS_SPACE_IO, ahciLegacyFakeIORangeMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI I/O region"));
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 1, PCI_ADDRESS_SPACE_IO, ahciLegacyFakeIORangeMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI I/O region"));
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 2, 8, PCI_ADDRESS_SPACE_IO, ahciLegacyFakeIORangeMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI I/O region"));
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 3, 1, PCI_ADDRESS_SPACE_IO, ahciLegacyFakeIORangeMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI I/O region"));
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 4, 0x10, PCI_ADDRESS_SPACE_IO, ahciLegacyFakeIORangeMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI I/O region for BMDMA"));
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 5, 4352, PCI_ADDRESS_SPACE_MEM, ahciMMIOMap);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("AHCI cannot register PCI memory region for registers"));
+
+    rc = PDMDevHlpCritSectInit(pDevIns, &pThis->lock, "AHCI");
+    if (RT_FAILURE(rc))
+    {
+        Log(("%s: Failed to create critical section.\n", __FUNCTION__));
+        return rc;
+    }
+
+    /* Create the timer for command completion coalescing feature. */
+    rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ahciCccTimer, pThis,
+                                TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "AHCI CCC Timer", &pThis->pHbaCccTimerR3);
+    if (RT_FAILURE(rc))
+    {
+        AssertMsgFailed(("pfnTMTimerCreate -> %Rrc\n", rc));
+        return rc;
+    }
+    pThis->pHbaCccTimerR0 = TMTimerR0Ptr(pThis->pHbaCccTimerR3);
+    pThis->pHbaCccTimerRC = TMTimerRCPtr(pThis->pHbaCccTimerR3);
+
+    /* Status LUN. */
+    pThis->IBase.pfnQueryInterface = ahciStatus_QueryInterface;
+    pThis->ILeds.pfnQueryStatusLed = ahciStatus_QueryStatusLed;
+
+    /*
+     * Create the transmit queue.
+     */
+    rc = PDMDevHlpPDMQueueCreate(pDevIns, sizeof(DEVPORTNOTIFIERQUEUEITEM), 30*32 /*Maximum of 30 ports multiplied with 32 tasks each port*/, 0,
+                                 ahciNotifyQueueConsumer, true, &pThis->pNotifierQueueR3);
+    if (RT_FAILURE(rc))
+        return rc;
+    pThis->pNotifierQueueR0 = PDMQueueR0Ptr(pThis->pNotifierQueueR3);
+    pThis->pNotifierQueueRC = PDMQueueRCPtr(pThis->pNotifierQueueR3);
+
+    /* Initialize static members on every port. */
+    for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+    {
+        /*
+         * Init members of the port.
+         */
+        PAHCIPort pAhciPort      = &pThis->ahciPort[i];
+        pAhciPort->pDevInsR3     = pDevIns;
+        pAhciPort->pDevInsR0     = PDMDEVINS_2_R0PTR(pDevIns);
+        pAhciPort->pDevInsRC     = PDMDEVINS_2_RCPTR(pDevIns);
+        pAhciPort->iLUN          = i;
+        pAhciPort->pAhciR3       = pThis;
+        pAhciPort->pAhciR0       = PDMINS_2_DATA_R0PTR(pDevIns);
+        pAhciPort->pAhciRC       = PDMINS_2_DATA_RCPTR(pDevIns);
+        pAhciPort->Led.u32Magic  = PDMLED_MAGIC;
+        pAhciPort->pDrvBase      = NULL;
+
+        /* Register statistics counter. */
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatDMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+                               "Number of DMA transfers.", "/Devices/SATA/Port%d/DMA", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+                               "Amount of data read.", "/Devices/SATA/Port%d/ReadBytes", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+                               "Amount of data written.", "/Devices/SATA/Port%d/WrittenBytes", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatIORequestsPerSecond, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+                               "Number of processed I/O requests per second.", "/Devices/SATA/Port%d/IORequestsPerSecond", i);
+#ifdef VBOX_WITH_STATISTICS
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileProcessTime, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_NS_PER_CALL,
+                               "Amount of time to process one request.", "/Devices/SATA/Port%d/ProfileProcessTime", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileMapIntoR3, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_NS_PER_CALL,
+                               "Amount of time to map the guest buffers into R3.", "/Devices/SATA/Port%d/ProfileMapIntoR3", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileReadWrite, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_NS_PER_CALL,
+                               "Amount of time for the read/write operation to complete.", "/Devices/SATA/Port%d/ProfileReadWrite", i);
+        PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileDestroyScatterGatherList, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_NS_PER_CALL,
+                               "Amount of time to destroy the scatter gather list and free associated ressources.", "/Devices/SATA/Port%d/ProfileDestroyScatterGatherList", i);
+#endif
+
+        ahciPortHwReset(pAhciPort);
+    }
+
+    /* Attach drivers to every available port. */
+    for (i = 0; i < pThis->cPortsImpl; i++)
+    {
+        char szName[24];
+        RTStrPrintf(szName, sizeof(szName), "Port%d", i);
+
+        PAHCIPort pAhciPort      = &pThis->ahciPort[i];
+        /*
+         * Init interfaces.
+         */
+        pAhciPort->IBase.pfnQueryInterface           = ahciPortQueryInterface;
+        pAhciPort->IPortAsync.pfnTransferCompleteNotify  = ahciTransferCompleteNotify;
+        pAhciPort->IMountNotify.pfnMountNotify       = ahciMountNotify;
+        pAhciPort->IMountNotify.pfnUnmountNotify     = ahciUnmountNotify;
+        pAhciPort->fAsyncIOThreadIdle                = true;
+
+        /*
+         * Attach the block driver
+         */
+        rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, szName);
+        if (RT_SUCCESS(rc))
+        {
+            rc = ahciConfigureLUN(pDevIns, pAhciPort);
+            if (RT_FAILURE(rc))
+            {
+                Log(("%s: Failed to configure the %s.\n", __FUNCTION__, szName));
+                return rc;
+            }
+
+            /* Mark that a device is present on that port */
+            if (i < 6)
+                pThis->dev.config[0x93] |= (1 << i);
+
+            /*
+             * Init vendor product data.
+             */
+            /* Generate a default serial number. */
+            char szSerial[AHCI_SERIAL_NUMBER_LENGTH+1];
+            RTUUID Uuid;
+            if (pAhciPort->pDrvBlock)
+                rc = pAhciPort->pDrvBlock->pfnGetUuid(pAhciPort->pDrvBlock, &Uuid);
+            else
+                RTUuidClear(&Uuid);
+
+            if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+            {
+                /* Generate a predictable serial for drives which don't have a UUID. */
+                RTStrPrintf(szSerial, sizeof(szSerial), "VB%x-1a2b3c4d",
+                            pAhciPort->iLUN);
+            }
+            else
+                RTStrPrintf(szSerial, sizeof(szSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+            /* Get user config if present using defaults otherwise. */
+            PCFGMNODE pCfgNode = CFGMR3GetChild(pCfgHandle, szName);
+            rc = CFGMR3QueryStringDef(pCfgNode, "SerialNumber", pAhciPort->szSerialNumber, sizeof(pAhciPort->szSerialNumber),
+                                      szSerial);
+            if (RT_FAILURE(rc))
+            {
+                if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+                    return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+                                N_("AHCI configuration error: \"SerialNumber\" is longer than 20 bytes"));
+                return PDMDEV_SET_ERROR(pDevIns, rc,
+                            N_("AHCI configuration error: failed to read \"SerialNumber\" as string"));
+            }
+
+            rc = CFGMR3QueryStringDef(pCfgNode, "FirmwareRevision", pAhciPort->szFirmwareRevision, sizeof(pAhciPort->szFirmwareRevision),
+                                      "1.0");
+            if (RT_FAILURE(rc))
+            {
+                if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+                    return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+                                N_("AHCI configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
+                return PDMDEV_SET_ERROR(pDevIns, rc,
+                            N_("AHCI configuration error: failed to read \"FirmwareRevision\" as string"));
+            }
+
+            rc = CFGMR3QueryStringDef(pCfgNode, "ModelNumber", pAhciPort->szModelNumber, sizeof(pAhciPort->szModelNumber),
+                                      "VBOX HARDDISK");
+            if (RT_FAILURE(rc))
+            {
+                if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+                    return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+                               N_("AHCI configuration error: \"ModelNumber\" is longer than 40 bytes"));
+                return PDMDEV_SET_ERROR(pDevIns, rc,
+                            N_("AHCI configuration error: failed to read \"ModelNumber\" as string"));
+            }
+
+            /*
+             * If the new async interface is available we use a PDMQueue to transmit
+             * the requests into R3.
+             * Otherwise we use a event semaphore and a async I/O thread which processes them.
+             */
+            if (pAhciPort->pDrvBlockAsync && pThis->fUseAsyncInterfaceIfAvailable)
+            {
+                LogRel(("AHCI: LUN#%d: using async I/O\n", pAhciPort->iLUN));
+                pAhciPort->fAsyncInterface = true;
+            }
+            else
+            {
+                LogRel(("AHCI: LUN#%d: using normal I/O\n", pAhciPort->iLUN));
+                pAhciPort->fAsyncInterface = false;
+
+                rc = RTSemEventCreate(&pAhciPort->AsyncIORequestSem);
+                AssertMsgRC(rc, ("Failed to create event semaphore for %s rc=%Rrc.\n", szName, rc));
+
+
+                rc = PDMDevHlpPDMThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
+                                              RTTHREADTYPE_IO, szName);
+                AssertMsgRC(rc, ("%s: Async IO Thread creation for %s failed rc=%Rrc\n", szName, rc));
+            }
+        }
+        else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+        {
+            pAhciPort->pDrvBase = NULL;
+            rc = VINF_SUCCESS;
+            LogRel(("%s: no driver attached\n", szName));
+        }
+        else
+            return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+                                       N_("AHCI: Failed to attach drive to %s"), szName);
+
+#ifdef DEBUG
+        for (uint32_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
+            pAhciPort->ahciIOTasks[i] = 0xff;
+#endif
+    }
+
+    /*
+     * Attach status driver (optional).
+     */
+    rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+    if (RT_SUCCESS(rc))
+        pThis->pLedsConnector = (PDMILEDCONNECTORS *)pBase->pfnQueryInterface(pBase, PDMINTERFACE_LED_CONNECTORS);
+    else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+    {
+        AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot attach to status driver"));
+    }
+
+    /*
+     * Setup IDE emulation.
+     * We only emulate the I/O ports but not bus master DMA.
+     * If the configuration values are not found the setup of the ports is as follows:
+     *     Primary Master:   Port 0
+     *     Primary Slave:    Port 1
+     *     Secondary Master: Port 2
+     *     Secondary Slave:  Port 3
+     */
+
+    /*
+     * Setup I/O ports for the PCI device.
+     */
+    pThis->aCts[0].irq          = 12;
+    pThis->aCts[0].IOPortBase1  = 0x1e8;
+    pThis->aCts[0].IOPortBase2  = 0x3e6;
+    pThis->aCts[1].irq          = 11;
+    pThis->aCts[1].IOPortBase1  = 0x168;
+    pThis->aCts[1].IOPortBase2  = 0x366;
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+    {
+        PAHCIATACONTROLLER pCtl = &pThis->aCts[i];
+        uint32_t iPortMaster, iPortSlave;
+        uint32_t cbSSMState = 0;
+        static const char *s_apszDescs[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+        {
+            { "PrimaryMaster", "PrimarySlave" },
+            { "SecondaryMaster", "SecondarySlave" }
+        };
+
+        rc = CFGMR3QueryU32Def(pCfgHandle, s_apszDescs[i][0], &iPortMaster, 2 * i);
+        if (RT_FAILURE(rc))
+            return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+                                       N_("AHCI configuration error: failed to read %s as U32"), s_apszDescs[i][0]);
+
+        rc = CFGMR3QueryU32Def(pCfgHandle, s_apszDescs[i][1], &iPortSlave, 2 * i + 1);
+        if (RT_FAILURE(rc))
+            return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+                                       N_("AHCI configuration error: failed to read %s as U32"), s_apszDescs[i][1]);
+
+        char szName[24];
+        RTStrPrintf(szName, sizeof(szName), "EmulatedATA%d", i);
+        rc = ataControllerInit(pDevIns, pCtl, pThis->ahciPort[iPortMaster].pDrvBase, pThis->ahciPort[iPortSlave].pDrvBase,
+                               &cbSSMState, szName, &pThis->ahciPort[iPortMaster].Led, &pThis->ahciPort[iPortMaster].StatBytesRead,
+                               &pThis->ahciPort[iPortMaster].StatBytesWritten);
+        if (RT_FAILURE(rc))
+            return rc;
+
+        cbTotalBufferSize += cbSSMState;
+
+        rc = PDMDevHlpIOPortRegister(pDevIns, pCtl->IOPortBase1, 8, (RTHCPTR)i,
+                                     ahciIOPortWrite1, ahciIOPortRead1, ahciIOPortWriteStr1, ahciIOPortReadStr1, "AHCI");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpIOPortRegisterR0(pDevIns, pCtl->IOPortBase1, 8, (RTR0PTR)i,
+                                           "ahciIOPortWrite1", "ahciIOPortRead1", NULL, NULL, "AHCI R0");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpIOPortRegisterGC(pDevIns, pCtl->IOPortBase1, 8, (RTGCPTR)i,
+                                            "ahciIOPortWrite1", "ahciIOPortRead1", NULL, NULL, "AHCI GC");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        rc = PDMDevHlpIOPortRegister(pDevIns, pCtl->IOPortBase2, 1, (RTHCPTR)i,
+                                     ahciIOPortWrite2, ahciIOPortRead2, NULL, NULL, "AHCI");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpIOPortRegisterR0(pDevIns, pCtl->IOPortBase2, 1, (RTR0PTR)i,
+                                            "ahciIOPortWrite2", "ahciIOPortRead2", NULL, NULL, "AHCI R0");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpIOPortRegisterGC(pDevIns, pCtl->IOPortBase2, 1, (RTGCPTR)i,
+                                            "ahciIOPortWrite2", "ahciIOPortRead2", NULL, NULL, "AHCI GC");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+    }
+
+    rc = PDMDevHlpSSMRegister(pDevIns, pDevIns->pDevReg->szDeviceName, iInstance,
+                              AHCI_SAVED_STATE_VERSION, sizeof(*pThis)+cbTotalBufferSize,
+                              ahciSavePrep, ahciSaveExec, NULL,
+                              ahciLoadPrep, ahciLoadExec, NULL);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    ahciReset(pDevIns);
+
+    return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceAHCI =
+{
+    /* u32Version */
+    PDM_DEVREG_VERSION,
+    /* szDeviceName */
+    "ahci",
+    /* szRCMod */
+    "VBoxDDGC.gc",
+    /* szR0Mod */
+    "VBoxDDR0.r0",
+    /* pszDescription */
+    "Intel AHCI controller.\n",
+    /* fFlags */
+    PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+    PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+    /* fClass */
+    PDM_DEVREG_CLASS_STORAGE,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(AHCI),
+    /* pfnConstruct */
+    ahciConstruct,
+    /* pfnDestruct */
+    ahciDestruct,
+    /* pfnRelocate */
+    ahciRelocate,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    ahciReset,
+    /* pfnSuspend */
+    ahciSuspend,
+    /* pfnResume */
+    ahciResume,
+    /* pfnAttach */
+    ahciAttach,
+    /* pfnDetach */
+    ahciDetach,
+    /* pfnQueryInterface. */
+    NULL,
+    /* pfnInitComplete */
+    NULL,
+    /* pfnPowerOff */
+    ahciPowerOff,
+    /* pfnSoftReset */
+    NULL,
+    /* u32VersionEnd */
+    PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
Index: trunk/src/VBox/Devices/Storage/DevBusLogic.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DevBusLogic.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/DevBusLogic.cpp	(revision 21321)
@@ -0,0 +1,2889 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * BusLogic SCSI host adapter BT-958
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/* Implemented looking at the driver source in the linux kernel (drivers/scsi/BusLogic.[ch]). */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC
+#include <VBox/pdmdev.h>
+#include <VBox/pdmifs.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/log.h>
+#ifdef IN_RING3
+# include <iprt/alloc.h>
+# include <iprt/cache.h>
+# include <iprt/param.h>
+#endif
+
+#include "VBoxSCSI.h"
+#include "../Builtins.h"
+
+/* Maximum number of attached devices the adapter can handle. */
+#define BUSLOGIC_MAX_DEVICES 16
+
+/* Maximum number of scatter gather elements this device can handle. */
+#define BUSLOGIC_MAX_SCATTER_GATHER_LIST_SIZE 128
+
+/* Size of the command buffer. */
+#define BUSLOGIC_COMMAND_SIZE_MAX 5
+
+/* Size of the reply buffer. */
+#define BUSLOGIC_REPLY_SIZE_MAX 64
+
+/* I/O port registered in the ISA compatible range to let the BIOS access
+ * the controller.
+ */
+#define BUSLOGIC_ISA_IO_PORT 0x330
+
+/** State saved version. */
+#define BUSLOGIC_SAVED_STATE_MINOR_VERSION 1
+
+/*
+ * State of a device attached to the buslogic host adapter.
+ */
+typedef struct BUSLOGICDEVICE
+{
+    /** Pointer to the owning buslogic device instance. - R3 pointer */
+    R3PTRTYPE(struct BUSLOGIC *)  pBusLogicR3;
+    /** Pointer to the owning buslogic device instance. - R0 pointer */
+    R0PTRTYPE(struct BUSLOGIC *)  pBusLogicR0;
+    /** Pointer to the owning buslogic device instance. - RC pointer */
+    RCPTRTYPE(struct BUSLOGIC *)  pBusLogicRC;
+
+    /** Flag whether device is present. */
+    bool                          fPresent;
+    /** LUN of the device. */
+    RTUINT                        iLUN;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                      Alignment0;
+#endif
+
+    /** Our base interace. */
+    PDMIBASE                      IBase;
+    /** SCSI port interface. */
+    PDMISCSIPORT                  ISCSIPort;
+    /** Led interface. */
+    PDMILEDPORTS                  ILed;
+    /** Pointer to the attached driver's base interface. */
+    R3PTRTYPE(PPDMIBASE)          pDrvBase;
+    /** Pointer to the underlying SCSI connector interface. */
+    R3PTRTYPE(PPDMISCSICONNECTOR) pDrvSCSIConnector;
+    /** The status LED state for this device. */
+    PDMLED                        Led;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                      Alignment1;
+#endif
+
+    /** Number of outstanding tasks on the port. */
+    volatile uint32_t             cOutstandingRequests;
+
+} BUSLOGICDEVICE, *PBUSLOGICDEVICE;
+
+/*
+ * Commands the BusLogic adapter supports.
+ */
+enum BUSLOGICCOMMAND
+{
+    BUSLOGICCOMMAND_TEST_COMMAND_COMPLETE_INTERRUPT = 0x00,
+    BUSLOGICCOMMAND_INITIALIZE_MAILBOX = 0x01,
+    BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND = 0x02,
+    BUSLOGICCOMMAND_EXECUTE_BIOS_COMMAND = 0x03,
+    BUSLOGICCOMMAND_INQUIRE_BOARD_ID = 0x04,
+    BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT = 0x05,
+    BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT = 0x06,
+    BUSLOGICCOMMAND_SET_PREEMPT_TIME_ON_BUS = 0x07,
+    BUSLOGICCOMMAND_SET_TIME_OFF_BUS = 0x08,
+    BUSLOGICCOMMAND_SET_BUS_TRANSFER_RATE = 0x09,
+    BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_0_TO_7 = 0x0a,
+    BUSLOGICCOMMAND_INQUIRE_CONFIGURATION = 0x0b,
+    BUSLOGICCOMMAND_ENABLE_TARGET_MODE = 0x0c,
+    BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION = 0x0d,
+    BUSLOGICCOMMAND_WRITE_ADAPTER_LOCAL_RAM = 0x1a,
+    BUSLOGICCOMMAND_READ_ADAPTER_LOCAL_RAM = 0x1b,
+    BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO = 0x1c,
+    BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO = 0x1d,
+    BUSLOGICCOMMAND_ECHO_COMMAND_DATA = 0x1f,
+    BUSLOGICCOMMAND_HOST_ADAPTER_DIAGNOSTIC = 0x20,
+    BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS = 0x21,
+    BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_8_TO_15 = 0x23,
+    BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES = 0x24,
+    BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT = 0x25,
+    BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX = 0x81,
+    BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND = 0x83,
+    BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER = 0x84,
+    BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER = 0x85,
+    BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION = 0x86,
+    BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER = 0x8b,
+    BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD = 0x8c,
+    BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION = 0x8d,
+    BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE = 0x8f,
+    BUSLOGICCOMMAND_STORE_HOST_ADAPTER_LOCAL_RAM = 0x90,
+    BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM = 0x91,
+    BUSLOGICCOMMAND_STORE_LOCAL_DATA_IN_EEPROM = 0x92,
+    BUSLOGICCOMMAND_UPLOAD_AUTO_SCSI_CODE = 0x94,
+    BUSLOGICCOMMAND_MODIFY_IO_ADDRESS = 0x95,
+    BUSLOGICCOMMAND_SET_CCB_FORMAT = 0x96,
+    BUSLOGICCOMMAND_WRITE_INQUIRY_BUFFER = 0x9a,
+    BUSLOGICCOMMAND_READ_INQUIRY_BUFFER = 0x9b,
+    BUSLOGICCOMMAND_FLASH_ROM_UPLOAD_DOWNLOAD = 0xa7,
+    BUSLOGICCOMMAND_READ_SCAM_DATA = 0xa8,
+    BUSLOGICCOMMAND_WRITE_SCAM_DATA = 0xa9
+} BUSLOGICCOMMAND;
+
+#pragma pack(1)
+/**
+ * Auto SCSI structure which is located
+ * in host adapter RAM and contains several
+ * configuration parameters.
+ */
+typedef struct AutoSCSIRam
+{
+    uint8_t       aInternalSignature[2];
+    uint8_t       cbInformation;
+    uint8_t       aHostAdaptertype[6];
+    uint8_t       uReserved1;
+    bool          fFloppyEnabled:           1;
+    bool          fFloppySecondary:         1;
+    bool          fLevelSensitiveInterrupt: 1;
+    unsigned char uReserved2:               2;
+    unsigned char uSystemRAMAreForBIOS:     3;
+    unsigned char uDMAChannel:              7;
+    bool          fDMAAutoConfiguration:    1;
+    unsigned char uIrqChannel:              7;
+    bool          fIrqAutoConfiguration:    1;
+    uint8_t       uDMATransferRate;
+    uint8_t       uSCSIId;
+    bool          fLowByteTerminated:       1;
+    bool          fParityCheckingEnabled:   1;
+    bool          fHighByteTerminated:      1;
+    bool          fNoisyCablingEnvironment: 1;
+    bool          fFastSynchronousNeogtiation: 1;
+    bool          fBusResetEnabled:            1;
+    bool          fReserved3:                  1;
+    bool          fActiveNegotiationEnabled:   1;
+    uint8_t       uBusOnDelay;
+    uint8_t       uBusOffDelay;
+    bool          fHostAdapterBIOSEnabled:     1;
+    bool          fBIOSRedirectionOfInt19:     1;
+    bool          fExtendedTranslation:        1;
+    bool          fMapRemovableAsFixed:        1;
+    bool          fReserved4:                  1;
+    bool          fBIOSSupportsMoreThan2Drives: 1;
+    bool          fBIOSInterruptMode:           1;
+    bool          fFlopticalSupport:            1;
+    uint16_t      u16DeviceEnabledMask;
+    uint16_t      u16WidePermittedMask;
+    uint16_t      u16FastPermittedMask;
+    uint16_t      u16SynchronousPermittedMask;
+    uint16_t      u16DisconnectPermittedMask;
+    uint16_t      u16SendStartUnitCommandMask;
+    uint16_t      u16IgnoreInBIOSScanMask;
+    unsigned char uPCIInterruptPin:             2;
+    unsigned char uHostAdapterIoPortAddress:    2;
+    bool          fStrictRoundRobinMode:        1;
+    bool          fVesaBusSpeedGreaterThan33MHz: 1;
+    bool          fVesaBurstWrite:               1;
+    bool          fVesaBurstRead:                1;
+    uint16_t      u16UltraPermittedMask;
+    uint32_t      uReserved5;
+    uint8_t       uReserved6;
+    uint8_t       uAutoSCSIMaximumLUN;
+    bool          fReserved7:                    1;
+    bool          fSCAMDominant:                 1;
+    bool          fSCAMenabled:                  1;
+    bool          fSCAMLevel2:                   1;
+    unsigned char uReserved8:                    4;
+    bool          fInt13Extension:               1;
+    bool          fReserved9:                    1;
+    bool          fCDROMBoot:                    1;
+    unsigned char uReserved10:                   5;
+    unsigned char uBootTargetId:                 4;
+    unsigned char uBootChannel:                  4;
+    bool          fForceBusDeviceScanningOrder:  1;
+    unsigned char uReserved11:                   7;
+    uint16_t      u16NonTaggedToAlternateLunPermittedMask;
+    uint16_t      u16RenegotiateSyncAfterCheckConditionMask;
+    uint8_t       aReserved12[10];
+    uint8_t       aManufacturingDiagnostic[2];
+    uint16_t      u16Checksum;
+} AutoSCSIRam, *PAutoSCSIRam;
+AssertCompileSize(AutoSCSIRam, 64);
+#pragma pack()
+
+#pragma pack(1)
+/**
+ * The local Ram.
+ */
+typedef union HostAdapterLocalRam
+{
+    /* Byte view. */
+    uint8_t u8View[256];
+    /* Structured view. */
+    struct
+    {
+        /** Offset 0 - 63 is for BIOS. */
+        uint8_t     u8Bios[64];
+        /** Auto SCSI structure. */
+        AutoSCSIRam autoSCSIData;
+    } structured;
+} HostAdapterLocalRam, *PHostAdapterLocalRam;
+AssertCompileSize(HostAdapterLocalRam, 256);
+#pragma pack()
+
+/*
+ * Main BusLogic device state.
+ */
+typedef struct BUSLOGIC
+{
+    /** The PCI device structure. */
+    PCIDEVICE                       dev;
+    /** Pointer to the device instance - HC ptr */
+    PPDMDEVINSR3                    pDevInsR3;
+    /** Pointer to the device instance - R0 ptr */
+    PPDMDEVINSR0                    pDevInsR0;
+    /** Pointer to the device instance - RC ptr. */
+    PPDMDEVINSRC                    pDevInsRC;
+
+    /* Whether R0 is enabled. */
+    bool                            fR0Enabled;
+    /** Whether GC is enabled. */
+    bool                            fGCEnabled;
+
+    /** Base address of the I/O ports. */
+    RTIOPORT                        IOPortBase;
+    /** Base address of the memory mapping. */
+    RTGCPHYS                        MMIOBase;
+    /** Status register - Readonly. */
+    volatile uint8_t                regStatus;
+    /** Interrupt register - Readonly. */
+    volatile uint8_t                regInterrupt;
+    /** Geometry register - Readonly. */
+    volatile uint8_t                regGeometry;
+
+    /** Local RAM for the fetch hostadapter local RAM request.
+     *  I don't know how big the buffer really is but the maximum
+     *  seems to be 256 bytes because the offset and count field in the command request
+     *  are only one byte big.
+     */
+    HostAdapterLocalRam             LocalRam;
+
+    /** Command code the guest issued. */
+    uint8_t                         uOperationCode;
+    /** Buffer for the command parameters the adapter is currently receiving from the guest.
+     *  Size of the largest command which is possible.
+     */
+    uint8_t                         aCommandBuffer[BUSLOGIC_COMMAND_SIZE_MAX]; /* Size of the biggest request. */
+    /** Current position in the command buffer. */
+    uint8_t                         iParameter;
+    /** Parameters left until the command is complete. */
+    uint8_t                         cbCommandParametersLeft;
+
+    /** Whether we are using the RAM or reply buffer. */
+    bool                            fUseLocalRam;
+    /** Buffer to store reply data from the controller to the guest. */
+    uint8_t                         aReplyBuffer[BUSLOGIC_REPLY_SIZE_MAX]; /* Size of the biggest reply. */
+    /** Position in the buffer we are reading next. */
+    uint8_t                         iReply;
+    /** Bytes left until the reply buffer is empty. */
+    uint8_t                         cbReplyParametersLeft;
+
+    /** Flag whether IRQs are enabled. */
+    bool                            fIRQEnabled;
+    /** Flag whether the ISA I/O port range is disabled
+     * to prevent the BIOs to access the device. */
+    bool                            fISAEnabled;
+
+    /** Number of mailboxes the guest set up. */
+    uint32_t                        cMailbox;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment0;
+#endif
+
+    /** Physical base address of the outgoing mailboxes. */
+    RTGCPHYS                        GCPhysAddrMailboxOutgoingBase;
+    /** Current outgoing mailbox position. */
+    uint32_t                        uMailboxOutgoingPositionCurrent;
+    /** Number of mailboxes ready. */
+    volatile uint32_t               cMailboxesReady;
+    /** Whether a notification to R3 was send. */
+    volatile bool                   fNotificationSend;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment1;
+#endif
+
+    /** Physical base address of the incoming mailboxes. */
+    RTGCPHYS                        GCPhysAddrMailboxIncomingBase;
+    /** Current incoming mailbox position. */
+    uint32_t                        uMailboxIncomingPositionCurrent;
+
+    /** Whether strict round robin is enabled. */
+    bool                            fStrictRoundRobinMode;
+    /** Whether the extended LUN CCB format is enabled for 32 possible logical units. */
+    bool                            fExtendedLunCCBFormat;
+
+    /** Queue to send tasks to R3. - HC ptr */
+    R3PTRTYPE(PPDMQUEUE)            pNotifierQueueR3;
+    /** Queue to send tasks to R3. - HC ptr */
+    R0PTRTYPE(PPDMQUEUE)            pNotifierQueueR0;
+    /** Queue to send tasks to R3. - RC ptr */
+    RCPTRTYPE(PPDMQUEUE)            pNotifierQueueRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                        Alignment2;
+#endif
+
+    /** Cache for task states. */
+    R3PTRTYPE(PRTOBJCACHE)          pTaskCache;
+
+    /** Device state for BIOS access. */
+    VBOXSCSI                        VBoxSCSI;
+
+    /** BusLogic device states. */
+    BUSLOGICDEVICE                  aDeviceStates[BUSLOGIC_MAX_DEVICES];
+
+    /** The base interface */
+    PDMIBASE                       IBase;
+    /** Status Port - Leds interface. */
+    PDMILEDPORTS                   ILeds;
+    /** Partner of ILeds. */
+    R3PTRTYPE(PPDMILEDCONNECTORS)  pLedsConnector;
+} BUSLOGIC, *PBUSLOGIC;
+
+/** Register offsets in the I/O port space. */
+#define BUSLOGIC_REGISTER_CONTROL   0 /* Writeonly */
+/** Fields for the control register. */
+# define BUSLOGIC_REGISTER_CONTROL_SCSI_BUSRESET   RT_BIT(4)
+# define BUSLOGIC_REGISTER_CONTROL_INTERRUPT_RESET RT_BIT(5)
+# define BUSLOGIC_REGISTER_CONTROL_SOFT_RESET      RT_BIT(6)
+# define BUSLOGIC_REGISTER_CONTROL_HARD_RESET      RT_BIT(7)
+
+#define BUSLOGIC_REGISTER_STATUS    0 /* Readonly */
+/** Fields for the status register. */
+# define BUSLOGIC_REGISTER_STATUS_COMMAND_INVALID                 RT_BIT(0)
+# define BUSLOGIC_REGISTER_STATUS_DATA_IN_REGISTER_READY          RT_BIT(2)
+# define BUSLOGIC_REGISTER_STATUS_COMMAND_PARAMETER_REGISTER_BUSY RT_BIT(3)
+# define BUSLOGIC_REGISTER_STATUS_HOST_ADAPTER_READY              RT_BIT(4)
+# define BUSLOGIC_REGISTER_STATUS_INITIALIZATION_REQUIRED         RT_BIT(5)
+# define BUSLOGIC_REGISTER_STATUS_DIAGNOSTIC_FAILURE              RT_BIT(6)
+# define BUSLOGIC_REGISTER_STATUS_DIAGNOSTIC_ACTIVE               RT_BIT(7)
+
+#define BUSLOGIC_REGISTER_COMMAND   1 /* Writeonly */
+#define BUSLOGIC_REGISTER_DATAIN    1 /* Readonly */
+#define BUSLOGIC_REGISTER_INTERRUPT 2 /* Readonly */
+/** Fields for the interrupt register. */
+# define BUSLOGIC_REGISTER_INTERRUPT_INCOMING_MAILBOX_LOADED      RT_BIT(0)
+# define BUSLOGIC_REGISTER_INTERRUPT_OUTCOMING_MAILBOX_AVAILABLE  RT_BIT(1)
+# define BUSLOGIC_REGISTER_INTERRUPT_COMMAND_COMPLETE             RT_BIT(2)
+# define BUSLOGIC_REGISTER_INTERRUPT_EXTERNAL_BUS_RESET           RT_BIT(3)
+# define BUSLOGIC_REGISTER_INTERRUPT_INTERRUPT_VALID              RT_BIT(7)
+
+#define BUSLOGIC_REGISTER_GEOMETRY  3 /* Readonly */
+# define BUSLOGIC_REGISTER_GEOMETRY_EXTENTED_TRANSLATION_ENABLED  RT_BIT(7)
+
+/* Structure for the INQUIRE_PCI_HOST_ADAPTER_INFORMATION reply. */
+#pragma pack(1)
+typedef struct ReplyInquirePCIHostAdapterInformation
+{
+    uint8_t       IsaIOPort;
+    uint8_t       IRQ;
+    unsigned char LowByteTerminated:1;
+    unsigned char HighByteTerminated:1;
+    unsigned char uReserved:2; /* Reserved. */
+    unsigned char JP1:1; /* Whatever that means. */
+    unsigned char JP2:1; /* Whatever that means. */
+    unsigned char JP3:1; /* Whatever that means. */
+    /** Whether the provided info is valid. */
+    unsigned char InformationIsValid: 1;
+    uint8_t       uReserved2; /* Reserved. */
+} ReplyInquirePCIHostAdapterInformation, *PReplyInquirePCIHostAdapterInformation;
+AssertCompileSize(ReplyInquirePCIHostAdapterInformation, 4);
+#pragma pack()
+
+/* Structure for the INQUIRE_CONFIGURATION reply. */
+#pragma pack(1)
+typedef struct ReplyInquireConfiguration
+{
+    unsigned char uReserved1:     5;
+    bool          fDmaChannel5:   1;
+    bool          fDmaChannel6:   1;
+    bool          fDmaChannel7:   1;
+    bool          fIrqChannel9:   1;
+    bool          fIrqChannel10:  1;
+    bool          fIrqChannel11:  1;
+    bool          fIrqChannel12:  1;
+    unsigned char uReserved2:     1;
+    bool          fIrqChannel14:  1;
+    bool          fIrqChannel15:  1;
+    unsigned char uReserved3:     1;
+    unsigned char uHostAdapterId: 4;
+    unsigned char uReserved4:     4;
+} ReplyInquireConfiguration, *PReplyInquireConfiguration;
+AssertCompileSize(ReplyInquireConfiguration, 3);
+#pragma pack()
+
+/* Structure for the INQUIRE_SETUP_INFORMATION reply. */
+#pragma pack(1)
+typedef struct ReplyInquireSetupInformationSynchronousValue
+{
+    unsigned char uOffset:         4;
+    unsigned char uTransferPeriod: 3;
+    bool fSynchronous:             1;
+}ReplyInquireSetupInformationSynchronousValue, *PReplyInquireSetupInformationSynchronousValue;
+AssertCompileSize(ReplyInquireSetupInformationSynchronousValue, 1);
+#pragma pack()
+
+#pragma pack(1)
+typedef struct ReplyInquireSetupInformation
+{
+    bool fSynchronousInitiationEnabled: 1;
+    bool fParityCheckingEnabled:        1;
+    unsigned char uReserved1:           6;
+    uint8_t uBusTransferRate;
+    uint8_t uPreemptTimeOnBus;
+    uint8_t uTimeOffBus;
+    uint8_t cMailbox;
+    uint8_t MailboxAddress[3];
+    ReplyInquireSetupInformationSynchronousValue SynchronousValuesId0To7[8];
+    uint8_t uDisconnectPermittedId0To7;
+    uint8_t uSignature;
+    uint8_t uCharacterD;
+    uint8_t uHostBusType;
+    uint8_t uWideTransferPermittedId0To7;
+    uint8_t uWideTransfersActiveId0To7;
+    ReplyInquireSetupInformationSynchronousValue SynchronousValuesId8To15[8];
+    uint8_t uDisconnectPermittedId8To15;
+    uint8_t uReserved2;
+    uint8_t uWideTransferPermittedId8To15;
+    uint8_t uWideTransfersActiveId8To15;
+} ReplyInquireSetupInformation, *PReplyInquireSetupInformation;
+AssertCompileSize(ReplyInquireSetupInformation, 34);
+#pragma pack()
+
+/* Structure for the INQUIRE_EXTENDED_SETUP_INFORMATION. */
+#pragma pack(1)
+typedef struct ReplyInquireExtendedSetupInformation
+{
+    uint8_t       uBusType;
+    uint8_t       uBiosAddress;
+    uint16_t      u16ScatterGatherLimit;
+    uint8_t       cMailbox;
+    uint32_t      uMailboxAddressBase;
+    unsigned char uReserved1: 2;
+    bool          fFastEISA:  1;
+    unsigned char uReserved2: 3;
+    bool          fLevelSensitiveInterrupt: 1;
+    unsigned char uReserved3: 1;
+    unsigned char aFirmwareRevision[3];
+    bool          fHostWideSCSI: 1;
+    bool          fHostDifferentialSCSI: 1;
+    bool          fHostSupportsSCAM: 1;
+    bool          fHostUltraSCSI: 1;
+    bool          fHostSmartTermination: 1;
+    unsigned char uReserved4: 3;
+} ReplyInquireExtendedSetupInformation, *PReplyInquireExtendedSetupInformation;
+AssertCompileSize(ReplyInquireExtendedSetupInformation, 14);
+#pragma pack()
+
+/* Structure for the INITIALIZE EXTENDED MAILBOX request. */
+#pragma pack(1)
+typedef struct RequestInitializeExtendedMailbox
+{
+    /** Number of mailboxes in guest memory. */
+    uint8_t  cMailbox;
+    /** Physical address of the first mailbox. */
+    uint32_t uMailboxBaseAddress;
+} RequestInitializeExtendedMailbox, *PRequestInitializeExtendedMailbox;
+AssertCompileSize(RequestInitializeExtendedMailbox, 5);
+#pragma pack()
+
+/*
+ * Structure of a mailbox in guest memory.
+ * The incoming and outgoing mailbox have the same size
+ * but the incoming one has some more fields defined which
+ * are marked as reserved in the outgoing one.
+ * The last field is also different from the type.
+ * For outgoing mailboxes it is the action and
+ * for incoming ones the completion status code for the task.
+ * We use one structure for both types.
+ */
+#pragma pack(1)
+typedef struct Mailbox
+{
+    /** Physical adress of the CCB structure in the guest memory. */
+    uint32_t u32PhysAddrCCB;
+    /** Type specific data. */
+    union
+    {
+        /** For outgoing mailboxes. */
+        struct
+        {
+            /** Reserved */
+            uint8_t uReserved[3];
+            /** Action code. */
+            uint8_t uActionCode;
+        } out;
+        /** For incoming mailboxes. */
+        struct
+        {
+            /** The host adapter status after finishing the request. */
+            uint8_t  uHostAdapterStatus;
+            /** The status of the device which executed the request after executing it. */
+            uint8_t  uTargetDeviceStatus;
+            /** Reserved. */
+            uint8_t  uReserved;
+            /** The completion status code of the request. */
+            uint8_t uCompletionCode;
+        } in;
+    } u;
+} Mailbox, *PMailbox;
+AssertCompileSize(Mailbox, 8);
+#pragma pack()
+
+/*
+ * Action codes for outgoing mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_OUTGOING_ACTION
+{
+    BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE = 0x00,
+    BUSLOGIC_MAILBOX_OUTGOING_ACTION_START_COMMAND = 0x01,
+    BUSLOGIC_MAILBOX_OUTGOING_ACTION_ABORT_COMMAND = 0x02
+};
+
+/*
+ * Completion codes for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_COMPLETION
+{
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_FREE = 0x00,
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITHOUT_ERROR = 0x01,
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED = 0x02,
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED_NOT_FOUND = 0x03,
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR = 0x04,
+    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_INVALID_CCB = 0x05
+};
+
+/*
+ * Host adapter status for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS
+{
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_CMD_COMPLETED = 0x00,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CMD_COMPLETED = 0x0a,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CMD_COMPLETED_WITH_FLAG = 0x0b,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_DATA_UNDERUN = 0x0c,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_SELECTION_TIMEOUT = 0x11,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_DATA_OVERRUN = 0x12,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_UNEXPECTED_BUS_FREE = 0x13,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_BUS_PHASE_REQUESTED = 0x14,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_OUTGOING_MAILBOX_ACTION_CODE = 0x15,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_OPERATION_CODE = 0x16,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CCB_HAS_INVALID_LUN = 0x17,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_PARAMETER = 0x1a,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_AUTO_REQUEST_SENSE_FAILED = 0x1b,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TAGGED_QUEUING_MESSAGE_REJECTED = 0x1c,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_UNSUPPORTED_MESSAGE_RECEIVED = 0x1d,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_HARDWARE_FAILED = 0x20,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TARGET_FAILED_RESPONSE_TO_ATN = 0x21,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_ASSERTED_RST = 0x22,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_OTHER_DEVICE_ASSERTED_RST = 0x23,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TARGET_DEVICE_RECONNECTED_IMPROPERLY = 0x24,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_ASSERTED_BUS_DEVICE_RESET = 0x25,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_ABORT_QUEUE_GENERATED = 0x26,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_SOFTWARE_ERROR = 0x27,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_HARDWARE_TIMEOUT_ERROR = 0x30,
+    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_PARITY_ERROR_DETECTED = 0x34
+};
+
+/*
+ * Device status codes for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS
+{
+    BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD = 0x00,
+    BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_CHECK_CONDITION = 0x02,
+    BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_DEVICE_BUSY = 0x08
+};
+
+/*
+ * Opcode types for CCB.
+ */
+enum BUSLOGIC_CCB_OPCODE
+{
+    BUSLOGIC_CCB_OPCODE_INITIATOR_CCB = 0x00,
+    BUSLOGIC_CCB_OPCODE_TARGET_CCB = 0x01,
+    BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_SCATTER_GATHER = 0x02,
+    BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_DATA_LENGTH = 0x03,
+    BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER = 0x04,
+    BUSLOGIC_CCB_OPCODE_BUS_DEVICE_RESET = 0x81
+};
+
+/*
+ * Data transfer direction.
+ */
+enum BUSLOGIC_CCB_DIRECTION
+{
+    BUSLOGIC_CCB_DIRECTION_UNKNOWN = 0x00,
+    BUSLOGIC_CCB_DIRECTION_IN      = 0x01,
+    BUSLOGIC_CCB_DIRECTION_OUT     = 0x02,
+    BUSLOGIC_CCB_DIRECTION_NO_DATA = 0x03
+};
+
+/*
+ * The command control block for a SCSI request.
+ */
+#pragma pack(1)
+typedef struct CommandControlBlock
+{
+    /** Opcode. */
+    uint8_t       uOpcode;
+    /** Reserved */
+    unsigned char uReserved1:       3;
+    /** Data direction for the request. */
+    unsigned char uDataDirection:   2;
+    /** Whether the request is tag queued. */
+    bool          fTagQueued:       1;
+    /** Queue tag mode. */
+    unsigned char uQueueTag:        2;
+    /** Length of the SCSI CDB. */
+    uint8_t       cbCDB;
+    /** Sense data length. */
+    uint8_t       cbSenseData;
+    /** Data length. */
+    uint32_t      cbData;
+    /** Data pointer.
+     *  This points to the data region or a scatter gather list based on the opcode.
+     */
+    uint32_t      u32PhysAddrData;
+    /** Reserved. */
+    uint8_t       uReserved2[2];
+    /** Host adapter status. */
+    uint8_t       uHostAdapterStatus;
+    /** Device adapter status. */
+    uint8_t       uDeviceStatus;
+    /** The device the request is send to. */
+    uint8_t       uTargetId;
+    /**The LUN in the device. */
+    unsigned char uLogicalUnit:     5;
+    /** Legacy tag. */
+    bool          fLegacyTagEnable: 1;
+    /** Legacy queue tag. */
+    unsigned char uLegacyQueueTag:  2;
+    /** The SCSI CDB. */
+    uint8_t       aCDB[12]; /* A CDB can be 12 bytes long. */
+    /** Reserved. */
+    uint8_t       uReserved3[6];
+    /** Sense data pointer. */
+    uint32_t      u32PhysAddrSenseData;
+} CommandControlBlock, *PCommandControlBlock;
+AssertCompileSize(CommandControlBlock, 40);
+#pragma pack()
+
+#pragma pack(1)
+typedef struct ScatterGatherEntry
+{
+    uint32_t   cbSegment;
+    uint32_t   u32PhysAddrSegmentBase;
+} ScatterGatherEntry, *PScatterGatherEntry;
+AssertCompileSize(ScatterGatherEntry, 8);
+#pragma pack()
+
+/*
+ * Task state for a CCB request.
+ */
+typedef struct BUSLOGICTASKSTATE
+{
+    /** Device this task is assigned to. */
+    R3PTRTYPE(PBUSLOGICDEVICE)     pTargetDeviceR3;
+    /** The command control block from the guest. */
+    CommandControlBlock CommandControlBlockGuest;
+    /** Mailbox read from guest memory. */
+    Mailbox             MailboxGuest;
+    /** The SCSI request we pass to the underlying SCSI engine. */
+    PDMSCSIREQUEST      PDMScsiRequest;
+    /** Number of bytes in all scatter gather entries. */
+    uint32_t            cbScatterGather;
+    /** Number of entries in the scatter gather list. */
+    uint32_t            cScatterGather;
+    /** Page map lock array. */
+    PPGMPAGEMAPLOCK     paPageLock;
+    /** Pointer to the scatter gather array. */
+    PPDMDATASEG         paScatterGather;
+    /** Pointer to the page map lock for the sense buffer. */
+    PGMPAGEMAPLOCK      pPageLockSense;
+    /** Pointer to the R3 sense buffer. */
+    uint8_t            *pu8SenseBuffer;
+    /** Flag whether this is a request from the BIOS. */
+    bool                fBIOS;
+} BUSLOGICTASKSTATE, *PBUSLOGICTASKSTATE;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+RT_C_DECLS_BEGIN
+PDMBOTHCBDECL(int) buslogicIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                        RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) buslogicIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                       RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) buslogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                     RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+PDMBOTHCBDECL(int) buslogicMMIORead(PPDMDEVINS pDevIns, void *pvUser,
+                                    RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+RT_C_DECLS_END
+
+#define PDMIBASE_2_PBUSLOGICDEVICE(pInterface)     ( (PBUSLOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(BUSLOGICDEVICE, IBase)) )
+#define PDMISCSIPORT_2_PBUSLOGICDEVICE(pInterface) ( (PBUSLOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(BUSLOGICDEVICE, ISCSIPort)) )
+#define PDMILEDPORTS_2_PBUSLOGICDEVICE(pInterface) ( (PBUSLOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(BUSLOGICDEVICE, ILed)) )
+#define PDMIBASE_2_PBUSLOGIC(pInterface)           ( (PBUSLOGIC)((uintptr_t)(pInterface) - RT_OFFSETOF(BUSLOGIC, IBase)) )
+#define PDMILEDPORTS_2_PBUSLOGIC(pInterface)       ( (PBUSLOGIC)((uintptr_t)(pInterface) - RT_OFFSETOF(BUSLOGIC, ILeds)) )
+
+/**
+ * Deasserts the interrupt line of the BusLogic adapter.
+ *
+ * @returns nothing
+ * @param   pBuslogic  Pointer to the BusLogic device instance.
+ */
+static void buslogicClearInterrupt(PBUSLOGIC pBusLogic)
+{
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+    pBusLogic->regInterrupt = 0;
+    PDMDevHlpPCISetIrqNoWait(pBusLogic->CTX_SUFF(pDevIns), 0, 0);
+}
+
+/**
+ * Assert IRQ line of the BusLogic adapter.
+ *
+ * @returns nothing.
+ * @param   pBusLogic  Pointer to the BusLogic device instance.
+ */
+static void buslogicSetInterrupt(PBUSLOGIC pBusLogic)
+{
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+    pBusLogic->regInterrupt |= BUSLOGIC_REGISTER_INTERRUPT_INTERRUPT_VALID;
+    PDMDevHlpPCISetIrqNoWait(pBusLogic->CTX_SUFF(pDevIns), 0, 1);
+}
+
+#if defined(IN_RING3)
+/**
+ * Initialize local RAM of host adapter with default values.
+ *
+ * @returns nothing.
+ * @param   pBusLogic.
+ */
+static void buslogicInitializeLocalRam(PBUSLOGIC pBusLogic)
+{
+    /*
+     * These values are mostly from what I think is right
+     * looking at the dmesg output from a Linux guest inside
+     * a VMware server VM.
+     *
+     * So they don't have to be right :)
+     */
+    memset(pBusLogic->LocalRam.u8View, 0, sizeof(HostAdapterLocalRam));
+    pBusLogic->LocalRam.structured.autoSCSIData.fLevelSensitiveInterrupt = true;
+    pBusLogic->LocalRam.structured.autoSCSIData.fParityCheckingEnabled = true;
+    pBusLogic->LocalRam.structured.autoSCSIData.fExtendedTranslation = true; /* Same as in geometry register. */
+    pBusLogic->LocalRam.structured.autoSCSIData.u16DeviceEnabledMask = ~0; /* All enabled. Maybe mask out non present devices? */
+    pBusLogic->LocalRam.structured.autoSCSIData.u16WidePermittedMask = ~0;
+    pBusLogic->LocalRam.structured.autoSCSIData.u16FastPermittedMask = ~0;
+    pBusLogic->LocalRam.structured.autoSCSIData.u16SynchronousPermittedMask = ~0;
+    pBusLogic->LocalRam.structured.autoSCSIData.u16DisconnectPermittedMask = ~0;
+    pBusLogic->LocalRam.structured.autoSCSIData.fStrictRoundRobinMode = pBusLogic->fStrictRoundRobinMode;
+    pBusLogic->LocalRam.structured.autoSCSIData.u16UltraPermittedMask = ~0;
+    /* @todo calculate checksum? */
+}
+
+/**
+ * Do a hardware reset of the buslogic adapter.
+ *
+ * @returns VBox status code.
+ * @param   pBusLogic Pointer to the BusLogic device instance.
+ */
+static int buslogicHwReset(PBUSLOGIC pBusLogic)
+{
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+
+    /* Reset registers to default value. */
+    pBusLogic->regStatus = BUSLOGIC_REGISTER_STATUS_HOST_ADAPTER_READY;
+    pBusLogic->regInterrupt = 0;
+    pBusLogic->regGeometry = BUSLOGIC_REGISTER_GEOMETRY_EXTENTED_TRANSLATION_ENABLED;
+    pBusLogic->uOperationCode = 0xff; /* No command executing. */
+    pBusLogic->iParameter = 0;
+    pBusLogic->cbCommandParametersLeft = 0;
+    pBusLogic->fIRQEnabled = true;
+    pBusLogic->fISAEnabled = true;
+    pBusLogic->uMailboxOutgoingPositionCurrent = 0;
+    pBusLogic->uMailboxIncomingPositionCurrent = 0;
+
+    buslogicInitializeLocalRam(pBusLogic);
+    vboxscsiInitialize(&pBusLogic->VBoxSCSI);
+
+    return VINF_SUCCESS;
+}
+#endif
+
+/**
+ * Resets the command state machine for the next command and notifies the guest.
+ *
+ * @returns nothing.
+ * @param   pBusLogic   Pointer to the BusLogic device instance
+ */
+static void buslogicCommandComplete(PBUSLOGIC pBusLogic)
+{
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+
+    pBusLogic->fUseLocalRam = false;
+    pBusLogic->regStatus |= BUSLOGIC_REGISTER_STATUS_HOST_ADAPTER_READY;
+    pBusLogic->iReply = 0;
+
+    /* Modify I/O address does not generate an interrupt. */
+    if (   (pBusLogic->uOperationCode != BUSLOGICCOMMAND_MODIFY_IO_ADDRESS)
+        && (pBusLogic->uOperationCode != BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND))
+    {
+        /* Notify that the command is complete. */
+        pBusLogic->regStatus &= ~BUSLOGIC_REGISTER_STATUS_DATA_IN_REGISTER_READY;
+        pBusLogic->regInterrupt |= BUSLOGIC_REGISTER_INTERRUPT_COMMAND_COMPLETE;
+
+        if (pBusLogic->fIRQEnabled)
+            buslogicSetInterrupt(pBusLogic);
+    }
+
+    pBusLogic->uOperationCode = 0xff;
+    pBusLogic->iParameter = 0;
+}
+
+#if defined(IN_RING3)
+/**
+ * Initiates a hard reset which was issued from the guest.
+ *
+ * @returns nothing
+ * @param   pBusLogic   Pointer to the BusLogic device instance.
+ */
+static void buslogicIntiateHardReset(PBUSLOGIC pBusLogic)
+{
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+
+    buslogicHwReset(pBusLogic);
+
+    /* We set the diagnostic active in the status register. */
+    pBusLogic->regStatus |= BUSLOGIC_REGISTER_STATUS_DIAGNOSTIC_ACTIVE;
+}
+
+/**
+ * Send a mailbox with set status codes to the guest.
+ *
+ * @returns nothing.
+ * @param   pBusLogicR                Pointer to the BubsLogic device instance.
+ * @param   pTaskState                Pointer to the task state with the mailbox to send.
+ * @param   uHostAdapterStatus        The host adapter status code to set.
+ * @param   uDeviceStatus             The target device status to set.
+ * @param   uMailboxCompletionCode    Completion status code to set in the mailbox.
+ */
+static void buslogicSendIncomingMailbox(PBUSLOGIC pBusLogic, PBUSLOGICTASKSTATE pTaskState,
+                                        uint8_t uHostAdapterStatus, uint8_t uDeviceStatus,
+                                        uint8_t uMailboxCompletionCode)
+{
+    pTaskState->MailboxGuest.u.in.uHostAdapterStatus = uHostAdapterStatus;
+    pTaskState->MailboxGuest.u.in.uTargetDeviceStatus = uDeviceStatus;
+    pTaskState->MailboxGuest.u.in.uCompletionCode = uMailboxCompletionCode;
+
+    RTGCPHYS GCPhysAddrMailboxIncoming = pBusLogic->GCPhysAddrMailboxIncomingBase + (pBusLogic->uMailboxIncomingPositionCurrent * sizeof(Mailbox));
+    RTGCPHYS GCPhysAddrCCB = (RTGCPHYS)pTaskState->MailboxGuest.u32PhysAddrCCB;
+
+    /* Update CCB. */
+    pTaskState->CommandControlBlockGuest.uHostAdapterStatus = uHostAdapterStatus;
+    pTaskState->CommandControlBlockGuest.uDeviceStatus = uDeviceStatus;
+    PDMDevHlpPhysWrite(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrCCB, &pTaskState->CommandControlBlockGuest, sizeof(CommandControlBlock));
+
+    /* Update mailbox. */
+    PDMDevHlpPhysWrite(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrMailboxIncoming, &pTaskState->MailboxGuest, sizeof(Mailbox));
+
+    /* Advance to next mailbox position. */
+    pBusLogic->uMailboxIncomingPositionCurrent++;
+    if (pBusLogic->uMailboxIncomingPositionCurrent >= pBusLogic->cMailbox)
+        pBusLogic->uMailboxIncomingPositionCurrent = 0;
+
+    pBusLogic->regInterrupt |= BUSLOGIC_REGISTER_INTERRUPT_INCOMING_MAILBOX_LOADED;
+    if (pBusLogic->fIRQEnabled)
+        buslogicSetInterrupt(pBusLogic);
+}
+
+#if defined(DEBUG)
+/**
+ * Dumps the content of a mailbox for debugging purposes.
+ *
+ * @return nothing
+ * @param  pMailbox   The mialbox to dump.
+ * @param  fOutgoing  true if dumping the outgoing state.
+ *                    false if dumping the incoming state.
+ */
+static void buslogicDumpMailboxInfo(PMailbox pMailbox, bool fOutgoing)
+{
+    Log(("%s: Dump for %s mailbox:\n", __FUNCTION__, fOutgoing ? "outgoing" : "incoming"));
+    Log(("%s: u32PhysAddrCCB=%#x\n", __FUNCTION__, pMailbox->u32PhysAddrCCB));
+    if (fOutgoing)
+    {
+        Log(("%s: uActionCode=%u\n", __FUNCTION__, pMailbox->u.out.uActionCode));
+    }
+    else
+    {
+        Log(("%s: uHostAdapterStatus=%u\n", __FUNCTION__, pMailbox->u.in.uHostAdapterStatus));
+        Log(("%s: uTargetDeviceStatus=%u\n", __FUNCTION__, pMailbox->u.in.uTargetDeviceStatus));
+        Log(("%s: uCompletionCode=%u\n", __FUNCTION__, pMailbox->u.in.uCompletionCode));
+    }
+}
+
+/**
+ * Dumps the content of a command control block for debugging purposes.
+ *
+ * @returns nothing.
+ * @param   pCCB    Pointer to the command control block to dump.
+ */
+static void buslogicDumpCCBInfo(PCommandControlBlock pCCB)
+{
+    Log(("%s: Dump for Command Control Block:\n", __FUNCTION__));
+    Log(("%s: uOpCode=%#x\n", __FUNCTION__, pCCB->uOpcode));
+    Log(("%s: uDataDirection=%u\n", __FUNCTION__, pCCB->uDataDirection));
+    Log(("%s: fTagQueued=%d\n", __FUNCTION__, pCCB->fTagQueued));
+    Log(("%s: uQueueTag=%u\n", __FUNCTION__, pCCB->uQueueTag));
+    Log(("%s: cbCDB=%u\n", __FUNCTION__, pCCB->cbCDB));
+    Log(("%s: cbSenseData=%u\n", __FUNCTION__, pCCB->cbSenseData));
+    Log(("%s: cbData=%u\n", __FUNCTION__, pCCB->cbData));
+    Log(("%s: u32PhysAddrData=%#x\n", __FUNCTION__, pCCB->u32PhysAddrData));
+    Log(("%s: uHostAdapterStatus=%u\n", __FUNCTION__, pCCB->uHostAdapterStatus));
+    Log(("%s: uDeviceStatus=%u\n", __FUNCTION__, pCCB->uDeviceStatus));
+    Log(("%s: uTargetId=%u\n", __FUNCTION__, pCCB->uTargetId));
+    Log(("%s: uLogicalUnit=%u\n", __FUNCTION__, pCCB->uLogicalUnit));
+    Log(("%s: fLegacyTagEnable=%u\n", __FUNCTION__, pCCB->fLegacyTagEnable));
+    Log(("%s: uLegacyQueueTag=%u\n", __FUNCTION__, pCCB->uLegacyQueueTag));
+    Log(("%s: uCDB[0]=%#x\n", __FUNCTION__, pCCB->aCDB[0]));
+    for (int i = 1; i < pCCB->cbCDB; i++)
+        Log(("%s: uCDB[%d]=%u\n", __FUNCTION__, i, pCCB->aCDB[i]));
+    Log(("%s: u32PhysAddrSenseData=%#x\n", __FUNCTION__, pCCB->u32PhysAddrSenseData));
+}
+#endif
+
+/**
+ * Maps the data buffer into R3.
+ *
+ * @returns VBox status code.
+ * @param   pTaskState    Pointer to the task state.
+ * @param   fReadonly     Flag whether the mappings should be readonly.
+ */
+static int buslogicMapGCDataBufIntoR3(PBUSLOGICTASKSTATE pTaskState, bool fReadonly)
+{
+    int rc = VINF_SUCCESS;
+    uint32_t cScatterGatherEntriesR3 = 0;
+    PPDMDEVINS pDevIns = pTaskState->CTX_SUFF(pTargetDevice)->CTX_SUFF(pBusLogic)->CTX_SUFF(pDevIns);
+
+    /*
+     * @todo: Check following assumption and what residual means.
+     *
+     * The BusLogic adapter can handle two different data buffer formats.
+     * The first one is that the data pointer entry in the CCB points to
+     * the buffer directly. In second mode the data pointer points to a
+     * scatter gather list which describes the buffer.
+     */
+    if (   (pTaskState->CommandControlBlockGuest.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_SCATTER_GATHER)
+        || (pTaskState->CommandControlBlockGuest.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER))
+    {
+        uint32_t cScatterGatherGCRead;
+        uint32_t iScatterGatherEntry;
+        ScatterGatherEntry aScatterGatherReadGC[32]; /* Number of scatter gather list entries read from guest memory. */
+        uint32_t cScatterGatherGCLeft = pTaskState->CommandControlBlockGuest.cbData / sizeof(ScatterGatherEntry);
+        RTGCPHYS GCPhysAddrScatterGatherCurrent = (RTGCPHYS)pTaskState->CommandControlBlockGuest.u32PhysAddrData;
+
+        /* First pass - count needed R3 scatter gather list entries. */
+        do
+        {
+            cScatterGatherGCRead =   (cScatterGatherGCLeft < RT_ELEMENTS(aScatterGatherReadGC))
+                                   ? cScatterGatherGCLeft
+                                   : RT_ELEMENTS(aScatterGatherReadGC);
+            cScatterGatherGCLeft -= cScatterGatherGCRead;
+
+            /* Read the SG entries. */
+            PDMDevHlpPhysRead(pDevIns, GCPhysAddrScatterGatherCurrent, &aScatterGatherReadGC[0],
+                              cScatterGatherGCRead * sizeof(ScatterGatherEntry));
+
+            for (iScatterGatherEntry = 0; iScatterGatherEntry < cScatterGatherGCRead; iScatterGatherEntry++)
+            {
+                RTGCPHYS    GCPhysAddrDataBase;
+                size_t      cbDataToTransfer;
+
+                Log(("%s: iScatterGatherEntry=%u\n", __FUNCTION__, iScatterGatherEntry));
+
+                GCPhysAddrDataBase = (RTGCPHYS)aScatterGatherReadGC[iScatterGatherEntry].u32PhysAddrSegmentBase;
+                cbDataToTransfer = aScatterGatherReadGC[iScatterGatherEntry].cbSegment;
+
+                Log(("%s: GCPhysAddrDataBase=%RGp cbDataToTransfer=%u\n", __FUNCTION__, GCPhysAddrDataBase, cbDataToTransfer));
+
+                /*
+                 * Check if the physical address is page aligned.
+                 */
+                if (GCPhysAddrDataBase & PAGE_OFFSET_MASK)
+                {
+                    RTGCPHYS    GCPhysAddrDataNextPage = PAGE_ADDRESS(GCPhysAddrDataBase) + PAGE_SIZE;
+                    uint32_t    u32GCPhysAddrDiff = GCPhysAddrDataNextPage - GCPhysAddrDataBase;
+
+                    Log(("%s: Align page: GCPhysAddrDataBase=%RGp GCPhysAddrDataNextPage=%RGp\n",
+                         __FUNCTION__, GCPhysAddrDataBase, GCPhysAddrDataNextPage));
+
+                    cScatterGatherEntriesR3++;
+                    /* Subtract size of the buffer in the actual page. */
+                    if (cbDataToTransfer < u32GCPhysAddrDiff)
+                        cbDataToTransfer = 0;
+                    else
+                        cbDataToTransfer -= u32GCPhysAddrDiff;
+                }
+
+                /* The address is now page aligned. */
+                while (cbDataToTransfer)
+                {
+                    Log(("%s: GCPhysAddrDataBase=%RGp cbDataToTransfer=%u cScatterGatherEntriesR3=%u\n",
+                         __FUNCTION__, GCPhysAddrDataBase, cbDataToTransfer, cScatterGatherEntriesR3));
+
+                    cScatterGatherEntriesR3++;
+
+                    /* Check if this is the last page the buffer is in. */
+                    if (cbDataToTransfer < PAGE_SIZE)
+                        cbDataToTransfer = 0;
+                    else
+                        cbDataToTransfer -= PAGE_SIZE;
+                }
+            }
+
+            /* Set address to the next entries to read. */
+            GCPhysAddrScatterGatherCurrent += cScatterGatherGCRead * sizeof(ScatterGatherEntry);
+        } while (cScatterGatherGCLeft);
+
+        Log(("%s: cScatterGatherEntriesR3=%u\n", __FUNCTION__, cScatterGatherEntriesR3));
+
+        /*
+         * Allocate page map lock and scatter gather array.
+         * @todo: Optimize with caching.
+         */
+        AssertMsg(!pTaskState->paPageLock && !pTaskState->paScatterGather, ("paPageLock or/and paScatterGather are not NULL\n"));
+        pTaskState->cScatterGather = cScatterGatherEntriesR3;
+        pTaskState->cbScatterGather = 0;
+        pTaskState->paPageLock = (PPGMPAGEMAPLOCK)RTMemAllocZ(cScatterGatherEntriesR3 * sizeof(PGMPAGEMAPLOCK));
+        AssertMsgReturn(pTaskState->paPageLock, ("Allocating page lock array failed\n"), VERR_NO_MEMORY);
+        pTaskState->paScatterGather = (PPDMDATASEG)RTMemAllocZ(cScatterGatherEntriesR3 * sizeof(PDMDATASEG));
+        AssertMsgReturn(pTaskState->paScatterGather, ("Allocating page lock array failed\n"), VERR_NO_MEMORY);
+
+        /* Second pass - map the elements into R3. **/
+        cScatterGatherGCLeft = pTaskState->CommandControlBlockGuest.cbData / sizeof(ScatterGatherEntry);
+        GCPhysAddrScatterGatherCurrent = (RTGCPHYS)pTaskState->CommandControlBlockGuest.u32PhysAddrData;
+        PPGMPAGEMAPLOCK    pPageLockCurrent = pTaskState->paPageLock;
+        PPDMDATASEG        pScatterGatherCurrent = pTaskState->paScatterGather;
+
+        do
+        {
+            cScatterGatherGCRead = (cScatterGatherGCLeft < RT_ELEMENTS(aScatterGatherReadGC)) ? cScatterGatherGCLeft : RT_ELEMENTS(aScatterGatherReadGC);
+            cScatterGatherGCLeft -= cScatterGatherGCRead;
+
+            /* Read the SG entries. */
+            PDMDevHlpPhysRead(pDevIns, GCPhysAddrScatterGatherCurrent, &aScatterGatherReadGC[0], cScatterGatherGCRead * sizeof(ScatterGatherEntry));
+
+            for (iScatterGatherEntry = 0; iScatterGatherEntry < cScatterGatherGCRead; iScatterGatherEntry++)
+            {
+                RTGCPHYS    GCPhysAddrDataBase;
+                uint32_t    cbDataToTransfer;
+
+                GCPhysAddrDataBase = (RTGCPHYS)aScatterGatherReadGC[iScatterGatherEntry].u32PhysAddrSegmentBase;
+                cbDataToTransfer = aScatterGatherReadGC[iScatterGatherEntry].cbSegment;
+                pTaskState->cbScatterGather += cbDataToTransfer;
+
+                /*
+                 * Check if the physical address is page aligned.
+                 */
+                if (GCPhysAddrDataBase & PAGE_OFFSET_MASK)
+                {
+                    RTGCPHYS    GCPhysAddrDataNextPage = PAGE_ADDRESS(GCPhysAddrDataBase) + PAGE_SIZE;
+                    uint32_t    u32GCPhysAddrDiff = GCPhysAddrDataNextPage - GCPhysAddrDataBase; /* Difference from the buffer start to the next page boundary. */
+
+                    /* Check if the mapping ends at the page boundary and set segment size accordingly. */
+                    pScatterGatherCurrent->cbSeg = (cbDataToTransfer < u32GCPhysAddrDiff) ? cbDataToTransfer : u32GCPhysAddrDiff;
+
+                    /* Create the mapping. */
+                    if (fReadonly)
+                        rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, PAGE_ADDRESS(GCPhysAddrDataBase), 0, (const void **)&pScatterGatherCurrent->pvSeg, pPageLockCurrent); /** @todo r=bird: PAGE_ADDRESS is the wrong macro here as well... */
+                    else
+                        rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, PAGE_ADDRESS(GCPhysAddrDataBase), 0, &pScatterGatherCurrent->pvSeg, pPageLockCurrent);
+
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Creating mapping failed rc=%Rrc\n", rc));
+
+                    /* Let pvBuf point to the start of the buffer in the page. */
+                    pScatterGatherCurrent->pvSeg = ((uint8_t *)pScatterGatherCurrent->pvSeg) + (GCPhysAddrDataBase - PAGE_ADDRESS(GCPhysAddrDataBase));
+
+                    /* Subtract size of the buffer in the actual page. */
+                    cbDataToTransfer -= (uint32_t)pScatterGatherCurrent->cbSeg;
+                    pPageLockCurrent++;
+                    pScatterGatherCurrent++;
+                    /* Let physical address point to the next page in the buffer. */
+                    GCPhysAddrDataBase = GCPhysAddrDataNextPage;
+                }
+
+                /* The address is now page aligned. */
+                while (cbDataToTransfer)
+                {
+                    /* Check if this is the last page the buffer is in. */
+                    if (cbDataToTransfer < PAGE_SIZE)
+                    {
+                        pScatterGatherCurrent->cbSeg = cbDataToTransfer;
+                        cbDataToTransfer = 0;
+                    }
+                    else
+                    {
+                        cbDataToTransfer -= PAGE_SIZE;
+                        pScatterGatherCurrent->cbSeg = PAGE_SIZE;
+                    }
+
+                    /* Create the mapping. */
+                    if (fReadonly)
+                        rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, GCPhysAddrDataBase, 0, (const void **)&pScatterGatherCurrent->pvSeg, pPageLockCurrent);
+                    else
+                        rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrDataBase, 0, &pScatterGatherCurrent->pvSeg, pPageLockCurrent);
+
+                    if (RT_FAILURE(rc))
+                        AssertMsgFailed(("Creating mapping failed rc=%Rrc\n", rc));
+
+                    /* Go to the next page. */
+                    GCPhysAddrDataBase += PAGE_SIZE;
+                    pPageLockCurrent++;
+                    pScatterGatherCurrent++;
+                }
+            }
+
+            /* Set address to the next entries to read. */
+            GCPhysAddrScatterGatherCurrent += cScatterGatherGCRead * sizeof(ScatterGatherEntry);
+
+        } while (cScatterGatherGCLeft);
+
+    }
+    else if (   (pTaskState->CommandControlBlockGuest.u32PhysAddrData != 0)
+             && (pTaskState->CommandControlBlockGuest.cbData != 0))
+    {
+        /* The buffer is not scattered. */
+        RTGCPHYS GCPhysAddrDataBase     = (RTGCPHYS)PAGE_ADDRESS(pTaskState->CommandControlBlockGuest.u32PhysAddrData);
+        RTGCPHYS GCPhysAddrDataEnd      = (RTGCPHYS)(pTaskState->CommandControlBlockGuest.u32PhysAddrData + pTaskState->CommandControlBlockGuest.cbData);
+        RTGCPHYS GCPhysAddrDataEndBase  = (RTGCPHYS)PAGE_ADDRESS(GCPhysAddrDataEnd);
+        RTGCPHYS GCPhysAddrDataNext     = (RTGCPHYS)PAGE_ADDRESS(GCPhysAddrDataEnd) + PAGE_SIZE;
+        uint32_t cPages = (GCPhysAddrDataNext - GCPhysAddrDataBase) / PAGE_SIZE;
+        uint32_t cbOffsetFirstPage = pTaskState->CommandControlBlockGuest.u32PhysAddrData & PAGE_OFFSET_MASK;
+
+        Log(("Non scattered buffer:\n"));
+        Log(("u32PhysAddrData=%#x\n", pTaskState->CommandControlBlockGuest.u32PhysAddrData));
+        Log(("cbData=%u\n", pTaskState->CommandControlBlockGuest.cbData));
+        Log(("GCPhysAddrDataBase=0x%RGp\n", GCPhysAddrDataBase));
+        Log(("GCPhysAddrDataEnd=0x%RGp\n", GCPhysAddrDataEnd));
+        Log(("GCPhysAddrDataEndBase=0x%RGp\n", GCPhysAddrDataEndBase));
+        Log(("GCPhysAddrDataNext=0x%RGp\n", GCPhysAddrDataNext));
+        Log(("cPages=%u\n", cPages));
+
+        pTaskState->paPageLock = (PPGMPAGEMAPLOCK)RTMemAllocZ(cPages * sizeof(PGMPAGEMAPLOCK));
+        AssertMsgReturn(pTaskState->paPageLock, ("Allocating page lock array failed\n"), VERR_NO_MEMORY);
+        pTaskState->paScatterGather = (PPDMDATASEG)RTMemAllocZ(cPages * sizeof(PDMDATASEG));
+        AssertMsgReturn(pTaskState->paScatterGather, ("Allocating scatter gather list failed\n"), VERR_NO_MEMORY);
+
+        PPGMPAGEMAPLOCK    pPageLockCurrent = pTaskState->paPageLock;
+        PPDMDATASEG        pScatterGatherCurrent = pTaskState->paScatterGather;
+
+        for (uint32_t i = 0; i < cPages; i++)
+        {
+            if (fReadonly)
+                rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, GCPhysAddrDataBase, 0, (const void **)&pScatterGatherCurrent->pvSeg, pPageLockCurrent);
+            else
+                rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrDataBase, 0, &pScatterGatherCurrent->pvSeg, pPageLockCurrent);
+
+            pScatterGatherCurrent->cbSeg = PAGE_SIZE;
+
+            pPageLockCurrent++;
+            pScatterGatherCurrent++;
+            GCPhysAddrDataBase += PAGE_SIZE;
+        }
+
+        /* Correct pointer of the first entry. */
+        pTaskState->paScatterGather[0].pvSeg = (uint8_t *)pTaskState->paScatterGather[0].pvSeg + cbOffsetFirstPage;
+        pTaskState->paScatterGather[0].cbSeg -= cbOffsetFirstPage;
+        /* Correct size of the last entry. */
+        pTaskState->paScatterGather[cPages-1].cbSeg = GCPhysAddrDataEnd - GCPhysAddrDataEndBase;
+        pTaskState->cScatterGather = cPages;
+        pTaskState->cbScatterGather = pTaskState->CommandControlBlockGuest.cbData;
+    }
+
+    return rc;
+}
+
+/**
+ * Free mapped pages and other allocated resources used for the scatter gather list.
+ *
+ * @returns nothing.
+ * @param   pTaskState    Pointer to the task state.
+ */
+static void buslogicFreeGCDataBuffer(PBUSLOGICTASKSTATE pTaskState)
+{
+    PPGMPAGEMAPLOCK pPageMapLock = pTaskState->paPageLock;
+    PPDMDEVINS      pDevIns = pTaskState->CTX_SUFF(pTargetDevice)->CTX_SUFF(pBusLogic)->CTX_SUFF(pDevIns);
+
+    for (uint32_t iPageLockCurrent = 0; iPageLockCurrent < pTaskState->cScatterGather; iPageLockCurrent++)
+    {
+        PDMDevHlpPhysReleasePageMappingLock(pDevIns, pPageMapLock);
+        pPageMapLock++;
+    }
+
+    /* @todo: optimize with caching. */
+    RTMemFree(pTaskState->paPageLock);
+    RTMemFree(pTaskState->paScatterGather);
+    pTaskState->paPageLock      = NULL;
+    pTaskState->paScatterGather = NULL;
+    pTaskState->cScatterGather  = 0;
+    pTaskState->cbScatterGather = 0;
+}
+
+/**
+ * Free the sense buffer.
+ *
+ * @returns nothing.
+ * @param   pTaskState   Pointer to the task state.
+ */
+static void buslogicFreeGCSenseBuffer(PBUSLOGICTASKSTATE pTaskState)
+{
+    PPDMDEVINS pDevIns = pTaskState->CTX_SUFF(pTargetDevice)->CTX_SUFF(pBusLogic)->CTX_SUFF(pDevIns);
+
+    PDMDevHlpPhysReleasePageMappingLock(pDevIns, &pTaskState->pPageLockSense);
+    pTaskState->pu8SenseBuffer = NULL;
+}
+
+/**
+ * Map the sense buffer into R3.
+ *
+ * @returns VBox status code.
+ * @param   pTaskState    Pointer to the task state.
+ * @note Current assumption is that the sense buffer is not scattered and does not cross a page boundary.
+ */
+static int buslogicMapGCSenseBufferIntoR3(PBUSLOGICTASKSTATE pTaskState)
+{
+    int rc = VINF_SUCCESS;
+    PPDMDEVINS pDevIns = pTaskState->CTX_SUFF(pTargetDevice)->CTX_SUFF(pBusLogic)->CTX_SUFF(pDevIns);
+    RTGCPHYS GCPhysAddrSenseBuffer = (RTGCPHYS)pTaskState->CommandControlBlockGuest.u32PhysAddrSenseData;
+#ifdef RT_STRICT
+    uint32_t cbSenseBuffer = pTaskState->CommandControlBlockGuest.cbSenseData;
+#endif
+    RTGCPHYS GCPhysAddrSenseBufferBase = PAGE_ADDRESS(GCPhysAddrSenseBuffer);
+
+    AssertMsg(GCPhysAddrSenseBuffer >= GCPhysAddrSenseBufferBase,
+              ("Impossible GCPhysAddrSenseBuffer < GCPhysAddrSenseBufferBase\n"));
+
+    /* Sanity checks for the assumption. */
+    AssertMsg(((GCPhysAddrSenseBuffer + cbSenseBuffer) < (GCPhysAddrSenseBufferBase + PAGE_SIZE)),
+              ("Sense buffer crosses page boundary\n"));
+
+    rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrSenseBufferBase, 0, (void **)&pTaskState->pu8SenseBuffer, &pTaskState->pPageLockSense);
+    AssertMsgRC(rc, ("Mapping sense buffer failed rc=%Rrc\n", rc));
+
+    /* Correct start address of the sense buffer. */
+    pTaskState->pu8SenseBuffer += (GCPhysAddrSenseBuffer - GCPhysAddrSenseBufferBase);
+
+    return rc;
+}
+#endif /* IN_RING3 */
+
+/**
+ * Parses the command buffer and executes it.
+ *
+ * @returns VBox status code.
+ * @param   pBusLogic  Pointer to the BusLogic device instance.
+ */
+static int buslogicProcessCommand(PBUSLOGIC pBusLogic)
+{
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+    AssertMsg(pBusLogic->uOperationCode != 0xff, ("There is no command to execute\n"));
+
+    switch (pBusLogic->uOperationCode)
+    {
+        case BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION:
+        {
+            PReplyInquirePCIHostAdapterInformation pReply = (PReplyInquirePCIHostAdapterInformation)pBusLogic->aReplyBuffer;
+            memset(pReply, 0, sizeof(ReplyInquirePCIHostAdapterInformation));
+
+            /* It seems VMware does not provide valid information here too, lets do the same :) */
+            pReply->InformationIsValid = 0;
+            pReply->IsaIOPort = 0xff; /* Make it invalid. */
+            pBusLogic->cbReplyParametersLeft = sizeof(ReplyInquirePCIHostAdapterInformation);
+            break;
+        }
+        case BUSLOGICCOMMAND_MODIFY_IO_ADDRESS:
+        {
+            pBusLogic->cbReplyParametersLeft = 0;
+            if (pBusLogic->aCommandBuffer[0] == 0x06)
+            {
+                Log(("Disabling ISA I/O ports.\n"));
+                pBusLogic->fISAEnabled = false;
+            }
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_BOARD_ID:
+        {
+            pBusLogic->aReplyBuffer[0] = '0'; /* @todo figure out what to write here. */
+            pBusLogic->aReplyBuffer[1] = '0'; /* @todo figure out what to write here. */
+
+            /* We report version 5.07B. This reply will provide the first two digits. */
+            pBusLogic->aReplyBuffer[2] = '5'; /* Major version 5 */
+            pBusLogic->aReplyBuffer[3] = '0'; /* Minor version 0 */
+            pBusLogic->cbReplyParametersLeft = 4; /* Reply is 4 bytes long */
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER:
+        {
+            pBusLogic->aReplyBuffer[0] = '7';
+            pBusLogic->cbReplyParametersLeft = 1;
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER:
+        {
+            pBusLogic->aReplyBuffer[0] = 'B';
+            pBusLogic->cbReplyParametersLeft = 1;
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER:
+        {
+            /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+            pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+            memset(pBusLogic->aReplyBuffer, 0, pBusLogic->cbReplyParametersLeft);
+            const char aModelName[] = "958";
+            int cCharsToTransfer =   (pBusLogic->cbReplyParametersLeft <= sizeof(aModelName))
+                                   ? pBusLogic->cbReplyParametersLeft
+                                   : sizeof(aModelName);
+
+            for (int i = 0; i < cCharsToTransfer; i++)
+                pBusLogic->aReplyBuffer[i] = aModelName[i];
+
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_CONFIGURATION:
+        {
+            pBusLogic->cbReplyParametersLeft = sizeof(ReplyInquireConfiguration);
+            PReplyInquireConfiguration pReply = (PReplyInquireConfiguration)pBusLogic->aReplyBuffer;
+            memset(pReply, 0, sizeof(ReplyInquireConfiguration));
+
+            pReply->uHostAdapterId = 7; /* The controller has always 7 as ID. */
+            /*
+             * The rest of this reply only applies for ISA adapters.
+             * This is a PCI adapter so they are not important and are skipped.
+             */
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION:
+        {
+            /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+            pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+            PReplyInquireExtendedSetupInformation pReply = (PReplyInquireExtendedSetupInformation)pBusLogic->aReplyBuffer;
+            memset(pReply, 0, sizeof(ReplyInquireExtendedSetupInformation));
+
+            pReply->fHostWideSCSI = true;
+            pReply->fHostUltraSCSI = true;
+            pReply->u16ScatterGatherLimit = 8192;
+
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION:
+        {
+            /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+            pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+            PReplyInquireSetupInformation pReply = (PReplyInquireSetupInformation)pBusLogic->aReplyBuffer;
+            memset(pReply, 0, sizeof(ReplyInquireSetupInformation));
+            break;
+        }
+        case BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM:
+        {
+            /*
+             * First element in the command buffer contains start offset to read from
+             * and second one the number of bytes to read.
+             */
+            uint8_t uOffset = pBusLogic->aCommandBuffer[0];
+            pBusLogic->cbReplyParametersLeft  = pBusLogic->aCommandBuffer[1];
+
+            pBusLogic->fUseLocalRam = true;
+            pBusLogic->iReply = uOffset;
+            break;
+        }
+        case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+        {
+            PRequestInitializeExtendedMailbox pRequest = (PRequestInitializeExtendedMailbox)pBusLogic->aCommandBuffer;
+
+            pBusLogic->cMailbox = pRequest->cMailbox;
+            pBusLogic->GCPhysAddrMailboxOutgoingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress;
+            /* The area for incoming mailboxes is right after the last entry of outgoing mailboxes. */
+            pBusLogic->GCPhysAddrMailboxIncomingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress + (pBusLogic->cMailbox * sizeof(Mailbox));
+
+            Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxOutgoingBase));
+            Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxIncomingBase));
+            Log(("cMailboxes=%u\n", pBusLogic->cMailbox));
+
+            pBusLogic->cbReplyParametersLeft = 0;
+            break;
+        }
+        case BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE:
+        {
+            if (pBusLogic->aCommandBuffer[0] == 0)
+                pBusLogic->fStrictRoundRobinMode = false;
+            else if (pBusLogic->aCommandBuffer[0] == 1)
+                pBusLogic->fStrictRoundRobinMode = true;
+            else
+                AssertMsgFailed(("Invalid round robin mode %d\n", pBusLogic->aCommandBuffer[0]));
+
+            pBusLogic->cbReplyParametersLeft = 0;
+            break;
+        }
+        case BUSLOGICCOMMAND_SET_CCB_FORMAT:
+        {
+            if (pBusLogic->aCommandBuffer[0] == 0)
+                pBusLogic->fExtendedLunCCBFormat = false;
+            else if (pBusLogic->aCommandBuffer[0] == 1)
+                pBusLogic->fExtendedLunCCBFormat = true;
+            else
+                AssertMsgFailed(("Invalid CCB format %d\n", pBusLogic->aCommandBuffer[0]));
+
+            pBusLogic->cbReplyParametersLeft = 0;
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES:
+        {
+            /* Each bit which is set in the 16bit wide variable means a present device. */
+            uint16_t u16TargetsPresentMask = 0;
+
+            for (uint8_t i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+            {
+                if (pBusLogic->aDeviceStates[i].fPresent)
+                    u16TargetsPresentMask |= (1 << i);
+            }
+            pBusLogic->aReplyBuffer[0] = (uint8_t)u16TargetsPresentMask;
+            pBusLogic->aReplyBuffer[1] = (uint8_t)(u16TargetsPresentMask >> 8);
+            pBusLogic->cbReplyParametersLeft = 2;
+            break;
+        }
+        case BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD:
+        {
+            pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+
+            for (uint8_t i = 0; i < pBusLogic->cbReplyParametersLeft; i++)
+                pBusLogic->aReplyBuffer[i] = 0; /* @todo Figure if we need something other here. It's not needed for the linux driver */
+
+            break;
+        }
+        case BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT:
+        {
+            if (pBusLogic->aCommandBuffer[0] == 0)
+                pBusLogic->fIRQEnabled = false;
+            else
+                pBusLogic->fIRQEnabled = true;
+            break;
+        }
+        case BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND: /* Should be handled already. */
+        default:
+            AssertMsgFailed(("Invalid command %#x\n", pBusLogic->uOperationCode));
+    }
+
+    Log(("cbReplyParametersLeft=%d\n", pBusLogic->cbReplyParametersLeft));
+
+    /* Set the data in ready bit in the status register in case the command has a reply. */
+    if (pBusLogic->cbReplyParametersLeft)
+        pBusLogic->regStatus |= BUSLOGIC_REGISTER_STATUS_DATA_IN_REGISTER_READY;
+    else
+        buslogicCommandComplete(pBusLogic);
+
+    return rc;
+}
+
+/**
+ * Read a register from the BusLogic adapter.
+ *
+ * @returns VBox status code.
+ * @param   pBusLogic    Pointer to the BusLogic instance data.
+ * @param   iRegister    The index of the register to read.
+ * @param   pu32         Where to store the register content.
+ */
+static int buslogicRegisterRead(PBUSLOGIC pBusLogic, unsigned iRegister, uint32_t *pu32)
+{
+    int rc = VINF_SUCCESS;
+
+    switch (iRegister)
+    {
+        case BUSLOGIC_REGISTER_STATUS:
+        {
+            *pu32 = pBusLogic->regStatus;
+            /*
+             * If the diagnostic active bit is set we are in a hard reset initiated from the guest.
+             * The guest reads the status register and waits that the host adapter ready bit is set.
+             */
+            if (pBusLogic->regStatus & BUSLOGIC_REGISTER_STATUS_DIAGNOSTIC_ACTIVE)
+            {
+                pBusLogic->regStatus &= ~BUSLOGIC_REGISTER_STATUS_DIAGNOSTIC_ACTIVE;
+                pBusLogic->regStatus |= BUSLOGIC_REGISTER_STATUS_HOST_ADAPTER_READY;
+            }
+            break;
+        }
+        case BUSLOGIC_REGISTER_DATAIN:
+        {
+            if (pBusLogic->fUseLocalRam)
+                *pu32 = pBusLogic->LocalRam.u8View[pBusLogic->iReply];
+            else
+                *pu32 = pBusLogic->aReplyBuffer[pBusLogic->iReply];
+
+            pBusLogic->iReply++;
+            pBusLogic->cbReplyParametersLeft--;
+
+            if (!pBusLogic->cbReplyParametersLeft)
+            {
+                /*
+                 * Reply finished, set command complete bit, unset data in ready bit and
+                 * interrupt the guest if enabled.
+                 */
+                buslogicCommandComplete(pBusLogic);
+            }
+            break;
+        }
+        case BUSLOGIC_REGISTER_INTERRUPT:
+        {
+            *pu32 = pBusLogic->regInterrupt;
+#if 0
+            if (pBusLogic->uOperationCode == BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT)
+                rc = PDMDeviceDBGFStop(pBusLogic->CTX_SUFF(pDevIns), RT_SRC_POS, "Interrupt disable command\n");
+#endif
+            break;
+        }
+        case BUSLOGIC_REGISTER_GEOMETRY:
+        {
+            *pu32 = pBusLogic->regGeometry;
+            break;
+        }
+        default:
+            AssertMsgFailed(("Register not available\n"));
+            rc = VERR_IOM_IOPORT_UNUSED;
+    }
+
+    Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+          __FUNCTION__, pu32, 1, pu32, iRegister, rc));
+
+    return rc;
+}
+
+/**
+ * Write a value to a register.
+ *
+ * @returns VBox status code.
+ * @param   pBusLogic    Pointer to the BusLogic instance data.
+ * @param   iRegister    The index of the register to read.
+ * @param   uVal         The value to write.
+ */
+static int buslogicRegisterWrite(PBUSLOGIC pBusLogic, unsigned iRegister, uint8_t uVal)
+{
+    int rc = VINF_SUCCESS;
+
+    switch (iRegister)
+    {
+        case BUSLOGIC_REGISTER_CONTROL:
+        {
+            if (uVal & BUSLOGIC_REGISTER_CONTROL_INTERRUPT_RESET)
+                buslogicClearInterrupt(pBusLogic);
+
+            if ((uVal & BUSLOGIC_REGISTER_CONTROL_HARD_RESET) || (uVal & BUSLOGIC_REGISTER_CONTROL_SOFT_RESET))
+            {
+#ifdef IN_RING3
+                buslogicIntiateHardReset(pBusLogic);
+#else
+                rc = VINF_IOM_HC_IOPORT_WRITE;
+#endif
+            }
+
+            break;
+        }
+        case BUSLOGIC_REGISTER_COMMAND:
+        {
+            /* Fast path for mailbox execution command. */
+            if ((uVal == BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND) && (pBusLogic->uOperationCode = 0xff))
+            {
+                ASMAtomicIncU32(&pBusLogic->cMailboxesReady);
+                if (!ASMAtomicXchgBool(&pBusLogic->fNotificationSend, true))
+                {
+                    /* Send new notification to the queue. */
+                    PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pBusLogic->CTX_SUFF(pNotifierQueue));
+                    AssertMsg(pItem, ("Allocating item for queue failed\n"));
+                    PDMQueueInsert(pBusLogic->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+                }
+
+                return rc;
+            }
+
+            /*
+             * Check if we are already fetch command parameters from the guest.
+             * If not we initialize executing a new command.
+             */
+            if (pBusLogic->uOperationCode == 0xff)
+            {
+                pBusLogic->uOperationCode = uVal;
+                pBusLogic->iParameter = 0;
+
+                /* Mark host adapter as busy. */
+                pBusLogic->regStatus &= ~BUSLOGIC_REGISTER_STATUS_HOST_ADAPTER_READY;
+
+                /* Get the number of bytes for parameters from the command code. */
+                switch (pBusLogic->uOperationCode)
+                {
+                    case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER:
+                    case BUSLOGICCOMMAND_INQUIRE_BOARD_ID:
+                    case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER:
+                    case BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION:
+                    case BUSLOGICCOMMAND_INQUIRE_CONFIGURATION:
+                    case BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES:
+                        pBusLogic->cbCommandParametersLeft = 0;
+                        break;
+                    case BUSLOGICCOMMAND_MODIFY_IO_ADDRESS:
+                    case BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION:
+                    case BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION:
+                    case BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER:
+                    case BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE:
+                    case BUSLOGICCOMMAND_SET_CCB_FORMAT:
+                    case BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD:
+                    case BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT:
+                        pBusLogic->cbCommandParametersLeft = 1;
+                        break;
+                    case BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM:
+                        pBusLogic->cbCommandParametersLeft = 2;
+                        break;
+                    case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+                        pBusLogic->cbCommandParametersLeft = sizeof(RequestInitializeExtendedMailbox);
+                        break;
+                    case BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND: /* Should not come here anymore. */
+                    default:
+                        AssertMsgFailed(("Invalid operation code %#x\n", uVal));
+                }
+            }
+            else
+            {
+                /*
+                 * The real adapter would set the Command register busy bit in the status register.
+                 * The guest has to wait until it is unset.
+                 * We don't need to do it because the guest does not continue execution while we are in this
+                 * function.
+                 */
+                pBusLogic->aCommandBuffer[pBusLogic->iParameter] = uVal;
+                pBusLogic->iParameter++;
+                pBusLogic->cbCommandParametersLeft--;
+            }
+
+            /* Start execution of command if there are no parameters left. */
+            if (!pBusLogic->cbCommandParametersLeft)
+            {
+                rc = buslogicProcessCommand(pBusLogic);
+                AssertMsgRC(rc, ("Processing command failed rc=%Rrc\n", rc));
+            }
+            break;
+        }
+        default:
+            AssertMsgFailed(("Register not available\n"));
+            rc = VERR_IOM_IOPORT_UNUSED;
+    }
+
+    return rc;
+}
+
+/**
+ * Memory mapped I/O Handler for read operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   GCPhysAddr  Physical address (in GC) where the read starts.
+ * @param   pv          Where to store the result.
+ * @param   cb          Number of bytes read.
+ */
+PDMBOTHCBDECL(int) buslogicMMIORead(PPDMDEVINS pDevIns, void *pvUser,
+                                    RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    /* the linux driver does not make use of the MMIO area. */
+    AssertMsgFailed(("MMIO Read\n"));
+    return VINF_SUCCESS;
+}
+
+/**
+ * Memory mapped I/O Handler for write operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   GCPhysAddr  Physical address (in GC) where the read starts.
+ * @param   pv          Where to fetch the result.
+ * @param   cb          Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) buslogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                     RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    /* the linux driver does not make use of the MMIO area. */
+    AssertMsgFailed(("MMIO Write\n"));
+    return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for IN operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   pu32        Where to store the result.
+ * @param   cb          Number of bytes read.
+ */
+PDMBOTHCBDECL(int) buslogicIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                       RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);;
+    unsigned iRegister = Port - pBusLogic->IOPortBase;
+
+    Assert(cb == 1);
+
+    return buslogicRegisterRead(pBusLogic, iRegister, pu32);
+}
+
+/**
+ * Port I/O Handler for OUT operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   u32         The value to output.
+ * @param   cb          The value size in bytes.
+ */
+PDMBOTHCBDECL(int) buslogicIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                        RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    int rc = VINF_SUCCESS;
+    unsigned iRegister = Port - pBusLogic->IOPortBase;
+    uint8_t uVal = (uint8_t)u32;
+
+    Assert(cb == 1);
+
+    rc = buslogicRegisterWrite(pBusLogic, iRegister, (uint8_t)uVal);
+
+    Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x Port=%#x rc=%Rrc\n",
+          pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, Port, rc));
+
+    return rc;
+}
+
+#ifdef IN_RING3
+/**
+ * Port I/O Handler for IN operations - legacy port.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   pu32        Where to store the result.
+ * @param   cb          Number of bytes read.
+ */
+static int  buslogicIsaIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                   RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    int rc;
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    Assert(cb == 1);
+
+    if (!pBusLogic->fISAEnabled)
+        return VERR_IOM_IOPORT_UNUSED;
+
+    rc = vboxscsiReadRegister(&pBusLogic->VBoxSCSI, (Port - BUSLOGIC_ISA_IO_PORT), pu32);
+
+    Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+          __FUNCTION__, pu32, 1, pu32, (Port - BUSLOGIC_ISA_IO_PORT), rc));
+
+    return rc;
+}
+
+static int buslogicPrepareBIOSSCSIRequest(PBUSLOGIC pBusLogic)
+{
+    int rc;
+    PBUSLOGICTASKSTATE pTaskState;
+    uint32_t           uTargetDevice;
+
+    rc = RTCacheRequest(pBusLogic->pTaskCache, (void **)&pTaskState);
+    AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+
+    pTaskState->fBIOS = true;
+
+    rc = vboxscsiSetupRequest(&pBusLogic->VBoxSCSI, &pTaskState->PDMScsiRequest, &uTargetDevice);
+    AssertMsgRCReturn(rc, ("Setting up SCSI request failed rc=%Rrc\n", rc), rc);
+
+    pTaskState->PDMScsiRequest.pvUser = pTaskState;
+
+    pTaskState->CTX_SUFF(pTargetDevice) = &pBusLogic->aDeviceStates[uTargetDevice];
+
+    if (!pTaskState->CTX_SUFF(pTargetDevice)->fPresent)
+    {
+        /* Device is not present. */
+        AssertMsg(pTaskState->PDMScsiRequest.pbCDB[0] == SCSI_INQUIRY,
+                    ("Device is not present but command is not inquiry\n"));
+
+        SCSIINQUIRYDATA ScsiInquiryData;
+
+        memset(&ScsiInquiryData, 0, sizeof(SCSIINQUIRYDATA));
+        ScsiInquiryData.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+        ScsiInquiryData.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+
+        memcpy(pBusLogic->VBoxSCSI.pBuf, &ScsiInquiryData, 5);
+
+        rc = vboxscsiRequestFinished(&pBusLogic->VBoxSCSI, &pTaskState->PDMScsiRequest);
+        AssertMsgRCReturn(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc), rc);
+
+        rc = RTCacheInsert(pBusLogic->pTaskCache, pTaskState);
+        AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+    }
+    else
+    {
+        LogFlowFunc(("before increment %u\n", pTaskState->CTX_SUFF(pTargetDevice)->cOutstandingRequests));
+        ASMAtomicIncU32(&pTaskState->CTX_SUFF(pTargetDevice)->cOutstandingRequests);
+        LogFlowFunc(("after increment %u\n", pTaskState->CTX_SUFF(pTargetDevice)->cOutstandingRequests));
+
+        rc = pTaskState->CTX_SUFF(pTargetDevice)->pDrvSCSIConnector->pfnSCSIRequestSend(pTaskState->CTX_SUFF(pTargetDevice)->pDrvSCSIConnector,
+                                                                                        &pTaskState->PDMScsiRequest);
+        AssertMsgRCReturn(rc, ("Sending request to SCSI layer failed rc=%Rrc\n", rc), rc);
+    }
+
+    return rc;
+}
+
+/**
+ * Port I/O Handler for OUT operations - legacy port.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   u32         The value to output.
+ * @param   cb          The value size in bytes.
+ */
+static int buslogicIsaIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                   RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    int rc;
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x Port=%#x\n",
+          pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, Port));
+
+    Assert(cb == 1);
+
+    if (!pBusLogic->fISAEnabled)
+        return VERR_IOM_IOPORT_UNUSED;
+
+    rc = vboxscsiWriteRegister(&pBusLogic->VBoxSCSI, (Port - BUSLOGIC_ISA_IO_PORT), (uint8_t)u32);
+    if (rc == VERR_MORE_DATA)
+    {
+        rc = buslogicPrepareBIOSSCSIRequest(pBusLogic);
+        AssertRC(rc);
+    }
+    else if (RT_FAILURE(rc))
+        AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+static DECLCALLBACK(int) buslogicIsaIOPortWriteStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    int rc;
+
+    Log2(("#%d %s: pvUser=%#p cb=%d Port=%#x\n",
+          pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+    rc = vboxscsiWriteString(pDevIns, &pBusLogic->VBoxSCSI, (Port - BUSLOGIC_ISA_IO_PORT),
+                             pGCPtrSrc, pcTransfer, cb);
+    if (rc == VERR_MORE_DATA)
+    {
+        rc = buslogicPrepareBIOSSCSIRequest(pBusLogic);
+        AssertRC(rc);
+    }
+    else if (RT_FAILURE(rc))
+        AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+    return rc;
+}
+
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+static DECLCALLBACK(int) buslogicIsaIOPortReadStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    LogFlowFunc(("#%d %s: pvUser=%#p cb=%d Port=%#x\n",
+                 pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+    return vboxscsiReadString(pDevIns, &pBusLogic->VBoxSCSI, (Port - BUSLOGIC_ISA_IO_PORT),
+                              pGCPtrDst, pcTransfer, cb);
+}
+
+static DECLCALLBACK(int) buslogicMMIOMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion,
+                                         RTGCPHYS GCPhysAddress, uint32_t cb,
+                                         PCIADDRESSSPACE enmType)
+{
+    PPDMDEVINS pDevIns = pPciDev->pDevIns;
+    PBUSLOGIC  pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    int   rc = VINF_SUCCESS;
+
+    Log2(("%s: registering MMIO area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
+
+    Assert(cb >= 32);
+
+    if (enmType == PCI_ADDRESS_SPACE_MEM)
+    {
+        /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+        rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL,
+                                   buslogicMMIOWrite, buslogicMMIORead, NULL, "BusLogic");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, 0,
+                                         "buslogicMMIOWrite", "buslogicMMIORead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpMMIORegisterGC(pDevIns, GCPhysAddress, cb, 0,
+                                         "buslogicMMIOWrite", "buslogicMMIORead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        pThis->MMIOBase = GCPhysAddress;
+    }
+    else if (enmType == PCI_ADDRESS_SPACE_IO)
+    {
+        rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+                                     NULL, buslogicIOPortWrite, buslogicIOPortRead, NULL, NULL, "BusLogic");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+                                           0, "buslogicIOPortWrite", "buslogicIOPortRead", NULL, NULL, "BusLogic");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpIOPortRegisterGC(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+                                           0, "buslogicIOPortWrite", "buslogicIOPortRead", NULL, NULL, "BusLogic");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        pThis->IOPortBase = (RTIOPORT)GCPhysAddress;
+    }
+    else
+        AssertMsgFailed(("Invalid enmType=%d\n", enmType));
+
+    return rc;
+}
+
+static DECLCALLBACK(int) buslogicDeviceSCSIRequestCompleted(PPDMISCSIPORT pInterface, PPDMSCSIREQUEST pSCSIRequest, int rcCompletion)
+{
+    int rc;
+    PBUSLOGICTASKSTATE pTaskState = (PBUSLOGICTASKSTATE)pSCSIRequest->pvUser;
+    PBUSLOGICDEVICE pBusLogicDevice = pTaskState->CTX_SUFF(pTargetDevice);
+    PBUSLOGIC pBusLogic = pBusLogicDevice->CTX_SUFF(pBusLogic);
+
+    LogFlowFunc(("before decrement %u\n", pBusLogicDevice->cOutstandingRequests));
+    ASMAtomicDecU32(&pBusLogicDevice->cOutstandingRequests);
+    LogFlowFunc(("after decrement %u\n", pBusLogicDevice->cOutstandingRequests));
+
+    if (pTaskState->fBIOS)
+    {
+        rc = vboxscsiRequestFinished(&pBusLogic->VBoxSCSI, pSCSIRequest);
+        AssertMsgRC(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc));
+    }
+    else
+    {
+        buslogicFreeGCDataBuffer(pTaskState);
+
+        if (pTaskState->pu8SenseBuffer)
+            buslogicFreeGCSenseBuffer(pTaskState);
+
+        buslogicSendIncomingMailbox(pBusLogic, pTaskState,
+                                    BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_CMD_COMPLETED,
+                                    BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+                                    BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITHOUT_ERROR);
+    }
+
+    /* Add task to the cache. */
+    rc = RTCacheInsert(pBusLogic->pTaskCache, pTaskState);
+    AssertMsgRC(rc, ("Inserting task state into cache failed rc=%Rrc\n", rc));
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Read mailbox from the guest and execute command.
+ *
+ * @returns VBox status code.
+ * @param   pBusLogic    Pointer to the BusLogic instance data.
+ */
+static int buslogicProcessMailboxNext(PBUSLOGIC pBusLogic)
+{
+    PBUSLOGICTASKSTATE pTaskState = NULL;
+    RTGCPHYS           GCPhysAddrMailboxCurrent;
+    int rc;
+
+    rc = RTCacheRequest(pBusLogic->pTaskCache, (void **)&pTaskState);
+    AssertMsgReturn(RT_SUCCESS(rc) && (pTaskState != NULL), ("Failed to get task state from cache\n"), rc);
+
+    pTaskState->fBIOS = false;
+
+    if (!pBusLogic->fStrictRoundRobinMode)
+    {
+        /* Search for a filled mailbox. */
+        do
+        {
+            /* Fetch mailbox from guest memory. */
+            GCPhysAddrMailboxCurrent = pBusLogic->GCPhysAddrMailboxOutgoingBase + (pBusLogic->uMailboxOutgoingPositionCurrent * sizeof(Mailbox));
+
+            PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrMailboxCurrent,
+                              &pTaskState->MailboxGuest, sizeof(Mailbox));
+
+            pBusLogic->uMailboxOutgoingPositionCurrent++;
+
+            /* Check if we reached the end and start from the beginning if so. */
+            if (pBusLogic->uMailboxOutgoingPositionCurrent >= pBusLogic->cMailbox)
+                pBusLogic->uMailboxOutgoingPositionCurrent = 0;
+        } while (pTaskState->MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE);
+    }
+    else
+    {
+        /* Fetch mailbox from guest memory. */
+        GCPhysAddrMailboxCurrent = pBusLogic->GCPhysAddrMailboxOutgoingBase + (pBusLogic->uMailboxOutgoingPositionCurrent * sizeof(Mailbox));
+
+        PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrMailboxCurrent,
+                          &pTaskState->MailboxGuest, sizeof(Mailbox));
+    }
+
+#ifdef DEBUG
+    buslogicDumpMailboxInfo(&pTaskState->MailboxGuest, true);
+#endif
+
+    if (pTaskState->MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_START_COMMAND)
+    {
+        bool fReadonly = false;
+
+        /* Fetch CCB now. */
+        RTGCPHYS GCPhysAddrCCB = (RTGCPHYS)pTaskState->MailboxGuest.u32PhysAddrCCB;
+        PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrCCB,
+                            &pTaskState->CommandControlBlockGuest, sizeof(CommandControlBlock));
+
+        PBUSLOGICDEVICE pTargetDevice = &pBusLogic->aDeviceStates[pTaskState->CommandControlBlockGuest.uTargetId];
+        pTaskState->CTX_SUFF(pTargetDevice) = pTargetDevice;
+
+#ifdef DEBUG
+        buslogicDumpCCBInfo(&pTaskState->CommandControlBlockGuest);
+#endif
+
+        switch (pTaskState->CommandControlBlockGuest.uDataDirection)
+        {
+            case BUSLOGIC_CCB_DIRECTION_UNKNOWN:
+            case BUSLOGIC_CCB_DIRECTION_IN:
+                fReadonly = false;
+                break;
+            case BUSLOGIC_CCB_DIRECTION_OUT:
+            case BUSLOGIC_CCB_DIRECTION_NO_DATA:
+                fReadonly = true;
+                break;
+            default:
+                AssertMsgFailed(("Invalid data transfer direction type %u\n",
+                                    pTaskState->CommandControlBlockGuest.uDataDirection));
+        }
+
+        /* Map required buffers. */
+        rc = buslogicMapGCDataBufIntoR3(pTaskState, fReadonly);
+        AssertMsgRC(rc, ("Mapping failed rc=%Rrc\n", rc));
+
+        if (pTaskState->CommandControlBlockGuest.cbSenseData)
+        {
+            rc = buslogicMapGCSenseBufferIntoR3(pTaskState);
+            AssertMsgRC(rc, ("Mapping sense buffer failed rc=%Rrc\n", rc));
+        }
+
+        /* Check if device is present on bus. If not return error immediately and don't process this further. */
+        if (!pBusLogic->aDeviceStates[pTaskState->CommandControlBlockGuest.uTargetId].fPresent)
+        {
+            buslogicFreeGCDataBuffer(pTaskState);
+
+            if (pTaskState->pu8SenseBuffer)
+                buslogicFreeGCSenseBuffer(pTaskState);
+
+            buslogicSendIncomingMailbox(pBusLogic, pTaskState,
+                                        BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_SELECTION_TIMEOUT,
+                                        BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+                                        BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+
+            rc = RTCacheInsert(pBusLogic->pTaskCache, pTaskState);
+            AssertMsgRC(rc, ("Failed to insert task state into cache rc=%Rrc\n", rc));
+        }
+        else
+        {
+            /* Setup SCSI request. */
+            pTaskState->PDMScsiRequest.uLogicalUnit          = pTaskState->CommandControlBlockGuest.uLogicalUnit;
+
+            if (pTaskState->CommandControlBlockGuest.uDataDirection == BUSLOGIC_CCB_DIRECTION_UNKNOWN)
+                pTaskState->PDMScsiRequest.uDataDirection = PDMSCSIREQUESTTXDIR_UNKNOWN;
+            else if (pTaskState->CommandControlBlockGuest.uDataDirection == BUSLOGIC_CCB_DIRECTION_IN)
+                pTaskState->PDMScsiRequest.uDataDirection = PDMSCSIREQUESTTXDIR_FROM_DEVICE;
+            else if (pTaskState->CommandControlBlockGuest.uDataDirection == BUSLOGIC_CCB_DIRECTION_OUT)
+                pTaskState->PDMScsiRequest.uDataDirection = PDMSCSIREQUESTTXDIR_TO_DEVICE;
+            else if (pTaskState->CommandControlBlockGuest.uDataDirection == BUSLOGIC_CCB_DIRECTION_NO_DATA)
+                pTaskState->PDMScsiRequest.uDataDirection = PDMSCSIREQUESTTXDIR_NONE;
+            else
+                AssertMsgFailed(("Invalid data direction type %d\n", pTaskState->CommandControlBlockGuest.uDataDirection));
+
+            pTaskState->PDMScsiRequest.cbCDB                 = pTaskState->CommandControlBlockGuest.cbCDB;
+            pTaskState->PDMScsiRequest.pbCDB                 = pTaskState->CommandControlBlockGuest.aCDB;
+            pTaskState->PDMScsiRequest.cbScatterGather       = pTaskState->cbScatterGather;
+            pTaskState->PDMScsiRequest.cScatterGatherEntries = pTaskState->cScatterGather;
+            pTaskState->PDMScsiRequest.paScatterGatherHead   = pTaskState->paScatterGather;
+            pTaskState->PDMScsiRequest.cbSenseBuffer         = pTaskState->CommandControlBlockGuest.cbSenseData;
+            pTaskState->PDMScsiRequest.pbSenseBuffer         = pTaskState->pu8SenseBuffer;
+            pTaskState->PDMScsiRequest.pvUser                = pTaskState;
+
+            LogFlowFunc(("before increment %u\n", pTargetDevice->cOutstandingRequests));
+            ASMAtomicIncU32(&pTargetDevice->cOutstandingRequests);
+            LogFlowFunc(("after increment %u\n", pTargetDevice->cOutstandingRequests));
+            rc = pTargetDevice->pDrvSCSIConnector->pfnSCSIRequestSend(pTargetDevice->pDrvSCSIConnector, &pTaskState->PDMScsiRequest);
+            AssertMsgRC(rc, ("Sending request to SCSI layer failed rc=%Rrc\n", rc));
+        }
+    }
+    else if (pTaskState->MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_ABORT_COMMAND)
+    {
+        AssertMsgFailed(("Not implemented yet\n"));
+    }
+    else
+        AssertMsgFailed(("Invalid outgoing mailbox action code %u\n", pTaskState->MailboxGuest.u.out.uActionCode));
+
+    /* We got the mailbox, mark it as free in the guest. */
+    pTaskState->MailboxGuest.u.out.uActionCode = BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE;
+    PDMDevHlpPhysWrite(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrMailboxCurrent, &pTaskState->MailboxGuest, sizeof(Mailbox));
+
+    if (pBusLogic->fStrictRoundRobinMode)
+    {
+        pBusLogic->uMailboxOutgoingPositionCurrent++;
+
+        /* Check if we reached the end and start from the beginning if so. */
+        if (pBusLogic->uMailboxOutgoingPositionCurrent >= pBusLogic->cMailbox)
+            pBusLogic->uMailboxOutgoingPositionCurrent = 0;
+    }
+
+    return rc;
+}
+
+/**
+ * Transmit queue consumer
+ * Queue a new async task.
+ *
+ * @returns Success indicator.
+ *          If false the item will not be removed and the flushing will stop.
+ * @param   pDevIns     The device instance.
+ * @param   pItem       The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) buslogicNotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+    PBUSLOGIC  pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    AssertMsg(pBusLogic->cMailboxesReady > 0, ("Got notification without any mailboxes ready\n"));
+
+    /* Reset notification send flag now. */
+    ASMAtomicXchgBool(&pBusLogic->fNotificationSend, false);
+
+    /* Process mailboxes. */
+    do
+    {
+        int rc;
+
+        rc = buslogicProcessMailboxNext(pBusLogic);
+        AssertMsgRC(rc, ("Processing mailbox failed rc=%Rrc\n", rc));
+    } while (ASMAtomicDecU32(&pBusLogic->cMailboxesReady) > 0);
+
+    return true;
+}
+
+static bool buslogicWaitForAsyncIOFinished(PBUSLOGIC pBusLogic, unsigned cMillies)
+{
+    uint64_t u64Start;
+    bool     fIdle;
+
+    /*
+     * Wait for any pending async operation to finish
+     */
+    u64Start = RTTimeMilliTS();
+    do
+    {
+        fIdle = true;
+
+        /* Check every port. */
+        for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+        {
+            PBUSLOGICDEVICE pBusLogicDevice = &pBusLogic->aDeviceStates[i];
+            if (ASMAtomicReadU32(&pBusLogicDevice->cOutstandingRequests))
+            {
+                fIdle = false;
+                break;
+            }
+        }
+        if (RTTimeMilliTS() - u64Start >= cMillies)
+            break;
+
+        /* Sleep for a bit. */
+        RTThreadSleep(100);
+    } while (!fIdle);
+
+    return fIdle;
+}
+
+static DECLCALLBACK(int) buslogicSaveLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    /* Wait that no task is pending on any device. */
+    if (!buslogicWaitForAsyncIOFinished(pBusLogic, 20000))
+    {
+        AssertLogRelMsgFailed(("BusLogic: There are still tasks outstanding\n"));
+        return VERR_TIMEOUT;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) buslogicSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    /* Every device first. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+    {
+        PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+
+        AssertMsg(!pDevice->cOutstandingRequests,
+                  ("There are still outstanding requests on this device\n"));
+        SSMR3PutBool(pSSM, pDevice->fPresent);
+        SSMR3PutU32(pSSM, pDevice->cOutstandingRequests);
+    }
+    /* Now the main device state. */
+    SSMR3PutU8    (pSSM, pBusLogic->regStatus);
+    SSMR3PutU8    (pSSM, pBusLogic->regInterrupt);
+    SSMR3PutU8    (pSSM, pBusLogic->regGeometry);
+    SSMR3PutMem   (pSSM, &pBusLogic->LocalRam, sizeof(pBusLogic->LocalRam));
+    SSMR3PutU8    (pSSM, pBusLogic->uOperationCode);
+    SSMR3PutMem   (pSSM, &pBusLogic->aCommandBuffer, sizeof(pBusLogic->aCommandBuffer));
+    SSMR3PutU8    (pSSM, pBusLogic->iParameter);
+    SSMR3PutU8    (pSSM, pBusLogic->cbCommandParametersLeft);
+    SSMR3PutBool  (pSSM, pBusLogic->fUseLocalRam);
+    SSMR3PutMem   (pSSM, pBusLogic->aReplyBuffer, sizeof(pBusLogic->aReplyBuffer));
+    SSMR3PutU8    (pSSM, pBusLogic->iReply);
+    SSMR3PutU8    (pSSM, pBusLogic->cbReplyParametersLeft);
+    SSMR3PutBool  (pSSM, pBusLogic->fIRQEnabled);
+    SSMR3PutBool  (pSSM, pBusLogic->fISAEnabled);
+    SSMR3PutU32   (pSSM, pBusLogic->cMailbox);
+    SSMR3PutGCPhys(pSSM, pBusLogic->GCPhysAddrMailboxOutgoingBase);
+    SSMR3PutU32   (pSSM, pBusLogic->uMailboxOutgoingPositionCurrent);
+    SSMR3PutU32   (pSSM, pBusLogic->cMailboxesReady);
+    SSMR3PutBool  (pSSM, pBusLogic->fNotificationSend);
+    SSMR3PutGCPhys(pSSM, pBusLogic->GCPhysAddrMailboxIncomingBase);
+    SSMR3PutU32   (pSSM, pBusLogic->uMailboxIncomingPositionCurrent);
+    SSMR3PutBool  (pSSM, pBusLogic->fStrictRoundRobinMode);
+    SSMR3PutBool  (pSSM, pBusLogic->fExtendedLunCCBFormat);
+    /* Now the data for the BIOS interface. */
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.regIdentify);
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.uTargetDevice);
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.uTxDir);
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.cbCDB);
+    SSMR3PutMem   (pSSM, pBusLogic->VBoxSCSI.aCDB, sizeof(pBusLogic->VBoxSCSI.aCDB));
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.iCDB);
+    SSMR3PutU32   (pSSM, pBusLogic->VBoxSCSI.cbBuf);
+    SSMR3PutU32   (pSSM, pBusLogic->VBoxSCSI.iBuf);
+    SSMR3PutBool  (pSSM, pBusLogic->VBoxSCSI.fBusy);
+    SSMR3PutU8    (pSSM, pBusLogic->VBoxSCSI.enmState);
+    if (pBusLogic->VBoxSCSI.cbCDB)
+        SSMR3PutMem(pSSM, pBusLogic->VBoxSCSI.pBuf, pBusLogic->VBoxSCSI.cbBuf);
+
+    return SSMR3PutU32(pSSM, ~0);
+}
+
+static DECLCALLBACK(int) buslogicLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t u32Version)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    int rc = VINF_SUCCESS;
+
+    /* We support saved states only from this and older versions. */
+    if (u32Version > BUSLOGIC_SAVED_STATE_MINOR_VERSION)
+        return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+    /* Every device first. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+    {
+        PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+
+        AssertMsg(!pDevice->cOutstandingRequests,
+                  ("There are still outstanding requests on this device\n"));
+        SSMR3GetBool(pSSM, &pDevice->fPresent);
+        SSMR3GetU32(pSSM, (uint32_t *)&pDevice->cOutstandingRequests);
+    }
+    /* Now the main device state. */
+    SSMR3GetU8    (pSSM, (uint8_t *)&pBusLogic->regStatus);
+    SSMR3GetU8    (pSSM, (uint8_t *)&pBusLogic->regInterrupt);
+    SSMR3GetU8    (pSSM, (uint8_t *)&pBusLogic->regGeometry);
+    SSMR3GetMem   (pSSM, &pBusLogic->LocalRam, sizeof(pBusLogic->LocalRam));
+    SSMR3GetU8    (pSSM, &pBusLogic->uOperationCode);
+    SSMR3GetMem   (pSSM, &pBusLogic->aCommandBuffer, sizeof(pBusLogic->aCommandBuffer));
+    SSMR3GetU8    (pSSM, &pBusLogic->iParameter);
+    SSMR3GetU8    (pSSM, &pBusLogic->cbCommandParametersLeft);
+    SSMR3GetBool  (pSSM, &pBusLogic->fUseLocalRam);
+    SSMR3GetMem   (pSSM, pBusLogic->aReplyBuffer, sizeof(pBusLogic->aReplyBuffer));
+    SSMR3GetU8    (pSSM, &pBusLogic->iReply);
+    SSMR3GetU8    (pSSM, &pBusLogic->cbReplyParametersLeft);
+    SSMR3GetBool  (pSSM, &pBusLogic->fIRQEnabled);
+    SSMR3GetBool  (pSSM, &pBusLogic->fISAEnabled);
+    SSMR3GetU32   (pSSM, &pBusLogic->cMailbox);
+    SSMR3GetGCPhys(pSSM, &pBusLogic->GCPhysAddrMailboxOutgoingBase);
+    SSMR3GetU32   (pSSM, &pBusLogic->uMailboxOutgoingPositionCurrent);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pBusLogic->cMailboxesReady);
+    SSMR3GetBool  (pSSM, (bool *)&pBusLogic->fNotificationSend);
+    SSMR3GetGCPhys(pSSM, &pBusLogic->GCPhysAddrMailboxIncomingBase);
+    SSMR3GetU32   (pSSM, &pBusLogic->uMailboxIncomingPositionCurrent);
+    SSMR3GetBool  (pSSM, &pBusLogic->fStrictRoundRobinMode);
+    SSMR3GetBool  (pSSM, &pBusLogic->fExtendedLunCCBFormat);
+    /* Now the data for the BIOS interface. */
+    SSMR3GetU8  (pSSM, &pBusLogic->VBoxSCSI.regIdentify);
+    SSMR3GetU8  (pSSM, &pBusLogic->VBoxSCSI.uTargetDevice);
+    SSMR3GetU8  (pSSM, &pBusLogic->VBoxSCSI.uTxDir);
+    SSMR3GetU8  (pSSM, &pBusLogic->VBoxSCSI.cbCDB);
+    SSMR3GetMem (pSSM, pBusLogic->VBoxSCSI.aCDB, sizeof(pBusLogic->VBoxSCSI.aCDB));
+    SSMR3GetU8  (pSSM, &pBusLogic->VBoxSCSI.iCDB);
+    SSMR3GetU32 (pSSM, &pBusLogic->VBoxSCSI.cbBuf);
+    SSMR3GetU32 (pSSM, &pBusLogic->VBoxSCSI.iBuf);
+    SSMR3GetBool(pSSM, (bool *)&pBusLogic->VBoxSCSI.fBusy);
+    SSMR3GetU8  (pSSM, (uint8_t *)&pBusLogic->VBoxSCSI.enmState);
+    if (pBusLogic->VBoxSCSI.cbCDB)
+    {
+        pBusLogic->VBoxSCSI.pBuf = (uint8_t *)RTMemAllocZ(pBusLogic->VBoxSCSI.cbCDB);
+        if (!pBusLogic->VBoxSCSI.pBuf)
+        {
+            LogRel(("BusLogic: Out of memory during restore.\n"));
+            return PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY,
+                                    N_("BusLogic: Out of memory during restore\n"));
+        }
+        SSMR3GetMem(pSSM, pBusLogic->VBoxSCSI.pBuf, pBusLogic->VBoxSCSI.cbBuf);
+    }
+
+    uint32_t u32;
+    SSMR3GetU32(pSSM, &u32);
+    if (RT_FAILURE(rc))
+        return rc;
+    if (u32 != ~0U)
+    {
+        AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+        return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Gets the pointer to the status LED of a device - called from the SCSi driver.
+ *
+ * @returns VBox status code.
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ * @param   iLUN            The unit which status LED we desire. Always 0 here as the driver
+ *                          doesn't know about other LUN's.
+ * @param   ppLed           Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) buslogicDeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+    PBUSLOGICDEVICE pDevice = PDMILEDPORTS_2_PBUSLOGICDEVICE(pInterface);
+    if (iLUN == 0)
+    {
+        *ppLed = &pDevice->Led;
+        Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+        return VINF_SUCCESS;
+    }
+    return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to BUSLOGICDEVICE::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *) buslogicDeviceQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PBUSLOGICDEVICE pDevice = PDMIBASE_2_PBUSLOGICDEVICE(pInterface);
+
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_SCSI_PORT:
+            return &pDevice->ISCSIPort;
+        case PDMINTERFACE_LED_PORTS:
+            return &pDevice->ILed;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ * @param   iLUN            The unit which status LED we desire.
+ * @param   ppLed           Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) buslogicStatusQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+    PBUSLOGIC pBusLogic = PDMILEDPORTS_2_PBUSLOGIC(pInterface);
+    if (iLUN < BUSLOGIC_MAX_DEVICES)
+    {
+        *ppLed = &pBusLogic->aDeviceStates[iLUN].Led;
+        Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+        return VINF_SUCCESS;
+    }
+    return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to ATADevState::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *) buslogicStatusQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PBUSLOGIC pBusLogic = PDMIBASE_2_PBUSLOGIC(pInterface);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pBusLogic->IBase;
+        case PDMINTERFACE_LED_PORTS:
+            return &pBusLogic->ILeds;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Detach notification.
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) buslogicDetach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PBUSLOGIC       pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+
+    Log(("%s:\n", __FUNCTION__));
+
+    AssertMsg(fFlags & PDMDEVATT_FLAGS_NOT_HOT_PLUG,
+              ("BusLogic: Device does not support hotplugging\n"));
+
+    /*
+     * Zero some important members.
+     */
+    pDevice->pDrvBase = NULL;
+    pDevice->fPresent = false;
+    pDevice->pDrvSCSIConnector = NULL;
+}
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int)  buslogicAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PBUSLOGIC       pThis   = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+    int rc;
+
+    AssertMsgReturn(fFlags & PDMDEVATT_FLAGS_NOT_HOT_PLUG,
+                    ("BusLogic: Device does not support hotplugging\n"),
+                    VERR_INVALID_PARAMETER);
+
+    /* the usual paranoia */
+    AssertRelease(!pDevice->pDrvBase);
+    AssertRelease(!pDevice->pDrvSCSIConnector);
+    Assert(pDevice->iLUN == iLUN);
+
+    /*
+     * Try attach the block device and get the interfaces,
+     * required as well as optional.
+     */
+    rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, NULL);
+    if (RT_SUCCESS(rc))
+    {
+        /* Get SCSI connector interface. */
+        pDevice->pDrvSCSIConnector = (PPDMISCSICONNECTOR)pDevice->pDrvBase->pfnQueryInterface(pDevice->pDrvBase, PDMINTERFACE_SCSI_CONNECTOR);
+        AssertMsgReturn(pDevice->pDrvSCSIConnector, ("Missing SCSI interface below\n"), VERR_PDM_MISSING_INTERFACE);
+        pDevice->fPresent = true;
+    }
+    else
+        AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pDevice->iLUN, rc));
+
+    if (RT_FAILURE(rc))
+    {
+        pDevice->pDrvBase = NULL;
+        pDevice->pDrvSCSIConnector = NULL;
+    }
+    return rc;
+}
+
+static DECLCALLBACK(void) buslogicSuspend(PPDMDEVINS pDevIns)
+{
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    /* Wait that no task is pending on any device. */
+    if (!buslogicWaitForAsyncIOFinished(pBusLogic, 20000))
+        AssertLogRelMsgFailed(("BusLogic: There are still tasks outstanding\n"));
+}
+
+static DECLCALLBACK(void) buslogicRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+    uint32_t i;
+    PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    pBusLogic->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+    pBusLogic->pNotifierQueueRC = PDMQueueRCPtr(pBusLogic->pNotifierQueueR3);
+
+    for (i = 0; i < BUSLOGIC_MAX_DEVICES; i++)
+    {
+        PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+
+        pDevice->pBusLogicRC = PDMINS_2_DATA_RCPTR(pDevIns);
+    }
+
+}
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(void)  buslogicReset(PPDMDEVINS pDevIns)
+{
+    PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    buslogicHwReset(pThis);
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDevIns     The device instance data.
+ */
+static DECLCALLBACK(int) buslogicDestruct(PPDMDEVINS pDevIns)
+{
+    int rc = VINF_SUCCESS;
+    PBUSLOGIC  pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+    rc = RTCacheDestroy(pThis->pTaskCache);
+    AssertMsgRC(rc, ("Destroying task cache failed rc=%Rrc\n", rc));
+
+    return rc;
+}
+
+/**
+ * Construct a device instance for a VM.
+ *
+ * @returns VBox status.
+ * @param   pDevIns     The device instance data.
+ *                      If the registration structure is needed, pDevIns->pDevReg points to it.
+ * @param   iInstance   Instance number. Use this to figure out which registers and such to use.
+ *                      The device number is also found in pDevIns->iInstance, but since it's
+ *                      likely to be freqently used PDM passes it as parameter.
+ * @param   pCfgHandle  Configuration node handle for the device. Use this to obtain the configuration
+ *                      of the device instance. It's also found in pDevIns->pCfgHandle, but like
+ *                      iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) buslogicConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
+{
+    PBUSLOGIC  pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+    int        rc = VINF_SUCCESS;
+
+    /*
+     * Validate and read configuration.
+     */
+    rc = CFGMR3AreValuesValid(pCfgHandle, "GCEnabled\0"
+                                          "R0Enabled\0");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+                                N_("BusLogic configuration error: unknown option specified"));
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "GCEnabled", &pThis->fGCEnabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("BusLogic configuration error: failed to read GCEnabled as boolean"));
+    Log(("%s: fGCEnabled=%d\n", __FUNCTION__, pThis->fGCEnabled));
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "R0Enabled", &pThis->fR0Enabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("BusLogic configuration error: failed to read R0Enabled as boolean"));
+    Log(("%s: fR0Enabled=%d\n", __FUNCTION__, pThis->fR0Enabled));
+
+
+    pThis->pDevInsR3 = pDevIns;
+    pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+    pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+    pThis->IBase.pfnQueryInterface = buslogicStatusQueryInterface;
+    pThis->ILeds.pfnQueryStatusLed = buslogicStatusQueryStatusLed;
+
+    PCIDevSetVendorId         (&pThis->dev, 0x104b); /* BusLogic */
+    PCIDevSetDeviceId         (&pThis->dev, 0x1040); /* BT-958 */
+    PCIDevSetCommand          (&pThis->dev, 0x0003);
+    PCIDevSetRevisionId       (&pThis->dev, 0x01);
+    PCIDevSetClassProg        (&pThis->dev, 0x00); /* SCSI */
+    PCIDevSetClassSub         (&pThis->dev, 0x00); /* SCSI */
+    PCIDevSetClassBase        (&pThis->dev, 0x01); /* Mass storage */
+    PCIDevSetBaseAddress      (&pThis->dev, 0, true, false, false, 0x00000000);
+    PCIDevSetBaseAddress      (&pThis->dev, 1, false, false, false, 0x00000000);
+    PCIDevSetSubSystemVendorId(&pThis->dev, 0x104b);
+    PCIDevSetSubSystemId      (&pThis->dev, 0x1040);
+    PCIDevSetInterruptLine    (&pThis->dev, 0x00);
+    PCIDevSetInterruptPin     (&pThis->dev, 0x01);
+
+    /*
+     * Register the PCI device, it's I/O regions.
+     */
+    rc = PDMDevHlpPCIRegister (pDevIns, &pThis->dev);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 32, PCI_ADDRESS_SPACE_IO, buslogicMMIOMap);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 32, PCI_ADDRESS_SPACE_MEM, buslogicMMIOMap);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    /* Register I/O port space in ISA region for BIOS access. */
+    rc = PDMDevHlpIOPortRegister(pDevIns, BUSLOGIC_ISA_IO_PORT, 3, NULL,
+                                 buslogicIsaIOPortWrite, buslogicIsaIOPortRead,
+                                 buslogicIsaIOPortWriteStr, buslogicIsaIOPortReadStr,
+                                 "BusLogic BIOS");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register legacy I/O handlers"));
+
+    /* Initialize task cache. */
+    rc = RTCacheCreate(&pThis->pTaskCache, 0 /* unlimited */, sizeof(BUSLOGICTASKSTATE), RTOBJCACHE_PROTECT_INSERT);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("BusLogic: Failed to initialize task cache\n"));
+
+    /* Intialize task queue. */
+    rc = PDMDevHlpPDMQueueCreate(pDevIns, sizeof(PDMQUEUEITEMCORE), 5, 0,
+                                 buslogicNotifyQueueConsumer, true, &pThis->pNotifierQueueR3);
+    if (RT_FAILURE(rc))
+        return rc;
+    pThis->pNotifierQueueR0 = PDMQueueR0Ptr(pThis->pNotifierQueueR3);
+    pThis->pNotifierQueueRC = PDMQueueRCPtr(pThis->pNotifierQueueR3);
+
+    /* Initialize per device state. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+    {
+        char szName[24];
+        PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[i];
+
+        RTStrPrintf(szName, sizeof(szName), "Device%d", i);
+
+        /* Initialize static parts of the device. */
+        pDevice->iLUN = i;
+        pDevice->pBusLogicR3 = pThis;
+        pDevice->pBusLogicR0 = PDMINS_2_DATA_R0PTR(pDevIns);
+        pDevice->pBusLogicRC = PDMINS_2_DATA_RCPTR(pDevIns);
+        pDevice->Led.u32Magic = PDMLED_MAGIC;
+        pDevice->IBase.pfnQueryInterface           = buslogicDeviceQueryInterface;
+        pDevice->ISCSIPort.pfnSCSIRequestCompleted = buslogicDeviceSCSIRequestCompleted;
+        pDevice->ILed.pfnQueryStatusLed            = buslogicDeviceQueryStatusLed;
+
+        /* Attach SCSI driver. */
+        rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, szName);
+        if (RT_SUCCESS(rc))
+        {
+            /* Get SCSI connector interface. */
+            pDevice->pDrvSCSIConnector = (PPDMISCSICONNECTOR)pDevice->pDrvBase->pfnQueryInterface(pDevice->pDrvBase, PDMINTERFACE_SCSI_CONNECTOR);
+            AssertMsgReturn(pDevice->pDrvSCSIConnector, ("Missing SCSI interface below\n"), VERR_PDM_MISSING_INTERFACE);
+
+            pDevice->fPresent = true;
+        }
+        else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+        {
+            pDevice->pDrvBase = NULL;
+            pDevice->fPresent = false;
+            rc = VINF_SUCCESS;
+            Log(("BusLogic: no driver attached to device %s\n", szName));
+        }
+        else
+        {
+            AssertLogRelMsgFailed(("BusLogic: Failed to attach %s\n", szName));
+            return rc;
+        }
+    }
+
+    /*
+     * Attach status driver (optional).
+     */
+    PPDMIBASE pBase;
+    rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+    if (RT_SUCCESS(rc))
+        pThis->pLedsConnector = (PDMILEDCONNECTORS *)pBase->pfnQueryInterface(pBase, PDMINTERFACE_LED_CONNECTORS);
+    else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+    {
+        AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot attach to status driver"));
+    }
+
+    rc = PDMDevHlpSSMRegister(pDevIns, pDevIns->pDevReg->szDeviceName, iInstance,
+                              BUSLOGIC_SAVED_STATE_MINOR_VERSION, sizeof(*pThis),
+                              buslogicSaveLoadPrep, buslogicSaveExec, NULL,
+                              buslogicSaveLoadPrep, buslogicLoadExec, NULL);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register save state handlers"));
+
+    rc = buslogicHwReset(pThis);
+    AssertMsgRC(rc, ("hardware reset of BusLogic host adapter failed rc=%Rrc\n", rc));
+
+    return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceBusLogic =
+{
+    /* u32Version */
+    PDM_DEVREG_VERSION,
+    /* szDeviceName */
+    "buslogic",
+    /* szRCMod */
+    "VBoxDDGC.gc",
+    /* szR0Mod */
+    "VBoxDDR0.r0",
+    /* pszDescription */
+    "BusLogic BT-958 SCSI host adapter.\n",
+    /* fFlags */
+      PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0
+    | PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION
+    | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+    /* fClass */
+    PDM_DEVREG_CLASS_STORAGE,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(BUSLOGIC),
+    /* pfnConstruct */
+    buslogicConstruct,
+    /* pfnDestruct */
+    buslogicDestruct,
+    /* pfnRelocate */
+    buslogicRelocate,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    buslogicReset,
+    /* pfnSuspend */
+    buslogicSuspend,
+    /* pfnResume */
+    NULL,
+    /* pfnAttach */
+    buslogicAttach,
+    /* pfnDetach */
+    buslogicDetach,
+    /* pfnQueryInterface. */
+    NULL,
+    /* pfnInitComplete */
+    NULL,
+    /* pfnPowerOff */
+    NULL,
+    /* pfnSoftReset */
+    NULL,
+    /* u32VersionEnd */
+    PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
Index: trunk/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp	(revision 21321)
@@ -0,0 +1,5496 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * LsiLogic LSI53c1030 SCSI controller.
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DEV_LSILOGICSCSI
+#include <VBox/pdmdev.h>
+#include <VBox/pdmqueue.h>
+#include <VBox/pdmcritsect.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/param.h>
+# include <iprt/alloc.h>
+# include <iprt/cache.h>
+#endif
+
+#include "VBoxSCSI.h"
+
+#include "../Builtins.h"
+
+/* I/O port registered in the ISA compatible range to let the BIOS access
+ * the controller.
+ */
+#define LSILOGIC_ISA_IO_PORT 0x340
+
+#define LSILOGIC_PORTS_MAX 1
+#define LSILOGIC_BUSES_MAX 1
+#define LSILOGIC_DEVICES_PER_BUS_MAX 16
+
+#define LSILOGIC_DEVICES_MAX (LSILOGIC_BUSES_MAX*LSILOGIC_DEVICES_PER_BUS_MAX)
+
+#define LSILOGICSCSI_REQUEST_QUEUE_DEPTH_DEFAULT 1024
+#define LSILOGICSCSI_REPLY_QUEUE_DEPTH_DEFAULT   128
+
+#define LSILOGICSCSI_MAXIMUM_CHAIN_DEPTH 3
+
+#define LSILOGIC_NR_OF_ALLOWED_BIGGER_LISTS 100
+
+#define LSILOGICSCSI_PCI_VENDOR_ID            (0x1000)
+#define LSILOGICSCSI_PCI_DEVICE_ID            (0x0030)
+#define LSILOGICSCSI_PCI_REVISION_ID          (0x00)
+#define LSILOGICSCSI_PCI_CLASS_CODE           (0x01)
+#define LSILOGICSCSI_PCI_SUBSYSTEM_VENDOR_ID  (0x1000)
+#define LSILOGICSCSI_PCI_SUBSYSTEM_ID         (0x8000)
+
+#define LSILOGIC_SAVED_STATE_MINOR_VERSION 1
+
+/**
+ * A simple SG element for a 64bit adress.
+ */
+#pragma pack(1)
+typedef struct MptSGEntrySimple64
+{
+    /** Length of the buffer this entry describes. */
+    unsigned u24Length:          24;
+    /** Flag whether this element is the end of the list. */
+    unsigned fEndOfList:          1;
+    /** Flag whether the address is 32bit or 64bits wide. */
+    unsigned f64BitAddress:       1;
+    /** Flag whether this buffer contains data to be transfered or is the destination. */
+    unsigned fBufferContainsData: 1;
+    /** Flag whether this is a local address or a system address. */
+    unsigned fLocalAddress:       1;
+    /** Element type. */
+    unsigned u2ElementType:       2;
+    /** Flag whether this is the last element of the buffer. */
+    unsigned fEndOfBuffer:        1;
+    /** Flag whether this is the last element of the current segment. */
+    unsigned fLastElement:        1;
+    /** Lower 32bits of the address of the data buffer. */
+    unsigned u32DataBufferAddressLow: 32;
+    /** Upper 32bits of the address of the data buffer. */
+    unsigned u32DataBufferAddressHigh: 32;
+} MptSGEntrySimple64, *PMptSGEntrySimple64;
+#pragma pack()
+AssertCompileSize(MptSGEntrySimple64, 12);
+
+/**
+ * A simple SG element for a 32bit adress.
+ */
+#pragma pack(1)
+typedef struct MptSGEntrySimple32
+{
+    /** Length of the buffer this entry describes. */
+    unsigned u24Length:          24;
+    /** Flag whether this element is the end of the list. */
+    unsigned fEndOfList:          1;
+    /** Flag whether the address is 32bit or 64bits wide. */
+    unsigned f64BitAddress:       1;
+    /** Flag whether this buffer contains data to be transfered or is the destination. */
+    unsigned fBufferContainsData: 1;
+    /** Flag whether this is a local address or a system address. */
+    unsigned fLocalAddress:       1;
+    /** Element type. */
+    unsigned u2ElementType:       2;
+    /** Flag whether this is the last element of the buffer. */
+    unsigned fEndOfBuffer:        1;
+    /** Flag whether this is the last element of the current segment. */
+    unsigned fLastElement:        1;
+    /** Lower 32bits of the address of the data buffer. */
+    unsigned u32DataBufferAddressLow: 32;
+} MptSGEntrySimple32, *PMptSGEntrySimple32;
+#pragma pack()
+AssertCompileSize(MptSGEntrySimple32, 8);
+
+/**
+ * A chain SG element.
+ */
+#pragma pack(1)
+typedef struct MptSGEntryChain
+{
+    /** Size of the segment. */
+    unsigned u16Length: 16;
+    /** Offset in 32bit words of the next chain element in the segment
+     *  identified by this element. */
+    unsigned u8NextChainOffset: 8;
+    /** Reserved. */
+    unsigned fReserved0:    1;
+    /** Flag whether the address is 32bit or 64bits wide. */
+    unsigned f64BitAddress: 1;
+    /** Reserved. */
+    unsigned fReserved1:    1;
+    /** Flag whether this is a local address or a system address. */
+    unsigned fLocalAddress: 1;
+    /** Element type. */
+    unsigned u2ElementType: 2;
+    /** Flag whether this is the last element of the buffer. */
+    unsigned u2Reserved2:   2;
+    /** Lower 32bits of the address of the data buffer. */
+    unsigned u32SegmentAddressLow: 32;
+    /** Upper 32bits of the address of the data buffer. */
+    unsigned u32SegmentAddressHigh: 32;
+} MptSGEntryChain, *PMptSGEntryChain;
+#pragma pack()
+AssertCompileSize(MptSGEntryChain, 12);
+
+typedef union MptSGEntryUnion
+{
+    MptSGEntrySimple64 Simple64;
+    MptSGEntrySimple32 Simple32;
+    MptSGEntryChain    Chain;
+} MptSGEntryUnion, *PMptSGEntryUnion;
+
+/**
+ * MPT Fusion message header - Common for all message frames.
+ * This is filled in by the guest.
+ */
+#pragma pack(1)
+typedef struct MptMessageHdr
+{
+    /** Function dependent data. */
+    uint16_t    u16FunctionDependent;
+    /** Chain offset. */
+    uint8_t     u8ChainOffset;
+    /** The function code. */
+    uint8_t     u8Function;
+    /** Function dependent data. */
+    uint8_t     au8FunctionDependent[3];
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context - Unique ID from the guest unmodified by the device. */
+    uint32_t    u32MessageContext;
+} MptMessageHdr, *PMptMessageHdr;
+#pragma pack()
+AssertCompileSize(MptMessageHdr, 12);
+
+/** Defined function codes found in the message header. */
+#define MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST        (0x00)
+#define MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT         (0x01)
+#define MPT_MESSAGE_HDR_FUNCTION_IOC_INIT               (0x02)
+#define MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS              (0x03)
+#define MPT_MESSAGE_HDR_FUNCTION_CONFIG                 (0x04)
+#define MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS             (0x05)
+#define MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE            (0x06)
+#define MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION     (0x07)
+#define MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK              (0x08)
+#define MPT_MESSAGE_HDR_FUNCTION_FW_DOWNLOAD            (0x09)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_CMD_BUFFER_POST (0x0A)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_ASSIST          (0x0B)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_STATUS_SEND     (0x0C)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_MODE_ABORT      (0x0D)
+
+#ifdef DEBUG
+/**
+ * Function names
+ */
+static const char * const g_apszMPTFunctionNames[] =
+{
+    "SCSI I/O Request",
+    "SCSI Task Management",
+    "IOC Init",
+    "IOC Facts",
+    "Config",
+    "Port Facts",
+    "Port Enable",
+    "Event Notification",
+    "Event Ack",
+    "Firmware Download"
+};
+#endif
+
+/**
+ * Default reply message.
+ * Send from the device to the guest upon completion of a request.
+ */
+ #pragma pack(1)
+typedef struct MptDefaultReplyMessage
+{
+    /** Function dependent data. */
+    uint16_t    u16FunctionDependent;
+    /** Length of the message in 32bit DWords. */
+    uint8_t     u8MessageLength;
+    /** Function which completed. */
+    uint8_t     u8Function;
+    /** Function dependent. */
+    uint8_t     au8FunctionDependent[3];
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context given in the request. */
+    uint32_t    u32MessageContext;
+    /** Function dependent status code. */
+    uint16_t    u16FunctionDependentStatus;
+    /** Status of the IOC. */
+    uint16_t    u16IOCStatus;
+    /** Additional log info. */
+    uint32_t    u32IOCLogInfo;
+} MptDefaultReplyMessage, *PMptDefaultReplyMessage;
+#pragma pack()
+AssertCompileSize(MptDefaultReplyMessage, 20);
+
+/**
+ * IO controller init request.
+ */
+#pragma pack(1)
+typedef struct MptIOCInitRequest
+{
+    /** Which system send this init request. */
+    uint8_t     u8WhoInit;
+    /** Reserved */
+    uint8_t     u8Reserved;
+    /** Chain offset in the SG list. */
+    uint8_t     u8ChainOffset;
+    /** Function to execute. */
+    uint8_t     u8Function;
+    /** Flags */
+    uint8_t     u8Flags;
+    /** Maximum number of devices the driver can handle. */
+    uint8_t     u8MaxDevices;
+    /** Maximum number of buses the driver can handle. */
+    uint8_t     u8MaxBuses;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** Reply frame size. */
+    uint16_t    u16ReplyFrameSize;
+    /** Reserved */
+    uint16_t    u16Reserved;
+    /** Upper 32bit part of the 64bit address the message frames are in.
+     *  That means all frames must be in the same 4GB segment. */
+    uint32_t    u32HostMfaHighAddr;
+    /** Upper 32bit of the sense buffer. */
+    uint32_t    u32SenseBufferHighAddr;
+} MptIOCInitRequest, *PMptIOCInitRequest;
+#pragma pack()
+AssertCompileSize(MptIOCInitRequest, 24);
+
+/**
+ * IO controller init reply.
+ */
+#pragma pack(1)
+typedef struct MptIOCInitReply
+{
+    /** Which subsystem send this init request. */
+    uint8_t     u8WhoInit;
+    /** Reserved */
+    uint8_t     u8Reserved;
+    /** Message length */
+    uint8_t     u8MessageLength;
+    /** Function. */
+    uint8_t     u8Function;
+    /** Flags */
+    uint8_t     u8Flags;
+    /** Maximum number of devices the driver can handle. */
+    uint8_t     u8MaxDevices;
+    /** Maximum number of busses the driver can handle. */
+    uint8_t     u8MaxBuses;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID */
+    uint32_t    u32MessageContext;
+    /** Reserved */
+    uint16_t    u16Reserved;
+    /** IO controller status. */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information. */
+    uint32_t    u32IOCLogInfo;
+} MptIOCInitReply, *PMptIOCInitReply;
+#pragma pack()
+AssertCompileSize(MptIOCInitReply, 20);
+
+/**
+ * IO controller facts request.
+ */
+#pragma pack(1)
+typedef struct MptIOCFactsRequest
+{
+    /** Reserved. */
+    uint16_t    u16Reserved;
+    /** Chain offset in SG list. */
+    uint8_t     u8ChainOffset;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint8_t     u8Reserved[3];
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+} MptIOCFactsRequest, *PMptIOCFactsRequest;
+#pragma pack()
+AssertCompileSize(MptIOCFactsRequest, 12);
+
+/**
+ * IO controller facts reply.
+ */
+#pragma pack(1)
+typedef struct MptIOCFactsReply
+{
+    /** Message version. */
+    uint16_t    u16MessageVersion;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint16_t    u16Reserved1;
+    /** IO controller number */
+    uint8_t     u8IOCNumber;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** IO controller exceptions */
+    uint16_t    u16IOCExceptions;
+    /** IO controller status. */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information. */
+    uint32_t    u32IOCLogInfo;
+    /** Maximum chain depth. */
+    uint8_t     u8MaxChainDepth;
+    /** The current value of the WhoInit field. */
+    uint8_t     u8WhoInit;
+    /** Block size. */
+    uint8_t     u8BlockSize;
+    /** Flags. */
+    uint8_t     u8Flags;
+    /** Depth of the reply queue. */
+    uint16_t    u16ReplyQueueDepth;
+    /** Size of a request frame. */
+    uint16_t    u16RequestFrameSize;
+    /** Reserved */
+    uint16_t    u16Reserved2;
+    /** Product ID. */
+    uint16_t    u16ProductID;
+    /** Current value of the high 32bit MFA address. */
+    uint32_t    u32CurrentHostMFAHighAddr;
+    /** Global credits - Number of entries allocated to queues */
+    uint16_t    u16GlobalCredits;
+    /** Number of ports on the IO controller */
+    uint8_t     u8NumberOfPorts;
+    /** Event state. */
+    uint8_t     u8EventState;
+    /** Current value of the high 32bit sense buffer address. */
+    uint32_t    u32CurrentSenseBufferHighAddr;
+    /** Current reply frame size. */
+    uint16_t    u16CurReplyFrameSize;
+    /** Maximum number of devices. */
+    uint8_t     u8MaxDevices;
+    /** Maximum number of buses. */
+    uint8_t     u8MaxBuses;
+    /** Size of the firmware image. */
+    uint32_t    u32FwImageSize;
+    /** Reserved. */
+    uint32_t    u32Reserved;
+    /** Firmware version */
+    uint32_t    u32FWVersion;
+} MptIOCFactsReply, *PMptIOCFactsReply;
+#pragma pack()
+AssertCompileSize(MptIOCFactsReply, 60);
+
+/**
+ * Port facts request
+ */
+#pragma pack(1)
+typedef struct MptPortFactsRequest
+{
+    /** Reserved */
+    uint16_t    u16Reserved1;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint16_t    u16Reserved2;
+    /** Port number to get facts for. */
+    uint8_t     u8PortNumber;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+} MptPortFactsRequest, *PMptPortFactsRequest;
+#pragma pack()
+AssertCompileSize(MptPortFactsRequest, 12);
+
+/**
+ * Port facts reply.
+ */
+#pragma pack(1)
+typedef struct MptPortFactsReply
+{
+    /** Reserved. */
+    uint16_t    u16Reserved1;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint16_t    u16Reserved2;
+    /** Port number the facts are for. */
+    uint8_t     u8PortNumber;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** Reserved. */
+    uint16_t    u16Reserved3;
+    /** IO controller status. */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information. */
+    uint32_t    u32IOCLogInfo;
+    /** Reserved */
+    uint8_t     u8Reserved;
+    /** Port type */
+    uint8_t     u8PortType;
+    /** Maximum number of devices on this port. */
+    uint16_t    u16MaxDevices;
+    /** SCSI ID of this port on the attached bus. */
+    uint16_t    u16PortSCSIID;
+    /** Protocol flags. */
+    uint16_t    u16ProtocolFlags;
+    /** Maxmimum number of target command buffers which can be posted to this port at a time. */
+    uint16_t    u16MaxPostedCmdBuffers;
+    /** Maximum number of target IDs that remain persistent between power/reset cycles. */
+    uint16_t    u16MaxPersistentIDs;
+    /** Maximum number of LAN buckets. */
+    uint16_t    u16MaxLANBuckets;
+    /** Reserved. */
+    uint16_t    u16Reserved4;
+    /** Reserved. */
+    uint32_t    u32Reserved;
+} MptPortFactsReply, *PMptPortFactsReply;
+#pragma pack()
+AssertCompileSize(MptPortFactsReply, 40);
+
+/**
+ * Port Enable request.
+ */
+#pragma pack(1)
+typedef struct MptPortEnableRequest
+{
+    /** Reserved. */
+    uint16_t    u16Reserved1;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved. */
+    uint16_t    u16Reserved2;
+    /** Port number to enable. */
+    uint8_t     u8PortNumber;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+} MptPortEnableRequest, *PMptPortEnableRequest;
+#pragma pack()
+AssertCompileSize(MptPortEnableRequest, 12);
+
+/**
+ * Port enable reply.
+ */
+#pragma pack(1)
+typedef struct MptPortEnableReply
+{
+    /** Reserved. */
+    uint16_t    u16Reserved1;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint16_t    u16Reserved2;
+    /** Port number which was enabled. */
+    uint8_t     u8PortNumber;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** Reserved. */
+    uint16_t    u16Reserved3;
+    /** IO controller status */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information. */
+    uint32_t    u32IOCLogInfo;
+} MptPortEnableReply, *PMptPortEnableReply;
+#pragma pack()
+AssertCompileSize(MptPortEnableReply, 20);
+
+/**
+ * Event notification request.
+ */
+#pragma pack(1)
+typedef struct MptEventNotificationRequest
+{
+    /** Switch - Turns event notification on and off. */
+    uint8_t     u8Switch;
+    /** Reserved. */
+    uint8_t     u8Reserved1;
+    /** Chain offset. */
+    uint8_t     u8ChainOffset;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved. */
+    uint8_t     u8reserved2[3];
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+} MptEventNotificationRequest, *PMptEventNotificationRequest;
+#pragma pack()
+AssertCompileSize(MptEventNotificationRequest, 12);
+
+/**
+ * Event notification reply.
+ */
+#pragma pack(1)
+typedef struct MptEventNotificationReply
+{
+    /** Event data length. */
+    uint16_t    u16EventDataLength;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** Reserved. */
+    uint16_t    u16Reserved1;
+    /** Ack required. */
+    uint8_t     u8AckRequired;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** Reserved. */
+    uint16_t    u16Reserved2;
+    /** IO controller status. */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information. */
+    uint32_t    u32IOCLogInfo;
+    /** Notification event. */
+    uint32_t    u32Event;
+    /** Event context. */
+    uint32_t    u32EventContext;
+    /** Event data. */
+    uint32_t    u32EventData;
+} MptEventNotificationReply, *PMptEventNotificationReply;
+#pragma pack()
+AssertCompileSize(MptEventNotificationReply, 32);
+
+#define MPT_EVENT_EVENT_CHANGE (0x0000000a)
+
+/**
+ * SCSI IO Request
+ */
+#pragma pack(1)
+typedef struct MptSCSIIORequest
+{
+    /** Target ID */
+    uint8_t     u8TargetID;
+    /** Bus number */
+    uint8_t     u8Bus;
+    /** Chain offset */
+    uint8_t     u8ChainOffset;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** CDB length. */
+    uint8_t     u8CDBLength;
+    /** Sense buffer length. */
+    uint8_t     u8SenseBufferLength;
+    /** Rserved */
+    uint8_t     u8Reserved;
+    /** Message flags. */
+    uint8_t     u8MessageFlags;
+    /** Message context ID. */
+    uint32_t    u32MessageContext;
+    /** LUN */
+    uint8_t     au8LUN[8];
+    /** Control values. */
+    uint32_t    u32Control;
+    /** The CDB. */
+    uint8_t     au8CDB[16];
+    /** Data length. */
+    uint32_t    u32DataLength;
+    /** Sense buffer low 32bit address. */
+    uint32_t    u32SenseBufferLowAddress;
+} MptSCSIIORequest, *PMptSCSIIORequest;
+#pragma pack()
+AssertCompileSize(MptSCSIIORequest, 48);
+
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_GET(x) (((x) & 0x3000000) >> 24)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE  (0x0)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE (0x1)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ  (0x2)
+
+/**
+ * SCSI IO error reply.
+ */
+#pragma pack(1)
+typedef struct MptSCSIIOErrorReply
+{
+    /** Target ID */
+    uint8_t     u8TargetID;
+    /** Bus number */
+    uint8_t     u8Bus;
+    /** Message length. */
+    uint8_t     u8MessageLength;
+    /** Function number. */
+    uint8_t     u8Function;
+    /** CDB length */
+    uint8_t     u8CDBLength;
+    /** Sense buffer length */
+    uint8_t     u8SenseBufferLength;
+    /** Reserved */
+    uint8_t     u8Reserved;
+    /** Message flags */
+    uint8_t     u8MessageFlags;
+    /** Message context ID */
+    uint32_t    u32MessageContext;
+    /** SCSI status. */
+    uint8_t     u8SCSIStatus;
+    /** SCSI state */
+    uint8_t     u8SCSIState;
+    /** IO controller status */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information */
+    uint32_t    u32IOCLogInfo;
+    /** Transfer count */
+    uint32_t    u32TransferCount;
+    /** Sense count */
+    uint32_t    u32SenseCount;
+    /** Response information */
+    uint32_t    u32ResponseInfo;
+} MptSCSIIOErrorReply, *PMptSCSIIOErrorReply;
+#pragma pack()
+AssertCompileSize(MptSCSIIOErrorReply, 32);
+
+#define MPT_SCSI_IO_ERROR_SCSI_STATE_AUTOSENSE_VALID (0x01)
+#define MPT_SCSI_IO_ERROR_SCSI_STATE_TERMINATED      (0x08)
+
+/**
+ * IOC status codes sepcific to the SCSI I/O error reply.
+ */
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_BUS      (0x0041)
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_TARGETID (0x0042)
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE (0x0043)
+
+/**
+ * SCSI task management request.
+ */
+#pragma pack(1)
+typedef struct MptSCSITaskManagementRequest
+{
+    /** Target ID */
+    uint8_t     u8TargetID;
+    /** Bus number */
+    uint8_t     u8Bus;
+    /** Chain offset */
+    uint8_t     u8ChainOffset;
+    /** Function number */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint8_t     u8Reserved1;
+    /** Task type */
+    uint8_t     u8TaskType;
+    /** Reserved */
+    uint8_t     u8Reserved2;
+    /** Message flags */
+    uint8_t     u8MessageFlags;
+    /** Message context ID */
+    uint32_t    u32MessageContext;
+    /** LUN */
+    uint8_t     au8LUN[8];
+    /** Reserved */
+    uint8_t     auReserved[28];
+    /** Task message context ID. */
+    uint32_t    u32TaskMessageContext;
+} MptSCSITaskManagementRequest, *PMptSCSITaskManagementRequest;
+#pragma pack()
+AssertCompileSize(MptSCSITaskManagementRequest, 52);
+
+/**
+ * SCSI task management reply.
+ */
+#pragma pack(1)
+typedef struct MptSCSITaskManagementReply
+{
+    /** Target ID */
+    uint8_t     u8TargetID;
+    /** Bus number */
+    uint8_t     u8Bus;
+    /** Message length */
+    uint8_t     u8MessageLength;
+    /** Function number */
+    uint8_t     u8Function;
+    /** Reserved */
+    uint8_t     u8Reserved1;
+    /** Task type */
+    uint8_t     u8TaskType;
+    /** Reserved */
+    uint8_t     u8Reserved2;
+    /** Message flags */
+    uint8_t     u8MessageFlags;
+    /** Message context ID */
+    uint32_t    u32MessageContext;
+    /** Reserved */
+    uint16_t    u16Reserved;
+    /** IO controller status */
+    uint16_t    u16IOCStatus;
+    /** IO controller log information */
+    uint32_t    u32IOCLogInfo;
+    /** Termination count */
+    uint32_t    u32TerminationCount;
+} MptSCSITaskManagementReply, *PMptSCSITaskManagementReply;
+#pragma pack()
+AssertCompileSize(MptSCSITaskManagementReply, 24);
+
+/**
+ * Configuration request
+ */
+#pragma pack(1)
+typedef struct MptConfigurationRequest
+{
+    /** Action code. */
+    uint8_t    u8Action;
+    /** Reserved. */
+    uint8_t    u8Reserved1;
+    /** Chain offset. */
+    uint8_t    u8ChainOffset;
+    /** Function number. */
+    uint8_t    u8Function;
+    /** Reserved. */
+    uint8_t    u8Reserved2[3];
+    /** Message flags. */
+    uint8_t    u8MessageFlags;
+    /** Message context ID. */
+    uint32_t   u32MessageContext;
+    /** Reserved. */
+    uint8_t    u8Reserved3[8];
+    /** Version number of the page. */
+    uint8_t    u8PageVersion;
+    /** Length of the page in 32bit Dwords. */
+    uint8_t    u8PageLength;
+    /** Page number to access. */
+    uint8_t    u8PageNumber;
+    /** Type of the page beeing accessed. */
+    uint8_t    u8PageType;
+    /** Page type dependent address. */
+    union
+    {
+        /** 32bit view. */
+        uint32_t u32PageAddress;
+        struct
+        {
+            /** Port number to get the configuration page for. */
+            uint8_t u8PortNumber;
+            /** Reserved. */
+            uint8_t u8Reserved[3];
+        } MPIPortNumber;
+        struct
+        {
+            /** Target ID to get the configuration page for. */
+            uint8_t u8TargetID;
+            /** Bus number to get the configuration page for. */
+            uint8_t u8Bus;
+            /** Reserved. */
+            uint8_t u8Reserved[2];
+        } BusAndTargetId;
+    } u;
+    MptSGEntrySimple64 SimpleSGElement;
+} MptConfigurationRequest, *PMptConfigurationRequest;
+#pragma pack()
+AssertCompileSize(MptConfigurationRequest, 40);
+
+/** Possible action codes. */
+#define MPT_CONFIGURATION_REQUEST_ACTION_HEADER        (0x00)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_CURRENT  (0x01)
+#define MPT_CONFIGURATION_REQUEST_ACTION_WRITE_CURRENT (0x02)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_DEFAULT  (0x03)
+#define MPT_CONFIGURATION_REQUEST_ACTION_DEFAULT       (0x04)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_NVRAM    (0x05)
+#define MPT_CONFIGURATION_REQUEST_ACTION_WRITE_NVRAM   (0x06)
+
+/** Page type codes. */
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_IO_UNIT    (0x00)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_IOC        (0x01)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_BIOS       (0x02)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_SCSI_PORT  (0x03)
+
+/**
+ * Configuration reply.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationReply
+{
+    /** Action code. */
+    uint8_t    u8Action;
+    /** Reserved. */
+    uint8_t    u8Reserved;
+    /** Message length. */
+    uint8_t    u8MessageLength;
+    /** Function number. */
+    uint8_t    u8Function;
+    /** Reserved. */
+    uint8_t    u8Reserved2[3];
+    /** Message flags. */
+    uint8_t    u8MessageFlags;
+    /** Message context ID. */
+    uint32_t   u32MessageContext;
+    /** Reserved. */
+    uint16_t   u16Reserved;
+    /** I/O controller status. */
+    uint16_t   u16IOCStatus;
+    /** I/O controller log information. */
+    uint32_t   u32IOCLogInfo;
+    /** Version number of the page. */
+    uint8_t    u8PageVersion;
+    /** Length of the page in 32bit Dwords. */
+    uint8_t    u8PageLength;
+    /** Page number to access. */
+    uint8_t    u8PageNumber;
+    /** Type of the page beeing accessed. */
+    uint8_t    u8PageType;
+} MptConfigurationReply, *PMptConfigurationReply;
+#pragma pack()
+AssertCompileSize(MptConfigurationReply, 24);
+
+/** Additional I/O controller status codes for the configuration reply. */
+#define MPT_IOCSTATUS_CONFIG_INVALID_ACTION (0x0020)
+#define MPT_IOCSTATUS_CONFIG_INVALID_TYPE   (0x0021)
+#define MPT_IOCSTATUS_CONFIG_INVALID_PAGE   (0x0022)
+#define MPT_IOCSTATUS_CONFIG_INVALID_DATA   (0x0023)
+#define MPT_IOCSTATUS_CONFIG_NO_DEFAULTS    (0x0024)
+#define MPT_IOCSTATUS_CONFIG_CANT_COMMIT    (0x0025)
+
+/**
+ * Union of all possible request messages.
+ */
+typedef union MptRequestUnion
+{
+    MptMessageHdr                Header;
+    MptIOCInitRequest            IOCInit;
+    MptIOCFactsRequest           IOCFacts;
+    MptPortFactsRequest          PortFacts;
+    MptPortEnableRequest         PortEnable;
+    MptEventNotificationRequest  EventNotification;
+    MptSCSIIORequest             SCSIIO;
+    MptSCSITaskManagementRequest SCSITaskManagement;
+    MptConfigurationRequest      Configuration;
+} MptRequestUnion, *PMptRequestUnion;
+
+/**
+ * Union of all possible reply messages.
+ */
+typedef union MptReplyUnion
+{
+    /** 16bit view. */
+    uint16_t                   au16Reply[30];
+    MptDefaultReplyMessage     Header;
+    MptIOCInitReply            IOCInit;
+    MptIOCFactsReply           IOCFacts;
+    MptPortFactsReply          PortFacts;
+    MptPortEnableReply         PortEnable;
+    MptEventNotificationReply  EventNotification;
+    MptSCSIIOErrorReply        SCSIIOError;
+    MptSCSITaskManagementReply SCSITaskManagement;
+    MptConfigurationReply      Configuration;
+} MptReplyUnion, *PMptReplyUnion;
+
+
+/**
+ * Configuration Page attributes.
+ */
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY            (0x00)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE          (0x10)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT          (0x20)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY (0x30)
+
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_GET(u8PageType) ((u8PageType) & 0xf0)
+
+/**
+ * Configuration Page types.
+ */
+#define MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT                  (0x00)
+#define MPT_CONFIGURATION_PAGE_TYPE_IOC                      (0x01)
+#define MPT_CONFIGURATION_PAGE_TYPE_BIOS                     (0x02)
+#define MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT            (0x03)
+#define MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE          (0x04)
+#define MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING            (0x09)
+
+#define MPT_CONFIGURATION_PAGE_TYPE_GET(u8PageType) ((u8PageType) & 0x0f)
+
+/**
+ * Configuration Page header - Common to all pages.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageHeader
+{
+    /** Version of the page. */
+    uint8_t     u8PageVersion;
+    /** The length of the page in 32bit D-Words. */
+    uint8_t     u8PageLength;
+    /** Number of the page. */
+    uint8_t     u8PageNumber;
+    /** Type of the page. */
+    uint8_t     u8PageType;
+} MptConfigurationPageHeader, *PMptConfigurationPageHeader;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageHeader, 4);
+
+/**
+ * Manufacturing page 0. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing0
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[76];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Name of the chip. */
+            uint8_t               abChipName[16];
+            /** Chip revision. */
+            uint8_t               abChipRevision[8];
+            /** Board name. */
+            uint8_t               abBoardName[16];
+            /** Board assembly. */
+            uint8_t               abBoardAssembly[16];
+            /** Board tracer number. */
+            uint8_t               abBoardTracerNumber[16];
+        } fields;
+    } u;
+} MptConfigurationPageManufacturing0, *PMptConfigurationPageManufacturing0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing0, 76);
+
+/**
+ * Manufacturing page 1. - Readonly Persistent.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing1
+{
+    /** The omnipresent header. */
+    MptConfigurationPageHeader    Header;
+    /** VPD info - don't know what belongs here so all zero. */
+    uint8_t                       abVPDInfo[256];
+} MptConfigurationPageManufacturing1, *PMptConfigurationPageManufacturing1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing1, 260);
+
+/**
+ * Manufacturing page 2. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing2
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                        abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader Header;
+            /** PCI Device ID. */
+            uint16_t                   u16PCIDeviceID;
+            /** PCI Revision ID. */
+            uint8_t                    u8PCIRevisionID;
+            /** Reserved. */
+            uint8_t                    u8Reserved;
+            /** Hardware specific settings... */
+        } fields;
+    } u;
+} MptConfigurationPageManufacturing2, *PMptConfigurationPageManufacturing2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing2, 8);
+
+/**
+ * Manufacturing page 3. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing3
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** PCI Device ID. */
+            uint16_t              u16PCIDeviceID;
+            /** PCI Revision ID. */
+            uint8_t               u8PCIRevisionID;
+            /** Reserved. */
+            uint8_t               u8Reserved;
+            /** Chip specific settings... */
+        } fields;
+    } u;
+} MptConfigurationPageManufacturing3, *PMptConfigurationPageManufacturing3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing3, 8);
+
+/**
+ * Manufacturing page 4. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing4
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[84];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Reserved. */
+            uint32_t              u32Reserved;
+            /** InfoOffset0. */
+            uint8_t               u8InfoOffset0;
+            /** Info size. */
+            uint8_t               u8InfoSize0;
+            /** InfoOffset1. */
+            uint8_t               u8InfoOffset1;
+            /** Info size. */
+            uint8_t               u8InfoSize1;
+            /** Size of the inquiry data. */
+            uint8_t               u8InquirySize;
+            /** Reserved. */
+            uint8_t               abReserved[3];
+            /** Inquiry data. */
+            uint8_t               abInquiryData[56];
+            /** IS volume settings. */
+            uint32_t              u32ISVolumeSettings;
+            /** IME volume settings. */
+            uint32_t              u32IMEVolumeSettings;
+            /** IM volume settings. */
+            uint32_t              u32IMVolumeSettings;
+        } fields;
+    } u;
+} MptConfigurationPageManufacturing4, *PMptConfigurationPageManufacturing4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing4, 84);
+
+/**
+ * IO Unit page 0. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit0
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** A unique identifier. */
+            uint64_t              u64UniqueIdentifier;
+        } fields;
+    } u;
+} MptConfigurationPageIOUnit0, *PMptConfigurationPageIOUnit0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit0, 12);
+
+/**
+ * IO Unit page 1. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit1
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Flag whether this is a single function PCI device. */
+            unsigned              fSingleFunction:         1;
+            /** Flag whether all possible paths to a device are mapped. */
+            unsigned              fAllPathsMapped:         1;
+            /** Reserved. */
+            unsigned              u4Reserved:              4;
+            /** Flag whether all RAID functionality is disabled. */
+            unsigned              fIntegratedRAIDDisabled: 1;
+            /** Flag whether 32bit PCI accesses are forced. */
+            unsigned              f32BitAccessForced:      1;
+            /** Reserved. */
+            unsigned              abReserved:             24;
+        } fields;
+    } u;
+} MptConfigurationPageIOUnit1, *PMptConfigurationPageIOUnit1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit1, 8);
+
+/**
+ * Adapter Ordering.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit2AdapterOrdering
+{
+    /** PCI bus number. */
+    unsigned    u8PCIBusNumber:   8;
+    /** PCI device and function number. */
+    unsigned    u8PCIDevFn:       8;
+    /** Flag whether the adapter is embedded. */
+    unsigned    fAdapterEmbedded: 1;
+    /** Flag whether the adapter is enabled. */
+    unsigned    fAdapterEnabled:  1;
+    /** Reserved. */
+    unsigned    u6Reserved:       6;
+    /** Reserved. */
+    unsigned    u8Reserved:       8;
+} MptConfigurationPageIOUnit2AdapterOrdering, *PMptConfigurationPageIOUnit2AdapterOrdering;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit2AdapterOrdering, 4);
+
+/**
+ * IO Unit page 2. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit2
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[28];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Reserved. */
+            unsigned              fReserved:           1;
+            /** Flag whether Pause on error is enabled. */
+            unsigned              fPauseOnError:       1;
+            /** Flag whether verbose mode is enabled. */
+            unsigned              fVerboseModeEnabled: 1;
+            /** Set to disable color video. */
+            unsigned              fDisableColorVideo:  1;
+            /** Flag whether int 40h is hooked. */
+            unsigned              fNotHookInt40h:      1;
+            /** Reserved. */
+            unsigned              u3Reserved:          3;
+            /** Reserved. */
+            unsigned              abReserved:         24;
+            /** BIOS version. */
+            uint32_t              u32BIOSVersion;
+            /** Adapter ordering. */
+            MptConfigurationPageIOUnit2AdapterOrdering aAdapterOrder[4];
+        } fields;
+    } u;
+} MptConfigurationPageIOUnit2, *PMptConfigurationPageIOUnit2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit2, 28);
+
+/*
+ * IO Unit page 3. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit3
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Number of GPIO values. */
+            uint8_t               u8GPIOCount;
+            /** Reserved. */
+            uint8_t               abReserved[3];
+        } fields;
+    } u;
+} MptConfigurationPageIOUnit3, *PMptConfigurationPageIOUnit3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit3, 8);
+
+/**
+ * IOC page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC0
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[28];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Total ammount of NV memory in bytes. */
+            uint32_t              u32TotalNVStore;
+            /** Number of free bytes in the NV store. */
+            uint32_t              u32FreeNVStore;
+            /** PCI vendor ID. */
+            uint16_t              u16VendorId;
+            /** PCI device ID. */
+            uint16_t              u16DeviceId;
+            /** PCI revision ID. */
+            uint8_t               u8RevisionId;
+            /** Reserved. */
+            uint8_t               abReserved[3];
+            /** PCI class code. */
+            uint32_t              u32ClassCode;
+            /** Subsystem vendor Id. */
+            uint16_t              u16SubsystemVendorId;
+            /** Subsystem Id. */
+            uint16_t              u16SubsystemId;
+        } fields;
+    } u;
+} MptConfigurationPageIOC0, *PMptConfigurationPageIOC0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC0, 28);
+
+/**
+ * IOC page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC1
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[16];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Flag whether reply coalescing is enabled. */
+            unsigned              fReplyCoalescingEnabled: 1;
+            /** Reserved. */
+            unsigned              u31Reserved:            31;
+            /** Coalescing Timeout in microseconds. */
+            unsigned              u32CoalescingTimeout:   32;
+            /** Coalescing depth. */
+            unsigned              u8CoalescingDepth:       8;
+            /** Reserved. */
+            unsigned              u8Reserved0:             8;
+            unsigned              u8Reserved1:             8;
+            unsigned              u8Reserved2:             8;
+        } fields;
+    } u;
+} MptConfigurationPageIOC1, *PMptConfigurationPageIOC1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC1, 16);
+
+/**
+ * IOC page 2. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC2
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Flag whether striping is supported. */
+            unsigned              fStripingSupported:            1;
+            /** Flag whether enhanced mirroring is supported. */
+            unsigned              fEnhancedMirroringSupported:   1;
+            /** Flag whether mirroring is supported. */
+            unsigned              fMirroringSupported:           1;
+            /** Reserved. */
+            unsigned              u26Reserved:                  26;
+            /** Flag whether SES is supported. */
+            unsigned              fSESSupported:                 1;
+            /** Flag whether SAF-TE is supported. */
+            unsigned              fSAFTESupported:               1;
+            /** Flag whether cross channel volumes are supported. */
+            unsigned              fCrossChannelVolumesSupported: 1;
+            /** Number of active integrated RAID volumes. */
+            unsigned              u8NumActiveVolumes:            8;
+            /** Maximum number of integrated RAID volumes supported. */
+            unsigned              u8MaxVolumes:                  8;
+            /** Number of active integrated RAID physical disks. */
+            unsigned              u8NumActivePhysDisks:          8;
+            /** Maximum number of integrated RAID physical disks supported. */
+            unsigned              u8MaxPhysDisks:                8;
+            /** RAID volumes... - not supported. */
+        } fields;
+    } u;
+} MptConfigurationPageIOC2, *PMptConfigurationPageIOC2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC2, 12);
+
+/**
+ * IOC page 3. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC3
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Number of active integrated RAID physical disks. */
+            uint8_t               u8NumPhysDisks;
+            /** Reserved. */
+            uint8_t               abReserved[3];
+        } fields;
+    } u;
+} MptConfigurationPageIOC3, *PMptConfigurationPageIOC3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC3, 8);
+
+/**
+ * IOC page 4. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC4
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[8];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Number of SEP entries in this page. */
+            uint8_t               u8ActiveSEP;
+            /** Maximum number of SEp entries supported. */
+            uint8_t               u8MaxSEP;
+            /** Reserved. */
+            uint16_t              u16Reserved;
+            /** SEP entries... - not supported. */
+        } fields;
+    } u;
+} MptConfigurationPageIOC4, *PMptConfigurationPageIOC4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC4, 8);
+
+/**
+ * IOC page 6. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC6
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[60];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            uint32_t                      u32CapabilitiesFlags;
+            uint8_t                       u8MaxDrivesIS;
+            uint8_t                       u8MaxDrivesIM;
+            uint8_t                       u8MaxDrivesIME;
+            uint8_t                       u8Reserved1;
+            uint8_t                       u8MinDrivesIS;
+            uint8_t                       u8MinDrivesIM;
+            uint8_t                       u8MinDrivesIME;
+            uint8_t                       u8Reserved2;
+            uint8_t                       u8MaxGlobalHotSpares;
+            uint8_t                       u8Reserved3;
+            uint16_t                      u16Reserved4;
+            uint32_t                      u32Reserved5;
+            uint32_t                      u32SupportedStripeSizeMapIS;
+            uint32_t                      u32SupportedStripeSizeMapIME;
+            uint32_t                      u32Reserved6;
+            uint8_t                       u8MetadataSize;
+            uint8_t                       u8Reserved7;
+            uint16_t                      u16Reserved8;
+            uint16_t                      u16MaxBadBlockTableEntries;
+            uint16_t                      u16Reserved9;
+            uint16_t                      u16IRNvsramUsage;
+            uint16_t                      u16Reserved10;
+            uint32_t                      u32IRNvsramVersion;
+            uint32_t                      u32Reserved11;
+        } fields;
+    } u;
+} MptConfigurationPageIOC6, *PMptConfigurationPageIOC6;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC6, 60);
+
+/**
+ * SCSI-SPI port page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort0
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Flag whether this port is information unit trnafsers capable. */
+            unsigned              fInformationUnitTransfersCapable: 1;
+            /** Flag whether the port is DT (Dual Transfer) capable. */
+            unsigned              fDTCapable:                       1;
+            /** Flag whether the port is QAS (Quick Arbitrate and Select) capable. */
+            unsigned              fQASCapable:                      1;
+            /** Reserved. */
+            unsigned              u5Reserved1:                      5;
+            /** Minimum Synchronous transfer period. */
+            unsigned              u8MinimumSynchronousTransferPeriod: 8;
+            /** Maximum synchronous offset. */
+            unsigned              u8MaximumSynchronousOffset:         8;
+            /** Reserved. */
+            unsigned              u5Reserved2:                      5;
+            /** Flag whether indicating the width of the bus - 0 narrow and 1 for wide. */
+            unsigned              fWide:                            1;
+            /** Reserved */
+            unsigned              fReserved:                        1;
+            /** Flag whether the port is AIP (Asynchronous Information Protection) capable. */
+            unsigned              fAIPCapable:                      1;
+            /** Signaling Type. */
+            unsigned              u2SignalingType:                  2;
+            /** Reserved. */
+            unsigned              u30Reserved:                     30;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIPort0, *PMptConfigurationPageSCSISPIPort0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort0, 12);
+
+/**
+ * SCSI-SPI port page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort1
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** The SCSI ID of the port. */
+            uint8_t               u8SCSIID;
+            /** Reserved. */
+            uint8_t               u8Reserved;
+            /** Port response IDs Bit mask field. */
+            uint16_t              u16PortResponseIDsBitmask;
+            /** Value for the on BUS timer. */
+            uint32_t              u32OnBusTimerValue;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIPort1, *PMptConfigurationPageSCSISPIPort1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort1, 12);
+
+/**
+ * Device settings for one device.
+ */
+#pragma pack(1)
+typedef struct MptDeviceSettings
+{
+    /** Timeout for I/O in seconds. */
+    unsigned    u8Timeout:             8;
+    /** Minimum synchronous factor. */
+    unsigned    u8SyncFactor:          8;
+    /** Flag whether disconnect is enabled. */
+    unsigned    fDisconnectEnable:     1;
+    /** Flag whether Scan ID is enabled. */
+    unsigned    fScanIDEnable:         1;
+    /** Flag whether Scan LUNs is enabled. */
+    unsigned    fScanLUNEnable:        1;
+    /** Flag whether tagged queuing is enabled. */
+    unsigned    fTaggedQueuingEnabled: 1;
+    /** Flag whether wide is enabled. */
+    unsigned    fWideDisable:          1;
+    /** Flag whether this device is bootable. */
+    unsigned    fBootChoice:           1;
+    /** Reserved. */
+    unsigned    u10Reserved:          10;
+} MptDeviceSettings, *PMptDeviceSettings;
+#pragma pack()
+AssertCompileSize(MptDeviceSettings, 4);
+
+/**
+ * SCSI-SPI port page 2. - Read/Write for the BIOS
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort2
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[76];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Flag indicating the bus scan order. */
+            unsigned              fBusScanOrderHighToLow:  1;
+            /** Reserved. */
+            unsigned              fReserved:               1;
+            /** Flag whether SCSI Bus resets are avoided. */
+            unsigned              fAvoidSCSIBusResets:     1;
+            /** Flag whether alternate CHS is used. */
+            unsigned              fAlternateCHS:           1;
+            /** Flag whether termination is disabled. */
+            unsigned              fTerminationDisabled:    1;
+            /** Reserved. */
+            unsigned              u27Reserved:            27;
+            /** Host SCSI ID. */
+            unsigned              u4HostSCSIID:            4;
+            /** Initialize HBA. */
+            unsigned              u2InitializeHBA:         2;
+            /** Removeable media setting. */
+            unsigned              u2RemovableMediaSetting: 2;
+            /** Spinup delay. */
+            unsigned              u4SpinupDelay:           4;
+            /** Negotiating settings. */
+            unsigned              u2NegotitatingSettings:  2;
+            /** Reserved. */
+            unsigned              u18Reserved:            18;
+            /** Device Settings. */
+            MptDeviceSettings     aDeviceSettings[16];
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIPort2, *PMptConfigurationPageSCSISPIPort2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort2, 76);
+
+/**
+ * SCSI-SPI device page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice0
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Negotiated Parameters. */
+            /** Information Units enabled. */
+            unsigned              fInformationUnitsEnabled: 1;
+            /** Dual Transfers Enabled. */
+            unsigned              fDTEnabled:               1;
+            /** QAS enabled. */
+            unsigned              fQASEnabled:              1;
+            /** Reserved. */
+            unsigned              u5Reserved1:              5;
+            /** Synchronous Transfer period. */
+            unsigned              u8NegotiatedSynchronousTransferPeriod: 8;
+            /** Synchronous offset. */
+            unsigned              u8NegotiatedSynchronousOffset: 8;
+            /** Reserved. */
+            unsigned              u5Reserved2:              5;
+            /** Width - 0 for narrow and 1 for wide. */
+            unsigned              fWide:                    1;
+            /** Reserved. */
+            unsigned              fReserved:                1;
+            /** AIP enabled. */
+            unsigned              fAIPEnabled:              1;
+            /** Flag whether negotiation occurred. */
+            unsigned              fNegotationOccured:       1;
+            /** Flag whether a SDTR message was rejected. */
+            unsigned              fSDTRRejected:            1;
+            /** Flag whether a WDTR message was rejected. */
+            unsigned              fWDTRRejected:            1;
+            /** Flag whether a PPR message was rejected. */
+            unsigned              fPPRRejected:             1;
+            /** Reserved. */
+            unsigned              u28Reserved:             28;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIDevice0, *PMptConfigurationPageSCSISPIDevice0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice0, 12);
+
+/**
+ * SCSI-SPI device page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice1
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[16];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Requested Parameters. */
+            /** Information Units enable. */
+            bool                  fInformationUnitsEnable: 1;
+            /** Dual Transfers Enable. */
+            bool                  fDTEnable:               1;
+            /** QAS enable. */
+            bool                  fQASEnable:              1;
+            /** Reserved. */
+            unsigned              u5Reserved1:              5;
+            /** Synchronous Transfer period. */
+            unsigned              u8NegotiatedSynchronousTransferPeriod: 8;
+            /** Synchronous offset. */
+            unsigned              u8NegotiatedSynchronousOffset: 8;
+            /** Reserved. */
+            unsigned              u5Reserved2:              5;
+            /** Width - 0 for narrow and 1 for wide. */
+            bool                  fWide:                   1;
+            /** Reserved. */
+            bool                  fReserved1:              1;
+            /** AIP enable. */
+            bool                  fAIPEnable:              1;
+            /** Reserved. */
+            bool                  fReserved2:              1;
+            /** WDTR disallowed. */
+            bool                  fWDTRDisallowed:         1;
+            /** SDTR disallowed. */
+            bool                  fSDTRDisallowed:         1;
+            /** Reserved. */
+            unsigned              u29Reserved:            29;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIDevice1, *PMptConfigurationPageSCSISPIDevice1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice1, 16);
+
+/**
+ * SCSI-SPI device page 2. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice2
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[16];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Reserved. */
+            unsigned              u4Reserved: 4;
+            /** ISI enable. */
+            unsigned              fISIEnable: 1;
+            /** Secondary driver enable. */
+            unsigned              fSecondaryDriverEnable:          1;
+            /** Reserved. */
+            unsigned              fReserved:                       1;
+            /** Slew reate controler. */
+            unsigned              u3SlewRateControler:             3;
+            /** Primary drive strength controler. */
+            unsigned              u3PrimaryDriveStrengthControl:   3;
+            /** Secondary drive strength controler. */
+            unsigned              u3SecondaryDriveStrengthControl: 3;
+            /** Reserved. */
+            unsigned              u12Reserved:                    12;
+            /** XCLKH_ST. */
+            unsigned              fXCLKH_ST:                       1;
+            /** XCLKS_ST. */
+            unsigned              fXCLKS_ST:                       1;
+            /** XCLKH_DT. */
+            unsigned              fXCLKH_DT:                       1;
+            /** XCLKS_DT. */
+            unsigned              fXCLKS_DT:                       1;
+            /** Parity pipe select. */
+            unsigned              u2ParityPipeSelect:              2;
+            /** Reserved. */
+            unsigned              u30Reserved:                    30;
+            /** Data bit pipeline select. */
+            unsigned              u32DataPipelineSelect:          32;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIDevice2, *PMptConfigurationPageSCSISPIDevice2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice2, 16);
+
+/**
+ * SCSI-SPI device page 3 (Revision G). - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice3
+{
+    /** Union. */
+    union
+    {
+        /** Byte view. */
+        uint8_t                   abPageData[12];
+        /** Field view. */
+        struct
+        {
+            /** The omnipresent header. */
+            MptConfigurationPageHeader    Header;
+            /** Number of times the IOC rejected a message because it doesn't support the operation. */
+            uint16_t                      u16MsgRejectCount;
+            /** Number of times the SCSI bus entered an invalid operation state. */
+            uint16_t                      u16PhaseErrorCount;
+            /** Number of parity errors. */
+            uint16_t                      u16ParityCount;
+            /** Reserved. */
+            uint16_t                      u16Reserved;
+        } fields;
+    } u;
+} MptConfigurationPageSCSISPIDevice3, *PMptConfigurationPageSCSISPIDevice3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice3, 12);
+
+/**
+ * Structure of all supported pages.
+ */
+typedef struct MptConfigurationPagesSupported
+{
+    MptConfigurationPageManufacturing0 ManufacturingPage0;
+    MptConfigurationPageManufacturing1 ManufacturingPage1;
+    MptConfigurationPageManufacturing2 ManufacturingPage2;
+    MptConfigurationPageManufacturing3 ManufacturingPage3;
+    MptConfigurationPageManufacturing4 ManufacturingPage4;
+    MptConfigurationPageIOUnit0        IOUnitPage0;
+    MptConfigurationPageIOUnit1        IOUnitPage1;
+    MptConfigurationPageIOUnit2        IOUnitPage2;
+    MptConfigurationPageIOUnit3        IOUnitPage3;
+    MptConfigurationPageIOC0           IOCPage0;
+    MptConfigurationPageIOC1           IOCPage1;
+    MptConfigurationPageIOC2           IOCPage2;
+    MptConfigurationPageIOC3           IOCPage3;
+    MptConfigurationPageIOC4           IOCPage4;
+    MptConfigurationPageIOC6           IOCPage6;
+    struct
+    {
+        MptConfigurationPageSCSISPIPort0   SCSISPIPortPage0;
+        MptConfigurationPageSCSISPIPort1   SCSISPIPortPage1;
+        MptConfigurationPageSCSISPIPort2   SCSISPIPortPage2;
+    } aPortPages[1]; /* Currently only one port supported. */
+    struct
+    {
+        struct
+        {
+            MptConfigurationPageSCSISPIDevice0 SCSISPIDevicePage0;
+            MptConfigurationPageSCSISPIDevice1 SCSISPIDevicePage1;
+            MptConfigurationPageSCSISPIDevice2 SCSISPIDevicePage2;
+            MptConfigurationPageSCSISPIDevice3 SCSISPIDevicePage3;
+        } aDevicePages[LSILOGIC_DEVICES_MAX];
+    } aBuses[1]; /* Only one bus at the moment. */
+} MptConfigurationPagesSupported, *PMptConfigurationPagesSupported;
+
+/**
+ * Possible SG element types.
+ */
+enum MPTSGENTRYTYPE
+{
+    MPTSGENTRYTYPE_TRANSACTION_CONTEXT = 0x00,
+    MPTSGENTRYTYPE_SIMPLE              = 0x01,
+    MPTSGENTRYTYPE_CHAIN               = 0x03
+};
+
+/**
+ * Reply data.
+ */
+typedef struct LSILOGICSCSIREPLY
+{
+    /** Lower 32 bits of the reply address in memory. */
+    uint32_t      u32HostMFALowAddress;
+    /** Full address of the reply in guest memory. */
+    RTGCPHYS      GCPhysReplyAddress;
+    /** Size of the reply. */
+    uint32_t      cbReply;
+    /** Different views to the reply depending on the request type. */
+    MptReplyUnion Reply;
+} LSILOGICSCSIREPLY, *PLSILOGICSCSIREPLY;
+
+/*
+ * State of a device attached to the buslogic host adapter.
+ */
+typedef struct LSILOGICDEVICE
+{
+    /** Pointer to the owning lsilogic device instance. - R3 pointer */
+    R3PTRTYPE(struct LSILOGICSCSI *)  pLsiLogicR3;
+    /** Pointer to the owning lsilogic device instance. - R0 pointer */
+    R0PTRTYPE(struct LSILOGICSCSI *)  pLsiLogicR0;
+    /** Pointer to the owning lsilogic device instance. - RC pointer */
+    RCPTRTYPE(struct LSILOGICSCSI *)  pLsiLogicRC;
+
+    /** LUN of the device. */
+    RTUINT                        iLUN;
+    /** Number of outstanding tasks on the port. */
+    volatile uint32_t             cOutstandingRequests;
+
+#if HC_ARCH_BITS == 64
+    uint32_t                      Alignment0;
+#endif
+
+    /** Our base interace. */
+    PDMIBASE                      IBase;
+    /** SCSI port interface. */
+    PDMISCSIPORT                  ISCSIPort;
+    /** Led interface. */
+    PDMILEDPORTS                  ILed;
+    /** Pointer to the attached driver's base interface. */
+    R3PTRTYPE(PPDMIBASE)          pDrvBase;
+    /** Pointer to the underlying SCSI connector interface. */
+    R3PTRTYPE(PPDMISCSICONNECTOR) pDrvSCSIConnector;
+    /** The status LED state for this device. */
+    PDMLED                        Led;
+
+} LSILOGICDEVICE, *PLSILOGICDEVICE;
+
+/**
+ * Defined states that the SCSI controller can have.
+ */
+typedef enum LSILOGICSTATE
+{
+    /** Reset state. */
+    LSILOGICSTATE_RESET       = 0x00,
+    /** Ready state. */
+    LSILOGICSTATE_READY       = 0x01,
+    /** Operational state. */
+    LSILOGICSTATE_OPERATIONAL = 0x02,
+    /** Fault state. */
+    LSILOGICSTATE_FAULT       = 0x04,
+    /** 32bit size hack */
+    LSILOGICSTATE_32BIT_HACK  = 0x7fffffff
+} LSILOGICSTATE;
+
+/**
+ * Which entity needs to initialize the controller
+ * to get into the operational state.
+ */
+typedef enum LSILOGICWHOINIT
+{
+    /** Not initialized. */
+    LSILOGICWHOINIT_NOT_INITIALIZED = 0x00,
+    /** System BIOS. */
+    LSILOGICWHOINIT_SYSTEM_BIOS     = 0x01,
+    /** ROM Bios. */
+    LSILOGICWHOINIT_ROM_BIOS        = 0x02,
+    /** PCI Peer. */
+    LSILOGICWHOINIT_PCI_PEER        = 0x03,
+    /** Host driver. */
+    LSILOGICWHOINIT_HOST_DRIVER     = 0x04,
+    /** Manufacturing. */
+    LSILOGICWHOINIT_MANUFACTURING   = 0x05,
+    /** 32bit size hack. */
+    LSILOGICWHOINIT_32BIT_HACK      = 0x7fffffff
+} LSILOGICWHOINIT;
+
+
+/**
+ * IOC status codes.
+ */
+#define LSILOGIC_IOCSTATUS_SUCCESS                0x0000
+#define LSILOGIC_IOCSTATUS_INVALID_FUNCTION       0x0001
+#define LSILOGIC_IOCSTATUS_BUSY                   0x0002
+#define LSILOGIC_IOCSTATUS_INVALID_SGL            0x0003
+#define LSILOGIC_IOCSTATUS_INTERNAL_ERROR         0x0004
+#define LSILOGIC_IOCSTATUS_RESERVED               0x0005
+#define LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES 0x0006
+#define LSILOGIC_IOCSTATUS_INVALID_FIELD          0x0007
+#define LSILOGIC_IOCSTATUS_INVALID_STATE          0x0008
+#define LSILOGIC_IOCSTATUS_OP_STATE_NOT_SUPPOTED  0x0009
+
+/**
+ * Device instance data for the emulated
+ * SCSI controller.
+ */
+typedef struct LSILOGICSCSI
+{
+    /** PCI device structure. */
+    PCIDEVICE            PciDev;
+    /** Pointer to the device instance. - R3 ptr. */
+    PPDMDEVINSR3         pDevInsR3;
+    /** Pointer to the device instance. - R0 ptr. */
+    PPDMDEVINSR0         pDevInsR0;
+    /** Pointer to the device instance. - RC ptr. */
+    PPDMDEVINSRC         pDevInsRC;
+
+    /** Flag whether the GC part of the device is enabled. */
+    bool                 fGCEnabled;
+    /** Flag whether the R0 part of the device is enabled. */
+    bool                 fR0Enabled;
+
+    /** The state the controller is currently in. */
+    LSILOGICSTATE        enmState;
+    /** Who needs to init the driver to get into operational state. */
+    LSILOGICWHOINIT      enmWhoInit;
+    /** Flag whether we are in doorbell function. */
+    bool                 fDoorbellInProgress;
+    /** Flag whether diagnostic access is enabled. */
+    bool                 fDiagnosticEnabled;
+
+    /** Flag whether a notification was send to R3. */
+    bool                 fNotificationSend;
+
+    /** Flag whether the guest enabled event notification from the IOC. */
+    bool                 fEventNotificationEnabled;
+
+#if HC_ARCH_BITS == 64
+    uint32_t             Alignment0;
+#endif
+
+    /** Queue to send tasks to R3. - R3 ptr */
+    R3PTRTYPE(PPDMQUEUE) pNotificationQueueR3;
+    /** Queue to send tasks to R3. - R0 ptr */
+    R0PTRTYPE(PPDMQUEUE) pNotificationQueueR0;
+    /** Queue to send tasks to R3. - RC ptr */
+    RCPTRTYPE(PPDMQUEUE) pNotificationQueueRC;
+
+#if HC_ARCH_BITS == 64
+    uint32_t             Alignment1;
+#endif
+
+    /** States for attached devices. */
+    LSILOGICDEVICE       aDeviceStates[LSILOGIC_DEVICES_MAX];
+
+    /** MMIO address the device is mapped to. */
+    RTGCPHYS             GCPhysMMIOBase;
+    /** I/O port address the device is mapped to. */
+    RTIOPORT             IOPortBase;
+
+    /** Interrupt mask. */
+    volatile uint32_t    uInterruptMask;
+    /** Interrupt status register. */
+    volatile uint32_t    uInterruptStatus;
+
+    /** Buffer for messages which are passed
+     * through the doorbell using the
+     * handshake method. */
+    uint32_t              aMessage[sizeof(MptConfigurationRequest)];
+    /** Actual position in the buffer. */
+    uint32_t              iMessage;
+    /** Size of the message which is given in the doorbell message in dwords. */
+    uint32_t              cMessage;
+
+    /** Reply buffer. */
+    MptReplyUnion         ReplyBuffer;
+    /** Next entry to read. */
+    uint32_t              uNextReplyEntryRead;
+    /** Size of the reply in the buffer in 16bit words. */
+    uint32_t              cReplySize;
+
+    /** The fault code of the I/O controller if we are in the fault state. */
+    uint16_t              u16IOCFaultCode;
+
+    /** Upper 32 bits of the moessage frame address to locate requests in guest memory. */
+    uint32_t              u32HostMFAHighAddr;
+    /** Upper 32 bits of the sense buffer address. */
+    uint32_t              u32SenseBufferHighAddr;
+    /** Maximum number of devices the driver reported he can handle. */
+    uint8_t               cMaxDevices;
+    /** Maximum number of buses the driver reported he can handle. */
+    uint8_t               cMaxBuses;
+    /** Current size of reply message frames in the guest. */
+    uint16_t              cbReplyFrame;
+
+    /** Next key to write in the sequence to get access
+     *  to diagnostic memory. */
+    uint32_t              iDiagnosticAccess;
+
+    /** Number entries allocated for the reply queue. */
+    uint32_t              cReplyQueueEntries;
+    /** Number entries allocated for the outstanding request queue. */
+    uint32_t              cRequestQueueEntries;
+
+    /** Critical section protecting the reply post queue. */
+    PDMCRITSECT           ReplyPostQueueCritSect;
+    /** Critical section protecting the reply free queue. */
+    PDMCRITSECT           ReplyFreeQueueCritSect;
+
+#if HC_ARCH_BITS == 64
+    uint32_t              Alignment2;
+#endif
+
+    /** Pointer to the start of the reply free queue - R3. */
+    R3PTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseR3;
+    /** Pointer to the start of the reply post queue - R3. */
+    R3PTRTYPE(volatile uint32_t *) pReplyPostQueueBaseR3;
+    /** Pointer to the start of the request queue - R3. */
+    R3PTRTYPE(volatile uint32_t *) pRequestQueueBaseR3;
+
+    /** Pointer to the start of the reply queue - R0. */
+    R0PTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseR0;
+    /** Pointer to the start of the reply queue - R0. */
+    R0PTRTYPE(volatile uint32_t *) pReplyPostQueueBaseR0;
+    /** Pointer to the start of the request queue - R0. */
+    R0PTRTYPE(volatile uint32_t *) pRequestQueueBaseR0;
+
+    /** Pointer to the start of the reply queue - RC. */
+    RCPTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseRC;
+    /** Pointer to the start of the reply queue - RC. */
+    RCPTRTYPE(volatile uint32_t *) pReplyPostQueueBaseRC;
+    /** Pointer to the start of the request queue - RC. */
+    RCPTRTYPE(volatile uint32_t *) pRequestQueueBaseRC;
+
+    /** Next free entry in the reply queue the guest can write a address to. */
+    volatile uint32_t     uReplyFreeQueueNextEntryFreeWrite;
+    /** Next valid entry the controller can read a valid address for reply frames from. */
+    volatile uint32_t     uReplyFreeQueueNextAddressRead;
+
+    /** Next free entry in the reply queue the guest can write a address to. */
+    volatile uint32_t     uReplyPostQueueNextEntryFreeWrite;
+    /** Next valid entry the controller can read a valid address for reply frames from. */
+    volatile uint32_t     uReplyPostQueueNextAddressRead;
+
+    /** Next free entry the guest can write a address to a request frame to. */
+    volatile uint32_t     uRequestQueueNextEntryFreeWrite;
+    /** Next valid entry the controller can read a valid address for request frames from. */
+    volatile uint32_t     uRequestQueueNextAddressRead;
+
+    /** Configuration pages. */
+    MptConfigurationPagesSupported ConfigurationPages;
+
+    /** BIOS emulation. */
+    VBOXSCSI                       VBoxSCSI;
+
+    /** Cache for allocated tasks. */
+    R3PTRTYPE(PRTOBJCACHE)         pTaskCache;
+
+    /** The base interface */
+    PDMIBASE                       IBase;
+    /** Status Port - Leds interface. */
+    PDMILEDPORTS                   ILeds;
+    /** Partner of ILeds. */
+    R3PTRTYPE(PPDMILEDCONNECTORS)  pLedsConnector;
+} LSILOGISCSI, *PLSILOGICSCSI;
+
+#define LSILOGIC_PCI_SPACE_IO_SIZE  256
+#define LSILOGIC_PCI_SPACE_MEM_SIZE 128 * _1K
+
+#define LSILOGIC_REG_DOORBELL 0x00
+# define LSILOGIC_REG_DOORBELL_SET_STATE(enmState)     (((enmState) & 0x0f) << 28)
+# define LSILOGIC_REG_DOORBELL_SET_USED(fUsed)         (((fUsed) ? 1 : 0) << 27)
+# define LSILOGIC_REG_DOORBELL_SET_WHOINIT(enmWhoInit) (((enmWhoInit) & 0x07) << 24)
+# define LSILOGIC_REG_DOORBELL_SET_FAULT_CODE(u16Code) (u16Code)
+# define LSILOGIC_REG_DOORBELL_GET_FUNCTION(x)         (((x) & 0xff000000) >> 24)
+# define LSILOGIC_REG_DOORBELL_GET_SIZE(x)             (((x) & 0x00ff0000) >> 16)
+
+#define LSILOGIC_REG_WRITE_SEQUENCE    0x04
+#define LSILOGIC_REG_HOST_DIAGNOSTIC   0x08
+#define LSILOGIC_REG_TEST_BASE_ADDRESS 0x0c
+#define LSILOGIC_REG_DIAG_RW_DATA      0x10
+#define LSILOGIC_REG_DIAG_RW_ADDRESS   0x14
+
+#define LSILOGIC_REG_HOST_INTR_STATUS  0x30
+# define LSILOGIC_REG_HOST_INTR_STATUS_W_MASK (RT_BIT(3))
+# define LSILOGIC_REG_HOST_INTR_STATUS_DOORBELL_STS    (RT_BIT(31))
+# define LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR      (RT_BIT(3))
+# define LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL (RT_BIT(0))
+
+#define LSILOGIC_REG_HOST_INTR_MASK    0x34
+# define LSILOGIC_REG_HOST_INTR_MASK_W_MASK (RT_BIT(0) | RT_BIT(3) | RT_BIT(8) | RT_BIT(9))
+# define LSILOGIC_REG_HOST_INTR_MASK_IRQ_ROUTING (RT_BIT(8) | RT_BIT(9))
+# define LSILOGIC_REG_HOST_INTR_MASK_DOORBELL RT_BIT(0)
+# define LSILOGIC_REG_HOST_INTR_MASK_REPLY    RT_BIT(3)
+
+#define LSILOGIC_REG_REQUEST_QUEUE     0x40
+#define LSILOGIC_REG_REPLY_QUEUE       0x44
+
+/* Functions which can be passed through the system doorbell. */
+#define LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET  0x40
+#define LSILOGIC_DOORBELL_FUNCTION_IO_UNIT_RESET       0x41
+#define LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE           0x42
+#define LSILOGIC_DOORBELL_FUNCTION_REPLY_FRAME_REMOVAL 0x43
+
+/**
+ * Scatter gather list entry data.
+ */
+typedef struct LSILOGICTASKSTATESGENTRY
+{
+    /** Flag whether the buffer in the list is from the guest or an
+     *  allocated temporary buffer because the segments in the guest
+     *  are not sector aligned.
+     */
+    bool     fGuestMemory;
+    /** Flag whether the buffer contains data or is the destination for the transfer. */
+    bool     fBufferContainsData;
+    /** Pointer to the start of the buffer. */
+    void    *pvBuf;
+    /** Size of the buffer. */
+    uint32_t cbBuf;
+    /** Flag dependent data. */
+    union
+    {
+        /** Data to handle direct mappings of guest buffers. */
+        PGMPAGEMAPLOCK  PageLock;
+        /** The segment in the guest which is not sector aligned. */
+        RTGCPHYS        GCPhysAddrBufferUnaligned;
+    } u;
+} LSILOGICTASKSTATESGENTRY, *PLSILOGICTASKSTATESGENTRY;
+
+/**
+ * Task state object which holds all neccessary data while
+ * processing the request from the guest.
+ */
+typedef struct LSILOGICTASKSTATE
+{
+    /** Target device. */
+    PLSILOGICDEVICE            pTargetDevice;
+    /** The message request from the guest. */
+    MptRequestUnion            GuestRequest;
+    /** Reply message if the request produces one. */
+    MptReplyUnion              IOCReply;
+    /** SCSI request structure for the SCSI driver. */
+    PDMSCSIREQUEST             PDMScsiRequest;
+    /** Address of the message request frame in guests memory.
+     *  Used to read the S/G entries in the second step. */
+    RTGCPHYS                   GCPhysMessageFrameAddr;
+    /** Number of scatter gather list entries. */
+    uint32_t                   cSGListEntries;
+    /** How many entries would fit into the sg list. */
+    uint32_t                   cSGListSize;
+    /** How many times the list was too big. */
+    uint32_t                   cSGListTooBig;
+    /** Pointer to the first entry of the scatter gather list. */
+    PPDMDATASEG                pSGListHead;
+    /** How many entries would fit into the sg info list. */
+    uint32_t                   cSGInfoSize;
+    /** Number of entries for the information entries. */
+    uint32_t                   cSGInfoEntries;
+    /** How many times the list was too big. */
+    uint32_t                   cSGInfoTooBig;
+    /** Pointer to the first mapping information entry. */
+    PLSILOGICTASKSTATESGENTRY  paSGEntries;
+    /** Size of the temporary buffer for unaligned guest segments. */
+    uint32_t                   cbBufferUnaligned;
+    /** Pointer to the temporary buffer. */
+    void                      *pvBufferUnaligned;
+    /** Pointer to the sense buffer. */
+    uint8_t                    abSenseBuffer[18];
+    /** Flag whether the request was issued from the BIOS. */
+    bool                       fBIOS;
+} LSILOGICTASKSTATE, *PLSILOGICTASKSTATE;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+RT_C_DECLS_BEGIN
+PDMBOTHCBDECL(int) lsilogicIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                        RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) lsilogicIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                       RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) lsilogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                     RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+PDMBOTHCBDECL(int) lsilogicMMIORead(PPDMDEVINS pDevIns, void *pvUser,
+                                    RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+PDMBOTHCBDECL(int) lsilogicDiagnosticWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                           RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+PDMBOTHCBDECL(int) lsilogicDiagnosticRead(PPDMDEVINS pDevIns, void *pvUser,
+                                          RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
+#ifdef IN_RING3
+static void lsilogicInitializeConfigurationPages(PLSILOGICSCSI pLsiLogic);
+static int lsilogicProcessConfigurationRequest(PLSILOGICSCSI pLsiLogic, PMptConfigurationRequest pConfigurationReq,
+                                               PMptConfigurationReply pReply);
+#endif
+RT_C_DECLS_END
+
+#define PDMIBASE_2_PLSILOGICDEVICE(pInterface)     ( (PLSILOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(LSILOGICDEVICE, IBase)) )
+#define PDMISCSIPORT_2_PLSILOGICDEVICE(pInterface) ( (PLSILOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(LSILOGICDEVICE, ISCSIPort)) )
+#define PDMILEDPORTS_2_PLSILOGICDEVICE(pInterface) ( (PLSILOGICDEVICE)((uintptr_t)(pInterface) - RT_OFFSETOF(LSILOGICDEVICE, ILed)) )
+#define LSILOGIC_RTGCPHYS_FROM_U32(Hi, Lo)         ( (RTGCPHYS)RT_MAKE_U64(Lo, Hi) )
+#define PDMIBASE_2_PLSILOGICSCSI(pInterface)       ( (PLSILOGICSCSI)((uintptr_t)(pInterface) - RT_OFFSETOF(LSILOGICSCSI, IBase)) )
+#define PDMILEDPORTS_2_PLSILOGICSCSI(pInterface)   ( (PLSILOGICSCSI)((uintptr_t)(pInterface) - RT_OFFSETOF(LSILOGICSCSI, ILeds)) )
+
+/** Key sequence the guest has to write to enable access
+ * to diagnostic memory. */
+static const uint8_t g_lsilogicDiagnosticAccess[] = {0x04, 0x0b, 0x02, 0x07, 0x0d};
+
+/**
+ * Updates the status of the interrupt pin of the device.
+ *
+ * @returns nothing.
+ * @param   pThis    Pointer to the device instance data.
+ */
+static void lsilogicUpdateInterrupt(PLSILOGICSCSI pThis)
+{
+    uint32_t uIntSts;
+
+    LogFlowFunc(("Updating interrupts\n"));
+
+    /* Mask out doorbell status so that it does not affect interrupt updating. */
+    uIntSts = (ASMAtomicReadU32(&pThis->uInterruptStatus) & ~LSILOGIC_REG_HOST_INTR_STATUS_DOORBELL_STS);
+    /* Check maskable interrupts. */
+    uIntSts &= ~(pThis->uInterruptMask & ~LSILOGIC_REG_HOST_INTR_MASK_IRQ_ROUTING);
+
+    if (uIntSts)
+    {
+        LogFlowFunc(("Setting interrupt\n"));
+        PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 1);
+    }
+    else
+    {
+        LogFlowFunc(("Clearing interrupt\n"));
+        PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 0);
+    }
+}
+
+/**
+ * Sets a given interrupt status bit in the status register and
+ * updates the interupt status.
+ *
+ * @returns nothing.
+ * @param   pLsiLogic    Pointer to the device instance.
+ * @param   uStatus      The status bit to set.
+ */
+DECLINLINE(void) lsilogicSetInterrupt(PLSILOGICSCSI pLsiLogic, uint32_t uStatus)
+{
+    ASMAtomicOrU32(&pLsiLogic->uInterruptStatus, uStatus);
+    lsilogicUpdateInterrupt(pLsiLogic);
+}
+
+/**
+ * Clears a given interrupt status bit in the status register and
+ * updates the interupt status.
+ *
+ * @returns nothing.
+ * @param   pLsiLogic    Pointer to the device instance.
+ * @param   uStatus      The status bit to set.
+ */
+DECLINLINE(void) lsilogicClearInterrupt(PLSILOGICSCSI pLsiLogic, uint32_t uStatus)
+{
+    ASMAtomicAndU32(&pLsiLogic->uInterruptStatus, ~uStatus);
+    lsilogicUpdateInterrupt(pLsiLogic);
+}
+
+/**
+ * Sets the I/O controller into fault state and sets the fault code.
+ *
+ * @returns nothing
+ * @param   pLsiLogic        Pointer to the controller device instance.
+ * @param   uIOCFaultCode    Fault code to set.
+ */
+DECLINLINE(void) lsilogicSetIOCFaultCode(PLSILOGICSCSI pLsiLogic, uint16_t uIOCFaultCode)
+{
+    if (pLsiLogic->enmState != LSILOGICSTATE_FAULT)
+    {
+        Log(("%s: Setting I/O controller into FAULT state: uIOCFaultCode=%u\n", __FUNCTION__, uIOCFaultCode));
+        pLsiLogic->enmState        = LSILOGICSTATE_FAULT;
+        pLsiLogic->u16IOCFaultCode = uIOCFaultCode;
+    }
+    else
+    {
+        Log(("%s: We are already in FAULT state\n"));
+    }
+}
+
+#ifdef IN_RING3
+/**
+ * Performs a hard reset on the controller.
+ *
+ * @returns VBox status code.
+ * @param   pThis    Pointer to the device instance to initialize.
+ */
+static int lsilogicHardReset(PLSILOGICSCSI pThis)
+{
+    pThis->enmState = LSILOGICSTATE_RESET;
+
+    /* The interrupts are masked out. */
+    pThis->uInterruptMask |= LSILOGIC_REG_HOST_INTR_MASK_DOORBELL |
+                             LSILOGIC_REG_HOST_INTR_MASK_REPLY;
+    /* Reset interrupt states. */
+    pThis->uInterruptStatus = 0;
+    lsilogicUpdateInterrupt(pThis);
+
+    /* Reset the queues. */
+    pThis->uReplyFreeQueueNextEntryFreeWrite = 0;
+    pThis->uReplyFreeQueueNextAddressRead = 0;
+    pThis->uReplyPostQueueNextEntryFreeWrite = 0;
+    pThis->uReplyPostQueueNextAddressRead = 0;
+    pThis->uRequestQueueNextEntryFreeWrite = 0;
+    pThis->uRequestQueueNextAddressRead = 0;
+
+    /* Disable diagnostic access. */
+    pThis->iDiagnosticAccess = 0;
+
+    /* Set default values. */
+    pThis->cMaxDevices  = LSILOGIC_DEVICES_MAX;
+    pThis->cMaxBuses    = 1;
+    pThis->cbReplyFrame = 128; /* @todo Figure out where it is needed. */
+    /* @todo: Put stuff to reset here. */
+
+    lsilogicInitializeConfigurationPages(pThis);
+
+    /* Mark that we finished performing the reset. */
+    pThis->enmState = LSILOGICSTATE_READY;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Finishes a context reply.
+ *
+ * @returns nothing
+ * @param   pLsiLogic            Pointer to the device instance
+ * @param   u32MessageContext    The message context ID to post.
+ */
+static void lsilogicFinishContextReply(PLSILOGICSCSI pLsiLogic, uint32_t u32MessageContext)
+{
+    int rc;
+    AssertMsg(!pLsiLogic->fDoorbellInProgress, ("We are in a doorbell function\n"));
+
+    /* Write message context ID into reply post queue. */
+    rc = PDMCritSectEnter(&pLsiLogic->ReplyPostQueueCritSect, VINF_SUCCESS);
+    AssertRC(rc);
+
+#if 0
+    /* Check for a entry in the queue. */
+    if (RT_UNLIKELY(pLsiLogic->uReplyPostQueueNextAddressRead != pLsiLogic->uReplyPostQueueNextEntryFreeWrite))
+    {
+        /* Set error code. */
+        lsilogicSetIOCFaultCode(pLsiLogic, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+        PDMCritSectLeave(&pLsiLogic->ReplyPostQueueCritSect);
+        return;
+    }
+#endif
+
+    /* We have a context reply. */
+    ASMAtomicWriteU32(&pLsiLogic->CTX_SUFF(pReplyPostQueueBase)[pLsiLogic->uReplyPostQueueNextEntryFreeWrite], u32MessageContext);
+    ASMAtomicIncU32(&pLsiLogic->uReplyPostQueueNextEntryFreeWrite);
+    pLsiLogic->uReplyPostQueueNextEntryFreeWrite %= pLsiLogic->cReplyQueueEntries;
+
+    PDMCritSectLeave(&pLsiLogic->ReplyPostQueueCritSect);
+
+    /* Set interrupt. */
+    lsilogicSetInterrupt(pLsiLogic, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+}
+#endif /* IN_RING3 */
+
+/**
+ * Takes neccessary steps to finish a reply frame.
+ *
+ * @returns nothing
+ * @param   pLsiLogic       Pointer to the device instance
+ * @param   pReply          Pointer to the reply message.
+ * @param   fForceReplyFifo Flag whether the use of the reply post fifo is forced.
+ */
+static void lsilogicFinishAddressReply(PLSILOGICSCSI pLsiLogic, PMptReplyUnion pReply, bool fForceReplyFifo)
+{
+    /*
+     * If we are in a doorbell function we set the reply size now and
+     * set the system doorbell status interrupt to notify the guest that
+     * we are ready to send the reply.
+     */
+    if (pLsiLogic->fDoorbellInProgress && !fForceReplyFifo)
+    {
+        /* Set size of the reply in 16bit words. The size in the reply is in 32bit dwords. */
+        pLsiLogic->cReplySize = pReply->Header.u8MessageLength * 2;
+        Log(("%s: cReplySize=%u\n", __FUNCTION__, pLsiLogic->cReplySize));
+        pLsiLogic->uNextReplyEntryRead = 0;
+        lsilogicSetInterrupt(pLsiLogic, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+    }
+    else
+    {
+        /*
+         * The reply queues are only used if the request was fetched from the request queue.
+         * Requests from the request queue are always transferred to R3. So it is not possible
+         * that this case happens in R0 or GC.
+         */
+#ifdef IN_RING3
+        int rc;
+        /* Grab a free reply message from the queue. */
+        rc = PDMCritSectEnter(&pLsiLogic->ReplyFreeQueueCritSect, VINF_SUCCESS);
+        AssertRC(rc);
+
+#if 0
+        /* Check for a free reply frame. */
+        if (RT_UNLIKELY(pLsiLogic->uReplyFreeQueueNextAddressRead != pLsiLogic->uReplyFreeQueueNextEntryFreeWrite))
+        {
+            /* Set error code. */
+            lsilogicSetIOCFaultCode(pLsiLogic, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+            PDMCritSectLeave(&pLsiLogic->ReplyFreeQueueCritSect);
+            return;
+        }
+#endif
+
+        uint32_t u32ReplyFrameAddressLow = pLsiLogic->CTX_SUFF(pReplyFreeQueueBase)[pLsiLogic->uReplyFreeQueueNextAddressRead];
+
+        pLsiLogic->uReplyFreeQueueNextAddressRead++;
+        pLsiLogic->uReplyFreeQueueNextAddressRead %= pLsiLogic->cReplyQueueEntries;
+
+        PDMCritSectLeave(&pLsiLogic->ReplyFreeQueueCritSect);
+
+        /* Build 64bit physical address. */
+        RTGCPHYS GCPhysReplyMessage = LSILOGIC_RTGCPHYS_FROM_U32(pLsiLogic->u32HostMFAHighAddr, u32ReplyFrameAddressLow);
+        size_t cbReplyCopied = (pLsiLogic->cbReplyFrame < sizeof(MptReplyUnion)) ? pLsiLogic->cbReplyFrame : sizeof(MptReplyUnion);
+
+        /* Write reply to guest memory. */
+        PDMDevHlpPhysWrite(pLsiLogic->CTX_SUFF(pDevIns), GCPhysReplyMessage, pReply, cbReplyCopied);
+
+        /* Write low 32bits of reply frame into post reply queue. */
+        rc = PDMCritSectEnter(&pLsiLogic->ReplyPostQueueCritSect, VINF_SUCCESS);
+        AssertRC(rc);
+
+#if 0
+        /* Check for a entry in the queue. */
+        if (RT_UNLIKELY(pLsiLogic->uReplyPostQueueNextAddressRead != pLsiLogic->uReplyPostQueueNextEntryFreeWrite))
+        {
+            /* Set error code. */
+            lsilogicSetIOCFaultCode(pLsiLogic, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+            PDMCritSectLeave(&pLsiLogic->ReplyPostQueueCritSect);
+            return;
+        }
+#endif
+
+        /* We have a address reply. Set the 31th bit to indicate that. */
+        ASMAtomicWriteU32(&pLsiLogic->CTX_SUFF(pReplyPostQueueBase)[pLsiLogic->uReplyPostQueueNextEntryFreeWrite],
+                          RT_BIT(31) | (u32ReplyFrameAddressLow >> 1));
+        ASMAtomicIncU32(&pLsiLogic->uReplyPostQueueNextEntryFreeWrite);
+        pLsiLogic->uReplyPostQueueNextEntryFreeWrite %= pLsiLogic->cReplyQueueEntries;
+
+        PDMCritSectLeave(&pLsiLogic->ReplyPostQueueCritSect);
+
+        if (fForceReplyFifo)
+        {
+            pLsiLogic->fDoorbellInProgress = false;
+            lsilogicSetInterrupt(pLsiLogic, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+        }
+
+        /* Set interrupt. */
+        lsilogicSetInterrupt(pLsiLogic, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+#else
+        AssertMsgFailed(("This is not allowed to happen.\n"));
+#endif
+    }
+}
+
+#ifdef IN_RING3
+/**
+ * Processes a given Request from the guest
+ *
+ * @returns VBox status code.
+ * @param   pLsiLogic    Pointer to the device instance.
+ * @param   pMessageHdr  Pointer to the message header of the request.
+ * @param   pReply       Pointer to the reply.
+ */
+static int lsilogicProcessMessageRequest(PLSILOGICSCSI pLsiLogic, PMptMessageHdr pMessageHdr, PMptReplyUnion pReply)
+{
+    int rc = VINF_SUCCESS;
+    bool fForceReplyPostFifo = false;
+
+#ifdef DEBUG
+    if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames))
+        Log(("Message request function: %s\n", g_apszMPTFunctionNames[pMessageHdr->u8Function]));
+    else
+        Log(("Message request function: <unknown>\n"));
+#endif
+
+    memset(pReply, 0, sizeof(MptReplyUnion));
+
+    switch (pMessageHdr->u8Function)
+    {
+        case MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT:
+        {
+            PMptSCSITaskManagementRequest pTaskMgmtReq = (PMptSCSITaskManagementRequest)pMessageHdr;
+
+            pReply->SCSITaskManagement.u8MessageLength     = 6;     /* 6 32bit dwords. */
+            pReply->SCSITaskManagement.u8TaskType          = pTaskMgmtReq->u8TaskType;
+            pReply->SCSITaskManagement.u32TerminationCount = 0;
+            //AssertMsgFailed(("todo\n"));
+            fForceReplyPostFifo = true;
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_IOC_INIT:
+        {
+            /*
+             * This request sets the I/O controller to the
+             * operational state.
+             */
+            PMptIOCInitRequest pIOCInitReq = (PMptIOCInitRequest)pMessageHdr;
+
+            /* Update configuration values. */
+            pLsiLogic->enmWhoInit             = (LSILOGICWHOINIT)pIOCInitReq->u8WhoInit;
+            pLsiLogic->cbReplyFrame           = pIOCInitReq->u16ReplyFrameSize;
+            pLsiLogic->cMaxBuses              = pIOCInitReq->u8MaxBuses;
+            pLsiLogic->cMaxDevices            = pIOCInitReq->u8MaxDevices;
+            pLsiLogic->u32HostMFAHighAddr     = pIOCInitReq->u32HostMfaHighAddr;
+            pLsiLogic->u32SenseBufferHighAddr = pIOCInitReq->u32SenseBufferHighAddr;
+
+            if (pLsiLogic->enmState == LSILOGICSTATE_READY)
+            {
+                pLsiLogic->enmState = LSILOGICSTATE_OPERATIONAL;
+            }
+
+            /* Return reply. */
+            pReply->IOCInit.u8MessageLength = 5;
+            pReply->IOCInit.u8WhoInit       = pLsiLogic->enmWhoInit;
+            pReply->IOCInit.u8MaxDevices    = pLsiLogic->cMaxDevices;
+            pReply->IOCInit.u8MaxBuses      = pLsiLogic->cMaxBuses;
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS:
+        {
+            pReply->IOCFacts.u8MessageLength      = 15;     /* 15 32bit dwords. */
+            pReply->IOCFacts.u16MessageVersion    = 0x0102; /* Version from the specification. */
+            pReply->IOCFacts.u8IOCNumber          = 0;      /* PCI function number. */
+            pReply->IOCFacts.u16IOCExceptions     = 0;
+            pReply->IOCFacts.u8MaxChainDepth      = LSILOGICSCSI_MAXIMUM_CHAIN_DEPTH;
+            pReply->IOCFacts.u8WhoInit            = pLsiLogic->enmWhoInit;
+            pReply->IOCFacts.u8BlockSize          = 12;     /* Block size in 32bit dwords. This is the largest request we can get (SCSI I/O). */
+            pReply->IOCFacts.u8Flags              = 0;      /* Bit 0 is set if the guest must upload the FW prior to using the controller. Obviously not needed here. */
+            pReply->IOCFacts.u16ReplyQueueDepth   = pLsiLogic->cReplyQueueEntries - 1; /* One entry is always free. */
+            pReply->IOCFacts.u16RequestFrameSize  = 128;    /* @todo Figure out where it is needed. */
+            pReply->IOCFacts.u16ProductID         = 0xcafe; /* Our own product ID :) */
+            pReply->IOCFacts.u32CurrentHostMFAHighAddr = pLsiLogic->u32HostMFAHighAddr;
+            pReply->IOCFacts.u16GlobalCredits     = pLsiLogic->cRequestQueueEntries - 1; /* One entry is always free. */
+            pReply->IOCFacts.u8NumberOfPorts      = 1;
+            pReply->IOCFacts.u8EventState         = 0; /* Event notifications not enabled. */
+            pReply->IOCFacts.u32CurrentSenseBufferHighAddr = pLsiLogic->u32SenseBufferHighAddr;
+            pReply->IOCFacts.u16CurReplyFrameSize = pLsiLogic->cbReplyFrame;
+            pReply->IOCFacts.u8MaxDevices         = pLsiLogic->cMaxDevices;
+            pReply->IOCFacts.u8MaxBuses           = pLsiLogic->cMaxBuses;
+            pReply->IOCFacts.u32FwImageSize       = 0; /* No image needed. */
+            pReply->IOCFacts.u32FWVersion         = 0;
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS:
+        {
+            PMptPortFactsRequest pPortFactsReq = (PMptPortFactsRequest)pMessageHdr;
+
+            pReply->PortFacts.u8MessageLength = 10;
+            pReply->PortFacts.u8PortNumber    = pPortFactsReq->u8PortNumber;
+
+            /* This controller only supports one bus with bus number 0. */
+            if (pPortFactsReq->u8PortNumber != 0)
+            {
+                pReply->PortFacts.u8PortType = 0; /* Not existant. */
+            }
+            else
+            {
+                pReply->PortFacts.u8PortType             = 0x01; /* SCSI Port. */
+                pReply->PortFacts.u16MaxDevices          = LSILOGIC_DEVICES_MAX;
+                pReply->PortFacts.u16ProtocolFlags       = RT_BIT(3) | RT_BIT(0); /* SCSI initiator and LUN supported. */
+                pReply->PortFacts.u16PortSCSIID          = 7; /* Default */
+                pReply->PortFacts.u16MaxPersistentIDs    = 0;
+                pReply->PortFacts.u16MaxPostedCmdBuffers = 0; /* Only applies for target mode which we dont support. */
+                pReply->PortFacts.u16MaxLANBuckets       = 0; /* Only for the LAN controller. */
+            }
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE:
+        {
+            /*
+             * The port enable request notifies the IOC to make the port available and perform
+             * appropriate discovery on the associated link.
+             */
+            PMptPortEnableRequest pPortEnableReq = (PMptPortEnableRequest)pMessageHdr;
+
+            pReply->PortEnable.u8MessageLength = 5;
+            pReply->PortEnable.u8PortNumber    = pPortEnableReq->u8PortNumber;
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION:
+        {
+            PMptEventNotificationRequest pEventNotificationReq = (PMptEventNotificationRequest)pMessageHdr;
+
+            if (pEventNotificationReq->u8Switch)
+                pLsiLogic->fEventNotificationEnabled = true;
+            else
+                pLsiLogic->fEventNotificationEnabled = false;
+
+            pReply->EventNotification.u16EventDataLength = 1; /* 1 32bit D-Word. */
+            pReply->EventNotification.u8MessageLength    = 8;
+            pReply->EventNotification.u8MessageFlags     = (1 << 7);
+            pReply->EventNotification.u8AckRequired      = 0;
+            pReply->EventNotification.u32Event           = MPT_EVENT_EVENT_CHANGE;
+            pReply->EventNotification.u32EventContext    = 0;
+            pReply->EventNotification.u32EventData       = pLsiLogic->fEventNotificationEnabled ? 1 : 0;
+
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK:
+        {
+            AssertMsgFailed(("todo"));
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_CONFIG:
+        {
+            PMptConfigurationRequest pConfigurationReq = (PMptConfigurationRequest)pMessageHdr;
+
+            rc = lsilogicProcessConfigurationRequest(pLsiLogic, pConfigurationReq, &pReply->Configuration);
+            AssertRC(rc);
+            break;
+        }
+        case MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST: /* Should be handled already. */
+        default:
+            AssertMsgFailed(("Invalid request function %#x\n", pMessageHdr->u8Function));
+    }
+
+    /* Copy common bits from request message frame to reply. */
+    pReply->Header.u8Function        = pMessageHdr->u8Function;
+    pReply->Header.u32MessageContext = pMessageHdr->u32MessageContext;
+
+    lsilogicFinishAddressReply(pLsiLogic, pReply, fForceReplyPostFifo);
+    return rc;
+}
+#endif
+
+/**
+ * Writes a value to a register at a given offset.
+ *
+ * @returns VBox status code.
+ * @param   pThis    Pointer to the LsiLogic SCSI controller instance data.
+ * @param   uOffset  Offset of the register to write.
+ * @param   pv       Pointer to the value to write
+ * @param   cb       Number of bytes to write.
+ */
+static int lsilogicRegisterWrite(PLSILOGICSCSI pThis, uint32_t uOffset, void *pv, unsigned cb)
+{
+    uint32_t u32 = *(uint32_t *)pv;
+    AssertMsg(cb == 4, ("cb != 4 is %d\n", cb));
+
+    LogFlowFunc(("pThis=%#p uOffset=%#x pv=%#p{%.*Rhxs} cb=%u\n", pThis, uOffset, pv, cb, pv, cb));
+
+    switch (uOffset)
+    {
+        case LSILOGIC_REG_REPLY_QUEUE:
+        {
+            /* Add the entry to the reply free queue. */
+            ASMAtomicWriteU32(&pThis->CTX_SUFF(pReplyFreeQueueBase)[pThis->uReplyFreeQueueNextEntryFreeWrite], u32);
+            pThis->uReplyFreeQueueNextEntryFreeWrite++;
+            pThis->uReplyFreeQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+            break;
+        }
+        case LSILOGIC_REG_REQUEST_QUEUE:
+        {
+            ASMAtomicWriteU32(&pThis->CTX_SUFF(pRequestQueueBase)[pThis->uRequestQueueNextEntryFreeWrite], u32);
+            pThis->uRequestQueueNextEntryFreeWrite++;
+            pThis->uRequestQueueNextEntryFreeWrite %= pThis->cRequestQueueEntries;
+
+            /* Send notification to R3 if there is not one send already. */
+            if (!ASMAtomicXchgBool(&pThis->fNotificationSend, true))
+            {
+                PPDMQUEUEITEMCORE pNotificationItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotificationQueue));
+                AssertPtr(pNotificationItem);
+
+                PDMQueueInsert(pThis->CTX_SUFF(pNotificationQueue), pNotificationItem);
+            }
+            break;
+        }
+        case LSILOGIC_REG_DOORBELL:
+        {
+            /*
+             * When the guest writes to this register a real device would set the
+             * doorbell status bit in the interrupt status register to indicate that the IOP
+             * has still to process the message.
+             * The guest needs to wait with posting new messages here until the bit is cleared.
+             * Because the guest is not continuing execution while we are here we can skip this.
+             */
+            if (!pThis->fDoorbellInProgress)
+            {
+                uint32_t uFunction = LSILOGIC_REG_DOORBELL_GET_FUNCTION(u32);
+
+                switch (uFunction)
+                {
+                    case LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET:
+                    {
+                        pThis->enmState = LSILOGICSTATE_RESET;
+
+                        /* Reset interrupt states. */
+                        pThis->uInterruptMask   = 0;
+                        pThis->uInterruptStatus = 0;
+                        lsilogicUpdateInterrupt(pThis);
+
+                        /* Reset the queues. */
+                        pThis->uReplyFreeQueueNextEntryFreeWrite = 0;
+                        pThis->uReplyFreeQueueNextAddressRead = 0;
+                        pThis->uReplyPostQueueNextEntryFreeWrite = 0;
+                        pThis->uReplyPostQueueNextAddressRead = 0;
+                        pThis->uRequestQueueNextEntryFreeWrite = 0;
+                        pThis->uRequestQueueNextAddressRead = 0;
+                        pThis->enmState = LSILOGICSTATE_READY;
+                        break;
+                    }
+                    case LSILOGIC_DOORBELL_FUNCTION_IO_UNIT_RESET:
+                    {
+                        AssertMsgFailed(("todo\n"));
+                        break;
+                    }
+                    case LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE:
+                    {
+                        pThis->cMessage = LSILOGIC_REG_DOORBELL_GET_SIZE(u32);
+                        pThis->iMessage = 0;
+                        AssertMsg(pThis->cMessage <= RT_ELEMENTS(pThis->aMessage),
+                                  ("Message doesn't fit into the buffer, cMessage=%u", pThis->cMessage));
+                        pThis->fDoorbellInProgress = true;
+                        /* Update the interrupt status to notify the guest that a doorbell function was started. */
+                        lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+                        break;
+                    }
+                    case LSILOGIC_DOORBELL_FUNCTION_REPLY_FRAME_REMOVAL:
+                    {
+                        AssertMsgFailed(("todo\n"));
+                        break;
+                    }
+                    default:
+                        AssertMsgFailed(("Unknown function %u to perform\n", uFunction));
+                }
+            }
+            else
+            {
+                /*
+                 * We are already performing a doorbell function.
+                 * Get the remaining parameters.
+                 */
+                AssertMsg(pThis->iMessage < RT_ELEMENTS(pThis->aMessage), ("Message is too big to fit into the buffer\n"));
+                /*
+                 * If the last byte of the message is written, force a switch to R3 because some requests might force
+                 * a reply through the FIFO which cannot be handled in GC or R0.
+                 */
+#ifndef IN_RING3
+                if (pThis->iMessage == pThis->cMessage - 1)
+                    return VINF_IOM_HC_MMIO_WRITE;
+#endif
+                pThis->aMessage[pThis->iMessage++] = u32;
+#ifdef IN_RING3
+                if (pThis->iMessage == pThis->cMessage)
+                {
+                    int rc = lsilogicProcessMessageRequest(pThis, (PMptMessageHdr)pThis->aMessage, &pThis->ReplyBuffer);
+                    AssertRC(rc);
+                }
+#endif
+            }
+            break;
+        }
+        case LSILOGIC_REG_HOST_INTR_STATUS:
+        {
+            /*
+             * Clear the bits the guest wants except the system doorbell interrupt and the IO controller
+             * status bit.
+             * The former bit is always cleared no matter what the guest writes to the register and
+             * the latter one is read only.
+             */
+            pThis->uInterruptStatus = pThis->uInterruptStatus & ~LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL;
+
+            /*
+             * Check if there is still a doorbell function in progress. Set the
+             * system doorbell interrupt bit again if it is.
+             * We do not use lsilogicSetInterrupt here because the interrupt status
+             * is updated afterwards anyway.
+             */
+            if (   (pThis->fDoorbellInProgress)
+                && (pThis->cMessage == pThis->iMessage))
+            {
+                if (pThis->uNextReplyEntryRead == pThis->cReplySize)
+                {
+                    /* Reply finished. Reset doorbell in progress status. */
+                    Log(("%s: Doorbell function finished\n", __FUNCTION__));
+                    pThis->fDoorbellInProgress = false;
+                }
+                ASMAtomicOrU32(&pThis->uInterruptStatus, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+            }
+
+            lsilogicUpdateInterrupt(pThis);
+            break;
+        }
+        case LSILOGIC_REG_HOST_INTR_MASK:
+        {
+            pThis->uInterruptMask = (u32 & LSILOGIC_REG_HOST_INTR_MASK_W_MASK);
+            lsilogicUpdateInterrupt(pThis);
+            break;
+        }
+        case LSILOGIC_REG_WRITE_SEQUENCE:
+        {
+            if (pThis->fDiagnosticEnabled)
+            {
+                /* Any value will cause a reset and disabling access. */
+                pThis->fDiagnosticEnabled = false;
+                pThis->iDiagnosticAccess  = 0;
+            }
+            else if ((u32 & 0xf) == g_lsilogicDiagnosticAccess[pThis->iDiagnosticAccess])
+            {
+                pThis->iDiagnosticAccess++;
+                if (pThis->iDiagnosticAccess == RT_ELEMENTS(g_lsilogicDiagnosticAccess))
+                {
+                    /*
+                     * Key sequence successfully written. Enable access to diagnostic
+                     * memory and register.
+                     */
+                    pThis->fDiagnosticEnabled = true;
+                }
+            }
+            else
+            {
+                /* Wrong value written - reset to beginning. */
+                pThis->iDiagnosticAccess = 0;
+            }
+            break;
+        }
+        default: /* Ignore. */
+        {
+            break;
+        }
+    }
+    return VINF_SUCCESS;
+}
+
+/**
+ * Reads the content of a register at a given offset.
+ *
+ * @returns VBox status code.
+ * @param   pThis    Pointer to the LsiLogic SCSI controller instance data.
+ * @param   uOffset  Offset of the register to read.
+ * @param   pv       Where to store the content of the register.
+ * @param   cb       Number of bytes to read.
+ */
+static int lsilogicRegisterRead(PLSILOGICSCSI pThis, uint32_t uOffset, void *pv, unsigned cb)
+{
+    uint32_t *pu32 = (uint32_t *)pv;
+    AssertMsg(cb == 4, ("cb != 4 is %d\n", cb));
+
+    switch (uOffset)
+    {
+        case LSILOGIC_REG_REPLY_QUEUE:
+        {
+            if (pThis->uReplyPostQueueNextEntryFreeWrite != pThis->uReplyPostQueueNextAddressRead)
+            {
+                *pu32 = pThis->CTX_SUFF(pReplyPostQueueBase)[pThis->uReplyPostQueueNextAddressRead];
+                pThis->uReplyPostQueueNextAddressRead++;
+                pThis->uReplyPostQueueNextAddressRead %= pThis->cReplyQueueEntries;
+            }
+            else
+            {
+                /* The reply post queue is empty. Reset interrupt. */
+                *pu32 = UINT32_C(0xffffffff);
+                lsilogicClearInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+            }
+            Log(("%s: Returning address %#x\n", __FUNCTION__, *pu32));
+            break;
+        }
+        case LSILOGIC_REG_DOORBELL:
+        {
+            *pu32  = LSILOGIC_REG_DOORBELL_SET_STATE(pThis->enmState);
+            *pu32 |= LSILOGIC_REG_DOORBELL_SET_USED(pThis->fDoorbellInProgress);
+            *pu32 |= LSILOGIC_REG_DOORBELL_SET_WHOINIT(pThis->enmWhoInit);
+            /*
+             * If there is a doorbell function in progress we pass the return value
+             * instead of the status code. We transfer 16bit of the reply
+             * during one read.
+             */
+            if (pThis->fDoorbellInProgress)
+            {
+                /* Return next 16bit value. */
+                *pu32 |= pThis->ReplyBuffer.au16Reply[pThis->uNextReplyEntryRead++];
+            }
+            else
+            {
+                /* We return the status code of the I/O controller. */
+                *pu32 |= pThis->u16IOCFaultCode;
+            }
+            break;
+        }
+        case LSILOGIC_REG_HOST_INTR_STATUS:
+        {
+            *pu32 = pThis->uInterruptStatus;
+            break;
+        }
+        case LSILOGIC_REG_HOST_INTR_MASK:
+        {
+            *pu32 = pThis->uInterruptMask;
+            break;
+        }
+        case LSILOGIC_REG_HOST_DIAGNOSTIC:
+        {
+            //AssertMsgFailed(("todo\n"));
+            break;
+        }
+        case LSILOGIC_REG_TEST_BASE_ADDRESS:
+        {
+            AssertMsgFailed(("todo\n"));
+            break;
+        }
+        case LSILOGIC_REG_DIAG_RW_DATA:
+        {
+            AssertMsgFailed(("todo\n"));
+            break;
+        }
+        case LSILOGIC_REG_DIAG_RW_ADDRESS:
+        {
+            AssertMsgFailed(("todo\n"));
+            break;
+        }
+        default: /* Ignore. */
+        {
+            break;
+        }
+    }
+
+    LogFlowFunc(("pThis=%#p uOffset=%#x pv=%#p{%.*Rhxs} cb=%u\n", pThis, uOffset, pv, cb, pv, cb));
+
+    return VINF_SUCCESS;
+}
+
+PDMBOTHCBDECL(int) lsilogicIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                        RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    uint32_t   uOffset = Port - pThis->IOPortBase;
+
+    Assert(cb <= 4);
+
+    int rc = lsilogicRegisterWrite(pThis, uOffset, &u32, cb);
+    if (rc == VINF_IOM_HC_MMIO_WRITE)
+        rc = VINF_IOM_HC_IOPORT_WRITE;
+
+    return rc;
+}
+
+PDMBOTHCBDECL(int) lsilogicIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                       RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    uint32_t   uOffset = Port - pThis->IOPortBase;
+
+    Assert(cb <= 4);
+
+    return lsilogicRegisterRead(pThis, uOffset, pu32, cb);
+}
+
+PDMBOTHCBDECL(int) lsilogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                     RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    uint32_t   uOffset = GCPhysAddr - pThis->GCPhysMMIOBase;
+
+    return lsilogicRegisterWrite(pThis, uOffset, pv, cb);
+}
+
+PDMBOTHCBDECL(int) lsilogicMMIORead(PPDMDEVINS pDevIns, void *pvUser,
+                                    RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    uint32_t   uOffset = GCPhysAddr - pThis->GCPhysMMIOBase;
+
+    return lsilogicRegisterRead(pThis, uOffset, pv, cb);
+}
+
+PDMBOTHCBDECL(int) lsilogicDiagnosticWrite(PPDMDEVINS pDevIns, void *pvUser,
+                                           RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    LogFlowFunc(("pThis=%#p GCPhysAddr=%RGp pv=%#p{%.*Rhxs} cb=%u\n", pThis, GCPhysAddr, pv, cb, pv, cb));
+
+    return VINF_SUCCESS;
+}
+
+PDMBOTHCBDECL(int) lsilogicDiagnosticRead(PPDMDEVINS pDevIns, void *pvUser,
+                                          RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    LogFlowFunc(("pThis=%#p GCPhysAddr=%RGp pv=%#p{%.*Rhxs} cb=%u\n", pThis, GCPhysAddr, pv, cb, pv, cb));
+
+    return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Copies a contigous buffer into the scatter gather list provided by the guest.
+ *
+ * @returns nothing
+ * @param   pTaskState    Pointer to the task state which contains the SGL.
+ * @param   pvBuf         Pointer to the buffer to copy.
+ * @param   cbCopy        Number of bytes to copy.
+ */
+static void lsilogicScatterGatherListCopyFromBuffer(PLSILOGICTASKSTATE pTaskState, void *pvBuf, size_t cbCopy)
+{
+    unsigned cSGEntry = 0;
+    PPDMDATASEG pSGEntry = &pTaskState->pSGListHead[cSGEntry];
+    uint8_t *pu8Buf = (uint8_t *)pvBuf;
+
+    while (cSGEntry < pTaskState->cSGListEntries)
+    {
+        size_t cbToCopy = (cbCopy < pSGEntry->cbSeg) ? cbCopy : pSGEntry->cbSeg;
+
+        memcpy(pSGEntry->pvSeg, pu8Buf, cbToCopy);
+
+        cbCopy -= cbToCopy;
+        /* We finished. */
+        if (!cbCopy)
+            break;
+
+        /* Advance the buffer. */
+        pu8Buf += cbToCopy;
+
+        /* Go to the next entry in the list. */
+        pSGEntry++;
+        cSGEntry++;
+    }
+}
+
+/**
+ * Copy a temporary buffer into a part of the guest scatter gather list
+ * described by the given descriptor entry.
+ *
+ * @returns nothing.
+ * @param   pDevIns    Pointer to the device instance data.
+ * @param   pSGInfo    Pointer to the segment info structure which describes the guest segments
+ *                     to write to which are unaligned.
+ */
+static void lsilogicCopyFromBufferIntoSGList(PPDMDEVINS pDevIns, PLSILOGICTASKSTATESGENTRY pSGInfo)
+{
+    RTGCPHYS GCPhysBuffer = pSGInfo->u.GCPhysAddrBufferUnaligned;
+
+    AssertMsg(!pSGInfo->fGuestMemory, ("This is not possible\n"));
+
+    /* Copy into SG entry. */
+    PDMDevHlpPhysWrite(pDevIns, GCPhysBuffer, pSGInfo->pvBuf, pSGInfo->cbBuf);
+
+}
+
+/**
+ * Copy a part of the guest scatter gather list into a temporary buffer.
+ *
+ * @returns nothing.
+ * @param   pDevIns    Pointer to the device instance data.
+ * @param   pSGInfo    Pointer to the segment info structure which describes the guest segments
+ *                     to read from which are unaligned.
+ */
+static void lsilogicCopyFromSGListIntoBuffer(PPDMDEVINS pDevIns, PLSILOGICTASKSTATESGENTRY pSGInfo)
+{
+    RTGCPHYS GCPhysBuffer = pSGInfo->u.GCPhysAddrBufferUnaligned;
+
+    AssertMsg(!pSGInfo->fGuestMemory, ("This is not possible\n"));
+
+    /* Copy into temporary buffer. */
+    PDMDevHlpPhysRead(pDevIns, GCPhysBuffer, pSGInfo->pvBuf, pSGInfo->cbBuf);
+}
+
+static int lsilogicScatterGatherListAllocate(PLSILOGICTASKSTATE pTaskState, uint32_t cSGList, uint32_t cSGInfo, uint32_t cbUnaligned)
+{
+    if (pTaskState->cSGListSize < cSGList)
+    {
+        /* The entries are not allocated yet or the number is too small. */
+        if (pTaskState->cSGListSize)
+            RTMemFree(pTaskState->pSGListHead);
+
+        /* Allocate R3 scatter gather list. */
+        pTaskState->pSGListHead = (PPDMDATASEG)RTMemAllocZ(cSGList * sizeof(PDMDATASEG));
+        if (!pTaskState->pSGListHead)
+            return VERR_NO_MEMORY;
+
+        /* Reset usage statistics. */
+        pTaskState->cSGListSize     = cSGList;
+        pTaskState->cSGListEntries  = cSGList;
+        pTaskState->cSGListTooBig = 0;
+    }
+    else if (pTaskState->cSGListSize > cSGList)
+    {
+        /*
+         * The list is too big. Increment counter.
+         * So that the destroying function can free
+         * the list if it is too big too many times
+         * in a row.
+         */
+        pTaskState->cSGListEntries = cSGList;
+        pTaskState->cSGListTooBig++;
+    }
+    else
+    {
+        /*
+         * Needed entries matches current size.
+         * Reset counter.
+         */
+        pTaskState->cSGListEntries = cSGList;
+        pTaskState->cSGListTooBig  = 0;
+    }
+
+    if (pTaskState->cSGInfoSize < cSGInfo)
+    {
+        /* The entries are not allocated yet or the number is too small. */
+        if (pTaskState->cSGInfoSize)
+            RTMemFree(pTaskState->paSGEntries);
+
+        pTaskState->paSGEntries = (PLSILOGICTASKSTATESGENTRY)RTMemAllocZ(cSGInfo * sizeof(LSILOGICTASKSTATESGENTRY));
+        if (!pTaskState->paSGEntries)
+            return VERR_NO_MEMORY;
+
+        /* Reset usage statistics. */
+        pTaskState->cSGInfoSize = cSGInfo;
+        pTaskState->cSGInfoEntries  = cSGInfo;
+        pTaskState->cSGInfoTooBig = 0;
+    }
+    else if (pTaskState->cSGInfoSize > cSGInfo)
+    {
+        /*
+         * The list is too big. Increment counter.
+         * So that the destroying function can free
+         * the list if it is too big too many times
+         * in a row.
+         */
+        pTaskState->cSGInfoEntries = cSGInfo;
+        pTaskState->cSGInfoTooBig++;
+    }
+    else
+    {
+        /*
+         * Needed entries matches current size.
+         * Reset counter.
+         */
+        pTaskState->cSGInfoEntries  = cSGInfo;
+        pTaskState->cSGInfoTooBig = 0;
+    }
+
+
+    if (pTaskState->cbBufferUnaligned < cbUnaligned)
+    {
+        if (pTaskState->pvBufferUnaligned)
+            RTMemFree(pTaskState->pvBufferUnaligned);
+
+        Log(("%s: Allocating buffer for unaligned segments cbUnaligned=%u\n", __FUNCTION__, cbUnaligned));
+
+        pTaskState->pvBufferUnaligned = RTMemAllocZ(cbUnaligned);
+        if (!pTaskState->pvBufferUnaligned)
+            return VERR_NO_MEMORY;
+
+        pTaskState->cbBufferUnaligned = cbUnaligned;
+    }
+
+    /* Make debugging easier. */
+#ifdef DEBUG
+    memset(pTaskState->pSGListHead, 0, pTaskState->cSGListSize * sizeof(PDMDATASEG));
+    memset(pTaskState->paSGEntries, 0, pTaskState->cSGInfoSize * sizeof(LSILOGICTASKSTATESGENTRY));
+    if (pTaskState->pvBufferUnaligned)
+        memset(pTaskState->pvBufferUnaligned, 0, pTaskState->cbBufferUnaligned);
+#endif
+    return VINF_SUCCESS;
+}
+
+/**
+ * Destroy a scatter gather list.
+ *
+ * @returns nothing.
+ * @param   pLsiLogic    Pointer to the LsiLogic SCSI controller.
+ * @param   pTaskState   Pointer to the task state.
+ */
+static void lsilogicScatterGatherListDestroy(PLSILOGICSCSI pLsiLogic, PLSILOGICTASKSTATE pTaskState)
+{
+    PPDMDEVINS                pDevIns     = pLsiLogic->CTX_SUFF(pDevIns);
+    PLSILOGICTASKSTATESGENTRY pSGInfoCurr = pTaskState->paSGEntries;
+
+    for (unsigned i = 0; i < pTaskState->cSGInfoEntries; i++)
+    {
+        if (pSGInfoCurr->fGuestMemory)
+        {
+            /* Release the lock. */
+            PDMDevHlpPhysReleasePageMappingLock(pDevIns, &pSGInfoCurr->u.PageLock);
+        }
+        else if (!pSGInfoCurr->fBufferContainsData)
+        {
+            /* Copy the data into the guest segments now. */
+            lsilogicCopyFromBufferIntoSGList(pLsiLogic->CTX_SUFF(pDevIns), pSGInfoCurr);
+        }
+
+        pSGInfoCurr++;
+    }
+
+    /* Free allocated memory if the list was too big too many times. */
+    if (pTaskState->cSGListTooBig >= LSILOGIC_NR_OF_ALLOWED_BIGGER_LISTS)
+    {
+        RTMemFree(pTaskState->pSGListHead);
+        RTMemFree(pTaskState->paSGEntries);
+        if (pTaskState->pvBufferUnaligned)
+            RTMemFree(pTaskState->pvBufferUnaligned);
+        pTaskState->cSGListSize = 0;
+        pTaskState->cSGInfoSize = 0;
+        pTaskState->cSGInfoEntries = 0;
+        pTaskState->cSGListTooBig  = 0;
+        pTaskState->pSGListHead = NULL;
+        pTaskState->paSGEntries = NULL;
+        pTaskState->pvBufferUnaligned = NULL;
+        pTaskState->cbBufferUnaligned = 0;
+    }
+}
+
+#ifdef DEBUG
+/**
+ * Dump an SG entry.
+ *
+ * @returns nothing.
+ * @param   pSGEntry    Pointer to the SG entry to dump
+ */
+static void lsilogicDumpSGEntry(PMptSGEntryUnion pSGEntry)
+{
+    switch (pSGEntry->Simple32.u2ElementType)
+    {
+        case MPTSGENTRYTYPE_SIMPLE:
+        {
+            Log(("%s: Dumping info for SIMPLE SG entry:\n", __FUNCTION__));
+            Log(("%s: u24Length=%u\n", __FUNCTION__, pSGEntry->Simple32.u24Length));
+            Log(("%s: fEndOfList=%d\n", __FUNCTION__, pSGEntry->Simple32.fEndOfList));
+            Log(("%s: f64BitAddress=%d\n", __FUNCTION__, pSGEntry->Simple32.f64BitAddress));
+            Log(("%s: fBufferContainsData=%d\n", __FUNCTION__, pSGEntry->Simple32.fBufferContainsData));
+            Log(("%s: fLocalAddress=%d\n", __FUNCTION__, pSGEntry->Simple32.fLocalAddress));
+            Log(("%s: fEndOfBuffer=%d\n", __FUNCTION__, pSGEntry->Simple32.fEndOfBuffer));
+            Log(("%s: fLastElement=%d\n", __FUNCTION__, pSGEntry->Simple32.fLastElement));
+            Log(("%s: u32DataBufferAddressLow=%u\n", __FUNCTION__, pSGEntry->Simple32.u32DataBufferAddressLow));
+            if (pSGEntry->Simple32.f64BitAddress)
+            {
+                Log(("%s: u32DataBufferAddressHigh=%u\n", __FUNCTION__, pSGEntry->Simple64.u32DataBufferAddressHigh));
+                Log(("%s: GCDataBufferAddress=%RGp\n", __FUNCTION__,
+                    ((uint64_t)pSGEntry->Simple64.u32DataBufferAddressHigh << 32) | pSGEntry->Simple64.u32DataBufferAddressLow));
+            }
+            else
+                Log(("%s: GCDataBufferAddress=%RGp\n", __FUNCTION__, pSGEntry->Simple32.u32DataBufferAddressLow));
+
+            break;
+        }
+        case MPTSGENTRYTYPE_CHAIN:
+        {
+            Log(("%s: Dumping info for CHAIN SG entry:\n", __FUNCTION__));
+            Log(("%s: u16Length=%u\n", __FUNCTION__, pSGEntry->Chain.u16Length));
+            Log(("%s: u8NExtChainOffset=%d\n", __FUNCTION__, pSGEntry->Chain.u8NextChainOffset));
+            Log(("%s: f64BitAddress=%d\n", __FUNCTION__, pSGEntry->Chain.f64BitAddress));
+            Log(("%s: fLocalAddress=%d\n", __FUNCTION__, pSGEntry->Chain.fLocalAddress));
+            Log(("%s: u32SegmentAddressLow=%u\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressLow));
+            Log(("%s: u32SegmentAddressHigh=%u\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressHigh));
+            if (pSGEntry->Chain.f64BitAddress)
+                Log(("%s: GCSegmentAddress=%RGp\n", __FUNCTION__,
+                    ((uint64_t)pSGEntry->Chain.u32SegmentAddressHigh << 32) | pSGEntry->Chain.u32SegmentAddressLow));
+            else
+                Log(("%s: GCSegmentAddress=%RGp\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressLow));
+            break;
+        }
+    }
+}
+#endif
+
+/**
+ * Create scatter gather list descriptors.
+ *
+ * @returns VBox status code.
+ * @param   pLsiLogic      Pointer to the LsiLogic SCSI controller.
+ * @param   pTaskState     Pointer to the task state.
+ * @param   GCPhysSGLStart Guest physical address of the first SG entry.
+ * @param   uChainOffset   Offset in bytes from the beginning of the SGL segment to the chain element.
+ * @thread  EMT
+ */
+static int lsilogicScatterGatherListCreate(PLSILOGICSCSI pLsiLogic, PLSILOGICTASKSTATE pTaskState,
+                                           RTGCPHYS GCPhysSGLStart, uint32_t uChainOffset)
+{
+    int                        rc           = VINF_SUCCESS;
+    PPDMDEVINS                 pDevIns      = pLsiLogic->CTX_SUFF(pDevIns);
+    PVM                        pVM          = PDMDevHlpGetVM(pDevIns);
+    bool                       fUnaligned;     /* Flag whether the current buffer is unaligned. */
+    uint32_t                   cbUnaligned;    /* Size of the unaligned buffers. */
+    uint32_t                   cSGEntriesR3 = 0;
+    uint32_t                   cSGInfo      = 0;
+    uint32_t                   cbSegment    = 0;
+    PLSILOGICTASKSTATESGENTRY  pSGInfoCurr  = NULL;
+    uint8_t                   *pu8BufferUnalignedPos = NULL;
+    uint8_t                   *pbBufferUnalignedSGInfoPos = NULL;
+    uint32_t                   cbUnalignedComplete = 0;
+    bool                       fDoMapping = false;
+    bool                       fEndOfList;
+    RTGCPHYS                   GCPhysSGEntryNext;
+    RTGCPHYS                   GCPhysSegmentStart;
+    uint32_t                   uChainOffsetNext;
+
+    /*
+     * Two passes - one to count needed scatter gather list entries and needed unaligned
+     * buffers and one to actually map the SG list into R3.
+     */
+    for (int i = 0; i < 2; i++)
+    {
+        fUnaligned      = false;
+        cbUnaligned     = 0;
+        fEndOfList      = false;
+
+        GCPhysSGEntryNext  = GCPhysSGLStart;
+        uChainOffsetNext   = uChainOffset;
+        GCPhysSegmentStart = GCPhysSGLStart;
+
+        if (fDoMapping)
+        {
+            Log(("%s: cSGInfo=%u\n", __FUNCTION__, cSGInfo));
+
+            /* The number of needed SG entries in R3 is known. Allocate needed memory. */
+            rc = lsilogicScatterGatherListAllocate(pTaskState, cSGInfo, cSGInfo, cbUnalignedComplete);
+            AssertMsgRC(rc, ("Failed to allocate scatter gather array rc=%Rrc\n", rc));
+
+            /* We are now able to map the pages into R3. */
+            pSGInfoCurr = pTaskState->paSGEntries;
+            /* Initialize first segment to remove the need for additional if checks later in the code. */
+            pSGInfoCurr->fGuestMemory= false;
+            pu8BufferUnalignedPos = (uint8_t *)pTaskState->pvBufferUnaligned;
+            pbBufferUnalignedSGInfoPos = pu8BufferUnalignedPos;
+        }
+
+        /* Go through the list until we reach the end. */
+        while (!fEndOfList)
+        {
+            bool fEndOfSegment = false;
+
+            while (!fEndOfSegment)
+            {
+                MptSGEntryUnion SGEntry;
+
+                Log(("%s: Reading SG entry from %RGp\n", __FUNCTION__, GCPhysSGEntryNext));
+
+                /* Read the entry. */
+                PDMDevHlpPhysRead(pDevIns, GCPhysSGEntryNext, &SGEntry, sizeof(MptSGEntryUnion));
+
+#ifdef DEBUG
+                lsilogicDumpSGEntry(&SGEntry);
+#endif
+
+                AssertMsg(SGEntry.Simple32.u2ElementType == MPTSGENTRYTYPE_SIMPLE, ("Invalid SG entry type\n"));
+
+                /* Check if this is a zero element. */
+                if (   !SGEntry.Simple32.u24Length
+                    && SGEntry.Simple32.fEndOfList
+                    && SGEntry.Simple32.fEndOfBuffer)
+                {
+                    pTaskState->cSGListEntries = 0;
+                    pTaskState->cSGInfoEntries = 0;
+                    return VINF_SUCCESS;
+                }
+
+                uint32_t cbDataToTransfer     = SGEntry.Simple32.u24Length;
+                bool     fBufferContainsData  = !!SGEntry.Simple32.fBufferContainsData;
+                RTGCPHYS GCPhysAddrDataBuffer = SGEntry.Simple32.u32DataBufferAddressLow;
+
+                if (SGEntry.Simple32.f64BitAddress)
+                {
+                    GCPhysAddrDataBuffer |= ((uint64_t)SGEntry.Simple64.u32DataBufferAddressHigh) << 32;
+                    GCPhysSGEntryNext += sizeof(MptSGEntrySimple64);
+                }
+                else
+                    GCPhysSGEntryNext += sizeof(MptSGEntrySimple32);
+
+                if (fDoMapping)
+                {
+                    pSGInfoCurr->fGuestMemory = false;
+                    pSGInfoCurr->fBufferContainsData = fBufferContainsData;
+                    pSGInfoCurr->cbBuf = cbDataToTransfer;
+                    pSGInfoCurr->pvBuf = pbBufferUnalignedSGInfoPos;
+                    pbBufferUnalignedSGInfoPos += cbDataToTransfer;
+                    pSGInfoCurr->u.GCPhysAddrBufferUnaligned = GCPhysAddrDataBuffer;
+                    if (fBufferContainsData)
+                        lsilogicCopyFromSGListIntoBuffer(pDevIns, pSGInfoCurr);
+                    pSGInfoCurr++;
+                }
+                else
+                {
+                    cbUnalignedComplete += cbDataToTransfer;
+                    cSGInfo++;
+                }
+
+                /* Check if we reached the end of the list. */
+                if (SGEntry.Simple32.fEndOfList)
+                {
+                    /* We finished. */
+                    fEndOfSegment = true;
+                    fEndOfList = true;
+                }
+                else if (SGEntry.Simple32.fLastElement)
+                {
+                    fEndOfSegment = true;
+                }
+            } /* while (!fEndOfSegment) */
+
+            /* Get next chain element. */
+            if (uChainOffsetNext)
+            {
+                MptSGEntryChain SGEntryChain;
+
+                PDMDevHlpPhysRead(pDevIns, GCPhysSegmentStart + uChainOffsetNext, &SGEntryChain, sizeof(MptSGEntryChain));
+
+                AssertMsg(SGEntryChain.u2ElementType == MPTSGENTRYTYPE_CHAIN, ("Invalid SG entry type\n"));
+
+               /* Set the next address now. */
+                GCPhysSGEntryNext = SGEntryChain.u32SegmentAddressLow;
+                if (SGEntryChain.f64BitAddress)
+                    GCPhysSGEntryNext |= ((uint64_t)SGEntryChain.u32SegmentAddressHigh) << 32;
+
+                GCPhysSegmentStart = GCPhysSGEntryNext;
+                uChainOffsetNext   = SGEntryChain.u8NextChainOffset * sizeof(uint32_t);
+            }
+
+        } /* while (!fEndOfList) */
+
+        fDoMapping = true;
+        if (fUnaligned)
+            cbUnalignedComplete += cbUnaligned;
+    }
+
+    uint32_t    cSGEntries;
+    PPDMDATASEG pSGEntryCurr = pTaskState->pSGListHead;
+    pSGInfoCurr              = pTaskState->paSGEntries;
+
+    /* Initialize first entry. */
+    pSGEntryCurr->pvSeg = pSGInfoCurr->pvBuf;
+    pSGEntryCurr->cbSeg = pSGInfoCurr->cbBuf;
+    pSGInfoCurr++;
+    cSGEntries = 1;
+
+    /* Construct the scatter gather list. */
+    for (unsigned i = 0; i < (pTaskState->cSGInfoEntries-1); i++)
+    {
+        if (pSGEntryCurr->cbSeg % 512 != 0)
+        {
+            AssertMsg((uint8_t *)pSGEntryCurr->pvSeg + pSGEntryCurr->cbSeg == pSGInfoCurr->pvBuf,
+                      ("Buffer ist not sector aligned but the buffer addresses are not adjacent\n"));
+
+            pSGEntryCurr->cbSeg += pSGInfoCurr->cbBuf;
+        }
+        else
+        {
+            if (((uint8_t *)pSGEntryCurr->pvSeg + pSGEntryCurr->cbSeg) == pSGInfoCurr->pvBuf)
+            {
+                pSGEntryCurr->cbSeg += pSGInfoCurr->cbBuf;
+            }
+            else
+            {
+                pSGEntryCurr++;
+                cSGEntries++;
+                pSGEntryCurr->pvSeg = pSGInfoCurr->pvBuf;
+                pSGEntryCurr->cbSeg = pSGInfoCurr->cbBuf;
+            }
+        }
+
+        pSGInfoCurr++;
+    }
+
+    pTaskState->cSGListEntries = cSGEntries;
+
+    return rc;
+}
+
+/*
+ * Disabled because the sense buffer provided by the LsiLogic driver for Windows XP
+ * crosses page boundaries.
+ */
+#if 0
+/**
+ * Free the sense buffer.
+ *
+ * @returns nothing.
+ * @param   pTaskState   Pointer to the task state.
+ */
+static void lsilogicFreeGCSenseBuffer(PLSILOGICSCSI pLsiLogic, PLSILOGICTASKSTATE pTaskState)
+{
+    PVM pVM = PDMDevHlpGetVM(pLsiLogic->CTX_SUFF(pDevIns));
+
+    PGMPhysReleasePageMappingLock(pVM, &pTaskState->PageLockSense);
+    pTaskState->pbSenseBuffer = NULL;
+}
+
+/**
+ * Map the sense buffer into R3.
+ *
+ * @returns VBox status code.
+ * @param   pTaskState    Pointer to the task state.
+ * @note Current assumption is that the sense buffer is not scattered and does not cross a page boundary.
+ */
+static int lsilogicMapGCSenseBufferIntoR3(PLSILOGICSCSI pLsiLogic, PLSILOGICTASKSTATE pTaskState)
+{
+    int rc = VINF_SUCCESS;
+    PPDMDEVINS pDevIns = pLsiLogic->CTX_SUFF(pDevIns);
+    RTGCPHYS GCPhysAddrSenseBuffer;
+
+    GCPhysAddrSenseBuffer = pTaskState->GuestRequest.SCSIIO.u32SenseBufferLowAddress;
+    GCPhysAddrSenseBuffer |= ((uint64_t)pLsiLogic->u32SenseBufferHighAddr << 32);
+
+#ifdef RT_STRICT
+    uint32_t cbSenseBuffer = pTaskState->GuestRequest.SCSIIO.u8SenseBufferLength;
+#endif
+    RTGCPHYS GCPhysAddrSenseBufferBase = PAGE_ADDRESS(GCPhysAddrSenseBuffer);
+
+    AssertMsg(GCPhysAddrSenseBuffer >= GCPhysAddrSenseBufferBase,
+              ("Impossible GCPhysAddrSenseBuffer < GCPhysAddrSenseBufferBase\n"));
+
+    /* Sanity checks for the assumption. */
+    AssertMsg(((GCPhysAddrSenseBuffer + cbSenseBuffer) <= (GCPhysAddrSenseBufferBase + PAGE_SIZE)),
+              ("Sense buffer crosses page boundary\n"));
+
+    rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrSenseBufferBase, (void **)&pTaskState->pbSenseBuffer, &pTaskState->PageLockSense);
+    AssertMsgRC(rc, ("Mapping sense buffer failed rc=%Rrc\n", rc));
+
+    /* Correct start address of the sense buffer. */
+    pTaskState->pbSenseBuffer += (GCPhysAddrSenseBuffer - GCPhysAddrSenseBufferBase);
+
+    return rc;
+}
+#endif
+
+#ifdef DEBUG
+static void lsilogicDumpSCSIIORequest(PMptSCSIIORequest pSCSIIORequest)
+{
+    Log(("%s: u8TargetID=%d\n", __FUNCTION__, pSCSIIORequest->u8TargetID));
+    Log(("%s: u8Bus=%d\n", __FUNCTION__, pSCSIIORequest->u8Bus));
+    Log(("%s: u8ChainOffset=%d\n", __FUNCTION__, pSCSIIORequest->u8ChainOffset));
+    Log(("%s: u8Function=%d\n", __FUNCTION__, pSCSIIORequest->u8Function));
+    Log(("%s: u8CDBLength=%d\n", __FUNCTION__, pSCSIIORequest->u8CDBLength));
+    Log(("%s: u8SenseBufferLength=%d\n", __FUNCTION__, pSCSIIORequest->u8SenseBufferLength));
+    Log(("%s: u8MessageFlags=%d\n", __FUNCTION__, pSCSIIORequest->u8MessageFlags));
+    Log(("%s: u32MessageContext=%#x\n", __FUNCTION__, pSCSIIORequest->u32MessageContext));
+    for (unsigned i = 0; i < RT_ELEMENTS(pSCSIIORequest->au8LUN); i++)
+        Log(("%s: u8LUN[%d]=%d\n", __FUNCTION__, i, pSCSIIORequest->au8LUN[i]));
+    Log(("%s: u32Control=%#x\n", __FUNCTION__, pSCSIIORequest->u32Control));
+    for (unsigned i = 0; i < RT_ELEMENTS(pSCSIIORequest->au8CDB); i++)
+        Log(("%s: u8CDB[%d]=%d\n", __FUNCTION__, i, pSCSIIORequest->au8CDB[i]));
+    Log(("%s: u32DataLength=%#x\n", __FUNCTION__, pSCSIIORequest->u32DataLength));
+    Log(("%s: u32SenseBufferLowAddress=%#x\n", __FUNCTION__, pSCSIIORequest->u32SenseBufferLowAddress));
+}
+#endif
+
+/**
+ * Processes a SCSI I/O request by setting up the request
+ * and sending it to the underlying SCSI driver.
+ * Steps needed to complete request are done in the
+ * callback called by the driver below upon completion of
+ * the request.
+ *
+ * @returns VBox status code.
+ * @param   pLsiLogic    Pointer to the device instance which sends the request.
+ * @param   pTaskState   Pointer to the task state data.
+ */
+static int lsilogicProcessSCSIIORequest(PLSILOGICSCSI pLsiLogic, PLSILOGICTASKSTATE pTaskState)
+{
+    int rc = VINF_SUCCESS;
+
+#ifdef DEBUG
+    lsilogicDumpSCSIIORequest(&pTaskState->GuestRequest.SCSIIO);
+#endif
+
+    pTaskState->fBIOS = false;
+
+    uint32_t uChainOffset = pTaskState->GuestRequest.SCSIIO.u8ChainOffset;
+
+    if (uChainOffset)
+        uChainOffset = uChainOffset * sizeof(uint32_t) - sizeof(MptSCSIIORequest);
+
+    /* Create Scatter gather list. */
+    rc = lsilogicScatterGatherListCreate(pLsiLogic, pTaskState,
+                                         pTaskState->GCPhysMessageFrameAddr + sizeof(MptSCSIIORequest),
+                                         uChainOffset);
+    AssertRC(rc);
+
+#if 0
+    /* Map sense buffer. */
+    rc = lsilogicMapGCSenseBufferIntoR3(pLsiLogic, pTaskState);
+    AssertRC(rc);
+#endif
+
+    if (RT_LIKELY(   (pTaskState->GuestRequest.SCSIIO.u8TargetID < LSILOGIC_DEVICES_MAX)
+                  && (pTaskState->GuestRequest.SCSIIO.u8Bus == 0)))
+    {
+        PLSILOGICDEVICE pTargetDevice;
+        pTargetDevice = &pLsiLogic->aDeviceStates[pTaskState->GuestRequest.SCSIIO.u8TargetID];
+
+        if (pTargetDevice->pDrvBase)
+        {
+            /* Setup the SCSI request. */
+            pTaskState->pTargetDevice                        = pTargetDevice;
+            pTaskState->PDMScsiRequest.uLogicalUnit          = pTaskState->GuestRequest.SCSIIO.au8LUN[1];
+
+            uint8_t uDataDirection = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_GET(pTaskState->GuestRequest.SCSIIO.u32Control);
+
+            if (uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE)
+                pTaskState->PDMScsiRequest.uDataDirection    = PDMSCSIREQUESTTXDIR_NONE;
+            else if (uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE)
+                pTaskState->PDMScsiRequest.uDataDirection    = PDMSCSIREQUESTTXDIR_TO_DEVICE;
+            else if (uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ)
+                pTaskState->PDMScsiRequest.uDataDirection    = PDMSCSIREQUESTTXDIR_FROM_DEVICE;
+
+            pTaskState->PDMScsiRequest.cbCDB                 = pTaskState->GuestRequest.SCSIIO.u8CDBLength;
+            pTaskState->PDMScsiRequest.pbCDB                 = pTaskState->GuestRequest.SCSIIO.au8CDB;
+            pTaskState->PDMScsiRequest.cbScatterGather       = pTaskState->GuestRequest.SCSIIO.u32DataLength;
+            pTaskState->PDMScsiRequest.cScatterGatherEntries = pTaskState->cSGListEntries;
+            pTaskState->PDMScsiRequest.paScatterGatherHead   = pTaskState->pSGListHead;
+            pTaskState->PDMScsiRequest.cbSenseBuffer         = sizeof(pTaskState->abSenseBuffer);
+            memset(pTaskState->abSenseBuffer, 0, pTaskState->PDMScsiRequest.cbSenseBuffer);
+            pTaskState->PDMScsiRequest.pbSenseBuffer         = pTaskState->abSenseBuffer;
+            pTaskState->PDMScsiRequest.pvUser                = pTaskState;
+
+            ASMAtomicIncU32(&pTargetDevice->cOutstandingRequests);
+            rc = pTargetDevice->pDrvSCSIConnector->pfnSCSIRequestSend(pTargetDevice->pDrvSCSIConnector, &pTaskState->PDMScsiRequest);
+            AssertMsgRC(rc, ("Sending request to SCSI layer failed rc=%Rrc\n", rc));
+            return VINF_SUCCESS;
+        }
+        else
+        {
+            /* Device is not present report SCSI selection timeout. */
+            pTaskState->IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE;
+        }
+    }
+    else
+    {
+        /* Report out of bounds target ID or bus. */
+        if (pTaskState->GuestRequest.SCSIIO.u8Bus != 0)
+            pTaskState->IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_BUS;
+        else
+            pTaskState->IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_TARGETID;
+    }
+
+    /* The rest is equal to both errors. */
+    pTaskState->IOCReply.SCSIIOError.u8TargetID = pTaskState->GuestRequest.SCSIIO.u8TargetID;
+    pTaskState->IOCReply.SCSIIOError.u8Bus = pTaskState->GuestRequest.SCSIIO.u8Bus;
+    pTaskState->IOCReply.SCSIIOError.u8MessageLength = sizeof(MptSCSIIOErrorReply) / 4;
+    pTaskState->IOCReply.SCSIIOError.u8Function = pTaskState->GuestRequest.SCSIIO.u8Function;
+    pTaskState->IOCReply.SCSIIOError.u8CDBLength = pTaskState->GuestRequest.SCSIIO.u8CDBLength;
+    pTaskState->IOCReply.SCSIIOError.u8SenseBufferLength = pTaskState->GuestRequest.SCSIIO.u8SenseBufferLength;
+    pTaskState->IOCReply.SCSIIOError.u32MessageContext = pTaskState->GuestRequest.SCSIIO.u32MessageContext;
+    pTaskState->IOCReply.SCSIIOError.u8SCSIStatus = SCSI_STATUS_OK;
+    pTaskState->IOCReply.SCSIIOError.u8SCSIState  = MPT_SCSI_IO_ERROR_SCSI_STATE_TERMINATED;
+    pTaskState->IOCReply.SCSIIOError.u32IOCLogInfo    = 0;
+    pTaskState->IOCReply.SCSIIOError.u32TransferCount = 0;
+    pTaskState->IOCReply.SCSIIOError.u32SenseCount    = 0;
+    pTaskState->IOCReply.SCSIIOError.u32ResponseInfo  = 0;
+
+    lsilogicFinishAddressReply(pLsiLogic, &pTaskState->IOCReply, false);
+    RTCacheInsert(pLsiLogic->pTaskCache, pTaskState);
+
+    return rc;
+}
+
+/**
+ * Called upon completion of the request from the SCSI driver below.
+ * This function frees all allocated ressources and notifies the guest
+ * that the process finished by asserting an interrupt.
+ *
+ * @returns VBox status code.
+ * @param   pInterface    Pointer to the interface the called funtion belongs to.
+ * @param   pSCSIRequest  Pointer to the SCSI request which finished.
+ */
+static DECLCALLBACK(int) lsilogicDeviceSCSIRequestCompleted(PPDMISCSIPORT pInterface, PPDMSCSIREQUEST pSCSIRequest, int rcCompletion)
+{
+    PLSILOGICTASKSTATE pTaskState      = (PLSILOGICTASKSTATE)pSCSIRequest->pvUser;
+    PLSILOGICDEVICE    pLsiLogicDevice = pTaskState->pTargetDevice;
+    PLSILOGICSCSI      pLsiLogic       = pLsiLogicDevice->CTX_SUFF(pLsiLogic);
+
+    ASMAtomicDecU32(&pLsiLogicDevice->cOutstandingRequests);
+
+    if (RT_UNLIKELY(pTaskState->fBIOS))
+    {
+        int rc = vboxscsiRequestFinished(&pLsiLogic->VBoxSCSI, pSCSIRequest);
+        AssertMsgRC(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc));
+    }
+    else
+    {
+#if 0
+        lsilogicFreeGCSenseBuffer(pLsiLogic, pTaskState);
+#else
+        RTGCPHYS GCPhysAddrSenseBuffer;
+
+        GCPhysAddrSenseBuffer = pTaskState->GuestRequest.SCSIIO.u32SenseBufferLowAddress;
+        GCPhysAddrSenseBuffer |= ((uint64_t)pLsiLogic->u32SenseBufferHighAddr << 32);
+
+        /* Copy the sense buffer over. */
+        PDMDevHlpPhysWrite(pLsiLogic->CTX_SUFF(pDevIns), GCPhysAddrSenseBuffer, pTaskState->abSenseBuffer,
+                             RT_UNLIKELY(pTaskState->GuestRequest.SCSIIO.u8SenseBufferLength < pTaskState->PDMScsiRequest.cbSenseBuffer)
+                           ? pTaskState->GuestRequest.SCSIIO.u8SenseBufferLength
+                           : pTaskState->PDMScsiRequest.cbSenseBuffer);
+#endif
+        lsilogicScatterGatherListDestroy(pLsiLogic, pTaskState);
+
+
+        if (RT_LIKELY(rcCompletion == SCSI_STATUS_OK))
+            lsilogicFinishContextReply(pLsiLogic, pTaskState->GuestRequest.SCSIIO.u32MessageContext);
+        else
+        {
+            /* The SCSI target encountered an error during processing post a reply. */
+            memset(&pTaskState->IOCReply, 0, sizeof(MptReplyUnion));
+            pTaskState->IOCReply.SCSIIOError.u8TargetID = pTaskState->GuestRequest.SCSIIO.u8TargetID;
+            pTaskState->IOCReply.SCSIIOError.u8Bus      = pTaskState->GuestRequest.SCSIIO.u8Bus;
+            pTaskState->IOCReply.SCSIIOError.u8MessageLength = 8;
+            pTaskState->IOCReply.SCSIIOError.u8Function  = pTaskState->GuestRequest.SCSIIO.u8Function;
+            pTaskState->IOCReply.SCSIIOError.u8CDBLength = pTaskState->GuestRequest.SCSIIO.u8CDBLength;
+            pTaskState->IOCReply.SCSIIOError.u8SenseBufferLength = pTaskState->GuestRequest.SCSIIO.u8SenseBufferLength;
+            pTaskState->IOCReply.SCSIIOError.u8MessageFlags = pTaskState->GuestRequest.SCSIIO.u8MessageFlags;
+            pTaskState->IOCReply.SCSIIOError.u32MessageContext = pTaskState->GuestRequest.SCSIIO.u32MessageContext;
+            pTaskState->IOCReply.SCSIIOError.u8SCSIStatus = rcCompletion;
+            pTaskState->IOCReply.SCSIIOError.u8SCSIState  = MPT_SCSI_IO_ERROR_SCSI_STATE_AUTOSENSE_VALID;
+            pTaskState->IOCReply.SCSIIOError.u16IOCStatus  = 0;
+            pTaskState->IOCReply.SCSIIOError.u32IOCLogInfo = 0;
+            pTaskState->IOCReply.SCSIIOError.u32TransferCount = 0;
+            pTaskState->IOCReply.SCSIIOError.u32SenseCount    = sizeof(pTaskState->abSenseBuffer);
+            pTaskState->IOCReply.SCSIIOError.u32ResponseInfo  = 0;
+
+            lsilogicFinishAddressReply(pLsiLogic, &pTaskState->IOCReply, true);
+        }
+    }
+
+    RTCacheInsert(pLsiLogic->pTaskCache, pTaskState);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ *          VERR_NOT_FOUND if the requested page could be found.
+ * @param   u8PageNumber  Number of the page to get.
+ * @param   ppPageHeader  Where to store the pointer to the page header.
+ * @param   ppbPageData   Where to store the pointer to the page data.
+ */
+static int lsilogicConfigurationIOUnitPageGetFromNumber(PLSILOGICSCSI pLsiLogic, uint8_t u8PageNumber,
+                                                        PMptConfigurationPageHeader *ppPageHeader,
+                                                        uint8_t **ppbPageData, size_t *pcbPage)
+{
+    int rc = VINF_SUCCESS;
+
+    AssertMsg(VALID_PTR(ppPageHeader) && VALID_PTR(ppbPageData), ("Invalid parameters\n"));
+
+    switch(u8PageNumber)
+    {
+        case 0:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOUnitPage0.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOUnitPage0.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOUnitPage0);
+            break;
+        case 1:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOUnitPage1.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOUnitPage1.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOUnitPage1);
+            break;
+        case 2:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOUnitPage2.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOUnitPage2.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOUnitPage2);
+            break;
+        case 3:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOUnitPage3.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOUnitPage3.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOUnitPage3);
+            break;
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ *          VERR_NOT_FOUND if the requested page could be found.
+ * @param   u8PageNumber  Number of the page to get.
+ * @param   ppPageHeader  Where to store the pointer to the page header.
+ * @param   ppbPageData   Where to store the pointer to the page data.
+ */
+static int lsilogicConfigurationIOCPageGetFromNumber(PLSILOGICSCSI pLsiLogic, uint8_t u8PageNumber,
+                                                     PMptConfigurationPageHeader *ppPageHeader,
+                                                     uint8_t **ppbPageData, size_t *pcbPage)
+{
+    int rc = VINF_SUCCESS;
+
+    AssertMsg(VALID_PTR(ppPageHeader) && VALID_PTR(ppbPageData), ("Invalid parameters\n"));
+
+    switch(u8PageNumber)
+    {
+        case 0:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage0.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage0.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage0);
+            break;
+        case 1:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage1.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage1.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage1);
+            break;
+        case 2:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage2.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage2.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage2);
+            break;
+        case 3:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage3.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage3.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage3);
+            break;
+        case 4:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage4.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage4.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage4);
+            break;
+        case 6:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.IOCPage6.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.IOCPage6.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.IOCPage6);
+            break;
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ *          VERR_NOT_FOUND if the requested page could be found.
+ * @param   u8PageNumber  Number of the page to get.
+ * @param   ppPageHeader  Where to store the pointer to the page header.
+ * @param   ppbPageData   Where to store the pointer to the page data.
+ */
+static int lsilogicConfigurationManufacturingPageGetFromNumber(PLSILOGICSCSI pLsiLogic, uint8_t u8PageNumber,
+                                                               PMptConfigurationPageHeader *ppPageHeader,
+                                                               uint8_t **ppbPageData, size_t *pcbPage)
+{
+    int rc = VINF_SUCCESS;
+
+    AssertMsg(VALID_PTR(ppPageHeader) && VALID_PTR(ppbPageData), ("Invalid parameters\n"));
+
+    switch(u8PageNumber)
+    {
+        case 0:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.ManufacturingPage0.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.ManufacturingPage0.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.ManufacturingPage0);
+            break;
+        case 1:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.ManufacturingPage1.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.ManufacturingPage1.abVPDInfo;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.ManufacturingPage1);
+            break;
+        case 2:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.ManufacturingPage2.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.ManufacturingPage2.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.ManufacturingPage2);
+            break;
+        case 3:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.ManufacturingPage3.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.ManufacturingPage3.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.ManufacturingPage3);
+            break;
+        case 4:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.ManufacturingPage4.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.ManufacturingPage4.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.ManufacturingPage4);
+            break;
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ *          VERR_NOT_FOUND if the requested page could be found.
+ * @param   u8PageNumber  Number of the page to get.
+ * @param   ppPageHeader  Where to store the pointer to the page header.
+ * @param   ppbPageData   Where to store the pointer to the page data.
+ */
+static int lsilogicConfigurationSCSISPIPortPageGetFromNumber(PLSILOGICSCSI pLsiLogic, uint8_t u8Port,
+                                                             uint8_t u8PageNumber,
+                                                             PMptConfigurationPageHeader *ppPageHeader,
+                                                             uint8_t **ppbPageData, size_t *pcbPage)
+{
+    int rc = VINF_SUCCESS;
+
+    AssertMsg(VALID_PTR(ppPageHeader) && VALID_PTR(ppbPageData), ("Invalid parameters\n"));
+
+    if (u8Port >= RT_ELEMENTS(pLsiLogic->ConfigurationPages.aPortPages))
+        return VERR_NOT_FOUND;
+
+    switch(u8PageNumber)
+    {
+        case 0:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage0.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage0.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage0);
+            break;
+        case 1:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage1.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage1.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage1);
+            break;
+        case 2:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage2.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage2.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aPortPages[u8Port].SCSISPIPortPage2);
+            break;
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ *          VERR_NOT_FOUND if the requested page could be found.
+ * @param   u8PageNumber  Number of the page to get.
+ * @param   ppPageHeader  Where to store the pointer to the page header.
+ * @param   ppbPageData   Where to store the pointer to the page data.
+ */
+static int lsilogicConfigurationSCSISPIDevicePageGetFromNumber(PLSILOGICSCSI pLsiLogic, uint8_t u8Bus,
+                                                               uint8_t u8TargetID, uint8_t u8PageNumber,
+                                                               PMptConfigurationPageHeader *ppPageHeader,
+                                                               uint8_t **ppbPageData, size_t *pcbPage)
+{
+    int rc = VINF_SUCCESS;
+
+    AssertMsg(VALID_PTR(ppPageHeader) && VALID_PTR(ppbPageData), ("Invalid parameters\n"));
+
+    if (u8Bus >= RT_ELEMENTS(pLsiLogic->ConfigurationPages.aBuses))
+        return VERR_NOT_FOUND;
+
+    if (u8TargetID >= RT_ELEMENTS(pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages))
+        return VERR_NOT_FOUND;
+
+    switch(u8PageNumber)
+    {
+        case 0:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0);
+            break;
+        case 1:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1);
+            break;
+        case 2:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2);
+            break;
+        case 3:
+            *ppPageHeader = &pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3.u.fields.Header;
+            *ppbPageData  =  pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3.u.abPageData;
+            *pcbPage      = sizeof(pLsiLogic->ConfigurationPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3);
+            break;
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    return rc;
+}
+
+/**
+ * Processes a Configuration request.
+ *
+ * @returns VBox status code.
+ * @param   pLsiLogic             Pointer to the device instance which sends the request.
+ * @param   pConfigurationReq     Pointer to the request structure.
+ * @param   pReply                Pointer to the reply message frame
+ */
+static int lsilogicProcessConfigurationRequest(PLSILOGICSCSI pLsiLogic, PMptConfigurationRequest pConfigurationReq,
+                                               PMptConfigurationReply pReply)
+{
+    int rc = VINF_SUCCESS;
+    uint8_t                     *pbPageData;
+    PMptConfigurationPageHeader pPageHeader;
+    uint8_t                     u8PageType, u8PageAttribute;
+    size_t                      cbPage;
+
+    LogFlowFunc(("pLsiLogic=%#p\n", pLsiLogic));
+
+    u8PageType = MPT_CONFIGURATION_PAGE_TYPE_GET(pConfigurationReq->u8PageType);
+    u8PageAttribute = MPT_CONFIGURATION_PAGE_ATTRIBUTE_GET(pConfigurationReq->u8PageType);
+
+    /* Copy common bits from the request into the reply. */
+    pReply->u8MessageLength   = 6; /* 6 32bit D-Words. */
+    pReply->u8Action          = pConfigurationReq->u8Action;
+    pReply->u8Function        = pConfigurationReq->u8Function;
+    pReply->u32MessageContext = pConfigurationReq->u32MessageContext;
+
+    switch (u8PageType)
+    {
+        case MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT:
+        {
+            /* Get the page data. */
+            rc = lsilogicConfigurationIOUnitPageGetFromNumber(pLsiLogic,
+                                                              pConfigurationReq->u8PageNumber,
+                                                              &pPageHeader, &pbPageData, &cbPage);
+            break;
+        }
+        case MPT_CONFIGURATION_PAGE_TYPE_IOC:
+        {
+            /* Get the page data. */
+            rc = lsilogicConfigurationIOCPageGetFromNumber(pLsiLogic,
+                                                           pConfigurationReq->u8PageNumber,
+                                                           &pPageHeader, &pbPageData, &cbPage);
+            break;
+        }
+        case MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING:
+        {
+            /* Get the page data. */
+            rc = lsilogicConfigurationManufacturingPageGetFromNumber(pLsiLogic,
+                                                                     pConfigurationReq->u8PageNumber,
+                                                                     &pPageHeader, &pbPageData, &cbPage);
+            break;
+        }
+        case MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT:
+        {
+            /* Get the page data. */
+            rc = lsilogicConfigurationSCSISPIPortPageGetFromNumber(pLsiLogic,
+                                                                   pConfigurationReq->u.MPIPortNumber.u8PortNumber,
+                                                                   pConfigurationReq->u8PageNumber,
+                                                                   &pPageHeader, &pbPageData, &cbPage);
+            break;
+        }
+        case MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE:
+        {
+            /* Get the page data. */
+            rc = lsilogicConfigurationSCSISPIDevicePageGetFromNumber(pLsiLogic,
+                                                                     pConfigurationReq->u.BusAndTargetId.u8Bus,
+                                                                     pConfigurationReq->u.BusAndTargetId.u8TargetID,
+                                                                     pConfigurationReq->u8PageNumber,
+                                                                     &pPageHeader, &pbPageData, &cbPage);
+            break;
+        }
+        default:
+            rc = VERR_NOT_FOUND;
+    }
+
+    if (rc == VERR_NOT_FOUND)
+    {
+        //AssertMsgFailed(("todo\n"));
+        pReply->u8PageType    = pConfigurationReq->u8PageType;
+        pReply->u8PageNumber  = pConfigurationReq->u8PageNumber;
+        pReply->u8PageLength  = pConfigurationReq->u8PageLength;
+        pReply->u8PageVersion = pConfigurationReq->u8PageVersion;
+        return VINF_SUCCESS;
+    }
+
+    pReply->u8PageType    = pPageHeader->u8PageType;
+    pReply->u8PageNumber  = pPageHeader->u8PageNumber;
+    pReply->u8PageLength  = pPageHeader->u8PageLength;
+    pReply->u8PageVersion = pPageHeader->u8PageVersion;
+
+    Log(("GuestRequest u8Action=%d\n", pConfigurationReq->u8Action));
+    Log(("u8PageType=%d\n", pPageHeader->u8PageType));
+    Log(("u8PageNumber=%d\n", pPageHeader->u8PageNumber));
+    Log(("u8PageLength=%d\n", pPageHeader->u8PageLength));
+    Log(("u8PageVersion=%d\n", pPageHeader->u8PageVersion));
+
+    for (int i = 0; i < pReply->u8PageLength; i++)
+        LogFlowFunc(("PageData[%d]=%#x\n", i, ((uint32_t *)pbPageData)[i]));
+
+    /*
+     * Don't use the scatter gather handling code as the configuration request always have only one
+     * simple element.
+     */
+    switch (pConfigurationReq->u8Action)
+    {
+        case MPT_CONFIGURATION_REQUEST_ACTION_DEFAULT: /* Nothing to do. We are always using the defaults. */
+        case MPT_CONFIGURATION_REQUEST_ACTION_HEADER:
+        {
+            /* Already copied above nothing to do. */
+            break;
+        }
+        case MPT_CONFIGURATION_REQUEST_ACTION_READ_NVRAM:
+        case MPT_CONFIGURATION_REQUEST_ACTION_READ_CURRENT:
+        case MPT_CONFIGURATION_REQUEST_ACTION_READ_DEFAULT:
+        {
+            uint32_t cbBuffer = pConfigurationReq->SimpleSGElement.u24Length;
+            if (cbBuffer != 0)
+            {
+                RTGCPHYS GCPhysAddrPageBuffer = pConfigurationReq->SimpleSGElement.u32DataBufferAddressLow;
+                if (pConfigurationReq->SimpleSGElement.f64BitAddress)
+                    GCPhysAddrPageBuffer |= (uint64_t)pConfigurationReq->SimpleSGElement.u32DataBufferAddressHigh << 32;
+
+                PDMDevHlpPhysWrite(pLsiLogic->CTX_SUFF(pDevIns), GCPhysAddrPageBuffer, pbPageData,
+                                   cbBuffer < cbPage ? cbBuffer : cbPage);
+            }
+            break;
+        }
+        case MPT_CONFIGURATION_REQUEST_ACTION_WRITE_CURRENT:
+        case MPT_CONFIGURATION_REQUEST_ACTION_WRITE_NVRAM:
+        {
+            uint32_t cbBuffer = pConfigurationReq->SimpleSGElement.u24Length;
+            if (cbBuffer != 0)
+            {
+                RTGCPHYS GCPhysAddrPageBuffer = pConfigurationReq->SimpleSGElement.u32DataBufferAddressLow;
+                if (pConfigurationReq->SimpleSGElement.f64BitAddress)
+                    GCPhysAddrPageBuffer |= (uint64_t)pConfigurationReq->SimpleSGElement.u32DataBufferAddressHigh << 32;
+
+                PDMDevHlpPhysRead(pLsiLogic->CTX_SUFF(pDevIns), GCPhysAddrPageBuffer, pbPageData,
+                                  cbBuffer < cbPage ? cbBuffer : cbPage);
+            }
+            break;
+        }
+        default:
+            AssertMsgFailed(("todo\n"));
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the configuration pages.
+ *
+ * @returns nothing
+ * @param   pLsiLogic    Pointer to the Lsilogic SCSI instance.
+ */
+static void lsilogicInitializeConfigurationPages(PLSILOGICSCSI pLsiLogic)
+{
+    PMptConfigurationPagesSupported pPages = &pLsiLogic->ConfigurationPages;
+
+    LogFlowFunc(("pLsiLogic=%#p\n", pLsiLogic));
+
+    /* Clear everything first. */
+    memset(pPages, 0, sizeof(MptConfigurationPagesSupported));
+
+    /* Manufacturing Page 0. */
+    pPages->ManufacturingPage0.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY
+                                                              | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING;
+    pPages->ManufacturingPage0.u.fields.Header.u8PageNumber = 0;
+    pPages->ManufacturingPage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageManufacturing0) / 4;
+    strncpy((char *)pPages->ManufacturingPage0.u.fields.abChipName,          "VBox MPT Fusion", 16);
+    strncpy((char *)pPages->ManufacturingPage0.u.fields.abChipRevision,      "1.0", 8);
+    strncpy((char *)pPages->ManufacturingPage0.u.fields.abBoardName,         "VBox MPT Fusion", 16);
+    strncpy((char *)pPages->ManufacturingPage0.u.fields.abBoardAssembly,     "SUN", 8);
+    strncpy((char *)pPages->ManufacturingPage0.u.fields.abBoardTracerNumber, "CAFECAFECAFECAFE", 16);
+
+    /* Manufacturing Page 1 - I don't know what this contains so we leave it 0 for now. */
+    pPages->ManufacturingPage1.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY
+                                                     | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING;
+    pPages->ManufacturingPage1.Header.u8PageNumber = 1;
+    pPages->ManufacturingPage1.Header.u8PageLength = sizeof(MptConfigurationPageManufacturing1) / 4;
+
+    /* Manufacturing Page 2. */
+    pPages->ManufacturingPage2.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                              | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING;
+    pPages->ManufacturingPage2.u.fields.Header.u8PageNumber = 2;
+    pPages->ManufacturingPage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageManufacturing2) / 4;
+    pPages->ManufacturingPage2.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_DEVICE_ID;
+    pPages->ManufacturingPage2.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_REVISION_ID;
+    /* Hardware specific settings - everything 0 for now. */
+
+    /* Manufacturing Page 3. */
+    pPages->ManufacturingPage3.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                              | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING;
+    pPages->ManufacturingPage3.u.fields.Header.u8PageNumber = 3;
+    pPages->ManufacturingPage3.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageManufacturing3) / 4;
+    pPages->ManufacturingPage3.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_DEVICE_ID;
+    pPages->ManufacturingPage3.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_REVISION_ID;
+    /* Chip specific settings - everything 0 for now. */
+
+    /* Manufacturing Page 4 - I don't know what this contains so we leave it 0 for now. */
+    pPages->ManufacturingPage4.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY
+                                                              | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING;
+    pPages->ManufacturingPage4.u.fields.Header.u8PageNumber = 4;
+    pPages->ManufacturingPage4.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageManufacturing4) / 4;
+
+
+    /* I/O Unit page 0. */
+    pPages->IOUnitPage0.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                       | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT;
+    pPages->IOUnitPage0.u.fields.Header.u8PageNumber = 0;
+    pPages->IOUnitPage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOUnit0) / 4;
+    pPages->IOUnitPage0.u.fields.u64UniqueIdentifier = 0xcafe;
+
+    /* I/O Unit page 1. */
+    pPages->IOUnitPage1.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                       | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT;
+    pPages->IOUnitPage1.u.fields.Header.u8PageNumber = 1;
+    pPages->IOUnitPage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOUnit1) / 4;
+    pPages->IOUnitPage1.u.fields.fSingleFunction         = true;
+    pPages->IOUnitPage1.u.fields.fAllPathsMapped         = false;
+    pPages->IOUnitPage1.u.fields.fIntegratedRAIDDisabled = true;
+    pPages->IOUnitPage1.u.fields.f32BitAccessForced      = false;
+
+    /* I/O Unit page 2. */
+    pPages->IOUnitPage2.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT
+                                                       | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT;
+    pPages->IOUnitPage2.u.fields.Header.u8PageNumber = 2;
+    pPages->IOUnitPage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOUnit2) / 4;
+    pPages->IOUnitPage2.u.fields.fPauseOnError       = false;
+    pPages->IOUnitPage2.u.fields.fVerboseModeEnabled = false;
+    pPages->IOUnitPage2.u.fields.fDisableColorVideo  = false;
+    pPages->IOUnitPage2.u.fields.fNotHookInt40h      = false;
+    pPages->IOUnitPage2.u.fields.u32BIOSVersion      = 0xcafecafe;
+    pPages->IOUnitPage2.u.fields.aAdapterOrder[0].fAdapterEnabled = true;
+    pPages->IOUnitPage2.u.fields.aAdapterOrder[0].fAdapterEmbedded = true;
+    pPages->IOUnitPage2.u.fields.aAdapterOrder[0].u8PCIBusNumber = 0;
+    pPages->IOUnitPage2.u.fields.aAdapterOrder[0].u8PCIDevFn     = pLsiLogic->PciDev.devfn;
+
+    /* I/O Unit page 3. */
+    pPages->IOUnitPage3.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                       | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT;
+    pPages->IOUnitPage3.u.fields.Header.u8PageNumber = 3;
+    pPages->IOUnitPage3.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOUnit3) / 4;
+    pPages->IOUnitPage3.u.fields.u8GPIOCount = 0;
+
+    /* IOC page 0. */
+    pPages->IOCPage0.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage0.u.fields.Header.u8PageNumber = 0;
+    pPages->IOCPage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC0) / 4;
+    pPages->IOCPage0.u.fields.u32TotalNVStore      = 0;
+    pPages->IOCPage0.u.fields.u32FreeNVStore       = 0;
+    pPages->IOCPage0.u.fields.u16VendorId          = LSILOGICSCSI_PCI_VENDOR_ID;
+    pPages->IOCPage0.u.fields.u16DeviceId          = LSILOGICSCSI_PCI_DEVICE_ID;
+    pPages->IOCPage0.u.fields.u8RevisionId         = LSILOGICSCSI_PCI_REVISION_ID;
+    pPages->IOCPage0.u.fields.u32ClassCode         = LSILOGICSCSI_PCI_CLASS_CODE;
+    pPages->IOCPage0.u.fields.u16SubsystemVendorId = LSILOGICSCSI_PCI_SUBSYSTEM_VENDOR_ID;
+    pPages->IOCPage0.u.fields.u16SubsystemId       = LSILOGICSCSI_PCI_SUBSYSTEM_ID;
+
+    /* IOC page 1. */
+    pPages->IOCPage1.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage1.u.fields.Header.u8PageNumber = 1;
+    pPages->IOCPage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC1) / 4;
+    pPages->IOCPage1.u.fields.fReplyCoalescingEnabled = false;
+    pPages->IOCPage1.u.fields.u32CoalescingTimeout    = 0;
+    pPages->IOCPage1.u.fields.u8CoalescingDepth       = 0;
+
+    /* IOC page 2. */
+    pPages->IOCPage2.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage2.u.fields.Header.u8PageNumber = 2;
+    pPages->IOCPage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC2) / 4;
+    /* Everything else here is 0. */
+
+    /* IOC page 3. */
+    pPages->IOCPage3.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage3.u.fields.Header.u8PageNumber = 3;
+    pPages->IOCPage3.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC3) / 4;
+    /* Everything else here is 0. */
+
+    /* IOC page 4. */
+    pPages->IOCPage4.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage4.u.fields.Header.u8PageNumber = 4;
+    pPages->IOCPage4.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC4) / 4;
+    /* Everything else here is 0. */
+
+    /* IOC page 6. */
+    pPages->IOCPage6.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                    | MPT_CONFIGURATION_PAGE_TYPE_IOC;
+    pPages->IOCPage6.u.fields.Header.u8PageNumber = 6;
+    pPages->IOCPage6.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageIOC6) / 4;
+    /* Everything else here is 0. */
+
+    for (unsigned i = 0; i < RT_ELEMENTS(pPages->aPortPages); i++)
+    {
+        /* SCSI-SPI port page 0. */
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                                              | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageNumber = 0;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort0) / 4;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fInformationUnitTransfersCapable = true;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fDTCapable                       = true;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fQASCapable                      = true;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u8MinimumSynchronousTransferPeriod =  0;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u8MaximumSynchronousOffset         = 0xff;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fWide                              = true;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fAIPCapable                        = true;
+        pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u2SignalingType                    = 0x3; /* Single Ended. */
+
+        /* SCSI-SPI port page 1. */
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                                              | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageNumber = 1;
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort1) / 4;
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u8SCSIID                  = 7;
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u16PortResponseIDsBitmask = (1 << 7);
+        pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u32OnBusTimerValue        =  0;
+
+        /* SCSI-SPI port page 2. */
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                                              | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageNumber = 2;
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort2) / 4;
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.u4HostSCSIID           = 7;
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.u2InitializeHBA        = 0x3;
+        pPages->aPortPages[i].SCSISPIPortPage2.u.fields.fTerminationDisabled   = true;
+        for (unsigned iDevice = 0; iDevice < RT_ELEMENTS(pPages->aPortPages[i].SCSISPIPortPage2.u.fields.aDeviceSettings); iDevice++)
+        {
+            pPages->aPortPages[i].SCSISPIPortPage2.u.fields.aDeviceSettings[iDevice].fBootChoice   = true;
+        }
+        /* Everything else 0 for now. */
+    }
+
+    for (unsigned uBusCurr = 0; uBusCurr < RT_ELEMENTS(pPages->aBuses); uBusCurr++)
+    {
+        for (unsigned uDeviceCurr = 0; uDeviceCurr < RT_ELEMENTS(pPages->aBuses[uBusCurr].aDevicePages); uDeviceCurr++)
+        {
+            /* SCSI-SPI device page 0. */
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                                                                                 | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageNumber = 0;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice0) / 4;
+            /* Everything else 0 for now. */
+
+            /* SCSI-SPI device page 1. */
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                                                                                 | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageNumber = 1;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice1) / 4;
+            /* Everything else 0 for now. */
+
+            /* SCSI-SPI device page 2. */
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+                                                                                                                 | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageNumber = 2;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice2) / 4;
+            /* Everything else 0 for now. */
+
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageType   =   MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+                                                                                                                 | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageNumber = 3;
+            pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice3) / 4;
+            /* Everything else 0 for now. */
+        }
+    }
+}
+
+/**
+ * Transmit queue consumer
+ * Queue a new async task.
+ *
+ * @returns Success indicator.
+ *          If false the item will not be removed and the flushing will stop.
+ * @param   pDevIns     The device instance.
+ * @param   pItem       The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) lsilogicNotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pDevIns=%#p pItem=%#p\n", pDevIns, pItem));
+
+    /* Only process request which arrived before we received the notification. */
+    uint32_t uRequestQueueNextEntryWrite = ASMAtomicReadU32(&pLsiLogic->uRequestQueueNextEntryFreeWrite);
+
+    /* Reset notification event. */
+    ASMAtomicXchgBool(&pLsiLogic->fNotificationSend, false);
+
+    /* Go through the messages now and process them. */
+    while (   RT_LIKELY(pLsiLogic->enmState == LSILOGICSTATE_OPERATIONAL)
+           && (pLsiLogic->uRequestQueueNextAddressRead != uRequestQueueNextEntryWrite))
+    {
+        uint32_t  u32RequestMessageFrameDesc = pLsiLogic->CTX_SUFF(pRequestQueueBase)[pLsiLogic->uRequestQueueNextAddressRead];
+        RTGCPHYS  GCPhysMessageFrameAddr = LSILOGIC_RTGCPHYS_FROM_U32(pLsiLogic->u32HostMFAHighAddr,
+                                                                      (u32RequestMessageFrameDesc & ~0x07));
+
+        PLSILOGICTASKSTATE pTaskState;
+
+        /* Get new task state. */
+        rc = RTCacheRequest(pLsiLogic->pTaskCache, (void **)&pTaskState);
+        AssertRC(rc);
+
+        pTaskState->GCPhysMessageFrameAddr = GCPhysMessageFrameAddr;
+
+        /* Read the message header from the guest first. */
+        PDMDevHlpPhysRead(pDevIns, GCPhysMessageFrameAddr, &pTaskState->GuestRequest, sizeof(MptMessageHdr));
+
+        /* Determine the size of the request. */
+        uint32_t cbRequest = 0;
+
+        switch (pTaskState->GuestRequest.Header.u8Function)
+        {
+            case MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST:
+                cbRequest = sizeof(MptSCSIIORequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT:
+                cbRequest = sizeof(MptSCSITaskManagementRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_IOC_INIT:
+                cbRequest = sizeof(MptIOCInitRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS:
+                cbRequest = sizeof(MptIOCFactsRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_CONFIG:
+                cbRequest = sizeof(MptConfigurationRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS:
+                cbRequest = sizeof(MptPortFactsRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE:
+                cbRequest = sizeof(MptPortEnableRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION:
+                cbRequest = sizeof(MptEventNotificationRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK:
+                AssertMsgFailed(("todo\n"));
+                //cbRequest = sizeof(MptEventAckRequest);
+                break;
+            case MPT_MESSAGE_HDR_FUNCTION_FW_DOWNLOAD:
+                AssertMsgFailed(("todo\n"));
+                break;
+            default:
+                AssertMsgFailed(("Unknown function issued %u\n", pTaskState->GuestRequest.Header.u8Function));
+                lsilogicSetIOCFaultCode(pLsiLogic, LSILOGIC_IOCSTATUS_INVALID_FUNCTION);
+        }
+
+        if (cbRequest != 0)
+        {
+            /* Read the complete message frame from guest memory now. */
+            PDMDevHlpPhysRead(pDevIns, GCPhysMessageFrameAddr, &pTaskState->GuestRequest, cbRequest);
+
+            /* Handle SCSI I/O requests now. */
+            if (pTaskState->GuestRequest.Header.u8Function == MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST)
+            {
+               rc = lsilogicProcessSCSIIORequest(pLsiLogic, pTaskState);
+               AssertRC(rc);
+            }
+            else
+            {
+                MptReplyUnion Reply;
+                rc = lsilogicProcessMessageRequest(pLsiLogic, &pTaskState->GuestRequest.Header, &Reply);
+                AssertRC(rc);
+                RTCacheInsert(pLsiLogic->pTaskCache, pTaskState);
+            }
+
+            pLsiLogic->uRequestQueueNextAddressRead++;
+            pLsiLogic->uRequestQueueNextAddressRead %= pLsiLogic->cRequestQueueEntries;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Port I/O Handler for IN operations - legacy port.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   pu32        Where to store the result.
+ * @param   cb          Number of bytes read.
+ */
+static int  lsilogicIsaIOPortRead (PPDMDEVINS pDevIns, void *pvUser,
+                                   RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+    int rc;
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    Assert(cb == 1);
+
+    rc = vboxscsiReadRegister(&pLsiLogic->VBoxSCSI, (Port - LSILOGIC_ISA_IO_PORT), pu32);
+
+    Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+          __FUNCTION__, pu32, 1, pu32, (Port - LSILOGIC_ISA_IO_PORT), rc));
+
+    return rc;
+}
+
+/**
+ * Prepares a request from the BIOS.
+ *
+ * @returns VBox status code.
+ * @param   pLsiLogic    Pointer to the LsiLogic device instance.
+ */
+static int lsilogicPrepareBIOSSCSIRequest(PLSILOGICSCSI pLsiLogic)
+{
+    int rc;
+    PLSILOGICTASKSTATE pTaskState;
+    uint32_t           uTargetDevice;
+
+    rc = RTCacheRequest(pLsiLogic->pTaskCache, (void **)&pTaskState);
+    AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+
+    pTaskState->fBIOS = true;
+
+    rc = vboxscsiSetupRequest(&pLsiLogic->VBoxSCSI, &pTaskState->PDMScsiRequest, &uTargetDevice);
+    AssertMsgRCReturn(rc, ("Setting up SCSI request failed rc=%Rrc\n", rc), rc);
+
+    pTaskState->PDMScsiRequest.pvUser = pTaskState;
+
+    pTaskState->pTargetDevice = &pLsiLogic->aDeviceStates[uTargetDevice];
+
+    if (!pTaskState->pTargetDevice->pDrvBase)
+    {
+        /* Device is not present. */
+        AssertMsg(pTaskState->PDMScsiRequest.pbCDB[0] == SCSI_INQUIRY,
+                    ("Device is not present but command is not inquiry\n"));
+
+        SCSIINQUIRYDATA ScsiInquiryData;
+
+        memset(&ScsiInquiryData, 0, sizeof(SCSIINQUIRYDATA));
+        ScsiInquiryData.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+        ScsiInquiryData.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+
+        memcpy(pLsiLogic->VBoxSCSI.pBuf, &ScsiInquiryData, 5);
+
+        rc = vboxscsiRequestFinished(&pLsiLogic->VBoxSCSI, &pTaskState->PDMScsiRequest);
+        AssertMsgRCReturn(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc), rc);
+
+        rc = RTCacheInsert(pLsiLogic->pTaskCache, pTaskState);
+        AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+    }
+    else
+    {
+        ASMAtomicIncU32(&pTaskState->pTargetDevice->cOutstandingRequests);
+
+        rc = pTaskState->pTargetDevice->pDrvSCSIConnector->pfnSCSIRequestSend(pTaskState->pTargetDevice->pDrvSCSIConnector,
+                                                                                &pTaskState->PDMScsiRequest);
+        AssertMsgRCReturn(rc, ("Sending request to SCSI layer failed rc=%Rrc\n", rc), rc);
+    }
+
+    return rc;
+}
+
+/**
+ * Port I/O Handler for OUT operations - legacy port.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pvUser      User argument.
+ * @param   uPort       Port number used for the IN operation.
+ * @param   u32         The value to output.
+ * @param   cb          The value size in bytes.
+ */
+static int lsilogicIsaIOPortWrite (PPDMDEVINS pDevIns, void *pvUser,
+                                   RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+    int rc;
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x Port=%#x\n",
+          pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, Port));
+
+    Assert(cb == 1);
+
+    rc = vboxscsiWriteRegister(&pLsiLogic->VBoxSCSI, (Port - LSILOGIC_ISA_IO_PORT), (uint8_t)u32);
+    if (rc == VERR_MORE_DATA)
+    {
+        rc = lsilogicPrepareBIOSSCSIRequest(pLsiLogic);
+        AssertRC(rc);
+    }
+    else if (RT_FAILURE(rc))
+        AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+static DECLCALLBACK(int) lsilogicIsaIOPortWriteStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc;
+
+    Log2(("#%d %s: pvUser=%#p cb=%d Port=%#x\n",
+          pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+    rc = vboxscsiWriteString(pDevIns, &pLsiLogic->VBoxSCSI, (Port - LSILOGIC_ISA_IO_PORT),
+                             pGCPtrSrc, pcTransfer, cb);
+    if (rc == VERR_MORE_DATA)
+    {
+        rc = lsilogicPrepareBIOSSCSIRequest(pLsiLogic);
+        AssertRC(rc);
+    }
+    else if (RT_FAILURE(rc))
+        AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+    return rc;
+}
+
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+static DECLCALLBACK(int) lsilogicIsaIOPortReadStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    LogFlowFunc(("#%d %s: pvUser=%#p cb=%d Port=%#x\n",
+                 pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+    return vboxscsiReadString(pDevIns, &pLsiLogic->VBoxSCSI, (Port - LSILOGIC_ISA_IO_PORT),
+                              pGCPtrDst, pcTransfer, cb);
+}
+
+static DECLCALLBACK(int) lsilogicMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion,
+                                     RTGCPHYS GCPhysAddress, uint32_t cb,
+                                     PCIADDRESSSPACE enmType)
+{
+    PPDMDEVINS pDevIns = pPciDev->pDevIns;
+    PLSILOGICSCSI  pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int   rc = VINF_SUCCESS;
+
+    Log2(("%s: registering area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
+
+    AssertMsg(   (enmType == PCI_ADDRESS_SPACE_MEM && cb >= LSILOGIC_PCI_SPACE_MEM_SIZE)
+              || (enmType == PCI_ADDRESS_SPACE_IO  && cb >= LSILOGIC_PCI_SPACE_IO_SIZE),
+              ("PCI region type and size do not match\n"));
+
+    if ((enmType == PCI_ADDRESS_SPACE_MEM) && (iRegion == 1))
+    {
+        /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+        rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL,
+                                   lsilogicMMIOWrite, lsilogicMMIORead, NULL, "LsiLogic");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, 0,
+                                         "lsilogicMMIOWrite", "lsilogicMMIORead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpMMIORegisterGC(pDevIns, GCPhysAddress, cb, 0,
+                                         "lsilogicMMIOWrite", "lsilogicMMIORead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        pThis->GCPhysMMIOBase = GCPhysAddress;
+    }
+    else if ((enmType == PCI_ADDRESS_SPACE_MEM) && (iRegion == 2))
+    {
+        /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+        rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL,
+                                   lsilogicDiagnosticWrite, lsilogicDiagnosticRead, NULL, "LsiLogicDiag");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, 0,
+                                         "lsilogicDiagnosticWrite", "lsilogicDiagnosticRead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpMMIORegisterGC(pDevIns, GCPhysAddress, cb, 0,
+                                         "lsilogicDiagnosticWrite", "lsilogicDiagnosticRead", NULL);
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+    }
+    else if (enmType == PCI_ADDRESS_SPACE_IO)
+    {
+        rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+                                     NULL, lsilogicIOPortWrite, lsilogicIOPortRead, NULL, NULL, "LsiLogic");
+        if (RT_FAILURE(rc))
+            return rc;
+
+        if (pThis->fR0Enabled)
+        {
+            rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+                                           0, "lsilogicIOPortWrite", "lsilogicIOPortRead", NULL, NULL, "LsiLogic");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        if (pThis->fGCEnabled)
+        {
+            rc = PDMDevHlpIOPortRegisterGC(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+                                           0, "lsilogicIOPortWrite", "lsilogicIOPortRead", NULL, NULL, "LsiLogic");
+            if (RT_FAILURE(rc))
+                return rc;
+        }
+
+        pThis->IOPortBase = (RTIOPORT)GCPhysAddress;
+    }
+    else
+        AssertMsgFailed(("Invalid enmType=%d iRegion=%d\n", enmType, iRegion));
+
+    return rc;
+}
+
+/**
+ * Waits until all I/O operations on all devices are complete.
+ *
+ * @retruns Flag which indicates if all I/O completed in the given timeout.
+ * @param   pLsiLogic    Pointer to the dveice instance to check.
+ * @param   cMillis      Timeout in milliseconds to wait.
+ */
+static bool lsilogicWaitForAsyncIOFinished(PLSILOGICSCSI pLsiLogic, unsigned cMillies)
+{
+    uint64_t u64Start;
+    bool     fIdle;
+
+    /*
+     * Wait for any pending async operation to finish
+     */
+    u64Start = RTTimeMilliTS();
+    do
+    {
+        fIdle = true;
+
+        /* Check every port. */
+        for (unsigned i = 0; i < RT_ELEMENTS(pLsiLogic->aDeviceStates); i++)
+        {
+            PLSILOGICDEVICE pLsiLogicDevice = &pLsiLogic->aDeviceStates[i];
+            if (ASMAtomicReadU32(&pLsiLogicDevice->cOutstandingRequests))
+            {
+                fIdle = false;
+                break;
+            }
+        }
+        if (RTTimeMilliTS() - u64Start >= cMillies)
+            break;
+
+        /* Sleep for a bit. */
+        RTThreadSleep(100);
+    } while (!fIdle);
+
+    return fIdle;
+}
+
+static DECLCALLBACK(int) lsilogicSaveLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    /* Wait that no task is pending on any device. */
+    if (!lsilogicWaitForAsyncIOFinished(pLsiLogic, 20000))
+    {
+        AssertLogRelMsgFailed(("LsiLogic: There are still tasks outstanding\n"));
+        return VERR_TIMEOUT;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) lsilogicSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    /* Every device first. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pLsiLogic->aDeviceStates); i++)
+    {
+        PLSILOGICDEVICE pDevice = &pLsiLogic->aDeviceStates[i];
+
+        AssertMsg(!pDevice->cOutstandingRequests,
+                  ("There are still outstanding requests on this device\n"));
+        SSMR3PutU32(pSSM, pDevice->cOutstandingRequests);
+    }
+    /* Now the main device state. */
+    SSMR3PutU32   (pSSM, pLsiLogic->enmState);
+    SSMR3PutU32   (pSSM, pLsiLogic->enmWhoInit);
+    SSMR3PutBool  (pSSM, pLsiLogic->fDoorbellInProgress);
+    SSMR3PutBool  (pSSM, pLsiLogic->fDiagnosticEnabled);
+    SSMR3PutBool  (pSSM, pLsiLogic->fNotificationSend);
+    SSMR3PutBool  (pSSM, pLsiLogic->fEventNotificationEnabled);
+    SSMR3PutU32   (pSSM, pLsiLogic->uInterruptMask);
+    SSMR3PutU32   (pSSM, pLsiLogic->uInterruptStatus);
+    for (unsigned i = 0; i < RT_ELEMENTS(pLsiLogic->aMessage); i++)
+        SSMR3PutU32   (pSSM, pLsiLogic->aMessage[i]);
+    SSMR3PutU32   (pSSM, pLsiLogic->iMessage);
+    SSMR3PutU32   (pSSM, pLsiLogic->cMessage);
+    SSMR3PutMem   (pSSM, &pLsiLogic->ReplyBuffer, sizeof(pLsiLogic->ReplyBuffer));
+    SSMR3PutU32   (pSSM, pLsiLogic->uNextReplyEntryRead);
+    SSMR3PutU32   (pSSM, pLsiLogic->cReplySize);
+    SSMR3PutU16   (pSSM, pLsiLogic->u16IOCFaultCode);
+    SSMR3PutU32   (pSSM, pLsiLogic->u32HostMFAHighAddr);
+    SSMR3PutU32   (pSSM, pLsiLogic->u32SenseBufferHighAddr);
+    SSMR3PutU8    (pSSM, pLsiLogic->cMaxDevices);
+    SSMR3PutU8    (pSSM, pLsiLogic->cMaxBuses);
+    SSMR3PutU16   (pSSM, pLsiLogic->cbReplyFrame);
+    SSMR3PutU32   (pSSM, pLsiLogic->iDiagnosticAccess);
+    SSMR3PutU32   (pSSM, pLsiLogic->cReplyQueueEntries);
+    SSMR3PutU32   (pSSM, pLsiLogic->cRequestQueueEntries);
+    SSMR3PutU32   (pSSM, pLsiLogic->uReplyFreeQueueNextEntryFreeWrite);
+    SSMR3PutU32   (pSSM, pLsiLogic->uReplyFreeQueueNextAddressRead);
+    SSMR3PutU32   (pSSM, pLsiLogic->uReplyPostQueueNextEntryFreeWrite);
+    SSMR3PutU32   (pSSM, pLsiLogic->uReplyPostQueueNextAddressRead);
+    SSMR3PutU32   (pSSM, pLsiLogic->uRequestQueueNextEntryFreeWrite);
+    SSMR3PutU32   (pSSM, pLsiLogic->uRequestQueueNextAddressRead);
+    SSMR3PutMem   (pSSM, &pLsiLogic->ConfigurationPages, sizeof(pLsiLogic->ConfigurationPages));
+    /* Now the data for the BIOS interface. */
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.regIdentify);
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.uTargetDevice);
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.uTxDir);
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.cbCDB);
+    SSMR3PutMem   (pSSM, pLsiLogic->VBoxSCSI.aCDB, sizeof(pLsiLogic->VBoxSCSI.aCDB));
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.iCDB);
+    SSMR3PutU32   (pSSM, pLsiLogic->VBoxSCSI.cbBuf);
+    SSMR3PutU32   (pSSM, pLsiLogic->VBoxSCSI.iBuf);
+    SSMR3PutBool  (pSSM, pLsiLogic->VBoxSCSI.fBusy);
+    SSMR3PutU8    (pSSM, pLsiLogic->VBoxSCSI.enmState);
+    if (pLsiLogic->VBoxSCSI.cbCDB)
+        SSMR3PutMem(pSSM, pLsiLogic->VBoxSCSI.pBuf, pLsiLogic->VBoxSCSI.cbBuf);
+
+    return SSMR3PutU32(pSSM, ~0);
+}
+
+static DECLCALLBACK(int) lsilogicLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t u32Version)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc = VINF_SUCCESS;
+
+    /* We support saved states only from this and older versions. */
+    if (u32Version > LSILOGIC_SAVED_STATE_MINOR_VERSION)
+        return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+    /* Every device first. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pLsiLogic->aDeviceStates); i++)
+    {
+        PLSILOGICDEVICE pDevice = &pLsiLogic->aDeviceStates[i];
+
+        AssertMsg(!pDevice->cOutstandingRequests,
+                  ("There are still outstanding requests on this device\n"));
+        SSMR3GetU32(pSSM, (uint32_t *)&pDevice->cOutstandingRequests);
+    }
+    /* Now the main device state. */
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->enmState);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->enmWhoInit);
+    SSMR3GetBool  (pSSM, &pLsiLogic->fDoorbellInProgress);
+    SSMR3GetBool  (pSSM, &pLsiLogic->fDiagnosticEnabled);
+    SSMR3GetBool  (pSSM, &pLsiLogic->fNotificationSend);
+    SSMR3GetBool  (pSSM, &pLsiLogic->fEventNotificationEnabled);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uInterruptMask);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uInterruptStatus);
+    for (unsigned i = 0; i < RT_ELEMENTS(pLsiLogic->aMessage); i++)
+        SSMR3GetU32   (pSSM, &pLsiLogic->aMessage[i]);
+    SSMR3GetU32   (pSSM, &pLsiLogic->iMessage);
+    SSMR3GetU32   (pSSM, &pLsiLogic->cMessage);
+    SSMR3GetMem   (pSSM, &pLsiLogic->ReplyBuffer, sizeof(pLsiLogic->ReplyBuffer));
+    SSMR3GetU32   (pSSM, &pLsiLogic->uNextReplyEntryRead);
+    SSMR3GetU32   (pSSM, &pLsiLogic->cReplySize);
+    SSMR3GetU16   (pSSM, &pLsiLogic->u16IOCFaultCode);
+    SSMR3GetU32   (pSSM, &pLsiLogic->u32HostMFAHighAddr);
+    SSMR3GetU32   (pSSM, &pLsiLogic->u32SenseBufferHighAddr);
+    SSMR3GetU8    (pSSM, &pLsiLogic->cMaxDevices);
+    SSMR3GetU8    (pSSM, &pLsiLogic->cMaxBuses);
+    SSMR3GetU16   (pSSM, &pLsiLogic->cbReplyFrame);
+    SSMR3GetU32   (pSSM, &pLsiLogic->iDiagnosticAccess);
+    SSMR3GetU32   (pSSM, &pLsiLogic->cReplyQueueEntries);
+    SSMR3GetU32   (pSSM, &pLsiLogic->cRequestQueueEntries);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uReplyFreeQueueNextEntryFreeWrite);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uReplyFreeQueueNextAddressRead);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uReplyPostQueueNextEntryFreeWrite);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uReplyPostQueueNextAddressRead);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uRequestQueueNextEntryFreeWrite);
+    SSMR3GetU32   (pSSM, (uint32_t *)&pLsiLogic->uRequestQueueNextAddressRead);
+    SSMR3GetMem   (pSSM, &pLsiLogic->ConfigurationPages, sizeof(pLsiLogic->ConfigurationPages));
+    /* Now the data for the BIOS interface. */
+    SSMR3GetU8  (pSSM, &pLsiLogic->VBoxSCSI.regIdentify);
+    SSMR3GetU8  (pSSM, &pLsiLogic->VBoxSCSI.uTargetDevice);
+    SSMR3GetU8  (pSSM, &pLsiLogic->VBoxSCSI.uTxDir);
+    SSMR3GetU8  (pSSM, &pLsiLogic->VBoxSCSI.cbCDB);
+    SSMR3GetMem (pSSM, pLsiLogic->VBoxSCSI.aCDB, sizeof(pLsiLogic->VBoxSCSI.aCDB));
+    SSMR3GetU8  (pSSM, &pLsiLogic->VBoxSCSI.iCDB);
+    SSMR3GetU32 (pSSM, &pLsiLogic->VBoxSCSI.cbBuf);
+    SSMR3GetU32 (pSSM, &pLsiLogic->VBoxSCSI.iBuf);
+    SSMR3GetBool(pSSM, (bool *)&pLsiLogic->VBoxSCSI.fBusy);
+    SSMR3GetU8  (pSSM, (uint8_t *)&pLsiLogic->VBoxSCSI.enmState);
+    if (pLsiLogic->VBoxSCSI.cbCDB)
+    {
+        pLsiLogic->VBoxSCSI.pBuf = (uint8_t *)RTMemAllocZ(pLsiLogic->VBoxSCSI.cbCDB);
+        if (!pLsiLogic->VBoxSCSI.pBuf)
+        {
+            LogRel(("LsiLogic: Out of memory during restore.\n"));
+            return PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY,
+                                    N_("LsiLogic: Out of memory during restore\n"));
+        }
+        SSMR3GetMem(pSSM, pLsiLogic->VBoxSCSI.pBuf, pLsiLogic->VBoxSCSI.cbBuf);
+    }
+
+    uint32_t u32;
+    SSMR3GetU32(pSSM, &u32);
+    if (RT_FAILURE(rc))
+        return rc;
+    if (u32 != ~0U)
+    {
+        AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+        return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Gets the pointer to the status LED of a device - called from the SCSi driver.
+ *
+ * @returns VBox status code.
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ * @param   iLUN            The unit which status LED we desire. Always 0 here as the driver
+ *                          doesn't know about other LUN's.
+ * @param   ppLed           Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) lsilogicDeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+    PLSILOGICDEVICE pDevice = PDMILEDPORTS_2_PLSILOGICDEVICE(pInterface);
+    if (iLUN == 0)
+    {
+        *ppLed = &pDevice->Led;
+        Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+        return VINF_SUCCESS;
+    }
+    return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to LSILOGICDEVICE::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *) lsilogicDeviceQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PLSILOGICDEVICE pDevice = PDMIBASE_2_PLSILOGICDEVICE(pInterface);
+
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_SCSI_PORT:
+            return &pDevice->ISCSIPort;
+        case PDMINTERFACE_LED_PORTS:
+            return &pDevice->ILed;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param   pInterface      Pointer to the interface structure containing the called function pointer.
+ * @param   iLUN            The unit which status LED we desire.
+ * @param   ppLed           Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) lsilogicStatusQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+    PLSILOGICSCSI pLsiLogic = PDMILEDPORTS_2_PLSILOGICSCSI(pInterface);
+    if (iLUN < LSILOGIC_DEVICES_MAX)
+    {
+        *ppLed = &pLsiLogic->aDeviceStates[iLUN].Led;
+        Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+        return VINF_SUCCESS;
+    }
+    return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param   pInterface          Pointer to ATADevState::IBase.
+ * @param   enmInterface        The requested interface identification.
+ */
+static DECLCALLBACK(void *) lsilogicStatusQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PLSILOGICSCSI pLsiLogic = PDMIBASE_2_PLSILOGICSCSI(pInterface);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pLsiLogic->IBase;
+        case PDMINTERFACE_LED_PORTS:
+            return &pLsiLogic->ILeds;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Detach notification.
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) lsilogicDetach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PLSILOGICSCSI   pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    PLSILOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+
+    AssertMsg(fFlags & PDMDEVATT_FLAGS_NOT_HOT_PLUG,
+              ("LsiLogic: Device does not support hotplugging\n"));
+
+    Log(("%s:\n", __FUNCTION__));
+
+    /*
+     * Zero some important members.
+     */
+    pDevice->pDrvBase = NULL;
+    pDevice->pDrvSCSIConnector = NULL;
+}
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver.
+ *
+ * @returns VBox status code.
+ * @param   pDevIns     The device instance.
+ * @param   iLUN        The logical unit which is being detached.
+ * @param   fFlags      Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int)  lsilogicAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+    PLSILOGICSCSI   pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    PLSILOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+    int rc;
+
+    AssertMsgReturn(fFlags & PDMDEVATT_FLAGS_NOT_HOT_PLUG,
+                    ("LsiLogic: Device does not support hotplugging\n"),
+                    VERR_INVALID_PARAMETER);
+
+    /* the usual paranoia */
+    AssertRelease(!pDevice->pDrvBase);
+    AssertRelease(!pDevice->pDrvSCSIConnector);
+    Assert(pDevice->iLUN == iLUN);
+
+    /*
+     * Try attach the block device and get the interfaces,
+     * required as well as optional.
+     */
+    rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, NULL);
+    if (RT_SUCCESS(rc))
+    {
+        /* Get SCSI connector interface. */
+        pDevice->pDrvSCSIConnector = (PPDMISCSICONNECTOR)pDevice->pDrvBase->pfnQueryInterface(pDevice->pDrvBase, PDMINTERFACE_SCSI_CONNECTOR);
+        AssertMsgReturn(pDevice->pDrvSCSIConnector, ("Missing SCSI interface below\n"), VERR_PDM_MISSING_INTERFACE);
+    }
+    else
+        AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pDevice->iLUN, rc));
+
+    if (RT_FAILURE(rc))
+    {
+        pDevice->pDrvBase = NULL;
+        pDevice->pDrvSCSIConnector = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @copydoc FNPDMDEVPOWEROFF
+ */
+static DECLCALLBACK(void) lsilogicPowerOff(PPDMDEVINS pDevIns)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    bool fIdle = lsilogicWaitForAsyncIOFinished(pLsiLogic, 20000);
+    Assert(fIdle);
+}
+
+/**
+ * @copydoc FNPDMDEVSUSPEND
+ */
+static DECLCALLBACK(void) lsilogicSuspend(PPDMDEVINS pDevIns)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    bool fIdle = lsilogicWaitForAsyncIOFinished(pLsiLogic, 20000);
+    Assert(fIdle);
+}
+
+/**
+ * @copydoc FNPDMDEVRESET
+ */
+static DECLCALLBACK(void) lsilogicReset(PPDMDEVINS pDevIns)
+{
+    PLSILOGICSCSI pLsiLogic = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc;
+
+    bool fIdle = lsilogicWaitForAsyncIOFinished(pLsiLogic, 20000);
+    Assert(fIdle);
+
+    rc = lsilogicHardReset(pLsiLogic);
+    AssertRC(rc);
+
+    vboxscsiInitialize(&pLsiLogic->VBoxSCSI);
+}
+
+/**
+ * @copydoc FNPDMDEVRELOCATE
+ */
+static DECLCALLBACK(void) lsilogicRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+    PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+    pThis->pDevInsRC        = PDMDEVINS_2_RCPTR(pDevIns);
+    pThis->pNotificationQueueRC = PDMQueueRCPtr(pThis->pNotificationQueueR3);
+
+    /* Relocate queues. */
+    pThis->pReplyFreeQueueBaseRC += offDelta;
+    pThis->pReplyPostQueueBaseRC += offDelta;
+    pThis->pRequestQueueBaseRC   += offDelta;
+}
+
+/**
+ * @copydoc FNPDMDEVDESTRUCT
+ */
+static DECLCALLBACK(int) lsilogicDestruct(PPDMDEVINS pDevIns)
+{
+    PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc = VINF_SUCCESS;
+
+    PDMR3CritSectDelete(&pThis->ReplyFreeQueueCritSect);
+    PDMR3CritSectDelete(&pThis->ReplyPostQueueCritSect);
+
+    /* Destroy task cache. */
+    if (pThis->pTaskCache)
+        rc = RTCacheDestroy(pThis->pTaskCache);
+
+    return rc;
+}
+
+/**
+ * @copydoc FNPDMDEVCONSTRUCT
+ */
+static DECLCALLBACK(int) lsilogicConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
+{
+    PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+    int rc = VINF_SUCCESS;
+    PVM pVM = PDMDevHlpGetVM(pDevIns);
+
+    /*
+     * Validate and read configuration.
+     */
+    rc = CFGMR3AreValuesValid(pCfgHandle, "GCEnabled\0"
+                                          "R0Enabled\0"
+                                          "ReplyQueueDepth\0"
+                                          "RequestQueueDepth\0");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+                                N_("LsiLogic configuration error: unknown option specified"));
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "GCEnabled", &pThis->fGCEnabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic configuration error: failed to read GCEnabled as boolean"));
+    Log(("%s: fGCEnabled=%d\n", __FUNCTION__, pThis->fGCEnabled));
+
+    rc = CFGMR3QueryBoolDef(pCfgHandle, "R0Enabled", &pThis->fR0Enabled, true);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic configuration error: failed to read R0Enabled as boolean"));
+    Log(("%s: fR0Enabled=%d\n", __FUNCTION__, pThis->fR0Enabled));
+
+    rc = CFGMR3QueryU32Def(pCfgHandle, "ReplyQueueDepth",
+                           &pThis->cReplyQueueEntries,
+                           LSILOGICSCSI_REPLY_QUEUE_DEPTH_DEFAULT);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic configuration error: failed to read ReplyQueue as integer"));
+    Log(("%s: ReplyQueueDepth=%u\n", __FUNCTION__, pThis->cReplyQueueEntries));
+
+    rc = CFGMR3QueryU32Def(pCfgHandle, "RequestQueueDepth",
+                           &pThis->cRequestQueueEntries,
+                           LSILOGICSCSI_REQUEST_QUEUE_DEPTH_DEFAULT);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic configuration error: failed to read RequestQueue as integer"));
+    Log(("%s: RequestQueueDepth=%u\n", __FUNCTION__, pThis->cRequestQueueEntries));
+
+
+    /* Init static parts. */
+    PCIDevSetVendorId         (&pThis->PciDev, LSILOGICSCSI_PCI_VENDOR_ID); /* LsiLogic */
+    PCIDevSetDeviceId         (&pThis->PciDev, LSILOGICSCSI_PCI_DEVICE_ID); /* LSI53C1030 */
+    PCIDevSetClassProg        (&pThis->PciDev,   0x00); /* SCSI */
+    PCIDevSetClassSub         (&pThis->PciDev,   0x00); /* SCSI */
+    PCIDevSetClassBase        (&pThis->PciDev,   0x01); /* Mass storage */
+    PCIDevSetSubSystemVendorId(&pThis->PciDev, LSILOGICSCSI_PCI_SUBSYSTEM_VENDOR_ID);
+    PCIDevSetSubSystemId      (&pThis->PciDev, LSILOGICSCSI_PCI_SUBSYSTEM_ID);
+    PCIDevSetInterruptPin     (&pThis->PciDev,   0x01); /* Interrupt pin A */
+
+    pThis->pDevInsR3 = pDevIns;
+    pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+    pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+    pThis->IBase.pfnQueryInterface = lsilogicStatusQueryInterface;
+    pThis->ILeds.pfnQueryStatusLed = lsilogicStatusQueryStatusLed;
+
+    /*
+     * Register the PCI device, it's I/O regions.
+     */
+    rc = PDMDevHlpPCIRegister (pDevIns, &pThis->PciDev);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, LSILOGIC_PCI_SPACE_IO_SIZE, PCI_ADDRESS_SPACE_IO, lsilogicMap);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM, lsilogicMap);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    rc = PDMDevHlpPCIIORegionRegister(pDevIns, 2, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM, lsilogicMap);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    /* Intialize task queue. */
+    rc = PDMDevHlpPDMQueueCreate(pDevIns, sizeof(PDMQUEUEITEMCORE), 2, 0,
+                                 lsilogicNotifyQueueConsumer, true, &pThis->pNotificationQueueR3);
+    if (RT_FAILURE(rc))
+        return rc;
+    pThis->pNotificationQueueR0 = PDMQueueR0Ptr(pThis->pNotificationQueueR3);
+    pThis->pNotificationQueueRC = PDMQueueRCPtr(pThis->pNotificationQueueR3);
+
+    /*
+     * We need one entry free in the queue.
+     */
+    pThis->cReplyQueueEntries++;
+    pThis->cRequestQueueEntries++;
+
+    /*
+     * Allocate memory for the queues.
+     */
+    uint32_t cbQueues;
+
+    cbQueues  = 2*pThis->cReplyQueueEntries * sizeof(uint32_t);
+    cbQueues += pThis->cRequestQueueEntries * sizeof(uint32_t);
+    rc = MMHyperAlloc(pVM, cbQueues, 1, MM_TAG_PDM_DEVICE_USER,
+                      (void **)&pThis->pReplyFreeQueueBaseR3);
+    if (RT_FAILURE(rc))
+        return VERR_NO_MEMORY;
+    pThis->pReplyFreeQueueBaseR0 = MMHyperR3ToR0(pVM, (void *)pThis->pReplyFreeQueueBaseR3);
+    pThis->pReplyFreeQueueBaseRC = MMHyperR3ToRC(pVM, (void *)pThis->pReplyFreeQueueBaseR3);
+
+    pThis->pReplyPostQueueBaseR3 = pThis->pReplyFreeQueueBaseR3 + pThis->cReplyQueueEntries;
+    pThis->pReplyPostQueueBaseR0 = MMHyperR3ToR0(pVM, (void *)pThis->pReplyPostQueueBaseR3);
+    pThis->pReplyPostQueueBaseRC = MMHyperR3ToRC(pVM, (void *)pThis->pReplyPostQueueBaseR3);
+
+    pThis->pRequestQueueBaseR3   = pThis->pReplyPostQueueBaseR3 + pThis->cReplyQueueEntries;
+    pThis->pRequestQueueBaseR0   = MMHyperR3ToR0(pVM, (void *)pThis->pRequestQueueBaseR3);
+    pThis->pRequestQueueBaseRC   = MMHyperR3ToRC(pVM, (void *)pThis->pRequestQueueBaseR3);
+
+    /*
+     * Create critical sections protecting the reply post and free queues.
+     */
+    rc = PDMDevHlpCritSectInit(pDevIns, &pThis->ReplyFreeQueueCritSect, "LsiLogicRFQ");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic: cannot create critical section for reply free queue"));
+
+    rc = PDMDevHlpCritSectInit(pDevIns, &pThis->ReplyPostQueueCritSect, "LsiLogicRPQ");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("LsiLogic: cannot create critical section for reply post queue"));
+
+    /*
+     * Allocate task cache.
+     */
+    rc = RTCacheCreate(&pThis->pTaskCache, 0, sizeof(LSILOGICTASKSTATE), RTOBJCACHE_PROTECT_INSERT);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc,
+                                N_("Cannot create task cache"));
+
+    /* Initialize per device state. */
+    for (unsigned i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+    {
+        char szName[24];
+        PLSILOGICDEVICE pDevice = &pThis->aDeviceStates[i];
+
+        RTStrPrintf(szName, sizeof(szName), "Device%d", i);
+
+        /* Initialize static parts of the device. */
+        pDevice->iLUN = i;
+        pDevice->pLsiLogicR3  = pThis;
+        pDevice->pLsiLogicR0  = PDMINS_2_DATA_R0PTR(pDevIns);
+        pDevice->pLsiLogicRC  = PDMINS_2_DATA_RCPTR(pDevIns);
+        pDevice->Led.u32Magic = PDMLED_MAGIC;
+        pDevice->IBase.pfnQueryInterface           = lsilogicDeviceQueryInterface;
+        pDevice->ISCSIPort.pfnSCSIRequestCompleted = lsilogicDeviceSCSIRequestCompleted;
+        pDevice->ILed.pfnQueryStatusLed            = lsilogicDeviceQueryStatusLed;
+
+        /* Attach SCSI driver. */
+        rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, szName);
+        if (RT_SUCCESS(rc))
+        {
+            /* Get SCSI connector interface. */
+            pDevice->pDrvSCSIConnector = (PPDMISCSICONNECTOR)pDevice->pDrvBase->pfnQueryInterface(pDevice->pDrvBase, PDMINTERFACE_SCSI_CONNECTOR);
+            AssertMsgReturn(pDevice->pDrvSCSIConnector, ("Missing SCSI interface below\n"), VERR_PDM_MISSING_INTERFACE);
+        }
+        else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+        {
+            pDevice->pDrvBase = NULL;
+            rc = VINF_SUCCESS;
+            Log(("LsiLogic: no driver attached to device %s\n", szName));
+        }
+        else
+        {
+            AssertLogRelMsgFailed(("LsiLogic: Failed to attach %s\n", szName));
+            return rc;
+        }
+    }
+
+    /*
+     * Attach status driver (optional).
+     */
+    PPDMIBASE pBase;
+    rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+    if (RT_SUCCESS(rc))
+        pThis->pLedsConnector = (PDMILEDCONNECTORS *)pBase->pfnQueryInterface(pBase, PDMINTERFACE_LED_CONNECTORS);
+    else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+    {
+        AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot attach to status driver"));
+    }
+
+    /* Initialize the SCSI emulation for the BIOS. */
+    rc = vboxscsiInitialize(&pThis->VBoxSCSI);
+    AssertRC(rc);
+
+    /* Register I/O port space in ISA region for BIOS access. */
+    rc = PDMDevHlpIOPortRegister(pDevIns, LSILOGIC_ISA_IO_PORT, 3, NULL,
+                                 lsilogicIsaIOPortWrite, lsilogicIsaIOPortRead,
+                                 lsilogicIsaIOPortWriteStr, lsilogicIsaIOPortReadStr,
+                                 "LsiLogic BIOS");
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot register legacy I/O handlers"));
+
+    /* Register save state handlers. */
+    rc = PDMDevHlpSSMRegister(pDevIns, pDevIns->pDevReg->szDeviceName, iInstance,
+                              LSILOGIC_SAVED_STATE_MINOR_VERSION, sizeof(*pThis),
+                              lsilogicSaveLoadPrep, lsilogicSaveExec, NULL,
+                              lsilogicSaveLoadPrep, lsilogicLoadExec, NULL);
+    if (RT_FAILURE(rc))
+        return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot register save state handlers"));
+
+    pThis->enmWhoInit = LSILOGICWHOINIT_SYSTEM_BIOS;
+
+    /* Perform hard reset. */
+    rc = lsilogicHardReset(pThis);
+    AssertRC(rc);
+
+    return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceLsiLogicSCSI =
+{
+    /* u32Version */
+    PDM_DEVREG_VERSION,
+    /* szDeviceName */
+    "lsilogicscsi",
+    /* szRCMod */
+    "VBoxDDGC.gc",
+    /* szR0Mod */
+    "VBoxDDR0.r0",
+    /* pszDescription */
+    "LSI Logic 53c1030 SCSI controller.\n",
+    /* fFlags */
+      PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0
+    | PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION
+    | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+    /* fClass */
+    PDM_DEVREG_CLASS_STORAGE,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(LSILOGICSCSI),
+    /* pfnConstruct */
+    lsilogicConstruct,
+    /* pfnDestruct */
+    lsilogicDestruct,
+    /* pfnRelocate */
+    lsilogicRelocate,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    lsilogicReset,
+    /* pfnSuspend */
+    lsilogicSuspend,
+    /* pfnResume */
+    NULL,
+    /* pfnAttach */
+    lsilogicAttach,
+    /* pfnDetach */
+    lsilogicDetach,
+    /* pfnQueryInterface. */
+    NULL,
+    /* pfnInitComplete */
+    NULL,
+    /* pfnPowerOff */
+    lsilogicPowerOff,
+    /* pfnSoftReset */
+    NULL,
+    /* u32VersionEnd */
+    PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
Index: trunk/src/VBox/Devices/Storage/DrvSCSI.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DrvSCSI.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/DrvSCSI.cpp	(revision 21321)
@@ -0,0 +1,883 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage drivers:
+ * Generic SCSI command parser and execution driver
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DRV_SCSI
+#include <VBox/pdmdrv.h>
+#include <VBox/pdmifs.h>
+#include <VBox/pdmthread.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/req.h>
+#include <iprt/semaphore.h>
+
+#include "Builtins.h"
+
+/**
+ * SCSI driver instance data.
+ */
+typedef struct DRVSCSI
+{
+    /** Pointer driver instance. */
+    PPDMDRVINS              pDrvIns;
+
+    /** Pointer to the attached driver's base interface. */
+    PPDMIBASE               pDrvBase;
+    /** Pointer to the attached driver's block interface. */
+    PPDMIBLOCK              pDrvBlock;
+    /** Pointer to the attached driver's async block interface. */
+    PPDMIBLOCKASYNC         pDrvBlockAsync;
+    /** Pointer to the attached driver's block bios interface. */
+    PPDMIBLOCKBIOS          pDrvBlockBios;
+    /** Pointer to the attached driver's mount interface. */
+    PPDMIMOUNT              pDrvMount;
+    /** Pointer to the SCSI port interface of the device above. */
+    PPDMISCSIPORT           pDevScsiPort;
+    /** pointer to the Led port interface of the dveice above. */
+    PPDMILEDPORTS           pLedPort;
+    /** The scsi connector interface .*/
+    PDMISCSICONNECTOR       ISCSIConnector;
+    /** The block port interface. */
+    PDMIBLOCKPORT           IPort;
+    /** The optional block async port interface. */
+    PDMIBLOCKASYNCPORT      IPortAsync;
+    /** The mount notify interface. */
+    PDMIMOUNTNOTIFY         IMountNotify;
+    /** The status LED state for this drive.
+     *  used in case the device doesn't has a Led interface
+     *  so we can use this to avoid if checks later on. */
+    PDMLED                  Led;
+    /** pointer to the Led to use. */
+    PPDMLED                 pLed;
+
+    /** Device type. */
+    PDMBLOCKTYPE            enmType;
+    /** BIOS PCHS Geometry. */
+    PDMMEDIAGEOMETRY        PCHSGeometry;
+    /** BIOS LCHS Geometry. */
+    PDMMEDIAGEOMETRY        LCHSGeometry;
+    /** Number of sectors this device has. */
+    uint64_t                cSectors;
+
+    /** The dedicated I/O thread for the non async approach. */
+    PPDMTHREAD              pAsyncIOThread;
+    /** Queue for passing the requests to the thread. */
+    PRTREQQUEUE             pQueueRequests;
+    /** Release statistics: number of bytes written. */
+    STAMCOUNTER             StatBytesWritten;
+    /** Release statistics: number of bytes read. */
+    STAMCOUNTER             StatBytesRead;
+} DRVSCSI, *PDRVSCSI;
+
+/** Converts a pointer to DRVSCSI::ISCSIConnecotr to a PDRVSCSI. */
+#define PDMISCSICONNECTOR_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, ISCSIConnector)) )
+
+#ifdef DEBUG
+/**
+ * Dumps a SCSI request structure for debugging purposes.
+ *
+ * @returns nothing.
+ * @param   pRequest    Pointer to the request to dump.
+ */
+static void drvscsiDumpScsiRequest(PPDMSCSIREQUEST pRequest)
+{
+    Log(("Dump for pRequest=%#p Command: %s\n", pRequest, SCSICmdText(pRequest->pbCDB[0])));
+    Log(("cbCDB=%u\n", pRequest->cbCDB));
+    for (uint32_t i = 0; i < pRequest->cbCDB; i++)
+        Log(("pbCDB[%u]=%#x\n", i, pRequest->pbCDB[i]));
+    Log(("cbScatterGather=%u\n", pRequest->cbScatterGather));
+    Log(("cScatterGatherEntries=%u\n", pRequest->cScatterGatherEntries));
+    /* Print all scatter gather entries. */
+    for (uint32_t i = 0; i < pRequest->cScatterGatherEntries; i++)
+    {
+        Log(("ScatterGatherEntry[%u].cbSeg=%u\n", i, pRequest->paScatterGatherHead[i].cbSeg));
+        Log(("ScatterGatherEntry[%u].pvSeg=%#p\n", i, pRequest->paScatterGatherHead[i].pvSeg));
+    }
+    Log(("pvUser=%#p\n", pRequest->pvUser));
+}
+#endif
+
+/**
+ * Copy the content of a buffer to a scatter gather list only
+ * copying only the amount of data which fits into the
+ * scatter gather list.
+ *
+ * @returns VBox status code.
+ * @param   pRequest    Pointer to the request which contains the S/G list entries.
+ * @param   pvBuf       Pointer to the buffer which should be copied.
+ * @param   cbBuf       Size of the buffer.
+ */
+static int drvscsiScatterGatherListCopyFromBuffer(PPDMSCSIREQUEST pRequest, void *pvBuf, size_t cbBuf)
+{
+    unsigned cSGEntry = 0;
+    PPDMDATASEG pSGEntry = &pRequest->paScatterGatherHead[cSGEntry];
+    uint8_t *pu8Buf = (uint8_t *)pvBuf;
+
+    LogFlowFunc(("pRequest=%#p pvBuf=%#p cbBuf=%u\n", pRequest, pvBuf, cbBuf));
+
+#ifdef DEBUG
+    for (unsigned i = 0; i < cbBuf; i++)
+        Log(("%s: pvBuf[%u]=%#x\n", __FUNCTION__, i, pu8Buf[i]));
+#endif
+
+    while (cSGEntry < pRequest->cScatterGatherEntries)
+    {
+        size_t cbToCopy = (cbBuf < pSGEntry->cbSeg) ? cbBuf : pSGEntry->cbSeg;
+
+        memcpy(pSGEntry->pvSeg, pu8Buf, cbToCopy);
+
+        cbBuf -= cbToCopy;
+        /* We finished. */
+        if (!cbBuf)
+            break;
+
+        /* Advance the buffer. */
+        pu8Buf += cbToCopy;
+
+        /* Go to the next entry in the list. */
+        pSGEntry++;
+        cSGEntry++;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static void drvscsiPadStr(int8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+    for (uint32_t i = 0; i < cbSize; i++)
+    {
+        if (*pbSrc)
+            pbDst[i] = *pbSrc++;
+        else
+            pbDst[i] = ' ';
+    }
+}
+
+/**
+ * Set the sense and advanced sense key in the buffer for error conditions.
+ *
+ * @returns SCSI status code.
+ * @param   pRequest      Pointer to the request which contains the sense buffer.
+ * @param   uSCSISenseKey The sense key to set.
+ * @param   uSCSIASC      The advanced sense key to set.
+ */
+DECLINLINE(int) drvscsiCmdError(PPDMSCSIREQUEST pRequest, uint8_t uSCSISenseKey, uint8_t uSCSIASC)
+{
+    AssertMsgReturn(pRequest->cbSenseBuffer >= 18, ("Sense buffer is not big enough\n"), SCSI_STATUS_OK);
+    AssertMsgReturn(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"), SCSI_STATUS_OK);
+    memset(pRequest->pbSenseBuffer, 0, pRequest->cbSenseBuffer);
+    pRequest->pbSenseBuffer[0] = (1 << 7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+    pRequest->pbSenseBuffer[2] = uSCSISenseKey;
+    pRequest->pbSenseBuffer[7]  = 10;
+    pRequest->pbSenseBuffer[12] = uSCSIASC;
+    pRequest->pbSenseBuffer[13] = 0x00; /** @todo: Provide more info. */
+    return SCSI_STATUS_CHECK_CONDITION;
+}
+
+/**
+ * Sets the sense key for a status good condition.
+ *
+ * @returns SCSI status code.
+ * @param   pRequest    Pointer to the request which contains the sense buffer.
+ */
+DECLINLINE(int) drvscsiCmdOk(PPDMSCSIREQUEST pRequest)
+{
+    AssertMsgReturn(pRequest->cbSenseBuffer >= 18, ("Sense buffer is not big enough\n"), SCSI_STATUS_OK);
+    AssertMsgReturn(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"), SCSI_STATUS_OK);
+    memset(pRequest->pbSenseBuffer, 0, pRequest->cbSenseBuffer);
+    /*
+     * Setting this breaks Linux guests on the BusLogic controller.
+     * According to the SCSI SPC spec sense data is returned after a
+     * CHECK CONDITION status or a REQUEST SENSE command.
+     * Both SCSI controllers have a feature called Auto Sense which
+     * fetches the sense data automatically from the device
+     * with REQUEST SENSE. So the SCSI subsystem in Linux should
+     * find this sense data even if the command finishes successfully
+     * but if it finds valid sense data it will let the command fail
+     * and it doesn't detect attached disks anymore.
+     * Disabling makes it work again and no other guest shows errors
+     * so I will leave it disabled for now.
+     *
+     * On the other hand it is possible that the devices fetch the sense data
+     * only after a command failed so the content is really invalid if
+     * the command succeeds.
+     */
+#if 0
+    pRequest->pbSenseBuffer[0]  = (1 << 7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+    pRequest->pbSenseBuffer[2]  = SCSI_SENSE_NONE;
+    pRequest->pbSenseBuffer[7]  = 10;
+    pRequest->pbSenseBuffer[12] = SCSI_ASC_NONE;
+    pRequest->pbSenseBuffer[13] = SCSI_ASC_NONE; /* Should be ASCQ but it has the same value for success. */
+#endif
+    return SCSI_STATUS_OK;
+}
+
+DECLINLINE(void) drvscsiH2BE_U16(uint8_t *pbBuf, uint16_t val)
+{
+    pbBuf[0] = val >> 8;
+    pbBuf[1] = val;
+}
+
+
+DECLINLINE(void) drvscsiH2BE_U24(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 16;
+    pbBuf[1] = val >> 8;
+    pbBuf[2] = val;
+}
+
+
+DECLINLINE(void) drvscsiH2BE_U32(uint8_t *pbBuf, uint32_t val)
+{
+    pbBuf[0] = val >> 24;
+    pbBuf[1] = val >> 16;
+    pbBuf[2] = val >> 8;
+    pbBuf[3] = val;
+}
+
+
+DECLINLINE(uint16_t) drvscsiBE2H_U16(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 8) | pbBuf[1];
+}
+
+
+DECLINLINE(uint32_t) drvscsiBE2H_U24(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
+}
+
+
+DECLINLINE(uint32_t) drvscsiBE2H_U32(const uint8_t *pbBuf)
+{
+    return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
+}
+
+DECLINLINE(uint64_t) drvscsiBE2H_U64(const uint8_t *pbBuf)
+{
+    return   ((uint64_t)pbBuf[0] << 56)
+           | ((uint64_t)pbBuf[1] << 48)
+           | ((uint64_t)pbBuf[2] << 40)
+           | ((uint64_t)pbBuf[3] << 32)
+           | ((uint64_t)pbBuf[4] << 24)
+           | ((uint64_t)pbBuf[5] << 16)
+           | ((uint64_t)pbBuf[6] << 8)
+           | (uint64_t)pbBuf[7];
+}
+
+/**
+ * Parses the CDB of a request and acts accordingly.
+ *
+ * @returns transfer direction type.
+ * @param   pThis    Pointer to the SCSI driver instance data.
+ * @param   pRequest Pointer to the request to process.
+ * @param   puOffset Where to store the start offset to start data transfer from.
+ * @param   pcbToTransfer Where to store the number of bytes to transfer.
+ * @param   piTxDir       Where to store the data transfer direction.
+ */
+static int drvscsiProcessCDB(PDRVSCSI pThis, PPDMSCSIREQUEST pRequest, uint64_t *puOffset, uint32_t *pcbToTransfer, int *piTxDir)
+{
+    int iTxDir = PDMBLOCKTXDIR_NONE;
+    int rc = SCSI_STATUS_OK;
+
+    /* We check for a command which needs to be handled even for non existant LUNs. */
+    switch (pRequest->pbCDB[0])
+    {
+        case SCSI_INQUIRY:
+        {
+            SCSIINQUIRYDATA ScsiInquiryReply;
+
+            memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+
+            ScsiInquiryReply.cbAdditional = 31;
+
+            /* We support only one attached device at LUN0 at the moment. */
+            if (pRequest->uLogicalUnit != 0)
+            {
+                ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+                ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+            }
+            else
+            {
+                switch (pThis->enmType)
+                {
+                    case PDMBLOCKTYPE_HARD_DISK:
+                        ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+                        break;
+                    default:
+                        AssertMsgFailed(("Device type %u not supported\n", pThis->enmType));
+                }
+
+                ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+                ScsiInquiryReply.u3AnsiVersion = 0x05; /* SPC-4 compliant */
+                drvscsiPadStr(ScsiInquiryReply.achVendorId, "VBOX", 8);
+                drvscsiPadStr(ScsiInquiryReply.achProductId, "HARDDISK", 16);
+                drvscsiPadStr(ScsiInquiryReply.achProductLevel, "1.0", 4);
+            }
+
+            drvscsiScatterGatherListCopyFromBuffer(pRequest, &ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+            rc = drvscsiCmdOk(pRequest);
+            break;
+        }
+        case SCSI_REPORT_LUNS:
+        {
+            /*
+             * If allocation length is less than 16 bytes SPC compliant devices have
+             * to return an error.
+             */
+            if (drvscsiBE2H_U32(&pRequest->pbCDB[6]) < 16)
+                rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+            else
+            {
+                uint8_t aReply[16]; /* We report only one LUN. */
+
+                memset(aReply, 0, sizeof(aReply));
+                drvscsiH2BE_U32(&aReply[0], 8); /* List length starts at position 0. */
+                drvscsiScatterGatherListCopyFromBuffer(pRequest, aReply, sizeof(aReply));
+                rc = drvscsiCmdOk(pRequest);
+            }
+            break;
+        }
+        case SCSI_TEST_UNIT_READY:
+        {
+            rc = drvscsiCmdOk(pRequest);
+            break;
+        }
+        default:
+        {
+            /* Now for commands which are only implemented for existant LUNs. */
+            if (RT_LIKELY(pRequest->uLogicalUnit == 0))
+            {
+                switch(pRequest->pbCDB[0])
+                {
+                    case SCSI_READ_CAPACITY:
+                    {
+                        uint8_t aReply[8];
+                        memset(aReply, 0, sizeof(aReply));
+
+                        /*
+                         * If sector size exceeds the maximum value that is
+                         * able to be stored in 4 bytes return 0xffffffff in this field
+                         */
+                        if (pThis->cSectors > UINT32_C(0xffffffff))
+                            drvscsiH2BE_U32(aReply, UINT32_C(0xffffffff));
+                        else
+                            drvscsiH2BE_U32(aReply, pThis->cSectors - 1);
+                        drvscsiH2BE_U32(&aReply[4], 512);
+                        drvscsiScatterGatherListCopyFromBuffer(pRequest, aReply, sizeof(aReply));
+                        rc =  drvscsiCmdOk(pRequest);
+                        break;
+                    }
+                    case SCSI_SERVICE_ACTION_IN_16:
+                    {
+                        AssertMsgFailed(("Not implemented yet.\n"));
+                        break;
+                    }
+                    case SCSI_MODE_SENSE_6:
+                    {
+                        uint8_t uModePage = pRequest->pbCDB[2] & 0x3f;
+                        uint8_t aReply[24];
+                        uint8_t *pu8ReplyPos;
+
+                        memset(aReply, 0, sizeof(aReply));
+                        aReply[0] = 4; /* Reply length 4. */
+                        aReply[1] = 0; /* Default media type. */
+                        aReply[2] = RT_BIT(4); /* Caching supported. */
+                        aReply[3] = 0; /* Block descriptor length. */
+
+                        pu8ReplyPos = aReply + 4;
+
+                        if ((uModePage == 0x08) || (uModePage == 0x3f))
+                        {
+                            memset(pu8ReplyPos, 0, 20);
+                            *pu8ReplyPos++ = 0x08; /* Page code. */
+                            *pu8ReplyPos++ = 0x12; /* Size of the page. */
+                            *pu8ReplyPos++ = 0x4;  /* Write cache enabled. */
+                        }
+
+                        drvscsiScatterGatherListCopyFromBuffer(pRequest, aReply, sizeof(aReply));
+                        rc =  drvscsiCmdOk(pRequest);
+                        break;
+                    }
+                    case SCSI_READ_6:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+                        *puOffset      = ((uint64_t)  pRequest->pbCDB[3]
+                                                    | (pRequest->pbCDB[2] <<  8)
+                                                    | ((pRequest->pbCDB[1] & 0x1f) << 16)) * 512;
+                        *pcbToTransfer = ((uint32_t)pRequest->pbCDB[4]) * 512;
+                        break;
+                    }
+                    case SCSI_READ_10:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+                        *puOffset = ((uint64_t)drvscsiBE2H_U32(&pRequest->pbCDB[2])) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U16(&pRequest->pbCDB[7])) * 512;
+                        break;
+                    }
+                    case SCSI_READ_12:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+                        *puOffset = ((uint64_t)drvscsiBE2H_U32(&pRequest->pbCDB[2])) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U32(&pRequest->pbCDB[6])) * 512;
+                        break;
+                    }
+                    case SCSI_READ_16:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+                        *puOffset = drvscsiBE2H_U64(&pRequest->pbCDB[2]) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U32(&pRequest->pbCDB[6])) * 512;
+                        break;
+                    }
+                    case SCSI_WRITE_6:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+                        *puOffset      = ((uint64_t)  pRequest->pbCDB[3]
+                                                    | (pRequest->pbCDB[2] <<  8)
+                                                    | ((pRequest->pbCDB[1] & 0x1f) << 16)) * 512;
+                        *pcbToTransfer = ((uint32_t)pRequest->pbCDB[4]) * 512;
+                        break;
+                    }
+                    case SCSI_WRITE_10:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+                        *puOffset = ((uint64_t)drvscsiBE2H_U32(&pRequest->pbCDB[2])) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U16(&pRequest->pbCDB[7])) * 512;
+                        break;
+                    }
+                    case SCSI_WRITE_12:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+                        *puOffset = ((uint64_t)drvscsiBE2H_U32(&pRequest->pbCDB[2])) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U32(&pRequest->pbCDB[6])) * 512;
+                        break;
+                    }
+                    case SCSI_WRITE_16:
+                    {
+                        iTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+                        *puOffset = drvscsiBE2H_U64(&pRequest->pbCDB[2]) * 512;
+                        *pcbToTransfer = ((uint32_t)drvscsiBE2H_U32(&pRequest->pbCDB[6])) * 512;
+                        break;
+                    }
+                    case SCSI_SYNCHRONIZE_CACHE:
+                    {
+                        /* @todo When async mode implemented we have to move this out here. */
+                        int rc2 = pThis->pDrvBlock->pfnFlush(pThis->pDrvBlock);
+                        AssertMsgRC(rc2, ("Flushing data failed rc=%Rrc\n", rc2));
+                        break;
+                    }
+                    case SCSI_READ_BUFFER:
+                    {
+                        uint8_t uDataMode = pRequest->pbCDB[1] & 0x1f;
+
+                        switch (uDataMode)
+                        {
+                            case 0x00:
+                            case 0x01:
+                            case 0x02:
+                            case 0x03:
+                            case 0x0a:
+                                break;
+                            case 0x0b:
+                            {
+                                uint8_t aReply[4];
+
+                                /* We do not implement an echo buffer. */
+                                memset(aReply, 0, sizeof(aReply));
+
+                                drvscsiScatterGatherListCopyFromBuffer(pRequest, aReply, sizeof(aReply));
+                                rc =  drvscsiCmdOk(pRequest);
+                                break;
+                            }
+                            case 0x1a:
+                            case 0x1c:
+                                break;
+                            default:
+                                AssertMsgFailed(("Invalid data mode\n"));
+                        }
+                        break;
+                    }
+                    case SCSI_START_STOP_UNIT:
+                    {
+                        /* Nothing to do. */
+                        break;
+                    }
+                    case SCSI_LOG_SENSE:
+                    {
+                        uint16_t cbMax = drvscsiBE2H_U16(&pRequest->pbCDB[7]);
+                        uint8_t uPageCode = pRequest->pbCDB[2] & 0x3f;
+                        uint8_t uSubPageCode = pRequest->pbCDB[3];
+
+                        switch (uPageCode)
+                        {
+                            case 0x00:
+                            {
+                                if (uSubPageCode == 0)
+                                {
+                                    uint8_t aReply[4];
+
+                                    aReply[0] = 0;
+                                    aReply[1] = 0;
+                                    aReply[2] = 0;
+                                    aReply[3] = 0;
+                                    drvscsiScatterGatherListCopyFromBuffer(pRequest, aReply, sizeof(aReply));
+                                    rc =  drvscsiCmdOk(pRequest);
+                                    break;
+                                }
+                            }
+                            default:
+                                rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                        }
+                        break;
+                    }
+                    default:
+                        //AssertMsgFailed(("Command %#x [%s] not implemented\n", pRequest->pbCDB[0], SCSICmdText(pRequest->pbCDB[0])));
+                        rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+                }
+            }
+            else
+            {
+                /* Report an error. */
+                rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_UNIT_DOES_NOT_RESPOND_TO_SELECTION);
+            }
+            break;
+        }
+    }
+
+    *piTxDir = iTxDir;
+
+    return rc;
+}
+
+static int drvscsiProcessRequestOne(PDRVSCSI pThis, PPDMSCSIREQUEST pRequest)
+{
+    int rc = VINF_SUCCESS;
+    int iTxDir;
+    int rcCompletion;
+    uint64_t uOffset;
+    uint32_t cbToTransfer;
+    uint32_t cSegmentsLeft;
+
+    LogFlowFunc(("Entered\n"));
+
+#ifdef DEBUG
+    drvscsiDumpScsiRequest(pRequest);
+#endif
+    rcCompletion = drvscsiProcessCDB(pThis, pRequest, &uOffset, &cbToTransfer, &iTxDir);
+    if ((rcCompletion == SCSI_STATUS_OK) && (iTxDir != PDMBLOCKTXDIR_NONE))
+    {
+        PPDMDATASEG pSegActual;
+
+        pSegActual = &pRequest->paScatterGatherHead[0];
+        cSegmentsLeft = pRequest->cScatterGatherEntries;
+
+        while(cbToTransfer && cSegmentsLeft)
+        {
+            uint32_t cbProcess = (cbToTransfer < pSegActual->cbSeg) ? cbToTransfer : (uint32_t)pSegActual->cbSeg;
+
+            Log(("%s: uOffset=%llu cbToTransfer=%u\n", __FUNCTION__, uOffset, cbToTransfer));
+
+            if (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+            {
+                pThis->pLed->Asserted.s.fReading = pThis->pLed->Actual.s.fReading = 1;
+                rc = pThis->pDrvBlock->pfnRead(pThis->pDrvBlock, uOffset,
+                                                pSegActual->pvSeg, cbProcess);
+                pThis->pLed->Actual.s.fReading = 0;
+                if (RT_FAILURE(rc))
+                    AssertMsgFailed(("%s: Failed to read data %Rrc\n", __FUNCTION__, rc));
+                STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, cbProcess);
+            }
+            else
+            {
+                pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
+                rc = pThis->pDrvBlock->pfnWrite(pThis->pDrvBlock, uOffset,
+                                                pSegActual->pvSeg, cbProcess);
+                pThis->pLed->Actual.s.fWriting = 0;
+                if (RT_FAILURE(rc))
+                    AssertMsgFailed(("%s: Failed to write data %Rrc\n", __FUNCTION__, rc));
+                STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, cbProcess);
+            }
+
+            /* Go to the next entry. */
+            uOffset += cbProcess;
+            cbToTransfer -= cbProcess;
+            pSegActual++;
+            cSegmentsLeft--;
+        }
+        AssertMsg(!cbToTransfer && !cSegmentsLeft,
+                  ("Transfer incomplete cbToTransfer=%u cSegmentsLeft=%u", cbToTransfer, cSegmentsLeft));
+        drvscsiCmdOk(pRequest);
+    }
+
+    /* Notify device. */
+    rc = pThis->pDevScsiPort->pfnSCSIRequestCompleted(pThis->pDevScsiPort, pRequest, rcCompletion);
+    AssertMsgRC(rc, ("Error while notifying device rc=%Rrc\n", rc));
+
+    return rc;
+}
+
+/**
+ * Request function to wakeup the thread.
+ *
+ * @returns VWRN_STATE_CHANGED.
+ */
+static int drvscsiAsyncIOLoopWakeupFunc(void)
+{
+    return VWRN_STATE_CHANGED;
+}
+
+/**
+ * The thread function which processes the requests asynchronously.
+ *
+ * @returns VBox status code.
+ * @param   pDrvIns    Pointer to the device instance data.
+ * @param   pThread    Pointer to the thread instance data.
+ */
+static int drvscsiAsyncIOLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+    int rc = VINF_SUCCESS;
+    PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+    LogFlowFunc(("Entering async IO loop.\n"));
+
+    if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+        return VINF_SUCCESS;
+
+    while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+    {
+        rc = RTReqProcess(pThis->pQueueRequests, RT_INDEFINITE_WAIT);
+        AssertMsg(rc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", rc));
+    }
+
+    return VINF_SUCCESS;
+}
+
+static int drvscsiAsyncIOLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+    int rc;
+    PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+    PRTREQ pReq;
+
+    AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
+
+    rc = RTReqCall(pThis->pQueueRequests, &pReq, 10000 /* 10 sec. */, (PFNRT)drvscsiAsyncIOLoopWakeupFunc, 0);
+    AssertMsgRC(rc, ("Inserting request into queue failed rc=%Rrc\n", rc));
+
+    return rc;
+}
+
+/* -=-=-=-=- ISCSIConnector -=-=-=-=- */
+
+/** @copydoc PDMISCSICONNECTOR::pfnSCSIRequestSend. */
+static DECLCALLBACK(int) drvscsiRequestSend(PPDMISCSICONNECTOR pInterface, PPDMSCSIREQUEST pSCSIRequest)
+{
+    int rc;
+    PDRVSCSI pThis = PDMISCSICONNECTOR_2_DRVSCSI(pInterface);
+    PRTREQ pReq;
+
+    AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
+
+    rc = RTReqCallEx(pThis->pQueueRequests, &pReq, 0, RTREQFLAGS_NO_WAIT, (PFNRT)drvscsiProcessRequestOne, 2, pThis, pSCSIRequest);
+    AssertMsgReturn(RT_SUCCESS(rc), ("Inserting request into queue failed rc=%Rrc\n", rc), rc);
+
+    return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/** @copydoc PDMIBASE::pfnQueryInterface. */
+static DECLCALLBACK(void *)  drvscsiQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PPDMDRVINS  pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+    PDRVSCSI    pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pDrvIns->IBase;
+        case PDMINTERFACE_SCSI_CONNECTOR:
+            return &pThis->ISCSIConnector;
+        case PDMINTERFACE_BLOCK_PORT:
+            return &pThis->IPort;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) drvscsiDestruct(PPDMDRVINS pDrvIns)
+{
+    int rc;
+    PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+    if (pThis->pQueueRequests)
+    {
+        rc = RTReqDestroyQueue(pThis->pQueueRequests);
+        AssertMsgRC(rc, ("Failed to destroy queue rc=%Rrc\n", rc));
+    }
+
+}
+
+/**
+ * Construct a block driver instance.
+ *
+ * @returns VBox status.
+ * @param   pDrvIns     The driver instance data.
+ *                      If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param   pCfgHandle  Configuration node handle for the driver. Use this to obtain the configuration
+ *                      of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ *                      iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvscsiConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+    int rc = VINF_SUCCESS;
+    PDMBLOCKTYPE enmType;
+    PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+    LogFlowFunc(("pDrvIns=%#p pCfgHandle=%#p\n", pDrvIns, pCfgHandle));
+
+    /*
+     * Initialize interfaces.
+     */
+    pDrvIns->IBase.pfnQueryInterface                    = drvscsiQueryInterface;
+    pThis->ISCSIConnector.pfnSCSIRequestSend            = drvscsiRequestSend;
+
+    /*
+     * Try attach driver below and query it's block interface.
+     */
+    rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pThis->pDrvBase);
+    AssertMsgReturn(RT_SUCCESS(rc), ("Attaching driver below failed rc=%Rrc\n", rc), rc);
+
+    /*
+     * Query the block and blockbios interfaces.
+     */
+    pThis->pDrvBlock = (PDMIBLOCK *)pThis->pDrvBase->pfnQueryInterface(pThis->pDrvBase, PDMINTERFACE_BLOCK);
+    if (!pThis->pDrvBlock)
+    {
+        AssertMsgFailed(("Configuration error: No block interface!\n"));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+    pThis->pDrvBlockBios = (PDMIBLOCKBIOS *)pThis->pDrvBase->pfnQueryInterface(pThis->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
+    if (!pThis->pDrvBlockBios)
+    {
+        AssertMsgFailed(("Configuration error: No block BIOS interface!\n"));
+        return VERR_PDM_MISSING_INTERFACE;
+    }
+
+    /* Query the SCSI port interface above. */
+    pThis->pDevScsiPort = (PPDMISCSIPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_SCSI_PORT);
+    AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
+
+    pThis->pDrvMount = (PDMIMOUNT *)pThis->pDrvBase->pfnQueryInterface(pThis->pDrvBase, PDMINTERFACE_MOUNT);
+
+    /* Query the optional LED interface above. */
+    pThis->pLedPort = (PPDMILEDPORTS)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_LED_PORTS);
+    if (pThis->pLedPort != NULL)
+    {
+        /* Get The Led. */
+        rc = pThis->pLedPort->pfnQueryStatusLed(pThis->pLedPort, 0, &pThis->pLed);
+        if (RT_FAILURE(rc))
+            pThis->pLed = &pThis->Led;
+    }
+    else
+        pThis->pLed = &pThis->Led;
+
+    /* Try to get the optional async block interface. */
+    pThis->pDrvBlockAsync = (PDMIBLOCKASYNC *)pThis->pDrvBase->pfnQueryInterface(pThis->pDrvBase, PDMINTERFACE_BLOCK_ASYNC);
+
+    enmType = pThis->pDrvBlock->pfnGetType(pThis->pDrvBlock);
+    if (enmType != PDMBLOCKTYPE_HARD_DISK)
+    {
+        AssertMsgFailed(("Configuration error: Not a disk or cd/dvd-rom. enmType=%d\n", enmType));
+        return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+    }
+    pThis->enmType = enmType;
+    pThis->cSectors = pThis->pDrvBlock->pfnGetSize(pThis->pDrvBlock) / 512;
+
+    /* Create request queue. */
+    rc = RTReqCreateQueue(&pThis->pQueueRequests);
+    AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create request queue rc=%Rrc\n"), rc);
+
+    /* Register statistics counter. */
+    PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+                            "Amount of data read.", "/Devices/SCSI/%d/ReadBytes", pDrvIns->iInstance);
+    PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+                            "Amount of data written.", "/Devices/SCSI/%d/WrittenBytes", pDrvIns->iInstance);
+
+    /* Create I/O thread. */
+    rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pThis->pAsyncIOThread, pThis, drvscsiAsyncIOLoop,
+                                  drvscsiAsyncIOLoopWakeup, 0, RTTHREADTYPE_IO, "SCSI async IO");
+    AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create async I/O thread rc=%Rrc\n"), rc);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * SCSI driver registration record.
+ */
+const PDMDRVREG g_DrvSCSI =
+{
+    /* u32Version */
+    PDM_DRVREG_VERSION,
+    /* szDriverName */
+    "SCSI",
+    /* pszDescription */
+    "Generic SCSI driver.",
+    /* fFlags */
+    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+    /* fClass. */
+    PDM_DRVREG_CLASS_SCSI,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(DRVSCSI),
+    /* pfnConstruct */
+    drvscsiConstruct,
+    /* pfnDestruct */
+    drvscsiDestruct,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    NULL,
+    /* pfnSuspend */
+    NULL,
+    /* pfnResume */
+    NULL,
+    /* pfnDetach */
+    NULL,
+    /* pfnPowerOff */
+    NULL
+};
Index: trunk/src/VBox/Devices/Storage/DrvSCSIHost.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DrvSCSIHost.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/DrvSCSIHost.cpp	(revision 21321)
@@ -0,0 +1,534 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage drivers:
+ * Host SCSI access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DRV_SCSIHOST
+#include <VBox/pdmdrv.h>
+#include <VBox/pdmifs.h>
+#include <VBox/pdmthread.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/req.h>
+#include <iprt/file.h>
+
+#if defined(RT_OS_LINUX)
+# include <limits.h>
+# include <scsi/sg.h>
+# include <sys/ioctl.h>
+#endif
+
+#include "../Builtins.h"
+
+/**
+ * SCSI driver instance data.
+ */
+typedef struct DRVSCSIHOST
+{
+    /** Pointer driver instance. */
+    PPDMDRVINS              pDrvIns;
+
+    /** Pointer to the SCSI port interface of the device above. */
+    PPDMISCSIPORT           pDevScsiPort;
+    /** The scsi connector interface .*/
+    PDMISCSICONNECTOR       ISCSIConnector;
+
+    /** PAth to the device file. */
+    char                   *pszDevicePath;
+    /** Handle to the device. */
+    RTFILE                  DeviceFile;
+
+    /** The dedicated I/O thread. */
+    PPDMTHREAD              pAsyncIOThread;
+    /** Queue for passing the requests to the thread. */
+    PRTREQQUEUE             pQueueRequests;
+} DRVSCSIHOST, *PDRVSCSIHOST;
+
+/** Converts a pointer to DRVSCSIHOST::ISCSIConnecotr to a PDRVSCSIHOST. */
+#define PDMISCSICONNECTOR_2_DRVSCSIHOST(pInterface) ( (PDRVSCSIHOST)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSIHOST, ISCSIConnector)) )
+
+#ifdef DEBUG
+/**
+ * Dumps a SCSI request structure for debugging purposes.
+ *
+ * @returns nothing.
+ * @param   pRequest    Pointer to the request to dump.
+ */
+static void drvscsihostDumpScsiRequest(PPDMSCSIREQUEST pRequest)
+{
+    Log(("Dump for pRequest=%#p Command: %s\n", pRequest, SCSICmdText(pRequest->pbCDB[0])));
+    Log(("cbCDB=%u\n", pRequest->cbCDB));
+    for (uint32_t i = 0; i < pRequest->cbCDB; i++)
+        Log(("pbCDB[%u]=%#x\n", i, pRequest->pbCDB[i]));
+    Log(("cbScatterGather=%u\n", pRequest->cbScatterGather));
+    Log(("cScatterGatherEntries=%u\n", pRequest->cScatterGatherEntries));
+    /* Print all scatter gather entries. */
+    for (uint32_t i = 0; i < pRequest->cScatterGatherEntries; i++)
+    {
+        Log(("ScatterGatherEntry[%u].cbSeg=%u\n", i, pRequest->paScatterGatherHead[i].cbSeg));
+        Log(("ScatterGatherEntry[%u].pvSeg=%#p\n", i, pRequest->paScatterGatherHead[i].pvSeg));
+    }
+    Log(("pvUser=%#p\n", pRequest->pvUser));
+}
+#endif
+
+/**
+ * Copy the content of a buffer to a scatter gather list
+ * copying only the amount of data which fits into the
+ * scatter gather list.
+ *
+ * @returns VBox status code.
+ * @param   pRequest    Pointer to the request which contains the S/G list entries.
+ * @param   pvBuf       Pointer to the buffer which should be copied.
+ * @param   cbBuf       Size of the buffer.
+ */
+static int drvscsihostScatterGatherListCopyFromBuffer(PPDMSCSIREQUEST pRequest, void *pvBuf, size_t cbBuf)
+{
+    unsigned cSGEntry = 0;
+    PPDMDATASEG pSGEntry = &pRequest->paScatterGatherHead[cSGEntry];
+    uint8_t *pu8Buf = (uint8_t *)pvBuf;
+
+    while (cSGEntry < pRequest->cScatterGatherEntries)
+    {
+        size_t cbToCopy = (cbBuf < pSGEntry->cbSeg) ? cbBuf : pSGEntry->cbSeg;
+
+        memcpy(pSGEntry->pvSeg, pu8Buf, cbToCopy);
+
+        cbBuf -= cbToCopy;
+        /* We finished. */
+        if (!cbBuf)
+            break;
+
+        /* Advance the buffer. */
+        pu8Buf += cbToCopy;
+
+        /* Go to the next entry in the list. */
+        pSGEntry++;
+        cSGEntry++;
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Set the sense and advanced sense key in the buffer for error conditions.
+ *
+ * @returns nothing.
+ * @param   pRequest      Pointer to the request which contains the sense buffer.
+ * @param   uSCSISenseKey The sense key to set.
+ * @param   uSCSIASC      The advanced sense key to set.
+ */
+DECLINLINE(void) drvscsiCmdError(PPDMSCSIREQUEST pRequest, uint8_t uSCSISenseKey, uint8_t uSCSIASC)
+{
+    AssertMsg(pRequest->cbSenseBuffer >= 2, ("Sense buffer is not big enough\n"));
+    AssertMsg(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"));
+    pRequest->pbSenseBuffer[0] = uSCSISenseKey;
+    pRequest->pbSenseBuffer[1] = uSCSIASC;
+}
+
+/**
+ * Sets the sense key for a status good condition.
+ *
+ * @returns nothing.
+ * @param   pRequest    Pointer to the request which contains the sense buffer.
+ */
+DECLINLINE(void) drvscsihostCmdOk(PPDMSCSIREQUEST pRequest)
+{
+    AssertMsg(pRequest->cbSenseBuffer >= 2, ("Sense buffer is not big enough\n"));
+    AssertMsg(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"));
+    pRequest->pbSenseBuffer[0] = SCSI_SENSE_NONE;
+    pRequest->pbSenseBuffer[1] = SCSI_ASC_NONE;
+}
+
+/**
+ * Returns the transfer direction of the given command
+ * in case the device does not provide this info.
+ *
+ * @returns transfer direction of the command.
+ *          SCSIHOSTTXDIR_NONE if no data is transfered.
+ *          SCSIHOSTTXDIR_FROM_DEVICE if the data is read from the device.
+ *          SCSIHOSTTXDIR_TO_DEVICE   if the data is written to the device.
+ * @param   uCommand The command byte.
+ */
+static unsigned drvscsihostGetTransferDirectionFromCommand(uint8_t uCommand)
+{
+    switch (uCommand)
+    {
+        case SCSI_INQUIRY:
+        case SCSI_REPORT_LUNS:
+        case SCSI_MODE_SENSE_6:
+        case SCSI_READ_TOC_PMA_ATIP:
+        case SCSI_READ_CAPACITY:
+        case SCSI_MODE_SENSE_10:
+        case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+        case SCSI_GET_CONFIGURATION:
+        case SCSI_READ_10:
+        case SCSI_READ_12:
+        case SCSI_READ_BUFFER:
+        case SCSI_READ_BUFFER_CAPACITY:
+        case SCSI_READ_DISC_INFORMATION:
+        case SCSI_READ_DVD_STRUCTURE:
+        case SCSI_READ_FORMAT_CAPACITIES:
+        case SCSI_READ_SUBCHANNEL:
+        case SCSI_READ_TRACK_INFORMATION:
+        case SCSI_READ_CD:
+        case SCSI_READ_CD_MSF:
+            return PDMSCSIREQUESTTXDIR_FROM_DEVICE;
+        case SCSI_TEST_UNIT_READY:
+        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+        case SCSI_START_STOP_UNIT:
+            return PDMSCSIREQUESTTXDIR_NONE;
+        case SCSI_WRITE_10:
+        case SCSI_WRITE_12:
+        case SCSI_WRITE_BUFFER:
+            return PDMSCSIREQUESTTXDIR_TO_DEVICE;
+        default:
+            AssertMsgFailed(("Command not known %#x\n", uCommand));
+    }
+
+    /* We should never get here in debug mode. */
+    AssertMsgFailed(("Impossible to get here!!!\n"));
+    return PDMSCSIREQUESTTXDIR_NONE; /* to make compilers happy. */
+}
+
+static int drvscsihostProcessRequestOne(PDRVSCSIHOST pThis, PPDMSCSIREQUEST pRequest)
+{
+    int rc = VINF_SUCCESS;
+    unsigned uTxDir;
+
+    LogFlowFunc(("Entered\n"));
+
+#ifdef DEBUG
+    drvscsihostDumpScsiRequest(pRequest);
+#endif
+
+    /* We implement only one device. */
+    if (pRequest->uLogicalUnit != 0)
+    {
+        switch (pRequest->pbCDB[0])
+        {
+            case SCSI_INQUIRY:
+            {
+                SCSIINQUIRYDATA ScsiInquiryReply;
+
+                memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+
+                ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+                ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+                drvscsihostScatterGatherListCopyFromBuffer(pRequest, &ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+                drvscsihostCmdOk(pRequest);
+                break;
+            }
+            default:
+                AssertMsgFailed(("Command not implemented for attached device\n"));
+                drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_NONE);
+        }
+    }
+    else
+    {
+#if defined(RT_OS_LINUX)
+        sg_io_hdr_t ScsiIoReq;
+        sg_iovec_t  *paSG = NULL;
+
+        /* Setup SCSI request. */
+        memset(&ScsiIoReq, 0, sizeof(sg_io_hdr_t));
+        ScsiIoReq.interface_id = 'S';
+
+        if (pRequest->uDataDirection == PDMSCSIREQUESTTXDIR_UNKNOWN)
+            uTxDir = drvscsihostGetTransferDirectionFromCommand(pRequest->pbCDB[0]);
+        else
+            uTxDir = pRequest->uDataDirection;
+
+        if (uTxDir == PDMSCSIREQUESTTXDIR_NONE)
+            ScsiIoReq.dxfer_direction = SG_DXFER_NONE;
+        else if (uTxDir == PDMSCSIREQUESTTXDIR_TO_DEVICE)
+            ScsiIoReq.dxfer_direction = SG_DXFER_TO_DEV;
+        else if (uTxDir == PDMSCSIREQUESTTXDIR_FROM_DEVICE)
+            ScsiIoReq.dxfer_direction = SG_DXFER_FROM_DEV;
+        else
+            AssertMsgFailed(("Invalid transfer direction %u\n", uTxDir));
+
+        ScsiIoReq.cmd_len     = pRequest->cbCDB;
+        ScsiIoReq.mx_sb_len   = pRequest->cbSenseBuffer;
+        ScsiIoReq.dxfer_len   = pRequest->cbScatterGather;
+
+        if (pRequest->cScatterGatherEntries > 0)
+        {
+            if (pRequest->cScatterGatherEntries == 1)
+            {
+                ScsiIoReq.iovec_count = 0;
+                ScsiIoReq.dxferp      = pRequest->paScatterGatherHead[0].pvSeg;
+            }
+            else
+            {
+                ScsiIoReq.iovec_count = pRequest->cScatterGatherEntries;
+
+                paSG = (sg_iovec_t *)RTMemAllocZ(pRequest->cScatterGatherEntries * sizeof(sg_iovec_t));
+                AssertPtrReturn(paSG, VERR_NO_MEMORY);
+
+                for (unsigned i = 0; i < pRequest->cScatterGatherEntries; i++)
+                {
+                    paSG[i].iov_base = pRequest->paScatterGatherHead[i].pvSeg;
+                    paSG[i].iov_len  = pRequest->paScatterGatherHead[i].cbSeg;
+                }
+                ScsiIoReq.dxferp = paSG;
+            }
+        }
+
+        ScsiIoReq.cmdp    = pRequest->pbCDB;
+        ScsiIoReq.sbp     = pRequest->pbSenseBuffer;
+        ScsiIoReq.timeout = UINT_MAX;
+        ScsiIoReq.flags  |= SG_FLAG_DIRECT_IO;
+
+        /** Issue command. */
+        rc = ioctl(pThis->DeviceFile, SG_IO, &ScsiIoReq);
+        if (rc < 0)
+        {
+            AssertMsgFailed(("Ioctl failed with rc=%d\n", rc));
+        }
+
+        /* Request processed successfully. */
+        Log(("Command successfully processed\n"));
+        if (ScsiIoReq.iovec_count > 0)
+            RTMemFree(paSG);
+#endif
+    }
+    /* Notify device that request finished. */
+    rc = pThis->pDevScsiPort->pfnSCSIRequestCompleted(pThis->pDevScsiPort, pRequest, SCSI_STATUS_OK);
+    AssertMsgRC(rc, ("Notifying device above failed rc=%Rrc\n", rc));
+
+    return rc;
+
+}
+
+/**
+ * Request function to wakeup the thread.
+ *
+ * @returns VWRN_STATE_CHANGED.
+ */
+static int drvscsihostAsyncIOLoopWakeupFunc(void)
+{
+    return VWRN_STATE_CHANGED;
+}
+
+/**
+ * The thread function which processes the requests asynchronously.
+ *
+ * @returns VBox status code.
+ * @param   pDrvIns    Pointer to the device instance data.
+ * @param   pThread    Pointer to the thread instance data.
+ */
+static int drvscsihostAsyncIOLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+    int rc = VINF_SUCCESS;
+    PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
+
+    LogFlowFunc(("Entering async IO loop.\n"));
+
+    if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+        return VINF_SUCCESS;
+
+    while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+    {
+        rc = RTReqProcess(pThis->pQueueRequests, RT_INDEFINITE_WAIT);
+        AssertMsg(rc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", rc));
+    }
+
+    return VINF_SUCCESS;
+}
+
+static int drvscsihostAsyncIOLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+    int rc;
+    PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
+    PRTREQ pReq;
+
+    AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
+
+    rc = RTReqCall(pThis->pQueueRequests, &pReq, 10000 /* 10 sec. */, (PFNRT)drvscsihostAsyncIOLoopWakeupFunc, 0);
+    AssertMsgRC(rc, ("Inserting request into queue failed rc=%Rrc\n"));
+
+    return rc;
+}
+
+/* -=-=-=-=- ISCSIConnector -=-=-=-=- */
+
+/** @copydoc PDMISCSICONNECTOR::pfnSCSIRequestSend. */
+static DECLCALLBACK(int) drvscsihostRequestSend(PPDMISCSICONNECTOR pInterface, PPDMSCSIREQUEST pSCSIRequest)
+{
+    int rc;
+    PDRVSCSIHOST pThis = PDMISCSICONNECTOR_2_DRVSCSIHOST(pInterface);
+    PRTREQ pReq;
+
+    AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
+
+    rc = RTReqCallEx(pThis->pQueueRequests, &pReq, 0, RTREQFLAGS_NO_WAIT, (PFNRT)drvscsihostProcessRequestOne, 2, pThis, pSCSIRequest);
+    AssertMsgReturn(RT_SUCCESS(rc), ("Inserting request into queue failed rc=%Rrc\n", rc), rc);
+
+    return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/** @copydoc PDMIBASE::pfnQueryInterface. */
+static DECLCALLBACK(void *)  drvscsihostQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PPDMDRVINS   pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+    PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pDrvIns->IBase;
+        case PDMINTERFACE_SCSI_CONNECTOR:
+            return &pThis->ISCSIConnector;
+        default:
+            return NULL;
+    }
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) drvscsihostDestruct(PPDMDRVINS pDrvIns)
+{
+    int rc;
+    PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
+
+    if (pThis->DeviceFile != NIL_RTFILE)
+        RTFileClose(pThis->DeviceFile);
+
+    if (pThis->pszDevicePath)
+        MMR3HeapFree(pThis->pszDevicePath);
+
+    if (pThis->pQueueRequests)
+    {
+        rc = RTReqDestroyQueue(pThis->pQueueRequests);
+        AssertMsgRC(rc, ("Failed to destroy queue rc=%Rrc\n", rc));
+    }
+
+}
+
+/**
+ * Construct a block driver instance.
+ *
+ * @returns VBox status.
+ * @param   pDrvIns     The driver instance data.
+ *                      If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param   pCfgHandle  Configuration node handle for the driver. Use this to obtain the configuration
+ *                      of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ *                      iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvscsihostConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+    int rc = VINF_SUCCESS;
+    PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
+
+    LogFlowFunc(("pDrvIns=%#p pCfgHandle=%#p\n", pDrvIns, pCfgHandle));
+
+    /*
+     * Read the configuration.
+     */
+    if (!CFGMR3AreValuesValid(pCfgHandle, "DevicePath\0"))
+        return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+                                N_("Invalid configuraton for host scsi access driver"));
+
+    /*
+     * Initialize interfaces.
+     */
+    pDrvIns->IBase.pfnQueryInterface                    = drvscsihostQueryInterface;
+    pThis->ISCSIConnector.pfnSCSIRequestSend            = drvscsihostRequestSend;
+    pThis->pDrvIns    = pDrvIns;
+    pThis->DeviceFile = NIL_RTFILE;
+
+    /* Query the SCSI port interface above. */
+    pThis->pDevScsiPort = (PPDMISCSIPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_SCSI_PORT);
+    AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
+
+    /* Create request queue. */
+    rc = RTReqCreateQueue(&pThis->pQueueRequests);
+    AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create request queue rc=%Rrc\n"), rc);
+
+    /* Open the device. */
+    rc = CFGMR3QueryStringAlloc(pCfgHandle, "DevicePath", &pThis->pszDevicePath);
+    if (RT_FAILURE(rc))
+        return PDMDRV_SET_ERROR(pDrvIns, rc,
+                                N_("Configuration error: Failed to get the \"DevicePath\" value"));
+
+    rc = RTFileOpen(&pThis->DeviceFile, pThis->pszDevicePath, RTFILE_O_OPEN | RTFILE_O_READWRITE);
+    if (RT_FAILURE(rc))
+        return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+                                   N_("DrvSCSIHost#%d: Failed to open device '%s'"), pDrvIns->iInstance, pThis->pszDevicePath);
+
+    /* Create I/O thread. */
+    rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pThis->pAsyncIOThread, pThis, drvscsihostAsyncIOLoop,
+                                  drvscsihostAsyncIOLoopWakeup, 0, RTTHREADTYPE_IO, "SCSI async IO");
+    AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create async I/O thread rc=%Rrc\n"), rc);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * SCSI driver registration record.
+ */
+const PDMDRVREG g_DrvSCSIHost =
+{
+    /* u32Version */
+    PDM_DRVREG_VERSION,
+    /* szDriverName */
+    "SCSIHost",
+    /* pszDescription */
+    "Host SCSI driver.",
+    /* fFlags */
+    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+    /* fClass. */
+    PDM_DRVREG_CLASS_SCSI,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(DRVSCSIHOST),
+    /* pfnConstruct */
+    drvscsihostConstruct,
+    /* pfnDestruct */
+    drvscsihostDestruct,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    NULL,
+    /* pfnSuspend */
+    NULL,
+    /* pfnResume */
+    NULL,
+    /* pfnDetach */
+    NULL
+};
Index: trunk/src/VBox/Devices/Storage/VBoxSCSI.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VBoxSCSI.cpp	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/VBoxSCSI.cpp	(revision 21321)
@@ -0,0 +1,359 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * Simple SCSI interface for BIOS access
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC /* @todo: Create extra group. */
+
+#if defined(IN_R0) || defined(IN_RC)
+# error This device has no R0 or GC components
+#endif
+
+#include <VBox/pdmdev.h>
+#include <VBox/pgm.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+
+#include "VBoxSCSI.h"
+
+/**
+ * Initializes the state for the SCSI interface.
+ *
+ * @returns VBox status code.
+ * @param   pVBoxSCSI    Pointer to the unitialized SCSI state.
+ */
+int vboxscsiInitialize(PVBOXSCSI pVBoxSCSI)
+{
+    pVBoxSCSI->regIdentify = 0;
+    pVBoxSCSI->cbCDB       = 0;
+    memset(pVBoxSCSI->aCDB, 0, sizeof(pVBoxSCSI->aCDB));
+    pVBoxSCSI->iCDB        = 0;
+    pVBoxSCSI->fBusy       = false;
+    pVBoxSCSI->cbBuf       = 0;
+    pVBoxSCSI->iBuf        = 0;
+    pVBoxSCSI->pBuf        = NULL;
+    pVBoxSCSI->enmState    = VBOXSCSISTATE_NO_COMMAND;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Reads a register value.
+ *
+ * @returns VBox status code.
+ * @param   pVBoxSCSI    Pointer to the SCSI state.
+ * @param   iRegister    Index of the register to read.
+ * @param   pu32Value    Where to store the content of the register.
+ */
+int vboxscsiReadRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint32_t *pu32Value)
+{
+    uint8_t uVal = 0;
+
+    switch (iRegister)
+    {
+        case 0:
+        {
+            if (ASMAtomicReadBool(&pVBoxSCSI->fBusy) == true)
+            {
+                uVal |= VBOX_SCSI_BUSY;
+                /* There is an I/O operation in progress.
+                 * Yield the execution thread to let the I/O thread make progress.
+                 */
+                RTThreadYield();
+            }
+            else
+                uVal &= ~VBOX_SCSI_BUSY;
+            break;
+        }
+        case 1:
+        {
+            if (pVBoxSCSI->cbBuf > 0)
+            {
+                AssertMsg(pVBoxSCSI->pBuf, ("pBuf is NULL\n"));
+                uVal = pVBoxSCSI->pBuf[pVBoxSCSI->iBuf];
+                pVBoxSCSI->iBuf++;
+                pVBoxSCSI->cbBuf--;
+                if (pVBoxSCSI->cbBuf == 0)
+                {
+                    /** The guest read the last byte from the data in buffer.
+                     *  Clear everything and reset command buffer.
+                     */
+                    RTMemFree(pVBoxSCSI->pBuf);
+                    pVBoxSCSI->pBuf  = NULL;
+                    pVBoxSCSI->cbCDB = 0;
+                    pVBoxSCSI->iCDB  = 0;
+                    pVBoxSCSI->iBuf  = 0;
+                    pVBoxSCSI->uTargetDevice = 0;
+                    pVBoxSCSI->enmState = VBOXSCSISTATE_NO_COMMAND;
+                    memset(pVBoxSCSI->aCDB, 0, sizeof(pVBoxSCSI->aCDB));
+                }
+            }
+            break;
+        }
+        case 2:
+        {
+            uVal = pVBoxSCSI->regIdentify;
+            break;
+        }
+        default:
+            AssertMsgFailed(("Invalid register to read from %u\n", iRegister));
+    }
+
+    *pu32Value = uVal;
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Writes to a register.
+ *
+ * @returns VBox status code.
+ *          VERR_MORE_DATA if a command is ready to be sent to the SCSI driver.
+ * @param   pVBoxSCSI    Pointer to the SCSI state.
+ * @param   iRegister    Index of the register to write to.
+ * @param   uVal         Value to write.
+ */
+int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal)
+{
+    int rc = VINF_SUCCESS;
+
+    switch (iRegister)
+    {
+        case 0:
+        {
+            if (pVBoxSCSI->enmState == VBOXSCSISTATE_NO_COMMAND)
+            {
+                pVBoxSCSI->enmState = VBOXSCSISTATE_READ_TXDIR;
+                pVBoxSCSI->uTargetDevice = uVal;
+            }
+            else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_TXDIR)
+            {
+                pVBoxSCSI->enmState = VBOXSCSISTATE_READ_CDB_SIZE;
+                pVBoxSCSI->uTxDir = uVal;
+            }
+            else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_CDB_SIZE)
+            {
+                pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_LOW;
+                pVBoxSCSI->cbCDB = uVal;
+            }
+            else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_LOW)
+            {
+                pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_HIGH;
+                pVBoxSCSI->cbBuf = uVal;
+            }
+            else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_HIGH)
+            {
+                pVBoxSCSI->enmState = VBOXSCSISTATE_READ_COMMAND;
+                pVBoxSCSI->cbBuf |= (((uint16_t)uVal) << 8);
+            }
+            else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_COMMAND)
+            {
+                pVBoxSCSI->aCDB[pVBoxSCSI->iCDB] = uVal;
+                pVBoxSCSI->iCDB++;
+
+                /* Check if we have all neccessary command data. */
+                if (pVBoxSCSI->iCDB == pVBoxSCSI->cbCDB)
+                {
+                    Log(("%s: Command ready for processing\n", __FUNCTION__));
+                    pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY;
+                    if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
+                    {
+                        /* This is a write allocate buffer. */
+                        pVBoxSCSI->pBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
+                        if (!pVBoxSCSI->pBuf)
+                            return VERR_NO_MEMORY;
+                    }
+                    else
+                    {
+                        /* This is a read from the device. */
+                        ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
+                        rc = VERR_MORE_DATA; /* @todo: Better return value to indicate ready command? */
+                    }
+                }
+            }
+            else
+                AssertMsgFailed(("Invalid state %d\n", pVBoxSCSI->enmState));
+            break;
+        }
+        case 1:
+        {
+            AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state\n"));
+            pVBoxSCSI->pBuf[pVBoxSCSI->iBuf++] = uVal;
+            if (pVBoxSCSI->iBuf == pVBoxSCSI->cbBuf)
+            {
+                rc = VERR_MORE_DATA;
+                ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
+            }
+            break;
+        }
+        case 2:
+        {
+            pVBoxSCSI->regIdentify = uVal;
+            break;
+        }
+        default:
+            AssertMsgFailed(("Invalid register to write to %u\n", iRegister));
+    }
+
+    return rc;
+}
+
+/**
+ * Sets up a SCSI request which the owning SCSI device can process.
+ *
+ * @returns VBox status code.
+ * @param   pVBoxSCSI      Pointer to the SCSI state.
+ * @param   pScsiRequest   Pointer to a scsi request to setup.
+ * @param   puTargetDevice Where to store the target device ID.
+ */
+int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, uint32_t *puTargetDevice)
+{
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p puTargetDevice=%#p\n", pVBoxSCSI, pScsiRequest, puTargetDevice));
+
+    AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state %u\n", pVBoxSCSI->enmState));
+
+    if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
+    {
+        Assert(!pVBoxSCSI->pBuf);
+        pVBoxSCSI->pBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
+        if (!pVBoxSCSI->pBuf)
+            return VERR_NO_MEMORY;
+    }
+
+    /** Allocate scatter gather element. */
+    pScsiRequest->paScatterGatherHead = (PPDMDATASEG)RTMemAllocZ(sizeof(PDMDATASEG) * 1); /* Only one element. */
+    if (!pScsiRequest->paScatterGatherHead)
+    {
+        RTMemFree(pVBoxSCSI->pBuf);
+        pVBoxSCSI->pBuf = NULL;
+        return VERR_NO_MEMORY;
+    }
+
+    /** Allocate sense buffer. */
+    pScsiRequest->cbSenseBuffer = 18;
+    pScsiRequest->pbSenseBuffer = (uint8_t *)RTMemAllocZ(pScsiRequest->cbSenseBuffer);
+
+    pScsiRequest->cbCDB = pVBoxSCSI->cbCDB;
+    pScsiRequest->pbCDB = pVBoxSCSI->aCDB;
+    pScsiRequest->uLogicalUnit = 0;
+    pScsiRequest->cbScatterGather = pVBoxSCSI->cbBuf;
+    pScsiRequest->cScatterGatherEntries = 1;
+
+    pScsiRequest->paScatterGatherHead[0].cbSeg = pVBoxSCSI->cbBuf;
+    pScsiRequest->paScatterGatherHead[0].pvSeg = pVBoxSCSI->pBuf;
+
+    *puTargetDevice = pVBoxSCSI->uTargetDevice;
+
+    return rc;
+}
+
+/**
+ * Notifies the device that a request finished and the incoming data
+ * is ready at the incoming data port.
+ */
+int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest)
+{
+    LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p\n", pVBoxSCSI, pScsiRequest));
+    RTMemFree(pScsiRequest->paScatterGatherHead);
+    RTMemFree(pScsiRequest->pbSenseBuffer);
+
+    if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
+    {
+        if (pVBoxSCSI->pBuf)
+            RTMemFree(pVBoxSCSI->pBuf);
+        pVBoxSCSI->pBuf  = NULL;
+        pVBoxSCSI->cbBuf = 0;
+        pVBoxSCSI->cbCDB = 0;
+        pVBoxSCSI->iCDB  = 0;
+        pVBoxSCSI->iBuf  = 0;
+        pVBoxSCSI->uTargetDevice = 0;
+        pVBoxSCSI->enmState = VBOXSCSISTATE_NO_COMMAND;
+        memset(pVBoxSCSI->aCDB, 0, sizeof(pVBoxSCSI->aCDB));
+    }
+
+    ASMAtomicXchgBool(&pVBoxSCSI->fBusy, false);
+
+    return VINF_SUCCESS;
+}
+
+int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+                       RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    RTGCPTR  GCDst      = *pGCPtrDst;
+    uint32_t cbTransfer = *pcTransfer * cb;
+    int rc              = VINF_SUCCESS;
+
+    LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfer=%u cb=%u\n",
+                 pDevIns, pVBoxSCSI, iRegister, *pcTransfer, cb));
+
+    /* Read string only valid for data in register. */
+    AssertMsg(iRegister == 1, ("Hey only register 1 can be read from with string\n"));
+    Assert(pVBoxSCSI->pBuf);
+
+    rc = PGMPhysSimpleDirtyWriteGCPtr(PDMDevHlpGetVMCPU(pDevIns), GCDst, pVBoxSCSI->pBuf, cbTransfer);
+    AssertRC(rc);
+
+    *pGCPtrDst = (RTGCPTR)((RTGCUINTPTR)GCDst + cbTransfer);
+    *pcTransfer = 0;
+
+    RTMemFree(pVBoxSCSI->pBuf);
+    pVBoxSCSI->pBuf  = NULL;
+    pVBoxSCSI->cbBuf = 0;
+    pVBoxSCSI->cbCDB = 0;
+    pVBoxSCSI->iCDB  = 0;
+    pVBoxSCSI->iBuf  = 0;
+    pVBoxSCSI->uTargetDevice = 0;
+    pVBoxSCSI->enmState = VBOXSCSISTATE_NO_COMMAND;
+    memset(pVBoxSCSI->aCDB, 0, sizeof(pVBoxSCSI->aCDB));
+
+    return rc;
+}
+
+int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+                       RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+    RTGCPTR  GCSrc      = *pGCPtrSrc;
+    uint32_t cbTransfer = *pcTransfer * cb;
+    int rc              = VINF_SUCCESS;
+
+    /* Read string only valid for data in register. */
+    AssertMsg(iRegister == 1, ("Hey only register 1 can be read from with string\n"));
+    AssertMsg(cbTransfer == 512, ("Only 512 byte transfers are allowed\n"));
+
+    rc = PGMPhysSimpleReadGCPtr(PDMDevHlpGetVMCPU(pDevIns), pVBoxSCSI->pBuf, GCSrc, cbTransfer);
+    AssertRC(rc);
+
+    *pGCPtrSrc = (RTGCPTR)((RTGCUINTPTR)GCSrc + cbTransfer);
+    *pcTransfer = 0;
+
+    return VERR_MORE_DATA;
+}
+
+
+
Index: trunk/src/VBox/Devices/Storage/VBoxSCSI.h
===================================================================
--- trunk/src/VBox/Devices/Storage/VBoxSCSI.h	(revision 21321)
+++ trunk/src/VBox/Devices/Storage/VBoxSCSI.h	(revision 21321)
@@ -0,0 +1,132 @@
+/* $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * Simple SCSI interface for BIOS access
+ */
+
+/*
+ * Copyright (C) 2006-2009 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/**
+ * This is a simple interface to access SCSI devices from the BIOS
+ * which is shared between the BusLogic and the LsiLogic
+ * SCSI host adapters to simplify the BIOS part.
+ *
+ * The BusLogic interface if available will be starting at port 0x330
+ * and the LsiLogic starts at 0x340 and each will have a size of 3 ports.
+ * The ports are used as described below:
+ *
+ * +--------+--------+----------+
+ * | Offset | Access | Purpose  |
+ * +--------+--------+----------+
+ * |   0    |  Write | Command  |
+ * +--------+--------+----------+
+ * |   0    |  Read  | Status   |
+ * +--------+--------+----------+
+ * |   1    |  Write | Data in  |
+ * +--------+--------+----------+
+ * |   1    |  Read  | Data out |
+ * +--------+--------+----------+
+ * |   2    |  R/W   | Detect   |
+ * +--------+--------+----------+
+ *
+ * The register at port 0 receives the SCSI CDB issued from the driver when writing to it but
+ * before writing the actual CDB the first write gives the size of the CDB in bytes.
+ *
+ * Reading the port at offset 0 gives status information about the adapter.
+ * If the busy bit is set the adapter is processing a previous issued request if it is
+ * cleared the command finished and the adapter can process another request.
+ * The driver has to poll this bit because the adapter will not assert an IRQ for simplicity reasons.
+ *
+ * The register at offset 2 is to detect if a host adapter is available
+ * If the driver writes a value to this port and gets the same value after reading it
+ * again the adapter is available.
+ *
+ * This part has no R0 or GC components.
+ */
+
+#ifndef ___Storage_VBoxSCSI_h
+#define ___Storage_VBoxSCSI_h
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+//#define DEBUG
+#include <VBox/pdmdev.h>
+
+typedef enum VBOXSCSISTATE
+{
+    VBOXSCSISTATE_NO_COMMAND            = 0x00,
+    VBOXSCSISTATE_READ_TXDIR            = 0x01,
+    VBOXSCSISTATE_READ_CDB_SIZE         = 0x02,
+    VBOXSCSISTATE_READ_BUFFER_SIZE_LOW  = 0x03,
+    VBOXSCSISTATE_READ_BUFFER_SIZE_HIGH = 0x04,
+    VBOXSCSISTATE_READ_COMMAND          = 0x05,
+    VBOXSCSISTATE_COMMAND_READY         = 0x06
+} VBOXSCSISTATE;
+
+#define VBOXSCSI_TXDIR_FROM_DEVICE 0
+#define VBOXSCSI_TXDIR_TO_DEVICE   1
+
+typedef struct VBOXSCSI
+{
+    /** The identify register. */
+    uint8_t              regIdentify;
+    /** The target device. */
+    uint8_t              uTargetDevice;
+    /** Transfer direction. */
+    uint8_t              uTxDir;
+    /** The size of the CDB we are issuing. */
+    uint8_t              cbCDB;
+    /** The command to issue. */
+    uint8_t              aCDB[12];
+    /** Current position in the array. */
+    uint8_t              iCDB;
+
+#if HC_ARCH_BITS == 64
+    uint32_t             Alignment0;
+#endif
+
+    /** Pointer to the buffer holding the data. */
+    R3PTRTYPE(uint8_t *) pBuf;
+    /** Size of the buffer in bytes. */
+    uint32_t             cbBuf;
+    /** Current position in the buffer. */
+    uint32_t             iBuf;
+    /** Flag whether a request is pending. */
+    volatile bool        fBusy;
+    /** The state we are in when fetching a command from the BIOS. */
+    VBOXSCSISTATE        enmState;
+} VBOXSCSI, *PVBOXSCSI;
+
+#define VBOX_SCSI_BUSY RT_BIT(0)
+
+#ifdef IN_RING3
+RT_C_DECLS_BEGIN
+int vboxscsiInitialize(PVBOXSCSI pVBoxSCSI);
+int vboxscsiReadRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint32_t *pu32Value);
+int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal);
+int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, uint32_t *puTargetDevice);
+int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest);
+int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+                        RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb);
+int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+                       RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb);
+RT_C_DECLS_END
+#endif
+
+#endif /* ___Storage_VBoxSCSI_h */
