Implement a simple para-virtualized IOMMU for handling device address
spaces in guests.
Four operations are implemented:
* attach/detach: guest creates an address space, symbolized by a unique
identifier (IOASID), and attaches the device to it.
* map/unmap: guest creates a GVA->GPA mapping in an address space. Devices
attached to this address space can then access the GVA.
Each subsystem can register its own IOMMU, by calling register/unregister.
A unique device-tree phandle is allocated for each IOMMU. The IOMMU
receives commands from the driver through the virtqueue, and has a set of
callbacks for each device, allowing to implement different map/unmap
operations for passed-through and emulated devices. Note that a single
virtual IOMMU per guest would be enough, this multi-instance model is just
here for experimenting and allow different subsystems to offer different
vIOMMU features.
Add a global --viommu parameter to enable the virtual IOMMU.
Signed-off-by: Jean-Philippe Brucker <jean-***@arm.com>
---
Makefile | 1 +
builtin-run.c | 2 +
include/kvm/devices.h | 4 +
include/kvm/iommu.h | 64 +++++
include/kvm/kvm-config.h | 1 +
include/kvm/virtio-iommu.h | 10 +
virtio/iommu.c | 628 +++++++++++++++++++++++++++++++++++++++++++++
virtio/mmio.c | 11 +
8 files changed, 721 insertions(+)
create mode 100644 include/kvm/iommu.h
create mode 100644 include/kvm/virtio-iommu.h
create mode 100644 virtio/iommu.c
diff --git a/Makefile b/Makefile
index 3e21c597..67953870 100644
--- a/Makefile
+++ b/Makefile
@@ -68,6 +68,7 @@ OBJS += virtio/net.o
OBJS += virtio/rng.o
OBJS += virtio/balloon.o
OBJS += virtio/pci.o
+OBJS += virtio/iommu.o
OBJS += disk/blk.o
OBJS += disk/qcow.o
OBJS += disk/raw.o
diff --git a/builtin-run.c b/builtin-run.c
index b4790ebc..7535b531 100644
--- a/builtin-run.c
+++ b/builtin-run.c
@@ -113,6 +113,8 @@ void kvm_run_set_wrapper_sandbox(void)
OPT_BOOLEAN('\0', "sdl", &(cfg)->sdl, "Enable SDL framebuffer"),\
OPT_BOOLEAN('\0', "rng", &(cfg)->virtio_rng, "Enable virtio" \
" Random Number Generator"), \
+ OPT_BOOLEAN('\0', "viommu", &(cfg)->viommu, \
+ "Enable virtio IOMMU"), \
OPT_CALLBACK('\0', "9p", NULL, "dir_to_share,tag_name", \
"Enable virtio 9p to share files between host and" \
" guest", virtio_9p_rootdir_parser, kvm), \
diff --git a/include/kvm/devices.h b/include/kvm/devices.h
index 405f1952..70a00c5b 100644
--- a/include/kvm/devices.h
+++ b/include/kvm/devices.h
@@ -11,11 +11,15 @@ enum device_bus_type {
DEVICE_BUS_MAX,
};
+struct iommu_ops;
+
struct device_header {
enum device_bus_type bus_type;
void *data;
int dev_num;
struct rb_node node;
+ struct iommu_ops *iommu_ops;
+ void *iommu_data;
};
int device__register(struct device_header *dev);
diff --git a/include/kvm/iommu.h b/include/kvm/iommu.h
new file mode 100644
index 00000000..925e1993
--- /dev/null
+++ b/include/kvm/iommu.h
@@ -0,0 +1,64 @@
+#ifndef KVM_IOMMU_H
+#define KVM_IOMMU_H
+
+#include <stdlib.h>
+
+#include "devices.h"
+
+#define IOMMU_PROT_NONE 0x0
+#define IOMMU_PROT_READ 0x1
+#define IOMMU_PROT_WRITE 0x2
+#define IOMMU_PROT_EXEC 0x4
+
+struct iommu_ops {
+ const struct iommu_properties *(*get_properties)(struct device_header *);
+
+ void *(*alloc_address_space)(struct device_header *);
+ void (*free_address_space)(void *);
+
+ int (*attach)(void *, struct device_header *, int flags);
+ int (*detach)(void *, struct device_header *);
+ int (*map)(void *, u64 virt_addr, u64 phys_addr, u64 size, int prot);
+ int (*unmap)(void *, u64 virt_addr, u64 size, int flags);
+};
+
+struct iommu_properties {
+ const char *name;
+ u32 phandle;
+
+ size_t input_addr_size;
+ u64 pgsize_mask;
+};
+
+/*
+ * All devices presented to the system have a device ID, that allows the IOMMU
+ * to identify them. Since multiple buses can share an IOMMU, this device ID
+ * must be unique system-wide. We define it here as:
+ *
+ * (bus_type << 16) + dev_num
+ *
+ * Where dev_num is the device number on the bus as allocated by devices.c
+ *
+ * TODO: enforce this limit, by checking that the device number allocator
+ * doesn't overflow BUS_SIZE.
+ */
+
+#define BUS_SIZE 0x10000
+
+static inline long device_to_iommu_id(struct device_header *dev)
+{
+ return dev->bus_type * BUS_SIZE + dev->dev_num;
+}
+
+#define iommu_id_to_bus(device_id) ((device_id) / BUS_SIZE)
+#define iommu_id_to_devnum(device_id) ((device_id) % BUS_SIZE)
+
+static inline struct device_header *iommu_get_device(u32 device_id)
+{
+ enum device_bus_type bus = iommu_id_to_bus(device_id);
+ u32 dev_num = iommu_id_to_devnum(device_id);
+
+ return device__find_dev(bus, dev_num);
+}
+
+#endif /* KVM_IOMMU_H */
diff --git a/include/kvm/kvm-config.h b/include/kvm/kvm-config.h
index 62dc6a2f..9678065b 100644
--- a/include/kvm/kvm-config.h
+++ b/include/kvm/kvm-config.h
@@ -60,6 +60,7 @@ struct kvm_config {
bool no_dhcp;
bool ioport_debug;
bool mmio_debug;
+ bool viommu;
};
#endif
diff --git a/include/kvm/virtio-iommu.h b/include/kvm/virtio-iommu.h
new file mode 100644
index 00000000..5532c82b
--- /dev/null
+++ b/include/kvm/virtio-iommu.h
@@ -0,0 +1,10 @@
+#ifndef KVM_VIRTIO_IOMMU_H
+#define KVM_VIRTIO_IOMMU_H
+
+#include "virtio.h"
+
+const struct iommu_properties *viommu_get_properties(void *dev);
+void *viommu_register(struct kvm *kvm, struct iommu_properties *props);
+void viommu_unregister(struct kvm *kvm, void *cookie);
+
+#endif
diff --git a/virtio/iommu.c b/virtio/iommu.c
new file mode 100644
index 00000000..c72e7322
--- /dev/null
+++ b/virtio/iommu.c
@@ -0,0 +1,628 @@
+#include <errno.h>
+#include <stdbool.h>
+
+#include <linux/compiler.h>
+
+#include <linux/bitops.h>
+#include <linux/byteorder.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_iommu.h>
+
+#include "kvm/guest_compat.h"
+#include "kvm/iommu.h"
+#include "kvm/threadpool.h"
+#include "kvm/virtio.h"
+#include "kvm/virtio-iommu.h"
+
+/* Max size */
+#define VIOMMU_DEFAULT_QUEUE_SIZE 256
+
+struct viommu_endpoint {
+ struct device_header *dev;
+ struct viommu_ioas *ioas;
+ struct list_head list;
+};
+
+struct viommu_ioas {
+ u32 id;
+
+ struct mutex devices_mutex;
+ struct list_head devices;
+ size_t nr_devices;
+ struct rb_node node;
+
+ struct iommu_ops *ops;
+ void *priv;
+};
+
+struct viommu_dev {
+ struct virtio_device vdev;
+ struct virtio_iommu_config config;
+
+ const struct iommu_properties *properties;
+
+ struct virt_queue vq;
+ size_t queue_size;
+ struct thread_pool__job job;
+
+ struct rb_root address_spaces;
+ struct kvm *kvm;
+};
+
+static int compat_id = -1;
+
+static struct viommu_ioas *viommu_find_ioas(struct viommu_dev *viommu,
+ u32 ioasid)
+{
+ struct rb_node *node;
+ struct viommu_ioas *ioas;
+
+ node = viommu->address_spaces.rb_node;
+ while (node) {
+ ioas = container_of(node, struct viommu_ioas, node);
+ if (ioas->id > ioasid)
+ node = node->rb_left;
+ else if (ioas->id < ioasid)
+ node = node->rb_right;
+ else
+ return ioas;
+ }
+
+ return NULL;
+}
+
+static struct viommu_ioas *viommu_alloc_ioas(struct viommu_dev *viommu,
+ struct device_header *device,
+ u32 ioasid)
+{
+ struct rb_node **node, *parent = NULL;
+ struct viommu_ioas *new_ioas, *ioas;
+ struct iommu_ops *ops = device->iommu_ops;
+
+ if (!ops || !ops->get_properties || !ops->alloc_address_space ||
+ !ops->free_address_space || !ops->attach || !ops->detach ||
+ !ops->map || !ops->unmap) {
+ /* Catch programming mistakes early */
+ pr_err("Invalid IOMMU ops");
+ return NULL;
+ }
+
+ new_ioas = calloc(1, sizeof(*new_ioas));
+ if (!new_ioas)
+ return NULL;
+
+ INIT_LIST_HEAD(&new_ioas->devices);
+ mutex_init(&new_ioas->devices_mutex);
+ new_ioas->id = ioasid;
+ new_ioas->ops = ops;
+ new_ioas->priv = ops->alloc_address_space(device);
+
+ /* A NULL priv pointer is valid. */
+
+ node = &viommu->address_spaces.rb_node;
+ while (*node) {
+ ioas = container_of(*node, struct viommu_ioas, node);
+ parent = *node;
+
+ if (ioas->id > ioasid) {
+ node = &((*node)->rb_left);
+ } else if (ioas->id < ioasid) {
+ node = &((*node)->rb_right);
+ } else {
+ pr_err("IOAS exists!");
+ free(new_ioas);
+ return NULL;
+ }
+ }
+
+ rb_link_node(&new_ioas->node, parent, node);
+ rb_insert_color(&new_ioas->node, &viommu->address_spaces);
+
+ return new_ioas;
+}
+
+static void viommu_free_ioas(struct viommu_dev *viommu,
+ struct viommu_ioas *ioas)
+{
+ if (ioas->priv)
+ ioas->ops->free_address_space(ioas->priv);
+
+ rb_erase(&ioas->node, &viommu->address_spaces);
+ free(ioas);
+}
+
+static int viommu_ioas_add_device(struct viommu_ioas *ioas,
+ struct viommu_endpoint *vdev)
+{
+ mutex_lock(&ioas->devices_mutex);
+ list_add_tail(&vdev->list, &ioas->devices);
+ ioas->nr_devices++;
+ vdev->ioas = ioas;
+ mutex_unlock(&ioas->devices_mutex);
+
+ return 0;
+}
+
+static int viommu_ioas_del_device(struct viommu_ioas *ioas,
+ struct viommu_endpoint *vdev)
+{
+ mutex_lock(&ioas->devices_mutex);
+ list_del(&vdev->list);
+ ioas->nr_devices--;
+ vdev->ioas = NULL;
+ mutex_unlock(&ioas->devices_mutex);
+
+ return 0;
+}
+
+static struct viommu_endpoint *viommu_alloc_device(struct device_header *device)
+{
+ struct viommu_endpoint *vdev = calloc(1, sizeof(*vdev));
+
+ device->iommu_data = vdev;
+ vdev->dev = device;
+
+ return vdev;
+}
+
+static int viommu_detach_device(struct viommu_dev *viommu,
+ struct viommu_endpoint *vdev)
+{
+ int ret;
+ struct viommu_ioas *ioas = vdev->ioas;
+ struct device_header *device = vdev->dev;
+
+ if (!ioas)
+ return -EINVAL;
+
+ pr_debug("detaching device %#lx from IOAS %u",
+ device_to_iommu_id(device), ioas->id);
+
+ ret = device->iommu_ops->detach(ioas->priv, device);
+ if (!ret)
+ ret = viommu_ioas_del_device(ioas, vdev);
+
+ if (!ioas->nr_devices)
+ viommu_free_ioas(viommu, ioas);
+
+ return ret;
+}
+
+static int viommu_handle_attach(struct viommu_dev *viommu,
+ struct virtio_iommu_req_attach *attach)
+{
+ int ret;
+ struct viommu_ioas *ioas;
+ struct device_header *device;
+ struct viommu_endpoint *vdev;
+
+ u32 device_id = le32_to_cpu(attach->device);
+ u32 ioasid = le32_to_cpu(attach->address_space);
+
+ device = iommu_get_device(device_id);
+ if (IS_ERR_OR_NULL(device)) {
+ pr_err("could not find device %#x", device_id);
+ return -ENODEV;
+ }
+
+ pr_debug("attaching device %#x to IOAS %u", device_id, ioasid);
+
+ vdev = device->iommu_data;
+ if (!vdev) {
+ vdev = viommu_alloc_device(device);
+ if (!vdev)
+ return -ENOMEM;
+ }
+
+ ioas = viommu_find_ioas(viommu, ioasid);
+ if (!ioas) {
+ ioas = viommu_alloc_ioas(viommu, device, ioasid);
+ if (!ioas)
+ return -ENOMEM;
+ } else if (ioas->ops->map != device->iommu_ops->map ||
+ ioas->ops->unmap != device->iommu_ops->unmap) {
+ return -EINVAL;
+ }
+
+ if (vdev->ioas) {
+ ret = viommu_detach_device(viommu, vdev);
+ if (ret)
+ return ret;
+ }
+
+ ret = device->iommu_ops->attach(ioas->priv, device, 0);
+ if (!ret)
+ ret = viommu_ioas_add_device(ioas, vdev);
+
+ if (ret && ioas->nr_devices == 0)
+ viommu_free_ioas(viommu, ioas);
+
+ return ret;
+}
+
+static int viommu_handle_detach(struct viommu_dev *viommu,
+ struct virtio_iommu_req_detach *detach)
+{
+ struct device_header *device;
+ struct viommu_endpoint *vdev;
+
+ u32 device_id = le32_to_cpu(detach->device);
+
+ device = iommu_get_device(device_id);
+ if (IS_ERR_OR_NULL(device)) {
+ pr_err("could not find device %#x", device_id);
+ return -ENODEV;
+ }
+
+ vdev = device->iommu_data;
+ if (!vdev)
+ return -ENODEV;
+
+ return viommu_detach_device(viommu, vdev);
+}
+
+static int viommu_handle_map(struct viommu_dev *viommu,
+ struct virtio_iommu_req_map *map)
+{
+ int prot = 0;
+ struct viommu_ioas *ioas;
+
+ u32 ioasid = le32_to_cpu(map->address_space);
+ u64 virt_addr = le64_to_cpu(map->virt_addr);
+ u64 phys_addr = le64_to_cpu(map->phys_addr);
+ u64 size = le64_to_cpu(map->size);
+ u32 flags = le64_to_cpu(map->flags);
+
+ ioas = viommu_find_ioas(viommu, ioasid);
+ if (!ioas) {
+ pr_err("could not find address space %u", ioasid);
+ return -ESRCH;
+ }
+
+ if (flags & ~VIRTIO_IOMMU_MAP_F_MASK)
+ return -EINVAL;
+
+ if (flags & VIRTIO_IOMMU_MAP_F_READ)
+ prot |= IOMMU_PROT_READ;
+
+ if (flags & VIRTIO_IOMMU_MAP_F_WRITE)
+ prot |= IOMMU_PROT_WRITE;
+
+ if (flags & VIRTIO_IOMMU_MAP_F_EXEC)
+ prot |= IOMMU_PROT_EXEC;
+
+ pr_debug("map %#llx -> %#llx (%llu) to IOAS %u", virt_addr,
+ phys_addr, size, ioasid);
+
+ return ioas->ops->map(ioas->priv, virt_addr, phys_addr, size, prot);
+}
+
+static int viommu_handle_unmap(struct viommu_dev *viommu,
+ struct virtio_iommu_req_unmap *unmap)
+{
+ struct viommu_ioas *ioas;
+
+ u32 ioasid = le32_to_cpu(unmap->address_space);
+ u64 virt_addr = le64_to_cpu(unmap->virt_addr);
+ u64 size = le64_to_cpu(unmap->size);
+
+ ioas = viommu_find_ioas(viommu, ioasid);
+ if (!ioas) {
+ pr_err("could not find address space %u", ioasid);
+ return -ESRCH;
+ }
+
+ pr_debug("unmap %#llx (%llu) from IOAS %u", virt_addr, size,
+ ioasid);
+
+ return ioas->ops->unmap(ioas->priv, virt_addr, size, 0);
+}
+
+static size_t viommu_get_req_len(union virtio_iommu_req *req)
+{
+ switch (req->head.type) {
+ case VIRTIO_IOMMU_T_ATTACH:
+ return sizeof(req->attach);
+ case VIRTIO_IOMMU_T_DETACH:
+ return sizeof(req->detach);
+ case VIRTIO_IOMMU_T_MAP:
+ return sizeof(req->map);
+ case VIRTIO_IOMMU_T_UNMAP:
+ return sizeof(req->unmap);
+ default:
+ pr_err("unknown request type %x", req->head.type);
+ return 0;
+ }
+}
+
+static int viommu_errno_to_status(int err)
+{
+ switch (err) {
+ case 0:
+ return VIRTIO_IOMMU_S_OK;
+ case EIO:
+ return VIRTIO_IOMMU_S_IOERR;
+ case ENOSYS:
+ return VIRTIO_IOMMU_S_UNSUPP;
+ case ERANGE:
+ return VIRTIO_IOMMU_S_RANGE;
+ case EFAULT:
+ return VIRTIO_IOMMU_S_FAULT;
+ case EINVAL:
+ return VIRTIO_IOMMU_S_INVAL;
+ case ENOENT:
+ case ENODEV:
+ case ESRCH:
+ return VIRTIO_IOMMU_S_NOENT;
+ case ENOMEM:
+ case ENOSPC:
+ default:
+ return VIRTIO_IOMMU_S_DEVERR;
+ }
+}
+
+static ssize_t viommu_dispatch_commands(struct viommu_dev *viommu,
+ struct iovec *iov, int nr_in, int nr_out)
+{
+ u32 op;
+ int i, ret;
+ ssize_t written_len = 0;
+ size_t len, expected_len;
+ union virtio_iommu_req *req;
+ struct virtio_iommu_req_tail *tail;
+
+ /*
+ * Are we picking up in the middle of a request buffer? Keep a running
+ * count.
+ *
+ * Here we assume that a request is always made of two descriptors, a
+ * head and a tail. TODO: get rid of framing assumptions by keeping
+ * track of request fragments.
+ */
+ static bool is_head = true;
+ static int cur_status = 0;
+
+ for (i = 0; i < nr_in + nr_out; i++, is_head = !is_head) {
+ len = iov[i].iov_len;
+ if (is_head && len < sizeof(req->head)) {
+ pr_err("invalid command length (%zu)", len);
+ cur_status = EIO;
+ continue;
+ } else if (!is_head && len < sizeof(*tail)) {
+ pr_err("invalid tail length (%zu)", len);
+ cur_status = 0;
+ continue;
+ }
+
+ if (!is_head) {
+ int status = viommu_errno_to_status(cur_status);
+
+ tail = iov[i].iov_base;
+ tail->status = cpu_to_le32(status);
+ written_len += sizeof(tail->status);
+ cur_status = 0;
+ continue;
+ }
+
+ req = iov[i].iov_base;
+ op = req->head.type;
+ expected_len = viommu_get_req_len(req) - sizeof(*tail);
+ if (expected_len != len) {
+ pr_err("invalid command %x length (%zu != %zu)", op,
+ len, expected_len);
+ cur_status = EIO;
+ continue;
+ }
+
+ switch (op) {
+ case VIRTIO_IOMMU_T_ATTACH:
+ ret = viommu_handle_attach(viommu, &req->attach);
+ break;
+
+ case VIRTIO_IOMMU_T_DETACH:
+ ret = viommu_handle_detach(viommu, &req->detach);
+ break;
+
+ case VIRTIO_IOMMU_T_MAP:
+ ret = viommu_handle_map(viommu, &req->map);
+ break;
+
+ case VIRTIO_IOMMU_T_UNMAP:
+ ret = viommu_handle_unmap(viommu, &req->unmap);
+ break;
+
+ default:
+ pr_err("unhandled command %x", op);
+ ret = -ENOSYS;
+ }
+
+ if (ret)
+ cur_status = -ret;
+ }
+
+ return written_len;
+}
+
+static void viommu_command(struct kvm *kvm, void *dev)
+{
+ int len;
+ u16 head;
+ u16 out, in;
+
+ struct virt_queue *vq;
+ struct viommu_dev *viommu = dev;
+ struct iovec iov[VIOMMU_DEFAULT_QUEUE_SIZE];
+
+ vq = &viommu->vq;
+
+ while (virt_queue__available(vq)) {
+ head = virt_queue__get_iov(vq, iov, &out, &in, kvm);
+
+ len = viommu_dispatch_commands(viommu, iov, in, out);
+ if (len < 0) {
+ /* Critical error, abort everything */
+ pr_err("failed to dispatch viommu command");
+ return;
+ }
+
+ virt_queue__set_used_elem(vq, head, len);
+ }
+
+ if (virtio_queue__should_signal(vq))
+ viommu->vdev.ops->signal_vq(kvm, &viommu->vdev, 0);
+}
+
+/* Virtio API */
+static u8 *viommu_get_config(struct kvm *kvm, void *dev)
+{
+ struct viommu_dev *viommu = dev;
+
+ return (u8 *)&viommu->config;
+}
+
+static u32 viommu_get_host_features(struct kvm *kvm, void *dev)
+{
+ return 1ULL << VIRTIO_RING_F_EVENT_IDX
+ | 1ULL << VIRTIO_RING_F_INDIRECT_DESC
+ | 1ULL << VIRTIO_IOMMU_F_INPUT_RANGE;
+}
+
+static void viommu_set_guest_features(struct kvm *kvm, void *dev, u32 features)
+{
+}
+
+static int viommu_init_vq(struct kvm *kvm, void *dev, u32 vq, u32 page_size,
+ u32 align, u32 pfn)
+{
+ void *ptr;
+ struct virt_queue *queue;
+ struct viommu_dev *viommu = dev;
+
+ if (vq != 0)
+ return -ENODEV;
+
+ compat__remove_message(compat_id);
+
+ queue = &viommu->vq;
+ queue->pfn = pfn;
+ ptr = virtio_get_vq(kvm, queue->pfn, page_size);
+
+ vring_init(&queue->vring, viommu->queue_size, ptr, align);
+ virtio_init_device_vq(&viommu->vdev, queue);
+
+ thread_pool__init_job(&viommu->job, kvm, viommu_command, viommu);
+
+ return 0;
+}
+
+static int viommu_get_pfn_vq(struct kvm *kvm, void *dev, u32 vq)
+{
+ struct viommu_dev *viommu = dev;
+
+ return viommu->vq.pfn;
+}
+
+static int viommu_get_size_vq(struct kvm *kvm, void *dev, u32 vq)
+{
+ struct viommu_dev *viommu = dev;
+
+ return viommu->queue_size;
+}
+
+static int viommu_set_size_vq(struct kvm *kvm, void *dev, u32 vq, int size)
+{
+ struct viommu_dev *viommu = dev;
+
+ if (viommu->vq.pfn)
+ /* Already init, can't resize */
+ return viommu->queue_size;
+
+ viommu->queue_size = size;
+
+ return size;
+}
+
+static int viommu_notify_vq(struct kvm *kvm, void *dev, u32 vq)
+{
+ struct viommu_dev *viommu = dev;
+
+ thread_pool__do_job(&viommu->job);
+
+ return 0;
+}
+
+static void viommu_notify_vq_gsi(struct kvm *kvm, void *dev, u32 vq, u32 gsi)
+{
+ /* TODO: when implementing vhost */
+}
+
+static void viommu_notify_vq_eventfd(struct kvm *kvm, void *dev, u32 vq, u32 fd)
+{
+ /* TODO: when implementing vhost */
+}
+
+static struct virtio_ops iommu_dev_virtio_ops = {
+ .get_config = viommu_get_config,
+ .get_host_features = viommu_get_host_features,
+ .set_guest_features = viommu_set_guest_features,
+ .init_vq = viommu_init_vq,
+ .get_pfn_vq = viommu_get_pfn_vq,
+ .get_size_vq = viommu_get_size_vq,
+ .set_size_vq = viommu_set_size_vq,
+ .notify_vq = viommu_notify_vq,
+ .notify_vq_gsi = viommu_notify_vq_gsi,
+ .notify_vq_eventfd = viommu_notify_vq_eventfd,
+};
+
+const struct iommu_properties *viommu_get_properties(void *dev)
+{
+ struct viommu_dev *viommu = dev;
+
+ return viommu->properties;
+}
+
+void *viommu_register(struct kvm *kvm, struct iommu_properties *props)
+{
+ struct viommu_dev *viommu;
+ u64 pgsize_mask = ~(PAGE_SIZE - 1);
+
+ if (!kvm->cfg.viommu)
+ return NULL;
+
+ props->phandle = fdt_alloc_phandle();
+
+ viommu = calloc(1, sizeof(struct viommu_dev));
+ if (!viommu)
+ return NULL;
+
+ viommu->queue_size = VIOMMU_DEFAULT_QUEUE_SIZE;
+ viommu->address_spaces = (struct rb_root)RB_ROOT;
+ viommu->properties = props;
+
+ viommu->config.page_sizes = props->pgsize_mask ?: pgsize_mask;
+ viommu->config.input_range.end = props->input_addr_size % BITS_PER_LONG ?
+ (1UL << props->input_addr_size) - 1 :
+ -1UL;
+
+ if (virtio_init(kvm, viommu, &viommu->vdev, &iommu_dev_virtio_ops,
+ VIRTIO_MMIO, 0, VIRTIO_ID_IOMMU, 0)) {
+ free(viommu);
+ return NULL;
+ }
+
+ pr_info("Loaded virtual IOMMU %s", props->name);
+
+ if (compat_id == -1)
+ compat_id = virtio_compat_add_message("virtio-iommu",
+ "CONFIG_VIRTIO_IOMMU");
+
+ return viommu;
+}
+
+void viommu_unregister(struct kvm *kvm, void *viommu)
+{
+ free(viommu);
+}
diff --git a/virtio/mmio.c b/virtio/mmio.c
index f0af4bd1..b3dea51a 100644
--- a/virtio/mmio.c
+++ b/virtio/mmio.c
@@ -1,14 +1,17 @@
#include "kvm/devices.h"
#include "kvm/virtio-mmio.h"
#include "kvm/ioeventfd.h"
+#include "kvm/iommu.h"
#include "kvm/ioport.h"
#include "kvm/virtio.h"
+#include "kvm/virtio-iommu.h"
#include "kvm/kvm.h"
#include "kvm/kvm-cpu.h"
#include "kvm/irq.h"
#include "kvm/fdt.h"
#include <linux/virtio_mmio.h>
+#include <linux/virtio_ids.h>
#include <string.h>
static u32 virtio_mmio_io_space_blocks = KVM_VIRTIO_MMIO_AREA;
@@ -237,6 +240,7 @@ void generate_virtio_mmio_fdt_node(void *fdt,
u8 irq,
enum irq_type))
{
+ const struct iommu_properties *props;
char dev_name[DEVICE_NAME_MAX_LEN];
struct virtio_mmio *vmmio = container_of(dev_hdr,
struct virtio_mmio,
@@ -254,6 +258,13 @@ void generate_virtio_mmio_fdt_node(void *fdt,
_FDT(fdt_property(fdt, "reg", reg_prop, sizeof(reg_prop)));
_FDT(fdt_property(fdt, "dma-coherent", NULL, 0));
generate_irq_prop(fdt, vmmio->irq, IRQ_TYPE_EDGE_RISING);
+
+ if (vmmio->hdr.device_id == VIRTIO_ID_IOMMU) {
+ props = viommu_get_properties(vmmio->dev);
+ _FDT(fdt_property_cell(fdt, "phandle", props->phandle));
+ _FDT(fdt_property_cell(fdt, "#iommu-cells", 1));
+ }
+
_FDT(fdt_end_node(fdt));
}
#else
--
2.12.1