Драйверы Linux: разработка TTY драйвера для устройства PCI
Автор: Степанов В.В., Баранова Е.А.
Журнал: Форум молодых ученых @forum-nauka
Статья в выпуске: 5 (9), 2017 года.
Бесплатный доступ
В данной статье рассмотрены общие принципы и методы разработки TTY драйверов Linux для PCI устройств, выполняющих функцию последовательного порта.
Операционная система linux, драйверы устройств, устройства pci, устройства tty, последовательный порт
Короткий адрес: https://sciup.org/140278520
IDR: 140278520
Текст научной статьи Драйверы Linux: разработка TTY драйвера для устройства PCI
Драйвер – системная программа, предназначенная для управления каким-либо физическим или виртуальным устройством компьютера. Драйверы устройств являются наиболее критической частью программного обеспечения компьютера, а также они являются и наиболее скрытой частью.
Последовательный порт был популярен в прошлом, когда он использовался для подключения модемов, клавиатур и других периферийных устройств. С развитием технологий в персональных компьютерах последовательный порт практически полностью был заменён более быстрым и компактным интерфейсом USB. Но для специализированных устройств его использование остаётся актуальным ввиду возможности передачи на расстояния до нескольких десятков метров для стандарта RS-232 или до одного километра для RS-485. Ключевым устройством для работы с последовательным портом является универсальный асинхронный приёмопередатчик (УАПП, англ. Universal Asynchronous Receiver-Transmitter, UART). UART выполняет функцию преобразования данных в последовательный вид.
В системе Linux для передачи информации через последовательный порт была разработана концепция системы, состоящей из драйвера UART порта, протокола передачи и драйвера TTY. На ядро TTY возложена обязанность следить за управлением потоком данных и за их формат. Это позволяет TTY драйверам заниматься только управлением оборудованием, а не организовывать взаимодействие с пространством пользователя. Также они позволяют производить настройку скорости передачи, режима проверки чётности и других параметров UART как с использованием интерфейса termios, так и непосредственно в консоли, используя утилиту stty.
В нашем случае мы рассматриваем подключение UART как PCI устройства. Стандарт PCI представляет собой как аппаратное подключение, так и программное обеспечение взаимодействия компонент. Для разработчиков модулей ядра Linux важной особенностью является автоматическая настройка PCI устройств во время загрузки системы. Таким образом драйверу необходимо только завершить инициализацию.
Каждому TTY драйверу необходимо создать struct tty_driver, которая описывает его и используется для регистрации и отмены регистрации в ядре TTY. Эта структура описана в файле
SERIAL_TYPE_NORMAL соответственно. Поле init_termios используется для управления скоростью, и другими настройками передачи. Все указанные поля должны быть заполнены во время регистрации драйвера в ядре системы. struct tty_operations определяет список функций, поддерживаемых драйвером. В нашем случае необходимо определить только функции open, close и write.
static const struct tty_operations device _ops = { .open = device _open,
.close = device _close,
.write = device _write,
};
Листинг 1 – пример описания struct tty_operations
Для работы с PCI устройством необходимо указать ядру Linux с каким именно устройством должен работать драйвер, для этого необходимо определить struct pci_device_id, в которой указываются идентификаторы производителя и устройства, описанные в документации к плате. Макрос MODULE_DEVICE_TABLE необходим для указания системе, с какими устройствами работает модуль.
static const struct pci_device_id device_ids[] = {
{ PCI_DEVICE(VENDOR_ID, DEVICE_ID) },
{ /* empty */ }, };
MODULE_DEVICE_TABLE(pci, device_ids);
Листинг 2 – пример описания struct pci_device_id
Основной структурой для драйвера PCI является struct pci_driver.
Для корректной работы необходимо определить поле name, которое будет отображено в /sys/bus/pci/drivers/. Поле id_table является указателем на struct pci_device_id. Поле probe – это указатель на зондирующую функцию, в ней производится настройка обнаруженного устройства. remove – указатель на функцию удаления PCI устройства из системы. Эти структуры описаны в файле
static struct { .name |
pci_driver pci_driver = |
= “device_name”, |
|
.id_table |
= device_ids, |
.probe |
= device_probe, |
.remove }; |
= device_remove, |
Листинг 3 – пример описания struct pci_driver
В функции инициализации модуля необходимо зарегистрировать как TTY драйвер, так и драйвер PCI. Функция tty_alloc_driver должна быть вызвана для создания struct tty_driver с количеством поддерживаемых этим драйвером TTY устройств и значением флага драйвера, в качестве параметров. После этого следует заполнить поля этой структуры в соответствии с их описанием. Для регистрации TTY драйвера необходимо вызвать функцию tty_register_driver и pci_register_driver для PCI драйвера. В случае неудачи следует не забывать возвращать в систему занятые ресурсы, в зависимости от места возникновения ошибок используются функции tty_unregister_driver, put_tty_driver и pci_unregister_driver.
static int __init device_init(void)
{ int retval;
/* инициализация struct tty_driver */ tty_driver = tty_alloc_driver(TTY_MINORS, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
if (!tty_driver)
{ /* обработка ошибки */ }
{ /* заполнение полей struct tty_driver */ } tty_set_operations(tty_driver, &tpm_ops);
/* регистрация TTY драйвера */ retval = tty_register_driver(tty_driver); if (retval)
{ /* обработка ошибки */ }
/* регистрация PCI драйвера */ retval = pci_register_driver( if (retval)
{ /* обработка ошибки */ } return retval;
}
Листинг 4 – пример описания функции инициализации
В функции очистки необходимо освободить все ресурсы, занятые в функции инициализации.
При обнаружении новых PCI устройств, управление которыми должно быть передано их драйверу, вызывается зондирующая функция probe. В этой функции должны быть выполнены такие действия, как разрешение устройства - pci_enable_device и получение доступа к регистрам устройства. Для работы буфера чтения необходимо произвести инициализацию его структуры вызовом tty_port_init, прикреплённой к каждому устройству, которое будет зарегистрировано, произвести инициализацию оборудования и зарегистрировать TTY устройство - tty_port_register_device.
static int device_probe(struct pci_dev *pdev, const struct pci_device_id *dev_ids)
{
DEVICE_STUCT *dev_st; /* структура, описывающая конкретное устройство
*/ struct device *tty_dev;
int err;
/* разрешение устройства */ err = pci_enable_device(pdev);
if (err)
{ /* обработка ошибки */ }
/* получение доступа к регистрам устройства */ dev_st ->baseAdr = ioremap(start, len);
pci_set_drvdata(pdev, dev_st);
/* заполнение dev_st */
/* инициализация структуры порта и регистрация устройства */ tty_port_init(&dev_st ->port);
tty_dev = tty_port_register_device(&dev_st ->port, tty_driver, devNo, &pdev->dev);
if (IS_ERR(tty_dev))
{ /* обработка ошибки */ } devNo++; /* количество обнаруженных устройств */ return 0;
}
Листинг 5 – пример описания зондирующей функции probe
В функции удаления устройств remove необходимо, также, освободить все занятые ресурсы: tty_unregister_device – удалить TTY устройства, уничтожить порт вызовом tty_port_destroy, освободить ресурсы ввода-вывода и отключить устройство вызовом функции pci_disable_device.
В функции open должен быть обеспечен учёт количества открытий файла TTY устройства и установка требуемой конфигурации устройства. Запрос линии прерывания request_irq должен производиться только при первом открытии. Аргументы функции не позволяют сразу обратиться к структуре устройства и идентифицировать открытое устройства можно, используя его младший номер, который хранится в поле index из struct tty_struct. Для быстрого доступа к структуре устройства можно сохранить указатель на неё в поле driver_data структуры struct tty_struct.
При последнем закрытии устройства должна быть выполнена функция free_irq для освобождения линии прерывания.
Функция write вызывается ядром TTY, когда готовы данные для отправки на TTY устройство, функция должна вернуть количество успешно переданных байт данных или отрицательный код ошибки. В этой функции данные должны быть записаны в регистры устройства для передачи, причём если нет возможности передать сразу всё сообщение, то лучше сразу вернуть количество реально переданных байт данных, а не погружать драйвер в сон.
Для чтения данных из устройства драйвер TTY не предполагает отдельной функции, он должен только обеспечивать передачу данных в ядро TTY, где происходит буферизация данных. Таким образом драйверу нет необходимости организовывать логику буферизации. Передача данных в ядро TTY может происходить в контексте прерывания. Для отправки данных ядру необходимо вызвать функцию tty_insert_flip_char, аргументами её являются указатель на struct tty_port и значение флага. Если был получен обычный символ, то флаг должен иметь значение TTY_NORMAL, а если произошла ошибка при передаче, оно должно быть установлено в TTY_BREAK, TTY_FRAME, TTY_PARITY или TTY_OVERRUN. Для проталкивания информации пользователю после считывания данных необходимо вызывать функцию tty_flip_buffer_push.
Используя данные рекомендации можно создать собственный драйвер последовательного порта, а изучив подробнее тему вопроса и руководство для конкретного устройства возможно разработать функции для более полного управления устройством.
Данная статья предназначена для людей уже имеющих представление о разработке драйверов для Linux, но возможно она будет полезна и тем, кто только начинает изучать данную область компьютерных технологий.
Список литературы Драйверы Linux: разработка TTY драйвера для устройства PCI
- Корбет Д. Драйверы Устройств Linux, Третья Редакция [Текст]/ Д. Корбет, А. Рубини, Г. Кроах-Хартман. - Sebastopol: O'Reilly, 2005.-636c.
- The Linux Kernel API [Электронный ресурс] URL: https://www.kernel.org/doc/htmldocs/kernel-api/ (Дата обращения 13.04.2017)
- Greg Kroah-Hartman. The tty Layer [Электронный ресурс] URL: http://www.linuxjournal.com/article/5896 (Дата обращения 18.04.2017)
- IBM Knowledge Center. Драйверы TTY [Электронный ресурс] URL: https://www.ibm.com/support/knowledgecenter/ru/ssw_aix_71/com.ibm.aix.genprogc/tty_drivers.htm (Дата обращения 19.04.2017)