前言:
在ARM gicv3中断控制器,有提到过ITS的作用,本篇就ITS进行更详细的介绍以及分析linux 内核中ITS代码的实现。
本文基于linux 4.19,介绍DT方式初始化的ITS代码。
ITS概述:
在GICv3中定义了一种新的中断类型,LPI(locality-specific peripheral interrupts)。LPI是一种基于消息的中断。中断信息不再通过中断线进行传递。
GICv3定义了两种方法实现LPI中断:
- forwarding方式
外设可以通过访问redistributor的寄存器GICR_SERLPIR,直接发送LPI中断 - 使用ITS方式
ITS(Interrupt Translation Service)在GICv3中是可选的。ITS负责接收来自外设的中断,并将它们转化为LPI INTID发送到相应的Redistributor
一般而言比较推荐使用ITS实现LPI,因为ITS提供了很多特性,在中断源比较多的场景,可以更加高效。
外设通过写GITS_TRANSLATER寄存器,发起LPI中断。此时ITS会获得2个信息:
EventID: 值保存在GITS_TRANSLATER寄存器中,表示外设发送中断的事件类型
DeviceID: 表示哪一个外设发起LPI中断。
ITS将DeviceID和eventID,通过一系列查表,得到LPI中断号,再使用LPI中断号查表,得到该中断的目标cpu。
The ITS table
当前,ITS使用三种类型的表来处理LPI的转换和路由:
device table: 映射deviceID到中断转换表
interrupt translation table:映射EventID到INTID。以及INTID属于的collection组
collection table:映射collection到Redistributor
所以一个ITS完整的处理流程是:
当外设往GITS_TRANSLATER寄存器中写数据后,ITS做如下操作:
- 使用DeviceID,从设备表(device table entry)中选择索引为DeviceID的表项。从该表项中,得到中断 转换表(interrupt translation table)的位置
- 使用EventID,从中断转换表中选择索引为EventID的表项。得到中断号,以及中断所属的collection号
- 使用collection号,从collection表格中,选择索引为collection号的表项。得到redistributor的映射信息
- 根据collection表项的映射信息,将中断信息,发送给对应的redistributor
The ITS Command:
its是由its的命令控制的。命令队列是一个循环buffer, 由三个寄存器定义。
GITS_CBASER: 指定命令队列的基地址和大小。命令队列必须64KB对齐,大小必须是4K的倍数。命令队列中的每一个索引是32字节。该寄存器还指定访问命令队列时its的cacheability和shareability的设置。
GITS_CREADR: 指向ITS将处理的下一个命令
GITS_CWRITER: 指向队列中应写入下一个新命令的索引。
在its的初始化过程以及lpi中断上报等过程中,会涉及到ITS command的发送。 具体的its commad指令参考spec.
现在我们已经知道ITS的具体作用以及处理流程,结合linux内核的实现进行分析。
ITS代码分析
its的代码位于drivers/irqchip/irq-gic-v3-its.c
1. ITS数据结构
struct its_node {
raw_spinlock_t lock;
struct list_head entry;
void __iomem *base;
phys_addr_t phys_base;
struct its_cmd_block *cmd_base;
struct its_cmd_block *cmd_write;
struct its_baser tables[GITS_BASER_NR_REGS];
struct its_collection *collections;
struct fwnode_handle *fwnode_handle;
u64 (*get_msi_base)(struct its_device *its_dev);
u64 cbaser_save;
u32 ctlr_save;
struct list_head its_device_list;
u64 flags;
unsigned long list_nr;
u32 ite_size;
u32 device_ids;
int numa_node;
unsigned int msi_domain_flags;
u32 pre_its_base; /* for Socionext Synquacer */
bool is_v4;
int vlpi_redist_offset;
};
base : its node的虚拟地址
phys_base: its node的物理地址
cmd_base: 命令队列的基地址
cmd_write: 指向队列中下一个命令的地址
tables[]: 指向device table或vpe table的结构体
collection: 指向its_collection结构体, 主要保存映射到的gicr的地址
cbaser_save: 保存cbaser寄存器的信息
ctlr_save:保存ctlr寄存器的
2. ITS的初始化
在gic初始化时,会进行ITS的初始化。
its的初始化操作主要是为its的device table以及collection table分配内存,并使能its.
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
.......
if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) -------- (1)
its_init(handle, &gic_data.rdists, gic_data.domain); ---- (2)
its_cpu_init(); ----- (3)
}
(1) ITS需要使能内核配置 CONFIG_ARM_GIC_V3_ITS. 如果架构支持LPI, 则进行ITS的初始化。
通过读GICD_TYPER(Interrupt Controller Type Register)寄存器的bit17查看架构是否支持LPI.
(2)its_init是 its的初始化入口。第三个参数需要注意下,它指定了its的parent domain是gic domain
(3) its_cpu_init 是在its初始化完成后,进行its的一些额外的配置,如enable lpi以及绑定its collection到its 目的redistributour。
1.1 its初始化函数its_init
int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
struct irq_domain *parent_domain)
{
...
its_parent = parent_domain;
of_node = to_of_node(handle);
if (of_node)
its_of_probe(of_node); --------- (1)
if (list_empty(&its_nodes)) {
pr_warn("ITS: No ITS available, not enabling LPIs\n");
return -ENXIO;
}
gic_rdists = rdists;
err = its_alloc_lpi_tables(); ------- (2)
if (err)
return err;
...
register_syscore_ops(&its_syscore_ops); ------(3)
return 0;
}
(1) its_of_probe
+->its_of_probe
+->its_probe_one
+->its_force_quiescent //让ITS处于非活动状态,在非静止状态改变ITS的配置会有安全的风险
+->its node malloc and init //为its_node分配空间,并对其进行初始化配置
+->its_alloc_tables //为device table 和 vpe table分配内存
+->its_alloc_collections //为collection table中映射到的gicr 地址分配内存; 每一个its都有一个collection table, ct可以保存在寄存器(GITS_BASER)或者内存(GITS_TYPER.HCC)
+->its_init_domain // its domain初始化,注册its domain相关操作
its probe过程, 主要是初始化its node数据结构, 为its tables分配内存, 初始化its domain并注册its domain相关操作。
its_domain初始化过程中,会指定its irq_domain的host_data为msi_domain_info, 在info-ops.prepare过程中会去创建ITS设备, its translation table会在那个阶段分配内存。
此外,its probe过程中还有一个标志位会被设置。
if (GITS_TYPER_HCC(typer))
its->flags |= ITS_FLAGS_SAVE_SUSPEND_STATE;
如果GITS_TYPER.hcc不为0, 那么就会将its->flags置为SUSPEND。
这个标志位可以判断its需不需要进行suspend或者resume流程,下文再详细描述。
(2) its_alloc_lpi_tables
+-> its_alloc_lpi_tables
+-> its_allocate_prop_table // 初始化lpi中断的配置表和状态表
+-> its_lpi_init
ITS 是为LPI服务的,所以在ITS初始化过程中还需要初始化LPI需要的两张表
(LPI configuration table, LPI pending tables ), 然后进行lpi的初始化。
LPI的这两张表就是LPI和其他类型中断的区别所在: LPI的中断的配置,以及中断的状态,是保存在memory的表中,而不是保存在gic的寄存器中的。
LPI 中断配置表:
中断配置表的基地址由GICR_PROPBASER寄存器决定。
对于LPI配置表,每个LPI中断占用1个字节(bit[7:0]),指定了该中断的使能(bit 0)和中断优先级(bit[7:2])。
当外部发送LPI中断给redistributor,redistributor首先要访问memory来获取LPI中断的配置表。为了加速这过程,redistributor中可以配置cache,用来缓存LPI中断的配置信息。
因为有了cache,所以LPI中断的配置信息,就有了2份拷贝,一份在memory中,一份在redistributor的cache中。如果软件修改了memory中的LPI中断的配置信息,需要将redistributor中的cache信息给无效掉。
通过该接口刷相关dcache
gic_flush_dcache_to_poc()
LPI 中断状态表
中单状态表的基地址由GICR_PENDBASER寄存器决定, 该寄存器还可以设置LPI中断状态表memory的属性,如shareability,cache属性等。
该状态表主要用于查看LPI是否pending状态。
该中断状态表由redistributor来设置。每个LPI中断,占用一个bit空间。
0: 该LPI中断,没有处于pending状态
1: 该LPI中断,处于pending状态
(3) register_syscore_ops
该操作主要是注册两个低功耗流程会用到的函数, suspend和resume。
在系统进行低功耗流程时(suspend 或者hibernate, 当然目前4.19还不支持its的hibernate), suspend时会调用its_save_disable, 保存its的一些寄存器状态,并disable its, 在 resume时调用its_restore_enable, 恢复之前its保存的寄存器状态,并enable its.
static struct syscore_ops its_syscore_ops = {
.suspend = its_save_disable,
.resume = its_restore_enable,
};
这个流程由低功耗的框架保证, 只需要通过register_syscore_ops函数注册suspend和resume函数即可。
1.2 its_cpu_init
+-> its_cpu_init
+-> its_cpu_init_lpis // 配置lpi 配置表和状态表, 以及使能lpi
+-> its_cpu_init_collections // 绑定每一个collection到target redistributor
+-> its_send_mapc // 发送its mapc command, mapc主要用于映射collection到目的redistributor
+-> its_send_invall //指定 memory中的LPI中断的配置信息和cache中保存的必须一致
3. its中断上报
和gic类似, 在中断上报时,如果设备挂载在its 下, 会调用到its domain的一系列operation
static const struct irq_domain_ops its_domain_ops = {
.alloc = its_irq_domain_alloc,
.free = its_irq_domain_free,
.activate = its_irq_domain_activate,
.deactivate = its_irq_domain_deactivate,
};
参考资料
- Arm Generic Interrupt Controller Architecture Specification
- GICv3 and GICv4 Software Overview