找回密码
 注册
关于网站域名变更的通知
查看: 219|回复: 1
打印 上一主题 下一主题

基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-6-15 10:53 | 只看该作者 |只看大图 回帖奖励 |正序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
本文将介绍看门狗驱动的实现。8 A9 J3 O& S  `1 [1 v1 H

# {. F/ @0 ?8 _7 t5 ~目标平台:TQ2440
! D7 t& k+ b! ~: T) k8 x& _$ i7 G& M4 p/ v" n3 R; X
CPU:s3c2440- |+ F( \$ R. m% A) d0 [3 I/ u: m% P

" x# n% |; T0 B5 r5 |1 X内核版本:2.6.30" W9 X' F; \. c2 S
+ Z  N( G- b4 I, u- G  i- ^) q
3 `2 \; Q7 c* X/ _: `- S4 R9 h! {
* W2 \% \9 W2 K- m! s; o0 q; m
1. 看门狗概述: s0 u5 W  i$ l- T
   看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。# Q0 c5 E- w  e5 [& N# ~# d# `
. I, |+ l) I0 h; t0 ^
   因此,看门狗通常用于对处于异常状态的CPU进行复位。2 n/ N: V% u; H: q4 @* R2 e

8 j( m6 {- N; t& g& x   具体的概念请自行百度。
9 b9 s. G5 O' h5 D/ Y8 B8 I) K$ l  a: S" V8 @. w, k3 I9 E/ }9 h# b
0 Q, C8 i! k# x9 Z4 a
2. S3C2440看门狗; v2 q  a; ^/ k" ~2 E
   s3c2440的看门狗的原理框图如下:6 S7 r0 t- b( _6 B
  t$ R* Q$ Q" s9 B
" I( Y4 R6 @+ U; ^

7 _9 W6 D* s7 q7 Q. s+ v  可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。% m! k$ X. W$ ?! z2 K0 a9 y
8 s7 z! J- |) z5 a
  定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。- M, G" e6 v3 I/ ~4 R7 o9 v* |

% Y7 y2 ~/ I8 X/ A& G! x4 y: z  看门狗定时器的频率的计算公式如下:
% f$ N6 R% Z# A, }" s6 f* [1 Z
; V! T$ a7 u3 c7 V" J/ k/ _& m + R5 i* `+ i( g
* H5 v+ ]) q) [$ ]% x: ?
' K, `5 G9 M+ l0 _' a4 P# N1 h
! U% _% c; k4 J  A3 o4 h: {
3. 看门狗驱动& d9 o- G# |: k
  看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c; C8 a9 i5 ]% A) L. T& W  t5 ?$ v/ X

  a; u  G  t5 {2 [3.1 模块注册以及probe函数: Y/ ^% _! r7 E7 K. o9 e
static struct platform_driver s3c2410wdt_driver = {
/ X" R2 |& T" X! ^( o+ m' A    .probe        = s3c2410wdt_probe," e5 z0 \  V9 P  E7 Y
    .remove        = s3c2410wdt_remove,
% m! v: z  `% `3 h    .shutdown    = s3c2410wdt_shutdown,
  R5 l  K) o; R$ G# E6 V    .suspend    = s3c2410wdt_suspend,: G9 M% v2 o& a- T1 H+ Z9 k
    .resume        = s3c2410wdt_resume,% |: l  {8 H' r% K
    .driver        = {0 ?  X5 n3 H0 M- T
        .owner    = THIS_MODULE,) [. Q9 V) e( w
        .name    = "s3c2410-wdt",
( \2 v+ g8 [- t1 @1 n; T    },/ L: r  c+ R4 D! k0 R2 {( G
};
: z8 K6 z* r$ ]$ L, c8 [0 F- `; a: m. k+ I
static char banner[] __initdata =6 Y$ Q: w0 E3 L# \0 D$ e. j
    KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
, n7 Z4 Z5 Q& C$ w- x7 C0 n+ }/ L8 ~$ M' z9 P
static int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}& `+ b: n. N1 Y9 V5 K

  `! B2 ~  p0 N# x7 f2 ~3 ymodule_init(watchdog_init)
7 Y; Z( [. H5 Y; D* O2 _  F模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。
' O/ m3 A! I$ B9 d
- c5 Z# O) P$ _) N% e8 R3 u( @3 Z+ U 该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。
$ ]5 ~+ _3 ~1 H$ |
( U2 G' A) O% ~" D, ~  s8 b6 _ 我们来看下这个函数:
0 u0 t0 G1 v# R. {$ d# G0 U, T7 {$ S
static int s3c2410wdt_probe(struct platform_device *pdev)
2 A/ B5 I. b2 P{" p* e" x+ F. c- A' v$ u, y
        struct resource *res;  |& F0 y" X! g! l; c9 {
        struct device *dev;
; e0 k( B4 h. ^# u        unsigned int wtcon;0 M8 R: F+ s- k, b/ g" d* j2 V
        int started = 0;
; d- [% s& i& [" a9 Z        int ret;
# y% h3 @& W& s3 b        int size;9 p4 U7 i: B& v: h6 J8 n" O
0 j+ V) I+ G- _8 N
        DBG("%s: probe=%p\n", __func__, pdev);& r  ^, M' }% R

0 [2 _* @4 B  o/ L        dev = &pdev->dev;# I, |, U- [9 P- ?6 a/ A
        wdt_dev = &pdev->dev;! e3 M* W( s6 G$ X" U) D

0 {+ \+ x. U, T  Q" w1 z  n        /* get the memory region for the watchdog timer */5 o$ \1 N% l+ Y
        /*获取平台资源,寄存器地址范围*/
0 ?' D0 B9 S' }% R) C* B        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);. ?/ D' Z" o5 x3 @. Z
        if (res == NULL) {& m1 G% w: d) `$ a/ I+ G) u
                dev_err(dev, "no memory resource specified\n");) c( g1 j4 U: [2 d7 d$ B' D# @( K8 F/ \
                return -ENOENT;3 f: t$ g* ?& c" S* ], U. l
        }
6 _  n8 c- q/ ^. f3 R9 x
+ A! G6 O; j5 u% [3 U) n3 V5 Z0 J        /*内存申请*/
) o$ [: G* C% }0 y        size = (res->end - res->start) + 1;
$ }: P. A- ^4 }6 \4 h& r        wdt_mem = request_mem_region(res->start, size, pdev->name);
7 C/ b& d8 u3 z, M: E* {4 Q* p( h0 I+ [        if (wdt_mem == NULL) {$ k% t3 K0 T+ _5 Q: b8 s- Q
                dev_err(dev, "failed to get memory region\n");% T+ `5 D' B) n1 `8 Z
                ret = -ENOENT;- M* x( r: z* P' j0 W
                goto err_req;) ^  J, O5 D! t5 W
        }7 i# J# ~* R$ Q1 |" t0 D: h

, W1 R  m" A* W- q        /*内存映射*/
4 R$ |: O- H, l" S        wdt_base = ioremap(res->start, size);: ?2 t! v% [6 _
        if (wdt_base == NULL) {# c$ C7 L! i% G7 n. z/ b( k, T+ {
                dev_err(dev, "failed to ioremap() region\n");0 _7 `- Q( q5 ]. \! f
                ret = -EINVAL;
9 L( C- `4 ~  W+ |+ y  X6 y! r3 S                goto err_req;
3 J7 T( f; j# y$ @2 w' b. S        }3 t, _. _5 N, v* U; H9 V' d5 ?
$ z+ t3 ?9 ]# A. j4 u* [( N
        DBG("probe: mapped wdt_base=%p\n", wdt_base);
' @: [& c, F" A( Z7 G" [6 q        $ q$ v4 Z9 b( \( r. F, ]! y
        /*获取平台资源,看门狗定时器中断号*/2 \; g3 u/ g3 d, c
        wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);; I/ r9 H7 |9 [( H; J( p
        if (wdt_irq == NULL) {
' C, f+ P& \4 m: M# A- w8 r                dev_err(dev, "no irq resource specified\n");. y- @# x! S8 X2 G
                ret = -ENOENT;
" b9 ]. @/ g6 _4 J, v. x                goto err_map;9 F) B0 G7 o" V& x2 o
        }, J$ p' [# h+ A0 ^' F( D
        6 P! J% R7 K9 }  s
        /*注册看门狗定时器中断*/
! f. R7 u0 y4 g$ q8 k! K# p  q" y        ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);/ f  Y5 Z; m0 [; q  j) t! p$ ^
        if (ret != 0) {
4 G4 F. Z1 o( o- g                dev_err(dev, "failed to install irq (%d)\n", ret);5 s/ M2 [8 c* q: i$ @' K' f
                goto err_map;
6 q9 o+ v5 J* `6 c/ G! ^: r( D        }6 l6 j4 g; e% [
        /*获取看门狗模块的时钟*/
5 u/ V' s7 n5 |! A- y        wdt_clock = clk_get(&pdev->dev, "watchdog");
0 r+ a' |: B* |7 ^, b& h: |+ x        if (IS_ERR(wdt_clock)) {
! I% _9 f7 T4 u; c- w7 @! r                dev_err(dev, "failed to find watchdog clock source\n");: H* m6 A3 }0 B- o8 F
                ret = PTR_ERR(wdt_clock);$ w2 Z6 z0 X! ^% @
                goto err_irq;
, N: }5 p6 {1 w  }7 C0 [( T4 a8 D$ U        }* ^8 O4 p% @+ V

7 x- N- @3 \0 r3 c0 [' z7 B) Q0 |( P        /*使能该时钟*/
0 {6 O# w( {: X8 ]        clk_enable(wdt_clock);
: ^1 |2 o( I, ]! q/ L
& v: @6 Q3 D" W8 _3 I0 T        /* see if we can actually set the requested timer margin, and if$ \1 ~) A! j! {
         * not, try the default value */
* z) [/ x' R/ w0 w! E" P
7 k/ C9 u5 S8 p0 k        /*设置定时器模块的时钟频率*/% z* S# W$ `; j
        if (s3c2410wdt_set_heartbeat(tmr_margin)) {; l! N' ]2 i7 q6 r
                started = s3c2410wdt_set_heartbeat(
! b6 H  o  @9 [                                        CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);  N# v  d: p& _+ E- Z
  y6 e; r1 [& o
                if (started == 0)% u& e" ^6 `( o& A- `( v0 e
                        dev_info(dev,
7 @: b0 m  V6 P" c7 w0 h" u* [                           "tmr_margin value out of range, default %d used\n",5 I* z4 Q4 M4 V7 V+ P4 t  @3 j
                               CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
$ O: s* s  g" h  y  d0 H9 B  s                else
/ e! c  K" H. r3 \) z  ^                        dev_info(dev, "default timer value is out of range, cannot start\n");  b9 _( X5 a$ u
        }
9 s( m+ w6 g% O: P
# G2 O, t, o8 C: [; h4 M- H. c        /*注册混杂设备,设备名watchdog,次设备号130*// z' n6 f  J8 D5 V
        ret = misc_register(&s3c2410wdt_miscdev);9 G8 {0 L7 w9 C( h$ M8 D4 F
        if (ret) {- p2 {2 {% {% \7 ~* f: R/ U
                dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",1 a* ^- y5 r+ ^3 a
                        WATCHDOG_MINOR, ret);6 f6 Q& \" Y- _2 K0 q* g
                goto err_clk;' e3 p* B4 k# y! Y0 L
        }4 o+ S; Z  q  }/ C5 x/ K

: `# l# _5 `$ `8 M* z$ B) a        /*7 R- a5 X2 d+ P/ {
          *如果需要在看门狗模块加载时启动看门狗则- M. r  U9 }3 d2 y- a
          *调用s3c2410wdt_start,否则调用s3c2410wdt_stop" F3 T: G  p' t4 |) _& o& E( i
        */
6 ?, d4 ~. I; R" ^5 b2 |        0 y# F  E2 _2 ]  W# L+ W6 C
        if (tmr_atboot && started == 0) {# k+ v* [. @& }/ T
                dev_info(dev, "starting watchdog timer\n");
+ r8 x3 X- H, l% i  ~" x( r                s3c2410wdt_start();
- K# p8 F9 {3 r7 p) g0 f+ ?        } else if (!tmr_atboot) {
9 i, n' {1 Y  `- I! L8 A: |                /* if we're not enabling the watchdog, then ensure it is( C5 R  h; g: j! |
                 * disabled if it has been left running from the bootloader. c  h+ T% l$ O& V$ Y
                 * or other source */$ Q$ ]7 I* J+ M  ?
" P3 }- l% d' [" c2 T4 n# ?
                s3c2410wdt_stop();
% E8 _  \6 O$ L$ Y        }
7 \1 y% r+ o' E$ |7 O- S- L" V6 I; T
* D; i1 K8 w$ c1 O/ c        /* print out a statement of readiness */
2 w7 Y; E7 b" d# I" Z8 p        /*读取控制寄存器,打印目前看门狗的状态*/: Z) K; S0 o2 h8 C1 K
        wtcon = readl(wdt_base + S3C2410_WTCON);9 r8 [, O  k+ Y' f! H
1 |0 t; L% w4 ?0 p0 U) p8 t) p+ B
        dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",7 W0 J% H2 I9 a8 A7 D& D
                 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
: e1 a. ~  {" V  g$ \" U                 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",) n1 ~' o+ Y# C
                 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
8 J3 k8 u5 z1 ]' T0 l
/ E3 ?6 i! B, g        return 0;
! U" W) Q4 c, e0 u: u7 M  A* Y5 h1 I+ `+ R! X& p* |+ I8 [) \
err_clk:
) f! r) z& K2 R5 h        clk_disable(wdt_clock);! `2 p7 m7 @8 C2 s
        clk_put(wdt_clock);
$ J4 P/ t2 \9 `  f, ?1 L0 }, U; L3 f( A0 H8 d
err_irq:
9 L" N( D1 C0 l6 C, ?) a        free_irq(wdt_irq->start, pdev);3 a1 O$ F+ u+ p; X
& e1 C" o' K' X; ~9 Z
err_map:
$ _0 {/ S, i" z% m. |        iounmap(wdt_base);
' p. j% \! _% Z
0 b4 b! l$ V3 D6 B" [  ^ err_req:4 h% N# K8 K9 ~( c8 p0 `' w5 s
        release_resource(wdt_mem);
2 C8 H( _( m$ d% j6 z. S( q! e  `        kfree(wdt_mem);
! G& g7 m. f# @4 C$ E5 m% K+ i$ V& K
        return ret;
2 ?( L8 a0 B  Y$ `9 Y* a}
! \/ a$ p5 @5 a8 ]) D7 D: N7 q4 ?
, b' i; d8 w  Y% G( L3 L* Q该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。0 \- ~' A) b( o0 Y
3 Z; l, T5 l) a7 O; Y5 R5 y
然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。
+ B* |( ^2 T* e+ {( U) t% U9 D, q* j5 k$ l" `' `
上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。% Q4 z+ X% p1 j6 Q4 {7 f8 i
$ a4 |4 Y3 T3 v) o( o  {# B, N- o
PS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/ARM/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。
$ e# o3 y( K% H0 U/ c+ O. P% W/ d& z7 U. g
3.2 s3c2410wdt_set_heartbeat
3 ~+ g8 T( c. |  g5 A2 D+ w9 Kstatic int s3c2410wdt_set_heartbeat(int timeout)  /*timeout 超时时间,单位秒*/
$ g& Y$ f  C9 ?. w0 U* P{
2 N# p$ N& |- i- a+ W' U" y0 A        unsigned int freq = clk_get_rate(wdt_clock);
3 w# b: w$ k6 ?        unsigned int count;5 [+ P6 k+ Z0 p  f, L
        unsigned int divisor = 1;6 \, O8 ]" |, D* M5 e8 t
        unsigned long wtcon;9 t) @) ?. {% P; ^/ ~
# t& x8 p. V% I
        if (timeout < 1)+ Z' ?1 O. f) I
                return -EINVAL;8 O: H# a4 ~3 u- e  C7 o
* i( s- j# M) x
        freq /= 128;    /*时钟源为PCLK/128,不使用16 32 和64*/4 i( A: `  K! Z; f4 A3 {" b
        count = timeout * freq;  /*得出计数器值*/
3 Y) h* e* l# P6 R
1 e; e& B: N1 s  n$ m        DBG("%s: count=%d, timeout=%d, freq=%d\n",$ W* R; H% p7 y3 D3 [3 c
            __func__, count, timeout, freq);
: z/ ]$ \# P3 ?6 W5 N. u6 c- q- ?( G0 P1 C; Z2 \
        /* if the count is bigger than the watchdog register,
- U- G; v+ B; T0 ]( L. d           then work out what we need to do (and if) we can
2 n4 ], J) `/ k. O1 @           actually make this value
$ r) \. c( n9 f        */2 A0 }: B% Z9 u2 |, j
        /*计数器最大值为0xFFFF,如果大于则要计算Prescaler value*/
' d1 p# U' o! q, B3 M) b% q' ]7 x: U        if (count >= 0x10000) {; o7 g( _% H3 V* r: S6 g
                for (divisor = 1; divisor <= 0x100; divisor++) {     /*Prescaler value最大为0xff*/; I8 X- h# w2 _4 x2 Z+ X8 k7 M
                        if ((count / divisor) < 0x10000)
' F2 M; L7 \: B' P* J                                break;
( h  c) O; r  ^1 h1 |2 f                }
  F. @. `) S& ]' ~5 X; r! E        /*找不到合适的Prescaler value,报错返回*/
% M6 B3 y# ?5 Z4 d+ h  e8 s! v                if ((count / divisor) >= 0x10000) {+ L% K4 t  C. M
                        dev_err(wdt_dev, "timeout %d too big\n", timeout);
9 j+ d$ f& b& T1 T3 ?                        return -EINVAL;
, k  G" j# Q. k$ W6 R; K6 y! k                }' g9 F* f. W+ {: v+ l
        }6 q2 Q' J( _! s; A9 z

! o# H& q5 h3 b8 L3 ~. i        tmr_margin = timeout;  /*保存timeout*/' v* |* M. P" v6 C8 n
: A# U+ {$ n, L' y- o
        DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
. D0 O( U$ l0 G" {7 ]6 D$ v            __func__, timeout, divisor, count, count/divisor);
0 n) m7 B6 r( Q( r' {1 h! h4 j* ^' L7 E  S
        count /= divisor;   /*根据Prescaler value计算出新的计数器值*// `1 p. j$ F- K- m$ u4 \
        wdt_count = count; /*保存计数器值*/
" }6 Z9 [: R7 b/ K6 R% S6 p
* X. p0 ?; e" t' M        /* update the pre-scaler */
* v5 P& _! N5 b- P        /*) J, P2 x# d, E+ N9 m
          *设置预分频计数器和数据寄存器
$ ]- g1 w  x9 K, c& J1 ?" N8 r# U( L        * NOTE:此时并未使能看门狗定时器
- o. a3 @1 ?7 E; j; u. d' o           */1 v! \' [4 t* ^1 s1 q3 L
        wtcon = readl(wdt_base + S3C2410_WTCON);
2 f# n. L8 {$ i8 V        wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
# D$ V+ Y/ S) m' _, V( }6 ~/ p        wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);7 V5 a# Y  W7 [3 `5 h6 c1 O
$ L) L7 S8 G8 @/ h" p) w
        writel(count, wdt_base + S3C2410_WTDAT);: l8 r, Q. u4 b; v
        writel(wtcon, wdt_base + S3C2410_WTCON);
" U0 P, J: w" o! X) w1 E# I6 n1 t  A0 \$ W1 b" z$ y' L' K% X
        return 0;
8 N# G) X+ a- C) p: @# j7 ]2 H}2 y1 J& Z6 L/ E, C/ f' Y
/ e+ e  ]6 \0 c/ M
形参timeout为定时时间,单位为秒。
/ H+ i! l$ j+ |# X2 t' g# ], Y$ I2 R' `: M
  这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。
) [! l; b7 s3 E$ i% N0 h8 _7 c! W4 o/ N  I9 V6 ]2 s# f- c. u+ @
  在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。. v3 d; J+ N7 M5 Z8 t, `$ v, }+ r8 L

/ d6 H7 Z( |% r* }  当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。
# P. k& J# h8 m1 |, A- {- I' |6 U8 W9 ^5 l
3.3 定时器的启动、停止和保活1 e5 M+ {4 W9 H* n% C1 S; P
3.3.1 停止/ X3 w2 L) n( k; V
定时器的停止由 s3c2410wdt_stop函数完成。2 s# m3 R* J& I  U$ h

/ R6 `3 ^0 ^. s2 i  Wstatic void __s3c2410wdt_stop(void)* X+ x4 L8 Q. ]" W" l
{
8 W/ k1 A' g. [" |6 r& a+ ^        unsigned long wtcon;
8 W3 h! ^8 [' g+ @
& u9 |2 v( F: k- y7 [. R        wtcon = readl(wdt_base + S3C2410_WTCON);9 o: Z+ M; o1 H, `
        /*禁止看门狗,禁止RESET*/
6 |; l3 I/ l- m( A+ x        wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
3 h; q: ]- e  X, ]/ u) o        writel(wtcon, wdt_base + S3C2410_WTCON);: t; z/ y3 e8 ?7 ?
}
& O) k( @3 m# p- h$ l' K& |
  D' R) A3 J8 G0 l3 Xstatic void s3c2410wdt_stop(void)
  q: p2 D& q. I/ E' S" p8 [{
: P, V7 n9 E: i( G% w. A" h    spin_lock(&wdt_lock);' x7 T4 J. g! p  P8 E9 e, ?
    __s3c2410wdt_stop();; b' F- d( F" R  S! w
    spin_unlock(&wdt_lock);
2 t7 r$ j; s* x0 q9 s- R( _}  h# C* L; _  m; O; o! A

, W2 d* n4 Z3 ~$ k. _/ c3.3.2 启动
& g& L7 f4 }; @* a0 ]! i3 v定时器的启动由s3c2410wdt_start函数完成。2 V3 t, \" x2 W  L3 e  W

( [  j9 A1 S5 r8 _; Ustatic void s3c2410wdt_start(void)5 @* B  B" L( \+ @
{! y, d) q) z* ]1 d4 e6 N  @
        unsigned long wtcon;
5 t7 \* p/ i: g9 V% D4 E* i6 V+ k- g" L4 X/ k  r5 w6 }1 u
        spin_lock(&wdt_lock);) i% t4 c% |4 p4 g6 t. h
* G# |' B) ~) [. B5 ]
        __s3c2410wdt_stop(); ./*先禁止看门狗*/
3 \* W' I3 [+ |  R  [) Y) Y8 ?8 k! C0 c
        wtcon = readl(wdt_base + S3C2410_WTCON); /*读取控制寄存器*/& k# t6 x* u) r; Z) L6 O" u6 j
        /*启动定时器,设置分频系数为128*/, x  S+ p- E5 d) [6 o# O) ^0 _
        wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 3 n1 c( c5 W0 n. X& o  M  _

: k/ j. B0 ^) c1 @' P        if (soft_noboot) { /*判断许是否需要RESET*/5 `6 A' j6 m. j& l' `, L7 Y
                wtcon |= S3C2410_WTCON_INTEN;          /*使能看门狗中断*/. |5 j1 M4 `0 m' q" E  Z
                wtcon &= ~S3C2410_WTCON_RSTEN;        /*取消RESET*/, C# l; N& Z8 V0 I- N$ i2 k
        } else {  /*复位*/
8 r- `5 P/ {; S5 ^) p& F4 F                wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看门狗中断*/- b; w$ p/ g3 x4 \- K, D
                wtcon |= S3C2410_WTCON_RSTEN;  /*设置RESET*/
6 d' h4 X" H) G& }. N! K; _        }5 E  ~9 Y+ Z1 `

' p# Q% c7 i9 s% g2 m        DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
3 F- v, c8 F. R& t0 T6 s5 R            __func__, wdt_count, wtcon);
* E3 g) F. U& K8 M" {% E9 ?2 c" [! E( ]
        writel(wdt_count, wdt_base + S3C2410_WTDAT);& J* C9 o$ @# m6 c6 \
        writel(wdt_count, wdt_base + S3C2410_WTCNT);
3 ]3 b: C  H, l2 x4 x        /*写入控制器,此时将启动看门狗定时器*/& X% W6 s( ~0 k1 v0 @$ x
        writel(wtcon, wdt_base + S3C2410_WTCON);        
) p1 O" E: u$ x; }& u        spin_unlock(&wdt_lock);4 @$ [5 T4 Z6 G2 V8 o, X) d8 Q
}' o6 d; T; F& W$ X
在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。
& B! G! g8 O3 V& |7 l# z0 ]: @# G. ^  x
3.3.3 保活& z: b+ o, j: E: p/ O7 w
定时器的保活由s3c2410wdt_keepalive函数完成。
# {. t) V0 U  |9 F/ f, ?, t; f9 Z5 X- E; ~" |4 o" p+ M
static void s3c2410wdt_keepalive(void)
' i9 O/ @9 G0 `+ }' G{, B" H2 N/ ^6 r% ~! \
        spin_lock(&wdt_lock);& t/ D* j) x! ]/ [# v9 K
        writel(wdt_count, wdt_base + S3C2410_WTCNT); /*重置计数器值*/* W' B/ X/ i+ K; o
        spin_unlock(&wdt_lock);
7 s0 S9 G  j- z0 Y0 ^}
- e3 _& g3 b0 ~) H4 }最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。
/ Z1 D9 f( ^0 b  t: J
3 y" l- Z  Z1 ]3 N! O5 B2 `% F$ ~2 W- g* ?; [
3.4 看门狗驱动API  C4 t2 U7 D4 }  @, z) k
看门狗驱动提供的API如下:
1 j! A; q' `. j" w8 d, `' f
! r) z- H# b4 N- nstatic const struct file_operations s3c2410wdt_fops = {# t! p  }  u* a
        .owner                = THIS_MODULE,- n* |! y4 q, z; E8 \! x! M! k  ^
        .llseek                = no_llseek,
. ^" O- S! l' Y/ u, H        .write                = s3c2410wdt_write,5 R- C; ]( Y9 E' u- F
        .unlocked_ioctl        = s3c2410wdt_ioctl,
7 @1 r% d5 j& \        .open                = s3c2410wdt_open,
( V: [, o( k6 ?) Q) H7 v2 v        .release        = s3c2410wdt_release,9 Y6 Z+ V; E- j. `% A# B9 G
};
3 d+ \* q2 b% Z4 Z2 t4 |& N. {9 I4 j) U3 f/ E
我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。# [- a% b; d+ h6 `/ O
3.4.1 open方法7 u* e. O& }' d9 j' t$ T7 C
static int s3c2410wdt_open(struct inode *inode, struct file *file)
) J% k; x% \8 j( y{
" J) D% r7 x# }( _' r/ h$ D- D        if (test_and_set_bit(0, &open_lock))/*看门狗设备文件只能open一次*/
# W8 x  _* v; o                return -EBUSY;6 }5 U8 ?0 X! p7 Q- j" }
3 B7 _, k, D" Y
        if (nowayout)
6 ^- M* a& a3 Z                __module_get(THIS_MODULE); /*增加模块引用计数*/
5 e# {3 g2 x2 W$ q- F8 c
% q; f9 I' |4 r8 _) j1 K        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/
* k- {1 I( k6 D) \1 Y# E: x2 Z! ?/ d$ K0 O' y" p5 ?
        /* start the timer */9 |# k; Y! V6 }. |) F* d
        s3c2410wdt_start();                /*启动定时器*/  N: f6 M3 P3 Z0 C) l
        return nonseekable_open(inode, file);  /*告知内核不支持llseek操作*/
3 X* I+ f' N3 T) m$ m}/ Q, ^5 r5 {" o2 p

$ @6 K9 c6 k% q2 g5 g这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。* Y% p4 @# l. |$ a6 ]$ n6 v. K6 g% L
3.4.2 release方法
$ O2 R$ D  Z# R: ^static int s3c2410wdt_release(struct inode *inode, struct file *file)
1 B" D3 C9 ^% V{
1 }6 @8 \3 ]8 Z) ~6 D2 x5 Q        /*6 H" a8 D1 k' }  t7 _. W
         *        Shut off the timer.# E0 ]8 k5 w- E( Q9 Q) V0 F/ f
         *         Lock it in if it's a module and we set nowayout
  `  F9 v: {3 {0 k- K# r% R( l7 O         */
3 P. G7 v9 v+ B" g4 z          /*状态是允许关闭看门狗,则停止看门狗,否则保活*/; q  O. ]; Q% D
        if (allow_close == CLOSE_STATE_ALLOW)
! X! l; y7 y% J9 G7 |5 w5 Z2 V                s3c2410wdt_stop();  U2 i# l" B  m$ S2 a6 I6 P
        else {
* \4 ^' ^8 o3 Z5 K2 s                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");$ @8 S8 Z/ P& {% x& g3 g  h4 ~
                s3c2410wdt_keepalive();
! e' K/ B2 d& ~& {' \( |. o3 w        }
  v4 E) D; o$ O$ _2 L7 v# [2 q4 e        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/" _& W& L! q9 D4 j$ Y/ v
        clear_bit(0, &open_lock);/ V; ~. Y" [$ q, v2 I3 s0 |9 L2 |- q. Z
        return 0;
& v; {2 ^# ?) X; q9 ?: D; u4 q# V}
; q' t2 F! V, u* Y" c6 V3.4.3 wirte方法
/ t* l, B7 t* F$ Y8 [* y3 b) Cstatic ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
9 Z# ^5 b. \& D) p                                size_t len, loff_t *ppos)- B0 g6 G8 S5 `; b. J5 Z" R* @( p
{
% T  S% N& {' F) u9 m        /*0 T* L2 z: e# s& b7 w
         *        Refresh the timer." s' h, O; C* p# r- n3 Z5 t4 N
         */
6 C/ v: r9 l# }3 N3 k) V2 M        if (len) {$ [' v+ q% U1 R. ]2 ^
                /*nowayout 为真,不允许看门狗停止,使其保活*/
: Y8 Y6 f5 r% A7 z. _                if (!nowayout) {
. H5 p( ]6 o- I3 V2 N1 x4 }* v                        size_t i;" @% Q/ j3 i% K! p, Y

1 J, n( T- r3 {$ A+ x/ g; K) n; b                        /* In case it was set long ago */
& S( o  X, V* d# d9 j" s3 L                        allow_close = CLOSE_STATE_NOT;/*设置标志位,不允许关闭看门狗*/
, n3 D+ P' m! ]* ?  r9 R, ?
+ p& r# S9 f7 U! e2 \% j2 k! M# Y                        for (i = 0; i != len; i++) {
  _2 x; ~! }, f9 L; b; c. y4 g( ^                                char c;- G$ V6 o  t3 ^5 z& S5 Z

2 s# A) s3 t- X6 r                                if (get_user(c, data + i)) /*从用户空间获取一个字节的数据*/
- b# B; a* Z- m+ Z5 U: F/ ~9 j* v                                        return -EFAULT;; c5 L& l& w4 X" {/ V2 O
                                /*读取到字符V,设置标志位,允许关闭看门狗*/
: K4 P5 H& H+ S                                if (c == 'V')                % W* Y( `6 Z' S$ U$ O4 S4 ^
                                        allow_close = CLOSE_STATE_ALLOW;# y( X5 H* X. @9 P
                        }5 C8 v8 m9 o' Q# R, W
                }6 w% y/ S8 q2 Y
                s3c2410wdt_keepalive(); /*保活*/
- u0 i$ X1 c! ~. C: t0 }        }6 R+ [5 k2 [8 d7 K
        return len;- g0 h6 _2 C5 I8 \9 a, {
}
6 _9 ?! D8 z' m  _2 }& m# L3 l) ^& B- x
只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。' D- M1 t' h; M1 r- Z  \
, i, l. Z8 S2 V7 ?& b6 K5 ^
3.4.4 unlocked_ioctl方法" q7 m- v7 z# Y4 H
static long s3c2410wdt_ioctl(struct file *file,        unsigned int cmd,3 Z& M. V) _5 O" o& h5 |1 F6 f2 y! |
                                                        unsigned long arg)
8 F7 o. f0 b7 d  O/ P9 t/ f- W{' b% f% F/ l: N
        void __user *argp = (void __user *)arg;. m. J+ Y9 l) m& F+ b2 [6 ~
        int __user *p = argp;/ Q4 z3 ]4 U, e
        int new_margin;9 T" y5 G: b* I1 V) o
3 C5 @9 ~* u" V& [; m1 A
        switch (cmd) {" O7 n. Z) J) w# C: @  q' {
        case WDIOC_GETSUPPORT:
' t5 C( e' S! |5 g                return copy_to_user(argp, &s3c2410_wdt_ident,) m* i: [2 K& B
                        sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;' [7 E$ h* n# _
        case WDIOC_GETSTATUS:3 T$ F3 b& `+ m8 Q3 o5 x; U
        case WDIOC_GETBOOTSTATUS:! P% K1 Z2 U( [3 s7 W& j
                return put_user(0, p);
- _; a# t# o0 k2 Q        case WDIOC_KEEPALIVE:
1 E2 t+ r5 M: ~; L                s3c2410wdt_keepalive();
  j& \2 _; R4 T7 C                return 0;
; F' V8 ?% W  [, H2 J+ m4 i        case WDIOC_SETTIMEOUT:
) y1 H) m- y/ I3 f                if (get_user(new_margin, p))
0 S; G) @* r+ b7 |+ g7 S' a                        return -EFAULT;
0 ?0 B* z  X9 a                if (s3c2410wdt_set_heartbeat(new_margin))% @7 [/ U, g6 |/ I: d, M+ C
                        return -EINVAL;" ]7 y8 i/ }8 x; S) f
                s3c2410wdt_keepalive();
: z5 z0 H3 v+ j- D) P. B                return put_user(tmr_margin, p);# f( Y% s& d- b' J
        case WDIOC_GETTIMEOUT:1 n2 o( t+ f/ h+ O
                return put_user(tmr_margin, p);
- p/ e; ^) u/ C) |0 F6 h) E        default:
) x* G1 d3 \% I/ O) D: t                return -ENOTTY;: x. n, H# e9 j) m/ [8 }
        }9 P( a( E% e7 W. c! q3 D
}
4 q. k$ H& ^/ h9 m2 w% j  X! i4. 测试程序% A2 `3 j. @; d# t, ?4 I% O
#include <stdio.h>2 ^8 m; n, W7 I/ k
#include <unistd.h>
4 g$ N; F0 I. l7 D; L#include <linux/watchdog.h>0 q* Y- ~; `* _+ J) Z& E7 {- N
#include <fcntl.h>
+ u3 L8 g# D! V4 d
# S1 D; S: V7 \( k: B( Lint main(void)
: `0 K# M8 w& D4 z0 _* O( ^{
& b, r$ m% o- R# `1 Z6 v# p        int fd, val, ret;; G5 @, I, E- |
        8 b3 l/ O. k( X3 T
        fd = open("/dev/watchdog", O_RDWR);$ F, @7 [5 {% E1 K8 Z! d9 n
        if(fd < 0){
# G$ i- r* \2 K% j( Z, m, g                printf("open device fail\n");
2 c: T: f8 }/ c0 L                return -1;
9 g+ P2 C+ P7 J) }        }
) K0 p$ V* s7 X$ C% N) i       
, ]* d/ g& i0 l        while(1){
* ^3 ^8 X4 D! m5 M/ @+ \* g* K                ret = write(fd, &val, sizeof(val));& [! v4 n; I7 m. ^( [
                if(ret < 0){7 i+ u  O: V: o( ]( i# ]) K. \
                        perror("watchdog write wrong\n");
* k% S4 x; t& Z7 x2 e! a                        return -1;( K* m! z3 t0 c& B3 p( `9 R
                }
: _# q* F# @1 g8 J: `' Q  I$ J5 i' u                sleep(5);% Z" g' }3 P& ?, T+ `
        }
; p. v9 P( @9 {: r, i       
* q- X3 r0 g+ E2 Z: g+ z' V: ^; D        return 0;
4 o# f8 a2 I* K}6 D% b" q* b' r: }  b6 {( H

; h2 Y8 R$ `& k$ G该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。8 g' J' r" A% [5 ]0 f- {, ~
可以将5秒替换为16秒,你会发现系统自动重启了。
7 K& H* P& ~6 B5 p9 q! p
8 x+ t8 T' J% j1 f5. 结束语
, R3 i/ |3 A7 _& [! m0 y" b$ N   本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。
8 Z! ?; U! s7 ~: M2 J' u& u" G( Z6 L+ t5 ?( B0 _& ]% d- Z' q  D
2 m$ z- n( Q  d

1 w1 b: |. R0 `1 |* J
  • TA的每日心情

    2019-11-29 15:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2020-6-15 13:09 | 只看该作者
    看门狗(watchdog)驱动
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-25 18:57 , Processed in 0.171875 second(s), 27 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表