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说明
- 中断使能接口
void enable_irq(unsigned int irq);
- 中断关闭接口
void disable_irq(unsigned int irq);
- 中断申请接口
int request_irq(unsigned int irq, interrupt_handler_t handler, void *arg, unsigned long type);
- 中断释放接口
void free_irq(unsigned int irq);
- 中断控制器驱动注册接口
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);
}
在中断处理函数中,主要做以下事情:
-
判断中断时IPI中断,还是SPI或者PPI中断,只支持处理PPI和SPI中断
-
从GIC相关寄存器,获取硬件中断号
-
根据中断号,找到对应中断处理函数,调用驱动注册的中断处理函数
-
发送eoi信号,清除gic中断相关标志位,表示该中断处理完成。
2.5 中断使用说明
本小节以arm generic timer为例,对中断使用进行说明,如下:
- 中断申请
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;
}
主要工作如下:
- 申请中断,注册中断处理程序。
- 使能中断
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定时器功能
- 关闭中断
void stop_timer(void)
{
unsigned long value = 0;
asm volatile("msr cntp_ctl_el0, %0" : : "r" (value));
disable_irq(ARCH_TIMER_IRQ);
}
主要工作如下:
-
关闭定时器使能
-
关闭中断
- 释放中断资源
void del_timer(void)
{
free_irq(ARCH_TIMER_IRQ);
}
主要工作:
- 释放中断处理函数
2.6 待完善
-
不支持中断级联,需要考虑中断级联(irq domain)
-
目前uboot使用物理中断号,多个中断控制器时,会存在中断号冲突,需要考虑映射虚拟中断号
-
多个中断控制器驱动时,需要考虑处理中断控制器的关系,目前只支持一个中断控制器驱动注册
-
中断描述符,根据中断控制器驱动支持的中断数量动态分配