中断程序设计
- 中断注册:request_irq()
- 中断处理:
检查是否产生中断
清除中断标志
硬件操作 - 注销中断: free_irq()
中断分层技术
工作队列:将中断的下半部提交到工作队列执行。
- 创建工作:INIT_WORK()
- 提交工作到内核默认队列:schedule_work()
利用定时器延时使按键去抖动
- 定义定时器变量
- 初始化定时器
init_timer初始化
设置超时函数 - add_timer 注册定时器
- mod_timer 启动定时器
设置时间
mod_timer
阻塞型驱动设计
内核等待队列
- 定义+初始化等待队列:DECLARE_WAIT_QUEUE_HEAD()
- 进入等待队列,睡眠:wait_event_interruptible()
- 从等待队列中唤醒进程:wake_up_interruptible()
POLL 机制
为了减少CPU资源的占用率,在编写驱动函数中添加poll机制
比如一个按键事件:
1、查询方法:一直在查询,不断去查询是否有事件发生,整个过程都是占用CPU资源,消耗CPU资源非常大。
2、中断方式:当有事件发生时,就去跳转到相应事件去处理,CPU占用时间少。
3、poll方式: 中断方式虽然占用CPU资源少,但是在应用程序上需要不断在死循环里面执行读取函数,应用程序不能去做其它事情。poll机制解决了这个问题,当有事件发生时,才去执行读read函数,按键事件没有按下时,超过时间后返回,去执行其它的处理函数
poll实现步骤:
- 在驱动函数file_operation结构体上添加一个.poll函数,然后在函数里执行poll_wait,这个函数用来判断硬件事件是否发生
- 测试程序需要调用ret = poll(fds, 1, 5000)函数来获取事件发生信息。
异步通知
为了使设备支持异步通知机制,驱动程序中涉及以下 3 项工作:
- 支持 F_SETOWN 命令,能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。不过此项工作已由内核完成,设备驱动无须处理。
- 支持 F_SETFL 命令的处理,每当 FASYNC 标志改变时,驱动程序中的 fasync()函数将得以执行。
驱动中应该实现 fasync()函数。 - 在设备资源可获得时,调用 kill_fasync()函数激发相应的信号
应用程序: - fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁
- 应用程序会调用“fcntl()”这个函数,把进程的 PID 号告诉给驱动程序。
- 应用程序还要通过“F_GETFL”读出“flags”,在 flags 上置上“FASYNC”位。
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变 fasync 标记, 最终会调用到驱动的 faync > fasync_helper:初始化/释放 fasync_struct
完整的字符设备驱动编写
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
int major = 0;
static struct class *key_class = NULL;
static struct device *key_device = NULL;
/* 定义+初始化等待队列*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 异步通知 */
static struct fasync_struct *button_async;
static struct button_irq_desc *irq_pd;
/* 定义关于中断硬件操作的变量*/
struct button_irq_desc {
int irq; /* 中断号 */
int pin; /* GPIO引脚 */
int key_val; /* 按键初始值 */
char *name; /* 名字 */
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT(16), S5PV210_GPH2(0), 0x01, "S1"}, /* S1 */
{IRQ_EINT(17), S5PV210_GPH2(1), 0x02, "S2"}, /* S2 */
{IRQ_EINT(18), S5PV210_GPH2(2), 0x03, "S3"}, /* S3 */
{IRQ_EINT(19), S5PV210_GPH2(3), 0x04, "S4"}, /* S4 */
{IRQ_EINT(24), S5PV210_GPH3(0), 0x05, "S5"}, /* S5 */
{IRQ_EINT(25), S5PV210_GPH3(1), 0x06, "S6"}, /* S6 */
{IRQ_EINT(26), S5PV210_GPH3(2), 0x07, "S7"}, /* S7 */
{IRQ_EINT(27), S5PV210_GPH3(3), 0x08, "S8"}, /* S8 */
};
struct button_irq_desc *button_irq = NULL;
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04,0x05,0x06,0x07,0x08 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84,0x85,0x86,0x87,0x88 */
static unsigned char key_val = 0;
/* 定义工作变量*/
struct work_struct *work1 = NULL;
/* 定义定时器变量*/
struct timer_list key_timer;
/* 中断事件标志, 中断处理函数将它置1,read函数将它置0 */
static volatile int ev_press = 0;
/* 中断函数*/
irqreturn_t key_interrupt(int irq, void *dev_id)
{
irq_pd = (struct button_irq_desc *)dev_id;
/* 检查是否产生中断(共享中断才用)*/
/* 清除中断标志*/
ev_press = 1;
/* 中断下半部: 将产生中断的中断号传递到工作函数,提交工作(到内核默认的工作队列)*/
schedule_work(work1);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 工作调度函数 */
void work1_fun(struct work_struct *work)
{
/* 启动定时器 */
mod_timer(&key_timer, (jiffies + HZ/10));
}
/* 定时器超时函数 */
void key_timer_function(unsigned long data)
{
unsigned int pinval = 0;
struct button_irq_desc *button_irqs = irq_pd;
/* 获取产生中断的引脚值 */
pinval = gpio_get_value(button_irqs->pin);
if(pinval == 1)/* 按键松开 */
{
key_val = 0x80 | button_irqs->key_val;
}
else/* 按键按下 */
{
key_val = button_irqs->key_val;
}
/* 唤醒休眠的进程 */
wake_up_interruptible(&button_waitq);
/* 用 kill_fasync 函数告诉应用程序,有数据可读了
* button_async 结构体里包含了发给谁(PID 指定)
* SIGIO : 表示要发送的信号类型
* POLL_IN: 表示发送的原因(有数据可读了)
*/
kill_fasync (&button_async, SIGIO, POLL_IN);
}
static int key_open(struct inode * inode, struct file * filp)
{
int i = 0;
int err = 0;
/* 使用 request_irq 函数注册中断 */
for(i = 0; i < sizeof(button_irqs) / sizeof(button_irqs[0]); i++)
{
err = request_irq(button_irqs[i].irq, key_interrupt, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, button_irqs[i].name, (void *)&button_irqs[i]);
}
if(err)
{
i--;
while(i--)
{
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY;
}
/* 创建工作 */
work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work1, work1_fun);
/* 初始化定时器 */
init_timer(&key_timer);
key_timer.function = key_timer_function;
/* 向内核注册一个定时器*/
add_timer(&key_timer);
return 0;
}
ssize_t key_read (struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if(size != 1)
return -EINVAL;
/*
* 如果没有按键动作, 休眠,即不会马上执行copy_to_user
* ev_press = 0时,进程会休眠,当有按键动作时,
* 会进入按键中断处理函数,里面将ev_press = 1,
* 然后唤醒进程,然后马上执行copy_to_user,继续往下跑。
*/
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按键动作, 返回键值给应用程序 */
if(copy_to_user(buf, &key_val, 1))
{
return -EFAULT;
}
ev_press = 0;
return 1;
}
int key_close (struct inode *node, struct file *file)
{
int i = 0;
/* 注销中断 */
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return 0;
}
unsigned int key_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
/* 该函数,只是将进程挂在 button_waitq 队列上,而不是立即休眠 */
poll_wait(file, &button_waitq, wait);
/* 当没有按键按下时,即不会进入按键中断处理函数,此时 ev_press = 0
* 当按键按下时,就会进入按键中断处理函数,此时 ev_press 被设置为 1
*/
if (ev_press)
mask |= POLLIN | POLLRDNORM; /* POLLIN 表示有数据可读 */
/* 如果有按键按下时, mask |= POLLIN | POLLRDNORM,否则 mask = 0 */
return mask;
}
static int key_fasync (int fd, struct file *filp, int on)
{
printk("driver: key_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.release = key_close,
.poll = key_poll,
.fasync = key_fasync,
};
/* 驱动程序的入口函数 */
static int __init Key_init(void)
{
/* 注册字符设备,第一个参数设置为 0 表示由系统自动分配主设备号 */
major = register_chrdev(0, "key_drv", &key_fops);
/* 创建 key_drv 类 */
key_class = class_create(THIS_MODULE, "key_drv");
/* 在 key_drv 类下创建/dev/key 设备,供应用程序打开设备*/
key_device = device_create(key_class, NULL, MKDEV(major, 0), NULL, "key");
return 0;
}
/* 驱动程序的出口函数 */
static void __exit Key_exit(void)
{
unregister_chrdev(major,"key_drv"); /* 注销字符设备 */
device_unregister(key_device); /* 卸载类下的设备 */
class_destroy(key_class); /* 卸载类 */
}
/* 用于修饰入口/出口函数,换句话说,相当于
* 告诉内核驱动程序的入口/出口函数在哪里
*/
module_init(Key_init);
module_exit(Key_exit);
/* 该驱动支持的协议 */
MODULE_LICENSE("GPL");
测试用例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000);
}
return 0;
}