* [PATCH 1/5] OMAP3: PM: Prevent hang in prcm_interrupt_handler
2009-10-02 18:14 [PATCH 0/5] OMAP PM fixes for 2.6.32 Kevin Hilman
@ 2009-10-02 18:14 ` Kevin Hilman
2009-10-02 18:14 ` [PATCH 2/5] OMAP3: PM: PRCM interrupt: check MPUGRPSEL register Kevin Hilman
2009-10-05 17:57 ` [PATCH 0/5] OMAP PM fixes for 2.6.32 Kevin Hilman
2009-10-05 18:02 ` Kevin Hilman
2 siblings, 1 reply; 8+ messages in thread
From: Kevin Hilman @ 2009-10-02 18:14 UTC (permalink / raw)
To: linux-omap; +Cc: linux-arm-kernel, Jon Hunter
From: Jon Hunter <jon-hunter@ti.com>
There are two scenarios where a race condition could result in a hang
in the prcm_interrupt handler. These are:
1). Waiting for PRM_IRQSTATUS_MPU register to clear.
Bit 0 of the PRM_IRQSTATUS_MPU register indicates that a wake-up event
is pending for the MPU. This bit can only be cleared if the all the
wake-up events latched in the various PM_WKST_x registers have been
cleared. If a wake-up event occurred during the processing of the prcm
interrupt handler, after the corresponding PM_WKST_x register was
checked but before the PRM_IRQSTATUS_MPU was cleared, then the CPU
would be stuck forever waiting for bit 0 in PRM_IRQSTATUS_MPU to be
cleared.
2). Waiting for the PM_WKST_x register to clear.
Some power domains have more than one wake-up source. The PM_WKST_x
registers indicate the source of a wake-up event and need to be cleared
after a wake-up event occurs. When the PM_WKST_x registers are read and
before they are cleared, it is possible that another wake-up event
could occur causing another bit to be set in one of the PM_WKST_x
registers. If this did occur after reading a PM_WKST_x register then
the CPU would miss this event and get stuck forever in a loop waiting
for that PM_WKST_x register to clear.
This patch address the above race conditions that would result in a
hang.
Signed-off-by: Jon Hunter <jon-hunter@ti.com>
Reviewed-by: Paul Walmsley <paul@pwsan.com>
Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com>
---
arch/arm/mach-omap2/pm34xx.c | 143 ++++++++++++++++++------------------------
1 files changed, 60 insertions(+), 83 deletions(-)
diff --git a/arch/arm/mach-omap2/pm34xx.c b/arch/arm/mach-omap2/pm34xx.c
index 0ff5a6c..1e7aae2 100644
--- a/arch/arm/mach-omap2/pm34xx.c
+++ b/arch/arm/mach-omap2/pm34xx.c
@@ -51,97 +51,74 @@ static void (*_omap_sram_idle)(u32 *addr, int save_state);
static struct powerdomain *mpu_pwrdm;
-/* PRCM Interrupt Handler for wakeups */
-static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id)
+/*
+ * PRCM Interrupt Handler Helper Function
+ *
+ * The purpose of this function is to clear any wake-up events latched
+ * in the PRCM PM_WKST_x registers. It is possible that a wake-up event
+ * may occur whilst attempting to clear a PM_WKST_x register and thus
+ * set another bit in this register. A while loop is used to ensure
+ * that any peripheral wake-up events occurring while attempting to
+ * clear the PM_WKST_x are detected and cleared.
+ */
+static void prcm_clear_mod_irqs(s16 module, u8 regs)
{
- u32 wkst, irqstatus_mpu;
- u32 fclk, iclk;
+ u32 wkst, fclk, iclk;
+ u16 wkst_off = (regs == 3) ? OMAP3430ES2_PM_WKST3 : PM_WKST1;
+ u16 fclk_off = (regs == 3) ? OMAP3430ES2_CM_FCLKEN3 : CM_FCLKEN1;
+ u16 iclk_off = (regs == 3) ? CM_ICLKEN3 : CM_ICLKEN1;
- /* WKUP */
- wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST);
+ wkst = prm_read_mod_reg(module, wkst_off);
if (wkst) {
- iclk = cm_read_mod_reg(WKUP_MOD, CM_ICLKEN);
- fclk = cm_read_mod_reg(WKUP_MOD, CM_FCLKEN);
- cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_ICLKEN);
- cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_FCLKEN);
- prm_write_mod_reg(wkst, WKUP_MOD, PM_WKST);
- while (prm_read_mod_reg(WKUP_MOD, PM_WKST))
- cpu_relax();
- cm_write_mod_reg(iclk, WKUP_MOD, CM_ICLKEN);
- cm_write_mod_reg(fclk, WKUP_MOD, CM_FCLKEN);
- }
-
- /* CORE */
- wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1);
- if (wkst) {
- iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN1);
- fclk = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1);
- cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN1);
- cm_set_mod_reg_bits(wkst, CORE_MOD, CM_FCLKEN1);
- prm_write_mod_reg(wkst, CORE_MOD, PM_WKST1);
- while (prm_read_mod_reg(CORE_MOD, PM_WKST1))
- cpu_relax();
- cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN1);
- cm_write_mod_reg(fclk, CORE_MOD, CM_FCLKEN1);
- }
- wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3);
- if (wkst) {
- iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN3);
- fclk = cm_read_mod_reg(CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
- cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN3);
- cm_set_mod_reg_bits(wkst, CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
- prm_write_mod_reg(wkst, CORE_MOD, OMAP3430ES2_PM_WKST3);
- while (prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3))
- cpu_relax();
- cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN3);
- cm_write_mod_reg(fclk, CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
- }
-
- /* PER */
- wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST);
- if (wkst) {
- iclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_ICLKEN);
- fclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_FCLKEN);
- cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_ICLKEN);
- cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_FCLKEN);
- prm_write_mod_reg(wkst, OMAP3430_PER_MOD, PM_WKST);
- while (prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST))
- cpu_relax();
- cm_write_mod_reg(iclk, OMAP3430_PER_MOD, CM_ICLKEN);
- cm_write_mod_reg(fclk, OMAP3430_PER_MOD, CM_FCLKEN);
+ iclk = cm_read_mod_reg(module, iclk_off);
+ fclk = cm_read_mod_reg(module, fclk_off);
+ while (wkst) {
+ cm_set_mod_reg_bits(wkst, module, iclk_off);
+ cm_set_mod_reg_bits(wkst, module, fclk_off);
+ prm_write_mod_reg(wkst, module, wkst_off);
+ wkst = prm_read_mod_reg(module, wkst_off);
+ }
+ cm_write_mod_reg(iclk, module, iclk_off);
+ cm_write_mod_reg(fclk, module, fclk_off);
}
+}
- if (omap_rev() > OMAP3430_REV_ES1_0) {
- /* USBHOST */
- wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST);
- if (wkst) {
- iclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
- CM_ICLKEN);
- fclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
- CM_FCLKEN);
- cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD,
- CM_ICLKEN);
- cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD,
- CM_FCLKEN);
- prm_write_mod_reg(wkst, OMAP3430ES2_USBHOST_MOD,
- PM_WKST);
- while (prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
- PM_WKST))
- cpu_relax();
- cm_write_mod_reg(iclk, OMAP3430ES2_USBHOST_MOD,
- CM_ICLKEN);
- cm_write_mod_reg(fclk, OMAP3430ES2_USBHOST_MOD,
- CM_FCLKEN);
+/*
+ * PRCM Interrupt Handler
+ *
+ * The PRM_IRQSTATUS_MPU register indicates if there are any pending
+ * interrupts from the PRCM for the MPU. These bits must be cleared in
+ * order to clear the PRCM interrupt. The PRCM interrupt handler is
+ * implemented to simply clear the PRM_IRQSTATUS_MPU in order to clear
+ * the PRCM interrupt. Please note that bit 0 of the PRM_IRQSTATUS_MPU
+ * register indicates that a wake-up event is pending for the MPU and
+ * this bit can only be cleared if the all the wake-up events latched
+ * in the various PM_WKST_x registers have been cleared. The interrupt
+ * handler is implemented using a do-while loop so that if a wake-up
+ * event occurred during the processing of the prcm interrupt handler
+ * (setting a bit in the corresponding PM_WKST_x register and thus
+ * preventing us from clearing bit 0 of the PRM_IRQSTATUS_MPU register)
+ * this would be handled.
+ */
+static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id)
+{
+ u32 irqstatus_mpu;
+
+ do {
+ prcm_clear_mod_irqs(WKUP_MOD, 1);
+ prcm_clear_mod_irqs(CORE_MOD, 1);
+ prcm_clear_mod_irqs(OMAP3430_PER_MOD, 1);
+ if (omap_rev() > OMAP3430_REV_ES1_0) {
+ prcm_clear_mod_irqs(CORE_MOD, 3);
+ prcm_clear_mod_irqs(OMAP3430ES2_USBHOST_MOD, 1);
}
- }
- irqstatus_mpu = prm_read_mod_reg(OCP_MOD,
- OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
- prm_write_mod_reg(irqstatus_mpu, OCP_MOD,
- OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
+ irqstatus_mpu = prm_read_mod_reg(OCP_MOD,
+ OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
+ prm_write_mod_reg(irqstatus_mpu, OCP_MOD,
+ OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
- while (prm_read_mod_reg(OCP_MOD, OMAP3_PRM_IRQSTATUS_MPU_OFFSET))
- cpu_relax();
+ } while (prm_read_mod_reg(OCP_MOD, OMAP3_PRM_IRQSTATUS_MPU_OFFSET));
return IRQ_HANDLED;
}
--
1.6.4.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 0/5] OMAP PM fixes for 2.6.32
2009-10-02 18:14 [PATCH 0/5] OMAP PM fixes for 2.6.32 Kevin Hilman
2009-10-02 18:14 ` [PATCH 1/5] OMAP3: PM: Prevent hang in prcm_interrupt_handler Kevin Hilman
@ 2009-10-05 17:57 ` Kevin Hilman
2009-10-05 18:02 ` Kevin Hilman
2 siblings, 0 replies; 8+ messages in thread
From: Kevin Hilman @ 2009-10-05 17:57 UTC (permalink / raw)
To: linux-omap; +Cc: linux-arm-kernel
Kevin Hilman <khilman@deeprootsystems.com> writes:
> These fixes are primarily for PRCM interrupt handler bugs
> and GPIO wakeup support.
>
> Jon Hunter (1):
> OMAP3: PM: Prevent hang in prcm_interrupt_handler
>
> Kevin Hilman (1):
> OMAP3: PM: Enable GPIO module-level wakeups
>
> Paul Walmsley (2):
> OMAP3: PM: PRCM interrupt: check MPUGRPSEL register
> OMAP3: PM: PRCM interrupt: only handle selected PRCM interrupts
>
> Vikram Pandita (1):
> OMAP3: PM: USBHOST: clear wakeup events on both hosts
>
Here's one more for the PM fixes queue from Atrem.
Kevin
>From ee894b18e064447f86019af38a90ccb091880942 Mon Sep 17 00:00:00 2001
From: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
Date: Thu, 1 Oct 2009 10:01:55 +0300
Subject: [PATCH 6/6] OMAP3: PM: introduce a new powerdomain walk helper
The 'pwrdm_for_each()' function walks powerdomains with a spinlock
locked, so the the callbacks cannot do anything which may sleep.
This patch introduces a 'pwrdm_for_each_nolock()' helper which does
the same, but without the spinlock locked. This fixes the following
lockdep warning:
[ 0.000000] WARNING: at kernel/lockdep.c:2460 lockdep_trace_alloc+0xac/0xec()
[ 0.000000] Modules linked in:
(unwind_backtrace+0x0/0xdc) from [<c0045464>] (warn_slowpath_common+0x48/0x60)
(warn_slowpath_common+0x48/0x60) from [<c0067dd4>] (lockdep_trace_alloc+0xac/0xec)
(lockdep_trace_alloc+0xac/0xec) from [<c009da14>] (kmem_cache_alloc+0x1c/0xd0)
(kmem_cache_alloc+0x1c/0xd0) from [<c00b21d8>] (d_alloc+0x1c/0x1a4)
(d_alloc+0x1c/0x1a4) from [<c00a887c>] (__lookup_hash+0xd8/0x118)
(__lookup_hash+0xd8/0x118) from [<c00a9f20>] (lookup_one_len+0x84/0x94)
(lookup_one_len+0x84/0x94) from [<c010d12c>] (debugfs_create_file+0x8c/0x20c)
(debugfs_create_file+0x8c/0x20c) from [<c010d320>] (debugfs_create_dir+0x1c/0x20)
(debugfs_create_dir+0x1c/0x20) from [<c000e8cc>] (pwrdms_setup+0x60/0x90)
(pwrdms_setup+0x60/0x90) from [<c002e010>] (pwrdm_for_each+0x30/0x80)
(pwrdm_for_each+0x30/0x80) from [<c000e79c>] (pm_dbg_init+0x7c/0x14c)
(pm_dbg_init+0x7c/0x14c) from [<c00232b4>] (do_one_initcall+0x5c/0x1b8)
(do_one_initcall+0x5c/0x1b8) from [<c00083f8>] (kernel_init+0x90/0x10c)
(kernel_init+0x90/0x10c) from [<c00242c4>] (kernel_thread_exit+0x0/0x8)
Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com>
---
arch/arm/mach-omap2/pm-debug.c | 4 +-
arch/arm/mach-omap2/powerdomain.c | 39 +++++++++++++++++-------
arch/arm/plat-omap/include/mach/powerdomain.h | 2 +
3 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/arch/arm/mach-omap2/pm-debug.c b/arch/arm/mach-omap2/pm-debug.c
index 1b4c160..2fc4d6a 100644
--- a/arch/arm/mach-omap2/pm-debug.c
+++ b/arch/arm/mach-omap2/pm-debug.c
@@ -541,7 +541,7 @@ static int __init pm_dbg_init(void)
printk(KERN_ERR "%s: only OMAP3 supported\n", __func__);
return -ENODEV;
}
-
+
d = debugfs_create_dir("pm_debug", NULL);
if (IS_ERR(d))
return PTR_ERR(d);
@@ -551,7 +551,7 @@ static int __init pm_dbg_init(void)
(void) debugfs_create_file("time", S_IRUGO,
d, (void *)DEBUG_FILE_TIMERS, &debug_fops);
- pwrdm_for_each(pwrdms_setup, (void *)d);
+ pwrdm_for_each_nolock(pwrdms_setup, (void *)d);
pm_dbg_dir = debugfs_create_dir("registers", d);
if (IS_ERR(pm_dbg_dir))
diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c
index 2594cbf..f00289a 100644
--- a/arch/arm/mach-omap2/powerdomain.c
+++ b/arch/arm/mach-omap2/powerdomain.c
@@ -273,35 +273,50 @@ struct powerdomain *pwrdm_lookup(const char *name)
}
/**
- * pwrdm_for_each - call function on each registered clockdomain
+ * pwrdm_for_each_nolock - call function on each registered clockdomain
* @fn: callback function *
*
* Call the supplied function for each registered powerdomain. The
* callback function can return anything but 0 to bail out early from
- * the iterator. The callback function is called with the pwrdm_rwlock
- * held for reading, so no powerdomain structure manipulation
- * functions should be called from the callback, although hardware
- * powerdomain control functions are fine. Returns the last return
- * value of the callback function, which should be 0 for success or
- * anything else to indicate failure; or -EINVAL if the function
- * pointer is null.
+ * the iterator. Returns the last return value of the callback function, which
+ * should be 0 for success or anything else to indicate failure; or -EINVAL if
+ * the function pointer is null.
*/
-int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm, void *user),
- void *user)
+int pwrdm_for_each_nolock(int (*fn)(struct powerdomain *pwrdm, void *user),
+ void *user)
{
struct powerdomain *temp_pwrdm;
- unsigned long flags;
int ret = 0;
if (!fn)
return -EINVAL;
- read_lock_irqsave(&pwrdm_rwlock, flags);
list_for_each_entry(temp_pwrdm, &pwrdm_list, node) {
ret = (*fn)(temp_pwrdm, user);
if (ret)
break;
}
+
+ return ret;
+}
+
+/**
+ * pwrdm_for_each - call function on each registered clockdomain
+ * @fn: callback function *
+ *
+ * This function is the same as 'pwrdm_for_each_nolock()', but keeps the
+ * &pwrdm_rwlock locked for reading, so no powerdomain structure manipulation
+ * functions should be called from the callback, although hardware powerdomain
+ * control functions are fine.
+ */
+int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm, void *user),
+ void *user)
+{
+ unsigned long flags;
+ int ret;
+
+ read_lock_irqsave(&pwrdm_rwlock, flags);
+ ret = pwrdm_for_each_nolock(fn, user);
read_unlock_irqrestore(&pwrdm_rwlock, flags);
return ret;
diff --git a/arch/arm/plat-omap/include/mach/powerdomain.h b/arch/arm/plat-omap/include/mach/powerdomain.h
index 6271d85..fa64614 100644
--- a/arch/arm/plat-omap/include/mach/powerdomain.h
+++ b/arch/arm/plat-omap/include/mach/powerdomain.h
@@ -135,6 +135,8 @@ struct powerdomain *pwrdm_lookup(const char *name);
int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm, void *user),
void *user);
+int pwrdm_for_each_nolock(int (*fn)(struct powerdomain *pwrdm, void *user),
+ void *user);
int pwrdm_add_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm);
int pwrdm_del_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm);
--
1.6.4.3
^ permalink raw reply related [flat|nested] 8+ messages in thread