From f8829caee311207afbc882794bdc5aa0db5caf33 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Sat, 21 Oct 2006 23:17:35 +0100 Subject: [MIPS] Fix aliasing bug in copy_to_user_page / copy_from_user_page The current implementation uses a sequence of a cacheflush and a copy. This is racy in case of a multithreaded debuggee and renders GDB virtually unusable. Aside this fixes a performance hog rendering access to /proc/cmdline very slow and resulting in a enough cache stalls for the 34K AP/SP programming model to make the bare metal code on the non-Linux VPE miss RT deadlines. The main part of this patch was originally written by Ralf Baechle; Atushi Nemoto did the the debugging. Signed-off-by: Atsushi Nemoto Signed-off-by: Ralf Baechle --- arch/mips/mm/init.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 7 deletions(-) (limited to 'arch/mips/mm/init.c') diff --git a/arch/mips/mm/init.c b/arch/mips/mm/init.c index 88b72c9a849..2de4d3c367a 100644 --- a/arch/mips/mm/init.c +++ b/arch/mips/mm/init.c @@ -30,11 +30,34 @@ #include #include #include +#include #include #include #include #include #include +#include + +/* Atomicity and interruptability */ +#ifdef CONFIG_MIPS_MT_SMTC + +#include + +#define ENTER_CRITICAL(flags) \ + { \ + unsigned int mvpflags; \ + local_irq_save(flags);\ + mvpflags = dvpe() +#define EXIT_CRITICAL(flags) \ + evpe(mvpflags); \ + local_irq_restore(flags); \ + } +#else + +#define ENTER_CRITICAL(flags) local_irq_save(flags) +#define EXIT_CRITICAL(flags) local_irq_restore(flags) + +#endif /* CONFIG_MIPS_MT_SMTC */ DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); @@ -80,13 +103,142 @@ unsigned long setup_zero_pages(void) return 1UL << order; } -#ifdef CONFIG_HIGHMEM -pte_t *kmap_pte; -pgprot_t kmap_prot; +/* + * These are almost like kmap_atomic / kunmap_atmic except they take an + * additional address argument as the hint. + */ #define kmap_get_fixmap_pte(vaddr) \ pte_offset_kernel(pmd_offset(pud_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr)), (vaddr)) +#ifdef CONFIG_MIPS_MT_SMTC +static pte_t *kmap_coherent_pte; +static void __init kmap_coherent_init(void) +{ + unsigned long vaddr; + + /* cache the first coherent kmap pte */ + vaddr = __fix_to_virt(FIX_CMAP_BEGIN); + kmap_coherent_pte = kmap_get_fixmap_pte(vaddr); +} +#else +static inline void kmap_coherent_init(void) {} +#endif + +static inline void *kmap_coherent(struct page *page, unsigned long addr) +{ + enum fixed_addresses idx; + unsigned long vaddr, flags, entrylo; + unsigned long old_ctx; + pte_t pte; + int tlbidx; + + inc_preempt_count(); + idx = (addr >> PAGE_SHIFT) & (FIX_N_COLOURS - 1); +#ifdef CONFIG_MIPS_MT_SMTC + idx += FIX_N_COLOURS * smp_processor_id(); +#endif + vaddr = __fix_to_virt(FIX_CMAP_END - idx); + pte = mk_pte(page, PAGE_KERNEL); +#if defined(CONFIG_64BIT_PHYS_ADDR) && defined(CONFIG_CPU_MIPS32_R1) + entrylo = pte.pte_high; +#else + entrylo = pte_val(pte) >> 6; +#endif + + ENTER_CRITICAL(flags); + old_ctx = read_c0_entryhi(); + write_c0_entryhi(vaddr & (PAGE_MASK << 1)); + write_c0_entrylo0(entrylo); + write_c0_entrylo1(entrylo); +#ifdef CONFIG_MIPS_MT_SMTC + set_pte(kmap_coherent_pte - (FIX_CMAP_END - idx), pte); + /* preload TLB instead of local_flush_tlb_one() */ + mtc0_tlbw_hazard(); + tlb_probe(); + tlb_probe_hazard(); + tlbidx = read_c0_index(); + mtc0_tlbw_hazard(); + if (tlbidx < 0) + tlb_write_random(); + else + tlb_write_indexed(); +#else + tlbidx = read_c0_wired(); + write_c0_wired(tlbidx + 1); + write_c0_index(tlbidx); + mtc0_tlbw_hazard(); + tlb_write_indexed(); +#endif + tlbw_use_hazard(); + write_c0_entryhi(old_ctx); + EXIT_CRITICAL(flags); + + return (void*) vaddr; +} + +#define UNIQUE_ENTRYHI(idx) (CKSEG0 + ((idx) << (PAGE_SHIFT + 1))) + +static inline void kunmap_coherent(struct page *page) +{ +#ifndef CONFIG_MIPS_MT_SMTC + unsigned int wired; + unsigned long flags, old_ctx; + + ENTER_CRITICAL(flags); + old_ctx = read_c0_entryhi(); + wired = read_c0_wired() - 1; + write_c0_wired(wired); + write_c0_index(wired); + write_c0_entryhi(UNIQUE_ENTRYHI(wired)); + write_c0_entrylo0(0); + write_c0_entrylo1(0); + mtc0_tlbw_hazard(); + tlb_write_indexed(); + tlbw_use_hazard(); + write_c0_entryhi(old_ctx); + EXIT_CRITICAL(flags); +#endif + dec_preempt_count(); + preempt_check_resched(); +} + +void copy_to_user_page(struct vm_area_struct *vma, + struct page *page, unsigned long vaddr, void *dst, const void *src, + unsigned long len) +{ + if (cpu_has_dc_aliases) { + void *vto = kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK); + memcpy(vto, src, len); + kunmap_coherent(page); + } else + memcpy(dst, src, len); + if ((vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc) + flush_cache_page(vma, vaddr, page_to_pfn(page)); +} + +EXPORT_SYMBOL(copy_to_user_page); + +void copy_from_user_page(struct vm_area_struct *vma, + struct page *page, unsigned long vaddr, void *dst, const void *src, + unsigned long len) +{ + if (cpu_has_dc_aliases) { + void *vfrom = + kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK); + memcpy(dst, vfrom, len); + kunmap_coherent(page); + } else + memcpy(dst, src, len); +} + +EXPORT_SYMBOL(copy_from_user_page); + + +#ifdef CONFIG_HIGHMEM +pte_t *kmap_pte; +pgprot_t kmap_prot; + static void __init kmap_init(void) { unsigned long kmap_vstart; @@ -97,11 +249,12 @@ static void __init kmap_init(void) kmap_prot = PAGE_KERNEL; } +#endif /* CONFIG_HIGHMEM */ -#ifdef CONFIG_32BIT void __init fixrange_init(unsigned long start, unsigned long end, pgd_t *pgd_base) { +#if defined(CONFIG_HIGHMEM) || defined(CONFIG_MIPS_MT_SMTC) pgd_t *pgd; pud_t *pud; pmd_t *pmd; @@ -122,7 +275,7 @@ void __init fixrange_init(unsigned long start, unsigned long end, for (; (k < PTRS_PER_PMD) && (vaddr != end); pmd++, k++) { if (pmd_none(*pmd)) { pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE); - set_pmd(pmd, __pmd(pte)); + set_pmd(pmd, __pmd((unsigned long)pte)); if (pte != pte_offset_kernel(pmd, 0)) BUG(); } @@ -132,9 +285,8 @@ void __init fixrange_init(unsigned long start, unsigned long end, } j = 0; } +#endif } -#endif /* CONFIG_32BIT */ -#endif /* CONFIG_HIGHMEM */ #ifndef CONFIG_NEED_MULTIPLE_NODES extern void pagetable_init(void); @@ -175,6 +327,7 @@ void __init paging_init(void) #ifdef CONFIG_HIGHMEM kmap_init(); #endif + kmap_coherent_init(); max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT; low = max_low_pfn; -- cgit From 99e3b942c62f42c8d5added63305e12372b06daf Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 19 Oct 2006 13:19:59 +0200 Subject: [MIPS] page.h: remove __pa() usages. __pa() was used by virt_to_page() and virt_addr_valid(). These latter are used when kernel is initialised so __pa() is not appropriate, we use virt_to_phys() instead. Futhermore __pa() is going to take care of CKSEG0/XKPHYS address mix for 64 bit kernels. This makes __pa() more complex than virt_to_phys() and this extra work is not needed by virt_to_page() and virt_addr_valid(). Eventually it consolidates virt_to_phys() prototype by making its argument 'const'. this avoids some warnings that was due to some virt_to_page() usages which pass const pointer. Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/mm/init.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'arch/mips/mm/init.c') diff --git a/arch/mips/mm/init.c b/arch/mips/mm/init.c index 2de4d3c367a..304991263f6 100644 --- a/arch/mips/mm/init.c +++ b/arch/mips/mm/init.c @@ -90,9 +90,9 @@ unsigned long setup_zero_pages(void) if (!empty_zero_page) panic("Oh boy, that early out of memory?"); - page = virt_to_page(empty_zero_page); + page = virt_to_page((void *)empty_zero_page); split_page(page, order); - while (page < virt_to_page(empty_zero_page + (PAGE_SIZE << order))) { + while (page < virt_to_page((void *)(empty_zero_page + (PAGE_SIZE << order)))) { SetPageReserved(page); page++; } @@ -448,8 +448,8 @@ void free_init_pages(char *what, unsigned long begin, unsigned long end) unsigned long addr; for (addr = begin; addr < end; addr += PAGE_SIZE) { - ClearPageReserved(virt_to_page(addr)); - init_page_count(virt_to_page(addr)); + ClearPageReserved(virt_to_page((void *)addr)); + init_page_count(virt_to_page((void *)addr)); memset((void *)addr, 0xcc, PAGE_SIZE); free_page(addr); totalram_pages++; -- cgit From a7837b76b6de932c31d0b7c71176ca8d1213a3ce Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 19 Oct 2006 13:20:04 +0200 Subject: [MIPS] setup.c: clean up initrd related code Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/mm/init.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'arch/mips/mm/init.c') diff --git a/arch/mips/mm/init.c b/arch/mips/mm/init.c index 304991263f6..4076963c601 100644 --- a/arch/mips/mm/init.c +++ b/arch/mips/mm/init.c @@ -460,11 +460,6 @@ void free_init_pages(char *what, unsigned long begin, unsigned long end) #ifdef CONFIG_BLK_DEV_INITRD void free_initrd_mem(unsigned long start, unsigned long end) { -#ifdef CONFIG_64BIT - /* Switch from KSEG0 to XKPHYS addresses */ - start = (unsigned long)phys_to_virt(CPHYSADDR(start)); - end = (unsigned long)phys_to_virt(CPHYSADDR(end)); -#endif free_init_pages("initrd memory", start, end); } #endif -- cgit From acd86b8622099c3206e0a1665545ae2318089b9c Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 19 Oct 2006 13:20:05 +0200 Subject: [MIPS] Make free_init_pages() arguments to be physical addresses It allows caller of this function to not care about CKSEG0/XKPHYS address mixes. It's now automatically done by free_init_pages(). We can now safely remove hack needed by 64 bit kernels with CONFIG_BUILD_ELF64=n in free_initmem(). Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/mm/init.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'arch/mips/mm/init.c') diff --git a/arch/mips/mm/init.c b/arch/mips/mm/init.c index 4076963c601..9e29ba9205f 100644 --- a/arch/mips/mm/init.c +++ b/arch/mips/mm/init.c @@ -443,15 +443,18 @@ void __init mem_init(void) } #endif /* !CONFIG_NEED_MULTIPLE_NODES */ -void free_init_pages(char *what, unsigned long begin, unsigned long end) +static void free_init_pages(char *what, unsigned long begin, unsigned long end) { - unsigned long addr; + unsigned long pfn; - for (addr = begin; addr < end; addr += PAGE_SIZE) { - ClearPageReserved(virt_to_page((void *)addr)); - init_page_count(virt_to_page((void *)addr)); - memset((void *)addr, 0xcc, PAGE_SIZE); - free_page(addr); + for (pfn = PFN_UP(begin); pfn < PFN_DOWN(end); pfn++) { + struct page *page = pfn_to_page(pfn); + void *addr = phys_to_virt(PFN_PHYS(pfn)); + + ClearPageReserved(page); + init_page_count(page); + memset(addr, POISON_FREE_INITMEM, PAGE_SIZE); + __free_page(page); totalram_pages++; } printk(KERN_INFO "Freeing %s: %ldk freed\n", what, (end - begin) >> 10); @@ -460,7 +463,9 @@ void free_init_pages(char *what, unsigned long begin, unsigned long end) #ifdef CONFIG_BLK_DEV_INITRD void free_initrd_mem(unsigned long start, unsigned long end) { - free_init_pages("initrd memory", start, end); + free_init_pages("initrd memory", + virt_to_phys((void *)start), + virt_to_phys((void *)end)); } #endif @@ -468,17 +473,13 @@ extern unsigned long prom_free_prom_memory(void); void free_initmem(void) { - unsigned long start, end, freed; + unsigned long freed; freed = prom_free_prom_memory(); if (freed) printk(KERN_INFO "Freeing firmware memory: %ldk freed\n",freed); - start = (unsigned long)(&__init_begin); - end = (unsigned long)(&__init_end); -#ifdef CONFIG_64BIT - start = PAGE_OFFSET | CPHYSADDR(start); - end = PAGE_OFFSET | CPHYSADDR(end); -#endif - free_init_pages("unused kernel memory", start, end); + free_init_pages("unused kernel memory", + __pa_symbol(&__init_begin), + __pa_symbol(&__init_end)); } -- cgit