Написание USB драйвера

Предыдущая  Содержание  Следующая V*D*V

Подход к написанию драйвера USB устройства аналогичен драйверу PCI: драйвер регистрирует свой объект драйвера с USB подсистемой  и затем использует идентификаторы поставщика и устройства для сообщения, когда его оборудование было установлено.

Какие устройства поддерживает драйвер?

Структура struct usb_device_id содержит список различных типов USB устройств, которые поддерживает этот драйвер. Этот список используется ядром USB, чтобы решить, какой драйвер предоставить устройству, или скриптами горячего подключения, чтобы решить, какой драйвер автоматически загрузить, когда устройство подключается к системе.

 

Структура struct usb_device_id определена со следующими полями:

 

__u16 match_flags

Определяет, какие из следующих полей в структуре устройства должны сопоставляться. Это битовое поле определяется разными значениями USB_DEVICE_ID_MATCH_*, указанными в файле include/linux/mod_devicetable.h. Это поле, как правило, никогда не устанавливается напрямую, а инициализируется с помощью макросов типа USB_DEVICE, описываемых ниже.

 

__u16 idVendor

Идентификатор поставщика USB для устройства. Этот номер присваивается форумом USB для своих членов и не может быть присвоен кем-то еще.

 

__u16 idProduct

Идентификатор продукта USB для устройства. Все поставщики, которые имеют выданный им идентификатор поставщика, могут управлять своими идентификаторами продукта, как они предпочитают.

 

__u16 bcdDevice_lo

__u16 bcdDevice_hi

Определяют нижнюю и верхнюю границу диапазона назначаемого поставщиком номера версии продукта. Значения bcdDevice_hi является включительным; его значение является значением наибольшего номера устройства. Обе эти величины представлены в двоично-десятичной (BCD) форме. Эти переменные в сочетании с idVendor и idProduct используются для определения данного варианта устройства.

 

__u8 bDeviceClass

__u8 bDeviceSubClass

__u8 bDeviceProtocol

Определяют класс, подкласс и протокол устройства, соответственно. Эти номера присваиваются форумом USB и определены в спецификации USB. Эти значения определяют поведение для всего устройства, в том числе все интерфейсы на этом устройстве.

 

__u8 bInterfaceClass

__u8 bInterfaceSubClass

__u8 bInterfaceProtocol

Подобно зависимым от устройства вышеприведённым величинам, эти определяют класса, подкласс и протокол отдельного интерфейса, соответственно. Эти номера присваиваются форумом USB и определены в спецификации USB.

 

kernel_ulong_t driver_info

Это значение не используется для сравнения, но оно содержит информацию о том, что драйвер может использовать, чтобы отличать разные устройства друг от друга в функции обратного вызова probe драйвера USB.

 

Как и с PCI устройствами, существует ряд макросов, которые используются для инициализации этой структуры:

 

USB_DEVICE(vendor, product)

Создаёт struct usb_device_id, которая может быть использована только для соответствия указанными значениям идентификаторов поставщика и продукта. Она очень часто используется для устройств USB, которым необходим специальный драйвер.

 

USB_DEVICE_VER(vendor, product, lo, hi)

Создаёт struct usb_device_id, которая может быть использована только для соответствия указанным значениям идентификаторов поставщика и продукта внутри диапазона версий.

 

USB_DEVICE_INFO(class, subclass, protocol)

Создаёт struct usb_device_id, которая может быть использованы для соответствия определённому классу USB устройств.

 

USB_INTERFACE_INFO(class, subclass, protocol)

Создаёт struct usb_device_id, которая может быть использована для соответствия определённому классу USB интерфейсов.

 

Итак, для простого драйвера USB устройства, который управляет только одним USB устройством от одного поставщика, таблица struct usb_device_id будет определяться как:

 

/* таблица устройств, которые работают с этим драйвером */

static struct usb_device_id skel_table [ ] = {

    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

    { }        /* Завершающая запись */

};

MODULE_DEVICE_TABLE (usb, skel_table);

 

Как и с драйвером PCI, необходим макрос MODULE_DEVICE_TABLE, чтобы разрешить инструментам пространства пользователя выяснить, какими устройствами может управлять этот драйвер. Но для USB драйверов первым значением в этом макросе должна быть строка usb.

Регистрация USB драйвера

Основной структурой, которую должны создать все USB драйверы, является struct usb_driver. Эта структура должна быть заполнена драйвером USB и состоит из ряда функций обратного вызова и переменных, описывающих USB драйвер для кода USB ядра:

 

struct module *owner

Указатель на модуль владельца этого драйвера. Ядро USB использует его для правильного подсчёта ссылок на этот драйвер USB, чтобы он не выгружался в несвоевременные моменты. Переменной должен быть присвоен макрос THIS_MODULE.

 

const char *name

Указатель на имя драйвера. Он должен быть уникальным среди всех USB драйверов в ядре и, как правило, установлен на такое же имя, что и имя модуля драйвера. Оно проявляется в sysfs в /sys/bus/usb/drivers/, когда драйвер находится в ядре.

 

const struct usb_device_id *id_table

Указатель на таблицу struct usb_device_id, которая содержит список всех различных видов устройств USB, которые драйвер может распознать. Если эта переменная не установлена, функция обратного вызова probe в драйвере USB никогда не вызывается. Если вы хотите, чтобы ваш драйвер всегда вызывался для каждого USB устройства в системе, создайте запись, которая устанавливает только поле driver_info:

 

static struct usb_device_id usb_ids[ ] = {

    {.driver_info = 42},

    { }

};

 

int (*probe) (struct usb_interface *intf, const struct usb_device_id *id)

Указатель на зондирующую функцию в USB драйвере. Эта функция (описанная в разделе "probe и disconnect в деталях") вызывается USB ядром, когда оно думает, что оно имеет структуру usb_interface, которую этот драйвер может обработать. Указатель на struct usb_device_id, который использовало USB ядро, чтобы принять это решение также передается в эту функцию. Если USB драйвер признаёт переданную ему структуру usb_interface, он должен правильно проинициализировать устройство и вернуть 0. Если драйвер не хочет признавать устройство или произошла ошибка, он должен вернуть отрицательное значение ошибки.

 

void (*disconnect) (struct usb_interface *intf)

Указатель на функцию отключения в USB драйвере. Эта функция (описанная в разделе "probe и disconnect в деталях") вызывается USB ядром, когда структура usb_interface была удалена из системы, или когда драйвер выгружается из ядра USB.

 

Таким образом, чтобы создать значимую структуру struct usb_driver, должны быть проинициализированы только пять полей:

 

static struct usb_driver skel_driver = {

    .owner = THIS_MODULE,

    .name = "skeleton",

    .id_table = skel_table,

    .probe = skel_probe,

    .disconnect = skel_disconnect,

};

 

struct usb_driver содержит несколько больше обратных вызовов, которые, как правило, очень часто не используются, и не требуются для правильной работы USB драйвера:

 

int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf)

Указатель на функцию ioctl в USB драйвере. Если он присутствует, то вызывается, когда программа пользовательского пространства делает вызов ioctl для записи файловой системы устройств usbfs, связанной с устройством USB, относящемуся к этому USB драйверу. На практике только драйвер USB концентратора использует этот ioctl, так как любому другому USB драйверу нет иной реальной необходимости его использовать.

 

int (*suspend) (struct usb_interface *intf, u32 state)

Указатель на функцию приостановки в USB драйвере. Она вызывается, когда работа устройства должна быть приостановлена USB ядром.

 

int (*resume) (struct usb_interface *intf)

Указатель на функцию возобновления в USB драйвере. Она вызывается, когда работа устройства возобновляется USB ядром.

 

Чтобы зарегистрировать struct usb_driver в USB ядре, выполняется вызов usb_register_driver с указателем на struct usb_driver. Для USB драйвера это традиционно делается  в коде инициализации модуле:

 

static int __init usb_skel_init(void)

{

    int result;

 

    /* регистрируем этот драйвер в подсистеме USB */

    result = usb_register(&skel_driver);

    if (result)

        err("usb_register failed. Error number %d", result);

 

    return result;

}

 

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

 

static void __exit usb_skel_exit(void)

{

    /* отменяем регистрацию этого драйвера в подсистеме USB */

    usb_deregister(&skel_driver);

}

probe и disconnect в деталях

В структуре struct usb_driver structure, описанной в предыдущем разделе, драйвер указывает две функции, которые в соответствующее время вызывает ядро USB. Функция probe вызывается, когда установлено устройство, которым, как думает ядро USB, должен управлять этот драйвер; функция probe должна выполнять проверки информации, переданной ей об устройстве, и решать, действительно ли этот драйвер подходит для этого устройства. Функция disconnect вызывается, когда по каким-то причинам драйвер не должен больше управлять устройством и может делать очистку.

 

Оба функции обратного вызова probe и disconnect вызываются в контексте потока USB узла ядра, так что засыпать в них допускается. Тем не менее, рекомендуется, чтобы большая часть работы выполнялась, когда устройство открыто пользователем, если это возможно, чтобы сократить время зондирования USB к минимуму. Такое требование появляется потому, что USB ядро обрабатывает добавление и удаление устройств USB в одном потоке, так что любой медленный драйвер устройства может привести к замедлению обнаружения USB устройства и это станет заметно для пользователя.

 

В функции обратного вызова probe, USB драйвер должен проинициализировать любые локальные структуры, которые он может использовать для управления USB устройством. Следует также сохранить в локальные структуры любую необходимую информацию об устройстве, так как это обычно легче сделать в данное время. Например, USB драйверы обычно хотят обнаружить адрес оконечной точки и размеры буферов для данного устройства, так как они необходимы для общения с устройством. Вот пример некоторого кода, который определяет две оконечные точки ВХОДА и ВЫХОДА поточного типа и сохраняет некоторую информацию о них в локальной структуре устройства:

 

/* установить информацию оконечной точки */

/* используем только первые поточные точки входа и выхода */

iface_desc = interface->cur_altsetting;

for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {

    endpoint = &iface_desc->endpoint[i].desc;

 

    if (!dev->bulk_in_endpointAddr &&

        (endpoint->bEndpointAddress & USB_DIR_IN) &&

        ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

        == USB_ENDPOINT_XFER_BULK)) {

        /* мы нашли оконечную точку входного потока */

        buffer_size = endpoint->wMaxPacketSize;

        dev->bulk_in_size = buffer_size;

        dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

        dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

        if (!dev->bulk_in_buffer) {

            err("Could not allocate bulk_in_buffer");

            goto error;

        }

    }

 

    if (!dev->bulk_out_endpointAddr &&

        !(endpoint->bEndpointAddress & USB_DIR_IN) &&

        ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

        == USB_ENDPOINT_XFER_BULK)) {

        /* мы нашли оконечную точку выходного потока */

        dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;

    }

}

if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {

    err("Could not find both bulk-in and bulk-out endpoints");

    goto error;

}

 

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

 

for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {

    endpoint = &iface_desc->endpoint[i].desc;

 

Затем, после того, как мы получили оконечную точку, и если мы уже не нашли ВХОДНУЮ оконечную точку поточного типа, мы проверяем, является ли направление этой оконечной точки ВХОДНЫМ. Это может быть проверено просмотром, содержится ли битовая маска USB_DIR_IN в переменной bEndpointAddress оконечной точки. Если это так, мы определяем, имеет ли оконечная точки тип поточной или нет, сначала накладывая битовую маску USB_ENDPOINT_XFERTYPE_MASK на переменную bmAttributes, а затем проверяя, совпадает  ли она со значением USB_ENDPOINT_XFER_BULK:

 

if (!dev->bulk_in_endpointAddr &&

    (endpoint->bEndpointAddress & USB_DIR_IN) &&

    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

    == USB_ENDPOINT_XFER_BULK)) {

 

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

 

/* мы нашли оконечную точку входного потока */

buffer_size = endpoint->wMaxPacketSize;

dev->bulk_in_size = buffer_size;

dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

if (!dev->bulk_in_buffer) {

    err("Could not allocate bulk_in_buffer");

    goto error;

}

 

Поскольку драйверу USB позднее в жизненном цикле устройства необходимо получать локальные структуры данных, связанные с этой struct usb_interface, может быть вызвана функция usb_set_intfdata:

 

/* сохраняем наш указатель на данные в этом интерфейсе устройства */

usb_set_intfdata(interface, dev);

 

Эта функция принимает указатель на любой тип данных и сохраняет его в структуре struct usb_interface для последующего доступа. Для получения данных должна быть вызвана функция usb_get_intfdata:

 

struct usb_skel *dev;

struct usb_interface *interface;

int subminor;

int retval = 0;

 

subminor = iminor(inode);

 

interface = usb_find_interface(&skel_driver, subminor);

if (!interface) {

    err ("%s - error, can't find device for minor %d",

            __FUNCTION__, subminor);

    retval = -ENODEV;

    goto exit;

}

 

dev = usb_get_intfdata(interface);

if (!dev) {

    retval = -ENODEV;

    goto exit;

}

 

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

 

Если USB драйвер не связан с другим типом подсистемы, которая обрабатывает взаимодействие пользователя с устройством (такой, как ввод, терминал, видео и так далее), драйвер может использовать старший номер USB, чтобы использовать традиционный интерфейс символьного драйвера с пользовательским пространством. Чтобы сделать это, драйвер USB должен вызвать функцию usb_register_dev в функции probe, когда он хочет зарегистрировать устройство в USB ядре. Убедитесь, что устройство и драйвер находятся в надлежащем состоянии, чтобы выполнить желание пользователя получить доступ к устройству, как только вызвана эта функция.

 

/* мы можем зарегистрировать это устройство сейчас, так как оно готово */

retval = usb_register_dev(interface, &skel_class);

if (retval) {

    /* что-то помешало зарегистрировать этот драйвер */

    err("Not able to get a minor for this device.");

    usb_set_intfdata(interface, NULL);

    goto error;

}

 

Функция usb_register_dev требует указатель на struct usb_interface и указатель на struct usb_class_driver. struct usb_class_driver используется для определения ряда различных параметров, о которых драйвер USB желает, чтобы их знало USB ядро при регистрации на младший номер. Эта структура состоит из следующих переменных:

 

char *name

Имя, которое использует sysfs для описания устройства. Головное имя пути, если присутствует, используется только в devfs и в этой книге не рассматривается. Если ряду устройств необходимо быть в этом имени, в строке имени должны быть символы %d. Например, чтобы создать в devfs имя usb/foo1 и в sysfs имя класса foo1, строка имени должна быть установлена как usb/foo%d.

 

struct file_operations *fops;

Указатель на struct file_operations, которую этот драйвер определил, чтобы использовать для регистрации в качестве символьного устройства. Смотрите Главу 3 для получения дополнительной информации об этой структуре.

 

mode_t mode;

Режим для файла devfs, который будет создан для этого драйвера; иначе неиспользуемый. Типичный установкой для этой переменной будет значение S_IRUSR в сочетании со значением S_IWUSR, которыми владелец файла устройства предоставит доступ только для чтения и записи.

 

int minor_base;

Это начало установленного младшего диапазона для этого драйвера. Все устройства, связанные с этим драйвером, создаются с уникальными, увеличивающимися младшими номерам, начиная с этого значения. Если в ядре была включена опция конфигурации CONFIG_USB_DYNAMIC_MINORS, в любой момент допускается только 16 устройств, связанных с этим драйвером. Если это так, эта  переменная игнорируется и все младшие номера для этого устройства распределяются по принципу "первый пришёл, первым обслужен". Рекомендуется, чтобы системы, которые имеют эту опцию разрешённой, использовали такие программы, как udev для управления узлами устройств в системе, так как статическое дерево /dev не будет работать должным образом.

 

После отключения USB устройства, все ресурсы, связанные с устройством должны быть очищены, если это возможно. В это время, если в функции probe для выделения младшего номера для этого USB устройства была вызвана usb_register_dev, должна быть вызвана функция usb_deregister_dev, чтобы вернуть USB ядру младший номер обратно.

 

В функции disconnect также важно извлечь из этого интерфейса все данные, которые была ранее установлены вызовом usb_set_intfdata. Затем установить указатель на данные в структуре struct usb_interface в NULL, чтобы предотвратить дальнейшие ошибки при доступе к данным ненадлежащим образом:

 

static void skel_disconnect(struct usb_interface *interface)

{

    struct usb_skel *dev;

    int minor = interface->minor;

 

    /* предохраняем skel_open( ) от гонки со skel_disconnect( ) */

    lock_kernel( );

 

    dev = usb_get_intfdata(interface);

    usb_set_intfdata(interface, NULL);

 

    /* возвращаем наш младший номер */

    usb_deregister_dev(interface, &skel_class);

 

    unlock_kernel( );

 

    /* уменьшаем наш счётчик использования */

    kref_put(&dev->kref, skel_delete);

 

    info("USB Skeleton #%d now disconnected", minor);

}

 

Обратите внимание на вызов lock_kernel в предыдущем фрагменте кода. Он получает большую блокировку ядра, чтобы обратный вызов disconnect не находился в состоянии гонки с вызовом open при попытке получить указатель на правильную структуру данных интерфейса. Поскольку open вызывается с полученной большой блокировкой ядра, если disconnect также получает эту блокировку, только одна часть драйвера может получить доступ, и затем установить указатель данных интерфейса.

 

Перед вызовом для устройства USB функции disconnect все urb-ы, которые в настоящее время находятся в процессе передачи для устройства, будут отменены ядром USB, поэтому драйвер не должен явно вызывать usb_kill_urb для этих urb-ов. Если драйвер пытается отправить urb в USB устройство после того, как оно было отключено вызовом usb_submit_urb, отправка завершится неудачно с ошибочным значением -EPIPE.

Отправка и управление Urb

Когда драйвер имеет данные для передачи в USB устройство (как обычно бывает в функции записи драйвера), для передачи данных на устройство должен быть создан urb:

 

urb = usb_alloc_urb(0, GFP_KERNEL);

if (!urb) {

    retval = -ENOMEM;

    goto error;

}

 

После успешного создания urb-а, для отправки данных в устройство наиболее эффективным образом также должен быть создан буфер DMA и данные, которые переданы в драйвер, должны быть скопированы в этот буфер:

 

buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);

if (!buf) {

    retval = -ENOMEM;

    goto error;

}

if (copy_from_user(buf, user_buffer, count)) {

    retval = -EFAULT;

    goto error;

}

 

После того как данные должным образом скопированы из пространства пользователя в локальный буфер, urb должен быть правильно проинициализирован, прежде чем он может быть отправлен в ядро USB:

 

/* проинициализируем urb надлежащим образом */

usb_fill_bulk_urb(urb, dev->udev,

            usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),

            buf, count, skel_write_bulk_callback, dev);

urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

 

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

 

/* отправляем данные из поточного порта */

retval = usb_submit_urb(urb, GFP_KERNEL);

if (retval) {

    err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);

    goto error;

}

 

После того, как urb успешно передан в USB устройство (или что-то произошло при передаче), USB ядром выполняется обратный вызов urb. В нашем примере мы проинициализировали urb для указания на функцию skel_write_bulk_callback и это та самая функция, которая вызывается:

 

static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)

{

    /* сообщения об синхронные/асинхронные разъединениях не являются ошибками */

    if (urb->status &&

        !(urb->status == -ENOENT ||

        urb->status == -ECONNRESET ||

        urb->status == -ESHUTDOWN)) {

        dbg("%s - nonzero write bulk status received: %d",

                __FUNCTION__, urb->status);

    }

 

    /* освобождаем наш выделенный буфер */

    usb_buffer_free(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma);

}

 

Первое вещью, которую делает функция обратного вызова, является проверка состояния urb-а для определения, завершён ли этот urb успешно или нет. Ошибочные значения, -ENOENT, -ECONNRESET и -ESHUTDOWN являются не реальными ошибками передачи, а просто  сообщают об условиях, сопровождающих успешную передачу. (Смотрите список возможных ошибок для urb-ов, подробно изложенный в разделе "struct urb".) Затем обратный вызов освобождает выделенный буфер, который был выделен для передачи этого urb-а.

 

Для другого urb-а характерно быть отправленным в устройство во время выполнения функции обратного вызова urb-а. Это полезно, когда в устройство передаются потоковые данные. Помните, что обратный вызов urb-а работает в контексте прерывания, поэтому он не должен выполнять любые выделения памяти, удерживать какие-либо семафоры, или не делать чего-то другого, что может привести процесс к засыпанию. При отправке urb-а изнутри обратного вызова используйте флаг GFP_ATOMIC, чтобы приказать USB ядру не засыпать, если необходимо выделять новые куски памяти во время процесса отправки.

Предыдущая  Содержание  Следующая