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.char...@oracle.com>
---
 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

Reply via email to