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