Add functions to keep track of VA ranges mapped in an ASI page-table. This will be used when unmapping to ensure the same range is unmapped, at the same page-table level. This is also be used to handle mapping and unmapping of overlapping VA ranges. Signed-off-by: Alexandre Chartre <alexandre.chartre@xxxxxxxxxx> --- arch/x86/include/asm/asi.h | 3 ++ arch/x86/mm/asi.c | 3 ++ arch/x86/mm/asi_pagetable.c | 71 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 0 deletions(-) diff --git a/arch/x86/include/asm/asi.h b/arch/x86/include/asm/asi.h index b5dbc49..be1c190 100644 --- a/arch/x86/include/asm/asi.h +++ b/arch/x86/include/asm/asi.h @@ -24,6 +24,7 @@ enum page_table_level { struct asi { spinlock_t lock; /* protect all attributes */ pgd_t *pgd; /* ASI page-table */ + struct list_head mapping_list; /* list of VA range mapping */ /* * An ASI page-table can have direct references to the full kernel @@ -69,6 +70,8 @@ struct asi_session { void asi_init_backend(struct asi *asi); void asi_fini_backend(struct asi *asi); +void asi_init_range_mapping(struct asi *asi); +void asi_fini_range_mapping(struct asi *asi); extern struct asi *asi_create(void); extern void asi_destroy(struct asi *asi); diff --git a/arch/x86/mm/asi.c b/arch/x86/mm/asi.c index dfde245..25633a6 100644 --- a/arch/x86/mm/asi.c +++ b/arch/x86/mm/asi.c @@ -104,6 +104,8 @@ struct asi *asi_create(void) if (!asi) return NULL; + asi_init_range_mapping(asi); + page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); if (!page) goto error; @@ -133,6 +135,7 @@ void asi_destroy(struct asi *asi) if (asi->pgd) free_page((unsigned long)asi->pgd); + asi_fini_range_mapping(asi); asi_fini_backend(asi); kfree(asi); diff --git a/arch/x86/mm/asi_pagetable.c b/arch/x86/mm/asi_pagetable.c index 0169395..a09a22d 100644 --- a/arch/x86/mm/asi_pagetable.c +++ b/arch/x86/mm/asi_pagetable.c @@ -5,10 +5,21 @@ */ #include <linux/mm.h> +#include <linux/slab.h> #include <asm/asi.h> /* + * Structure to keep track of address ranges mapped into an ASI. + */ +struct asi_range_mapping { + struct list_head list; + void *ptr; /* range start address */ + size_t size; /* range size */ + enum page_table_level level; /* mapping level */ +}; + +/* * Get the pointer to the beginning of a page table directory from a page * table directory entry. */ @@ -75,6 +86,39 @@ void asi_fini_backend(struct asi *asi) } } +void asi_init_range_mapping(struct asi *asi) +{ + INIT_LIST_HEAD(&asi->mapping_list); +} + +void asi_fini_range_mapping(struct asi *asi) +{ + struct asi_range_mapping *range, *range_next; + + list_for_each_entry_safe(range, range_next, &asi->mapping_list, list) { + list_del(&range->list); + kfree(range); + } +} + +/* + * Return the range mapping starting at the specified address, or NULL if + * no such range is found. + */ +static struct asi_range_mapping *asi_get_range_mapping(struct asi *asi, + void *ptr) +{ + struct asi_range_mapping *range; + + lockdep_assert_held(&asi->lock); + list_for_each_entry(range, &asi->mapping_list, list) { + if (range->ptr == ptr) + return range; + } + + return NULL; +} + /* * Check if an offset in the address space isolation page-table is valid, * i.e. check that the offset is on a page effectively belonging to the @@ -574,6 +618,7 @@ static int asi_copy_pgd_range(struct asi *asi, int asi_map_range(struct asi *asi, void *ptr, size_t size, enum page_table_level level) { + struct asi_range_mapping *range_mapping; unsigned long addr = (unsigned long)ptr; unsigned long end = addr + ((unsigned long)size); unsigned long flags; @@ -582,8 +627,34 @@ int asi_map_range(struct asi *asi, void *ptr, size_t size, pr_debug("ASI %p: MAP %px/%lx/%d\n", asi, ptr, size, level); spin_lock_irqsave(&asi->lock, flags); + + /* check if the range is already mapped */ + range_mapping = asi_get_range_mapping(asi, ptr); + if (range_mapping) { + pr_debug("ASI %p: MAP %px/%lx/%d already mapped\n", + asi, ptr, size, level); + err = -EBUSY; + goto done; + } + + /* map new range */ + range_mapping = kmalloc(sizeof(*range_mapping), GFP_KERNEL); + if (!range_mapping) { + err = -ENOMEM; + goto done; + } + err = asi_copy_pgd_range(asi, asi->pgd, current->mm->pgd, addr, end, level); + if (err) + goto done; + + INIT_LIST_HEAD(&range_mapping->list); + range_mapping->ptr = ptr; + range_mapping->size = size; + range_mapping->level = level; + list_add(&range_mapping->list, &asi->mapping_list); +done: spin_unlock_irqrestore(&asi->lock, flags); return err; -- 1.7.1