昨天将了驱动实现的机制,这里将介绍策略部分。

第一部分:中断的处理

讲之前先介绍驱动实现的功能,这是一个触摸屏的驱动,硬件平台是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年暑假作的一个项目,过了很久才写些总结,好忘了再看!

 

 

 

 

.