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

Linux Device Drivers
Este post faz parte da série Device Drivers para Linux Embarcado. Leia também os outros posts da série:

O tema em foco para este artigo será: Linux Device Drivers - Diferenças entre Drivers de Plataforma e de Dispositivo. Será mostrado as diferenças e semelhanças entre eles, além de exemplos.

 

Um conceito muito importante quando se inicia na programação de device drivers para Linux é saber a diferença entre drivers de plataforma e drivers de dispositivo. Muitas pessoas que iniciam o estudo de linux device drivers, começam fazendo ou estudando drivers do tipo UART, I2C ou SPI. Porém, definitivamente você deveria primeiro saber essa diferença entre driver de plataforma e driver de dispositivo, porque isso te daria uma visão bem ampla de como funciona o drivers no Linux. Será apresentado neste artigo quais são essas diferenças e como eles interagem.

 

Linux Device Drivers: O que é um Driver de Plataforma

 

De acordo com a documentação do kernel, existem dois conceitos distintos: Platform devices e Device drivers. Platform devices são dispositivos que aparecem como entidades autônomas no sistema e contêm informações de hardware. Estão associados a controladores de barramento como: SPI, I2C e USB. Device Driver proporciona um modelo de driver padrão, com funções como probe e remove, usadas por todos os drivers. Estão associados a dispositivos externos ao processador e ligados através de barramentos como os citados acima.

 

Então, em termos práticos, driver de plataforma é uma abstração para representar um controlador ou adaptador interno de um SoC, como por exemplo, controlador I2C, controlador SPI ou controlador USB. E também outros periféricos mais complexos como controlador de Interrupções e controlador de DMA. Ele também pode ser visto como um pseudo-barramento que conecta o CPU a um controlador interno a ele, isto é, no mesmo chip.  Na figura abaixo vemos vários controladores como Ethernet MAC, USART0-1, SPI, I2C (Two Wire Interface), CAN e outros. Todos conectados ao CPU ARM (ARM processor) através do barramento APB.

 

ARM_architecture.png

 

Ele é bastante usado em sistemas embarcados, principalmente nas arquiteturas ARM, PowerPC e MIPS. Então é muito importante entendê-lo. Se você pretende, por exemplo, fazer um BSP (Board Support Package) de uma placa nova, terá que fazer vários drivers de plataforma. É um trabalho extremamente complexo e árduo. Geralmente é necessário uma equipe para isso.

 

 

A camada de driver de plataforma

 

Para termos uma ideia melhor, será apresentado um exemplo prático. No Linux nós podemos imaginar que há três camadas de software distintas. A camada mais baixa é a dos plataform drivers. A segunda é a de linux device drivers. A terceira e última é a camada de aplicativos. ‘Camada mais baixa’ significa que está mais próxima do hardware. Assim, do ponto de vista de camadas, se tomarmos como exemplo uma memória EEPROM AT24cxx da ATMEL, acessada através de barramento I2C, teríamos:

  • 1° camada: Driver de plataforma I2C (geralmente feito pelo fabricante);

  • 2º camada: Driver de dispositivo at24.c;

  • 3º camada: O software em espaço de usuário para ler e escrever a memória.

 

 

Então, usando a abordagem do Devfs ou a abordagem do Sysfs (Unified Device Driver Model), quando você chama read() lá no seu software, acontece resumidamente o seguinte:

 

camadas_kernel.png

 

A função master_xfer() é responsável pela comunicação I2C propriamente dita e é implementada no driver de plataforma I2C. A implementação dessa função e todo o código fonte do driver em si é diferente para cada SoC. Se a placa que você comprou tem um SoC que o fabricante não implementou o driver de plataforma I2C, você terá que fazê-lo. Um driver de plataforma qualquer é bem mais complicado de implementar do que um driver de dispositivo.

 

Existem poucas exceções onde um mesmo driver de plataforma serve para processadores diferentes. O driver i2c-mv64xxx.c, por exemplo, é um driver para controlador I2C de processadores da Marvell e que graças a um patch suporta também o controlador I2C do A10/A13 da Allwinner.

 

 

Comparação entre dois drivers de plataforma

 

Vamos ver exemplos de implementação para dois fabricantes diferentes.

 

Driver i2c-tegra.c (apenas algumas linhas):

 

 

Driver i2c-omap.c (apenas algumas linhas):

 

 

Esses trechos são as implementações da função master_xfer para estes SoCs baseados em ARM. Percebeu o quanto eles são diferentes? E o que eles têm em comum? A primeira coisa que notamos em comum é a estrutura i2c_algorithm. Todo driver de plataforma I2C terá obrigatoriamente ela. E deverá ter uma função responsável por implementar a função de callback da API I2C, master_xfer(). A partir dai, cada fabricante implementa sua master_xfer de acordo com seu hardware, conforme vimos acima. Leia o código completo destes drivers no kernel Linux e você verá o quanto são distintos.

 

Driver I2C Tegra

Driver I2C Omap

 

Mas existem elementos comuns a todos os drivers de plataforma. Eles terão sempre as seguintes estruturas:

 

 

representando o Platform device. E a outra estrutura:

 

 

representando o Platform driver. Nesse aspecto, ele se assemelha ao driver de dispositivo, porque ambos apresentam uma estrutura principal com funções de callback probe() e remove().

 

Na primeira estrutura (platform_device) serão definidos aspectos ligados ao hardware propriamente dito, como endereços de registradores mapeados em memória e linhas de interrupção. E na segunda (platform_driver), deverão ser definidas ao menos as funções probe() e remove(), através das quais o kernel insere e remove o driver de plataforma do sistema. As funções shutdown(), suspend() e resume() estão relacionadas a gerenciamento de energia.

 

O Platform driver é registrado no sistema através da função platform_driver_register() e o Platform device através da função platform_device_register(). A função platform_driver_register() pode ser encontrada em diversos drivers do kernel Linux, como pode ser visto aqui. Já a função platform_device_register() poderá ser encontrada em arquivos de board, especialmente a estrutura platform_device, como será visto num exemplo dado abaixo.

 

 

Suporte a Device Tree

 

Outro ponto importante para salientar é o uso do Device Tree. Como dito no artigo de introdução, é mandatório que todo driver de plataforma que é desenvolvido atualmente já inclua suporte a Device Tree. Ele passou a ser usado na versão 3.1 do kernel, no caso da plataforma ARM. Em relação ao driver de plataforma i2c-tegra.c, o trecho de código que oferece suporte a Device Tree é mostrado abaixo:

 

 

A função of_match_device() permite obter a entrada correspondente na estrutura tegra_i2c_of_match, mostrada logo abaixo. É útil para obter o campo especifico data, usado para alterar o comportamento do driver dependendo da variante do dispositivo detectado. Abaixo é mostrado a tabela de compatibilidade para os controladores I2C que esse driver suporta:

 

 

Os valores atribuídos a compatible são strings usadas para ligar um platform device ao platform driver. Abaixo é mostrado o código do Device Tree propriamente dito para o controlador I2C-1 do Tegra20:

 

  

 

O que é um driver de dispositivo

 

De acordo com a definição da documentação do kernel, driver de dispositivo é uma estrutura estaticamente alocada. Em termos práticos, é a camada de software usada para comunicação com um dispositivo externo ao processador ou SoC e que está associado a algum barramento como, por exemplo, barramento I2C, barramento SPI ou barramento USB. Para o exemplo dado acima da memória EEPROM, o barramento é I2C. Se em vez da memória AT24Cxxx, fosse a memória AT25DF641, o barramento seria SPI. Então todo dispositivo externo se comunica com o processador através de um barramento.

 

O driver de dispositivo, diferente do driver de plataforma, teoricamente funciona em qualquer processador, mesmo que sejam de arquiteturas diferentes. Ou seja, seu código é independente de hardware, enquanto que o driver de plataforma depende do hardware. O tópico do artigo anterior Escrevendo Device Drivers Portáveis se refere especificamente a drivers de dispositivo. O driver de dispositivo é uma camada concebida para ser portável. Pode-se até rodar o mesmo driver de dispositivo em um Linux Desktop e em uma placa com Linux Embarcado.

 

Existem essencialmente três tipos de dispositivos: dispositivos de rede, dispositivos de bloco e dispositivos de caractere. Uma memória I2C EEPROM como a AT24Cxx se encaixa bem na categoria de dispositivo de caractere. Desta forma, nós teríamos sob o diretório /dev sua representação através de um nó, como AT24C, através do qual o usuário pode ler e escrever na memória. Porém, essa abordagem é tipica do kernel 2.4.x, mas que também pode ser usada nos kernels 2.6.x e 3.x. Outra possibilidade é a abordagem que pode ser usada para o desenvolvimento de drivers a partir do kernel 2.6, o Unified Device Driver Model. Esse modelo disponibiliza uma interface para comunicação com o espaço de usuário através de entradas no diretório /sys e é o modelo usado no driver at24.c.

 

No entanto, para o desenvolvedor de software, não muda nada. Pode ser usada a mesma API de acesso a arquivos open(), read(), write() e close().

 

 

Como eles interagem

 

No tópico acima já foi dado um exemplo do que acontece quando você chamada read(). Desta forma, a partir de uma chamada de sistema no software o kernel passa por varias funções até chegar na camada  de drivers de dispositivo. E dai, ele continua mais adiante chamando outras funções, até chegar na camada de driver de plataforma. Para, finalmente, chegar no dispositivo.

 

Funciona como, conforme citada acima, três camadas de software: aplicação, driver de dispositivo e driver de plataforma. Se algumas dessas duas últimas camadas não estiver presente, você não poderá acessar o dispositivo de barramento a partir de um software.

 

É importante salientar que alguns periféricos dispensam o uso da camada de driver de dispositivo, como é o caso de conversores A/D e D/A, caso seja utilizado o framework IIO para acessá-los. O framework já disponibiliza diretamente no diretório /sys, entradas para o espaço de usuário. Outro exemplo é o controlador de GPIO. Isso se aplica geralmente a periféricos internos ao SoC que não estão associados a um barramento de comunicação, como I2C, SPI ou USB.

 

 

Entradas para acesso a dispositivo no diretório /dev

 

Para que o usuário possa acessar um dispositivo é necessário um mecanismo para criar nós de dispositivos em uma pasta acessível a ele.

 

Muitos drivers de dispositivo no kernel 2.4 são implementados como drivers de caractere. Eles usam a cdev, uma abstração do kernel para drivers de caractere. Eles também podem ser usados nas versões do kernel 2.6.x e 3.x. A estrutura principal usado nesses drivers é file_operations. Nela se define as operações para abrir e fechar arquivo, leitura e escrita, entre outras.

 

 

Major e Minor number

 

Tudo no Linux, assim como no Unix, é representado através de arquivos, incluindo dispositivos de hardware. Todos os dispositivos são como arquivos regulares. Eles podem ser abertos, fechados, lidos e escritos usando as mesmas chamadas de sistema que são usadas para manipular arquivos. O kernel Linux usa o par de números major e minor para representar um dispositivo de caractere. Eles se encontram na pasta /dev do sistema de arquivos.

 

Todos os dispositivos controlados pelo mesmo driver tem um major number em comum. Os minor numbers são usadas para distinguir entre diferentes dispositivos e seus controladores. O major number também identifica o tipo de dispositivo no sistema(se é uma memória I2C ou um terminal serial, por exemplo). Você pode alocar esses números de forma estática ou dinâmica.

 

Na alocação estática você deve escolher o major number que não esteja sendo utilizado. O registro estático deve ser feito através da função register_chrdev_region().

Você pode conferir a lista de major number utilizados através do arquivo /proc/devices

 

Character devices:

 

 

Na alocação dinâmica o kernel irá atribuir o major number através da função alloc_chrdev_region() . Porém essa técnica não garante que sempre será utilizado o mesmo major number para determinado driver.

 

Como dito anteriormente o user-space se comunica com os device drivers através de arquivos de dispositivos residentes no /dev. Ao listar esse diretório temos como exemplo:

 

 

 

Veja que nas colunas 5 e 6 temos as informações do major e minor numbers. A criação desses arquivos podem ser realizadas basicamente de três maneiras:

  1. Em tempo de construção do sistema de arquivos.

  2. Utilizando o comando mknod.

  3. Através de um sistema de gerenciamento de dispositivos, como por exemplo o udev.

 

 

Arquivo Board

 

Até o kernel 3.0 as plataformas ARM utilizavam extensamente o arquivo de board para realizar o instanciamento dos platform devices, dentre outras coisas.

 

Vejamos o exemplo da Beaglebone no arquivo arch/arm/mach-omap2/board-omap3beagle.c para instanciar os leds presentes na placa.

 

Declaração das estruturas:

 

 

E finalmente o instanciamento realizado na função omap3_beagle_init:

 

 

 

Entradas para acesso a dispositivo no diretório /sys

 

Também é possível disponibilizar no espaço de usuário entradas no diretório /sys usando a interface de arquivo SYSFS. A vantagem do SYSFS é que ele organiza os dispositivos em classes, barramentos, tipos de dispositivos, etc.

 

Os sysfs exporta detalhes internos de implementação do kernel e depende de suas estruturas internas do kernel. É consenso pelos desenvolvedores do kernel Linux que ele não fornece uma API interna estável. Portanto, há aspectos da interface sysfs que podem não ser estáveis em todas as versões do kernel.

 

Atualmente há três lugares para classificação de dispositivos:  /sys/block, /sys/class e /sys/bus. É planejado que estas pastas não conterão quaisquer diretórios de dispositivo em si, mas apenas listas de symlinks apontando para a árvore unificada /sys/devices. Todos os três lugares tem regras completamente diferentes de como acessar informações de dispositivo.

 

Pode-se criar entradas no diretorio /sys usando a função int sysfs_create_bin_file() e remover com a função sysfs_remove_bin_file(). As funções de escrita e leitura podem ser definidas através da estrutura bin_attribute:

 

 

Exemplo de atribuição para leitura: http://lxr.free-electrons.com/source/drivers/misc/eeprom/at24.c#L572

 

Exemplo de atribuição para escrita: http://lxr.free-electrons.com/source/drivers/misc/eeprom/at24.c#L586

 

 

Conclusão

 

Neste artigo foi mostrado a diferença entre drivers de plataforma e drivers de dispositivo, quais as características de cada um e como eles interagem. Além disso, foram abordadas duas formas de acesso a drivers de dispositivo pelo usuário: entradas no /dev e entradas no /sys.

 

No próximo artigo nós iremos implementar um driver hello world para mostrar a estrutura básica de um driver de dispositivo.

Artigo escrito em conjunto com Diego Sueiro.

 

 

Referências

 

http://linuxtutorial.info/modules.php?name=MContent&pageid=94

http://www.makelinux.net/ldd3/chp-3-sect-2

https://www.kernel.org/doc/Documentation/i2c/instantiating-devices

https://www.kernel.org/doc/Documentation/sysfs-rules.txt

https://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf

Outros artigos da série

<< Device Drivers para Linux Embarcado - IntroduçãoExemplo de driver para Linux Embarcado >>
Este post faz da série Device Drivers para Linux Embarcado. Leia também os outros posts da série:
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.

7
Deixe um comentário

avatar
 
6 Comment threads
1 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
Vinicius MacielWendell SilvaCaio PereiraMarcelo Rodrigo Dos Santos AndriolliComunicação SPI em Linux - Embarcados - Sua fonte de informações sobre Sistemas Embarcados Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
trackback

[…] kernel. No artigo Linux Device Drivers – Diferenças entre Drivers de Plataforma e de Dispositivo [2] explico em detalhes o funcionamento geral dos drivers. Consulte essa […]

Wendell Silva
Visitante
Wendell Silva

Vinicius, há um erro (bobo) na primeira frase do primeiro parágrafo do tópico "O que é um driver de plataforma". Acredito que em vez de "...dois conceitos distintos: Platform devices e Platform drivers." seria "...dois conceitos distintos: Platform devices e Device drivers."

No mais, obrigado pelo excelente artigo.

Vinicius Maciel
Visitante
vinifr

Sim, tem razão. Obrigado pela observação!

trackback

[…] no artigo Linux Device Drivers – Diferenças entre Drivers de Plataforma e de Dispositivo [4]. uImage e modules referem-se ao kernel e módulos do kernel respectivamente. Você deve ter […]

Caio Pereira
Visitante
Caio Pereira

Vinicius e Diego, excelente artigo! muito bem explicado! Parabéns

Marcelo Rodrigo Dos Santos Andriolli
Visitante
Marcelo Andriolli

Parabéns Vinicius! Muito bacana o teu post! Usei muito device tree em projetos com Linux rodando em processadores softcore para FPGA.