字符设备驱动基础(II)

系统整体的工作原理:
  1. 应用层->API->设备驱动->硬件
    1. 发相应得指令可以指令可以执行不一样得操作,这个规则靠设备驱动来指定。
    2. 这些规则通过api显示出来,给应用层调用
    3. 应用层传相应参数进来,设备驱动通过参数控制硬件执行相应操作
  2. API: open, read, write, close 系统调用
    1. open一个设备,read从设备中读数据,write往设备中写数据
  3. 驱动源码中要提供真正的open, read, write, close等函数实体

注意:如果我们不使用unregister_chrdev函数将模块注销掉,当我们卸载模块时,使用cat /proc/devices还会查看到这个模块存在

file_operation结构体
  1. 元素主要是函数指针,用来挂接实体函数地址
  2. 每个设备驱动都需要一个结构体类型的变量,定义一个opration来维护read, write, close
  3. 设备驱动向内核注册时提供该结构体类型的变量, 内核需要知道你有这个驱动需要注册
自动分配主设备号

内核知道自己哪些主设备号能用,主动分给我们使用,这样的话,不用我们cat查看,然后再自己分配了

register_chrdev详解:向内核注册驱动 ,需要包含#include<linux/fs.h>

  1. 作用 : 驱动向内核注册file_operation结构体,函数指针和函数实体挂钩
  2. 参数 :
    1. major : 主设备号。这个设备号类似给每类设备一个身份证号,具体区分需要次设备号,当前设备绑定的编号,可以自己指定,也可以让内核给分配。 参数传0进去表示让内核帮我们自动分配一个合适的空白的没被使用的主设备号,内核如果成功返回,就会返回分配的主设备号
    2. name , 设备驱动的名字
    3. fops : 结构体结构体指针,指向我们填充的file_operation, 把我们结构体变量传给函数,在函数内部完成注册
  3. inline和static
    1. static 为了防止和其他文件的函数名冲突
    2. inline可以直接展开,不用担心多个文件调用的时候出现重复定义,而且函数很短,可以节省开销
内核如何管理字符设备驱动:
  1. 内核中有一个数组用来存储注册的字符设备驱动, 数组的下标和主设备号有关系
  2. register_chrdev内部将我们要注册的驱动的信息(主要是)存储在数组中相应的位置
  3. cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)
驱动设备文件的创建:

(1) 何为设备文件 : 用来索引这个文件的

(2) 设备文件的关键信息是: 主次设备号,我们上层api打开设备文件就要对应相应的file_operation结构体,这个就对应相应的数组下标也就是主设备号

使用mknod创建设备文件: mknod /dev/xxx c 主设备号 次设备号

应用和驱动的数据交换api
  1. copy_from_user 用来将数据从用户空间复制到内核空间
  2. copry_to_user 用来将数据从内核空间复制到用户空间

上面两个接口和常规的复制函数有点不同,返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制剩下的字节数

如何操作硬件:
硬件还是那个硬件
  1. 硬件物理原理不变,和裸机调试一样的,同样是拉高拉低电平点亮led
  2. 硬件操作接口(寄存器)不变,控制寄存器和数据寄存器
  3. 硬件操作代码不变, 用指针指向寄存器地址,然后将数据写进去

不同点:

  1. 寄存器地址,裸机部分使用真实的物理地址,现在需要使用虚拟机地址,用的是该物理地址在内核虚拟地址空间相对应的虚拟地址, 寄存器的地址是CPU设计时决定的,从datasheet中查到的
  2. 编程方法不同。裸机中习惯用函数直接操作寄存器,再kernel中习惯使用io封装好的寄存器来操作寄存器,这样的话具备一定的移植性
内核虚拟映射方法:
  1. 为什么需要虚拟地址映射

    1. MMU只要一开就是用虚拟地址,关了都是物理地址,并不做真正的区分
  2. 内核中有两套虚拟地址映射方法:动态和静态

  3. 静态映射方法的特点:

    1. 内核启动时以代码的形式硬编码(使用代码写死),如果要更改必须改源代码之后重新编译内核,在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效,对于移植好的内核,你用不用它都在那里,在整个内核运行过程中都是有效的
  4. 动态映射的特点:

    1. 事先没有做任何建立,驱动程序根据需要随时动态的建立映射,使用,销毁映射,映射是短期临时的,
    2. 在程序运行过程中去申请动态映射,使用,不用的时候就销毁
    如何选择虚拟地址映射方法:
    1. 2种映射并不排他,可以同时使用
    2. 静态映射类似C语言中的全局变量, 动态映射类似于malloc申请内存
    3. 静态映射的好处是执行效率高,就是静态映射始终存在,不需要使用时候申请动态的开销,坏处是始终占用虚拟地址空间,动态映射的好处是按需要使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)
静态映射表
  1. 不同版本的内核中静态映射表的位置,文件名可能不同

  2. 不同的soc的静态映射表位置,文件名可能不同

  3. 所谓映射表其实就是头文件的宏定义

    三星版本内核中的静态映射表:
    1.主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h

    ​ CPU在安排寄存器地址时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部有很多个寄存器的地址是连续的。所以内核在定义寄存器地址时都是先找到基地址,然后再用及地址+偏移量来寻找具体的一个寄存器

    ​ map-s5p.h中定义的就是要用到的几个模块的寄存器基地址, 如果以后我们有需要,可以自己再往上面添加自己要用到的基地址

    ​ map-s5p.h中定义的是模块的寄存器基地址的虚拟地址

    2.虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h

    ​ #define S3C_ADDR_BAS (0xFD000000) //三星移植时确定的静态映射表的虚拟地址基地址,表中所有虚拟地址都是以这个地址+偏移量来指定的

    3.GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h

    ​ 定义了所有相关的gpio寄存器

    ​ 表中是GPIO的各个端口的基地址的定义

    4.GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

静态映射操作LED

参考裸机中的操作方法添加led的操作代码

再init/exit函数中分别点亮和熄灭LED

我们最终要在read和write中去实现点亮灯和关闭灯的操作

(1) 先定义好应用和驱动之间的控制接口,这个是由自己来定义的,譬如应用写"on", 灯亮,"off"灯灭

(2) 我们需要尽量做得简单,让应用层的判断通过一个字符来决定亮灭

module_test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>

int major;
char kbuf[100];

#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT

#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)

static int test_open(struct inode *inode, struct file *file)
{
	printk("test_open-----");
	return 0;
}

static int test_release(struct inode *inode, struct file *file)
{
	printk("test_release----------");
	return 0;
}


static ssize_t test_read( struct file *file, char *buf, size_t count, loff_t *ppos )
{
	int ret = -1;
	printk("test_read----------");
	//使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间的一个buf中
	ret = copy_to_user(buf, kbuf, count);
	if(ret)
	{
		printk("copy to user failed\n");
		return -EINVAL;
	}
	
	
	return 0;
}

//写函数的本质就是将应用层传递过来的数据先复制到内核中,然后以正确的方式写入硬件,完成操作
static ssize_t test_write( struct file *file, const char *buf, size_t count,
						  loff_t *ppos )
{
	int ret = -1;
	rGPJ0CON = 0x11111111;
	
	printk("test_write----------");
	
	memset(kbuf, 0, sizeof(kbuf));
	//使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间的一个buf中
	ret = copy_from_user(kbuf, buf, count);
	if(ret)
	{
		printk("copy from user failed\n");
		return -EINVAL;
	}

	if(kbuf[0] == '1')
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if(kbuf[0] == '0')
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	printk("copy from user successful\n");
	
	//从应用层过来的数据可以用来真正操控硬件
	return 0;
}

//定义一个file_operation结构体
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,  

	.open		= test_open,     //将来应用open打开这个设备实际调用
	.release	= test_release,  //就是这个close对应的函数
	.read       = test_read,
	.write      = test_write,
};

static int __init chrdev_init(void)
{
	//在这里注册file_operation结构体,在module_init宏调用的函数中去注册字符设备驱动
	printk("chrdev_init\n");
	if ((major = register_chrdev(0, "test", &test_fops)) < 0) {
		printk("adb: unable to get major %d\n", major);
		return -1;
	}
	printk("register_chrdev successful\n");


	
	return 0;
}

static void __exit chrdev_exit(void)
{
	unregister_chrdev(major, "test");
	//在module_exit宏调用的函数中去注销字符设备驱动
	printk("chrdev_exit\n");
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("GPL");

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FILE "/dev/test"   //mknod创建的设备文件名
char buf[100];

int main(void)
{
	int fd = -1;
	int i;
	fd = open(FILE, O_RDWR);
	if(fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	
	printf("open %s success...\n", FILE);

	while(1)
	{
		memset(buf, 0, sizeof(buf));
		printf("please enter on|off\n");
		scanf("%s", buf);
		if(!strcmp(buf, "on"))
			write(fd, "1", 1);
		else if(!strcmp(buf, "off"))
			write(fd, "0", 1);
		else if(!strcmp(buf, "flash"))
		{
			for(i = 0; i< 3; i++)
			{
				write(fd, "1", 1);
				sleep(1);
				write(fd, "0", 1);
				sleep(1);
			}
		}
		else if(!strcmp(buf, "quit"))
			break;
	}
	
	//关闭文件
	close(fd);
	return 0;
}

动态映射操作led:
  1. 如何建立动态映射

    1. request_mem_region 向内核申请,报告需要映射的内存资源
    2. ioremap, 真正用来实现映射,传给他一个物理地址,返回映射之后的虚拟地址
    3. 注意映射建立时,要先申请再映射,然后使用,使用完之后要先解除映射再释放
  2. 如何销毁动态映射

    1. iounmap
    2. release_mem_region

    module_test.c

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-bank.h>
    #include <linux/string.h>
    #include <linux/io.h>
    #include <linux/ioport.h>
    
    
    int major;
    char kbuf[100];
    
    #define GPJ0CON S5PV210_GPJ0CON
    #define GPJ0DAT S5PV210_GPJ0DAT
    
    #define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
    #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
    
    #define GPJ0CON_PA 0xe0200240
    #define GPJ0DAT_PA 0xe0200244
    
    unsigned int *pGPJ0CON;
    unsigned int *pGPJ0DAT;
    
    static int test_open(struct inode *inode, struct file *file)
    {
    	printk("test_open-----");
    	return 0;
    }
    
    static int test_release(struct inode *inode, struct file *file)
    {
    	printk("test_release----------");
    	return 0;
    }
    
    
    static ssize_t test_read( struct file *file, char *buf, size_t count, loff_t *ppos )
    {
    	int ret = -1;
    	printk("test_read----------");
    	//使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间的一个buf中
    	ret = copy_to_user(buf, kbuf, count);
    	if(ret)
    	{
    		printk("copy to user failed\n");
    		return -EINVAL;
    	}
    	
    	
    	return 0;
    }
    
    //写函数的本质就是将应用层传递过来的数据先复制到内核中,然后以正确的方式写入硬件,完成操作
    static ssize_t test_write( struct file *file, const char *buf, size_t count,
    						  loff_t *ppos )
    {
    	int ret = -1;
    	rGPJ0CON = 0x11111111;
    	
    	printk("test_write----------");
    	
    	memset(kbuf, 0, sizeof(kbuf));
    	//使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间的一个buf中
    	ret = copy_from_user(kbuf, buf, count);
    	if(ret)
    	{
    		printk("copy from user failed\n");
    		return -EINVAL;
    	}
    
    	if(kbuf[0] == '1')
    	{
    		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    	}
    	else if(kbuf[0] == '0')
    	{
    		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    	}
    
    
    	printk("copy from user successful\n");
    	
    	//从应用层过来的数据可以用来真正操控硬件
    	return 0;
    }
    
    //定义一个file_operation结构体
    static const struct file_operations test_fops = {
    	.owner		= THIS_MODULE,  
    
    	.open		= test_open,     //将来应用open打开这个设备实际调用
    	.release	= test_release,  //就是这个close对应的函数
    	.read       = test_read,
    	.write      = test_write,
    };
    
    static int __init chrdev_init(void)
    {
    	//在这里注册file_operation结构体,在module_init宏调用的函数中去注册字符设备驱动
    	printk("chrdev_init\n");
    	if ((major = register_chrdev(0, "test", &test_fops)) < 0) {
    		printk("adb: unable to get major %d\n", major);
    		return -1;
    	}
    	printk("register_chrdev successful\n");
    
    	//使用动态映射的方法来操控寄存器
    	if(!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
    		return -EINVAL;
    	
    	if(!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
    		return -EINVAL;
    	
    	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
    	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
    	
    	*pGPJ0CON  = 0x11111111;
    	*pGPJ0DAT  = ((0<<3) | (0<<4) | (0<<5));  //亮
    	return 0;
    }
    
    static void __exit chrdev_exit(void)
    {
    	unregister_chrdev(major, "test");
    	//解除映射
    	iounmap(pGPJ0CON);
    	iounmap(pGPJ0DAT);
    	
    	release_mem_region(GPJ0CON_PA, 4);
    	release_mem_region(GPJ0DAT_PA, 4);
    	//在module_exit宏调用的函数中去注销字符设备驱动
    	printk("chrdev_exit\n");
    }
    
    module_init(chrdev_init);
    module_exit(chrdev_exit);
    
    MODULE_LICENSE("GPL");
    
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值