linux学习(3)--中断开发 -按键驱动

主要内容: 中断开发--按键驱动
1,linux中file, cdev, inode之间的关系
2,新的注册字符设备的方式
3, 中断申请
4, 文件io模型实现之阻塞和非阻塞
-----------------------------------------

linux中file和inode结构体的关系:


struct file对象:描述进程中打开open一个文件的信息:文件名,标志(可读写),文件偏移
open("/dev/led", O_RDWR|O_CREAT, 0666);
struct file {
    struct path        f_path;
    const struct file_operations    *f_op;
        unsigned int         f_flags;
    fmode_t            f_mode;
    loff_t            f_pos; //文件偏移
    void            *private_data;//万能指针
    
}


struct cdev对象:描述一个字符设备对象信息(设备号+文件操作对象),任何一个字符设备驱动都有该对象,
struct cdev {
    struct kobject kobj;// 基类
    struct module *owner;
    const struct file_operations *ops;//文件操作对象
    struct list_head list;// 链表
    dev_t dev; //设备号
    unsigned int count;
};

struct inode对象: 描述文件系统中的某个文件的属性(文件权限,类型,uid,gid,修改时间等)
struct inode {
    umode_t            i_mode;
    uid_t            i_uid;
    gid_t            i_gid;
    dev_t            i_rdev; //设备号
    const struct file_operations    *i_fop;
    struct timespec        i_atime;
    struct timespec        i_mtime;
    struct timespec        i_ctime;
}

int led_drv_open(struct inode *inode, struct file *filp)
{

    printk("-------^_^ %s-------\n", __FUNCTION__);

    static int a = 38;
    filp->private_data = &a;
    
    xxxxxxxx
}
long led_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{

    // 通过filp找到inode
    struct inode *node = filp->f_path.dentry->d_inode;
    //通过inode获取到注册设备号
    int minor = iminor(node);
    int major = imajor(node);
    //获取到数据
    int *p = (int *)filp->private_data;
    
    xxxxxxxx
}


2,新的注册字符设备的方式
    cdev_alloc(void)
    cdev_init(struct cdev * cdev,const struct file_operations * fops)
    cdev_add(struct cdev * p,dev_t dev,unsigned count)

    
    //静态申请设备号---仅仅是得到一个设备号而已
    // 参数1---设备号
    // 参数2---设备的个数
    // 参数3--描述字符串--/proc/devices
    //返回负数出错
    dev_t devno = MKDEV(260, 0);
    ret = register_chrdev_region(devno, 1, "key_new_drv");
    if(ret < 0)
    {
        printk(KERN_ERR"register_chrdev_region error\n");
        goto err_free;
    }

    // 动态分配一个struct cdev 对象
    key_dev->cdev = cdev_alloc();
    //初始化cdev中fops
    cdev_init(key_dev->cdev, &key_fops);
    //将当前cdev注册到系统中去
    //参数2---设备号
    //参数3--设备的个数,一般都填1
    cdev_add(key_dev->cdev, devno, 1);


    
3,申请中断:
    //  参数1---中断号
    // 中断号获取: IRQ_EINT(1)或者去找irqs.h
    //参数2--中断的处理方法irqreturn_t (*irq_handler_t)(int, void *);
    //参数3--中断的触发方式
    /*
        #define IRQF_TRIGGER_NONE    0x00000000 //内部中断触发
        #define IRQF_TRIGGER_RISING    0x00000001
        #define IRQF_TRIGGER_FALLING    0x00000002
        #define IRQF_TRIGGER_HIGH    0x00000004
        #define IRQF_TRIGGER_LOW    0x00000008
    */
    //参数3--表示一个字符串--自定义--/proc/interrupts
    // 参数4--参数给第二个参数的数据
    // 正确返回0
    request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char * name,void * dev)
    
    
    中断处理函数:
    irqreturn_t  key_irq_svc(int irqno, void *dev_id)
    {

        return IRQ_HANDLED;

    }

    
    
    
    //参数1--中断号码
    //参数2--和request_irq中最后一个参数保持一致
    free_irq(int irqno,void * dev_id)


    
    
    
    
实现阻塞: 默认情况,大部分的函数默认都是阻塞
      scanf()-- fgets()
      accept(); read/recv/recvfrom
    
    0---需要一个等待队列头
        struct __wait_queue_head wait_queue_head_t;
        init_waitqueue_head(struct wait_queue_head_t *q)
        
        
    // 参数1---表示等待队列头
    // 参数2---表示一个条件--如果为假,就在此休眠,如果为真,就不休眠
    1,根据条件可以让进程进入到休眠状态
        wait_event_interruptible(struct wait_queue_head_t wq, int condition)
        
    2, 资源可达的时候需要唤醒
        wake_up_interruptible(wait_queue_head_t *q)
    
   /***************************************************************************************************/

中断下半部的实现方法:

waitqueue,tasklet,work,软中断

tasklet:

struct tasklet_struct

tasklet_init

tasklet_schdule

tasklet_kill

工作队列:

struct work_struct *work

INIT_WORK

schdule_work

等待队列:

struct __wait_queue_head

init_waitqueue_head

wait_event_interruptible

wake_up_interruptible

/***************************************************************************************************/
非阻塞:
        在应用中设定非阻塞模式:
        int fd = open("/dev/key0", O_RDWR|O_NONBLOCK)
        
        read() 有数据就得到数据,没有数据就得到一个出错码--EAGAIN;
        -----------------------------------------------------------
        驱动:
            xxx_read
            {
                区分阻塞还是非阻塞
                if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
                {
                    return -EAGAIN;
                }

            }

代码示例:

/*******************key_app.c***********************/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h>

struct key_event{
	int code; //按键的名字---下键,回车键,ESC--KEY_ESC
	int value; //按键的状态---按下和抬起--1/0
};


int main(int argc, char *argv[])
{
	int ret;
	struct key_event event;

	int fd = open("/dev/key0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}


	while(1)
	{
		ret = read(fd, &event, sizeof(struct key_event));
		if(ret < 0)
		{
			perror("read");
			exit(1);
		}
	
		if(event.code == KEY_DOWN)
		{
			if(event.value)
			{
				printf("<APP>-------KEY_DOWN pressed\n");
			}else{
				printf("<APP>-------KEY_DOWN up\n");
			}
			
		}
		

	}



	return 0;
}
/***********************key_drv.c***************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/wait.h>

#include <asm/io.h>
#include <asm/uaccess.h>


//#define USE_STATIC_MAJOR 1
#define KEY_MAJOR  260


//设计一个按键数据包
struct key_event{
	int code; //按键的名字---下键,回车键,ESC--KEY_ESC
	int value; //按键的状态---按下和抬起--1/0
};


//设计一个全局的设备对象类
struct s5pv210_key{
	dev_t devno;
	int irqno;
	struct cdev *cdev;
	struct class *cls;
	struct device *dev;
	struct key_event event;

	wait_queue_head_t wq_head;
	int have_data; //表示一个标志,是否有数据
};


//声明一个对象

struct s5pv210_key *key_dev;



int key_drv_open(struct inode *inode, struct file *filp)
{

	printk("-------^_^ %s-------\n", __FUNCTION__);
	
	memset(&key_dev->event, 0, sizeof(struct key_event));
	key_dev->have_data = 0;
	
	return 0;

}

//  write(fd, buf, size);
ssize_t key_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{

	
	printk("-------^_^ %s-------\n", __FUNCTION__);
	// 区分应用的需求

	
	return 0;

}

int key_drv_close(struct inode *inode, struct file *filp)
{

	
	printk("-------^_^ %s-------\n", __FUNCTION__);

	return 0;
}




ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;

	//区分阻塞还是非阻塞
	if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
	{
		return -EAGAIN;
	}

	//判断是否有资源
	wait_event_interruptible(key_dev->wq_head,  key_dev->have_data);
	
	//将中断处理函数获取到数据给用户
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0)
	{
		printk("copy_to_user error\n");
		return -EFAULT;
	}
	

	//清空event
	memset(&key_dev->event, 0, sizeof(struct key_event));
	// 拷贝数据之后表示没有数据
	key_dev->have_data = 0;

	return count;
	
}


const struct file_operations key_fops = {
	.open = key_drv_open,
	.write = key_drv_write,
	.read = key_drv_read,
	.release = key_drv_close,
};


//表示当前的中断号码
irqreturn_t  key_irq_svc(int irqno, void *dev_id)
{
	printk("-------^_^ %s-------\n", __FUNCTION__);

	// 区分是按下还是抬起
		
	int value = gpio_get_value(S5PV210_GPH0(1));

	if(value){
		//抬起

		printk("<KERNEL>-------KEY_DOWN pressed\n");
		key_dev->event.code = KEY_DOWN;
		key_dev->event.value = 0;
		
	}else{
		printk("<KERNEL>-------KEY_DOWN up\n");
		key_dev->event.code = KEY_DOWN;
		key_dev->event.value = 1;
	}

	//此时就有数据
	key_dev->have_data = 1;
	//唤醒等待队列
	wake_up_interruptible(&key_dev->wq_head);
	

	return IRQ_HANDLED;

}


static int __init key_drv_init(void)
{

	/*
		编写驱动的套路
		0, 实例化全局的设备对象-- kzalloc
		1,  申请主设备号---register_chrdev
		2, 自动创建设备节点---class_create, device_create
		3, 初始化硬件--ioremap
		4,实现 file_operation

	*/ 
	
	// 模块加载函数中主要完成系统资源的申请
	printk("-------^_^ %s-------\n", __FUNCTION__);
	int ret;

	// 0, 实例化全局的设备对象
	//参数1---分配大小
	//参数2--分配的标志, GFP_KERNEL--如果当前暂时没有内存,会尝试等待
	key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
	if(key_dev == NULL)
	{
		printk(KERN_ERR"kzalloc error\n");
		return -ENOMEM;
	}
	
	
	// 1,  申请主设备号
	#ifdef USE_STATIC_MAJOR
		//静态申请设备号---仅仅是得到一个设备号而已
		// 参数1---设备号
		// 参数2---设备的个数
		// 参数3--描述字符串--/proc/devices
		//返回负数出错
		key_dev->devno = MKDEV(KEY_MAJOR, 32);
		ret = register_chrdev_region(devno, 1, "key_new_drv");
		if(ret < 0)
		{
			printk(KERN_ERR"register_chrdev_region error\n");
			goto err_free;
		}

	#else
		// 参数1---系统动态分配之后的设备号
		// 参数2--次设备号的起始值
		// 参数3---设备的个数
		// 参数4--描述字符串--/proc/devices
		//正确返回0
		ret = alloc_chrdev_region(&key_dev->devno,32, 1, "key_new_drv");
		if(ret != 0)
		{
			printk(KERN_ERR"register_chrdev_region error\n");
			goto err_free;
		}
	#endif

	// 动态分配一个struct cdev 对象
	key_dev->cdev = cdev_alloc();
	//初始化cdev中fops
	cdev_init(key_dev->cdev, &key_fops);
	//将当前cdev注册到系统中去
	//参数2---设备号
	//参数3--设备的个数,一般都填1
	cdev_add(key_dev->cdev, key_dev->devno, 1);



	// 2 ---自动创建设备节点

	//创建一个类
	// 参数1---当前模块--THIS_MODULE
	// 参数2---字符串,表示类的名字
	//返回值--struct class指针类型
	key_dev->cls = class_create(THIS_MODULE,"key_cls");
	if(IS_ERR(key_dev->cls))
	{
		printk("class_create error\n");
		ret = PTR_ERR(key_dev->cls);
		goto err_unregister;
		
	}

	//创建一个设备节点
	// 参数1---class_create返回的指针
	// 参数2---该设备非父类--一般都是填NULL
	//参数3--设备号--包含了主设备号major和次设备号minor 
	//参数4---私有数据指针---一般都是填NULL
	//参数5---设备节点的名字
	//结果  /dev/led
	// 返回值--struct device指针
	key_dev->dev = device_create(key_dev->cls, NULL,key_dev->devno, NULL, "key%d", 0);
	if(IS_ERR(key_dev->dev))
	{
		printk("device_create error\n");
		ret = PTR_ERR(key_dev->dev);
		goto err_class_destroy;
		
	}

	// 3, 初始化硬件---要么映射地址/申请中断
	//  参数1---中断号
	// 中断号获取: IRQ_EINT(1)或者去找irqs.h

	key_dev->irqno = IRQ_EINT(1);
		
	ret = request_irq(key_dev->irqno, key_irq_svc, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
						 "eint1-keydown", NULL);
	if(ret != 0)
	{
		printk("request_irq error\n");
		ret = -EBUSY;
		goto err_device_destrory;
	}


	//初始化等待队列头
	init_waitqueue_head(&key_dev->wq_head);
	
	return 0;


err_device_destrory:
	device_destroy(key_dev->cls, key_dev->devno);

err_class_destroy:
	class_destroy(key_dev->cls);

err_unregister:
	cdev_del(key_dev->cdev);
	unregister_chrdev_region(key_dev->devno, 1);

err_free:
	kfree(key_dev);
	return ret;

}


static void __exit key_drv_exit(void)
{

	printk("-------^_^ %s-------\n", __FUNCTION__);

	//释放中断
	//参数1--中断号码
	//参数2--和request_irq中最后一个参数保持一致
	free_irq(key_dev->irqno, NULL);
	
	// 模块卸载函数中主要完成系统资源的释放
	device_destroy(key_dev->cls, key_dev->devno);
	
	class_destroy(key_dev->cls);

	cdev_del(key_dev->cdev);
	unregister_chrdev_region(key_dev->devno, 1);
	
	kfree(key_dev);

}


module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");

猜你喜欢

转载自blog.csdn.net/linken_yue/article/details/82318990