完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
穷根究底linux内核函数之s3c2410_gpio_cfgpin()
原创文章,转载注明出处:http://home.elecfans.com/space.php?uid=710245&do=blog&id=24588 flyriz 2012-07-06 在ARM驱动程序的学习过程中,经常碰到对IO操作的函数:s3c2410_gpio_cfgpin(),结合linux内核源代码(版本linux-2.6.29.4),做一个详细的分析,以如下代码为例: s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP); 从函数名上来看,其作用是把S3C2410的GPB5引脚设置为输出,接下来就像做数学题一样进行化简吧,为方便描述,过程中用等号表示。 先把参数展开,参数S3C2410_GPB5 宏展开: S3C2410_GPB5 = S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) S3C2410_GPIO_BANKB = (32*1) S3C2410_GPIONO(bank,offset) = ((bank) + (offset)) S3C2410_GPB5 = (32*1)+5 参数S3C2410_GPB5_OUTP 宏展开: S3C2410_GPB5_OUTP = (0x01 << 10) 参数简化后的函数: s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP)= s3c2410_gpio_cfgpin( (32*1)+5,(0x01 << 10) ) 接下为再对函数本身化简,进入函数:s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function) void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function) { void __iomem *base = S3C24XX_GPIO_BASE(pin); unsigned long mask; unsigned long con; unsigned long flags; if (pin < S3C2410_GPIO_BANKB) { mask = 1 << S3C2410_GPIO_OFFSET(pin); } else { mask = 3 << S3C2410_GPIO_OFFSET(pin)*2; } switch (function) { case S3C2410_GPIO_LEAVE: mask = 0; function = 0; break; case S3C2410_GPIO_INPUT: case S3C2410_GPIO_OUTPUT: case S3C2410_GPIO_SFN2: case S3C2410_GPIO_SFN3: if (pin < S3C2410_GPIO_BANKB) { function -= 1; function &= 1; function <<= S3C2410_GPIO_OFFSET(pin); } else { function &= 3; function <<= S3C2410_GPIO_OFFSET(pin)*2; } } /* modify the specified register wwith IRQs off */ local_irq_save(flags); con = __raw_readl(base + 0x00); con &= ~mask; con |= function; __raw_writel(con, base + 0x00); local_irq_restore(flags); } 现在看第一个子函数: S3C24XX_GPIO_BASE(pin),这也是最复杂的一个子函数,宏展开后: S3C24XX_GPIO_BASE(pin) = S3C2410_GPIO_BASE(pin) = ( ( ( (pin) & ~31 ) >> 1 ) + S3C24XX_VA_GPIO ) S3C24XX_VA_GPIO = ( (S3C24XX_PA_GPIO-S3C24XX_PA_UART) + S3C24XX_VA_UART ) S3C24XX_PA_GPIO = S3C2410_PA_GPIO = (0x56000000) S3C24XX_PA_UART = S3C2410_PA_UART = (0x50000000) S3C24XX_VA_UART = S3C_VA_UART = S3C_ADDR(0x01000000) S3C_ADDR(x)=(S3C_ADDR_BASE + (x)) S3C_ADDR_BASE=(0xF4000000) S3C24XX_VA_UART=(0xF4000000)+(0x01000000)=(0xF5000000) S3C24XX_VA_GPIO =(0x56000000)-(0x50000000)+(0xF5000000) =0xFB000000 最后:S3C24XX_GPIO_BASE(pin)=( ( ( (pin) & ~31 ) >> 1 ) + 0xFB000000 说明: ( ( ( (pin) & ~31 ) >> 1 ),这个东东是为了得到指定的pin引脚的寄存器相对于GPIO基地址的偏移量,至于为什么要这样写我接下来会说明。 S3C24XX_VA_GPIO,GPIO基地址的虚拟地址。顺便说明一下VA,PA,VA:虚拟地址,PA:物理地址,都是英语单词的首字母。现在很好理解了,先根据GPIO,UART的物理地址算出两者的偏移量:(S3C24XX_PA_GPIO- S3C24XX_PA_UART),然后UART虚拟地址加上这个偏移量就得到GPIO的虚拟地址了:( (S3C24XX_PA_GPIO- S3C24XX_PA_UART) + S3C24XX_VA_UART )。至于S3C24XX_VA_UART为什么最后就变成了(0xF5000000),我也暂时不知道,内核代码就是这么写的,应该是这样一个转换规则吧。 分析完这个子函数,接下来的代码就很好理解了。有一个地方要注意一下:if (pin < S3C2410_GPIO_BANKB),两次出现这个表达式,因为这个芯片的PORT A与其他的端口的不一样,它能实现的功能比较少,GPACON中只用一个数据位来控制这个引脚的功能,其他的比如PORT B的GPBCON寄存器,是用两个数据位来控制的。所以对PORT A的引脚要单独处理了。 有一个问题: S3C_VA_UART = S3C_ADDR(0x01000000),这里的UART虚拟地址转制为什么是这样子的? 再看一下源代码里面的其他虚拟地址的转换: #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) /* system control */ #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */ #define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */ #define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */ 这个S3C_ADDR里面的内容是如何确定的? S3C_ADDR_BASE=(0xF4000000),这个基地址又是怎么确定的? 期待高手的解答,谢谢! |
|
相关推荐
2 个讨论
|
|
不好意思,这个问题好几天没有回你!
S3C_ADDR里面的内容是如何确定的? 答:S3C_ADDR里面看起来很有规律哦,从0x00000000开始,然后每隔1M往上加,这里是要1M对齐,这个要看MMU映射原理,第一级页表里面的每个入口放的是与其关联的1MB虚拟地址是如何映射的描述符,真要把它搞情况推荐你看《ARM嵌入式系统开发——软件设计与优化》——沈建华译(这本书是ARM公司的几个资深工程师写的,应该没有人能比他们更权威了,我曾经也有想写一个关于ARM的书,但看了这本书后,觉得无法超越,如果要写,我觉得写一本这本书的附件更有意义)。 第二个问题:0xF0000000是怎么来的? 答:我与你的内核不一样,我是2.6.13,初学者不要去研究高版本的,高版本的很多注释也许不是原版,甚至去掉了,linux发展很快,还没有人力去做这些细微的事情!下面源码: /* we have a bit of a tight squeeze to fit all our registers from * 0xF00000000 upwards, since we use all of the nGCS space in some * capacity, and also need to fit the S3C2410 registers in as well... * * we try to ensure stuff like the IRQ registers are available for * an single MOVS instruction (ie, only 8 bits of set data) * * Note, we are trying to remove some of these from the implementation * as they are only useful to certain drivers... */ #ifndef __ASSEMBLY__ #define S3C2410_ADDR(x) ((void __iomem *)0xF0000000 + (x)) #else #define S3C2410_ADDR(x) (0xF0000000 + (x)) #endif #define S3C2400_ADDR(x) S3C2410_ADDR(x) /* interrupt controller is the first thing we put in, to make * the assembly code for the irq detection easier */ #define S3C24XX_VA_IRQ S3C2410_ADDR(0x00000000) #define S3C2400_PA_IRQ (0x14400000) #define S3C2410_PA_IRQ (0x4A000000) #define S3C24XX_SZ_IRQ SZ_1M 下面掠过 /* we have a bit of a tight squeeze to fit all our registers from * 0xF00000000 upwards, since we use all of the nGCS space in some * capacity, and also need to fit the S3C2410 registers in as well... * 这段很让人费解,我的理解只是根据我个人经验,也许比较牵强!仅供参考!我没有时间去找官方资料来佐证,有兴趣有时间的朋友,请跟帖补上,来上海我带你去看外滩(呵呵)!linux把从0xf0000000往上的高端地址拿来做虚拟地址的映射,其实这里从0xf0000000到顶0xffffffff,总共才256M空间,所以说比较紧凑(tight squeeze),我们每个外设需要1M的虚拟地址空间,那么整个CPU最多可以映射256个设备控制器,也不少了,CPU也没有这么多设备控制器! mmu的东西实在太庞大!仅作抛砖引玉! |
|
|
|
|
|
只有小组成员才能发言,加入小组>>
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-5 15:22 , Processed in 0.916334 second(s), Total 50, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号