четверг, 19 августа 2010 г.

USB драйвер в Linux

Введение

Драйвер устройства - это некий код, который позволяет операционной системе взаимодействовать с устройством. Ядро Linux предоставляет API для работы с физическими интерфейсами (для USB это подсистема ядра USB Core). Этот слой можно назвать транспортным уровнем. Прикладной уровень протоколов (например HID для работы с USB мышью) реализуется драйверами устройств.
В ОС Linux драйвера устройств выполняются в виде модулей ядра. Модулем ядра называют часть кода ядра, который может динамически загружаться и выгружаться во время работы ядра.

Пишем модуль

Примерный код драйвера для usb находится в файле drivers/usb/usb-skeleton.c дерева исходного кода ядра.

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

Для указания точки входа модуля ядра, используется макрос
module_init(my_driver_init);
Прототип функции инициализации модуля -
int __init my_driver_init(void);
В функции выполняется инициализация структур, регистрация устройств и т.д.
Для регистрации драйвера в системе имеется структура usb_driver, в которой указывается имя модуля, параметры работы и указатели на функции обратного вызова драйвера.
Здесь как и во многих подсистемах ядра (например в виртуальной файловой системе), используется объектно ориентированный подход.
Для нашего модуля структура будет выглядеть так:
static struct usb_driver my_driver = {
    .name  = "my_driver",
    .probe  = my_driver_probe,
    .disconnect = my_driver_disconnect,
    .id_table = my_driver_id_table
};

Поле name - имя драйвера. Оно должно быть уникальным и обычно совпадает с именем модуля.
Поле probe - указатель на функцию, которую подсистема USB ядра вызывает при подключении устройства.
Поле disconnect - указатель на функцию, которую подсистема USB ядра вызывает при отключении устройства.
Поле id_table - указатель на структуру usb_device_id (см. ниже).

Функция
my_driver_init()
в нашем случае выглядит вот так:
static int __init my_driver_init(void)
{
    int result;

    printk("My driver init");

    result = usb_register(&my_driver);
    if (result)
        err("My driver register failed with error number %d", result);

    return result;
}

В структуре usb_device_id указываются идентификаторы производителя и устройства, для которых предназначен драйвер. При подключении устройства подсистема USB ядра считывает эти идентификатор с устройства и вызывает соответствующий драйвер.
В нашем случае определение структуры будет следующим:
#define VENDOR_ID  0xfff0
#define PRODUCT_ID  0xfff0

static const struct usb_device_id my_driver_id_table[] = {
    { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
    { }
};

В реальном драйвере вместо кода 0xfff0 должны стоять идентификаторы реального устройства, которые можно получить у производителя.

Функция probe и disconnect в нашем модуле будут выглядеть следующим образом:
static int my_driver_probe(struct usb_interface* interface, const struct usb_device_id* id)
{
    printk("My moudle probe\n");
}

static void my_driver_disconnect(struct usb_interface* interface)
{
    printk("My driver disconnect\n");
}

Для указания точки выхода используется макрос
module_exit(my_driver_exit);
Прототип функции выхода -
void __exit my_driver_exit(void)
В функции выполняется очистка ресурсов.
В нашем случае она выглядит так:
static void __exit my_driver_exit(void)
{
    printk("My driver exit");
    usb_deregister(&my_driver);
}

Этот минимальный код ничего кроме реакции на подключение устройства пока не делает. В дальнейшем я постараюсь описать процесс обмена данными с устройством.

Литература
1. Р. Лав. Разработка ядра Linux.
2. Linux device drivers 3rd edition.

IP адрес интерфейса

Некоторое время назад передо мной встала задача инициировать процедуру получения IP адрес для интерфейса по DHCP. Для это существует команда dhclient <интерфейс>. Нужно как то понимать, что dhclient отработал корректно и IP адрес получен. Можно парсить вывод программы на sdtout, а можно проверить, установлен ли IP адрес у интерфейса. С парсингом вывода всё понятно, поэтому я расскажу как получить IP адрес интерфейса.

Приведённый ниже код получает список сетевых интерфейсов, и выводит их IP адреса.
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>

#include <iostream>

int main (int argc, char* argv[])
{
    struct ifconf ifc;
    int numreqs = 30;

    int skfd = socket(AF_INET, SOCK_DGRAM, 0);

    //Получаем список интерфейсов
    ifc.ifc_buf = 0;
    for (;;)
    {
        ifc.ifc_len = sizeof(struct ifreq) * numreqs;
        ifc.ifc_buf = new char[ifc.ifc_len];

        if (ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
        {
            return false;
        }
        if (ifc.ifc_len == sizeof(struct ifreq) * numreqs)
        {
            delete[] ifc.ifc_buf;
            numreqs += 10;
            continue;
        }
        break;
    }

    //Проходимся по списку интерфейсов
    bool res = false;
    struct ifreq *ifr = ifc.ifc_req;
    for (int n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq))
    {
        char addr[16];
        sockaddr_in* sock = reinterpret_cast<sockaddr_in*>(&ifr->ifr_ifru.ifru_addr);
        inet_ntop(AF_INET, &sock->sin_addr, addr, 16);

        std::cout << ifr->ifr_name << " - " << addr << std::endl;

        ifr++;
    }

    return 0;
}