Linux驱动之中断系统

一、Linux中断

​ 在裸机中断中我们需要做很多工作,比如关闭MMU,Cache等配置寄存器,使能中断。但是linux内核提供了完善的中断框架,我们只需要申请中断request_irq,然后注册中断处理函数即可。使用很方便。最后还应释放中断free_irq

​ 我们还会经常使用到使能禁能中断。

​ 如果有两个任务,A-B。当A先禁能中断10s,B后禁能2s,B要使能以后就会打乱A的节奏。所以要需要查看一下当前的中断状态flags,B才会决定是否要禁能2s后使能,否则A就会将系统崩溃。

#include<linux/interrupt.h>

static inline int __must_check 
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)		//中断请求函数
void free_irq(unsigned int, void *);		//中断释放函数

void disable_irq(unsigned int irq);			//禁能中断
void enable_irq(unsigned int irq);			//使能中断
void disable_irq_nosync(unsigned int irq);

local_irq_disable()		   				   //禁能当前处理器的中断
local_irq_save(flags) 					   //保存中断状态
local_irq_restore(flags)				   //恢复中断状态

二、中断上下半部

​ 中断分为上半部与下半部。一般我们所说的中断都是上半部的,中断要求快速执行。但是如果我们处理一些比较浪费时间的程序,就要使用中断下半部了。

​ 注:irq_request函数是申请中断上半部的。

1.软中断

​ linux内核引入软中断与tasklet。软中断使用softirq_action结构体。软中断机制就是“谁触发,谁执行”。因此要想使用软中断,就要注册对应的软中断函数,使用void open_softirq函数去注册。注册以后触发函数。注册号后通过raise_softirq触发中断。

​ 软中断在编译的时候要静态编译,使用**softirq_init(void)**初始化中断。

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

static struct softirq_action softirq_vec[NR_SOFTIRQS];
enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};
void open_softirq(int nr, void (*action)(struct softirq_action *));
void raise_softirq(unsigned int nr);
void softirq_init(void);

2.tasklet

​ tasklet_struct结构体用于定义中断下半部,然后初始化。初始化也可以使用DECLARE_TASKLET宏直接定义并且完成初始化。

tasklet_schedule调度中断下半部。

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};
DECLARE_TASKLET(name, func, data);
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
static inline void tasklet_schedule(struct tasklet_struct *t);

3.工作队列

​ 工作队列是一种中断下半部的执行方式。workqueue_struct结构体定义。也是初始化,调度等等。

三、设备树描述中断

​ 首先compatible中有一些选项,我们的芯片是“arm,cotex-a7-gic”,因此选择这个。

​ interrupt-controller描述结点是中断控制器。

​ #interrupt-cells描述了中断源的由3个cells组成。第一个代表SPI,PPI还是;第二个代表中断号;第三个代表触发类型。

1.设备树文件描述

​ 举个例子:gpio1中描述中断的信息是interrupts,它是SPI的中断,中断号为66,67,高电平触发中断类型。

intc: interrupt-controller@00a01000 {
	compatible = "arm,cortex-a7-gic";
	#interrupt-cells = <3>;
	interrupt-controller;
	reg = <0x00a01000 0x1000>,
	      <0x00a02000 0x100>;
};

gpio1: gpio@0209c000 {
		compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
		reg = <0x0209c000 0x4000>;
		interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
fxls8471@1e {
	compatible = "fsl,fxls8471";
	reg = <0x1e>;
	position = <0>;
	interrupt-parent = <&gpio5>;
	interrupts = <0 8>;			GPIO5_00 , 低电平触发
};

2.获取中断号

​ 通用的获取中断号函数

unsigned int irq_of_parse_and_map(struct device_node *dev,int index);

​ 对于GPIO也有API函数获取中断号,因为GPIO是经常使用的。返回中断号。

int gpio_to_irq(unsigned int gpio);

四、按键中断

1.修改设备树

​ 主要是添加interrupt-parentinterrupts

/*
    IRQ_TYPE_NONE		= 0x00000000,
    IRQ_TYPE_EDGE_RISING	= 0x00000001,
    IRQ_TYPE_EDGE_FALLING	= 0x00000002,
    IRQ_TYPE_EDGE_BOTH	= (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
*/
key{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "pintitus,key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_gpio_keys>;
	key-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
	status = "okay";
	interrupt-parent = <&gpio5>;
	interrupts = <1 IRQ_TYPE_EDGE_BOTH>
};

2.中断配置程序

结构体

​ 设备结构体,用于初始化按键

/*key结构体*/
struct irq_keydesc{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int , void*);        /*中断处理函数*/
} 

/*设备结构体*/
struct dev{
    dev_t   devid;        /*设备号*/
    int     major;        /*主设备号*/
    int     minor;        /*次设备号*/
    struct  cdev cdev;    /*字符设备*/
    struct class *class;  /*设备的类*/
    struct device *device;/*设备*/
    struct device_node *nd;/*设备结点*/
    
    int    timeperiod;     /*定时周期*/
    struct timer_list timerdev;    /*timer设备*/
    
    struct irq_keydesc irqkey[key_CNT];
};

初始化按键中断

​ 1.注册设备

​ 2.按键初始化 , 定时器初始化

​ 3.中断初始化

​ 4.中断处理函数编写

static int __init key_module_init(void){
    int ret = 0;
    /*  1.注册字符设备    */
    if(key.major){
        key.devid = MKDEV(key.major , 0);
        ret = register_chrdev_region(key.devid , key_CNT , key_NAME);
    }else{
        ret = alloc_chrdev_region(&key.devid , 0 , key_CNT , key_NAME);
        key.major = MAJOR(key.devid);
        key.minor = MINOR(key.devid);
    }
    if(ret < 0){
        goto failed_devid;
    }
    printk("key.major = %d , key.minor = %d \r\n" ,key.major , key.minor );

    /*  2.添加字符设备  */
    key.cdev.owner = THIS_MODULE;
    cdev_init( &key.cdev , &key_fops);
    ret = cdev_add(&key.cdev , key.devid , key_CNT);
    if(ret < 0){
        goto failed_cdev;
    }

    /*  自动创建设备结点    */
    /*  3.创建设备的类    */
    key.class = class_create(THIS_MODULE , key_NAME);
    if(IS_ERR(key.class)){
        ret = PTR_ERR(key.class);
        goto failed_class;
    }
    /*    4.创建设备    */
    key.device = device_create(key.class , NULL , key.devid , NULL ,key_NAME);
    if(IS_ERR(key.device)){
        ret = PTR_ERR(key.device);
        goto failed_device;
    }

    /*  按键初始化  */
    key.nd = of_find_node_by_path("/key");
    if(key.nd == NULL){
        ret = -EINVAL;
        goto failed_findnd;
    }

    key.irqkey[0].gpio = of_get_named_gpio(key.nd , "key-gpios" , 0);
    memset(key.irqkey[0].name , 0 ,sizeof(key.irqkey[0].name));
    key.irqkey[0].name[0] = "KEY";
    gpio_request(key.irqkey[0].gpio ,key.irqkey[0].name);
    gpio_direction_input(key.irqkey[0].gpio);

    /* 获取中断号 */
#if 1
    key.irqkey[0].irqnum = gpio_to_irq(key.irqkey[0].gpio);
#else
    key.irqkey[0].irqnum = irq_of_parse_and_map(key.nd , 0);
#endif
    /*  中断初始化    */
    key.irqkey[0].handler = key_irqhandler;
    key.irqkey[0].value = KEY_VALUE;

    ret = request_irq(key.irqkey[0].irqnum , key.irqkey[0].handler , 
                IRQF_TRIGGER_FALLING||IRQF_TRIGGER_RISING ,
                key.irqkey[0].name , &key);
    if(ret){
        printk("irq %d request failed!\r\n" , key.irqkey[0].irqnum);
        goto failed_irq;
    }
    /*  定时器初始化    */
    init_timer(&key.timerdev);
    key.timerdev.function = timer_handler;

    return 0;
    
failed_irq:
failed_findnd:
    device_destroy(key.class , key.devid);
    printk("failed_findnd\r\n");
failed_device:
    class_destroy(key.class);
    printk("failed_device\r\n");
failed_class:
    cdev_del(&key.cdev);
    printk("failed_class\r\n");
failed_cdev:
    unregister_chrdev_region(key.devid , key_CNT);
    printk("failed_cdev\r\n");
failed_devid:
    return ret;
}

按键中断函数编写

​ 开启定时器用于消抖

/*  
    按键中断函数
*/
static irqreturn_t key_irqhandler(int irq, void *dev_id){
    struct struct_dev *dev = dev_id;

    //触发按键中断以后,给定消抖定时器参数
    dev->timerdev.data = (volatile long)dev_id;
    mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

定时器处理函数

​ 获取按键的IO值,输出打印信息。

/*
    Timer处理函数
*/
static void timer_handler(unsigned long dummy){
    int value = 0;
    struct struct_dev *dev = (struct struct_dev*)dummy;
    
    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 1){         /*按下*/
        printk("Key Down! \r\n");
    }else if(value == 0){
        printk("Key Up! \r\n");
    }
}

五、中断下半部使用

1.tasklet的使用

​ 首先定义tasklet结构体,然后初始化tasklet。

​ 初始化函数第一个参数传入tasklet结构体变量;第二个参数是tasklet处理函数,第三个参数是tasklet处理函数传入的参数。

tasklet_init(&key.tasklet , Key_irq_tasklet  , &key);

​ 然后在想要调用中断下半部去调度tasklet

tasklet_schedule(&dev->tasklet);

​ 最后编写tasklet处理函数

/*
    按键中断下半部
*/
static void Key_irq_tasklet(unsigned long *data){
    struct struct_dev *dev = (struct struct_dev *)data;

    dev->timerdev.data = (volatile long)data;
    mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));
}

2.工作队列的使用

​ 类似于tasklet。依旧是定义work_struct变量,初始化工作队列,调度函数,编写处理函数。

	/*work*/
    struct work_struct work;
    
    INIT_WORK(&key.work , Key_irq_workqueue );
    
    schedule_work(&dev.work);
    
/*
    工作队列的处理函数
*/
static void Key_irq_workqueue(struct work_struct *work){
    struct struct_dev *dev = container_of(work, struct struct_dev, work);
    
    dev->timerdev.data = (unsigned long)dev;
    mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值