Linux时间子系统之(十七):ARM generic timer驱动代码分析

作者:linuxer 发布于:2014-12-2 10:47 分类:时间子系统

一、前言

关注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的行为。 

 

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

标签: armv7-timer arm_arch_timer

评论:

raceant
2018-06-27 20:05
请教下cp15和memory map的方式是不是硬件设计是就固定了? 如果硬件没有做map的话怎样通过读写内存就能访问CP15的寄存器呢? 而且system_counter不在core中,难道需要另外的driver? 正在研究这部分代码
raceant
2018-06-27 20:11
@raceant:@linuxer 帮忙解答一下不胜感激
奋斗
2018-04-03 23:28
arch_timer_handler_phys;
         timer_handler(ARCH_TIMER_PHYS_ACCESS, evt);
                    evt->event_handler(evt);
请问下,这里的event_handler在哪里赋值的,我看了代码,没有找到在哪里赋值呢。
misakamm20001
2017-09-14 00:53
hi linuxer 我翻了下代码没找到CONFIG_HZ是如何设定到local timer里去的 请问这部分设置是在哪里完成的?
misaka
2017-09-14 15:02
@misakamm20001:仔细看了一下似乎arm-timer feature并不支持PERIOD模式,所以应该是采用oneshot模式 利用tick_sched_timer进行模拟tick行为的吧?
linuxer
2017-09-15 09:21
@misaka:在__arch_timer_setup的代码中的确没有设定CLOCK_EVT_FEAT_PERIODIC,这说明的确arm-timer不支持PERIOD模式。

如果没有配置hrtimer,我记得在周期性tick的配置下,每次tick到来的时候都需要再次program next event,以便下次的定时中断会送达CPU。

如果配置了hrtimer,并且配置周期性tick,实际上周期性tick的到了是利用了一个sched_timer来模拟的,每次tick到来的时候,rearm该sched_timer就OK了。
misaka
2017-09-15 10:05
@linuxer:ok thanks.  那么请问如果是采用period mode的timer是不是有寄存器来设定产生中断的时间间隔?
cookii
2017-09-15 10:20
@misaka:这种和硬件有关的具体问题,就不要在这里问了吧?不然的话你让楼主怎么回答你呢??
misaka
2017-09-15 15:36
@cookii:只是交流一下而已  软件说到底还是为硬件服务 不搞清楚硬件行为是没有意义的 软件的流程我现在从上到下大概过了一遍 就顺便和linuxer聊聊硬件 linuxer经验丰富  能听他说几句  我也会受益匪浅   我并不是为了解决问题  如果只是解决问题问一大推问题大概不会比纯粹因为好奇想要深入了解更让回答者来的舒服的多。
linuxer
2017-09-18 09:12
@misaka:这个问题并不复杂,只要工作的时间足够长,就能接触各种各样的硬件情况。HW timer其实各家都设计不太一样,一个可能的设计是这样的:
1、有一个counter寄存器
2、有一个match寄存器
3、count寄存器是count down的
4、match寄存器加载到counter寄存器,然后会count down,0值的时候产生中断
5、可以配置产生中断的时候是否reload match寄存器的值。如果配置,那么就是periodic mode,否则是one-shot mode
misaka
2017-09-18 10:13
@linuxer:明白了,谢谢你
coldiceangel
2016-02-25 15:12
能否在(ARM Linux)内核捕获硬件引起的时钟中断?在内核“看起来”已经“死掉”且没有任何打印输出的情况下,有人告诉我最基本的时钟中断还是存在的,我怎么验证这一点?
郭健
2016-02-25 19:18
@coldiceangel:基本上我是不太同意你说的那句话的,因为“死掉”是一个非常模糊的词语。

如果你一定想要验证这一点,也很简单,timer中断handler里面 toggle一个GPIO不就可以了吗?然后示波器可以看到GPIO的波形
bsp
2016-04-01 16:12
@coldiceangel:也有可能是串口挂死吧,如果有网络的话 可以ping一下ip。
如果可以ping通,那么处理网卡的cpu本地中断是正常的;如果ping通但ssh连不上(确保sshd打开的情况下),那可能是cpu过于繁忙或者死锁了。
passerby
2016-01-06 22:42
@linuxer 对于arch_mem_timer还是有些疑惑,我看到高通里面是用一个best_frame来注册的timer,我打印/proc/interrupts看到有很多arch_mem_timer的SPI中断,但是我不知道这些中断是谁产生的?用来做什么的?
linuxer
2016-01-07 00:05
@passerby:其实我对高通的芯片根本不了解,呵呵~~因为从来没有做过。
不过,我想高通可能的设计是这样的:
1、通过system register接口可以访问那些per cpu的local timer
2、通过MMIO接口可以访问一个SOC级别的timer

你说你打印看到若干arch_mem_timer的SPI中断,那么我想这个arch_mem_timer也许就是上面的那个SOC级别的timer,当然,这个timer不是ARMv7或者ARMv8定义的generic timer的功能,应该是自己增加的设计。

上面都是猜的,完全不靠谱的,仅供参考
passerby
2016-01-07 11:24
@linuxer:@linuxer,你们这里的最新评论只能看到5条。有没有办法看到更多评论的。因为有的时候可能评论的人数比较多,就看不到5条之前的了。
wowo
2016-01-07 13:09
@passerby:页面上有一个评论列表的东西,你可以看看。
passerby
2016-01-07 13:27
@wowo:Thanks 看到这个之后,突然感觉世界变大了。看到了大家思想碰撞出的火花 0~0
schedule
2015-08-10 07:08
10-50M的频率,怎么计算出纳秒的精度呢?
linuxer
2015-08-10 08:59
@schedule:10MHz的clock,每一个cycle就是1/10MHz = 100ns,也就是说,counter的精度是以100ns为基础累加的,你不可能得到小于100ns的精度。不过,这也是远远超过之前的低精度timer,因为它是基于tick的,基本上是属于ms级别的
passerby
2015-07-13 17:00
@linuxer,有个关于system counter   local timer的问题。我以高通的代码作为例子
enum ppi_nr {
    PHYS_SECURE_PPI,
    PHYS_NONSECURE_PPI,
    VIRT_PPI,
    HYP_PPI,
    MAX_TIMER_PPI
};

35     timer {
36         compatible = "arm,armv7-timer";
37         interrupts = <1 2 0xff08>,
38                  <1 3 0xff08>,
39                  <1 4 0xff08>,
40                  <1 1 0xff08>;
41         clock-frequency = <19200000>;
42     };
高通以ppi中断的形式用request_percpu_irq注册了四个ppi中断,我想问这个注册四个具体作用是什么?我有点迷惑这个是global timer还是local timer,而且为什么要以PPI形式注册。

timer@b120000 {
142         #address-cells = <1>;
143         #size-cells = <1>;
144         ranges;
145         compatible = "arm,armv7-timer-mem";
146         reg = <0xb120000 0x1000>;
147         clock-frequency = <19200000>;
148
149         frame@b121000 {
150             frame-number = <0>;
151             interrupts = <0 8 0x4>,
152                      <0 7 0x4>;
153             reg = <0xb121000 0x1000>,
154                   <0xb122000 0x1000>;
155         };
156
157         frame@b123000 {
158             frame-number = <1>;
159             interrupts = <0 9 0x4>;
160             reg = <0xb123000 0x1000>;
161             status = "disabled";
162         };
163
164         frame@b124000 {
165             frame-number = <2>;
166             interrupts = <0 10 0x4>;
167             reg = <0xb124000 0x1000>;
168             status = "disabled";
169         };
................
而这几个按照你上面所说的MMIO注册的timer,这几个timer我能够理解是以SPI形式注册不同core的system counter和local timer,但对于cp15上面注册的4个PPI确实不太能理解原理,能解释下吗
passerby
2015-07-13 18:21
@passerby:还有点补充,如果是local timer,那CP15寄存器访问形式注册为PPI形式。那么每个CORE都会产生中断并且执行中断服务函数,local timer应该只会让它对应的CORE中断,而让其他CORE也中断?这里不太理解
passerby
2015-07-14 14:16
@passerby:@linuxer,能否解答下疑惑,却是有点不太理解。
linuxer
2015-07-14 18:17
@passerby:稍等,有一段时间没有看这些代码了,我需要恢复一下现场。
linuxer
2015-07-14 23:34
@passerby:高通以ppi中断的形式用request_percpu_irq注册了四个ppi中断,我想问这个注册四个具体作用是什么?
-------------------------------------------
要理解这些ppi的中断,需要理解虚拟化的概念,需要理解security的概念,这些都不是三言两语可以讲清楚的,更重要的是我也在消化理解中,呵呵~~~

关于local timer的解释
---------------------------
35     timer {
36         compatible = "arm,armv7-timer";
37         interrupts = <1 2 0xff08>,
38                  <1 3 0xff08>,
39                  <1 4 0xff08>,
40                  <1 1 0xff08>;
41         clock-frequency = <19200000>;
42     };
如果dts文件中包括上面这样的armv7-timer的device node,那么说明硬件系统包括system counter(这是全局性的counter)以及各个processor的local timer的硬件。各个local timer的中断只会送达本processor而不会送往其他的processor,因此注册的是PPI的中断。


memory map的timer
--------------------------------------
memory map的timer是全局性的timer(这里的全局是指对所有的processor都是可见的),因此该timer需要注册的是SPI类型的中断。
passerby
2015-07-16 10:01
@linuxer:谢谢linuxer这么晚了还回复我,大概懂了你的意思。
passerby
2015-05-26 13:28
我现在想做个实现,在arch_counter_register中我会改变arch_timer_rate。以使内核timekeeper的墙上时间变快或者变慢,达到欺骗效果。但是现在发现一个问题,我把arch_timer_rate减小时,系统跑起来是OK的。但是我增大arch_timer_rate时可能随时崩溃掉。我跟了下arch_counter_register函数,并没有发现对CLK有操作的东西,只是注册一个clocksource,但为什么对系统的影响会这么大呢。
passerby
2015-05-26 15:58
@passerby:@linuxer, 求教。
wowo
2015-05-26 16:59
@passerby:帮顶,估计linuxer同学又在做各种表格,哈哈。
linuxer
2015-05-26 19:05
@wowo:啥也别说了,泪牛满面~~~
linuxer
2015-05-26 19:04
@passerby:改动arch_timer_rate这个变量带来的影响很大的,影响了clocksource和clockevent模块,而实际上,这是内核中的基本服务,大量的其他内核模块都要使用clocksource和clockevent模块的,你怎么做实际上是动了地基,不建议如此操作。

我很奇怪,你为何要使内核timekeeper的墙上时间变快或者变慢,用来欺骗谁呢?这是一个什么功能?
passerby
2015-05-26 20:46
@linuxer:实现跑分欺骗功能,希望通过clocksource的方法来实现。但是看起来比较奇怪,我虽然动了地基,但是让时间变快就OK,但是让时间变慢就会出现直接死机重启。所以觉得很奇怪。
linuxer
2015-05-27 08:39
@passerby:跑分程序应该是运行在用户空间吧,如果这样,可以考虑修改时间子系统中用户空间接口那一部分的模块,不要动底层的代码。
passerby
2015-05-27 09:03
@linuxer:因为原来实现可以通过伪造一个比较慢的clocksource,当检测到跑分程序启动就切换clocksource,这样就能将系统时间慢下来。但是现在我想直接改下clocksource却发现不行,所以感觉是不是代码有所变化。
RobinHsiang
2015-05-27 10:29
@passerby:为什么不把这些精力花到系统的优化上面呢?
我是不是问了一个好笑而又愚蠢的问题?
linuxer
2015-05-27 10:48
@RobinHsiang:同意!还是正面迎击问题比较好
passerby
2015-05-27 11:01
@RobinHsiang:是客户需要手机跑到一个SOC厂家标称的CPU分数,但是这个分数是SOC厂商的比较好条件下的性能效果分数。由于材质、散热、做工各方面的问题,手机无法达到那么好的效果。但是客户又不接受的你说法,一定要达到那个分数。这个时候就像用些歪门邪道来做点手脚。
buyit
2015-04-29 10:15
@linuxer
你好,有些概念我看了之后还是觉得有点模糊,麻烦你帮忙解答一下,多谢。抱歉我对这一块的理解有欠缺,似懂非懂,要真正做一遍才能彻底领悟,目前的问题本身也许也存在问题,请你包涵。

我们的SOC采用一个单核的A5,由于A5单核模式下面不支持ARM的generic timer架构,于是只能自己去搭这个timer的架构。目前是用一个56 bit的的system counter放在always-on里面,这个counter一直不停下,如果要读取的话是通过mem map方式读取。由于A5 core里面没有集成timer,因此在SOC级别放了一个32 bit的timer,同样通过mem map方式读取和配置timer。下面的这些问题我没有想明白:
1. 在我们自己的架构下,arm_arch_timer.c 是不是就不能直接用了?  比如timer肯定不能直接使用这个模块的代码,因为我们用的不是ARM的timer IP,因此寄存器不一样。
2. system counter我准备按照ARM的代码创建:
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,
};

上面是照抄的,我觉得应该不需要改动什么,自己实现一下read函数就行了。

3. timer,这个需要去实现clock_event_device结构体,我准备照抄kernel里面tegra现成的:
static struct clock_event_device tegra_clockevent = {
.name  = "timer0",
.rating  = 300,
.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC,
.set_next_event = tegra_timer_set_next_event,
.set_mode = tegra_timer_set_mode,
};

CLOCK_EVT_FEAT_PERIODIC这个flag需要置位吗?ARM的代码里面似乎没有置位。假设我不主动置位,会影响系统的什么特性吗?

CLOCK_EVT_FEAT_C3STOP这个flag是否需要置位?我看ARM把cp15类型的per cpu timer置位了这个flag,但是其它mem map的timer没有置位,我们用的是mem map类型的,所以这个不需要置位。
还有个问题:这个timer的精度只有300,在我们SOC里面全靠它来更新ktime,这样的架构是否有问题?是否有可能改进设计?

4. 我看到kernel里面有关于broadcast timer的代码,在我们的SOC里面只有单核A5,是否完全不需要考虑这一块的设计?当它不存在就可以了?下面两个config不知道是否需要打开?
CONFIG_ARCH_HAS_TICK_BROADCAST=y
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y

5. 单核的A5, timer不在core里面,假设用了no_hz,那么如果进入cpuidle,这个timer是关还是开呢?我觉得必须开着,否则下一个timer中断就不会过来了因为timer关闭了。

6.单核A5, timer不在core里面,假设用了no_hz,那么如果进入suspend,这个timer是关还是开呢?我觉得可以关闭,因为我们的system counter放在always-on里面,系统resume的时候可以通过读取system counter来补偿ktime。并且kernel suspend的时候普通的软件timer是不可能唤醒系统的,所以这个timer可以关闭。

7. 假设这是别家的芯片,多核A5, SMP,并且采用了ARM generic timer的架构,那么它是怎么运作的? 当cpu3进入cpuidle的时候,cpu3的core被clk gating了,那么cpu3的timer可能也被停止了,这时候是需要借助同一个cluster里面其它的cpu来给cpu3 broadcast timer中断吗? 这里涉及到一个设计逻辑:在cpu3进入cpuidle之前,cpu3是否会判断和计算一下下一个到期的timer,如果下一个timer很快到期,cpu3有两种选择,要么完全依赖于cpu0~cpu2的broadcast timer irq,直接idle。要么干脆不idle了先处理完下一个timer事件再说。我猜应该是前一种,甚至可以理解为cpu3执行idle代码的时候不会判断到期的timer,直接发出一个broadcast timer的广播,告诉其它cpu接管timer事件,然后cpu3直接idle。

8. broadcast这种行为,是由同一个cluster里面的其它cpu利用自身的cp15 timer的中断来广播给已经idle的cpu,还是全局的mem map的timer利用自身的timer中断来广播给所有的cpu?我觉得后者的可能性更高一些,假设一个cluster里面有cpu0~cpu3,假设cpu3进入cpuidle并且timer停止了,那么cpu0, cpu1, cpu2的timer仍然在运行中不受影响,一个全局的具有broadcast功能的timer会负责记录cpu3上面的下一个唤醒事件,等触发时间到来的时候,全局timer负责唤醒cpu3,这个过程中cpu0, cpu1, cpu2并不会接收这个广播事件,因为它们自身的cpu timer运行良好并不需要全局timer的帮助。理解是否正确?

9. cpuidle的时候per-cpu的timer是否关闭是否要看进入idle的级别?如果是最浅的级别,cpu简单执行了WFI,那么这种情况下per-cpu的timer应该不会停下,所以下一个event到期了之后cpu可以顺利被per-cpu timer唤醒。 如果进入的idle级别很深,肯定会关闭per-cpu timer, 这时候对系统架构来说是不是必须有一个全局的broadcast timer存在?否则的话这个idle的cpu就不能正常处理tick了,是不是会有问题? 有没有可能其它没有idle的cpu通过IPI来广播timer event给这个已经idle的cpu?

问题有点多,有点长,不好意思。期待你的耐心解答,先谢过了 :)
linuxer
2015-04-29 12:56
@buyit:在我们自己的架构下,arm_arch_timer.c 是不是就不能直接用了?  比如timer肯定不能直接使用这个模块的代码,因为我们用的不是ARM的timer IP,因此寄存器不一样。
---------------------
的确是不能直接使用了。不过听你的描述,你们是A5单核系统,因此cpu core中没有local timer,这时候,system counter和HW timer都集成在SOC上的一个HW block中,这时候使用arm_arch_timer.c作为参考其实不是那么适合,建议使用PXA系列的timer程序做参考就OK了。

CLOCK_EVT_FEAT_PERIODIC这个flag需要置位吗?ARM的代码里面似乎没有置位。假设我不主动置位,会影响系统的什么特性吗?
-----------------------------
这个flag和底层硬件是否有周期性产生timer中断相关,如果硬件有这个能力,当然要设定并且在clock event device中实现set_mode这个callback函数。如果不设定这个标记也没有关系,这也意味着底层的hw timer只是支持one shot mode,那么tick device layer会帮你用one shot mode来模拟周期性的timer中,具体参考tick_setup_periodic代码

CLOCK_EVT_FEAT_C3STOP设定问题
----------------------
其实我的文档中已经描述过这个问题。这个是和cpu idle的实现有关,如果你们打算设计的cpu idle状态不会导致HW timer停止工作就不需要设定这个bit。

还有个问题:这个timer的精度只有300,在我们SOC里面全靠它来更新ktime,这样的架构是否有问题?是否有可能改进设计?
-------------------------------
timer的rating是300不意味timer的精度就是300,实际timer的精度是和system counter以及HW timer的输入的clock设定相关的

我看到kernel里面有关于broadcast timer的代码,在我们的SOC里面只有单核A5,是否完全不需要考虑这一块的设计?当它不存在就可以了?下面两个config不知道是否需要打开?
----------------------
没有CLOCK_EVT_FEAT_C3STOP的问题则不需要打开

单核的A5, timer不在core里面,假设用了no_hz,那么如果进入cpuidle,这个timer是关还是开呢?我觉得必须开着,否则下一个timer中断就不会过来了因为timer关闭了。
----------------------------
设计是平衡的艺术。打开timer、功耗增加,关闭timer、虽然降低功耗,但是会增加软件的复杂度(需要进行特别的设计)。你问的这个问题是一个SOC设计的问题,不是一个时间子系统的软件设计问题,当然,最终的答案来自你们的产品需求。


其他问题改天再回答吧,^_^
buyit
2015-04-29 14:05
@linuxer:@linuxer : 非常干你你的耐心回答。
关于CLOCK_EVT_FEAT_C3STOP的设置,我的理解是:假设我们系统只有一个A5 core,SOC里面假设有5个timer。那么在cpu idle的时候就算我把和A5绑定的那个local timer关闭了,我仍然要让另外一个timer来做广播,似乎多次一举,还不如直接让A5的local timer一直运行着,毕竟在我们的架构下面local timer就是普通的SOC timer,不存在哪一个更省电的问题。这样子我在定义clock event device的时候不应该设置CLOCK_EVT_FEAT_C3STOP这个flag,同时也不应该开启那两个broadcast的config,
不知道理解是否正确?
期待你对我其它问题的解答 :)
buyit
2015-04-29 14:06
@linuxer:非常感谢你的耐心解答,笔误。。。
linuxer
2015-04-30 00:34
@linuxer:我发现你其他的问题都是和tick broadcast framework相关,那我还是用一份文档来完整的描述它好了。
顺便聊几句关于CPU idle设计的问题。这个问题实际上是一个系统问题,需要多个工种来协调。从系统软件的角度来看,当然希望能够在cpu idle的时候保持timer的运作,这样软件复杂度比较简单,但是如果设计的CPU本身需要提供极低功耗的特性,那么在执行cpu idle的时候,有可能会关闭更多的HW block以便节省功耗,这时候,软件team只能妥协了。
buyit
2015-04-30 22:16
@linuxer:期待broadcast的文章赶快面世。
对这个问题我仍然有疑惑:在只有一个A5 core的SOC里面假设有5个timer。假设我把其中一个timer配置成了A5 的local timer,假设HW团队也不反对,我在定义clock event device的时候不设置CLOCK_EVT_FEAT_C3STOP这个flag,同时也不开启那两个broadcast的config,在cpu idle的时候和A5绑定的那个local timer仍然运行,系统中另外的4个timer都没有用来做广播,请问这样子的设计是不是对软硬件都最好的设计?
linuxer
2015-05-03 22:39
@buyit:其实你没有给定具体的应用场景,我不敢说是“对于软硬件最好的设计”,可以肯定的说:你的方案是一个比较通用的设计。

假设你的处理器想使用在传感器网络的,这样场景一般是使用纽扣电池的,而且你期望你的CPU可以在一个纽扣电池的情况下,可以使用1~2年的时候,这时候,所有的设计都是围绕的如何降低功耗来进行,这时候,你是不是想要在idle的时候turn off那个HW timer?当然,我这里描述的内容可能不符合你说的场景,我只是举一个例子而已
buyit
2015-05-04 10:17
@linuxer:你猜的太准了,我的应用场景还真是需要超低功耗的,假设顺着你的思路,如果在idle的时候turn off那个和A5绑定的local timer,是否系统中其它的global timer也全部需要关闭?因为所有timer的物理上来说都是一模一样的,应该不存在谁比较省电谁比较费电?那结论是系统idle的时候可以把所有HW timer都关闭?那么系统退出idle的时候是不是需要额外的代码逻辑来补偿xtime等等?是不是有点类似于是从suspend退出来的时候读取rtc那种模式?kernel内部有么有这种场景的参考例子?请指教。
linuxer
2015-05-04 23:11
@buyit:如果真的是有超低功耗的需求,那么我觉得整个系统的电源管理的状态可以考虑设计成两个mode:
1、工作状态。
2、deep sleep状态。
工作状态非常短,可能每隔十几分钟醒来一次进行运算,整个工作状态可能就几个毫秒,运算完毕就立刻进入deep sleep状态。
在deep sleep状态,除了一个小小模块在工作,其余的HW block都被shutdown,这个小小模块包括(和wakeup source相关):
1、RTC
2、几个GPIO的硬件逻辑
3、PMU
上面的几个电路可以正常工作,但是工作在非常慢的clock上,例如32K的晶振(这时候,系统的main crystal也被turn off)。除了上面的几个block,所有的power被turn off

我感觉,这时候cpu idle framework似乎是无法满足超低功耗的需求。
linuxer
2015-05-05 12:15
@buyit:补充两句:
1、CPU idle和suspend是两种不同的状态,本质上CPU idle用于系统的working mode(例如在编辑文本的过程中,cpu暂时休息一下,等到用户输入),这时候,整个系统的外设都是standby的,按理说任何外设的中断事件都应该可以让CPU立刻回到working mode。只不过,由于multi core体系下,很多的外设build in到CPU core,因此,cpu idle也有可能关闭那些build in的外设。suspend绝对是一个系统行为,需要系统的全部模块参与,CPU idle仅仅是由cpu这一个HW block参与。
2、如果系统的负荷非常轻,那么其实没有必要设计复杂的cpu idle状态,因此这时候系统的行为多半是迅速处理完事情,立刻suspend
3、suspend的开销比较大(进入的时候要冻结所有进程、遍历所有driver),处于suspend的时间要足够久才能省电。如果suspend了,但是2秒就resume,肯定是起不到省电的目的
4、suspend的时候,时间子系统已经suspend(各种普通timer已经没有意义了),只能借助RTC来唤醒系统,也就是说需要使用alarm timer。
5、可能有人会问:如果换成cpuidle也进入最深级别,假设采用一模一样的代码逻辑,只留下rtc,gpio,pmu,会有什么问题吗?
---------------------------
基本上这个很有困难,cpu idle是内核的自主行为,不需要其他外设驱动的状态,也不需要考虑AP的感受。suspend是一个系统行为,一般由AP发起
6、问题:是不是一定需要一个HW timer可以用来唤醒cpu,否则的话就违反了cpuidle的定义?
----------------------
重点不是HW timer,而是整个系统仍然处于工作状态,任何用户的动作、或者外设的动作都需要系统响应。
buyit
2015-04-22 12:14
好文!
对CP15盒mem两种模式的system counter的读取方式解读有误。
事实上只要系统中存在了cp15类型的模式,mem模式时不会调用到的, 看源码:
arch_counter_read-->arch_counter_get_cntvct-->arch_timer_read_counter()

arch_timer_read_counter是一个函数指针,它的定义如下:
u64 (*arch_timer_read_counter)(void) = arch_counter_get_cntvct_cp15;

所以初始化的时候默认采用cp15方式读取,再看下是否有人对它赋值:
static void __init arch_counter_register(unsigned type)
{
    u64 start_count;

    /* Register the CP15 based counter if we have one */
    if (type & ARCH_CP15_TIMER)
        arch_timer_read_counter = arch_counter_get_cntvct_cp15;
    else
        arch_timer_read_counter = arch_counter_get_cntvct_mem;
......
}

所以在初始化的时候,假设cp15和mem两种方式同时存在,那么只会采用cp15。只有当cp15不存在的时候,才会用到mem,也就是函数arch_counter_get_cntvct_mem

再看下你举例里面高通8974的例子,事实上你漏了dtsi里面前面的一段:

    timer {
        compatible = "arm,armv7-timer";
        interrupts = <1 2 0xf08>,
                 <1 3 0xf08>,
                 <1 4 0xf08>,
                 <1 1 0xf08>;
        clock-frequency = <19200000>;
    };

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

请注意8974平台注册了arm,armv7-timer这种cp15类型的system counter。
后面的arm,armv7-timer-mem是为了注册timer用的,并不代表system counter会真的采用mem模式去读取。
linuxer
2015-04-22 13:06
@buyit:多谢你的指正!
实际上每次回头再看以前的文档都是觉得惨不忍睹。实际上cp15和memory map的方式有很大性能的不同,我相信ARM公司终将会把per core类型的硬件全部使用cp15类型的方式来访问。对于timer硬件,global的counter是全局的,应该是使用memory map的方式访问,属于各个cpu core的local timer,应该应用更快的系统总线(CP15)访问。同样的道理也适用于GIC,在最新的GIC中已经移除了CPU interface这部分的HW block功能,只是保留Distributor的内容,而原来GIC中的CPU interface被集成到了ARM core中。
buyit
2015-04-22 13:32
@linuxer:这是我见过为数不多的可以讲why的技术文章。有些小错漏在所难免,不必计较。
请抽空更新剩下的几个章节,期待中。
buyit
2015-04-22 17:40
@linuxer:@linuxer @蜗蜗:
  请问两位是否可以私下交流一下?
  爱才之心大起 :=)

skype id: tangramybu@outlook.com
raceant
2018-06-27 20:16
@buyit:@buyit @linuxer 高通多注册了一个armv7-timer-mem,有什么作用呢,这个timer的reg需要和armv7-timer相同才能work,但是多加一个timer好像没有用

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog