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

UnixForum





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

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

Часть 13: Передача данных в устройство и из устройства USB

Оригинал: "Device Drivers, Part 13: Data Transfer to and from USB Devices"
Автор: Anil Kumar Pugalia
Дата публикации: December 29, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

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

Пагс продолжил: "Чтобы ответить на твой вопрос о том, как драйвер выборочно регистрирует или пропускает определенный интерфейс устройства USB, ты должна разобраться, насколько важно значение, возвращаемое обратным вызовом probe()". Обратите внимание, что ядро USB должно обращаться к probe для всех интерфейсов обнаруженного устройства, за исключением тех, которые уже зарегистрированы - таким образом, когда это делается в первый раз, это будет происходить для всех интерфейсов. Итак, если probe возвращает 0, то это значит, что драйвер зарегистрирован для этого интерфейса. Возвращение кода ошибки означает, что для него регистрация не выполнена. Вот и все. "Это просто" - прокомментировала Светлана.

"Теперь, давайте поговорим о последней задаче - передаче данных в устройство USB и из этого устройства" — продолжил Пагс.

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

"Все тривиально. Этот макрос, главным образом, для depmod, работающем в пользовательском пространстве" - сказал он. "Модуль" это еще один термин, обозначающий драйвер, который можно динамически загружать/выгружать. Макрос MODULE_DEVICE_TABLE создает две переменные в секции модуля, доступной только на чтение, которые с помощью depmod выделяются и переносятся в /lib/modules/<kernel_version> в глобальные файлы, используемые для отображения устройств. Двумя такими файлами будут файлы драйверов для USB и PCI - modules.usbmap и modules.pcimap, соответственно. В результате, как мы видели на примере с автозагрузкой драйвера usb-storage, появляется возможность автоматически выполнять загрузку этих драйверов.

Передача данных через USB

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

USB, будучи аппаратным протоколом, формирует в пространстве ядра обычный горизонтальный слой. И, следовательно, чтобы предоставить интерфейс пользовательского пространства, к нему следует подключаться через один из вертикальных слоев. Поскольку символьная вертикаль (драйвер) уже рассматривалась, то для простоты рассмотрения всего потока передачи данных именно ее предпочтительнее выбрать для соединения с горизонталью USB.

Также нам не нужно искать свободный старший символьный номер, а можно воспользоваться старшим символьным номером 180, зарезервированным для файлов символьных устройств, использующих USB. Кроме того, для объединения воедино всей логики символьного драйвера с горизонталью USB, предоставляются следующие интерфейсы, объявленные в заголовочном файле <linux/usb.h>:

int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);
void usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);

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

Первый параметр в указанных выше функциях является указателем на интерфейс, получаемый в качестве первого параметра в обеих функциях probe и disconnect. Во второй параметр, struct usb_class_driver, нужно прежде, чем вызывать функцию usb_register_dev, занести имя файла устройства и набор операций, используемых с файлом устройства. Пример использования смотрите в тексте функций pen_probe и pen_disconnect в листинге кода pen_driver.c, приведенного ниже.

Кроме того, поскольку теперь предоставляются операции для работы с файлами (запись, чтение и т. д.), это именно то, что нам нужно при передаче данных в устройство USB и из него. Так что в функциях pen_write и pen_ read, которые показаны ниже, показывается, как можно сделать вызов функции usb_bulk_msg() (прототип в <linux/usb.h<) для того, чтобы выполнить обмен данными соотвественно с источниками / приемниками данных 0×01 и 0×82 для памяти флеш-устройства. Чтобы определить номера источников / приемников данных для нашего флеш-устройства, взгляните на строки 'Е' в средней части рис.1.

Рис.1: Спецификации USB флеш-устройства

Обратитесь к заголовочному файлу <linux/usb.h> в исходном коде ядра за полным списком прототипов API ядра USB для других функций передачи данных, в которых используются конкретные источники / приемники данных, например, usb_control_msg(), usb_interrupt_msg() и т.п.. Макросы usb_rcvbulkpipe(), usb_sndbulkpipe(), а также многие другие, определенные также в <linux/usb.h>, вычисляют битовую маску фактического источника / приемника данных для различных API ядра USB.

Обратите внимание, что флэш-накопитель относится к классу устройств USB массового хранения данных, для работы с которыми предполагается использование команд, похожих на команды SCSI, которые предназначены для передачи данных в источник / приемник данных массовой памяти (bulk endpoints). Поэтому в случае, если данные не отформатированы должным образом, команды read/write, который показаны ниже в листинге кода, могут, в действительности, не передавать данные так, как это от них ожидается. Но все же, в этом коде собран весь драйвер USB. Чтобы получить представление о реальной передаче данных через USB простым и элегантным способом, может потребоваться воспользоваться некоторым специально настроенным устройством USB, похожим на то, что показано здесь.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
 
#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
#define BULK_EP_OUT 0x01
#define BULK_EP_IN 0x82
#define MAX_PKT_SIZE 512
 
static struct usb_device *device;
static struct usb_class_driver class;
static unsigned char bulk_buf[MAX_PKT_SIZE];
 
static int pen_open(struct inode *i, struct file *f)
{
    return 0;
}
static int pen_close(struct inode *i, struct file *f)
{
    return 0;
}
static ssize_t pen_read(struct file *f, char __user *buf, size_t cnt, loff_t *off)
{
    int retval;
    int read_cnt;
 
    /* Read the data from the bulk endpoint */
    retval = usb_bulk_msg(device, usb_rcvbulkpipe(device, BULK_EP_IN),
            bulk_buf, MAX_PKT_SIZE, &read_cnt, 5000);
    if (retval)
    {
        printk(KERN_ERR "Bulk message returned %d\n", retval);
        return retval;
    }
    if (copy_to_user(buf, bulk_buf, MIN(cnt, read_cnt)))
    {
        return -EFAULT;
    }
 
    return MIN(cnt, read_cnt);
}
static ssize_t pen_write(struct file *f, const char __user *buf, size_t cnt, loff_t *off)
{
    int retval;
    int wrote_cnt = MIN(cnt, MAX_PKT_SIZE);
 
    if (copy_from_user(bulk_buf, buf, MIN(cnt, MAX_PKT_SIZE)))
    {
        return -EFAULT;
    }
 
    /* Write the data into the bulk endpoint */
    retval = usb_bulk_msg(device, usb_sndbulkpipe(device, BULK_EP_OUT),
            bulk_buf, MIN(cnt, MAX_PKT_SIZE), &wrote_cnt, 5000);
    if (retval)
    {
        printk(KERN_ERR "Bulk message returned %d\n", retval);
        return retval;
    }
 
    return wrote_cnt;
}
 
static struct file_operations fops =
{
    .open = pen_open,
    .release = pen_close,
    .read = pen_read,
    .write = pen_write,
};
 
static int pen_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    int retval;
 
    device = interface_to_usbdev(interface);
 
    class.name = "usb/pen%d";
    class.fops = &fops;
    if ((retval = usb_register_dev(interface, &class)) < 0)
    {
        /* Something prevented us from registering this driver */
        err("Not able to get a minor for this device.");
    }
    else
    {
        printk(KERN_INFO "Minor obtained: %d\n", interface->minor);
    }
 
    return retval;
}
 
static void pen_disconnect(struct usb_interface *interface)
{
    usb_deregister_dev(interface, &class);
}
 
/* Table of devices that work with this driver */
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)
{
    int result;
 
    /* Register this driver with the USB subsystem */
    if ((result = usb_register(&pen_driver)))
    {
        err("usb_register failed. Error number %d", result);
    }
    return result;
}
 
static void __exit pen_exit(void)
{
    /* Deregister this driver with the USB subsystem */
    usb_deregister(&pen_driver);
}
 
module_init(pen_init);
module_exit(pen_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("USB Pen Device Driver");

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

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

Тем временем Пагс для живой демонстрации передачи данных через USB подключил к своей системе единственное в своем роде творение - Комплект для создания драйверов в Linux (LDDK- Linux device driver kit).

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


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