一起分析Linux系统设计思想——05字符设备驱动之LED驱动(二)

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

1 自动生成设备文件节点

当设备节点比较少时,我们可以手动生成设备节点,但随着设备数量的增加和主次设备号的自动分配,再使用手动创建就不太现实了,这时候就需要用到udev/mdev机制,完成设备节点的自动创建。

完成设备节点自动创建的代码如下。代码中最重要的两个函数是 class_create()class_device_create()

module init 代码:

Tips:为了把代码写的简单一点,错误处理的并不严谨,如果拿到真实项目上使用请做严谨的错误判断和处理。

 /* ko模块源码中的init函数 */
 69 int __init cdriver_init(void)
 70 {
 71     int minor = 0;
 72    
        /* 如果注册字符设备时第一个入参是0,系统就会自动分配主设备号并返回 */
 73     major = register_chrdev(0, "cdriver", &cdriver_fops);                                                 /* 创建设备类,并在类中创建设备 */                     
 75     led_class = class_create(THIS_MODULE, "leds"); /*创建leds设备类*/
 76     if (NULL == led_class)
 77         return -EINVAL;
 78     for (minor = 0; minor < 3; minor++) { /*在类中创建多个子设备*/
 79         led_class_dev[minor] = class_device_create(led_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);                                       
 80         if (NULL == led_class_dev[minor])  
 81             return -EINVAL;
 82     }
 83     /* io物理地址映射从open函数中拿到了这里(因为多个子设备共享,防止每次打开子设备时都映射一次)*/
 84     pgpfcon = ioremap(GPFCON_ADD_BASE, sizeof(int)*2);
 85     if (pgpfcon == NULL)
 86         return -ENOMEM;
 87     pgpfdata = pgpfcon + 1;
 88     
 89     return 0;
 90 }

module exit 代码:

 /* ko模块源码中的exit函数 */
 92 void __exit cdriver_exit(void)
 93 {
 94     int minor = 0;
 95 
 96     iounmap(pgpfcon);
 97 
 98     unregister_chrdev(major, "cdriver"); /*注销掉设备*/
 99     for (minor = 0; minor < 3; minor++)
100         class_device_unregister(led_class_dev[minor]); /*注销掉设备文件节点*/
101     class_destroy(led_class); /*销毁设备类,注意和注销设备文件节点的顺序不能颠倒*/
102 
103 }

2 设备和驱动的关系

设备属于资源,驱动属于加工和使用资源的途径和手段。

很明显,使用 面向对象 的思想来管理设备和驱动再合适不过了。一个个设备就是一个个对象,具有相同或相似特性的设备就是一类设备,行为相似的一类设备就可以共享一个驱动程序,该驱动程序就是类内的方法。

在本篇中,LED就是一类设备,LED驱动程序就是类内的方法(open方法、write方法),LED0、LED1和LED2就是具体的对象。

那靠什么来区分不同的对象呢?——应用程序靠设备文件节点;内核驱动程序靠 次设备号

# 在嵌入式设备上生成的3个led设备文件节点 
/work/001-led # ls /dev/led* -al
crw-rw----    1 0        0        251,   0 Jan  1 00:42 /dev/led0 #次设备号为0
crw-rw----    1 0        0        251,   1 Jan  1 00:42 /dev/led1 #次设备号为1
crw-rw----    1 0        0        251,   2 Jan  1 00:42 /dev/led2 #次设备号为2

在应用程序中打开、读写和操作各个次设备,在内核驱动程序中都可以感知到当前操作的是哪个设备(次设备号会从应用层通过入参形式带下来)。

先来看一下对open函数的处理:

 14 int cdriver_open(struct inode *inode, struct file *file)
 15 {
        /* 最关键的是这行,从inode指针中获取次设备号 */
 16     int minor = MINOR(inode->i_rdev); 
 17 
 18     printk("cdriver open success!\n");
 19 
     	/* 下面是通过次设备号来区分并配置各个LED设备
            具体怎么算的不用关心,和原理图以及手册相关 */
 20     *pgpfcon &= ~(0x3 << ((minor+4)*2)); /*将GPF5设置为输出引脚*/
 21     *pgpfcon |= 0x1 << ((minor+4)*2);
 22 
 23     *pgpfdata |= 0x1 << (minor+4); /*将LED的初始状态设置为关闭*/
 24 
 25     return 0;
 26 }

再来看一下对write函数的处理:

35 ssize_t led_write(struct file *file, const char __user *user_buff, size_t len, loff_t * offset)
 36 {
 37     int cmd_code = 0xff;
        /* 最关键的是这行代码,从file指针中获取inode,再获取次设备号 */
 38     int minor = MINOR(file->f_dentry->d_inode->i_rdev);
 39 
 40     //printk("write \n");
 41     if (len != sizeof(int))
 42         return -EINVAL;
 43 
 44     //printk("usr space: buff is %#x \n", *(int *)user_buff);
 45 
 46     if (copy_from_user(&cmd_code, user_buff, len))
 47         return -EFAULT;
 48     //printk("cmd_code is %#x \n", cmd_code);
 49 
     	/* 根据次设备号(minor)来识别和操作不同的led设备;
     	   具体算法请忽略,和原理图以及数据手册相关(不是重点)*/
 50     if (1 == cmd_code)
 51         *pgpfdata &= ~(0x1 << (minor+4));
 52     else if (0 == cmd_code)
 53         *pgpfdata |= 0x1 << (minor+4);
 54     else ;
 55 
 56     return 0;
 57 }

3 基于多设备实现流水灯

最后,在应用层我们通过操作三个LED设备来实现一个流水灯的效果。

到这里我们也能感觉出来了,在Linux系统下实现流水灯和单片机上的 逻辑/策略 并没有什么不同,不同的是 框架/机制

所以,内核驱动编程的核心和重点是掌握Linux驱动框架。

为了完整性,最后把应用层代码贴出来(代码比较冗余,有兴趣的自己可以优化一下)。

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 
  6 int main(int argc, char **argv)
  7 {
  8     int fd1, fd2, fd3;
  9     int val = 0xff;
 10 
     	/* 这个延时是为了将insmod和该应用程序同时加到/etc/init.d/rcS中时,
     	   保证执行顺序的*/
 11     usleep(500*1000); 
 12 
 13     fd1 = open("/dev/led0", O_RDWR);
 14     if (fd1 < 0)
 15     {
 16         printf("error, can't open %s\n", "/dev/led0");
 17         return 0;
 18     }
 19     fd2 = open("/dev/led1", O_RDWR);
 20     if (fd2 < 0)
 21     {
 22         printf("error, can't open %s\n", "/dev/led1");
 23         return 0;
 24     }
 25     fd3 = open("/dev/led2", O_RDWR);
 26     if (fd3 < 0)
 27     {
 28         printf("error, can't open %s\n", "/dev/led2");
 29         return 0;
 30     }
 31 
 32     for(;;){
 33         val = 1;
 34         write(fd3, &val, sizeof(int));
 35         usleep(200 * 1000);
 36         val = 0;
 37         write(fd3, &val, sizeof(int));
 38         usleep(200 * 1000);
 39 
 40 
 41         val = 1;
 42         write(fd2, &val, sizeof(int));
 43         usleep(200 * 1000);
 44         val = 0;
 45         write(fd2, &val, sizeof(int));
 46         usleep(200 * 1000);
 47 
 48 
 49         val = 1;
 50         write(fd1, &val, sizeof(int));
 51         usleep(200 * 1000);
 52         val = 0;
 53         write(fd1, &val, sizeof(int));
 54         usleep(200 * 1000);
 55     }
 56 
 57     (void)close(fd1);
 58     (void)close(fd2);
 59     (void)close(fd3);
 60 
 61     return 0;
 62 }
 63 

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值