4 Comentários

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 >>
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

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

Beaglebone Black » Exemplo de driver para Linux Embarcado
Talvez você goste:
Comentários:

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!

Séries



Outros da Série

Menu

WEBINAR
 
Linux Embarcado: Desvendando o Pin Control Subsystem - Kernel Linux

Data: 26/02 às 19:30 h | Apoio: Mouser Electronics
 
INSCREVA-SE AGORA »



 
close-link