03. Linux字符设备驱动--按键驱动

中断程序设计

  • 中断注册: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 项工作:

  1. 支持 F_SETOWN 命令,能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。不过此项工作已由内核完成,设备驱动无须处理。
  2. 支持 F_SETFL 命令的处理,每当 FASYNC 标志改变时,驱动程序中的 fasync()函数将得以执行。
    驱动中应该实现 fasync()函数。
  3. 在设备资源可获得时,调用 kill_fasync()函数激发相应的信号
    应用程序:
  4. fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁
  5. 应用程序会调用“fcntl()”这个函数,把进程的 PID 号告诉给驱动程序。
  6. 应用程序还要通过“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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值