* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* openUBMC is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include <mc/exception.h>
#include <mc/filesystem.h>
#include <mc/log.h>
#ifndef __ZEPHYR__
#include <dlfcn.h>
#endif
#include <devmon-common/device_driver.h>
#include <devmon-common/driver_abi.h>
#include <devmon-common/driver_source.h>
#include <devmon-common/model_privilege.h>
namespace devmon {
device_driver::device_driver(const std::string& driver_name) : m_driver_name(driver_name)
{}
device_driver::~device_driver()
{
unload();
}
std::vector<driver_load_info> device_driver::s_load_info_table;
std::mutex device_driver::s_load_info_mutex;
mc::string dump_device_driver(const device_driver_t& driver, driver_handle_t handle)
{
if (driver.dump == nullptr) {
return {};
}
const char* dump_cstr = driver.dump(handle);
return dump_cstr != nullptr ? mc::string(dump_cstr) : mc::string();
}
void device_driver::load(const std::string& driver_path)
{
std::string driver_file_name = "lib" + m_driver_name + ".so";
load_file(driver_path + "/" + driver_file_name);
}
void device_driver::load_file(const std::string& library_file)
{
load_file(library_file, {});
}
void device_driver::load_file(const std::string& library_file, const driver_load_meta& meta)
{
#ifdef __ZEPHYR__
(void)library_file;
(void)meta;
MC_THROW(mc::system_exception, "Dynamic driver loading is not supported on Zephyr for driver: ${name}",
("name", m_driver_name));
#else
MC_ASSERT_THROW(m_driver_handle == nullptr, mc::invalid_arg_exception, "Driver already loaded: ${name}",
("name", m_driver_name));
auto start = std::chrono::steady_clock::now();
driver_load_info info;
info.success = false;
info.load_time_ms = 0.0;
info.device_count = 0;
info.driver_name = mc::filesystem::path(library_file).filename().string();
info.source = meta.source.empty() ? "image" : meta.source;
info.load_path = meta.load_path;
if (info.load_path.empty()) {
info.load_path = mc::filesystem::path(library_file).parent_path().string();
if (!info.load_path.empty() && info.load_path.back() != '/') {
info.load_path += "/";
}
}
info.checksum_result = meta.checksum_result.empty() ? "na" : meta.checksum_result;
info.gold_checksum_result = meta.gold_checksum_result;
info.temp_checksum_result = meta.temp_checksum_result;
ilog("Trying to load driver from: ${file}", ("file", library_file));
auto* driver_handle = dlopen(library_file.c_str(), RTLD_LAZY);
if (driver_handle == nullptr) {
std::string error_msg = dlerror();
info.error_msg = error_msg;
info.success = false;
info.load_time_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
record_load_info(std::move(info));
MC_THROW(mc::system_exception, "Failed to load driver: ${error}", ("error", error_msg));
}
m_driver_handle = driver_handle;
bool registration_failed = false;
std::string registration_error;
try {
register_driver();
info.success = true;
info.device_count = m_devices.size();
} catch (const std::exception& e) {
registration_error = e.what();
registration_failed = true;
} catch (...) {
registration_error = "unknown driver registration error";
registration_failed = true;
}
info.load_time_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
if (registration_failed) {
info.error_msg = registration_error;
unload();
}
record_load_info(std::move(info));
if (registration_failed) {
MC_THROW(mc::bad_function_call_exception, "Failed to register driver: ${error}", ("error", registration_error));
}
dlog("Driver ${name} loaded successfully from ${file}", ("name", m_driver_name)("file", library_file));
#endif
}
void device_driver::unload()
{
#ifndef __ZEPHYR__
void* handle = m_driver_handle;
m_driver_handle = nullptr;
if (handle == nullptr) {
ilog("Driver ${name} not loaded", ("name", m_driver_name));
return;
}
m_devices.clear();
dlclose(handle);
#endif
}
void device_driver::register_driver()
{
#ifdef __ZEPHYR__
MC_THROW(mc::bad_function_call_exception,
"register_driver via dlsym is not supported on Zephyr for driver: ${name}", ("name", m_driver_name));
#else
register_device_driver_func func =
reinterpret_cast<register_device_driver_func>(dlsym(m_driver_handle, "register_device_driver"));
MC_ASSERT_THROW(func, mc::bad_function_call_exception, "Failed to get export_device_driver function: ${error}",
("error", dlerror()));
device_driver_t* devices = nullptr;
uint8_t devices_count = 0;
status_t ret = func(&devices, &devices_count);
MC_ASSERT_THROW(ret == STATUS_OK, mc::bad_function_call_exception, "Failed to get device driver manager: ${error}",
("error", ret));
m_devices.clear();
m_devices.reserve(devices_count);
for (uint8_t i = 0; i < devices_count; i++) {
m_devices.emplace_back(std::make_shared<device_driver_t>(devices[i]));
}
#endif
}
device_driver_t& device_driver::get_device(std::string& device_name)
{
MC_ASSERT_THROW(!m_devices.empty(), mc::invalid_arg_exception, "Device driver not loaded: ${name}",
("name", m_driver_name));
for (auto& device : m_devices) {
if (device->device_name == device_name) {
return *device;
}
}
MC_THROW(mc::invalid_arg_exception, "Device driver not found: ${name}", ("name", device_name));
}
const device_drivers& device_driver::get_devices() const
{
return m_devices;
}
void device_driver::set_device_driver(device_drivers* device_drivers)
{
m_devices = *device_drivers;
}
std::string_view device_driver::get_name()
{
return m_driver_name;
}
std::vector<driver_load_info> device_driver::get_load_info_snapshot()
{
std::lock_guard<std::mutex> lock(s_load_info_mutex);
return s_load_info_table;
}
void device_driver::record_load_info(driver_load_info&& info)
{
std::lock_guard<std::mutex> lock(s_load_info_mutex);
for (auto& existing : s_load_info_table) {
if (existing.driver_name == info.driver_name) {
existing = std::move(info);
return;
}
}
s_load_info_table.push_back(std::move(info));
}
void device_driver::remove_load_info(const std::string& driver_name)
{
std::lock_guard<std::mutex> lock(s_load_info_mutex);
s_load_info_table.erase(std::remove_if(s_load_info_table.begin(), s_load_info_table.end(),
[&driver_name](const driver_load_info& info) {
return info.driver_name == driver_name;
}),
s_load_info_table.end());
}
device_driver_factory& device_driver_factory::get_instance()
{
static device_driver_factory instance;
return instance;
}
void device_driver_factory::set_driver_path(const std::string& driver_path)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_driver_path = driver_path;
}
void device_driver_factory::set_flash_driver_paths(const std::string& gold_path, const std::string& temp_path)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_gold_driver_path = gold_path;
m_temp_driver_path = temp_path;
m_flash_paths_explicit = !gold_path.empty() || !temp_path.empty();
}
std::string device_driver_factory::make_driver_cache_key(std::string_view driver_name, driver_abi_version version) const
{
return std::string(driver_name) + "@" + std::string(to_string(version));
}
void device_driver_factory::get_driver_path(std::string& driver_path) const
{
std::lock_guard<std::mutex> lock(m_mutex);
driver_path = m_driver_path;
}
bool device_driver_factory::is_production_driver_path(const std::string& driver_path) const
{
std::string normalized = driver_path;
while (!normalized.empty() && normalized.back() == '/') {
normalized.pop_back();
}
return normalized == "/opt/bmc/drivers";
}
void device_driver_factory::get_flash_driver_paths(std::string& gold_path, std::string& temp_path) const
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_flash_paths_explicit) {
gold_path = m_gold_driver_path;
temp_path = m_temp_driver_path;
} else if (is_production_driver_path(m_driver_path)) {
gold_path = driver_source::k_default_gold_directory;
temp_path = driver_source::k_default_temp_directory;
} else {
gold_path.clear();
temp_path.clear();
}
}
device_driver_factory::driver_ptr device_driver_factory::find_driver(const std::string& cache_key) const
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_drivers.find(cache_key);
return it != m_drivers.end() ? it->second : nullptr;
}
bool device_driver_factory::remove_driver_by_key(const std::string& cache_key)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_drivers.find(cache_key);
if (it != m_drivers.end()) {
m_drivers.erase(it);
return true;
}
return false;
}
void device_driver_factory::set_or_create_driver(const std::string& cache_key, const std::string& driver_name,
device_drivers* device_drivers)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_drivers.find(cache_key);
if (it != m_drivers.end()) {
it->second->set_device_driver(device_drivers);
return;
}
driver_ptr driver = std::make_shared<device_driver>(driver_name);
driver->set_device_driver(device_drivers);
m_drivers.emplace(cache_key, driver);
}
std::vector<std::string> device_driver_factory::build_driver_candidates(std::string_view driver_name,
driver_abi_version version) const
{
const std::string library_name(driver_name);
std::string driver_path;
get_driver_path(driver_path);
const std::string path_separator = (!driver_path.empty() && driver_path.back() == '/') ? "" : "/";
const std::string base_path = driver_path + path_separator + "lib";
if (version == driver_abi_version::abi_v1) {
return {
base_path + "Csr1" + library_name + ".so",
base_path + library_name + ".so",
};
}
return {base_path + library_name + ".so"};
}
std::shared_ptr<device_driver> device_driver_factory::get_device_driver(const std::string& driver_name)
{
try {
return get_device_driver(driver_name, driver_abi_version::abi_v2);
} catch (const std::exception& e) {
MC_THROW(mc::invalid_arg_exception, "${error}", ("error", e.what()));
}
}
std::shared_ptr<device_driver> device_driver_factory::get_device_driver(const std::string& driver_name,
driver_abi_version abi_version)
{
const std::string cache_key = make_driver_cache_key(driver_name, abi_version);
auto cached = find_driver(cache_key);
if (cached) {
return cached;
}
driver_ptr selected_driver;
std::exception_ptr selected_error;
driver_load_meta no_candidate_meta;
const auto selection_result = try_load_selected_driver(cache_key, driver_name, abi_version, selected_driver,
selected_error, no_candidate_meta);
if (selection_result == selected_driver_load_result::loaded) {
return selected_driver;
}
if (selection_result == selected_driver_load_result::failed && selected_error) {
std::rethrow_exception(selected_error);
}
std::exception_ptr last_error;
for (const auto& candidate : build_driver_candidates(driver_name, abi_version)) {
try {
driver_ptr driver = std::make_shared<device_driver>(driver_name);
driver->load_file(candidate, no_candidate_meta);
driver_ptr cached_driver;
bool inserted = false;
{
std::lock_guard<std::mutex> lock(m_mutex);
auto insert_result = m_drivers.emplace(cache_key, driver);
cached_driver = insert_result.first->second;
inserted = insert_result.second;
}
if (!inserted) {
driver->unload();
ilog("Reused cached driver ${name}@${abi} after concurrent load",
("name", driver_name)("abi", to_string(abi_version)));
return cached_driver;
}
apply_model_privileges();
ilog("Loaded driver ${name}@${abi} from ${file}",
("name", driver_name)("abi", to_string(abi_version))("file", candidate));
return cached_driver;
} catch (...) {
wlog("unexpected exception loading driver file ${file}", ("file", candidate));
last_error = std::current_exception();
}
}
if (last_error) {
std::rethrow_exception(last_error);
}
MC_THROW(mc::invalid_arg_exception, "Driver not found: ${name}@${abi}",
("name", driver_name)("abi", to_string(abi_version)));
}
device_driver_factory::selected_driver_load_result
device_driver_factory::try_load_selected_driver(const std::string& cache_key, const std::string& driver_name,
driver_abi_version abi_version, driver_ptr& out_driver,
std::exception_ptr& last_error, driver_load_meta& no_candidate_meta)
{
std::string image_dir;
std::string gold_dir;
std::string temp_dir;
get_driver_path(image_dir);
get_flash_driver_paths(gold_dir, temp_dir);
std::vector<std::string> names;
if (abi_version == driver_abi_version::abi_v1) {
names.push_back("Csr1" + driver_name);
}
names.push_back(driver_name);
auto load_selection = [this, &cache_key, &driver_name, &out_driver,
&last_error](const driver_source::selection& selection) {
if (!selection.file.has_value()) {
return false;
}
const auto& selected = *selection.file;
try {
driver_ptr driver = std::make_shared<device_driver>(driver_name);
driver_load_meta meta{driver_source::location_name(selected.source), selected.directory,
selected.source == driver_source::location::image ? "na" : "pass",
selection.gold_checksum, selection.temp_checksum};
driver->load_file(selected.path, meta);
driver_ptr cached_driver;
bool inserted = false;
{
std::lock_guard<std::mutex> lock(m_mutex);
auto result = m_drivers.emplace(cache_key, driver);
cached_driver = result.first->second;
inserted = result.second;
}
out_driver = cached_driver;
if (!inserted) {
driver->unload();
} else {
apply_model_privileges();
}
return true;
} catch (const std::exception& e) {
elog("Failed to load driver ${name}: ${error}", ("name", driver_name)("error", e.what()));
last_error = std::current_exception();
return false;
} catch (...) {
elog("Failed to load driver ${name}: unknown error", ("name", driver_name));
last_error = std::current_exception();
return false;
}
};
bool attempted = false;
for (const auto& name : names) {
const auto primary = driver_source::select_driver_file(name, image_dir, gold_dir, temp_dir);
if (!primary.file.has_value()) {
no_candidate_meta = {"image", "", "na", primary.gold_checksum, primary.temp_checksum};
continue;
}
attempted = true;
if (load_selection(primary)) {
return selected_driver_load_result::loaded;
}
const auto source = primary.file->source;
if (source == driver_source::location::gold) {
auto temp = driver_source::select_driver_file(name, "", "", temp_dir);
temp.gold_checksum = primary.gold_checksum;
if (temp.file.has_value()) {
attempted = true;
}
if (load_selection(temp)) {
return selected_driver_load_result::loaded;
}
auto image = driver_source::select_driver_file(name, image_dir, "", "");
image.gold_checksum = primary.gold_checksum;
image.temp_checksum = temp.temp_checksum;
if (image.file.has_value()) {
attempted = true;
}
if (load_selection(image)) {
return selected_driver_load_result::loaded;
}
} else if (source == driver_source::location::temp) {
auto image = driver_source::select_driver_file(name, image_dir, "", "");
image.gold_checksum = primary.gold_checksum;
image.temp_checksum = primary.temp_checksum;
if (image.file.has_value()) {
attempted = true;
}
if (load_selection(image)) {
return selected_driver_load_result::loaded;
}
}
}
return attempted ? selected_driver_load_result::failed : selected_driver_load_result::no_candidate;
}
void device_driver_factory::set_device_driver(const std::string& driver_name, device_drivers* device_drivers)
{
set_or_create_driver(driver_name, driver_name, device_drivers);
set_or_create_driver(make_driver_cache_key(driver_name, driver_abi_version::abi_v1), driver_name, device_drivers);
set_or_create_driver(make_driver_cache_key(driver_name, driver_abi_version::abi_v2), driver_name, device_drivers);
}
void device_driver_factory::set_device_driver(const std::string& driver_name, driver_abi_version abi_version,
device_drivers* device_drivers)
{
set_or_create_driver(make_driver_cache_key(driver_name, abi_version), driver_name, device_drivers);
}
void device_driver_factory::remove_device_driver(const std::string& driver_name)
{
device_driver::remove_load_info("lib" + driver_name + ".so");
device_driver::remove_load_info("libCsr1" + driver_name + ".so");
remove_driver_by_key(driver_name);
remove_driver_by_key(make_driver_cache_key(driver_name, driver_abi_version::abi_v1));
remove_driver_by_key(make_driver_cache_key(driver_name, driver_abi_version::abi_v2));
}
void device_driver_factory::remove_device_driver(const std::string& driver_name, driver_abi_version abi_version)
{
remove_driver_by_key(make_driver_cache_key(driver_name, abi_version));
}
}