| /* |
| * vhost_scsi host device |
| * |
| * Copyright IBM, Corp. 2011 |
| * |
| * Authors: |
| * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> |
| * |
| * Changes for QEMU mainline + tcm_vhost kernel upstream: |
| * Nicholas Bellinger <nab@risingtidesystems.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2 or later. |
| * See the COPYING.LIB file in the top-level directory. |
| * |
| */ |
| |
| #include <sys/ioctl.h> |
| #include "config.h" |
| #include "qemu/queue.h" |
| #include "monitor/monitor.h" |
| #include "migration/migration.h" |
| #include "hw/virtio/vhost-scsi.h" |
| #include "hw/virtio/vhost.h" |
| #include "hw/virtio/virtio-scsi.h" |
| #include "hw/virtio/virtio-bus.h" |
| #include "hw/virtio/virtio-access.h" |
| #include "hw/fw-path-provider.h" |
| |
| /* Features supported by host kernel. */ |
| static const int kernel_feature_bits[] = { |
| VIRTIO_F_NOTIFY_ON_EMPTY, |
| VIRTIO_RING_F_INDIRECT_DESC, |
| VIRTIO_RING_F_EVENT_IDX, |
| VIRTIO_SCSI_F_HOTPLUG, |
| VHOST_INVALID_FEATURE_BIT |
| }; |
| |
| static int vhost_scsi_set_endpoint(VHostSCSI *s) |
| { |
| VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); |
| const VhostOps *vhost_ops = s->dev.vhost_ops; |
| struct vhost_scsi_target backend; |
| int ret; |
| |
| memset(&backend, 0, sizeof(backend)); |
| pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn); |
| ret = vhost_ops->vhost_call(&s->dev, VHOST_SCSI_SET_ENDPOINT, &backend); |
| if (ret < 0) { |
| return -errno; |
| } |
| return 0; |
| } |
| |
| static void vhost_scsi_clear_endpoint(VHostSCSI *s) |
| { |
| VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); |
| struct vhost_scsi_target backend; |
| const VhostOps *vhost_ops = s->dev.vhost_ops; |
| |
| memset(&backend, 0, sizeof(backend)); |
| pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn); |
| vhost_ops->vhost_call(&s->dev, VHOST_SCSI_CLEAR_ENDPOINT, &backend); |
| } |
| |
| static int vhost_scsi_start(VHostSCSI *s) |
| { |
| int ret, abi_version, i; |
| VirtIODevice *vdev = VIRTIO_DEVICE(s); |
| BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); |
| VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); |
| const VhostOps *vhost_ops = s->dev.vhost_ops; |
| |
| if (!k->set_guest_notifiers) { |
| error_report("binding does not support guest notifiers"); |
| return -ENOSYS; |
| } |
| |
| ret = vhost_ops->vhost_call(&s->dev, |
| VHOST_SCSI_GET_ABI_VERSION, &abi_version); |
| if (ret < 0) { |
| return -errno; |
| } |
| if (abi_version > VHOST_SCSI_ABI_VERSION) { |
| error_report("vhost-scsi: The running tcm_vhost kernel abi_version:" |
| " %d is greater than vhost_scsi userspace supports: %d, please" |
| " upgrade your version of QEMU\n", abi_version, |
| VHOST_SCSI_ABI_VERSION); |
| return -ENOSYS; |
| } |
| |
| ret = vhost_dev_enable_notifiers(&s->dev, vdev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| s->dev.acked_features = vdev->guest_features; |
| ret = vhost_dev_start(&s->dev, vdev); |
| if (ret < 0) { |
| error_report("Error start vhost dev"); |
| goto err_notifiers; |
| } |
| |
| ret = vhost_scsi_set_endpoint(s); |
| if (ret < 0) { |
| error_report("Error set vhost-scsi endpoint"); |
| goto err_vhost_stop; |
| } |
| |
| ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, true); |
| if (ret < 0) { |
| error_report("Error binding guest notifier"); |
| goto err_endpoint; |
| } |
| |
| /* guest_notifier_mask/pending not used yet, so just unmask |
| * everything here. virtio-pci will do the right thing by |
| * enabling/disabling irqfd. |
| */ |
| for (i = 0; i < s->dev.nvqs; i++) { |
| vhost_virtqueue_mask(&s->dev, vdev, i, false); |
| } |
| |
| return ret; |
| |
| err_endpoint: |
| vhost_scsi_clear_endpoint(s); |
| err_vhost_stop: |
| vhost_dev_stop(&s->dev, vdev); |
| err_notifiers: |
| vhost_dev_disable_notifiers(&s->dev, vdev); |
| return ret; |
| } |
| |
| static void vhost_scsi_stop(VHostSCSI *s) |
| { |
| VirtIODevice *vdev = VIRTIO_DEVICE(s); |
| BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); |
| VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); |
| int ret = 0; |
| |
| if (k->set_guest_notifiers) { |
| ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false); |
| if (ret < 0) { |
| error_report("vhost guest notifier cleanup failed: %d\n", ret); |
| } |
| } |
| assert(ret >= 0); |
| |
| vhost_scsi_clear_endpoint(s); |
| vhost_dev_stop(&s->dev, vdev); |
| vhost_dev_disable_notifiers(&s->dev, vdev); |
| } |
| |
| static uint32_t vhost_scsi_get_features(VirtIODevice *vdev, |
| uint32_t features) |
| { |
| VHostSCSI *s = VHOST_SCSI(vdev); |
| |
| return vhost_get_features(&s->dev, kernel_feature_bits, features); |
| } |
| |
| static void vhost_scsi_set_config(VirtIODevice *vdev, |
| const uint8_t *config) |
| { |
| VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config; |
| VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev); |
| |
| if ((uint32_t) virtio_ldl_p(vdev, &scsiconf->sense_size) != vs->sense_size || |
| (uint32_t) virtio_ldl_p(vdev, &scsiconf->cdb_size) != vs->cdb_size) { |
| error_report("vhost-scsi does not support changing the sense data and CDB sizes"); |
| exit(1); |
| } |
| } |
| |
| static void vhost_scsi_set_status(VirtIODevice *vdev, uint8_t val) |
| { |
| VHostSCSI *s = (VHostSCSI *)vdev; |
| bool start = (val & VIRTIO_CONFIG_S_DRIVER_OK); |
| |
| if (s->dev.started == start) { |
| return; |
| } |
| |
| if (start) { |
| int ret; |
| |
| ret = vhost_scsi_start(s); |
| if (ret < 0) { |
| error_report("virtio-scsi: unable to start vhost: %s\n", |
| strerror(-ret)); |
| |
| /* There is no userspace virtio-scsi fallback so exit */ |
| exit(1); |
| } |
| } else { |
| vhost_scsi_stop(s); |
| } |
| } |
| |
| static void vhost_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| } |
| |
| static void vhost_scsi_realize(DeviceState *dev, Error **errp) |
| { |
| VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev); |
| VHostSCSI *s = VHOST_SCSI(dev); |
| Error *err = NULL; |
| int vhostfd = -1; |
| int ret; |
| |
| if (!vs->conf.wwpn) { |
| error_setg(errp, "vhost-scsi: missing wwpn"); |
| return; |
| } |
| |
| if (vs->conf.vhostfd) { |
| vhostfd = monitor_fd_param(cur_mon, vs->conf.vhostfd, &err); |
| if (vhostfd == -1) { |
| error_setg(errp, "vhost-scsi: unable to parse vhostfd: %s", |
| error_get_pretty(err)); |
| error_free(err); |
| return; |
| } |
| } else { |
| vhostfd = open("/dev/vhost-scsi", O_RDWR); |
| if (vhostfd < 0) { |
| error_setg(errp, "vhost-scsi: open vhost char device failed: %s", |
| strerror(errno)); |
| return; |
| } |
| } |
| |
| virtio_scsi_common_realize(dev, &err, vhost_dummy_handle_output, |
| vhost_dummy_handle_output, |
| vhost_dummy_handle_output); |
| if (err != NULL) { |
| error_propagate(errp, err); |
| close(vhostfd); |
| return; |
| } |
| |
| s->dev.nvqs = VHOST_SCSI_VQ_NUM_FIXED + vs->conf.num_queues; |
| s->dev.vqs = g_new(struct vhost_virtqueue, s->dev.nvqs); |
| s->dev.vq_index = 0; |
| s->dev.backend_features = 0; |
| |
| ret = vhost_dev_init(&s->dev, (void *)(uintptr_t)vhostfd, |
| VHOST_BACKEND_TYPE_KERNEL, true); |
| if (ret < 0) { |
| error_setg(errp, "vhost-scsi: vhost initialization failed: %s", |
| strerror(-ret)); |
| return; |
| } |
| |
| /* At present, channel and lun both are 0 for bootable vhost-scsi disk */ |
| s->channel = 0; |
| s->lun = 0; |
| /* Note: we can also get the minimum tpgt from kernel */ |
| s->target = vs->conf.boot_tpgt; |
| |
| error_setg(&s->migration_blocker, |
| "vhost-scsi does not support migration"); |
| migrate_add_blocker(s->migration_blocker); |
| } |
| |
| static void vhost_scsi_unrealize(DeviceState *dev, Error **errp) |
| { |
| VirtIODevice *vdev = VIRTIO_DEVICE(dev); |
| VHostSCSI *s = VHOST_SCSI(dev); |
| |
| migrate_del_blocker(s->migration_blocker); |
| error_free(s->migration_blocker); |
| |
| /* This will stop vhost backend. */ |
| vhost_scsi_set_status(vdev, 0); |
| |
| g_free(s->dev.vqs); |
| |
| virtio_scsi_common_unrealize(dev, errp); |
| } |
| |
| /* |
| * Implementation of an interface to adjust firmware path |
| * for the bootindex property handling. |
| */ |
| static char *vhost_scsi_get_fw_dev_path(FWPathProvider *p, BusState *bus, |
| DeviceState *dev) |
| { |
| VHostSCSI *s = VHOST_SCSI(dev); |
| /* format: channel@channel/vhost-scsi@target,lun */ |
| return g_strdup_printf("channel@%x/%s@%x,%x", s->channel, |
| qdev_fw_name(dev), s->target, s->lun); |
| } |
| |
| static Property vhost_scsi_properties[] = { |
| DEFINE_VHOST_SCSI_PROPERTIES(VHostSCSI, parent_obj.conf), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void vhost_scsi_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); |
| FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(klass); |
| |
| dc->props = vhost_scsi_properties; |
| set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); |
| vdc->realize = vhost_scsi_realize; |
| vdc->unrealize = vhost_scsi_unrealize; |
| vdc->get_features = vhost_scsi_get_features; |
| vdc->set_config = vhost_scsi_set_config; |
| vdc->set_status = vhost_scsi_set_status; |
| fwc->get_dev_path = vhost_scsi_get_fw_dev_path; |
| } |
| |
| static void vhost_scsi_instance_init(Object *obj) |
| { |
| VHostSCSI *dev = VHOST_SCSI(obj); |
| |
| device_add_bootindex_property(obj, &dev->bootindex, "bootindex", NULL, |
| DEVICE(dev), NULL); |
| } |
| |
| static const TypeInfo vhost_scsi_info = { |
| .name = TYPE_VHOST_SCSI, |
| .parent = TYPE_VIRTIO_SCSI_COMMON, |
| .instance_size = sizeof(VHostSCSI), |
| .class_init = vhost_scsi_class_init, |
| .instance_init = vhost_scsi_instance_init, |
| .interfaces = (InterfaceInfo[]) { |
| { TYPE_FW_PATH_PROVIDER }, |
| { } |
| }, |
| }; |
| |
| static void virtio_register_types(void) |
| { |
| type_register_static(&vhost_scsi_info); |
| } |
| |
| type_init(virtio_register_types) |