Patchwork [4/4] ATAPI pass through v3

login
register
mail settings
Submitter Bique Alexandre
Date Aug. 6, 2009, 3:50 p.m.
Message ID <200908061650.05474.alexandre.bique@citrix.com>
Download mbox | patch
Permalink /patch/30863/
State Superseded
Headers show

Comments

Bique Alexandre - Aug. 6, 2009, 3:50 p.m.
The ATAPI pass through feature.
Bique Alexandre - Aug. 7, 2009, 4:34 p.m.
On Friday 07 August 2009 17:06:36 Juan Quintela wrote:
> Bique Alexandre <alexandre.bique@citrix.com> wrote:
> > The ATAPI pass through feature.
>
> +# define CHECK_SAME_VALUE(Val1, Val2)
>
> Can we call this something like: assert_equal() ? or anything else more
> descriptive?

It's not an assertion because Val1 and Val2 are allowed to be different and it 
doesn't call abort. It just displays some debut information. Would CHECK_EQUAL 
be alright for you ?

>
> +/* The generic packet command opcodes for CD/DVD Logical Units,
> + * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
> +static const struct {
> +    unsigned short packet_command;
> +    const char * const text;
> +} packet_command_texts[] = {
>
> Please, use C99 intializers:
>
> +    { GPCMD_TEST_UNIT_READY, "Test Unit Ready" },
>
> use this format, same for rest of structs
>
>      {
>        .packet_command = GPCMD_TEST_UNIT_READY,
>        .text = "Test Unit Ready"
>      },

I see no reason to do that. It takes more place. We are not going to add a lot 
of additional fields. And even if we do, this structure should be used only 
time at only one place and if we decide to add a new field in the beginning or 
the middle of this structure, it should be worth to do some regexp to fix the 
declaration.

> +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:
> +        assert(0);
>          ^^^^^^^
> print a nice error message?
> die?
> something?

I Will do it, but what do you want me to say ?
"We reached a part of the code we should never reach. Please send a bug report 
to Qemu developers. Thanks." ?

> +        break;
> +    }
>
> +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 0x40: block_size = 0; break;
>
> move this one here, same fro the same two with 16 value?
> group all of them by block_size?  Same for the rest of the cases.

I can but I don't want to. Because if you want to double check this, you would 
prefer to see this sorted so you can check the reference table at the same 
time you check your switch case.

> +        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 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
> +        }
>
>
> +#if CONFIG_ATAPI_PT
> +#include "atapi-pt.c"
> +#endif
>
> Why do we include it here?  coulden't yust compile it as a new file?
I did. I am going to send you my new patches where I made some part of ide.c 
public and introduced 3 new headers.

Thanks for review.
Bique Alexandre - Aug. 7, 2009, 5:10 p.m.
On Friday 07 August 2009 17:46:00 Juan Quintela wrote:
> Bique Alexandre <alexandre.bique@citrix.com> wrote:
> > On Friday 07 August 2009 17:06:36 Juan Quintela wrote:
> >> Bique Alexandre <alexandre.bique@citrix.com> wrote:
> >> > The ATAPI pass through feature.
> >>
> >> +# define CHECK_SAME_VALUE(Val1, Val2)
> >>
> >> Can we call this something like: assert_equal() ? or anything else more
> >> descriptive?
> >
> > It's not an assertion because Val1 and Val2 are allowed to be different
> > and it doesn't call abort. It just displays some debut information. Would
> > CHECK_EQUAL be alright for you ?
> >
> >> +/* The generic packet command opcodes for CD/DVD Logical Units,
> >> + * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
> >> +static const struct {
> >> +    unsigned short packet_command;
> >> +    const char * const text;
> >> +} packet_command_texts[] = {
> >>
> >> Please, use C99 intializers:
> >>
> >> +    { GPCMD_TEST_UNIT_READY, "Test Unit Ready" },
> >>
> >> use this format, same for rest of structs
> >>
> >>      {
> >>        .packet_command = GPCMD_TEST_UNIT_READY,
> >>        .text = "Test Unit Ready"
> >>      },
> >
> > I see no reason to do that. It takes more place. We are not going to add
> > a lot of additional fields. And even if we do, this structure should be
> > used only time at only one place and if we decide to add a new field in
> > the beginning or the middle of this structure, it should be worth to do
> > some regexp to fix the declaration.
>
> Being coherent with everyhing else (new fields use C99 initilizers).
> Consistence is important IMHO.

C89 features are also part of C99. I agree that C99 initializers is a great 
feature, but I really feel that it's not the right place to use it :)

> >> +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:
> >> +        assert(0);
> >>          ^^^^^^^
> >> print a nice error message?
> >> die?
> >> something?
> >
> > I Will do it, but what do you want me to say ?
> > "We reached a part of the code we should never reach. Please send a bug
> > report to Qemu developers. Thanks." ?
>
> The imposible has happened!!! We received a reply with size %d.  Please
> inform <foo@bar.com>.
>
> Not sure about the mail/web adderss, etc.

Hey fun. For sure I'll add this :)

> >> +        break;
> >> +    }
> >>
> >> +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 0x40: block_size = 0; break;
> >>
> >> move this one here, same fro the same two with 16 value?
> >> group all of them by block_size?  Same for the rest of the cases.
> >
> > I can but I don't want to. Because if you want to double check this, you
> > would prefer to see this sorted so you can check the reference table at
> > the same time you check your switch case.
>
> Then, were is the reference table pointer? :)

It comes from the "Mt. Fuji Commands for Multimedia Devices SFF8090i v4" page 
343.

> One comment indicating it would be nice.
>
> /* The order of this case is the same that table number foo at page X */
>
> Otherwise, it looks very random.

Done.

> >> +        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 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
> >> +        }
> >>
> >>
> >> +#if CONFIG_ATAPI_PT
> >> +#include "atapi-pt.c"
> >> +#endif
> >>
> >> Why do we include it here?  coulden't yust compile it as a new file?
> >
> > I did. I am going to send you my new patches where I made some part of
> > ide.c public and introduced 3 new headers.
> >
> > Thanks for review.
>
> You are welcome.

Thanks. I send the new patches with git format and git send-mail this time 
:-).

Patch

diff --git a/block/raw-posix.c b/block/raw-posix.c
index bdee07f..0510379 100644
--- a/block/raw-posix.c
+++ b/block/raw-posix.c
@@ -154,6 +154,12 @@  static int raw_open_common(BlockDriverState *bs, const char *filename,
     else if (!(bdrv_flags & BDRV_O_CACHE_WB))
         s->open_flags |= O_DSYNC;
 
+    /* If we open a cdrom device, and there is no media inside, we
+     * have to add O_NONBLOCK to open else it will fail */
+    if (bdrv_get_type_hint(bs) == BDRV_TYPE_CDROM &&
+        bdrv_is_sg(bs))
+        s->open_flags |= O_NONBLOCK;
+
     s->fd = -1;
     fd = open(filename, s->open_flags, 0644);
     if (fd < 0) {
@@ -1027,7 +1033,10 @@  static int hdev_open(BlockDriverState *bs, const char *filename, int flags)
 
     s->type = FTYPE_FILE;
 #if defined(__linux__) && defined(CONFIG_AIO)
-    if (strstart(filename, "/dev/sg", NULL)) {
+    if (strstart(filename, "/dev/sg", NULL) ||
+        strstart(filename, "/dev/cd", NULL) ||
+        strstart(filename, "/dev/dvd", NULL) ||
+        strstart(filename, "/dev/sr", NULL)) {
         bs->sg = 1;
     }
 #endif
diff --git a/hw/atapi-pt.c b/hw/atapi-pt.c
new file mode 100644
index 0000000..85f6b6a
--- /dev/null
+++ b/hw/atapi-pt.c
@@ -0,0 +1,1002 @@ 
+int atapi_pt_allow_fw_upgrade = 0;
+
+#define DEBUG_IDE_ATAPI_PT
+
+#define MSF_TO_FRAMES(M, S, F) (((M) * CD_SECS + (S)) * CD_FRAMES + (F))
+
+#ifdef DEBUG_IDE_ATAPI_PT
+# define DPRINTF(Args...) printf(Args)
+# define CHECK_SAME_VALUE(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_SAME_VALUE(Val1, Val2)
+#endif /* DEBUG_IDE_ATAPI_PT */
+
+/* The generic packet command opcodes for CD/DVD Logical Units,
+ * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
+static const struct {
+    unsigned short packet_command;
+    const char * const text;
+} packet_command_texts[] = {
+    { GPCMD_TEST_UNIT_READY, "Test Unit Ready" },
+    { GPCMD_REQUEST_SENSE, "Request Sense" },
+    { GPCMD_FORMAT_UNIT, "Format Unit" },
+    { GPCMD_INQUIRY, "Inquiry" },
+    { GPCMD_START_STOP_UNIT, "Start/Stop Unit" },
+    { GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL, "Prevent/Allow Medium Removal" },
+    { GPCMD_READ_FORMAT_CAPACITIES, "Read Format Capacities" },
+    { GPCMD_READ_CDVD_CAPACITY, "Read Cd/Dvd Capacity" },
+    { GPCMD_READ_10, "Read 10" },
+    { GPCMD_WRITE_10, "Write 10" },
+    { GPCMD_SEEK, "Seek" },
+    { GPCMD_WRITE_AND_VERIFY_10, "Write and Verify 10" },
+    { GPCMD_VERIFY_10, "Verify 10" },
+    { GPCMD_FLUSH_CACHE, "Flush Cache" },
+    { GPCMD_READ_SUBCHANNEL, "Read Subchannel" },
+    { GPCMD_READ_TOC_PMA_ATIP, "Read Table of Contents" },
+    { GPCMD_READ_HEADER, "Read Header" },
+    { GPCMD_PLAY_AUDIO_10, "Play Audio 10" },
+    { GPCMD_GET_CONFIGURATION, "Get Configuration" },
+    { GPCMD_PLAY_AUDIO_MSF, "Play Audio MSF" },
+    { GPCMD_PLAYAUDIO_TI, "Play Audio TrackIndex" },
+    { GPCMD_GET_EVENT_STATUS_NOTIFICATION,
+      "Get Event Status Notification" },
+    { GPCMD_PAUSE_RESUME, "Pause/Resume" },
+    { GPCMD_STOP_PLAY_SCAN, "Stop Play/Scan" },
+    { GPCMD_READ_DISC_INFO, "Read Disc Info" },
+    { GPCMD_READ_TRACK_RZONE_INFO, "Read Track Rzone Info" },
+    { GPCMD_RESERVE_RZONE_TRACK, "Reserve Rzone Track" },
+    { GPCMD_SEND_OPC, "Send OPC" },
+    { GPCMD_MODE_SELECT_10, "Mode Select 10" },
+    { GPCMD_REPAIR_RZONE_TRACK, "Repair Rzone Track" },
+    { GPCMD_MODE_SENSE_10, "Mode Sense 10" },
+    { GPCMD_CLOSE_TRACK, "Close Track" },
+    { GPCMD_BLANK, "Blank" },
+    { GPCMD_SEND_EVENT, "Send Event" },
+    { GPCMD_SEND_KEY, "Send Key" },
+    { GPCMD_REPORT_KEY, "Report Key" },
+    { GPCMD_LOAD_UNLOAD, "Load/Unload" },
+    { GPCMD_SET_READ_AHEAD, "Set Read-ahead" },
+    { GPCMD_READ_12, "Read 12" },
+    { GPCMD_GET_PERFORMANCE, "Get Performance" },
+    { GPCMD_SEND_DVD_STRUCTURE, "Send DVD Structure" },
+    { GPCMD_READ_DVD_STRUCTURE, "Read DVD Structure" },
+    { GPCMD_SET_STREAMING, "Set Streaming" },
+    { GPCMD_READ_CD_MSF, "Read CD MSF" },
+    { GPCMD_SCAN, "Scan" },
+    { GPCMD_SET_SPEED, "Set Speed" },
+    { GPCMD_PLAY_CD, "Play CD" },
+    { GPCMD_MECHANISM_STATUS, "Mechanism Status" },
+    { GPCMD_READ_CD, "Read CD" },
+    { GPCMD_READ_BUFFER_CAPACITY, "Read Buffer Capacity" },
+    { GPCMD_READ_BUFFER, "Read Buffer" },
+    { GPCMD_SEND_CUE_SHEET, "Send Cue Sheet" },
+    { 0, 0 }
+};
+
+#ifdef DEBUG_IDE_ATAPI_PT
+static const char *atapi_cmd_to_str(int cmd)
+{
+    int i;
+
+    for (i = 0; packet_command_texts[i].text; ++i)
+        if (packet_command_texts[i].packet_command == cmd)
+            return packet_command_texts[i].text;
+    return 0;
+}
+#endif /* DEBUG_IDE_ATAPI_PT */
+
+/* From Table 303 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
+static const char * const sense_key_texts[16] = {
+    "No sense data",
+    "Recovered error",
+    "Not ready",
+    "Medium error",
+    "Hardware error",
+    "Illegal request",
+    "Unit attention",
+    "Data protect",
+    "Blank check",
+    "(reserved)",
+    "(reserved)",
+    "Aborted command",
+    "(reserved)",
+    "(reserved)",
+    "Miscompare",
+    "(reserved)",
+};
+
+/* From Table 304 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
+static const struct {
+    unsigned long asc_ascq;
+    const char * const text;
+} sense_data_texts[] = {
+    { 0x000000, "No additional sense information" },
+    { 0x000011, "Play operation in progress" },
+    { 0x000012, "Play operation paused" },
+    { 0x000013, "Play operation successfully completed" },
+    { 0x000014, "Play operation stopped due to error" },
+    { 0x000015, "No current audio status to return" },
+    { 0x010c0a, "Write error - padding blocks added" },
+    { 0x011700, "Recovered data with no error correction applied" },
+    { 0x011701, "Recovered data with retries" },
+    { 0x011702, "Recovered data with positive head offset" },
+    { 0x011703, "Recovered data with negative head offset" },
+    { 0x011704, "Recovered data with retries and/or CIRC applied" },
+    { 0x011705, "Recovered data using previous sector ID" },
+    { 0x011800, "Recovered data with error correction applied" },
+    { 0x011801, "Recovered data with error correction and retries applied"},
+    { 0x011802, "Recovered data - the data was auto-reallocated" },
+    { 0x011803, "Recovered data with CIRC" },
+    { 0x011804, "Recovered data with L-EC" },
+    { 0x015d00, "Failure prediction threshold exceeded"
+      " - Predicted logical unit failure" },
+    { 0x015d01, "Failure prediction threshold exceeded"
+      " - Predicted media failure" },
+    { 0x015dff, "Failure prediction threshold exceeded - False" },
+    { 0x017301, "Power calibration area almost full" },
+    { 0x020400, "Logical unit not ready - cause not reportable" },
+    /* Following is misspelled in ATAPI 2.6, _and_ in Mt. Fuji */
+    { 0x020401, "Logical unit not ready"
+      " - in progress [sic] of becoming ready" },
+    { 0x020402, "Logical unit not ready - initializing command required" },
+    { 0x020403, "Logical unit not ready - manual intervention required" },
+    { 0x020404, "Logical unit not ready - format in progress" },
+    { 0x020407, "Logical unit not ready - operation in progress" },
+    { 0x020408, "Logical unit not ready - long write in progress" },
+    { 0x020600, "No reference position found (media may be upside down)" },
+    { 0x023000, "Incompatible medium installed" },
+    { 0x023a00, "Medium not present" },
+    { 0x025300, "Media load or eject failed" },
+    { 0x025700, "Unable to recover table of contents" },
+    { 0x030300, "Peripheral device write fault" },
+    { 0x030301, "No write current" },
+    { 0x030302, "Excessive write errors" },
+    { 0x030c00, "Write error" },
+    { 0x030c01, "Write error - Recovered with auto reallocation" },
+    { 0x030c02, "Write error - auto reallocation failed" },
+    { 0x030c03, "Write error - recommend reassignment" },
+    { 0x030c04, "Compression check miscompare error" },
+    { 0x030c05, "Data expansion occurred during compress" },
+    { 0x030c06, "Block not compressible" },
+    { 0x030c07, "Write error - recovery needed" },
+    { 0x030c08, "Write error - recovery failed" },
+    { 0x030c09, "Write error - loss of streaming" },
+    { 0x031100, "Unrecovered read error" },
+    { 0x031106, "CIRC unrecovered error" },
+    { 0x033101, "Format command failed" },
+    { 0x033200, "No defect spare location available" },
+    { 0x033201, "Defect list update failure" },
+    { 0x035100, "Erase failure" },
+    { 0x037200, "Session fixation error" },
+    { 0x037201, "Session fixation error writin lead-in" },
+    { 0x037202, "Session fixation error writin lead-out" },
+    { 0x037300, "CD control error" },
+    { 0x037302, "Power calibration area is full" },
+    { 0x037303, "Power calibration area error" },
+    { 0x037304, "Program memory area / RMA update failure" },
+    { 0x037305, "Program memory area / RMA is full" },
+    { 0x037306, "Program memory area / RMA is (almost) full" },
+    { 0x040200, "No seek complete" },
+    { 0x040300, "Write fault" },
+    { 0x040900, "Track following error" },
+    { 0x040901, "Tracking servo failure" },
+    { 0x040902, "Focus servo failure" },
+    { 0x040903, "Spindle servo failure" },
+    { 0x041500, "Random positioning error" },
+    { 0x041501, "Mechanical positioning or changer error" },
+    { 0x041502, "Positioning error detected by read of medium" },
+    { 0x043c00, "Mechanical positioning or changer error" },
+    { 0x044000, "Diagnostic failure on component (ASCQ)" },
+    { 0x044400, "Internal CD/DVD logical unit failure" },
+    { 0x04b600, "Media load mechanism failed" },
+    { 0x051a00, "Parameter list length error" },
+    { 0x052000, "Invalid command operation code" },
+    { 0x052100, "Logical block address out of range" },
+    { 0x052102, "Invalid address for write" },
+    { 0x052400, "Invalid field in command packet" },
+    { 0x052600, "Invalid field in parameter list" },
+    { 0x052601, "Parameter not supported" },
+    { 0x052602, "Parameter value invalid" },
+    { 0x052700, "Write protected media" },
+    { 0x052c00, "Command sequence error" },
+    { 0x052c03, "Current program area is not empty" },
+    { 0x052c04, "Current program area is empty" },
+    { 0x053001, "Cannot read medium - unknown format" },
+    { 0x053002, "Cannot read medium - incompatible format" },
+    { 0x053900, "Saving parameters not supported" },
+    { 0x054e00, "Overlapped commands attempted" },
+    { 0x055302, "Medium removal prevented" },
+    { 0x055500, "System resource failure" },
+    { 0x056300, "End of user area encountered on this track" },
+    { 0x056400, "Illegal mode for this track or incompatible medium" },
+    { 0x056f00, "Copy protection key exchange failure"
+      " - Authentication failure" },
+    { 0x056f01, "Copy protection key exchange failure - Key not present" },
+    { 0x056f02, "Copy protection key exchange failure"
+      " - Key not established" },
+    { 0x056f03, "Read of scrambled sector without authentication" },
+    { 0x056f04, "Media region code is mismatched to logical unit" },
+    { 0x056f05, "Drive region must be permanent"
+      " / region reset count error" },
+    { 0x057203, "Session fixation error - incomplete track in session" },
+    { 0x057204, "Empty or partially written reserved track" },
+    { 0x057205, "No more RZONE reservations are allowed" },
+    { 0x05bf00, "Loss of streaming" },
+    { 0x062800, "Not ready to ready transition, medium may have changed" },
+    { 0x062900, "Power on, reset or hardware reset occurred" },
+    { 0x062a00, "Parameters changed" },
+    { 0x062a01, "Mode parameters changed" },
+    { 0x062e00, "Insufficient time for operation" },
+    { 0x063f00, "Logical unit operating conditions have changed" },
+    { 0x063f01, "Microcode has been changed" },
+    { 0x065a00, "Operator request or state change input (unspecified)" },
+    { 0x065a01, "Operator medium removal request" },
+    { 0x0bb900, "Play operation aborted" },
+    /* Here we use 0xff for the key (not a valid key) to signify
+     * that these can have _any_ key value associated with them... */
+    { 0xff0401, "Logical unit is in process of becoming ready" },
+    { 0xff0400, "Logical unit not ready, cause not reportable" },
+    { 0xff0402, "Logical unit not ready, initializing command required" },
+    { 0xff0403, "Logical unit not ready, manual intervention required" },
+    { 0xff0500, "Logical unit does not respond to selection" },
+    { 0xff0800, "Logical unit communication failure" },
+    { 0xff0802, "Logical unit communication parity error" },
+    { 0xff0801, "Logical unit communication time-out" },
+    { 0xff2500, "Logical unit not supported" },
+    { 0xff4c00, "Logical unit failed self-configuration" },
+    { 0xff3e00, "Logical unit has not self-configured yet" },
+};
+
+#ifdef DEBUG_IDE_ATAPI_PT
+static const char *atapi_ascq_to_str(int ascq)
+{
+    int i;
+
+    for (i = 0; sense_data_texts[i].text; ++i)
+        if (sense_data_texts[i].asc_ascq == ascq)
+            return sense_data_texts[i].text;
+    return 0;
+}
+#endif /* DEBUG_IDE_ATAPI_PT */
+
+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:
+        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);
+}
+
+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;
+}
+
+static 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_SAME_VALUE(s->lcyl, 0);
+        CHECK_SAME_VALUE(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_SAME_VALUE(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_SAME_VALUE(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_SAME_VALUE(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);
+        }
+        cmd->dxfer_len = nbblocks * blocksize;
+        CHECK_SAME_VALUE(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_SAME_VALUE(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_SAME_VALUE(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;
+    }
+}
+
+static 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..61dc443
--- /dev/null
+++ b/hw/atapi-pt.h
@@ -0,0 +1,6 @@ 
+#ifndef ATAPI_PT_H
+# define ATAPI_PT_H
+
+extern int atapi_pt_allow_fw_upgrade;
+
+#endif /* !ATAPI_PT_H */
diff --git a/hw/ide.c b/hw/ide.c
index 5c2693e..35cef28 100644
--- a/hw/ide.c
+++ b/hw/ide.c
@@ -36,6 +36,19 @@ 
 #include "sh.h"
 #include "dma.h"
 
+#include <stdint.h>
+#include <limits.h>
+#include <asm/byteorder.h>
+#include <scsi/sg.h>
+#include <assert.h>
+
+#ifdef __linux__
+# include <linux/hdreg.h>
+# define CONFIG_ATAPI_PT 1
+#else
+# define CONFIG_ATAPI_PT 0
+#endif /* __linux__ */
+
 /* debug IDE devices */
 //#define DEBUG_IDE
 //#define DEBUG_IDE_ATAPI
@@ -419,6 +432,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 */
@@ -467,6 +524,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;
@@ -1288,6 +1350,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];
@@ -1675,6 +1745,10 @@  static int ide_dvd_read_structure(IDEState *s, int format,
     }
 }
 
+#if CONFIG_ATAPI_PT
+#include "atapi-pt.c"
+#endif
+
 static void ide_atapi_cmd(IDEState *s)
 {
     const uint8_t *packet;
@@ -2495,7 +2569,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 {
@@ -2533,7 +2607,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:
@@ -2872,6 +2946,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/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[] = {