aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2010-06-18 05:44:31 -0400
committerTejun Heo <tj@kernel.org>2010-06-18 09:07:23 -0400
commit9983b6f0cf8263e51bcf4c8a9dc0c1ef175b3c60 (patch)
treea9b7bacbdf8e30b931c2ebd849104b04b157d48d
parenta92d3ff9e5dbd958d8008a3e7a573e988e370ca3 (diff)
percpu: fix first chunk match in per_cpu_ptr_to_phys()
per_cpu_ptr_to_phys() determines whether the passed in @addr belongs to the first_chunk or not by just matching the address against the address range of the base unit (unit0, used by cpu0). When an adress from another cpu was passed in, it will always determine that the address doesn't belong to the first chunk even when it does. This makes the function return a bogus physical address which may lead to crash. This problem was discovered by Cliff Wickman while investigating a crash during kdump on a SGI UV system. Signed-off-by: Tejun Heo <tj@kernel.org> Reported-by: Cliff Wickman <cpw@sgi.com> Tested-by: Cliff Wickman <cpw@sgi.com> Cc: stable@kernel.org
-rw-r--r--mm/percpu.c31
1 files changed, 28 insertions, 3 deletions
diff --git a/mm/percpu.c b/mm/percpu.c
index 46485e1b26fc..6470e7710231 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -229,8 +229,8 @@ static int __maybe_unused pcpu_page_idx(unsigned int cpu, int page_idx)
229 return pcpu_unit_map[cpu] * pcpu_unit_pages + page_idx; 229 return pcpu_unit_map[cpu] * pcpu_unit_pages + page_idx;
230} 230}
231 231
232static unsigned long __maybe_unused pcpu_chunk_addr(struct pcpu_chunk *chunk, 232static unsigned long pcpu_chunk_addr(struct pcpu_chunk *chunk,
233 unsigned int cpu, int page_idx) 233 unsigned int cpu, int page_idx)
234{ 234{
235 return (unsigned long)chunk->base_addr + pcpu_unit_offsets[cpu] + 235 return (unsigned long)chunk->base_addr + pcpu_unit_offsets[cpu] +
236 (page_idx << PAGE_SHIFT); 236 (page_idx << PAGE_SHIFT);
@@ -978,7 +978,32 @@ bool is_kernel_percpu_address(unsigned long addr)
978 */ 978 */
979phys_addr_t per_cpu_ptr_to_phys(void *addr) 979phys_addr_t per_cpu_ptr_to_phys(void *addr)
980{ 980{
981 if (pcpu_addr_in_first_chunk(addr)) { 981 void __percpu *base = __addr_to_pcpu_ptr(pcpu_base_addr);
982 bool in_first_chunk = false;
983 unsigned long first_start, first_end;
984 unsigned int cpu;
985
986 /*
987 * The following test on first_start/end isn't strictly
988 * necessary but will speed up lookups of addresses which
989 * aren't in the first chunk.
990 */
991 first_start = pcpu_chunk_addr(pcpu_first_chunk, pcpu_first_unit_cpu, 0);
992 first_end = pcpu_chunk_addr(pcpu_first_chunk, pcpu_last_unit_cpu,
993 pcpu_unit_pages);
994 if ((unsigned long)addr >= first_start &&
995 (unsigned long)addr < first_end) {
996 for_each_possible_cpu(cpu) {
997 void *start = per_cpu_ptr(base, cpu);
998
999 if (addr >= start && addr < start + pcpu_unit_size) {
1000 in_first_chunk = true;
1001 break;
1002 }
1003 }
1004 }
1005
1006 if (in_first_chunk) {
982 if ((unsigned long)addr < VMALLOC_START || 1007 if ((unsigned long)addr < VMALLOC_START ||
983 (unsigned long)addr >= VMALLOC_END) 1008 (unsigned long)addr >= VMALLOC_END)
984 return __pa(addr); 1009 return __pa(addr);