工作中,调试一个新硬件平台(mx28)上的设备驱动。由于要满足时序上的要求,要用到udelay来作延时。由于时序要求比较严格,在使用过程中,发现了一问题。然后udelay(1) 进行测试,用示波器测出的结果竟然是4.7us ,接近5us。证实了udelay 不准确,并不是1us。
跟踪了一下代码。arm 下面的udelay 应该是在arch/arminclude/asm/delay.h
- extern void __udelay(unsigned long usecs);
- extern void __const_udelay(unsigned long);
- #define MAX_UDELAY_MS 2
- #define udelay(n) \
- (__builtin_constant_p(n) ? \
- /* gcc的内建函数__builtin_constant_p用于判断n是否为编译时常数,如果n是常数,返回 1,否则返回 0。*/
- ((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \
- __const_udelay((n) * 0x68dbul)) : \
- /*n> MAX_UDELAY_MS * 1000时出错返回,否则调用函数__const_udelay */
- __udelay(n))
最终实现是在__udelay, __const_udelay。宏定义udelay(n) 。先判定__builtin_constant_p(n)否。int __builtin_constant_p( exp ) 是GCC 提供的一个内置函数判断一个值是否是编译时常量。Linux 相当频繁地使用常量检测,来进行优化。这个函数并不能检测出所有的常量。Udelay 的最大延时值为MAX_UDELAY_MS * 1000 ,即最大不能超过2000us,也就是2ms。看到了一个奇怪的数字,2199023U*HZ,为什么出现这个数字?猜测__const_delay 与__udelay 差别就在这个地方。不得不看一下__udelay中的实现。
- //arch/arm/lib/delay.S
- .LC0: .word loops_per_jiffy
- .LC1: .word (2199023*HZ)>>11 //HZ = 100
- /*
- * r0 <= 2000
- * lpj <= 0x01ffffff (max. 3355 bogomips)
- * HZ <= 1000
- */
- ENTRY(__udelay) //udelay 参数不为常数时。
- ldr r2, .LC1 //装载LC1到r2
- mul r0, r2, r0 // r0 = r0 * r2 此时r0为udelay参数n
- ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06 //udelay 参数不为常数时。
- ldr r2, .LC0 //装载LC0到r2
- ldr r2, [r2] @ max = 0x01ffffff //将r2作为指针,读取指针指向的数存放 r2中 也就是loops_per_jiffy的值
- mov r0, r0, lsr #14 @ max = 0x0001ffff //r0中内容右移14位
- mov r2, r2, lsr #10 @ max = 0x00007fff //r2中内容右移10位
- mul r0, r2, r0 @ max = 2^32-1 // r0 = r0 * r2
- movs r0, r0, lsr #6 //r0中内容右移6位, movs为下一指令moveq 更新了CPSR标志位
- moveq pc, lr
- /*到这个地方应该明白是怎么实现的了,就是利用loops_per_jiffy这个值。汇编语言忘记得差不多了,查了一下。搞明白了每行的意思。最后r0就是用来作递减的loops。上面相当于数学算式如下:
- R0 =( ( (n *((2199023*HZ)>>11)) >> 14 ) * (loops_per_jiffy >> 10) ) >> 6
- 不明白这具体是怎么算的,为什么出现移位?下面的注释中写到
- loops = n * HZ * loops_per_jiffy / 1000000
- 这个loops 应该这个值啊。N秒的loops值除以1000000,就应该是N us 的loops 值。为什么会出现这些移位呢?2199023是怎么来的?*/
- /*
- * loops = r0 * HZ * loops_per_jiffy / 1000000
- *
- * Oh, if only we had a cycle counter...
- */
- @ Delay routine
- ENTRY(__delay)
- subs r0, r0, #1
- #if 0
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- movls pc, lr
- subs r0, r0, #1
- #endif
- bhi __delay //如果为正数就跳转到__delay
- mov pc, lr
- ENDPROC(__udelay)
- ENDPROC(__const_udelay)
- ENDPROC(__delay)
为什么会出现这些移位呢?2199023是怎么来的?这个计算式里面就只有一个loops_per_jiffy看起来不是常量,其实也是常量。干脆找了一下loops_per_jiffy的计算方法。
- 在init/calibrate.c
- void __init calibrate_delay(void)
- {
- unsigned long ticks, loopbit;
- int lps_precision = LPS_PREC;
- loops_per_jiffy = (1<<12); //每滴答循环次数
- printk("Calibrating delay loop… ");
- while (loops_per_jiffy <<= 1) {
- /* 等待一个系统滴答的开始 */
- ticks = jiffies;
- while (ticks == jiffies)
- /* nothing */;
- /* Go .. */
- ticks = jiffies;
- __delay(loops_per_jiffy); //延迟 循环loops_per_jiffy次
- /*
- * 原型
- * void __delay(unsigned long loops)
- * {
- * cur_timer->delay(loops);
- * }
- *
- */
- ticks = jiffies – ticks; //判断是否同一个滴答内
- if (ticks)
- break; //不同则跳出,已经得到一个粗值
- }
- /* Do a binary approximation to get loops_per_jiffy set to equal one clock
- (up to lps_precision bits) */
- loops_per_jiffy >>= 1;
- loopbit = loops_per_jiffy;
- /* 第二次计算,采用二分法逐步逼近精确值 */
- while ( lps_precision– && (loopbit >>= 1) ) {
- loops_per_jiffy |= loopbit;
- ticks = jiffies;
- while (ticks == jiffies); //等待一个滴答开始
- ticks = jiffies;
- __delay(loops_per_jiffy); // 循环loops_per_jiffy,先其值为粗值的1/2,
- // 而精确值必在粗值和loops_per_jiffy之间
- if (jiffies != ticks) /* longer than 1 tick */
- loops_per_jiffy &= ~loopbit; //倘若大于一个滴答,则说明loops_per_jiffy大于实际值
- // 减去loopbit值
- }
- .
- .
- .
loops_per_jiffy最后可以得出,也就是它确确实实是个常量,并且至少大于(1<<12)。这时候在网上查了一下arm delay 。终于找到了原因。
loops = n * HZ * loops_per_jiffy / 1000000
上面的计算,在非浮点处理器(整型处理器)运行时,很容易变为0。 如果loops_per_jiffy * HZ/1000000时,loops_per_jiffy * HZ / 1000000=0。无能你想要延迟多少微秒,总为0。处理方法,除1000000变为乘1/1000000,为保持精度,1/1000000要先左移30位, 变为
(1/1000000)<<30 = 2^30 / 1000000 = 2199023U>>11
哎,考虑得非常全面。这就明白了“为什么会出现这些移位呢?2199023是怎么来的?”。2199023 >> 11 = (1/1000000) * 2^30汇编中出现的移位则是为了把向左移的30位移回来。考虑到溢出,所以分成了>>14 , >>10, >>6,最后等同于 >>30 。到此处就彻底明白了arm 中的udelay是如何实现的。最初udelay不准,为什么会跑到5us?跟代码似乎也没有什么不对的地方,可能跟loops_per_jiffy有关。
参考资料:
http://blog.chinaunix.net/space.php?uid=20657684
阅读(16642) | 评论(0) | 转发(1) |