Exemplo de driver para Linux Embarcado

device driver

Neste artigo daremos um pequeno e simples exemplo de como codificar um device driver para Linux Embarcado. Na verdade esse exemplo funciona no Linux Desktop também, a única diferença será o compilador usado e arquivo para compilação Makefile. Então vamos lá!

 

 

Preparando o ambiente para desenvolvimento

 

Para que possamos executar o driver numa determinada placa cujo processador é diferente daquele onde o código será desenvolvido, nós precisamos usar um toolchain cruzado ou cross-toolchain. Mas se você for executar esse exemplo no PC, você pode usar o compilador gcc nativo. E se você já tem um toolchain instalado em seu sistema, pode pular esta parte do artigo e ir direto para a compilação do kernel.

 

Das plataformas utilizadas em Linux Embarcado, a plataforma ARM é a mais usada e difundida atualmente. Assim as instruções a seguir visam apenas placas com processadores ARM. Mas você poderia rodar esse exemplo em qualquer placa ARM ou mesmo placas com processadores diferentes como MIPS e PowerPC, fazendo as devidas adaptações. Para processadores MIPS, por exemplo, você deve usar algo como gcc-mips-linux-gnueabi.

 

Será utilizado para o exemplo o cross-toolchain da Linaro gcc-linaro-arm-linux-gnueabihf-4.7, por ser estável e por rodar em praticamente qualquer distribuição de sua escolha. E se o Linux que roda em sua placa não suporta ponto flutuante por hardware, use a versão gcc-linaro-arm-linux-gnueabi-4.7, ou seja, sem o hf no final.

 

1 – Digite o seguinte comando em seu Linux Desktop para baixá-lo:

 

  

2 – Descompacte o compilador: 

 

  

3 – Crie a pasta toolchains/gnueabi sob /opt: 

 

  

4 – Mova a pasta do compilador para a pasta /opt/toolchains/gnueabi: 

 

  

5 – Adicione o diretório do compilador ao PATH de seu usuário: 

 

 

 E adicione à última linha: 

 

 

 6 – Teste o funcionamento básico (antes de fazer o teste, feche e abra o terminal novamente): 

 

 

Você deverá ver na última linha: gcc version 4.7.3 20130328 (prerelease) (crosstool-NG linaro-1.13.1-4.7-2013.04-20130415 – Linaro GCC 2013.04)

 

Para seguirmos com o resto do tutorial, é necessário que o leitor tenha instalado em sua máquina o git, além de ferramentas de desenvolvimento básicas. Instale-as através do comando: 

 

 

O git-email será útil se você pretende enviar patches para algum repositório que utiliza o git, como o kernel Linux.

 

 

Gerando uma imagem de kernel para processadores da Allwinner

 

O kernel Linux usado nas placas com processadores da Allwinner como A13-Olinuxino e Cubieboard é um kernel derivado do trabalho de uma comunidade na internet conhecida como comunidade sunxi. O seu mantenedor é um desenvolvedor chamado Alejandro Mery. Existe também uma versão especial para o u-boot, o u-boot-sunxi. O nome sunxi é pelo fato de o fabricante do processador, Allwinner, ser chinês. Por isso ele foi batizado como linux-sunxi.

 

Existe também um  trabalho que está em andamento para incluir o suporte aos processadores da Allwinner no kernel oficial. Mas ainda muitos periféricos estão sem driver, como o audio e o vídeo. Dessa forma, nós iremos usar o kernel da comunidade sunxi. Para baixá-lo, digite: 

 

  

Isso pode demorar um pouco! Depois de concluído, configure e compile o kernel com os seguintes comandos: 

 

 

A opção sun4i_defconfig é usada para processadores da Allwinner modelo A10. Mude para a opção adequada de acordo com seu processador. Se tudo deu certo, dentro da pasta do código fonte do kernel, no caminho arch/arm/boot, deverá ter o arquivo de imagem do kernel uImage. E em output/ os módulos do kernel. Agora transfira o kernel e módulos para a placa.

 

 

Gerando uma imagem de kernel para processadores da Texas

 

O kernel Linux mantido pela Texas Instruments está entre um dos mais estáveis entre processadores ARM. Desde de muito tempo, eles têm funcionários dedicados à manutenção e suporte ao kernel Linux. As instruções abordadas aqui se aplicam à placa BeagleBone Black rodando a distribuição Debian. Se o leitor for usar o driver na distribuição Ångström Linux, que já vem gravada na memória da Beaglebone Black na revisão B, deve usar um compilador sem ponto flutuante, ou seja, sem o hf no final. Na BeagleBone Black revisão C, a distribuição gravada na memória flash é o Debian.

 

Para baixar o kernel para a placa BeagleBone Black execute os seguintes comandos: 

 

 

Agora compile o kernel com os seguintes comandos: 

 

 

A opção bb.org_defconfig é a configuração de kernel para a placa BeagleBone. E dtbs se refere ao arquivo de Device Tree, explicado no artigo Linux Device Drivers – Diferenças entre Drivers de Plataforma e de Dispositivo. zImage e modules referem-se ao kernel e módulos do kernel respectivamente. Você deve ter percebido alguns comandos em comum no processo de compilação dos dois processadores. Os mesmos comandos se aplicam para a compilação do kernel para outros processadores ARM, com ou sem suporte a Device Tree.

 

Se tudo deu certo, deverá existir um arquivo zImage em arch/arm/boot, assim como no caso da Allwinner. Além disso, haverá o arquivo de Device Tree, am335x-boneblack.dtb, em arch/arm/boot/dts e os módulos do kernel em output/. Agora transfira o kernel, o arquivo de Device Tree e os módulos para a placa.

 

 

Driver Hello World

 

O leitor pode estar se perguntando que relação tem baixar ou compilar o kernel com a programação de um device driver. Para compilar o código do driver, é necessário que se tenha o código fonte do kernel. E se você for compilar um driver embutido no kernel, deverá necessariamente recompilar o código fonte do kernel! Além disso, não é possível carregar um driver num kernel que não foi gerado por você. 

 

O driver exemplo será compilado como módulo (loadable module), ou seja, separado do kernel Linux. Mas não apenas isso, o código estará fora da árvore do kernel. Por esse motivo, o módulo precisa ser carregado por meio do comando insmod. Você não poderá usar o modprobe!

 

Salve o código abaixo como hello.c num diretório qualquer. Pode ser fora da pasta do código fonte do kernel.

 

 

Vamos agora ao arquivo Makefile. Salve o conteúdo abaixo com o nome Makefile (com M maiúsculo mesmo) na mesma pasta do código fonte do driver.

 

  

Altere o valor de KDIR de acordo com o caminho onde o código fonte do kernel se encontra, o qual foi baixado e compilado anteriormente. Para compilar o driver simplesmente digite: 

 

  

O compilador referenciado nesse comando, arm-linux-gnueabihf-gcc, é o mesmo que baixamos nas instruções para preparação do ambiente. Se você usa outro, altere para o nome correto. Essa é a forma como os drivers de código fechado, feitos por certos fabricantes, como Nvidia, são feitos. É bastante prático quando é necessário fazer testes e alterações no código do driver, pelo fato de que não é necessário recompilar o kernel.

 

O leitor verificará que o driver foi compilado corretamente se o arquivo hello.ko for criado na pasta.

 

 

Explicação do código

 

1. __init

 

O kernel toma isso como sinal de que a função é usada apenas durante a fase de inicialização e libera recursos de memória utilizados, após a inicialização do kernel, quando o kernel imprime: Freeing unused kernel memory: 236k freed

Definido em: include/linux/init.h

 

2. __exit

 

Alguns drivers podem ser configurados como módulos (como o nosso exemplo acima). Nesses casos eles usam o exit. Entretanto, se eles são compilados embutidos no kernel, eles não necessitam do exit. Assim como o __init, indica que a função será colocada numa região do binário para ser descartada depois.

 

3. Cabeçalhos específicos do kernel Linux: linux/xxx.h

 

Não existe acesso à biblioteca C padrão na API do kernel Linux. Ele tem sua própria API. Para imprimir mensagens, por exemplo, usa-se printk() em vez de printf().

 

4. Uma função de inicialização - hello_init()

 

Chamada quando o módulo é carregado, retornando o código 0 em caso de sucesso e um valor negativo em caso de falha. Declarada pela macro module_init().

 

5. Uma função de cleanup - hello_exit()

 

Chamada quando o módulo é descarregado e declarada pela macro module_exit().

 

6. module_init()/module_exit()

 

A macro module_init() indica que função será chamada quando o módulo for carregado no kernel. O nome da função não importa, embora que <nome_do_módulo>_init() é uma convenção. Já a macro module_exit(), indica a função que será chamada quando o módulo é removido do sistema via rmmod.

 

7. Declarações de metadados

 

Declarações de metadados são feitas através das seguintes macros: MODULE_LICENSE(), MODULE_DESCRIPTION() and MODULE_AUTHOR(). Eles indicam a licença, a descrição do que módulo faz e o autor, respectivamente.

 

Alguns observações importantes:

 

  1. A partir do módulo do kernel, apenas um limitado número de funções do kernel pode ser chamado;
  2. Funções e variáveis ​​devem ser explicitamente exportadas pelo kernel para serem visivéis para outros módulos do kernel;
  3. Duas macros são utilizadas no kernel para ​​exportar funções e variáveis:
  • EXPORT_SYMBOL(nome_do_simbolo), exporta uma função ou variável para todos os módulos;
  • EXPORT_SYMBOL_GPL (nome_do_simbolo), exporta uma função ou variável só para módulos GPL.

 

Um detalhe interessante é que em drivers modernos as macros module_init() e module_exit() não são mais usadas, embora ainda funcionem. Em vez delas, usa-se module_i2c_driver() para drivers de I2C, module_spi_driver para drivers de SPI, e assim por diante. Mas como nosso exemplo é apenas para ilustrar a adição e remoção do driver no sistema, não faria sentido usar module_i2c_driver(), por exemplo.  

 

 

Transferindo o driver para a placa

 

Provavelmente, a forma mais fácil de transferir o driver ou um arquivo qualquer para a placa de desenvolvimento é através de ssh. Exemplo: 

 

 

 

Inserindo e removendo o driver

 

Para carregar o módulo no Linux da placa, mude para pasta onde foi transferido o arquivo e digite como usuário root:

 

 

Para remover o módulo do sistema digite:

 

 

Os mesmos comandos se aplicam ao Linux Desktop para testar o driver. Ao inserir o driver no sistema o leitor deverá ver a mensagem “Hello World!” e quando removê-lo via rmmod, “Good bye cruel world!”. Caso não veja, simplesmente digite o comando: 

 

 

O printk é utilizado na programação do kernel para imprimir mensagens para os logs do kernel. E pr_alert é definido como: 

 

 

A sintaxe de printk é: printk ("log level" "message", );

 

Os níveis de log indicam a importância da mensagem que está sendo impressa. No kernel são definidos 8 níveis de log no arquivo printk.h: 

 

 

Como normalmente o nível de log no sistema é configurado para 4, o leitor deverá ver as mensagens do driver sem precisar alterar o nível do log do seu sistema. No próximo artigo veremos um exemplo de driver I2C.

 

 

Para saber mais

 

Linux Device Drivers – Diferenças entre Drivers de Plataforma e de Dispositivo 

Device Drivers para Linux Embarcado – Introdução 

Como se tornar um especialista em Linux embarcado 

Seminário Linux Embarcado 2011 

Anatomia de um Sistema Linux embarcado 

Embedded Linux Build Systems 

Controlador Industrial com Linux embarcado 

Como o Linux é Construído

http://elinux.org/Building_BBB_Kernel#Downloading_and_building_the_Linux_Kernel

http://www.ti.com/lsds/ti/tools-software/linux.page

http://free-electrons.com/doc/training/linux-kernel/

http://tuxthink.blogspot.com.br/2012/07/printk-and-console-log-level.html

 

 

Referências

 

https://www.kernel.org/

http://www.ti.com/

http://beagleboard.org/black

https://www.embarcados.com.br/linux-device-drivers-diferencas-entre-drivers-de-plataforma-e-de-dispositivo/

Outros artigos da série

<< Linux Device Drivers - Diferenças entre Drivers de Plataforma e de DispositivoComunicação SPI em Linux >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Vinicius Maciel
Cursando Tecnologia em Telemática no IFCE. Trabalho com programação de Sistemas Embarcados desde 2009. Tenho experiência em desenvolvimento de software para Linux embarcado em C/C++. Criação ou modificação de devices drivers para o sistema operacional Linux. Uso de ferramentas open source para desenvolvimento e debug incluindo gcc, gdb, eclipse.

4
Deixe um comentário

avatar
 
2 Comment threads
2 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Diego SueiroRodrigo SubtilComunicação SPI em Linux - Embarcados - Sua fonte de informações sobre Sistemas Embarcados Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
trackback

[…] Exemplo de driver para Linux Embarcado, artigo escrito por Vinicius Maciel […]

Rodrigo Subtil
Visitante
Rodrigo

Precisei fazer algumas alterações para o kernel para a beaglebone..

- Para baixar o kernel para a beaglebone não seria o comando
git clone https://github.com/beagleboard/kernel.git

- e após de devemos fazer um ?

Agora gostaria de gerar uma zImage e um device tree, poderia me ajudar?

Diego Sueiro
Visitante
Diego Sueiro

Rodrigo,

Utilize o repo https://github.com/beagleboard/linux.git, assim como mencionado no post.

Ele está sendo utilizado como o principal pelos integrantes do projeto.

Para gerar o zImage e o dtb siga os passos do tutorial e nas linhas onde se encontra uImage, troque por zImage.

Rodrigo Subtil
Visitante
Rodrigo

Olá Diego,

Obrigado pela rápida resposta, meu kernel está rodando na Beaglebone black!