| /* |
| * Copyright (c) 2015, Linaro Limited, Shannon Zhao |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #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; |
| struct xen_add_to_physmap_range xatp; |
| |
| 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; |
| |
| gpfns = kzalloc(sizeof(xen_pfn_t) * nr, GFP_KERNEL); |
| idxs = kzalloc(sizeof(xen_ulong_t) * nr, GFP_KERNEL); |
| errs = kzalloc(sizeof(int) * nr, 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.domid = DOMID_SELF; |
| xatp.size = nr; |
| xatp.space = XENMAPSPACE_dev_mmio; |
| |
| 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 |