ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL AXI交互(2-2)

ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL数据的AXI交互(2-2)

内容接
ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL数据的AXI交互(2-1)
4. XGpioPs_CfgInitialize函数

  • 函数功能: 该函数用于设置 GPIO 单个引脚的输出使能(即允许或禁止该引脚作为输出)。它先通过断言校验输入参数,然后将全局引脚号转换为所属 Bank 及 Bank 内位号,接着读取对应 Bank 的输出使能寄存器,根据参数修改目标位,最后将修改值写回寄存器。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
*
* 设置指定引脚的输出使能。
*
* @param	InstancePtr 是指向 XGpioPs 实例的指针。
* @param	Pin 是要写入数据的引脚编号。
*		在 Zynq 中有效值为 0-117,在 Zynq Ultrascale+ MP 中为 0-173。
* @param	OpEnable 指定是否使能指定引脚的输出使能。
*		有效值为 0(禁止输出使能),1(使能输出使能)。
*
* @return	无。
*
* @note		无。
*
*****************************************************************************/
void XGpioPs_SetOutputEnablePin(const XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)
{
	u8 Bank;                     // 存储引脚所在的 bank 编号
	u8 PinNumber;                // 存储引脚在 bank 内的位号(0~31)
	u32 OpEnableReg;             // 存储读出的输出使能寄存器值

	Xil_AssertVoid(InstancePtr != NULL);                      // 断言:实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 断言:实例已初始化就绪
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);            // 断言:引脚号在有效范围内
	Xil_AssertVoid(OpEnable <= (u32)1);                      // 断言:输出使能值合法(0或1)

	/* 获取引脚所在的 bank 编号以及在 bank 内的位号。 */
#ifdef versal
	// Versal 平台需要传入实例指针(用于区分 PMC/PS GPIO)
	XGpioPs_GetBankPin(InstancePtr, (u8)Pin, &Bank, &PinNumber);
#else
	// 其他平台(Zynq、ZynqMP)使用简化版本
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);
#endif

	// 读取对应 bank 的输出使能寄存器(OUTEN)当前值
	OpEnableReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				       ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				       XGPIOPS_OUTEN_OFFSET);

	if (OpEnable != (u32)0) {          // 如果 OpEnable 非 0(即为 1),表示要使能输出
		OpEnableReg |= ((u32)1 << (u32)PinNumber); // 将对应位设为 1(输出使能)
	} else {                           // 否则禁止输出使能
		OpEnableReg &= ~((u32)1 << (u32)PinNumber); // 将对应位清零(输出禁止)
	}

	// 将修改后的值写回输出使能寄存器
	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			  ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			  XGPIOPS_OUTEN_OFFSET, OpEnableReg);
}
  • 编程逻辑
    XGpioPs_SetOutputEnablePin函数和XGpioPs_SetDirectionPin函数的写法很类似,没有太多深入分析的必要。基本可以分为三步,第一步用Xil_AssertVoid断言形参是有意义的,比如指针非空,初始化已就绪等。然后读取输出使能寄存器,最后将想改写的位改成需要的值,其他位的值不变进行写回。
    5. XGpioPs_WritePin函数
  • 函数功能:该函数用于向指定的 GPIO 引脚写入数据(高电平或低电平)。实现路径为:先通过断言检查参数有效性,再将全局引脚号转换为所属 Bank 和 Bank 内位号,然后根据位号选择使用低 16 位或高 16 位数据寄存器,最后构造一个包含掩码和数据的 32 位值写入硬件,确保只修改目标引脚而保持其他引脚状态不变。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
*
* 向指定引脚写入数据。
*
* @param	InstancePtr 是指向 XGpioPs 实例的指针。
* @param	Pin 是要写入数据的引脚编号。
*		在 Zynq 中有效值为 0-117,在 Zynq Ultrascale+ MP 中为 0-173。
* @param	Data 是要写入指定引脚的数据(0 或 1)。
*
* @return	无。
*
* @note		该函数通过掩码方式写入指定 GPIO Bank 的指定引脚。
*		其他引脚的先前状态保持不变。
*
*****************************************************************************/
void XGpioPs_WritePin(const XGpioPs *InstancePtr, u32 Pin, u32 Data)
{
	u32 RegOffset;                               // 存储寄存器偏移(低16位或高16位)
	u32 Value;                                   // 存储最终要写入寄存器的32位值
	u8 Bank;                                     // 引脚所属的 Bank 编号
	u8 PinNumber;                                // 引脚在 Bank 内的位号(0~31)
	u32 DataVar = Data;                          // Data 的副本,用于后续位操作

	Xil_AssertVoid(InstancePtr != NULL);                      // 断言:实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 断言:实例已初始化就绪
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);            // 断言:引脚号有效

	/* 获取引脚所在的 Bank 编号以及在 Bank 内的位号。 */
#ifdef versal
	XGpioPs_GetBankPin(InstancePtr, (u8)Pin, &Bank, &PinNumber); // Versal 平台需传实例指针
#else
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);         // 其他平台直接转换
#endif

	if (PinNumber > 15U) {
		/* 掩码数据寄存器只有 16 位数据位(0-15),超过 15 的位需使用高16位寄存器 */
		PinNumber -= (u8)16;                               // 减去16,映射到高16位中的位置
		RegOffset = XGPIOPS_DATA_MSW_OFFSET;               // 使用高16位数据寄存器偏移
	} else {
		RegOffset = XGPIOPS_DATA_LSW_OFFSET;               // 使用低16位数据寄存器偏移
	}

	/*
	 * 构造待写入掩码/数据寄存器的 32 位值:
	 * 高16位是掩码(写1表示保持原值,写0表示允许修改),低16位是数据值。
	 */
	DataVar &= (u32)0x01;                                  // 只保留 Data 的最低位(0或1)
	Value = ~((u32)1 << (PinNumber + 16U)) &               // 掩码:将目标位对应的掩码位设为0
		((DataVar << PinNumber) | 0xFFFF0000U);          // 数据:将数据位放到低16位,高16位全1(保持其他位)
	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,     // 写入寄存器
			  ((u32)(Bank) * XGPIOPS_DATA_MASK_OFFSET) +
			  RegOffset, Value);
}

6. XGpio_Initialize函数

  • 函数功能:该函数根据传入的设备 ID 初始化 XGpio 驱动实例。实现路径为:先断言实例指针非空,然后调用 XGpio_LookupConfig 查找匹配的配置项;若查找失败则标记实例未就绪并返回设备未找到错误;若成功则调用 XGpio_CfgInitialize 完成实例配置初始化并返回成功状态。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
* 基于给定的设备 ID 初始化调用者提供的 XGpio 实例。
*
* 除了初始化 InstancePtr 外,不做其他操作。
*
* @param	InstancePtr 是指向 XGpio 实例的指针。该指针所引用的内存必须
*		由调用者预先分配。后续通过 XGpio API 操作实例/驱动时必须使用此指针。
* @param	DeviceId 是由此 XGpio 实例控制的设备的唯一 ID。传入设备 ID 将
*		把通用的 XGpio 实例关联到特定的设备,由调用者或应用开发者选择。
*
* @return
*		- XST_SUCCESS:初始化成功。
*		- XST_DEVICE_NOT_FOUND:未找到具有所提供设备 ID 的设备配置数据。
*
* @note		无。
*
*****************************************************************************/
int XGpio_Initialize(XGpio * InstancePtr, u16 DeviceId)
{
	XGpio_Config *ConfigPtr;   // 定义配置指针,用于暂存查找到的配置项

	/*
	 * 断言参数
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);   // 确保实例指针非空,否则触发断言并返回0

	/*
	 * 在设备配置表中查找配置数据。
	 * 在下方初始化此驱动时使用此配置信息。
	 */
	ConfigPtr = XGpio_LookupConfig(DeviceId);   // 根据设备ID查找配置表项
	if (ConfigPtr == (XGpio_Config *) NULL) {   // 若未找到匹配的配置
		InstancePtr->IsReady = 0;              // 将实例的就绪标志设为0(未就绪)
		return (XST_DEVICE_NOT_FOUND);         // 返回设备未找到错误
	}

	// 找到配置,调用配置初始化函数完成实例初始化,并返回其执行结果
	return XGpio_CfgInitialize(InstancePtr, ConfigPtr,
				   ConfigPtr->BaseAddress);
}
  • 编程逻辑
    XGpio_Initialize函数实际上包含两个函数即XGpio_LookupConfigXGpio_CfgInitialize和这两个函数的功能之前初始化ps端的io是一样的。XGpio_LookupConfig是根据ID将该ID代表的设备参数如基址作为返回值赋给ConfigPtr 配置指针变量。XGpio_CfgInitialize函数如下所示,这个函数还是记录一些device的参数信息,如之前在XGpio_LookupConfig已经找到的InterruptPresent(中断是否存在),IsDual(是否双通道),还有EffectiveAddr(基地址,其实在上一层还是用的ConfigPtr 的元素),这个函数唯一产生的数据就是XIL_COMPONENT_IS_READY。也就是XGpio_CfgInitialize函数干的活就是把ConfigPtr(也就是系统产生的XGpio_ConfigTable变量的值)都倒给了InstancePtr,然后又给InstancePtr变量加了个READY-就绪信号。XGpio_Initialize的功能也就此解开:那就是根据DeviceId找到与其相关的XGpio_ConfigTable元素,然后都赋值给InstancePtr变量,再加个就绪信号。
  • 辅助说明
    1> XGpio_CfgInitialize函数
c 复制代码
/****************************************************************************/
/**
* 基于给定的配置数据初始化调用者提供的 XGpio 实例。
*
* 除了初始化 InstancePtr 外,不做其他操作。
*
* @param	InstancePtr 是指向 XGpio 实例的指针。该指针所引用的内存必须
*		由调用者预先分配。后续通过 XGpio API 操作驱动时必须使用此指针。
* @param	Config 是一个指向包含特定 GPIO 设备信息的结构体的引用。
*		此函数根据 Config 的内容初始化指定设备的 InstancePtr 对象。
*		通过多次调用并传入不同的 Config 信息,可以初始化多个实例对象。
* @param 	EffectiveAddr 是虚拟内存地址空间中的设备基地址。
*		调用者有责任确保在调用此函数后,从 EffectiveAddr 到设备物理基地址
*		的地址映射保持不变。如果在调用此函数后地址映射发生变化,可能会
*		发生意外错误。如果不使用地址转换,则使用 Config->BaseAddress
*		作为此参数,即传递物理地址。
*
* @return
* 		- XST_SUCCESS:初始化成功。
*
* @note		无。
*
*****************************************************************************/
int XGpio_CfgInitialize(XGpio * InstancePtr, XGpio_Config * Config,
			UINTPTR EffectiveAddr)
{
	/* 断言参数 */
	Xil_AssertNonvoid(InstancePtr != NULL);   // 确保实例指针非空,否则触发断言并返回0

	/* 设置一些默认值 */
	InstancePtr->BaseAddress = EffectiveAddr; // 设置设备基地址

	InstancePtr->InterruptPresent = Config->InterruptPresent; // 设置中断存在标志
	InstancePtr->IsDual = Config->IsDual;                    // 设置是否双通道标志

	/*
	 * 指示实例现在已就绪可用,初始化无错误
	 */
	InstancePtr->IsReady = XIL_COMPONENT_IS_READY; // 标记实例为就绪状态
	return (XST_SUCCESS);                           // 返回初始化成功
}

7. XScuGic_LookupConfig函数

  • 功能简介:XScuGic_LookupConfig函数根据唯一的设备 ID 在系统配置表中查找对应的 SCU GIC 设备配置信息。实现路径为:遍历全局配置表 XScuGic_ConfigTable[],比较每个表项的 DeviceId 与传入参数,找到匹配项后返回其指针;若未找到则返回 NULL。
  • 函数主体
c 复制代码
/*****************************************************************************/
/**
*
* 根据唯一的设备 ID 查找设备配置信息。
* 系统中的一个表格包含了每个设备的配置信息。
*
* @param	DeviceId 是设备的唯一标识符。
*
* @return	指向指定设备的 XScuGic 配置结构体的指针,
*         如果未找到该设备则返回 NULL。
*
* @note		无。
*
******************************************************************************/
XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId)
{
	XScuGic_Config *CfgPtr = NULL;      // 初始化配置指针为 NULL,用于保存找到的表项地址
	u32 Index;                          // 定义循环索引变量

	for (Index = 0U; Index < (u32)XPAR_SCUGIC_NUM_INSTANCES; Index++) {
		// 遍历配置表,XPAR_SCUGIC_NUM_INSTANCES 是系统生成的宏,表示 SCU GIC 实例总数
		if (XScuGic_ConfigTable[Index].DeviceId == DeviceId) {
			// 如果当前表项的 DeviceId 与传入的 DeviceId 匹配
			CfgPtr = &XScuGic_ConfigTable[Index]; // 将该表项的地址赋给 CfgPtr
			break;                                 // 找到后立即退出循环
		}
	}

	return (XScuGic_Config *)CfgPtr;    // 返回配置指针(若未找到则为 NULL)
}
  • 程序分析:函数首先声明了一个指向配置结构体的指针 CfgPtr 并初始化为 NULL,这一步确保了在查找失败时函数有明确的返回值,避免返回未初始化的野指针。接着定义了一个循环索引变量 Index,用于遍历配置表。然后进入 for 循环,循环上限使用系统生成的宏 XPAR_SCUGIC_NUM_INSTANCES,该宏反映了系统中 SCU GIC 实例的数量,使代码能够自动适配不同的硬件配置,无需硬编码。在循环体内,逐一判断当前表项的 DeviceId 是否等于传入的参数,若相等则将当前表项的地址赋值给 CfgPtr,并立即 break 跳出循环------因为设备 ID 具有唯一性,找到后无需继续查找。最后,函数返回 CfgPtr(要么指向匹配的配置项,要么为 NULL),调用者可以根据返回值判断是否查找成功。
  • 辅助说明
    1> XScuGic_ConfigTable数组
    可以发现只有一个元素,包括ID和中断控制器基址(XPAR_PS7_SCUGIC_0_BASEADDR-0xF8F00100)和分发器基地址(XPAR_PS7_SCUGIC_0_DIST_BASEADDR-0xF8F01000)
c 复制代码
XScuGic_Config XScuGic_ConfigTable[XPAR_XSCUGIC_NUM_INSTANCES] =
{
	{
		XPAR_PS7_SCUGIC_0_DEVICE_ID,
		XPAR_PS7_SCUGIC_0_BASEADDR,
		XPAR_PS7_SCUGIC_0_DIST_BASEADDR,
		{{0}}		/**< Initialize the HandlerTable to 0 */
	}
};

8. XScuGic_CfgInitialize函数

  • 功能简介:XScuGic_CfgInitialize用于初始化 SCU GIC(通用中断控制器)的驱动实例。先进行参数断言和单双核配置检查,若实例尚未就绪,则初始化中断处理函数表(为每个中断号填入存根函数和回调引用),然后根据 GIC 版本执行分发器和 CPU 接口的初始化,最后标记实例为就绪状态并返回成功。
  • 函数主体
c 复制代码
/*****************************************************************************/
/**
*
* 对特定的中断控制器实例/驱动进行配置初始化。初始化包括:
*
* - 初始化 XScuGic 结构体的字段
* - 使用存根函数调用初始化向量表
* - 所有中断源被禁用
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
* @param	ConfigPtr 是指向与此驱动关联的特定设备的配置表的指针。
* @param	EffectiveAddr 是虚拟内存地址空间中的设备基地址。
*		调用者有责任确保在调用此函数后,从 EffectiveAddr 到设备物理基地址
*		的地址映射保持不变。如果在调用此函数后地址映射发生变化,可能会
*		发生意外错误。如果不使用地址转换,则使用 Config->BaseAddress
*		作为此参数,即传递物理地址。
*
* @return
*		- XST_SUCCESS:初始化成功
*
* @note		无。
*
******************************************************************************/
s32  XScuGic_CfgInitialize(XScuGic *InstancePtr,
				XScuGic_Config *ConfigPtr,
				u32 EffectiveAddr)
{
	u32 Int_Id;
	(void) EffectiveAddr;   // 消除未使用参数的编译警告

	Xil_AssertNonvoid(InstancePtr != NULL);   // 断言:实例指针非空
	Xil_AssertNonvoid(ConfigPtr != NULL);     // 断言:配置指针非空
	/*
     * 检测 Zynq-7000 基础硅片配置:双核或单核。
     * 如果是单核配置,则对 CPU ID=1 调用断言
	 */
#ifdef ARMA9
	if (XPAR_CPU_ID == 0x01) {   // 如果当前 CPU ID 为 1(即第二个 CPU)
		// 读取 EFUSE 状态寄存器,检查是否为双核,若是单核则触发断言
		Xil_AssertNonvoid((Xil_In32(XPS_EFUSE_BASEADDR
			+ EFUSE_STATUS_OFFSET) & EFUSE_STATUS_CPU_MASK) == 0);
	}
#endif

	if(InstancePtr->IsReady != XIL_COMPONENT_IS_READY) {  // 仅当实例未就绪时执行初始化

		InstancePtr->IsReady = 0U;                     // 标记为未就绪
		InstancePtr->Config = ConfigPtr;               // 保存配置指针

		// 遍历所有可能的中断输入
		for (Int_Id = 0U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS; Int_Id++) {
			/*
			* 将处理函数初始化为指向存根函数,以处理尚未连接处理函数的中断。
			* 仅当处理函数为 0(即未被工具/用户静态初始化)时才进行初始化。
			* 设置回调引用指向此实例,以便追踪未处理的中断。
			*/
			if ((InstancePtr->Config->HandlerTable[Int_Id].Handler
					== (Xil_InterruptHandler)NULL)) {   // 如果处理函数为空
				// 设置为存根处理函数
				InstancePtr->Config->HandlerTable[Int_Id].Handler
						= (Xil_InterruptHandler)StubHandler;
			}
			// 设置回调引用指向当前实例
			InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = InstancePtr;
		}
#if defined (GICv3)   // 如果使用 GICv3 版本
	u32 Waker_State;
	// 读取重分配器的 Waker 寄存器
	Waker_State = XScuGic_ReDistReadReg(InstancePtr, XSCUGIC_RDIST_WAKER_OFFSET);
	// 清除低功耗状态位,唤醒 GIC
	XScuGic_ReDistWriteReg(InstancePtr, XSCUGIC_RDIST_WAKER_OFFSET,
							Waker_State & (~ XSCUGIC_RDIST_WAKER_LOW_POWER_STATE_MASK));
		/* 通过 ICC_SRE_EL1 使能系统寄存器接口 */
		#if EL3
			XScuGic_Enable_SystemReg_CPU_Interface_EL3();   // EL3 下使能
		#endif
			XScuGic_Enable_SystemReg_CPU_Interface_EL1();   // EL1 下使能
		isb();   // 指令同步屏障,确保使能生效
#endif
		XScuGic_Stop(InstancePtr);                 // 停止 GIC,使其处于已知状态
		DistributorInit(InstancePtr);              // 初始化分发器
#if defined (GICv3)
		XScuGic_set_priority_filter(0xff);         // GICv3:设置优先级过滤
#else
		CPUInitialize(InstancePtr);                // 非 GICv3:初始化 CPU 接口
#endif
		InstancePtr->IsReady = XIL_COMPONENT_IS_READY;   // 标记实例就绪
	}

	return XST_SUCCESS;   // 返回成功
}
  • 程序分析
    首先通过 (void)EffectiveAddr 消除未使用参数的编译警告,接着使用 Xil_AssertNonvoid 断言确保实例指针和配置指针非空。然后通过条件编译 #ifdef ARMA9 检测是否为 Zynq-7000 单核配置:如果当前 CPU ID 为 1(即使用第二个 CPU),则读取 EFUSE 状态寄存器检查实际是否为双核设计,若是单核则触发断言------这是为了防止在单核芯片上使用第二个 CPU 导致错误。接下来判断实例的 IsReady 标志:若已就绪则跳过初始化直接返回成功,否则开始初始化。先将 IsReady 置 0,保存配置指针。然后循环遍历所有中断输入(0 到 XSCUGIC_MAX_NUM_INTR_INPUTS-1),对于每个中断号,如果处理函数表中对应的 Handler 为空(未被静态初始化),则将其设置为存根函数 StubHandler,同时将回调引用指向当前实例,这样未连接的中断会进入存根函数并可通过实例定位。由于ZYNQ GIC版本不是GICv3部分语句跳过,直接调用 XScuGic_Stop 停止 GIC,然后调用 DistributorInit 初始化分发器,再根据版本调用 CPUInitialize 初始化 CPU 接口。最后将 IsReady 标记为 XIL_COMPONENT_IS_READY,返回 XST_SUCCESS。
  • 辅助说明
    1>XScuGic_Stop函数
c 复制代码
/****************************************************************************/
/**
* 检查中断目标寄存器是否将所有中断都定向到当前 CPU。
* 如果它们被编程为转发到当前 CPU,此 API 会禁用所有中断并禁用 GIC 分发器。
* 此 API 还会从所有中断的中断目标寄存器中移除当前 CPU。
*
* @param	InstancePtr 是指向要操作的实例的指针。
*
* @return	无。
*
* @note		无
*
*****************************************************************************/
void XScuGic_Stop(XScuGic *InstancePtr)
{
	u32 Int_Id;                         // 中断号循环变量
	u32 RegValue;                       // 暂存寄存器值
	u32 Target_Cpu;                     // 中断目标 CPU 寄存器值
	u32 DistDisable = 1;                // 标记分发器是否可以禁用,初始为 1(可以)
	u32 LocalCpuID = ((u32)0x1 << CpuId); // 当前 CPU 的 ID 掩码(这里CpuId = 0,因此LocalCpuID  = 1)

	Xil_AssertVoid(InstancePtr != NULL); // 断言:实例指针非空

	/* 如果分发器已经禁用,则无需做任何事 */
	RegValue = XScuGic_DistReadReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET); // 读取分发器使能寄存器
	if ((RegValue & XSCUGIC_EN_INT_MASK) == 0U) { // 如果分发器使能位为 0(已禁用)
		return;                                 // 直接返回
	}

	// 将当前 CPU 掩码扩展到 32 位(每 8 位重复一次)
	LocalCpuID |= LocalCpuID << 8U;     // 低 16 位填充,(这里LocalCpuID  = 0x0101)
	LocalCpuID |= LocalCpuID << 16U;    // 高 16 位填充,(这里LocalCpuID  = 0x01010101)

	/*
	 * 检查中断是否仅定向到当前 CPU。
	 * 同时从所有中断的中断目标寄存器中移除当前 CPU。
	 */
	for (Int_Id = 32U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS; // 遍历 SPI 中断(从 32 号开始)
			Int_Id = Int_Id+4U) {   // 每个目标寄存器对应 4 个中断

		Target_Cpu = XScuGic_DistReadReg(InstancePtr,
					XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id)); // 读取当前 4 个中断的目标寄存器
	//#define XSCUGIC_SPI_TARGET_OFFSET_CALC(InterruptID) ((u32)XSCUGIC_SPI_TARGET_OFFSET + (((InterruptID)/4U) * 4U))
	//也就是XSCUGIC_SPI_TARGET_OFFSET_CALC计算方式是Int_Id对4去余(不是取余)然后乘4,这是因为每个目标寄存器占用 4 字节,(InterruptID)/4U 
	//算出寄存器索引,再乘以 4 得到该寄存器相对于基地址的字节偏移,从而定位到正确的寄存器。例如,中断 32~35 的 InterruptID/4 为 8,
	//偏移为8*4=32,加上基地址即得目标寄存器的偏移量。XSCUGIC_MAX_NUM_INTR_INPUTS为95,也就是这个循环会从32一直增加到95结束。最终的计算值
	//应该是0x20,0x24,0x28,0x2C,0x30,0x34,0x38......0x5C(92U)。这些都属于SPI。
	((u32)XSCUGIC_SPI_TARGET_OFFSET + (((InterruptID)/4U) * 4U))
		if ((Target_Cpu != LocalCpuID) && (Target_Cpu != 0)) {
			/*
			 * 如果目标寄存器中还编程了其他 CPU,
			 * 则 GIC 分发器不能禁用。
			 */
			DistDisable = 0;        // 标记分发器不可禁用
		}

		/* 从中断目标寄存器中移除当前 CPU */
		Target_Cpu &= (~LocalCpuID); // 清除当前 CPU 对应的位
		XScuGic_DistWriteReg(InstancePtr,
		XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id), Target_Cpu); // 写回寄存器
	}

	/*
	 * 如果 GIC 分发器可以安全禁用,则禁用所有中断,然后禁用分发器。
	 */
	if (DistDisable == 1) { // 如果所有中断都只定向到当前 CPU
		for (Int_Id = 0U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS;
				Int_Id = Int_Id+32U) { // 步长 32,每个禁用寄存器控制 32 个中断
			/*
			 * 禁用所有中断
			 */
			XScuGic_DistWriteReg(InstancePtr,
			  XSCUGIC_EN_DIS_OFFSET_CALC(XSCUGIC_DISABLE_OFFSET,
							Int_Id), // 计算禁用寄存器的偏移
			0xFFFFFFFFU); // 写入全 1,禁用这 32 个中断
		}
		XScuGic_DistWriteReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET, 0U); // 禁用分发器
	}
}

第一个for循环读取的寄存器地址计算过程为:绝对地址=(InstancePtr)->Config->DistBaseAddress + XSCUGIC_SPI_TARGET_OFFSET_CALC(InterruptID) = (InstancePtr)->Config->DistBaseAddress + ((u32)XSCUGIC_SPI_TARGET_OFFSET + (((InterruptID)/4U) * 4U)) = 0xF8F01000(前面的函数已获得)+ 0x00000800U (来自系统宏定义)+ (InterruptID)/4U) * 4U(函数体的长注释中有说明)=0xF8F01820,0xF8F01824,0xF8F01828,0xF8F0182C......查找UG585可知均为中断处理目标寄存器定义如下(每个寄存器都是相似的,只有ID不一样),配置这个寄存器,本质上是在决定:"当这个中断发生时,应该通知哪个CPU来干活。"比如"CPU 0 targeted" 就是指该中断的目标(Target)是 CPU 0,意味着当这个中断发生时,GIC 会把它路由到 CPU 0,由 CPU 0 来响应和处理。

2>DistributorInit函数

这个函数很简单,其实就是为了调用DoDistributorInit函数。其先调用 XScuGic_DistReadReg 读取分发器使能寄存器的值,判断 GIC 分发器是否已经被使能(即 XSCUGIC_EN_INT_MASK 位是否为 0)。如果分发器尚未使能(值为 0),则调用 DoDistributorInit 执行真正的分发器初始化

c 复制代码
/*****************************************************************************/
/**
*
* DistributorInit 初始化 GIC 的分发器。它调用 DoDistributorInit 来完成初始化。
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
*
* @return	无
*
* @note		无。
*
******************************************************************************/
static void DistributorInit(XScuGic *InstancePtr)
{
	u32 RegValue;   // 用于暂存读取的寄存器值

#if USE_AMP==1 && (defined (ARMA9) || defined(__aarch64__))
#warning "Building GIC for AMP"
	/*
	 * 在 openamp 配置中,GIC 初始化由主 CPU 负责,
	 * 因此这里什么也不做,直接返回。
	 */
	return;   // AMP 模式下,从 CPU 不参与 GIC 初始化
#endif

	Xil_AssertVoid(InstancePtr != NULL);   // 断言:实例指针非空

	// 读取分发器使能寄存器的值
	RegValue = XScuGic_DistReadReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET);
	if ((RegValue & XSCUGIC_EN_INT_MASK) == 0U) {   // 如果分发器尚未使能
		DoDistributorInit(InstancePtr);   // 调用真正的初始化函数
		return;
	}
	// 如果分发器已经使能,则直接返回,不做任何操作
}

DoDistributorInit 函数

DoDistributorInit 函数用于初始化 GIC 分发器,使其处于可工作的安全状态。首先禁用分发器(GICv3 还需配置安全路由模式),然后依次为所有 SPI 中断设置触发模式(电平敏感、高有效)、为所有中断设置默认优先级(0xA0)、根据 GIC 版本配置安全域,接着禁用所有中断源,最后重新使能分发器(GICv3 还需使能 Group0 和 Group1 中断)

c 复制代码
/*****************************************************************************/
/**
*
* DoDistributorInit 初始化 GIC 的分发器。初始化包括:
*
* - 写入触发模式、优先级
* - 禁用所有中断源
* - 使能分发器
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
*
* @return	无
*
* @note		无。
*
******************************************************************************/
static void DoDistributorInit(XScuGic *InstancePtr)
{
	u32 Int_Id;   // 中断号循环变量

#if defined (GICv3)   // 如果使用 GICv3 版本
	u32 Temp;   // 临时变量

	// 读取分发器使能寄存器的当前值
	Temp = XScuGic_DistReadReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET);
	// 构造需要设置的值:使能 ARE_NS 和 ARE_S(亲和性路由),不使能分发器本身
	Temp = (XSCUGIC500_DCTLR_ARE_NS_ENABLE | XSCUGIC500_DCTLR_ARE_S_ENABLE);
	Temp &= ~(XSCUGIC_EN_INT_MASK);   // 清除使能位
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET, Temp); // 禁用分发器并设置 ARE
#else   // 非 GICv3(如 GICv2,ZYNQ执行此分支)
	// 直接向使能寄存器写入 0,禁用分发器
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET, 0U);
#endif

	/*
	 * 在 int_security 寄存器中为非安全中断设置安全域
	 * 所有中断都是安全的,因此保留默认值。对于非安全中断,设置为 1。
	 * (此处不执行实际写入,保持默认全安全)
	 */

	/*
	 * 对于共享外设中断 SPI(INT_ID[最大..32]),进行以下设置:
	 */

	/*
	 * 1. 在 int_config 寄存器中设置触发模式
	 * 只写入 SPI 中断,因此从 32 开始
	 */
	for (Int_Id = 32U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS;
			Int_Id = Int_Id+16U) {
		/*
		 * 每个 INT_ID 使用两位,每个寄存器控制 16 个 INT_ID
		 * 将它们全部设置为电平敏感、高有效(写入 0 代表此配置)
		 */
		XScuGic_DistWriteReg(InstancePtr,
					XSCUGIC_INT_CFG_OFFSET_CALC(Int_Id),
					0U);
	}


#define DEFAULT_PRIORITY    0xa0a0a0a0U   // 默认优先级(每个字节 0xA0)
	for (Int_Id = 0U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS;
			Int_Id = Int_Id+4U) {
		/*
		 * 2. 使用优先级寄存器设置优先级
		 * priority_level 和 spi_target 寄存器每个 INT_ID 占用一个字节
		 * 写入一个默认值,后续可以修改
		 */
		XScuGic_DistWriteReg(InstancePtr,
					XSCUGIC_PRIORITY_OFFSET_CALC(Int_Id),
					DEFAULT_PRIORITY);
	}

#if defined (GICv3)   // GICv3 还需要配置安全域
	for (Int_Id = 0U; Int_Id<XSCUGIC_MAX_NUM_INTR_INPUTS;Int_Id=Int_Id+32U) {
		// 为所有中断设置安全目标寄存器为默认值(全安全)
		XScuGic_DistWriteReg(InstancePtr,
				XSCUGIC_SECURITY_TARGET_OFFSET_CALC(Int_Id),
				XSCUGIC_DEFAULT_SECURITY);
	}
	/*
	 * 为 SGI/PPI 设置安全域
	 */
	XScuGic_ReDistSGIPPIWriteReg(InstancePtr,XSCUGIC_RDIST_IGROUPR_OFFSET,
									XSCUGIC_DEFAULT_SECURITY);
#endif
	// 3. 禁用所有中断(此时还未使能分发器,但先写入禁用状态)
	for (Int_Id = 0U; Int_Id<XSCUGIC_MAX_NUM_INTR_INPUTS;Int_Id=Int_Id+32U) {
		/*
		 * 4. 使用 enable_set 寄存器来禁用 SPI。暂时保持所有中断禁用。
		 * 注意:这里使用的是禁用寄存器(DISABLE_OFFSET),写入全 1 表示禁用
		 */
		XScuGic_DistWriteReg(InstancePtr,
		XSCUGIC_EN_DIS_OFFSET_CALC(XSCUGIC_DISABLE_OFFSET, Int_Id),
			0xFFFFFFFFU);   // 禁用这 32 个中断
	}
#if defined (GICv3)   // GICv3 最后使能分发器并开启中断组
	Temp = XScuGic_DistReadReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET); // 读取当前值
	Temp |= XSCUGIC_EN_INT_MASK;   // 设置使能位
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET, Temp); // 使能分发器
	XScuGic_Enable_Group1_Interrupts();   // 使能 Group1 中断(非安全)
	XScuGic_Enable_Group0_Interrupts();    // 使能 Group0 中断(安全)
#else   // 非 GICv3 直接使能分发器
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_DIST_EN_OFFSET,
					XSCUGIC_EN_INT_MASK);   // 设置使能位
#endif
}

3>CPUInitialize函数

c 复制代码
#if !defined (GICv3)   // 仅当未定义 GICv3(即使用 GICv2 或更早版本)时编译此函数
/*****************************************************************************/
/**
*
* CPUInitialize 初始化 GIC 的 CPU 接口。初始化包括:
*
*	- 设置 CPU 的中断优先级阈值
*	- 使能 CPU 接口
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
*
* @return	无
*
* @note		无。
*
******************************************************************************/
static void CPUInitialize(XScuGic *InstancePtr)
{
	/*
	 * 使用优先级掩码寄存器设置 CPU 的优先级掩码
	 */
	XScuGic_CPUWriteReg(InstancePtr, XSCUGIC_CPU_PRIOR_OFFSET, 0xF0U);
	// 写入 0xF0:只允许优先级高于或等于 0xF0(即数值小于等于 0xF0)的中断被响应


	/*
	 * 如果 CPU 同时在两个安全域中运行,则通过 control_s 寄存器设置参数。
	 * 1. 设置 FIQen=1,使 FIQ 用于安全中断,
	 * 2. 设置 AckCtl 位
	 * 3. 设置 SBPR 位以选择二进制指针行为
	 * 4. 设置 EnableS = 1 以使能安全中断
	 * 5. 设置 EnableNS = 1 以使能非安全中断
	 */

	/*
	 * 如果 CPU 仅在安全域中运行,则设置 control_s 寄存器。
	 * 1. 设置 FIQen=1,
	 * 2. 设置 EnableS=1,使 CPU 接口能够发出安全中断信号。
	 *    除非需要安全中断,否则仅使能 IRQ 输出。
	 */
	XScuGic_CPUWriteReg(InstancePtr, XSCUGIC_CONTROL_OFFSET, 0x07U);
	// 写入 0x07(二进制 111):使能安全中断、使能非安全中断、允许安全中断使用 FIQ

}
#endif

9. XScuGic_Connect函数

  • 功能简介:XScuGic_Connect用于将指定的中断源(Int_Id)与用户定义的中断处理函数(Handler)绑定。先通过断言检查输入参数的有效性,然后将处理函数和回调引用存入实例的 HandlerTable 表中对应中断号的位置,最后返回成功状态。
  • 函数主体
c 复制代码
/*****************************************************************************/
/**
*
* 建立中断源的中断 ID 与识别到中断时要运行的处理函数之间的连接。
* 调用中提供的 CallbackRef 参数将作为处理函数被调用时的参数。
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
* @param	Int_Id 包含中断源的 ID,取值范围为 0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1。
* @param	Handler 是该中断的处理函数。
* @param	CallBackRef 是回调引用,通常是连接驱动的实例指针。
*
* @return
*		- XST_SUCCESS:处理函数连接成功。
*
* @note
*
* 警告:传入的处理函数将覆盖之前任何已连接的处理函数。
*
****************************************************************************/
s32  XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
				Xil_InterruptHandler Handler, void *CallBackRef)
{
	/*
	 * 断言参数
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);                     // 确保实例指针非空
	Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);    // 确保中断 ID 有效
	Xil_AssertNonvoid(Handler != NULL);                         // 确保处理函数指针非空
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 确保实例已初始化

	/*
	 * 使用 Int_Id 作为表中的索引,以选择正确的处理函数
	 */
	InstancePtr->Config->HandlerTable[Int_Id].Handler = (Xil_InterruptHandler)Handler; // 存入处理函数
	InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef;               // 存入回调引用

	return XST_SUCCESS;   // 返回成功状态
}
  • 程序分析
    指向 GIC 实例的指针、中断 ID、中断处理函数指针以及回调引用。首先连续调用 Xil_AssertNonvoid 进行断言校验:确保实例指针非空、中断 ID 不超过最大中断数、处理函数指针非空、以及实例已经初始化就绪(IsReady 标志为真)------这些断言在调试阶段能快速捕获参数错误。接着,将传入的 Handler 强制转换为 Xil_InterruptHandler 类型后存入 InstancePtr->Config->HandlerTable[Int_Id].Handler 中,同时将 CallBackRef 存入同一表项的 CallBackRef 字段。HandlerTable 是配置结构体中的函数指针数组,每个中断号对应一个表项。最后返回 XST_SUCCESS 表示连接成功。该函数不涉及硬件寄存器操作,仅维护软件层面的中断映射表,后续当中断发生时,GIC 驱动会从该表中取出对应的处理函数并执行。当中断实际发生时,GIC 驱动会调用注册的 Handler,并且会把之前保存的 CallBackRef 作为参数传回给处理函数
    10. XGpio_InterruptEnable函数
  • 功能简介:XGpio_InterruptEnable函数用于使能 XGpio 实例中指定通道的中断。先通过断言检查实例指针、实例就绪状态以及硬件是否支持中断,然后读取中断使能寄存器(IER),将传入的掩码与当前值按位或后写回寄存器,从而只使能指定的中断位而不影响其他位。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
* 使能中断。要真正产生中断,还必须调用 XGpio_InterruptGlobalEnable() 
* 来使能全局中断。如果硬件设备在构建时未包含中断能力,此函数将触发断言。
*
* @param	InstancePtr 是要操作的 GPIO 实例。
* @param	Mask 是要使能的中断掩码。位位置为 1 的位将被使能。
*		此掩码由 xgpio_l.h 中的 XGPIO_IR* 位通过 OR 运算形成。
*
* @return	无。
*
* @note		无。
*
*****************************************************************************/
void XGpio_InterruptEnable(XGpio *InstancePtr, u32 Mask)
{
	u32 Register;   // 用于暂存读取到的中断使能寄存器值

	Xil_AssertVoid(InstancePtr != NULL);                      // 断言:实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 断言:实例已初始化就绪
	Xil_AssertVoid(InstancePtr->InterruptPresent == TRUE);    // 断言:硬件支持中断功能

	/*
	 * 读取中断使能寄存器,仅使能指定的中断,
	 * 不禁用也不使能其他中断。
	 */

	Register = XGpio_ReadReg(InstancePtr->BaseAddress, XGPIO_IER_OFFSET); // 读取当前 IER 值
	XGpio_WriteReg(InstancePtr->BaseAddress, XGPIO_IER_OFFSET,
			Register | Mask); // 将 Mask 中为 1 的位写入 IER,其他位保持不变
}
  • 功能分析:XGpio_InterruptEnable接收两个参数:指向 XGpio 实例的指针 InstancePtr 和中断使能掩码 Mask。首先连续调用 Xil_AssertVoid 进行断言检查:第一句确保实例指针非空,第二句确保实例已经初始化就绪(IsReady 标志为真),第三句确保硬件设备确实支持中断功能(InterruptPresent 为 TRUE)------若设备在构建时未包含中断能力,则此断言会触发,避免无效操作。接着,通过 XGpio_ReadReg 读取当前的中断使能寄存器值,该寄存器的偏移量为 XGPIO_IER_OFFSET。然后将读取到的值与传入的 Mask 进行按位或运算,目的是将 Mask 中为 1 的位设置为 1(即使能对应的中断),而保持其他位原有的状态不变。最后,调用 XGpio_WriteReg 将运算结果写回中断使能寄存器,完成中断使能操作。这个函数的操作方式和很多函数都很像,遵循了"读-改-写"的典型寄存器操作模式,确保只修改指定的中断位,不影响同寄存器中其他中断的使能状态。
    11.XGpio_InterruptGlobalEnable函数
  • 功能简介:XGpio_InterruptGlobalEnable函数用于使能 XGpio 实例的全局中断输出信号。通过断言检查实例有效性和中断能力,然后向全局中断使能寄存器(GIE)写入使能掩码,允许已使能的通道中断传递到中断控制器。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
* 使能中断输出信号。通过 XGpio_InterruptEnable() 使能的中断,
* 必须在此函数设置全局使能位后才能传递出去。
* 此函数旨在方便地使能所有中断(两个通道),用于退出临界区。
* 如果硬件设备在构建时未包含中断能力,此函数将触发断言。
*
* @param	InstancePtr 是要操作的 GPIO 实例。
*
* @return	无。
*
* @note		无。
*
*****************************************************************************/
void XGpio_InterruptGlobalEnable(XGpio *InstancePtr)
{
	Xil_AssertVoid(InstancePtr != NULL);                      // 断言:实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 断言:实例已初始化就绪
	Xil_AssertVoid(InstancePtr->InterruptPresent == TRUE);    // 断言:硬件支持中断功能

	// 向全局中断使能寄存器写入使能掩码,打开中断总开关
	XGpio_WriteReg(InstancePtr->BaseAddress, XGPIO_GIE_OFFSET,
			XGPIO_GIE_GINTR_ENABLE_MASK);
}
  • 程序分析:函数首先使用三个 Xil_AssertVoid 断言进行参数校验:第一句确保实例指针非空,第二句确保实例已经初始化就绪,第三句确保硬件设备在构建时包含了中断功能(InterruptPresent 为 TRUE),否则触发断言防止无效操作。接着,调用 XGpio_WriteReg 向全局中断使能寄存器(偏移 XGPIO_GIE_OFFSET)写入预定义的使能掩码 XGPIO_GIE_GINTR_ENABLE_MASK。该寄存器控制整个 GPIO 模块的中断输出总开关,只有该位使能后,之前通过 XGpio_InterruptEnable 设置的各通道中断才能实际向 CPU 发出信号。函数不读取当前值,直接写入使能掩码,因为该寄存器通常只包含一个全局使能位,直接写 1 即可打开,无需读‑改‑写。这种设计允许快速退出临界区时批量恢复中断。偏移地址XGPIO_GIE_OFFSET = 0x11C,XGPIO_GIE_GINTR_ENABLE_MASK = 0x80000000。查看AXI GPIO 手册DS744可知寄存器地址和位定义如下图,和程序是一一对应的。

  • 辅助说明

    AXI GPIO IP 寄存器地址与位定义


    12. XScuGic_Enable函数

  • 功能简介:XScuGic_Enable函数用于使能指定的中断源(由 Int_Id 标识),并将该中断映射到当前 CPU 核心。先进行参数断言,然后在 GICv3 下对 SGI/PPI 进行特殊处理(映射到当前 CPU 并操作重分配器使能寄存器),最后通过分发器的使能设置寄存器将对应中断位置 1。

  • 函数主体

c 复制代码
/*****************************************************************************/
/**
*
* 使能作为参数 Int_Id 提供的中断源。调用此函数后,指定 Int_Id 的任何挂起中断条件都将触发。
* 此 API 还会将中断映射到请求的 CPU。
*
* @param	InstancePtr 是指向 XScuGic 实例的指针。
* @param	Int_Id 包含中断源的 ID,取值范围为 0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1
*
* @return	无。
*
* @note		无。
*
****************************************************************************/
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
{
	u32 Mask;               // 用于构造使能掩码
	u8 Cpu_Id = (u8)CpuId;  // 获取当前 CPU ID

#if defined (GICv3)
	u32 Temp;               // 临时变量,用于读取和修改重分配器寄存器
#endif
	/*
	 * 断言参数
	 */
	Xil_AssertVoid(InstancePtr != NULL);                           // 实例指针非空
	Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);          // 中断 ID 有效
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 实例已初始化

#if defined (GICv3)
	if (Int_Id < XSCUGIC_SPI_INT_ID_START) {   // 如果是 SGI 或 PPI(ID < 32)
		// 将该中断映射到当前 CPU
		XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id);

		Int_Id &= 0x1f;                        // 取低 5 位,得到中断在寄存器中的位号(0~31)
		Int_Id = 1 << Int_Id;                  // 转换为位掩码

		// 读取重分配器中的使能寄存器
		Temp = XScuGic_ReDistSGIPPIReadReg(InstancePtr, XSCUGIC_RDIST_ISENABLE_OFFSET);
		Temp |= Int_Id;                        // 设置对应位
		// 写回重分配器使能寄存器
		XScuGic_ReDistSGIPPIWriteReg(InstancePtr, XSCUGIC_RDIST_ISENABLE_OFFSET, Temp);
	}
#endif
	// 将中断映射到当前 CPU(对 SPI 起主要作用)
	XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id);
	/*
	 * 使用 Int_Id 为目标位位置创建适当的掩码
	 */
	Mask = 0x00000001U << (Int_Id % 32U);    // 每个使能寄存器控制 32 个中断,计算位掩码
	/*
	 * 通过设置使能设置寄存器中的相应位来使能选定的中断源
	 */
	XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
				((Int_Id / 32U) * 4U), Mask);   // 写入分发器使能设置寄存器
}
  • 函数分析:函数首先通过断言检查实例指针非空、中断 ID 有效以及实例已就绪。调用 XScuGic_InterruptMaptoCpu(对 SPI 起主要作用)确保中断目标 CPU 为当前核。然后构造掩码:Mask = 1 << (Int_Id % 32),因为每个使能寄存器控制 32 个中断。最后计算使能设置寄存器的地址偏移(基地址 + 中断号/32 * 4),写入掩码,使分发器中的对应中断使能位变为 1,从而允许该中断被转发到 CPU 接口。
  • 辅助说明
    1>XScuGic_InterruptMaptoCpu函数操作的寄存器

2>

13. XScuGic_SetPriorityTriggerType函数

  • 函数功能:XScuGic_SetPriorityTriggerType函数用于设置指定中断源(Int_Id)的优先级和触发类型。先通过断言检查参数有效性,然后在 GICv3 下对 SGI/PPI 进行单独处理(写入重分配器的优先级和配置寄存器),对于 SPI 中断,则通过读‑改‑写方式分别更新分发器中的优先级寄存器和中断配置寄存器。
  • 函数主体
c 复制代码
/****************************************************************************/
/**
* 为指定的 IRQ 源设置中断优先级和触发类型。
*
* @param	InstancePtr 是指向要操作的实例的指针。
* @param	Int_Id 是要修改的 IRQ 源编号。
* @param	Priority 是 IRQ 源的新优先级。0 为最高优先级,0xF8(248) 为最低。
*           支持 32 个优先级级别,步进为 8。因此支持的优先级为:
*           0, 8, 16, 24, 32, 40, ..., 248。
* @param	Trigger 是 IRQ 源的新触发类型。
*           每对位描述一个 INT_ID 的配置:
*           SFI    只读,始终为 b10
*           PPI    只读,取决于 PPI 的配置方式:
*                  b01 高电平敏感
*                  b11 上升沿敏感
*           SPI    LSB 为只读:
*                  b01 高电平敏感
*                  b11 上升沿敏感
*
* @return	无。
*
* @note		无。
*
****************************************************************************/
void XScuGic_SetPriorityTriggerType(XScuGic *InstancePtr, u32 Int_Id,
					u8 Priority, u8 Trigger)
{
	u32 RegValue;               // 用于暂存读取的寄存器值
#if defined (GICv3)
	u32 Temp;                   // GICv3 下用于临时操作
	u32 Index;                  // 触发类型在配置寄存器中的位偏移
#endif
	u8 LocalPriority;           // 优先级本地副本
	LocalPriority = Priority;   // 复制优先级值

	// 断言:参数有效性检查
	Xil_AssertVoid(InstancePtr != NULL);                           // 实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 实例已就绪
	Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);          // 中断 ID 有效
	Xil_AssertVoid(Trigger <= (u8)XSCUGIC_INT_CFG_MASK);           // 触发类型合法
	Xil_AssertVoid(LocalPriority <= (u8)XSCUGIC_MAX_INTR_PRIO_VAL); // 优先级合法

#if defined (GICv3)//zynq不涉及此部分
	if (Int_Id < XSCUGIC_SPI_INT_ID_START )   // 如果是 SGI 或 PPI(ID < 32)
	{
		// 写入重分配器的优先级寄存器
		XScuGic_ReDistSGIPPIWriteReg(InstancePtr, XSCUGIC_RDIST_INT_PRIORITY_OFFSET_CALC(Int_Id), Priority);
		// 读取重分配器的中断配置寄存器
		Temp = XScuGic_ReDistSGIPPIReadReg(InstancePtr, XSCUGIC_RDIST_INT_CONFIG_OFFSET_CALC(Int_Id));
		// 获取该中断在配置寄存器中的位偏移
		Index = XScuGic_Get_Rdist_Int_Trigger_Index(Int_Id);
		// 将触发类型写入对应位
		Temp |= (Trigger << Index);
		// 写回配置寄存器
		XScuGic_ReDistSGIPPIWriteReg(InstancePtr, XSCUGIC_RDIST_INT_CONFIG_OFFSET_CALC(Int_Id), Temp);
		return;   // SGI/PPI 处理完毕,直接返回
	}
#endif

	/*
	 * 使用 Int_Id 确定要写入的优先级寄存器
	 */
	RegValue = XScuGic_DistReadReg(InstancePtr,
			XSCUGIC_PRIORITY_OFFSET_CALC(Int_Id));

	/*
	 * 优先级位位于 GIC 优先级寄存器的第 7 到第 3 位。
	 * 这意味着支持的优先级级别为 32,步进为 8。
	 * 优先级可以是 0, 8, 16, 24, 32, 48, ... 等。
	 * 写入寄存器前,屏蔽低 3 位。
	 */
	LocalPriority = LocalPriority & (u8)XSCUGIC_INTR_PRIO_MASK;   // 保留高 5 位,低 3 位清零
	/*
	 * 在寄存器中移位并屏蔽优先级和触发类型的正确位
	 */
	RegValue &= ~(XSCUGIC_PRIORITY_MASK << ((Int_Id%4U)*8U));      // 清除该中断的优先级字节
	RegValue |= (u32)LocalPriority << ((Int_Id%4U)*8U);           // 写入新优先级

	/*
	 * 将值写回寄存器
	 */
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_PRIORITY_OFFSET_CALC(Int_Id),
				RegValue);

	/*
	 * 使用 Int_Id 确定要写入的中断配置寄存器
	 */
	RegValue = XScuGic_DistReadReg(InstancePtr,
			XSCUGIC_INT_CFG_OFFSET_CALC(Int_Id));

	/*
	 * 在寄存器中移位并屏蔽优先级和触发类型的正确位
	 */
	RegValue &= ~(XSCUGIC_INT_CFG_MASK << ((Int_Id%16U)*2U));      // 清除该中断的 2 位触发类型字段
	RegValue |= (u32)Trigger << ((Int_Id%16U)*2U);                // 写入新触发类型

	/*
	 * 将值写回寄存器
	 */
	XScuGic_DistWriteReg(InstancePtr, XSCUGIC_INT_CFG_OFFSET_CALC(Int_Id),
				RegValue);
}
  • 功能分析: 函数接收四个参数:GIC 实例指针、中断 ID、优先级值(0 最高,0xF8 最低,步进为 8)和触发类型(电平敏感或边沿触发)。首先将优先级保存到局部变量 LocalPriority,然后通过一系列断言确保实例非空、实例已就绪、中断 ID 有效、触发类型合法(不超过 XSCUGIC_INT_CFG_MASK)以及优先级值合法(不超过 XSCUGIC_MAX_INTR_PRIO_VAL)。在 GICv3 下的程序不做分析。对于 SPI 中断(以及非 GICv3 下的所有中断),首先读取分发器中对应中断的优先级寄存器(每个寄存器控制 4 个中断,每个中断占用 1 字节),将 LocalPriority 与 XSCUGIC_INTR_PRIO_MASK 相与以屏蔽低 3 位(因为 GIC 优先级只使用高 5 位,步进为 8),然后清除该中断在寄存器中的原优先级位,再将新优先级写入对应字节位置,最后写回寄存器。接着读取分发器中的中断配置寄存器(每个寄存器控制 16 个中断,每个中断占用 2 位),清除该中断对应的 2 位触发类型字段,再将传入的 Trigger 值写入该字段,最后写回寄存器,完成配置。可以看出经典的写程序套路:断言->读出->修改值->写回
    13. Xil_ExceptionRegisterHandler函数
  • 函数功能:函数用于注册一个异常处理函数。当处理器遇到指定的异常时,该处理函数会被调用。在 Versal 平台且满足特定条件(Cortex-A72、EL3、非 R5)时,强制将异常 ID 改为 FIQ 中断;然后将处理函数和传入的数据分别存入全局异常向量表 XExc_VectorTable 的对应表项中。
  • 函数主体
c 复制代码
/*****************************************************************************/
/**
*@brief     建立异常源 ID 与识别到异常时要运行的处理函数之间的连接。
*           调用中作为 DataPtr 提供的参数将用作处理函数被调用时的参数。
*
* @param    Id: 包含异常源的 32 位 ID,应为 XIL_EXCEPTION_INT 
*           或介于 0 到 XIL_EXCEPTION_LAST 之间的值。
*           详见 xil_mach_exception.h。
* @param    Handler: 要注册的异常处理函数
* @param    Data: 一个数据引用,当处理函数被调用时该数据会被传递给它。
*
****************************************************************************/
void Xil_ExceptionRegisterHandler(u32 Id, Xil_ExceptionHandler Handler,
				  void *Data)
{
	if (Id == XIL_EXCEPTION_ID_INT) {          // 如果是中断异常
		// 将处理函数存入中断向量表(MicroBlaze 只有 1 个中断表项)
		MB_InterruptVectorTable[0].Handler = Handler;
		// 将数据指针存入中断向量表
		MB_InterruptVectorTable[0].CallBackRef = Data;
	}
	else {                                      // 非中断异常
#ifdef MICROBLAZE_EXCEPTIONS_ENABLED           // 如果使能了异常功能
		// 将处理函数存入异常向量表(按异常 ID 索引)
		MB_ExceptionVectorTable[Id].Handler = Handler;
		// 将数据指针存入异常向量表
		MB_ExceptionVectorTable[Id].CallBackRef = Data;
#endif
		// 若未使能异常功能,则什么都不做
	}
}
  • 功能分析
    函数接收三个参数:异常 ID、异常处理函数指针、以及一个将被传递给处理函数的通用数据指针。首先通过条件编译判断当前是否为 Versal 平台、非 ARM R5 核心、且处于 EL3 异常级别。如果条件满足,则注释说明:Versal 中的 Cortex-A72 处理器与 GIC-500 配合,而 GIC-500 在 EL3 下仅支持 FIQ,因此将传入的 Exception_id 强制覆盖为 XIL_EXCEPTION_ID_FIQ_INT,忽略用户传入的异常 ID。这样做的目的是适配硬件限制,确保在 EL3 下只使用 FIQ 来处理中断。接着,将 Handler 存入全局异常向量 XExc_VectorTable[Exception_id].Handler 中,同时将 Data 存入同一表项的 .Data 字段。该向量表在系统启动时初始化,后续当指定异常发生时,异常处理流程会从该表中取出对应的处理函数并调用,同时传入保存的 Data 作为参数。函数没有返回值,不进行参数校验(假设调用者保证合法性),实现非常简洁。
相关推荐
楼田莉子2 小时前
仿muduo的高并发服务器——前置知识讲解和时间轮模块
服务器·开发语言·c++·后端·学习
小夏子_riotous2 小时前
Docker学习路径——5、容器数据卷
linux·运维·服务器·学习·docker·容器·云计算
qeen872 小时前
【数据结构】队列及其C语言模拟实现
c语言·数据结构·c++·学习·队列
苦 涩3 小时前
考研408笔记之计算机网络(六)——应用层
笔记·计算机网络·考研408
MY_TEUCK11 小时前
Sealos 平台部署实战指南:结合 Cursor 与版本发布流程
java·人工智能·学习·aigc
handler0112 小时前
Linux: 基本指令知识点(2)
linux·服务器·c语言·c++·笔记·学习
炽烈小老头13 小时前
【每天学习一点算法 2026/04/20】除自身以外数组的乘积
学习·算法
破浪前行·吴14 小时前
数据结构概述
数据结构·学习
.千余16 小时前
【Linux】基本指令3
linux·服务器·开发语言·学习