vivi实现了两个延时函数:udelay和mdelay。前者是微秒级,后者是毫秒级。对于一般应用已经可以了。但是在分析其实现过程中,发现其设计是不合理的。在探讨过程中,把中断的层次概念弄清晰了,算是额外的收获。下面展开具体分析。
#ifndef _VIVI_TIME_H_ #define _VIVI_TIME_H_ #include "config.h"
#ifndef __ASSEMBLY__ void init_time(void); void mdelay(unsigned int); void udelay(unsigned int); #endif
#endif /* _VIVI_TIME_H_ */
|
udelay和mdelay类似。现在以udelay为主线。首先,声明是在【include/time.h】中,如上所示。然后追踪其定义,利用source insight很容易找到。发现其核心的处理部分是在【arch/s3c2410/proc.c】中,如下:
static void time_wait(unsigned int sec, int unit) { unsigned long ticks, clock_tick_rate; /* clear interupt bit */ SRCPND |= INT_TIMER4; INTPND |= INT_TIMER4; INTMSK &= ~INT_TIMER4; /* enable timer 4 interrupt */ clock_tick_rate = get_clock_tick_rate(); if (clock_tick_rate == 0) { printk("Can not get a clock tick rate\n"); return; }
if (unit == 0) { ticks = (clock_tick_rate * (sec)) / (1000 * 1000); } else { ticks = (clock_tick_rate * (sec)) / (1000); }
TCNTB4 = ticks; TCON = (TCON_4_UPDATE | COUNT_4_OFF); /* load counter value */ TCON = (COUNT_4_ON); /* start timer */ while (!(INTPND & INT_TIMER4)) ; TCON = (COUNT_4_OFF); /* stop timer */ INTMSK |= INT_TIMER4; /* mask timer 4 interrupt */ /* clear interupt bit */ SRCPND |= INT_TIMER4; INTPND |= INT_TIMER4; }
|
很明显的步骤:
・开启中断标志
・设置初值
・设置模式,开启定时器
・查询,直到中断产生,即表示时间到
・关定时器,清中断信号
这个函数很奇怪。虽然开启了中断,却不使用中断处理函数,而是查询的方式。知道vivi的中断向量表对IRQ的处理都是死循环,而这个延时函数却能够很好的运行。
为什么?原因就在于中断的分层控制机制。
中断控制可以理解为两层控制。最顶层的中断控制是cpu的中断控制,即cpsr寄存器,可以控制IRQ、FIQ中断的开启或关闭。这是最顶层的,没有比这更高的了。第二层就是SoC自己实现的中断控制器,当然可能有两级甚至多级中断控制。它实现产生中断信号,并将这个中断信号送到cpu,进行处理。所以也比较明显,如果cpsr的I位没有使能,那么第二层产生了信号,送达cpu,但是cpu是置之不理的。从硬件的角度看,中断控制的层次就理顺了。上面这个问题就比较容易理解了。
通过上述分析,得知这个函数的实现是很差的,需要改写。好的方式应该是关中断,只关注定时器,实现对时间的管理,实现相应的延时处理。或者采用中断方式处理,处理部分放到中断服务程序中。不过bootloader还是不用中断方便些。
记在此处,备忘。以后分析中断要基于这个层次模型来分析。另外,要把vivi延时机制改写。
补记:
借鉴U-boot-1.2.0改进了vivi的延时机制。U-boot并没有采用中断的方式,而是采用查询的方法。对bootloader来说,完成的任务并不复杂,使用查询可以减少中断处理的复杂度。
vivi延时机制的改进patch如下(基本采用U-boot的代码,作出了稍许的改动):
[armlinux@lqm patch]$ cat vivi_timer.patch diff -urN vivi-0.1.4.orig/arch/s3c2410/proc.c vivi/arch/s3c2410/proc.c --- vivi-0.1.4.orig/arch/s3c2410/proc.c 2003-01-30 23:46:11.000000000 +0800 +++ vivi/arch/s3c2410/proc.c 2007-10-02 14:17:53.000000000 +0800 @@ -261,75 +261,123 @@ return (freq / (prescale + 1) / divider); } -static void -time_wait(unsigned int sec, int unit) +int timer_load_val = 0; +static ulong timestamp; +static ulong lastdec; + +static void +init_timer4(void) { - unsigned long ticks, clock_tick_rate; + /* + * use PWM Timer4 because it has no output + * prescaler for Timer 4 is 15. + * + * Notice: TCFG0 configured by init_time(); + */ + if (timer_load_val == 0) { + /* + * 10ms clock period, clock frequency is 100Hz + * prescaler = 15 and divider = 1/2 + * so timer_load_val should be 15625 if PCLK is 50MHz + * + * Formula: + * [Timer input clock Frequency = PCLK/{prescaler value+1}/{divider value}] + */ + timer_load_val = get_bus_clk(GET_PCLK)/(2 * (15 + 1) * 100); + printk("Timer4: 10ms and timer_load_val is %d!\n", timer_load_val); + } + + /* load value for 10ms timeout */ + lastdec = TCNTB4 = timer_load_val; + /* auto load, manual update of Timer 4 */ + TCON = (TCON & ~0x00700000) | 0x00600000; + TCON = (TCON & ~0x00700000) | 0x00500000; + timestamp = 0; +} - /* clear interupt bit */ - SRCPND |= INT_TIMER4; - INTPND |= INT_TIMER4; - INTMSK &= ~INT_TIMER4; /* enable timer 4 interrupt */ +/* macro to read the 16 bit timer */ +static inline ulong READ_TIMER(void) +{ + return (TCNTO4 & 0xffff); +} - clock_tick_rate = get_clock_tick_rate(); - if (clock_tick_rate == 0) { - printk("Can not get a clock tick rate\n"); - return; - } +/* + * timer without interrupts + */ - if (unit == 0) { - ticks = (clock_tick_rate * (sec)) / (1000 * 1000); +void reset_timer_masked(void) +{ + /* reset time */ + lastdec = READ_TIMER(); + timestamp = 0; +} + +ulong get_timer_masked(void) +{ + ulong now = READ_TIMER(); + + if (lastdec >= now) { + /* normal mode */ + timestamp += lastdec - now; } else { - ticks = (clock_tick_rate * (sec)) / (1000); + /* we have an overflow ... */ + timestamp += lastdec + timer_load_val - now; } + lastdec = now; - TCNTB4 = ticks; - TCON = (TCON_4_UPDATE | COUNT_4_OFF); /* load counter value */ + return timestamp; +} - TCON = (COUNT_4_ON); /* start timer */ +void reset_timer(void) +{ + reset_timer_masked(); +} - while (!(INTPND & INT_TIMER4)) ; - - TCON = (COUNT_4_OFF); /* stop timer */ +ulong get_timer(ulong base) +{ + return get_timer_masked() - base; +} - INTMSK |= INT_TIMER4; /* mask timer 4 interrupt */ - /* clear interupt bit */ - SRCPND |= INT_TIMER4; - INTPND |= INT_TIMER4; +void set_timer(ulong t) +{ + timestamp = t; } -static void -time_delay(unsigned int sec, int unit) +/* + * type + * 0 -- usec + * 1 -- msec + */ +void delay(ulong time, unsigned char type) { - unsigned int remain = sec; + ulong tmo; + ulong start = get_timer(0); - while (remain > 0) { - if (remain > 40) { - sec = 40; + if (time >= 1000) { + tmo = time / 1000; + tmo *= (timer_load_val * 100); + if (!type) { + tmo /= 1000; + } + } else { + tmo = time * (timer_load_val * 100); + if (type) { + tmo /= 1000; } else { - sec = remain; + tmo /= (1000 * 1000); } - time_wait(sec, unit); - remain -= sec; } -} - -void -arch_udelay(unsigned int usec) -{ - time_delay(usec, 0); -} -void -arch_mdelay(unsigned int msec) -{ - time_delay(msec, 1); + while ((ulong)(get_timer_masked() - start) < tmo) { + /* NOP */; + } } void init_time(void) { TCFG0 = (TCFG0_DZONE(0) | TCFG0_PRE1(15) | TCFG0_PRE0(0)); + init_timer4(); } |
现在看来,无论是bootloader还是kernel,延时是必不可少的组成部分。采取的方法无非有两种:一种是采用关闭中断、打开定时器的方法实现,软件查询,占用CPU的时间比较多,适用于事件并不复杂,并发性要求不高的地方;另一种就是采用中断实现,利用中断服务例程来处理到时操作,占用的CPU时间少。这两种方法在不同的情况下,还可以采用一些辅助机制,比如有限状态机的编程处理方法,来增强对多事件的并行处理能力。
对vivi的了解逐步深入,发现vivi算是一个比较粗糙的产品,其代码的组织虽然脱胎于kernel,但是离U-boot、Linux kernel等优秀的软件来说,还差的太远。在这种情况下,要么推导重来,自己依据此框架完全实现一个bootloader,要么依据此架构,用学习的态度增加功能,尽量不变动原有的代码。第一种情况并不可取,因为可以学习U-boot,掌握精通,足以应付各种需求。第二种情况适合于学习,最终会过渡到U-boot上。其实,所有的这些学习都是基础,而对bootloader的编写和移植,要点一是对SoC的了解,对体系结构的了解,二是就是无OS的驱动程序的编写和移植。其他的就没有什么困难的了。
深入的去学习bootloader,对自己各个方面都是一个巩固和提高的过程。还是那句话,现在就是打基础,打好基础!慢慢来。。。
评论
发表评论