zoukankan      html  css  js  c++  java
  • linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html

    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 ... ");

    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! ");

    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] ", __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] ", __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! ");

    128     return 0;

    129 }

    130

    131 static void __exit wdt_exit(void)

    132 {

    133     platform_driver_unregister(&wdt_pdrv);

    134     printk("bye wdt! ");

    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! ");

    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] ", __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] ", __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] ");

    19     printf("func : reset interrupt start stop ");

    20     printf("[%s] ", 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

  • 相关阅读:
    docker-compose
    Cassandra
    npm常用命令
    k8s linux win10
    wsl2 docker 迁移
    docker http 代理
    mysql查看当前所有的数据库和索引大小
    mybatis 遍历list拼接 or查询
    es head crud
    nginx 代理转发mysql
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6252274.html
Copyright © 2011-2022 走看看