diff options
Diffstat (limited to 'arch/i386/mm/hugetlbpage.c')
-rw-r--r-- | arch/i386/mm/hugetlbpage.c | 112 |
1 files changed, 111 insertions, 1 deletions
diff --git a/arch/i386/mm/hugetlbpage.c b/arch/i386/mm/hugetlbpage.c index 1719a8141f81..34728e4afe48 100644 --- a/arch/i386/mm/hugetlbpage.c +++ b/arch/i386/mm/hugetlbpage.c | |||
@@ -17,6 +17,113 @@ | |||
17 | #include <asm/tlb.h> | 17 | #include <asm/tlb.h> |
18 | #include <asm/tlbflush.h> | 18 | #include <asm/tlbflush.h> |
19 | 19 | ||
20 | static unsigned long page_table_shareable(struct vm_area_struct *svma, | ||
21 | struct vm_area_struct *vma, | ||
22 | unsigned long addr, pgoff_t idx) | ||
23 | { | ||
24 | unsigned long saddr = ((idx - svma->vm_pgoff) << PAGE_SHIFT) + | ||
25 | svma->vm_start; | ||
26 | unsigned long sbase = saddr & PUD_MASK; | ||
27 | unsigned long s_end = sbase + PUD_SIZE; | ||
28 | |||
29 | /* | ||
30 | * match the virtual addresses, permission and the alignment of the | ||
31 | * page table page. | ||
32 | */ | ||
33 | if (pmd_index(addr) != pmd_index(saddr) || | ||
34 | vma->vm_flags != svma->vm_flags || | ||
35 | sbase < svma->vm_start || svma->vm_end < s_end) | ||
36 | return 0; | ||
37 | |||
38 | return saddr; | ||
39 | } | ||
40 | |||
41 | static int vma_shareable(struct vm_area_struct *vma, unsigned long addr) | ||
42 | { | ||
43 | unsigned long base = addr & PUD_MASK; | ||
44 | unsigned long end = base + PUD_SIZE; | ||
45 | |||
46 | /* | ||
47 | * check on proper vm_flags and page table alignment | ||
48 | */ | ||
49 | if (vma->vm_flags & VM_MAYSHARE && | ||
50 | vma->vm_start <= base && end <= vma->vm_end) | ||
51 | return 1; | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | /* | ||
56 | * search for a shareable pmd page for hugetlb. | ||
57 | */ | ||
58 | static void huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud) | ||
59 | { | ||
60 | struct vm_area_struct *vma = find_vma(mm, addr); | ||
61 | struct address_space *mapping = vma->vm_file->f_mapping; | ||
62 | pgoff_t idx = ((addr - vma->vm_start) >> PAGE_SHIFT) + | ||
63 | vma->vm_pgoff; | ||
64 | struct prio_tree_iter iter; | ||
65 | struct vm_area_struct *svma; | ||
66 | unsigned long saddr; | ||
67 | pte_t *spte = NULL; | ||
68 | |||
69 | if (!vma_shareable(vma, addr)) | ||
70 | return; | ||
71 | |||
72 | spin_lock(&mapping->i_mmap_lock); | ||
73 | vma_prio_tree_foreach(svma, &iter, &mapping->i_mmap, idx, idx) { | ||
74 | if (svma == vma) | ||
75 | continue; | ||
76 | |||
77 | saddr = page_table_shareable(svma, vma, addr, idx); | ||
78 | if (saddr) { | ||
79 | spte = huge_pte_offset(svma->vm_mm, saddr); | ||
80 | if (spte) { | ||
81 | get_page(virt_to_page(spte)); | ||
82 | break; | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | |||
87 | if (!spte) | ||
88 | goto out; | ||
89 | |||
90 | spin_lock(&mm->page_table_lock); | ||
91 | if (pud_none(*pud)) | ||
92 | pud_populate(mm, pud, (unsigned long) spte & PAGE_MASK); | ||
93 | else | ||
94 | put_page(virt_to_page(spte)); | ||
95 | spin_unlock(&mm->page_table_lock); | ||
96 | out: | ||
97 | spin_unlock(&mapping->i_mmap_lock); | ||
98 | } | ||
99 | |||
100 | /* | ||
101 | * unmap huge page backed by shared pte. | ||
102 | * | ||
103 | * Hugetlb pte page is ref counted at the time of mapping. If pte is shared | ||
104 | * indicated by page_count > 1, unmap is achieved by clearing pud and | ||
105 | * decrementing the ref count. If count == 1, the pte page is not shared. | ||
106 | * | ||
107 | * called with vma->vm_mm->page_table_lock held. | ||
108 | * | ||
109 | * returns: 1 successfully unmapped a shared pte page | ||
110 | * 0 the underlying pte page is not shared, or it is the last user | ||
111 | */ | ||
112 | int huge_pmd_unshare(struct mm_struct *mm, unsigned long *addr, pte_t *ptep) | ||
113 | { | ||
114 | pgd_t *pgd = pgd_offset(mm, *addr); | ||
115 | pud_t *pud = pud_offset(pgd, *addr); | ||
116 | |||
117 | BUG_ON(page_count(virt_to_page(ptep)) == 0); | ||
118 | if (page_count(virt_to_page(ptep)) == 1) | ||
119 | return 0; | ||
120 | |||
121 | pud_clear(pud); | ||
122 | put_page(virt_to_page(ptep)); | ||
123 | *addr = ALIGN(*addr, HPAGE_SIZE * PTRS_PER_PTE) - HPAGE_SIZE; | ||
124 | return 1; | ||
125 | } | ||
126 | |||
20 | pte_t *huge_pte_alloc(struct mm_struct *mm, unsigned long addr) | 127 | pte_t *huge_pte_alloc(struct mm_struct *mm, unsigned long addr) |
21 | { | 128 | { |
22 | pgd_t *pgd; | 129 | pgd_t *pgd; |
@@ -25,8 +132,11 @@ pte_t *huge_pte_alloc(struct mm_struct *mm, unsigned long addr) | |||
25 | 132 | ||
26 | pgd = pgd_offset(mm, addr); | 133 | pgd = pgd_offset(mm, addr); |
27 | pud = pud_alloc(mm, pgd, addr); | 134 | pud = pud_alloc(mm, pgd, addr); |
28 | if (pud) | 135 | if (pud) { |
136 | if (pud_none(*pud)) | ||
137 | huge_pmd_share(mm, addr, pud); | ||
29 | pte = (pte_t *) pmd_alloc(mm, pud, addr); | 138 | pte = (pte_t *) pmd_alloc(mm, pud, addr); |
139 | } | ||
30 | BUG_ON(pte && !pte_none(*pte) && !pte_huge(*pte)); | 140 | BUG_ON(pte && !pte_none(*pte) && !pte_huge(*pte)); |
31 | 141 | ||
32 | return pte; | 142 | return pte; |