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