linux設備驅動歸納總結(五):4.寫個簡單的LED驅動
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在上面的章節的知識,已經能夠實現個簡單的LED驅動。居於前面操作LED的函數(5th_mm_2/3rd/test.c),我一步一步來修改。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、實現硬件操作函數
一般的,我寫驅動的時候,我會先確定一些基本的硬件操作函數能夠使用。如LED驅動我要實現三個操作:配置、開燈和關燈,所以我先要實現這幾個硬件操作函數。
其實這些在我介紹IO內存時已經實現了(5th_mm_2/3rd/test.c),我只是稍作了一點修改,改了一下內存的數據類型,其實沒什麼大出入。
/*5th_mm_4/1st/test.c*/
1 #include <linux/module.h>
2 #include <linux/init.h>
3
4 #include <asm/io.h>
5 #include <linux/ioport.h>
6
7 unsigned long virt, phys;
8 unsigned long gpecon, gpedat, gpeup; //其實我就改了這裏的數據類型,其實都是用來存放地址
9 unsigned long reg; //沒有多大的影響。
10 struct resource *led_resource;
11
12 void s3c_led_config(void) //還將函數的名字改成好聽點
13 {
14 reg = ioread32(gpecon);
15 reg &= ~(3 << 24);
16 reg |= (1 << 24);
17 iowrite32(reg, gpecon);
18
19 reg = ioread32(gpeup);
20 reg &= ~(3 << 12);
21 iowrite32(reg, gpeup);
22 }
23
24 void s3c_led_on(void)
25 {
26 reg = ioread32(gpedat);
27 reg &= ~(1 << 12);
28 iowrite32(reg, gpedat);
29 }
30
31 void s3c_led_off(void)
32 {
33 reg = ioread32(gpedat);
34 reg |= (1 << 12);
35 iowrite32(reg, gpedat);
36 }
37
38 void init_led_device(void)
39 {
40 phys = 0x56000000;
41 virt = (unsigned long)ioremap(phys, 0x0c);
42
43 gpecon = virt + 0x40;
44 gpedat = virt + 0x44;
45 gpeup = virt + 0x48;
46 }
47
48 static int __init test_init(void) //模塊初始化函數
49 {
50 init_led_device();
51
52 led_resource = request_mem_region(phys, 0x0c, "LED_MEM");
53 if(NULL == led_resource){
54 printk("request mem error!\n");
55 return - ENOMEM;
56 }
57
58 s3c_led_config();
59 s3c_led_on();
60 printk("hello led!\n");
61 return 0;
62 }
63
64 static void __exit test_exit(void) //模塊卸載函數
65 {
66 if(NULL != led_resource){
67 s3c_led_off();
68 iounmap((void *)virt);
69 release_mem_region(phys, 0x0c);
70 }
71 printk("bye\n");
72 }
73
74 module_init(test_init);
75 module_exit(test_exit);
76
77 MODULE_LICENSE("GPL");
78 MODULE_AUTHOR("xoao bai");
79 MODULE_VERSION("v0.1");
至於驗證我就不做了,效果還是一樣,加載模塊燈亮,卸載模塊燈滅。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、面向對象思想——定義一個LED的結構體
上面的函數中,一大堆的全局變量實在讓人看起來不舒服。我在第三章字符設備的文中介紹過,把這些變量定義在一個結構體中,方便以後引用,如函數傳參。
/*5th_mm_4/2nd/test.c*/
7 struct _led_t{
8 //hardware obb
9 unsigned long virt, phys;
10 unsigned long gpecon, gpedat, gpeup;
11 unsigned long reg;
12 struct resource *led_resource;
13
14 void (*config)(struct _led_t *); //這裏把LED驅動的三個操作函數指針也放進去
15 void (*on)(struct _led_t *);
16 void (*off)(struct _led_t *);
17 };
根據上面定義的數據結構,我再修改一下1st目錄的程序,就成了2nd目錄中的函數。現在函數做了兩步:
1)實現硬件的基本操作。
2)定義了一個面向對象數據類型。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、實現硬件設備初始化函數和註銷函數
在對硬件進程操作(配置,開燈、關燈)之前,需要先進行IO內存映射等操作,前面的函數寫得很零散,這裏我整理了一下:
1)當插入模塊時,需要進行一些內存映射等設備初始化操作,使用函數init_led_device。
2)當卸載模塊時,需要進行一些硬件註銷操作,使用函數eixt_led_device。
接下來就要封裝這兩個函數:
/*5th_mm_4/3rd/test.c */
45 int init_led_device(struct _led_t *led)
46 {
47 led->phys = 0x56000000; //1指定物理地址
48
49 led->led_resource = request_mem_region(led->phys, 0x0c, "LED_MEM");
50 if(NULL == led->led_resource){ //2申請內存區域
51 return - 1;
52 }
53
54 led->virt = (unsigned long)ioremap(led->phys, 0x0c); //3內存映射
55
56 led->gpecon = led->virt + 0x40; //4指定寄存器地址
57 led->gpedat = led->virt + 0x44;
58 led->gpeup = led->virt + 0x48;
59
60 led->config = s3c_led_config; //5將操作函數也放進結構體成員
61 led->on = s3c_led_on;
62 led->off = s3c_led_off;
63
64 return 0;
65 }
66
67 void exit_led_device(struct _led_t *led)
68 {
69 if(NULL != led->led_resource){
70 iounmap((void *)led->virt);
71 release_mem_region(led->phys, 0x0c);
72 }
73 }
74
75 struct _led_t my_led;
76
77 static int __init test_init(void) //模塊初始化函數
78 {
79 if (-1 == init_led_device(&my_led)){ //加載模塊時就調用init_led_device
80 printk("request mem error!\n");
81 return - ENOMEM;
82 }
83
84 my_led.config(&my_led); //這裏調用操作函數是多於了,我遲點會放在ioctl中
85 my_led.on(&my_led); //這裏只不過加載時候燈亮一下,讓我知道加載成功
86 printk("hello led!\n");
87 return 0;
88 }
89
90 static void __exit test_exit(void) //模塊卸載函數
91 {
92 my_led.off(&my_led);
93 exit_led_device(&my_led); //卸載時調用exit_led_device
94 printk("bye\n");
95 }
至於驗證我就不做了,效果還是一樣,加載模塊燈亮,卸載模塊燈滅。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、實現字符設備的申請,即模塊與內核的接口
需要實現ioctl功能,首先要這個設備需要先註冊,使用字符設備註冊的知識:
字符設備註冊三步曲:
/*5th_mm_4/4th/test.c*/
18 struct _led_t{
19 //hardware obb
20 unsigned long virt, phys;
21 unsigned long gpecon, gpedat, gpeup;
22 unsigned long reg;
23 struct resource *led_resource;
24
25 void (*config)(struct _led_t *);
26 void (*on)(struct _led_t *);
27 void (*off)(struct _led_t *);
28
29 //kernel oob
30 dev_t devno; //往結構體添加了兩個成員
31 struct cdev led_cdev;
32 };
。。。。。。
90 struct _led_t my_led;
91 struct file_operations s3c_led_fops = {
92 //暫時還是空的
93 };
94
95 static int __init led_driver__init(void) //模塊初始化函數
96 {
97 int ret;
98
99 ret = init_led_device(&my_led);
100 if (ret){
101 P_DEBUG("request mem error!\n");
102 ret = - ENOMEM;
103 goto err0;
104 }
105
106 ret = alloc_chrdev_region(&my_led.devno, 0, 1, "s3c_led_driver"); //1申請cdev
107 if (ret){
108 P_DEBUG("alloc chrdev failed!\n");
109 goto err1;
110 }
111 P_DEBUG("major[%d], minor[%d]\n", MAJOR(my_led.devno), MINOR(my_led.devno));
112
113 cdev_init(&my_led.led_cdev, &s3c_led_fops); //2初始化cdev
114
115 ret = cdev_add(&my_led.led_cdev, my_led.devno, 1); //3添加cdev
116 if (ret){
117 P_DEBUG("cdev_add failed!\n");
118 goto err2;
119 }
120
121 my_led.config(&my_led);
122 my_led.on(&my_led);
123 P_DEBUG("hello led!\n");
124 return 0;
125
126 err2:
127 unregister_chrdev_region(my_led.devno, 1);
128 err1:
129 exit_led_device(&my_led);
130 err0:
131 return ret;
132 }
133
134 static void __exit led_driver__exit(void) //模塊卸載函數
135 {
136 my_led.off(&my_led);
137
138 unregister_chrdev_region(my_led.devno, 1); //卸載是註銷cdev結構
139 exit_led_device(&my_led);
140 P_DEBUG("bye\n");
141 }
這裏就可以驗證一下了:
[root: 4th]# insmod test.ko
<kernel>[led_driver__init]major[253], minor[0] //申請成功的設備號
<kernel>[led_driver__init]hello led!
[root: 4th]# rmmod test
<kernel>[led_driver__exit]bye
既然設備申請成功,接下來就是要實現系統調用接口了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、實現系統調用對應的函數ioctl
在這裏,我需要實現的內容是,在應用層使用ioctl系統調用,可以操作LED配置、打開和關閉。接下來實現文件操作結構體中的ioctl。
1、首先要定義命令:
/*5th_mm_4/5th/led_ioctl.h*/
1 #ifndef _LED_H
2 #define _LED_H
3
4 #define LED_MAGIC 'x'
5 #define LED_CONF _IO(LED_MAGIC, 0)
6 #define LED_ON _IO(LED_MAGIC, 1)
7 #define LED_OFF _IO(LED_MAGIC, 2)
8
9 #endif /* _LED_H */
2、接着實現文件操作結構體中的ioctl:
/*5th_mm_4/5th/led_driver.c */ //這裏我把文件的名字改了
92 int s3c_led_ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsign ed long args)
93 {
94 int ret;
95 struct _led_t *dev = container_of(node->i_cdev, struct _led_t, led_cdev);
96 switch(cmd){
97 case LED_CONF:
98 dev->config(dev);
99 break;
100 case LED_ON:
101 dev->on(dev);
102 break;
103 case LED_OFF:
104 dev->off(dev);
105 break;
106 default:
107 P_DEBUG("unknow cmd!\n");
108 ret = - EINVAL;
109 goto err0;
110 }
111 return 0;
112
113 err0:
114 return ret;
115 }
116
117 struct _led_t my_led;
118 struct file_operations s3c_led_fops = {
119 .ioctl = s3c_led_ioctl, //一定要加上。打開和關閉操作我不實現,使用默認的
120 };
3、接着實現應用層函數:
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <strings.h>
6 #include <sys/ioctl.h>
7
8 #include "led_ioctl.h"
9
10 int main(int argc, char *argv[])
11 {
12 int fd;
13 fd = open("/dev/led_driver", O_RDWR);
14 if(fd < 0){
15 perror("open");
16 return -1;
17 }
18
19 ioctl(fd, LED_CONF);
20
21 if(!strncasecmp("on", argv[1], 3))
22 ioctl(fd, LED_ON);
23
24 if(!strncasecmp("off", argv[1], 3))
25 ioctl(fd, LED_OFF);
26
27
28 return 0;
29 }
驗證一下:
[root: 5th]# insmod led_driver.ko
<kernel>[led_driver__init]major[253], minor[0]
<kernel>[led_driver__init]hello led!
[root: 5th]# mknod /dev/led_driver c 253 0
[root: 5th]# ./app on //亮燈
[root: 5th]# ./app off //滅燈
[root: 5th]# rmmod led_driver
<kernel>[led_driver__exit]bye
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
六、使用信號量
其實在單處理器非搶佔內核下,是沒有必要使用到內核同步機制的,這裏使用信號量來限制只能同時一個進程打開並操作led設備文件。實現的方法就是在打開的時候使用信號量:
/*5th_mm_4/6th/led_driver.c*/
20 struct _led_t{
21 //hardware obb
22 unsigned long virt, phys;
23 unsigned long gpecon, gpedat, gpeup;
24 unsigned long reg;
25 struct resource *led_resource;
26
27 void (*config)(struct _led_t *);
28 void (*on)(struct _led_t *);
29 void (*off)(struct _led_t *);
30
31 //kernel oob
32 dev_t devno;
33 struct cdev led_cdev;
34 struct semaphore led_sem; //非搶佔下,其實單純使用一個標誌flag來實現也行,
35 }; //文件打開減一,關閉加一,flag不爲零時可打開。
。。。。。。。。
63 int init_led_device(struct _led_t *led)
64 {
65 led->phys = 0x56000000;
66
67 led->led_resource = request_mem_region(led->phys, 0x0c, "LED_MEM");
68 if(NULL == led->led_resource){
69 return - 1;
70 }
71
72 led->virt = (unsigned long)ioremap(led->phys, 0x0c);
73
74 led->gpecon = led->virt + 0x40;
75 led->gpedat = led->virt + 0x44;
76 led->gpeup = led->virt + 0x48;
77
78 led->config = s3c_led_config;
79 led->on = s3c_led_on;
80 led->off = s3c_led_off;
81
82 sema_init(&led->led_sem, 1);
83
84 return 0;
85 }
。。。。。。。
120 int s3c_led_open (struct inode *node, struct file *filp)
121 {
122 struct _led_t *dev = container_of(node->i_cdev, struct _led_t, led_cdev);
123 filp->private_data = dev;
124
125 if (down_trylock(&dev->led_sem)){ //獲得鎖
126 P_DEBUG("led busy!\n");
127 return - EBUSY;
128 }
129
130 return 0;
131 }
132
133 int s3c_led_release (struct inode *node, struct file *filp)
134 {
135 struct _led_t *dev = filp->private_data;
136 up(&dev->led_sem); //釋放鎖
137 return 0;
138 }
139
140
141 struct _led_t my_led;
142 struct file_operations s3c_led_fops = {
143 .ioctl = s3c_led_ioctl,
144 .open = s3c_led_open,
145 .release = s3c_led_release,
146 };
爲了驗證,修改一下應用程序,使程序陷入死循環不退出:
/*5th_mm_4/6th/app.c*/
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <strings.h>
6 #include <sys/ioctl.h>
7
8 #include "led_ioctl.h"
9
10 int main(int argc, char *argv[])
11 {
12 int fd;
13 fd = open("/dev/led_driver", O_RDWR);
14 if(fd < 0){
15 perror("open");
16 return -1;
17 }
18
19 ioctl(fd, LED_CONF);
20
21 if(!strncasecmp("on", argv[1], 3))
22 ioctl(fd, LED_ON);
23
24 if(!strncasecmp("off", argv[1], 3))
25 ioctl(fd, LED_OFF);
26
27 while(1)
28 {
29 ;
30 }
31
32 close(fd);
33 return 0;
34 }
也來驗證一下:
[root: 6th]# insmod led_driver.ko
<kernel>[led_driver__init]major[253], minor[0]
<kernel>[led_driver__init]hello led!
[root: 6th]# mknod /dev/led_driver c 253 0
[root: 6th]# ./app on & //後臺開燈
[root: 6th]# ./app off //在滅燈
<kernel>[s3c_led_open]led busy! //滅燈進程無法打開設備文件,返回錯誤
open: Device or resource busy
[root: 6th]# rmmod led_driver
<kernel>[led_driver__exit]bye
這樣,一個簡單的LED驅動就實現了,大家也可以嘗試將my_led結構體通過kmalloc來申請,我只是覺得這個結構體佔用的空間不多,就把這個步驟免了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
七、總結
上面的驅動我是按以下順序寫的:
1)實現硬件操作config,on.off
2)定義面向對象數據結構
3)定義硬件初始化操作
4)實現字符設備註冊
5)實現ioctl等字符設備操作
6)實現信號量限制打開文件個數
上面介紹了我寫驅動函數的步驟,其實最先的步驟應該是定義面向對象的數據結構,在開始實現其他的函數操作,只不過我之前已經將部分的硬件操作函數寫好了,所以就稍稍改了前三步的步驟。接下來總結一下:
順序不是一成不變的,但無論怎麼寫,也要按照從底層到上層,逐個逐個往上封裝。
當然,這個驅動只是我結合了之前學的知識寫的,內核中的驅動不可能這麼簡單,
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代碼: 5th_mm_4.rar
要配置GPE12管腳爲輸出管腳,需要配置寄存器GPECON[24,25]爲0b01。
通過配置GPEUP[12]來決定GPE12管腳是否上拉。
.....
void s3c_led_on(void)
{
reg = ioread32(gpedat);
reg &= ~(1 << 12);
iowrite32(reg, gpedat);
} 中reg &= ~(1 << 12)是.....
要配置GPE12管腳爲輸出管腳,需要配置寄存器GPECON[24,25]爲0b01。
通過配置GPEUP[12]來決定GPE12管腳是否上拉。
通過給GPEDAT[12]賦值來點亮或者熄滅led。
所以這裏要reg &= ~(1 << 12)來使GPEDAT[12]清零。
void s3c_led_on(void)
{
reg = ioread32(gpedat);
reg &= ~(1 << 12);
iowrite32(reg, gpedat);
} 中reg &= ~(1 << 12)是怎麼確定的阿?爲什麼左移12阿?謝謝