mirror of
https://github.com/clearlinux/kvmtool.git
synced 2026-06-15 18:05:49 +00:00
15542bab78
Due to our kernel heritage we have code in kvmtool that relies on the (still) implicit -std=gnu89 compiler switch. It turns out that this just affects some structure initialization, where we currently provide a cast to the type, which upsets GCC for anything beyond -std=gnu89 (for instance gnu99 or gnu11). We do need the casts when initializing structures that are not assigned to the same type, so we put it there explicitly. This allows us to compile with all the three GNU standards GCC currently supports: gnu89/90, gnu99 and gnu11. GCC threatens people with moving to gnu11 as the new default standard, so lets fix this better sooner than later. (Compiling without GNU extensions still breaks and I don't bother to fix that without very good reasons.) Signed-off-by: Andre Przywara <andre.przywara@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
312 lines
6.9 KiB
C
312 lines
6.9 KiB
C
#include "kvm/virtio-scsi.h"
|
|
#include "kvm/virtio-pci-dev.h"
|
|
#include "kvm/disk-image.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 <linux/kernel.h>
|
|
#include <linux/virtio_scsi.h>
|
|
#include <linux/vhost.h>
|
|
|
|
#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 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;
|
|
struct kvm_irqfd irq;
|
|
int r;
|
|
|
|
if (sdev->vhost_fd == 0)
|
|
return;
|
|
|
|
irq = (struct kvm_irqfd) {
|
|
.gsi = gsi,
|
|
.fd = eventfd(0, 0),
|
|
};
|
|
file = (struct vhost_vring_file) {
|
|
.index = vq,
|
|
.fd = irq.fd,
|
|
};
|
|
|
|
r = ioctl(kvm->vm_fd, KVM_IRQFD, &irq);
|
|
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 int get_pfn_vq(struct kvm *kvm, void *dev, u32 vq)
|
|
{
|
|
struct scsi_dev *sdev = dev;
|
|
|
|
return sdev->vqs[vq].pfn;
|
|
}
|
|
|
|
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 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_pfn_vq = get_pfn_vq,
|
|
.get_size_vq = get_size_vq,
|
|
.set_size_vq = set_size_vq,
|
|
.notify_vq = notify_vq,
|
|
.notify_vq_gsi = notify_vq_gsi,
|
|
.notify_vq_eventfd = notify_vq_eventfd,
|
|
};
|
|
|
|
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);
|