aboutsummaryrefslogtreecommitdiff
path: root/hw/virtio/virtio-iommu.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/virtio/virtio-iommu.c')
-rw-r--r--hw/virtio/virtio-iommu.c205
1 files changed, 203 insertions, 2 deletions
diff --git a/hw/virtio/virtio-iommu.c b/hw/virtio/virtio-iommu.c
index 21ec63b..fc5c75d 100644
--- a/hw/virtio/virtio-iommu.c
+++ b/hw/virtio/virtio-iommu.c
@@ -49,6 +49,7 @@ typedef struct VirtIOIOMMUDomain {
typedef struct VirtIOIOMMUEndpoint {
uint32_t id;
VirtIOIOMMUDomain *domain;
+ IOMMUMemoryRegion *iommu_mr;
QLIST_ENTRY(VirtIOIOMMUEndpoint) next;
} VirtIOIOMMUEndpoint;
@@ -101,7 +102,7 @@ static IOMMUMemoryRegion *virtio_iommu_mr(VirtIOIOMMU *s, uint32_t sid)
bus_n = PCI_BUS_NUM(sid);
iommu_pci_bus = iommu_find_iommu_pcibus(s, bus_n);
if (iommu_pci_bus) {
- devfn = sid & PCI_DEVFN_MAX;
+ devfn = sid & (PCI_DEVFN_MAX - 1);
dev = iommu_pci_bus->pbdev[devfn];
if (dev) {
return &dev->iommu_mr;
@@ -124,11 +125,84 @@ static gint interval_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
}
}
+static void virtio_iommu_notify_map(IOMMUMemoryRegion *mr, hwaddr virt_start,
+ hwaddr virt_end, hwaddr paddr,
+ uint32_t flags)
+{
+ IOMMUTLBEntry entry;
+ IOMMUAccessFlags perm = IOMMU_ACCESS_FLAG(flags & VIRTIO_IOMMU_MAP_F_READ,
+ flags & VIRTIO_IOMMU_MAP_F_WRITE);
+
+ if (!(mr->iommu_notify_flags & IOMMU_NOTIFIER_MAP) ||
+ (flags & VIRTIO_IOMMU_MAP_F_MMIO) || !perm) {
+ return;
+ }
+
+ trace_virtio_iommu_notify_map(mr->parent_obj.name, virt_start, virt_end,
+ paddr, perm);
+
+ entry.target_as = &address_space_memory;
+ entry.addr_mask = virt_end - virt_start;
+ entry.iova = virt_start;
+ entry.perm = perm;
+ entry.translated_addr = paddr;
+
+ memory_region_notify_iommu(mr, 0, entry);
+}
+
+static void virtio_iommu_notify_unmap(IOMMUMemoryRegion *mr, hwaddr virt_start,
+ hwaddr virt_end)
+{
+ IOMMUTLBEntry entry;
+
+ if (!(mr->iommu_notify_flags & IOMMU_NOTIFIER_UNMAP)) {
+ return;
+ }
+
+ trace_virtio_iommu_notify_unmap(mr->parent_obj.name, virt_start, virt_end);
+
+ entry.target_as = &address_space_memory;
+ entry.addr_mask = virt_end - virt_start;
+ entry.iova = virt_start;
+ entry.perm = IOMMU_NONE;
+ entry.translated_addr = 0;
+
+ memory_region_notify_iommu(mr, 0, entry);
+}
+
+static gboolean virtio_iommu_notify_unmap_cb(gpointer key, gpointer value,
+ gpointer data)
+{
+ VirtIOIOMMUInterval *interval = (VirtIOIOMMUInterval *) key;
+ IOMMUMemoryRegion *mr = (IOMMUMemoryRegion *) data;
+
+ virtio_iommu_notify_unmap(mr, interval->low, interval->high);
+
+ return false;
+}
+
+static gboolean virtio_iommu_notify_map_cb(gpointer key, gpointer value,
+ gpointer data)
+{
+ VirtIOIOMMUMapping *mapping = (VirtIOIOMMUMapping *) value;
+ VirtIOIOMMUInterval *interval = (VirtIOIOMMUInterval *) key;
+ IOMMUMemoryRegion *mr = (IOMMUMemoryRegion *) data;
+
+ virtio_iommu_notify_map(mr, interval->low, interval->high,
+ mapping->phys_addr, mapping->flags);
+
+ return false;
+}
+
static void virtio_iommu_detach_endpoint_from_domain(VirtIOIOMMUEndpoint *ep)
{
+ VirtIOIOMMUDomain *domain = ep->domain;
+
if (!ep->domain) {
return;
}
+ g_tree_foreach(domain->mappings, virtio_iommu_notify_unmap_cb,
+ ep->iommu_mr);
QLIST_REMOVE(ep, next);
ep->domain = NULL;
}
@@ -137,16 +211,19 @@ static VirtIOIOMMUEndpoint *virtio_iommu_get_endpoint(VirtIOIOMMU *s,
uint32_t ep_id)
{
VirtIOIOMMUEndpoint *ep;
+ IOMMUMemoryRegion *mr;
ep = g_tree_lookup(s->endpoints, GUINT_TO_POINTER(ep_id));
if (ep) {
return ep;
}
- if (!virtio_iommu_mr(s, ep_id)) {
+ mr = virtio_iommu_mr(s, ep_id);
+ if (!mr) {
return NULL;
}
ep = g_malloc0(sizeof(*ep));
ep->id = ep_id;
+ ep->iommu_mr = mr;
trace_virtio_iommu_get_endpoint(ep_id);
g_tree_insert(s->endpoints, GUINT_TO_POINTER(ep_id), ep);
return ep;
@@ -268,6 +345,10 @@ static int virtio_iommu_attach(VirtIOIOMMU *s,
ep->domain = domain;
+ /* Replay domain mappings on the associated memory region */
+ g_tree_foreach(domain->mappings, virtio_iommu_notify_map_cb,
+ ep->iommu_mr);
+
return VIRTIO_IOMMU_S_OK;
}
@@ -311,6 +392,7 @@ static int virtio_iommu_map(VirtIOIOMMU *s,
VirtIOIOMMUDomain *domain;
VirtIOIOMMUInterval *interval;
VirtIOIOMMUMapping *mapping;
+ VirtIOIOMMUEndpoint *ep;
if (flags & ~VIRTIO_IOMMU_MAP_F_MASK) {
return VIRTIO_IOMMU_S_INVAL;
@@ -340,6 +422,11 @@ static int virtio_iommu_map(VirtIOIOMMU *s,
g_tree_insert(domain->mappings, interval, mapping);
+ QLIST_FOREACH(ep, &domain->endpoint_list, next) {
+ virtio_iommu_notify_map(ep->iommu_mr, virt_start, virt_end, phys_start,
+ flags);
+ }
+
return VIRTIO_IOMMU_S_OK;
}
@@ -352,6 +439,7 @@ static int virtio_iommu_unmap(VirtIOIOMMU *s,
VirtIOIOMMUMapping *iter_val;
VirtIOIOMMUInterval interval, *iter_key;
VirtIOIOMMUDomain *domain;
+ VirtIOIOMMUEndpoint *ep;
int ret = VIRTIO_IOMMU_S_OK;
trace_virtio_iommu_unmap(domain_id, virt_start, virt_end);
@@ -369,6 +457,10 @@ static int virtio_iommu_unmap(VirtIOIOMMU *s,
uint64_t current_high = iter_key->high;
if (interval.low <= current_low && interval.high >= current_high) {
+ QLIST_FOREACH(ep, &domain->endpoint_list, next) {
+ virtio_iommu_notify_unmap(ep->iommu_mr, current_low,
+ current_high);
+ }
g_tree_remove(domain->mappings, iter_key);
trace_virtio_iommu_unmap_done(domain_id, current_low, current_high);
} else {
@@ -755,6 +847,107 @@ static gint int_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
return (ua > ub) - (ua < ub);
}
+static gboolean virtio_iommu_remap(gpointer key, gpointer value, gpointer data)
+{
+ VirtIOIOMMUMapping *mapping = (VirtIOIOMMUMapping *) value;
+ VirtIOIOMMUInterval *interval = (VirtIOIOMMUInterval *) key;
+ IOMMUMemoryRegion *mr = (IOMMUMemoryRegion *) data;
+
+ trace_virtio_iommu_remap(mr->parent_obj.name, interval->low, interval->high,
+ mapping->phys_addr);
+ virtio_iommu_notify_map(mr, interval->low, interval->high,
+ mapping->phys_addr, mapping->flags);
+ return false;
+}
+
+static void virtio_iommu_replay(IOMMUMemoryRegion *mr, IOMMUNotifier *n)
+{
+ IOMMUDevice *sdev = container_of(mr, IOMMUDevice, iommu_mr);
+ VirtIOIOMMU *s = sdev->viommu;
+ uint32_t sid;
+ VirtIOIOMMUEndpoint *ep;
+
+ sid = virtio_iommu_get_bdf(sdev);
+
+ qemu_mutex_lock(&s->mutex);
+
+ if (!s->endpoints) {
+ goto unlock;
+ }
+
+ ep = g_tree_lookup(s->endpoints, GUINT_TO_POINTER(sid));
+ if (!ep || !ep->domain) {
+ goto unlock;
+ }
+
+ g_tree_foreach(ep->domain->mappings, virtio_iommu_remap, mr);
+
+unlock:
+ qemu_mutex_unlock(&s->mutex);
+}
+
+static int virtio_iommu_notify_flag_changed(IOMMUMemoryRegion *iommu_mr,
+ IOMMUNotifierFlag old,
+ IOMMUNotifierFlag new,
+ Error **errp)
+{
+ if (old == IOMMU_NOTIFIER_NONE) {
+ trace_virtio_iommu_notify_flag_add(iommu_mr->parent_obj.name);
+ } else if (new == IOMMU_NOTIFIER_NONE) {
+ trace_virtio_iommu_notify_flag_del(iommu_mr->parent_obj.name);
+ }
+ return 0;
+}
+
+/*
+ * The default mask (TARGET_PAGE_MASK) is the smallest supported guest granule,
+ * for example 0xfffffffffffff000. When an assigned device has page size
+ * restrictions due to the hardware IOMMU configuration, apply this restriction
+ * to the mask.
+ */
+static int virtio_iommu_set_page_size_mask(IOMMUMemoryRegion *mr,
+ uint64_t new_mask,
+ Error **errp)
+{
+ IOMMUDevice *sdev = container_of(mr, IOMMUDevice, iommu_mr);
+ VirtIOIOMMU *s = sdev->viommu;
+ uint64_t cur_mask = s->config.page_size_mask;
+
+ trace_virtio_iommu_set_page_size_mask(mr->parent_obj.name, cur_mask,
+ new_mask);
+
+ if ((cur_mask & new_mask) == 0) {
+ error_setg(errp, "virtio-iommu page mask 0x%"PRIx64
+ " is incompatible with mask 0x%"PRIx64, cur_mask, new_mask);
+ return -1;
+ }
+
+ /*
+ * After the machine is finalized, we can't change the mask anymore. If by
+ * chance the hotplugged device supports the same granule, we can still
+ * accept it. Having a different masks is possible but the guest will use
+ * sub-optimal block sizes, so warn about it.
+ */
+ if (qdev_hotplug) {
+ int new_granule = ctz64(new_mask);
+ int cur_granule = ctz64(cur_mask);
+
+ if (new_granule != cur_granule) {
+ error_setg(errp, "virtio-iommu page mask 0x%"PRIx64
+ " is incompatible with mask 0x%"PRIx64, cur_mask,
+ new_mask);
+ return -1;
+ } else if (new_mask != cur_mask) {
+ warn_report("virtio-iommu page mask 0x%"PRIx64
+ " does not match 0x%"PRIx64, cur_mask, new_mask);
+ }
+ return 0;
+ }
+
+ s->config.page_size_mask &= new_mask;
+ return 0;
+}
+
static void virtio_iommu_device_realize(DeviceState *dev, Error **errp)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
@@ -910,9 +1103,14 @@ static gboolean reconstruct_endpoints(gpointer key, gpointer value,
VirtIOIOMMU *s = (VirtIOIOMMU *)data;
VirtIOIOMMUDomain *d = (VirtIOIOMMUDomain *)value;
VirtIOIOMMUEndpoint *iter;
+ IOMMUMemoryRegion *mr;
QLIST_FOREACH(iter, &d->endpoint_list, next) {
+ mr = virtio_iommu_mr(s, iter->id);
+ assert(mr);
+
iter->domain = d;
+ iter->iommu_mr = mr;
g_tree_insert(s->endpoints, GUINT_TO_POINTER(iter->id), iter);
}
return false; /* continue the domain traversal */
@@ -979,6 +1177,9 @@ static void virtio_iommu_memory_region_class_init(ObjectClass *klass,
IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
imrc->translate = virtio_iommu_translate;
+ imrc->replay = virtio_iommu_replay;
+ imrc->notify_flag_changed = virtio_iommu_notify_flag_changed;
+ imrc->iommu_set_page_size_mask = virtio_iommu_set_page_size_mask;
}
static const TypeInfo virtio_iommu_info = {