驱动相关的内核函数分析
目录:
字符设备驱动程序对一些高级特性的实现 nonseekable_open; scull_p_poll
Linux内核中获取当前时间 do_gettimeofday
用户空间和内核空间传递数据:get_user;put_user;copy_to_user;copy_from_user
__raw_readl和__raw_writel
GPIO端口控制及宏定义
字符设备驱动程序对一些高级特性的实现 nonseekable_open; scull_p_poll
详见:http://blog.sina.com.cn/s/blog_6e5b342e0100m87o.html
但像串口或键盘一类设备,使用的是数据流,所以使用lseek定位这些设备没有意义;在 open 方法中调用 nonseekable_open() 时,它会通知内核设备不支持 llseek,nonseekable_open() 函数的实现定义在 fs/open.c 中。
int nonseekable_open(struct inode *inode, struct file *filp) { filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); return 0; }
为了完整起见,如果不希望设备被 seek,还应该将 file_operations 结构中的 llseek 方法设置为特殊的辅助函数 no_llseek 。即" .llseek = no_llseek, "。
而no_llseek函数无需手动定义,内核中已经定义好了。
Linux内核中获取当前时间 do_gettimeofday
#include <linux/time.h> void do_gettimeofday(struct timeval *tv);
获取当前距离系统启动的时间差,并将时间拆分为秒和微秒存入struct timeval结构体。
get_user;put_user;copy_to_user;copy_from_user
这些函数均是内核态用于内核空间与用户空间的数据传输的函数
get_user和put_user复制的是简单的数据类型,如char,int ,long等;
copy_to_user和copy_from_user则是以数据块的形式在内核空间与用户空间传输的。
//函数与宏定义 unsigned long copy_to_user(void __user *to, const void *from, unsigned long n){...} unsigned long copy_from_user(void *to, const void __user *from, unsigned long n){...} #define put_user(x,ptr) (...) #define put_user(x,ptr) (...)
arch\arm\include\asm\Io.h
#define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))
#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))
注:(volatile unsigned int __force *)指针强制转换为unsigned int型。
其中volatile关键字有以下用途:
(1)用来同步,因为同一个东西可能在不同的存储介质中有多个副本,有些情况下会使得这些副本中的值不同,这是不允许的,所以干脆用volatile,让它只有一个,没有其他的副本,这样就不会发生不同步的问题。
如下所示:
volatile的意思是告诉编译器,在编程源代码时,对这个变量不要使用优化。
在一般的程序设计中,如:
int *a; int b;
b = (*a) * (*a);这种情况。
通常编译器为了减少存储器的读写时间,会把代码优化为:
int *a; int b; int c;
c = *a;
b = c * c;
如果把int *a改为volatile int* a编译器就不会自动把它优化掉了。在整个运算过程中,对变量*a的值又读取了一次。防止因变量*a的值在这一期间发生了改变,而导致程序结果的错误。
(2)防止编译器优化去掉某些语句,像我在arm中见到个寄存器非常奇怪,当中断来的时候,相对应的位置1,而清0又不能向这位写0,向这位写1才是1才是清中断(清0),
// 假设0x560012300 为寄存器地址
#define INTPAND *(volatile unsigned int *)0x560012300
INTPAND = INTPAND; // 清中断
像编译器如果看到有INTPAND = INTPAND;这种看似无用的操作,如果没有volatile说明,编译器就很有可能会去掉INTPAND = INTPAND;实际上有用的东西,却被编译器当没用的东西优化掉了。
(3)当地址是io端口的时候,读写这个地址是不能对它进行缓存的,这是相对于某些嵌入式中有cache才有这个。比如写这个io端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候再写到io端口,这样就不能使数据及时的写到io端口,有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到io端口,从而避免了读写io端口的延时。
在include\linux\compiler.h中:
#ifdef __CHECKER__
……
extern void __chk_io_ptr(void __iomem *);
#else
……
# define __chk_io_ptr(x) (void)0
……
#endif
__raw_readl(a)展开是:((void)0, *(volatile unsigned int _force *)(a))。在定义了__CHECKER__的时候先调用__chk_io_ptr检查该地址,否则__chk_io_ptr什么也不做,* (volatile unsigned int _force *)(a)就是返回地址为a处的值。(void)xx的做法有时候是有用的,例如编译器打开了检查未使用的参数的时候需要将没有用到的参数这么弄一下才能 编译通过。
注:语句表达式 x= (y=(a+b), z=10); 的执行过程:顺序执行括号内的语句,注意各语句分隔使用的是逗号,把最后一个语句z的值赋给x。
CPU对I/O的物理地址的编程方式有两种:一种是I/O映射,一种是内存映射。 __raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出来的操作方法有:inb、outb、 _memcpy_fromio、readb、writeb、ioread8、iowrite8等。
S3C2410GPIO端口的宏定义 arch/arm/mach-s3c2410/include/mach/regs-gpio.h
1.S3C2410_GPB5是端口编号,定义在regs-gpio.h中,
#define S3C2410_GPIO_BANKB (32*1)
#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
#define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
S3C2410共有130个GPIO,分为9组(GPA~GPJ),每组最多可以有 32个,每个GPIO有2~4个可选功能,每组的控制寄存器空间有4个,例如对于GPB,有GPBCON、GPBDAT、GPBUP和Reserved, 分别是功能配置、数据缓存、上拉使能和保留。
上面的S3C2410_GPB5就是GPIO的编号,也就是在号码空间(0~32*9-1)中的位置,bank是分组的基号码,offset是组内偏移量。(也就是说把所有的IO口从0开始进行统一的编号如:S3C2410_GPA0=0,S3C2410_GPA1=1,S3C2410_GPB0=32,S3C2410_GPC0=48等)
2.S3C2410_GPB5_OUTP是端口功能,定义在regs-gpio.h中,
#define S3C2410_GPB5_INP (0x00 << 10)
#define S3C2410_GPB5_OUTP (0x01 << 10)
GPBCON的第10、11两位用于配置GPB5的功能,00 = Input ,01 = Output
3.S3C2410 GPIO的操作函数
在hardware.h文件中有:
s3c2410_gpio_cfgpin //配置端口的GPIO的功能
s3c2410_gpio_getcfg //读取功能配置
s3c2410_gpio_pullup //配置上拉电阻
s3c2410_modify_misccr //杂项配置
s3c2410_gpio_getirq //给定端口,转换出IRQ号
s3c2410_gpio_irqfilter //配置IRQ过滤使能与否
s3c2410_gpio_setpin //写数据到端口
s3c2410_gpio_getpin //从端口读数据
这些函数的实现在gpio.h中
void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) { void __iomem *base = S3C2410_GPIO_BASE(pin);//算出端口所在组虚拟基址如://GPA=0xF0E00000 //GPB=0XF0E00010 unsigned long offs = S3C2410_GPIO_OFFSET(pin); //算出端口所在组的偏移量(0~31) unsigned long flags; unsigned long dat; local_irq_save(flags); dat = __raw_readl(base + 0x04); //虚拟基址加0x04为 GP*DAT寄存器,加0x00为GP*ON等 //读出当前GP*DAT寄存器的值 dat &= ~(1 << offs); //根据offs偏移量对该寄存器中选中的 位 清零,其他位保持不变 dat |= to << offs; //根据形参对要求的位进行位操作,来实现对具体某个IO口的配置 __raw_writel(dat, base + 0x04); //将配置写入到寄存器(这里是虚拟地址) local_irq_restore(flags); }
unsigned int s3c2410_gpio_getpin(unsigned int pin) { void __iomem *base = S3C24XX_GPIO_BASE(pin); unsigned long offs = S3C2410_GPIO_OFFSET(pin); return __raw_readl(base + 0x04) & (1<< offs); }
s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值与所要读取的GPIO对应的bit mask相与以后的值,0表示该GPIO对应的bit为0, 非0表示该bit为1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9为低电平则返回的是0,如果是高电平则返回的是GPxDAT中的GPG9对应位的值为0x0100而不是0x0001,查处问题后修改也很简单了。
4.S3C2410_GPIO_BASE和S3C2410_GPIO_OFFSET也是在regs-gpio.h文件中定义,
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
#define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
而在map.h中有:
/* GPIO ports */
#define S3C24XX_VA_GPIO S3C2410_ADDR(0x00E00000) //虚拟地址S3C24XX_VA_GPIO= 0xF0E00000
#define S3C2400_PA_GPIO (0x15600000)
#define S3C2410_PA_GPIO (0x56000000) //GPACON 物理地址
#define S3C24XX_SZ_GPIO SZ_1M //0x100000 = 1024 *1024
S3C2410_GPIO_BASE作用是:根 据端口编号pin,算出端口所在组的虚拟基址。((pin) & ~31)是去掉pin当中小于等于31的零头(清0低5位),>>1的原因是每组GPIO中最多可以有32个端口,控制这些端口需要4个寄存 器空间,4个寄存器空间就需要4*4=16个字节进行编址,32/16=2,左移一位刚好满足。也就是说,上一组端口和下一组端口的编号相差32,而控制 寄存器的地址相差16。
S3C2410_GPIO_OFFSET作用是:根据端口编号pin,算出端口所在组的偏移量。((pin) & 31)即去掉比31大的数(清0第6位以上的位)。