Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Драйверы устройств в Linux

Часть 12: Драйверы USB в Linux - продолжение

Оригинал: "Device Drivers, Part 12: USB Drivers in Linux Continued"
Автор: Anil Kumar Pugalia
Дата публикации: November 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

В этой двенадцатой статье из серии статей, посвященных драйверам устройств в Linux, мы продвинемся дальше в написании нашего первого драйвера USB для Linux; эта статья - продолжение предыдущей статьи.

Пагс продолжил: "Давай соберем драйвер устройства USB, который мы закодировали предыдущий раз, и будет использовать то же самое удобное флеш-устройство JetFlash фирмы Transcend, имеющее идентификационный код поставщика 0x058f (vendor ID) и идентификационный код изделия 0×6387 (product ID). Для этого давай подробнее рассмотрим протокол USB, а затем преобразуем наши знания в код".

Источники / приемники данных устройств USB и их типы

В зависимости от типа и параметров информации, подлежащей передаче, устройство USB может иметь один или несколько источников / приемников данных, каждый из которых должен относиться к одной из следующих четырех категорий:

  • Control - для передачи управляющей информации. К примерам относятся перезагрузка устройства, запрос информации об устройстве и т. д. Во всех устройствах USB всегда есть источник / приемник данных типа control, имеющий номер ноль и используемый по умолчанию.
  • Interrupt - для быстрой передачи данных малого объема, как правило, до 8 байтов. Например, передача данных в последовательные порты, обмен данными с устройствами, с которыми непосредственно работает человек (HIDS - human interface devices), такими как клавиатуры, мыши и т. д.
  • Bulk - для большой, но сравнительно медленной передачи данных.Типичным примером будет передача данных в запоминающие устройства.
  • Isochronous - для передачи данных большого объема при наличии широкой пропускной способности канала, но целостность данных не гарантируется. На практике типичными примерами будут данные, для которых важно время передачи, например, аудиоданные, видеоданные и т.д.

Кроме того, для всех случаев, кроме источников / приемников данных типа control, должен указываться режим "in" или "out", определяющий направление передачи данных; "in" означает передачу данных с устройства USB на хост-машину, а "out" - наоборот.

Технически, источник / приемник данных идентифицируется с помощью 8-битового числа, в самом старшем бите (MSB) которого указывается направление потока данных: 0 означает "out", а 1 - "in". Источники / приемники данных типа control являются двунаправленными и бит MSB игнорируется.

На рис.1 показан типичный фрагмент спецификаций USB устройств, подключенных к системе.

Рис.1: Фрагмент данных из proc, описывающие устройства USB

Если конкретно, то в строках для устройства E:, изображаемых на рисунке, показаны примеры источника / приемника данных хост контроллера UHCI и двух источников / приемников данных типа bulk для рассматриваемого нами флэш-устройства. Кроме того показано, что номера источников / приемников данных равны (в шестнадцатеричном виде) 0x81, 0x01 и 0x82, соответственно; старшие биты (MSB) в первом и третьем случае равны 1, что указывает на источники / приемники типа "in" — на рисунке они указывается с помощью I, а во втором случае — источник / приемник данных вида "out", что указывается с помощью O. В битах MxPS указывается максимальный размер пакета, т. е. размер данных, которые могут быть переданы за один раз. Опять же, как предполагается, что для источника / приемника данных типа interrupt это будет 2 (<=8), и 64 — для источников / приемников данных типа bulk. В Ivl определяется интервал в миллисекундах, который должен быть между двумя последовательными передачами пакетов данных для правильной их передачи, и он более важен для источников / приемников данных типа interrupt.

Разбираемся с секцией устройства USB

Как мы только что мы выяснили при обсуждении строки для устройства E:, сейчас потребуется также разобраться с полями в других строках. Если кратко, то в этих строчках в секции устройства USB приведено полное описание устройства, соответствующее спецификациям USB, которые обсуждались в предыдущей статье.

Вернемся обратно к рис.1. Первой буквой в первой строке в секции каждого устройства будет буква T, указывающая положение устройства в дереве USB, однозначно определяемое тройкой <usb bus number, usb tree level, usb port> (<номер шины usb, уровень в дереве usb, порт usb>). Символ D представляет собой дескриптор устройства, в котором указывается, как минимум, версия устройства, класс/ категория устройства, а также количество конфигураций для этого устройства.

Здесь будет столько строк C, сколько есть конфигураций, хотя, как правило, используется одна конфигурация. В конфигурационном дескрипторе C указывается индекс конфигурации, атрибуты устройства для данной конфигурации, максимальная мощность (на самом деле, ток), потребляемая устройством в данной конфигурации, и количество интерфейсов для данной конфигурации.

В зависимости от этого, будет определяться количество строк I. Их будет больше в случае, если есть альтернативные варианты интерфейсов, т. е. с тем же самым номером интерфейса, но с различными свойствами - типичный сценарий для веб-камер.

I представляет собой дескриптор интерфейса, в котором указывается индекс интерфейса, альтернативный номер, функциональный класс/категория этого интерфейса, драйвер, связанный с этим интерфейсом, и номер источника / приемника данных, используемого с этим интерфейсом.

Класс интерфейса может совпадать с классом устройства, либо может от него отличаться. И, как об этом уже говорилось ранее, строк E, в зависимости от числа источников / приемников данных, конечных точек, может быть много.

Символ *, расположенный после C и I, указывает на используемую в настоящий момент конфигурацию и интерфейс, соответственно. В строке P указываются идентификационный код поставщика (vendor ID), идентификационный код изделия (product ID) и версия изделия. Строки S являются текстовыми дескрипторами, в которых приводится описание устройства, предоставляемое некоторыми конкретными производителями устройств.

"Было бы хорошо заглянуть в директорий /proc/bus/usb/devices с тем, чтобы выяснить, было ли обнаружено устройство или нет, и, возможно, получить первые краткие сведения об устройстве. Но, скорее всего, эти сведения также будут нужны для того, чтобы написать драйвер устройства. Итак, есть ли способ получить к этим сведениям из кода на языке C?" - спросила Светлана.

"Да, конечно; это как раз то, о чем я собираюсь рассказать тебе дальше. Помнишь ли ты, что как только устройство USB подключается к системе, драйвер хост контроллера USB заносит информацию об этом устройстве в общий слой ядра USB? Чтобы быть точным, он помещает эту информацию в набор структур, входящих одна в другую точно также как это определено в спецификациях USB" — ответил Пагс.

Ниже показаны конкретные структуры данных, которые определены в <linux/usb.h>; для лучшего понимания они приведены здесь в обратном порядке:

struct usb_device
{
    …
    struct usb_device_descriptor descriptor;
    struct usb_host_config *config, *actconfig;
    …
};
struct usb_host_config
{
    struct usb_config_descriptor desc;
    …
    struct usb_interface *interface[USB_MAXINTERFACES];
    …
};
struct usb_interface
{
    struct usb_host_interface *altsetting /* array */, *cur_altsetting;
    …
};
struct usb_host_interface
{
    struct usb_interface_descriptor desc;
    struct usb_host_endpoint *endpoint /* array */;
    …
};
struct usb_host_endpoint
{
    struct usb_endpoint_descriptor desc;
    …
};

Итак, при доступе к дескриптору struct usb_device для конкретного устройства, можно получить всю информация о конкретном устройстве USB, точно такую, какая есть в /proc. Но как получить дескриптор устройства?

Поскольку драйверы USB пишутся для интерфейсов устройств, а не для всего устройства в целом, дескриптор устройства, на самом деле, не находится непосредственно в драйвере; вместо этого для каждого интерфейса есть дескриптор интерфейса (указывающий на структуру struct usb_interface).

Напомним, что в обратных вызовах probe и disconnect, которые выполняются ядром USB для каждого интерфейса зарегистрированного устройства, в качестве первого параметра указывается дескриптор соответствующего интерфейса. Ниже приведены прототипы:

int (*probe)(struct usb_interface *interface, const struct usb_device_id *id);
void (*disconnect)(struct usb_interface *interface);

Итак, если есть указатель на интерфейс, то можно получить доступ ко всей информации о соответствующем интерфейсе, а для получения дескриптора устройства можно воспользоваться следующим макросом:

struct usb_device device = interface_to_usbdev(interface);

Добавляем полученные знания в наш драйвер, который раньше мог выполнять только регистрацию устройства, и получаем следующий листинг с кодом (pen_info.c):

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
 
static struct usb_device *device;
 
static int pen_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint;
    int i;
 
    iface_desc = interface->cur_altsetting;
    printk(KERN_INFO "Pen i/f %d now probed: (%04X:%04X)\n",
            iface_desc->desc.bInterfaceNumber, id->idVendor, id->idProduct);
    printk(KERN_INFO "ID->bNumEndpoints: %02X\n",
            iface_desc->desc.bNumEndpoints);
    printk(KERN_INFO "ID->bInterfaceClass: %02X\n",
            iface_desc->desc.bInterfaceClass);
 
    for (i = 0; i < iface_desc->desc.bNumEndpoints; i++)
    {
        endpoint = &iface_desc->endpoint[i].desc;
 
        printk(KERN_INFO "ED[%d]->bEndpointAddress: 0x%02X\n",
                i, endpoint->bEndpointAddress);
        printk(KERN_INFO "ED[%d]->bmAttributes: 0x%02X\n",
                i, endpoint->bmAttributes);
        printk(KERN_INFO "ED[%d]->wMaxPacketSize: 0x%04X (%d)\n",
                i, endpoint->wMaxPacketSize, endpoint->wMaxPacketSize);
    }
 
    device = interface_to_usbdev(interface);
    return 0;
}
 
static void pen_disconnect(struct usb_interface *interface)
{
    printk(KERN_INFO "Pen i/f %d now disconnected\n",
            interface->cur_altsetting->desc.bInterfaceNumber);
}
 
static struct usb_device_id pen_table[] =
{
    { USB_DEVICE(0x058F, 0x6387) },
    {} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, pen_table);
 
static struct usb_driver pen_driver =
{
    .name = "pen_driver",
    .probe = pen_probe,
    .disconnect = pen_disconnect,
    .id_table = pen_table,
};
 
static int __init pen_init(void)
{
    return usb_register(&pen_driver);
}
 
static void __exit pen_exit(void)
{
    usb_deregister(&pen_driver);
}
 
module_init(pen_init);
module_exit(pen_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("USB Pen Info Driver");

Затем, вместе со специальными операциями с флеш-устройством, можем повторить обычные операции, которые выполняются для всех драйверов Linux:

  • Собираем драйвер (файл .ko) с помощью запуска команды make.
  • Загружаем драйвер с помощью команды insmod pen_info.ko
  • Подключаем флейш-устройство (после того, как мы увидели, что драйвер usb-storage еще не загружен)
  • Отсоединяем флеш-устройство.
  • Проверяем журналы с помощью команды dmesg
  • Выгружаем драйвер с помощью команды rmmod pen_info.

На рис.2 показана часть описанных выше шагов, которые были выполнены в системе Пагса. Не забудьте проверить (посмотрите вывод команды cat /proc/bus/usb/devices), что к интерфейсу флеш-устройства подключен не обычный драйвер usb-storage, а драйвер pen_info.

Рис.2: Вывод данных с помощью dmesg

Подведем итог

Прежде, чем сделать еще один перерыв, Пагс воспользовался двумя из множества способов, с помощью которых драйвер сообщает в ядро USB через таблицу struct usb_device_id о том, какое устройство используется. При использовании первого способа с помощью макроса USB_DEVICE() точно так, как это делалось выше, указывается пара <vendor id, product id> (<идентификатор поставщика, идентификатор изделия>). При использовании второго способа с помощью макроса USB_DEVICE_INFO() указывается класс/категория устройства. На самом деле, в заголовочном файле <linux/usb.h> есть много других макросов, которые можно использовать в различных сочетаниях. Более того, большое число этих макросов можно указывать в таблице usb_device_id (которая завершается записью null) и указывать критерий выбора, что позволяет написать один драйвер для максимально возможного количества устройств.

"Ранее ты также упоминал о том, что для одного устройства пишутся несколько драйверов. А как, в общем случае, мы при регистрации интерфейсов устройства USB выбираем, какой интерфейс регистрировать, а какой - нет?" - спросила Светлана. "Разумеется, это тема дальнейшего обсуждения нашей последней темы для драйверов любого устройства - механизмов передачи данных" — ответил Пагс.


К предыдущей статье Оглавление К следующей статье