Linux -- Driver framework

04 Jul 2020

Post Tags

Linux kernel Device-tree

General device driver framework

A typical character device driver may include following components.

1.initial & exit function (initial and delete driver module)
2.register & unregister function (inside initial, exit function and used for configurating operations of drivers)
3.file_operation structure (include basic operation accept by drives like: open,read,write and etc..)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/device.h>

#define DEV_NAME "Embedchrdev"
#define DEV_CNT (1)


static int chr_dev_open(struct inode *inode, struct file *filp){
	/* Todo */
    printk("chrdevtest_open \n");
    return 0;
}

static int chr_dev_release(struct inode *inode, struct file *filp){
	/* Todo */
    printk("chrdevtest_release \n");
    return 0;
}

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{	/* Todo */
    printk("chrdevtest_read \n");
    return 0;
 }

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{	/* Todo */
    printk("chrdevtest_write \n");
    return 0;
 }

// pack the chrdev property inside a structure
struct chrdev_dev{
	dev_t devid;/* deviceid */
  	struct cdev cdev; 
  	struct class *class; /* class */
  	struct device *device; /* device */
  	int major; /* major devid */
  	int minor;
  	struct device_node *rgb_led_node; /* rgb_led root node */
};



static struct file_operations chr_dev_fops={
	.owner = THIS_MODULE,
	// some operations are optional depends on usage
	.open = chr_dev_open,
	.read = chr_dev_read,
	.write = chr_dev_write,
	.release = chr_dev_release,
};

struct chrdev_dev chrdev;

static int __init chrdev_init(void)
{
 int ret = 0;
 printk("chrdev init\n");
 // step (1). register device and/or get device id
 
 

 if (chrdev.major) { /* using defineded deivce number */
        chrdev.devid = MKDEV(chrdev.major, 0);
        register_chrdev_region(chrdev.devid, DEV_CNT, DEV_NAME);
    } else {    /* not defined the device number */
		// device name is 'EmbedCharDev', can be checked with cat /proc/devices
        ret = alloc_chrdev_region(&chrdev.devid, 0, DEV_CNT, DEV_NAME); /* apply for device number */ 
		if (ret < 0) {
			printk("fail to alloc devno\n");
 			goto alloc_err;
 		}
        chrdev.major = MAJOR(chrdev.devid); /* get major device number */ 
        chrdev.minor = MINOR(chrdev.devid); /* get minor device number */
    } 

 
 //Step (2). match the char device with file operations structure and functions.
 cdev_init(&chrdev.cdev, &chr_dev_fops);

 //Step (3)
 //add chrdev to cdev_map list
 ret = cdev_add(&chrdev.cdev, chrdev.devid, DEV_CNT);
 if (ret < 0) {
 printk("fail to add cdev\n");
 goto add_err;
 }
 return 0;


 add_err:
 //if add char device failed, unregister the chrdev
 unregister_chrdev_region(chrdev.devid, DEV_CNT);
 alloc_err:
 return ret;
 }

 static void __exit chrdev_exit(void){
	 unregister_chrdev_region(chrdev.devid, DEV_CNT);
	 cdev_del(&chrdev.cdev);
 }


 module_init(chrdev_init);
 module_exit(chrdev_exit);
 MODULE_LICENSE("GPL");

1. Driver with device tree and bus(i2c bus for example)

The main framework of bus based driver is similar with general framework, but added device_driver match table which is used to match the driver with device(device tree or device resource file).

1.1 Framework of i2c device driver based on i2c bus

The i2c_driver is used to characterize the property of a i2c device.

static const struct i2c_device_id gtp_device_id[] ={
    {"jie,i2c_mpu6050", 0},
    {}
};

static const struct of_device_id mpu6050_match_table[]={
    {.compatible = "jie,i2c_mpu6050"},
    {/* Sentinel */}
};


struct i2c_driver mpu6050_driver = {
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
    .id_table = gtp_device_id,
    .driver ={
            .name = "mpu6050",
            .owner = THIS_MODULE,
            .of_match_table = mpu6050_match_table,
    },
};

The .probe indicate the initial functional which will be loaded when mpu6050_driver match with a device. Most of time in .probe we only register and add the device.

The .of_match_table and .id_table will compare and match the driver with a device tree or board-level resource files. Following is a i2c device tree node and the property of compatible will be matched with mpu6050_match_table and gtp_device_id.

THe main purpose of .of_match_table and .i2c_device_id is same. The only difference between them is priority. System will try match with .of_match_table first if there is no rules, it will try to match with .i2c_device_id .

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
 	status = "okay";


	i2c_mpu6050@68 {
		compatible = "jie,i2c_mpu6050";
 		reg = <0x68>;
		position = <0>;

	};
};

The following part is the framework of a i2c driver. The start flow can be simplified as:

  1. register the i2c driver inside initial function
  2. if driver match with the device, it goes into .probe function
  3. inside .probe register your i2c device (register,bind,add,initial).
  4. running the file operation function(open,release,write, read, etc.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


struct mpu6050_dev { 
    dev_t devid;
    struct cdev cdev; /* cdev */
    struct class *class; /* class */
    struct device *device; /* device */
    struct device_node * dev_node; /* device_node */
    int major; /* major deviceid */
    void *private_data; /* data */

    short mpu6050_result[6]; /* 6 aixs gyro and accel data */
};



static int mpu6050_open(struct inode *inode, struct file *file)
{

    /* TODO*/
    return 0;
}

static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    return 0;
}

static int mpu6050_release(struct inode *inode, struct file *filp)
{
    return 0;
} 

static struct file_operations mpu6050_chr_dev_fops =
    {
            .owner = THIS_MODULE,
            .open = mpu6050_open,
            .read = mpu6050_read,
            .release = mpu6050_release,
};

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    
    return 0;
}

static int mpu6050_remove(struct i2c_client *client)
{
    
    return 0;
}


static const struct i2c_device_id gtp_device_id[] ={
    {"jie,i2c_mpu6050", 0},
    {}
};

static const struct of_device_id mpu6050_match_table[]={
    {.compatible = "jie,i2c_mpu6050"},
    {/* Sentinel */}
};



struct i2c_driver mpu6050_driver = {
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
    .id_table = gtp_device_id,
    .driver ={
            .name = "mpu6050",
            .owner = THIS_MODULE,
            .of_match_table = mpu6050_match_table,
    },
};


static int __init mpu6050_driver_init(void)
{
    int ret;
    pr_info("mpu6050_driver_init");
    ret = i2c_add_driver(&mpu6050_driver);

    return 0;
}


static void __exit mpu6050_driver_exit(void){
    
    pr_info("mpu6050_driver_exit");
    i2c_del_driver(&mpu6050_driver);
        

}


module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);
MODULE_LICENSE("GPL");

2. Driver with device tree and platform

Platform bus can be seen as a kind of virtual bus, and the usage of platform bus is very similar with normal BUS.

2.1


Post Tags

Linux kernel Device-tree