Embedded Linux Device Driver Development Driver Hierarchy/Separation
When we learned about I2C, USB, and SD drivers, we found a commonality. When we were driving development, each driver was layered in three parts, from top to bottom:
1, XXX device driver
2, XXX core layer
3, XXX host controller driver
But we need to write the main part of the device driver, the host controller driver part is also a small amount of writing, the main interaction between the two by the core layer interface to achieve; this structure is clear, and greatly conducive to our drive development, which Is the use of Linux device driver development in the two important ideas, the following one by one to resolve
First, device-driven layered thinking
In object-oriented programming, you can define a base class for a similar class of things, and specific things can inherit functions from this base class. If the inheritance of this thing, the implementation of a function of its consistent with the base class, then it can directly inherit the functions of the base class; on the contrary, it can be overloaded. This object-oriented design concept greatly improves the reusability of code and is a good representation of the relationship between real-world things.
The Linux kernel is written entirely in C and assembly language, but object-oriented design ideas are frequently used. In terms of device drivers, a framework is often designed for similar devices, while the core layer in the framework implements some of the functions common to the device. Similarly, if a specific device does not want to use core-level functions, it can be overloaded. for example:
Return_type core_funca(xxx_device *bottom_dev, param1_type param1, param1_type param2)
{
If (bottom_dev->funca)
Return bottom_dev->funca(param1, param2);
/* Funca code common to the core layer*/
...
}
In the implementation of core_funca described above, it is checked whether the underlying device is overloaded with funca(). If overloaded, the underlying code is called; otherwise, the generic layer is used directly. The advantage of this is that the code in the core layer can handle the func() corresponding functions of the vast majority of such devices, and only a few special devices need to reimplement funca().
Look at an example:
Copyreturn_type core_funca(xxx_device *bottom_dev, param1_type param1, param1_type param2)
{
/* Generic step code A */
...
Bottom_dev->funca_ops1();
/* Generic step code B */
...
Bottom_dev->funca_ops2();
/* Generic step code C*/
...
Bottom_dev->funca_ops3();
}
The above code assumes that in order to implement funca(), for the same type of device, the operation flow is consistent, and the steps “common code A, bottom ops1, common code B, bottom ops2, common code C, and bottom ops3†are all passed through. The obvious benefit of the design is that, for the generic code A, B, C, the specific underlying driver does not need to be implemented again, but only the underlying operations ops1, ops2, and ops3. Figure 1 clearly reflects the relationship between the device driver's core layer and the specific device driver. In fact, this layering may be only two layers (a in Figure 1) or multiple layers (b in Figure 1).
This layered design is not uncommon in Linux's input, RTC, MTD, I2C, SPI, TTY, USB and many other device driver types.
Second, the host drive and peripheral drive separation of ideas
The meaning of separation of host and peripheral drivers
In the design of the Linux device driver framework, apart from the layered design implementation, there are also separate ideas. As a simple example, suppose we want to access a peripheral through the SPI bus. In this access procedure, the purpose of accessing the SPI peripheral YYY by operating the register of the SPI controller on the CPU XXX is the simplest method. :
Copyreturn_type xxx_write_spi_yyy(...)
{
Xxx_write_spi_host_ctrl_reg(ctrl);
Xxx_ write_spi_host_data_reg(buf);
While(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
...
}
If the driver is designed in this way, the result is that for any SPI peripheral, its driver code is CPU-related. That is, of course, when used on the CPU XXX, it accesses the XXX SPI master control register. When used on XXX1, it accesses the XXX1 SPI master control register:
Return_type xxx1_write_spi_yyy(...)
{
Xxx1_write_spi_host_ctrl_reg(ctrl);
Xxx1_ write_spi_host_data_reg(buf);
While(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
...
}
This is obviously unacceptable because it means that the peripheral YYY needs different drivers when used on different CPUs XXX and XXX1. Then, we can use the idea of ​​figure to separate the host controller driver and peripheral driver. This structure is that the drivers of the peripherals a, b, and c are not related to the driver of the host controllers A, B, and C. The host controller driver does not care about the peripherals, and the peripheral drivers do not care about the host, and the peripherals only access The common API of the core layer performs data transmission, and any combination between the host and the peripherals can be performed.
If we do not separate the host from the peripherals in the figure above, the peripherals a, b, c and the hosts A, B, and C are combined and nine different drivers are required. Imagine a total of m host controllers, n peripherals, the result of the separation is the need for m+n drives, and m*n drives are required for no separation.
Subsystems such as the Linux SPI, I2C, USB, and ASoC (ALSA SoC) typically use this separation of design ideas.
Shenzhen Xcool Vapor Technology Co.,Ltd , http://www.xcoolvapor.com