本文主要是介绍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 中断虚拟化——中断子系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!