diff options
| author | Helge Deller <deller@gmx.de> | 2009-06-16 16:51:48 -0400 |
|---|---|---|
| committer | Kyle McMartin <kyle@mcmartin.ca> | 2009-07-02 23:34:09 -0400 |
| commit | e82a3b75127188f20c7780bec580e148beb29da7 (patch) | |
| tree | 8a11d55123c20b5341a95941c77eb3d35e4ef9d8 | |
| parent | 84be31be3727d11b2a91781306b642e801c5a379 (diff) | |
parisc: ensure broadcast tlb purge runs single threaded
The TLB flushing functions on hppa, which causes PxTLB broadcasts on the system
bus, needs to be protected by irq-safe spinlocks to avoid irq handlers to deadlock
the kernel. The deadlocks only happened during I/O intensive loads and triggered
pretty seldom, which is why this bug went so long unnoticed.
Signed-off-by: Helge Deller <deller@gmx.de>
[edited to use spin_lock_irqsave on UP as well since we'd been locking there
all this time anyway, --kyle]
Signed-off-by: Kyle McMartin <kyle@mcmartin.ca>
| -rw-r--r-- | arch/parisc/include/asm/tlbflush.h | 14 | ||||
| -rw-r--r-- | arch/parisc/kernel/cache.c | 23 | ||||
| -rw-r--r-- | arch/parisc/kernel/pci-dma.c | 12 |
3 files changed, 30 insertions, 19 deletions
diff --git a/arch/parisc/include/asm/tlbflush.h b/arch/parisc/include/asm/tlbflush.h index 1f6fd4fc05b9..8f1a8100bf2d 100644 --- a/arch/parisc/include/asm/tlbflush.h +++ b/arch/parisc/include/asm/tlbflush.h | |||
| @@ -12,14 +12,12 @@ | |||
| 12 | * N class systems, only one PxTLB inter processor broadcast can be | 12 | * N class systems, only one PxTLB inter processor broadcast can be |
| 13 | * active at any one time on the Merced bus. This tlb purge | 13 | * active at any one time on the Merced bus. This tlb purge |
| 14 | * synchronisation is fairly lightweight and harmless so we activate | 14 | * synchronisation is fairly lightweight and harmless so we activate |
| 15 | * it on all SMP systems not just the N class. We also need to have | 15 | * it on all systems not just the N class. |
| 16 | * preemption disabled on uniprocessor machines, and spin_lock does that | ||
| 17 | * nicely. | ||
| 18 | */ | 16 | */ |
| 19 | extern spinlock_t pa_tlb_lock; | 17 | extern spinlock_t pa_tlb_lock; |
| 20 | 18 | ||
| 21 | #define purge_tlb_start(x) spin_lock(&pa_tlb_lock) | 19 | #define purge_tlb_start(flags) spin_lock_irqsave(&pa_tlb_lock, flags) |
| 22 | #define purge_tlb_end(x) spin_unlock(&pa_tlb_lock) | 20 | #define purge_tlb_end(flags) spin_unlock_irqrestore(&pa_tlb_lock, flags) |
| 23 | 21 | ||
| 24 | extern void flush_tlb_all(void); | 22 | extern void flush_tlb_all(void); |
| 25 | extern void flush_tlb_all_local(void *); | 23 | extern void flush_tlb_all_local(void *); |
| @@ -63,14 +61,16 @@ static inline void flush_tlb_mm(struct mm_struct *mm) | |||
| 63 | static inline void flush_tlb_page(struct vm_area_struct *vma, | 61 | static inline void flush_tlb_page(struct vm_area_struct *vma, |
| 64 | unsigned long addr) | 62 | unsigned long addr) |
| 65 | { | 63 | { |
| 64 | unsigned long flags; | ||
| 65 | |||
| 66 | /* For one page, it's not worth testing the split_tlb variable */ | 66 | /* For one page, it's not worth testing the split_tlb variable */ |
| 67 | 67 | ||
| 68 | mb(); | 68 | mb(); |
| 69 | mtsp(vma->vm_mm->context,1); | 69 | mtsp(vma->vm_mm->context,1); |
| 70 | purge_tlb_start(); | 70 | purge_tlb_start(flags); |
| 71 | pdtlb(addr); | 71 | pdtlb(addr); |
| 72 | pitlb(addr); | 72 | pitlb(addr); |
| 73 | purge_tlb_end(); | 73 | purge_tlb_end(flags); |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | void __flush_tlb_range(unsigned long sid, | 76 | void __flush_tlb_range(unsigned long sid, |
diff --git a/arch/parisc/kernel/cache.c b/arch/parisc/kernel/cache.c index f34082da009a..b6ed34de14e1 100644 --- a/arch/parisc/kernel/cache.c +++ b/arch/parisc/kernel/cache.c | |||
| @@ -397,12 +397,13 @@ EXPORT_SYMBOL(flush_kernel_icache_range_asm); | |||
| 397 | 397 | ||
| 398 | void clear_user_page_asm(void *page, unsigned long vaddr) | 398 | void clear_user_page_asm(void *page, unsigned long vaddr) |
| 399 | { | 399 | { |
| 400 | unsigned long flags; | ||
| 400 | /* This function is implemented in assembly in pacache.S */ | 401 | /* This function is implemented in assembly in pacache.S */ |
| 401 | extern void __clear_user_page_asm(void *page, unsigned long vaddr); | 402 | extern void __clear_user_page_asm(void *page, unsigned long vaddr); |
| 402 | 403 | ||
| 403 | purge_tlb_start(); | 404 | purge_tlb_start(flags); |
| 404 | __clear_user_page_asm(page, vaddr); | 405 | __clear_user_page_asm(page, vaddr); |
| 405 | purge_tlb_end(); | 406 | purge_tlb_end(flags); |
| 406 | } | 407 | } |
| 407 | 408 | ||
| 408 | #define FLUSH_THRESHOLD 0x80000 /* 0.5MB */ | 409 | #define FLUSH_THRESHOLD 0x80000 /* 0.5MB */ |
| @@ -443,20 +444,24 @@ extern void clear_user_page_asm(void *page, unsigned long vaddr); | |||
| 443 | 444 | ||
| 444 | void clear_user_page(void *page, unsigned long vaddr, struct page *pg) | 445 | void clear_user_page(void *page, unsigned long vaddr, struct page *pg) |
| 445 | { | 446 | { |
| 447 | unsigned long flags; | ||
| 448 | |||
| 446 | purge_kernel_dcache_page((unsigned long)page); | 449 | purge_kernel_dcache_page((unsigned long)page); |
| 447 | purge_tlb_start(); | 450 | purge_tlb_start(flags); |
| 448 | pdtlb_kernel(page); | 451 | pdtlb_kernel(page); |
| 449 | purge_tlb_end(); | 452 | purge_tlb_end(flags); |
| 450 | clear_user_page_asm(page, vaddr); | 453 | clear_user_page_asm(page, vaddr); |
| 451 | } | 454 | } |
| 452 | EXPORT_SYMBOL(clear_user_page); | 455 | EXPORT_SYMBOL(clear_user_page); |
| 453 | 456 | ||
| 454 | void flush_kernel_dcache_page_addr(void *addr) | 457 | void flush_kernel_dcache_page_addr(void *addr) |
| 455 | { | 458 | { |
| 459 | unsigned long flags; | ||
| 460 | |||
| 456 | flush_kernel_dcache_page_asm(addr); | 461 | flush_kernel_dcache_page_asm(addr); |
| 457 | purge_tlb_start(); | 462 | purge_tlb_start(flags); |
| 458 | pdtlb_kernel(addr); | 463 | pdtlb_kernel(addr); |
| 459 | purge_tlb_end(); | 464 | purge_tlb_end(flags); |
| 460 | } | 465 | } |
| 461 | EXPORT_SYMBOL(flush_kernel_dcache_page_addr); | 466 | EXPORT_SYMBOL(flush_kernel_dcache_page_addr); |
| 462 | 467 | ||
| @@ -489,8 +494,10 @@ void __flush_tlb_range(unsigned long sid, unsigned long start, | |||
| 489 | if (npages >= 512) /* 2MB of space: arbitrary, should be tuned */ | 494 | if (npages >= 512) /* 2MB of space: arbitrary, should be tuned */ |
| 490 | flush_tlb_all(); | 495 | flush_tlb_all(); |
| 491 | else { | 496 | else { |
| 497 | unsigned long flags; | ||
| 498 | |||
| 492 | mtsp(sid, 1); | 499 | mtsp(sid, 1); |
| 493 | purge_tlb_start(); | 500 | purge_tlb_start(flags); |
| 494 | if (split_tlb) { | 501 | if (split_tlb) { |
| 495 | while (npages--) { | 502 | while (npages--) { |
| 496 | pdtlb(start); | 503 | pdtlb(start); |
| @@ -503,7 +510,7 @@ void __flush_tlb_range(unsigned long sid, unsigned long start, | |||
| 503 | start += PAGE_SIZE; | 510 | start += PAGE_SIZE; |
| 504 | } | 511 | } |
| 505 | } | 512 | } |
| 506 | purge_tlb_end(); | 513 | purge_tlb_end(flags); |
| 507 | } | 514 | } |
| 508 | } | 515 | } |
| 509 | 516 | ||
diff --git a/arch/parisc/kernel/pci-dma.c b/arch/parisc/kernel/pci-dma.c index 7d927eac932b..c07f618ff7da 100644 --- a/arch/parisc/kernel/pci-dma.c +++ b/arch/parisc/kernel/pci-dma.c | |||
| @@ -90,12 +90,14 @@ static inline int map_pte_uncached(pte_t * pte, | |||
| 90 | if (end > PMD_SIZE) | 90 | if (end > PMD_SIZE) |
| 91 | end = PMD_SIZE; | 91 | end = PMD_SIZE; |
| 92 | do { | 92 | do { |
| 93 | unsigned long flags; | ||
| 94 | |||
| 93 | if (!pte_none(*pte)) | 95 | if (!pte_none(*pte)) |
| 94 | printk(KERN_ERR "map_pte_uncached: page already exists\n"); | 96 | printk(KERN_ERR "map_pte_uncached: page already exists\n"); |
| 95 | set_pte(pte, __mk_pte(*paddr_ptr, PAGE_KERNEL_UNC)); | 97 | set_pte(pte, __mk_pte(*paddr_ptr, PAGE_KERNEL_UNC)); |
| 96 | purge_tlb_start(); | 98 | purge_tlb_start(flags); |
| 97 | pdtlb_kernel(orig_vaddr); | 99 | pdtlb_kernel(orig_vaddr); |
| 98 | purge_tlb_end(); | 100 | purge_tlb_end(flags); |
| 99 | vaddr += PAGE_SIZE; | 101 | vaddr += PAGE_SIZE; |
| 100 | orig_vaddr += PAGE_SIZE; | 102 | orig_vaddr += PAGE_SIZE; |
| 101 | (*paddr_ptr) += PAGE_SIZE; | 103 | (*paddr_ptr) += PAGE_SIZE; |
| @@ -168,11 +170,13 @@ static inline void unmap_uncached_pte(pmd_t * pmd, unsigned long vaddr, | |||
| 168 | if (end > PMD_SIZE) | 170 | if (end > PMD_SIZE) |
| 169 | end = PMD_SIZE; | 171 | end = PMD_SIZE; |
| 170 | do { | 172 | do { |
| 173 | unsigned long flags; | ||
| 171 | pte_t page = *pte; | 174 | pte_t page = *pte; |
| 175 | |||
| 172 | pte_clear(&init_mm, vaddr, pte); | 176 | pte_clear(&init_mm, vaddr, pte); |
| 173 | purge_tlb_start(); | 177 | purge_tlb_start(flags); |
| 174 | pdtlb_kernel(orig_vaddr); | 178 | pdtlb_kernel(orig_vaddr); |
| 175 | purge_tlb_end(); | 179 | purge_tlb_end(flags); |
| 176 | vaddr += PAGE_SIZE; | 180 | vaddr += PAGE_SIZE; |
| 177 | orig_vaddr += PAGE_SIZE; | 181 | orig_vaddr += PAGE_SIZE; |
| 178 | pte++; | 182 | pte++; |
