我想用一个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微秒后过期
在回调函数中,pin a被拉低
计时器向前推24微秒
第二次触发回调时,引脚B被拉低
我在内核4.1.2中使用了beagleboneblack和rt抢占补丁。
我在示波器上看到的是,第一个计时器的工作速度大约为65-67微秒(我可以忍受)。
但是转发似乎出现了故障,因为我测量的引脚A变低和引脚B变低的时间在2到50微秒之间。
因此,实际上,第二次触发回调有时发生在我定义的24微秒之前。
这个时机对我的用例不起作用。
我做错什么了?
最佳答案
所以我自己来回答:这是一个错误期望的问题。
我们在这里期望的是在回调期间按我们设置的数量(24us)将计时器向前设置。但是,如果我们看一下hrtimer_forward_now()
的内核实现,我们可以看到时间实际上被添加到计时器的最后一个事件/事件中(参见delta
的计算):
从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 }
这意味着这里不考虑计时器触发和实际执行回调之间的时间延迟。hrtimer在时间间隔上是精确的,不受触发和回调之间通常的延迟的影响。
我们希望在计算中包含该时间,因为我们希望计时器从在计时器回调中执行操作的那一刻起重新启动。
我试着把这个画成下图:
下面是红色编号的气泡:
定时器以x时间启动
时间x已过,计时器被触发
在“delay x”之后,根据系统负载和其他因素,调用hrtimer的回调函数
hrtimer_forward_now
根据最后一个事件加上新的预期时间(未来可能只有2us,而不是24)设置新的计时器。这里是期望与现实的差异。hrtimer在最后一个事件之后触发24us,而我们希望它在调用
forward_now()
之后触发24us。总而言之,我们完全破坏了上面的代码示例,并在触发两个gpio管脚之间进行了
usleep_range()
调用。这个函数的底层实现也是用hrtimer
完成的,但是它对用户是隐藏的,并且它的行为与我们在本例中所期望的一样。