ARM GICv3 ITS介绍及代码分析

本文深入探讨GICv3中的中断翻译服务(ITS)及其在Linux 4.19内核中的实现。ITS用于处理LPI中断,通过复杂的表结构进行中断转换和路由,提高多中断源场景下的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

在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做如下操作:

  1. 使用DeviceID,从设备表(device table entry)中选择索引为DeviceID的表项。从该表项中,得到中断 转换表(interrupt translation table)的位置
  2. 使用EventID,从中断转换表中选择索引为EventID的表项。得到中断号,以及中断所属的collection号
  3. 使用collection号,从collection表格中,选择索引为collection号的表项。得到redistributor的映射信息
  4. 根据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,
};

参考资料

  1. Arm Generic Interrupt Controller Architecture Specification
  2. GICv3 and GICv4 Software Overview
<think>好的,用户现在在问如何在Hypervisor中实现GICv3 LPI中断的虚拟化,并且希望得到软件实现的代码。首先,我需要回顾之前的对话,看看用户已经了解了什么。之前已经介绍GICv3 LPI虚拟化的基本原理,包括虚拟ITS、LPI配置表、中断注入流程等。现在用户可能想实际动手实现,需要具体的代码示例。 接下来,我需要考虑用户的需求是什么。用户可能是开发虚拟化平台的工程师,或者是在学习ARM虚拟化的学生。他们可能需要具体的代码结构来理解如何在实际中操作。但必须注意,虚拟化代码通常和具体的硬件、Hypervisor框架相关,比如KVM/Xen,所以代码示例可能需要简化,或者给出伪代码,而不是具体的实现。 另外,用户可能没有说出的是,他们可能需要指导如何开始编写代码,或者需要了解实现中的关键点,比如如何维护虚拟ITS、如何处理中断映射等。同时,安全问题、性能优化也是他们可能关心的,但当前的问题集中在代码实现上。 接下来,我需要整理之前的回答中的关键点,比如虚拟ITS的创建、LPI配置表的虚拟化、中断注入流程,并将这些转换为代码结构。由于实际的Hypervisor代码复杂,涉及很多底层操作和硬件交互,所以示例代码应该是伪代码,或者简化的C结构体,函数接口等。同时,需要指出依赖的硬件特性,比如GICv4.1的支持,以及可能需要的软件模块,如设备表管理、命令队列处理等。 还要考虑用户可能需要注意事项,比如内存隔离、权限验证、上下文切换优化等。代码示例需要展示这些方面,比如在虚拟ITS命令处理时过滤命令,验证VM的权限。另外,中断注入的流程需要展示如何将物理中断映射到虚拟中断,并更新Pending表。 可能还需要提到不同Hypervisor的区别,比如KVM和Xen在实现上的差异,但用户并没有指定,所以保持通用性更好。最后,提醒用户实际部署时需要结合具体硬件和Hypervisor框架进行调整,并参考厂商文档。</think>在Hypervisor中实现GICv3 LPI虚拟化需要结合具体虚拟化框架(如KVM/Xen),以下是基于软件模拟的核心代码框架和关键逻辑实现思路: --- ### **一、虚拟ITS核心数据结构(伪代码)** ```c // 虚拟ITS实例(每个VM一个) struct virt_its { uint32_t vits_id; // 虚拟ITS标识符 uint64_t cmdq_base; // 虚拟命令队列基地址 uint64_t device_table; // 虚拟设备表基地址 uint64_t collection_table; // 虚拟集合表基地址 spinlock_t lock; // 并发锁 }; // LPI映射条目 struct lpi_mapping { uint32_t phy_intid; // 物理中断号 uint32_t virt_intid; // 虚拟中断号 uint8_t priority; // 优先级 bool enabled; // 使能状态 uint16_t vpe_id; // 目标虚拟CPU }; // 全局ITS管理器 struct its_manager { struct list_head vits_list; // 所有虚拟ITS链表 struct rb_root lpi_map; // 物理→虚拟中断号映射红黑树 }; ``` --- ### **二、关键操作函数实现** #### **1. 虚拟ITS命令处理** ```c // 解析虚拟机下发的ITS命令(关键指令示例) void handle_its_command(struct virt_its *vits, uint64_t cmd) { uint8_t cmd_type = (cmd >> 0) & 0xFF; switch (cmd_type) { case ITS_CMD_MAPD: // 设备映射 uint32_t dev_id = (cmd >> 32) & 0xFFFF; uint64_t itt_addr = (cmd >> 16) & 0xFFFFFFFFFFFF; // 验证虚拟机是否有权访问该设备 if (!validate_device_access(vits->vm, dev_id)) { inject_abort(vits->vm); return; } // 更新虚拟设备表 update_virtual_device_table(vits, dev_id, itt_addr); break; case ITS_CMD_MAPC: // 集合映射 uint16_t col_id = (cmd >> 32) & 0xFFFF; uint16_t vpe_id = (cmd >> 48) & 0xFFFF; // 绑定集合到虚拟CPU bind_collection_to_vpe(vits, col_id, vpe_id); break; // 其他命令处理... } } ``` #### **2. 物理LPI→虚拟LPI转换** ```c // 中断到达时的转换逻辑 bool route_physical_lpi(uint32_t phy_intid, struct its_manager *mgr) { struct lpi_mapping *map; // 查找映射关系 spin_lock(&mgr->lock); map = find_lpi_map(mgr->lpi_map, phy_intid); if (!map || !map->enabled) { spin_unlock(&mgr->lock); return false; // 未分配或未使能 } // 注入虚拟中断到目标vCPU struct vcpu *target = get_vcpu_by_vpeid(map->vpe_id); inject_virtual_lpi(target, map->virt_intid, map->priority); // 更新虚拟Pending表 uint64_t *pendtab = get_vpendtab(target); set_bit(map->virt_intid % 256, pendtab); spin_unlock(&mgr->lock); return true; } ``` --- ### **三、关键内存表初始化示例** ```c // 初始化虚拟LPI配置表 void init_virtual_lpi_config(struct vm *vm) { uint64_t propbase = vm->gicr_propbase; // 来自虚拟机配置 uint8_t *config_table = map_guest_memory(vm, propbase, LPI_TABLE_SIZE); // 默认配置:禁用所有LPI memset(config_table, 0, LPI_TABLE_SIZE); flush_cache(config_table, LPI_TABLE_SIZE); } // 创建虚拟Pending表 void create_virtual_pendtab(struct vm *vm) { uint64_t pendbase = allocate_physical_page(); // 分配物理页 uint8_t *pendtab = map_to_guest(vm, pendbase, PAGE_SIZE); // 清空Pending状态 memset(pendtab, 0, PAGE_SIZE); vm->gicr_pendbase = pendbase; // 写入虚拟GICR_PENDBASER } ``` --- ### **四、中断注入接口(以KVM为例)** ```c // 向虚拟机注入虚拟LPI中断 void kvm_inject_vlpi(struct kvm_vcpu *vcpu, uint32_t virt_intid) { struct kvm *kvm = vcpu->kvm; struct vgic_irq *irq = vgic_get_irq(kvm, vcpu, virt_intid); spin_lock(&irq->irq_lock); if (!irq->active) { irq->pending = true; // 触发虚拟中断请求 kvm_vgic_kick_vcpu(vcpu); } spin_unlock(&irq->irq_lock); vgic_put_irq(kvm, irq); } ``` --- ### **五、安全关键检查(必须实现)** ```c // 验证虚拟机是否有权映射物理设备 bool validate_device_access(struct vm *vm, uint32_t dev_id) { // 检查设备ID是否在分配给该VM的范围内 return (dev_id >= vm->device_range_start && dev_id <= vm->device_range_end); } // 过滤非法ITS命令 bool filter_its_command(uint64_t cmd) { uint8_t cmd_type = cmd & 0xFF; // 禁止虚拟机执行某些高危命令(如DISCARD、INVALL) return (cmd_type != ITS_CMD_DISCARD && cmd_type != ITS_CMD_INVALL); } ``` --- ### **六、性能优化建议** 1. **缓存虚拟映射表**:使用两级缓存(如Radix Tree)加速物理→虚拟中断号查找 2. **批量中断处理**:对Pending表更新使用位图批量操作 3. **硬件加速支持**:若硬件支持GICv4.1,直接使用`VMAPTP`/`VMAPP`指令绕过模拟 --- ### **注意事项** - 完整实现需参考具体Hypervisor框架(如[KVM GICv3模拟代码](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/virt/kvm/arm/vgic/vgic-its.c)) - 必须与芯片的GIC实现严格对齐(如AWS Graviton3ITS扩展) - 调试时需结合系统跟踪工具(如ARM Fast Models + DS-5)
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值