| #include "kvm/virtio-vsock.h" |
| #include "kvm/virtio-pci-dev.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_vsock.h> |
| #include <linux/vhost.h> |
| |
| #define VIRTIO_VSOCK_QUEUE_SIZE 128 |
| |
| static LIST_HEAD(vdevs); |
| static int compat_id = -1; |
| |
| enum { |
| VSOCK_VQ_RX = 0, /* for host to guest data */ |
| VSOCK_VQ_TX = 1, /* for guest to host data */ |
| VSOCK_VQ_EVENT = 2, |
| VSOCK_VQ_MAX = 3, |
| }; |
| |
| struct vsock_dev { |
| struct virt_queue vqs[VSOCK_VQ_MAX]; |
| struct virtio_vsock_config config; |
| u32 features; |
| int vhost_fd; |
| struct virtio_device vdev; |
| struct list_head list; |
| struct kvm *kvm; |
| bool started; |
| }; |
| |
| static u8 *get_config(struct kvm *kvm, void *dev) |
| { |
| struct vsock_dev *vdev = dev; |
| |
| return ((u8 *)(&vdev->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 vsock_dev *vdev = dev; |
| |
| vdev->features = features; |
| } |
| |
| static bool is_event_vq(u32 vq) |
| { |
| return vq == VSOCK_VQ_EVENT; |
| } |
| |
| 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 vsock_dev *vdev = dev; |
| struct virt_queue *queue; |
| void *p; |
| int r; |
| |
| compat__remove_message(compat_id); |
| |
| queue = &vdev->vqs[vq]; |
| queue->pfn = pfn; |
| p = virtio_get_vq(kvm, queue->pfn, page_size); |
| |
| vring_init(&queue->vring, VIRTIO_VSOCK_QUEUE_SIZE, p, align); |
| virtio_init_device_vq(&vdev->vdev, queue); |
| |
| if (vdev->vhost_fd == -1) |
| return 0; |
| |
| if (is_event_vq(vq)) |
| return 0; |
| |
| state.num = queue->vring.num; |
| r = ioctl(vdev->vhost_fd, VHOST_SET_VRING_NUM, &state); |
| if (r < 0) |
| die_perror("VHOST_SET_VRING_NUM failed"); |
| |
| state.num = 0; |
| r = ioctl(vdev->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(vdev->vhost_fd, VHOST_SET_VRING_ADDR, &addr); |
| if (r < 0) |
| die_perror("VHOST_SET_VRING_ADDR failed"); |
| |
| return 0; |
| } |
| |
| static void notify_vq_eventfd(struct kvm *kvm, void *dev, u32 vq, u32 efd) |
| { |
| struct vsock_dev *vdev = dev; |
| struct vhost_vring_file file = { |
| .index = vq, |
| .fd = efd, |
| }; |
| int r; |
| |
| if (is_event_vq(vq)) |
| return; |
| |
| if (vdev->vhost_fd == -1) |
| return; |
| |
| r = ioctl(vdev->vhost_fd, VHOST_SET_VRING_KICK, &file); |
| if (r < 0) |
| die_perror("VHOST_SET_VRING_KICK failed"); |
| } |
| |
| static void notify_status(struct kvm *kvm, void *dev, u32 status) |
| { |
| struct vsock_dev *vdev = dev; |
| int r, start; |
| |
| start = !!(status & VIRTIO_CONFIG_S_DRIVER_OK); |
| if (vdev->started == start) |
| return; |
| |
| r = ioctl(vdev->vhost_fd, VHOST_VSOCK_SET_RUNNING, &start); |
| if (r != 0) |
| die("VHOST_VSOCK_SET_RUNNING failed %d", errno); |
| |
| vdev->started = start; |
| } |
| |
| 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 vsock_dev *vdev = dev; |
| |
| return &vdev->vqs[vq]; |
| } |
| |
| static int get_size_vq(struct kvm *kvm, void *dev, u32 vq) |
| { |
| return VIRTIO_VSOCK_QUEUE_SIZE; |
| } |
| |
| static int set_size_vq(struct kvm *kvm, void *dev, u32 vq, int size) |
| { |
| return size; |
| } |
| |
| static void notify_vq_gsi(struct kvm *kvm, void *dev, u32 vq, u32 gsi) |
| { |
| struct vhost_vring_file file; |
| struct vsock_dev *vdev = dev; |
| struct kvm_irqfd irq; |
| int r; |
| |
| if (vdev->vhost_fd == -1) |
| return; |
| |
| if (is_event_vq(vq)) |
| 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(vdev->vhost_fd, VHOST_SET_VRING_CALL, &file); |
| if (r < 0) |
| die_perror("VHOST_SET_VRING_CALL failed"); |
| } |
| |
| static int get_vq_count(struct kvm *kvm, void *dev) |
| { |
| return VSOCK_VQ_MAX; |
| } |
| |
| static struct virtio_ops vsock_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_vq_eventfd = notify_vq_eventfd, |
| .notify_status = notify_status, |
| .notify_vq_gsi = notify_vq_gsi, |
| .notify_vq = notify_vq, |
| .get_vq_count = get_vq_count, |
| }; |
| |
| static void virtio_vhost_vsock_init(struct kvm *kvm, struct vsock_dev *vdev) |
| { |
| struct kvm_mem_bank *bank; |
| struct vhost_memory *mem; |
| u64 features; |
| int r, i; |
| |
| vdev->vhost_fd = open("/dev/vhost-vsock", O_RDWR); |
| if (vdev->vhost_fd < 0) |
| die_perror("Failed opening vhost-vsock device"); |
| |
| mem = calloc(1, sizeof(*mem) + sizeof(struct vhost_memory_region)); |
| if (mem == NULL) |
| die("Failed allocating memory for vhost memory map"); |
| |
| i = 0; |
| list_for_each_entry(bank, &kvm->mem_banks, list) { |
| mem->regions[i] = (struct vhost_memory_region) { |
| .guest_phys_addr = bank->guest_phys_addr, |
| .memory_size = bank->size, |
| .userspace_addr = (unsigned long)bank->host_addr, |
| }; |
| i++; |
| } |
| mem->nregions = i; |
| |
| r = ioctl(vdev->vhost_fd, VHOST_SET_OWNER); |
| if (r != 0) |
| die_perror("VHOST_SET_OWNER failed"); |
| |
| r = ioctl(vdev->vhost_fd, VHOST_SET_MEM_TABLE, mem); |
| if (r != 0) |
| die_perror("VHOST_SET_MEM_TABLE failed"); |
| |
| r = ioctl(vdev->vhost_fd, VHOST_GET_FEATURES, &features); |
| if (r != 0) |
| die_perror("VHOST_GET_FEATURES failed"); |
| |
| r = ioctl(vdev->vhost_fd, VHOST_SET_FEATURES, &features); |
| if (r != 0) |
| die_perror("VHOST_SET_FEATURES failed"); |
| |
| r = ioctl(vdev->vhost_fd, VHOST_VSOCK_SET_GUEST_CID, &vdev->config.guest_cid); |
| if (r != 0) |
| die_perror("VHOST_VSOCK_SET_GUEST_CID failed"); |
| |
| vdev->vdev.use_vhost = true; |
| |
| free(mem); |
| } |
| |
| static int virtio_vsock_init_one(struct kvm *kvm, u64 guest_cid) |
| { |
| struct vsock_dev *vdev; |
| int r; |
| |
| vdev = calloc(1, sizeof(struct vsock_dev)); |
| if (vdev == NULL) |
| return -ENOMEM; |
| |
| *vdev = (struct vsock_dev) { |
| .config = (struct virtio_vsock_config) { |
| .guest_cid = guest_cid, |
| }, |
| .vhost_fd = -1, |
| .kvm = kvm, |
| }; |
| |
| list_add_tail(&vdev->list, &vdevs); |
| |
| r = virtio_init(kvm, vdev, &vdev->vdev, &vsock_dev_virtio_ops, |
| VIRTIO_DEFAULT_TRANS(kvm), PCI_DEVICE_ID_VIRTIO_VSOCK, |
| VIRTIO_ID_VSOCK, PCI_CLASS_VSOCK); |
| if (r < 0) |
| return r; |
| |
| virtio_vhost_vsock_init(kvm, vdev); |
| |
| if (compat_id == -1) |
| compat_id = virtio_compat_add_message("virtio-vsock", "CONFIG_VIRTIO_VSOCK"); |
| |
| return 0; |
| } |
| |
| static int virtio_vsock_exit_one(struct kvm *kvm, struct vsock_dev *vdev) |
| { |
| list_del(&vdev->list); |
| free(vdev); |
| |
| return 0; |
| } |
| |
| int virtio_vsock_init(struct kvm *kvm) |
| { |
| int r; |
| |
| if (kvm->cfg.vsock_cid == 0) |
| return 0; |
| |
| r = virtio_vsock_init_one(kvm, kvm->cfg.vsock_cid); |
| if (r < 0) |
| goto cleanup; |
| |
| return 0; |
| cleanup: |
| return virtio_vsock_exit(kvm); |
| } |
| virtio_dev_init(virtio_vsock_init); |
| |
| int virtio_vsock_exit(struct kvm *kvm) |
| { |
| while (!list_empty(&vdevs)) { |
| struct vsock_dev *vdev; |
| |
| vdev = list_first_entry(&vdevs, struct vsock_dev, list); |
| virtio_vsock_exit_one(kvm, vdev); |
| } |
| |
| return 0; |
| } |
| virtio_dev_exit(virtio_vsock_exit); |