Subject: [PATCH v3] ARM: SMP_TWD: make setup()/stop() reentrant

From: [email-masked] (Santosh Shilimkar)
Date: Mon, 22 Oct 2012 18:06:51 +0530

Hi Linus,

On Monday 22 October 2012 03:40 PM, Linus Walleij wrote:
> From: Linus Walleij <[email-masked]>
>
> It has been brought to my knowledge that the .setup()/.stop()
> function pair in the SMP TWD is going to be called from atomic
> contexts for CPUs coming and going, and then the
> clk_prepare()/clk_unprepare() calls cannot be called
> on subsequent .setup()/.stop() iterations. This is however
> just the tip of an iceberg as the function pair is not
> designed to be reentrant at all.
>
> This change makes the SMP_TWD clock .setup()/.stop() pair reentrant
> by splitting the .setup() function in three parts:
>
> - One COMMON part that is executed the first time the first CPU
>    in the TWD cluster is initialized. This will fetch the TWD
>    clk for the cluster and prepare+enable it. If no clk is
>    available it will calibrate the rate instead.
>
> - One part that is executed the FIRST TIME a certain CPU is
>    brought on-line. This initializes and sets up the clock event
>    for a certain CPU.
>
> - One part that is executed on every subsequent .setup() call.
>    This will re-initialize the clock event. This is augmented
>    to call the clk_enable()/clk_disable() pair properly.
>
> Cc: Shawn Guo <[email-masked]>
> Reported-by: Peter Chen <[email-masked]>
> Signed-off-by: Linus Walleij <[email-masked]>
> ---
> ChangeLog v2->v3:
> - Split setup() in three parts
> - re-register the clock event on every setup()
> ---
Patch largely looks good to me. Few comments

>   arch/arm/kernel/smp_twd.c | 45 ++++++++++++++++++++++++++++++++++++++++-----
>   1 file changed, 40 insertions(+), 5 deletions(-)
>
> diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c
> index b92d524..73e25e2 100644
> --- a/arch/arm/kernel/smp_twd.c
> +++ b/arch/arm/kernel/smp_twd.c
> @@ -31,6 +31,8 @@ static void __iomem *twd_base;
>
>   static struct clk *twd_clk;
>   static unsigned long twd_timer_rate;
> +static bool common_setup_called;
> +static DEFINE_PER_CPU(bool, percpu_setup_called);
>
>   static struct clock_event_device __percpu **twd_evt;
>   static int twd_ppi;
> @@ -93,6 +95,8 @@ static void twd_timer_stop(struct clock_event_device *clk)
>   {
>   	twd_set_mode(CLOCK_EVT_MODE_UNUSED, clk);
>   	disable_percpu_irq(clk->irq);
> +	if (!IS_ERR(twd_clk))
> +		clk_disable(twd_clk);
Is this really needed? This clock disable is bogus since it
can not really disable the clock.

>   }
>
>   #ifdef CONFIG_COMMON_CLK
> @@ -264,15 +268,46 @@ static struct clk *twd_get_clock(void)
>   static int __cpuinit twd_timer_setup(struct clock_event_device *clk)
>   {
>   	struct clock_event_device **this_cpu_clk;
> +	int cpu = smp_processor_id();
>
> -	if (!twd_clk)
> +	/*
> +	 * If the basic setup for this CPU has been done before don't
> +	 * bother with the below.
> +	 */
> +	if (per_cpu(percpu_setup_called, cpu)) {
> +		if (!IS_ERR(twd_clk))
> +			clk_enable(twd_clk);
> +		__raw_writel(0, twd_base + TWD_TIMER_CONTROL);
> +		clockevents_register_device(*__this_cpu_ptr(twd_evt));
> +		enable_percpu_irq(clk->irq, 0);
> +		return 0;
> +	}
> +	per_cpu(percpu_setup_called, cpu) = true;
> +
> +	/*
> +	 * This stuff only need to be done once for the entire TWD cluster
> +	 * during the runtime of the system.
> +	 */
> +	if (!common_setup_called) {
>   		twd_clk = twd_get_clock();
>
Moving the 'common_setup_called' code under one helper
might be cleaner. No strong preference though.

> -	if (!IS_ERR_OR_NULL(twd_clk))
> -		twd_timer_rate = clk_get_rate(twd_clk);
> -	else
> -		twd_calibrate_rate();
> +		/*
> +		 * We use IS_ERR_OR_NULL() here, because if the clock stubs
> +		 * are active we will get a valid clk reference which is
> +		 * however NULL and will return the rate 0. In that case we
> +		 * need to calibrate the rate instead.
> +		 */
> +		if (!IS_ERR_OR_NULL(twd_clk))
> +			twd_timer_rate = clk_get_rate(twd_clk);
> +		else
> +			twd_calibrate_rate();
> +	}
> +	common_setup_called = true;
>
So "common_setup_called" will get updated every time the
twd_timer_setup() gets called. You can move this inside
the if loop.

regards
Santosh