Blob Blame History Raw
// Copyright(c) 2018-2020, Intel Corporation
//
// Redistribution  and  use  in source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of  source code  must retain the  above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name  of Intel Corporation  nor the names of its contributors
//   may be used to  endorse or promote  products derived  from this  software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
// IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
// LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
// CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
// SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
// INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
// CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <dirent.h>
#include <linux/limits.h>
#include <pthread.h>
#include <pwd.h>
#include <unistd.h>

#include <json-c/json.h>

#include "pluginmgr.h"
#include "opae_int.h"

#define OPAE_PLUGIN_CONFIGURE "opae_plugin_configure"
typedef int (*opae_plugin_configure_t)(opae_api_adapter_table *, const char *);

typedef struct _platform_data {
	uint16_t vendor_id;
	uint16_t device_id;
	const char *native_plugin;
	uint32_t flags;
#define OPAE_PLATFORM_DATA_DETECTED 0x00000001
#define OPAE_PLATFORM_DATA_LOADED   0x00000002
} platform_data;

static platform_data platform_data_table[] = {
	{ 0x8086, 0xbcbd, "libxfpga.so", 0 },
	{ 0x8086, 0xbcc0, "libxfpga.so", 0 },
	{ 0x8086, 0xbcc1, "libxfpga.so", 0 },
	{ 0x8086, 0x09c4, "libxfpga.so", 0 },
	{ 0x8086, 0x09c5, "libxfpga.so", 0 },
	{ 0x8086, 0x0b2b, "libxfpga.so", 0 },
	{ 0x8086, 0x0b2c, "libxfpga.so", 0 },
	{ 0x8086, 0x0b30, "libxfpga.so", 0 },
	{ 0x8086, 0x0b31, "libxfpga.so", 0 },
	{      0,      0,          NULL, 0 },
};

static int initialized;
static int finalizing;

STATIC opae_api_adapter_table *adapter_list = (void *)0;
static pthread_mutex_t adapter_list_lock =
	PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

#define MAX_PLUGINS PLUGIN_SUPPORTED_DEVICES_MAX
STATIC plugin_cfg *opae_plugin_mgr_config_list;
STATIC int opae_plugin_mgr_plugin_count;

#define CFG_PATH_MAX 64
#define HOME_CFG_PATHS 3
STATIC const char _opae_home_cfg_files[HOME_CFG_PATHS][CFG_PATH_MAX] = {
	{ "/.local/opae.cfg" },
	{ "/.local/opae/opae.cfg" },
	{ "/.config/opae/opae.cfg" },
};
#define SYS_CFG_PATHS 2
STATIC const char _opae_sys_cfg_files[SYS_CFG_PATHS][CFG_PATH_MAX] = {
	{ "/usr/local/etc/opae/opae.cfg" },
	{ "/etc/opae/opae.cfg" },
};


// Find the canonicalized configuration file. If null, the file was not found.
// Otherwise, it's the first configuration file found from a list of possible
// paths. Note: The char * returned is allocated here, caller must free.
STATIC char *find_cfg(void)
{
	int i = 0;
	char *file_name = NULL;
	char home_cfg[PATH_MAX] = { 0, };
	char *home_cfg_ptr = &home_cfg[0];
	size_t len;

	// get the user's home directory
	struct passwd *user_passwd = getpwuid(getuid());

	// first look in possible paths in the users home directory
	for (i = 0; i < HOME_CFG_PATHS; ++i) {
		len = strnlen(user_passwd->pw_dir,
			      sizeof(home_cfg) - 1);
		memcpy(home_cfg, user_passwd->pw_dir, len);
		home_cfg[len] = '\0';

		home_cfg_ptr = home_cfg + strlen(home_cfg);

		len = strnlen(_opae_home_cfg_files[i], CFG_PATH_MAX);
		memcpy(home_cfg_ptr, _opae_home_cfg_files[i], len);
		home_cfg_ptr[len] = '\0';

		file_name = canonicalize_file_name(home_cfg);
		if (file_name)
			return file_name;

		home_cfg[0] = '\0';
	}

	// now look in possible system paths
	for (i = 0; i < SYS_CFG_PATHS; ++i) {
		len = strnlen(_opae_sys_cfg_files[i], CFG_PATH_MAX);
		memcpy(home_cfg, _opae_sys_cfg_files[i], len);
		home_cfg[len] = '\0';

		file_name = canonicalize_file_name(home_cfg);
		if (file_name)
			return file_name;
	}

	return NULL;
}

STATIC void *opae_plugin_mgr_find_plugin(const char *lib_path)
{
	char plugin_path[PATH_MAX];
	const char *search_paths[] = { OPAE_MODULE_SEARCH_PATHS };
	unsigned i;
	void *dl_handle;

	for (i = 0 ;
		i < sizeof(search_paths) / sizeof(search_paths[0]) ; ++i) {

		snprintf(plugin_path, sizeof(plugin_path),
			 "%s%s", search_paths[i], lib_path);

		dl_handle = dlopen(plugin_path, RTLD_LAZY | RTLD_LOCAL);

		if (dl_handle)
			return dl_handle;
	}

	return NULL;
}

STATIC opae_api_adapter_table *opae_plugin_mgr_alloc_adapter(const char *lib_path)
{
	void *dl_handle;
	opae_api_adapter_table *adapter;

	dl_handle = opae_plugin_mgr_find_plugin(lib_path);

	if (!dl_handle) {
		char *err = dlerror();
		OPAE_ERR("failed to load \"%s\" %s", lib_path, err ? err : "");
		return NULL;
	}

	adapter = (opae_api_adapter_table *)calloc(
		1, sizeof(opae_api_adapter_table));

	if (!adapter) {
		dlclose(dl_handle);
		OPAE_ERR("out of memory");
		return NULL;
	}

	adapter->plugin.path = (char *)lib_path;
	adapter->plugin.dl_handle = dl_handle;

	return adapter;
}

STATIC int opae_plugin_mgr_free_adapter(opae_api_adapter_table *adapter)
{
	int res;
	char *err;

	res = dlclose(adapter->plugin.dl_handle);

	if (res) {
		err = dlerror();
		OPAE_ERR("dlclose failed with %d %s", res, err ? err : "");
	}

	free(adapter);

	return res;
}

STATIC int opae_plugin_mgr_configure_plugin(opae_api_adapter_table *adapter,
					    const char *config)
{
	opae_plugin_configure_t cfg;

	cfg = (opae_plugin_configure_t)dlsym(adapter->plugin.dl_handle,
					     OPAE_PLUGIN_CONFIGURE);

	if (!cfg) {
		OPAE_ERR("failed to find %s in \"%s\"", OPAE_PLUGIN_CONFIGURE,
			 adapter->plugin.path);
		return 1;
	}

	return cfg(adapter, config);
}

STATIC void opae_plugin_mgr_reset_cfg(void)
{
	plugin_cfg *ptr = opae_plugin_mgr_config_list;
	plugin_cfg *tmp = NULL;
	while (ptr) {
		tmp = ptr;
		ptr = ptr->next;
		free(tmp->cfg);
		free(tmp);
	}
	opae_plugin_mgr_config_list = NULL;
	opae_plugin_mgr_plugin_count = 0;
}

STATIC void opae_plugin_mgr_add_plugin(plugin_cfg *cfg)
{
	plugin_cfg *ptr = opae_plugin_mgr_config_list;
	cfg->next = NULL;
	if (!ptr) {
		opae_plugin_mgr_config_list = cfg;
	} else {
		while (ptr->next) {
			ptr = ptr->next;
		}
		ptr->next = cfg;
	}
	opae_plugin_mgr_plugin_count++;
}

STATIC int opae_plugin_mgr_initialize_all(void)
{
	int res;
	opae_api_adapter_table *aptr;
	int errors = 0;

	for (aptr = adapter_list; aptr; aptr = aptr->next) {

		if (aptr->initialize) {
			res = aptr->initialize();
			if (res) {
				OPAE_MSG("\"%s\" initialize() routine failed",
					 aptr->plugin.path);
				++errors;
			}
		}
	}

	return errors;
}

int opae_plugin_mgr_finalize_all(void)
{
	int res;
	opae_api_adapter_table *aptr;
	int errors = 0;
	int i = 0;

	opae_mutex_lock(res, &adapter_list_lock);

	if (finalizing)
		return 0;

	finalizing = 1;

	for (aptr = adapter_list; aptr;) {
		opae_api_adapter_table *trash;

		if (aptr->finalize) {
			res = aptr->finalize();
			if (res) {
				OPAE_MSG("\"%s\" finalize() routine failed",
					 aptr->plugin.path);
				++errors;
			}
		}

		trash = aptr;
		aptr = aptr->next;

		if (opae_plugin_mgr_free_adapter(trash))
			++errors;
	}

	adapter_list = NULL;

	// reset platforms detected to 0
	for (i = 0 ; platform_data_table[i].native_plugin ; ++i) {
		platform_data_table[i].flags = 0;
	}

	opae_plugin_mgr_reset_cfg();
	initialized = 0;
	finalizing = 0;
	opae_mutex_unlock(res, &adapter_list_lock);

	return errors;
}

#define JSON_GET(_jobj, _key, _jvar)                                           \
	do {                                                                   \
		if (!json_object_object_get_ex(_jobj, _key, _jvar)) {          \
			OPAE_ERR("Error getting object: %s", _key);            \
			return 1;                                              \
		}                                                              \
	} while (0)

#define MAX_PLUGIN_CFG_SIZE 8192
STATIC int process_plugin(const char *name, json_object *j_config)
{
	json_object *j_plugin = NULL;
	json_object *j_plugin_cfg = NULL;
	json_object *j_enabled = NULL;
	const char *stringified = NULL;
	size_t len;

	JSON_GET(j_config, "plugin", &j_plugin);
	JSON_GET(j_config, "configuration", &j_plugin_cfg);
	JSON_GET(j_config, "enabled", &j_enabled);
	if (json_object_get_string_len(j_plugin) > PLUGIN_NAME_MAX) {
		OPAE_ERR("plugin name too long");
		return 1;
	}

	plugin_cfg *cfg = malloc(sizeof(plugin_cfg));
	if (!cfg) {
		OPAE_ERR("Could not allocate memory for plugin cfg");
		return 1;
	}

	stringified = json_object_to_json_string_ext(j_plugin_cfg, JSON_C_TO_STRING_PLAIN);
	if (!stringified) {
		OPAE_ERR("error getting plugin configuration");
		free(cfg);
		return 1;
	}

	cfg->cfg_size = strlen(stringified) + 1;

	if (cfg->cfg_size >= MAX_PLUGIN_CFG_SIZE) {
		OPAE_ERR("plugin config too large");
		free(cfg);
		return 1;
	}

	cfg->cfg = malloc(cfg->cfg_size);
	if (!cfg->cfg) {
		OPAE_ERR("error allocating memory for plugin configuration");
		cfg->cfg_size = 0;
		free(cfg);
		return 1;
	}

	len = strnlen(stringified, cfg->cfg_size - 1);
	memcpy(cfg->cfg, stringified, len);
	cfg->cfg[len] = '\0';

	len = strnlen(name, PLUGIN_NAME_MAX - 1);
	memcpy(cfg->name, name, len);
	cfg->name[len] = '\0';

	len = strnlen(json_object_get_string(j_plugin), PLUGIN_NAME_MAX - 1);
	memcpy(cfg->plugin, json_object_get_string(j_plugin), len);
	cfg->plugin[len] = '\0';

	cfg->enabled = json_object_get_boolean(j_enabled);

	opae_plugin_mgr_add_plugin(cfg);

	return 0;
}

STATIC int process_cfg_buffer(const char *buffer, const char *filename)
{
	int num_plugins = 0;
	int num_errors = 0;
	int i = 0;
	int res = 1;
	json_object *root = NULL;
	json_object *j_plugins = NULL;
	json_object *j_configs = NULL;
	json_object *j_plugin = NULL;
	json_object *j_config = NULL;
	const char *plugin_name = NULL;
	enum json_tokener_error j_err = json_tokener_success;

	root = json_tokener_parse_verbose(buffer, &j_err);
	if (!root) {
		OPAE_ERR("Error parsing config file: '%s' - %s", filename,
			 json_tokener_error_desc(j_err));
		goto out_free;
	}

	if (!json_object_object_get_ex(root, "plugins", &j_plugins)) {
		OPAE_ERR("Error parsing config file: '%s' - missing 'plugins'", filename);
		goto out_free;
	}
	if (!json_object_object_get_ex(root, "configurations", &j_configs)) {
		OPAE_ERR("Error parsing config file: '%s' - missing 'configs'", filename);
		goto out_free;
	}

	if (!json_object_is_type(j_plugins, json_type_array)) {
		OPAE_ERR("'plugins' JSON object not array type");
		goto out_free;
	}

	num_plugins = json_object_array_length(j_plugins);
	num_errors = 0;
	for (i = 0; i < num_plugins; ++i) {
		j_plugin = json_object_array_get_idx(j_plugins, i);
		plugin_name = json_object_get_string(j_plugin);

		if (json_object_object_get_ex(j_configs, plugin_name, &j_config)) {
			num_errors += process_plugin(plugin_name, j_config);
		} else {
			OPAE_ERR("Could not find plugin configuration for '%s'", plugin_name);
			num_errors += 1;
		}
	}
	res = num_errors;

out_free:
	if (root)
		json_object_put(root);
	return res;
}

#define MAX_CFG_SIZE 4096
STATIC int opae_plugin_mgr_parse_config(const char *filename)
{
	char buffer[MAX_CFG_SIZE] = { 0 };
	char *ptr = &buffer[0];
	size_t bytes_read = 0, total_read = 0;
	FILE *fp = NULL;
	if (filename) {
		fp = fopen(filename, "r");
	} else {
		OPAE_MSG("config file is NULL");
		return 1;
	}

	if (!fp) {
		OPAE_ERR("Error opening config file: %s", filename);
		return 1;
	}

	while ((bytes_read = fread(ptr + total_read, 1, 1, fp))
	       && total_read < MAX_CFG_SIZE) {
		total_read += bytes_read;
	}

	if (ferror(fp)) {
		OPAE_ERR("Error reading config file: %s - %s", filename, strerror(errno));
		goto out_err;
	}
	if (!feof(fp)) {
		OPAE_ERR("Unknown error reading config file: %s", filename);
		goto out_err;
	}
	fclose(fp);
	fp = NULL;

	return process_cfg_buffer(buffer, filename);
out_err:
	fclose(fp);
	fp = NULL;
	return 1;
}

STATIC int opae_plugin_mgr_register_adapter(opae_api_adapter_table *adapter)
{
	opae_api_adapter_table *aptr;

	adapter->next = NULL;

	if (!adapter_list) {
		adapter_list = adapter;
		return 0;
	}

	// new entries go to the end of the list.
	for (aptr = adapter_list; aptr->next; aptr = aptr->next)
		/* find the last entry */;

	aptr->next = adapter;

	return 0;
}

STATIC void opae_plugin_mgr_detect_platform(uint16_t vendor, uint16_t device)
{
	int i;

	for (i = 0 ; platform_data_table[i].native_plugin ; ++i) {

		if (platform_data_table[i].vendor_id == vendor &&
		    platform_data_table[i].device_id == device) {
			OPAE_DBG("platform detected: vid=0x%04x did=0x%04x -> %s",
					vendor, device,
					platform_data_table[i].native_plugin);

			platform_data_table[i].flags |= OPAE_PLATFORM_DATA_DETECTED;
		}

	}
}

STATIC int opae_plugin_mgr_detect_platforms(void)
{
	DIR *dir;
	char base_dir[PATH_MAX];
	char file_path[PATH_MAX];
	struct dirent *dirent;
	int errors = 0;

	// Iterate over the directories in /sys/bus/pci/devices.
	// This directory contains symbolic links to device directories
	// where 'vendor' and 'device' files exist.

	memcpy(base_dir, "/sys/bus/pci/devices", 21);

	dir = opendir(base_dir);
	if (!dir) {
		OPAE_ERR("Failed to open %s. Aborting platform detection.", base_dir);
		return 1;
	}

	while ((dirent = readdir(dir)) != NULL) {
		FILE *fp;
		unsigned vendor = 0;
		unsigned device = 0;

		if (!strcmp(dirent->d_name, ".") ||
		    !strcmp(dirent->d_name, ".."))
			continue;

		// Read the 'vendor' file.
		if (snprintf(file_path, sizeof(file_path),
			     "%s/%s/vendor",
			     base_dir,
			     dirent->d_name) < 0) {
			OPAE_ERR("snprintf buffer overflow");
			++errors;
			goto out_close;
		}

		fp = fopen(file_path, "r");
		if (!fp) {
			OPAE_ERR("Failed to open %s. Aborting platform detection.", file_path);
			++errors;
			goto out_close;
		}

		if (EOF == fscanf(fp, "%x", &vendor)) {
			OPAE_ERR("Failed to read %s. Aborting platform detection.", file_path);
			fclose(fp);
			++errors;
			goto out_close;
		}

		fclose(fp);

		// Read the 'device' file.
		if (snprintf(file_path, sizeof(file_path),
			     "%s/%s/device",
			     base_dir,
			     dirent->d_name) < 0) {
			OPAE_ERR("snprintf buffer overflow");
			++errors;
			goto out_close;
		}

		fp = fopen(file_path, "r");
		if (!fp) {
			OPAE_ERR("Failed to open %s. Aborting platform detection.", file_path);
			++errors;
			goto out_close;
		}

		if (EOF == fscanf(fp, "%x", &device)) {
			OPAE_ERR("Failed to read %s. Aborting platform detection.", file_path);
			fclose(fp);
			++errors;
			goto out_close;
		}

		fclose(fp);

		// Detect platform for this (vendor, device).
		opae_plugin_mgr_detect_platform((uint16_t) vendor, (uint16_t) device);
	}

out_close:
	closedir(dir);
	return errors;
}

STATIC int opae_plugin_mgr_load_cfg_plugin(plugin_cfg *cfg)
{
	int res = 0;
	opae_api_adapter_table *adapter = NULL;

	if (cfg->enabled && cfg->cfg && cfg->cfg_size) {
		adapter = opae_plugin_mgr_alloc_adapter(cfg->plugin);
		if (!adapter) {
			OPAE_ERR("malloc failed");
			return 1;
		}
		res = opae_plugin_mgr_configure_plugin(adapter, cfg->cfg);
		if (res) {
			opae_plugin_mgr_free_adapter(adapter);
			OPAE_ERR("failed to configure plugin \"%s\"",
				 cfg->name);
			return 1;
		}

		res = opae_plugin_mgr_register_adapter(adapter);
		if (res) {
			opae_plugin_mgr_free_adapter(adapter);
			OPAE_ERR("Failed to register \"%s\"", cfg->name);
			return 1;
		}

	}

	return 0;
}

STATIC int opae_plugin_mgr_load_cfg_plugins(void)
{
	plugin_cfg *ptr = opae_plugin_mgr_config_list;
	int errors = 0;
	while (ptr) {
		errors += opae_plugin_mgr_load_cfg_plugin(ptr);
		ptr = ptr->next;
	}
	return errors;
}

STATIC int opae_plugin_mgr_load_dflt_plugins(int *platforms_detected)
{
	int i = 0, j = 0;
	int res = 0;
	opae_api_adapter_table *adapter = NULL;
	int errors = opae_plugin_mgr_detect_platforms();
	if (errors)
		return errors;
	// Load each of the native plugins that were detected.
	*platforms_detected = 0;

	for (i = 0 ; platform_data_table[i].native_plugin ; ++i) {
		const char *native_plugin;
		int already_loaded;

		if (!(platform_data_table[i].flags & OPAE_PLATFORM_DATA_DETECTED))
			continue; // This platform was not detected.

		native_plugin = platform_data_table[i].native_plugin;
		(*platforms_detected)++;

		// Iterate over the table again to prevent multiple loads
		// of the same native plugin.
		already_loaded = 0;
		for (j = 0 ; platform_data_table[j].native_plugin ; ++j) {

			if (!strcmp(native_plugin, platform_data_table[j].native_plugin) &&
			    (platform_data_table[j].flags & OPAE_PLATFORM_DATA_LOADED)) {
				already_loaded = 1;
				break;
			}

		}

		if (already_loaded)
			continue;

		adapter = opae_plugin_mgr_alloc_adapter(native_plugin);

		if (!adapter) {
			OPAE_ERR("malloc failed");
			return ++errors;
		}

		// TODO: pass serialized json for native plugin
		res = opae_plugin_mgr_configure_plugin(adapter, "");
		if (res) {
			opae_plugin_mgr_free_adapter(adapter);
			OPAE_ERR("failed to configure plugin \"%s\"",
				 native_plugin);
			++errors;
			continue; // Keep going.
		}

		res = opae_plugin_mgr_register_adapter(adapter);
		if (res) {
			opae_plugin_mgr_free_adapter(adapter);
			OPAE_ERR("Failed to register \"%s\"", native_plugin);
			++errors;
			continue; // Keep going.
		}

		platform_data_table[i].flags |= OPAE_PLATFORM_DATA_LOADED;
	}
	return errors;
}

int opae_plugin_mgr_initialize(const char *cfg_file)
{
	int res;
	int errors = 0;
	int platforms_detected = 0;
	opae_plugin_mgr_plugin_count = 0;
	char *found_cfg = NULL;
	const char *use_cfg = NULL;

	opae_mutex_lock(res, &adapter_list_lock);

	if (initialized) { // prevent multiple init.
		opae_mutex_unlock(res, &adapter_list_lock);
		return 0;
	}
	found_cfg = find_cfg();
	use_cfg = cfg_file ? cfg_file : found_cfg;
	if (use_cfg) {
		opae_plugin_mgr_parse_config(use_cfg);
		if (found_cfg) {
			free(found_cfg);
		}
	}

	if (opae_plugin_mgr_plugin_count) {
		errors = opae_plugin_mgr_load_cfg_plugins();
	} else {
		// fail-safe, try to detect plugins based on supported devices
		errors = opae_plugin_mgr_load_dflt_plugins(&platforms_detected);
	}

	if (errors)
		goto out_unlock;

	// Call each plugin's initialization routine.
	errors += opae_plugin_mgr_initialize_all();

	if (!errors && (opae_plugin_mgr_plugin_count || platforms_detected))
		initialized = 1;

out_unlock:
	opae_mutex_unlock(res, &adapter_list_lock);

	return errors;
}

int opae_plugin_mgr_for_each_adapter
	(int (*callback)(const opae_api_adapter_table *, void *), void *context)
{
	int res;
	int cb_res = OPAE_ENUM_CONTINUE;
	opae_api_adapter_table *aptr;

	if (!callback) {
		OPAE_ERR("NULL callback passed to %s()", __func__);
		return OPAE_ENUM_STOP;
	}

	opae_mutex_lock(res, &adapter_list_lock);

	for (aptr = adapter_list; aptr; aptr = aptr->next) {
		cb_res = callback(aptr, context);
		if (cb_res)
			break;
	}

	opae_mutex_unlock(res, &adapter_list_lock);

	return cb_res;
}