中断子系统
中断子系统
中断子系统框架
- CPU
- 中断控制器
- 外设
- 中断向量表
- 中断号
- Linux 内核中断子系统
- 中断编程接口
中断控制器: GIC
GIC的由来
ARM cortex-A
系列处理器,提供了4个管脚给soc
,实现外界中断的传递。分别是:
- nIRQ: 物理普通中断
- nFIQ: 物理快速中断
- nVIRQ: 虚拟普通中断
- nVFIQ: 虚拟快速中断
如下图所示:
其中虚拟中断,是为了实现虚拟化而加入的,在这个系列中,不讨论虚拟中断,只介绍物理中断的相关知识。
在arm
的soc
系统中,会有多个外设,均有可能会产生中断发送给arm cpu
,等待cpu
处理。而arm cpu
对中断,只提供了2根信号,一个nIRQ
,一个是nFIQ
。 因此就需要有一个中断控制器来作为中间的桥接,收集soc的所有中断信号,然后仲裁选择合适的中断,再发送给CPU,等待CPU处理。如下图所示:
这中间的桥接器件,就是arm公司推出大名鼎鼎的gic,general interrupt controller。
gic其实是一个架构,版本历经了gicv1(已弃用),gicv2,gicv3,gicv4。对于不同的gic版本,arm公司设计了对应的gic IP。
- gic400,支持gicv2架构版本。
- gic500,支持gicv3架构版本。
- gic600,支持gicv3架构版本。
gic的核心功能,就是对soc中外设的中断源的管理,并且提供给软件,配置以及控制这些中断源。
对应的中断源有效时,gic根据该中断源的配置,决定是否将该中断信号,发送给CPU。如果有多个中断源有效,那么gic还会进行仲裁,选择最高优先级中断,发送给CPU。
当CPU接受到gic发送的中断,通过读取gic的寄存器,就可以知道,中断的来源来自于哪里,从而可以做相应的处理。
当CPU处理完中断之后,会告诉gic,其实就是访问gic的寄存器,该中断处理完毕。gic接受到该信息后,就将该中断源取消,避免又重新发送该中断给cpu以及允许中断抢占。
中断控制器的作用
- 负责处理各种中断
- 优先级、屏蔽、使能
单核下的中断控制器
多核下的中断控制器
中断分类
SGI 16 Software Generated Interrupts**
– 中断 号 ID0~ID15 ,用于多核之间通讯
注:软中断,软件产生的中断,用于给其他的core发送中断信号
SGI中断也称IPI中断,即处理器间中断,一个处理器可以向其他处理器发送中断,以达到目标处理器执行某种事情。
PPI 16 external Private Peripheral Interrupts
– 每个 core 私有的中断, 如本地时钟 ID16~ID31(私有外设中断,该中断来源于外设,但是该中断只对指定的core有效。)
SPI Shared Peripheral Interrupt
– 所有 core 共享的中断,可以在多个 core 上运行
–支持 范围可配置 32~1019 步进 32 ,从 ID32 开始
注:共享外设中断,该中断来源于外设,但是该中断可以对所有的core有效。
virtual interrupt
虚拟中断,用于支持虚拟机
LPI: (Locality-specific Peripheral Interrupt)
LPI 是 GICv3 中的新特性,它们在很多方面与其他类型的中断不同。LPI 始终是基于消息的中断,它们的配置保存在表中而不是寄存器。比如 PCIe 的 MSI/MSI-x 中断。
Group分组:
group0:安全中断,由nFIQ驱动
group1:非安全中断,由nFIQ驱动
中断优先级
因为soc中,中断有很多,为了方便对中断的管理,对每个中断,附加了中断优先级。在中断仲裁时,高优先级的中断,会优于低优先级的中断,发送给cpu处理。 当cpu在响应低优先级中断时,如果此时来了高优先级中断,那么高优先级中断会抢占低优先级中断,而被处理器响应。
中断触发方式
- edge-triggered : 边沿触发,当中断源产生一个边沿,中断有效
- level-sensitive :电平触发,当中断源为指定电平,中断有效
中断号
为了方便对中断的管理,gic为每个中断,分配了一个中断号,也就是interrupt ID。对于中断号,gic也进行了分配:
ID0-ID15,分配给SGI(software-generated interrupt)
ID16-ID31,分配给PPI(private peripheral interrupt)
ID32-ID1019分配给SPI (shared peripheral interrupt)
其他
在具体的arm的cpu中,对于PPI,又进行了详细的分配。这个,就得看arm cpu的TRM了。
- HW interrupt ID
- IRQ number
- IRQ_domain
GIC控制器中断处理流程
Interrupt states
对于每一个中断而言,有以下4个状态:
- Inactive : 中断处于无效状态
- Pending : 中断处于有效状态,但是cpu没有响应该中断
- Active : cpu在响应该中断
- Active and pending : cpu在响应该中断,但是该中断源又发送中断过来 以下是中断状态的转移图。至于图中的转移条件,在gic架构文档中,有介绍。
Models for handling
interrupts
- 1 N model
- N N model
中断控制器的内部结构
- Distributor
中断控制器内部结构
- CPU interfaces
- interrupt priority mask
GIC 处理中断流程
GIC 检测到使能的中断发生,将中断状态 设 为
pending
GIC 的仲裁器将最高优先级的 pending 中断发送到指定的 CPU interface
CPU interface 根据配置,将中断信号发送到 CPU
CPU 应答该中断,读取寄存器获取 interrupt ID GIC更新中断状态为 active
>> Pending -> active
>> Pending -> active and pending 中断重新产生
>> Active -> active and pending 若中断状态为 active
- CPU 处理完中断后,发送 EOI 信号给 GIC
中断生命周期
一个中断,是有生命周期的。以下是流程图:
- start:中断开始
- generate:中断源产生中断,发送给gic
- deliver:gic将中断发送给cpu
- activate:cpu响应该中断
- deactivate: cpu响应完中断,告诉gic,中断处理完毕,gic更新该中断状态
- end:中断结束
banking banking
包括以下两个:
- 1、中断banking
对于PPI和SGI,gic可以有多个中断对应于同一个中断号。比如在soc中,有多个外设的中断,共享同一个中断号。
- 2、寄存器banking
对于同一个gic寄存器地址,在不同的情况下,访问的是不同的寄存器。例如在secure和non-secure状态下,访问同一个gic寄存器,其实是访问的不同的gic的寄存器。 具体,更多的信息,得看gic spec以及arm spec。
中断实例
v express ARM A9 开发 板
GIC PL390
t imer SP804
编写 RTC 裸机中断程序
RTC 控制器介绍
中断函数的编写规则
编写中断函数需要注意的几个地方
- 中断函数的特点
- 无传参
- 无返回值
- 短小精悍
在 Linux 下编写RTC 驱动中断程序
Linux 内核中断接口
request_irq( unsigned int irq, irq_handler_t handler,unsigned long flags const char *name, void *dev)
–IRQF_SHARED :多设备共享一个中断号
–IRQF_TIMER
–IRQF_PERCPU
–IRQF_NOBALANCING
–IRQF_IRQPOLL
–IRQF_ONESHOT :禁止中断嵌套
–IRQF_NO_SUSPEND
–IRQF_FORCE_RESUME
–IRQF_NO_THREAD
–IRQF_EARLY_RESUME
- request_irq()
- f ree_irq()
- HW interrupt ID 和中断号
- 物理地址与虚拟地址
RTC代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
int counter = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
printk("counter: %d, irqnum: %d\n", counter++, irq);
set_rtc_alarm(regs);
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hilbert");
.PHONY: all clean
#ifneq ($(KERNELRELEASE),)
obj-m := rtc.o
#else
EXTRA_CFLAGS += -DDEBUG
KDIR := /home/linux-5.10.4
ARCH_ARGS := CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
#endif
Linux中断处理流程
- CPU 硬件自动完成的
- GIC 驱动
- Linux 内核完成的
- 用户编写的中断服务程序
中断上半部和下半部
- 为什么要分上半部和下半部?
- 关中 断、实时性
- 上半 部:响应中断,硬件配置,发送 EOI 给 GIC
- 下半 部:数据复制、数据包封装转发、编解码
中断下半部
- 软中断
- t asklet
- 工作队列: workqueue
- 中断线程化
SoftIRQ:软中断
软中断的相关知识
编程接口
–void open_softirq(int nr, void (action)(struct softirq_action ));
–void raise_softirq(unsigned int nr
–void raise_softirq_irqoff(unsigned int nr);
编程实验:给内核添加一个软中断
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
软中断的运行
软中断的实现
- softirq 的数据结构: struct softirq_action
- 软中断描述符数组 struct softirq_action softirq_vec
软中断的运行
- 运行时机:中断退出后的某个时机
- 开中断,可以被打断,不允许嵌套
- 中断延后到线程执行: ksoftirqd
中断下半部: tasklet
t asklet 编程实战
编程接口
–DECLARE_TASKLET(name, _callback
–void tasklet_init(struct tasklet_struct void (func)(unsigned long), unsigned long data)
–void tasklet_schedule(struct tasklet_struct *t);
–void tasklet_hi_schedule(struct tasklet_struct t);
–void tasklet_kill(struct tasklet_struct *);
实验:将 RTC 驱动的下半部改为 tasklet 来实现
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
bool use_callback;
union {
void (*func)(unsigned long data);
void (*callback)(struct tasklet_struct *t);
};
unsigned long data;
};
tasklet的运行
t asklet 的实现:基于软中断
–tasklet_vec 链表:软中断优先级 6 TASKLET_SOFTIRQ
–tasklet_hi_vec :使用软中断优先级 0 HI_SOFTIRQ
开 中断,在中断上下文中执行
tasklet 负载过重时,在进程上下文中执行
中断下半部: workqueue
工作队列编程实战
编程接口
–DECLARE_WORK(n, f);
–INIT_WORK (_work, _func);
–schedule_work ( struct work_struct *work);
–cancel_work_sync ( struct work_struct *);
实验:将 RTC 驱动的中断下半部改用工作队列实现
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
}
typedef void (*work_func_t )(struct work_struct *work)
延迟 工作队列
延迟工作队列编程实战
编程接口
–DECLARE_DELAYED_WORK(n, f)
–INIT_DELAYED_WORK(_work, _func)
–schedule_delayed_work(delayed_work , jiffies);
–bool flush_delayed_work ( struct delayed_work *dwork);
–cancel_delayed_work_sync ( struct work_struct *wirk);
实验:将 RTC 驱动的下半部改用延迟工作队列实现
struct delayed_work {
struct work_struct work;
struct timer_list timer
/* target workqueue and CPU -->timer uses to queue -->work */
struct workqueue_struct *wq;
int cpu;
};
typedef void (*work_func_t)(struct work_struct *
工作队列的运行
工作 队列的实现
相关 概念
–先 struct 一个 work( 内含中断下半部的处理函数
–s chedule_work :将 work 添加 workqueue
–执行 workqueue 的线程: worker thread
工作队列的运行
- 单 线 程式
- 多线程式
workqueue 队列的 弊端
Work
items w0, w1, w2 are queued to a bound wq q0 on the same CPU. w0 burns
CPU for 5ms then sleeps for 10ms then burns CPU for 5ms again before finishing.
w1 and w2 burn CPU for 5ms then sleep for 10ms.
CMWQ工作队列
CMWQ 并发管理工作 队列
concurrency Managed Workqueue
–工作项: work item ,添加到工作队列 ( 中
–提供两种 worker 线程池: bound 和 unbound
–workqueue 可 通过 flag 参数绑定到指定类型的线程池执行
–max_active :工作 队列在每个 CPU 上并发处理 的 work 个数
CMWQ 工作队列的运行
Work items w0, w1, w2 are queued to a bound wq q0 on the same CPU. w0 burns
CPU for 5ms then sleeps for 10ms then burns CPU for 5ms again before finishing.
w1 and w2 burn CPU for 5ms then sleep for 10ms.
simple FIFO scheduling
max_active >= 3
max_active = 2
w1 and w2 are queued to wq q1
中断线程化:request_threaded_irq
中断线程化编程实战
- 实验:将 RTC 驱动的中断下半部改为线程化执行
- 编程 接口
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t
thread_fn,
unsigned
long flags,
const
char *name, void *dev);
- 分享
- 举报
-
dmmonstr 2024-09-12 14:06:20回复 举报学习了:)
-
浏览量:3547次2020-07-22 15:04:49
-
浏览量:894次2023-10-26 13:41:10
-
浏览量:1612次2023-10-26 13:56:58
-
浏览量:720次2023-10-30 14:53:50
-
浏览量:5037次2017-11-15 11:09:58
-
浏览量:1974次2020-06-28 13:47:29
-
浏览量:1853次2022-05-30 09:56:07
-
浏览量:4926次2021-04-01 16:31:35
-
浏览量:4464次2021-08-23 17:12:33
-
浏览量:3787次2020-08-19 16:34:45
-
浏览量:1463次2019-10-30 15:26:36
-
2020-07-29 18:32:39
-
浏览量:5738次2021-03-31 15:36:17
-
浏览量:3738次2020-12-24 10:37:59
-
浏览量:3022次2020-12-03 17:16:26
-
浏览量:3759次2020-11-23 11:13:25
-
浏览量:4643次2021-03-30 14:17:51
-
浏览量:6313次2021-03-29 11:34:27
-
浏览量:5069次2021-03-29 14:17:09
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
Hilbert
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明