地址映射
MMU(Memory Manage Unit)为内存管理单元,Linux内核2.6以前必须要有MMU,之后支持无MMU的处理器。MMU的功能有:
①完成虚拟空间到物理空间的映射。②内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。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函数
1 2 3 4 5
| #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)); }
|
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 寄存器对应的虚拟地址,使用如下代码即可:
1 2 3
| #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) static void __iomem* SW_MUX_GPIO1_IO03; SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
|
宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址, SW_MUX_GPIO1_IO03 是映射后的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可。
iounmap函数
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:
1
| void iounmap (volatile void __iomem *addr)
|
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。
I/O内存访问
当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。
当外部寄存器或内存映射到内存空间时,称为 I/O 内存。
但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。
读操作函数
1 2 3
| u8 readb(const volatile void __iomem *addr) u16 readw(const volatile void __iomem *addr) u32 readl(const volatile void __iomem *addr)
|
参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
写操作函数
1 2 3
| void writeb(u8 value, volatile void __iomem *addr) void writew(u16 value, volatile void __iomem *addr) void writel(u32 value, volatile void __iomem *addr)
|
参数 value 是要写入的数值, addr 是要写入的地址。
写LED驱动程序
首先在汇编点亮LED的程序里面使能了imx6ull的外设时钟,使能GPIO1_03,配置了IO口的属性,之后进行了初始化,DR寄存器置位点灯。
LED的驱动开发首先要查出所要用到的寄存器的地址。
1 2 3 4 5 6 7
| CCM_CCGR1 20C_406Ch
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 20E_02F4h IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 20E_0068h GPIO1_DR 209_C000h GPIO1_GDIR 20A_0004h
|
GPIO1_DR和GPIO1_GPIR的地址实在GPIO memory map(GPIO的地址映射)中寻找。
DR(data register),即数据寄存器,它是32位的,一个GPIO组最大只有32个IO,因此DR寄存器中的每个位都对应一个 GPIO。
- 当GPIO被配置为输出模式后,向指定的位写入数据那么相应的IO就会输出相应的高低电平,例如,要设置GPIO5_IO03输出低电平,那么就应该设置 GPIO5.DR=0x08。
- 当 GPIO被配置为输入模式后,此寄存器就保存着对应IO的电平值,每个位对对应一个GPIO,例如,当GPIO5_IO03这个引脚接地的话,那么 GPIO5.DR 的bit3就是0。
GDIR(GPIO direction register),即方向寄存器,也是32位的,用来设置某个GPIO的工作方向的,即输入/输出。
同样的,每个IO对应一个位,如果要设置GPIO为输入,就设置相应的位为0,如果要设置为输出,就设置为 1。
程序以及搭建思路
led驱动步骤:
①、书写字符设备驱动框架,添加头文件,定义file_operations,以及相关函数框架。
②、添加地址宏定义创建指针,以及在入口函数中实现虚拟地址的映射,注册设备。
③、在出接口地址中取消虚拟地址的映射,注销设备。
④、在open函数中实现GPIO的使能,以及IO引脚的配置。
⑤、在write函数中从应用层获取数据,来根据数据来进行GPIO的输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
| #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/fs.h>
#define LED_MAJOR 200 #define LED_NAME "led"
#define CCM_CCGR1_BASE (0X020C406C) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)
#define on 1 #define off 0 static void __iomem *CCM_CCGR1; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;
void led_switch(u8 sta) { u32 num; if(sta == on) { num =readl(GPIO1_DR); num &= ~(1 << 3); writel( num, GPIO1_DR); printk("on\n"); } else if(sta == off) { num = readl(GPIO1_DR); num |= (1 << 3); writel( num, GPIO1_DR); printk("off\n"); } }
static int led_dev_open(struct inode *inode, struct file *filp) { return 0; }
static int led_dev_release(struct inode *inode, struct file *filp) { return 0; } static ssize_t led_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } static ssize_t led_dev_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt) { int val = 0; unsigned char databuf[1]; unsigned char state ;
val = copy_from_user(databuf, buf, cnt); if(val < 0) { printk("kernl write failed \r\n"); return -EFAULT; } state = databuf[0]; if(state == on) led_switch(on); else if(state == off) led_switch(off);
return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_dev_open, .read = led_dev_read, .write = led_dev_write, .release = led_dev_release, }; static int __init led_init(void) { int value = 0; u32 val = 0; CCM_CCGR1 = ioremap(CCM_CCGR1_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); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); val = readl(CCM_CCGR1); val &= ~(3 << 26); val |= (3 << 26); writel(val, CCM_CCGR1); writel(5,SW_MUX_GPIO1_IO03); writel(0x10B0, SW_PAD_GPIO1_IO03); val = readl(GPIO1_GDIR); val &= ~(1 << 3); val |= (1 << 3); writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val |= (1 << 3); writel( val , GPIO1_DR); value = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); if(value < 0) { printk("led register failed\r\n"); return -EIO; } return 0; } static void __exit led_exit(void) { iounmap(CCM_CCGR1); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); iounmap(SW_PAD_GPIO1_IO03); iounmap(SW_MUX_GPIO1_IO03); unregister_chrdev(LED_MAJOR, LED_NAME); printk("led exit\r\n"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
|
应用层程序
搭建思路:
①、获取命令行的数据
②、打开设备
③、写入指令
④、关闭设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include "stdio.h" #include "stdlib.h" #include "unistd.h" #include "fcntl.h" #include "string.h" #include "sys/stat.h" #include "sys/types.h"
int main(int argc , char *argv[]) { int value; FILE *fp == NULL; char *filename unsigned char databuf[1]; filename = argv[1]; if((fp = fopen(filename, "w+"))==NULL) { perror("fopen error:"); exit(-1); } printf("文件打开成功!\n"); databuf[0] = atoi(argv[2]); if(sizeof(buf) > fwrite(databuf, 1, sizeof(databuf), fp)) { printf("fwrite error\n"); fclose(fp); exit(-1); } printf("数据写入成功!\n"); fclose(fp); exit(0); }
|
Makefile文件编写,跟之前一样。
1 2 3 4 5 6 7 8 9 10 11 12
| KERNELDIR := /home/moss/linux/imx-kernel/linux-fslc-4.9-2.0.x-imx CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
|