精华内容
下载资源
问答
  • Linux字符设备驱动详解
    千次阅读
    2021-11-21 20:40:23

    系列文章目录

    Linux字符设备驱动详解
    Linux字符设备驱动详解二(使用设备驱动模型)
    Linux字符设备驱动详解三(使用class)
    Linux字符设备驱动详解四(使用自属的xbus驱动总线)
    Linux字符设备驱动详解五(使用platform虚拟平台总线)
    Linux字符设备驱动详解六(设备树实现RGB灯驱动)
    Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

    前言

    很久没有认真写一篇博客了,刚好最近学习了Linux字符设备驱动,好记性不如烂笔头,当然是要抓紧记下来,在开始之前安利一位师弟写的几篇博客,写得很不错。本文主要来自正点原子、野火Linux教程及本人理解,若有侵权请及时联系本人删除。
    从单片机到ARM Linux驱动——Linux驱动入门篇
    Linux字符设备驱动开发(2)——让开发板上的灯闪烁

    驱动目录

    /dev/xxx

    正文

    Linux内核是怎么设计字符设备

    结合前两篇文章,我这里讲的就比较简洁,下图是字符设备的整体框图。将其分为三步。
    在这里插入图片描述

    第一步:填充并保存硬件接口

    这一步就是驱动文件所实现的,以原子LED驱动为例,第一步主要完成通过操作寄存器填充硬件接口,然后通过cdev_init函数保存接口file_operation到cdev中,通过cdev_add函数,根据哈希函数保存cdev到probes哈希表中,方便内核查找file_operation使用。而这两个函数下面代码register_chrdev(LED_MAJOR, LED_NAME, &led_fops) 已经帮我们都完成了,详情参考Linux源码。

    下图说明了Linux使用一张哈希表(数组+链表)来管理设备号。
    在这里插入图片描述
    talk is cheap, show you the code(原子驱动文件)

    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/ide.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/gpio.h>
    #include <asm/mach/map.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>
    
    #define LED_MAJOR		200		/* 主设备号 */
    #define LED_NAME		"led" 	/* 设备名字 */
    
    #define LEDOFF 	0				/* 关灯 */
    #define LEDON 	1				/* 开灯 */
     
    /* 寄存器物理地址 */
    #define CCM_CCGR1_BASE				(0X020C406C)	
    #define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
    #define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
    #define GPIO1_DR_BASE				(0X0209C000)
    #define GPIO1_GDIR_BASE				(0X0209C004)
    
    /* 映射后的寄存器虚拟地址指针 */
    static void __iomem *IMX6U_CCM_CCGR1;
    static void __iomem *SW_MUX_GPIO1_IO03;
    static void __iomem *SW_PAD_GPIO1_IO03;
    static void __iomem *GPIO1_DR;
    static void __iomem *GPIO1_GDIR;
    
    /*
     * @description		: LED打开/关闭
     * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
     * @return 			: 无
     */
    void led_switch(u8 sta)
    {
    	u32 val = 0;
    	if(sta == LEDON) {
    		val = readl(GPIO1_DR);
    		val &= ~(1 << 3);	
    		writel(val, GPIO1_DR);
    	}else if(sta == LEDOFF) {
    		val = readl(GPIO1_DR);
    		val|= (1 << 3);	
    		writel(val, GPIO1_DR);
    	}	
    }
    
    /*
     * @description		: 打开设备
     * @param - inode 	: 传递给驱动的inode
     * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
     * 					  一般在open的时候将private_data指向设备结构体。
     * @return 			: 0 成功;其他 失败
     */
    static int led_open(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    /*
     * @description		: 从设备读取数据 
     * @param - filp 	: 要打开的设备文件(文件描述符)
     * @param - buf 	: 返回给用户空间的数据缓冲区
     * @param - cnt 	: 要读取的数据长度
     * @param - offt 	: 相对于文件首地址的偏移
     * @return 			: 读取的字节数,如果为负值,表示读取失败
     */
    static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    {
    	return 0;
    }
    
    /*
     * @description		: 向设备写数据 
     * @param - filp 	: 设备文件,表示打开的文件描述符
     * @param - buf 	: 要写给设备写入的数据
     * @param - cnt 	: 要写入的数据长度
     * @param - offt 	: 相对于文件首地址的偏移
     * @return 			: 写入的字节数,如果为负值,表示写入失败
     */
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
    	int retvalue;
    	unsigned char databuf[1];
    	unsigned char ledstat;
    
    	retvalue = copy_from_user(databuf, buf, cnt);
    	if(retvalue < 0) {
    		printk("kernel write failed!\r\n");
    		return -EFAULT;
    	}
    
    	ledstat = databuf[0];		/* 获取状态值 */
    
    	if(ledstat == LEDON) {	
    		led_switch(LEDON);		/* 打开LED灯 */
    	} else if(ledstat == LEDOFF) {
    		led_switch(LEDOFF);	/* 关闭LED灯 */
    	}
    	return 0;
    }
    
    /*
     * @description		: 关闭/释放设备
     * @param - filp 	: 要关闭的设备文件(文件描述符)
     * @return 			: 0 成功;其他 失败
     */
    static int led_release(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    /* 设备操作函数 */
    static struct file_operations led_fops = {
    	.owner = THIS_MODULE,
    	.open = led_open,
    	.read = led_read,
    	.write = led_write,
    	.release = 	led_release,
    };
    
    /*
     * @description	: 驱动出口函数
     * @param 		: 无
     * @return 		: 无
     */
    static int __init led_init(void)
    {
    	int retvalue = 0;
    	u32 val = 0;
    
    	/* 初始化LED */
    	/* 1、寄存器地址映射 */
      	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
      	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    	GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
    
    	/* 2、使能GPIO1时钟 */
    	val = readl(IMX6U_CCM_CCGR1);
    	val &= ~(3 << 26);	/* 清楚以前的设置 */
    	val |= (3 << 26);	/* 设置新值 */
    	writel(val, IMX6U_CCM_CCGR1);
    
    	/* 3、设置GPIO1_IO03的复用功能,将其复用为
    	 *    GPIO1_IO03,最后设置IO属性。
    	 */
    	writel(5, SW_MUX_GPIO1_IO03);
    	
    	/*寄存器SW_PAD_GPIO1_IO03设置IO属性
    	 *bit 16:0 HYS关闭
    	 *bit [15:14]: 00 默认下拉
         *bit [13]: 0 kepper功能
         *bit [12]: 1 pull/keeper使能
         *bit [11]: 0 关闭开路输出
         *bit [7:6]: 10 速度100Mhz
         *bit [5:3]: 110 R0/6驱动能力
         *bit [0]: 0 低转换率
    	 */
    	writel(0x10B0, SW_PAD_GPIO1_IO03);
    
    	/* 4、设置GPIO1_IO03为输出功能 */
    	val = readl(GPIO1_GDIR);
    	val &= ~(1 << 3);	/* 清除以前的设置 */
    	val |= (1 << 3);	/* 设置为输出 */
    	writel(val, GPIO1_GDIR);
    
    	/* 5、默认关闭LED */
    	val = readl(GPIO1_DR);
    	val |= (1 << 3);	
    	writel(val, GPIO1_DR);
    
    	/* 6、注册字符设备驱动 */
    	retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    	if(retvalue < 0){
    		printk("register chrdev failed!\r\n");
    		return -EIO;
    	}
    	return 0;
    }
    
    /*
     * @description	: 驱动出口函数
     * @param 		: 无
     * @return 		: 无
     */
    static void __exit led_exit(void)
    {
    	/* 取消映射 */
    	iounmap(IMX6U_CCM_CCGR1);
    	iounmap(SW_MUX_GPIO1_IO03);
    	iounmap(SW_PAD_GPIO1_IO03);
    	iounmap(GPIO1_DR);
    	iounmap(GPIO1_GDIR);
    
    	/* 注销字符设备驱动 */
    	unregister_chrdev(LED_MAJOR, LED_NAME);
    }
    
    module_init(led_init);
    module_exit(led_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("mu-xin");
    

    通过Makefile编译,make后生成名为“led.ko”的驱动模块文件,使用insmod命令加载模块,到此完成第一步

    第二步:创建设备文件(节点)

    第二步使用mknod命令创建设备文件(节点)。接下来我们就可以在用户态操作这个文件(节点),比如向此设备文件(节点)写入数据。

    sudo mknod /dev/xxx c 244 0
    

    在这里插入图片描述
    注意inode上的file_operation并不是自己构造的file_operation,而是字符设备通用的def_chr_fops,那么自己构建的file_operation等在应用程序调用open函数之后,才会绑定在文件上。

    第三步:用户空间编程

    完成前两步我们就可以通过写APP文件,或者使用echo命令操作设备文件(节点)。

    野火echo命令测试

    野火驱动文件

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    
    #include <linux/fs.h>
    #include <linux/uaccess.h>
    #include <asm/io.h>
    
    #define DEV_MAJOR		0		/* 动态申请主设备号 */
    #define DEV_NAME		"red_led" 	/*led设备名字 */
    
    /* GPIO虚拟地址指针 */
    static void __iomem *IMX6U_CCM_CCGR1;
    static void __iomem *SW_MUX_GPIO1_IO04;
    static void __iomem *SW_PAD_GPIO1_IO04;
    static void __iomem *GPIO1_DR;
    static void __iomem *GPIO1_GDIR;
    
    static int led_open(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    {
    	return -EFAULT;
    }
    
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
    
    	unsigned char databuf[10];
    
    	if(cnt >10)
    		cnt =10;
    		
        /*从用户空间拷贝数据到内核空间*/
        if(copy_from_user(databuf, buf, cnt)){
    		return -EIO;
    	}
        	
    	if(!memcmp(databuf,"on",2)) {	
    		iowrite32(0 << 4, GPIO1_DR);	
    	} else if(!memcmp(databuf,"off",3)) {
    		iowrite32(1 << 4, GPIO1_DR);
    	}
    	/*写成功后,返回写入的字数*/
    	return cnt;
    }
    
    static int led_release(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    /* 自定义led的file_operations 接口*/
    static struct file_operations led_fops = {
    	.owner = THIS_MODULE,
    	.open = led_open,
    	.read = led_read,
    	.write = led_write,
    	.release = 	led_release,
    };
    
    int major = 0;
    static int __init led_init(void)
    {
    	
    	/* GPIO相关寄存器映射 */
      	IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);
    	SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);
      	SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);
    	GPIO1_GDIR = ioremap(0x0209c004, 4);
    	GPIO1_DR = ioremap(0x0209c000, 4);
    
    
    	/* 使能GPIO1时钟 */
    	iowrite32(0xffffffff, IMX6U_CCM_CCGR1);
    
    	/* 设置GPIO1_IO04复用为普通GPIO*/
    	iowrite32(5, SW_MUX_GPIO1_IO04);
    	
        /*设置GPIO属性*/
    	iowrite32(0x10B0, SW_PAD_GPIO1_IO04);
    
    	/* 设置GPIO1_IO04为输出功能 */
    	iowrite32(1 << 4, GPIO1_GDIR);
    
    	/* LED输出高电平 */
    	iowrite32(1<< 4, GPIO1_DR);
    
    	/* 注册字符设备驱动 */
    	major = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops);
        printk(KERN_ALERT "led major:%d\n",major);
    
    	return 0;
    }
    
    static void __exit led_exit(void)
    {
    	/* 取消映射 */
    	iounmap(IMX6U_CCM_CCGR1);
    	iounmap(SW_MUX_GPIO1_IO04);
    	iounmap(SW_PAD_GPIO1_IO04);
    	iounmap(GPIO1_DR);
    	iounmap(GPIO1_GDIR);
    
    	/* 注销字符设备驱动 */
    	unregister_chrdev(major, DEV_NAME);
    }
    
    
    module_init(led_init);
    module_exit(led_exit);
    
    MODULE_LICENSE("GPL2");
    MODULE_AUTHOR("embedfire ");
    MODULE_DESCRIPTION("led_module");
    MODULE_ALIAS("led_module");
    

    echo命令示例(注意echo命令的功能是在显示器上显示一段文字,该命令的一般格式为: echo [ -n ] 字符串,其中选项n表示输出文字后不换行;字符串能加单引号,也能不加单引号。该篇Linux字符设备驱动详解三(使用class))未加单引号

    sudo sh -c "echo on >/dev/xxx"
    开灯
    sudo sh -c "echo on >/dev/xxx"
    灭灯
    

    原子APP文件测试

    原子APP代码如下

    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    
    #define LEDOFF 	0
    #define LEDON 	1
    
    /*
     * @description		: main主程序
     * @param - argc 	: argv数组元素个数
     * @param - argv 	: 具体参数
     * @return 			: 0 成功;其他 失败
     */
    int main(int argc, char *argv[])
    {
    	int fd, retvalue,i;
    	char *filename;
    	unsigned char databuf[1];
    	
    	if(argc != 3){
    		printf("Error Usage!\r\n");
    		return -1;
    	}
    
    	filename = argv[1];
    
    	/* 打开led驱动 */
    	fd = open(filename, O_RDWR);
    	if(fd < 0){
    		printf("file %s open failed!\r\n", argv[1]);
    		return -1;
    	}
    
    	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭,使用字符转整形函数atoi()函数*/
    
    	/* 向/dev/led文件写入数据 */
    	for(i=0;i<10;i++)
    	{
    		retvalue = write(fd, databuf, sizeof(databuf));
    		if(retvalue < 0){
    			printf("LED Control Failed!\r\n");
    			close(fd);
    			return -1;
    		}
    		sleep(1);
    		retvalue = write(fd, 0, sizeof(0));
    		if(retvalue < 0){
    			printf("LED Control Failed!\r\n");
    			close(fd);
    			return -1;
    		}
    		sleep(1);
    	}
    	retvalue = close(fd); /* 关闭文件 */
    	if(retvalue < 0){
    		printf("file %s close failed!\r\n", argv[1]);
    		return -1;
    	}
    	return 0;
    }
    

    通过命令编译生成ledApp这个应用程序

    arm-linux-gnueabihf-gcc ledApp.c -o ledApp
    

    命令测试

    ./ledApp /dev/led 1
    开灯
    ./ledApp /dev/led 0
    灭灯
    

    当我们在用户空间调用open后发生了什么,在用户空间调用open函数后,系统由用户态进入内核态
    在这里插入图片描述

    • get_unused_fd_flags
      • 为本次操作分配一个未使用过的文件描述符
    • do_file_open
      • 生成一个空白struct file结构体
      • 从文件系统中查找到文件对应的inode
    • do_dentry_open
    static int do_dentry_open(struct file *f,
    			  struct inode *inode,
    			  int (*open)(struct inode *, struct file *))
    {
    	...
    	/*把inode的i_fop赋值给struct file的f_op*/
    	f->f_op = fops_get(inode->i_fop);
    	...
    	if (!open)
    		open = f->f_op->open;
    	if (open) {
    		error = open(inode, f);
    		if (error)
    			goto cleanup_all;
    	}
    	...
    }
    

    在do_dentry_open函数中调用下面的open函数

    • def_chr_fops->chrdev_open
    static int chrdev_open(struct inode *inode, struct file *filp)
    {
    	const struct file_operations *fops;
    	struct cdev *p;
    	struct cdev *new = NULL;
    	...
    	struct kobject *kobj;
    	int idx;
    	/*从内核哈希表cdev_map中,根据设备号查找自己注册的sturct cdev,获取cdev中的file_operation接口*/
    	kobj = kobj_lookup(cdev_map, inode>i_rdev,&idx);
    	new = container_of(kobj, struct cdev, kobj);
    	...
    	inode->i_cdev = p = new;
    	...
    	fops = fops_get(p->ops);
    	...
    	/*把cdev中的file_operation接口赋值给struct file的f_op*/
    	replace_fops(filp, fops);
    	
    	/*调用自己实现的file_operation接口中的open函数*/
    	if (filp->f_op->open) {
    		ret = filp->f_op->open(inode, filp);
    		if (ret)
    			goto out_cdev_put;
    	}
    	...
    }
    

    总结

    完成上面三步后就完成了LED字符设备的驱动编写,其他的字符设备可参考此驱动。

    用户空间调用open函数打开某个设备文件后,在进程中会为设备文件分配一个未使用过的文件描述符,并且生成一个空白struct file结构体,然后从文件系统中查找到文件对应的inode,这里的inode也就是第二步自己在文件系统中创建的设备节点,然后把该inode的i_fop赋值给进程中的struct file的f_op,也就是说此时进程已经找到了设备节点了,但是还没有调用我们自己写的驱动,即真正的file_operation接口。

    接下来为了调用真正的file_operation接口,我们从内核哈希表cdev_map中,根据设备号查找自己注册的sturct cdev,获取cdev中的file_operation接口,把cdev中的file_operation接口赋值给struct file的f_op,此时进程已经可以最终调用自己实现的file_operation接口中的open函数,至此进程获得了该设备文件的自己实现的读写等操作。

    注意glibc库的fopen函数、系统调用open函数和自己实现的硬件接口open函数不能混淆。在字符设备驱动中系统调用open函数最终调用的是自己实现的硬件接口open函数。

    最后,本篇基于Linux早期内核(2.4之前),没有统一的设备驱动模型,后面介绍Linux内核2.6版本以后的设备驱动模型,即使用挂载在/sys目录下的sysfs。
    详情请阅读:Linux字符设备驱动详解二(使用设备驱动模型)

    更多相关内容
  • 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序。可以有一个读,一个写进程共享该字符设备,进行聊天;也可以由多个读和多个写进程共享该字符设备,进行聊天
  • 字符设备驱动

    2018-10-13 22:04:57
    Linux设备驱动PPT,字符设备驱动PPT,真心讲的挺不错的
  • 字符设备是Linux三大设备...编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。 Linux一切皆文件,那么作为一个设备文件,它的操作方法接口封装在structfi
  • 基于linux系统的字符设备驱动研究与设计.pdf
  • 在Linux内核里面,设备(device)主要分为字符设备,块设备,网络设备,字符设备驱动是Linux驱动基础,在看《Linux 设备驱动开发详解》这本书的过程中,把字符设备相知识记录整理如下。 字符设备驱动的组成 字符设备...
  • 基于Linux字符设备驱动程序的设计与实现.pdf
  • Linux 字符设备驱动框架 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行...
  • 今天小编就为大家分享一篇关于Linux内核设备驱动之字符设备驱动笔记整理,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • 简单的字符设备的驱动程序,并对所编写的设备驱动程序进行测试,了解Linux操作系统如何管理字符设备。由于网上许多资源不完整,本资源整合了许多内容。包括驱动程序memdev.c,memdev.h,app-mem.c,MakeFile文件。...
  • linux,字符设备驱动,led驱动,gpio,有驱动源码和测试程序
  • 嵌入式Linux字符设备驱动的设计与应用、电子技术,开发板制作交流
  • Linux设备驱动程序学习(1)-字符设备驱动程序.pdf
  • 基于嵌入式Linux的字符设备驱动程序的设计、电子技术,开发板制作交流
  • * 高级字符设备驱动 ******************/ (1)ioctl 除了读取和写入设备外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。比如弹出介质,改变波特率等等。这些操作通过ioctl方法...
  • 字符设备驱动编写流程,对设备初始化和释放; 2、把数据从内核传送到硬件和从硬件读取数据; 3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据; 4、检测和处理设备出现的错误
  • 虚拟字符设备驱动程序在Linux的实现.pdf
  • 基于ARM的嵌入式Linux字符设备驱动设计研究.pdf
  • 字符驱动原理,适合刚入门的小白!
  • linux字符设备驱动程序,示例代码。 共8个文件。包括内核态的驱动程序和用户态的测试例程。
  • linux 字符设备驱动 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘,打印机等。请注意,以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。  在...
  • linux 字符设备驱动-led

    2017-07-25 15:07:34
    博客http://blog.csdn.net/qq_30951423/article/details/76071333中的字符设备驱动,调试通过的。
  • 这是字符设备驱动的经典程序,globalmem可以实现对设备的读写操作,很有意思,希望大神们多多指教。
  • 字符设备驱动程序 操作系统OS的课程设计 附带完整课程设计报告
  • 本例子是一个linux字符设备驱动的最简单的例子,有详细的说明,适合初次接触者。
  • Linux增加字符设备驱动实验,可以打印出一个hello world

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 178,769
精华内容 71,507
关键字:

字符设备驱动

友情链接: filesysx.zip