| @@ -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.16] - 2026-10-26 | ||
| 4 | +隔离双向事务,按来源和已登记响应分配重组资源,清理查询异常/复位残留并修复PCIe补齐越界。 | ||
| 5 | + | ||
| 3 | ## [1.130.15] - 2026-08-25 | 6 | ## [1.130.15] - 2026-08-25 |
| 4 | 更新版本号,触发流水线推包 | 7 | 更新版本号,触发流水线推包 |
| 5 | 8 | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | +# 双向事务隔离与共享资源验证 | ||
| 2 | + | ||
| 3 | +VDPCI、NC-SI和通用消息仅允许响应方向完成本端查询;VDPCI继续上报主动通知。NC-SI原端点没有主动报文上报接口,本改动也不宣告AEN通知支持;VDPCI的on_receive契约不能套用到NC-SI。通用端点原本没有主动通知契约,TO=1保留过滤并输出DEBUG类型/来源/Tag。PCIe补齐只使用resize,修复尾后越界写入。 | ||
| 4 | + | ||
| 5 | +重组键包含介质、双向物理地址、双向EID、Tag和TO。最多32项,每消息4096B,包间100ms、总期限1s;新片到达同样先执行100ms包间检查;250ms空闲巡检只决定空闲回收粒度,不把包间窗口延长到250ms或1s。拒绝新SOM时保留同键合法半包,通过准入后才替换;容量检查先扣除被替换项,满池仍允许合法重启。普通报文16项、同来源4项;另16项仅留给本端已登记且传输键和消息类型匹配的预期响应,TO=0本身不获优先权。全局预期响应最多64项;超时、释放、EID变化和复位回收半包,旧令牌不能清理新登记。 | ||
| 6 | + | ||
| 7 | +同路由的所有上层协议共享8个Tag占用,发送前登记预期响应;发送失败、消费回调异常、查询结束和复位释放。NVMe More Processing按现有等待期限续期,不复活过期登记;旧协程不得擦除复位后创建的同键等待项。 | ||
| 8 | + | ||
| 9 | +## 验证归属 | ||
| 10 | + | ||
| 11 | +- 原基础快照bfb0756曾为131/134通过;两处旧discovery测试漏传ep_name,修正后3c6f740d为134/134。本说明不把历史失败或通过混作当前分支结果。 | ||
| 12 | +- 共享准入集成候选4620c09完整167/167通过;其中11项新增覆盖可信响应配额、单来源配额、占用续期、跨类型Tag、异常/复位清理。生产核心ASan/UBSan及精确时钟验证19.999/20ms和续期38.999/39ms边界;本拆分分支单独重编译并运行完整回归,结果见PR自验证报告。 | ||
| 13 | +- 候选4620c09在173.6.0.14专用43000四卡实测:44次Base/时间/错误码应答成功,28条通知(52帧)全部上报,5次无应答查询均约3.01s超时,NC-SI前后各8次成功;BMC应答桩为空。启动初次探测路由未齐全,重试前保留失败;通知轮库存就绪标志仍为false,不宣告该轮库存验收通过。 | ||
| 14 | +- 候选原始源码、运行库、安装收据、失败及成功日志位于任务交付目录的`evidence/mctpd-admission-20261026/`。测试后43000已停止、PID0,其他31个容器状态未变,控制台18443和正式39443均HTTP200。 | ||
| 15 | + | ||
| 16 | +## 尚未覆盖 | ||
| 17 | + | ||
| 18 | +本PR不含PLDM编解码/业务(独立PR !13/!12)。统一异步TX完成、完整跨接口/路由代次、Control专用配额、单包限流、真实NVMe More Processing最终应答和阶段2仍待实现/验收。单元测试中的介质字段不等于实际完整SMBus接口身份;现有Lua等待键尚未完成全部关联维度改造。 | ||
| 19 | + | ||
| 20 | +按peer失效接口`invalidate_peer(medium_type, remote, eid)`清理匹配重组并终结对应响应预约,保留其他介质、物理地址及EID。旧Tag保持隔离至原查询截止后再加1秒消息寿命,迟到帧丢弃、旧协程release不能提前解除;隔离记录仍计入64项总预算。后续路由管理调用该接口;本PR不声称已完成上层重绑接线,期限外任意迟到和本端复位后的Tag重用仍需产品时限约束。 | ||
| 21 | + | ||
| 22 | +`Send`同样遵守传输Tag占用:8个Tag均忙时明确失败并计入not_send_count,不复用在途查询Tag、不静默丢弃;调用者需要处理背压并按业务策略重试,释放后恢复。 | ||
| @@ -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.16", |
| 6 | "author": "Huawei", | 6 | "author": "Huawei", |
| 7 | "license": "Mulan PSL v2", | 7 | "license": "Mulan PSL v2", |
| 8 | "options": { | 8 | "options": { |
| @@ -386,6 +386,13 @@ static void register_transport_protocols(lua_State *L, luawrap::stack_table &t) | |||
| 386 | cls.def_property("m_wrong_seq_cnt", c_func_wrap(L, mctp_protocol::get_wrong_seq_cnt), | 386 | cls.def_property("m_wrong_seq_cnt", c_func_wrap(L, mctp_protocol::get_wrong_seq_cnt), |
| 387 | c_func_wrap(L, mctp_protocol::set_wrong_seq_cnt)); | 387 | c_func_wrap(L, mctp_protocol::set_wrong_seq_cnt)); |
| 388 | cls.set("set_control_rx_handler", c_func_wrap(L, set_control_rx_handler)); | 388 | cls.set("set_control_rx_handler", c_func_wrap(L, set_control_rx_handler)); |
| 389 | + cls.set("reset_reassembly", c_func_wrap(L, mctp_protocol::reset_reassembly)); | ||
| 390 | + cls.set("invalidate_peer", c_func_wrap(L, mctp_protocol::invalidate_peer)); | ||
| 391 | + cls.set("reserve_response", c_func_wrap(L, mctp_protocol::reserve_response)); | ||
| 392 | + cls.set("response_reserved", c_func_wrap(L, mctp_protocol::response_reserved)); | ||
| 393 | + cls.set("renew_response", c_func_wrap(L, mctp_protocol::renew_response)); | ||
| 394 | + cls.set("release_response", c_func_wrap(L, mctp_protocol::release_response)); | ||
| 395 | + cls.set("expire_reassembly", c_func_wrap(L, mctp_protocol::expire_reassembly)); | ||
| 389 | t.set("mctp_protocol", cls); | 396 | t.set("mctp_protocol", cls); |
| 390 | }); | 397 | }); |
| 391 | } | 398 | } |
| @@ -162,8 +162,7 @@ bool pcie_vdm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const | |||
| 162 | 162 | ||
| 163 | m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end()); | 163 | m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end()); |
| 164 | if (pad_len > 0) { | 164 | if (pad_len > 0) { |
| 165 | - m_tx_buf.resize(m_tx_buf.size() + pad_len); | 165 | + m_tx_buf.resize(m_tx_buf.size() + pad_len, 0); |
| 166 | - std::fill_n(m_tx_buf.begin() + m_tx_buf.size(), pad_len, 0); | ||
| 167 | } | 166 | } |
| 168 | 167 | ||
| 169 | return protocol::push(ctx, slice(m_tx_buf), medium_type); | 168 | return protocol::push(ctx, slice(m_tx_buf), medium_type); |
| @@ -11,7 +11,9 @@ | |||
| 11 | */ | 11 | */ |
| 12 | #ifndef MCTP_PROTOCOL_H | 12 | #ifndef MCTP_PROTOCOL_H |
| 13 | #define MCTP_PROTOCOL_H | 13 | #define MCTP_PROTOCOL_H |
| 14 | +#include <chrono> | ||
| 14 | #include <map> | 15 | #include <map> |
| 16 | +#include <tuple> | ||
| 15 | 17 | ||
| 16 | #include "mctp_cmds.h" | 18 | #include "mctp_cmds.h" |
| 17 | #include "protocol.h" | 19 | #include "protocol.h" |
| @@ -76,9 +78,21 @@ enum class mctp_route_policy { | |||
| 76 | }; | 78 | }; |
| 77 | 79 | ||
| 78 | struct package_ctx { | 80 | struct package_ctx { |
| 79 | - uint8_t last_seq; | 81 | + uint64_t reservation = 0; |
| 80 | - size_t fragment_size; | 82 | + uint8_t last_seq; |
| 81 | - buffer_t m_buf; | 83 | + size_t fragment_size; |
| 84 | + buffer_t m_buf; | ||
| 85 | + std::chrono::steady_clock::time_point started; | ||
| 86 | + std::chrono::steady_clock::time_point last_packet; | ||
| 87 | +}; | ||
| 88 | + | ||
| 89 | +using message_key = std::tuple<uint32_t, uint16_t, uint16_t, uint8_t, uint8_t, uint8_t, bool>; | ||
| 90 | + | ||
| 91 | +struct response_reservation { | ||
| 92 | + uint64_t token; | ||
| 93 | + uint8_t message_type; | ||
| 94 | + std::chrono::steady_clock::time_point deadline; | ||
| 95 | + bool retired = false; | ||
| 82 | }; | 96 | }; |
| 83 | 97 | ||
| 84 | using control_rx_handler_t = std::function<bool(const protocol_ctx &, const mctp_ctrl_msg &, size_t, const int &)>; | 98 | using control_rx_handler_t = std::function<bool(const protocol_ctx &, const mctp_ctrl_msg &, size_t, const int &)>; |
| @@ -103,7 +117,14 @@ public: | |||
| 103 | uint32_t get_wrong_seq_cnt() const; | 117 | uint32_t get_wrong_seq_cnt() const; |
| 104 | void set_wrong_seq_cnt(uint32_t wrong_seq_cnt); | 118 | void set_wrong_seq_cnt(uint32_t wrong_seq_cnt); |
| 105 | 119 | ||
| 106 | - void set_control_rx_handler(std::optional<control_rx_handler_t> h); | 120 | + void set_control_rx_handler(std::optional<control_rx_handler_t> h); |
| 121 | + void reset_reassembly(); | ||
| 122 | + void invalidate_peer(uint32_t medium_type, uint16_t remote, uint8_t eid); | ||
| 123 | + void expire_reassembly(); | ||
| 124 | + uint64_t reserve_response(const protocol_ctx &ctx, uint32_t medium_type, uint8_t message_type, uint32_t timeout_ms); | ||
| 125 | + bool response_reserved(const protocol_ctx &ctx, uint32_t medium_type); | ||
| 126 | + bool renew_response(uint64_t token, uint32_t timeout_ms); | ||
| 127 | + void release_response(uint64_t token); | ||
| 107 | 128 | ||
| 108 | mctp_route_policy get_route_policy() const; | 129 | mctp_route_policy get_route_policy() const; |
| 109 | void set_route_policy(mctp_route_policy rp); | 130 | void set_route_policy(mctp_route_policy rp); |
| @@ -123,14 +144,8 @@ protected: | |||
| 123 | bool process_ctrl_msg(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type); | 144 | bool process_ctrl_msg(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type); |
| 124 | bool is_ctrl_message(const slice_t &data); | 145 | bool is_ctrl_message(const slice_t &data); |
| 125 | 146 | ||
| 126 | - inline uint32_t make_msg_id(uint8_t src, uint8_t dest, uint8_t tag) | 147 | + message_key make_msg_id(const protocol_ctx &ctx, const mctp_hdr &hdr, uint32_t medium_type) const; |
| 127 | - { | 148 | + message_key response_key(const protocol_ctx &ctx, uint32_t medium_type) const; |
| 128 | - mctp_hdr h = {.v = 0}; | ||
| 129 | - h.u.src = src; | ||
| 130 | - h.u.dest = dest; | ||
| 131 | - h.u.msg_tag = tag; | ||
| 132 | - return h.v; | ||
| 133 | - } | ||
| 134 | 149 | ||
| 135 | private: | 150 | private: |
| 136 | uint8_t m_version; | 151 | uint8_t m_version; |
| @@ -141,8 +156,10 @@ private: | |||
| 141 | uint32_t m_wrong_seq_cnt; | 156 | uint32_t m_wrong_seq_cnt; |
| 142 | mctp_route_policy m_route_policy; | 157 | mctp_route_policy m_route_policy; |
| 143 | 158 | ||
| 144 | - buffer_t m_tx_buf; | 159 | + buffer_t m_tx_buf; |
| 145 | - std::map<uint32_t, package_ctx> m_rx_ctx; | 160 | + std::map<message_key, package_ctx> m_rx_ctx; |
| 161 | + std::map<message_key, response_reservation> m_responses; | ||
| 162 | + uint64_t m_next_reservation = 0; | ||
| 146 | 163 | ||
| 147 | std::optional<control_rx_handler_t> m_control_rx; | 164 | std::optional<control_rx_handler_t> m_control_rx; |
| 148 | }; | 165 | }; |
| @@ -51,8 +51,9 @@ function c_vdpci_endpoint:send(_, data, req_ctx) | |||
| 51 | end | 51 | end |
| 52 | 52 | ||
| 53 | function c_vdpci_endpoint:process(_, data) | 53 | function c_vdpci_endpoint:process(_, data) |
| 54 | - local ok = self.mctp_engine:wakeup_request(self:make_rsp_id(nil), data) | 54 | + -- 对端拥有 Tag 的通知不能完成本端 requester,即使双方恰好使用相同 Tag。 |
| 55 | - -- 返回false代表没有对应的请求,通过信号的方式将接收的数据发送出去 | 55 | + local ok = not self.mctp_ctx.tag_owner and self.mctp_engine:wakeup_request(self:make_rsp_id(nil), data) |
| 56 | + -- 保留既有未匹配报文的通知契约;此处不把通知转换为需要回复的请求。 | ||
🔵 Low Priority 本行把“只有 TO=0(对端不是 Tag 拥有者,即响应方向)才允许 wakeup_request”确立为 endpoint 处理接收报文的约定,ep_pldm.lua:120 同步采用;但同一约定下的兄弟实现仍是旧形式:ep_ncsi.lua:120-124 失败模式:以本 PR 修复的同一机制,对端 TO=1 的主动报文只要恰好使用与本端待办查询相同的 Tag(NC-SI 按 IID、general 按 Tag),就会把该等待项唤醒并塞入通知数据,造成查询拿到错误响应、通知丢失——正是 issue #11 记录的“28 条通知只上报 26 条”同类缺陷,只是换到了 NC-SI / 通用消息类型上。 这是可选的后续一致性改进(本 PR 已在描述中限定范围),不是本次必须修复项;若要闭环该约定,建议把 建议:在 c_ep_mctp 中提供一个共享辅助方法(如 ![]() ![]() Incorrect? | |||
| 56 | if not ok then | 57 | if not ok then |
| 57 | self.on_receive:emit(data) | 58 | self.on_receive:emit(data) |
| 58 | end | 59 | end |
| @@ -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, |
| @@ -14,12 +14,15 @@ 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') |
| 16 | local c_general_endpoint = require('mctp_endpoints.ep_general') | 16 | local c_general_endpoint = require('mctp_endpoints.ep_general') |
| 17 | +local c_ncsi_endpoint = require('mctp_endpoints.ep_ncsi') | ||
| 18 | +local mctp = require('mctp') | ||
| 17 | 19 | ||
| 18 | TestEndpointGeneral = {} | 20 | TestEndpointGeneral = {} |
| 19 | 21 | ||
| 20 | function TestEndpointGeneral:setupClass() | 22 | function TestEndpointGeneral:setupClass() |
| 21 | self.general_endpoint = | 23 | self.general_endpoint = |
| 22 | c_general_endpoint.new(c_mctp_engine.new(), 0, 0, mctp_def.PHY_MEDIUM_PCI, mctp_def.ENDPOINT_IS_DISCOVERY) | 24 | c_general_endpoint.new(c_mctp_engine.new(), 0, 0, mctp_def.PHY_MEDIUM_PCI, mctp_def.ENDPOINT_IS_DISCOVERY) |
| 25 | + self.general_endpoint:set_msg_type(0x0f) | ||
| 23 | end | 26 | end |
| 24 | 27 | ||
| 25 | function TestEndpointGeneral:teardownClass() end | 28 | function TestEndpointGeneral:teardownClass() end |
| @@ -43,3 +46,184 @@ function TestEndpointGeneral:test_request() | |||
| 43 | ret, _ = pcall(self.send, self, req_bin) | 46 | ret, _ = pcall(self.send, self, req_bin) |
| 44 | lu.assertEquals(ret, true) | 47 | lu.assertEquals(ret, true) |
| 45 | end | 48 | end |
| 49 | + | ||
| 50 | +function TestEndpointGeneral:test_only_response_direction_completes_general_and_ncsi() | ||
| 51 | + for _, case in ipairs({ | ||
| 52 | + { endpoint = c_general_endpoint, context = mctp.general_ctx }, | ||
| 53 | + { endpoint = c_ncsi_endpoint, context = mctp.ncsi_ctx, iid = 1 }, | ||
| 54 | + }) do | ||
| 55 | + local engine = c_mctp_engine.new() | ||
| 56 | + local ep = case.endpoint.new(engine, 0x2000, 9, mctp_def.PHY_MEDIUM_PCI, mctp_def.ENDPOINT_IS_DISCOVERY) | ||
| 57 | + local key = ep:prepare_mctp_request(case.iid) | ||
| 58 | + local pending = { co = coroutine.running(), rsp = 'waiting' } | ||
| 59 | + engine.rsp_wait_list[key] = pending | ||
| 60 | + local transport = mctp.mctp_ctx.new() | ||
| 61 | + transport:set_prev(ep.pcie_vdm_ctx) | ||
| 62 | + transport.src, transport.dest, transport.tag, transport.tag_owner = 9, 11, 0, true | ||
| 63 | + local ctx = case.context.new() | ||
| 64 | + ctx:set_prev(transport) | ||
| 65 | + if case.iid then | ||
| 66 | + ctx.iid = case.iid | ||
| 67 | + end | ||
| 68 | + ep:rx(ctx, 'notification') | ||
| 69 | + lu.assertEquals(pending.rsp, 'waiting') | ||
| 70 | + lu.assertIsNil(pending.err) | ||
| 71 | + lu.assertEquals(engine.rsp_wait_list[key], pending) | ||
| 72 | + transport.tag_owner = false | ||
| 73 | + ep:rx(ctx, 'response') | ||
| 74 | + lu.assertEquals(pending.rsp, 'response') | ||
| 75 | + end | ||
| 76 | +end | ||
| 77 | + | ||
| 78 | +TestRequesterReservations = {} | ||
| 79 | +local skynet = require('skynet') | ||
| 80 | +local c_ep_mctp = require('mctp_endpoints.ep_mctp') | ||
| 81 | + | ||
| 82 | +function TestRequesterReservations:setup() | ||
| 83 | + self.engine = c_mctp_engine.new(nil, 1) | ||
| 84 | + self.engine.bmc_eid, self.engine.bmc_phy_addr = 10, 0x1000 | ||
| 85 | + self.threads, self.tags = {}, {} | ||
| 86 | + self.old_wait = skynet.wait | ||
| 87 | + skynet.wait = coroutine.yield | ||
| 88 | + self.engine.general_proto:set_capture_handler(function(is_push, ctx) | ||
| 89 | + if is_push then | ||
| 90 | + self.tags[#self.tags + 1] = ctx:get('tag', 'mctp_ctx') or -1 | ||
| 91 | + end | ||
| 92 | + end) | ||
| 93 | +end | ||
| 94 | + | ||
| 95 | +function TestRequesterReservations:teardown() | ||
| 96 | + skynet.wait = self.old_wait | ||
| 97 | + for _, co in ipairs(self.threads) do | ||
| 98 | + if coroutine.status(co) == 'suspended' then | ||
| 99 | + coroutine.resume(co) | ||
| 100 | + end | ||
| 101 | + end | ||
| 102 | +end | ||
| 103 | + | ||
| 104 | +function TestRequesterReservations:peer(msg_type) | ||
| 105 | + local ep = c_general_endpoint.new(self.engine, 0x20, 9, 15, mctp_def.ENDPOINT_IS_DISCOVERY) | ||
| 106 | + ep:set_msg_type(msg_type) | ||
| 107 | + return ep | ||
| 108 | +end | ||
| 109 | + | ||
| 110 | +function TestRequesterReservations:begin(ep) | ||
| 111 | + local id = #self.threads + 1 | ||
| 112 | + local co = coroutine.create(function() | ||
| 113 | + return c_ep_mctp.request(ep, id, function() | ||
| 114 | + ep.general_ctx.msg_type = ep:get_msg_type() | ||
| 115 | + self.engine.general_proto:push('query', ep.general_ctx, ep:make_msg_key(ep:get_msg_type())) | ||
| 116 | + end, 1000) | ||
| 117 | + end) | ||
| 118 | + self.threads[#self.threads + 1] = co | ||
| 119 | + local ok, err = coroutine.resume(co) | ||
| 120 | + lu.assertTrue(ok, tostring(err)) | ||
| 121 | + lu.assertEquals(coroutine.status(co), 'suspended') | ||
| 122 | + return co | ||
| 123 | +end | ||
| 124 | + | ||
| 125 | +function TestRequesterReservations:test_tags_are_shared_across_message_types_and_released() | ||
| 126 | + local first, second = self:peer(15), self:peer(16) | ||
| 127 | + local one = self:begin(first) | ||
| 128 | + self:begin(second) | ||
| 129 | + lu.assertEquals(self.tags, { 0, 1 }) | ||
| 130 | + for _ = 1, 6 do | ||
| 131 | + self:begin(first) | ||
| 132 | + end | ||
| 133 | + lu.assertEquals(self.tags, { 0, 1, 2, 3, 4, 5, 6, 7 }) | ||
| 134 | + local ok = pcall(c_ep_mctp.request, second, nil, function() | ||
| 135 | + error('callback must not run when all tags are occupied') | ||
| 136 | + end, 1000) | ||
| 137 | + lu.assertFalse(ok) | ||
| 138 | + lu.assertEquals(#self.tags, 8) | ||
| 139 | + lu.assertTrue(coroutine.resume(one)) | ||
| 140 | + self:begin(second) | ||
| 141 | + lu.assertEquals(self.tags[9], 0) | ||
| 142 | +end | ||
| 143 | + | ||
| 144 | +function TestRequesterReservations:test_send_failure_releases_reservation() | ||
| 145 | + local ep = self:peer(15) | ||
| 146 | + lu.assertFalse(pcall(c_ep_mctp.request, ep, nil, function() | ||
| 147 | + error('send failed') | ||
| 148 | + end, 1000)) | ||
| 149 | + lu.assertFalse(self.engine.mctp_proto:response_reserved(ep.mctp_ctx, 15)) | ||
| 150 | + ep.msg_tag = 0 | ||
| 151 | + self:begin(ep) | ||
| 152 | + lu.assertEquals(self.tags, { 0 }) | ||
| 153 | +end | ||
| 154 | + | ||
| 155 | +function TestRequesterReservations:test_reset_releases_before_waiting_coroutine_resumes() | ||
| 156 | + local ep = self:peer(15) | ||
| 157 | + self:begin(ep) | ||
| 158 | + lu.assertTrue(self.engine.mctp_proto:response_reserved(ep.mctp_ctx, 15)) | ||
| 159 | + self.engine:reset_all_request() | ||
| 160 | + lu.assertFalse(self.engine.mctp_proto:response_reserved(ep.mctp_ctx, 15)) | ||
| 161 | +end | ||
| 162 | + | ||
| 163 | +function TestRequesterReservations:test_post_response_callback_failure_releases_resources() | ||
| 164 | + local ep = self:peer(15) | ||
| 165 | + skynet.wait = self.old_wait | ||
| 166 | + lu.assertFalse(pcall(c_ep_mctp.request, ep, nil, function() end, 1000, function() | ||
| 167 | + error('consumer failed') | ||
| 168 | + end)) | ||
| 169 | + lu.assertEquals(self.engine.rsp_wait_list, {}) | ||
| 170 | + lu.assertFalse(self.engine.mctp_proto:response_reserved(ep.mctp_ctx, 15)) | ||
| 171 | +end | ||
| 172 | + | ||
| 173 | +function TestRequesterReservations:test_reset_during_send_failure_keeps_replacement_waiter() | ||
| 174 | + local replacement = { co = coroutine.running() } | ||
| 175 | + local result = self.engine:request('reused', function() | ||
| 176 | + self.engine:reset_all_request() | ||
| 177 | + self.engine.rsp_wait_list.reused = replacement | ||
| 178 | + error('old send failed after reset') | ||
| 179 | + end, 1000) | ||
| 180 | + lu.assertEquals(result, mctp_def.E_SEND_FAILED) | ||
| 181 | + lu.assertIs(self.engine.rsp_wait_list.reused, replacement) | ||
| 182 | +end | ||
| 183 | + | ||
| 184 | +function TestRequesterReservations:test_global_capacity_rejects_before_send_then_recovers() | ||
| 185 | + local tokens, proto = {}, self.engine.mctp_proto | ||
| 186 | + local ep = self:peer(15) | ||
| 187 | + for source = 20, 27 do | ||
| 188 | + ep.eid = source | ||
| 189 | + for _ = 1, 8 do | ||
| 190 | + ep:prepare_mctp_request() | ||
| 191 | + local token = proto:reserve_response(ep.mctp_ctx, 15, 15, 1000) | ||
| 192 | + lu.assertTrue(token > 0) | ||
| 193 | + tokens[#tokens + 1] = token | ||
| 194 | + end | ||
| 195 | + end | ||
| 196 | + ep.eid = 9 | ||
| 197 | + local sent = false | ||
| 198 | + lu.assertFalse(pcall(c_ep_mctp.request, ep, nil, function() | ||
| 199 | + sent = true | ||
| 200 | + end, 1000)) | ||
| 201 | + lu.assertFalse(sent) | ||
| 202 | + lu.assertEquals(self.engine.rsp_wait_list, {}) | ||
| 203 | + lu.assertEquals(ep.not_send_count, 1) | ||
| 204 | + proto:release_response(tokens[1]) | ||
| 205 | + self:begin(ep) | ||
| 206 | + lu.assertEquals(#self.tags, 1) | ||
| 207 | + for _, token in ipairs(tokens) do | ||
| 208 | + proto:release_response(token) | ||
| 209 | + end | ||
| 210 | +end | ||
| 211 | + | ||
| 212 | +function TestRequesterReservations:test_send_reports_backpressure_without_reusing_active_tags() | ||
| 213 | + local ep, sent = self:peer(15), 0 | ||
| 214 | + for _ = 1, 8 do | ||
| 215 | + self:begin(ep) | ||
| 216 | + end | ||
| 217 | + local ok, err = pcall(c_ep_mctp.send, ep, function() | ||
| 218 | + sent = sent + 1 | ||
| 219 | + end) | ||
| 220 | + lu.assertFalse(ok) | ||
| 221 | + lu.assertStrContains(tostring(err), 'all message tags are occupied') | ||
| 222 | + lu.assertEquals(sent, 0) | ||
| 223 | + lu.assertEquals(ep.not_send_count, 1) | ||
| 224 | + lu.assertTrue(coroutine.resume(self.threads[1])) | ||
| 225 | + c_ep_mctp.send(ep, function() | ||
| 226 | + sent = sent + 1 | ||
| 227 | + end) | ||
| 228 | + lu.assertEquals(sent, 1) | ||
| 229 | +end | ||
| @@ -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') |
| @@ -43,3 +43,60 @@ function TestEndpointVdPCI:test_request() | |||
| 43 | ret, _ = pcall(self.send, self, req_bin) | 43 | ret, _ = pcall(self.send, self, req_bin) |
| 44 | lu.assertEquals(ret, true) | 44 | lu.assertEquals(ret, true) |
| 45 | end | 45 | end |
| 46 | + | ||
| 47 | +local function pending_endpoint() | ||
| 48 | + local engine = c_mctp_engine.new(nil, 1) | ||
| 49 | + engine.bmc_eid = 11 | ||
| 50 | + local ep = c_vd_pci_endpoint.new(engine, 0x2000, 9, mctp_def.PHY_MEDIUM_PCI, mctp_def.ENDPOINT_IS_DISCOVERY) | ||
| 51 | + local key = ep:prepare_mctp_request() | ||
| 52 | + local pending = { co = coroutine.running(), rsp = 'still waiting' } | ||
| 53 | + engine.rsp_wait_list[key] = pending | ||
| 54 | + local received = {} | ||
| 55 | + ep.on_receive:on(function(data) | ||
| 56 | + received[#received + 1] = data | ||
| 57 | + end) | ||
| 58 | + local ctx = mctp.mctp_ctx.new() | ||
| 59 | + ctx:set_prev(ep.pcie_vdm_ctx) | ||
| 60 | + ctx.src, ctx.dest, ctx.tag = 9, 11, 0 | ||
| 61 | + return ep, pending, received, ctx, key | ||
| 62 | +end | ||
| 63 | + | ||
| 64 | +function TestEndpointVdPCI:test_same_tag_notification_does_not_complete_request() | ||
| 65 | + local ep, pending, received, ctx, key = pending_endpoint() | ||
| 66 | + ctx.tag_owner = true | ||
| 67 | + for tag = 0, 7 do | ||
| 68 | + ctx.tag = tag | ||
| 69 | + ep:rx(ctx, 'notification' .. tag) | ||
| 70 | + end | ||
| 71 | + lu.assertEquals(pending.rsp, 'still waiting') | ||
| 72 | + lu.assertIsNil(pending.err) | ||
| 73 | + lu.assertEquals(ep.mctp_engine.rsp_wait_list[key], pending) | ||
| 74 | + lu.assertEquals(received, { | ||
| 75 | + 'notification0', | ||
| 76 | + 'notification1', | ||
| 77 | + 'notification2', | ||
| 78 | + 'notification3', | ||
| 79 | + 'notification4', | ||
| 80 | + 'notification5', | ||
| 81 | + 'notification6', | ||
| 82 | + 'notification7', | ||
| 83 | + }) | ||
| 84 | +end | ||
| 85 | + | ||
| 86 | +function TestEndpointVdPCI:test_matching_response_completes_request() | ||
| 87 | + local ep, pending, received, ctx = pending_endpoint() | ||
| 88 | + ctx.tag_owner = false | ||
| 89 | + ep:rx(ctx, 'response') | ||
| 90 | + lu.assertEquals(pending.rsp, 'response') | ||
| 91 | + lu.assertEquals(received, {}) | ||
| 92 | +end | ||
| 93 | + | ||
| 94 | +function TestEndpointVdPCI:test_unmatched_response_keeps_legacy_notification() | ||
| 95 | + local ep, pending, received, ctx, key = pending_endpoint() | ||
| 96 | + ctx.tag_owner, ctx.tag = false, 1 | ||
| 97 | + ep:rx(ctx, 'legacy notification') | ||
| 98 | + lu.assertEquals(pending.rsp, 'still waiting') | ||
| 99 | + lu.assertIsNil(pending.err) | ||
| 100 | + lu.assertEquals(ep.mctp_engine.rsp_wait_list[key], pending) | ||
| 101 | + lu.assertEquals(received, { 'legacy notification' }) | ||
| 102 | +end | ||
| @@ -24,6 +24,64 @@ local getpid = utils_core.getpid | |||
| 24 | local mctp_utils = require('mctp_utils') | 24 | local mctp_utils = require('mctp_utils') |
| 25 | TestMctpEngine = {} | 25 | TestMctpEngine = {} |
| 26 | 26 | ||
| 27 | +TestMctpReassemblyTimer = {} | ||
| 28 | + | ||
| 29 | +function TestMctpReassemblyTimer:setup() | ||
| 30 | + self.original_timer = skynet.timer.new | ||
| 31 | + self.timers, self.expired, self.reset = {}, 0, 0 | ||
| 32 | + skynet.timer.new = function(delay, callback) | ||
| 33 | + local timer = { delay = delay, callback = callback } | ||
| 34 | + self.timers[#self.timers + 1] = timer | ||
| 35 | + return timer | ||
| 36 | + end | ||
| 37 | + self.engine = { | ||
| 38 | + mctp_proto = { | ||
| 39 | + expire_reassembly = function() | ||
| 40 | + self.expired = self.expired + 1 | ||
| 41 | + end, | ||
| 42 | + reset_reassembly = function() | ||
| 43 | + self.reset = self.reset + 1 | ||
| 44 | + end, | ||
| 45 | + }, | ||
| 46 | + } | ||
| 47 | +end | ||
| 48 | + | ||
| 49 | +function TestMctpReassemblyTimer:teardown() | ||
| 50 | + skynet.timer.new = self.original_timer | ||
| 51 | +end | ||
| 52 | + | ||
| 53 | +function TestMctpReassemblyTimer:test_idle_tick_expires_without_receive() | ||
| 54 | + c_mctp_engine.start_reassembly_timer(self.engine) | ||
| 55 | + lu.assertEquals(self.timers[1].delay, 250) | ||
| 56 | + self.timers[1].callback() | ||
| 57 | + lu.assertEquals(self.expired, 1) | ||
| 58 | + lu.assertEquals(#self.timers, 2) | ||
| 59 | + lu.assertEquals(self.timers[2].delay, 250) | ||
| 60 | + c_mctp_engine.stop_reassembly_timer(self.engine) | ||
| 61 | + lu.assertEquals(self.reset, 1) | ||
| 62 | + self.timers[2].callback() | ||
| 63 | + lu.assertEquals(self.expired, 1) | ||
| 64 | + lu.assertEquals(#self.timers, 2) | ||
| 65 | +end | ||
| 66 | + | ||
| 67 | +function TestMctpReassemblyTimer:test_restart_invalidates_old_callbacks() | ||
| 68 | + c_mctp_engine.start_reassembly_timer(self.engine) | ||
| 69 | + c_mctp_engine.start_reassembly_timer(self.engine) | ||
| 70 | + self.timers[1].callback() | ||
| 71 | + lu.assertEquals(self.expired, 0) | ||
| 72 | + lu.assertEquals(#self.timers, 2) | ||
| 73 | + self.timers[2].callback() | ||
| 74 | + lu.assertEquals(self.expired, 1) | ||
| 75 | + c_mctp_engine.stop_reassembly_timer(self.engine) | ||
| 76 | + c_mctp_engine.start_reassembly_timer(self.engine) | ||
| 77 | + self.timers[3].callback() | ||
| 78 | + lu.assertEquals(self.expired, 1) | ||
| 79 | + lu.assertEquals(#self.timers, 4) | ||
| 80 | + self.timers[4].callback() | ||
| 81 | + lu.assertEquals(self.expired, 2) | ||
| 82 | + c_mctp_engine.stop_reassembly_timer(self.engine) | ||
| 83 | +end | ||
| 84 | + | ||
| 27 | function TestMctpEngine:setupClass() | 85 | function TestMctpEngine:setupClass() |
| 28 | -- discovery的init会启动set eid轮询任务,测试环境loop同步死循环, | 86 | -- discovery的init会启动set eid轮询任务,测试环境loop同步死循环, |
| 29 | -- 需在new discovery之前先hook c_tasks使其不自动执行 | 87 | -- 需在new discovery之前先hook c_tasks使其不自动执行 |
| @@ -23,6 +23,277 @@ end | |||
| 23 | 23 | ||
| 24 | local MSG_BYTE = '\x0F' -- general protocol | 24 | local MSG_BYTE = '\x0F' -- general protocol |
| 25 | 25 | ||
| 26 | +TestMctpReassemblyIsolation = {} | ||
| 27 | + | ||
| 28 | +local function setup_reassembly(self) | ||
| 29 | + self.proto = mctp.mctp_protocol.new() | ||
| 30 | + self.proto.pcie_eid, self.proto.smbus_eid = 10, 10 | ||
| 31 | + self.general = mctp.general_protocol.new() | ||
| 32 | + self.proto:add_children(self.general) | ||
| 33 | + self.received = {} | ||
| 34 | + self.general:set_rx_handler(function(ctx, data, medium) | ||
| 35 | + self.received[#self.received + 1] = { data, ctx:get('tag_owner'), medium, ctx:get('remote_phy_addr') } | ||
| 36 | + return true | ||
| 37 | + end) | ||
| 38 | +end | ||
| 39 | + | ||
| 40 | +TestMctpReassemblyIsolation.setup = setup_reassembly | ||
| 41 | + | ||
| 42 | +local function fill_ordinary(self) | ||
| 43 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 44 | + for src = 20, 35 do | ||
| 45 | + lu.assertEquals(self.proto:pop(mctp_hdr(10, src, 1, 0, 0, 1, 0) .. MSG_BYTE .. 'old', ctx, 15), E_OK) | ||
| 46 | + end | ||
| 47 | + return ctx | ||
| 48 | +end | ||
| 49 | + | ||
| 50 | +local function reassembly_pop(self, ctx, medium, src, owner, som, eom, seq, payload) | ||
| 51 | + return self.proto:pop(mctp_hdr(10, src, som, eom, seq, owner, 0) .. payload, ctx, medium) | ||
| 52 | +end | ||
| 53 | + | ||
| 54 | +function TestMctpReassemblyIsolation:test_same_tag_opposite_directions() | ||
| 55 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 56 | + ctx.remote_phy_addr = 0x2000 | ||
| 57 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'request') | ||
| 58 | + reassembly_pop(self, ctx, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'response') | ||
| 59 | + reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'A') | ||
| 60 | + reassembly_pop(self, ctx, 15, 9, 0, 0, 1, 1, 'B') | ||
| 61 | + lu.assertEquals(self.received, { { 'requestA', true, 15, 0x2000 }, { 'responseB', false, 15, 0x2000 } }) | ||
| 62 | +end | ||
| 63 | + | ||
| 64 | +function TestMctpReassemblyIsolation:test_same_tag_different_physical_sources() | ||
| 65 | + local first, second = mctp.pcie_vdm_ctx.new(), mctp.pcie_vdm_ctx.new() | ||
| 66 | + first.remote_phy_addr, second.remote_phy_addr = 0x2000, 0x2100 | ||
| 67 | + reassembly_pop(self, first, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'first') | ||
| 68 | + reassembly_pop(self, second, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'second') | ||
| 69 | + reassembly_pop(self, first, 15, 9, 1, 0, 1, 1, 'A') | ||
| 70 | + reassembly_pop(self, second, 15, 9, 1, 0, 1, 1, 'B') | ||
| 71 | + lu.assertEquals(self.received, { { 'firstA', true, 15, 0x2000 }, { 'secondB', true, 15, 0x2100 } }) | ||
| 72 | +end | ||
| 73 | + | ||
| 74 | +function TestMctpReassemblyIsolation:test_same_tag_different_media() | ||
| 75 | + local pci, smbus = mctp.pcie_vdm_ctx.new(), mctp.sm_bus_ctx.new() | ||
| 76 | + pci.remote_phy_addr, smbus.remote_phy_addr = 32, 32 | ||
| 77 | + reassembly_pop(self, pci, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'pcie') | ||
| 78 | + reassembly_pop(self, smbus, 2, 9, 1, 1, 0, 0, MSG_BYTE .. 'smbus') | ||
| 79 | + reassembly_pop(self, pci, 15, 9, 1, 0, 1, 1, 'A') | ||
| 80 | + reassembly_pop(self, smbus, 2, 9, 1, 0, 1, 1, 'B') | ||
| 81 | + lu.assertEquals(self.received, { { 'pcieA', true, 15, 32 }, { 'smbusB', true, 2, 32 } }) | ||
| 82 | +end | ||
| 83 | + | ||
| 84 | +function TestMctpReassemblyIsolation:test_single_packet_cancels_incomplete_message() | ||
| 85 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 86 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 87 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 1, 0, MSG_BYTE .. 'new') | ||
| 88 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 89 | + lu.assertEquals(self.received, { { 'new', true, 15, 0 } }) | ||
| 90 | +end | ||
| 91 | + | ||
| 92 | +function TestMctpReassemblyIsolation:test_invalid_fragment_size_cancels_message() | ||
| 93 | + for _, eom in ipairs({ 0, 1 }) do | ||
| 94 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 95 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 96 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, eom, 1, 'oversize'), E_FAIL) | ||
| 97 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 98 | + end | ||
| 99 | + lu.assertEquals(self.received, {}) | ||
| 100 | +end | ||
| 101 | + | ||
| 102 | +function TestMctpReassemblyIsolation:test_address_change_cancels_reassembly() | ||
| 103 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 104 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 105 | + self.proto.pcie_eid = 11 | ||
| 106 | + self.proto.pcie_eid = 10 | ||
| 107 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 108 | + lu.assertEquals(self.received, {}) | ||
| 109 | +end | ||
| 110 | + | ||
| 111 | +function TestMctpReassemblyIsolation:test_message_size_is_bounded() | ||
| 112 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 113 | + local first = MSG_BYTE .. string.rep('A', 63) | ||
| 114 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, first), E_OK) | ||
| 115 | + for i = 1, 63 do | ||
| 116 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 0, i % 4, string.rep('B', 64)), E_OK) | ||
| 117 | + end | ||
| 118 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 0, 'X'), E_FAIL) | ||
| 119 | + lu.assertEquals(self.received, {}) | ||
| 120 | +end | ||
| 121 | + | ||
| 122 | +function TestMctpReassemblyIsolation:test_pool_is_bounded_and_existing_messages_survive() | ||
| 123 | + local ctx = fill_ordinary(self) | ||
| 124 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 36, 1, 1, 0, 0, MSG_BYTE .. 'extra'), E_FAIL) | ||
| 125 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 20, 1, 1, 0, 0, MSG_BYTE .. string.rep('X', 4096)), E_FAIL) | ||
| 126 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 20, 1, 0, 1, 1, 'X'), E_OK) | ||
| 127 | + lu.assertEquals(self.received, { { 'oldX', true, 15, 0 } }) | ||
| 128 | +end | ||
| 129 | + | ||
| 130 | +function TestMctpReassemblyIsolation:test_exact_message_limit_is_delivered() | ||
| 131 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 132 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. string.rep('A', 63)) | ||
| 133 | + for i = 1, 62 do | ||
| 134 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 0, i % 4, string.rep('B', 64)), E_OK) | ||
| 135 | + end | ||
| 136 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 3, string.rep('C', 64)), E_OK) | ||
| 137 | + lu.assertEquals( | ||
| 138 | + self.received, | ||
| 139 | + { { string.rep('A', 63) .. string.rep('B', 3968) .. string.rep('C', 64), true, 15, 0 } } | ||
| 140 | + ) | ||
| 141 | +end | ||
| 142 | + | ||
| 143 | +function TestMctpReassemblyIsolation:test_idle_fragments_expire_and_pool_recovers() | ||
| 144 | + local ctx = fill_ordinary(self) | ||
| 145 | + require('mc.utils').msleep(120) | ||
| 146 | + self.proto:expire_reassembly() | ||
| 147 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 20, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 148 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 52, 1, 1, 0, 0, MSG_BYTE .. 'new'), E_OK) | ||
| 149 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 52, 1, 0, 1, 1, 'Y'), E_OK) | ||
| 150 | + lu.assertEquals(self.received, { { 'newY', true, 15, 0 } }) | ||
| 151 | +end | ||
| 152 | + | ||
| 153 | +function TestMctpReassemblyIsolation:test_reset_discards_partial_messages() | ||
| 154 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 155 | + reassembly_pop(self, ctx, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 156 | + self.proto:reset_reassembly() | ||
| 157 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 158 | + lu.assertEquals(self.received, {}) | ||
| 159 | +end | ||
| 160 | + | ||
| 161 | +-- 接收保留名额必须由本端已发起的查询授权,不能只相信对端自报的 TO。 | ||
| 162 | +TestMctpAdmission = {} | ||
| 163 | +TestMctpAdmission.setup = setup_reassembly | ||
| 164 | + | ||
| 165 | +local function expected_response(self, src, tag, timeout) | ||
| 166 | + local physical = mctp.pcie_vdm_ctx.new() | ||
| 167 | + physical.remote_phy_addr = 0x2000 | ||
| 168 | + local ctx = mctp.mctp_ctx.new() | ||
| 169 | + ctx:set_prev(physical) | ||
| 170 | + ctx.src, ctx.dest, ctx.tag, ctx.tag_owner = 10, src, tag or 0, true | ||
| 171 | + local token = self.proto:reserve_response(ctx, 15, 0x0f, timeout or 1000) | ||
| 172 | + return token, physical, ctx | ||
| 173 | +end | ||
| 174 | + | ||
| 175 | +function TestMctpAdmission:test_peer_invalidation_quarantines_only_matching_reservation() | ||
| 176 | + local token, physical, ctx = expected_response(self, 9) | ||
| 177 | + local other = expected_response(self, 11) | ||
| 178 | + reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 179 | + reassembly_pop(self, physical, 15, 11, 0, 1, 0, 0, MSG_BYTE .. 'keep') | ||
| 180 | + self.proto:invalidate_peer(15, physical.remote_phy_addr, 9) | ||
| 181 | + lu.assertFalse(self.proto:renew_response(token, 1000)) | ||
| 182 | + lu.assertTrue(self.proto:renew_response(other, 1000)) | ||
| 183 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 0, 1, 1, 'X'), E_FAIL) | ||
| 184 | + lu.assertEquals(reassembly_pop(self, physical, 15, 11, 0, 0, 1, 1, 'Y'), E_OK) | ||
| 185 | + self.proto:release_response(token) -- old coroutine cleanup cannot reopen a retired tag | ||
| 186 | + lu.assertTrue(self.proto:response_reserved(ctx, 15)) | ||
| 187 | + lu.assertEquals(self.proto:reserve_response(ctx, 15, 0x0f, 1000), 0) | ||
| 188 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 1, 1, 0, MSG_BYTE .. 'late'), E_FAIL) | ||
| 189 | + lu.assertEquals(self.received, { { 'keepY', false, 15, 0x2000 } }) | ||
| 190 | +end | ||
| 191 | + | ||
| 192 | +function TestMctpAdmission:test_peer_invalidation_preserves_other_address_and_medium() | ||
| 193 | + local first, other, smbus = mctp.pcie_vdm_ctx.new(), mctp.pcie_vdm_ctx.new(), mctp.sm_bus_ctx.new() | ||
| 194 | + first.remote_phy_addr, other.remote_phy_addr, smbus.remote_phy_addr = 32, 33, 32 | ||
| 195 | + for _, pair in ipairs({ { first, 15 }, { other, 15 }, { smbus, 2 } }) do | ||
| 196 | + reassembly_pop(self, pair[1], pair[2], 9, 1, 1, 0, 0, MSG_BYTE .. 'keep') | ||
| 197 | + end | ||
| 198 | + self.proto:invalidate_peer(15, 32, 9) | ||
| 199 | + self.proto:invalidate_peer(15, 32, 9) -- repeated invalidation is harmless | ||
| 200 | + lu.assertEquals(reassembly_pop(self, first, 15, 9, 1, 0, 1, 1, 'X'), E_FAIL) | ||
| 201 | + lu.assertEquals(reassembly_pop(self, other, 15, 9, 1, 0, 1, 1, 'Y'), E_OK) | ||
| 202 | + lu.assertEquals(reassembly_pop(self, smbus, 2, 9, 1, 0, 1, 1, 'Z'), E_OK) | ||
| 203 | + lu.assertEquals(self.received, { { 'keepY', true, 15, 33 }, { 'keepZ', true, 2, 32 } }) | ||
| 204 | +end | ||
| 205 | + | ||
| 206 | +function TestMctpAdmission:test_retired_tag_expires_and_can_be_reused() | ||
| 207 | + local token, physical, ctx = expected_response(self, 9, 0, 20) | ||
| 208 | + self.proto:invalidate_peer(15, physical.remote_phy_addr, 9) | ||
| 209 | + self.proto:release_response(token) | ||
| 210 | + lu.assertTrue(self.proto:response_reserved(ctx, 15)) | ||
| 211 | + require('utils.core').usleep(1100000) | ||
| 212 | + lu.assertFalse(self.proto:response_reserved(ctx, 15)) | ||
| 213 | + lu.assertTrue(self.proto:reserve_response(ctx, 15, 0x0f, 1000) > 0) | ||
| 214 | +end | ||
| 215 | + | ||
| 216 | +function TestMctpAdmission:test_ordinary_pool_cannot_consume_response_reserve() | ||
| 217 | + local ctx = fill_ordinary(self) | ||
| 218 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 36, 1, 1, 0, 0, MSG_BYTE .. 'extra'), E_FAIL) | ||
| 219 | + local token, physical = expected_response(self, 9) | ||
| 220 | + lu.assertNotEquals(token, 0) | ||
| 221 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'answer'), E_OK) | ||
| 222 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, '\x01fake'), E_FAIL) | ||
| 223 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 0, 1, 1, 'X'), E_OK) | ||
| 224 | + lu.assertEquals(self.received, { { 'answerX', false, 15, 0x2000 } }) | ||
| 225 | +end | ||
| 226 | + | ||
| 227 | +function TestMctpAdmission:test_source_quota_does_not_evict_existing_messages() | ||
| 228 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 229 | + for tag = 0, 3 do | ||
| 230 | + lu.assertEquals(self.proto:pop(mctp_hdr(10, 9, 1, 0, 0, 1, tag) .. MSG_BYTE .. 'old', ctx, 15), E_OK) | ||
| 231 | + end | ||
| 232 | + lu.assertEquals(self.proto:pop(mctp_hdr(10, 9, 1, 0, 0, 0, 4) .. MSG_BYTE .. 'extra', ctx, 15), E_FAIL) | ||
| 233 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 10, 1, 1, 0, 0, MSG_BYTE .. 'other'), E_OK) | ||
| 234 | + lu.assertEquals(reassembly_pop(self, ctx, 15, 9, 1, 0, 1, 1, 'X'), E_OK) | ||
| 235 | + lu.assertEquals(self.received, { { 'oldX', true, 15, 0 } }) | ||
| 236 | +end | ||
| 237 | + | ||
| 238 | +function TestMctpAdmission:test_direction_physical_address_type_and_peer_must_match_pending() | ||
| 239 | + local other = fill_ordinary(self) | ||
| 240 | + local token, physical = expected_response(self, 9) | ||
| 241 | + lu.assertNotEquals(token, 0) | ||
| 242 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 1, 1, 0, 0, MSG_BYTE .. 'fake'), E_FAIL) | ||
| 243 | + lu.assertEquals(reassembly_pop(self, other, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'fake'), E_FAIL) | ||
| 244 | + lu.assertEquals(reassembly_pop(self, physical, 15, 8, 0, 1, 0, 0, MSG_BYTE .. 'fake'), E_FAIL) | ||
| 245 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, '\x01fake'), E_FAIL) | ||
| 246 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'answer'), E_OK) | ||
| 247 | +end | ||
| 248 | + | ||
| 249 | +function TestMctpAdmission:test_release_invalidates_incomplete_response_and_preserves_other_token() | ||
| 250 | + local token, physical, ctx = expected_response(self, 9) | ||
| 251 | + lu.assertNotEquals(token, 0) | ||
| 252 | + lu.assertTrue(self.proto:response_reserved(ctx, 15)) | ||
| 253 | + lu.assertEquals(self.proto:reserve_response(ctx, 15, 0x0f, 1000), 0) | ||
| 254 | + reassembly_pop(self, physical, 15, 9, 0, 1, 0, 0, MSG_BYTE .. 'old') | ||
| 255 | + self.proto:release_response(token) | ||
| 256 | + lu.assertFalse(self.proto:response_reserved(ctx, 15)) | ||
| 257 | + lu.assertEquals(reassembly_pop(self, physical, 15, 9, 0, 0, 1, 1, 'X'), E_FAIL) | ||
| 258 | + local replacement = self.proto:reserve_response(ctx, 15, 0x0f, 1000) | ||
| 259 | + lu.assertNotEquals(replacement, token) | ||
| 260 | + self.proto:release_response(token) | ||
| 261 | + lu.assertTrue(self.proto:response_reserved(ctx, 15)) | ||
| 262 | + self.proto:reset_reassembly() | ||
| 263 | + lu.assertFalse(self.proto:response_reserved(ctx, 15)) | ||
| 264 | +end | ||
| 265 | + | ||
| 266 | +function TestMctpAdmission:test_extend_wait_keeps_only_the_current_reservation() | ||
| 267 | + local token, _, ctx = expected_response(self, 9) | ||
| 268 | + lu.assertTrue(self.proto:renew_response(token, 2000)) | ||
| 269 | + lu.assertFalse(self.proto:renew_response(token, 0)) | ||
| 270 | + self.proto:release_response(token) | ||
| 271 | + local replacement = self.proto:reserve_response(ctx, 15, 0x0f, 1000) | ||
| 272 | + lu.assertFalse(self.proto:renew_response(token, 2000)) | ||
| 273 | + lu.assertTrue(self.proto:renew_response(replacement, 2000)) | ||
| 274 | +end | ||
| 275 | + | ||
| 276 | +function TestMctpAdmission:test_reservations_and_response_pool_are_bounded() | ||
| 277 | + local physical | ||
| 278 | + for src = 20, 83 do | ||
| 279 | + local token, p = expected_response(self, src) | ||
| 280 | + lu.assertNotEquals(token, 0) | ||
| 281 | + physical = p | ||
| 282 | + end | ||
| 283 | + local token = expected_response(self, 84) | ||
| 284 | + lu.assertEquals(token, 0) | ||
| 285 | + for src = 20, 35 do | ||
| 286 | + lu.assertEquals(reassembly_pop(self, physical, 15, src, 0, 1, 0, 0, MSG_BYTE .. 'response'), E_OK) | ||
| 287 | + end | ||
| 288 | + lu.assertEquals(reassembly_pop(self, physical, 15, 36, 0, 1, 0, 0, MSG_BYTE .. 'response'), E_FAIL) | ||
| 289 | + local ordinary = fill_ordinary(self) | ||
| 290 | + lu.assertEquals(reassembly_pop(self, ordinary, 15, 20, 1, 1, 0, 0, MSG_BYTE .. 'new'), E_OK) | ||
| 291 | + lu.assertEquals(reassembly_pop(self, physical, 15, 20, 0, 1, 0, 0, MSG_BYTE .. 'fresh'), E_OK) | ||
| 292 | + lu.assertEquals(reassembly_pop(self, ordinary, 15, 20, 1, 0, 1, 1, 'X'), E_OK) | ||
| 293 | + lu.assertEquals(reassembly_pop(self, physical, 15, 20, 0, 0, 1, 1, 'Y'), E_OK) | ||
| 294 | + lu.assertEquals(self.received, { { 'newX', true, 15, 0 }, { 'freshY', false, 15, 0x2000 } }) | ||
| 295 | +end | ||
| 296 | + | ||
| 26 | --- Build a complete MCTP fragment (header + payload) | 297 | --- Build a complete MCTP fragment (header + payload) |
| 27 | local function fragment(dest, src, som, eom, seq, to, tag, payload) | 298 | local function fragment(dest, src, som, eom, seq, to, tag, payload) |
| 28 | return mctp_hdr(dest, src, som, eom, seq, to, tag) .. payload | 299 | return mctp_hdr(dest, src, som, eom, seq, to, tag) .. payload |
| @@ -14,6 +14,25 @@ local mctp_def = require('mctp_def') | |||
| 14 | 14 | ||
| 15 | TestMctpPcieVdm = {} | 15 | TestMctpPcieVdm = {} |
| 16 | 16 | ||
| 17 | +function TestMctpPcieVdm:test_tx_padding_at_capacity_boundary() | ||
| 18 | + local ctx = mctp.pcie_vdm_ctx.new() | ||
| 19 | + ctx.routing, ctx.bmc_phy_addr, ctx.remote_phy_addr = 2, 3, 32 | ||
| 20 | + for _, length in ipairs({ 115, 114, 113, 116, 67, 66, 65, 68 }) do | ||
| 21 | + local proto = mctp.pcie_vdm_protocol.new() | ||
| 22 | + local sent | ||
| 23 | + proto:set_tx_handler(function(_, data) | ||
| 24 | + sent = data | ||
| 25 | + return true | ||
| 26 | + end) | ||
| 27 | + local payload = string.rep('\xa5', length) | ||
| 28 | + proto:push(payload, ctx, mctp_def.PHY_MEDIUM_PCI) | ||
| 29 | + local padding = (4 - length % 4) % 4 | ||
| 30 | + lu.assertNotIsNil(sent) | ||
| 31 | + lu.assertEquals(#sent, 12 + length + padding) | ||
| 32 | + lu.assertEquals(sent:sub(13), payload .. string.rep('\0', padding)) | ||
| 33 | + end | ||
| 34 | +end | ||
| 35 | + | ||
| 17 | local PCIE_ROUTE_TO_RC <const> = 0 | 36 | local PCIE_ROUTE_TO_RC <const> = 0 |
| 18 | local PCIE_ROUTE_BY_ID <const> = 2 | 37 | local PCIE_ROUTE_BY_ID <const> = 2 |
| 19 | local PCIE_BROADCAST_FROM_RC <const> = 3 | 38 | local PCIE_BROADCAST_FROM_RC <const> = 3 |


[P2] EID 更新只应清理对应介质的在途状态
PCIe 与 SMBus 共用同一个 mctp_protocol。若 SMBus 查询已收到首片,此时 PCIe 重新发现经 set_bmc_eid 更新本端 EID,这里的 reset_reassembly() 会把两种介质的 m_rx_ctx 和 m_responses 一并清空:随后合法的 SMBus 尾片查不到半包,NVMe More Processing 的预约续期也返回 false,造成无关 SMBus 查询失败;set_smbus_eid 中的同类调用对 PCIe 同样存在影响。本轮用当前 47083fdb 的原始 C++ 做了 ASan/UBSan 对照:SMBus 首片→PCIe EID 10→11→SMBus 尾片,当前提交交付数为 0、预约/续期均为 false;共同基线 0fe122aa 同样操作交付数为 1,当前提交不改 PCIe EID 也交付 1。请按被修改的介质筛选清理重组与预约,将全表清空留给整引擎复位,并补充双向跨介质回归。