Módulos do kernel carregáveis

Como parte dos requisitos do kernel do módulo introduzidos no Android 8.0, todos os kernels do sistema-em-chip (SoC) precisam oferecer suporte a módulos de kernel carregáveis.

Opções de configuração do kernel

Para oferecer suporte a módulos de kernel carregáveis, android-base.config em todos os kernels comuns inclui as seguintes opções de kernel-config (ou equivalentes da versão do kernel):

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

Todos os kernels do dispositivo precisam ativar essas opções. Os módulos do kernel também precisam oferecer suporte ao descarregamento e à recarga sempre que possível.

Assinatura de módulo

A assinatura de módulo não é compatível com módulos de fornecedores do GKI. Em dispositivos que precisam oferecer suporte à inicialização verificada, o Android exige que os módulos do kernel estejam nas partições com o dm-verity ativado. Isso elimina a necessidade de assinar módulos individuais para verificar a autenticidade deles. O Android 13 introduziu o conceito de módulos de GKI. Os módulos do GKI usam a infraestrutura de assinatura do tempo de build do kernel para diferenciar o GKI de outros módulos no momento da execução. Os módulos não assinados podem ser carregados, desde que usem apenas símbolos que aparecem na lista de permissões ou fornecidos por outros módulos não assinados. Para facilitar a assinatura de módulos do GKI durante o build do GKI usando o par de chaves do kernel, a configuração do kernel do GKI ativou CONFIG_MODULE_SIG_ALL=y. Para evitar a assinatura de módulos que não são GKI durante builds do kernel do dispositivo, adicione # CONFIG_MODULE_SIG_ALL is not set como parte dos fragmentos de configuração do kernel.

Locais de arquivos

Embora o Android 7.x e versões anteriores não exijam módulos do kernel (e incluam suporte a insmod e rmmod), o Android 8.x e versões mais recentes recomendam o uso de módulos do kernel no ecossistema. A tabela a seguir mostra o possível suporte a periféricos específicos da placa necessário em três modos de inicialização do Android.

Modo de inicialização Armazenamento Tela Teclado Bateria PMIC Tela touchscreen NFC, Wi-Fi,
Bluetooth
Sensores Câmera
Recuperação
Carregador
Android

Além da disponibilidade nos modos de inicialização do Android, os módulos do kernel também podem ser categorizados por quem os possui (o fornecedor do SoC ou o ODM). Se módulos do kernel estiverem sendo usados, os requisitos para a colocação deles no sistema de arquivos são os seguintes:

  • Todos os kernels precisam ter suporte integrado para inicialização e montagem de partições.
  • Os módulos do kernel precisam ser carregados de uma partição somente leitura.
  • Para dispositivos que precisam ter inicialização verificada, os módulos do kernel precisam ser carregados de partições verificadas.
  • Os módulos do kernel não podem estar localizados em /system.
  • Os módulos GKI necessários para o dispositivo precisam ser carregados de /system/lib/modules, que é um link simbólico para /system_dlkm/lib/modules.
  • Os módulos do kernel do fornecedor do SoC que são necessários para o Android completo ou os modos de carregador precisam estar localizados em /vendor/lib/modules.
  • Se uma partição ODM existir, os módulos do kernel do ODM que forem necessários para os modos Android completo ou carregador precisam estar localizados em /odm/lib/modules. Caso contrário, esses módulos precisam estar localizados em /vendor/lib/modules.
  • Os módulos do kernel do fornecedor do SoC e do ODM que são necessários para o modo de recuperação precisam estar localizados no ramfs de recuperação em /lib/modules.
  • Os módulos do kernel necessários para o modo de recuperação e os modos de carregador ou Android completo precisam existir na rootfs de recuperação e nas partições /vendor ou /odm (conforme descrito acima).
  • Os módulos do kernel usados no modo de recuperação não podem depender de módulos localizados apenas em /vendor ou /odm, porque essas partições não são montadas no modo de recuperação.
  • Os módulos do kernel do fornecedor do SoC não podem depender dos módulos do kernel do ODM.

No Android 7.x e versões anteriores, as partições /vendor e /odm não são montadas antecipadamente. No Android 8.x e versões mais recentes, para tornar possível o carregamento de módulos dessas partições, foram feitas provisões para montar partições com antecedência para dispositivos não A/B e A/B. Isso também garante que as partições sejam montadas nos modos Android e Carregador.

Suporte ao sistema de build do Android

No BoardConfig.mk, o build do Android define uma variável BOARD_VENDOR_KERNEL_MODULES que fornece uma lista completa dos módulos do kernel destinados à imagem do fornecedor. Os módulos listados nesta variável são copiados para a imagem do fornecedor em /lib/modules/ e, depois de serem montados no Android, aparecem em /vendor/lib/modules (de acordo com os requisitos acima). Exemplo de configuração dos módulos do kernel do fornecedor:

vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_VENDOR_KERNEL_MODULES := \
  $(vendor_lkm_dir)/vendor_module_a.ko \
  $(vendor_lkm_dir)/vendor_module_b.ko \
  $(vendor_lkm_dir)/vendor_module_c.ko

Neste exemplo, um repositório pré-criado de módulo do kernel do fornecedor é mapeado para o build do Android no local listado acima.

A imagem de recuperação pode conter um subconjunto dos módulos do fornecedor. O build do Android define a variável BOARD_RECOVERY_KERNEL_MODULES para esses módulos. Exemplo:

vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_RECOVERY_KERNEL_MODULES := \
  $(vendor_lkm_dir)/vendor_module_a.ko \
  $(vendor_lkm_dir)/vendor_module_b.ko

O build do Android cuida da execução de depmod para gerar os arquivos modules.dep necessários em /vendor/lib/modules e /lib/modules (recovery ramfs).

Carregamento e controle de versão do módulo

Carregue todos os módulos do kernel em uma única passagem de init.rc* chamando modprobe -a. Isso evita a sobrecarga de inicialização repetida do ambiente de execução C para o binário modprobe. O evento early-init pode ser modificado para invocar modprobe:

on early-init
    exec u:r:vendor_modprobe:s0 -- /vendor/bin/modprobe -a -d \
        /vendor/lib/modules module_a module_b module_c ...

Normalmente, um módulo do kernel precisa ser compilado com o kernel que o módulo será usado. Caso contrário, o kernel se recusa a carregar o módulo. O CONFIG_MODVERSIONS oferece uma solução alternativa detectando falhas na interface binária do aplicativo (ABI). Esse recurso calcula um valor de verificação de redundância cíclica (CRC, na sigla em inglês) para o protótipo de cada símbolo exportado no kernel e armazena os valores como parte dele. Para símbolos usados por um módulo do kernel, os valores também são armazenados no módulo do kernel. Quando o módulo é carregado, os valores dos símbolos usados pelo módulo são comparados com os do kernel. Se os valores corresponderem, o módulo será carregado. Caso contrário, a carga falhará.

Para ativar a atualização da imagem do kernel separadamente da imagem do fornecedor, ative CONFIG_MODVERSIONS. Isso permite pequenas atualizações do kernel (como correções de bugs do LTS) mantendo a compatibilidade com os módulos de kernel existentes na imagem do fornecedor. No entanto, CONFIG_MODVERSIONS não corrige uma interrupção de ABI por conta própria. Se o protótipo de um símbolo exportado no kernel mudar, seja devido à modificação da origem ou porque a configuração do kernel mudou, isso vai interromper a compatibilidade com os módulos do kernel que usam esse símbolo. Nesses casos, o módulo do kernel precisa ser recompilado.

Por exemplo, a estrutura task_struct no kernel (definida em include/linux/sched.h) contém muitos campos incluídos condicionalmente dependendo da configuração do kernel. O campo sched_info só está presente se CONFIG_SCHED_INFO estiver ativado, o que ocorre quando CONFIG_SCHEDSTATS ou CONFIG_TASK_DELAY_ACCT estão ativados. Se essas opções de configuração mudarem de estado, o layout da estrutura task_struct vai mudar, e todas as interfaces exportadas do kernel que usam task_struct serão alteradas (por exemplo, set_cpus_allowed_ptr em kernel/sched/core.c). A compatibilidade com módulos de kernel compilados anteriormente que usam essas interfaces é interrompida, exigindo que esses módulos sejam recriados com a nova configuração do kernel.

Para mais detalhes sobre CONFIG_MODVERSIONS, consulte a documentação na árvore do kernel em Documentation/kbuild/modules.rst.