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_LookupConfig和XGpio_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 作为参数。函数没有返回值,不进行参数校验(假设调用者保证合法性),实现非常简洁。