zoukankan      html  css  js  c++  java
  • 【Linux开发】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! ");

    55 return - ENOMEM;

    56 }

    57

    58 s3c_led_config();

    59 s3c_led_on();

    60 printk("hello led! ");

    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 ");

    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! ");

    81 return - ENOMEM;

    82 }

    83

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

    85 my_led.on(&my_led); //这里只不过加载时候灯亮一下,让我知道加载成功

    86 printk("hello led! ");

    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 ");

    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! ");

    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! ");

    109 goto err1;

    110 }

    111 P_DEBUG("major[%d], minor[%d] ", 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! ");

    118 goto err2;

    119 }

    120

    121 my_led.config(&my_led);

    122 my_led.on(&my_led);

    123 P_DEBUG("hello led! ");

    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 ");

    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! ");

    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! ");

    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

    源代码: 5th_mm_4.rar   

  • 相关阅读:
    UVa 1151 Buy or Build【最小生成树】
    UVa 216 Getting in Line【枚举排列】
    UVa 729 The Hamming Distance Problem【枚举排列】
    HDU 5214 Movie【贪心】
    HDU 5223 GCD
    POJ 1144 Network【割顶】
    UVa 11025 The broken pedometer【枚举子集】
    HDU 2515 Yanghee 的算术【找规律】
    Java基本语法
    Java环境变量,jdk和jre的区别,面向对象语言编程
  • 原文地址:https://www.cnblogs.com/huty/p/8518577.html
Copyright © 2011-2022 走看看