Процесс портирования драйверов устройств Linux
Здравствуйте, хаброчитатели!
Введение
Иногда так случается, что возникает необходимость перейти на более новую версию ядра Linux и, соответственно, выполнить перенос уже существующих драйверов устройств.
Процесс переноса может занять от нескольких минут до более продолжительного промежутка времени. Зависит это не только от сложности драйвера, но и от того, с какой и на какую версию ядра вы собираетесь перейти (API имеет свойство меняться — отсюда лезут все проблемы), а также от качества реализации кода, бывает, что проще переписать, чем перенести, но об этом не будем.
К сожалению, я не могу прикрепить исходный код драйвера, но мы рассмотрим все проблемы, с которыми я и вы можете столкнуться в процессе переноса. Далее будет рассмотрен пример переноса простого драйвера c версии ядра 2.6.25 на 4.12.5, который расположен в drivers/serial/name_uart.c. Также нам очень поможет следующий ресурс 2.6.25 и 4.12.5, где можно посмотреть структуру ядра, а также исходные коды.
Постановка задачи
Постановка задачи крайне примитивна и проста — требуется перенести выше упомянутый драйвер с версии ядра 2.6.25 на 4.12.5.
Реализация
Первым делом, если вы не знакомы с драйвером который вам предстоит перенести, то я бы порекомендовал хотя бы поверхностно изучить его. Это может значительно упростить задачу. После этого можно приступать к переносу.
Шаг первый
Наш драйвер имеет следующий путь в ядре 2.6.25: drivers/serial/name_uart.c
Теперь нам нужно найти подходящую директорию, куда можно его положить. И тут мы встречаем первую проблему — такой директории drivers/serial/ в ядре 4.12.5 нет.
Решается она очень просто: берем и кладем наш драйвер в drivers/tty/serial/name_uart.c
Шаг второй
После этого, чтобы Linux смог включить наш драйвер в сборку, нам нужно добавить его в два файлика.
Первый файлик: drivers/tty/serial/Makefile
В него мы добавляем следующую строчку:
obj-$(CONFIG_SERIAL_NAME) += name_uart.o
Второй файлик: drivers/tty/serial/Kconfig
В него мы пишем следующее:
config SERIAL_NAME
tristate "SERIAL_NAME UART driver"
help
Write description here
Шаг третий
Как только мы выполнили первые два шага, можно перейти к сборке.
Из корневой директории запускаем make menuconfig и включаем наш драйвер в сборку.
При выполнении команды открывается графический интерфейс и проблемы найти в нем наш драйвер быть не должно (поиск можно сделать следующим путем: жмем / и пишем полное или часть названия, далее enter).
Есть и иной способ — можно просто отредактировать .config файл и включить в сборку ваш драйвер там.
Далее можно попытаться собрать ядро с нашим включенным драйвером.
Шаг четвертый
После того как мы попытались собрать ядро, скорее всего у нас посыпались ошибки, которые нужно решить, чтобы после выполнить успешную сборку ядра и обязательно проверить на работоспособность наш драйвер.
Ошибка, с которой столкнулся я — это устаревший интерфейс работы с proc системой, а также удаленный макрос.
Например, вызов функции irequest_irq в ядре 2.6.25 проходит успешно, НО
irequest_irq(64 + ISC_DMA, dma_interrupt_handler, IRQF_DISABLED, "DMA", NULL);
в ядре 4.12.5 макрос IRQF_DISABLED был удален, и поэтому драйвер не собирался.
Решение — возьмем и подставим 0 вместо IRQF_DISABLED.
irequest_irq(64 + ISC_DMA, dma_interrupt_handler, 0, "DMA", NULL);
Следующая ошибка заключалась в том, что в ядре версии 2.6.25 взаимодействие с proc системой происходило с помощью create_proc_entry, реализацию которой вы больше не найдете в 4.12.5.
Поэтому необходимо было немного переписать реализацию и в итоге получился следующий вариант:
/******************************************************************************
* /proc interface
******************************************************************************/
static int xr16_get_status(struct seq_file *m, void *v)
{
struct uart_name_uart *pp;
unsigned long f;
u64 tmp;
int i, xmax = ARRAY_SIZE(pp->in_irq);
seq_printf(m, "UART NAME ports stat:\n");
for (i = 0; i < UART_NAME_MAXPORTS; i++) {
pp = &uart_name_16_ports[i];
if (!pp->used)
continue;
local_irq_save(f);
tmp = ktime_to_us(ktime_sub(ktime_get(), pp->ktm_last));
do_div(tmp, USEC_PER_SEC);
/*
* Необходимая вам реализация.
*/
local_irq_restore(f);
}
return 0;
}
static int xr16_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, uart_name_get_status, NULL);
}
static const struct file_operations uart_name_proc_fops = {
.owner = THIS_MODULE,
.open = uart_name_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
}
Если обобщить все выше изложенное, то перенос драйвера разделяется на следующие этапы:
1. скопировать исходные коды драйвера предыдущей версии на новую версию ядра Linux;
2. добавить описание в Kconfig и в Makefile;
3. подцепить с помощью busybox в сборку ядра наш драйвер (или с помощью прямого редактирования .config файла);
4. пытаться устранить все ошибки при сборке, пока ядро не соберется.
Итак, мы рассмотрели основы того, как происходит процесс портирования драйвера устройства на более новую версию ядра Linux, а также проблемы, с которыми можно столкнуться.
В следующей статье мы напишем свой первый полноценный собственный драйвер по заданному техническому заданию для одной из новых версий ядра Linux, а также протестируем его не только с помощью стандартных утилит Linux, но и напишем свой тест для него.
Пожалуйста, если вы нашли неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Спасибо!