// 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 #endif // HAVE_CONFIG_H #include #include #include #include #include #include #include #include #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; }