in hw/s390x/virtio-ccw.c [316:723]
static int virtio_ccw_cb(SubchDev *sch, CCW1 ccw)
{
int ret;
VirtioRevInfo revinfo;
uint8_t status;
VirtioFeatDesc features;
hwaddr indicators;
VqConfigBlock vq_config;
VirtioCcwDevice *dev = sch->driver_data;
VirtIODevice *vdev = virtio_ccw_get_vdev(sch);
bool check_len;
int len;
VirtioThinintInfo thinint;
if (!dev) {
return -EINVAL;
}
trace_virtio_ccw_interpret_ccw(sch->cssid, sch->ssid, sch->schid,
ccw.cmd_code);
check_len = !((ccw.flags & CCW_FLAG_SLI) && !(ccw.flags & CCW_FLAG_DC));
if (dev->revision < 0 && ccw.cmd_code != CCW_CMD_SET_VIRTIO_REV) {
if (dev->force_revision_1) {
/*
* virtio-1 drivers must start with negotiating to a revision >= 1,
* so post a command reject for all other commands
*/
return -ENOSYS;
} else {
/*
* If the driver issues any command that is not SET_VIRTIO_REV,
* we'll have to operate the device in legacy mode.
*/
dev->revision = 0;
}
}
/* Look at the command. */
switch (ccw.cmd_code) {
case CCW_CMD_SET_VQ:
ret = virtio_ccw_handle_set_vq(sch, ccw, check_len, dev->revision < 1);
break;
case CCW_CMD_VDEV_RESET:
virtio_ccw_reset_virtio(dev, vdev);
ret = 0;
break;
case CCW_CMD_READ_FEAT:
if (check_len) {
if (ccw.count != sizeof(features)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(features)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
ccw_dstream_advance(&sch->cds, sizeof(features.features));
ret = ccw_dstream_read(&sch->cds, features.index);
if (ret) {
break;
}
if (features.index == 0) {
if (dev->revision >= 1) {
/* Don't offer legacy features for modern devices. */
features.features = (uint32_t)
(vdev->host_features & ~vdc->legacy_features);
} else {
features.features = (uint32_t)vdev->host_features;
}
} else if ((features.index == 1) && (dev->revision >= 1)) {
/*
* Only offer feature bits beyond 31 if the guest has
* negotiated at least revision 1.
*/
features.features = (uint32_t)(vdev->host_features >> 32);
} else {
/* Return zeroes if the guest supports more feature bits. */
features.features = 0;
}
ccw_dstream_rewind(&sch->cds);
features.features = cpu_to_le32(features.features);
ret = ccw_dstream_write(&sch->cds, features.features);
if (!ret) {
sch->curr_status.scsw.count = ccw.count - sizeof(features);
}
}
break;
case CCW_CMD_WRITE_FEAT:
if (check_len) {
if (ccw.count != sizeof(features)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(features)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read(&sch->cds, features);
if (ret) {
break;
}
features.features = le32_to_cpu(features.features);
if (features.index == 0) {
virtio_set_features(vdev,
(vdev->guest_features & 0xffffffff00000000ULL) |
features.features);
} else if ((features.index == 1) && (dev->revision >= 1)) {
/*
* If the guest did not negotiate at least revision 1,
* we did not offer it any feature bits beyond 31. Such a
* guest passing us any bit here is therefore buggy.
*/
virtio_set_features(vdev,
(vdev->guest_features & 0x00000000ffffffffULL) |
((uint64_t)features.features << 32));
} else {
/*
* If the guest supports more feature bits, assert that it
* passes us zeroes for those we don't support.
*/
if (features.features) {
qemu_log_mask(LOG_GUEST_ERROR,
"Guest bug: features[%i]=%x (expected 0)",
features.index, features.features);
/* XXX: do a unit check here? */
}
}
sch->curr_status.scsw.count = ccw.count - sizeof(features);
ret = 0;
}
break;
case CCW_CMD_READ_CONF:
if (check_len) {
if (ccw.count > vdev->config_len) {
ret = -EINVAL;
break;
}
}
len = MIN(ccw.count, vdev->config_len);
if (!ccw.cda) {
ret = -EFAULT;
} else {
virtio_bus_get_vdev_config(&dev->bus, vdev->config);
ret = ccw_dstream_write_buf(&sch->cds, vdev->config, len);
if (ret) {
sch->curr_status.scsw.count = ccw.count - len;
}
}
break;
case CCW_CMD_WRITE_CONF:
if (check_len) {
if (ccw.count > vdev->config_len) {
ret = -EINVAL;
break;
}
}
len = MIN(ccw.count, vdev->config_len);
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read_buf(&sch->cds, vdev->config, len);
if (!ret) {
virtio_bus_set_vdev_config(&dev->bus, vdev->config);
sch->curr_status.scsw.count = ccw.count - len;
}
}
break;
case CCW_CMD_READ_STATUS:
if (check_len) {
if (ccw.count != sizeof(status)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(status)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
address_space_stb(&address_space_memory, ccw.cda, vdev->status,
MEMTXATTRS_UNSPECIFIED, NULL);
sch->curr_status.scsw.count = ccw.count - sizeof(vdev->status);
ret = 0;
}
break;
case CCW_CMD_WRITE_STATUS:
if (check_len) {
if (ccw.count != sizeof(status)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(status)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read(&sch->cds, status);
if (ret) {
break;
}
if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
virtio_ccw_stop_ioeventfd(dev);
}
if (virtio_set_status(vdev, status) == 0) {
if (vdev->status == 0) {
virtio_ccw_reset_virtio(dev, vdev);
}
if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
virtio_ccw_start_ioeventfd(dev);
}
sch->curr_status.scsw.count = ccw.count - sizeof(status);
ret = 0;
} else {
/* Trigger a command reject. */
ret = -ENOSYS;
}
}
break;
case CCW_CMD_SET_IND:
if (check_len) {
if (ccw.count != sizeof(indicators)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(indicators)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (sch->thinint_active) {
/* Trigger a command reject. */
ret = -ENOSYS;
break;
}
if (virtio_get_num_queues(vdev) > NR_CLASSIC_INDICATOR_BITS) {
/* More queues than indicator bits --> trigger a reject */
ret = -ENOSYS;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read(&sch->cds, indicators);
if (ret) {
break;
}
indicators = be64_to_cpu(indicators);
dev->indicators = get_indicator(indicators, sizeof(uint64_t));
sch->curr_status.scsw.count = ccw.count - sizeof(indicators);
ret = 0;
}
break;
case CCW_CMD_SET_CONF_IND:
if (check_len) {
if (ccw.count != sizeof(indicators)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(indicators)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read(&sch->cds, indicators);
if (ret) {
break;
}
indicators = be64_to_cpu(indicators);
dev->indicators2 = get_indicator(indicators, sizeof(uint64_t));
sch->curr_status.scsw.count = ccw.count - sizeof(indicators);
ret = 0;
}
break;
case CCW_CMD_READ_VQ_CONF:
if (check_len) {
if (ccw.count != sizeof(vq_config)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(vq_config)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else {
ret = ccw_dstream_read(&sch->cds, vq_config.index);
if (ret) {
break;
}
vq_config.index = be16_to_cpu(vq_config.index);
if (vq_config.index >= VIRTIO_QUEUE_MAX) {
ret = -EINVAL;
break;
}
vq_config.num_max = virtio_queue_get_num(vdev,
vq_config.index);
vq_config.num_max = cpu_to_be16(vq_config.num_max);
ret = ccw_dstream_write(&sch->cds, vq_config.num_max);
if (!ret) {
sch->curr_status.scsw.count = ccw.count - sizeof(vq_config);
}
}
break;
case CCW_CMD_SET_IND_ADAPTER:
if (check_len) {
if (ccw.count != sizeof(thinint)) {
ret = -EINVAL;
break;
}
} else if (ccw.count < sizeof(thinint)) {
/* Can't execute command. */
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
} else if (dev->indicators && !sch->thinint_active) {
/* Trigger a command reject. */
ret = -ENOSYS;
} else {
if (ccw_dstream_read(&sch->cds, thinint)) {
ret = -EFAULT;
} else {
thinint.ind_bit = be64_to_cpu(thinint.ind_bit);
thinint.summary_indicator =
be64_to_cpu(thinint.summary_indicator);
thinint.device_indicator =
be64_to_cpu(thinint.device_indicator);
dev->summary_indicator =
get_indicator(thinint.summary_indicator, sizeof(uint8_t));
dev->indicators =
get_indicator(thinint.device_indicator,
thinint.ind_bit / 8 + 1);
dev->thinint_isc = thinint.isc;
dev->routes.adapter.ind_offset = thinint.ind_bit;
dev->routes.adapter.summary_offset = 7;
dev->routes.adapter.adapter_id = css_get_adapter_id(
CSS_IO_ADAPTER_VIRTIO,
dev->thinint_isc);
sch->thinint_active = ((dev->indicators != NULL) &&
(dev->summary_indicator != NULL));
sch->curr_status.scsw.count = ccw.count - sizeof(thinint);
ret = 0;
}
}
break;
case CCW_CMD_SET_VIRTIO_REV:
len = sizeof(revinfo);
if (ccw.count < len) {
ret = -EINVAL;
break;
}
if (!ccw.cda) {
ret = -EFAULT;
break;
}
ret = ccw_dstream_read_buf(&sch->cds, &revinfo, 4);
if (ret < 0) {
break;
}
revinfo.revision = be16_to_cpu(revinfo.revision);
revinfo.length = be16_to_cpu(revinfo.length);
if (ccw.count < len + revinfo.length ||
(check_len && ccw.count > len + revinfo.length)) {
ret = -EINVAL;
break;
}
/*
* Once we start to support revisions with additional data, we'll
* need to fetch it here. Nothing to do for now, though.
*/
if (dev->revision >= 0 ||
revinfo.revision > virtio_ccw_rev_max(dev) ||
(dev->force_revision_1 && !revinfo.revision)) {
ret = -ENOSYS;
break;
}
ret = 0;
dev->revision = revinfo.revision;
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}