Процесс портирования драйверов устройств Linux

Здравствуйте, хаброчитатели!

Введение


Иногда так случается, что возникает необходимость перейти на более новую версию ядра Linux и, соответственно, выполнить перенос уже существующих драйверов устройств.

5rntbjo583-vvnhho7wszb7pjs0.png

Процесс переноса может занять от нескольких минут до более продолжительного промежутка времени. Зависит это не только от сложности драйвера, но и от того, с какой и на какую версию ядра вы собираетесь перейти (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, но и напишем свой тест для него.

Пожалуйста, если вы нашли неточности, или вам есть что добавить — напишите в ЛС или в комментарии.

Спасибо!

© Habrahabr.ru