Subject: dove (marvell A510) crash on boot with config_preempt

From: [email-masked] (Sebastian Hesselbarth)
Date: Thu, 10 Jul 2014 22:55:07 +0200

On 07/10/2014 02:33 PM, Sebastian Hesselbarth wrote:
> On 07/08/2014 05:17 PM, Jason Cooper wrote:
>> On Sun, Jul 06, 2014 at 08:08:45AM +0200, Jean-Francois Moine wrote:
>>> Since the official 3.15.0 release, the kernel crashes at boot time
>>> when compiled with the option CONFIG_PREEMPT.
>>>
>>> Reverting the commit 431a84b1a4f7d1a0085d5b91330c5053cc8e8b12
>>>
>>>     ARM: 8034/1: Disable preemption in iwmmxt_task_enable()
>>>
>>> removes the problem.
>>>
>>> Linux version 3.16.0-rc3-00062-gd92a333-dirty (jef at armhf) (gcc
>>> version 4.8.3 (Debian 4.8.3-4) ) #5 PREEMPT Thu Jul 3 19:46:39 CEST 2014
> [...]
>>> PJ4 iWMMXt v2 coprocessor enabled.
> [...]
>>> Unable to handle kernel paging request at virtual address fffffffe
>>> pgd = bb25c000
>>> [fffffffe] *pgd=3bfde821, *pte=00000000, *ppte=00000000
>>> Internal error: Oops: 80000007 [#1] PREEMPT ARM
>>> Modules linked in:
>>> CPU: 0 PID: 62 Comm: startpar Not tainted
>>> 3.16.0-rc3-00062-gd92a333-dirty #5
>>> task: bb230b80 ti: bb256000 task.ti: bb256000
>>> PC is at 0xfffffffe
>>> LR is at iwmmxt_task_copy+0x44/0x4c
>>> pc : [<fffffffe>]    lr : [<800130ac>]    psr: 40000033
>>> sp : bb257de8  ip : 00000013  fp : bb257ea4
>>> r10: bb256000  r9 : fffffdfe  r8 : 76e898e6
>>> r7 : bb257ec8  r6 : bb256000  r5 : 7ea12760  r4 : 000000a0
>>> r3 : ffffffff  r2 : 00000003  r1 : bb257df8  r0 : 00000000
>>> Flags: nZcv  IRQs on  FIQs on  Mode SVC_32  ISA Thumb  Segment user
>>> Control: 10c5387d  Table: 3b25c019  DAC: 00000015
>>> Process startpar (pid: 62, stack limit = 0xbb256248)
> 
> Ok, I have been able to debug this despite my limited knowledge of
> iWMMXt and ARM asm. While the patch below fixes the issue, I have
> no clue if it is the right approach or if there should be a different
> solution. I'd like to leave that to either Russell or Catalin to decide.

After thinking a while about it and because I missed it to mention:

I did a bisect which ends in

commit 1fb333489fb917c704ad43e51b45c12f52215a9c
 ("Merge branches 'alignment', 'fixes', 'l2c' (early part) and 'misc'
into for-next")

which clearly isn't the offending commit itself but finally causing
iwmmxt code to show the issue.

I compared introduced {inc,dec}_preempt_count macros

.macro  inc_preempt_count, ti, tmp
ldr     \tmp, [\ti, #TI_PREEMPT]        @ get preempt count
add     \tmp, \tmp, #1                  @ increment it
str     \tmp, [\ti, #TI_PREEMPT]
.endm

.macro  dec_preempt_count, ti, tmp
ldr     \tmp, [\ti, #TI_PREEMPT]        @ get preempt count
sub     \tmp, \tmp, #1                  @ decrement it
str     \tmp, [\ti, #TI_PREEMPT]
.endm

with common C defines for preempt_{disable,enable}

#define preempt_disable() \
do { \
        preempt_count_inc(); \
        barrier(); \
} while (0)

#define preempt_enable() \
do { \
        barrier(); \
        preempt_count_dec(); \
} while (0)

and wondered about the missing barriers.

The thing about iwmmxt.S is that it is assembled with -mcpu=iwmmxt
causing the assembler to drop down to xscale instructions (?) which
don't allow any atomic operations.

Anyway, I may be wrong about it. At least I wanted to mention that
bisect ends in above merge commit of l2c related cleanup and not
the iwmmxt preempt commit itself.

Sebastian

> If anything in below explanation is wrong, please correct me
> immediately!
> 
> Above mentioned commit basically added {inc,dec}_preempt_count macros
> to iwmmxt_task_enable to run it with preemption disabled:
> 
>  ENTRY(iwmmxt_task_enable)
> +       inc_preempt_count r10, r3
> [...]
> concan_save:
> [...]
> concan_dump:
> [...]
> concan_load:
> [...]
> +3:
> +#ifdef CONFIG_PREEMPT_COUNT
> +       get_thread_info r10
> +#endif
> +4:     dec_preempt_count r10, r3
>         mov     pc, lr
> 
> Unfortunately, other procedures in iwmmxt.S, e.g. iwmmxt_task_copy,
> also branch to above concan_{save,dump,load} labels without disabling
> preemption first:
> 
> ENTRY(iwmmxt_task_copy)
> [...]
> 1:    @ this task owns Concan regs -- grab a copy from there
>     mov    r0, #0            @ nothing to load
>     mov    r2, #3            @ save all regs
>     mov    r3, lr            @ preserve return address
>     bl    concan_dump
>     msr    cpsr_c, ip        @ restore interrupt mode
>     mov    pc, r3
> 
> This causes two issues that finally lead to observed behavior:
> (a) introduced {inc,dec}_preempt_count use r3 as temporary register,
>     while iwmmxt_task_copy uses it to store its return address
> (b) branching to concan_foo labels decrements preempt_count without
>     incrementing it first
> 
> The patch below addresses (a) by using r4 as temporary register for
> {inc,dec}_preempt_count macro and (b) by moving concan_foo into
> separate code sections and call them from iwmmxt_task_enable like
> the other procedures do.