2012年3月19日 星期一

Linux Kernel New HAL Timer Device Driver


前言
由於現在的作業系統對於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 *))