parisc: Optimize TLB flush functions based on timing results
Helge Deller
maintainerRFC PATCH 1/1 parisc: Optimize TLB flush functions based on timing results
This patch optimizes the TLB flushing functions flush_tlb_mm() and __flush_tlb_range() in a way that it adds timing routines to calculate the optimal size up to which flushes of small TLB ranges perform faster than when performing a full TLB shootdown.
Here are some timing results for a rp5470 and J5000 machine:
Machine: rp5470, PA8700, 875 MHz
I-cache : 768 KB
D-cache : 1536 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLB
Whole cache flush 692084 cycles, flushing 13214592 bytes 5904392 cycles Setting cache flush threshold to 1512 kB.
Whole TLB flush 40990 cycles, flushing 4096000 bytes 448989 cycles Setting TLB flush threshold to 368 kB.
Machine: J5000/785, PA8500, 440MHz
I-cache : 512 KB
D-cache : 1024 KB (WB, direct mapped)
ITLB entries : 160
DTLB entries : 160 - shared with ITLB
Whole cache flush 268605 cycles, flushing 13214592 bytes 1861037 cycles Setting cache flush threshold to 1024 kB
Whole TLB flush 14131 cycles, flushing 13221888 bytes 769329 cycles Setting TLB flush threshold to 240 kB
arch/parisc/include/asm/tlbflush.h +15 -37
@@ -24,40 +24,25 @@ extern void flush_tlb_all_local(void *);#define smp_flush_tlb_all () flush_tlb_all() -/* -* flush_tlb_mm() -* -* XXX This code is NOT valid for HP-UX compatibility processes, -* (although it will probably work 99% of the time). HP-UX -* processes are free to play with the space id's and save them -* over long periods of time, etc. so we have to preserve the -* space and just flush the entire tlb. We need to check the -* personality in order to do that, but the personality is not -* currently being set correctly. -* -* Of course, Linux processes could do the same thing, but -* we don't support that (and the compilers, dynamic linker, -* etc. do not do that). -*/ +int __flush_tlb_range (unsigned long sid, +unsigned long start,unsigned long end); + +#define flush_tlb_range (vma, start, end) \ + __flush_tlb_range((vma)->vm_mm->context, start, end) + +#define flush_tlb_kernel_range (start, end) \ + __flush_tlb_range(0, start, end)static inline void flush_tlb_mm (struct mm_struct *mm) { -BUG_ON (mm == &init_mm);/* Should never happen */ - -#if 1 || defined(CONFIG_SMP ) -flush_tlb_all (); -#else -/* FIXME: currently broken, causing space id and protection ids -* to go out of sync, resulting in faults on userspace accesses. -*/ -if (mm) { -if (mm->context !=0 ) -free_sid (mm->context); - mm->context =alloc_sid (); -if (mm == current->active_mm) -load_context (mm->context); +struct vm_area_struct *vma; + +for (vma = mm->mmap; vma; vma = vma->vm_next) { +/* exit loop if flush_tlb_all() was called. */ +if (unlikely (__flush_tlb_range (mm->context, + vma->vm_start, vma->vm_end))) +return ; } -#endif } static inline void flush_tlb_page(struct vm_area_struct *vma,
@@ -76,11 +61,4 @@ static inline void flush_tlb_page(struct vm_area_struct *vma, purge_tlb_end(flags); } -void __flush_tlb_range (unsigned long sid, -unsigned long start,unsigned long end); - -#define flush_tlb_range (vma,start,end) __flush_tlb_range((vma)->vm_mm->context,start,end) - -#define flush_tlb_kernel_range (start, end) __flush_tlb_range(0,start,end) -#endif
arch/parisc/kernel/cache.c +48 -10
106 lines not on this page · raw message
Helge Deller
maintainerQuoted 24 lines · Helge Deller
This patch optimizes the TLB flushing functions
flush_tlb_mm()and__flush_tlb_range()in a way that it adds timing routines to calculate the optimal size up to which flushes of small TLB ranges perform faster than when performing a full TLB shootdown.Here are some timing results for a rp5470 and J5000 machine:
Machine: rp5470, PA8700, 875 MHz
I-cache : 768 KB
D-cache : 1536 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 692084 cycles, flushing 13214592 bytes 5904392 cycles Setting cache flush threshold to 1512 kB.
Whole TLB flush 40990 cycles, flushing 4096000 bytes 448989 cycles Setting TLB flush threshold to 368 kB.Machine: J5000/785, PA8500, 440MHz
I-cache : 512 KB
D-cache : 1024 KB (WB, direct mapped)
ITLB entries : 160
DTLB entries : 160 - shared with ITLB
Quoted 4 lines
Whole cache flush 268605 cycles, flushing 13214592 bytes 1861037 cycles Setting cache flush threshold to 1024 kB
Whole TLB flush 14131 cycles, flushing 13221888 bytes 769329 cycles Setting TLB flush threshold to 240 kB
One more machine:
Machine: C8000, PA8800, 2 CPUs each 900 MHz
I-cache : 32768 KB
D-cache : 32768 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLB
Whole cache flush 4298354 cycles, flushing 13214592 bytes 1440513 cycles Setting cache flush threshold to 32768 kB
Whole TLB flush 15625 cycles, flushing 13221888 bytes 781230 cycles Setting TLB flush threshold to 260 kB
Quoted 188 lines
Signed-off-by Helge Deller <[email protected]>arch/parisc/include/asm/tlbflush.h +15
-37@@ -24,40 +24,25 @@ extern void flush_tlb_all_local(void *);#define smp_flush_tlb_all () flush_tlb_all() -/* -* flush_tlb_mm() -* -* XXX This code is NOT valid for HP-UX compatibility processes, -* (although it will probably work 99% of the time). HP-UX -* processes are free to play with the space id's and save them -* over long periods of time, etc. so we have to preserve the -* space and just flush the entire tlb. We need to check the -* personality in order to do that, but the personality is not -* currently being set correctly. -* -* Of course, Linux processes could do the same thing, but -* we don't support that (and the compilers, dynamic linker, -* etc. do not do that). -*/ +int __flush_tlb_range (unsigned long sid, +unsigned long start,unsigned long end); + +#define flush_tlb_range (vma, start, end) \ + __flush_tlb_range((vma)->vm_mm->context, start, end) + +#define flush_tlb_kernel_range (start, end) \ + __flush_tlb_range(0, start, end)static inline void flush_tlb_mm (struct mm_struct *mm) { -BUG_ON (mm == &init_mm);/* Should never happen */ - -#if 1 || defined(CONFIG_SMP ) -flush_tlb_all (); -#else -/* FIXME: currently broken, causing space id and protection ids -* to go out of sync, resulting in faults on userspace accesses. -*/ -if (mm) { -if (mm->context !=0 ) -free_sid (mm->context); - mm->context =alloc_sid (); -if (mm == current->active_mm) -load_context (mm->context); +struct vm_area_struct *vma; + +for (vma = mm->mmap; vma; vma = vma->vm_next) { +/* exit loop if flush_tlb_all() was called. */ +if (unlikely (__flush_tlb_range (mm->context, + vma->vm_start, vma->vm_end))) +return ; } -#endif } static inline void flush_tlb_page(struct vm_area_struct *vma,@@ -76,11 +61,4 @@ static inline void flush_tlb_page(struct vm_area_struct *vma, purge_tlb_end(flags); } -void __flush_tlb_range (unsigned long sid, -unsigned long start,unsigned long end); - -#define flush_tlb_range (vma,start,end) __flush_tlb_range((vma)->vm_mm->context,start,end) - -#define flush_tlb_kernel_range (start, end) __flush_tlb_range(0,start,end) -#endif arch/parisc/kernel/cache.c +48
-10@@ -342,12 +342,15 @@ EXPORT_SYMBOL(flush_data_cache_local);EXPORT_SYMBOL (flush_kernel_icache_range_asm);#define FLUSH_THRESHOLD 0x80000/* 0.5MB */ -int parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; +static unsigned long parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; + +#define FLUSH_TLB_THRESHOLD (2*1024*1024)/* 2MB initial TLB threshold */ +static unsigned long parisc_tlb_flush_threshold__read_mostly =FLUSH_TLB_THRESHOLD ;void __init parisc_setup_cache_timing (void ) {unsigned long rangetime, alltime; -unsigned long size; +unsigned long size, start; alltime =mfctl (16 );flush_data_cache ();@@ -364,14 +367,42 @@ void __init parisc_setup_cache_timing(void)/* Racy, but if we see an intermediate value, it's ok too... */ parisc_cache_flush_threshold = size * alltime / rangetime; - parisc_cache_flush_threshold = (parisc_cache_flush_threshold +L1_CACHE_BYTES -1 ) &~ (L1_CACHE_BYTES -1 ); + parisc_cache_flush_threshold =L1_CACHE_ALIGN (parisc_cache_flush_threshold);if (!parisc_cache_flush_threshold) parisc_cache_flush_threshold =FLUSH_THRESHOLD ;if (parisc_cache_flush_threshold > cache_info.dc_size) parisc_cache_flush_threshold = cache_info.dc_size; -printk (KERN_INFO "Setting cache flush threshold to %x (%d CPUs online)\n" , parisc_cache_flush_threshold,num_online_cpus ()); +printk (KERN_INFO "Setting cache flush threshold to %lu kB\n" , + parisc_cache_flush_threshold/1024 ); + +/* calculate TLB flush threshold */ + + alltime =mfctl (16 ); +flush_tlb_all (); + alltime =mfctl (16 ) - alltime; + + size =PAGE_SIZE ; + start = (unsigned long ) _text; + rangetime =mfctl (16 ); +while (start < (unsigned long ) _end) { +flush_tlb_kernel_range (start, start +PAGE_SIZE ); + start +=PAGE_SIZE ; + size +=PAGE_SIZE ; + } + rangetime =mfctl (16 ) - rangetime; + +printk (KERN_DEBUG "Whole TLB flush %lu cycles, flushing %lu bytes %lu cycles\n" , + alltime, size, rangetime); + + parisc_tlb_flush_threshold = size * alltime / rangetime; + parisc_tlb_flush_threshold =PAGE_ALIGN (parisc_tlb_flush_threshold); +if (!parisc_tlb_flush_threshold) + parisc_tlb_flush_threshold =FLUSH_TLB_THRESHOLD ; + +printk (KERN_INFO "Setting TLB flush threshold to %lu kB\n" , + parisc_tlb_flush_threshold/1024 ); }extern void purge_kernel_dcache_page_asm (unsigned long );@@ -418,32 +449,39 @@ void purge_tlb_entries(struct mm_struct *mm, unsigned long addr) }EXPORT_SYMBOL (purge_tlb_entries); -void __flush_tlb_range (unsigned long sid,unsigned long start, +/* __flush_tlb_range() +* +* returns 1 if all TLBs were flushed. +*/ +int __flush_tlb_range (unsigned long sid,unsigned long start,unsigned long end) { -unsigned long npages; +unsigned long size; - npages = ((end - (start &PAGE_MASK )) + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; -if (npages >=512 )/* 2MB of space: arbitrary, should be tuned */ + size = (end - start); +if (size >= parisc_tlb_flush_threshold) {flush_tlb_all (); +return 1 ; + }else {unsigned long flags;purge_tlb_start (flags);mtsp (sid,1 );if (split_tlb) { -while (npages--) { +while (start < end) {pdtlb (start);pitlb (start); start +=PAGE_SIZE ; } }else { -while (npages--) { +while (start < end) {pdtlb (start); start +=PAGE_SIZE ; } }purge_tlb_end (flags); +return 0 ; } }
So, the threshold for the whole cache flush is roughly the size of the cache.
Quoted 248 lines · Helge Deller
Quoted 24 lines · Helge Deller
This patch optimizes the TLB flushing functions
flush_tlb_mm()and__flush_tlb_range()in a way that it adds timing routines to calculate the optimal size up to which flushes of small TLB ranges perform faster than when performing a full TLB shootdown.Here are some timing results for a rp5470 and J5000 machine:
Machine: rp5470, PA8700, 875 MHz
I-cache : 768 KB
D-cache : 1536 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 692084 cycles, flushing 13214592 bytes 5904392 cycles Setting cache flush threshold to 1512 kB.
Whole TLB flush 40990 cycles, flushing 4096000 bytes 448989 cycles Setting TLB flush threshold to 368 kB.Machine: J5000/785, PA8500, 440MHz
I-cache : 512 KB
D-cache : 1024 KB (WB, direct mapped)
ITLB entries : 160
DTLB entries : 160 - shared with ITLBQuoted 4 lines
Whole cache flush 268605 cycles, flushing 13214592 bytes 1861037 cycles Setting cache flush threshold to 1024 kB
Whole TLB flush 14131 cycles, flushing 13221888 bytes 769329 cycles Setting TLB flush threshold to 240 kBOne more machine:
Machine: C8000, PA8800, 2 CPUs each 900 MHz
I-cache : 32768 KB
D-cache : 32768 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 4298354 cycles, flushing 13214592 bytes 1440513 cycles Setting cache flush threshold to 32768 kB
Whole TLB flush 15625 cycles, flushing 13221888 bytes 781230 cycles Setting TLB flush threshold to 260 kBQuoted 201 lines
Signed-off-by Helge Deller <[email protected]>arch/parisc/include/asm/tlbflush.h +15
-34@@ -24,40 +24,25 @@ extern void flush_tlb_all_local(void *);#define smp_flush_tlb_all () flush_tlb_all() -/* -* flush_tlb_mm() -* -* XXX This code is NOT valid for HP-UX compatibility processes, -* (although it will probably work 99% of the time). HP-UX -* processes are free to play with the space id's and save them -* over long periods of time, etc. so we have to preserve the -* space and just flush the entire tlb. We need to check the -* personality in order to do that, but the personality is not -* currently being set correctly. -* -* Of course, Linux processes could do the same thing, but -* we don't support that (and the compilers, dynamic linker, -* etc. do not do that). -*/ +int __flush_tlb_range (unsigned long sid, +unsigned long start,unsigned long end); + +#define flush_tlb_range (vma, start, end) \ + __flush_tlb_range((vma)->vm_mm->context, start, end) + +#define flush_tlb_kernel_range (start, end) \ + __flush_tlb_range(0, start, end)static inline void flush_tlb_mm (struct mm_struct *mm) { -BUG_ON (mm == &init_mm);/* Should never happen */ - -#if 1 || defined(CONFIG_SMP ) -flush_tlb_all (); -#else -/* FIXME: currently broken, causing space id and protection ids -* to go out of sync, resulting in faults on userspace accesses. -*/ -if (mm) { -if (mm->context !=0 ) -free_sid (mm->context); - mm->context =alloc_sid (); -if (mm == current->active_mm) -load_context (mm->context); +struct vm_area_struct *vma; + +for (vma = mm->mmap; vma; vma = vma->vm_next) { +/* exit loop if flush_tlb_all() was called. */ +if (unlikely (__flush_tlb_range (mm->context, + vma->vm_start, vma->vm_end))) +return ; } -#endif } static inline void flush_tlb_page(struct vm_area_struct *vma,@@ -76,11 +61,4 @@ static inline void flush_tlb_page(structvm_area_struct *vma,purge_tlb_end (flags ); } -void __flush_tlb_range (unsigned long sid, -unsigned long start,unsigned long end); - -#define flush_tlb_range (vma,start,end)__flush_tlb_range((vma)->vm_mm->context,start,end)
arch/parisc/include/asm/tlbflush.h +0
-2- -#define flush_tlb_kernel_range (start, end)
__flush_tlb_range(0,start,end)arch/parisc/include/asm/tlbflush.h +0
-1-#endif arch/parisc/kernel/cache.c +3
-1@@ -342,12 +342,15 @@ EXPORT_SYMBOL(flush_data_cache_local);EXPORT_SYMBOL (flush_kernel_icache_range_asm);#define FLUSH_THRESHOLD 0x80000/* 0.5MB */ -int parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; +static unsigned long parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; + +#define FLUSH_TLB_THRESHOLD (2*1024*1024)/* 2MB initial TLB threshold */
+static unsigned longparisc_tlb_flush_threshold__read_mostly=FLUSH_TLB_THRESHOLD;void __init parisc_setup_cache_timing(void) { unsigned long rangetime, alltime;arch/parisc/kernel/cache.c +22
-3-unsigned long size; +unsigned long size, start; alltime =mfctl (16 );flush_data_cache ();@@ -364,14 +367,42 @@ void __init parisc_setup_cache_timing(void)/* Racy, but if we see an intermediate value, it's ok too... */ parisc_cache_flush_threshold = size * alltime / rangetime; - parisc_cache_flush_threshold = (parisc_cache_flush_threshold + L1_CACHE_BYTES - 1) &~ (L1_CACHE_BYTES - 1); + parisc_cache_flush_threshold =L1_CACHE_ALIGN (parisc_cache_flush_threshold);if (!parisc_cache_flush_threshold) parisc_cache_flush_threshold =FLUSH_THRESHOLD ;if (parisc_cache_flush_threshold > cache_info.dc_size) parisc_cache_flush_threshold = cache_info.dc_size; - printk(KERN_INFO "Setting cache flush threshold to %x (%d CPUs online)\n", parisc_cache_flush_threshold, num_online_cpus()); + printk(KERN_INFO "Setting cache flush threshold to %lu kB\n" , + parisc_cache_flush_threshold/1024 ); + +/* calculate TLB flush threshold */ + + alltime =mfctl (16 ); +flush_tlb_all (); + alltime =mfctl (16 ) - alltime; + + size =PAGE_SIZE ; + start = (unsigned long ) _text; + rangetime =mfctl (16 ); +while (start < (unsigned long ) _end) { +flush_tlb_kernel_range (start, start +PAGE_SIZE ); + start +=PAGE_SIZE ; + size +=PAGE_SIZE ; + } + rangetime =mfctl (16 ) - rangetime; + + printk(KERN_DEBUG "Whole TLB flush %lu cycles, flushing %lubytes %lu cycles\n",
arch/parisc/kernel/cache.c +4
-0+ alltime, size, rangetime); + + parisc_tlb_flush_threshold = size * alltime / rangetime; + parisc_tlb_flush_threshold =
PAGE_ALIGN(parisc_tlb_flush_threshold);arch/parisc/kernel/cache.c +18
-6+if (!parisc_tlb_flush_threshold) + parisc_tlb_flush_threshold =FLUSH_TLB_THRESHOLD ; + +printk (KERN_INFO "Setting TLB flush threshold to %lu kB\n" , + parisc_tlb_flush_threshold/1024 ); }extern void purge_kernel_dcache_page_asm (unsigned long );@@ -418,32 +449,39 @@ void purge_tlb_entries(struct mm_struct *mm, unsigned long addr) }EXPORT_SYMBOL (purge_tlb_entries); -void __flush_tlb_range (unsigned long sid,unsigned long start, +/* __flush_tlb_range() +* +* returns 1 if all TLBs were flushed. +*/ +int __flush_tlb_range (unsigned long sid,unsigned long start,unsigned long end) { -unsigned long npages; +unsigned long size; - npages = ((end - (start &PAGE_MASK )) + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; -if (npages >=512 )/* 2MB of space: arbitrary, should be tuned */ + size = (end - start); +if (size >= parisc_tlb_flush_threshold) {flush_tlb_all (); +return 1 ; + }else {unsigned long flags;purge_tlb_start (flags);mtsp (sid,1 );if (split_tlb) { -while (npages--) { +while (start < end) {pdtlb (start);pitlb (start); start +=PAGE_SIZE ; } }else { -while (npages--) { +while (start < end) {pdtlb (start); start +=PAGE_SIZE ; } }purge_tlb_end (flags); +return 0 ; }}
Signature · 1 line
John David Anglin [email protected]
Helge Deller
maintainerQuoted 1 line · John David Anglin
So, the threshold for the whole cache flush is roughly the size of the cache.
It's exactly the size of the cache, because of this line in arch/parisc/kernel/cache.c:
if (parisc_cache_flush_threshold > cache_info.dc_size)
parisc_cache_flush_threshold = cache_info.dc_size;
So, if the calculated threshold is bigger than the cache, the code chooses the cache size.
Helge
Quoted 239 lines · Helge Deller
Quoted 235 lines · Helge Deller
Quoted 24 lines
This patch optimizes the TLB flushing functions
flush_tlb_mm()and__flush_tlb_range()in a way that it adds timing routines to calculate the optimal size up to which flushes of small TLB ranges perform faster than when performing a full TLB shootdown.Here are some timing results for a rp5470 and J5000 machine:
Machine: rp5470, PA8700, 875 MHz
I-cache : 768 KB
D-cache : 1536 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 692084 cycles, flushing 13214592 bytes 5904392 cycles Setting cache flush threshold to 1512 kB.
Whole TLB flush 40990 cycles, flushing 4096000 bytes 448989 cycles Setting TLB flush threshold to 368 kB.Machine: J5000/785, PA8500, 440MHz
I-cache : 512 KB
D-cache : 1024 KB (WB, direct mapped)
ITLB entries : 160
DTLB entries : 160 - shared with ITLBQuoted 4 lines
Whole cache flush 268605 cycles, flushing 13214592 bytes 1861037 cycles Setting cache flush threshold to 1024 kB
Whole TLB flush 14131 cycles, flushing 13221888 bytes 769329 cycles Setting TLB flush threshold to 240 kBOne more machine:
Machine: C8000, PA8800, 2 CPUs each 900 MHz
I-cache : 32768 KB
D-cache : 32768 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 4298354 cycles, flushing 13214592 bytes 1440513 cycles Setting cache flush threshold to 32768 kB
Whole TLB flush 15625 cycles, flushing 13221888 bytes 781230 cycles Setting TLB flush threshold to 260 kBQuoted 188 lines
Signed-off-by Helge Deller <[email protected]>arch/parisc/include/asm/tlbflush.h +15
-37@@ -24,40 +24,25 @@ extern void flush_tlb_all_local(void *);#define smp_flush_tlb_all () flush_tlb_all() -/* -* flush_tlb_mm() -* -* XXX This code is NOT valid for HP-UX compatibility processes, -* (although it will probably work 99% of the time). HP-UX -* processes are free to play with the space id's and save them -* over long periods of time, etc. so we have to preserve the -* space and just flush the entire tlb. We need to check the -* personality in order to do that, but the personality is not -* currently being set correctly. -* -* Of course, Linux processes could do the same thing, but -* we don't support that (and the compilers, dynamic linker, -* etc. do not do that). -*/ +int __flush_tlb_range (unsigned long sid, +unsigned long start,unsigned long end); + +#define flush_tlb_range (vma, start, end) \ + __flush_tlb_range((vma)->vm_mm->context, start, end) + +#define flush_tlb_kernel_range (start, end) \ + __flush_tlb_range(0, start, end)static inline void flush_tlb_mm (struct mm_struct *mm) { -BUG_ON (mm == &init_mm);/* Should never happen */ - -#if 1 || defined(CONFIG_SMP ) -flush_tlb_all (); -#else -/* FIXME: currently broken, causing space id and protection ids -* to go out of sync, resulting in faults on userspace accesses. -*/ -if (mm) { -if (mm->context !=0 ) -free_sid (mm->context); - mm->context =alloc_sid (); -if (mm == current->active_mm) -load_context (mm->context); +struct vm_area_struct *vma; + +for (vma = mm->mmap; vma; vma = vma->vm_next) { +/* exit loop if flush_tlb_all() was called. */ +if (unlikely (__flush_tlb_range (mm->context, + vma->vm_start, vma->vm_end))) +return ; } -#endif } static inline void flush_tlb_page(struct vm_area_struct *vma,@@ -76,11 +61,4 @@ static inline void flush_tlb_page(struct vm_area_struct *vma, purge_tlb_end(flags); } -void __flush_tlb_range (unsigned long sid, -unsigned long start,unsigned long end); - -#define flush_tlb_range (vma,start,end) __flush_tlb_range((vma)->vm_mm->context,start,end) - -#define flush_tlb_kernel_range (start, end) __flush_tlb_range(0,start,end) -#endif arch/parisc/kernel/cache.c +48
-10@@ -342,12 +342,15 @@ EXPORT_SYMBOL(flush_data_cache_local);EXPORT_SYMBOL (flush_kernel_icache_range_asm);#define FLUSH_THRESHOLD 0x80000/* 0.5MB */ -int parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; +static unsigned long parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; + +#define FLUSH_TLB_THRESHOLD (2*1024*1024)/* 2MB initial TLB threshold */ +static unsigned long parisc_tlb_flush_threshold__read_mostly =FLUSH_TLB_THRESHOLD ;void __init parisc_setup_cache_timing (void ) {unsigned long rangetime, alltime; -unsigned long size; +unsigned long size, start; alltime =mfctl (16 );flush_data_cache ();@@ -364,14 +367,42 @@ void __init parisc_setup_cache_timing(void)/* Racy, but if we see an intermediate value, it's ok too... */ parisc_cache_flush_threshold = size * alltime / rangetime; - parisc_cache_flush_threshold = (parisc_cache_flush_threshold +L1_CACHE_BYTES -1 ) &~ (L1_CACHE_BYTES -1 ); + parisc_cache_flush_threshold =L1_CACHE_ALIGN (parisc_cache_flush_threshold);if (!parisc_cache_flush_threshold) parisc_cache_flush_threshold =FLUSH_THRESHOLD ;if (parisc_cache_flush_threshold > cache_info.dc_size) parisc_cache_flush_threshold = cache_info.dc_size; -printk (KERN_INFO "Setting cache flush threshold to %x (%d CPUs online)\n" , parisc_cache_flush_threshold,num_online_cpus ()); +printk (KERN_INFO "Setting cache flush threshold to %lu kB\n" , + parisc_cache_flush_threshold/1024 ); + +/* calculate TLB flush threshold */ + + alltime =mfctl (16 ); +flush_tlb_all (); + alltime =mfctl (16 ) - alltime; + + size =PAGE_SIZE ; + start = (unsigned long ) _text; + rangetime =mfctl (16 ); +while (start < (unsigned long ) _end) { +flush_tlb_kernel_range (start, start +PAGE_SIZE ); + start +=PAGE_SIZE ; + size +=PAGE_SIZE ; + } + rangetime =mfctl (16 ) - rangetime; + +printk (KERN_DEBUG "Whole TLB flush %lu cycles, flushing %lu bytes %lu cycles\n" , + alltime, size, rangetime); + + parisc_tlb_flush_threshold = size * alltime / rangetime; + parisc_tlb_flush_threshold =PAGE_ALIGN (parisc_tlb_flush_threshold); +if (!parisc_tlb_flush_threshold) + parisc_tlb_flush_threshold =FLUSH_TLB_THRESHOLD ; + +printk (KERN_INFO "Setting TLB flush threshold to %lu kB\n" , + parisc_tlb_flush_threshold/1024 ); }extern void purge_kernel_dcache_page_asm (unsigned long );@@ -418,32 +449,39 @@ void purge_tlb_entries(struct mm_struct *mm, unsigned long addr) }EXPORT_SYMBOL (purge_tlb_entries); -void __flush_tlb_range (unsigned long sid,unsigned long start, +/* __flush_tlb_range() +* +* returns 1 if all TLBs were flushed. +*/ +int __flush_tlb_range (unsigned long sid,unsigned long start,unsigned long end) { -unsigned long npages; +unsigned long size; - npages = ((end - (start &PAGE_MASK )) + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; -if (npages >=512 )/* 2MB of space: arbitrary, should be tuned */ + size = (end - start); +if (size >= parisc_tlb_flush_threshold) {flush_tlb_all (); +return 1 ; + }else {unsigned long flags;purge_tlb_start (flags);mtsp (sid,1 );if (split_tlb) { -while (npages--) { +while (start < end) {pdtlb (start);pitlb (start); start +=PAGE_SIZE ; } }else { -while (npages--) { +while (start < end) {pdtlb (start); start +=PAGE_SIZE ; } }purge_tlb_end (flags); +return 0 ; } }
RFC PATCH 1/1 parisc: Fix some PTE/TLB race conditions and optimize __flush_tlb_range based on timing results
In reply to Helge DellerThis patch is an evolution of the change posted by Helge based on buildd testing.
The increased use of pdtlb/pitlb instructions seemed to increase the frequency of random segmentation faults building packages. Further, we had a number of cases where TLB inserts would repeatedly fail and all forward progress would stop. The Haskell ghc package caused a lot of trouble in this area. The final indication of a race in pte handling was this syslog entry on sibaris:
[63165.884000] swap_free: Unused swap offset entry 00000004 [63165.948000] BUG: Bad page map in process mysqld pte:00000100 pmd:019bbec5 [63166.028000] addr:00000000ec464000 vm_flags:00100073 anon_vma:0000000221023828 mapping: (null) index:ec464 [63166.156000] CPU: 1 PID: 9176 Comm: mysqld Not tainted 4.0.0-2-parisc64-smp #1 Debian 4.0.5-1 [63166.256000] Backtrace: [63166.284000] [<0000000040173eb0>] show_stack+0x20/0x38 [63166.344000] [<0000000040444424>] dump_stack+0x9c/0x110 [63166.408000] [<00000000402a0d38>] print_bad_pte+0x1a8/0x278 [63166.476000] [<00000000402a28b8>] unmap_single_vma+0x3d8/0x770 [63166.544000] [<00000000402a4090>] zap_page_range+0xf0/0x198 [63166.612000] [<00000000402ba2a4>] SyS_madvise+0x404/0x8c0
Note that the pte value is 0 except for the accessed bit 0x100. This bit shouldn't be set without the present bit.
It should be noted that the madvise system call is probably a trigger for many of the random segmentation faults.
In looking at the kernel code, I found the following problems:
1) The pte_clear define didn't take TLB lock when clearing a pte.
2) We didn't test pte present bit inside lock in exception support.
3) The pte and tlb locks needed to merged in order to ensure consistency between page table and TLB. This also has the effect of serializing TLB broadcasts on SMP systems.
The attached change implements the above and a few other tweaks to try to improve performance. Based on the timing code, TLB purges are very slow (e.g., ~ 209 cycles per page on rp3440). Thus, I think it beneficial to test the split_tlb variable to avoid duplicate purges. Probably, all PA 2.0 machines have combined TLBs.
I dropped using __flush_tlb_range in flush_tlb_mm as I realized all applications and most threads have a stack size that is too large to make this useful. I added some comments to this effect.
Since implementing 1 through 3, I haven't had any random segmentation faults on mx3210 (rp3440) in about one week of building code and running as a Debian buildd.
Quoted 224 lines · Helge Deller
Quoted 24 lines · Helge Deller
This patch optimizes the TLB flushing functions
flush_tlb_mm()and__flush_tlb_range()in a way that it adds timing routines to calculate the optimal size up to which flushes of small TLB ranges perform faster than when performing a full TLB shootdown.Here are some timing results for a rp5470 and J5000 machine:
Machine: rp5470, PA8700, 875 MHz
I-cache : 768 KB
D-cache : 1536 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 692084 cycles, flushing 13214592 bytes 5904392 cycles Setting cache flush threshold to 1512 kB.
Whole TLB flush 40990 cycles, flushing 4096000 bytes 448989 cycles Setting TLB flush threshold to 368 kB.Machine: J5000/785, PA8500, 440MHz
I-cache : 512 KB
D-cache : 1024 KB (WB, direct mapped)
ITLB entries : 160
DTLB entries : 160 - shared with ITLBQuoted 4 lines
Whole cache flush 268605 cycles, flushing 13214592 bytes 1861037 cycles Setting cache flush threshold to 1024 kB
Whole TLB flush 14131 cycles, flushing 13221888 bytes 769329 cycles Setting TLB flush threshold to 240 kBOne more machine:
Machine: C8000, PA8800, 2 CPUs each 900 MHz
I-cache : 32768 KB
D-cache : 32768 KB (WB, direct mapped)
ITLB entries : 240
DTLB entries : 240 - shared with ITLBWhole cache flush 4298354 cycles, flushing 13214592 bytes 1440513 cycles Setting cache flush threshold to 32768 kB
Whole TLB flush 15625 cycles, flushing 13221888 bytes 781230 cycles Setting TLB flush threshold to 260 kBQuoted 180 lines
Signed-off-by Helge Deller <[email protected]>arch/parisc/include/asm/tlbflush.h +15
-37@@ -24,40 +24,25 @@ extern void flush_tlb_all_local(void *);#define smp_flush_tlb_all () flush_tlb_all() -/* -* flush_tlb_mm() -* -* XXX This code is NOT valid for HP-UX compatibility processes, -* (although it will probably work 99% of the time). HP-UX -* processes are free to play with the space id's and save them -* over long periods of time, etc. so we have to preserve the -* space and just flush the entire tlb. We need to check the -* personality in order to do that, but the personality is not -* currently being set correctly. -* -* Of course, Linux processes could do the same thing, but -* we don't support that (and the compilers, dynamic linker, -* etc. do not do that). -*/ +int __flush_tlb_range (unsigned long sid, +unsigned long start,unsigned long end); + +#define flush_tlb_range (vma, start, end) \ + __flush_tlb_range((vma)->vm_mm->context, start, end) + +#define flush_tlb_kernel_range (start, end) \ + __flush_tlb_range(0, start, end)static inline void flush_tlb_mm (struct mm_struct *mm) { -BUG_ON (mm == &init_mm);/* Should never happen */ - -#if 1 || defined(CONFIG_SMP ) -flush_tlb_all (); -#else -/* FIXME: currently broken, causing space id and protection ids -* to go out of sync, resulting in faults on userspace accesses. -*/ -if (mm) { -if (mm->context !=0 ) -free_sid (mm->context); - mm->context =alloc_sid (); -if (mm == current->active_mm) -load_context (mm->context); +struct vm_area_struct *vma; + +for (vma = mm->mmap; vma; vma = vma->vm_next) { +/* exit loop if flush_tlb_all() was called. */ +if (unlikely (__flush_tlb_range (mm->context, + vma->vm_start, vma->vm_end))) +return ; } -#endif } static inline void flush_tlb_page(struct vm_area_struct *vma,@@ -76,11 +61,4 @@ static inline void flush_tlb_page(struct vm_area_struct *vma, purge_tlb_end(flags); } -void __flush_tlb_range (unsigned long sid, -unsigned long start,unsigned long end); - -#define flush_tlb_range (vma,start,end) __flush_tlb_range((vma)->vm_mm->context,start,end) - -#define flush_tlb_kernel_range (start, end) __flush_tlb_range(0,start,end) -#endif arch/parisc/kernel/cache.c +47
-10@@ -342,12 +342,15 @@ EXPORT_SYMBOL(flush_data_cache_local);EXPORT_SYMBOL (flush_kernel_icache_range_asm);#define FLUSH_THRESHOLD 0x80000/* 0.5MB */ -int parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; +static unsigned long parisc_cache_flush_threshold__read_mostly =FLUSH_THRESHOLD ; + +#define FLUSH_TLB_THRESHOLD (2*1024*1024)/* 2MB initial TLB threshold */ +static unsigned long parisc_tlb_flush_threshold__read_mostly =FLUSH_TLB_THRESHOLD ;void __init parisc_setup_cache_timing (void ) {unsigned long rangetime, alltime; -unsigned long size; +unsigned long size, start; alltime =mfctl (16 );flush_data_cache ();@@ -364,14 +367,42 @@ void __init parisc_setup_cache_timing(void)/* Racy, but if we see an intermediate value, it's ok too... */ parisc_cache_flush_threshold = size * alltime / rangetime; - parisc_cache_flush_threshold = (parisc_cache_flush_threshold +L1_CACHE_BYTES -1 ) &~ (L1_CACHE_BYTES -1 ); + parisc_cache_flush_threshold =L1_CACHE_ALIGN (parisc_cache_flush_threshold);if (!parisc_cache_flush_threshold) parisc_cache_flush_threshold =FLUSH_THRESHOLD ;if (parisc_cache_flush_threshold > cache_info.dc_size) parisc_cache_flush_threshold = cache_info.dc_size; -printk (KERN_INFO "Setting cache flush threshold to %x (%d CPUs online)\n" , parisc_cache_flush_threshold,num_online_cpus ()); +printk (KERN_INFO "Setting cache flush threshold to %lu kB\n" , + parisc_cache_flush_threshold/1024 ); + +/* calculate TLB flush threshold */ + + alltime =mfctl (16 ); +flush_tlb_all (); + alltime =mfctl (16 ) - alltime; + + size =PAGE_SIZE ; + start = (unsigned long ) _text; + rangetime =mfctl (16 ); +while (start < (unsigned long ) _end) { +flush_tlb_kernel_range (start, start +PAGE_SIZE ); + start +=PAGE_SIZE ; + size +=PAGE_SIZE ; + } + rangetime =mfctl (16 ) - rangetime; + +printk (KERN_DEBUG "Whole TLB flush %lu cycles, flushing %lu bytes %lu cycles\n" , + alltime, size, rangetime); + + parisc_tlb_flush_threshold = size * alltime / rangetime; + parisc_tlb_flush_threshold =PAGE_ALIGN (parisc_tlb_flush_threshold); +if (!parisc_tlb_flush_threshold) + parisc_tlb_flush_threshold =FLUSH_TLB_THRESHOLD ; + +printk (KERN_INFO "Setting TLB flush threshold to %lu kB\n" , + parisc_tlb_flush_threshold/1024 ); }extern void purge_kernel_dcache_page_asm (unsigned long );@@ -418,32 +449,39 @@ void purge_tlb_entries(struct mm_struct *mm, unsigned long addr) }EXPORT_SYMBOL (purge_tlb_entries); -void __flush_tlb_range (unsigned long sid,unsigned long start, +/* __flush_tlb_range() +* +* returns 1 if all TLBs were flushed. +*/ +int __flush_tlb_range (unsigned long sid,unsigned long start,unsigned long end) { -unsigned long npages; +unsigned long size; - npages = ((end - (start &PAGE_MASK )) + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; -if (npages >=512 )/* 2MB of space: arbitrary, should be tuned */ + size = (end - start); +if (size >= parisc_tlb_flush_threshold) {flush_tlb_all (); +return 1 ; + }else {unsigned long flags;purge_tlb_start (flags);mtsp (sid,1 );if (split_tlb) { -while (npages--) { +while (start < end) {pdtlb (start);pitlb (start); start +=PAGE_SIZE ; } }else { -while (npages--) { +while (start < end) {pdtlb (start); start +=PAGE_SIZE ;
Quoted 10 lines
arch/parisc/kernel/cache.c void purge_tlb_entries(struct mm_struct *mm, unsigned long addr) · line 487
Quoted 8 lines
arch/parisc/kernel/cache.c +1
-0} }purge_tlb_end (flags); +return 0 ; } }
arch/parisc/include/asm/pgtable.h +37 -18
@@ -16,7 +16,7 @@#include <asm/processor.h> #include <asm/cache.h> -extern spinlock_t pa_dbit_lock; +extern spinlock_t pa_tlb_lock;/* * kern_addr_valid(ADDR) tests if ADDR is pointing to valid kernel
@@ -33,6 +33,19 @@ extern spinlock_t pa_dbit_lock;*/ #define kern_addr_valid (addr) (1) +/* Purge data and instruction TLB entries. Must be called holding +* the pa_tlb_lock. The TLB purge instructions are slow on SMP +* machines since the purge must be broadcast to all CPUs. +*/ + +static inline void purge_tlb_entries (struct mm_struct *mm,unsigned long addr) +{ +mtsp (mm->context,1 ); +pdtlb (addr); +if (unlikely (split_tlb)) +pitlb (addr); +} +/* Certain architectures need to do special things when PTEs * within a page table are directly modified. Thus, the following * hook is made available.
@@ -42,15 +55,20 @@ extern spinlock_t pa_dbit_lock; *(pteptr) = (pteval); \ } while(0) -extern void purge_tlb_entries(struct mm_struct *, unsigned long); +#define pte_inserted (x) \ + ((pte_val(x) & (_PAGE_PRESENT|_PAGE_ACCESSED)) \ + == (_PAGE_PRESENT|_PAGE_ACCESSED)) -#define set_pte_at (mm, addr, ptep, pteval) \ - do { \ +#define set_pte_at (mm, addr, ptep, pteval) \ + do { \ + pte_t old_pte; \ unsigned long flags; \ - spin_lock_irqsave(&pa_dbit_lock, flags); \ - set_pte(ptep, pteval); \ - purge_tlb_entries(mm, addr); \ - spin_unlock_irqrestore(&pa_dbit_lock, flags); \ + spin_lock_irqsave(&pa_tlb_lock, flags); \ + old_pte = *ptep; \ + set_pte(ptep, pteval); \ + if (pte_inserted(old_pte)) \ + purge_tlb_entries(mm, addr); \ + spin_unlock_irqrestore(&pa_tlb_lock, flags); \ } while (0)#endif /* !__ASSEMBLY__ */
@@ -268,7 +286,7 @@ extern unsigned long *empty_zero_page;#define pte_none (x) (pte_val(x) == 0)#define pte_present (x) (pte_val(x) & _PAGE_PRESENT) -#define pte_clear (mm,addr,xp) do { pte_val(*(xp)) = 0; } while (0) +#define pte_clear (mm,addr,xp) set_pte_at(mm, addr, xp, __pte(0))#define pmd_flag (x) (pmd_val(x) & PxD_FLAG_MASK)#define pmd_address (x) ((unsigned long)(pmd_val(x) &~ PxD_FLAG_MASK) << PxD_VALUE_SHIFT)
@@ -435,15 +453,15 @@ static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, unsignedif (!pte_young (*ptep))return 0 ; -spin_lock_irqsave (&pa_dbit_lock, flags); +spin_lock_irqsave (&pa_tlb_lock, flags); pte = *ptep;if (!pte_young (pte)) { -spin_unlock_irqrestore (&pa_dbit_lock, flags); +spin_unlock_irqrestore (&pa_tlb_lock, flags);return 0 ; }set_pte (ptep,pte_mkold (pte));purge_tlb_entries (vma->vm_mm, addr); -spin_unlock_irqrestore (&pa_dbit_lock, flags); +spin_unlock_irqrestore (&pa_tlb_lock, flags);return 1 ; }
@@ -453,11 +471,12 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm, unsigned long addr,pte_t old_pte;unsigned long flags; -spin_lock_irqsave (&pa_dbit_lock, flags); +spin_lock_irqsave (&pa_tlb_lock, flags); old_pte = *ptep; -pte_clear (mm,addr,ptep); -purge_tlb_entries (mm, addr); -spin_unlock_irqrestore (&pa_dbit_lock, flags); +set_pte (ptep,__pte (0 )); +if (pte_inserted (old_pte)) +purge_tlb_entries (mm, addr); +spin_unlock_irqrestore (&pa_tlb_lock, flags);return old_pte; }
@@ -465,10 +484,10 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm, unsigned long addr,static inline void ptep_set_wrprotect (struct mm_struct *mm,unsigned long addr,pte_t *ptep) {unsigned long flags; -spin_lock_irqsave (&pa_dbit_lock, flags); +spin_lock_irqsave (&pa_tlb_lock, flags);set_pte (ptep,pte_wrprotect (*ptep));purge_tlb_entries (mm, addr); -spin_unlock_irqrestore (&pa_dbit_lock, flags); +spin_unlock_irqrestore (&pa_tlb_lock, flags); }#define pte_same (A ,B ) (pte_val(A) == pte_val(B))
arch/parisc/include/asm/tlbflush.h +29 -24
87 lines not on this page · raw message
arch/parisc/kernel/cache.c +67 -38
141 lines not on this page · raw message
arch/parisc/kernel/entry.S +79 -84
398 lines not on this page · raw message
arch/parisc/kernel/traps.c +0 -3
@@ -43,10 +43,6 @@#include "../math-emu/math-emu.h" /* for handle_fpe() */ -#if defined(CONFIG_SMP ) || defined(CONFIG_DEBUG_SPINLOCK ) -DEFINE_SPINLOCK (pa_dbit_lock); -#endif