MCTP协议帧头字段说明
1 MCTP帧格式

| 字节位置 | 字段名 | 描述 |
|---|---|---|
| 1 | 从设备地址 | [7:1] 位:目标设备的 SMBus 从机地址 [0] 位:SMBus R/W# 位(必须设为 0b),所有 MCTP 消息使用 SMBus 写事务 |
| 2 | 命令码 | SMBus 命令码 所有基于 SMBus 的 MCTP 消息使用固定命令码 0x0F |
| 3 | 字节计数 | SMBus Block Write 协议的字节计数值 表示从本字段后到 PEC 字节前的总字节数 |
| 4 | 数据字节1 | SMBus 源从机地址 [7:1] 位:源设备的 SMBus 从机地址 |
| 5 | 数据字节2 | MCTP 标识头 [7:4] 位:MCTP 保留位(由基础规范定义) [3:0] 位:MCTP 头版本(0001b 表示符合 MCTP v1.0 和当前传输绑定规范) |
| 6 | 数据字节3 | 目标端点 ID (*) |
| 7 | 数据字节4 | 源端点 ID (*) |
| 8 | 数据字节5 | [7] SOM:消息起始标志 () [6] EOM:消息结束标志 () [5:4] 包序列号 () [3] TO:标签所有者位 () [2:0] 消息标签 (*) |
| 9 | 数据字节6 | [7] IC:完整性检查位 () [6:0] 消息类型 () |
| 10-N-1 | 数据字节7~M | MCTP 消息头和净荷数据 (*) |
| N | PEC | 包错误校验码(符合 SMBus 2.0 规范) 所有 MCTP 事务必须包含 PEC 字节,由源端生成、目的端校验 |
Byte Count表示除PEC字段外,Byte Count后面所有的数据的总长度。
MCTP协议的帧头包括:Slave Address、Command Code和Byte Count
如果发送的数据大于MTU,MCTP提供了切片功能,能够把数据包切片发送,在客户端重组数据。MCTP协议的数据字节5提供的各个标志位,实现协议的切片发送数据。MCTP支持i2c的标准模式、快速模式(400KHz)和增强快速模式(1MHz),支持7-bit从器件地址,不支持10-bit从器件地址。
2 分段发送
单个消息小于MTU,需要把SOM和EOM都置为1,表示消息不分段。
多分段消息:当消息长度大于MTU时,需分成多个分段发送。
- 第一个分段:设置
SOM=1,序列号为N。 - 中间分段:
SOM=0,EOM=0,序列号N。 - 最后一个分段:
SOM=0,EOM=1,序列号N。 - 注意:同一个消息的所有分段必须使用同一个序列号(Sequence Number)。
MCTP分段发送流程:
- 发送端将应用层消息分割成若干个可传输的段(Segment),每个段加上MCTP头部。
- 每个分段头部设置相同的序列号(Sequence Number)。
- 设置第一个分段的
SO位和最后一个分段的EO位。 - 将所有分段按顺序发送。
3 MCTP并发
在MCTP协议中并没有详细说明Msg Tag字段的用途。一般情况下,我们发送完一条指令后,会等待对方指令处理完成后返回处理结果。如果能在等待处理长时间请求的同时,下发新的新命令,这样就可以提高效率。MCTP协议中的Msg Tag的作用就是用于标记一个MCTP request,可以在上个请求没返回结果之前下发新的请求。Msg Tag只占3个bit,需要维护一个可用的Tag池(虽然池内的可用Tag值数量不大,只是为了形象说明)。
Intel的OpenBMC的mctpd进程就使用struct Tag类来管理Tag池:
struct Tags
{
std::optional<uint8_t> next() const;
void emplace(uint8_t flag);
void erase(uint8_t flag);
uint8_t bits{0xff};
};
同时用两个队列来存放需要发送的MCTP消息和已发送的MCTP消息:
struct Endpoint
{
Tags availableTags;
/* 已发送的MCTP消息 */
std::map<uint8_t, std::shared_ptr<Message>> transmittedMessages{};
/* 要发送的MCTP消息 */
std::map<size_t, std::shared_ptr<Message>> queuedMessages{};
size_t msgCounter{0u};
void transmitQueuedMessages(struct mctp* mctp, mctp_eid_t destEid);
};
mctpd发送MCTP消息的函数:
void MctpTransmissionQueue::Endpoint::transmitQueuedMessages(struct mctp* mctp,
mctp_eid_t destEid)
{
while (!queuedMessages.empty())
{
/* 获取可用的mctp tag值 */
const std::optional<uint8_t> nextTag = availableTags.next();
if (!nextTag) /* 如果没有有效的tag就先不发送消息 */
{
break;
}
auto msgTag = nextTag.value();
auto queuedMessageIter = queuedMessages.begin();
auto message = std::move(queuedMessageIter->second);
/* 从queuedMessages队列中取出消息,并删除存放在队列中的消息 */
queuedMessages.erase(queuedMessageIter);
int rc = mctp_message_tx(mctp, destEid, message->payload.data(),
message->payload.size(), true, msgTag,
message->privateData.data());
if (rc < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error in mctp_message_tx");
continue;
}
/* 发送成功,已被占用的tag值需要从tag池中删除 */
availableTags.erase(msgTag);
/* 把消息的tag值设置成已发送的消息的tag值相同 */
message->tag = msgTag;
/* 把已发送的消息存放到已发送的消息队列中 */
transmittedMessages.emplace(msgTag, std::move(message));
}
}
在接受到对端的response后,需要匹配是前面哪个消息的response,如何判断呢?自然而然是通过tag值去判断即可(简单又粗暴):
bool MctpTransmissionQueue::receive(struct mctp* mctp, mctp_eid_t srcEid,
uint8_t msgTag,
std::vector<uint8_t>&& response,
boost::asio::io_context& ioc)
{
auto endpointIter = endpoints.find(srcEid);
if (endpointIter == endpoints.end())
{
return false;
}
auto& endpoint = endpointIter->second;
/* 通过tag值在已发送队列中查找是否存在已发送的消息 */
auto messageIter = endpoint.transmittedMessages.find(msgTag);
/* 没有匹配的消息,说明当前message是有问题的 */
if (messageIter == endpoint.transmittedMessages.end())
{
return false;
}
const auto message = messageIter->second;
message->response = std::move(response);
/* 有tag值匹配的消息,说明收到的对应的response,删除已发送队列中的消息。 */
endpoint.transmittedMessages.erase(messageIter);
/* 回收tag值,存放回tag池中 */
message->tag.reset();
endpoint.availableTags.emplace(msgTag);
// Now that another tag is available, try to transmit any queued messages
message->timer.cancel();
ioc.post([this, mctp, srcEid] {
endpoints[srcEid].transmitQueuedMessages(mctp, srcEid);
});
return true;
}
});
return true;
}

浙公网安备 33010602011771号