// Copyright(c) 2017-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
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include "xfpga.h"
#include "common_int.h"
#include "error_int.h"
#include "props.h"
/* mutex to protect global data structures */
extern pthread_mutex_t global_lock;
struct dev_list {
char sysfspath[SYSFS_PATH_MAX];
char devpath[DEV_PATH_MAX];
fpga_objtype objtype;
fpga_guid guid;
uint16_t segment;
uint8_t bus;
uint8_t device;
uint8_t function;
uint8_t socket_id;
uint16_t vendor_id;
uint16_t device_id;
uint32_t fpga_num_slots;
uint64_t fpga_bitstream_id;
fpga_version fpga_bbs_version;
fpga_accelerator_state accelerator_state;
uint32_t accelerator_num_mmios;
uint32_t accelerator_num_irqs;
struct dev_list *next;
struct dev_list *parent;
struct dev_list *fme;
};
STATIC bool matches_filter(const struct dev_list *attr, const fpga_properties filter)
{
struct _fpga_properties *_filter = (struct _fpga_properties *)filter;
bool res = true;
int err = 0;
char buffer[PATH_MAX] = {0};
if (pthread_mutex_lock(&_filter->lock)) {
OPAE_MSG("Failed to lock filter mutex");
return false;
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_PARENT)) {
struct _fpga_token *_parent_tok =
(struct _fpga_token *)_filter->parent;
char spath[PATH_MAX] = {0};
if (FPGA_ACCELERATOR != attr->objtype) {
res = false; // Only accelerator can have a parent
goto out_unlock;
}
if (NULL == _parent_tok) {
res = false; // Reject search based on NULL parent token
goto out_unlock;
}
if (sysfs_get_fme_path(attr->sysfspath, spath) != FPGA_OK) {
res = false;
goto out_unlock;
}
// sysfs_get_fme_path returns the real path
// compare that agains the realpath of the parent_tok
if (!realpath(_parent_tok->sysfspath, buffer)) {
res = false;
goto out_unlock;
}
if (strcmp(spath, buffer)) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_OBJTYPE)) {
if (_filter->objtype != attr->objtype) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_SEGMENT)) {
if (_filter->segment != attr->segment) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_BUS)) {
if (_filter->bus != attr->bus) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_DEVICE)) {
if (_filter->device != attr->device) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_FUNCTION)) {
if (_filter->function != attr->function) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_SOCKETID)) {
if (_filter->socket_id != attr->socket_id) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_GUID)) {
if (0 != memcmp(attr->guid, _filter->guid, sizeof(fpga_guid))) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_OBJECTID)) {
uint64_t objid;
fpga_result result;
result = sysfs_objectid_from_path(attr->sysfspath, &objid);
if (result != FPGA_OK || _filter->object_id != objid) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_VENDORID)) {
if (_filter->vendor_id != attr->vendor_id) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_DEVICEID)) {
if (_filter->device_id != attr->device_id) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_NUM_ERRORS)) {
uint32_t errors;
char errpath[SYSFS_PATH_MAX] = { 0, };
if (snprintf(errpath, sizeof(errpath),
"%s/errors", attr->sysfspath) < 0) {
OPAE_ERR("snprintf buffer overflow");
res = false;
goto out_unlock;
}
errors = count_error_files(errpath);
if (errors != _filter->num_errors) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_OBJTYPE)
&& (FPGA_DEVICE == _filter->objtype)) {
if (FIELD_VALID(_filter, FPGA_PROPERTY_NUM_SLOTS)) {
if ((FPGA_DEVICE != attr->objtype)
|| (attr->fpga_num_slots
!= _filter->u.fpga.num_slots)) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_BBSID)) {
if ((FPGA_DEVICE != attr->objtype)
|| (attr->fpga_bitstream_id
!= _filter->u.fpga.bbs_id)) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_BBSVERSION)) {
if ((FPGA_DEVICE != attr->objtype)
|| (attr->fpga_bbs_version.major
!= _filter->u.fpga.bbs_version.major)
|| (attr->fpga_bbs_version.minor
!= _filter->u.fpga.bbs_version.minor)
|| (attr->fpga_bbs_version.patch
!= _filter->u.fpga.bbs_version.patch)) {
res = false;
goto out_unlock;
}
}
} else if (FIELD_VALID(_filter, FPGA_PROPERTY_OBJTYPE)
&& (FPGA_ACCELERATOR == _filter->objtype)) {
if (FIELD_VALID(_filter, FPGA_PROPERTY_ACCELERATOR_STATE)) {
if ((FPGA_ACCELERATOR != attr->objtype)
|| (attr->accelerator_state
!= _filter->u.accelerator.state)) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_NUM_MMIO)) {
if ((FPGA_ACCELERATOR != attr->objtype)
|| (attr->accelerator_num_mmios
!= _filter->u.accelerator.num_mmio)) {
res = false;
goto out_unlock;
}
}
if (FIELD_VALID(_filter, FPGA_PROPERTY_NUM_INTERRUPTS)) {
if ((FPGA_ACCELERATOR != attr->objtype)
|| (attr->accelerator_num_irqs
!= _filter->u.accelerator.num_interrupts)) {
res = false;
goto out_unlock;
}
}
}
out_unlock:
err = pthread_mutex_unlock(&_filter->lock);
if (err) {
OPAE_ERR("pthread_mutex_unlock() failed: %S", strerror(err));
}
return res;
}
STATIC bool matches_filters(const struct dev_list *attr, const fpga_properties *filter,
uint32_t num_filter)
{
uint32_t i;
if (!num_filter) // no filter == match everything
return true;
for (i = 0; i < num_filter; ++i) {
if (matches_filter(attr, filter[i])) {
return true;
}
}
return false;
}
STATIC struct dev_list *add_dev(const char *sysfspath, const char *devpath,
struct dev_list *parent)
{
struct dev_list *pdev;
size_t len;
pdev = (struct dev_list *)calloc(1, sizeof(*pdev));
if (NULL == pdev)
return NULL;
len = strnlen(sysfspath, sizeof(pdev->sysfspath) - 1);
memcpy(pdev->sysfspath, sysfspath, len);
pdev->sysfspath[len] = '\0';
len = strnlen(devpath, sizeof(pdev->devpath) - 1);
memcpy(pdev->devpath, devpath, len);
pdev->devpath[len] = '\0';
pdev->next = parent->next;
parent->next = pdev;
pdev->parent = parent;
return pdev;
}
STATIC fpga_result enum_fme(const char *sysfspath, const char *name,
struct dev_list *parent)
{
fpga_result result;
struct stat stats;
struct dev_list *pdev;
char dpath[DEV_PATH_MAX];
int resval = 0;
uint64_t value = 0;
// Make sure it's a directory.
if (stat(sysfspath, &stats) != 0) {
OPAE_MSG("stat failed: %s", strerror(errno));
return FPGA_NOT_FOUND;
}
if (!S_ISDIR(stats.st_mode))
return FPGA_OK;
snprintf(dpath, sizeof(dpath),
FPGA_DEV_PATH "/%s", name);
pdev = add_dev(sysfspath, dpath, parent);
if (!pdev) {
OPAE_MSG("Failed to allocate device");
return FPGA_NO_MEMORY;
}
pdev->objtype = FPGA_DEVICE;
pdev->segment = parent->segment;
pdev->bus = parent->bus;
pdev->device = parent->device;
pdev->function = parent->function;
pdev->vendor_id = parent->vendor_id;
pdev->device_id = parent->device_id;
// Discover the FME GUID from sysfs (pr/interface_id)
result = sysfs_get_fme_pr_interface_id(sysfspath, pdev->guid);
if (FPGA_OK != result) {
OPAE_MSG("Failed to get PR interface id");
return result;
}
// Discover the socket id from the FME's sysfs entry.
if (sysfs_path_is_valid(sysfspath, FPGA_SYSFS_SOCKET_ID) == FPGA_OK) {
resval = sysfs_parse_attribute64(sysfspath, FPGA_SYSFS_SOCKET_ID, &value);
if (resval != 0) {
return FPGA_NOT_FOUND;
}
parent->socket_id = (uint8_t)value;
}
// Read number of slots
resval = sysfs_parse_attribute64(sysfspath, FPGA_SYSFS_NUM_SLOTS, &value);
if (resval != 0) {
return FPGA_NOT_FOUND;
}
pdev->fpga_num_slots = (uint32_t) value;
// Read bitstream id
resval = sysfs_parse_attribute64(sysfspath, FPGA_SYSFS_BITSTREAM_ID, &pdev->fpga_bitstream_id);
if (resval != 0) {
return FPGA_NOT_FOUND;
}
pdev->fpga_bbs_version.major =
FPGA_BBS_VER_MAJOR(pdev->fpga_bitstream_id);
pdev->fpga_bbs_version.minor =
FPGA_BBS_VER_MINOR(pdev->fpga_bitstream_id);
pdev->fpga_bbs_version.patch =
FPGA_BBS_VER_PATCH(pdev->fpga_bitstream_id);
parent->fme = pdev;
return FPGA_OK;
}
STATIC fpga_result enum_afu(const char *sysfspath, const char *name,
struct dev_list *parent)
{
fpga_result result;
int resval = 0;
struct stat stats;
struct dev_list *pdev;
char spath[PATH_MAX] = { 0, };
char dpath[DEV_PATH_MAX] = { 0, };
uint64_t value = 0;
// Make sure it's a directory.
if (stat(sysfspath, &stats) != 0) {
OPAE_ERR("stat failed: %s", strerror(errno));
return FPGA_NOT_FOUND;
}
if (!S_ISDIR(stats.st_mode))
return FPGA_OK;
int res;
snprintf(dpath, sizeof(dpath), FPGA_DEV_PATH "/%s", name);
pdev = add_dev(sysfspath, dpath, parent);
if (!pdev) {
OPAE_ERR("Failed to allocate device");
return FPGA_NO_MEMORY;
}
pdev->objtype = FPGA_ACCELERATOR;
pdev->segment = parent->segment;
pdev->bus = parent->bus;
pdev->device = parent->device;
pdev->function = parent->function;
pdev->vendor_id = parent->vendor_id;
pdev->device_id = parent->device_id;
pdev->socket_id = parent->socket_id = 0;
// get the socket id from the fme
if (sysfs_get_fme_path(sysfspath, spath) == FPGA_OK) {
resval = sysfs_parse_attribute64(spath, FPGA_SYSFS_SOCKET_ID, &value);
if (resval) {
OPAE_MSG("error reading socket_id");
} else {
pdev->socket_id = parent->socket_id = value;
}
}
res = open(pdev->devpath, O_RDWR);
if (-1 == res) {
pdev->accelerator_state = FPGA_ACCELERATOR_ASSIGNED;
} else {
close(res);
pdev->accelerator_state = FPGA_ACCELERATOR_UNASSIGNED;
}
// FIXME: not to rely on hard-coded constants.
pdev->accelerator_num_mmios = 2;
pdev->accelerator_num_irqs = 0;
// Discover the AFU GUID from sysfs.
snprintf(spath, sizeof(spath),
"%s/" FPGA_SYSFS_AFU_GUID, sysfspath);
result = sysfs_read_guid(spath, pdev->guid);
/* if we can't read the afu_id, remove device from list */
if (FPGA_OK != result) {
OPAE_MSG("Could not read afu_id from '%s', ignoring", spath);
parent->next = pdev->next;
free(pdev);
}
return FPGA_OK;
}
typedef struct _enum_region_ctx{
struct dev_list *list;
bool include_port;
} enum_region_ctx;
STATIC fpga_result enum_regions(const sysfs_fpga_device *device, void *context)
{
enum_region_ctx *ctx = (enum_region_ctx *)context;
fpga_result result = FPGA_OK;
struct dev_list *pdev = add_dev(device->sysfs_path, "", ctx->list);
if (!pdev) {
OPAE_MSG("Failed to allocate device");
return FPGA_NO_MEMORY;
}
// Assign bus, function, device
// segment,device_id ,vendor_id
pdev->function = device->function;
pdev->segment = device->segment;
pdev->bus = device->bus;
pdev->device = device->device;
pdev->device_id = device->device_id;
pdev->vendor_id = device->vendor_id;
// Enum fme
if (device->fme) {
result = enum_fme(device->fme->sysfs_path,
device->fme->sysfs_name, pdev);
if (result != FPGA_OK) {
OPAE_ERR("Failed to enum FME");
return result;
}
}
// Enum port
if (device->port && ctx->include_port) {
result = enum_afu(device->port->sysfs_path,
device->port->sysfs_name, pdev);
if (result != FPGA_OK) {
OPAE_ERR("Failed to enum PORT");
return result;
}
}
return FPGA_OK;
}
STATIC fpga_result enum_fpga_region_resources(struct dev_list *list,
bool include_port)
{
enum_region_ctx ctx = {.list = list, .include_port = include_port};
return sysfs_foreach_device(enum_regions, &ctx);
}
/// Determine if filters require reading AFUs
///
/// Return true if any of the following conditions are met:
/// * The number of filters is zero
/// * At least one filter specifies FPGA_ACCELERATOR as object type
/// * At least one filter does NOT specify an object type
/// Return false otherwise
bool include_afu(const fpga_properties *filters, uint32_t num_filters)
{
size_t i = 0;
if (!num_filters)
return true;
for (i = 0; i < num_filters; ++i) {
struct _fpga_properties *_filter =
(struct _fpga_properties *)filters[i];
if (FIELD_VALID(_filter, FPGA_PROPERTY_OBJTYPE)) {
if (_filter->objtype == FPGA_ACCELERATOR) {
return true;
}
} else {
return true;
}
}
return false;
}
fpga_result __XFPGA_API__ xfpga_fpgaEnumerate(const fpga_properties *filters,
uint32_t num_filters, fpga_token *tokens,
uint32_t max_tokens,
uint32_t *num_matches)
{
fpga_result result = FPGA_NOT_FOUND;
struct dev_list head;
struct dev_list *lptr;
if (NULL == num_matches) {
OPAE_MSG("num_matches is NULL");
return FPGA_INVALID_PARAM;
}
/* requiring a max number of tokens, but not providing a pointer to
* return them through is invalid */
if ((max_tokens > 0) && (NULL == tokens)) {
OPAE_MSG("max_tokens > 0 with NULL tokens");
return FPGA_INVALID_PARAM;
}
if ((num_filters > 0) && (NULL == filters)) {
OPAE_MSG("num_filters > 0 with NULL filters");
return FPGA_INVALID_PARAM;
}
if (!num_filters && (NULL != filters)) {
OPAE_MSG("num_filters == 0 with non-NULL filters");
return FPGA_INVALID_PARAM;
}
*num_matches = 0;
memset(&head, 0, sizeof(head));
//enum FPGA regions & resources
result = enum_fpga_region_resources(&head,
include_afu(filters, num_filters));
if (result != FPGA_OK) {
OPAE_MSG("No FPGA resources found");
return result;
}
/* create and populate token data structures */
for (lptr = head.next; NULL != lptr; lptr = lptr->next) {
struct _fpga_token *_tok;
if (!strnlen(lptr->devpath, sizeof(lptr->devpath)))
continue;
// propagate the socket_id field.
lptr->socket_id = lptr->parent->socket_id;
lptr->fme = lptr->parent->fme;
/* FIXME: do we need to keep a global list of tokens? */
/* For now we do becaue it is used in xfpga_fpgaUpdateProperties
* to lookup a parent from the global list of tokens...*/
_tok = token_add(lptr->sysfspath, lptr->devpath);
if (NULL == _tok) {
OPAE_MSG("Failed to allocate memory for token");
result = FPGA_NO_MEMORY;
goto out_free_trash;
}
// FIXME: should check contents of filter for token magic
if (matches_filters(lptr, filters, num_filters)) {
if (*num_matches < max_tokens) {
if (xfpga_fpgaCloneToken(_tok, &tokens[*num_matches])
!= FPGA_OK) {
// FIXME: should we error out here?
OPAE_MSG("Error cloning token");
}
}
++(*num_matches);
}
}
out_free_trash:
/* FIXME: should this live in a separate function? */
for (lptr = head.next; NULL != lptr;) {
struct dev_list *trash = lptr;
lptr = lptr->next;
free(trash);
}
return result;
}
fpga_result __XFPGA_API__ xfpga_fpgaCloneToken(fpga_token src, fpga_token *dst)
{
struct _fpga_token *_src = (struct _fpga_token *)src;
struct _fpga_token *_dst;
size_t len;
if (NULL == src || NULL == dst) {
OPAE_MSG("src or dst in NULL");
return FPGA_INVALID_PARAM;
}
if (_src->magic != FPGA_TOKEN_MAGIC) {
OPAE_MSG("Invalid src");
return FPGA_INVALID_PARAM;
}
_dst = calloc(1, sizeof(struct _fpga_token));
if (NULL == _dst) {
OPAE_MSG("Failed to allocate memory for token");
return FPGA_NO_MEMORY;
}
_dst->magic = FPGA_TOKEN_MAGIC;
_dst->device_instance = _src->device_instance;
_dst->subdev_instance = _src->subdev_instance;
len = strnlen(_src->sysfspath, sizeof(_src->sysfspath) - 1);
strncpy(_dst->sysfspath, _src->sysfspath, len + 1);
len = strnlen(_src->devpath, sizeof(_src->devpath) - 1);
strncpy(_dst->devpath, _src->devpath, len + 1);
// shallow-copy error list
_dst->errors = _src->errors;
*dst = _dst;
return FPGA_OK;
}
fpga_result __XFPGA_API__ xfpga_fpgaDestroyToken(fpga_token *token)
{
fpga_result result = FPGA_OK;
int err = 0;
if (NULL == token || NULL == *token) {
OPAE_MSG("Invalid token pointer");
return FPGA_INVALID_PARAM;
}
struct _fpga_token *_token = (struct _fpga_token *)*token;
if (pthread_mutex_lock(&global_lock)) {
OPAE_MSG("Failed to lock global mutex");
return FPGA_EXCEPTION;
}
if (_token->magic != FPGA_TOKEN_MAGIC) {
OPAE_MSG("Invalid token");
result = FPGA_INVALID_PARAM;
goto out_unlock;
}
// invalidate magic (just in case)
_token->magic = FPGA_INVALID_MAGIC;
free(*token);
*token = NULL;
out_unlock:
err = pthread_mutex_unlock(&global_lock);
if (err) {
OPAE_ERR("pthread_mutex_unlock() failed: %S", strerror(err));
}
return result;
}