diff mbox

[5/8] scsi-disk: implement ALUA policy

Message ID 1448636346-24641-6-git-send-email-hare@suse.de
State New
Headers show

Commit Message

Hannes Reinecke Nov. 27, 2015, 2:59 p.m. UTC
Implement ALUA policies 'optimized-standby' and 'optimized-nonoptimized'
to emulate active/passive multipath configurations.

Signed-off-by: Hannes Reinecke <hare@suse.de>
---
 hw/scsi/scsi-disk.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 204 insertions(+), 7 deletions(-)
diff mbox

Patch

diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 52c73be..59d09e4 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -89,6 +89,7 @@  struct SCSIDiskState
     char *serial;
     char *vendor;
     char *product;
+    char *alua_policy;
     bool tray_open;
     bool tray_locked;
 };
@@ -795,9 +796,14 @@  static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
         outbuf[4] = 36 - 5;
     }
 
-    /* Enable TGPS bit */
+    /* Enable TGPS bits */
     if (s->wwn) {
-        outbuf[5] = 0x10;
+        if (s->alua_policy &&
+            !strcmp(s->alua_policy, "optimized-standby")) {
+            outbuf[5] = 0x30;
+        } else {
+            outbuf[5] = 0x10;
+        }
     }
 
     /* Sync data transfer and TCQ.  */
@@ -1958,18 +1964,53 @@  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);
+    uint16_t current_port_group = (uint16_t)-1;
+    uint16_t alternate_port_group = (uint16_t)-1;
+    uint8_t primary_alua_state = ALUA_STATE_ACTIVE_OPTIMIZED;
+    uint8_t secondary_alua_state = ALUA_STATE_STANDBY;
+    uint8_t new_current_state, new_alternate_state = 0;
     uint8_t *p = inbuf;
     PortGroupEnumerate pg;
     PortGroupSetEnumerate ps;
-    int i, pg_found = 0;
+    bool switch_current, switch_alternate;
+    int i, pg_found = 0, primary_state_found = 0, secondary_state_found = 0;
 
     if (!s->wwn) {
         scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+        return;
+    }
+
+    if (!s->alua_policy) {
+        printf("No ALUA policy set\n");
+        scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+        return;
     }
 
     pg.numgrp = 0;
     pg.wwn = s->wwn;
     qbus_enumerate_port_group(&pg, sysbus_get_default());
+    if (s->alua_policy) {
+        if (pg.numgrp != 2) {
+            printf("ALUA policy can not handle %d port groups", pg.numgrp);
+            scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+            return;
+        }
+        if (!strcmp(s->alua_policy, "optimized-nonoptimized")) {
+            primary_alua_state = ALUA_STATE_ACTIVE_OPTIMIZED;
+            secondary_alua_state = ALUA_STATE_ACTIVE_NON_OPTIMIZED;
+        }
+        for (i = 0; i < pg.numgrp; i++) {
+            if (pg.grp[i] == s->port_group) {
+                current_port_group = pg.grp[i];
+                new_current_state = (pg.alua_state[i] & 0x0f);
+            } else {
+                alternate_port_group = pg.grp[i];
+                new_alternate_state = (pg.alua_state[i] & 0x0f);
+            }
+        }
+    } else {
+        current_port_group = s->port_group;
+    }
 
     p = &inbuf[4];
     /* Validate input before continuing */
@@ -1980,6 +2021,24 @@  static void scsi_emulate_set_target_port_groups(SCSIDiskReq *r, uint8_t *inbuf)
         port_group = ((uint16_t)p[2] << 8) + p[3];
         alua_state = p[0] & 0x7;
 
+        if (s->alua_policy) {
+            if ((port_group != current_port_group) &&
+                (port_group != alternate_port_group)) {
+                printf("pg %x: port_group not handled by policy", port_group);
+                scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+                return;
+            }
+            if (alua_state == primary_alua_state) {
+                primary_state_found++;
+            } else if (alua_state == secondary_alua_state) {
+                secondary_state_found++;
+            } else {
+                printf("pg %x: state %x not handled by policy\n",
+                       port_group, alua_state);
+                scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+                return;
+            }
+        }
         for (i = 0; i < pg.numgrp; i++) {
             if ((port_group == pg.grp[i]) &&
                 (alua_state == (pg.alua_state[i] & 0x0f))) {
@@ -1990,12 +2049,22 @@  static void scsi_emulate_set_target_port_groups(SCSIDiskReq *r, uint8_t *inbuf)
         }
         p += 4;
     }
+    if (s->alua_policy) {
+        if (primary_state_found != 1 &&
+            secondary_state_found != 1) {
+            printf("State change forbidden by policy\n");
+            scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+            return;
+        }
+    }
     if (pg_found == pg.numgrp) {
         printf("all ports in requested state\n");
         scsi_req_complete(&r->req, GOOD);
         return;
     }
 
+    switch_current = true;
+    switch_alternate = true;
     p = &inbuf[4];
     while (p < inbuf + buflen) {
         uint16_t port_group;
@@ -2003,20 +2072,44 @@  static void scsi_emulate_set_target_port_groups(SCSIDiskReq *r, uint8_t *inbuf)
 
         port_group = ((uint16_t)p[2] << 8) + p[3];
         alua_state = p[0] & 0x7;
-
-        if (port_group == s->port_group) {
+        if (port_group == current_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;
+            new_current_state = alua_state;
+            switch_current = false;
         } else {
             ps.wwn = s->wwn;
             ps.port_group = port_group;
             ps.alua_state = alua_state;
+            new_alternate_state = alua_state;
             qbus_enumerate_set_port(&ps, sysbus_get_default());
+            switch_alternate = false;
         }
         p += 4;
     }
+
+    if (s->alua_policy) {
+        ps.wwn = s->wwn;
+        if (switch_current) {
+            ps.port_group = current_port_group;
+            if (new_alternate_state == primary_alua_state) {
+                ps.alua_state = secondary_alua_state;
+            } else {
+                ps.alua_state = primary_alua_state;
+            }
+            qbus_enumerate_set_port(&ps, sysbus_get_default());
+        } else if (switch_alternate) {
+            ps.port_group = alternate_port_group;
+            if (new_current_state == primary_alua_state) {
+                ps.alua_state = secondary_alua_state;
+            } else {
+                ps.alua_state = primary_alua_state;
+            }
+            qbus_enumerate_set_port(&ps, sysbus_get_default());
+        }
+    }
     scsi_req_complete(&r->req, GOOD);
 }
 
@@ -3168,6 +3261,39 @@  static void scsi_disk_class_initfn(ObjectClass *klass, void *data)
     dc->vmsd  = &vmstate_scsi_disk_state;
 }
 
+static void scsi_disk_get_alua_policy(Object *obj, Visitor *v, void *opaque,
+                                      const char *name, Error **errp)
+{
+    SCSIDiskState *s = OBJECT_CHECK(SCSIDiskState, obj, "scsi-disk");
+
+    visit_type_str(v, &s->alua_policy, name, errp);
+}
+
+static void scsi_disk_set_alua_policy(Object *obj, Visitor *v, void *opaque,
+                                      const char *name, Error **errp)
+{
+    SCSIDiskState *s = OBJECT_CHECK(SCSIDiskState, obj, "scsi-disk");
+    char *alua_policy;
+    Error *local_err = NULL;
+
+    visit_type_str(v, &alua_policy, name, &local_err);
+    if (local_err) {
+        goto out;
+    }
+
+    if (strcmp(alua_policy, "optimized-standby") &&
+        strcmp(alua_policy, "optimized-nonoptimized")) {
+        error_setg(&local_err, "Invalid ALUA policy %s\n", alua_policy);
+        goto out;
+    }
+    g_free(s->alua_policy);
+    s->alua_policy = alua_policy;
+out:
+    if (local_err) {
+        error_propagate(errp, local_err);
+    }
+}
+
 static void scsi_disk_get_alua_state(Object *obj, Visitor *v, void *opaque,
                                      const char *name, Error **errp)
 {
@@ -3192,9 +3318,76 @@  static void scsi_disk_set_alua_state(Object *obj, Visitor *v, void *opaque,
         error_setg(&local_err, "Invalid ALUA state %d\n", alua_state);
         goto out;
     }
+    if (s->alua_policy) {
+        uint8_t primary_alua_state = ALUA_STATE_ACTIVE_OPTIMIZED;
+        uint8_t secondary_alua_state = ALUA_STATE_STANDBY;
+        PortGroupEnumerate pg;
+        PortGroupSetEnumerate ps;
+        bool switch_to_primary = false;
+        int i;
 
-    s->alua_state = (s->alua_state & 0xf0) | alua_state;
-    scsi_device_set_ua(&s->qdev, SENSE_CODE(ASYMMETRIC_ACCESS_STATE_CHANGED));
+        if (!strcmp(s->alua_policy, "optimized-nonoptimized")) {
+            primary_alua_state = ALUA_STATE_ACTIVE_OPTIMIZED;
+            secondary_alua_state = ALUA_STATE_ACTIVE_NON_OPTIMIZED;
+        }
+
+        if (alua_state != primary_alua_state &&
+            alua_state != secondary_alua_state) {
+            error_setg(&local_err, "ALUA state %d forbidden by policy\n",
+                       alua_state);
+            goto out;
+        }
+        if (!s->wwn) {
+            error_setg(&local_err, "No WWN set\n");
+            goto out;
+        }
+        pg.numgrp = 0;
+        pg.wwn = s->wwn;
+
+        if (sysbus_get_default()) {
+            qbus_enumerate_port_group(&pg, sysbus_get_default());
+        }
+        if (pg.numgrp == 0) {
+            error_setg(&local_err, "No port group found for %" PRIx64 "\n",
+                       s->wwn);
+            goto out;
+        }
+        if (pg.numgrp > 2) {
+            error_setg(&local_err, "Too many port groups for policy\n");
+            goto out;
+        }
+        for (i = 0; i < pg.numgrp; i++) {
+            if (pg.grp[i] == s->port_group) {
+                if ((pg.alua_state[i] & 0x0f) == alua_state) {
+                    /* Nothing to be done */
+                    goto out;
+                }
+                if (alua_state == primary_alua_state) {
+                    switch_to_primary = true;
+                }
+                printf("pg %x: implicit switch primary to new ALUA state %d\n",
+                       s->port_group, alua_state);
+                s->alua_state = (s->alua_state & 0xf0) | alua_state;
+                scsi_device_set_ua(&s->qdev,
+                                   SENSE_CODE(ASYMMETRIC_ACCESS_STATE_CHANGED));
+                continue;
+            }
+            ps.port_group = pg.grp[i];
+        }
+        ps.wwn = s->wwn;
+        if (switch_to_primary) {
+            ps.alua_state = secondary_alua_state;
+        } else {
+            ps.alua_state = primary_alua_state;
+        }
+        printf("pg %x: implicit switch secondary to new ALUA state %d\n",
+               ps.port_group, alua_state);
+        qbus_enumerate_set_port(&ps, sysbus_get_default());
+    } else {
+        s->alua_state = (s->alua_state & 0xf0) | alua_state;
+        scsi_device_set_ua(&s->qdev,
+                           SENSE_CODE(ASYMMETRIC_ACCESS_STATE_CHANGED));
+    }
 
 out:
     if (local_err) {
@@ -3208,6 +3401,10 @@  static void scsi_disk_instance_initfn(Object *obj)
                         scsi_disk_get_alua_state,
                         scsi_disk_set_alua_state, NULL, NULL, NULL);
     object_property_set_int(obj, ALUA_STATE_ACTIVE_OPTIMIZED, "alua_state", NULL);
+    object_property_add(obj, "alua_policy", "str",
+                        scsi_disk_get_alua_policy,
+                        scsi_disk_set_alua_policy, NULL, NULL, NULL);
+    object_property_set_str(obj, "", "alua_policy", NULL);
 }
 
 static const TypeInfo scsi_disk_info = {