Commit 1b19ca9f authored by Russell King's avatar Russell King Committed by Linus Torvalds

Fix CPU spinlock lockups on secondary CPU bringup

Secondary CPU bringup typically calls calibrate_delay() during its
initialization.  However, calibrate_delay() modifies a global variable
(loops_per_jiffy) used for udelay() and __delay().

A side effect of 71c696b1 ("calibrate: extract fall-back calculation
into own helper") introduced in the 2.6.39 merge window means that we
end up with a substantial period where loops_per_jiffy is zero.  This
causes the spinlock debugging code to malfunction:

	u64 loops = loops_per_jiffy * HZ;
	for (;;) {
		for (i = 0; i < loops; i++) {
			if (arch_spin_trylock(&lock->raw_lock))
				return;
			__delay(1);
		}
		...
	}

by never calling arch_spin_trylock() - resulting in the CPU locking
up in an infinite loop inside __spin_lock_debug().

Work around this by only writing to loops_per_jiffy only once we have
completed all the calibration decisions.
Tested-by: default avatarSantosh Shilimkar <santosh.shilimkar@ti.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
Cc: <stable@kernel.org> (2.6.39-stable)
--
Better solutions (such as omitting the calibration for secondary CPUs,
or arranging for calibrate_delay() to return the LPJ value and leave
it to the caller to decide where to store it) are a possibility, but
would be much more invasive into each architecture.

I think this is the best solution for -rc and stable, but it should be
revisited for the next merge window.

 init/calibrate.c |   14 ++++++++------
 1 files changed, 8 insertions(+), 6 deletions(-)
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 33b1e693
...@@ -245,30 +245,32 @@ static unsigned long __cpuinit calibrate_delay_converge(void) ...@@ -245,30 +245,32 @@ static unsigned long __cpuinit calibrate_delay_converge(void)
void __cpuinit calibrate_delay(void) void __cpuinit calibrate_delay(void)
{ {
unsigned long lpj;
static bool printed; static bool printed;
if (preset_lpj) { if (preset_lpj) {
loops_per_jiffy = preset_lpj; lpj = preset_lpj;
if (!printed) if (!printed)
pr_info("Calibrating delay loop (skipped) " pr_info("Calibrating delay loop (skipped) "
"preset value.. "); "preset value.. ");
} else if ((!printed) && lpj_fine) { } else if ((!printed) && lpj_fine) {
loops_per_jiffy = lpj_fine; lpj = lpj_fine;
pr_info("Calibrating delay loop (skipped), " pr_info("Calibrating delay loop (skipped), "
"value calculated using timer frequency.. "); "value calculated using timer frequency.. ");
} else if ((loops_per_jiffy = calibrate_delay_direct()) != 0) { } else if ((lpj = calibrate_delay_direct()) != 0) {
if (!printed) if (!printed)
pr_info("Calibrating delay using timer " pr_info("Calibrating delay using timer "
"specific routine.. "); "specific routine.. ");
} else { } else {
if (!printed) if (!printed)
pr_info("Calibrating delay loop... "); pr_info("Calibrating delay loop... ");
loops_per_jiffy = calibrate_delay_converge(); lpj = calibrate_delay_converge();
} }
if (!printed) if (!printed)
pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)\n", pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)\n",
loops_per_jiffy/(500000/HZ), lpj/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100, loops_per_jiffy); (lpj/(5000/HZ)) % 100, lpj);
loops_per_jiffy = lpj;
printed = true; printed = true;
} }
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