一、platform 驱动的工作过程
platform模型驱动编程,需要实现platform_device(设备)与platform_driver(驱动)在platform(虚拟总线)上的注册、匹配,相互绑定,然后再做为一个普通的字符设备进行相应的应用,总之如果编写的是基于字符设备的platform驱动,在遵循并实现platform总线上驱动与设备的特定接口的情况下,最核心的还是字符设备的核心结构:cdev、 file_operations(他包含的操作函数接口)、dev_t(设备号)、设备文件(/dev)等,因为用platform机制编写的字符驱动,它的本质是字符驱动。
我们要记住,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。
在一般情况下,2.6内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。那么我们编写platform模型驱动时,需要完成两个工作:
a -- 实现platform驱动
b -- 实现platform设备
然而在实现这两个工作的过程中还需要实现其他的很多小工作,在后面介绍。platform模型驱动的实现过程核心架构就很简单,如下所示:
platform驱动模型三个对象:platform总线、platform设备、platform驱动。
platform总线对应的内核结构:struct bus_type-->它包含的最关键的函数:match() (要注意的是,这块由内核完成,我们不参与)
platform设备对应的内核结构:struct platform_device-->注册:platform_device_register(unregister)
platform驱动对应的内核结构:struct platform_driver-->注册:platform_driver_register(unregister)
那具体platform驱动的工作过程是什么呢:
设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;
如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;
如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致它的probe函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。
二、实现platform 驱动与设备的详细过程
1、思考问题?
在分析platform 之前,可以先思考一下下面的问题:
a -- 为什么要用 platform 驱动?不用platform驱动可以吗?
b -- 设备驱动中引入platform 概念有什么好处?
现在先不回答,看完下面的分析就明白了,后面会附上总结。
2、platform_device 结构体 VS platform_driver 结构体
这两个结构体分别描述了设备和驱动,二者有什么关系呢?先看一下具体结构体对比
前面提到,实现platform模型的过程就是总线对设备和驱动的匹配过程 。打个比方,就好比相亲,总线是红娘,设备是男方,驱动是女方:
a -- 红娘(总线)负责男方(设备)和女方(驱动)的撮合;
b -- 男方(女方)找到红娘,说我来登记一下,看有没有合适的姑娘(汉子)—— 设备或驱动的注册;
c -- 红娘这时候就需要看看有没有八字(二者的name 字段)匹配的姑娘(汉子)——match 函数进行匹配,看name是否相同;
d -- 如果八字不合,就告诉男方(女方)没有合适的对象,先等着,别急着乱做事 —— 设备和驱动会等待,直到匹配成功;
e -- 终于遇到八字匹配的了,那就结婚呗!接完婚,男方就向女方交代,我有多少存款,我的房子在哪,钱放在哪等等( struct resource *resource),女方说好啊,于是去房子里拿钱,去给男方买菜啦,给自己买衣服、化妆品、首饰啊等等(int (*probe)(struct platform_device *) 匹配成功后驱动执行的第一个函数),当然如果男的跟小三跑了(设备卸载),女方也不会继续待下去的( int (*remove)(struct platform_device *))。
3、设备资源结构体
在struct platform_device 结构体中有一重要成员 struct resource *resource
[cpp] view plain copy
1. struct resource {
2. resource_size_t start; 资源起始地址
3. resource_size_t end; 资源结束地址
4. const char *name;
5. unsigned long flags; 区分是资源什么类型的
6. struct resource *parent, *sibling, *child;
7. };
8.
9. #define IORESOURCE_MEM 0x00000200
10. #define IORESOURCE_IRQ 0x00000400
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如
a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;
b -- flags为 IORESOURCE_IRQ 时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值;
下面看一个实例:
[cpp] view plain copy
1. static struct resource beep_resource[] =
2. {
3. [0] = {
4. .start = 0x114000a0,
5. .end = 0x114000a0+0x4,
6. .flags = IORESOURCE_MEM,
7. },
8.
9. [1] = {
10. .start = 0x139D0000,
11. .end = 0x139D0000+0x14,
12. .flags = IORESOURCE_MEM,
13. },
14. };
4、将字符设备添加到platform的driver中
前面我们提到platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳,下面我们看一下添加的过程:
[cpp] view plain copy
1. static struct file_operations hello_ops=
2. {
3. .open = hello_open,
4. .release = hello_release,
5. .unlocked_ioctl = hello_ioctl,
6. };
7.
8. static int hello_remove(struct platform_device *pdev)
9. {
10. 注销分配的各种资源
11. }
12.
13. static int hello_probe(struct platform_device *pdev)
14. {
15. 1.申请设备号
16. 2.cdev初始化注册,&hello_ops
17. 3.从pdev读出硬件资源
18. 4.对硬件资源初始化,ioremap,request_irq( )
19. }
20.
21. static int hello_init(void)
22. {
23. 只注册 platform_driver
24. }
25.
26. static void hello_exit(void)
27. {
28. 只注销 platform_driver
29. }
可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。
5、platform是如何匹配device和driver
这时就该总线出场了,系统为platform总线定义了一个bus_type 的实例platform_bus_type,其定义如下:
[cpp] view plain copy
1. struct bus_type platform_bus_type = {
2. .name = "platform",
3. .dev_groups = platform_dev_groups,
4. .match = platform_match,
5. .uevent = platform_uevent,
6. .pm = &platform_dev_pm_ops,
7. };
其又是怎样工作的呢?在platform.c (e:\linux-3.14-fs4412\drivers\base) 31577 2014/3/31 中可以看到
[cpp] view plain copy
1. __platform_driver_register()
2. {
3. drv->driver.bus = &platform_bus_type; 536行
4. }
在platform_bus_type 中调用 了platform_match:
[cpp] view plain copy
1. static int platform_match(struct device *dev, struct device_driver *drv)
2. {
3. struct platform_device *pdev = to_platform_device(dev);
4. struct platform_driver *pdrv = to_platform_driver(drv);
5.
6. 匹配设备树信息,如果有设备树,就调用 of_driver_match_device() 函数进行匹配
7. if (of_driver_match_device(dev, drv))
8. return 1;
9.
10.
11. 匹配id_table
12. if (pdrv->id_table)
13. return platform_match_id(pdrv->id_table, pdev) != NULL;
14.
15. 最基本匹配规则
16. return (strcmp(pdev->name, drv->name) == 0);
17. }
6、解决问题
现在可以回答这两个问题了
a -- 为什么要用 platform 驱动?不用platform驱动可以吗?
b -- 设备驱动中引入platform 概念有什么好处?
引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;
设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
三、实例
这是一个蜂鸣器的驱动,其实前面已经有解析 Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动, 下面来看一下,套上platform 外壳后的程序:
1、device.c
[cpp] view plain copy
1. #include <linux/module.h>
2. #include <linux/device.h>
3. #include <linux/platform_device.h>
4. #include <linux/ioport.h>
5.
6. static struct resource beep_resource[] =
7. {
8. [0] ={
9. .start = 0x114000a0,
10. .end = 0x114000a0 + 0x4,
11. .flags = IORESOURCE_MEM,
12. },
13.
14. [1] ={
15. .start = 0x139D0000,
16. .end = 0x139D0000 + 0x14,
17. .flags = IORESOURCE_MEM,
18. }
19. };
20.
21. static void hello_release(struct device *dev)
22. {
23. printk("hello_release\n");
24. return ;
25. }
26.
27.
28.
29. static struct platform_device hello_device=
30. {
31. .name = "bigbang",
32. .id = -1,
33. .dev.release = hello_release,
34. .num_resources = ARRAY_SIZE(beep_resource),
35. .resource = beep_resource,
36. };
37.
38. static int hello_init(void)
39. {
40. printk("hello_init");
41. return platform_device_register(&hello_device);
42. }
43.
44. static void hello_exit(void)
45. {
46. printk("hello_exit");
47. platform_device_unregister(&hello_device);
48. return;
49. }
50.
51. MODULE_LICENSE("GPL");
52. module_init(hello_init);
53. module_exit(hello_exit);
2、driver.c
[cpp] view plain copy
1. #include <linux/module.h>
2. #include <linux/fs.h>
3. #include <linux/cdev.h>
4. #include <linux/device.h>
5. #include <linux/platform_device.h>
6. #include <asm/io.h>
7.
8. static int major = 250;
9. static int minor=0;
10. static dev_t devno;
11. static struct class *cls;
12. static struct device *test_device;
13.
14. #define TCFG0 0x0000
15. #define TCFG1 0x0004
16. #define TCON 0x0008
17. #define TCNTB0 0x000C
18. #define TCMPB0 0x0010
19.
20. static unsigned int *gpd0con;
21. static void *timer_base;
22.
23. #define MAGIC_NUMBER 'k'
24. #define BEEP_ON _IO(MAGIC_NUMBER ,0)
25. #define BEEP_OFF _IO(MAGIC_NUMBER ,1)
26. #define BEEP_FREQ _IO(MAGIC_NUMBER ,2)
27.
28. static void fs4412_beep_init(void)
29. {
30. writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
31. writel ((readl(timer_base +TCFG0 )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0);
32. writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 );
33.
34. writel (500, timer_base +TCNTB0 );
35. writel (250, timer_base +TCMPB0 );
36. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON );
37. }
38.
39. void fs4412_beep_on(void)
40. {
41. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
42. }
43.
44. void fs4412_beep_off(void)
45. {
46. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
47. }
48.
49. static void beep_unmap(void)
50. {
51. iounmap(gpd0con);
52. iounmap(timer_base);
53. }
54.
55. static int beep_open (struct inode *inode, struct file *filep)
56. {
57. fs4412_beep_on();
58. return 0;
59. }
60.
61. static int beep_release(struct inode *inode, struct file *filep)
62. {
63. fs4412_beep_off();
64. return 0;
65. }
66.
67. #define BEPP_IN_FREQ 100000
68. static void beep_freq(unsigned long arg)
69. {
70. writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0 );
71. writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
72.
73. }
74.
75. static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
76. {
77. switch(cmd)
78. {
79. case BEEP_ON:
80. fs4412_beep_on();
81. break;
82. case BEEP_OFF:
83. fs4412_beep_off();
84. break;
85. case BEEP_FREQ:
86. beep_freq( arg );
87. break;
88. default :
89. return -EINVAL;
90. }
91. return 0;
92. }
93.
94. static struct file_operations beep_ops=
95. {
96. .open = beep_open,
97. .release = beep_release,
98. .unlocked_ioctl = beep_ioctl,
99. };
100.
101. static int beep_probe(struct platform_device *pdev)
102. {
103. int ret;
104. printk("match ok!");
105.
106. gpd0con = ioremap(pdev->resource[0].start,pdev->resource[0].end - pdev->resource[0].start);
107. timer_base = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start);
108.
109. devno = MKDEV(major,minor);
110. ret = register_chrdev(major,"beep",&beep_ops);
111.
112. cls = class_create(THIS_MODULE, "myclass");
113. if(IS_ERR(cls))
114. {
115. unregister_chrdev(major,"beep");
116. return -EBUSY;
117. }
118.
119. test_device = device_create(cls,NULL,devno,NULL,"beep");//mknod /dev/hello
120. if(IS_ERR(test_device))
121. {
122. class_destroy(cls);
123. unregister_chrdev(major,"beep");
124. return -EBUSY;
125. }
126.
127. fs4412_beep_init();
128.
129. return 0;
130. }
131.
132. static int beep_remove(struct platform_device *pdev)
133. {
134. beep_unmap();
135. device_destroy(cls,devno);
136. class_destroy(cls);
137. unregister_chrdev(major,"beep");
138.
139. return 0;
140. }
141.
142.
143. static struct platform_driver beep_driver=
144. {
145. .driver.name = "bigbang",
146. .probe = beep_probe,
147. .remove = beep_remove,
148. };
149.
150.
151. static int beep_init(void)
152. {
153. printk("beep_init");
154.
155. return platform_driver_register(&beep_driver);
156. }
157.
158. static void beep_exit(void)
159. {
160. printk("beep_exit");
161. platform_driver_unregister(&beep_driver);
162.
163. return;
164. }
165.
166.
167. MODULE_LICENSE("GPL");
168. module_init(beep_init);
169. module_exit(beep_exit);
3、makefile
[cpp] view plain copy
1. ifneq ($(KERNELRELEASE),)
2. obj-m:=device.o driver.o
3. $(info "2nd")
4. else
5. #KDIR := /lib/modules/$(shell uname -r)/build
6. KDIR := /home/fs/linux/linux-3.14-fs4412
7. PWD:=$(shell pwd)
8. all:
9. $(info "1st")
10. make -C $(KDIR) M=$(PWD) modules
11. clean:
12. rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
13. endif
4、test.c
[cpp] view plain copy
1. #include <sys/types.h>
2. #include <sys/stat.h>
3. #include <fcntl.h>
4. #include <stdio.h>
5.
6. main()
7. {
8. int fd,i,lednum;
9.
10. fd = open("/dev/beep",O_RDWR);
11. if(fd<0)
12. {
13. perror("open fail \n");
14. return ;
15. }
16.
17. sleep(10);
18. close(fd);
19. }
来源:玩转单片机
免责声明:本文系网络转载,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请第一时间告知,我们将根据您提供的证明材料确认版权并按国家标准支付稿酬或立即删除内容!本文内容为原作者观点,并不代表本公众号赞同其观点和对其真实性负责。
为您发布产品,请点击“阅读原文”