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 <h...@suse.de> --- 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