Registering platform bus devices and drivers under Linux

preface

The main content of this paper is to register platform bus devices and platform bus drivers under Linux.

1, platform bus

Platform bus model: a bus virtualized by the Linux kernel is not a real wire.
The platform bus model divides the original driver c file into two C files, one is device.c and the other is driver.c, in which the stable ones are placed in driver.c and the ones that need to be changed are placed in device.c. This can improve the reusability of code and reduce the repeatability of code, which is the role of platform bus model.
So how to write a driver designed based on the platform bus model? In fact, driver.c and device.c are registered respectively. There is a structure member in these two files to match each other. When the two drivers are loaded, the structure member used to store the matching name in the two files has the same content, the matching is successful, otherwise the matching fails.

2, Registering platform devices

1. Introduction to structure

device.c contains hardware resources, which refer to register address, interrupt number, clock and other hardware resources.
In the Linux kernel, a structure is used to describe hardware resources.
platform_ The device structure is defined in / linux-4.1.15/include/linux/platform_device.h.

struct platform_device {
    const char  *name;  
    //If the name used in platform bus matching is consistent with that in driver, the matching is successful. Otherwise, the matching fails, and the name will be generated under / sys/bus/platform/devices
    int     id;  //Device id, generally write - 1
    bool    id_auto;
    struct device   dev; //Embedded device structure
    u32     num_resources; //Number of resources
    struct resource *resource; //Hardware resources in device
    const struct platform_device_id *id_entry;
    char *driver_override; 
    struct mfd_cell *mfd_cell;
    struct pdev_archdata    archdata;
};

Register function:

extern int platform_device_register(struct platform_device *);

Logoff function:

extern void platform_device_unregister(struct platform_device *);

The resource structure is defined under / linux-4.1.15/include/linux/ioport.h.

struct resource {
    resource_size_t start; //Resource start
    resource_size_t end; //End of resource
    const char *name;  //Resource name
    unsigned long flags;  //Resource type
    struct resource *parent, *sibling, *child;
};

The macro definitions used are also in / linux-4.1.15/include/linux/ioport.h.

#define IORESOURCE_ IO memory
#define IORESOURCE_MEM represents a piece of physical memory
#define IORESOURCE_IRQ indicates interrupt

After the device is loaded successfully, you can see the name defined by yourself to match the driver in the / sys/bus/platform/devices path.

2.device.c file

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/platform_device.h> 

void beep_release(struct device *dev){
	printk("beep_release!\n");
}

struct resource beep_resource[] = {
	[0] = {
		.start = 0x20AC000,
		.end = 0x20AC003,
		.flags = IORESOURCE_MEM, //Register type
		.name = "GPIO5_DR"
	}
};

struct platform_device beep_device = {
    .name = "beep_test", 
     //If the name used in platform bus matching is consistent with that in driver, the matching is successful. Otherwise, the matching fails, and the name will be generated under / sys/bus/platform/devices
    .id = -1,  //Device id, generally write - 1
    .resource = beep_resource, //Hardware resources in device
    .num_resources = ARRAY_SIZE(beep_resource), //Number of resources
	.dev = {
		.release = beep_release  //Release or delete device
	}
};

static int device_init(void)
{
	printk("hello device_init!\n");
	return platform_device_register(&beep_device); //register
}

static int device_exit(void)
{
	printk("device_exit!\n");
	platform_device_unregister(&beep_device); //cancellation
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");

3.Makefile file

obj-m += device.o
KDIR:=/linux/linux-4.1.15
PWD?=$(shell pwd)
all:
        make -C $(KDIR) M=$(PWD) modules
clean:
        make -C $(KDIR) M=$(PWD) clean

3, Register platform driver

1. Introduction to structure

The idea of writing driver.c: first define a platform_driver structure variable, and then implement each member variable in the structure. When the driver and device match successfully, the probe function will be executed. After the match is successful, the focus is on the probe function.
platform_ The driver structure is defined in / linux-4.1.15/include/linux/platform_device.h.

struct platform_driver {
	int (*probe)(struct platform_device *);
	//When the driver and device match successfully, the probe function will be executed
	int (*remove)(struct platform_device *);
	//This function will be executed when either driver or device is remove d
	void (*shutdown)(struct platform_device *);
	//This function is executed when the device receives the shutdown command
	int (*suspend)(struct platform_device *, pm_message_t state);
	//This function is executed when the device receives the suspend command
	int (*resume)(struct platform_device *);
	//This function is executed when the device receives the resume command
	struct device_driver driver;
	const struct platform_device_id *id_table; //Priority is higher than name under driver
	bool prevent_deferred_probe;
};

device_ The driver structure is defined under / linux-4.1.15/include/linux/device.h.

struct device_driver {
    const char *name;  //The name used when matching with device, with priority lower than ID_ Name in table
    struct module  *owner;
};

2.driver.c file

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/platform_device.h> 

int beep_probe(struct platform_device *pdev)
{
	printk("beep_probe matching ok!\n");
	return 0;
}

int beep_remove(struct platform_device *pdev)
{
	printk("beep_remove!\n");
	return 0;
}

const struct platform_device_id beep_idtable = {
	.name = "beep_test"  //The name priority here is higher than the name in the driver, and the priority matches the name in the device
};

struct platform_driver beep_device = {
	.probe = beep_probe,
	.remove = beep_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "beep123"  //It needs to be the same as the name in the device, otherwise the matching is unsuccessful. If there is no idtable, the device matches the name
	},
	.id_table = &beep_idtable
};

static int beep_driver_init(void)
{
	int ret = 0;
	ret = platform_driver_register(&beep_device);
	if(ret < 0) {
		printk("platform_driver_register error!\n");
		return ret;
	}
	printk("platform_driver_register ok!\n");
	return 0;
}

static int beep_driver_exit(void)
{
	printk("beep_driver_exit!\n");
	platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");

3.Makefile file

obj-m += driver.o
KDIR:=/linux/linux-4.1.15
PWD?=$(shell pwd)
all:
        make -C $(KDIR) M=$(PWD) modules
clean:
        make -C $(KDIR) M=$(PWD) clean

4, Operation results

After compiling the above code, send the drivers to the development board respectively, and then load the drivers. The loading sequence does not affect the matching results.
First, load the device driver and print the information of successful registration.

Use the following command to view the devices generated under the platform bus.

ls /sys/bus/platform/devices

Then load the driver driver and print the information of successful registration and matching.

In order to verify that the matching results are independent of the loading order, we uninstall the two drivers first, and you can see that they both print the unloading information.

Then load the driver first.

It can be seen that loading the driver first does not print the information of successful matching. The information of successful matching is written in driver.c. at the same time, the "beep_test" defined in the code does not appear in the device.
We load the device driver again.

Not only the information of successful matching is printed, but also the device appears. This is the same as the result of loading the device driver first and then the driver driver. Therefore, the loading sequence of device and driver drivers will not affect the matching result.

summary

The above is all about registering platform bus devices and platform bus drivers under Linux. We should pay attention to the idea of platform bus model, which will help us simplify problems and improve efficiency.

Keywords: Linux

Added by sgtbash on Mon, 01 Nov 2021 15:48:25 +0200