Linux驱动开发学习笔记【8】:Linux中断系统

目录

一、Linux内核中断处理过程

1.1、裸机中断

1.2、linux中断

二、linux中断的上半部和下半部

2.1 软中断

2.2 tasklet

2.3 工作队列

2.4 中断线程化

三、设备树中的中断节点信息

四、获取中断号

五、按键输入带定时器消抖程序编写

1、GPIO输入中断上半部、下半部(tasklet或work)参考使用示例


一、Linux内核中断处理过程

1.1、裸机中断

(1)使能中断,初始化相应的寄存器。

(2)注册中断服务函数,也就是向 irqTable数组的指定标号处写入中断服务函数

(3)中断发生以后进入 IRQ中断服务函数,在 IRQ中断服务函数在数组 irqTable里面查找具体的中断处理函数,找到以后执行相应的中断处理函数。

1.2、linux中断

(1)先知道要使用的中断对应的中断号

(2)申请 request_irqrequest_irq 函数会激活 (使能 )中断,所以不需要我们手动去使能中断

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)如果不用中断了,那就要使用 free_irq 释放点

(4)编写中断处理函数 irqreturn_t (*irq_handler_t) (int, void *),返回值如下:

enum irqreturn { 
    IRQ_NONE = (0 << 0), 
    IRQ_HANDLED = (1 << 0), 
    IRQ_WAKE_THREAD = (1 << 1), 
};

(5)使能和禁止中断

void enable_irq(unsigned int irq) 
void disable_irq(unsigned int irq)

enable_irq disable_irq 用于使能和禁止指定的中断, irq 就是要禁止的中断号。disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数,此函数调用后会立即返回,不会等待当前中断处理程序执行完毕。

void disable_irq_nosync(unsigned int irq)

有时候我们需要关闭当前处理器的整个中断系统,这个时候可以使用如下两个函数,但是如果有两个中断A和B同时需要关闭一段时间后开启,A间隔时间长,B间隔时间短一些,那么在B的间隔时间到后开启中断的同时,A中断也被开启了,这时A不想开启的。

local_irq_enable() 
local_irq_disable()

这时可以使用如下2个函数,在禁止中断时会保存当前的中断状态,在使能该中断时将中断的状态恢复到禁止前的状态,不会影响其他的中断使能。local_irq_save函数用于禁止中断,并且将中断状态保存在 flags中。local_irq_restore用于恢复中断,将中断到 flags状态。

local_irq_save(flags) 
local_irq_restore(flags)

二、linux中断的上半部和下半部

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。

下半部:如果中断处理过 程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。那么linux中的中断下半部是怎么实现的呢?可以通过如下3种方法

2.1 软中断

Linux内核使用结构体 softirq_action表示软中断, softirq_action结构 体定义在文件 include/linux/interrupt.h中,内容如下:

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

在 kernel/softirq.c文件中一共定义了 10个软中断,如下所示:

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
};

要使用软中断,就要使用函数 open_softirq 先注册,注册好软中断以后需要通过 raise_softirq 函数触发,软中断必须在编译的时候静态注册! Linux内核使用 softirq_init 函数初始化软中断,softirq_init函数定义在 kernel/softirq.c文件里面。

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

2.2 tasklet

tasklet是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet之间,建议大家使用 tasklet。也需要用到上半部,只是需要在上半部的中断处理函数中调用tasklet_secedule来出发下半部的tasklet。使用步骤:

第一步:定义一个 tasklet结构体

第二步:编写对应的中断处理函数

第三步:初始化、设置对应的处理函数

2.3 工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断

2.4 中断线程化

https://blog.csdn.net/viewsky11/article/details/72780324

三、设备树中的中断节点信息

如果使用设备树的话就需要在设备树中设置好中断属性信息, Linux内核通过读取设备树中的中断属性信息来配置中断。对于中断控制器而言,设备树绑定信息参考文档路径为:Documentation/devicetree/bindings/arm/gic.txt

Primary GIC is attached directly to the CPU and typically has PPIs and SGIs.
Secondary GICs are cascaded into the upward interrupt controller and do not
have PPIs or SGIs.

Main node required properties:

- compatible : should be one of:
	"arm,gic-400"
	"arm,cortex-a15-gic"
	"arm,cortex-a9-gic"
	"arm,cortex-a7-gic"
	"arm,arm11mp-gic"
	"brcm,brahma-b15-gic"
	"arm,arm1176jzf-devchip-gic"
	"qcom,msm-8660-qgic"
	"qcom,msm-qgic2"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
  interrupt source.  The type shall be a <u32> and the value shall be 3.

  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
  interrupts.

  The 2nd cell contains the interrupt number for the interrupt type.
  SPI interrupts are in the range [0-987].  PPI interrupts are in the
  range [0-15].

  The 3rd cell is the flags, encoded as follows:
	bits[3:0] trigger type and level flags.
		1 = low-to-high edge triggered
		2 = high-to-low edge triggered (invalid for SPIs)
		4 = active high level-sensitive
		8 = active low level-sensitive (invalid for SPIs).
	bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
	the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
	the interrupt is wired to that CPU.  Only valid for PPI interrupts.
	Also note that the configurability of PPI interrupts is IMPLEMENTATION
	DEFINED and as such not guaranteed to be present (most SoC available
	in 2014 seem to ignore the setting of this flag and use the hardware
	default value).

- reg : Specifies base physical address(s) and size of the GIC registers. The
  first region is the GIC distributor register base and size. The 2nd region is
  the GIC cpu interface register base and size.

Optional
- interrupts	: Interrupt source of the parent interrupt controller on
  secondary GICs, or VGIC maintenance interrupt on primary GIC (see
  below).

- cpu-offset	: per-cpu offset within the distributor and cpu interface
  regions, used when the GIC doesn't have banked registers. The offset is
  cpu-offset * cpu-nr.
intc: interrupt-controller@00a01000 {
		compatible = "arm,cortex-a7-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
		reg = <0x00a01000 0x1000>,
		      <0x00a02000 0x100>;
};
 
 gpio5: gpio@020ac000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x020ac000 0x4000>;
    interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

#interrupt-cells表示此中断控制器下设备的 cells大小,对于设备而言,会使用 interrupts属性描述中断信息,interrupt-cells描述了 interrupts属性的cells大小,也就是一条信息有几个cells。

第一个 cells:中断类型, 0表示 SPI中断,1表示 PPI中断。

第二个 cells:中断号,对 于 SPI中断来说中断号的范围为 0~987,对于 PPI中断来说中断号的范围为 0~15。

第三个 cells:标志 bit[3:0]表示中断触发类型,为 1的时候表示上升沿触发,为 2的时候表示下降沿触发,为 4的时候表示高电平触发,为 8的时候表示低电平触发。 bit[15:8]为 PPI中断的 CPU掩码。

上面2个节点都是NXP在 imx6ull.dtsi 给写好了,也就是不需要我们修改的,那么我们在设备树中要添加中断信息的时候,怎么做的呢?下面为NXP写的一个芯片GPIO中断示例,当然肯定不止只有GPIO中断,还有其他的比如定时器、串口等等。GPIO中断绑定信息参考文档为:Documentation/devicetree/bindings/gpio/gpio-mxs.txt

As the GPIO controller is embedded in the PIN controller and all the
GPIO ports share the same IO space with PIN controller, the GPIO node
will be represented as sub-nodes of MXS pinctrl node.

Required properties for GPIO node:
- compatible : Should be "fsl,<soc>-gpio".  The supported SoCs include
  imx23 and imx28.
- interrupts : Should be the port interrupt shared by all 32 pins.
- gpio-controller : Marks the device node as a gpio controller.
- #gpio-cells : Should be two.  The first cell is the pin number and
  the second cell is used to specify the gpio polarity:
      0 = active high
      1 = active low
- interrupt-controller: Marks the device node as an interrupt controller.
- #interrupt-cells : Should be 2.  The first cell is the GPIO number.
  The second cell bits[3:0] is used to specify trigger type and level flags:
      1 = low-to-high edge triggered.
      2 = high-to-low edge triggered.
      4 = active high level-sensitive.
      8 = active low level-sensitive.
fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};

总结:

①、 #interrupt-cells,指定中断源的信息 cells个数。

②、 interrupt-controller,表示当前节点为中断控制器。

③、 interrupts,指定中断号,触发方式等。

④、 interrupt-parent,指定父中断,也就是中断控制器。

四、获取中断号

编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map函数从 interupts 属性中提取到对应的设备号,函数原型如下:

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

dev:设备节点。

index:索引号 interrupts属性可能包含多条中断信息,通过 index指定要获取的信息。

返回值:中断号。

如果使用GPIO的话,可以使用 gpio_to_irq 函数来获取 gpio对应的中断号,函数原型如

int gpio_to_irq(unsigned int gpio)

gpio :要获取的 GPIO编号。

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

五、按键输入带定时器消抖程序编写

完整的实验程序:https://github.com/denghengli/linux_driver/tree/master/14_keyirq

1、GPIO输入中断上半部、下半部(tasklet或work)参考使用示例


/*中断处理函数*/
static irqreturn_t xxx_irq_func(int irq, void *dev_id)
{
    ...
    tasklet_schedule(&xxx_tasklet);/*调度tasklet*/
    schedule_work(&dev->work);/*调度work*/
    ...
	return IRQ_HANDLED;
}
/*中断下半部初始化*/
static void xxx_tasklet_func(unsigned long data)
{
}
static void xxx_work_func(struct work_struct *work)
{
	struct key_dev *dev = container_of(work, struct key_dev, work);
}


/*驱动初始化*/
static int xxx_open(struct inode *inode, struct file *filp)
{
	int ret = 0;
    ...
    /*注册中断处理函数*/
    request_irq(xxx_irq, xxx_irq_func, 0, "xxx", &xxx_dev)
    /*下半部 -- tasklet*/
    tasklet_init(&xxx_tasklet, xxx_tasklet_func, (unsigned long)xxx_dev);
    /*下半部 -- work*/
    INIT_WORK(&xxx_work, xxx_work_func);
    ...
	return ret;
}

/*释放驱动设备*/
static int xxx_release(struct inode *inode, struct file *filp)
{
	int ret = 0;
    ...
    free_irq(xxx_irq, &xxx_dev);
	...
	return ret;
}

猜你喜欢

转载自blog.csdn.net/m0_37845735/article/details/107306265