|
|
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
|
|