The process of porting Linux device drivers

  • Tutorial
Hello, harachtochiteli!

Introduction


Sometimes it happens that it becomes necessary to upgrade to a newer version of the Linux kernel and, accordingly, transfer existing device drivers.



The transfer process can take from several minutes to a longer period of time. This depends not only on the complexity of the driver, but also on which and which version of the kernel you are going to switch to (the API tends to change - all problems come from here), as well as on the quality of the code implementation, it can be easier to rewrite than to transfer but we will not talk about this.

Unfortunately, I can’t attach the driver source code, but we will look at all the problems that you and I may encounter during the transfer process. Next, we will consider an example of porting a simple driver from the kernel version 2.6.25 to 4.12.5, which is located in drivers / serial / name_uart.c. The following resource 2.6.25 and 4.12.5 , where you can see the structure of the kernel, as well as the source codes, will also help us a lot .

Formulation of the problem


The problem statement is extremely primitive and simple - you need to transfer the above-mentioned driver from the kernel version 2.6.25 to 4.12.5.

Implementation


First of all, if you are not familiar with the driver that you have to transfer, then I would recommend to study it at least superficially. This can greatly simplify the task. After that, you can proceed to the transfer.

Step One
Our driver has the following path in the 2.6.25 kernel: drivers / serial / name_uart.c
Now we need to find the appropriate directory where to put it. And here we meet the first problem - there is no such directory drivers / serial / in the kernel 4.12.5.

It is solved very simply: we take and put our driver in drivers / tty / serial / name_uart.c
Step two

After that, so that Linux can include our driver in the assembly, we need to add it to two files.

First file: drivers / tty / serial / Makefile
We add the following line to it:

obj-$(CONFIG_SERIAL_NAME)	+= name_uart.o

Second file: drivers / tty / serial / Kconfig
In it we write the following:

config SERIAL_NAME
	tristate "SERIAL_NAME UART driver"
	help
	  Write description here

Step Three.
Once we have completed the first two steps, we can proceed to the assembly.

From the root directory, run make menuconfig and include our driver in the assembly.
When the command is executed, the graphical interface opens and there should be no problems finding our driver in it (the search can be done in the following way: press / and write the full or part of the name, then enter).

There is another way - you can simply edit the .config file and include your driver in the assembly there.

Next, you can try to build a kernel with our driver turned on.

Fourth step

After we tried to build the kernel, most likely we had errors that needed to be solved in order to successfully build the kernel afterwards and be sure to check our driver for operability.

The error that I encountered was an outdated interface for working with the proc system, as well as a remote macro.

For example, a call to the irequest_irq function in the 2.6.25 kernel is successful, BUT

irequest_irq(64 + ISC_DMA, dma_interrupt_handler, IRQF_DISABLED, "DMA", NULL);

in the kernel 4.12.5, the IRQF_DISABLED macro was deleted, and therefore the driver was not built.
Solution - take and substitute 0 instead of IRQF_DISABLED.

irequest_irq(64 + ISC_DMA, dma_interrupt_handler, 0, "DMA", NULL);

The next error was that in the kernel of version 2.6.25, interaction with the proc system occurred using create_proc_entry, the implementation of which you will no longer find in 4.12.5.

Therefore, it was necessary to rewrite the implementation a bit and as a result the following option turned out:

/******************************************************************************
 * /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,
}

To summarize all of the above, the driver transfer is divided into the following steps:
1. copy the source code of the driver of the previous version to the new version of the Linux kernel;
2. add a description in Kconfig and in the Makefile;
3. pick up our driver using busybox in the kernel assembly (or using direct editing of the .config file);
4. try to eliminate all errors during assembly until the kernel is assembled.

So, we examined the basics of how the process of porting a device driver to a newer version of the Linux kernel occurs, as well as problems that you may encounter.

In the next article, we will write our first full-fledged native driver for a given technical task for one of the new versions of the Linux kernel, and we will also test it not only using standard Linux utilities, but we will write our own test for it.

Please, if you find inaccuracies, or you have something to add, write in the PM or in the comments.

Thank!