| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/errno.h> |
| #include <linux/gfp.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/memremap.h> |
| #include <linux/slab.h> |
| |
| #include <asm/page.h> |
| |
| #include <xen/balloon.h> |
| #include <xen/page.h> |
| #include <xen/xen.h> |
| |
| static DEFINE_MUTEX(list_lock); |
| static struct page *page_list; |
| static unsigned int list_count; |
| |
| static struct resource *target_resource; |
| |
| /* |
| * If arch is not happy with system "iomem_resource" being used for |
| * the region allocation it can provide it's own view by creating specific |
| * Xen resource with unused regions of guest physical address space provided |
| * by the hypervisor. |
| */ |
| int __weak __init arch_xen_unpopulated_init(struct resource **res) |
| { |
| *res = &iomem_resource; |
| |
| return 0; |
| } |
| |
| static int fill_list(unsigned int nr_pages) |
| { |
| struct dev_pagemap *pgmap; |
| struct resource *res, *tmp_res = NULL; |
| void *vaddr; |
| unsigned int i, alloc_pages = round_up(nr_pages, PAGES_PER_SECTION); |
| struct range mhp_range; |
| int ret; |
| |
| res = kzalloc(sizeof(*res), GFP_KERNEL); |
| if (!res) |
| return -ENOMEM; |
| |
| res->name = "Xen scratch"; |
| res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; |
| |
| mhp_range = mhp_get_pluggable_range(true); |
| |
| ret = allocate_resource(target_resource, res, |
| alloc_pages * PAGE_SIZE, mhp_range.start, mhp_range.end, |
| PAGES_PER_SECTION * PAGE_SIZE, NULL, NULL); |
| if (ret < 0) { |
| pr_err("Cannot allocate new IOMEM resource\n"); |
| goto err_resource; |
| } |
| |
| /* |
| * Reserve the region previously allocated from Xen resource to avoid |
| * re-using it by someone else. |
| */ |
| if (target_resource != &iomem_resource) { |
| tmp_res = kzalloc(sizeof(*tmp_res), GFP_KERNEL); |
| if (!tmp_res) { |
| ret = -ENOMEM; |
| goto err_insert; |
| } |
| |
| tmp_res->name = res->name; |
| tmp_res->start = res->start; |
| tmp_res->end = res->end; |
| tmp_res->flags = res->flags; |
| |
| ret = request_resource(&iomem_resource, tmp_res); |
| if (ret < 0) { |
| pr_err("Cannot request resource %pR (%d)\n", tmp_res, ret); |
| kfree(tmp_res); |
| goto err_insert; |
| } |
| } |
| |
| pgmap = kzalloc(sizeof(*pgmap), GFP_KERNEL); |
| if (!pgmap) { |
| ret = -ENOMEM; |
| goto err_pgmap; |
| } |
| |
| pgmap->type = MEMORY_DEVICE_GENERIC; |
| pgmap->range = (struct range) { |
| .start = res->start, |
| .end = res->end, |
| }; |
| pgmap->nr_range = 1; |
| pgmap->owner = res; |
| |
| #ifdef CONFIG_XEN_HAVE_PVMMU |
| /* |
| * memremap will build page tables for the new memory so |
| * the p2m must contain invalid entries so the correct |
| * non-present PTEs will be written. |
| * |
| * If a failure occurs, the original (identity) p2m entries |
| * are not restored since this region is now known not to |
| * conflict with any devices. |
| */ |
| if (!xen_feature(XENFEAT_auto_translated_physmap)) { |
| xen_pfn_t pfn = PFN_DOWN(res->start); |
| |
| for (i = 0; i < alloc_pages; i++) { |
| if (!set_phys_to_machine(pfn + i, INVALID_P2M_ENTRY)) { |
| pr_warn("set_phys_to_machine() failed, no memory added\n"); |
| ret = -ENOMEM; |
| goto err_memremap; |
| } |
| } |
| } |
| #endif |
| |
| vaddr = memremap_pages(pgmap, NUMA_NO_NODE); |
| if (IS_ERR(vaddr)) { |
| pr_err("Cannot remap memory range\n"); |
| ret = PTR_ERR(vaddr); |
| goto err_memremap; |
| } |
| |
| for (i = 0; i < alloc_pages; i++) { |
| struct page *pg = virt_to_page(vaddr + PAGE_SIZE * i); |
| |
| pg->zone_device_data = page_list; |
| page_list = pg; |
| list_count++; |
| } |
| |
| return 0; |
| |
| err_memremap: |
| kfree(pgmap); |
| err_pgmap: |
| if (tmp_res) { |
| release_resource(tmp_res); |
| kfree(tmp_res); |
| } |
| err_insert: |
| release_resource(res); |
| err_resource: |
| kfree(res); |
| return ret; |
| } |
| |
| /** |
| * xen_alloc_unpopulated_pages - alloc unpopulated pages |
| * @nr_pages: Number of pages |
| * @pages: pages returned |
| * @return 0 on success, error otherwise |
| */ |
| int xen_alloc_unpopulated_pages(unsigned int nr_pages, struct page **pages) |
| { |
| unsigned int i; |
| int ret = 0; |
| |
| /* |
| * Fallback to default behavior if we do not have any suitable resource |
| * to allocate required region from and as the result we won't be able to |
| * construct pages. |
| */ |
| if (!target_resource) |
| return xen_alloc_ballooned_pages(nr_pages, pages); |
| |
| mutex_lock(&list_lock); |
| if (list_count < nr_pages) { |
| ret = fill_list(nr_pages - list_count); |
| if (ret) |
| goto out; |
| } |
| |
| for (i = 0; i < nr_pages; i++) { |
| struct page *pg = page_list; |
| |
| BUG_ON(!pg); |
| page_list = pg->zone_device_data; |
| list_count--; |
| pages[i] = pg; |
| |
| #ifdef CONFIG_XEN_HAVE_PVMMU |
| if (!xen_feature(XENFEAT_auto_translated_physmap)) { |
| ret = xen_alloc_p2m_entry(page_to_pfn(pg)); |
| if (ret < 0) { |
| unsigned int j; |
| |
| for (j = 0; j <= i; j++) { |
| pages[j]->zone_device_data = page_list; |
| page_list = pages[j]; |
| list_count++; |
| } |
| goto out; |
| } |
| } |
| #endif |
| } |
| |
| out: |
| mutex_unlock(&list_lock); |
| return ret; |
| } |
| EXPORT_SYMBOL(xen_alloc_unpopulated_pages); |
| |
| /** |
| * xen_free_unpopulated_pages - return unpopulated pages |
| * @nr_pages: Number of pages |
| * @pages: pages to return |
| */ |
| void xen_free_unpopulated_pages(unsigned int nr_pages, struct page **pages) |
| { |
| unsigned int i; |
| |
| if (!target_resource) { |
| xen_free_ballooned_pages(nr_pages, pages); |
| return; |
| } |
| |
| mutex_lock(&list_lock); |
| for (i = 0; i < nr_pages; i++) { |
| pages[i]->zone_device_data = page_list; |
| page_list = pages[i]; |
| list_count++; |
| } |
| mutex_unlock(&list_lock); |
| } |
| EXPORT_SYMBOL(xen_free_unpopulated_pages); |
| |
| static int __init unpopulated_init(void) |
| { |
| int ret; |
| |
| if (!xen_domain()) |
| return -ENODEV; |
| |
| ret = arch_xen_unpopulated_init(&target_resource); |
| if (ret) { |
| pr_err("xen:unpopulated: Cannot initialize target resource\n"); |
| target_resource = NULL; |
| } |
| |
| return ret; |
| } |
| early_initcall(unpopulated_init); |