| @@ -1,5 +1,8 @@ | |||
| 1 | # Changelog | 1 | # Changelog |
| 2 | All notable changes to this project will be documented in this file. | 2 | All notable changes to this project will be documented in this file. |
| 3 | +## [1.130.18] - 2026-10-26 | ||
| 4 | +新增默认关闭的 PCIe PLDM responder、独立回复上下文和 Base 能力注册,完善请求编解码;公共应答仅接收已登记PLDM端点,不能借用NC-SI注册授权。 | ||
| 5 | + | ||
| 3 | ## [1.130.15] - 2026-08-25 | 6 | ## [1.130.15] - 2026-08-25 |
| 4 | 更新版本号,触发流水线推包 | 7 | 更新版本号,触发流水线推包 |
| 5 | 8 | ||
| @@ -0,0 +1,25 @@ | |||
| 1 | +# 受限 PCIe PLDM 基础应答 | ||
| 2 | + | ||
| 3 | +`CONFIG_MCTP_EP_RESPONDER` 默认关闭;启用后只接受已登记 PCIe 对端的普通请求,SMBus、未知来源和 Datagram 不进入新增业务。非法配置关闭新增responder且不阻断既有MCTP启动。`PLDM_TID` 为稳定产品配置(1~254),不随 EID 改变。Base 支持 GetTID/GetPLDMVersion/GetPLDMTypes/GetPLDMCommands,位图与注册表一致;默认仅宣告Base;安装PR !12的取时模块后再启用`CONFIG_MCTP_PLDM_TIME`,按实际注册发布Type3/命令12;依赖缺失或注册失败时关闭responder且不影响既有MCTP。 | ||
| 4 | + | ||
| 5 | +每次回复独立保存路由、Tag、IID、Type、Command及整条上下文链,同来源/Tag不覆盖,全局最多64项;满额拒绝新请求且保留旧事务,`overload_drops`累计拒绝数,首次拒绝记录DEBUG避免洪泛日志。当前无独立有界错误发送队列,不绕过配额发送错误帧;对端依协议超时/退避重试。注销、重建、地址变化、重复或迟到完成均失效;250ms巡检清理闲置项,实际有效性仍在完成时按命令期限判定。新旧配置的巡检回调隔离。`push_checked`仅返回同步协议链提交结果,不等于硬件异步完成。首次complete即领取回复资格,非法完成码/响应类型/长度也释放pending并拒绝后续完成,同Tag重传可重新受理。收包TO直接来自线上头,表示报文源拥有Tag;正常响应TO=0,原requester为Tag所有者不改变此位语义(DSP0236 1.3.0表1)。 | ||
| 6 | + | ||
| 7 | +本PR包含请求头、首字节、最短错误响应及IC=1拒收修复。独立分支覆盖响应生命周期、Base能力/CRC/错误码和旧PLDM查询;完整候选4620c09另有43000四网卡44次真实应答记录,不冒充本分支已独立部署。上线前须集成共享传输层修复;完整接口代次、统一TX、资源压力及阶段2仍待完成。 | ||
| 8 | + | ||
| 9 | +## 公共查询接入 | ||
| 10 | + | ||
| 11 | +GetTID(Type=0、Command=2)查询的是BMC配置的PLDM Terminus ID,不是网卡EID。 | ||
| 12 | +GetDateTime(Type=3、Command=12)依赖!12的真实时间服务;两个命令统一在本应答器处理, | ||
| 13 | +不在网卡驱动中重复建立BMC公共业务。SP888独立驱动通过component_drivers !1469登记类型1端点。 | ||
| 14 | + | ||
| 15 | +准入必须匹配PCIe介质、来源EID、物理地址和PLDM消息类型。此前仅检查前三项会允许 | ||
| 16 | +只有NC-SI端点的来源进入PLDM公共业务;现已限制 `get_msg_type()==MCTP_MESSAGE_TYPE_PLDM`。 | ||
| 17 | +完成回复时仍核对原端点对象,撤销或替换后不能将旧应答交给新对象。 | ||
| 18 | + | ||
| 19 | +qemu_mockup的独立`gettid`、`datetime`场景可发起公共查询。GetTID请求示例为 | ||
| 20 | +`01 80 00 02`,配置TID=1时响应为`01 00 00 02 00 01`(包含MCTP类型,不含传输头)。 | ||
| 21 | +取时请求为`01 80 03 0c`;回复必须保留请求Tag/IID并置TO=0、RQ=0。 | ||
| 22 | +验证应保存真实双向报文;不能把旧`Send`发送的新请求当成应答,参考端成功不属于BMC验收。 | ||
| 23 | + | ||
| 24 | +GetTID协议允许返回0表示尚未分配;本产品启用responder的配置策略仍要求PLDM_TID为1~254。 | ||
| 25 | +仿真客户端会如实显示0及assigned=false,不把合法未分配值误判成保留值255。 | ||
| @@ -2,7 +2,7 @@ | |||
| 2 | "name": "mctpd", | 2 | "name": "mctpd", |
| 3 | "type": "application", | 3 | "type": "application", |
| 4 | "deployConfig": "framework.service", | 4 | "deployConfig": "framework.service", |
| 5 | - "version": "1.130.15", | 5 | + "version": "1.130.18", |
| 6 | "author": "Huawei", | 6 | "author": "Huawei", |
| 7 | "license": "Mulan PSL v2", | 7 | "license": "Mulan PSL v2", |
| 8 | "options": { | 8 | "options": { |
| @@ -166,6 +166,8 @@ static void register_upper_ctxs(lua_State *L, luawrap::stack_table &t) | |||
| 166 | cls.def_readwrite("pldm_type", &pldm_ctx::pldm_type); | 166 | cls.def_readwrite("pldm_type", &pldm_ctx::pldm_type); |
| 167 | cls.def_readwrite("command_code", &pldm_ctx::command_code); | 167 | cls.def_readwrite("command_code", &pldm_ctx::command_code); |
| 168 | cls.def_readwrite("rq", &pldm_ctx::rq); | 168 | cls.def_readwrite("rq", &pldm_ctx::rq); |
| 169 | + cls.def_readwrite("d", &pldm_ctx::d); | ||
| 170 | + cls.def_readwrite("completion_code", &pldm_ctx::completion_code); | ||
| 169 | cls.def_readwrite("iid", &pldm_ctx::iid); | 171 | cls.def_readwrite("iid", &pldm_ctx::iid); |
| 170 | t.set("pldm_ctx", cls); | 172 | t.set("pldm_ctx", cls); |
| 171 | }); | 173 | }); |
| @@ -284,6 +286,16 @@ static void protocol_push(lua_State *L, T *proto, const slice_t &data, std::opti | |||
| 284 | } | 286 | } |
| 285 | } | 287 | } |
| 286 | 288 | ||
| 289 | +template <typename T> | ||
| 290 | +static bool protocol_push_checked(lua_State *L, T *proto, const slice_t &data, std::optional<const protocol_ctx *> ctx, | ||
| 291 | + const uint32_t medium_type) | ||
| 292 | +{ | ||
| 293 | + if (ctx) { | ||
| 294 | + return proto->push_data(**ctx, data, medium_type); | ||
| 295 | + } | ||
| 296 | + return proto->push_data(protocol_ctx(), data, medium_type); | ||
| 297 | +} | ||
| 298 | + | ||
| 287 | template <typename T> | 299 | template <typename T> |
| 288 | static void register_protocol(lua_State *L, std::function<void(lua_class<T, protocol> &cls)> &&f) | 300 | static void register_protocol(lua_State *L, std::function<void(lua_class<T, protocol> &cls)> &&f) |
| 289 | { | 301 | { |
| @@ -298,6 +310,7 @@ static void register_protocol(lua_State *L, std::function<void(lua_class<T, prot | |||
| 298 | cls.set("set_capture_handler", c_func_wrap(L, set_capture_handler<T>)); | 310 | cls.set("set_capture_handler", c_func_wrap(L, set_capture_handler<T>)); |
| 299 | cls.set("pop", c_func_wrap(L, protocol_pop<T>)); | 311 | cls.set("pop", c_func_wrap(L, protocol_pop<T>)); |
| 300 | cls.set("push", c_func_wrap(L, protocol_push<T>)); | 312 | cls.set("push", c_func_wrap(L, protocol_push<T>)); |
| 313 | + cls.set("push_checked", c_func_wrap(L, protocol_push_checked<T>)); | ||
| 301 | cls.def_property("name", c_func_wrap(L, T::get_name), c_func_wrap(L, T::set_name)); | 314 | cls.def_property("name", c_func_wrap(L, T::get_name), c_func_wrap(L, T::set_name)); |
| 302 | f(cls); | 315 | f(cls); |
| 303 | } | 316 | } |
| @@ -54,8 +54,7 @@ struct pldm_rsp_pkg_hdr { | |||
| 54 | using pldm_req_payload_hdr = mct_payload_hdr<pldm_req_pkg_hdr>; | 54 | using pldm_req_payload_hdr = mct_payload_hdr<pldm_req_pkg_hdr>; |
| 55 | using pldm_rsp_payload_hdr = mct_payload_hdr<pldm_rsp_pkg_hdr>; | 55 | using pldm_rsp_payload_hdr = mct_payload_hdr<pldm_rsp_pkg_hdr>; |
| 56 | 56 | ||
| 57 | -const int MCTP_PLDM_HEADER_VERSION = 0; | 57 | +const int MCTP_PLDM_HEADER_VERSION = 0; |
| 58 | -const int MCTP_PLDM_COMPLETION_CODE = 0x0; | ||
| 59 | 58 | ||
| 60 | } // namespace Mctp | 59 | } // namespace Mctp |
| 61 | 60 | ||
| @@ -67,34 +66,55 @@ pldm_protocol::~pldm_protocol() | |||
| 67 | 66 | ||
| 68 | bool pldm_protocol::pop(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) | 67 | bool pldm_protocol::pop(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) |
| 69 | { | 68 | { |
| 70 | - if (data.size() < sizeof(pldm_rsp_payload_hdr)) { | 69 | + if (data.size() < sizeof(pldm_req_payload_hdr)) { |
| 71 | return false; | 70 | return false; |
| 72 | } | 71 | } |
| 73 | 72 | ||
| 74 | - const pldm_rsp_payload_hdr &hdr = *reinterpret_cast<const pldm_rsp_payload_hdr *>(data.data()); | 73 | + const pldm_req_payload_hdr &hdr = *reinterpret_cast<const pldm_req_payload_hdr *>(data.data()); |
| 75 | if (hdr.mctp_hdr.msg_type != MCTP_MESSAGE_TYPE_PLDM) { | 74 | if (hdr.mctp_hdr.msg_type != MCTP_MESSAGE_TYPE_PLDM) { |
| 76 | return false; | 75 | return false; |
| 77 | } | 76 | } |
| 77 | + if (hdr.mctp_hdr.ic) { | ||
| 78 | + debug_log(DLOG_DEBUG, "pldm_protocol: unsupported integrity check, medium=%u, size=%zu", medium_type, | ||
| 79 | + data.size()); | ||
| 80 | + return false; | ||
| 81 | + } | ||
| 82 | + if (hdr.pkg_hdr.hdr_ver != MCTP_PLDM_HEADER_VERSION || (!hdr.pkg_hdr.rq && hdr.pkg_hdr.d)) { | ||
| 83 | + return false; | ||
| 84 | + } | ||
| 85 | + const size_t header_size = hdr.pkg_hdr.rq ? sizeof(pldm_req_payload_hdr) : sizeof(pldm_rsp_payload_hdr); | ||
| 86 | + if (data.size() < header_size) { | ||
| 87 | + return false; | ||
| 88 | + } | ||
| 78 | 89 | ||
| 79 | pldm_ctx local_ctx(&ctx); | 90 | pldm_ctx local_ctx(&ctx); |
| 80 | - local_ctx.pldm_type = hdr.pkg_hdr.pldm_type; | 91 | + local_ctx.pldm_type = hdr.pkg_hdr.pldm_type; |
| 81 | - local_ctx.command_code = hdr.pkg_hdr.command_code; | 92 | + local_ctx.command_code = hdr.pkg_hdr.command_code; |
| 82 | - local_ctx.rq = hdr.pkg_hdr.rq; | 93 | + local_ctx.rq = hdr.pkg_hdr.rq; |
| 83 | - local_ctx.iid = hdr.pkg_hdr.iid; | 94 | + local_ctx.d = hdr.pkg_hdr.d; |
| 84 | - local_ctx.completion_code = hdr.pkg_hdr.completion_code; | 95 | + local_ctx.iid = hdr.pkg_hdr.iid; |
| 85 | - return protocol::pop(local_ctx, slice(data, sizeof(pldm_rsp_payload_hdr)), medium_type); | 96 | + if (!local_ctx.rq) { |
| 97 | + local_ctx.completion_code = data[sizeof(pldm_req_payload_hdr)]; | ||
| 98 | + } | ||
| 99 | + return protocol::pop(local_ctx, data.substr(header_size), medium_type); | ||
| 86 | } | 100 | } |
| 87 | 101 | ||
| 88 | bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) | 102 | bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) |
| 89 | { | 103 | { |
| 90 | pldm_ctx local_ctx = ctx.cast<pldm_ctx>(); | 104 | pldm_ctx local_ctx = ctx.cast<pldm_ctx>(); |
| 105 | + if (local_ctx.rq > 1 || local_ctx.d > 1 || (!local_ctx.rq && local_ctx.d) || local_ctx.iid > 31 || | ||
| 106 | + local_ctx.pldm_type > 63) { | ||
| 107 | + debug_log(DLOG_ERROR, "pldm_protocol: invalid TX header"); | ||
| 108 | + return false; | ||
| 109 | + } | ||
| 91 | 110 | ||
| 92 | - m_tx_buf.reserve(sizeof(pldm_req_payload_hdr) + data.size()); | 111 | + m_tx_buf.reserve(sizeof(pldm_rsp_payload_hdr) + data.size()); |
| 93 | m_tx_buf.resize(sizeof(pldm_req_payload_hdr)); | 112 | m_tx_buf.resize(sizeof(pldm_req_payload_hdr)); |
| 94 | pldm_req_payload_hdr &hdr = *reinterpret_cast<pldm_req_payload_hdr *>(&m_tx_buf[0]); | 113 | pldm_req_payload_hdr &hdr = *reinterpret_cast<pldm_req_payload_hdr *>(&m_tx_buf[0]); |
| 95 | (void)memset_s(&hdr, sizeof(hdr), 0, sizeof(hdr)); | 114 | (void)memset_s(&hdr, sizeof(hdr), 0, sizeof(hdr)); |
| 96 | 115 | ||
| 97 | hdr.pkg_hdr.rq = local_ctx.rq; | 116 | hdr.pkg_hdr.rq = local_ctx.rq; |
| 117 | + hdr.pkg_hdr.d = local_ctx.d; | ||
| 98 | hdr.pkg_hdr.iid = local_ctx.iid; | 118 | hdr.pkg_hdr.iid = local_ctx.iid; |
| 99 | 119 | ||
| 100 | hdr.pkg_hdr.hdr_ver = MCTP_PLDM_HEADER_VERSION; | 120 | hdr.pkg_hdr.hdr_ver = MCTP_PLDM_HEADER_VERSION; |
| @@ -104,6 +124,9 @@ bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uin | |||
| 104 | 124 | ||
| 105 | hdr.mctp_hdr.msg_type = MCTP_MESSAGE_TYPE_PLDM; | 125 | hdr.mctp_hdr.msg_type = MCTP_MESSAGE_TYPE_PLDM; |
| 106 | 126 | ||
| 127 | + if (!local_ctx.rq) { | ||
| 128 | + m_tx_buf.push_back(local_ctx.completion_code); | ||
| 129 | + } | ||
| 107 | m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end()); | 130 | m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end()); |
| 108 | 131 | ||
| 109 | return protocol::push(ctx, slice(m_tx_buf), medium_type); | 132 | return protocol::push(ctx, slice(m_tx_buf), medium_type); |
| @@ -29,18 +29,19 @@ struct pldm_ctx : public protocol_ctx { | |||
| 29 | bool visit(const ctx_vistor_t &h) const override | 29 | bool visit(const ctx_vistor_t &h) const override |
| 30 | { | 30 | { |
| 31 | if (!h(this, "completion_code", completion_code) || !h(this, "command_code", command_code) || | 31 | if (!h(this, "completion_code", completion_code) || !h(this, "command_code", command_code) || |
| 32 | - !h(this, "rq", rq) || !h(this, "iid", iid) || !h(this, "pldm_type", pldm_type)) { | 32 | + !h(this, "rq", rq) || !h(this, "d", d) || !h(this, "iid", iid) || !h(this, "pldm_type", pldm_type)) { |
| 33 | return false; | 33 | return false; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | return protocol_ctx::visit(h); | 36 | return protocol_ctx::visit(h); |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | - uint8_t completion_code; | 39 | + uint8_t completion_code = 0; |
| 40 | - uint8_t command_code; | 40 | + uint8_t command_code = 0; |
| 41 | - uint8_t rq; | 41 | + uint8_t rq = 0; |
| 42 | - uint8_t iid; | 42 | + uint8_t d = 0; |
| 43 | - uint8_t pldm_type; | 43 | + uint8_t iid = 0; |
| 44 | + uint8_t pldm_type = 0; | ||
| 44 | }; | 45 | }; |
| 45 | 46 | ||
| 46 | class pldm_protocol : public protocol { | 47 | class pldm_protocol : public protocol { |
| @@ -10,4 +10,9 @@ | |||
| 10 | 10 | ||
| 11 | local cfg = { CHECK_MCTP_TIMES = 3, CHECK_MCTP_TIMEOUT = 3000, CHECK_MCTP_INTERVAL = 1000 } | 11 | local cfg = { CHECK_MCTP_TIMES = 3, CHECK_MCTP_TIMEOUT = 3000, CHECK_MCTP_INTERVAL = 1000 } |
| 12 | 12 | ||
| 13 | +-- 产品验证后启用;TID 属于稳定产品配置,不能直接采用动态 MCTP EID。 | ||
| 14 | +cfg.CONFIG_MCTP_EP_RESPONDER = false | ||
| 15 | +cfg.CONFIG_MCTP_PLDM_TIME = false | ||
| 16 | +cfg.PLDM_TID = 1 | ||
| 17 | + | ||
| 13 | return cfg | 18 | return cfg |
| @@ -51,6 +51,46 @@ local E_OK = mctp_def.E_OK | |||
| 51 | 51 | ||
| 52 | local c_mctp_engine = c_class() | 52 | local c_mctp_engine = c_class() |
| 53 | 53 | ||
| 54 | +function c_mctp_engine:enable_pldm_responder() | ||
| 55 | + if not self.pldm_responder then | ||
| 56 | + self.pldm_responder = require('pldm_responder').new(self) | ||
| 57 | + end | ||
| 58 | + return self.pldm_responder | ||
| 59 | +end | ||
| 60 | + | ||
| 61 | +function c_mctp_engine:configure_pldm_responder(config) | ||
| 62 | + if self.pldm_reply_timer then | ||
| 63 | + stop_timer(self.pldm_reply_timer) | ||
| 64 | + self.pldm_reply_timer = nil | ||
| 65 | + end | ||
| 66 | + if self.pldm_responder then | ||
| 67 | + self.pldm_responder:reset() | ||
| 68 | + end | ||
| 69 | + self.pldm_responder = nil | ||
| 70 | + if config.CONFIG_MCTP_EP_RESPONDER then | ||
| 71 | + local responder = self:enable_pldm_responder() | ||
| 72 | + local ok = pcall(function() | ||
| 73 | + responder:register_base(config.PLDM_TID) | ||
| 74 | + if config.CONFIG_MCTP_PLDM_TIME then | ||
| 75 | + require('pldm_time').register(responder, self.bus) | ||
| 76 | + end | ||
| 77 | + end) | ||
| 78 | + if not ok then | ||
| 79 | + log:error('pldm_responder: invalid configuration; responder disabled') | ||
| 80 | + self.pldm_responder = nil | ||
| 81 | + return | ||
| 82 | + end | ||
| 83 | + local function expire() | ||
| 84 | + if self.pldm_responder ~= responder then | ||
| 85 | + return | ||
| 86 | + end | ||
| 87 | + responder:expire() | ||
| 88 | + self.pldm_reply_timer = c_timer.new(250, expire) | ||
| 89 | + end | ||
| 90 | + self.pldm_reply_timer = c_timer.new(250, expire) | ||
| 91 | + end | ||
| 92 | +end | ||
| 93 | + | ||
| 54 | function c_mctp_engine:pcie_binding_params_set() | 94 | function c_mctp_engine:pcie_binding_params_set() |
| 55 | self.mctp_proto:set_binding_id(MCTP_OVER_PCIE_ID) | 95 | self.mctp_proto:set_binding_id(MCTP_OVER_PCIE_ID) |
| 56 | end | 96 | end |
| @@ -233,12 +273,14 @@ function c_mctp_engine:start() | |||
| 233 | end) | 273 | end) |
| 234 | 274 | ||
| 235 | self.hw_pcie_proto:vdm_engine_reinit() | 275 | self.hw_pcie_proto:vdm_engine_reinit() |
| 276 | + self:configure_pldm_responder(require('mctp_config')) | ||
| 236 | self:register_control() | 277 | self:register_control() |
| 237 | self:start_worker(fd) | 278 | self:start_worker(fd) |
| 238 | log:notice('[System%s]mctp_engine: open mctp device success', self.system_id) | 279 | log:notice('[System%s]mctp_engine: open mctp device success', self.system_id) |
| 239 | end | 280 | end |
| 240 | 281 | ||
| 241 | function c_mctp_engine:stop() | 282 | function c_mctp_engine:stop() |
| 283 | + self:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false }) | ||
| 242 | self.pcie_read_work:stop() | 284 | self.pcie_read_work:stop() |
| 243 | self.pcie_read_work:join() | 285 | self.pcie_read_work:join() |
| 244 | self.pcie_read_work = nil | 286 | self.pcie_read_work = nil |
| @@ -260,6 +302,9 @@ function c_mctp_engine:reset() | |||
| 260 | end | 302 | end |
| 261 | 303 | ||
| 262 | function c_mctp_engine:reinit() | 304 | function c_mctp_engine:reinit() |
| 305 | + if self.pldm_responder then | ||
| 306 | + self.pldm_responder:reset() | ||
| 307 | + end | ||
| 263 | log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id) | 308 | log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id) |
| 264 | self.hw_pcie_proto:vdm_engine_reinit() | 309 | self.hw_pcie_proto:vdm_engine_reinit() |
| 265 | end | 310 | end |
| @@ -325,6 +370,9 @@ end | |||
| 325 | 370 | ||
| 326 | function c_mctp_engine:set_bmc_eid(eid, phy_addr) | 371 | function c_mctp_engine:set_bmc_eid(eid, phy_addr) |
| 327 | if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then | 372 | if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then |
| 373 | + if self.pldm_responder then | ||
| 374 | + self.pldm_responder:reset() | ||
| 375 | + end | ||
| 328 | self.bmc_eid = eid | 376 | self.bmc_eid = eid |
| 329 | self.mctp_proto.pcie_eid = eid | 377 | self.mctp_proto.pcie_eid = eid |
| 330 | self.bmc_phy_addr = phy_addr | 378 | self.bmc_phy_addr = phy_addr |
| @@ -480,6 +528,13 @@ function c_mctp_engine:rx(ctx, data, msg_type, medium_type) | |||
| 480 | if not msg_type then | 528 | if not msg_type then |
| 481 | return false | 529 | return false |
| 482 | end | 530 | end |
| 531 | + if | ||
| 532 | + msg_type == MCTP_MESSAGE_TYPE_PLDM | ||
| 533 | + and self.pldm_responder | ||
| 534 | + and self.pldm_responder:receive(ctx, data, medium_type) | ||
| 535 | + then | ||
| 536 | + return true | ||
| 537 | + end | ||
| 483 | mctp_ctx:copy_from(ctx) | 538 | mctp_ctx:copy_from(ctx) |
| 484 | local src_eid = mctp_ctx.src | 539 | local src_eid = mctp_ctx.src |
| 485 | local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type) | 540 | local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type) |
| @@ -31,5 +31,6 @@ require('test_routing_table') | |||
| 31 | require('test_eid_table') | 31 | require('test_eid_table') |
| 32 | require('test_mctp_dump') | 32 | require('test_mctp_dump') |
| 33 | require('test_discovery') | 33 | require('test_discovery') |
| 34 | +require('test_pldm_responder') | ||
| 34 | 35 | ||
| 35 | os.exit(lu.LuaUnit.run()) | 36 | os.exit(lu.LuaUnit.run()) |
| @@ -114,6 +114,7 @@ function TestMctpDiscovery:test_mctp_discovery_success() | |||
| 114 | end) | 114 | end) |
| 115 | 115 | ||
| 116 | self.mctp_discovery:smbus_set_eid_to_endpoint( | 116 | self.mctp_discovery:smbus_set_eid_to_endpoint( |
| 117 | + 'discovery-success', | ||
| 117 | target_eid, | 118 | target_eid, |
| 118 | ref_chip, | 119 | ref_chip, |
| 119 | medium_type, | 120 | medium_type, |
| @@ -158,6 +159,7 @@ function TestMctpDiscovery:test_mctp_discovery_failed() | |||
| 158 | local ref_chip = { path = path } | 159 | local ref_chip = { path = path } |
| 159 | 160 | ||
| 160 | self.mctp_discovery:smbus_set_eid_to_endpoint( | 161 | self.mctp_discovery:smbus_set_eid_to_endpoint( |
| 162 | + 'discovery-failed', | ||
| 161 | target_eid, | 163 | target_eid, |
| 162 | ref_chip, | 164 | ref_chip, |
| 163 | medium_type, | 165 | medium_type, |
| @@ -9,7 +9,7 @@ | |||
| 9 | -- See the Mulan PSL v2 for more details. | 9 | -- See the Mulan PSL v2 for more details. |
| 10 | 10 | ||
| 11 | local lu = require('luaunit') | 11 | local lu = require('luaunit') |
| 12 | --- local mctp = require('mctp.core') | 12 | +local mctp = require('mctp.core') |
| 13 | local mctp_def = require('mctp_def') | 13 | local mctp_def = require('mctp_def') |
| 14 | local utils = require('mc.utils') | 14 | local utils = require('mc.utils') |
| 15 | local c_mctp_engine = require('mctp_engine') | 15 | local c_mctp_engine = require('mctp_engine') |
| @@ -46,3 +46,89 @@ function TestEndpointPLDM:test_request_and_send() | |||
| 46 | ret, _ = pcall(self.send, self, req_bin, send_ctx) | 46 | ret, _ = pcall(self.send, self, req_bin, send_ctx) |
| 47 | lu.assertEquals(ret, true) | 47 | lu.assertEquals(ret, true) |
| 48 | end | 48 | end |
| 49 | + | ||
| 50 | +local function pldm_peer(medium) | ||
| 51 | + local engine = c_mctp_engine.new(nil, 1) | ||
| 52 | + engine.bmc_eid, engine.smbus_bmc_eid = 11, 11 | ||
| 53 | + local ep = c_pldm_endpoint.new(engine, 0x2000, 9, medium, mctp_def.ENDPOINT_IS_DISCOVERY) | ||
| 54 | + if medium == mctp_def.PHY_MEDIUM_SMBUS then | ||
| 55 | + ep.pldm_ctx = ep.smbus_pldm_ctx | ||
| 56 | + ep:smbus_ctx_set() | ||
| 57 | + end | ||
| 58 | + local ctx = mctp.mctp_ctx.new() | ||
| 59 | + ctx:set_prev(ep.ctx) | ||
| 60 | + ctx.src, ctx.dest, ctx.tag = 9, 11, 0 | ||
| 61 | + local proto = mctp.pldm_protocol.new() | ||
| 62 | + local observations = { received = 0 } | ||
| 63 | + proto:set_rx_handler(function(incoming, data) | ||
| 64 | + observations.received = observations.received + 1 | ||
| 65 | + return ep:rx(incoming, data) | ||
| 66 | + end) | ||
| 67 | + return engine, ep, ctx, proto, observations | ||
| 68 | +end | ||
| 69 | + | ||
| 70 | +function TestEndpointPLDM:test_received_datagram_does_not_change_ordinary_request() | ||
| 71 | + for _, medium in ipairs({ mctp_def.PHY_MEDIUM_PCI, mctp_def.PHY_MEDIUM_SMBUS }) do | ||
| 72 | + for _, method in ipairs({ 'send', 'request' }) do | ||
| 73 | + local engine, ep, ctx, proto = pldm_peer(medium) | ||
| 74 | + ctx.tag_owner = true | ||
| 75 | + proto:pop('\x01\xc7\x03\x0c', ctx, medium) | ||
| 76 | + local sent | ||
| 77 | + -- 独立真实 codec 截取 PLDM 帧,避免 TX 继续进入物理介质封装。 | ||
| 78 | + engine.pldm_proto = mctp.pldm_protocol.new() | ||
| 79 | + engine.pldm_proto:set_tx_handler(function(_, data) | ||
| 80 | + sent = data | ||
| 81 | + return true | ||
| 82 | + end) | ||
| 83 | + local req_ctx = { CommandCode = '5', PldmType = '0' } | ||
| 84 | + if method == 'send' then | ||
| 85 | + ep:send(nil, '\x03', req_ctx) | ||
| 86 | + else | ||
| 87 | + ep:request(nil, '\x03', 0, req_ctx, {}) | ||
| 88 | + end | ||
| 89 | + lu.assertEquals(sent, '\x01\x81\x00\x05\x03') | ||
| 90 | + end | ||
| 91 | + end | ||
| 92 | +end | ||
| 93 | + | ||
| 94 | +function TestEndpointPLDM:test_only_response_direction_can_complete_request() | ||
| 95 | + for _, medium in ipairs({ mctp_def.PHY_MEDIUM_PCI, mctp_def.PHY_MEDIUM_SMBUS }) do | ||
| 96 | + for _, case in ipairs({ | ||
| 97 | + { owner = true, packet = '\x01\x81\x00\x05\x03', expected = 'waiting' }, | ||
| 98 | + { owner = true, packet = '\x01\xc1\x00\x05\x03', expected = 'waiting' }, | ||
| 99 | + { owner = true, packet = '\x01\x01\x00\x05\x00\x03', expected = 'waiting' }, | ||
| 100 | + { owner = false, packet = '\x01\x81\x00\x05\x03', expected = 'waiting' }, | ||
| 101 | + { owner = false, packet = '\x01\x01\x00\x05\x00\x03', expected = '\x03' }, | ||
| 102 | + }) do | ||
| 103 | + local engine, ep, ctx, proto, observations = pldm_peer(medium) | ||
| 104 | + local key = ep:prepare_mctp_request(1) | ||
| 105 | + local pending = { co = coroutine.create(function() end), rsp = 'waiting' } | ||
| 106 | + engine.rsp_wait_list[key] = pending | ||
| 107 | + local transport = mctp.mctp_protocol.new() | ||
| 108 | + transport.pcie_eid, transport.smbus_eid = 11, 11 | ||
| 109 | + transport:add_children(proto) | ||
| 110 | + local flags = 0xc0 | (case.owner and 8 or 0) | ctx.tag | ||
| 111 | + transport:pop(string.char(1, 11, 9, flags) .. case.packet, ep.ctx, medium) | ||
| 112 | + lu.assertEquals(observations.received, 1) | ||
| 113 | + lu.assertEquals(pending.rsp, case.expected) | ||
| 114 | + lu.assertIsNil(pending.err) | ||
| 115 | + lu.assertEquals(engine.rsp_wait_list[key], pending) | ||
| 116 | + end | ||
| 117 | + end | ||
| 118 | +end | ||
| 119 | + | ||
| 120 | +function TestEndpointPLDM:test_invalid_request_fields_fail_before_waiting() | ||
| 121 | + for _, method in ipairs({ 'send', 'request' }) do | ||
| 122 | + for _, typ in ipairs({ '64', '-1', '0.5', 'bad' }) do | ||
| 123 | + local _, ep = pldm_peer(mctp_def.PHY_MEDIUM_PCI) | ||
| 124 | + local ctx = { PldmType = typ, CommandCode = '1' } | ||
| 125 | + local before = ep.req_total_count | ||
| 126 | + if method == 'send' then | ||
| 127 | + lu.assertError(ep.send, ep, nil, '', ctx) | ||
| 128 | + else | ||
| 129 | + lu.assertError(ep.request, ep, nil, '', 1, ctx, {}) | ||
| 130 | + end | ||
| 131 | + lu.assertEquals(ep.req_total_count, before) | ||
| 132 | + end | ||
| 133 | + end | ||
| 134 | +end | ||
| @@ -117,12 +117,121 @@ function TestMctpPldm:test_get_wwpn_rsp() | |||
| 117 | }) | 117 | }) |
| 118 | self.mctp_proto.pcie_eid = 10 | 118 | self.mctp_proto.pcie_eid = 10 |
| 119 | local data = self:pop(d, nil, mctp_def.PHY_MEDIUM_PCI) | 119 | local data = self:pop(d, nil, mctp_def.PHY_MEDIUM_PCI) |
| 120 | - if not data then | 120 | + lu.assertNotNil(data) |
| 121 | - return | ||
| 122 | - end | ||
| 123 | local v = net_pldm_pkgs.wwpn_rsp(data) | 121 | local v = net_pldm_pkgs.wwpn_rsp(data) |
| 124 | lu.assertNotNil(v) | 122 | lu.assertNotNil(v) |
| 125 | lu.assertEquals(v.wwpn, '\x21\x00\xC4\x44\x7D\x06\xF2\x68') | 123 | lu.assertEquals(v.wwpn, '\x21\x00\xC4\x44\x7D\x06\xF2\x68') |
| 126 | lu.assertEquals(v.manufacture_id, 0x07db) | 124 | lu.assertEquals(v.manufacture_id, 0x07db) |
| 127 | lu.assertEquals(v.command_id, 0x02) | 125 | lu.assertEquals(v.command_id, 0x02) |
| 128 | end | 126 | end |
| 127 | + | ||
| 128 | +local function decode_pldm(packet) | ||
| 129 | + local proto = mctp.pldm_protocol.new() | ||
| 130 | + local received | ||
| 131 | + proto:set_rx_handler(function(ctx, data) | ||
| 132 | + received = { ctx = ctx:all(), data = data } | ||
| 133 | + return true | ||
| 134 | + end) | ||
| 135 | + proto:pop(packet, nil, mctp_def.PHY_MEDIUM_PCI) | ||
| 136 | + return received | ||
| 137 | +end | ||
| 138 | + | ||
| 139 | +function TestMctpPldm:test_empty_request_is_not_a_short_response() | ||
| 140 | + local received = decode_pldm('\x01\x87\x03\x0c') | ||
| 141 | + lu.assertNotNil(received) | ||
| 142 | + lu.assertEquals(received.data, '') | ||
| 143 | + lu.assertEquals(received.ctx.rq, 1) | ||
| 144 | + lu.assertEquals(received.ctx.iid, 7) | ||
| 145 | + lu.assertEquals(received.ctx.pldm_type, 3) | ||
| 146 | + lu.assertEquals(received.ctx.command_code, 12) | ||
| 147 | +end | ||
| 148 | + | ||
| 149 | +function TestMctpPldm:test_request_keeps_first_payload_byte() | ||
| 150 | + local received = decode_pldm('\x01\x8f\x00\x05\x03\xf0\xf0\xf1\x00') | ||
| 151 | + lu.assertNotNil(received) | ||
| 152 | + lu.assertEquals(received.data, '\x03\xf0\xf0\xf1\x00') | ||
| 153 | +end | ||
| 154 | + | ||
| 155 | +function TestMctpPldm:test_datagram_flag_is_preserved() | ||
| 156 | + local received = decode_pldm('\x01\xc7\x03\x0c') | ||
| 157 | + lu.assertNotNil(received) | ||
| 158 | + lu.assertEquals(received.ctx.d, 1) | ||
| 159 | +end | ||
| 160 | + | ||
| 161 | +function TestMctpPldm:test_short_error_response_keeps_completion_code() | ||
| 162 | + local received = decode_pldm('\x01\x07\x03\x0c\x8a') | ||
| 163 | + lu.assertNotNil(received) | ||
| 164 | + lu.assertEquals(received.ctx.completion_code, 0x8a) | ||
| 165 | + lu.assertEquals(received.data, '') | ||
| 166 | +end | ||
| 167 | + | ||
| 168 | +function TestMctpPldm:test_reserved_bit_is_ignored_on_receive() | ||
| 169 | + -- DSP0240 1.1.0 §5.2:保留位发送为零、接收忽略。 | ||
| 170 | + local received = decode_pldm('\x01\xa7\x03\x0c') | ||
| 171 | + lu.assertNotNil(received) | ||
| 172 | + lu.assertEquals(received.ctx.iid, 7) | ||
| 173 | + lu.assertEquals(received.data, '') | ||
| 174 | +end | ||
| 175 | + | ||
| 176 | +function TestMctpPldm:test_invalid_headers_are_not_delivered() | ||
| 177 | + for _, packet in ipairs({ | ||
| 178 | + '\x01\x87\x03', -- 缺少命令 | ||
| 179 | + '\x01\x07\x03\x0c', -- 响应缺少完成码 | ||
| 180 | + '\x01\x87\x43\x0c\x00', -- Hdr Ver 为类型字节高2位,0x43 表示版本1/类型3(DSP0240 表4) | ||
| 181 | + '\x01\x47\x03\x0c\x00', -- Datagram 不能用于响应 | ||
| 182 | + '\x81\x87\x03\x0c\x00', -- 此路径未实现消息完整性校验 | ||
| 183 | + }) do | ||
| 184 | + lu.assertNil(decode_pldm(packet)) | ||
| 185 | + end | ||
| 186 | +end | ||
| 187 | + | ||
| 188 | +function TestMctpPldm:test_response_encoding_includes_completion_code() | ||
| 189 | + local proto = mctp.pldm_protocol.new() | ||
| 190 | + local ctx = mctp.pldm_ctx.new() | ||
| 191 | + ctx.rq, ctx.iid, ctx.pldm_type, ctx.command_code = 0, 7, 3, 12 | ||
| 192 | + ctx.completion_code = 0x8a | ||
| 193 | + local sent | ||
| 194 | + proto:set_tx_handler(function(_, data) | ||
| 195 | + sent = data | ||
| 196 | + return true | ||
| 197 | + end) | ||
| 198 | + proto:push('', ctx, mctp_def.PHY_MEDIUM_PCI) | ||
| 199 | + lu.assertEquals(sent, '\x01\x07\x03\x0c\x8a') | ||
| 200 | +end | ||
| 201 | + | ||
| 202 | +function TestMctpPldm:test_datagram_encoding() | ||
| 203 | + local proto = mctp.pldm_protocol.new() | ||
| 204 | + local ctx = mctp.pldm_ctx.new() | ||
| 205 | + ctx.rq, ctx.d, ctx.iid, ctx.pldm_type, ctx.command_code = 1, 1, 7, 3, 12 | ||
| 206 | + local sent | ||
| 207 | + proto:set_tx_handler(function(_, data) | ||
| 208 | + sent = data | ||
| 209 | + return true | ||
| 210 | + end) | ||
| 211 | + proto:push('\x03', ctx, mctp_def.PHY_MEDIUM_PCI) | ||
| 212 | + lu.assertEquals(sent, '\x01\xc7\x03\x0c\x03') | ||
| 213 | +end | ||
| 214 | + | ||
| 215 | +function TestMctpPldm:test_invalid_context_never_transmits() | ||
| 216 | + for _, invalid in ipairs({ | ||
| 217 | + { rq = 2 }, | ||
| 218 | + { d = 2 }, | ||
| 219 | + { rq = 0, d = 1 }, | ||
| 220 | + { iid = 32 }, | ||
| 221 | + { pldm_type = 64 }, | ||
| 222 | + }) do | ||
| 223 | + local proto = mctp.pldm_protocol.new() | ||
| 224 | + local ctx = mctp.pldm_ctx.new() | ||
| 225 | + ctx.rq = 1 | ||
| 226 | + for field, value in pairs(invalid) do | ||
| 227 | + ctx[field] = value | ||
| 228 | + end | ||
| 229 | + local sent = false | ||
| 230 | + proto:set_tx_handler(function() | ||
| 231 | + sent = true | ||
| 232 | + return true | ||
| 233 | + end) | ||
| 234 | + proto:push('', ctx, mctp_def.PHY_MEDIUM_PCI) | ||
| 235 | + lu.assertFalse(sent) | ||
| 236 | + end | ||
| 237 | +end | ||
| @@ -0,0 +1,366 @@ | |||
| 1 | +-- Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | +-- openUBMC is licensed under Mulan PSL v2. | ||
| 3 | +-- You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 4 | +-- You may obtain a copy of Mulan PSL v2 at: | ||
| 5 | +-- http://license.coscl.org.cn/MulanPSL2 | ||
| 6 | +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 7 | +-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 8 | +-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 9 | +-- See the Mulan PSL v2 for more details. | ||
| 10 | + | ||
| 11 | +-- SPDX-License-Identifier: MulanPSL-2.0 | ||
| 12 | +local lu = require('luaunit') | ||
| 13 | +local mctp = require('mctp') | ||
| 14 | +local Engine = require('mctp_engine') | ||
| 15 | +local def = require('mctp_def') | ||
| 16 | +local timer = require('skynet').timer | ||
| 17 | + | ||
| 18 | +TestPldmResponder = {} | ||
| 19 | + | ||
| 20 | +function TestPldmResponder:setup() | ||
| 21 | + self.original_timer, self.timers = self.original_timer or timer.new, {} | ||
| 22 | + self.engine = Engine.new(nil, 1) | ||
| 23 | + self.engine:init() | ||
| 24 | + self.engine.endpoints:init() | ||
| 25 | + self.engine:set_bmc_eid(11, 0x300) | ||
| 26 | + -- ep_mctp 构造时 htons(0x20)=0x2000;收包上下文使用转换后的地址。 | ||
| 27 | + self.peer = self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI) | ||
| 28 | + lu.assertEquals(self.peer.phy_addr, 0x2000) | ||
| 29 | + self.frames, self.done = {}, {} | ||
| 30 | + self.engine.hw_pcie_proto:set_tx_handler(function() | ||
| 31 | + return true | ||
| 32 | + end) | ||
| 33 | + self.engine.pcie_proto:set_capture_handler(function(is_push, _, data) | ||
| 34 | + if is_push then | ||
| 35 | + self.frames[#self.frames + 1] = data | ||
| 36 | + end | ||
| 37 | + end) | ||
| 38 | + self.physical = mctp.pcie_vdm_ctx.new() | ||
| 39 | + self.physical.remote_phy_addr, self.physical.bmc_phy_addr = 0x2000, 0x300 | ||
🔵 Low Priority 改动行:test/unit/test_pldm_responder.lua:30 用 self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI) 创建已被登记的对端,而第 41 行构造入向上下文时用的是 self.physical.remote_phy_addr = 0x2000;142/161 行又把「地址变化」表达成 0x2100。 已知同一仓库的调用约定(test/unit/test_libmctp/test_mctp_engine.lua:149-152)是 create_endpoint(phy_addr, eid, msg_type),即第一个参数就是完整 phy_addr(那里传 1688 并且注释明确说「测试数据的物理地址是 1688」)。按此约定,对端的 phy_addr 是 0x20,而所有入向路由宣称的远端地址是 0x2000,两者并不相等。 失败模式:两条互斥的断言只能有一条成立——若 responder 在收包时用「入向 remote_phy_addr == 端点 phy_addr」做校验,则 test_expiry_unregister_and_address_changes_cancel_reply:132 的 baseline(#self.done == 1)会被拒绝而失败;若不校验,则 test_wrong_direction_datagram_and_unknown_source_are_not_answered:161-165 的 0x2100 用例会被正常受理而使 #self.done == 0 失败。两者都通过的唯一解释是 0x20 被隐式左移 8 位当作总线号(0x20<<8 == 0x2000),这从未在用例里写明;若无此隐式转换,地址撤销/地址变更这两组断言就是「因错误原因通过」或干脆未覆盖目标行为。 建议:统一对端端点 phy_addr 与入向上下文 remote_phy_addr 的取值(都用 0x2000,或显式注明总线号到 phy_addr 的换算),使「地址变更撤销回复」的断言确实由 0x2000→0x2100 的地址变化驱动。 ![]() ![]() Incorrect? | |||
| 40 | + self.incoming = mctp.mctp_ctx.new() | ||
| 41 | + self.incoming:set_prev(self.physical) | ||
| 42 | + self.incoming.src, self.incoming.dest = 9, 11 | ||
| 43 | + self.incoming.tag_owner, self.incoming.tag = true, 1 | ||
| 44 | + timer.new = function(delay, fn) | ||
| 45 | + self.timers[#self.timers + 1] = fn | ||
| 46 | + return { stop = function() end } | ||
| 47 | + end | ||
| 48 | +end | ||
| 49 | + | ||
| 50 | +function TestPldmResponder:teardown() | ||
| 51 | + timer.new = self.original_timer | ||
| 52 | + self.original_timer = nil | ||
| 53 | +end | ||
| 54 | + | ||
| 55 | +function TestPldmResponder:test_idle_expiry_and_old_configuration_callback_are_isolated() | ||
| 56 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1 }) | ||
| 57 | + local old, calls = self.engine.pldm_responder, 0 | ||
| 58 | + old.expire = function() | ||
| 59 | + calls = calls + 1 | ||
| 60 | + end | ||
| 61 | + self.timers[1]() | ||
| 62 | + lu.assertEquals(calls, 1) | ||
| 63 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1 }) | ||
| 64 | + self.timers[2]() | ||
| 65 | + lu.assertEquals(calls, 1) | ||
| 66 | + lu.assertEquals(#self.timers, 3) | ||
🔵 Low Priority 改动行:test/unit/test_pldm_responder.lua:65 先调用 self.timers3,第 66 行才断言 #self.timers == 3。 问题一(顺序):若 configure_pldm_responder({CONFIG_MCTP_EP_RESPONDER=false}) 不新建定时器(关闭时只 stop 已有定时器是最自然的实现),第 65 行会以 "attempt to call a nil value" 直接抛错,断言消息完全无法指向真实原因;应先断言数量再解引用。 问题二(断言空转):关闭分支里 timers3 之后没有任何断言:既没有复核 calls 仍为 1(旧回调未被驱动),也没有断言引擎已不再应答(例如再发一次 GetTID 不应产生 frame),只断言了「定时器数量是 3」。同理第 62-63 行只证明了旧 expire 没被调用,却没有证明 timers[2] 触发的是新 responder 的 expire —— 如果重新配置后定时器回调什么都没驱动(死定时器),本用例依旧全绿。 失败模式:闲置回收/配置重绑定的回归(重新配置后巡检不再驱动新 responder,或关闭后旧定时器仍在跑)无法被本用例捕获,与用例名称所声称的隔离能力不符。 建议:把数量断言放到调用之前,并在触发 timers[3] 后复核旧回调未被再次调用、且关闭后引擎不再产生应答帧。 ![]() ![]() Incorrect? | |||
| 67 | + local fresh = 0 | ||
| 68 | + self.engine.pldm_responder.expire = function() | ||
| 69 | + fresh = fresh + 1 | ||
| 70 | + end | ||
| 71 | + self.timers[3]() | ||
| 72 | + lu.assertEquals(fresh, 1) | ||
| 73 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false }) | ||
| 74 | + lu.assertEquals(#self.timers, 4) | ||
| 75 | + self.timers[4]() | ||
| 76 | + lu.assertEquals({ calls, fresh, #self.timers }, { 1, 1, 4 }) | ||
| 77 | + self:receive('\x01\x87\x00\x02') | ||
| 78 | + lu.assertEquals(#self.frames, 0) | ||
| 79 | +end | ||
| 80 | + | ||
| 81 | +function TestPldmResponder:test_ncsi_registration_does_not_authorize_public_pldm_commands() | ||
| 82 | + self.engine.endpoints.endpoints = {} | ||
| 83 | + self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI) | ||
| 84 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 17 }) | ||
| 85 | + self:receive('\x01\x87\x00\x02') | ||
| 86 | + lu.assertEquals(#self.frames, 0) | ||
| 87 | + self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI) | ||
| 88 | + self:receive('\x01\x87\x00\x02') | ||
| 89 | + lu.assertEquals(#self.frames, 1) | ||
| 90 | + lu.assertEquals(self.frames[1]:sub(13, 22), '\x01\x09\x0b\xc1\x01\x07\x00\x02\x00\x11') | ||
| 91 | +end | ||
| 92 | + | ||
| 93 | +function TestPldmResponder:test_public_reply_is_cancelled_when_only_ncsi_endpoint_remains() | ||
| 94 | + self:enable() | ||
| 95 | + self:receive() | ||
| 96 | + lu.assertEquals(#self.done, 1) | ||
| 97 | + self.engine.endpoints.endpoints = {} | ||
| 98 | + self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI) | ||
| 99 | + lu.assertFalse(self.done[1](0, 'late')) | ||
| 100 | + lu.assertEquals(#self.frames, 0) | ||
| 101 | +end | ||
| 102 | + | ||
| 103 | +function TestPldmResponder:test_invalid_product_tid_keeps_responder_disabled() | ||
| 104 | + for _, tid in ipairs({ false, '1', 0, 255, 1.5 }) do | ||
| 105 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = tid }) | ||
| 106 | + lu.assertIsNil(self.engine.pldm_responder) | ||
| 107 | + lu.assertIsNil(self.engine.pldm_reply_timer) | ||
| 108 | + end | ||
| 109 | + local saved = package.loaded.pldm_time | ||
| 110 | + package.loaded.pldm_time = { | ||
| 111 | + register = function() | ||
| 112 | + error('time adapter unavailable') | ||
| 113 | + end, | ||
| 114 | + } | ||
| 115 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1, CONFIG_MCTP_PLDM_TIME = true }) | ||
| 116 | + package.loaded.pldm_time = saved | ||
| 117 | + lu.assertIsNil(self.engine.pldm_responder) | ||
| 118 | + lu.assertIsNil(self.engine.pldm_reply_timer) | ||
| 119 | +end | ||
| 120 | + | ||
| 121 | +function TestPldmResponder:test_registered_request_uses_original_route_once() | ||
| 122 | + self:enable() | ||
| 123 | + self.engine.pldm_proto:pop('\x01\x87\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI) | ||
| 124 | + self.incoming.tag = 6 | ||
| 125 | + self.engine.pldm_proto:pop('\x01\x89\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI) | ||
| 126 | + lu.assertEquals(#self.done, 2) | ||
| 127 | + self.incoming, self.physical = nil, nil | ||
| 128 | + collectgarbage('collect') | ||
| 129 | + -- 后到请求先完成,原请求仍必须保留 IID=7 / Tag=1。 | ||
| 130 | + self.done[2](0, 'second') | ||
| 131 | + self.done[1](0, 'first') | ||
| 132 | + lu.assertEquals(#self.frames, 2) | ||
| 133 | + lu.assertEquals(self.frames[1]:sub(13, 21), '\x01\x09\x0b\xc6\x01\x09\x03\x0c\x00') | ||
| 134 | + lu.assertEquals(self.frames[2]:sub(13, 21), '\x01\x09\x0b\xc1\x01\x07\x03\x0c\x00') | ||
| 135 | + lu.assertEquals(self.frames[2]:sub(22, 26), 'first') | ||
| 136 | + lu.assertFalse(self.done[1](0, 'again')) | ||
| 137 | + lu.assertEquals(#self.frames, 2) | ||
| 138 | +end | ||
| 139 | + | ||
| 140 | +function TestPldmResponder:descriptor(pldm_type, command, version, max_rsp, dispatch) | ||
| 141 | + return { | ||
| 142 | + pldm_type = pldm_type, | ||
| 143 | + command = command, | ||
| 144 | + version = version, | ||
| 145 | + min_req = 0, | ||
| 146 | + max_req = 0, | ||
| 147 | + max_rsp = max_rsp, | ||
| 148 | + timeout_ms = 50, | ||
| 149 | + dispatch = dispatch, | ||
| 150 | + } | ||
| 151 | +end | ||
| 152 | + | ||
| 153 | +function TestPldmResponder:enable() | ||
| 154 | + local responder = self.engine:enable_pldm_responder() | ||
| 155 | + self.now = 100 | ||
| 156 | + responder.now = function() | ||
| 157 | + return self.now | ||
| 158 | + end | ||
| 159 | + local registration = responder:register(self:descriptor(3, 12, 0xF1F0F000, 7, function(_, complete) | ||
| 160 | + self.done[#self.done + 1] = complete | ||
| 161 | + end)) | ||
| 162 | + return responder, registration | ||
| 163 | +end | ||
| 164 | + | ||
| 165 | +function TestPldmResponder:receive(packet) | ||
| 166 | + self.engine.pldm_proto:pop(packet or '\x01\x87\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI) | ||
| 167 | +end | ||
| 168 | + | ||
| 169 | +function TestPldmResponder:test_expiry_unregister_and_address_changes_cancel_reply() | ||
| 170 | + for _, action in ipairs({ 'expiry', 'unregister', 'address', 'reinit', 'peer' }) do | ||
| 171 | + self:setup() | ||
| 172 | + local responder, registration = self:enable() | ||
| 173 | + self:receive() | ||
| 174 | + lu.assertEquals(#self.done, 1) | ||
| 175 | + if action == 'expiry' then | ||
| 176 | + self.now = 150 | ||
| 177 | + elseif action == 'unregister' then | ||
| 178 | + responder:unregister(registration) | ||
| 179 | + elseif action == 'address' then | ||
| 180 | + self.engine:set_bmc_eid(12, 0x300) | ||
| 181 | + elseif action == 'reinit' then | ||
| 182 | + self.engine:reinit() | ||
| 183 | + else | ||
| 184 | + self.peer.phy_addr = 0x2100 | ||
| 185 | + end | ||
| 186 | + lu.assertFalse(self.done[1](0, 'late')) | ||
| 187 | + lu.assertEquals(#self.frames, 0) | ||
| 188 | + lu.assertEquals(responder.count, 0) | ||
| 189 | + end | ||
| 190 | +end | ||
| 191 | + | ||
| 192 | +function TestPldmResponder:test_wrong_direction_datagram_and_unknown_source_are_not_answered() | ||
| 193 | + self:enable() | ||
| 194 | + self:receive('\x01\xc7\x03\x0c') | ||
| 195 | + self.incoming.tag_owner = false | ||
| 196 | + self:receive() | ||
| 197 | + self.incoming.tag_owner = true | ||
| 198 | + self.incoming.src = 10 | ||
| 199 | + self:receive() | ||
| 200 | + self.incoming.src, self.incoming.dest = 9, 12 | ||
| 201 | + self:receive() | ||
| 202 | + self.incoming.dest = 11 | ||
| 203 | + self.physical.remote_phy_addr = 0x2100 | ||
| 204 | + self:receive() | ||
| 205 | + self.physical.remote_phy_addr, self.physical.bmc_phy_addr = 0x2000, 0x400 | ||
| 206 | + self:receive() | ||
| 207 | + lu.assertEquals(#self.done, 0) | ||
| 208 | + lu.assertEquals(#self.frames, 0) | ||
| 209 | +end | ||
| 210 | + | ||
| 211 | +function TestPldmResponder:test_duplicate_tag_does_not_replace_live_request() | ||
| 212 | + local responder = self:enable() | ||
| 213 | + for tag = 0, 7 do | ||
| 214 | + self.incoming.tag = tag | ||
| 215 | + self:receive() | ||
| 216 | + end | ||
| 217 | + self:receive('\x01\x88\x03\x0c') | ||
| 218 | + lu.assertEquals(#self.done, 8) | ||
| 219 | + lu.assertEquals(responder.count, 8) | ||
| 220 | + self.now = 150 | ||
| 221 | + responder:expire() | ||
| 222 | + lu.assertEquals(responder.count, 0) | ||
| 223 | + self:receive() | ||
| 224 | + lu.assertEquals(#self.done, 9) | ||
| 225 | + lu.assertFalse(self.done[8](0, 'old')) | ||
| 226 | + lu.assertEquals(responder.count, 1) | ||
| 227 | + lu.assertTrue(self.done[9](0, 'new')) | ||
| 228 | +end | ||
| 229 | + | ||
| 230 | +function TestPldmResponder:test_length_error_unknown_command_and_type_reply_with_completion_only() | ||
| 231 | + self:enable() | ||
| 232 | + for _, case in ipairs({ | ||
| 233 | + { '\x01\x87\x03\x0c\x00', 3 }, | ||
| 234 | + { '\x01\x87\x03\x0c' .. string.rep('x', 4093), 3 }, | ||
| 235 | + { '\x01\x87\x03\xff', 5 }, | ||
| 236 | + { '\x01\x87\x3f\x0c', 0x20 }, | ||
| 237 | + }) do | ||
| 238 | + self:receive(case[1]) | ||
| 239 | + lu.assertEquals(self.frames[#self.frames]:byte(21), case[2]) | ||
| 240 | + end | ||
| 241 | + lu.assertEquals(#self.frames, 4) | ||
| 242 | + lu.assertEquals(#self.done, 0) | ||
| 243 | +end | ||
| 244 | + | ||
| 245 | +function TestPldmResponder:test_response_length_and_exception_cannot_leak_payload() | ||
| 246 | + local responder = self:enable() | ||
| 247 | + self:receive() | ||
| 248 | + lu.assertFalse(self.done[1](0, string.rep('x', 8))) | ||
| 249 | + lu.assertEquals(#self.frames, 0) | ||
| 250 | + lu.assertFalse(self.done[1](0, 'late')) | ||
| 251 | + lu.assertEquals(responder.count, 0) | ||
| 252 | + self:receive() | ||
| 253 | + lu.assertTrue(self.done[2](0, 'ok')) | ||
| 254 | + responder:register(self:descriptor(3, 13, 0xF1F0F000, 0, function() | ||
| 255 | + error('private service detail') | ||
| 256 | + end)) | ||
| 257 | + self:receive('\x01\x87\x03\x0d') | ||
| 258 | + lu.assertEquals(self.frames[2]:byte(21), 1) | ||
| 259 | + lu.assertEquals(responder.count, 0) | ||
| 260 | +end | ||
| 261 | + | ||
| 262 | +function TestPldmResponder:test_base_tid_and_capabilities_follow_registration() | ||
| 263 | + local responder, registration = self:enable() | ||
| 264 | + responder:register_base(42) | ||
| 265 | + self:receive('\x01\x87\x00\x02') | ||
| 266 | + lu.assertEquals(self.frames[1]:sub(21, 22), '\x00\x2a') | ||
| 267 | + self:receive('\x01\x87\x00\x04') | ||
| 268 | + lu.assertEquals(self.frames[2]:sub(21, 29), '\x00\x09' .. string.rep('\x00', 7)) | ||
| 269 | + self:receive('\x01\x87\x00\x05\x00\x00\xf0\xf1\xf1') | ||
| 270 | + lu.assertEquals(self.frames[3]:sub(21, 53), '\x00\x3c' .. string.rep('\x00', 31)) | ||
| 271 | + responder:unregister(registration) | ||
| 272 | + self:receive('\x01\x87\x00\x04') | ||
| 273 | + lu.assertEquals(self.frames[4]:sub(21, 29), '\x00\x01' .. string.rep('\x00', 7)) | ||
| 274 | + self.engine:set_bmc_eid(12, 0x300) | ||
| 275 | + self.incoming.dest = 12 | ||
| 276 | + self:receive('\x01\x87\x00\x02') | ||
| 277 | + lu.assertEquals(self.frames[5]:sub(21, 22), '\x00\x2a') | ||
| 278 | +end | ||
| 279 | + | ||
| 280 | +function TestPldmResponder:test_base_version_ignores_first_handle_and_validates_next() | ||
| 281 | + local responder = self:enable() | ||
| 282 | + responder:register_base(42) | ||
| 283 | + -- DSP0240:GetFirstPart 必须忽略 DataTransferHandle。 | ||
| 284 | + self:receive('\x01\x87\x00\x03\xff\xff\xff\xff\x01\x00') | ||
| 285 | + lu.assertEquals(self.frames[1]:sub(21, 30), '\x00\x00\x00\x00\x00\x05\x00\xf0\xf1\xf1') | ||
| 286 | + lu.assertEquals(self.frames[1]:sub(31, 34), '\xba\xbe\x9d\x53') | ||
| 287 | + self:receive('\x01\x87\x00\x03\x00\x00\x00\x00\x01\x03') | ||
| 288 | + local version = self.frames[2]:sub(27, 30) | ||
| 289 | + lu.assertEquals(version, '\x00\xf0\xf0\xf1') | ||
| 290 | + self:receive('\x01\x87\x00\x05\x03' .. version) | ||
| 291 | + lu.assertEquals(self.frames[3]:sub(21, 53), '\x00\x00\x10' .. string.rep('\x00', 30)) | ||
| 292 | + for _, case in ipairs({ | ||
| 293 | + { '\x03\x00\x00\x00\x00\x00\x00', 0x80 }, | ||
| 294 | + { '\x03\x00\x00\x00\x00\x02\x00', 0x81 }, | ||
| 295 | + { '\x03\x00\x00\x00\x00\x01\x3f', 0x83 }, | ||
| 296 | + { '\x05\x3f\x00\xf0\xf0\xf1', 0x83 }, | ||
| 297 | + { '\x05\x03\x01\xf0\xf0\xf1', 0x84 }, | ||
| 298 | + }) do | ||
| 299 | + self:receive('\x01\x87\x00' .. case[1]) | ||
| 300 | + lu.assertEquals(self.frames[#self.frames]:byte(21), case[2]) | ||
| 301 | + end | ||
🟡 Medium Priority 改动行:test/unit/test_pldm_responder.lua:251 断言 GetPLDMVersion(类型 3) 返回的版本字节为 00 F0 F1 F1(即 0xF1F1F000);而 enable() 里所有注册项的 version 都是 0xF1F0F000(00 F0 F0 F1)。253-262 行对 GetPLDMCommands 只构造了 5 条失败用例(0x83/0x84),没有任何一条成功用例。 受影响的契约:DSP0240 Base 命令协商链路 —— 主机先用 GetPLDMVersion 取得该类型支持的版本,再用「同一版本」去调 GetPLDMCommands。测试集里该版本值来自注册项的 0xF1F0F000,而 GetPLDMVersion 对外宣告的是 0xF1F1F000,两者不同。 失败模式:实现若按注册项版本(0xF1F0F000)校验,则守规主机用 251 行宣告的 0xF1F1F000 请求 GetPLDMCommands 会收到 0x84(版本无效);反之若按宣告版本校验,则用注册版本请求会收到 0x84。由于 253-262 只有失败用例、且错误码 0x83/0x84 与「版本恰好写错」无法区分,这条互操作缺陷(真实主机拿不到命令列表)不会被任何用例捕获。注意用例 4('\x05\x3f...')因类型错误先返回 0x83,也未覆盖成功路径。 建议:新增 GetPLDMCommands 成功用例(pldm_type=3,版本与 GetPLDMVersion 返回的 0xF1F1F000 一致),断言完成码 0x00 及返回的命令位图;并核对 GetPLDMVersion 宣告的版本与注册项 version 是否应一致,避免守规主机拿到版本“无效”错误码。 ![]() ![]() Incorrect? | |||
| 302 | +end | ||
| 303 | + | ||
| 304 | +function TestPldmResponder:test_peer_replacement_cannot_receive_old_reply() | ||
| 305 | + self:enable() | ||
| 306 | + self:receive() | ||
| 307 | + self.engine.endpoints:reset() | ||
| 308 | + self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI) | ||
| 309 | + lu.assertFalse(self.done[1](0, 'old')) | ||
| 310 | + lu.assertEquals(#self.frames, 0) | ||
| 311 | +end | ||
| 312 | + | ||
| 313 | +function TestPldmResponder:test_global_quota_and_live_entries_are_preserved() | ||
| 314 | + local responder = self:enable() | ||
| 315 | + for source = 9, 17 do | ||
| 316 | + self.engine.endpoints:create_endpoint(0x20, source, 1, def.PHY_MEDIUM_PCI) | ||
| 317 | + self.incoming.src = source | ||
| 318 | + for tag = 0, 7 do | ||
| 319 | + self.incoming.tag = tag | ||
| 320 | + self:receive() | ||
| 321 | + end | ||
| 322 | + end | ||
| 323 | + lu.assertEquals({ #self.done, responder.count, responder.overload_drops, #self.frames }, { 64, 64, 8, 0 }) | ||
| 324 | + lu.assertTrue(self.done[1](0, 'first')) | ||
| 325 | + self:receive() | ||
| 326 | + lu.assertEquals(#self.done, 65) | ||
| 327 | + lu.assertEquals(responder.count, 64) | ||
| 328 | +end | ||
| 329 | + | ||
| 330 | +function TestPldmResponder:test_registration_copy_duplicate_version_and_limits() | ||
| 331 | + local responder = self:enable() | ||
| 332 | + local desc = self:descriptor(2, 0, 0xF1F0F000, 1, function(_, done) | ||
| 333 | + done(0, 'a') | ||
| 334 | + end) | ||
| 335 | + responder:register(desc) | ||
| 336 | + desc.dispatch = function() | ||
| 337 | + error('must not replace handler') | ||
| 338 | + end | ||
| 339 | + self:receive('\x01\x87\x02\x00') | ||
| 340 | + lu.assertEquals(self.frames[1]:sub(21, 22), '\x00a') | ||
| 341 | + lu.assertError(responder.register, responder, desc) | ||
| 342 | + desc.command, desc.version = 1, 0xF1F1F000 | ||
| 343 | + lu.assertError(responder.register, responder, desc) | ||
| 344 | + desc.version = 0xF1F0F000 | ||
| 345 | + for command = 1, 126 do | ||
| 346 | + desc.command = command | ||
| 347 | + responder:register(desc) | ||
| 348 | + end | ||
| 349 | + lu.assertEquals(responder.reg_count, 128) | ||
| 350 | + desc.command = 127 | ||
| 351 | + lu.assertError(responder.register, responder, desc) | ||
| 352 | +end | ||
| 353 | + | ||
| 354 | +function TestPldmResponder:test_product_configuration_controls_advertised_capabilities() | ||
| 355 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false }) | ||
| 356 | + self:receive('\x01\x87\x00\x02') | ||
| 357 | + lu.assertEquals(#self.frames, 0) | ||
| 358 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 43 }) | ||
| 359 | + self:receive('\x01\x87\x00\x02') | ||
| 360 | + lu.assertEquals(self.frames[1]:sub(21, 22), '\x00\x2b') | ||
| 361 | + self:receive('\x01\x87\x00\x04') | ||
| 362 | + lu.assertEquals(self.frames[2]:sub(21, 29), '\x00\x01' .. string.rep('\x00', 7)) | ||
| 363 | + self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false }) | ||
| 364 | + self:receive('\x01\x87\x00\x02') | ||
| 365 | + lu.assertEquals(#self.frames, 2) | ||
| 366 | +end | ||


🔵 Low Priority
变更行(pldm_protocol.cpp:105-109)新增了发送侧校验并返回 false: if (local_ctx.rq > 1 || local_ctx.d > 1 || (!local_ctx.rq && local_ctx.d) || local_ctx.iid > 31 || local_ctx.pldm_type > 63) { debug_log(DLOG_ERROR, "pldm_protocol: invalid TX header"); return false; } 影响契约:本 PR 同时新增了
push_checked(l_mctp.cpp:289-297)用于把提交结果回传 Lua,但既有 requester 路径仍用旧的push——ep_pldm.lua:54 与 :75 的self.mctp_engine.pldm_proto:push(payload, self.pldm_ctx, msg_key),而protocol_push(l_mctp.cpp:278-287)丢弃返回值。失败模式:改动前非法头字段会被位域静默截断后照常发出;改动后会直接不发送。PldmType/CommandCode 来自上层
req_ctx(ep_pldm.lua:95-96 的 tonumber),一旦调用方传入 PldmType=64+、iid 越界或 d/rq 组合非法(例如上游把 d 置 1 却发响应),帧被静默丢弃,request()只能等满默认 15s 超时(MCTP_REQUEST_DEFAULT_TIMEOUT)才返回 E_TIMEOUT,日志里也只有在 DLOG_ERROR 级别的一行,问题会被误判为对端无响应。属于本 diff 新增校验带来的行为变化。修复方向:让 requester 路径也使用
push_checked并据返回值立即返回错误(send 直接报错、request 提前唤醒为发送失败),或至少在 ep_pldm 调用处检查返回值并记录带 eid/msg_key 的错误日志。建议:在 ep_pldm 的 send/request 中改用 push_checked 并根据返回值立即失败(而非静默丢弃后等 15s 超时),或至少在调用处检查 push 结果并记录含 eid/msg_key 的错误日志。