linux的led驱动的实验总结,linux设备驱动归纳总结(五):4.写个简单的LED驱动

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

2

#include

3

4

#include

5

#include

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){

70iounmap((void

*)led->virt);

71release_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

84my_led.config(&my_led); //这里调用操作函数是多于了,我迟点会放在ioctl中

85my_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

106ret

= 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

[led_driver__init]major[253],

minor[0]//申请成功的设备号

[led_driver__init]hello

led!

[root:

4th]# rmmod test

[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

2

#include

3

#include

4

#include

5

#include

6

#include

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

[led_driver__init]major[253],

minor[0]

[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

[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

3

#include

4

#include

5

#include

6

#include

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

[led_driver__init]major[253],

minor[0]

[led_driver__init]hello

led!

[root: 6th]# mknod /dev/led_driver c

253 0

[root: 6th]#

./app on &

//后台开灯

[root: 6th]#

./app off

//在灭灯

[s3c_led_open]led

busy!

//灭灯进程无法打开设备文件,返回错误

open: Device or resource busy

[root: 6th]# rmmod led_driver

[led_driver__exit]bye

这样,一个简单的LED驱动就实现了,大家也可以尝试将my_led结构体通过kmalloc来申请,我只是觉得这个结构体占用的空间不多,就把这个步骤免了。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

七、总结

上面的驱动我是按以下顺序写的:

1)实现硬件操作config,on.off

2)定义面向对象数据结构

3)定义硬件初始化操作

4)实现字符设备注册

5)实现ioctl等字符设备操作

6)实现信号量限制打开文件个数

上面介绍了我写驱动函数的步骤,其实最先的步骤应该是定义面向对象的数据结构,在开始实现其他的函数操作,只不过我之前已经将部分的硬件操作函数写好了,所以就稍稍改了前三步的步骤。接下来总结一下:

顺序不是一成不变的,但无论怎么写,也要按照从底层到上层,逐个逐个往上封装。

当然,这个驱动只是我结合了之前学的知识写的,内核中的驱动不可能这么简单,

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

你可能感兴趣的:(linux的led驱动的实验总结,linux设备驱动归纳总结(五):4.写个简单的LED驱动)