MCTP协议帧头字段说明

1 MCTP帧格式

2025-06-18-16-27-58-image

字节位置 字段名 描述
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=0EOM=0,序列号N。
  • 最后一个分段SOM=0EOM=1,序列号N。
  • 注意:同一个消息的所有分段必须使用同一个序列号(Sequence Number)。

MCTP分段发送流程:

  1. 发送端将应用层消息分割成若干个可传输的段(Segment),每个段加上MCTP头部。
  2. 每个分段头部设置相同的序列号(Sequence Number)。
  3. 设置第一个分段的SO位和最后一个分段的EO位。
  4. 将所有分段按顺序发送。

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;
}
posted @ 2025-08-04 17:43  cockpunctual  阅读(247)  评论(0)    收藏  举报