#include "kvm/virtio-scsi.h" #include "kvm/virtio-pci-dev.h" #include "kvm/disk-image.h" #include "kvm/irq.h" #include "kvm/kvm.h" #include "kvm/pci.h" #include "kvm/ioeventfd.h" #include "kvm/guest_compat.h" #include "kvm/virtio-pci.h" #include "kvm/virtio.h" #include #include #include #define VIRTIO_SCSI_QUEUE_SIZE 128 #define NUM_VIRT_QUEUES 3 static LIST_HEAD(sdevs); static int compat_id = -1; struct scsi_dev { struct virt_queue vqs[NUM_VIRT_QUEUES]; struct virtio_scsi_config config; struct vhost_scsi_target target; u32 features; int vhost_fd; struct virtio_device vdev; struct list_head list; struct kvm *kvm; }; static u8 *get_config(struct kvm *kvm, void *dev) { struct scsi_dev *sdev = dev; return ((u8 *)(&sdev->config)); } static u32 get_host_features(struct kvm *kvm, void *dev) { return 1UL << VIRTIO_RING_F_EVENT_IDX | 1UL << VIRTIO_RING_F_INDIRECT_DESC; } static void set_guest_features(struct kvm *kvm, void *dev, u32 features) { struct scsi_dev *sdev = dev; sdev->features = features; } static void notify_status(struct kvm *kvm, void *dev, u32 status) { } static int init_vq(struct kvm *kvm, void *dev, u32 vq, u32 page_size, u32 align, u32 pfn) { struct vhost_vring_state state = { .index = vq }; struct vhost_vring_addr addr; struct scsi_dev *sdev = dev; struct virt_queue *queue; void *p; int r; compat__remove_message(compat_id); queue = &sdev->vqs[vq]; queue->pfn = pfn; p = virtio_get_vq(kvm, queue->pfn, page_size); vring_init(&queue->vring, VIRTIO_SCSI_QUEUE_SIZE, p, align); if (sdev->vhost_fd == 0) return 0; state.num = queue->vring.num; r = ioctl(sdev->vhost_fd, VHOST_SET_VRING_NUM, &state); if (r < 0) die_perror("VHOST_SET_VRING_NUM failed"); state.num = 0; r = ioctl(sdev->vhost_fd, VHOST_SET_VRING_BASE, &state); if (r < 0) die_perror("VHOST_SET_VRING_BASE failed"); addr = (struct vhost_vring_addr) { .index = vq, .desc_user_addr = (u64)(unsigned long)queue->vring.desc, .avail_user_addr = (u64)(unsigned long)queue->vring.avail, .used_user_addr = (u64)(unsigned long)queue->vring.used, }; r = ioctl(sdev->vhost_fd, VHOST_SET_VRING_ADDR, &addr); if (r < 0) die_perror("VHOST_SET_VRING_ADDR failed"); return 0; } static void notify_vq_gsi(struct kvm *kvm, void *dev, u32 vq, u32 gsi) { struct vhost_vring_file file; struct scsi_dev *sdev = dev; int r; if (sdev->vhost_fd == 0) return; file = (struct vhost_vring_file) { .index = vq, .fd = eventfd(0, 0), }; r = irq__add_irqfd(kvm, gsi, file.fd, -1); if (r < 0) die_perror("KVM_IRQFD failed"); r = ioctl(sdev->vhost_fd, VHOST_SET_VRING_CALL, &file); if (r < 0) die_perror("VHOST_SET_VRING_CALL failed"); if (vq > 0) return; r = ioctl(sdev->vhost_fd, VHOST_SCSI_SET_ENDPOINT, &sdev->target); if (r != 0) die("VHOST_SCSI_SET_ENDPOINT failed %d", errno); } static void notify_vq_eventfd(struct kvm *kvm, void *dev, u32 vq, u32 efd) { struct scsi_dev *sdev = dev; struct vhost_vring_file file = { .index = vq, .fd = efd, }; int r; if (sdev->vhost_fd == 0) return; r = ioctl(sdev->vhost_fd, VHOST_SET_VRING_KICK, &file); if (r < 0) die_perror("VHOST_SET_VRING_KICK failed"); } static int notify_vq(struct kvm *kvm, void *dev, u32 vq) { return 0; } static struct virt_queue *get_vq(struct kvm *kvm, void *dev, u32 vq) { struct scsi_dev *sdev = dev; return &sdev->vqs[vq]; } static int get_size_vq(struct kvm *kvm, void *dev, u32 vq) { return VIRTIO_SCSI_QUEUE_SIZE; } static int set_size_vq(struct kvm *kvm, void *dev, u32 vq, int size) { return size; } static int get_vq_count(struct kvm *kvm, void *dev) { return NUM_VIRT_QUEUES; } static struct virtio_ops scsi_dev_virtio_ops = { .get_config = get_config, .get_host_features = get_host_features, .set_guest_features = set_guest_features, .init_vq = init_vq, .get_vq = get_vq, .get_size_vq = get_size_vq, .set_size_vq = set_size_vq, .notify_status = notify_status, .notify_vq = notify_vq, .notify_vq_gsi = notify_vq_gsi, .notify_vq_eventfd = notify_vq_eventfd, .get_vq_count = get_vq_count, }; static void virtio_scsi_vhost_init(struct kvm *kvm, struct scsi_dev *sdev) { struct vhost_memory *mem; u64 features; int r; sdev->vhost_fd = open("/dev/vhost-scsi", O_RDWR); if (sdev->vhost_fd < 0) die_perror("Failed openning vhost-scsi device"); mem = calloc(1, sizeof(*mem) + sizeof(struct vhost_memory_region)); if (mem == NULL) die("Failed allocating memory for vhost memory map"); mem->nregions = 1; mem->regions[0] = (struct vhost_memory_region) { .guest_phys_addr = 0, .memory_size = kvm->ram_size, .userspace_addr = (unsigned long)kvm->ram_start, }; r = ioctl(sdev->vhost_fd, VHOST_SET_OWNER); if (r != 0) die_perror("VHOST_SET_OWNER failed"); r = ioctl(sdev->vhost_fd, VHOST_GET_FEATURES, &features); if (r != 0) die_perror("VHOST_GET_FEATURES failed"); r = ioctl(sdev->vhost_fd, VHOST_SET_FEATURES, &features); if (r != 0) die_perror("VHOST_SET_FEATURES failed"); r = ioctl(sdev->vhost_fd, VHOST_SET_MEM_TABLE, mem); if (r != 0) die_perror("VHOST_SET_MEM_TABLE failed"); sdev->vdev.use_vhost = true; free(mem); } static int virtio_scsi_init_one(struct kvm *kvm, struct disk_image *disk) { struct scsi_dev *sdev; if (!disk) return -EINVAL; sdev = calloc(1, sizeof(struct scsi_dev)); if (sdev == NULL) return -ENOMEM; *sdev = (struct scsi_dev) { .config = (struct virtio_scsi_config) { .num_queues = NUM_VIRT_QUEUES - 2, .seg_max = VIRTIO_SCSI_CDB_SIZE - 2, .max_sectors = 65535, .cmd_per_lun = 128, .sense_size = VIRTIO_SCSI_SENSE_SIZE, .cdb_size = VIRTIO_SCSI_CDB_SIZE, .max_channel = 0, .max_target = 0, .max_lun = 16383, .event_info_size = sizeof(struct virtio_scsi_event), }, .kvm = kvm, }; strncpy((char *)&sdev->target.vhost_wwpn, disk->wwpn, sizeof(sdev->target.vhost_wwpn)); sdev->target.vhost_tpgt = strtol(disk->tpgt, NULL, 0); virtio_init(kvm, sdev, &sdev->vdev, &scsi_dev_virtio_ops, VIRTIO_DEFAULT_TRANS(kvm), PCI_DEVICE_ID_VIRTIO_SCSI, VIRTIO_ID_SCSI, PCI_CLASS_BLK); list_add_tail(&sdev->list, &sdevs); virtio_scsi_vhost_init(kvm, sdev); if (compat_id == -1) compat_id = virtio_compat_add_message("virtio-scsi", "CONFIG_VIRTIO_SCSI"); return 0; } static int virtio_scsi_exit_one(struct kvm *kvm, struct scsi_dev *sdev) { int r; r = ioctl(sdev->vhost_fd, VHOST_SCSI_CLEAR_ENDPOINT, &sdev->target); if (r != 0) die("VHOST_SCSI_CLEAR_ENDPOINT failed %d", errno); list_del(&sdev->list); free(sdev); return 0; } int virtio_scsi_init(struct kvm *kvm) { int i, r = 0; for (i = 0; i < kvm->nr_disks; i++) { if (!kvm->disks[i]->wwpn) continue; r = virtio_scsi_init_one(kvm, kvm->disks[i]); if (r < 0) goto cleanup; } return 0; cleanup: return virtio_scsi_exit(kvm); } virtio_dev_init(virtio_scsi_init); int virtio_scsi_exit(struct kvm *kvm) { while (!list_empty(&sdevs)) { struct scsi_dev *sdev; sdev = list_first_entry(&sdevs, struct scsi_dev, list); virtio_scsi_exit_one(kvm, sdev); } return 0; } virtio_dev_exit(virtio_scsi_exit);