Re: [PATCH] clk: fix spin_lock/unlock imbalance on bad clk_enable() reentrancy

From: David Lechner
Date: Wed Dec 20 2017 - 15:33:28 EST


On 12/20/2017 01:24 PM, Michael Turquette wrote:
Quoting David Lechner (2017-12-20 10:53:27)
On 12/19/2017 04:29 PM, Michael Turquette wrote:
Hi David,

Quoting David Lechner (2017-12-15 08:29:56)
On 12/12/2017 10:14 PM, David Lechner wrote:
On 12/12/2017 05:43 PM, David Lechner wrote:
If clk_enable() is called in reentrant way and spin_trylock_irqsave() is
not working as expected, it is possible to get a negative enable_refcnt
which results in a missed call to spin_unlock_irqrestore().

It works like this:

1. clk_enable() is called.
2. clk_enable_unlock() calls spin_trylock_irqsave() and sets
ÂÂÂ enable_refcnt = 1.
3. Another clk_enable() is called before the first has returned
ÂÂÂ (reentrant), but somehow spin_trylock_irqsave() is returning true.
ÂÂÂ (I'm not sure how/why this is happening yet, but it is happening
to me
ÂÂÂ with arch/arm/mach-davinci clocks that I am working on).

I think I have figured out that since CONFIG_SMP=n and
CONFIG_DEBUG_SPINLOCK=n on my kernel that

#define arch_spin_trylock(lock)({ barrier(); (void)(lock); 1; })

in include/linux/spinlock_up.h is causing the problem.

So, basically, reentrancy of clk_enable() is broken for non-SMP systems,
but I'm not sure I know how to fix it.



Here is what I came up with for a fix. If it looks reasonable, I will
resend as a proper patch.

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index bb1b1f9..53fad5f 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -136,12 +136,23 @@ static void clk_prepare_unlock(void)
mutex_unlock(&prepare_lock);
}

+#ifdef CONFIG_SMP
+#define NO_SMP 0
+#else
+#define NO_SMP 1
+#endif
+
static unsigned long clk_enable_lock(void)
__acquires(enable_lock)
{
- unsigned long flags;
+ unsigned long flags = 0;

- if (!spin_trylock_irqsave(&enable_lock, flags)) {
+ /*
+ * spin_trylock_irqsave() always returns true on non-SMP system
(unless

Ugh, wrapped lines in patch make me sad.

Sorry, I was being lazy. :-/


+ * spin lock debugging is enabled) so don't call
spin_trylock_irqsave()
+ * unless we are on an SMP system.
+ */
+ if (NO_SMP || !spin_trylock_irqsave(&enable_lock, flags)) {

I'm not sure that this looks reasonable. The inverse logic (NO_SMP = 0
being equivalent to SMP = 1) just makes things harder to read for no
reason.

More to the point, did you fix your enable/disable call imbalance? If
so, did you test that fix without this patch? I'd like to know if fixing
the enable/disable imbalance is Good Enough. I'd prefer to take only
that change and not this patch.

Without this patch, the imbalance in calls to spin lock/unlock are
fixed, but I still get several WARN_ONCE_ON() because the reference
count becomes negative, so I would not call it Good Enough.

Several WARN_ON_ONCE? Have you narrowed down exactly which conditions in
the lock/unlock path are firing?

Also, I wasn't referring to the lock/unlock imbalance in my email above.
I was referring to the enable count mismatch. Have you fixed that bug? I
assume it's an incorrect clk consumer driver causing it.


OK, explaining from the beginning once again. This bug was discovered because we need to call clk_enable() in a reentrant way on a non SMP system on purpose (by design, not by chance). The call path is this:

1. clk_enable() is called.

int clk_enable(struct clk *clk)
{
if (!clk)
return 0;

return clk_core_enable_lock(clk->core);
}

2. Which in turn calls clk_core_enable_lock()


static int clk_core_enable_lock(struct clk_core *core)
{
unsigned long flags;
int ret;

flags = clk_enable_lock();
ret = clk_core_enable(core);
clk_enable_unlock(flags);

return ret;
}

3. Which calls clk_enable_lock()

static unsigned long clk_enable_lock(void)
__acquires(enable_lock)
{
unsigned long flags = 0;

if (!spin_trylock_irqsave(&enable_lock, flags)) {
if (enable_owner == current) {
enable_refcnt++;
__acquire(enable_lock);
return flags;
}
spin_lock_irqsave(&enable_lock, flags);
}
WARN_ON_ONCE(enable_owner != NULL);
WARN_ON_ONCE(enable_refcnt != 0);
enable_owner = current;
enable_refcnt = 1;
return flags;
}

4. spin_trylock_irqsave() returns true, enable_owner was NULL and enable_refcnt was 0, so no warnings. When the function exits, enable_owner == current and enable_refcnt ==1.

5. Now we are back in clk_core_enable_lock() and clk_core_enable() is called.

static int clk_core_enable(struct clk_core *core)
{
int ret = 0;

lockdep_assert_held(&enable_lock);

if (!core)
return 0;

if (WARN_ON(core->prepare_count == 0))
return -ESHUTDOWN;

if (core->enable_count == 0) {
ret = clk_core_enable(core->parent);

if (ret)
return ret;

trace_clk_enable_rcuidle(core);

if (core->ops->enable)
ret = core->ops->enable(core->hw);

trace_clk_enable_complete_rcuidle(core);

if (ret) {
clk_core_disable(core->parent);
return ret;
}
}

core->enable_count++;
return 0;
}

6. This results in calling the callback core->ops->enable(), which in this case is the following function. This clock has to enable another clock temporarily in order for this clock to start.

static void usb20_phy_clk_enable(struct davinci_clk *clk)
{
u32 val;
u32 timeout = 500000; /* 500 msec */

val = readl(DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG));

/* Starting USB 2.O PLL requires that the USB 2.O PSC is
* enabled. The PSC can be disabled after the PLL has locked.
*/
clk_enable(usb20_clk);

/*
* Turn on the USB 2.0 PHY, but just the PLL, and not OTG. The
* USB 1.1 host may use the PLL clock without USB 2.0 OTG being
* used.
*/
val &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN);
val |= CFGCHIP2_PHY_PLLON;

writel(val, DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG));

while (--timeout) {
val = readl(DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG));
if (val & CFGCHIP2_PHYCLKGD)
goto done;
udelay(1);
}

pr_err("Timeout waiting for USB 2.0 PHY clock good\n");
done:
clk_disable(usb20_clk);
}

7. The usb20_phy_clk_enable() calls clk_enable(). We have reentrancy because we have not returned from the first clk_enable() yet.

8. As before, clk_enable() calls clk_core_enable_lock()

9. Which calls clk_enable_lock().

static unsigned long clk_enable_lock(void)
__acquires(enable_lock)
{
unsigned long flags = 0;

if (!spin_trylock_irqsave(&enable_lock, flags)) {
if (enable_owner == current) {
enable_refcnt++;
__acquire(enable_lock);
return flags;
}
spin_lock_irqsave(&enable_lock, flags);
}
WARN_ON_ONCE(enable_owner != NULL);
WARN_ON_ONCE(enable_refcnt != 0);
enable_owner = current;
enable_refcnt = 1;
return flags;
}

10. If this was running on an SMP system, spin_trylock_irqsave() would return false because we already hold the lock for enable_lock that we aquired in step 3 and everything would be OK. But this is not an SMP system, so spin_trylock_irqsave() always returns true. So the if block is skipped and we get a warning because enable_owner == current and then another warning because enable_refcnt == 1.

11. clk_enable_lock() returns back to clk_core_enable_lock().

12. clk_core_enable() is called. There is nothing notable about this call.

13. clk_enable_unlock() is called.

static void clk_enable_unlock(unsigned long flags)
__releases(enable_lock)
{
WARN_ON_ONCE(enable_owner != current);
WARN_ON_ONCE(enable_refcnt == 0);

if (--enable_refcnt) {
__release(enable_lock);
return;
}
enable_owner = NULL;
spin_unlock_irqrestore(&enable_lock, flags);
}

14. enable_owner == current and enable_refcnt == 0, so no warnings. When the function returns, enable_onwer == NULL and enable_refcnt == 0.

15. clk_core_enable_lock() returns to clk_enable()

16. clk_enable() returns to usb20_phy_clk_enable()

17. usb20_phy_clk_enable() locks the PLL, then calls clk_disable()

void clk_disable(struct clk *clk)
{
if (IS_ERR_OR_NULL(clk))
return;

clk_core_disable_lock(clk->core);
}

18. Which calls clk_core_disable_lock()

static void clk_core_disable_lock(struct clk_core *core)
{
unsigned long flags;

flags = clk_enable_lock();
clk_core_disable(core);
clk_enable_unlock(flags);
}

19. Which calls clk_enable_lock()

static unsigned long clk_enable_lock(void)
__acquires(enable_lock)
{
unsigned long flags = 0;

if (!spin_trylock_irqsave(&enable_lock, flags)) {
if (enable_owner == current) {
enable_refcnt++;
__acquire(enable_lock);
return flags;
}
spin_lock_irqsave(&enable_lock, flags);
}
WARN_ON_ONCE(enable_owner != NULL);
WARN_ON_ONCE(enable_refcnt != 0);
enable_owner = current;
enable_refcnt = 1;
return flags;
}

20. spin_trylock_irqsave() always returns true, so skip the if block. enable_owner == NULL and enable_refcnt == 0, so no warnings. On return enable_owner == current and enable_refcnt == 1.

21. clk_core_disable() is called. Nothing notable about this.

22. clk_enable_unlock() is called.

static void clk_enable_unlock(unsigned long flags)
__releases(enable_lock)
{
WARN_ON_ONCE(enable_owner != current);
WARN_ON_ONCE(enable_refcnt == 0);

if (--enable_refcnt) {
__release(enable_lock);
return;
}
enable_owner = NULL;
spin_unlock_irqrestore(&enable_lock, flags);
}

23. enable_owner == current and enable_refcnt == 1, so no warnings. On return enable_owner == NULL and enable_refcnt == 0.

24. clk_core_disable_lock() returns to clk_disable()

25. clk_disable() returns to usb20_phy_clk_enable()

26. usb20_phy_clk_enable() returns to clk_core_enable()

27. clk_core_enable() returns to clk_core_enable_lock()

28 clk_core_enable_lock() calls clk_enable_unlock()

static void clk_enable_unlock(unsigned long flags)
__releases(enable_lock)
{
WARN_ON_ONCE(enable_owner != current);
WARN_ON_ONCE(enable_refcnt == 0);

if (--enable_refcnt) {
__release(enable_lock);
return;
}
enable_owner = NULL;
spin_unlock_irqrestore(&enable_lock, flags);
}

29. enable_owner == NULL, so we get a warning. enable_refcnt == 0, so we get another warning. --enable_refcnt != 0, so we return early in the if statement. on return, enable_onwer == NULL and enable_refcnt == -1.

30. clk_enable_unlock() returns to clk_core_enable_lock()

31. clk_core_enable_lock() returns to clk_enable(). This is the original clk_enable() from step 1, so we are done, but we have left enable_refcnt == -1. The next call to a clk_enable() will fix this and the warning will be suppressed because of WARN_ON_ONCE().



So, as you can see, we get 4 warnings here. There is no problem with any clock provider or consumer (as far as I can tell). The bug here is that spin_trylock_irqsave() always returns true on non-SMP systems, which messes up the reference counting.

usb20_phy_clk_enable() currently works because mach-davinci does not use the common clock framework. However, I am trying to move it to the common clock framework, which is how I discovered this bug.