为什么我的hrtimer回调在转发后返回得太早了? [英] Why does my hrtimer callback return too early after forwarding it?
问题描述
我想使用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的时间间隔精确,不受触发和回调之间通常的延迟的影响. 我们的期望值是将该时间包括到计算中,因为我们希望计时器从我们在计时器回调中执行操作的那一刻起重新启动.
我试图将其绘制到下图中:
在红色气泡后面,我们得到:
- 定时器以X时间启动
- X时间过去了,计时器被触发
- 根据系统负载和其他因素,在延迟X"之后,调用hrtimer的回调函数
-
hrtimer_forward_now
根据上一个事件加上新的预期时间(将来可能只有2us,而不是24)来设置新的计时器 - 这是期望与现实的差异. hrtimer在上一个事件之后触发24us,这是因为我们希望它在调用
forward_now()
后触发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:
- timer is started with X time to fire
- time X has passed, the timer is triggered
- after "delay X" depending on the load of the system and other factors, the callback function for the hrtimer is called
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)- 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屋!