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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
本文将介绍看门狗驱动的实现。
# L3 E+ F" }  r7 l  O5 t& Y& K+ k1 w* k2 {% y0 K; w: p
目标平台:TQ2440 " R$ n8 y# b5 }* q  |7 X

. {6 Y; G0 y+ v4 N1 N8 j0 V* T. UCPU:s3c24400 ~9 J3 p; [2 I" ^0 f: L4 w

6 }, i# l/ S) z4 w0 Q内核版本:2.6.30
! a8 ]! p6 |4 k5 f# K8 U2 Q7 ]( O( {' ^, v* `

- a- C& ~. N) V/ m
+ l5 ]6 @6 \. h6 K* R2 t1. 看门狗概述
) {8 i3 n4 l7 m) A+ W   看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。
/ _7 L' K& u6 h* B+ u( Q' A; j
   因此,看门狗通常用于对处于异常状态的CPU进行复位。( W( v; x( x! h

- g0 h& j# |* @- |" ^' G) h8 V( \   具体的概念请自行百度。; E+ z1 ?, k& {) A+ |% c; ?

/ K6 Q& o8 f7 @# Y0 `- ]
, c( y4 W1 k% z. {) i# z! e2. S3C2440看门狗0 X# c$ K  D3 T
   s3c2440的看门狗的原理框图如下:" ~7 ]7 O4 h6 T& ^9 v4 p0 q

) f! Q; I' [! f2 ]0 q
4 E- ]; T7 O) s! x* ]" l5 l; {/ s1 A  W3 a8 N& A
  可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。
. V; u$ o# n7 C% T0 }  i0 J' {( x: p! \7 E
  定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。4 q: ~. N$ Z$ O* K
6 S" i0 w2 R9 f4 y1 h6 N/ Q( i
  看门狗定时器的频率的计算公式如下:; c" H( {6 V0 y. T
& j% |( F# t5 E( e
% f7 T3 ~0 g6 L* i0 ?

% r" j# f- n1 Y9 @/ V' x$ w
# \0 b: {" j/ m/ l4 C/ p$ {  N
* ]9 q2 V! w0 Y0 Y& `3 c6 M3. 看门狗驱动7 T+ u% X0 e! X0 w  i
  看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c/ c/ k; O( c9 {" Y
/ r. h" v$ Q% x0 U) Z' J" X
3.1 模块注册以及probe函数
" ?% M9 C7 S) y# ?, `9 K' T6 M( ?! `static struct platform_driver s3c2410wdt_driver = {: M) S( A$ N& S, ~& U
    .probe        = s3c2410wdt_probe,$ R" e& M4 F9 Y$ U; V* T1 d
    .remove        = s3c2410wdt_remove,9 Q  J# ?/ r; S" C
    .shutdown    = s3c2410wdt_shutdown,
9 v& T5 w4 D+ I  U$ L    .suspend    = s3c2410wdt_suspend,
$ M1 i& j- X0 _& X    .resume        = s3c2410wdt_resume,
% U* x$ f$ B& {* r: e    .driver        = {
2 h* ]# v3 g8 C& {9 A        .owner    = THIS_MODULE,
+ j* _: o$ k" P/ X& T8 E; `. A/ D, M4 a        .name    = "s3c2410-wdt",
; W# Y# q. P8 r    },
+ D6 h- s8 D' x4 L, {};
5 G+ f3 ]! y3 |5 N1 z8 p+ U  q5 D- A2 \% @
static char banner[] __initdata =
& N  I& e& H) H1 b6 k8 T' _    KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; ( w4 B4 P7 ]: n  x5 d7 g, ~

7 c" Z0 u6 i! a! ?! c0 gstatic int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}# v. ?/ E  b3 S5 u

# i! O- I8 Q# Q4 S, Fmodule_init(watchdog_init)
# t3 O; V( B  a5 x2 Q模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。% D0 H6 M5 M5 v+ m

$ ^3 O2 k0 t% g# w; w 该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。1 p2 Y# O9 o3 O/ Y+ T! |  q
5 I8 }5 z4 F6 U% ?$ Q* X( A2 s
我们来看下这个函数:: o5 d; M3 {& K: O. L
; w6 I+ C' w* b" w& O$ M; f
static int s3c2410wdt_probe(struct platform_device *pdev)3 }* d2 ]  Z0 s, x* b' m
{5 h( @* T' W/ c1 h  T1 C
        struct resource *res;
; U  z/ l  S  A- Z" F        struct device *dev;
$ g5 `' ]8 v5 {3 q# n7 N/ l        unsigned int wtcon;
+ `1 m- O' V2 \" f        int started = 0;
3 A1 g) ]+ I3 o( D        int ret;0 V0 a: u" B  \1 |
        int size;3 i$ ~$ D$ U/ U! Z( S0 b

& S5 i8 ?5 P) `; f        DBG("%s: probe=%p\n", __func__, pdev);
+ b- ~+ L# {+ O) J6 {# c  C% i' P9 U" @/ D! N' \. G7 _
        dev = &pdev->dev;
0 W# l+ l, T! E        wdt_dev = &pdev->dev;" m6 G7 A9 d: n! a

1 I9 U; y6 V/ K4 x8 [% |7 m5 w0 U        /* get the memory region for the watchdog timer */
3 l7 O+ P; {  U: x- a9 R) ?        /*获取平台资源,寄存器地址范围*/$ j( G& W( R* }
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
# _* N9 b3 H" N0 l" d        if (res == NULL) {2 M! T+ e( G! j9 S! o9 W7 |: c
                dev_err(dev, "no memory resource specified\n");
8 l3 Z" u. ^) d; n" Q                return -ENOENT;; Z2 M2 O" z+ _5 b1 y
        }" K4 @7 B3 e2 r3 I6 C" A: L
1 K1 |: D! r3 s# ?- S5 p
        /*内存申请*/
: |% j7 C1 D, Z) ]9 J, g        size = (res->end - res->start) + 1;6 u  N+ l+ N  K) t
        wdt_mem = request_mem_region(res->start, size, pdev->name);( }7 x) B+ `+ E8 I4 E: f* Z2 `
        if (wdt_mem == NULL) {
8 U$ r3 I# L  |$ s                dev_err(dev, "failed to get memory region\n");* L# s" y  f) V* g9 M! o7 W
                ret = -ENOENT;7 x; o. ?# C: P+ T* d
                goto err_req;
. J* U% n; N1 ~, b8 m$ D  w        }2 D  m; m; J/ ]+ W  T  }  d

6 j( A( x) H$ e4 ]7 Y4 s+ s+ _        /*内存映射*/
1 D' R6 h' H9 ^4 r5 B        wdt_base = ioremap(res->start, size);
2 l4 Z6 Z- t! Z  Y        if (wdt_base == NULL) {) F$ g, g9 _, m6 h/ j" A
                dev_err(dev, "failed to ioremap() region\n");
9 K' B/ `4 u  f9 \( a                ret = -EINVAL;
+ G9 S3 z& D0 p9 i- P( e                goto err_req;) Z' F5 t/ Y2 v& [/ @8 v2 G
        }: A4 r4 ]0 Y% y" y
% [+ d  z- J5 Q$ ^6 v) Q
        DBG("probe: mapped wdt_base=%p\n", wdt_base);
8 Z2 g# N6 O. T- S+ `/ G        4 w5 A. X# V5 F, c& @
        /*获取平台资源,看门狗定时器中断号*/
3 X( `7 ?2 V9 z5 c! Q# [) G0 v! F1 v        wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);, O( z/ ?- R* m) U
        if (wdt_irq == NULL) {( _2 P! V0 F2 z) D1 H1 m+ p5 B0 @. ~8 Z
                dev_err(dev, "no irq resource specified\n");
- s* q, g  K1 o                ret = -ENOENT;& D: B+ z, X, J
                goto err_map;
) v- t( r9 ]/ m6 U' D: H        }. @# q& G2 o* [% K* f; p, u
       
! t0 @/ v' V- }% |7 |2 r) O        /*注册看门狗定时器中断*/
9 @# J; \5 D5 G5 I) S, P/ j$ q' v        ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);8 }; v) V0 d  {  `' i, q9 W- p8 x
        if (ret != 0) {
8 h, b' ?- j+ K8 c                dev_err(dev, "failed to install irq (%d)\n", ret);; b/ q* Q" ?0 c7 G; Y
                goto err_map;
0 w# H2 w3 ^, B/ F! ^, ^        }8 X' n2 l  B/ G8 D3 f
        /*获取看门狗模块的时钟*/- j- |! s* h* ]( ^4 h3 f9 k9 O+ l
        wdt_clock = clk_get(&pdev->dev, "watchdog");- {; H+ z7 R/ I2 V; I
        if (IS_ERR(wdt_clock)) {; x) T/ y& E+ M  U  R: I
                dev_err(dev, "failed to find watchdog clock source\n");
, q$ f3 n! K, j4 e! `: Y( U                ret = PTR_ERR(wdt_clock);6 T. H" D$ X) R
                goto err_irq;: ]" Z. D( n' ~2 }" n9 u" t
        }
. o: J0 F# j( z; [5 F9 M! b2 S
, d8 Z9 H/ w' L, Y        /*使能该时钟*/$ J  ?7 W* w* ~
        clk_enable(wdt_clock);
9 ?8 F4 i- \' X3 o5 M( h3 k) ?
8 p" u0 x, a7 k1 b$ n& P/ z        /* see if we can actually set the requested timer margin, and if! \6 X% j1 X6 [6 e; f, ~
         * not, try the default value */
! e* _% D) C" F3 ~
# P2 t4 M/ l$ ?! s, i  r& u. g. @# c0 H        /*设置定时器模块的时钟频率*/
6 z* V( l( f* [+ V        if (s3c2410wdt_set_heartbeat(tmr_margin)) {8 b5 }! n6 a# r/ c. ^  `. Z
                started = s3c2410wdt_set_heartbeat(
7 ~7 w* M( m" J: ?  _. Z                                        CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
$ O, D. U$ X' [9 H* L% F2 w( k6 L$ }9 @4 ^, V1 q' O
                if (started == 0)
9 K  L6 S* S  G- W3 L1 D                        dev_info(dev,. _0 O  h# Z5 N* _4 J. L' j! ?& c7 i
                           "tmr_margin value out of range, default %d used\n",
; x% O" |6 ]+ B# _                               CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);& o- d+ y5 B9 O  _% M% y/ n
                else
/ D# T& z% Z- \& m+ e& M4 S                        dev_info(dev, "default timer value is out of range, cannot start\n");7 d( ]; O6 _, ~% Z6 p
        }
* x) y6 b- R; u2 g+ K( E+ H# k2 R" @/ x: x9 `
        /*注册混杂设备,设备名watchdog,次设备号130*/$ b2 ~" o, I$ [8 w" ^
        ret = misc_register(&s3c2410wdt_miscdev);: u; e: w! O5 C) `0 j) n! t6 \
        if (ret) {
) }% I) ?) z$ ?- c                dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",2 u8 o% ^$ E/ A; P2 r+ x( z8 w
                        WATCHDOG_MINOR, ret);
% Q6 }! u6 j, t+ D7 P                goto err_clk;
5 {2 p2 c2 ]. A' n- R: V        }
7 h4 }- C  c4 M1 [) L; ~* x) x2 G2 x2 A" c
        /*- n- W( n4 p; [' G# y% {
          *如果需要在看门狗模块加载时启动看门狗则" t) {( B+ N; {
          *调用s3c2410wdt_start,否则调用s3c2410wdt_stop! }/ p* C, G# R
        */' x0 g# [" x( B  l
        " s2 F0 z% M% l3 G1 U0 I1 \4 n- G1 _
        if (tmr_atboot && started == 0) {
+ K9 r: E( v- ^                dev_info(dev, "starting watchdog timer\n");' {" r* W1 U' Q$ V
                s3c2410wdt_start();
7 ~! |. Q! N8 w- i) D# b; Z        } else if (!tmr_atboot) {
: H: y- e; Y' ?+ ~- V                /* if we're not enabling the watchdog, then ensure it is( @0 {: u" h" i; Y# q  ^
                 * disabled if it has been left running from the bootloader
! `- |$ c, M# J8 o4 {; }* N, w                 * or other source */9 C" S# w) W! g" h; Q& O3 h

0 u8 y. \* }  c0 w                s3c2410wdt_stop();8 r2 F5 B/ M% \  Z7 P
        }4 c" Y+ W9 g6 Z# W8 |, P

1 C+ L1 F% p3 }9 L7 v' a        /* print out a statement of readiness */
/ T% S) s, _% ]/ Q        /*读取控制寄存器,打印目前看门狗的状态*/
0 a6 l( q% u/ N* C+ I, K* C# ]        wtcon = readl(wdt_base + S3C2410_WTCON);) f5 ?' P$ K0 D% e
1 X4 X1 j. e1 H$ X5 G& y; |
        dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",/ {- C$ h$ R, F
                 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",- w+ O0 x8 f& R7 I3 F( X! }$ j
                 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",  n+ X8 _* E0 P
                 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
, Z; R8 X' X- [! v: E
. J& p! v; ~+ P2 Q8 \, ^) x        return 0;" {; l6 G  }" F/ s- p% ~3 a

' w; P8 C5 s' G' Q9 T, Y! n0 ] err_clk:$ Q1 @  M, ]+ t. i: L& y
        clk_disable(wdt_clock);' f/ z- R. Z) x3 i  g$ D
        clk_put(wdt_clock);$ O* C# \# c6 M3 y9 B
) r$ F+ v& A5 @1 P4 S, G+ y3 L) M
err_irq:
1 {) R9 t% u0 t: Q4 }. z- _! T        free_irq(wdt_irq->start, pdev);/ C4 V* @, q9 }8 g' G

) S8 E" h  u. G+ y err_map:) p: z* \6 E5 p3 w
        iounmap(wdt_base);
, s% R2 C; r. i  A) N
* |8 N  Q( X1 d4 ^0 H err_req:* b8 C3 k3 _1 ^  v, B
        release_resource(wdt_mem);
( n+ b7 c& C# p! {" k        kfree(wdt_mem);
3 D7 q* i* D: ?
# |1 W2 J9 R& k) R# V3 X" N        return ret;, Y2 y) R+ J8 Q$ U9 C
}. h3 R# G6 V2 L0 ^

4 f5 J, j" T  X% l6 ]该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。/ {" S2 b6 G) V# @* N
9 E2 e" f9 h/ E8 r$ V/ G
然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。) g4 @5 h& Q, C
) U! L& B. X# K! F) {# U- s, J
上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。/ l; k1 X% \' g6 ^2 m0 |

: B; S9 I. ~8 @$ r+ [/ |7 W7 h: QPS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/ARM/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。; W0 T7 ^$ S; L& Q" R* t* k
- a, b& n/ z! |5 |# y7 b7 K% N. j
3.2 s3c2410wdt_set_heartbeat7 `$ O) u7 z' _1 h9 u, p
static int s3c2410wdt_set_heartbeat(int timeout)  /*timeout 超时时间,单位秒*/
5 h$ Q+ a0 T- X/ K& c6 a  K$ T{
% F" ~% N/ |8 {- Z( k7 u        unsigned int freq = clk_get_rate(wdt_clock);+ q5 B1 x# }1 W; M" G( W- A0 m& e
        unsigned int count;6 S  }1 Y% f% \' O0 u5 r
        unsigned int divisor = 1;2 W2 b" ]/ s/ `6 u% n/ v
        unsigned long wtcon;9 l# J. p- U" c" ?  c+ e, j
; o- A: p- k6 U6 R& T* Q
        if (timeout < 1)
/ T2 A9 u- h  N: `. ^1 @' X                return -EINVAL;& D9 Q; H! S! d6 v! ~
: o" ]0 ^  i% }+ p9 _2 T" I
        freq /= 128;    /*时钟源为PCLK/128,不使用16 32 和64*/
# k+ t' G9 W2 P6 d        count = timeout * freq;  /*得出计数器值*/
# w: u8 G# G7 K7 c
3 L, T* b& b$ g& V        DBG("%s: count=%d, timeout=%d, freq=%d\n",5 a+ p) m* N9 B/ q7 u
            __func__, count, timeout, freq);
- j! M# D  X7 V& F
: c& M4 h7 m9 M- |4 P- v1 p        /* if the count is bigger than the watchdog register,
7 W/ @# q" O! A$ }: [           then work out what we need to do (and if) we can5 w- S+ J/ U- [6 I
           actually make this value9 C+ g- j7 u! X) ^
        */
* v* x4 w" B! W0 a1 b& e        /*计数器最大值为0xFFFF,如果大于则要计算Prescaler value*/+ F# C& L6 x; I9 a7 _
        if (count >= 0x10000) {
$ O; d/ C+ T8 q/ p# D6 [/ q2 B                for (divisor = 1; divisor <= 0x100; divisor++) {     /*Prescaler value最大为0xff*/2 A# Y# _# V; U* D/ ?
                        if ((count / divisor) < 0x10000)
9 S. Y  `3 B' ?$ @                                break;
) A: Y, q! x6 ^2 E7 Y9 j5 m$ B                }
2 I0 }- ^- N# f( k8 M        /*找不到合适的Prescaler value,报错返回*/. O& c  v, r8 q0 s6 c: k3 ~, T
                if ((count / divisor) >= 0x10000) {1 I3 W/ L; ?! {1 h* a+ B6 \; W4 B
                        dev_err(wdt_dev, "timeout %d too big\n", timeout);
, \' j: s, d/ l) y                        return -EINVAL;
% |5 _5 D7 z* I1 F: h                }! x% t& @" T0 h  R  j  p8 B
        }. k, Q/ o" J$ L+ _6 ^; H7 I5 @! _
. E1 Y7 w. \4 ?  R' P
        tmr_margin = timeout;  /*保存timeout*/$ X$ Q' H5 ?$ i5 V& {

. G5 e" D  V5 C        DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
- k" H: m) w6 m6 d            __func__, timeout, divisor, count, count/divisor);
; L. E2 ^! T0 ?
4 s$ _2 H" x  s! C( G! n7 k/ M        count /= divisor;   /*根据Prescaler value计算出新的计数器值*/
3 s; t3 d! L% L9 r1 P, H        wdt_count = count; /*保存计数器值*/
' o  Q! i/ `) l+ T9 A; @. E' ]5 J' _7 X1 s6 N" [
        /* update the pre-scaler */
$ j$ ^! c2 o  c) r+ l        /*
* [: P5 B( j. \7 P& X2 }1 K) M          *设置预分频计数器和数据寄存器
, g; H- e4 m5 P9 {, t; i        * NOTE:此时并未使能看门狗定时器
3 T6 j% \- |* H* t4 Z0 O3 H# P: A           */
1 l! a/ B- b3 [; @        wtcon = readl(wdt_base + S3C2410_WTCON);6 r; M/ E" T, {8 k2 |
        wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
, S6 W! N) ^8 O  t" k: \9 C        wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);( |1 Z2 m- F: w- F7 D
1 T2 M2 s6 K2 w* j2 s6 W: w( ^
        writel(count, wdt_base + S3C2410_WTDAT);
2 O- ?9 D* \( s, w, F        writel(wtcon, wdt_base + S3C2410_WTCON);
! B* W, s/ j$ S; f1 [  [& X  A! {9 k3 ], M& s+ H8 ~# V3 Y5 m3 B
        return 0;
4 j6 y4 F6 m& i4 x  T/ G, B* [}
6 [* Q) ?7 {# M* s6 G/ F
& `! l5 ~: p+ o: u& V* }形参timeout为定时时间,单位为秒。
* }1 v6 @# T/ v
2 y4 u1 _) S5 N$ A* s3 d1 \  这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。
" Q" f; a: s5 X( Y6 s( ^& n6 C$ @9 o: J8 o  p
  在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。- w/ O7 N* C+ s( Y
$ V' t& S  F7 j& U9 p5 [. y, B
  当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。
6 u# q; W2 |% F9 n' g; Q. |
' i* c! J1 U2 W; h3.3 定时器的启动、停止和保活
0 N6 D; S. k+ r9 V- ~$ F$ y0 T3.3.1 停止
6 x1 E6 w4 L7 s2 L. C定时器的停止由 s3c2410wdt_stop函数完成。/ a& w+ x/ i8 M

2 [. r/ u; f" D9 Estatic void __s3c2410wdt_stop(void)
, V, S- }: I+ d9 N1 @+ G& H{
* d2 w0 u% j$ r. o6 H2 S        unsigned long wtcon;
4 _- B8 C' r% p) b7 A
! y- q# z  [0 L! x9 j+ J5 e        wtcon = readl(wdt_base + S3C2410_WTCON);
* e9 e0 V' M$ E5 s5 i5 r. S* y        /*禁止看门狗,禁止RESET*/' h, [2 l0 t3 w/ V$ {
        wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);- m2 P5 o) |" j6 t9 r6 j4 M2 V
        writel(wtcon, wdt_base + S3C2410_WTCON);
  h% ~- a6 w6 d# U$ v( k, U3 `1 b, a0 h}; f# x3 d- V3 L2 U0 F0 N5 U
- g, r( j/ a& h) r7 c
static void s3c2410wdt_stop(void). L# m) r: X0 t2 M* G" }
{
$ s5 p1 k% I3 {( d# d* r    spin_lock(&wdt_lock);4 O9 {" w. W5 |
    __s3c2410wdt_stop();" X3 k9 `7 Y& L' B6 k# t! ?
    spin_unlock(&wdt_lock);
% }0 l! G4 K" a) H}
; r0 w$ x* Q8 G8 u) N
" S. |1 z# u5 \: A& _5 r0 ~3.3.2 启动
, k! H( j; _/ m% K- ]/ X; z定时器的启动由s3c2410wdt_start函数完成。* q6 x( u  v  @* N* {: {+ Q

2 f1 T  |- X. j8 E8 P8 wstatic void s3c2410wdt_start(void)/ F+ m8 ~8 P& G# T# Q
{
7 J& z; k4 Q. f        unsigned long wtcon;: G7 P* I4 u4 B) \5 C% T/ r

* ^) `5 N& e1 J8 T1 O( T        spin_lock(&wdt_lock);$ J, T) q9 r3 M

' d& n, i7 G, e- O        __s3c2410wdt_stop(); ./*先禁止看门狗*/
9 A( E7 q+ J! J) i. U# b. N' v$ E  F2 P* j2 @
        wtcon = readl(wdt_base + S3C2410_WTCON); /*读取控制寄存器*/! h2 d: f8 M1 k8 w1 |; [
        /*启动定时器,设置分频系数为128*/) r" I  H2 `( @; q
        wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
2 I, ^: N/ T# a- f7 A8 Z
/ V) c; V; }% u9 K- k        if (soft_noboot) { /*判断许是否需要RESET*/3 s) p! J! J: h" n. |! K. i5 L- f
                wtcon |= S3C2410_WTCON_INTEN;          /*使能看门狗中断*/
( U2 n7 O9 x) F" I( @                wtcon &= ~S3C2410_WTCON_RSTEN;        /*取消RESET*/
% M1 W2 P- R- D" V3 ^        } else {  /*复位*/7 U0 l( G  D  \" K$ Y1 P
                wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看门狗中断*/
& I8 \0 f- t6 l( d% |. n2 \: m7 y1 H                wtcon |= S3C2410_WTCON_RSTEN;  /*设置RESET*/
) y& U. Z# s( c! F2 n        }
$ T+ Y2 g5 b3 {' |; j6 ]0 O) J
$ f' H! l, R& D( K% z) W; f        DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
; p% ]+ K! v( [7 v# @, l2 P            __func__, wdt_count, wtcon);
0 g  r+ H' z/ C% I+ B
4 W) G, _9 q. Y        writel(wdt_count, wdt_base + S3C2410_WTDAT);, `& A! W; m3 R7 P) E4 b
        writel(wdt_count, wdt_base + S3C2410_WTCNT);/ e( O1 o- h! {4 \5 V! I& O4 ]
        /*写入控制器,此时将启动看门狗定时器*/
& R* K8 r: d/ {# w& v4 m        writel(wtcon, wdt_base + S3C2410_WTCON);        
' i* a4 [+ E" |. j! x" E8 y        spin_unlock(&wdt_lock);
, `' c: }! x8 N3 U' G/ c  f; V}
, M1 D+ f' K4 `9 J8 r在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。9 }6 b+ T8 a% g  n$ u5 |6 f

( M8 O2 }1 B$ g  O3.3.3 保活8 H& L: d: }' @( z. O! T3 I
定时器的保活由s3c2410wdt_keepalive函数完成。* ?8 N8 @8 w9 Z  _- \

4 X  K  G0 N9 V! V0 _9 S: Qstatic void s3c2410wdt_keepalive(void)  f' q2 r5 e+ H; }0 c
{5 |) I1 q' b! _+ X. w
        spin_lock(&wdt_lock);
9 [7 i$ k# A7 m        writel(wdt_count, wdt_base + S3C2410_WTCNT); /*重置计数器值*/
2 j0 F' x3 h5 s5 j        spin_unlock(&wdt_lock);
# [' t6 Y0 [% [* c: i3 R! U}: I; ?; ^* k2 v: E! Q
最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。
, x! t8 j5 O0 `, V. T% C7 f7 G' M8 B

* @, L, N. J( W  B3.4 看门狗驱动API
! `& K) j, F, W* a4 x, [看门狗驱动提供的API如下:
! g  K8 q2 k' g; c" Q$ T" v! F+ Y+ p) `  U" l0 ]. E. D
static const struct file_operations s3c2410wdt_fops = {0 h( _1 m# G1 B2 }
        .owner                = THIS_MODULE,
9 x. m& r3 m7 ?8 |4 T' h+ @        .llseek                = no_llseek,  ]6 p- |6 h& x
        .write                = s3c2410wdt_write,. `0 E: D  F$ g/ u) f1 ~
        .unlocked_ioctl        = s3c2410wdt_ioctl,
  B( p5 _9 f0 D4 C        .open                = s3c2410wdt_open,
* H  F" Y) F6 f2 P, R2 H6 X* x6 _        .release        = s3c2410wdt_release,; R2 \9 ?, I. V
};
: X0 f* ]2 ~, z3 g, n1 m7 @6 `; x9 a" U; s3 Q$ h6 g
我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。
" c; d# h' o+ p' `! e. k& |4 ^3.4.1 open方法
/ f" v7 U, `4 ?2 F" Ostatic int s3c2410wdt_open(struct inode *inode, struct file *file)
& j, ?1 O& M2 I) o{. k4 G: Z9 A  f7 a: o, B' T
        if (test_and_set_bit(0, &open_lock))/*看门狗设备文件只能open一次*/5 s- V8 T2 R9 o! M" D
                return -EBUSY;
, w8 `% [4 s1 P& `3 r) m$ S9 J
2 n: a, T; [; I: S* f4 d        if (nowayout), S+ H, [& {; ?- {
                __module_get(THIS_MODULE); /*增加模块引用计数*/# u$ |/ ]( @5 h6 V2 t6 ]

( J" d4 i) _+ G/ M  [( P        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/! w0 ]4 n# T; s+ s

/ c* J0 u% P. l3 W$ z/ ?+ d        /* start the timer */. m8 o) J6 [- W8 R: }
        s3c2410wdt_start();                /*启动定时器*/$ z8 T$ i- c2 E1 t! `
        return nonseekable_open(inode, file);  /*告知内核不支持llseek操作*/
  i( X" l) K" L% K9 f}
  G$ [) W+ n3 Q5 N' C8 p% e& ?7 u2 [: S+ @. \9 t0 }4 v* z* T# t
这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。
* |: W# Y' I7 U  p9 M& ?# X1 Y, {3.4.2 release方法. I) U: U: W4 v& E/ d& W- j& l  S! Q
static int s3c2410wdt_release(struct inode *inode, struct file *file)
. x& h3 u/ }) b$ o{5 m' Z( m) N" A
        /*# E2 ^* t9 \! V( d
         *        Shut off the timer.8 |- U- ~' O# a: O- u$ B+ y
         *         Lock it in if it's a module and we set nowayout% ~* l7 {& V( i
         */$ X% g9 P+ W6 S) J" k4 f
          /*状态是允许关闭看门狗,则停止看门狗,否则保活*/
3 }1 Y5 c$ q7 w; h1 d. S        if (allow_close == CLOSE_STATE_ALLOW)
( s1 T* K9 N; b                s3c2410wdt_stop();
3 J: z" s# S% M- O        else {
% L& l. m' m: t: U: W( p  U+ u; o                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
: G5 E& O6 U4 C+ J                s3c2410wdt_keepalive();+ g- m$ n0 D6 V* C
        }
3 j6 v: J: L- ]$ ^% q* L8 L        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/! W6 o; n+ t1 {
        clear_bit(0, &open_lock);
; o' A8 b/ D. \        return 0;
6 I. u% S$ Z$ f, C2 y& N, j& A, |}; X. H1 Y1 y$ U" e( V( q
3.4.3 wirte方法
! G: p- x5 q& O! f: e6 G' Estatic ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
. ~3 |+ s( z! H- `. y/ }                                size_t len, loff_t *ppos)6 [$ R4 P+ k' q1 l5 {9 }# i
{
9 Y# ~$ @9 d( v5 O        /*+ g% y$ h+ M% u9 L
         *        Refresh the timer.
/ ]' x% ^3 H- L         */( v, i) b  w2 |
        if (len) {
' }/ ~" D. k4 L+ P6 w                /*nowayout 为真,不允许看门狗停止,使其保活*/( f- Z  |. {) v* y1 `; k4 J) A
                if (!nowayout) {! s3 ?& s4 K7 O; D
                        size_t i;
, m' B9 d* U$ T: e6 j8 t4 V* M" D. W$ B
                        /* In case it was set long ago */
: Q7 x! X) \: B+ D" @                        allow_close = CLOSE_STATE_NOT;/*设置标志位,不允许关闭看门狗*/+ C( D$ O1 }8 g" O$ @8 @
/ j9 d3 Z( E. U' d: I
                        for (i = 0; i != len; i++) {
+ ~* {, |- x2 V: W# e. R% o                                char c;
. P1 W9 Z/ z- R
) h5 b, M8 f$ L) |7 K                                if (get_user(c, data + i)) /*从用户空间获取一个字节的数据*/
/ M7 K7 x, X; [                                        return -EFAULT;
- K& A% V( K$ Y0 ?0 g                                /*读取到字符V,设置标志位,允许关闭看门狗*/
4 H3 O" ~# E- L+ Y8 f' b                                if (c == 'V')               
- u# X  J) x( [- E% x% I6 p                                        allow_close = CLOSE_STATE_ALLOW;! B% @, s! a, ?& l! y3 Z
                        }
0 |) r; ]- u7 m1 Q- Y. {% h                }
$ d3 e; i, }% G. P, B& e" K                s3c2410wdt_keepalive(); /*保活*/+ ?5 o* ~" B# \9 A
        }
* v+ o; Y0 j, d2 d. H% S; \        return len;
& ~* ~+ C6 l) b2 e( J2 ?) I: F}
1 P6 u8 A, t" L$ S6 ~  ^$ p1 G6 q6 o, b1 g  m) q' X9 Z: h
只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。. ]8 t/ H1 p4 m- i' ]
& p7 O1 ?3 X- t1 S
3.4.4 unlocked_ioctl方法. k" [3 s" L9 k! k1 C; d, F$ K) u
static long s3c2410wdt_ioctl(struct file *file,        unsigned int cmd,
. L9 P: F. `* P( K" T# r" B                                                        unsigned long arg)# F; d3 n# \* z8 E; |
{4 w3 G) Q$ T3 u% @% J$ r& f
        void __user *argp = (void __user *)arg;
* t% u1 q. O( x/ Y7 U        int __user *p = argp;5 J, w5 m7 P& N
        int new_margin;
0 n' \7 v$ [3 i/ |+ L; C* C! V) r/ T1 i, W
        switch (cmd) {
4 Q9 @/ k5 O/ o7 S        case WDIOC_GETSUPPORT:
/ c" b3 d1 |1 L' F6 k- r7 I- c                return copy_to_user(argp, &s3c2410_wdt_ident,
' G! U$ T" F' [/ u: R8 J                        sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;$ C9 I$ L! `; i( W: @
        case WDIOC_GETSTATUS:
  n: L( \( ]1 H; Z/ T        case WDIOC_GETBOOTSTATUS:% ~: `% d$ W  c
                return put_user(0, p);/ G, G& O2 h1 {
        case WDIOC_KEEPALIVE:; G8 ?; }9 w2 d" e3 S6 V
                s3c2410wdt_keepalive();4 P1 f. a2 w; A8 k
                return 0;
. Z, x3 T& i. g$ K' Z. C, I! O6 T        case WDIOC_SETTIMEOUT:5 E$ C, W1 p5 B1 T5 v
                if (get_user(new_margin, p))
4 S0 y) t- Z: o$ I2 f) K                        return -EFAULT;: z" ~; c' l$ H% K8 n
                if (s3c2410wdt_set_heartbeat(new_margin))8 t7 @. T# f& E5 y
                        return -EINVAL;
( n& v0 Y' ]: O: B* B- x8 Z1 d9 |; N                s3c2410wdt_keepalive();2 `! C" B( e' x. O6 c
                return put_user(tmr_margin, p);
! I3 }2 r8 b$ m! o% u        case WDIOC_GETTIMEOUT:  e4 @" h7 a4 a  H! O$ {" ]8 ]# J
                return put_user(tmr_margin, p);
& v" J7 k7 `5 q  y4 h1 P( m6 T' Q% {        default:% ~9 \4 P# B. e$ [' T% I, U
                return -ENOTTY;; Z- E, Y3 ~; I' v9 {2 v
        }
2 N: R$ n; X" L# d}  N2 V' ?- ?5 b9 R8 q
4. 测试程序. F7 v# y# l; }6 t$ E4 [# `7 V
#include <stdio.h>3 g1 P( e1 `1 h+ F5 ^( P, [
#include <unistd.h>
. o& q! e$ N: g2 k#include <linux/watchdog.h>! W6 w' Y' k$ N2 ^- W3 Z$ p( o- U/ ~
#include <fcntl.h>) H* X8 o% K3 ?& ^

4 |: k( D7 g: L' x8 U/ I# Vint main(void)  Z1 g4 Q$ G8 c' f
{
0 J; N3 `4 D* ?3 G5 }        int fd, val, ret;& e% D4 C* X. M) D9 `, C
       
5 C9 [% V4 X' `8 O9 U/ y- C        fd = open("/dev/watchdog", O_RDWR);
, L/ T! Z! ~6 ?( a. g        if(fd < 0){/ E2 o/ s- F8 c% Q: t
                printf("open device fail\n");+ h7 R  x6 U7 P4 R3 Q" K0 ?' N1 |% @% @
                return -1;3 D  y: R8 B$ _  r/ I
        }
  T* `/ u% B$ B* f. g( v1 L        2 r8 S# {' O  d9 }  s" A4 W  e
        while(1){+ b2 U) L4 k  t" F
                ret = write(fd, &val, sizeof(val));
0 @. ^; M2 o5 N6 g                if(ret < 0){
2 T7 a6 ?3 l! c! z  E. F                        perror("watchdog write wrong\n");
, }$ I9 v% l) l+ Q' V4 @                        return -1;
, F5 ~% U! d+ L5 z                }
- _3 x- N8 a! i& H0 c  L$ G% ^; b                sleep(5);
, |4 d- h8 ?1 I  G, v* k7 w        }: o  i) v1 s! v1 G, `" k& M( W
       
- Q3 K0 f" G% b' _$ K        return 0;
+ r; n$ d" I' p6 z}) [$ `, u/ c$ \* W
4 S7 G& Q4 q. Q% Y9 l, {! g
该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。8 ]$ h0 i1 |7 E8 e( O1 `( x: L
可以将5秒替换为16秒,你会发现系统自动重启了。
3 ?; ~1 f6 H9 l/ _5 Z8 h2 b/ q) u* Y3 i1 k  R( i2 s& {
5. 结束语: S+ {( B. y# q0 Z1 U6 E
   本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。
) f9 Z9 M& N# n5 u1 Q; q
2 F6 {+ s  O$ G, _0 B4 J" z% P2 g6 E. [+ U; b
( t, c+ L5 K2 T; c+ g! J6 l
  • 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 16:42 , Processed in 0.234375 second(s), 27 queries , Gzip On.

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

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

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