全部博文(404)
分类: LINUX
2008-09-25 16:13:41
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年暑假作的一个项目,过了很久才写些总结,好忘了再看!