Linux时间子系统之(十六):clockevent

作者:linuxer 发布于:2014-12-9 19:56 分类:时间子系统

一、clock event控制的通用逻辑

1、产生clock event的设备

各种系统的timer硬件形形色色,不过在general clock event device layer,struct clock_event_device被来抽象一个可以产生clock event的timer硬件设备,如下:

struct clock_event_device {
    void          (*event_handler)(struct clock_event_device *);
    int            (*set_next_event)(unsigned long evt,   struct clock_event_device *);
    int            (*set_next_ktime)(ktime_t expires,     struct clock_event_device *);
    ktime_t       next_event;
    u64            max_delta_ns;
    u64            min_delta_ns;
    u32            mult;
    u32            shift;
    enum clock_event_mode    mode;
    unsigned int        features;
    unsigned long        retries;

    void            (*broadcast)(const struct cpumask *mask);
    void            (*set_mode)(enum clock_event_mode mode, struct clock_event_device *);
    void            (*suspend)(struct clock_event_device *);
    void            (*resume)(struct clock_event_device *);
    unsigned long        min_delta_ticks;
    unsigned long        max_delta_ticks;

    const char        *name;
    int            rating;
    int            irq; -------------使用的IRQ number
    const struct cpumask    *cpumask; ----该clock event device附着在哪一个CPU core上
    struct list_head    list;
    struct module        *owner;
} ____cacheline_aligned;

在把目光投向每一个成员之前,我们先聊一聊____cacheline_aligned,其实最开始定义这个数据结构的时候没有这个属性,而且struct clock_event_device的各个成员也不是这样排列的。为了提高cacheline hit radio,内核工程师对这个数据结构进行的改造,一方面对齐了cache line,另外一方面重排了那些有密切关系的成员(所谓密切关系就是指在代码执行上下文中,如果访问了A成员,有非常大的概率访问B成员)。

event_handler顾名思义就是产生了clock event的时候调用的handler。一般而言,底层的clock event chip driver会注册中断处理函数,在硬件timer中断到来的时候调用该timer中断handler,而在这个中断handler中再调用event_handler。

既然是产生clock event的device,那么总是要控制下一次event产生的时间点,我们有两个成员函数完成这个功能:set_next_event和set_next_ktime。set_next_ktime函数可以直接接收ktime作为参数,而set_next_event设定的counter的cycle数值。一般的timer硬件都是用cycle值设定会比较方便,当然,不排除有些奇葩可以直接使用ktime(秒、纳秒),这时候clock event device的features成员要打上CLOCK_EVT_FEAT_KTIME的标记。

features成员是描述底层硬件的功能feature的,包括:

#define CLOCK_EVT_FEAT_PERIODIC        0x000001------具备产生周期性event的能力
#define CLOCK_EVT_FEAT_ONESHOT        0x000002-----具备产生oneshot类型event的能力
#define CLOCK_EVT_FEAT_KTIME        0x000004-------上面已经描述了

#define CLOCK_EVT_FEAT_C3STOP        0x000008
#define CLOCK_EVT_FEAT_DUMMY        0x000010

CLOCK_EVT_FEAT_C3STOP是一个很有意思的feature:内核中有一个模块叫做cpuidle framework,当没有任务做的时候,cpu会进入idle状态。这种CPU的sleep state叫做C-states,有C1/C2…Cn种states(具体多少种和CPU设计相关),当然不同的状态是在功耗和唤醒时间上进行平衡,CPU睡的越浅,功耗越大,但是能够很快的唤醒。一般而言,在sleep state的CPU可以被local timer唤醒,但是,当CPU进入某个深度睡眠状态的时候,停止了local timer的运作,这时候,local timer将无法唤醒CPU了。

kernel的注释说这是一个x86(64)的功能设计失误(misfeature),不过,在嵌入式平台上,这也可以认为是对功耗的极致追求(ARM 的generic timer也有这个misfeature,呵呵~~)。为了让系统可以继续运作,传说中tick broadcast framework粉墨登场了。struct clock_event_device中的broadcast这个callback函数是和clock event广播有关。在per CPU 的local timer硬件无法正常运作的时候,需要一个独立于各个CPU的timer硬件来作为broadcast clock event device。在这种情况下,它可以将clock event广播到所有的CPU core,以此推动各个CPU core上的tick device的运作。当然它不是本文的主要内容,如果有兴趣可以参考本站其他文档。

clock event mode的定义如下:

enum clock_event_mode {
    CLOCK_EVT_MODE_UNUSED = 0, ------未使用
    CLOCK_EVT_MODE_SHUTDOWN, ------被软件shutdown
    CLOCK_EVT_MODE_PERIODIC, -------工作状态,处于periodic模式,周期性产生event
    CLOCK_EVT_MODE_ONESHOT, -------工作状态,处于one shot模式,event是一次性的
    CLOCK_EVT_MODE_RESUME, -------处于系统resume中
};

具体的mode设定是由set_mode这个callback函数来完成的,毫无疑问,这是需要底层的clock event chip driver需要设定的。虽然定义了这么多mode,实际上底层的硬件未必支持,有些HW timer硬件支持oneshot和periodic的设定,不过有些就不支持(例如:ARM general timer)。具体clock event device如何在各个mode中切换会在tick device的文档中描述。

rating、mult和shift的概念和clocksource中的类似,这里就不描述了。此外很多成员这里也不细述了,在后面的逻辑分析中自然就清晰了。

2、如何组织系统中的clock event device?

相关的全局变量定义如下:

static LIST_HEAD(clockevent_devices);
static LIST_HEAD(clockevents_released);

clock event device core模块使用两个链表来管理系统中的clock event device,一个是clockevent_devices链表,该链表中的clock event device都是当前active的device。active的clock device有两种情况,一种是cpu core的current  clockevent device,这些device是各个cpu上产生tick的那个clock event device,还有一些active的device由于优先级比较低,当前没有使用,不过可以作为backup的device。另外一个是clockevents_released链表,这个链表中clock event device都是由于种种原因,无法进入active list,从而挂入了该队列。

 

二、向上层的其它driver提供操作clock event的通用API

1、设定clock event device的触发event的时间参数

int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bool force)
{
    unsigned long long clc;
    int64_t delta;
    int rc;

    dev->next_event = expires; -------设定下一次触发clock event的时间
    if (dev->features & CLOCK_EVT_FEAT_KTIME)----- -------------(1)
        return dev->set_next_ktime(expires, dev);

    delta = ktime_to_ns(ktime_sub(expires, ktime_get()));----- ----------(2)
    if (delta <= 0)
        return force ? clockevents_program_min_delta(dev) : -ETIME; -----------(3)

    delta = min(delta, (int64_t) dev->max_delta_ns);
    delta = max(delta, (int64_t) dev->min_delta_ns); ------------------(4)

    clc = ((unsigned long long) delta * dev->mult) >> dev->shift; ---------转换成cycle值

    rc = dev->set_next_event((unsigned long) clc, dev); ---调用底层driver callback函数进行设定

    return (rc && force) ? clockevents_program_min_delta(dev) : rc; -----------(5)
}

我们先看看该接口函数的参数值:dev指向具体的clock event device,expires参数是设定下一次产生event的时间点,force参数控制在expires设定异常的时候(例如设定在一个过去的时间点上产生event)该函数 的行为,一种是出错返回,另外一种还是进行event的产生,只是设定一个最小的delta。

(1)如果chip driver支持使用ktime的设定(这需要硬件支持,设定了CLOCK_EVT_FEAT_KTIME flag的那些clock event device才支持哦),事情会比较简单,直接调用set_next_ktime就搞定了。

(2)对于一个“正常”的clock event device,需要转换成cycle这样的单位。不过在转换成cycle之前,需要先将ktime格式的时间(传入的expires参数就是这样的格式)转换成纳秒这样的时间单位。

(3)delta小于0意味着用户设定的时间点已经是过去的一个时间点,如果强制产生event的话,那么事不宜迟,要立刻产生event,这需要调用clockevents_program_min_delta函数,代码如下:

static int clockevents_program_min_delta(struct clock_event_device *dev)
{
    unsigned long long clc;
    int64_t delta;

    delta = dev->min_delta_ns; ---------------------------(a)
    dev->next_event = ktime_add_ns(ktime_get(), delta); ----------------(b)

    if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
        return 0;

    dev->retries++; -------记录retry的次数
    clc = ((unsigned long long) delta * dev->mult) >> dev->shift; ------转换成cycle值
    return dev->set_next_event((unsigned long) clc, dev); ----调用底层driver callback函数进行设定
}

(a)在逻辑思维的世界里,你可以想像任意小的时间片段,但是在实现面,这个要收到timer硬件能力的限制。一个输入频率是1Hz的timer硬件,其最小时间粒度就是1秒,如何产生0.1秒的clock event呢?因此,所谓立刻产出也就是在硬件允许的最小的时间点上产生event。在注册clock event device的时候已经设定这个参数了,就是struct clock_event_device的min_delta_ns这个成员。

(b)ktime_get函数获取当前的时间点,加上min_delta_ns就是下一次要触发event的时间点,struct clock_event_device的next_event 这个成员就是用来记录下一次要触发event的时间点信息的。

(4)有最小值就有最大值,struct clock_event_device的max_delta_ns这个成员就是设定next event触发的最大时间值。这个最大值是和硬件counter的bit数目有关:如果一个硬件timer最大能表示60秒的时间长度,那么设定65秒后触发clock event是没有意义的,因为这时候counter会溢出,如果强行设定那么硬件实际会在5秒后触发event。同样的,max_delta_ns也是在注册clock event device的时候设定了这个参数。

(5)如果调用底层driver callback函数进行实际的cycle设定的时候出错(例如:由于种种原因,在实际设定的时候发现时间点是过去值,如果仍然设定,那么上层软件实际上要等到硬件counter 溢出后在下一个round中才会触发event,实际这时候黄花菜都凉了),并且是强制产生event的话,那么也需要调用clockevents_program_min_delta函数在最小时间点上产生clock event。

2、更换clock event设备

上层的tick device驱动层会根据情况(clock event的精度,是否local cpu的)调用clockevents_exchange_device 进行clock event设备的更换,具体代码如下:

void clockevents_exchange_device(struct clock_event_device *old,  struct clock_event_device *new)
{
    unsigned long flags;

    local_irq_save(flags);

    if (old) { -------------------------------(1)
        module_put(old->owner);
        clockevents_set_mode(old, CLOCK_EVT_MODE_UNUSED);
        list_del(&old->list);
        list_add(&old->list, &clockevents_released);
    }

    if (new) { ------------------------------(2)
        BUG_ON(new->mode != CLOCK_EVT_MODE_UNUSED);
        clockevents_shutdown(new);
    }
    local_irq_restore(flags);
}

(1)旧的clock event device要被替换掉,因此将其模式设定为CLOCK_EVT_MODE_UNUSED,并且从全局clock event device链表中摘下来,挂入clockevents_released链表

(2)我们要确保新的clock event设备没有被使用,如果新的clock event设备不是CLOCK_EVT_MODE_UNUSED状态,说明其目前被其他的上层软件使用,因此要kernel panic,并且shutdown该clock event device。这是该设备状态是CLOCK_EVT_MODE_SHUTDOWN状态。这里没有插入clockevent_devices全局链表的动作,主要是因为在调用该函数之前,新的clock event device已经挂入队列了。

 

三、向底层clockevent chip driver提供的接口

1、配置clock event device

底层的clock event chip driver可以调用clockevents_config对该设备进行配置。这里的操作类似clocksource中的内容,也就是说调用者输入频率参数,在clockevents_config中计算struct clock_event_device中一些类似mult、shift等成员的配置值:

void clockevents_config(struct clock_event_device *dev, u32 freq)
{
    u64 sec;

    if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))--------------(1)
        return;

    sec = dev->max_delta_ticks;-------------------------(2)
    do_div(sec, freq);
    if (!sec)
        sec = 1;-----限制最小值
    else if (sec > 600 && dev->max_delta_ticks > UINT_MAX)
        sec = 600; -------------------------------(3)

    clockevents_calc_mult_shift(dev, freq, sec);------------------(4)
    dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false);-------(5)
    dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true);
}

(1)底层的硬件驱动知道自己的能力,可以通过struct clock_event_device中的features成员宣称自己的功能:CLOCK_EVT_FEAT_PERIODIC说明该硬件timer可以产生周期性的clock event,CLOCK_EVT_FEAT_ONESHOT说明自己只能产生one shot类型的clock event。如果能产生one shot类型的event,那么即便是硬件不支持周期性的clock event,其实上层的软件可以通过不断设定next event的方法来模拟周期性的clock event。但是如果只是支持周期性的clock event就有些麻烦了,这时候是无法模拟one shot类型的event。也就是说,在这种情况下,整个系统必须有周期性tick,同时,系统无法支持高精度timer和dynamic tick的情况。在这样的硬件条件下,后续的配置是没有意义的,因此直接return。

(2)底层的硬件驱动需要设定max_delta_ticks和min_delta_ticks。需要注意的是这里的tick不是system tick,而是输入硬件timer的clock tick,其实就是可以设定最大和最小cycle数目。这个cycle数目除以freq就是时间值,单位是秒。

(3)又是600秒,是否似曾相识。我们在clock source那篇文档中已经介绍过了,这里就不再赘述

(4)根据输入频率和最大的秒数可以计算出clock event的mult和shift这两个成员的数值,具体计算过程在clock source那篇文档中已经介绍过了,这里就不再赘述。

(5)计算min_delta_ticks和max_delta_ticks对应的ns值。

2、注册clock event device

底层的timer硬件驱动会调用clockevents_register_device函数向系统注册clock event device,代码如下:

void clockevents_register_device(struct clock_event_device *dev)
{
    unsigned long flags;

    if (!dev->cpumask) {---------------------------(1)
        WARN_ON(num_possible_cpus() > 1);
        dev->cpumask = cpumask_of(smp_processor_id());
    }

    raw_spin_lock_irqsave(&clockevents_lock, flags); --------------(2)

    list_add(&dev->list, &clockevent_devices); ----加入clock event设备全局列表
    tick_check_new_device(dev); -----------------------(3)
    clockevents_notify_released(); ----------------------(4)

    raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}

(1)clock event device的cpumask指明该设备为哪一个CPU工作,如果没有设定并且cpu的个数大于1的时候要给出warning信息并进行设定(设定为当前运行该代码的那个CPU core)。在multi core的环境下,底层driver在调用该接口函数注册clock event设备之前就需要设定cpumask成员,毕竟一个timer硬件附着在哪一个cpu上底层硬件最清楚,这里仅仅是亡羊补牢(毕竟我们还是希望代码不要随随便便就kernel panic了,尽量让代码执行下去)。

(2)考虑到来自多个CPU上的并发,这里使用spin lock进行保护。关闭本地中断,可以防止来自本cpu上的并发操作。

(3)调用tick_check_new_device函数,让上层软件知道底层又注册一个新的clock device,当然,是否上层软件要使用这个新的clock event device是上层软件的事情,clock event device driver这个层次完成描述自己能力这部分代码就OK了。tick device相关内容请参考本站其他文档。

(4)如果上层软件想要使用新的clock event device的话(tick_check_new_device函数中有可能会进行此操作),它会调用clockevents_exchange_device函数(可以参考上面的描述)。这时候,旧的clock event会被从clockevent_devices链表中摘下,挂到clockevents_released队列中。在clockevents_notify_released函数中,会将old clock event device重新挂入clockevent_devices,并调用tick_check_new_device函数。

3、配置并注册clock event device

是上面两个接口函数的综合体,这里不再详细描述。

 

四、用户空间接口

1、sysfs接口初始化

在系统初始化的时候会调用clockevents_init_sysfs函数(呵呵~~~对于clock source,这个函数名字是init_clocksource_sysfs,很显然不是一个人的风格,我喜欢clockevents_sysfs_init和clocksource_sysfs_init这种函数定义风格)来初始化clock event device layer的sys file system接口,如下:

static int __init clockevents_init_sysfs(void)
{
    int err = subsys_system_register(&clockevents_subsys, NULL); --注册clock event这种bus type

    if (!err)
        err = tick_init_sysfs();
    return err;
}

该说的在clocksource那份文档中都描述了,这里直接不再赘述,重点关注tick_init_sysfs函数:

static int __init tick_init_sysfs(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {----------遍历各个CPU的clock event device
        struct device *dev = &per_cpu(tick_percpu_dev, cpu);--------------(1)
        int err;

        dev->id = cpu;
        dev->bus = &clockevents_subsys;
        err = device_register(dev);--------------------------(2)
        if (!err)
            err = device_create_file(dev, &dev_attr_current_device);------------(3)
        if (!err)
            err = device_create_file(dev, &dev_attr_unbind_device);
        if (err)
            return err;
    }
    return tick_broadcast_init_sysfs(); -----------------------(4)
}

(1)我们始终还是要回归统一模型的三件套,bus type,device和driver。现在有了clock event bus type,还缺少device。在clock source中,由于它是全局的,属于所有CPU core的,因此我们定义了一个device就OK了。对于clock event,我们需要为每一个CPU定义一个device,如下:

static DEFINE_PER_CPU(struct device, tick_percpu_dev);

是否需要定义clock event device对应的driver呢?本来定义device和driver的目的是让它们在适当的时机(bus type的match函数)相遇,相知(调用driver的probe函数)。对于clock event device而言,是不需要定义driver的,因为设备树提供了其他的方法来进行具体底层chip级别的初始化(具体可以参考ARM generic timer驱动中的描述)。

还有一点需要注意,实际上,每一个硬件设备都是设备模型中的一个device,不过,在clock event device这个场景下,实际上系统并不是为每一个timer硬件定义一个device,而仅仅是为每一个当前active(作为tick device那个clock event device)的clockevent设备定义了一个device数据结构,有空的时候大家可以思考一下这个问题。

(2)调用device_register就可以把所有的clock event device注册到系统中,统一设备模型会帮我们做一切事情。

(3)device_register之后,每个clock event device体现为一个sysfs中的目录,我们还需要为这个目录增加一些属性文件。定义如下:

static DEVICE_ATTR(current_device, 0444, sysfs_show_current_tick_dev, NULL);

static DEVICE_ATTR(unbind_device, 0200, NULL, sysfs_unbind_tick_dev);

(4)除了per cpu的device,还有一个broadcast device,定义如下:

static struct device tick_bc_dev = {
    .init_name    = "broadcast",
    .id        = 0,
    .bus        = &clockevents_subsys,
};

这个device在tick_broadcast_init_sysfs函数中调用device_register注册到系统,并创建了dev_attr_current_device的属性。和per cpu device不同的是:没有提供用户空间的unbind操作,也就是说,userspace无法unbind当前的broadcast clock event device。

2、属性文件操作:显示current tick device

读clock event device的current_device属性文件

static ssize_t sysfs_show_current_tick_dev(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct tick_device *td;
    ssize_t count = 0;

    raw_spin_lock_irq(&clockevents_lock);
    td = tick_get_tick_dev(dev); ----获取tick device
    if (td && td->evtdev)
        count = snprintf(buf, PAGE_SIZE, "%s\n", td->evtdev->name);
    raw_spin_unlock_irq(&clockevents_lock);
    return count;
}

一个active的clock event device对应的tick device有两种情况,一种是per cpu的tick device,驱动系统运作。另外一种是broadcast tick device,这些内容留在在tick device部分描述吧。

3、属性文件操作:unbind tick device

一个tick device总是绑定一个属于该cpu core并且精度最高的那个clock event device。通过sysfs的接口可以解除这个绑定。如果解除绑定的那个clock event device是unused,可以直接从clockevent_devices全局链表中删除。如果该设备当前使用中,那么需要找到一个替代的clock event device。如果找不到一个替代的clock event device,那么不能unbind当前的device,返回EBUSY。具体的代码逻辑就不分析,大家可以自己阅读代码理解。


原创文章,转发请注明出处。蜗窝科技

http://www.wowotech.net/timer_subsystem/clock-event.html

标签: clockevent

评论:

tao
2018-09-14 14:38
请问smp中多个核,一个核进入了wfi,spi中断会导致这个核唤醒么,local timer需要是ppi中断吗,对于这块有点理不清,看目前在用的板子几个timer都是spi中断,这样怎么让各个核的tick相互独立,wfi的时候不会被其他核的tick唤醒。。
薛文旺
2018-08-16 11:34
“ 3、配置并注册clock event device
         是上面两个接口函数的综合体,这里不再详细描述。 ”是指哪个函数?
linuxer
2018-08-16 19:16
@薛文旺:clockevents_config和clockevents_register_device
薛文旺
2018-08-17 17:25
@linuxer:最后说明一下哪个函数是这两个的综合体
hahaha
2018-08-20 09:39
@薛文旺:函数名是:clockevents_config_and_register
bubble
2015-12-17 10:04
void clockevents_register_device(struct clock_event_device *dev)
{
      ......
    list_add(&dev->list, &clockevent_devices); ----加入clock event设备全局列表
    tick_check_new_device(dev); -----------------------(3)
    clockevents_notify_released(); ----------------------(4)

}
根据描述(4)在clockevents_notify_released函数中,会将old clock event device重新挂入clockevent_devices,并调用tick_check_new_device函数。
我的疑问是:为什么在这个注册函数里面,要clockevent_released链表中的clockevent都调用tick_check_new_device函数?按理来说clockevent_released链表中的clockevent应该是在优先匹配的时候被比下去了的。
linuxer
2015-12-17 21:40
@bubble:clockevents_lock保护了clockevent_devices和clockevents_released这两个链表之间clockevent device的插入,摘下等操作,但是不会阻止对clock_event_device这个数据结构中各个成员的修改,也许在上面步骤(3)中切换clock event device之后,以及在从clockevents_released摘下设备,插入到clockevent_devices之间,很多事情发生了,因此,再次调用tick_check_new_device。
bubble
2015-12-21 10:21
@linuxer:哦,这样啊!
不好意思答复的比较晚,谢谢!(*^__^*)
schedule
2016-04-09 17:54
@linuxer:那如果再次调用tick_check_new_device 又发生了修改,那么不是陷入无休止的循环往复了?
fenghusthu
2016-08-13 11:53
@bubble:有可能其他cpu摘下的clock_event_device在这个cpu还可以再抢救一下
andyshrk
2015-09-16 18:18
请教下,max_delta_ticks min_delta_ticks这两个值是怎么确定的?
对于32bit的timer来说,按我的理解,最新可以设置的值就是1,最大可以设置的值是0xffffffff
可是,我看到好多驱动里面都是设置的是最小0xf,最大0xffffffff
这个是什么原因呢?
linuxer
2015-09-17 09:14
@andyshrk:max_delta_ticks很好理解,系统的HW timer是32个bit的话,最大就是0xffffffff,按理说,硬件timer的最小精度是1,min_delta_ticks应该是1,但是,如果设定为1的话,表示系统可以分辨的时间精度非常高。我们举一个例子:对于clock input是13M的HW timer,一个cycle就是1/13M = 77ns,如果min_delta_ticks设定是1,说明系统可以分辨大约80ns的time slice。系统中如果有两个hrtimer,trigger的时间相隔100ns,那么系统会为这两个hrtimer产生两次中断,分别在不同的时间点产生clock event,并调用hrtimer的callback函数。时间精度高当然好,但是从而带来的负作用就是产生的event事件比较多,从而影响了系统的性能。如果min_delta_ticks提高到0xf,时间的精度(是指clockevent trigger event的时间精度)变成1.2ms,也就是意味着超期时间比较近的hrtimer可以一并处理(clockevent只trigger一次),较少的trigger疑问较少的中断次数以及较少代码执行,CPU节省出来可以做更多的事情。
bsp
2016-04-01 15:54
@linuxer:1.2ms ->1.2us,^_^
hello_world
2016-04-01 17:36
@bsp:笔误了,多谢指正,^_^
tianhello
2015-04-01 11:04
你好,关于时间精度的问题我想问一下。如果要写一个timer的驱动,那么在这个驱动程序中,timer的精度大小是在驱动程序里自己定义的吗?这个精度是随便写一个数还是和其他的什么元素相关?
希望您能抽空帮小菜鸟解决一下疑问。
linuxer
2015-04-01 19:15
@tianhello:timer的精度大小是在驱动程序里自己定义的吗?
-------------------------
你所说的timer的精度是指clock event device结构中的那个rating吗?
tianhello
2015-04-01 21:38
@linuxer:对,就是clock event device那个结构体的精度,我看的linux版本是3.10版的。
linuxer
2015-04-01 23:15
@tianhello:rating是描述clock source的精度的,毫无疑问,一个输入频率是20MHz的counter精度一定是大于10ms一个cycle的counter。关于rating,内核代码的注释已经足够了,如下:

        1-99: Unfit for real use
            Only available for bootup and testing purposes.
        100-199: Base level usability.
            Functional for real use, but not desired.
        200-299: Good.
            A correct and usable clocksource.
        300-399: Desired.
            A reasonably fast and accurate clocksource.
        400-499: Perfect
            The ideal clocksource. A must-use where  available.

rating当然不能随便写一个,还是要和该timer的精度关联起来。
tianhello
2015-04-02 10:01
@linuxer:这个意思是说,一个由20MHz时钟驱动的时钟源,它的精度是50ns,所以它的精度大于10ms的时钟源。
内核代码注释我有看,但是不明白这里的1-99,100-199这些数字是怎么来的,它是由频率算出来的吗?
比如说频率为20MHz,那么它的精度就是10,这里的10是在1-99这个范围里的吗?
linuxer
2015-04-02 23:19
@tianhello:好像也没有直接的关系,不过系统中的HW timer也不会太多,可以根据具体情况具体分配rating。
tianhello
2015-04-04 00:57
@linuxer:好的,thank you
bsp
2016-04-01 15:58
@tianhello:rating其实是个经验值,不经和timer的频率有关,还和timer的属性有关。cpu-local-timer(PPI中断)的rating要比SPI的timer高,而且频率越高rating也会高。
tigger
2015-03-04 19:49
struct clock_event_device中的broadcast这个callback函数是和clock event广播有关。在per CPU 的local timer硬件无法正常运作的时候,需要一个独立于各个CPU的timer硬件来作为broadcast clock event device。在这种情况下,它可以将clock event广播到所有的CPU core,以此推动各个CPU core上的tick device的运作

1:这个broadcase clock event 是register_persistent_clock 这里的那个persistent的 clock吗?
2:这个broadcase clock event 跟setup_sched_clock 这个schedule 的clock 有什么关系呢?
3:这个broadcase clock event 对精度有要求吗?
linuxer
2015-03-05 22:44
@tigger:目前我只能先回答你第一个问题:
这个broadcase clock event 是register_persistent_clock 这里的那个persistent的 clock吗?
--------------------------------------
不是,persistent clock是独立于CPU的一个clock,当SOC进入低功耗状态,很可能global timer和per cpu timer都已经停止工作了,这时候唯一能以来的就是RTC的计时了,因此,register_persistent_clock 多半是指的RTC的那个clock

另外两个我需要再阅读一下代码,过几天再回答你吧。
tigger
2015-03-06 10:26
@linuxer:遇到一款比较特殊的芯片
persistent clock 他不用RTC,而是用的一个开机之后就不会掉电的timer,进入低功耗也照样工作。我看了一下,这个persistent clock 就是用clocksource_mmio_init 初始化的一个clocksource。

至于不用RTC,我的猜测,因为他的RTC是在PMIC上面,AP跟PMIC通信有delay,这个不掉点的timer就在AP这边,直接通过寄存器访问,速度快。
linuxer
2015-03-06 11:32
@tigger:奇葩总会有的,呵呵~~~对于大部分的SOC,timer都是build in在SOC上,而且随着SOC进入sleep状态而被关闭。anyway,persistent clock应该是和系统设计有关的,具体问题具体分析。
~零~
2015-03-18 15:50
@tigger:是高通的芯片吧
tigger
2015-03-18 16:53
@~零~:haha,你猜!
passerby
2015-03-29 13:27
@tigger:我猜不是,我在代码中看到高通也是用RTC
tigger
2015-03-29 14:18
@passerby:高通的芯片架构是我现在接触的几款芯片中感觉最好的。当然我详细了解的也没有几颗。不像有些家,芯片有bug,全靠软件擦屁股^ _ ^
bsp
2016-04-01 16:01
@tigger:你是再说海思的芯片全是bug吗?呵呵

发表评论:

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