diff options
author | Al Cooper <alcooperx@gmail.com> | 2011-11-08 09:59:01 -0500 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2011-11-08 12:59:22 -0500 |
commit | 4f1a1eb530071c39fb239fd26c912a64284b1408 (patch) | |
tree | e4b00b9dab468604f97e2fbe1585ba0e87f37991 /arch/mips/kernel/cevt-r4k.c | |
parent | e63fb7a9dae820b3ff6754f794fd713f83e32fff (diff) |
MIPS: Kernel hangs occasionally during boot.
The Kernel hangs occasionally during boot after "Calibrating delay loop..".
This is caused by the c0_compare_int_usable() routine in cevt-r4k.c
returning false which causes the system to disable the timer and hang later.
The false return happens because the routine is using a series of four calls
to irq_disable_hazard() as a delay while it waits for the timer changes to
propagate to the cp0 cause register. On newer MIPS cores, like the 74K, the
series of irq_disable_hazard() calls turn into ehb instructions and can take
as little as a few clock ticks for all 4 instructions. This is not enough of
a delay, so the routine thinks the timer is not working. This fix uses up
to a max number of cycle counter ticks for the delay and uses
back_to_back_c0_hazard() instead of irq_disable_hazard() to handle the
hazard condition between cp0 writes and cp0 reads.
Signed-off-by: Al Cooper <alcooperx@gmail.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/2911/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/kernel/cevt-r4k.c')
-rw-r--r-- | arch/mips/kernel/cevt-r4k.c | 38 |
1 files changed, 19 insertions, 19 deletions
diff --git a/arch/mips/kernel/cevt-r4k.c b/arch/mips/kernel/cevt-r4k.c index 98c5a9737c14..e2d8e199be32 100644 --- a/arch/mips/kernel/cevt-r4k.c +++ b/arch/mips/kernel/cevt-r4k.c | |||
@@ -103,19 +103,10 @@ static int c0_compare_int_pending(void) | |||
103 | 103 | ||
104 | /* | 104 | /* |
105 | * Compare interrupt can be routed and latched outside the core, | 105 | * Compare interrupt can be routed and latched outside the core, |
106 | * so a single execution hazard barrier may not be enough to give | 106 | * so wait up to worst case number of cycle counter ticks for timer interrupt |
107 | * it time to clear as seen in the Cause register. 4 time the | 107 | * changes to propagate to the cause register. |
108 | * pipeline depth seems reasonably conservative, and empirically | ||
109 | * works better in configurations with high CPU/bus clock ratios. | ||
110 | */ | 108 | */ |
111 | 109 | #define COMPARE_INT_SEEN_TICKS 50 | |
112 | #define compare_change_hazard() \ | ||
113 | do { \ | ||
114 | irq_disable_hazard(); \ | ||
115 | irq_disable_hazard(); \ | ||
116 | irq_disable_hazard(); \ | ||
117 | irq_disable_hazard(); \ | ||
118 | } while (0) | ||
119 | 110 | ||
120 | int c0_compare_int_usable(void) | 111 | int c0_compare_int_usable(void) |
121 | { | 112 | { |
@@ -126,8 +117,12 @@ int c0_compare_int_usable(void) | |||
126 | * IP7 already pending? Try to clear it by acking the timer. | 117 | * IP7 already pending? Try to clear it by acking the timer. |
127 | */ | 118 | */ |
128 | if (c0_compare_int_pending()) { | 119 | if (c0_compare_int_pending()) { |
129 | write_c0_compare(read_c0_count()); | 120 | cnt = read_c0_count(); |
130 | compare_change_hazard(); | 121 | write_c0_compare(cnt); |
122 | back_to_back_c0_hazard(); | ||
123 | while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) | ||
124 | if (!c0_compare_int_pending()) | ||
125 | break; | ||
131 | if (c0_compare_int_pending()) | 126 | if (c0_compare_int_pending()) |
132 | return 0; | 127 | return 0; |
133 | } | 128 | } |
@@ -136,7 +131,7 @@ int c0_compare_int_usable(void) | |||
136 | cnt = read_c0_count(); | 131 | cnt = read_c0_count(); |
137 | cnt += delta; | 132 | cnt += delta; |
138 | write_c0_compare(cnt); | 133 | write_c0_compare(cnt); |
139 | compare_change_hazard(); | 134 | back_to_back_c0_hazard(); |
140 | if ((int)(read_c0_count() - cnt) < 0) | 135 | if ((int)(read_c0_count() - cnt) < 0) |
141 | break; | 136 | break; |
142 | /* increase delta if the timer was already expired */ | 137 | /* increase delta if the timer was already expired */ |
@@ -145,12 +140,17 @@ int c0_compare_int_usable(void) | |||
145 | while ((int)(read_c0_count() - cnt) <= 0) | 140 | while ((int)(read_c0_count() - cnt) <= 0) |
146 | ; /* Wait for expiry */ | 141 | ; /* Wait for expiry */ |
147 | 142 | ||
148 | compare_change_hazard(); | 143 | while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) |
144 | if (c0_compare_int_pending()) | ||
145 | break; | ||
149 | if (!c0_compare_int_pending()) | 146 | if (!c0_compare_int_pending()) |
150 | return 0; | 147 | return 0; |
151 | 148 | cnt = read_c0_count(); | |
152 | write_c0_compare(read_c0_count()); | 149 | write_c0_compare(cnt); |
153 | compare_change_hazard(); | 150 | back_to_back_c0_hazard(); |
151 | while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS)) | ||
152 | if (!c0_compare_int_pending()) | ||
153 | break; | ||
154 | if (c0_compare_int_pending()) | 154 | if (c0_compare_int_pending()) |
155 | return 0; | 155 | return 0; |
156 | 156 | ||