Message ID | 20230113052914.3845596-20-Penny.Zheng@arm.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | xen/arm: Add Armv8-R64 MPU support to Xen - Part#1 | expand |
Hi, On 13/01/2023 05:28, Penny Zheng wrote: > The new helper xen_mpumap_update() is responsible for updating an entry > in Xen MPU memory mapping table, including creating a new entry, updating > or destroying an existing one. > > This commit only talks about populating a new entry in Xen MPU mapping table( > xen_mpumap). Others will be introduced in the following commits. > > In xen_mpumap_update_entry(), firstly, we shall check if requested address > range [base, limit) is not mapped. Then we use pr_of_xenaddr() to build up the > structure of MPU memory region(pr_t). > In the last, we set memory attribute and permission based on variable @flags. > > To summarize all region attributes in one variable @flags, layout of the > flags is elaborated as follows: > [0:2] Memory attribute Index > [3:4] Execute Never > [5:6] Access Permission > [7] Region Present > Also, we provide a set of definitions(REGION_HYPERVISOR_RW, etc) that combine > the memory attribute and permission for common combinations. > > Signed-off-by: Penny Zheng <penny.zheng@arm.com> > Signed-off-by: Wei Chen <wei.chen@arm.com> > --- > xen/arch/arm/include/asm/arm64/mpu.h | 72 +++++++ > xen/arch/arm/mm_mpu.c | 276 ++++++++++++++++++++++++++- > 2 files changed, 340 insertions(+), 8 deletions(-) > > diff --git a/xen/arch/arm/include/asm/arm64/mpu.h b/xen/arch/arm/include/asm/arm64/mpu.h > index c945dd53db..fcde6ad0db 100644 > --- a/xen/arch/arm/include/asm/arm64/mpu.h > +++ b/xen/arch/arm/include/asm/arm64/mpu.h > @@ -16,6 +16,61 @@ > */ > #define ARM_MAX_MPU_MEMORY_REGIONS 255 > > +/* Access permission attributes. */ > +/* Read/Write at EL2, No Access at EL1/EL0. */ > +#define AP_RW_EL2 0x0 > +/* Read/Write at EL2/EL1/EL0 all levels. */ > +#define AP_RW_ALL 0x1 > +/* Read-only at EL2, No Access at EL1/EL0. */ > +#define AP_RO_EL2 0x2 > +/* Read-only at EL2/EL1/EL0 all levels. */ > +#define AP_RO_ALL 0x3 > + > +/* > + * Excute never. > + * Stage 1 EL2 translation regime. > + * XN[1] determines whether execution of the instruction fetched from the MPU > + * memory region is permitted. > + * Stage 2 EL1/EL0 translation regime. > + * XN[0] determines whether execution of the instruction fetched from the MPU > + * memory region is permitted. > + */ > +#define XN_DISABLED 0x0 > +#define XN_P2M_ENABLED 0x1 > +#define XN_ENABLED 0x2 > + > +/* > + * Layout of the flags used for updating Xen MPU region attributes > + * [0:2] Memory attribute Index > + * [3:4] Execute Never > + * [5:6] Access Permission > + * [7] Region Present > + */ > +#define _REGION_AI_BIT 0 > +#define _REGION_XN_BIT 3 > +#define _REGION_AP_BIT 5 > +#define _REGION_PRESENT_BIT 7 > +#define _REGION_XN (2U << _REGION_XN_BIT) > +#define _REGION_RO (2U << _REGION_AP_BIT) > +#define _REGION_PRESENT (1U << _REGION_PRESENT_BIT) > +#define REGION_AI_MASK(x) (((x) >> _REGION_AI_BIT) & 0x7U) > +#define REGION_XN_MASK(x) (((x) >> _REGION_XN_BIT) & 0x3U) > +#define REGION_AP_MASK(x) (((x) >> _REGION_AP_BIT) & 0x3U) > +#define REGION_RO_MASK(x) (((x) >> _REGION_AP_BIT) & 0x2U) > + > +/* > + * _REGION_NORMAL is convenience define. It is not meant to be used > + * outside of this header. > + */ > +#define _REGION_NORMAL (MT_NORMAL|_REGION_PRESENT) > + > +#define REGION_HYPERVISOR_RW (_REGION_NORMAL|_REGION_XN) > +#define REGION_HYPERVISOR_RO (_REGION_NORMAL|_REGION_XN|_REGION_RO) > + > +#define REGION_HYPERVISOR REGION_HYPERVISOR_RW > + > +#define INVALID_REGION (~0UL) > + > #ifndef __ASSEMBLY__ > > /* Protection Region Base Address Register */ > @@ -49,6 +104,23 @@ typedef struct { > prlar_t prlar; > } pr_t; > > +/* Access to set base address of MPU protection region(pr_t). */ > +#define pr_set_base(pr, paddr) ({ \ > + pr_t *_pr = pr; \ > + _pr->prbar.reg.base = (paddr >> MPU_REGION_SHIFT); \ > +}) > + > +/* Access to set limit address of MPU protection region(pr_t). */ > +#define pr_set_limit(pr, paddr) ({ \ > + pr_t *_pr = pr; \ > + _pr->prlar.reg.limit = (paddr >> MPU_REGION_SHIFT); \ > +}) > + > +#define region_is_valid(pr) ({ \ > + pr_t *_pr = pr; \ > + _pr->prlar.reg.en; \ > +}) Can they all be implemented using static inline? > + > #endif /* __ASSEMBLY__ */ > > #endif /* __ARM64_MPU_H__ */ > diff --git a/xen/arch/arm/mm_mpu.c b/xen/arch/arm/mm_mpu.c > index f2b494449c..08720a7c19 100644 > --- a/xen/arch/arm/mm_mpu.c > +++ b/xen/arch/arm/mm_mpu.c > @@ -22,9 +22,23 @@ > #include <xen/init.h> > #include <xen/mm.h> > #include <xen/page-size.h> > +#include <xen/spinlock.h> > #include <asm/arm64/mpu.h> > #include <asm/page.h> > > +#ifdef NDEBUG > +static inline void > +__attribute__ ((__format__ (__printf__, 1, 2))) > +region_printk(const char *fmt, ...) {} > +#else > +#define region_printk(fmt, args...) \ > + do \ > + { \ > + dprintk(XENLOG_ERR, fmt, ## args); \ > + WARN(); \ > + } while (0) > +#endif > + > /* Xen MPU memory region mapping table. */ > pr_t __aligned(PAGE_SIZE) __section(".data.page_aligned") > xen_mpumap[ARM_MAX_MPU_MEMORY_REGIONS]; > @@ -46,6 +60,8 @@ uint64_t __ro_after_init next_transient_region_idx; > /* Maximum number of supported MPU memory regions by the EL2 MPU. */ > uint64_t __ro_after_init max_xen_mpumap; > > +static DEFINE_SPINLOCK(xen_mpumap_lock); > + > /* Write a MPU protection region */ > #define WRITE_PROTECTION_REGION(sel, pr, prbar_el2, prlar_el2) ({ \ > uint64_t _sel = sel; \ > @@ -73,6 +89,28 @@ uint64_t __ro_after_init max_xen_mpumap; > _pr; \ > }) > > +/* > + * In boot-time, fixed MPU regions(e.g. Xen text section) are added > + * at the front, indexed by next_fixed_region_idx, the others like > + * boot-only regions(e.g. early FDT) should be added at the rear, > + * indexed by next_transient_region_idx. > + * With more and more MPU regions added-in, when the two indexes > + * meet and pass with each other, we would run out of the whole > + * EL2 MPU memory regions. > + */ > +static bool __init xen_boot_mpu_regions_is_full(void) > +{ > + return next_transient_region_idx < next_fixed_region_idx; > +} > + > +static void __init update_boot_xen_mpumap_idx(uint64_t idx) > +{ > + if ( idx == next_transient_region_idx ) > + next_transient_region_idx--; > + else > + next_fixed_region_idx++; > +} > + > /* > * Access MPU protection region, including both read/write operations. > * Armv8-R AArch64 at most supports 255 MPU protection regions. > @@ -197,6 +235,236 @@ static void access_protection_region(bool read, pr_t *pr_read, > } > } > > +/* > + * Standard entry for building up the structure of MPU memory region(pr_t). > + * It is equivalent to mfn_to_xen_entry in MMU system. > + * base and limit both refer to inclusive address. > + */ > +static inline pr_t pr_of_xenaddr(paddr_t base, paddr_t limit, unsigned attr) > +{ > + prbar_t prbar; > + prlar_t prlar; > + pr_t region; > + > + /* Build up value for PRBAR_EL2. */ > + prbar = (prbar_t) { > + .reg = { > + .ap = AP_RW_EL2, /* Read/Write at EL2, no access at EL1/EL0. */ > + .xn = XN_ENABLED, /* No need to execute outside .text */ > + }}; > + > + switch ( attr ) > + { > + case MT_NORMAL_NC: > + /* > + * ARM ARM: Overlaying the shareability attribute (DDI > + * 0406C.b B3-1376 to 1377) > + * > + * A memory region with a resultant memory type attribute of normal, > + * and a resultant cacheability attribute of Inner non-cacheable, > + * outer non-cacheable, must have a resultant shareability attribute > + * of outer shareable, otherwise shareability is UNPREDICTABLE. > + * > + * On ARMv8 sharability is ignored and explicitly treated as outer > + * shareable for normal inner non-cacheable, outer non-cacheable. > + */ > + prbar.reg.sh = LPAE_SH_OUTER; > + break; > + case MT_DEVICE_nGnRnE: > + case MT_DEVICE_nGnRE: > + /* > + * Shareability is ignored for non-normal memory, Outer is as > + * good as anything. > + * > + * On ARMv8 sharability is ignored and explicitly treated as outer > + * shareable for any device memory type. > + */ > + prbar.reg.sh = LPAE_SH_OUTER; > + break; > + default: > + /* Xen mappings are SMP coherent */ > + prbar.reg.sh = LPAE_SH_INNER; > + break; > + } > + > + /* Build up value for PRLAR_EL2. */ > + prlar = (prlar_t) { > + .reg = { > + .ns = 0, /* Hyp mode is in secure world */ > + .ai = attr, > + .en = 1, /* Region enabled */ > + }}; > + > + /* Build up MPU memory region. */ > + region = (pr_t) { > + .prbar = prbar, > + .prlar = prlar, > + }; > + > + /* Set base address and limit address. */ > + pr_set_base(®ion, base); > + pr_set_limit(®ion, limit); > + > + return region; > +} > + > +#define MPUMAP_REGION_FAILED 0 > +#define MPUMAP_REGION_FOUND 1 > +#define MPUMAP_REGION_INCLUSIVE 2 > +#define MPUMAP_REGION_OVERLAP 3 > + > +/* > + * Check whether memory range [base, limit] is mapped in MPU memory > + * region table \mpu. Only address range is considered, memory attributes > + * and permission are not considered here. > + * If we find the match, the associated index will be filled up. > + * If the entry is not present, INVALID_REGION will be set in \index > + * > + * Make sure that parameter \base and \limit are both referring > + * inclusive addresss > + * > + * Return values: > + * MPUMAP_REGION_FAILED: no mapping and no overmapping > + * MPUMAP_REGION_FOUND: find an exact match in address > + * MPUMAP_REGION_INCLUSIVE: find an inclusive match in address > + * MPUMAP_REGION_OVERLAP: overlap with the existing mapping > + */ > +static int mpumap_contain_region(pr_t *mpu, uint64_t nr_regions, > + paddr_t base, paddr_t limit, uint64_t *index) Is it really possible to support 2^64 - 1 region? If so, is that the case on arm32 as well? > +{ > + uint64_t i = 0; > + uint64_t _index = INVALID_REGION; > + > + /* Allow index to be NULL */ > + index = index ?: &_index; > + > + for ( ; i < nr_regions; i++ ) > + { > + paddr_t iter_base = pr_get_base(&mpu[i]); > + paddr_t iter_limit = pr_get_limit(&mpu[i]); > + > + /* Found an exact valid match */ > + if ( (iter_base == base) && (iter_limit == limit) && > + region_is_valid(&mpu[i]) ) > + { > + *index = i; > + return MPUMAP_REGION_FOUND; > + } > + > + /* No overlapping */ > + if ( (iter_limit < base) || (iter_base > limit) ) > + continue; > + /* Inclusive and valid */ > + else if ( (base >= iter_base) && (limit <= iter_limit) && > + region_is_valid(&mpu[i]) ) > + { > + *index = i; > + return MPUMAP_REGION_INCLUSIVE; > + } > + else > + { > + region_printk("Range 0x%"PRIpaddr" - 0x%"PRIpaddr" overlaps with the existing region 0x%"PRIpaddr" - 0x%"PRIpaddr"\n", > + base, limit, iter_base, iter_limit); > + return MPUMAP_REGION_OVERLAP; > + } > + } > + > + return MPUMAP_REGION_FAILED; > +} > + > +/* > + * Update an entry at the index @idx. > + * @base: base address > + * @limit: limit address(exclusive) > + * @flags: region attributes, should be the combination of REGION_HYPERVISOR_xx > + */ > +static int xen_mpumap_update_entry(paddr_t base, paddr_t limit, > + unsigned int flags) > +{ > + uint64_t idx; > + int rc; > + > + rc = mpumap_contain_region(xen_mpumap, max_xen_mpumap, base, limit - 1, > + &idx); > + if ( rc == MPUMAP_REGION_OVERLAP ) > + return -EINVAL; > + > + /* We are inserting a mapping => Create new region. */ > + if ( flags & _REGION_PRESENT ) > + { > + if ( rc != MPUMAP_REGION_FAILED ) > + return -EINVAL; > + > + if ( xen_boot_mpu_regions_is_full() ) > + { > + region_printk("There is no room left in EL2 MPU memory region mapping\n"); > + return -ENOMEM; > + } > + > + /* During boot time, the default index is next_fixed_region_idx. */ > + if ( system_state <= SYS_STATE_active ) > + idx = next_fixed_region_idx; > + > + xen_mpumap[idx] = pr_of_xenaddr(base, limit - 1, REGION_AI_MASK(flags)); > + /* Set permission */ > + xen_mpumap[idx].prbar.reg.ap = REGION_AP_MASK(flags); > + xen_mpumap[idx].prbar.reg.xn = REGION_XN_MASK(flags); > + > + /* Update and enable the region */ > + access_protection_region(false, NULL, (const pr_t*)(&xen_mpumap[idx]), > + idx); > + > + if ( system_state <= SYS_STATE_active ) > + update_boot_xen_mpumap_idx(idx); > + } > + > + return 0; > +} > + > +static int xen_mpumap_update(paddr_t base, paddr_t limit, unsigned int flags) > +{ > + int rc; > + > + /* > + * The hardware was configured to forbid mapping both writeable and > + * executable. > + * When modifying/creating mapping (i.e _REGION_PRESENT is set), > + * prevent any update if this happen. > + */ > + if ( (flags & _REGION_PRESENT) && !REGION_RO_MASK(flags) && > + !REGION_XN_MASK(flags) ) > + { > + region_printk("Mappings should not be both Writeable and Executable.\n"); > + return -EINVAL; > + } > + > + if ( !IS_ALIGNED(base, PAGE_SIZE) || !IS_ALIGNED(limit, PAGE_SIZE) ) > + { > + region_printk("base address 0x%"PRIpaddr", or limit address 0x%"PRIpaddr" is not page aligned.\n", > + base, limit); > + return -EINVAL; > + } > + > + spin_lock(&xen_mpumap_lock); > + > + rc = xen_mpumap_update_entry(base, limit, flags); > + > + spin_unlock(&xen_mpumap_lock); > + > + return rc; > +} > + > +int map_pages_to_xen(unsigned long virt, > + mfn_t mfn, > + unsigned long nr_mfns, > + unsigned int flags) > +{ > + ASSERT(virt == mfn_to_maddr(mfn)); > + > + return xen_mpumap_update(mfn_to_maddr(mfn), > + mfn_to_maddr(mfn_add(mfn, nr_mfns)), flags); > +} > + > /* TODO: Implementation on the first usage */ > void dump_hyp_walk(vaddr_t addr) > { > @@ -230,14 +498,6 @@ void *ioremap(paddr_t pa, size_t len) > return NULL; > } > > -int map_pages_to_xen(unsigned long virt, > - mfn_t mfn, > - unsigned long nr_mfns, > - unsigned int flags) > -{ > - return -ENOSYS; > -} > - Why do you implement map_pages_to_xen() at a different place? > int destroy_xen_mappings(unsigned long s, unsigned long e) > { > return -ENOSYS; Cheers,
Hi Julien > -----Original Message----- > From: Julien Grall <julien@xen.org> > Sent: Monday, February 6, 2023 5:46 AM > To: Penny Zheng <Penny.Zheng@arm.com>; xen-devel@lists.xenproject.org > Cc: Wei Chen <Wei.Chen@arm.com>; Stefano Stabellini > <sstabellini@kernel.org>; Bertrand Marquis <Bertrand.Marquis@arm.com>; > Volodymyr Babchuk <Volodymyr_Babchuk@epam.com> > Subject: Re: [PATCH v2 19/40] xen/mpu: populate a new region in Xen MPU > mapping table > > Hi, > > On 13/01/2023 05:28, Penny Zheng wrote: > > The new helper xen_mpumap_update() is responsible for updating an > > entry in Xen MPU memory mapping table, including creating a new entry, > > updating or destroying an existing one. > > > > This commit only talks about populating a new entry in Xen MPU mapping > > table( xen_mpumap). Others will be introduced in the following commits. > > > > In xen_mpumap_update_entry(), firstly, we shall check if requested > > address range [base, limit) is not mapped. Then we use pr_of_xenaddr() > > to build up the structure of MPU memory region(pr_t). > > In the last, we set memory attribute and permission based on variable > @flags. > > > > To summarize all region attributes in one variable @flags, layout of > > the flags is elaborated as follows: > > [0:2] Memory attribute Index > > [3:4] Execute Never > > [5:6] Access Permission > > [7] Region Present > > Also, we provide a set of definitions(REGION_HYPERVISOR_RW, etc) that > > combine the memory attribute and permission for common combinations. > > > > Signed-off-by: Penny Zheng <penny.zheng@arm.com> > > Signed-off-by: Wei Chen <wei.chen@arm.com> > > --- > > xen/arch/arm/include/asm/arm64/mpu.h | 72 +++++++ > > xen/arch/arm/mm_mpu.c | 276 ++++++++++++++++++++++++++- > > 2 files changed, 340 insertions(+), 8 deletions(-) > > [...] > > + > > +#define MPUMAP_REGION_FAILED 0 > > +#define MPUMAP_REGION_FOUND 1 > > +#define MPUMAP_REGION_INCLUSIVE 2 > > +#define MPUMAP_REGION_OVERLAP 3 > > + > > +/* > > + * Check whether memory range [base, limit] is mapped in MPU memory > > + * region table \mpu. Only address range is considered, memory > > +attributes > > + * and permission are not considered here. > > + * If we find the match, the associated index will be filled up. > > + * If the entry is not present, INVALID_REGION will be set in \index > > + * > > + * Make sure that parameter \base and \limit are both referring > > + * inclusive addresss > > + * > > + * Return values: > > + * MPUMAP_REGION_FAILED: no mapping and no overmapping > > + * MPUMAP_REGION_FOUND: find an exact match in address > > + * MPUMAP_REGION_INCLUSIVE: find an inclusive match in address > > + * MPUMAP_REGION_OVERLAP: overlap with the existing mapping */ > > +static int mpumap_contain_region(pr_t *mpu, uint64_t nr_regions, > > + paddr_t base, paddr_t limit, > > +uint64_t *index) > > Is it really possible to support 2^64 - 1 region? If so, is that the case on arm32 > as well? > No, the allowable bitwidth is 8 bit. I'll change it uint8_t instead > > +{ > > + uint64_t i = 0; > > + uint64_t _index = INVALID_REGION; > > + > > + /* Allow index to be NULL */ > > + index = index ?: &_index; > > + > > + for ( ; i < nr_regions; i++ ) > > + { > > + paddr_t iter_base = pr_get_base(&mpu[i]); > > + paddr_t iter_limit = pr_get_limit(&mpu[i]); > > + > > + /* Found an exact valid match */ > > + if ( (iter_base == base) && (iter_limit == limit) && > > + region_is_valid(&mpu[i]) ) > > + { > > + *index = i; > > + return MPUMAP_REGION_FOUND; > > + } > > + > > + /* No overlapping */ > > + if ( (iter_limit < base) || (iter_base > limit) ) > > + continue; > > + /* Inclusive and valid */ > > + else if ( (base >= iter_base) && (limit <= iter_limit) && > > + region_is_valid(&mpu[i]) ) > > + { > > + *index = i; > > + return MPUMAP_REGION_INCLUSIVE; > > + } > > + else > > + { > > + region_printk("Range 0x%"PRIpaddr" - 0x%"PRIpaddr" overlaps > with the existing region 0x%"PRIpaddr" - 0x%"PRIpaddr"\n", > > + base, limit, iter_base, iter_limit); > > + return MPUMAP_REGION_OVERLAP; > > + } > > + } > > + > > + return MPUMAP_REGION_FAILED; > > +} > > + > > +/* > > + * Update an entry at the index @idx. > > + * @base: base address > > + * @limit: limit address(exclusive) > > + * @flags: region attributes, should be the combination of > > +REGION_HYPERVISOR_xx */ static int > xen_mpumap_update_entry(paddr_t > > +base, paddr_t limit, > > + unsigned int flags) { > > + uint64_t idx; > > + int rc; > > + > > + rc = mpumap_contain_region(xen_mpumap, max_xen_mpumap, base, > limit - 1, > > + &idx); > > + if ( rc == MPUMAP_REGION_OVERLAP ) > > + return -EINVAL; > > + > > + /* We are inserting a mapping => Create new region. */ > > + if ( flags & _REGION_PRESENT ) > > + { > > + if ( rc != MPUMAP_REGION_FAILED ) > > + return -EINVAL; > > + > > + if ( xen_boot_mpu_regions_is_full() ) > > + { > > + region_printk("There is no room left in EL2 MPU memory region > mapping\n"); > > + return -ENOMEM; > > + } > > + > > + /* During boot time, the default index is next_fixed_region_idx. */ > > + if ( system_state <= SYS_STATE_active ) > > + idx = next_fixed_region_idx; > > + > > + xen_mpumap[idx] = pr_of_xenaddr(base, limit - 1, > REGION_AI_MASK(flags)); > > + /* Set permission */ > > + xen_mpumap[idx].prbar.reg.ap = REGION_AP_MASK(flags); > > + xen_mpumap[idx].prbar.reg.xn = REGION_XN_MASK(flags); > > + > > + /* Update and enable the region */ > > + access_protection_region(false, NULL, (const > pr_t*)(&xen_mpumap[idx]), > > + idx); > > + > > + if ( system_state <= SYS_STATE_active ) > > + update_boot_xen_mpumap_idx(idx); > > + } > > + > > + return 0; > > +} > > + > > +static int xen_mpumap_update(paddr_t base, paddr_t limit, unsigned > > +int flags) { > > + int rc; > > + > > + /* > > + * The hardware was configured to forbid mapping both writeable and > > + * executable. > > + * When modifying/creating mapping (i.e _REGION_PRESENT is set), > > + * prevent any update if this happen. > > + */ > > + if ( (flags & _REGION_PRESENT) && !REGION_RO_MASK(flags) && > > + !REGION_XN_MASK(flags) ) > > + { > > + region_printk("Mappings should not be both Writeable and > Executable.\n"); > > + return -EINVAL; > > + } > > + > > + if ( !IS_ALIGNED(base, PAGE_SIZE) || !IS_ALIGNED(limit, PAGE_SIZE) ) > > + { > > + region_printk("base address 0x%"PRIpaddr", or limit address > 0x%"PRIpaddr" is not page aligned.\n", > > + base, limit); > > + return -EINVAL; > > + } > > + > > + spin_lock(&xen_mpumap_lock); > > + > > + rc = xen_mpumap_update_entry(base, limit, flags); > > + > > + spin_unlock(&xen_mpumap_lock); > > + > > + return rc; > > +} > > + > > +int map_pages_to_xen(unsigned long virt, > > + mfn_t mfn, > > + unsigned long nr_mfns, > > + unsigned int flags) { > > + ASSERT(virt == mfn_to_maddr(mfn)); > > + > > + return xen_mpumap_update(mfn_to_maddr(mfn), > > + mfn_to_maddr(mfn_add(mfn, nr_mfns)), > > +flags); } > > + > > /* TODO: Implementation on the first usage */ > > void dump_hyp_walk(vaddr_t addr) > > { > > @@ -230,14 +498,6 @@ void *ioremap(paddr_t pa, size_t len) > > return NULL; > > } > > > > -int map_pages_to_xen(unsigned long virt, > > - mfn_t mfn, > > - unsigned long nr_mfns, > > - unsigned int flags) > > -{ > > - return -ENOSYS; > > -} > > - > > Why do you implement map_pages_to_xen() at a different place? > > > > int destroy_xen_mappings(unsigned long s, unsigned long e) > > { > > return -ENOSYS; > > Cheers, > > -- > Julien Grall
diff --git a/xen/arch/arm/include/asm/arm64/mpu.h b/xen/arch/arm/include/asm/arm64/mpu.h index c945dd53db..fcde6ad0db 100644 --- a/xen/arch/arm/include/asm/arm64/mpu.h +++ b/xen/arch/arm/include/asm/arm64/mpu.h @@ -16,6 +16,61 @@ */ #define ARM_MAX_MPU_MEMORY_REGIONS 255 +/* Access permission attributes. */ +/* Read/Write at EL2, No Access at EL1/EL0. */ +#define AP_RW_EL2 0x0 +/* Read/Write at EL2/EL1/EL0 all levels. */ +#define AP_RW_ALL 0x1 +/* Read-only at EL2, No Access at EL1/EL0. */ +#define AP_RO_EL2 0x2 +/* Read-only at EL2/EL1/EL0 all levels. */ +#define AP_RO_ALL 0x3 + +/* + * Excute never. + * Stage 1 EL2 translation regime. + * XN[1] determines whether execution of the instruction fetched from the MPU + * memory region is permitted. + * Stage 2 EL1/EL0 translation regime. + * XN[0] determines whether execution of the instruction fetched from the MPU + * memory region is permitted. + */ +#define XN_DISABLED 0x0 +#define XN_P2M_ENABLED 0x1 +#define XN_ENABLED 0x2 + +/* + * Layout of the flags used for updating Xen MPU region attributes + * [0:2] Memory attribute Index + * [3:4] Execute Never + * [5:6] Access Permission + * [7] Region Present + */ +#define _REGION_AI_BIT 0 +#define _REGION_XN_BIT 3 +#define _REGION_AP_BIT 5 +#define _REGION_PRESENT_BIT 7 +#define _REGION_XN (2U << _REGION_XN_BIT) +#define _REGION_RO (2U << _REGION_AP_BIT) +#define _REGION_PRESENT (1U << _REGION_PRESENT_BIT) +#define REGION_AI_MASK(x) (((x) >> _REGION_AI_BIT) & 0x7U) +#define REGION_XN_MASK(x) (((x) >> _REGION_XN_BIT) & 0x3U) +#define REGION_AP_MASK(x) (((x) >> _REGION_AP_BIT) & 0x3U) +#define REGION_RO_MASK(x) (((x) >> _REGION_AP_BIT) & 0x2U) + +/* + * _REGION_NORMAL is convenience define. It is not meant to be used + * outside of this header. + */ +#define _REGION_NORMAL (MT_NORMAL|_REGION_PRESENT) + +#define REGION_HYPERVISOR_RW (_REGION_NORMAL|_REGION_XN) +#define REGION_HYPERVISOR_RO (_REGION_NORMAL|_REGION_XN|_REGION_RO) + +#define REGION_HYPERVISOR REGION_HYPERVISOR_RW + +#define INVALID_REGION (~0UL) + #ifndef __ASSEMBLY__ /* Protection Region Base Address Register */ @@ -49,6 +104,23 @@ typedef struct { prlar_t prlar; } pr_t; +/* Access to set base address of MPU protection region(pr_t). */ +#define pr_set_base(pr, paddr) ({ \ + pr_t *_pr = pr; \ + _pr->prbar.reg.base = (paddr >> MPU_REGION_SHIFT); \ +}) + +/* Access to set limit address of MPU protection region(pr_t). */ +#define pr_set_limit(pr, paddr) ({ \ + pr_t *_pr = pr; \ + _pr->prlar.reg.limit = (paddr >> MPU_REGION_SHIFT); \ +}) + +#define region_is_valid(pr) ({ \ + pr_t *_pr = pr; \ + _pr->prlar.reg.en; \ +}) + #endif /* __ASSEMBLY__ */ #endif /* __ARM64_MPU_H__ */ diff --git a/xen/arch/arm/mm_mpu.c b/xen/arch/arm/mm_mpu.c index f2b494449c..08720a7c19 100644 --- a/xen/arch/arm/mm_mpu.c +++ b/xen/arch/arm/mm_mpu.c @@ -22,9 +22,23 @@ #include <xen/init.h> #include <xen/mm.h> #include <xen/page-size.h> +#include <xen/spinlock.h> #include <asm/arm64/mpu.h> #include <asm/page.h> +#ifdef NDEBUG +static inline void +__attribute__ ((__format__ (__printf__, 1, 2))) +region_printk(const char *fmt, ...) {} +#else +#define region_printk(fmt, args...) \ + do \ + { \ + dprintk(XENLOG_ERR, fmt, ## args); \ + WARN(); \ + } while (0) +#endif + /* Xen MPU memory region mapping table. */ pr_t __aligned(PAGE_SIZE) __section(".data.page_aligned") xen_mpumap[ARM_MAX_MPU_MEMORY_REGIONS]; @@ -46,6 +60,8 @@ uint64_t __ro_after_init next_transient_region_idx; /* Maximum number of supported MPU memory regions by the EL2 MPU. */ uint64_t __ro_after_init max_xen_mpumap; +static DEFINE_SPINLOCK(xen_mpumap_lock); + /* Write a MPU protection region */ #define WRITE_PROTECTION_REGION(sel, pr, prbar_el2, prlar_el2) ({ \ uint64_t _sel = sel; \ @@ -73,6 +89,28 @@ uint64_t __ro_after_init max_xen_mpumap; _pr; \ }) +/* + * In boot-time, fixed MPU regions(e.g. Xen text section) are added + * at the front, indexed by next_fixed_region_idx, the others like + * boot-only regions(e.g. early FDT) should be added at the rear, + * indexed by next_transient_region_idx. + * With more and more MPU regions added-in, when the two indexes + * meet and pass with each other, we would run out of the whole + * EL2 MPU memory regions. + */ +static bool __init xen_boot_mpu_regions_is_full(void) +{ + return next_transient_region_idx < next_fixed_region_idx; +} + +static void __init update_boot_xen_mpumap_idx(uint64_t idx) +{ + if ( idx == next_transient_region_idx ) + next_transient_region_idx--; + else + next_fixed_region_idx++; +} + /* * Access MPU protection region, including both read/write operations. * Armv8-R AArch64 at most supports 255 MPU protection regions. @@ -197,6 +235,236 @@ static void access_protection_region(bool read, pr_t *pr_read, } } +/* + * Standard entry for building up the structure of MPU memory region(pr_t). + * It is equivalent to mfn_to_xen_entry in MMU system. + * base and limit both refer to inclusive address. + */ +static inline pr_t pr_of_xenaddr(paddr_t base, paddr_t limit, unsigned attr) +{ + prbar_t prbar; + prlar_t prlar; + pr_t region; + + /* Build up value for PRBAR_EL2. */ + prbar = (prbar_t) { + .reg = { + .ap = AP_RW_EL2, /* Read/Write at EL2, no access at EL1/EL0. */ + .xn = XN_ENABLED, /* No need to execute outside .text */ + }}; + + switch ( attr ) + { + case MT_NORMAL_NC: + /* + * ARM ARM: Overlaying the shareability attribute (DDI + * 0406C.b B3-1376 to 1377) + * + * A memory region with a resultant memory type attribute of normal, + * and a resultant cacheability attribute of Inner non-cacheable, + * outer non-cacheable, must have a resultant shareability attribute + * of outer shareable, otherwise shareability is UNPREDICTABLE. + * + * On ARMv8 sharability is ignored and explicitly treated as outer + * shareable for normal inner non-cacheable, outer non-cacheable. + */ + prbar.reg.sh = LPAE_SH_OUTER; + break; + case MT_DEVICE_nGnRnE: + case MT_DEVICE_nGnRE: + /* + * Shareability is ignored for non-normal memory, Outer is as + * good as anything. + * + * On ARMv8 sharability is ignored and explicitly treated as outer + * shareable for any device memory type. + */ + prbar.reg.sh = LPAE_SH_OUTER; + break; + default: + /* Xen mappings are SMP coherent */ + prbar.reg.sh = LPAE_SH_INNER; + break; + } + + /* Build up value for PRLAR_EL2. */ + prlar = (prlar_t) { + .reg = { + .ns = 0, /* Hyp mode is in secure world */ + .ai = attr, + .en = 1, /* Region enabled */ + }}; + + /* Build up MPU memory region. */ + region = (pr_t) { + .prbar = prbar, + .prlar = prlar, + }; + + /* Set base address and limit address. */ + pr_set_base(®ion, base); + pr_set_limit(®ion, limit); + + return region; +} + +#define MPUMAP_REGION_FAILED 0 +#define MPUMAP_REGION_FOUND 1 +#define MPUMAP_REGION_INCLUSIVE 2 +#define MPUMAP_REGION_OVERLAP 3 + +/* + * Check whether memory range [base, limit] is mapped in MPU memory + * region table \mpu. Only address range is considered, memory attributes + * and permission are not considered here. + * If we find the match, the associated index will be filled up. + * If the entry is not present, INVALID_REGION will be set in \index + * + * Make sure that parameter \base and \limit are both referring + * inclusive addresss + * + * Return values: + * MPUMAP_REGION_FAILED: no mapping and no overmapping + * MPUMAP_REGION_FOUND: find an exact match in address + * MPUMAP_REGION_INCLUSIVE: find an inclusive match in address + * MPUMAP_REGION_OVERLAP: overlap with the existing mapping + */ +static int mpumap_contain_region(pr_t *mpu, uint64_t nr_regions, + paddr_t base, paddr_t limit, uint64_t *index) +{ + uint64_t i = 0; + uint64_t _index = INVALID_REGION; + + /* Allow index to be NULL */ + index = index ?: &_index; + + for ( ; i < nr_regions; i++ ) + { + paddr_t iter_base = pr_get_base(&mpu[i]); + paddr_t iter_limit = pr_get_limit(&mpu[i]); + + /* Found an exact valid match */ + if ( (iter_base == base) && (iter_limit == limit) && + region_is_valid(&mpu[i]) ) + { + *index = i; + return MPUMAP_REGION_FOUND; + } + + /* No overlapping */ + if ( (iter_limit < base) || (iter_base > limit) ) + continue; + /* Inclusive and valid */ + else if ( (base >= iter_base) && (limit <= iter_limit) && + region_is_valid(&mpu[i]) ) + { + *index = i; + return MPUMAP_REGION_INCLUSIVE; + } + else + { + region_printk("Range 0x%"PRIpaddr" - 0x%"PRIpaddr" overlaps with the existing region 0x%"PRIpaddr" - 0x%"PRIpaddr"\n", + base, limit, iter_base, iter_limit); + return MPUMAP_REGION_OVERLAP; + } + } + + return MPUMAP_REGION_FAILED; +} + +/* + * Update an entry at the index @idx. + * @base: base address + * @limit: limit address(exclusive) + * @flags: region attributes, should be the combination of REGION_HYPERVISOR_xx + */ +static int xen_mpumap_update_entry(paddr_t base, paddr_t limit, + unsigned int flags) +{ + uint64_t idx; + int rc; + + rc = mpumap_contain_region(xen_mpumap, max_xen_mpumap, base, limit - 1, + &idx); + if ( rc == MPUMAP_REGION_OVERLAP ) + return -EINVAL; + + /* We are inserting a mapping => Create new region. */ + if ( flags & _REGION_PRESENT ) + { + if ( rc != MPUMAP_REGION_FAILED ) + return -EINVAL; + + if ( xen_boot_mpu_regions_is_full() ) + { + region_printk("There is no room left in EL2 MPU memory region mapping\n"); + return -ENOMEM; + } + + /* During boot time, the default index is next_fixed_region_idx. */ + if ( system_state <= SYS_STATE_active ) + idx = next_fixed_region_idx; + + xen_mpumap[idx] = pr_of_xenaddr(base, limit - 1, REGION_AI_MASK(flags)); + /* Set permission */ + xen_mpumap[idx].prbar.reg.ap = REGION_AP_MASK(flags); + xen_mpumap[idx].prbar.reg.xn = REGION_XN_MASK(flags); + + /* Update and enable the region */ + access_protection_region(false, NULL, (const pr_t*)(&xen_mpumap[idx]), + idx); + + if ( system_state <= SYS_STATE_active ) + update_boot_xen_mpumap_idx(idx); + } + + return 0; +} + +static int xen_mpumap_update(paddr_t base, paddr_t limit, unsigned int flags) +{ + int rc; + + /* + * The hardware was configured to forbid mapping both writeable and + * executable. + * When modifying/creating mapping (i.e _REGION_PRESENT is set), + * prevent any update if this happen. + */ + if ( (flags & _REGION_PRESENT) && !REGION_RO_MASK(flags) && + !REGION_XN_MASK(flags) ) + { + region_printk("Mappings should not be both Writeable and Executable.\n"); + return -EINVAL; + } + + if ( !IS_ALIGNED(base, PAGE_SIZE) || !IS_ALIGNED(limit, PAGE_SIZE) ) + { + region_printk("base address 0x%"PRIpaddr", or limit address 0x%"PRIpaddr" is not page aligned.\n", + base, limit); + return -EINVAL; + } + + spin_lock(&xen_mpumap_lock); + + rc = xen_mpumap_update_entry(base, limit, flags); + + spin_unlock(&xen_mpumap_lock); + + return rc; +} + +int map_pages_to_xen(unsigned long virt, + mfn_t mfn, + unsigned long nr_mfns, + unsigned int flags) +{ + ASSERT(virt == mfn_to_maddr(mfn)); + + return xen_mpumap_update(mfn_to_maddr(mfn), + mfn_to_maddr(mfn_add(mfn, nr_mfns)), flags); +} + /* TODO: Implementation on the first usage */ void dump_hyp_walk(vaddr_t addr) { @@ -230,14 +498,6 @@ void *ioremap(paddr_t pa, size_t len) return NULL; } -int map_pages_to_xen(unsigned long virt, - mfn_t mfn, - unsigned long nr_mfns, - unsigned int flags) -{ - return -ENOSYS; -} - int destroy_xen_mappings(unsigned long s, unsigned long e) { return -ENOSYS;