博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
4. led驱动控制:驱动中操作寄存器完成led控制
阅读量:3939 次
发布时间:2019-05-23

本文共 7407 字,大约阅读时间需要 24 分钟。

知识点:

地址映射

  1. 裸机的LED灯实验,就是直接操作I.mx6ull的寄存器,完成点灯
  2. Linux驱动中,也可以这样完成;
  3. 但是并不相同;
  4. linux操作系统中,操作系统内核并不直接访问物理内存地址,访问的地址都是虚拟地址
  5. 可以借助地址映射完成(对应的在板子上完成这个功能的是cpu中的物理硬件是MMU)

在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。

但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。
(这是通过ioremap函数完成的,下面会具体讲)

MMU(其实就是在操作系统中的内存管理所使用的)

  1. 用来完成虚拟空间到物理空间的映射
  2. 内存保护,设置存储器的访问权限,设置虚拟存储的缓冲特性(页表等等)

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址

比 如 I.MX6ULL 的GPIO1_IO03引脚的复用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068
如果没有开启 MMU 的话,直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。在开启了 MMU后,并且设置了内存映射,因此不能直接向 0X020E0068 这个地址写入数据(原因就是上面说过的,操作系统并不直接使用物理地址而是使用虚拟地址)。
我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap

ioremap 与 iounmap

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间

定 义 在arch/arm/include/asm/io.h 文件中,定义如下:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE)void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,unsigned int mtype){
return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));}

ioremap 是宏定义,有两个参数: cookie 和 size,其调用的是函数__arm_ioremap

函数__arm_ioremap有三个参数和一个返回值,含义如下:

phys_addr:要映射给的物理起始地址。size:要映射的内存空间大小(字节数)。mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。

使用举例:

假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址,使用如下代码即可:

#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)		// 查手册得到的物理地址static void __iomem* SW_MUX_GPIO1_IO03;			// 定义的虚拟映射的地址变量SW_MUX_GPIO1_IO03 = ioremap(GPIO1_GDIR_BASE, 4);	// 使用ioremap进行映射

对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作,即相当于对物理地址值(寄存器)进行设置;

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,函数原型如下:

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,代码如下:(与上面对应)

iounmap(SW_MUX_GPIO1_IO03);

实验

实验目标:利用imx6ull平台,使用在驱动中操作寄存器的方法完成 对led的控制。

1. 完成led驱动端编写:包括 :

①:驱动入口出口函数编写及注册
②:在初始化函数中完成对虚拟内存的映射和初始化操作
③:在出口函数中完成对led的关闭、虚拟内存的释放
④:编写file_operations结构体变量,并编写对应成员函数
⑤:成员函数中必须包括对led的操作(write函数中)
2. APP端编写:包括:
①:通过0、1操作led灯的亮灭
②:完善错误提示信息

代码:

驱动端伪代码:

这是整体框架的结构,本实验中,需要进一步完善对于write函数的处理;

在这里插入图片描述
另外:一般在模块的入口函数里led_init()函数里面进行硬件的IO初始化(放到open里面也可以)
这样在模块加载的时候就初始化完成了以下设置 : 时钟、电气属性、GPIO输出输出和默认电平的初始化
在这之前别忘了刚刚讲过的
需要地址映射
这样才能设置我们期望初始化的内容
在这里插入图片描述
根据上面的框架,不难完成驱动编写了叭~
如果不完成write函数的具体实现,那么使用APP去操作led的目的可能无法实现,只能通过加载驱动和卸载驱动完成,这样不行;
因此需要自行对write函数进行实现哦,其实很简单的,就是根据传入的buf参数,对led的寄存器进行控制,进而开关;代码见下面

驱动端代码:

#include 
#include
#include
#include
#include
#include
#include
#include
#define MAJOR_NUM 201#define MAJOR_NAME "ledDev"#define CCM_CCGR0_BASE (0x020c4068) /* 时钟寄存器物理地址*/static void __iomem * IMX6ULL_CCM_CCGR0; /* 时钟寄存器虚拟地址*/#define SW_MUX_GPIO1_IO03_BASE (0x020e0068) /* IO复用寄存器物理地址*/static void __iomem * SW_MUX_GPIO1_IO03; /* IO复用寄存器虚拟地址*/#define SW_PAD_GPIO1_IO03_BASE (0x020e02f4) /* IO电气属性寄存器物理地址*/static void __iomem * SW_PAD_GPIO1_IO03; /* IO电气属性寄存器虚拟地址*/#define GPIO1_DR_BASE (0x0209c000) /* (初始化)默认电平寄存器物理地址*/static void __iomem * GPIO1_DR; /* (初始化)默认电平寄存器虚拟地址*/#define CGPIO1_GDIR_BASE (0x0209c004) /* (初始化)输出输出设置寄存器物理地址*/static void __iomem * CGPIO1_GDIR; /* (初始化)输出输出设置寄存器虚拟地址*/static int ret = 0;static char writebuf[1];/** 字符设备操作函数:ledDev_read 读 * 用于读取当前led相关寄存器的数据 * 用户空间使用系统调用函数read操作 本设备的文件描述符时,调用本函数 */static ssize_t ledDev_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos){ printk("Now In Kernel: ledDev_reading.......\n"); printk("Now In Kernel: ledDev_read.......ok!!\n"); return 0;}/** 字符设备操作函数:ledDev_write 写 * 用于向当前led相关寄存器写入数据 * 用户空间使用系统调用函数write操作 当前设备文件描述符时,调用本函数 */static ssize_t ledDev_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos){ printk("ledDev_writing.......\n"); ret = copy_from_user(writebuf, buf, count); if(0 > ret){ printk("copy_from_user failed!\n"); return -2; } if(writebuf[0] == 0){ ret = readl(GPIO1_DR); // close led ret |= (1 << 3); writel(ret, GPIO1_DR); }else if(writebuf[0] == 1){ ret = readl(GPIO1_DR); // open led ret &= ~(1 << 3); writel(ret, GPIO1_DR); }else{ printk("invalid argument!\n"); return -1; } printk("Now In Kernel: ledDev_write.......OK!!!\n"); return 0;}/** 字符设备操作函数:ledDev_open 开 * 用户空间使用系统调用 open函数操作/dev/led时,调用本函数 */static int ledDev_open(struct inode *inode, struct file *filp){ printk("Now In Kernel: ledDev_open.......\n"); return 0;}/** 字符设备操作函数:ledDev_release 关 * 用户空间使用系统调用 close函数操作 本设备文件描述符时,调用本函数 */static int ledDev_release(struct inode *inode, struct file *filp){ printk("Now In Kernel: ledDev_release.......\n"); return 0;}/** 字符设备操作集合 * 用来描述当前字符设备的操作函数,函数可以自行删减 */static const struct file_operations ledDev_fops = { .owner = THIS_MODULE, .read = ledDev_read, .write = ledDev_write, .open = ledDev_open, .release = ledDev_release,};/** 初始化函数 ledDev_init() * 作为module_init()的参数 * 使用register_chrdev()函数完成对字符设备信息的注册 */static int __init ledDev_init(void){ printk("ledDev_init ing.......\n"); /** led配置初始化模块 * 包括:1. 内存映射 2. 时钟初始化 3. 设置IO复用和电气属性 4. 初始化GPIO引脚 */ IMX6ULL_CCM_CCGR0 = ioremap(CCM_CCGR0_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); CGPIO1_GDIR = ioremap(CGPIO1_GDIR_BASE,4); ret = readl(IMX6ULL_CCM_CCGR0); ret &= ~(3 << 26); writel(ret, IMX6ULL_CCM_CCGR0); ret = readl(SW_MUX_GPIO1_IO03); ret = 0x5; writel(ret, SW_MUX_GPIO1_IO03); ret = readl(SW_PAD_GPIO1_IO03); ret = 0x10b0; writel(ret, SW_PAD_GPIO1_IO03); ret = readl(CGPIO1_GDIR); // 设置引脚为输出 ret |= 1 << 3; writel(ret, CGPIO1_GDIR); ret = readl(GPIO1_DR); // 打开led灯 ret |= (1 << 3); writel(ret, GPIO1_DR); ret = register_chrdev(MAJOR_NUM, MAJOR_NAME, &ledDev_fops); if(ret < 0){ printk("ledDev_init failed!,....\n"); return -EIO; } printk("ledDev_init ing.......OK!!\n"); return 0;}/** 卸载函数 ledDev_exit() * 作为module_exit()的参数 * 使用unregister_chrdev()函数完成对字符设备信息的卸载 */static void __exit ledDev_exit(void){ printk("ledDev_exit ing.......\n"); /** led配置注销模块 * 包括:内存映射的释放 */ ret = readl(GPIO1_DR); // 关闭led灯 ret |= (1 << 3); writel(ret, GPIO1_DR); iounmap(IMX6ULL_CCM_CCGR0); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(CGPIO1_GDIR); unregister_chrdev(MAJOR_NUM, MAJOR_NAME); printk("ledDev_exit ing.......OK!!\n");}/** * 入口函数和出口函数 * 用来向内核注册设备 * 在操作系统中使用modprobe 挂载驱动模块时,会默认调用modules_init()中注册的函数 * 在操作系统中使用modprobe -r 卸载驱动模块时,会默认调用modules_exit()中注册的函数 */module_init(ledDev_init);module_exit(ledDev_exit);/** * license和作者 */MODULE_LICENSE("GPL");MODULE_AUTHOR("QJY");

APP端代码:

/** * 本文件对应驱动模块ledDev的测试APP * argv[1] : filename:要进行互动的驱动设备名称(通过/dev/ledDev) * argv[2]  : 0:表示关闭led灯;   1:表示打开led灯 */#include 
#include
#include
#include
#include
#include
#include
#include
static char ledbuf[1];int main(int argc,char* argv[]){ int fd = -1; if(argc != 3){ printf("Error Usage\n"); return -1; } fd = open(argv[1],O_RDWR); if(fd == -1){ printf("open %s failed!\n",argv[1]); return errno; } ledbuf[0] = atoi(argv[2]); int ret = write(fd, ledbuf, sizeof(ledbuf)); printf("%d",ret); if(ret < 0){ printf("write failed!\n"); return errno; } ret = close(fd); if(ret == -1){ printf("close fd failed!\n"); return errno; } return 0;}

转载地址:http://aizwi.baihongyu.com/

你可能感兴趣的文章
Java8 Lambda表达式使用集合(笔记)
查看>>
Java魔法师Unsafe
查看>>
spring cloud java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest
查看>>
Centos系统安装MySQL(整理)
查看>>
postgresql计算两点距离(经纬度地理位置)
查看>>
postgres多边形存储--解决 Points of LinearRing do not form a closed linestring
查看>>
postgresql+postgis空间数据库总结
查看>>
spring 之 Http Cache 和 Etag(转)
查看>>
基于Lucene查询原理分析Elasticsearch的性能(转)
查看>>
依赖多个项目,重复jar包不同版本冲突解决
查看>>
HttpClient请求外部服务器NoHttpResponseException
查看>>
springCloud升级到Finchley.RELEASE,SpringBoot升级到2.0.4
查看>>
Spring boot + Arthas
查看>>
omitted for duplicate jar包冲突排查
查看>>
如何保证缓存与数据库的双写一致性?
查看>>
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy排查
查看>>
深浅拷贝,深浅克隆clone
查看>>
Java基础零散技术(笔记)
查看>>
Mysql优化sql排查EXPLAIN EXTENDED
查看>>
架构学习笔记(笔记)
查看>>