+#endif /* !BLOCK_RAW_POSIX_H */
diff --git a/hw/atapi-pt.c b/hw/atapi-pt.c
new file mode 100644
-index 0000000..413cc47
+index 0000000..47ee6af
--- /dev/null
+++ b/hw/atapi-pt.c
-@@ -0,0 +1,1427 @@
+@@ -0,0 +1,1163 @@
++#include <utime.h>
+#include <sys/stat.h>
++#include <fcntl.h>
+
+#undef DEBUG_IDE_ATAPI_PT
+
+}
+
+
++/* For each SCSI command we need to know up to 3 data sizes. These are:
++ * 1. The amount of data to send to the LU.
++ * 2. The size of the buffer provided for data sent back from the LU.
++ * 3. The amount of data the LU wanted to send.
++ *
++ * These are all measured in bytes.
++ *
++ * The table ide_atapi_cmd_data_sizes specifies how to determine these sizes for
++ * each SCSI command. Each size is given by:
++ *
++ * constant + (base * block_size)
++ *
++ * where base is a value specified within the command data. This is specified in
++ * our table as an offset into the data at which the value starts and the size of
++ * the value, in bytes. All base value are assumed to be MSB first (lowest offset).
++ *
++ * The number of bytes forming the base value can only take values between 0 and 4
++ * inclusive, with 0 indicating that there is no base value.
++ *
++ * The amount of data to send to the LU and the size of the receiving buffer are
++ * both determined from the CDB. A value of 0 means that no data is transfered and
++ * a value of -1 indicates that the table entry is invalid (eg undefined command).
++ *
++ * The amount of data the LU wanted to return is determined from the returned data.
++ * A value of 0 is invalid and a value of -1 indicates that this size is the same
++ * as the receiving buffer size.
++ *
++ * A few commands are too complex for this scheme and so are handled by code in
++ * ide_atapi_pt_cmd() and ide_atapi_pt_do_sg_io().
++ */
++static struct
++{
++ int dout_len_const; // size of data to send to LU, dout
++ int dout_len_offset;
++ int dout_len_size;
++ int dout_block_size;
++
++ int alloc_len_const; // size of buffer to receive din
++ int alloc_len_offset;
++ int alloc_len_size;
++ int alloc_block_size;
++
++ int din_len_const; // size of data LU wanted to send, din
++ int din_len_offset;
++ int din_len_size;
++ int din_block_size;
++} atapi_data_sizes[256] = {
++ // dout buffer din
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x00 GPCMD_TEST_UNIT_READY
++ {-1},{-1}, // 0x01-0x02
++ {-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0}, // 0x03 GPCMD_REQUEST_SENSE
++ {12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x04 GPCMD_FORMAT_UNIT
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x05-0x0c
++ {-1},{-1},{-1},{-1},{-1}, // 0x0d-0x11
++ { 0, 0, 0, 0, 0, 4, 1, 1, 5, 4, 1, 1}, // 0x12 GPCMD_INQUIRY
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x13-0x1a
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x1b GPCMD_START_STOP_UNIT
++ {-1},{-1}, // 0x1c-0x1d
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x1e GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL
++ {-1},{-1},{-1},{-1}, // 0x1f-0x20
++ { 0, 0, 0, 0, 0, 7, 2, 1, 4, 3, 1, 1}, // 0x23 GPCMD_READ_FORMAT_CAPACITIES
++ {-1}, // 0x24
++ { 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0}, // 0x25 GPCMD_READ_CDVD_CAPACITY
++ {-1},{-1}, // 0x26-0x27
++ { 0, 0, 0, 0, 0, 7, 2, CD_FRAMESIZE,
++ -1, 0, 0, 0}, // 0x28 GPCMD_READ_10
++ {-1}, // 0x29
++ { 0, 7, 2, CD_FRAMESIZE,
++ 0, 0, 0, 0 , 0, 0, 0, 0}, // 0x2a GPCMD_WRITE_10
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x2b GPCMD_SEEK
++ {-1},{-1}, // 0x2c-0x2d
++ { 0, 7, 2, CD_FRAMESIZE,
++ 0, 0, 0, 0, 0, 0, 0, 0}, // 0x2e GPCMD_WRITE_AND_VERIFY_10
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x2f GPCMD_VERIFY_10
++ {-1},{-1},{-1},{-1},{-1}, // 0x30-0x34
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x35 GPCMD_FLUSH_CACHE
++ {-1},{-1},{-1},{-1},{-1}, // 0x36-0x3a
++ { 0, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x3b GPCMD_WRITE_BUFFER
++ { 0, 0, 0, 0, 0, 6, 3, 1, 4, 1, 3, 1}, // 0x3c GPCMD_READ_BUFFER
++ {-1},{-1},{-1},{-1},{-1}, // 0x3d-0x41
++ { 0, 0, 0, 0, 0, 7, 2, 1, 4, 2, 2, 1}, // 0x42 GPCMD_READ_SUBCHANNEL
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x43 GPCMD_READ_TOC_PMA_ATIP
++ { 0, 0, 0, 0, 0, 7, 2, 1, 8, 0, 0, 0}, // 0x44 GPCMD_READ_HEADER
++ { 0, 0, 0, 0, 0, 7, 2, CD_FRAMESIZE,
++ -1, 0, 0, 0}, // 0x45 GPCMD_PLAY_AUDIO_10
++ { 0, 0, 0, 0, 0, 7, 2, 1, 4, 0, 4, 1}, // 0x46 GPCMD_GET_CONFIGURATION
++ { 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0}, // 0x47 GPCMD_PLAY_AUDIO_MSF
++ {-1},{-1}, // 0x48-0x49
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x4a GPCMD_GET_EVENT_STATUS_NOTIFICATION
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x4b GPCMD_PAUSE_RESUME
++ {-1},{-1}, // 0x4c-0x4d
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x4e GPCMD_STOP_PLAY_SCAN
++ {-1},{-1}, // 0x4f-0x50
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x51 GPCMD_READ_DISC_INFO
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x52 GPCMD_READ_TRACK_RZONE_INFO
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x53 GPCMD_RESERVE_RZONE_TRACK
++ { 0, 7, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x54 GPCMD_SEND_OPC
++ { 0, 7, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x55 GPCMD_MODE_SELECT_10
++ {-1},{-1}, // 0x56-0x57
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x58 GPCMD_REPAIR_RZONE_TRACK
++ {-1}, // 0x59
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x5a GPCMD_MODE_SENSE_10
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x5b GPCMD_CLOSE_TRACK
++ { 0, 0, 0, 0, 0, 7, 2, 1, 2, 0, 2, 1}, // 0x5c GPCMD_READ_BUFFER_CAPACITY
++ { 0, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0x5d GPCMD_SEND_CUE_SHEET
++ {-1},{-1}, // 0x5e-0x5f
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x60-0x67
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x68-0x6f
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x70-0x77
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x78-0x7f
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x80-0x87
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x88-0x8f
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x90-0x97
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0x98-0x9f
++ {-1}, // 0xa0
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xa1 GPCMD_BLANK
++ { 0, 8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xa2 GPCMD_SEND_EVENT
++ { 0, 8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xa3 GPCMD_SEND_KEY
++ { 0, 0, 0, 0, 0, 8, 2, 1, 2, 0, 2, 1}, // 0xa4 GPCMD_REPORT_KEY
++ {-1}, // 0xa5
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xa6 GPCMD_LOAD_UNLOAD
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xa7 GPCMD_SET_READ_AHEAD
++ { 0, 0, 0, 0, 0, 6, 4, CD_FRAMESIZE,
++ -1, 0, 0, 0}, // 0xa8 GPCMD_READ_12
++ {-1}, // 0xa9
++ { 0, 6, 4, CD_FRAMESIZE,
++ 0, 0, 0, 0, 0, 0, 0, 0}, // 0xaa GPCMD_WRITE_12
++ {-1}, // 0xab
++ { 0, 0, 0, 0, 0, 8, 2, 1, 4, 0, 4, 1}, // 0xac GPCMD_GET_PERFORMANCE
++ { 0, 0, 0, 0, 0, 8, 2, 1, 2, 0, 2, 1}, // 0xad GPCMD_READ_DVD_STRUCTURE
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xae-0xb5
++ { 0, 9, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xb6 GPCMD_SET_STREAMING
++ {-1},{-1}, // 0xb7-0xb8
++ { 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0}, // 0xb9 GPCMD_READ_CD_MSF
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xba GPCMD_SCAN
++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xbb GPCMD_SET_SPEED - Not in docs
++ {-1}, // 0xbc
++ { 0, 0, 0, 0, 0, 8, 2, 1, 8, 6, 2, 1}, // 0xbd GPCMD_MECHANISM_STATUS
++ { 0, 0, 0, 0, 0, 6, 3, 1, -1, 0, 0, 0}, // 0xbe GPCMD_READ_CD
++ { 0, 8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 0xbf GPCMD_SEND_DVD_STRUCTURE
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xc0-0xc7
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xc8-0xcf
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xd0-0xd7
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xd8-0xdf
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xe0-0xe7
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xe8-0xef
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1}, // 0xf0-0xf7
++ {-1},{-1},{-1},{-1},{-1},{-1},{-1},{-1} // 0xf8-0xff
++};
++
++// TODO: check these commands:
++//# define GPCMD_PLAY_CD 0xbc
++//# define GPCMD_PLAYAUDIO_TI 0x48
++//# define GPCMD_GET_MEDIA_STATUS 0xda
++
++
++static uint32_t ide_atapi_pt_read_field_at_offset(uint8_t *data, int offset, int field_size)
++{
++ switch(field_size)
++ {
++ case 0:
++ return 0;
++ case 1:
++ return data[offset];
++ case 2:
++ return ube16_to_cpu(data + offset);
++ case 3:
++ return ube24_to_cpu(data + offset);
++ case 4:
++ return ube32_to_cpu(data + offset);
++ default:
++ assert(0);
++ return -1;
++ }
++}
++
++
+static void ide_atapi_pt_set_error(IDEState *s, int sense_key, int asc, int error)
+{
+ s->atapi_pt.sense.sense_key = sense_key;
+ ide_set_irq(s);
+}
+
-+#if 0
-+static int cmd_count = 0;
-+static int finish_count = 0;
-+static int no_finish_count = 0;
-+
-+static void ide_atapi_pt_sg_io_finished(IDEState *s)
++static void ide_atapi_pt_do_sg_io(IDEState *s)
+{
++ struct sg_io_v4 *cmd = &s->atapi_pt.cmd;
+ BDRVRawState *raw_state = s->bs->opaque;
-+ int read_bytes;
-+ int i;
++ int r;
++ uint8_t cmd_code = s->atapi_pt.request[0];
++ uint32_t din_desired;
+
-+ ++finish_count;
-+ DEBUG_PRINTF("**** finish (%p, u=%p, raw=%p, fd=%d, %d-%d(%d))\n",
-+ s, s->atapi_pt.cmd.usr_ptr,
-+ raw_state, raw_state->fd, cmd_count,
-+ finish_count + no_finish_count, no_finish_count);
++ assert(cmd->din_xfer_len != (__u32)-1);
+
-+ DEBUG_PRINTF("Preread: 0x");
-+ for(i = 0; i < sizeof(s->atapi_pt.cmd); ++i)
-+ DEBUG_PRINTF("%02x", ((unsigned char *)&s->atapi_pt.cmd)[i]);
-+ DEBUG_PRINTF("\n");
++ s->atapi_pt.sense.error_code = 0;
++ s->atapi_pt.sense.sense_key = 0;
++ s->atapi_pt.sense.asc = 0;
++ s->atapi_pt.sense.ascq = 0;
+
-+#if 1
-+ read_bytes = read(raw_state->fd, &s->atapi_pt.cmd, sizeof (s->atapi_pt.cmd));
+
-+ DEBUG_PRINTF("Postread: 0x");
-+ for(i = 0; i < sizeof(s->atapi_pt.cmd); ++i)
-+ DEBUG_PRINTF("%02x", ((unsigned char *)&s->atapi_pt.cmd)[i]);
-+ DEBUG_PRINTF("\n");
-+#else
-+ DEBUG_PRINTF("Postread: 0x");
-+ for(i = 0; i < sizeof (s->atapi_pt.cmd); ++i) {
-+ read(raw_state->fd, &((unsigned char *)&s->atapi_pt.cmd)[i], 1);
-+ DEBUG_PRINTF("%02x", ((unsigned char *)&s->atapi_pt.cmd)[i]);
-+ }
-+ DEBUG_PRINTF("\n");
-+ read_bytes = i;
-+#endif
-+
-+ // DEBUG_PRINTF("finish read\n");
++ /* Send command and wait for reply, SG_IO ioctl*/
++ r = ioctl(raw_state->fd, 0x2285, cmd);
+
-+ if (read_bytes != sizeof (s->atapi_pt.cmd))
++ if(s->atapi_pt.request[0] == GPCMD_GET_EVENT_STATUS_NOTIFICATION)
+ {
-+ ide_atapi_pt_error(s);
-+ s->atapi_pt.cmd_sent = NULL;
-+ return;
++ struct stat file_stat;
++ int fd;
++
++ if(s->io_buffer[2] == 4 && s->io_buffer[4] == 2)
++ {
++ /* This is a "new media" message, tell any other VMs */
++ DEBUG_PRINTF("[ATAPI] new media detected\n");
++
++ fd = open(IDE_ATAPI_PT_NEW_CD_FILE, O_WRONLY | O_TRUNC | O_CREAT, 0666);
++ if(fd < 0) {
++ DEBUG_PRINTF("Error touching new CD file\n");
++ } else {
++ close(fd);
++ utime(IDE_ATAPI_PT_NEW_CD_FILE, NULL);
++ }
++
++ if(stat(IDE_ATAPI_PT_NEW_CD_FILE, &file_stat) != 0)
++ DEBUG_PRINTF("Error writing to new CD file\n");
++ else
++ s->atapi_pt.new_cd_time = file_stat.st_ctime;
++ }
++
++ if(s->io_buffer[2] == 4 && s->io_buffer[4] == 3)
++ {
++ /* This is a "media removed" message, tell any other VMs */
++ DEBUG_PRINTF("[ATAPI] media removed\n");
++
++ fd = open(IDE_ATAPI_PT_NEW_CD_FILE, O_WRONLY | O_TRUNC | O_CREAT, 0666);
++ if(fd < 0) {
++ DEBUG_PRINTF("Error touching new CD file\n");
++ } else {
++ close(fd);
++ utime(IDE_ATAPI_PT_NEW_CD_FILE, NULL);
++ }
++
++ if(stat(IDE_ATAPI_PT_EJECT_CD_FILE, &file_stat) != 0)
++ DEBUG_PRINTF("Error writing to eject CD file\n");
++ else
++ s->atapi_pt.eject_time = file_stat.st_ctime;
++ }
++
++ if((s->io_buffer[2] == 4 && s->io_buffer[4] == 0 && s->io_buffer[5] == 2) ||
++ (s->io_buffer[4] == 0 && s->io_buffer[5] == 0 &&
++ s->io_buffer[6] == 0 && s->io_buffer[7] == 0))
++ {
++ /* This is a no activity message we can hijack if we need to */
++
++ if(stat(IDE_ATAPI_PT_NEW_CD_FILE, &file_stat) == 0)
++ {
++ /* There's been a new media message that we haven't seen yet */
++ DEBUG_PRINTF("[ATAPI] new media message spotted\n");
++ s->atapi_pt.new_cd_time = file_stat.st_ctime;
++
++ s->io_buffer[2] = 4;
++ s->io_buffer[4] = 2;
++ s->io_buffer[5] = 2;
++ s->io_buffer[6] = 0;
++ s->io_buffer[7] = 0;
++ }
++ else if(stat(IDE_ATAPI_PT_EJECT_CD_FILE, &file_stat) == 0 &&
++ s->atapi_pt.eject_time < file_stat.st_ctime)
++ {
++ /* There's been an eject message that we haven't seen yet */
++ DEBUG_PRINTF("[ATAPI] media removed message spotted\n");
++ s->atapi_pt.eject_time = file_stat.st_ctime;
++
++ s->io_buffer[2] = 4;
++ s->io_buffer[4] = 3;
++ s->io_buffer[5] = 1;
++ s->io_buffer[6] = 0;
++ s->io_buffer[7] = 0;
++ }
++ }
+ }
+
-+ 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",
++ if(r || cmd->driver_status || cmd->transport_status ||
++ cmd->device_status) {
++ DEBUG_PRINTF("[\e[1;31mERROR\e[m] (%s) sense: 0x%02x,%02x,%02x (%s)\n",
++ atapi_cmd_to_str(s->atapi_pt.request[0]),
+ 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_sense_to_str(s->atapi_pt.sense.sense_key,
+ s->atapi_pt.sense.asc,
-+ 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);
++ s->atapi_pt.sense.ascq));
+ ide_atapi_pt_error(s);
-+ s->atapi_pt.cmd_sent = NULL;
+ return;
+ }
-+ if(s->atapi_pt.cmd_sent == NULL) {
-+ static int null_count = 0;
-+ ++null_count;
-+ DEBUG_PRINTF("\e[1;31m%s called with cmd_sent = NULL %d time%c\e[m\n",
-+ __FUNCTION__, null_count, (null_count == 1) ? ' ' : 's');
++
++ if(cmd->din_xfer_len == 0)
++ {
++ // Nothing else to do
+ ide_atapi_cmd_ok(s);
-+ s->atapi_pt.cmd_sent = NULL;
+ return;
+ }
-+ // DEBUG_PRINTF("finish sent\n");
-+ s->atapi_pt.cmd_sent(s);
-+ s->atapi_pt.cmd_sent = NULL;
-+ DEBUG_PRINTF("**** finished (%p)\n", 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;
++ din_desired = atapi_data_sizes[cmd_code].din_len_const +
++ (ide_atapi_pt_read_field_at_offset(s->io_buffer,
++ atapi_data_sizes[cmd_code].din_len_offset,
++ atapi_data_sizes[cmd_code].din_len_size) *
++ atapi_data_sizes[cmd_code].din_block_size);
++
++ /* cmd->din_xfer_len is the size of the buffer provided for the din data,
++ * din_desired in the size of the data the LU tried to send to us. Either one
++ * may be bigger. */
++
++ if(s->atapi_pt.request[0] == GPCMD_READ_BUFFER)
++ {
++ switch (s->atapi_pt.request[1] & 7)
++ {
++ case 0: // data with header, as specified in atapi_data_sizes table
++ break;
++
++ case 2: // data only
++ din_desired = cmd->din_xfer_len;
++ break;
++
++ case 3: // header only
++ din_desired = 4;
++ break;
++
++ case 1: // vendor specific
++ default:
++ DEBUG_PRINTF("\e[3;31mIllegal read buffer mode %d\e[m\n",
++ s->io_buffer[1] & 7);
++ ide_atapi_pt_set_error(s, SENSE_ILLEGAL_REQUEST,
++ ASC_ILLEGAL_OPCODE, 0x70);
++ return;
++ }
++ }
++
++ if(din_desired == (__u32)-1)
++ din_desired = cmd->din_xfer_len;
++
++ DEBUG_PRINTF("Reply, %d in %d\n", din_desired, cmd->din_xfer_len);
++ ide_atapi_cmd_reply(s, din_desired, cmd->din_xfer_len);
+}
+
-+static void ide_atapi_pt_read_pio_end(IDEState *s)
++static void ide_atapi_pt_dout_fetch_pio_done(IDEState *s)
+{
+ ide_transfer_stop(s);
-+ ide_atapi_pt_read_finish(s);
++ ide_atapi_pt_do_sg_io(s);
+}
+
-+static void ide_atapi_pt_read_dma_cb(void *opaque, int ret)
++static void ide_atapi_pt_dout_fetch_dma_done(void *opaque, int ret)
+{
+ BMDMAState *bm = opaque;
+ IDEState *s = bm->ide_if;
+ }
+
+ i = dma_buf_rw(bm, 0);
-+ ide_atapi_pt_read_finish(s);
++ ide_atapi_pt_do_sg_io(s);
+}
-+#endif
+
-+#if 0
+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);
++ ide_dma_start(s, ide_atapi_pt_dout_fetch_dma_done);
+ return;
+ }
+
+ ~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_atapi_pt_dout_fetch_pio_done);
+ ide_set_irq(s);
+ return;
+}
-+#endif
-+
-+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;
-+ }
-+ 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)
+{
+ return block_size;
+}
+
-+
+static void ide_atapi_pt_cmd(IDEState *s)
+{
+ struct sg_io_v4 *cmd = &s->atapi_pt.cmd;
-+ int r;
-+ int command;
-+ BDRVRawState *raw_state = s->bs->opaque;
++ uint8_t cmd_code;
+
-+ memset(cmd, 0, sizeof (*cmd));
++ memset(cmd, 0, sizeof(*cmd));
+ memcpy(s->atapi_pt.request, s->io_buffer, ATAPI_PACKET_SIZE);
++ cmd_code = s->atapi_pt.request[0];
+ 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->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;
++ s->status |= BUSY_STAT;
+
+ cmd->din_xferp = (__u64)s->io_buffer;
-+ cmd->dout_xferp = (__u64)(s->io_buffer + ATAPI_PACKET_SIZE);
-+ s->atapi_pt.cmd_sent = ide_atapi_cmd_ok;
-+ command = s->io_buffer[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);
-+ break;
-+
-+ /******************/
-+ /* WRITE COMMANDS */
-+ /******************/
++ cmd->dout_xferp = (__u64)s->io_buffer;
+
-+ case GPCMD_WRITE_10:
-+ case GPCMD_WRITE_AND_VERIFY_10:
-+ cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 7) * CD_FRAMESIZE;
++ DEBUG_PRINTF("[ATAPI] sending command: 0x%02x (\e[0;32m%s\e[m)\n",
++ cmd_code, atapi_cmd_to_str(cmd_code));
+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
++ cmd->dout_xfer_len = atapi_data_sizes[cmd_code].dout_len_const +
++ (ide_atapi_pt_read_field_at_offset(s->atapi_pt.request,
++ atapi_data_sizes[cmd_code].dout_len_offset,
++ atapi_data_sizes[cmd_code].dout_len_size) *
++ atapi_data_sizes[cmd_code].dout_block_size);
+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ break;
-+
-+ case GPCMD_WRITE_12:
-+ cmd->dout_xfer_len = ube32_to_cpu(s->io_buffer + 6);
++ cmd->din_xfer_len = atapi_data_sizes[cmd_code].alloc_len_const +
++ (ide_atapi_pt_read_field_at_offset(s->atapi_pt.request,
++ atapi_data_sizes[cmd_code].alloc_len_offset,
++ atapi_data_sizes[cmd_code].alloc_len_size) *
++ atapi_data_sizes[cmd_code].alloc_block_size);
+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ case GPCMD_WRITE_BUFFER:
++ /* A few commands need special attention */
++ switch(cmd_code)
+ {
-+ 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;
-+ }
-+
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
-+
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
++ case GPCMD_BLANK: // bigger timeout while blanking
++ cmd->timeout = 1000 * 60 * 80; // 80 mins
+ break;
-+ }
-+
-+ case GPCMD_SEND_CUE_SHEET:
-+ cmd->dout_xfer_len = ube24_to_cpu(s->io_buffer + 6);
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
++ case GPCMD_CLOSE_TRACK:
++ cmd->timeout = 1000 * 60 * 5; // 5 mins
+ break;
+
-+ 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);
-+
-+ DEBUG_PRINTF("Mode select 10 %d bytes\n", cmd->dout_xfer_len);
++ case GPCMD_WRITE_BUFFER:
++ if((s->io_buffer[1] & 7) != 0 && // Combined header and data mode
++ (s->io_buffer[1] & 7) != 2) // Data mode
+ {
-+ int i;
-+ DEBUG_PRINTF("0x");
-+ for(i = 0; i < 12; ++i)
-+ DEBUG_PRINTF("%02x ", s->io_buffer[i]);
-+ DEBUG_PRINTF("\n0x");
-+ for(i = 0; i < cmd->dout_xfer_len; ++i)
-+ DEBUG_PRINTF("%02x ", s->io_buffer[i + 12]);
-+ DEBUG_PRINTF("\n");
++ DEBUG_PRINTF("\e[3;31mIllegal write buffer mode %d\e[m\n",
++ s->io_buffer[1] & 7);
++ ide_atapi_pt_set_error(s, SENSE_ILLEGAL_REQUEST,
++ ASC_ILLEGAL_OPCODE, 0x70);
++ return;
+ }
+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ case GPCMD_SEND_KEY:
-+ case GPCMD_SEND_EVENT:
-+ cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 8);
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
-+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ 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);
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
-+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ case GPCMD_SET_STREAMING:
-+ cmd->dout_xfer_len = ube16_to_cpu(s->io_buffer + 9);
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
-+
-+ if (cmd->dout_xfer_len == 0)
-+ goto simple_cmd;
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ case GPCMD_FORMAT_UNIT:
-+ cmd->dout_xfer_len = 12;
-+
-+ DEBUG_PRINTF("Write (%s) %d bytes\n", atapi_cmd_to_str(command),
-+ cmd->dout_xfer_len);
-+
-+ // ide_atapi_pt_wcmd(s);
-+ // return;
-+ break;
-+
-+ /*****************/
-+ /* 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:
+ int max_size = s->io_buffer[4];
+
+ int size = 8 + s->atapi_pt.sense.add_sense_len;
-+
-+ // 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 = 2;
-+ // ATAPI_PT_SEND_PACKET;
-+ // return;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ s->atapi_pt.reply_size_init = 2;
-+ // ATAPI_PT_SEND_PACKET;
-+ // return;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ case GPCMD_READ_10:
-+ case GPCMD_READ_12:
-+ {
-+ int blocksize = 0, nbblocks;
-+
-+ 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->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;
-+ break;
-+ }
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
-+ 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;
-+ break;
-+
+ 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;
++ // We read blocks, not bytes
++ cmd->din_xfer_len *= ide_atapi_pt_read_cd_block_size(s->io_buffer);
+ break;
-+ }
+
+ case GPCMD_READ_CD_MSF:
+ {
+ 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;
-+ break;
-+ }
-+
-+ 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;
+ break;
+ }
+
+ 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;
+ break;
+ }
+
-+ 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;
-+ break;
++ case GPCMD_GET_PERFORMANCE:
++ // din_xfer_len is currently the max number of descriptors
++ switch(s->io_buffer[10])
++ {
++ case 0: // performance
++ if((s->io_buffer[1] & 3) == 0) // nominal performance, 16 byte descriptors
++ cmd->din_xfer_len *= 16;
++ else // exceptions, 6 byte descriptors
++ cmd->din_xfer_len *= 6;
+
-+ 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;
-+ break;
++ cmd->din_xfer_len += 8; // 8 bytes of header
++ break;
+
-+ 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;
-+ break;
++ case 1: // unusable area
++ cmd->din_xfer_len *= 8; // 8 byte descriptors
++ cmd->din_xfer_len += 8; // 8 bytes of header
++ break;
+
-+ 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;
++ case 2: // defect status
++ cmd->din_xfer_len *= 2048; // 2048 byte descriptors
++ cmd->din_xfer_len += 8; // 8 bytes of header
++ break;
+
-+ default:
-+ DEBUG_PRINTF("[UNHANDLED] 0x%02x\n", s->io_buffer[0]);
-+ exit(1);
-+ return;
++ default:
++ DEBUG_PRINTF("\e[3;31mReserved get performance type %d\e[m\n",
++ s->io_buffer[10]);
++ ide_atapi_pt_set_error(s, SENSE_ILLEGAL_REQUEST,
++ ASC_ILLEGAL_OPCODE, 0x70);
++ return;
++ }
++ break;
+ }
+
-+
-+ s->atapi_pt.sense.error_code = 0;
-+ s->atapi_pt.sense.sense_key = 0;
-+ s->atapi_pt.sense.asc = 0;
-+ s->atapi_pt.sense.ascq = 0;
-+
-+ /* Send command and wait for reply, SG_IO ioctl*/
-+ r = ioctl(raw_state->fd, 0x2285, cmd);
-+
-+ if(command == GPCMD_GET_EVENT_STATUS_NOTIFICATION)
++ if(cmd->dout_xfer_len == (__u32)-1)
+ {
-+ struct stat file_stat;
-+
-+ if(s->io_buffer[2] == 4 && s->io_buffer[4] == 2)
-+ {
-+ /* This is a "new media" message, tell any other VMs */
-+ DEBUG_PRINTF("[ATAPI] new media\n");
-+ system("touch " IDE_ATAPI_PT_NEW_CD_FILE);
-+
-+ if(stat(IDE_ATAPI_PT_NEW_CD_FILE, &file_stat) != 0)
-+ DEBUG_PRINTF("Error writing to new CD file\n");
-+ else
-+ s->atapi_pt.new_cd_time = file_stat.st_ctime;
-+ }
-+
-+ if(s->io_buffer[2] == 4 && s->io_buffer[4] == 3)
-+ {
-+ /* This is a "media removed" message, tell any other VMs */
-+ DEBUG_PRINTF("[ATAPI] media removed\n");
-+ system("touch " IDE_ATAPI_PT_EJECT_CD_FILE);
-+
-+ if(stat(IDE_ATAPI_PT_EJECT_CD_FILE, &file_stat) != 0)
-+ DEBUG_PRINTF("Error writing to eject CD file\n");
-+ else
-+ s->atapi_pt.eject_time = file_stat.st_ctime;
-+ }
-+
-+ if((s->io_buffer[2] == 4 && s->io_buffer[4] == 0 && s->io_buffer[5] == 2) ||
-+ (s->io_buffer[4] == 0 && s->io_buffer[5] == 0 &&
-+ s->io_buffer[6] == 0 && s->io_buffer[7] == 0))
-+ {
-+ /* This is a no activity message we can hijack if we need to */
-+
-+ if(stat(IDE_ATAPI_PT_NEW_CD_FILE, &file_stat) == 0 &&
-+ s->atapi_pt.new_cd_time < file_stat.st_ctime)
-+ {
-+ /* There's been a new media message that we haven't seen yet */
-+ DEBUG_PRINTF("[ATAPI] new media message spotted\n");
-+ s->atapi_pt.new_cd_time = file_stat.st_ctime;
-+
-+ s->io_buffer[2] = 4;
-+ s->io_buffer[4] = 2;
-+ s->io_buffer[5] = 2;
-+ s->io_buffer[6] = 0;
-+ s->io_buffer[7] = 0;
-+ }
-+ else if(stat(IDE_ATAPI_PT_EJECT_CD_FILE, &file_stat) == 0 &&
-+ s->atapi_pt.eject_time < file_stat.st_ctime)
-+ {
-+ /* There's been an eject message that we haven't seen yet */
-+ DEBUG_PRINTF("[ATAPI] media removed message spotted\n");
-+ s->atapi_pt.eject_time = file_stat.st_ctime;
-+
-+ s->io_buffer[2] = 4;
-+ s->io_buffer[4] = 3;
-+ s->io_buffer[5] = 1;
-+ s->io_buffer[6] = 0;
-+ s->io_buffer[7] = 0;
-+ }
-+ }
++ DEBUG_PRINTF("[UNHANDLED SCSI COMMAND] 0x%02x\n", cmd_code);
++ exit(1);
+ }
+
-+ if(r || cmd->driver_status || cmd->transport_status ||
-+ cmd->device_status) {
-+ /*
-+ DEBUG_PRINTF("[\e[1;31mERROR\e[m]\n"
-+ "\tcommand 0x%02x (%s)\n"
-+ "\terrno: %d (%s)\n"
-+ "\tsense: 0x%02x,%02x,%02x (%s)\n"
-+ "\tdriver: %d, transport: %d, device: %d\n",
-+ command, atapi_cmd_to_str(command),
-+ errno,
-+ strerror(errno) ? : "(null)",
-+ s->atapi_pt.sense.sense_key,
-+ s->atapi_pt.sense.asc,
-+ s->atapi_pt.sense.ascq,
-+ atapi_sense_to_str(s->atapi_pt.sense.sense_key,
-+ s->atapi_pt.sense.asc,
-+ s->atapi_pt.sense.ascq),
-+ cmd->driver_status,
-+ cmd->transport_status,
-+ cmd->device_status);
-+ */
-+ DEBUG_PRINTF("[\e[1;31mERROR\e[m] (%s) sense: 0x%02x,%02x,%02x (%s)\n",
-+ atapi_cmd_to_str(command),
-+ s->atapi_pt.sense.sense_key,
-+ s->atapi_pt.sense.asc,
-+ s->atapi_pt.sense.ascq,
-+ atapi_sense_to_str(s->atapi_pt.sense.sense_key,
-+ s->atapi_pt.sense.asc,
-+ s->atapi_pt.sense.ascq));
-+ ide_atapi_pt_error(s);
++ if(cmd->dout_xfer_len > 0)
++ {
++ ide_atapi_pt_wcmd(s);
+ return;
+ }
+
-+ s->atapi_pt.cmd_sent(s);
++ ide_atapi_pt_do_sg_io(s);
+}
diff --git a/hw/ide.c b/hw/ide.c
index e8d676e..6c1d60a 100644
s->drive_serial = drive_serial++;
strncpy(s->drive_serial_str, drive_get_serial(s->bs),
diff --git a/vl.c b/vl.c
-index cd354de..b5dfbd5 100644
+index f3b0dae..ae461c8 100644
--- a/vl.c
+++ b/vl.c
@@ -2192,8 +2192,10 @@ static int bt_parse(const char *opt)