Commit b95a5c4e authored by Matthew Wilcox's avatar Matthew Wilcox Committed by Linus Torvalds

[PATCH] parisc: Fix N-class SMP

Fix N class SMP

The main fixes are:

 - memory barriers to our space and control register moves
 - fix for the N class merced bus problem which can't have more than
   one PxTLB broadcast outstanding at once
 - make smp_call_function() wait until the function completes

This now boots and runs on a 32MB N4000 in Fort Collins with 2 cpus
and discontig mem support.
Committed-by: default avatarJames Bottomley <jejb@parisc-linux.org>
Committed-by: default avatarRandolph Chung <tausq@parisc-linux.org>
parent 865e59dd
...@@ -33,6 +33,17 @@ int dcache_stride; ...@@ -33,6 +33,17 @@ int dcache_stride;
int icache_stride; int icache_stride;
EXPORT_SYMBOL(dcache_stride); EXPORT_SYMBOL(dcache_stride);
#if defined(CONFIG_SMP)
/* On some machines (e.g. ones with the Merced bus), there can be
* only a single PxTLB broadcast at a time; this must be guaranteed
* by software. We put a spinlock around all TLB flushes to
* ensure this.
*/
spinlock_t pa_tlb_lock = SPIN_LOCK_UNLOCKED;
EXPORT_SYMBOL(pa_tlb_lock);
#endif
struct pdc_cache_info cache_info; struct pdc_cache_info cache_info;
#ifndef CONFIG_PA20 #ifndef CONFIG_PA20
static struct pdc_btlb_info btlb_info; static struct pdc_btlb_info btlb_info;
...@@ -306,3 +317,13 @@ EXPORT_SYMBOL(flush_kernel_dcache_range_asm); ...@@ -306,3 +317,13 @@ EXPORT_SYMBOL(flush_kernel_dcache_range_asm);
EXPORT_SYMBOL(flush_kernel_dcache_page); EXPORT_SYMBOL(flush_kernel_dcache_page);
EXPORT_SYMBOL(flush_data_cache_local); EXPORT_SYMBOL(flush_data_cache_local);
EXPORT_SYMBOL(flush_kernel_icache_range_asm); EXPORT_SYMBOL(flush_kernel_icache_range_asm);
void clear_user_page_asm(void *page, unsigned long vaddr)
{
/* This function is implemented in assembly in pacache.S */
extern void __clear_user_page_asm(void *page, unsigned long vaddr);
purge_tlb_start();
__clear_user_page_asm(page, vaddr);
purge_tlb_end();
}
...@@ -475,9 +475,9 @@ copy_user_page_asm: ...@@ -475,9 +475,9 @@ copy_user_page_asm:
.procend .procend
#endif #endif
.export clear_user_page_asm,code .export __clear_user_page_asm,code
clear_user_page_asm: __clear_user_page_asm:
.proc .proc
.callinfo NO_CALLS .callinfo NO_CALLS
.entry .entry
......
...@@ -104,7 +104,9 @@ static inline int map_pte_uncached(pte_t * pte, ...@@ -104,7 +104,9 @@ static inline int map_pte_uncached(pte_t * pte,
if (!pte_none(*pte)) if (!pte_none(*pte))
printk(KERN_ERR "map_pte_uncached: page already exists\n"); printk(KERN_ERR "map_pte_uncached: page already exists\n");
set_pte(pte, __mk_pte(*paddr_ptr, PAGE_KERNEL_UNC)); set_pte(pte, __mk_pte(*paddr_ptr, PAGE_KERNEL_UNC));
purge_tlb_start();
pdtlb_kernel(orig_vaddr); pdtlb_kernel(orig_vaddr);
purge_tlb_end();
vaddr += PAGE_SIZE; vaddr += PAGE_SIZE;
orig_vaddr += PAGE_SIZE; orig_vaddr += PAGE_SIZE;
(*paddr_ptr) += PAGE_SIZE; (*paddr_ptr) += PAGE_SIZE;
...@@ -179,7 +181,9 @@ static inline void unmap_uncached_pte(pmd_t * pmd, unsigned long vaddr, ...@@ -179,7 +181,9 @@ static inline void unmap_uncached_pte(pmd_t * pmd, unsigned long vaddr,
do { do {
pte_t page = *pte; pte_t page = *pte;
pte_clear(pte); pte_clear(pte);
purge_tlb_start();
pdtlb_kernel(orig_vaddr); pdtlb_kernel(orig_vaddr);
purge_tlb_end();
vaddr += PAGE_SIZE; vaddr += PAGE_SIZE;
orig_vaddr += PAGE_SIZE; orig_vaddr += PAGE_SIZE;
pte++; pte++;
......
...@@ -333,6 +333,7 @@ smp_call_function (void (*func) (void *info), void *info, int retry, int wait) ...@@ -333,6 +333,7 @@ smp_call_function (void (*func) (void *info), void *info, int retry, int wait)
struct smp_call_struct data; struct smp_call_struct data;
unsigned long timeout; unsigned long timeout;
static spinlock_t lock = SPIN_LOCK_UNLOCKED; static spinlock_t lock = SPIN_LOCK_UNLOCKED;
int retries = 0;
if (num_online_cpus() < 2) if (num_online_cpus() < 2)
return 0; return 0;
...@@ -365,21 +366,22 @@ smp_call_function (void (*func) (void *info), void *info, int retry, int wait) ...@@ -365,21 +366,22 @@ smp_call_function (void (*func) (void *info), void *info, int retry, int wait)
/* Send a message to all other CPUs and wait for them to respond */ /* Send a message to all other CPUs and wait for them to respond */
send_IPI_allbutself(IPI_CALL_FUNC); send_IPI_allbutself(IPI_CALL_FUNC);
retry:
/* Wait for response */ /* Wait for response */
timeout = jiffies + HZ; timeout = jiffies + HZ;
while ( (atomic_read (&data.unstarted_count) > 0) && while ( (atomic_read (&data.unstarted_count) > 0) &&
time_before (jiffies, timeout) ) time_before (jiffies, timeout) )
barrier (); barrier ();
if (atomic_read (&data.unstarted_count) > 0) {
printk(KERN_CRIT "SMP CALL FUNCTION TIMED OUT! (cpu=%d), try %d\n",
smp_processor_id(), ++retries);
goto retry;
}
/* We either got one or timed out. Release the lock */ /* We either got one or timed out. Release the lock */
mb(); mb();
smp_call_function_data = NULL; smp_call_function_data = NULL;
if (atomic_read (&data.unstarted_count) > 0) {
printk(KERN_CRIT "SMP CALL FUNCTION TIMED OUT! (cpu=%d)\n",
smp_processor_id());
return -ETIMEDOUT;
}
while (wait && atomic_read (&data.unfinished_count) > 0) while (wait && atomic_read (&data.unfinished_count) > 0)
barrier (); barrier ();
......
...@@ -184,4 +184,22 @@ typedef struct { ...@@ -184,4 +184,22 @@ typedef struct {
#define KERNEL_START (0x10100000 - 0x1000) #define KERNEL_START (0x10100000 - 0x1000)
/* This is for the serialisation of PxTLB broadcasts. At least on the
* N class systems, only one PxTLB inter processor broadcast can be
* active at any one time on the Merced bus. This tlb purge
* synchronisation is fairly lightweight and harmless so we activate
* it on all SMP systems not just the N class. */
#ifdef CONFIG_SMP
extern spinlock_t pa_tlb_lock;
#define purge_tlb_start(x) spin_lock(&pa_tlb_lock)
#define purge_tlb_end(x) spin_unlock(&pa_tlb_lock)
#else
#define purge_tlb_start(x) do { } while(0)
#define purge_tlb_end(x) do { } while (0)
#endif
#endif #endif
...@@ -51,9 +51,12 @@ static inline void flush_tlb_page(struct vm_area_struct *vma, ...@@ -51,9 +51,12 @@ static inline void flush_tlb_page(struct vm_area_struct *vma,
{ {
/* For one page, it's not worth testing the split_tlb variable */ /* For one page, it's not worth testing the split_tlb variable */
mb();
mtsp(vma->vm_mm->context,1); mtsp(vma->vm_mm->context,1);
purge_tlb_start();
pdtlb(addr); pdtlb(addr);
pitlb(addr); pitlb(addr);
purge_tlb_end();
} }
static inline void flush_tlb_range(struct vm_area_struct *vma, static inline void flush_tlb_range(struct vm_area_struct *vma,
...@@ -61,6 +64,7 @@ static inline void flush_tlb_range(struct vm_area_struct *vma, ...@@ -61,6 +64,7 @@ static inline void flush_tlb_range(struct vm_area_struct *vma,
{ {
unsigned long npages; unsigned long npages;
npages = ((end - (start & PAGE_MASK)) + (PAGE_SIZE - 1)) >> PAGE_SHIFT; npages = ((end - (start & PAGE_MASK)) + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
if (npages >= 512) /* XXX arbitrary, should be tuned */ if (npages >= 512) /* XXX arbitrary, should be tuned */
flush_tlb_all(); flush_tlb_all();
...@@ -68,16 +72,20 @@ static inline void flush_tlb_range(struct vm_area_struct *vma, ...@@ -68,16 +72,20 @@ static inline void flush_tlb_range(struct vm_area_struct *vma,
mtsp(vma->vm_mm->context,1); mtsp(vma->vm_mm->context,1);
if (split_tlb) { if (split_tlb) {
purge_tlb_start();
while (npages--) { while (npages--) {
pdtlb(start); pdtlb(start);
pitlb(start); pitlb(start);
start += PAGE_SIZE; start += PAGE_SIZE;
} }
purge_tlb_end();
} else { } else {
purge_tlb_start();
while (npages--) { while (npages--) {
pdtlb(start); pdtlb(start);
start += PAGE_SIZE; start += PAGE_SIZE;
} }
purge_tlb_end();
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment