• 9G10内核时钟tick实现


    9G10中PIT(Periodic Interval Timer)提供OS调度中断,它提供了最高精度和最有效的管理(即使系统长时间响应)。
    一. 硬件
    PIT目标是提供OS的周期中断。PIT提供一个可编程溢出计数器和一个reset-on-read特性。它包含两个计数器:20bit CPIV counter和12bit PICNT(Periodic Interval) counter。两个计数器都工作在Master Clock/16。
    CPIV从0增加到PIT_MR中PIV设定值,到达设定值后复位(0)且PICNT加1。假如中断是能,PIT_SR的PITS位置位,触发一个中断。
    写一个新的PIV值到PITMR不会reset/restart计数器。
    当读取PIT_PIVR(Periodic Interval Value Register)时,PICNT复位(0),且PITS清零(获取到一个中断)。PICNT表示自从上次读取PIT_PIVR后多少个Periodic Interval过去了。
    当读取PIT_PIIR(Periodic Interval Image Register)时,对CPIV和PICNT无影响。
    PIT可通过PIT_MR的PITEN位使能或关闭,仅当CPIV变为0时PITEN才生效。
    PIT共有4个寄存器,分别是PIT_MR(Mode Register),PIT_SR(Status Register),PIT_PIVR(Periodic Interval Value Register),PIT_PIIR(Periodic Interval Image Register)。
    PIT_MR: bit0-19 PIV, bit24 PITEN, bit25 PITIEN
    PIT_SR:bit0 PITS
    PIT_PIVR:bit0-19 CPIV,bit20-31 PICNT
    PIT_PIIR:bit0-19 CPIV,bit20-31 PICNT
    二. 软件
    在板级文件中machine_desc中设置timer=&at91sam926x_timer,下面看下at91sam926x_timer的初始化函数。
    at91sam926x_time.c
    /*
    * Set up both clocksource and clockevent support.
    */
    static void __init at91sam926x_pit_init(void)
    {
    unsigned long pit_rate;
    unsigned bits;

    /*
    * Use our actual MCK to figure out how many MCK/16 ticks per
    * 1/HZ period (instead of a compile-time constant LATCH).
    */
    pit_rate = clk_get_rate(clk_get(NULL, "mck")) / 16;
    pit_cycle = (pit_rate + HZ/2) / HZ; //向上取整,四舍五入
    WARN_ON(((pit_cycle - 1) & ~AT91_PIT_PIV) != 0);

    /* Initialize and enable the timer */
    at91sam926x_pit_reset();

    /*
    * Register clocksource. The high order bits of PIV are unused,
    * so this isn't a 32-bit counter unless we get clockevent irqs.
    */
    /* mult = (10^9 << shift) / pit_rate */
    pit_clk.mult = clocksource_hz2mult(pit_rate, pit_clk.shift);
    bits = 12 /* PICNT */ + ilog2(pit_cycle) /* PIV */;
    pit_clk.mask = CLOCKSOURCE_MASK(bits);
    clocksource_register(&pit_clk); // hz或cycle到ns的转换

    /* Set up irq handler */
    setup_irq(AT91_ID_SYS, &at91sam926x_pit_irq);

    /* Set up and register clockevents */
    /* factor = (pit_rate << shift) / NSEC_PER_SEC */
    pit_clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, pit_clkevt.shift);
    pit_clkevt.cpumask = cpumask_of(0);
    clockevents_register_device(&pit_clkevt); // ns到hz或cycle的转换
    }


    static struct clocksource pit_clk = {
    .name = "pit",
    .rating = 175,
    .read = read_pit_clk,
    .shift = 20,
    .flags = CLOCK_SOURCE_IS_CONTINUOUS,
    };
    static struct clock_event_device pit_clkevt = {
    .name = "pit",
    .features = CLOCK_EVT_FEAT_PERIODIC,
    .shift = 32,
    .rating = 100,
    .set_mode = pit_clkevt_mode,
    };
    Clocksource注册过程
    Clocksource_register(&pit_clk); -->clocksource_enqueue(&pit_clk);
    将pit_clk添加到系统时钟源list中。
    Clockevents_register_device(&pit_clkevt);
    -->clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, &pit_clkevt);
    --> raw_notifier_call_chain(&clockevents_chain, reason, &pit_clkevt);
    -->__raw_notifier_call_chain(nh, val, v, -1, NULL);
    -->notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
    -->nb->notifier_call(nb, val, v);
    即nb->notifier_call((&clockevents_chain)->head, CLOCK_EVT_NOTIFY_ADD, &pit_clkevt);
    Kernel/notifier.c
    notifier_call()函数的初始化在start_kernel()的tick_init()中,后面分析。
    -->tick_check_new_device(&pit_clkevt); //kernel/time/tick-common.c
    确认clock_event_device的rating是否高于当前的,高于则替换当前时钟事件设备
    若pit_clkevt->features&CLOCK_EVT_FEAT_ONESHOT 还要调用 tick_oneshot_notify()
    -->tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
    -->tick_setup_periodic(newdev, 0);
    ---->tick_set_periodic_handler(newdev, 0);
    ------>(&pit_clkevt)->event_handler = tick_handle_periodic;
    // kernel/time/tick-common.c
    即设置pit_clkevt的事件处理函数为tick_handle_periodic()。
    ---->clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
    ---->(&pit_clkevt)->set_mode(CLOCK_EVT_MODE_PERIODIC, &pit_clkevt);
    pit_clkevt.mode = CLOCK_EVT_MODE_PERIODIC;
    至此调用pit_clkevt的set_mode()函数,完成全部初始化工作

    在init/main.c的start_kernel()中tick_init()调用过程:
    tick_init(); //kernel/time/tick-common.c
    -->clockevents_register_notifier(&tick_notifier);
    -->raw_notifier_chain_register(&clockevents_chain, nb);
    -->notifier_chain_register(&nh->head, n); // kernel/notifier.c
    -->向clockevents_chian注册tick_notifier()

    static int tick_notify(struct notifier_block *nb, unsigned long reason,
    void *dev)
    {
    switch (reason) {

    case CLOCK_EVT_NOTIFY_ADD:
    return tick_check_new_device(dev);

    case CLOCK_EVT_NOTIFY_BROADCAST_ON:
    case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
    case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
    tick_broadcast_on_off(reason, dev);
    break;

    case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
    case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
    tick_broadcast_oneshot_control(reason);
    break;

    case CLOCK_EVT_NOTIFY_CPU_DYING:
    tick_handover_do_timer(dev);
    break;

    case CLOCK_EVT_NOTIFY_CPU_DEAD:
    tick_shutdown_broadcast_oneshot(dev);
    tick_shutdown_broadcast(dev);
    tick_shutdown(dev);
    break;

    case CLOCK_EVT_NOTIFY_SUSPEND:
    tick_suspend();
    tick_suspend_broadcast();
    break;

    case CLOCK_EVT_NOTIFY_RESUME:
    tick_resume();
    break;

    default:
    break;
    }
    return NOTIFY_OK;
    }

    static struct notifier_block tick_notifier = {
    .notifier_call = tick_notify,
    };
    即向系统注册了notifier_block结构体tick_notifier的notifier_call()回调函数。


    中断处理过程
    根据初始化过程,每个tick或Hz产生一次中断。
    static struct irqaction at91sam926x_pit_irq = {
    .name = "at91_tick",
    .flags = IRQF_SHARED | IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
    .handler = at91sam926x_pit_interrupt
    };
    static irqreturn_t at91sam926x_pit_interrupt(int irq, void *dev_id)
    {

    /* The PIT interrupt may be disabled, and is shared */
    if ((pit_clkevt.mode == CLOCK_EVT_MODE_PERIODIC)
    && (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS)) {
    unsigned nr_ticks;

    /* Get number of ticks performed before irq, and ack it */
    nr_ticks = PIT_PICNT(at91_sys_read(AT91_PIT_PIVR));
    do {
    pit_cnt += pit_cycle;
    pit_clkevt.event_handler(&pit_clkevt);
    nr_ticks--;
    } while (nr_ticks);
    return IRQ_HANDLED;
    }

    return IRQ_NONE;
    }

    at91sam926x_pit_interrupt()
    -->pit_clkevt.event_handler(&pit_clkevt);
    -->tick_handle_periodic(&pit_clkevt); //kernel/time/tick-common.c
    -->tick_periodic(cpu);
    -->do_timer(1); //kernel/timer.c 更新jiffies_64和时间
    -->update_times(ticks);
    -->update_wall_time(); //更新xtime

    void do_timer(unsigned long ticks)
    {
    jiffies_64 += ticks;
    update_times(ticks);
    }
    static inline void update_times(unsigned long ticks)
    {
    update_wall_time();
    calc_load(ticks);
    }
    三. 知识扩展
    1)时钟系统中最重要的结构体变量:clockevent clocksource xtime。
    clockevent为kernel提供了时钟中断的一些处理函数,特别是对于tickless系统,提供了设置下一次时钟中断时间点的接口set_next_event和timer模式设置接口set_mode。
    clocksource则是kernel真正的计数者 时钟源,常规的时钟中断中的计数以及提高精度的补充计数都来自于clocksource。
    clocksource成员rating代表了时钟精度,参考值如下:
    1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
    100--199:基本可用,可用作真实的时钟源,但不推荐;
    200--299:精度较好,可用作真实的时钟源;
    300--399:很好,精确的时钟源;
    400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
    xtime是kernel的墙上时间,记录从1970-1-1至今的时间差,不管是用户空间还是内核空间要获取的系统时间都需要去读取xtime。
    2)timer中断模式
    kernel下timer的中断模式支持2种:周期性和一次性,也就是periodic和oneshot。
    对于固定tick(1/HZ)系统的timer使用periodic。但是对于tickless系统,则必须要使用oneshot 模式了,因为每次timer中断间隔不一样长。
    3系统启动时读取硬件时间,一般在rtc驱动加载后,调用rtc_hctosys()(driver/rtc/hctosys.c)。其根据内核配置CONFIG_RTC_HCTOSYS来决定是否同步时间,并读取CONFIG_RTC_HCTOSYS_DEVICE(一般为rtc0)来获取RTC时间。
    late_initcall(rtc_hctosys);

    参考:
    http://blog.csdn.net/skyflying2012/article/details/44727409

  • 相关阅读:
    PHP获取指定分钟数的下一个整数倍
    phpspreadsheet
    澳大利亚 主要城市列表
    db2编目抽取
    openssl实现CA自签证书和颁发数字证书
    基于Docker的redis集群搭建
    Python测试DB2连通性
    在Vim中查看文件编码
    搭建redis集群
    Python(十)之GUI编程
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/6013731.html
Copyright © 2020-2023  润新知