首先明确的是对于驱动的作用类似于为应用程序提供接口用来关联设备和操作设备进行打开,读写等操作,在上篇的总结中了解到了驱动的开发框架,在此基础上进行系统设备和驱动程序的关联,首先是需要创建设备文件,这个设备文件放在根目录下的/dev/下,可以查看/dev/目录下的设备可知道当前系统运行的设备有哪些,可以看到有不少设备文件这里截取部分
我们接下来就是要创建一个设备文件用来关联我们的驱动,以供应用程序来调用
设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。
使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号
需要注意的是我们创建的设备文件的主次设备号不能被其他的设备文件已经使用,这里创建一个设备文件,主设备号选250 次设备号选0
mknod /dev/test c 250 0
通过cat /proc/devices可以查看设备号
然后在安装我们的驱动文件
insmod module.ko
这里是通过什么把我们的设备文件和驱动关联的呢?
答案是主设备号,我们创建的设备文件的主设备号是250,驱动文件里的设备号也是250,通过主设备号就可以关联设备文件和驱动文件,接下来就是在应用程序中调用设备驱动接口,对应有open、write等
注意应用程序调用的接口要与设备提供的接口一致
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
应用程序
#define FILE "/dev/test" // 刚才mknod创建的设备文件名
char buf[100];
int main(void)
{
int fd = -1;
int i = 0;
fd = open(FILE, O_RDWR);
if (fd < 0)
{
printf("open %s error.\n", FILE);
return -1;
}
printf("open %s success..\n", FILE);
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \n");
scanf("%s", buf);
if (!strcmp(buf, "on"))
{
write(fd, "1", 1);
}
else if (!strcmp(buf, "off"))
{
write(fd, "0", 1);
}
else if (!strcmp(buf, "flash"))
{
for (i=0; i<3; i++)
{
write(fd, "1", 1);
sleep(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);//实际调用的是file_operations 结构体中的 release
return 0;
}
执行结果
简单总结:
一份驱动文件
创建/dev/目录下的设备文件
驱动文件和设备文件通过主设备号关联
应用程序通过调用程序接口达到调用驱动函数的目的
第二个总结:虚拟地址映射
虚拟内存概念这里就不说了,具体百度有解释
虚拟地址分为静态虚拟地址和动态两种,一个题外话还有一个叫虚拟内存的东西,别搞混了
虚拟静态地址,可以理解为已经确定的地址表,一直存在是静态的,而虚拟动态地址是动态的需要调用函数申请和释放的,这两种方法各有利弊
虚拟静态地址
对于三星的X5PV210来说主要查看内核下的这几个文件
主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h
虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h
GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h
map-s5p.h文件内容
#ifndef __ASM_PLAT_MAP_S5P_H
#define __ASM_PLAT_MAP_S5P_H __FILE__
#define S5P_VA_CHIPID S3C_ADDR(0x00700000)
#define S5P_VA_GPIO S3C_ADDR(0x00500000)
#define S5P_VA_SYSTIMER S3C_ADDR(0x01200000)
#define S5P_VA_SROMC S3C_ADDR(0x01100000)
#define S5P_VA_AUDSS S3C_ADDR(0X01600000)
#define S5P_VA_UART0 (S3C_VA_UART + 0x0)
#define S5P_VA_UART1 (S3C_VA_UART + 0x400)
#define S5P_VA_UART2 (S3C_VA_UART + 0x800)
#define S5P_VA_UART3 (S3C_VA_UART + 0xC00)
#define S3C_UART_OFFSET (0x400)
#define VA_VIC(x) (S3C_VA_IRQ + ((x) * 0x10000))
#define VA_VIC0 VA_VIC(0)
#define VA_VIC1 VA_VIC(1)
#define VA_VIC2 VA_VIC(2)
#define VA_VIC3 VA_VIC(3)
#endif /* __ASM_PLAT_MAP_S5P_H */
map-base.h文件内容
#ifndef __ASM_PLAT_MAP_H
#define __ASM_PLAT_MAP_H __FILE__
/* Fit all our registers in at 0xF4000000 upwards, trying to use as
* little of the VA space as possible so vmalloc and friends have a
* better chance of getting memory.
*
* we try to ensure stuff like the IRQ registers are available for
* an single MOVS instruction (ie, only 8 bits of set data)
*/
#define S3C_ADDR_BASE (0xFD000000)
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
#define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */
#define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
#define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */
#define S3C_VA_OTG S3C_ADDR(0x00E00000) /* OTG */
#define S3C_VA_OTGSFR S3C_ADDR(0x00F00000) /* OTG PHY */
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
/* This is used for the CPU specific mappings that may be needed, so that
* they do not need to directly used S3C_ADDR() and thus make it easier to
* modify the space for mapping.
*/
#define S3C_ADDR_CPU(x) S3C_ADDR(0x00500000 + (x))
#endif /* __ASM_PLAT_MAP_H */
其他两个就不展示出来了,在对寄存器进行操作外部设备时
定义寄存器
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
操作寄存器
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
就可以达到通过虚拟静态地址操作寄存器达到操作外部设备的目的
虚拟动态地址
需要通过调用函数来申请,在使用结束后需要对动态映射表进行释放,节省资源
定义寄存器地址,对于动态申请虚拟地址映射所用到的物理地址还需要申请,通过request_mem_region(GPJ0CON_PA, 4, “GPJ0CON”)来申请
#define GPJ0CON_PA 0xe0200240 //物理地址
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
申请映射虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
操作寄存器
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
注意这里只是用于早起阶段的学习,由于soc引脚数量有限,一般引脚需要分时复用来实现对不同功能外设的操作,所以在对寄存器的操作时不能影响其他外设的状态,这就涉及到需要判断io是否空闲,再进行申请和操作,后面会记录到,目前就先到这里。