|
|
7711c0 |
From 2f019168e70aa391d110a09dbae9ac937091ddc2 Mon Sep 17 00:00:00 2001
|
|
|
7711c0 |
From: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
7711c0 |
Date: Wed, 7 Nov 2018 18:00:02 +0100
|
|
|
7711c0 |
Subject: [PATCH 28/34] hw/scsi: add VPD Block Limits emulation
|
|
|
7711c0 |
MIME-Version: 1.0
|
|
|
7711c0 |
Content-Type: text/plain; charset=UTF-8
|
|
|
7711c0 |
Content-Transfer-Encoding: 8bit
|
|
|
7711c0 |
|
|
|
7711c0 |
RH-Author: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
7711c0 |
Message-id: <20181107180007.22954-5-pbonzini@redhat.com>
|
|
|
7711c0 |
Patchwork-id: 82943
|
|
|
7711c0 |
O-Subject: [RHEL7.6.z qemu-kvm-rhev PATCH 4/9] hw/scsi: add VPD Block Limits emulation
|
|
|
7711c0 |
Bugzilla: 1566195
|
|
|
7711c0 |
RH-Acked-by: Max Reitz <mreitz@redhat.com>
|
|
|
7711c0 |
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
7711c0 |
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
|
|
7711c0 |
|
|
|
7711c0 |
From: Daniel Henrique Barboza <danielhb413@gmail.com>
|
|
|
7711c0 |
|
|
|
7711c0 |
The VPD Block Limits Inquiry page is optional, allowing SCSI devices
|
|
|
7711c0 |
to not implement it. This is the case for devices like the MegaRAID
|
|
|
7711c0 |
SAS 9361-8i and Microsemi PM8069.
|
|
|
7711c0 |
|
|
|
7711c0 |
In case of SCSI passthrough, the response of this request is used by
|
|
|
7711c0 |
the QEMU SCSI layer to set the max_io_sectors that the guest
|
|
|
7711c0 |
device will support, based on the value of the max_sectors_kb that
|
|
|
7711c0 |
the device has set in the host at that time. Without this response,
|
|
|
7711c0 |
the guest kernel is free to assume any value of max_io_sectors
|
|
|
7711c0 |
for the SCSI device. If this value is greater than the value from
|
|
|
7711c0 |
the host, SCSI Sense errors will occur because the guest will send
|
|
|
7711c0 |
read/write requests that are larger than the underlying host device
|
|
|
7711c0 |
is configured to support. An example of this behavior can be seen
|
|
|
7711c0 |
in [1].
|
|
|
7711c0 |
|
|
|
7711c0 |
A workaround is to set the max_sectors_kb host value back in the guest
|
|
|
7711c0 |
kernel (a process that can be automated using rc.local startup scripts
|
|
|
7711c0 |
and the like), but this has several drawbacks:
|
|
|
7711c0 |
|
|
|
7711c0 |
- it can be troublesome if the guest has many passthrough devices that
|
|
|
7711c0 |
needs this tuning;
|
|
|
7711c0 |
|
|
|
7711c0 |
- if a change in max_sectors_kb is made in the host side, manual change
|
|
|
7711c0 |
in the guests will also be required;
|
|
|
7711c0 |
|
|
|
7711c0 |
- during an OS install it is difficult, and sometimes not possible, to
|
|
|
7711c0 |
go to a terminal and change the max_sectors_kb prior to the installation.
|
|
|
7711c0 |
This means that the disk can't be used during the install process. The
|
|
|
7711c0 |
easiest alternative here is to roll back to scsi-hd, install the guest
|
|
|
7711c0 |
and then go back to SCSI passthrough when the installation is done and
|
|
|
7711c0 |
max_sectors_kb can be set.
|
|
|
7711c0 |
|
|
|
7711c0 |
An easier way would be to QEMU handle the absence of the Block Limits
|
|
|
7711c0 |
VPD device response, setting max_io_sectors accordingly and allowing
|
|
|
7711c0 |
the guest to use the device without the hassle.
|
|
|
7711c0 |
|
|
|
7711c0 |
This patch adds emulation of the Block Limits VPD response for
|
|
|
7711c0 |
SCSI passthrough devices of type TYPE_DISK that doesn't support
|
|
|
7711c0 |
it. The following changes were made:
|
|
|
7711c0 |
|
|
|
7711c0 |
- scsi_handle_inquiry_reply will now check the available VPD
|
|
|
7711c0 |
pages from the Inquiry EVPD reply. In case the device does not
|
|
|
7711c0 |
|
|
|
7711c0 |
- a new function called scsi_generic_set_vpd_bl_emulation,
|
|
|
7711c0 |
that is called during device realize, was created to set a
|
|
|
7711c0 |
new flag 'needs_vpd_bl_emulation' of the device. This function
|
|
|
7711c0 |
retrieves the Inquiry EVPD response of the device to check for
|
|
|
7711c0 |
VPD BL support.
|
|
|
7711c0 |
|
|
|
7711c0 |
- scsi_handle_inquiry_reply will now check the available VPD
|
|
|
7711c0 |
pages from the Inquiry EVPD reply in case the device needs
|
|
|
7711c0 |
VPD BL emulation, adding the Block Limits page (0xb0) to
|
|
|
7711c0 |
the list. This will make the guest kernel aware of the
|
|
|
7711c0 |
support that we're now providing by emulation.
|
|
|
7711c0 |
|
|
|
7711c0 |
- a new function scsi_emulate_block_limits creates the
|
|
|
7711c0 |
emulated Block Limits response. This function is called
|
|
|
7711c0 |
inside scsi_read_complete in case the device requires
|
|
|
7711c0 |
Block Limits VPD emulation and we detected a SCSI Sense
|
|
|
7711c0 |
error in the VPD Block Limits reply that was issued
|
|
|
7711c0 |
from the guest kernel to the device. This error is
|
|
|
7711c0 |
expected: we're reporting support from our side, but
|
|
|
7711c0 |
the device isn't aware of it.
|
|
|
7711c0 |
|
|
|
7711c0 |
With this patch, the guest now queries the Block Limits
|
|
|
7711c0 |
page during the device configuration because it is being
|
|
|
7711c0 |
advertised in the Supported Pages response. It will either
|
|
|
7711c0 |
receive the Block Limits page from the hardware, if it supports
|
|
|
7711c0 |
it, or will receive an emulated response from QEMU. At any rate,
|
|
|
7711c0 |
the guest now has the information to set the max_sectors_kb
|
|
|
7711c0 |
parameter accordingly, sparing the user of SCSI sense errors
|
|
|
7711c0 |
that would happen without the emulated response and in the
|
|
|
7711c0 |
absence of Block Limits support from the hardware.
|
|
|
7711c0 |
|
|
|
7711c0 |
[1] https://bugzilla.redhat.com/show_bug.cgi?id=1566195
|
|
|
7711c0 |
|
|
|
7711c0 |
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1566195
|
|
|
7711c0 |
Reported-by: Dac Nguyen <dacng@us.ibm.com>
|
|
|
7711c0 |
Signed-off-by: Daniel Henrique Barboza <danielhb413@gmail.com>
|
|
|
7711c0 |
Message-Id: <20180627172432.11120-4-danielhb413@gmail.com>
|
|
|
7711c0 |
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
7711c0 |
(cherry picked from commit a71c775b24ebc664129eb1d9b4c360590353efd5)
|
|
|
7711c0 |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
7711c0 |
---
|
|
|
7711c0 |
hw/scsi/scsi-disk.c | 2 +-
|
|
|
7711c0 |
hw/scsi/scsi-generic.c | 132 +++++++++++++++++++++++++++++++++++++++++++++----
|
|
|
7711c0 |
include/hw/scsi/scsi.h | 3 +-
|
|
|
7711c0 |
3 files changed, 125 insertions(+), 12 deletions(-)
|
|
|
7711c0 |
|
|
|
7711c0 |
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
|
|
|
7711c0 |
index ea86849..b3d53ec 100644
|
|
|
7711c0 |
--- a/hw/scsi/scsi-disk.c
|
|
|
7711c0 |
+++ b/hw/scsi/scsi-disk.c
|
|
|
7711c0 |
@@ -2646,7 +2646,7 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
|
|
|
7711c0 |
s->features |= (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS);
|
|
|
7711c0 |
|
|
|
7711c0 |
scsi_realize(&s->qdev, errp);
|
|
|
7711c0 |
- scsi_generic_read_device_identification(&s->qdev);
|
|
|
7711c0 |
+ scsi_generic_read_device_inquiry(&s->qdev);
|
|
|
7711c0 |
}
|
|
|
7711c0 |
|
|
|
7711c0 |
typedef struct SCSIBlockReq {
|
|
|
7711c0 |
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
|
|
|
7711c0 |
index c6307a8..4266003 100644
|
|
|
7711c0 |
--- a/hw/scsi/scsi-generic.c
|
|
|
7711c0 |
+++ b/hw/scsi/scsi-generic.c
|
|
|
7711c0 |
@@ -145,6 +145,8 @@ static int execute_command(BlockBackend *blk,
|
|
|
7711c0 |
|
|
|
7711c0 |
static void scsi_handle_inquiry_reply(SCSIGenericReq *r, SCSIDevice *s)
|
|
|
7711c0 |
{
|
|
|
7711c0 |
+ uint8_t page, page_len;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
/*
|
|
|
7711c0 |
* EVPD set to zero returns the standard INQUIRY data.
|
|
|
7711c0 |
*
|
|
|
7711c0 |
@@ -168,22 +170,57 @@ static void scsi_handle_inquiry_reply(SCSIGenericReq *r, SCSIDevice *s)
|
|
|
7711c0 |
s->scsi_version = r->buf[2];
|
|
|
7711c0 |
}
|
|
|
7711c0 |
}
|
|
|
7711c0 |
- if (s->type == TYPE_DISK && r->req.cmd.buf[2] == 0xb0) {
|
|
|
7711c0 |
- uint32_t max_transfer =
|
|
|
7711c0 |
- blk_get_max_transfer(s->conf.blk) / s->blocksize;
|
|
|
7711c0 |
|
|
|
7711c0 |
- assert(max_transfer);
|
|
|
7711c0 |
- stl_be_p(&r->buf[8], max_transfer);
|
|
|
7711c0 |
- /* Also take care of the opt xfer len. */
|
|
|
7711c0 |
- stl_be_p(&r->buf[12],
|
|
|
7711c0 |
- MIN_NON_ZERO(max_transfer, ldl_be_p(&r->buf[12])));
|
|
|
7711c0 |
+ if (s->type == TYPE_DISK && (r->req.cmd.buf[1] & 0x01)) {
|
|
|
7711c0 |
+ page = r->req.cmd.buf[2];
|
|
|
7711c0 |
+ if (page == 0xb0) {
|
|
|
7711c0 |
+ uint32_t max_transfer =
|
|
|
7711c0 |
+ blk_get_max_transfer(s->conf.blk) / s->blocksize;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ assert(max_transfer);
|
|
|
7711c0 |
+ stl_be_p(&r->buf[8], max_transfer);
|
|
|
7711c0 |
+ /* Also take care of the opt xfer len. */
|
|
|
7711c0 |
+ stl_be_p(&r->buf[12],
|
|
|
7711c0 |
+ MIN_NON_ZERO(max_transfer, ldl_be_p(&r->buf[12])));
|
|
|
7711c0 |
+ } else if (page == 0x00 && s->needs_vpd_bl_emulation) {
|
|
|
7711c0 |
+ /*
|
|
|
7711c0 |
+ * Now we're capable of supplying the VPD Block Limits
|
|
|
7711c0 |
+ * response if the hardware can't. Add it in the INQUIRY
|
|
|
7711c0 |
+ * Supported VPD pages response in case we are using the
|
|
|
7711c0 |
+ * emulation for this device.
|
|
|
7711c0 |
+ *
|
|
|
7711c0 |
+ * This way, the guest kernel will be aware of the support
|
|
|
7711c0 |
+ * and will use it to proper setup the SCSI device.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+ page_len = r->buf[3];
|
|
|
7711c0 |
+ r->buf[page_len + 4] = 0xb0;
|
|
|
7711c0 |
+ r->buf[3] = ++page_len;
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
}
|
|
|
7711c0 |
}
|
|
|
7711c0 |
|
|
|
7711c0 |
+static int scsi_emulate_block_limits(SCSIGenericReq *r)
|
|
|
7711c0 |
+{
|
|
|
7711c0 |
+ r->buflen = scsi_disk_emulate_vpd_page(&r->req, r->buf);
|
|
|
7711c0 |
+ r->io_header.sb_len_wr = 0;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ /*
|
|
|
7711c0 |
+ * We have valid contents in the reply buffer but the
|
|
|
7711c0 |
+ * io_header can report a sense error coming from
|
|
|
7711c0 |
+ * the hardware in scsi_command_complete_noio. Clean
|
|
|
7711c0 |
+ * up the io_header to avoid reporting it.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+ r->io_header.driver_status = 0;
|
|
|
7711c0 |
+ r->io_header.status = 0;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ return r->buflen;
|
|
|
7711c0 |
+}
|
|
|
7711c0 |
+
|
|
|
7711c0 |
static void scsi_read_complete(void * opaque, int ret)
|
|
|
7711c0 |
{
|
|
|
7711c0 |
SCSIGenericReq *r = (SCSIGenericReq *)opaque;
|
|
|
7711c0 |
SCSIDevice *s = r->req.dev;
|
|
|
7711c0 |
+ SCSISense sense;
|
|
|
7711c0 |
int len;
|
|
|
7711c0 |
|
|
|
7711c0 |
assert(r->req.aiocb != NULL);
|
|
|
7711c0 |
@@ -200,6 +237,27 @@ static void scsi_read_complete(void * opaque, int ret)
|
|
|
7711c0 |
DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, len);
|
|
|
7711c0 |
|
|
|
7711c0 |
r->len = -1;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ /*
|
|
|
7711c0 |
+ * Check if this is a VPD Block Limits request that
|
|
|
7711c0 |
+ * resulted in sense error but would need emulation.
|
|
|
7711c0 |
+ * In this case, emulate a valid VPD response.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+ if (s->needs_vpd_bl_emulation) {
|
|
|
7711c0 |
+ int is_vpd_bl = r->req.cmd.buf[0] == INQUIRY &&
|
|
|
7711c0 |
+ r->req.cmd.buf[1] & 0x01 &&
|
|
|
7711c0 |
+ r->req.cmd.buf[2] == 0xb0;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ if (is_vpd_bl && sg_io_sense_from_errno(-ret, &r->io_header, &sense)) {
|
|
|
7711c0 |
+ len = scsi_emulate_block_limits(r);
|
|
|
7711c0 |
+ /*
|
|
|
7711c0 |
+ * No need to let scsi_read_complete go on and handle an
|
|
|
7711c0 |
+ * INQUIRY VPD BL request we created manually.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+ goto req_complete;
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+
|
|
|
7711c0 |
if (len == 0) {
|
|
|
7711c0 |
scsi_command_complete_noio(r, 0);
|
|
|
7711c0 |
goto done;
|
|
|
7711c0 |
@@ -234,6 +292,8 @@ static void scsi_read_complete(void * opaque, int ret)
|
|
|
7711c0 |
if (r->req.cmd.buf[0] == INQUIRY) {
|
|
|
7711c0 |
scsi_handle_inquiry_reply(r, s);
|
|
|
7711c0 |
}
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+req_complete:
|
|
|
7711c0 |
scsi_req_data(&r->req, len);
|
|
|
7711c0 |
scsi_req_unref(&r->req);
|
|
|
7711c0 |
|
|
|
7711c0 |
@@ -435,7 +495,49 @@ int scsi_SG_IO_FROM_DEV(BlockBackend *blk, uint8_t *cmd, uint8_t cmd_size,
|
|
|
7711c0 |
return 0;
|
|
|
7711c0 |
}
|
|
|
7711c0 |
|
|
|
7711c0 |
-void scsi_generic_read_device_identification(SCSIDevice *s)
|
|
|
7711c0 |
+/*
|
|
|
7711c0 |
+ * Executes an INQUIRY request with EVPD set to retrieve the
|
|
|
7711c0 |
+ * available VPD pages of the device. If the device does
|
|
|
7711c0 |
+ * not support the Block Limits page (page 0xb0), set
|
|
|
7711c0 |
+ * the needs_vpd_bl_emulation flag for future use.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+static void scsi_generic_set_vpd_bl_emulation(SCSIDevice *s)
|
|
|
7711c0 |
+{
|
|
|
7711c0 |
+ uint8_t cmd[6];
|
|
|
7711c0 |
+ uint8_t buf[250];
|
|
|
7711c0 |
+ uint8_t page_len;
|
|
|
7711c0 |
+ int ret, i;
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ memset(cmd, 0, sizeof(cmd));
|
|
|
7711c0 |
+ memset(buf, 0, sizeof(buf));
|
|
|
7711c0 |
+ cmd[0] = INQUIRY;
|
|
|
7711c0 |
+ cmd[1] = 1;
|
|
|
7711c0 |
+ cmd[2] = 0x00;
|
|
|
7711c0 |
+ cmd[4] = sizeof(buf);
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ ret = scsi_SG_IO_FROM_DEV(s->conf.blk, cmd, sizeof(cmd),
|
|
|
7711c0 |
+ buf, sizeof(buf));
|
|
|
7711c0 |
+ if (ret < 0) {
|
|
|
7711c0 |
+ /*
|
|
|
7711c0 |
+ * Do not assume anything if we can't retrieve the
|
|
|
7711c0 |
+ * INQUIRY response to assert the VPD Block Limits
|
|
|
7711c0 |
+ * support.
|
|
|
7711c0 |
+ */
|
|
|
7711c0 |
+ s->needs_vpd_bl_emulation = false;
|
|
|
7711c0 |
+ return;
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+ page_len = buf[3];
|
|
|
7711c0 |
+ for (i = 4; i < page_len + 4; i++) {
|
|
|
7711c0 |
+ if (buf[i] == 0xb0) {
|
|
|
7711c0 |
+ s->needs_vpd_bl_emulation = false;
|
|
|
7711c0 |
+ return;
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+ s->needs_vpd_bl_emulation = true;
|
|
|
7711c0 |
+}
|
|
|
7711c0 |
+
|
|
|
7711c0 |
+static void scsi_generic_read_device_identification(SCSIDevice *s)
|
|
|
7711c0 |
{
|
|
|
7711c0 |
uint8_t cmd[6];
|
|
|
7711c0 |
uint8_t buf[250];
|
|
|
7711c0 |
@@ -480,6 +582,16 @@ void scsi_generic_read_device_identification(SCSIDevice *s)
|
|
|
7711c0 |
}
|
|
|
7711c0 |
}
|
|
|
7711c0 |
|
|
|
7711c0 |
+void scsi_generic_read_device_inquiry(SCSIDevice *s)
|
|
|
7711c0 |
+{
|
|
|
7711c0 |
+ scsi_generic_read_device_identification(s);
|
|
|
7711c0 |
+ if (s->type == TYPE_DISK) {
|
|
|
7711c0 |
+ scsi_generic_set_vpd_bl_emulation(s);
|
|
|
7711c0 |
+ } else {
|
|
|
7711c0 |
+ s->needs_vpd_bl_emulation = false;
|
|
|
7711c0 |
+ }
|
|
|
7711c0 |
+}
|
|
|
7711c0 |
+
|
|
|
7711c0 |
static int get_stream_blocksize(BlockBackend *blk)
|
|
|
7711c0 |
{
|
|
|
7711c0 |
uint8_t cmd[6];
|
|
|
7711c0 |
@@ -581,7 +693,7 @@ static void scsi_generic_realize(SCSIDevice *s, Error **errp)
|
|
|
7711c0 |
|
|
|
7711c0 |
/* Only used by scsi-block, but initialize it nevertheless to be clean. */
|
|
|
7711c0 |
s->default_scsi_version = -1;
|
|
|
7711c0 |
- scsi_generic_read_device_identification(s);
|
|
|
7711c0 |
+ scsi_generic_read_device_inquiry(s);
|
|
|
7711c0 |
}
|
|
|
7711c0 |
|
|
|
7711c0 |
const SCSIReqOps scsi_generic_req_ops = {
|
|
|
7711c0 |
diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h
|
|
|
7711c0 |
index b6e05c4..ee3a411 100644
|
|
|
7711c0 |
--- a/include/hw/scsi/scsi.h
|
|
|
7711c0 |
+++ b/include/hw/scsi/scsi.h
|
|
|
7711c0 |
@@ -87,6 +87,7 @@ struct SCSIDevice
|
|
|
7711c0 |
uint64_t port_wwn;
|
|
|
7711c0 |
int scsi_version;
|
|
|
7711c0 |
int default_scsi_version;
|
|
|
7711c0 |
+ bool needs_vpd_bl_emulation;
|
|
|
7711c0 |
};
|
|
|
7711c0 |
|
|
|
7711c0 |
extern const VMStateDescription vmstate_scsi_device;
|
|
|
7711c0 |
@@ -186,7 +187,7 @@ void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense);
|
|
|
7711c0 |
void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense);
|
|
|
7711c0 |
void scsi_device_report_change(SCSIDevice *dev, SCSISense sense);
|
|
|
7711c0 |
void scsi_device_unit_attention_reported(SCSIDevice *dev);
|
|
|
7711c0 |
-void scsi_generic_read_device_identification(SCSIDevice *dev);
|
|
|
7711c0 |
+void scsi_generic_read_device_inquiry(SCSIDevice *dev);
|
|
|
7711c0 |
int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed);
|
|
|
7711c0 |
int scsi_disk_emulate_vpd_page(SCSIRequest *req, uint8_t *outbuf);
|
|
|
7711c0 |
int scsi_SG_IO_FROM_DEV(BlockBackend *blk, uint8_t *cmd, uint8_t cmd_size,
|
|
|
7711c0 |
--
|
|
|
7711c0 |
1.8.3.1
|
|
|
7711c0 |
|