mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
xen/arm: Allocate and free P2M pages from the P2M pool
This commit sets/tearsdown of p2m pages pool for non-privileged Arm guests by calling `p2m_set_allocation` and `p2m_teardown_allocation`. - For dom0, P2M pages should come from heap directly instead of p2m pool, so that the kernel may take advantage of the extended regions. - For xl guests, the setting of the p2m pool is called in `XEN_DOMCTL_shadow_op` and the p2m pool is destroyed in `domain_relinquish_resources`. Note that domctl->u.shadow_op.mb is updated with the new size when setting the p2m pool. - For dom0less domUs, the setting of the p2m pool is called before allocating memory during domain creation. Users can specify the p2m pool size by `xen,domain-p2m-mem-mb` dts property. To actually allocate/free pages from the p2m pool, this commit adds two helper functions namely `p2m_alloc_page` and `p2m_free_page` to `struct p2m_domain`. By replacing the `alloc_domheap_page` and `free_domheap_page` with these two helper functions, p2m pages can be added/removed from the list of p2m pool rather than from the heap. Since page from `p2m_alloc_page` is cleaned, take the opportunity to remove the redundant `clean_page` in `p2m_create_table`. This is part of CVE-2022-33747 / XSA-409. Signed-off-by: Henry Wang <Henry.Wang@arm.com> Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
This commit is contained in:
1 parent
cf2a68d2ff
commit
cbea5a1149
5 files changed
+118
-5
No files matched your search
@@ -215,6 +215,14 @@ with the following properties:
|
||||
In the future other possible property values might be added to
|
||||
enable only selected interfaces.
|
||||
|
||||
- xen,domain-p2m-mem-mb
|
||||
|
||||
Optional. A 32-bit integer specifying the amount of megabytes of RAM
|
||||
used for the domain P2M pool. This is in-sync with the shadow_memory
|
||||
option in xl.cfg. Leaving this field empty in device tree will lead to
|
||||
the default size of domain P2M pool, i.e. 1MB per guest vCPU plus 4KB
|
||||
per MB of guest RAM plus 512KB for guest extended regions.
|
||||
|
||||
Under the "xen,domain" compatible node, one or more sub-nodes are present
|
||||
for the DomU kernel and ramdisk.
|
||||
|
||||
|
||||
@@ -1002,6 +1002,7 @@ enum {
|
||||
PROG_page,
|
||||
PROG_mapping,
|
||||
PROG_p2m,
|
||||
PROG_p2m_pool,
|
||||
PROG_done,
|
||||
};
|
||||
|
||||
@@ -1067,6 +1068,11 @@ int domain_relinquish_resources(struct domain *d)
|
||||
if ( ret )
|
||||
return ret;
|
||||
|
||||
PROGRESS(p2m_pool):
|
||||
ret = p2m_teardown_allocation(d);
|
||||
if( ret )
|
||||
return ret;
|
||||
|
||||
PROGRESS(done):
|
||||
break;
|
||||
|
||||
|
||||
@@ -3622,6 +3622,21 @@ static void __init find_gnttab_region(struct domain *d,
|
||||
kinfo->gnttab_start, kinfo->gnttab_start + kinfo->gnttab_size);
|
||||
}
|
||||
|
||||
static unsigned long __init domain_p2m_pages(unsigned long maxmem_kb,
|
||||
unsigned int smp_cpus)
|
||||
{
|
||||
/*
|
||||
* Keep in sync with libxl__get_required_paging_memory().
|
||||
* 256 pages (1MB) per vcpu, plus 1 page per MiB of RAM for the P2M map,
|
||||
* plus 128 pages to cover extended regions.
|
||||
*/
|
||||
unsigned long memkb = 4 * (256 * smp_cpus + (maxmem_kb / 1024) + 128);
|
||||
|
||||
BUILD_BUG_ON(PAGE_SIZE != SZ_4K);
|
||||
|
||||
return DIV_ROUND_UP(memkb, 1024) << (20 - PAGE_SHIFT);
|
||||
}
|
||||
|
||||
static int __init construct_domain(struct domain *d, struct kernel_info *kinfo)
|
||||
{
|
||||
unsigned int i;
|
||||
@@ -3733,6 +3748,8 @@ static int __init construct_domU(struct domain *d,
|
||||
const char *dom0less_enhanced;
|
||||
int rc;
|
||||
u64 mem;
|
||||
u32 p2m_mem_mb;
|
||||
unsigned long p2m_pages;
|
||||
|
||||
rc = dt_property_read_u64(node, "memory", &mem);
|
||||
if ( !rc )
|
||||
@@ -3742,6 +3759,18 @@ static int __init construct_domU(struct domain *d,
|
||||
}
|
||||
kinfo.unassigned_mem = (paddr_t)mem * SZ_1K;
|
||||
|
||||
rc = dt_property_read_u32(node, "xen,domain-p2m-mem-mb", &p2m_mem_mb);
|
||||
/* If xen,domain-p2m-mem-mb is not specified, use the default value. */
|
||||
p2m_pages = rc ?
|
||||
p2m_mem_mb << (20 - PAGE_SHIFT) :
|
||||
domain_p2m_pages(mem, d->max_vcpus);
|
||||
|
||||
spin_lock(&d->arch.paging.lock);
|
||||
rc = p2m_set_allocation(d, p2m_pages, NULL);
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
if ( rc != 0 )
|
||||
return rc;
|
||||
|
||||
printk("*** LOADING DOMU cpus=%u memory=%"PRIx64"KB ***\n", d->max_vcpus, mem);
|
||||
|
||||
kinfo.vpl011 = dt_property_read_bool(node, "vpl011");
|
||||
|
||||
+22
-1
@@ -50,6 +50,9 @@ static int handle_vuart_init(struct domain *d,
|
||||
static long p2m_domctl(struct domain *d, struct xen_domctl_shadow_op *sc,
|
||||
XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
{
|
||||
long rc;
|
||||
bool preempted = false;
|
||||
|
||||
if ( unlikely(d == current->domain) )
|
||||
{
|
||||
printk(XENLOG_ERR "Tried to do a p2m domctl op on itself.\n");
|
||||
@@ -66,9 +69,27 @@ static long p2m_domctl(struct domain *d, struct xen_domctl_shadow_op *sc,
|
||||
switch ( sc->op )
|
||||
{
|
||||
case XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION:
|
||||
return 0;
|
||||
{
|
||||
/* Allow and handle preemption */
|
||||
spin_lock(&d->arch.paging.lock);
|
||||
rc = p2m_set_allocation(d, sc->mb << (20 - PAGE_SHIFT), &preempted);
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
|
||||
if ( preempted )
|
||||
/* Not finished. Set up to re-run the call. */
|
||||
rc = hypercall_create_continuation(__HYPERVISOR_domctl, "h",
|
||||
u_domctl);
|
||||
else
|
||||
/* Finished. Return the new allocation. */
|
||||
sc->mb = p2m_get_allocation(d);
|
||||
|
||||
return rc;
|
||||
}
|
||||
case XEN_DOMCTL_SHADOW_OP_GET_ALLOCATION:
|
||||
{
|
||||
sc->mb = p2m_get_allocation(d);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printk(XENLOG_ERR "Bad p2m domctl op %u\n", sc->op);
|
||||
|
||||
+53
-4
@@ -44,6 +44,54 @@ static uint64_t generate_vttbr(uint16_t vmid, mfn_t root_mfn)
|
||||
return (mfn_to_maddr(root_mfn) | ((uint64_t)vmid << 48));
|
||||
}
|
||||
|
||||
static struct page_info *p2m_alloc_page(struct domain *d)
|
||||
{
|
||||
struct page_info *pg;
|
||||
|
||||
spin_lock(&d->arch.paging.lock);
|
||||
/*
|
||||
* For hardware domain, there should be no limit in the number of pages that
|
||||
* can be allocated, so that the kernel may take advantage of the extended
|
||||
* regions. Hence, allocate p2m pages for hardware domains from heap.
|
||||
*/
|
||||
if ( is_hardware_domain(d) )
|
||||
{
|
||||
pg = alloc_domheap_page(NULL, 0);
|
||||
if ( pg == NULL )
|
||||
{
|
||||
printk(XENLOG_G_ERR "Failed to allocate P2M pages for hwdom.\n");
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pg = page_list_remove_head(&d->arch.paging.p2m_freelist);
|
||||
if ( unlikely(!pg) )
|
||||
{
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
return NULL;
|
||||
}
|
||||
d->arch.paging.p2m_total_pages--;
|
||||
}
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
|
||||
return pg;
|
||||
}
|
||||
|
||||
static void p2m_free_page(struct domain *d, struct page_info *pg)
|
||||
{
|
||||
spin_lock(&d->arch.paging.lock);
|
||||
if ( is_hardware_domain(d) )
|
||||
free_domheap_page(pg);
|
||||
else
|
||||
{
|
||||
d->arch.paging.p2m_total_pages++;
|
||||
page_list_add_tail(pg, &d->arch.paging.p2m_freelist);
|
||||
}
|
||||
spin_unlock(&d->arch.paging.lock);
|
||||
}
|
||||
|
||||
/* Return the size of the pool, rounded up to the nearest MB */
|
||||
unsigned int p2m_get_allocation(struct domain *d)
|
||||
{
|
||||
@@ -747,7 +795,7 @@ static int p2m_create_table(struct p2m_domain *p2m, lpae_t *entry)
|
||||
|
||||
ASSERT(!p2m_is_valid(*entry));
|
||||
|
||||
page = alloc_domheap_page(NULL, 0);
|
||||
page = p2m_alloc_page(p2m->domain);
|
||||
if ( page == NULL )
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -877,7 +925,7 @@ static void p2m_free_entry(struct p2m_domain *p2m,
|
||||
pg = mfn_to_page(mfn);
|
||||
|
||||
page_list_del(pg, &p2m->pages);
|
||||
free_domheap_page(pg);
|
||||
p2m_free_page(p2m->domain, pg);
|
||||
}
|
||||
|
||||
static bool p2m_split_superpage(struct p2m_domain *p2m, lpae_t *entry,
|
||||
@@ -901,7 +949,7 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, lpae_t *entry,
|
||||
ASSERT(level < target);
|
||||
ASSERT(p2m_is_superpage(*entry, level));
|
||||
|
||||
page = alloc_domheap_page(NULL, 0);
|
||||
page = p2m_alloc_page(p2m->domain);
|
||||
if ( !page )
|
||||
return false;
|
||||
|
||||
@@ -1665,7 +1713,7 @@ int p2m_teardown(struct domain *d)
|
||||
|
||||
while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
{
|
||||
free_domheap_page(pg);
|
||||
p2m_free_page(p2m->domain, pg);
|
||||
count++;
|
||||
/* Arbitrarily preempt every 512 iterations */
|
||||
if ( !(count % 512) && hypercall_preempt_check() )
|
||||
@@ -1689,6 +1737,7 @@ void p2m_final_teardown(struct domain *d)
|
||||
return;
|
||||
|
||||
ASSERT(page_list_empty(&p2m->pages));
|
||||
ASSERT(page_list_empty(&d->arch.paging.p2m_freelist));
|
||||
|
||||
if ( p2m->root )
|
||||
free_domheap_pages(p2m->root, P2M_ROOT_ORDER);
|
||||
|
||||