本文共 7407 字,大约阅读时间需要 24 分钟。
在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。 但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。
(这是通过ioremap函数完成的,下面会具体讲)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 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 ,
定 义 在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
,
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");
/** * 本文件对应驱动模块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/