前言
由於現在的作業系統對於Real Time的需求越來越多,而對於Real Time的需求則是需要一個較精確的時間計時,以往的Linux在時間的設計上已經不符這個需求,所以在2.6.38之後的版本中已經針對它來修改,這份文件將會說明對於這個新架構的HAL Timer Device Driver的寫法為何。
以往的問題
- 時間不夠精細,需要能夠取得nanosecond
- 在計算時間時必需能夠考慮到timer counter overflow的問題
現在的做法
你可以註冊多個clocksource, 所謂的clocksource就是可以產生一個固定的clock, 並且可以讀回其計數值, 系統中將會比較各個clocksource的精確度, 以找出最佳的clocksource做為系統的clocksource, 而這個clocksource將會做為系統的計算時間用, 另外必須註冊一個做為clock event使用, 這就是取代以往的timer interrupt, 而clocksource和clockevent可以為同一個, 也可以為分別不同的timer, 若系統的timer夠多, 建議可以設定不同的timer
How to
- 在arch/arm/mach-xxx的目錄中必須包以下的程式碼
- 在MACHINE_START宣告中須有timer的資料結構指定
MACHINE_START(MOXAART, "Moxa ART CPU platform")
.boot_params = 0x100,
.fixup = moxaart_fixup,
.map_io = moxaart_map_io, /* map physical to virtual address */
.init_irq = irq_init_irq,
.timer = &moxaart_timer,
.init_machine = moxaart_init, /* register platform_device */
MACHINE_END
其中timer的資料結構如下
extern void moxaart_timer_init(void);
static struct sys_timer moxaart_timer = {
.init = moxaart_timer_init,
};
其中init的成員為一個call back function,其寫法如下
void __init moxaart_timer_init(void)
{
*timer_ctrl_reg = timer_ctrl_reg_value;
setup_irq(IRQ_TIMER1, &moxaart_timer_irq);
moxaart_clocksource_init();
moxaart_clockevent_init();
}
請注意其中將會註冊一個clock source以及一個clock event。
- 如何初始化以及設定一個clock source
- 啓動該計時器,準備一個call back function讓系統可以隨時讀取讓計時器的值,最後呼叫一個kernel API clocksource_mmio_init()來設定一個clock source, 以下為範例程式
static cycle_t moxaart_clocksource_read(struct clocksource *c)
{
return ~moxaart_timer_get_counter(CLOCK_TIMER);
}
static void __init moxaart_clocksource_init(void)
{
moxaart_timer_set_reload(CLOCK_TIMER, MAX_COUNTER);
moxaart_timer_set_counter(CLOCK_TIMER, MAX_COUNTER);
moxaart_timer_enable(CLOCK_TIMER);
clocksource_mmio_init(NULL, "timer2", APB_CLK, CLOCK_RATING, 32, moxaart_clocksource_read);
}
請注意在讀取計時器的值call back function中的return值,若讓計時器是遞減的值則必須反相加一個 ”~”, 若是遞加則不用。
- 如何初始化以及設定一個clock event
- 這個初始化較為繁複,首先你要準備一個資料結構struct clock_evelt_device, 以下為其範例
static struct clock_event_device clockevent_moxaart = {
.name = "timer1",
.features = CLOCK_EVT_FEAT_PERIODIC|CLOCK_EVT_FEAT_ONESHOT,
.irq = IRQ_TIMER1,
.rating = CLOCK_RATING,
.shift = CLOCK_SHIFT,
.set_mode = moxaart_set_mode,
.set_next_event = moxaart_set_next_event,
};
其中的features設定CLOCK_EVT_FEAT_PERIODIC, 是較為傳統的timer功能,就是會一直產生的固定時間,也就是以前所謂的tick功能,而CLOCK_EVT_FEAT_ONESHOT的功能較為新的設計,若要有high resolution (至nanosecond等級),則必須有這個功能,意思就是timer可以只產生一次到的interrupt,而不是前面所提的periodic一直產生的timer interrupt。另外對shift成員的設定為一個為2的幾次方值,如32則表示所有的timer counter值將會先乘於2的32次方。
- 接下需要準備ISR, 其設定如下
static struct irqaction moxaart_timer_irq = {
.name = "timer1",
.flags = IRQF_DISABLED|IRQF_TIMER|IRQF_TRIGGER_FALLING|IRQF_IRQPOLL,
.handler = moxaart_timer_interrupt,
.dev_id = &clockevent_moxaart
};
請注意其成員dev_id為上面所宣告的clockevent_moxaart。
- 在clock_event_device中的成員set_mode call back function則是在設定該timer是要使用何種方,以下為其寫法
static void moxaart_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
{
switch ( mode ) {
case CLOCK_EVT_MODE_PERIODIC:
moxaart_timer_set_reload(USED_TIMER, LATCH);
moxaart_timer_set_counter(USED_TIMER, LATCH);
moxaart_timer_enable(USED_TIMER);
break;
case CLOCK_EVT_MODE_ONESHOT:
moxaart_timer_set_reload(USED_TIMER, 0);
moxaart_timer_set_match1(USED_TIMER, 0);
timer_ctrl_reg_value &= ~(TIMER1_INT_ENABLE);
timer_ctrl_reg_value |= (TIMER1_ENABLE|TIMER1_USED_PCLK);
*timer_ctrl_reg = timer_ctrl_reg_value;
break;
case CLOCK_EVT_MODE_RESUME:
moxaart_timer_enable(USED_TIMER);
break;
case CLOCK_EVT_MODE_SHUTDOWN:
case CLOCK_EVT_MODE_UNUSED:
default:
moxaart_timer_disable(USED_TIMER);
break;
}
}
請注意其模式中的periodic和oneshot定義的不同,在oneshot的模式中會將reload的值設為零。
- 而對於set_next_event的call back function,則是只是設定讓計時器的reload值
static int moxaart_set_next_event(unsigned long evt, struct clock_event_device *unused)
{
moxaart_timer_set_reload(USED_TIMER, evt);
return 0;
}
- 對於ISR的寫法如下
static irqreturn_t moxaart_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt=dev_id;
*(volatile unsigned int *)(MOXAART_TIMER_VA_BASE+TIMER_INTR_STATE) = 0;
evt->event_handler(evt);
return IRQ_HANDLED;
}
對於timer interrupt的寫法其實很制式,大家都一樣。
- 最後則是clock event的初始化,其寫法如下
static void __init moxaart_clockevent_init(void)
{
clockevent_moxaart.mult = div_sc(APB_CLK, NSEC_PER_SEC, clockevent_moxaart.shift);
clockevent_moxaart.max_delta_ns = clockevent_delta2ns(0xffffffff, &clockevent_moxaart);
clockevent_moxaart.min_delta_ns = clockevent_delta2ns(0xf, &clockevent_moxaart);
clockevent_moxaart.cpumask = cpumask_of(0);
clockevents_register_device(&clockevent_moxaart);
}
- 在arch/arm/Kconfig的有關自己CPU的選項中必須有以下的設定
select GENERIC_CLOCKEVENTS
select CLKSRC_MMIO
以下是一個範例:
config ARCH_MOXAART
bool "MOXA ART CPU"
select CPU_FA526
select USB_ARCH_HAS_EHCI
select MIGHT_HAVE_PCI
select ARCH_REQUIRE_GPIOLIB
select GENERIC_GPIO
select GENERIC_CLOCKEVENTS
select CLKSRC_MMIO
help
Support for MOXA ART CPU
附錄
Kernel APIs
/**
* clocksource_mmio_init - Initialize a simple mmio based clocksource
* @base: Virtual address of the clock readout register
* @name: Name of the clocksource
* @hz: Frequency of the clocksource in Hz
* @rating: Rating of the clocksource
* @bits: Number of valid bits
* @read: One of clocksource_mmio_read*() above
*/
int __init clocksource_mmio_init(void __iomem *base, const char *name,
unsigned long hz, int rating, unsigned bits,
cycle_t (*read)(struct clocksource *))
沒有留言:
張貼留言