All of lore.kernel.org
 help / color / mirror / Atom feed
From: Bique Alexandre <alexandre.bique@citrix.com>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH] ATAPI pass through
Date: Tue, 30 Jun 2009 16:34:53 +0100	[thread overview]
Message-ID: <200906301634.53197.alexandre.bique@citrix.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 1157 bytes --]

Hi,

This patch implements ATAPI pass through on Linux by using bsg device.

What works:
 - read CD
 - burn CD with TAO
 - blank CD
 - play audio CD
 - play DVD movie

What doesn't work:
 - burn CD with SAO

DVD blanking and burning needs some testing.

Things which can be done better:
 - I exported the data structure BDRVRawState from raw-posix.c in raw-posix.h, 
because I use the file descriptor of the bsg device to poll, write and read. 
Maybe there is a better solution, but I didn't find what I was looking for in 
block.h.
 - file splitting: everything is in ide.c, even my atapi pass through code in 
the beginning. I didn't want to put everything in this file so I moved my code 
in atapi-pt.c but I did #include "atapi-pt.c" in ide.c so it's still in the 
spirit of what has been done before: all in ide.c. I'd like to split correctly 
this code but I will have to make some static functions public, and if I move 
atapi-pt out from ide.c why not atapi out from ide.c and maybe some other 
stuff ? That's why I did nothing yet. What would you prefer ?

Please, can you comment on this patch ? Thank you very much.

Regards,
-- 
Alexandre Bique

[-- Attachment #2: atapi-pass-through --]
[-- Type: text/x-patch, Size: 53824 bytes --]

diff --git a/Makefile b/Makefile
index 2a4b3f3..df5b5c4 100644
--- a/Makefile
+++ b/Makefile
@@ -300,8 +300,9 @@ endif
 test speed: all
 	$(MAKE) -C tests $@
 
+.PHONY: TAGS
 TAGS:
-	etags *.[ch] tests/*.[ch] block/*.[ch] hw/*.[ch]
+	find "$(SRC_PATH)" -name '*.[hc]' -print0 | xargs -0 etags
 
 cscope:
 	rm -f ./cscope.*
diff --git a/block.c b/block.c
index 3fe9317..962b640 100644
--- a/block.c
+++ b/block.c
@@ -883,6 +883,7 @@ void bdrv_set_type_hint(BlockDriverState *bs, int type)
 {
     bs->type = type;
     bs->removable = ((type == BDRV_TYPE_CDROM ||
+                      type == BDRV_TYPE_CDROM_PT ||
                       type == BDRV_TYPE_FLOPPY));
 }
 
diff --git a/block.h b/block.h
index b595772..b362218 100644
--- a/block.h
+++ b/block.h
@@ -102,9 +102,11 @@ void bdrv_flush_all(void);
 int bdrv_is_allocated(BlockDriverState *bs, int64_t sector_num, int nb_sectors,
 	int *pnum);
 
-#define BDRV_TYPE_HD     0
-#define BDRV_TYPE_CDROM  1
-#define BDRV_TYPE_FLOPPY 2
+#define BDRV_TYPE_HD            0
+#define BDRV_TYPE_CDROM         1
+#define BDRV_TYPE_FLOPPY        2
+#define BDRV_TYPE_CDROM_PT      3
+
 #define BIOS_ATA_TRANSLATION_AUTO   0
 #define BIOS_ATA_TRANSLATION_NONE   1
 #define BIOS_ATA_TRANSLATION_LBA    2
diff --git a/block/raw-posix.c b/block/raw-posix.c
index 8b1e67c..39a83f3 100644
--- a/block/raw-posix.c
+++ b/block/raw-posix.c
@@ -99,20 +99,7 @@
    reopen it to see if the disk has been changed */
 #define FD_OPEN_TIMEOUT 1000
 
-typedef struct BDRVRawState {
-    int fd;
-    int type;
-    unsigned int lseek_err_cnt;
-    int open_flags;
-#if defined(__linux__)
-    /* linux floppy specific */
-    int64_t fd_open_time;
-    int64_t fd_error_time;
-    int fd_got_error;
-    int fd_media_changed;
-#endif
-    uint8_t* aligned_buf;
-} BDRVRawState;
+#include "raw-posix.h"
 
 static int posix_aio_init(void);
 
diff --git a/block/raw-posix.h b/block/raw-posix.h
new file mode 100644
index 0000000..0d3bf85
--- /dev/null
+++ b/block/raw-posix.h
@@ -0,0 +1,19 @@
+#ifndef BLOCK_RAW_POSIX_H
+# define BLOCK_RAW_POSIX_H
+
+typedef struct BDRVRawState {
+    int fd;
+    int type;
+    unsigned int lseek_err_cnt;
+    int open_flags;
+#if defined(__linux__)
+    /* linux floppy specific */
+    int64_t fd_open_time;
+    int64_t fd_error_time;
+    int fd_got_error;
+    int fd_media_changed;
+#endif
+    uint8_t* aligned_buf;
+} BDRVRawState;
+
+#endif /* !BLOCK_RAW_POSIX_H */
diff --git a/configure b/configure
index eb9d73a..9a28082 100755
--- a/configure
+++ b/configure
@@ -191,6 +191,7 @@ nptl="yes"
 mixemu="no"
 bluez="yes"
 kvm="no"
+atapi_pt="no"
 kerneldir=""
 aix="no"
 blobs="yes"
@@ -323,6 +324,19 @@ AIX)
 aix="yes"
 make="gmake"
 ;;
+Linux)
+atapi_pt="yes"
+audio_drv_list="oss"
+audio_possible_drivers="oss alsa sdl esd pa"
+linux="yes"
+linux_user="yes"
+usb="linux"
+kvm="yes"
+if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
+    kqemu="yes"
+    audio_possible_drivers="$audio_possible_drivers fmod"
+fi
+;;
 *)
 audio_drv_list="oss"
 audio_possible_drivers="oss alsa sdl esd pa"
@@ -501,6 +515,10 @@ for opt do
   ;;
   --disable-docs) build_docs="no"
   ;;
+  --enable-atapi-pt) atapi_pt="yes"
+  ;;
+  --disable-atapi-pt) atapi_pt="no"
+  ;;
   *) echo "ERROR: unknown option $opt"; show_help="yes"
   ;;
   esac
@@ -646,6 +664,8 @@ echo "  --disable-vde            disable support for vde network"
 echo "  --disable-pthread        disable pthread support"
 echo "  --disable-aio            disable AIO support"
 echo "  --enable-io-thread       enable IO thread"
+echo "  --enable-atapi-pt        enable atapi device pass through"
+echo "  --disable-atapi-pt       disable atapi device pass through"
 echo "  --disable-blobs          disable installing provided firmware blobs"
 echo "  --kerneldir=PATH         look for kernel includes in PATH"
 echo ""
@@ -1453,6 +1473,7 @@ echo "Install blobs     $blobs"
 echo -e "KVM support       $kvm"
 echo "fdt support       $fdt"
 echo "preadv support    $preadv"
+echo "atapi-pt support  $atapi_pt"
 
 if test $sdl_too_old = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -1823,6 +1844,22 @@ bsd)
 ;;
 esac
 
+# ATAPI pass through
+if [ "$atapi_pt" = "yes" ] ; then
+  if [ "$targetos" != "Linux" ] ; then
+    echo "error: you need a Linux target OS to use ATAPI pass through" >&2
+    exit 1
+  fi
+  cat <<EOF >>$config_h
+#define CONFIG_ATAPI_PT 1
+#ifndef __linux__
+#error "You can't enable ATAPI pass through if you're not using linux."
+#endif /* __linux__ */
+EOF
+else
+  echo "#define CONFIG_ATAPI_PT 0" >>$config_h
+fi
+
 # Determine what linker flags to use to force archive inclusion
 check_linker_flags()
 {
diff --git a/hw/atapi-pt.c b/hw/atapi-pt.c
new file mode 100644
index 0000000..692042e
--- /dev/null
+++ b/hw/atapi-pt.c
@@ -0,0 +1,968 @@
+//#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 DEBUG_PRINTF(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 DEBUG_PRINTF(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(IDEState *s)
+{
+    BDRVRawState *raw_state = s->bs->opaque;
+    int read_bytes;
+    read_bytes = read(raw_state->fd, &s->atapi_pt.cmd, sizeof (s->atapi_pt.cmd));
+
+    if (read_bytes != sizeof (s->atapi_pt.cmd))
+    {
+        ide_atapi_pt_error(s);
+        return;
+    }
+
+    if (s->atapi_pt.cmd.driver_status ||
+        s->atapi_pt.cmd.transport_status ||
+        s->atapi_pt.cmd.device_status)
+    {
+        DEBUG_PRINTF("[\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, transport: %d, device: %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.transport_status,
+                     s->atapi_pt.cmd.device_status);
+        ide_atapi_pt_error(s);
+        return;
+    }
+    s->atapi_pt.cmd_sent(s);
+}
+
+#define ATAPI_PT_SEND_PACKET                                         \
+    do {                                                                \
+        BDRVRawState *raw_state = s->bs->opaque;                        \
+        DEBUG_PRINTF("[ATAPI:%d] sending command: 0x%02x (\e[0;32m%s\e[m)\n", \
+                     raw_state->fd, s->atapi_pt.request[0],             \
+                     atapi_cmd_to_str(s->atapi_pt.request[0]));         \
+        memset(&s->atapi_pt.sense, 0, sizeof (s->atapi_pt.sense));      \
+        int wrote = write(raw_state->fd, &s->atapi_pt.cmd,              \
+                          sizeof (s->atapi_pt.cmd));                    \
+        if (wrote != sizeof (s->atapi_pt.cmd))                          \
+            ide_atapi_pt_error(s);                                      \
+    } while (0)
+
+static void ide_atapi_pt_read_finish(IDEState *s)
+{
+    assert(s->atapi_pt.cmd.dout_xfer_len > 0);
+    s->atapi_pt.cmd.dout_xferp = (__u64)s->io_buffer;
+    s->atapi_pt.cmd_sent = ide_atapi_cmd_ok;
+    ATAPI_PT_SEND_PACKET;
+}
+
+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.dout_xfer_len;
+        ide_dma_start(s, ide_atapi_pt_read_dma_cb);
+        return;
+    }
+
+    /* PIO */
+    s->packet_transfer_size = s->atapi_pt.cmd.dout_xfer_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.dout_xfer_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.din_xfer_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;
+    }
+    DEBUG_PRINTF("[reply] size: %d, din_resid: %d, max_in:%d, response_len: %d\n",
+                 size, s->atapi_pt.cmd.din_resid, s->atapi_pt.cmd.din_xfer_len,
+                 s->atapi_pt.cmd.response_len);
+    ide_atapi_cmd_reply(s, size, s->atapi_pt.cmd.din_xfer_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_v4 *cmd = &s->atapi_pt.cmd;
+
+    memset(cmd, 0, sizeof (*cmd));
+    memcpy(s->atapi_pt.request, s->io_buffer, ATAPI_PACKET_SIZE);
+    cmd->guard            = 'Q';
+    cmd->protocol         = 0;
+    cmd->subprotocol      = 0;
+    cmd->request_len      = ATAPI_PACKET_SIZE;
+    cmd->request          = (__u64)s->atapi_pt.request;
+    cmd->response         = (__u64)&s->atapi_pt.sense;
+    cmd->max_response_len = sizeof (s->atapi_pt.sense);
+    cmd->timeout          = 15000; // 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);
+        s->atapi_pt.cmd_sent = ide_atapi_cmd_ok;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+        /******************/
+        /* WRITE COMMANDS */
+        /******************/
+
+    case GPCMD_WRITE_10:
+    case GPCMD_WRITE_AND_VERIFY_10:
+        cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 7) * CD_FRAMESIZE;
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_WRITE_12:
+        cmd->dout_xfer_len = ube32_to_cpu(s->io_buffer + 6);
+        if (cmd->dout_xfer_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;
+
+        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->dout_xfer_len = parameter_list_length + 4;
+            break;
+        case 0x2: // Data mode
+            cmd->dout_xfer_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:
+            goto illegal_request;
+        }
+
+        ide_atapi_pt_wcmd(s);
+        return;
+    }
+
+    case GPCMD_SEND_CUE_SHEET:
+        cmd->dout_xfer_len = ube24_to_cpu(s->io_buffer + 6);
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_MODE_SELECT_10:
+        cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        CHECK_SAME_VALUE(s->lcyl | (s->hcyl << 8), cmd->dout_xfer_len);
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SEND_KEY:
+    case GPCMD_SEND_EVENT:
+        cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 8);
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SEND_OPC:
+        cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 7) << 3;
+        CHECK_SAME_VALUE(s->lcyl | (s->hcyl << 8), cmd->dout_xfer_len);
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_SET_STREAMING:
+        cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 9);
+        if (cmd->dout_xfer_len == 0)
+            goto simple_cmd;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+    case GPCMD_FORMAT_UNIT:
+        cmd->dout_xfer_len = 12;
+        ide_atapi_pt_wcmd(s);
+        return;
+
+        /*****************/
+        /* READ COMMANDS */
+        /*****************/
+
+    case GPCMD_INQUIRY:
+        cmd->din_xferp = (__u64)s->io_buffer;
+        cmd->din_xfer_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;
+        ATAPI_PT_SEND_PACKET;
+        break;
+
+    case GPCMD_REQUEST_SENSE:
+    {
+        // send the previous sense command
+        DEBUG_PRINTF("=== 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;
+
+        DEBUG_PRINTF("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->din_xfer_len = ube16_to_cpu(s->io_buffer + 8);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 4;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_READ_HEADER:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_MECHANISM_STATUS:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 8);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_offset = 6;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_REPORT_KEY:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 8);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_READ_BUFFER_CAPACITY:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        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->din_xfer_len = 8 + 8 * ube16_to_cpu(s->io_buffer + 8);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 4;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_READ_10:
+    case GPCMD_READ_12:
+    {
+        int blocksize = 0, nbblocks;
+
+        switch (s->io_buffer[0]) {
+        case GPCMD_READ_10: blocksize = CD_FRAMESIZE; break;
+        case GPCMD_READ_12: blocksize = CD_FRAMESIZE_RAW0; break;
+        default: assert(0);
+        }
+        nbblocks = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xfer_len = nbblocks * blocksize;
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        return;
+    }
+
+    case GPCMD_READ_BUFFER:
+        // TODO check this one is correct
+        cmd->din_xferp = (__u64)s->io_buffer;
+        cmd->din_xfer_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->din_xfer_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;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_READ_CDVD_CAPACITY:
+        cmd->din_xfer_len = 8;
+        CHECK_SAME_VALUE(s->lcyl | (s->hcyl << 8), cmd->din_xfer_len);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_MODE_SENSE_10:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        CHECK_SAME_VALUE(s->lcyl | (s->hcyl << 8), cmd->din_xfer_len);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        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->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        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->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_init = 2;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_READ_SUBCHANNEL:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_len = 2;
+        s->atapi_pt.reply_size_offset = 2;
+        ATAPI_PT_SEND_PACKET;
+        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->din_xfer_len = block_count * block_size;
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        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->din_xfer_len = block_count * block_size;
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        return;
+    }
+
+    case GPCMD_PLAY_AUDIO_10:
+    {
+        int block_count = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xfer_len = block_count * CD_FRAMESIZE;
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        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->din_xfer_len = block_count * CD_FRAMESIZE;
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = cmd->din_xfer_len;
+        ATAPI_PT_SEND_PACKET;
+        return;
+    }
+
+    case GPCMD_READ_FORMAT_CAPACITIES:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_read_format_capacities_sent;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_GET_CONFIGURATION:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 7);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = 4;
+        s->atapi_pt.reply_size_len = 4;
+        ATAPI_PT_SEND_PACKET;
+        return;
+
+    case GPCMD_SEND_DVD_STRUCTURE:
+        cmd->din_xfer_len = ube16_to_cpu(s->io_buffer + 8);
+        cmd->din_xferp = (__u64)s->io_buffer;
+        s->atapi_pt.cmd_sent = ide_atapi_pt_standard_reply;
+        s->atapi_pt.reply_size_init = 2;
+        s->atapi_pt.reply_size_len = 2;
+        ATAPI_PT_SEND_PACKET;
+        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 ?
+        DEBUG_PRINTF("[\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;
+    }
+}
diff --git a/hw/ide.c b/hw/ide.c
index 1e56786..01e153f 100644
--- a/hw/ide.c
+++ b/hw/ide.c
@@ -22,6 +22,7 @@
  * 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"
@@ -36,6 +37,15 @@
 #include "sh.h"
 #include "dma.h"
 
+#if CONFIG_ATAPI_PT
+#include "block/raw-posix.h"
+
+#include <linux/cdrom.h>
+#include <linux/bsg.h>
+#endif /* CONFIG_ATAPI_PT */
+
+#include <assert.h>
+
 /* debug IDE devices */
 //#define DEBUG_IDE
 //#define DEBUG_IDE_ATAPI
@@ -216,13 +226,18 @@
 
 #define ATAPI_PACKET_SIZE 12
 
-/* The generic packet command opcodes for CD/DVD Logical Units,
- * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
+#ifndef _LINUX_CDROM_H
+/*********************************************************************
+ * Generic Packet commands, MMC commands, and such
+ *********************************************************************/
+
+ /* The generic packet command opcodes for CD/DVD Logical Units,
+  * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
 #define GPCMD_BLANK			    0xa1
 #define GPCMD_CLOSE_TRACK		    0x5b
 #define GPCMD_FLUSH_CACHE		    0x35
 #define GPCMD_FORMAT_UNIT		    0x04
-#define GPCMD_GET_CONFIGURATION		    0x46
+#define GPCMD_GET_CONFIGURATION             0x46
 #define GPCMD_GET_EVENT_STATUS_NOTIFICATION 0x4a
 #define GPCMD_GET_PERFORMANCE		    0xac
 #define GPCMD_INQUIRY			    0x12
@@ -238,6 +253,8 @@
 #define GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL  0x1e
 #define GPCMD_READ_10			    0x28
 #define GPCMD_READ_12			    0xa8
+#define GPCMD_READ_BUFFER		    0x3c
+#define GPCMD_READ_BUFFER_CAPACITY	    0x5c
 #define GPCMD_READ_CDVD_CAPACITY	    0x25
 #define GPCMD_READ_CD			    0xbe
 #define GPCMD_READ_CD_MSF		    0xb9
@@ -246,15 +263,16 @@
 #define GPCMD_READ_FORMAT_CAPACITIES	    0x23
 #define GPCMD_READ_HEADER		    0x44
 #define GPCMD_READ_TRACK_RZONE_INFO	    0x52
-#define GPCMD_READ_SUBCHANNEL		    0x42
-#define GPCMD_READ_TOC_PMA_ATIP		    0x43
+#define GPCMD_READ_SUBCHANNEL	            0x42
+#define GPCMD_READ_TOC_PMA_ATIP             0x43
 #define GPCMD_REPAIR_RZONE_TRACK	    0x58
 #define GPCMD_REPORT_KEY		    0xa4
 #define GPCMD_REQUEST_SENSE		    0x03
 #define GPCMD_RESERVE_RZONE_TRACK	    0x53
+#define GPCMD_SEND_CUE_SHEET		    0x5d
 #define GPCMD_SCAN			    0xba
 #define GPCMD_SEEK			    0x2b
-#define GPCMD_SEND_DVD_STRUCTURE	    0xad
+#define GPCMD_SEND_DVD_STRUCTURE	    0xbf
 #define GPCMD_SEND_EVENT		    0xa2
 #define GPCMD_SEND_KEY			    0xa3
 #define GPCMD_SEND_OPC			    0x54
@@ -263,9 +281,11 @@
 #define GPCMD_START_STOP_UNIT		    0x1b
 #define GPCMD_STOP_PLAY_SCAN		    0x4e
 #define GPCMD_TEST_UNIT_READY		    0x00
-#define GPCMD_VERIFY_10			    0x2f
+#define GPCMD_VERIFY_10                     0x2f
 #define GPCMD_WRITE_10			    0x2a
+#define GPCMD_WRITE_12			    0xaa
 #define GPCMD_WRITE_AND_VERIFY_10	    0x2e
+#define GPCMD_WRITE_BUFFER		    0x3b
 /* This is listed as optional in ATAPI 2.6, but is (curiously)
  * missing from Mt. Fuji, Table 57.  It _is_ mentioned in Mt. Fuji
  * Table 377 as an MMC command for SCSi devices though...  Most ATAPI
@@ -279,18 +299,20 @@
  * older drives only.
  */
 #define GPCMD_GET_MEDIA_STATUS		    0xda
-#define GPCMD_MODE_SENSE_6		    0x1a
+#define GPCMD_MODE_SENSE_6                  0x1a
 
 /* Mode page codes for mode sense/set */
+#define GPMODE_VENDOR_PAGE		0x00
 #define GPMODE_R_W_ERROR_PAGE		0x01
 #define GPMODE_WRITE_PARMS_PAGE		0x05
+#define GPMODE_WCACHING_PAGE		0x08
 #define GPMODE_AUDIO_CTL_PAGE		0x0e
 #define GPMODE_POWER_PAGE		0x1a
 #define GPMODE_FAULT_FAIL_PAGE		0x1c
 #define GPMODE_TO_PROTECT_PAGE		0x1d
 #define GPMODE_CAPABILITIES_PAGE	0x2a
 #define GPMODE_ALL_PAGES		0x3f
-/* Not in Mt. Fuji, but in ATAPI 2.6 -- depricated now in favor
+/* Not in Mt. Fuji, but in ATAPI 2.6 -- deprecated now in favor
  * of MODE_SENSE_POWER_PAGE */
 #define GPMODE_CDROM_PAGE		0x0d
 
@@ -308,6 +330,14 @@
 #define CD_SECS                       60 /* seconds per minute */
 #define CD_FRAMES                     75 /* frames per second */
 #define CD_FRAMESIZE                2048 /* bytes per frame, "cooked" mode */
+
+#else
+
+/* This one is not present in linux/cdrom.h */
+#define GPCMD_MODE_SENSE_6                  0x1a
+
+#endif /* !_LINUX_CDROM_H */
+
 #define CD_MAX_BYTES       (CD_MINS * CD_SECS * CD_FRAMES * CD_FRAMESIZE)
 #define CD_MAX_SECTORS     (CD_MAX_BYTES / 512)
 
@@ -346,12 +376,15 @@
 #define MMC_PROFILE_HDDVD_RW_DL         0x005A
 #define MMC_PROFILE_INVALID             0xFFFF
 
+
 #define ATAPI_INT_REASON_CD             0x01 /* 0 = data transfer */
 #define ATAPI_INT_REASON_IO             0x02 /* 1 = transfer to the host */
 #define ATAPI_INT_REASON_REL            0x04
 #define ATAPI_INT_REASON_TAG            0xf8
 
 /* same constants as bochs */
+#define ASC_NONE                             0x00
+#define ASC_READ_ERROR                       0x11
 #define ASC_ILLEGAL_OPCODE                   0x20
 #define ASC_LOGICAL_BLOCK_OOR                0x21
 #define ASC_INV_FIELD_IN_CMD_PACKET          0x24
@@ -367,15 +400,42 @@
 #define CFA_INVALID_ADDRESS     0x21
 #define CFA_ADDRESS_OVERFLOW    0x2f
 
-#define SENSE_NONE            0
-#define SENSE_NOT_READY       2
-#define SENSE_ILLEGAL_REQUEST 5
-#define SENSE_UNIT_ATTENTION  6
+/* Sense keys */
+#define SENSE_NONE             0
+#define SENSE_RECOVERED_ERROR  1
+#define SENSE_NOT_READY        2
+#define SENSE_MEDIUM_ERROR     3
+#define SENSE_HARDWARE_ERROR   4
+#define SENSE_ILLEGAL_REQUEST  5
+#define SENSE_UNIT_ATTENTION   6
+#define SENSE_DATA_PROTECT     7
+#define SENSE_BLANK_CHECK      8
+#define SENSE_VENDOR_SPECIFIC  9
+#define SENSE_COPY_ABORTED     10
+#define SENSE_ABORTED_COMMAND  11
+#define SENSE_VOLUME_OVERFLOW  13
+#define SENSE_MISCOMPARE       14
+
+#define IO_BUFFER_MAX_SIZE      (IDE_DMA_BUF_SECTORS * 512 + 4)
 
 struct IDEState;
 
 typedef void EndTransferFunc(struct IDEState *);
 
+#if CONFIG_ATAPI_PT
+typedef struct ATAPIPassThroughState
+{
+    uint8_t              request[ATAPI_PACKET_SIZE];
+    struct sg_io_v4      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 */
@@ -424,6 +484,10 @@ 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_cmd)(struct IDEState *); // the ATAPI cmd handler
     /* ATA DMA state */
     int io_buffer_size;
     QEMUSGList sg;
@@ -1229,6 +1293,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];
@@ -1616,6 +1688,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;
@@ -1664,9 +1740,9 @@ static void ide_atapi_cmd(IDEState *s)
             switch(action) {
             case 0: /* current values */
                 switch(code) {
-                case 0x01: /* error recovery */
+                case 0x01: /* read write error recovery parameters */
                     cpu_to_ube16(&buf[0], 16 + 6);
-                    buf[2] = 0x70;
+                    buf[2] = 0x70; /* Obselete: medium type code */
                     buf[3] = 0;
                     buf[4] = 0;
                     buf[5] = 0;
@@ -1683,17 +1759,17 @@ static void ide_atapi_cmd(IDEState *s)
                     buf[15] = 0x00;
                     ide_atapi_cmd_reply(s, 16, max_len);
                     break;
-                case 0x2a:
+                case 0x2a: /* CD/DVD capabilities & mechanical status */
                     cpu_to_ube16(&buf[0], 28 + 6);
-                    buf[2] = 0x70;
+                    buf[2] = 0x70; /* Obselete: medium type code */
                     buf[3] = 0;
                     buf[4] = 0;
                     buf[5] = 0;
                     buf[6] = 0;
                     buf[7] = 0;
 
-                    buf[8] = 0x2a;
-                    buf[9] = 0x12;
+                    buf[8] = 0x2a; /* page code */
+                    buf[9] = 0x12; /* page lenght */
                     buf[10] = 0x00;
                     buf[11] = 0x00;
 
@@ -2474,7 +2550,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:
@@ -2813,8 +2889,20 @@ static void ide_init2(IDEState *ide_state,
 
             if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) {
                 s->is_cdrom = 1;
+                s->atapi_cmd = ide_atapi_cmd;
 		bdrv_set_change_cb(s->bs, cdrom_change_cb, s);
             }
+#if CONFIG_ATAPI_PT
+            else if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM_PT) {
+                BDRVRawState *raw_state = s->bs->opaque;
+                s->is_cdrom = 1;
+                s->atapi_cmd = ide_atapi_pt_cmd;
+                bdrv_set_change_cb(s->bs, cdrom_change_cb, s);
+                qemu_set_fd_handler(raw_state->fd,
+                                    (IOHandler *)ide_atapi_pt_sg_io_finished,
+                                    NULL, s);
+            }
+#endif /* CONFIG_ATAPI_PT */
         }
         s->drive_serial = drive_serial++;
         strncpy(s->drive_serial_str, drive_get_serial(s->bs),
diff --git a/qemu-options.hx b/qemu-options.hx
index a94f9d3..d362954 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -88,6 +88,14 @@ 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-pt", HAS_ARG, QEMU_OPTION_cdrom_pt,
+    "-cdrom-pt file     use cdrom pass through\n")
+STEXI
+@item -cdrom-pt @var{device}
+Use @var{device} as CD-ROM device. It uses bsg on linux so the device
+should be something like @file{/dev/bsg/1:0:0:0}.
+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 7b7489c..178125a 100644
--- a/vl.c
+++ b/vl.c
@@ -2071,6 +2071,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"
@@ -2207,7 +2208,7 @@ int drive_init(struct drive_opt *arg, int snapshot, void *opaque)
     char serial[21];
     const char *mediastr = "";
     BlockInterfaceType type;
-    enum { MEDIA_DISK, MEDIA_CDROM } media;
+    enum { MEDIA_DISK, MEDIA_CDROM, MEDIA_CDROM_PT } media;
     int bus_id, unit_id;
     int cyls, heads, secs, translation;
     BlockDriverState *bdrv;
@@ -2366,6 +2367,8 @@ int drive_init(struct drive_opt *arg, int snapshot, void *opaque)
 	        return -1;
             }
 	    media = MEDIA_CDROM;
+        } else if (!strcmp(buf, "cdrompt")) {
+	    media = MEDIA_CDROM_PT;
 	} else {
 	    fprintf(stderr, "qemu: '%s' invalid media\n", str);
 	    return -1;
@@ -2531,6 +2534,9 @@ int drive_init(struct drive_opt *arg, int snapshot, void *opaque)
 	case MEDIA_CDROM:
             bdrv_set_type_hint(bdrv, BDRV_TYPE_CDROM);
 	    break;
+        case MEDIA_CDROM_PT:
+            bdrv_set_type_hint(bdrv, BDRV_TYPE_CDROM_PT);
+            break;
 	}
         break;
     case IF_SD:
@@ -5266,6 +5272,9 @@ int main(int argc, char **argv, char **envp)
             case QEMU_OPTION_cdrom:
                 drive_add(optarg, CDROM_ALIAS);
                 break;
+            case QEMU_OPTION_cdrom_pt:
+                drive_add(optarg, CDROM_PT_ALIAS);
+                break;
             case QEMU_OPTION_boot:
                 boot_devices = optarg;
                 /* We just do some generic consistency checks */

             reply	other threads:[~2009-06-30 15:35 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-06-30 15:34 Bique Alexandre [this message]
2009-06-30 16:26 ` [Qemu-devel] [PATCH] ATAPI pass through Kevin Wolf
2009-06-30 18:10   ` Bique Alexandre
2009-07-01  6:57   ` Alexander Graf
2009-07-01  8:44     ` Christoph Hellwig
2009-07-01  9:36     ` Paul Brook
2009-07-01 11:29       ` Christoph Hellwig
2009-06-30 17:01 ` Christoph Hellwig
2009-06-30 17:55   ` Bique Alexandre
2009-07-01  8:43     ` Christoph Hellwig

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=200906301634.53197.alexandre.bique@citrix.com \
    --to=alexandre.bique@citrix.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.