Este post foi elaborado em dupla, juntamente com o aluno José Gerardo Arruda Júnior.
- A importância dos device drivers
Eles atuam como uma camada de abstração entre os dispositivos de hardware e o SO, ou aplicativos, e simplificam a programação, tornando possível a escrita de softwares independentes do tipo de hardware que está sendo utilizado (para citar um rápido exemplo, ao escrever um programa que necessita enviar um comando de impressão para uma impressora, é desejável que o fato da impressora ser da HP ou da Lexmark não faça diferença).
Além de prover uma camada de abstração e de mapear as chamadas do SO ao hardware, os device drivers também são importantes por otimizarem o desempenho de operações de IO, incluir tratamento de erro a nível de hardware e software e permitir acesso paralelo, por diversos dispositivos, ao hardware.
No Linux, os device drivers se comportam como "caixas pretas" responsáveis por fazer com que determinado componente de hardware responda às chamadas de uma API bem definida, que é a interface de comunicação entre os drivers e o SO, já vista anteriormente.
A arquitetura de device drivers no Linux apresenta modularidade, ou seja, os drivers são construídos separadamente do restante do Kernel e, quando necessário, são "plugados" em tempo de execução. Essa modularidade facilita o desenvolvimento de drivers para Linux.
Assim, podemos dizer que no Linux os device drivers são representados como módulos, que são programas que estendem a funcionalidade do Kernel e que realizam sua comunicação através de chamadas de funções/métodos das interfaces públicas disponibilizadas por eles.
- O que é desejável de um device-driver?
Alguns dos requisitos essenciais de um device driver vistos até agora foram: implementação de uma interface disponibilizada pelo SO para mapear as requisições do SO ao hardware, fornecimento de uma camada de abstração ao programador, otimização de desempenho de operações de IO, tratamento de erros a nível de software e hardware, e gerenciamento de acesso paralelo, por diversos dispositivos, ao hardware. Contudo, falta explicitar uma característica muito importante desejável aos device drivers, que é a flexibilidade.
A palavra flexibilidade enfatiza o papel do device driver de prover um mecanismo e não uma política. A idéia de mecanismo vem de "Quais funcionalidades devem ser fornecidas?" enquanto a de política vem de "Como essas funcionalidades podem ser utilizadas?" .
Dessa forma, quando se está desenvolvendo um driver, o programador deve estar atento ao conceito de escrever código para acessar o hardware, mas não forçar políticas ao usuário, uma vez que diferentes usuários possuem diferentes necessidades. Dessa forma, o driver deve lidar com o problema de tornar o hardware acessível, deixando o problema de como usar o hardware para o SO/aplicativos.
- O que é necessário para poder desenvolver um device-driver no Linux?
Em termos práticos, de ambiente de desenvolvimento, a partir da versão 2.6.x do Kernel do Linux, a compilação de drivers tornou-se um pouco mais complicada do que era anteriormente, tornando-se necessário que se tenha a árvore de código fonte do kernel completa compilada, uma vez que é necessário compilar o módulo (driver) utilizando-se o mesmo kernel no qual você irá instalar o seu módulo (driver) (ver abaixo).
Vamos descrever aqui como fazer para criar e compilar um device driver bem simples (inútil na verdade, mas serve a título de exemplo), a ser incorporado ao kernel como um módulo.
Primeiramente crie um arquivo .c (driverTest.c) com o seguinte conteúdo:
#include [linux/module.h]
#include [linux/init.h]
#include [linux/kernel.h]
MODULE_LICENSE("GPL")
static int hello_init(void){
printk(" [1]Hello World!\n" );
return 0;
}
static void hello_exit(void){
printk("[1] Bye World!\n" );
}
module_init(hello_init);
module_exit(hello_exit);
Quando um device driver é carregado no kernel, algumas tarefas preliminares são executadas, como resetar o dispositivo, reservar memória RAM, reservar interrupções, reservar ports de IO, etc. Essas tarefas são executadas em kernel-space por duas funções que precisam estar presentes (e explicitamente declaradas) no código do seu módulo (driver): module_init e module_exit. Elas correspondem aos comandos insmod e rmmod do user-space (ver abaixo), que são utilizados para instalar e remover módulos do kernel, respectivamente.
As funções hello_init e hello_exit podem receber qualquer nome. Contudo, para que elas sejam identificadas como sendo as funções de instalação e remoção do driver, elas precisam ser passadas como parâmetro para as funções module_init e module_exit. É isso que está acontecendo no nosso exemplo.
A função printk é bem parecida com a conhecida função printf, com a diferença que ela só funciona dentro do kernel.
Para compilar o seu módulo vc precisa gerar um makefile
obj-m := driverTest.o
e para compilar o módulo digite (assumindo que a versão do kernel utilizado é 2.6.8):
$ make -C /usr/src/kernel-source-2.6.8 M=pwd modules
Esse módulo agora pertence ao kernel-space e irá se tornar parte dele assim que for carregado.
- Como se instalar um device-driver USB?
Para remover o módulo (driver) do kernel utiliza-se o comando rmmod.
Através do comando lsmod é possível verificar se o módulo (driver) foi instalado/desinstalado corretamente.
- Descrevendo uma implementação de driver USB
Inicialmente especificada em 1996 e muito difundida na atualidade, a tecnologia USB apresenta diversas características muito vantajosas como por exemplo: a padronização de conexão, utilização de tecnologia Plug and Play, alimentação elétrica própria, conexão de vários aparelhos ao mesmo tempo (é possível conectar até 127 dispositivos ao mesmo tempo em uma única porta USB), ampla compatibilidade (relativo a plataformas e sistemas operacionais), cabos longos (até cinco metros de comprimento), hot-swap (possibilidade de conectar e desconectar o dispositivo com o computador ligado).
O protocolo de comunicação entre os dispositivos conectados via USB é tal que o host, ou o computador ou equipamento que recebe as conexões, emite um sinal para encontrar os dispositivos conectados e estabelece um endereço para cada um deles.
Uma vez estabelecida a comunicação, o host recebe informação sobre qual tipo de conexão o dispositivo conectado utiliza.
Há quatro tipos distintos de conexões USB, a saber: Bulk (utilizado por dispositivos que manipulam grandes volumes de informações, como scanners e impressoras), Control (utilizado para controle e configuração de dispositivos), Interrupt (utilizado para dispositivos que manipulam/transferem poucos dados, como teclados e mouses) e Isochronous (utilizado em transmissões contínuas, onde os dados são transferidos a todo momento, como em caixas de som).
Na figura 2, temos um esquema representativo do suporte oferecido pelo Linux à tecnologia USB. Na figura podemos ver o USB Core, que é um subsistema do kernel que fornece uma API específica para suportar dispositivos USB e controladores host. A função do USB Core é abstrair todo o hardware ou partes dependentes de dispositivos através da definição de estruturas de dados, macros e funções.
Para exemplificar como isso é feito, podemos apresentar algumas das funções mais utiilzadas, como por exemplo a função interface_to_usbdev() que
Na API do USB Core, dispositivos são representados pela estrutura usb_device e drivers USB devem definir uma estrutura usb_driver como a seguir:
//Nome único ao driver.
//Normalmente utiliza-se o nome do módulo.
const char *name;
const struct usb_device_id *id_table;
int (*probe)
(struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect)
(struct usb_interface *intf);
Para se registrar o seu driver, deve-se utilizar a função usb_register(), como no exemplo a seguir:
static struct usb_driver mtouchusb_driver = {e para se desregistrar o driver deve-se utilizar usb_deregister(), como a seguir:
.name = "mtouchusb",
.probe = mtouchusb_probe,
.disconnect = mtouchusb_disconnect,
.id_table = mtouchusb_devices,
};
static int __init mtouchusb_init(void){
dbg("%s - celled", __FUNCTION__);
return usb_register(&mtouchusb_driver);
}
static int __exit mtouchusb_cleanup(void){
dbg("%s - celled", __FUNCTION__);
return usb_deregister(&mtouchusb_driver);
}
Para saber mais respeito de como utilizar a API do USB Core, recomendo [12] e [13]
- Como se testar um device-driver?
1) Instale o seu driver em um local temporário:
Instale os drivers no diretório /tmp até que se tenha concluído as modificações e testes das rotinas __info(), __init() e attach(). Copie o binário do seu driver no diretório /tmp e faça o link para o driver do diretório de drivers do kernel.
2) Utilize uma conexão serial para controlar sua máquina de testes através de um sistema host separado.
3) Utilize um kernel alternativo.
4) Utilize um kernel adicional para realizar experimentos com configurações diferentes de variáveis de kernel.
5) Construir planos de contingência para potenciais perdas de dados durante a execução dos testes do sistema.
Referências:
Todos os sites utilizados como referência foram visitados no dia 12 de Julho de 2009.
[1] http://en.wikipedia.org/wiki/Device_driver
[2] http://www.inf.pucrs.br/~eduardob/disciplinas/ProgPerif/sem09.1/trabalhos/Seminarios/g3/Device%20Drivers%20no%20Windows%20e%20Linux.doc
[3] http://www.linuxjournal.com/article/2476
[4] http://www.lrr.in.tum.de/Par/arch/usb/usbdoc/
[5] http://lwn.net/Kernel/LDD3/
[6] http://www.freesoftwaremagazine.com/articles/drivers_linux?page=0%2C0
[7] http://matthias.vallentin.cc/2007/04/writing-a-linux-kernel-driver-for-an-unknown-usb-device/
[8] http://linux.about.com/od/commands/l/blcmdl8_insmod.htm
[9] http://linux.about.com/library/cmd/blcmdl8_rmmod.htm
[10] http://linux.about.com/library/cmd/blcmdl8_lsmod.htm
[11] http://www.infowester.com/usb.php
[12] http://free-electrons.com/docs/linux-usb/
[13] http://www.lrr.in.tum.de/Par/arch/usb/usbdoc/node18.html
[14] http://docs.sun.com/app/docs/doc/819-3159/fsfqv?a=view
[15] http://docs.sun.com/app/docs/doc/819-3159/fdlbq?a=view
No comments:
Post a Comment