// 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;
}