[RFC v2 10/26] mm/asi: Keep track of VA ranges mapped in ASI page-table

From: Alexandre Chartre
Date: Thu Jul 11 2019 - 10:29:11 EST


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