Linux時間子系統之(十七):ARM generic timer驅動代碼分析


專題文檔匯總目錄

 Notes:ARM平台Clock/Timer架構;System counter、Timer以及兩者之間關系;Per cpu timer通過CP15訪問,System counter通過memory mapped IO訪問;將System counter和Per cpu timer分別作為clocksource和clock event device注冊到Linux時間子系統。

原文地址:Linux時間子系統之(十七):ARM generic timer驅動代碼分析

 

一、前言

關注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驅動工程師應該會注意到timer硬件的演化過程。在單核時代,各個SOC vendor廠商購買ARM core的IP,然后自己設計SOC上的peripherals,這里面就包括了timer的硬件。由於沒有統一的標准,各個廠商的設計各不相同,這給驅動工程師帶來了工作量。然而,如果僅僅是工作量的話就還好,實際上,不僅僅如此。linux的時間子系統要求硬件timer提供下面兩種能力:一是free running的counter,此外需要能夠在指定的counter值上產生中斷的能力。有些硬件廠商會考慮到軟件的需求(例如:PXA270的timer硬件),但是有些硬件廠商做的就不夠,例如:S3C2451的timer硬件。我們在寫PXA270的timer硬件驅動的時候是毫無壓力的,而在寫S3C2451的timer的驅動的時候,最大的願望就是把三星的HW timer的設計人員拉出來打一頓。

進入多核時代后,ARM公司提供了timer的硬件設計,集成在了自己的多核結構中。例如:在Cortex A15 MPcore的硬件體系結構中有一個HW block叫做Generic Timer(該硬件取代了A9中的global timer、private timer的功能),為系統提供了計時以及觸發timer event的功能。

本文主要描述了Generic Timer的相關硬件知識以及在linux kernel中如何驅動該硬件。Generic Timer的代碼位於linux-3.14/drivers/clocksource/目錄下,該目錄保存了所有clock source相關的driver,arm_arch_timer.c就是驅動Cortex A15 MPcore的Generic Timer的。

二、硬件描述

1、block diagram

ARM generic timer相關的硬件block如下圖所示(用綠色標記):

gtimer

ARM generic timer的硬件block主要是SOC上的System counter(多個process共享,用來記錄時間的流逝)以及附着在各個processor上的Timer(用於觸發timer event)組成,其他的generic timer的硬件電路主要是用來進行交流generic time event的。例如各個processor中的timer和system counter外設進行交互,各個processor中的timer進行信息交互。System counter的功能很簡單,就是計算輸入時鍾已經過了多少個clock,開始的時候是0,每一個clock,System counter會加一。System counter的counter value需要分發到各個timer中,也就是說,從各個timer的角度看,system counter value應該是一致的。Timer其實就是定時器,它可以定義一段指定的時間,當時間到了,就會assert一個外部的輸出信號(可以輸出到GIC,作為一個interrupt source)。

從power domain來看,ARM generic timer分成兩個部分:System counter和各個Multiprocessor系統中的Timer_x、接口電路等。之所以這么分原因很明顯:功耗方面(電源管理)的考量。在power saving mode下,可以shutdown各個processor系統的供電,但是可以保持system counter的供電,這樣,至少系統時間可以保持住。

和power domain類似,clock domain也是不同的,system counter和processor工作在不同的clock下,軟件修改了CPU的頻率也不會影響system counter的工作節奏,從而也不會改變timer的行為。

Notes:System counter提供一個計數,分發到各個timer中。Timer定時器基於System counter計數值,超時后觸發一個信號到GIC,超時函數進行處理。考慮到功耗,各CPU專屬的Timer、電路可以被單獨關閉;而System counter保持運行。

2、System counter

關於System Counter的規格整理如下:

規格 描述
System counter的計數器有多少bit? 至少56個bit,和具體的實現相關。
輸入頻率的范圍為何? 典型值是1M~50MHz
操作模式為何? 正常的時候,每個clock都會加一,但是,在power saving mode的時候,我們希望功耗可以更低一些,這時候會考慮將system clock的輸入頻率降低下來,例如降低4倍。為了保證system counter的計時是准確的,可以設定每個clock增加4,而不是加1,這樣system counter的計時仍然保持准確。
溢出時間 至少40年
精度要求為何? 未規定,推薦是每24小時在正負10秒的誤差
reset值為何? 0
是否支持debug? 可以設定enable halt-on-debug。這個和JTAG調試相關。如果你使用JTAG調試,在單步運行的時候當然系統counter停下來,否則timer的中斷就會來了,導致你無法正常的進行程序代碼跟蹤。

除了基本的計時功能,system count還提供了event stream的功能。我們知道,ARMv7的處理器提供了wait for event的機制,該機制允許processor進入low power state並等待event的到來。這個event可能是來自另外的process的send event指令,也可能是外部HW block產生的event,比如來自system counter的wake-up event。軟件可以配置system counter產生周期性的event,具體可以配置的參數包括:

(1)指定產生event的bit。我們可以選擇system counter中的低16bit。

(2)選定的bit當發生0到1的遷移(或是1到0的遷移)產生event

經過配置后,實際上system counter產生的是一個event stream,event產生的頻率是由選定的bit位置決定的。設定bit 0會產出頻率非常高的event stream,而設定15bit會產生頻率最慢的event stream,因為system counter的值不斷累加,直到bit 15發生翻轉才會觸發一個event。

3、Timers

各個cpu的timer是根據system counter的值來觸發timer event的,因此,系統中一定有一個機制讓System counter的值廣播到各個CPU的timer HW block上,同時運行在各個processor上的軟件可以通過接口獲取System counter的值。

處理器可以通過CNTPCT寄存器來獲取system counter的當前值,我們稱之physical counter。有physical就有virtual,processor可以通過CNTVCT寄存器訪問virtual counter,不過,對於不支持security extension和virtualization extension的系統,virtual counter和physical counter是一樣的值。

系統中每個processor都會附着多個timer,具體如下:

(1)對於不支持security extension的SOC(不支持security extension也就意味着 不支持virtualization extension),timer實際上有兩個,一個是physical timer,另外一個是virtual timer。雖然有兩個,不過從行為上看,virtual timer和physical timer行為一致

(2)對於支持security extension但不支持virtualization extension的SOC,每個cpu有三個timer:Non-secure physical timer,Secure physical timer和virtual timer

(3)對於支持virtualization extension的SOC,每個cpu有四個timer:Non-secure PL1 physical timer,Secure PL1 physical timer,Non-secure PL2 physical timer和virtual timer

每個timer都會有三個寄存器(我們用physical timer為例描述):

(1)64-bit CompareValue register。該寄存器配合system counter可以實現一個64 bit unsigned upcounter。如果physical counter - CompareValue >= 0的話,觸發中斷。也就是說,CompareValue register其實就是一個64比特的upcounter,設定為一個比當前system counter要大的值,隨着system counter的不斷累加,當system counter value觸及CompareValue register設定的值的時候,便會向GIC觸發中斷。

(2)32-bit TimerValue register。該寄存器配合system counter可以實現一個32 bit signed downcounter(有的時候,使用downcounter會讓軟件邏輯更容易,看ARM generic timer的設計人員考慮的多么周到)。開始的時候,我們可以設定TimerValue寄存器的值為1000(假設我們想down count 1000,然后觸發中斷),向該寄存器寫入1000實際上也就是設定了CompareValue register的值是system counter值加上1000。隨着system counter的值不斷累加,TimerValue register的值在遞減,當值<=0的時候,便會向GIC觸發中斷

(3)32-bit控制寄存器。該寄存器主要對timer進行控制,具體包括:enable或是disable該timer,mask或者unmask該timer的output signal(timer interrupt)

各個processor的各個Timer都可以產生中斷,因此它和GIC有接口。當然,由於timer的中斷是屬於各個CPU的,因此使用PPI類型的中斷,具體可以參考GIC文檔。當然,如果讓timer觸發中斷,當然要確保該timer是enable並且是umask的。

4、軟件編程接口

由上面的描述可知,ARM generic timer的硬件包括兩個部分:一個是per cpu的timer硬件,另外一個就是system level的counter硬件。對於per cpu的timer硬件,使用system control register(CP15)來訪問是最合適的,而且速度也快。要訪問system level的counter硬件,當然使用memory mapped IO的形式(請注意block diagram中的APB總線,很多system level的外設都是通過APB訪問的)。

三、初始化

1、Generic Timer的device node和Generic Timer clocksource driver的匹配過程

(1)clock source driver中的聲明

在linux/include/linux/clocksource.h目錄下的clocksource.h文件中定義了CLOCKSOURCE_OF_DECLARE宏如下:

#define CLOCKSOURCE_OF_DECLARE(name, compat, fn)            \
    static const struct of_device_id __clksrc_of_table_##name    \
        __used __section(__clksrc_of_table)            \
         = { .compatible = compat,                \
             .data = (fn == (clocksource_of_init_fn)NULL) ? fn : fn }

CLOCKSOURCE_OF_DECLARE這個宏其實就是初始化了一個struct of_device_id的靜態常量,並放置在__clksrc_of_table section中。arm_arch_timer.c文件中使用CLOCKSOURCE_OF_DECLARE這個宏定義了若干個靜態的struct of_device_id常量,如下:

CLOCKSOURCE_OF_DECLARE(armv7_arch_timer, "arm,armv7-timer", arch_timer_init);
CLOCKSOURCE_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_init);

CLOCKSOURCE_OF_DECLARE(armv7_arch_timer_mem, "arm,armv7-timer-mem",
               arch_timer_mem_init);

這里compatible的名字使用了armv7、armv8這樣的字樣而不是Cortex A15,我猜測ARM公司是認為這樣的generic timer的硬件block是ARMv7或者v8指令集的特性,所有使用這些指令集的core都應該使用這樣的generic timer的硬件結構。不論是v7還是v8,其初始化函數都是一個arch_timer_init。從這個角度看,把ARM的generic timer的驅動放到drivers的目錄下更合理(原來是放在arch目錄下),這樣多個arch(ARM和ARM64)可以共享一個ARM ARCH timer的驅動程序。

這里還有一個疑問是:"arm,armv7-timer"和"arm,armv7-timer-mem"有什么不同?實際上訪問ARM generic timer有兩種形式,一種是通過協處理器CP15訪問timer的寄存器,我們稱之CP15 timer。另外一種是通過寄存器接口訪問timer,也就是說,generic timer的控制寄存器被memory map到CPU的地址空間,這種我們稱之memory mapped timer。arch_timer_mem_init是for memory mapped timer類型的驅動初始化的,arch_timer_init是for CP15 timer類型的驅動進行初始化的。

Travelhop同學在他的程序員的“紀律性”文章中說到:有技術追求的年輕人要多問幾個為什么?因此,我們這里再追問一個問題:為何要有CP15 timer和memory mapped timer呢?都能完成對ARM generic timer的控制,為什么要提供兩種方式呢?其實最開始的時候,driver只支持CP15 type的timer訪問形態,畢竟這種方式比memory mapped register的訪問速度要更快一些。但是,這種方式不能控制system level的counter硬件部分(只能使用memory mapped IO形式訪問),因此功能受限。比如:system counter可以提供一組frequency table,可以讓軟件設定當然counter的輸入頻率以及每個clock下counter增加的數目。這樣的設定可以讓system counter的硬件在不同的輸入頻率下工作,有更好的電源管理特性。

此外,有些系統不支持協處理的訪問,這種情況下又想給系統增加ARM generic timer的功能,這時候必須使用memory mapped register的方式來訪問ARM generic timer的所有硬件block(包括system counter和per cpu的timer)。這時候,在訪問timer硬件的時候雖然性能不佳,但總是好過功能喪失。

在linux kernel編譯的時候,你可以配置多個clocksource進入內核,編譯系統會把所有的CLOCKSOURCE_OF_DECLARE宏定義的數據放入到一個特殊的section中(section name是__clksrc_of_table),我們稱這個特殊的section叫做clock source table。這個table也就保存了kernel支持的所有的clock source的ID信息(最重要的是驅動代碼初始化函數和DT compatible string)。我們來看看struct of_device_id的定義:

struct of_device_id
{
    char    name[32];------要匹配的device node的名字
    char    type[32];-------要匹配的device node的類型
    char    compatible[128];---匹配字符串(DT compatible string),用來匹配適合的device node
    const void *data;--------對於clock source,這里是初始化函數指針
};

這個數據結構主要被用來進行Device node和driver模塊進行匹配用的。從該數據結構的定義可以看出,在匹配過程中,device name、device type和DT compatible string都是考慮的因素。更細節的內容請參考__of_device_is_compatible函數。

(2)device node

一個示例性的Generic Timer(CP15 type的timer)的device node(我們以瑞芯微的RK3288處理器為例)定義如下:

timer {
                compatible = "arm,armv7-timer";
                interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>,
                             <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>,
                             <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>,
                             <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
                clock-frequency = <24000000>;
        };

Generic Timer這個HW block的Device node中定義了各種屬性,其中就包括了System counter的輸入clock frequency,中斷資源描述等信息。compatible 屬性用於驅動匹配的,在系統啟動的時候,系統中的所有的device node形成一個樹狀結構,在clock source初始化的時候進行device node和driver匹配(compatible 字符串的比對),device node攜帶的信息會在初始化的時候傳遞給具體的驅動。該節點的各個屬性的具體含義后面會詳細描述。

MMIO type的timer的device node(我們以高通的msm8974處理器為例)定義如下:

timer@f9020000 {
    #address-cells = <1>;
    #size-cells = <1>;
    ranges;
    compatible = "arm,armv7-timer-mem";
    reg = <0xf9020000 0x1000>;--------system counter的寄存器
    clock-frequency = <19200000>;

    frame@f9021000 {-----------percpu的timer的定義,目前最大支持8個frame
        frame-number = <0>;
        interrupts = <0 8 0x4>, -------physical timer的中斷
                 <0 7 0x4>; ----------virtual timer的中斷
        reg = <0xf9021000 0x1000>,-----first view base address
              <0xf9022000 0x1000>;------second view base address

    };

……

   };

(3)device node和clock source driver的匹配

在系統初始化的時候start_kernel函數會調用time_init進行時間子系統的初始化,代碼如下:

void __init time_init(void)
{
    if (machine_desc->init_time) {
        machine_desc->init_time();
    } else {
#ifdef CONFIG_COMMON_CLK
        of_clk_init(NULL);
#endif
        clocksource_of_init();
    }
}

clock source的初始化有兩種形態,一種是調用machine driver的init_time函數,另外一種是調用clocksource_of_init,使用device tree形式的初始化。具體使用哪種形態的初始化是和系統設計相關的,我們這里來看看device tree形式的初始化,畢竟device tree是未來的方向。具體代碼如下:

void __init clocksource_of_init(void)
{
    struct device_node *np;
    const struct of_device_id *match;
    clocksource_of_init_fn init_func;
    unsigned clocksources = 0;

    for_each_matching_node_and_match(np, __clksrc_of_table, &match) {
        if (!of_device_is_available(np))
            continue;

        init_func = match->data;
        init_func(np);
        clocksources++;
    }
    if (!clocksources)
        pr_crit("%s: no matching clocksources found\n", __func__);
}

__clksrc_of_table就是內核的clock source table,這個table也就保存了kernel支持的所有的clock source driver的ID信息(用於和device node的匹配)。clocksource_of_init函數執行之前,系統已經完成了device tree的初始化,因此系統中的所有的設備節點都已經形成了一個樹狀結構,每個節點代表一個設備的device node。clocksource_of_init是針對系統中的所有的device node,掃描clock source table,進行匹配,一旦匹配到,就調用該clock source driver的初始化函數,並把該timer硬件的device node作為參數傳遞給clocksource driver。

2、CP15 Timer初始化代碼分析

CP15 Timer初始化代碼如下所示:

static void __init arch_timer_init(struct device_node *np)
{
    int i;

    if (arch_timers_present & ARCH_CP15_TIMER) {----------------(1)
        pr_warn("arch_timer: multiple nodes in dt, skipping\n");
        return;
    }

    arch_timers_present |= ARCH_CP15_TIMER;

for (i = PHYS_SECURE_PPI; i < MAX_TIMER_PPI; i++)--------------(2)
        arch_timer_ppi[i] = irq_of_parse_and_map(np, i);

arch_timer_detect_rate(NULL, np); ------------------------(3)

    if (is_hyp_mode_available() || !arch_timer_ppi[VIRT_PPI]) {-------------(4)
        arch_timer_use_virtual = false;

        if (!arch_timer_ppi[PHYS_SECURE_PPI] ||
            !arch_timer_ppi[PHYS_NONSECURE_PPI]) {
            pr_warn("arch_timer: No interrupt available, giving up\n");
            return;
        }
    }

    arch_timer_register(); --------------------------(5)
    arch_timer_common_init(); ------------------------(6)
}

(1)arch_timers_present用來記錄系統中的timer情況,定義如下:

#define ARCH_CP15_TIMER    BIT(0)
#define ARCH_MEM_TIMER    BIT(1)
static unsigned arch_timers_present __initdata;

該變量只有兩個bit有效,bit 0標識是否有CP15 timer,bit 1標識memory mapped timer是否已經初始化。

如果在調用arch_timer_init之前,ARCH_CP15_TIMER已經置位,說明之前已經有一個ARM arch timer的device node進行了初始化的動作,這多半是由於device tree的database中有兩個或者多個cp15 timer的節點,這時候,我們初始化一個就OK了。

(2)這部分的代碼是分配IRQ。ARM generic timer使用4個PPI的中斷,對於Cortex A15,和timer相關的PPI包括:

Secure Physical Timer event (ID 29,也就是上面device node中的13,29 = 16 + 13)
Non-secure Physical Timer event (ID 30,也就是上面device node中的14,30 = 16 + 14)
Virtual Timer event (ID 27)
Hypervisor Timer event (ID 26)

函數irq_of_parse_and_map對該device node中的interrupt屬性進行分析,並分配IRQ number,建立HW interrupt ID和該IRQ number的映射。irq_of_parse_and_map這個函數在中斷子系統中已經詳細描述過了,這里不再贅述。至此,arch_timer_ppi數組中保存了ARM generic timer使用IRQ number。

(3)arch_timer_detect_rate這個函數用來確定system counter的輸入clock頻率,具體實現如下:

static void arch_timer_detect_rate(void __iomem *cntbase, struct device_node *np)
{
    if (of_property_read_u32(np, "clock-frequency", &arch_timer_rate)) {
        if (cntbase)
            arch_timer_rate = readl_relaxed(cntbase + CNTFRQ);
        else
            arch_timer_rate = arch_timer_get_cntfrq();
    }
}

arch_timer_rate這個全局變量用來保存system counter的輸入頻率,基本上,這個數據有兩個可能的來源:

        (a)device tree node中的clock-frequency屬性

        (b)寄存器CNTFRQ

我們優先考慮從clock-frequency屬性中獲取該數據,如果device node中沒有定義該屬性,那么從CNTFRQ寄存器中讀取。訪問CNTFRQ寄存器有兩種形態,如果cntbase是NULL的話,說明是CP15 timer,可以通過協處理器來獲取該值(調用arch_timer_get_cntfrq函數)。如果給出了cntbase的值,說明是memory mapped的方式來訪問CNTFRQ寄存器(直接使用readl_relaxed函數)。

(4)如果沒有定義virtual timer的中斷(arch_timer_ppi[VIRT_PPI]==0),那么我們只能是使用physical timer的,這時候,需要設定arch_timer_use_virtual這個全局變量為false。arch_timer_use_virtual這個變量名字已經說明的很清楚了,它標識系統是否使用virtual timer。ok,既然使用physical timer,那么需要定義physical timer中斷,包括secure和non-secure physical timer event PPI。只要有一個沒有定義,那么就出錯退出了。

如果系統支持虛擬化,那么CPU會處於HYP mode,這時候,我們也是應該使用physical timer的,virtual timer是guest OS需要訪問的。

(5) arch_timer_register的代碼如下:

static int __init arch_timer_register(void)
{
    int err;
    int ppi;

    arch_timer_evt = alloc_percpu(struct clock_event_device);------------(a)

    if (arch_timer_use_virtual) {--------------------------(b)
        ppi = arch_timer_ppi[VIRT_PPI];
        err = request_percpu_irq(ppi, arch_timer_handler_virt,
                     "arch_timer", arch_timer_evt);
    } else {
        ppi = arch_timer_ppi[PHYS_SECURE_PPI];
        err = request_percpu_irq(ppi, arch_timer_handler_phys,
                     "arch_timer", arch_timer_evt);
        if (!err && arch_timer_ppi[PHYS_NONSECURE_PPI]) {
            ppi = arch_timer_ppi[PHYS_NONSECURE_PPI];
            err = request_percpu_irq(ppi, arch_timer_handler_phys,
                         "arch_timer", arch_timer_evt);
        }
    }

    err = register_cpu_notifier(&arch_timer_cpu_nb);----------------(c)

    err = arch_timer_cpu_pm_init();-----------------------(d)

    arch_timer_setup(this_cpu_ptr(arch_timer_evt)); ----------------(e)

    return 0;

}

(a)分配一個類型是struct clock_event_device的per cpu變量。struct clock_event_device是對一個能夠觸發timer event的設備進行抽象。對於ARM generic timer而言,每個CPU都有一個timer硬件block,就是一個clock event device。

(b)根據當前是使用physical timer還是virtual timer,分別注冊一個per cpu的IRQ。如果使用physical timer的話,需要注冊secure和non-secure physical timer event PPI。如果使用virtual timer的話,需要注冊virtual timer中斷。

(c)這里的代碼主要是formulti core系統的,用於non-BSP上的generic timer硬件的初始化,其概念類似GIC driver的初始化,這里就不再具體描述了。

(d)這里主要是注冊一個回調函數,在processor進入和退出low power state的時候會調用該回調函數進行電源管理相關的處理。

(e)初始化BSP上的timer硬件對應的clock event device,並調用clockevents_register_device函數將該clock event device注冊到linux kernel的時間子系統中。non-BSP的timer硬件的setup是通過event notifier機制完成的,具體請參考步驟c。

(6)CP15 timer和memory mapped timer雖然接口形態不一樣,但是總是有共同的部分,這些代碼被封裝到arch_timer_common_init函數中,具體如下:

static void __init arch_timer_common_init(void)
{
    unsigned mask = ARCH_CP15_TIMER | ARCH_MEM_TIMER; ---------(a)
    if ((arch_timers_present & mask) != mask) {
        if (of_find_matching_node(NULL, arch_timer_mem_of_match) &&
                !(arch_timers_present & ARCH_MEM_TIMER))
            return;
        if (of_find_matching_node(NULL, arch_timer_of_match) &&
                !(arch_timers_present & ARCH_CP15_TIMER))
            return;
    }

    arch_timer_banner(arch_timers_present);-----------------(b)
    arch_counter_register(arch_timers_present);----------------(c)
    arch_timer_arch_init();--------------------------(d)
}

(a)實際上,即便是系統中存在兩種timer,這個函數的代碼執行一次就OK了。這很好理解,例如arch_counter_register函數用來注冊system count,而實際上,無論是CP15 timer還是memory mapped的timer,system counter是system level的,只有一個,注冊一次就OK了。

明白了上面的思路后,這段代碼就比較簡單了。在系統中存在兩種timer的時候,要等到后一個timer初始化的時候再執行后面具體的arch_timer_banner到arch_timer_arch_init部分的代碼。

(b)輸出ARM generic timer的相關信息到控制台

(c)向linux kernel的時間子系統注冊clock source、timer counter、shed clock設備。

(d)主要是注冊delay timer(忙等待那種)。

3、memory mapped Timer初始化代碼分析

TODO

四、和linux kernel時間子系統的接口

linux的時間子系統需要兩種時間相關的硬件:一個是free running的counter(system counter),抽象為clock source device,另外一個就是能夠產生中斷的能力的timer(per cpu timer),抽象為clock event device。對於ARM generic timer driver而言,我們需要定義linux kernel時間子系統的clock source和clock event device並注冊到系統。

1、定義clocksource並注冊到系統

ARM generic timer中的system counter硬件block對應的clock source定義如下:

static struct clocksource clocksource_counter = {
    .name    = "arch_sys_counter",
    .rating    = 400,
    .read    = arch_counter_read,
    .mask    = CLOCKSOURCE_MASK(56),
    .flags    = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_SUSPEND_NONSTOP,
};

(這里順便吐槽一下clocksource_counter這個變量名,實在是太差了)rating標識該clock source的精度等級,數字越大,精度等級越高。read函數用來讀取當前counter的值。在ARM generic timer驅動初始化的過程中會調用arch_counter_register函數注冊該clock source

static void __init arch_counter_register(unsigned type)
{
    u64 start_count;

    if (type & ARCH_CP15_TIMER)--------------------(1)
        arch_timer_read_counter = arch_counter_get_cntvct;
    else
        arch_timer_read_counter = arch_counter_get_cntvct_mem;

    start_count = arch_timer_read_counter();
    clocksource_register_hz(&clocksource_counter, arch_timer_rate);------(2)
    cyclecounter.mult = clocksource_counter.mult;
    cyclecounter.shift = clocksource_counter.shift;
    timecounter_init(&timecounter, &cyclecounter, start_count); ---------(3)

    /* 56 bits minimum, so we assume worst case rollover */
    sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate);------(4)
}

(1)在定義ARM generic timer的clock source的時候,read函數被設定成arch_counter_read,該函數會調用arch_timer_read_counter 函數,而這個函數指針會在初始化的時候根據timer的類型進行設定。

(2)向系統注冊一個clock soure(也就是一個free running的counter),並給出counter的工作頻率作為傳入的參數。linux時間子系統的clock source模塊會根據counter的工作頻率設定struct clocksource的各個成員,例如mult和shitf等

(3)clocksource模塊是為timekeeping模塊提供服務的,但是其他的驅動模塊也有一些計時需求,這時候可以考慮使用timercounter。ARM generic timer靜態定義了一個timercounter的全局變量,其他模塊可以通過arch_timer_get_timecounter獲取timercounter,並可以調用timecounter_read獲取一個納秒值。

(4)TODO

2、定義clock_event_device並注冊到系統

和clocksource不同,ARM generic timer是由alloc_percpu動態分配的。考慮到system counter只有一個,而timer是附着在各個CPU上,這樣的分配也是合理的。在driver的初始化過程中(先是BSP初始化,然后其他CPU的初始化是通過event notifier機制完成),會調用arch_timer_setup來初始化clock_event_device數據結構並注冊到系統中

static int arch_timer_setup(struct clock_event_device *clk)
{
    __arch_timer_setup(ARCH_CP15_TIMER, clk); ----初始化clock event device並注冊到系統

    if (arch_timer_use_virtual)----------enable timer interrupt
        enable_percpu_irq(arch_timer_ppi[VIRT_PPI], 0);
    else {
        enable_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI], 0);
        if (arch_timer_ppi[PHYS_NONSECURE_PPI])
            enable_percpu_irq(arch_timer_ppi[PHYS_NONSECURE_PPI], 0);
    }

    arch_counter_set_user_access();
    if (IS_ENABLED(CONFIG_ARM_ARCH_TIMER_EVTSTREAM))---判斷是否enable了timer event
        arch_timer_configure_evtstream(); -------配置並enable timer event

    return 0;
}

TODO:這里等到完成clockevent文檔之后再來更新。

原創文章,轉發請注明出處。蝸窩科技

http://www.wowotech.net/linux_kenrel/arm-generic-timer.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM