Desenvolver código do kernel para GKI

A imagem genérica do kernel (GKI) reduz a fragmentação do kernel ao se alinhar com o kernel do Linux upstream. No entanto, há motivos válidos para que alguns patches não sejam aceitos upstream, e há cronogramas de produtos que precisam ser atendidos. Portanto, alguns patches são mantidos nas fontes do kernel comum do Android (ACK, na sigla em inglês) em que o GKI é criado.

Os desenvolvedores precisam enviar as mudanças de código upstream usando a lista de e-mails do kernel do Linux (LKML, na sigla em inglês) como primeira escolha e enviar as mudanças de código para o ramo android-mainline do ACK somente quando houver um motivo forte para que o upstream não seja viável. Confira abaixo exemplos de motivos válidos e como lidar com eles.

  • O patch foi enviado ao LKML, mas não foi aceito a tempo para um lançamento do produto. Para processar esse patch:

    • Forneça evidências de que o patch foi enviado ao LKML e comentários recebidos para o patch ou um tempo estimado em que o patch foi enviado para upstream.
    • Decida um curso de ação para implantar o patch no ACK, aprová-lo upstream e, em seguida, removê-lo do ACK quando a versão final upstream for fundida no ACK.
  • O patch define EXPORT_SYMBOLS_GPL() para um módulo do fornecedor, mas não pode ser enviado para upstream porque não há módulos na árvore que consumam esse símbolo. Para processar esse patch, forneça detalhes sobre por que o módulo não pode ser enviado para upstream e as alternativas que você considerou antes de fazer essa solicitação.

  • O patch não é genérico o suficiente para o upstream e não há tempo para refatorá-lo antes do lançamento de um produto. Para processar esse patch, forneça um tempo estimado para que um patch reformulado seja enviado upstream. O patch não será aceito no ACK sem um plano para enviar um patch reformulado upstream para revisão.

  • O patch não pode ser aceito pelo upstream porque... <insert reason here>. Para processar esse patch, entre em contato com a equipe do kernel do Android e trabalhe com a gente nas opções para refatorar o patch para que ele possa ser enviado para revisão e aceito upstream.

Há muitas outras justificativas possíveis. Ao enviar seu bug ou patch, inclua uma justificativa válida e espere alguma iteração e discussão. Sabemos que o ACK tem alguns patches, especialmente nas fases iniciais do GKI, enquanto todos estão aprendendo a trabalhar upstream, mas não podem relaxar os cronogramas de produtos para fazer isso. Os requisitos de upstream vão se tornar mais rigorosos com o tempo.

Requisitos de patch

Os patches precisam estar em conformidade com os padrões de codificação do kernel do Linux descritos na árvore de origem do Linux, sejam eles enviados para o upstream ou para o ACK. O script scripts/checkpatch.pl é executado como parte dos testes de pré-envio do Gerrit. Portanto, execute-o com antecedência para garantir que ele seja aprovado. Para executar o script checkpatch com a mesma configuração do teste de pré-envio, use //build/kernel/static_analysis:checkpatch_presubmit. Para mais detalhes, consulte build/kernel/kleaf/docs/checkpatch.md.

Patches ACK

Os patches enviados para o ACK precisam estar em conformidade com os padrões de programação do kernel do Linux e as diretrizes de contribuição. É necessário incluir uma tag Change-Id na mensagem de confirmação. Se você enviar o patch para várias ramificações (por exemplo, android-mainline e android12-5.4), use o mesmo Change-Id para todas as instâncias do patch.

Envie os patches primeiro para o LKML para uma análise upstream. Se o patch for:

  • Aceito upstream, ele é mesclado automaticamente em android-mainline.
  • Não foi aceito upstream. Envie para android-mainline com uma referência ao envio upstream ou uma explicação de por que ele não foi enviado para o LKML.

Depois que um patch é aceito upstream ou em android-mainline, ele pode ser retornado ao ACK baseado em LTS apropriado (como android12-5.4 e android11-5.4 para patches que corrigem o código específico do Android). O envio para android-mainline permite testes com novos candidatos de lançamento upstream e garante que o patch esteja na próxima ACK baseada em LTS. As exceções incluem casos em que um patch upstream é transferido de volta para android12-5.4 porque é provável que ele já esteja em android-mainline.

Patches upstream

Conforme especificado nas diretrizes de contribuição, os patches upstream destinados a núcleos ACK se enquadram nos seguintes grupos (listados em ordem de probabilidade de aceitação).

  • UPSTREAM:: é provável que os patches selecionados de "android-mainline" sejam aceitos no ACK se houver um caso de uso razoável.
  • BACKPORT:: os patches de upstream que não são escolhidos de forma limpa e precisam de modificação também podem ser aceitos se houver um caso de uso razoável.
  • FROMGIT:: patches escolhidos de uma ramificação de mantenedor em preparação para envio à linha principal do Linux podem ser aceitos se houver um prazo próximo. Elas precisam ser justificadas para o conteúdo e a programação.
  • FROMLIST:: é improvável que patches enviados para o LKML, mas que ainda não foram aceitos em uma ramificação de mantenedor, sejam aceitos, a menos que a justificativa seja convincente o suficiente para que o patch seja aceito independente de ser ou não lançado no Linux upstream (assumimos que não será). É preciso haver um problema associado aos patches FROMLIST para facilitar a discussão com a equipe do kernel do Android.

Patches específicos do Android

Se não for possível fazer as mudanças necessárias na upstream, tente enviar patches fora da árvore para o ACK diretamente. O envio de patches fora da árvore exige que você crie um problema no TI que cite o patch e o motivo pelo qual ele não pode ser enviado para upstream (consulte a lista anterior para ver exemplos). No entanto, há alguns casos em que o código não pode ser enviado para upstream. Esses casos são cobertos da seguinte maneira e precisam seguir as diretrizes de contribuição para patches específicos do Android e serem marcados com o prefixo ANDROID: no assunto.

Mudanças em gki_defconfig

Todas as mudanças de CONFIG para gki_defconfig precisam ser aplicadas às versões arm64 e x86, a menos que a CONFIG seja específica da arquitetura. Para solicitar uma mudança na configuração CONFIG, crie um problema na equipe de TI para discutir a mudança. Qualquer mudança de CONFIG que afete a interface do módulo do kernel (KMI, na sigla em inglês) depois de congelada é rejeitada. Nos casos em que os parceiros solicitam configurações conflitantes para uma única configuração, resolvemos os conflitos em discussões sobre os bugs relacionados.

Código que não existe na upstream

Modificações em códigos que já são específicos do Android não podem ser enviadas para upstream. Por exemplo, embora o driver de vinculação seja mantido upstream, as modificações nos recursos de herança de prioridade do driver de vinculação não podem ser enviadas upstream porque são específicas do Android. Seja explícito no bug e no patch sobre por que o código não pode ser enviado para upstream. Se possível, divida os patches em partes que podem ser enviadas para o upstream e partes específicas do Android que não podem ser enviadas para o upstream para minimizar a quantidade de código fora da árvore mantido no ACK.

Outras mudanças nessa categoria são atualizações em arquivos de representação de KMI, listas de símbolos de KMI, gki_defconfig, scripts de build ou configuração ou outros scripts que não existem upstream.

Módulos fora da árvore

O upstream Linux desencoraja ativamente o suporte para a criação de módulos fora da árvore. Essa é uma posição razoável, já que os mantenedores do Linux não garantem a compatibilidade binária ou de origem no kernel e não querem oferecer suporte ao código que não está na árvore. No entanto, o GKI faz garantias de ABI para módulos do fornecedor, garantindo que as interfaces KMI sejam estáveis para a vida útil apoiada de um kernel. Portanto, há uma classe de mudanças para oferecer suporte a módulos do fornecedor que são aceitáveis para ACK, mas não para upstream.

Por exemplo, considere um patch que adiciona macros EXPORT_SYMBOL_GPL() em que os módulos que usam a exportação não estão na árvore de origem. Embora seja necessário tentar solicitar o upstream EXPORT_SYMBOL_GPL() e fornecer um módulo que use o símbolo exportado recentemente, se houver uma justificativa válida para o módulo não ser enviado para o upstream, você poderá enviar o patch para ACK. Você precisa incluir a justificativa de por que o módulo não pode ser enviado para upstream no problema. Não solicite a variante não GPL, EXPORT_SYMBOL().

Configurações ocultas

Alguns módulos na árvore selecionam automaticamente as configurações ocultas que não podem ser especificadas em gki_defconfig. Por exemplo, CONFIG_SND_SOC_TOPOLOGY é selecionado automaticamente quando CONFIG_SND_SOC_SOF=y é configurado. Para acomodar a criação de módulos fora da árvore, o GKI inclui um mecanismo para ativar as configurações ocultas.

Para ativar uma configuração oculta, adicione uma instrução select em init/Kconfig.gki para que ela seja selecionada automaticamente com base na configuração do kernel CONFIG_GKI_HACKS_TO_FIX, que é ativada em gki_defconfig. Use esse mecanismo apenas para configurações ocultas. Se a configuração não estiver oculta, ela precisará ser especificada em gki_defconfig, explicitamente ou como uma dependência.

Governadores carregáveis

Para frameworks de kernel (como cpufreq) que oferecem suporte a governantes de carga, é possível substituir o governante padrão (como o governante schedutil do cpufreq). Para estruturas (como a estrutura térmica) que não oferecem suporte a governantes ou drivers carregáveis, mas ainda exigem uma implementação específica do fornecedor, crie um problema na TI e consulte a equipe do kernel do Android.

Vamos trabalhar com você e os mantenedores upstream para adicionar o suporte necessário.

Hooks do fornecedor

Em versões anteriores, era possível adicionar modificações específicas do fornecedor diretamente ao kernel principal. Isso não é possível com a GKI 2.0 porque o código específico do produto precisa ser implementado em módulos e não será aceito no upstream dos kernels principais ou no ACK. Para ativar recursos de valor agregado em que os parceiros confiam com impacto mínimo no código do kernel principal, o GKI aceita ganchos de fornecedores que permitem que os módulos sejam invocados no código do kernel principal. Além disso, as estruturas de dados principais podem ser preenchidas com campos de dados do fornecedor que estão disponíveis para armazenar dados específicos do fornecedor e implementar esses recursos.

Os ganchos do fornecedor têm duas variantes (normal e restrita) baseadas em pontos de rastreamento (não eventos de rastreamento) aos quais os módulos do fornecedor podem se conectar. Por exemplo, em vez de adicionar uma nova função sched_exit() para fazer uma contabilidade na saída da tarefa, os fornecedores podem adicionar um hook em do_exit() que um módulo do fornecedor pode anexar para processamento. Um exemplo de implementação inclui os seguintes acionadores do fornecedor.

  • Os ganchos normais do fornecedor usam DECLARE_HOOK() para criar uma função de ponto de rastreamento com o nome trace_name, em que name é o identificador exclusivo do rastreamento. Por convenção, os nomes de acionadores normais do fornecedor começam com android_vh. Portanto, o nome do acionador sched_exit() seria android_vh_sched_exit.
  • Os hooks restritos do fornecedor são necessários para casos como hooks de agendador em que a função anexada precisa ser chamada mesmo que a CPU esteja off-line ou exija um contexto não atômico. Os hooks de fornecedores restritos não podem ser desconectados. Portanto, os módulos que se conectam a um hook restrito nunca podem ser descarregados. Os nomes de hooks de fornecedores restritos começam com android_rvh.

Para adicionar um gancho de fornecedor, registre um problema na TI e envie patches. Como acontece com todos os patches específicos do Android, é necessário que um problema exista e que você forneça uma justificativa. O suporte a ganchos do fornecedor está disponível apenas no ACK. Portanto, não envie esses patches para o Linux upstream.

Adicionar campos do fornecedor às estruturas

É possível associar dados do fornecedor a estruturas de dados importantes adicionando campos android_vendor_data usando as macros ANDROID_VENDOR_DATA(). Por exemplo, para oferecer suporte a recursos de valor agregado, anexe campos a estruturas, conforme mostrado no exemplo de código abaixo.

Para evitar possíveis conflitos entre os campos necessários pelos fornecedores e os necessários pelos OEMs, os OEMs nunca devem usar campos declarados usando macros ANDROID_VENDOR_DATA(). Em vez disso, os OEMs precisam usar ANDROID_OEM_DATA() para declarar campos android_oem_data.

#include <linux/android_vendor.h>
...
struct important_kernel_data {
  [all the standard fields];
  /* Create vendor data for use by hook implementations. The
   * size of vendor data is based on vendor input. Vendor data
   * can be defined as single u64 fields like the following that
   * declares a single u64 field named "android_vendor_data1" :
   */
  ANDROID_VENDOR_DATA(1);

  /*
   * ...or an array can be declared. The following is equivalent to
   * u64 android_vendor_data2[20]:
   */
  ANDROID_VENDOR_DATA_ARRAY(2, 20);

  /*
   * SoC vendors must not use fields declared for OEMs and
   * OEMs must not use fields declared for SoC vendors.
   */
  ANDROID_OEM_DATA(1);

  /* no further fields */
}

Definir hooks do fornecedor

Adicione ganchos do fornecedor ao código do kernel como pontos de rastreamento declarando-os usando DECLARE_HOOK() ou DECLARE_RESTRICTED_HOOK() e, em seguida, adicionando-os ao código como um ponto de rastreamento. Por exemplo, para adicionar trace_android_vh_sched_exit() à função de kernel do_exit() existente:

#include <trace/hooks/exit.h>
void do_exit(long code)
{
    struct task_struct *tsk = current;
    ...
    trace_android_vh_sched_exit(tsk);
    ...
}

A função trace_android_vh_sched_exit() verifica inicialmente apenas se algo está anexado. No entanto, se um módulo do fornecedor registrar um gerenciador usando register_trace_android_vh_sched_exit(), a função registrada será chamada. O gerenciador precisa estar ciente do contexto em relação a bloqueios mantidos, estado de RCS e outros fatores. O hook precisa ser definido em um arquivo de cabeçalho no diretório include/trace/hooks.

Por exemplo, o código a seguir mostra uma possível declaração para trace_android_vh_sched_exit() no arquivo include/trace/hooks/exit.h.

/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched
#define TRACE_INCLUDE_PATH trace/hooks

#if !defined(_TRACE_HOOK_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HOOK_SCHED_H
#include <trace/hooks/vendor_hooks.h>
/*
 * Following tracepoints are not exported in tracefs and provide a
 * mechanism for vendor modules to hook and extend functionality
 */

struct task_struct;

DECLARE_HOOK(android_vh_sched_exit,
             TP_PROTO(struct task_struct *p),
             TP_ARGS(p));

#endif /* _TRACE_HOOK_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

Para instanciar as interfaces necessárias para o gancho do fornecedor, adicione o arquivo de cabeçalho com a declaração de gancho a drivers/android/vendor_hooks.c e exporte os símbolos. Por exemplo, o código a seguir conclui a declaração do hook android_vh_sched_exit().

#ifndef __GENKSYMS__
/* struct task_struct */
#include <linux/sched.h>
#endif

#define CREATE_TRACE_POINTS
#include <trace/hooks/vendor_hooks.h>
#include <trace/hooks/exit.h>
/*
 * Export tracepoints that act as a bare tracehook (i.e. have no trace
 * event associated with them) to allow external modules to probe
 * them.
 */
EXPORT_TRACEPOINT_SYMBOL_GPL(android_vh_sched_exit);

OBSERVAÇÃO: as estruturas de dados usadas na declaração de hook precisam ser definidas totalmente para garantir a estabilidade da ABI. Caso contrário, não é seguro dereferênciar os ponteiros opacos ou usar a struct em contextos dimensionados. O include que fornece a definição completa dessas estruturas de dados precisa estar na seção #ifndef __GENKSYMS__ de drivers/android/vendor_hooks.c. Os arquivos de cabeçalho em include/trace/hooks não devem incluir o arquivo de cabeçalho do kernel com as definições de tipo para evitar mudanças de CRC que quebrem o KMI. Em vez disso, declare os tipos.

Anexar aos hooks do fornecedor

Para usar os Hooks do fornecedor, o módulo do fornecedor precisa registrar um gerenciador para o hook, normalmente feito durante a inicialização do módulo. Por exemplo, o código a seguir mostra o gerenciador foo.ko do módulo para trace_android_vh_sched_exit().

#include <trace/hooks/sched.h>
...
static void foo_sched_exit_handler(void *data, struct task_struct *p)
{
    foo_do_exit_accounting(p);
}
...
static int foo_probe(..)
{
    ...
    rc = register_trace_android_vh_sched_exit(foo_sched_exit_handler, NULL);
    ...
}

Usar ganchos de fornecedor em arquivos de cabeçalho

Para usar os ganchos do fornecedor em arquivos de cabeçalho, talvez seja necessário atualizar o arquivo de cabeçalho do gancho do fornecedor para redefinir TRACE_INCLUDE_PATH e evitar erros de build que indicam que um arquivo de cabeçalho de ponto de rastreamento não foi encontrado. Por exemplo:

In file included from .../common/init/main.c:111:
In file included from .../common/include/trace/events/initcall.h:74:
.../common/include/trace/define_trace.h:95:10: fatal error: 'trace/hooks/initcall.h' file not found
   95 | #include TRACE_INCLUDE(TRACE_INCLUDE_FILE)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:90:32: note: expanded from macro 'TRACE_INCLUDE'
   90 | # define TRACE_INCLUDE(system) __TRACE_INCLUDE(system)
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:87:34: note: expanded from macro '__TRACE_INCLUDE'
   87 | # define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h)
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:10:27: note: expanded from macro '__stringify'
   10 | #define __stringify(x...)       __stringify_1(x)
      |                                 ^~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:9:29: note: expanded from macro '__stringify_1'
    9 | #define __stringify_1(x...)     #x
      |                                 ^~
<scratch space>:14:1: note: expanded from here
   14 | "trace/hooks/initcall.h"
      | ^~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Para corrigir esse tipo de erro de build, aplique a correção equivalente ao arquivo de cabeçalho de gancho do fornecedor que você está incluindo. Para mais informações, consulte https://r.android.com/3066703.

diff --git a/include/trace/hooks/mm.h b/include/trace/hooks/mm.h
index bc6de7e53d66..039926f7701d 100644
--- a/include/trace/hooks/mm.h
+++ b/include/trace/hooks/mm.h
@@ -2,7 +2,10 @@
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mm

+#ifdef CREATE_TRACE_POINTS
 #define TRACE_INCLUDE_PATH trace/hooks
+#define UNDEF_TRACE_INCLUDE_PATH
+#endif

A definição de UNDEF_TRACE_INCLUDE_PATH informa a include/trace/define_trace.h para desdefinir TRACE_INCLUDE_PATH após a criação dos pontos de rastreamento.

Recursos principais do kernel

Se nenhuma das técnicas anteriores permitir a implementação de um recurso de um módulo, adicione o recurso como uma modificação específica do Android ao kernel principal. Crie um problema no rastreador de problemas (TI) para iniciar a conversa.

Interface de programação de aplicativos do usuário (UAPI)

  • Arquivos de cabeçalho da UAPI. As mudanças nos arquivos de cabeçalho da UAPI precisam ocorrer upstream, a menos que sejam mudanças em interfaces específicas do Android. Use arquivos de cabeçalho específicos do fornecedor para definir interfaces entre os módulos do fornecedor e o código do espaço do usuário do fornecedor.
  • nós sysfs. Não adicione novos nós sysfs ao kernel do GKI. Essas adições são válidas apenas em módulos de fornecedores. Os nós sysfs usados pelas bibliotecas não dependentes de SoC e dispositivo e pelo código Java que compõem o framework Android podem ser alterados apenas de maneiras compatíveis e precisam ser alterados upstream se não forem nós sysfs específicos do Android. É possível criar nós sysfs específicos do fornecedor para serem usados pelo espaço do usuário do fornecedor. Por padrão, o acesso aos nós do sysfs pelo espaço do usuário é negado usando o SELinux. Cabe ao fornecedor adicionar os rótulos SELinux apropriados para permitir o acesso por softwares autorizados do fornecedor.
  • Nós do DebugFS. Os módulos do fornecedor podem definir nós em debugfs apenas para depuração, já que debugfs não é montado durante a operação normal do dispositivo.