Introdução aos Sistemas Operacionais/Exemplo de Drivers USB em Linux: LED USB
Introdução
[editar | editar código-fonte]Este trabalho tem por objetivo apresentar um estudo de caso sobre o desenvolvimento de um driver para dispositivos LED USB para o Sistema Operacional Linux. O objeto de estudo deste trabalho é o driver desenvolvimento para um dispostivo chamado USB HID Visual Signal Indicator - trata-se basicamente de um dispositivo com uma série de LED's que podem acender nas cores vermelho, verde e azul.
Informações gerais
[editar | editar código-fonte]- Autores:
- Adalberto Ribeiro Sampaio Junior
- Everton Lima Aleixo
- Pedro Henrique Pires de Castro
- Versão do kernel:
- Linux 2.6.31 kernel
- Identificação do driver analisado:
- LED USB Driver
- Lista de arquivos de códigos-fontes analisados:
- Outras informações relevantes:
Carga (load ou init)
[editar | editar código-fonte]Inicialização das Estruturas
[editar | editar código-fonte]Para o dispositivo analisado, Led USB, surge a questão "que tipo de dispositivo ele é e que interface para o espaço do usuário podemos usar?" Um dispositivo de bloco não faz sentido, com um dispositivo que não precisa armazenar dados em um sistema de arquivos, mas um dispositivo de caractere pode ser usado. Se não usamos um driver de dispositivo de caractere, entretanto, major e minor number precisam ser reservados para isto. E quantos minor numbers precisam ser reservados para este driver? E se alguém quiser plugar 100 diferentes dispositivos USB Led no sistema? Para antecipar isto, nós também precisaríamos inventar algum caminho para chamar o driver para ligar e desligar diferentes cores individualmente. Tradicionalmente, isto pode ser feito usando diferentes comandos ioctl no driver de caractere, mas pode ser feito algo muito melhor do que criar um novo comando ioctl no kernel.
Como todo dispositivo USB cria seu próprio diretório na árvore sysfs, então porque não usar sysfs e criar três arquivos no diretório do dispositivo USB, azul, vermelho e verde? Isto pode permitir que muitos programas user-space, sejam em C ou shell script, mudem as cores no nosso dispositivo Led.
Para começar o nosso USB driver, nós precisamos providenciar um subsistema USB com cinco coisas:
- Um ponteiro para o módulo proprietário deste driver: isto permite que o USB core controlar o contador de referências do módulo corretamente.
- O nome do driver USB.
- Uma lista USB IDs que o driver poderá prover: esta tabela é usada pelo USB core para determinar que driver poderá ser relacionado com qual dispositivo;o script hot-plug user-space udado para carregar o driver automaticamente quando dispositivo é plugado no sistema.
- Uma função probe() chamada pelo USB core quando um dispositivo é encontrado e relacionado com a tabela USB ID
- Uma função disconnect() chamada quando o disposito é removido do sistema.
O driver recupera esta informação com o código a seguir:
static struct usb_driver led_driver = {
.owner = THIS_MODULE,
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
}
A variável id_table é definida como:
static struct usb_device_id id_table[] = {
{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
{ },
}
Reconhecimento ou detecção (probe)
[editar | editar código-fonte]A função led_probe() é chamada quando o USB core tiver encontrado o dispositivo USB. Tudo que precisamos para fazer isto é inicializar o dispositivo e criar os três arquivos sysfs, na localização adequada. Isto é feito com o código a seguir:
static int led_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct usb_led *dev = NULL;
int retval = -ENOMEM;
dev = kmalloc(sizeof(struct usb_led), GFP_KERNEL);
if (dev == NULL) {
dev_err(&interface->dev, "Out of memory\n");
goto error;
}
memset (dev, 0x00, sizeof (*dev));
<dev->udev = usb_get_dev(udev);
usb_set_intfdata (interface, dev);
/* Cria nossos três arquivos sysfs no diretório do dispositivo USB */
device_create_file(&interface->dev, &dev_attr_blue);
device_create_file(&interface->dev, &dev_attr_red);
device_create_file(&interface->dev, &dev_attr_green);
dev_info(&interface->dev, "USB LED device now attached\n");
return 0;
error:
kfree(dev);
return retval;
}
Registro do driver (register e deregister)
[editar | editar código-fonte]Quando o módulo é carregado, a estrutura led_driver precisa ser registrada com o USB core. Isto é realizado com uma chamda a função usb_register():
retval = usb_register(&led_driver);
if(retval)
err("usb_register failed" "Error number %d",retval);
Do mesmo modo, quando o driver é descarregado do sistema, ele deve "desregistrar" ele mesmo do USB core:
usb_deregister(&led_driver);
Comunicação
[editar | editar código-fonte]Quando os arquivos sysfs são lidos, nós queremos mostrar o valor atual que esta no LED; quando ele é escrito, nós queremos setar um LED especifico. Para fazer isto, a macro a seguir cria duas funções para cada cor do LED e declara um arquivo de atributo para o dispositivo sysfs:
#define show_set(value) \ static ssize_t \ show_##value(struct device *dev, char *buf) \ { \ struct usb_interface *intf = \ to_usb_interface(dev); \ struct usb_led *led = usb_get_intfdata(intf); \ \ return sprintf(buf, "%d\n", led->value); \ } \ \ static ssize_t \ set_##value(struct device *dev, const char *buf, \ size_t count) \ { \ struct usb_interface *intf = \ to_usb_interface(dev); \ struct usb_led *led = usb_get_intfdata(intf); \ int temp = simple_strtoul(buf, NULL, 10); \ \ led->value = temp; \ change_color(led); \ return count; \ } \ static DEVICE_ATTR(value, S_IWUGO | S_IRUGO, show_##value, set_##value); show_set(blue); show_set(red); show_set(green);
Isto cria seis funções, show_blue(), set_blue(), show_red(), set_red(), show_gree() and set_gree(); e três estruturas de atributos, dev_attr_blue, dev_attr_red e dev_attr_green. Devido a natureza simples das callbacks do arquivo sysfs e o fato que nós precisamos fazer a mesma coisa para cada valor diferente (azul, vermelho e verde), uma macro foi usada para reduzir a digitação. Isto é uma ocorrência comum para funções de arquivos sysfs;
Então para habilitar o LED vermelho, um usuário escreve um 1 para o arquivo 1 no sysfs, que chama a função set_red() no driver, que chama a função change_color() como:
#define BLUE 0x04 #define RED 0x02 #define GREEN 0x01 buffer = kmalloc(8, GFP_KERNEL); color = 0x07; if (led->blue) color &= ~(BLUE); if (led->red) color &= ~(RED); if (led->green) color &= ~(GREEN); retval = usb_control_msg(led->udev, usb_sndctrlpipe(led->udev, 0), 0x12, 0xc8, (0x02 * 0x100) + 0x0a, (0x00 * 0x100) + color, buffer, 8, 2 * HZ); kfree(buffer);
Esta função inicia setando todos os bits na variavel color pra 1. Então se algum LED estão ligados, ela desliga somente o do bit especificado. Nós então enviamos uma messagem de controle USB para o despositivo para escrever este valor de cor no dispositivo.
Parece estranho que somente uma pequena váriavel de buffer, que tem somente 8 bytes de comprimento, é criada com a chamada pra o kmalloc. Porque não simplesmente declara ela na pilha e deixa o overhead de alocação dinâmica alocar e destrui-la? Isto é feito porque algumas arquiteturas que executam Linux não podem enviar dados USB criados na pilha do kernel, então todos os dados que estão sendo mandados para o dispositivo USB devem ser criados dinamicamente.
Finalização
[editar | editar código-fonte]A função led_disconnect() é igualmente simples, como nós precisamos somente liberar nossa memória alocada e remover os arquivos sysfs:
dev = usb_get_intfdata (interface); usb_set_intfdata (interface, NULL); device_remove_file(&interface->dev, &dev_attr_blue); device_remove_file(&interface->dev, &dev_attr_red); device_remove_file(&interface->dev, &dev_attr_green); usb_put_dev(dev->udev); kfree(dev); dev_info(&interface->dev, "USB LED now disconnected\n");
Considerações finais
[editar | editar código-fonte]- Informações gerais do driver
- Conclusões
- Mapa conceitual sobre o driver estudado;