mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
xen/riscv: add root page table allocation
Introduce support for allocating and initializing the root page table required for RISC-V stage-2 address translation. To implement root page table allocation the following is introduced: - p2m_get_clean_page() and p2m_alloc_root_table(), p2m_allocate_root() helpers to allocate and zero a 16 KiB root page table, as mandated by the RISC-V privileged specification for Sv32x4/Sv39x4/Sv48x4/Sv57x4 modes. - Update p2m_init() to inititialize p2m_root_order. - Add maddr_to_page() and page_to_maddr() macros for easier address manipulation. - Introduce paging_ret_to_domheap() to return some pages before allocate 16 KiB pages for root page table. - Allocate root p2m table after p2m pool is initialized. - Add construct_hgatp() to construct the hgatp register value based on p2m->root, p2m->hgatp_mode and VMID. Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com> Acked-by: Jan Beulich <jbeulich@suse.com>
This commit is contained in:
1 parent
3810f15521
commit
824ec51384
6 files changed
+206
-29
No files matched your search
@@ -167,6 +167,10 @@ extern struct page_info *frametable_virt_start;
|
||||
#define mfn_to_page(mfn) (frametable_virt_start + mfn_x(mfn))
|
||||
#define page_to_mfn(pg) _mfn((pg) - frametable_virt_start)
|
||||
|
||||
/* Convert between machine addresses and page-info structures. */
|
||||
#define maddr_to_page(ma) mfn_to_page(maddr_to_mfn(ma))
|
||||
#define page_to_maddr(pg) mfn_to_maddr(page_to_mfn(pg))
|
||||
|
||||
static inline void *page_to_virt(const struct page_info *pg)
|
||||
{
|
||||
return mfn_to_virt(mfn_x(page_to_mfn(pg)));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifndef ASM__RISCV__P2M_H
|
||||
#define ASM__RISCV__P2M_H
|
||||
|
||||
#include <xen/bitops.h>
|
||||
#include <xen/errno.h>
|
||||
#include <xen/mm.h>
|
||||
#include <xen/rwlock.h>
|
||||
@@ -9,6 +10,9 @@
|
||||
|
||||
#include <asm/page-bits.h>
|
||||
|
||||
#define P2M_ROOT_ORDER (ilog2(GSTAGE_ROOT_PAGE_TABLE_SIZE) - PAGE_SHIFT)
|
||||
#define P2M_ROOT_PAGES BIT(P2M_ROOT_ORDER, U)
|
||||
|
||||
#define paddr_bits PADDR_BITS
|
||||
|
||||
/* Get host p2m table */
|
||||
@@ -30,6 +34,11 @@ struct p2m_domain {
|
||||
/* Pages used to construct the p2m */
|
||||
struct page_list_head pages;
|
||||
|
||||
/* The root of the p2m tree. May be concatenated */
|
||||
struct page_info *root;
|
||||
|
||||
struct gstage_mode_desc mode;
|
||||
|
||||
/* Back pointer to domain */
|
||||
struct domain *domain;
|
||||
|
||||
@@ -43,6 +52,12 @@ struct p2m_domain {
|
||||
* shattered), call p2m_tlb_flush_sync().
|
||||
*/
|
||||
bool need_flush;
|
||||
|
||||
/*
|
||||
* Indicate if it is required to clean the cache when writing an entry or
|
||||
* when a page is needed to be fully cleared and cleaned.
|
||||
*/
|
||||
bool clean_dcache;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -130,6 +145,8 @@ unsigned char get_max_supported_mode(void);
|
||||
|
||||
int p2m_init(struct domain *d);
|
||||
|
||||
unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid);
|
||||
|
||||
#endif /* ASM__RISCV__P2M_H */
|
||||
|
||||
/*
|
||||
|
||||
@@ -10,4 +10,7 @@ int paging_domain_init(struct domain *d);
|
||||
int paging_freelist_adjust(struct domain *d, unsigned long pages,
|
||||
bool *preempted);
|
||||
|
||||
int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages);
|
||||
int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages);
|
||||
|
||||
#endif /* ASM_RISCV_PAGING_H */
|
||||
@@ -180,6 +180,8 @@
|
||||
#define HGATP_MODE_MASK HGATP32_MODE_MASK
|
||||
#endif
|
||||
|
||||
#define GSTAGE_ROOT_PAGE_TABLE_SIZE KB(16)
|
||||
|
||||
#define TOPI_IID_SHIFT 16
|
||||
#define TOPI_IID_MASK 0xfff
|
||||
#define TOPI_IPRIO_MASK 0xff
|
||||
|
||||
+98
-1
@@ -3,6 +3,7 @@
|
||||
#include <xen/init.h>
|
||||
#include <xen/lib.h>
|
||||
#include <xen/macros.h>
|
||||
#include <xen/domain_page.h>
|
||||
#include <xen/mm.h>
|
||||
#include <xen/paging.h>
|
||||
#include <xen/rwlock.h>
|
||||
@@ -109,6 +110,74 @@ void __init guest_mm_init(void)
|
||||
local_hfence_gvma_all();
|
||||
}
|
||||
|
||||
static void clear_and_clean_page(struct page_info *page, bool clean_dcache)
|
||||
{
|
||||
void *p = __map_domain_page(page);
|
||||
|
||||
clear_page(p);
|
||||
|
||||
/*
|
||||
* If the IOMMU doesn't support coherent walks and the p2m tables are
|
||||
* shared between the CPU and IOMMU, it is necessary to clean the
|
||||
* d-cache.
|
||||
*/
|
||||
if ( clean_dcache )
|
||||
clean_dcache_va_range(p, PAGE_SIZE);
|
||||
|
||||
unmap_domain_page(p);
|
||||
}
|
||||
|
||||
unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid)
|
||||
{
|
||||
return MASK_INSR(mfn_x(page_to_mfn(p2m->root)), HGATP_PPN_MASK) |
|
||||
MASK_INSR(p2m->mode.mode, HGATP_MODE_MASK) |
|
||||
MASK_INSR(vmid, HGATP_VMID_MASK);
|
||||
}
|
||||
|
||||
static int p2m_alloc_root_table(struct p2m_domain *p2m)
|
||||
{
|
||||
struct domain *d = p2m->domain;
|
||||
struct page_info *page;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Return back P2M_ROOT_PAGES to assure the root table memory is also
|
||||
* accounted against the P2M pool of the domain.
|
||||
*/
|
||||
if ( (rc = paging_ret_to_domheap(d, P2M_ROOT_PAGES)) )
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* As mentioned in the Priviliged Architecture Spec (version 20240411)
|
||||
* in Section 18.5.1, for the paged virtual-memory schemes (Sv32x4,
|
||||
* Sv39x4, Sv48x4, and Sv57x4), the root page table is 16 KiB and must
|
||||
* be aligned to a 16-KiB boundary.
|
||||
*/
|
||||
page = alloc_domheap_pages(d, P2M_ROOT_ORDER, MEMF_no_owner);
|
||||
if ( !page )
|
||||
{
|
||||
/*
|
||||
* If allocation of root table pages fails, the pages acquired above
|
||||
* must be returned to the freelist to maintain proper freelist
|
||||
* balance.
|
||||
*/
|
||||
paging_refill_from_domheap(d, P2M_ROOT_PAGES);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for ( unsigned int i = 0; i < P2M_ROOT_PAGES; i++ )
|
||||
{
|
||||
clear_and_clean_page(page + i, p2m->clean_dcache);
|
||||
|
||||
page_list_add(page + i, &p2m->pages);
|
||||
}
|
||||
|
||||
p2m->root = page;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int p2m_init(struct domain *d)
|
||||
{
|
||||
struct p2m_domain *p2m = p2m_get_hostp2m(d);
|
||||
@@ -124,6 +193,24 @@ int p2m_init(struct domain *d)
|
||||
rwlock_init(&p2m->lock);
|
||||
INIT_PAGE_LIST_HEAD(&p2m->pages);
|
||||
|
||||
/*
|
||||
* Currently, the infrastructure required to enable CONFIG_HAS_PASSTHROUGH
|
||||
* is not ready for RISC-V support.
|
||||
*
|
||||
* When CONFIG_HAS_PASSTHROUGH=y, p2m->clean_dcache must be properly
|
||||
* initialized.
|
||||
* At the moment, it defaults to false because the p2m structure is
|
||||
* zero-initialized.
|
||||
*/
|
||||
#ifdef CONFIG_HAS_PASSTHROUGH
|
||||
# error "Add init of p2m->clean_dcache"
|
||||
#endif
|
||||
|
||||
/* TODO: don't hardcode used for a domain g-stage mode. */
|
||||
p2m->mode.mode = HGATP_MODE_SV39X4;
|
||||
p2m->mode.paging_levels = 2;
|
||||
safe_strcpy(p2m->mode.name, "Sv39x4");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -134,10 +221,20 @@ int p2m_init(struct domain *d)
|
||||
*/
|
||||
int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
|
||||
{
|
||||
struct p2m_domain *p2m = p2m_get_hostp2m(d);
|
||||
int rc;
|
||||
|
||||
if ( (rc = paging_freelist_adjust(d, pages, preempted)) )
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
/*
|
||||
* First, initialize p2m pool. Then allocate the root
|
||||
* table so that the necessary pages can be returned from the p2m pool,
|
||||
* since the root table must be allocated using alloc_domheap_pages(...)
|
||||
* to meet its specific requirements.
|
||||
*/
|
||||
if ( !p2m->root )
|
||||
rc = p2m_alloc_root_table(p2m);
|
||||
|
||||
return rc;
|
||||
}
|
||||
+82
-28
@@ -4,46 +4,67 @@
|
||||
#include <xen/sched.h>
|
||||
#include <xen/spinlock.h>
|
||||
|
||||
static int _paging_ret_to_domheap(struct domain *d)
|
||||
{
|
||||
struct page_info *page;
|
||||
|
||||
ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
|
||||
/* Return memory to domheap. */
|
||||
page = page_list_remove_head(&d->arch.paging.freelist);
|
||||
if( page )
|
||||
{
|
||||
d->arch.paging.total_pages--;
|
||||
free_domheap_page(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
printk(XENLOG_ERR
|
||||
"failed to free pages, P2M freelist is empty\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _paging_add_to_freelist(struct domain *d)
|
||||
{
|
||||
struct page_info *page;
|
||||
|
||||
ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
|
||||
/* Need to allocate more memory from domheap */
|
||||
page = alloc_domheap_page(d, MEMF_no_owner);
|
||||
if ( page == NULL )
|
||||
{
|
||||
printk(XENLOG_ERR "failed to allocate pages\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
d->arch.paging.total_pages++;
|
||||
page_list_add_tail(page, &d->arch.paging.freelist);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int paging_freelist_adjust(struct domain *d, unsigned long pages,
|
||||
bool *preempted)
|
||||
{
|
||||
struct page_info *pg;
|
||||
|
||||
ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
|
||||
for ( ; ; )
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if ( d->arch.paging.total_pages < pages )
|
||||
{
|
||||
/* Need to allocate more memory from domheap */
|
||||
pg = alloc_domheap_page(d, MEMF_no_owner);
|
||||
if ( pg == NULL )
|
||||
{
|
||||
printk(XENLOG_ERR "Failed to allocate pages.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
ACCESS_ONCE(d->arch.paging.total_pages)++;
|
||||
page_list_add_tail(pg, &d->arch.paging.freelist);
|
||||
}
|
||||
rc = _paging_add_to_freelist(d);
|
||||
else if ( d->arch.paging.total_pages > pages )
|
||||
{
|
||||
/* Need to return memory to domheap */
|
||||
pg = page_list_remove_head(&d->arch.paging.freelist);
|
||||
if ( pg )
|
||||
{
|
||||
ACCESS_ONCE(d->arch.paging.total_pages)--;
|
||||
free_domheap_page(pg);
|
||||
}
|
||||
else
|
||||
{
|
||||
printk(XENLOG_ERR
|
||||
"Failed to free pages, freelist is empty.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
rc = _paging_ret_to_domheap(d);
|
||||
else
|
||||
break;
|
||||
|
||||
if ( rc )
|
||||
return rc;
|
||||
|
||||
/* Check to see if we need to yield and try again */
|
||||
if ( preempted && general_preempt_check() )
|
||||
{
|
||||
@@ -55,6 +76,39 @@ int paging_freelist_adjust(struct domain *d, unsigned long pages,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages)
|
||||
{
|
||||
ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
|
||||
for ( unsigned int i = 0; i < nr_pages; i++ )
|
||||
{
|
||||
int rc = _paging_add_to_freelist(d);
|
||||
|
||||
if ( rc )
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages)
|
||||
{
|
||||
ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
|
||||
if ( d->arch.paging.total_pages < nr_pages )
|
||||
return false;
|
||||
|
||||
for ( unsigned int i = 0; i < nr_pages; i++ )
|
||||
{
|
||||
int rc = _paging_ret_to_domheap(d);
|
||||
|
||||
if ( rc )
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Domain paging struct initialization. */
|
||||
int paging_domain_init(struct domain *d)
|
||||
{
|
||||
|
||||