mm, numa: rework do_pages_move
[linux-2.6-microblaze.git] / mm / migrate.c
index 1e59a3f..6068d40 100644 (file)
@@ -1444,141 +1444,103 @@ out:
 }
 
 #ifdef CONFIG_NUMA
-/*
- * Move a list of individual pages
- */
-struct page_to_node {
-       unsigned long addr;
-       struct page *page;
-       int node;
-       int status;
-};
 
-static struct page *new_page_node(struct page *p, unsigned long private,
-               int **result)
+static int store_status(int __user *status, int start, int value, int nr)
 {
-       struct page_to_node *pm = (struct page_to_node *)private;
-
-       while (pm->node != MAX_NUMNODES && pm->page != p)
-               pm++;
+       while (nr-- > 0) {
+               if (put_user(value, status + start))
+                       return -EFAULT;
+               start++;
+       }
 
-       if (pm->node == MAX_NUMNODES)
-               return NULL;
+       return 0;
+}
 
-       *result = &pm->status;
+static int do_move_pages_to_node(struct mm_struct *mm,
+               struct list_head *pagelist, int node)
+{
+       int err;
 
-       if (PageHuge(p))
-               return alloc_huge_page_node(page_hstate(compound_head(p)),
-                                       pm->node);
-       else if (thp_migration_supported() && PageTransHuge(p)) {
-               struct page *thp;
+       if (list_empty(pagelist))
+               return 0;
 
-               thp = alloc_pages_node(pm->node,
-                       (GFP_TRANSHUGE | __GFP_THISNODE) & ~__GFP_RECLAIM,
-                       HPAGE_PMD_ORDER);
-               if (!thp)
-                       return NULL;
-               prep_transhuge_page(thp);
-               return thp;
-       } else
-               return __alloc_pages_node(pm->node,
-                               GFP_HIGHUSER_MOVABLE | __GFP_THISNODE, 0);
+       err = migrate_pages(pagelist, alloc_new_node_page, NULL, node,
+                       MIGRATE_SYNC, MR_SYSCALL);
+       if (err)
+               putback_movable_pages(pagelist);
+       return err;
 }
 
 /*
- * Move a set of pages as indicated in the pm array. The addr
- * field must be set to the virtual address of the page to be moved
- * and the node number must contain a valid target node.
- * The pm array ends with node = MAX_NUMNODES.
+ * Resolves the given address to a struct page, isolates it from the LRU and
+ * puts it to the given pagelist.
+ * Returns -errno if the page cannot be found/isolated or 0 when it has been
+ * queued or the page doesn't need to be migrated because it is already on
+ * the target node
  */
-static int do_move_page_to_node_array(struct mm_struct *mm,
-                                     struct page_to_node *pm,
-                                     int migrate_all)
+static int add_page_for_migration(struct mm_struct *mm, unsigned long addr,
+               int node, struct list_head *pagelist, bool migrate_all)
 {
+       struct vm_area_struct *vma;
+       struct page *page;
+       unsigned int follflags;
        int err;
-       struct page_to_node *pp;
-       LIST_HEAD(pagelist);
 
        down_read(&mm->mmap_sem);
+       err = -EFAULT;
+       vma = find_vma(mm, addr);
+       if (!vma || addr < vma->vm_start || !vma_migratable(vma))
+               goto out;
 
-       /*
-        * Build a list of pages to migrate
-        */
-       for (pp = pm; pp->node != MAX_NUMNODES; pp++) {
-               struct vm_area_struct *vma;
-               struct page *page;
-               struct page *head;
-               unsigned int follflags;
-
-               err = -EFAULT;
-               vma = find_vma(mm, pp->addr);
-               if (!vma || pp->addr < vma->vm_start || !vma_migratable(vma))
-                       goto set_status;
-
-               /* FOLL_DUMP to ignore special (like zero) pages */
-               follflags = FOLL_GET | FOLL_DUMP;
-               if (!thp_migration_supported())
-                       follflags |= FOLL_SPLIT;
-               page = follow_page(vma, pp->addr, follflags);
+       /* FOLL_DUMP to ignore special (like zero) pages */
+       follflags = FOLL_GET | FOLL_DUMP;
+       if (!thp_migration_supported())
+               follflags |= FOLL_SPLIT;
+       page = follow_page(vma, addr, follflags);
 
-               err = PTR_ERR(page);
-               if (IS_ERR(page))
-                       goto set_status;
+       err = PTR_ERR(page);
+       if (IS_ERR(page))
+               goto out;
 
-               err = -ENOENT;
-               if (!page)
-                       goto set_status;
+       err = -ENOENT;
+       if (!page)
+               goto out;
 
-               err = page_to_nid(page);
+       err = 0;
+       if (page_to_nid(page) == node)
+               goto out_putpage;
 
-               if (err == pp->node)
-                       /*
-                        * Node already in the right place
-                        */
-                       goto put_and_set;
+       err = -EACCES;
+       if (page_mapcount(page) > 1 && !migrate_all)
+               goto out_putpage;
 
-               err = -EACCES;
-               if (page_mapcount(page) > 1 &&
-                               !migrate_all)
-                       goto put_and_set;
-
-               if (PageHuge(page)) {
-                       if (PageHead(page)) {
-                               isolate_huge_page(page, &pagelist);
-                               err = 0;
-                               pp->page = page;
-                       }
-                       goto put_and_set;
+       if (PageHuge(page)) {
+               if (PageHead(page)) {
+                       isolate_huge_page(page, pagelist);
+                       err = 0;
                }
+       } else {
+               struct page *head;
 
-               pp->page = compound_head(page);
                head = compound_head(page);
                err = isolate_lru_page(head);
-               if (!err) {
-                       list_add_tail(&head->lru, &pagelist);
-                       mod_node_page_state(page_pgdat(head),
-                               NR_ISOLATED_ANON + page_is_file_cache(head),
-                               hpage_nr_pages(head));
-               }
-put_and_set:
-               /*
-                * Either remove the duplicate refcount from
-                * isolate_lru_page() or drop the page ref if it was
-                * not isolated.
-                */
-               put_page(page);
-set_status:
-               pp->status = err;
-       }
-
-       err = 0;
-       if (!list_empty(&pagelist)) {
-               err = migrate_pages(&pagelist, new_page_node, NULL,
-                               (unsigned long)pm, MIGRATE_SYNC, MR_SYSCALL);
                if (err)
-                       putback_movable_pages(&pagelist);
-       }
+                       goto out_putpage;
 
+               err = 0;
+               list_add_tail(&head->lru, pagelist);
+               mod_node_page_state(page_pgdat(head),
+                       NR_ISOLATED_ANON + page_is_file_cache(head),
+                       hpage_nr_pages(head));
+       }
+out_putpage:
+       /*
+        * Either remove the duplicate refcount from
+        * isolate_lru_page() or drop the page ref if it was
+        * not isolated.
+        */
+       put_page(page);
+out:
        up_read(&mm->mmap_sem);
        return err;
 }
@@ -1593,79 +1555,79 @@ static int do_pages_move(struct mm_struct *mm, nodemask_t task_nodes,
                         const int __user *nodes,
                         int __user *status, int flags)
 {
-       struct page_to_node *pm;
-       unsigned long chunk_nr_pages;
-       unsigned long chunk_start;
-       int err;
-
-       err = -ENOMEM;
-       pm = (struct page_to_node *)__get_free_page(GFP_KERNEL);
-       if (!pm)
-               goto out;
+       int current_node = NUMA_NO_NODE;
+       LIST_HEAD(pagelist);
+       int start, i;
+       int err = 0, err1;
 
        migrate_prep();
 
-       /*
-        * Store a chunk of page_to_node array in a page,
-        * but keep the last one as a marker
-        */
-       chunk_nr_pages = (PAGE_SIZE / sizeof(struct page_to_node)) - 1;
-
-       for (chunk_start = 0;
-            chunk_start < nr_pages;
-            chunk_start += chunk_nr_pages) {
-               int j;
+       for (i = start = 0; i < nr_pages; i++) {
+               const void __user *p;
+               unsigned long addr;
+               int node;
 
-               if (chunk_start + chunk_nr_pages > nr_pages)
-                       chunk_nr_pages = nr_pages - chunk_start;
-
-               /* fill the chunk pm with addrs and nodes from user-space */
-               for (j = 0; j < chunk_nr_pages; j++) {
-                       const void __user *p;
-                       int node;
-
-                       err = -EFAULT;
-                       if (get_user(p, pages + j + chunk_start))
-                               goto out_pm;
-                       pm[j].addr = (unsigned long) p;
-
-                       if (get_user(node, nodes + j + chunk_start))
-                               goto out_pm;
-
-                       err = -ENODEV;
-                       if (node < 0 || node >= MAX_NUMNODES)
-                               goto out_pm;
-
-                       if (!node_state(node, N_MEMORY))
-                               goto out_pm;
-
-                       err = -EACCES;
-                       if (!node_isset(node, task_nodes))
-                               goto out_pm;
+               err = -EFAULT;
+               if (get_user(p, pages + i))
+                       goto out_flush;
+               if (get_user(node, nodes + i))
+                       goto out_flush;
+               addr = (unsigned long)p;
+
+               err = -ENODEV;
+               if (node < 0 || node >= MAX_NUMNODES)
+                       goto out_flush;
+               if (!node_state(node, N_MEMORY))
+                       goto out_flush;
 
-                       pm[j].node = node;
+               err = -EACCES;
+               if (!node_isset(node, task_nodes))
+                       goto out_flush;
+
+               if (current_node == NUMA_NO_NODE) {
+                       current_node = node;
+                       start = i;
+               } else if (node != current_node) {
+                       err = do_move_pages_to_node(mm, &pagelist, current_node);
+                       if (err)
+                               goto out;
+                       err = store_status(status, start, current_node, i - start);
+                       if (err)
+                               goto out;
+                       start = i;
+                       current_node = node;
                }
 
-               /* End marker for this chunk */
-               pm[chunk_nr_pages].node = MAX_NUMNODES;
-
-               /* Migrate this chunk */
-               err = do_move_page_to_node_array(mm, pm,
-                                                flags & MPOL_MF_MOVE_ALL);
-               if (err < 0)
-                       goto out_pm;
+               /*
+                * Errors in the page lookup or isolation are not fatal and we simply
+                * report them via status
+                */
+               err = add_page_for_migration(mm, addr, current_node,
+                               &pagelist, flags & MPOL_MF_MOVE_ALL);
+               if (!err)
+                       continue;
 
-               /* Return status information */
-               for (j = 0; j < chunk_nr_pages; j++)
-                       if (put_user(pm[j].status, status + j + chunk_start)) {
-                               err = -EFAULT;
-                               goto out_pm;
-                       }
-       }
-       err = 0;
+               err = store_status(status, i, err, 1);
+               if (err)
+                       goto out_flush;
 
-out_pm:
-       free_page((unsigned long)pm);
+               err = do_move_pages_to_node(mm, &pagelist, current_node);
+               if (err)
+                       goto out;
+               if (i > start) {
+                       err = store_status(status, start, current_node, i - start);
+                       if (err)
+                               goto out;
+               }
+               current_node = NUMA_NO_NODE;
+       }
+out_flush:
+       /* Make sure we do not overwrite the existing error */
+       err1 = do_move_pages_to_node(mm, &pagelist, current_node);
+       if (!err1)
+               err1 = store_status(status, start, current_node, i - start);
+       if (!err)
+               err = err1;
 out:
        return err;
 }