一.以查詢方式實現 1.寫出驅動框架 1.1 仿照其他程式加一些必要的頭文件 1.2 構造一個結構體file_operations 1.3 根據file_operations的所選項寫出所需的函數,並構建出來 1.4 入口函數、出口函數的註冊和卸載 1.5 修飾入口函數和出口函數 1.6 給sysf ...
一.以查詢方式實現
1.寫出驅動框架
1.1 仿照其他程式加一些必要的頭文件
1.2 構造一個結構體file_operations
1.3 根據file_operations的所選項寫出所需的函數,並構建出來
1.4 入口函數、出口函數的註冊和卸載
1.5 修飾入口函數和出口函數
1.6 給sysfs提供更多的信息,並有udev機制自動創建/dev/xxx設備節點
2.硬體操作
2.1 sch原理圖
2.2 2440手冊
2.3 編程:物理地址到虛擬地址的映射
1 /*
2 *以查詢的方式實現按鍵驅動程式
3 */
4
5 #include <linux/module.h>
6 #include <linux/kernel.h>
7 #include <linux/fs.h>
8 #include <linux/init.h>
9 #include <linux/delay.h>
10 #include <asm/uaccess.h>
11 #include <asm/irq.h>
12 #include <asm/io.h>
13 #include <asm/arch/regs-gpio.h>
14 #include <asm/hardware.h>
15
16
17 static struct class *g_ptSeconddrvCls;
18 static struct class_device *g_ptSeconddrvClsDev;
19
20 volatile unsigned long *gpfcon;
21 volatile unsigned long *gpfdat;
22
23 volatile unsigned long *gpgcon;
24 volatile unsigned long *gpgdat;
25
26 static int seconddrv_open(struct inode *inode, struct file *file)
27 {
28 /* 配置GPF0,2為輸入引腳 */
29 *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
30
31 /* 配置GPG3,11為輸入引腳 */
32 *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
33
34 return 0;
35 }
36
37 static ssize_t seconddrv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
38 {
39 /* 返回4個引腳的電平 */
40 unsigned char key_vals[4];
41 int regval;
42
43 if (size != sizeof(key_vals))
44 return -EINVAL;
45
46 /* 讀GPF0,2 */
47 regval = *gpfdat;
48 key_vals[0] = (regval & (1<<0)) ? 1 : 0;
49 key_vals[1] = (regval & (1<<2)) ? 1 : 0;
50
51
52 /* 讀GPG3,11 */
53 regval = *gpgdat;
54 key_vals[2] = (regval & (1<<3)) ? 1 : 0;
55 key_vals[3] = (regval & (1<<11)) ? 1 : 0;
56
57 copy_to_user(buf, key_vals, sizeof(key_vals));
58
59 return sizeof(key_vals);
60 }
61
62 static struct file_operations seconddrv_fops = {
63 .owner = THIS_MODULE,
64 .open = seconddrv_open,
65 .read = seconddrv_read,
66 };
67
68
69 int g_iMajor;
70 static int seconddrv_init(void)
71 {
72 g_iMajor = register_chrdev(0, "second_drv", &seconddrv_fops);
73
74 g_ptSeconddrvCls = class_create(THIS_MODULE, "second_drv");
75
76 g_ptSeconddrvClsDev = class_device_create(g_ptSeconddrvCls, NULL, MKDEV(g_iMajor, 0), NULL, "buttons"); /* /dev/buttons */
77
78 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
79 gpfdat = gpfcon + 1;
80
81 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
82 gpgdat = gpgcon + 1;
83
84 return 0;
85 }
86
87 static int seconddrv_exit(void)
88 {
89 unregister_chrdev(g_iMajor, "second_drv");
90 class_device_unregister(g_ptSeconddrvClsDev);
91 class_destroy(g_ptSeconddrvCls);
92 iounmap(gpfcon);
93 iounmap(gpgcon);
94 return 0;
95 }
96
97 module_init(seconddrv_init);
98
99 module_exit(seconddrv_exit);
100 MODULE_LICENSE("GPL");
查詢方式的按鍵驅動程式 1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5
6 /* 2nddrvtest
7 */
8 int main(int argc, char **argv)
9 {
10 int fd;
11 unsigned char key_vals[4];
12 int cnt = 0;
13
14 fd = open("/dev/buttons", O_RDWR);
15 if (fd < 0)
16 {
17 printf("can't open!\n");
18 }
19
20 while (1)
21 {
22 read(fd, key_vals, sizeof(key_vals));
23 if (!key_vals[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
24 {
25 printf("%04d key pressed: %d %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]);
26 }
27 }
28
29 return 0;
30 }
測試程式
二、使用中斷方式實現按鍵驅動程式
使用查詢方式實現時,程式一直占用CPU資源,CPU利用率低。
改進的辦法:使用中斷的方式實現
1.linux中斷異常體系分析
1.1 異常向量的設置trap_init()
trap_init被用來設置各種異常的處理向量,trap_init函數將異常向量複製到0xffff0000處
trap_init
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
vector_irq
vector_stub(巨集)//計算返回地址,保存一些寄存器
__irq_usr //根據被中斷的工作模式進入相應的某個跳轉分支->進入管理模式
usr_entry(巨集)//保存一些寄存器的值
.maro irq_handler(巨集)
asm_do_IRQ //異常處理函數
1.2 異常處理函數asm_do_IRQ
asm_do_IRQ
定義了一個結構體數組desc[NR_IRQS]
irq_enter
desc_handle_irq:desc->handle_irq
1.3 結構數組desc[NR_IRQS]的構建
s3c24xx_init_irq
set_irq_chip //chip=s3c_irq_eint0t4,可設置觸發方式,使能中斷,禁止中斷等底層晶元相關的操作
set_irq_flags
set_irq_handler(irqno, handle_edge_irq)//handle_irq=handle_edge_irq
__set_irq_handler
desc_handle_irq
中斷處理函數handle_edge_irq
handle_edge_irq
desc->chip->ack(irq)//清中斷
handle_IRQ_event//取出action鏈表中的成員,執行action->handler
1.4 用戶註冊中斷處理函數的過程request_irq
request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
//用戶通過request_irq向內核註冊中斷處理函數:a.分配了irqaction結構b.調用setup_irq函數
setup_irq(irq,action)//將新建的irqaction結構鏈入irq_desc[irq]結構的action鏈表中P405
desc->chip->settype//定義引腳為中斷引腳
desc->chip->startup/enable//引腳使能
1.5 free_irq(irq,dev_id)//卸載中斷服務程式
a.出鏈
b.禁止中斷
1.6 總結:handle_irq是這個中斷的處理函數入口,發生中斷時,總入口函數asm_do_IRQ函數將根據中斷號調用相應irq_desc數組項的handle_irq。handle_irq使用chip結構體中的函數來清除、屏蔽或者重新enable中斷,還一一調用用戶在action鏈表中註冊的中斷處理函數。
2.編寫代碼
1 /*
2 *用中斷方式實現按鍵驅動程式
3 */
4
5 #include <linux/module.h>
6 #include <linux/kernel.h>
7 #include <linux/fs.h>
8 #include <linux/init.h>
9 #include <linux/delay.h>
10 #include <linux/irq.h>
11 #include <asm/uaccess.h>
12 #include <asm/irq.h>
13 #include <asm/io.h>
14 #include <asm/arch/regs-gpio.h>
15 #include <asm/hardware.h>
16
17
18 static struct class *g_ptThirddrvCls;
19 static struct class_device *g_ptThirddrvClsDev;
20
21 volatile unsigned long *gpfcon;
22 volatile unsigned long *gpfdat;
23
24 volatile unsigned long *gpgcon;
25 volatile unsigned long *gpgdat;
26
27 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
28
29 /* 中斷事件標誌, 中斷服務程式將它置1,third_drv_read將它清0 */
30 static volatile int ev_press = 0;
31
32 struct pin_desc{
33 unsigned int pin;
34 unsigned int key_val;
35 };
36
37 static unsigned char key_val;
38
39 struct pin_desc pins_desc[4] = {
40 {S3C2410_GPF0, 0x01},
41 {S3C2410_GPF2, 0x02},
42 {S3C2410_GPG3, 0x03},
43 {S3C2410_GPG11, 0x04},
44 };
45
46
47 /*
48 * 確定按鍵值
49 */
50 static irqreturn_t buttons_irq(int irq, void *dev_id)
51 {
52 struct pin_desc * pindesc = (struct pin_desc *)dev_id;
53 unsigned int pinval;
54
55 pinval = s3c2410_gpio_getpin(pindesc->pin);
56
57 if (pinval)
58 {
59 /* 鬆開 */
60 key_val = 0x80 | pindesc->key_val;
61 }
62 else
63 {
64 /* 按下 */
65 key_val = pindesc->key_val;
66 }
67
68 ev_press = 1; /* 表示中斷發生了 */
69 wake_up_interruptible(&button_waitq); /* 喚醒休眠的進程 */
70
71
72 return IRQ_RETVAL(IRQ_HANDLED);
73 }
74
75 static int thirddrv_open(struct inode *inode, struct file *file)
76 {
77 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
78 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
79 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
80 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
81
82 return 0;
83 }
84
85 static ssize_t thirddrv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
86 {
87
88 if (size != 1)
89 return -EINVAL;
90
91 /* 如果沒有按鍵動作, 休眠 */
92 wait_event_interruptible(button_waitq, ev_press);
93
94 /* 如果有按鍵動作, 返回鍵值 */
95 copy_to_user(buf, &key_val, 1);
96 ev_press = 0;
97
98 return 1;
99 }
100
101 static int thirddrv_close (struct inode * inode, struct file * file)
102 {
103 free_irq(IRQ_EINT0,&pins_desc[0]);
104 free_irq(IRQ_EINT2,&pins_desc[1]);
105 free_irq(IRQ_EINT11,&pins_desc[2]);
106 free_irq(IRQ_EINT19,&pins_desc[3]);
107
108 return 0;
109 }
110
111 static struct file_operations thirddrv_fops = {
112 .owner = THIS_MODULE,
113 .open = thirddrv_open,
114 .read = thirddrv_read,
115 .release = thirddrv_close,
116 };
117
118
119 int g_iMajor;
120 static int thirddrv_init(void)
121 {
122 g_iMajor = register_chrdev(0, "third_drv", &thirddrv_fops);
123
124 g_ptThirddrvCls = class_create(THIS_MODULE, "third_drv");
125
126 g_ptThirddrvClsDev = class_device_create(g_ptThirddrvCls, NULL, MKDEV(g_iMajor, 0), NULL, "buttons"); /* /dev/buttons */
127
128 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
129 gpfdat = gpfcon + 1;
130
131 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
132 gpgdat = gpgcon + 1;
133
134 return 0;
135 }
136
137 static int thirddrv_exit(void)
138 {
139 unregister_chrdev(g_iMajor, "third_drv");
140 class_device_unregister(g_ptThirddrvClsDev);
141 class_destroy(g_ptThirddrvCls);
142 iounmap(gpfcon);
143 iounmap(gpgcon);
144 return 0;
145 }
146
147 module_init(thirddrv_init);
148
149 module_exit(thirddrv_exit);
150 MODULE_LICENSE("GPL");
驅動程式
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5 #include <unistd.h>
6
7 /* thirddrvtest
8 */
9 int main(int argc, char **argv)
10 {
11 int fd;
12 unsigned char key_val;
13
14 fd = open("/dev/buttons", O_RDWR);
15 if (fd < 0)
16 {
17 printf("can't open!\n");
18 }
19
20 while (1)
21 {
22 //read(fd, &key_val, 1);
23 //printf("key_val = 0x%x\n", key_val);
24 sleep(5);
25 }
26
27 return 0;
28 }
測試程式
三、在上述基礎上添加poll機制
雖然我們實現了中斷式的按鍵驅動,但是我們發覺,測試程式是一個無限的迴圈。在實際應用中並不存在這樣的情況,所以我們要進一步優化——poll機制。
1.poll機制的分析
1.1 函數原型
int poll(struct pollfd *fds,nfds_t nfds, int timeout)
//Poll機制會判斷fds中的文件是否可讀,如果可讀則會立即返回,返回的值就是可讀fd的數量,如果不可讀,那麼就進程就會
休眠timeout這麼長的時間,然後再來判斷是否有文件可讀,如果有,返回fd的數量,如果沒有,則返回0.
1.2 poll機制在內核實現分析:
應用程式調用poll
sys_poll
do_sys_poll
poll_initwait(&table)//table->pt->qproc = qproc = __pollwait
do_poll(nfds, head, &table, timeout)
for (;;) {
if (do_pollfd(pfd, pt)) { //return mask;mask=file->f_op->poll(file,pwait)
count++;
pt = NULL;
}
if (count || !*timeout || signal_pending(current))
break;//break的條件是:count非0,超時,有信號在等待處理
__timeout = schedule_timeout(__timeout)//休眠
}
1.3 總結
①poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函數註冊一下回調函數__pollwait,它就是我們的驅動程式執行poll_wait時,真正被調用的函數。
②接下來執行file->f_op->poll,即我們驅動程式里自己實現的poll函數它會調用poll_wait把自己掛入某個隊列,這個隊列也是我們的驅動自己定義的;它還判斷一下設備是否就緒。
③如果設備未就緒,do_sys_poll里會讓進程休眠一定時間,這個時間是應用提供的“超時時間”
④進程被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程式喚醒。驅動程式發現條件就緒時,就把“某個隊列”上掛著的進程喚醒,這個隊列,就是前面通過poll_wait把本進程掛過去的隊列。
⑤如果驅動程式沒有去喚醒進程,那麼schedule_timeout(__timeout)超時後,會重覆2、3動作1
次,然後返回。
2.寫代碼
①在測試程式中加入以下代碼
while (1)
{
ret = poll(fds, 1, 5000);
if (ret == 0)
{
printf("time out\n");
}
int main()
{
int ret;
struct pollfd fds[1]
}
②在file_operations中增加一行
.poll = forth_drv_poll
③寫函數forth_drv_poll
forth_drv_poll
poll_wait
p->qproc(file,wait_address,p)//相當於調用了__pollwait(file,&button_waitq,p)
//把當前進程掛到button_waitq隊列里去
④在增加如下代碼
if (ev_press)
mask |= POLLIN | POLLRDNORM;
1 /*
2 *使用poll 機制實現按鍵驅動程式
3 */
4
5 #include <linux/module.h>
6 #include <linux/kernel.h>
7 #include <linux/fs.h>
8 #include <linux/init.h>
9 #include <linux/delay.h>
10 #include <linux/irq.h>
11 #include <asm/uaccess.h>
12 #include <asm/irq.h>
13 #include <asm/io.h>
14 #include <asm/arch/regs-gpio.h>
15 #include <asm/hardware.h>
16
17
18 static struct class *g_ptForthdrvCls;
19 static struct class_device *g_ptForthdrvClsDev;
20
21 volatile unsigned long *gpfcon;
22 volatile unsigned long *gpfdat;
23
24 volatile unsigned long *gpgcon;
25 volatile unsigned long *gpgdat;
26
27 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
28
29 /* 中斷事件標誌, 中斷服務程式將它置1,forth_drv_read將它清0 */
30 static volatile int ev_press = 0;
31
32 struct pin_desc{
33 unsigned int pin;
34 unsigned int key_val;
35 };
36
37 static unsigned char key_val;
38
39 struct pin_desc pins_desc[4] = {
40 {S3C2410_GPF0, 0x01},
41 {S3C2410_GPF2, 0x02},
42 {S3C2410_GPG3, 0x03},
43 {S3C2410_GPG11, 0x04},
44 };
45
46
47 /*
48 * 確定按鍵值
49 */
50 static irqreturn_t buttons_irq(int irq, void *dev_id)
51 {
52 struct pin_desc * pindesc = (struct pin_desc *)dev_id;
53 unsigned int pinval;
54
55 pinval = s3c2410_gpio_getpin(pindesc->pin);
56
57 if (pinval)
58 {
59 /* 鬆開 */
60 key_val = 0x80 | pindesc->key_val;
61 }
62 else
63 {
64 /* 按下 */
65 key_val = pindesc->key_val;
66 }
67
68 ev_press = 1; /* 表示中斷發生了 */
69 wake_up_interruptible(&button_waitq); /* 喚醒休眠的進程 */
70
71
72 return IRQ_RETVAL(IRQ_HANDLED);
73 }
74
75 static int forthdrv_open(struct inode *inode, struct file *file)
76 {
77 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
78 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
79 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2