|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [RFC 12/22] xen/arm: p2m: Introduce p2m_get_entry and use it to implement __p2m_lookupo
On Wed, 31 Aug 2016, Julien Grall wrote:
> > > @@ -236,28 +238,99 @@ static lpae_t *p2m_get_root_pointer(struct
> > > p2m_domain *p2m,
> > >
> > > /*
> > > * Lookup the MFN corresponding to a domain's GFN.
> > > + * Lookup mem access in the ratrix tree.
> > > + * The entries associated to the GFN is considered valid.
> > > + */
> > > +static p2m_access_t p2m_mem_access_radix_get(struct p2m_domain *p2m,
> > > gfn_t gfn)
> > > +{
> > > + void *ptr;
> > > +
> > > + if ( !p2m->mem_access_enabled )
> > > + return p2m_access_rwx;
> >
> > Shouldn't this be p2m->default_access?
>
> default_access will always be p2m_access_rwx when memaccess is disabled. It
> will lead to crash a if you try to restrict permission without memaccess.
>
> Note that, this is matching the behavior of __p2m_get_mem_access.
I would like to avoid some places to use p2m_access_rwx and others to
use p2m->default_access. But it is true that in this context both are
fine. This was just a suggestion.
> > > + ptr = radix_tree_lookup(&p2m->mem_access_settings, gfn_x(gfn));
> > > + if ( !ptr )
> > > + return p2m_access_rwx;
> >
> > Same here?
>
> The radix tree will contain all the permission restriction but p2m_access_rwx.
> This is because you may change the default_access whilst memaccess is enabled
> and you don't know what page was restricted with the default access.
>
> Note that this is matching the behavior of p2m_mem_access_radix_set.
>
> >
> >
> > > + else
> > > + return radix_tree_ptr_to_int(ptr);
> > > +}
> > > +
> > > +#define GUEST_TABLE_MAP_FAILED 0
> > > +#define GUEST_TABLE_SUPER_PAGE 1
> > > +#define GUEST_TABLE_NORMAL_PAGE 2
> > > +
> > > +static int p2m_create_table(struct p2m_domain *p2m, lpae_t *entry,
> > > + int level_shift);
> > > +
> > > +/*
> > > + * Take the currently mapped table, find the corresponding GFN entry,
> > > + * and map the next table, if available.
> >
> > It is important to write down that the function also unmaps the previous
> > table.
>
> Will do.
>
> >
> > > *
> > > - * There are no processor functions to do a stage 2 only lookup therefore
> > > we
> > > - * do a a software walk.
> > > + * Return values:
> > > + * GUEST_TABLE_MAP_FAILED: Either read_only was set and the entry
> > > + * was empty, or allocating a new page failed.
> > > + * GUEST_TABLE_NORMAL_PAGE: next level mapped normally
> > > + * GUEST_TABLE_SUPER_PAGE: The next entry points to a superpage.
> > > */
> > > -static mfn_t __p2m_lookup(struct domain *d, gfn_t gfn, p2m_type_t *t)
> > > +static int p2m_next_level(struct p2m_domain *p2m, bool read_only,
> > > + lpae_t **table, unsigned int offset)
> > > {
> > > - struct p2m_domain *p2m = &d->arch.p2m;
> > > - const paddr_t paddr = pfn_to_paddr(gfn_x(gfn));
> > > - const unsigned int offsets[4] = {
> > > - zeroeth_table_offset(paddr),
> > > - first_table_offset(paddr),
> > > - second_table_offset(paddr),
> > > - third_table_offset(paddr)
> > > - };
> > > - const paddr_t masks[4] = {
> > > - ZEROETH_MASK, FIRST_MASK, SECOND_MASK, THIRD_MASK
> > > - };
> > > - lpae_t pte, *map;
> > > + lpae_t *entry;
> > > + int ret;
> > > + mfn_t mfn;
> > > +
> > > + entry = *table + offset;
> > > +
> > > + if ( !p2m_valid(*entry) )
> > > + {
> > > + if ( read_only )
> > > + return GUEST_TABLE_MAP_FAILED;
> > > +
> > > + ret = p2m_create_table(p2m, entry, /* not used */ ~0);
> > > + if ( ret )
> > > + return GUEST_TABLE_MAP_FAILED;
> > > + }
> > > +
> > > + /* The function p2m_next_level is never called at the 3rd level */
> > > + if ( p2m_mapping(*entry) )
> > > + return GUEST_TABLE_SUPER_PAGE;
> > > +
> > > + mfn = _mfn(entry->p2m.base);
> > > +
> > > + unmap_domain_page(*table);
> > > + *table = map_domain_page(mfn);
> > > +
> > > + return GUEST_TABLE_NORMAL_PAGE;
> >
> > I am a bit worried about having the same function doing the lookup and
> > creating new tables, especially given it doesn't tell you whether the
> > entry was already there or it was created: the return value is the same
> > in both cases. At the very least the return values should be different.
>
> I don't understand your worry here. Why would you care that the table has been
> allocated or was already existing?
>
> If the caller does not want to allocate table, it can request to browse the
> entry in a read-only mode (see read_only).
To avoid unintentional side effects, such as calling this function for a
lookup but passing read-only as false by mistake. But maybe we just need
to be careful enough in the code reviews, after all this function won't
be called that many times.
> > > +}
> > > +
> > > +/*
> > > + * Get the details of a given gfn.
> > > + *
> > > + * If the entry is present, the associated MFN will be returned and the
> > > + * access and type filled up. The page_order will correspond to the
> > > + * order of the mapping in the page table (i.e it could be a superpage).
> > > + *
> > > + * If the entry is not present, INVALID_MFN will be returned and the
> > > + * page_order will be set according to the order of the invalid range.
> > > + */
> > > +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
> > > + p2m_type_t *t, p2m_access_t *a,
> > > + unsigned int *page_order)
> > > +{
> > > + paddr_t addr = pfn_to_paddr(gfn_x(gfn));
> > > + unsigned int level = 0;
> > > + lpae_t entry, *table;
> > > + int rc;
> > > mfn_t mfn = INVALID_MFN;
> > > - paddr_t mask = 0;
> > > p2m_type_t _t;
> > > - unsigned int level;
> > > +
> > > + /* Convenience aliases */
> > > + const unsigned int offsets[4] = {
> > > + zeroeth_table_offset(addr),
> > > + first_table_offset(addr),
> > > + second_table_offset(addr),
> > > + third_table_offset(addr)
> > > + };
> > >
> > > ASSERT(p2m_is_locked(p2m));
> > > BUILD_BUG_ON(THIRD_MASK != PAGE_MASK);
> > > @@ -267,46 +340,75 @@ static mfn_t __p2m_lookup(struct domain *d, gfn_t
> > > gfn, p2m_type_t *t)
> > >
> > > *t = p2m_invalid;
> > >
> > > - map = p2m_get_root_pointer(p2m, gfn);
> > > - if ( !map )
> > > - return INVALID_MFN;
> > > + /* XXX: Check if the mapping is lower than the mapped gfn */
> > >
> > > - ASSERT(P2M_ROOT_LEVEL < 4);
> > > -
> > > - for ( level = P2M_ROOT_LEVEL ; level < 4 ; level++ )
> > > + /* This gfn is higher than the highest the p2m map currently holds */
> > > + if ( gfn_x(gfn) > gfn_x(p2m->max_mapped_gfn) )
> > > {
> > > - mask = masks[level];
> > > -
> > > - pte = map[offsets[level]];
> > > + for ( level = P2M_ROOT_LEVEL; level < 3; level++ )
> > > + {
> > > + if ( (gfn_x(gfn) & (level_masks[level] >> PAGE_SHIFT)) >
> > > + gfn_x(p2m->max_mapped_gfn) )
> > > + break;
> > > + goto out;
> >
> > I am not sure what this loop is for, but it looks wrong.
>
> As mentioned in the description of the function, if the entry is not present,
> the function will return the order of the invalid range.
>
> The loop will find the highest possible order by checking the base of the
> block mapping is greater than the max mapped gfn.
All right, but shouldn't the `goto out` be right after the loop?
Otherwise it is not really a loop :-)
> > > + }
> > > + }
> > >
> > > - if ( level == 3 && !p2m_table(pte) )
> > > - /* Invalid, clobber the pte */
> > > - pte.bits = 0;
> > > - if ( level == 3 || !p2m_table(pte) )
> > > - /* Done */
> > > - break;
> > > + table = p2m_get_root_pointer(p2m, gfn);
> > >
> > > - ASSERT(level < 3);
> > > + /*
> > > + * the table should always be non-NULL because the gfn is below
> > > + * p2m->max_mapped_gfn and the root table pages are always present.
> > > + */
> > > + BUG_ON(table == NULL);
> > >
> > > - /* Map for next level */
> > > - unmap_domain_page(map);
> > > - map = map_domain_page(_mfn(pte.p2m.base));
> > > + for ( level = P2M_ROOT_LEVEL; level < 3; level++ )
> > > + {
> > > + rc = p2m_next_level(p2m, true, &table, offsets[level]);
> > > + if ( rc == GUEST_TABLE_MAP_FAILED )
> > > + goto out_unmap;
> > > + else if ( rc != GUEST_TABLE_NORMAL_PAGE )
> > > + break;
> > > }
> > >
> > > - unmap_domain_page(map);
> > > + entry = table[offsets[level]];
> > >
> > > - if ( p2m_valid(pte) )
> > > + if ( p2m_valid(entry) )
> > > {
> > > - ASSERT(mask);
> > > - ASSERT(pte.p2m.type != p2m_invalid);
> > > - mfn = _mfn(paddr_to_pfn((pte.bits & PADDR_MASK & mask) |
> > > - (paddr & ~mask)));
> > > - *t = pte.p2m.type;
> > > + *t = entry.p2m.type;
> >
> > Why don't you have a check for ( t ) like you have done in the case of
> > ( a )? It would be more consistent.
>
> This is done at the beginning of the function:
>
> /* Allow t to be NULL */
> t = t ?: &_t;
>
> *t = p2m_invalid;
>
> This was kept from the implementation of __p2m_lookup because some callers
> check the type before checking if the MFN is invalid (e.g get_page_from_gfn).
>
> I didn't follow the same principle for the access because going through the
> radix tree is expensive and most of the hot path does not care about the
> access.
Makes sense.
> > > +
> > > + if ( a )
> > > + *a = p2m_mem_access_radix_get(p2m, gfn);
> > > +
> > > + mfn = _mfn(entry.p2m.base);
> > > + /*
> > > + * The entry may point to a superpage. Find the MFN associated
> > > + * to the GFN.
> > > + */
> > > + mfn = mfn_add(mfn, gfn_x(gfn) & ((1UL << level_orders[level]) -
> > > 1));
> > > }
> > >
> > > +out_unmap:
> > > + unmap_domain_page(table);
> > > +
> > > +out:
> > > + if ( page_order )
> > > + *page_order = level_shifts[level] - PAGE_SHIFT;
> > > +
> > > return mfn;
> > > }
> > >
> > > +/*
> > > + * Lookup the MFN corresponding to a domain's GFN.
> > > + *
> > > + * There are no processor functions to do a stage 2 only lookup therefore
> > > we
> > > + * do a a software walk.
> > > + */
> > > +static mfn_t __p2m_lookup(struct domain *d, gfn_t gfn, p2m_type_t *t)
> > > +{
> > > + return p2m_get_entry(&d->arch.p2m, gfn, t, NULL, NULL);
> > > +}
> > > +
> > > mfn_t p2m_lookup(struct domain *d, gfn_t gfn, p2m_type_t *t)
> > > {
> > > mfn_t ret;
> > > diff --git a/xen/include/asm-arm/page.h b/xen/include/asm-arm/page.h
> > > index 05d9f82..1c5bd8b 100644
> > > --- a/xen/include/asm-arm/page.h
> > > +++ b/xen/include/asm-arm/page.h
> > > @@ -457,15 +457,19 @@ static inline int gva_to_ipa(vaddr_t va, paddr_t
> > > *paddr, unsigned int flags)
> > > #define LPAE_ENTRY_MASK (LPAE_ENTRIES - 1)
> > >
> > > #define THIRD_SHIFT (PAGE_SHIFT)
> > > +#define THIRD_ORDER 0
> > > #define THIRD_SIZE ((paddr_t)1 << THIRD_SHIFT)
> > > #define THIRD_MASK (~(THIRD_SIZE - 1))
> > > #define SECOND_SHIFT (THIRD_SHIFT + LPAE_SHIFT)
> > > +#define SECOND_ORDER (THIRD_ORDER + LPAE_SHIFT)
> > > #define SECOND_SIZE ((paddr_t)1 << SECOND_SHIFT)
> > > #define SECOND_MASK (~(SECOND_SIZE - 1))
> > > #define FIRST_SHIFT (SECOND_SHIFT + LPAE_SHIFT)
> > > +#define FIRST_ORDER (SECOND_ORDER + LPAE_SHIFT)
> > > #define FIRST_SIZE ((paddr_t)1 << FIRST_SHIFT)
> > > #define FIRST_MASK (~(FIRST_SIZE - 1))
> > > #define ZEROETH_SHIFT (FIRST_SHIFT + LPAE_SHIFT)
> > > +#define ZEROETH_ORDER (FIRST_ORDER + LPAE_SHIFT)
> > > #define ZEROETH_SIZE ((paddr_t)1 << ZEROETH_SHIFT)
> > > #define ZEROETH_MASK (~(ZEROETH_SIZE - 1))
> >
> > It might be clearer to define them by SHIFT:
> >
> > #define THIRD_ORDER (THIRD_SHIFT - PAGE_SHIFT)
> > #define SECOND_ORDER (SECOND_SHIFT - PAGE_SHIFT)
> > #define FIRST_ORDER (FIRST_SHIFT - PAGE_SHIFT)
> > #define ZEROETH_ORDER (ZEROETH_SHIFT - PAGE_SHIFT)
>
> I was following the way the other constant has been defined. The order of the
> 2nd level can be expressed in term of the order of the 3rd level...
>
> I don't think this is clearer, but I don't mind to use them.
>
> > or avoid them and just use level_shifts which is already defined. I
> > don't think they add much value.
>
> Really? It avoids to spread - {PAGE,LPAE}_SHIFT everywhere in the code so the
> code will be cleaner.
>
> Note that I have noticed that there are still few places where (level_shifts -
> PAGE_SHIFT) is still in use. However, we can replace by level_orders.
The reason why I suggested the alternative implementation is that
"order" is not commonly used when dealing with pagetable levels (while
"mask" and "shift" are). I had a guess about what it meant, but wasn't
sure. To be sure I had to read the implementation. I think this version
is more obvious about what it does. That said, this is just a
suggestion, not a requirement.
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |