1. 概述
中断控制是计算机发展中一种重要的技术。最初它是为克服对I/O接口控制采用程序查询所带来的处理器低效率而产生的。中断控制的主要优点是只有在I/O需要服务时才能得到处理器的响应,而不需要处理器不断地进行查询。由此,最初的中断全部是对外部设备而言的,即称为外部中断(或硬件中断)。
但随着计算机系统结构的不断改进以及应用技术的日益提高,中断的适用范围也随之扩大,出现了所谓的内部中断(或叫异常),它是为解决机器运行时所出现的某些随机事件及编程方便而出现的。因而形成了一个完整的中断系统。
如上图,当一个I/O设备完成它的工作后,它就会产生一个中断, 它通过在总线上声明已分配的信号来实现此目的。主板上的中断控制器芯片会检测到这个信号,然后执行中断操作。
如果在中断前没有其他中断操作阻塞的话,中断控制器将立刻对中断进行处理,如果在中断前还有其他中断操作正在执行,或者有其他设备发出级别更高的中断信号的话,那么这个设备将暂时不处理。在这种情况下,该设备会继续在总线上置起中断信号,直到得到CPU服务。
2. ARM架构中断知识
补充部分ARM架构中断知识。
2.1 GIC版本
GIC: Generic Interrupt Controller(通用的中断控制器)
- gic400,支持gicv2架构版本。
- gic500,支持gicv3架构版本。
- gic600,支持gicv3架构版本。
- gic700, 支持gicv4.1架构版本。
2.2 GIC Model
GIC是一个为Cortex-A和Arm Cortex-R设计的标准的中断控制器
2.2.1 GICV3/GICV4的四大组件
- Distributor
- Redistributor
- CPU insterface
- Interrupt Translation Service
2.2.2 GIC四种类型中断:
-
SGI 软件产生中断(Software-generated interrupts)
-
PPI 私有中断(Private Peripheral interrupts)
-
SPI 共享中断(Share Peripheral interrupts)
-
LPI 区域特定中断(Locality-specific Peripheral Interrupts)
参考文章:【笔记】Arm CoreLink Generic Interrupt Controller v3 and v4 Overview_伯春岱的博客-CSDN博客
3. Linux内核中断
3.1 中断向量表
代码跟踪:
android/kernel/msm-5.4/arch/arm64/kernel/head.S
android/kernel/msm-5.4/arch/arm64/kernel/entry.S
- 如果发生异常后并没有exception level切换,并且发生异常之前使用的栈指针是SP_EL0,那么使用第一组异常向量表。
- 如果发生异常后并没有exception level切换,并且发生异常之前使用的栈指针是SP_EL1/2/3,那么使用第二组异常向量表。
- 如果发生异常导致了exception level切换,并且发生异常之前的exception level运行在AARCH64模式,那么使用第三组异常向量表。
- 如果发生异常导致了exception level切换,并且发生异常之前的exception level运行在AARCH32模式,那么使用第四组异常向量表
3.2 中断处理流程
除了响应系统调用外,内核也要响应设备的 服务请求,这称为中断,它会中断当前的执行,如下图所示:
中断处理
接收到设备中断响应后,内核处理针对该中断处理流程如下:
3.3 中断申请流程
TODO
4. 中断上下文
TODO
5. 中断在驱动中的应用
TODO
5.1 应用场景
TODO
5.2 使用流程
中断服务/处理程序,需要通过申请/注册来处理设备中断。这类程序的设计要点是需要运行得进可能快,以减少对活动线程中断的影响。如果中断要做的工作不少,尤其是还可能被锁阻塞,那么最好用中断线程来处理(如:request_threaded_irq),有内核来调度。
5.2.1 配置中断IO
TODO
5.2.2 request_threaded_irq 与 request_irq的使用区别
如下文件:include/linux/interrupt.h 摘取
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_irq与 request_threaded_irq非常类似,request_irq在request_threaded_irq基础上进行了封装,将参数thread_fn可以置为NULL。handler是在发生中断时首先要执行的程序,类似中断处理上文,执行处理费时下文可以增加tasklet或者工作队列实现。thread_fn是要在线程里执行的handler类似中断处理下文。IRQF_ONESHOT用来标明是在中断线程thread_fn执行完后在重新打开该中断。
handler 与 thread_fn的处理差异涉及工作队列与线程处理差异:
编号 |
差异 |
详情 |
1 |
调度优先级不同 |
threaded irq handler所在的进程(内核线程),调度类别是SCHED_FIFO,是实时内核线程。 workqueue所依赖的线程池创建的kworker线程调度类别是SCHED_NORMAL,是普通内核线程。 |
2 |
多核并发效率不同 |
workqueue机制是内核启动时会为每个CPU创建几个不同优先级的kworker(worker_thread)内核线程,用以集中处理各种中断的下半部的work。所以多个不同设备中断的work都会由同一个kworker线程来处理(一个CPU处理)。 threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个CPU上执行。 所以workqueue机制在多CPU系统中并发效率不如threaded irq。 |
request_threaded_irq:将中断处理线程化,将上下文缩短为kernel thread 执行后续中断任务,运用调度机制减少延时,提高中断响应,提高处理效率。(在负载较大/系统占用资源高时效果明显,避免了中断丢失情况)。
request_irq:类似于同步处理事务,适合非高频率中断响应,在负载较大/系统占用资源高时会出现处理任务丢失现象。(不具备高可用特性)。
5.2.3 devm_request_threaded_irq/request_threaded_irq 申请中断:
/**
* devm_request_threaded_irq - allocate an interrupt line for a managed device
* @dev: device to request interrupt for
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs
* @thread_fn: function to be called in a threaded interrupt context. NULL
* for devices which handle everything in @handler
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device, dev_name(dev) if NULL
* @dev_id: A cookie passed back to the handler function
*
* Except for the extra @dev argument, this function takes the
* same arguments and performs the same function as
* request_threaded_irq(). IRQs requested with this function will be
* automatically freed on driver detach.
*
* If an IRQ allocated with this function needs to be freed
* separately, devm_free_irq() must be used.
*/
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id)
{
struct irq_devres *dr;
int rc;
dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
if (!devname)
devname = dev_name(dev);
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
if (rc) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id;
devres_add(dev, dr);
return 0;
}
EXPORT_SYMBOL(devm_request_threaded_irq);
- 如果只使用中断线程,则参数 handler 可以置为NULL,这时IRQF_ONESHOT标识不可少。IRQF_ONESHOT的作用是保证thread_fn函数执行完整,才会接受下一个中断信号。如果不设置该位,在高低电平触发中断情况下,永远没有机会处理线程thread_fn。
- 如果只使用中断work,则参数thread_fn可以置为NULL
例1:
error = devm_request_threaded_irq(&client->dev, client->irq,
NULL, mxt_interrupt, IRQF_ONESHOT,
client->name, data);
if (error) {
dev_err(&client->dev, "Failed to register interrupt\n");
goto err_disable_regulator;
}
disable_irq(client->irq);
例2:
ts_data->irq = gpio_to_irq(pdata->irq_gpio);
pdata->irq_gpio_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
ret = request_threaded_irq(ts_data->irq, NULL, fts_irq_handler,
pdata->irq_gpio_flags,
FTS_DRIVER_NAME, ts_data);
摘录://中断标志位
/*
* These correspond to the IORESOURCE_IRQ_* defines in
* linux/ioport.h to select the interrupt line behaviour. When
* requesting an interrupt without specifying a IRQF_TRIGGER, the
* setting should be assumed to be "as already configured", which
* may be as per machine or firmware initialisation.
*/
#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
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in a shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend. Does not guarantee
* that this interrupt will wake the system from a suspended
* state. See Documentation/power/suspend-and-interrupts.rst
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
* IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
* interrupt handler after suspending interrupts. For system
* wakeup devices users need to implement wakeup detection in
* their interrupt handlers.
*/
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
5.2.4 request_irq申请中断
接口:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
例:
ret = request_irq(irq->irq, emac_isr, 0, "emac-core0", irq);
if (ret) {
netdev_err(adpt->netdev, "could not request emac-core0 irq\n");
return ret;
}
中断处理程序
static irqreturn_t emac_isr(int _irq, void *data)
{
struct emac_irq *irq = data;
struct emac_adapter *adpt =
container_of(irq, struct emac_adapter, irq);
struct emac_rx_queue *rx_q = &adpt->rx_q;
u32 isr, status;
/* disable the interrupt */
writel(0, adpt->base + EMAC_INT_MASK);
isr = readl_relaxed(adpt->base + EMAC_INT_STATUS);
status = isr & irq->mask;
if (status == 0)
goto exit;
if (status & ISR_ERROR) {
net_err_ratelimited("%s: error interrupt 0x%lx\n",
adpt->netdev->name, status & ISR_ERROR);
/* reset MAC */
schedule_work(&adpt->work_thread);
}
/* Schedule the napi for receive queue with interrupt
* status bit set
*/
if (status & rx_q->intr) {
if (napi_schedule_prep(&rx_q->napi)) {
irq->mask &= ~rx_q->intr;
__napi_schedule(&rx_q->napi);
}
}
if (status & TX_PKT_INT)
emac_mac_tx_process(adpt, &adpt->tx_q);
if (status & ISR_OVER)
net_warn_ratelimited("%s: TX/RX overflow interrupt\n",
adpt->netdev->name);
exit:
/* enable the interrupt */
writel(irq->mask, adpt->base + EMAC_INT_MASK);
return IRQ_HANDLED;
}
5.2.5 中断处理程序
设备驱动分为两半,上半部用于快速处理中断,到下半部的调度工作在之后处理。上半部快速处理中断很重要,因为上半部运行在中断禁止模式(irq_disable mode),会推迟新的中断产生,如果运行的时间太长,就会造成延时问题。下半部可以作为tasklet或者动作队列,之后由内核线程调度。
注:mutex_lock互斥锁是不能在中断处理中使用,互斥锁是休眠锁,应用在多进程/多线程同步操作。
5.2.6 中断返回值:
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device or was not handled
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
IRQ_HANDLED: 正确收到设备中断,并处理
IRQ_NONE:中断不是来自此设备或未处理
5.3 特殊关注点
TODO
6. 总结
TODO