diff options
author | David S. Miller <davem@davemloft.net> | 2014-04-29 02:52:11 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-05-04 01:41:19 -0400 |
commit | 70ffc6ebaead783ac8dafb1e87df0039bb043596 (patch) | |
tree | d9ca664c73bce776a703e2bdcd19085f5b6da2c7 /arch | |
parent | d037d16372bbe4d580342bebbb8826821ad9edf0 (diff) |
sparc64: Fix top-level fault handling bugs.
Make get_user_insn() able to cope with huge PMDs.
Next, make do_fault_siginfo() more robust when get_user_insn() can't
actually fetch the instruction. In particular, use the MMU announced
fault address when that happens, instead of calling
compute_effective_address() and computing garbage.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/sparc/mm/fault_64.c | 82 |
1 files changed, 52 insertions, 30 deletions
diff --git a/arch/sparc/mm/fault_64.c b/arch/sparc/mm/fault_64.c index 69bb818fdd79..a8ff0d1a3b69 100644 --- a/arch/sparc/mm/fault_64.c +++ b/arch/sparc/mm/fault_64.c | |||
@@ -96,38 +96,51 @@ static unsigned int get_user_insn(unsigned long tpc) | |||
96 | pte_t *ptep, pte; | 96 | pte_t *ptep, pte; |
97 | unsigned long pa; | 97 | unsigned long pa; |
98 | u32 insn = 0; | 98 | u32 insn = 0; |
99 | unsigned long pstate; | ||
100 | 99 | ||
101 | if (pgd_none(*pgdp)) | 100 | if (pgd_none(*pgdp) || unlikely(pgd_bad(*pgdp))) |
102 | goto outret; | 101 | goto out; |
103 | pudp = pud_offset(pgdp, tpc); | 102 | pudp = pud_offset(pgdp, tpc); |
104 | if (pud_none(*pudp)) | 103 | if (pud_none(*pudp) || unlikely(pud_bad(*pudp))) |
105 | goto outret; | 104 | goto out; |
106 | pmdp = pmd_offset(pudp, tpc); | ||
107 | if (pmd_none(*pmdp)) | ||
108 | goto outret; | ||
109 | 105 | ||
110 | /* This disables preemption for us as well. */ | 106 | /* This disables preemption for us as well. */ |
111 | __asm__ __volatile__("rdpr %%pstate, %0" : "=r" (pstate)); | 107 | local_irq_disable(); |
112 | __asm__ __volatile__("wrpr %0, %1, %%pstate" | ||
113 | : : "r" (pstate), "i" (PSTATE_IE)); | ||
114 | ptep = pte_offset_map(pmdp, tpc); | ||
115 | pte = *ptep; | ||
116 | if (!pte_present(pte)) | ||
117 | goto out; | ||
118 | 108 | ||
119 | pa = (pte_pfn(pte) << PAGE_SHIFT); | 109 | pmdp = pmd_offset(pudp, tpc); |
120 | pa += (tpc & ~PAGE_MASK); | 110 | if (pmd_none(*pmdp) || unlikely(pmd_bad(*pmdp))) |
111 | goto out_irq_enable; | ||
112 | |||
113 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE | ||
114 | if (pmd_trans_huge(*pmdp)) { | ||
115 | if (pmd_trans_splitting(*pmdp)) | ||
116 | goto out_irq_enable; | ||
121 | 117 | ||
122 | /* Use phys bypass so we don't pollute dtlb/dcache. */ | 118 | pa = pmd_pfn(*pmdp) << PAGE_SHIFT; |
123 | __asm__ __volatile__("lduwa [%1] %2, %0" | 119 | pa += tpc & ~HPAGE_MASK; |
124 | : "=r" (insn) | ||
125 | : "r" (pa), "i" (ASI_PHYS_USE_EC)); | ||
126 | 120 | ||
121 | /* Use phys bypass so we don't pollute dtlb/dcache. */ | ||
122 | __asm__ __volatile__("lduwa [%1] %2, %0" | ||
123 | : "=r" (insn) | ||
124 | : "r" (pa), "i" (ASI_PHYS_USE_EC)); | ||
125 | } else | ||
126 | #endif | ||
127 | { | ||
128 | ptep = pte_offset_map(pmdp, tpc); | ||
129 | pte = *ptep; | ||
130 | if (pte_present(pte)) { | ||
131 | pa = (pte_pfn(pte) << PAGE_SHIFT); | ||
132 | pa += (tpc & ~PAGE_MASK); | ||
133 | |||
134 | /* Use phys bypass so we don't pollute dtlb/dcache. */ | ||
135 | __asm__ __volatile__("lduwa [%1] %2, %0" | ||
136 | : "=r" (insn) | ||
137 | : "r" (pa), "i" (ASI_PHYS_USE_EC)); | ||
138 | } | ||
139 | pte_unmap(ptep); | ||
140 | } | ||
141 | out_irq_enable: | ||
142 | local_irq_enable(); | ||
127 | out: | 143 | out: |
128 | pte_unmap(ptep); | ||
129 | __asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate)); | ||
130 | outret: | ||
131 | return insn; | 144 | return insn; |
132 | } | 145 | } |
133 | 146 | ||
@@ -153,7 +166,8 @@ show_signal_msg(struct pt_regs *regs, int sig, int code, | |||
153 | } | 166 | } |
154 | 167 | ||
155 | static void do_fault_siginfo(int code, int sig, struct pt_regs *regs, | 168 | static void do_fault_siginfo(int code, int sig, struct pt_regs *regs, |
156 | unsigned int insn, int fault_code) | 169 | unsigned long fault_addr, unsigned int insn, |
170 | int fault_code) | ||
157 | { | 171 | { |
158 | unsigned long addr; | 172 | unsigned long addr; |
159 | siginfo_t info; | 173 | siginfo_t info; |
@@ -161,10 +175,18 @@ static void do_fault_siginfo(int code, int sig, struct pt_regs *regs, | |||
161 | info.si_code = code; | 175 | info.si_code = code; |
162 | info.si_signo = sig; | 176 | info.si_signo = sig; |
163 | info.si_errno = 0; | 177 | info.si_errno = 0; |
164 | if (fault_code & FAULT_CODE_ITLB) | 178 | if (fault_code & FAULT_CODE_ITLB) { |
165 | addr = regs->tpc; | 179 | addr = regs->tpc; |
166 | else | 180 | } else { |
167 | addr = compute_effective_address(regs, insn, 0); | 181 | /* If we were able to probe the faulting instruction, use it |
182 | * to compute a precise fault address. Otherwise use the fault | ||
183 | * time provided address which may only have page granularity. | ||
184 | */ | ||
185 | if (insn) | ||
186 | addr = compute_effective_address(regs, insn, 0); | ||
187 | else | ||
188 | addr = fault_addr; | ||
189 | } | ||
168 | info.si_addr = (void __user *) addr; | 190 | info.si_addr = (void __user *) addr; |
169 | info.si_trapno = 0; | 191 | info.si_trapno = 0; |
170 | 192 | ||
@@ -239,7 +261,7 @@ static void __kprobes do_kernel_fault(struct pt_regs *regs, int si_code, | |||
239 | /* The si_code was set to make clear whether | 261 | /* The si_code was set to make clear whether |
240 | * this was a SEGV_MAPERR or SEGV_ACCERR fault. | 262 | * this was a SEGV_MAPERR or SEGV_ACCERR fault. |
241 | */ | 263 | */ |
242 | do_fault_siginfo(si_code, SIGSEGV, regs, insn, fault_code); | 264 | do_fault_siginfo(si_code, SIGSEGV, regs, address, insn, fault_code); |
243 | return; | 265 | return; |
244 | } | 266 | } |
245 | 267 | ||
@@ -525,7 +547,7 @@ do_sigbus: | |||
525 | * Send a sigbus, regardless of whether we were in kernel | 547 | * Send a sigbus, regardless of whether we were in kernel |
526 | * or user mode. | 548 | * or user mode. |
527 | */ | 549 | */ |
528 | do_fault_siginfo(BUS_ADRERR, SIGBUS, regs, insn, fault_code); | 550 | do_fault_siginfo(BUS_ADRERR, SIGBUS, regs, address, insn, fault_code); |
529 | 551 | ||
530 | /* Kernel mode? Handle exceptions or die */ | 552 | /* Kernel mode? Handle exceptions or die */ |
531 | if (regs->tstate & TSTATE_PRIV) | 553 | if (regs->tstate & TSTATE_PRIV) |