0 说明
基于 SRIO Programming and Performance Data on Keystone DSP 对其进行中文翻译及自己的说明。
这份文件是关于Keystone DSP上的Serial RapidIO (SRIO)编程和性能数据的应用报告。报告详细介绍了SRIO在Keystone DSP上的配置、编程、性能测试以及一些编程技巧。
主要内容包括:
-
SRIO配置:介绍了如何配置SRIO端口模式(1x/2x/4x)、设备ID、Load Store Unit (LSU)、消息和数据包DMA、中断设置、回环模式以及数据包转发。
-
SRIO传输编程:讨论了如何使用LSU进行数据传输,包括直接I/O、门铃和维护传输。提供了基于优先级的RX缓冲区分配和SRIO寄存器检查的示例代码。
-
其他SRIO编程考虑:包括匹配ACKID、软重置以及一些编程技巧和提示。
-
SRIO性能数据:提供了在不同操作条件下测量的性能数据,包括传输开销、不同类型数据包的吞吐量、不同链路配置下的吞吐量以及不同内存缓冲对SRIO吞吐量的影响。
-
附录A:提供了一个示例项目的介绍,该项目包含了用于配置Keystone设备外设的通用配置文件,以及用于不同测试案例的源代码。
下面开始对该文件进行详细分析
1 引言
RapidIO 是一种非专有的、高带宽的系统级互连。它是一种包交换互连,主要用作芯片间和板卡间的内部系统接口,性能水平达到每秒千兆字节。Keystone DSP 上的 SRIO(串行 RapidIO)非常灵活且功能强大;它为客户提供了许多模式和选项,SRIO 用户指南详细介绍了这些内容。本应用笔记提供了一些关于 SRIO 编程的补充信息。示例代码/项目随本应用说明一起提供。关于 SRIO 编程的示例代码基于寄存器层 CSL(芯片支持层)。大部分代码使用如下定义的 SRIO 寄存器指针:
#include <ti/csl/cslr_srio.h>
#include <ti/csl/cslr_device.h>
CSL_SrioRegs * srioRegs = (CSL_SrioRegs *)CSL_SRIO_CONFIG_REGS;
2 SRIO配置
2.1 1x/2x/4x配置
KeyStone Family SRIO端口配置使用PLM端口路径控制寄存器来配置SRIO端口模式,PATH_CONFIG字段用于配置使用多少信道,PATH_MODE字段用于选择1x、2x或4x端口的组合。下图展示了端口配置的详细描述。
下面是配置它的示例代码:
// 定义一个枚举类型,用于配置SRIO的1x、2x或4x路径模式
typedef enum
{
// 1xLaneA模式:1条Lane A,位移操作设置路径配置和模式
SRIO_PATH_CTL_1xLaneA =
(1 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_CONFIGURATION_SHIFT) |
(0 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_MODE_SHIFT),
// 1xLaneA_1xLaneB模式:1条Lane A和1条Lane B
SRIO_PATH_CTL_1xLaneA_1xLaneB =
(2 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_CONFIGURATION_SHIFT) |
(0 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_MODE_SHIFT),
// 2xLaneAB模式:2条Lane AB
SRIO_PATH_CTL_2xLaneAB =
(2 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_CONFIGURATION_SHIFT) |
(1 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_MODE_SHIFT),
// 更多模式,每种模式都通过位移操作设置路径配置和模式
// ...(此处省略以简化)
// 4xLaneABCD模式:4条Lane ABCD
SRIO_PATH_CTL_4xLaneABCD =
(4 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_CONFIGURATION_SHIFT) |
(4 << CSL_SRIO_RIO_PLM_SP_PATH_CTL_PATH_MODE_SHIFT)
} SRIO_1x2x4x_Path_Control;
// 配置SRIO 1x 2x或4x路径模式的函数
void Keystone_SRIO_set_1x2x4x_Path(SRIO_1x2x4x_Path_Control srio_1x2x4x_path_control)
{
// 这个寄存器是一个全局寄存器,即使它可以从任何一个端口访问。
// 因此,不需要从每个端口编程,它基本上是一个单一的寄存器。
srioRegs->RIO_PLM[0].RIO_PLM_SP_PATH_CTL =
(srioRegs->RIO_PLM[0].RIO_PLM_SP_PATH_CTL & (~SRIO_1x2x4x_PATH_CONTROL_MASK)) |
srio_1x2x4x_path_control;
}
// ...(此处省略)
// 调用函数,设置SRIO为1xLaneA模式
Keystone_SRIO_set_1x2x4x_Path(SRIO_PATH_CTL_1xLaneA);
在这段代码中,Keystone_SRIO_set_1x2x4x_Path 函数用于设置SRIO的路径模式。通过调用这个函数并传递一个枚举值作为参数,可以配置SRIO使用不同的路径配置和模式。例如,最后一行代码调用这个函数并设置SRIO为1xLaneA模式。
在SRIO(Serial RapidIO)的配置中,端口根据所使用的“最低”通道(Lane)进行编号。这里的“最低”通道指的是在多通道配置中,端口所使用的通道中编号最小的一个。根据这个规则,端口的编号和其可能的配置如下:
如果一个端口使用通道A,那么该端口的编号是0,并且该端口可能是1x、2x或4x配置。
如果最低的通道是B,那么端口的编号是1,并且该端口必须是1x配置。
如果最低的通道是C,那么端口的编号是2,并且该端口可能是1x或2x配置。
如果最低的通道是D,那么端口的编号是3,并且该端口必须是1x配置。
以下是一些不同配置下的端口编号示例:
对于1xLaneA配置,只有一个端口,编号为0。
对于1xLaneA_1xLaneB配置,有两个端口,编号分别为0和1。
对于2xLaneAB配置,只有一个端口,编号为0。
对于1xLaneA_1xLaneB_1xLaneC_1xLaneD配置,有四个端口,编号分别为0、1、2和3。
对于2xLaneAB_1xLaneC_1xLaneD配置,有三个端口,编号分别为0、1和2。
对于1xLaneA_1xLaneB_2xLaneCD配置,有三个端口,编号分别为0、1和2。
对于2xLaneAB_2xLaneCD配置,有两个端口,编号分别为0和2。
对于4xLaneABCD配置,只有一个端口,编号为0。
这些配置和编号规则有助于在编程时正确地设置和管理SRIO端口和通道。
在SRIO配置完成后,我们必须轮询SP_ERR_STATUS寄存器来检查端口状态是否为PORT_OK。
2.2 设备ID配置
KeyStone系列的SRIO支持16个本地设备ID。
下面是配置设备id的示例代码
// 定义一个结构体,用于配置SRIO的设备ID路由
typedef struct {
/* PATTERN提供与传入目标ID一对一比较的16位模式 */
Uint16 idPattern;
/* MATCH指示目标ID的哪16位与PATTERN进行比较 */
Uint16 idMatchMask;
/* 将与此BRR匹配的目标ID的维护请求/保留数据包路由到LLM */
Uint8 routeMaintenance;
} SRIO_Device_ID_Routing_Config;
// 设置SRIO设备ID的函数
void Keystone_SRIO_set_device_ID(
SRIO_Device_ID_Routing_Config *device_id_routing_config,
Uint32 uiDeviceIdNum)
{
int i;
// TLM_SP(n)_BRR_x_PATTERN_MATCH寄存器持有15个允许的目标ID,
// 注意第一个寄存器不被使用。我们使用RIO_BASE_ID寄存器来保存第一个ID
srioRegs->RIO_BASE_ID = device_id_routing_config[0].idPattern | // 大ID
((device_id_routing_config[0].idPattern & 0xFF) << 16); // 小ID
uiDeviceIdNum = _min2(SRIO_MAX_DEVICEID_NUM, uiDeviceIdNum);
for (i = 1; i < uiDeviceIdNum; i++)
{
// 请注意,必须启用SRIO块5~8,以使相应的RIO_TLM[0:3]生效
srioRegs->RIO_TLM[i/4].brr[i&3].RIO_TLM_SP_BRR_CTL =
(device_id_routing_config[i].routeMaintenance <<
CSL_SRIO_RIO_TLM_SP_BRR_1_CTL_ROUTE_MR_TO_LLM_SHIFT) |
(0 << CSL_SRIO_RIO_TLM_SP_BRR_1_CTL_PRIVATE_SHIFT) |
(1 << CSL_SRIO_RIO_TLM_SP_BRR_1_CTL_ENABLE_SHIFT);
srioRegs->RIO_TLM[i/4].brr[i&3].RIO_TLM_SP_BRR_PATTERN_MATCH =
(device_id_routing_config[i].idPattern <<
CSL_SRIO_RIO_TLM_SP_BRR_1_PATTERN_MATCH_PATTERN_SHIFT) |
(device_id_routing_config[i].idMatchMask <<
CSL_SRIO_RIO_TLM_SP_BRR_1_PATTERN_MATCH_MATCH_SHIFT);
}
}
// 这里可以设置最多16个设备ID
SRIO_Device_ID_Routing_Config dsp0_device_ID_routing_config[] =
{
// idPattern idMatchMask routeMaintenance
{DSP0_SRIO_BASE_ID + 0, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 1, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 2, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 3, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 4, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 5, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 6, 0xFFFF, 1},
{DSP0_SRIO_BASE_ID + 7, 0xFFFF, 1},
};
// ...
// 调用函数,配置设备ID
Keystone_SRIO_set_device_ID(&dsp0_device_ID_routing_config,
sizeof(dsp0_device_ID_routing_config) / sizeof(SRIO_Device_ID_Routing_Config));
这段代码定义了一个结构体 SRIO_Device_ID_Routing_Config,用于配置Serial RapidIO(SRIO)的设备ID路由。该结构体包含三个字段:idPattern 用于指定与传入目标ID一对一比较的16位模式,idMatchMask 用于指示目标ID的哪16位与idPattern进行比较,以及routeMaintenance 用于指示是否将匹配此路由寄存器的维护请求/保留数据包路由到LLM(逻辑链路管理器)。
Keystone_SRIO_set_device_ID 函数用于设置SRIO的设备ID。它接受一个指向SRIO_Device_ID_Routing_Config结构体数组的指针和一个表示设备ID数量的无符号整数。函数首先设置RIO_BASE_ID寄存器,然后遍历数组,为每个设备ID配置相应的路由寄存器。
在函数的最后,有一个示例数组dsp0_device_ID_routing_config,它定义了8个设备ID的路由配置。然后调用Keystone_SRIO_set_device_ID函数,将这个数组和设备ID的数量传递给它,以配置这些设备ID。
四个TLM端口基础路由寄存器集属于四个端口数据路径,因此必须为寄存器集启用相应的BLK5~BLK8。如果仅启用部分端口数据路径块,那么支持的dest ID数量将减少。例如,如果只启用BLK5(端口0数据路径),那么只能支持4个dest ID。再举一个例子,如果您使用配置0,模式1(1x模式),通常只需启用BLK5,但如果您想支持超过4个ID,则必须启用BLK6~BLK8。
在为一条端口数据路径配置的寄存器集中的ID,如果PRIVATE位=0,则可以用于其他端口。
PATTERN和MATCH字段是16位。在仅使用8位DeviceID的系统上,可以通过适当地设置MATCH字段来从比较中移除上八位。在混合使用8位和16位DeviceID的系统上,可以使用全部16位进行比较,在这种情况下,由硬件在比较前用八个零位前缀接收到的8位destID。
2.3 LSU(加载存储单元)设置
在KeyStone家族的SRIO中,Direct IO、门铃(doorbell)和维护(maintenance)传输是通过LSU(加载/存储单元)模块实现的。总共有8个LSU。每个LSU寄存器集代表一个传输请求,为了支持多个挂起的DirectIO传输请求,设有两组影子LSU寄存器,每组有16个影子寄存器集。影子寄存器组0由LSU0至LSU3共享,影子寄存器组1由LSU4至LSU7共享。
LSU_SETUP_REG0用于为每个LSU设置影子寄存器的数量。下表显示了影子寄存器的配置。
LSU_SETUP_REG1用于选择在LSU传输完成时设置的中断标志位。
如果LSU_SETUP_REG1中的LSU_EDMA位被清零为0,则使用传输的源ID映射索引来选择LSU中断状态寄存器0(LSU0_ICSR)中的一个标志位。源ID索引是TLM端口基路由寄存器(n)模式与匹配寄存器中16个设备ID的索引。例如,如果传输使用设备ID 1作为源ID,那么当传输完成时,LSU中断状态寄存器0(LSU0_ICSR)的第1位将被设置。
如果LSU_SETUP_REG1中的LSU_EDMA位被设置为1,则使用传输的LSU编号来选择LSU中断状态寄存器1中的一个标志位。例如,如果LSU编号为2,则LSU中断状态寄存器1(LSU1_ICSR)的第2位被设置。
请注意,只有当LSU被禁用而外设被启用时,LSU_SETUP_REG0和LSU_SETUP_REG1才是可编程的。如果SRIO初始化代码首先禁用SRIO,这些寄存器同样无法正确写入,以下代码显示了操作过程。
typedef enum
{
SRIO_LSU_SHADOW_REGS_SETUP_4_4_4_4, // SRIO LSU影子寄存器设置4_4_4_4
SRIO_LSU_SHADOW_REGS_SETUP_5_5_5_1, // SRIO LSU影子寄存器设置5_5_5_1
SRIO_LSU_SHADOW_REGS_SETUP_5_5_4_2, // SRIO LSU影子寄存器设置5_5_4_2
SRIO_LSU_SHADOW_REGS_SETUP_5_5_3_3, // SRIO LSU影子寄存器设置5_5_3_3
SRIO_LSU_SHADOW_REGS_SETUP_5_4_4_3, // SRIO LSU影子寄存器设置5_4_4_3
SRIO_LSU_SHADOW_REGS_SETUP_6_6_3_1, // SRIO LSU影子寄存器设置6_6_3_1
// ... 其他设置省略
SRIO_LSU_SHADOW_REGS_SETUP_9_3_2_2 // SRIO LSU影子寄存器设置9_3_2_2
} SRIO_LSU_Shadow_Registers_Setup; // SRIO LSU影子寄存器设置
typedef enum {
LSU_INT_DRIVE_BY_SRCID = 0, // LSU中断由源ID驱动,值为0
LSU_INT_DRIVE_BY_LSU_NUM // LSU中断由LSU编号驱动
} SRIO_LSU_Interrupt_setup; // SRIO LSU中断设置
typedef struct
{
SRIO_LSU_Shadow_Registers_Setup lsuGrp0ShadowRegsSetup; // LSU组0的影子寄存器设置
SRIO_LSU_Shadow_Registers_Setup lsuGrp1ShadowRegsSetup; // LSU组1的影子寄存器设置
SRIO_LSU_Interrupt_setup lsuIntSetup[8]; // 8个LSU中断设置
} SRIO_LSU_Cfg; // SRIO LSU配置
// ... 代码段省略
/* 全局启用SRIO中使用的块,包括MMR块 */
Keystone_SRIO_GlobalEnable();
/* LSU设置寄存器仅在LSU禁用且外设启用时可编程 */
if(srio_cfg->lsu_cfg)
{
/* 在LSU之间设置影子寄存器的分配 */
srioRegs->RIO_LSU_SETUP_REG0 =
(srio_cfg->lsu_cfg->lsuGrp0ShadowRegsSetup << CSL_SRIO_RIO_LSU_SETUP_REG0_SHADOW_GRP0_SHIFT) |
(srio_cfg->lsu_cfg->lsuGrp1ShadowRegsSetup << CSL_SRIO_RIO_LSU_SETUP_REG0_SHADOW_GRP1_SHIFT);
/* 根据LSU编号或源ID设置LSU中断 */
cfgValue = 0;
for(i=0; i<SRIO_MAX_LSU_NUM; i++)
{
cfgValue |= srio_cfg->lsu_cfg->lsuIntSetup[i] << i;
}
srioRegs->RIO_LSU_SETUP_REG1 = cfgValue;
}
/* 启用其他可选块 */
Keystone_SRIO_enable_blocks(&srio_cfg->blockEn);
这段代码是配置串行快速IO(SRIO)的负载存储单元(LSU)的设置。代码首先定义了两个枚举类型和一个结构体类型,然后进行了一些配置操作。
SRIO_LSU_Shadow_Registers_Setup 枚举类型定义了多种LSU影子寄存器的配置选项,每个选项代表不同的寄存器分配方式。
SRIO_LSU_Interrupt_setup 枚举类型定义了LSU中断的两种配置方式:基于源ID(LSU_INT_DRIVE_BY_SRCID)和基于LSU编号(LSU_INT_DRIVE_BY_LSU_NUM)。
SRIO_LSU_Cfg 结构体类型包含了LSU影子寄存器的配置和中断设置。
在代码的主要部分,首先调用了 Keystone_SRIO_GlobalEnable() 函数来全局启用SRIO,包括MMR块。然后,如果 srio_cfg->lsu_cfg 非空,代码将配置LSU影子寄存器的分配,并通过循环设置中断配置。最后,调用 Keystone_SRIO_enable_blocks() 函数启用其他可选的块。
2.4 消息和数据包DMA设置
在KeyStone家族的SRIO(Serial RapidIO)中,类型11(消息)和类型9(数据流)使用数据包DMA(Direct Memory Access)在DSP内部传输和接收数据。
在SRIO中,消息传输(Message TX)专门有16个队列,这些队列的编号从672到687。这些队列用于管理消息数据的传输,确保数据能够高效地在DSP和外部设备之间传输。
数据包DMA是一种硬件机制,它允许数据在不需要CPU直接介入的情况下,在内存和传输介质(如SRIO接口)之间移动。这种机制可以显著提高数据传输的效率,减少CPU的负载,从而提高系统的整体性能。在使用这些队列时,通常需要进行配置,包括队列的大小、传输模式、中断设置等。
在KeyStone家族的SRIO中,由于有16个发送队列(TX队列)和4个端口,我们需要将发送队列映射到端口,并为每个队列设置优先级。RIO_TX_QUEUE_SCH_INFO 寄存器用于配置TX队列的输出端口和CRF(Critical Request Flow)标志以确定优先级。PACKET_DMA_TX_CHANNEL_SCHEDULER_CONFIG_REG 寄存器用于配置类型11(消息)和类型9(数据流)的输出数据包优先级。以下代码展示了如何进行这些配置。
请注意,优先级不能设置为3,因为消息响应(PRIO)的优先级必须比发送消息的优先级高1,以防止死锁的发生。
// 定义一个结构体,用于存储SRIO传输队列的调度信息
typedef struct
{
Uint8 outputPort; // 输出端口
Uint8 priority; // 优先级
Uint8 CRF; // CRF,可能是某种标志或配置信息
} SRIO_TX_Queue_Sch_Info;
// 配置Keystone设备的SRIO传输队列
void Keystone_SRIO_TX_Queue_Cfg(
SRIO_TX_Queue_Sch_Info * TX_Queue_Sch_Info, // 指向传输队列调度信息的指针
Uint32 uiNumTxQueue) // 传输队列的数量
{
int i;
Uint32 uiMask, uiShift, uiRegIndex;
// 对于SRIO,优先级3是最高的,0是最低的
// 对于PktDMA通道,优先级0是最高的,3是最低的
Uint32 mapSrioPriToTxChPri[4]={3,2,1,0};
// 将传输队列的数量限制为最小值,以防止超出设备的最大通道数
uiNumTxQueue= _min2(SRIO_PKTDMA_MAX_CH_NUM, uiNumTxQueue);
// 遍历每个队列
for(i=0; i< uiNumTxQueue; i++)
{
// 计算寄存器索引、位移和掩码
uiRegIndex= i/4;
uiShift= (i&3)*8;
uiMask= 0xFF<<uiShift;
// 更新设备寄存器中的传输队列调度信息
srioRegs->RIO_TX_QUEUE_SCH_INFO[uiRegIndex] =
(srioRegs->RIO_TX_QUEUE_SCH_INFO[uiRegIndex]&(~uiMask)) // 清除字段
|((TX_Queue_Sch_Info[i].CRF
|(TX_Queue_Sch_Info[i].outputPort<<4))<<uiShift);
// PRIO字段在TX_QUEUE_SCH_INFOx中是只读的,
// 实际生效的优先级信息来自于PKTDMA TX通道
srioDmaTxChPriority[i] =
mapSrioPriToTxChPri[TX_Queue_Sch_Info[i].priority];
}
}
// 定义一个数组,包含多达16个传输队列的配置信息
SRIO_TX_Queue_Sch_Info TX_Queue_Sch_Info[]=
{
// 输出端口 优先级 CRF
{0, 0, 0},
{1, 0, 0},
{2, 0, 0},
{3, 0, 0},
{0, 0, 1},
{1, 0, 1},
{2, 0, 1},
{3, 0, 1},
{0, 1, 0},
{1, 1, 0},
{2, 1, 0},
{3, 1, 0},
{0, 1, 1},
{1, 1, 1},
{2, 1, 1},
{3, 1, 1}
};
// 应用传输队列的配置
Keystone_SRIO_TX_Queue_Cfg(&TX_Queue_Sch_Info,
sizeof(TX_Queue_Sch_Info)/sizeof(SRIO_TX_Queue_Sch_Info));
这段代码定义了一个结构体SRIO_TX_Queue_Sch_Info,用于存储SRIO发送队列的调度信息,包括输出端口、优先级和CRF标志。然后,它实现了一个函数Keystone_SRIO_TX_Queue_Cfg,用于配置SRIO的发送队列。
在Keystone_SRIO_TX_Queue_Cfg函数中,首先定义了一个数组mapSrioPriToTxChPri,用于将SRIO优先级映射到Packet DMA TX通道的优先级。在SRIO中,优先级3是最高的,而在Packet DMA通道中,优先级0是最高的,因此需要进行映射。
函数遍历传入的发送队列信息数组TX_Queue_Sch_Info,并根据数组中的信息配置每个队列。每个RIO_TX_QUEUE_SCH_INFO寄存器控制4个队列,因此需要计算寄存器索引uiRegIndex和位移uiShift。使用位掩码uiMask来清除相应的字段,然后设置输出端口和CRF标志。
由于TX_QUEUE_SCH_INFOx寄存器中的PRIO字段是只读的,实际的优先级信息来自Packet DMA TX通道。因此,代码设置了srioDmaTxChPriority[i]数组,其中包含了映射后的优先级。
在代码的最后部分,定义了一个TX_Queue_Sch_Info数组,其中包含了多达16个发送队列的配置信息。然后调用Keystone_SRIO_TX_Queue_Cfg函数,将这个数组传递给它,以便配置SRIO的发送队列。
下图显示了SRIO类型9和类型11数据包接收数据路径。
有20个接收流(Rx flows)。 在SRIO中的关键配置是根据SrcId(源ID)、DstId(目标ID)、Mailbox(邮箱)、Letter(信件)、StreamId(流ID)、COS(服务类别)等将Rx消息映射到一个流。
类型11使用RIO_RXU_MAP_L和RIO_RXU_MAP_H来配置根据邮箱、信件和设备ID的映射;类型9使用RIO_RXU_TYPE9_MAP0、RIO_RXU_TYPE9_MAP1和RIO_RXU_TYPE9_MAP2来配置根据COS、流ID和设备ID的映射。类型11和类型9共享相同的RIO_RXU_MAP_QID用于接收流和目标队列选择。请注意,当目标队列设置为0x1FFF时,RX目标队列将由RX流中的配置决定。以下是消息接收配置的示例代码。
/* 定义一个结构体,用于确定数据包被推送到哪个流和队列 */
typedef struct {
/* 将匹配的消息映射到流ID和可选的目的队列ID */
Uint8 flowId; // 流ID
Uint16 destQuID; // 目的队列ID
/* 通用字段匹配 */
Uint16 dstId; // 目的设备ID
Uint8 dstProm; // 目的设备优先级
Uint16 srcId; // 源设备ID
Uint8 srcProm; // 源设备优先级
Uint8 tt; // 传输类型
/* 类型11消息字段匹配 */
Uint8 mbx; // 消息邮箱
Uint8 mbxMask; // 消息邮箱掩码
Uint8 ltr; // 长途路由
Uint8 ltrMask; // 长途路由掩码
Uint8 segMap; // 段映射
/* 类型9消息字段匹配 */
Uint8 cos; // 服务类别
Uint8 cosMask; // 服务类别掩码
Uint16 streamId; // 流ID
Uint16 streamMask; // 流掩码
} SRIO_RX_Message_Map;
/* 配置消息与PacketDMA流和队列之间的映射 */
void Keystone_map_SRIO_RX_message(SRIO_RX_Message_Map * srio_message_map, Uint32 uiNumMessageMap) {
int i;
uiNumMessageMap = _min2(SRIO_MAX_MSG_MAP_ENTRY_NUM, uiNumMessageMap); // 限制映射条目的数量
for (i = 0; i < uiNumMessageMap; i++) {
// 配置接收单元映射寄存器低32位
srioRegs->RXU_MAP[i].RIO_RXU_MAP_L =
(srio_message_map[i].ltrMask << CSL_SRIO_RIO_RXU_MAP_L_LTR_MASK_SHIFT) |
(srio_message_map[i].mbxMask << CSL_SRIO_RIO_RXU_MAP_L_MBX_MASK_SHIFT) |
(srio_message_map[i].ltr << CSL_SRIO_RIO_RXU_MAP_L_LTR_SHIFT) |
(srio_message_map[i].mbx << CSL_SRIO_RIO_RXU_MAP_L_MBX_SHIFT) |
(srio_message_map[i].srcId << CSL_SRIO_RIO_RXU_MAP_L_SRCID_SHIFT);
// 配置接收单元映射寄存器高32位
srioRegs->RXU_MAP[i].RIO_RXU_MAP_H =
(srio_message_map[i].dstId << CSL_SRIO_RIO_RXU_MAP_H_DEST_ID_SHIFT) |
(srio_message_map[i].dstProm << CSL_SRIO_RIO_RXU_MAP_H_DEST_PROM_SHIFT) |
(srio_message_map[i].tt << CSL_SRIO_RIO_RXU_MAP_H_TT_MASK) |
(srio_message_map[i].srcProm << CSL_SRIO_RIO_RXU_MAP_H_SRC_PROM_SHIFT) |
(srio_message_map[i].segMap << CSL_SRIO_RIO_RXU_MAP_H_SEG_MAP_SHIFT);
// 配置类型9消息的映射寄存器
srioRegs->RXU_TYPE9_MAP[i].RIO_RXU_TYPE9_MAP0 =
(srio_message_map[i].cosMask << CSL_SRIO_RIO_RXU_TYPE9_MAP0_COS_MASK_SHIFT) |
(srio_message_map[i].cos << CSL_SRIO_RIO_RXU_TYPE9_MAP0_COS_SHIFT) |
(srio_message_map[i].srcId << CSL_SRIO_RIO_RXU_TYPE9_MAP0_SRCID_SHIFT);
srioRegs->RXU_TYPE9_MAP[i].RIO_RXU_TYPE9_MAP1 =
(srio_message_map[i].dstId << CSL_SRIO_RIO_RXU_TYPE9_MAP1_DEST_ID
这段代码主要用于配置一个消息映射,以确定数据包接收流和队列。
下面是这段代码的简要说明:
结构体定义 (SRIO_RX_Message_Map):
此结构体用于存储与SRIO消息相关的各种字段,如流ID、目标队列ID、源/目标设备ID、优先级、传输类型等。它还包含用于匹配特定类型消息的字段,例如类型11消息的字段(mailbox、letter等)和类型9消息的字段(服务类别、流ID等)。
函数 Keystone_map_SRIO_RX_message:
此函数用于将消息映射到PacketDMA流和队列。它接受一个指向SRIO_RX_Message_Map数组和一个表示映射条目数量的整数。函数首先确保映射条目数量不超过硬件支持的最大值(SRIO_MAX_MSG_MAP_ENTRY_NUM)。然后,它遍历映射数组,并将每个条目的信息写入相应的硬件寄存器,以配置消息到流和队列的映射。
消息映射数组 (DSP0_message_map):
这是一个SRIO_RX_Message_Map类型的数组,包含了预定义的消息映射配置。
每个条目定义了一个流ID、目标队列ID以及其他用于匹配接收消息的字段。
在这个例子中,只有目标设备ID用于确定接收流ID,而目标队列ID在流配置中设定。
配置调用:
代码的最后部分调用了Keystone_map_SRIO_RX_message函数,传递了DSP0_message_map数组和其大小,以配置硬件的消息映射。
2.5 中断设置
SRIO(Serial RapidIO)生成112个中断事件;这些事件被映射到24个中断输出,这些输出可以路由到DSP核心或DMA(直接存储器访问)。
所有的112个中断都可以映射到INTDST0~15。64个门铃事件可以根据INTERRUPT_CTL寄存器的设置映射到INTDST0~15或INTDST16~23。如果INTERRUPT_CTL=0,门铃事件将映射到INTDST16~23;如果INTERRUPT_CTL=1,门铃事件同样映射到INTDST16~23。
请注意,中断节流默认是启用的。中断节流要求DSP核心在每次中断服务后重写INTDSTn_RATE_CNT寄存器以启用下一个中断,否则,无论内部中断状态如何变化,中断都不会再次触发。这就是用户通常只看到一个SRIO中断的常见原因。如果对于特定的INTDST不希望使用中断节流,可以使用INTDST_RATE_DIS寄存器来禁用。
所有与消息相关的中断或事件都由Packet DMA和QMSS(队列管理子系统)处理。
下面是显示SRIO中断设置的示例代码。
/*Word index of the Interrupt Routing Registers*/
typedef enum
{
DOORBELL0_ICRR1 = (0x00/4),
DOORBELL0_ICRR2 = (0x04/4),
DOORBELL1_ICRR1 = (0x0C/4),
DOORBELL1_ICRR2 = (0x10/4),
DOORBELL2_ICRR1 = (0x18/4),
DOORBELL2_ICRR2 = (0x1C/4),
DOORBELL3_ICRR1 = (0x24/4),
DOORBELL3_ICRR2 = (0x28/4),
LSU_SRCID_ICRR1 = (0x30/4),
LSU_SRCID_ICRR2 = (0x34/4),
LSU_SRCID_ICRR3 = (0x38/4),
LSU_SRCID_ICRR4 = (0x3C/4),
LSU_ICRR1 = (0x40/4),
ERR_RST_EVNT_ICRR1 = (0x50/4),
ERR_RST_EVNT_ICRR2 = (0x54/4),
ERR_RST_EVNT_ICRR3 = (0x58/4)
}SRIO_ICRR_Index;
typedef enum
{
/* SRIO interrupt source constant,
high 16 bits is the ICRR register index,
lower 16 bits is the offset of the field in the register*/
DOORBELL0_0_INT = ((DOORBELL0_ICRR1 << 16) | 0x0000),
DOORBELL0_1_INT = ((DOORBELL0_ICRR1 << 16) | 0x0004),
DOORBELL0_2_INT = ((DOORBELL0_ICRR1 << 16) | 0x0008),
DOORBELL0_3_INT = ((DOORBELL0_ICRR1 << 16) | 0x000C),
DOORBELL0_4_INT = ((DOORBELL0_ICRR1 << 16) | 0x0010),
DOORBELL0_5_INT = ((DOORBELL0_ICRR1 << 16) | 0x0014),
DOORBELL0_6_INT = ((DOORBELL0_ICRR1 << 16) | 0x0018),
DOORBELL0_7_INT = ((DOORBELL0_ICRR1 << 16) | 0x001C),
DOORBELL0_8_INT = ((DOORBELL0_ICRR2 << 16) | 0x0000),
DOORBELL0_9_INT = ((DOORBELL0_ICRR2 << 16) | 0x0004),
DOORBELL0_10_INT = ((DOORBELL0_ICRR2 << 16) | 0x0008),
DOORBELL0_11_INT = ((DOORBELL0_ICRR2 << 16) | 0x000C),
DOORBELL0_12_INT = ((DOORBELL0_ICRR2 << 16) | 0x0010),
DOORBELL0_13_INT = ((DOORBELL0_ICRR2 << 16) | 0x0014),
DOORBELL0_14_INT = ((DOORBELL0_ICRR2 << 16) | 0x0018),
DOORBELL0_15_INT = ((DOORBELL0_ICRR2 << 16) | 0x001C),
DOORBELL1_0_INT = ((DOORBELL1_ICRR1 << 16) | 0x0000),
DOORBELL1_1_INT = ((DOORBELL1_ICRR1 << 16) | 0x0004),
DOORBELL1_2_INT = ((DOORBELL1_ICRR1 << 16) | 0x0008),
DOORBELL1_3_INT = ((DOORBELL1_ICRR1 << 16) | 0x000C),
DOORBELL1_4_INT = ((DOORBELL1_ICRR1 << 16) | 0x0010),
DOORBELL1_5_INT = ((DOORBELL1_ICRR1 << 16) | 0x0014),
DOORBELL1_6_INT = ((DOORBELL1_ICRR1 << 16) | 0x0018),
DOORBELL1_7_INT = ((DOORBELL1_ICRR1 << 16) | 0x001C),
DOORBELL1_8_INT = ((DOORBELL1_ICRR2 << 16) | 0x0000),
DOORBELL1_9_INT = ((DOORBELL1_ICRR2 << 16) | 0x0004),
DOORBELL1_10_INT = ((DOORBELL1_ICRR2 << 16) | 0x0008),
DOORBELL1_11_INT = ((DOORBELL1_ICRR2 << 16) | 0x000C),
DOORBELL1_12_INT = ((DOORBELL1_ICRR2 << 16) | 0x0010),
DOORBELL1_13_INT = ((DOORBELL1_ICRR2 << 16) | 0x0014),
DOORBELL1_14_INT = ((DOORBELL1_ICRR2 << 16) | 0x0018),
DOORBELL1_15_INT = ((DOORBELL1_ICRR2 << 16) | 0x001C),
DOORBELL2_0_INT = ((DOORBELL2_ICRR1 << 16) | 0x0000),
DOORBELL2_1_INT = ((DOORBELL2_ICRR1 << 16) | 0x0004),
DOORBELL2_2_INT = ((DOORBELL2_ICRR1 << 16) | 0x0008),
DOORBELL2_3_INT = ((DOORBELL2_ICRR1 << 16) | 0x000C),
DOORBELL2_4_INT = ((DOORBELL2_ICRR1 << 16) | 0x0010),
DOORBELL2_5_INT = ((DOORBELL2_ICRR1 << 16) | 0x0014),
DOORBELL2_6_INT = ((DOORBELL2_ICRR1 << 16) | 0x0018),
DOORBELL2_7_INT = ((DOORBELL2_ICRR1 << 16) | 0x001C),
DOORBELL2_8_INT = ((DOORBELL2_ICRR2 << 16) | 0x0000),
DOORBELL2_9_INT = ((DOORBELL2_ICRR2 << 16) | 0x0004),
DOORBELL2_10_INT = ((DOORBELL2_ICRR2 << 16) | 0x0008),
DOORBELL2_11_INT = ((DOORBELL2_ICRR2 << 16) | 0x000C),
DOORBELL2_12_INT = ((DOORBELL2_ICRR2 << 16) | 0x0010),
DOORBELL2_13_INT = ((DOORBELL2_ICRR2 << 16) | 0x0014),
DOORBELL2_14_INT = ((DOORBELL2_ICRR2 << 16) | 0x0018),
DOORBELL2_15_INT = ((DOORBELL2_ICRR2 << 16) | 0x001C),
DOORBELL3_0_INT = ((DOORBELL3_ICRR1 << 16) | 0x0000),
DOORBELL3_1_INT = ((DOORBELL3_ICRR1 << 16) | 0x0004),
DOORBELL3_2_INT = ((DOORBELL3_ICRR1 << 16) | 0x0008),
DOORBELL3_3_INT = ((DOORBELL3_ICRR1 << 16) | 0x000C),
DOORBELL3_4_INT = ((DOORBELL3_ICRR1 << 16) | 0x0010),
DOORBELL3_5_INT = ((DOORBELL3_ICRR1 << 16) | 0x0014),
DOORBELL3_6_INT = ((DOORBELL3_ICRR1 << 16) | 0x0018),
DOORBELL3_7_INT = ((DOORBELL3_ICRR1 << 16) | 0x001C),
DOORBELL3_8_INT = ((DOORBELL3_ICRR2 << 16) | 0x0000),
DOORBELL3_9_INT = ((DOORBELL3_ICRR2 << 16) | 0x0004),
DOORBELL3_10_INT = ((DOORBELL3_ICRR2 << 16) | 0x0008),
DOORBELL3_11_INT = ((DOORBELL3_ICRR2 << 16) | 0x000C),
DOORBELL3_12_INT = ((DOORBELL3_ICRR2 << 16) | 0x0010),
DOORBELL3_13_INT = ((DOORBELL3_ICRR2 << 16) | 0x0014),
DOORBELL3_14_INT = ((DOORBELL3_ICRR2 << 16) | 0x0018),
DOORBELL3_15_INT = ((DOORBELL3_ICRR2 << 16) | 0x001C),
SRCID0_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0000),
SRCID1_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0004),
SRCID2_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0008),
SRCID3_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x000C),
SRCID4_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0010),
SRCID5_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0014),
SRCID6_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x0018),
SRCID7_Transaction_Complete_OK = ((LSU_SRCID_ICRR1 << 16) | 0x001C),
SRCID8_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0000),
SRCID9_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0004),
SRCID10_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0008),
SRCID11_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x000C),
SRCID12_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0010),
SRCID13_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0014),
SRCID14_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x0018),
SRCID15_Transaction_Complete_OK = ((LSU_SRCID_ICRR2 << 16) | 0x001C),
SRCID0_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0000),
SRCID1_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0004),
SRCID2_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0008),
SRCID3_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x000C),
SRCID4_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0010),
SRCID5_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0014),
SRCID6_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x0018),
SRCID7_Transaction_Complete_ERR = ((LSU_SRCID_ICRR3 << 16) | 0x001C),
SRCID8_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0000),
SRCID9_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0004),
SRCID10_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0008),
SRCID11_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x000C),
SRCID12_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0010),
SRCID13_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0014),
SRCID14_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x0018),
SRCID15_Transaction_Complete_ERR = ((LSU_SRCID_ICRR4 << 16) | 0x001C),
LSU0_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0000),
LSU1_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0004),
LSU2_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0008),
LSU3_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x000C),
LSU4_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0010),
LSU5_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0014),
LSU6_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x0018),
LSU7_Transaction_Complete_OK = ((LSU_ICRR1 << 16) | 0x001C),
Multicast_event = ((ERR_RST_EVNT_ICRR1<<16)|0),
Port_write_In_received = ((ERR_RST_EVNT_ICRR1<<16)|4),
Logical_Layer_Error = ((ERR_RST_EVNT_ICRR1<<16)|8),
Port0_Error = ((ERR_RST_EVNT_ICRR2<<16)|0),
Port1_Error = ((ERR_RST_EVNT_ICRR2<<16)|4),
Port2_Error = ((ERR_RST_EVNT_ICRR2<<16)|8),
Port3_Error = ((ERR_RST_EVNT_ICRR2<<16)|12),
Device_Reset = ((ERR_RST_EVNT_ICRR3<<16)|0)
}SRIO_Interrupt_Source;
中断路由寄存器索引的词索引,用于表示中断路由寄存器(Interrupt Routing Registers)的索引和中断源常量。
定义一个枚举类型SRIO_ICRR_Index,包含以下值:
- DOORBELL0_ICRR1到DOORBELL3_ICRR2:表示不同门铃中断控制寄存器的索引。
- LSU_SRCID_ICRR1到LSU_SRCID_ICRR4:表示源ID相关中断控制寄存器的索引。
- LSU_ICRR1:表示LSU(负载存储单元)中断控制寄存器的索引。
- ERR_RST_EVNT_ICRR1到ERR_RST_EVNT_ICRR3:表示错误和复位事件中断控制寄存器的索引。
定义一个枚举类型SRIO_Interrupt_Source,包含以下值:
- DOORBELL0_0_INT到DOORBELL3_15_INT:表示不同门铃中断的源,每个门铃有16个中断源。
- SRCID0_Transaction_Complete_OK到SRCID15_Transaction_Complete_ERR:表示不同源ID的交易完成中断,分为成功和错误两种情况,每个源ID有一个成功和一个错误的中断源。
- LSU0_Transaction_Complete_OK到LSU7_Transaction_Complete_OK:表示LSU的交易完成中断,共有8个中断源。
- Multicast_event、Port_write_In_received、Logical_Layer_Error:表示不同类型的中断源。
- Port0_Error到Port3_Error:表示不同端口的错误中断源。
- Device_Reset:表示设备复位中断源。
每个中断源的定义都是通过将中断控制寄存器的索引左移16位,然后与一个偏移量进行按位或操作得到的。这个偏移量表示中断字段在寄存器中的位置。
// SRIO中断目的地的枚举定义
typedef enum {
// 中断目的地从0到15
INTDST_0 = 0, // 中断目的地0
INTDST_1, // 中断目的地1
// ...以此类推,直到INTDST_15
// 仅门铃使用的中断目的地
INTDST_16 = 0, // 门铃中断目的地0
INTDST_17, // 门铃中断目的地1
// ...以此类推,直到INTDST_23
} SRIO_Interrupt_Dest;
// 定义SRIO中断映射结构,包含中断源和中断目的地编号
typedef struct {
SRIO_Interrupt_Source interrupt_event; // 中断事件源
SRIO_Interrupt_Dest INTDST_number; // 中断目的地编号
} SRIO_Interrupt_Map;
// 定义SRIO中断速率结构,包含中断目的地编号和中断速率计数器
typedef struct {
SRIO_Interrupt_Dest INTDST_number; // 中断目的地编号
Uint32 interrupt_rate_counter; // 中断速率计数器
} SRIO_Interrupt_Rate;
// 定义SRIO门铃路由控制枚举,用于控制门铃中断是路由到专用中断还是通用中断
typedef enum {
SRIO_DOORBELL_ROUTE_TO_DEDICATE_INT = 0, // 路由到专用中断
SRIO_DOORBELL_ROUTE_TO_GENERAL_INT // 路由到通用中断
} SRIO_Doorbell_Route_Coutrol;
// 定义SRIO中断配置结构,包含中断映射表、中断速率配置等
typedef struct {
SRIO_Interrupt_Map *interrupt_map; // 中断映射表指针
Uint32 uiNumInterruptMap; // 中断映射数量
SRIO_Interrupt_Rate *interrupt_rate; // 中断速率配置指针
Uint32 uiNumInterruptRateCfg; // 具有速率配置的中断目的地数量
SRIO_Doorbell_Route_Coutrol doorbell_route_ctl; // 门铃路由控制
} SRIO_Interrupt_Cfg;
// Keystone_SRIO_Interrupt_init函数用于初始化SRIO中断
void Keystone_SRIO_Interrupt_init(SRIO_Interrupt_Cfg *interrupt_cfg) {
// ...函数实现代码...
}
// 定义一个SRIO中断映射数组,用于指定中断事件到特定核心的中断目的地
SRIO_Interrupt_Map interrupt_map[] = {
{DOORBELL0_0_INT, INTDST_16}, // 将DOORBELL0_0_INT路由到核心0
// ...其他中断映射...
};
// 创建一个SRIO中断配置结构实例
SRIO_Interrupt_Cfg interrupt_cfg;
// ...其他代码...
interrupt_cfg.interrupt_map = interrupt_map;
interrupt_cfg.uiNumInterruptMap = sizeof(interrupt_map) / sizeof(SRIO_Interrupt_Map);
// 在这个测试中不使用中断速率控制
interrupt_cfg.interrupt_rate = NULL;
interrupt_cfg.uiNumInterruptRateCfg = 0;
interrupt_cfg.doorbell_route_ctl = SRIO_DOORBELL_ROUTE_TO_DEDICATE_INT;
// 初始化SRIO中断
Keystone_SRIO_Interrupt_init(&interrupt_cfg);
2.6 回环模式
2.6.1 内部数字回环
在内部数字回环模式下,TX数据在数字域内进行回环。
回环[3:0]字段在外设设置控制寄存器1中用于设置数字回环测试。以下代码展示了配置过程:
srioRegs->RIO_PER_SET_CNTL1 |= (0xF<<CSL_SRIO_RIO_PER_SET_CNTL1_LOOPBACK_SHIFT);
2.6.2 内部SERDES回环
如果配置了SERDES回环,TX数据将在Serdes内部进行回环。
SRIO SERDES发送通道配置寄存器中的环回字段[22:21]和SRIO SERDES接收通道配置寄存器中的环回字段[24:23]用于启用SERDES环回测试。以下代码展示了如何进行配置:
serdesRegs->link[i].CFGTX |= (serdes_cfg->linkSetup[i]->loopBack<<21);
serdesRegs->link[i].CFGRX |= (serdes_cfg->linkSetup[i]->loopBack<<23);
2.6.3 外部线路环回
如果启用了外部线路环回,则在 RX “线路”上接收到的数据将直接发送回 TX “线路”。 下图显示了一个外部线路环回测试。在此测试中,DSP 1 被配置为线路环回模式,以支持 DSP 0 测试两个 DSP 之间的外部“线路”。在这种模式下,DSP 1 不能发送或接收数据。
PLM Port(n) 实现特定控制寄存器的位 23 LLB_EN 用于控制外部线路环回测试。以下代码显示了配置过程:
for(i=0; i<SRIO_MAX_PORT_NUM; i++)
{
srioRegs->RIO_PLM[i].RIO_PLM_SP_IMP_SPEC_CTL =
(1 << CSL_SRIO_RIO_PLM_SP_IMP_SPEC_CTL_LLB_EN_SHIFT);
}
这段代码它遍历所有的 SRIO 端口。对于每个端口,它将 RIO_PLM_SP_IMP_SPEC_CTL 寄存器的 LLB_EN 位设置为 1,从而启用线路环回功能。CSL_SRIO_RIO_PLM_SP_IMP_SPEC_CTL_LLB_EN_SHIFT 是一个宏,它定义了 LLB_EN 位在寄存器中的位置。通过将 1 左移这个位数,代码将相应位置的位设置为 1,而其他位保持不变。这表明在编写这段代码时,应该有一个定义了 SRIO_MAX_PORT_NUM 的地方,它指定了 SRIO 端口的最大数量。
2.7 数据包转发
KeyStone 系列的 SRIO 支持数据包转发功能,它可以将从一个端口接收到的数据转发到任何其他端口。通过这个功能,多个 DSP 可以像链条一样连接起来。 下图显示了一个外部转发回环测试。在这种模式下,当某些数据包被转发出去时,DSP 1 仍然可以发送或接收自己的数据。
Keystone 系列的 SRIO 转发功能由 MAU(内存访问单元)模块处理,它只支持 NREAD、NWRITE、NWRITE_R、SWRITE 和门铃(Doorbell)数据包类型。所有其他类型都不支持,可能会导致未定义的行为,包括生成错误响应(ERROR response)。
PF_16b_CNTL[0:7] 和 PF_8b_CNTL[0:7] 控制数据包转发设备 ID 边界和输出端口选择。当入站设备 ID 与设置的数据包转发条目匹配时,数据包将被转发到 MAU 并进行进一步处理。
以下代码展示了数据包转发的配置过程:
// 定义一个结构体,用于配置SRIO数据包的转发
typedef struct
{
Uint16 forwardingID_up_8; // 上8位设备ID边界
Uint16 forwardingID_lo_8; // 下8位设备ID边界
Uint16 forwardingID_up_16; // 上16位设备ID边界
Uint16 forwardingID_lo_16; // 下16位设备ID边界
Uint32 outport; // 目的ID落在此表项8位或16位范围内的数据包的输出端口编号
} SRIO_PktForwarding_Cfg;
// 配置SRIO数据包转发的函数
void Keystone_SRIO_packet_forwarding_Cfg(
SRIO_PktForwarding_Cfg * PktForwardingEntry_cfg, // 指向转发配置结构的指针
Uint32 pktForwardingEntryNum) // 转发条目的数量
{
int i = 0;
// 确保转发条目数量不超过最大值
pktForwardingEntryNum= _min2(SRIO_MAX_FORWARDING_ENTRY_NUM, pktForwardingEntryNum);
// 遍历所有转发条目
for(i=0; i<pktForwardingEntryNum; i++)
{
// 设置16位控制寄存器,用于指定16位设备ID的上界和下界
srioRegs->PF_CNTL[i].RIO_PF_16B_CNTL =
(PktForwardingEntry_cfg[i].forwardingID_up_16 << CSL_SRIO_RIO_PF_16B_CNTL_DEVID_16B_UP_SHIFT) |
(PktForwardingEntry_cfg[i].forwardingID_lo_16 << CSL_SRIO_RIO_PF_16B_CNTL_DEVID_16B_LO_SHIFT);
// 设置8位控制寄存器,用于指定8位设备ID的上界和下界以及输出端口
srioRegs->PF_CNTL[i].RIO_PF_8B_CNTL =
(PktForwardingEntry_cfg[i].forwardingID_up_8 << CSL_SRIO_RIO_PF_8B_CNTL_DEVID_8B_UP_SHIFT) |
(PktForwardingEntry_cfg[i].forwardingID_lo_8 << CSL_SRIO_RIO_PF_8B_CNTL_DEVID_8B_LO_SHIFT) |
(PktForwardingEntry_cfg[i].outport << CSL_SRIO_RIO_PF_8B_CNTL_OUT_PORT_SHIFT);
}
}
// 配置数组,最多可以设置8个条目
SRIO_PktForwarding_Cfg DSP1_PktForwarding_Cfg[]=
{
// 以下为具体的转发配置,包括设备ID范围和相应的输出端口
{DSP0_SRIO_BASE_ID+0, DSP0_SRIO_BASE_ID+1, DSP0_SRIO_BASE_ID+0, DSP0_SRIO_BASE_ID+1, 2},
{DSP0_SRIO_BASE_ID+2, DSP0_SRIO_BASE_ID+2, DSP0_SRIO_BASE_ID+2, DSP0_SRIO_BASE_ID+2, 2},
{DSP0_SRIO_BASE_ID+3, DSP0_SRIO_BASE_ID+3, DSP0_SRIO_BASE_ID+3, DSP0_SRIO_BASE_ID+3, 3},
{DSP0_SRIO_BASE_ID+4, DSP0_SRIO_BASE_ID+7, DSP0_SRIO_BASE_ID+4, DSP0_SRIO_BASE_ID+7, 3}
};
……
// 调用函数进行配置,传入配置数组和条目数量
Keystone_SRIO_packet_forwarding_Cfg(&DSP1_PktForwarding_Cfg,
sizeof(DSP1_PktForwarding_Cfg)/sizeof(SRIO_PktForwarding_Cfg));
typedef struct: 定义了一个结构体SRIO_PktForwarding_Cfg,用于存储数据包转发的配置信息。这个结构体包含了转发ID的上界和下界(8位和16位),以及输出端口编号。
void Keystone_SRIO_packet_forwarding_Cfg(...): 这是一个函数,用于配置SRIO的数据包转发。它接受一个指向SRIO_PktForwarding_Cfg结构体数组的指针和一个表示数组大小的整数。
在函数内部,首先使用_min2函数确保转发表项的数量不超过SRIO_MAX_FORWARDING_ENTRY_NUM定义的最大值。
接下来,使用一个for循环遍历所有的转发表项。对于每个表项,代码将设置两个寄存器:RIO_PF_16B_CNTL和RIO_PF_8B_CNTL。这些寄存器用于指定16位和8位设备ID的范围,以及输出端口。
SRIO_PktForwarding_Cfg DSP1_PktForwarding_Cfg[]: 定义了一个SRIO_PktForwarding_Cfg类型的数组,用于存储DSP1的转发配置。这个数组包含了4个表项,每个表项都指定了8位和16位设备ID的范围,以及输出端口。
最后,调用Keystone_SRIO_packet_forwarding_Cfg函数,将DSP1的转发配置传递给它。这将设置SRIO的转发表,以便根据设备ID将数据包转发到正确的输出端口。
2.8 接收模式
SRIO如何处理接收到的数据包由三个配置位控制。
0b1 — 支持混杂ID/单播ID。
0b1 — 接受所有维护数据包,无论目标ID是什么。
0b1 — 允许数据包转发和多播。
// 定义一个结构体,用于配置SRIO端口的接收模式
typedef struct {
Bool accept_maintenance_with_any_ID; // 是否接受任何ID的维护报文
// 如果accept_data_with_any_ID为真,则不会转发任何数据包
Bool support_multicast_forwarding; // 是否支持多播转发
} SRIO_Port_RX_Mode;
// 定义一个结构体,用于配置SRIO的接收模式
typedef struct {
// 如果accept_data_with_any_ID为真,则不会转发任何数据包
Bool accept_data_with_any_ID;
SRIO_Port_RX_Mode port_rx_mode[4]; // 四个端口的接收模式配置
} SRIO_RX_Mode;
// 配置SRIO的接收模式
void Keystone_SRIO_RxMode_Setup(SRIO_RX_Mode * rxMode)
{
int i;
if(rxMode) // 如果传入的rxMode指针非空
{
// 配置接收任何ID的数据包
srioRegs->RIO_PER_SET_CNTL = (srioRegs->RIO_PER_SET_CNTL &
(~CSL_SRIO_RIO_PER_SET_CNTL_LOG_TGT_ID_DIS_MASK)) |
(rxMode->accept_data_with_any_ID <<
CSL_SRIO_RIO_PER_SET_CNTL_LOG_TGT_ID_DIS_SHIFT);
// 为所有端口设置RX模式
for(i = 0; i < SRIO_MAX_PORT_NUM; i++)
{
// 配置每个端口的接收模式
srioRegs->RIO_TLM[i].RIO_TLM_SP_CONTROL = (srioRegs->RIO_TLM[i].RIO_TLM_SP_CONTROL &
(~(CSL_SRIO_RIO_TLM_SP_CONTROL_TGT_ID_DIS_MASK |
CSL_SRIO_RIO_TLM_SP_CONTROL_MTC_TGT_ID_DIS_MASK))) |
(rxMode->port_rx_mode[i].accept_maintenance_with_any_ID <<
CSL_SRIO_RIO_TLM_SP_CONTROL_MTC_TGT_ID_DIS_SHIFT) |
(rxMode->port_rx_mode[i].support_multicast_forwarding <<
CSL_SRIO_RIO_TLM_SP_CONTROL_TGT_ID_DIS_SHIFT);
}
}
}
// ... 其他代码 ...
SRIO_RX_Mode rxMode; // 创建一个SRIO_RX_Mode类型的变量rxMode
// ... 其他代码 ...
// 清除配置结构体,确保未使用的字段为0
memset(&rxMode, 0, sizeof(rxMode));
rxMode.port_rx_mode[0].support_multicast_forwarding = TRUE; // 端口0支持多播转发
rxMode.port_rx_mode[1].support_multicast_forwarding = TRUE; // 端口1支持多播转发
rxMode.port_rx_mode[2].support_multicast_forwarding = TRUE; // 端口2支持多播转发
rxMode.port_rx_mode[3].support_multicast_forwarding = TRUE; // 端口3支持多播转发
Keystone_SRIO_RxMode_Setup(&rxMode); // 调用函数,设置SRIO的接收模式
3 SRIO 传输编程
3.1 LSU 传输
Direct IO, doorbell and maintenance transfers在LSU模块中实现,每个LSU寄存器组代表一个传输请求。LSU寄存器的编程相当直接。如上一节所讨论的,存在多个影子寄存器组,我们必须在编程寄存器之前,通过轮询LSU_REG6中的FULL和BUSY位,直到有一个影子寄存器可用。下面是编程LSU的示例代码。
// SRIO LSU传输的结构体定义
typedef struct SRIO_LSU_Transfer {
Uint32 rioAddressMSB; // RapidIO地址的最高32位
Uint32 rioAddressLSB_ConfigOffset; // RapidIO地址的最低32位和配置偏移
Uint32 localDspAddress; // 本地DSP地址
Uint32 bytecount; // 字节数
SRIO_Packet_Type packetType; // 包类型
Uint16 dstID; // 目的地ID
Uint16 doorbellInfo; // 门铃信息
Bool waitLsuReady; // 如果BUSY或FULL,是否等待LSU就绪
Uint8 lsuNum; // 用于此传输的LSU编号
Uint8 doorbellValid; // 门铃有效标志
Uint8 intrRequest; // 中断请求
Uint8 supGoodInt; // 支持良好中断
Uint8 priority; // 优先级
Uint8 outPortID; // 输出端口ID
Uint8 idSize; // ID大小
Uint8 srcIDMap; // 源ID映射
Uint8 hopCount; // 跳数
Uint8 transactionID; // 交易ID,用于完成状态检查
Uint8 contextBit; // 交易上下文位,用于完成状态检查
} SRIO_LSU_Transfer;
// LSU传输函数
void Keystone_SRIO_LSU_transfer(SRIO_LSU_Transfer *transfer) {
// ... 函数实现 ...
}
LSU传输完成情况通过LSU_STAT_REG寄存器进行检查。从LSU_REG6读取的交易ID用于确定LSU_STAT_REG寄存器中完成代码的索引;从LSU_REG6读取的上下文位用于确定完成代码的有效性。上下文位会为每个事务切换,例如,对于第一个交易ID=2的传输,上下文位=1;然后,对于下一个交易ID=2的传输,上下文位=0。因此,只有在LSU_STAT_REG寄存器中交易ID=2的上下文位从1切换到0后,才表示第二个传输完成,并且完成代码有效。以下是LSU完成检查的示例代码。
//这是一个用于指示LSU状态寄存器中状态索引的表格,用于SRIO_MAX_LSU_NUM个LSU的事务。
Uint8 LSU_state_index_table[SRIO_MAX_LSU_NUM][SRIO_LSU0_4_MAX_SHADOW_REG_SET]=
{
/*LSU0的索引*/ {0, 1, 2, 3, 4, 5, 6, 7, 8},
/*LSU1的索引*/ {9, 10, 11, 12, 13, 14},
/*LSU2的索引*/ {15, 16, 17, 18, 19},
/*LSU3的索引*/ {20, 21, 22, 23},
/*LSU4的索引*/ {24, 25, 26, 27, 28, 29, 30, 31, 32},
/*LSU5的索引*/ {33, 34, 35, 36, 37, 38},
/*LSU6的索引*/ {39, 40, 41, 42, 43},
/*LSU7的索引*/ {44, 45, 46, 47}
};
/*等待LSU传输完成,并返回完成代码*/
Uint32 Keystone_SRIO_wait_LSU_completion(Uint32 lsuNum,
Uint32 transactionID, Uint32 contextBit)
{
Uint32 uiStateIndex= LSU_state_index_table[lsuNum][transactionID];
Uint32 uiCompletionCode;
do
{
uiCompletionCode=(srioRegs->LSU_STAT_REG[uiStateIndex/8]>>
((uiStateIndex&7)*4))&0xF;
}
while((uiCompletionCode&1) != contextBit);
return (uiCompletionCode>>1);
}
LSU_state_index_table 是一个二维数组,它定义了每个LSU的传输ID到状态寄存器索引的映射。每个LSU有一系列的传输槽,这些槽对应的状态索引存储在这个表中。
Keystone_SRIO_wait_LSU_completion 函数有三个参数:lsuNum 表示LSU的编号,transactionID 表示传输的ID,contextBit 表示上下文位。
函数首先根据LSU编号和传输ID在LSU_state_index_table表中查找对应的状态索引。
然后,函数进入一个循环,不断读取对应的状态寄存器来检查传输是否完成。这里使用了一个位掩码操作来提取完成码:uiCompletionCode=(srioRegs->LSU_STAT_REG[uiStateIndex/8]>>((uiStateIndex&7)*4))&0xF; 这里srioRegs->LSU_STAT_REG是一个结构体指针,指向包含状态寄存器的结构体,uiStateIndex/8和(uiStateIndex&7)*4分别用于计算正确的寄存器和位位置。
循环会一直执行,直到uiCompletionCode的最低有效位(LSB)与传入的contextBit相匹配,这表明传输已经完成。
最后,函数返回移除了最低有效位的uiCompletionCode,即return (uiCompletionCode>>1);,这个返回值表示传输的完成状态。
下面是直接IO传输的示例代码。
// 定义SRIO数据包类型的枚举
typedef enum
{
SRIO_PKT_TYPE_NREAD = 0x24, // 非维持读操作
SRIO_PKT_TYPE_NWRITE = 0x54, // 非维持写操作
SRIO_PKT_TYPE_NWRITE_R = 0x55, // 非维持写响应操作
SRIO_PKT_TYPE_SWRITE = 0x60, // 维持写操作
SRIO_PKT_TYPE_MTN_READ = 0x80, // 维持多传输读操作
SRIO_PKT_TYPE_MTN_WRITE = 0x81, // 维持多传输写操作
SRIO_PKT_TYPE9_STREAM = 0x90, // 流数据包类型
SRIO_PKT_TYPE_DOORBELL = 0xA0, // 门铃数据包类型
SRIO_PKT_TYPE11_MESSAGE = 0xB0 // 消息数据包类型
} SRIO_Packet_Type;
// Keystone的SRIO直接输入/输出操作函数
Int32 Keystone_SRIO_DirectIO(Uint32 uiLocalAddress, Uint32 uiRemoteAddress,
Uint32 uiDestID, Uint32 uiByteCount, Uint32 uiPort, Uint32 uiLSU_No,
SRIO_Packet_Type packetType)
{
SRIO_LSU_Transfer lsuTransfer; // 定义一个SRIO LSU传输结构体
lsuTransfer.rioAddressMSB = 0; // SRIO地址的高位
lsuTransfer.rioAddressLSB_ConfigOffset = uiRemoteAddress; // SRIO地址的低位和配置偏移
lsuTransfer.localDspAddress = uiLocalAddress; // 本地DSP地址
lsuTransfer.bytecount = uiByteCount; // 字节数
lsuTransfer.packetType = packetType; // 数据包类型
lsuTransfer.dstID = uiDestID; // 目的地ID
lsuTransfer.doorbellInfo = 0; // 门铃信息
lsuTransfer.waitLsuReady = 1; // 等待LSU准备
lsuTransfer.lsuNum = uiLSU_No; // LSU编号
lsuTransfer.doorbellValid = 0; // 门铃有效标志
lsuTransfer.intrRequest = 0; // 中断请求标志
lsuTransfer.supGoodInt = 0; // 支持良好中断标志
lsuTransfer.priority = 0; // 优先级
lsuTransfer.outPortID = uiPort; // 输出端口ID
lsuTransfer.idSize = 0; // ID大小
lsuTransfer.srcIDMap = 0; // 源ID映射
lsuTransfer.hopCount = 0; // 跳数计数
Keystone_SRIO_LSU_transfer(&lsuTransfer); // 执行SRIO LSU传输
return Keystone_SRIO_wait_LSU_completion(uiLSU_No, // 等待LSU传输完成
lsuTransfer.transactionID, // 事务ID
lsuTransfer.contextBit); // 上下文位
}
3.2 消息传输
KeyStone系列通过QMSS(Quick Messaging Subsystem)和Packet DMA(Packet Direct Memory Access)来处理SRIO Type 11和Type 9数据包。有关详细信息,请参阅KeyStone架构多核导航用户指南(SPRUGR9)。
对于数据接收,如果消息接收映射和接收流设置得当,一旦接收到数据,Packet DMA会将数据包推送到接收目标队列。然后,应用程序可以从接收队列中获取数据包。对于数据传输,应用程序只需将数据包推送到16个发送队列中的一个,Packet DMA和SRIO外围设备将根据发送队列调度配置来传输数据包。
SRIO的特殊点在于数据包描述符中的协议特定字段。它们的定义如下。
/****************************************************************************/
/* 定义SRIO Type 9消息发送描述符的位和字布局 */
/****************************************************************************/
#ifdef _BIG_ENDIAN
// 如果是大端模式
typedef struct
{
/* 字0 */
Uint32 SRC_ID : 16; // 源节点ID
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
/* 字1 */
Uint32 StreamID : 16; // 事务的流ID
Uint32 reserved0 : 5; // 保留位
Uint32 TT : 2; // RapidIO tt字段,8位或16位ID
Uint32 reserved1 : 1; // 保留位
Uint32 COS : 8; // 服务等级
} SRIO_Type9_Message_TX_Descriptor;
#else
// 如果是小端模式
typedef struct
{
/* 字0 */
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
Uint32 SRC_ID : 16; // 源节点ID
/* 字1 */
Uint32 COS : 8; // 服务等级
Uint32 reserved1 : 1; // 保留位
Uint32 TT : 2; // RapidIO tt字段,8位或16位ID
Uint32 reserved0 : 5; // 保留位
Uint32 StreamID : 16; // 事务的流ID
} SRIO_Type9_Message_TX_Descriptor;
#endif
/****************************************************************************/
/* 定义SRIO Type 9消息接收描述符的位和字布局 */
/****************************************************************************/
#ifdef _BIG_ENDIAN
// 如果是大端模式
typedef struct
{
/* 字0 */
Uint32 SRC_ID : 16; // 源节点ID
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
/* 字1 */
Uint32 StreamID : 16; // 事务的流ID
Uint32 reserved0 : 1; // 保留位
Uint32 PRI : 4; // 消息优先级(虚拟通道||优先级||流量控制)
Uint32 T : 1; // 这是TT [0]位
Uint32 CC : 2; // 完成码
Uint32 COS : 8; // 服务等级
} SRIO_Type9_Message_RX_Descriptor;
#else
// 如果是小端模式
typedef struct
{
/* 字0 */
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
Uint32 SRC_ID : 16; // 源节点ID
/* 字1 */
Uint32 COS : 8; // 服务等级
Uint32 CC : 2; // 完成码
Uint32 T : 1; // 这是TT [0]位
Uint32 PRI : 4; // 消息优先级(虚拟通道||优先级||流量控制)
Uint32 reserved0 : 1; // 保留位
Uint32 StreamID : 16; // 事务的流ID
} SRIO_Type9_Message_RX_Descriptor;
#endif
/****************************************************************************/
/* 定义SRIO Type 11消息接收描述符的位和字布局 */
/****************************************************************************/
#ifdef _BIG_ENDIAN
// 如果是大端模式
typedef struct
{
/* 字0 */
Uint32 SRC_ID : 16; // 源节点ID
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
/* 字1 */
Uint32 reserved0 : 15; // 保留位
Uint32 CC : 2; // 完成码
Uint32 PRI : 4; // 消息优先级(虚拟通道||优先级||流量控制)
Uint32 TT : 2; // RapidIO tt字段,8位或16位设备ID
Uint32 LTR : 3; // 目的地字母
Uint32 MailBox : 6; // 目的地邮箱
} SRIO_Type11_Message_RX_Descriptor;
#else
// 如果是小端模式
typedef struct
{
/* 字0 */
Uint32 Dest_ID : 16; // 消息发送到的目标节点ID
Uint32 SRC_ID : 16; // 源节点ID
/* 字1 */
Uint32 MailBox : 6; // 目的地邮箱
Uint32 LTR : 3; // 目的地字母
Uint32 TT : 2; // RapidIO tt字段,8位或16位设备ID
Uint32 PRI : 4; // 消息优先级(虚拟通道||优先级||流量控制)
Uint32 CC : 2; // 完成码
Uint32 reserved0 : 15; // 保留位
} SRIO_Type11_Message_RX_Descriptor;
另一个我们需要注意的点是,对于数据传输,主机数据包描述符中的Packet_Type字段必须正确设置,它用于识别SRIO事务类型,对于类型9,Packet_Type应设置为30;如果Packet_Type为31,则选择类型11。应用程序只需要从FDQ中弹出一个描述符,并在描述符中填充PS字段,然后将描述符推送到相关的TX队列。
以下是填充TX数据包描述符的示例代码。
typedef enum
{
SRIO_TYPE9_CPPI_PACKET = 30,
SRIO_TYPE11_CPPI_PACKET = 31
}SRIO_CPPI_Packet_Type;
//Build SRIO type11 message descriptor
void Keystone_SRIO_Build_Type11_Msg_Desc(
HostPacketDescriptor * hostDescriptor, Uint32 uiSrcID, Uint32 uiDestID,
Uint32 uiByteCount, Uint32 uiMailBox, Uint32 uiLetter)
{
SRIO_Type11_Message_TX_Descriptor * type11MsgTxDesc;
hostDescriptor->packet_type = SRIO_TYPE11_CPPI_PACKET;
hostDescriptor->packet_length= uiByteCount;
hostDescriptor->buffer_len= uiByteCount;
hostDescriptor->psv_word_count= 2; //SRIO uses 2 Protocol Specific words
type11MsgTxDesc= (SRIO_Type11_Message_TX_Descriptor *)
(((Uint32)hostDescriptor)+32);
type11MsgTxDesc->Dest_ID = uiDestID;
type11MsgTxDesc->SRC_ID = uiSrcID;
type11MsgTxDesc->Retry_Count= 1;
type11MsgTxDesc->SSIZE= SRIO_SSIZE_256_BYTES;
type11MsgTxDesc->TT = 0;
type11MsgTxDesc->LTR= uiLetter;
type11MsgTxDesc->MailBox= uiMailBox;
}
//Build SRIO type9 data stream message Descriptor
void Keystone_SRIO_Build_Type9_Msg_Desc(
HostPacketDescriptor * hostDescriptor, Uint32 uiSrcID, Uint32 uiDestID,
Uint32 uiByteCount, Uint32 uiStreamID, Uint32 uiCOS)
{
SRIO_Type9_Message_TX_Descriptor * type9MsgTxDesc;
hostDescriptor->packet_type = SRIO_TYPE9_CPPI_PACKET;
hostDescriptor->packet_length= uiByteCount;
hostDescriptor->buffer_len= uiByteCount;
hostDescriptor->psv_word_count= 2; //SRIO uses 2 Protocol Specific words
type9MsgTxDesc= (SRIO_Type9_Message_TX_Descriptor *)
(((Uint32)hostDescriptor)+32);
type9MsgTxDesc->Dest_ID = uiDestID;
type9MsgTxDesc->SRC_ID = uiSrcID;
type9MsgTxDesc->StreamID = uiStreamID;
type9MsgTxDesc->TT = 0;
type9MsgTxDesc->COS = uiCOS;
}
请注意,类型9的数据包支持最大64K字节的负载,而类型11的数据包只支持最大4K字节的负载。