Implement support for REPORT TARGET PORT GROUPS scsi command. Note that target port groups are referenced per SCSI wwn , which might be connected to different hosts. So we need to walk the entire qtree to find all eligible SCSI devices.
Signed-off-by: Hannes Reinecke <h...@suse.de> --- hw/scsi/scsi-disk.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index 583cacd..8dabed3 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -795,6 +795,11 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) outbuf[4] = 36 - 5; } + /* Enable TGPS bit */ + if (s->wwn) { + outbuf[5] = 0x10; + } + /* Sync data transfer and TCQ. */ outbuf[7] = 0x10 | (req->bus->info->tcq ? 0x02 : 0); return buflen; @@ -1819,6 +1824,97 @@ static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf) scsi_write_same_complete, data); } +typedef struct PortGroupEnumerate { + int numgrp; + uint64_t wwn; + uint16_t grp[16]; + uint8_t alua_state[16]; + uint16_t alua_mask; +} PortGroupEnumerate; + +static void qbus_enumerate_port_group(PortGroupEnumerate *, BusState *); + +static void qdev_enumerate_port_group(PortGroupEnumerate *pg, DeviceState *dev) +{ + BusState *child; + + if (!strcmp(object_get_typename(OBJECT(dev->parent_bus)), TYPE_SCSI_BUS)) { + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev); + DPRINTF("wwn 0x%" PRIx64 " pg %u state %x\n", + s->wwn, s->port_group, (s->alua_state & 0x0f)); + if (s->wwn == pg->wwn) { + bool pg_found = false; + int i; + + for (i = 0; i < pg->numgrp; i++) { + if (pg->grp[i] == s->port_group) { + pg_found = true; + break; + } + } + if (!pg_found) { + pg->grp[pg->numgrp] = s->port_group; + pg->alua_state[pg->numgrp] = s->alua_state; + pg->alua_mask |= 1 << (s->alua_state & 0x0f); + pg->numgrp++; + } + } + } + QLIST_FOREACH(child, &dev->child_bus, sibling) { + qbus_enumerate_port_group(pg, child); + } +} + +static void qbus_enumerate_port_group(PortGroupEnumerate *pg, BusState *bus) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + qdev_enumerate_port_group(pg, dev); + } +} + +typedef struct PortDescEnumerate { + int numdesc; + uint64_t wwn; + uint16_t port_group; + uint8_t *desc; +} PortDescEnumerate; + +static void qbus_enumerate_port_desc(PortDescEnumerate *, BusState *); + +static void qdev_enumerate_port_desc(PortDescEnumerate *pd, 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 == pd->wwn && + s->port_group == pd->port_group) { + pd->desc[0] = 0; + pd->desc[1] = 0; + pd->desc[2] = (s->port_index >> 8) & 0xff; + pd->desc[3] = s->port_index & 0xff; + pd->desc += 4; + pd->numdesc++; + } + } + QLIST_FOREACH(child, &dev->child_bus, sibling) { + qbus_enumerate_port_desc(pd, child); + } +} + +static void qbus_enumerate_port_desc(PortDescEnumerate *pd, BusState *bus) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + qdev_enumerate_port_desc(pd, dev); + } +} + static void scsi_disk_emulate_write_data(SCSIRequest *req) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); @@ -1860,6 +1956,54 @@ static void scsi_disk_emulate_write_data(SCSIRequest *req) } } +static int scsi_emulate_report_target_port_groups(SCSIDiskState *s, uint8_t *inbuf) +{ + uint8_t *p = inbuf; + PortGroupEnumerate pg; + PortDescEnumerate pd; + int buflen = 0, i; + + if (!s->wwn) { + return -1; + } + + pg.numgrp = 0; + pg.wwn = s->wwn; + + if (sysbus_get_default()) + qbus_enumerate_port_group(&pg, sysbus_get_default()); + + if (pg.numgrp == 0) { + return -1; + } + DPRINTF("wwn 0x%" PRIx64 " %d port groups \n", s->wwn, pg.numgrp); + p = &inbuf[4]; + for (i = 0; i < pg.numgrp; i++) { + pd.numdesc = 0; + pd.wwn = s->wwn; + pd.port_group = pg.grp[i]; + pd.desc = &p[8]; + buflen += 8; + qbus_enumerate_port_desc(&pd, sysbus_get_default()); + DPRINTF("pg %x: %d port descriptors\n", pg.grp[i], pd.numdesc); + p[0] = pg.alua_state[i]; + p[1] = pg.alua_mask; + p[2] = (pg.grp[i] >> 8) & 0xff; + p[3] = pg.grp[i] & 0xff; + p[7] = pd.numdesc; + p += 8 + pd.numdesc * 4; + buflen += pd.numdesc * 4; + } + if (buflen) { + inbuf[0] = (buflen >> 24) & 0xff; + inbuf[1] = (buflen >> 16) & 0xff; + inbuf[2] = (buflen >> 8) & 0xff; + inbuf[3] = buflen & 0xff; + } + + return buflen + 4; +} + static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); @@ -2058,6 +2202,19 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) goto illegal_request; } break; + case MAINTENANCE_IN: + if ((req->cmd.buf[1] & 31) == MI_REPORT_TARGET_PORT_GROUPS) { + DPRINTF("MI REPORT TARGET PORT GROUPS\n"); + memset(outbuf, 0, req->cmd.xfer); + buflen = scsi_emulate_report_target_port_groups(s, outbuf); + if (buflen < 0) { + goto illegal_request; + } + break; + } + DPRINTF("Unsupported Maintenance In\n"); + goto illegal_request; + break; case MECHANISM_STATUS: buflen = scsi_emulate_mechanism_status(s, outbuf); if (buflen < 0) { @@ -2918,7 +3075,7 @@ static void scsi_disk_set_alua_state(Object *obj, Visitor *v, void *opaque, goto out; } - s->alua_state = alua_state; + s->alua_state = (s->alua_state & 0xf0) | alua_state; scsi_device_set_ua(&s->qdev, SENSE_CODE(ASYMMETRIC_ACCESS_STATE_CHANGED)); out: -- 1.8.4.5