system lockup with 2.6.29 on Cavium/Octeon
Hi All,
I have a system lockup problem that I have been looking at on a custom Cavium/Octeon 5010 based design. I am running on linux-2.6.29 with David Daney's latest round of PCI and ethernet patches (posted here on this list).
I have tracked the problem back to local_flush_tlb_kernel_range() in arch/mips/mm/tlb-r4k.c. At the top of this function is:
void local_flush_tlb_kernel_range(unsigned long start, unsigned
long end)
{
unsigned long flags;
int size;
ENTER_CRITICAL(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
size = (size + 1) >> 1;
if (size <= current_cpu_data.tlbsize / 2) {The problem is that typical example values I see passed in for start and end are:
start = c000000000006000
end = ffffffffc01d8000Now the vmalloc area starts at 0xc000000000000000 and the kernel code and data is all at 0xffffffff80000000 and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via __vunmap() and __purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
But is this analysis correct?
Regards
Greg
The address range size calculation inside local_flush_tlb_kernel_range() is being truncated by a too small size variable holder on 64bit systems. The truncated size can result in an erroneous tlbsize check that means we sit spinning inside a loop trying to flush a hige number of TLB entries. This is for all intents and purposes a system hang. Fix by using an appropriately sized valiable to hold the size.
---
arch/mips/mm/tlb-r4k.c renamed from arch/mips/mm/tlb-r4k.c.org +1 -1
@@ -161,7 +161,7 @@ void local_flush_tlb_range(struct vm_arevoid local_flush_tlb_kernel_range (unsigned long start,unsigned long end) {unsigned long flags; -int size; +unsigned long size;ENTER_CRITICAL (flags); size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ;
------------------------------------------------------------------------
Greg Ungerer -- Principal Engineer EMAIL: [email protected] SnapGear Group, McAfee PHONE: +61 7 3435 2888 825 Stanley St, FAX: +61 7 3891 3630 Woolloongabba, QLD, 4102, Australia WEB: http://www.SnapGear.com
Quoted 25 lines · Greg Ungerer
I have a system lockup problem that I have been looking at on a custom Cavium/Octeon 5010 based design. I am running on linux-2.6.29 with David Daney's latest round of PCI and ethernet patches (posted here on this list).
I have tracked the problem back to
local_flush_tlb_kernel_range()inarch/mips/mm/tlb-r4k.c. At the top of this function is:void local_flush_tlb_kernel_range(unsigned long start, unsigned long
end){ unsigned long flags; int size; ENTER_CRITICAL(flags); size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; size = (size + 1) >> 1; if (size <= current_cpu_data.tlbsize / 2) {The problem is that typical example values I see passed in for start and end are:
start = c000000000006000 end = ffffffffc01d8000
Quoted 6 lines
Now the vmalloc area starts at
0xc000000000000000and the kernel code and data is all at0xffffffff80000000and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via__vunmap()and__purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.
Either start or end address is sensible but not the combination - both addresses should be in the same segment. Start is in XKSEG, end in CKSEG2 and in between there are vast wastelands of unused address space exabytes in size.
Quoted 10 lines
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
But is this analysis correct?
Yes - but I think we have two issues here. The one is the calculation overflowing int for the arguments you're seeing. The other being that the arguments simply are looking wrong.
There are a few more instances of the same overflow issue which the patch below is fixing.
Ralf
arch/mips/mm/tlb-r3k.c | 6 ++---- arch/mips/mm/tlb-r4k.c | 6 ++---- arch/mips/mm/tlb-r8k.c | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-)
arch/mips/mm/tlb-r3k.c +2 -4
@@ -82,8 +82,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -int size; +unsigned long size, flags;#ifdef DEBUG_TLB printk("[tlbrange<%lu,0x%08lx,0x%08lx>]",
@@ -121,8 +120,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags;#ifdef DEBUG_TLB printk ("[tlbrange<%lu,0x%08lx,0x%08lx>]" , start, end);
arch/mips/mm/tlb-r4k.c +2 -4
@@ -117,8 +117,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -int size; +unsigned long size, flags;ENTER_CRITICAL (flags); size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ;
@@ -160,8 +159,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags;ENTER_CRITICAL (flags); size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ;
arch/mips/mm/tlb-r8k.c +1 -2
@@ -111,8 +111,7 @@ out_restore:/* Usable for KV1 addresses only! */ void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags; size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; size = (size +1 ) >>1 ;
Hi Ralf,
Quoted 34 lines · Ralf Baechle
Quoted 25 lines · Greg Ungerer
I have a system lockup problem that I have been looking at on a custom Cavium/Octeon 5010 based design. I am running on linux-2.6.29 with David Daney's latest round of PCI and ethernet patches (posted here on this list).
I have tracked the problem back to
local_flush_tlb_kernel_range()inarch/mips/mm/tlb-r4k.c. At the top of this function is:void local_flush_tlb_kernel_range(unsigned long start, unsigned long
end){ unsigned long flags; int size; ENTER_CRITICAL(flags); size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; size = (size + 1) >> 1; if (size <= current_cpu_data.tlbsize / 2) {The problem is that typical example values I see passed in for start and end are:
start = c000000000006000 end = ffffffffc01d8000Quoted 6 lines
Now the vmalloc area starts at
0xc000000000000000and the kernel code and data is all at0xffffffff80000000and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via__vunmap()and__purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.
Quoted 4 lines
Either start or end address is sensible but not the combination - both addresses should be in the same segment. Start is in XKSEG, end in CKSEG2 and in between there are vast wastelands of unused address space exabytes in size.
Yes, exactly, that looked odd to me too.
So I tracked it back to see how these both ended up being in there. It turns out that MODULE_START, as defined in arch/mips/include/asm/pgtable-64.h, is CKSSEG, so it is 0xffffffffc0000000 in my case. And VMALLOC_START/MAP_BASE is defined to be 0xc000000000000000.
In module_alloc() there is a call to __get_vm_area() with MODULE_START as the start address, and this is how the 0xfff... addresses end up in the vmap_area table. The usual vmalloc() calls use VMALLOC_START and that is how the 0xc000... addresses get into the vmap_area table.
Interestingly the definition of MODULE_START is like this:
#if defined(CONFIG_MODULES) && defined(KBUILD_64BIT_SYM32) && \
VMALLOC_START != CKSSEG/* Load modules into 32bit-compatible segment. */
#define MODULE_START CKSSEG
If MODULE_START wasn't defined then the module_alloc() code would have just called vmalloc() directly - and we wouldn't be in this mess :-)
Quoted 9 lines
Quoted 9 lines
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
Quoted 8 lines
Quoted 1 line
But is this analysis correct?
Yes - but I think we have two issues here. The one is the calculation overflowing int for the arguments you're seeing. The other being that the arguments simply are looking wrong.
There are a few more instances of the same overflow issue which the patch below is fixing.
Indeed, looks good.
Regards
Greg
Quoted 71 lines
Ralf
arch/mips/mm/tlb-r3k.c | 6 ++---- arch/mips/mm/tlb-r4k.c | 6 ++---- arch/mips/mm/tlb-r8k.c | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-)arch/mips/mm/tlb-r3k.c +2
-4@@ -82,8 +82,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -int size; +unsigned long size, flags;#ifdef DEBUG_TLB printk("[tlbrange<%lu,0x%08lx,0x%08lx>]",@@ -121,8 +120,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags;#ifdef DEBUG_TLB printk ("[tlbrange<%lu,0x%08lx,0x%08lx>]" , start, end);arch/mips/mm/tlb-r4k.c +2
-4@@ -117,8 +117,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -int size; +unsigned long size, flags;ENTER_CRITICAL (flags); size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ;@@ -160,8 +159,7 @@ void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags;ENTER_CRITICAL (flags); size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ;arch/mips/mm/tlb-r8k.c +1
-2@@ -111,8 +111,7 @@ out_restore:/* Usable for KV1 addresses only! */ void local_flush_tlb_kernel_range (unsigned long start,unsigned long end) { -unsigned long flags; -int size; +unsigned long size, flags; size = (end - start + (PAGE_SIZE -1 )) >>PAGE_SHIFT ; size = (size +1 ) >>1 ;
Signature · 5 lines
------------------------------------------------------------------------ Greg Ungerer -- Principal Engineer EMAIL: [email protected] SnapGear Group, McAfee PHONE: +61 7 3435 2888 825 Stanley St, FAX: +61 7 3891 3630 Woolloongabba, QLD, 4102, Australia WEB: http://www.SnapGear.com
Quoted 11 lines · Greg Ungerer
Interestingly the definition of
MODULE_STARTis like this:#if defined(CONFIG_MODULES) && defined(KBUILD_64BIT_SYM32) && \ VMALLOC_START != CKSSEG/* Load modules into 32bit-compatible segment. */
#defineMODULE_STARTCKSSEGIf
MODULE_STARTwasn't defined then themodule_alloc()code would have just calledvmalloc()directly - and we wouldn't be in this mess :-)
The reason it's done like this is that if the kernel is in CKSEG0 and modules in CKSEG2 all address references to kernel code and variables are just 32-bit that is they can be referenced with a much shorter instruction sequence than for full blown 64-bit code. This is just one of the artefacts and I think we can just ignore it.
Ralf
Quoted 18 lines · Ralf Baechle
Quoted 6 lines
Now the vmalloc area starts at
0xc000000000000000and the kernel code and data is all at0xffffffff80000000and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via__vunmap()and__purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.Either start or end address is sensible but not the combination - both addresses should be in the same segment. Start is in XKSEG, end in CKSEG2 and in between there are vast wastelands of unused address space exabytes in size.
Quoted 6 lines
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
Quoted 8 lines
Quoted 4 lines
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
But is this analysis correct?
Yes - but I think we have two issues here. The one is the calculation overflowing int for the arguments you're seeing. The other being that the arguments simply are looking wrong.
The wrong combination comes from lazy vunmapping which was introduced in 2.6.28 cycle. Maybe we can add new API (non-lazy version of vfree()) to vmalloc.c to implement module_free(), but I suppose fallbacking to local_flush_tlb_all() in local_flush_tlb_kernel_range() is enough().
---
Atsushi Nemoto
Hi Atsushi,
Quoted 26 lines · Atsushi Nemoto
Quoted 17 lines · Ralf Baechle
Quoted 6 lines
Now the vmalloc area starts at
0xc000000000000000and the kernel code and data is all at0xffffffff80000000and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via__vunmap()and__purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.Either start or end address is sensible but not the combination - both addresses should be in the same segment. Start is in XKSEG, end in CKSEG2 and in between there are vast wastelands of unused address space exabytes in size.
Quoted 6 lines
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
Quoted 7 lines
Quoted 4 lines
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
But is this analysis correct?
Yes - but I think we have two issues here. The one is the calculation overflowing int for the arguments you're seeing. The other being that the arguments simply are looking wrong.
Quoted 5 lines
The wrong combination comes from lazy vunmapping which was introduced in 2.6.28 cycle. Maybe we can add new API (non-lazy version of
vfree()) tovmalloc.cto implementmodule_free(), but I suppose fallbacking tolocal_flush_tlb_all()inlocal_flush_tlb_kernel_range()isenough().
Is there any performance impact on falling back to that?
The flushing due to lazy vunmapping didn't seem to happen often in the tests I was running.
Regards
Greg
------------------------------------------------------------------------
Greg Ungerer -- Principal Engineer EMAIL: [email protected] SnapGear Group, McAfee PHONE: +61 7 3435 2888 825 Stanley St, FAX: +61 7 3891 3630 Woolloongabba, QLD, 4102, Australia WEB: http://www.SnapGear.com
Quoted 33 lines · Greg Ungerer
Quoted 26 lines · Atsushi Nemoto
Quoted 17 lines · Ralf Baechle
Quoted 6 lines
Now the vmalloc area starts at
0xc000000000000000and the kernel code and data is all at0xffffffff80000000and above. I don't know if the start and end are reasonable values, but I can see some logic as to where they come from. The code path that leads to this is via__vunmap()and__purge_vmap_area_lazy(). So it is not too difficult to see how we end up with values like this.Either start or end address is sensible but not the combination - both addresses should be in the same segment. Start is in XKSEG, end in CKSEG2 and in between there are vast wastelands of unused address space exabytes in size.
Quoted 6 lines
But the size calculation above with these types of values will result in still a large number. Larger than the 32bit "int" that is "size". I see large negative values fall out as size, and so the following tlbsize check becomes true, and the code spins inside the loop inside that if statement for a _very_ long time trying to flush tlb entries.
Quoted 7 lines
Quoted 4 lines
This is of course easily fixed, by making that size "unsigned long". The patch below trivially does this.
But is this analysis correct?
Yes - but I think we have two issues here. The one is the calculation overflowing int for the arguments you're seeing. The other being that the arguments simply are looking wrong.
Quoted 5 lines
The wrong combination comes from lazy vunmapping which was introduced in 2.6.28 cycle. Maybe we can add new API (non-lazy version of
vfree()) tovmalloc.cto implementmodule_free(), but I suppose fallbacking tolocal_flush_tlb_all()inlocal_flush_tlb_kernel_range()isenough().
Quoted 4 lines
Is there any performance impact on falling back to that?
The flushing due to lazy vunmapping didn't seem to happen often in the tests I was running.
It would depend on the workload. Some depend heavily on the performance of vmalloc & co. What I'm wondering now is if we no tend to always flush the entire TLB instead of just a few entries. The real cost of a TLB flush is often not the flushing but the eventual reload of the entries. That's factors that are hard to predict so benchmarking would be interesting.
Ralf
Quoted 10 lines · Greg Ungerer
Quoted 5 lines
The wrong combination comes from lazy vunmapping which was introduced in 2.6.28 cycle. Maybe we can add new API (non-lazy version of
vfree()) tovmalloc.cto implementmodule_free(), but I suppose fallbacking tolocal_flush_tlb_all()inlocal_flush_tlb_kernel_range()isenough().Is there any performance impact on falling back to that?
The flushing due to lazy vunmapping didn't seem to happen often in the tests I was running.
I think the wrong combination can happen only when some modules were unloaded, so performance impact would not be serious even if exists.
---
Atsushi Nemoto