User Space Device Drivers no Linux – Parte 2
- por Sergio Prado
Na parte 1 deste artigo estudamos as vantagens e desvantagens de um driver de dispositivo para Linux rodando em espaço de usuário, e cobrimos algumas possíveis soluções através da chamada de sistema mmap() e do framework UIO.
Neste artigo estudaremos alguns frameworks do kernel que possibilitam acessar diretamente algumas interfaces de hardware e barramentos, incluindo GPIO, USB, SPI e I2C.
Vamos então começar com o framework de GPIO do kernel.
GPIO
O Linux possui uma interface no sysfs capaz de exportar para o usuário os GPIOs disponíveis no sistema. Para usar esta funcionalidade, basta habilitar a opção CONFIG_GPIO_SYSFS no menu de configuração do kernel.
Com esta funcionalidade habilitada, o acesso aos GPIOs será disponibilizado em /sys/class/gpio/. Neste diretório teremos um arquivo chamado export, que possibilita exportar para o usuário um GPIO do sistema.
Se eu quiser por exemplo usar o GPIO 10 do sistema, devo escrever 10 neste arquivo:
$ echo 10 > /sys/class/gpio/export |
Neste mesmo diretório temos o arquivo unexport, que desfaz o export:
$ echo 10 > /sys/class/gpio/unexport |
Mas como sei o número do GPIO no sistema? No Linux, os GPIOs possuem uma numeração única. Por exemplo, imagine uma CPU com 4 portas de GPIO, cada uma contendo 32 GPIOs, totalizando 128 GPIOs. O GPIO 1 da porta 1 (GPIO1.1) representa o GPIO 1 no Linux. O GPIO 32 da porta 1 (GPIO1.32) representa o GPIO 32 no Linux. Já o GPIO 1 da porta 2 (GPIO2.1) representa o GPIO 33 no Linux. O GPIO 32 da porta 2 (GPIO2.32) representa o GPIO 64 no Linux. E a contagem continua até 128.
Portanto, para descobrir o número do GPIO que você deve usar para fazer o export, basta olhar no esquemático da placa qual a porta e o GPIO que pretende utilizar e fazer o cálculo.
Com o GPIO exportado, será criado o diretório /sys/class/gpio/gpioN, onde N é o número do GPIO exportado. Neste diretório teremos alguns arquivos, dentre eles:
- direction: configura o GPIO como entrada (“in”) ou saída (“out”).
- value: quando configurado como entrada, serve para ler o GPIO. Quando configurado como saída, serve para escrever no GPIO (“0” ou “1”).
Se eu quiser por exemplo setar o GPIO 35, basta executar os comandos abaixo:
$ echo 35 > /sys/class/gpio/export $ echo out > /sys/class/gpio/gpio35/direction $ echo 1 > /sys/class/gpio/gpio35/value $ echo 35 > /sys/class/gpio/unexport |
E se eu quiser ler o GPIO 47, os comandos abaixo fazem isso:
$ echo 47 > /sys/class/gpio/export $ echo in > /sys/class/gpio/gpio47/direction $ cat /sys/class/gpio/gpio47/value $ echo 47 > /sys/class/gpio/unexport |
Podemos perceber então que esta interface permite acessar diretamente qualquer dispositivo de hardware conectado à um GPIO do sistema. E nada te impede de criar uma aplicação e usar as APIs de leitura e escrita em arquivo para acessar os GPIOs da placa.
A documentação desta interface esta disponivel nos fontes do kernel em Documentation/gpio.txt.
USB
O Linux é capaz de exportar para o usuário o acesso ao barramento USB do sistema, possibilitando escrever drivers de dispositivos USB em espaço de usuário.
Nas versões mais antigas do kernel, o acesso ao barramento USB era realizado via usbfs, montado em /proc/bus/usb. Nas versões mais novas do kernel o acesso ao barramento USB é exportado para o usuário em /dev/bus/usb/.
$ tree /dev/bus/usb/ /dev/bus/usb/ ├── 001 │ ├── 001 │ ├── 002 │ ├── 003 │ ├── 004 │ ├── 005 │ ├── 009 │ └── 010 ├── 002 │ ├── 001 │ ├── 002 │ └── 003 ├── 003 │ ├── 001 │ └── 002 └── 004 └── 001 |
Cada diretório dentro de /dev/bus/usb/ representa um barramento USB do sistema, e cada arquivo dentro destes diretórios representa um dispositivo conectado ao barramento.
Uma aplicação pode acessar diretamente estes arquivos para conversar com o dispositivo conectado ao barramento USB.
Como as regras para comunicação com os dispositivos usando esta interface são mais complexas, existe uma biblioteca que abstrai e facilita o acesso chamada libusb. É bastante comum encontrar drivers em espaço de usuário para impressoras, scanners e adaptadores JTAG usando esta biblioteca. E se você pretende desenvolver um driver em espaço de usuário para um dispositivo USB, provavelmente também deverá usá-la.
I2C
O kernel possibilita o acesso ao barramento I2C através da funcionalidade i2c-dev. Para usá-la, basta habilitar a opção CONFIG_I2C_CHARDEV no menu de configuração do kernel.
Com esta funcionalidade habilitada, os barramentos I2C serão exportados em /dev/i2c-X, onde X é o número do barramento.
Exemplo de um sistema com dois barramentos I2C:
$ ls /dev/i2c* /dev/i2c-0 /dev/i2c-1 |
O acesso ao barramento pode ser realizado normalmente com a API de acesso à arquivos. Por exemplo, este é o código para ler o primeiro registro de um dispositivo conectado ao barramento I2C no endereço 0x1C:
1 2 3 4 5 6 7 8 9 10 11 12 |
void main(void) { int fd, val; fd = open("/dev/i2c-0", O_RDWR); ioctl(fd, I2C_SLAVE, 0x1C); read(fd, &val, 4); close(fd); } |
A documentação sobre como utilizar esta interface encontra-se nos fontes do kernel em Documentation/i2c/dev-interface.
Existe ainda um projeto chamado i2c-tools que provê um conjunto de ferramentas e uma biblioteca de funções para facilitar o desenvolvimento de drivers I2C em user space.
Por exemplo, a ferramenta i2cdetect permite detectar todos os dispositivos conectados à um determinado barramento I2C:
$ i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- |
As ferramentas i2cset e i2cget permitem escrever e ler o barramento I2C, respectivamente:
$ i2cset -f -y 0 0x48 10 0xA5 $ i2cget -f -y 0 0x48 10 0xA5 |
E a ferramenta i2cdump permite fazer um dump de todos os registros de um determinado dispositivo conectado ao barramento I2C:
$ i2cdump -f -y 0 0x48 No size specified (using byte-data access) 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: 00 29 21 00 00 40 01 00 00 02 ff 00 00 c0 ef 3a .)!..@?..?...??: 10: 66 10 70 99 61 40 07 00 00 77 77 07 77 03 a5 56 f?p?a@?..ww?w??V 20: 63 a3 13 55 24 35 aa 00 b0 64 1b fe dd dd 5e 62 c??U$5?.?d????^b 30: 57 7e 4e 5c 7f 6a 42 42 5f 4c 45 42 60 00 9b 2b W~N\?jBB_LEB`.?+ 40: b7 00 b5 01 0f 0e 36 cf 00 5f 00 00 00 00 00 00 ?.????6?._...... 50: 00 08 00 00 00 00 2b 00 08 06 ff 00 00 ff 09 00 .?....+.??....?. 60: e0 10 00 e0 10 00 bb 42 62 c0 00 00 00 00 00 00 ??.??.?Bb?...... 70: 00 00 01 01 00 80 00 01 01 00 00 00 00 00 00 00 ..??.?.??....... 80: 00 a3 18 00 00 00 00 00 00 00 00 00 00 00 00 00 .??............. 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ e0: 00 99 00 0a 44 44 00 40 00 00 00 00 00 00 00 00 .?.?DD.@........ f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ |
SPI
O kernel possibilita o acesso ao barramento SPI através da funcionalidade spidev. Para usar esta funcionalidade, basta habilitar a opção CONFIG_SPI_SPIDEV no kernel.
Com esta funcionalidade habilitada, os barramentos SPI são exportados em /dev/spidevB.C, onde B é o número do barramento e C é o chip-select do slave conectado ao barramento. O acesso também é feito através da API de arquivos (open(), read(), write(), ioctl(), etc).
A documentação sobre como utilizar esta interface encontra-se nos fontes do kernel em Documentation/spi/spidev.
E TEM MAIS?
Sim! Se você procurar, vai encontrar muitos outros mecanismos para desenvolver um device driver no Linux em user space, cada um com seus casos de uso:
- uinput: criar e manipular dispositivos de input em user space.
- gadgetfs: desenvolver um driver de gadget USB (USB device).
- fuse: implementar um sistema de arquivo em user space.
- fusd: framework para rotear o acesso à arquivos de dispositivo para uma aplicação rodando em user space.
- cuse: criar drivers de dispositivo de caractere em user space.
- buse: criar drivers de dispositivos de bloco em user space.
Pronto para desenvolver seu primeiro driver em user space?
Divirta-se!
Sergio Prado