Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1255468
  • 博文数量: 404
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 5382
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-03 16:29
文章存档

2010年(40)

2009年(140)

2008年(224)

我的朋友

分类: LINUX

2008-09-25 16:13:41

ads7843 驱动程序分析
本文将从驱动程序的机制和策略两方面分析:机制包括驱动程序的框架和硬件的控制,
策略主要是中断的处理和缓冲区的使用,这两种方法在 驱动的设计中经常用到 。

1. 框架

大部分驱动程序操作设计到三个重要的数据结构:file_operation,file,inode;


static struct file_operations ads7843_fops = {
 owner:  THIS_MODULE,
 open:  ads7843_ts_open,
 read:  ads7843_ts_read, 
 release: ads7843_ts_release,
#ifdef USE_ASYNC
 fasync:  ads7843_ts_fasync,
#endif
 poll:  ads7843_ts_poll,
};


(1)open ,read 读写和打开设备
(2)poll:unsigned (*poll)(struct file*,struct poll_table_struct *);
            查询某个文件描述符的读取河写入是否会阻塞。
           常用于需要使用多个输入输出流而又不阻塞其中任何一个的应用中。
           poll_wait(filp, &(tsdev.wq), wait);
           poll_wait(struct file *filp, wait_queue_head_t *, poll_table *);
              文件描述符                    等待队列         轮讯表
           功能:向轮讯表中加入一个等待队列 
            返回一个可以指示读写的位掩码,通常一个可读设备返回 POLLIN | POLLRDNORM;           
                    可写设备返回 POLLOUT | POLLRDNORM;
    static unsigned int ads7843_ts_poll(struct file *filp, struct poll_table_struct *wait)
{
        poll_wait(filp, &(tsdev.wq), wait);
         return (tsdev.head == tsdev.tail) ? 0 : (POLLIN | POLLRDNORM);
}


  (3)  int (*fasync) (int ,struct file *,int );异步通知通知设备FASYNC标志发生了变化;

static int ads7843_ts_fasync(int fd, struct file *filp, int mode)
{
 return fasync_helper(fd, filp, mode, &(tsdev.aq));

(4) release:
 int (*release) (struct inode *inode, struct file *filp);当file结构被释放时,调用;
   
   
这里有两个非常重要的调用就是异步和轮询机制,将在驱动程序的策略中分析。
 
框架的另一部分就是模块的初始化和注销

module_init(ads7843_ts_init);
module_exit(ads7843_ts_exit);
  
 static int __init ads7843_ts_init(void)
{
 int ret;
 tsEvent = tsEvent_dummy;

 ret = register_chrdev(0, DEVICE_NAME, &ads7843_fops);
 if (ret < 0) {
   printk(DEVICE_NAME " can't get major number\n");
   return ret;
 }
 tsMajor = ret;

 /* Request touch interrupt */
 ret = request_irq(ADS7843_IRQNO, ads7843_isr_tc, SA_INTERRUPT,
     DEVICE_NAME, ads7843_isr_tc);
 if (ret) goto tc_failed;
  
 tc_failed:
 return ret;
}
   
这里是模块向系统注册的部分;并向中断系统申请中断 
register_chrdev()是2.6以前的版本
 
        
 static void __exit ads7843_ts_exit(void)
{
 unregister_chrdev(tsMajor, DEVICE_NAME);
 free_irq(ADS7843_IRQNO, ads7843_isr_tc);
}       
向系统注销,并释放中断         

 宏及全局变量

自由许可证 MODULE_LICENSE("GPL"); 还可以有其他选择,BSD/GPL,之间的区别没有研究过
 设备名称: #define DEVICE_NAME "ads7843_ts"
设备号:  分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载时甚至编译时
         指定主设备号的余地 ,次设备号是为多次打开同一设备而准备的标识 
  eg.  定义一个全局变量 static int tsMajor = 0;
      #define TS_MINOR 16
  打开计数 static int ts_dev_open; 
设备数据结构
typedef struct {
  unsigned short pressure;
  unsigned short x;
  unsigned short y;
  unsigned short pad;
} TS_RET;

typedef struct {
 unsigned int penStatus;  /* PEN_UP, PEN_DOWN, PEN_SAMPLE */
 TS_RET buf[MAX_TS_BUF];  /* protect against overrun */
 unsigned int head, tail; /* head and tail for queued events */
 wait_queue_head_t wq;
 spinlock_t lock;
#ifdef USE_ASYNC
 struct fasync_struct *aq;
#endif
} TS_DEV;
 这个结构的定义依据相应的硬件特征 ,由驱动程序员定义。

机制实现了一系列的必要的接口,策略的分析讲在后面讲到。

 

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

第一部分:中断的处理

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

阅读(2288) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~