为什么我的hrtimer回调在转发后返回得太早了? [英] Why does my hrtimer callback return too early after forwarding it?

查看:170
本文介绍了为什么我的hrtimer回调在转发后返回得太早了?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用hrtimer来控制两个硬件gpio引脚来执行一些总线信令.我在这样的内核模块中设置了hrtimer

#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>

#define PIN_A_HIGH_TO_A_LOW_US  48  /* microseconds */
#define PIN_A_LOW_TO_B_LOW_US   24  /* microseconds */

static struct kt_data {
    struct hrtimer timer;
    ktime_t period;
} *data;

typedef enum {
    eIdle = 0,
    eSetPinALow,
    eSetPinBLow,
} teControlState;

static enum hrtimer_restart TimerCallback(struct hrtimer *var);
static void StopTimer(void);

static teControlState cycle_state = eIdle;

static enum hrtimer_restart TimerCallback(struct hrtimer *var)
{
    local_irq_disable();

    switch (cycle_state) {
    case eSetPinALow:
        SetPinA_Low();
        data->period = ktime_set(0, PIN_A_LOW_TO_B_LOW_US * 1000);
        cycle_state = eSetPinBLow;
        break;
    case eSetPinBLow:
        SetPinB_Low();
        /* Do Stuff */
        /* no break */
    default:
        cycle_state = eIdle;
        break;
    }

    if (cycle_state != eIdle) {
        hrtimer_forward_now(var, data->period);
        local_irq_enable();
        return HRTIMER_RESTART;
    }

    local_irq_enable();
    return HRTIMER_NORESTART;
}

void StartBusCycleControl(void)
{
    SetPinA_High();
    SetPinB_High();

    data->period = ktime_set(0, PIN_A_HIGH_TO_A_LOW_US * 1000);
    cycle_state = eSetPinALow;
    hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL);
}

int InitTimer(void)
{
    data = kmalloc(sizeof(*data), GFP_KERNEL);

    if (data) {
        hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        data->timer.function = TimerCallback;
        printk(KERN_INFO DRV_NAME
               ": %s hr timer successfully initialized\n", __func__);
        return 0;
    } else {
        printk(KERN_CRIT DRV_NAME
               ": %s failed to initialize the hr timer\n", __func__);
        return -ENOMEM;
    }
}

所以这个想法是

  • 两个引脚在开始时都是高电平
  • hrtimer设置为在48微秒后过期
  • 在回调函数中,引脚A被拉至低电平
  • 将计时器向前推24微秒
  • 第二次触发回调时,引脚B被拉至低电平

我将BeagleBoneBlack与内核4.1.2一起使用rt-preempt补丁.

我在范围内看到的是,第一个计时器的工作原理像一个大约65-67微秒的护身符(我可以忍受它). 但是,转发似乎会出现故障,因为我测量的是引脚A变为低电平和引脚B变为低电平之间的时间在2到50微秒之间. 因此,从本质上讲,第二次触发回调有时发生在我定义的24微秒之前. 而且这种时机不适用于我的用例.

是否有任何指向我做错事情的指针?

解决方案

所以我要自己回答:这是一个期望值错误的问题.

我们在这里期望的是按照设置的数量(24us)将计时器在回调期间向前设置 .但是,如果我们看一下hrtimer_forward_now()的内核实现,我们可以看到时间实际上是添加到计时器的最后一个事件/发生的时间(请参见delta的计算): /p>

来自 Linux/kernel/time/hrtimer.c

833 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
834 {
835         u64 orun = 1;
836         ktime_t delta;
837 
838         delta = ktime_sub(now, hrtimer_get_expires(timer));
839 
840         if (delta.tv64 < 0)
841                 return 0;
842 
843         if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
844                 return 0;
845 
846         if (interval.tv64 < hrtimer_resolution)
847                 interval.tv64 = hrtimer_resolution;
848 
849         if (unlikely(delta.tv64 >= interval.tv64)) {
850                 s64 incr = ktime_to_ns(interval);
851 
852                 orun = ktime_divns(delta, incr);
853                 hrtimer_add_expires_ns(timer, incr * orun);
854                 if (hrtimer_get_expires_tv64(timer) > now.tv64)
855                         return orun;
856                 /*
857                  * This (and the ktime_add() below) is the
858                  * correction for exact:
859                  */
860                 orun++;
861         }
862         hrtimer_add_expires(timer, interval);
863 
864         return orun;
865 }

这意味着在此处不考虑计时器触发与实际执行回调之间所花费的时间延迟. hrtimers的时间间隔精确,不受触发和回调之间通常的延迟的影响. 我们的期望值是将该时间包括到计算中,因为我们希望计时器从我们在计时器回调中执行操作的那一刻起重新启动.

我试图将其绘制到下图中:

在红色气泡后面,我们得到:

  1. 定时器以X时间启动
  2. X时间过去了,计时器被触发
  3. 根据系统负载和其他因素,在延迟X"之后,调用hrtimer的回调函数
  4. hrtimer_forward_now根据上一个事件加上新的预期时间(将来可能只有2us,而不是24)来设置新的计时器
  5. 这是期望与现实的差异. hrtimer在上一个事件之后触发24us,这是因为我们希望它在调用forward_now()
  6. 后触发24us.

总而言之,我们完全废弃了上面的代码示例,并在触发两个GPIO引脚之间进行了usleep_range()调用.该功能的基础实现也可以通过hrtimer完成,但对用户而言是隐藏的,并且在这种情况下可以像我们期望的那样起作用.

I want to use a hrtimer to control two hardware gpio pins to do some bus signalling. I set up a hrtimer in a kernel module like this

#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>

#define PIN_A_HIGH_TO_A_LOW_US  48  /* microseconds */
#define PIN_A_LOW_TO_B_LOW_US   24  /* microseconds */

static struct kt_data {
    struct hrtimer timer;
    ktime_t period;
} *data;

typedef enum {
    eIdle = 0,
    eSetPinALow,
    eSetPinBLow,
} teControlState;

static enum hrtimer_restart TimerCallback(struct hrtimer *var);
static void StopTimer(void);

static teControlState cycle_state = eIdle;

static enum hrtimer_restart TimerCallback(struct hrtimer *var)
{
    local_irq_disable();

    switch (cycle_state) {
    case eSetPinALow:
        SetPinA_Low();
        data->period = ktime_set(0, PIN_A_LOW_TO_B_LOW_US * 1000);
        cycle_state = eSetPinBLow;
        break;
    case eSetPinBLow:
        SetPinB_Low();
        /* Do Stuff */
        /* no break */
    default:
        cycle_state = eIdle;
        break;
    }

    if (cycle_state != eIdle) {
        hrtimer_forward_now(var, data->period);
        local_irq_enable();
        return HRTIMER_RESTART;
    }

    local_irq_enable();
    return HRTIMER_NORESTART;
}

void StartBusCycleControl(void)
{
    SetPinA_High();
    SetPinB_High();

    data->period = ktime_set(0, PIN_A_HIGH_TO_A_LOW_US * 1000);
    cycle_state = eSetPinALow;
    hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL);
}

int InitTimer(void)
{
    data = kmalloc(sizeof(*data), GFP_KERNEL);

    if (data) {
        hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        data->timer.function = TimerCallback;
        printk(KERN_INFO DRV_NAME
               ": %s hr timer successfully initialized\n", __func__);
        return 0;
    } else {
        printk(KERN_CRIT DRV_NAME
               ": %s failed to initialize the hr timer\n", __func__);
        return -ENOMEM;
    }
}

So the idea is that

  • both pins are high at the start
  • hrtimer is set to expire after 48 microseconds
  • in the callback function, pin A is pulled to low
  • the timer is pushed forward 24 microseconds
  • the second time the callback is triggered, pin B is pulled to low

I use a BeagleBoneBlack with Kernel 4.1.2 with the rt-preempt patches.

What I see at the scope is that the first timer works like a charm with about 65-67 microseconds (I can live with that). but the forwarding seems to malfunction because the times I measure between pin A going low and pin B going low are between 2 and 50 microseconds. So in essence, the second time the callback is triggered sometimes happens before the 24 microseconds I defined. And that timing doesn't work for my use case.

Any pointers to what I am doing wrong?

解决方案

So to answer this myself: This is a problem of wrong expectations.

What we expected here is to set the timer forward during the callback by the amount we set (24us). But if we take a look at the kernel implementation of hrtimer_forward_now()we can see that the time is actually added to the last event/occurrence of the timer (see the calculation of delta):

From Linux/kernel/time/hrtimer.c

833 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
834 {
835         u64 orun = 1;
836         ktime_t delta;
837 
838         delta = ktime_sub(now, hrtimer_get_expires(timer));
839 
840         if (delta.tv64 < 0)
841                 return 0;
842 
843         if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
844                 return 0;
845 
846         if (interval.tv64 < hrtimer_resolution)
847                 interval.tv64 = hrtimer_resolution;
848 
849         if (unlikely(delta.tv64 >= interval.tv64)) {
850                 s64 incr = ktime_to_ns(interval);
851 
852                 orun = ktime_divns(delta, incr);
853                 hrtimer_add_expires_ns(timer, incr * orun);
854                 if (hrtimer_get_expires_tv64(timer) > now.tv64)
855                         return orun;
856                 /*
857                  * This (and the ktime_add() below) is the
858                  * correction for exact:
859                  */
860                 orun++;
861         }
862         hrtimer_add_expires(timer, interval);
863 
864         return orun;
865 }

That means that the time delay it took between the timer firing and the callback actually executing is not taken into account here. The hrtimers are meant to be precise in interval timing and not be influenced by the usual delays between firing and the callback. Where our expectancy was to include that time into the calculation because we wanted the timer to restart from the moment we executed an action in the timer callback.

I tried to draw this into the following diagram:

Following the red numbered bubbles we get:

  1. timer is started with X time to fire
  2. time X has passed, the timer is triggered
  3. after "delay X" depending on the load of the system and other factors, the callback function for the hrtimer is called
  4. hrtimer_forward_now sets the new timer forward based on the last event plus the new expected time (which might be only 2us in the future instead of 24)
  5. Here is the discrepancy of expectation vs reality. The hrtimer fires 24us after the last event when we expect it to fire 24us after the call to forward_now()

To sum it all up, we completely trashed the above code example and went with a usleep_range() call between triggering the two GPIO pins. The underlying implementation of that function is also done with hrtimer but it is hidden from the user and it acts as we expect in this case.

这篇关于为什么我的hrtimer回调在转发后返回得太早了?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆