昨天将了驱动实现的机制,这里将介绍策略部分。
第一部分:中断的处理
讲之前先介绍驱动实现的功能,这是一个触摸屏的驱动,硬件平台是ARM。
驱动程序需要返回触摸笔的状态:
(1)触摸笔按下、提起的数据及状态报告;
(2)触摸笔在屏上滑动时,数据及滑动状态的识别。
下面从源码开始分析:
static void ads7843_isr_tc(int irq, void *dev_id, struct pt_regs *reg)
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_UP)
{
tsdev.penStatus = PEN_DOWN;
ts_timer.expires = jiffies + TS_TIMER_DELAY;
if(ts_dev_open)
add_timer(&ts_timer);
wait_up_int();
}
else
{
tsdev.penStatus = PEN_UP;
del_timer_sync(&ts_timer);//在定时器到期前禁止一个已注册的定时器
wait_down_int();
tsEvent();
}
spin_unlock_irq(&(tsdev.lock));
}
static void ts_timer_handler(unsigned long data)
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_DOWN)
ads7843_get_xy();
mod_timer(&ts_timer, jiffies + TS_TIMER_DELAY); //更新定时器到达的时间
spin_unlock_irq(&(tsdev.lock));
}
static void tsEvent_raw(void)
{
if (tsdev.penStatus == PEN_DOWN) {
BUF_HEAD.x = save_x = x;
BUF_HEAD.y = save_y = y;
BUF_HEAD.pressure = PEN_DOWN;
} else {
BUF_HEAD.x = save_x;//0;
BUF_HEAD.y = save_y;//0;
BUF_HEAD.pressure = PEN_UP;
}
tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);
wake_up_interruptible(&(tsdev.wq));
}
对于策略无关紧要的细节这里我先忽略,直指最核心的内容。
这里,读者可以看到中断处理例程中顶部、底部处理方法的应用和定时器如何发挥
它巧妙的功效的。
先列出定时器的数据结构和几个重要的API:
static struct timer_list ts_timer;
定时器链表的结构。
init_timer();
初始化定时器
add_timer();
增加一个定时器,相当于打开定时器;
del_timer_sync();
删除定时器,正确的说法叫禁止已经打开的定时器,说删除便于理解。
del_timer()也有同样的功能,但大多数驱动开发者推荐使用前者,因为它可以保证
在此函数返回时没有任何CPU在使用定时器。可能你会问,既然如此为什么要实现del_timer()呢?
我也同样有疑问,你可以加入内核开发者邮件列表,向他们提问。
mod_timer();
修改定时器时间。
为了方便,我讲形参已经省去,这并不妨害实际的分析。对于定时器的细节可以
参考linux内核源码,当然魏永明老师译著的《linux设备驱动程序》是一本不错的书。
另外定时器的使用有许多必须要注意的地方,这是因为通常定时器的处理函数运行时已经脱离了
原来的进程上下文,这就要求对于避免竞态的考虑。
简单的说定时器就是在定义定时器后的将来某个时间运行某个例程,当然定时时间和例程都是由程序员决定的。
下面开始进入正题了,策略?这是一个饱含中国智慧的词。
我讲从中断事件产生的时间顺序来讲解中断实现的策略。
当触摸笔按下,产生一个中断,进入中断例程ads7843_isr_tc。
spin_lock_irq(&(tsdev.lock));spin_unlock_irq(&(tsdev.lock));是防止竞态的硕锁,这里不涉及。
程序进行笔状态的判断,笔的初始状态为UP,此时进入
if (tsdev.penStatus == PEN_UP)
{
tsdev.penStatus = PEN_DOWN;
ts_timer.expires = jiffies + TS_TIMER_DELAY;
if(ts_dev_open)
add_timer(&ts_timer);
wait_up_int();
}
这个分支。修改笔的状态为DOWN,并开启定时器,推出。
下面的行为将分为两种情况:
一,触摸笔立即提起(前面两个动作联起来就是在屏上点了下),产生硬件中断,进入ads7843_isr_tc,
根据笔的状态DOWN,进入
else
{
tsdev.penStatus = PEN_UP;
del_timer_sync(&ts_timer);
wait_down_int();
tsEvent();
}
修改笔状态为UP,删除定时器。tsEVENT()读取数据,tsEvent_raw()即是这个函数的
重载实现,这里用了OOP的概念,其实在驱动源码中tsEVENT()只是一个void *的空函数。
根据笔的状态
static void tsEvent_raw(void)
{
if (tsdev.penStatus == PEN_DOWN) {
BUF_HEAD.x = save_x = x;
BUF_HEAD.y = save_y = y;
BUF_HEAD.pressure = PEN_DOWN;
} else {
BUF_HEAD.x = save_x;//0;
BUF_HEAD.y = save_y;//0;
BUF_HEAD.pressure = PEN_UP;
}
程序将进入else分支,读取原始的保存值,如果是第一次产生中断,则是初始值0;对于为何要读保存的值
读者将在下面的分析得到理解。
考虑第二种情况:
二,触摸笔在屏上滑动。也就是说产生的定时器没有被禁止,现面看看定时器处理例程,
它实现了一个轮询读取硬件数据方法。
static void ts_timer_handler(unsigned long data)
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_DOWN)
ads7843_get_xy();
mod_timer(&ts_timer, jiffies + TS_TIMER_DELAY); //更新定时器到达的时间
spin_unlock_irq(&(tsdev.lock));
}
正如开始分析,当触摸笔按下,笔的状态改为DOWN,于是当定时器定时时间带来时,
将执行ads7843_get_xy();这是读取硬件数据,在此函数中读取完硬件数据后同样调用了
tsEvent_raw()返回数据。
这时,笔的状态为DOWN,则执行与前面不同的if分支
BUF_HEAD.x = save_x = x;
BUF_HEAD.y = save_y = y;
获得新数据,并保存到save_x,save_y中,这两个值在触摸笔提起的时候读取。
数据读取完成后,修改定时器定时,为下一次轮询作准备。
这里可以得出结论:
(1)如果触摸笔一直在屏上滑动,则定时器周期性的轮询获得数据。
(2)如果触摸笔提起则返回的数据是上一次轮询获得的数据,也就是说在屏上的同
一点上,一次按下并提起的操作将产生两组相同的数据,只是笔的状态改变了。
完全实现了要求的功能,这里还要说的就是,驱动程序的实现分为机制和策略两部分:
机制是一系列linux的内核接口,而策略依赖于特定的硬件和要实现的具体功能。
写出一个好的驱动程序有赖于知识和技能的积累,当然智力是基础,如果智慧和智力有
区别的话,我想智慧只属于天才,如linus。
这是07年暑假作的一个项目,过了很久才写些总结,好忘了再看!
.