分类: LINUX
2010-12-08 22:32:22
函数request_irq 是Linux 系统中驱动程序注册中断的方法。irq 为所要申请的硬件中断号,handler 为系统所注册的中断处理子程序,irq_flags 为申请时的选项,devname 为指向设备名称的字符指针,dev_id 为申请时告诉系统的设备标识。若中断申请成功则返回0,失败则返回负值。
ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, SA_INTERRUPT,
DEVICE_NAME, s3c2410_isr_adc);
调用该函数来进行A/D转换的中断注册,所要申请的硬件中断号为IRQ_ADC_DONE(62),在arch/irq s.h中定义;系统所注册的中断处理子程序为s3c2410_isr_adc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。
ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT,
DEVICE_NAME, s3c2410_isr_tc);
接着继续调用该函数来进行触摸的中断注册,所要申请的硬件中断号为IRQ_TC(61);系统所注册的中断处理子程序为s3c2410_isr_tc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。
wait_down_int();
调用该宏函数来设置为等待中断模式【笔按下产生中断】,具体定义如下:
#define wait_down_int() { ADCTSC = DOWN_INT | XP_PULL_UP_EN | \
XP_AIN | XM_HIZ | YP_AIN | YM_GND | \
XP_PST(WAIT_INT_MODE); }
用该宏函数来设置ADC 控制寄存器,参考S3C2410 芯片datasheet 中关于的章节,具体设置参数如下:
DOWN_INT = 1<<8 * 0 该位保留且应该设为0 【笔按下或笔抬起中断信号控制位,设为0 表示笔按下产生中断信号】
XP_PULL_UP_EN = 1<<3 * 0 上拉开关使能,设为0 表示XP 引脚上拉使能
XP_AIN = 1<<4 * 1 选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚
XM_HIZ = 1<<5 * 0 选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态
YP_AIN = 1<<6 * 1 选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚
#ifdef CONFIG_DEVFS_FS
devfs_ts_dir = devfs_mk_dir(NULL, "touchscreen", NULL);
devfs_tsraw = devfs_register(devfs_ts_dir, "0raw", DEVFS_FL_DEFAULT,
tsMajor, TSRAW_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,
&s3c2410_fops, NULL);
#endif
这里调用了devfs_mk_dir 函数,在设备文件系统中创建了一个名为touchscreen 的目录,并返回一个带有目录结构的数据结构变量devfs_ts_dir。将该变量作为下一步devfs_register 函数的参数,该参数在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入。
调用devfs_register 函数后,会在刚才创建的touchscreen 目录下再创建一个名为0raw 的设备文件节点。该函数的参数中,DEVFS_FL_DEFAULT 为该函数的标志选项,tsMajor 为注册字符设备时系统自动分配的主设备号,TSRAW_MINOR(1)为次设备号,S_IFCHR | S_IRUSR | S_IWUSR 为默认的文件模式,&s3c2410_fops 为传入内核的file_operations 结构中的函数接口,私有数据指针为空。返回一个devfs_handle_t 数据结构的变量devfs_tsraw,这会在调用设备文件系统注册清除函数devfs_unregister 时作为参数传入。
free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); 中国触摸屏网
free_irq(IRQ_TC, s3c2410_isr_tc);
最后依次注销A/D转换和定时器这两个已经申请的中断
接下来看一下A/D转换的中断处理函数:
static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg)
其中参数irq 为中断号,dev_id 为申请中断时告诉系统的设备标识,regs 为中断发生时寄存器内容。该函数在中断产生时由系统来调用,调用时以上参数已经由系统传入。
在/kernel/include/linux/spinlock.h 文件中:
#define spin_lock_irq(lock) do{local_irq_disable();spin_lock(lock);}while (0)
#define spin_unlock_irq(lock) do{spin_unlock(lock);local_irq_enable();}while(0)
#define DEBUG_SPINLOCKS 0
#if (DEBUG_SPINLOCKS < 1)
typedef struct { } spinlock_t;
#define SPIN_LOCK_UNLOCKED (spinlock_t) { }
#define spin_lock_init(lock) do { } while(0)
#define spin_lock(lock) (void)(lock)
#define spin_unlock_wait(lock) do { } while(0)
#define spin_unlock(lock) do { } while(0)
可见上面这四个宏函数都是空函数,这样的话spin_lock_irq(lock)和spin_unlock_irq(lock)这两个宏函数就相当于分别只调用了local_irq_disable();和local_irq_enable();两个宏函数。关于自旋锁的作用和概念可以参考一篇《Linux内核的同步机制》文章的相关章节。 99touch触摸屏资讯网
在/kernel/include/asm-arm/system.h 文件中:
#define local_irq_disable() __cli()
#define local_irq_enable() __sti()
在/kernel/include/asm-arm/proc-armo/system.h 文件中:
#define __sti() \
do { \
unsigned long temp; \
__asm__ __volatile__( \
" mov %0, pc @ sti\n" \
" bic %0, %0, #0x08000000\n" \
" teqp %0, #0\n" \
: "=r" (temp) \
: \
: "memory"); \
} while(0)
#define __cli() \
do { \
unsigned long temp; \
__asm__ __volatile__( \
" mov %0, pc @ cli\n" \
" orr %0, %0, #0x08000000\n" \
" teqp %0, #0\n" \
: "=r" (temp) \
: \
: "memory"); \
} while(0)
最后用ARM 汇编指令实现了对IRQ 的使能和禁止。
spin_lock_irq(&(tsdev.lock));
这样调用spin_lock_irq 宏函数,实际上只是做了local_irq_disable();一步,就是禁止IRQ 中断。
if (tsdev.penStatus == PEN_UP)
s3c2410_get_XY();
然后根据变量tsdev.penStatus 所处的状态,若为笔抬起则调用s3c2410_get_XY 函数来取得A/D转换得到的坐标值,该函数会在后面说明。
#ifdef HOOK_FOR_DRAG
else
s3c2410_get_XY();
#endif
这里表示如果定义了笔拖曳,且在笔没有抬起的情况下,继续调用s3c2410_get_XY 函数来得到最新的坐标值。
spin_unlock_irq(&(tsdev.lock));
最后调用spin_unlock_irq 宏函数,相当于只做了local_irq_enable();一步,来重新使能IRQ 中断。最后退出这个中断服务子程序。
继续来看一下另一个中断处理函数,即触摸中断处理函数:
static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg)
该函数的参数和上面A/D转换中断处理函数的定义一样,不再累赘。
spin_lock_irq(&(tsdev.lock));
也同上面的意思一样,首先禁止IRQ 中断。
if (tsdev.penStatus == PEN_UP) {
start_ts_adc();
}
接着根据变量tsdev.penStatus 的状态值判断是否进行A/D转换。若笔抬起,则调用函数start_ts_adc 来进行A/D转换,该函数会在后面说明。
else {
tsdev.penStatus = PEN_UP;
DPRINTK("PEN UP: x: d, y: d\n", x, y);
wait_down_int();
tsEvent();
}
如果变量tsdev.penStatus 的状态值不是笔抬起,则先将该变量状态设为笔抬起,然后调用宏函数wait_down_int()。该宏函数已在前面说明,用来设置为等待中断模式。最后调用tsEvent 函数指针所指的函数,在模块初始化函数s3c2410_ts_init 中,tsEvent 指向的是一个空函数tsEvent_dummy,而在打开设备函数s3c2410_ts_open 中,tsEvent 会指向tsEvent_raw 函数,该函数负责填充缓冲区,并唤醒等待的进程。该函数也会在后面加以说明。
中国触摸屏网99touch.com
spin_unlock_irq(&(tsdev.lock));
中断处理函数的最后一步都一样,重新使能IRQ 中断。退出中断服务子程序。
99Touch.com中国触摸屏
else if (adc_state == 1)
{
adc_state = 0;
disable_ts_adc();
x = (ADCDAT1 & 0x3ff);
tsdev.penStatus = PEN_DOWN;
DPRINTK("PEN DOWN: x: d, y: d\n", x, y);
wait_up_int();
tsEvent();
}
若查看A/D转换的状态变量,若为1 表示进行过Y坐标的A/D转换,将该变量设为0。然后调用宏函数disable_ts_adc 来禁止通过读操作启动A/D转换。
接着将x = (ADCDAT1 & 0x3ff); 这一步将Y坐标的ADC转换数据寄存器的D9~D0 这10为读出到变量x(这里由于是竖屏,参考原理图后知道,硬件连线有过改动,将XP,XM 和YP,YM 进行了对换,这样ADCDAT1 里读出的是XP,XM 方向电阻导通的值,也就是x轴坐标值)。
随后将变量tsdev.penStatus 的状态值改为笔按下,并调用wait_up_int 宏函数来设置为等待中断模式【笔抬起产生中断】,具体定义如下:
#define wait_up_int() { ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN | XM_HIZ | \ 中国触摸屏网99touch.com
YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); }
用该宏函数来设置ADC 控制寄存器,参考S3C2410 芯片datasheet 中关于的章节,具体设置参数如下:
UP_INT = 1<<8 * 1 该位保留且应该设为0,这里设为1 不知道为什么 【笔按下或笔抬起中断信号控制位,设为1 表示笔抬起产生中断信号】
XP_PULL_UP_EN = 1<<3 * 0 上拉开关使能,设为0 表示XP 引脚上拉使能
XP_AIN = 1<<4 * 1 选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚
XM_HIZ = 1<<5 * 0 选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态
YP_AIN = 1<<6 * 1 选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚
YM_GND = 1<<7 * 1 选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地
XP_PST(WAIT_INT_MODE); = 3 X坐标Y坐标手动测量设置,设为3 表示等待中断模式
最后调用函数指针tsEvent 所指向的函数。在s3c2410_get_XY 函数里面,应该表示这个驱动的设备文件已经打开,在打开设备文件函数中,tsEvent 函数指针就指向了tsEvent_raw 这个函数,也就是说,下面执行的是tsEvent_raw 函数。tsEvent_raw 函数负责填充缓冲区,并唤醒等待的进程,该函数会在后面说明。
99touch.com中国触摸屏
------------------------------------------------------------------------
static void tsEvent_raw(void)
来看一下tsEvent_raw 这个函数。
if (tsdev.penStatus == PEN_DOWN)
{
BUF_HEAD.x = x;
BUF_HEAD.y = y;
BUF_HEAD.pressure = PEN_DOWN;
一上来就根据变量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 数据结构。 中国触摸屏网99touch.com
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(0x0F),所以这个宏函数相当于就是将变量tsdev.head 这个表示buf 头位置的值加1,然后取模16,即指向下一个buf ,形成一个在环形缓冲区上绕环的头指针。
在/kernel/include/linux/sched.h 文件中:
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
该宏函数定义为__wake_up 函数,参数TASK_INTERRUPTIBLE 为1,表示要唤醒的任务的状态为中断模式,参数1 表示要唤醒的互斥进程数目为1。
对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。关于interruptible_sleep_on 和wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。 99触摸屏
在/kernel/kernel/sched.c 文件中:
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
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表示???
99touch触摸屏
在/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, #0x0c000000\n" \
" 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, #0x0c000000\n" \
" 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 队列的进程。