Closed
fix(drivers): 内存边界与异步生命周期修复及压力测试 #1307
liuxingsenAdd on Sep 27Closed at 25 days ago
fix(drivers): 内存边界与异步生命周期修复及压力测试 #1307
Closed
Total: 119 files changed+4577-797
| @@ -0,0 +1,53 @@ | |||
| 1 | +# component_drivers 历史 gtest 50 轮记录 | ||
| 2 | + | ||
| 3 | +本报告保留 2026-09-27 旧构建的 50 轮全量串行压力测试记录。报告提交 `8bdd569f` 之后分支仍有修复,现又整合最新主线;以下历史数字不代表当前提交通过验收。当前提交的实际验证以 PR 自验证报告为准。 | ||
| 4 | + | ||
| 5 | +## 验收结果 | ||
| 6 | + | ||
| 7 | +- 轮次:50/50 通过,失败轮次 0。 | ||
| 8 | +- 每轮 Meson 测试可执行文件:207/207 通过。 | ||
| 9 | +- 每轮 gtest 用例:11,566/11,566 通过,失败 0,跳过 0,不完整 0。 | ||
O | |||
| 10 | +- 累计 Meson 可执行文件运行:10,350 次。 | ||
| 11 | +- 累计 gtest 用例运行:578,300 次。 | ||
| 12 | +- 历史全量构建通过。该构建未启用 sanitizer 插桩;仅设置 ASan/MSan/UBSan 环境变量不能证明内存检查通过。 | ||
| 13 | + | ||
| 14 | +最终证据: | ||
| 15 | + | ||
| 16 | +- 摘要:`test_failure_logs/gtest-50-20260927/final-50-rerun-2/run-20260927T142812Z-5/summary.json` | ||
| 17 | +- 摘要 SHA-256:`5f8c48d87bd9141bdb797daaeaa60d113f300f2e9c3fbf4bcf5b820d608b02b2` | ||
| 18 | +- 摘要中 50 轮均为 `passed=true`,且 `suite_errors`、`suites_without_cases`、`suites_with_incomplete_cases`、`missing_suites`、`unexpected_suites` 和 `errors` 均为空。 | ||
| 19 | + | ||
| 20 | +## 执行方式 | ||
| 21 | + | ||
| 22 | +当时复用与旧源码绑定的 `builddir_memory_audit`,先完成全量构建及 1 轮全量预检,再从第 1 轮开始正式计数。测试在独立 mount、IPC、network 和 PID 命名空间内串行执行,避免测试 D-Bus 及其他任务互相干扰。 | ||
| 23 | + | ||
| 24 | +```bash | ||
| 25 | +ninja -C builddir_memory_audit -j8 | ||
| 26 | + | ||
| 27 | +unshare --mount --ipc --net --pid --fork --mount-proc \ | ||
| 28 | + python3 test_failure_logs/gtest-20-20260927/isolated_run.py \ | ||
| 29 | + python3 scripts/run_memory_audit_stress.py \ | ||
| 30 | + --build-dir builddir_memory_audit \ | ||
| 31 | + --rounds 50 \ | ||
| 32 | + --log-root test_failure_logs/gtest-50-20260927/final-50-rerun-2 | ||
| 33 | +``` | ||
| 34 | + | ||
| 35 | +正式运行前的全量预检同样通过:207/207 个可执行文件、11,566/11,566 个用例,失败/跳过/不完整均为 0。 | ||
| 36 | + | ||
| 37 | +## 压测发现及闭环 | ||
| 38 | + | ||
| 39 | +| 问题 | 修复与回归覆盖 | | ||
| 40 | +| --- | --- | | ||
| 41 | +| CSR1 aggregate 最终强引用释放时的宿主竞争 | 访问宿主时提升 weak owner;新增阻塞析构确定性用例。 | | ||
| 42 | +| Scanner/CSR1 嵌入接口 timer 在宿主析构后被释放 | timer 脱离嵌入对象父子树,显式绑定宿主 executor;覆盖替换、停止及跨生命周期释放。 | | ||
| 43 | +| Broadcom 光模块/网络端口 timer 重启后旧回调仍在运行 | 启动新 timer 前停止并等待旧 timer;异步用例使用条件变量等待真实事件。 | | ||
| 44 | +| Huawei 升级工作线程与设备析构顺序 | 析构前等待升级完成;结果用例 join 后再读取,并覆盖重试、并发拒绝和成员生命期。 | | ||
| 45 | +| Huawei VPD/MCTP/日志定时器在嵌入接口析构后回调,正式旧批次第 32 轮触发 SIGSEGV | 六类 timer 脱离嵌入对象树并绑定宿主 executor,重启前停止旧实例,设备析构入口停止全部 VPD/MCTP 任务;新增 3 个定时器回归用例,完整 `test_hw_nvme_abi` 100/100 次通过。最终 50 轮的第 32 轮也通过。 | | ||
| 46 | +| Samsung/Huawei/Dera SMART 将 128 位 `PowerOnHours` 的高半部在 `uint64_t` 上左移 64 位以上 | 高低 64 位分别解析,按 `double` 属性契约组合;覆盖高位、低位和组合极值。 | | ||
| 47 | +| gtest 日志行与后台调试日志交错,导致通过用例被统计为 incomplete | 统计器按待完成用例名前缀识别交错行;新增交错日志成功/失败/跳过解析回归用例。 | | ||
| 48 | + | ||
| 49 | +其他已闭环内容包括 Scanner 确定性用例、SMBus 帧式传输覆盖、Hisi/Hi1822/Emulex 生命周期用例恢复、Samsung 遥测定时器及 OEM 析构顺序,以及 Huawei 升级工作线程回收。 | ||
| 50 | + | ||
| 51 | +## 范围边界 | ||
| 52 | + | ||
| 53 | +该历史记录仅对应当时的主机构建、配置及注册的 207 个测试程序。上面的隔离辅助脚本和日志路径是原环境记录,仓库本身不包含这些文件;新环境不能直接复制命令重跑。它不代替 BMC/Zephyr 真机负载、未启用构建配置、其他架构或预编译外部依赖内部的动态检测。 | ||
| @@ -24,6 +24,11 @@ Interface_Bus::Interface_Bus() | |||
| 24 | Timeout = static_cast<uint16_t>(0); | 24 | Timeout = static_cast<uint16_t>(0); |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | +Interface_Bus::~Interface_Bus() | ||
| 28 | +{ | ||
| 29 | + stop_access_timer(); | ||
| 30 | +} | ||
| 31 | + | ||
| 27 | void Interface_Bus::stop_access_timer() | 32 | void Interface_Bus::stop_access_timer() |
| 28 | { | 33 | { |
| 29 | if (m_access_timer != nullptr) { | 34 | if (m_access_timer != nullptr) { |
| @@ -94,12 +99,17 @@ void Interface_Bus::SetAccessibility(bool status, uint16_t disable_duration) | |||
| 94 | ilog("SetAccessibility: disabling access for bus ${name}, duration=${dur}s", | 99 | ilog("SetAccessibility: disabling access for bus ${name}, duration=${dur}s", |
| 95 | ("name", get_owner() != nullptr ? get_owner()->get_object_name() : "unknown")("dur", disable_duration)); | 100 | ("name", get_owner() != nullptr ? get_owner()->get_object_name() : "unknown")("dur", disable_duration)); |
| 96 | internal_bus->set_access_enabled(false); | 101 | internal_bus->set_access_enabled(false); |
| 97 | - m_access_timer = mc::make_shared<mc::timer>(this); | 102 | + // The member owns the timer. A queued handler can retain it beyond this |
| 103 | + // embedded interface's lifetime, so it must not retain an interface parent. | ||
| 104 | + m_access_timer = mc::make_shared<mc::timer>(); | ||
| 105 | + auto* owner = get_owner(); | ||
| 106 | + m_access_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 98 | m_access_timer->set_single_shot(true); | 107 | m_access_timer->set_single_shot(true); |
| 99 | m_access_timer->timeout.connect([this]() { | 108 | m_access_timer->timeout.connect([this]() { |
| 100 | AccessEnabled = true; | 109 | AccessEnabled = true; |
| 101 | Timeout = 0; | 110 | Timeout = 0; |
| 102 | - m_access_timer.reset(); | 111 | + // Keep the timer until stop_access_timer(): destruction must still be |
| 112 | + // able to wait for this callback before destroying the interface. | ||
| 103 | if (auto* bus = resolve_internal_bus(); bus != nullptr) { | 113 | if (auto* bus = resolve_internal_bus(); bus != nullptr) { |
| 104 | bus->set_access_enabled(true); | 114 | bus->set_access_enabled(true); |
| 105 | } else { | 115 | } else { |
| @@ -29,7 +29,7 @@ namespace dev::csr1 { | |||
| 29 | class Interface_Bus : public mc::engine::interface<Interface_Bus, dev::resource_gen::Bus> { | 29 | class Interface_Bus : public mc::engine::interface<Interface_Bus, dev::resource_gen::Bus> { |
| 30 | public: | 30 | public: |
| 31 | Interface_Bus(); | 31 | Interface_Bus(); |
| 32 | - ~Interface_Bus() = default; | 32 | + ~Interface_Bus(); |
| 33 | 33 | ||
| 34 | void SetAccessibility(bool status, uint16_t disable_duration) override; | 34 | void SetAccessibility(bool status, uint16_t disable_duration) override; |
| 35 | bool isAccessAllowed(); | 35 | bool isAccessAllowed(); |
| @@ -14,6 +14,7 @@ | |||
| 14 | #define CSR1_INTERFACE_AGGREGATE_H | 14 | #define CSR1_INTERFACE_AGGREGATE_H |
| 15 | 15 | ||
| 16 | #include <mc/future.h> | 16 | #include <mc/future.h> |
| 17 | +#include <mc/memory/weak_ptr.h> | ||
| 17 | #include <resource_tree/interface/Chip/Aggregate.h> | 18 | #include <resource_tree/interface/Chip/Aggregate.h> |
| 18 | 19 | ||
| 19 | #include "i_scheduler.h" | 20 | #include "i_scheduler.h" |
| @@ -242,6 +243,9 @@ private: | |||
| 242 | 243 | ||
| 243 | // 宿主芯片对象,传递给 dispatcher 的 owner_guard | 244 | // 宿主芯片对象,传递给 dispatcher 的 owner_guard |
| 244 | mc::object_base* m_owner_object = nullptr; | 245 | mc::object_base* m_owner_object = nullptr; |
| 246 | + // 在宿主仍存活时建立弱引用。host_guard() 必须通过非抛异常的 lock() | ||
| 247 | + // 升级,不能在 final-release 窗口对裸指针调用 noexcept shared_ptr 构造。 | ||
| 248 | + mc::weak_ptr<mc::object_base> m_owner_weak; | ||
| 245 | 249 | ||
| 246 | // 芯片去重:避免同一芯片创建多个 Aggregate。 | 250 | // 芯片去重:避免同一芯片创建多个 Aggregate。 |
| 247 | // 声明 key = 物理芯片身份 + 声明窗口(BaseOffset+Length):窗口不同不共享 | 251 | // 声明 key = 物理芯片身份 + 声明窗口(BaseOffset+Length):窗口不同不共享 |
| @@ -41,6 +41,9 @@ std::vector<uint8_t> Interface_BitIO::Read(uint32_t offset, uint8_t length, uint | |||
| 41 | void Interface_BitIO::Write(uint32_t offset, uint8_t length, uint32_t mask, const std::vector<uint8_t>& indata) | 41 | void Interface_BitIO::Write(uint32_t offset, uint8_t length, uint32_t mask, const std::vector<uint8_t>& indata) |
| 42 | { | 42 | { |
| 43 | ensure_chip_accessible(m_chip, m_access_state, m_interface_chip); | 43 | ensure_chip_accessible(m_chip, m_access_state, m_interface_chip); |
| 44 | + if (length > indata.size()) { | ||
| 45 | + MC_THROW(mc::invalid_arg_exception, "BitIO Write length exceeds input buffer size"); | ||
| 46 | + } | ||
| 44 | std::vector<uint8_t> out_buffer; | 47 | std::vector<uint8_t> out_buffer; |
| 45 | BusLock lock(*m_chip->get_host_bus()); | 48 | BusLock lock(*m_chip->get_host_bus()); |
| 46 | std::string error; | 49 | std::string error; |
| @@ -243,6 +243,12 @@ Interface_Chip::Interface_Chip() | |||
| 243 | 243 | ||
| 244 | Interface_Chip::~Interface_Chip() | 244 | Interface_Chip::~Interface_Chip() |
| 245 | { | 245 | { |
| 246 | + // The periodic callback also captures this; cancel and join it before | ||
| 247 | + // tearing down any state it can access. | ||
| 248 | + if (m_access_watchdog != nullptr) { | ||
| 249 | + m_access_watchdog->stop(); | ||
| 250 | + m_access_watchdog.reset(); | ||
| 251 | + } | ||
| 246 | // 停掉在途的自动恢复计时器:stop(true) 会等待正在执行的回调结束——接口在禁用 | 252 | // 停掉在途的自动恢复计时器:stop(true) 会等待正在执行的回调结束——接口在禁用 |
| 247 | // 窗口内被析构(SHM 重建/热插拔)时,若计时器继续到点触发,回调里的 | 253 | // 窗口内被析构(SHM 重建/热插拔)时,若计时器继续到点触发,回调里的 |
| 248 | // is_host_bus_accessible() / m_on_accessibility_changed 会解引用已析构的本接口; | 254 | // is_host_bus_accessible() / m_on_accessibility_changed 会解引用已析构的本接口; |
| @@ -523,7 +529,10 @@ void Interface_Chip::apply_access_command_ms(bool status, int64_t disable_durati | |||
| 523 | } | 529 | } |
| 524 | access_state->access_enabled = false; | 530 | access_state->access_enabled = false; |
| 525 | 531 | ||
| 526 | - access_state->access_timer = mc::make_shared<mc::timer>(this); | 532 | + // State owns the timer; queued handlers must not retain a parent |
| 533 | + // pointer to this embedded interface after cancellation. | ||
| 534 | + access_state->access_timer = mc::make_shared<mc::timer>(); | ||
| 535 | + access_state->access_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 527 | access_state->access_timer->set_single_shot(true); | 536 | access_state->access_timer->set_single_shot(true); |
| 528 | access_state->access_timer->timeout.connect([this, owner_weak, access_state, name]() { | 537 | access_state->access_timer->timeout.connect([this, owner_weak, access_state, name]() { |
| 529 | // 在途回调可能在接口析构后触发(禁用窗口内 SHM 重建/热插拔):先经 | 538 | // 在途回调可能在接口析构后触发(禁用窗口内 SHM 重建/热插拔):先经 |
| @@ -655,7 +664,9 @@ int32_t Interface_Chip::SetLockStatus(const mc::engine::context& context, uint8_ | |||
| 655 | // 显式拷贝 shared_ptr 到本地变量,确保 lambda 捕获时能正确保活 | 664 | // 显式拷贝 shared_ptr 到本地变量,确保 lambda 捕获时能正确保活 |
| 656 | auto timer_bus = physical_bus; | 665 | auto timer_bus = physical_bus; |
| 657 | auto lock_id = new_lock->lock_id; | 666 | auto lock_id = new_lock->lock_id; |
| 658 | - new_lock->lock_timer = mc::make_shared<mc::timer>(this); | 667 | + new_lock->lock_timer = mc::make_shared<mc::timer>(); |
| 668 | + auto* owner = get_owner(); | ||
| 669 | + new_lock->lock_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 659 | new_lock->lock_timer->set_single_shot(true); | 670 | new_lock->lock_timer->set_single_shot(true); |
| 660 | new_lock->lock_timer->timeout.connect( | 671 | new_lock->lock_timer->timeout.connect( |
| 661 | [this, timer_bus, bus_name, channel_chain, current_chip_name, lock_id]() { | 672 | [this, timer_bus, bus_name, channel_chain, current_chip_name, lock_id]() { |
| @@ -968,7 +979,9 @@ void Interface_Chip::start_access_watchdog() | |||
| 968 | if (m_access_watchdog != nullptr) { | 979 | if (m_access_watchdog != nullptr) { |
| 969 | return; // 已启动(幂等) | 980 | return; // 已启动(幂等) |
| 970 | } | 981 | } |
| 971 | - m_access_watchdog = mc::make_shared<mc::timer>(this); | 982 | + m_access_watchdog = mc::make_shared<mc::timer>(); |
| 983 | + auto* owner = get_owner(); | ||
| 984 | + m_access_watchdog->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 972 | m_access_watchdog->timeout.connect([this]() { | 985 | m_access_watchdog->timeout.connect([this]() { |
| 973 | on_access_watchdog_tick(); | 986 | on_access_watchdog_tick(); |
| 974 | }); | 987 | }); |
| @@ -59,9 +59,10 @@ void Scheduler::register_task(ITask* task) | |||
| 59 | // Reuse the state when a task is registered more than once. This keeps | 59 | // Reuse the state when a task is registered more than once. This keeps |
| 60 | // the single-flight guarantee attached to the task rather than to an | 60 | // the single-flight guarantee attached to the task rather than to an |
| 61 | // accidental duplicate Entry. | 61 | // accidental duplicate Entry. |
| 62 | - for (const auto& existing : m_entries) { | 62 | + for (const auto& state : m_retired_states) { |
| 63 | - if (existing.task == task && existing.run_state) { | 63 | + if (state->task == task) { |
| 64 | - entry.run_state = existing.run_state; | 64 | + entry.run_state = state; |
| 65 | + entry.next_run_ms = UINT64_MAX; | ||
| 65 | break; | 66 | break; |
| 66 | } | 67 | } |
| 67 | } | 68 | } |
| @@ -85,10 +86,17 @@ void Scheduler::unregister_task(ITask* task) | |||
| 85 | { | 86 | { |
| 86 | std::lock_guard<std::recursive_mutex> lock(m_mutex); | 87 | std::lock_guard<std::recursive_mutex> lock(m_mutex); |
| 87 | 88 | ||
| 88 | - auto it = std::remove_if(m_entries.begin(), m_entries.end(), [&states, task](const Entry& e) { | 89 | + auto it = std::remove_if(m_entries.begin(), m_entries.end(), [this, &states, task](const Entry& e) { |
| 89 | if (e.task == task) { | 90 | if (e.task == task) { |
| 90 | if (e.run_state) { | 91 | if (e.run_state) { |
| 91 | - states.push_back(e.run_state); | 92 | + if (e.run_state->can_retire && e.run_state->running.load(std::memory_order_acquire)) { |
| 93 | + if (std::find(m_retired_states.begin(), m_retired_states.end(), e.run_state) == | ||
| 94 | + m_retired_states.end()) { | ||
| 95 | + m_retired_states.push_back(e.run_state); | ||
| 96 | + } | ||
| 97 | + } else { | ||
| 98 | + states.push_back(e.run_state); | ||
| 99 | + } | ||
| 92 | } | 100 | } |
| 93 | return true; | 101 | return true; |
| 94 | } | 102 | } |
| @@ -101,8 +109,9 @@ void Scheduler::unregister_task(ITask* task) | |||
| 101 | arm_next(); | 109 | arm_next(); |
| 102 | } | 110 | } |
| 103 | 111 | ||
| 104 | - // 生命周期保证:等待被注销 Entry 的 run() 结束,避免 owner 在异步 | 112 | + // Tasks bound to their retained host can unregister inside an asynchronous |
| 105 | - // callback 仍使用 task 时销毁 task。等待放在锁外,避免 run() 反向访问 Scheduler。 | 113 | + // callback without waiting on themselves. Unmanaged and replaceable child |
| 114 | + // tasks must finish before their caller can destroy or replace them. | ||
| 106 | wait_for_tasks(states); | 115 | wait_for_tasks(states); |
| 107 | } | 116 | } |
| 108 | 117 | ||
| @@ -172,6 +181,8 @@ void Scheduler::clear() | |||
| 172 | } | 181 | } |
| 173 | } | 182 | } |
| 174 | m_entries.clear(); | 183 | m_entries.clear(); |
| 184 | + states.insert(states.end(), m_retired_states.begin(), m_retired_states.end()); | ||
| 185 | + m_retired_states.clear(); | ||
| 175 | } | 186 | } |
| 176 | 187 | ||
| 177 | // 与 unregister_task 相同,只等待 clear() 移除的任务。 | 188 | // 与 unregister_task 相同,只等待 clear() 移除的任务。 |
| @@ -211,13 +222,18 @@ void Scheduler::on_timer(uint64_t expected_generation) | |||
| 211 | continue; | 222 | continue; |
| 212 | } | 223 | } |
| 213 | 224 | ||
| 214 | - if (entry.task != nullptr) { | ||
| 215 | - entry.period_ms = std::max(entry.task->period_ms(), MIN_TICK_MS); | ||
| 216 | - } | ||
| 217 | - | ||
| 218 | if (!entry.run_state) { | 225 | if (!entry.run_state) { |
| 219 | entry.run_state = std::make_shared<RunState>(entry.task); | 226 | entry.run_state = std::make_shared<RunState>(entry.task); |
| 220 | } | 227 | } |
| 228 | + auto owner = entry.run_state->owner.lock(); | ||
| 229 | + if (entry.run_state->has_owner && !owner) { | ||
| 230 | + // The host is being destroyed and will unregister its task. | ||
| 231 | + // Do not dereference the task or dispatch another read. | ||
| 232 | + continue; | ||
| 233 | + } | ||
| 234 | + if (entry.task != nullptr) { | ||
| 235 | + entry.period_ms = std::max(entry.task->period_ms(), MIN_TICK_MS); | ||
| 236 | + } | ||
| 221 | // 任务仍在执行时丢弃本轮,不把慢 I/O 变成排队堆积。 | 237 | // 任务仍在执行时丢弃本轮,不把慢 I/O 变成排队堆积。 |
| 222 | bool claimed = false; | 238 | bool claimed = false; |
| 223 | { | 239 | { |
| @@ -227,6 +243,7 @@ void Scheduler::on_timer(uint64_t expected_generation) | |||
| 227 | std::memory_order_acquire); | 243 | std::memory_order_acquire); |
| 228 | } | 244 | } |
| 229 | if (claimed) { | 245 | if (claimed) { |
| 246 | + entry.run_state->owner_guard = std::move(owner); | ||
| 230 | // 下一次触发严格从本轮完成后重新计算;慢任务期间不保留 | 247 | // 下一次触发严格从本轮完成后重新计算;慢任务期间不保留 |
| 231 | // 周期 deadline,也不做 missed-period 补偿。 | 248 | // 周期 deadline,也不做 missed-period 补偿。 |
| 232 | entry.next_run_ms = UINT64_MAX; | 249 | entry.next_run_ms = UINT64_MAX; |
| @@ -283,6 +300,7 @@ void Scheduler::execute_task(const std::shared_ptr<RunState>& state) | |||
| 283 | 300 | ||
| 284 | void Scheduler::complete_task(const std::shared_ptr<RunState>& state) | 301 | void Scheduler::complete_task(const std::shared_ptr<RunState>& state) |
| 285 | { | 302 | { |
| 303 | + mc::shared_ptr<mc::object_base> completed_owner; | ||
| 286 | { | 304 | { |
| 287 | std::lock_guard<std::recursive_mutex> scheduler_lock(m_mutex); | 305 | std::lock_guard<std::recursive_mutex> scheduler_lock(m_mutex); |
| 288 | for (auto& entry : m_entries) { | 306 | for (auto& entry : m_entries) { |
| @@ -296,8 +314,11 @@ void Scheduler::complete_task(const std::shared_ptr<RunState>& state) | |||
| 296 | entry.next_run_ms = now_ms + entry.period_ms; | 314 | entry.next_run_ms = now_ms + entry.period_ms; |
| 297 | break; | 315 | break; |
| 298 | } | 316 | } |
| 317 | + m_retired_states.erase(std::remove(m_retired_states.begin(), m_retired_states.end(), state), | ||
| 318 | + m_retired_states.end()); | ||
| 299 | { | 319 | { |
| 300 | std::lock_guard<std::mutex> state_lock(state->mutex); | 320 | std::lock_guard<std::mutex> state_lock(state->mutex); |
| 321 | + completed_owner = std::move(state->owner_guard); | ||
| 301 | state->running.store(false, std::memory_order_release); | 322 | state->running.store(false, std::memory_order_release); |
| 302 | } | 323 | } |
| 303 | arm_next(); | 324 | arm_next(); |
| @@ -52,6 +52,13 @@ public: | |||
| 52 | { | 52 | { |
| 53 | return {}; | 53 | return {}; |
| 54 | } | 54 | } |
| 55 | + // Opt in only when retaining the owner also guarantees that this task | ||
| 56 | + // cannot be replaced/destroyed. Replaceable child dispatchers must wait | ||
| 57 | + // for their in-flight run before unregister_task returns. | ||
| 58 | + virtual bool lifetime_bound_to_owner() const | ||
| 59 | + { | ||
| 60 | + return false; | ||
| 61 | + } | ||
| 55 | }; | 62 | }; |
| 56 | 63 | ||
| 57 | class SchedulerTestHelper; // 测试辅助类,friend 访问私有成员 | 64 | class SchedulerTestHelper; // 测试辅助类,friend 访问私有成员 |
| @@ -78,10 +85,16 @@ private: | |||
| 78 | 85 | ||
| 79 | struct RunState { | 86 | struct RunState { |
| 80 | explicit RunState(ITask* task, mc::shared_ptr<mc::object_base> owner_guard = {}) | 87 | explicit RunState(ITask* task, mc::shared_ptr<mc::object_base> owner_guard = {}) |
| 81 | - : task(task), owner_guard(std::move(owner_guard)) | 88 | + : task(task), owner(owner_guard), has_owner(owner_guard != nullptr), |
| 89 | + can_retire(has_owner && task->lifetime_bound_to_owner()) | ||
| 82 | {} | 90 | {} |
| 83 | 91 | ||
| 84 | - ITask* task; | 92 | + ITask* task; |
| 93 | + mc::weak_ptr<mc::object_base> owner; | ||
| 94 | + bool has_owner; | ||
| 95 | + bool can_retire; | ||
| 96 | + // Only an in-flight task owns its host. Idle registration must not | ||
| 97 | + // prevent the host destructor from unregistering the task. | ||
| 85 | mc::shared_ptr<mc::object_base> owner_guard; | 98 | mc::shared_ptr<mc::object_base> owner_guard; |
| 86 | std::atomic<bool> running{false}; | 99 | std::atomic<bool> running{false}; |
| 87 | std::mutex mutex; | 100 | std::mutex mutex; |
| @@ -101,8 +114,11 @@ private: | |||
| 101 | void complete_task(const std::shared_ptr<RunState>& state); | 114 | void complete_task(const std::shared_ptr<RunState>& state); |
| 102 | static void wait_for_tasks(const std::vector<std::shared_ptr<RunState>>& states); | 115 | static void wait_for_tasks(const std::vector<std::shared_ptr<RunState>>& states); |
| 103 | 116 | ||
| 104 | - std::vector<Entry> m_entries; | 117 | + std::vector<Entry> m_entries; |
| 105 | - mc::shared_ptr<mc::timer> m_timer; | 118 | + // Managed tasks may unregister from their own asynchronous completion. |
| 119 | + // Keep their state until completion for shutdown and re-registration. | ||
| 120 | + std::vector<std::shared_ptr<RunState>> m_retired_states; | ||
| 121 | + mc::shared_ptr<mc::timer> m_timer; | ||
| 106 | 122 | ||
| 107 | // 时间零点(steady) | 123 | // 时间零点(steady) |
| 108 | std::chrono::steady_clock::time_point m_epoch = std::chrono::steady_clock::now(); | 124 | std::chrono::steady_clock::time_point m_epoch = std::chrono::steady_clock::now(); |
| @@ -151,7 +151,9 @@ void IScanner::init_startup_delay_timer() | |||
| 151 | m_startup_delay_timer.reset(); | 151 | m_startup_delay_timer.reset(); |
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | - m_startup_delay_timer = mc::make_shared<mc::timer>(this); | 154 | + // The member owns this timer; an embedded interface is not a safe weak parent |
| 155 | + // when a cancellation completion releases the timer after Scanner destruction. | ||
| 156 | + m_startup_delay_timer = mc::make_shared<mc::timer>(); | ||
| 155 | m_startup_delay_timer->bind_executor(executor()); | 157 | m_startup_delay_timer->bind_executor(executor()); |
| 156 | m_startup_delay_timer->timeout.connect([this]() { | 158 | m_startup_delay_timer->timeout.connect([this]() { |
| 157 | m_at_start_up = false; | 159 | m_at_start_up = false; |
| @@ -239,7 +241,8 @@ void IScanner::start_scanner_task() | |||
| 239 | // 创建定时器并启动 | 241 | // 创建定时器并启动 |
| 240 | m_scan_active = true; | 242 | m_scan_active = true; |
| 241 | m_adjust_period = period_ms; | 243 | m_adjust_period = period_ms; |
| 242 | - m_scanner_timer = mc::make_shared<mc::timer>(this); | 244 | + // Keep ownership in the member, without a parent link to this embedded interface. |
| 245 | + m_scanner_timer = mc::make_shared<mc::timer>(); | ||
| 243 | m_scanner_timer->bind_executor(executor()); | 246 | m_scanner_timer->bind_executor(executor()); |
| 244 | m_scanner_timer->timeout.connect([this]() { | 247 | m_scanner_timer->timeout.connect([this]() { |
| 245 | handle_scanner_timeout(); | 248 | handle_scanner_timeout(); |
| @@ -120,6 +120,11 @@ public: | |||
| 120 | // poll runtime 时卸载可能重入并释放 owner,guard 使 owner 在整个执行批次 | 120 | // poll runtime 时卸载可能重入并释放 owner,guard 使 owner 在整个执行批次 |
| 121 | // 内存活。owner 非托管(测试夹具)或正在析构时返回空。 | 121 | // 内存活。owner 非托管(测试夹具)或正在析构时返回空。 |
| 122 | mc::shared_ptr<mc::object_base> owner_guard() override; | 122 | mc::shared_ptr<mc::object_base> owner_guard() override; |
| 123 | + bool lifetime_bound_to_owner() const override | ||
| 124 | + { | ||
| 125 | + // SmcDfxInfo_object owns this dispatcher as a non-replaceable member. | ||
| 126 | + return true; | ||
| 127 | + } | ||
| 123 | 128 | ||
| 124 | // 设置 owner(SmcDfxInfo_object 在 init 早期调用;follower 亦需设置—— | 129 | // 设置 owner(SmcDfxInfo_object 在 init 早期调用;follower 亦需设置—— |
| 125 | // promotion 后其任务注册时 guard 必须可用) | 130 | // promotion 后其任务注册时 guard 必须可用) |
| @@ -123,6 +123,10 @@ uint32_t bus_can::write(ACCESS_DATA_S& input) | |||
| 123 | { | 123 | { |
| 124 | ensure_driver_ready(m_can_drv, "write", m_object_name, m_id); | 124 | ensure_driver_ready(m_can_drv, "write", m_object_name, m_id); |
| 125 | 125 | ||
| 126 | + if (input.length > input.in_buffer.size()) { | ||
| 127 | + MC_THROW(mc::invalid_op_exception, "CAN write length exceeds input buffer size"); | ||
| 128 | + } | ||
| 129 | + | ||
| 126 | try { | 130 | try { |
| 127 | m_can_drv->write(input.in_buffer.data(), input.length, input.write_time_out); | 131 | m_can_drv->write(input.in_buffer.data(), input.length, input.write_time_out); |
| 128 | return 0; | 132 | return 0; |
| @@ -110,6 +110,15 @@ void bus_i2c::bus_unlock() | |||
| 110 | 110 | ||
| 111 | std::string_view bus_i2c::format_tx_buf(const ACCESS_DATA_S& data) | 111 | std::string_view bus_i2c::format_tx_buf(const ACCESS_DATA_S& data) |
| 112 | { | 112 | { |
| 113 | + // On reads, data.length describes the reply, not the transmit buffer. | ||
| 114 | + return format_tx_buf(data, data.in_buffer.size()); | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +std::string_view bus_i2c::format_tx_buf(const ACCESS_DATA_S& data, size_t write_length) | ||
| 118 | +{ | ||
| 119 | + if (write_length > data.in_buffer.size()) { | ||
| 120 | + MC_THROW(mc::invalid_arg_exception, "write length exceeds input buffer size"); | ||
| 121 | + } | ||
| 113 | // 验证偏移量宽度 | 122 | // 验证偏移量宽度 |
| 114 | if (data.offset_width > MAX_OFFSET_WIDTH) { | 123 | if (data.offset_width > MAX_OFFSET_WIDTH) { |
| 115 | MC_THROW(mc::invalid_arg_exception, "offsetWidth must be 0~4"); | 124 | MC_THROW(mc::invalid_arg_exception, "offsetWidth must be 0~4"); |
| @@ -119,7 +128,7 @@ std::string_view bus_i2c::format_tx_buf(const ACCESS_DATA_S& data) | |||
| 119 | m_tx_buffer.clear(); | 128 | m_tx_buffer.clear(); |
| 120 | 129 | ||
| 121 | // 计算总长度:地址(1字节) + 偏移量(offset_width字节) + 数据长度 | 130 | // 计算总长度:地址(1字节) + 偏移量(offset_width字节) + 数据长度 |
| 122 | - size_t total_length = 1 + data.offset_width + data.length; | 131 | + size_t total_length = 1 + data.offset_width + write_length; |
| 123 | m_tx_buffer.reserve(total_length); | 132 | m_tx_buffer.reserve(total_length); |
| 124 | 133 | ||
| 125 | // 添加地址字节 | 134 | // 添加地址字节 |
| @@ -131,8 +140,8 @@ std::string_view bus_i2c::format_tx_buf(const ACCESS_DATA_S& data) | |||
| 131 | } | 140 | } |
| 132 | 141 | ||
| 133 | // 添加数据缓冲区内容 | 142 | // 添加数据缓冲区内容 |
| 134 | - if (!data.in_buffer.empty() && data.length > 0) { | 143 | + if (write_length > 0) { |
| 135 | - m_tx_buffer.append(reinterpret_cast<const char*>(data.in_buffer.data()), data.length); | 144 | + m_tx_buffer.append(reinterpret_cast<const char*>(data.in_buffer.data()), write_length); |
| 136 | } | 145 | } |
| 137 | 146 | ||
| 138 | return std::string_view(m_tx_buffer); | 147 | return std::string_view(m_tx_buffer); |
| @@ -260,7 +269,7 @@ uint32_t bus_i2c::write(ACCESS_DATA_S& data) | |||
| 260 | } | 269 | } |
| 261 | 270 | ||
| 262 | // 准备写入数据 | 271 | // 准备写入数据 |
| 263 | - const std::string_view tx_buf = format_tx_buf(data); | 272 | + const std::string_view tx_buf = format_tx_buf(data, data.length); |
| 264 | 273 | ||
| 265 | int32_t ret = 0; | 274 | int32_t ret = 0; |
| 266 | try { | 275 | try { |
| @@ -81,6 +81,8 @@ public: | |||
| 81 | #endif | 81 | #endif |
| 82 | 82 | ||
| 83 | private: | 83 | private: |
| 84 | + std::string_view format_tx_buf(const ACCESS_DATA_S& data, size_t write_length); | ||
| 85 | + | ||
| 84 | uint8_t m_id = 0; | 86 | uint8_t m_id = 0; |
| 85 | uint32_t m_speed = DEFAULT_SPEED; | 87 | uint32_t m_speed = DEFAULT_SPEED; |
| 86 | uint8_t m_mode = DEFAULT_MODE; | 88 | uint8_t m_mode = DEFAULT_MODE; |
| @@ -504,6 +504,9 @@ int32_t chip_base::data_access(uint32_t mask, uint32_t offset, uint32_t length, | |||
| 504 | ACCESS_DATA_S data = {}; | 504 | ACCESS_DATA_S data = {}; |
| 505 | m_last_access_error.clear(); | 505 | m_last_access_error.clear(); |
| 506 | try { | 506 | try { |
| 507 | + if (optype == CHIP_OP_TYPE_WRITE && length > in_buffer.size()) { | ||
| 508 | + MC_THROW(mc::invalid_arg_exception, "write length exceeds input buffer size"); | ||
| 509 | + } | ||
| 507 | data = pack_access_data(mask, offset, length, in_buffer, out_buffer, optype, protocol_flag); | 510 | data = pack_access_data(mask, offset, length, in_buffer, out_buffer, optype, protocol_flag); |
| 508 | result = input(data); | 511 | result = input(data); |
| 509 | 512 | ||
| @@ -519,7 +522,7 @@ int32_t chip_base::data_access(uint32_t mask, uint32_t offset, uint32_t length, | |||
| 519 | } | 522 | } |
| 520 | 523 | ||
| 521 | // 对于读取操作,将读取的数据移动回原始的 out_buffer | 524 | // 对于读取操作,将读取的数据移动回原始的 out_buffer |
| 522 | - if (optype == CHIP_OP_TYPE_READ && !data.out_buffer.empty()) { | 525 | + if (optype == CHIP_OP_TYPE_READ) { |
| 523 | out_buffer = std::move(data.out_buffer); | 526 | out_buffer = std::move(data.out_buffer); |
| 524 | } | 527 | } |
| 525 | } catch (const std::exception& e) { | 528 | } catch (const std::exception& e) { |
| @@ -627,6 +630,15 @@ int32_t chip_base::bit_read(uint32_t offset, uint8_t length, uint32_t mask, std: | |||
| 627 | return ret; | 630 | return ret; |
| 628 | } | 631 | } |
| 629 | 632 | ||
| 633 | + // An empty reply must not be replaced by the caller's preallocated buffer. | ||
| 634 | + const size_t required_length = (length == 0 && mask != 0) ? 1 : length; | ||
| 635 | + if (out_buffer.size() < required_length) { | ||
| 636 | + error = "bit_read response too short"; | ||
| 637 | + record_error_history(error); | ||
| 638 | + out_buffer.clear(); | ||
| 639 | + return HAS_ERR; | ||
| 640 | + } | ||
| 641 | + | ||
| 630 | // 对读取的数据进行掩码处理。 | 642 | // 对读取的数据进行掩码处理。 |
| 631 | // 注意:has_shifted_bit_read=true 的总线(如 LocalBus)已在 HAL 层完成 | 643 | // 注意:has_shifted_bit_read=true 的总线(如 LocalBus)已在 HAL 层完成 |
| 632 | // (value & mask) >> ctz(mask) 的位域提取,返回的是移位后的字段值,此处无需重复处理; | 644 | // (value & mask) >> ctz(mask) 的位域提取,返回的是移位后的字段值,此处无需重复处理; |
| @@ -641,13 +653,16 @@ int32_t chip_base::bit_read(uint32_t offset, uint8_t length, uint32_t mask, std: | |||
| 641 | result_buffer.push_back(chip_base_get_value_with_mask(mask, data_buf[0])); | 653 | result_buffer.push_back(chip_base_get_value_with_mask(mask, data_buf[0])); |
| 642 | break; | 654 | break; |
| 643 | case sizeof(uint16_t): { | 655 | case sizeof(uint16_t): { |
| 644 | - uint16_t value = chip_base_get_value_with_mask(mask, *((uint16_t*)data_buf)); | 656 | + uint16_t raw = static_cast<uint16_t>(data_buf[0]) | (static_cast<uint16_t>(data_buf[1]) << 8); |
| 657 | + uint16_t value = chip_base_get_value_with_mask(mask, raw); | ||
| 645 | result_buffer.push_back(value & 0xFF); | 658 | result_buffer.push_back(value & 0xFF); |
| 646 | result_buffer.push_back(value >> 8); | 659 | result_buffer.push_back(value >> 8); |
| 647 | break; | 660 | break; |
| 648 | } | 661 | } |
| 649 | case sizeof(uint32_t): { | 662 | case sizeof(uint32_t): { |
| 650 | - uint32_t value = chip_base_get_value_with_mask(mask, *((uint32_t*)data_buf)); | 663 | + uint32_t raw = static_cast<uint32_t>(data_buf[0]) | (static_cast<uint32_t>(data_buf[1]) << 8) | |
| 664 | + (static_cast<uint32_t>(data_buf[2]) << 16) | (static_cast<uint32_t>(data_buf[3]) << 24); | ||
| 665 | + uint32_t value = chip_base_get_value_with_mask(mask, raw); | ||
| 651 | result_buffer.push_back(value & 0xFF); | 666 | result_buffer.push_back(value & 0xFF); |
| 652 | result_buffer.push_back((value >> 8) & 0xFF); | 667 | result_buffer.push_back((value >> 8) & 0xFF); |
| 653 | result_buffer.push_back((value >> 16) & 0xFF); | 668 | result_buffer.push_back((value >> 16) & 0xFF); |
| @@ -497,7 +497,10 @@ int32_t chip_canbus_chip::bit_write(uint32_t offset, uint8_t length, uint32_t ma | |||
| 497 | uint8_t cmd = static_cast<uint8_t>((offset >> 16) & 0xFF); | 497 | uint8_t cmd = static_cast<uint8_t>((offset >> 16) & 0xFF); |
| 498 | uint16_t sig_id = static_cast<uint16_t>(offset & 0xFFFF); | 498 | uint16_t sig_id = static_cast<uint16_t>(offset & 0xFFFF); |
| 499 | uint8_t pos = find_data_pos(length); | 499 | uint8_t pos = find_data_pos(length); |
| 500 | - uint8_t pad = pos - kEngcanSigIdLen; | 500 | + if (in_buffer.size() < length) { |
| 501 | + MC_THROW(mc::invalid_arg_exception, "CAN bit_write input buffer too short"); | ||
| 502 | + } | ||
| 503 | + uint8_t pad = pos - kEngcanSigIdLen; | ||
| 501 | 504 | ||
| 502 | // Build CAN ID | 505 | // Build CAN ID |
| 503 | uint32_t can_id = pack_can_id(0, 0x3F, 1, kEngcanCmdBase + cmd, m_address, m_protocol, kExtendedFrameType); | 506 | uint32_t can_id = pack_can_id(0, 0x3F, 1, kEngcanCmdBase + cmd, m_address, m_protocol, kExtendedFrameType); |
| @@ -167,7 +167,12 @@ int32_t chip_smc::bit_read(uint32_t offset, uint8_t size, uint32_t mask, std::ve | |||
| 167 | para--; | 167 | para--; |
| 168 | } | 168 | } |
| 169 | 169 | ||
| 170 | - uint32_t data_length = read_buffer[SMC_READ_LEN_OFFSET] - SMC_EXTRA_LEN; | 170 | + const uint32_t response_length = read_buffer[SMC_READ_LEN_OFFSET]; |
| 171 | + if (response_length < SMC_EXTRA_LEN) { | ||
| 172 | + error = "response error, invalid smc response length"; | ||
| 173 | + return HAS_ERR; | ||
| 174 | + } | ||
| 175 | + const uint32_t data_length = response_length - SMC_EXTRA_LEN; | ||
| 171 | if (para * size + size > data_length) { | 176 | if (para * size + size > data_length) { |
| 172 | error = "request error, error para, para: " + std::to_string(para) + ", size: " + std::to_string(size) + | 177 | error = "request error, error para, para: " + std::to_string(para) + ", size: " + std::to_string(size) + |
| 173 | ", read_len: " + std::to_string(data_length); | 178 | ", read_len: " + std::to_string(data_length); |
| @@ -176,27 +181,14 @@ int32_t chip_smc::bit_read(uint32_t offset, uint8_t size, uint32_t mask, std::ve | |||
| 176 | 181 | ||
| 177 | uint8_t* dataBuf = read_buffer + SMC_READ_DATA_OFFSET + para * size; | 182 | uint8_t* dataBuf = read_buffer + SMC_READ_DATA_OFFSET + para * size; |
| 178 | 183 | ||
| 179 | - switch (size) { | 184 | + // SMC register payloads are little-endian byte sequences. |
| 180 | - case sizeof(uint8_t): | 185 | + uint32_t raw_value = 0; |
| 181 | - out_buffer.push_back(chip_smc_get_value_with_mask(mask, dataBuf[0])); | 186 | + for (uint8_t i = 0; i < size; ++i) { |
| 182 | - break; | 187 | + raw_value |= static_cast<uint32_t>(dataBuf[i]) << (BITS_PER_BYTE * i); |
| 183 | - case sizeof(uint16_t): { | 188 | + } |
| 184 | - uint16_t value = chip_smc_get_value_with_mask(mask, *((uint16_t*)dataBuf)); | 189 | + const uint32_t value = chip_smc_get_value_with_mask(mask, raw_value); |
| 185 | - out_buffer.push_back(value & 0xFF); | 190 | + for (uint8_t i = 0; i < size; ++i) { |
| 186 | - out_buffer.push_back(value >> 8); | 191 | + out_buffer.push_back(static_cast<uint8_t>(value >> (BITS_PER_BYTE * i))); |
| 187 | - break; | ||
| 188 | - } | ||
| 189 | - case sizeof(uint32_t): { | ||
| 190 | - uint32_t value = chip_smc_get_value_with_mask(mask, *((uint32_t*)dataBuf)); | ||
| 191 | - out_buffer.push_back(value & 0xFF); | ||
| 192 | - out_buffer.push_back((value >> 8) & 0xFF); | ||
| 193 | - out_buffer.push_back((value >> 16) & 0xFF); | ||
| 194 | - out_buffer.push_back(value >> 24); | ||
| 195 | - break; | ||
| 196 | - } | ||
| 197 | - default: | ||
| 198 | - // 早期校验已拒绝 size=0/3/>4,此处不可达 | ||
| 199 | - __builtin_unreachable(); | ||
| 200 | } | 192 | } |
| 201 | 193 | ||
| 202 | return HAS_OK; | 194 | return HAS_OK; |
| @@ -214,8 +206,14 @@ int32_t chip_smc::bit_write(uint32_t offset, uint8_t size, uint32_t mask, const | |||
| 214 | return HAS_OK; | 206 | return HAS_OK; |
| 215 | } | 207 | } |
| 216 | 208 | ||
| 217 | - uint32_t para = offset & SMC_PARA_MASK; | 209 | + if (in_buffer.size() < size) { |
| 218 | - if (para == 0 && ((1U << (BITS_PER_BYTE * size)) - 1) == mask) { | 210 | + error = "request error, bit_write input length is shorter than size"; |
| 211 | + return HAS_ERR; | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + uint32_t para = offset & SMC_PARA_MASK; | ||
| 215 | + const uint32_t full_mask = size == sizeof(uint32_t) ? UINT32_MAX : ((1U << (BITS_PER_BYTE * size)) - 1U); | ||
| 216 | + if (para == 0 && full_mask == mask) { | ||
| 219 | // mask 全为1时不需要读取硬件值, 直接写入硬件 | 217 | // mask 全为1时不需要读取硬件值, 直接写入硬件 |
| 220 | return write(offset, size, in_buffer.data(), error); | 218 | return write(offset, size, in_buffer.data(), error); |
| 221 | } | 219 | } |
| @@ -230,28 +228,27 @@ int32_t chip_smc::bit_write(uint32_t offset, uint8_t size, uint32_t mask, const | |||
| 230 | return ret; | 228 | return ret; |
| 231 | } | 229 | } |
| 232 | 230 | ||
| 233 | - uint32_t data_length = read_buffer[SMC_READ_LEN_OFFSET] - 1; | 231 | + const uint32_t response_length = read_buffer[SMC_READ_LEN_OFFSET]; |
| 232 | + if (response_length < SMC_EXTRA_LEN) { | ||
| 233 | + error = "response error, invalid smc response length"; | ||
| 234 | + return HAS_ERR; | ||
| 235 | + } | ||
| 236 | + const uint32_t data_length = response_length - SMC_EXTRA_LEN; | ||
| 234 | if (para * size + size > data_length) { | 237 | if (para * size + size > data_length) { |
| 235 | error = "bit_write para size overflow"; | 238 | error = "bit_write para size overflow"; |
| 236 | return HAS_ERR; | 239 | return HAS_ERR; |
| 237 | } | 240 | } |
| 238 | 241 | ||
| 239 | - uint8_t* dataBuf = read_buffer + SMC_READ_DATA_OFFSET + para * size; | 242 | + uint8_t* dataBuf = read_buffer + SMC_READ_DATA_OFFSET + para * size; |
| 240 | - switch (size) { | 243 | + uint32_t original = 0; |
| 241 | - case sizeof(uint8_t): | 244 | + uint32_t input = 0; |
| 242 | - *((uint8_t*)dataBuf) = chip_smc_set_value_with_mask(mask, *((uint8_t*)dataBuf), in_buffer[0]); | 245 | + for (uint8_t i = 0; i < size; ++i) { |
| 243 | - break; | 246 | + original |= static_cast<uint32_t>(dataBuf[i]) << (BITS_PER_BYTE * i); |
| 244 | - case sizeof(uint16_t): | 247 | + input |= static_cast<uint32_t>(in_buffer[i]) << (BITS_PER_BYTE * i); |
| 245 | - *((uint16_t*)dataBuf) = | 248 | + } |
| 246 | - chip_smc_set_value_with_mask(mask, *((uint16_t*)dataBuf), *((uint16_t*)in_buffer.data())); | 249 | + const uint32_t value = chip_smc_set_value_with_mask(mask, original, input); |
| 247 | - break; | 250 | + for (uint8_t i = 0; i < size; ++i) { |
| 248 | - case sizeof(uint32_t): | 251 | + dataBuf[i] = static_cast<uint8_t>(value >> (BITS_PER_BYTE * i)); |
| 249 | - *((uint32_t*)dataBuf) = | ||
| 250 | - chip_smc_set_value_with_mask(mask, *((uint32_t*)dataBuf), *((uint32_t*)in_buffer.data())); | ||
| 251 | - break; | ||
| 252 | - default: | ||
| 253 | - // 早期校验已拒绝 size=0/3/>4,此处不可达 | ||
| 254 | - __builtin_unreachable(); | ||
| 255 | } | 252 | } |
| 256 | 253 | ||
| 257 | return write(offset, size, dataBuf, error); | 254 | return write(offset, size, dataBuf, error); |
| @@ -446,11 +443,16 @@ uint32_t chip_smc::read_data(uint32_t read_length, uint8_t* out_buffer, uint8_t | |||
| 446 | return HAS_ERR; | 443 | return HAS_ERR; |
| 447 | } | 444 | } |
| 448 | 445 | ||
| 449 | - if (out_buffer_vec.at(SMC_RESP_RAW_CC_OFFSET) != SMC_CC_SUCCESS) { | 446 | + if (out_buffer_vec.size() <= SMC_RESP_RAW_CC_OFFSET) { |
| 450 | - if (out_buffer_vec.at(SMC_RESP_RAW_CC_OFFSET) == SMC_CC_NOT_READY) { | 447 | + error = "response error, smc response length is too short"; |
| 448 | + return HAS_ERR; | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + if (out_buffer_vec[SMC_RESP_RAW_CC_OFFSET] != SMC_CC_SUCCESS) { | ||
| 452 | + if (out_buffer_vec[SMC_RESP_RAW_CC_OFFSET] == SMC_CC_NOT_READY) { | ||
| 451 | continue; | 453 | continue; |
| 452 | } else { | 454 | } else { |
| 453 | - m_last_smc_cc = out_buffer_vec.at(SMC_RESP_RAW_CC_OFFSET); | 455 | + m_last_smc_cc = out_buffer_vec[SMC_RESP_RAW_CC_OFFSET]; |
| 454 | error = get_object_name() + " Smc read_data : request error, " + | 456 | error = get_object_name() + " Smc read_data : request error, " + |
| 455 | std::string(smc_cc_to_string(m_last_smc_cc)) + | 457 | std::string(smc_cc_to_string(m_last_smc_cc)) + |
| 456 | " , SMC completion code:" + std::to_string(m_last_smc_cc); | 458 | " , SMC completion code:" + std::to_string(m_last_smc_cc); |
| @@ -459,12 +461,23 @@ uint32_t chip_smc::read_data(uint32_t read_length, uint8_t* out_buffer, uint8_t | |||
| 459 | } | 461 | } |
| 460 | } | 462 | } |
| 461 | 463 | ||
| 462 | - if (static_cast<uint32_t>(out_buffer_vec.at(SMC_RESP_RAW_READ_LEN_OFFSET) + SMC_MIN_READ_LEN) > read_length) { | 464 | + const uint32_t response_length = out_buffer_vec[SMC_RESP_RAW_READ_LEN_OFFSET]; |
| 463 | - read_length = out_buffer_vec.at(SMC_RESP_RAW_READ_LEN_OFFSET) + SMC_MIN_READ_LEN; | 465 | + if (response_length < SMC_EXTRA_LEN) { |
| 466 | + error = "response error, invalid smc response length"; | ||
| 467 | + return HAS_ERR; | ||
| 468 | + } | ||
| 469 | + const uint32_t frame_length = response_length + SMC_MIN_READ_LEN; | ||
| 470 | + if (frame_length > read_length) { | ||
| 471 | + read_length = frame_length; | ||
| 464 | continue; | 472 | continue; |
| 465 | } | 473 | } |
| 466 | 474 | ||
| 467 | - out_buffer_vec.resize(out_buffer_vec.at(SMC_RESP_RAW_READ_LEN_OFFSET) + SMC_MIN_READ_LEN); | 475 | + if (out_buffer_vec.size() < frame_length) { |
| 476 | + error = "response error, smc response length exceeds received bytes"; | ||
| 477 | + return HAS_ERR; | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + out_buffer_vec.resize(frame_length); | ||
| 468 | 481 | ||
| 469 | // 倒序插入, 拼凑原始响应数据计算响应crc | 482 | // 倒序插入, 拼凑原始响应数据计算响应crc |
| 470 | out_buffer_vec.insert(out_buffer_vec.begin(), m_address | 1); | 483 | out_buffer_vec.insert(out_buffer_vec.begin(), m_address | 1); |
| @@ -473,8 +486,7 @@ uint32_t chip_smc::read_data(uint32_t read_length, uint8_t* out_buffer, uint8_t | |||
| 473 | 486 | ||
| 474 | // 构建crc_input时去掉最后一个字节的响应crc | 487 | // 构建crc_input时去掉最后一个字节的响应crc |
| 475 | std::string crc_input(out_buffer_vec.begin(), out_buffer_vec.end() - 1); | 488 | std::string crc_input(out_buffer_vec.begin(), out_buffer_vec.end() - 1); |
| 476 | - uint8_t real_length = out_buffer_vec.at(SMC_READ_LEN_OFFSET); | 489 | + uint8_t crc8 = mc::crypto::CRC8().calculate(crc_input); |
| 477 | - uint8_t crc8 = mc::crypto::CRC8().calculate(crc_input); | ||
| 478 | if (crc8 != out_buffer_vec.back()) { | 490 | if (crc8 != out_buffer_vec.back()) { |
| 479 | error = | 491 | error = |
| 480 | "crc mismatch, crc8: " + std::to_string(crc8) + ", expected: " + std::to_string(out_buffer_vec.back()); | 492 | "crc mismatch, crc8: " + std::to_string(crc8) + ", expected: " + std::to_string(out_buffer_vec.back()); |
| @@ -623,7 +635,13 @@ uint32_t chip_smc::forward_read(uint32_t length, std::vector<uint8_t>& data_out) | |||
| 623 | return ret; | 635 | return ret; |
| 624 | } | 636 | } |
| 625 | 637 | ||
| 626 | - uint32_t actual_length = std::min(length, static_cast<uint32_t>(out_buffer[SMC_READ_LEN_OFFSET] - 1U)); | 638 | + const uint32_t response_length = out_buffer[SMC_READ_LEN_OFFSET]; |
| 639 | + if (response_length < SMC_EXTRA_LEN || | ||
| 640 | + response_length - SMC_EXTRA_LEN > SMC_MAX_BUFFER_LEN - SMC_READ_DATA_OFFSET) { | ||
| 641 | + elog("forward_read failed: invalid smc response length"); | ||
| 642 | + return HAS_ERR; | ||
| 643 | + } | ||
| 644 | + const uint32_t actual_length = std::min(length, response_length - SMC_EXTRA_LEN); | ||
| 627 | data_out.resize(actual_length); | 645 | data_out.resize(actual_length); |
| 628 | for (uint32_t i = 0; i < actual_length; i++) { | 646 | for (uint32_t i = 0; i < actual_length; i++) { |
| 629 | data_out[i] = out_buffer[i + SMC_READ_DATA_OFFSET]; | 647 | data_out[i] = out_buffer[i + SMC_READ_DATA_OFFSET]; |
| @@ -685,10 +703,20 @@ int32_t chip_smc::forward_by_front_chip(ACCESS_DATA_S& data, const std::string& | |||
| 685 | return forward_read_mctp(data.length, data.out_buffer, error); | 703 | return forward_read_mctp(data.length, data.out_buffer, error); |
| 686 | } | 704 | } |
| 687 | 705 | ||
| 706 | + if (data.offset_width > sizeof(data.offset)) { | ||
| 707 | + elog("forward request has invalid offset width: ${width}", ("width", data.offset_width)); | ||
| 708 | + return HAS_ERR; | ||
| 709 | + } | ||
| 710 | + | ||
| 688 | if (data.optype == CHIP_OP_TYPE_WRITE) { | 711 | if (data.optype == CHIP_OP_TYPE_WRITE) { |
| 689 | uint8_t buffer[BUFFER_LEN] = {0}; | 712 | uint8_t buffer[BUFFER_LEN] = {0}; |
| 690 | uint8_t chip_type = is_smc ? FORWARD_TYPE_SMC : 0x00; | 713 | uint8_t chip_type = is_smc ? FORWARD_TYPE_SMC : 0x00; |
| 691 | 714 | ||
| 715 | + if (data.length > data.in_buffer.size() || data.length > BUFFER_LEN - data.offset_width) { | ||
| 716 | + elog("forward write length exceeds input or buffer capacity"); | ||
| 717 | + return HAS_ERR; | ||
| 718 | + } | ||
| 719 | + | ||
| 692 | uint32_t offset = calc_forward_opcode(data.addr, data.length, chip_type, bus_id); | 720 | uint32_t offset = calc_forward_opcode(data.addr, data.length, chip_type, bus_id); |
| 693 | uint32_t buffer_offset = 0; | 721 | uint32_t buffer_offset = 0; |
| 694 | for (uint32_t i = data.offset_width; i > 0; i--) { | 722 | for (uint32_t i = data.offset_width; i > 0; i--) { |
| @@ -118,9 +118,18 @@ bool NetworkPort::update_partition_info_by_ncsi() | |||
| 118 | void NetworkPort::start_ncsi_update_task_broadcom(ncsi_over_mctp_broadcom_ptr ncsi_over_mctp, mc::milliseconds interval, | 118 | void NetworkPort::start_ncsi_update_task_broadcom(ncsi_over_mctp_broadcom_ptr ncsi_over_mctp, mc::milliseconds interval, |
| 119 | uint8_t package_id) | 119 | uint8_t package_id) |
| 120 | { | 120 | { |
| 121 | + // A queued callback can retain the old timer beyond member replacement. | ||
| 122 | + // Stop and join it before changing the state that its callback accesses. | ||
| 123 | + if (m_ncsi_timer) { | ||
| 124 | + m_ncsi_timer->stop(); | ||
| 125 | + } | ||
| 121 | m_ncsi_over_mctp_broadcom = ncsi_over_mctp; | 126 | m_ncsi_over_mctp_broadcom = ncsi_over_mctp; |
| 122 | - m_ncsi_timer = mc::make_shared<mc::timer>(this); | 127 | + // The interface is a value member; queued timers must not retain its |
| 123 | - m_package_id = package_id; | 128 | + // parent link, and must run on the owning device's executor. |
| 129 | + m_ncsi_timer = mc::make_shared<mc::timer>(); | ||
| 130 | + auto* owner = get_owner(); | ||
| 131 | + m_ncsi_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 132 | + m_package_id = package_id; | ||
| 124 | 133 | ||
| 125 | m_ncsi_timer->timeout.connect([this]() { | 134 | m_ncsi_timer->timeout.connect([this]() { |
| 126 | update_partition_info_by_ncsi(); | 135 | update_partition_info_by_ncsi(); |
| @@ -56,6 +56,7 @@ public: | |||
| 56 | bool SetLLDPTxEnable(bool enable) override; | 56 | bool SetLLDPTxEnable(bool enable) override; |
| 57 | 57 | ||
| 58 | private: | 58 | private: |
| 59 | + friend struct BoardcomNcsiTimerTestAccess; | ||
| 59 | ncsi_over_mctp_broadcom_ptr m_ncsi_over_mctp_broadcom; | 60 | ncsi_over_mctp_broadcom_ptr m_ncsi_over_mctp_broadcom; |
| 60 | mc::shared_ptr<mc::timer> m_ncsi_timer; | 61 | mc::shared_ptr<mc::timer> m_ncsi_timer; |
| 61 | uint8_t m_package_id = 0; | 62 | uint8_t m_package_id = 0; |
| @@ -44,9 +44,18 @@ bool OpticalModule::get_presence_info_by_ncsi() | |||
| 44 | void OpticalModule::start_ncsi_update_task_broadcom(ncsi_over_mctp_broadcom_ptr ncsi_over_mctp, | 44 | void OpticalModule::start_ncsi_update_task_broadcom(ncsi_over_mctp_broadcom_ptr ncsi_over_mctp, |
| 45 | mc::milliseconds interval, uint8_t package_id) | 45 | mc::milliseconds interval, uint8_t package_id) |
| 46 | { | 46 | { |
| 47 | + // A queued callback can retain the old timer beyond member replacement. | ||
| 48 | + // Stop and join it before changing the state that its callback accesses. | ||
| 49 | + if (m_ncsi_timer) { | ||
| 50 | + m_ncsi_timer->stop(); | ||
| 51 | + } | ||
| 47 | m_ncsi_over_mctp_broadcom = ncsi_over_mctp; | 52 | m_ncsi_over_mctp_broadcom = ncsi_over_mctp; |
| 48 | - m_ncsi_timer = mc::make_shared<mc::timer>(this); | 53 | + // The interface is a value member; queued timers must not retain its |
| 49 | - m_package_id = package_id; | 54 | + // parent link, and must run on the owning device's executor. |
| 55 | + m_ncsi_timer = mc::make_shared<mc::timer>(); | ||
| 56 | + auto* owner = get_owner(); | ||
| 57 | + m_ncsi_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 58 | + m_package_id = package_id; | ||
| 50 | m_ncsi_timer->timeout.connect([this]() { | 59 | m_ncsi_timer->timeout.connect([this]() { |
| 51 | get_presence_info_by_ncsi(); | 60 | get_presence_info_by_ncsi(); |
| 52 | }); | 61 | }); |
| @@ -62,6 +62,7 @@ public: | |||
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | private: | 64 | private: |
| 65 | + friend struct BoardcomNcsiTimerTestAccess; | ||
| 65 | ncsi_over_mctp_broadcom_ptr m_ncsi_over_mctp_broadcom; | 66 | ncsi_over_mctp_broadcom_ptr m_ncsi_over_mctp_broadcom; |
| 66 | mc::shared_ptr<mc::timer> m_ncsi_timer; | 67 | mc::shared_ptr<mc::timer> m_ncsi_timer; |
| 67 | uint8_t m_package_id = 0; | 68 | uint8_t m_package_id = 0; |
| @@ -18,6 +18,7 @@ | |||
| 18 | #include <mc/runtime.h> | 18 | #include <mc/runtime.h> |
| 19 | #include <string> | 19 | #include <string> |
| 20 | 20 | ||
| 21 | +#include "../../owned_timer.h" | ||
| 21 | #include "hi1825_board_card.h" | 22 | #include "hi1825_board_card.h" |
| 22 | 23 | ||
| 23 | namespace dev { | 24 | namespace dev { |
| @@ -29,7 +30,8 @@ hi1825_board_card::hi1825_board_card() | |||
| 29 | 30 | ||
| 30 | hi1825_board_card::~hi1825_board_card() | 31 | hi1825_board_card::~hi1825_board_card() |
| 31 | { | 32 | { |
| 32 | - // 清理资源 | 33 | + stop_owned_timer(m_smbus_start_delay_timer); |
| 34 | + stop_owned_timer(m_smbus_update_timer); | ||
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | void hi1825_board_card::init_network_ports() | 37 | void hi1825_board_card::init_network_ports() |
| @@ -260,6 +262,8 @@ void hi1825_board_card::attach_smbus_obj(smbus_obj_ptr smbus_obj) | |||
| 260 | 262 | ||
| 261 | bool hi1825_board_card::start_smbus_protocol() | 263 | bool hi1825_board_card::start_smbus_protocol() |
| 262 | { | 264 | { |
| 265 | + stop_owned_timer(m_smbus_start_delay_timer); | ||
| 266 | + stop_owned_timer(m_smbus_update_timer); | ||
| 263 | register_RefChip(); | 267 | register_RefChip(); |
| 264 | if (!init_smbus_protocol()) { | 268 | if (!init_smbus_protocol()) { |
| 265 | return false; | 269 | return false; |
| @@ -277,16 +281,16 @@ bool hi1825_board_card::start_smbus_protocol() | |||
| 277 | 281 | ||
| 278 | const uint8_t slot = m_pcie_device.Slot.get_value().as<uint8_t>(); | 282 | const uint8_t slot = m_pcie_device.Slot.get_value().as<uint8_t>(); |
| 279 | 283 | ||
| 280 | - m_smbus_update_timer = mc::make_shared<mc::timer>(this); | 284 | + m_smbus_update_timer = mc::make_shared<mc::timer>(); |
| 285 | + m_smbus_update_timer->bind_executor(executor()); | ||
| 281 | m_smbus_update_timer->timeout.connect([this]() { | 286 | m_smbus_update_timer->timeout.connect([this]() { |
| 282 | update_info_by_smbus(); | 287 | update_info_by_smbus(); |
| 283 | }); | 288 | }); |
| 284 | m_smbus_update_timer->set_single_shot(false); | 289 | m_smbus_update_timer->set_single_shot(false); |
| 285 | 290 | ||
| 286 | - m_smbus_start_delay_timer = mc::make_shared<mc::timer>(this); | 291 | + m_smbus_start_delay_timer = mc::make_shared<mc::timer>(); |
| 292 | + m_smbus_start_delay_timer->bind_executor(executor()); | ||
| 287 | m_smbus_start_delay_timer->timeout.connect([this]() { | 293 | m_smbus_start_delay_timer->timeout.connect([this]() { |
| 288 | - m_smbus_start_delay_timer->stop(); | ||
| 289 | - m_smbus_start_delay_timer.reset(); | ||
| 290 | m_smbus_update_timer->start(m_smbus_interval); | 294 | m_smbus_update_timer->start(m_smbus_interval); |
| 291 | }); | 295 | }); |
| 292 | m_smbus_start_delay_timer->set_single_shot(true); | 296 | m_smbus_start_delay_timer->set_single_shot(true); |
| @@ -296,14 +300,8 @@ bool hi1825_board_card::start_smbus_protocol() | |||
| 296 | 300 | ||
| 297 | void hi1825_board_card::stop_smbus_protocol() | 301 | void hi1825_board_card::stop_smbus_protocol() |
| 298 | { | 302 | { |
| 299 | - if (m_smbus_start_delay_timer) { | 303 | + stop_owned_timer(m_smbus_start_delay_timer); |
| 300 | - m_smbus_start_delay_timer->stop(); | 304 | + stop_owned_timer(m_smbus_update_timer); |
| 301 | - m_smbus_start_delay_timer.reset(); | ||
| 302 | - } | ||
| 303 | - if (m_smbus_update_timer) { | ||
| 304 | - m_smbus_update_timer->stop(); | ||
| 305 | - m_smbus_update_timer.reset(); | ||
| 306 | - } | ||
| 307 | m_network_adapter_log_collection.clean_smbus_obj(); | 305 | m_network_adapter_log_collection.clean_smbus_obj(); |
| 308 | m_network_adapter_oem.clean_smbus_obj(); | 306 | m_network_adapter_oem.clean_smbus_obj(); |
| 309 | m_pcie_card.clean_smbus_obj(); | 307 | m_pcie_card.clean_smbus_obj(); |
| @@ -12,6 +12,9 @@ | |||
| 12 | 12 | ||
| 13 | #include <endian.h> | 13 | #include <endian.h> |
| 14 | 14 | ||
| 15 | +#include <cstddef> | ||
| 16 | +#include <cstring> | ||
| 17 | + | ||
| 15 | #include "interface/network_adapter/prbs_test.h" | 18 | #include "interface/network_adapter/prbs_test.h" |
| 16 | 19 | ||
| 17 | #include <securec.h> | 20 | #include <securec.h> |
| @@ -126,6 +129,15 @@ struct prbs_get_result_port_rsp_t { | |||
| 126 | }; | 129 | }; |
| 127 | #pragma pack() | 130 | #pragma pack() |
| 128 | 131 | ||
| 132 | +template <typename Response> | ||
| 133 | +static uint32_t read_bit_error_rate(const std::string& rsp, size_t index) | ||
| 134 | +{ | ||
| 135 | + uint32_t value = 0; | ||
| 136 | + const auto offset = offsetof(Response, bit_error_rate) + index * sizeof(value); | ||
| 137 | + std::memcpy(&value, rsp.data() + offset, sizeof(value)); | ||
| 138 | + return le32toh(value); | ||
| 139 | +} | ||
| 140 | + | ||
| 129 | void NetworkAdapter_PRBSTest::set_smbus_obj(mc::shared_ptr<smbus> smbus_obj) | 141 | void NetworkAdapter_PRBSTest::set_smbus_obj(mc::shared_ptr<smbus> smbus_obj) |
| 130 | { | 142 | { |
| 131 | m_smbus_obj = smbus_obj; | 143 | m_smbus_obj = smbus_obj; |
| @@ -193,7 +205,7 @@ static std::tuple<uint8_t, std::vector<uint32_t>, uint32_t, uint32_t> parse_prbs | |||
| 193 | std::vector<uint32_t> bit_error_rates; | 205 | std::vector<uint32_t> bit_error_rates; |
| 194 | bit_error_rates.reserve(PRBS_CHANNEL_NUM); | 206 | bit_error_rates.reserve(PRBS_CHANNEL_NUM); |
| 195 | for (size_t i = 0; i < PRBS_CHANNEL_NUM; i++) { | 207 | for (size_t i = 0; i < PRBS_CHANNEL_NUM; i++) { |
| 196 | - bit_error_rates.push_back(i < lane_num ? rsp_struct->bit_error_rate[i] : 0); | 208 | + bit_error_rates.push_back(i < lane_num ? read_bit_error_rate<prbs_get_result_om_rsp_t>(rsp, i) : 0); |
| 197 | } | 209 | } |
| 198 | uint32_t rx_lost_of_lock = rsp_struct->rx_loss_of_lock; | 210 | uint32_t rx_lost_of_lock = rsp_struct->rx_loss_of_lock; |
| 199 | uint32_t tx_lost_of_lock = rsp_struct->tx_loss_of_lock; | 211 | uint32_t tx_lost_of_lock = rsp_struct->tx_loss_of_lock; |
| @@ -215,7 +227,7 @@ static std::tuple<uint8_t, std::vector<uint32_t>> parse_prbs_get_result_port(con | |||
| 215 | std::vector<uint32_t> bit_error_rates; | 227 | std::vector<uint32_t> bit_error_rates; |
| 216 | bit_error_rates.reserve(PRBS_CHANNEL_NUM); | 228 | bit_error_rates.reserve(PRBS_CHANNEL_NUM); |
| 217 | for (size_t i = 0; i < PRBS_CHANNEL_NUM; i++) { | 229 | for (size_t i = 0; i < PRBS_CHANNEL_NUM; i++) { |
| 218 | - bit_error_rates.push_back(rsp_struct->bit_error_rate[i]); | 230 | + bit_error_rates.push_back(read_bit_error_rate<prbs_get_result_port_rsp_t>(rsp, i)); |
| 219 | } | 231 | } |
| 220 | return {rsp_struct->exe_result, bit_error_rates}; | 232 | return {rsp_struct->exe_result, bit_error_rates}; |
| 221 | } | 233 | } |
| @@ -12,6 +12,7 @@ | |||
| 12 | 12 | ||
| 13 | #include <mc/log.h> | 13 | #include <mc/log.h> |
| 14 | 14 | ||
| 15 | +#include "../../../owned_timer.h" | ||
| 15 | #include "interface/network_port/fibrechannel.h" | 16 | #include "interface/network_port/fibrechannel.h" |
| 16 | 17 | ||
| 17 | namespace dev { | 18 | namespace dev { |
| @@ -65,7 +66,8 @@ void NetworkPort_FibreChannel::start_pldm_update_task_huawei(pldm_over_mctp_hw_p | |||
| 65 | stop_pldm_update_task(); | 66 | stop_pldm_update_task(); |
| 66 | 67 | ||
| 67 | m_pldm_over_mctp_hw = pldm_over_mctp; | 68 | m_pldm_over_mctp_hw = pldm_over_mctp; |
| 68 | - m_pldm_timer = mc::make_shared<mc::timer>(this); | 69 | + m_pldm_timer = mc::make_shared<mc::timer>(); |
| 70 | + m_pldm_timer->bind_executor(executor()); | ||
| 69 | m_pldm_timer->timeout.connect([this, port_id]() { | 71 | m_pldm_timer->timeout.connect([this, port_id]() { |
| 70 | update_fcinfo_by_pldm(port_id); | 72 | update_fcinfo_by_pldm(port_id); |
| 71 | }); | 73 | }); |
| @@ -75,10 +77,7 @@ void NetworkPort_FibreChannel::start_pldm_update_task_huawei(pldm_over_mctp_hw_p | |||
| 75 | 77 | ||
| 76 | void NetworkPort_FibreChannel::stop_pldm_update_task() | 78 | void NetworkPort_FibreChannel::stop_pldm_update_task() |
| 77 | { | 79 | { |
| 78 | - if (m_pldm_timer) { | 80 | + stop_owned_timer(m_pldm_timer); |
| 79 | - m_pldm_timer->stop(); | ||
| 80 | - m_pldm_timer.reset(); | ||
| 81 | - } | ||
| 82 | if (m_pldm_over_mctp_hw) { | 81 | if (m_pldm_over_mctp_hw) { |
| 83 | m_pldm_over_mctp_hw = nullptr; | 82 | m_pldm_over_mctp_hw = nullptr; |
| 84 | } | 83 | } |
| @@ -15,6 +15,7 @@ | |||
| 15 | #include <cmath> | 15 | #include <cmath> |
| 16 | #include <vector> | 16 | #include <vector> |
| 17 | 17 | ||
| 18 | +#include "../../../owned_timer.h" | ||
| 18 | #include "interface/network_port/serdes.h" | 19 | #include "interface/network_port/serdes.h" |
| 19 | #include "securec.h" | 20 | #include "securec.h" |
| 20 | 21 | ||
| @@ -117,7 +118,8 @@ void NetworkPort_Serdes::start_smbus_update_task(smbus_obj_ptr smbus_obj, mc::mi | |||
| 117 | stop_smbus_update_task(); | 118 | stop_smbus_update_task(); |
| 118 | 119 | ||
| 119 | m_smbus_obj = smbus_obj; | 120 | m_smbus_obj = smbus_obj; |
| 120 | - m_smbus_timer = mc::make_shared<mc::timer>(this); | 121 | + m_smbus_timer = mc::make_shared<mc::timer>(); |
| 122 | + m_smbus_timer->bind_executor(executor()); | ||
| 121 | m_smbus_timer->timeout.connect([this, port_id]() { | 123 | m_smbus_timer->timeout.connect([this, port_id]() { |
| 122 | update_port_serdes_info_by_smbus(port_id); | 124 | update_port_serdes_info_by_smbus(port_id); |
| 123 | }); | 125 | }); |
| @@ -127,10 +129,7 @@ void NetworkPort_Serdes::start_smbus_update_task(smbus_obj_ptr smbus_obj, mc::mi | |||
| 127 | 129 | ||
| 128 | void NetworkPort_Serdes::stop_smbus_update_task() | 130 | void NetworkPort_Serdes::stop_smbus_update_task() |
| 129 | { | 131 | { |
| 130 | - if (m_smbus_timer) { | 132 | + stop_owned_timer(m_smbus_timer); |
| 131 | - m_smbus_timer->stop(); | ||
| 132 | - m_smbus_timer.reset(); | ||
| 133 | - } | ||
| 134 | m_smbus_obj = nullptr; | 133 | m_smbus_obj = nullptr; |
| 135 | restore_property_defaults(); | 134 | restore_property_defaults(); |
| 136 | } | 135 | } |
| @@ -12,6 +12,7 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/smart.h" | 13 | #include "interface/nvme/smart.h" |
| 14 | #include <algorithm> | 14 | #include <algorithm> |
| 15 | +#include <cmath> | ||
| 15 | #include <cstddef> | 16 | #include <cstddef> |
| 16 | #include <cstring> | 17 | #include <cstring> |
| 17 | #include <mc/log.h> | 18 | #include <mc/log.h> |
| @@ -73,19 +74,20 @@ bool NVMe_Smart::update_smart_by_mctp() | |||
| 73 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); | 74 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); |
| 74 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); | 75 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); |
| 75 | 76 | ||
| 76 | - uint64_t power_on_hours = 0; | 77 | + uint64_t power_on_hours_low = 0; |
| 78 | + uint64_t power_on_hours_high = 0; | ||
| 77 | for (int i = 0; i < 8; ++i) { | 79 | for (int i = 0; i < 8; ++i) { |
| 78 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8)); | 80 | + power_on_hours_low |= static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8); |
| 79 | - } | 81 | + power_on_hours_high |= static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << (i * 8); |
| 80 | - for (int i = 0; i < 8; ++i) { | ||
| 81 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << ((i + 8) * 8)); | ||
| 82 | } | 82 | } |
| 83 | + const double power_on_hours = | ||
| 84 | + std::ldexp(static_cast<double>(power_on_hours_high), 64) + static_cast<double>(power_on_hours_low); | ||
| 83 | 85 | ||
| 84 | CriticalWarning.set_value(smart_log.critical_warning); | 86 | CriticalWarning.set_value(smart_log.critical_warning); |
| 85 | AvailableSpare.set_value(smart_log.avail_spare); | 87 | AvailableSpare.set_value(smart_log.avail_spare); |
| 86 | UsedPercentage.set_value(smart_log.percent_used); | 88 | UsedPercentage.set_value(smart_log.percent_used); |
| 87 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); | 89 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); |
| 88 | - PowerOnHours.set_value(static_cast<double>(power_on_hours)); | 90 | + PowerOnHours.set_value(power_on_hours); |
| 89 | 91 | ||
| 90 | return true; | 92 | return true; |
| 91 | } | 93 | } |
| @@ -14,6 +14,7 @@ | |||
| 14 | #include <device_tree/interface/PCIeDevice.h> | 14 | #include <device_tree/interface/PCIeDevice.h> |
| 15 | #include <mc/log.h> | 15 | #include <mc/log.h> |
| 16 | #include <nvme/nvme_mi_over_mctp_huawei.h> | 16 | #include <nvme/nvme_mi_over_mctp_huawei.h> |
| 17 | +#include "interface/timer_lifetime.h" | ||
| 17 | 18 | ||
| 18 | namespace dev { | 19 | namespace dev { |
| 19 | 20 | ||
| @@ -25,12 +26,9 @@ hw_nvme_pcie4::hw_nvme_pcie4() : m_mctp_object(nullptr), m_ctrl_id(0) | |||
| 25 | 26 | ||
| 26 | hw_nvme_pcie4::~hw_nvme_pcie4() | 27 | hw_nvme_pcie4::~hw_nvme_pcie4() |
| 27 | { | 28 | { |
| 28 | - if (m_nvme_mi_over_mctp_obj) { | 29 | + m_upgrade.wait_for_completion(); |
| 29 | - m_nvme_mi_over_mctp_obj.reset(); | 30 | + stop_vpd_update_tasks(); |
| 30 | - } | 31 | + stop_mctp_update_tasks(); |
| 31 | - if (m_vpd_obj) { | ||
| 32 | - m_vpd_obj.reset(); | ||
| 33 | - } | ||
| 34 | } | 32 | } |
| 35 | 33 | ||
| 36 | bool hw_nvme_pcie4::init(mc::mutable_dict& csr_object, const mc::dict& connector) | 34 | bool hw_nvme_pcie4::init(mc::mutable_dict& csr_object, const mc::dict& connector) |
| @@ -359,7 +357,7 @@ void hw_nvme_pcie4::start_vpd_update_tasks() | |||
| 359 | m_nvme_product_info.update_product_info_by_vpd(); | 357 | m_nvme_product_info.update_product_info_by_vpd(); |
| 360 | m_nvme_multi_record.update_multi_record_by_vpd(); | 358 | m_nvme_multi_record.update_multi_record_by_vpd(); |
| 361 | }; | 359 | }; |
| 362 | - m_static_update_timer = mc::make_shared<mc::timer>(this); | 360 | + huawei::replace_timer(m_static_update_timer); |
| 363 | m_static_update_timer->timeout.connect(refresh_static_vpd); | 361 | m_static_update_timer->timeout.connect(refresh_static_vpd); |
| 364 | m_static_update_timer->set_single_shot(false); | 362 | m_static_update_timer->set_single_shot(false); |
| 365 | m_static_update_timer->start(m_vpd_static_interval); | 363 | m_static_update_timer->start(m_vpd_static_interval); |
| @@ -405,10 +403,7 @@ void hw_nvme_pcie4::stop_vpd_update_tasks() | |||
| 405 | m_cooling.stop_vpd_update_task(); | 403 | m_cooling.stop_vpd_update_task(); |
| 406 | m_nvme_status.stop_vpd_update_task(); | 404 | m_nvme_status.stop_vpd_update_task(); |
| 407 | m_nvme_basic_management.stop_vpd_update_task(); | 405 | m_nvme_basic_management.stop_vpd_update_task(); |
| 408 | - if (m_static_update_timer) { | 406 | + huawei::stop_and_release_timer(m_static_update_timer); |
| 409 | - m_static_update_timer->stop(); | ||
| 410 | - m_static_update_timer.reset(); | ||
| 411 | - } | ||
| 412 | if (m_vpd_obj) { | 407 | if (m_vpd_obj) { |
| 413 | m_vpd_obj.reset(); | 408 | m_vpd_obj.reset(); |
| 414 | } | 409 | } |
| @@ -88,6 +88,7 @@ public: | |||
| 88 | NVMe_Oem m_nvme_oem; | 88 | NVMe_Oem m_nvme_oem; |
| 89 | 89 | ||
| 90 | private: | 90 | private: |
| 91 | + friend struct HuaweiTimerTestAccess; | ||
| 91 | // VPD 协议类型(华为只支持 NVMe-MI 和 SFF) | 92 | // VPD 协议类型(华为只支持 NVMe-MI 和 SFF) |
| 92 | // NVME_MI = 0, SFF = 1 | 93 | // NVME_MI = 0, SFF = 1 |
| 93 | enum class VpdProtocolType { | 94 | enum class VpdProtocolType { |
| @@ -12,6 +12,7 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/cooling.h" | 13 | #include "interface/cooling.h" |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | 16 | ||
| 16 | namespace { | 17 | namespace { |
| 17 | 18 | ||
| @@ -41,13 +42,12 @@ Cooling::Cooling(mc::milliseconds no_data_debounce) : m_temp_no_data_debounce(no | |||
| 41 | 42 | ||
| 42 | void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) | 43 | void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) |
| 43 | { | 44 | { |
| 44 | - m_vpd_obj = vpd; | 45 | + huawei::replace_timer(m_vpd_timer); |
| 45 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | 46 | + m_vpd_obj = vpd; |
| 46 | m_vpd_timer->timeout.connect([this]() { | 47 | m_vpd_timer->timeout.connect([this]() { |
| 47 | update_cooling_by_vpd(); | 48 | update_cooling_by_vpd(); |
| 48 | }); | 49 | }); |
| 49 | m_vpd_timer->set_single_shot(false); | 50 | m_vpd_timer->set_single_shot(false); |
| 50 | - m_vpd_timer->start(interval); | ||
| 51 | 51 | ||
| 52 | // 设备重新插入后再次 start,重置温度防抖状态使新会话从干净状态开始 | 52 | // 设备重新插入后再次 start,重置温度防抖状态使新会话从干净状态开始 |
| 53 | // (0x80 的 5 分钟计时不跨会话累计) | 53 | // (0x80 的 5 分钟计时不跨会话累计) |
| @@ -58,14 +58,12 @@ void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds inter | |||
| 58 | m_temp_avg_debounce.clear_debounce_val(); | 58 | m_temp_avg_debounce.clear_debounce_val(); |
| 59 | m_temp_fail_debounce = mc::debounce::BinaryContinue(3, 1); | 59 | m_temp_fail_debounce = mc::debounce::BinaryContinue(3, 1); |
| 60 | m_has_valid_temp = false; | 60 | m_has_valid_temp = false; |
| 61 | + m_vpd_timer->start(interval); | ||
| 61 | } | 62 | } |
| 62 | 63 | ||
| 63 | void Cooling::stop_vpd_update_task() | 64 | void Cooling::stop_vpd_update_task() |
| 64 | { | 65 | { |
| 65 | - if (m_vpd_timer) { | 66 | + huawei::stop_and_release_timer(m_vpd_timer); |
| 66 | - m_vpd_timer->stop(); | ||
| 67 | - m_vpd_timer.reset(); | ||
| 68 | - } | ||
| 69 | } | 67 | } |
| 70 | 68 | ||
| 71 | bool Cooling::update_cooling_by_vpd() | 69 | bool Cooling::update_cooling_by_vpd() |
| @@ -38,6 +38,7 @@ public: | |||
| 38 | bool update_cooling_by_vpd(); | 38 | bool update_cooling_by_vpd(); |
| 39 | 39 | ||
| 40 | private: | 40 | private: |
| 41 | + friend struct HuaweiTimerTestAccess; | ||
| 41 | vpd_protocol_ptr m_vpd_obj; | 42 | vpd_protocol_ptr m_vpd_obj; |
| 42 | mc::shared_ptr<mc::timer> m_vpd_timer; | 43 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 43 | 44 | ||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "interface/nvme/basic_management.h" | 13 | #include "interface/nvme/basic_management.h" |
| 14 | #include <chrono> | 14 | #include <chrono> |
| 15 | #include <mc/log.h> | 15 | #include <mc/log.h> |
| 16 | +#include "interface/timer_lifetime.h" | ||
| 16 | #include "mc/debounce/binary_continue.h" | 17 | #include "mc/debounce/binary_continue.h" |
| 17 | 18 | ||
| 18 | namespace dev { | 19 | namespace dev { |
| @@ -20,17 +21,16 @@ namespace dev { | |||
| 20 | void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval, | 21 | void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval, |
| 21 | std::function<void()> on_update_callback) | 22 | std::function<void()> on_update_callback) |
| 22 | { | 23 | { |
| 24 | + huawei::replace_timer(m_vpd_timer); | ||
| 23 | m_vpd_obj = vpd; | 25 | m_vpd_obj = vpd; |
| 24 | m_on_update_callback = on_update_callback; | 26 | m_on_update_callback = on_update_callback; |
| 25 | m_start_time = std::chrono::steady_clock::now(); | 27 | m_start_time = std::chrono::steady_clock::now(); |
| 26 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | ||
| 27 | m_vpd_timer->timeout.connect([this]() { | 28 | m_vpd_timer->timeout.connect([this]() { |
| 28 | if (update_basic_management_by_vpd() && m_on_update_callback) { | 29 | if (update_basic_management_by_vpd() && m_on_update_callback) { |
| 29 | m_on_update_callback(); | 30 | m_on_update_callback(); |
| 30 | } | 31 | } |
| 31 | }); | 32 | }); |
| 32 | m_vpd_timer->set_single_shot(false); | 33 | m_vpd_timer->set_single_shot(false); |
| 33 | - m_vpd_timer->start(interval); | ||
| 34 | debounce_port0_link_active = | 34 | debounce_port0_link_active = |
| 35 | std::make_unique<mc::debounce::BinaryContinue>(NVME_PL_DEBOUNCE_HIGH, NVME_PL_DEBOUNCE_LOW); | 35 | std::make_unique<mc::debounce::BinaryContinue>(NVME_PL_DEBOUNCE_HIGH, NVME_PL_DEBOUNCE_LOW); |
| 36 | debounce_drive_functional = | 36 | debounce_drive_functional = |
| @@ -39,14 +39,12 @@ void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milli | |||
| 39 | std::make_unique<mc::debounce::BinaryContinue>(NVME_DN_DEBOUNCE_HIGH, NVME_DN_DEBOUNCE_LOW); | 39 | std::make_unique<mc::debounce::BinaryContinue>(NVME_DN_DEBOUNCE_HIGH, NVME_DN_DEBOUNCE_LOW); |
| 40 | debounce_link_fault = | 40 | debounce_link_fault = |
| 41 | std::make_unique<mc::debounce::BinaryContinue>(NVME_LINK_FAULT_DEBOUNCE_HIGH, NVME_LINK_FAULT_DEBOUNCE_LOW); | 41 | std::make_unique<mc::debounce::BinaryContinue>(NVME_LINK_FAULT_DEBOUNCE_HIGH, NVME_LINK_FAULT_DEBOUNCE_LOW); |
| 42 | + m_vpd_timer->start(interval); | ||
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | void NVMe_BasicManagement::stop_vpd_update_task() | 45 | void NVMe_BasicManagement::stop_vpd_update_task() |
| 45 | { | 46 | { |
| 46 | - if (m_vpd_timer) { | 47 | + huawei::stop_and_release_timer(m_vpd_timer); |
| 47 | - m_vpd_timer->stop(); | ||
| 48 | - m_vpd_timer.reset(); | ||
| 49 | - } | ||
| 50 | } | 48 | } |
| 51 | 49 | ||
| 52 | bool NVMe_BasicManagement::update_basic_management_by_vpd() | 50 | bool NVMe_BasicManagement::update_basic_management_by_vpd() |
| @@ -47,6 +47,7 @@ public: | |||
| 47 | bool update_basic_management_by_vpd(); | 47 | bool update_basic_management_by_vpd(); |
| 48 | 48 | ||
| 49 | private: | 49 | private: |
| 50 | + friend struct HuaweiTimerTestAccess; | ||
| 50 | vpd_protocol_ptr m_vpd_obj; | 51 | vpd_protocol_ptr m_vpd_obj; |
| 51 | mc::shared_ptr<mc::timer> m_vpd_timer; | 52 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 52 | std::function<void()> m_on_update_callback; | 53 | std::function<void()> m_on_update_callback; |
| @@ -20,6 +20,7 @@ | |||
| 20 | #include <mc/log.h> | 20 | #include <mc/log.h> |
| 21 | #include <mutex> | 21 | #include <mutex> |
| 22 | #include <sstream> | 22 | #include <sstream> |
| 23 | +#include "interface/timer_lifetime.h" | ||
| 23 | #include "nvme/nvme_mi_over_mctp.h" | 24 | #include "nvme/nvme_mi_over_mctp.h" |
| 24 | 25 | ||
| 25 | namespace dev { | 26 | namespace dev { |
| @@ -770,7 +771,7 @@ void NVMe_LogCollection::start_log_collection_task() | |||
| 770 | return; | 771 | return; |
| 771 | } | 772 | } |
| 772 | 773 | ||
| 773 | - m_log_timer = mc::make_shared<mc::timer>(this); | 774 | + huawei::replace_timer(m_log_timer); |
| 774 | m_log_timer->timeout.connect([this]() { | 775 | m_log_timer->timeout.connect([this]() { |
| 775 | collect_all_logs(); | 776 | collect_all_logs(); |
| 776 | }); | 777 | }); |
| @@ -785,10 +786,7 @@ void NVMe_LogCollection::start_log_collection_task() | |||
| 785 | 786 | ||
| 786 | void NVMe_LogCollection::stop_log_collection_task() | 787 | void NVMe_LogCollection::stop_log_collection_task() |
| 787 | { | 788 | { |
| 788 | - if (m_log_timer) { | 789 | + huawei::stop_and_release_timer(m_log_timer); |
| 789 | - m_log_timer->stop(); | ||
| 790 | - m_log_timer.reset(); | ||
| 791 | - } | ||
| 792 | } | 790 | } |
| 793 | 791 | ||
| 794 | } // namespace dev | 792 | } // namespace dev |
| @@ -29,8 +29,11 @@ constexpr auto LOG_COLLECTION_INTERVAL = std::chrono::hours(24); | |||
| 29 | 29 | ||
| 30 | class MC_API NVMe_LogCollection : public mc::engine::interface<NVMe_LogCollection, gen::NVMe_LogCollection> { | 30 | class MC_API NVMe_LogCollection : public mc::engine::interface<NVMe_LogCollection, gen::NVMe_LogCollection> { |
| 31 | public: | 31 | public: |
| 32 | - NVMe_LogCollection() = default; | 32 | + NVMe_LogCollection() = default; |
| 33 | - ~NVMe_LogCollection() = default; | 33 | + ~NVMe_LogCollection() |
| 34 | + { | ||
| 35 | + stop_log_collection_task(); | ||
| 36 | + } | ||
| 34 | 37 | ||
| 35 | bool DumpErrorInfo(const std::string& path) override; | 38 | bool DumpErrorInfo(const std::string& path) override; |
| 36 | bool DumpSmartInfo(const std::string& path) override; | 39 | bool DumpSmartInfo(const std::string& path) override; |
| @@ -53,6 +56,7 @@ public: | |||
| 53 | void collect_all_logs(); | 56 | void collect_all_logs(); |
| 54 | 57 | ||
| 55 | private: | 58 | private: |
| 59 | + friend struct HuaweiTimerTestAccess; | ||
| 56 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; | 60 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; |
| 57 | uint32_t m_ctrl_id = 0; | 61 | uint32_t m_ctrl_id = 0; |
| 58 | mc::shared_ptr<mc::timer> m_log_timer; | 62 | mc::shared_ptr<mc::timer> m_log_timer; |
| @@ -12,11 +12,13 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/smart.h" | 13 | #include "interface/nvme/smart.h" |
| 14 | #include <algorithm> | 14 | #include <algorithm> |
| 15 | +#include <cmath> | ||
| 15 | #include <cstddef> | 16 | #include <cstddef> |
| 16 | #include <cstring> | 17 | #include <cstring> |
| 17 | #include <mc/log.h> | 18 | #include <mc/log.h> |
| 18 | #include <nvme/nvme_mi_over_mctp.h> | 19 | #include <nvme/nvme_mi_over_mctp.h> |
| 19 | #include "interface/nvme/oem.h" | 20 | #include "interface/nvme/oem.h" |
| 21 | +#include "interface/timer_lifetime.h" | ||
| 20 | #include "securec.h" | 22 | #include "securec.h" |
| 21 | 23 | ||
| 22 | namespace dev { | 24 | namespace dev { |
| @@ -24,9 +26,9 @@ namespace dev { | |||
| 24 | void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::milliseconds interval, | 26 | void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::milliseconds interval, |
| 25 | std::function<void()> on_update_callback) | 27 | std::function<void()> on_update_callback) |
| 26 | { | 28 | { |
| 29 | + huawei::replace_timer(m_mctp_timer); | ||
| 27 | m_nvme_mi_obj = nvme_mi_obj; | 30 | m_nvme_mi_obj = nvme_mi_obj; |
| 28 | m_on_update_callback = on_update_callback; | 31 | m_on_update_callback = on_update_callback; |
| 29 | - m_mctp_timer = mc::make_shared<mc::timer>(this); | ||
| 30 | m_mctp_timer->timeout.connect([this]() { | 32 | m_mctp_timer->timeout.connect([this]() { |
| 31 | if (update_smart_by_mctp() && m_on_update_callback) { | 33 | if (update_smart_by_mctp() && m_on_update_callback) { |
| 32 | m_on_update_callback(); | 34 | m_on_update_callback(); |
| @@ -38,10 +40,7 @@ void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::m | |||
| 38 | 40 | ||
| 39 | void NVMe_Smart::stop_mctp_update_task() | 41 | void NVMe_Smart::stop_mctp_update_task() |
| 40 | { | 42 | { |
| 41 | - if (m_mctp_timer) { | 43 | + huawei::stop_and_release_timer(m_mctp_timer); |
| 42 | - m_mctp_timer->stop(); | ||
| 43 | - m_mctp_timer.reset(); | ||
| 44 | - } | ||
| 45 | } | 44 | } |
| 46 | 45 | ||
| 47 | bool NVMe_Smart::update_smart_by_mctp() | 46 | bool NVMe_Smart::update_smart_by_mctp() |
| @@ -83,23 +82,24 @@ bool NVMe_Smart::update_smart_by_mctp() | |||
| 83 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); | 82 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); |
| 84 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); | 83 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); |
| 85 | 84 | ||
| 86 | - uint64_t power_on_hours = 0; | 85 | + uint64_t power_on_hours_low = 0; |
| 86 | + uint64_t power_on_hours_high = 0; | ||
| 87 | for (int i = 0; i < 8; ++i) { | 87 | for (int i = 0; i < 8; ++i) { |
| 88 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8)); | 88 | + power_on_hours_low |= static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8); |
| 89 | - } | 89 | + power_on_hours_high |= static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << (i * 8); |
| 90 | - for (int i = 0; i < 8; ++i) { | ||
| 91 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << ((i + 8) * 8)); | ||
| 92 | } | 90 | } |
| 91 | + const double power_on_hours = | ||
| 92 | + std::ldexp(static_cast<double>(power_on_hours_high), 64) + static_cast<double>(power_on_hours_low); | ||
| 93 | 93 | ||
| 94 | CriticalWarning.set_value(smart_log.critical_warning); | 94 | CriticalWarning.set_value(smart_log.critical_warning); |
| 95 | AvailableSpare.set_value(smart_log.avail_spare); | 95 | AvailableSpare.set_value(smart_log.avail_spare); |
| 96 | UsedPercentage.set_value(smart_log.percent_used); | 96 | UsedPercentage.set_value(smart_log.percent_used); |
| 97 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); | 97 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); |
| 98 | - PowerOnHours.set_value(static_cast<double>(power_on_hours)); | 98 | + PowerOnHours.set_value(power_on_hours); |
| 99 | // 记录带外获取到的值到快照,用于 BMA 断开后恢复 | 99 | // 记录带外获取到的值到快照,用于 BMA 断开后恢复 |
| 100 | if (m_nvme_oem) { | 100 | if (m_nvme_oem) { |
| 101 | m_nvme_oem->update_prop_without_bma("TLCSpareBlockPercentage", mc::variant(smart_log.avail_spare)); | 101 | m_nvme_oem->update_prop_without_bma("TLCSpareBlockPercentage", mc::variant(smart_log.avail_spare)); |
| 102 | - m_nvme_oem->update_prop_without_bma("PowerOnHours", mc::variant(static_cast<double>(power_on_hours))); | 102 | + m_nvme_oem->update_prop_without_bma("PowerOnHours", mc::variant(power_on_hours)); |
| 103 | } | 103 | } |
| 104 | return true; | 104 | return true; |
| 105 | } | 105 | } |
| @@ -48,6 +48,7 @@ public: | |||
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | private: | 50 | private: |
| 51 | + friend struct HuaweiTimerTestAccess; | ||
| 51 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; | 52 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; |
| 52 | mc::shared_ptr<mc::timer> m_mctp_timer; | 53 | mc::shared_ptr<mc::timer> m_mctp_timer; |
| 53 | std::function<void()> m_on_update_callback; | 54 | std::function<void()> m_on_update_callback; |
| @@ -12,30 +12,28 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/status.h" | 13 | #include "interface/nvme/status.h" |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | #include "mc/debounce/continue.h" | 16 | #include "mc/debounce/continue.h" |
| 16 | 17 | ||
| 17 | namespace dev { | 18 | namespace dev { |
| 18 | 19 | ||
| 19 | void NVMe_Status::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) | 20 | void NVMe_Status::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) |
| 20 | { | 21 | { |
| 22 | + huawei::replace_timer(m_vpd_timer); | ||
| 21 | m_vpd_obj = vpd; | 23 | m_vpd_obj = vpd; |
| 22 | m_start_time = std::chrono::steady_clock::now(); | 24 | m_start_time = std::chrono::steady_clock::now(); |
| 23 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | ||
| 24 | m_vpd_timer->timeout.connect([this]() { | 25 | m_vpd_timer->timeout.connect([this]() { |
| 25 | update_status_by_vpd(); | 26 | update_status_by_vpd(); |
| 26 | }); | 27 | }); |
| 27 | m_vpd_timer->set_single_shot(false); | 28 | m_vpd_timer->set_single_shot(false); |
| 28 | - m_vpd_timer->start(interval); | ||
| 29 | debounce_failure = std::make_unique<mc::debounce::Continue>(NVME_FAILURE_DEBOUNCE_COUNT); | 29 | debounce_failure = std::make_unique<mc::debounce::Continue>(NVME_FAILURE_DEBOUNCE_COUNT); |
| 30 | debounce_pre_failure = std::make_unique<mc::debounce::Continue>(NVME_PRE_FAILURE_DEBOUNCE_COUNT); | 30 | debounce_pre_failure = std::make_unique<mc::debounce::Continue>(NVME_PRE_FAILURE_DEBOUNCE_COUNT); |
| 31 | + m_vpd_timer->start(interval); | ||
| 31 | } | 32 | } |
| 32 | 33 | ||
| 33 | void NVMe_Status::stop_vpd_update_task() | 34 | void NVMe_Status::stop_vpd_update_task() |
| 34 | { | 35 | { |
| 35 | - if (m_vpd_timer) { | 36 | + huawei::stop_and_release_timer(m_vpd_timer); |
| 36 | - m_vpd_timer->stop(); | ||
| 37 | - m_vpd_timer.reset(); | ||
| 38 | - } | ||
| 39 | } | 37 | } |
| 40 | 38 | ||
| 41 | bool NVMe_Status::update_status_by_vpd() | 39 | bool NVMe_Status::update_status_by_vpd() |
| @@ -40,6 +40,7 @@ public: | |||
| 40 | bool update_status_by_vpd(); | 40 | bool update_status_by_vpd(); |
| 41 | 41 | ||
| 42 | private: | 42 | private: |
| 43 | + friend struct HuaweiTimerTestAccess; | ||
| 43 | vpd_protocol_ptr m_vpd_obj; | 44 | vpd_protocol_ptr m_vpd_obj; |
| 44 | mc::shared_ptr<mc::timer> m_vpd_timer; | 45 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 45 | std::chrono::steady_clock::time_point m_start_time; | 46 | std::chrono::steady_clock::time_point m_start_time; |
| @@ -12,14 +12,15 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/vendor_specific_smart.h" | 13 | #include "interface/nvme/vendor_specific_smart.h" |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | 16 | ||
| 16 | namespace dev { | 17 | namespace dev { |
| 17 | 18 | ||
| 18 | void NVMe_VendorSpecificSmart::start_mctp_update_task(nvme_mi_over_mctp_huawei_ptr nvme_mi_obj, | 19 | void NVMe_VendorSpecificSmart::start_mctp_update_task(nvme_mi_over_mctp_huawei_ptr nvme_mi_obj, |
| 19 | mc::milliseconds interval) | 20 | mc::milliseconds interval) |
| 20 | { | 21 | { |
| 22 | + huawei::replace_timer(m_mctp_timer); | ||
| 21 | m_nvme_mi_obj = nvme_mi_obj; | 23 | m_nvme_mi_obj = nvme_mi_obj; |
| 22 | - m_mctp_timer = mc::make_shared<mc::timer>(this); | ||
| 23 | m_mctp_timer->timeout.connect([this]() { | 24 | m_mctp_timer->timeout.connect([this]() { |
| 24 | update_vendor_specific_smart_by_mctp(); | 25 | update_vendor_specific_smart_by_mctp(); |
| 25 | }); | 26 | }); |
| @@ -29,10 +30,7 @@ void NVMe_VendorSpecificSmart::start_mctp_update_task(nvme_mi_over_mctp_huawei_p | |||
| 29 | 30 | ||
| 30 | void NVMe_VendorSpecificSmart::stop_mctp_update_task() | 31 | void NVMe_VendorSpecificSmart::stop_mctp_update_task() |
| 31 | { | 32 | { |
| 32 | - if (m_mctp_timer) { | 33 | + huawei::stop_and_release_timer(m_mctp_timer); |
| 33 | - m_mctp_timer->stop(); | ||
| 34 | - m_mctp_timer.reset(); | ||
| 35 | - } | ||
| 36 | } | 34 | } |
| 37 | 35 | ||
| 38 | std::vector<uint8_t> NVMe_VendorSpecificSmart::GetLogPage(uint8_t vendor_specific_lid) | 36 | std::vector<uint8_t> NVMe_VendorSpecificSmart::GetLogPage(uint8_t vendor_specific_lid) |
| @@ -37,6 +37,7 @@ public: | |||
| 37 | std::vector<uint8_t> GetLogPage(uint8_t vendor_specific_lid) override; | 37 | std::vector<uint8_t> GetLogPage(uint8_t vendor_specific_lid) override; |
| 38 | 38 | ||
| 39 | private: | 39 | private: |
| 40 | + friend struct HuaweiTimerTestAccess; | ||
| 40 | nvme_mi_over_mctp_huawei_ptr m_nvme_mi_obj; | 41 | nvme_mi_over_mctp_huawei_ptr m_nvme_mi_obj; |
| 41 | mc::shared_ptr<mc::timer> m_mctp_timer; | 42 | mc::shared_ptr<mc::timer> m_mctp_timer; |
| 42 | }; | 43 | }; |
| @@ -41,6 +41,11 @@ constexpr uint8_t RETRY_TIMES_DEFAULT = 3; | |||
| 41 | } // namespace | 41 | } // namespace |
| 42 | 42 | ||
| 43 | NVMe_Upgrade::~NVMe_Upgrade() | 43 | NVMe_Upgrade::~NVMe_Upgrade() |
| 44 | +{ | ||
| 45 | + wait_for_completion(); | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +void NVMe_Upgrade::wait_for_completion() | ||
| 44 | { | 49 | { |
| 45 | if (m_upgrade_thread && m_upgrade_thread->joinable()) { | 50 | if (m_upgrade_thread && m_upgrade_thread->joinable()) { |
| 46 | m_upgrade_thread->join(); | 51 | m_upgrade_thread->join(); |
| @@ -33,6 +33,9 @@ public: | |||
| 33 | NVMe_Upgrade() = default; | 33 | NVMe_Upgrade() = default; |
| 34 | ~NVMe_Upgrade(); | 34 | ~NVMe_Upgrade(); |
| 35 | 35 | ||
| 36 | + // Join before the owning device tears down resources used by the worker. | ||
| 37 | + void wait_for_completion(); | ||
| 38 | + | ||
| 36 | bool Upgrade(const std::string& firmware_type, const std::string& protocol, const std::string& file_path) override; | 39 | bool Upgrade(const std::string& firmware_type, const std::string& protocol, const std::string& file_path) override; |
| 37 | bool Active(const std::string& firmware_type) override; | 40 | bool Active(const std::string& firmware_type) override; |
| 38 | 41 | ||
| @@ -42,6 +45,7 @@ public: | |||
| 42 | } | 45 | } |
| 43 | 46 | ||
| 44 | private: | 47 | private: |
| 48 | + friend struct NVMeUpgradeTestAccess; | ||
| 45 | void update_progress(uint8_t percent); | 49 | void update_progress(uint8_t percent); |
| 46 | void update_result(bool success, uint32_t code, const std::string& detail); | 50 | void update_result(bool success, uint32_t code, const std::string& detail); |
| 47 | void do_download_async(const std::string& file_path); | 51 | void do_download_async(const std::string& file_path); |
| @@ -12,6 +12,7 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/cooling.h" | 13 | #include "interface/cooling.h" |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | 16 | ||
| 16 | namespace { | 17 | namespace { |
| 17 | 18 | ||
| @@ -41,13 +42,16 @@ Cooling::Cooling(mc::milliseconds no_data_debounce) : m_temp_no_data_debounce(no | |||
| 41 | 42 | ||
| 42 | void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) | 43 | void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) |
| 43 | { | 44 | { |
| 44 | - m_vpd_obj = vpd; | 45 | + stop_vpd_update_task(); |
| 45 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | 46 | + m_vpd_obj = vpd; |
| 47 | + // Queued handlers may retain the timer beyond this embedded interface. | ||
| 48 | + m_vpd_timer = mc::make_shared<mc::timer>(); | ||
| 49 | + auto* owner = get_owner(); | ||
| 50 | + m_vpd_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 46 | m_vpd_timer->timeout.connect([this]() { | 51 | m_vpd_timer->timeout.connect([this]() { |
| 47 | update_cooling_by_vpd(); | 52 | update_cooling_by_vpd(); |
| 48 | }); | 53 | }); |
| 49 | m_vpd_timer->set_single_shot(false); | 54 | m_vpd_timer->set_single_shot(false); |
| 50 | - m_vpd_timer->start(interval); | ||
| 51 | 55 | ||
| 52 | // 设备重新插入后再次 start,重置温度防抖状态使新会话从干净状态开始 | 56 | // 设备重新插入后再次 start,重置温度防抖状态使新会话从干净状态开始 |
| 53 | // (0x80 的 5 分钟计时不跨会话累计) | 57 | // (0x80 的 5 分钟计时不跨会话累计) |
| @@ -58,14 +62,12 @@ void Cooling::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds inter | |||
| 58 | m_temp_avg_debounce.clear_debounce_val(); | 62 | m_temp_avg_debounce.clear_debounce_val(); |
| 59 | m_temp_fail_debounce = mc::debounce::BinaryContinue(3, 1); | 63 | m_temp_fail_debounce = mc::debounce::BinaryContinue(3, 1); |
| 60 | m_has_valid_temp = false; | 64 | m_has_valid_temp = false; |
| 65 | + m_vpd_timer->start(interval); | ||
| 61 | } | 66 | } |
| 62 | 67 | ||
| 63 | void Cooling::stop_vpd_update_task() | 68 | void Cooling::stop_vpd_update_task() |
| 64 | { | 69 | { |
| 65 | - if (m_vpd_timer) { | 70 | + samsung::stop_and_release_timer(m_vpd_timer); |
| 66 | - m_vpd_timer->stop(); | ||
| 67 | - m_vpd_timer.reset(); | ||
| 68 | - } | ||
| 69 | } | 71 | } |
| 70 | 72 | ||
| 71 | bool Cooling::update_cooling_by_vpd() | 73 | bool Cooling::update_cooling_by_vpd() |
| @@ -38,6 +38,7 @@ public: | |||
| 38 | bool update_cooling_by_vpd(); | 38 | bool update_cooling_by_vpd(); |
| 39 | 39 | ||
| 40 | private: | 40 | private: |
| 41 | + friend struct SamsungVpdTimerTestAccess; | ||
| 41 | vpd_protocol_ptr m_vpd_obj; | 42 | vpd_protocol_ptr m_vpd_obj; |
| 42 | mc::shared_ptr<mc::timer> m_vpd_timer; | 43 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 43 | 44 | ||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "interface/nvme/basic_management.h" | 13 | #include "interface/nvme/basic_management.h" |
| 14 | #include <chrono> | 14 | #include <chrono> |
| 15 | #include <mc/log.h> | 15 | #include <mc/log.h> |
| 16 | +#include "interface/timer_lifetime.h" | ||
| 16 | #include "mc/debounce/binary_continue.h" | 17 | #include "mc/debounce/binary_continue.h" |
| 17 | 18 | ||
| 18 | namespace dev { | 19 | namespace dev { |
| @@ -20,17 +21,19 @@ namespace dev { | |||
| 20 | void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval, | 21 | void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval, |
| 21 | std::function<void()> on_update_callback) | 22 | std::function<void()> on_update_callback) |
| 22 | { | 23 | { |
| 24 | + stop_vpd_update_task(); | ||
| 23 | m_vpd_obj = vpd; | 25 | m_vpd_obj = vpd; |
| 24 | m_on_update_callback = on_update_callback; | 26 | m_on_update_callback = on_update_callback; |
| 25 | m_start_time = std::chrono::steady_clock::now(); | 27 | m_start_time = std::chrono::steady_clock::now(); |
| 26 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | 28 | + m_vpd_timer = mc::make_shared<mc::timer>(); |
| 29 | + auto* owner = get_owner(); | ||
| 30 | + m_vpd_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 27 | m_vpd_timer->timeout.connect([this]() { | 31 | m_vpd_timer->timeout.connect([this]() { |
| 28 | if (update_basic_management_by_vpd() && m_on_update_callback) { | 32 | if (update_basic_management_by_vpd() && m_on_update_callback) { |
| 29 | m_on_update_callback(); | 33 | m_on_update_callback(); |
| 30 | } | 34 | } |
| 31 | }); | 35 | }); |
| 32 | m_vpd_timer->set_single_shot(false); | 36 | m_vpd_timer->set_single_shot(false); |
| 33 | - m_vpd_timer->start(interval); | ||
| 34 | debounce_port0_link_active = | 37 | debounce_port0_link_active = |
| 35 | std::make_unique<mc::debounce::BinaryContinue>(NVME_PL_DEBOUNCE_HIGH, NVME_PL_DEBOUNCE_LOW); | 38 | std::make_unique<mc::debounce::BinaryContinue>(NVME_PL_DEBOUNCE_HIGH, NVME_PL_DEBOUNCE_LOW); |
| 36 | debounce_drive_functional = | 39 | debounce_drive_functional = |
| @@ -39,14 +42,12 @@ void NVMe_BasicManagement::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milli | |||
| 39 | std::make_unique<mc::debounce::BinaryContinue>(NVME_DN_DEBOUNCE_HIGH, NVME_DN_DEBOUNCE_LOW); | 42 | std::make_unique<mc::debounce::BinaryContinue>(NVME_DN_DEBOUNCE_HIGH, NVME_DN_DEBOUNCE_LOW); |
| 40 | debounce_link_fault = | 43 | debounce_link_fault = |
| 41 | std::make_unique<mc::debounce::BinaryContinue>(NVME_LINK_FAULT_DEBOUNCE_HIGH, NVME_LINK_FAULT_DEBOUNCE_LOW); | 44 | std::make_unique<mc::debounce::BinaryContinue>(NVME_LINK_FAULT_DEBOUNCE_HIGH, NVME_LINK_FAULT_DEBOUNCE_LOW); |
| 45 | + m_vpd_timer->start(interval); | ||
| 42 | } | 46 | } |
| 43 | 47 | ||
| 44 | void NVMe_BasicManagement::stop_vpd_update_task() | 48 | void NVMe_BasicManagement::stop_vpd_update_task() |
| 45 | { | 49 | { |
| 46 | - if (m_vpd_timer) { | 50 | + samsung::stop_and_release_timer(m_vpd_timer); |
| 47 | - m_vpd_timer->stop(); | ||
| 48 | - m_vpd_timer.reset(); | ||
| 49 | - } | ||
| 50 | } | 51 | } |
| 51 | 52 | ||
| 52 | bool NVMe_BasicManagement::update_basic_management_by_vpd() | 53 | bool NVMe_BasicManagement::update_basic_management_by_vpd() |
| @@ -48,6 +48,7 @@ public: | |||
| 48 | bool update_basic_management_by_vpd(); | 48 | bool update_basic_management_by_vpd(); |
| 49 | 49 | ||
| 50 | private: | 50 | private: |
| 51 | + friend struct SamsungVpdTimerTestAccess; | ||
| 51 | vpd_protocol_ptr m_vpd_obj; | 52 | vpd_protocol_ptr m_vpd_obj; |
| 52 | mc::shared_ptr<mc::timer> m_vpd_timer; | 53 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 53 | std::function<void()> m_on_update_callback; | 54 | std::function<void()> m_on_update_callback; |
| @@ -20,6 +20,7 @@ | |||
| 20 | #include <mc/log.h> | 20 | #include <mc/log.h> |
| 21 | #include <mutex> | 21 | #include <mutex> |
| 22 | #include <sstream> | 22 | #include <sstream> |
| 23 | +#include "interface/timer_lifetime.h" | ||
| 23 | #include "nvme/nvme_mi_over_mctp.h" | 24 | #include "nvme/nvme_mi_over_mctp.h" |
| 24 | 25 | ||
| 25 | namespace dev { | 26 | namespace dev { |
| @@ -770,7 +771,11 @@ void NVMe_LogCollection::start_log_collection_task() | |||
| 770 | return; | 771 | return; |
| 771 | } | 772 | } |
| 772 | 773 | ||
| 773 | - m_log_timer = mc::make_shared<mc::timer>(this); | 774 | + // The interface is embedded in the device. A delayed cancellation must |
| 775 | + // not retain a raw parent pointer after the device has been destroyed. | ||
| 776 | + m_log_timer = mc::make_shared<mc::timer>(); | ||
| 777 | + auto* owner = get_owner(); | ||
| 778 | + m_log_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 774 | m_log_timer->timeout.connect([this]() { | 779 | m_log_timer->timeout.connect([this]() { |
| 775 | collect_all_logs(); | 780 | collect_all_logs(); |
| 776 | }); | 781 | }); |
| @@ -785,10 +790,7 @@ void NVMe_LogCollection::start_log_collection_task() | |||
| 785 | 790 | ||
| 786 | void NVMe_LogCollection::stop_log_collection_task() | 791 | void NVMe_LogCollection::stop_log_collection_task() |
| 787 | { | 792 | { |
| 788 | - if (m_log_timer) { | 793 | + samsung::stop_and_release_timer(m_log_timer); |
| 789 | - m_log_timer->stop(); | ||
| 790 | - m_log_timer.reset(); | ||
| 791 | - } | ||
| 792 | } | 794 | } |
| 793 | 795 | ||
| 794 | } // namespace dev | 796 | } // namespace dev |
| @@ -28,8 +28,11 @@ constexpr auto LOG_COLLECTION_INTERVAL = std::chrono::hours(24); | |||
| 28 | 28 | ||
| 29 | class MC_API NVMe_LogCollection : public mc::engine::interface<NVMe_LogCollection, gen::NVMe_LogCollection> { | 29 | class MC_API NVMe_LogCollection : public mc::engine::interface<NVMe_LogCollection, gen::NVMe_LogCollection> { |
| 30 | public: | 30 | public: |
| 31 | - NVMe_LogCollection() = default; | 31 | + NVMe_LogCollection() = default; |
| 32 | - ~NVMe_LogCollection() = default; | 32 | + ~NVMe_LogCollection() |
| 33 | + { | ||
| 34 | + stop_log_collection_task(); | ||
| 35 | + } | ||
| 33 | 36 | ||
| 34 | bool DumpErrorInfo(const std::string& path) override; | 37 | bool DumpErrorInfo(const std::string& path) override; |
| 35 | bool DumpSmartInfo(const std::string& path) override; | 38 | bool DumpSmartInfo(const std::string& path) override; |
| @@ -52,6 +55,7 @@ public: | |||
| 52 | void collect_all_logs(); | 55 | void collect_all_logs(); |
| 53 | 56 | ||
| 54 | private: | 57 | private: |
| 58 | + friend struct SamsungLogTimerTestAccess; | ||
| 55 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; | 59 | nvme_mi_over_mctp_ptr m_nvme_mi_obj; |
| 56 | uint32_t m_ctrl_id = 0; | 60 | uint32_t m_ctrl_id = 0; |
| 57 | mc::shared_ptr<mc::timer> m_log_timer; | 61 | mc::shared_ptr<mc::timer> m_log_timer; |
| @@ -12,11 +12,13 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/smart.h" | 13 | #include "interface/nvme/smart.h" |
| 14 | #include <algorithm> | 14 | #include <algorithm> |
| 15 | +#include <cmath> | ||
| 15 | #include <cstddef> | 16 | #include <cstddef> |
| 16 | #include <cstring> | 17 | #include <cstring> |
| 17 | #include <mc/log.h> | 18 | #include <mc/log.h> |
| 18 | #include <nvme/nvme_mi_over_mctp.h> | 19 | #include <nvme/nvme_mi_over_mctp.h> |
| 19 | #include "interface/nvme/oem.h" | 20 | #include "interface/nvme/oem.h" |
| 21 | +#include "interface/timer_lifetime.h" | ||
| 20 | #include "securec.h" | 22 | #include "securec.h" |
| 21 | 23 | ||
| 22 | namespace dev { | 24 | namespace dev { |
| @@ -24,9 +26,14 @@ namespace dev { | |||
| 24 | void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::milliseconds interval, | 26 | void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::milliseconds interval, |
| 25 | std::function<void()> on_update_callback) | 27 | std::function<void()> on_update_callback) |
| 26 | { | 28 | { |
| 29 | + stop_mctp_update_task(); | ||
| 27 | m_nvme_mi_obj = nvme_mi_obj; | 30 | m_nvme_mi_obj = nvme_mi_obj; |
| 28 | m_on_update_callback = on_update_callback; | 31 | m_on_update_callback = on_update_callback; |
| 29 | - m_mctp_timer = mc::make_shared<mc::timer>(this); | 32 | + // The interface is embedded in the device. A queued timer handler can |
| 33 | + // outlive it, so only the member owns the timer; there is no parent link. | ||
| 34 | + m_mctp_timer = mc::make_shared<mc::timer>(); | ||
| 35 | + auto* owner = get_owner(); | ||
| 36 | + m_mctp_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 30 | m_mctp_timer->timeout.connect([this]() { | 37 | m_mctp_timer->timeout.connect([this]() { |
| 31 | if (update_smart_by_mctp() && m_on_update_callback) { | 38 | if (update_smart_by_mctp() && m_on_update_callback) { |
| 32 | m_on_update_callback(); | 39 | m_on_update_callback(); |
| @@ -38,10 +45,7 @@ void NVMe_Smart::start_mctp_update_task(nvme_mi_over_mctp_ptr nvme_mi_obj, mc::m | |||
| 38 | 45 | ||
| 39 | void NVMe_Smart::stop_mctp_update_task() | 46 | void NVMe_Smart::stop_mctp_update_task() |
| 40 | { | 47 | { |
| 41 | - if (m_mctp_timer) { | 48 | + samsung::stop_and_release_timer(m_mctp_timer); |
| 42 | - m_mctp_timer->stop(); | ||
| 43 | - m_mctp_timer.reset(); | ||
| 44 | - } | ||
| 45 | } | 49 | } |
| 46 | 50 | ||
| 47 | bool NVMe_Smart::update_smart_by_mctp() | 51 | bool NVMe_Smart::update_smart_by_mctp() |
| @@ -73,23 +77,26 @@ bool NVMe_Smart::update_smart_by_mctp() | |||
| 73 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); | 77 | size_t copy_len = std::min(smart_data.size() - header_size, sizeof(nvme_smart_log_response_t)); |
| 74 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); | 78 | (void)memcpy_s(&smart_log, sizeof(nvme_smart_log_response_t), smart_data.data() + header_size, copy_len); |
| 75 | 79 | ||
| 76 | - uint64_t power_on_hours = 0; | 80 | + uint64_t power_on_hours_low = 0; |
| 81 | + uint64_t power_on_hours_high = 0; | ||
| 77 | for (int i = 0; i < 8; ++i) { | 82 | for (int i = 0; i < 8; ++i) { |
| 78 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8)); | 83 | + power_on_hours_low |= static_cast<uint64_t>(smart_log.power_on_hours_l[i]) << (i * 8); |
| 79 | - } | 84 | + power_on_hours_high |= static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << (i * 8); |
| 80 | - for (int i = 0; i < 8; ++i) { | ||
| 81 | - power_on_hours |= (static_cast<uint64_t>(smart_log.power_on_hours_h[i]) << ((i + 8) * 8)); | ||
| 82 | } | 85 | } |
| 86 | + // The NVMe counter is 128 bits; the exposed property is a double. | ||
| 87 | + // Scale the upper half in floating point rather than shifting a uint64_t by 64 or more. | ||
| 88 | + const double power_on_hours = | ||
| 89 | + std::ldexp(static_cast<double>(power_on_hours_high), 64) + static_cast<double>(power_on_hours_low); | ||
| 83 | 90 | ||
| 84 | CriticalWarning.set_value(smart_log.critical_warning); | 91 | CriticalWarning.set_value(smart_log.critical_warning); |
| 85 | AvailableSpare.set_value(smart_log.avail_spare); | 92 | AvailableSpare.set_value(smart_log.avail_spare); |
| 86 | UsedPercentage.set_value(smart_log.percent_used); | 93 | UsedPercentage.set_value(smart_log.percent_used); |
| 87 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); | 94 | TLCSpareBlockPercentage.set_value(smart_log.avail_spare); |
| 88 | - PowerOnHours.set_value(static_cast<double>(power_on_hours)); | 95 | + PowerOnHours.set_value(power_on_hours); |
| 89 | // 记录带外获取到的值到快照,用于 BMA 断开后恢复 | 96 | // 记录带外获取到的值到快照,用于 BMA 断开后恢复 |
| 90 | if (m_nvme_oem) { | 97 | if (m_nvme_oem) { |
| 91 | m_nvme_oem->update_prop_without_bma("TLCSpareBlockPercentage", mc::variant(smart_log.avail_spare)); | 98 | m_nvme_oem->update_prop_without_bma("TLCSpareBlockPercentage", mc::variant(smart_log.avail_spare)); |
| 92 | - m_nvme_oem->update_prop_without_bma("PowerOnHours", mc::variant(static_cast<double>(power_on_hours))); | 99 | + m_nvme_oem->update_prop_without_bma("PowerOnHours", mc::variant(power_on_hours)); |
| 93 | } | 100 | } |
| 94 | 101 | ||
| 95 | return true; | 102 | return true; |
| @@ -12,30 +12,31 @@ | |||
| 12 | 12 | ||
| 13 | #include "interface/nvme/status.h" | 13 | #include "interface/nvme/status.h" |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | #include "mc/debounce/continue.h" | 16 | #include "mc/debounce/continue.h" |
| 16 | 17 | ||
| 17 | namespace dev { | 18 | namespace dev { |
| 18 | 19 | ||
| 19 | void NVMe_Status::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) | 20 | void NVMe_Status::start_vpd_update_task(vpd_protocol_ptr vpd, mc::milliseconds interval) |
| 20 | { | 21 | { |
| 22 | + stop_vpd_update_task(); | ||
| 21 | m_vpd_obj = vpd; | 23 | m_vpd_obj = vpd; |
| 22 | m_start_time = std::chrono::steady_clock::now(); | 24 | m_start_time = std::chrono::steady_clock::now(); |
| 23 | - m_vpd_timer = mc::make_shared<mc::timer>(this); | 25 | + m_vpd_timer = mc::make_shared<mc::timer>(); |
| 26 | + auto* owner = get_owner(); | ||
| 27 | + m_vpd_timer->bind_executor(owner != nullptr ? owner->executor() : executor()); | ||
| 24 | m_vpd_timer->timeout.connect([this]() { | 28 | m_vpd_timer->timeout.connect([this]() { |
| 25 | update_status_by_vpd(); | 29 | update_status_by_vpd(); |
| 26 | }); | 30 | }); |
| 27 | m_vpd_timer->set_single_shot(false); | 31 | m_vpd_timer->set_single_shot(false); |
| 28 | - m_vpd_timer->start(interval); | ||
| 29 | debounce_failure = std::make_unique<mc::debounce::Continue>(NVME_FAILURE_DEBOUNCE_COUNT); | 32 | debounce_failure = std::make_unique<mc::debounce::Continue>(NVME_FAILURE_DEBOUNCE_COUNT); |
| 30 | debounce_pre_failure = std::make_unique<mc::debounce::Continue>(NVME_PRE_FAILURE_DEBOUNCE_COUNT); | 33 | debounce_pre_failure = std::make_unique<mc::debounce::Continue>(NVME_PRE_FAILURE_DEBOUNCE_COUNT); |
| 34 | + m_vpd_timer->start(interval); | ||
| 31 | } | 35 | } |
| 32 | 36 | ||
| 33 | void NVMe_Status::stop_vpd_update_task() | 37 | void NVMe_Status::stop_vpd_update_task() |
| 34 | { | 38 | { |
| 35 | - if (m_vpd_timer) { | 39 | + samsung::stop_and_release_timer(m_vpd_timer); |
| 36 | - m_vpd_timer->stop(); | ||
| 37 | - m_vpd_timer.reset(); | ||
| 38 | - } | ||
| 39 | } | 40 | } |
| 40 | 41 | ||
| 41 | bool NVMe_Status::update_status_by_vpd() | 42 | bool NVMe_Status::update_status_by_vpd() |
| @@ -40,6 +40,7 @@ public: | |||
| 40 | bool update_status_by_vpd(); | 40 | bool update_status_by_vpd(); |
| 41 | 41 | ||
| 42 | private: | 42 | private: |
| 43 | + friend struct SamsungVpdTimerTestAccess; | ||
| 43 | vpd_protocol_ptr m_vpd_obj; | 44 | vpd_protocol_ptr m_vpd_obj; |
| 44 | mc::shared_ptr<mc::timer> m_vpd_timer; | 45 | mc::shared_ptr<mc::timer> m_vpd_timer; |
| 45 | std::chrono::steady_clock::time_point m_start_time; | 46 | std::chrono::steady_clock::time_point m_start_time; |
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "samsung_nvme_pcie4.h" | 12 | #include "samsung_nvme_pcie4.h" |
| 13 | #include <device_tree/interface/PCIeDevice.h> | 13 | #include <device_tree/interface/PCIeDevice.h> |
| 14 | #include <mc/log.h> | 14 | #include <mc/log.h> |
| 15 | +#include "interface/timer_lifetime.h" | ||
| 15 | 16 | ||
| 16 | namespace dev { | 17 | namespace dev { |
| 17 | 18 | ||
| @@ -32,6 +33,13 @@ samsung_nvme_pcie4::samsung_nvme_pcie4() : m_mctp_object(nullptr), m_ctrl_id(0) | |||
| 32 | 33 | ||
| 33 | samsung_nvme_pcie4::~samsung_nvme_pcie4() | 34 | samsung_nvme_pcie4::~samsung_nvme_pcie4() |
| 34 | { | 35 | { |
| 36 | + // VPD callbacks also access sibling interfaces; join them before any | ||
| 37 | + // members (including the cached protocol and static timer) are destroyed. | ||
| 38 | + stop_vpd_update_tasks(); | ||
| 39 | + // MCTP callbacks access sibling interfaces, especially m_nvme_oem, which | ||
| 40 | + // is destroyed before the telemetry interfaces by reverse member order. | ||
| 41 | + m_nvme_smart.stop_mctp_update_task(); | ||
| 42 | + m_nvme_log_collection.stop_log_collection_task(); | ||
| 35 | if (m_nvme_mi_over_mctp_obj) { | 43 | if (m_nvme_mi_over_mctp_obj) { |
| 36 | m_nvme_mi_over_mctp_obj.reset(); | 44 | m_nvme_mi_over_mctp_obj.reset(); |
| 37 | } | 45 | } |
| @@ -390,7 +398,9 @@ void samsung_nvme_pcie4::start_vpd_update_tasks() | |||
| 390 | m_nvme_product_info.update_product_info_by_vpd(); | 398 | m_nvme_product_info.update_product_info_by_vpd(); |
| 391 | m_nvme_multi_record.update_multi_record_by_vpd(); | 399 | m_nvme_multi_record.update_multi_record_by_vpd(); |
| 392 | }; | 400 | }; |
| 393 | - m_static_update_timer = mc::make_shared<mc::timer>(this); | 401 | + samsung::stop_and_release_timer(m_static_update_timer); |
| 402 | + m_static_update_timer = mc::make_shared<mc::timer>(); | ||
| 403 | + m_static_update_timer->bind_executor(executor()); | ||
| 394 | m_static_update_timer->timeout.connect(refresh_static_vpd); | 404 | m_static_update_timer->timeout.connect(refresh_static_vpd); |
| 395 | m_static_update_timer->set_single_shot(false); | 405 | m_static_update_timer->set_single_shot(false); |
| 396 | m_static_update_timer->start(m_vpd_static_interval); | 406 | m_static_update_timer->start(m_vpd_static_interval); |
| @@ -436,10 +446,7 @@ void samsung_nvme_pcie4::stop_vpd_update_tasks() | |||
| 436 | m_cooling.stop_vpd_update_task(); | 446 | m_cooling.stop_vpd_update_task(); |
| 437 | m_nvme_status.stop_vpd_update_task(); | 447 | m_nvme_status.stop_vpd_update_task(); |
| 438 | m_nvme_basic_management.stop_vpd_update_task(); | 448 | m_nvme_basic_management.stop_vpd_update_task(); |
| 439 | - if (m_static_update_timer) { | 449 | + samsung::stop_and_release_timer(m_static_update_timer); |
| 440 | - m_static_update_timer->stop(); | ||
| 441 | - m_static_update_timer.reset(); | ||
| 442 | - } | ||
| 443 | if (m_vpd_obj) { | 450 | if (m_vpd_obj) { |
| 444 | m_vpd_obj.reset(); | 451 | m_vpd_obj.reset(); |
| 445 | } | 452 | } |
| @@ -86,6 +86,7 @@ public: | |||
| 86 | NVMe_Oem m_nvme_oem; | 86 | NVMe_Oem m_nvme_oem; |
| 87 | 87 | ||
| 88 | private: | 88 | private: |
| 89 | + friend struct SamsungVpdTimerTestAccess; | ||
| 89 | // VPD 协议类型(与1.0保持一致) | 90 | // VPD 协议类型(与1.0保持一致) |
| 90 | // NVME_MI = 0, SFF = 1, SAMSUNG_SFF = 2 | 91 | // NVME_MI = 0, SFF = 1, SAMSUNG_SFF = 2 |
| 91 | enum class VpdProtocolType { | 92 | enum class VpdProtocolType { |
| @@ -253,6 +253,10 @@ bool ncsi_over_mctp_grt::get_mac_addr(uint8_t package_id, uint8_t channel_id, ui | |||
| 253 | return false; | 253 | return false; |
| 254 | } | 254 | } |
| 255 | 255 | ||
| 256 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 257 | + ilog("requested MAC length exceeds response field size"); | ||
| 258 | + return false; | ||
| 259 | + } | ||
| 256 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); | 260 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 257 | return true; | 261 | return true; |
| 258 | } | 262 | } |
| @@ -279,6 +283,10 @@ bool ncsi_over_mctp_grt::get_default_mac_addr(uint8_t package_id, uint8_t channe | |||
| 279 | return false; | 283 | return false; |
| 280 | } | 284 | } |
| 281 | 285 | ||
| 286 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 287 | + ilog("requested MAC length exceeds response field size"); | ||
| 288 | + return false; | ||
| 289 | + } | ||
| 282 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); | 290 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 283 | return true; | 291 | return true; |
| 284 | } | 292 | } |
| @@ -123,6 +123,10 @@ bool ncsi_over_mctp_mellanox::get_mac_addr(uint8_t package_id, uint8_t channel_i | |||
| 123 | return false; | 123 | return false; |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 127 | + ilog("requested MAC length exceeds response field size"); | ||
| 128 | + return false; | ||
| 129 | + } | ||
| 126 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); | 130 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 127 | return true; | 131 | return true; |
| 128 | } | 132 | } |
| @@ -156,6 +160,10 @@ bool ncsi_over_mctp_mellanox::get_default_mac_addr(uint8_t package_id, uint8_t c | |||
| 156 | return false; | 160 | return false; |
| 157 | } | 161 | } |
| 158 | 162 | ||
| 163 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 164 | + ilog("requested MAC length exceeds response field size"); | ||
| 165 | + return false; | ||
| 166 | + } | ||
| 159 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); | 167 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 160 | return true; | 168 | return true; |
| 161 | } | 169 | } |
| @@ -80,6 +80,10 @@ bool ncsi_over_mctp_mucse::get_mac_addr(uint8_t package_id, uint8_t channel_id, | |||
| 80 | if (!check_response_data(reinterpret_cast<oem_mucse_public_rsp_t*>(rsp), OEM_MUCSE_GET_MAC_ADDR)) { | 80 | if (!check_response_data(reinterpret_cast<oem_mucse_public_rsp_t*>(rsp), OEM_MUCSE_GET_MAC_ADDR)) { |
| 81 | return false; | 81 | return false; |
| 82 | } | 82 | } |
| 83 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 84 | + ilog("requested MAC length exceeds response field size"); | ||
| 85 | + return false; | ||
| 86 | + } | ||
| 83 | errno_t err = memcpy_s(mac_addr, len, rsp->mac_addr, len); | 87 | errno_t err = memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 84 | if (err != EOK) { | 88 | if (err != EOK) { |
| 85 | ilog("memcpy_s failed, err: ${err}", ("err", err)); | 89 | ilog("memcpy_s failed, err: ${err}", ("err", err)); |
| @@ -226,6 +226,10 @@ bool ncsi_over_mctp_standard::disable_hardware_arbittration(uint8_t package_id, | |||
| 226 | ilog("unable to select package id, package_id: ${package_id}", ("package_id", package_id)); | 226 | ilog("unable to select package id, package_id: ${package_id}", ("package_id", package_id)); |
| 227 | return false; | 227 | return false; |
| 228 | } | 228 | } |
| 229 | + if (rsp_str.size() < SELECT_PACKAGE_RSP_LEN) { | ||
| 230 | + ilog("select package response size ${size} is invalid", ("size", rsp_str.size())); | ||
| 231 | + return false; | ||
| 232 | + } | ||
| 229 | 233 | ||
| 230 | rsp = reinterpret_cast<select_package_rsp*>(rsp_str.data()); | 234 | rsp = reinterpret_cast<select_package_rsp*>(rsp_str.data()); |
| 231 | uint16_t response_code = SHORT_BY_BIG_ENDIAN(rsp->rsp_code); | 235 | uint16_t response_code = SHORT_BY_BIG_ENDIAN(rsp->rsp_code); |
| @@ -11,6 +11,7 @@ | |||
| 11 | */ | 11 | */ |
| 12 | 12 | ||
| 13 | #include "ncsi_over_mctp_wangxun.h" | 13 | #include "ncsi_over_mctp_wangxun.h" |
| 14 | +#include <cstddef> | ||
| 14 | #include <iomanip> | 15 | #include <iomanip> |
| 15 | #include <ncsi/ncsi_protocol.h> | 16 | #include <ncsi/ncsi_protocol.h> |
| 16 | #include <ncsi/ncsi_wangxun.h> | 17 | #include <ncsi/ncsi_wangxun.h> |
| @@ -116,6 +117,10 @@ bool ncsi_over_mctp_wangxun::get_mac_address(uint8_t package_id, uint8_t channel | |||
| 116 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_MAC_ADDRESS)) { | 117 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_MAC_ADDRESS)) { |
| 117 | return false; | 118 | return false; |
| 118 | } | 119 | } |
| 120 | + if (len > sizeof(rsp->mac_addr)) { | ||
| 121 | + ilog("Error: requested MAC length exceeds response field size"); | ||
| 122 | + return false; | ||
| 123 | + } | ||
| 119 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); | 124 | (void)memcpy_s(mac_addr, len, rsp->mac_addr, len); |
| 120 | 125 | ||
| 121 | return true; | 126 | return true; |
| @@ -289,8 +294,8 @@ bool ncsi_over_mctp_wangxun::get_sff_info(uint8_t package_id, uint8_t channel_id | |||
| 289 | ilog("Error: failed to send NCSI get_sff_info command"); | 294 | ilog("Error: failed to send NCSI get_sff_info command"); |
| 290 | return false; | 295 | return false; |
| 291 | } | 296 | } |
| 292 | - if (rsp_str.empty()) { | 297 | + if (rsp_str.size() < sizeof(oem_wx_get_sff_info_rsp_t)) { |
| 293 | - ilog("Error: cannot get sff info"); | 298 | + ilog("Error: invalid sff info response size ${size}", ("size", rsp_str.size())); |
| 294 | return false; | 299 | return false; |
| 295 | } | 300 | } |
| 296 | 301 | ||
| @@ -315,8 +320,9 @@ bool ncsi_over_mctp_wangxun::get_vpd_sn(uint8_t package_id, uint8_t channel_id, | |||
| 315 | ilog("Error: failed to send NCSI get_vpd_sn command"); | 320 | ilog("Error: failed to send NCSI get_vpd_sn command"); |
| 316 | return false; | 321 | return false; |
| 317 | } | 322 | } |
| 318 | - if (rsp_str.empty()) { | 323 | + constexpr size_t data_offset = offsetof(oem_wx_get_vpd_sn_rsp_t, sn); |
| 319 | - ilog("Error: cannot get vpd sn"); | 324 | + if (rsp_str.size() < data_offset) { |
| 325 | + ilog("Error: invalid vpd sn response size ${size}", ("size", rsp_str.size())); | ||
| 320 | return false; | 326 | return false; |
| 321 | } | 327 | } |
| 322 | 328 | ||
| @@ -324,6 +330,10 @@ bool ncsi_over_mctp_wangxun::get_vpd_sn(uint8_t package_id, uint8_t channel_id, | |||
| 324 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_VPD_SN)) { | 330 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_VPD_SN)) { |
| 325 | return false; | 331 | return false; |
| 326 | } | 332 | } |
| 333 | + if (rsp->sn_len > sizeof(rsp->sn) || rsp->sn_len > rsp_str.size() - data_offset) { | ||
| 334 | + ilog("Error: invalid vpd sn field length ${size}", ("size", rsp->sn_len)); | ||
| 335 | + return false; | ||
| 336 | + } | ||
| 327 | 337 | ||
| 328 | std::stringstream ss; | 338 | std::stringstream ss; |
| 329 | for (size_t i = 0; i < rsp->sn_len; i++) { | 339 | for (size_t i = 0; i < rsp->sn_len; i++) { |
| @@ -347,8 +357,9 @@ bool ncsi_over_mctp_wangxun::get_vpd_pn(uint8_t package_id, uint8_t channel_id, | |||
| 347 | ilog("Error: failed to send NCSI get_vpd_pn command"); | 357 | ilog("Error: failed to send NCSI get_vpd_pn command"); |
| 348 | return false; | 358 | return false; |
| 349 | } | 359 | } |
| 350 | - if (rsp_str.empty()) { | 360 | + constexpr size_t data_offset = offsetof(oem_wx_get_vpd_pn_rsp_t, pn); |
| 351 | - ilog("Error: cannot get vpd pn"); | 361 | + if (rsp_str.size() < data_offset) { |
| 362 | + ilog("Error: invalid vpd pn response size ${size}", ("size", rsp_str.size())); | ||
| 352 | return false; | 363 | return false; |
| 353 | } | 364 | } |
| 354 | 365 | ||
| @@ -356,6 +367,10 @@ bool ncsi_over_mctp_wangxun::get_vpd_pn(uint8_t package_id, uint8_t channel_id, | |||
| 356 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_VPD_PN)) { | 367 | if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t*>(rsp), WX_CMD_GET_VPD_PN)) { |
| 357 | return false; | 368 | return false; |
| 358 | } | 369 | } |
| 370 | + if (rsp->pn_len > sizeof(rsp->pn) || rsp->pn_len > rsp_str.size() - data_offset) { | ||
| 371 | + ilog("Error: invalid vpd pn field length ${size}", ("size", rsp->pn_len)); | ||
| 372 | + return false; | ||
| 373 | + } | ||
| 359 | 374 | ||
| 360 | std::stringstream ss; | 375 | std::stringstream ss; |
| 361 | for (size_t i = 0; i < rsp->pn_len; i++) { | 376 | for (size_t i = 0; i < rsp->pn_len; i++) { |
| @@ -257,10 +257,18 @@ LIBMCPP_SRCDIR="$PROJECT_DIR/subprojects/libmcpp" | |||
| 257 | PCDIR="$PROJECT_DIR/.pkg-config" | 257 | PCDIR="$PROJECT_DIR/.pkg-config" |
| 258 | BUILD_DIR="$PROJECT_DIR/builddir" | 258 | BUILD_DIR="$PROJECT_DIR/builddir" |
| 259 | 259 | ||
| 260 | -# bingo/conan 把 Lua 放进 temp/ 时不必再强制 apt 装 liblua5.4-dev。 | 260 | +lua_headers_complete() { |
| 261 | + local header | ||
| 262 | + for header in lua.h lauxlib.h lualib.h luaconf.h; do | ||
| 263 | + [ -f "$1/$header" ] || return 1 | ||
| 264 | + done | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +# bingo/conan 把完整的 Lua 放进 temp/ 时不必再强制 apt 装 liblua5.4-dev。 | ||
| 261 | conan_temp_lua_ready() { | 268 | conan_temp_lua_ready() { |
| 262 | local t="${PROJECT_DIR}/temp" | 269 | local t="${PROJECT_DIR}/temp" |
| 263 | - { [ -f "$t/include/luajit/lua.h" ] || [ -f "$t/include/lua.h" ]; } && [ -e "$t/usr/lib64/liblua.so" ] | 270 | + { lua_headers_complete "$t/include/luajit" || lua_headers_complete "$t/include"; } && |
| 271 | + [ -e "$t/usr/lib64/liblua.so" ] | ||
| 264 | } | 272 | } |
| 265 | CACHE_LAYOUT_VERSION="5" | 273 | CACHE_LAYOUT_VERSION="5" |
| 266 | 274 | ||
| @@ -1011,18 +1019,24 @@ ensure_host_packages() { | |||
| 1011 | } | 1019 | } |
| 1012 | 1020 | ||
| 1013 | # 把一份 Lua 头/库接到 libmcpp 要求的 lua_root(必须在源码树外),并写 native-file。 | 1021 | # 把一份 Lua 头/库接到 libmcpp 要求的 lua_root(必须在源码树外),并写 native-file。 |
| 1014 | -# $1 头文件目录(内有 lua.h) $2 库目录 $3 链接名(lua / lua5.4) $4 日志说明 | 1022 | +# $1 完整头文件目录 $2 库目录 $3 链接名(lua / lua5.4) $4 日志说明 |
| 1015 | activate_lua_toolchain() { | 1023 | activate_lua_toolchain() { |
| 1016 | local hdr_dir="$1" | 1024 | local hdr_dir="$1" |
| 1017 | local lib_dir="$2" | 1025 | local lib_dir="$2" |
| 1018 | local link_name="$3" | 1026 | local link_name="$3" |
| 1019 | local label="$4" | 1027 | local label="$4" |
| 1020 | local _h _so | 1028 | local _h _so |
| 1029 | + local _lua_headers=(lua.h lauxlib.h lualib.h luaconf.h lua.hpp luajit.h compat-5.3.h lprefix_53.h lbuffer.h) | ||
| 1030 | + | ||
| 1031 | + if ! lua_headers_complete "$hdr_dir" || [ ! -e "$lib_dir/lib${link_name}.so" ]; then | ||
| 1032 | + print_error "Lua 工具链不完整: hdr=${hdr_dir} lib=${lib_dir}/lib${link_name}.so" | ||
| 1033 | + return 1 | ||
| 1034 | + fi | ||
| 1021 | 1035 | ||
| 1022 | local _lua_inc_dir="$PROJECT_DIR/.lua54_inc" | 1036 | local _lua_inc_dir="$PROJECT_DIR/.lua54_inc" |
| 1023 | mkdir -p "$_lua_inc_dir" | 1037 | mkdir -p "$_lua_inc_dir" |
| 1024 | # 新来源没有的头要删掉旧软链,避免可选的 lua.hpp 等仍指到上一份 Lua。 | 1038 | # 新来源没有的头要删掉旧软链,避免可选的 lua.hpp 等仍指到上一份 Lua。 |
| 1025 | - for _h in lua.h lauxlib.h lualib.h luaconf.h lua.hpp; do | 1039 | + for _h in "${_lua_headers[@]}"; do |
| 1026 | if [ -f "$hdr_dir/$_h" ]; then | 1040 | if [ -f "$hdr_dir/$_h" ]; then |
| 1027 | ln -sfn "$hdr_dir/$_h" "$_lua_inc_dir/$_h" | 1041 | ln -sfn "$hdr_dir/$_h" "$_lua_inc_dir/$_h" |
| 1028 | elif [ -L "$_lua_inc_dir/$_h" ]; then | 1042 | elif [ -L "$_lua_inc_dir/$_h" ]; then |
| @@ -1035,7 +1049,7 @@ activate_lua_toolchain() { | |||
| 1035 | _workspace_key="$(printf '%s\n' "$PROJECT_DIR" | sha256sum | cut -d' ' -f1)" | 1049 | _workspace_key="$(printf '%s\n' "$PROJECT_DIR" | sha256sum | cut -d' ' -f1)" |
| 1036 | _lua_root="${XDG_CACHE_HOME:-${HOME:-/tmp}/.cache}/openubmc/component_drivers_lua_root/${_workspace_key}" | 1050 | _lua_root="${XDG_CACHE_HOME:-${HOME:-/tmp}/.cache}/openubmc/component_drivers_lua_root/${_workspace_key}" |
| 1037 | mkdir -p "$_lua_root/include" "$_lua_root/usr/lib64" | 1051 | mkdir -p "$_lua_root/include" "$_lua_root/usr/lib64" |
| 1038 | - for _h in lua.h lauxlib.h lualib.h luaconf.h lua.hpp; do | 1052 | + for _h in "${_lua_headers[@]}"; do |
| 1039 | if [ -f "$hdr_dir/$_h" ]; then | 1053 | if [ -f "$hdr_dir/$_h" ]; then |
| 1040 | ln -sfn "$hdr_dir/$_h" "$_lua_root/include/$_h" | 1054 | ln -sfn "$hdr_dir/$_h" "$_lua_root/include/$_h" |
| 1041 | elif [ -L "$_lua_root/include/$_h" ]; then | 1055 | elif [ -L "$_lua_root/include/$_h" ]; then |
| @@ -1052,7 +1066,7 @@ activate_lua_toolchain() { | |||
| 1052 | done | 1066 | done |
| 1053 | shopt -u nullglob | 1067 | shopt -u nullglob |
| 1054 | 1068 | ||
| 1055 | - if [ ! -f "$_lua_root/include/lua.h" ] || [ ! -e "$_lua_root/usr/lib64/lib${link_name}.so" ]; then | 1069 | + if ! lua_headers_complete "$_lua_root/include" || [ ! -e "$_lua_root/usr/lib64/lib${link_name}.so" ]; then |
| 1056 | print_error "Lua 工具链不完整: hdr=${hdr_dir} lib=${lib_dir}/lib${link_name}.so" | 1070 | print_error "Lua 工具链不完整: hdr=${hdr_dir} lib=${lib_dir}/lib${link_name}.so" |
| 1057 | exit 1 | 1071 | exit 1 |
| 1058 | fi | 1072 | fi |
| @@ -1071,7 +1085,7 @@ c_link_args = ['-L${lib_dir}', '-l${link_name}'] | |||
| 1071 | NEOF | 1085 | NEOF |
| 1072 | # 路径和内容都属于工具链:同一目录升级 Lua 也须重建预编译库与主构建配置。 | 1086 | # 路径和内容都属于工具链:同一目录升级 Lua 也须重建预编译库与主构建配置。 |
| 1073 | local _lua_inputs=("$_luajit_native_ini" "$lib_dir/lib${link_name}.so") | 1087 | local _lua_inputs=("$_luajit_native_ini" "$lib_dir/lib${link_name}.so") |
| 1074 | - for _h in lua.h lauxlib.h lualib.h luaconf.h lua.hpp; do | 1088 | + for _h in "${_lua_headers[@]}"; do |
| 1075 | if [ -f "$hdr_dir/$_h" ]; then | 1089 | if [ -f "$hdr_dir/$_h" ]; then |
| 1076 | _lua_inputs+=("$hdr_dir/$_h") | 1090 | _lua_inputs+=("$hdr_dir/$_h") |
| 1077 | fi | 1091 | fi |
| @@ -1107,7 +1121,7 @@ _lua_header_try_dir() { | |||
| 1107 | *) return 1 ;; | 1121 | *) return 1 ;; |
| 1108 | esac | 1122 | esac |
| 1109 | for sub in "/$version_dir" ""; do | 1123 | for sub in "/$version_dir" ""; do |
| 1110 | - if [ -f "${base}${sub}/lua.h" ]; then | 1124 | + if lua_headers_complete "${base}${sub}"; then |
| 1111 | # 通用 include 下可能还留有 Lua 5.1;必须与选中的 .pc/库版本一致。 | 1125 | # 通用 include 下可能还留有 Lua 5.1;必须与选中的 .pc/库版本一致。 |
| 1112 | version=$(awk ' | 1126 | version=$(awk ' |
| 1113 | /^[[:space:]]*#[[:space:]]*define[[:space:]]+LUA_VERSION_NUM[[:space:]]+/ { | 1127 | /^[[:space:]]*#[[:space:]]*define[[:space:]]+LUA_VERSION_NUM[[:space:]]+/ { |
| @@ -1206,7 +1220,7 @@ setup_system_lua() { | |||
| 1206 | libdir="$(_lua_lib_dir_from_pc lua5.3 lua5.3 || true)" | 1220 | libdir="$(_lua_lib_dir_from_pc lua5.3 lua5.3 || true)" |
| 1207 | link_name="lua5.3" | 1221 | link_name="lua5.3" |
| 1208 | fi | 1222 | fi |
| 1209 | - if [ -z "$inc" ] || [ ! -f "$inc/lua.h" ] || [ -z "$libdir" ] || [ ! -e "$libdir/lib${link_name}.so" ]; then | 1223 | + if [ -z "$inc" ] || ! lua_headers_complete "$inc" || [ -z "$libdir" ] || [ ! -e "$libdir/lib${link_name}.so" ]; then |
| 1210 | print_error "未找到系统 Lua 5.4/5.3(需要 lua.h 与 liblua5.4.so)" | 1224 | print_error "未找到系统 Lua 5.4/5.3(需要 lua.h 与 liblua5.4.so)" |
| 1211 | print_error "请安装 liblua5.4-dev,或提供 temp/include/lua.h 与 temp/usr/lib64/liblua.so" | 1225 | print_error "请安装 liblua5.4-dev,或提供 temp/include/lua.h 与 temp/usr/lib64/liblua.so" |
| 1212 | exit 1 | 1226 | exit 1 |
| @@ -1415,9 +1429,9 @@ main() { | |||
| 1415 | # bingo/conan temp 布局:旧包把头放在 include/luajit/,skynet 的 Lua 5.4 则直接 | 1429 | # bingo/conan temp 布局:旧包把头放在 include/luajit/,skynet 的 Lua 5.4 则直接 |
| 1416 | # 放在 include/(与 gtest/bus 等混放)。两种都接受。 | 1430 | # 放在 include/(与 gtest/bus 等混放)。两种都接受。 |
| 1417 | local _lua_hdr="" | 1431 | local _lua_hdr="" |
| 1418 | - if [ -f "$_conan_temp/include/luajit/lua.h" ]; then | 1432 | + if lua_headers_complete "$_conan_temp/include/luajit"; then |
| 1419 | _lua_hdr="$_conan_temp/include/luajit/lua.h" | 1433 | _lua_hdr="$_conan_temp/include/luajit/lua.h" |
| 1420 | - elif [ -f "$_conan_temp/include/lua.h" ]; then | 1434 | + elif lua_headers_complete "$_conan_temp/include"; then |
| 1421 | _lua_hdr="$_conan_temp/include/lua.h" | 1435 | _lua_hdr="$_conan_temp/include/lua.h" |
| 1422 | fi | 1436 | fi |
| 1423 | if [ -n "$_lua_hdr" ] && [ -e "$_conan_temp/usr/lib64/liblua.so" ]; then | 1437 | if [ -n "$_lua_hdr" ] && [ -e "$_conan_temp/usr/lib64/liblua.so" ]; then |
| @@ -0,0 +1,106 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | + | ||
| 3 | +from collections import Counter | ||
| 4 | +from pathlib import Path | ||
| 5 | +import shutil | ||
| 6 | +import subprocess | ||
| 7 | +import tempfile | ||
| 8 | +import unittest | ||
| 9 | + | ||
| 10 | +import run_memory_audit_stress as stress | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +class ParseCasesTest(unittest.TestCase): | ||
| 14 | + def test_accepts_run_marker_interleaved_after_runtime_log(self): | ||
| 15 | + output = """\ | ||
| 16 | +DEBUG thread_pool.cpp:494 operator() | io thread 1 started[ RUN ] MucseInterfaceTest.CoolingUpdatesSignedTemperatureBySmbus | ||
| 17 | +[ OK ] MucseInterfaceTest.CoolingUpdatesSignedTemperatureBySmbus (0 ms) | ||
| 18 | +""" | ||
| 19 | + | ||
| 20 | + self.assertEqual( | ||
| 21 | + stress.parse_cases(output), | ||
| 22 | + {"run": 1, "ok": 1, "incomplete": 0}, | ||
| 23 | + ) | ||
| 24 | + | ||
| 25 | + def test_accepts_log_text_interleaved_after_result_name(self): | ||
| 26 | + output = """\ | ||
| 27 | +[ RUN ] MucseInterfaceTest.CoolingUpdatesTemperatureByNcsi | ||
| 28 | +[ OK ] MucseInterfaceTest.CoolingUpdatesTemperatureByNcsiDEBUG runtime started (1 ms) | ||
| 29 | +""" | ||
| 30 | + | ||
| 31 | + self.assertEqual( | ||
| 32 | + stress.parse_cases(output), | ||
| 33 | + {"run": 1, "ok": 1, "incomplete": 0}, | ||
| 34 | + ) | ||
| 35 | + | ||
| 36 | + def test_ignores_repeated_failure_summary_without_run(self): | ||
| 37 | + output = """\ | ||
| 38 | +[ FAILED ] Suite.FailedCase | ||
| 39 | +""" | ||
| 40 | + | ||
| 41 | + self.assertEqual(stress.parse_cases(output), {"run": 0, "incomplete": 0}) | ||
| 42 | + | ||
| 43 | + def test_accepts_multiple_markers_on_one_line(self): | ||
| 44 | + output = """\ | ||
| 45 | +[ RUN ] Suite.First[ OK ] Suite.First (0 ms)[ RUN ] Suite.Second | ||
| 46 | +[ OK ] Suite.Second (0 ms) | ||
| 47 | +""" | ||
| 48 | + | ||
| 49 | + self.assertEqual( | ||
| 50 | + stress.parse_cases(output), | ||
| 51 | + {"run": 2, "ok": 2, "incomplete": 0}, | ||
| 52 | + ) | ||
| 53 | + | ||
| 54 | + | ||
| 55 | +class GtestCountErrorTest(unittest.TestCase): | ||
| 56 | + def test_accepts_first_and_matching_round_counts(self): | ||
| 57 | + self.assertIsNone(stress.gtest_count_error(11574, None)) | ||
| 58 | + self.assertIsNone(stress.gtest_count_error(11574, 11574)) | ||
| 59 | + | ||
| 60 | + def test_rejects_changed_round_count(self): | ||
| 61 | + self.assertEqual( | ||
| 62 | + stress.gtest_count_error(11573, 11574), | ||
| 63 | + "GoogleTest case count changed from 11574 to 11573", | ||
| 64 | + ) | ||
| 65 | + | ||
| 66 | + | ||
| 67 | +@unittest.skipUnless(shutil.which("meson"), "requires Meson") | ||
| 68 | +class RoundResultTest(unittest.TestCase): | ||
| 69 | + def run_fixture(self, skipped): | ||
| 70 | + with tempfile.TemporaryDirectory(prefix="stress-round-result-") as tmp: | ||
| 71 | + root = Path(tmp) | ||
| 72 | + (root / "meson.build").write_text( | ||
| 73 | + "project('stress-fixture')\n" | ||
| 74 | + "python = import('python').find_installation('python3')\n" | ||
| 75 | + "test('fixture', python, args: files('fixture.py'))\n" | ||
| 76 | + ) | ||
| 77 | + output = "[ RUN ] Suite.First\n[ OK ] Suite.First (0 ms)\n" | ||
| 78 | + if skipped: | ||
| 79 | + output += "[ RUN ] Suite.Second\n[ SKIPPED ] Suite.Second (0 ms)\n" | ||
| 80 | + (root / "fixture.py").write_text(f"print({output!r})\n") | ||
| 81 | + build = root / "build" | ||
| 82 | + setup = subprocess.run( | ||
| 83 | + ["meson", "setup", str(build), str(root)], | ||
| 84 | + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, | ||
| 85 | + ) | ||
| 86 | + self.assertEqual(setup.returncode, 0, setup.stdout) | ||
| 87 | + results = root / "results" | ||
| 88 | + results.mkdir() | ||
| 89 | + return stress.run_round( | ||
| 90 | + build, results, 1, Counter({"stress-fixture:fixture": 1}), 30, | ||
| 91 | + ) | ||
| 92 | + | ||
| 93 | + def test_accepts_complete_successful_round(self): | ||
| 94 | + outcome = self.run_fixture(skipped=False) | ||
| 95 | + self.assertTrue(outcome["passed"], outcome) | ||
| 96 | + self.assertEqual(outcome["gtest_cases"]["ok"], 1) | ||
| 97 | + | ||
| 98 | + def test_rejects_skipped_cases_even_when_meson_reports_success(self): | ||
| 99 | + outcome = self.run_fixture(skipped=True) | ||
| 100 | + self.assertEqual(outcome["meson_exit_code"], 0, outcome) | ||
| 101 | + self.assertEqual(outcome["gtest_cases"]["skipped"], 1, outcome) | ||
| 102 | + self.assertFalse(outcome["passed"], outcome) | ||
| 103 | + | ||
| 104 | + | ||
| 105 | +if __name__ == "__main__": | ||
| 106 | + unittest.main() | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 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 | +"""Compatibility entry point for the unified Lua build-script tests.""" | ||
| 11 | +from pathlib import Path | ||
| 12 | +import runpy | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +if __name__ == "__main__": | ||
| 16 | + runpy.run_path(str(Path(__file__).with_name("tests") / "test_smart_build_lua.py"), | ||
| 17 | + run_name="__main__") | ||
| @@ -8,10 +8,12 @@ | |||
| 8 | # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | 8 | # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, |
| 9 | # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | 9 | # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. |
| 10 | # See the Mulan PSL v2 for more details. | 10 | # See the Mulan PSL v2 for more details. |
| 11 | -"""只运行 smart_build 的 Lua 辅助函数,不执行主构建或包安装。""" | 11 | +"""验证 Lua 选择、缓存和小型编译探针,不执行驱动主构建或包安装。""" |
| 12 | 12 | ||
| 13 | import os | 13 | import os |
| 14 | from pathlib import Path | 14 | from pathlib import Path |
| 15 | +import shlex | ||
| 16 | +import shutil | ||
| 15 | import subprocess | 17 | import subprocess |
| 16 | import tempfile | 18 | import tempfile |
| 17 | import unittest | 19 | import unittest |
| @@ -51,6 +53,8 @@ class LuaToolchainTests(unittest.TestCase): | |||
| 51 | def header(self, directory, version): | 53 | def header(self, directory, version): |
| 52 | directory.mkdir(parents=True, exist_ok=True) | 54 | directory.mkdir(parents=True, exist_ok=True) |
| 53 | (directory / "lua.h").write_text(f"#define LUA_VERSION_NUM {version}\n") | 55 | (directory / "lua.h").write_text(f"#define LUA_VERSION_NUM {version}\n") |
| 56 | + for name in ("lauxlib.h", "lualib.h", "luaconf.h"): | ||
| 57 | + (directory / name).touch() | ||
| 54 | return directory | 58 | return directory |
| 55 | 59 | ||
| 56 | def pc(self, name, cflags=""): | 60 | def pc(self, name, cflags=""): |
| @@ -119,6 +123,22 @@ meson_extra_args=() | |||
| 119 | self.assertNotEqual(result.returncode, 0) | 123 | self.assertNotEqual(result.returncode, 0) |
| 120 | self.assertEqual(result.stdout, "") | 124 | self.assertEqual(result.stdout, "") |
| 121 | 125 | ||
| 126 | + def test_rejects_matching_version_with_missing_core_header(self): | ||
| 127 | + self.header(self.include, 504) | ||
| 128 | + (self.include / "lauxlib.h").unlink() | ||
| 129 | + result = self.shell('_lua_header_try_dir "$TEST_ROOT/include" 504') | ||
| 130 | + self.assertNotEqual(result.returncode, 0) | ||
| 131 | + self.assertEqual(result.stdout, "") | ||
| 132 | + | ||
| 133 | + def test_conan_temp_readiness_requires_complete_headers(self): | ||
| 134 | + inc = self.header(self.root / "project/temp/include", 504) | ||
| 135 | + lib = self.root / "project/temp/usr/lib64" | ||
| 136 | + lib.mkdir(parents=True) | ||
| 137 | + (lib / "liblua.so").touch() | ||
| 138 | + self.assertEqual(self.shell("conan_temp_lua_ready").returncode, 0) | ||
| 139 | + (inc / "luaconf.h").unlink() | ||
| 140 | + self.assertNotEqual(self.shell("conan_temp_lua_ready").returncode, 0) | ||
| 141 | + | ||
| 122 | def test_accepts_matching_header_directly_in_include(self): | 142 | def test_accepts_matching_header_directly_in_include(self): |
| 123 | self.header(self.include, 504) | 143 | self.header(self.include, 504) |
| 124 | self.assert_directory( | 144 | self.assert_directory( |
| @@ -198,6 +218,13 @@ setup_libmcpp_cache | |||
| 198 | 'printf "\\n/* updated header */\\n" >> "$TEST_ROOT/include/lua.h"' | 218 | 'printf "\\n/* updated header */\\n" >> "$TEST_ROOT/include/lua.h"' |
| 199 | ) | 219 | ) |
| 200 | 220 | ||
| 221 | + def test_prebuilt_cache_rebuilds_when_compatibility_header_changes(self): | ||
| 222 | + self.include.mkdir() | ||
| 223 | + (self.include / "compat-5.3.h").write_text("/* initial compatibility header */\n") | ||
| 224 | + self.assert_cache_rebuilt( | ||
| 225 | + 'printf "updated compatibility header\\n" >> "$TEST_ROOT/include/compat-5.3.h"' | ||
| 226 | + ) | ||
| 227 | + | ||
| 201 | def test_prebuilt_cache_rebuilds_when_library_changes(self): | 228 | def test_prebuilt_cache_rebuilds_when_library_changes(self): |
| 202 | self.assert_cache_rebuilt( | 229 | self.assert_cache_rebuilt( |
| 203 | 'printf "new library bytes\\n" > "$TEST_ROOT/lib/liblua5.4.so"' | 230 | 'printf "new library bytes\\n" > "$TEST_ROOT/lib/liblua5.4.so"' |
| @@ -206,7 +233,7 @@ setup_libmcpp_cache | |||
| 206 | def test_prebuilt_cache_rebuilds_when_lua_source_moves(self): | 233 | def test_prebuilt_cache_rebuilds_when_lua_source_moves(self): |
| 207 | self.assert_cache_rebuilt(r""" | 234 | self.assert_cache_rebuilt(r""" |
| 208 | mkdir "$TEST_ROOT/moved" | 235 | mkdir "$TEST_ROOT/moved" |
| 209 | -cp "$TEST_ROOT/include/lua.h" "$TEST_ROOT/moved/lua.h" | 236 | +cp "$TEST_ROOT/include/"*.h "$TEST_ROOT/moved/" |
| 210 | hdr="$TEST_ROOT/moved" | 237 | hdr="$TEST_ROOT/moved" |
| 211 | """) | 238 | """) |
| 212 | 239 | ||
| @@ -246,5 +273,161 @@ lua_dependency_matches "$BUILD_DIR" | |||
| 246 | self.assertEqual((self.root / "setup-calls").read_text().splitlines(), ["setup", "setup"]) | 273 | self.assertEqual((self.root / "setup-calls").read_text().splitlines(), ["setup", "setup"]) |
| 247 | 274 | ||
| 248 | 275 | ||
| 276 | +HARNESS = r''' | ||
| 277 | +source "$SMART_BUILD_SCRIPT" | ||
| 278 | +PROJECT_DIR="$LUA_TEST_PROJECT" | ||
| 279 | +BUILD_DIR="$PROJECT_DIR/builddir" | ||
| 280 | +CACHE_DIR="$PROJECT_DIR/.libmcpp_prebuilt" | ||
| 281 | +LIBMCPP_SRCDIR="$PROJECT_DIR/subprojects/libmcpp" | ||
| 282 | +PCDIR="$PROJECT_DIR/.pkg-config" | ||
| 283 | +# Keep dependency selection real; suppress unrelated package/cache/build side effects. | ||
| 284 | +ensure_host_packages() { :; } | ||
| 285 | +setup_relative_timestamp() { :; } | ||
| 286 | +setup_ccache() { :; } | ||
| 287 | +get_system_info() { echo '4 8 8 0'; } | ||
| 288 | +resolve_libmcpp_revision() { :; } | ||
| 289 | +setup_libmcpp_cache() { | ||
| 290 | + printf '%s\n' "${meson_extra_args[@]}" > "$PROJECT_DIR/selected-args.txt" | ||
| 291 | + touch "$PROJECT_DIR/reached-build" | ||
| 292 | + exit 0 | ||
| 293 | +} | ||
| 294 | +main --setup-only | ||
| 295 | +''' | ||
| 296 | + | ||
| 297 | + | ||
| 298 | +class LuaSelectionIntegrationTests(unittest.TestCase): | ||
| 299 | + @classmethod | ||
| 300 | + def setUpClass(cls): | ||
| 301 | + if not shutil.which("meson") or subprocess.run( | ||
| 302 | + ["pkg-config", "--exists", "lua5.4"], check=False | ||
| 303 | + ).returncode: | ||
| 304 | + raise unittest.SkipTest("requires Meson and the Lua 5.4 development package") | ||
| 305 | + cls.cflags = shlex.split(subprocess.check_output( | ||
| 306 | + ["pkg-config", "--cflags-only-I", "lua5.4"], text=True)) | ||
| 307 | + cls.headers = next(Path(f[2:]) for f in cls.cflags if (Path(f[2:]) / "lua.h").is_file()) | ||
| 308 | + cls.libdir = Path(subprocess.check_output( | ||
| 309 | + ["pkg-config", "--variable=libdir", "lua5.4"], text=True).strip()) | ||
| 310 | + | ||
| 311 | + def setUp(self): | ||
| 312 | + self.tmp = tempfile.TemporaryDirectory(prefix="smart-build-lua-") | ||
| 313 | + self.addCleanup(self.tmp.cleanup) | ||
| 314 | + self.project = Path(self.tmp.name) / "project" | ||
| 315 | + self.project.mkdir() | ||
| 316 | + (self.project / "meson.build").touch() | ||
| 317 | + self.env = {**os.environ, "SMART_BUILD_SCRIPT": str(SCRIPT), | ||
| 318 | + "_SMART_BUILD_ORIG_SCRIPT": str(SCRIPT), | ||
| 319 | + "LUA_TEST_PROJECT": str(self.project), | ||
| 320 | + "XDG_CACHE_HOME": str(Path(self.tmp.name) / "cache"), | ||
| 321 | + "PKG_CONFIG_PATH": "", "PKG_CONFIG_SYSROOT_DIR": "", | ||
| 322 | + "LD_LIBRARY_PATH": "", "SMART_BUILD_NO_TIMESTAMP": "1"} | ||
| 323 | + | ||
| 324 | + def select(self): | ||
| 325 | + return subprocess.run(["bash", "-c", HARNESS], env=self.env, | ||
| 326 | + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30) | ||
| 327 | + | ||
| 328 | + def compile_and_run(self, require_temp=False): | ||
| 329 | + native = self.project / "builddir_luajit_native.ini" | ||
| 330 | + self.assertTrue(native.is_file(), "Lua native file was not generated") | ||
| 331 | + args = (self.project / "selected-args.txt").read_text().splitlines() | ||
| 332 | + roots = [arg.split("=", 1)[1] for arg in args if arg.startswith("-Dlibmcpp:lua_root=")] | ||
| 333 | + self.assertEqual(len(roots), 1, args) | ||
| 334 | + self.assertFalse(Path(roots[0]).is_relative_to(self.project)) | ||
| 335 | + # Consume the emitted native file and lua_root with the real compiler/linker. | ||
| 336 | + (self.project / "meson_options.txt").write_text("option('lua_root', type: 'string')\n") | ||
| 337 | + (self.project / "meson.build").write_text("""project('lua-probe', 'cpp') | ||
| 338 | +cc = meson.get_compiler('cpp') | ||
| 339 | +root = get_option('lua_root') | ||
| 340 | +lua = cc.find_library('lua', dirs: root / 'usr/lib64') | ||
| 341 | +executable('probe', 'probe.cpp', dependencies: lua, | ||
| 342 | + include_directories: include_directories(root / 'include')) | ||
| 343 | +""") | ||
| 344 | + (self.project / "probe.cpp").write_text('''extern "C" { | ||
| 345 | +#include <lua.h> | ||
| 346 | +#include <lauxlib.h> | ||
| 347 | +#include <lualib.h> | ||
| 348 | +} | ||
| 349 | +static_assert(LUA_VERSION_NUM == 504, "Lua 5.4 is required"); | ||
| 350 | +''' + ('#ifndef SMART_BUILD_TEST_TEMP\n#error Conan headers lost priority\n#endif\n' if require_temp else '') + ''' | ||
| 351 | +int main() { | ||
| 352 | + lua_State *state = luaL_newstate(); | ||
| 353 | + if (!state) return 1; | ||
| 354 | + luaL_openlibs(state); | ||
| 355 | + int status = luaL_dostring(state, "assert(_VERSION == 'Lua 5.4')"); | ||
| 356 | + lua_close(state); | ||
| 357 | + return status; | ||
| 358 | +} | ||
| 359 | +''') | ||
| 360 | + build = self.project / "probe-build" | ||
| 361 | + commands = [["meson", "setup", str(build), str(self.project), | ||
| 362 | + "--native-file=" + str(native), "-Dlua_root=" + roots[0]], | ||
| 363 | + ["meson", "compile", "-C", str(build)], [str(build / "probe")]] | ||
| 364 | + for command in commands: | ||
| 365 | + result = subprocess.run(command, env=self.env, text=True, | ||
| 366 | + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | ||
| 367 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 368 | + | ||
| 369 | + def test_missing_temp_uses_system_lua_and_regenerates_native_paths(self): | ||
| 370 | + (self.project / "builddir_luajit_native.ini").write_text("stale paths from another checkout\n") | ||
| 371 | + result = self.select() | ||
| 372 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 373 | + self.compile_and_run() | ||
| 374 | + | ||
| 375 | + def test_complete_temp_keeps_priority_over_system_lua(self): | ||
| 376 | + inc = self.project / "temp/include" | ||
| 377 | + lib = self.project / "temp/usr/lib64" | ||
| 378 | + inc.mkdir(parents=True) | ||
| 379 | + lib.mkdir(parents=True) | ||
| 380 | + for header in self.headers.glob("*.h*"): | ||
| 381 | + shutil.copyfile(header, inc / header.name) | ||
| 382 | + with (inc / "lua.h").open("a") as output: | ||
| 383 | + output.write("\n#define SMART_BUILD_TEST_TEMP 1\n") | ||
| 384 | + (lib / "liblua.so").symlink_to(self.libdir / "liblua5.4.so") | ||
| 385 | + result = self.select() | ||
| 386 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 387 | + self.compile_and_run(require_temp=True) | ||
| 388 | + | ||
| 389 | + def test_incomplete_temp_does_not_mix_its_headers_with_system_library(self): | ||
| 390 | + inc = self.project / "temp/include" | ||
| 391 | + inc.mkdir(parents=True) | ||
| 392 | + (inc / "lua.h").write_text("#error incomplete temp headers must not be selected\n") | ||
| 393 | + result = self.select() | ||
| 394 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 395 | + self.compile_and_run() | ||
| 396 | + | ||
| 397 | + def test_temp_with_library_but_missing_core_headers_falls_back_as_a_pair(self): | ||
| 398 | + self.assertEqual(self.select().returncode, 0) | ||
| 399 | + inc = self.project / "temp/include" | ||
| 400 | + lib = self.project / "temp/usr/lib64" | ||
| 401 | + inc.mkdir(parents=True) | ||
| 402 | + lib.mkdir(parents=True) | ||
| 403 | + (inc / "lua.h").write_text("#error incomplete temp must not replace system headers\n") | ||
| 404 | + (lib / "liblua.so").symlink_to(self.libdir / "liblua5.4.so") | ||
| 405 | + result = self.select() | ||
| 406 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 407 | + self.compile_and_run() | ||
| 408 | + | ||
| 409 | + def test_selection_removes_stale_optional_headers_and_library_aliases(self): | ||
| 410 | + self.assertEqual(self.select().returncode, 0) | ||
| 411 | + args = (self.project / "selected-args.txt").read_text().splitlines() | ||
| 412 | + root = Path(next(arg.split("=", 1)[1] for arg in args | ||
| 413 | + if arg.startswith("-Dlibmcpp:lua_root="))) | ||
| 414 | + stale = [self.project / ".lua54_inc/luajit.h", root / "include/luajit.h", | ||
| 415 | + root / "usr/lib64/liblua.so.99"] | ||
| 416 | + for path in stale: | ||
| 417 | + path.symlink_to(self.project / "removed-old-dependency") | ||
| 418 | + result = self.select() | ||
| 419 | + self.assertEqual(result.returncode, 0, result.stdout) | ||
| 420 | + for path in stale: | ||
| 421 | + self.assertFalse(path.is_symlink(), f"stale dependency remains: {path}") | ||
| 422 | + self.compile_and_run() | ||
| 423 | + | ||
| 424 | + def test_missing_both_sources_stops_before_build_with_actionable_error(self): | ||
| 425 | + self.env["PKG_CONFIG_LIBDIR"] = str(Path(self.tmp.name) / "empty-pkgconfig") | ||
| 426 | + result = self.select() | ||
| 427 | + self.assertNotEqual(result.returncode, 0, result.stdout) | ||
| 428 | + self.assertIn("liblua5.4-dev", result.stdout) | ||
| 429 | + self.assertFalse((self.project / "reached-build").exists()) | ||
| 430 | + | ||
| 431 | + | ||
| 249 | if __name__ == "__main__": | 432 | if __name__ == "__main__": |
| 250 | unittest.main(verbosity=2) | 433 | unittest.main(verbosity=2) |
| @@ -15,11 +15,17 @@ | |||
| 15 | #include <gtest/gtest.h> | 15 | #include <gtest/gtest.h> |
| 16 | 16 | ||
| 17 | #include <chrono> | 17 | #include <chrono> |
| 18 | +#include <future> | ||
| 19 | +#include <mc/timer.h> | ||
| 20 | +#include <resource_tree/interface/Bus.h> | ||
| 18 | #include <string> | 21 | #include <string> |
| 19 | #include <thread> | 22 | #include <thread> |
| 20 | 23 | ||
| 21 | -#include "../../../../../drivers/csr1/bus/common/bus_object_base.h" | 24 | +#define private public |
| 22 | #include "../../../../../drivers/csr1/bus/common/interface/i_bus.h" | 25 | #include "../../../../../drivers/csr1/bus/common/interface/i_bus.h" |
| 26 | +#undef private | ||
| 27 | + | ||
| 28 | +#include "../../../../../drivers/csr1/bus/common/bus_object_base.h" | ||
| 23 | #include "../../../../../drivers/internal/manager.h" | 29 | #include "../../../../../drivers/internal/manager.h" |
| 24 | 30 | ||
| 25 | class TestBusObject : public mc::engine::object<TestBusObject> { | 31 | class TestBusObject : public mc::engine::object<TestBusObject> { |
| @@ -340,4 +346,71 @@ TEST_F(InterfaceBusNoInternalTest, enable_without_internal_bus_is_noop_keeping_s | |||
| 340 | EXPECT_TRUE(m_object->m_interface_bus.AccessEnabled.get_value().as<bool>()); | 346 | EXPECT_TRUE(m_object->m_interface_bus.AccessEnabled.get_value().as<bool>()); |
| 341 | } | 347 | } |
| 342 | 348 | ||
| 349 | +TEST_F(InterfaceBusTest, cancelled_access_timer_does_not_retain_interface_parent) | ||
| 350 | +{ | ||
| 351 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 352 | + auto timer = m_object->m_interface_bus.m_access_timer; | ||
| 353 | + ASSERT_NE(timer, nullptr); | ||
| 354 | + m_object->m_interface_bus.SetAccessibility(true, 0); | ||
| 355 | + EXPECT_FALSE(timer->is_active()); | ||
| 356 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 357 | + m_object.reset(); | ||
| 358 | +} | ||
| 359 | + | ||
| 360 | +TEST_F(InterfaceBusTest, replaced_access_timer_does_not_retain_interface_parent) | ||
| 361 | +{ | ||
| 362 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 363 | + auto timer = m_object->m_interface_bus.m_access_timer; | ||
| 364 | + ASSERT_NE(timer, nullptr); | ||
| 365 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 366 | + EXPECT_NE(timer, m_object->m_interface_bus.m_access_timer); | ||
| 367 | + EXPECT_FALSE(timer->is_active()); | ||
| 368 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 369 | + m_object.reset(); | ||
| 370 | +} | ||
| 371 | + | ||
| 372 | +TEST_F(InterfaceBusTest, completed_access_timer_does_not_retain_interface_parent) | ||
| 373 | +{ | ||
| 374 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 375 | + auto timer = m_object->m_interface_bus.m_access_timer; | ||
| 376 | + ASSERT_NE(timer, nullptr); | ||
| 377 | + // Run the installed completion callback deterministically; the existing | ||
| 378 | + // timer_callback_re_enables_access test covers actual expiry dispatch. | ||
| 379 | + timer->stop(); | ||
| 380 | + timer->timeout(); | ||
| 381 | + EXPECT_TRUE(m_object->m_interface_bus.isAccessAllowed()); | ||
| 382 | + EXPECT_EQ(m_object->m_interface_bus.m_access_timer, timer); | ||
| 383 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 384 | + m_object.reset(); | ||
| 385 | +} | ||
| 386 | + | ||
| 387 | +TEST_F(InterfaceBusTest, destruction_stops_access_timer_retained_by_pending_work) | ||
| 388 | +{ | ||
| 389 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 390 | + auto timer = m_object->m_interface_bus.m_access_timer; | ||
| 391 | + ASSERT_NE(timer, nullptr); | ||
| 392 | + m_object.reset(); | ||
| 393 | + EXPECT_FALSE(timer->is_active()); | ||
| 394 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 395 | + timer->stop(); | ||
| 396 | +} | ||
| 397 | + | ||
| 398 | +TEST_F(InterfaceBusTest, access_timer_callback_uses_owner_executor) | ||
| 399 | +{ | ||
| 400 | + mc::any_executor owner_executor{mc::runtime::runtime_strand{}}; | ||
| 401 | + m_object->bind_executor(owner_executor); | ||
| 402 | + m_object->m_interface_bus.SetAccessibility(false, 1800); | ||
| 403 | + auto timer = m_object->m_interface_bus.m_access_timer; | ||
| 404 | + ASSERT_NE(timer, nullptr); | ||
| 405 | + auto result = std::make_shared<std::promise<bool>>(); | ||
| 406 | + auto ready = result->get_future(); | ||
| 407 | + timer->timeout.connect([owner_executor, result]() { | ||
| 408 | + result->set_value(owner_executor.running_in_this_thread()); | ||
| 409 | + }); | ||
| 410 | + timer->start(mc::milliseconds(1)); | ||
| 411 | + ASSERT_EQ(ready.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 412 | + EXPECT_TRUE(ready.get()); | ||
| 413 | + timer->stop(); | ||
| 414 | +} | ||
| 415 | + | ||
| 343 | } // namespace | 416 | } // namespace |
| @@ -81,6 +81,18 @@ public: | |||
| 81 | return 0; | 81 | return 0; |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | + static bool task_is_idle(ITask* task) | ||
| 85 | + { | ||
| 86 | + auto& sched = Scheduler::get_instance(); | ||
| 87 | + std::lock_guard<std::recursive_mutex> lock(sched.m_mutex); | ||
| 88 | + for (const auto& entry : sched.m_entries) { | ||
| 89 | + if (entry.task == task) { | ||
| 90 | + return entry.run_state && !entry.run_state->running.load(std::memory_order_acquire); | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + return false; | ||
| 94 | + } | ||
| 95 | + | ||
| 84 | // 将某 task 的 entry 重新置为到期(next_run_ms=0),驱动下一轮 on_timer 执行 | 96 | // 将某 task 的 entry 重新置为到期(next_run_ms=0),驱动下一轮 on_timer 执行 |
| 85 | static void make_task_due(ITask* task) | 97 | static void make_task_due(ITask* task) |
| 86 | { | 98 | { |
| @@ -165,9 +177,9 @@ public: | |||
| 165 | } | 177 | } |
| 166 | 178 | ||
| 167 | private: | 179 | private: |
| 168 | - uint64_t m_period; | 180 | + uint64_t m_period; |
| 169 | - uint32_t m_run_count; | 181 | + std::atomic<uint32_t> m_run_count; |
| 170 | - bool m_throw_std; | 182 | + bool m_throw_std; |
| 171 | }; | 183 | }; |
| 172 | 184 | ||
| 173 | // ===== Task that registers another task during run: 验证锁外执行 ===== | 185 | // ===== Task that registers another task during run: 验证锁外执行 ===== |
| @@ -349,6 +361,104 @@ private: | |||
| 349 | std::atomic<uint32_t> m_run_count{0}; | 361 | std::atomic<uint32_t> m_run_count{0}; |
| 350 | }; | 362 | }; |
| 351 | 363 | ||
| 364 | +// The completion promise lives outside the task so a pending hardware result | ||
| 365 | +// does not itself keep the host alive and hide Scheduler ownership mistakes. | ||
| 366 | +struct owned_task_signals { | ||
| 367 | + mc::promise<void> completion{mc::runtime::immediate_executor{}}; | ||
| 368 | + std::atomic<bool> entered{false}; | ||
| 369 | + std::atomic<uint32_t> run_count{0}; | ||
| 370 | + std::atomic<bool> destroyed{false}; | ||
| 371 | +}; | ||
| 372 | + | ||
| 373 | +class owned_async_task : public mc::object_base, public dev::csr1::ITask { | ||
| 374 | +public: | ||
| 375 | + explicit owned_async_task(std::shared_ptr<owned_task_signals> signals) : m_signals(std::move(signals)) | ||
| 376 | + {} | ||
| 377 | + | ||
| 378 | + ~owned_async_task() override | ||
| 379 | + { | ||
| 380 | + dev::csr1::Scheduler::get_instance().unregister_task(this); | ||
| 381 | + m_signals->destroyed.store(true, std::memory_order_release); | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + void run() override | ||
| 385 | + { | ||
| 386 | + ADD_FAILURE() << "Scheduler must use run_async"; | ||
| 387 | + } | ||
| 388 | + | ||
| 389 | + mc::future<void> run_async() override | ||
| 390 | + { | ||
| 391 | + if (m_signals->run_count.fetch_add(1, std::memory_order_acq_rel) != 0) { | ||
| 392 | + return mc::resolve(); | ||
| 393 | + } | ||
| 394 | + auto completion = m_signals->completion.get_future(); | ||
| 395 | + m_signals->entered.store(true, std::memory_order_release); | ||
| 396 | + return completion; | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + uint64_t period_ms() const override | ||
| 400 | + { | ||
| 401 | + return 60000; | ||
| 402 | + } | ||
| 403 | + | ||
| 404 | + mc::shared_ptr<mc::object_base> owner_guard() override | ||
| 405 | + { | ||
| 406 | + return shared_from_this(); | ||
| 407 | + } | ||
| 408 | + bool lifetime_bound_to_owner() const override | ||
| 409 | + { | ||
| 410 | + return true; | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | +private: | ||
| 414 | + std::shared_ptr<owned_task_signals> m_signals; | ||
| 415 | +}; | ||
| 416 | + | ||
| 417 | +// Unlike owned_async_task, keeping this task's host alive does not prevent | ||
| 418 | +// reinitialization from replacing its unique_ptr child. | ||
| 419 | +class replaceable_async_task : public dev::csr1::ITask { | ||
| 420 | +public: | ||
| 421 | + replaceable_async_task(mc::object_base* owner, std::shared_ptr<owned_task_signals> signals) | ||
| 422 | + : m_owner(owner), m_signals(std::move(signals)) | ||
| 423 | + {} | ||
| 424 | + | ||
| 425 | + ~replaceable_async_task() override | ||
| 426 | + { | ||
| 427 | + m_signals->destroyed.store(true, std::memory_order_release); | ||
| 428 | + } | ||
| 429 | + | ||
| 430 | + void run() override | ||
| 431 | + { | ||
| 432 | + ADD_FAILURE() << "Scheduler must use run_async"; | ||
| 433 | + } | ||
| 434 | + | ||
| 435 | + mc::future<void> run_async() override | ||
| 436 | + { | ||
| 437 | + auto completion = m_signals->completion.get_future(); | ||
| 438 | + m_signals->entered.store(true, std::memory_order_release); | ||
| 439 | + return completion; | ||
| 440 | + } | ||
| 441 | + | ||
| 442 | + uint64_t period_ms() const override | ||
| 443 | + { | ||
| 444 | + return 60000; | ||
| 445 | + } | ||
| 446 | + | ||
| 447 | + mc::shared_ptr<mc::object_base> owner_guard() override | ||
| 448 | + { | ||
| 449 | + return m_owner->shared_from_this(); | ||
| 450 | + } | ||
| 451 | + | ||
| 452 | +private: | ||
| 453 | + mc::object_base* m_owner; | ||
| 454 | + std::shared_ptr<owned_task_signals> m_signals; | ||
| 455 | +}; | ||
| 456 | + | ||
| 457 | +class replaceable_task_host : public mc::object_base { | ||
| 458 | +public: | ||
| 459 | + std::unique_ptr<replaceable_async_task> task; | ||
| 460 | +}; | ||
| 461 | + | ||
| 352 | template <typename Predicate> | 462 | template <typename Predicate> |
| 353 | bool wait_until(Predicate&& predicate, std::chrono::milliseconds timeout) | 463 | bool wait_until(Predicate&& predicate, std::chrono::milliseconds timeout) |
| 354 | { | 464 | { |
| @@ -381,6 +491,220 @@ protected: | |||
| 381 | } | 491 | } |
| 382 | }; | 492 | }; |
| 383 | 493 | ||
| 494 | +TEST_F(SchedulerTest, idle_registration_does_not_retain_owner) | ||
| 495 | +{ | ||
| 496 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 497 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 498 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 499 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 500 | + auto* task = owner.get(); | ||
| 501 | + sched.register_task(task); | ||
| 502 | + | ||
| 503 | + owner.reset(); | ||
| 504 | + | ||
| 505 | + EXPECT_TRUE(tracked.expired()); | ||
| 506 | + EXPECT_TRUE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 507 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 508 | + EXPECT_FALSE(signals->entered.load(std::memory_order_acquire)); | ||
| 509 | +} | ||
| 510 | + | ||
| 511 | +TEST_F(SchedulerTest, async_owner_is_retained_until_future_completion_then_released) | ||
| 512 | +{ | ||
| 513 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 514 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 515 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 516 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 517 | + auto* task = owner.get(); | ||
| 518 | + sched.register_task(task); | ||
| 519 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 520 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 521 | + const bool entered = wait_until([&] { | ||
| 522 | + return signals->entered.load(std::memory_order_acquire); | ||
| 523 | + }, std::chrono::seconds(2)); | ||
| 524 | + EXPECT_TRUE(entered); | ||
| 525 | + | ||
| 526 | + // Release from another thread so a broken implementation that enters the | ||
| 527 | + // destructor early can still be unblocked by settling the future below. | ||
| 528 | + auto released = std::async(std::launch::async, [owner = std::move(owner)]() mutable { | ||
| 529 | + owner.reset(); | ||
| 530 | + }); | ||
| 531 | + EXPECT_EQ(released.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 532 | + EXPECT_FALSE(tracked.expired()); | ||
| 533 | + EXPECT_FALSE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 534 | + | ||
| 535 | + signals->completion.set_value(); | ||
| 536 | + released.wait(); | ||
| 537 | + EXPECT_TRUE(wait_until([&] { | ||
| 538 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 539 | + }, std::chrono::seconds(2))); | ||
| 540 | + EXPECT_TRUE(tracked.expired()); | ||
| 541 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 542 | +} | ||
| 543 | + | ||
| 544 | +TEST_F(SchedulerTest, guarded_async_callback_can_unregister_before_completing_its_future) | ||
| 545 | +{ | ||
| 546 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 547 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 548 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 549 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 550 | + auto* task = owner.get(); | ||
| 551 | + sched.register_task(task); | ||
| 552 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 553 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 554 | + EXPECT_TRUE(wait_until([&] { | ||
| 555 | + return signals->entered.load(std::memory_order_acquire); | ||
| 556 | + }, std::chrono::seconds(2))); | ||
| 557 | + | ||
| 558 | + // Model a hardware completion on a different worker than run_async(). | ||
| 559 | + // Unregistration happens inside that callback, before its future settles. | ||
| 560 | + // No external owner reference remains; the scheduler guard must protect it. | ||
| 561 | + owner.reset(); | ||
| 562 | + std::atomic<bool> settled{false}; | ||
| 563 | + auto callback = std::async(std::launch::async, [&] { | ||
| 564 | + sched.unregister_task(task); | ||
| 565 | + if (!settled.exchange(true)) { | ||
| 566 | + signals->completion.set_value(); | ||
| 567 | + } | ||
| 568 | + }); | ||
| 569 | + const auto status = callback.wait_for(std::chrono::seconds(2)); | ||
| 570 | + // Break the deadlock on a failing implementation so the regression has a | ||
| 571 | + // bounded failure and can still clean up its worker and runtime safely. | ||
| 572 | + if (!settled.exchange(true)) { | ||
| 573 | + signals->completion.set_value(); | ||
| 574 | + } | ||
| 575 | + callback.wait(); | ||
| 576 | + EXPECT_EQ(status, std::future_status::ready); | ||
| 577 | + EXPECT_TRUE(wait_until([&] { | ||
| 578 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 579 | + }, std::chrono::seconds(2))); | ||
| 580 | + EXPECT_TRUE(tracked.expired()); | ||
| 581 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 582 | +} | ||
| 583 | + | ||
| 584 | +TEST_F(SchedulerTest, managed_unregister_reregister_preserves_pending_single_flight) | ||
| 585 | +{ | ||
| 586 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 587 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 588 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 589 | + auto* task = owner.get(); | ||
| 590 | + sched.register_task(task); | ||
| 591 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 592 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 593 | + EXPECT_TRUE(wait_until([&] { | ||
| 594 | + return signals->entered.load(std::memory_order_acquire); | ||
| 595 | + }, std::chrono::seconds(2))); | ||
| 596 | + | ||
| 597 | + auto removed = std::async(std::launch::async, [&] { | ||
| 598 | + sched.unregister_task(task); | ||
| 599 | + }); | ||
| 600 | + const auto status = removed.wait_for(std::chrono::seconds(2)); | ||
| 601 | + if (status != std::future_status::ready) { | ||
| 602 | + signals->completion.set_value(); | ||
| 603 | + } | ||
| 604 | + removed.wait(); | ||
| 605 | + ASSERT_EQ(status, std::future_status::ready); | ||
| 606 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 607 | + | ||
| 608 | + sched.register_task(task); | ||
| 609 | + EXPECT_TRUE(sched.has_task(task)); | ||
| 610 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 611 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 612 | + EXPECT_FALSE(wait_until([&] { | ||
| 613 | + return signals->run_count.load(std::memory_order_acquire) > 1; | ||
| 614 | + }, std::chrono::milliseconds(100))); | ||
| 615 | + EXPECT_EQ(signals->run_count.load(std::memory_order_acquire), 1u); | ||
| 616 | + | ||
| 617 | + signals->completion.set_value(); | ||
| 618 | + EXPECT_TRUE(wait_until([&] { | ||
| 619 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 620 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 621 | + return signals->run_count.load(std::memory_order_acquire) >= 2; | ||
| 622 | + }, std::chrono::seconds(2))); | ||
| 623 | + sched.clear(); | ||
| 624 | +} | ||
| 625 | + | ||
| 626 | +TEST_F(SchedulerTest, clear_waits_for_unregistered_managed_pending_future) | ||
| 627 | +{ | ||
| 628 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 629 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 630 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 631 | + auto* task = owner.get(); | ||
| 632 | + sched.register_task(task); | ||
| 633 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 634 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 635 | + EXPECT_TRUE(wait_until([&] { | ||
| 636 | + return signals->entered.load(std::memory_order_acquire); | ||
| 637 | + }, std::chrono::seconds(2))); | ||
| 638 | + | ||
| 639 | + auto removed = std::async(std::launch::async, [&] { | ||
| 640 | + sched.unregister_task(task); | ||
| 641 | + }); | ||
| 642 | + const auto status = removed.wait_for(std::chrono::seconds(2)); | ||
| 643 | + if (status != std::future_status::ready) { | ||
| 644 | + signals->completion.set_value(); | ||
| 645 | + } | ||
| 646 | + removed.wait(); | ||
| 647 | + ASSERT_EQ(status, std::future_status::ready); | ||
| 648 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 649 | + | ||
| 650 | + std::promise<void> clear_started; | ||
| 651 | + auto started = clear_started.get_future(); | ||
| 652 | + auto cleared = std::async(std::launch::async, [&] { | ||
| 653 | + clear_started.set_value(); | ||
| 654 | + sched.clear(); | ||
| 655 | + }); | ||
| 656 | + EXPECT_EQ(started.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 657 | + EXPECT_EQ(cleared.wait_for(std::chrono::milliseconds(100)), std::future_status::timeout); | ||
| 658 | + signals->completion.set_value(); | ||
| 659 | + EXPECT_EQ(cleared.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 660 | + cleared.wait(); | ||
| 661 | + owner.reset(); | ||
| 662 | + EXPECT_TRUE(wait_until([&] { | ||
| 663 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 664 | + }, std::chrono::seconds(2))); | ||
| 665 | +} | ||
| 666 | + | ||
| 667 | +TEST_F(SchedulerTest, managed_host_unregister_waits_before_replacing_child_task) | ||
| 668 | +{ | ||
| 669 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 670 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 671 | + auto owner = mc::make_shared<replaceable_task_host>(); | ||
| 672 | + mc::weak_ptr<replaceable_task_host> tracked = owner; | ||
| 673 | + owner->task = std::make_unique<replaceable_async_task>(owner.get(), signals); | ||
| 674 | + auto* task = owner->task.get(); | ||
| 675 | + sched.register_task(task); | ||
| 676 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 677 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 678 | + EXPECT_TRUE(wait_until([&] { | ||
| 679 | + return signals->entered.load(std::memory_order_acquire); | ||
| 680 | + }, std::chrono::seconds(2))); | ||
| 681 | + | ||
| 682 | + std::promise<void> unregister_started; | ||
| 683 | + auto started = unregister_started.get_future(); | ||
| 684 | + auto replaced = std::async(std::launch::async, [&] { | ||
| 685 | + unregister_started.set_value(); | ||
| 686 | + sched.unregister_task(task); | ||
| 687 | + // Model Interface_Aggregate::init replacing its dispatcher after stop. | ||
| 688 | + owner->task.reset(); | ||
| 689 | + }); | ||
| 690 | + EXPECT_EQ(started.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 691 | + EXPECT_TRUE(wait_until([&] { | ||
| 692 | + return !sched.has_task(task); | ||
| 693 | + }, std::chrono::seconds(2))); | ||
| 694 | + EXPECT_EQ(replaced.wait_for(std::chrono::milliseconds(100)), std::future_status::timeout); | ||
| 695 | + EXPECT_FALSE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 696 | + EXPECT_FALSE(tracked.expired()); | ||
| 697 | + | ||
| 698 | + // Always settle the operation, including on the broken implementation, | ||
| 699 | + // so the failure does not leave a blocked worker or outstanding future. | ||
| 700 | + signals->completion.set_value(); | ||
| 701 | + EXPECT_EQ(replaced.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 702 | + replaced.wait(); | ||
| 703 | + EXPECT_TRUE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 704 | + EXPECT_FALSE(tracked.expired()); | ||
| 705 | + EXPECT_EQ(owner->task, nullptr); | ||
| 706 | +} | ||
| 707 | + | ||
| 384 | // ===== 场景五(P0):task 异常逃逸不应阻止后续 task 与重新 arm ===== | 708 | // ===== 场景五(P0):task 异常逃逸不应阻止后续 task 与重新 arm ===== |
| 385 | 709 | ||
| 386 | TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception) | 710 | TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception) |
| @@ -866,8 +1190,8 @@ public: | |||
| 866 | } | 1190 | } |
| 867 | 1191 | ||
| 868 | private: | 1192 | private: |
| 869 | - uint64_t m_period; | 1193 | + std::atomic<uint64_t> m_period; |
| 870 | - uint32_t m_run_count; | 1194 | + std::atomic<uint32_t> m_run_count; |
| 871 | }; | 1195 | }; |
| 872 | 1196 | ||
| 873 | } // namespace | 1197 | } // namespace |
| @@ -883,9 +1207,16 @@ TEST_F(SchedulerTest, period_change_takes_effect_on_next_round) | |||
| 883 | 1207 | ||
| 884 | // 第一轮:按注册周期执行 | 1208 | // 第一轮:按注册周期执行 |
| 885 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); | 1209 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); |
| 886 | - ASSERT_TRUE(wait_until([&task] { | 1210 | + // run_count changes before complete_task() publishes the next deadline. |
| 887 | - return task.run_count() == 1u; | 1211 | + // Wait for that completion under the scheduler lock before forcing due. |
| 888 | - }, std::chrono::milliseconds(300))); | 1212 | + const bool completed = wait_until([&task] { |
| 1213 | + return task.run_count() == 1u && dev::csr1::SchedulerTestHelper::task_is_idle(&task); | ||
| 1214 | + }, std::chrono::milliseconds(300)); | ||
| 1215 | + EXPECT_TRUE(completed); | ||
| 1216 | + if (!completed) { | ||
| 1217 | + sched.unregister_task(&task); | ||
| 1218 | + return; | ||
| 1219 | + } | ||
| 889 | EXPECT_EQ(task.run_count(), 1u); | 1220 | EXPECT_EQ(task.run_count(), 1u); |
| 890 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 2000u); | 1221 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 2000u); |
| 891 | 1222 | ||
| @@ -895,8 +1226,8 @@ TEST_F(SchedulerTest, period_change_takes_effect_on_next_round) | |||
| 895 | 1226 | ||
| 896 | // 第二轮:entry.period_ms 刷新为新周期 | 1227 | // 第二轮:entry.period_ms 刷新为新周期 |
| 897 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); | 1228 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); |
| 898 | - ASSERT_TRUE(wait_until([&task] { | 1229 | + EXPECT_TRUE(wait_until([&task] { |
| 899 | - return task.run_count() == 2u; | 1230 | + return task.run_count() == 2u && dev::csr1::SchedulerTestHelper::task_is_idle(&task); |
| 900 | }, std::chrono::milliseconds(300))); | 1231 | }, std::chrono::milliseconds(300))); |
| 901 | EXPECT_EQ(task.run_count(), 2u); | 1232 | EXPECT_EQ(task.run_count(), 2u); |
| 902 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 400u); | 1233 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 400u); |
| @@ -2499,6 +2499,26 @@ TEST_F(InterfaceBitIOTest, Read_success_without_interface_chip_no_callback) | |||
| 2499 | 2499 | ||
| 2500 | // ===== Write: bit_write 成功路径 ===== | 2500 | // ===== Write: bit_write 成功路径 ===== |
| 2501 | 2501 | ||
| 2502 | +TEST_F(InterfaceBitIOTest, memory_safety_write_rejects_short_input) | ||
| 2503 | +{ | ||
| 2504 | + auto chip = std::make_shared<mock_chip_for_bitio>(); | ||
| 2505 | + m_bitio->m_chip = chip.get(); | ||
| 2506 | + for (uint8_t length : {2, 4, 255}) { | ||
| 2507 | + SCOPED_TRACE(+length); | ||
| 2508 | + EXPECT_THROW(m_bitio->Write(0x20, length, 0xffffffffU, {0x5a}), mc::invalid_arg_exception); | ||
| 2509 | + EXPECT_EQ(chip->get_last_write_offset(), 0U); | ||
| 2510 | + } | ||
| 2511 | +} | ||
| 2512 | + | ||
| 2513 | +TEST_F(InterfaceBitIOTest, memory_safety_write_preserves_prefix_length) | ||
| 2514 | +{ | ||
| 2515 | + auto chip = std::make_shared<mock_chip_for_bitio>(); | ||
| 2516 | + m_bitio->m_chip = chip.get(); | ||
| 2517 | + EXPECT_NO_THROW(m_bitio->Write(0x20, 1, 0xff, {0x34, 0x56})); | ||
| 2518 | + EXPECT_EQ(chip->get_last_write_offset(), 0x20U); | ||
| 2519 | + EXPECT_EQ(chip->get_last_write_length(), 1U); | ||
| 2520 | +} | ||
| 2521 | + | ||
| 2502 | TEST_F(InterfaceBitIOTest, Write_success) | 2522 | TEST_F(InterfaceBitIOTest, Write_success) |
| 2503 | { | 2523 | { |
| 2504 | auto mock_chip = std::make_shared<mock_chip_for_bitio>(); | 2524 | auto mock_chip = std::make_shared<mock_chip_for_bitio>(); |
| @@ -31,6 +31,7 @@ | |||
| 31 | #include "../../../../../../drivers/internal/hasdef.h" | 31 | #include "../../../../../../drivers/internal/hasdef.h" |
| 32 | 32 | ||
| 33 | #include <atomic> | 33 | #include <atomic> |
| 34 | +#include <condition_variable> | ||
| 34 | #include <deque> | 35 | #include <deque> |
| 35 | #include <thread> | 36 | #include <thread> |
| 36 | 37 | ||
| @@ -97,6 +98,35 @@ public: | |||
| 97 | dev::csr1::Interface_Aggregate aggregate; | 98 | dev::csr1::Interface_Aggregate aggregate; |
| 98 | }; | 99 | }; |
| 99 | 100 | ||
| 101 | +struct AggregateHostDestructionGate { | ||
| 102 | + std::mutex mutex; | ||
| 103 | + std::condition_variable cv; | ||
| 104 | + bool entered = false; | ||
| 105 | + bool release = false; | ||
| 106 | +}; | ||
| 107 | + | ||
| 108 | +// Keep the Aggregate member alive after the host's final strong reference has | ||
| 109 | +// reached zero. This makes the otherwise narrow final-release/destructor | ||
| 110 | +// window deterministic for host_guard() regression coverage. | ||
| 111 | +class BlockingAggregateHostObject : public mc::engine::object<BlockingAggregateHostObject> { | ||
| 112 | +public: | ||
| 113 | + MC_OBJECT(BlockingAggregateHostObject, "BlockingAggregateHost", "/bmc/test/BlockingAggregateHost/${object_name}", | ||
| 114 | + (dev::csr1::Interface_Aggregate)) | ||
| 115 | + | ||
| 116 | + ~BlockingAggregateHostObject() | ||
| 117 | + { | ||
| 118 | + std::unique_lock<std::mutex> lock(gate->mutex); | ||
| 119 | + gate->entered = true; | ||
| 120 | + gate->cv.notify_all(); | ||
| 121 | + gate->cv.wait(lock, [this] { | ||
| 122 | + return gate->release; | ||
| 123 | + }); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + std::shared_ptr<AggregateHostDestructionGate> gate; | ||
| 127 | + dev::csr1::Interface_Aggregate aggregate; | ||
| 128 | +}; | ||
| 129 | + | ||
| 100 | // 真实互斥的总线 mock:bus_lock/bus_unlock 背后是真 mutex(与生产总线一致), | 130 | // 真实互斥的总线 mock:bus_lock/bus_unlock 背后是真 mutex(与生产总线一致), |
| 101 | // 同时暴露锁深度计数,供用例断言"读期间持锁、读后释放" | 131 | // 同时暴露锁深度计数,供用例断言"读期间持锁、读后释放" |
| 102 | class counted_locking_bus : public dev::bus_base { | 132 | class counted_locking_bus : public dev::bus_base { |
| @@ -284,6 +314,7 @@ MC_REFLECT(dev::csr1::test::WindowScannerInterface, | |||
| 284 | (ReplyFromDfx)(ReplyFromDfxFailure)(FallbackToIndependent)(ReplyFromDfxSlow)) | 314 | (ReplyFromDfx)(ReplyFromDfxFailure)(FallbackToIndependent)(ReplyFromDfxSlow)) |
| 285 | MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner"))) | 315 | MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner"))) |
| 286 | MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) | 316 | MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) |
| 317 | +MC_REFLECT(dev::csr1::test::BlockingAggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) | ||
| 287 | 318 | ||
| 288 | namespace { | 319 | namespace { |
| 289 | 320 | ||
| @@ -297,6 +328,18 @@ std::vector<uint8_t> pattern_bytes(uint32_t size) | |||
| 297 | return data; | 328 | return data; |
| 298 | } | 329 | } |
| 299 | 330 | ||
| 331 | +bool wait_for_host_release(const mc::weak_ptr<dev::csr1::test::AggregateHostObject>& host) | ||
| 332 | +{ | ||
| 333 | + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); | ||
| 334 | + while (!host.expired()) { | ||
| 335 | + if (std::chrono::steady_clock::now() >= deadline) { | ||
| 336 | + return false; | ||
| 337 | + } | ||
| 338 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 339 | + } | ||
| 340 | + return true; | ||
| 341 | +} | ||
| 342 | + | ||
| 300 | class window_regression_service : public mc::engine::service { | 343 | class window_regression_service : public mc::engine::service { |
| 301 | public: | 344 | public: |
| 302 | explicit window_regression_service(const std::string& name) : mc::engine::service(name) | 345 | explicit window_regression_service(const std::string& name) : mc::engine::service(name) |
| @@ -826,12 +869,15 @@ TEST_F(AggregateWindowRegressionTest, follower_degrades_after_master_destroyed) | |||
| 826 | m_agg_b->start(); | 869 | m_agg_b->start(); |
| 827 | EXPECT_EQ(m_agg_b->m_master_aggregate, agg_a); | 870 | EXPECT_EQ(m_agg_b->m_master_aggregate, agg_a); |
| 828 | 871 | ||
| 872 | + mc::weak_ptr<dev::csr1::test::AggregateHostObject> master_host = m_hosts.front(); | ||
| 829 | { | 873 | { |
| 830 | // 宿主连同聚合一起析构并注销(模拟芯片卸载):本用例的首个宿主即 master 宿主 | 874 | // 宿主连同聚合一起析构并注销(模拟芯片卸载):本用例的首个宿主即 master 宿主 |
| 831 | auto host_a = m_hosts.front(); | 875 | auto host_a = m_hosts.front(); |
| 832 | m_service->unregister_object(std::string(host_a->get_object_path())); | 876 | m_service->unregister_object(std::string(host_a->get_object_path())); |
| 833 | m_hosts.erase(m_hosts.begin()); | 877 | m_hosts.erase(m_hosts.begin()); |
| 834 | } // master 析构并注销 | 878 | } // master 析构并注销 |
| 879 | + // Engine unregister callbacks can briefly retain the host asynchronously. | ||
| 880 | + ASSERT_TRUE(wait_for_host_release(master_host)); | ||
| 835 | 881 | ||
| 836 | // follower 不再向已析构 master 转发:回放迁移旧 entry + 新注册落自身 dispatcher | 882 | // follower 不再向已析构 master 转发:回放迁移旧 entry + 新注册落自身 dispatcher |
| 837 | m_agg_b->AddScanner("AfterDeathScanner", 0, 1, 0, 0xFF, 0); | 883 | m_agg_b->AddScanner("AfterDeathScanner", 0, 1, 0, 0xFF, 0); |
| @@ -843,6 +889,50 @@ TEST_F(AggregateWindowRegressionTest, follower_degrades_after_master_destroyed) | |||
| 843 | EXPECT_EQ(m_chip_b->read_records().size(), 1u); | 889 | EXPECT_EQ(m_chip_b->read_records().size(), 1u); |
| 844 | } | 890 | } |
| 845 | 891 | ||
| 892 | +TEST_F(AggregateWindowRegressionTest, host_guard_refuses_owner_during_final_release) | ||
| 893 | +{ | ||
| 894 | + auto gate = std::make_shared<dev::csr1::test::AggregateHostDestructionGate>(); | ||
| 895 | + auto host = mc::make_shared<dev::csr1::test::BlockingAggregateHostObject>(); | ||
| 896 | + host->gate = gate; | ||
| 897 | + host->set_object_name("FinalReleaseHost"); | ||
| 898 | + | ||
| 899 | + m_chip_a = std::make_shared<dev::csr1::test::windowed_mock_chip>("Chip_FPGA_1_01010315", 0x70); | ||
| 900 | + auto* aggregate = &host->aggregate; | ||
| 901 | + aggregate->m_chip = m_chip_a.get(); | ||
| 902 | + aggregate->set_owner_object(host.get()); | ||
| 903 | + m_service->register_object(host); | ||
| 904 | + aggregate->init(336, 16, 60000); | ||
| 905 | + aggregate->AddScanner("FinalReleaseScanner", 4, 2, 0, 0xFFFF, 0); | ||
| 906 | + aggregate->start(); | ||
| 907 | + | ||
| 908 | + m_service->unregister_object(std::string(host->get_object_path())); | ||
| 909 | + std::thread destroyer([host = std::move(host)]() mutable { | ||
| 910 | + host.reset(); | ||
| 911 | + }); | ||
| 912 | + | ||
| 913 | + bool destructor_entered = false; | ||
| 914 | + { | ||
| 915 | + std::unique_lock<std::mutex> lock(gate->mutex); | ||
| 916 | + destructor_entered = gate->cv.wait_for(lock, std::chrono::seconds(5), [&gate] { | ||
| 917 | + return gate->entered; | ||
| 918 | + }); | ||
| 919 | + } | ||
| 920 | + | ||
| 921 | + if (destructor_entered) { | ||
| 922 | + // The raw owner still points at live storage, but its strong count is | ||
| 923 | + // already zero. Retention must use a non-throwing weak upgrade. | ||
| 924 | + EXPECT_EQ(aggregate->host_guard(), nullptr); | ||
| 925 | + } | ||
| 926 | + | ||
| 927 | + { | ||
| 928 | + std::lock_guard<std::mutex> lock(gate->mutex); | ||
| 929 | + gate->release = true; | ||
| 930 | + } | ||
| 931 | + gate->cv.notify_all(); | ||
| 932 | + destroyer.join(); | ||
| 933 | + ASSERT_TRUE(destructor_entered); | ||
| 934 | +} | ||
| 935 | + | ||
| 846 | // ===== 8.6 读通道:Aggregate 共享读与 Scanner 独立读共用总线互斥锁 ===== | 936 | // ===== 8.6 读通道:Aggregate 共享读与 Scanner 独立读共用总线互斥锁 ===== |
| 847 | 937 | ||
| 848 | TEST_F(AggregateWindowRegressionTest, aggregate_tick_holds_bus_lock_during_read_and_releases) | 938 | TEST_F(AggregateWindowRegressionTest, aggregate_tick_holds_bus_lock_during_read_and_releases) |
| @@ -936,11 +1026,13 @@ TEST_F(AggregateWindowRegressionTest, master_destroyed_existing_scanners_migrate | |||
| 936 | EXPECT_EQ(m_agg_b->dispatcher()->entry_count(), 0u); | 1026 | EXPECT_EQ(m_agg_b->dispatcher()->entry_count(), 0u); |
| 937 | 1027 | ||
| 938 | // master 宿主析构(模拟芯片卸载) | 1028 | // master 宿主析构(模拟芯片卸载) |
| 1029 | + mc::weak_ptr<dev::csr1::test::AggregateHostObject> master_host = m_hosts.front(); | ||
| 939 | { | 1030 | { |
| 940 | auto host_a = m_hosts.front(); | 1031 | auto host_a = m_hosts.front(); |
| 941 | m_service->unregister_object(std::string(host_a->get_object_path())); | 1032 | m_service->unregister_object(std::string(host_a->get_object_path())); |
| 942 | m_hosts.erase(m_hosts.begin()); | 1033 | m_hosts.erase(m_hosts.begin()); |
| 943 | } | 1034 | } |
| 1035 | + ASSERT_TRUE(wait_for_host_release(master_host)); | ||
| 944 | 1036 | ||
| 945 | // follower 继续 tick:已转发的 entry 迁移回自身 dispatcher,原 Scanner 仍被供数 | 1037 | // follower 继续 tick:已转发的 entry 迁移回自身 dispatcher,原 Scanner 仍被供数 |
| 946 | m_agg_b->start(); | 1038 | m_agg_b->start(); |
| @@ -28,7 +28,22 @@ | |||
| 28 | #include <string> | 28 | #include <string> |
| 29 | #include <vector> | 29 | #include <vector> |
| 30 | 30 | ||
| 31 | -using dev::test::Csr1ChipObjectTestBase; | 31 | +// Only the complex chip owns aggregate scheduler tasks. Keep this cleanup in |
| 32 | +// its fixture so other chip tests need not link the complex scheduler library. | ||
| 33 | +class Csr1ChipObjectTestBase : public dev::test::Csr1ChipObjectTestBase { | ||
| 34 | +protected: | ||
| 35 | + static void TearDownTestSuite() | ||
| 36 | + { | ||
| 37 | + dev::csr1::Scheduler::get_instance().clear(); | ||
| 38 | + dev::test::Csr1ChipObjectTestBase::TearDownTestSuite(); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + void TearDown() override | ||
| 42 | + { | ||
| 43 | + dev::csr1::Scheduler::get_instance().clear(); | ||
| 44 | + dev::test::Csr1ChipObjectTestBase::TearDown(); | ||
| 45 | + } | ||
| 46 | +}; | ||
| 32 | 47 | ||
| 33 | class mock_chip_bus : public dev::bus_base { | 48 | class mock_chip_bus : public dev::bus_base { |
| 34 | public: | 49 | public: |
| @@ -33,6 +33,7 @@ | |||
| 33 | #include <gtest/gtest.h> | 33 | #include <gtest/gtest.h> |
| 34 | 34 | ||
| 35 | #include <chrono> | 35 | #include <chrono> |
| 36 | +#include <future> | ||
| 36 | #include <memory> | 37 | #include <memory> |
| 37 | #include <string> | 38 | #include <string> |
| 38 | #include <thread> | 39 | #include <thread> |
| @@ -255,4 +256,129 @@ TEST_F(ChipWatchdogTest, real_watchdog_timer_pauses_and_resumes_within_poll_wind | |||
| 255 | EXPECT_TRUE(events->at(1)); | 256 | EXPECT_TRUE(events->at(1)); |
| 256 | } | 257 | } |
| 257 | 258 | ||
| 259 | +TEST_F(ChipWatchdogTest, cancelled_and_replaced_access_timers_do_not_retain_interface_parent) | ||
| 260 | +{ | ||
| 261 | + auto chip = make_chip("timer_access_cancel"); | ||
| 262 | + auto& iface = chip->m_interface_chip; | ||
| 263 | + iface.SetAccessibility(false, 1800); | ||
| 264 | + auto first = iface.m_access_state->access_timer; | ||
| 265 | + ASSERT_NE(first, nullptr); | ||
| 266 | + iface.SetAccessibility(false, 1800); | ||
| 267 | + auto second = iface.m_access_state->access_timer; | ||
| 268 | + EXPECT_NE(first, second); | ||
| 269 | + EXPECT_FALSE(first->is_active()); | ||
| 270 | + EXPECT_EQ(first->get_parent(), nullptr); | ||
| 271 | + iface.SetAccessibility(true, 0); | ||
| 272 | + EXPECT_FALSE(second->is_active()); | ||
| 273 | + EXPECT_EQ(second->get_parent(), nullptr); | ||
| 274 | + chip.reset(); | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +TEST_F(ChipWatchdogTest, completed_access_timer_has_no_interface_parent) | ||
| 278 | +{ | ||
| 279 | + auto chip = make_chip("timer_access_complete"); | ||
| 280 | + auto& iface = chip->m_interface_chip; | ||
| 281 | + iface.SetAccessibility(false, 1800); | ||
| 282 | + auto timer = iface.m_access_state->access_timer; | ||
| 283 | + ASSERT_NE(timer, nullptr); | ||
| 284 | + timer->stop(); | ||
| 285 | + timer->timeout(); | ||
| 286 | + EXPECT_TRUE(iface.m_access_state->access_enabled.load()); | ||
| 287 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 288 | + chip.reset(); | ||
| 289 | +} | ||
| 290 | + | ||
| 291 | +TEST_F(ChipWatchdogTest, destroyed_interface_stops_retained_watchdog) | ||
| 292 | +{ | ||
| 293 | + auto chip = make_chip("timer_watchdog_destroy"); | ||
| 294 | + auto timer = chip->m_interface_chip.m_access_watchdog; | ||
| 295 | + ASSERT_NE(timer, nullptr); | ||
| 296 | + chip.reset(); | ||
| 297 | + EXPECT_FALSE(timer->is_active()); | ||
| 298 | + EXPECT_EQ(timer->get_parent(), nullptr); | ||
| 299 | + timer->stop(); | ||
| 300 | +} | ||
| 301 | + | ||
| 302 | +TEST_F(ChipWatchdogTest, access_and_lock_timers_are_safe_to_release_after_interface_destruction) | ||
| 303 | +{ | ||
| 304 | + auto chip = make_chip("timer_access_lock_destroy"); | ||
| 305 | + auto bus = std::make_shared<test_watchdog_bus>(); | ||
| 306 | + attach_bus(*chip, bus); | ||
| 307 | + auto& iface = chip->m_interface_chip; | ||
| 308 | + iface.SetAccessibility(false, 1800); | ||
| 309 | + auto access_timer = iface.m_access_state->access_timer; | ||
| 310 | + mc::engine::context context; | ||
| 311 | + context.set_arg("Requestor", std::string("timer_test")); | ||
| 312 | + ASSERT_EQ(iface.SetLockStatus(context, 1, 1800), 0); | ||
| 313 | + auto lock_timer = bus->get_bus_lock_info()->lock_timer; | ||
| 314 | + // Retain timers past interface destruction, as queued work can do. | ||
| 315 | + EXPECT_EQ(access_timer->get_parent(), nullptr); | ||
| 316 | + EXPECT_EQ(lock_timer->get_parent(), nullptr); | ||
| 317 | + chip.reset(); | ||
| 318 | + EXPECT_FALSE(access_timer->is_active()); | ||
| 319 | + EXPECT_FALSE(lock_timer->is_active()); | ||
| 320 | + EXPECT_EQ(bus->get_bus_lock_info(), nullptr); | ||
| 321 | +} | ||
| 322 | + | ||
| 323 | +TEST_F(ChipWatchdogTest, renewed_unlocked_and_completed_lock_timers_have_no_interface_parent) | ||
| 324 | +{ | ||
| 325 | + auto chip = make_chip("timer_lock_replace"); | ||
| 326 | + auto bus = std::make_shared<test_watchdog_bus>(); | ||
| 327 | + attach_bus(*chip, bus); | ||
| 328 | + auto& iface = chip->m_interface_chip; | ||
| 329 | + mc::engine::context context; | ||
| 330 | + context.set_arg("Requestor", std::string("timer_test")); | ||
| 331 | + ASSERT_EQ(iface.SetLockStatus(context, 1, 1800), 0); | ||
| 332 | + auto first = bus->get_bus_lock_info()->lock_timer; | ||
| 333 | + ASSERT_EQ(iface.SetLockStatus(context, 1, 1800), 0); | ||
| 334 | + auto second = bus->get_bus_lock_info()->lock_timer; | ||
| 335 | + EXPECT_NE(first, second); | ||
| 336 | + EXPECT_FALSE(first->is_active()); | ||
| 337 | + EXPECT_EQ(first->get_parent(), nullptr); | ||
| 338 | + ASSERT_EQ(iface.SetLockStatus(context, 0, 0), 0); | ||
| 339 | + EXPECT_FALSE(second->is_active()); | ||
| 340 | + EXPECT_EQ(second->get_parent(), nullptr); | ||
| 341 | + | ||
| 342 | + ASSERT_EQ(iface.SetLockStatus(context, 1, 1800), 0); | ||
| 343 | + auto completed = bus->get_bus_lock_info()->lock_timer; | ||
| 344 | + completed->stop(); | ||
| 345 | + completed->timeout(); | ||
| 346 | + EXPECT_EQ(bus->get_bus_lock_info(), nullptr); | ||
| 347 | + EXPECT_EQ(iface.LockStatus.get_value().as<uint8_t>(), 0); | ||
| 348 | + EXPECT_EQ(completed->get_parent(), nullptr); | ||
| 349 | + chip.reset(); | ||
| 350 | +} | ||
| 351 | + | ||
| 352 | +TEST_F(ChipWatchdogTest, interface_timers_use_owner_executor) | ||
| 353 | +{ | ||
| 354 | + auto chip = make_chip("timer_owner_executor"); | ||
| 355 | + auto bus = std::make_shared<test_watchdog_bus>(); | ||
| 356 | + attach_bus(*chip, bus); | ||
| 357 | + auto& iface = chip->m_interface_chip; | ||
| 358 | + // Recreate the watchdog after assigning the executor, matching production | ||
| 359 | + // initialization where the owner is attached before its interface starts. | ||
| 360 | + iface.m_access_watchdog->stop(); | ||
| 361 | + iface.m_access_watchdog.reset(); | ||
| 362 | + mc::any_executor owner_executor{mc::runtime::runtime_strand{}}; | ||
| 363 | + chip->bind_executor(owner_executor); | ||
| 364 | + iface.start_access_watchdog(); | ||
| 365 | + iface.SetAccessibility(false, 1800); | ||
| 366 | + mc::engine::context context; | ||
| 367 | + context.set_arg("Requestor", std::string("timer_test")); | ||
| 368 | + ASSERT_EQ(iface.SetLockStatus(context, 1, 1800), 0); | ||
| 369 | + auto lock_timer = bus->get_bus_lock_info()->lock_timer; | ||
| 370 | + for (auto timer : {iface.m_access_watchdog, iface.m_access_state->access_timer, lock_timer}) { | ||
| 371 | + auto result = std::make_shared<std::promise<bool>>(); | ||
| 372 | + auto ready = result->get_future(); | ||
| 373 | + timer->timeout.connect([owner_executor, result]() { | ||
| 374 | + result->set_value(owner_executor.running_in_this_thread()); | ||
| 375 | + }); | ||
| 376 | + timer->set_single_shot(true); | ||
| 377 | + timer->start(mc::milliseconds(1)); | ||
| 378 | + ASSERT_EQ(ready.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 379 | + EXPECT_TRUE(ready.get()); | ||
| 380 | + timer->stop(); | ||
| 381 | + } | ||
| 382 | +} | ||
| 383 | + | ||
| 258 | } // namespace | 384 | } // namespace |
| @@ -1583,6 +1583,45 @@ TEST_F(Csr1ScannerTest, dfx_value_is_not_fanned_out_to_merge_members) | |||
| 1583 | drain_io_queue(); | 1583 | drain_io_queue(); |
| 1584 | } | 1584 | } |
| 1585 | 1585 | ||
| 1586 | +TEST_F(Csr1ScannerTest, stopped_timers_do_not_retain_embedded_interface_parent) | ||
| 1587 | +{ | ||
| 1588 | + auto csr = create_valid_scanner_csr(); | ||
| 1589 | + ASSERT_TRUE(m_scanner->init(csr, mc::dict{})); | ||
| 1590 | + ASSERT_TRUE(m_scanner->start()); | ||
| 1591 | + | ||
| 1592 | + // A queued cancellation completion can retain either timer beyond stop(). | ||
| 1593 | + auto startup_timer = m_scanner->i_scanner.m_startup_delay_timer; | ||
| 1594 | + auto scan_timer = m_scanner->i_scanner.m_scanner_timer; | ||
| 1595 | + ASSERT_NE(startup_timer, nullptr); | ||
| 1596 | + ASSERT_NE(scan_timer, nullptr); | ||
| 1597 | + ASSERT_TRUE(m_scanner->stop()); | ||
| 1598 | + | ||
| 1599 | + EXPECT_EQ(startup_timer->get_parent(), nullptr); | ||
| 1600 | + EXPECT_EQ(scan_timer->get_parent(), nullptr); | ||
| 1601 | + m_scanner.reset(); | ||
| 1602 | + startup_timer.reset(); | ||
| 1603 | + scan_timer.reset(); | ||
| 1604 | +} | ||
| 1605 | + | ||
| 1606 | +TEST_F(Csr1ScannerTest, replaced_timers_do_not_retain_embedded_interface_parent) | ||
| 1607 | +{ | ||
| 1608 | + auto csr = create_valid_scanner_csr(); | ||
| 1609 | + ASSERT_TRUE(m_scanner->init(csr, mc::dict{})); | ||
| 1610 | + ASSERT_TRUE(m_scanner->start()); | ||
| 1611 | + auto startup_timer = m_scanner->i_scanner.m_startup_delay_timer; | ||
| 1612 | + auto scan_timer = m_scanner->i_scanner.m_scanner_timer; | ||
| 1613 | + ASSERT_NE(startup_timer, nullptr); | ||
| 1614 | + ASSERT_NE(scan_timer, nullptr); | ||
| 1615 | + | ||
| 1616 | + m_scanner->i_scanner.init_startup_delay_timer(); | ||
| 1617 | + ASSERT_TRUE(m_scanner->start()); | ||
| 1618 | + | ||
| 1619 | + EXPECT_EQ(startup_timer->get_parent(), nullptr); | ||
| 1620 | + EXPECT_EQ(scan_timer->get_parent(), nullptr); | ||
| 1621 | + EXPECT_NE(m_scanner->i_scanner.m_startup_delay_timer, startup_timer); | ||
| 1622 | + EXPECT_NE(m_scanner->i_scanner.m_scanner_timer, scan_timer); | ||
| 1623 | +} | ||
| 1624 | + | ||
| 1586 | TEST_F(ScannerMergeRegistryTest, unregister_master_with_no_followers_erases_group) | 1625 | TEST_F(ScannerMergeRegistryTest, unregister_master_with_no_followers_erases_group) |
| 1587 | { | 1626 | { |
| 1588 | auto& reg = dev::csr1::ScannerMergeRegistry::instance(); | 1627 | auto& reg = dev::csr1::ScannerMergeRegistry::instance(); |
| @@ -1732,7 +1732,7 @@ TEST_F(Csr1SmcDfxInfoTest, owner_guard_keeps_owner_alive_across_concurrent_relea | |||
| 1732 | // 回归(真实 due 快照路径):经 Scheduler::on_timer 的完整 Phase1/Phase2 执行, | 1732 | // 回归(真实 due 快照路径):经 Scheduler::on_timer 的完整 Phase1/Phase2 执行, |
| 1733 | // tick 内(mock BlockIO.Read 注入点,等价于同步调用递归 poll runtime 的重入窗口) | 1733 | // tick 内(mock BlockIO.Read 注入点,等价于同步调用递归 poll runtime 的重入窗口) |
| 1734 | // 对象被注销且本地强引用释放,due 快照的 owner_guard 保活整个批次; | 1734 | // 对象被注销且本地强引用释放,due 快照的 owner_guard 保活整个批次; |
| 1735 | -// on_timer 返回(快照销毁)后对象才析构。 | 1735 | +// on_timer 只投递任务;异步硬件访问完成后才允许对象析构。 |
| 1736 | TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | 1736 | TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) |
| 1737 | { | 1737 | { |
| 1738 | auto obj = mc::make_shared<dev::csr1::SmcDfxInfo_object>(); | 1738 | auto obj = mc::make_shared<dev::csr1::SmcDfxInfo_object>(); |
| @@ -1746,8 +1746,9 @@ TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | |||
| 1746 | 1746 | ||
| 1747 | mc::weak_ptr<dev::csr1::SmcDfxInfo_object> tracked = obj; | 1747 | mc::weak_ptr<dev::csr1::SmcDfxInfo_object> tracked = obj; |
| 1748 | 1748 | ||
| 1749 | - auto& sched = dev::csr1::Scheduler::get_instance(); | 1749 | + auto& sched = dev::csr1::Scheduler::get_instance(); |
| 1750 | - auto* disp = &obj->get_dispatcher(); | 1750 | + auto* disp = &obj->get_dispatcher(); |
| 1751 | + std::shared_ptr<dev::csr1::Scheduler::RunState> run_state; | ||
| 1751 | 1752 | ||
| 1752 | // tick 内注入"卸载重入":注销(teardown 清理)+ 释放本地强引用, | 1753 | // tick 内注入"卸载重入":注销(teardown 清理)+ 释放本地强引用, |
| 1753 | // 模拟递归 poll runtime 中卸载流程释放对象的全部非快照引用。 | 1754 | // 模拟递归 poll runtime 中卸载流程释放对象的全部非快照引用。 |
| @@ -1767,12 +1768,24 @@ TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | |||
| 1767 | for (auto& e : sched.m_entries) { | 1768 | for (auto& e : sched.m_entries) { |
| 1768 | if (e.task == disp) { | 1769 | if (e.task == disp) { |
| 1769 | e.next_run_ms = 0; | 1770 | e.next_run_ms = 0; |
| 1771 | + run_state = e.run_state; | ||
| 1770 | } | 1772 | } |
| 1771 | } | 1773 | } |
| 1772 | } | 1774 | } |
| 1775 | + ASSERT_NE(run_state, nullptr); | ||
| 1773 | EXPECT_NO_THROW(sched.on_timer()); | 1776 | EXPECT_NO_THROW(sched.on_timer()); |
| 1774 | 1777 | ||
| 1775 | - // 批次结束(快照销毁)后对象析构 | 1778 | + // 等待实际任务完成,不能把 on_timer 返回当作硬件访问已经结束。 |
| 1779 | + { | ||
| 1780 | + std::unique_lock<std::mutex> lock(run_state->mutex); | ||
| 1781 | + ASSERT_TRUE(run_state->cv.wait_for(lock, std::chrono::seconds(5), [&] { | ||
| 1782 | + return !run_state->running.load(); | ||
| 1783 | + })); | ||
| 1784 | + } | ||
| 1785 | + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); | ||
| 1786 | + while (!tracked.expired() && std::chrono::steady_clock::now() < deadline) { | ||
| 1787 | + std::this_thread::yield(); | ||
| 1788 | + } | ||
| 1776 | EXPECT_TRUE(tracked.expired()); | 1789 | EXPECT_TRUE(tracked.expired()); |
| 1777 | 1790 | ||
| 1778 | m_chip->blockio.on_read = nullptr; | 1791 | m_chip->blockio.on_read = nullptr; |
| @@ -386,4 +386,30 @@ TEST_F(BusCanTest, extend_read_invalid_read_length_throws) | |||
| 386 | } | 386 | } |
| 387 | } | 387 | } |
| 388 | 388 | ||
| 389 | +TEST_F(BusCanTest, memory_safety_write_rejects_oversized_source_length) | ||
| 390 | +{ | ||
| 391 | + mc::mutable_dict csr; | ||
| 392 | + ASSERT_TRUE(m_bus.init(csr)); | ||
| 393 | + for (uint32_t length : {2U, 4U, 255U}) { | ||
| 394 | + SCOPED_TRACE(length); | ||
| 395 | + ACCESS_DATA_S data{}; | ||
| 396 | + data.in_buffer = {0x5a}; | ||
| 397 | + data.length = length; | ||
| 398 | + EXPECT_THROW(m_bus.write(data), mc::invalid_op_exception); | ||
| 399 | + EXPECT_EQ(DRIVER_CANBUS::test_state().last_write_len, 0U); | ||
| 400 | + DRIVER_CANBUS::reset_test_state(); | ||
| 401 | + } | ||
| 402 | +} | ||
| 403 | + | ||
| 404 | +TEST_F(BusCanTest, memory_safety_write_preserves_explicit_prefix_length) | ||
| 405 | +{ | ||
| 406 | + mc::mutable_dict csr; | ||
| 407 | + ASSERT_TRUE(m_bus.init(csr)); | ||
| 408 | + ACCESS_DATA_S data{}; | ||
| 409 | + data.in_buffer = {0x34, 0x56}; | ||
| 410 | + data.length = 1; | ||
| 411 | + EXPECT_EQ(m_bus.write(data), HAS_OK); | ||
| 412 | + EXPECT_EQ(DRIVER_CANBUS::test_state().last_write_len, 1U); | ||
| 413 | +} | ||
| 414 | + | ||
| 389 | } // namespace | 415 | } // namespace |
| @@ -619,4 +619,49 @@ TEST_F(bus_i2c_test, init_skips_iocfg_when_multiio_missing) | |||
| 619 | EXPECT_EQ(DRIVER_COMM::comm_test_state().iocfg_config_count, 0); | 619 | EXPECT_EQ(DRIVER_COMM::comm_test_state().iocfg_config_count, 0); |
| 620 | } | 620 | } |
| 621 | 621 | ||
| 622 | +// The transmit source length is independent of the requested reply length. | ||
| 623 | +TEST_F(bus_i2c_test, memory_safety_read_uses_actual_input_size) | ||
| 624 | +{ | ||
| 625 | + ACCESS_DATA_S data{}; | ||
| 626 | + data.addr = 0x70; | ||
| 627 | + data.offset_width = 1; | ||
| 628 | + data.offset = 0x12; | ||
| 629 | + data.in_buffer = {0x34, 0x56}; | ||
| 630 | + for (uint32_t read_length : {0U, 1U, 2U, 4U, 255U}) { | ||
| 631 | + SCOPED_TRACE(read_length); | ||
| 632 | + data.length = read_length; | ||
| 633 | + EXPECT_EQ(std::string(m_bus.format_tx_buf(data)), std::string("\x70\x12\x34\x56", 4)); | ||
| 634 | + } | ||
| 635 | + data.in_buffer.clear(); | ||
| 636 | + EXPECT_EQ(std::string(m_bus.format_tx_buf(data)), std::string("\x70\x12", 2)); | ||
| 637 | +} | ||
| 638 | + | ||
| 639 | +TEST_F(bus_i2c_test, memory_safety_write_rejects_oversized_source_length) | ||
| 640 | +{ | ||
| 641 | + mc::mutable_dict csr; | ||
| 642 | + ASSERT_TRUE(m_bus.init(csr)); | ||
| 643 | + for (uint32_t length : {2U, 4U, 255U}) { | ||
| 644 | + SCOPED_TRACE(length); | ||
| 645 | + ACCESS_DATA_S data{}; | ||
| 646 | + data.addr = 0x70; | ||
| 647 | + data.length = length; | ||
| 648 | + data.in_buffer = {0x5a}; | ||
| 649 | + EXPECT_THROW(m_bus.write(data), mc::invalid_arg_exception); | ||
| 650 | + } | ||
| 651 | +} | ||
| 652 | + | ||
| 653 | +TEST_F(bus_i2c_test, memory_safety_write_preserves_explicit_prefix_length) | ||
| 654 | +{ | ||
| 655 | + mc::mutable_dict csr; | ||
| 656 | + ASSERT_TRUE(m_bus.init(csr)); | ||
| 657 | + ACCESS_DATA_S data{}; | ||
| 658 | + data.addr = 0x70; | ||
| 659 | + data.offset_width = 1; | ||
| 660 | + data.offset = 0x12; | ||
| 661 | + data.length = 1; | ||
| 662 | + data.in_buffer = {0x34, 0x56}; | ||
| 663 | + ASSERT_EQ(m_bus.write(data), HAS_OK); | ||
| 664 | + EXPECT_EQ(m_bus.m_tx_buffer, std::string("\x70\x12\x34", 3)); | ||
| 665 | +} | ||
| 666 | + | ||
| 622 | } // namespace | 667 | } // namespace |
| @@ -895,6 +895,20 @@ TEST_F(chip_canbus_chip_can_bus_test, bit_read_wrong_bus_type_returns_err) | |||
| 895 | // bit_write (offset: bits 31-16 = cmd, bits 15-0 = sig_id) | 895 | // bit_write (offset: bits 31-16 = cmd, bits 15-0 = sig_id) |
| 896 | // =========================================================================== | 896 | // =========================================================================== |
| 897 | 897 | ||
| 898 | +TEST_F(chip_canbus_chip_can_bus_test, memory_safety_bit_write_rejects_short_input) | ||
| 899 | +{ | ||
| 900 | + auto bus = attach_can_bus(); | ||
| 901 | + for (uint8_t length : {1, 2, 4, 6}) { | ||
| 902 | + for (size_t size = 0; size < length; ++size) { | ||
| 903 | + SCOPED_TRACE(::testing::Message() << +length << "/" << size); | ||
| 904 | + std::vector<uint8_t> input(size, 0x5a), output; | ||
| 905 | + std::string error; | ||
| 906 | + EXPECT_EQ(m_chip->bit_write(0x00011234, length, 0xff, input, output, error), HAS_ERR); | ||
| 907 | + EXPECT_FALSE(error.empty()); | ||
| 908 | + } | ||
| 909 | + } | ||
| 910 | +} | ||
| 911 | + | ||
| 898 | TEST_F(chip_canbus_chip_can_bus_test, bit_write_length2_success) | 912 | TEST_F(chip_canbus_chip_can_bus_test, bit_write_length2_success) |
| 899 | { | 913 | { |
| 900 | auto bus = attach_can_bus(); | 914 | auto bus = attach_can_bus(); |
| @@ -1067,4 +1067,60 @@ TEST_F(chip_topo_path_test, topo_path_different_addr_are_different) | |||
| 1067 | EXPECT_NE(m_chip1->get_topo_path(), m_chip3->get_topo_path()); | 1067 | EXPECT_NE(m_chip1->get_topo_path(), m_chip3->get_topo_path()); |
| 1068 | } | 1068 | } |
| 1069 | 1069 | ||
| 1070 | +TEST_F(chip_base_test, memory_safety_bit_read_rejects_every_short_reply) | ||
| 1071 | +{ | ||
| 1072 | + m_chip->set_left_bus(m_bus); | ||
| 1073 | + m_chip->set_host_bus(m_bus); | ||
| 1074 | + for (uint8_t width : {1, 2, 4}) { | ||
| 1075 | + for (size_t received = 0; received < width; ++received) { | ||
| 1076 | + for (uint32_t mask : {0U, 0xffffffffU}) { | ||
| 1077 | + SCOPED_TRACE(::testing::Message() << +width << "/" << received << "/" << mask); | ||
| 1078 | + m_bus->add_read_payload(std::vector<uint8_t>(received, 0x5a)); | ||
| 1079 | + std::vector<uint8_t> input; | ||
| 1080 | + // A preallocated caller buffer must not disguise an empty reply. | ||
| 1081 | + std::vector<uint8_t> output(width, 0xff); | ||
| 1082 | + std::string error; | ||
| 1083 | + EXPECT_EQ(m_chip->bit_read(0x10, width, mask, input, output, error), HAS_ERR); | ||
| 1084 | + EXPECT_FALSE(error.empty()); | ||
| 1085 | + } | ||
| 1086 | + } | ||
| 1087 | + } | ||
| 1088 | +} | ||
| 1089 | + | ||
| 1090 | +TEST_F(chip_base_test, memory_safety_shifted_bus_preserves_value_and_rejects_short_reply) | ||
| 1091 | +{ | ||
| 1092 | + m_chip->set_left_bus(m_bus); | ||
| 1093 | + m_chip->set_host_bus(m_bus); | ||
| 1094 | + m_bus->has_shifted_bit_read = true; | ||
| 1095 | + std::vector<uint8_t> input, output; | ||
| 1096 | + std::string error; | ||
| 1097 | + | ||
| 1098 | + // LocalBus has already extracted (register & 0xf0) >> 4. | ||
| 1099 | + m_bus->add_read_payload({3, 0, 0, 0}); | ||
| 1100 | + ASSERT_EQ(m_chip->bit_read(0x10, 4, 0xf0, input, output, error), HAS_OK); | ||
| 1101 | + EXPECT_EQ(output, (std::vector<uint8_t>{3, 0, 0, 0})); | ||
| 1102 | + | ||
| 1103 | + // A shifted response must still contain the requested number of bytes. | ||
| 1104 | + m_bus->add_read_payload({3}); | ||
| 1105 | + EXPECT_EQ(m_chip->bit_read(0x10, 4, 0xf0, input, output, error), HAS_ERR); | ||
| 1106 | + EXPECT_TRUE(output.empty()); | ||
| 1107 | + EXPECT_FALSE(error.empty()); | ||
| 1108 | +} | ||
| 1109 | + | ||
| 1110 | +TEST_F(chip_base_test, memory_safety_bit_write_rejects_short_input_before_bus) | ||
| 1111 | +{ | ||
| 1112 | + m_chip->set_left_bus(m_bus); | ||
| 1113 | + m_chip->set_host_bus(m_bus); | ||
| 1114 | + for (uint8_t width : {1, 2, 4}) { | ||
| 1115 | + for (size_t provided = 0; provided < width; ++provided) { | ||
| 1116 | + SCOPED_TRACE(::testing::Message() << +width << "/" << provided); | ||
| 1117 | + std::vector<uint8_t> input(provided, 0x5a), output; | ||
| 1118 | + std::string error; | ||
| 1119 | + EXPECT_EQ(m_chip->bit_write(0x10, width, 0xffffffffU, input, output, error), HAS_ERR); | ||
| 1120 | + EXPECT_FALSE(error.empty()); | ||
| 1121 | + EXPECT_EQ(m_bus->m_write_call_count, 0); | ||
| 1122 | + } | ||
| 1123 | + } | ||
| 1124 | +} | ||
| 1125 | + | ||
| 1070 | } // namespace | 1126 | } // namespace |
| @@ -320,12 +320,13 @@ static std::vector<uint8_t> smc_read_frame(uint8_t addr, uint8_t cmd, const std: | |||
| 320 | class chip_smc_exposed : public dev::chip_smc { | 320 | class chip_smc_exposed : public dev::chip_smc { |
| 321 | public: | 321 | public: |
| 322 | using dev::chip_smc::forward_by_front_chip; | 322 | using dev::chip_smc::forward_by_front_chip; |
| 323 | + using dev::chip_smc::forward_read; | ||
| 323 | }; | 324 | }; |
| 324 | 325 | ||
| 325 | -static std::shared_ptr<smc_test_bus> init_exposed_chip(chip_smc_exposed& chip) | 326 | +static std::shared_ptr<smc_test_bus> init_exposed_chip(chip_smc_exposed& chip, uint8_t address = 0x10) |
| 326 | { | 327 | { |
| 327 | mc::mutable_dict csr; | 328 | mc::mutable_dict csr; |
| 328 | - csr["Address"] = static_cast<uint32_t>(0x10); | 329 | + csr["Address"] = static_cast<uint32_t>(address); |
| 329 | csr["OffsetWidth"] = static_cast<uint8_t>(1); | 330 | csr["OffsetWidth"] = static_cast<uint8_t>(1); |
| 330 | csr["AddrWidth"] = static_cast<uint8_t>(1); | 331 | csr["AddrWidth"] = static_cast<uint8_t>(1); |
| 331 | csr["object_name"] = std::string("smc_test"); | 332 | csr["object_name"] = std::string("smc_test"); |
| @@ -408,6 +409,32 @@ TEST_F(chip_smc_test, bit_read_para_overflow_error) | |||
| 408 | EXPECT_NE(error.find("para"), std::string::npos); | 409 | EXPECT_NE(error.find("para"), std::string::npos); |
| 409 | } | 410 | } |
| 410 | 411 | ||
| 412 | +TEST_F(chip_smc_test, bit_read_rejects_zero_response_length_before_offset) | ||
| 413 | +{ | ||
| 414 | + auto bus = bind_test_bus(); | ||
| 415 | + bus->enqueue_read_payload({0x00, 0x00}); | ||
| 416 | + | ||
| 417 | + std::vector<uint8_t> in, out; | ||
| 418 | + std::string error; | ||
| 419 | + EXPECT_EQ(m_chip.bit_read(0x21FF, 1, 0xFF, in, out, error), HAS_ERR); | ||
| 420 | + EXPECT_NE(error.find("length"), std::string::npos); | ||
| 421 | + EXPECT_TRUE(out.empty()); | ||
| 422 | +} | ||
| 423 | + | ||
| 424 | +TEST_F(chip_smc_test, bit_read_rejects_truncated_response) | ||
| 425 | +{ | ||
| 426 | + auto bus = bind_test_bus(); | ||
| 427 | + // The old parser padded the missing data/CRC with zero; that padded | ||
| 428 | + // frame has CRC8 == 0 and was accepted despite missing received bytes. | ||
| 429 | + bus->enqueue_read_payload({0x03, 0x00, 0x1C}); | ||
| 430 | + | ||
| 431 | + std::vector<uint8_t> in, out; | ||
| 432 | + std::string error; | ||
| 433 | + EXPECT_EQ(m_chip.bit_read(0x2100, 1, 0xFF, in, out, error), HAS_ERR); | ||
| 434 | + EXPECT_NE(error.find("length"), std::string::npos); | ||
| 435 | + EXPECT_TRUE(out.empty()); | ||
| 436 | +} | ||
| 437 | + | ||
| 411 | TEST_F(chip_smc_test, bit_read_uint16) | 438 | TEST_F(chip_smc_test, bit_read_uint16) |
| 412 | { | 439 | { |
| 413 | auto bus = bind_test_bus(); | 440 | auto bus = bind_test_bus(); |
| @@ -430,6 +457,21 @@ TEST_F(chip_smc_test, bit_read_uint32) | |||
| 430 | EXPECT_EQ(out, (std::vector<uint8_t>{0x78, 0x56, 0x34, 0x12})); | 457 | EXPECT_EQ(out, (std::vector<uint8_t>{0x78, 0x56, 0x34, 0x12})); |
| 431 | } | 458 | } |
| 432 | 459 | ||
| 460 | +TEST_F(chip_smc_test, bit_read_accepts_last_byte_of_maximum_payload) | ||
| 461 | +{ | ||
| 462 | + auto bus = bind_test_bus(); | ||
| 463 | + std::vector<uint8_t> payload(254, 0x00); | ||
| 464 | + payload.back() = 0xA5; | ||
| 465 | + auto frame = smc_read_frame(0x10, 0x21, payload); | ||
| 466 | + bus->enqueue_read_payload(frame); | ||
| 467 | + bus->enqueue_read_payload(std::move(frame)); | ||
| 468 | + | ||
| 469 | + std::vector<uint8_t> in, out; | ||
| 470 | + std::string error; | ||
| 471 | + EXPECT_EQ(m_chip.bit_read(0x21FE, 1, 0xFF, in, out, error), HAS_OK); | ||
| 472 | + EXPECT_EQ(out, (std::vector<uint8_t>{0xA5})); | ||
| 473 | +} | ||
| 474 | + | ||
| 433 | // ---- bit_write:读-改-写分支 ---- | 475 | // ---- bit_write:读-改-写分支 ---- |
| 434 | 476 | ||
| 435 | TEST_F(chip_smc_test, bit_write_rejects_invalid_lengths) | 477 | TEST_F(chip_smc_test, bit_write_rejects_invalid_lengths) |
| @@ -442,6 +484,60 @@ TEST_F(chip_smc_test, bit_write_rejects_invalid_lengths) | |||
| 442 | } | 484 | } |
| 443 | } | 485 | } |
| 444 | 486 | ||
| 487 | +TEST_F(chip_smc_test, bit_write_rejects_short_input_before_bus_access) | ||
| 488 | +{ | ||
| 489 | + auto bus = bind_test_bus(); | ||
| 490 | + for (uint8_t width : {1, 2, 4}) { | ||
| 491 | + for (size_t supplied = 0; supplied < width; ++supplied) { | ||
| 492 | + for (uint32_t mask : {0x0FU, 0xFFFFFFFFU}) { | ||
| 493 | + SCOPED_TRACE(::testing::Message() << +width << "/" << supplied << "/" << mask); | ||
| 494 | + std::vector<uint8_t> in(supplied, 0x12), out; | ||
| 495 | + std::string error; | ||
| 496 | + EXPECT_EQ(m_chip.bit_write(0x2100, width, mask, in, out, error), HAS_ERR); | ||
| 497 | + EXPECT_NE(error.find("input"), std::string::npos); | ||
| 498 | + } | ||
| 499 | + } | ||
| 500 | + } | ||
| 501 | + EXPECT_EQ(bus->write_call_count(), 0); | ||
| 502 | + EXPECT_EQ(bus->read_call_count(), 0); | ||
| 503 | +} | ||
| 504 | + | ||
| 505 | +TEST_F(chip_smc_test, bit_write_full_uint32_mask_writes_without_read) | ||
| 506 | +{ | ||
| 507 | + auto bus = bind_test_bus(); | ||
| 508 | + std::vector<uint8_t> in = {0x78, 0x56, 0x34, 0x12}; | ||
| 509 | + std::vector<uint8_t> out; | ||
| 510 | + std::string error; | ||
| 511 | + EXPECT_EQ(m_chip.bit_write(0x2100, 4, 0xFFFFFFFF, in, out, error), HAS_OK); | ||
| 512 | + EXPECT_EQ(bus->read_call_count(), 0); | ||
| 513 | + ASSERT_GE(bus->last_write_buffer().size(), 9U); | ||
| 514 | + const auto& frame = bus->last_write_buffer(); | ||
| 515 | + EXPECT_EQ((std::vector<uint8_t>(frame.end() - 5, frame.end() - 1)), in); | ||
| 516 | +} | ||
| 517 | + | ||
| 518 | +TEST_F(chip_smc_test, bit_write_rejects_zero_response_length_before_offset) | ||
| 519 | +{ | ||
| 520 | + auto bus = bind_test_bus(); | ||
| 521 | + bus->enqueue_read_payload({0x00, 0x00}); | ||
| 522 | + | ||
| 523 | + std::vector<uint8_t> in = {0x12}; | ||
| 524 | + std::vector<uint8_t> out; | ||
| 525 | + std::string error; | ||
| 526 | + EXPECT_EQ(m_chip.bit_write(0x21FF, 1, 0x0F, in, out, error), HAS_ERR); | ||
| 527 | + EXPECT_NE(error.find("length"), std::string::npos); | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +TEST_F(chip_smc_test, bit_write_rejects_truncated_response_with_valid_padded_crc) | ||
| 531 | +{ | ||
| 532 | + auto bus = bind_test_bus(); | ||
| 533 | + bus->enqueue_read_payload({0x03, 0x00, 0x1C}); | ||
| 534 | + std::vector<uint8_t> input = {0x12}, output; | ||
| 535 | + std::string error; | ||
| 536 | + EXPECT_EQ(m_chip.bit_write(0x2100, 1, 0x0F, input, output, error), HAS_ERR); | ||
| 537 | + // One command starts the read; no register write may follow the short reply. | ||
| 538 | + EXPECT_EQ(bus->write_call_count(), 1); | ||
| 539 | +} | ||
| 540 | + | ||
| 445 | TEST_F(chip_smc_test, bit_write_read_modify_write_byte) | 541 | TEST_F(chip_smc_test, bit_write_read_modify_write_byte) |
| 446 | { | 542 | { |
| 447 | auto bus = bind_test_bus(); | 543 | auto bus = bind_test_bus(); |
| @@ -816,6 +912,106 @@ TEST(chip_smc_forward, forward_by_front_chip_smc_write) | |||
| 816 | EXPECT_EQ(chip.forward_by_front_chip(d, "smc", 1), HAS_OK); | 912 | EXPECT_EQ(chip.forward_by_front_chip(d, "smc", 1), HAS_OK); |
| 817 | } | 913 | } |
| 818 | 914 | ||
| 915 | +TEST(chip_smc_forward, forward_by_front_chip_rejects_length_larger_than_input) | ||
| 916 | +{ | ||
| 917 | + chip_smc_exposed chip; | ||
| 918 | + auto bus = init_exposed_chip(chip); | ||
| 919 | + | ||
| 920 | + ACCESS_DATA_S d{}; | ||
| 921 | + d.optype = CHIP_OP_TYPE_WRITE; | ||
| 922 | + d.addr = 0x60; | ||
| 923 | + d.offset = 0x20; | ||
| 924 | + d.length = 4; | ||
| 925 | + d.offset_width = 1; | ||
| 926 | + d.in_buffer = {0x11}; | ||
| 927 | + | ||
| 928 | + EXPECT_EQ(chip.forward_by_front_chip(d, "smc", 1), HAS_ERR); | ||
| 929 | + EXPECT_EQ(bus->write_call_count(), 0); | ||
| 930 | +} | ||
| 931 | + | ||
| 932 | +TEST(chip_smc_forward, forward_by_front_chip_rejects_oversized_offset_width) | ||
| 933 | +{ | ||
| 934 | + chip_smc_exposed chip; | ||
| 935 | + auto bus = init_exposed_chip(chip); | ||
| 936 | + | ||
| 937 | + ACCESS_DATA_S d{}; | ||
| 938 | + d.optype = CHIP_OP_TYPE_WRITE; | ||
| 939 | + d.addr = 0x60; | ||
| 940 | + d.offset = 0x20; | ||
| 941 | + d.length = 1; | ||
| 942 | + d.in_buffer = {0x11}; | ||
| 943 | + for (uint8_t width : {5, 251, 255}) { | ||
| 944 | + for (auto operation : {CHIP_OP_TYPE_READ, CHIP_OP_TYPE_WRITE}) { | ||
| 945 | + SCOPED_TRACE(::testing::Message() << +width << "/" << operation); | ||
| 946 | + d.offset_width = width; | ||
| 947 | + d.optype = operation; | ||
| 948 | + EXPECT_EQ(chip.forward_by_front_chip(d, "eeprom", 1), HAS_ERR); | ||
| 949 | + } | ||
| 950 | + } | ||
| 951 | + EXPECT_EQ(bus->write_call_count(), 0); | ||
| 952 | + EXPECT_EQ(bus->read_call_count(), 0); | ||
| 953 | +} | ||
| 954 | + | ||
| 955 | +TEST(chip_smc_forward, forward_write_capacity_boundary_with_offset) | ||
| 956 | +{ | ||
| 957 | + for (uint8_t width : {0, 1, 4}) { | ||
| 958 | + for (uint32_t extra : {0U, 1U}) { | ||
| 959 | + SCOPED_TRACE(::testing::Message() << +width << "/" << extra); | ||
| 960 | + chip_smc_exposed chip; | ||
| 961 | + auto bus = init_exposed_chip(chip); | ||
| 962 | + ACCESS_DATA_S d{}; | ||
| 963 | + d.optype = CHIP_OP_TYPE_WRITE; | ||
| 964 | + d.addr = 0x60; | ||
| 965 | + d.offset = 0x12345678; | ||
| 966 | + d.offset_width = width; | ||
| 967 | + d.length = 250 - width + extra; | ||
| 968 | + d.in_buffer.assign(d.length, 0xAB); | ||
| 969 | + EXPECT_EQ(chip.forward_by_front_chip(d, "smc", 1), extra ? HAS_ERR : HAS_OK); | ||
| 970 | + EXPECT_EQ(bus->write_call_count(), extra ? 0 : 1); | ||
| 971 | + } | ||
| 972 | + } | ||
| 973 | +} | ||
| 974 | + | ||
| 975 | +TEST(chip_smc_forward, forward_read_rejects_zero_response_length) | ||
| 976 | +{ | ||
| 977 | + chip_smc_exposed chip; | ||
| 978 | + auto bus = init_exposed_chip(chip, 0x88); | ||
| 979 | + // CRC8([0x88, 0x31, 0x89, 0x00]) is zero, so the legacy parser accepts | ||
| 980 | + // this malformed frame and exposes the length subtraction bug. | ||
| 981 | + bus->enqueue_read_payload({0x00, 0x00}); | ||
| 982 | + | ||
| 983 | + std::vector<uint8_t> out; | ||
| 984 | + EXPECT_EQ(chip.forward_read(1, out), HAS_ERR); | ||
| 985 | + EXPECT_TRUE(out.empty()); | ||
| 986 | +} | ||
| 987 | + | ||
| 988 | +TEST(chip_smc_forward, forward_read_rejects_truncated_response) | ||
| 989 | +{ | ||
| 990 | + chip_smc_exposed chip; | ||
| 991 | + auto bus = init_exposed_chip(chip); | ||
| 992 | + bus->enqueue_read_payload({0x03, 0x00, 0xAB}); | ||
| 993 | + | ||
| 994 | + std::vector<uint8_t> out; | ||
| 995 | + EXPECT_EQ(chip.forward_read(1, out), HAS_ERR); | ||
| 996 | + EXPECT_TRUE(out.empty()); | ||
| 997 | +} | ||
| 998 | + | ||
| 999 | +TEST(chip_smc_forward, forward_read_accepts_maximum_payload) | ||
| 1000 | +{ | ||
| 1001 | + chip_smc_exposed chip; | ||
| 1002 | + auto bus = init_exposed_chip(chip); | ||
| 1003 | + std::vector<uint8_t> payload(254, 0x00); | ||
| 1004 | + payload.front() = 0x11; | ||
| 1005 | + payload.back() = 0xEE; | ||
| 1006 | + auto frame = smc_read_frame(0x10, 0x31, payload); | ||
| 1007 | + bus->enqueue_read_payload(frame); | ||
| 1008 | + bus->enqueue_read_payload(std::move(frame)); | ||
| 1009 | + | ||
| 1010 | + std::vector<uint8_t> out; | ||
| 1011 | + EXPECT_EQ(chip.forward_read(255, out), HAS_OK); | ||
| 1012 | + EXPECT_EQ(out, payload); | ||
| 1013 | +} | ||
| 1014 | + | ||
| 819 | TEST(chip_smc_forward, forward_by_front_chip_smc_write_memcpy_fail) | 1015 | TEST(chip_smc_forward, forward_by_front_chip_smc_write_memcpy_fail) |
| 820 | { | 1016 | { |
| 821 | chip_smc_exposed chip; | 1017 | chip_smc_exposed chip; |
| @@ -21,6 +21,7 @@ | |||
| 21 | #include <thread> | 21 | #include <thread> |
| 22 | #include <vector> | 22 | #include <vector> |
| 23 | #include <gtest/gtest.h> | 23 | #include <gtest/gtest.h> |
| 24 | +#include "../interface/test_async_wait.h" | ||
| 24 | #include "emulex_card.h" | 25 | #include "emulex_card.h" |
| 25 | #include "emulex_om.h" | 26 | #include "emulex_om.h" |
| 26 | #include "emulex_port.h" | 27 | #include "emulex_port.h" |
| @@ -136,6 +137,46 @@ std::unique_ptr<emulex_test_service> EmulexOpticalModuleTest::m_test_service; | |||
| 136 | 137 | ||
| 137 | // ========================= 基本功能测试 ========================= | 138 | // ========================= 基本功能测试 ========================= |
| 138 | 139 | ||
| 140 | +TEST_F(EmulexOpticalModuleTest, EmbeddedNcsiTimerCanOutliveDevice) | ||
| 141 | +{ | ||
| 142 | + auto device = mc::make_shared<dev::emulex_optical_module>(); | ||
| 143 | + auto& iface = device->m_optical_module; | ||
| 144 | + iface.start_ncsi_update_task_broadcom(nullptr, mc::milliseconds(60000), 0); | ||
| 145 | + auto retained = dev::BoardcomNcsiTimerTestAccess::timer(iface); | ||
| 146 | + ASSERT_NE(retained, nullptr); | ||
| 147 | + // A value-member interface must never own the queued timer as a child: | ||
| 148 | + // get_parent() alone may hide a dangling link when weak promotion fails. | ||
| 149 | + EXPECT_TRUE(iface.get_children().empty()); | ||
| 150 | + iface.stop_ncsi_update_task(); | ||
| 151 | + EXPECT_FALSE(retained->is_active()); | ||
| 152 | + device.reset(); | ||
| 153 | + retained.reset(); | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +TEST_F(EmulexOpticalModuleTest, EmbeddedNcsiTimerUsesOwnerExecutor) | ||
| 157 | +{ | ||
| 158 | + auto device = mc::make_shared<dev::emulex_optical_module>(); | ||
| 159 | + mc::any_executor expected{mc::runtime::runtime_strand{}}; | ||
| 160 | + device->bind_executor(expected); | ||
| 161 | + auto& iface = device->m_optical_module; | ||
| 162 | + iface.start_ncsi_update_task_broadcom(nullptr, mc::milliseconds(60000), 0); | ||
| 163 | + auto timer = dev::BoardcomNcsiTimerTestAccess::timer(iface); | ||
| 164 | + ASSERT_NE(timer, nullptr); | ||
| 165 | + auto observed = std::make_shared<std::promise<bool>>(); | ||
| 166 | + auto result = observed->get_future(); | ||
| 167 | + timer->timeout.connect([observed, expected] { | ||
| 168 | + observed->set_value(expected.running_in_this_thread()); | ||
| 169 | + }); | ||
| 170 | + timer->set_single_shot(true); | ||
| 171 | + timer->start(mc::milliseconds(1)); | ||
| 172 | + const auto ready = result.wait_for(std::chrono::seconds(2)); | ||
| 173 | + iface.stop_ncsi_update_task(); | ||
| 174 | + EXPECT_EQ(ready, std::future_status::ready); | ||
| 175 | + if (ready == std::future_status::ready) { | ||
| 176 | + EXPECT_TRUE(result.get()); | ||
| 177 | + } | ||
| 178 | +} | ||
| 179 | + | ||
| 139 | TEST_F(EmulexOpticalModuleTest, Constructor) | 180 | TEST_F(EmulexOpticalModuleTest, Constructor) |
| 140 | { | 181 | { |
| 141 | // Constructor 测试只验证对象能否正确创建 | 182 | // Constructor 测试只验证对象能否正确创建 |
| @@ -25,6 +25,7 @@ | |||
| 25 | #include <thread> | 25 | #include <thread> |
| 26 | #include <vector> | 26 | #include <vector> |
| 27 | #include <gtest/gtest.h> | 27 | #include <gtest/gtest.h> |
| 28 | +#include "../interface/test_async_wait.h" | ||
| 28 | #include "emulex_card.h" | 29 | #include "emulex_card.h" |
| 29 | #include "emulex_port.h" | 30 | #include "emulex_port.h" |
| 30 | 31 | ||
| @@ -218,6 +219,46 @@ mc::shared_ptr<test_imu_ipmb_object> EmulexPortTest::m_ipmb_obj; | |||
| 218 | 219 | ||
| 219 | // ========================= 基本功能测试 ========================= | 220 | // ========================= 基本功能测试 ========================= |
| 220 | 221 | ||
| 222 | +TEST_F(EmulexPortTest, EmbeddedNcsiTimerCanOutliveDevice) | ||
| 223 | +{ | ||
| 224 | + auto device = mc::make_shared<dev::emulex_port>(); | ||
| 225 | + auto& iface = device->m_network_port; | ||
| 226 | + iface.start_ncsi_update_task_broadcom(nullptr, mc::milliseconds(60000), 0); | ||
| 227 | + auto retained = dev::BoardcomNcsiTimerTestAccess::timer(iface); | ||
| 228 | + ASSERT_NE(retained, nullptr); | ||
| 229 | + // A value-member interface must never own the queued timer as a child: | ||
| 230 | + // get_parent() alone may hide a dangling link when weak promotion fails. | ||
| 231 | + EXPECT_TRUE(iface.get_children().empty()); | ||
| 232 | + iface.stop_ncsi_update_task(); | ||
| 233 | + EXPECT_FALSE(retained->is_active()); | ||
| 234 | + device.reset(); | ||
| 235 | + retained.reset(); | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +TEST_F(EmulexPortTest, EmbeddedNcsiTimerUsesOwnerExecutor) | ||
| 239 | +{ | ||
| 240 | + auto device = mc::make_shared<dev::emulex_port>(); | ||
| 241 | + mc::any_executor expected{mc::runtime::runtime_strand{}}; | ||
| 242 | + device->bind_executor(expected); | ||
| 243 | + auto& iface = device->m_network_port; | ||
| 244 | + iface.start_ncsi_update_task_broadcom(nullptr, mc::milliseconds(60000), 0); | ||
| 245 | + auto timer = dev::BoardcomNcsiTimerTestAccess::timer(iface); | ||
| 246 | + ASSERT_NE(timer, nullptr); | ||
| 247 | + auto observed = std::make_shared<std::promise<bool>>(); | ||
| 248 | + auto result = observed->get_future(); | ||
| 249 | + timer->timeout.connect([observed, expected] { | ||
| 250 | + observed->set_value(expected.running_in_this_thread()); | ||
| 251 | + }); | ||
| 252 | + timer->set_single_shot(true); | ||
| 253 | + timer->start(mc::milliseconds(1)); | ||
| 254 | + const auto ready = result.wait_for(std::chrono::seconds(2)); | ||
| 255 | + iface.stop_ncsi_update_task(); | ||
| 256 | + EXPECT_EQ(ready, std::future_status::ready); | ||
| 257 | + if (ready == std::future_status::ready) { | ||
| 258 | + EXPECT_TRUE(result.get()); | ||
| 259 | + } | ||
| 260 | +} | ||
| 261 | + | ||
| 221 | TEST_F(EmulexPortTest, Constructor) | 262 | TEST_F(EmulexPortTest, Constructor) |
| 222 | { | 263 | { |
| 223 | dev::emulex_port port; | 264 | dev::emulex_port port; |
| @@ -580,11 +621,7 @@ TEST_F(EmulexPortTest, InitStartStopPldmProtocol) | |||
| 580 | 621 | ||
| 581 | // ========================= start_pldm_protocol 边界测试 ========================= | 622 | // ========================= start_pldm_protocol 边界测试 ========================= |
| 582 | 623 | ||
| 583 | -// 临时禁用:start_pldm_protocol 内部对入参指针无 null check,传入 nullptr 会段错误, | 624 | +TEST_F(EmulexPortTest, StartPldmProtocol_WithNullPldmPtr) |
| 584 | -// EXPECT_NO_THROW 无法捕获信号;保留测试体作为后续修代码后的回归用例。 | ||
| 585 | -// 覆盖影响:init() 路径已被其他测试覆盖,start_pldm_protocol() 路径已被 | ||
| 586 | -// StartPldmProtocol_StartsProtocol/InitStartStopPldmProtocol 等覆盖,禁用不影响覆盖率。 | ||
| 587 | -TEST_F(EmulexPortTest, DISABLED_StartPldmProtocol_WithNullPldmPtr) | ||
| 588 | { | 625 | { |
| 589 | auto csr = create_test_csr(); | 626 | auto csr = create_test_csr(); |
| 590 | csr["bmc.dev.NetworkPort"] = mc::dict{{"PortId", 0}}; | 627 | csr["bmc.dev.NetworkPort"] = mc::dict{{"PortId", 0}}; |
| @@ -595,8 +632,11 @@ TEST_F(EmulexPortTest, DISABLED_StartPldmProtocol_WithNullPldmPtr) | |||
| 595 | mc::milliseconds interval(100); | 632 | mc::milliseconds interval(100); |
| 596 | EXPECT_NO_THROW({ | 633 | EXPECT_NO_THROW({ |
| 597 | bool result = m_port->start_pldm_protocol(nullptr, interval); | 634 | bool result = m_port->start_pldm_protocol(nullptr, interval); |
| 598 | - EXPECT_TRUE(result == true || result == false); | 635 | + EXPECT_TRUE(result); |
| 599 | }); | 636 | }); |
| 637 | + EXPECT_FALSE(m_port->m_network_port_link_info.update_link_status_by_pldm(0)); | ||
| 638 | + EXPECT_FALSE(m_port->m_network_port_fibrechannel.update_fcinfo_by_pldm(0)); | ||
| 639 | + m_port->stop_pldm_protocol(); | ||
| 600 | } | 640 | } |
| 601 | 641 | ||
| 602 | // ========================= update_port_bdf 边界测试 ========================= | 642 | // ========================= update_port_bdf 边界测试 ========================= |
| @@ -25,6 +25,7 @@ | |||
| 25 | #include <thread> | 25 | #include <thread> |
| 26 | #include <vector> | 26 | #include <vector> |
| 27 | #include <gtest/gtest.h> | 27 | #include <gtest/gtest.h> |
| 28 | +#include "../test_async_wait.h" | ||
| 28 | #include "interface/network_port/link_info.h" | 29 | #include "interface/network_port/link_info.h" |
| 29 | 30 | ||
| 30 | using namespace dev; | 31 | using namespace dev; |
| @@ -620,8 +621,10 @@ TEST_F(BoardcomNetworkPortLinkInfoTest, StopPldmUpdateTask_AfterStart) | |||
| 620 | 621 | ||
| 621 | TEST_F(BoardcomNetworkPortLinkInfoTest, TimerCallback_UpdatesLinkStatus) | 622 | TEST_F(BoardcomNetworkPortLinkInfoTest, TimerCallback_UpdatesLinkStatus) |
| 622 | { | 623 | { |
| 624 | + RequestCounter calls; | ||
| 623 | // 设置 MCTP 请求处理器 | 625 | // 设置 MCTP 请求处理器 |
| 624 | - dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>& data, std::string& rsp) { | 626 | + dev::set_mctp_request_handler([&calls](const mc::dict& ctx, const std::vector<uint8_t>& data, std::string& rsp) { |
| 627 | + calls.record(); | ||
| 625 | if (!ctx.contains("PldmType") || !ctx.contains("CommandCode")) { | 628 | if (!ctx.contains("PldmType") || !ctx.contains("CommandCode")) { |
| 626 | return false; | 629 | return false; |
| 627 | } | 630 | } |
| @@ -673,12 +676,11 @@ TEST_F(BoardcomNetworkPortLinkInfoTest, TimerCallback_UpdatesLinkStatus) | |||
| 673 | 676 | ||
| 674 | m_link_info->start_pldm_update_task_emulex(m_pldm, interval, port_id); | 677 | m_link_info->start_pldm_update_task_emulex(m_pldm, interval, port_id); |
| 675 | // 等待定时器回调执行 | 678 | // 等待定时器回调执行 |
| 676 | - std::this_thread::sleep_for(std::chrono::milliseconds(150)); | 679 | + EXPECT_TRUE(calls.wait_for(1)); |
| 680 | + m_link_info->stop_pldm_update_task(); | ||
| 677 | 681 | ||
| 678 | // 验证链路状态已被更新 | 682 | // 验证链路状态已被更新 |
| 679 | EXPECT_EQ(m_link_info->LinkStatus.get_value(), 1); | 683 | EXPECT_EQ(m_link_info->LinkStatus.get_value(), 1); |
| 680 | - | ||
| 681 | - m_link_info->stop_pldm_update_task(); | ||
| 682 | } | 684 | } |
| 683 | 685 | ||
| 684 | // ========================= 完整工作流测试 ========================= | 686 | // ========================= 完整工作流测试 ========================= |
| @@ -0,0 +1,90 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2025 Huawei Technologies Co., Ltd. | ||
| 3 | + * openUBMC is licensed under Mulan PSL v2. | ||
| 4 | + * You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 5 | + * You may obtain a copy of Mulan PSL v2 at: | ||
| 6 | + * http://license.coscl.org.cn/MulanPSL2 | ||
| 7 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 8 | + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 9 | + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 10 | + * See the Mulan PSL v2 for more details. | ||
| 11 | + */ | ||
| 12 | + | ||
| 13 | +#pragma once | ||
| 14 | + | ||
| 15 | +#include <chrono> | ||
| 16 | +#include <condition_variable> | ||
| 17 | +#include <future> | ||
| 18 | +#include <mc/object.h> | ||
| 19 | +#include <mc/timer.h> | ||
| 20 | +#include <mutex> | ||
| 21 | +#include "interface/network_port.h" | ||
| 22 | +#include "interface/optical_module.h" | ||
| 23 | + | ||
| 24 | +namespace dev { | ||
| 25 | +struct BoardcomNcsiTimerTestAccess { | ||
| 26 | + static auto timer(const NetworkPort& port) | ||
| 27 | + { | ||
| 28 | + return port.m_ncsi_timer; | ||
| 29 | + } | ||
| 30 | + static auto timer(const OpticalModule& module) | ||
| 31 | + { | ||
| 32 | + return module.m_ncsi_timer; | ||
| 33 | + } | ||
| 34 | +}; | ||
| 35 | +} // namespace dev | ||
| 36 | + | ||
| 37 | +namespace drivers::pcie_nic_card::boardcom::interface::test { | ||
| 38 | +// Wait for real timer requests, with synchronized state and a bounded deadline. | ||
| 39 | +class RequestCounter { | ||
| 40 | +public: | ||
| 41 | + void record() | ||
| 42 | + { | ||
| 43 | + std::lock_guard<std::mutex> lock(m_mutex); | ||
| 44 | + ++m_count; | ||
| 45 | + m_changed.notify_all(); | ||
| 46 | + } | ||
| 47 | + int count() | ||
| 48 | + { | ||
| 49 | + std::lock_guard<std::mutex> lock(m_mutex); | ||
| 50 | + return m_count; | ||
| 51 | + } | ||
| 52 | + bool wait_for(int target, std::chrono::milliseconds timeout = std::chrono::seconds(2)) | ||
| 53 | + { | ||
| 54 | + std::unique_lock<std::mutex> lock(m_mutex); | ||
| 55 | + return m_changed.wait_for(lock, timeout, [&] { | ||
| 56 | + return m_count >= target; | ||
| 57 | + }); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | +private: | ||
| 61 | + std::mutex m_mutex; | ||
| 62 | + std::condition_variable m_changed; | ||
| 63 | + int m_count = 0; | ||
| 64 | +}; | ||
| 65 | + | ||
| 66 | +// Join the update timer without invoking an interface stop that resets the | ||
| 67 | +// observed properties. Access the owned timer even when it has no parent. | ||
| 68 | +inline auto find_timer(const dev::NetworkPort& object) | ||
| 69 | +{ | ||
| 70 | + return dev::BoardcomNcsiTimerTestAccess::timer(object); | ||
| 71 | +} | ||
| 72 | +inline auto find_timer(const dev::OpticalModule& object) | ||
| 73 | +{ | ||
| 74 | + return dev::BoardcomNcsiTimerTestAccess::timer(object); | ||
| 75 | +} | ||
| 76 | +inline void stop_child_timers(const dev::NetworkPort& object) | ||
| 77 | +{ | ||
| 78 | + if (auto timer = find_timer(object)) { | ||
| 79 | + timer->stop(); | ||
| 80 | + } | ||
| 81 | +} | ||
| 82 | +inline void stop_child_timers(const mc::object& object) | ||
| 83 | +{ | ||
| 84 | + for (const auto& child : object.get_children()) { | ||
| 85 | + if (auto timer = mc::dynamic_pointer_cast<mc::timer>(child)) { | ||
| 86 | + timer->stop(); | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | +} | ||
| 90 | +} // namespace drivers::pcie_nic_card::boardcom::interface::test | ||
| @@ -29,6 +29,7 @@ | |||
| 29 | #include <gtest/gtest.h> | 29 | #include <gtest/gtest.h> |
| 30 | #include "interface/cooling.h" | 30 | #include "interface/cooling.h" |
| 31 | #include "interface/network_adapter/oem.h" | 31 | #include "interface/network_adapter/oem.h" |
| 32 | +#include "test_async_wait.h" | ||
| 32 | 33 | ||
| 33 | using namespace dev; | 34 | using namespace dev; |
| 34 | 35 | ||
| @@ -551,8 +552,10 @@ TEST_F(BoardcomNetworkAdapterCoolingTest, StopPldmUpdateTask_MultipleStop) | |||
| 551 | 552 | ||
| 552 | TEST_F(BoardcomNetworkAdapterCoolingTest, TimerCallback_UpdatesTemperature) | 553 | TEST_F(BoardcomNetworkAdapterCoolingTest, TimerCallback_UpdatesTemperature) |
| 553 | { | 554 | { |
| 555 | + RequestCounter calls; | ||
| 554 | // 设置 MCTP 请求处理器 | 556 | // 设置 MCTP 请求处理器 |
| 555 | - dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>& data, std::string& rsp) { | 557 | + dev::set_mctp_request_handler([&calls](const mc::dict& ctx, const std::vector<uint8_t>& data, std::string& rsp) { |
| 558 | + calls.record(); | ||
| 556 | if (!ctx.contains("PldmType") || !ctx.contains("CommandCode")) { | 559 | if (!ctx.contains("PldmType") || !ctx.contains("CommandCode")) { |
| 557 | return false; | 560 | return false; |
| 558 | } | 561 | } |
| @@ -586,12 +589,11 @@ TEST_F(BoardcomNetworkAdapterCoolingTest, TimerCallback_UpdatesTemperature) | |||
| 586 | while (std::chrono::steady_clock::now() < deadline && m_cooling->TemperatureCelsius.get_value() != 60) { | 589 | while (std::chrono::steady_clock::now() < deadline && m_cooling->TemperatureCelsius.get_value() != 60) { |
| 587 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); | 590 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); |
| 588 | } | 591 | } |
| 592 | + m_cooling->stop_pldm_update_task(); | ||
| 589 | 593 | ||
| 590 | // 验证温度已被更新 | 594 | // 验证温度已被更新 |
| 591 | EXPECT_EQ(m_cooling->TemperatureCelsius.get_value(), 60); | 595 | EXPECT_EQ(m_cooling->TemperatureCelsius.get_value(), 60); |
| 592 | EXPECT_EQ(m_cooling->TemperatureStatus.get_value(), 0); | 596 | EXPECT_EQ(m_cooling->TemperatureStatus.get_value(), 0); |
| 593 | - | ||
| 594 | - m_cooling->stop_pldm_update_task(); | ||
| 595 | } | 597 | } |
| 596 | 598 | ||
| 597 | TEST_F(BoardcomNetworkAdapterCoolingTest, TimerCallback_HandlesFailure) | 599 | TEST_F(BoardcomNetworkAdapterCoolingTest, TimerCallback_HandlesFailure) |
| @@ -1162,12 +1164,15 @@ TEST_F(BoardcomNetworkAdapterCoolingTest, RestoreNcsiPropertyDefaults_DirectCall | |||
| 1162 | 1164 | ||
| 1163 | TEST_F(BoardcomNetworkAdapterCoolingTest, NcsiTimerCallback_UpdatesTemperature) | 1165 | TEST_F(BoardcomNetworkAdapterCoolingTest, NcsiTimerCallback_UpdatesTemperature) |
| 1164 | { | 1166 | { |
| 1165 | - dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1167 | + RequestCounter calls; |
| 1168 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | ||
| 1169 | + calls.record(); | ||
| 1166 | rsp = make_ncsi_temp_rsp_bytes(80, 45); | 1170 | rsp = make_ncsi_temp_rsp_bytes(80, 45); |
| 1167 | return true; | 1171 | return true; |
| 1168 | }); | 1172 | }); |
| 1169 | m_cooling->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(50), 0); | 1173 | m_cooling->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(50), 0); |
| 1170 | - std::this_thread::sleep_for(std::chrono::milliseconds(120)); | 1174 | + EXPECT_TRUE(calls.wait_for(1)); |
| 1175 | + stop_child_timers(*m_cooling); | ||
| 1171 | EXPECT_EQ(m_cooling->TemperatureCelsius.get_value(), 45); | 1176 | EXPECT_EQ(m_cooling->TemperatureCelsius.get_value(), 45); |
| 1172 | m_cooling->stop_ncsi_update_task(); | 1177 | m_cooling->stop_ncsi_update_task(); |
| 1173 | } | 1178 | } |
| @@ -28,6 +28,7 @@ | |||
| 28 | #include "interface/network_port.h" | 28 | #include "interface/network_port.h" |
| 29 | #include "interface/network_port/link_info.h" | 29 | #include "interface/network_port/link_info.h" |
| 30 | #include "interface/network_port/oem.h" | 30 | #include "interface/network_port/oem.h" |
| 31 | +#include "test_async_wait.h" | ||
| 31 | 32 | ||
| 32 | using namespace dev; | 33 | using namespace dev; |
| 33 | 34 | ||
| @@ -754,22 +755,36 @@ TEST_F(BoardcomNetworkPortNcsiTest, UpdatePartitionInfoByNcsi_OneMac_OnlyMacAddr | |||
| 754 | 755 | ||
| 755 | // ----- start_ncsi_update_task_broadcom / stop_ncsi_update_task ----- | 756 | // ----- start_ncsi_update_task_broadcom / stop_ncsi_update_task ----- |
| 756 | 757 | ||
| 758 | +TEST_F(BoardcomNetworkPortNcsiTest, ReplacingRetainedTimerStopsPreviousTimer) | ||
| 759 | +{ | ||
| 760 | + m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(60000), 0); | ||
| 761 | + auto previous = find_timer(*m_network_port); | ||
| 762 | + EXPECT_NE(previous, nullptr); | ||
| 763 | + m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(60000), 1); | ||
| 764 | + if (previous) { | ||
| 765 | + EXPECT_FALSE(previous->is_active()); | ||
| 766 | + // Keep failure paths safe on the original implementation too. | ||
| 767 | + previous->stop(); | ||
| 768 | + } | ||
| 769 | + m_network_port->stop_ncsi_update_task(); | ||
| 770 | +} | ||
| 771 | + | ||
| 757 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_TimerFiresInvokesUpdatePartitionInfo) | 772 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_TimerFiresInvokesUpdatePartitionInfo) |
| 758 | { | 773 | { |
| 759 | - int call_count = 0; | 774 | + RequestCounter calls; |
| 760 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 775 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 761 | - ++call_count; | 776 | + calls.record(); |
| 762 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); | 777 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); |
| 763 | return true; | 778 | return true; |
| 764 | }); | 779 | }); |
| 765 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); | 780 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); |
| 766 | - std::this_thread::sleep_for(std::chrono::milliseconds(80)); | 781 | + // Join the callback without resetting the BDF observed below. |
| 767 | - EXPECT_GT(call_count, 0); | 782 | + EXPECT_TRUE(calls.wait_for(1)); |
| 783 | + stop_child_timers(*m_network_port); | ||
| 768 | EXPECT_EQ(m_network_port->BDF.get_value().as<std::string>(), "0000:18:02.0"); | 784 | EXPECT_EQ(m_network_port->BDF.get_value().as<std::string>(), "0000:18:02.0"); |
| 769 | m_network_port->stop_ncsi_update_task(); | 785 | m_network_port->stop_ncsi_update_task(); |
| 770 | - int prev = call_count; | 786 | + const int prev = calls.count(); |
| 771 | - std::this_thread::sleep_for(std::chrono::milliseconds(50)); | 787 | + EXPECT_FALSE(calls.wait_for(prev + 1, std::chrono::milliseconds(50))); |
| 772 | - EXPECT_EQ(call_count, prev); | ||
| 773 | } | 788 | } |
| 774 | 789 | ||
| 775 | TEST_F(BoardcomNetworkPortNcsiTest, StopNcsiUpdateTask_StopsAndResetsTimer) | 790 | TEST_F(BoardcomNetworkPortNcsiTest, StopNcsiUpdateTask_StopsAndResetsTimer) |
| @@ -918,39 +933,36 @@ TEST_F(BoardcomNetworkPortNcsiTest, UpdatePartitionInfoByNcsi_TLVOffsetExceedsBu | |||
| 918 | 933 | ||
| 919 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_ConsecutiveStarts_ReplacesTimer) | 934 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_ConsecutiveStarts_ReplacesTimer) |
| 920 | { | 935 | { |
| 921 | - int call_count = 0; | 936 | + RequestCounter calls; |
| 922 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 937 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 923 | - ++call_count; | 938 | + calls.record(); |
| 924 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); | 939 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); |
| 925 | return true; | 940 | return true; |
| 926 | }); | 941 | }); |
| 927 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); | 942 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); |
| 928 | - std::this_thread::sleep_for(std::chrono::milliseconds(30)); | 943 | + EXPECT_TRUE(calls.wait_for(1)); |
| 929 | - int prev = call_count; | ||
| 930 | - EXPECT_GT(prev, 0); | ||
| 931 | // 第二次 start:覆盖 m_ncsi_timer 和 m_package_id | 944 | // 第二次 start:覆盖 m_ncsi_timer 和 m_package_id |
| 932 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 1); | 945 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 1); |
| 933 | - std::this_thread::sleep_for(std::chrono::milliseconds(60)); | 946 | + const int prev = calls.count(); |
| 934 | - EXPECT_GT(call_count, prev); | 947 | + EXPECT_TRUE(calls.wait_for(prev + 1)); |
| 935 | m_network_port->stop_ncsi_update_task(); | 948 | m_network_port->stop_ncsi_update_task(); |
| 936 | } | 949 | } |
| 937 | 950 | ||
| 938 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_StartStopStartStop_StopsBetweenCycles) | 951 | TEST_F(BoardcomNetworkPortNcsiTest, StartNcsiUpdateTask_StartStopStartStop_StopsBetweenCycles) |
| 939 | { | 952 | { |
| 940 | - int call_count = 0; | 953 | + RequestCounter calls; |
| 941 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 954 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 942 | - ++call_count; | 955 | + calls.record(); |
| 943 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); | 956 | rsp = make_partition_config_rsp_bytes(0x18, 0x02, 0x0, {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}); |
| 944 | return true; | 957 | return true; |
| 945 | }); | 958 | }); |
| 946 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); | 959 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); |
| 947 | - std::this_thread::sleep_for(std::chrono::milliseconds(30)); | 960 | + EXPECT_TRUE(calls.wait_for(1)); |
| 948 | m_network_port->stop_ncsi_update_task(); | 961 | m_network_port->stop_ncsi_update_task(); |
| 949 | - int after_first = call_count; | 962 | + const int after_first = calls.count(); |
| 950 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); | 963 | m_network_port->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); |
| 951 | - std::this_thread::sleep_for(std::chrono::milliseconds(30)); | 964 | + EXPECT_TRUE(calls.wait_for(after_first + 1)); |
| 952 | m_network_port->stop_ncsi_update_task(); | 965 | m_network_port->stop_ncsi_update_task(); |
| 953 | - EXPECT_GT(call_count, after_first); | ||
| 954 | } | 966 | } |
| 955 | 967 | ||
| 956 | // ----- SendNcsiTestPacket 异常路径:覆盖 try/catch 中的 elog 分支 ----- | 968 | // ----- SendNcsiTestPacket 异常路径:覆盖 try/catch 中的 elog 分支 ----- |
| @@ -28,6 +28,7 @@ | |||
| 28 | #include "interface/optical_module/power.h" | 28 | #include "interface/optical_module/power.h" |
| 29 | #include "interface/optical_module/temperature_celsius.h" | 29 | #include "interface/optical_module/temperature_celsius.h" |
| 30 | #include "interface/optical_module/voltage.h" | 30 | #include "interface/optical_module/voltage.h" |
| 31 | +#include "test_async_wait.h" | ||
| 31 | 32 | ||
| 32 | using namespace dev; | 33 | using namespace dev; |
| 33 | 34 | ||
| @@ -888,6 +889,11 @@ public: | |||
| 888 | 889 | ||
| 889 | class BoardcomTemperatureCelsiusTest : public mc::test::TestWithEngine { | 890 | class BoardcomTemperatureCelsiusTest : public mc::test::TestWithEngine { |
| 890 | protected: | 891 | protected: |
| 892 | + // Tests that call get_om_temp_by_ncsi() directly only need start() to | ||
| 893 | + // inject the NCSI transport. Keep the periodic callback outside the test | ||
| 894 | + // window so it cannot race the direct call and overwrite its assertion. | ||
| 895 | + static constexpr mc::milliseconds MANUAL_POLL_INTERVAL{60000}; | ||
| 896 | + | ||
| 891 | void SetUp() override | 897 | void SetUp() override |
| 892 | { | 898 | { |
| 893 | m_owner = mc::make_shared<TemperatureCelsiusTestObject>(); | 899 | m_owner = mc::make_shared<TemperatureCelsiusTestObject>(); |
| @@ -999,7 +1005,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_NullNcsi_ReturnsFalse) | |||
| 999 | 1005 | ||
| 1000 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_RequestHandlerReturnsFalse_ReturnsFalse) | 1006 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_RequestHandlerReturnsFalse_ReturnsFalse) |
| 1001 | { | 1007 | { |
| 1002 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1008 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1003 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { | 1009 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { |
| 1004 | return false; | 1010 | return false; |
| 1005 | }); | 1011 | }); |
| @@ -1009,7 +1015,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_RequestHandlerReturnsFals | |||
| 1009 | 1015 | ||
| 1010 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_EmptyRsp_ReturnsFalse) | 1016 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_EmptyRsp_ReturnsFalse) |
| 1011 | { | 1017 | { |
| 1012 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1018 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1013 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1019 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1014 | rsp.clear(); | 1020 | rsp.clear(); |
| 1015 | return true; | 1021 | return true; |
| @@ -1020,7 +1026,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_EmptyRsp_ReturnsFalse) | |||
| 1020 | 1026 | ||
| 1021 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_ShortRsp_ReturnsFalse) | 1027 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_ShortRsp_ReturnsFalse) |
| 1022 | { | 1028 | { |
| 1023 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1029 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1024 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1030 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1025 | rsp = std::string(OEM_BROADCOM_TEMP_RSP_LEN - 1, '\0'); | 1031 | rsp = std::string(OEM_BROADCOM_TEMP_RSP_LEN - 1, '\0'); |
| 1026 | return true; | 1032 | return true; |
| @@ -1031,7 +1037,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_ShortRsp_ReturnsFalse) | |||
| 1031 | 1037 | ||
| 1032 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidRspCode_ReturnsFalse) | 1038 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidRspCode_ReturnsFalse) |
| 1033 | { | 1039 | { |
| 1034 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1040 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1035 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1041 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1036 | rsp = make_temp_rsp_bytes(45, 70, BROADCOM_MANUFACTURE_ID_REP, /*response_code=*/0x0001); | 1042 | rsp = make_temp_rsp_bytes(45, 70, BROADCOM_MANUFACTURE_ID_REP, /*response_code=*/0x0001); |
| 1037 | return true; | 1043 | return true; |
| @@ -1042,7 +1048,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidRspCode_ReturnsFal | |||
| 1042 | 1048 | ||
| 1043 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_BadManufacturerId_ReturnsFalse) | 1049 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_BadManufacturerId_ReturnsFalse) |
| 1044 | { | 1050 | { |
| 1045 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1051 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1046 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1052 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1047 | rsp = make_temp_rsp_bytes(45, 70, /*manufacturer_id=*/0xDEADBEEF); | 1053 | rsp = make_temp_rsp_bytes(45, 70, /*manufacturer_id=*/0xDEADBEEF); |
| 1048 | return true; | 1054 | return true; |
| @@ -1054,7 +1060,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_BadManufacturerId_Returns | |||
| 1054 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailSmbiosNotReady_Sets0xFFFF) | 1060 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailSmbiosNotReady_Sets0xFFFF) |
| 1055 | { | 1061 | { |
| 1056 | // temp_info 失败 + SmBiosStatus != 3 → 设为 0xFFFF 并 snapshot | 1062 | // temp_info 失败 + SmBiosStatus != 3 → 设为 0xFFFF 并 snapshot |
| 1057 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1063 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1058 | m_owner->m_aoem.SmBiosStatus.set_value(static_cast<uint8_t>(0)); | 1064 | m_owner->m_aoem.SmBiosStatus.set_value(static_cast<uint8_t>(0)); |
| 1059 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { | 1065 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { |
| 1060 | return false; | 1066 | return false; |
| @@ -1067,7 +1073,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailSmbiosNotReady_Sets0x | |||
| 1067 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailSmbiosReady_NoChange) | 1073 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailSmbiosReady_NoChange) |
| 1068 | { | 1074 | { |
| 1069 | // temp_info 失败 + SmBiosStatus == 3 → 不写值,直接 return | 1075 | // temp_info 失败 + SmBiosStatus == 3 → 不写值,直接 return |
| 1070 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1076 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1071 | m_owner->m_tc.TemperatureCelsius.set_value(42.0); | 1077 | m_owner->m_tc.TemperatureCelsius.set_value(42.0); |
| 1072 | m_owner->m_aoem.SmBiosStatus.set_value(static_cast<uint8_t>(3)); | 1078 | m_owner->m_aoem.SmBiosStatus.set_value(static_cast<uint8_t>(3)); |
| 1073 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { | 1079 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) { |
| @@ -1086,7 +1092,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailNoNetworkAdapterOem_N | |||
| 1086 | tc2->set_optical_module_oem(&m_owner->m_oem); | 1092 | tc2->set_optical_module_oem(&m_owner->m_oem); |
| 1087 | // set_network_adapter_oem 故意不调用 | 1093 | // set_network_adapter_oem 故意不调用 |
| 1088 | tc2->set_owner(m_owner.get()); | 1094 | tc2->set_owner(m_owner.get()); |
| 1089 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1095 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1090 | // tc2 没 start_ncsi_update_task,ncsi 为 null → 走最早 return false | 1096 | // tc2 没 start_ncsi_update_task,ncsi 为 null → 走最早 return false |
| 1091 | EXPECT_FALSE(tc2->get_om_temp_by_ncsi(0)); | 1097 | EXPECT_FALSE(tc2->get_om_temp_by_ncsi(0)); |
| 1092 | m_owner->m_tc.stop_ncsi_update_task(); | 1098 | m_owner->m_tc.stop_ncsi_update_task(); |
| @@ -1094,7 +1100,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_FailNoNetworkAdapterOem_N | |||
| 1094 | 1100 | ||
| 1095 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_NormalTemp_SetsValue) | 1101 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_NormalTemp_SetsValue) |
| 1096 | { | 1102 | { |
| 1097 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1103 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1098 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(1)); | 1104 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(1)); |
| 1099 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1105 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1100 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(50)); | 1106 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(50)); |
| @@ -1108,7 +1114,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_NormalTemp_SetsValue) | |||
| 1108 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempAndPresent_Sets0x8000) | 1114 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempAndPresent_Sets0x8000) |
| 1109 | { | 1115 | { |
| 1110 | // CHIP_TEMP_INVALID (0xFF) + Presence != 0 + m_optical_module != nullptr → 设为 0x8000 | 1116 | // CHIP_TEMP_INVALID (0xFF) + Presence != 0 + m_optical_module != nullptr → 设为 0x8000 |
| 1111 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1117 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1112 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(1)); | 1118 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(1)); |
| 1113 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1119 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1114 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); | 1120 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); |
| @@ -1122,7 +1128,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempAndPresent_Set | |||
| 1122 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempNotPresent_NoAlarm) | 1128 | TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempNotPresent_NoAlarm) |
| 1123 | { | 1129 | { |
| 1124 | // CHIP_TEMP_INVALID + Presence == 0 → 走正常路径,保留 cur_temp 值 | 1130 | // CHIP_TEMP_INVALID + Presence == 0 → 走正常路径,保留 cur_temp 值 |
| 1125 | - m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1131 | + m_owner->m_tc.start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1126 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(0)); | 1132 | m_owner->m_om.Presence.set_value(static_cast<uint8_t>(0)); |
| 1127 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1133 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1128 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); | 1134 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); |
| @@ -1140,7 +1146,7 @@ TEST_F(BoardcomTemperatureCelsiusTest, GetOmTempByNcsi_InvalidTempNoOpticalModul | |||
| 1140 | tc2->set_optical_module_oem(&m_owner->m_oem); | 1146 | tc2->set_optical_module_oem(&m_owner->m_oem); |
| 1141 | tc2->set_network_adapter_oem(&m_owner->m_aoem); | 1147 | tc2->set_network_adapter_oem(&m_owner->m_aoem); |
| 1142 | tc2->set_owner(m_owner.get()); | 1148 | tc2->set_owner(m_owner.get()); |
| 1143 | - tc2->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0, 0); | 1149 | + tc2->start_ncsi_update_task_broadcom(m_ncsi, MANUAL_POLL_INTERVAL, 0, 0); |
| 1144 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1150 | dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1145 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); | 1151 | rsp = make_temp_rsp_bytes(static_cast<int8_t>(CHIP_TEMP_INVALID)); |
| 1146 | return true; | 1152 | return true; |
| @@ -1311,11 +1317,27 @@ TEST_F(BoardcomOpticalModuleTest, GetPresenceInfoByNcsi_NotPresent_SetsPresenceT | |||
| 1311 | m_optical_module->stop_ncsi_update_task(); | 1317 | m_optical_module->stop_ncsi_update_task(); |
| 1312 | } | 1318 | } |
| 1313 | 1319 | ||
| 1320 | +TEST_F(BoardcomOpticalModuleTest, ReplacingRetainedTimerStopsPreviousTimer) | ||
| 1321 | +{ | ||
| 1322 | + m_mctp = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE_NCSI, ""); | ||
| 1323 | + m_ncsi = std::make_shared<dev::ncsi_over_mctp_broadcom>(m_mctp); | ||
| 1324 | + m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(60000), 0); | ||
| 1325 | + auto previous = find_timer(*m_optical_module); | ||
| 1326 | + EXPECT_NE(previous, nullptr); | ||
| 1327 | + m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(60000), 1); | ||
| 1328 | + if (previous) { | ||
| 1329 | + EXPECT_FALSE(previous->is_active()); | ||
| 1330 | + // Keep failure paths safe on the original implementation too. | ||
| 1331 | + previous->stop(); | ||
| 1332 | + } | ||
| 1333 | + m_optical_module->stop_ncsi_update_task(); | ||
| 1334 | +} | ||
| 1335 | + | ||
| 1314 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_TimerFiresInvokesGetPresenceInfo) | 1336 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_TimerFiresInvokesGetPresenceInfo) |
| 1315 | { | 1337 | { |
| 1316 | - int call_count = 0; | 1338 | + RequestCounter calls; |
| 1317 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1339 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1318 | - ++call_count; | 1340 | + calls.record(); |
| 1319 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); | 1341 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); |
| 1320 | return true; | 1342 | return true; |
| 1321 | }); | 1343 | }); |
| @@ -1325,15 +1347,13 @@ TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_TimerFiresInvokesGetPresen | |||
| 1325 | m_optical_module->set_port_id(1); | 1347 | m_optical_module->set_port_id(1); |
| 1326 | 1348 | ||
| 1327 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); | 1349 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); |
| 1328 | - std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 1350 | + EXPECT_TRUE(calls.wait_for(1)); |
| 1329 | - EXPECT_GT(call_count, 0); | 1351 | + m_optical_module->stop_ncsi_update_task(); |
| 1330 | EXPECT_EQ(m_optical_module->Presence.get_value(), 1); | 1352 | EXPECT_EQ(m_optical_module->Presence.get_value(), 1); |
| 1331 | 1353 | ||
| 1332 | - m_optical_module->stop_ncsi_update_task(); | 1354 | + // No further request may arrive after stop has joined the callback. |
| 1333 | - // 停止后 call_count 不再增加 | 1355 | + const int prev_count = calls.count(); |
| 1334 | - int prev_count = call_count; | 1356 | + EXPECT_FALSE(calls.wait_for(prev_count + 1, std::chrono::milliseconds(80))); |
| 1335 | - std::this_thread::sleep_for(std::chrono::milliseconds(80)); | ||
| 1336 | - EXPECT_EQ(call_count, prev_count); | ||
| 1337 | } | 1357 | } |
| 1338 | 1358 | ||
| 1339 | TEST_F(BoardcomOpticalModuleTest, StopNcsiUpdateTask_WithoutStart_NoCrash) | 1359 | TEST_F(BoardcomOpticalModuleTest, StopNcsiUpdateTask_WithoutStart_NoCrash) |
| @@ -1393,9 +1413,9 @@ TEST_F(BoardcomOpticalModuleTest, SnapshotPropWithoutBma_OemSet_UpdatePropInvoke | |||
| 1393 | // 多次启动定时器(覆盖 m_ncsi_timer 重新赋值、m_package_id 更新) | 1413 | // 多次启动定时器(覆盖 m_ncsi_timer 重新赋值、m_package_id 更新) |
| 1394 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_CalledTwice_ReplacesTimer) | 1414 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_CalledTwice_ReplacesTimer) |
| 1395 | { | 1415 | { |
| 1396 | - int call_count = 0; | 1416 | + RequestCounter calls; |
| 1397 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1417 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1398 | - ++call_count; | 1418 | + calls.record(); |
| 1399 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); | 1419 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); |
| 1400 | return true; | 1420 | return true; |
| 1401 | }); | 1421 | }); |
| @@ -1405,22 +1425,20 @@ TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_CalledTwice_ReplacesTimer) | |||
| 1405 | m_optical_module->set_port_id(1); | 1425 | m_optical_module->set_port_id(1); |
| 1406 | 1426 | ||
| 1407 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); | 1427 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); |
| 1408 | - std::this_thread::sleep_for(std::chrono::milliseconds(60)); | 1428 | + EXPECT_TRUE(calls.wait_for(1)); |
| 1409 | - int prev = call_count; | ||
| 1410 | - EXPECT_GT(prev, 0); | ||
| 1411 | // 第二次 start:覆盖 m_ncsi_timer 和 m_package_id | 1429 | // 第二次 start:覆盖 m_ncsi_timer 和 m_package_id |
| 1412 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 1); | 1430 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 1); |
| 1413 | - std::this_thread::sleep_for(std::chrono::milliseconds(60)); | 1431 | + const int prev = calls.count(); |
| 1414 | - EXPECT_GT(call_count, prev); | 1432 | + EXPECT_TRUE(calls.wait_for(prev + 1)); |
| 1415 | m_optical_module->stop_ncsi_update_task(); | 1433 | m_optical_module->stop_ncsi_update_task(); |
| 1416 | } | 1434 | } |
| 1417 | 1435 | ||
| 1418 | // start + 触发 timer + stop 后立即重启 + stop | 1436 | // start + 触发 timer + stop 后立即重启 + stop |
| 1419 | TEST_F(BoardcomOpticalModuleTest, StartStopStartStop_StopsTimerBetweenCycles) | 1437 | TEST_F(BoardcomOpticalModuleTest, StartStopStartStop_StopsTimerBetweenCycles) |
| 1420 | { | 1438 | { |
| 1421 | - int call_count = 0; | 1439 | + RequestCounter calls; |
| 1422 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1440 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1423 | - ++call_count; | 1441 | + calls.record(); |
| 1424 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); | 1442 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); |
| 1425 | return true; | 1443 | return true; |
| 1426 | }); | 1444 | }); |
| @@ -1429,17 +1447,16 @@ TEST_F(BoardcomOpticalModuleTest, StartStopStartStop_StopsTimerBetweenCycles) | |||
| 1429 | m_ncsi = std::make_shared<dev::ncsi_over_mctp_broadcom>(m_mctp); | 1447 | m_ncsi = std::make_shared<dev::ncsi_over_mctp_broadcom>(m_mctp); |
| 1430 | m_optical_module->set_port_id(1); | 1448 | m_optical_module->set_port_id(1); |
| 1431 | 1449 | ||
| 1432 | - // 第一次:start → 跑 30ms → stop | 1450 | + // First cycle must actually issue a request before stopping. |
| 1433 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); | 1451 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); |
| 1434 | - std::this_thread::sleep_for(std::chrono::milliseconds(30)); | 1452 | + EXPECT_TRUE(calls.wait_for(1)); |
| 1435 | m_optical_module->stop_ncsi_update_task(); | 1453 | m_optical_module->stop_ncsi_update_task(); |
| 1436 | - int after_first_stop = call_count; | 1454 | + const int after_first_stop = calls.count(); |
| 1437 | 1455 | ||
| 1438 | - // 第二次:start → 跑 30ms → stop | 1456 | + // Restart must issue a new request after the first stop. |
| 1439 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); | 1457 | m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(10), 0); |
| 1440 | - std::this_thread::sleep_for(std::chrono::milliseconds(30)); | 1458 | + EXPECT_TRUE(calls.wait_for(after_first_stop + 1)); |
| 1441 | m_optical_module->stop_ncsi_update_task(); | 1459 | m_optical_module->stop_ncsi_update_task(); |
| 1442 | - EXPECT_GT(call_count, after_first_stop); | ||
| 1443 | } | 1460 | } |
| 1444 | 1461 | ||
| 1445 | // 连续 stop 不崩溃(覆盖 stop 内部 m_ncsi_timer 已被 reset 的分支) | 1462 | // 连续 stop 不崩溃(覆盖 stop 内部 m_ncsi_timer 已被 reset 的分支) |
| @@ -1537,19 +1554,18 @@ TEST_F(BoardcomOpticalModuleTest, GetPresenceInfoByNcsi_MediaInfoRspCodeFail) | |||
| 1537 | // 覆盖 set_single_shot(false) 路径 | 1554 | // 覆盖 set_single_shot(false) 路径 |
| 1538 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_StartsNonSingleShotTimer) | 1555 | TEST_F(BoardcomOpticalModuleTest, StartNcsiUpdateTask_StartsNonSingleShotTimer) |
| 1539 | { | 1556 | { |
| 1540 | - m_mctp = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE_NCSI, ""); | 1557 | + m_mctp = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE_NCSI, ""); |
| 1541 | - m_ncsi = std::make_shared<dev::ncsi_over_mctp_broadcom>(m_mctp); | 1558 | + m_ncsi = std::make_shared<dev::ncsi_over_mctp_broadcom>(m_mctp); |
| 1542 | - int call_count = 0; | 1559 | + RequestCounter calls; |
| 1543 | - dev::set_mctp_request_handler([&call_count](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { | 1560 | + dev::set_mctp_request_handler([&calls](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) { |
| 1544 | - ++call_count; | 1561 | + calls.record(); |
| 1545 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); | 1562 | rsp = make_media_info_rsp_bytes(MEDIA_BIT_FIBER); |
| 1546 | return true; | 1563 | return true; |
| 1547 | }); | 1564 | }); |
| 1548 | - m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); | ||
| 1549 | m_optical_module->set_port_id(1); | 1565 | m_optical_module->set_port_id(1); |
| 1550 | - std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 1566 | + m_optical_module->start_ncsi_update_task_broadcom(m_ncsi, mc::milliseconds(20), 0); |
| 1551 | - // 非单次触发器,应该多次调用 | 1567 | + // Two real requests distinguish a repeating timer from a single shot. |
| 1552 | - EXPECT_GT(call_count, 1); | 1568 | + EXPECT_TRUE(calls.wait_for(2)); |
| 1553 | m_optical_module->stop_ncsi_update_task(); | 1569 | m_optical_module->stop_ncsi_update_task(); |
| 1554 | } | 1570 | } |
| 1555 | 1571 | ||


💡 建议:验收文档声称“跳过 0”,与 PR 描述声明的 4 个既有 ScannerTest 跳过用例不一致
docs/gtest-stress-50-20260927.md(第 9、35 行)写明最终 50 轮“跳过 0,不完整 0”,并要求suites_without_cases为空。但 PR 描述反复声明“每轮全量测试包含 4 个既有 ScannerTest 跳过用例,报告单独计数”;且按当前scripts/run_memory_audit_stress.py的no_cases判定,这类 suite 是 OK 但 0 个 RUN 用例,会落入no_cases使passed=False——这正是此前检视指出的“无法作为通过/失败门禁”问题。修复建议:两份文档需互相佐证——若最终构建已消除这 4 个跳过用例(
跳过 0成立),请同步更新 PR 描述的“4 个既有 ScannerTest 跳过用例”表述;若 4 个用例仍跳过,则本验收文档的“跳过 0 / suites_without_cases 为空”与脚本统计逻辑矛盾,需说明这些 suite 如何在no_cases判定下被“报告单独计数”而不计为错误。