linux設備驅動歸納總結(五):4.寫個簡單的LED驅動

小白的博客——提升自已,分享別人

xiaobai.blog.chinaunix.net

好的女人可以不共享。好的技術絕對要共享!   
個人資料
  • 博客訪問:74424
  • 博文數量:42
  • 博客積分:687
  • 博客等級:中校
  • 關注人氣: 4
  • 註冊時間:2010-12-01 18:55:26
訂閱我的博客
  • 訂閱
  • 訂閱到鮮果
  • 訂閱到抓蝦
  • 訂閱到Google
最近來訪
字體大小:  博文

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   

[發評論] 評論 重要提示:警惕虛假中獎信息!
  • diytvgy 2011-02-18 09:45
    liuyewei0108: 受教,謝謝~~~~~.....
    是我沒在原文中講清楚。。。
  • liuyewei0108 2011-02-18 09:22
    diytvgy: 在我的開發板上,GPE12管腳上接了一個LED燈。
    要配置GPE12管腳爲輸出管腳,需要配置寄存器GPECON[24,25]爲0b01。
    通過配置GPEUP[12]來決定GPE12管腳是否上拉。
    .....
    受教,謝謝~~~~~
  • diytvgy 2011-02-17 21:48
    liuyewei0108: 求助:
    void s3c_led_on(void)

    {

    reg = ioread32(gpedat);

    reg &= ~(1 << 12);

    iowrite32(reg, gpedat);

    } 中reg &= ~(1 << 12)是.....
    在我的開發板上,GPE12管腳上接了一個LED燈。
    要配置GPE12管腳爲輸出管腳,需要配置寄存器GPECON[24,25]爲0b01。
    通過配置GPEUP[12]來決定GPE12管腳是否上拉。
    通過給GPEDAT[12]賦值來點亮或者熄滅led。
    所以這裏要reg &= ~(1 << 12)來使GPEDAT[12]清零。
  • liuyewei0108 2011-02-17 16:58
    求助:
    void s3c_led_on(void)

    {

    reg = ioread32(gpedat);

    reg &= ~(1 << 12);

    iowrite32(reg, gpedat);

    } 中reg &= ~(1 << 12)是怎麼確定的阿?爲什麼左移12阿?謝謝
發評論
驗證碼:  更換一張
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章