本章开始编写第一个真正的 Linux 字符设备驱动。
Linux 下 LED 灯驱动原理
LED 灯驱动最终也是对 I.MX6ULL 的 IO 口进行配置,并配置相应的硬件寄存器,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux的驱动框架。
地址映射
MMU 全称叫做 MemoryManage Unit,也就是内存管理单元。
MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
物理地址:真实存在的地址,如 512MB 的 DDR3;
虚拟地址:MMU映射的地址,如32 位的处理器,虚拟地址范围是 2^32=4GB。
MMU就是将少的物理地址映射到更多的虚拟地址,之前裸机开发用到的都是物理内存,直接往寄存器写数据;有了MMU之后,就需要向寄存器所在的物理地址映射的虚拟地址写数据。
如下图:
物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap。
1、 ioremap 函数
ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h
1 #define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
2
3 void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
4 {
5 return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
6 }
phys_addr:要映射给的物理起始地址。
size:要映射的内存空间大小。
mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。
返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。
2、 iounmap 函数
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射:
void iounmap (volatile void __iomem *addr)
3、加载/卸载驱动
SW_MUX_GPIO1_IO03 就是一个__iomem类型的指针。
//加载
#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);
//卸载
iounmap(SW_MUX_GPIO1_IO03);
I/O 内存访问函数
I/O 端口:当外部寄存器或内存映射到 IO 空间时;
I/O 内存:当外部寄存器或内存映射到内存空间时。
对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。
Linux 内核不建议直接通过指针访问这些地址,而是推荐使用一组操作函数来对映射后的内存进行读写操作。
1、读操作函数
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
2、写操作函数
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
以上函数不做太多说明,有眼就行。
实验程序编写
程序的编写和上一章虚拟设备驱动的编写类似,不用写注释了,自己能看懂就行了,理解大概意思就行,后边儿还会学习新字符设备驱动。
LED 灯驱动程序编写
led.c
在这里插入代码片
编写测试 APP
ledApp.c
在这里插入代码片
运行测试
编译驱动程序
Makefile
KERNELDIR := /home/zxy/linux/kernel_lib/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
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
编译测试 APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
运行测试
depmod //第一次加载驱动的时候需要运行此命令
modprobe led.ko //加载驱动
(或insmod,两个命令都行,上边儿那个就是能查找依赖关系)
mknod /dev/led c 200 0
./ledApp /dev/led 1 //打开 LED 灯
./ledApp /dev/led 0 //关闭 LED 灯
rmmod led.ko //卸载驱动模块
总结
没啥好说,没啥新知识。
就是原子出厂的设备树中内置了led的设备树,里边儿有心跳灯功能,进入到设备树文件.dts中,将其中的led设备树相关的状态"on"改为"off",然后在内核目录make dtbs即可重新编译设备树,mfgtool烧写即可。