diff mbox

[v4] clk: allow reentrant calls into the clk framework

Message ID 1364368183-24420-1-git-send-email-mturquette@linaro.org
State New
Headers show

Commit Message

Mike Turquette March 27, 2013, 7:09 a.m. UTC
Reentrancy into the clock framework from the clk.h api is necessary for
clocks that are prepared and unprepared via i2c_transfer (which includes
many PMICs and discrete audio chips) as well as for several other use
cases.

This patch implements reentrancy by adding two global atomic_t's which
track the context of the current caller.  Context in this case is the
return value from get_current().  One context variable is for slow
operations protected by the prepare_mutex and the other is for fast
operations protected by the enable_lock spinlock.

The clk.h api implementations are modified to first see if the relevant
global lock is already held and if so compare the global context (set by
whoever is holding the lock) against their own context (via a call to
get_current()).  If the two match then this function is a nested call
from the one already holding the lock and we procede.  If the context
does not match then procede to call mutex_lock and busy-wait for the
existing task to complete.

This patch does not increase concurrency for unrelated calls into the
clock framework.  Instead it simply allows reentrancy by the single task
which is currently holding the global clock framework lock.

Signed-off-by: Mike Turquette <mturquette@linaro.org>
Cc: Rajagopal Venkat <rajagopal.venkat@linaro.org>
Cc: David Brown <davidb@codeaurora.org>
Cc: Ulf Hansson <ulf.hansson@linaro.org>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/clk/clk.c |  255 ++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 186 insertions(+), 69 deletions(-)

Comments

Laurent Pinchart March 27, 2013, 9:08 a.m. UTC | #1
Hi Mike,

Thanks for the patch.

Please see below for a couple of comments.

On Wednesday 27 March 2013 00:09:43 Mike Turquette wrote:
> Reentrancy into the clock framework from the clk.h api is necessary for
> clocks that are prepared and unprepared via i2c_transfer (which includes
> many PMICs and discrete audio chips) as well as for several other use
> cases.
> 
> This patch implements reentrancy by adding two global atomic_t's which
> track the context of the current caller.  Context in this case is the
> return value from get_current().  One context variable is for slow
> operations protected by the prepare_mutex and the other is for fast
> operations protected by the enable_lock spinlock.
> 
> The clk.h api implementations are modified to first see if the relevant
> global lock is already held and if so compare the global context (set by
> whoever is holding the lock) against their own context (via a call to
> get_current()).  If the two match then this function is a nested call
> from the one already holding the lock and we procede.  If the context
> does not match then procede to call mutex_lock and busy-wait for the
> existing task to complete.
> 
> This patch does not increase concurrency for unrelated calls into the
> clock framework.  Instead it simply allows reentrancy by the single task
> which is currently holding the global clock framework lock.
> 
> Signed-off-by: Mike Turquette <mturquette@linaro.org>
> Cc: Rajagopal Venkat <rajagopal.venkat@linaro.org>
> Cc: David Brown <davidb@codeaurora.org>
> Cc: Ulf Hansson <ulf.hansson@linaro.org>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/clk/clk.c |  255 +++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 186 insertions(+), 69 deletions(-)
> 
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 5e8ffff..17432a5 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -19,9 +19,12 @@
>  #include <linux/of.h>
>  #include <linux/device.h>
>  #include <linux/init.h>
> +#include <linux/sched.h>
> 
>  static DEFINE_SPINLOCK(enable_lock);
>  static DEFINE_MUTEX(prepare_lock);
> +static atomic_t prepare_context;
> +static atomic_t enable_context;
> 
>  static HLIST_HEAD(clk_root_list);
>  static HLIST_HEAD(clk_orphan_list);

[snip]

> @@ -566,6 +548,35 @@ struct clk *__clk_lookup(const char *name)
>  	return NULL;
>  }
> 
> +/***  locking & reentrancy ***/
> +
> +static void clk_fwk_lock(void)
> +{
> +	/* hold the framework-wide lock, context == NULL */
> +	mutex_lock(&prepare_lock);
> +
> +	/* set context for any reentrant calls */
> +	atomic_set(&prepare_context, (int) get_current());

Won't that break on 64-bit architectures with sizeof(void *) != sizeof(int) ?

I wonder if it would make sense to abstract these operations in a generic 
recursive mutex. Given that it would delay this patch past v3.10 I won't push 
for that.

> +}
> +
> +static void clk_fwk_unlock(void)
> +{
> +	/* clear the context */
> +	atomic_set(&prepare_context, 0);
> +
> +	/* release the framework-wide lock, context == NULL */
> +	mutex_unlock(&prepare_lock);
> +}
> +
> +static bool clk_is_reentrant(void)
> +{
> +	if (mutex_is_locked(&prepare_lock))
> +		if ((void *) atomic_read(&prepare_context) == get_current())
> +			return true;
> +
> +	return false;
> +}
> +
>  /***        clk api        ***/
> 
>  void __clk_unprepare(struct clk *clk)

[snip]

> @@ -877,6 +945,30 @@ static void __clk_recalc_rates(struct clk *clk,
> unsigned long msg) __clk_recalc_rates(child, msg);
>  }
> 
> +unsigned long __clk_get_rate(struct clk *clk)
> +{
> +	unsigned long ret;
> +
> +	if (!clk) {
> +		ret = 0;
> +		goto out;

You could return 0 directly here.

> +	}
> +
> +	if (clk->flags & CLK_GET_RATE_NOCACHE)
> +		__clk_recalc_rates(clk, 0);
> +
> +	ret = clk->rate;
> +
> +	if (clk->flags & CLK_IS_ROOT)
> +		goto out;

And return ret here.

> +
> +	if (!clk->parent)
> +		ret = 0;
> +
> +out:
> +	return ret;
> +}
> +
>  /**
>   * clk_get_rate - return the rate of clk
>   * @clk: the clk whose rate is being returned
> @@ -889,14 +981,22 @@ unsigned long clk_get_rate(struct clk *clk)
>  {
>  	unsigned long rate;
> 
> -	mutex_lock(&prepare_lock);
> +	/*
> +	 * FIXME - any locking here seems heavy weight
> +	 * can clk->rate be replaced with an atomic_t?
> +	 * same logic can likely be applied to prepare_count & enable_count
> +	 */
> 
> -	if (clk && (clk->flags & CLK_GET_RATE_NOCACHE))
> -		__clk_recalc_rates(clk, 0);
> +	if (clk_is_reentrant()) {
> +		rate = __clk_get_rate(clk);
> +		goto out;

You can return directly here as well.

> +	}
> 
> +	clk_fwk_lock();
>  	rate = __clk_get_rate(clk);
> -	mutex_unlock(&prepare_lock);
> +	clk_fwk_unlock();
> 
> +out:
>  	return rate;
>  }
>  EXPORT_SYMBOL_GPL(clk_get_rate);
> @@ -1073,6 +1173,39 @@ static void clk_change_rate(struct clk *clk)
>  		clk_change_rate(child);
>  }
> 
> +int __clk_set_rate(struct clk *clk, unsigned long rate)
> +{
> +	int ret = 0;
> +	struct clk *top, *fail_clk;
> +
> +	/* bail early if nothing to do */
> +	if (rate == clk->rate)
> +		return 0;
> +
> +	if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
> +		return -EBUSY;
> +	}

Braces are not necessary.

> +	/* calculate new rates and get the topmost changed clock */
> +	top = clk_calc_new_rates(clk, rate);
> +	if (!top)
> +		return -EINVAL;
> +
> +	/* notify that we are about to change rates */
> +	fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
> +	if (fail_clk) {
> +		pr_warn("%s: failed to set %s rate\n", __func__,
> +				fail_clk->name);
> +		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
> +		return -EBUSY;
> +	}
> +
> +	/* change the rates */
> +	clk_change_rate(top);
> +
> +	return ret;
> +}
> +
>  /**
>   * clk_set_rate - specify a new rate for clk
>   * @clk: the clk whose rate is being changed
Thomas Gleixner March 27, 2013, 9:40 a.m. UTC | #2
On Wed, 27 Mar 2013, Mike Turquette wrote:

> Reentrancy into the clock framework from the clk.h api is necessary
> for clocks that are prepared and unprepared via i2c_transfer (which
> includes many PMICs and discrete audio chips) as well as for several
> other use cases.

That explanation sucks.

Why does an i2c clock need reentrancy? Just because it's i2c or what?

What exactly are the "several other use cases"?

Why do you need the spinlock side reentrant? If a clock is handled by
i2c it hardly can hold the spinlock over a i2c transfer.

A few pointers to code which needs this would be nice as well.

I'm refraining from reviewing the horrible implementation until I get
proper answers to the above questions.

Thanks,

	tglx
Laurent Pinchart March 27, 2013, 9:59 a.m. UTC | #3
Hi Thomas,

On Wednesday 27 March 2013 10:40:28 Thomas Gleixner wrote:
> On Wed, 27 Mar 2013, Mike Turquette wrote:
> > Reentrancy into the clock framework from the clk.h api is necessary
> > for clocks that are prepared and unprepared via i2c_transfer (which
> > includes many PMICs and discrete audio chips) as well as for several
> > other use cases.
> 
> That explanation sucks.
> 
> Why does an i2c clock need reentrancy? Just because it's i2c or what?

That's just an example, other clocks need reentrancy as well.

> What exactly are the "several other use cases"?

Please see below.

> Why do you need the spinlock side reentrant? If a clock is handled by
> i2c it hardly can hold the spinlock over a i2c transfer.
> 
> A few pointers to code which needs this would be nice as well.

See for instance 
http://git.linuxtv.org/pinchartl/media.git/commitdiff/c4abb868a4cb6ad33fde76a6383543f3bd6699b0

That commit exposes the XCLKA and XCLKB clock outputs of the OMAP3 ISP (camera 
interface) as clock framework objects. The issue here is that the ISP has 
several inputs clocks. One of them is a parent of XCLK[AB], and another one 
controls access to the ISP registers (L4 bus clock). The ISP driver thus needs 
to enable/disable and prepare/unprepare the L4 clock in the XCLK[AB] 
enable/disable and prepare/unprepare operation handlers.

> I'm refraining from reviewing the horrible implementation until I get
> proper answers to the above questions.
Thomas Gleixner March 27, 2013, 11:24 a.m. UTC | #4
On Wed, 27 Mar 2013, Mike Turquette wrote:
> +/***  locking & reentrancy ***/
> +
> +static void clk_fwk_lock(void)

This function name sucks as much as the whole implementation does.

> +{
> +	/* hold the framework-wide lock, context == NULL */
> +	mutex_lock(&prepare_lock);
> +
> +	/* set context for any reentrant calls */
> +	atomic_set(&prepare_context, (int) get_current());

And what's the point of the atomic here? There is no need for an
atomic if you hold the lock. Neither here nor on the reader side.

Aside of that, the cast to (int) and the one below to (void *) are
blantantly wrong on 64 bit.

> +}
> +
> +static void clk_fwk_unlock(void)
> +{
> +	/* clear the context */
> +	atomic_set(&prepare_context, 0);
> +
> +	/* release the framework-wide lock, context == NULL */
> +	mutex_unlock(&prepare_lock);
> +}
> +
> +static bool clk_is_reentrant(void)
> +{
> +	if (mutex_is_locked(&prepare_lock))
> +		if ((void *) atomic_read(&prepare_context) == get_current())

Mooo.

> +			return true;
> +
> +	return false;
> +}

Why the heck do you need this function?

Just to sprinkle all these ugly constructs into the code:

> -	mutex_lock(&prepare_lock);
> +	/* re-enter if call is from the same context */
> +	if (clk_is_reentrant()) {
> +		__clk_unprepare(clk);
> +		return;
> +	}

Sigh. Why not doing the obvious?

Step 1/2: Wrap locking in helper functions

+static void clk_prepare_lock(void)
+{
+	mutex_lock(&prepare_lock);
+}
+
+static void clk_prepare_unlock(void)
+{
+	mutex_unlock(&prepare_lock);
+}

That way the whole change in the existing code boils down to:

-	mutex_lock(&prepare_lock);
+	clk_prepare_lock();
...
-	mutex_unlock(&prepare_lock);
+	clk_prepare_unlock();

Ditto for the spinlock.

And there is no pointless reshuffling of functions required.


Step 2/2: Implement reentrancy

+static struct task_struct *prepare_owner;
+static int prepare_refcnt;

static void clk_prepare_lock()
{
-	mutex_lock(&prepare_lock);
+	if (!mutex_trylock(&prepare_lock)) {
+		if (prepare_owner == current) {
+		   	prepare_refcnt++;
+			return;
+		}
+		mutex_lock(&prepare_lock);
+	}
+	WARN_ON_ONCE(prepare_owner != NULL);
+	WARN_ON_ONCE(prepare_refcnt != 0);
+	prepare_owner = current;
+	prepare_refcnt = 1;
}

static void clk_prepare_unlock(void)
{
-	mutex_unlock(&prepare_lock);
+	WARN_ON_ONCE(prepare_owner != current);
+	WARN_ON_ONCE(prepare_refcnt == 0);
+
+	if (--prepare_refcnt)
+		return;
+	prepare_owner = NULL;
+	mutex_unlock(&prepare_lock);
}

Ditto for the spinlock.

That step requires ZERO change to the functions. They simply work and
you don't need all this ugly reentrancy hackery.

Thanks,

	tglx
Mike Turquette March 27, 2013, 3:06 p.m. UTC | #5
Quoting Laurent Pinchart (2013-03-27 02:08:15)
> Hi Mike,
> 
> Thanks for the patch.
> 
> Please see below for a couple of comments.
> 
> On Wednesday 27 March 2013 00:09:43 Mike Turquette wrote:
> > Reentrancy into the clock framework from the clk.h api is necessary for
> > clocks that are prepared and unprepared via i2c_transfer (which includes
> > many PMICs and discrete audio chips) as well as for several other use
> > cases.
> > 
> > This patch implements reentrancy by adding two global atomic_t's which
> > track the context of the current caller.  Context in this case is the
> > return value from get_current().  One context variable is for slow
> > operations protected by the prepare_mutex and the other is for fast
> > operations protected by the enable_lock spinlock.
> > 
> > The clk.h api implementations are modified to first see if the relevant
> > global lock is already held and if so compare the global context (set by
> > whoever is holding the lock) against their own context (via a call to
> > get_current()).  If the two match then this function is a nested call
> > from the one already holding the lock and we procede.  If the context
> > does not match then procede to call mutex_lock and busy-wait for the
> > existing task to complete.
> > 
> > This patch does not increase concurrency for unrelated calls into the
> > clock framework.  Instead it simply allows reentrancy by the single task
> > which is currently holding the global clock framework lock.
> > 
> > Signed-off-by: Mike Turquette <mturquette@linaro.org>
> > Cc: Rajagopal Venkat <rajagopal.venkat@linaro.org>
> > Cc: David Brown <davidb@codeaurora.org>
> > Cc: Ulf Hansson <ulf.hansson@linaro.org>
> > Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/clk/clk.c |  255 +++++++++++++++++++++++++++++++++++---------------
> >  1 file changed, 186 insertions(+), 69 deletions(-)
> > 
> > diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> > index 5e8ffff..17432a5 100644
> > --- a/drivers/clk/clk.c
> > +++ b/drivers/clk/clk.c
> > @@ -19,9 +19,12 @@
> >  #include <linux/of.h>
> >  #include <linux/device.h>
> >  #include <linux/init.h>
> > +#include <linux/sched.h>
> > 
> >  static DEFINE_SPINLOCK(enable_lock);
> >  static DEFINE_MUTEX(prepare_lock);
> > +static atomic_t prepare_context;
> > +static atomic_t enable_context;
> > 
> >  static HLIST_HEAD(clk_root_list);
> >  static HLIST_HEAD(clk_orphan_list);
> 
> [snip]
> 
> > @@ -566,6 +548,35 @@ struct clk *__clk_lookup(const char *name)
> >       return NULL;
> >  }
> > 
> > +/***  locking & reentrancy ***/
> > +
> > +static void clk_fwk_lock(void)
> > +{
> > +     /* hold the framework-wide lock, context == NULL */
> > +     mutex_lock(&prepare_lock);
> > +
> > +     /* set context for any reentrant calls */
> > +     atomic_set(&prepare_context, (int) get_current());
> 
> Won't that break on 64-bit architectures with sizeof(void *) != sizeof(int) ?
> 

Ok, I can use atomic64_t in the next version.  Good catch.

> I wonder if it would make sense to abstract these operations in a generic 
> recursive mutex. Given that it would delay this patch past v3.10 I won't push 
> for that.
> 

Having a nice implementation of recursive mutexes would have saved me
some time.

> > +}
> > +
> > +static void clk_fwk_unlock(void)
> > +{
> > +     /* clear the context */
> > +     atomic_set(&prepare_context, 0);
> > +
> > +     /* release the framework-wide lock, context == NULL */
> > +     mutex_unlock(&prepare_lock);
> > +}
> > +
> > +static bool clk_is_reentrant(void)
> > +{
> > +     if (mutex_is_locked(&prepare_lock))
> > +             if ((void *) atomic_read(&prepare_context) == get_current())
> > +                     return true;
> > +
> > +     return false;
> > +}
> > +
> >  /***        clk api        ***/
> > 
> >  void __clk_unprepare(struct clk *clk)
> 
> [snip]
> 
> > @@ -877,6 +945,30 @@ static void __clk_recalc_rates(struct clk *clk,
> > unsigned long msg) __clk_recalc_rates(child, msg);
> >  }
> > 
> > +unsigned long __clk_get_rate(struct clk *clk)
> > +{
> > +     unsigned long ret;
> > +
> > +     if (!clk) {
> > +             ret = 0;
> > +             goto out;
> 
> You could return 0 directly here.
> 

Call me crazy, but I prefer having a single return statement in a
function if possible.  That goes for the similar review comments below.

> > +     }
> > +
> > +     if (clk->flags & CLK_GET_RATE_NOCACHE)
> > +             __clk_recalc_rates(clk, 0);
> > +
> > +     ret = clk->rate;
> > +
> > +     if (clk->flags & CLK_IS_ROOT)
> > +             goto out;
> 
> And return ret here.
> 
> > +
> > +     if (!clk->parent)
> > +             ret = 0;
> > +
> > +out:
> > +     return ret;
> > +}
> > +
> >  /**
> >   * clk_get_rate - return the rate of clk
> >   * @clk: the clk whose rate is being returned
> > @@ -889,14 +981,22 @@ unsigned long clk_get_rate(struct clk *clk)
> >  {
> >       unsigned long rate;
> > 
> > -     mutex_lock(&prepare_lock);
> > +     /*
> > +      * FIXME - any locking here seems heavy weight
> > +      * can clk->rate be replaced with an atomic_t?
> > +      * same logic can likely be applied to prepare_count & enable_count
> > +      */
> > 
> > -     if (clk && (clk->flags & CLK_GET_RATE_NOCACHE))
> > -             __clk_recalc_rates(clk, 0);
> > +     if (clk_is_reentrant()) {
> > +             rate = __clk_get_rate(clk);
> > +             goto out;
> 
> You can return directly here as well.
> 
> > +     }
> > 
> > +     clk_fwk_lock();
> >       rate = __clk_get_rate(clk);
> > -     mutex_unlock(&prepare_lock);
> > +     clk_fwk_unlock();
> > 
> > +out:
> >       return rate;
> >  }
> >  EXPORT_SYMBOL_GPL(clk_get_rate);
> > @@ -1073,6 +1173,39 @@ static void clk_change_rate(struct clk *clk)
> >               clk_change_rate(child);
> >  }
> > 
> > +int __clk_set_rate(struct clk *clk, unsigned long rate)
> > +{
> > +     int ret = 0;
> > +     struct clk *top, *fail_clk;
> > +
> > +     /* bail early if nothing to do */
> > +     if (rate == clk->rate)
> > +             return 0;
> > +
> > +     if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
> > +             return -EBUSY;
> > +     }
> 
> Braces are not necessary.
> 

No harm in having them, but I can remove them in the next version.

Thanks for the review,
Mike

> > +     /* calculate new rates and get the topmost changed clock */
> > +     top = clk_calc_new_rates(clk, rate);
> > +     if (!top)
> > +             return -EINVAL;
> > +
> > +     /* notify that we are about to change rates */
> > +     fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
> > +     if (fail_clk) {
> > +             pr_warn("%s: failed to set %s rate\n", __func__,
> > +                             fail_clk->name);
> > +             clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
> > +             return -EBUSY;
> > +     }
> > +
> > +     /* change the rates */
> > +     clk_change_rate(top);
> > +
> > +     return ret;
> > +}
> > +
> >  /**
> >   * clk_set_rate - specify a new rate for clk
> >   * @clk: the clk whose rate is being changed
> 
> -- 
> Regards,
> 
> Laurent Pinchart
Mike Turquette March 27, 2013, 4:47 p.m. UTC | #6
Quoting Thomas Gleixner (2013-03-27 04:24:12)
> On Wed, 27 Mar 2013, Mike Turquette wrote:
> > +/***  locking & reentrancy ***/
> > +
> > +static void clk_fwk_lock(void)
> 
> This function name sucks as much as the whole implementation does.
> 
> > +{
> > +     /* hold the framework-wide lock, context == NULL */
> > +     mutex_lock(&prepare_lock);
> > +
> > +     /* set context for any reentrant calls */
> > +     atomic_set(&prepare_context, (int) get_current());
> 
> And what's the point of the atomic here? There is no need for an
> atomic if you hold the lock. Neither here nor on the reader side.
> 

I had wondered about that.  So the barriers in mutex_lock and
spin_lock_irqsave are sufficient such that the (unprotected) read-side
will always see the correct data?  That makes sense to me since accesses
to the clock tree are still serialized.

> Aside of that, the cast to (int) and the one below to (void *) are
> blantantly wrong on 64 bit.
> 

Since the atomic type is no longer required (based on the above
assumption) then this problem goes away.  Each context is just a global
pointer.

> > +}
> > +
> > +static void clk_fwk_unlock(void)
> > +{
> > +     /* clear the context */
> > +     atomic_set(&prepare_context, 0);
> > +
> > +     /* release the framework-wide lock, context == NULL */
> > +     mutex_unlock(&prepare_lock);
> > +}
> > +
> > +static bool clk_is_reentrant(void)
> > +{
> > +     if (mutex_is_locked(&prepare_lock))
> > +             if ((void *) atomic_read(&prepare_context) == get_current())
> 
> Mooo.

Woof?

> 
> > +                     return true;
> > +
> > +     return false;
> > +}
> 
> Why the heck do you need this function?
> 
> Just to sprinkle all these ugly constructs into the code:
> 
> > -     mutex_lock(&prepare_lock);
> > +     /* re-enter if call is from the same context */
> > +     if (clk_is_reentrant()) {
> > +             __clk_unprepare(clk);
> > +             return;
> > +     }
> 
> Sigh. Why not doing the obvious?
> 
> Step 1/2: Wrap locking in helper functions
> 
> +static void clk_prepare_lock(void)
> +{
> +       mutex_lock(&prepare_lock);
> +}
> +
> +static void clk_prepare_unlock(void)
> +{
> +       mutex_unlock(&prepare_lock);
> +}
> 
> That way the whole change in the existing code boils down to:
> 
> -       mutex_lock(&prepare_lock);
> +       clk_prepare_lock();
> ...
> -       mutex_unlock(&prepare_lock);
> +       clk_prepare_unlock();
> 
> Ditto for the spinlock.
> 
> And there is no pointless reshuffling of functions required.
> 
> 
> Step 2/2: Implement reentrancy
> 
> +static struct task_struct *prepare_owner;
> +static int prepare_refcnt;
> 
> static void clk_prepare_lock()
> {
> -       mutex_lock(&prepare_lock);
> +       if (!mutex_trylock(&prepare_lock)) {
> +               if (prepare_owner == current) {
> +                       prepare_refcnt++;
> +                       return;
> +               }
> +               mutex_lock(&prepare_lock);
> +       }
> +       WARN_ON_ONCE(prepare_owner != NULL);
> +       WARN_ON_ONCE(prepare_refcnt != 0);
> +       prepare_owner = current;
> +       prepare_refcnt = 1;
> }
> 
> static void clk_prepare_unlock(void)
> {
> -       mutex_unlock(&prepare_lock);
> +       WARN_ON_ONCE(prepare_owner != current);
> +       WARN_ON_ONCE(prepare_refcnt == 0);
> +
> +       if (--prepare_refcnt)
> +               return;
> +       prepare_owner = NULL;
> +       mutex_unlock(&prepare_lock);
> }
> 
> Ditto for the spinlock.
> 
> That step requires ZERO change to the functions. They simply work and
> you don't need all this ugly reentrancy hackery.
> 

Thanks for the review Thomas.  I will steal your code and call it my own
in the next version.  In particular getting rid of the atomics makes
things much nicer.

Regards,
Mike

> Thanks,
> 
>         tglx
Thomas Gleixner March 27, 2013, 5:09 p.m. UTC | #7
On Wed, 27 Mar 2013, Mike Turquette wrote:
> Thanks for the review Thomas.  I will steal your code and call it my own
> in the next version.

Sure.

> In particular getting rid of the atomics makes things much nicer.

I'd say using the helper functions and not having all these
conditionals makes it really nice :)

Thanks,

	tglx
Russell King - ARM Linux March 27, 2013, 10:56 p.m. UTC | #8
On Wed, Mar 27, 2013 at 12:09:43AM -0700, Mike Turquette wrote:
> +	/* set context for any reentrant calls */
> +	atomic_set(&prepare_context, (int) get_current());
...
> +	if (mutex_is_locked(&prepare_lock))
> +		if ((void *) atomic_read(&prepare_context) == get_current())
> +			return true;

If you really want to do it like this, then the _correct_ way to do it
is:

		if (atomic_read(&prepare_context) == (int)get_current())

So that any effects from the cast are the same in both parts.  Otherwise,
you will be running into the possibility that a cast could do something
like truncate the returned value, resulting in the test condition using
pointers always being false.

It's not that much of a problem on ARM, but it's a matter of good
programming discpline that such issues are avoided.

Even better would be to cast it to unsigned long - this is the value
which the atomic types use, and that is less likely to be truncated.
It also helps to avoid the possibility of compilers complaining about
a narrowing cast too - especially as the entire Linux kernel assumes
that pointers can be cast to unsigned long and back again with no loss.
Mike Turquette March 28, 2013, 3 a.m. UTC | #9
Quoting Russell King - ARM Linux (2013-03-27 15:56:10)
> On Wed, Mar 27, 2013 at 12:09:43AM -0700, Mike Turquette wrote:
> > +     /* set context for any reentrant calls */
> > +     atomic_set(&prepare_context, (int) get_current());
> ...
> > +     if (mutex_is_locked(&prepare_lock))
> > +             if ((void *) atomic_read(&prepare_context) == get_current())
> > +                     return true;
> 
> If you really want to do it like this, then the _correct_ way to do it
> is:
> 
>                 if (atomic_read(&prepare_context) == (int)get_current())
> 
> So that any effects from the cast are the same in both parts.  Otherwise,
> you will be running into the possibility that a cast could do something
> like truncate the returned value, resulting in the test condition using
> pointers always being false.
> 
> It's not that much of a problem on ARM, but it's a matter of good
> programming discpline that such issues are avoided.
> 
> Even better would be to cast it to unsigned long - this is the value
> which the atomic types use, and that is less likely to be truncated.
> It also helps to avoid the possibility of compilers complaining about
> a narrowing cast too - especially as the entire Linux kernel assumes
> that pointers can be cast to unsigned long and back again with no loss.

The atomics and casts will go away in the next version but the insights
are useful.  Thanks for the review.

Regards,
Mike
Mike Turquette March 28, 2013, 4:45 a.m. UTC | #10
This fifth attempt at allowing calls to the clk api to reenter splits
the last patch into two parts.  The first patch abstracts out the
locking details into some helper functions and converts all of the
direct calls to the mutex and spinlock api to use these helpers.

The second patch introduces the reentrancy logic into these helper
functions.  Fundamentally the reentrancy logic hasn't changed since v4,
but fixing casting bugs, removing unnecessary barriers and better design
& beautification separate this approach from the last one.

Changes tested on top of the latest clk-next branch with an OMAP4430
Panda board.

Mike Turquette (2):
  clk: abstract locking out into helper functions
  clk: allow reentrant calls into the clk framework

 drivers/clk/clk.c |  136 ++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 98 insertions(+), 38 deletions(-)
Laurent Pinchart March 28, 2013, 10:44 a.m. UTC | #11
Hi Mike,

On Wednesday 27 March 2013 21:45:56 Mike Turquette wrote:
> This fifth attempt at allowing calls to the clk api to reenter splits
> the last patch into two parts.  The first patch abstracts out the
> locking details into some helper functions and converts all of the
> direct calls to the mutex and spinlock api to use these helpers.
> 
> The second patch introduces the reentrancy logic into these helper
> functions.  Fundamentally the reentrancy logic hasn't changed since v4,
> but fixing casting bugs, removing unnecessary barriers and better design
> & beautification separate this approach from the last one.
> 
> Changes tested on top of the latest clk-next branch with an OMAP4430
> Panda board.
> 
> Mike Turquette (2):
>   clk: abstract locking out into helper functions
>   clk: allow reentrant calls into the clk framework
> 
>  drivers/clk/clk.c |  136 +++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 98 insertions(+), 38 deletions(-)

For the whole patch set,

Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

on a DM3730.
Mike Turquette March 28, 2013, 8:59 p.m. UTC | #12
This sixth version of the series fixes up two minor cosmetic issues and
adds reviewed-by's and tested-by's.  If there are no more comments to
this version then I'll merge these patches into clk-next so that it can
get some cycles in linux-next.

Changes tested on top of the latest clk-next branch with an OMAP4430
Panda board.

Mike Turquette (2):
  clk: abstract locking out into helper functions
  clk: allow reentrant calls into the clk framework

 drivers/clk/clk.c |  139 ++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 101 insertions(+), 38 deletions(-)
diff mbox

Patch

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 5e8ffff..17432a5 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -19,9 +19,12 @@ 
 #include <linux/of.h>
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/sched.h>
 
 static DEFINE_SPINLOCK(enable_lock);
 static DEFINE_MUTEX(prepare_lock);
+static atomic_t prepare_context;
+static atomic_t enable_context;
 
 static HLIST_HEAD(clk_root_list);
 static HLIST_HEAD(clk_orphan_list);
@@ -456,27 +459,6 @@  unsigned int __clk_get_prepare_count(struct clk *clk)
 	return !clk ? 0 : clk->prepare_count;
 }
 
-unsigned long __clk_get_rate(struct clk *clk)
-{
-	unsigned long ret;
-
-	if (!clk) {
-		ret = 0;
-		goto out;
-	}
-
-	ret = clk->rate;
-
-	if (clk->flags & CLK_IS_ROOT)
-		goto out;
-
-	if (!clk->parent)
-		ret = 0;
-
-out:
-	return ret;
-}
-
 unsigned long __clk_get_flags(struct clk *clk)
 {
 	return !clk ? 0 : clk->flags;
@@ -566,6 +548,35 @@  struct clk *__clk_lookup(const char *name)
 	return NULL;
 }
 
+/***  locking & reentrancy ***/
+
+static void clk_fwk_lock(void)
+{
+	/* hold the framework-wide lock, context == NULL */
+	mutex_lock(&prepare_lock);
+
+	/* set context for any reentrant calls */
+	atomic_set(&prepare_context, (int) get_current());
+}
+
+static void clk_fwk_unlock(void)
+{
+	/* clear the context */
+	atomic_set(&prepare_context, 0);
+
+	/* release the framework-wide lock, context == NULL */
+	mutex_unlock(&prepare_lock);
+}
+
+static bool clk_is_reentrant(void)
+{
+	if (mutex_is_locked(&prepare_lock))
+		if ((void *) atomic_read(&prepare_context) == get_current())
+			return true;
+
+	return false;
+}
+
 /***        clk api        ***/
 
 void __clk_unprepare(struct clk *clk)
@@ -600,9 +611,15 @@  void __clk_unprepare(struct clk *clk)
  */
 void clk_unprepare(struct clk *clk)
 {
-	mutex_lock(&prepare_lock);
+	/* re-enter if call is from the same context */
+	if (clk_is_reentrant()) {
+		__clk_unprepare(clk);
+		return;
+	}
+
+	clk_fwk_lock();
 	__clk_unprepare(clk);
-	mutex_unlock(&prepare_lock);
+	clk_fwk_unlock();
 }
 EXPORT_SYMBOL_GPL(clk_unprepare);
 
@@ -648,10 +665,16 @@  int clk_prepare(struct clk *clk)
 {
 	int ret;
 
-	mutex_lock(&prepare_lock);
-	ret = __clk_prepare(clk);
-	mutex_unlock(&prepare_lock);
+	/* re-enter if call is from the same context */
+	if (clk_is_reentrant()) {
+		ret = __clk_prepare(clk);
+		goto out;
+	}
 
+	clk_fwk_lock();
+	ret = __clk_prepare(clk);
+	clk_fwk_unlock();
+out:
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_prepare);
@@ -692,8 +715,27 @@  void clk_disable(struct clk *clk)
 {
 	unsigned long flags;
 
+	/* this call re-enters if it is from the same context */
+	if (spin_is_locked(&enable_lock)) {
+		if ((void *) atomic_read(&enable_context) == get_current()) {
+			__clk_disable(clk);
+			return;
+		}
+	}
+
+	/* hold the framework-wide lock, context == NULL */
 	spin_lock_irqsave(&enable_lock, flags);
+
+	/* set context for any reentrant calls */
+	atomic_set(&enable_context, (int) get_current());
+
+	/* disable the clock(s) */
 	__clk_disable(clk);
+
+	/* clear the context */
+	atomic_set(&enable_context, 0);
+
+	/* release the framework-wide lock, context == NULL */
 	spin_unlock_irqrestore(&enable_lock, flags);
 }
 EXPORT_SYMBOL_GPL(clk_disable);
@@ -745,10 +787,29 @@  int clk_enable(struct clk *clk)
 	unsigned long flags;
 	int ret;
 
+	/* this call re-enters if it is from the same context */
+	if (spin_is_locked(&enable_lock)) {
+		if ((void *) atomic_read(&enable_context) == get_current()) {
+			ret = __clk_enable(clk);
+			goto out;
+		}
+	}
+
+	/* hold the framework-wide lock, context == NULL */
 	spin_lock_irqsave(&enable_lock, flags);
+
+	/* set context for any reentrant calls */
+	atomic_set(&enable_context, (int) get_current());
+
+	/* enable the clock(s) */
 	ret = __clk_enable(clk);
-	spin_unlock_irqrestore(&enable_lock, flags);
 
+	/* clear the context */
+	atomic_set(&enable_context, 0);
+
+	/* release the framework-wide lock, context == NULL */
+	spin_unlock_irqrestore(&enable_lock, flags);
+out:
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_enable);
@@ -792,10 +853,17 @@  long clk_round_rate(struct clk *clk, unsigned long rate)
 {
 	unsigned long ret;
 
-	mutex_lock(&prepare_lock);
+	/* this call re-enters if it is from the same context */
+	if (clk_is_reentrant()) {
+		ret = __clk_round_rate(clk, rate);
+		goto out;
+	}
+
+	clk_fwk_lock();
 	ret = __clk_round_rate(clk, rate);
-	mutex_unlock(&prepare_lock);
+	clk_fwk_unlock();
 
+out:
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_round_rate);
@@ -877,6 +945,30 @@  static void __clk_recalc_rates(struct clk *clk, unsigned long msg)
 		__clk_recalc_rates(child, msg);
 }
 
+unsigned long __clk_get_rate(struct clk *clk)
+{
+	unsigned long ret;
+
+	if (!clk) {
+		ret = 0;
+		goto out;
+	}
+
+	if (clk->flags & CLK_GET_RATE_NOCACHE)
+		__clk_recalc_rates(clk, 0);
+
+	ret = clk->rate;
+
+	if (clk->flags & CLK_IS_ROOT)
+		goto out;
+
+	if (!clk->parent)
+		ret = 0;
+
+out:
+	return ret;
+}
+
 /**
  * clk_get_rate - return the rate of clk
  * @clk: the clk whose rate is being returned
@@ -889,14 +981,22 @@  unsigned long clk_get_rate(struct clk *clk)
 {
 	unsigned long rate;
 
-	mutex_lock(&prepare_lock);
+	/*
+	 * FIXME - any locking here seems heavy weight
+	 * can clk->rate be replaced with an atomic_t?
+	 * same logic can likely be applied to prepare_count & enable_count
+	 */
 
-	if (clk && (clk->flags & CLK_GET_RATE_NOCACHE))
-		__clk_recalc_rates(clk, 0);
+	if (clk_is_reentrant()) {
+		rate = __clk_get_rate(clk);
+		goto out;
+	}
 
+	clk_fwk_lock();
 	rate = __clk_get_rate(clk);
-	mutex_unlock(&prepare_lock);
+	clk_fwk_unlock();
 
+out:
 	return rate;
 }
 EXPORT_SYMBOL_GPL(clk_get_rate);
@@ -1073,6 +1173,39 @@  static void clk_change_rate(struct clk *clk)
 		clk_change_rate(child);
 }
 
+int __clk_set_rate(struct clk *clk, unsigned long rate)
+{
+	int ret = 0;
+	struct clk *top, *fail_clk;
+
+	/* bail early if nothing to do */
+	if (rate == clk->rate)
+		return 0;
+
+	if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
+		return -EBUSY;
+	}
+
+	/* calculate new rates and get the topmost changed clock */
+	top = clk_calc_new_rates(clk, rate);
+	if (!top)
+		return -EINVAL;
+
+	/* notify that we are about to change rates */
+	fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
+	if (fail_clk) {
+		pr_warn("%s: failed to set %s rate\n", __func__,
+				fail_clk->name);
+		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
+		return -EBUSY;
+	}
+
+	/* change the rates */
+	clk_change_rate(top);
+
+	return ret;
+}
+
 /**
  * clk_set_rate - specify a new rate for clk
  * @clk: the clk whose rate is being changed
@@ -1096,44 +1229,18 @@  static void clk_change_rate(struct clk *clk)
  */
 int clk_set_rate(struct clk *clk, unsigned long rate)
 {
-	struct clk *top, *fail_clk;
 	int ret = 0;
 
-	/* prevent racing with updates to the clock topology */
-	mutex_lock(&prepare_lock);
-
-	/* bail early if nothing to do */
-	if (rate == clk->rate)
-		goto out;
-
-	if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
-		ret = -EBUSY;
-		goto out;
-	}
-
-	/* calculate new rates and get the topmost changed clock */
-	top = clk_calc_new_rates(clk, rate);
-	if (!top) {
-		ret = -EINVAL;
-		goto out;
-	}
-
-	/* notify that we are about to change rates */
-	fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
-	if (fail_clk) {
-		pr_warn("%s: failed to set %s rate\n", __func__,
-				fail_clk->name);
-		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
-		ret = -EBUSY;
+	if (clk_is_reentrant()) {
+		ret = __clk_set_rate(clk, rate);
 		goto out;
 	}
 
-	/* change the rates */
-	clk_change_rate(top);
+	clk_fwk_lock();
+	ret = __clk_set_rate(clk, rate);
+	clk_fwk_unlock();
 
 out:
-	mutex_unlock(&prepare_lock);
-
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_set_rate);
@@ -1148,10 +1255,16 @@  struct clk *clk_get_parent(struct clk *clk)
 {
 	struct clk *parent;
 
-	mutex_lock(&prepare_lock);
+	if (clk_is_reentrant()) {
+		parent = __clk_get_parent(clk);
+		goto out;
+	}
+
+	clk_fwk_lock();
 	parent = __clk_get_parent(clk);
-	mutex_unlock(&prepare_lock);
+	clk_fwk_unlock();
 
+out:
 	return parent;
 }
 EXPORT_SYMBOL_GPL(clk_get_parent);
@@ -1330,6 +1443,7 @@  out:
 int clk_set_parent(struct clk *clk, struct clk *parent)
 {
 	int ret = 0;
+	bool reenter;
 
 	if (!clk || !clk->ops)
 		return -EINVAL;
@@ -1337,8 +1451,10 @@  int clk_set_parent(struct clk *clk, struct clk *parent)
 	if (!clk->ops->set_parent)
 		return -ENOSYS;
 
-	/* prevent racing with updates to the clock topology */
-	mutex_lock(&prepare_lock);
+	reenter = clk_is_reentrant();
+
+	if (!reenter)
+		clk_fwk_lock();
 
 	if (clk->parent == parent)
 		goto out;
@@ -1367,7 +1483,8 @@  int clk_set_parent(struct clk *clk, struct clk *parent)
 	__clk_reparent(clk, parent);
 
 out:
-	mutex_unlock(&prepare_lock);
+	if (!reenter)
+		clk_fwk_unlock();
 
 	return ret;
 }