Вопрос или проблема
Как написать драйвер ядра Linux для UART?
Через UART я получаю непрерывный и асинхронный поток данных: главная проблема здесь – не упустить ни одной данные.
Я использую serdev
(http://events17.linuxfoundation.org/sites/events/files/slides/serdev-elce-2017-2.pdf), чтобы получать входящие данные, реализуя receive_buf
в драйвере. Обратите внимание, что я не знаю, как получить номер IRQ, связанный с этим IRQ receive_buf
(в противном случае я бы мог использовать потоковый IRQ, как объяснено ниже).
Теперь, в драйвере ядра Linux, я знаю про IRQ и механизмы верхней и нижней половины: коротко говоря, когда срабатывает IRQ, верхняя половина вызванного обращения должна обрабатывать все, что можно обработать быстро (чтобы сделать прерывание возможным снова как можно скорее, чтобы не пропустить входящие данные), и передать остальную работу нижней половине (которая будет вызвана позже).
Для нижней половины, насколько я понимаю, существуют разные возможные механизмы: потоковые IRQ, рабочие очереди, softirq, tasklet. Softirq и tasklet, похоже, являются кандидатами на устаревание в ближайшем будущем https://lwn.net/Articles/960041/, поэтому я оставляю их позади. Рабочие очереди, похоже, позволяют добавлять в очередь только один элемент работы за раз, поскольку функция queue_work
из API возвращает false, если работа уже находится в очереди, так что я не совсем уверен, могу ли я использовать эти очереди для накопления 1 элемента работы для каждого куска данных. Наконец, потоковые IRQ, по всей видимости, являются лучшими кандидатами, но для создания потокового IRQ нужен номер IRQ: как уже было объяснено, я не знаю, как получить его из serdev
.
Какой “хороший или предпочтительный” способ действовать?
Используя serdev
, как я могу инициализировать потоковый IRQ из колбека receive_buf
(т.е. как получить IRQ, связанный с этим колбеком)? Насколько я понимаю, это был бы лучший способ, верно?
Есть ли другие варианты?
Существует ли пример, с которого я мог бы начать? (ничего не нашел)
Я работаю на Raspberry Pi с ядром 6.1.x.
Ответ или решение
Как написать драйвер ядра Linux для UART
Написание драйвера для UART (Universal Asynchronous Receiver-Transmitter) в ядре Linux представляет собой важную задачу, особенно когда необходимо гарантировать, что не пропустите ни одного полученного байта данных. В этой статье вы узнаете основные шаги, касающиеся разработки драйвера для UART с использованием инфраструктуры serdev
, а также обсудим методы обработки прерываний и рабочие очереди.
Основные компоненты ядра и работа с прерываниями
-
Понимание архитектуры UART: UART управляет асинхронным вводом-выводом данных и может работать с различными контурами передачи данных. Убедитесь, что вы хорошо понимаете спецификации устройства, которое будет использоваться.
-
Используйте интерфейс
serdev
:serdev
значительно упрощает написание драйверов для серийных устройств. Он инкапсулирует работу с UART, позволяя сосредоточиться на обработке данных. Основной методом для обработки входящих данных будетreceive_buf
, который будет вызываться при поступлении новых данных. -
Инициализация и управление прерываниями: Прерывания играют ключевую роль в системе обработки данных. Когда вы используете
serdev
, вам не нужно напрямую получать IRQ номер. Вместо этого следует следовать стандартному протоколу, позволяющемуserdev
обрабатывать привязку прерываний и соответствующие вызовы.
Обработка входящих данных
При написании receive_buf
:
- Соблюдайте высокую производительность. Ваша задача здесь – быстро считывать данные в буфер и разбудить поток для более детальной обработки.
- Если вы используете асинхронный поток, постарайтесь минимизировать время блокировки IRQ. Используйте меньше работы в обработчике прерываний.
Методики управления очередью
Для обработки задач, не требующих немедленного исполнения, рассмотрите различные подходы:
-
Threaded IRQ: Используйте встроенные функции для создания потокового прерывания. Хотя вам и не нужно явно получать IRQ для
serdev
, вы можете использовать функциюdevm_request_irq
в рамкахserdev
, чтобы настроить обработчик прерываний как поток. -
Рабочие очереди (workqueues): Это хороший вариант для обработки большего объёма данных с задержкой. В отличие от
threaded IRQ
, workqueue позволяет обрабатывать несколько задач последовательно.
Пример простого драйвера для UART
Вот упрощенный пример, который демонстрирует как можно реализовать драйвер, используя serdev
:
#include <linux/module.h>
#include <linux/serdev.h>
#include <linux/string.h>
struct my_uart_data {
struct serdev_device *serdev;
};
static int my_uart_receive_buf(struct serdev_device *serdev, const void *buf, size_t len) {
// Обработка полученных данных
printk(KERN_INFO "Received %zu bytes\n", len);
return len; // Возвращаем количество обработанных байт
}
static const struct serdev_device_ops my_uart_ops = {
.receive_buf = my_uart_receive_buf,
};
static int my_uart_probe(struct serdev_device *serdev) {
struct my_uart_data *data;
data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->serdev = serdev;
serdev->dev.driver_data = data; // Сохраняем данные драйвера
serdev_set_drvdata(serdev, data);
serdev_device_register(serdev);
return 0;
}
static int my_uart_remove(struct serdev_device *serdev) {
// Освобождение ресурсов
return 0;
}
static struct serdev_driver my_uart_driver = {
.driver = {
.name = "my_uart_driver",
},
.probe = my_uart_probe,
.remove = my_uart_remove,
.ops = &my_uart_ops,
};
module_serdev_driver(my_uart_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ваше имя");
MODULE_DESCRIPTION("Драйвер UART на основе serdev");
Заключение
Создание драйвера для UART в ядре Linux — задача, требующая тщательной проработки. Использование serdev
для абстрагирования и управления прерываниями значительно упростит процесс. Следуя рекомендованным подходам и примерному коду, вы сможете разработать надежное решение для обработки асинхронно получаемых данных.
Дополнительно рекомендуем обратиться к документации по serdev
и изучить существующие драйвера, чтобы повысить свои знания в этой области.