Uma correção descrita para o trampolim rethook do LoongArch remove o salvamento e a restauração do registrador de base percpu $r21 (também $u0) (fonte). A alteração toca arch/loongarch/kernel/rethook_trampoline.S, com 0 linhas adicionadas e 2 linhas removidas.

A convenção do registrador

No LoongArch, a convenção do kernel é que $r21 (também $u0) guarde o endereço-base percpu da CPU atual durante a execução em modo kernel. Nesse período, o registrador deve apontar sempre para a área percpu do núcleo em execução (fonte).

Causa e sintomas

O trampolim rethook salvava $r21 na entrada e o restaurava na saída. Se a tarefa migrasse de CPU dentro do handler, o endereço-base lido da pilha era o antigo e voltava ao registrador; assim, uma tarefa que passasse a rodar em outra CPU ficava com a base percpu da CPU anterior (fonte).

Os sintomas descritos incluem falhas de scheduling-while-atomic, avisos do RCU de execução na CPU errada, falha na verificação de balanceamento de carga de CPU ociosa em WARN_ON_ONCE(rq != this_rq()) e travamento total de CPUs porque os temporizadores deixavam de ser reiniciados no laço ocioso (fonte).

Quando o defeito aparecia

O disparo exigia três condições simultâneas: hardware LoongArch, um módulo que colocasse sondas em funções do kernel chamadas com frequência e um kernel compilado com preempção completa (preempt=full) (fonte).

O defeito vem da implementação antiga de kretprobe no Linux 6.3 e foi copiado na transição para rethook (fonte).

Correção e contorno

A correção remove o salvamento e a restauração de $r21 no trampolim rethook, porque o registrador já contém a base correta da CPU atual depois da migração (fonte).

Como contorno temporário, o registro indica acrescentar preempt=none ou preempt=voluntary aos parâmetros de inicialização do kernel, para evitar o ponto de cessão que dispara o escalonamento. Isso exige um kernel compilado com PREEMPT_DYNAMIC; sem essa opção, o parâmetro de inicialização não tem efeito (fonte).

O PREEMPT_DYNAMIC, introduzido no Linux 5.12, permite sobrescrever no boot, pelo parâmetro preempt=, o modo de preempção escolhido na compilação, sem recompilar o kernel (fonte).

Inspiração do tema: hands-on-deepin-loongarch-kernel-fixes.