Classe de dispositivos
Tags: linux, lkm, linuxkernelmodule, drivers
Published: 2023-09-13 20:21:56


Nesse segundo texto sobre a introdução ao desenvolvimento de drivers de dispositivos pro kernel linux, vamos falar sobre classes de dispositivos.

Caso tenha caído de paraquedas nesse texto, aqui esta o [link](https://github.com/GuilhermeGiacomoSimoes/introducao_dev_driver_linux) do github com o compilado dos códigos e links dessa serie.

**O que são classes de dispositivos?**
Classes de dispositivos é uma forma de classificar a familia do dispositivo do qual voce esta escrevendo o driver e tambem uma forma de facilitar uma comunicação do kernel com o driver em questão.

Os dispositivos são enumerados nas classes e cada vez que um novo driver é atribuído a classe ele incrementa um atributo de classe chamado devnum, e atribui esse devnum ao dispositivo. O devnum nunca é decrementado, por tanto se você remover o dispositivo e inserir novamente ele receberá outro devnum.

Após inserirmos o módulo de driver no kernel, ele criará um diretório com o nome da sua classe no caminho `/sys/class` .
Se quisermos ver as classes de dispositivos registradas no nosso kernel, podemos listar os diretorios em `/sys/class`


![listagem das classes registradas no kernel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9vofoexyauxc7s7cu2l.png)

Podemos observar que temos classes de bluetooth, graphics, cpuid, etc. 
Essas são as classes registradas no meu kernel, e podemos criar uma nova classe de dispositivo, ou usar alguma já existente como a classe de usb ou bluetooth. 
Nesse texto, vamos criar nossa própria classe. Toda classe deve ficar declarada em um arquivo header para que seja usada pelos drivers, extensões e interfaces. Como os dispositivos estão vinculados aos drivers, eles devem ser adicionados a classe a qual pertencem.

Para criar uma classe de dispositivo e consequentemente um diretorio para essa classe no `/sys/class` , é preciso registrar essas informações em uma struct chamada `class` que fica declarada dentro do header `include/linux/device.h`. , portanto…

**Mãos no codigo**
Entao, sem mais enrolação, vamos criar um novo módulo chamado `my_class.c`, lembrando que todos os códigos completos estão no link do github citado acima.
```C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_AUTHOR("SEU NOME <seu_email@email.com>");
MODULE_DESCRIPTION("Classes de dispositivos");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");

static int __init class_init(void)
{
  return 0;
}

static void __exit class_exit(void)
{}

module_init(class_init);
module_exit(class_exit);
```

Assim como seu Makefile padrão:
```Makefile
obj-m += my_class.o

all: run

run:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```

Vamos declarar também um header `my_class.h` pra organizar nosso código e dentro do header vamos já criar nossa variável device_class.
```C
#ifndef __MY_CLASS_H__
#define __MY_CLASS_H__

static struct class *device_class = NULL;

#endif
```

Agora precisamos tanto importar o header `device.h` quanto o nosso header `my_class.`h dentro do `my_class.c` .
```C
#include <linux/device.h>
#include "my_class.h"
```

Para criarmos a nossa classe, precisamos agora alocar memória para a struct e preencher seus atributos.
Provavelmente você esta acostumado a usar o `malloc()` para alocação de memória em aplicações C em nível de usuário. Porém o `malloc()` faz parte da biblioteca `stdlib.h` e nós não temos acesso as bibliotecas no nível do kernel.
Porém, no kernel temos um header chamado `slab.h` que tem o poder de fazer alocações de memória, então, podemos importar ele no nosso `my_class.c`: `#include <linux/slab.h>`. E depois usar a função `kzalloc()` para alocar memória para a nossa classe.

Uma coisa a se frizar da função `kzalloc()` é que ela recebe dois parametros, ao contrario de `malloc()` que só recebe um: A quantidade de bytes a ser alocado. 
O kzalloc() recebe a quantidade de bytes a ser alocado e mais uma flag, que diz respeito ao controle do comportamento dos alocadores.

Segundo a Documentação oficial:

> Eles informam quais zonas de memória podem ser usadas, o quanto o alocador deve tentar encontrar memória livre, se a memória pode ser acessada pelo espaço do usuário, etc.

Caso queira mais informações sobre as flags de alocacao de memoria, pode acessar esse [link](https://dri.freedesktop.org/docs/drm/core-api/memory-allocation.html).
No nosso caso, vamos utilizar a flag `GFP_ATOMIC` , que sinaliza ao alocador que a reserva de memória é extremamente justificada e o kernel terá problemas caso a alocação não seja feita. Porém para a maioria dos casos, `GFP_KERNEL` resolve.

Então alocaremos memória para a nossa classe dentro da nossa função de inicialização do modulo:
```C
#include <linux/slab.h>

// ... 
static int __init class_init(void)
{
  device_class = (struct class *) kzalloc(sizeof(struct class), GFP_ATOMIC);
  if(!device_class) 
    pr_err("ERRO NA ALOCACAO DA CLASSE");
```

Após a nossa alocação de classe vamos preencher somente um atributo dessa classe: O seu nome.
```C
 device_class->name = "7segment";
```

O atributo name é bem óbvio. Ele é somente o nome da classe do nosso dispositivo. Aqui declarei "7segment" porque nos próximos textos, usaremos esse modulo e construiremos um circuito simples em uma protoboard e controlaremos um display de 7 segmentos através do kernel.

Por fim, chamaremos uma função dentro da função de inicialização que registrará nossa classe e criará um diretório para a classe dentro de `/sys/class` com o nome que especificamos, nesse caso 7segment.

```C
class_register(device_class);
```

Agora, na função de `__exit` precisamos matar a classe de dispositivo que criamos, então vamos incrementar essa linha de código na função de `__exit`
```C
 class_unregister(device_class);
 class_destroy(device_class);
```

Somente para fins de visualização, vamos colocar dois `pr_info()` , um no` __init` e outro no `__exit` para printar algo no console

No init:
```C
static int __init class_init(void)
{
  //...
  pr_info("class registrada");
  return 0;
}
```

E no exit:
```C
static void __exit class_exit(void)
{
  //...
 pr_info("Modulo removido");
}
```

Vamos compilar: `make` . 
Vamos limpar o nosso log para melhor visualizar o resultado do nosso módulo `sudo dmesg -C`.
E por fim vamos inserir o módulo no kernel: `sudo insmod my_class.ko` . 
Após isso a saída do nosso sudo dmesg :
```bash
[ 1107.594074] my_class: loading out-of-tree module taints kernel.
[ 1107.594093] my_class: class registrada
```

Além disso, se consultarmos o diretorio `/sys/class` temos o diretório da classe 7segment que criamos.

![saida do dmesg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wc7gmaonl36nt82ntpw0.png)


Quando removermos o modulo: `sudo rmmod my_class` . O diretório da classe também é removido.


**Revisão**

1. Aprendemos o que é uma classe de dispositivo.
2. Como criar uma nova classe de dispositivo.
3. Vimos também uma BREVÍSSIMA introdução sobre a alocação de memória no kernel.