Yuhang Zheng

Linux platform平台总线、平台设备、平台驱动

N 人看过

平台总线(platform_bus)的需求来源?

随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作,
人们为了提高效率,发现控制器的操作逻辑(方法)是一样的,只有寄存器地址不一样,如果将与硬件有关的
代码(platform_device)和驱动代码(platform_driver)分开,升级soc后,因为驱动方式一样,
只需要修改与硬件有关的代码就可以,实现一个驱动控制多个设备。

平台(platform)总线是一种虚拟的总线,在 /sys/bus/platform 目录可以看到。
平台总线三要素:平台总线、平台设备、平台驱动
平台总线原则:先分离,后合并

分离:

将设备信息封装成 platform_device,将驱动信息封装成 platform_driver,并为各自起名称,
然后将 platform_device 中的 struct device 和 platform_driver 中的 struct device_driver 分别注册到设备链表和驱动链表中。

1
2
3
4
5
6
7
8
9
10
11
int platform_device_register(struct platform_device *pdev)

  return platform_device_add(pdev);

    ret = device_add(&pdev->dev);

int platform_driver_register(struct platform_driver *drv)

  return driver_register(&drv->driver);

    ret = bus_add_driver(drv);

合并:

在系统每注册一个设备(驱动)时,平台总线会找与之匹配的驱动(设备),匹配原则是名称相同。

装载(insmod)时设备和驱动没有顺序,卸载(rmmod)时必须先卸载设备文件,因为卸载设备会调用驱动中的 remove 函数

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
// 下面的“|”表示包含于上一个之中
// 描述设备的信息
struct platform_device {
const char * name; // 用于和platform_driver进行匹配的名字--自定义
int id; // 一般直接填-1,区分不同的控制组
struct device dev; // 父类
|
void (*release)(struct device *dev); // 设备卸载时调用的函数
void *platform_data; // 匹配后传递的自定义数据
... ...
u32 num_resources; // 资源的个数
struct resource * resource; // 描述资源信息
|
resource_size_t start; // 起始位置
resource_size_t end; // 结束位置
const char *name; // 自定义
unsigned long flags; // 区分不同的资源,一般是 内存或者中断资源
... ...
};
// 描述设备的操作方法
struct platform_driver {
int (*probe)(struct platform_device *); // 设备和驱动匹配后调用的函数
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; // 父类
|
const char *name; // 该名字可以用于匹配,但比id_table中的name优先级低
// 此名称在 /sys/bus/platform/drivers/xxx
... ...
const struct platform_device_id *id_table;
|
char name[PLATFORM_NAME_SIZE]; // 用于和platform_device的名字进行匹配,优先级高
};
// 平台总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, // 用于匹配,此函数可以看出匹配名称的优先级
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 注册 platform_devive
int platform_device_register(struct platform_device *pdev);

// 注销 platform_devive
void platform_device_unregister(struct platform_device *pdev);

// 注册 platform_driver
int platform_driver_register(struct platform_driver *drv);

// 注销 platform_driver
void platform_driver_unregister(struct platform_driver *drv);

// 批量注册pdev
int platform_add_devices(struct platform_device **devs, int num);

获取资源的接口:

1
2
3
4
5
6
// 通过类型和编号获取资源
// 参数1:pdev
// 参数2:获取的资源类型
// 参数3:获取的资源编号

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
1
2
3
4
// 通过类型和名称获取资源
// 参数3:获取资源的名称

struct resource * platform_get_resource_byname(struct platform_device * dev,unsigned int type,const char * name);
1
2
3
// 通过编号获取中断资源

int platform_get_irq(struct platform_device * dev,unsigned int num);
1
2
3
// 通过名称获取中断资源

int platform_get_irq_byname(struct platform_device * dev,const char * name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 重点:资源编号一定是按照同种类型来排序的
struct resource led_res[] = {
[0] = { // 获取内存资源时,此内存资源的编号为0
... ...
.flags = IORESOURCE_MEM,
},
[1] = { // 获取中断资源时,此中断资源的编号为0
... ...
.flags = IORESOURCE_IRQ,
},
[2] = { // 获取内存资源时,此内存资源的编号为1
... ...
.flags = IORESOURCE_MEM,
},
};

790679-20210118143126904-714280859

plat_led_dev.c

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
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>

#include "plat_led.h"

#define GPL2_0 0x11000100
#define GP_SIZE 8

// 平台自定义数据,与硬件相关
struct regled led_reg = {
.ctl_clr = 0x0f,
.ctl_set = 0x01,
.dat_clr = 0x01,
.dat_set = 0x01,
};

static struct resource led_res[] = {
[0] = {
.start = GPL2_0,
.end = GPL2_0 + GP_SIZE - 1,
.name = "led0",
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 888,
.end = 888,
.name = "virt_irq",
.flags = IORESOURCE_IRQ,
},
};

void plat_led_release(struct device *dev)
{
// 为了卸载模块时不报错误
}

struct platform_device led_pdev = {
.name = "plat_led",
.id = -1,
.dev = {
.platform_data = &led_reg,
.release = plat_led_release,
},
.num_resources = ARRAY_SIZE(led_res),
.resource = led_res,
};

static int __init plat_led_dev_init(void)
{
platform_device_register(&led_pdev);

return 0;
}

static void __exit plat_led_dev_exit(void)
{
platform_device_unregister(&led_pdev);
}

module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");

plat_led_drv.c

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include "plat_led.h"

struct samsung *platled;

static int platled_open(struct inode *inode, struct file *fops)
{
writeb(readb(platled->reg_base+4) & (~platled->reg->dat_clr), platled->reg_base+4);

return 0;
}

static int platled_close(struct inode *inode, struct file *fops)
{
return 0;
}

static ssize_t platled_write(struct file *fops, const char __user *buf, size_t size, loff_t *fpos)
{
int value;
//暂时忽略返回值
copy_from_user(&value, buf, size);

if (value)
writeb((readb(platled->reg_base+4) & (~platled->reg->dat_clr)) | platled->reg->dat_set, platled->reg_base+4);
else
writeb(readb(platled->reg_base+4) & (~platled->reg->dat_clr), platled->reg_base+4);

return 0;
}

const struct file_operations platled_fops = {
.open = platled_open,
.release = platled_close,
.write = platled_write,
};

static int led_register(void)
{
int ret;

platled = kmalloc(sizeof(struct samsung), GFP_KERNEL);
if (platled == NULL)
{
printk("kmalloc fail!\n");
return -ENOMEM;
}

platled->major = register_chrdev(0, "plat_led", &platled_fops);
if (platled->major < 0)
{
printk("register_chrdev fail!\n");
ret = -EFAULT;
goto chrdev_err;
}

platled->cls = class_create(THIS_MODULE, "plat_led");
if (platled->cls < 0)
{
printk("class_create fail!\n");
ret = -EFAULT;
goto class_err;
}

platled->dev = device_create(platled->cls, NULL, MKDEV(platled->major, 0), NULL, "plat_led");
if (platled->dev < 0)
{
printk("device_create fail!\n");
ret = -EFAULT;
goto device_err;
}

return 0;

device_err:
class_destroy(platled->cls);

class_err:
unregister_chrdev(platled->major, "plat_led");

chrdev_err:
kfree(platled);

return ret;
}

static void led_unregister(void)
{
device_destroy(platled->cls, MKDEV(platled->major, 0));
class_destroy(platled->cls);
unregister_chrdev(platled->major, "plat_led");
kfree(platled);
}


int plat_led_probe(struct platform_device *pdev)
{
int ret;
struct resource *res;

ret = led_register();
if (ret < 0)
{
printk("plat_led_probe fail\n");
return -EFAULT;
}

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL)
{
printk("platform_get_resource fail\n");
return -ENODEV;
}
// 获取平台自定义数据
platled->reg = pdev->dev.platform_data;

platled->reg_base = ioremap(res->start, resource_size(res));
writel((readl(platled->reg_base) & (~platled->reg->ctl_clr)) | platled->reg->ctl_set, platled->reg_base);

return 0;
}

int plat_led_remove(struct platform_device *pdev)
{
iounmap(platled->reg_base);
led_unregister();

return 0;
}

const struct platform_device_id led_id_table[] = {
{"plat_led", 0x1234}, //第二个整数是自定义
};

struct platform_driver led_pdrv = {
.probe = plat_led_probe,
.remove = plat_led_remove,
.driver = {
.name = "red_led",
},
.id_table = led_id_table,// name用于匹配
};

static int __init plat_led_drv_init(void)
{
platform_driver_register(&led_pdrv);

return 0;
}

static void __exit plat_led_drv_exit(void)
{
platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_drv_init);
module_exit(plat_led_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");

plat_led.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef __PLAT_LED_H_
#define __PLAT_LED_H_

struct regled {
unsigned long ctl_clr;
unsigned long ctl_set;
unsigned long dat_clr;
unsigned long dat_set;
};

struct samsung {
int major;
struct class *cls;
struct device *dev;
struct regled *reg;
void *reg_base;
};

#endif