[PATCH 0/2] Real SCSI device passthrough (v5)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

[PATCH 0/2] Real SCSI device passthrough (v5)

Laurent Vivier-4

This series of patches allows to connect real SCSI device to the virtual
SCSI controller of Qemu using the SCSI Generic interface (/dev/sg)

for instance:

qemu -hda my_disk.qcow2 -drive file=/dev/sg3,if=scsi

[PATCH 1/2] Real SCSI device passthrough (v5)

This patch allows to connect the virtual SCSI interface of Qemu to
a real SCSI device of the host.

[PATCH 2/2] Real SCSI device DMA split (v5)

This patch allows to split a READ or WRITE into several READ or WRITE.

Laurent



Reply | Threaded
Open this post in threaded view
|

[PATCH 1/2] Real SCSI device passthrough (v5)

Laurent Vivier-4

This patch allows to connect the virtual SCSI interface of Qemu to
a real SCSI device of the host.
Using the devices /dev/sg, it allows to send the SCSI commands from the
virtual SCSI interface to the real device.

It has been tested with a SATA disk and an ATA CD burner with ide-scsi module
and wodim to burn a CD-RW.

v5 allows to read movies from a DVD. It has been tested with PowerDVD and XP with unprotected DVD.

Laurent
---
 Makefile          |    1
 block-raw-posix.c |   27 +-
 block.c           |   16 +
 block.h           |    2
 block_int.h       |    4
 hw/esp.c          |    4
 hw/lsi53c895a.c   |    8
 hw/scsi-disk.h    |    2
 hw/scsi-generic.c |  653 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 710 insertions(+), 7 deletions(-)

Index: qemu/Makefile
===================================================================
--- qemu.orig/Makefile 2007-12-18 10:58:12.000000000 +0100
+++ qemu/Makefile 2007-12-19 11:27:16.000000000 +0100
@@ -56,6 +56,7 @@ OBJS+=irq.o
 OBJS+=i2c.o smbus.o smbus_eeprom.o max7310.o max111x.o wm8750.o
 OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o
 OBJS+=scsi-disk.o cdrom.o
+OBJS+=scsi-generic.o
 OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o
 OBJS+=sd.o ssi-sd.o
 
Index: qemu/block.c
===================================================================
--- qemu.orig/block.c 2007-12-18 11:01:01.000000000 +0100
+++ qemu/block.c 2007-12-19 11:27:16.000000000 +0100
@@ -786,6 +786,11 @@ int bdrv_is_read_only(BlockDriverState *
     return bs->read_only;
 }
 
+int bdrv_is_sg(BlockDriverState *bs)
+{
+    return bs->sg;
+}
+
 /* XXX: no longer used */
 void bdrv_set_change_cb(BlockDriverState *bs,
                         void (*change_cb)(void *opaque), void *opaque)
@@ -1394,3 +1399,14 @@ void bdrv_set_locked(BlockDriverState *b
         drv->bdrv_set_locked(bs, locked);
     }
 }
+
+/* needed for generic scsi interface */
+
+int bdrv_ioctl(BlockDriverState *bs,  unsigned long int req, void *buf)
+{
+    BlockDriver *drv = bs->drv;
+
+    if (drv && drv->bdrv_ioctl)
+        return drv->bdrv_ioctl(bs, req, buf);
+    return -ENOTSUP;
+}
Index: qemu/block.h
===================================================================
--- qemu.orig/block.h 2007-12-18 11:01:01.000000000 +0100
+++ qemu/block.h 2007-12-19 11:27:16.000000000 +0100
@@ -119,6 +119,7 @@ int bdrv_get_type_hint(BlockDriverState
 int bdrv_get_translation_hint(BlockDriverState *bs);
 int bdrv_is_removable(BlockDriverState *bs);
 int bdrv_is_read_only(BlockDriverState *bs);
+int bdrv_is_sg(BlockDriverState *bs);
 int bdrv_is_inserted(BlockDriverState *bs);
 int bdrv_media_changed(BlockDriverState *bs);
 int bdrv_is_locked(BlockDriverState *bs);
@@ -148,6 +149,7 @@ int bdrv_snapshot_delete(BlockDriverStat
 int bdrv_snapshot_list(BlockDriverState *bs,
                        QEMUSnapshotInfo **psn_info);
 char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn);
+int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf);
 
 char *get_human_readable_size(char *buf, int buf_size, int64_t size);
 int path_is_absolute(const char *path);
Index: qemu/block_int.h
===================================================================
--- qemu.orig/block_int.h 2007-12-18 10:58:13.000000000 +0100
+++ qemu/block_int.h 2007-12-19 11:27:16.000000000 +0100
@@ -82,6 +82,9 @@ struct BlockDriver {
     int (*bdrv_eject)(BlockDriverState *bs, int eject_flag);
     int (*bdrv_set_locked)(BlockDriverState *bs, int locked);
 
+    /* to control generic scsi devices */
+    int (*bdrv_ioctl)(BlockDriverState *bs, unsigned long int req, char *buf);
+
     BlockDriverAIOCB *free_aiocb;
     struct BlockDriver *next;
 };
@@ -93,6 +96,7 @@ struct BlockDriverState {
     int removable; /* if true, the media can be removed */
     int locked;    /* if true, the media cannot temporarily be ejected */
     int encrypted; /* if true, the media is encrypted */
+    int sg;        /* if true, the device is a /dev/sg* */
     /* event callback when inserting/removing */
     void (*change_cb)(void *opaque);
     void *change_opaque;
Index: qemu/hw/esp.c
===================================================================
--- qemu.orig/hw/esp.c 2007-12-18 10:58:12.000000000 +0100
+++ qemu/hw/esp.c 2007-12-19 11:27:16.000000000 +0100
@@ -615,7 +615,9 @@ void esp_scsi_attach(void *opaque, Block
     }
     DPRINTF("Attaching block device %d\n", id);
     /* Command queueing is not implemented.  */
-    s->scsi_dev[id] = scsi_disk_init(bd, 0, esp_command_complete, s);
+    s->scsi_dev[id] = scsi_generic_init(bd, 0, esp_command_complete, s);
+    if (s->scsi_dev[id] == NULL)
+        s->scsi_dev[id] = scsi_disk_init(bd, 0, esp_command_complete, s);
 }
 
 void *esp_init(target_phys_addr_t espaddr,
Index: qemu/hw/lsi53c895a.c
===================================================================
--- qemu.orig/hw/lsi53c895a.c 2007-12-18 10:58:12.000000000 +0100
+++ qemu/hw/lsi53c895a.c 2007-12-19 11:27:16.000000000 +0100
@@ -1236,6 +1236,8 @@ static uint8_t lsi_reg_readb(LSIState *s
         return s->sdid;
     case 0x07: /* GPREG0 */
         return 0x7f;
+    case 0x08: /* Revision ID */
+        return 0x00;
     case 0xa: /* SSID */
         return s->ssid;
     case 0xb: /* SBCL */
@@ -1281,6 +1283,8 @@ static uint8_t lsi_reg_readb(LSIState *s
         return s->ctest4;
     case 0x22: /* CTEST5 */
         return s->ctest5;
+    case 0x23: /* CTEST6 */
+         return 0;
     case 0x24: /* DBC[0:7] */
         return s->dbc & 0xff;
     case 0x25: /* DBC[8:15] */
@@ -1838,7 +1842,9 @@ void lsi_scsi_attach(void *opaque, Block
         s->scsi_dev[id]->destroy(s->scsi_dev[id]);
     }
     DPRINTF("Attaching block device %d\n", id);
-    s->scsi_dev[id] = scsi_disk_init(bd, 1, lsi_command_complete, s);
+    s->scsi_dev[id] = scsi_generic_init(bd, 1, lsi_command_complete, s);
+    if (s->scsi_dev[id] == NULL)
+        s->scsi_dev[id] = scsi_disk_init(bd, 1, lsi_command_complete, s);
 }
 
 void *lsi_scsi_init(PCIBus *bus, int devfn)
Index: qemu/hw/scsi-disk.h
===================================================================
--- qemu.orig/hw/scsi-disk.h 2007-12-18 10:58:12.000000000 +0100
+++ qemu/hw/scsi-disk.h 2007-12-19 11:27:16.000000000 +0100
@@ -26,6 +26,8 @@ struct SCSIDevice
 
 SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq,
                            scsi_completionfn completion, void *opaque);
+SCSIDevice *scsi_generic_init(BlockDriverState *bdrv, int tcq,
+                           scsi_completionfn completion, void *opaque);
 
 /* cdrom.c */
 int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track);
Index: qemu/hw/scsi-generic.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ qemu/hw/scsi-generic.c 2007-12-19 14:38:07.000000000 +0100
@@ -0,0 +1,653 @@
+/*
+ * Generic SCSI Device support
+ *
+ * Copyright (c) 2007 Bull S.A.S.
+ * Based on code by Paul Brook
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Laurent Vivier <[hidden email]>
+ *
+ * This code is licenced under the LGPL.
+ *
+ */
+
+#include "qemu-common.h"
+#include "block.h"
+#include "scsi-disk.h"
+
+#ifndef __linux__
+
+SCSIDevice *scsi_generic_init(BlockDriverState *bdrv, int tcq,
+                              scsi_completionfn completion, void *opaque)
+{
+    return NULL;
+}
+
+#else /* __linux__ */
+
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, args...) \
+do { printf("scsi-generic: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while(0)
+#endif
+
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "scsi-generic: " fmt , ##args); } while (0)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <scsi/sg.h>
+#include <scsi/scsi.h>
+
+#define LOAD_UNLOAD 0xa6
+#define SET_CD_SPEED 0xbb
+#define BLANK 0xa1
+
+#define SCSI_CMD_BUF_SIZE     16
+#define SCSI_SENSE_BUF_SIZE 32
+
+#define SG_ERR_DRIVER_TIMEOUT 0x06
+#define SG_ERR_DRIVER_SENSE 0x08
+
+#ifndef MAX_UINT
+#define MAX_UINT ((unsigned int)-1)
+#endif
+
+typedef struct SCSIRequest {
+    BlockDriverAIOCB *aiocb;
+    struct SCSIRequest *next;
+    SCSIDeviceState *dev;
+    uint32_t tag;
+    uint8_t cmd[SCSI_CMD_BUF_SIZE];
+    int cmdlen;
+    uint8_t *buf;
+    int buflen;
+    int len;
+    sg_io_hdr_t io_header;
+} SCSIRequest;
+
+struct SCSIDeviceState
+{
+    SCSIRequest *requests;
+    BlockDriverState *bdrv;
+    int blocksize;
+    int lun;
+    scsi_completionfn completion;
+    void *opaque;
+    int driver_status;
+    uint8_t sensebuf[SCSI_SENSE_BUF_SIZE];
+};
+
+/* Global pool of SCSIRequest structures.  */
+static SCSIRequest *free_requests = NULL;
+
+static SCSIRequest *scsi_new_request(SCSIDeviceState *s, uint32_t tag)
+{
+    SCSIRequest *r;
+
+    if (free_requests) {
+        r = free_requests;
+        free_requests = r->next;
+    } else {
+        r = qemu_malloc(sizeof(SCSIRequest));
+        r->buf = NULL;
+        r->buflen = 0;
+    }
+    r->dev = s;
+    r->tag = tag;
+    memset(r->cmd, 0, sizeof(r->cmd));
+    memset(&r->io_header, 0, sizeof(r->io_header));
+    r->cmdlen = 0;
+    r->len = 0;
+    r->aiocb = NULL;
+
+    /* link */
+
+    r->next = s->requests;
+    s->requests = r;
+    return r;
+}
+
+static void scsi_remove_request(SCSIRequest *r)
+{
+    SCSIRequest *last;
+    SCSIDeviceState *s = r->dev;
+
+    if (s->requests == r) {
+        s->requests = r->next;
+    } else {
+        last = s->requests;
+        while (last && last->next != r)
+            last = last->next;
+        if (last) {
+            last->next = r->next;
+        } else {
+            BADF("Orphaned request\n");
+        }
+    }
+    r->next = free_requests;
+    free_requests = r;
+}
+
+static SCSIRequest *scsi_find_request(SCSIDeviceState *s, uint32_t tag)
+{
+    SCSIRequest *r;
+
+    r = s->requests;
+    while (r && r->tag != tag)
+        r = r->next;
+
+    return r;
+}
+
+/* Helper function for command completion.  */
+static void scsi_command_complete(void *opaque, int ret)
+{
+    SCSIRequest *r = (SCSIRequest *)opaque;
+    SCSIDeviceState *s = r->dev;
+    uint32_t tag;
+    int sense;
+
+    s->driver_status = r->io_header.driver_status;
+    if (ret != 0)
+        sense = HARDWARE_ERROR;
+    else {
+        if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) {
+            sense = HARDWARE_ERROR;
+            BADF("Driver Timeout\n");
+        } else if ((s->driver_status & SG_ERR_DRIVER_SENSE) == 0)
+            sense = NO_SENSE;
+        else
+            sense = s->sensebuf[2] & 0x0f;
+    }
+
+    DPRINTF("Command complete 0x%p tag=0x%x sense=%d\n", r, r->tag, sense);
+    tag = r->tag;
+    scsi_remove_request(r);
+    s->completion(s->opaque, SCSI_REASON_DONE, tag, sense);
+}
+
+/* Cancel a pending data transfer.  */
+static void scsi_cancel_io(SCSIDevice *d, uint32_t tag)
+{
+    DPRINTF("scsi_cancel_io 0x%x\n", tag);
+    SCSIDeviceState *s = d->state;
+    SCSIRequest *r;
+    DPRINTF("Cancel tag=0x%x\n", tag);
+    r = scsi_find_request(s, tag);
+    if (r) {
+        if (r->aiocb)
+            bdrv_aio_cancel(r->aiocb);
+        r->aiocb = NULL;
+        scsi_remove_request(r);
+    }
+}
+
+static int execute_command(BlockDriverState *bdrv,
+                           SCSIRequest *r, int direction,
+   BlockDriverCompletionFunc *complete)
+{
+
+    r->io_header.interface_id = 'S';
+    r->io_header.dxfer_direction = direction;
+    r->io_header.dxferp = r->buf;
+    r->io_header.dxfer_len = r->buflen;
+    r->io_header.cmdp = r->cmd;
+    r->io_header.cmd_len = r->cmdlen;
+    r->io_header.mx_sb_len = sizeof(r->dev->sensebuf);
+    r->io_header.sbp = r->dev->sensebuf;
+    r->io_header.timeout = MAX_UINT;
+    r->io_header.usr_ptr = r;
+    r->io_header.flags |= SG_FLAG_DIRECT_IO;
+
+    if (bdrv_pwrite(bdrv, -1, &r->io_header, sizeof(r->io_header)) == -1) {
+        BADF("execute_command: write failed ! (%d)\n", errno);
+        return -1;
+    }
+    if (complete == NULL) {
+        int ret;
+        r->aiocb = NULL;
+        while ((ret = bdrv_pread(bdrv, -1, &r->io_header,
+                                           sizeof(r->io_header))) == -1 &&
+                      errno == EINTR);
+        if (ret == -1) {
+            BADF("execute_command: read failed !\n");
+            return -1;
+        }
+        return 0;
+    }
+
+    r->aiocb = bdrv_aio_read(bdrv, 0, (uint8_t*)&r->io_header,
+                          -(int64_t)sizeof(r->io_header), complete, r);
+    if (r->aiocb == NULL) {
+        BADF("execute_command: read failed !\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+    SCSIRequest *r = (SCSIRequest *)opaque;
+    SCSIDeviceState *s = r->dev;
+    int len;
+
+    if (ret) {
+        DPRINTF("IO error\n");
+        scsi_command_complete(r, ret);
+        return;
+    }
+    len = r->io_header.dxfer_len - r->io_header.resid;
+    DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, len);
+
+    r->len = -1;
+    s->completion(s->opaque, SCSI_REASON_DATA, r->tag, len);
+}
+
+/* Read more data from scsi device into buffer.  */
+static void scsi_read_data(SCSIDevice *d, uint32_t tag)
+{
+    SCSIDeviceState *s = d->state;
+    SCSIRequest *r;
+    int ret;
+
+    DPRINTF("scsi_read_data 0x%x\n", tag);
+    r = scsi_find_request(s, tag);
+    if (!r) {
+        BADF("Bad read tag 0x%x\n", tag);
+        /* ??? This is the wrong error.  */
+        scsi_command_complete(r, -EINVAL);
+        return;
+    }
+
+    if (r->len == -1) {
+        scsi_command_complete(r, 0);
+        return;
+    }
+
+    if (r->cmd[0] == REQUEST_SENSE && s->driver_status & SG_ERR_DRIVER_SENSE)
+    {
+        memcpy(r->buf, s->sensebuf, 16);
+        r->io_header.driver_status = 0;
+        r->len = -1;
+        s->completion(s->opaque, SCSI_REASON_DATA, r->tag, 16);
+        return;
+    }
+
+    ret = execute_command(s->bdrv, r, SG_DXFER_FROM_DEV, scsi_read_complete);
+    if (ret == -1) {
+        scsi_command_complete(r, -EINVAL);
+        return;
+    }
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+    SCSIRequest *r = (SCSIRequest *)opaque;
+
+    DPRINTF("scsi_write_complete() ret = %d\n", ret);
+    if (ret) {
+        DPRINTF("IO error\n");
+        scsi_command_complete(r, ret);
+        return;
+    }
+
+    scsi_command_complete(r, ret);
+}
+
+/* Write data to a scsi device.  Returns nonzero on failure.
+   The transfer may complete asynchronously.  */
+static int scsi_write_data(SCSIDevice *d, uint32_t tag)
+{
+    SCSIDeviceState *s = d->state;
+    SCSIRequest *r;
+    int ret;
+
+    DPRINTF("scsi_write_data 0x%x\n", tag);
+    r = scsi_find_request(s, tag);
+    if (!r) {
+        BADF("Bad write tag 0x%x\n", tag);
+        /* ??? This is the wrong error.  */
+        scsi_command_complete(r, -EINVAL);
+        return 0;
+    }
+
+    if (r->len == 0) {
+        r->len = r->buflen;
+        s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->len);
+        return 0;
+    }
+
+    ret = execute_command(s->bdrv, r, SG_DXFER_TO_DEV, scsi_write_complete);
+    if (ret == -1) {
+        scsi_command_complete(r, -EINVAL);
+        return 1;
+    }
+
+    return 0;
+}
+
+/* Return a pointer to the data buffer.  */
+static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag)
+{
+    SCSIDeviceState *s = d->state;
+    SCSIRequest *r;
+    r = scsi_find_request(s, tag);
+    if (!r) {
+        BADF("Bad buffer tag 0x%x\n", tag);
+        return NULL;
+    }
+    return r->buf;
+}
+
+static int scsi_length(uint8_t *cmd, int blocksize, int *cmdlen, uint32_t *len)
+{
+    switch (cmd[0] >> 5) {
+    case 0:
+        *len = cmd[4];
+        *cmdlen = 6;
+        break;
+    case 1:
+    case 2:
+        *len = cmd[8] | (cmd[7] << 8);
+        *cmdlen = 10;
+        break;
+    case 4:
+        *len = cmd[13] | (cmd[12] << 8) | (cmd[11] << 16) | (cmd[10] << 24);
+        *cmdlen = 16;
+        break;
+    case 5:
+        *len = cmd[9] | (cmd[8] << 8) | (cmd[7] << 16) | (cmd[6] << 24);
+        *cmdlen = 12;
+        break;
+    default:
+        return -1;
+    }
+
+    switch(cmd[0]) {
+    case TEST_UNIT_READY:
+    case REZERO_UNIT:
+    case START_STOP:
+    case SEEK_6:
+    case WRITE_FILEMARKS:
+    case SPACE:
+    case ERASE:
+    case ALLOW_MEDIUM_REMOVAL:
+    case VERIFY:
+    case SEEK_10:
+    case SYNCHRONIZE_CACHE:
+    case LOCK_UNLOCK_CACHE:
+    case LOAD_UNLOAD:
+    case SET_CD_SPEED:
+    case SET_LIMITS:
+    case WRITE_LONG:
+    case MOVE_MEDIUM:
+    case UPDATE_BLOCK:
+        *len = 0;
+        break;
+    case MODE_SENSE:
+        break;
+    case WRITE_SAME:
+        *len = 1;
+        break;
+    case READ_CAPACITY:
+        *len = 8;
+        break;
+    case READ_BLOCK_LIMITS:
+        *len = 6;
+        break;
+    case READ_POSITION:
+        *len = 20;
+        break;
+    case SEND_VOLUME_TAG:
+        *len *= 40;
+        break;
+    case MEDIUM_SCAN:
+        *len *= 8;
+        break;
+    case WRITE_10:
+        cmd[1] &= ~0x08; /* disable FUA */
+    case WRITE_VERIFY:
+    case WRITE_6:
+    case WRITE_12:
+    case WRITE_VERIFY_12:
+        *len *= blocksize;
+        break;
+    case READ_10:
+        cmd[1] &= ~0x08; /* disable FUA */
+    case READ_6:
+    case READ_REVERSE:
+    case RECOVER_BUFFERED_DATA:
+    case READ_12:
+        *len *= blocksize;
+        break;
+    }
+    return 0;
+}
+
+static int is_write(int command)
+{
+    switch (command) {
+    case COPY:
+    case COPY_VERIFY:
+    case COMPARE:
+    case CHANGE_DEFINITION:
+    case LOG_SELECT:
+    case MODE_SELECT:
+    case MODE_SELECT_10:
+    case SEND_DIAGNOSTIC:
+    case WRITE_BUFFER:
+    case FORMAT_UNIT:
+    case REASSIGN_BLOCKS:
+    case RESERVE:
+    case SEARCH_EQUAL:
+    case SEARCH_HIGH:
+    case SEARCH_LOW:
+    case WRITE_6:
+    case WRITE_10:
+    case WRITE_VERIFY:
+    case UPDATE_BLOCK:
+    case WRITE_LONG:
+    case WRITE_SAME:
+    case SEARCH_HIGH_12:
+    case SEARCH_EQUAL_12:
+    case SEARCH_LOW_12:
+    case WRITE_12:
+    case WRITE_VERIFY_12:
+    case SET_WINDOW:
+    case MEDIUM_SCAN:
+    case SEND_VOLUME_TAG:
+    case WRITE_LONG_2:
+        return 1;
+    }
+    return 0;
+}
+
+/* Execute a scsi command.  Returns the length of the data expected by the
+   command.  This will be Positive for data transfers from the device
+   (eg. disk reads), negative for transfers to the device (eg. disk writes),
+   and zero if the command does not transfer any data.  */
+
+static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
+                                 uint8_t *cmd, int lun)
+{
+    SCSIDeviceState *s = d->state;
+    uint32_t len;
+    int cmdlen;
+    SCSIRequest *r;
+    int ret;
+
+    /* ??? Tags are not unique for different luns.  We only implement a
+       single lun, so this should not matter.  */
+
+    if (lun != s->lun || (cmd[1] >> 5) != s->lun) {
+        DPRINTF("Unimplemented LUN %d\n", lun ? lun : cmd[1] >> 5);
+        s->completion(s->opaque, SCSI_REASON_DONE, tag, ILLEGAL_REQUEST);
+        return 0;
+    }
+
+    if (scsi_length(cmd, s->blocksize, &cmdlen, &len) == -1) {
+        BADF("Unsupported command length, command %x\n", cmd[0]);
+        return 0;
+    }
+
+    DPRINTF("Command: lun=%d tag=0x%x data=0x%02x len %d\n", lun, tag,
+            cmd[0], len);
+
+    r = scsi_find_request(s, tag);
+    if (r) {
+        BADF("Tag 0x%x already in use %p\n", tag, r);
+        scsi_cancel_io(d, tag);
+    }
+    r = scsi_new_request(s, tag);
+
+    memcpy(r->cmd, cmd, cmdlen);
+    r->cmdlen = cmdlen;
+
+    if (len == 0) {
+        if (r->buf != NULL)
+            free(r->buf);
+        r->buflen = 0;
+        r->buf = NULL;
+        ret = execute_command(s->bdrv, r, SG_DXFER_NONE, scsi_command_complete);
+        if (ret == -1) {
+            scsi_command_complete(r, -EINVAL);
+            return 0;
+        }
+        return 0;
+    }
+
+    if (r->buflen != len) {
+        if (r->buf != NULL)
+            free(r->buf);
+        r->buf = qemu_malloc(len);
+        r->buflen = len;
+    }
+
+    memset(r->buf, 0, r->buflen);
+    r->len = len;
+    if (is_write(cmd[0])) {
+        r->len = 0;
+        return -len;
+    }
+
+    return len;
+}
+
+static int get_blocksize(BlockDriverState *bdrv)
+{
+    uint8_t cmd[10];
+    uint8_t buf[8];
+    uint8_t sensebuf[8];
+    sg_io_hdr_t io_header;
+    int ret;
+
+    memset(cmd, sizeof(cmd), 0);
+    memset(buf, sizeof(buf), 0);
+    cmd[0] = READ_CAPACITY;
+
+    memset(&io_header, 0, sizeof(io_header));
+    io_header.interface_id = 'S';
+    io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+    io_header.dxfer_len = sizeof(buf);
+    io_header.dxferp = buf;
+    io_header.cmdp = cmd;
+    io_header.cmd_len = sizeof(cmd);
+    io_header.mx_sb_len = sizeof(sensebuf);
+    io_header.sbp = sensebuf;
+    io_header.timeout = 6000; /* XXX */
+
+    ret = bdrv_pwrite(bdrv, -1, &io_header, sizeof(io_header));
+    if (ret == -1)
+        return -1;
+
+    while ((ret = bdrv_pread(bdrv, -1, &io_header, sizeof(io_header))) == -1 &&
+           errno == EINTR);
+
+    if (ret == -1)
+        return -1;
+
+    return (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+}
+
+static void scsi_destroy(SCSIDevice *d)
+{
+    SCSIRequest *r, *n;
+
+    r = d->state->requests;
+    while (r) {
+        n = r->next;
+        qemu_free(r);
+        r = n;
+    }
+
+    r = free_requests;
+    while (r) {
+        n = r->next;
+        qemu_free(r);
+        r = n;
+    }
+
+    qemu_free(d->state);
+    qemu_free(d);
+}
+
+SCSIDevice *scsi_generic_init(BlockDriverState *bdrv, int tcq,
+                              scsi_completionfn completion, void *opaque)
+{
+    int sg_version;
+    SCSIDevice *d;
+    SCSIDeviceState *s;
+    struct sg_scsi_id scsiid;
+
+    /* check we are really using a /dev/sg* file */
+
+    if (!bdrv_is_sg(bdrv))
+        return NULL;
+
+    /* check we are using a driver managing SG_IO (version 3 and after */
+
+    if (bdrv_ioctl(bdrv, SG_GET_VERSION_NUM, (char*)&sg_version) < 0 ||
+        sg_version < 30000)
+        return NULL;
+
+    /* get LUN of the /dev/sg? */
+
+    if (bdrv_ioctl(bdrv, SG_GET_SCSI_ID, &scsiid))
+        return NULL;
+
+    /* define device state */
+
+    s = (SCSIDeviceState *)qemu_mallocz(sizeof(SCSIDeviceState));
+    s->bdrv = bdrv;
+    s->requests = NULL;
+    s->completion = completion;
+    s->opaque = opaque;
+    s->lun = scsiid.lun;
+    s->blocksize = get_blocksize(s->bdrv);
+    s->driver_status = 0;
+    memset(s->sensebuf, 0, sizeof(s->sensebuf));
+    /* removable media returns 0 if not present */
+    if (s->blocksize <= 0)
+        s->blocksize = 2048;
+
+    /* define function to manage device */
+
+    d = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice));
+    d->state = s;
+    d->destroy = scsi_destroy;
+    d->send_command = scsi_send_command;
+    d->read_data = scsi_read_data;
+    d->write_data = scsi_write_data;
+    d->cancel_io = scsi_cancel_io;
+    d->get_buf = scsi_get_buf;
+
+    return d;
+}
+#endif /* __linux__ */
Index: qemu/block-raw-posix.c
===================================================================
--- qemu.orig/block-raw-posix.c 2007-12-18 15:06:21.000000000 +0100
+++ qemu/block-raw-posix.c 2007-12-19 11:27:16.000000000 +0100
@@ -151,7 +151,7 @@ static int raw_pread(BlockDriverState *b
     if (ret < 0)
         return ret;
 
-    if (lseek(s->fd, offset, SEEK_SET) == (off_t)-1) {
+    if (offset >= 0 && lseek(s->fd, offset, SEEK_SET) == (off_t)-1) {
         ++(s->lseek_err_cnt);
         if(s->lseek_err_cnt <= 10) {
             DEBUG_BLOCK_PRINT("raw_pread(%d:%s, %" PRId64 ", %p, %d) [%" PRId64
@@ -204,7 +204,7 @@ static int raw_pwrite(BlockDriverState *
     if (ret < 0)
         return ret;
 
-    if (lseek(s->fd, offset, SEEK_SET) == (off_t)-1) {
+    if (offset >= 0 && lseek(s->fd, offset, SEEK_SET) == (off_t)-1) {
         ++(s->lseek_err_cnt);
         if(s->lseek_err_cnt) {
             DEBUG_BLOCK_PRINT("raw_pwrite(%d:%s, %" PRId64 ", %p, %d) [%"
@@ -276,8 +276,8 @@ void qemu_aio_init(void)
            seems to fix the problem. */
         struct aioinit ai;
         memset(&ai, 0, sizeof(ai));
-        ai.aio_threads = 1;
-        ai.aio_num = 1;
+        ai.aio_threads = 16;
+        ai.aio_num = 16;
         ai.aio_idle_time = 365 * 100000;
         aio_init(&ai);
     }
@@ -387,7 +387,10 @@ static RawAIOCB *raw_aio_setup(BlockDriv
     acb->aiocb.aio_sigevent.sigev_signo = aio_sig_num;
     acb->aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
     acb->aiocb.aio_buf = buf;
-    acb->aiocb.aio_nbytes = nb_sectors * 512;
+    if (nb_sectors < 0)
+        acb->aiocb.aio_nbytes = -nb_sectors;
+    else
+        acb->aiocb.aio_nbytes = nb_sectors * 512;
     acb->aiocb.aio_offset = sector_num * 512;
     acb->next = first_aio;
     first_aio = acb;
@@ -679,6 +682,8 @@ static int hdev_open(BlockDriverState *b
         s->fd_open_flags = open_flags;
         /* open will not fail even if no floppy is inserted */
         open_flags |= O_NONBLOCK;
+    } else if (strstart(filename, "/dev/sg", NULL)) {
+        bs->sg = 1;
     }
 #endif
     fd = open(filename, open_flags, 0644);
@@ -858,6 +863,12 @@ static int raw_set_locked(BlockDriverSta
     return 0;
 }
 
+static int raw_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
+{
+    BDRVRawState *s = bs->opaque;
+
+    return ioctl(s->fd, req, buf);
+}
 #else
 
 static int raw_is_inserted(BlockDriverState *bs)
@@ -880,6 +891,10 @@ static int raw_set_locked(BlockDriverSta
     return -ENOTSUP;
 }
 
+static int raw_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
+{
+    return -ENOTSUP;
+}
 #endif /* !linux */
 
 BlockDriver bdrv_host_device = {
@@ -906,4 +921,6 @@ BlockDriver bdrv_host_device = {
     .bdrv_media_changed = raw_media_changed,
     .bdrv_eject = raw_eject,
     .bdrv_set_locked = raw_set_locked,
+    /* generic scsi device */
+    .bdrv_ioctl = raw_ioctl,
 };



Reply | Threaded
Open this post in threaded view
|

[PATCH 2/2] Real SCSI device DMA split (v5)

Laurent Vivier-4

With some emulated SCSI devices, like usb-storage or ide-scsi, DMA transfers
are limited to 64 kiB or 32 kiB.
This patch allows to split a READ or WRITE into several READ or WRITE.

Laurent
---
 hw/scsi-generic.c |  104 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 100 insertions(+), 4 deletions(-)

Index: qemu/hw/scsi-generic.c
===================================================================
--- qemu.orig/hw/scsi-generic.c 2007-12-20 10:55:24.000000000 +0100
+++ qemu/hw/scsi-generic.c 2007-12-20 16:23:24.000000000 +0100
@@ -61,6 +61,8 @@ do { fprintf(stderr, "scsi-generic: " fm
 #define MAX_UINT ((unsigned int)-1)
 #endif
 
+#define MAX_CHUNK 65536
+
 typedef struct SCSIRequest {
     BlockDriverAIOCB *aiocb;
     struct SCSIRequest *next;
@@ -72,6 +74,8 @@ typedef struct SCSIRequest {
     int buflen;
     int len;
     sg_io_hdr_t io_header;
+    int remaining;
+    int offset;
 } SCSIRequest;
 
 struct SCSIDeviceState
@@ -84,6 +88,7 @@ struct SCSIDeviceState
     void *opaque;
     int driver_status;
     uint8_t sensebuf[SCSI_SENSE_BUF_SIZE];
+    int max_chunk;
 };
 
 /* Global pool of SCSIRequest structures.  */
@@ -101,6 +106,7 @@ static SCSIRequest *scsi_new_request(SCS
         r->buf = NULL;
         r->buflen = 0;
     }
+    r->offset = 0;
     r->dev = s;
     r->tag = tag;
     memset(r->cmd, 0, sizeof(r->cmd));
@@ -191,24 +197,90 @@ static void scsi_cancel_io(SCSIDevice *d
     }
 }
 
+static void scsi_cmd_next(uint8_t *cmd, uint32_t inc)
+{
+    uint32_t addr;
+    switch (cmd[0] >> 5) {
+    case 0:
+        addr = cmd[3] | (cmd[2] << 8);
+        addr += inc;
+        cmd[2] = addr >> 8;
+        cmd[3] = addr;
+        break;
+    case 1:
+    case 2:
+    case 4:
+    case 5:
+        addr = cmd[5] | ((cmd[4] << 8) | ((cmd[3] << 16) | (cmd[2] << 24)));
+        addr += inc;
+        cmd[2] = addr >> 24;
+        cmd[3] = addr >> 16;
+        cmd[4] = addr >> 8;
+        cmd[5] = addr;
+        break;
+    }
+}
+static void scsi_set_length(uint8_t *cmd, uint32_t len)
+{
+    switch (cmd[0] >> 5) {
+    case 0:
+        cmd[4] = len;
+        break;
+    case 1:
+    case 2:
+        cmd[7] = (len >> 8);
+        cmd[8] = len;
+        break;
+    case 4:
+        cmd[10] = len >> 24;
+        cmd[11] = len >> 16;
+        cmd[12] = len >> 8;
+        cmd[13] = len;
+        break;
+    case 5:
+        cmd[6] = len >> 24;
+        cmd[7] = len >> 16;
+        cmd[8] = len >> 8;
+        cmd[9] = len;
+        break;
+    }
+}
+
 static int execute_command(BlockDriverState *bdrv,
                            SCSIRequest *r, int direction,
    BlockDriverCompletionFunc *complete)
 {
+    SCSIDeviceState *s = r->dev;
 
+    r->remaining = 0;
+retry:
+    if (s->max_chunk && r->buflen > s->max_chunk) {
+        r->remaining = r->buflen - s->max_chunk;
+        scsi_set_length(r->cmd, s->max_chunk / s->blocksize);
+        r->buflen = s->max_chunk;
+    }
     r->io_header.interface_id = 'S';
     r->io_header.dxfer_direction = direction;
-    r->io_header.dxferp = r->buf;
+    r->io_header.dxferp = r->buf + r->offset;
     r->io_header.dxfer_len = r->buflen;
     r->io_header.cmdp = r->cmd;
     r->io_header.cmd_len = r->cmdlen;
-    r->io_header.mx_sb_len = sizeof(r->dev->sensebuf);
-    r->io_header.sbp = r->dev->sensebuf;
+    r->io_header.mx_sb_len = sizeof(s->sensebuf);
+    r->io_header.sbp = s->sensebuf;
     r->io_header.timeout = MAX_UINT;
     r->io_header.usr_ptr = r;
     r->io_header.flags |= SG_FLAG_DIRECT_IO;
 
     if (bdrv_pwrite(bdrv, -1, &r->io_header, sizeof(r->io_header)) == -1) {
+        if (errno == 12) {
+            if (!s->max_chunk) {
+                s->max_chunk = MAX_CHUNK;
+                goto retry;
+            } else if (s->max_chunk > s->blocksize) {
+                s->max_chunk >>= 1;
+                goto retry;
+            }
+        }
         BADF("execute_command: write failed ! (%d)\n", errno);
         return -1;
     }
@@ -246,7 +318,18 @@ static void scsi_read_complete(void * op
         scsi_command_complete(r, ret);
         return;
     }
-    len = r->io_header.dxfer_len - r->io_header.resid;
+    r->offset += r->io_header.dxfer_len - r->io_header.resid;
+    if (r->remaining != 0) {
+        scsi_cmd_next(r->cmd, r->buflen / s->blocksize);
+        scsi_set_length(r->cmd, r->remaining / s->blocksize);
+        r->buflen = r->remaining;
+        ret = execute_command(s->bdrv, r, SG_DXFER_FROM_DEV,
+                              scsi_read_complete);
+        if (ret == -1)
+            scsi_command_complete(r, -EINVAL);
+        return;
+    }
+    len = r->offset;
     DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, len);
 
     r->len = -1;
@@ -293,6 +376,7 @@ static void scsi_read_data(SCSIDevice *d
 static void scsi_write_complete(void * opaque, int ret)
 {
     SCSIRequest *r = (SCSIRequest *)opaque;
+    SCSIDeviceState *s = r->dev;
 
     DPRINTF("scsi_write_complete() ret = %d\n", ret);
     if (ret) {
@@ -300,6 +384,17 @@ static void scsi_write_complete(void * o
         scsi_command_complete(r, ret);
         return;
     }
+    r->offset += r->io_header.dxfer_len - r->io_header.resid;
+    if (r->remaining != 0) {
+        scsi_cmd_next(r->cmd, r->buflen / s->blocksize);
+        scsi_set_length(r->cmd, r->remaining / s->blocksize);
+        r->buflen = r->remaining;
+        ret = execute_command(s->bdrv, r, SG_DXFER_TO_DEV,
+                              scsi_write_complete);
+        if (ret == -1)
+            scsi_command_complete(r, -EINVAL);
+        return;
+    }
 
     scsi_command_complete(r, ret);
 }
@@ -641,6 +736,7 @@ SCSIDevice *scsi_generic_init(BlockDrive
     s->lun = scsiid.lun;
     s->blocksize = get_blocksize(s->bdrv);
     s->driver_status = 0;
+    s->max_chunk = 0;
     memset(s->sensebuf, 0, sizeof(s->sensebuf));
     /* removable media returns 0 if not present */
     if (s->blocksize <= 0)



Reply | Threaded
Open this post in threaded view
|

Re: [PATCH 1/2] Real SCSI device passthrough (v5)

Thiemo Seufer
In reply to this post by Laurent Vivier-4
Laurent Vivier wrote:

>
> This patch allows to connect the virtual SCSI interface of Qemu to
> a real SCSI device of the host.
> Using the devices /dev/sg, it allows to send the SCSI commands from the
> virtual SCSI interface to the real device.
>
> It has been tested with a SATA disk and an ATA CD burner with ide-scsi module
> and wodim to burn a CD-RW.
>
> v5 allows to read movies from a DVD. It has been tested with PowerDVD and XP with unprotected DVD.

Please update to current CVS, I committed v4 before I saw this one.


Thiemo