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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
本文将介绍看门狗驱动的实现。
6 d! m+ M, m' A" P' s8 ^& Z. J+ p4 z6 S( A. R3 d, M9 E
目标平台:TQ2440 ' |5 O" ^, a$ ~6 k4 U6 P2 ~
& F5 j9 ~% |, s( `6 {
CPU:s3c24405 h0 w# r  N; y+ Z6 j6 P

# g/ l: c# |; K7 M: |6 d) `( ]; P2 [0 ]内核版本:2.6.30
/ z3 O1 ]: B( D+ J) \2 n! x2 A' d$ j0 _) V0 D+ {

. V4 D( m/ y1 |/ o9 G5 P8 o  w0 N% w
1. 看门狗概述
+ L9 g9 x. |% y3 Z3 c/ y0 c   看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。3 d! I; U+ b, j4 q) a" z

+ ?& `' K+ K  F% C/ S   因此,看门狗通常用于对处于异常状态的CPU进行复位。) r: R2 D# J0 q6 P' W# f
9 l. I  I1 L) R! V" g
   具体的概念请自行百度。: S  v3 V  m% G- y8 Y

' d9 N' x5 P7 ^" I4 ^3 z6 @4 b& O/ B9 `% J5 B
2. S3C2440看门狗& _/ C( O! _7 G0 R4 y
   s3c2440的看门狗的原理框图如下:1 H( I  W/ j$ s( n4 U; [
% k# c; v( e& Y: ~5 ]

; K$ d" v' ?! P8 W. A& d+ T5 T; |  k# e0 `
  可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。
, ?  s. d  w6 R* _2 _
* f. A1 X/ Z3 i$ n7 `/ M# U% r  定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。/ f7 ?8 s; i' V4 V6 g6 k! j: m

2 C( [# E3 r6 A( t  看门狗定时器的频率的计算公式如下:
, y% L- z) z( W( b' M' K4 G$ z+ i' a

! F4 L- `  D6 Y. P9 p5 g& _
2 M+ T: M8 f8 w" O$ j. Z% Q, c% J& o7 t, _! Z6 Y; [- O

$ @( K6 _) N0 f  F* s3. 看门狗驱动4 i" j5 i8 M! G: h% p' b* w- }0 h$ c* P
  看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c) c6 w& l" o% v! q0 r" ?
; J$ S' r0 R- y6 Z
3.1 模块注册以及probe函数/ I% b# o! A& f! N0 j0 w) S
static struct platform_driver s3c2410wdt_driver = {
4 O' x- f6 W7 c" I  g    .probe        = s3c2410wdt_probe,
( w- y, j( @7 W! Q0 j    .remove        = s3c2410wdt_remove,
' N6 n0 h# F' u8 P. v( Y0 f    .shutdown    = s3c2410wdt_shutdown,% I; w& K+ K4 Z* U( U/ F7 N
    .suspend    = s3c2410wdt_suspend,8 g# m  H( m, i
    .resume        = s3c2410wdt_resume,
9 k" V2 s. ?: ]5 n+ I. I. S    .driver        = {0 d+ b: f% w# f) {7 G% ?7 j
        .owner    = THIS_MODULE,
& E2 z) L' ^. w5 k. z% I% Y# m        .name    = "s3c2410-wdt",
) |+ ?* u1 ~$ P+ L/ i# J, @    },- k0 c" V8 O( L/ O) g: x$ ^
};
: q! f5 h" Z" y3 A6 x$ S$ D& E  ?5 w- Z3 o% o
static char banner[] __initdata =/ B: ^7 n8 d& {* F7 d8 d
    KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; $ N8 x; [; l- ~) A% w+ R
5 G8 l' V  K$ C+ x4 T
static int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}
9 c+ j( p/ p* g) E9 Y
' F  K- L: P" V; x; Smodule_init(watchdog_init)
5 G# i2 V/ \( y6 }* `模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。
" o; K; \" R. u. i) E4 q
6 g* @1 a/ {3 p8 i% L6 z9 d2 |3 ? 该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。, a- o$ P0 i/ `" E5 q' m, I$ I

/ G8 T; o/ I" K8 B5 W/ z. c 我们来看下这个函数:
& I' F& t, r# w5 ]6 N3 E/ k, L% K5 M2 o  \+ C- I- i. C! O9 [
static int s3c2410wdt_probe(struct platform_device *pdev)* y: L( C, M! s( Z! b1 U. ^
{
% i  L5 f( n; i; w        struct resource *res;
) ]; j4 T+ w) @$ a) Z9 V- ~        struct device *dev;/ ^8 w: C/ R. g' u
        unsigned int wtcon;5 I: F7 s% U1 Y. o; Q8 ~7 F% {
        int started = 0;* ]) `5 b" J2 F" B: W. |2 e: E3 A
        int ret;
( w# {/ g$ b8 a- D/ _( k# H        int size;
: g$ z+ f1 J1 j, i+ T, c/ [. Z4 i/ A" C0 D' D2 w9 w
        DBG("%s: probe=%p\n", __func__, pdev);5 B2 {/ r9 d2 D' }7 E% B: K, X

/ m% X+ j) [* V3 l        dev = &pdev->dev;+ Q/ ?2 W* |8 K& J6 |, w* o! _% _
        wdt_dev = &pdev->dev;" Y& m/ h, n  G- x) Y; u# q
8 w2 q5 C5 O! \( m( ~% f& z# O& H. C
        /* get the memory region for the watchdog timer */# d! V' s; H* L
        /*获取平台资源,寄存器地址范围*/+ K) Z- X! A/ f
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
# O$ S1 [1 @2 v& s* ~        if (res == NULL) {+ {& d% q6 S1 m1 w/ z
                dev_err(dev, "no memory resource specified\n");
9 S' N4 p) |/ X  W6 h                return -ENOENT;) e% C$ ~! }; N; Z/ n" ]3 P
        }
/ G8 k* x3 @8 _9 X9 P7 }/ p
* u5 D' C+ r: a4 Y# C% C- V        /*内存申请*/
/ w. P. _( {% L2 |: f3 K. T        size = (res->end - res->start) + 1;1 K2 ?4 L! u1 M* s) g" J
        wdt_mem = request_mem_region(res->start, size, pdev->name);
2 o9 Z5 v! L+ ~7 X        if (wdt_mem == NULL) {
8 H3 q, c- |2 \                dev_err(dev, "failed to get memory region\n");
' Z6 _6 ]; k( p% i                ret = -ENOENT;6 K- H0 |) _9 n" d8 F0 N4 E' k6 y
                goto err_req;
. A/ @! c5 Q! F4 L, N7 c' \5 G        }/ x7 X! D& ?! s6 M5 Y; \# B
" p. A: _/ x+ F" e
        /*内存映射*/" e5 i0 d5 u* ~, b) }6 P
        wdt_base = ioremap(res->start, size);
2 f: ]: v" [+ `- o& y        if (wdt_base == NULL) {
) u, ~" \) a% }. J$ d                dev_err(dev, "failed to ioremap() region\n");: D" f+ l# ?5 V7 w; _. V/ I) r
                ret = -EINVAL;
6 c6 ^( U0 S# k( @/ A, ?/ S                goto err_req;* i2 Q/ E, ~: a( I- E1 e, ^9 S
        }3 e  M$ Q, d) M% c# g
% W2 M0 T: _# B0 G9 d
        DBG("probe: mapped wdt_base=%p\n", wdt_base);/ l2 Y; |1 f, N' ^
        2 Y4 V# }* Z2 {: y. @- G
        /*获取平台资源,看门狗定时器中断号*/, X; Q; z7 q' P( q
        wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
4 y, f( j$ G) @' ?( S        if (wdt_irq == NULL) {1 H0 T7 l6 d$ ~, C: @: c0 v
                dev_err(dev, "no irq resource specified\n");' y7 Z) T+ f# |$ R% e# B7 d
                ret = -ENOENT;
1 l6 ~( C' q% r7 i& W1 e& p) e                goto err_map;
  i. w+ T; ]7 P        }
5 I0 D% R/ E$ g, q% y- B  j       
* I% k* B7 S4 T# j        /*注册看门狗定时器中断*/; L- [4 a$ O6 d% k1 y. _
        ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
& J5 Z' [3 `' C: H  m$ |8 c- g        if (ret != 0) {
) R8 P/ ^0 t& w: J- ]$ m+ r                dev_err(dev, "failed to install irq (%d)\n", ret);
  ?9 ~" `4 w4 d7 F                goto err_map;3 _$ ?9 u3 A+ ~" @! E1 q  Z
        }5 r( R; L5 q4 }- Y# x  F
        /*获取看门狗模块的时钟*/
) i" j3 ^! I" p' J        wdt_clock = clk_get(&pdev->dev, "watchdog");
5 I( u" f7 W% h$ |- _9 ?        if (IS_ERR(wdt_clock)) {
) [/ p4 X" V5 S$ q4 B                dev_err(dev, "failed to find watchdog clock source\n");2 F; D9 B. q0 N% b! ?
                ret = PTR_ERR(wdt_clock);
6 ?3 o; H* z/ n" u: Y4 D                goto err_irq;
* _, K, y9 ^( A0 e/ P        }
- N- u: I) G8 M  B( C
: k5 S* S; h/ V0 n9 j. h        /*使能该时钟*/
+ e+ r5 n  E- v. j; h. J5 A$ ?        clk_enable(wdt_clock);
- ~8 _' }/ V* [* q- M- s7 T6 J+ e' w/ s$ V$ Z8 S& {
        /* see if we can actually set the requested timer margin, and if
7 `* }" v0 `; k- Y" H! v2 P0 f         * not, try the default value */
- U6 y) l$ g/ T, [+ G  W7 P, g7 f( @* K! S: w
        /*设置定时器模块的时钟频率*/$ H7 c/ B+ i4 P6 n
        if (s3c2410wdt_set_heartbeat(tmr_margin)) {
0 W3 t7 F* M& s" R( b                started = s3c2410wdt_set_heartbeat(. ~! T7 x9 {5 t5 A
                                        CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);/ R4 v2 C" y0 z7 g9 h+ }1 q2 W, s

, y0 Y! U  e( w! U                if (started == 0)' C& R* R+ K* {8 ?
                        dev_info(dev,
, Q1 G: g, |( B4 `                           "tmr_margin value out of range, default %d used\n",1 T3 f$ G5 Z# y6 _9 o+ P  r
                               CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
" d6 \- [  w9 n( D                else
7 w/ o! j- e4 I4 O                        dev_info(dev, "default timer value is out of range, cannot start\n");$ W! ]) p) E1 o$ U
        }
8 a, L. L6 J* u. E* U7 w; ^" [) R, Z  c4 b5 W& D
        /*注册混杂设备,设备名watchdog,次设备号130*/. B* T, B: f" s8 p0 ?% G8 M7 W
        ret = misc_register(&s3c2410wdt_miscdev);
8 r% S, k, e" I8 O6 _9 b        if (ret) {
% Y; o. k, z1 E8 X$ a1 K1 E& Z                dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",$ O+ d6 K$ @' u  V( V8 m! \
                        WATCHDOG_MINOR, ret);1 I7 `) q0 s* E0 ^% L) S
                goto err_clk;
0 D# l! g+ m7 {" Z        }
5 |5 U/ j/ M) e2 M0 K, c
7 B! l* V/ Z- `+ o8 V1 @( j        /*, S' Q! }. j( o8 J" B4 |
          *如果需要在看门狗模块加载时启动看门狗则
6 m/ v! G" {# \2 H' Y8 F: k          *调用s3c2410wdt_start,否则调用s3c2410wdt_stop- T' d% T5 B. U) {
        */
3 `% b+ u  N: T/ z* a9 T! B% \; A        ' `5 t+ z1 _# A- e
        if (tmr_atboot && started == 0) {
# Z+ f( r5 t! Z2 o0 t3 o                dev_info(dev, "starting watchdog timer\n");
4 C- H1 n6 |" n* I6 J1 e# o                s3c2410wdt_start();! E+ Z! `3 i0 t: S
        } else if (!tmr_atboot) {
: }  N( D1 v8 m9 |# L0 D3 S$ D1 z( H                /* if we're not enabling the watchdog, then ensure it is
0 r: z3 s5 P( s- L/ G( d: x                 * disabled if it has been left running from the bootloader2 C/ u+ N5 W, h5 W
                 * or other source */* V% P) u1 C/ Z% a* w- M$ t

2 i5 y& n; b% ?' F7 D                s3c2410wdt_stop();; W3 c( M2 G' G0 D; o0 _
        }7 \( x5 t% e8 p$ a& Q4 b
9 j! {/ n& B$ v' Z2 T) d( w- _5 n
        /* print out a statement of readiness */
$ g4 u( s7 J3 H6 U        /*读取控制寄存器,打印目前看门狗的状态*/7 s& I  X% H" z; H
        wtcon = readl(wdt_base + S3C2410_WTCON);8 \: t. ~! p" x5 N+ b% M
4 ]! L/ v5 L0 [7 J
        dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
0 z( p, c7 B/ F. C) A/ H4 h8 A                 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
% o- f, q5 ]) d7 S0 M" O5 t                 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
) o' {: M2 J' ^: [  L4 U/ w                 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
$ c" o) D7 |1 U8 B8 n, D; t4 q6 `; D+ x
        return 0;2 t7 D4 p' `% v( m, q
9 o* O! B" G  Q
err_clk:
, T8 h' G1 p3 `/ S7 h+ ^        clk_disable(wdt_clock);1 ~4 X  Y5 z3 X9 w
        clk_put(wdt_clock);
; P( S1 f, r4 @! W/ R2 c1 R
4 a, O& N' }% @2 U- r$ Z; h err_irq:
# D$ U7 S( [# u0 o+ L6 L3 G) @        free_irq(wdt_irq->start, pdev);
; u5 g; {% o9 `- E% z+ t2 c+ m$ v+ N( L, I6 w
err_map:8 O' H  c" F+ |
        iounmap(wdt_base);9 F: I0 K" k# K# r

0 }% o0 k9 z1 H err_req:( K+ @8 G2 A% E5 ~* q) h
        release_resource(wdt_mem);
/ X; K  h1 L- ^) L        kfree(wdt_mem);
3 P5 e, U6 L; w9 v: p5 b5 W! E3 t2 G! t! G# P5 |! a
        return ret;
& J* j2 |$ h! U}( i! Z: L) S8 J* Y/ |( @8 s

5 B' v& E+ n; {3 @1 H该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。; j. _$ A; I" x3 p
! u4 W8 Z- N" V3 T" G" ~, y( h
然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。
. ?3 n# X( r9 D( k9 v: _  ?0 {( H& a4 r6 [' A: S7 s
上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。9 j6 x0 M5 u7 @6 I
" K, ^: G/ n* B# x9 k
PS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/ARM/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。
" y% I/ a( `& d! q+ Y% \0 I# V$ Q4 L
3.2 s3c2410wdt_set_heartbeat
2 b& g1 D1 I9 u; ystatic int s3c2410wdt_set_heartbeat(int timeout)  /*timeout 超时时间,单位秒*/
: X$ K/ g( D5 }/ R/ x{, V& i1 A2 J% P" _" b0 O" H
        unsigned int freq = clk_get_rate(wdt_clock);  U- u: Z, l" }: c% h
        unsigned int count;
( Z9 T2 ]3 w1 e0 V        unsigned int divisor = 1;
- W/ r7 w5 L8 g' ]        unsigned long wtcon;
- U# p" o3 k& \% D2 U* n6 s
, }  T+ q* `0 S  z8 s. P; S7 b        if (timeout < 1)1 `( Z, d: s! S4 v
                return -EINVAL;
$ u4 O& @# T& s, k, k8 W9 B" o6 e. u
        freq /= 128;    /*时钟源为PCLK/128,不使用16 32 和64*/
, O2 o; J6 X/ y: F1 ^! `        count = timeout * freq;  /*得出计数器值*/; s* M' K8 c2 j8 ^4 ?5 h) q  H: L" u

/ }" I* p0 B* s        DBG("%s: count=%d, timeout=%d, freq=%d\n",$ h1 I  t5 q/ \+ k" P/ t
            __func__, count, timeout, freq);
9 P1 M9 N3 ^% K5 u* e! j% F+ v+ t& c
5 F: `+ C: ]1 X# a        /* if the count is bigger than the watchdog register,
  ~+ s# E; d+ C  u( j           then work out what we need to do (and if) we can  X2 `1 }/ z, `* B: U
           actually make this value0 C2 e; |# O& B6 r. e
        */
3 N$ k- `5 T" G: ^% C0 s/ T4 U        /*计数器最大值为0xFFFF,如果大于则要计算Prescaler value*/7 M& y) Y$ k& k. C1 O0 P5 N
        if (count >= 0x10000) {( U4 Q/ ?" R$ n) ]: U+ W
                for (divisor = 1; divisor <= 0x100; divisor++) {     /*Prescaler value最大为0xff*/
7 K% n; ~7 {  B                        if ((count / divisor) < 0x10000)9 j! X& I5 Q, A
                                break;2 y4 D, S  e$ }% Q, `4 a/ R4 f, n" r
                }
' y. D% m6 M4 ?( u& Q        /*找不到合适的Prescaler value,报错返回*/: |$ x6 r4 z& q. W3 Q* P5 t
                if ((count / divisor) >= 0x10000) {6 F# d9 K3 M0 D: r
                        dev_err(wdt_dev, "timeout %d too big\n", timeout);
0 I% B! @7 j' ~9 I- G0 z1 [                        return -EINVAL;. t6 o8 B5 c3 b8 n+ x$ C( S- X
                }) C% I/ Y9 D- D! x& ]  [
        }! L( S. u; v% s6 C0 K: C

* [' J0 W- v& [5 x4 a* P        tmr_margin = timeout;  /*保存timeout*/
- X8 m0 Z0 l' W$ k; b1 i& W/ b% g, p, X: T8 h! k# v
        DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",# o8 C1 n* t: n/ I; ]
            __func__, timeout, divisor, count, count/divisor);4 c& V, X# o) P6 v' q5 }# W
, G% f7 ^& M- M' D5 }5 ~
        count /= divisor;   /*根据Prescaler value计算出新的计数器值*/
. w0 a. y# c0 ]1 X  n" C: Q7 m        wdt_count = count; /*保存计数器值*/, G. _$ Y8 b' y" K% O3 f4 B

) L: `5 z9 T( |        /* update the pre-scaler */ % n' I( |1 U2 M( H$ q1 i
        /*6 {( a, v4 a$ }
          *设置预分频计数器和数据寄存器
! X$ A& J# Y# s' F. l' q& Y  W        * NOTE:此时并未使能看门狗定时器
+ R7 b# C: O3 t1 n! G" k           */
) J+ f/ W; a4 Z+ |- v        wtcon = readl(wdt_base + S3C2410_WTCON);7 v2 G$ J7 `0 Z# h
        wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
8 K8 m! b  v( T  p4 L% Z        wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
4 G* y% ?) B  y9 B& [5 h1 n+ I. I6 [/ S$ d
        writel(count, wdt_base + S3C2410_WTDAT);
5 I# \; r( |5 F  D        writel(wtcon, wdt_base + S3C2410_WTCON);5 w" }# G* n$ f7 j! w# l

: s2 w& E: e, @: p7 P& S, K        return 0;! Q/ k" Y& k% [5 T8 @
}
0 U8 O0 X9 D# ?3 r" V7 Z6 a1 y9 I( P0 A2 z6 L4 ~+ @
形参timeout为定时时间,单位为秒。) u6 c- O# x+ P1 q3 L

% @9 K, r% K# z  这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。
; }+ T8 ]0 b7 W' B+ c- V6 U5 m4 N% U
  在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。4 P2 D7 n2 o- D

3 P6 @+ R: `8 |% @* ^$ K& N  当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。
' u+ Q' d( r3 P* A
+ P/ C1 n% W- p7 J& k3.3 定时器的启动、停止和保活
2 T! Z6 w: \5 c% |2 W3.3.1 停止& B- y- f+ s! l7 v2 D1 @
定时器的停止由 s3c2410wdt_stop函数完成。; S7 P% [7 ?4 r9 f4 o9 j2 s

% M8 z$ S) j; r: C- d1 Q1 estatic void __s3c2410wdt_stop(void): k3 i4 p% `5 {- S% C9 d
{) h! c  u5 A0 a: |$ @$ U
        unsigned long wtcon;
, {- Y+ j9 ~: O- K+ i3 z! n! c; T# g  S& i
        wtcon = readl(wdt_base + S3C2410_WTCON);6 p; Q5 F' t4 t7 V4 d. y- ^
        /*禁止看门狗,禁止RESET*/) |8 h( s( q3 b9 Q- j4 c
        wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
' T  {- i  L  `; v        writel(wtcon, wdt_base + S3C2410_WTCON);
# k% w0 Y/ M; X$ C- ~  S}
5 ^% N; x$ Z+ V2 E3 `. A9 q7 a. Z$ p- U! t6 f) R2 z" u1 M5 H4 w
static void s3c2410wdt_stop(void)
9 @/ n0 T" U9 k{
( P! ]/ b* D1 }4 u& T# m    spin_lock(&wdt_lock);% u7 g6 G, L) s) d4 f) O/ K6 f
    __s3c2410wdt_stop();) |3 G( Z; ~: [6 S" o$ y& D
    spin_unlock(&wdt_lock);# \) A, t6 n2 }1 g: }
}
1 G5 `* l. z5 l
; j, _! k4 Q# H3 u3 e3.3.2 启动
8 g4 X/ H5 R* M3 q- `* F定时器的启动由s3c2410wdt_start函数完成。
6 r, j; P4 K. I& Z
+ @" L4 V; e% W0 I- Nstatic void s3c2410wdt_start(void)
1 G* F, n  j1 _* Q! `' d/ V( }1 `: Y+ ^{
7 D. r- U  Q0 Q) Y$ s! ~  P: G        unsigned long wtcon;
' {" d# u7 w4 x8 o4 D2 L9 y: `6 z; y2 g3 m/ Y" K: y
        spin_lock(&wdt_lock);0 L4 a- v! n! c$ C6 O" L9 ~

% p& v" O  m5 {, C$ K4 R1 `  B5 u        __s3c2410wdt_stop(); ./*先禁止看门狗*/
2 G; E9 x7 w+ J$ q/ N. g1 V2 B) `! q: M0 d) G( d7 N
        wtcon = readl(wdt_base + S3C2410_WTCON); /*读取控制寄存器*/- W( _- w, o: Z( W1 [- b
        /*启动定时器,设置分频系数为128*/1 L- F+ e9 m/ g# Y, l
        wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 2 I0 p6 `. O/ y2 L, F# S3 W

9 [& T5 R7 l& |2 U3 M8 _9 n        if (soft_noboot) { /*判断许是否需要RESET*/; _( z  w8 g- e/ Z  Q4 |1 p
                wtcon |= S3C2410_WTCON_INTEN;          /*使能看门狗中断*/
0 g5 O" L0 G8 Q) ^% \                wtcon &= ~S3C2410_WTCON_RSTEN;        /*取消RESET*/
5 f- N0 w5 U# l5 U7 W- q        } else {  /*复位*/4 t2 V. k# ^# m# f4 ]
                wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看门狗中断*/: `- n" Z6 u& H
                wtcon |= S3C2410_WTCON_RSTEN;  /*设置RESET*/
" L. R: A9 y) i$ L  F& {        }4 B; g3 r8 l& v% g

& q5 Y( A5 _  Z" L        DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
. h5 ~" t; g8 j) N/ ~            __func__, wdt_count, wtcon);2 }, k: M( D8 L/ A$ K+ D
/ N' y  W) f$ e2 {" B
        writel(wdt_count, wdt_base + S3C2410_WTDAT);6 m  {% H' w) I: C6 ?
        writel(wdt_count, wdt_base + S3C2410_WTCNT);
& o% c4 n5 L4 ^, n/ C) Q        /*写入控制器,此时将启动看门狗定时器*/
7 p( N! ]7 u, s2 g% l        writel(wtcon, wdt_base + S3C2410_WTCON);         # h) N  I$ |4 ~  S) k. x! s
        spin_unlock(&wdt_lock);
: \0 j8 L; A; |+ h! M' V. @}5 s/ u$ r' U# g; k. p6 M$ z6 o8 v
在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。$ \& x3 x# Z- U9 |7 S

; g4 N* ~* ]3 i% k0 E3.3.3 保活
) m3 J( ~. r, [1 d定时器的保活由s3c2410wdt_keepalive函数完成。
" h+ m3 \: k% |7 b! }1 H( c# }0 d) w' P, V% H' k
static void s3c2410wdt_keepalive(void)
6 q! ?9 X" Z8 y# n( g8 W0 z{" i7 Y8 A& U. S1 A3 L
        spin_lock(&wdt_lock);$ Z  I& {4 e7 \; c+ N  z3 T
        writel(wdt_count, wdt_base + S3C2410_WTCNT); /*重置计数器值*/$ y' d! S" c! A2 Z
        spin_unlock(&wdt_lock);: ]$ e& g8 K+ @& v  u
}
3 u9 B2 t3 N& V  }' c最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。
3 K8 b/ P0 y* s5 }
4 q6 T' u8 s; t3 B' V
7 y" O, G. _* I" B1 A3.4 看门狗驱动API4 @3 s  n7 Y* ^2 m" g$ b
看门狗驱动提供的API如下:
+ @4 x7 G1 y4 v! X" R
# V. |2 }8 ^. O5 V+ ?static const struct file_operations s3c2410wdt_fops = {
% l0 C, g- u% U# [        .owner                = THIS_MODULE,: b& n+ H6 W0 J$ y" A& m
        .llseek                = no_llseek,
8 l) M- l9 d8 e1 v% L7 ?) \        .write                = s3c2410wdt_write,
5 w; x+ U+ e1 G; y" O' ~        .unlocked_ioctl        = s3c2410wdt_ioctl,
+ R; N, B# x8 v% m- j# I, H        .open                = s3c2410wdt_open,
  \) C) i5 Z2 y, c7 a) G2 Z4 |        .release        = s3c2410wdt_release,1 Z* [! Y! o) K8 A
};
9 l! w7 V7 G9 k- r5 d' N
7 X% A7 M% d0 H" E我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。" z- O3 F' z) I. B* ~- B* j& j
3.4.1 open方法
; c4 j% f! k) D) Fstatic int s3c2410wdt_open(struct inode *inode, struct file *file)( o% a: `$ E& N# S: \
{( }) L6 b7 c* M% h
        if (test_and_set_bit(0, &open_lock))/*看门狗设备文件只能open一次*/
9 x/ G1 {5 ]# K$ X# j  o                return -EBUSY;% v; a6 p7 \! I* ~8 ~

0 P$ O% a0 k7 Q; i- E        if (nowayout)
  s6 {) T7 D5 M" V                __module_get(THIS_MODULE); /*增加模块引用计数*/  j& x5 L4 [+ ^" z! i

# m0 T) R% N$ B* l        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/$ \/ A+ F4 x# }
+ b3 w  T2 F: g1 S4 t  T
        /* start the timer */$ Y1 a: R- f. ?
        s3c2410wdt_start();                /*启动定时器*/
) T- E: t5 ~- o1 c/ ^        return nonseekable_open(inode, file);  /*告知内核不支持llseek操作*/- S" S! \1 t4 l" L/ N! A. @3 B
}' l: g0 ]6 l* C, t
/ H& m, `$ K0 i
这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。
, u  [& `: A9 a( D3.4.2 release方法
9 A4 i% V+ L1 ?: Dstatic int s3c2410wdt_release(struct inode *inode, struct file *file)- F5 \* I# f( \
{$ {! [5 O# a9 h) |  K! g% ^
        /*" @' }4 t- v8 l" V$ l" d: G
         *        Shut off the timer.
1 s1 ~) }: W1 \/ I         *         Lock it in if it's a module and we set nowayout
5 l9 x6 A. T7 g7 A. m" j; W         */+ k3 {& i$ [3 U6 A% Z: w: e
          /*状态是允许关闭看门狗,则停止看门狗,否则保活*/, G% h* r8 p5 P( z
        if (allow_close == CLOSE_STATE_ALLOW)
" m6 e' E0 [% X" v* Q5 w2 ]                s3c2410wdt_stop();, h7 ^; ?$ W% I
        else {
" e: k* U8 s9 T6 S* d0 u                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
( s+ h8 R4 B, }" G! j                s3c2410wdt_keepalive();
9 |- L9 b4 \( W; U) O        }
& N/ r, W/ t3 y. C0 O        allow_close = CLOSE_STATE_NOT;  /*设置标志位,不允许关闭看门狗*/& ?. c7 F3 O9 d  a9 i
        clear_bit(0, &open_lock);/ l3 ?0 A- Z' M4 U
        return 0;! |+ q$ g, a# k  M& [9 d1 M3 R
}$ c' A5 L. h# E# ]1 ^6 |; c8 S
3.4.3 wirte方法
0 i; {8 E$ Y& f8 ]static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,, N! [8 @/ d  e4 `
                                size_t len, loff_t *ppos)
3 D- }! r( w- R  u6 \+ A  a{0 ^1 L- p9 E* X6 U6 Q. ^" A, u% g
        /*
% l3 D8 U% B7 K6 \         *        Refresh the timer.
( J1 v  Y/ f+ h6 m, \         */" y( X1 X- {$ M4 P
        if (len) {
) C3 i$ o. h0 f) ~                /*nowayout 为真,不允许看门狗停止,使其保活*/& Q: \: U* D7 F' F# @) R7 F
                if (!nowayout) {' O, a3 {- |2 w: `7 @& |
                        size_t i;* V* l4 j8 r+ I& v- s

1 {$ q  o7 {  E                        /* In case it was set long ago */
, D( p$ ]+ Q5 g7 L. E6 ~                        allow_close = CLOSE_STATE_NOT;/*设置标志位,不允许关闭看门狗*/
' Q0 R& W: S- P- h. t8 I: U9 L" x2 i
                        for (i = 0; i != len; i++) {; F( ?: O' y' B2 u' T' [
                                char c;1 O* i2 ~+ b" P2 w0 v% G
5 Y$ l0 E* f# G3 {  ^) r2 J. U2 G
                                if (get_user(c, data + i)) /*从用户空间获取一个字节的数据*/
$ F* ^4 }+ R9 O2 b$ @9 ]* k. [4 [                                        return -EFAULT;
5 }" e5 J" I7 A& [                                /*读取到字符V,设置标志位,允许关闭看门狗*/5 u. Q4 ?: ?" h/ A2 W, u! T( S7 I
                                if (c == 'V')                ' Y6 `7 G% [( {
                                        allow_close = CLOSE_STATE_ALLOW;
1 l+ f& M8 r+ x5 e+ s& J6 }                        }
) X8 C* |" Q5 X- m7 T/ _" Q                }
9 O4 O- C) L" |% a) c                s3c2410wdt_keepalive(); /*保活*/& W4 _% f' B. t3 g% W; a
        }
- ~- K" L5 \, }: f' N5 L$ Q3 w8 l& s        return len;
1 g1 j3 J, V, A& |5 d}
" v1 u5 `) L+ Y. c" T) I2 _  u% U. }" ]1 P
只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。$ G( h2 V8 Y. D- k( F/ D* G
+ V5 t/ v4 P6 L, ]# [( i8 ^/ S
3.4.4 unlocked_ioctl方法
9 _; Z9 G# J, z+ s0 S6 S3 q# `' F( sstatic long s3c2410wdt_ioctl(struct file *file,        unsigned int cmd,
0 y; L3 E* m+ W+ ]% }6 y! R( e* H                                                        unsigned long arg)
) `0 F9 z  X% H, B% X{0 o( }( C* _  X" F: l* X
        void __user *argp = (void __user *)arg;
# i0 C2 a$ l! X        int __user *p = argp;
5 P  F% p/ m9 r+ @" b8 ]! J, o        int new_margin;; l1 N* y" c6 N2 }2 i; A

1 b- J! I$ W, o( C* L% O$ z        switch (cmd) {7 U- n) q  X' A( N9 U
        case WDIOC_GETSUPPORT:
# E! H8 e6 l" z: C( {                return copy_to_user(argp, &s3c2410_wdt_ident,
  O# V1 v. ^& P! W' r+ ^                        sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
. D# K/ q/ b9 f; _' a& \        case WDIOC_GETSTATUS:
- x8 j) Z" F! F) z4 h        case WDIOC_GETBOOTSTATUS:
) o1 Z" ]6 q7 J; f# s) {+ a9 s                return put_user(0, p);6 G4 B7 N8 ?( O4 D& @' f6 i1 [1 b
        case WDIOC_KEEPALIVE:; M2 ^4 W  i( K
                s3c2410wdt_keepalive();
4 Z( T4 f6 d8 q$ p3 |( l                return 0;) N# ^8 \) h  X- p; y
        case WDIOC_SETTIMEOUT:
7 U  B+ N5 K6 r8 ?, u4 l% Q                if (get_user(new_margin, p))
/ u: e- L! m% H6 A) e3 R( q& R2 }                        return -EFAULT;/ Z0 N' I6 G) n3 m/ U
                if (s3c2410wdt_set_heartbeat(new_margin))
1 [* ?3 q9 {/ ]0 R                        return -EINVAL;# C: U  n1 C0 A6 r  m. l  u
                s3c2410wdt_keepalive();
" a' V: G( o  ]: A4 P                return put_user(tmr_margin, p);! o# @# J( A* x) }% `; S; K
        case WDIOC_GETTIMEOUT:
( J9 V1 `2 u9 I0 m4 u                return put_user(tmr_margin, p);0 M5 |' N4 i0 p3 P
        default:
: {5 z: a8 M! R) ?                return -ENOTTY;
/ a8 C- ~: }" X* a3 z        }9 ]/ `6 H3 l0 e4 ~) C$ Y1 r' T$ b
}) [4 X5 \! H, ^
4. 测试程序' s; ?5 k! L- ]6 E
#include <stdio.h>
1 V% _" k+ r) ]3 l9 ?9 `#include <unistd.h>6 x/ L7 z- C  j6 V+ p/ |/ L  L$ U
#include <linux/watchdog.h>
$ o5 E$ s. \5 H+ G#include <fcntl.h>
3 y6 {8 i' P; e; }0 F; i: D5 v. @' m
int main(void)
# p( j) l1 M0 J9 K{
/ ^' r7 F: P) e: d! t        int fd, val, ret;
0 q1 Q0 U$ F" C" K       
  X' W6 b1 `( d2 |6 i" C6 Y7 p        fd = open("/dev/watchdog", O_RDWR);
. P/ c3 I& [1 A8 Q: Q        if(fd < 0){
9 z3 L- K& ~' R1 A                printf("open device fail\n");- J# I- W9 Y8 p  A! A: ]( b
                return -1;3 L" n; W" ]# d$ W
        }( P, _7 ?8 w3 e! @
       
# m7 d1 B9 A$ q4 ?  W        while(1){; T; Q1 h  r4 X4 \  i1 |% Z  r! a; @/ Y
                ret = write(fd, &val, sizeof(val));
, W/ p7 F# J( y# e                if(ret < 0){& z) W6 l% w: M# o) Z
                        perror("watchdog write wrong\n");
8 n7 w1 [$ e5 V: \                        return -1;
; x) Z. @- Z1 a                }
) G% k% _2 z1 w! Z$ e# w. W5 D) M                sleep(5);
, Y. s, S& N$ h4 q' j7 R        }
% f% y* E; {* f! q# f! f* A  w2 \- J        , R8 k0 X. C" \! k
        return 0;" X0 A  ^8 X9 d& x6 f$ O9 S
}  ~0 c' |0 M) D6 s' b) Q4 R1 A% S

3 Y2 T3 l1 A+ ?" t$ ~( a8 C# T该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。
: j/ z7 A3 ~; b可以将5秒替换为16秒,你会发现系统自动重启了。; y3 E; ~9 y( J

3 A$ W$ L( z8 J5. 结束语
- t( Q% J5 b' i) l   本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。5 T; }6 @; j# t- O
8 L  y9 M1 ?7 l
* ]* ?( M5 F" v! \5 B7 H; Q9 P

1 v1 b0 P. h3 N, V* F! @
  • 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 23:05 , Processed in 0.171875 second(s), 27 queries , Gzip On.

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

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

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