|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
本文将介绍看门狗驱动的实现。: E" B4 x6 }" N+ P2 o$ y
& b9 R$ _% M9 N, H# y$ p& _
目标平台:TQ2440
- f: D, x% B; B3 o0 q6 e6 x7 L
7 m9 V' |( ]! g- RCPU:s3c24404 W# d# {( n# S! H8 v$ Z3 x) f! ]+ e
8 Z9 E3 c5 ~9 \1 L
内核版本:2.6.30+ ]: L4 D* v2 c& u; H6 L: q
) ]+ R/ k) @9 V) [' E
! t _3 {% T$ M7 K. h- |
, y$ T X! _0 \" b; I W1. 看门狗概述* M! [) V$ ?/ X1 m) ^% z9 y$ A
看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。0 [7 B% l% t+ p' }- S$ l* z
0 C+ l! x& X2 ^- n' f" y
因此,看门狗通常用于对处于异常状态的CPU进行复位。
; z3 C; K- u d) f. {1 d, o" S0 {- w- @/ @( p0 c6 ~
具体的概念请自行百度。
# l! S# p, c# z G+ J, r' k' |' @! S( C: R: @& b
* j' Y( k, J2 S! X8 |+ B
2. S3C2440看门狗
( y; k+ }# C- d) U3 I- Z" V! E s3c2440的看门狗的原理框图如下:
+ f! \2 ~- N# C7 y* a3 u# w @+ x% A) {0 p$ _+ k# _# Q* c
( u2 o/ n# _' Y; m& t$ i
/ v0 T P) Z }* ^1 a& Q
可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。% _# m, w5 W6 h, B/ k0 K
0 y/ l+ z( l% z8 R( ]6 b& A$ h% G 定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。
& f1 V& g6 ^6 O5 Y2 f9 y
+ f3 p/ @$ E& |, V4 z, ` 看门狗定时器的频率的计算公式如下:
0 j7 R& c+ N4 w; z8 c( x# I' a; H1 x1 Q2 V
. _) D9 _. X ^0 t) v o, d1 n: k( u0 \
: s+ E3 H5 O, M3 g; o, \
' ?9 d$ s; f$ e ~: v7 z
3. 看门狗驱动
( J9 |" P& l- \* i |' T( R( g 看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c. f" t0 ^( V% }7 j
5 k, g9 S+ _$ ?+ C- K0 I% L3.1 模块注册以及probe函数
& v2 ^% i8 {6 b9 r I& n# N$ Ystatic struct platform_driver s3c2410wdt_driver = {! r8 Q7 W4 K6 o- C4 y% s P: }$ N
.probe = s3c2410wdt_probe,- V1 p+ b; a3 R+ \2 G# c/ I( U
.remove = s3c2410wdt_remove, U' s4 j* k" U4 A5 _& p) r
.shutdown = s3c2410wdt_shutdown,, @4 J/ |% R- H4 F) \9 V5 D
.suspend = s3c2410wdt_suspend,
8 `* p8 ?% @' E; o9 I4 O/ G .resume = s3c2410wdt_resume,
$ V% D {1 D% d7 f4 _3 s, V .driver = {0 O/ `$ b: A; f# o' Y
.owner = THIS_MODULE,
) s& ?5 R. [6 e( N .name = "s3c2410-wdt",
; U) n" K3 N9 q6 \4 T) e& H }, q6 e: r; e1 U
};7 l/ ?& k* G" G, l+ u5 b, Q ]
0 b/ n8 t9 i5 e& c8 F; Z
static char banner[] __initdata =
! z* E) z' ^2 d. t3 z+ p KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; 0 [" U9 R" H5 z. J- u7 H- A3 c+ z! H3 S
* t/ r# F6 l: v% [+ f9 M5 \static int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}: \6 |$ I# C+ o4 l& e( M1 z
5 E T! {$ }% i. qmodule_init(watchdog_init)
B4 _7 v6 ?. ^6 M模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。6 p4 n) A* y% L8 ?
5 ?) p1 a: T0 _; k! N 该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。
; M# L; ?/ T' @
' f% ~. k* f! Y9 s7 }0 `" N 我们来看下这个函数:; Z" s8 ^! @3 q" `6 Q! k" W
6 ^% s" N" u& x" E- R# Estatic int s3c2410wdt_probe(struct platform_device *pdev)7 P( ]/ d4 }% O# |. a0 j E
{! m4 Q& @( \6 i- m% u: Z
struct resource *res;3 c6 Y* f! s+ p
struct device *dev;
* I% X" v. B3 g* t" F unsigned int wtcon;8 C4 q) `6 T/ [! {
int started = 0;( v0 V: t& q }+ t# D3 m) N I
int ret;/ o) t9 J0 J4 Y
int size;* A$ S9 b7 u$ }* T) d8 S
# a; P6 V4 r5 K5 i: f# m' h9 B9 q DBG("%s: probe=%p\n", __func__, pdev);0 v$ k2 G3 b% n/ c
q+ Z5 Z6 n" j& X7 P dev = &pdev->dev;. ?/ m# Y" b2 s
wdt_dev = &pdev->dev;
, A% ~, }# A$ I5 O
9 B1 K( f4 u1 k8 e6 @2 Q /* get the memory region for the watchdog timer */$ x2 }( U8 E2 j8 k
/*获取平台资源,寄存器地址范围*/* |% t# ?, u* i, D1 o/ m0 t
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2 ^$ a3 M3 x2 k2 t7 @% r+ C1 g if (res == NULL) {
, @/ b. j( e! q dev_err(dev, "no memory resource specified\n");* W( {! |3 O" P. w8 A( v; S0 z
return -ENOENT;' o! \# H; O9 ^' h' F, d
}
( x& p) g+ h4 Z; q6 n8 i7 c4 j" k0 m' @5 e/ I# K, u0 J( m. W7 \! z
/*内存申请*/
4 {* n8 R- I+ _5 f size = (res->end - res->start) + 1;
" a" `6 Q0 {5 e9 y wdt_mem = request_mem_region(res->start, size, pdev->name);& g" ~2 P! k" K. q6 ?- X
if (wdt_mem == NULL) {
: D# K# M6 T ?5 b, X" t dev_err(dev, "failed to get memory region\n");1 F! m- ~$ L$ t6 r$ [6 K
ret = -ENOENT;
2 R. A# R) d5 `& Z5 U goto err_req;
+ Q: P T0 R6 J7 F# { }
& e8 K" G, b* F& Z% W% v/ U' {1 u- F6 T6 B9 S
/*内存映射*/, O# ^( |# ^# Q% L! B7 D: F- R" I4 x
wdt_base = ioremap(res->start, size);4 h3 u* t6 T" _* v
if (wdt_base == NULL) {
" I6 Z: h; f5 ~4 n3 d: f* m! G dev_err(dev, "failed to ioremap() region\n");$ {! B% y& R- ?, v; A
ret = -EINVAL;
0 V% k: t% Y1 }2 a! ` goto err_req;
1 ?3 H1 _! V- a f+ C }3 a( l7 M: ~6 D( M X1 i
/ M( ~( x8 j8 s" p DBG("probe: mapped wdt_base=%p\n", wdt_base);5 A9 }$ m! P& _- ]+ z4 z+ F# S) g
; ]8 C" O/ C u3 u) K8 }' q /*获取平台资源,看门狗定时器中断号*/7 H6 G! v% G9 h$ G
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);- X N9 W& {) Y+ l& j: X: Q3 l9 [
if (wdt_irq == NULL) {
1 ^8 y$ \; w+ ]9 f' w2 _ dev_err(dev, "no irq resource specified\n");+ [0 l; \. I1 Q- d6 k
ret = -ENOENT;% x, k; A" n* J: N# G
goto err_map;* A$ K0 R% @9 x! }0 s% C- `" h
}" C( S: |# v, @1 c& e6 [
" l; b# O/ J/ |4 H v9 ^# L6 x /*注册看门狗定时器中断*/
0 D- {' \3 Y' q' Y5 ~ ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);) T. ~ ? A' G0 ^, H. \
if (ret != 0) {
! b% Y. I5 ?' N0 V* ]1 t* f2 A0 i' | dev_err(dev, "failed to install irq (%d)\n", ret);
1 n9 u9 r* t. J `/ q goto err_map;# p. v4 d4 i$ I Z
}2 `) L j; ?; P7 @& j( o
/*获取看门狗模块的时钟*/
/ v/ L8 ~9 l' k- d2 q4 [2 Q wdt_clock = clk_get(&pdev->dev, "watchdog");: X& u% Z J3 n- h
if (IS_ERR(wdt_clock)) {7 n8 [3 F1 {' ]
dev_err(dev, "failed to find watchdog clock source\n");
" n% O7 E4 U8 l) W* Q' E# V ret = PTR_ERR(wdt_clock);* U0 S0 x& \9 Y4 \. F7 n
goto err_irq;4 P" _, w. V% y; z
}4 S5 c( u" o) E
) x$ c5 @& N0 w" y* Q /*使能该时钟*/
% u( @' O$ Y* `3 ] clk_enable(wdt_clock);# a( Y" M9 L% U( A$ c K8 j
* ]1 e) y! M& E$ J& r
/* see if we can actually set the requested timer margin, and if6 k& S5 P$ F5 {& b" G9 c% f
* not, try the default value */
" A2 \1 O& ~1 |
, H8 C y8 ?6 |1 `2 w' g+ }3 c /*设置定时器模块的时钟频率*/
# t* w0 C. c, u# ?. s' ~( c if (s3c2410wdt_set_heartbeat(tmr_margin)) {, L# x/ }$ M3 @3 G. Q4 |
started = s3c2410wdt_set_heartbeat(
8 J+ c4 w3 m$ a) H7 E1 A CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);) ]3 Q, b6 _( c/ l8 v& P: `; w
& u$ l- b; d0 A5 _3 j) x" x5 ^ if (started == 0)' ^0 |3 i& A R( J, M7 `5 C) W. h( ]
dev_info(dev,
7 j% v6 ^( M3 D* r. G "tmr_margin value out of range, default %d used\n",
8 V, C7 F/ b2 r/ V- p# |5 h) d CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);; x5 y: @# n3 |* x. L
else% K. w) }- d Y. E6 Q! l) ?" C+ P
dev_info(dev, "default timer value is out of range, cannot start\n");
/ p4 E: @6 t" {0 V" `+ _ }4 ?+ i5 {" ~& D* L. B
+ Z* m4 m4 U& p5 ~! j- A/ \ /*注册混杂设备,设备名watchdog,次设备号130*/
% j6 X% F' `) m; @ ret = misc_register(&s3c2410wdt_miscdev);
* J- ~' S& R- K0 w& Z; L if (ret) {
' ~, a s7 M- Z2 B& C5 v8 p dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",+ B/ p& v4 {$ n/ o
WATCHDOG_MINOR, ret);: C6 P+ D& J4 Y8 g
goto err_clk;
6 W5 Y( D3 r* c0 u' }8 a+ p. b5 S }6 z; D& g0 f) j$ @8 F2 u
8 [6 X, i; P- }- L5 `* L /*0 t2 n" B9 B( C3 o( A8 c& `
*如果需要在看门狗模块加载时启动看门狗则8 t$ |, o2 D8 [) S
*调用s3c2410wdt_start,否则调用s3c2410wdt_stop
! n) ]' w# Y8 H/ s+ L9 | */
1 Y9 k, T( L2 g9 t5 k4 r1 m, ]+ j7 N ) y" S; y' m: L5 c% s
if (tmr_atboot && started == 0) {* y. {5 ~& H8 y, _- {# h, }. B1 R
dev_info(dev, "starting watchdog timer\n");- q I X* C) ]4 o7 B6 |
s3c2410wdt_start();
# w, x! v9 C9 c/ u } else if (!tmr_atboot) {
- z+ c7 b- E! `8 Q' H( @4 X6 x' v /* if we're not enabling the watchdog, then ensure it is8 M" |7 n) M# H/ R
* disabled if it has been left running from the bootloader
: N; m) i0 j3 p2 t8 q+ ] * or other source */
1 P! |5 f3 U" P& ^9 c& q' a% P7 o8 x. Q8 k# A+ ^* Y7 K0 G. T
s3c2410wdt_stop();: M7 N9 _$ I: B* ~& h7 s
}
& z3 j' F- J( L* C G
& ~6 B; ]% U, V! z5 o0 n /* print out a statement of readiness */2 w% l& E) b5 [7 K9 u* g
/*读取控制寄存器,打印目前看门狗的状态*/
4 Q. J$ L5 U% z wtcon = readl(wdt_base + S3C2410_WTCON);
% u- n# L6 h- p0 c$ J9 r1 k4 t& U$ L. \( r& ?
dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",9 r; Z6 ?" N. ~: u* o" Y; c
(wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",9 n) ~; v0 B& J# [! ]
(wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
, d7 B+ Y! R6 Y) p (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
N, C k7 X& i8 P8 a' J
" r7 O+ F+ @! U$ S3 T return 0;! Q+ L9 O0 z- A# c% g* S% a
/ ~8 U! |. v3 c2 u9 N8 @0 r1 q
err_clk:
; r5 W7 @8 r3 }2 Q* i6 L% M1 z clk_disable(wdt_clock);+ L: \9 h0 _9 q4 d5 K" ~! Y( k
clk_put(wdt_clock);
5 R B1 t. m1 F- a4 j1 M- c
+ S1 \& E. F3 h( l7 o* m2 q err_irq:
& A* y" ]0 e3 G) n free_irq(wdt_irq->start, pdev);
, H$ L$ g+ i6 I. P/ c! g; P8 _+ z' \/ Z$ _
err_map:; v# e! l. z A/ x0 r$ h: D! M
iounmap(wdt_base);" D+ s1 o6 G/ H/ u' w5 B
; [, N( A# Z0 X7 T8 i
err_req:
& o5 |( f& i$ i, T+ F6 `& N) h! \ release_resource(wdt_mem);
1 j# p$ C4 g0 _7 z x: d4 t kfree(wdt_mem);# _, `$ ~9 l& [0 y/ U: H( o2 e
3 l% @6 ~; }+ z1 y! o2 U$ } return ret;% ]7 \. U6 F, p! ~$ @! T
}
/ a% ]! g1 d) `% \* X% r: ~, P X
: M3 l. V3 X0 n+ L! J该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。
5 M, B9 l9 b& C' T1 x3 m1 ]$ r1 Q8 z* Y
然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。. f- S/ l) @- N1 m" r# a
) J; G: h; X: g& w9 I9 [0 p8 I
上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。
B& `( p; f# T2 R1 m/ s
9 Y8 v& S/ G. y, U* UPS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/ARM/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。
1 J* v. q$ ] e/ ?. S& c$ ?2 z! u+ Q6 I( [
3.2 s3c2410wdt_set_heartbeat
3 R) p2 ?/ ?8 X d: Pstatic int s3c2410wdt_set_heartbeat(int timeout) /*timeout 超时时间,单位秒*/% K6 B8 I. F1 v1 A1 x/ U: j
{: J# U- @* k( ~! W& D! }
unsigned int freq = clk_get_rate(wdt_clock);5 Z$ ]5 ?+ k0 i" X1 R, W
unsigned int count;% w, Y9 m, O% H0 n
unsigned int divisor = 1;
, a$ C! v- x% @8 [) K. S2 L unsigned long wtcon;
9 y! W0 ^# B4 D& A9 h3 A$ K2 L0 @# b, t0 R2 ]
if (timeout < 1). B5 L& z2 {- o: X
return -EINVAL;+ i- }- E$ C4 w; z @" `
- I8 o) k& ]6 @" `- R freq /= 128; /*时钟源为PCLK/128,不使用16 32 和64*/) }! M7 W% Q$ C4 u7 E; z8 ?) D
count = timeout * freq; /*得出计数器值*/ T5 ~. ^ T. ~
3 @% }" y5 o9 _; E3 e1 Z3 ? DBG("%s: count=%d, timeout=%d, freq=%d\n",+ q. c4 J0 k# c- b
__func__, count, timeout, freq);
% R, K1 z# h- @' {1 G3 d; \
% v8 h8 @" C$ l4 B0 K- u /* if the count is bigger than the watchdog register,5 P8 n+ e5 u, l4 D3 \
then work out what we need to do (and if) we can# G3 ~9 ~7 p! W; U7 i
actually make this value0 z. \9 J, w9 W! e* y7 _
*/6 c) ^! A" |4 F/ M2 I% q
/*计数器最大值为0xFFFF,如果大于则要计算Prescaler value*/' F0 v& p* x n2 s& W ?0 b
if (count >= 0x10000) {
7 H) [% P: P5 w$ D9 B$ }0 e/ ` for (divisor = 1; divisor <= 0x100; divisor++) { /*Prescaler value最大为0xff*/
N9 y) F' l2 E+ |8 Q" V9 @! h if ((count / divisor) < 0x10000)
S( c9 n9 }. I# ~ break;( r2 S; J4 L$ p1 e: N9 l' s) d/ N
}3 ?4 E) W2 X( f, z' |
/*找不到合适的Prescaler value,报错返回*/" l) ~+ e6 c3 x9 b4 d
if ((count / divisor) >= 0x10000) {! ?6 q+ R. T, v/ _
dev_err(wdt_dev, "timeout %d too big\n", timeout);. J+ J: F! w) {
return -EINVAL;5 f8 B% ?% e0 X$ V( `9 ^7 p8 Q
}
b0 ?+ c% F6 A& {# x }
[0 t7 j# N. }9 j8 j) p( w; q3 v# ^; a6 o; B/ J9 [
tmr_margin = timeout; /*保存timeout*/- K1 j W" d. R4 h0 o
) s; b. R- P, g( b DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
6 T6 G6 s, y! o& |& { __func__, timeout, divisor, count, count/divisor);' h5 I+ f- Z2 u+ c" V/ o
! d" F5 W2 r5 o+ w count /= divisor; /*根据Prescaler value计算出新的计数器值*/
& n$ g: v( N& j* c5 d wdt_count = count; /*保存计数器值*/0 q( m) f+ u8 w
" D( b# _8 U8 J, i3 x0 }& p# M L /* update the pre-scaler */
6 C( C2 i. z. B" A+ R$ F: @; b: o' s- ^ /*; J% X$ p( W& R! r/ Q3 F; ~9 w
*设置预分频计数器和数据寄存器
~ `$ n2 q4 a" Y7 r! L ?3 E * NOTE:此时并未使能看门狗定时器! z( U) L7 Z* N0 M2 r
*/
& H% [4 a; a5 @' P wtcon = readl(wdt_base + S3C2410_WTCON);
0 G" o. R6 n' u wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
0 a7 J5 M8 B6 L) W. Q wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);& x$ K1 n4 c b3 M
$ F& M0 y% I# B+ q
writel(count, wdt_base + S3C2410_WTDAT);
# x7 y4 q; k$ ~' z7 W4 J0 d writel(wtcon, wdt_base + S3C2410_WTCON);) s, E( p! o2 k s) J; |
5 A/ E! W5 G. v1 [; [+ z, N3 k return 0;( V/ D3 c* X! C' D& Q5 ~
}
8 |& B( o& {6 l- d: u, `0 O, p. s. m% X
形参timeout为定时时间,单位为秒。
5 _: J3 p4 ]$ f3 _, ^
! G; p9 H. Y: y9 ^& ]9 _6 C/ O 这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。
. Z, G0 L8 ^3 q* G; M2 f
0 x0 `% {: n# a' @3 }& L 在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。
2 J+ J$ C O+ c5 E
4 B+ L, V5 e9 I 当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。' z5 f! \/ y: c
/ w ^, `8 h; \, i; u$ ~
3.3 定时器的启动、停止和保活
9 ~4 Z- C( ^1 N2 d/ R% r3.3.1 停止* U: S- s; l$ U( H U; O+ l
定时器的停止由 s3c2410wdt_stop函数完成。2 s( T! _' J0 [
; h3 G% I# v- n6 I0 ^" g4 G- Lstatic void __s3c2410wdt_stop(void)# w+ Y y. y" n
{
# ^: ]# G7 {( C; j+ U8 [ unsigned long wtcon;
! i! G0 k% c. ]6 B( M
' {; R" ]# R* L& o6 \ _ wtcon = readl(wdt_base + S3C2410_WTCON);
- {7 L* d- P" Q) K' Y! ~ /*禁止看门狗,禁止RESET*// e( G% Y- S) M* H9 m4 v
wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
. T4 D% A+ C( t( a/ b' W writel(wtcon, wdt_base + S3C2410_WTCON);8 U& ]7 p9 g; \7 x5 d. Z
}& a* J$ \$ X: a
' n5 y8 @+ I1 Ostatic void s3c2410wdt_stop(void)! h# b3 \; x, A
{6 t, F v3 u i
spin_lock(&wdt_lock);* U& @9 y' b4 c+ F4 L# m4 u# W0 l
__s3c2410wdt_stop();
& d0 ~* x' K0 }5 a+ q spin_unlock(&wdt_lock);
: j+ X# Q ^* m3 M+ a}) V2 @ ~3 e: ^
% m. a8 j% n! |3 K5 c0 o3.3.2 启动
% Y) H6 S1 q8 F5 _1 D* F" s \定时器的启动由s3c2410wdt_start函数完成。
3 B; o" H1 [: u P0 k) C2 s" \" W
& l; U. ~: T8 f1 B. W" A) q& ~static void s3c2410wdt_start(void)
$ ^8 ^/ B7 J% Z x5 g' j{
8 U7 Q& R5 C" z0 }) R0 e unsigned long wtcon;
8 }0 m9 w: b% V# w$ _ f* v% t0 y, [: U; y
spin_lock(&wdt_lock);* ^, z5 [) W2 y3 h; O
4 D% t1 p! I' O7 t
__s3c2410wdt_stop(); ./*先禁止看门狗*/
2 ~ A, b6 h. f$ L* `
# B( A' R9 [ q wtcon = readl(wdt_base + S3C2410_WTCON); /*读取控制寄存器*/
- K0 Q2 p4 C. U# B4 o /*启动定时器,设置分频系数为128*/
9 A) @% F* N& s2 \7 x y wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; , z. \$ J, b3 t) t+ @$ W; n
; K8 `) [5 j/ O5 l/ W- L: Q5 t% _
if (soft_noboot) { /*判断许是否需要RESET*/
, a9 w i7 Y& o2 ?5 u: p( ~ wtcon |= S3C2410_WTCON_INTEN; /*使能看门狗中断*/
6 s* F& o1 f/ l wtcon &= ~S3C2410_WTCON_RSTEN; /*取消RESET*/! U9 x9 F( ~' i0 i0 m$ f( z
} else { /*复位*/
2 b, t! I( H; A* D& _; | wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看门狗中断*/
& H1 z' y7 c& l7 S" } l wtcon |= S3C2410_WTCON_RSTEN; /*设置RESET*/
, a4 q5 O) w0 V }
2 ?7 u! k9 }* ?& h! e, d9 {7 u1 a! h( C6 V* Y
DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",4 ^2 w/ \' h9 F V
__func__, wdt_count, wtcon);
" e- P$ U1 @) u1 ^) v5 B# B0 b& K8 U/ ~6 b
writel(wdt_count, wdt_base + S3C2410_WTDAT);
% w# w& [' k( R" ~ writel(wdt_count, wdt_base + S3C2410_WTCNT);0 P& m% J) l7 L* n
/*写入控制器,此时将启动看门狗定时器*/4 Y" |3 A' {& S
writel(wtcon, wdt_base + S3C2410_WTCON); ; R7 a/ w) I @/ S' V8 r7 P
spin_unlock(&wdt_lock);) Y1 \5 N, I$ |6 z2 `: Y: p
}3 ~7 A' N0 {* M* ?
在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。0 ]% S* B& ~' u4 u. [) `7 {7 m& w
/ f1 _# N% D) l$ j: d7 [% t3.3.3 保活. p* g7 {+ T8 ^! [; n* ~. ^9 K
定时器的保活由s3c2410wdt_keepalive函数完成。
) L) w3 d {0 }3 c ? h) q* J8 r: |3 D( `9 a4 S
static void s3c2410wdt_keepalive(void)
8 W2 ^% T: H% U# ~" ?$ C{
# h0 `% h! Q, T- R, h! o spin_lock(&wdt_lock);
, A$ x' [/ h$ _ writel(wdt_count, wdt_base + S3C2410_WTCNT); /*重置计数器值*/. b1 @( J7 l, s+ i
spin_unlock(&wdt_lock);+ {- S* L3 O7 e- B6 P" O! P5 p
}. ~6 ?. ~& ~1 j4 S% s: c v
最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。
# V* T* v% m+ t
1 x- Q6 ?6 ^) S) y# J+ \. I) U: B, _
3.4 看门狗驱动API
[( f. ^6 ]4 A% Q& S5 Y* z2 S看门狗驱动提供的API如下:+ @ {- B+ K/ k
9 T3 U' h$ }8 R* Tstatic const struct file_operations s3c2410wdt_fops = {
2 i3 J' \& p* Y. a/ L! a .owner = THIS_MODULE,
4 A4 a) f5 k5 U. G: q, _ .llseek = no_llseek,9 i% C5 x4 x2 f) l7 n7 k
.write = s3c2410wdt_write,
+ Z, t4 Y0 U/ Z1 M* | .unlocked_ioctl = s3c2410wdt_ioctl,: C' C; n' n7 d: n/ }; ]
.open = s3c2410wdt_open,
2 c/ i t1 F/ o8 O: p2 I .release = s3c2410wdt_release,( |6 [( }* V& P$ G9 o- y T
};
" k; H+ ^6 d# }+ @0 D7 Q
4 ]+ C6 E# S6 W' p ], A- M我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。
7 A) @: `$ q# A; D3.4.1 open方法
1 E% g( ?' _: F7 ^4 h- fstatic int s3c2410wdt_open(struct inode *inode, struct file *file)2 {, n z8 {+ a& M0 Q; \( c
{& g+ k7 s+ u, F+ b x5 O7 G0 A
if (test_and_set_bit(0, &open_lock))/*看门狗设备文件只能open一次*/
& B6 b1 V: q) J( e3 x% p$ E5 Y9 o return -EBUSY;
! h) U' U& v1 l) c# E" p% ?7 L( u. Z$ E* k' |' X
if (nowayout)0 I0 G) P" Y# u, N& O; L
__module_get(THIS_MODULE); /*增加模块引用计数*/0 J9 O: C& `. @1 a
1 y2 o7 p- n6 A4 y allow_close = CLOSE_STATE_NOT; /*设置标志位,不允许关闭看门狗*/
/ T. R& E8 q! d- P% p/ M. l+ x2 |3 @0 _ j8 j# c4 C
/* start the timer */8 U2 n1 v4 s4 Z- @
s3c2410wdt_start(); /*启动定时器*/
: a% m5 d. S3 X0 B7 R return nonseekable_open(inode, file); /*告知内核不支持llseek操作*/
. V" _3 d- p5 o5 ?& Z: x+ G% @}
8 ]- I1 j) O! d7 @
3 C' l: e5 ]2 J9 k' ^) L* M这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。
( A, G% `% `/ O# |/ N3.4.2 release方法
. A2 t9 Q3 b ?( ~$ @' Ustatic int s3c2410wdt_release(struct inode *inode, struct file *file)
, A' X& V) ^1 f{
9 |, @& `- H- w /*
o6 t7 i4 u& ~ * Shut off the timer.
8 p; G4 u7 V6 ] * Lock it in if it's a module and we set nowayout1 t0 Z& t" Q' Y7 H A9 U
*/
# u) @; O+ z: c& ?: b9 F /*状态是允许关闭看门狗,则停止看门狗,否则保活*/
" ^. e# N9 G, G$ r. I/ }/ u if (allow_close == CLOSE_STATE_ALLOW)
' f: I( q" w+ v# W2 \, @ s3c2410wdt_stop();
! x/ }9 l2 ~5 T$ b/ \1 x7 n$ y else {# ~, M R1 y: C7 m9 S6 M7 g8 e6 U# Q
dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");3 l5 I g8 n3 x1 v5 H$ [
s3c2410wdt_keepalive();
. l8 i7 Z7 O% ~7 B }
( ?0 G: m' I8 H: |: k allow_close = CLOSE_STATE_NOT; /*设置标志位,不允许关闭看门狗*/; M4 A. y" f% L) d4 H
clear_bit(0, &open_lock);
7 U* b; I) p+ h9 ?* v. M return 0;
8 f9 b$ ?+ e0 y; W$ m* Q}9 H y2 }2 s9 c R4 c1 N# x- \
3.4.3 wirte方法
9 N e, x0 p! n7 x# Rstatic ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
" P3 B4 [) G) ]& d8 z size_t len, loff_t *ppos)+ _! }- |6 G- U9 `+ y+ `, z
{ X Z* A' f7 Y# O
/*
0 X4 P) G2 d) X * Refresh the timer.3 X4 d: K* R: @7 @
*/
" P* c, H% f3 Q if (len) {0 I6 T5 n0 J) A7 X
/*nowayout 为真,不允许看门狗停止,使其保活*/
% g$ z/ M" K) }- ~3 ~) |. A2 x if (!nowayout) {9 T/ S: Y1 t: v* s% @& Y
size_t i;
8 C/ }! k: c7 o- h# W" O0 A! h& v8 w8 a6 c: a
/* In case it was set long ago */" ]* y2 z9 S( T9 `7 l! Z
allow_close = CLOSE_STATE_NOT;/*设置标志位,不允许关闭看门狗*/3 D% {+ f* {$ C
* A6 ~* @: r4 Z. M
for (i = 0; i != len; i++) {
& j# M* W/ I( j4 }5 x" E char c;
$ Q3 b0 m0 X5 j1 }# X8 `* ~$ \$ |7 n2 Y* o' C- |
if (get_user(c, data + i)) /*从用户空间获取一个字节的数据*/
) p2 S/ O" b$ V5 K. i( u) U/ Z) H return -EFAULT;2 E- k% t( x( O/ j. S
/*读取到字符V,设置标志位,允许关闭看门狗*/
. i6 ~* u4 ]5 y if (c == 'V') 1 i1 c, @3 L$ L8 y V0 R
allow_close = CLOSE_STATE_ALLOW;
8 _. [. C; F7 p- o }
) t! q2 u! v: w( J4 f$ i* ^7 `5 X: B }
/ U/ J* @& F8 Y- f. |) z% y s3c2410wdt_keepalive(); /*保活*/
D8 V1 }! H& F2 n) c1 H! I8 X/ i }3 t/ }% \" a5 `5 f
return len;
3 P. q4 p A1 _2 X0 _2 S' P8 _: J" G}' ~; ~: S! { L- J: ]& I) y
% v, g) ^+ V1 B9 E: ~# Q只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。! B J/ B( F- _3 i
2 L: I* D; M4 R* c" O4 P3.4.4 unlocked_ioctl方法
+ k0 i9 u; ?3 c" |% Estatic long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,3 Z- ^" B6 q/ v2 z* e* |
unsigned long arg)! R; f: Q* T$ G# j# p' T
{% s) \9 v, i. J+ S3 D7 s3 `0 w
void __user *argp = (void __user *)arg;/ a* T( f1 p! I: L' ~$ ]) d# {& d6 Q" Q
int __user *p = argp;
0 ]4 ?0 W0 t7 B/ p' D, S int new_margin;
2 X2 }. d4 |; H* O3 n) h1 _& a, n6 B' B1 `; F3 V
switch (cmd) {, V# e( E1 b0 p- j7 X
case WDIOC_GETSUPPORT:
6 x2 w) Y+ k' x) |" c return copy_to_user(argp, &s3c2410_wdt_ident,
0 _$ v5 n/ |& E2 @' Y7 }1 q/ P* } sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;) O( o. D, k% F
case WDIOC_GETSTATUS:
2 f; H! G* U" e G& { case WDIOC_GETBOOTSTATUS:
0 l* }/ a/ e) n X3 e return put_user(0, p);
2 g7 e" r b" t9 `- B3 m! X case WDIOC_KEEPALIVE:% l( f7 s! E M# I4 {2 m
s3c2410wdt_keepalive();+ @0 _/ W* l! F' U G
return 0;
& {5 _7 t0 t" q; R' }$ Z case WDIOC_SETTIMEOUT:
5 w" N0 K: F6 B/ Q/ D7 v if (get_user(new_margin, p)), G6 o6 Q. x l9 h; L
return -EFAULT;
$ ~3 P% K7 Q8 z/ u if (s3c2410wdt_set_heartbeat(new_margin))
2 O0 f1 Y7 F4 C N return -EINVAL;% O1 e5 `# H. E2 V% _/ x
s3c2410wdt_keepalive();3 w& O) @0 W( m' t% L/ x: B) _
return put_user(tmr_margin, p);7 e. m) G( k2 O x. l, t2 o, t* P8 R! ]
case WDIOC_GETTIMEOUT:/ y) W2 t( C1 r. P( T) |9 \
return put_user(tmr_margin, p);
; q+ Z+ R% V# d default:
; S4 A: x' H; a5 x; @/ A6 i# g return -ENOTTY;
8 o2 L0 ^% [9 j: ^9 t' Z2 [# {+ s }
% }: O9 R$ W6 k}) I' l# [9 x- E! G* z
4. 测试程序
8 Z( F9 D5 t( w: j5 G. ^#include <stdio.h>
* L1 p# i# y; n5 v6 J8 C) j9 W- v#include <unistd.h>
" F: ?9 y6 O0 w( l/ _5 k#include <linux/watchdog.h>
' ~* y8 |' C8 I! _' a0 B- n#include <fcntl.h>
' ]4 X& D [2 X1 R' P2 m4 a& D3 @5 z3 `8 P0 T9 W
int main(void)/ s8 Z$ ]3 ^" i+ J3 l; X6 `
{- M" H# x4 @& J& U" _* x; g' V
int fd, val, ret;
4 ~5 P! B0 H [3 d+ e4 M
]& O" W$ n7 g* | _ fd = open("/dev/watchdog", O_RDWR);6 y* J5 q1 u, r0 N; ^8 t$ f
if(fd < 0){. ?% w. W. l6 R2 h# E7 x2 V
printf("open device fail\n");" ^: E5 ?( m( H7 y3 e
return -1;
) I, d; v! v1 e4 \8 j: o/ J }
) Y& P- z$ w X* s+ w: S - I& ?0 s% R2 w% a9 N$ l1 l1 G
while(1){: T8 @1 Q& B2 C" f/ d* v* I# t2 e9 M
ret = write(fd, &val, sizeof(val));! j; E+ y8 Q5 @+ A+ Y
if(ret < 0){/ s) O8 [6 U0 j/ R) F6 {2 j
perror("watchdog write wrong\n");0 R$ p9 N" ?, ^0 d$ d. p, R! M+ k; G% `
return -1;
" t2 c; L9 A2 L0 L }
7 v6 m* u: r9 o0 y/ k sleep(5);
1 \( Z$ U/ K4 Q$ H$ q1 ? }
( H* S/ e. I [3 F" T ( g! t& G3 `3 N4 N9 J
return 0;4 U( H5 d+ m. g' V
}
$ A) i0 ?2 [% I7 ?6 |& J1 D+ Y% ~
" C3 |7 f1 `5 ]' {# v* t( o" q该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。8 }2 t _+ d8 r; M3 A7 L* s8 ?
可以将5秒替换为16秒,你会发现系统自动重启了。
6 Y/ t7 u) V* P `2 r/ E, g& Z2 r
$ w5 l. h4 G4 T. [5. 结束语
+ G0 o7 k1 B' l9 V+ K 本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。
- D5 u. I. P; g! Q8 X# y: a; a# h7 a' J
# U u0 l9 P& R/ _
3 Z2 t! ~$ I: h' k$ u* U |
|