diff mbox

[RFC,25/34] scsi: add Hyper-V/VMBus SCSI controller

Message ID 20180206203048.11096-26-rkagan@virtuozzo.com (mailing list archive)
State New, archived
Headers show

Commit Message

Roman Kagan Feb. 6, 2018, 8:30 p.m. UTC
Add an implementation of Hyper-V/VMBus SCSI controller.

Kudos to Evgeny Yakovlev (formerly eyakovlev@virtuozzo.com) for research
and prototyping.

Signed-off-by: Roman Kagan <rkagan@virtuozzo.com>
---
 hw/scsi/hv-scsi.c     | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/scsi/Makefile.objs |   2 +
 hw/scsi/trace-events  |   6 +
 3 files changed, 406 insertions(+)
 create mode 100644 hw/scsi/hv-scsi.c

Comments

Paolo Bonzini Feb. 7, 2018, 11:11 a.m. UTC | #1
On 06/02/2018 21:30, Roman Kagan wrote:
> +    blk_io_plug(d->conf.blk);
> +    if (scsi_req_enqueue(sreq)) {
> +        scsi_req_continue(sreq);
> +    }
> +    blk_io_unplug(d->conf.blk);

Is plug/unplug really useful?

Paolo
Denis V. Lunev Feb. 7, 2018, 11:25 a.m. UTC | #2
On 02/07/2018 02:11 PM, Paolo Bonzini wrote:
> On 06/02/2018 21:30, Roman Kagan wrote:
>> +    blk_io_plug(d->conf.blk);
>> +    if (scsi_req_enqueue(sreq)) {
>> +        scsi_req_continue(sreq);
>> +    }
>> +    blk_io_unplug(d->conf.blk);
> Is plug/unplug really useful?
>
> Paolo
good point. here - no. plug/unplug should be moved to upper
layer, where we process all requests. Plug/Unplug should
cover all currently available bits.

Den
Roman Kagan Feb. 7, 2018, 8 p.m. UTC | #3
On Wed, Feb 07, 2018 at 12:11:50PM +0100, Paolo Bonzini wrote:
> On 06/02/2018 21:30, Roman Kagan wrote:
> > +    blk_io_plug(d->conf.blk);
> > +    if (scsi_req_enqueue(sreq)) {
> > +        scsi_req_continue(sreq);
> > +    }
> > +    blk_io_unplug(d->conf.blk);
> 
> Is plug/unplug really useful?

I guess no.  Must have been copied mindlessly from somewhere.

Thanks,
Roman.
diff mbox

Patch

diff --git a/hw/scsi/hv-scsi.c b/hw/scsi/hv-scsi.c
new file mode 100644
index 0000000000..bbfc26bf0a
--- /dev/null
+++ b/hw/scsi/hv-scsi.c
@@ -0,0 +1,398 @@ 
+/*
+ * QEMU Hyper-V storage device support
+ *
+ * Copyright (c) 2017-2018 Virtuozzo International GmbH.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/vmbus/vmbus.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+#include "qemu/iov.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "trace.h"
+#include "hvscsi-proto.h"
+
+#define TYPE_HV_SCSI "hv-scsi"
+#define HV_SCSI_GUID "ba6163d9-04a1-4d29-b605-72e2ffb1dc7f"
+#define HV_SCSI_MAX_TRANSFER_BYTES (IOV_MAX * TARGET_PAGE_SIZE)
+
+typedef struct HvScsi {
+    VMBusDevice parent;
+    uint16_t num_queues;
+    SCSIBus bus;
+    enum {
+        HV_SCSI_RESET,
+        HV_SCSI_INITIALIZING,
+        HV_SCSI_INITIALIZED,
+    } state;
+    uint8_t protocol_major;
+    uint8_t protocol_minor;
+} HvScsi;
+
+#define HV_SCSI(obj) OBJECT_CHECK(HvScsi, (obj), TYPE_HV_SCSI)
+
+typedef struct HvScsiReq
+{
+    VMBusChanReq vmreq;
+    HvScsi *s;
+    SCSIRequest *sreq;
+    hv_stor_packet *reply;
+} HvScsiReq;
+
+static void hv_scsi_init_req(HvScsi *s, HvScsiReq *req)
+{
+    VMBusChanReq *vmreq = &req->vmreq;
+
+    req->s = s;
+    if (vmreq->comp) {
+        req->reply = vmreq->comp;
+    }
+}
+
+static void hv_scsi_free_req(HvScsiReq *req)
+{
+    vmbus_release_req(req);
+}
+
+static void hv_scsi_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+    HvScsiReq *req = sreq->hba_private;
+
+    vmbus_save_req(f, &req->vmreq);
+}
+
+static void *hv_scsi_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+    HvScsiReq *req;
+    HvScsi *scsi = container_of(sreq->bus, HvScsi, bus);
+
+    req = vmbus_load_req(f, VMBUS_DEVICE(scsi), sizeof(*req));
+    if (!req) {
+        error_report("failed to load VMBus request from saved state");
+        return NULL;
+    }
+
+    hv_scsi_init_req(scsi, req);
+    scsi_req_ref(sreq);
+    req->sreq = sreq;
+    return req;
+}
+
+static int complete_io(HvScsiReq *req, uint32_t status)
+{
+    VMBusChanReq *vmreq = &req->vmreq;
+    int res = 0;
+
+    if (vmreq->comp) {
+        req->reply->operation = HV_STOR_OPERATION_COMPLETE_IO;
+        req->reply->flags = 0;
+        req->reply->status = status;
+        res = vmbus_chan_send_completion(vmreq);
+    }
+
+    if (req->sreq) {
+        scsi_req_unref(req->sreq);
+    }
+    hv_scsi_free_req(req);
+    return res;
+}
+
+static int hv_scsi_complete_req(HvScsiReq *req, uint8_t scsi_status,
+                                uint32_t srb_status, size_t resid)
+{
+    hv_srb_packet *srb = &req->reply->srb;
+
+    srb->scsi_status = scsi_status;
+    srb->srb_status = srb_status;
+
+    assert(resid <= srb->transfer_length);
+    srb->transfer_length -= resid;
+
+    return complete_io(req, 0);
+}
+
+static void hv_scsi_request_cancelled(SCSIRequest *r)
+{
+    HvScsiReq *req = r->hba_private;
+    hv_scsi_complete_req(req, GOOD, HV_SRB_STATUS_ABORTED, 0);
+}
+
+static QEMUSGList *hv_scsi_get_sg_list(SCSIRequest *r)
+{
+    HvScsiReq *req = r->hba_private;
+    return &req->vmreq.sgl;
+}
+
+static void hv_scsi_command_complete(SCSIRequest *r, uint32_t status,
+                                     size_t resid)
+{
+    HvScsiReq *req = r->hba_private;
+    hv_srb_packet *srb = &req->reply->srb;
+
+    trace_hvscsi_command_complete(r, status, resid);
+
+    srb->sense_length = scsi_req_get_sense(r, srb->sense_data,
+                                           sizeof(srb->sense_data));
+    hv_scsi_complete_req(req, status, HV_SRB_STATUS_SUCCESS, resid);
+}
+
+static struct SCSIBusInfo hv_scsi_info = {
+    .tcq = true,
+    .max_channel = HV_SRB_MAX_CHANNELS - 1,
+    .max_target = HV_SRB_MAX_TARGETS - 1,
+    .max_lun = HV_SRB_MAX_LUNS_PER_TARGET - 1,
+    .complete = hv_scsi_command_complete,
+    .cancel = hv_scsi_request_cancelled,
+    .get_sg_list = hv_scsi_get_sg_list,
+    .save_request = hv_scsi_save_request,
+    .load_request = hv_scsi_load_request,
+};
+
+static void handle_missing_target(HvScsiReq *req)
+{
+    /*
+     * SRB_STATUS_INVALID_LUN should be enough and it works for windows guests
+     * However linux stor_vsc driver ignores any scsi and srb status errors
+     * for all INQUIRY and MODE_SENSE commands.
+     * So, specifically for those linux clients we also have to fake
+     * an INVALID_LUN sense response.
+     */
+    size_t len = 0;
+    QEMUSGList *sgl = &req->vmreq.sgl;
+    hv_srb_packet *srb = &req->reply->srb;
+    struct iovec iov[4];
+    int iov_cnt;
+
+    iov_cnt = vmbus_map_sgl(sgl, DMA_DIRECTION_FROM_DEVICE, iov,
+                            ARRAY_SIZE(iov), srb->transfer_length, 0);
+
+    switch (srb->cdb[0]) {
+    case INQUIRY: {
+        /* Report invalid device type */
+        uint8_t data = 0x7F;
+        len = iov_from_buf(iov, iov_cnt, 0, &data, sizeof(data));
+        break;
+    }
+
+    case REPORT_LUNS: {
+        /* Report 0 luns */
+        uint32_t data = 0;
+        len = iov_from_buf(iov, iov_cnt, 0, &data, sizeof(data));
+        break;
+    }
+
+    default:
+        error_report("Don't know how to handle 0x%x for bad target",
+                               srb->cdb[0]);
+        break;
+    }
+
+    srb->sense_data[0] = 0x72;
+    srb->sense_data[1] = sense_code_LUN_NOT_SUPPORTED.key;
+    srb->sense_data[2] = sense_code_LUN_NOT_SUPPORTED.asc;
+    srb->sense_data[3] = sense_code_LUN_NOT_SUPPORTED.ascq;
+    srb->sense_length = 4;
+
+    iov_memset(iov, iov_cnt, len, 0, -1);
+    vmbus_unmap_sgl(sgl, DMA_DIRECTION_FROM_DEVICE, iov, iov_cnt, -1);
+
+    srb->scsi_status = CHECK_CONDITION;
+    srb->srb_status = HV_SRB_STATUS_INVALID_LUN |
+        HV_SRB_STATUS_AUTOSENSE_VALID;
+    complete_io(req, 0);
+}
+
+static void hv_scsi_execute_srb(HvScsiReq *req)
+{
+    SCSIRequest *sreq;
+    SCSIDevice *d;
+    VMBusChanReq *vmreq = &req->vmreq;
+    hv_stor_packet *storpkt = req->reply;
+    hv_srb_packet *srb = &storpkt->srb;
+
+    memcpy(storpkt, vmreq->msg, vmreq->msglen);
+
+    trace_hvscsi_srb_packet(srb->length, srb->target, srb->lun,
+            srb->cdb_length, srb->transfer_length, srb->data_in);
+
+    d = scsi_device_find(&req->s->bus, srb->channel, srb->target, srb->lun);
+    if (!d || (srb->lun && d->lun != srb->lun)) {
+        handle_missing_target(req);
+        return;
+    }
+
+    req->sreq = sreq = scsi_req_new(d, srb->channel, srb->lun, srb->cdb, req);
+    assert(sreq);
+
+    scsi_req_ref(sreq);
+    blk_io_plug(d->conf.blk);
+    if (scsi_req_enqueue(sreq)) {
+        scsi_req_continue(sreq);
+    }
+    blk_io_unplug(d->conf.blk);
+    scsi_req_unref(sreq);
+}
+
+static void hv_scsi_handle_packet(HvScsiReq *req)
+{
+    HvScsi *scsi = req->s;
+    struct hv_stor_packet *msg = req->vmreq.msg;
+    uint32_t status = 0;
+
+    trace_hvscsi_vstor_request(msg->operation, msg->flags);
+
+    switch (msg->operation) {
+    case HV_STOR_OPERATION_EXECUTE_SRB:
+        if (scsi->state != HV_SCSI_INITIALIZED) {
+            error_report("%s: EXECUTE_SRB while not initialized", __func__);
+            status = 1;
+            break;
+        }
+        hv_scsi_execute_srb(req);
+        return; /* SRB packets are completed asynchronously */
+
+    case HV_STOR_OPERATION_BEGIN_INITIALIZATION:
+        scsi->state = HV_SCSI_INITIALIZING;
+        break;
+
+    case HV_STOR_OPERATION_QUERY_PROTOCOL_VERSION:
+        scsi->protocol_major = msg->version.major_minor >> 8;
+        scsi->protocol_minor = msg->version.major_minor & 0xFF;
+        break;
+
+    case HV_STOR_OPERATION_QUERY_PROPERTIES:
+        req->reply->properties.max_channel_count = scsi->num_queues;
+        req->reply->properties.flags = HV_STOR_PROPERTIES_MULTI_CHANNEL_FLAG;
+        req->reply->properties.max_transfer_bytes = HV_SCSI_MAX_TRANSFER_BYTES;
+        break;
+
+    case HV_STOR_OPERATION_END_INITIALIZATION:
+        if (scsi->state != HV_SCSI_INITIALIZING) {
+            error_report("%s: END_INITIALIZATION srb while not initializing",
+                         __func__);
+            status = 1;
+            break;
+        }
+        scsi->state = HV_SCSI_INITIALIZED;
+        break;
+
+    default:
+        error_report("unknown vstor packet operation %d", msg->operation);
+        break;
+    }
+
+    complete_io(req, status);
+}
+
+static void hv_scsi_notify_cb(VMBusChannel *chan)
+{
+    HvScsi *scsi = HV_SCSI(vmbus_channel_device(chan));
+
+    for (;;) {
+        HvScsiReq *req = vmbus_channel_recv(chan, sizeof(*req));
+        if (!req) {
+            break;
+        }
+
+        hv_scsi_init_req(scsi, req);
+        hv_scsi_handle_packet(req);
+    }
+}
+
+static void hv_scsi_reset(HvScsi *scsi)
+{
+    qbus_reset_all(&scsi->bus.qbus);
+    scsi->state = HV_SCSI_RESET;
+    scsi->protocol_major = 0;
+    scsi->protocol_minor = 0;
+}
+
+static uint16_t hv_scsi_num_channels(VMBusDevice *dev)
+{
+    return HV_SCSI(dev)->num_queues;
+}
+
+static void hv_scsi_close_channel(VMBusDevice *dev)
+{
+    HvScsi *scsi = HV_SCSI(dev);
+    hv_scsi_reset(scsi);
+}
+
+static void hv_scsi_dev_realize(VMBusDevice *vdev, Error **errp)
+{
+    HvScsi *scsi = HV_SCSI(vdev);
+
+    scsi_bus_new(&scsi->bus, sizeof(scsi->bus), DEVICE(scsi),
+                 &hv_scsi_info, NULL);
+    return;
+}
+
+static void hv_scsi_dev_reset(VMBusDevice *vdev)
+{
+    HvScsi *scsi = HV_SCSI(vdev);
+    hv_scsi_reset(scsi);
+}
+
+static void hv_scsi_dev_unrealize(VMBusDevice *vdev, Error **errp)
+{
+    HvScsi *scsi = HV_SCSI(vdev);
+    hv_scsi_reset(scsi);
+}
+
+static const VMStateDescription vmstate_hv_scsi = {
+    .name = TYPE_HV_SCSI,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(parent, HvScsi, 0, vmstate_vmbus_dev, VMBusDevice),
+        VMSTATE_UINT32(state, HvScsi),
+        VMSTATE_UINT8(protocol_major, HvScsi),
+        VMSTATE_UINT8(protocol_minor, HvScsi),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property hv_scsi_properties[] = {
+    DEFINE_PROP_UUID("instanceid", HvScsi, parent.instanceid),
+    DEFINE_PROP_UINT16("num_queues", HvScsi, num_queues, 1),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void hv_scsi_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    VMBusDeviceClass *vdc = VMBUS_DEVICE_CLASS(klass);
+
+    qemu_uuid_parse(HV_SCSI_GUID, &vdc->classid);
+    dc->props = hv_scsi_properties;
+    dc->fw_name = "scsi";
+    dc->vmsd = &vmstate_hv_scsi;
+    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+    vdc->vmdev_realize = hv_scsi_dev_realize;
+    vdc->vmdev_unrealize = hv_scsi_dev_unrealize;
+    vdc->vmdev_reset = hv_scsi_dev_reset;
+    vdc->num_channels = hv_scsi_num_channels;
+    vdc->close_channel = hv_scsi_close_channel;
+    vdc->chan_notify_cb = hv_scsi_notify_cb;
+}
+
+static const TypeInfo hv_scsi_type_info = {
+    .name = TYPE_HV_SCSI,
+    .parent = TYPE_VMBUS_DEVICE,
+    .instance_size = sizeof(HvScsi),
+    .class_init = hv_scsi_class_init,
+};
+
+static void hv_scsi_register_types(void)
+{
+    type_register_static(&hv_scsi_type_info);
+}
+
+type_init(hv_scsi_register_types)
diff --git a/hw/scsi/Makefile.objs b/hw/scsi/Makefile.objs
index b188f7242b..2630b3a756 100644
--- a/hw/scsi/Makefile.objs
+++ b/hw/scsi/Makefile.objs
@@ -13,3 +13,5 @@  obj-y += virtio-scsi.o virtio-scsi-dataplane.o
 obj-$(CONFIG_VHOST_SCSI) += vhost-scsi-common.o vhost-scsi.o
 obj-$(CONFIG_VHOST_USER_SCSI) += vhost-scsi-common.o vhost-user-scsi.o
 endif
+
+obj-$(CONFIG_VMBUS) += hv-scsi.o
diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events
index 6e299d0338..eec9f2dfb0 100644
--- a/hw/scsi/trace-events
+++ b/hw/scsi/trace-events
@@ -229,3 +229,9 @@  spapr_vscsi_process_login(void) "Got login, sending response !"
 spapr_vscsi_queue_cmd_no_drive(uint64_t lun) "Command for lun 0x%08" PRIx64 " with no drive"
 spapr_vscsi_queue_cmd(uint32_t qtag, unsigned cdb, const char *cmd, int lun, int ret) "Queued command tag 0x%"PRIx32" CMD 0x%x=%s LUN %d ret: %d"
 spapr_vscsi_do_crq(unsigned c0, unsigned c1) "crq: %02x %02x ..."
+
+# hw/scsi/hv-scsi.c
+hvscsi_vstor_request(int operation, uint32_t flags) "vstor packet: operation %d, flags 0x%x"
+hvscsi_srb_packet(uint16_t length, uint8_t target_id, uint8_t lun, uint8_t cdb_length, uint32_t data_transfer_length, uint8_t data_in) "SRB packet: length %d, target %d, lun %d, cdb length %d, data transfer size %d, direction %d"
+hvscsi_command_complete(void *r, int status, size_t resid) "scsi command complete: request %p, status %d, resid %zu"
+hvscsi_missing_target(uint8_t target_id, uint8_t lun) "missing target %d, lun %d"