Line data Source code
1 : /**
2 : * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include "nic_plugin_manager.h"
12 :
13 : #include <acl/acl_rt.h>
14 : #include <dirent.h>
15 : #include <dlfcn.h>
16 : #include <algorithm>
17 : #include <cstddef>
18 : #include <cstdlib>
19 : #include <cstring>
20 : #include <memory>
21 : #include <mutex>
22 : #include <string>
23 : #include <unordered_map>
24 : #include <vector>
25 :
26 : #include "hcomm_result_defs.h"
27 : #include "log.h"
28 : #include "param_check_pub.h"
29 :
30 : namespace hcomm {
31 : namespace {
32 : constexpr const char *HCOMM_NIC_PLUGIN_DIR = "hcomm_plugin";
33 : constexpr const char *HCOMM_NIC_PLUGIN_SO_ENV = "HCOMM_NIC_PLUGIN_SO";
34 :
35 9 : std::once_flag &LoadOnce()
36 : {
37 : static std::once_flag loadOnce;
38 9 : return loadOnce;
39 : }
40 :
41 0 : std::vector<std::unique_ptr<NicPluginEntry>> &LoadedPlugins()
42 : {
43 0 : static std::vector<std::unique_ptr<NicPluginEntry>> loadedPlugins;
44 0 : return loadedPlugins;
45 : }
46 :
47 9 : std::unordered_map<CommProtocol, const NicPluginEntry *> &ProtocolPlugins()
48 : {
49 9 : static std::unordered_map<CommProtocol, const NicPluginEntry *> protocolPlugins;
50 9 : return protocolPlugins;
51 : }
52 :
53 0 : bool EndsWithSo(const std::string &path)
54 : {
55 0 : constexpr const char *suffix = ".so";
56 0 : constexpr size_t suffixLen = 3U;
57 0 : return path.size() >= suffixLen && path.compare(path.size() - suffixLen, suffixLen, suffix) == 0;
58 : }
59 :
60 44 : bool IsOpsHeaderValid(const CommAbiHeader &header, uint32_t magicWord, uint32_t version, const char *opsName)
61 : {
62 44 : if (header.magicWord != magicWord) {
63 1 : HCCL_RUN_WARNING("[NicPlugin] %s magicWord[0x%08x] mismatch, expected[0x%08x].",
64 : opsName, header.magicWord, magicWord);
65 1 : return false;
66 : }
67 43 : if (header.version != version) {
68 1 : HCCL_RUN_WARNING("[NicPlugin] %s version[%u] mismatch, expected[%u].", opsName, header.version, version);
69 1 : return false;
70 : }
71 42 : if (header.size < sizeof(CommAbiHeader)) {
72 0 : HCCL_RUN_WARNING("[NicPlugin] %s size[%u] is smaller than ABI header[%zu].",
73 : opsName, header.size, sizeof(CommAbiHeader));
74 0 : return false;
75 : }
76 42 : return true;
77 : }
78 :
79 9 : bool ValidateEndpointOps(const HcommNicEndpointOps *ops)
80 : {
81 9 : return ops != nullptr && IsOpsHeaderValid(ops->header, HCOMM_NIC_ENDPOINT_OPS_MAGIC_WORD,
82 9 : HCOMM_NIC_ENDPOINT_OPS_VERSION, "endpoint ops");
83 : }
84 :
85 31 : bool ValidateChannelOps(const HcommNicChannelOps *ops)
86 : {
87 31 : return ops != nullptr && IsOpsHeaderValid(ops->header, HCOMM_NIC_CHANNEL_OPS_MAGIC_WORD,
88 31 : HCOMM_NIC_CHANNEL_OPS_VERSION, "channel ops");
89 : }
90 :
91 : template <typename PluginOps>
92 44 : bool IsPluginOpAvailable(const PluginOps *ops, size_t opOffset, size_t opSize)
93 : {
94 44 : return ops != nullptr && ops->header.size >= opOffset + opSize;
95 : }
96 :
97 0 : void RegisterPluginProtocols(const NicPluginEntry *plugin)
98 : {
99 0 : auto &protocolPlugins = ProtocolPlugins();
100 0 : for (uint32_t idx = 0; idx < plugin->info->protocolCount; ++idx) {
101 0 : const CommProtocol protocol = plugin->info->protocols[idx];
102 0 : auto iter = protocolPlugins.find(protocol);
103 0 : if (iter != protocolPlugins.end()) {
104 0 : HCCL_RUN_WARNING("[NicPlugin] protocol[%d] handler[%s] is overwritten by plugin[%s].",
105 : protocol,
106 : iter->second->info->name == nullptr ? "unknown" : iter->second->info->name,
107 : plugin->info->name == nullptr ? "unknown" : plugin->info->name);
108 : }
109 0 : protocolPlugins[protocol] = plugin;
110 0 : HCCL_RUN_INFO("[NicPlugin] protocol[%d] is handled by plugin[%s].",
111 : protocol, plugin->info->name == nullptr ? "unknown" : plugin->info->name);
112 : }
113 0 : }
114 :
115 0 : void *LoadSymbol(void *soHandle, const char *soPath, const char *symbol)
116 : {
117 0 : dlerror();
118 0 : void *addr = dlsym(soHandle, symbol);
119 0 : const char *dlsymErr = dlerror();
120 0 : if (dlsymErr != nullptr || addr == nullptr) {
121 0 : HCCL_RUN_WARNING("[NicPlugin] dlsym %s from %s failed: %s.",
122 : symbol, soPath, dlsymErr == nullptr ? "unknown" : dlsymErr);
123 0 : return nullptr;
124 : }
125 0 : return addr;
126 : }
127 :
128 0 : void LoadOnePlugin(const std::string &path)
129 : {
130 0 : if (path.empty()) {
131 0 : return;
132 : }
133 0 : void *soHandle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
134 0 : if (soHandle == nullptr) {
135 0 : HCCL_RUN_WARNING("[NicPlugin] dlopen %s failed: %s.", path.c_str(), dlerror());
136 0 : return;
137 : }
138 :
139 : auto getInfo = reinterpret_cast<HcommNicPluginGetInfoFunc>(
140 0 : LoadSymbol(soHandle, path.c_str(), "HcommNicPluginGetInfo"));
141 : auto createEndpoint = reinterpret_cast<HcommNicPluginCreateEndpointFunc>(
142 0 : LoadSymbol(soHandle, path.c_str(), "HcommNicPluginCreateEndpoint"));
143 : auto createChannel = reinterpret_cast<HcommNicPluginCreateChannelFunc>(
144 0 : LoadSymbol(soHandle, path.c_str(), "HcommNicPluginCreateChannel"));
145 0 : if (getInfo == nullptr || createEndpoint == nullptr || createChannel == nullptr) {
146 0 : dlclose(soHandle);
147 0 : return;
148 : }
149 :
150 0 : const HcommNicPluginInfo *info = getInfo();
151 0 : if (!ValidatePluginInfo(path.c_str(), info, createEndpoint, createChannel)) {
152 0 : dlclose(soHandle);
153 0 : return;
154 : }
155 :
156 : std::unique_ptr<NicPluginEntry> plugin(new (std::nothrow) NicPluginEntry{soHandle, info, createEndpoint,
157 0 : createChannel});
158 0 : if (plugin == nullptr) {
159 0 : HCCL_RUN_WARNING("[NicPlugin] allocate plugin entry for %s failed.", path.c_str());
160 0 : dlclose(soHandle);
161 0 : return;
162 : }
163 0 : RegisterPluginProtocols(plugin.get());
164 0 : LoadedPlugins().emplace_back(std::move(plugin));
165 0 : }
166 :
167 0 : void LoadDefaultDirectory(const std::string &pluginDir)
168 : {
169 0 : DIR *dir = opendir(pluginDir.c_str());
170 0 : if (dir == nullptr) {
171 0 : HCCL_RUN_INFO("[NicPlugin] plugin directory %s is unavailable.", pluginDir.c_str());
172 0 : return;
173 : }
174 0 : std::vector<std::string> soPaths;
175 0 : for (dirent *entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
176 0 : const std::string name(entry->d_name);
177 0 : if (name == "." || name == ".." || !EndsWithSo(name)) {
178 0 : continue;
179 : }
180 0 : soPaths.emplace_back(pluginDir + "/" + name);
181 0 : }
182 0 : closedir(dir);
183 0 : std::sort(soPaths.begin(), soPaths.end());
184 0 : for (const auto &path : soPaths) {
185 0 : LoadOnePlugin(path);
186 : }
187 0 : }
188 :
189 0 : void LoadExplicitPlugins(const char *envValue)
190 : {
191 0 : if (envValue == nullptr || envValue[0] == '\0') {
192 0 : return;
193 : }
194 0 : const std::string paths(envValue);
195 0 : size_t start = 0;
196 0 : while (start <= paths.size()) {
197 0 : const size_t end = paths.find(':', start);
198 0 : const std::string path = paths.substr(start, end == std::string::npos ? std::string::npos : end - start);
199 0 : LoadOnePlugin(path);
200 0 : if (end == std::string::npos) {
201 0 : break;
202 : }
203 0 : start = end + 1;
204 0 : }
205 0 : }
206 :
207 1 : void LoadPluginsOnce()
208 : {
209 1 : uint32_t deviceCount = 0;
210 1 : const aclError ret = aclrtGetDeviceCount(&deviceCount);
211 1 : if (ret == ACL_SUCCESS && deviceCount != 0) {
212 1 : HCCL_RUN_INFO("[NicPlugin] plugin loading skipped, aclrtGetDeviceCount ret[%d], count[%u].",
213 : ret, deviceCount);
214 1 : return;
215 : }
216 :
217 0 : const char *ascendHomePath = getenv("ASCEND_HOME_PATH");
218 0 : if (ascendHomePath != nullptr && ascendHomePath[0] != '\0') {
219 0 : LoadDefaultDirectory(std::string(ascendHomePath) + "/" + HCOMM_NIC_PLUGIN_DIR);
220 : } else {
221 0 : HCCL_RUN_INFO("[NicPlugin] ASCEND_HOME_PATH is empty, skip default plugin directory.");
222 0 : LoadExplicitPlugins(getenv(HCOMM_NIC_PLUGIN_SO_ENV));
223 : }
224 : }
225 :
226 : template <typename PluginOps>
227 4 : void DestroyPluginCtx(PluginOps *ops, void *pluginCtx)
228 : {
229 8 : if (ops != nullptr && IsPluginOpAvailable(ops, offsetof(PluginOps, destroy), sizeof(ops->destroy)) &&
230 4 : ops->destroy != nullptr) {
231 4 : ops->destroy(pluginCtx);
232 : }
233 4 : }
234 :
235 : template <typename PluginOps>
236 4 : HcommResult InitPluginCtxOrDestroy(PluginOps *ops, void *pluginCtx)
237 : {
238 4 : CHK_PTR_NULL(pluginCtx);
239 4 : CHK_PTR_NULL(ops);
240 4 : if (!IsPluginOpAvailable(ops, offsetof(PluginOps, init), sizeof(ops->init)) || ops->init == nullptr) {
241 3 : return HCCL_SUCCESS;
242 : }
243 1 : HcommResult ret = ops->init(pluginCtx);
244 1 : if (ret != HCCL_SUCCESS) {
245 0 : DestroyPluginCtx(ops, pluginCtx);
246 : }
247 1 : return ret;
248 : }
249 : } // namespace
250 :
251 4 : bool ValidatePluginInfo(const char *soPath, const HcommNicPluginInfo *info,
252 : HcommNicPluginCreateEndpointFunc createEndpoint, HcommNicPluginCreateChannelFunc createChannel)
253 : {
254 4 : if (info == nullptr) {
255 0 : HCCL_RUN_WARNING("[NicPlugin] %s exports null plugin info.", soPath);
256 0 : return false;
257 : }
258 4 : if (!IsOpsHeaderValid(info->header, HCOMM_NIC_PLUGIN_INFO_MAGIC_WORD,
259 : HCOMM_NIC_PLUGIN_INFO_VERSION, "plugin info")) {
260 2 : return false;
261 : }
262 2 : constexpr size_t requiredSize = offsetof(HcommNicPluginInfo, protocols) +
263 : sizeof(static_cast<HcommNicPluginInfo *>(nullptr)->protocols);
264 2 : if (info->header.size < requiredSize) {
265 1 : HCCL_RUN_WARNING("[NicPlugin] %s plugin info size[%u] is smaller than required[%zu].",
266 : soPath, info->header.size, requiredSize);
267 1 : return false;
268 : }
269 1 : if (info->protocolCount == 0 || info->protocolCount > HCOMM_NIC_PLUGIN_MAX_PROTOCOLS) {
270 0 : HCCL_RUN_WARNING("[NicPlugin] %s invalid protocolCount[%u].", soPath, info->protocolCount);
271 0 : return false;
272 : }
273 1 : if (createEndpoint == nullptr || createChannel == nullptr) {
274 0 : HCCL_RUN_WARNING("[NicPlugin] %s missing create endpoint/channel symbol.", soPath);
275 0 : return false;
276 : }
277 2 : for (uint32_t idx = 0; idx < info->protocolCount; ++idx) {
278 1 : const CommProtocol protocol = info->protocols[idx];
279 1 : if (protocol < COMM_PROTOCOL_HCCS || protocol > COMM_PROTOCOL_HCCS_ONLY) {
280 0 : HCCL_RUN_WARNING("[NicPlugin] %s invalid protocol[%d].", soPath, info->protocols[idx]);
281 0 : return false;
282 : }
283 : }
284 1 : return true;
285 : }
286 :
287 9 : void LoadAllNicPlugins()
288 : {
289 9 : std::call_once(LoadOnce(), LoadPluginsOnce);
290 9 : }
291 :
292 9 : const NicPluginEntry *FindHostNicPlugin(CommProtocol protocol)
293 : {
294 9 : LoadAllNicPlugins();
295 9 : const auto &protocolPlugins = ProtocolPlugins();
296 9 : auto iter = protocolPlugins.find(protocol);
297 9 : const NicPluginEntry *entry = iter == protocolPlugins.end() ? nullptr : iter->second;
298 9 : return entry;
299 : }
300 :
301 1 : HcommResult CreatePluginEndpoint(const EndpointDesc *endpoint, EndpointHandle *endpointHandle)
302 : {
303 1 : CHK_PTR_NULL(endpoint);
304 1 : CHK_PTR_NULL(endpointHandle);
305 1 : const NicPluginEntry *entry = FindHostNicPlugin(endpoint->protocol);
306 1 : if (entry == nullptr) {
307 0 : return HCCL_E_NOT_FOUND;
308 : }
309 1 : void *pluginCtx = nullptr;
310 1 : HcommNicEndpointOps *ops = nullptr;
311 1 : CHK_RET(static_cast<HcclResult>(entry->createEndpoint(endpoint, &pluginCtx, &ops)));
312 1 : CHK_PRT_RET(!ValidateEndpointOps(ops), HCCL_ERROR("[NicPlugin] invalid endpoint ops."), HCCL_E_PARA);
313 1 : HcommResult ret = InitPluginCtxOrDestroy(ops, pluginCtx);
314 1 : if (ret != HCCL_SUCCESS) {
315 0 : return ret;
316 : }
317 1 : PluginEndpointCtx *ctx = new (std::nothrow) PluginEndpointCtx{ops, pluginCtx, entry};
318 1 : if (ctx == nullptr) {
319 0 : DestroyPluginCtx(ops, pluginCtx);
320 0 : return HCCL_E_MEMORY;
321 : }
322 1 : *endpointHandle = MAKE_PLUGIN_EP_HANDLE(ctx);
323 1 : return HCCL_SUCCESS;
324 : }
325 :
326 1 : HcommResult DestroyPluginEndpoint(EndpointHandle endpointHandle)
327 : {
328 1 : PluginEndpointCtx *ctx = PLUGIN_EP_CTX(endpointHandle);
329 1 : CHK_PTR_NULL(ctx);
330 1 : DestroyPluginCtx(ctx->ops, ctx->ctx);
331 1 : delete ctx;
332 1 : return HCCL_SUCCESS;
333 : }
334 :
335 3 : HcommResult CreatePluginChannel(EndpointHandle endpointHandle, const HcommChannelDesc *channelDesc,
336 : ChannelHandle *channelHandle)
337 : {
338 3 : PluginEndpointCtx *endpointCtx = PLUGIN_EP_CTX(endpointHandle);
339 3 : CHK_PTR_NULL(endpointCtx);
340 3 : CHK_PTR_NULL(endpointCtx->entry);
341 3 : CHK_PTR_NULL(channelDesc);
342 3 : CHK_PTR_NULL(channelHandle);
343 3 : void *pluginCtx = nullptr;
344 3 : HcommNicChannelOps *ops = nullptr;
345 3 : CHK_RET(static_cast<HcclResult>(
346 : endpointCtx->entry->createChannel(endpointCtx->ctx, channelDesc, &pluginCtx, &ops)));
347 3 : CHK_PRT_RET(!ValidateChannelOps(ops), HCCL_ERROR("[NicPlugin] invalid channel ops."), HCCL_E_PARA);
348 3 : HcommResult ret = InitPluginCtxOrDestroy(ops, pluginCtx);
349 3 : if (ret != HCCL_SUCCESS) {
350 0 : return ret;
351 : }
352 3 : PluginChannelCtx *ctx = new (std::nothrow) PluginChannelCtx{ops, pluginCtx, endpointCtx->entry};
353 3 : if (ctx == nullptr) {
354 0 : DestroyPluginCtx(ops, pluginCtx);
355 0 : return HCCL_E_MEMORY;
356 : }
357 3 : *channelHandle = MAKE_PLUGIN_CH_HANDLE(ctx);
358 3 : return HCCL_SUCCESS;
359 : }
360 :
361 3 : HcommResult DestroyPluginChannel(ChannelHandle channelHandle)
362 : {
363 3 : PluginChannelCtx *ctx = PLUGIN_CH_CTX(channelHandle);
364 3 : CHK_PTR_NULL(ctx);
365 3 : DestroyPluginCtx(ctx->ops, ctx->ctx);
366 3 : delete ctx;
367 3 : return HCCL_SUCCESS;
368 : }
369 :
370 13 : HcommResult UnsupportedPluginOp(const char *opName)
371 : {
372 13 : HCCL_RUN_WARNING("[NicPlugin] plugin operation[%s] is not supported.", opName == nullptr ? "unknown" : opName);
373 13 : return HCCL_E_NOT_SUPPORT;
374 : }
375 :
376 8 : bool IsEndpointOpAvailable(const HcommNicEndpointOps *ops, size_t opOffset, size_t opSize)
377 : {
378 8 : return ValidateEndpointOps(ops) && IsPluginOpAvailable(ops, opOffset, opSize);
379 : }
380 :
381 28 : bool IsChannelOpAvailable(const HcommNicChannelOps *ops, size_t opOffset, size_t opSize)
382 : {
383 28 : return ValidateChannelOps(ops) && IsPluginOpAvailable(ops, opOffset, opSize);
384 : }
385 :
386 : } // namespace hcomm
|