linux設備驅動歸納總結(十一):寫個簡單的看門狗驅動
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
設備驅動的歸納已經差不多了,趁著知識點還沒有遺忘,寫點代碼鞏固一下,來個簡單的看門狗驅動——靜態(tài)平臺類的雜設備看門狗驅動,有定時和重啟兩個基本功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、S3C2440中的看門狗——具體請看s3c2440文檔
看門狗應該可以算是S3C2440中最簡單的一個設備的,僅僅只有三個寄存器需要配置。S3C2440中的看門狗有兩種功能(只能二選一):
1、復位功能,在指定時間內,如果沒有執(zhí)行喂狗操作。指定時間到達后會執(zhí)行系統(tǒng)重啟操作。
2、定時功能,每隔指定的時間,看門狗就會往處理器發(fā)送中斷,執(zhí)行中斷處理函數(shù)。
接下來就要看看控制看門狗的三個寄存器:
1、控制寄存器——WTCON: 
首先要看看分頻系數(shù)Prescaler
value[15:8]和時鐘分頻Clock
select[4:3]。看門狗的時鐘頻率就是通過這兩個值分頻得出的:
t_watchdog = 1/[ PCLK /
(Prescaler value + 1) / Division_factor ]
每個(1/t_watchdog)秒,看門狗計數(shù)器減一。
Watchdog
timer[5]是看門狗的是能控制位,初始化時必須將此為置一,不然看門狗無法操作。
Interrupt
generation[2]是控制中斷的產生,如果你使用看門狗來執(zhí)行定時功能,置一代表使能看門狗產生中斷,反之不產生中斷。
Reset
enable/disable[0]用控制看門狗是否復位,如果置一,代表當時間到達后系統(tǒng)重啟。
2、數(shù)據(jù)寄存器WTDAT和計數(shù)寄存器WTCNT
這兩個都是用于存放16位數(shù)據(jù)的寄存器。
1、如果是復位模式,從WTCON[5]置一開始,每隔(1/t_watchdog)秒,WTCNT中的值減一,直到WTCNT為0,系統(tǒng)就會復位。在WTCNT還沒有為0前,可以重新往WTCNT中賦值,這樣WTCNT就會從新的數(shù)值開始減一,這就是所謂的喂狗操作,重復地在WTCNT變0前喂狗,就可以讓系統(tǒng)不復位。
2、如果是定時模式,
從WTCON[5]置一開始,每隔(1/t_watchdog)秒,WTCNT中的值減一,直到WTCNT為0,看門狗往處理器發(fā)送中斷信號,并自動將WTDAT賦值給WTCNT,讓WTCNT重新開始減一。這樣的重復操作就實現(xiàn)了看門狗的定時。
注意:不管是哪一種模式,一開始時看門狗都不會將WTDAT的值賦給WTCNT,都是直接以WTCNT的值開始計數(shù)。所以,在是能看門狗之前,必須先設備WTCNT的值,指定時間長短。
接下來的程序我需要的時間間隔是1秒,所以,Prescaler
value[15:8]我賦值為99,Clock
select[4:3]我賦值為128。S3C2440中的PCLK定義為50000000,通過公式可以計算出:
t_watchdog
= 1/[ PCLK / (Prescaler value + 1) / Division_factor ] = 3906.25
所以,看門狗的計數(shù)間隔是(1/3906.25)秒,1秒需要計數(shù)大約3906次。
看門狗介紹完畢,接著就開始寫驅動了,在寫驅動前貼張之前我介紹過的圖,我寫驅動的步驟: 
接在來我就會按這個順序來寫個簡單的看門狗驅動,實現(xiàn)看門狗的兩種功能,定時和復位。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、第一個看門狗程序,代碼路徑11th_wdt/1st。
寫驅動前先要寫一些基本的操作代碼,檢驗一下該設備是否能正常工作。
第一個看門狗程序做了三步,簡單實現(xiàn)了看門狗的復位操作:
1、定義了一個維護看門狗數(shù)據(jù)的結構體:
9 struct _wdt_t {
10 unsigned long phys,
virt;
//存放物理地址和對應的虛擬地址
11 unsigned long wtcon, wtdat,
wtcnt;
//存放寄存器
12 unsigned long reg;
13
14 void (*init_reset)(struct
_wdt_t *);
//操作看門狗的函數(shù)指針
15 };
2、實現(xiàn)了看門狗的復位操作:
19 static void
s3c_wdt_init_reset(struct _wdt_t *wdt)
20 {
21 iowrite32((int)(WDT_1S *
10), wdt->wtdat);
//其實這個不設置也可以
22 iowrite32((int)(WDT_1S *
10), wdt->wtcnt);
//設置10秒后系統(tǒng)復位
23 iowrite32(0x6339,
wdt->wtcon);
//設置wtcon寄存器為0x6339,使能了看門狗和復位功能
24 }
3、封裝了設備的初始化和注銷函數(shù):
26 int init_wdt_device(struct
_wdt_t *wdt)
27 {
28 int ret = 0;
29 //ioremap
30 wdt->phys = 0x53000000;
31 wdt->virt = (unsigned
long)ioremap(wdt->phys, 0x0c);
32 wdt->wtcon = wdt->virt
+ 0x0;
33 wdt->wtdat = wdt->virt
+ 0x4;
34 wdt->wtcnt = wdt->virt
+ 0x8;
35
36 //function
37 wdt->init_reset =
s3c_wdt_init_reset;
38 return ret;
39 }
40
41 void destroy_wdt_device(struct
_wdt_t *wdt)
42 {
43 iounmap((void *)wdt->virt);
44 }
寫完后編譯,效果就是加載模塊后十秒系統(tǒng)重啟,看門狗的復位功能驗證成功。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、第二個看門狗程序,代碼路徑11th_wdt/2nd。
第一個看門狗程序實現(xiàn)了復位操作,接下來我就要在原來代碼的基礎上加上看門狗的定時功能。很簡單,只貼上就三個函數(shù),其他細節(jié)請看源代碼:
1、初始化看門狗,設置為定時功能,每隔一秒產生一次中斷:
29 static void
s3c_wdt_init_interrupt(struct _wdt_t *wdt)
30 {
31 iowrite32((int)(WDT_1S),
wdt->wtdat);
32 iowrite32((int)(WDT_1S),
wdt->wtcnt);
33 iowrite32(0x6338,
wdt->wtcon);
34 }
2、初始話后還不能產生中斷,還需要實現(xiàn)開啟和關閉中斷操作,其實就是設置寄存器的一位:
36 static void s3c_wdt_start(struct
_wdt_t *wdt)
37 {
38 wdt->reg =
ioread32(wdt->wtcon);
39 wdt->reg |= (1 <<
2);
40 iowrite32(wdt->reg,
wdt->wtcon);
41 }
42
43 static void s3c_wdt_stop(struct
_wdt_t *wdt)
44 {
45 wdt->reg =
ioread32(wdt->wtcon);
46 wdt->reg &= (1 <<
2);
47 iowrite32(wdt->reg,
wdt->wtcon);
48 }
既然是產生了中斷,就要注冊中斷和實現(xiàn)中斷處理函數(shù),中斷的注冊操作應該放在設備初始化函數(shù)中:
50
irqreturn_t wdt_irq_handler(int irqno, void *dev_id)
51 {
52 printk("wang wang wang
...\n");
53 return IRQ_HANDLED;
54 }
55
56 int init_wdt_device(struct
_wdt_t *wdt)
57 {
58 int ret = 0;
59 //ioremap
60 wdt->phys = 0x53000000;
61 wdt->virt = (unsigned
long)ioremap(wdt->phys, 0x0c);
62 wdt->wtcon = wdt->virt
+ 0x0;
63 wdt->wtdat = wdt->virt
+ 0x4;
64 wdt->wtcnt = wdt->virt
+ 0x8;
65
66 //irq
67 ret =
request_irq(IRQ_S3C2440_WDT, wdt_irq_handler, IRQF_TRIGGER_NONE,
68 "s3c2440_wdt-irq",
NULL);
69 if(ret){
70 printk("request
wdt-irq failed!\n");
71 goto err;
72 }
73
74 //function
75 wdt->init_reset =
s3c_wdt_init_reset;
76 wdt->init_interrupt =
s3c_wdt_init_interrupt;
77 wdt->start =
s3c_wdt_start;
78 wdt->stop =
s3c_wdt_stop;
79
80 return ret;
81
82 err:
83 iounmap((void *)wdt->virt);
84 return ret;
85 }
86
87 void destroy_wdt_device(struct
_wdt_t *wdt)
88 {
89
free_irq(IRQ_S3C2440_WDT, NULL);
90 iounmap((void *)wdt->virt);
91 }
寫完后編譯,效果就是加載模塊后每隔一秒打印出一句話,看門狗的定時功能驗證成功。
注意:一般是不能加載成功的,運行命令”cat
/proc/interrupt”就知道,系統(tǒng)中本身就注冊了一個看門狗中斷,為了能夠執(zhí)行成功,方法有兩個:(之前介紹過的兩男共享一妞)
1、干掉系統(tǒng)中的看門狗中斷:
方法:配置內核時不要選上看門狗設備。
2、修改內核源代碼,添加共享標記。
我這里使用的是第一種解決方法。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、第三個看門狗程序,代碼路徑11th_wdt/3rd。
上面已經實現(xiàn)了圖上的三步:定義面向對象結構體,實現(xiàn)基本的硬件操作函數(shù)、定義設備的初始化和注銷函數(shù)。
接下來就將它修改成靜態(tài)平臺類驅動。方法很簡單,將之前放在wdt_init的操作放在probe函數(shù)中,配對成功后自動調用,將之前放在wdt_exit的操作放在remove函數(shù)中。
貼上部分代碼:
97 static int
s3c_wdt_probe(struct platform_device *pdev)
98 {
99 printk("[%s]\n",
__FUNCTION__);
100
my_wdt.phys = pdev->resource[0].start;
101
my_wdt.irqno = pdev->resource[1].start;
102 init_wdt_device(&my_wdt);
103
my_wdt.init_interrupt(&my_wdt);
104 my_wdt.start(&my_wdt);
105 return 0;
106 }
107
108 static int
s3c_wdt_remove(struct platform_device *pdev)
109 {
110 printk("[%s]\n",
__FUNCTION__);
111 my_wdt.stop(&my_wdt);
112
destroy_wdt_device(&my_wdt);
113 return 0;
114 }
115
116 static
struct platform_driver wdt_pdrv = {
117 .probe
= s3c_wdt_probe,
118
.remove = s3c_wdt_remove,
119
.driver = {
120
.name = "s3c_wdt_xb",
121 },
122 };
123
124 static int __init
wdt_init(void)
125 {
126
platform_driver_register(&wdt_pdrv);
127 printk("hello
wdt!\n");
128 return 0;
129 }
130
131 static void __exit
wdt_exit(void)
132 {
133
platform_driver_unregister(&wdt_pdrv);
134 printk("bye wdt!\n");
135 }
136
137 module_init(wdt_init);
138 module_exit(wdt_exit);
既然說是靜態(tài)平臺類驅動,那就是需要修改內核代碼,方法在linux設備驅動歸納總結(九):1.platform設備驅動有介紹,在三處文件添加代碼:
1、arch/arm/mach-s3c2440/mach-mini2440.c
250 static struct platform_device
*mini2440_devices[] __initdata = {
251 &s3c_device_usb,
252 &s3c_device_rtc,
253 &s3c_device_lcd,
254 &s3c_device_wdt,
255 &s3c_device_led,
256
&s3c_device_wdt_xb,
//這是我新加的
257 &s3c_device_i2c0,
258 &s3c_device_iis,
259 &s3c_device_dm9k,
260 &net_device_cs8900,
261 &s3c24xx_uda134x,
262 };
2、arch/arm/plat-s3c24xx/devs.c
379 /* Watchdog xiaobai*/
380
381 static struct resource
s3c_wdt_xb_resource[] = {
382 [0] = {
383 .start = 0x53000000,
384 .end = 0x530000ff,
385 .flags =
IORESOURCE_MEM,
386 },
387 [1] = {
388 .start =
IRQ_S3C2440_WDT,
389 .end =
IRQ_S3C2440_WDT,
390 .flags =
IORESOURCE_IRQ,
391 }
392 };
393
394 struct platform_device
s3c_device_wdt_xb = {
395 .name =
"s3c_wdt_xb",
396 .id = -1,
397 .num_resources =
ARRAY_SIZE(s3c_wdt_xb_resource),
398 .resource =
s3c_wdt_xb_resource,
399 };
400
401
EXPORT_SYMBOL(s3c_device_wdt_xb);
3、arch/arm/plat-s3c/include/plat/devs.h
27 extern struct platform_device
s3c_device_dm9k;
28 extern struct platform_device
net_device_cs8900;
29 extern struct platform_device
s3c_device_fb;
30 extern struct platform_device
s3c_device_usb;
31 extern struct platform_device
s3c_device_lcd;
32 extern struct platform_device
s3c_device_wdt;
33 extern struct platform_device
s3c_device_led;
34 extern
struct platform_device s3c_device_wdt_xb;
//這是我添加的
35 extern struct platform_device
s3c_device_i2c0;
36 extern struct platform_device
s3c_device_i2c1;
37 extern struct platform_device
s3c_device_iis;
修改后重新編譯內核,加載模塊后,內核會自動調用probe函數(shù),具體效果就是每隔一秒打印一句話。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、第四個看門狗程序,代碼路徑11th_wdt/4th。
接下來就要注冊雜設備類驅動,并且提供ioctl命令給用戶空間控制看門狗。
先要在頭文件中實現(xiàn)幾個命令:
/*11th_wdt/4th/ioctl_wdt.h*/
1 #ifndef _WDT_H
2 #define _WDT_H
3
4 #define MAGIC 'x'
5 #define WDT_RESET
_IO(MAGIC, 0) //產生一個數(shù)字
6 #define WDT_INTERRUPT
_IO(MAGIC, 1)
7 #define WDT_START
_IO(MAGIC, 2)
8 #define WDT_STOP
_IO(MAGIC, 3)
9
10 #endif /* _WDT_H */
再看看ioctl的實現(xiàn),很簡單,四個命令對應四個不同的操作:
112 int s3c_wdt_ioctl (struct inode
*node, struct file *filp,
113 unsigned int cmd,
unsigned long args)
114 {
115 switch(cmd){
116 case WDT_RESET:
117
my_wdt.init_reset(&my_wdt);
118 break;
119 case WDT_INTERRUPT:
120
my_wdt.init_interrupt(&my_wdt);
121 break;
122 case WDT_START:
123
my_wdt.start(&my_wdt);
124 break;
125 case WDT_STOP:
126
my_wdt.stop(&my_wdt);
127 break;
128 default:
129 printk("unknow
ioctl cmd!\n");
130 return - EINVAL;
131 }
132 return 0;
133 }
134
135 static struct file_operations
wdt_fops = {
136 .ioctl = s3c_wdt_ioctl,
137 };
接著是實現(xiàn)雜設備類驅動:
139 /*******************
140 * misc class *
141 *******************/
142
143 static
struct miscdevice wdt_misc = {
144 .name
= "s3c_wdt",
145 .fops
= &wdt_fops,
146 };
147
148 /*******************
149 * platform總線操作*
150 *******************/
151 static int s3c_wdt_probe(struct
platform_device *pdev)
152 {
153 printk("[%s]\n",
__FUNCTION__);
154 my_wdt.phys =
pdev->resource[0].start;
155 my_wdt.irqno =
pdev->resource[1].start;
156
157 init_wdt_device(&my_wdt);
158
misc_register(&wdt_misc);
159 return 0;
160 }
161
162 static int s3c_wdt_remove(struct
platform_device *pdev)
163 {
164 printk("[%s]\n",
__FUNCTION__);
165
misc_deregister(&wdt_misc);
166
destroy_wdt_device(&my_wdt);
167 return 0;
168 }
最后就可以寫個應用程序驗證一下了:
/*11th_wdt/4th/app.c */
8 #include "ioctl_wdt.h"
9
10 int main(int argc, char
*argv[])
11 {
12 int fd;
13
14 fd = open("/dev/s3c_wdt",
O_RDWR);
15 if(fd < 0)
16 perror("open");
17
18 printf("./app
[func]\n");
19 printf("func : reset
interrupt start stop\n");
20 printf("[%s]\n",
argv[1]);
21
22
if(!strncasecmp("reset", argv[1], 5))
23
ioctl(fd, WDT_RESET);
24
if(!strncasecmp("interrupt", argv[1], 9))
25
ioctl(fd, WDT_INTERRUPT);
26
if(!strncasecmp("start", argv[1], 5))
27
ioctl(fd, WDT_START);
28
if(!strncasecmp("stop", argv[1], 4))
29
ioctl(fd, WDT_STOP);
30
31 return 0;
32 }
編譯后驗證一下,可以使用./app
reset啟動復位功能,也可以./app
interrupt & ./app start啟動定時功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
六、第五個看門狗程序,代碼路徑11th_wdt/5th。
看門狗的驅動就基本完成了,最后還有個功能要補充,喂狗,直接上代碼:
65 static void s3c_wdt_feed(struct
_wdt_t *wdt)
66 {
67 iowrite32((int)(WDT_1S *
10), wdt->wtcnt);
68 }
然后稍稍修改一下其他代碼就可以了。
編譯后運行,執(zhí)行./app
reset后,只要在10秒內執(zhí)行./app
feed,系統(tǒng)就不會重啟。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
七、總結
簡單的看門狗驅動基本上就完成了,當然還有很多需要完善的地方,如設定定時的時間等。具體包含了以下知識點:
1、字符設備的方法;
2、io內存——ioremap;
3、中斷注冊;
4、platform設備驅動;
5、雜設備驅動;
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代碼: 11th_wdt.rar
|