首页 > 其他分享 >minos 2.4 中断虚拟化——中断子系统

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

时间:2024-06-08 15:10:46浏览次数:24  
标签:return 中断 irq 2.4 sync handler minos desc

  • 首发公号:Rand_cs

前面讲述了 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_irq

int 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;
    } else
        return -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,注册对应的 irq
    for (i = 0; i < NR_CPUS; i++) {
        // 获取 per cpu 类型中断对应的 irq_desc
        irq_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_desc
    irq_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 的亲和性为当前 cpu
    if (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_desc
        irq_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;
    }
    // 执行 handler
    ret = irq_desc->handler(irq_desc->hno, irq_desc->pdata);
    // drop priority
    irq_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_el
    irq_from_current_el
        irq_handler
            do_irq_handler
                do_handle_host_irq
                    irq_desc->handler

__irq_exception_from_lower_el
    irq_from_lower_el
        irq_handler
            ......

异常

异常描述符

struct sync_desc {
    uint8_t aarch;     // 执行状态
    uint8_t irq_safe;  // 概念同 Linux,如果handler不会导致死锁竞争等,safe
    uint8_t ret_addr_adjust;  // 返回地址修正
    uint8_t resv;      // pad
    sync_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_el
    sync_exception_from_current_el
        handle_sync_exception
            ec->handler
            
__sync_exception_from_lower_el
    sync_exception_from_lower_el
        handle_sync_exception
            ec->handler
  • 首发公号:Rand_cs

标签:return,中断,irq,2.4,sync,handler,minos,desc
From: https://www.cnblogs.com/randcs/p/18238637

相关文章

  • CH582,CH592,CH57x系列芯片看门狗中断使用示例
    #include"CH58x_common.h"/**********************************************************************@fnDebugInit**@brief调试初始化**@returnnone*/voidDebugInit(void){GPIOA_SetBits(GPIO_Pin_9);GPIOA_ModeCfg(GPIO_Pin......
  • GD32如何配置中断优先级分组以及中断优先级
    使用GD32MCU的过程中,大家可能会有以下疑问:中断优先级如何配置和使用?本文将会为大家解析中断优先级分组以及中断优先级的配置使用:中断优先级分组配置一个GD32MCU系统需要大家明确系统中使用的中断优先级分组,避免中断优先级配置越界导致一些不符合预期的中断现象。中断优先......
  • AnolisOS7.9(CentOS7)部署K8s(1.22.4)集群
    一.安装K8s集群1.准备工作,2台服务器①192.168.5.140-做为master节点#在该节点运行命令设置主机名:hostnamectlset-hostnamemaster②192.168.5.141-做为node1节点,在该节点运行命令设置主机名:#在该节点运行命令设置主机名:hostnamectlset-hostna......
  • 按键中断驱动程序-poll机制
    前言:学东西的时候总是喜欢碎碎念,去思考该怎么学。关于嵌入式开发,以前就觉得嵌入式只是一个工具,关键还是结合专业知识赋能,比如控制、信号处理、神经网络、计算机网络、各种协议比如蓝牙、wifi,音视频,当然,如果能够把内核学的很透彻,那也是很了不起的。现在越学越觉得这个东西应该就是......
  • 定时器Timer中断
    一、CPU定时器Timer二、CPU定时器寄存器CpuTimer0Regs1.定时器计数器TIM2.定时器周期寄存器PRD3.定时器预定标寄存器-高TPRH(1)预定标计数器-高PSCH     与PSC相同(2)定时器分频器-高TDDRH     与TDDR相同4.定时器预定标寄存器-低TPR(1)预定标计数......
  • 中断卡在configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
    今天在调试以太网驱动的时候遇到一个问题,当程序执行到这个/*addthenetworkinterface(IPv4/IPv6)withRTOS*/netif_add(&gnetif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&tcpip_input);函数里面的netif_invoke_ext_callback(netif,LWIP_NSC_NETIF_ADDED,N......
  • QNX-9—QNX官网文档翻译—中断-3—Writing an Interrupt Handler
    翻译:QNXSoftwareDevelopmentPlatform-->Programming-->Programmer'sGuidehttps://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.prog/topic/inthandler.html前言:及时处理硬件事件的关键是硬件产生中断。中断只是处理器正在执行的操作的暂停或中断......
  • 七、FreeRTOS学习笔记-中断管理
    FreeRTOS学习笔记-中断管理中断:让CPU打断正常运行的程序,转而去处理紧急的事件(程序)中断执行机制,可简单概括为三步:1、中断请求:外设产生中断请求(GPIO外部中断、定时器中断等)2、响应中断:CPU停止执行当前程序,转而去执行中断处理程序(ISR)3、退出中断:执行完毕,返回被打断的程序处,继续......
  • 基于标准库的STM32的外部中断EXTI
            毕设已经告一段落了,接下来准备开始整理一下毕设中用到的知识与技术细节,今天整理的是STM32从编码器获取数据的方式-----外部中断(EXTI):外部中断分为四个硬件相关外设,GPIO/AFIO/EXTI/NVIC(EXTI/NVIC不需要开启时钟)1.RCC开启时钟RCC_APB2PeriphClockCmd(RCC_APB2P......
  • 原子上下文和中断上下文的区别
    原子上下文(AtomicContext)定义:原子本意是“不能被进一步分割的最小粒子”,而原子操作指的是“不可被中断的一个或一系列操作”。在操作系统中,原子上下文通常与原子操作相关,这些操作在执行过程中不会被其他操作或中断打断,从而保证其完整性。特点:不可分割性:原子操作在执行过程中......