Linux Platform Driver Model (I)_Device Information

Keywords: Linux C network Programming

I am here Linux Character Device Driver Framework In this paper, we briefly introduce the programming model of Linux character device. In that model, as long as the application program opens () the corresponding device files, we can use ioctl to control our hardware through the driver. This model is intuitive, but from the point of view of software design, it is a very bad way. It has a fatal problem, that is, device information and driver code. Redundancy together, once the hardware information changes or even the device is no longer available, it is necessary to modify the driver source code, which is very troublesome. In order to solve the problem of coupling between the driver code and the device information, Linux puts forward the concept of platform bus (platform bus). Even if the device information and driver are separated by virtual bus, the proposal of device tree is further deepened. This kind of thought, will carry on the better arrangement to the equipment information. The platform bus maintains two linked lists to manage devices and drivers respectively. When a device is registered on the bus, the bus searches for the corresponding driver according to its name. If it finds it, it imports the device information into the driver and executes the driver. When a driver is registered on the platform bus, the bus also searches for the device. In short, the platform bus is responsible for matching device information and driver code, so that the driver and device information can be separated.

Before the emergence of device tree, device information can only be written in C language. After 3.0, device information begins to support two kinds of writing methods at the same time - device tree and C language. If device tree is used to manually write device information into device tree, the kernel can automatically extract device information from device tree and encapsulate it into corresponding platform_devic. E object, i2c_device object and registered in the corresponding bus, if we use the device tree, we do not need to encode the device information. If we use C language, obviously, we need to manually encapsulate device information using the structure provided by the kernel. This encapsulation can be divided into two forms. One is to use platform files (static), and write all devices on the whole board in one file and compile them into the kernel. Another is to use modules (dynamic) to compile the device information we need into modules in insmod into the kernel. For ARM platform, using device tree to encapsulate device information is the trend in the future, but for historical reasons, these three ways coexist in the current kernel. After encapsulation, the corresponding x xxx_device instance is created and finally registered in the bus. For the device information of the platform bus, I am in Linux Device Tree Syntax Explanation In this paper, we have discussed how to write the device tree, so we mainly discuss four issues:

  1. How to encapsulate device information in C language?
  2. How to convert the device information of the device tree to that of the C language?
  3. How to encapsulate C language device information into platform_device structure?
  4. How to register the encapsulated platform_device structure into the platform bus?

What are resources?

The so-called device information is mainly divided into two kinds: hardware information and software information. Hardware information mainly includes xxx controller on xxxx address. xxx device occupies xxx interrupt number, namely address resource and interrupt resource. The kernel provides struct resource to encapsulate these resources. There are many kinds of software information, such as MAC address in network card devices, etc. These information need us to encapsulate the device object in the form of private data (the kernel is written with object-oriented thinking, the device information of a device is an object, that is, an instance of the structure, and the driving method of a device is also an object). This part of information needs us. Custom structure for encapsulation.

struct resource

This structure is used to describe an address resource or interrupt resource. In addition to this structure, the kernel also provides macros to help us quickly create a resource object.

//include/linux/ioport.h
 18 struct resource {    
 19         resource_size_t start;
 20         resource_size_t end;
 21         const char *name;
 22         unsigned long flags;
 23         unsigned long desc;
 24         struct resource *parent, *sibling, *child;
 25 };

struct resource
--19--> startRepresents where the resource starts, if so IO Address resource is the starting physical address. If the resource is interrupted, it is the interrupt number.;
--20--> endRepresents the end of the resource, if so IO Address address is the last mappingPhysical address,If resources are interrupted, no filling is required.;
--21--> nameThat's the name of this resource.
--22--> flagsRepresents the type of resource, and the extraction function compares its parameters and members when searching for resources. In theory, it can be written as long as possible, but qualified engineers should use the macros provided by the kernel. These macros also exist."ioport.h"Definitions are made, such asIORESOURCE_MEMRepresents that this resource is an address resource.IORESOURCE_IRQRepresents that this resource is an interrupt resource....

//include/linux/ioport.h
 33 #define IORESOURCE_BITS         0x000000ff      /* Bus-specific bits */
 35 #define IORESOURCE_TYPE_BITS    0x00001f00      /* Resource type */
 36 #define IORESOURCE_IO           0x00000100      /* PCI/ISA I/O ports */
 37 #define IORESOURCE_MEM          0x00000200
 38 #define IORESOURCE_REG          0x00000300      /* Register offsets */
 39 #define IORESOURCE_IRQ          0x00000400          
 40 #define IORESOURCE_DMA          0x00000800
 41 #define IORESOURCE_BUS          0x00001000
 ...
147 #define DEFINE_RES_IO(_start, _size)   
152 #define DEFINE_RES_MEM(_start, _size)   
157 #define DEFINE_RES_IRQ(_irq)  
162 #define DEFINE_RES_DMA(_dma)   

With these attributes, a resource can be described completely, but if each resource needs to be managed separately rather than composing a data structure, it is obviously a very foolish approach, so the resource structure of the kernel also provides three pointers: parent,sibling,child(24), which are used to represent the parent resource, sibling resource and child resource of the resource respectively, so that the kernel can In order to manage a large number of system resources efficiently by using tree structure, the linux kernel has two tree structures: iomem_resource and ioport_resource. When developing at board level, the ROM resources on the motherboard are usually put into a node of the iomem_resource tree, and the inherent I/O resources of the system are linked to the ioport_resource tree.

The following is a small example of two ways of expressing address resources and interrupt resources. It is strongly recommended to use the version of DEFINE_RES_XXX.

//IO Address Resources, Fill in the resource structure + flags macro
struct resource res= {
    .start = 0x10000000,
    .end     = 0x20000000-1,
    .flags = IORESOURCE_MEM
};
//IO address resources, using defined macros provided by the kernel
struct resource res = DEFINE_RES_MEM(0x20000000, 1024);
//Interrupt resources and fill the resource structure + flags macro by yourself
struct resource res = { 
        .start  = 10,
        .flags  = IORESOURCE_IRQ,
};
//Interrupt resources, using defined macros provided by the kernel
struct resource res = DEFINE_RES_IRQ(11);

The following is an example of a resource array, which is written as an array when multiple resources are available. Here I use both of the above methods.

struct resource res[] = {
    [0] = {
        .start  = 0x10000000,
        .end    = 0x20000000-1,
        .flags  = IORESOURCE_MEM
    },
    [1] = DEFINE_RES_MEM(0x20000000, 1024),
    [2] = {
        .start  = 10,   //interrupt number
        .flags  = IORESOURCE_IRQ|IRQF_TRIGGER_RISING //include/linux/interrupt.h
    },
    [3] = DEFINE_RES_IRQ(11),   
};

resource VS dts

So far, we have discussed the use of device tree and resource structure to write device information. Obviously, these two methods are ultimately the same way. Here we briefly discuss the conversion between the two. Here I am. Linux Device Tree Syntax Explanation Node of dm9000 network card used in this paper

Writing its address resource as resource is like this. For clarity, there are also two ways to write it:

struct resource res[] = {
    [0] = {
        .start = 0x05000000,
        .end = 0x05000000+0x2-1,
        .flags = IORESOURCE_MEM,
    },
    [1] = DEFINE_RES_MEM(0x05000004,2),
};

platform_device object

This object is the device information object that we will ultimately register on the platform bus and encode the device information. In fact, it is to create a platform_device object. It can be seen that platform_device, like other devices, is a subclass of device.

//include/linux/platform_device.h
 22 struct platform_device {                                    
 23         const char      *name;
 24         int             id;
 25         bool            id_auto;
 26         struct device   dev;
 27         u32             num_resources;
 28         struct resource *resource;
 29 
 30         const struct platform_device_id *id_entry;
 31 
 32         /* MFD cell pointer */
 33         struct mfd_cell *mfd_cell;
 34 
 35         /* arch specific additions */
 36         struct pdev_archdata    archdata;
 37 };

In this object, we are mainly concerned about the following members

struct platform_device
--23-->name That's the name of the device. Note, the module name.(lsmod)!=Device name(/proc/devices)!=Device File Name(/dev),This name is the bridge between driver method and device information matching.
--24-->Express this platform_device Objects represent several devices, when multiple devices have common resources(MFD),Fill in the corresponding number of equipment, if only one, fill in-1
--26-->Parent object(include/linux/device.h +722),We usually care about what's inside.platform_dataandrelease,The former is used to store private device information, and the latter is used to call back the kernel when the last reference of the device is deleted. rmmod No problem.
--27-->The number of resources, i.e. resource The number of elements in an array, we useARRAY_SIZE()Macros determine the size of arrays(include/linux/kernel.h +54 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) )
--28-->Resource pointer, if there are more than one resource struct resource[]Array name

static struct platform_device demoobj = {
    //2. init obj
    .name   = "demo0",
    .id = 0,
    .dev    = {
        .platform_data = &priv,
        .release = dev_release,
    },
    .num_resources  = ARRAY_SIZE(res),
    .resource   = res,
};

Registration and cancellation of equipment objects

With the platform_device object ready, you can then register it into the kernel, which obviously has the relevant functions ready for us.

/**
 *Registration: Add the specified device to the device list of the platform bus in the kernel, wait for matching, and call back probe in the driver if the matching is successful.
 */
int platform_device_register(struct platform_device *);
/**
 *Log-off: Delete the specified device from the device list, and call back the release in the driver method and device information if the driver matches.
 */
void platform_device_unregister(struct platform_device *);

Typically, we write platform_device_register in the module loaded function and platform_device_unregister in the module unloaded function.

Posted by mjr on Thu, 21 Mar 2019 11:12:54 -0700