Device Drivers USB no Linux

INSTITUTO TECNOLÓGICO DE AERONÁUTICA

Alunos:

Bruno César Alves
Pedro de Sousa Cau Ramos Salles
Marcus Leandro Rosa Santos

Professor: Yano
Curso: 2º ano de Computação

Introdução: Descreva a importância de device-drivers e a arquitetura de device-drivers do Linux.

Primeiramente começamos definindo o que é um Driver de dispositivo. Um Driver de dispositivo nada mais é do que um código que depende dos dispositivos os quais ele manipula. Cada dispositivo tem sua própria arquitetura específica e aceita certo número de comandos específicos para segundo os quais esse foi projetado. Cabe ao Driver de dispositivo a implementação do código de comunicação com tal dispositivo. O Driver, na verdade, faz a comunicação direta com o equipamento a ser manipulado. Entende-se por equipamento ou dispositivos, elementos como terminal, impressora, placa de vídeo etc.
Cada Driver de dispositivo trata de um tipo de dispositivo ou, no máximo, de uma classe de dispositivos intimamente relacionados. Por exemplo, provavelmente seria uma boa idéia ter um único Driver de terminal, mesmo que o sistema suportasse diversos tipos de terminal, todos ligeiramente diferentes. Por outro lado, um terminal burro para impressão de listagens e um terminal gráfico inteligente com um mouse são tão diferenciados que Drivers diferentes devem ser utilizados.
O Drivers de dispositivos são os únicos componentes do sistema que sabem realmente como as controladoras dos dispositivos são, quantos registradores elas tem, como é política de trabalho dela e as entradas que ela aceita. Na verdade são os Drivers que enviam os comandos para as controladoras e verificam se eles foram executados de maneira correta. Sendo assim, podemos resumir a tarefa de um Driver de dispositivo como aceitar as solicitações abstratas do software independente de dispositivo acima dele e cuidar para que a solicitação seja executava.
Enfim, podemos dizer que o Driver de dispositivo é de suma importância para o controle e manipulação dos diversos dispositivos existentes numa máquina. Sem ele a programação do sistema operacional seria muito trabalhosa e deveria considerar todos os casos possíveis de diferentes tipos para um mesmo dispositivo, sem contar que limitaria a produção de um tipo diferente de dispositivo para os sistemas operacionais já existentes.

Device Drivers possuem um papel especial no Kernel do Linux. São “caixas pretas” distintas que fazem uma parte em particular do hardware responder a uma interface de programação interna bem definida. Escondem completamente os detalhes de como o dispositivo funciona. Atividades de usuário são feitas através de um conjunto de chamadas padronizadas que são independentes do driver específico. O papel do device driver então é mapear estas chamadas para operações especificas do dispositivo que atuam sobre o hardware. É nesta interface de programação que os drivers podem ser construídos separadamente do resto do Kernel e “plugados” em tempo de execução quando necessário. Esta modularidade é que faz com que drivers para o Linux sejam fáceis de escrever.
Agora falaremos um pouco como é a arquitetura do Linux Device Driver. A figura que ilustra tal arquitetura encontra-se abaixo:

Arquitetura do Linux Device Driver

fig4.JPG

Figura 1.

Conforme a Figura 1 acima, Drivers no Linux são representados como módulos, que são partes de código que estendem a funcionalidade do Kernel do Linux. Módulos podem ser dispostos, a comunicação entre os módulos é feita através de chamadas de funções. Em tempo de carregamento (load time), um módulo exporta todas as funções que serão públicas para uma tabela de símbolos que o Linux mantém. Estas funções então são visíveis para todos os módulos. Acessos aos dispositivos são feitos através da camada de abstração de hardware, na qual sua implementação depende da plataforma de hardware em que o kernel é compilado, como por exemplo no x86 ou SPARC.

Requisitos: O que é desejável de um device-driver?

Devemos estar ciente das situações em que alguns tipos de acesso ao dispositivo poderiam afetar de maneira inapropriada o sistema como um todo, para evitar isso se deve, o device driver, fornecer controles adequados. Por exemplo, operações do dispositivo que afetam os recursos globais (tais como o ajuste de uma linha da interrupção), que poderiam danificar o hardware (firmware de carregamento, por exemplo), ou que poderiam afetar outros usuários (tais como o ajuste de um tamanho de bloco em uma movimentação de fita), são geralmente somente disponível aos usuários suficientemente privilegiados, e esta verificação deve ser feita no próprio device driver.
Os escritores dos Drivers devem ter cuidados para evitar a introdução de erros de segurança. A língua de programação C permite a ocorrência de diversos tipos dos erros. Muitos problemas atuais da segurança são criados, por exemplo, pelos erros do overrun do buffer, em que o programador se esquece de verificar quantos dados são escritos em um buffer, e os dados terminam pó serrem escritos além da extremidade do buffer, assim ocorre um overwriting de dados não relacionados. Tais erros podem comprometer o sistema inteiro e devem ser evitados. Felizmente, evitar estes erros é normalmente relativamente fácil no contexto do Driver de dispositivo, em que a relação ao usuário é estreitamente definida e altamente controlada.
Algumas outras idéias gerais da segurança valem a pena se terem em mente. Toda entrada recebida dos processos do usuário deve ser tratada com grande suspeita; nunca confiar nelas a menos que se tenha verificado. Tenha cuidado com a não inicializada memória; toda a memória obtida do Kernel deve ser zerada ou de outra maneira inicializada antes de ser disponível a um processo ou a um dispositivo. Se não, o escapamento da informação (divulgação dos dados, das senhas, etc.) pode ocorrer. Se seu dispositivo interpretar dados emitidos a ele, esteja certo que o usuário não pode enviar qualquer coisa que poderia comprometer o sistema. Finalmente, pense sobre o possível efeito de operações de dispositivo; se houver operações específicas (por exemplo, recarregando dos firmware em uma placa de adaptador ou formatação de um disco) que poderiam afetar o sistema, estas operações devem quase certamente ser restringidas a usuários privilegiados.

Ferramentas: O que é necessário para poder desenvolver um device-driver no Linux?

Conhecimentos básicos de compilação do kernel, experiência de programação em C no Linux e, finalmente, o conhecimento de técnicas de estruturas de dados, como a lista encadeada.
A primeira coisa que um programador deve saber antes de tentar escrever um
Driver, é de como o kernel do Linux compila, prestando atenção
para o processo de compilação (o compilador gcc).
Os drivers de dispositivo no Linux são conhecidos como módulos e podem ser carregados dinamicamente usando o comando insmod.

Um único módulo pode ser compilado por si só, e também pode ser ligada ao kernel (aqui, o cuidado tem que ser tomadas sobre o tipo de Driver).
Um exemplo: um módulo simples

# define MODULE 
#include <linux/module.h> 
int init_module (void) { /* Carrega um módulo no kernel * / 
printk ( "Olá kernel n"); 
return 0; 
} 
void cleanup_module (void) / * Remove módulo do kernel * / 
{  
printk ( "Adeus Kerneln"); 
}

Compilando o módulo

  1. gcc -c hello.c
  2. insmod hello.o

A saída é
Olá kernel

  1. rmmod hello.o

Adeus Kernel
Init_module Como funciona?

init_module carrega a imagem e coloca no espaço módulo do kernel e é executado. O módulo da função init.
O módulo começa com uma imagem módulo estrutura e é seguido pelo código de dados, conforme o caso.
Valor Retornado
No sucesso, zero é retornado. Em erro, -1 é retornado .

Desenvolvimento: Descreva uma implementação de driver USB?

Vamos começar tentando fazer uma lâmpada utilizando dispositivo USB no Linux. Don Marti assinalou um elegante dispositivo, o USB Indicador de sinal visual, fabricado pela Delcom Engenharia e mostrado na Figura abaixo. Não tenho qualquer relação com esta empresa, apenas acho que eles fazem bonitos produtos. Este dispositivo pode ser encomendado on-line a partir do site da Delcom, www.delcom-eng.com. Este artigo explica como o faz esse dispositivo funcionar em linux.

fig5.JPG

Figura 2. Delcom USB do sinal visual indicador é um simples primeiro USB programação projeto.
O Protocolo de Hardware
O primeiro objetivo, tentando escrever um driver para o dispositivo é determinar a forma de controlá-lo. Delcom Engenharia é bom o suficiente para transportar toda a especificação do protocolo USB e utilizar os seus dispositivos com o produto, e também está disponível on-line gratuitamente. A documentação revela que comandos o controlador do chip USB aceita e como utilizá-los. Oferecem também uma DLL do Microsoft Windows para ajudar os utilizadores de outros sistemas operacionais para escrever código para controlar o dispositivo.
A documentação para este dispositivo é apenas a documentação para o controlador USB na lâmpada. Ela não diz explicitamente o modo de ligar as diferentes cores de LEDs. Para isso, temos de fazer um pouco de investigação.
Nenhum Documentos? Engenharia reversa!
Se o protocolo USB para este dispositivo não tivesse sido documentado ou disponível para mim, eu teria de fazer engenharia reversa para obter esta informação a partir do próprio dispositivo. Uma ferramenta útil para este tipo de trabalho é um programa gratuito chamado USB Snoopy, www.wingmanteam.com / usbsnoopy; outra versão é o SnoopyPro, usbsnoop.sourceforge.net. Estes programas são os dois programas do Windows que permitem aos usuários capturar o USB dados que são enviadas e recebidas a partir de qualquer dispositivo USB em um sistema Windows. Tudo que um desenvolvedor precisa fazer é encontrar uma máquina com Windows, instalar o driver fornecido pelo fabricante para o dispositivo e executar o programa Snoop. Os dados são capturados em um arquivo para serem analisados posteriormente.
Após a abertura do dispositivo lâmpada, zelando para não perder a mola que facilmente pula no momento em que é aberto o dispositivo, a placa de circuito pode ser inspecionadas (figura abaixo). Usando um omímetro, ou qualquer tipo de dispositivo para a detecção de continuidade, foi determinado que os três diferentes LEDs estão ligados aos primeiros três pinos de 1 sobre a porta principal do chip controlador.
Na leitura da documentação, o comando USB para controlar o nível do port 1 pinos é Major 10 de Menores 2, Comprimento 0. O comando escreve o byte menos significativo do comando no port 1, e o port 1 é elevado ao valor de alto após reset. Então, é esse o comando USB que temos de enviar o dispositivo para mudar a diferentes LEDs.

fig6.JPG

Figura 3. Os três LEDs estão ligados aos primeiros três pinos do chip controlador.
Qual LED é qual?
Agora que sabemos quais os comandos para habilitar um pino do port, é preciso determinar que qual cor de LED é conectado com qual pino. Isso é fácil de fazer com um programa simples que percorre todas as combinações possíveis de diferentes valores para os três pinos do port e, em seguida, envia o valor para o dispositivo. Este programa permitiu-me a criar uma tabela de valores e cores dos LEDs (Tabela abaixo).

Tabela 1. Porta Valores e os conseqüentes LED Padrões
Valor dos Ports em hexadecimal | Valor dos Ports em binário | LEDs ligados
0x00 | 000 | Vermelho, Verde, Azul
0x01 | 001 | Vermelho, Azul
0x02 | 010 | Verde, Azul
0x03 | 011 | Azul
0x04 | 100 | Vermelho, Verde
0x05 | 101 | Vermelho
0x06 | 110 | Verde
0x07 | 111 | Nenhum LEDs ligado

Um Kernel Driver

Armados com os nossas recém encontradas informações, estamos a lançar-se uma rápida implementação do kernel driver. Deve ser um driver USB, mas qual é o tipo de interface no espaço de usuário devemos usar? Um bloco de dispositivo não faz sentido, uma vez que este dispositivo não necessita de armazenar arquivos de dados, mas um dispositivo de caractere poderia funcionar. Se usarmos um driver de dispositivo de caracteres, no entanto, um maior e menor número devem ser reservado para ele. E quantos menores números que temos para este driver? E se alguém quisesse ligar 100 diferentes dispositivos USB lâmpada para este sistema? Para nos antecipar quanto a isso, teríamos a necessidade de reservar pelo menos 100 menores números, o que seria um total desperdício se todos os usuários tivessem apenas um dispositivo por vez. Se fizermos um driver de caractere, também teriam de inventar alguma maneira de dizer ao driver para ligar e desligar as diferentes cores individualmente. Tradicionalmente, o que poderia ser feito utilizando-se diferentes comandos ioctl sobre o driver de caractere, mas sabemos muito bem como criar um novo comando ioctl no kernel.
Como todos os dispositivos USB aparecem em seu próprio diretório na árvore sysfs, então por que não utilizar sysfs e criar três arquivos no diretório do dispositivo USB, azul, vermelho e verde? Isso permitiria que qualquer programa de usuário especial, seja ele um programa em C ou um script, possa alterar as cores no nosso dispositivo LED. Isso também iria poupar-nos de ter de escrever um personagem condutor e implorar por um pedaço de menores números para o nosso dispositivo.
Para iniciar o nosso driver USB, é necessário fornecer o USB subsistema com cinco coisas:
• Um ponteiro para o módulo proprietário deste condutor: isto permite que o núcleo USB controle o módulo de contagem de referência do driver corretamente.
• O nome do driver USB.
• A lista dos IDs desse driver USB deverá fornecer: esta tabela é usada pela USB fundamentais para determinar qual controlador deve ser acompanhado até o dispositivo que, a hot-plug-espaço scripts do usuário possa utilizá-lo para carregar o driver automaticamente quando um dispositivo é ligado à rede.
• A probe () função chamada pelo núcleo quando um dispositivo USB é encontrado que coincide com o ID USB tabela.
• Um disconnect () função chamada quando o dispositivo é removido do sistema.
O Driver obtém essa informação com o seguinte trecho de código:

static struct usb_driver led_driver = {
    .owner =    THIS_MODULE,
    .name =        "usbled",
    .probe =    led_probe,
    .disconnect =    led_disconnect,
    .id_table =    id_table,
};

O id_table variável é definida como:

static struct usb_device_id id_table [] = {
    { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
    { },
};

MODULE_DEVICE_TABLE (usb, id_table);

O led_probe () e led_disconnect () funções são descritos posteriormente.
Quando o driver módulo é carregado, este led_driver estrutura deve ser registrado com o USB núcleo. Isto é realizado com uma única chamada para o usb_register () função:

retval = usb_register(&led_driver);
if (retval)
        err("usb_register failed. "
            "Error number %d", retval);

Do mesmo modo, quando o driver é descarregado a partir do sistema, deve-se retirar o USB núcleo:

usb_deregister(&led_driver);

O led_probe () é chamado quando o USB núcleo encontra o nosso dispositivo USB lâmpada. Tudo que precisa fazer é inicializar o dispositivo e criar o sysfs, três arquivos, no local adequado. Isto é feito com o seguinte código:

/* Initialize our local device structure */
dev = kmalloc(sizeof(struct usb_led), GFP_KERNEL);
memset (dev, 0x00, sizeof (*dev));

dev->udev = usb_get_dev(udev);
usb_set_intfdata (interface, dev);

/* Create our three sysfs files in the USB
* device directory */
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;

O led_disconnect () é tão simples, como temos apenas com a nossa memória alocada sysfs e remover os arquivos:

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");

Quando os ficheiros são lidos a partir sysfs, queremos mostrar o valor atual de LED que, quando é escrito para, queremos que o conjunto específico LED. Para fazer isto, a seguinte macro cria duas funções para cada cor e declara um LED sysfs dispositivo atributo arquivo:

#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);

São criadas seis funções, show_blue (), set_blue (), show_red (), set_red (), show_green () e set_green (); e três atributos estruturais, dev_attr_blue, dev_attr_red e dev_attr_green. Devido à simples natureza do arquivo sysfs chamadas e ao fato de que precisamos para fazer a mesma coisa para cada valor diferente (azul, vermelho e verde), uma macro foi utilizado para reduzir a digitação. Esta é uma ocorrência comum para ficheiro de funções sysfs; um exemplo disto na árvore fonte do kernel é o I2C chip drivers em drivers/i2c/chips.
Assim, para permitir que o LED vermelho, um usuário escreve um arquivo 1 para o vermelho no sysfs, que apela a set_red () em função do driver, que exige a função change_color () function. O change_color ()é algo parecido com:

#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 começa pela definição de todos os bits de cor para a variável 1. Então, se houver LEDs que estão a serem ativados, ele desliga-se apenas que bit específica. Em seguida, envie uma mensagem para o controle USB dispositivo para escrever que cor valor para o dispositivo.
É estranho que o primeiro parece minúsculo tampão variável, que é de apenas 8-bytes, é criado com uma chamada para kmalloc. Porque não simplesmente declará-lo na pilha e saltar a sobrecarga de alocação dinâmica e, em seguida, destruí-lo? Isto é feito porque algumas arquitecturas que executam o Linux não pode enviar dados USB criado na pilha do kernel, para que todos os dados que está a ser enviada para um dispositivo USB deve ser criados dinamicamente.
LEDs em Ação
Com este kernel driver criado, construído e carregado, quando a lâmpada dispositivo USB é ligado à corrente, o condutor é obrigado a isso. Todos os dispositivos USB vinculados a este driver pode ser encontrado no diretório sysfs para o motorista:

$ tree /sys/bus/usb/drivers/usbled/
/sys/bus/usb/drivers/usbled/
`-- 4-1.4:1.0 ->
../../../../devices/pci0000:00/0000:00:0d.0/usb4/4-1/4-1.4/4-1.4:1.0

O arquivo em que o diretório é um link de volta para a verdadeira localização da árvore sysfs para esse dispositivo USB. Se olharmos para o directório, podemos ver os arquivos do driver foi criado para os LEDs:

$ tree /sys/bus/usb/drivers/usbled/4-1.4:1.0/
/sys/bus/usb/drivers/usbled/4-1.4:1.0/
|-- bAlternateSetting
|-- bInterfaceClass
|-- bInterfaceNumber
|-- bInterfaceProtocol
|-- bInterfaceSubClass
|-- bNumEndpoints
|-- blue
|-- detach_state
|-- green
|-- iInterface
|-- power
|   `-- state
`-- red

Então, por escrito ou 0 ou 1 para o azul, verde e vermelho arquivos nesse diretório, os LEDs mudam de cor:

$ cd /sys/bus/usb/drivers/usbled/4-1.4:1.0/
$ cat green red blue
0
0
0
$ echo 1 > red
[greg@duel 4-1.4:1.0]$ echo 1 > blue
[greg@duel 4-1.4:1.0]$ cat green red blue
0
1
1

Isto produz a cor mostrada na Figura 4.

fig7.JPG

Figura 4. O dispositivo com os LEDs vermelho e azul ligados

Instalação: Descreva como instalar um device-driver?

Depois de criado o módulo no kernel do Linux, basta digitar o comando Insmod nome_do_driver.o e ele estará pronto para ser usado, conforme um exemplo mostrado mais acima nesse documento.

Testes: Como testar o device-driver?

Para testar um device Driver pode-se fazer um programa especifico que testa as condições críticas do Driver, verificando se os resultados de saída batem com a especificação do device Driver.

Referências.

http://www.linuxjournal.com/article/7353

http://www.freesoftwaremagazine.com/articles/drivers_linux?page=0%2C1

http://lwn.net/Kernel/LDD3/

Tanenbaum, Andrew S. – Sistemas operacionais: projeto e implementação.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License