[PATCH v4] clk: allow reentrant calls into the clk framework
Mike Turquette
mturquette at linaro.org
Wed Mar 27 11:06:34 EDT 2013
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 at linaro.org>
> > Cc: Rajagopal Venkat <rajagopal.venkat at linaro.org>
> > Cc: David Brown <davidb at codeaurora.org>
> > Cc: Ulf Hansson <ulf.hansson at linaro.org>
> > Cc: Laurent Pinchart <laurent.pinchart at 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
More information about the linux-arm-kernel
mailing list