From: Jérôme Glisse <jgli...@redhat.com>

This patch only introduce core HMM functions for registering a new mirror and
stopping a mirror as well as registering and unregistering a device.

The lifecycle of HMM object is handled differently then one of mmu_notifier
because unlike mmu_notifier there can be concurrent call from both mm code to
HMM code and/or from device driver code to HMM code. Moreover lifetime of HMM
can be uncorrelated from the lifetime of the process that is being mirror.

Signed-off-by: Jérôme Glisse <jgli...@redhat.com>
Signed-off-by: Sherry Cheung <sche...@nvidia.com>
Signed-off-by: Subhash Gutti <sgu...@nvidia.com>
Signed-off-by: Mark Hairgrove <mhairgr...@nvidia.com>
Signed-off-by: John Hubbard <jhubb...@nvidia.com>
Signed-off-by: Jatin Kumar <jaku...@nvidia.com>
---
 MAINTAINERS              |   7 +
 include/linux/hmm.h      | 129 ++++++++++++++++
 include/linux/mm.h       |  11 ++
 include/linux/mm_types.h |  14 ++
 kernel/fork.c            |   2 +
 mm/Kconfig               |  15 ++
 mm/Makefile              |   1 +
 mm/hmm.c                 | 374 +++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 553 insertions(+)
 create mode 100644 include/linux/hmm.h
 create mode 100644 mm/hmm.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a442d32..c5bb62e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4533,6 +4533,13 @@ F:       include/uapi/linux/if_hippi.h
 F:     net/802/hippi.c
 F:     drivers/net/hippi/
 
+HMM - Heterogeneous Memory Management
+M:     Jérôme Glisse <jgli...@redhat.com>
+L:     linux...@kvack.org
+S:     Maintained
+F:     mm/hmm.c
+F:     include/linux/hmm.h
+
 HOST AP DRIVER
 M:     Jouni Malinen <j...@w1.fi>
 L:     hos...@shmoo.com (subscribers-only)
diff --git a/include/linux/hmm.h b/include/linux/hmm.h
new file mode 100644
index 0000000..8eddc15
--- /dev/null
+++ b/include/linux/hmm.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Authors: Jérôme Glisse <jgli...@redhat.com>
+ */
+/* This is a heterogeneous memory management (hmm). In a nutshell this provide
+ * an API to mirror a process address on a device which has its own mmu using
+ * its own page table for the process. It supports everything except special
+ * vma.
+ *
+ * Mandatory hardware features :
+ *   - An mmu with pagetable.
+ *   - Read only flag per cpu page.
+ *   - Page fault ie hardware must stop and wait for kernel to service fault.
+ *
+ * Optional hardware features :
+ *   - Dirty bit per cpu page.
+ *   - Access bit per cpu page.
+ *
+ * The hmm code handle all the interfacing with the core kernel mm code and
+ * provide a simple API. It does support migrating system memory to device
+ * memory and handle migration back to system memory on cpu page fault.
+ *
+ * Migrated memory is considered as swaped from cpu and core mm code point of
+ * view.
+ */
+#ifndef _HMM_H
+#define _HMM_H
+
+#ifdef CONFIG_HMM
+
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/atomic.h>
+#include <linux/mm_types.h>
+#include <linux/mmu_notifier.h>
+#include <linux/workqueue.h>
+#include <linux/mman.h>
+
+
+struct hmm_device;
+struct hmm_mirror;
+struct hmm;
+
+
+/* hmm_device - Each device must register one and only one hmm_device.
+ *
+ * The hmm_device is the link btw HMM and each device driver.
+ */
+
+/* struct hmm_device_operations - HMM device operation callback
+ */
+struct hmm_device_ops {
+       /* release() - mirror must stop using the address space.
+        *
+        * @mirror: The mirror that link process address space with the device.
+        *
+        * This callback is call either on mm destruction or as result to a
+        * call to hmm_mirror_release(). Device driver have to stop all hw
+        * thread and all usage of the address space, it has to dirty all pages
+        * that have been dirty by the device. But it must not clear any entry
+        * from the mirror page table.
+        */
+       void (*release)(struct hmm_mirror *mirror);
+};
+
+/* struct hmm_device - per device HMM structure
+ *
+ * @name: Device name (uniquely identify the device on the system).
+ * @ops: The hmm operations callback.
+ * @mirrors: List of all active mirrors for the device.
+ * @mutex: Mutex protecting mirrors list.
+ *
+ * Each device that want to mirror an address space must register one of this
+ * struct (only once per linux device).
+ */
+struct hmm_device {
+       const char                      *name;
+       const struct hmm_device_ops     *ops;
+       struct list_head                mirrors;
+       struct mutex                    mutex;
+};
+
+int hmm_device_register(struct hmm_device *device);
+int hmm_device_unregister(struct hmm_device *device);
+
+
+/* hmm_mirror - device specific mirroring functions.
+ *
+ * Each device that mirror a process has a uniq hmm_mirror struct associating
+ * the process address space with the device. Same process can be mirrored by
+ * several different devices at the same time.
+ */
+
+/* struct hmm_mirror - per device and per mm HMM structure
+ *
+ * @device: The hmm_device struct this hmm_mirror is associated to.
+ * @hmm: The hmm struct this hmm_mirror is associated to.
+ * @dlist: List of all hmm_mirror for same device.
+ * @mlist: List of all hmm_mirror for same process.
+ *
+ * Each device that want to mirror an address space must register one of this
+ * struct for each of the address space it wants to mirror. Same device can
+ * mirror several different address space. As well same address space can be
+ * mirror by different devices.
+ */
+struct hmm_mirror {
+       struct hmm_device       *device;
+       struct hmm              *hmm;
+       struct list_head        dlist;
+       struct hlist_node       mlist;
+};
+
+int hmm_mirror_register(struct hmm_mirror *mirror, struct hmm_device *device);
+void hmm_mirror_unregister(struct hmm_mirror *mirror);
+
+
+#endif /* CONFIG_HMM */
+#endif
diff --git a/include/linux/mm.h b/include/linux/mm.h
index f80d019..0e054f9 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2208,5 +2208,16 @@ void __init setup_nr_node_ids(void);
 static inline void setup_nr_node_ids(void) {}
 #endif
 
+#ifdef CONFIG_HMM
+static inline void hmm_mm_init(struct mm_struct *mm)
+{
+       mm->hmm = NULL;
+}
+#else /* !CONFIG_HMM */
+static inline void hmm_mm_init(struct mm_struct *mm)
+{
+}
+#endif /* !CONFIG_HMM */
+
 #endif /* __KERNEL__ */
 #endif /* _LINUX_MM_H */
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 6d34aa2..57a3e1c 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -15,6 +15,10 @@
 #include <asm/page.h>
 #include <asm/mmu.h>
 
+#ifdef CONFIG_HMM
+struct hmm;
+#endif
+
 #ifndef AT_VECTOR_SIZE_ARCH
 #define AT_VECTOR_SIZE_ARCH 0
 #endif
@@ -426,6 +430,16 @@ struct mm_struct {
 #ifdef CONFIG_MMU_NOTIFIER
        struct mmu_notifier_mm *mmu_notifier_mm;
 #endif
+#ifdef CONFIG_HMM
+       /*
+        * hmm always register an mmu_notifier we rely on mmu notifier to keep
+        * refcount on mm struct as well as forbiding registering hmm on a
+        * dying mm
+        *
+        * This field is set with mmap_sem old in write mode.
+        */
+       struct hmm *hmm;
+#endif
 #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
        pgtable_t pmd_huge_pte; /* protected by page_table_lock */
 #endif
diff --git a/kernel/fork.c b/kernel/fork.c
index 4dc2dda..0bb9dc4 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -27,6 +27,7 @@
 #include <linux/binfmts.h>
 #include <linux/mman.h>
 #include <linux/mmu_notifier.h>
+#include <linux/hmm.h>
 #include <linux/fs.h>
 #include <linux/mm.h>
 #include <linux/vmacache.h>
@@ -568,6 +569,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, 
struct task_struct *p)
        mm_init_aio(mm);
        mm_init_owner(mm, p);
        mmu_notifier_mm_init(mm);
+       hmm_mm_init(mm);
        clear_tlb_flush_pending(mm);
 #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
        mm->pmd_huge_pte = NULL;
diff --git a/mm/Kconfig b/mm/Kconfig
index 1d1ae6b..b249db0 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -618,3 +618,18 @@ config MAX_STACK_SIZE_MB
          changed to a smaller value in which case that is used.
 
          A sane initial value is 80 MB.
+
+if STAGING
+config HMM
+       bool "Enable heterogeneous memory management (HMM)"
+       depends on MMU
+       select MMU_NOTIFIER
+       select GENERIC_PAGE_TABLE
+       default n
+       help
+         Heterogeneous memory management provide infrastructure for a device
+         to mirror a process address space into an hardware mmu or into any
+         things supporting pagefault like event.
+
+         If unsure, say N to disable hmm.
+endif # STAGING
diff --git a/mm/Makefile b/mm/Makefile
index 3548460..cb2f9ed 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -73,3 +73,4 @@ obj-$(CONFIG_GENERIC_EARLY_IOREMAP) += early_ioremap.o
 obj-$(CONFIG_CMA)      += cma.o
 obj-$(CONFIG_MEMORY_BALLOON) += balloon_compaction.o
 obj-$(CONFIG_PAGE_EXTENSION) += page_ext.o
+obj-$(CONFIG_HMM) += hmm.o
diff --git a/mm/hmm.c b/mm/hmm.c
new file mode 100644
index 0000000..55afec0
--- /dev/null
+++ b/mm/hmm.c
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Authors: Jérôme Glisse <jgli...@redhat.com>
+ */
+/* This is the core code for heterogeneous memory management (HMM). HMM intend
+ * to provide helper for mirroring a process address space on a device as well
+ * as allowing migration of data between system memory and device memory refer
+ * as remote memory from here on out.
+ *
+ * Refer to include/linux/hmm.h for further information on general design.
+ */
+#include <linux/export.h>
+#include <linux/bitmap.h>
+#include <linux/list.h>
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/mmu_notifier.h>
+#include <linux/mm.h>
+#include <linux/hugetlb.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/ksm.h>
+#include <linux/rmap.h>
+#include <linux/swap.h>
+#include <linux/swapops.h>
+#include <linux/mmu_context.h>
+#include <linux/memcontrol.h>
+#include <linux/hmm.h>
+#include <linux/wait.h>
+#include <linux/mman.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+
+#include "internal.h"
+
+static struct srcu_struct srcu;
+
+
+/* struct hmm - per mm_struct HMM states.
+ *
+ * @mirrors: List of all mirror for this mm (one per device).
+ * @mm: The mm struct this hmm is associated with.
+ * @vm_end: Last valid address for this mm (exclusive).
+ * @lock: Serialize the mirror list modifications.
+ * @kref: Reference counter
+ * @mmu_notifier: The mmu_notifier of this mm.
+ *
+ * For each process address space (mm_struct) there is one and only one hmm
+ * struct. hmm functions will redispatch to each devices the change made to
+ * the process address space.
+ */
+struct hmm {
+       struct hlist_head       mirrors;
+       struct mm_struct        *mm;
+       unsigned long           vm_end;
+       spinlock_t              lock;
+       struct kref             kref;
+       struct mmu_notifier     mmu_notifier;
+};
+
+static struct mmu_notifier_ops hmm_notifier_ops;
+
+
+/* hmm - core HMM functions.
+ *
+ * Core HMM functions that deal with all the process mm activities.
+ */
+
+static int hmm_init(struct hmm *hmm)
+{
+       hmm->mm = current->mm;
+       hmm->vm_end = TASK_SIZE;
+       kref_init(&hmm->kref);
+       INIT_HLIST_HEAD(&hmm->mirrors);
+       spin_lock_init(&hmm->lock);
+
+       /* register notifier */
+       hmm->mmu_notifier.ops = &hmm_notifier_ops;
+       return __mmu_notifier_register(&hmm->mmu_notifier, current->mm);
+}
+
+static int hmm_add_mirror(struct hmm *hmm, struct hmm_mirror *mirror)
+{
+       struct hmm_mirror *tmp;
+
+       spin_lock(&hmm->lock);
+       hlist_for_each_entry_rcu(tmp, &hmm->mirrors, mlist)
+               if (tmp->device == mirror->device) {
+                       /* Same device can mirror only once. */
+                       spin_unlock(&hmm->lock);
+                       return -EINVAL;
+               }
+       hlist_add_head(&mirror->mlist, &hmm->mirrors);
+       spin_unlock(&hmm->lock);
+
+       return 0;
+}
+
+static inline struct hmm *hmm_ref(struct hmm *hmm)
+{
+       if (!hmm || !kref_get_unless_zero(&hmm->kref))
+               return NULL;
+       return hmm;
+}
+
+static void hmm_destroy(struct kref *kref)
+{
+       struct hmm *hmm;
+
+       hmm = container_of(kref, struct hmm, kref);
+       BUG_ON(!hlist_empty(&hmm->mirrors));
+
+       down_write(&hmm->mm->mmap_sem);
+       /* A new hmm might have been register before reaching that point. */
+       if (hmm->mm->hmm == hmm)
+               hmm->mm->hmm = NULL;
+       up_write(&hmm->mm->mmap_sem);
+
+       mmu_notifier_unregister(&hmm->mmu_notifier, hmm->mm);
+
+       kfree(hmm);
+}
+
+static inline struct hmm *hmm_unref(struct hmm *hmm)
+{
+       if (hmm)
+               kref_put(&hmm->kref, hmm_destroy);
+       return NULL;
+}
+
+
+/* hmm_notifier - HMM callback for mmu_notifier tracking change to process mm.
+ *
+ * HMM use use mmu notifier to track change made to process address space.
+ */
+static void hmm_notifier_release(struct mmu_notifier *mn, struct mm_struct *mm)
+{
+       struct hmm *hmm;
+       int id;
+
+       /*
+        * The hmm structure can not be free because the mmu_notifier srcu is
+        * read locked thus any concurrent hmm_mirror_unregister that would
+        * free hmm would have to wait on the mmu_notifier srcu.
+        */
+       hmm = container_of(mn, struct hmm, mmu_notifier);
+
+       id = srcu_read_lock(&srcu);
+       spin_lock(&hmm->lock);
+       while (hmm->mirrors.first) {
+               struct hmm_mirror *mirror;
+
+               mirror = hlist_entry(hmm->mirrors.first,
+                                    struct hmm_mirror,
+                                    mlist);
+               hlist_del_init_rcu(&mirror->mlist);
+               spin_unlock(&hmm->lock);
+
+               mirror->device->ops->release(mirror);
+
+               mutex_lock(&mirror->device->mutex);
+               list_del_init(&mirror->dlist);
+               mutex_unlock(&mirror->device->mutex);
+
+               spin_lock(&hmm->lock);
+       }
+       spin_unlock(&hmm->lock);
+
+       srcu_read_unlock(&srcu, id);
+}
+
+static struct mmu_notifier_ops hmm_notifier_ops = {
+       .release                = hmm_notifier_release,
+};
+
+
+/* hmm_mirror - per device mirroring functions.
+ *
+ * Each device that mirror a process has a uniq hmm_mirror struct. A process
+ * can be mirror by several devices at the same time.
+ *
+ * Below are all the functions and their helpers use by device driver to mirror
+ * the process address space. Those functions either deals with updating the
+ * device page table (through hmm callback). Or provide helper functions use by
+ * the device driver to fault in range of memory in the device page table.
+ */
+
+/* hmm_mirror_register() - register mirror against current process for a 
device.
+ *
+ * @mirror: The mirror struct being registered.
+ * @device: The device struct to against which the mirror is registered.
+ * Returns: 0 on success or -ENOMEM, -EINVAL on error.
+ *
+ * Call when device driver want to start mirroring a process address space. The
+ * HMM shim will register mmu_notifier and start monitoring process address
+ * space changes. Hence callback to device driver might happen even before this
+ * function return.
+ *
+ * The task device driver want to mirror must be current !
+ *
+ * Only one mirror per mm and hmm_device can be created, it will return NULL if
+ * the hmm_device already has an hmm_mirror for the the mm.
+ */
+int hmm_mirror_register(struct hmm_mirror *mirror, struct hmm_device *device)
+{
+       struct mm_struct *mm = current->mm;
+       struct hmm *hmm = NULL;
+       int ret = 0;
+
+       /* Sanity checks. */
+       BUG_ON(!device);
+       BUG_ON(!mm);
+
+       /*
+        * Initialize the mirror struct fields, the mlist init and del dance is
+        * necessary to make the error path easier for driver and for hmm.
+        */
+       INIT_HLIST_NODE(&mirror->mlist);
+       INIT_LIST_HEAD(&mirror->dlist);
+       mutex_lock(&device->mutex);
+       mirror->device = device;
+       list_add(&mirror->dlist, &device->mirrors);
+       mutex_unlock(&device->mutex);
+
+       down_write(&mm->mmap_sem);
+
+       hmm = mm->hmm ? hmm_ref(hmm) : NULL;
+       if (hmm == NULL) {
+               /* no hmm registered yet so register one */
+               hmm = kzalloc(sizeof(*mm->hmm), GFP_KERNEL);
+               if (hmm == NULL) {
+                       up_write(&mm->mmap_sem);
+                       ret = -ENOMEM;
+                       goto error;
+               }
+
+               ret = hmm_init(hmm);
+               if (ret) {
+                       up_write(&mm->mmap_sem);
+                       kfree(hmm);
+                       goto error;
+               }
+
+               mm->hmm = hmm;
+       }
+
+       mirror->hmm = hmm;
+       ret = hmm_add_mirror(hmm, mirror);
+       up_write(&mm->mmap_sem);
+       if (ret) {
+               mirror->hmm = NULL;
+               hmm_unref(hmm);
+               goto error;
+       }
+       return 0;
+
+error:
+       mutex_lock(&device->mutex);
+       list_del_init(&mirror->dlist);
+       mutex_unlock(&device->mutex);
+       return ret;
+}
+EXPORT_SYMBOL(hmm_mirror_register);
+
+static void hmm_mirror_release(struct hmm_mirror *mirror)
+{
+       spin_lock(&mirror->hmm->lock);
+       if (!hlist_unhashed(&mirror->mlist)) {
+               hlist_del_init_rcu(&mirror->mlist);
+               spin_unlock(&mirror->hmm->lock);
+               mirror->device->ops->release(mirror);
+
+               mutex_lock(&mirror->device->mutex);
+               list_del_init(&mirror->dlist);
+               mutex_unlock(&mirror->device->mutex);
+       } else
+               spin_unlock(&mirror->hmm->lock);
+}
+
+/* hmm_mirror_unregister() - unregister a mirror.
+ *
+ * @mirror: The mirror that link process address space with the device.
+ *
+ * Driver can call this function when it wants to stop mirroring a process.
+ * This will trigger a call to the ->stop() callback if it did not aleady
+ * happen.
+ */
+void hmm_mirror_unregister(struct hmm_mirror *mirror)
+{
+       int id;
+
+       id = srcu_read_lock(&srcu);
+       hmm_mirror_release(mirror);
+       srcu_read_unlock(&srcu, id);
+
+       /*
+        * Wait for any running method to finish, of course including
+        * ->release() if it was run by hmm_notifier_release instead of us.
+        */
+       synchronize_srcu(&srcu);
+
+       mirror->hmm = hmm_unref(mirror->hmm);
+}
+EXPORT_SYMBOL(hmm_mirror_unregister);
+
+
+/* hmm_device - Each device driver must register one and only one hmm_device
+ *
+ * The hmm_device is the link btw HMM and each device driver.
+ */
+
+/* hmm_device_register() - register a device with HMM.
+ *
+ * @device: The hmm_device struct.
+ * Returns: 0 on success or -EINVAL otherwise.
+ *
+ *
+ * Call when device driver want to register itself with HMM. Device driver can
+ * only register once. It will return a reference on the device thus to release
+ * a device the driver must unreference the device.
+ */
+int hmm_device_register(struct hmm_device *device)
+{
+       /* sanity check */
+       BUG_ON(!device);
+       BUG_ON(!device->name);
+       BUG_ON(!device->ops);
+       BUG_ON(!device->ops->release);
+
+       mutex_init(&device->mutex);
+       INIT_LIST_HEAD(&device->mirrors);
+
+       return 0;
+}
+EXPORT_SYMBOL(hmm_device_register);
+
+/* hmm_device_unregister() - unregister a device with HMM.
+ *
+ * @device: The hmm_device struct.
+ * Returns: 0 on success or -EBUSY otherwise.
+ *
+ * Call when device driver want to unregister itself with HMM. This will check
+ * that there is no any active mirror and returns -EBUSY if so.
+ */
+int hmm_device_unregister(struct hmm_device *device)
+{
+       mutex_lock(&device->mutex);
+       if (!list_empty(&device->mirrors)) {
+               mutex_unlock(&device->mutex);
+               return -EBUSY;
+       }
+       mutex_unlock(&device->mutex);
+       synchronize_srcu(&srcu);
+       return 0;
+}
+EXPORT_SYMBOL(hmm_device_unregister);
+
+
+static int __init hmm_subsys_init(void)
+{
+       return init_srcu_struct(&srcu);
+}
+subsys_initcall(hmm_subsys_init);
-- 
1.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to