minos 2.4 中断虚拟化——中断子系统

2024-06-03 07:52

本文主要是介绍minos 2.4 中断虚拟化——中断子系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首发公号:Rand_cs
该项目来自乐敏大佬:https://github.com/minosproject/minos

前面讲述了 minos 对 GICv2 的一些配置和管理,这一节再往上走一走,看看 minos 的中断子系统

中断

中断描述符

/** if a irq is handled by minos, then need to register* the irq handler otherwise it will return the vnum* to the handler and pass the virq to the vm*/
struct irq_desc {irq_handle_t handler;   // 中断 handler 函数uint16_t hno;           // 物理中断号uint16_t affinity;      // cpu 亲和性unsigned long flags;    spinlock_t lock;unsigned long irq_count;void *pdata;        void *owner;struct kobject *kobj;struct poll_event_kernel *poll_event;
};

由 minos(hypervisor) 处理的每一个中断,都有一个 irq_desc 描述符,其中主要记录了该中断对应的物理中断号 hno,以及对应的 handler

// SGI(Software Generated Interrupts)软件中断
// PPI(Private Peripheral Interrupts)私有外设中断
// SPI(Shared Peripheral Interrupts)共享外设中断
static struct irq_desc percpu_irq_descs[PERCPU_IRQ_DESC_SIZE] = {[0 ... (PERCPU_IRQ_DESC_SIZE - 1)] = {default_irq_handler,},
};static struct irq_desc spi_irq_descs[SPI_IRQ_DESC_SIZE] = {[0 ... (SPI_IRQ_DESC_SIZE - 1)] = {default_irq_handler,},
};static int default_irq_handler(uint32_t irq, void *data)
{pr_warn("irq %d is not register\n", irq);return 0;
}

全局定义了两个 irq_desc 数组,percpu_irq_descs 表示 per cpu 中断,SGI 是发送给特定 CPU(组) 的中断,PPI 是每个 CPU 私有中断,它们都可以看作为 percpu 中断,而 SPI 是所有 CPU 共享(GICD_ITARGETSR设置亲和性)的外部中断。

这里再具体说一下我理解的 percpu 中断,对于 PPI 来说比较好理解,比如说时钟中断,本身就有 NCPU 个的时钟中断源,每个 CPU 私人具有一个中断源,所以我们定义 NCPU 个的 irq_desc 来分别描述这 NCPU 个时钟中断。没什么问题,但是 SGI 呢,我们这样想,对于 CPU0 来说,其他 CPU 包括自己都有可能向 CPU0 发送 SGI,同理对于其他 CPU 也是这样,那么每一种 SGI,我们也定义 NCPU 个 irq_desc 来描述,很合理。

spi_irq_descs 的下标我们可以当做虚拟中断号 virq,一个设备的硬件中断号记录在设备树文件里面,比如说串口:

        pl011@9000000 {clock-names = "uartclk\0apb_pclk";clocks = < 0x8000 0x8000 >;interrupts = < 0x00 0x01 0x04 >;reg = < 0x00 0x9000000 0x00 0x1000 >;compatible = "arm,pl011\0arm,primecell";};

interrupts = < 0x00 0x01 0x04 >;对于设备树的 interrupts 语句,后面一般跟 3 个数或者 2 个数,倒数第二个表示硬件中断号,倒数第一个表示触发方式,倒数第三个表示中断域,比如说是 SPI?PPI?

从这里可以看出串口 pl011 的中断号为 0x01,但似乎这个数不太对,怎么会在 32 以内?那是因为获取了这个数之后还要进行转换,在设备树分析的时候,从 interrupts 获取到中断信息后,马上会调用 irq_xlate 转换中断号

int get_device_irq_index(struct device_node *node, uint32_t *irq,unsigned long *flags, int index)
{int irq_cells, len, i;of32_t *value;uint32_t irqv[4];if (!node)return -EINVAL;value = (of32_t *)of_getprop(node, "interrupts", &len);if (!value || (len < sizeof(of32_t)))return -ENOENT;irq_cells = of_n_interrupt_cells(node);if (irq_cells == 0) {pr_err("bad irqcells - %s\n", node->name);return -ENOENT;}pr_debug("interrupt-cells %d\n", irq_cells);len = len / sizeof(of32_t);if (index >= len)return -ENOENT;value += (index * irq_cells);for (i = 0; i < irq_cells; i++)irqv[i] = of32_to_cpu(*value++);return irq_xlate(node, irqv, irq_cells, irq, flags);
}irq_xlate -> irq_chip->irq_xlate -> gic_xlate_irqint gic_xlate_irq(struct device_node *node,uint32_t *intspec, unsigned int intsize,uint32_t *hwirq, unsigned long *type)
{if (intsize != 3)return -EINVAL;// SPI 中断if (intspec[0] == 0)*hwirq = intspec[1] + 32;// PPI 中断else if (intspec[0] == 1) {if (intspec[1] >= 16)return -EINVAL;*hwirq = intspec[1] + 16;} elsereturn -EINVAL;*type = intspec[2];return 0;
}

通过上述代码我们可以知道,pl101 的中断实际上是 1 + 32 = 33,这是一个物理中断号,在 minos 中物理中断号与虚拟中断号是一样的,没有做什么复杂的映射。在 Linux 系统,因为要考虑各个平台,各个平台使用的中断控制器,向后兼容一系列复杂的原因,做不到物理中断号与虚拟中断号直接映射。但目前 minos 没有太多平台特性,只支持 ARM,所以将物理中断号和虚拟中断号直接映射来简化实现。

注册中断

// 注册 percpu 类型的 irq
int request_irq_percpu(uint32_t irq, irq_handle_t handler,unsigned long flags, char *name, void *data)
{int i;struct irq_desc *irq_desc;unsigned long flag;unused(name);if ((irq >= NR_PERCPU_IRQS) || !handler)return -EINVAL;// 遍历每个CPU,注册对应的 irqfor (i = 0; i < NR_CPUS; i++) {// 获取 per cpu 类型中断对应的 irq_descirq_desc = get_irq_desc_cpu(i, irq);if (!irq_desc)continue;// 初始化 irq_desc 结构体spin_lock_irqsave(&irq_desc->lock, flag);irq_desc->handler = handler;irq_desc->pdata = data;irq_desc->flags |= flags;irq_desc->affinity = i;irq_desc->hno = irq;/* enable the irq here */// 使能该中断irq_chip->irq_unmask_cpu(irq, i);// irq_desc 中也取消 masked 标志irq_desc->flags &= ~IRQ_FLAGS_MASKED;spin_unlock_irqrestore(&irq_desc->lock, flag);}return 0;
}// 注册普通的 SPI 共享外设
int request_irq(uint32_t irq, irq_handle_t handler,unsigned long flags, char *name, void *data)
{int type;struct irq_desc *irq_desc;unsigned long flag;unused(name);if (!handler)return -EINVAL;// 获取该 irq 对应的 irq_desc// irq < 32 返回 percpu_irq_descs// irq >= 32 返回 spi_descirq_desc = get_irq_desc(irq);if (!irq_desc)return -ENOENT;type = flags & IRQ_FLAGS_TYPE_MASK;flags &= ~IRQ_FLAGS_TYPE_MASK;// 设置 irq_desc 各个字段spin_lock_irqsave(&irq_desc->lock, flag);irq_desc->handler = handler;irq_desc->pdata = data;irq_desc->flags |= flags;irq_desc->hno = irq;/* enable the hw irq and set the mask bit */// 使能该中断irq_chip->irq_unmask(irq);// 在 irq_desc 层级也取消屏蔽irq_desc->flags &= ~IRQ_FLAGS_MASKED;// 如果 irq < SPI_IRQ_BASE,要么是 SGI 软件中断,要么是 PPI 私有中断// 都属于 percpu 中断,设置该 irq 的亲和性为当前 cpuif (irq < SPI_IRQ_BASE)irq_desc->affinity = smp_processor_id();spin_unlock_irqrestore(&irq_desc->lock, flag);// 设置触发类型if (type)irq_set_type(irq, type);return 0;
}

minos 中有上述两个注册中断函数,看函数名称一个是注册 percpu 类型的中断,一个是注册其他(SPI) 类型的中断,但其实 request_irq 什么类型的中断都会注册,从代码 if (irq < SPI_IRQ_BASE)就可以看出来

注册中断就是在中断号对应的 irq_desc 填写好 handler 等信息,然后 irq_chip->irq_unmask(irq);使能该中断,中断的注册主要就是做这两件事

另外,对于某个状态的状态标志,虽然寄存器里面存有相关信息,但是我们一般在系统软件层面上也设置相关标志,那么每次获取状态信息直接读取变量就行了,不用再去从设备寄存器里面获取

中断处理

int do_irq_handler(void)
{uint32_t irq;struct irq_desc *irq_desc;int cpuid = smp_processor_id();while (1) {// 循环调用 get_pending_irq 读取 IAR 寄存器来获取中断号irq = irq_chip->get_pending_irq();if (irq >= BAD_IRQ)return 0;// 根据中断号获取 irq_descirq_desc = get_irq_desc_cpu(cpuid, irq);// 不太可能为空,如果为空可能是发生了伪中断if (unlikely(!irq_desc)) {pr_err("irq is not actived %d\n", irq);irq_chip->irq_eoi(irq);irq_chip->irq_dir(irq);continue;}do_handle_host_irq(cpuid, irq_desc);}return 0;ec->handler
}// 执行中断对应的 handler
static int do_handle_host_irq(int cpuid, struct irq_desc *irq_desc)
{int ret;if (cpuid != irq_desc->affinity) {pr_notice("irq %d do not belong to this cpu\n", irq_desc->hno);ret =  -EINVAL;goto out;}// 执行 handlerret = irq_desc->handler(irq_desc->hno, irq_desc->pdata);// drop priorityirq_chip->irq_eoi(irq_desc->hno);
out:/** 1: if the hw irq is to vcpu do not DIR it.* 2: if the hw irq is to vcpu but failed to send then DIR it.* 3: if the hw irq is to userspace process, do not DIR it.*/// 除了上述三种情况,调用 irq_dir deactivate if (ret || !(irq_desc->flags & IRQ_FLAGS_VCPU))irq_chip->irq_dir(irq_desc->hno);return ret;
}

与前文联系起来:

__irq_exception_from_current_elirq_from_current_elirq_handlerdo_irq_handlerdo_handle_host_irqirq_desc->handler__irq_exception_from_lower_elirq_from_lower_elirq_handler......

异常

异常描述符

struct sync_desc {uint8_t aarch;     // 执行状态uint8_t irq_safe;  // 概念同 Linux,如果handler不会导致死锁竞争等,safeuint8_t ret_addr_adjust;  // 返回地址修正uint8_t resv;      // padsync_handler_t handler;  
};

对于异常的处理,也类似中断,每一个异常都定义了一个 sync_desc 来描述,里面记录了 handler 等信息

其他都比较好理解,就这个返回地址修正什么意思呢?当发生异常的时候,是将发生异常的指令的地址保存到 ELR_EL2 寄存器里面,但是返回的时候不一定返回异常指令地址。比如说 svc 系统调用指令,当 svc 执行完成后肯定是返回 svc 下一条指令,这个 ret_addr_adjust 就是做这个事情的,记录对应异常是否需要返回地址的修正

手册里有个地方记录着每种异常的伪代码,其中记录了是否修正,以及修正值:TODO 补充链接

#define DEFINE_SYNC_DESC(t, arch, h, is, raa)           \static struct sync_desc sync_desc_##t __used = {    \.aarch = arch,                  \.handler = h,                   \.irq_safe = is,                 \.ret_addr_adjust = raa,             \}DEFINE_SYNC_DESC(trap_unknown, EC_TYPE_AARCH64, unknown_trap_handler, 1, 0);
DEFINE_SYNC_DESC(trap_kernel_da, EC_TYPE_AARCH64, kernel_mem_fault, 1, 0);
DEFINE_SYNC_DESC(trap_kernel_ia, EC_TYPE_AARCH64, kernel_mem_fault, 1, 0);

目前 minos 定义了上述几个异常描述符(还有一些与虚拟化相关,暂且不谈),实际就两个,一个是指令异常,一个是数据异常,其他的都处于未定义状态(都调用到 panic)

异常处理

static void handle_sync_exception(gp_regs *regs)
{uint32_t esr_value;uint32_t ec_type;struct sync_desc *ec;// 获取异常原因,ESR[31:26]记录了异常的种类,其值当做异常号esr_value = read_esr();ec_type = ESR_ELx_EC(esr_value);if (ec_type >= ESR_ELx_EC_MAX)panic("unknown sync exception type from current EL %d\n", ec_type);/** for normal userspace process the return address shall* be adjust*/// 获取该异常对应的异常描述符ec = process_sync_descs[ec_type];// 修正返回地址regs->pc += ec->ret_addr_adjust;// 处理该异常ec->handler(regs, ec_type, esr_value);
}

再与前文联系起来:

__sync_exception_from_current_elsync_exception_from_current_elhandle_sync_exceptionec->handler__sync_exception_from_lower_elsync_exception_from_lower_elhandle_sync_exceptionec->handler

首发公号:Rand_cs

这篇关于minos 2.4 中断虚拟化——中断子系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1026396

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

FreeRTOS学习笔记(四)Freertos的中断管理及临界保护

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Cortex-M 中断管理1.1 中断优先级分组1.2 相关寄存器1.3 相关宏定义1.4 FreeRTOS 开关中断 二、临界段及其保护2.1 taskENTER_CRITICAL( ) 和 taskEXIT_CRITICAL( )2.2 taskENTER_CRITICAL_FROM_ISR( )

【虚拟化】AIO主机安装PVE8,配置网络,安装win11(virtio,qcow2,scsi,oobe,adk)

【虚拟化】AIO主机安装PVE8,配置网络,安装win11(virtio,qcow2,scsi,oobe,adk) 文章目录 1、ESXI vs PVE,AIO主机系统二选一2、PVE网络配置(DNS,换源,网卡,https,概览)3、win11虚拟化配置(virtio,raw,qcow2)附,域名解析,rocky9.4,黑群晖 1、ESXI vs PVE,AIO主机系统二选

【QNX+Android虚拟化方案】120 - Android 侧 USB2.0 插拔过程

【QNX+Android虚拟化方案】120 - Android 侧 USB2.0 插拔过程 基于原生纯净代码,自学总结 纯技术分享,不会也不敢涉项目、不泄密、不传播代码文档!!! 本文禁止转载分享 !!! 汇总链接:《【QNX+Android虚拟化方案】00 - 系列文章链接汇总》 本文链接:《【QNX+Android虚拟化方案】120 - Android 侧 USB2.0

Cortex-A7:ARM官方推荐的嵌套中断实现机制

0 参考资料 ARM Cortex-A(armV7)编程手册V4.0.pdf ARM体系结构与编程第2版 1 前言 Cortex-M系列内核MCU中断硬件原生支持嵌套中断,开发者不需要为了实现嵌套中断而进行额外的工作。但在Cortex-A7中,硬件原生是不支持嵌套中断的,这从Cortex-A7中断向量表中仅为外部中断设置了一个中断向量可以看出。本文介绍ARM官方推荐使用的嵌套中断实现机

ARM 虚拟化介绍

0.目录 文章目录 0.目录1.概述 1.1 Before you begin 2.虚拟化介绍 2.1 虚拟化为什么重要2.2 hypervisors的两种类型2.3 全虚拟化和半虚拟化2.4 虚拟机和虚拟CPUs 3.AArch64中的虚拟化4.stage 2 转换 4.1 什么是stage 2 转换4.2 VMIDs4.3 VMID vs ASID4.4 属性整合和覆盖4.5模拟

外部中断的边缘触发和电平触发

MCS-51单片机中的边缘触发是指当输入引脚电平由高到低发生跳变时,才引起中断。而电平触发是指只要外部引脚为低电平就引起中断。         在电平触发方式下,当外部引脚的低电平在中断服务返回前没有被拉高时(即撤除中断请求状态),会引起反复的不需要的中断,造成程序执行的错误。这类中断方式下,需要在中断服务程序中设置指令,清除外部中断的低电平状态,使之变为高电平。

linux下的虚拟化

1.下载并且安装 下载客户机和工具 完成之后打开客户机,并且进行安装;安装之后会出现配置软件的界面,我们按照自己的需求进行相关配置即可 这个界面会有我们需要的各种相关设置 在设置自己的超级用户密码以及自己账户及密码之后就完成了虚拟机的安装、接下来进行reboot重新启动即可 到这就已经成功在Linux中安装了虚拟机,完成了虚拟化部署 二、复制虚拟机到远程主机

【Java编程思想】线程的基本协作机制 与 线程的中断

wait/notify Java在Object类中定义了一些线程协作的基本方法,wait和notify public final void wait() throws InterruptedException;public final native void wait(long timeout) throws InterruptedException; 一个带时间参数,单位是毫秒,表示最

基于IMX6ULL的Cortex-A中断原理讲解,以及编写其中断向量表

首先借助STM32我们需要了解中断系统是如何构成的         会有一个中断源,也就是能够向CPU发出中断请求的设备或事件。中断源不分硬件和软件,也就是产生中断信号,就会执行中断服务函数         但是CPU是如何知道中断源产生后就找到对应的中断服务函数呢,这个时候就要引入中断向量表,它的主要功能是描述中断对应的中断服务函数,每个中断源都有一个唯一的中断号(也称向量号),