|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * Copyright (c) 2015, Linaro Limited, Shannon Zhao | 
|  | */ | 
|  |  | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/acpi.h> | 
|  | #include <xen/xen.h> | 
|  | #include <xen/page.h> | 
|  | #include <xen/interface/memory.h> | 
|  | #include <asm/xen/hypervisor.h> | 
|  | #include <asm/xen/hypercall.h> | 
|  |  | 
|  | static int xen_unmap_device_mmio(const struct resource *resources, | 
|  | unsigned int count) | 
|  | { | 
|  | unsigned int i, j, nr; | 
|  | int rc = 0; | 
|  | const struct resource *r; | 
|  | struct xen_remove_from_physmap xrp; | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | r = &resources[i]; | 
|  | nr = DIV_ROUND_UP(resource_size(r), XEN_PAGE_SIZE); | 
|  | if ((resource_type(r) != IORESOURCE_MEM) || (nr == 0)) | 
|  | continue; | 
|  |  | 
|  | for (j = 0; j < nr; j++) { | 
|  | xrp.domid = DOMID_SELF; | 
|  | xrp.gpfn = XEN_PFN_DOWN(r->start) + j; | 
|  | rc = HYPERVISOR_memory_op(XENMEM_remove_from_physmap, | 
|  | &xrp); | 
|  | if (rc) | 
|  | return rc; | 
|  | } | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int xen_map_device_mmio(const struct resource *resources, | 
|  | unsigned int count) | 
|  | { | 
|  | unsigned int i, j, nr; | 
|  | int rc = 0; | 
|  | const struct resource *r; | 
|  | xen_pfn_t *gpfns; | 
|  | xen_ulong_t *idxs; | 
|  | int *errs; | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | struct xen_add_to_physmap_range xatp = { | 
|  | .domid = DOMID_SELF, | 
|  | .space = XENMAPSPACE_dev_mmio | 
|  | }; | 
|  |  | 
|  | r = &resources[i]; | 
|  | nr = DIV_ROUND_UP(resource_size(r), XEN_PAGE_SIZE); | 
|  | if ((resource_type(r) != IORESOURCE_MEM) || (nr == 0)) | 
|  | continue; | 
|  |  | 
|  | gpfns = kcalloc(nr, sizeof(xen_pfn_t), GFP_KERNEL); | 
|  | idxs = kcalloc(nr, sizeof(xen_ulong_t), GFP_KERNEL); | 
|  | errs = kcalloc(nr, sizeof(int), GFP_KERNEL); | 
|  | if (!gpfns || !idxs || !errs) { | 
|  | kfree(gpfns); | 
|  | kfree(idxs); | 
|  | kfree(errs); | 
|  | rc = -ENOMEM; | 
|  | goto unmap; | 
|  | } | 
|  |  | 
|  | for (j = 0; j < nr; j++) { | 
|  | /* | 
|  | * The regions are always mapped 1:1 to DOM0 and this is | 
|  | * fine because the memory map for DOM0 is the same as | 
|  | * the host (except for the RAM). | 
|  | */ | 
|  | gpfns[j] = XEN_PFN_DOWN(r->start) + j; | 
|  | idxs[j] = XEN_PFN_DOWN(r->start) + j; | 
|  | } | 
|  |  | 
|  | xatp.size = nr; | 
|  |  | 
|  | set_xen_guest_handle(xatp.gpfns, gpfns); | 
|  | set_xen_guest_handle(xatp.idxs, idxs); | 
|  | set_xen_guest_handle(xatp.errs, errs); | 
|  |  | 
|  | rc = HYPERVISOR_memory_op(XENMEM_add_to_physmap_range, &xatp); | 
|  | kfree(gpfns); | 
|  | kfree(idxs); | 
|  | kfree(errs); | 
|  | if (rc) | 
|  | goto unmap; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  |  | 
|  | unmap: | 
|  | xen_unmap_device_mmio(resources, i); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int xen_platform_notifier(struct notifier_block *nb, | 
|  | unsigned long action, void *data) | 
|  | { | 
|  | struct platform_device *pdev = to_platform_device(data); | 
|  | int r = 0; | 
|  |  | 
|  | if (pdev->num_resources == 0 || pdev->resource == NULL) | 
|  | return NOTIFY_OK; | 
|  |  | 
|  | switch (action) { | 
|  | case BUS_NOTIFY_ADD_DEVICE: | 
|  | r = xen_map_device_mmio(pdev->resource, pdev->num_resources); | 
|  | break; | 
|  | case BUS_NOTIFY_DEL_DEVICE: | 
|  | r = xen_unmap_device_mmio(pdev->resource, pdev->num_resources); | 
|  | break; | 
|  | default: | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  | if (r) | 
|  | dev_err(&pdev->dev, "Platform: Failed to %s device %s MMIO!\n", | 
|  | action == BUS_NOTIFY_ADD_DEVICE ? "map" : | 
|  | (action == BUS_NOTIFY_DEL_DEVICE ? "unmap" : "?"), | 
|  | pdev->name); | 
|  |  | 
|  | return NOTIFY_OK; | 
|  | } | 
|  |  | 
|  | static struct notifier_block platform_device_nb = { | 
|  | .notifier_call = xen_platform_notifier, | 
|  | }; | 
|  |  | 
|  | static int __init register_xen_platform_notifier(void) | 
|  | { | 
|  | if (!xen_initial_domain() || acpi_disabled) | 
|  | return 0; | 
|  |  | 
|  | return bus_register_notifier(&platform_bus_type, &platform_device_nb); | 
|  | } | 
|  |  | 
|  | arch_initcall(register_xen_platform_notifier); | 
|  |  | 
|  | #ifdef CONFIG_ARM_AMBA | 
|  | #include <linux/amba/bus.h> | 
|  |  | 
|  | static int xen_amba_notifier(struct notifier_block *nb, | 
|  | unsigned long action, void *data) | 
|  | { | 
|  | struct amba_device *adev = to_amba_device(data); | 
|  | int r = 0; | 
|  |  | 
|  | switch (action) { | 
|  | case BUS_NOTIFY_ADD_DEVICE: | 
|  | r = xen_map_device_mmio(&adev->res, 1); | 
|  | break; | 
|  | case BUS_NOTIFY_DEL_DEVICE: | 
|  | r = xen_unmap_device_mmio(&adev->res, 1); | 
|  | break; | 
|  | default: | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  | if (r) | 
|  | dev_err(&adev->dev, "AMBA: Failed to %s device %s MMIO!\n", | 
|  | action == BUS_NOTIFY_ADD_DEVICE ? "map" : | 
|  | (action == BUS_NOTIFY_DEL_DEVICE ? "unmap" : "?"), | 
|  | adev->dev.init_name); | 
|  |  | 
|  | return NOTIFY_OK; | 
|  | } | 
|  |  | 
|  | static struct notifier_block amba_device_nb = { | 
|  | .notifier_call = xen_amba_notifier, | 
|  | }; | 
|  |  | 
|  | static int __init register_xen_amba_notifier(void) | 
|  | { | 
|  | if (!xen_initial_domain() || acpi_disabled) | 
|  | return 0; | 
|  |  | 
|  | return bus_register_notifier(&amba_bustype, &amba_device_nb); | 
|  | } | 
|  |  | 
|  | arch_initcall(register_xen_amba_notifier); | 
|  | #endif |