ARMv8Uboot中断设计说明

ARMv8Uboot中断设计说明

1. 背景介绍

因为客户需求,需要在uboot实现定时器功能,且不能阻塞,因此需要在Uboot支持中断功能,为了方便拓展,对uboot中断系统进行了类似linux系统的分层设计,本文主要介绍Uboot中断的设计思路,以及后续对这个子系统改进的方向。

2. Uboot中断设计

2.1 Uboot中断框架

Uboot的中断框架如图所示:
在这里插入图片描述
各个层次说明如下:

driver层:中断的使用者,负责各自使用的中断的request、enable、disable和free

中断管理及接口层:负责向上层(driver层)提供相应的接口,以及向下层提供注册接口(中断控制器抽象层),以及实现中断的流控处理

中断控制器抽象层:负责向中断管理及接口层注册中断控制器驱动

配置信息层:负责配置中断控制器信息及各个中断控制器关系

2.2 数据结构说明

struct irq_chip {
	void (*irq_enable)(unsigned int irq);
	void (*irq_disable)(unsigned int irq);
	void (*irq_eoi)(unsigned int irq);
	void (*irq_set_type)(unsigned int irq, unsigned int type);
}

中断控制器抽象结构如上,目前只支持部分控制器的操作,可以扩展。

struct irq_desc {
	struct irq_chip *chip;
	struct irq_action action;
};

中断描述符,每个中断对应有一个中断描述符,主要描述该中断的信息以及对应的中断控制器抽象

struct irq_action {
	interrupt_handler_t *handler;
	void *arg;
	int count;
};

中断行为抽象,每个中断对应有一个中断行为抽象,主要提供中断处理函数以及相关的参数。

后续继续完善

2.3 中断管理及接口层API说明

  1. 中断使能接口
void enable_irq(unsigned int irq);
  1. 中断关闭接口
void disable_irq(unsigned int irq);
  1. 中断申请接口
int request_irq(unsigned int irq, interrupt_handler_t handler, void *arg, unsigned long type);
  1. 中断释放接口
void free_irq(unsigned int irq);
  1. 中断控制器驱动注册接口
int register_irqchip(const struct irq_chip *chip);

后续继续完善。

2.4 Uboot支持ARM中断过程

2.4.1 设置异常向量表

这一项基本没有工作量,Uboot默认已经设置好相关的异常向量表,代码如下图所示:
在这里插入图片描述
从start.S可以知道,uboot工作在EL2模式,因此设置了EL2模式下的异常向量表。

异常向量表如下:

在这里插入图片描述
从表中可以看出,中断的入口函数为do_irq(),发生中断后,在该函数处理即可。

2.4.2 打开ARM中断和设置中断路由

Uboot默认没有支持中断,因此中断相关的初始化函数,默认都是空的,需要具体实现。

ARM64的中断初始化代码在uboot以下路径:

/* arch/arm/lib/interrupts_64.c */

/* 中断初始化,在board_init_r()会调用 */
int interrupt_init(void)
{
	unsigned long value;
	enable_interrupts();

    /* 需要把中断路由到EL2模式,默认中断不会路由到EL2 */
	if (current_el() == 2) {
		asm volatile("mrs %0, hcr_el2" : "=r" (value));
		value |= (1 << 4);
		asm volatile("msr hcr_el2, %0" : : "r" (value));
	}
	return 0;
}

void enable_interrupts(void)
{
	asm volatile("msr daifclr, #2");        //使能core中断
	return;
}

int disable_interrupts(void)
{
	asm volatile("msr daifset, #2");        //关闭Core中断
	return 0;
}

2.4.3 初始化GIC中断控制器

在gic驱动的probe函数中,对gic进行一些初始化,代码如下:

static void gic_dist_init(struct gic_chip_data *priv)
{
	unsigned int gic_irqs = priv->gic_irqs;
	void __iomem *base = priv->dist_base;
	unsigned int i;

	writel(GICD_DISABLE, base + GICD_CTLR);

	/*
	 * Set all SPI interrupts to be level triggered, active low.
	 */
	for (i = 32; i < gic_irqs; i += 16)
		writel(GICD_INT_ACTLOW_LVLTRIG, base + GICD_ICFGR + i / 4);

	/*
	 * Deactivate and disable all SPIs.
	 * Leave the PPI and SGIs alone as they are in the redistributor
	 * registers on GICv3.
	 */
	for (i = 32; i < gic_irqs; i += 32) {
		writel_relaxed(GICD_INT_EN_CLR_X32,
			       base + GICD_ICACTIVERn + i / 8);
		writel_relaxed(GICD_INT_EN_CLR_X32,
			       base + GICD_ICENABLERn + i / 8);
	}

	/*
	 * Deal with the banked PPI interrupts, disable all PPI interrupts
	 */
	writel(GICD_INT_EN_CLR_X32, base + GICD_ICACTIVERn);
	writel(GICD_INT_EN_CLR_PPI, base + GICD_ICENABLERn);

	/*
	 * Enable group0 interrupts
	 */
	writel(GICD_ENABLE, base + GICD_CTLR);
}

static void gic_cpu_init(struct gic_chip_data *priv)
{
	void __iomem *base = priv->cpu_base;

    /* Support send irq signal to CPU */
	writel(GICC_ENABLE, base + GICC_CTLR);
}

static int gicv2_probe(struct udevice *dev)
{
	struct gic_chip_data *priv = dev_get_priv(dev);
	unsigned int val;
	int ret;
	int gic_irqs;

	priv->dist_base = (void __iomem *)dev_read_addr_index(dev, 0);
	if (!priv->dist_base) {
		log_err("No device support\n");
		return -ENOENT;
	}

	priv->cpu_base = (void __iomem *)dev_read_addr_index(dev, 1);
	if (!priv->cpu_base) {
		log_err("No device support\n");
		return -ENOENT;
	}

    /* Get max irq number */
	gic_irqs = readl(priv->dist_base + GICD_TYPER) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;

	priv->gic_irqs = gic_irqs;

	gic_dist_init(priv);

	gic_cpu_init(priv);
	ret = register_irqchip(&gic_irq_ops);
	if (ret < 0) {
		log_err("register irqchip error!\n");
		return ret;
	}

	log_info("gic probe finish!\n");
	return 0;
}

2.4.4 GIC中断处理

void handle_irq_event(struct pt_regs *pt_regs, unsigned int esr)
{
	struct irq_desc *irq_desc = NULL;
	struct irq_action *action = NULL;
	struct irq_chip *irq_chip = NULL;
	unsigned int irqnr;

	do {
		irqnr = gic_get_irqinfo();
		irq_desc = &g_irq_desc[irqnr];
		if (!irq_desc) {
			log_err("Not support this irq:%u\n", irqnr);
			break;
		}

		if (irqnr > 15 && irqnr < 1020) {
			irq_chip = irq_desc->chip;
			if (!irq_chip) {
				log_err("Don't have irq chip support!\n");
				break;
			}

			action = &irq_desc->action;
			if (!action) {
				log_err("Need request irq first!\n");
				break;
			}
			if (!action->handler) {
				action->handler = handler_bad_action;
				action->handler(action->arg);
			}
			action->handler(action->arg);

			if (irq_chip->irq_eoi)
				irq_chip->irq_eoi(irqnr);
			continue;
		} else {
			/* IPI interrupt */
			log_debug("Not support!\n");
			break;
		}
	} while (1);
}

在中断处理函数中,主要做以下事情:

  1. 判断中断时IPI中断,还是SPI或者PPI中断,只支持处理PPI和SPI中断

  2. 从GIC相关寄存器,获取硬件中断号

  3. 根据中断号,找到对应中断处理函数,调用驱动注册的中断处理函数

  4. 发送eoi信号,清除gic中断相关标志位,表示该中断处理完成。

2.5 中断使用说明

本小节以arm generic timer为例,对中断使用进行说明,如下:

  1. 中断申请
void timer_irq_handler(void *arg)
{
	struct timer *timer = (struct timer *)arg;
	int flag;

	flag = timer->function(timer);
	if (timer->flag == TIMER_RESTART)
		start_timer(timer);
	else
		stop_timer();
}

int init_timer(struct timer *timer)
{
	int ret;

	ret = request_irq(ARCH_TIMER_IRQ, timer_irq_handler,
			  (void *)timer, IRQ_TYPE_LEVEL_HIGH);
	if (ret < 0) {
		printf("request irq error!\n");
		return ret;
	}

	return 0;
}

主要工作如下:

  • 申请中断,注册中断处理程序。
  1. 使能中断
void start_timer(struct timer *timer)
{
	unsigned long value, cmp;

	enable_irq(ARCH_TIMER_IRQ);
	value = 0;
	asm volatile("msr cntp_ctl_el0, %0" : : "r" (value));

	cmp = get_ticks() + (get_tbclk() / 1000) * timer->time_ms;
	asm volatile("msr cntp_cval_el0, %0" : : "r" (cmp));

	value = 1;
	asm volatile("msr cntp_ctl_el0, %0" : : "r" (value));
}

主要工作如下:

  • 使能中断

  • 设置arm generic timer的比较寄存器,满足条件下,触发中断信息发送到gic上

  • 使能generic timer定时器功能

  1. 关闭中断
void stop_timer(void)
{
	unsigned long value = 0;

	asm volatile("msr cntp_ctl_el0, %0" : : "r" (value));
	disable_irq(ARCH_TIMER_IRQ);
}

主要工作如下:

  • 关闭定时器使能

  • 关闭中断

  1. 释放中断资源
void del_timer(void)
{
	free_irq(ARCH_TIMER_IRQ);
}

主要工作:

  • 释放中断处理函数

2.6 待完善

  1. 不支持中断级联,需要考虑中断级联(irq domain)

  2. 目前uboot使用物理中断号,多个中断控制器时,会存在中断号冲突,需要考虑映射虚拟中断号

  3. 多个中断控制器驱动时,需要考虑处理中断控制器的关系,目前只支持一个中断控制器驱动注册

  4. 中断描述符,根据中断控制器驱动支持的中断数量动态分配

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值