linux設備驅動歸納總結(十一):寫個簡單的看門狗驅動
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
設備驅動的歸納已經差不多了,趁着知識點還沒有遺忘,寫點代碼鞏固一下,來個簡單的看門狗驅動——靜態平臺類的雜設備看門狗驅動,有定時和重啓兩個基本功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、S3C2440中的看門狗——具體請看s3c2440文檔
看門狗應該可以算是S3C2440中最簡單的一個設備的,僅僅只有三個寄存器需要配置。S3C2440中的看門狗有兩種功能(只能二選一):
1、復位功能,在指定時間內,如果沒有執行喂狗操作。指定時間到達後會執行系統重啓操作。
2、定時功能,每隔指定的時間,看門狗就會往處理器發送中斷,執行中斷處理函數。
接下來就要看看控制看門狗的三個寄存器:
1、控制寄存器——WTCON:
首先要看看分頻係數Prescaler value[15:8]和時鐘分頻Clock select[4:3]。看門狗的時鐘頻率就是通過這兩個值分頻得出的:
t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ]
每個(1/t_watchdog)秒,看門狗計數器減一。
Watchdog timer[5]是看門狗的是能控制位,初始化時必須將此爲置一,不然看門狗無法操作。
Interrupt generation[2]是控制中斷的產生,如果你使用看門狗來執行定時功能,置一代表使能看門狗產生中斷,反之不產生中斷。
Reset enable/disable[0]用控制看門狗是否復位,如果置一,代表當時間到達後系統重啓。
2、數據寄存器WTDAT和計數寄存器WTCNT
這兩個都是用於存放16位數據的寄存器。
1、如果是復位模式,從WTCON[5]置一開始,每隔(1/t_watchdog)秒,WTCNT中的值減一,直到WTCNT爲0,系統就會復位。在WTCNT還沒有爲0前,可以重新往WTCNT中賦值,這樣WTCNT就會從新的數值開始減一,這就是所謂的喂狗操作,重複地在WTCNT變0前喂狗,就可以讓系統不復位。
2、如果是定時模式, 從WTCON[5]置一開始,每隔(1/t_watchdog)秒,WTCNT中的值減一,直到WTCNT爲0,看門狗往處理器發送中斷信號,並自動將WTDAT賦值給WTCNT,讓WTCNT重新開始減一。這樣的重複操作就實現了看門狗的定時。
注意:不管是哪一種模式,一開始時看門狗都不會將WTDAT的值賦給WTCNT,都是直接以WTCNT的值開始計數。所以,在是能看門狗之前,必須先設備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
所以,看門狗的計數間隔是(1/3906.25)秒,1秒需要計數大約3906次。
看門狗介紹完畢,接着就開始寫驅動了,在寫驅動前貼張之前我介紹過的圖,我寫驅動的步驟:
接在來我就會按這個順序來寫個簡單的看門狗驅動,實現看門狗的兩種功能,定時和復位。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、第一個看門狗程序,代碼路徑11th_wdt/1st。
寫驅動前先要寫一些基本的操作代碼,檢驗一下該設備是否能正常工作。
第一個看門狗程序做了三步,簡單實現了看門狗的復位操作:
1、定義了一個維護看門狗數據的結構體:
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 *); //操作看門狗的函數指針
15 };
2、實現了看門狗的復位操作:
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秒後系統復位
23 iowrite32(0x6339, wdt->wtcon); //設置wtcon寄存器爲0x6339,使能了看門狗和復位功能
24 }
3、封裝了設備的初始化和註銷函數:
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 }
寫完後編譯,效果就是加載模塊後十秒系統重啓,看門狗的復位功能驗證成功。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、第二個看門狗程序,代碼路徑11th_wdt/2nd。
第一個看門狗程序實現了復位操作,接下來我就要在原來代碼的基礎上加上看門狗的定時功能。很簡單,只貼上就三個函數,其他細節請看源代碼:
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、初始話後還不能產生中斷,還需要實現開啓和關閉中斷操作,其實就是設置寄存器的一位:
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 }
既然是產生了中斷,就要註冊中斷和實現中斷處理函數,中斷的註冊操作應該放在設備初始化函數中:
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”就知道,系統中本身就註冊了一個看門狗中斷,爲了能夠執行成功,方法有兩個:(之前介紹過的兩男共享一妞)
1、幹掉系統中的看門狗中斷:
方法:配置內核時不要選上看門狗設備。
2、修改內核源代碼,添加共享標記。
我這裏使用的是第一種解決方法。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、第三個看門狗程序,代碼路徑11th_wdt/3rd。
上面已經實現了圖上的三步:定義面向對象結構體,實現基本的硬件操作函數、定義設備的初始化和註銷函數。
接下來就將它修改成靜態平臺類驅動。方法很簡單,將之前放在wdt_init的操作放在probe函數中,配對成功後自動調用,將之前放在wdt_exit的操作放在remove函數中。
貼上部分代碼:
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);
既然說是靜態平臺類驅動,那就是需要修改內核代碼,方法在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函數,具體效果就是每隔一秒打印一句話。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、第四個看門狗程序,代碼路徑11th_wdt/4th。
接下來就要註冊雜設備類驅動,並且提供ioctl命令給用戶空間控制看門狗。
先要在頭文件中實現幾個命令:
/*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) //產生一個數字
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的實現,很簡單,四個命令對應四個不同的操作:
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 };
接着是實現雜設備類驅動:
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 }
然後稍稍修改一下其他代碼就可以了。
編譯後運行,執行./app reset後,只要在10秒內執行./app feed,系統就不會重啓。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
七、總結
簡單的看門狗驅動基本上就完成了,當然還有很多需要完善的地方,如設定定時的時間等。具體包含了以下知識點:
1、字符設備的方法;
2、io內存——ioremap;
3、中斷註冊;
4、platform設備驅動;
5、雜設備驅動;
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx