Use as diretrizes a seguir para aumentar a robustez e a confiabilidade dos módulos do seu fornecedor. Muitas diretrizes, quando seguidas, podem ajudar a facilitar a determinação da ordem correta de carregamento do módulo e a ordem na qual os drivers devem testar os dispositivos.
Um módulo pode ser uma biblioteca ou um driver .
Módulos de biblioteca são bibliotecas que fornecem APIs para uso de outros módulos. Esses módulos normalmente não são específicos de hardware. Exemplos de módulos de biblioteca incluem um módulo de criptografia AES, a estrutura
remoteproc
que é compilada como um módulo e um módulo logbuffer. O código do módulo emmodule_init()
é executado para configurar estruturas de dados, mas nenhum outro código é executado, a menos que seja acionado por um módulo externo.Módulos de driver são drivers que investigam ou se vinculam a um tipo específico de dispositivo. Esses módulos são específicos de hardware. Exemplos de módulos de driver incluem UART, PCIe e hardware codificador de vídeo. Os módulos de driver são ativados somente quando o dispositivo associado está presente no sistema.
Se o dispositivo não estiver presente, o único código do módulo executado será o código
module_init()
que registra o driver na estrutura principal do driver.Se o dispositivo estiver presente e o driver investigar ou vincular-se a esse dispositivo com êxito, outro código de módulo poderá ser executado.
Use o módulo init/exit corretamente
Os módulos de driver devem registrar um driver em module_init()
e cancelar o registro de um driver em module_exit()
. Uma maneira simples de impor essas restrições é usar macros wrapper, o que evita o uso direto de macros module_init()
, *_initcall()
ou module_exit()
.
Para módulos que podem ser descarregados, use
module_ subsystem _driver()
. Exemplos:module_platform_driver()
,module_i2c_driver()
emodule_pci_driver()
.Para módulos que não podem ser descarregados, use
builtin_ subsystem _driver()
Exemplos:builtin_platform_driver()
,builtin_i2c_driver()
ebuiltin_pci_driver()
.
Alguns módulos de driver usam module_init()
e module_exit()
porque registram mais de um driver. Para um módulo de driver que usa module_init()
e module_exit()
para registrar vários drivers, tente combinar os drivers em um único driver. Por exemplo, você pode diferenciar usando a string compatible
ou os dados auxiliares do dispositivo em vez de registrar drivers separados. Alternativamente, você pode dividir o módulo do driver em dois módulos.
Exceções de função de inicialização e saída
Os módulos da biblioteca não registram drivers e estão isentos de restrições em module_init()
e module_exit()
pois podem precisar dessas funções para configurar estruturas de dados, filas de trabalho ou threads de kernel.
Use a macro MODULE_DEVICE_TABLE
Os módulos de driver devem incluir a macro MODULE_DEVICE_TABLE
, que permite ao espaço do usuário determinar os dispositivos suportados por um módulo de driver antes de carregar o módulo. O Android pode usar esses dados para otimizar o carregamento de módulos, evitando o carregamento de módulos para dispositivos que não estão presentes no sistema. Para obter exemplos sobre como usar a macro, consulte o código upstream.
Evite incompatibilidades de CRC devido a tipos de dados declarados posteriormente
Não inclua arquivos de cabeçalho para obter visibilidade dos tipos de dados declarados posteriormente. Algumas estruturas, uniões e outros tipos de dados definidos em um arquivo de cabeçalho ( header-Ah
) podem ser declarados posteriormente em um arquivo de cabeçalho diferente ( header-Bh
) que normalmente usa ponteiros para esses tipos de dados. Este padrão de código significa que o kernel está tentando intencionalmente manter a estrutura de dados privada para os usuários de header-Bh
.
Os usuários de header-Bh
não devem incluir header-Ah
para acessar diretamente o interior dessas estruturas de dados declaradas adiante. Fazer isso causa problemas de incompatibilidade de CRC CONFIG_MODVERSIONS
(o que gera problemas de conformidade com ABI) quando um kernel diferente (como o kernel GKI) tenta carregar o módulo.
Por exemplo, struct fwnode_handle
é definido em include/linux/fwnode.h
, mas é declarado adiante como struct fwnode_handle;
em include/linux/device.h
porque o kernel está tentando manter os detalhes de struct fwnode_handle
privados dos usuários de include/linux/device.h
. Neste cenário, não adicione #include <linux/fwnode.h>
em um módulo para obter acesso aos membros da struct fwnode_handle
. Qualquer design no qual você precise incluir esses arquivos de cabeçalho indica um padrão de design incorreto.
Não acesse diretamente as estruturas principais do kernel
Acessar ou modificar diretamente as estruturas de dados principais do kernel pode levar a comportamentos indesejáveis, incluindo vazamentos de memória, travamentos e compatibilidade quebrada com versões futuras do kernel. Uma estrutura de dados é uma estrutura de dados central do kernel quando atende a qualquer uma das seguintes condições:
A estrutura de dados é definida em
KERNEL-DIR /include/
. Por exemplo,struct device
estruct dev_links_info
. As estruturas de dados definidas eminclude/linux/soc
estão isentas.A estrutura de dados é alocada ou inicializada pelo módulo, mas fica visível para o kernel ao ser passada, indiretamente (através de um ponteiro em uma estrutura) ou diretamente, como entrada em uma função exportada pelo kernel. Por exemplo, um módulo de driver
cpufreq
inicializa astruct cpufreq_driver
e a passa como entrada paracpufreq_register_driver()
. Após este ponto, o módulo do drivercpufreq
não deve modificarstruct cpufreq_driver
diretamente porque chamarcpufreq_register_driver()
tornastruct cpufreq_driver
visível para o kernel.A estrutura de dados não é inicializada pelo seu módulo. Por exemplo,
struct regulator_dev
retornado porregulator_register()
.
Acesse as principais estruturas de dados do kernel somente por meio de funções exportadas pelo kernel ou por meio de parâmetros passados explicitamente como entrada para ganchos do fornecedor. Se você não possui uma API ou gancho de fornecedor para modificar partes de uma estrutura de dados principal do kernel, provavelmente isso é intencional e você não deve modificar a estrutura de dados dos módulos. Por exemplo, não modifique nenhum campo dentro struct device
ou struct device.links
.
Para modificar
device.devres_head
, use uma funçãodevm_*()
comodevm_clk_get()
,devm_regulator_get()
oudevm_kzalloc()
.Para modificar campos dentro de
struct device.links
, use uma API de link de dispositivo comodevice_link_add()
oudevice_link_del()
.
Não analise nós do devicetree com propriedade compatível
Se um nó da árvore de dispositivos (DT) tiver uma propriedade compatible
, um struct device
será alocado para ele automaticamente ou quando of_platform_populate()
for chamado no nó DT pai (normalmente pelo driver de dispositivo do dispositivo pai). A expectativa padrão (exceto para alguns dispositivos inicializados antecipadamente para o agendador) é que um nó DT com uma propriedade compatible
tenha um struct device
e um driver de dispositivo correspondente. Todas as outras exceções já são tratadas pelo código upstream.
Além disso, fw_devlink
(anteriormente chamado de of_devlink
) considera os nós DT com a propriedade compatible
como dispositivos com um struct device
alocado que é testado por um driver. Se um nó DT tiver uma propriedade compatible
, mas o struct device
alocado não for testado, fw_devlink
poderá bloquear a investigação de seus dispositivos consumidores ou poderá impedir que chamadas sync_state()
sejam chamadas para seus dispositivos fornecedores.
Se o seu driver usar uma função of_find_*()
(como of_find_node_by_name()
ou of_find_compatible_node()
) para encontrar diretamente um nó DT que tenha uma propriedade compatible
e, em seguida, analisar esse nó DT, corrija o módulo escrevendo um driver de dispositivo que possa testar o dispositivo ou remover a propriedade compatible
(possível apenas se não tiver sido upstreamed). Para discutir alternativas, entre em contato com a equipe do kernel do Android em kernel-team@android.com e esteja preparado para justificar seus casos de uso.
Use phandles DT para procurar fornecedores
Consulte um fornecedor usando um phandle (uma referência/ponteiro para um nó DT) em DT sempre que possível. O uso de ligações e phandles de DT padrão para se referir a fornecedores permite que fw_devlink
(anteriormente of_devlink
) determine automaticamente dependências entre dispositivos analisando o DT em tempo de execução. O kernel pode então testar automaticamente os dispositivos na ordem correta, eliminando a necessidade de ordenação de carregamento de módulo ou MODULE_SOFTDEP()
.
Cenário legado (sem suporte DT no kernel ARM)
Anteriormente, antes de o suporte DT ser adicionado aos kernels ARM, os consumidores, como dispositivos de toque, procuravam fornecedores, como reguladores, usando strings exclusivas globalmente. Por exemplo, o driver ACME PMIC pode registrar ou anunciar vários reguladores (como acme-pmic-ldo1
para acme-pmic-ldo10
) e um driver de toque pode procurar um regulador usando regulator_get(dev, "acme-pmic-ldo10")
. No entanto, em uma placa diferente, o LDO8 pode fornecer o dispositivo de toque, criando um sistema complicado onde o mesmo driver de toque precisa determinar a cadeia de pesquisa correta para o regulador para cada placa em que o dispositivo de toque é usado.
Cenário atual (suporte DT no kernel ARM)
Depois que o suporte DT foi adicionado aos kernels ARM, os consumidores podem identificar fornecedores no DT consultando o nó da árvore de dispositivos do fornecedor usando um phandle . Os consumidores também podem nomear o recurso com base na finalidade para a qual ele é usado, e não em quem o fornece. Por exemplo, o driver de toque do exemplo anterior poderia usar regulator_get(dev, "core")
e regulator_get(dev, "sensor")
para obter os fornecedores que alimentam o núcleo e o sensor do dispositivo de toque. O DT associado para tal dispositivo é semelhante ao seguinte exemplo de código:
touch-device {
compatible = "fizz,touch";
...
core-supply = <&acme_pmic_ldo4>;
sensor-supply = <&acme_pmic_ldo10>;
};
acme-pmic {
compatible = "acme,super-pmic";
...
acme_pmic_ldo4: ldo4 {
...
};
...
acme_pmic_ldo10: ldo10 {
...
};
};
Cenário do pior dos dois mundos
Alguns drivers portados de kernels mais antigos incluem comportamento legado no DT que pega a pior parte do esquema legado e força-o no esquema mais novo que visa facilitar as coisas. Nesses drivers, o driver do consumidor lê a string a ser usada para pesquisa usando uma propriedade DT específica do dispositivo, o fornecedor usa outra propriedade específica do fornecedor para definir o nome a ser usado para registrar o recurso do fornecedor, então o consumidor e o fornecedor continuam usando o mesmo velho esquema de usar strings para procurar o fornecedor. Neste cenário do pior dos dois mundos:
O driver de toque usa código semelhante ao seguinte:
str = of_property_read(np, "fizz,core-regulator"); core_reg = regulator_get(dev, str); str = of_property_read(np, "fizz,sensor-regulator"); sensor_reg = regulator_get(dev, str);
O DT usa código semelhante ao seguinte:
touch-device { compatible = "fizz,touch"; ... fizz,core-regulator = "acme-pmic-ldo4"; fizz,sensor-regulator = "acme-pmic-ldo4"; }; acme-pmic { compatible = "acme,super-pmic"; ... ldo4 { regulator-name = "acme-pmic-ldo4" ... }; ... acme_pmic_ldo10: ldo10 { ... regulator-name = "acme-pmic-ldo10" }; };
Não modifique erros da API da estrutura
APIs de estrutura, como regulator
, clocks
, irq
, gpio
, phys
e extcon
, retornam -EPROBE_DEFER
como um valor de retorno de erro para indicar que um dispositivo está tentando testar, mas não pode neste momento, e o kernel deve tentar novamente a investigação mais tarde. Para garantir que a função .probe()
do seu dispositivo falhe conforme o esperado nesses casos, não substitua ou remapeie o valor do erro. Substituir ou remapear o valor do erro pode fazer com que -EPROBE_DEFER
seja descartado e fazer com que seu dispositivo nunca seja investigado.
Use variantes da API devm_*()
Quando o dispositivo adquire um recurso usando uma API devm_*()
, o recurso é liberado automaticamente pelo kernel se o dispositivo falhar na investigação ou se a investigação for bem-sucedida e posteriormente for desvinculado. Essa funcionalidade torna o código de tratamento de erros na função probe()
mais limpo porque não requer saltos goto
para liberar os recursos adquiridos por devm_*()
e simplifica as operações de desvinculação do driver.
Lidar com a desvinculação do driver de dispositivo
Seja intencional ao desvincular drivers de dispositivo e não deixe a desvinculação indefinida porque indefinido não significa não permitido. Você deve implementar totalmente a desvinculação do driver de dispositivo ou desabilitar explicitamente a desvinculação do driver de dispositivo.
Implementando desvinculação de driver de dispositivo
Ao optar por implementar totalmente a desvinculação do driver de dispositivo, desvincule os drivers de dispositivo de forma limpa para evitar vazamentos de memória ou recursos e problemas de segurança. Você pode vincular um dispositivo a um driver chamando a função probe()
do driver e desvincular um dispositivo chamando a função remove()
do driver. Se não existir nenhuma função remove()
, o kernel ainda poderá desvincular o dispositivo; o núcleo do driver pressupõe que nenhum trabalho de limpeza será necessário ao driver quando ele se desvincular do dispositivo. Um driver que não está associado a um dispositivo não precisa fazer nenhum trabalho de limpeza explícito quando ambos os itens a seguir forem verdadeiros:
Todos os recursos adquiridos pela função
probe()
de um driver são por meio de APIsdevm_*()
.O dispositivo de hardware não precisa de uma sequência de desligamento ou desativação.
Nessa situação, o núcleo do driver lida com a liberação de todos os recursos adquiridos por meio de APIs devm_*()
. Se alguma das afirmações anteriores for falsa, o driver precisará executar a limpeza (liberar recursos e desligar ou desativar o hardware) ao se desvincular de um dispositivo. Para garantir que um dispositivo possa desvincular um módulo de driver corretamente, use uma das seguintes opções:
Se o hardware não precisar de uma sequência de desligamento ou desativação, altere o módulo do dispositivo para adquirir recursos usando APIs
devm_*()
.Implemente a operação do driver
remove()
na mesma estrutura da funçãoprobe()
e, em seguida, execute as etapas de limpeza usando a funçãoremove()
.
Desativar explicitamente a desvinculação do driver de dispositivo (não recomendado)
Ao optar por desabilitar explicitamente a desvinculação do driver de dispositivo, você precisa proibir a desvinculação e o descarregamento do módulo.
Para não permitir a desvinculação, defina o sinalizador
suppress_bind_attrs
comotrue
nastruct device_driver
do driver; essa configuração impede que os arquivosbind
eunbind
sejam exibidos no diretóriosysfs
do driver. O arquivounbind
é o que permite que o espaço do usuário acione a desvinculação de um driver de seu dispositivo.Para impedir o descarregamento do módulo, certifique-se de que o módulo tenha
[permanent]
emlsmod
. Ao não usarmodule_exit()
oumodule_XXX_driver()
, o módulo é marcado como[permanent]
.
Não carregue firmware de dentro da função de teste
O driver não deve carregar o firmware de dentro da função .probe()
, pois eles podem não ter acesso ao firmware se o driver testar antes que o flash ou o sistema de arquivos baseado em armazenamento permanente seja montado. Nesses casos, a API request_firmware*()
pode ficar bloqueada por um longo tempo e depois falhar, o que pode retardar desnecessariamente o processo de inicialização. Em vez disso, adie o carregamento do firmware para quando um cliente começar a usar o dispositivo. Por exemplo, um driver de vídeo pode carregar o firmware quando o dispositivo de vídeo é aberto.
Usar .probe()
para carregar firmware pode ser adequado em alguns casos, como em um driver de relógio que precisa de firmware para funcionar, mas o dispositivo não está exposto ao espaço do usuário. Outros casos de uso apropriados são possíveis.
Implementar sondagem assíncrona
Dê suporte e use testes assíncronos para aproveitar melhorias futuras, como carregamento de módulos paralelos ou testes de dispositivos para acelerar o tempo de inicialização, que podem ser adicionados ao Android em versões futuras. Módulos de driver que não usam sondagem assíncrona podem reduzir a eficácia de tais otimizações.
Para marcar um driver como compatível e preferível à investigação assíncrona, defina o campo probe_type
no membro struct device_driver
do driver. O exemplo a seguir mostra esse suporte habilitado para um driver de plataforma:
static struct platform_driver acme_driver = {
.probe = acme_probe,
...
.driver = {
.name = "acme",
...
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
Fazer um driver funcionar com investigação assíncrona não requer código especial. No entanto, lembre-se do seguinte ao adicionar suporte à análise assíncrona.
Não faça suposições sobre dependências previamente testadas. Verifique direta ou indiretamente (a maioria das chamadas de estrutura) e retorne
-EPROBE_DEFER
se um ou mais fornecedores ainda não estiverem prontos.Se você adicionar dispositivos filhos na função de investigação de um dispositivo pai, não presuma que os dispositivos filhos serão investigados imediatamente.
Se uma análise falhar, execute o tratamento de erros e a limpeza adequados (consulte Usar variantes da API devm_*() ).
Não use MODULE_SOFTDEP para solicitar testes de dispositivos
A função MODULE_SOFTDEP()
não é uma solução confiável para garantir a ordem das provas do dispositivo e não deve ser usada pelos seguintes motivos.
Sonda adiada. Quando um módulo é carregado, a investigação do dispositivo pode ser adiada porque um de seus fornecedores não está pronto. Isso pode levar a uma incompatibilidade entre a ordem de carregamento do módulo e a ordem de teste do dispositivo.
Um driver, muitos dispositivos. Um módulo de driver pode gerenciar um tipo de dispositivo específico. Se o sistema incluir mais de uma instância de um tipo de dispositivo e cada um desses dispositivos tiver um requisito de ordem de teste diferente, você não poderá respeitar esses requisitos usando a ordem de carregamento do módulo.
Sondagem assíncrona. Os módulos de driver que executam testes assíncronos não testam imediatamente um dispositivo quando o módulo é carregado. Em vez disso, um thread paralelo trata da sondagem do dispositivo, o que pode levar a uma incompatibilidade entre a ordem de carregamento do módulo e a ordem da sondagem do dispositivo. Por exemplo, quando um módulo de driver principal I2C executa sondagem assíncrona e um módulo de driver de toque depende do PMIC que está no barramento I2C, mesmo que o driver de toque e o driver PMIC sejam carregados na ordem correta, a sonda do driver de toque pode ser tentada antes a sonda do driver PMIC.
Se você tiver módulos de driver usando a função MODULE_SOFTDEP()
, corrija-os para que não usem essa função. Para ajudá-lo, a equipe do Android atualizou alterações que permitem ao kernel lidar com problemas de ordenação sem usar MODULE_SOFTDEP()
. Especificamente, você pode usar fw_devlink
para garantir a ordem da análise e (após a análise de todos os consumidores de um dispositivo) usar o retorno de chamada sync_state()
para executar quaisquer tarefas necessárias.
Use #if IS_ENABLED() em vez de #ifdef para configurações
Use #if IS_ENABLED(CONFIG_XXX)
em vez de #ifdef CONFIG_XXX
para garantir que o código dentro do bloco #if
continue a ser compilado se a configuração mudar para uma configuração tristate no futuro. As diferenças são as seguintes:
#if IS_ENABLED(CONFIG_XXX)
é avaliado comotrue
quandoCONFIG_XXX
é definido como módulo (=m
) ou integrado (=y
).#ifdef CONFIG_XXX
é avaliado comotrue
quandoCONFIG_XXX
está definido como integrado (=y
) , mas não quandoCONFIG_XXX
está definido como módulo (=m
). Use isto apenas quando tiver certeza de que deseja fazer a mesma coisa quando a configuração estiver definida como módulo ou desabilitada.
Use a macro correta para compilações condicionais
Se CONFIG_XXX
for definido como módulo ( =m
), o sistema de compilação definirá automaticamente CONFIG_XXX_MODULE
. Se o seu driver é controlado por CONFIG_XXX
e você deseja verificar se o seu driver está sendo compilado como um módulo, siga as seguintes orientações:
No arquivo C (ou qualquer arquivo de origem que não seja um arquivo de cabeçalho) do seu driver, não use
#ifdef CONFIG_XXX_MODULE
pois é desnecessariamente restritivo e quebra se a configuração for renomeada paraCONFIG_XYZ
. Para qualquer arquivo de origem sem cabeçalho compilado em um módulo, o sistema de compilação define automaticamenteMODULE
para o escopo desse arquivo. Portanto, para verificar se um arquivo C (ou qualquer arquivo fonte sem cabeçalho) está sendo compilado como parte de um módulo, use#ifdef MODULE
(sem o prefixoCONFIG_
).Nos arquivos de cabeçalho, a mesma verificação é mais complicada porque os arquivos de cabeçalho não são compilados diretamente em um binário, mas sim compilados como parte de um arquivo C (ou outros arquivos de origem). Use as seguintes regras para arquivos de cabeçalho:
Para um arquivo de cabeçalho que usa
#ifdef MODULE
, o resultado muda com base em qual arquivo de origem o está usando. Isso significa que o mesmo arquivo de cabeçalho na mesma compilação pode ter diferentes partes de seu código compiladas para diferentes arquivos de origem (módulo versus integrado ou desabilitado). Isso pode ser útil quando você deseja definir uma macro que precisa ser expandida de uma maneira para o código integrado e de uma maneira diferente para um módulo.Para um arquivo de cabeçalho que precisa ser compilado em um trecho de código quando um
CONFIG_XXX
específico é definido como módulo (independentemente de o arquivo de origem que o inclui ser um módulo), o arquivo de cabeçalho deve usar#ifdef CONFIG_XXX_MODULE
.