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