@@ -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 = {
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(-)