中断子系统

中断子系统 Hilbert 2024-08-29 09:13:00 202

中断子系统



中断子系统框架

  • CPU
  • 中断控制器
  • 外设
  • 中断向量表
  • 中断号
  • Linux 内核中断子系统
  • 中断编程接口

中断控制器: GIC

GIC的由来

ARM cortex-A系列处理器,提供了4个管脚给soc,实现外界中断的传递。分别是:

  • nIRQ: 物理普通中断
  • nFIQ: 物理快速中断
  • nVIRQ: 虚拟普通中断
  • nVFIQ: 虚拟快速中断

如下图所示:

其中虚拟中断,是为了实现虚拟化而加入的,在这个系列中,不讨论虚拟中断,只介绍物理中断的相关知识。

armsoc系统中,会有多个外设,均有可能会产生中断发送给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);
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Hilbert
红包 1 1 评论 打赏
评论
1个
内容存在敏感词
手气红包
  • dmmonstr 2024-09-12 14:06:20
    回复
    学习了:)
相关专栏
关于作者
Hilbert

Hilbert

暂无个性签名~

原创12
阅读1.1w
收藏10
点赞18
评论1
打赏用户 0
我要创作
分享技术经验,可获取创作收益
分类专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Hilbert
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区