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-philippe.bruc...@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

_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

Reply via email to