5.2.13.驱动中如何操控硬件 5.2.14.静态映射操作LED1 5.2.15.静态映射操作LED2 内核映射表的使用,裸机操作真实物理地址, 驱动是 操作 虚拟地址

5.2.13.驱动中如何操控硬件
5.2.13.1、还是那个硬件
(1)硬件物理原理不变
(2)硬件操作接口(寄存器)不变
(3)硬件操作代码不变
5.2.13.2、哪里不同了?
(1)寄存器地址不同。原来是直接用物理地址,现在需要用该物理地址在内核虚拟地址空间相对应虚拟地址寄存器的物理地址是CPU设计时决定的,从datasheet中查找到的
(2)编程方法不同。裸机中习惯直接用函数指针操作寄存器地址,而kernel中习惯用封装好的io读写函数来操作寄存器,以实现最大程度可移植性。
5.2.13.3、内核的虚拟地址映射方法
(1)为什么需要虚拟地址映射

内核系统启动起来之后 就会打开 mmu (内存地址映射)
(2)内核中有2套虚拟地址映射方法:动态和静态
(3)静态映射方法的特点:
    内核移植时以代码的形式硬编码,如果要更改必须改源代码后重新编译内核
    在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效
    对于移植好的内核,你用不用他都在那里
(4)动态映射方法的特点:
    驱动程序根据需要随时动态的建立映射、使用、销毁映射
    映射是短期临时的
5.2.13.4、如何选择虚拟地址映射方法
(1)2种映射并不排他,可以同时使用

  动态映射可以映射多次
(2)静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存
(3)静态映射的好处是执行效率高,坏处是始终占用虚拟地址空间;动态映射的好处是按需使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)
 

5.2.14.静态映射操作LED1
5.2.14.1、关于静态映射要说的
(1)不同版本内核中静态映射表位置、文件名可能不同
(2)不同SoC的静态映射表位置、文件名可能不同
(3)所谓映射表其实就是头文件中的宏定义
5.2.14.2、三星版本内核中的静态映射表
(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_BASE    (0xFD000000)        // 三星移植时确定的静态映射表的基地址,表中的所有虚拟地址都是以这个地址+偏移量来指定的
(3)GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
表中是GPIO的各个端口的基地址的定义
(4)GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h
 

物理地址和虚拟地址 基地址不一样,但是偏移量是一样的!!!!

1.主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h


#define S5P_VA_GPIO		    S3C_ADDR(0x00500000)  /* 所有GPIO 基地址  虚拟地址 FD500000  */

            S3C_ADDR : #define S3C_ADDR(x)	((void __iomem __force *)S3C_ADDR_BASE + (x))
            S3C_ADDR_BASE:  #define S3C_ADDR_BASE	(0xFD000000) 

所以  S5P_VA_GPIO	就等于  虚拟地址 FD500000 

3)GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
表中是GPIO的各个端口的基地址的定义

#define S5PV210_GPA0_BASE		(S5P_VA_GPIO + 0x000)  //因为S5P_VA_GPIO等于FD500000 

所以  S5PV210_GPA0_BASE 	就等于  虚拟地址 FD500000 

#define S5PV210_GPJ0_BASE		(S5P_VA_GPIO + 0x240) //FD500240
#define S5PV210_GPJ1_BASE		(S5P_VA_GPIO + 0x260)//FD500260
#define S5PV210_GPJ2_BASE		(S5P_VA_GPIO + 0x280)//FD500280

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

#define S5PV210_GPJ0CON			(S5PV210_GPJ0_BASE + 0x00)//FD500240
#define S5PV210_GPJ0DAT			(S5PV210_GPJ0_BASE + 0x04)//FD500244

5.2.13.驱动中如何操控硬件 5.2.14.静态映射操作LED1 5.2.15.静态映射操作LED2 内核映射表的使用,裸机操作真实物理地址, 驱动是 操作 虚拟地址_第1张图片

分析方法:我们自己怎么找 映射表呢???

1.一般都在头文件当中

2.在 /arch/arm/这个目录下 

     /arch/arm/plat -s5p/

plat是平台!

arch/arm/plat-samsung/include/plat/map

一般都叫 map 什么什么

#define S5P_VA_GPIO            S3C_ADDR(0x00500000)    :  VA 一般 表示 虚拟地址, 

#define S5PV210_PA_GPIO        (0xE0200000)  :    PA 一般表示物理地址(动态映射会用到)

5.2.15.静态映射操作LED2
5.2.15.1、参考裸机中的操作方法添加LED操作代码
(1)宏定义
(2)在init和exit函数中分别点亮和熄灭LED
5.2.15.2、实践测试
(1)insmod和rmmod时观察LED亮灭变化
(2)打印出寄存器的值和静态映射表中的分析相对比

这里只是更改了驱动文件 module_test.c,app.c和Makefile无更改

#include 		// module_init  module_exit
#include 			// __init   __exit
#include 
#include       //copy_from_user
#include   // 错误码
#include //arch/arm/mach-s5pv210/include/mach/regs-gpio.h  这两个顺序不能放错,c语言基础
#include  //arch/arm/mach-s5pv210/include/mach/gpio-bank.h



#define MYMAJOR 200  /* 定义 register_chrdev 注册设备的 主设备号 */

#define MYNAME  "test_char" /* 定义 register_chrdev 注册设备的 设备名字 */



#define GPJ0CON	  S5PV210_GPJ0CON	   // FD500240 虚拟地址	
#define GPJ0DAT	  S5PV210_GPJ0DAT	   // FD500244


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

int mymajor; /* 定义 register_chrdev 注册设备号*/

char kbuf[100];/* 内核空间的 buf*/



/* NOTE  自己定义函数指针  test_chrdev_open  */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
	printk(KERN_INFO "test_chrdev_open module_test.c->test_chrdev_open \n");  
	
	return 0;

} /* test_chrdev_open() */




/* NOTE  自己定义函数指针 test_chrdev_release ,   release对应的就是 close  */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_release \n");

	return 0;
}


static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_read \n");
	
	/* 从内核 的 kbuf, 复制到用户的 ubuf  */
	ret = copy_to_user(ubuf,kbuf,size);    /* 成功后 就会拷贝到用户 ubuf */
	if(ret)  /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
	{
		printk(KERN_ERR "copy_to_user  fail \n"); 
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user  OK!!! module_test.c->test_chrdev_write\n");
	
	
	
	
	return 0;
}

// 写函数的本质:将应用层 传递过来的数据先 复制到 内核中,然后将之正确的方式写入硬件完成的操作!(数据从应用层到驱动层的复制,)
// 内核有一个 虚拟地址空间,应用层有一个 虚拟地址空间
static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf,
			size_t count, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_write\n");
	
	
	//使用改函数将: 应用层传过来的 ubuf 中的内容 拷贝到驱动空间中的 一个 kbuf 中
	/* 不能用memcpy(kbuf,buf); 因为 2 个 不在一个地址空间中,不能 比较 霍元甲和成龙 谁更厉害 ,不在一个年龄段*/
	ret = copy_from_user(kbuf,user_buf,count);    /*  成功后 就会 放到 kbuf 中 */
	if(ret)  /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
	{
		printk(KERN_ERR "copy_from_user fail \n"); 
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user OK!!! module_test.c->test_chrdev_write\n");
	
	
	/* 真正的 驱动的 数据从 应用层 复制 到 驱动中后,我们就要根据这个数据去写硬件的操作,所以下面就应该操作硬件 */
	
	return 0;	
}


//自定义  file_operations 结构体 及其元素填充
/* NOTE  定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,                    /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
	
	.open		= test_chrdev_open,  /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open  函数指针*/
	.release	= test_chrdev_release,         /* release对应的就是 close    函数指针 */
	.write		= test_chrdev_write, 
	.read		= test_chrdev_read, 
};









// 模块安装函数
static int __init chrdev_init(void)
{	
	//int ret = -1;  /* 定义 register_chrdev 的返回值  */
	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	
	// 在 module_init 宏 调用函数中去注册字符串 设备驱动
	mymajor = register_chrdev(0, "test_char", &test_fops);   /* major设成0,内核帮我们自动分配空白的设备号,分配的值会 做返回值 ,负数还是返回失败  */
	if(mymajor < 0)
	{
		printk(KERN_ERR "registe_chrdev fail \n");
		return -EINVAL;  /* 返回一个错误码 需要加 ’-‘负号*/
	}
	printk(KERN_INFO "自动分配 register_chrdev success....mymajor = %d \n",mymajor);
	
	/* 现在把 硬件操作放到这里,不用操作app.c 就可以操作硬件 */
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5));  //LED亮
	
	printk(KERN_INFO "S5PV210_GPJ0CON = %p \n",S5PV210_GPJ0CON);
	printk(KERN_INFO "S5PV210_GPJ0DAT = %p \n",S5PV210_GPJ0DAT );
	return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	// 在 module_exit宏 调用函数中去注销 字符串 设备驱动
	unregister_chrdev(mymajor, "test_char");  /* 这里不判断返回值 了,一般不会出错 */
	
	// 模块卸载 LED灭
	rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息




/***********************************************************
如果 KERN_DEBUG 打印不出来,更改打印级别 或者  	
printk(KERN_DEBUG "chrdev_init helloworld init\n"); 

[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
7       4       1       7
[root@liang_x210 driver_test]# echo 8 > /proc/sys/kernel/printk
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
8       4       1       7
************************************************************/


运行结果:

5.2.13.驱动中如何操控硬件 5.2.14.静态映射操作LED1 5.2.15.静态映射操作LED2 内核映射表的使用,裸机操作真实物理地址, 驱动是 操作 虚拟地址_第2张图片

5.2.13.驱动中如何操控硬件 5.2.14.静态映射操作LED1 5.2.15.静态映射操作LED2 内核映射表的使用,裸机操作真实物理地址, 驱动是 操作 虚拟地址_第3张图片

 

5.2.15.3、将代码移动到open和close函数中去

 更改了驱动文件 module_test.c

5.2.13.驱动中如何操控硬件 5.2.14.静态映射操作LED1 5.2.15.静态映射操作LED2 内核映射表的使用,裸机操作真实物理地址, 驱动是 操作 虚拟地址_第4张图片

 然后在 app.c 中, close之前 加入了一个 sleep(4);延时 4s!!!

你可能感兴趣的:(朱老师,5linux驱动开发,驱动开发)