Commit 4f1a1eb530071c39fb239fd26c912a64284b1408

Authored by Al Cooper
Committed by Ralf Baechle
1 parent e63fb7a9da

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>

Showing 1 changed file with 19 additions and 19 deletions Side-by-side Diff

arch/mips/kernel/cevt-r4k.c
... ... @@ -103,20 +103,11 @@
103 103  
104 104 /*
105 105 * Compare interrupt can be routed and latched outside the core,
106   - * so a single execution hazard barrier may not be enough to give
107   - * it time to clear as seen in the Cause register. 4 time the
108   - * pipeline depth seems reasonably conservative, and empirically
109   - * works better in configurations with high CPU/bus clock ratios.
  106 + * so wait up to worst case number of cycle counter ticks for timer interrupt
  107 + * changes to propagate to the cause register.
110 108 */
  109 +#define COMPARE_INT_SEEN_TICKS 50
111 110  
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   -
120 111 int c0_compare_int_usable(void)
121 112 {
122 113 unsigned int delta;
... ... @@ -126,8 +117,12 @@
126 117 * IP7 already pending? Try to clear it by acking the timer.
127 118 */
128 119 if (c0_compare_int_pending()) {
129   - write_c0_compare(read_c0_count());
130   - compare_change_hazard();
  120 + cnt = read_c0_count();
  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 126 if (c0_compare_int_pending())
132 127 return 0;
133 128 }
... ... @@ -136,7 +131,7 @@
136 131 cnt = read_c0_count();
137 132 cnt += delta;
138 133 write_c0_compare(cnt);
139   - compare_change_hazard();
  134 + back_to_back_c0_hazard();
140 135 if ((int)(read_c0_count() - cnt) < 0)
141 136 break;
142 137 /* increase delta if the timer was already expired */
143 138  
... ... @@ -145,12 +140,17 @@
145 140 while ((int)(read_c0_count() - cnt) <= 0)
146 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 146 if (!c0_compare_int_pending())
150 147 return 0;
151   -
152   - write_c0_compare(read_c0_count());
153   - compare_change_hazard();
  148 + cnt = read_c0_count();
  149 + write_c0_compare(cnt);
  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 154 if (c0_compare_int_pending())
155 155 return 0;
156 156