понедельник, 16 мая 2011 г.

14.6. Собираем всё вместе

Собираем всё вместе
V*D*V
Чтобы лучше понять, что делает драйверная модель, давайте пройдём по шагам жизненный цикл устройства в ядре. Мы опишем, как с драйверной моделью взаимодействует подсистема PCI, основные понятия добавления и удаления драйвера, и как добавляются и удаляются из системы устройства. Эти сведения, а при описании PCI кода ядра в частности, распространяются на все другие подсистемы, которые используют драйверное ядро для управления своими драйверами и устройствами.

Как показано на Рисунке 14-3, взаимодействие между PCI ядром, драйверным ядром и отдельными драйверами PCI является довольно сложным.

Рисунок 14-3. Процесс создания устройства
Рисунок 14-3. Процесс создания устройства

Добавление устройства
Подсистема PCI декларирует единственную struct bus_type, названную pci_bus_type, которая инициализируется следующими значениями:

struct bus_type pci_bus_type = {
    .name      = "pci",
    .match     = pci_bus_match,
    .hotplug   = pci_hotplug,
    .suspend   = pci_device_suspend,
    .resume    = pci_device_resume,
    .dev_attrs = pci_dev_attrs,
};

Эта переменная pci_bus_type регистрируется в драйверном ядре, когда подсистема PCI загружается в ядро с помощью вызова bus_register. Когда это происходит, драйверное ядро создаёт в sysfs каталог /sys/bus/pci, который состоит из двух каталогов: devices и drivers.

Все драйверы PCI должны определить переменную struct pci_driver, которая определяет различные функции, которые может выполнять этот драйвер PCI (для получения дополнительной информации о подсистеме PCI и как написать драйвер PCI, смотрите Главу 12). Эта структура содержит struct device_driver, которая затем инициализируется ядром PCI при регистрации драйвера PCI:

/* инициализация обычных полей драйвера */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.probe = pci_device_probe;
drv->driver.remove = pci_device_remove;
drv->driver.kobj.ktype = &pci_driver_kobj_type;

Этот код устанавливает шину для драйвера задавая указатель на pci_bus_type и указатели на функции probe и remove, чтобы они указывали на функции в ядре PCI. Чтобы файлы атрибутов драйвера PCI работали соответствующим образом, ktype для kobject-а драйвера устанавливается на переменную pci_driver_kobj_type. Затем ядро PCI регистрирует PCI драйвер в драйверном ядре:

/* регистрация в ядре */
error = driver_register(&drv->driver);

Драйвер теперь готов быть связанным со всеми PCI устройствами, которые он поддерживает.

Ядро PCI, с помощью архитектурно-зависимого кода, который фактически общается с шиной PCI, начинает зондирование адресного пространства PCI, осуществляя поиск всех устройств PCI. Когда устройство PCI найдено, ядро PCI создаёт в памяти новую переменную типа struct pci_dev. Часть структуры struct pci_dev выглядит следующим образом:

struct pci_dev {
    /* ... */
    unsigned int devfn;
    unsigned short vendor;
    unsigned short device;
    unsigned short subsystem_vendor;
    unsigned short subsystem_device;
    unsigned int class;
    /* ... */
    struct pci_driver *driver;
    /* ... */
    struct device dev;
    /* ... */
};

Шинно-зависимые поля этого PCI устройства инициализируются ядром PCI (devfn, vendor, device и другие поля) и переменная parent переменной struct device является установленной на устройство шины PCI, в которой живёт это устройство PCI. Переменная bus устанавливается для указания на структуру pci_bus_type. Затем устанавливаются переменные name и bus_id, в зависимости от названия и ID, считанного из PCI устройства.

После инициализации структуры устройства PCI устройство регистрируется в драйверном ядре вызовом:

device_register(&dev->dev);

В функции device_register драйверное ядро инициализирует ряд полей устройства, регистрирует kobject устройства в kobject-е ядра (которое вызывает генерацию события горячего подключения, но мы обсудим это позже в этой главе) и затем добавляет устройство в список устройств, который содержится в родителе устройства. Это делается так, чтобы все устройства можно было обойти в надлежащем порядке, всегда зная, где в иерархии устройств живёт каждое из них.

Устройство добавляется затем в зависимый от шины список всех устройств, в этом примере, в список pci_bus_type. Затем обходится список всех драйверов, которые зарегистрированы на шине, и для каждого драйвера вызывается функция шины match, с указанием этого устройства. Для шины pci_bus_type, прежде, чем устройство было передано драйверному ядру, функция match была установлена ядром PCI для указания на функцию pci_bus_match.

Функция pci_bus_match приводит переданную ему драйверным ядром struct device обратно к struct pci_dev. Она также приводит struct device_driver обратно к struct pci_driver и затем смотрит на зависимую от устройства PCI информацию устройства и драйвер, чтобы увидеть, если драйвер утверждает, что он может поддерживать устройство такого вида. Если соответствие не успешно, функция обратно в драйверное ядро возвращает 0 и драйверное ядро переходит к следующему драйверу в своём списке.

Если соответствие найдено успешно, функция возвращает обратно в драйверное ядро 1. Это вызывает установку драйверным ядром указателя driver на struct device для указания на этот драйвер и затем оно вызывает функцию probe, которая указана в struct device_driver.

Ранее, до того, как PCI драйвер был зарегистрирован в драйверном ядре, переменная probe была установлена для указания на функцию pci_device_probe. Эта функция приводит (ещё раз) struct device обратно к struct pci_dev и struct driver, которая обратно становится устройством (device) в struct pci_driver. Она вновь проверяет, что этот драйвер заявляет, что он может поддерживать это устройство (которая представляется избыточной дополнительной проверкой по какой-то неизвестной причине), увеличивает счётчик ссылок устройства и затем вызывает функцию probe PCI драйвера с указателем на структуру struct pci_dev, к которой она должна привязываться.

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

$ tree /sys/bus/pci
/sys/bus/pci/
|-- devices
|   |-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
|   |-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1
|   |-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2
|   |-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0
|   |-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
|   |-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0
|   |-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
|   |-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0
|   |-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1
|   |-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2
|   |-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0
|   |-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
|   |-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0
|   |-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0
|   |-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
|   `-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0
`-- drivers
    |-- ALI15x3_IDE
    |   `-- 0000:00:0f.0 -> ../../../../devices/pci0000:00/0000:00:0f.0
    |-- ehci_hcd
    |   `-- 0000:00:09.2 -> ../../../../devices/pci0000:00/0000:00:09.2
    |-- ohci_hcd
    |   |-- 0000:00:02.0 -> ../../../../devices/pci0000:00/0000:00:02.0
    |   |-- 0000:00:09.0 -> ../../../../devices/pci0000:00/0000:00:09.0
    |   `-- 0000:00:09.1 -> ../../../../devices/pci0000:00/0000:00:09.1
    |-- orinoco_pci
    |   `-- 0000:00:12.0 -> ../../../../devices/pci0000:00/0000:00:12.0
    |-- radeonfb
    |   `-- 0000:00:14.0 -> ../../../../devices/pci0000:00/0000:00:14.0
    |-- serial
    `-- trident
        `-- 0000:00:04.0 -> ../../../../devices/pci0000:00/0000:00:04.0
Удаление устройства
PCI устройство может быть удалено из системы несколькими различными способами. Все устройства Card-Bus в действительности PCI устройства в другом физическом форм-факторе и ядро PCI в ядре не делает различий между ними. Системы, которые позволяют удаление или добавление PCI устройств во время работы машины, становятся всё более популярными и Linux их поддерживает. Существует также фальшивый драйвер PCI горячего подключения, который позволяет разработчикам тестирование, чтобы увидеть, что их PCI драйвер правильно обрабатывает удаление устройства во время работы системы. Этот модуль называется fakephp и заставляет ядро думать, что PCI устройство ушло, но не позволяет пользователям физически удалить из системы PCI устройство, которое не имеет надлежащего оборудования, чтобы сделать это. Смотрите документацию этого драйвера для получения дополнительной информации о том, как его использовать для тестирования PCI драйверов.

Ядро PCI прилагает гораздо меньше усилий для удаления устройства, чем для его добавления. Когда PCI устройство должно быть удалено, вызывается функция pci_remove_bus_device. Эта функция выполняет некоторые специфические для PCI очистки и служебные действия и затем вызывает функцию device_unregister с указателем на член struct device в struct pci_dev.

В функции device_unregister драйверное ядро лишь отсоединяет файлы sysfs от драйвера, связанного с устройством (если таковое имеется), удаляет устройство из своего внутреннего списка устройств и вызывает kobject_del с указателем на struct kobject, которая содержится в структуре struct device. Эта функция делает вызов горячего подключения в пользовательское пространство сообщая, что kobject теперь удалён из системы и затем она удаляет все файлы sysfs, связанные с kobject, и сам каталог в sysfs, который был первоначально создан kobject-ом.

Функция kobject_del также удаляет ссылку kobject-а самого устройства. Если эта ссылка была последней (что означает, что в пространстве пользователя нет файлов, которые были бы открыты в записи устройства в sysfs), то вызывается функция release в самом PCI устройстве, pci_release_dev. Эта функция просто освобождает память, которую занимала struct pci_dev.

После этого удаляют все записи в sysfs, связанные с устройством, и память, связанная с устройством, освобождается. PCI устройство теперь полностью удалено из системы.
Добавление драйвера
Драйвер PCI добавляется в ядро PCI при вызове функции pci_register_driver. Эта функция просто инициализирует структуру struct device_driver, которая содержится в структуре struct pci_driver, как уже упоминалось в разделе о добавлении устройства. Затем ядро PCI вызывает в драйверном ядре функцию driver_register с указателем на структуру struct device_driver, содержащуюся в структуре struct pci_driver.

Функция driver_register инициализирует несколько блокировок в структуре struct device_driver и затем вызывает функцию bus_add_driver. Эта функция выполняет следующее шаги:

Ищет шину, с которой должен быть связан драйвер. Если эта шина не найдена, функция мгновенно возвращается.
Создаётся каталог драйвера в sysfs на основе имени драйвера и шины, с которой он связан.
Захватывается внутренняя блокировка шины и затем обходятся все устройства, которые были зарегистрированы на шине, и для них вызывается функция совпадения (match), как при добавлении нового устройства. Если эта функция функция совпадения (match) завершается успешно, то происходит оставшаяся часть процесса подключения, как описано в предыдущем разделе.
Удаление драйвера
Удаление драйвера представляет собой очень простое действие. Для PCI драйвера, драйвер вызывает функцию pci_unregister_driver. Эта функция просто вызывает функцию driver_unregister драйверного ядра с указателем на структуру struct device_driver, часть структуры struct pci_driver, переданной ему.

Функция driver_unregister выполняет некоторую основную служебную работу, очищая некоторые атрибуты в sysfs, которые были связаны с записью драйвера в дереве sysfs. Затем она перебирает все устройства, которые были прикреплены к этому драйверу и вызывает для них функцию release. Это происходит так же, как упоминалось ранее для функции release, когда из системы удаляется устройство.

После того, как все устройства отсоединены от драйвера, код драйвера выполняет этот уникальный кусочек логики:

down(&drv->unload_sem);
up(&drv->unload_sem);

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

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.