Introdução aos Sistemas Operacionais/Exemplo de Drivers USB em Linux: Multilaser Mouse USB
Objetivos
editarEste material é fruto de um estudo de caso de um driver usb(Multilaser Mouse USB) para linux e tem como intuito principal apresentar as características deste driver. Visamos com este material, tornar fácil a compreensão do código fonte, através do detalhamento do código e explicações baseadas em nossos conhecimentos e comentários feitos pelo autor.
Introdução
editarAqui será apresentado de uma forma clara, o funcionamento do driver Multilaser Mouse USB(usbmouse.c), como: Detalhamento do driver escolhido, código-fonte para download, quais módulos são carregados e como são carregados, dispositivos que podem ser reconhecidos e os que não podem ser reconhecidos com este driver, estruturas utilizadas, registro do driver, comunicação, etc.
Será apresentado também trechos de códigos relativos aos tópicos citados, como forma de exemplificar a funcionalidade descrita.
O módulo usbmouse.c ativa um suporte genérico a mouses usb.
Informações gerais
editar- Versão do kernel:
- Identificação do driver analisado:
- Nome: USB HID Boot Protocol mouse driver
- Lista de arquivos de códigos-fontes analisados:
- Outras informações relevantes:
Carga (load ou init)
editar- Módulos Carregados:
MODULE_AUTHOR ( "Vojtech Pavlik <vojtech@ucw.cz>" )
MODULE_DESCRIPTION ( "USB HID Boot Protocol mouse driver" )
MODULE_LICENSE ( "GPL" )
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table)
module_init(usb_mouse_init)
module_exit(usb_mouse_exit)
- Processo de carga do módulo
A carga do módulo é feita pela função module_init
- Parâmetros que podem ser passados na carga do módulo
- Como passar uma parâmetro para o módulo, etc;
Reconhecimento ou detecção (probe)
editar- Dispositivos que podem ser reconhecidos segundo o fabricante:
- Dispositivos que não podem ser reconhecidos:
- Como se processa a identificação do dispositivo
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
Em caso de reconhecimento do dispositivo, ou seja, se o dispositivo puder ser controlado por este driver, chamará a funão probe. Em outras palavras, o campo probe define usb_mouse_probe como a função a ser chamada quando o USB core desejar testar se o driver pode suportar determinado dispositivo.
Segue sintaxe da função usb_mouse_probe:
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;
interface = intf->cur_altsetting;
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
#ifdef CONFIG_USB_HID
if (usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct))
& (HID_QUIRK_IGNORE|HID_QUIRK_IGNORE_MOUSE)) {
return -ENODEV;
}
#endif
//segue com a inicialização dos dados como descrito no próximo tópico.
- O que ocorre quando o driver não é reconhecido (erro), etc;
Inicialização de estruturas
editarApós a identificação do dispositivo, a inicialização de algumas estruturas se torna necessária para estabelecer a comunicação com ele.
- Quais estruturas são inicializadas
struct usb_mouse {
//nome do dispositivo
char name[128];
char phys[64];
//dispositivo USB
struct usb_device *usbdev;
//Dispositivo de entrada
struct input_dev *dev;
//Pedido USB de bloco
struct urb *irq;
// interrupção de dados USB
signed char *data;
dma_addr_t data_dma;
};
- Tipo de estruturas inicializadas (buffer, por exemplo)
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
//inicializando a estutura mouse
mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
input_dev = input_allocate_device();
if (!mouse || !input_dev)
goto fail1;
//inicializando o buffer
mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma);
if (!mouse->data)
goto fail1;
//inicializando os URBs
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
//Condições de controle
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
//copiam informações para uma posterior utilização
mouse->usbdev = dev;
mouse->dev = input_dev;
//define um caminho estável na árvore de dispositivos USB
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
- Registro de URB;
//Alocando os URBs
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
//...
//Após a utilização é feito a desalocação dos URBs
usb_free_urb(mouse->irq);
- Tipos de URBs
- INTERRUPT
Estrutura:
/**mouse->irq: Um ponteiro para o URB a ser inicializado.
dev: Indica o dispositivo USB para o qual o URB será enviado.
pipe: India para qual endpoint do dispositivo URB será enviado.
mouse->data: Um ponteiro para o buffer de enviou ou recepção de dados.
(maxp > 8 ? 8 : maxp): Indica o tamanho do buffer apontado por mouse->data.
usb_mouse_irq: É um apontador para uma função do tipo callback que deverá ser chamada quando o URB for completado.
mouse: Equivale ao campo void *mouse da estrutura struct mouse->irq, comentada anteriormente.
endpoint->bInterval: Indica o intervalo entre as interrupções, conforme definido na estrutura mouse->irq.
*/
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
- BULK
Não possui.
- CONTROL
Não possui.
- ISOCHRONOUS
Não possui.
Registro do driver (register)
editar- Como o driver é reconhecido
Uma vez que a estrutura usb_driver estiver definida, o dispositivo poderá ser registrado no sistema através da chamada à função usb_register, como segue no código:
static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver);
if (retval == 0)
info(DRIVER_VERSION ":" DRIVER_DESC);
return retval;
}
- Quais procedimentos (comandos) para verificar qual dispositivo específico que foi reconhecido
A estrutura struct usb_device_id *id_table: aponta para a lista de estruturas, na qual cada item identifica um dispositivo suportado para que, no momento da inserção de um novo dispositivo, o USB core possa verificar se o driver pode ou não controlá-lo, comparando suas características com os dados desta tabela.
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
- Quais drivers que foram carregados para o dispositivo, etc;
Comunicação
editar- Arquivos especiais de dispositivo (/proc, /dev e /sys)
- Criação de interface
O USB core disponibiliza para ca driver a estrutura struct usb_interface, e a partir desta estrutura que o driver gerencia o dispositivo.
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
//...
interface = intf->cur_altsetting;
}
- Criação de endpoint
Toda a comunicação entre um dispositivo USB e o USB host começa ou termina em um endpoint.
No kernel do Linux, os endpoints são acessados através da estrutura struct usb_host_endpoint(definida em usb.h), na qual contém uma subestrutura chamada struct usb_endpoint_descriptor que aponta para as informações reais do endpoint, exatamente no formato especificado pelo dispositivo.
Neste driver esta estrutura é definida na função usb_mouse_probe, como segue no exemplo:
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_endpoint_descriptor *endpoint;
//...
endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
//...
}
- Criação de configuração (configuration)
As configurações são utilizadas para permitir que um mesmo dispositivo possa operar de formas diferentes, conforme apropriado.
O linux permite o acesso às configurações de um dispositivo através da estrutura struct usb_host_config e ao dispositivo como um todo por meio da estrutura struct usb_device. O USB core exige que as informações sobre o dispositivo lhe sejam passadas no forma da estrutura usb_device.
Neste driver ela é especificada na função usb_mouse_probe.
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
//...
}
- Tipos de comunicação utilizados pelo driver (control, bulk, etc);
O driver usbmouse.c utiliza somente a comunicação do tipo INTERRUPT, para transmissão de pequenas quantidade de dados mediante a solicitação do computador ou do dispositivo em uma taxa fixa. Método também utilizado por teclados.
O nome deste tipo de transmissão se deve ao fato de ocorrer em intervalos pré-definidos. Em alguns caso, é utilizada para controlar o dispositivo.
Finalização
editar- Mensagens de remoção do dispositivo
- Desalocação/liberação de estruturas;
Qualquer funcionalidade registrada deve ser desalocada da memória quando não for mais necessária. Neste caso o driver é desalocado através da função usb_deregister invocada na função de saída do módulo.
static void __exit usb_mouse_exit(void)
{
usb_deregister(&usb_mouse_driver);
}
Considerações finais
editar- Informações gerais do driver
- Conclusões
- Mapa conceitual sobre o driver estudado;