qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 4/8] scsi-disk: Implement 'SET TARGET PORT GROUPS'


From: Hannes Reinecke
Subject: [Qemu-devel] [PATCH 4/8] scsi-disk: Implement 'SET TARGET PORT GROUPS'
Date: Fri, 27 Nov 2015 15:59:02 +0100

Implement 'SET TARGET PORT GROUPS' handling. The ports states are
switched as indicated in the command; no strategy is implemented.
This might cause issues with standard Linux behaviour, which will
only switch the passive path to 'active' and leave the former
active path alone.

Signed-off-by: Hannes Reinecke <address@hidden>
---
 hw/scsi/scsi-disk.c  | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/block/scsi.h |   5 +++
 2 files changed, 123 insertions(+)

diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 8dabed3..52c73be 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -1915,6 +1915,111 @@ static void qbus_enumerate_port_desc(PortDescEnumerate 
*pd, BusState *bus)
     }
 }
 
+typedef struct PortGroupSetEnumerate {
+    uint64_t wwn;
+    uint16_t port_group;
+    uint8_t alua_state;
+} PortGroupSetEnumerate;
+
+static void qbus_enumerate_set_port(PortGroupSetEnumerate *, BusState *);
+
+static void qdev_enumerate_set_port(PortGroupSetEnumerate *ps, DeviceState 
*dev)
+{
+    BusState *child;
+
+    if (!strcmp(object_get_typename(OBJECT(dev->parent_bus)), TYPE_SCSI_BUS)) {
+        SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev);
+        if (s->wwn == ps->wwn &&
+            s->port_group == ps->port_group) {
+            printf("pg %x: switch ALUA state %x -> %x\n",
+                   s->port_group, (s->alua_state & 0x0f), ps->alua_state);
+            s->alua_state = (s->alua_state & 0xf0) | ps->alua_state;
+            scsi_device_set_ua(&s->qdev,
+                               SENSE_CODE(ASYMMETRIC_ACCESS_STATE_CHANGED));
+        }
+    }
+    QLIST_FOREACH(child, &dev->child_bus, sibling) {
+        qbus_enumerate_set_port(ps, child);
+    }
+}
+
+static void qbus_enumerate_set_port(PortGroupSetEnumerate *ps, BusState *bus)
+{
+    BusChild *kid;
+
+    QTAILQ_FOREACH(kid, &bus->children, sibling) {
+        DeviceState *dev = kid->child;
+        qdev_enumerate_set_port(ps, dev);
+    }
+}
+
+static void scsi_emulate_set_target_port_groups(SCSIDiskReq *r, uint8_t *inbuf)
+{
+    SCSIRequest *req = &r->req;
+    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+    uint32_t buflen = scsi_data_cdb_xfer(r->req.cmd.buf);
+    uint8_t *p = inbuf;
+    PortGroupEnumerate pg;
+    PortGroupSetEnumerate ps;
+    int i, pg_found = 0;
+
+    if (!s->wwn) {
+        scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+    }
+
+    pg.numgrp = 0;
+    pg.wwn = s->wwn;
+    qbus_enumerate_port_group(&pg, sysbus_get_default());
+
+    p = &inbuf[4];
+    /* Validate input before continuing */
+    while (p < inbuf + buflen) {
+        uint16_t port_group;
+        uint8_t alua_state;
+
+        port_group = ((uint16_t)p[2] << 8) + p[3];
+        alua_state = p[0] & 0x7;
+
+        for (i = 0; i < pg.numgrp; i++) {
+            if ((port_group == pg.grp[i]) &&
+                (alua_state == (pg.alua_state[i] & 0x0f))) {
+                printf("pg %x: port already in state %d\n",
+                       pg.grp[i], (pg.alua_state[i] & 0x0f));
+                pg_found++;
+            }
+        }
+        p += 4;
+    }
+    if (pg_found == pg.numgrp) {
+        printf("all ports in requested state\n");
+        scsi_req_complete(&r->req, GOOD);
+        return;
+    }
+
+    p = &inbuf[4];
+    while (p < inbuf + buflen) {
+        uint16_t port_group;
+        uint8_t alua_state;
+
+        port_group = ((uint16_t)p[2] << 8) + p[3];
+        alua_state = p[0] & 0x7;
+
+        if (port_group == s->port_group) {
+            printf("pg %x: explicit switch current ALUA state "
+                   "%x -> %x\n",
+                   port_group, (s->alua_state & 0x0f), alua_state);
+            s->alua_state = (s->alua_state & 0xf0) | alua_state;
+        } else {
+            ps.wwn = s->wwn;
+            ps.port_group = port_group;
+            ps.alua_state = alua_state;
+            qbus_enumerate_set_port(&ps, sysbus_get_default());
+        }
+        p += 4;
+    }
+    scsi_req_complete(&r->req, GOOD);
+}
+
 static void scsi_disk_emulate_write_data(SCSIRequest *req)
 {
     SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
@@ -1951,6 +2056,16 @@ static void scsi_disk_emulate_write_data(SCSIRequest 
*req)
         scsi_disk_emulate_write_same(r, r->iov.iov_base);
         break;
 
+    case MAINTENANCE_OUT:
+        if ((req->cmd.buf[1] & 31) == MO_SET_TARGET_PORT_GROUPS) {
+            DPRINTF("MO SET TARGET PORT GROUPS\n");
+            scsi_emulate_set_target_port_groups(r, r->iov.iov_base);
+            break;
+        }
+        DPRINTF("Unsupported Maintenance Out\n");
+        scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+        break;
+
     default:
         abort();
     }
@@ -2215,6 +2330,9 @@ static int32_t scsi_disk_emulate_command(SCSIRequest 
*req, uint8_t *buf)
         DPRINTF("Unsupported Maintenance In\n");
         goto illegal_request;
         break;
+    case MAINTENANCE_OUT:
+        DPRINTF("Maintenance Out (len %lu)\n", (long)r->req.cmd.xfer);
+        break;
     case MECHANISM_STATUS:
         buflen = scsi_emulate_mechanism_status(s, outbuf);
         if (buflen < 0) {
diff --git a/include/block/scsi.h b/include/block/scsi.h
index a9d0f64..47a25b8 100644
--- a/include/block/scsi.h
+++ b/include/block/scsi.h
@@ -156,6 +156,11 @@ const char *scsi_command_name(uint8_t cmd);
 #define MI_REPORT_TARGET_PORT_GROUPS 0xa
 
 /*
+ * MAINTENANCE OUT subcodes
+ */
+#define MO_SET_TARGET_PORT_GROUPS 0xa
+
+/*
  * READ POSITION service action codes
  */
 #define SHORT_FORM_BLOCK_ID  0x00
-- 
1.8.4.5




reply via email to

[Prev in Thread] Current Thread [Next in Thread]