910ba598c8
Current ARM local timer code registers CPUFREQ notifiers even in case the twd_timer_setup() isn't called. That seems to be wrong and would eventually lead to kernel crash on the CPU frequency transitions on the SOCs where the local timer doesn't exist or broken because of hardware BUG. Fix it by testing twd_evt and *__this_cpu_ptr(twd_evt). The issue was observed with v3.3-rc3 and building an OMAP2+ kernel on OMAP3 SOC which doesn't have TWD. Below is the dump for reference : Unable to handle kernel paging request at virtual address 007e900 pgd = cdc20000 [007e9000] *pgd=00000000 Internal error: Oops: 5 [#1] SMP Modules linked in: CPU: 0 Not tainted (3.3.0-rc3-pm+debug+initramfs #9) PC is at twd_update_frequency+0x34/0x48 LR is at twd_update_frequency+0x10/0x48 pc : [<c001382c>] lr : [<c0013808>] psr: 60000093 sp : ce311dd8 ip : 00000000 fp : 00000000 r10: 00000000 r9 : 00000001 r8 : ce310000 r7 : c0440458 r6 : c00137f8 r5 : 00000000 r4 : c0947a74 r3 : 00000000 r2 : 007e9000 r1 : 00000000 r0 : 00000000 Flags: nZCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment usr Control: 10c5387d Table: 8dc20019 DAC: 00000015 Process sh (pid: 599, stack limit = 0xce3102f8) Stack: (0xce311dd8 to 0xce312000) 1dc0: 6000c 1de0: 00000001 00000002 00000000 00000000 00000000 00000000 00000 1e00: ffffffff c093d8f0 00000000 ce311ebc 00000001 00000001 ce310 1e20: c001386c c0437c4c c0e95b60 c0e95ba8 00000001 c0e95bf8 ffff4 1e40: 00000000 00000000 c005ef74 ce310000 c0435cf0 ce311ebc 00000 1e60: ce352b40 0007a120 c08d5108 c08ba040 c08ba040 c005f030 00000 1e80: c08bc554 c032fe2c 0007a120 c08d4b64 ce352b40 c08d8618 ffff8 1ea0: c08ba040 c033364c ce311ecc c0433b50 00000002 ffffffea c0330 1ec0: 0007a120 0007a120 22222201 00000000 22222222 00000000 ce357 1ee0: ce3d6000 cdc2aed8 ce352ba0 c0470164 00000002 c032f47c 00034 1f00: c0331cac ce352b40 00000007 c032f6d0 ce352bbc 0003d090 c0930 1f20: c093d8bc c03306a4 00000007 ce311f80 00000007 cdc2aec0 ce358 1f40: ce8d20c0 00000007 b6fe5000 ce311f80 00000007 ce310000 0000c 1f60: c000de74 ce987400 ce8d20c0 b6fe5000 00000000 00000000 0000c 1f80: 00000000 00000000 001fbac8 00000000 00000007 001fbac8 00004 1fa0: c000df04 c000dd60 00000007 001fbac8 00000001 b6fe5000 00000 1fc0: 00000007 001fbac8 00000007 00000004 b6fe5000 00000000 00202 1fe0: 00000000 beb565f8 00101ffc 00008e8c 60000010 00000001 00000 [<c001382c>] (twd_update_frequency+0x34/0x48) from [<c008ac4c>] ) [<c008ac4c>] (smp_call_function_single+0x17c/0x1c8) from [<c0013) [<c0013890>] (twd_cpufreq_transition+0x24/0x30) from [<c0437c4c>) [<c0437c4c>] (notifier_call_chain+0x44/0x84) from [<c005efe4>] () [<c005efe4>] (__srcu_notifier_call_chain+0x70/0xa4) from [<c005f) [<c005f030>] (srcu_notifier_call_chain+0x18/0x20) from [<c032fe2) [<c032fe2c>] (cpufreq_notify_transition+0xc8/0x1b0) from [<c0333) [<c033364c>] (omap_target+0x1b4/0x28c) from [<c032f47c>] (__cpuf) [<c032f47c>] (__cpufreq_driver_target+0x50/0x64) from [<c0331d24) [<c0331d24>] (cpufreq_set+0x78/0x98) from [<c032f6d0>] (store_sc) [<c032f6d0>] (store_scaling_setspeed+0x5c/0x74) from [<c03306a4>) [<c03306a4>] (store+0x58/0x74) from [<c014d868>] (sysfs_write_fi) [<c014d868>] (sysfs_write_file+0x80/0xb4) from [<c00f2c2c>] (vfs) [<c00f2c2c>] (vfs_write+0xa8/0x138) from [<c00f2e9c>] (sys_write) [<c00f2e9c>] (sys_write+0x40/0x6c) from [<c000dd60>] (ret_fast_s) Code: e594300c e792210c e1a01000 e5840004 (e7930002) ---[ end trace 5da3b5167c1ecdda ]--- Reported-by: Kevin Hilman <khilman@ti.com> Acked-by: Marc Zyngier <marc.zyngier@arm.com> Tested-by: Kevin Hilman <khilman@ti.com> Signed-off-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
271 lines
6.0 KiB
C
271 lines
6.0 KiB
C
/*
|
|
* linux/arch/arm/kernel/smp_twd.c
|
|
*
|
|
* Copyright (C) 2002 ARM Ltd.
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/io.h>
|
|
|
|
#include <asm/smp_twd.h>
|
|
#include <asm/localtimer.h>
|
|
#include <asm/hardware/gic.h>
|
|
|
|
/* set up by the platform code */
|
|
void __iomem *twd_base;
|
|
|
|
static struct clk *twd_clk;
|
|
static unsigned long twd_timer_rate;
|
|
|
|
static struct clock_event_device __percpu **twd_evt;
|
|
|
|
static void twd_set_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *clk)
|
|
{
|
|
unsigned long ctrl;
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
/* timer load already set up */
|
|
ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE
|
|
| TWD_TIMER_CONTROL_PERIODIC;
|
|
__raw_writel(twd_timer_rate / HZ, twd_base + TWD_TIMER_LOAD);
|
|
break;
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
/* period set, and timer enabled in 'next_event' hook */
|
|
ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT;
|
|
break;
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
default:
|
|
ctrl = 0;
|
|
}
|
|
|
|
__raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
|
|
}
|
|
|
|
static int twd_set_next_event(unsigned long evt,
|
|
struct clock_event_device *unused)
|
|
{
|
|
unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL);
|
|
|
|
ctrl |= TWD_TIMER_CONTROL_ENABLE;
|
|
|
|
__raw_writel(evt, twd_base + TWD_TIMER_COUNTER);
|
|
__raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* local_timer_ack: checks for a local timer interrupt.
|
|
*
|
|
* If a local timer interrupt has occurred, acknowledge and return 1.
|
|
* Otherwise, return 0.
|
|
*/
|
|
int twd_timer_ack(void)
|
|
{
|
|
if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) {
|
|
__raw_writel(1, twd_base + TWD_TIMER_INTSTAT);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void twd_timer_stop(struct clock_event_device *clk)
|
|
{
|
|
twd_set_mode(CLOCK_EVT_MODE_UNUSED, clk);
|
|
disable_percpu_irq(clk->irq);
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_FREQ
|
|
|
|
/*
|
|
* Updates clockevent frequency when the cpu frequency changes.
|
|
* Called on the cpu that is changing frequency with interrupts disabled.
|
|
*/
|
|
static void twd_update_frequency(void *data)
|
|
{
|
|
twd_timer_rate = clk_get_rate(twd_clk);
|
|
|
|
clockevents_update_freq(*__this_cpu_ptr(twd_evt), twd_timer_rate);
|
|
}
|
|
|
|
static int twd_cpufreq_transition(struct notifier_block *nb,
|
|
unsigned long state, void *data)
|
|
{
|
|
struct cpufreq_freqs *freqs = data;
|
|
|
|
/*
|
|
* The twd clock events must be reprogrammed to account for the new
|
|
* frequency. The timer is local to a cpu, so cross-call to the
|
|
* changing cpu.
|
|
*/
|
|
if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE)
|
|
smp_call_function_single(freqs->cpu, twd_update_frequency,
|
|
NULL, 1);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block twd_cpufreq_nb = {
|
|
.notifier_call = twd_cpufreq_transition,
|
|
};
|
|
|
|
static int twd_cpufreq_init(void)
|
|
{
|
|
if (twd_evt && *__this_cpu_ptr(twd_evt) && !IS_ERR(twd_clk))
|
|
return cpufreq_register_notifier(&twd_cpufreq_nb,
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
|
|
|
return 0;
|
|
}
|
|
core_initcall(twd_cpufreq_init);
|
|
|
|
#endif
|
|
|
|
static void __cpuinit twd_calibrate_rate(void)
|
|
{
|
|
unsigned long count;
|
|
u64 waitjiffies;
|
|
|
|
/*
|
|
* If this is the first time round, we need to work out how fast
|
|
* the timer ticks
|
|
*/
|
|
if (twd_timer_rate == 0) {
|
|
printk(KERN_INFO "Calibrating local timer... ");
|
|
|
|
/* Wait for a tick to start */
|
|
waitjiffies = get_jiffies_64() + 1;
|
|
|
|
while (get_jiffies_64() < waitjiffies)
|
|
udelay(10);
|
|
|
|
/* OK, now the tick has started, let's get the timer going */
|
|
waitjiffies += 5;
|
|
|
|
/* enable, no interrupt or reload */
|
|
__raw_writel(0x1, twd_base + TWD_TIMER_CONTROL);
|
|
|
|
/* maximum value */
|
|
__raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
|
|
|
|
while (get_jiffies_64() < waitjiffies)
|
|
udelay(10);
|
|
|
|
count = __raw_readl(twd_base + TWD_TIMER_COUNTER);
|
|
|
|
twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
|
|
|
|
printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000,
|
|
(twd_timer_rate / 10000) % 100);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t twd_handler(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
|
|
|
|
if (twd_timer_ack()) {
|
|
evt->event_handler(evt);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static struct clk *twd_get_clock(void)
|
|
{
|
|
struct clk *clk;
|
|
int err;
|
|
|
|
clk = clk_get_sys("smp_twd", NULL);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk));
|
|
return clk;
|
|
}
|
|
|
|
err = clk_prepare(clk);
|
|
if (err) {
|
|
pr_err("smp_twd: clock failed to prepare: %d\n", err);
|
|
clk_put(clk);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
err = clk_enable(clk);
|
|
if (err) {
|
|
pr_err("smp_twd: clock failed to enable: %d\n", err);
|
|
clk_unprepare(clk);
|
|
clk_put(clk);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
/*
|
|
* Setup the local clock events for a CPU.
|
|
*/
|
|
void __cpuinit twd_timer_setup(struct clock_event_device *clk)
|
|
{
|
|
struct clock_event_device **this_cpu_clk;
|
|
|
|
if (!twd_evt) {
|
|
int err;
|
|
|
|
twd_evt = alloc_percpu(struct clock_event_device *);
|
|
if (!twd_evt) {
|
|
pr_err("twd: can't allocate memory\n");
|
|
return;
|
|
}
|
|
|
|
err = request_percpu_irq(clk->irq, twd_handler,
|
|
"twd", twd_evt);
|
|
if (err) {
|
|
pr_err("twd: can't register interrupt %d (%d)\n",
|
|
clk->irq, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!twd_clk)
|
|
twd_clk = twd_get_clock();
|
|
|
|
if (!IS_ERR_OR_NULL(twd_clk))
|
|
twd_timer_rate = clk_get_rate(twd_clk);
|
|
else
|
|
twd_calibrate_rate();
|
|
|
|
__raw_writel(0, twd_base + TWD_TIMER_CONTROL);
|
|
|
|
clk->name = "local_timer";
|
|
clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
|
|
CLOCK_EVT_FEAT_C3STOP;
|
|
clk->rating = 350;
|
|
clk->set_mode = twd_set_mode;
|
|
clk->set_next_event = twd_set_next_event;
|
|
|
|
this_cpu_clk = __this_cpu_ptr(twd_evt);
|
|
*this_cpu_clk = clk;
|
|
|
|
clockevents_config_and_register(clk, twd_timer_rate,
|
|
0xf, 0xffffffff);
|
|
enable_percpu_irq(clk->irq, 0);
|
|
}
|