全部博文(404)
分类: LINUX
2008-09-26 10:41:13
触摸屏驱动在/kernel/drivers/char/s
函数request_irq 是Linux 系统中驱动程序注册中断的方法。irq 为所要申请的硬件中断号,handler 为系统所注册的中断处理子程序,irq_flags 为申请时的选项,devname 为指向设备名称的字符指针,dev_id 为申请时告诉系统的设备标识。若中断申请成功则返回0,失败则返回负值。
ret = request_irq(IRQ_TC, s wait_down_int();
#ifdef CONFIG_DEVFS_FS
模块的退出函数为s
接下来看一下A/D转换的中断处理函数: 在/kernel/include/linux/spinlock.h 文件中: #define spin_lock_irq(lock) do{local_irq_disable();spin_lock(lock);}while (0) #define DEBUG_SPINLOCKS 0 在/kernel/include/asm-arm/system.h 文件中: #define __sti() \ #define __cli() \ 最后用ARM 汇编指令实现了对IRQ 的使能和禁止。 if (tsdev.penStatus == PEN_UP)
也同上面的意思一样,首先禁止IRQ 中断。 下面先来看启动A/D转换的函数:
else if (adc_state == 1) |
一上来就根据变量tsdev.penStatus 的状态值进行判断,若为笔按下,将从A/D转换器中采集的x轴y轴坐标以及笔按下的状态存入变量tsdev 中的buf 成员中的相应变量中。
#ifdef HOOK_FOR_DRAG
ts_timer.expires = jiffies + TS_TIMER_DELAY;
add_timer(&ts_timer);
#endif
如果定义了笔拖曳,先将定时器的定时溢出值更新,然后调用add_timer 函数重新增加定时器计时,变量ts_timer 为struct timer_list 数据结构。
}
else
{
#ifdef HOOK_FOR_DRAG
del_timer(&ts_timer);
#endif
如果定义了笔拖曳,调用del_timer 函数来删除定时器,变量ts_timer 为struct timer_list 数据结构。
BUF_HEAD.x = 0;
BUF_HEAD.y = 0;
BUF_HEAD.pressure = PEN_UP;
}
若变量tsdev.penStatus 的状态值不是笔按下,则x轴y轴坐标写为0和笔抬起的状态一起存入变量tsdev 中的buf 成员中的相应变量中。
tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);
其中INCBUF 宏函数定义如下:
#define INCBUF(x,mod) ((++(x)) & ((mod) - 1))
由于这里MAX_TS_BUF=16,这样(mod) - 1)就为15(0x
if (q) {
unsigned long flags;
wq_read_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr, 0);
wq_read_unlock_irqrestore(&q->lock, flags);
}
}
宏函数wq_read_lock_irqsave 的作用主要就是保存IRQ 和FIQ 的中断使能状态,并禁止IRQ 中断;而宏函数wq_read_unlock_irqrestore 的作用就是恢复IRQ 和FIQ 的中断使能状态。现在可以得知__wake_up 这个函数的作用,它首先保存IRQ 和FIQ 的中断使能状态,并禁止IRQ 中断,接着调用__wake_up_common 函数来唤醒等待q 队列的进程,最后再恢复IRQ 和FIQ 的中断使能状态。
static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, const int sync)
该函数的作用是唤醒在等待当前等待队列的进程。参数q 表示要操作的等待队列,mode 表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE 或TASK_INTERRUPTIBLE 等。nr_exclusive 是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???
在/kernel/include/linux/wait.h 文件中:
struct __wait_queue_head {
wq_lock_t lock;
struct list_head task_list;
#if WAITQUEUE_DEBUG
long __magic;
long __creator;
#endif
};
typedef struct __wait_queue_head wait_queue_head_t;
这是等待队列数据结构。
# define wq_read_lock_irqsave spin_lock_irqsave
# define wq_read_unlock_irqrestore spin_unlock_irqrestore
看到这里可以知道其实宏函数wq_read_lock_irqsave 和wq_read_unlock_irqrestore 等价于宏函数spin_lock_irqsave 和spin_unlock_irqrestore,并直接将自己的参数传了下去。
在/kernel/include/linux/spinlock.h 文件中:
#define spin_lock_irqsave(lock, flags) do {local_irq_save(flags);spin_lock(lock);}while (0)
#define spin_unlock_irqrestore(lock, flags) do {spin_unlock(lock); local_irq_restore(flags); } while (0)
在这两个宏函数中,前面已经提到spin_lock 和spin_unlock 其实都为空函数,那么实际只执行了local_irq_save 和local_irq_restore 这两个宏函数。
在/kernel/include/asm-arm/system.h 文件中:
#define local_irq_save(x) __save_flags_cli(x)
#define local_irq_restore(x) __restore_flags(x)
这里local_irq_save 和local_irq_restore 这两个宏函数又分别等价于__save_flags_cli 和__restore_flags 这两个宏函数。
在/kernel/include/asm-arm/proc-armo/system.h 文件中:
#define __save_flags_cli(x) \
do { \
unsigned long temp; \
__asm__ __volatile__( \
" mov %0, pc @ save_flags_cli\n" \
" orr %1, %0, #0x08000000\n" \
" and %0, %0, #0x
" teqp %1, #0\n" \
: "=r" (x), "=r" (temp) \
: \
: "memory"); \
} while (0)
#define __restore_flags(x) \
do { \
unsigned long temp; \
__asm__ __volatile__( \
" mov %0, pc @ restore_flags\n" \
" bic %0, %0, #0x
" orr %0, %0, %1\n" \
" teqp %0, #0\n" \
: "=&r" (temp) \
: "r" (x) \
: "memory"); \
} while (0)
最后用ARM 汇编指令实现了对当前程序状态寄存器CPSR 中的IRQ 和FIQ 中断使能状态的保存和恢复。而且在__save_flags_cli 宏函数中,除了对IRQ 和FIQ 中断使能状态的保存外,还禁止了IRQ 中断。
wake_up_interruptible(&(tsdev.wq));
在这个tsEvent_raw 函数最后,调用wake_up_interruptible 函数来以中断模式唤醒等待tsdev.wq 队列的进程。
由于上面这个wake_up_interruptible 函数的作用还不是很明确,所以需要分析一下打开设备文件这个函数。
触摸屏打开设备文件函数定义如下:
static int s
tsdev.penStatus = PEN_UP;
首先是最简单的将变量tsdev.head 和tsdev.tail 这两个表示buf 头尾位置的值清零,然后将变量tsdev.penStatus 的状态值初始化为笔抬起。
#ifdef HOOK_FOR_DRAG init_timer(&ts_timer); ts_timer.function = ts_timer_handler;
#endif
如果定义了笔拖曳,先调用init_timer 函数来初始化一个定时器,变量ts_timer 为struct timer_list 数据结构,然后将定时调用的函数指针指向ts_timer_handler 函数。 tsEvent = tsEvent_raw; 将函数指针tsEvent 指向tsEvent_raw 函数。
在/kernel/include/linux/wait.h 文件中:
static inline void init_waitqueue_head(wait_queue_head_t *q) {
#if WAITQUEUE_DEBUG if (!q) WQ_BUG();
#end
if q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
INIT_LIST_HEAD(&q->task_list);
#if WAITQUEUE_DEBUG q->__magic = (long)&q->__magic;
q->__creator = (long)current_text_addr();
#endif }
该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。
init_waitqueue_head(&(tsdev.wq));
在这个s
MOD_INC_USE_COUNT;
return 0;
最后调用MOD_INC_USE_COUNT; 来对设备文件计数器加一计数,并返回再来分析一下用户层要调用的读取设备文件的接口函数:
static ssize_t s
这个函数实现的任务是将事件队列从设备缓存中读到用户空间的数据缓存中。实现的过程主要是通过一个循环,只有在事件队列的头、尾指针不重合时,才能成功的从tsdev.tail指向的队列尾部读取到一组触摸信息数据,并退出循环。否则调用读取函数的进程就要进入睡眠。
TS_RET ts_ret;
retry:
if (tsdev.head != tsdev.tail) {
int count;
count = tsRead(&ts_ret);
if (count) copy_to_user(buffer, (char *)&ts_ret, count);
return count; }
这个函数中,首先通过变量tsdev.head 和tsdev.tail 是否相等来判断环形缓冲区是否为空。若不相等则表示环形缓冲区中有触摸屏数据,调用tsRead 函数将触摸屏数据读入TS_RET 数据结构的ts_ret 变量中,该函数会在后面说明。 接下来调用copy_to_user 函数来把内核空间的数据复制到用户空间,在这里就是把驱动程序里的变量ts_ret 中的数据复制到用户程序的buffer 中,然后返回复制的数据长度。
else {
if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
interruptible_sleep_on(&(tsdev.wq));
if (signal_pending(current)) return -ERESTARTSYS;
goto retry; }
若变量tsdev.head 和tsdev.tail 相等,则表示环形缓冲区为空,首先根据file->f_flags 与上O_NONBLOCK 值来进行判断,若为O_NONBLOCK 值,则表示采用非阻塞的文件IO方法,立即返回,否则才会调用interruptible_sleep_on 函数,调用该函数的进程将会进入睡眠,直到被唤醒。关于O_NONBLOCK 值的含义在我总结的《IIS音频驱动程序分析》一文中有详细说明。
在/kernel/kernel/sched.c 文件中:
void interruptible_sleep_on(wait_queue_head_t *q) {
SLEEP_ON_VAR current->state = TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD schedule();
SLEEP_ON_TAIL }
常用的睡眠操作有interruptible_sleep_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。
关于interruptible_sleep_on 和wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。
如果进程被唤醒,则会继续跳到retry: 循环读取环形缓冲区,直到读取到一组触摸信息数据才会退出。 static int tsRead(TS_RET * ts_ret) 这个函数主要将环形缓冲区的x轴y轴坐标数据和笔的状态数据传入该函数形式参数所指的指针变量中。 spin_lock_irq(&(tsdev.lock)); 上文已经解释过了,
调用该宏函数来禁止IRQ 中断。
ts_ret->x = BUF_TAIL.x;
ts_ret->y = BUF_TAIL.y;
ts_ret->pressure = BUF_TAIL.pressure;
tsdev.tail = INCBUF(tsdev.tail, MAX_TS_BUF);
接着把变量tsdev 的环形缓冲区中相关数据赋值给该函数形式参数所指的指针变量中,并将表示环形缓冲区队列尾部的变量tsdev.tail 加一,这就意味着从环形队列中读取一组数据,尾指针加一。
spin_unlock_irq(&(tsdev.lock));
return sizeof(TS_RET);
上文已经解释过了,调用该宏函数来重新使能IRQ 中断,然后返回。
再来看一个定时器定时调用的函数:
#ifdef HOOK_FOR_DRAG
static void ts_timer_handler(unsigned long data){
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_DOWN) { start_ts_adc(); }
spin_unlock_irq(&(tsdev.lock)); }
#endif
这个函数需要定义过笔拖曳才有效。首先调用宏函数spin_lock_irq 来禁止IRQ 中断。 然后在变量tsdev.penStatus 状态为笔按下的时候,调用宏函数start_ts_adc 来启动A/D转换,转换的是X轴的坐标。 最后再调用宏函数spin_unlock_irq 来重新使能IRQ 中断
最后再来看一个释放设备文件的函数:
static int s
#ifdef HOOK_FOR_DRAG del_timer(&ts_timer);
#endif MOD_DEC_USE_COUNT;
return 0;
其实也很简单,在定义了笔拖曳的情况下,调用del_timer 函数来删除定时器,变量ts_timer 为struct timer_list 数据结构,然后调用MOD_DEC_USE_COUNT; 将设备文件计数器减一计数,并返回。
经过对整个触摸屏驱动程序的流程,以及S
首先在驱动模块初始化函数中,除了对驱动的字符设备的注册外,还要对中断进行申请。这里申请了两个触摸屏相关的中断,一个是IRQ_TC 中断,查阅了数据手册后了解到,该中断在笔按下时,由XP 管脚产生表示中断的低电平信号,而笔抬起是没有中断信号产生的。另一个是IRQ_ADC_DONE 中断,该中断是当芯片内部A/D转换结束后,通知中断控制器产生中断,这时就可以去读取转换得到的数据。当触摸屏按下后,就会出发中断,这时会调用申请中断时附带的s