linux设备驱动归纳总结(十一):写个简单的看门狗驱动
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
设备驱动的归纳已经差不多了,趁着知识点还没有遗忘,写点代码巩固一下,来个简单的看门狗驱动——静态平台类的杂设备看门狗驱动,有定时和重启两个基本功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、S3C2440中的看门狗——具体请看s3c2440文档
看门狗应该可以算是S3C2440中最简单的一个设备的,仅仅只有三个寄存器需要配置。S3C2440中的看门狗有两种功能(只能二选一):
1、复位功能,在指定时间内,如果没有执行喂狗操作。指定时间到达后会执行系统重启操作。
2、定时功能,每隔指定的时间,看门狗就会往处理器发送中断,执行中断处理函数。
接下来就要看看控制看门狗的三个寄存器:
1、控制寄存器——WTCON:
首先要看看分频系数Prescaler value[15:8]和时钟分频Clock select[4:3]。看门狗的时钟频率就是通过这两个值分频得出的:
t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ]
每个(1/t_watchdog)秒,看门狗计数器减一。
Watchdog timer[5]是看门狗的是能控制位,初始化时必须将此为置一,不然看门狗无法操作。
Interrupt generation[2]是控制中断的产生,如果你使用看门狗来执行定时功能,置一代表使能看门狗产生中断,反之不产生中断。
Reset enable/disable[0]用控制看门狗是否复位,如果置一,代表当时间到达后系统重启。
2、数据寄存器WTDAT和计数寄存器WTCNT
这两个都是用于存放16位数据的寄存器。
1、如果是复位模式,从WTCON[5]置一开始,每隔(1/t_watchdog)秒,WTCNT中的值减一,直到WTCNT为0,系统就会复位。在WTCNT还没有为0前,可以重新往WTCNT中赋值,这样WTCNT就会从新的数值开始减一,这就是所谓的喂狗操作,重复地在WTCNT变0前喂狗,就可以让系统不复位。
2、如果是定时模式, 从WTCON[5]置一开始,每隔(1/t_watchdog)秒,WTCNT中的值减一,直到WTCNT为0,看门狗往处理器发送中断信号,并自动将WTDAT赋值给WTCNT,让WTCNT重新开始减一。这样的重复操作就实现了看门狗的定时。
注意:不管是哪一种模式,一开始时看门狗都不会将WTDAT的值赋给WTCNT,都是直接以WTCNT的值开始计数。所以,在是能看门狗之前,必须先设备WTCNT的值,指定时间长短。
接下来的程序我需要的时间间隔是1秒,所以,Prescaler value[15:8]我赋值为99,Clock select[4:3]我赋值为128。S3C2440中的PCLK定义为50000000,通过公式可以计算出:
t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ] = 3906.25
所以,看门狗的计数间隔是(1/3906.25)秒,1秒需要计数大约3906次。
看门狗介绍完毕,接着就开始写驱动了,在写驱动前贴张之前我介绍过的图,我写驱动的步骤:
接在来我就会按这个顺序来写个简单的看门狗驱动,实现看门狗的两种功能,定时和复位。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、第一个看门狗程序,代码路径11th_wdt/1st。
写驱动前先要写一些基本的操作代码,检验一下该设备是否能正常工作。
第一个看门狗程序做了三步,简单实现了看门狗的复位操作:
1、定义了一个维护看门狗数据的结构体:
9 struct _wdt_t {
10 unsigned long phys, virt; //存放物理地址和对应的虚拟地址
11 unsigned long wtcon, wtdat, wtcnt; //存放寄存器
12 unsigned long reg;
13
14 void (*init_reset)(struct _wdt_t *); //操作看门狗的函数指针
15 };
2、实现了看门狗的复位操作:
19 static void s3c_wdt_init_reset(struct _wdt_t *wdt)
20 {
21 iowrite32((int)(WDT_1S * 10), wdt->wtdat); //其实这个不设置也可以
22 iowrite32((int)(WDT_1S * 10), wdt->wtcnt); //设置10秒后系统复位
23 iowrite32(0x6339, wdt->wtcon); //设置wtcon寄存器为0x6339,使能了看门狗和复位功能
24 }
3、封装了设备的初始化和注销函数:
26 int init_wdt_device(struct _wdt_t *wdt)
27 {
28 int ret = 0;
29 //ioremap
30 wdt->phys = 0x53000000;
31 wdt->virt = (unsigned long)ioremap(wdt->phys, 0x0c);
32 wdt->wtcon = wdt->virt + 0x0;
33 wdt->wtdat = wdt->virt + 0x4;
34 wdt->wtcnt = wdt->virt + 0x8;
35
36 //function
37 wdt->init_reset = s3c_wdt_init_reset;
38 return ret;
39 }
40
41 void destroy_wdt_device(struct _wdt_t *wdt)
42 {
43 iounmap((void *)wdt->virt);
44 }
写完后编译,效果就是加载模块后十秒系统重启,看门狗的复位功能验证成功。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、第二个看门狗程序,代码路径11th_wdt/2nd。
第一个看门狗程序实现了复位操作,接下来我就要在原来代码的基础上加上看门狗的定时功能。很简单,只贴上就三个函数,其他细节请看源代码:
1、初始化看门狗,设置为定时功能,每隔一秒产生一次中断:
29 static void s3c_wdt_init_interrupt(struct _wdt_t *wdt)
30 {
31 iowrite32((int)(WDT_1S), wdt->wtdat);
32 iowrite32((int)(WDT_1S), wdt->wtcnt);
33 iowrite32(0x6338, wdt->wtcon);
34 }
2、初始话后还不能产生中断,还需要实现开启和关闭中断操作,其实就是设置寄存器的一位:
36 static void s3c_wdt_start(struct _wdt_t *wdt)
37 {
38 wdt->reg = ioread32(wdt->wtcon);
39 wdt->reg |= (1 << 2);
40 iowrite32(wdt->reg, wdt->wtcon);
41 }
42
43 static void s3c_wdt_stop(struct _wdt_t *wdt)
44 {
45 wdt->reg = ioread32(wdt->wtcon);
46 wdt->reg &= (1 << 2);
47 iowrite32(wdt->reg, wdt->wtcon);
48 }
既然是产生了中断,就要注册中断和实现中断处理函数,中断的注册操作应该放在设备初始化函数中:
50 irqreturn_t wdt_irq_handler(int irqno, void *dev_id)
51 {
52 printk("wang wang wang ...\n");
53 return IRQ_HANDLED;
54 }
55
56 int init_wdt_device(struct _wdt_t *wdt)
57 {
58 int ret = 0;
59 //ioremap
60 wdt->phys = 0x53000000;
61 wdt->virt = (unsigned long)ioremap(wdt->phys, 0x0c);
62 wdt->wtcon = wdt->virt + 0x0;
63 wdt->wtdat = wdt->virt + 0x4;
64 wdt->wtcnt = wdt->virt + 0x8;
65
66 //irq
67 ret = request_irq(IRQ_S3C2440_WDT, wdt_irq_handler, IRQF_TRIGGER_NONE,
68 "s3c2440_wdt-irq", NULL);
69 if(ret){
70 printk("request wdt-irq failed!\n");
71 goto err;
72 }
73
74 //function
75 wdt->init_reset = s3c_wdt_init_reset;
76 wdt->init_interrupt = s3c_wdt_init_interrupt;
77 wdt->start = s3c_wdt_start;
78 wdt->stop = s3c_wdt_stop;
79
80 return ret;
81
82 err:
83 iounmap((void *)wdt->virt);
84 return ret;
85 }
86
87 void destroy_wdt_device(struct _wdt_t *wdt)
88 {
89 free_irq(IRQ_S3C2440_WDT, NULL);
90 iounmap((void *)wdt->virt);
91 }
写完后编译,效果就是加载模块后每隔一秒打印出一句话,看门狗的定时功能验证成功。
注意:一般是不能加载成功的,运行命令”cat /proc/interrupt”就知道,系统中本身就注册了一个看门狗中断,为了能够执行成功,方法有两个:(之前介绍过的两男共享一妞)
1、干掉系统中的看门狗中断:
方法:配置内核时不要选上看门狗设备。
2、修改内核源代码,添加共享标记。
我这里使用的是第一种解决方法。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、第三个看门狗程序,代码路径11th_wdt/3rd。
上面已经实现了图上的三步:定义面向对象结构体,实现基本的硬件操作函数、定义设备的初始化和注销函数。
接下来就将它修改成静态平台类驱动。方法很简单,将之前放在wdt_init的操作放在probe函数中,配对成功后自动调用,将之前放在wdt_exit的操作放在remove函数中。
贴上部分代码:
97 static int s3c_wdt_probe(struct platform_device *pdev)
98 {
99 printk("[%s]\n", __FUNCTION__);
100 my_wdt.phys = pdev->resource[0].start;
101 my_wdt.irqno = pdev->resource[1].start;
102 init_wdt_device(&my_wdt);
103 my_wdt.init_interrupt(&my_wdt);
104 my_wdt.start(&my_wdt);
105 return 0;
106 }
107
108 static int s3c_wdt_remove(struct platform_device *pdev)
109 {
110 printk("[%s]\n", __FUNCTION__);
111 my_wdt.stop(&my_wdt);
112 destroy_wdt_device(&my_wdt);
113 return 0;
114 }
115
116 static struct platform_driver wdt_pdrv = {
117 .probe = s3c_wdt_probe,
118 .remove = s3c_wdt_remove,
119 .driver = {
120 .name = "s3c_wdt_xb",
121 },
122 };
123
124 static int __init wdt_init(void)
125 {
126 platform_driver_register(&wdt_pdrv);
127 printk("hello wdt!\n");
128 return 0;
129 }
130
131 static void __exit wdt_exit(void)
132 {
133 platform_driver_unregister(&wdt_pdrv);
134 printk("bye wdt!\n");
135 }
136
137 module_init(wdt_init);
138 module_exit(wdt_exit);
既然说是静态平台类驱动,那就是需要修改内核代码,方法在linux设备驱动归纳总结(九):1.platform设备驱动有介绍,在三处文件添加代码:
1、arch/arm/mach-s3c2440/mach-mini2440.c
250 static struct platform_device *mini2440_devices[] __initdata = {
251 &s3c_device_usb,
252 &s3c_device_rtc,
253 &s3c_device_lcd,
254 &s3c_device_wdt,
255 &s3c_device_led,
256 &s3c_device_wdt_xb, //这是我新加的
257 &s3c_device_i2c0,
258 &s3c_device_iis,
259 &s3c_device_dm9k,
260 &net_device_cs8900,
261 &s3c24xx_uda134x,
262 };
2、arch/arm/plat-s3c24xx/devs.c
379 /* Watchdog xiaobai*/
380
381 static struct resource s3c_wdt_xb_resource[] = {
382 [0] = {
383 .start = 0x53000000,
384 .end = 0x530000ff,
385 .flags = IORESOURCE_MEM,
386 },
387 [1] = {
388 .start = IRQ_S3C2440_WDT,
389 .end = IRQ_S3C2440_WDT,
390 .flags = IORESOURCE_IRQ,
391 }
392 };
393
394 struct platform_device s3c_device_wdt_xb = {
395 .name = "s3c_wdt_xb",
396 .id = -1,
397 .num_resources = ARRAY_SIZE(s3c_wdt_xb_resource),
398 .resource = s3c_wdt_xb_resource,
399 };
400
401 EXPORT_SYMBOL(s3c_device_wdt_xb);
3、arch/arm/plat-s3c/include/plat/devs.h
27 extern struct platform_device s3c_device_dm9k;
28 extern struct platform_device net_device_cs8900;
29 extern struct platform_device s3c_device_fb;
30 extern struct platform_device s3c_device_usb;
31 extern struct platform_device s3c_device_lcd;
32 extern struct platform_device s3c_device_wdt;
33 extern struct platform_device s3c_device_led;
34 extern struct platform_device s3c_device_wdt_xb; //这是我添加的
35 extern struct platform_device s3c_device_i2c0;
36 extern struct platform_device s3c_device_i2c1;
37 extern struct platform_device s3c_device_iis;
修改后重新编译内核,加载模块后,内核会自动调用probe函数,具体效果就是每隔一秒打印一句话。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、第四个看门狗程序,代码路径11th_wdt/4th。
接下来就要注册杂设备类驱动,并且提供ioctl命令给用户空间控制看门狗。
先要在头文件中实现几个命令:
/*11th_wdt/4th/ioctl_wdt.h*/
1 #ifndef _WDT_H
2 #define _WDT_H
3
4 #define MAGIC 'x'
5 #define WDT_RESET _IO(MAGIC, 0) //产生一个数字
6 #define WDT_INTERRUPT _IO(MAGIC, 1)
7 #define WDT_START _IO(MAGIC, 2)
8 #define WDT_STOP _IO(MAGIC, 3)
9
10 #endif /* _WDT_H */
再看看ioctl的实现,很简单,四个命令对应四个不同的操作:
112 int s3c_wdt_ioctl (struct inode *node, struct file *filp,
113 unsigned int cmd, unsigned long args)
114 {
115 switch(cmd){
116 case WDT_RESET:
117 my_wdt.init_reset(&my_wdt);
118 break;
119 case WDT_INTERRUPT:
120 my_wdt.init_interrupt(&my_wdt);
121 break;
122 case WDT_START:
123 my_wdt.start(&my_wdt);
124 break;
125 case WDT_STOP:
126 my_wdt.stop(&my_wdt);
127 break;
128 default:
129 printk("unknow ioctl cmd!\n");
130 return - EINVAL;
131 }
132 return 0;
133 }
134
135 static struct file_operations wdt_fops = {
136 .ioctl = s3c_wdt_ioctl,
137 };
接着是实现杂设备类驱动:
139 /*******************
140 * misc class *
141 *******************/
142
143 static struct miscdevice wdt_misc = {
144 .name = "s3c_wdt",
145 .fops = &wdt_fops,
146 };
147
148 /*******************
149 * platform总线操作*
150 *******************/
151 static int s3c_wdt_probe(struct platform_device *pdev)
152 {
153 printk("[%s]\n", __FUNCTION__);
154 my_wdt.phys = pdev->resource[0].start;
155 my_wdt.irqno = pdev->resource[1].start;
156
157 init_wdt_device(&my_wdt);
158 misc_register(&wdt_misc);
159 return 0;
160 }
161
162 static int s3c_wdt_remove(struct platform_device *pdev)
163 {
164 printk("[%s]\n", __FUNCTION__);
165 misc_deregister(&wdt_misc);
166 destroy_wdt_device(&my_wdt);
167 return 0;
168 }
最后就可以写个应用程序验证一下了:
/*11th_wdt/4th/app.c */
8 #include "ioctl_wdt.h"
9
10 int main(int argc, char *argv[])
11 {
12 int fd;
13
14 fd = open("/dev/s3c_wdt", O_RDWR);
15 if(fd < 0)
16 perror("open");
17
18 printf("./app [func]\n");
19 printf("func : reset interrupt start stop\n");
20 printf("[%s]\n", argv[1]);
21
22 if(!strncasecmp("reset", argv[1], 5))
23 ioctl(fd, WDT_RESET);
24 if(!strncasecmp("interrupt", argv[1], 9))
25 ioctl(fd, WDT_INTERRUPT);
26 if(!strncasecmp("start", argv[1], 5))
27 ioctl(fd, WDT_START);
28 if(!strncasecmp("stop", argv[1], 4))
29 ioctl(fd, WDT_STOP);
30
31 return 0;
32 }
编译后验证一下,可以使用./app reset启动复位功能,也可以./app interrupt & ./app start启动定时功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
六、第五个看门狗程序,代码路径11th_wdt/5th。
看门狗的驱动就基本完成了,最后还有个功能要补充,喂狗,直接上代码:
65 static void s3c_wdt_feed(struct _wdt_t *wdt)
66 {
67 iowrite32((int)(WDT_1S * 10), wdt->wtcnt);
68 }
然后稍稍修改一下其他代码就可以了。
编译后运行,执行./app reset后,只要在10秒内执行./app feed,系统就不会重启。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
七、总结
简单的看门狗驱动基本上就完成了,当然还有很多需要完善的地方,如设定定时的时间等。具体包含了以下知识点:
1、字符设备的方法;
2、io内存——ioremap;
3、中断注册;
4、platform设备驱动;
5、杂设备驱动;
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx