Introdução aos Sistemas Operacionais/Exemplo de Drivers USB em Linux: Generic Bluetooth USB driver

Objetivos editar

Este conteúdo foi produzido com o intuito de demonstrar as especificidades de um driver usb para Linux, mais especificamente o driver btusb. Baseando em comentários do código fonte procuramos mostrar as funcionalidades do driver, suas funções principais e de interação com o dispositivo. Para estes fins utilizamos o dispositivo "Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)" como base para os experimentos.


Introdução editar

Neste trabalho iremos falar sobre o driver genérico Bluetooth em Linux (btusb.c), que é um driver para reconhecer dispositivos bluetooth no linux. Iremos neste e nos próximos capítulos descrever exemplos práticos da sua instalação e de como ele funciona além de mostrar trechos de código e as funcionalidades.

Abaixo segue algumas informações relacionadas ao driver em análise, que podem ser encontradas no site http://www.qbik.ch:

Author(s)	Marcel Holtmann
Maintainer	Marcel Holtmann
Maintainer Email	marcel <at> holtmann.org
Where to find	Included in Kernel sources
Driver mode	Kernel module

O conteúdo apresentado a seguir foi produzido com objetivo de demonstrar o conceito de drivers em Linux, e por meio de um estudo de caso, concretizamos o conhecimento de forma prática. Seguindo a metodologia de comentários linha-a-linha, citações e explicações das estruturas e funções, tornamos este conteúdo bem didático e prático para consultas, podendo assim ser utilizado como referência a futuros trabalhos na área de desenvolvimento de drivers para Linux

Informações gerais editar

  • Autores:
    • Wanderson Paim de Jesus
    • Everton Cerqueira de Souza
    • Bruno Campos Rodrigues
    • Chrystian Andersen
  • Versão do kernel:

2.6.32

  • Identificação do driver analisado:
    • Nome: Generic Bluetooth USB driver - btusb
  • Outras informações relevantes:

O dispositivo que será utilisado para demonstrar as diversas fases envolvidas no processo de reconhecimento e comunicação software-driver-hardware é o Mini Adaptador Bluetooth Usb Dongle 2.0. Abaixo segue suas especificações:

  • Compatível com USB 2.0 / 1.1 /1.0
  • Compatível com especificação Bluetooth 2.0 / 1.2 / 1.1
  • Suporta A2DP (Bluetooth Estéreo)
  • Suporta Enhanced data rate (v2.0 + EDR) = 3.0 Mbit/s
  • Vendor ID = 0a12
  • Product ID = 0001

 

Carga (load ou init) editar

  • Módulos Carregados:
 MODULE_DEVICE_TABLE(usb, btusb_table)
 module_init(btusb_init)
 module_exit(btusb_exit)
 module_param(ignore_dga, bool, 0644)
 MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001")
 module_param(ignore_csr, bool, 0644)
 MODULE_PARM_DESC(ignore_csr, "Ignore devices with id 0a12:0001")
 module_param(ignore_sniffer, bool, 0644)
 MODULE_PARM_DESC(ignore_sniffer, "Ignore devices with id 0a12:0002")
 module_param(disable_scofix, bool, 0644)
 MODULE_PARM_DESC(disable_scofix, "Disable fixup of wrong SCO buffer size");
 module_param(force_scofix, bool, 0644)
 MODULE_PARM_DESC(force_scofix, "Force fixup of wrong SCO buffers size")
 module_param(reset, bool, 0644)
 MODULE_PARM_DESC(reset, "Send HCI reset command on initialization")
 MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>")
 MODULE_DESCRIPTION("Generic Bluetooth USB driver ver " VERSION)
 MODULE_VERSION(VERSION)
 MODULE_LICENSE("GPL")
  • Processo de carga do módulo

Ao conectar o dispositivo a uma porta usb, um driver deve ser associado a ele, e para isso são analisados os dispositivos suportados por cada driver. Então o driver escolhido é carregado utilizando sua função de carga, ou init. A função de carga do btusb é a seguinte:

 static int __init btusb_init(void)
 {
        BT_INFO("Generic Bluetooth USB driver ver %s", VERSION);
        return usb_register(&btusb_driver);
 }
  • 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

Os drivers genéricos geralmente dão suporte a uma série de dispositivos. Abaixo segue uma lista de dispositivos reconhecidos pelo driver seguidos de uma lista dos dispositivos não reconhecidos.

  • Dispositivos que podem ser reconhecidos segundo o fabricante:
"Generic Bluetooth USB device ", 
"Apple MacBookPro 7,1", 
"Apple iMac11,1 ",
"Apple MacBookPro6,2 ",
"AVM BlueFRITZ! USB v2.0 ",
"Bluetooth Ultraport Module from IBM ",
"ALPS Modules with non-standard id ",
"Ericsson with non-standard id",
"Canyon CN-BTU1 with HID interfaces"
"Broadcom / BCM2033 "
"Dell Computer " 
"Integrated System Solution / KY-BT100 Bluetooth Adapter "
"Dell Computer / BCM2045 "
"Dell Computer / Wireless 350 Bluetooth "
"Broadcom / BCM2045B "
"Dell Computer / BCM2046 Bluetooth Device "
"ASUSTek Computer / BT-253 "
"Cambridge Silicon Radio / Bluetooth Dongle (HCI mode) "
"ASUSTek Computer / "
"Broadcom / BCM92045B3 ROM "
"Hewlett-Packard / HP Integrated Module "
"ASUSTek Computer / BT-253 "
"Broadcom / BCM92045B3 ROM "
"Broadcom / Foxconn Bluetooth 2.0 plus EDR "
"Belkin Components / BCM92045DG Non-UHE "
"Apple Computer "
"Broadcom / BCM92035DGROM "
"Broadcom / BCM2045A "
"SiW / SiW "
"Broadcom / IBM Bluetooth Module "
"Dell Computer / Dell Wireless 370 Bluetooth Mini-card "
"Broadcom / ThinkPad Bluetooth with Enhanced Data Rate II "
"Alps Electric / BCM2046 Bluetooth Device "
"Compaq Computer / Bluetooth by hp "
"Alps Electric / UGX "
"Taiyo Yuden "
"Apple Computer / Bluetooth USB Host Controller "
"Broadcom / BCM92046DG-CL1ROM "
"Alps Electric / UGX "
"Broadcom / BCM2045A "
"Micro Star International "
"Broadcom / BCM2046 Bluetooth Device "
"Hewlett-Packard / HP Bluetooth Module "
"Foxconn / Hon Hai / Broadcom Bluetooth Device "
"ASUSTek Computer / BT-270 "
"Hewlett-Packard / HP Integrated Module "


  • Dispositivos que não podem ser reconhecidos (lista negra):
"CSR BlueCore devices  Broadcom ", 
"BCM2033 without firmware ", 
"Broadcom BCM2035 ",
"Broadcom BCM2045 ",
"IBM/Lenovo ThinkPad with Broadcom chip ",
"HP laptop with Broadcom chip ",
"Dell laptop with Broadcom chip ",
"Dell Wireless 370 and 410 devices ",
"Belkin F8T012 and F8T013 devices ",
"Asus WL-BTD202 device ",
"Kensington Bluetooth USB adapter ",
"RTX Telecom based adapters with buggy SCO support ",
"CONWISE Technology based adapters with buggy SCO support ",
"Digianswer devices ",
"CSR BlueCore Bluetooth Sniffer ",
"Frontline ComProbe Bluetooth Sniffer "
  • Como se processa a identificação do dispositivo
 static struct usb_driver btusb_driver = {
        .name           = "btusb",
        .probe          = btusb_probe,
        .disconnect     = btusb_disconnect,
 #ifdef CONFIG_PM
        .suspend        = btusb_suspend,
        .resume         = btusb_resume,
 #endif
        .id_table       = btusb_table,
        .supports_autosuspend = 1,
 };
  • O que ocorre quando o driver não é reconhecido (erro), etc;

Quando acontece um erro na hora da inicialização as seguintes funções são chamadas:

 usb_free_urb(entry); //que libera a memória usada pela urb quando esta tudo acabado
 kfree_skb(skb); //libera o buffer e seta sua referencia como zero
 p54u_free_urbs(dev); //chama uma função que faz o sistema parar de rastrear os urb

E depois chama o return

 return ret;

Inicialização de estruturas editar

  • Quais estruturas são inicializadas

As estruturas inicializadas ao chamar o btusb.c são:

usb_driver
btusb_driver
usb_device_id
btusb_data
  • Tipo de estruturas inicializadas (buffer, por exemplo)
  • Registro de URB;

Registro do driver (register) editar

  • Como o driver é reconhecido

O processo de reconhecimento do módulo é feito automaticamente quando algum dispositivo sem driver específico é conectado na porta usb. Para demonstrar este passo, utilizamos o dispositivo Mini Adaptador Bluetooth Usb Dongle 2.0. Iniciamos o processo de identificação do dispositivo executando o comando dmesg antes e depois de conecta-lo, redirecionando a saida para arquivos de texto distintos. Desta forma podemos compara-los utilizando a função diff.

> dmesg > saida1
> dmesg > saida2
> diff saida1 saida2

Saida:

> [29376.760175] usb 4-2: new full speed USB device using uhci_hcd and address 3
> [29376.918448] usb 4-2: configuration #1 chosen from 1 choice

O resultado obtido acima não mostra o driver que foi utilizado, pois este já estava carregado quando a máquina foi iniciada. Mas analisando algumas linhas da saida do comando dmesg, encontramos alguns dados relevantes:

[   22.102349] NET: Registered protocol family 31
[   22.102351] Bluetooth: HCI device and connection manager initialized
[   22.102354] Bluetooth: HCI socket layer initialized
[   22.103951] Bluetooth: Generic Bluetooth USB driver ver 0.6
[   22.104051] usbcore: registered new interface driver btusb

Esses dados mostram que o driver btusb foi associado ao dispositivo de Bluetooth conectado.


  • Quais drivers que foram carregados para o dispositivo, etc;

Quando o dispositivo é plugado, alguns drivers são associados a ele e podem ser listados com o comando lsmod | more. Na saída do comando, executado para o caso de teste deste documento, encontramos os seguintes dados:

btusb                  13001  4 
bluetooth              58685  10 hidp,rfcomm,sco,bnep,l2cap, btusb 
  • Quais procedimentos (comandos) para verificar qual dispositivo específico que foi reconhecido

O dispositivo ainda pode ser checado com o comando lsusb, que mostra todos os dispositivos conectados as portas USB disponíveis no computador, separados por barramentos (Bus), que funcionam de forma semelhante a um HUB (equipamento de rede). De modo que todo dispositivo ligado a esse barramento forma um novo Device. Ao executarmos esse comando com o nosso dispositivo conectado obtemos a seguinte saida:

Bus 004 Device 003: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 002: ID 064e:a110 Suyin Corp. HP Webcam

Analisando essa saída, podemos interpretar que o nosso dispositivo (o primeiro da lista) está ligado no barramento 004 e é o terceiro Device neste barramento. Ainda podemos reconhecer nesta saída o Vendor ID e Product ID, identificados no último parâmetro antes do nome do dispositivo, 0a12:0001 que representa VendorID:ProductID.

Comunicação editar

  • Arquivos especiais de dispositivo (/proc, /dev e /sys)
  • Criação de endpoint

A comunicação via USB se dá através dos chamados endpoints. Um endpoint pode transportar dados em uma única direção, seja do computador para o dispositivo (chamado OUT endpoint) ou do dispositivo para o computador (chamado IN endpoint). Os endpoints são descritos no kernel por um ponteiro que aponta para uma estrutura chamada usb_endpoint_descriptor, que contém todos os dados específicos da USB nos mesmos formatos especificados pelo próprio driver. Podemos identificar estas estruturas no driver em análise (btusb) nas seguintes linhas:


163 struct btusb_data {
        .
        .
        .
184         struct usb_endpoint_descriptor *intr_ep;
185         struct usb_endpoint_descriptor *bulk_tx_ep;
186         struct usb_endpoint_descriptor *bulk_rx_ep;
187         struct usb_endpoint_descriptor *isoc_tx_ep;
188         struct usb_endpoint_descriptor *isoc_rx_ep;
        .
        .
        .
}


Os atributos disponibilizados por estas estruturas estrutura são acessados por diversas vezes na estrutura do driver:

bEndpointAddress - endereço USB deste endpoint em específico.

273    pipe = usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress);
.
.
.
355    pipe = usb_rcvbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress);
.
.
.
468    pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress);
.
.
.
685    pipe = usb_sndbulkpipe(data->udev,
686                           data->bulk_tx_ep->bEndpointAddress);
.
.
.
702    pipe = usb_sndisocpipe(data->udev,
703                           data->isoc_tx_ep->bEndpointAddress);

wMaxPacketSize - expecifica o tamanho máximo, em bytes, que este endpoint pode transportar por vez. Vale ressaltar que o tamanho dos dados enviados pelo driver poder ser maior que este valor, mas os dados serão divididos em (dataSize/wMaxPacketSize) pacotes, que finalmente serão transmitidos ao dispositivo. Acompanhe no exemplo em estudo:


265    size = le16_to_cpu(data->intr_ep->wMaxPacketSize);
.
.
.
459    size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) *
460                                          BTUSB_MAX_ISOC_FRAMES;
.
.
.
480    __fill_isoc_descriptor(urb, size,
481                    le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize));
.
.
.
715    __fill_isoc_descriptor(urb, skb->len,
716                         le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize));


bInterval - Este atributo é usado para os endpoints do tipo interrupção, e este valor representa o intervalo entre as requisições de interrupção. Valor que é representado em milisegundos. Segue sua utilização no driver btusb.


275    usb_fill_int_urb(urb, data->udev, pipe, buf, size,
276                                            btusb_intr_complete, hdev,
277                                            data->intr_ep->bInterval);
.
.
.

474    urb->interval = data->isoc_rx_ep->bInterval;
.
.
.
709    urb->interval = data->isoc_tx_ep->bInterval;
  • Criação de interface

Os endpoints, apresentados na seção anterior, são encapsulados por uma interface, que controlam apenas um tipo de conexão USB lógica, como um mouse, teclado ou stream de audio. Sendo assim, cada interface é controlada exclusivamente por um driver, desta forma, quando um dispositivo precisa de se comunicar com o computador por meio de mais de uma interface, serão necessários mais de um driver. Um para cada interface. As interfaces possuem alguns parâmetros de configuração que podem ser alterados de acordo com a necessidade e especificidade de cada endpoint. O estado inicial da interface pode ser alterado pelos parametros de configuração, que podem definir, por exemplo, a largura de banda USB exigida pelo dispositivo. A estrutura definida no kernel pelas interfaces é a usb_interface e está representada no btusb na definição das estruturas iniciais:

163 struct btusb_data {
.
.
166         struct usb_interface *intf;
167         struct usb_interface *isoc;
.
.
.
  • Criação de configuração (configuration)
  • Tipos de comunicação utilizados pelo driver (control, bulk, etc);

Os drivers USB comunicam com todos os dispositivos USB através de uma estrutura chamada urb (USB request block), ela é utilizada para enviar e receber dados de um endpoint USB em um dispositivo específico de maneira assincrona. Abaixo segue o trecho de código do btusb referente a comunicação utilizando este conceito de urb.

static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags)
{
	struct btusb_data *data = hdev->driver_data;
	struct urb *urb;
	unsigned char *buf;
	unsigned int pipe;
	int err, size;

	BT_DBG("%s", hdev->name);

	if (!data->intr_ep)
		return -ENODEV;
/*
* Quando um driver tem dados pra mandar para o dispositivo USB, um urb (usb request block) 
* deve ser alocado para transmitir estes dados ao dispositivo. A chamada ao usb_alloc_urb é feita
* neste caso.
*/
	urb = usb_alloc_urb(0, mem_flags);
	if (!urb)
		return -ENOMEM;

	size = le16_to_cpu(data->intr_ep->wMaxPacketSize);

/*
* Uma vez alocado o urb, deve ser criado um buffer de DMA para mandar dados para o dispositivo de maneira
* mais eficiente, assim como os dados passados para o driver.
*/

	buf = kmalloc(size, mem_flags);
	if (!buf) {
		usb_free_urb(urb);
		return -ENOMEM;
	}

	pipe = usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress);

/*
* Agora que os dados do usuário já foram copiados para o buffer local, o urb deve ser inicializado
* corretamente antes de ser submetido ao núcleo USB.
*/
        /* Inicializa o urb de fato */
	usb_fill_int_urb(urb, data->udev, pipe, buf, size,
						btusb_intr_complete, hdev,
						data->intr_ep->bInterval);

	urb->transfer_flags |= URB_FREE_BUFFER;

	usb_anchor_urb(urb, &data->intr_anchor);


/*
* Agora que o urb já está devidamente alocado, os dados estão copiados e o urb
* devidamente inicializado e submetido ao núcleo do USB, ele pode ser transmitido ao dispositivo
*/


	err = usb_submit_urb(urb, mem_flags);
	if (err < 0) {
		BT_ERR("%s urb %p submission failed (%d)",
						hdev->name, urb, -err);
		usb_unanchor_urb(urb);
	}

	usb_free_urb(urb);

	return err;
}

Finalização editar

  • Mensagens de remoção do dispositivo
 static void __exit btusb_exit(void)
 {
         usb_deregister(&btusb_driver);
 }
  • Desalocação/liberação de estruturas;

Considerações finais editar

  • Informações gerais do driver
  • Conclusões
  • Mapa conceitual sobre o driver estudado;

Bibliografia/Referências editar