以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
补充内容:字符设备驱动基础5——驱动如何操控硬件_天糊土的博客-CSDN博客
一、静态映射表的建立过程
关于“静态映射表的建立”这部分内容,有以下三个关键:
(1)什么是映射表?
所谓映射表,也就是一些关于具体的物理地址、虚拟地址的宏定义。
(2)如何建立映射表?
1)在/x210kernel/arch/arm/mach-s5pv210/mach-smdkc110.c文件中有如下内容:
关于MACHINE_START这个宏的描述,见博客kernel移植——从三星官方内核开始移植,简单地理解,就是这个宏定义了一个struct machine_desc结构体类型的变量,这个结构体变量描述了某个开发板的相关信息,其中就包括静态映射表相关的信息。
这个结构体变量的成员.map_io是一个函数指针,指向smdkc110_map_io函数。该函数位于/arch/arm/mach-s5pv210/mach-smdkc110.c文件中,主要作用是使用(1)中的映射表来建立linux内核的页表映射关系。
2)smdkc110_map_io函数调用关系如下:
|…smdkc110_map_io函数(位于/arch/arm/mach-s5pv210/mach-smdkc110.c)
|………s5p_init_io 函数(位于/arch/arm/plat-s5p/cpu.c)
|……………iotable_init 函数(位于/arch/arm/mm/mmu.c)
3)分析变量s5p_iodesc,它是struct map_desc结构体变量。
struct map_desc {unsigned long virtual;//…………虚拟地址unsigned long pfn;//……………………物理地址unsigned long length;//……………映射长度unsigned int type; };
/* minimal IO mapping */static struct map_desc s5p_iodesc[] __initdata = {{.virtual = (unsigned long)S5P_VA_CHIPID,.pfn = __phys_to_pfn(S5P_PA_CHIPID),.length = SZ_4K,.type = MT_DEVICE,}, {.virtual = (unsigned long)S3C_VA_SYS,.pfn = __phys_to_pfn(S5P_PA_SYSCON),.length = SZ_64K,.type = MT_DEVICE,}, {.virtual = (unsigned long)S3C_VA_UART,.pfn = __phys_to_pfn(S3C_PA_UART),.length = SZ_4K,.type = MT_DEVICE,}, {.virtual = (unsigned long)VA_VIC0,.pfn = __phys_to_pfn(S5P_PA_VIC0),.length = SZ_16K,.type = MT_DEVICE,}, {.virtual = (unsigned long)VA_VIC1,.pfn = __phys_to_pfn(S5P_PA_VIC1),.length = SZ_16K,.type = MT_DEVICE,}, {.virtual = (unsigned long)S3C_VA_TIMER,.pfn = __phys_to_pfn(S5P_PA_TIMER),.length = SZ_16K,.type = MT_DEVICE,}, {.virtual = (unsigned long)S5P_VA_GPIO,.pfn = __phys_to_pfn(S5P_PA_GPIO),.length = SZ_4K,.type = MT_DEVICE,}, };
经过分析,真正的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc这个变量中,该变量的本质是一个结构体数组,数组中每一个元素就是一个映射。这个映射描述了一段物理地址到虚拟地址之间的映射。
这个结构体数组所记录的几个映射关系被iotable_init函数所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系。这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
(3)开机时建立映射表的流程
kernel启动时,smdkc110_map_io如何被调用的?
调用关系如下:
|…………第二阶段的start_kernel函数(位于x210_kernel/init/main.c文件中)
|………………setup_arch函数(位于x210_kernel/arch/arm/kernel/setup.c文件)
|……………………paging_init函数(位于x210_kernel/arch/arm/mm/mmu.c文件中)
|…………………………devicemaps_init函数(该函数部分内容如下)
/* 位于1000行前后* Ask the machine support to map in the statically mapped devices.*/ if (mdesc->map_io) //这里的mdesc是struct machine_desc结构体变量mdesc->map_io();
二、动态映射结构体方式操作寄存器
下面是之前讲解的独立的多次映射:
// 使用动态映射的方式来操作寄存器/* **request_mem_region参数含义 **第一个参数:起始地址(或者说寄存器的原始地址) **第二个参数:寄存器的长度(一般32bit,因此4字节) **第三个参数:这段空间的名字*/ */ if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))return -EINVAL;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))return -EINVAL;pGPJ0CON = ioremap(GPJ0CON_PA, 4);pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);*pGPJ0CON = 0x11111111;*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
下面是真实驱动中,用结构体封装的方法来进行多个寄存器的地址映射。
typedef struct GPJ0REG {volatile unsigned int gpj0con;volatile unsigned int gpj0dat; }gpj0_reg_t;#define GPJ0CON S5PV210_GPJ0CON #define GPJ0DAT S5PV210_GPJ0DAT #define rGPJ0CON *((volatile unsigned int *)GPJ0CON) #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)#define GPJ0_REGBASE 0xe0200240 //这个怎么来的?数据手册?gpj0_reg_t *pGPJ0REG;/****省略部分代码****/static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏调用的函数中去注册字符设备驱动// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);/* // 使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))return -EINVAL;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))return -EINVAL;pGPJ0CON = ioremap(GPJ0CON_PA, 4);pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);*pGPJ0CON = 0x11111111;*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 */// 2步完成了映射if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "GPJ0REG"))return -EINVAL;pGPJ0REG = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t));// 映射之后用指向结构体的指针来进行操作// 指针使用->结构体内元素的方式来操作各个寄存器pGPJ0REG->gpj0con = 0x11111111;pGPJ0REG->gpj0dat = ((0<<3) | (0<<4) | (0<<5)); // 亮return 0; }// 模块卸载函数 static void __exit chrdev_exit(void) {printk(KERN_INFO "chrdev_exit helloworld exit\n");// *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); pGPJ0REG->gpj0dat = ((1<<3) | (1<<4) | (1<<5)); // 解除映射 /*iounmap(pGPJ0CON);iounmap(pGPJ0DAT);release_mem_region(GPJ0CON_PA, 4);release_mem_region(GPJ0DAT_PA, 4); */iounmap(pGPJ0REG);release_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t));// 在module_exit宏调用的函数中去注销字符设备驱动unregister_chrdev(mymajor, MYNAME); }module_init(chrdev_init); module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("aston"); // 描述模块的作者 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息