diff --git a/Makefile.target b/Makefile.target
index 49ba08d..cf0cb27 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -327,7 +327,7 @@ obj-y += e1000.o
 obj-y += wdt_ib700.o wdt_i6300esb.o
 
 # Hardware support
-obj-i386-y = ide.o pckbd.o vga.o $(sound-obj-y) dma.o
+obj-i386-y = ide.o atapi-pt.o atapi-data.o pckbd.o vga.o $(sound-obj-y) dma.o
 obj-i386-y += fdc.o mc146818rtc.o serial.o i8259.o i8254.o pcspk.o pc.o
 obj-i386-y += cirrus_vga.o apic.o ioapic.o parallel.o acpi.o piix_pci.o
 obj-i386-y += usb-uhci.o vmmouse.o vmport.o vmware_vga.o hpet.o
@@ -338,7 +338,8 @@ CPPFLAGS += -DHAS_AUDIO -DHAS_AUDIO_CHOICE
 endif
 
 # shared objects
-obj-ppc-y = ppc.o ide.o vga.o $(sound-obj-y) dma.o openpic.o
+obj-ppc-y = ppc.o ide.o atapi-pt.o atapi-data.o
+obj-ppc-y += vga.o $(sound-obj-y) dma.o openpic.o
 # PREP target
 obj-ppc-y += pckbd.o serial.o i8259.o i8254.o fdc.o mc146818rtc.o
 obj-ppc-y += prep_pci.o ppc_prep.o
@@ -365,7 +366,8 @@ LIBS+= $(FDT_LIBS)
 obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o
 obj-mips-y += mips_timer.o mips_int.o dma.o vga.o serial.o i8254.o i8259.o rc4030.o
 obj-mips-y += g364fb.o jazz_led.o dp8393x.o
-obj-mips-y += ide.o gt64xxx.o pckbd.o fdc.o mc146818rtc.o usb-uhci.o acpi.o ds1225y.o
+obj-mips-y += ide.o  atapi-pt.o atapi-data.o gt64xxx.o
+obj-mips-y += pckbd.o fdc.o mc146818rtc.o usb-uhci.o acpi.o ds1225y.o
 obj-mips-y += piix_pci.o parallel.o cirrus_vga.o pcspk.o $(sound-obj-y)
 obj-mips-y += mipsnet.o
 obj-mips-y += pflash_cfi01.o
@@ -401,7 +403,7 @@ obj-cris-y += etraxfs_ser.o
 obj-cris-y += pflash_cfi02.o
 
 ifeq ($(TARGET_ARCH), sparc64)
-obj-sparc-y = sun4u.o ide.o pckbd.o vga.o apb_pci.o
+obj-sparc-y = sun4u.o ide.o atapi-pt.o atapi-data.o pckbd.o vga.o apb_pci.o
 obj-sparc-y += fdc.o mc146818rtc.o serial.o
 obj-sparc-y += cirrus_vga.o parallel.o
 else
@@ -420,7 +422,7 @@ obj-arm-y += arm-semi.o
 obj-arm-y += pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o
 obj-arm-y += pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o
 obj-arm-y += pflash_cfi01.o gumstix.o
-obj-arm-y += zaurus.o ide.o serial.o spitz.o tosa.o tc6393xb.o
+obj-arm-y += zaurus.o ide.o atapi-pt.o atapi-data.o serial.o spitz.o tosa.o tc6393xb.o
 obj-arm-y += omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o
 obj-arm-y += omap2.o omap_dss.o soc_dma.o
 obj-arm-y += omap_sx1.o palm.o tsc210x.o
@@ -438,7 +440,7 @@ endif
 
 obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o
 obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o serial.o
-obj-sh4-y += ide.o
+obj-sh4-y += ide.o atapi-pt.o atapi-data.o
 
 obj-m68k-y = an5206.o mcf5206.o mcf_uart.o mcf_intc.o mcf5208.o mcf_fec.o
 obj-m68k-y += m68k-semi.o dummy_m68k.o
diff --git a/hw/atapi-pt.c b/hw/atapi-pt.c
new file mode 100644
index 0000000..abb5c26
--- /dev/null
+++ b/hw/atapi-pt.c
@@ -0,0 +1,796 @@
+/*
+ * ATAPI pass through implementation
+ *
+ * Copyright (c) 2009 Alexandre Bique
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "block.h"
+#include "atapi-defines.h"
+#include "atapi-pt.h"
+
+int atapi_pt_allow_fw_upgrade = 0;
+
+#define DEBUG_IDE_ATAPI_PT
+
+#ifdef DEBUG_IDE_ATAPI_PT
+# define DPRINTF(Args...) printf(Args)
+# define CHECK_EQUAL(Val1, Val2)                                        \
+    do {                                                                \
+        if ((Val1) != (Val2))                                           \
+            printf("[\e[1;32m!VALUE\e[m] %s:%d, %s=%d %s=%d\n",         \
+                   __PRETTY_FUNCTION__, __LINE__, #Val1, (Val1),        \
+                   #Val2, (Val2));                                      \
+    } while (0)
+#else
+# define DPRINTF(Args...)
+# define CHECK_EQUAL(Val1, Val2)
+#endif /* DEBUG_IDE_ATAPI_PT */
+
+static inline uint32_t msf_to_frames(uint32_t minutes,
+                                     uint32_t seconds,
+                                     uint32_t frames)
+{
+    return (minutes * CD_SECS + seconds) * CD_FRAMES + frames;
+}
+
+static void ide_atapi_pt_set_error(IDEState *s, int sense_key, int asc, int error)
+{
+    s->atapi_pt.sense.sense_key  = sense_key;
+    s->atapi_pt.sense.asc        = asc;
+    s->atapi_pt.sense.error_code = error;
+    s->status  = READY_STAT | ERR_STAT;
+    s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+    ide_set_irq(s);
+}
+
+static void ide_atapi_pt_error(IDEState *s)
+{
+    s->status  = READY_STAT | ERR_STAT;
+    s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+    ide_set_irq(s);
+}
+
+static void ide_atapi_pt_sg_io_finished(void *opaque, int ret)
+{
+    IDEState *s = opaque;
+
+    if (ret) {
+        DPRINTF("IO error\n");
+        ide_atapi_pt_error(s);
+        return;
+    }
+
+    if (s->atapi_pt.cmd.driver_status ||
+        s->atapi_pt.cmd.host_status ||
+        s->atapi_pt.cmd.status)
+    {
+        DPRINTF("[\e[1;31mERROR\e[m]\n"
+                "\tsense_key: 0x%02x (\e[0;35m%s\e[m)\n"
+                "\terror: 0x%02x\n"
+                "\tasc: 0x%02x, 0x%x (\e[0;35m%s\e[m)\n"
+                "\terrno: %d (%s)\n"
+                "\tdriver: %d, host: %d, status: %d\n",
+                s->atapi_pt.sense.sense_key,
+                sense_key_texts[s->atapi_pt.sense.sense_key],
+                s->atapi_pt.sense.error_code,
+                s->atapi_pt.sense.asc,
+                s->atapi_pt.sense.ascq,
+                atapi_ascq_to_str(s->atapi_pt.sense.ascq),
+                errno,
+                strerror(errno) ? : "(null)",
+                s->atapi_pt.cmd.driver_status,
+                s->atapi_pt.cmd.host_status,
+                s->atapi_pt.cmd.status);
+        ide_atapi_pt_error(s);
+        return;
+    }
+    s->atapi_pt.cmd_sent(s);
+}
+
+static void ide_atapi_pt_send_packet(IDEState *s)
+{
+    DPRINTF("[ATAPI-PT] sending command: 0x%02x (\e[0;32m%s\e[m)\n",
+            s->atapi_pt.request[0], atapi_cmd_to_str(s->atapi_pt.request[0]));
+    bdrv_aio_ioctl(s->bs, SG_IO, &s->atapi_pt.cmd,
+                   ide_atapi_pt_sg_io_finished, s);
+}
+
+static void ide_atapi_pt_read_finish(IDEState *s)
+{
+    s->atapi_pt.cmd.dxferp = s->io_buffer;
+    s->atapi_pt.cmd_sent = ide_atapi_cmd_ok;
+    ide_atapi_pt_send_packet(s);
+}
+
+static void ide_atapi_pt_read_pio_end(IDEState *s)
+{
+    ide_transfer_stop(s);
+    ide_atapi_pt_read_finish(s);
+}
+
+static void ide_atapi_pt_read_dma_cb(void *opaque, int ret)
+{
+    BMDMAState *bm = opaque;
+    IDEState *s = bm->ide_if;
+    int i = 0;
+
+    if (ret < 0) {
+        ide_atapi_io_error(s, ret);
+        return;
+    }
+
+    i = dma_buf_rw(bm, 0);
+    ide_atapi_pt_read_finish(s);
+}
+
+static void ide_atapi_pt_wcmd(IDEState *s)
+{
+    if (s->atapi_dma)
+    {
+        /* DMA */
+        s->io_buffer_index = 0;
+        s->io_buffer_size = s->atapi_pt.cmd.dxfer_len;
+        ide_dma_start(s, ide_atapi_pt_read_dma_cb);
+        return;
+    }
+
+    /* PIO */
+    s->packet_transfer_size = s->atapi_pt.cmd.dxfer_len;
+    s->io_buffer_size = 0;
+    s->elementary_transfer_size = 0;
+    s->io_buffer_index = 0;
+    s->status |= DRQ_STAT;
+    s->status &= ~BUSY_STAT;
+    s->nsector = (s->nsector & ~7) &
+        ~ATAPI_INT_REASON_IO &
+        ~ATAPI_INT_REASON_CD;
+    ide_transfer_start(s, s->io_buffer, s->atapi_pt.cmd.dxfer_len,
+                       ide_atapi_pt_read_pio_end);
+    ide_set_irq(s);
+    return;
+}
+
+static void ide_atapi_pt_read_format_capacities_sent(IDEState *s)
+{
+    int size = (s->io_buffer[3] << 3) + 4;
+    ide_atapi_cmd_reply(s, size, s->atapi_pt.cmd.dxfer_len);
+}
+
+static void ide_atapi_pt_standard_reply(IDEState *s)
+{
+    uint32_t size = s->atapi_pt.reply_size_init;
+
+    switch (s->atapi_pt.reply_size_len)
+    {
+    case 0:
+        break;
+    case 1:
+        size += s->io_buffer[s->atapi_pt.reply_size_offset];
+        break;
+    case 2:
+        size += ube16_to_cpu(s->io_buffer + s->atapi_pt.reply_size_offset);
+        break;
+    case 3:
+        size += ube24_to_cpu(s->io_buffer + s->atapi_pt.reply_size_offset);
+        break;
+    case 4:
+        size += ube32_to_cpu(s->io_buffer + s->atapi_pt.reply_size_offset);
+        break;
+    default:
+        fprintf(stderr,
+                "The imposible has happened!!! We received a reply with a %d "
+                "bytes length field. Please inform "
+                "Alexandre Bique <bique.alexandre@gmail.com>.",
+                s->atapi_pt.reply_size_len);
+        assert(0);
+        break;
+    }
+    DPRINTF("[reply] size: %d, resid: %d, max_in:%d\n",
+            size, s->atapi_pt.cmd.resid, s->atapi_pt.cmd.dxfer_len);
+    ide_atapi_cmd_reply(s, size, s->atapi_pt.cmd.dxfer_len);
+}
+
+/* This data comes from the "Mt. Fuji Commands for
+ * Multimedia Devices SFF8090i v4" page 343. */
+static int ide_atapi_pt_read_cd_block_size(const uint8_t *io_buffer)
+{
+    int sector_type = (io_buffer[2] >> 2) & 7;
+    int error_flags = (io_buffer[9] >> 1) & 3;
+    int flags_bits = io_buffer[9] & ~7;
+    int block_size = 0;
+
+    // expected sector type
+    switch (sector_type)
+    {
+    case 0: // Any type
+    case 1: // CD-DA
+        block_size = (flags_bits) ? 2352 : 0;
+        break;
+
+    case 2: // Mode 1
+        switch (flags_bits)
+        {
+        case 0x0: block_size = 0; break;
+        case 0x10:
+        case 0x50: block_size = 2048; break;
+        case 0x18:
+        case 0x58: block_size = 2336; break;
+        case 0x20:
+        case 0x60: block_size = 4; break;
+        case 0x30:
+        case 0x70:
+        case 0x78: block_size = 2052; break;
+        case 0x38: block_size = 2340; break;
+        case 0x40: block_size = 0; break;
+        case 0xa0: block_size = 16; break;
+        case 0xb0: block_size = 2064; break;
+        case 0xb8: block_size = 2352; break;
+        case 0xe0: block_size = 16; break;
+        case 0xf0: block_size = 2064; break;
+        case 0xf8: block_size = 2352; break;
+
+        default: return 0; // illegal
+        }
+        break;
+
+    case 3: // Mode 2
+        switch (flags_bits)
+        {
+        case 0x0: block_size = 0; break;
+        case 0x10:
+        case 0x50:
+        case 0x18:
+        case 0x58: block_size = 2336; break;
+        case 0x20:
+        case 0x60: block_size = 4; break;
+        case 0x30:
+        case 0x70:
+        case 0x78:
+        case 0x38: block_size = 2340; break;
+        case 0x40: block_size = 0; break;
+        case 0xa0: block_size = 16; break;
+        case 0xb0:
+        case 0xb8: block_size = 2352; break;
+        case 0xe0: block_size = 16; break;
+        case 0xf0:
+        case 0xf8: block_size = 2352; break;
+        default: return 0; // illegal
+        }
+        break;
+
+    case 4: // Mode 2 Form 1
+        switch (flags_bits)
+        {
+        case 0x0: block_size = 0; break;
+        case 0x10: block_size = 2048; break;
+        case 0x18: block_size = 2328; break;
+        case 0x20: block_size = 4; break;
+        case 0x40: block_size = 8; break;
+        case 0x50: block_size = 2056; break;
+        case 0x58: block_size = 2336; break;
+        case 0x60: block_size = 12; break;
+        case 0x70: block_size = 2060; break;
+        case 0x78: block_size = 2340; break;
+        case 0xa0: block_size = 16; break;
+        case 0xe0: block_size = 24; break;
+        case 0xf0: block_size = 2072; break;
+        case 0xf8: block_size = 2352; break;
+        default: return 0; // illegal
+        }
+        break;
+
+    case 5: // Mode 2 Form 2
+        switch (flags_bits)
+        {
+        case 0x0: block_size = 0; break;
+        case 0x10:
+        case 0x18: block_size = 2328; break;
+        case 0x20: block_size = 4; break;
+        case 0x40: block_size = 8; break;
+        case 0x50:
+        case 0x58: block_size = 2336; break;
+        case 0x60: block_size = 12; break;
+        case 0x70:
+        case 0x78: block_size = 2340; break;
+        case 0xa0: block_size = 16; break;
+        case 0xe0: block_size = 24; break;
+        case 0xf0:
+        case 0xf8: block_size = 2352; break;
+        default: return 0; // illegal
+        }
+        break;
+
+    default:
+        return 0; // illegal
+    }
+
+    switch (error_flags)
+    {
+    case 1: block_size += 294; break;
+    case 2: block_size += 296; break;
+    }
+
+    return block_size;
+}
+
+void ide_atapi_pt_cmd(IDEState *s)
+{
+    struct sg_io_hdr *cmd = &s->atapi_pt.cmd;
+
+    memcpy(s->atapi_pt.request, s->io_buffer, ATAPI_PACKET_SIZE);
+    cmd->interface_id    = 'S';
+    cmd->dxfer_direction = SG_DXFER_NONE;
+    cmd->cmd_len         = ATAPI_PACKET_SIZE;
+    cmd->mx_sb_len       = sizeof (s->atapi_pt.sense);
+    cmd->dxfer_len       = 0;
+    cmd->iovec_count     = 0;
+    cmd->dxferp          = s->io_buffer;
+    cmd->cmdp            = s->atapi_pt.request;
+    cmd->sbp             = (unsigned char *)&s->atapi_pt.sense;
+    cmd->timeout         = 0xffffff; // 15 seconds
+
+    s->status                    |= BUSY_STAT;
+    s->atapi_pt.reply_size_init   = 0;
+    s->atapi_pt.reply_size_offset = 0;
+    s->atapi_pt.reply_size_len    = 0;
+
+    switch (s->io_buffer[0])
+    {
+        /*******************/
+        /* SIMPLE COMMANDS */
+        /*******************/
+
+    case GPCMD_BLANK: // bigger timeout while blanking
+        cmd->timeout = 1000 * 60 * 80; // 80 mins
+        goto simple_cmd;
+    case GPCMD_CLOSE_TRACK:
+        cmd->timeout = 1000 * 60 * 5; // 5 mins
+        goto simple_cmd;
+    case GPCMD_FLUSH_CACHE: // also called SYNCHRONIZE_CACHE
+    case GPCMD_LOAD_UNLOAD:
+    case GPCMD_PAUSE_RESUME:
+    case GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
+    case GPCMD_REPAIR_RZONE_TRACK:
+    case GPCMD_RESERVE_RZONE_TRACK:
+    case GPCMD_SCAN:
+    case GPCMD_SEEK:
+    case GPCMD_SET_READ_AHEAD:
+    case GPCMD_START_STOP_UNIT:
+    case GPCMD_STOP_PLAY_SCAN:
+    case GPCMD_TEST_UNIT_READY:
+    case GPCMD_VERIFY_10:
+    case GPCMD_SET_SPEED: /* FIXME: find the documentation */
+    simple_cmd:
+        CHECK_EQUAL(s->lcyl, 0);
+        CHECK_EQUAL(s->hcyl, 0);
+        cmd->dxfer_direction = SG_DXFER_NONE;
+        s->atapi_pt.cmd_sent = ide_atapi_cmd_ok;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+        /******************/
+        /* WRITE COMMANDS */
+        /******************/
+
+    case GPCMD_WRITE_10:
+    case GPCMD_WRITE_AND_VERIFY_10:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7) * CD_FRAMESIZE;
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_WRITE_12:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube32_to_cpu(s->io_buffer + 6);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_WRITE_BUFFER:
+    {
+        int32_t parameter_list_length = ube24_to_cpu(s->io_buffer + 3);
+        int8_t mode = s->io_buffer[1] & 0x03;
+
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        switch (mode)
+        {
+        case 0x0: // Combined header and data mode
+            // The documentation is confusing because it says that parameter
+            // list length contains all the data, but the buffer should be
+            // greater than parameter list length + 4...
+            cmd->dxfer_len = parameter_list_length + 4;
+            break;
+        case 0x2: // Data mode
+            cmd->dxfer_len = parameter_list_length;
+            break;
+        case 0x1: // Vendor specific
+        case 0x4: // Download microcode
+        case 0x5: // Download microcode and save mode
+        case 0x6: // Download microcode with offsets
+        case 0x7: // Download microcode with offsets and save mode
+        default:
+            if (!atapi_pt_allow_fw_upgrade)
+                goto illegal_request;
+            cmd->dxfer_len = parameter_list_length;
+            break;
+        }
+
+        ide_atapi_pt_wcmd(s);
+        return;
+    }
+
+    case GPCMD_SEND_CUE_SHEET:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube24_to_cpu(s->io_buffer + 6);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_MODE_SELECT_10:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        CHECK_EQUAL(s->lcyl | (s->hcyl << 8), cmd->dxfer_len);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SEND_KEY:
+    case GPCMD_SEND_EVENT:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 8);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        CHECK_EQUAL(s->lcyl | (s->hcyl << 8), cmd->dxfer_len);
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SEND_OPC:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7) << 3;
+        CHECK_EQUAL(s->lcyl | (s->hcyl << 8), cmd->dxfer_len);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SET_STREAMING:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 9);
+        if (cmd->dxfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_FORMAT_UNIT:
+        cmd->dxfer_direction = SG_DXFER_TO_DEV;
+        cmd->dxfer_len = 12;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+        /*****************/
+        /* READ COMMANDS */
+        /*****************/
+
+    case GPCMD_INQUIRY:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = s->io_buffer[4];
+        s->atapi_pt.reply_size_init = 5;
+        s->atapi_pt.reply_size_offset = 4;
+        s->atapi_pt.reply_size_len = 1;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_REQUEST_SENSE:
+    {
+        // send the previous sense command
+        DPRINTF("=== REQUEST SENSE ===\n"
+                "atapi_cmd_error: sense=0x%x asc=0x%x error=0x%x\n",
+                s->atapi_pt.sense.sense_key,
+                s->atapi_pt.sense.asc,
+                s->atapi_pt.sense.error_code);
+
+        int max_size = s->io_buffer[4];
+
+        int size = 8 + s->atapi_pt.sense.add_sense_len;
+
+        DPRINTF("max_size: %d, add_sense_len: %d, sizeof: %lu\n",
+                max_size, s->atapi_pt.sense.add_sense_len,
+                sizeof (s->atapi_pt.sense));
+        memcpy(s->io_buffer, &s->atapi_pt.sense, sizeof (s->atapi_pt.sense));
+        ide_atapi_cmd_reply(s, size, max_size);
+        return;
+    }
+
+    case GPCMD_READ_DVD_STRUCTURE:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 8);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 4;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_HEADER:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_MECHANISM_STATUS:
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 8);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_offset = 6;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_REPORT_KEY:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 8);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_init = 2;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_BUFFER_CAPACITY:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_init = 2;
+        return;
+
+    case GPCMD_GET_PERFORMANCE:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = 8 + 8 * ube16_to_cpu(s->io_buffer + 8);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 4;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_10:
+    case GPCMD_READ_12:
+    {
+        int blocksize = 0, nbblocks;
+
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        switch (s->io_buffer[0]) {
+        case GPCMD_READ_10:
+            blocksize = CD_FRAMESIZE;
+            nbblocks = ube16_to_cpu(s->io_buffer + 7);
+            break;
+        case GPCMD_READ_12:
+            blocksize = CD_FRAMESIZE_RAW0;
+            nbblocks = ube32_to_cpu(s->io_buffer + 6);
+            break;
+        default:
+            assert(0);
+            break;
+        }
+        cmd->dxfer_len = nbblocks * blocksize;
+        CHECK_EQUAL(cmd->dxfer_len, (s->hcyl << 8) | s->lcyl);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+    }
+
+    case GPCMD_READ_BUFFER:
+        // TODO check this one is correct
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube24_to_cpu(s->io_buffer + 6);
+
+        switch (s->io_buffer[1] & 0x7)
+        {
+        case 0: // data with header
+            s->atapi_pt.reply_size_init = 4;
+            s->atapi_pt.reply_size_len = 3;
+            s->atapi_pt.reply_size_offset = 1;
+            break;
+
+        case 2: // data only
+            s->atapi_pt.reply_size_init = cmd->dxfer_len;
+            break;
+
+        case 3: // header only
+            s->atapi_pt.reply_size_init = 4;
+            break;
+
+        case 1: // vendor specific
+        default:
+            goto illegal_request;
+        }
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_CDVD_CAPACITY:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = 8;
+        CHECK_EQUAL(s->lcyl | (s->hcyl << 8), cmd->dxfer_len);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_MODE_SENSE_10:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        CHECK_EQUAL(s->lcyl | (s->hcyl << 8), cmd->dxfer_len);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_init = 2;
+        //s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_GET_EVENT_STATUS_NOTIFICATION:
+    case GPCMD_READ_DISC_INFO:
+    case GPCMD_READ_TOC_PMA_ATIP:
+    case GPCMD_READ_TRACK_RZONE_INFO:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_init = 2;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_SUBCHANNEL:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_offset = 2;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_READ_CD:
+    {
+        // command fields
+        int block_count = ((s->io_buffer[6] << 16) |
+                           ube16_to_cpu(s->io_buffer + 7));
+        int block_size = ide_atapi_pt_read_cd_block_size(s->io_buffer);
+
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = block_count * block_size;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+    }
+
+    case GPCMD_READ_CD_MSF:
+    {
+        // command fields
+        int starting_frame =
+            msf_to_frames(s->io_buffer[3], s->io_buffer[4], s->io_buffer[5]);
+        int ending_frame =
+            msf_to_frames(s->io_buffer[6], s->io_buffer[7], s->io_buffer[8]);
+        int block_count = ending_frame - starting_frame;
+        int block_size = ide_atapi_pt_read_cd_block_size(s->io_buffer);
+
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = block_count * block_size;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+    }
+
+    case GPCMD_PLAY_AUDIO_10:
+    {
+        int block_count = ube16_to_cpu(s->io_buffer + 7);
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = block_count * CD_FRAMESIZE;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+    }
+
+    case GPCMD_PLAY_AUDIO_MSF:
+    {
+        int starting_frame =
+            msf_to_frames(s->io_buffer[3], s->io_buffer[4], s->io_buffer[5]);
+        int ending_frame =
+            msf_to_frames(s->io_buffer[6], s->io_buffer[7], s->io_buffer[8]);
+        int block_count = ending_frame - starting_frame;
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = block_count * CD_FRAMESIZE;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->dxfer_len;
+        ide_atapi_pt_send_packet(s);
+        return;
+    }
+
+    case GPCMD_READ_FORMAT_CAPACITIES:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_read_format_capacities_sent;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_GET_CONFIGURATION:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 7);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = 4;
+        s->atapi_pt.reply_size_len = 4;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case GPCMD_SEND_DVD_STRUCTURE:
+        cmd->dxfer_direction = SG_DXFER_FROM_DEV;
+        cmd->dxfer_len = ube16_to_cpu(s->io_buffer + 8);
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = 2;
+        s->atapi_pt.reply_size_len = 2;
+        ide_atapi_pt_send_packet(s);
+        return;
+
+    case 0x01: // GPMODE_R_W_ERROR_PAGE ?
+    case 0x1a: // GPMODE_POWER_PAGE ?
+    case 0xfa:
+    case 0xfd:
+    case 0xf2:
+    case 0xf3: // WIN_SECURITY_ERASE_PREPARE ?
+    case 0xee: // WIN_IDENTIFY_DMA ?
+    case 0xdf: // WIN_DOORUNLOCK ?
+        DPRINTF("[\e[3;31mILLEGAL?\e[m] 0x%02x, size: %d\n",
+                s->io_buffer[0], s->lcyl | (s->hcyl << 8));
+    illegal_request:
+        ide_atapi_pt_set_error(s, SENSE_ILLEGAL_REQUEST,
+                               ASC_ILLEGAL_OPCODE, 0x70);
+        return;
+
+    default:
+        fprintf(stderr, "[ATAPI-PT] We got an unhandled command: 0x%02x. "
+                "Please report.\n", s->io_buffer[0]);
+        exit(1);
+        return;
+    }
+}
+
+void ide_atapi_pt_identify(IDEState *s)
+{
+    if (s->identify_set) {
+	memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data));
+	return;
+    }
+
+    if (bdrv_ioctl(s->bs, HDIO_GET_IDENTITY, s->io_buffer)) {
+        ide_atapi_identify(s);
+        perror("atapi");
+        exit(1);
+        return;
+    }
+
+    memcpy(s->identify_data, s->io_buffer, sizeof(s->identify_data));
+    s->identify_set = 1;
+}
diff --git a/hw/atapi-pt.h b/hw/atapi-pt.h
new file mode 100644
index 0000000..639270e
--- /dev/null
+++ b/hw/atapi-pt.h
@@ -0,0 +1,35 @@
+/*
+ * ATAPI pass through declarations
+ *
+ * Copyright (c) 2009 Alexandre Bique
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef ATAPI_PT_H
+# define ATAPI_PT_H
+
+# include "ide.h"
+
+extern int atapi_pt_allow_fw_upgrade;
+
+void ide_atapi_pt_cmd(IDEState * s);
+void ide_atapi_pt_identify(IDEState * s);
+
+#endif /* !ATAPI_PT_H */
diff --git a/hw/ide.c b/hw/ide.c
index 9db9cb2..382b05e 100644
--- a/hw/ide.c
+++ b/hw/ide.c
@@ -22,21 +22,8 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#include "hw.h"
-#include "pc.h"
-#include "pci.h"
-#include "scsi-disk.h"
-#include "pcmcia.h"
-#include "block.h"
-#include "block_int.h"
-#include "qemu-timer.h"
-#include "sysemu.h"
-#include "ppc_mac.h"
-#include "mac_dbdma.h"
-#include "sh.h"
-#include "dma.h"
 #include "ide.h"
-#include "atapi-defines.h"
+#include "atapi-pt.h"
 
 /* debug IDE devices */
 //#define DEBUG_IDE
@@ -44,6 +31,7 @@
 //#define DEBUG_AIO
 #define USE_DMA_CDROM
 
+
 /* XXX: DVDs that could fit on a CD will be reported as a CD */
 static inline int media_present(IDEState *s)
 {
@@ -62,7 +50,6 @@ static inline int media_is_cd(IDEState *s)
 
 static void ide_dma_restart(IDEState *s);
 static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret);
-static void ide_atapi_cmd_error(IDEState *s, int sense_key, int asc);
 
 static void padstr(char *str, const char *src, int len)
 {
@@ -1977,7 +1964,7 @@ static void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
             /* ATAPI commands */
         case WIN_PIDENTIFY:
             if (s->is_cdrom) {
-                ide_atapi_identify(s);
+                s->atapi_identify(s);
                 s->status = READY_STAT | SEEK_STAT;
                 ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
             } else {
@@ -2015,7 +2002,7 @@ static void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
             s->atapi_dma = s->feature & 1;
             s->nsector = 1;
             ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE,
-                               ide_atapi_cmd);
+                               s->atapi_cmd);
             break;
         /* CF-ATA commands */
         case CFA_REQ_EXT_ERROR_CODE:
@@ -2354,6 +2341,16 @@ static void ide_init2(IDEState *ide_state,
 
             if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) {
                 s->is_cdrom = 1;
+                if (!bdrv_is_sg(s->bs)) {
+                    s->atapi_cmd = ide_atapi_cmd;
+                    s->atapi_identify = ide_atapi_identify;
+                }
+#if CONFIG_ATAPI_PT
+                else {
+                    s->atapi_cmd = ide_atapi_pt_cmd;
+                    s->atapi_identify = ide_atapi_pt_identify;
+                }
+#endif /* CONFIG_ATAPI_PT */
 		bdrv_set_change_cb(s->bs, cdrom_change_cb, s);
             }
         }
diff --git a/hw/ide.h b/hw/ide.h
index 064e2d3..58c74b9 100644
--- a/hw/ide.h
+++ b/hw/ide.h
@@ -40,7 +40,20 @@
 #include "dma.h"
 #include "atapi-defines.h"
 
+
 #include <stdint.h>
+#include <limits.h>
+#include <asm/byteorder.h>
+#include <assert.h>
+
+#ifdef __linux__
+# include <linux/hdreg.h>
+# define CONFIG_ATAPI_PT 1
+#else
+# define CONFIG_ATAPI_PT 0
+#endif /* __linux__ */
+
+#include <scsi/sg.h>
 
 /* Bits of HD_STATUS */
 #define ERR_STAT		0x01
@@ -218,6 +231,50 @@ struct IDEState;
 
 typedef void EndTransferFunc(struct IDEState *);
 
+typedef struct request_sense {
+#if defined(__BIG_ENDIAN_BITFIELD)
+    uint8_t valid      : 1;
+    uint8_t error_code : 7;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+    uint8_t error_code : 7;
+    uint8_t valid      : 1;
+#endif
+    uint8_t segment_number;
+#if defined(__BIG_ENDIAN_BITFIELD)
+    uint8_t reserved1 : 2;
+    uint8_t ili       : 1;
+    uint8_t reserved2 : 1;
+    uint8_t sense_key : 4;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+    uint8_t sense_key : 4;
+    uint8_t reserved2 : 1;
+    uint8_t ili       : 1;
+    uint8_t reserved1 : 2;
+#endif
+    uint8_t information[4];
+    uint8_t add_sense_len;
+    uint8_t command_info[4];
+    uint8_t asc;
+    uint8_t ascq;
+    uint8_t fruc;
+    uint8_t sks[3];
+    uint8_t asb[46];
+} request_sense;
+
+#if CONFIG_ATAPI_PT
+typedef struct ATAPIPassThroughState
+{
+    uint8_t              request[ATAPI_PACKET_SIZE];
+    struct sg_io_hdr     cmd;
+    struct request_sense sense;
+    void                 (*cmd_sent)(struct IDEState *);
+
+    uint32_t             reply_size_init;   // initial value
+    uint32_t             reply_size_offset; // offset in s->io_buffer
+    uint32_t             reply_size_len;    // length in byte (0, 1, 2, 3 or 4)
+} ATAPIPassThroughState;
+#endif /* CONFIG_ATAPI_PT */
+
 /* NOTE: IDEState represents in fact one drive */
 typedef struct IDEState {
     /* ide config */
@@ -266,6 +323,11 @@ typedef struct IDEState {
     int lba;
     int cd_sector_size;
     int atapi_dma; /* true if dma is requested for the packet cmd */
+#if CONFIG_ATAPI_PT
+    ATAPIPassThroughState atapi_pt;
+#endif /* CONFIG_ATAPI_PT */
+    void (*atapi_identify)(struct IDEState *); // the ATAPI identify
+    void (*atapi_cmd)(struct IDEState *); // the ATAPI cmd handler
     /* ATA DMA state */
     int io_buffer_size;
     QEMUSGList sg;
@@ -287,27 +349,27 @@ typedef struct IDEState {
     int is_read;
 } IDEState;
 
-#define BM_STATUS_DMAING 0x01
-#define BM_STATUS_ERROR  0x02
-#define BM_STATUS_INT    0x04
-#define BM_STATUS_DMA_RETRY  0x08
-#define BM_STATUS_PIO_RETRY  0x10
+#define BM_STATUS_DMAING        0x01
+#define BM_STATUS_ERROR         0x02
+#define BM_STATUS_INT           0x04
+#define BM_STATUS_DMA_RETRY     0x08
+#define BM_STATUS_PIO_RETRY     0x10
 
-#define BM_CMD_START     0x01
-#define BM_CMD_READ      0x08
+#define BM_CMD_START            0x01
+#define BM_CMD_READ             0x08
 
-#define IDE_TYPE_PIIX3   0
-#define IDE_TYPE_CMD646  1
-#define IDE_TYPE_PIIX4   2
+#define IDE_TYPE_PIIX3          0
+#define IDE_TYPE_CMD646         1
+#define IDE_TYPE_PIIX4          2
 
 /* CMD646 specific */
-#define MRDMODE		0x71
-#define   MRDMODE_INTR_CH0	0x04
-#define   MRDMODE_INTR_CH1	0x08
-#define   MRDMODE_BLK_CH0	0x10
-#define   MRDMODE_BLK_CH1	0x20
-#define UDIDETCR0	0x73
-#define UDIDETCR1	0x7B
+#define MRDMODE                 0x71
+#define MRDMODE_INTR_CH0	0x04
+#define MRDMODE_INTR_CH1	0x08
+#define MRDMODE_BLK_CH0         0x10
+#define MRDMODE_BLK_CH1         0x20
+#define UDIDETCR0               0x73
+#define UDIDETCR1               0x7B
 
 typedef struct BMDMAState {
     uint8_t cmd;
@@ -367,6 +429,14 @@ static inline int ube16_to_cpu(const uint8_t *buf)
     return (buf[0] << 8) | buf[1];
 }
 
+#if CONFIG_ATAPI_PT /* only atapi-pt uses it so let's avoid unused
+                            * warning */
+static inline int ube24_to_cpu(const uint8_t *buf)
+{
+    return (buf[0] << 16) | (buf[1] << 8) | buf[2];
+}
+#endif /* CONFIG_ATAPI_PT */
+
 static inline int ube32_to_cpu(const uint8_t *buf)
 {
     return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
diff --git a/qemu-options.hx b/qemu-options.hx
index 1b420a3..2761223 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -91,6 +91,13 @@ Use @var{file} as CD-ROM image (you cannot use @option{-hdc} and
 using @file{/dev/cdrom} as filename (@pxref{host_drives}).
 ETEXI
 
+DEF("cdrom-allow-fw-upgrade", 0, QEMU_OPTION_cdrom_allow_fw_upgrade,
+    "-cdrom-allow-fw-upgrade     allow the guest to process cdrom firmware upgrade.\n")
+STEXI
+@item -cdrom-allow-fw-upgrade
+Allow Qemu to pass through ATAPI firmware upgrade command.
+ETEXI
+
 DEF("drive", HAS_ARG, QEMU_OPTION_drive,
     "-drive [file=file][,if=type][,bus=n][,unit=m][,media=d][,index=i]\n"
     "       [,cyls=c,heads=h,secs=s[,trans=t]][,snapshot=on|off]\n"
diff --git a/vl.c b/vl.c
index fdd4f03..e29d13c 100644
--- a/vl.c
+++ b/vl.c
@@ -142,6 +142,7 @@ int main(int argc, char **argv)
 #include "hw/smbios.h"
 #include "hw/xen.h"
 #include "hw/qdev.h"
+#include "hw/atapi-pt.h"
 #include "bt-host.h"
 #include "net.h"
 #include "monitor.h"
@@ -1792,6 +1793,7 @@ static int bt_parse(const char *opt)
 
 #define HD_ALIAS "index=%d,media=disk"
 #define CDROM_ALIAS "index=2,media=cdrom"
+#define CDROM_PT_ALIAS "index=2,media=cdrompt"
 #define FD_ALIAS "index=%d,if=floppy"
 #define PFLASH_ALIAS "if=pflash"
 #define MTD_ALIAS "if=mtd"
@@ -5119,6 +5121,9 @@ int main(int argc, char **argv, char **envp)
             case QEMU_OPTION_cdrom:
                 drive_add(optarg, CDROM_ALIAS);
                 break;
+            case QEMU_OPTION_cdrom_allow_fw_upgrade:
+                atapi_pt_allow_fw_upgrade = 1;
+                break;
             case QEMU_OPTION_boot:
                 {
                     static const char * const params[] = {
