Linux内核中断实验

实验说明:
        当按键按下时,发生中断,在中断函数里开启定时器计时。其中中断程序分为上下半部两部分,上半部进行处理中断程序的调度工作,下半部分开启定时器,在定时是到达设定时间以后,程序进入定时器处理函数,完成按键值的读取。

        在设备树下加入如下程序段:

key {
 			compatible = "atkalpha-key";
			pinctrl-names="default";
			pinctrl-0=<&pinctrl_key>; 
			cd-gpio=<&gpio1 18 GPIO_ACTIVE_LOW>;
			interrupt-parent=<&gpio1>;
			interrupts=<18 IRQ_TYPE_EDGE_BOTH>;
 			status = "okay";	
 		};

程序说明:

1、设置 interrupt-parent 属性值为“ gpio1 ”,因为 KEY0 所使用的 GPIO 为 GPIO1_IO18,也就是 设置 KEY0 的 GPIO 中断控制器为 gpio1
2、设置 interrupts 属性, 也就是设置中断源,指定中断号与触发方式 。第一个 cells 18 表示 GPIO1 组的 18 号 IO IRQ_TYPE_EDGE_BOTH中断触发方式。

Linux内核驱动程序:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>

#include <linux/cdev.h>
#include <linux/device.h>

#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/ioctl.h>

#define KEY_CNT 1
#define KEY_NAME "key"

#define KEYVALUE 0XFE
#define KEYNOVALUE 0

/*按键结构体*/
struct irq_keydesc{
    int key_gpio;/*IO编号*/
    int irqnum;/*中断号*/
    unsigned char value;/*键值*/
    char name[10];/*名字*/
    irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/*设备结构体*/
struct key_dev {
    int major;
    int minor;
    dev_t devide;//设备号
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node * bl_nd1;/*设备节点*/
    struct timer_list timer;/*定义一个定时器*/
    unsigned long timerperiod;/*定义一个周期*/
    atomic_t keyvalue;
    atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
    struct irq_keydesc irq_keydesc;/*按键结构体*/
    struct tasklet_struct tasklet;
};

struct key_dev key;
/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
 static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &key; /* 设置私有数据 */
	printk("irqkey open!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    struct key_dev *dev=(struct key_dev * )filp->private_data;
	return 0;
}


/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    struct key_dev *dev=(struct key_dev * )filp->private_data;
    int value;
    int valuerelease;
    int ret;
    value=atomic_read(&dev->keyvalue);
    valuerelease=atomic_read(&dev->releasekey);
    ret = copy_to_user(buf, &value, sizeof(value));
	return ret;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int key_release(struct inode *inode, struct file *filp)
{
    struct key_dev *dev=(struct key_dev * )filp->private_data;
	printk("key release!\r\n");
	return 0;
}


static struct file_operations key_ops={
    .owner=THIS_MODULE,
    .open=key_open,
    .release=key_release,
    .write=key_write,
    .read=key_read,
};


/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    int value=0;
    struct key_dev *dev=dev_id; 
    tasklet_schedule(&key.tasklet);
    return IRQ_HANDLED;
}

static void key_tasklet(unsigned long data)
{
    struct key_dev *dev=(struct key_dev *)data;
    printk("key_tasklet\n");
    dev->timer.data=data;
    mod_timer(&key.timer,jiffies+msecs_to_jiffies(20));/*开启定时器*/
}


/*定时器回调函数*/
void timer_function(unsigned long t)
{  
    struct key_dev *dev=(struct key_dev * )t;
    static int sta=0;
    int value=0;
    //dev->timerperiod=1000;
    unsigned long period;
    value = gpio_get_value(dev->irq_keydesc.key_gpio); 	/* 读取IO值 */
	
    if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 1);			
	}
    //mod_timer(&key.timer,jiffies+msecs_to_jiffies(period));
    printk("jiffies=%#x\n",jiffies);
    printk("keyvalue=%d\n",dev->keyvalue);
}

/*按键初始化*/
static int irqkey_init(void)
{
    int ret=0;

    /*获取设备节点*/
    key.bl_nd1=of_find_node_by_path("/key");
    if(key.bl_nd1==NULL)
    {
        printk("of_find_node_by_path failed\r\n");
        ret = -EINVAL;
        goto fail_nd;
    }
    /*获取设备cd-gpio属性的第一个GPIO编号*/
    key.irq_keydesc.key_gpio=of_get_named_gpio(key.bl_nd1,"cd-gpio",0);
    if(key.irq_keydesc.key_gpio<0)
    {
        printk("of_get_named_gpio failed\r\n");
        ret = -EINVAL;
        goto fail_naned;
    }

    /*申请一个GPIO管脚*/
    ret=gpio_request(key.irq_keydesc.key_gpio,"cd-gpio");
    if(ret==0){
        printk("gpio_requests success\n");
    }

    /*将GPIO设置为输出,默认输出高电平*/
    ret=gpio_direction_input(key.irq_keydesc.key_gpio);
    if(ret<0)
    {
        printk("gpio_direction_input failed\n");
        ret = -EINVAL;
        goto fail_input;
    }


    /*获取中断号*/
    key.irq_keydesc.irqnum = gpio_to_irq(key.irq_keydesc.key_gpio);

    /*按键中断初始化*/
    key.irq_keydesc.handler = key0_handler;
    key.irq_keydesc.value = KEYNOVALUE;

    ret = request_irq(key.irq_keydesc.irqnum, key.irq_keydesc.handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "irqkey", 				&key);
		if(ret < 0){
			printk("irq %d request failed!\r\n", key.irq_keydesc.irqnum);
			ret = -EFAULT;
            goto fail_irq;
        }

        /*初始化tasklet*/
        tasklet_init(&key.tasklet,key_tasklet,(unsigned long)&key);


    /*初始化定时器*/
    init_timer(&key.timer);
    key.timer.function=timer_function;
    //timerled.timer.expires=jiffies+msecs_to_jiffies(1000);
    key.timer.data=(unsigned long)&key;
    //timerled.timerperiod=5000;
    //add_timer(&timerled.timer);/*启动定时器*/ 

    return 0;
    fail_irq:
    fail_input:
         gpio_free(key.irq_keydesc.key_gpio);    
    fail_naned:
    fail_nd:
        return ret;
}
static int __init keey_init(void)
{
    /*初始化原子变量*/
    atomic_set(&key.keyvalue,0);
    printk("keyvalue0=%d\n",key.keyvalue);
    atomic_set(&key.releasekey, 0);
    int ret=0;
       
	/*注册设备号*/
    key.major=0;
    if(key.major){
        key.devide=MKDEV(key.major,0);
        register_chrdev_region(key.devide,KEY_CNT,KEY_NAME);
    }else{
        ret=alloc_chrdev_region(&key.devide, 0, KEY_CNT, KEY_NAME);	/* 申请设备号 */
		if(ret<0)
            {
                goto fail_devide;
            }
        key.major = MAJOR(key.devide);	/* 获取分配号的主设备号 */
		key.minor = MINOR(key.devide);	/* 获取分配号的次设备号 */
        printk("major=%d\n",key.major);
        printk("minor=%d\n",key.minor);
    }


     /*初始化cdev*/
    key.cdev.owner=THIS_MODULE;
    cdev_init(&key.cdev, &key_ops);
    
	
	/* 3、添加一个cdev */
	ret= cdev_add(&key.cdev, key.devide, KEY_CNT);
    if(ret<0)
        {
            goto fail_cdev;
        }

    //创建设备节点
	/* 4、创建类 */
	key.class = class_create(THIS_MODULE, KEY_NAME);
	if (IS_ERR(key.class)) {
		ret= PTR_ERR(key.class);
        goto fail_class;
	}

	/* 5、创建设备 */
	key.device = device_create(key.class, NULL, key.devide, NULL, KEY_NAME);
	if (IS_ERR(key.device)) {
		ret=PTR_ERR(key.device);
        goto fail_device;
	}  

    atomic_set(&key.keyvalue,KEYNOVALUE);
    ret=irqkey_init();
    if(ret<0)
    {
        printk("irqkey_init failed\n");
    }
   

    return 0;
    fail_device:
        class_destroy(key.class);
    fail_class:
        cdev_del(&key.cdev);
    fail_cdev:
        unregister_chrdev_region(key.devide, KEY_CNT);
    fail_devide:
        return ret;
}

static void __exit keey_exit(void)
{
    del_timer(&key.timer);
    gpio_free(key.irq_keydesc.key_gpio);
    free_irq(key.irq_keydesc.irqnum,&key);
   
    device_destroy(key.class,key.devide);
    class_destroy(key.class);
    cdev_del(&key.cdev);
    unregister_chrdev_region(key.devide, KEY_CNT);
    
    printk("exit\n");
    printk("exit\n");
}


/*模块的加载与卸载*/
module_init(keey_init);
module_exit(keey_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchuanqiang");

程序说明:
1、获取中断号key.irq_keydesc.irqnum = gpio_to_irq(key.irq_keydesc.key_gpio);

int gpio_to_irq(unsigned int gpio)

函数参数和返回值含义如下:

gpio:要获取的 GPIO 编号。

返回值:GPIO 对应的中断号。

2、ret = request_irq(key.irq_keydesc.irqnum, key.irq_keydesc.handler,

                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "irqkey", &key);

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

函数参数和返回值含义如下:

irq:要申请中断的中断号。

handler:中断处理函数,当中断发生以后就会执行此中断处理函数。

flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志。

name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。

dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经

被申请了。

3、 tasklet_init(&key.tasklet,key_tasklet,(unsigned long)&key);初始化 tasklet

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);

函数参数和返回值含义如下:

t:要初始化的 tasklet

functasklet 的处理函数。

data:要传递给 func 函数的参数

返回值:没有返回值。

4、 tasklet_schedule(&key.tasklet);在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行。

void tasklet_schedule(struct tasklet_struct *t)

函数参数和返回值含义如下:

t:要调度的 tasklet

5、struct key_dev key;变量key的传递:

中断触发时,static irqreturn_t key0_handler(int irq, void *dev_id)//dev_id=&key;

执行tasklet函数时,static void key_tasklet(unsigned long data)//data=&key;

执行定时器回调函数,void timer_function(unsigned long t)//t=&key。

6、该实验程序的运行顺序:

         当按键按下时,发生中断,在中断函数里开启定时器计时。其中中断程序分为上下半部两部分,上半部进行处理中断程序的调度工作,下半部分开启定时器,在定时是到达设定时间以后,程序进入定时器处理函数,完成按键值的读取。

中断使用方式:

(1)获取中断号 int gpio_to_irq(unsigned int gpio)

(2)编写中断处理函数

(3)申请中断

        int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

(4)初始化tasklet

        void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

  (5)在中断处理函数调用tasklet_schedule转到下半部

中断框架:

/* 定义 taselet */

struct tasklet_struct testtasklet;

/* tasklet 处理函数 */

void testtasklet_func(unsigned long data)

{

        /* tasklet 具体处理内容 */

}

/* 中断处理函数 */

irqreturn_t test_handler(int irq, void *dev_id)

{

        ......

        /* 调度 tasklet */

        tasklet_schedule(&testtasklet);

        ......

}

/* 驱动入口函数 */

static int __init xxxx_init(void)

{

        ......

        /* 初始化 tasklet */ 

        tasklet_init(&testtasklet, testtasklet_func, data);

        /* 注册中断处理函数 */

        request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);

        ......

}

7、中断的上半部与下半部:上半部直接在中断触发时就可响应,下半部则是将那些需要花费时间的中断处理放在下半部。

8、下半部的处理机制:软中断、tasklet、工作队列

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值