| @@ -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.19] - 2026-10-27 | ||
| 4 | +增加有界PCIe消息队列、Control/requester保留容量及驱动写入结果回传;拒绝后不再绕过调度直接写设备。 | ||
| 5 | + | ||
| 3 | ## [1.130.15] - 2026-08-25 | 6 | ## [1.130.15] - 2026-08-25 |
| 4 | 更新版本号,触发流水线推包 | 7 | 更新版本号,触发流水线推包 |
| 5 | 8 | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# PCIe消息发送与完成语义 | ||
| 2 | + | ||
| 3 | +现有requester、Control及后续responder复用同进程的唯一发送队列。完整消息在EOM后才交给写服务,禁止只发出因容量限制而被拒绝消息的前缀。所有System共用64项/256KiB上限,其中Control预留8项/16KiB、requester预留16项/64KiB、普通响应40项/176KiB;单消息最多128帧/16KiB。字节预算只包含帧数据,Lua表与IPC副本须计入最终内存验收。 | ||
| 4 | + | ||
| 5 | +写服务单次实际写入后返回结果;不隐式重试、不因另一通道失败重置全部实例。失败停止同消息后续帧。请求失败只唤醒原wait对象;`on_tx_complete`通知驱动写入结果,旧Send返回只表示提交/准入,不表示对端已收到。无法撤回已交给驱动的单帧,驱动阻塞耗时仍需实测,不能将入队或write成功称为PCIe端到端完成。 | ||
| 6 | + | ||
| 7 | +100ms为本轮TX等待上界候选,由队列`deadline_ms`选项集中定义,使用单调时钟逐帧检查。唯一10ms检查器回收未完成的分片和过期排队项;驱动调用过期则通知一次失败、暂停新准入,实际调用返回前仍保留其额度,不再生成替代调用。驱动恢复返回后停止旧消息尾片并恢复准入;不能通过Lua定时器中断内核write,永久挂起仍需修复驱动或人工恢复服务。完整业务期限需在responder/V2后续接入时传递。 | ||
| 8 | + | ||
| 9 | +复位、重新配置、本地及owner地址变化取消排队项;peer对象/地址改变也会在写前失效。写入成功/失败计数在原debug dump中输出,失败日志按1、2、4、8…次记录原因。NVMe命令槽在同步异常时也释放。旧Send计数仍指准入,不等于驱动写入。完整跨接口及路由代次仍由后续改动补齐,该PR不宣告阶段1整体或阶段2完成。 | ||
| 10 | + | ||
| 11 | +底层拒绝的修复:原`channel_protocol::push`收到回调false仍调用`write_to`;现在存在发送回调时直接向上传递其结果。用C语言类比,该回调相当于结构体里的发送函数指针,false代表调用方已拒绝,不能再次走默认发送函数。无回调的旧直写路径保留,但写失败返回false。 | ||
| 12 | + | ||
| 13 | +分片上下文的修复:`mctp_ctx fragment_ctx = local_ctx`等价于复制C结构体的成员;`set_prev(&ctx)`保留当前调用的原上下文链,SOM/EOM只写副本。它是函数栈上的临时对象,在一次同步下传返回后失效。Lua队列保存帧字符串、数值和另行复制的地址字段,不保留这个临时对象地址;调用者原上下文不随分片改变。没有改变线上MCTP头格式,CPU架构差异仍由既有硬件编码层处理。 | ||
| 14 | + | ||
| 15 | +验证覆盖完整聚合、消息/字节配额、Control/requester保留、有效对象不被淘汰、deadline/地址往返/取消、活跃IPC仍占用额度、驱动失败停止尾片、迟到失败与复用wait隔离、真实管道拒绝后不直写。该PR拆分基于main,需和共享传输修复及PLDM模块共同验收,不能以独立分支测试替代最终镜像验收。 | ||
| @@ -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.19", |
| 6 | "author": "Huawei", | 6 | "author": "Huawei", |
| 7 | "license": "Mulan PSL v2", | 7 | "license": "Mulan PSL v2", |
| 8 | "options": { | 8 | "options": { |
| @@ -198,6 +198,8 @@ static void register_ctxs(lua_State *L, luawrap::stack_table &t) | |||
| 198 | cls.def_readwrite("dest", &mctp_ctx::dest); | 198 | cls.def_readwrite("dest", &mctp_ctx::dest); |
| 199 | cls.def_readwrite("tag_owner", &mctp_ctx::tag_owner); | 199 | cls.def_readwrite("tag_owner", &mctp_ctx::tag_owner); |
| 200 | cls.def_readwrite("tag", &mctp_ctx::tag); | 200 | cls.def_readwrite("tag", &mctp_ctx::tag); |
| 201 | + cls.def_readwrite("som", &mctp_ctx::som); | ||
| 202 | + cls.def_readwrite("eom", &mctp_ctx::eom); | ||
| 201 | t.set("mctp_ctx", cls); | 203 | t.set("mctp_ctx", cls); |
| 202 | }); | 204 | }); |
| 203 | register_ctx<pcie_vdm_ctx, protocol_ctx>(L, [L, &t](auto &cls) { | 205 | register_ctx<pcie_vdm_ctx, protocol_ctx>(L, [L, &t](auto &cls) { |
| @@ -85,13 +85,16 @@ int channel_protocol::rx() | |||
| 85 | 85 | ||
| 86 | bool channel_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) | 86 | bool channel_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type) |
| 87 | { | 87 | { |
| 88 | - if (protocol::push(ctx, data, medium_type)) { | 88 | + const bool accepted = protocol::push(ctx, data, medium_type); |
| 89 | - return true; | 89 | + // 显式调度器拒绝必须向上传播,不能绕过背压直接写设备。 |
| 90 | + if (accepted || m_tx_handler) { | ||
| 91 | + return accepted; | ||
| 90 | } | 92 | } |
| 91 | 93 | ||
| 92 | // 下层协议不处理自己发出去 | 94 | // 下层协议不处理自己发出去 |
| 93 | if (write_to(m_fd, data) != E_OK) { | 95 | if (write_to(m_fd, data) != E_OK) { |
| 94 | debug_log(DLOG_INFO, "channel_protocol: write failed, fd = %d, %s", m_fd, strerror(errno)); | 96 | debug_log(DLOG_INFO, "channel_protocol: write failed, fd = %d, %s", m_fd, strerror(errno)); |
| 97 | + return false; | ||
| 95 | } | 98 | } |
| 96 | 99 | ||
| 97 | return true; | 100 | return true; |
| @@ -306,7 +306,11 @@ bool mctp_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uin | |||
| 306 | hdr.u.seq = i & MCTP_HDR_SEQ_MASK; | 306 | hdr.u.seq = i & MCTP_HDR_SEQ_MASK; |
| 307 | 307 | ||
| 308 | m_tx_buf.insert(m_tx_buf.end(), it, next_it); | 308 | m_tx_buf.insert(m_tx_buf.end(), it, next_it); |
| 309 | - if (!protocol::push(ctx, slice(m_tx_buf), msg_key)) { | 309 | + mctp_ctx fragment_ctx = local_ctx; |
| 310 | + fragment_ctx.set_prev(&ctx); | ||
| 311 | + fragment_ctx.som = hdr.u.som; | ||
| 312 | + fragment_ctx.eom = hdr.u.eom; | ||
| 313 | + if (!protocol::push(fragment_ctx, slice(m_tx_buf), msg_key)) { | ||
| 310 | return false; | 314 | return false; |
| 311 | } | 315 | } |
| 312 | 316 | ||
| @@ -68,6 +68,8 @@ public: | |||
| 68 | mctp_eid_t dest; | 68 | mctp_eid_t dest; |
| 69 | bool tag_owner; | 69 | bool tag_owner; |
| 70 | uint8_t tag; | 70 | uint8_t tag; |
| 71 | + bool som = true; // 仅发送边界元数据,不改变线上头格式 | ||
| 72 | + bool eom = true; | ||
| 71 | }; | 73 | }; |
| 72 | 74 | ||
| 73 | enum class mctp_route_policy { | 75 | enum class mctp_route_policy { |
| @@ -150,10 +150,12 @@ function c_mctp_discovery:process_prepare_for_ep_discovery_req(req) | |||
| 150 | local mctp_ctx, ctx = self.ep:get_ctx_by_medium(self.ep.medium_type) | 150 | local mctp_ctx, ctx = self.ep:get_ctx_by_medium(self.ep.medium_type) |
| 151 | local eid = mctp_ctx.src | 151 | local eid = mctp_ctx.src |
| 152 | local phy_addr = ctx.remote_phy_addr | 152 | local phy_addr = ctx.remote_phy_addr |
| 153 | + -- 复位先使旧事务失效;Control endpoint 及其接收上下文由 reset 保留。 | ||
| 154 | + -- 然后恢复请求来源并在新代次提交应答,避免把本次成功响应一并取消。 | ||
| 155 | + self:set_discovered(false) | ||
| 153 | self.mctp_engine:set_owner_eid(eid, phy_addr) | 156 | self.mctp_engine:set_owner_eid(eid, phy_addr) |
| 154 | log:debug('[System%s]mctp_discovery: prepare for endpoint discovery req', self.system_id) | 157 | log:debug('[System%s]mctp_discovery: prepare for endpoint discovery req', self.system_id) |
| 155 | self.ep:response(req, MCTP_CTRL_CC_SUCCESS, ROUTE_TO_ROOT) | 158 | self.ep:response(req, MCTP_CTRL_CC_SUCCESS, ROUTE_TO_ROOT) |
| 156 | - self:set_discovered(false) | ||
| 157 | return true | 159 | return true |
| 158 | end | 160 | end |
| 159 | 161 | ||
| @@ -105,11 +105,14 @@ function c_nvme_endpoint:request(ctx, data, timeout_ms, req_ctx, rsp_ctx) | |||
| 105 | end | 105 | end |
| 106 | end | 106 | end |
| 107 | 107 | ||
| 108 | - local rsp = c_nvme_endpoint.super.request(self, slot, function(key) | 108 | + local ok, rsp = pcall(c_nvme_endpoint.super.request, self, slot, function(key) |
| 109 | self.wait_info[key] = { count = 0, is_replay = is_replay } | 109 | self.wait_info[key] = { count = 0, is_replay = is_replay } |
| 110 | self.mctp_engine.nvme_proto:push(payload, self.nvme_ctx, msg_key) | 110 | self.mctp_engine.nvme_proto:push(payload, self.nvme_ctx, msg_key) |
| 111 | end, timeout_ms, more_process_cb) | 111 | end, timeout_ms, more_process_cb) |
| 112 | self:release_cmd_slot(slot) | 112 | self:release_cmd_slot(slot) |
| 113 | + if not ok then | ||
| 114 | + error(rsp) | ||
| 115 | + end | ||
| 113 | 116 | ||
| 114 | local index = slot + 1 | 117 | local index = slot + 1 |
| 115 | local nvme_ctx = self.nvme_ctx | 118 | local nvme_ctx = self.nvme_ctx |
| @@ -131,10 +134,13 @@ function c_nvme_endpoint:send(_, data, req_ctx) | |||
| 131 | self:pcie_ctx_set() | 134 | self:pcie_ctx_set() |
| 132 | end | 135 | end |
| 133 | local payload, slot, _, msg_key = self:prepare_nvme_request(req_ctx, nil, data) | 136 | local payload, slot, _, msg_key = self:prepare_nvme_request(req_ctx, nil, data) |
| 134 | - c_nvme_endpoint.super.send(self, function() | 137 | + local ok, err = pcall(c_nvme_endpoint.super.send, self, function() |
| 135 | self.mctp_engine.nvme_proto:push(payload, self.nvme_ctx, msg_key) | 138 | self.mctp_engine.nvme_proto:push(payload, self.nvme_ctx, msg_key) |
| 136 | end) | 139 | end) |
| 137 | self:release_cmd_slot(slot) | 140 | self:release_cmd_slot(slot) |
| 141 | + if not ok then | ||
| 142 | + error(err) | ||
| 143 | + end | ||
| 138 | end | 144 | end |
| 139 | 145 | ||
| 140 | local function check_ctx_value(val) | 146 | local function check_ctx_value(val) |
| @@ -26,6 +26,7 @@ local stop_timer = c_timer.stop | |||||||||||||||||||||||
| 26 | local mctp_utils = require('mctp_utils') | 26 | local mctp_utils = require('mctp_utils') | ||||||||||||||||||||
| 27 | local mctp_def = require('mctp_def') | 27 | local mctp_def = require('mctp_def') | ||||||||||||||||||||
| 28 | local print_msg = mctp_utils.print_msg | 28 | local print_msg = mctp_utils.print_msg | ||||||||||||||||||||
| 29 | +local shared_tx = require('mctp_tx').new() | ||||||||||||||||||||||
| 29 | 30 | ||||||||||||||||||||||
| 30 | local MCTP_DEV_NAME = '/dev/mctp' | 31 | local MCTP_DEV_NAME = '/dev/mctp' | ||||||||||||||||||||
| 31 | local MCTP1_DEV_NAME = '/dev/mctp1' | 32 | local MCTP1_DEV_NAME = '/dev/mctp1' | ||||||||||||||||||||
| @@ -84,6 +85,9 @@ function c_mctp_engine:ctor(bus, system_id) | |||||||||||||||||||||||
| 84 | self.module_name = 'MCTP' | 85 | self.module_name = 'MCTP' | ||||||||||||||||||||
| 85 | self.bus = bus | 86 | self.bus = bus | ||||||||||||||||||||
| 86 | self.system_id = system_id | 87 | self.system_id = system_id | ||||||||||||||||||||
| 88 | + self.tx_queue, self.tx_epoch = shared_tx, 0 | ||||||||||||||||||||||
| 89 | + self.on_tx_complete = signal.new() | ||||||||||||||||||||||
🔵 Low Priority 变更行:89 行新增 影响:在整个 可选改进(非必须):要么删除该信号,要么在 ep_mctp/ep_mctp_ctrl 的发送侧订阅并把失败计入已有的 建议:确认该信号是否真有消费方:若无,删除 ![]() ![]() Incorrect? | |||||||||||||||||||||||
| 90 | + self.tx_written_count, self.tx_failed_count = 0, 0 | ||||||||||||||||||||||
| 87 | 91 | ||||||||||||||||||||||
| 88 | self.mctp_req_timeout_count = 0 | 92 | self.mctp_req_timeout_count = 0 | ||||||||||||||||||||
| 89 | self.on_mctp_req_timeout = signal.new() | 93 | self.on_mctp_req_timeout = signal.new() | ||||||||||||||||||||
| @@ -96,7 +100,14 @@ function c_mctp_engine:ctor(bus, system_id) | |||||||||||||||||||||||
| 96 | end | 100 | end | ||||||||||||||||||||
| 97 | 101 | ||||||||||||||||||||||
| 98 | function c_mctp_engine:dump_eid_info() | 102 | function c_mctp_engine:dump_eid_info() | ||||||||||||||||||||
| 99 | - return self.eid_table:dump_eid_table(self.endpoints) | 103 | + local info = self.eid_table:dump_eid_table(self.endpoints) | ||||||||||||||||||||
| 104 | + info[#info + 1] = string.format( | ||||||||||||||||||||||
| 105 | + 'PCIe TX written=%d failed=%d blocked=%s\r\n', | ||||||||||||||||||||||
| 106 | + self.tx_written_count, | ||||||||||||||||||||||
| 107 | + self.tx_failed_count, | ||||||||||||||||||||||
| 108 | + tostring(self.tx_queue.blocked == true) | ||||||||||||||||||||||
| 109 | + ) | ||||||||||||||||||||||
| 110 | + return info | ||||||||||||||||||||||
| 100 | end | 111 | end | ||||||||||||||||||||
| 101 | 112 | ||||||||||||||||||||||
| 102 | function c_mctp_engine:get_eid_table() | 113 | function c_mctp_engine:get_eid_table() | ||||||||||||||||||||
| @@ -185,6 +196,98 @@ local function get_mctp_dev_name(system_id) | |||||||||||||||||||||||
| 185 | end | 196 | end | ||||||||||||||||||||
| 186 | end | 197 | end | ||||||||||||||||||||
| 187 | 198 | ||||||||||||||||||||||
| 199 | +function c_mctp_engine:invalidate_pcie_tx() | ||||||||||||||||||||||
| 200 | + self.tx_epoch = self.tx_epoch + 1 | ||||||||||||||||||||||
| 201 | + self.tx_queue:cancel(self) | ||||||||||||||||||||||
| 202 | +end | ||||||||||||||||||||||
| 203 | + | ||||||||||||||||||||||
| 204 | +function c_mctp_engine:configure_pcie_tx(fd) | ||||||||||||||||||||||
| 205 | + self:invalidate_pcie_tx() | ||||||||||||||||||||||
| 206 | + local transport, physical = mctp.mctp_ctx.new(), mctp.pcie_vdm_ctx.new() | ||||||||||||||||||||||
| 207 | + self.hw_pcie_proto:set_tx_handler(function(ctx, data, msg_key) | ||||||||||||||||||||||
| 208 | + local msg_type, eid, medium = self.get_ep_details(msg_key) | ||||||||||||||||||||||
| 209 | + local ep = self.endpoints:get_endpoint(eid, msg_type, medium) | ||||||||||||||||||||||
| 210 | + transport:copy_from(ctx) | ||||||||||||||||||||||
| 211 | + physical:copy_from(ctx) | ||||||||||||||||||||||
🔵 Low Priority 变更行:201-204 行在每帧(每个分片)回调里都 影响:这是 PCIe 出口最热的路径(PLDM/NCSI/SPDM 业务每一帧都会经过),每条消息的每个分片都构造两个 C++ ctx 对象并做两次 copy_from;同文件 542 行 建议:续帧( ![]() ![]() Incorrect? | |||||||||||||||||||||||
| 212 | + local remote_addr, local_addr = physical.remote_phy_addr, physical.bmc_phy_addr | ||||||||||||||||||||||
| 213 | + -- responder 可服务已纳管的其他类型端点;不要求业务先注册 PLDM requester。 | ||||||||||||||||||||||
| 214 | + if not ep and not transport.tag_owner then | ||||||||||||||||||||||
| 215 | + for _, peer in pairs(self.endpoints.endpoints) do | ||||||||||||||||||||||
| 216 | + if peer.eid == eid and peer.medium_type == medium and peer.phy_addr == remote_addr then | ||||||||||||||||||||||
| 217 | + ep = peer | ||||||||||||||||||||||
| 218 | + break | ||||||||||||||||||||||
| 219 | + end | ||||||||||||||||||||||
| 220 | + end | ||||||||||||||||||||||
| 221 | + end | ||||||||||||||||||||||
| 222 | + local peer_type = ep and ep:get_msg_type() | ||||||||||||||||||||||
| 223 | + local epoch, bmc_eid, bmc_addr = self.tx_epoch, self.bmc_eid, self.bmc_phy_addr | ||||||||||||||||||||||
| 224 | + local peer_eid, peer_addr = ep and ep.eid, ep and ep.phy_addr | ||||||||||||||||||||||
| 225 | + local key = transport.tag_owner and ep and ep.cur_rsp_key | ||||||||||||||||||||||
| 226 | + local wait = key and self.rsp_wait_list[key] | ||||||||||||||||||||||
| 227 | + local meta = { | ||||||||||||||||||||||
| 228 | + key = table.concat({ msg_key, transport.src, transport.dest, transport.tag }, ':'), | ||||||||||||||||||||||
| 229 | + fd = fd, | ||||||||||||||||||||||
| 230 | + som = transport.som, | ||||||||||||||||||||||
| 231 | + eom = transport.eom, | ||||||||||||||||||||||
| 232 | + kind = msg_type == 0 and 'control' or (transport.tag_owner and 'request' or 'response'), | ||||||||||||||||||||||
| 233 | + deadline = self.tx_queue.now() + self.tx_queue.deadline_ms, | ||||||||||||||||||||||
| 234 | + valid = function() | ||||||||||||||||||||||
| 235 | + return self.tx_epoch == epoch | ||||||||||||||||||||||
| 236 | + and self.bmc_eid == bmc_eid | ||||||||||||||||||||||
| 237 | + and self.bmc_phy_addr == bmc_addr | ||||||||||||||||||||||
| 238 | + and ( | ||||||||||||||||||||||
| 239 | + msg_type == 0 | ||||||||||||||||||||||
| 240 | + or ( | ||||||||||||||||||||||
| 241 | + ep ~= nil | ||||||||||||||||||||||
| 242 | + and ep.eid == peer_eid | ||||||||||||||||||||||
| 243 | + and ep.phy_addr == peer_addr | ||||||||||||||||||||||
| 244 | + and self.endpoints:get_endpoint(eid, peer_type, medium) == ep | ||||||||||||||||||||||
| 245 | + and peer_addr == remote_addr | ||||||||||||||||||||||
| 246 | + and local_addr == bmc_addr | ||||||||||||||||||||||
| 247 | + ) | ||||||||||||||||||||||
| 248 | + ) | ||||||||||||||||||||||
| 249 | + end, | ||||||||||||||||||||||
| 250 | + done = function(ok, reason) | ||||||||||||||||||||||
| 251 | + if ok then | ||||||||||||||||||||||
| 252 | + self.tx_written_count = self.tx_written_count + 1 | ||||||||||||||||||||||
| 253 | + else | ||||||||||||||||||||||
| 254 | + self.tx_failed_count = self.tx_failed_count + 1 | ||||||||||||||||||||||
| 255 | + local n = self.tx_failed_count | ||||||||||||||||||||||
| 256 | + if n & (n - 1) == 0 then | ||||||||||||||||||||||
| 257 | + log:notice( | ||||||||||||||||||||||
| 258 | + '[System%s]MCTP TX failed: key=%s reason=%s count=%d', | ||||||||||||||||||||||
| 259 | + self.system_id, | ||||||||||||||||||||||
| 260 | + msg_key, | ||||||||||||||||||||||
| 261 | + reason, | ||||||||||||||||||||||
| 262 | + n | ||||||||||||||||||||||
| 263 | + ) | ||||||||||||||||||||||
| 264 | + end | ||||||||||||||||||||||
| 265 | + end | ||||||||||||||||||||||
| 266 | + if not ok and wait and self.rsp_wait_list[key] == wait then | ||||||||||||||||||||||
| 267 | + wait.err = errors.invalid_mctp_request() | ||||||||||||||||||||||
| 268 | + if wait.co ~= coroutine.running() then | ||||||||||||||||||||||
| 269 | + skynet.wakeup(wait.co) | ||||||||||||||||||||||
| 270 | + end | ||||||||||||||||||||||
| 271 | + end | ||||||||||||||||||||||
🟡 Medium Priority 变更行: 受影响契约: 失效模式:此同步路径下 建议:在登记 wait 时记录其所属协程/挂起状态(如 Suggestions
![]() ![]() Incorrect? | |||||||||||||||||||||||
| 272 | + self.on_tx_complete:emit(ok, reason, msg_key) | ||||||||||||||||||||||
| 273 | + end, | ||||||||||||||||||||||
| 274 | + } | ||||||||||||||||||||||
| 275 | + if ep and ep.is_trace == 'start' then | ||||||||||||||||||||||
| 276 | + print_msg('SEND', data, medium) | ||||||||||||||||||||||
| 277 | + end | ||||||||||||||||||||||
| 278 | + local admitted, notified = self.tx_queue:enqueue(self, meta, data) | ||||||||||||||||||||||
| 279 | + if not admitted then | ||||||||||||||||||||||
| 280 | + if not notified then | ||||||||||||||||||||||
| 281 | + pcall(meta.done, false, 'capacity') | ||||||||||||||||||||||
| 282 | + end | ||||||||||||||||||||||
| 283 | + if ep then | ||||||||||||||||||||||
| 284 | + ep.tx_admission_failed = true | ||||||||||||||||||||||
| 285 | + end | ||||||||||||||||||||||
| 286 | + end | ||||||||||||||||||||||
🟡 Medium Priority 变更行:243-247 行在 受影响契约:队列内部的完成通知统一走 建议:将 246 行的直接回调改为受保护调用(例如 Suggestions
![]() ![]() Incorrect? | |||||||||||||||||||||||
| 287 | + return admitted | ||||||||||||||||||||||
| 288 | + end) | ||||||||||||||||||||||
| 289 | +end | ||||||||||||||||||||||
| 290 | + | ||||||||||||||||||||||
| 188 | function c_mctp_engine:start() | 291 | function c_mctp_engine:start() | ||||||||||||||||||||
| 189 | local fd = self.hw_pcie_proto:open_path(get_mctp_dev_name(self.system_id), O_RDWR) | 292 | local fd = self.hw_pcie_proto:open_path(get_mctp_dev_name(self.system_id), O_RDWR) | ||||||||||||||||||||
| 190 | if fd < 0 then | 293 | if fd < 0 then | ||||||||||||||||||||
| @@ -192,15 +295,7 @@ function c_mctp_engine:start() | |||||||||||||||||||||||
| 192 | return | 295 | return | ||||||||||||||||||||
| 193 | end | 296 | end | ||||||||||||||||||||
| 194 | 297 | ||||||||||||||||||||||
| 195 | - self.hw_pcie_proto:set_tx_handler(function(_, data, msg_key) | 298 | + self:configure_pcie_tx(fd) | ||||||||||||||||||||
| 196 | - local msg_type, eid, medium_type = self.get_ep_details(msg_key) | ||||||||||||||||||||||
| 197 | - local ep = self.endpoints:get_endpoint(eid, msg_type, medium_type) | ||||||||||||||||||||||
| 198 | - if ep and ep.is_trace == 'start' then | ||||||||||||||||||||||
| 199 | - print_msg('SEND', data, medium_type) | ||||||||||||||||||||||
| 200 | - end | ||||||||||||||||||||||
| 201 | - skynet.send('.mctp_write_service', 'lua', fd, data) | ||||||||||||||||||||||
| 202 | - return true | ||||||||||||||||||||||
| 203 | - end) | ||||||||||||||||||||||
| 204 | 299 | ||||||||||||||||||||||
| 205 | self.smbus_proto:set_tx_handler(function(_, data, msg_key) | 300 | self.smbus_proto:set_tx_handler(function(_, data, msg_key) | ||||||||||||||||||||
| 206 | local msg_type, eid, medium_type = self.get_ep_details(msg_key) | 301 | local msg_type, eid, medium_type = self.get_ep_details(msg_key) | ||||||||||||||||||||
| @@ -239,6 +334,7 @@ function c_mctp_engine:start() | |||||||||||||||||||||||
| 239 | end | 334 | end | ||||||||||||||||||||
| 240 | 335 | ||||||||||||||||||||||
| 241 | function c_mctp_engine:stop() | 336 | function c_mctp_engine:stop() | ||||||||||||||||||||
| 337 | + self:invalidate_pcie_tx() | ||||||||||||||||||||||
| 242 | self.pcie_read_work:stop() | 338 | self.pcie_read_work:stop() | ||||||||||||||||||||
| 243 | self.pcie_read_work:join() | 339 | self.pcie_read_work:join() | ||||||||||||||||||||
| 244 | self.pcie_read_work = nil | 340 | self.pcie_read_work = nil | ||||||||||||||||||||
| @@ -260,6 +356,7 @@ function c_mctp_engine:reset() | |||||||||||||||||||||||
| 260 | end | 356 | end | ||||||||||||||||||||
| 261 | 357 | ||||||||||||||||||||||
| 262 | function c_mctp_engine:reinit() | 358 | function c_mctp_engine:reinit() | ||||||||||||||||||||
| 359 | + self:invalidate_pcie_tx() | ||||||||||||||||||||||
| 263 | log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id) | 360 | log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id) | ||||||||||||||||||||
| 264 | self.hw_pcie_proto:vdm_engine_reinit() | 361 | self.hw_pcie_proto:vdm_engine_reinit() | ||||||||||||||||||||
| 265 | end | 362 | end | ||||||||||||||||||||
| @@ -317,6 +414,7 @@ end | |||||||||||||||||||||||
| 317 | 414 | ||||||||||||||||||||||
| 318 | function c_mctp_engine:set_owner_eid(eid, phy_addr) | 415 | function c_mctp_engine:set_owner_eid(eid, phy_addr) | ||||||||||||||||||||
| 319 | if self.owner_eid ~= eid or self.owner_phy_addr ~= phy_addr then | 416 | if self.owner_eid ~= eid or self.owner_phy_addr ~= phy_addr then | ||||||||||||||||||||
| 417 | + self:invalidate_pcie_tx() | ||||||||||||||||||||||
| 320 | self.owner_eid = eid | 418 | self.owner_eid = eid | ||||||||||||||||||||
| 321 | self.owner_phy_addr = phy_addr | 419 | self.owner_phy_addr = phy_addr | ||||||||||||||||||||
| 322 | log:notice('[System%s]mctp_engine: set owner_eid=%d, owner_phy_addr=%d', self.system_id, eid, phy_addr) | 420 | log:notice('[System%s]mctp_engine: set owner_eid=%d, owner_phy_addr=%d', self.system_id, eid, phy_addr) | ||||||||||||||||||||
| @@ -325,6 +423,7 @@ end | |||||||||||||||||||||||
| 325 | 423 | ||||||||||||||||||||||
| 326 | function c_mctp_engine:set_bmc_eid(eid, phy_addr) | 424 | function c_mctp_engine:set_bmc_eid(eid, phy_addr) | ||||||||||||||||||||
| 327 | if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then | 425 | if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then | ||||||||||||||||||||
| 426 | + self:invalidate_pcie_tx() | ||||||||||||||||||||||
| 328 | self.bmc_eid = eid | 427 | self.bmc_eid = eid | ||||||||||||||||||||
| 329 | self.mctp_proto.pcie_eid = eid | 428 | self.mctp_proto.pcie_eid = eid | ||||||||||||||||||||
| 330 | self.bmc_phy_addr = phy_addr | 429 | self.bmc_phy_addr = phy_addr | ||||||||||||||||||||
| @@ -426,7 +525,8 @@ function c_mctp_engine:request(key, cb, timeout_ms, more_process_cb) | |||||||||||||||||||||||
| 426 | wait_info = { co = coroutine.running() } | 525 | wait_info = { co = coroutine.running() } | ||||||||||||||||||||
| 427 | self.rsp_wait_list[key] = wait_info | 526 | self.rsp_wait_list[key] = wait_info | ||||||||||||||||||||
| 428 | local ok, err = pcall(cb, key) | 527 | local ok, err = pcall(cb, key) | ||||||||||||||||||||
| 429 | - if not ok then | 528 | + if not ok or wait_info.err then | ||||||||||||||||||||
| 529 | + err = wait_info.err or err | ||||||||||||||||||||||
| 430 | self.rsp_wait_list[key] = nil | 530 | self.rsp_wait_list[key] = nil | ||||||||||||||||||||
| 431 | log:error('[System%s]mctp_engine: request cb failed, %s', self.system_id, err) | 531 | log:error('[System%s]mctp_engine: request cb failed, %s', self.system_id, err) | ||||||||||||||||||||
| 432 | return E_SEND_FAILED, err | 532 | return E_SEND_FAILED, err | ||||||||||||||||||||
🔵 Low Priority 变更行:492-496 行新增 失效模式:test/unit/test_write_service.lua 里的引擎用例只用手工构造的 建议:补一条针对 ![]() ![]() Incorrect? | |||||||||||||||||||||||
| @@ -12,26 +12,21 @@ local skynet = require('skynet') | |||
| 12 | require('skynet.manager') | 12 | require('skynet.manager') |
| 13 | local mctp = require('mctp') | 13 | local mctp = require('mctp') |
| 14 | local logging = require('mc.logging') | 14 | local logging = require('mc.logging') |
| 15 | - | 15 | +local failures = 0 |
| 16 | -local MAX_RETRY_COUNT = 50 | ||
| 17 | -local retry_count = 0 | ||
| 18 | 16 | ||
| 19 | skynet.start(function() | 17 | skynet.start(function() |
| 20 | skynet.register('.mctp_write_service') | 18 | skynet.register('.mctp_write_service') |
| 21 | - skynet.dispatch('lua', function(_, _, fd, data) | 19 | + skynet.dispatch('lua', function(session, _, fd, data) |
| 22 | - local ok, err | 20 | + local ok, err = pcall(mctp.write, fd, data) |
| 23 | - for i = 1, 10 do | 21 | + if not ok then |
| 24 | - ok, err = pcall(mctp.write, fd, data) | 22 | + failures = failures + 1 |
| 25 | - if ok then | 23 | + if failures & (failures - 1) == 0 then |
| 26 | - retry_count = 0 | 24 | + logging:notice('mctp_write_service: failed count=%d error=%s', failures, tostring(err)) |
| 27 | - break | ||
| 28 | end | 25 | end |
| 29 | - retry_count = retry_count + 1 | ||
| 30 | - logging:notice('mctp_write_service failed, count: %s, err: %s', i, err) | ||
| 31 | - skynet.sleep(50) | ||
| 32 | end | 26 | end |
| 33 | - if retry_count > MAX_RETRY_COUNT then | 27 | + -- 每次仅写一帧;上层有界调度器收到结果后才继续该消息。 |
| 34 | - skynet.send('.mctpd', 'lua', 'reset') | 28 | + if session ~= 0 then |
| 29 | + skynet.ret(skynet.pack(ok)) | ||
| 35 | end | 30 | end |
🔵 Low Priority 改动位置:src/service/write_service.lua:19-26,删除 失败链:驱动写失败现在只产生一条 debug 日志;上层 建议:写失败时保留可观测性:使用 notice/error 级日志并累计失败计数;对连续失败提供升级路径(例如通过 engine.on_tx_complete 的消费方触发 reset),避免驱动异常被静默吞掉。 ![]() ![]() Incorrect? | |||
| 36 | end) | 31 | end) |
| 37 | end) | 32 | end) |
| @@ -451,6 +451,18 @@ local function test_mctp_dump(bus) | |||
| 451 | lu.assertNotIsNil(file) | 451 | lu.assertNotIsNil(file) |
| 452 | local data = file:read('a') | 452 | local data = file:read('a') |
| 453 | file:close() | 453 | file:close() |
| 454 | + -- 后台Control发送数量受轮询时序影响;逐实例验证新增诊断,原库存/请求计数继续精确比对。 | ||
| 455 | + local tx_lines | ||
| 456 | + data, tx_lines = data:gsub( | ||
| 457 | + 'PCIe TX written=(%d+) failed=(%d+) blocked=(%a+)\r?\n', | ||
| 458 | + function(written, failed, blocked) | ||
| 459 | + lu.assertTrue(tonumber(written) > 0) | ||
| 460 | + lu.assertEquals(tonumber(failed), 0) | ||
| 461 | + lu.assertEquals(blocked, 'false') | ||
| 462 | + return '' | ||
| 463 | + end | ||
| 464 | + ) | ||
| 465 | + lu.assertEquals(tx_lines, 2) | ||
| 454 | local pid = utils_core.getpid() | 466 | local pid = utils_core.getpid() |
| 455 | local expert_str = string.format(str, pid, pid, pid, pid, pid) | 467 | local expert_str = string.format(str, pid, pid, pid, pid, pid) |
| 456 | lu.assertEquals(data, expert_str) | 468 | lu.assertEquals(data, expert_str) |
| @@ -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-test', | ||
| 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-test', | ||
| 161 | target_eid, | 163 | target_eid, |
| 162 | ref_chip, | 164 | ref_chip, |
| 163 | medium_type, | 165 | medium_type, |
| @@ -250,7 +250,7 @@ function TestMctpEngine:test_start() | |||
| 250 | end | 250 | end |
| 251 | 251 | ||
| 252 | mctp_engine.hw_pcie_proto.set_tx_handler = function(_, func) | 252 | mctp_engine.hw_pcie_proto.set_tx_handler = function(_, func) |
| 253 | - func(_, '123456', 123456) | 253 | + lu.assertEquals(type(func), 'function') -- 真实分片入队由TestWriteService覆盖 |
| 254 | end | 254 | end |
| 255 | 255 | ||
| 256 | mctp_engine.smbus_proto.set_tx_handler = function(_, func) | 256 | mctp_engine.smbus_proto.set_tx_handler = function(_, func) |
| @@ -301,3 +301,18 @@ function TestMctpSeqValidation:test_tag_owner_preserved() | |||
| 301 | lu.assertEquals(self.rx_ctx.tag_owner, true) | 301 | lu.assertEquals(self.rx_ctx.tag_owner, true) |
| 302 | lu.assertEquals(self.rx_ctx.tag, 5) | 302 | lu.assertEquals(self.rx_ctx.tag, 5) |
| 303 | end | 303 | end |
| 304 | + | ||
| 305 | +function TestMctpSeqValidation:test_tx_context_preserves_message_boundaries_without_mutating_caller() | ||
| 306 | + local proto, ctx, seen = mctp.mctp_protocol.new(), mctp.mctp_ctx.new(), {} | ||
| 307 | + ctx.src, ctx.dest, ctx.tag, ctx.tag_owner = 10, 9, 3, true | ||
| 308 | + proto.max_size = 64 | ||
| 309 | + proto:set_tx_handler(function(incoming, data) | ||
| 310 | + local copy = mctp.mctp_ctx.new() | ||
| 311 | + copy:copy_from(incoming) | ||
| 312 | + seen[#seen + 1] = { copy.som, copy.eom, data:byte(4) } | ||
| 313 | + return true | ||
| 314 | + end) | ||
| 315 | + proto:push(string.rep('x', 130), ctx, mctp_def.PHY_MEDIUM_PCI) | ||
| 316 | + lu.assertEquals(seen, { { true, false, 0x8b }, { false, false, 0x1b }, { false, true, 0x6b } }) | ||
| 317 | + lu.assertEquals({ ctx.som, ctx.eom }, { true, true }) | ||
| 318 | +end | ||
| @@ -20,3 +20,529 @@ function TestWriteService:test_config() | |||
| 20 | local write_service = require('service.write_service') | 20 | local write_service = require('service.write_service') |
| 21 | lu.assertNotIsNil(write_service) | 21 | lu.assertNotIsNil(write_service) |
| 22 | end | 22 | end |
| 23 | + | ||
| 24 | +function TestWriteService:fixture() | ||
| 25 | + local ok, Tx = pcall(require, 'mctp_tx') | ||
| 26 | + lu.assertTrue(ok, '需要消息级有界发送队列') | ||
| 27 | + local f = { tasks = {}, timers = {}, writes = {}, results = {}, now = 100, owner = {} } | ||
| 28 | + f.queue = Tx.new({ | ||
| 29 | + defer = function(fn) | ||
| 30 | + f.tasks[#f.tasks + 1] = fn | ||
| 31 | + end, | ||
| 32 | + timeout = function(_, fn) | ||
| 33 | + f.timers[#f.timers + 1] = fn | ||
| 34 | + end, | ||
| 35 | + now = function() | ||
| 36 | + return f.now | ||
| 37 | + end, | ||
| 38 | + write = function(fd, data) | ||
| 39 | + f.writes[#f.writes + 1] = { fd, data } | ||
| 40 | + if f.on_write then | ||
| 41 | + return f.on_write(fd, data) | ||
| 42 | + end | ||
| 43 | + return true | ||
| 44 | + end, | ||
| 45 | + }) | ||
| 46 | + function f:add(data, som, eom, kind) | ||
| 47 | + return self.queue:enqueue(self.owner, { | ||
| 48 | + key = 'peer:tag', | ||
| 49 | + fd = 9, | ||
| 50 | + som = som, | ||
| 51 | + eom = eom, | ||
| 52 | + kind = kind or 'response', | ||
| 53 | + deadline = 200, | ||
| 54 | + valid = function() | ||
| 55 | + return not self.invalid | ||
| 56 | + end, | ||
| 57 | + done = function(ok, reason) | ||
| 58 | + self.results[#self.results + 1] = { ok, reason } | ||
| 59 | + end, | ||
| 60 | + }, data) | ||
| 61 | + end | ||
| 62 | + function f:run() | ||
| 63 | + while #self.tasks > 0 do | ||
| 64 | + table.remove(self.tasks, 1)() | ||
| 65 | + end | ||
| 66 | + end | ||
| 67 | + return f | ||
| 68 | +end | ||
| 69 | + | ||
| 70 | +function TestWriteService:test_message_is_admitted_before_any_fragment_is_written() | ||
| 71 | + local f = self:fixture() | ||
| 72 | + lu.assertTrue(f:add('first', true, false)) | ||
| 73 | + lu.assertEquals(#f.tasks, 0) | ||
| 74 | + lu.assertTrue(f:add('last', false, true)) | ||
| 75 | + lu.assertEquals(#f.tasks, 1) | ||
| 76 | + lu.assertEquals(#f.writes, 0) | ||
| 77 | + f:run() | ||
| 78 | + lu.assertEquals(f.writes, { { 9, 'first' }, { 9, 'last' } }) | ||
| 79 | + lu.assertEquals(f.results, { { true, 'written' } }) | ||
| 80 | + lu.assertEquals({ f.queue.count, f.queue.bytes }, { 0, 0 }) | ||
| 81 | +end | ||
| 82 | + | ||
| 83 | +function TestWriteService:test_full_response_pool_preserves_requester_and_control_capacity() | ||
| 84 | + local f = self:fixture() | ||
| 85 | + for _ = 1, 40 do | ||
| 86 | + lu.assertTrue(f:add('r', true, true)) | ||
| 87 | + end | ||
| 88 | + lu.assertFalse(f:add('overflow', true, true)) | ||
| 89 | + for _ = 1, 16 do | ||
| 90 | + lu.assertTrue(f:add('q', true, true, 'request')) | ||
| 91 | + end | ||
| 92 | + for _ = 1, 8 do | ||
| 93 | + lu.assertTrue(f:add('c', true, true, 'control')) | ||
| 94 | + end | ||
| 95 | + lu.assertFalse(f:add('overflow', true, true, 'control')) | ||
| 96 | + lu.assertEquals(f.queue.count, 64) | ||
| 97 | + lu.assertEquals(#f.tasks, 1) | ||
| 98 | + f:run() | ||
| 99 | + lu.assertEquals(#f.writes, 64) | ||
| 100 | + lu.assertTrue(f:add('again', true, true)) | ||
| 101 | + f:run() | ||
| 102 | + lu.assertEquals(#f.writes, 65) | ||
| 103 | +end | ||
| 104 | + | ||
| 105 | +function TestWriteService:test_byte_budget_and_failed_partial_message_do_not_send_prefix() | ||
| 106 | + local f = self:fixture() | ||
| 107 | + for _ = 1, 11 do | ||
| 108 | + lu.assertTrue(f:add(string.rep('x', 16384), true, true)) | ||
| 109 | + end | ||
| 110 | + lu.assertFalse(f:add('x', true, true)) | ||
| 111 | + for _ = 1, 4 do | ||
| 112 | + lu.assertTrue(f:add(string.rep('q', 16384), true, true, 'request')) | ||
| 113 | + end | ||
| 114 | + lu.assertTrue(f:add(string.rep('c', 16384), true, true, 'control')) | ||
| 115 | + lu.assertFalse(f:add('x', true, true)) | ||
| 116 | + lu.assertEquals(f.queue.bytes, 262144) | ||
| 117 | + f:run() | ||
| 118 | + lu.assertTrue(f:add('first', true, false)) | ||
| 119 | + lu.assertFalse(f:add(string.rep('x', 16384), false, true)) | ||


🟡 Medium Priority
改动位置:src/lualib/mctp_discovery.lua:155,把
self:set_discovered(false)从"应答之后"移到"应答之前"。失败链:
set_discovered(false)同步触发on_discovery_changed:emit(false)(mctp_discovery.lua:82-89;signal 为同步派发,mctp_app.lua:145 需显式 next_tick 才能延后,可佐证),监听者调用self.mctp_engine:reset();c_mctp_engine:reset()(mctp_engine.lua:311-321)先执行self.owner_eid = 0; self.bmc_eid = 0; self.bmc_phy_addr = 0,之后才轮到第 158 行的self.ep:response(...)。问题:第 156 行
set_owner_eid(eid, phy_addr)只恢复了 owner,bmc_eid/bmc_phy_addr没有恢复。而c_ep_mctp_ctrl:response(ep_mctp_ctrl.lua:57-59)用mctp_ctx.src = self.mctp_engine.bmc_eid、dest = owner_eid组帧,add_pcie_vdm_ctx又写pcie_vdm_ctx.bmc_phy_addr = mctp_engine.bmc_phy_addr。于是在重发现(discovered 原为 true)场景下,本条 PrepareForEndpointDiscovery 应答的源 EID 变成 0、bmc_phy_addr 变成 0;改动前应答在 reset 之前组帧,携带的是上一轮已分配的 bmc_eid/bmc_phy_addr。对端若按 src EID/phy_addr 校验或路由,握手应答可能被丢弃,重发现停在握手阶段。测试 test_rediscovery_cancels_old_work_but_sends_control_response(test/unit/test_write_service.lua:363-386)只断言
#f.writes == 1,未校验写出帧头的 src/phy_addr,覆盖不到该差异。建议:在 set_discovered(false) 触发 reset 前保存 bmc_eid/bmc_phy_addr,reset 后用请求中的值恢复;或让 control 应答改用请求携带的 src/phy_addr 组帧,避免应答帧 src EID=0、bmc_phy_addr=0。