]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - mm/hugetlb.c
usbip: tools: fix atoi() on non-null terminated string
[linux.git] / mm / hugetlb.c
index 47566bb0b4b12fb126d01e4dc464737829363454..5c390f5a5207b5c0b4d1d55524d2f655c7edcae1 100644 (file)
@@ -1479,22 +1479,20 @@ static int free_pool_huge_page(struct hstate *h, nodemask_t *nodes_allowed,
 /*
  * Dissolve a given free hugepage into free buddy pages. This function does
  * nothing for in-use (including surplus) hugepages. Returns -EBUSY if the
- * number of free hugepages would be reduced below the number of reserved
- * hugepages.
+ * dissolution fails because a give page is not a free hugepage, or because
+ * free hugepages are fully reserved.
  */
 int dissolve_free_huge_page(struct page *page)
 {
-       int rc = 0;
+       int rc = -EBUSY;
 
        spin_lock(&hugetlb_lock);
        if (PageHuge(page) && !page_count(page)) {
                struct page *head = compound_head(page);
                struct hstate *h = page_hstate(head);
                int nid = page_to_nid(head);
-               if (h->free_huge_pages - h->resv_huge_pages == 0) {
-                       rc = -EBUSY;
+               if (h->free_huge_pages - h->resv_huge_pages == 0)
                        goto out;
-               }
                /*
                 * Move PageHWPoison flag from head page to the raw error page,
                 * which makes any subpages rather than the error page reusable.
@@ -1508,6 +1506,7 @@ int dissolve_free_huge_page(struct page *page)
                h->free_huge_pages_node[nid]--;
                h->max_huge_pages--;
                update_and_free_page(h, head);
+               rc = 0;
        }
 out:
        spin_unlock(&hugetlb_lock);
@@ -3327,8 +3326,8 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
        struct page *page;
        struct hstate *h = hstate_vma(vma);
        unsigned long sz = huge_page_size(h);
-       const unsigned long mmun_start = start; /* For mmu_notifiers */
-       const unsigned long mmun_end   = end;   /* For mmu_notifiers */
+       unsigned long mmun_start = start;       /* For mmu_notifiers */
+       unsigned long mmun_end   = end;         /* For mmu_notifiers */
 
        WARN_ON(!is_vm_hugetlb_page(vma));
        BUG_ON(start & ~huge_page_mask(h));
@@ -3340,6 +3339,11 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
         */
        tlb_remove_check_page_size_change(tlb, sz);
        tlb_start_vma(tlb, vma);
+
+       /*
+        * If sharing possible, alert mmu notifiers of worst case.
+        */
+       adjust_range_if_pmd_sharing_possible(vma, &mmun_start, &mmun_end);
        mmu_notifier_invalidate_range_start(mm, mmun_start, mmun_end);
        address = start;
        for (; address < end; address += sz) {
@@ -3350,6 +3354,10 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
                ptl = huge_pte_lock(h, mm, ptep);
                if (huge_pmd_unshare(mm, &address, ptep)) {
                        spin_unlock(ptl);
+                       /*
+                        * We just unmapped a page of PMDs by clearing a PUD.
+                        * The caller's TLB flush range should cover this area.
+                        */
                        continue;
                }
 
@@ -3432,12 +3440,23 @@ void unmap_hugepage_range(struct vm_area_struct *vma, unsigned long start,
 {
        struct mm_struct *mm;
        struct mmu_gather tlb;
+       unsigned long tlb_start = start;
+       unsigned long tlb_end = end;
+
+       /*
+        * If shared PMDs were possibly used within this vma range, adjust
+        * start/end for worst case tlb flushing.
+        * Note that we can not be sure if PMDs are shared until we try to
+        * unmap pages.  However, we want to make sure TLB flushing covers
+        * the largest possible range.
+        */
+       adjust_range_if_pmd_sharing_possible(vma, &tlb_start, &tlb_end);
 
        mm = vma->vm_mm;
 
-       tlb_gather_mmu(&tlb, mm, start, end);
+       tlb_gather_mmu(&tlb, mm, tlb_start, tlb_end);
        __unmap_hugepage_range(&tlb, vma, start, end, ref_page);
-       tlb_finish_mmu(&tlb, start, end);
+       tlb_finish_mmu(&tlb, tlb_start, tlb_end);
 }
 
 /*
@@ -3502,14 +3521,15 @@ static void unmap_ref_private(struct mm_struct *mm, struct vm_area_struct *vma,
  * cannot race with other handlers or page migration.
  * Keep the pte_same checks anyway to make transition from the mutex easier.
  */
-static int hugetlb_cow(struct mm_struct *mm, struct vm_area_struct *vma,
+static vm_fault_t hugetlb_cow(struct mm_struct *mm, struct vm_area_struct *vma,
                       unsigned long address, pte_t *ptep,
                       struct page *pagecache_page, spinlock_t *ptl)
 {
        pte_t pte;
        struct hstate *h = hstate_vma(vma);
        struct page *old_page, *new_page;
-       int ret = 0, outside_reserve = 0;
+       int outside_reserve = 0;
+       vm_fault_t ret = 0;
        unsigned long mmun_start;       /* For mmu_notifiers */
        unsigned long mmun_end;         /* For mmu_notifiers */
        unsigned long haddr = address & huge_page_mask(h);
@@ -3573,8 +3593,7 @@ static int hugetlb_cow(struct mm_struct *mm, struct vm_area_struct *vma,
                        return 0;
                }
 
-               ret = (PTR_ERR(new_page) == -ENOMEM) ?
-                       VM_FAULT_OOM : VM_FAULT_SIGBUS;
+               ret = vmf_error(PTR_ERR(new_page));
                goto out_release_old;
        }
 
@@ -3677,12 +3696,13 @@ int huge_add_to_page_cache(struct page *page, struct address_space *mapping,
        return 0;
 }
 
-static int hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
-                          struct address_space *mapping, pgoff_t idx,
-                          unsigned long address, pte_t *ptep, unsigned int flags)
+static vm_fault_t hugetlb_no_page(struct mm_struct *mm,
+                       struct vm_area_struct *vma,
+                       struct address_space *mapping, pgoff_t idx,
+                       unsigned long address, pte_t *ptep, unsigned int flags)
 {
        struct hstate *h = hstate_vma(vma);
-       int ret = VM_FAULT_SIGBUS;
+       vm_fault_t ret = VM_FAULT_SIGBUS;
        int anon_rmap = 0;
        unsigned long size;
        struct page *page;
@@ -3745,11 +3765,7 @@ static int hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
 
                page = alloc_huge_page(vma, haddr, 0);
                if (IS_ERR(page)) {
-                       ret = PTR_ERR(page);
-                       if (ret == -ENOMEM)
-                               ret = VM_FAULT_OOM;
-                       else
-                               ret = VM_FAULT_SIGBUS;
+                       ret = vmf_error(PTR_ERR(page));
                        goto out;
                }
                clear_huge_page(page, address, pages_per_huge_page(h));
@@ -3873,12 +3889,12 @@ u32 hugetlb_fault_mutex_hash(struct hstate *h, struct mm_struct *mm,
 }
 #endif
 
-int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
+vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
                        unsigned long address, unsigned int flags)
 {
        pte_t *ptep, entry;
        spinlock_t *ptl;
-       int ret;
+       vm_fault_t ret;
        u32 hash;
        pgoff_t idx;
        struct page *page = NULL;
@@ -4208,7 +4224,7 @@ long follow_hugetlb_page(struct mm_struct *mm, struct vm_area_struct *vma,
                if (absent || is_swap_pte(huge_ptep_get(pte)) ||
                    ((flags & FOLL_WRITE) &&
                      !huge_pte_write(huge_ptep_get(pte)))) {
-                       int ret;
+                       vm_fault_t ret;
                        unsigned int fault_flags = 0;
 
                        if (pte)
@@ -4302,11 +4318,21 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
        pte_t pte;
        struct hstate *h = hstate_vma(vma);
        unsigned long pages = 0;
+       unsigned long f_start = start;
+       unsigned long f_end = end;
+       bool shared_pmd = false;
+
+       /*
+        * In the case of shared PMDs, the area to flush could be beyond
+        * start/end.  Set f_start/f_end to cover the maximum possible
+        * range if PMD sharing is possible.
+        */
+       adjust_range_if_pmd_sharing_possible(vma, &f_start, &f_end);
 
        BUG_ON(address >= end);
-       flush_cache_range(vma, address, end);
+       flush_cache_range(vma, f_start, f_end);
 
-       mmu_notifier_invalidate_range_start(mm, start, end);
+       mmu_notifier_invalidate_range_start(mm, f_start, f_end);
        i_mmap_lock_write(vma->vm_file->f_mapping);
        for (; address < end; address += huge_page_size(h)) {
                spinlock_t *ptl;
@@ -4317,6 +4343,7 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
                if (huge_pmd_unshare(mm, &address, ptep)) {
                        pages++;
                        spin_unlock(ptl);
+                       shared_pmd = true;
                        continue;
                }
                pte = huge_ptep_get(ptep);
@@ -4352,9 +4379,13 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
         * Must flush TLB before releasing i_mmap_rwsem: x86's huge_pmd_unshare
         * may have cleared our pud entry and done put_page on the page table:
         * once we release i_mmap_rwsem, another task can do the final put_page
-        * and that page table be reused and filled with junk.
+        * and that page table be reused and filled with junk.  If we actually
+        * did unshare a page of pmds, flush the range corresponding to the pud.
         */
-       flush_hugetlb_tlb_range(vma, start, end);
+       if (shared_pmd)
+               flush_hugetlb_tlb_range(vma, f_start, f_end);
+       else
+               flush_hugetlb_tlb_range(vma, start, end);
        /*
         * No need to call mmu_notifier_invalidate_range() we are downgrading
         * page table protection not changing it to point to a new page.
@@ -4362,7 +4393,7 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
         * See Documentation/vm/mmu_notifier.rst
         */
        i_mmap_unlock_write(vma->vm_file->f_mapping);
-       mmu_notifier_invalidate_range_end(mm, start, end);
+       mmu_notifier_invalidate_range_end(mm, f_start, f_end);
 
        return pages << h->order;
 }
@@ -4549,12 +4580,40 @@ static bool vma_shareable(struct vm_area_struct *vma, unsigned long addr)
        /*
         * check on proper vm_flags and page table alignment
         */
-       if (vma->vm_flags & VM_MAYSHARE &&
-           vma->vm_start <= base && end <= vma->vm_end)
+       if (vma->vm_flags & VM_MAYSHARE && range_in_vma(vma, base, end))
                return true;
        return false;
 }
 
+/*
+ * Determine if start,end range within vma could be mapped by shared pmd.
+ * If yes, adjust start and end to cover range associated with possible
+ * shared pmd mappings.
+ */
+void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma,
+                               unsigned long *start, unsigned long *end)
+{
+       unsigned long check_addr = *start;
+
+       if (!(vma->vm_flags & VM_MAYSHARE))
+               return;
+
+       for (check_addr = *start; check_addr < *end; check_addr += PUD_SIZE) {
+               unsigned long a_start = check_addr & PUD_MASK;
+               unsigned long a_end = a_start + PUD_SIZE;
+
+               /*
+                * If sharing is possible, adjust start/end if necessary.
+                */
+               if (range_in_vma(vma, a_start, a_end)) {
+                       if (a_start < *start)
+                               *start = a_start;
+                       if (a_end > *end)
+                               *end = a_end;
+               }
+       }
+}
+
 /*
  * Search for a shareable pmd page for hugetlb. In any case calls pmd_alloc()
  * and returns the corresponding pte. While this is not necessary for the
@@ -4652,6 +4711,11 @@ int huge_pmd_unshare(struct mm_struct *mm, unsigned long *addr, pte_t *ptep)
 {
        return 0;
 }
+
+void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma,
+                               unsigned long *start, unsigned long *end)
+{
+}
 #define want_pmd_share()       (0)
 #endif /* CONFIG_ARCH_WANT_HUGE_PMD_SHARE */