User Space Device Drivers no Linux – Parte 2

- por Sergio Prado

Categorias: Linux

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

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.