linux物理地址到虚拟地址映射MMU

9、linux物理地址到虚拟地址映射MMU

在单片机和裸机中操作硬件直接访问物理寄存器即可

例如 unsigned int *p = 0x12345678;

*p = 0x87654321;

但是在Linux上不行,在Linux上,如果要想操作硬件,需要先把物理地址转换为虚拟地址,因为Linux使能了MMU(物理地址转换为虚拟地址),所以我们在Linux上不能直接操作物理地址。

MMU的好处?

​ (1) 让虚拟地址成了可能

​ (2) 可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,这样就保证了系统的安全。

MMU非常复杂**,我们如何完成物理地址到虚拟地址的转换?**

内核给我们提供了函数

ioremap,iounmap

ioremap

ioremap : 把物理地址转换成虚拟地址

static inline void __iomem *ioremap(unsigned long port, unsigned long size)
{
	return IO_CONCAT(__IO_PREFIX,ioremap) (port, size);
}

port 映射物理地址的起始地址。

size : 映射多大的内存空间

返回值: 成功返回虚拟地址的首地址、 失败: 返回NULL

iounmap

iounmap: 释放掉ioremap映射的地址

static inline void iounmap(volatile void __iomem *addr)
{
	IO_CONCAT(__IO_PREFIX,iounmap)(addr);
}

addr 要取消映射的虚拟地址的首地址

注意: 物理地址只能被映射一次,多次映射会失败

如何查看那些物理地址被映射过了?

cat /proc/iomem 

linux物理地址到虚拟地址映射MMU_第1张图片

10、LED驱动

•GPIO (General Purpose InputOutput Port)

– 通用输入输出口
– 单片机或处理器对外设进行操作的主要方式
– 通过设置GPIO的高电平或者低电平来实现
对外设的操作
– GPIO很多是复用的
•程序对寄存器操作即可实现对GPIO的操作

1、查看电路图、datasheet

编写IED驱动,主要是对GPIO的操作

linux物理地址到虚拟地址映射MMU_第2张图片

在核心板连接到了GPL2_0
在这里插入图片描述

在datasheet中找到GPL2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rmJQGS1-1618902548334)(linux驱动.assets/image-20210412130042151.png)]

GPL2_0的控制寄存器,主要是控制输入/输出(电平方向)

linux物理地址到虚拟地址映射MMU_第3张图片

数据寄存器,写入0x1 为高电平、0为低电平 因为这里使用的是GPL2_0 第0位对应着GPL2_0 , 如果是2_1
就是对应着第1位 第1位为1 则为高电平、0位低电平

linux物理地址到虚拟地址映射MMU_第4张图片

2、编写驱动程序

这里使用的是物理地址和虚拟地址映射的方式来操作GPIO

#include 
#include 
#include 	//杂项设备的头文件struct miscdevice
#include 		//文件操作的头文件  struct file_operations 
#include   //这个是copy_to/from_user 的头文件
#include 		// ioremap 函数的头文件

#define GPIO_LED_CON 0x11000100 //GPL2CON
#define GPIO_LED_DAT 0x11000104 //GPL2DAT
unsigned char * LED_CON;
unsigned char * LED_DAT;
ssize_t misc_write (struct file * file, const char __user *user, size_t n, loff_t *loff_t)
{
	char buf[64] = {0};
	printk("misc_write!\n");
	if (copy_from_user(buf, user, n) != 0)
	{
		printk("copy from user is error!\n");
		return -1;
	}
	printk("copy from user is success!\n");
	//应用层写1 为 打开LED         0为关闭LED
	if (buf[0] == 1)
	{
		*LED_DAT = 0x1;
	}
	else if (buf[0] == 0)
	{
		*LED_DAT = 0x0;
	}	
	printk("misc_write %c\n", buf[0]);
	return 0;
}
int misc_open (struct inode *inode, struct file *file)
{
	printk("misc_open!\n");
	return 0;
}
int misc_release (struct inode *inode, struct file *file)
{
	printk("misc_release!\n");
	return 0;
}
//第二步填充file_operations结构体
struct file_operations misc_fops = {
	.owner = THIS_MODULE,			//	当前用户拥有		
	.open = misc_open,
	.write = misc_write,
	.read = misc_read
};
//第一步填充miscdevice结构体
struct miscdevice miscdev = {
	.minor = MISC_DYNAMIC_MINOR,  	//自动获取次设备号
	.name = "my_led",  			//杂项设备名称   在/dev下可以看到my_led设备节点
	.fops = &misc_fops				//文件操作集
};
static int misc_init(void)
{
	int ret;
	//第三步 注册杂项设备并生成设备节点
	ret = misc_register(&miscdev);
	if (0 > ret)
	{	
		printk("misc register is error!\n");
		return -1;
	}
	printk("misc register is success!\n");
	// 物理地址映射到虚拟地址
	LED_DAT = ioremap(GPIO_LED_DAT, 1); //数据寄存器只有一个字节 映射一个字节
	if (NULL == LED_DAT)
	{
		printk("LED_DAT ioremap is error!\n");
		return -EBUSY;
	}
	LED_CON = ioremap(GPIO_LED_CON, 4); //控制寄存器有4个字节
	if (NULL == LED_CON)
	{
		printk("LED_CON ioremap is error!\n");
		return -EBUSY;
	}
	*LED_CON |= 0x1;    //设置为output
	return 0;
}
static void misc_exit(void)
{
	misc_deregister(&miscdev);  //卸载
	//取消映射
	iounmap(LED_CON);
	iounmap(LED_DAT);
	printk("byb byb\n");  // 这里打印函数需要用printk 因为printf是C库函数
}
module_init(misc_init);  		//入口
module_exit(misc_exit);			//出口
MODULE_LICENSE("GPL");			//协议声明
3、应用程序
/*************************************************************************
    > File Name: app.c
    > 作者:YJK 
    > Mail: [email protected] 
    > Created Time: 2021年04月11日 星期日 21时31分17秒
 ************************************************************************/

#include
#include 
#include 
#include 
#include 
#include 
int main(int argc,char *argv[])
{
	int fd;
	char buf[64] ={0};
	char *str = "hello world";
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		perror("open");
		return -1;
	}
	char dat = 0x1;
	ret = write(fd, &dat, 1);
	if (ret == -1)
	{
		perror("write");
		return -1;
	}
	sleep(1);
	dat = 0x0;
	write(fd, &dat, 1);
	sleep(1);
	dat = 0x1;
	write(fd, &dat, 1);
	close(fd);
	return 0;
}

执行make之后生成.ko 模块文件,然后copy到开发板,使用insmod xx.ko 导入模块,然后执行app程序

你可能感兴趣的:(linux驱动,linux)