@@ -5,6 +5,7 @@
* Copyright (c) 2012, Citrix Systems
*/
+#include <xen/dt-overlay.h>
#include <xen/errno.h>
#include <xen/guest_access.h>
#include <xen/hypercall.h>
@@ -176,6 +177,8 @@ long arch_do_domctl(struct xen_domctl *domctl, struct domain *d,
return rc;
}
+ case XEN_DOMCTL_dt_overlay:
+ return dt_overlay_domctl(d, &domctl->u.dt_overlay);
default:
return subarch_do_domctl(domctl, d, u_domctl);
}
@@ -356,24 +356,136 @@ static int overlay_get_nodes_info(const void *fdto, char **nodes_full_path)
return 0;
}
+/* This function should be called with the overlay_lock taken */
+static struct overlay_track *
+find_track_entry_from_tracker(const void *overlay_fdt,
+ uint32_t overlay_fdt_size)
+{
+ struct overlay_track *entry, *temp;
+ bool found_entry = false;
+
+ ASSERT(spin_is_locked(&overlay_lock));
+
+ /*
+ * First check if dtbo is correct i.e. it should one of the dtbo which was
+ * used when dynamically adding the node.
+ * Limitation: Cases with same node names but different property are not
+ * supported currently. We are relying on user to provide the same dtbo
+ * as it was used when adding the nodes.
+ */
+ list_for_each_entry_safe( entry, temp, &overlay_tracker, entry )
+ {
+ if ( memcmp(entry->overlay_fdt, overlay_fdt, overlay_fdt_size) == 0 )
+ {
+ found_entry = true;
+ break;
+ }
+ }
+
+ if ( !found_entry )
+ {
+ printk(XENLOG_ERR "Cannot find any matching tracker with input dtbo."
+ " Operation is supported only for prior added dtbo.\n");
+ return NULL;
+ }
+
+ return entry;
+}
+
+static int remove_irq(unsigned long s, unsigned long e, void *data)
+{
+ struct domain *d = data;
+ int rc = 0;
+
+ /*
+ * IRQ should always have access unless there are duplication of
+ * of irqs in device tree. There are few cases of xen device tree
+ * where there are duplicate interrupts for the same node.
+ */
+ if (!irq_access_permitted(d, s))
+ return 0;
+ /*
+ * TODO: We don't handle shared IRQs for now. So, it is assumed that
+ * the IRQs was not shared with another domain.
+ */
+ rc = irq_deny_access(d, s);
+ if ( rc )
+ {
+ printk(XENLOG_ERR "unable to revoke access for irq %ld\n", s);
+ return rc;
+ }
+
+ rc = release_guest_irq(d, s);
+ if ( rc )
+ {
+ printk(XENLOG_ERR "unable to release irq %ld\n", s);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int remove_all_irqs(struct rangeset *irq_ranges, struct domain *d)
+{
+ return rangeset_report_ranges(irq_ranges, 0, ~0UL, remove_irq, d);
+}
+
+static int remove_iomem(unsigned long s, unsigned long e, void *data)
+{
+ struct domain *d = data;
+ int rc = 0;
+ p2m_type_t t;
+ mfn_t mfn;
+
+ mfn = p2m_lookup(d, _gfn(s), &t);
+ if ( mfn_x(mfn) == 0 || mfn_x(mfn) == ~0UL )
+ return -EINVAL;
+
+ rc = iomem_deny_access(d, s, e);
+ if ( rc )
+ {
+ printk(XENLOG_ERR "Unable to remove %pd access to %#lx - %#lx\n",
+ d, s, e);
+ return rc;
+ }
+
+ rc = unmap_mmio_regions(d, _gfn(s), e - s, _mfn(s));
+ if ( rc )
+ return rc;
+
+ return rc;
+}
+
+static int remove_all_iomems(struct rangeset *iomem_ranges, struct domain *d)
+{
+ return rangeset_report_ranges(iomem_ranges, 0, ~0UL, remove_iomem, d);
+}
+
/* Check if node itself can be removed and remove node from IOMMU. */
-static int remove_node_resources(struct dt_device_node *device_node)
+static int remove_node_resources(struct dt_device_node *device_node,
+ struct domain *d)
{
int rc = 0;
unsigned int len;
domid_t domid;
- domid = dt_device_used_by(device_node);
+ if ( !d )
+ {
+ domid = dt_device_used_by(device_node);
- dt_dprintk("Checking if node %s is used by any domain\n",
- device_node->full_name);
+ dt_dprintk("Checking if node %s is used by any domain\n",
+ device_node->full_name);
- /* Remove the node if only it's assigned to hardware domain or domain io. */
- if ( domid != hardware_domain->domain_id && domid != DOMID_IO )
- {
- printk(XENLOG_ERR "Device %s is being used by domain %u. Removing nodes failed\n",
- device_node->full_name, domid);
- return -EINVAL;
+ /*
+ * We also check if device is assigned to DOMID_IO as when a domain
+ * is destroyed device is assigned to DOMID_IO.
+ */
+ if ( domid != DOMID_IO )
+ {
+ printk(XENLOG_ERR "Device %s is being assigned to %u. Device is assigned to %d\n",
+ device_node->full_name, DOMID_IO, domid);
+ return -EINVAL;
+ }
}
/* Check if iommu property exists. */
@@ -381,9 +493,12 @@ static int remove_node_resources(struct dt_device_node *device_node)
{
if ( dt_device_is_protected(device_node) )
{
- rc = iommu_remove_dt_device(device_node);
- if ( rc < 0 )
- return rc;
+ if ( !list_empty(&device_node->domain_list) )
+ {
+ rc = iommu_deassign_dt_device(d, device_node);
+ if ( rc < 0 )
+ return rc;
+ }
}
}
@@ -392,7 +507,8 @@ static int remove_node_resources(struct dt_device_node *device_node)
/* Remove all descendants from IOMMU. */
static int
-remove_descendant_nodes_resources(const struct dt_device_node *device_node)
+remove_descendant_nodes_resources(const struct dt_device_node *device_node,
+ struct domain *d)
{
int rc = 0;
struct dt_device_node *child_node;
@@ -402,12 +518,12 @@ remove_descendant_nodes_resources(const struct dt_device_node *device_node)
{
if ( child_node->child )
{
- rc = remove_descendant_nodes_resources(child_node);
+ rc = remove_descendant_nodes_resources(child_node, d);
if ( rc )
return rc;
}
- rc = remove_node_resources(child_node);
+ rc = remove_node_resources(child_node, d);
if ( rc )
return rc;
}
@@ -420,8 +536,7 @@ static int remove_nodes(const struct overlay_track *tracker)
{
int rc = 0;
struct dt_device_node *overlay_node;
- unsigned int j;
- struct domain *d = hardware_domain;
+ unsigned int j, len;
for ( j = 0; j < tracker->num_nodes; j++ )
{
@@ -431,18 +546,15 @@ static int remove_nodes(const struct overlay_track *tracker)
write_lock(&dt_host_lock);
- rc = remove_descendant_nodes_resources(overlay_node);
- if ( rc )
+ /* Check if iommu property exists. */
+ if ( dt_get_property(overlay_node, "iommus", &len) )
{
- write_unlock(&dt_host_lock);
- return rc;
- }
-
- rc = remove_node_resources(overlay_node);
- if ( rc )
- {
- write_unlock(&dt_host_lock);
- return rc;
+ if ( dt_device_is_protected(overlay_node) )
+ {
+ rc = iommu_remove_dt_device(overlay_node);
+ if ( rc < 0 )
+ return rc;
+ }
}
dt_dprintk("Removing node: %s\n", overlay_node->full_name);
@@ -457,22 +569,6 @@ static int remove_nodes(const struct overlay_track *tracker)
write_unlock(&dt_host_lock);
}
- /* Remove IRQ access. */
- if ( tracker->irq_ranges )
- {
- rc = rangeset_consume_ranges(tracker->irq_ranges, irq_remove_cb, d);
- if ( rc )
- return rc;
- }
-
- /* Remove mmio access. */
- if ( tracker->iomem_ranges )
- {
- rc = rangeset_consume_ranges(tracker->iomem_ranges, iomem_remove_cb, d);
- if ( rc )
- return rc;
- }
-
return rc;
}
@@ -485,8 +581,7 @@ static long handle_remove_overlay_nodes(const void *overlay_fdt,
uint32_t overlay_fdt_size)
{
int rc;
- struct overlay_track *entry, *temp, *track;
- bool found_entry = false;
+ struct overlay_track *entry;
rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
if ( rc )
@@ -494,31 +589,11 @@ static long handle_remove_overlay_nodes(const void *overlay_fdt,
spin_lock(&overlay_lock);
- /*
- * First check if dtbo is correct i.e. it should one of the dtbo which was
- * used when dynamically adding the node.
- * Limitation: Cases with same node names but different property are not
- * supported currently. We are relying on user to provide the same dtbo
- * as it was used when adding the nodes.
- */
- list_for_each_entry_safe( entry, temp, &overlay_tracker, entry )
- {
- if ( memcmp(entry->overlay_fdt, overlay_fdt, overlay_fdt_size) == 0 )
- {
- track = entry;
- found_entry = true;
- break;
- }
- }
-
- if ( !found_entry )
+ entry = find_track_entry_from_tracker(overlay_fdt, overlay_fdt_size);
+ if ( entry == NULL )
{
rc = -EINVAL;
-
- printk(XENLOG_ERR "Cannot find any matching tracker with input dtbo."
- " Removing nodes is supported only for prior added dtbo.\n");
goto out;
-
}
rc = remove_nodes(entry);
@@ -536,9 +611,6 @@ static long handle_remove_overlay_nodes(const void *overlay_fdt,
xfree(entry->nodes_address);
- rangeset_destroy(entry->irq_ranges);
- rangeset_destroy(entry->iomem_ranges);
-
xfree(entry);
out:
@@ -620,15 +692,7 @@ static long add_nodes(struct overlay_track *tr, char **nodes_full_path)
return -EFAULT;
}
- rc = handle_device(hardware_domain, overlay_node, p2m_mmio_direct_c,
- tr->iomem_ranges,
- tr->irq_ranges);
write_unlock(&dt_host_lock);
- if ( rc )
- {
- printk(XENLOG_ERR "Adding IRQ and IOMMU failed\n");
- return rc;
- }
/* Keep overlay_node address in tracker. */
tr->nodes_address[j] = (unsigned long)overlay_node;
@@ -638,9 +702,7 @@ static long add_nodes(struct overlay_track *tr, char **nodes_full_path)
}
/*
* Adds device tree nodes under target node.
- * We use tr->dt_host_new to unflatten the updated device_tree_flattened. This
- * is done to avoid the removal of device_tree generation, iomem regions mapping
- * to hardware domain done by handle_node().
+ * We use tr->dt_host_new to unflatten the updated device_tree_flattened.
*/
static long handle_add_overlay_nodes(void *overlay_fdt,
uint32_t overlay_fdt_size)
@@ -774,20 +836,6 @@ static long handle_add_overlay_nodes(void *overlay_fdt,
goto err;
}
- tr->irq_ranges = rangeset_new(hardware_domain, "Overlays: Interrupts", 0);
- if (tr->irq_ranges == NULL)
- {
- printk(XENLOG_ERR "Creating IRQ rangeset failed");
- goto err;
- }
-
- tr->iomem_ranges = rangeset_new(hardware_domain, "Overlay: I/O Memory", 0);
- if (tr->iomem_ranges == NULL)
- {
- printk(XENLOG_ERR "Creating IOMMU rangeset failed");
- goto err;
- }
-
rc = add_nodes(tr, nodes_full_path);
if ( rc )
{
@@ -843,14 +891,165 @@ static long handle_add_overlay_nodes(void *overlay_fdt,
xfree(tr->nodes_address);
xfree(tr->fdt);
- rangeset_destroy(tr->irq_ranges);
- rangeset_destroy(tr->iomem_ranges);
-
xfree(tr);
return rc;
}
+static long handle_detach_overlay_nodes(struct domain *d,
+ const void *overlay_fdt,
+ uint32_t overlay_fdt_size)
+{
+ int rc;
+ unsigned int j;
+ struct overlay_track *entry;
+
+ rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
+ if ( rc )
+ return rc;
+
+ spin_lock(&overlay_lock);
+
+ entry = find_track_entry_from_tracker(overlay_fdt, overlay_fdt_size);
+ if ( entry == NULL )
+ {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ for ( j = 0; j < entry->num_nodes; j++ )
+ {
+ struct dt_device_node *overlay_node;
+
+ overlay_node = (struct dt_device_node *)entry->nodes_address[j];
+ if ( overlay_node == NULL )
+ {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ write_lock(&dt_host_lock);
+ rc = remove_descendant_nodes_resources(overlay_node, d);
+ if ( rc )
+ {
+ write_unlock(&dt_host_lock);
+ goto out;
+ }
+
+ rc = remove_node_resources(overlay_node, d);
+ if ( rc )
+ {
+ write_unlock(&dt_host_lock);
+ goto out;
+ }
+ write_unlock(&dt_host_lock);
+
+ rc = remove_all_irqs(entry->irq_ranges, d);
+ if ( rc )
+ goto out;
+
+ rc = remove_all_iomems(entry->iomem_ranges, d);
+ if ( rc )
+ goto out;
+ }
+
+ /* Remove IRQ access. */
+ if ( entry->irq_ranges )
+ {
+ rc = rangeset_consume_ranges(entry->irq_ranges, irq_remove_cb, d);
+ if ( rc )
+ goto out;
+ }
+
+ /* Remove mmio access. */
+ if ( entry->iomem_ranges )
+ {
+ rc = rangeset_consume_ranges(entry->iomem_ranges, iomem_remove_cb, d);
+ if ( rc )
+ goto out;
+ }
+
+ rangeset_destroy(entry->irq_ranges);
+ rangeset_destroy(entry->iomem_ranges);
+
+ out:
+ spin_unlock(&overlay_lock);
+
+ return rc;
+}
+
+static long handle_attach_overlay_nodes(struct domain *d,
+ const void *overlay_fdt,
+ uint32_t overlay_fdt_size)
+{
+ int rc;
+ unsigned int j;
+ struct overlay_track *entry;
+
+ rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
+ if ( rc )
+ return rc;
+
+ spin_lock(&overlay_lock);
+
+ entry = find_track_entry_from_tracker(overlay_fdt, overlay_fdt_size);
+ if ( entry == NULL )
+ {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ entry->irq_ranges = rangeset_new(d, "Overlays: Interrupts", 0);
+ if (entry->irq_ranges == NULL)
+ {
+ rc = -ENOMEM;
+ printk(XENLOG_ERR "Creating IRQ rangeset failed");
+ goto out;
+ }
+
+ entry->iomem_ranges = rangeset_new(d, "Overlay: I/O Memory", 0);
+ if (entry->iomem_ranges == NULL)
+ {
+ rc = -ENOMEM;
+ printk(XENLOG_ERR "Creating IOMMU rangeset failed");
+ goto out;
+ }
+
+ for ( j = 0; j < entry->num_nodes; j++ )
+ {
+ struct dt_device_node *overlay_node;
+
+ overlay_node = (struct dt_device_node *)entry->nodes_address[j];
+ if ( overlay_node == NULL )
+ {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ write_lock(&dt_host_lock);
+ rc = handle_device(d, overlay_node, p2m_mmio_direct_c,
+ entry->iomem_ranges, entry->irq_ranges);
+ write_unlock(&dt_host_lock);
+ if ( rc )
+ {
+ printk(XENLOG_ERR "Adding IRQ and IOMMU failed\n");
+ goto out;
+ }
+ }
+
+ spin_unlock(&overlay_lock);
+
+ return 0;
+
+ out:
+ spin_unlock(&overlay_lock);
+
+ rangeset_destroy(entry->irq_ranges);
+ rangeset_destroy(entry->iomem_ranges);
+
+ return rc;
+}
+
long dt_overlay_sysctl(struct xen_sysctl_dt_overlay *op)
{
long ret;
@@ -890,6 +1089,45 @@ long dt_overlay_sysctl(struct xen_sysctl_dt_overlay *op)
return ret;
}
+long dt_overlay_domctl(struct domain *d, struct xen_domctl_dt_overlay *op)
+{
+ long ret;
+ void *overlay_fdt;
+
+ if ( op->overlay_op != XEN_DOMCTL_DT_OVERLAY_ATTACH &&
+ op->overlay_op != XEN_DOMCTL_DT_OVERLAY_DETACH )
+ return -EOPNOTSUPP;
+
+ if ( op->overlay_fdt_size == 0 || op->overlay_fdt_size > KB(500) )
+ return -EINVAL;
+
+ if ( op->pad[0] || op->pad[1] || op->pad[2] )
+ return -EINVAL;
+
+ overlay_fdt = xmalloc_bytes(op->overlay_fdt_size);
+
+ if ( overlay_fdt == NULL )
+ return -ENOMEM;
+
+ ret = copy_from_guest(overlay_fdt, op->overlay_fdt, op->overlay_fdt_size);
+ if ( ret )
+ {
+ gprintk(XENLOG_ERR, "copy from guest failed\n");
+ xfree(overlay_fdt);
+
+ return -EFAULT;
+ }
+
+ if ( op->overlay_op == XEN_DOMCTL_DT_OVERLAY_DETACH )
+ ret = handle_detach_overlay_nodes(d, overlay_fdt, op->overlay_fdt_size);
+ else
+ ret = handle_attach_overlay_nodes(d, overlay_fdt, op->overlay_fdt_size);
+
+ xfree(overlay_fdt);
+
+ return ret;
+}
+
/*
* Local variables:
* mode: C
@@ -1190,6 +1190,17 @@ struct xen_domctl_vmtrace_op {
typedef struct xen_domctl_vmtrace_op xen_domctl_vmtrace_op_t;
DEFINE_XEN_GUEST_HANDLE(xen_domctl_vmtrace_op_t);
+#if defined(__arm__) || defined(__aarch64__)
+struct xen_domctl_dt_overlay {
+ XEN_GUEST_HANDLE_64(const_void) overlay_fdt; /* IN: overlay fdt. */
+ uint32_t overlay_fdt_size; /* IN: Overlay dtb size. */
+#define XEN_DOMCTL_DT_OVERLAY_ATTACH 1
+#define XEN_DOMCTL_DT_OVERLAY_DETACH 2
+ uint8_t overlay_op; /* IN: Attach or detach. */
+ uint8_t pad[3]; /* IN: Must be zero. */
+};
+#endif
+
struct xen_domctl {
uint32_t cmd;
#define XEN_DOMCTL_createdomain 1
@@ -1277,6 +1288,7 @@ struct xen_domctl {
#define XEN_DOMCTL_vmtrace_op 84
#define XEN_DOMCTL_get_paging_mempool_size 85
#define XEN_DOMCTL_set_paging_mempool_size 86
+#define XEN_DOMCTL_dt_overlay 87
#define XEN_DOMCTL_gdbsx_guestmemio 1000
#define XEN_DOMCTL_gdbsx_pausevcpu 1001
#define XEN_DOMCTL_gdbsx_unpausevcpu 1002
@@ -1339,6 +1351,9 @@ struct xen_domctl {
struct xen_domctl_vuart_op vuart_op;
struct xen_domctl_vmtrace_op vmtrace_op;
struct xen_domctl_paging_mempool paging_mempool;
+#if defined(__arm__) || defined(__aarch64__)
+ struct xen_domctl_dt_overlay dt_overlay;
+#endif
uint8_t pad[128];
} u;
};
@@ -1184,14 +1184,11 @@ typedef struct xen_sysctl_cpu_policy xen_sysctl_cpu_policy_t;
DEFINE_XEN_GUEST_HANDLE(xen_sysctl_cpu_policy_t);
#endif
-#if defined(__arm__) || defined (__aarch64__)
+#if defined(__arm__) || defined(__aarch64__)
/*
* XEN_SYSCTL_dt_overlay
- * Performs addition/removal of device tree nodes under parent node using dtbo.
- * This does in three steps:
- * - Adds/Removes the nodes from dt_host.
- * - Adds/Removes IRQ permission for the nodes.
- * - Adds/Removes MMIO accesses.
+ * Performs addition/removal of device tree nodes under parent node using dtbo
+ * from dt_host.
*/
struct xen_sysctl_dt_overlay {
XEN_GUEST_HANDLE_64(const_void) overlay_fdt; /* IN: overlay fdt. */
@@ -1265,7 +1262,7 @@ struct xen_sysctl {
struct xen_sysctl_cpu_policy cpu_policy;
#endif
-#if defined(__arm__) || defined (__aarch64__)
+#if defined(__arm__) || defined(__aarch64__)
struct xen_sysctl_dt_overlay dt_overlay;
#endif
uint8_t pad[128];
@@ -39,15 +39,22 @@ struct overlay_track {
};
struct xen_sysctl_dt_overlay;
+struct xen_domctl_dt_overlay;
#ifdef CONFIG_OVERLAY_DTB
long dt_overlay_sysctl(struct xen_sysctl_dt_overlay *op);
+long dt_overlay_domctl(struct domain *d, struct xen_domctl_dt_overlay *op);
#else
#include <xen/errno.h>
static inline long dt_overlay_sysctl(struct xen_sysctl_dt_overlay *op)
{
return -EOPNOTSUPP;
}
+static inline long dt_overlay_domctl(struct domain *d,
+ struct xen_domctl_dt_overlay *op)
+{
+ return -EOPNOTSUPP;
+}
#endif
#endif /* __XEN_DT_OVERLAY_H__ */