Chinaunix首页 | 论坛 | 博客
  • 博客访问: 103964
  • 博文数量: 38
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 21
  • 用 户 组: 普通用户
  • 注册时间: 2017-01-15 22:12
文章分类
文章存档

2017年(38)

我的朋友

分类: 嵌入式

2017-01-27 11:50:06

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中的值减一,直到WTCNT0,系统就会复位。在WTCNT还没有为0前,可以重新往WTCNT中赋值,这样WTCNT就会从新的数值开始减一,这就是所谓的喂狗操作,重复地在WTCNT0前喂狗,就可以让系统不复位。

2、如果是定时模式, 从WTCON[5]置一开始,每隔(1/t_watchdog)秒,WTCNT中的值减一,直到WTCNT0,看门狗往处理器发送中断信号,并自动将WTDAT赋值给WTCNT,让WTCNT重新开始减一。这样的重复操作就实现了看门狗的定时。

注意:不管是哪一种模式,一开始时看门狗都不会将WTDAT的值赋给WTCNT,都是直接以WTCNT的值开始计数。所以,在是能看门狗之前,必须先设备WTCNT的值,指定时间长短。


接下来的程序我需要的时间间隔是1秒,所以,Prescaler value[15:8]我赋值为99Clock select[4:3]我赋值为128S3C2440中的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设备驱动有介绍,在三处文件添加代码:

1arch/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 };

2arch/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);

3arch/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、字符设备的方法;

2io内存——ioremap

3、中断注册;

4platform设备驱动;

5、杂设备驱动;


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 11th_wdt.rar  

阅读(2070) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~