blob: 5fe1b714e5f4d1dad99812d0f78c1574c5c70893 [file] [log] [blame]
#include "kvm/virtio-vsock.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/kvm.h>
#include <linux/vhost.h>
#include <linux/virtio_config.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include "kvm/guest_compat.h"
#include "kvm/kvm.h"
#include "kvm/virtio.h"
#include "kvm/virtio-pci-dev.h"
#include "kvm/util.h"
#define VIRTIO_VSOCK_QUEUE_SIZE 256
#define VIRTIO_VSOCK_NUM_QUEUES 3
static int compat_id = -1;
static struct vsock_dev *g_vdev = NULL;
struct virtio_vsock_config {
u64 guest_cid;
};
struct vsock_dev {
struct virt_queue vqs[VIRTIO_VSOCK_NUM_QUEUES];
struct virtio_vsock_config config;
u64 features;
int vhost_fd;
u8 status;
struct virtio_device dev;
struct kvm *kvm;
};
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) {
struct vsock_dev *vdev = dev;
return vdev->features;
}
static void set_guest_features(struct kvm *kvm, void *dev, u32 features) {
struct vsock_dev *vdev = dev;
u64 nfeatures = features;
if (ioctl(vdev->vhost_fd, VHOST_SET_FEATURES, &nfeatures) != 0) {
pr_err("Unable to set vhost features for virtio-vsock");
return;
}
vdev->features = features;
}
static void notify_status(struct kvm *kvm, void *dev, u8 status) {
struct vsock_dev *vdev = dev;
if ((vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) == 0 &&
(status & VIRTIO_CONFIG_S_DRIVER_OK) != 0) {
// The driver was just enabled.
int on = 1;
if (ioctl(vdev->vhost_fd, VHOST_VSOCK_SET_RUNNING, &on) != 0)
die_perror("VHOST_VSOCK_SET_RUNNING failed");
}
if ((vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) != 0 &&
(status & VIRTIO_CONFIG_S_DRIVER_OK) == 0) {
// The driver was just disabled.
int off = 0;
if (ioctl(vdev->vhost_fd, VHOST_VSOCK_SET_RUNNING, &off) != 0)
die_perror("VHOST_VSOCK_SET_RUNNING failed");
}
vdev->status = status;
}
static int init_vq(struct kvm *kvm, void *dev, u32 vq, u32 page_size, u32 align,
u32 pfn) {
compat__remove_message(compat_id);
int ret = 0;
void *p = NULL;
struct vsock_dev *vdev = dev;
struct virt_queue *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);
if (vq > 1) {
// TODO(chirantan): Implement the event virtqueue
return 0;
}
struct vhost_vring_state state = {
.index = vq,
.num = queue->vring.num,
};
if (ioctl(vdev->vhost_fd, VHOST_SET_VRING_NUM, &state) != 0) {
ret = -errno;
pr_err("VHOST_SET_VRING_NUM failed for vsock device: %d", ret);
return ret;
}
state.num = 0;
if (ioctl(vdev->vhost_fd, VHOST_SET_VRING_BASE, &state) != 0) {
ret = -errno;
pr_err("VHOST_SET_VRING_BASE failed for vsock device: %d", ret);
return ret;
}
struct vhost_vring_addr addr = {
.index = vq,
.desc_user_addr = (u64)queue->vring.desc,
.avail_user_addr = (u64)queue->vring.avail,
.used_user_addr = (u64)queue->vring.used,
};
if (ioctl(vdev->vhost_fd, VHOST_SET_VRING_ADDR, &addr) != 0) {
ret = -errno;
pr_err("VHOST_SET_VRING_ADDR failed for vsock device: %d", ret);
return ret;
}
return 0;
}
static void notify_vq_gsi(struct kvm *kvm, void *dev, u32 vq, u32 gsi) {
if (vq > 1) {
// TODO(chirantan): Implement the event virtqueue
return;
}
int fd = eventfd(0, 0);
if (fd < 0) {
// No graceful way to exit here.
die_perror("Unable to create eventfd");
}
struct kvm_irqfd irq = {
.gsi = gsi,
.fd = fd,
};
if (ioctl(kvm->vm_fd, KVM_IRQFD, &irq) != 0)
die_perror("KVM_IRQFD failed for vsock device");
struct vhost_vring_file file = {
.index = vq,
.fd = irq.fd,
};
struct vsock_dev *vdev = dev;
if (ioctl(vdev->vhost_fd, VHOST_SET_VRING_CALL, &file) != 0)
die_perror("VHOST_SET_VRING_CALL failed for vsock device");
}
static void notify_vq_eventfd(struct kvm *kvm, void *dev, u32 vq, u32 efd) {
if (vq > 1) {
// TODO(chirantan): Implement the event virtqueue
return;
}
struct vsock_dev *vdev = dev;
struct vhost_vring_file file = {
.index = vq,
.fd = efd,
};
if (ioctl(vdev->vhost_fd, VHOST_SET_VRING_KICK, &file) != 0)
die_perror("VHOST_VRING_SET_KICK failed for vsock device");
}
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 vsock_dev *vdev = dev;
return vdev->vqs[vq].pfn;
}
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)
{
// Unsupported?
return size;
}
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_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,
.notify_status = notify_status,
};
int virtio_vsock_init(struct kvm *kvm) {
int ret = 0;
if (kvm->cfg.guest_cid == 0)
return 0;
if (g_vdev != NULL) {
pr_err("Already initialized virtio vsock once");
return -EINVAL;
}
struct vsock_dev *vdev = malloc(sizeof(struct vsock_dev));
if (vdev == NULL)
return -ENOMEM;
vdev->config = (struct virtio_vsock_config) {
.guest_cid = kvm->cfg.guest_cid,
};
vdev->kvm = kvm;
ret = virtio_init(kvm, vdev, &vdev->dev, &vsock_dev_virtio_ops,
VIRTIO_DEFAULT_TRANS(kvm), PCI_DEVICE_ID_VIRTIO_VSOCK,
VIRTIO_ID_VSOCK, PCI_CLASS_VSOCK);
if (ret < 0)
goto cleanup;
vdev->vhost_fd = open("/dev/vhost-vsock", O_RDWR);
if (vdev->vhost_fd < 0) {
ret = -errno;
pr_err("Unable to open vhost-vsock device: %d", ret);
goto cleanup;
}
if (ioctl(vdev->vhost_fd, VHOST_SET_OWNER) != 0) {
ret = -errno;
pr_err("VHOST_SET_OWNER failed on vhost-vsock device: %d", ret);
goto vhost_cleanup;
}
if (ioctl(vdev->vhost_fd, VHOST_GET_FEATURES, &vdev->features) != 0) {
ret = -errno;
pr_err("VHOST_GET_FEATURES failed on vhost-vsock device: %d", ret);
goto vhost_cleanup;
}
struct vhost_memory *mem = malloc(sizeof(struct vhost_memory) +
sizeof(struct vhost_memory_region));
if (mem == NULL) {
ret = -ENOMEM;
goto vhost_cleanup;
}
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,
};
if (ioctl(vdev->vhost_fd, VHOST_SET_MEM_TABLE, mem) != 0) {
ret = -errno;
pr_err("VHOST_SET_MEM_TABLE on vhost-vsock device failed: %d", ret);
goto vhost_mem_cleanup;
}
free(mem); // sigh... manual memory management
if (ioctl(vdev->vhost_fd, VHOST_VSOCK_SET_GUEST_CID,
&vdev->config.guest_cid) != 0) {
ret = -errno;
pr_err("VHOST_VSOCK_SET_GUEST_CID failed: %d", ret);
goto vhost_cleanup;
}
vdev->dev.use_vhost = true;
if (compat_id == -1) {
compat_id = virtio_compat_add_message("virtio-vsock",
"CONFIG_VIRTIO_VSOCKETS");
}
g_vdev = vdev;
return 0;
vhost_mem_cleanup:
free(mem);
vhost_cleanup:
close(vdev->vhost_fd);
cleanup:
free(vdev);
return ret;
}
virtio_dev_init(virtio_vsock_init);
int virtio_vsock_exit(struct kvm *kvm) {
if (g_vdev == NULL)
return 0;
struct vsock_dev *vdev = g_vdev;
g_vdev = NULL;
close(vdev->vhost_fd);
free(vdev);
return 0;
}
virtio_dev_exit(virtio_vsock_exit);