8.驱动与硬件通信
驱动与硬件通信
驱动程序控制设备,主要是通过访问设备内的寄存器来达到控制目的,因此我们讨论如何访问硬件,就成了如何访问这些寄存器了.
一、地址映射
在Linux系统中,无论是内核程序还是应用程序,都只能使用虚拟地址,而芯片手册中给出的硬件寄存器地址或者RAM地址则是物理地址,无法直接使用,因此,我们读写寄存器的第1步就是将将它的物理地址映射为虚拟地址。
1.1动态映射
所谓动态映射,是指在驱动程序中采用ioremap函数将物理地址映射为虚拟地址。
原型:void * ioremap(physaddr, size)
参数:
Physaddr:待映射的物理地址
Size: 映射的区域长度
返回值:映射后的虚拟地址
1.2静态映射
所谓静态映射,是指Linux系统根据用户事先指定的映射关系,在内核启动时,自动地将物理地址映射为虚拟地址。
1.2.1映射关系
在静态映射中,用户是通过map_desc结构来指明物理地址与虚拟地址的映射关系。
struct map_desc{
unsigned long virtual; /* 映射后的虚拟地址 */
unsigned long pfn; /* 物理地址所在的页帧号 */
unsigned long length; /* 映射长度 */
unsigned int type; /* 映射的设备类型 */
};
pfn: 利用__phys_to_pfn(物理地址)可以计算出物理地址所在的物理页帧号
该结构有四个成员:第一个参数是我们的虚拟地址,第二个参数才是我们实际的物理地址,只不过我们现在使用页桢号来表示。假如物理地址是50008000,一页的大小为4K,用物理地址除以4K,就得到页桢号了。
1.2.2如何映射
在source insight里面找到自己平台对应的Cpu.c文件进入,可以找到如下结构:
可以看出这是个数组,这个数组的元素是结构struct map_desc。一个这样的结构对应的是内存的一片映射区域。
看一下GPIO的这一片区域映射关系:
其中主要参数的来源:
查看S3c6410芯片手册memory map章节:
0x7f008000刚好是GPIO这片区域的起始物理地址。然后这片区域会被映射到下面的虚拟地址:
S3C64XX_VA_GPIO:
通过查找找到S3C_ADRR_CPU:
可以看到同一个文件里面的上面个有对S3C_ADDR的定义:
同时也有对S3C_ADDR_BASE的定义
找到的是上面的S3C_ADDR_BASE 0XF4000000加上偏移量x,就是S3C_ADDR的地址。有了这样的地址,当我们的内核要去使用这表的的时候:
当我们的6410启动的时候,会去找这样的一张表来完成映射。这就是内核启动的时候完成的静态映射。
12.3静态映射应用
在工程里面搜索S3C64XX_GPNCON:
这就是我们拿到的IO的虚拟地址,下面我们分析一下到底是如何来的:
1.搜索:S3C64XX_GPN_BASE
我们可以同时看到S3C64XX_GPN_BASE以及S3C64XX_GPIOREG的来源
2.我们接着搜索:S3C64XX_VA_GPIO
可以看到,我们又回到了前面静态映射的物理地址所以S3C64XX_GPNCON的地址就是我们的虚拟地址,我们可以直接使用。
理论上呢拿到S3C64XX_GPNCON这个值就可以使用了但是我在led程序中使用的时候居然卡死在程序里面出不来了,而我学习内存静态转换的过程也就卡到这儿了希望日后可以解决吧
1 #include <mach/map.h> 2 #include <mach/regs-gpio.h> 3 #include <mach/regs-clock.h> 4 #include <mach/gpio-bank-n.h> 5 #include <plat/gpio-cfg.h> 6 7 8 9 int led_open (struct inode *node, struct file *filp) 10 { 11 writel(0x1111,S3C64XX_GPNCON); //为虚拟地址写入值 12 return 0; 13 } 14 15 //响应系统调用函数的驱动函数 16 long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 17 { 18 switch (cmd) //通过不同命令执行控制命令 19 { 20 case LED_ON: 21 writel(0x00,S3C64XX_GPNDAT); 22 return 0; 23 24 case LED_OFF: 25 writel(0xff,S3C64XX_GPNDAT); 26 return 0; 27 28 default: 29 return -EINVAL; 30 } 31 }
二、寄存器读写
在完成地址映射后,就可以读写寄存器了,Linux内核提供了一系列函数,来读写寄存器。
1 unsigned ioread8(void *addr) 2 unsigned ioread16(void *addr) 3 unsigned ioread32(void *addr) 4 unsigned readb(address) 5 unsigned readw(address) 6 unsigned readl(address) 7 8 9 void iowrite8(u8 value, void *addr) 10 void iowrite16(u16 value, void *addr) 11 void iowrite32(u32 value, void *addr) 12 void writeb(unsigned value, address) 13 void writew(unsigned value, address) 14 void writel(unsigned value, address)