// 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
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif // _GNU_SOURCE
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <opae/properties.h>
#include "xfpga.h"
#include "common_int.h"
#include "opae_drv.h"
#include "types_int.h"
#include "intel-fpga.h"
#define EVENT_SOCKET_NAME "/tmp/fpga_event_socket"
#define EVENT_SOCKET_NAME_LEN 23
enum request_type { REGISTER_EVENT = 0, UNREGISTER_EVENT = 1 };
struct event_request {
enum request_type type;
fpga_event_type event;
uint64_t object_id;
};
fpga_result send_event_request(int conn_socket, int fd,
struct event_request *req)
{
struct msghdr mh;
struct cmsghdr *cmh;
struct iovec iov[1];
char buf[CMSG_SPACE(sizeof(int))];
ssize_t n;
int *fd_ptr;
/* set up ancillary data message header */
iov[0].iov_base = req;
iov[0].iov_len = sizeof(*req);
memset(buf, 0, sizeof(buf));
mh.msg_name = NULL;
mh.msg_namelen = 0;
mh.msg_iov = iov;
mh.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
mh.msg_control = buf;
mh.msg_controllen = CMSG_LEN(sizeof(int));
mh.msg_flags = 0;
cmh = CMSG_FIRSTHDR(&mh);
cmh->cmsg_len = CMSG_LEN(sizeof(int));
cmh->cmsg_level = SOL_SOCKET;
cmh->cmsg_type = SCM_RIGHTS;
fd_ptr = (int *)CMSG_DATA(cmh);
*fd_ptr = fd;
/* send ancillary data */
n = sendmsg(conn_socket, &mh, 0);
if (n < 0) {
OPAE_ERR("sendmsg failed: %s", strerror(errno));
return FPGA_EXCEPTION;
}
return FPGA_OK;
}
STATIC fpga_result send_fme_event_request(fpga_handle handle,
fpga_event_handle event_handle,
int fme_operation)
{
int fd = FILE_DESCRIPTOR(event_handle);
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
fpga_result res = FPGA_OK;
opae_fme_info fme_info = { 0 };
if (fme_operation != FPGA_IRQ_ASSIGN
&& fme_operation != FPGA_IRQ_DEASSIGN) {
OPAE_ERR("Invalid FME operation requested");
return FPGA_INVALID_PARAM;
}
res = opae_get_fme_info(_handle->fddev, &fme_info);
if (res) {
return res;
}
/*capability field is set to 1 if the platform supports interrupts*/
if (fme_info.capability & FPGA_FME_CAP_ERR_IRQ) {
res = opae_fme_set_err_irq(_handle->fddev, 0, fme_operation == FPGA_IRQ_ASSIGN ? fd : -1);
if (res) {
OPAE_ERR("Could not set eventfd %s", strerror(errno));
}
} else {
OPAE_ERR("FME interrupts not supported in hw");
res = FPGA_NOT_SUPPORTED;
}
return res;
}
STATIC fpga_result send_port_event_request(fpga_handle handle,
fpga_event_handle event_handle,
int port_operation)
{
fpga_result res = FPGA_OK;
int fd = FILE_DESCRIPTOR(event_handle);
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
opae_port_info port_info = { 0 };
if (port_operation != FPGA_IRQ_ASSIGN
&& port_operation != FPGA_IRQ_DEASSIGN) {
OPAE_ERR("Invalid PORT operation requested");
return FPGA_INVALID_PARAM;
}
res = opae_get_port_info(_handle->fddev, &port_info);
if (res) {
return res;
}
/*capability field is set to 1 if the platform supports interrupts*/
if (port_info.capability & FPGA_PORT_CAP_ERR_IRQ) {
res = opae_port_set_err_irq(_handle->fddev, 0, port_operation == FPGA_IRQ_ASSIGN ? fd : -1);
if (res) {
OPAE_ERR("Could not set eventfd");
}
} else {
OPAE_ERR("PORT interrupts not supported in hw");
res = FPGA_NOT_SUPPORTED;
}
return res;
}
STATIC fpga_result send_uafu_event_request(fpga_handle handle,
fpga_event_handle event_handle,
uint32_t flags, int uafu_operation)
{
int res = FPGA_OK;
int fd = FILE_DESCRIPTOR(event_handle);
struct _fpga_event_handle *_eh =
(struct _fpga_event_handle *)event_handle;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
opae_port_info port_info = { 0 };
int32_t neg = -1;
if (uafu_operation != FPGA_IRQ_ASSIGN
&& uafu_operation != FPGA_IRQ_DEASSIGN) {
OPAE_ERR("Invalid UAFU operation requested");
return FPGA_INVALID_PARAM;
}
res = opae_get_port_info(_handle->fddev, &port_info);
if (res) {
return res;
}
/*capability field is set to 1 if the platform supports interrupts*/
if (port_info.capability & FPGA_PORT_CAP_UAFU_IRQ) {
if (flags >= port_info.num_uafu_irqs) {
OPAE_ERR("Invalid User Interrupt vector id");
return FPGA_INVALID_PARAM;
}
if (uafu_operation == FPGA_IRQ_ASSIGN) {
res = opae_port_set_user_irq(_handle->fddev, 0, flags, 1, &fd);
_eh->flags = flags;
} else {
res = opae_port_set_user_irq(_handle->fddev, 0, _eh->flags, 1, &neg);
}
if (res) {
OPAE_ERR("Could not set eventfd");
res = FPGA_EXCEPTION;
}
} else {
OPAE_ERR("UAFU interrupts not supported in hw");
res = FPGA_NOT_SUPPORTED;
}
return res;
}
/*
* Uses driver ioctls to determine whether the driver supports interrupts
* on this platform. objtype is an output parameter.
*/
STATIC fpga_result check_interrupts_supported(fpga_handle handle,
fpga_objtype *objtype)
{
fpga_result res = FPGA_OK;
fpga_result destroy_res = FPGA_OK;
fpga_properties prop = NULL;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
opae_fme_info fme_info = { 0 };
opae_port_info port_info = { 0 };
res = xfpga_fpgaGetPropertiesFromHandle(handle, &prop);
if (res != FPGA_OK) {
OPAE_MSG("Could not get FPGA properties from handle");
return res;
}
res = fpgaPropertiesGetObjectType(prop, objtype);
if (res != FPGA_OK) {
OPAE_MSG("Could not determine FPGA object type");
goto destroy_prop;
}
if (*objtype == FPGA_DEVICE) {
res = opae_get_fme_info(_handle->fddev, &fme_info);
if (res) {
res = FPGA_EXCEPTION;
goto destroy_prop;
}
if (fme_info.capability & FPGA_FME_CAP_ERR_IRQ) {
res = FPGA_OK;
} else {
OPAE_MSG("Interrupts not supported in hw");
res = FPGA_NOT_SUPPORTED;
}
} else if (*objtype == FPGA_ACCELERATOR) {
res = opae_get_port_info(_handle->fddev, &port_info);
if (res) {
OPAE_ERR("Could not get PORT info: %s",
strerror(errno));
goto destroy_prop;
}
if (port_info.capability & FPGA_PORT_CAP_ERR_IRQ) {
res = FPGA_OK;
} else {
OPAE_MSG("Interrupts not supported in hw");
res = FPGA_NOT_SUPPORTED;
}
}
destroy_prop:
destroy_res = fpgaDestroyProperties(&prop);
if (destroy_res != FPGA_OK) {
OPAE_MSG("Could not destroy FPGA properties");
return destroy_res;
}
return res;
}
STATIC fpga_result driver_register_event(fpga_handle handle,
fpga_event_type event_type,
fpga_event_handle event_handle,
uint32_t flags)
{
fpga_objtype objtype;
fpga_result res = FPGA_OK;
res = check_interrupts_supported(handle, &objtype);
if (res != FPGA_OK) {
OPAE_MSG(
"Could not determine whether interrupts are supported");
return FPGA_NOT_SUPPORTED;
}
switch (event_type) {
case FPGA_EVENT_ERROR:
if (objtype == FPGA_DEVICE) {
return send_fme_event_request(handle, event_handle,
FPGA_IRQ_ASSIGN);
} else if (objtype == FPGA_ACCELERATOR) {
return send_port_event_request(handle, event_handle,
FPGA_IRQ_ASSIGN);
}
OPAE_ERR("Invalid objtype: %d", objtype);
return FPGA_EXCEPTION;
case FPGA_EVENT_INTERRUPT:
if (objtype != FPGA_ACCELERATOR) {
OPAE_MSG("User events need an accelerator object");
return FPGA_INVALID_PARAM;
}
return send_uafu_event_request(handle, event_handle, flags,
FPGA_IRQ_ASSIGN);
case FPGA_EVENT_POWER_THERMAL:
OPAE_MSG("Thermal interrupts not supported");
return FPGA_NOT_SUPPORTED;
default:
OPAE_ERR("Invalid event type");
return FPGA_EXCEPTION;
}
}
STATIC fpga_result driver_unregister_event(fpga_handle handle,
fpga_event_type event_type,
fpga_event_handle event_handle)
{
fpga_objtype objtype;
fpga_result res = FPGA_OK;
res = check_interrupts_supported(handle, &objtype);
if (res != FPGA_OK) {
OPAE_MSG(
"Could not determine whether interrupts are supported");
return FPGA_NOT_SUPPORTED;
}
switch (event_type) {
case FPGA_EVENT_ERROR:
if (objtype == FPGA_DEVICE) {
return send_fme_event_request(handle, event_handle,
FPGA_IRQ_DEASSIGN);
} else if (objtype == FPGA_ACCELERATOR) {
return send_port_event_request(handle, event_handle,
FPGA_IRQ_DEASSIGN);
}
OPAE_ERR("Invalid objtype: %d", objtype);
return FPGA_EXCEPTION;
case FPGA_EVENT_INTERRUPT:
if (objtype != FPGA_ACCELERATOR) {
OPAE_MSG("User events need an Accelerator object");
return FPGA_INVALID_PARAM;
}
return send_uafu_event_request(handle, event_handle, 0,
FPGA_IRQ_DEASSIGN);
case FPGA_EVENT_POWER_THERMAL:
OPAE_MSG("Thermal interrupts not supported");
return FPGA_NOT_SUPPORTED;
default:
OPAE_ERR("Invalid event type");
return FPGA_EXCEPTION;
}
}
STATIC fpga_result daemon_register_event(fpga_handle handle,
fpga_event_type event_type,
fpga_event_handle event_handle,
uint32_t flags)
{
int fd = FILE_DESCRIPTOR(event_handle);
fpga_result result = FPGA_OK;
struct sockaddr_un addr;
struct event_request req;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
fpga_properties prop = NULL;
uint64_t object_id = (uint64_t) -1;
UNUSED_PARAM(flags);
if (_handle->fdfpgad < 0) {
/* connect to event socket */
_handle->fdfpgad = socket(AF_UNIX, SOCK_STREAM, 0);
if (_handle->fdfpgad < 0) {
OPAE_ERR("socket: %s", strerror(errno));
return FPGA_EXCEPTION;
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, EVENT_SOCKET_NAME,
EVENT_SOCKET_NAME_LEN);
if (connect(_handle->fdfpgad, (struct sockaddr *)&addr,
sizeof(addr))
< 0) {
OPAE_DBG("connect: %s", strerror(errno));
result = FPGA_NO_DAEMON;
goto out_close_conn;
}
}
/* get the requestor's object ID */
result = xfpga_fpgaGetPropertiesFromHandle(handle, &prop);
if (result != FPGA_OK) {
OPAE_ERR("failed to get props");
goto out_close_conn;
}
result = fpgaPropertiesGetObjectID(prop, &object_id);
if (result != FPGA_OK) {
fpgaDestroyProperties(&prop);
OPAE_ERR("failed to get object ID");
goto out_close_conn;
}
result = fpgaDestroyProperties(&prop);
if (result != FPGA_OK) {
OPAE_ERR("failed to destroy props");
goto out_close_conn;
}
/* create event registration request */
req.type = REGISTER_EVENT;
req.event = event_type;
req.object_id = object_id;
/* send event packet */
result = send_event_request(_handle->fdfpgad, fd, &req);
if (result != FPGA_OK) {
OPAE_ERR("send_event_request failed");
goto out_close_conn;
}
return result;
out_close_conn:
close(_handle->fdfpgad);
_handle->fdfpgad = -1;
return result;
}
STATIC fpga_result daemon_unregister_event(fpga_handle handle,
fpga_event_type event_type)
{
fpga_result result = FPGA_OK;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
struct event_request req;
ssize_t n;
fpga_properties prop = NULL;
uint64_t object_id = (uint64_t) -1;
if (_handle->fdfpgad < 0) {
OPAE_MSG("No fpgad connection");
return FPGA_INVALID_PARAM;
}
/* get the requestor's object ID */
result = xfpga_fpgaGetPropertiesFromHandle(handle, &prop);
if (result != FPGA_OK) {
OPAE_ERR("failed to get properties");
goto out_close_conn;
}
result = fpgaPropertiesGetObjectID(prop, &object_id);
if (result != FPGA_OK) {
fpgaDestroyProperties(&prop);
OPAE_ERR("failed to get object ID");
goto out_close_conn;
}
result = fpgaDestroyProperties(&prop);
if (result != FPGA_OK) {
OPAE_ERR("failed to destroy properties");
goto out_close_conn;
}
req.type = UNREGISTER_EVENT;
req.event = event_type;
req.object_id = object_id;
n = send(_handle->fdfpgad, &req, sizeof(req), 0);
if (n < 0) {
OPAE_ERR("send : %s", strerror(errno));
result = FPGA_EXCEPTION;
goto out_close_conn;
}
return result;
out_close_conn:
close(_handle->fdfpgad);
_handle->fdfpgad = -1;
return result;
}
fpga_result __XFPGA_API__
xfpga_fpgaCreateEventHandle(fpga_event_handle *event_handle)
{
struct _fpga_event_handle *_eh;
fpga_result result = FPGA_OK;
pthread_mutexattr_t mattr;
int err = 0;
ASSERT_NOT_NULL(event_handle);
_eh = malloc(sizeof(struct _fpga_event_handle));
if (NULL == _eh) {
OPAE_ERR("Could not allocate memory for event handle");
return FPGA_NO_MEMORY;
}
_eh->magic = FPGA_EVENT_HANDLE_MAGIC;
/* create eventfd */
_eh->fd = eventfd(0, 0);
if (_eh->fd < 0) {
OPAE_ERR("eventfd : %s", strerror(errno));
result = FPGA_EXCEPTION;
goto out_free;
}
if (pthread_mutexattr_init(&mattr)) {
OPAE_MSG("Failed to initialized event handle mutex attributes");
result = FPGA_EXCEPTION;
goto out_free;
}
if (pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE)) {
OPAE_MSG("Failed to initialize event handle mutex attributes");
result = FPGA_EXCEPTION;
goto out_attr_destroy;
}
if (pthread_mutex_init(&_eh->lock, &mattr)) {
OPAE_MSG("Failed to initialize event handle mutex");
result = FPGA_EXCEPTION;
goto out_attr_destroy;
}
pthread_mutexattr_destroy(&mattr);
*event_handle = (fpga_event_handle)_eh;
return FPGA_OK;
out_attr_destroy:
err = pthread_mutexattr_destroy(&mattr);
if (err)
OPAE_ERR("pthread_mutexatr_destroy() failed: %s",
strerror(err));
out_free:
free(_eh);
return result;
}
fpga_result __XFPGA_API__
xfpga_fpgaDestroyEventHandle(fpga_event_handle *event_handle)
{
struct _fpga_event_handle *_eh;
fpga_result result = FPGA_OK;
int err = 0;
// sanity check
if (!event_handle) {
return FPGA_INVALID_PARAM;
}
_eh = (struct _fpga_event_handle *)*event_handle;
result = event_handle_check_and_lock(_eh);
if (result)
return result;
if (close(_eh->fd) < 0) {
OPAE_ERR("eventfd : %s", strerror(errno));
err = pthread_mutex_unlock(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %S",
strerror(err));
if (errno == EBADF)
return FPGA_INVALID_PARAM;
else
return FPGA_EXCEPTION;
}
_eh->magic = FPGA_INVALID_MAGIC;
err = pthread_mutex_unlock(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %S", strerror(err));
err = pthread_mutex_destroy(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_destroy() failed: %S", strerror(err));
free(*event_handle);
*event_handle = NULL;
return FPGA_OK;
}
fpga_result __XFPGA_API__
xfpga_fpgaGetOSObjectFromEventHandle(const fpga_event_handle eh, int *fd)
{
struct _fpga_event_handle *_eh = (struct _fpga_event_handle *)eh;
fpga_result result = FPGA_OK;
int err = 0;
result = event_handle_check_and_lock(_eh);
if (result)
return result;
*fd = _eh->fd;
err = pthread_mutex_unlock(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %s", strerror(err));
return FPGA_OK;
}
fpga_result __XFPGA_API__ xfpga_fpgaRegisterEvent(fpga_handle handle,
fpga_event_type event_type,
fpga_event_handle event_handle,
uint32_t flags)
{
fpga_result result = FPGA_OK;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
struct _fpga_event_handle *_eh =
(struct _fpga_event_handle *)event_handle;
struct _fpga_token *_token;
int err;
result = handle_check_and_lock(_handle);
if (result)
return result;
result = event_handle_check_and_lock(_eh);
if (result)
goto out_unlock_handle;
_token = (struct _fpga_token *)_handle->token;
if (_token->magic != FPGA_TOKEN_MAGIC) {
OPAE_MSG("Invalid token found in handle");
result = FPGA_INVALID_PARAM;
goto out_unlock;
}
switch (event_type) {
case FPGA_EVENT_INTERRUPT:
if (!strstr(_token->devpath, "port")) {
OPAE_MSG("Handle does not refer to accelerator object");
result = FPGA_INVALID_PARAM;
goto out_unlock;
}
break;
case FPGA_EVENT_ERROR: /* fall through */
case FPGA_EVENT_POWER_THERMAL:
break;
}
/* TODO: reject unknown flags */
/* try driver first */
result = driver_register_event(handle, event_type, event_handle, flags);
if (result == FPGA_NOT_SUPPORTED) {
result = daemon_register_event(handle, event_type, event_handle,
flags);
}
out_unlock:
err = pthread_mutex_unlock(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %s", strerror(err));
out_unlock_handle:
err = pthread_mutex_unlock(&_handle->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %s", strerror(err));
return result;
}
fpga_result __XFPGA_API__
xfpga_fpgaUnregisterEvent(fpga_handle handle, fpga_event_type event_type,
fpga_event_handle event_handle)
{
fpga_result result = FPGA_OK;
int err;
struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
struct _fpga_event_handle *_eh =
(struct _fpga_event_handle *)event_handle;
struct _fpga_token *_token;
result = handle_check_and_lock(_handle);
if (result)
return result;
result = event_handle_check_and_lock(_eh);
if (result)
goto out_unlock_handle;
_token = (struct _fpga_token *)_handle->token;
if (_token->magic != FPGA_TOKEN_MAGIC) {
OPAE_MSG("Invalid token found in handle");
result = FPGA_INVALID_PARAM;
goto out_unlock;
}
switch (event_type) {
case FPGA_EVENT_INTERRUPT:
if (!strstr(_token->devpath, "port")) {
OPAE_MSG("Handle does not refer to accelerator object");
result = FPGA_INVALID_PARAM;
goto out_unlock;
}
break;
case FPGA_EVENT_ERROR: /* fall through */
case FPGA_EVENT_POWER_THERMAL:
break;
}
/* try driver first */
result = driver_unregister_event(handle, event_type, event_handle);
if (result == FPGA_NOT_SUPPORTED) {
result = daemon_unregister_event(handle, event_type);
}
out_unlock:
err = pthread_mutex_unlock(&_eh->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %s", strerror(err));
out_unlock_handle:
err = pthread_mutex_unlock(&_handle->lock);
if (err)
OPAE_ERR("pthread_mutex_unlock() failed: %s", strerror(err));
return result;
}