Implement ALUA policies 'optimized-standby' and 'optimized-nonoptimized' to emulate active/passive multipath configurations.
Signed-off-by: Hannes Reinecke <h...@suse.de> --- hw/scsi/scsi-disk.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 204 insertions(+), 7 deletions(-) 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 = { -- 1.8.4.5