Blob Blame History Raw
From 99928f8a8f671a7e6a139e014f4db55482d09d16 Mon Sep 17 00:00:00 2001
From: Tom Rix <trix@redhat.com>
Date: Thu, 14 May 2020 09:50:28 -0400
Subject: [PATCH] Import fpgad from opae-legacy

fpgad is a useful tool that was removed in 1.4.0
Reimport it from the opae-legacy, release/1.4.1 branch
Change the cmake build system to integrate back into build.

Signed-off-by: Tom Rix <trix@redhat.com>
---
 tools/CMakeLists.txt                          |    1 +
 tools/fpgad/CMakeLists.txt                    |   82 ++
 tools/fpgad/api/CMakeLists.txt                |   46 +
 tools/fpgad/api/device_monitoring.c           |   73 ++
 tools/fpgad/api/device_monitoring.h           |   46 +
 tools/fpgad/api/logging.c                     |  132 ++
 tools/fpgad/api/logging.h                     |   37 +
 tools/fpgad/api/opae_events_api.c             |  239 ++++
 tools/fpgad/api/opae_events_api.h             |   85 ++
 tools/fpgad/api/sysfs.c                       |   76 ++
 tools/fpgad/api/sysfs.h                       |   45 +
 tools/fpgad/command_line.c                    |  516 ++++++++
 tools/fpgad/command_line.h                    |   84 ++
 tools/fpgad/config_file.c                     |  621 ++++++++++
 tools/fpgad/config_file.h                     |   38 +
 tools/fpgad/daemonize.c                       |   98 ++
 tools/fpgad/event_dispatcher_thread.c         |  266 ++++
 tools/fpgad/event_dispatcher_thread.h         |   63 +
 tools/fpgad/events_api_thread.c               |  296 +++++
 tools/fpgad/events_api_thread.h               |   43 +
 tools/fpgad/fpgad.c                           |  192 +++
 tools/fpgad/fpgad.cfg                         |   38 +
 tools/fpgad/fpgad.conf                        |    4 +
 tools/fpgad/fpgad.h                           |   76 ++
 tools/fpgad/fpgad.service.in                  |   21 +
 tools/fpgad/monitor_thread.c                  |  261 ++++
 tools/fpgad/monitor_thread.h                  |   50 +
 tools/fpgad/monitored_device.c                |  394 ++++++
 tools/fpgad/monitored_device.h                |  123 ++
 tools/fpgad/plugins/fpgad-vc/CMakeLists.txt   |   41 +
 tools/fpgad/plugins/fpgad-vc/fpgad-vc.c       | 1085 +++++++++++++++++
 .../fpgad/plugins/fpgad-xfpga/CMakeLists.txt  |   41 +
 tools/fpgad/plugins/fpgad-xfpga/fpgad-xfpga.c |  992 +++++++++++++++
 33 files changed, 6205 insertions(+)
 create mode 100644 tools/fpgad/CMakeLists.txt
 create mode 100644 tools/fpgad/api/CMakeLists.txt
 create mode 100644 tools/fpgad/api/device_monitoring.c
 create mode 100644 tools/fpgad/api/device_monitoring.h
 create mode 100644 tools/fpgad/api/logging.c
 create mode 100644 tools/fpgad/api/logging.h
 create mode 100644 tools/fpgad/api/opae_events_api.c
 create mode 100644 tools/fpgad/api/opae_events_api.h
 create mode 100644 tools/fpgad/api/sysfs.c
 create mode 100644 tools/fpgad/api/sysfs.h
 create mode 100644 tools/fpgad/command_line.c
 create mode 100644 tools/fpgad/command_line.h
 create mode 100644 tools/fpgad/config_file.c
 create mode 100644 tools/fpgad/config_file.h
 create mode 100644 tools/fpgad/daemonize.c
 create mode 100644 tools/fpgad/event_dispatcher_thread.c
 create mode 100644 tools/fpgad/event_dispatcher_thread.h
 create mode 100644 tools/fpgad/events_api_thread.c
 create mode 100644 tools/fpgad/events_api_thread.h
 create mode 100644 tools/fpgad/fpgad.c
 create mode 100644 tools/fpgad/fpgad.cfg
 create mode 100644 tools/fpgad/fpgad.conf
 create mode 100644 tools/fpgad/fpgad.h
 create mode 100644 tools/fpgad/fpgad.service.in
 create mode 100644 tools/fpgad/monitor_thread.c
 create mode 100644 tools/fpgad/monitor_thread.h
 create mode 100644 tools/fpgad/monitored_device.c
 create mode 100644 tools/fpgad/monitored_device.h
 create mode 100644 tools/fpgad/plugins/fpgad-vc/CMakeLists.txt
 create mode 100644 tools/fpgad/plugins/fpgad-vc/fpgad-vc.c
 create mode 100644 tools/fpgad/plugins/fpgad-xfpga/CMakeLists.txt
 create mode 100644 tools/fpgad/plugins/fpgad-xfpga/fpgad-xfpga.c

diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index d4b55036..a6a0bc9b 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -32,6 +32,7 @@ opae_add_subdirectory(fpgaconf)
 opae_add_subdirectory(fpgainfo)
 opae_add_subdirectory(fpgametrics)
 opae_add_subdirectory(fpgaport)
+opae_add_subdirectory(fpgad)
 
 # extra
 opae_add_subdirectory(extra/userclk)
diff --git a/tools/fpgad/CMakeLists.txt b/tools/fpgad/CMakeLists.txt
new file mode 100644
index 00000000..19d1e7c5
--- /dev/null
+++ b/tools/fpgad/CMakeLists.txt
@@ -0,0 +1,82 @@
+## 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.
+
+add_subdirectory(api)
+add_subdirectory(plugins/fpgad-xfpga)
+add_subdirectory(plugins/fpgad-vc)
+
+opae_add_executable(TARGET fpgad
+    SOURCE
+        command_line.c
+        config_file.c
+        event_dispatcher_thread.c
+        events_api_thread.c
+        fpgad.c
+        monitor_thread.c
+        daemonize.c
+        monitored_device.c
+    LIBS
+        opae-c
+        bitstream
+        fpgad-api
+        dl
+        rt
+        ${libjson-c_LIBRARIES}
+        ${libuuid_LIBRARIES}
+    COMPONENT toolfpgad
+)
+
+target_include_directories(fpgad
+    PRIVATE
+        ${OPAE_LIBS_ROOT}/libopae-c
+        ${OPAE_LIBS_ROOT}/libbitstream
+        ${OPAE_SDK_SOURCE}/tools
+)
+
+configure_file(fpgad.service.in fpgad.service @ONLY NEWLINE_STYLE UNIX)
+
+if ("${CPACK_GENERATOR}" STREQUAL "RPM")
+
+  if ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr")
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fpgad.service DESTINATION /etc/systemd/system COMPONENT toolfpgad)
+  else()
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fpgad.service DESTINATION ${CMAKE_INSTALL_PREFIX}/etc/systemd/system COMPONENT toolfpgad)
+  endif()
+
+else()
+
+  # DEB
+  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fpgad.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/system COMPONENT toolfpgad)
+
+endif()
+
+if ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr")
+  install(FILES fpgad.cfg DESTINATION /etc/opae COMPONENT toolfpgad)
+  install(FILES fpgad.conf DESTINATION /etc/sysconfig COMPONENT toolfpgad)
+else()
+  install(FILES fpgad.cfg DESTINATION ${CMAKE_INSTALL_PREFIX}/etc/opae COMPONENT toolfpgad)
+  install(FILES fpgad.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/etc/sysconfig COMPONENT toolfpgad)
+endif()
diff --git a/tools/fpgad/api/CMakeLists.txt b/tools/fpgad/api/CMakeLists.txt
new file mode 100644
index 00000000..cdf86c4f
--- /dev/null
+++ b/tools/fpgad/api/CMakeLists.txt
@@ -0,0 +1,46 @@
+## 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.
+
+opae_add_shared_library(TARGET fpgad-api
+    SOURCE
+        logging.c
+        opae_events_api.c
+        device_monitoring.c
+        sysfs.c
+    LIBS
+        ${CMAKE_THREAD_LIBS_INIT}
+        ${libjson-c_LIBRARIES}
+    VERSION ${OPAE_VERSION}
+    SOVERSION ${OPAE_VERSION_MAJOR}
+    COMPONENT toolfpgad_api
+) 
+
+target_include_directories(fpgad-api
+    PRIVATE
+        ${OPAE_SDK_SOURCE}/tools
+        ${OPAE_LIBS_ROOT}/libopae-c
+        ${OPAE_LIBS_ROOT}/libbitstream
+)
diff --git a/tools/fpgad/api/device_monitoring.c b/tools/fpgad/api/device_monitoring.c
new file mode 100644
index 00000000..b9207519
--- /dev/null
+++ b/tools/fpgad/api/device_monitoring.c
@@ -0,0 +1,73 @@
+// Copyright(c) 2018-2019, 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 "device_monitoring.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("device_monitoring: " format, ##__VA_ARGS__)
+
+bool mon_has_error_occurred(fpgad_monitored_device *d, void *err)
+{
+	unsigned i;
+	for (i = 0 ; i < d->num_error_occurrences ; ++i) {
+		if (err == d->error_occurrences[i])
+			return true;
+	}
+	return false;
+}
+
+bool mon_add_device_error(fpgad_monitored_device *d, void *err)
+{
+	if (d->num_error_occurrences <
+		(sizeof(d->error_occurrences) /
+		 sizeof(d->error_occurrences[0]))) {
+		d->error_occurrences[d->num_error_occurrences++] = err;
+		return true;
+	}
+	LOG("exceeded max number of device errors!\n");
+	return false;
+}
+
+void mon_remove_device_error(fpgad_monitored_device *d, void *err)
+{
+	unsigned i;
+	unsigned j;
+	unsigned removed = 0;
+	for (i = j = 0 ; i < d->num_error_occurrences ; ++i) {
+		if (d->error_occurrences[i] != err)
+			d->error_occurrences[j++] = d->error_occurrences[i];
+		else
+			++removed;
+	}
+	d->num_error_occurrences -= removed;
+}
diff --git a/tools/fpgad/api/device_monitoring.h b/tools/fpgad/api/device_monitoring.h
new file mode 100644
index 00000000..c782665e
--- /dev/null
+++ b/tools/fpgad/api/device_monitoring.h
@@ -0,0 +1,46 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_API_DEVICE_MONITORING_H__
+#define __FPGAD_API_DEVICE_MONITORING_H__
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "fpgad/fpgad.h"
+#include "fpgad/monitored_device.h"
+
+bool mon_has_error_occurred(fpgad_monitored_device *d, void *err);
+
+bool mon_add_device_error(fpgad_monitored_device *d, void *err);
+
+void mon_remove_device_error(fpgad_monitored_device *d, void *err);
+
+#endif /* __FPGAD_API_DEVICE_MONITORING_H__ */
diff --git a/tools/fpgad/api/logging.c b/tools/fpgad/api/logging.c
new file mode 100644
index 00000000..e33eeddc
--- /dev/null
+++ b/tools/fpgad/api/logging.c
@@ -0,0 +1,132 @@
+// 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
+
+#include <time.h>
+#include "logging.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("logging: " format, ##__VA_ARGS__)
+
+STATIC pthread_mutex_t log_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+STATIC FILE *log_file;
+
+#define BUF_TIME_LEN    256
+
+int log_open(const char *filename)
+{
+	int res;
+	int err;
+
+	fpgad_mutex_lock(err, &log_lock);
+
+	log_file = fopen(filename, "a");
+	if (log_file) {
+		time_t raw;
+		struct tm tm;
+		char timebuf[BUF_TIME_LEN];
+		size_t len;
+
+		time(&raw);
+		localtime_r(&raw, &tm);
+		asctime_r(&tm, timebuf);
+
+		len = strnlen(timebuf, sizeof(timebuf));
+		if (len < BUF_TIME_LEN) {
+			timebuf[len - 1] = '\0'; /* erase \n */
+		} else {
+			printf(" Invalid time stamp buffer size \n");
+			fpgad_mutex_unlock(err, &log_lock);
+			return -1;
+		}
+
+		res = fprintf(log_file, "----- %s -----\n", timebuf);
+		fflush(log_file);
+	} else {
+		res = -1;
+	}
+
+	fpgad_mutex_unlock(err, &log_lock);
+
+	return res;
+}
+
+int log_printf(const char *fmt, ...)
+{
+	va_list l;
+	int res = -1;
+	int err;
+
+	va_start(l, fmt);
+
+	fpgad_mutex_lock(err, &log_lock);
+
+	if (log_file) {
+		res = vfprintf(log_file, fmt, l);
+		fflush(log_file);
+	}
+
+	fpgad_mutex_unlock(err, &log_lock);
+
+	va_end(l);
+
+	return res;
+}
+
+void log_set(FILE *fptr)
+{
+	int err;
+
+	fpgad_mutex_lock(err, &log_lock);
+
+	log_close();
+	log_file = fptr;
+
+	fpgad_mutex_unlock(err, &log_lock);
+}
+
+void log_close(void)
+{
+	int err;
+
+	fpgad_mutex_lock(err, &log_lock);
+
+	if (log_file) {
+		if (log_file != stdout &&
+		    log_file != stderr) {
+			fclose(log_file);
+		}
+		log_file = NULL;
+	}
+
+	fpgad_mutex_unlock(err, &log_lock);
+}
diff --git a/tools/fpgad/api/logging.h b/tools/fpgad/api/logging.h
new file mode 100644
index 00000000..b4e95c1d
--- /dev/null
+++ b/tools/fpgad/api/logging.h
@@ -0,0 +1,37 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_API_LOGGING_H__
+#define __FPGAD_API_LOGGING_H__
+
+#include "fpgad/fpgad.h"
+
+int log_open(const char *filename);
+int log_printf(const char *fmt, ...);
+void log_set(FILE *fptr);
+void log_close(void);
+
+#endif /* __FPGAD_API_LOGGING_H__ */
diff --git a/tools/fpgad/api/opae_events_api.c b/tools/fpgad/api/opae_events_api.c
new file mode 100644
index 00000000..14a757f0
--- /dev/null
+++ b/tools/fpgad/api/opae_events_api.c
@@ -0,0 +1,239 @@
+// Copyright(c) 2018-2019, 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 <inttypes.h>
+
+#include "opae_events_api.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("opae_events_api: " format, ##__VA_ARGS__)
+
+STATIC pthread_mutex_t list_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+STATIC api_client_event_registry *event_registry_list;
+
+int opae_api_register_event(int conn_socket,
+			    int fd,
+			    fpga_event_type e,
+			    uint64_t object_id)
+{
+	api_client_event_registry *r =
+		(api_client_event_registry *) malloc(sizeof(*r));
+	int err;
+
+	if (!r)
+		return ENOMEM;
+
+	r->conn_socket = conn_socket;
+	r->fd = fd;
+	r->data = 1;
+	r->event = e;
+	r->object_id = object_id;
+
+	fpgad_mutex_lock(err, &list_lock);
+
+	r->next = event_registry_list;
+	event_registry_list = r;
+
+	fpgad_mutex_unlock(err, &list_lock);
+
+	return 0;
+}
+
+STATIC void release_event_registry(api_client_event_registry *r)
+{
+	close(r->fd);
+	free(r);
+}
+
+int opae_api_unregister_event(int conn_socket,
+			      fpga_event_type e,
+			      uint64_t object_id)
+{
+	api_client_event_registry *trash;
+	api_client_event_registry *save;
+	int err;
+	int res = 0;
+
+	fpgad_mutex_lock(err, &list_lock);
+
+	trash = event_registry_list;
+
+	if (!trash) { // empty list
+		res = 1;
+		goto out_unlock;
+	}
+
+	if ((conn_socket == trash->conn_socket) &&
+		(e == trash->event) &&
+		(object_id == trash->object_id)) {
+
+		// found at head of list
+
+		event_registry_list = event_registry_list->next;
+		release_event_registry(trash);
+		goto out_unlock;
+	}
+
+	save = trash;
+	trash = trash->next;
+	while (trash) {
+
+		if ((conn_socket == trash->conn_socket) &&
+			(e == trash->event) &&
+			(object_id == trash->object_id))
+			break;
+
+		save = trash;
+		trash = trash->next;
+	}
+
+	if (!trash) { // not found
+		res = 1;
+		goto out_unlock;
+	}
+
+	// found at trash
+	save->next = trash->next;
+	release_event_registry(trash);
+
+out_unlock:
+	fpgad_mutex_unlock(err, &list_lock);
+	return res;
+}
+
+STATIC api_client_event_registry *
+find_event_for(int conn_socket)
+{
+	api_client_event_registry *r;
+
+	for (r = event_registry_list ; r ; r = r->next)
+		if (conn_socket == r->conn_socket)
+			break;
+
+	return r;
+}
+
+void opae_api_unregister_all_events_for(int conn_socket)
+{
+	api_client_event_registry *r;
+	int err;
+
+	fpgad_mutex_lock(err, &list_lock);
+
+	r = find_event_for(conn_socket);
+	while (r) {
+		opae_api_unregister_event(conn_socket, r->event, r->object_id);
+		r = find_event_for(conn_socket);
+	}
+
+	fpgad_mutex_unlock(err, &list_lock);
+}
+
+void opae_api_unregister_all_events(void)
+{
+	api_client_event_registry *r;
+	int err;
+
+	fpgad_mutex_lock(err, &list_lock);
+
+	for (r = event_registry_list ; r != NULL ; ) {
+		api_client_event_registry *trash;
+		trash = r;
+		r = r->next;
+		release_event_registry(trash);
+	}
+
+	event_registry_list = NULL;
+
+	fpgad_mutex_unlock(err, &list_lock);
+}
+
+void opae_api_for_each_registered_event
+(void (*cb)(api_client_event_registry *r, void *context),
+void *context)
+{
+	api_client_event_registry *r;
+	int err;
+
+	fpgad_mutex_lock(err, &list_lock);
+
+	for (r = event_registry_list; r != NULL; r = r->next) {
+		cb(r, context);
+	}
+
+	fpgad_mutex_unlock(err, &list_lock);
+}
+
+STATIC void check_and_send_EVENT_ERROR(api_client_event_registry *r,
+				       void *context)
+{
+	fpgad_monitored_device *d =
+		(fpgad_monitored_device *)context;
+
+	if ((r->event == FPGA_EVENT_ERROR) &&
+	    (r->object_id == d->object_id)) {
+		LOG("object_id: 0x%" PRIx64 " event: FPGA_EVENT_ERROR\n",
+			d->object_id);
+		if (write(r->fd, &r->data, sizeof(r->data)) < 0)
+			LOG("write failed: %s\n", strerror(errno));
+		r->data++;
+	}
+}
+
+void opae_api_send_EVENT_ERROR(fpgad_monitored_device *d)
+{
+	opae_api_for_each_registered_event(check_and_send_EVENT_ERROR,
+					   d);
+}
+
+STATIC void check_and_send_EVENT_POWER_THERMAL(api_client_event_registry *r,
+					       void *context)
+{
+	fpgad_monitored_device *d =
+		(fpgad_monitored_device *)context;
+
+	if ((r->event == FPGA_EVENT_POWER_THERMAL) &&
+	    (r->object_id == d->object_id)) {
+		LOG("object_id: 0x%" PRIx64 " event: FPGA_EVENT_POWER_THERMAL\n",
+			d->object_id);
+		if (write(r->fd, &r->data, sizeof(r->data)) < 0)
+			LOG("write failed: %s\n", strerror(errno));
+		r->data++;
+	}
+}
+
+void opae_api_send_EVENT_POWER_THERMAL(fpgad_monitored_device *d)
+{
+	opae_api_for_each_registered_event(check_and_send_EVENT_POWER_THERMAL,
+					   d);
+}
diff --git a/tools/fpgad/api/opae_events_api.h b/tools/fpgad/api/opae_events_api.h
new file mode 100644
index 00000000..9e165cc1
--- /dev/null
+++ b/tools/fpgad/api/opae_events_api.h
@@ -0,0 +1,85 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_API_OPAE_EVENTS_API_H__
+#define __FPGAD_API_OPAE_EVENTS_API_H__
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <opae/types.h>
+
+#include "fpgad/fpgad.h"
+#include "fpgad/monitored_device.h"
+
+enum request_type {
+	REGISTER_EVENT = 0,
+	UNREGISTER_EVENT
+};
+
+struct event_request {
+	enum request_type type;
+	fpga_event_type event;
+	uint64_t object_id;
+};
+
+typedef struct _api_client_event_registry {
+	int conn_socket;
+	int fd;
+	uint64_t data;
+	fpga_event_type event;
+	uint64_t object_id;
+	struct _api_client_event_registry *next;
+} api_client_event_registry;
+
+// 0 on success
+int opae_api_register_event(int conn_socket,
+			    int fd,
+			    fpga_event_type e,
+			    uint64_t object_id);
+
+// 0 on success
+int opae_api_unregister_event(int conn_socket,
+			      fpga_event_type e,
+			      uint64_t object_id);
+
+void opae_api_unregister_all_events_for(int conn_socket);
+
+void opae_api_unregister_all_events(void);
+
+void opae_api_for_each_registered_event(void (*cb)(api_client_event_registry *r,
+						   void *context),
+					void *context);
+
+void opae_api_send_EVENT_ERROR(fpgad_monitored_device *d);
+
+void opae_api_send_EVENT_POWER_THERMAL(fpgad_monitored_device *d);
+
+#endif /* __FPGAD_API_OPAE_EVENTS_API_H__ */
diff --git a/tools/fpgad/api/sysfs.c b/tools/fpgad/api/sysfs.c
new file mode 100644
index 00000000..ae8d1403
--- /dev/null
+++ b/tools/fpgad/api/sysfs.c
@@ -0,0 +1,76 @@
+// Copyright(c) 2019-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 <stdio.h>
+
+#include "logging.h"
+#include "sysfs.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("sysfs: " format, ##__VA_ARGS__)
+
+int file_write_string(const char *path, const char *str, size_t len)
+{
+	FILE *fp;
+	size_t num;
+
+	fp = fopen(path, "w");
+	if (!fp)
+		return 1;
+
+	num = fwrite(str, 1, len, fp);
+
+	if (!num || ferror(fp)) {
+		fclose(fp);
+		return 1;
+	}
+
+	fclose(fp);
+
+	return 0;
+}
+
+char *cstr_dup(const char *s)
+{
+	char *p;
+	size_t len = strnlen(s, 8192);
+
+	p = malloc(len+1);
+	if (!p)
+		return NULL;
+
+	memcpy(p, s, len);
+	p[len] = '\0';
+
+	return p;
+}
diff --git a/tools/fpgad/api/sysfs.h b/tools/fpgad/api/sysfs.h
new file mode 100644
index 00000000..f8f3b3d9
--- /dev/null
+++ b/tools/fpgad/api/sysfs.h
@@ -0,0 +1,45 @@
+// Copyright(c) 2019, 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.
+
+#ifndef __FPGAD_API_SYSFS_H__
+#define __FPGAD_API_SYSFS_H__
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <opae/types.h>
+#include <stdint.h>
+
+// 0 on success
+int file_write_string(const char *path, const char *str, size_t len);
+
+char *cstr_dup(const char *s);
+
+#endif /* __FPGAD_API_SYSFS_H__ */
diff --git a/tools/fpgad/command_line.c b/tools/fpgad/command_line.c
new file mode 100644
index 00000000..4e0bfb9d
--- /dev/null
+++ b/tools/fpgad/command_line.c
@@ -0,0 +1,516 @@
+// 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.
+
+#define _GNU_SOURCE
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "command_line.h"
+#include "config_file.h"
+#include "monitored_device.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("args: " format, ##__VA_ARGS__)
+
+extern fpgad_supported_device default_supported_devices_table[];
+
+#define OPT_STR ":hdl:p:s:n:c:v"
+
+STATIC struct option longopts[] = {
+	{ "help",           no_argument,       NULL, 'h' },
+	{ "daemon",         no_argument,       NULL, 'd' },
+	{ "logfile",        required_argument, NULL, 'l' },
+	{ "pidfile",        required_argument, NULL, 'p' },
+	{ "socket",         required_argument, NULL, 's' },
+	{ "null-bitstream", required_argument, NULL, 'n' },
+	{ "config",         required_argument, NULL, 'c' },
+	{ "version",        no_argument,       NULL, 'v' },
+
+	{ 0, 0, 0, 0 }
+};
+
+#define DEFAULT_DIR_ROOT      "/var/lib/opae"
+#define DEFAULT_DIR_ROOT_SIZE 13
+#define DEFAULT_LOG           "fpgad.log"
+#define DEFAULT_PID           "fpgad.pid"
+#define DEFAULT_CFG           "fpgad.cfg"
+
+void cmd_show_help(FILE *fptr)
+{
+	fprintf(fptr, "Usage: fpgad <options>\n");
+	fprintf(fptr, "\n");
+	fprintf(fptr, "\t-d,--daemon                 run as daemon process.\n");
+	fprintf(fptr, "\t-l,--logfile <file>         the log file for daemon mode [%s].\n", DEFAULT_LOG);
+	fprintf(fptr, "\t-p,--pidfile <file>         the pid file for daemon mode [%s].\n", DEFAULT_PID);
+	fprintf(fptr, "\t-s,--socket <sock>          the unix domain socket [/tmp/fpga_event_socket].\n");
+	fprintf(fptr, "\t-n,--null-bitstream <file>  NULL bitstream (for AP6 handling, may be\n"
+		      "\t                            given multiple times).\n");
+	fprintf(fptr, "\t-c,--config <file>          the configuration file [%s].\n", DEFAULT_CFG);
+	fprintf(fptr, "\t-v,--version                display the version and exit.\n");
+}
+
+STATIC bool cmd_register_null_gbs(struct fpgad_config *c, char *null_gbs_path)
+{
+	char *canon_path = NULL;
+
+	if (c->num_null_gbs < (sizeof(c->null_gbs) / sizeof(c->null_gbs[0]))) {
+		canon_path = canonicalize_file_name(null_gbs_path);
+
+		if (canon_path) {
+
+			memset(&c->null_gbs[c->num_null_gbs], 0,
+				 sizeof(opae_bitstream_info));
+
+			if (opae_load_bitstream(canon_path,
+						&c->null_gbs[c->num_null_gbs])) {
+				LOG("failed to load NULL GBS \"%s\"\n", canon_path);
+				opae_unload_bitstream(&c->null_gbs[c->num_null_gbs]);
+				free(canon_path);
+				return false;
+			}
+
+			c->num_null_gbs++;
+
+			LOG("registering NULL bitstream \"%s\"\n", canon_path);
+
+		} else {
+			LOG("error with NULL GBS argument: %s\n", strerror(errno));
+			return false;
+		}
+
+	} else {
+		LOG("maximum number of NULL bitstreams exceeded. Ignoring -n option.\n");
+	}
+	return true;
+}
+
+int cmd_parse_args(struct fpgad_config *c, int argc, char *argv[])
+{
+	int getopt_ret;
+	int option_index;
+	size_t len;
+
+	while (-1 != (getopt_ret = getopt_long(argc, argv, OPT_STR, longopts, &option_index))) {
+		const char *tmp_optarg = optarg;
+
+		if (optarg && ('=' == *tmp_optarg))
+			++tmp_optarg;
+
+		if (!optarg && (optind < argc) &&
+			(NULL != argv[optind]) &&
+			('-' != argv[optind][0]))
+			tmp_optarg = argv[optind++];
+
+		switch (getopt_ret) {
+		case 'h':
+			cmd_show_help(stdout);
+			return -2;
+			break;
+
+		case 'd':
+			c->daemon = 1;
+			LOG("daemon requested\n");
+			break;
+
+		case 'l':
+			if (tmp_optarg) {
+				len = strnlen(tmp_optarg, PATH_MAX - 1);
+				memcpy(c->logfile, tmp_optarg, len);
+				c->logfile[len] = '\0';
+			} else {
+				LOG("missing logfile parameter.\n");
+				return 1;
+			}
+			break;
+
+		case 'p':
+			if (tmp_optarg) {
+				len = strnlen(tmp_optarg, PATH_MAX - 1);
+				memcpy(c->pidfile, tmp_optarg, len);
+				c->pidfile[len] = '\0';
+			} else {
+				LOG("missing pidfile parameter.\n");
+				return 1;
+			}
+			break;
+
+		case 'n':
+			if (tmp_optarg) {
+				if (!cmd_register_null_gbs(c, (char *)tmp_optarg)) {
+					LOG("invalid null gbs path: \"%s\"\n", tmp_optarg);
+					return 1;
+				}
+			} else {
+				LOG("missing bitstream parameter.\n");
+				return 1;
+			}
+			break;
+
+		case 's':
+			if (tmp_optarg) {
+				c->api_socket = tmp_optarg;
+				LOG("daemon socket is %s\n", c->api_socket);
+			} else {
+				LOG("missing socket parameter.\n");
+				return 1;
+			}
+			break;
+
+		case 'c':
+			if (tmp_optarg) {
+				len = strnlen(tmp_optarg, PATH_MAX - 1);
+				memcpy(c->cfgfile, tmp_optarg, len);
+				c->cfgfile[len] = '\0';
+			} else {
+				LOG("missing cfgfile parameter.\n");
+				return 1;
+			}
+			break;
+
+		case 'v':
+			fprintf(stdout, "fpgad %s %s%s\n",
+					OPAE_VERSION,
+					OPAE_GIT_COMMIT_HASH,
+					OPAE_GIT_SRC_TREE_DIRTY ? "*":"");
+			return -2;
+			break;
+
+		case ':':
+			LOG("Missing option argument.\n");
+			return 1;
+
+		case '?':
+			LOG("Invalid command option.\n");
+			return 1;
+
+		default:
+			LOG("Invalid command option.\n");
+			return 1;
+		}
+
+	}
+
+	return 0;
+}
+
+int cmd_canonicalize_paths(struct fpgad_config *c)
+{
+	char *sub;
+	bool def;
+	mode_t mode;
+	struct stat stat_buf;
+	bool search = true;
+	char buf[PATH_MAX];
+	char *canon_path;
+	uid_t uid;
+	size_t len;
+
+	uid = geteuid();
+
+	if (!uid) {
+		// If we're being run as root, then use DEFAULT_DIR_ROOT
+		// as the working directory.
+		memcpy(c->directory, DEFAULT_DIR_ROOT, sizeof(DEFAULT_DIR_ROOT));
+		c->directory[sizeof(DEFAULT_DIR_ROOT)] = '\0';
+		mode = 0755;
+		c->filemode = 0026;
+	} else {
+		// We're not root. Try to use ${HOME}/.opae
+		struct passwd *passwd;
+
+		passwd = getpwuid(uid);
+
+		canon_path = canonicalize_file_name(passwd->pw_dir);
+
+		if (canon_path) {
+			snprintf(c->directory, sizeof(c->directory),
+					"%s/.opae", canon_path);
+			free(canon_path);
+		} else {
+			// ${HOME} not found or invalid - use current dir.
+			if (getcwd(buf, sizeof(buf))) {
+				if (snprintf(c->directory, sizeof(c->directory),
+					     "%s/.opae", buf) < 0) {
+					len = strnlen("./.opae",
+						sizeof(c->directory) - 1);
+					memcpy(c->directory, "./.opae", len);
+					c->directory[len] = '\0';
+				}
+			} else {
+				// Current directory not found - use /
+				len = strnlen("/.opae", sizeof(c->directory) - 1);
+				memcpy(c->directory, "/.opae", len);
+				c->directory[len] = '\0';
+			}
+		}
+
+		mode = 0775;
+		c->filemode = 0022;
+	}
+
+	if (cmd_path_is_symlink(c->directory)) {
+		LOG("Aborting - working directory contains a link: %s\n.",
+		    c->directory);
+		return 1;
+	}
+	LOG("daemon working directory is %s\n", c->directory);
+
+	// Create the directory if it doesn't exist.
+	if (lstat(c->directory, &stat_buf) && (errno == ENOENT)) {
+		if (mkdir(c->directory, mode)) {
+			LOG("mkdir failed\n");
+			return 1;
+		}
+	}
+
+	// Verify logfile and pidfile do not contain ".."
+	// nor "/".
+	def = false;
+	sub = strstr(c->logfile, "..");
+	if (sub)
+		def = true;
+
+	sub = strstr(c->logfile, "/");
+	if (sub)
+		def = true;
+
+	if (def || (c->logfile[0] == '\0')) {
+		if (snprintf(c->logfile, sizeof(c->logfile),
+			     "%s/%s", c->directory, DEFAULT_LOG) < 0) {
+			len = strnlen("./" DEFAULT_LOG,
+					sizeof(c->logfile) - 1);
+			memcpy(c->logfile, "./" DEFAULT_LOG, len);
+			c->logfile[len] = '\0';
+		}
+	} else {
+		len = strnlen(c->logfile, sizeof(buf) - 1);
+		memcpy(buf, c->logfile, len);
+		buf[len] = '\0';
+
+		if (snprintf(c->logfile, sizeof(c->logfile),
+			     "%s/%s", c->directory, buf) < 0) {
+			len = strnlen("./" DEFAULT_LOG,
+					sizeof(c->logfile) - 1);
+			memcpy(c->logfile, "./" DEFAULT_LOG, len);
+			c->logfile[len] = '\0';
+		}
+	}
+
+	if (cmd_path_is_symlink(c->logfile)) {
+		LOG("Aborting - log file path contains a link: %s\n.",
+		    c->logfile);
+		return 1;
+	}
+	LOG("daemon log file is %s\n", c->logfile);
+
+	def = false;
+	sub = strstr(c->pidfile, "..");
+	if (sub)
+		def = true;
+
+	sub = strstr(c->pidfile, "/");
+	if (sub)
+		def = true;
+
+	if (def || (c->pidfile[0] == '\0')) {
+
+		if (snprintf(c->pidfile, sizeof(c->pidfile),
+			     "%s/%s", c->directory, DEFAULT_PID) < 0) {
+			len = strnlen("./" DEFAULT_PID,
+					sizeof(c->pidfile) - 1);
+			memcpy(c->pidfile, "./" DEFAULT_PID, len);
+			c->pidfile[len] = '\0';
+		}
+
+	} else {
+		len = strnlen(c->pidfile, sizeof(buf) - 1);
+		memcpy(buf, c->pidfile, len);
+		buf[len] = '\0';
+
+		if (snprintf(c->pidfile, sizeof(c->pidfile),
+			     "%s/%s", c->directory, buf) < 0) {
+			len = strnlen("./" DEFAULT_PID,
+					sizeof(c->pidfile) - 1);
+			memcpy(c->pidfile, "./" DEFAULT_PID, len);
+			c->pidfile[len] = '\0';
+		}
+	}
+
+	if (cmd_path_is_symlink(c->pidfile)) {
+		LOG("Aborting - pid file path contains a link: %s\n.",
+		    c->pidfile);
+		return 1;
+	}
+	LOG("daemon pid file is %s\n", c->pidfile);
+
+	// Verify cfgfile doesn't contain ".."
+	def = false;
+	sub = strstr(c->cfgfile, "..");
+	if (sub)
+		def = true;
+
+	if (def || (c->cfgfile[0] == '\0')) {
+		search = true;
+	} else {
+		canon_path = canonicalize_file_name(c->cfgfile);
+		if (canon_path) {
+
+			if (!cmd_path_is_symlink(c->cfgfile)) {
+
+				len = strnlen(canon_path,
+					      sizeof(c->cfgfile) - 1);
+				memcpy(c->cfgfile,
+					  canon_path,
+					  len);
+				c->cfgfile[len] = '\0';
+
+				if (!cfg_load_config(c)) {
+					LOG("daemon cfg file is %s\n",
+					    c->cfgfile);
+					search = false; // found and loaded it
+				}
+
+			}
+
+			free(canon_path);
+		}
+	}
+
+	if (search) {
+		c->cfgfile[0] = '\0';
+		if (cfg_find_config_file(c))
+			LOG("failed to find config file.\n");
+		else {
+			if (cfg_load_config(c))
+				LOG("failed to load config file %s\n",
+				    c->cfgfile);
+			else
+				LOG("daemon cfg file is %s\n", c->cfgfile);
+		}
+	}
+
+	if (!c->supported_devices) {
+		LOG("using default configuration.\n");
+		c->cfgfile[0] = '\0';
+		c->supported_devices = default_supported_devices_table;
+	}
+
+	return 0;
+}
+
+void cmd_destroy(struct fpgad_config *c)
+{
+	unsigned i;
+
+	if (c->daemon)
+		unlink(c->pidfile);
+
+	for (i = 0 ; i < c->num_null_gbs ; ++i) {
+		if (c->null_gbs[i].filename)
+			free((char *)c->null_gbs[i].filename);
+		opae_unload_bitstream(&c->null_gbs[i]);
+	}
+	c->num_null_gbs = 0;
+
+	if (c->supported_devices &&
+	    (c->supported_devices != default_supported_devices_table)) {
+
+		for (i = 0 ; c->supported_devices[i].library_path ; ++i) {
+			fpgad_supported_device *d = &c->supported_devices[i];
+			if (d->library_path)
+				free((void *)d->library_path);
+			if (d->config)
+				free((void *)d->config);
+		}
+
+		free(c->supported_devices);
+	}
+	c->supported_devices = NULL;
+}
+
+bool cmd_path_is_symlink(const char *path)
+{
+	char component[PATH_MAX];
+	struct stat stat_buf;
+	size_t len;
+	char *pslash;
+
+	len = strnlen(path, PATH_MAX - 1);
+	if (!len) // empty path
+		return false;
+
+	memcpy(component, path, len);
+	component[len] = '\0';
+
+	if (component[0] == '/') {
+		// absolute path
+
+		pslash = realpath(path, component);
+
+		if (strcmp(component, path))
+			return true;
+
+
+	} else {
+		// relative path
+
+		pslash = strrchr(component, '/');
+
+		while (pslash) {
+
+			if (fstatat(AT_FDCWD, component,
+				    &stat_buf, AT_SYMLINK_NOFOLLOW)) {
+				return false;
+			}
+
+			if (S_ISLNK(stat_buf.st_mode))
+				return true;
+
+			*pslash = '\0';
+			pslash = strrchr(component, '/');
+		}
+
+		if (fstatat(AT_FDCWD, component,
+			    &stat_buf, AT_SYMLINK_NOFOLLOW)) {
+			return false;
+		}
+
+		if (S_ISLNK(stat_buf.st_mode))
+			return true;
+
+	}
+
+	return false;
+}
diff --git a/tools/fpgad/command_line.h b/tools/fpgad/command_line.h
new file mode 100644
index 00000000..99aec655
--- /dev/null
+++ b/tools/fpgad/command_line.h
@@ -0,0 +1,84 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_COMMAND_LINE_H__
+#define __FPGAD_COMMAND_LINE_H__
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <linux/limits.h>
+#include "bitstream.h"
+
+struct _fpgad_supported_device;
+
+#define MAX_NULL_GBS 32
+
+struct fpgad_config {
+	useconds_t poll_interval_usec;
+
+	bool daemon;
+	char directory[PATH_MAX];
+	char logfile[PATH_MAX];
+	char pidfile[PATH_MAX];
+	char cfgfile[PATH_MAX];
+	mode_t filemode;
+
+	bool running;
+
+	const char *api_socket;
+
+	opae_bitstream_info null_gbs[MAX_NULL_GBS];
+	unsigned num_null_gbs;
+
+	pthread_t bmc_monitor_thr;
+	pthread_t monitor_thr;
+	pthread_t event_dispatcher_thr;
+	pthread_t events_api_thr;
+
+	struct _fpgad_supported_device *supported_devices;
+};
+
+extern struct fpgad_config global_config;
+
+/*
+** Returns
+**  -2 if --help requested
+**  -1 on parse error
+**   0 on success
+*/
+int cmd_parse_args(struct fpgad_config *c, int argc, char *argv[]);
+
+void cmd_show_help(FILE *fptr);
+
+// 0 on success
+int cmd_canonicalize_paths(struct fpgad_config *c);
+
+void cmd_destroy(struct fpgad_config *c);
+
+bool cmd_path_is_symlink(const char *path);
+
+#endif /* __FPGAD_COMMAND_LINE_H__ */
diff --git a/tools/fpgad/config_file.c b/tools/fpgad/config_file.c
new file mode 100644
index 00000000..2172d2fc
--- /dev/null
+++ b/tools/fpgad/config_file.c
@@ -0,0 +1,621 @@
+// 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
+
+#include <sys/types.h>
+#include <pwd.h>
+#include "config_file.h"
+#include "monitored_device.h"
+#include "api/sysfs.h"
+
+#include <json-c/json.h>
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("cfg: " format, ##__VA_ARGS__)
+
+#define CFG_TRY_FILE(__f) \
+do { \
+	canon = canonicalize_file_name(__f); \
+	if (canon) { \
+ \
+		if (!cmd_path_is_symlink(__f)) { \
+			size_t len = strnlen(canon, \
+					sizeof(c->cfgfile) - 1); \
+			memcpy(c->cfgfile, \
+				canon, \
+				len); \
+			c->cfgfile[len] = '\0'; \
+			free(canon); \
+			return 0; \
+		} \
+ \
+		free(canon); \
+	} \
+} while (0)
+
+int cfg_find_config_file(struct fpgad_config *c)
+{
+	char path[PATH_MAX];
+	char *e;
+	char *canon = NULL;
+	uid_t uid;
+	size_t len;
+
+	uid = geteuid();
+
+	e = getenv("FPGAD_CONFIG_FILE");
+	if (e) {
+		// try $FPGAD_CONFIG_FILE
+		len = strnlen(e, sizeof(path) - 1);
+		memcpy(path, e, len);
+		path[len] = '\0';
+
+		CFG_TRY_FILE(path);
+	}
+
+	if (!uid) {
+		CFG_TRY_FILE("/var/lib/opae/fpgad.cfg");
+	} else {
+		struct passwd *passwd;
+
+		passwd = getpwuid(uid);
+
+		// try $HOME/.opae/fpgad.cfg
+		snprintf(path, sizeof(path),
+			     "%s/.opae/fpgad.cfg", passwd->pw_dir);
+
+		CFG_TRY_FILE(path);
+	}
+
+	CFG_TRY_FILE("/etc/opae/fpgad.cfg");
+
+	return 1; // not found
+}
+
+STATIC char *cfg_read_file(const char *file)
+{
+	FILE *fp;
+	size_t len;
+	char *buf;
+
+	fp = fopen(file, "r");
+	if (!fp) {
+		LOG("fopen failed.\n");
+		return NULL;
+	}
+
+	if (fseek(fp, 0, SEEK_END)) {
+		LOG("fseek failed.\n");
+		fclose(fp);
+		return NULL;
+	}
+
+	len = (size_t)ftell(fp);
+	++len; // for \0
+
+	if (len == 1) {
+		LOG("%s is empty.\n", file);
+		fclose(fp);
+		return NULL;
+	}
+
+	if (fseek(fp, 0, SEEK_SET)) {
+		LOG("fseek failed.\n");
+		fclose(fp);
+		return NULL;
+	}
+
+	buf = (char *)malloc(len);
+	if (!buf) {
+		LOG("malloc failed.\n");
+		fclose(fp);
+		return NULL;
+	}
+
+	if ((fread(buf, 1, len - 1, fp) != len - 1) ||
+	    ferror(fp)) {
+		LOG("fread failed.\n");
+		fclose(fp);
+		free(buf);
+		return NULL;
+	}
+
+	fclose(fp);
+	buf[len - 1] = '\0';
+
+	return buf;
+}
+
+typedef struct _cfg_vendor_device_id {
+	uint16_t vendor_id;
+	uint16_t device_id;
+	struct _cfg_vendor_device_id *next;
+} cfg_vendor_device_id;
+
+typedef struct _cfg_plugin_configuration {
+	char *configuration;
+	bool enabled;
+	char *library;
+	cfg_vendor_device_id *devices;
+	struct _cfg_plugin_configuration *next;
+} cfg_plugin_configuration;
+
+STATIC cfg_vendor_device_id *alloc_device(uint16_t vendor_id,
+					  uint16_t device_id)
+{
+	cfg_vendor_device_id *p;
+
+	p = (cfg_vendor_device_id *)malloc(sizeof(cfg_vendor_device_id));
+	if (p) {
+		p->vendor_id = vendor_id;
+		p->device_id = device_id;
+		p->next = NULL;
+	}
+
+	return p;
+}
+
+STATIC cfg_plugin_configuration *alloc_configuration(char *configuration,
+						     bool enabled,
+						     char *library,
+						     cfg_vendor_device_id *devs)
+{
+	cfg_plugin_configuration *p;
+
+	p = (cfg_plugin_configuration *)
+		malloc(sizeof(cfg_plugin_configuration));
+	if (p) {
+		p->configuration = configuration;
+		p->enabled = enabled;
+		p->library = library;
+		p->devices = devs;
+		p->next = NULL;
+	}
+
+	return p;
+}
+
+STATIC cfg_vendor_device_id *
+cfg_process_plugin_devices(const char *name,
+			   json_object *j_devices)
+{
+	int i;
+	int devs;
+	cfg_vendor_device_id *head = NULL;
+	cfg_vendor_device_id *id = NULL;
+	uint16_t vendor_id;
+	uint16_t device_id;
+	const char *s;
+	char *endptr;
+
+	if (!json_object_is_type(j_devices, json_type_array)) {
+		LOG("'devices' JSON object not array.\n");
+		return NULL;
+	}
+
+	devs = json_object_array_length(j_devices);
+	for (i = 0 ; i < devs ; ++i) {
+		json_object *j_dev = json_object_array_get_idx(j_devices, i);
+		json_object *j_vid;
+		json_object *j_did;
+
+		if (!json_object_is_type(j_dev, json_type_array)) {
+			LOG("%s 'devices' entry %d not array.\n",
+				name, i);
+			goto out_free;
+		}
+
+		if (json_object_array_length(j_dev) != 2) {
+			LOG("%s 'devices' entry %d not array[2].\n",
+				name, i);
+			goto out_free;
+		}
+
+		j_vid = json_object_array_get_idx(j_dev, 0);
+		if (json_object_is_type(j_vid, json_type_string)) {
+			s = json_object_get_string(j_vid);
+			endptr = NULL;
+
+			vendor_id = (uint16_t)strtoul(s, &endptr, 0);
+			if (*endptr != '\0') {
+				LOG("%s malformed Vendor ID at devices[%d]\n",
+					name, i);
+				goto out_free;
+			}
+
+		} else if (json_object_is_type(j_vid, json_type_int)) {
+			vendor_id = (uint16_t)json_object_get_int(j_vid);
+		} else {
+			LOG("%s invalid Vendor ID at devices[%d]\n",
+				name, i);
+			goto out_free;
+		}
+
+		j_did = json_object_array_get_idx(j_dev, 1);
+		if (json_object_is_type(j_did, json_type_string)) {
+			s = json_object_get_string(j_did);
+			endptr = NULL;
+
+			device_id = (uint16_t)strtoul(s, &endptr, 0);
+			if (*endptr != '\0') {
+				LOG("%s malformed Device ID at devices[%d]\n",
+					name, i);
+				goto out_free;
+			}
+
+		} else if (json_object_is_type(j_did, json_type_int)) {
+			device_id = (uint16_t)json_object_get_int(j_did);
+		} else {
+			LOG("%s invalid Device ID at devices[%d]\n",
+				name, i);
+			goto out_free;
+		}
+
+		if (!head) {
+			head = alloc_device(vendor_id, device_id);
+			if (!head) {
+				LOG("malloc failed.\n");
+				goto out_free;
+			}
+
+			id = head;
+		} else {
+			id->next = alloc_device(vendor_id, device_id);
+			if (!id->next) {
+				LOG("malloc failed.\n");
+				goto out_free;
+			}
+
+			id = id->next;
+		}
+	}
+
+	return head;
+
+out_free:
+	for (id = head ; id ; ) {
+		cfg_vendor_device_id *trash = id;
+		id = id->next;
+		free(trash);
+	}
+	return NULL;
+}
+
+STATIC int cfg_process_plugin(const char *name,
+			      json_object *j_configurations,
+			      cfg_plugin_configuration **list)
+{
+	json_object *j_cfg_plugin = NULL;
+	json_object *j_cfg_plugin_configuration = NULL;
+	json_object *j_enabled = NULL;
+	json_object *j_plugin = NULL;
+	json_object *j_devices = NULL;
+	char *configuration = NULL;
+	bool enabled = false;
+	char *plugin = NULL;
+	cfg_plugin_configuration *c = NULL;
+
+	if (!json_object_object_get_ex(j_configurations,
+				       name,
+				       &j_cfg_plugin)) {
+		LOG("couldn't find configurations section"
+		    " for %s.\n", name);
+		return 1;
+	}
+
+	if (!json_object_object_get_ex(j_cfg_plugin,
+				       "configuration",
+				       &j_cfg_plugin_configuration)) {
+		LOG("couldn't find %s configuration section.\n", name);
+		return 1;
+	}
+
+	configuration = (char *)json_object_to_json_string_ext(
+				j_cfg_plugin_configuration,
+				JSON_C_TO_STRING_PLAIN);
+	if (!configuration) {
+		LOG("failed to parse configuration for %s.\n", name);
+		return 1;
+	}
+
+	configuration = cstr_dup(configuration);
+	if (!configuration) {
+		LOG("cstr_dup failed.\n");
+		return 1;
+	}
+
+	if (!json_object_object_get_ex(j_cfg_plugin,
+				       "enabled",
+				       &j_enabled)) {
+		LOG("couldn't find enabled key"
+		    " for %s.\n", name);
+		goto out_free;
+	}
+
+	if (!json_object_is_type(j_enabled, json_type_boolean)) {
+		LOG("enabled key for %s not boolean.\n", name);
+		goto out_free;
+	}
+
+	enabled = json_object_get_boolean(j_enabled);
+
+	if (!json_object_object_get_ex(j_cfg_plugin,
+				       "plugin",
+				       &j_plugin)) {
+		LOG("couldn't find plugin key"
+		    " for %s.\n", name);
+		goto out_free;
+	}
+
+	if (!json_object_is_type(j_plugin, json_type_string)) {
+		LOG("plugin key for %s not string.\n", name);
+		goto out_free;
+	}
+
+	plugin = cstr_dup(json_object_get_string(j_plugin));
+	if (!plugin) {
+		LOG("cstr_dup failed.\n");
+		goto out_free;
+	}
+
+	if (!json_object_object_get_ex(j_cfg_plugin,
+				       "devices",
+				       &j_devices)) {
+		LOG("couldn't find devices key"
+		    " for %s.\n", name);
+		goto out_free;
+	}
+
+	if (!(*list)) { // list is empty
+		c = alloc_configuration(configuration,
+					enabled,
+					plugin,
+					NULL);
+		if (!c) {
+			LOG("malloc failed.\n");
+			goto out_free;
+		}
+
+		*list = c;
+	} else {
+		for (c = *list ; c->next ; c = c->next)
+		/* find the end of the list */ ;
+
+		c->next = alloc_configuration(configuration,
+					      enabled,
+					      plugin,
+					      NULL);
+		if (!c->next) {
+			LOG("malloc failed.\n");
+			goto out_free;
+		}
+
+		c = c->next;
+	}
+
+	c->devices = cfg_process_plugin_devices(name, j_devices);
+
+	return 0;
+
+out_free:
+	if (configuration)
+		free(configuration);
+	if (plugin)
+		free(plugin);
+	if (c)
+		free(c);
+	return 1;
+}
+
+STATIC fpgad_supported_device *
+cfg_json_to_supported(cfg_plugin_configuration *configurations)
+{
+	cfg_plugin_configuration *c;
+	cfg_vendor_device_id *d;
+	size_t num_devices = 0;
+	fpgad_supported_device *supported;
+	int i;
+
+	// find the number of devices
+	for (c = configurations ; c ; c = c->next) {
+		if (!c->enabled) // skip it
+			continue;
+		for (d = c->devices ; d ; d = d->next) {
+			++num_devices;
+		}
+	}
+
+	++num_devices; // +1 for NULL terminator
+
+	supported = calloc(num_devices, sizeof(fpgad_supported_device));
+	if (!supported) {
+		LOG("calloc failed.\n");
+		return NULL;
+	}
+
+	i = 0;
+	for (c = configurations ; c ; c = c->next) {
+		if (!c->enabled) // skip it
+			continue;
+		for (d = c->devices ; d ; d = d->next) {
+			fpgad_supported_device *dev = &supported[i++];
+
+			dev->vendor_id = d->vendor_id;
+			dev->device_id = d->device_id;
+			dev->library_path = cstr_dup(c->library);
+			dev->config = cstr_dup(c->configuration);
+		}
+	}
+
+	for (c = configurations ; c ; ) {
+		cfg_plugin_configuration *ctrash = c;
+
+		for (d = c->devices ; d ; ) {
+			cfg_vendor_device_id *dtrash = d;
+			d = d->next;
+			free(dtrash);
+		}
+
+		c = c->next;
+
+		if (ctrash->configuration)
+			free(ctrash->configuration);
+		if (ctrash->library)
+			free(ctrash->library);
+		free(ctrash);
+	}
+
+	return supported;
+}
+
+STATIC bool cfg_verify_supported_devices(fpgad_supported_device *d)
+{
+	while (d->library_path) {
+		char *sub = NULL;
+
+		if (d->library_path[0] == '/') {
+			LOG("plugin library paths may not "
+			    "be absolute paths: %s\n", d->library_path);
+			return false;
+		}
+
+		if (cmd_path_is_symlink(d->library_path)) {
+			LOG("plugin library paths may not "
+			    "contain links: %s\n", d->library_path);
+			return false;
+		}
+
+		sub = strstr((char *)d->library_path, "..");
+		if (sub) {
+			LOG("plugin library paths may not "
+			    "contain .. : %s\n", d->library_path);
+			return false;
+		}
+
+		++d;
+	}
+
+	return true;
+}
+
+int cfg_load_config(struct fpgad_config *c)
+{
+	char *cfg_buf;
+	json_object *root = NULL;
+	json_object *j_configurations = NULL;
+	json_object *j_plugins = NULL;
+	enum json_tokener_error j_err = json_tokener_success;
+	int res = 1;
+	int num_plugins;
+	int i;
+	cfg_plugin_configuration *configurations = NULL;
+
+	cfg_buf = cfg_read_file(c->cfgfile);
+	if (!cfg_buf)
+		return res;
+
+	root = json_tokener_parse_verbose(cfg_buf, &j_err);
+	if (!root) {
+		LOG("error parsing %s: %s\n",
+		    c->cfgfile,
+		    json_tokener_error_desc(j_err));
+		goto out_free;
+	}
+
+	if (!json_object_object_get_ex(root,
+				       "configurations",
+				       &j_configurations)) {
+		LOG("failed to find configurations section in %s.\n",
+		    c->cfgfile);
+		goto out_put;
+	}
+
+	if (!json_object_object_get_ex(root, "plugins", &j_plugins)) {
+		LOG("failed to find plugins section in %s.\n", c->cfgfile);
+		goto out_put;
+	}
+
+	if (!json_object_is_type(j_plugins, json_type_array)) {
+		LOG("'plugins' JSON object not array.\n");
+		goto out_put;
+	}
+
+	num_plugins = json_object_array_length(j_plugins);
+	for (i = 0 ; i < num_plugins ; ++i) {
+		json_object *j_plugin;
+		const char *plugin_name;
+
+		j_plugin = json_object_array_get_idx(j_plugins, i);
+		plugin_name = json_object_get_string(j_plugin);
+
+		if (cfg_process_plugin(plugin_name,
+				       j_configurations,
+				       &configurations))
+			goto out_put;
+	}
+
+	if (!configurations) {
+		LOG("no configurations found in %s.\n", c->cfgfile);
+		goto out_put;
+	}
+
+	c->supported_devices = cfg_json_to_supported(configurations);
+
+	if (c->supported_devices) {
+
+		if (cfg_verify_supported_devices(c->supported_devices)) {
+			res = 0;
+		} else {
+			fpgad_supported_device *trash = c->supported_devices;
+
+			LOG("invalid configuration file\n");
+
+			while (trash->library_path) {
+				free((void *)trash->library_path);
+				if (trash->config)
+					free((void *)trash->config);
+
+				++trash;
+			}
+
+			free(c->supported_devices);
+			c->supported_devices = NULL;
+		}
+
+	}
+
+out_put:
+	json_object_put(root);
+out_free:
+	free(cfg_buf);
+	return res;
+}
diff --git a/tools/fpgad/config_file.h b/tools/fpgad/config_file.h
new file mode 100644
index 00000000..0e476a30
--- /dev/null
+++ b/tools/fpgad/config_file.h
@@ -0,0 +1,38 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_CONFIG_FILE_H__
+#define __FPGAD_CONFIG_FILE_H__
+
+#include "fpgad.h"
+
+// 0 on success
+int cfg_find_config_file(struct fpgad_config *c);
+
+// 0 on success
+int cfg_load_config(struct fpgad_config *c);
+
+#endif /* __FPGAD_CONFIG_FILE_H__ */
diff --git a/tools/fpgad/daemonize.c b/tools/fpgad/daemonize.c
new file mode 100644
index 00000000..79f05e50
--- /dev/null
+++ b/tools/fpgad/daemonize.c
@@ -0,0 +1,98 @@
+// 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.
+
+/*
+ * daemonize.c : routine to become a system daemon process.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+int daemonize(void (*hndlr)(int, siginfo_t *, void *), mode_t mask, const char *dir)
+{
+	pid_t pid;
+	pid_t sid;
+	int res;
+	int fd;
+	struct sigaction sa;
+
+	pid = fork();
+	if (pid < 0) // fork() failed.
+		return errno;
+
+	// 1) Orphan the child process so that it runs in the background.
+	if (pid > 0)
+		exit(0);
+
+	// 2) Become leader of a new session and process group leader of new process
+	// group. The process is now detached from its controlling terminal.
+	sid = setsid();
+	if (sid < 0)
+		return errno;
+
+	// 3) Establish signal handler.
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_flags     = SA_SIGINFO | SA_RESETHAND;
+	sa.sa_sigaction = hndlr;
+
+	res = sigaction(SIGINT, &sa, NULL);
+	if (res < 0)
+		return errno;
+
+	res = sigaction(SIGTERM, &sa, NULL);
+	if (res < 0)
+		return errno;
+
+	// 4) Orphan the child again - the session leading process terminates.
+	// (only session leaders can request TTY).
+	pid = fork();
+	if (pid < 0) // fork() failed.
+		return errno;
+
+	if (pid > 0)
+		exit(0);
+
+	// 5) Set new file mode mask.
+	umask(mask);
+
+	// 6) change directory
+	res = chdir(dir);
+	if (res < 0)
+		return errno;
+
+	// 7) Close all open file descriptors
+	fd = sysconf(_SC_OPEN_MAX);
+	while (fd >= 0)
+		close(fd--);
+
+	return 0;
+}
diff --git a/tools/fpgad/event_dispatcher_thread.c b/tools/fpgad/event_dispatcher_thread.c
new file mode 100644
index 00000000..235a3263
--- /dev/null
+++ b/tools/fpgad/event_dispatcher_thread.c
@@ -0,0 +1,266 @@
+// 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
+
+#include <semaphore.h>
+#include <time.h>
+#include <inttypes.h>
+#include "event_dispatcher_thread.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("event_dispatcher_thread: " format, ##__VA_ARGS__)
+
+event_dispatcher_thread_config event_dispatcher_config = {
+	.global = &global_config,
+	.sched_policy = SCHED_RR,
+	.sched_priority = 30,
+};
+
+#define EVENT_DISPATCH_QUEUE_DEPTH 512
+
+typedef struct _evt_dispatch_queue {
+	event_dispatch_queue_item q[EVENT_DISPATCH_QUEUE_DEPTH];
+	unsigned head;
+	unsigned tail;
+	pthread_mutex_t lock;
+} evt_dispatch_queue;
+
+STATIC sem_t evt_dispatch_sem;
+
+STATIC evt_dispatch_queue normal_queue = {
+	{ { NULL, NULL, NULL }, },
+	0,
+	0,
+	PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+};
+
+STATIC evt_dispatch_queue high_priority_queue = {
+	{ { NULL, NULL, NULL }, },
+	0,
+	0,
+	PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+};
+
+STATIC void evt_queue_init(evt_dispatch_queue *q)
+{
+	memset(q->q, 0, sizeof(q->q));
+	q->head = q->tail = 0;
+}
+
+STATIC void evt_queue_destroy(evt_dispatch_queue *q)
+{
+	q->head = q->tail = 0;
+}
+
+STATIC volatile bool dispatcher_is_ready = false;
+
+bool evt_dispatcher_is_ready(void)
+{
+	return dispatcher_is_ready;
+}
+
+STATIC bool evt_queue_is_full(evt_dispatch_queue *q)
+{
+	const size_t num = sizeof(q->q) / sizeof(q->q[0]);
+
+	if (q->tail > q->head) {
+		if ((q->head == 0) && (q->tail == (num - 1)))
+			return true;
+	} else if (q->tail < q->head) {
+		if (q->tail == (q->head - 1))
+			return true;
+	}
+	return false;
+}
+
+STATIC bool evt_queue_is_empty(evt_dispatch_queue *q)
+{
+	return q->head == q->tail;
+}
+
+STATIC bool _evt_queue_response(evt_dispatch_queue *q,
+				fpgad_respond_event_t callback,
+				fpgad_monitored_device *device,
+				void *context)
+{
+	int res;
+
+	opae_mutex_lock(res, &q->lock);
+
+	if (evt_queue_is_full(q)) {
+		opae_mutex_unlock(res, &q->lock);
+		return false;
+	}
+
+	q->q[q->tail].callback = callback;
+	q->q[q->tail].device = device;
+	q->q[q->tail].context = context;
+
+	q->tail = (q->tail + 1) % EVENT_DISPATCH_QUEUE_DEPTH;
+
+	opae_mutex_unlock(res, &q->lock);
+
+	sem_post(&evt_dispatch_sem);
+
+	return true;
+}
+
+STATIC bool _evt_queue_get(evt_dispatch_queue *q,
+			   event_dispatch_queue_item *item)
+{
+	int res;
+
+	opae_mutex_lock(res, &q->lock);
+
+	if (evt_queue_is_empty(q)) {
+		opae_mutex_unlock(res, &q->lock);
+		return false;
+	}
+
+	*item = q->q[q->head];
+	memset(&q->q[q->head], 0, sizeof(q->q[0]));
+	q->head = (q->head + 1) % EVENT_DISPATCH_QUEUE_DEPTH;
+
+	opae_mutex_unlock(res, &q->lock);
+
+	return true;
+}
+
+bool evt_queue_response(fpgad_respond_event_t callback,
+			fpgad_monitored_device *device,
+			void *context)
+{
+	return _evt_queue_response(&normal_queue,
+				   callback,
+				   device,
+				   context);
+}
+
+bool evt_queue_get(event_dispatch_queue_item *item)
+{
+	return _evt_queue_get(&normal_queue, item);
+}
+
+bool evt_queue_response_high(fpgad_respond_event_t callback,
+			     fpgad_monitored_device *device,
+			     void *context)
+{
+	return _evt_queue_response(&high_priority_queue,
+				   callback,
+				   device,
+				   context);
+}
+
+bool evt_queue_get_high(event_dispatch_queue_item *item)
+{
+	return _evt_queue_get(&high_priority_queue, item);
+}
+
+void *event_dispatcher_thread(void *thread_context)
+{
+	event_dispatcher_thread_config *c =
+		(event_dispatcher_thread_config *)thread_context;
+	struct sched_param sched_param;
+	int policy = 0;
+	int res;
+	struct timespec ts;
+
+	LOG("starting\n");
+
+	res = pthread_getschedparam(pthread_self(), &policy, &sched_param);
+	if (res) {
+		LOG("error getting scheduler params: %s\n", strerror(res));
+	} else {
+		policy = c->sched_policy;
+		sched_param.sched_priority = c->sched_priority;
+
+		res = pthread_setschedparam(pthread_self(),
+					    policy,
+					    &sched_param);
+		if (res) {
+			LOG("error setting scheduler params"
+			    " (got root?): %s\n", strerror(res));
+		}
+	}
+
+	evt_queue_init(&normal_queue);
+	evt_queue_init(&high_priority_queue);
+
+	if (sem_init(&evt_dispatch_sem, 0, 0)) {
+		LOG("failed to init queue sem.\n");
+		goto out_exit;
+	}
+
+	dispatcher_is_ready = true;
+
+	while (c->global->running) {
+
+		clock_gettime(CLOCK_REALTIME, &ts);
+
+		ts.tv_nsec += c->global->poll_interval_usec * 1000;
+		if (ts.tv_nsec > 1000000000) {
+			++ts.tv_sec;
+			ts.tv_nsec -= 1000000000;
+		}
+
+		res = sem_timedwait(&evt_dispatch_sem, &ts);
+
+		if (!res) {
+			event_dispatch_queue_item item;
+
+			// Process all high-priority items first
+			while (evt_queue_get_high(&item)) {
+				LOG("dispatching (high) for object_id: 0x%" PRIx64 ".\n",
+					item.device->object_id);
+				item.callback(item.device, item.context);
+			}
+
+			if (evt_queue_get(&item)) {
+				LOG("dispatching for object_id: 0x%" PRIx64 ".\n",
+					item.device->object_id);
+				item.callback(item.device, item.context);
+			}
+		}
+
+	}
+
+	dispatcher_is_ready = false;
+
+	evt_queue_destroy(&normal_queue);
+	evt_queue_destroy(&high_priority_queue);
+
+	sem_destroy(&evt_dispatch_sem);
+
+out_exit:
+	LOG("exiting\n");
+	return NULL;
+}
diff --git a/tools/fpgad/event_dispatcher_thread.h b/tools/fpgad/event_dispatcher_thread.h
new file mode 100644
index 00000000..5fdff07b
--- /dev/null
+++ b/tools/fpgad/event_dispatcher_thread.h
@@ -0,0 +1,63 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_EVENT_DISPATCHER_THREAD_H__
+#define __FPGAD_EVENT_DISPATCHER_THREAD_H__
+
+#include "fpgad.h"
+#include "monitored_device.h"
+
+typedef struct _event_dispatcher_thread_config {
+	struct fpgad_config *global;
+	int sched_policy;
+	int sched_priority;
+} event_dispatcher_thread_config;
+
+extern event_dispatcher_thread_config event_dispatcher_config;
+
+void *event_dispatcher_thread(void *);
+
+typedef struct _event_dispatch_queue_item {
+	fpgad_respond_event_t callback;
+	fpgad_monitored_device *device;
+	void *context;
+} event_dispatch_queue_item;
+
+bool evt_dispatcher_is_ready(void);
+
+bool evt_queue_response(fpgad_respond_event_t callback,
+			fpgad_monitored_device *device,
+			void *context);
+
+bool evt_queue_get(event_dispatch_queue_item *item);
+
+bool evt_queue_response_high(fpgad_respond_event_t callback,
+			     fpgad_monitored_device *device,
+			     void *context);
+
+bool evt_queue_get_high(event_dispatch_queue_item *item);
+
+#endif /* __FPGAD_EVENT_DISPATCHER_THREAD_H__ */
diff --git a/tools/fpgad/events_api_thread.c b/tools/fpgad/events_api_thread.c
new file mode 100644
index 00000000..19a9e134
--- /dev/null
+++ b/tools/fpgad/events_api_thread.c
@@ -0,0 +1,296 @@
+// 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
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <poll.h>
+#include <inttypes.h>
+#include "events_api_thread.h"
+#include "api/opae_events_api.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("events_api_thread: " format, ##__VA_ARGS__)
+
+events_api_thread_config events_api_config = {
+	.global = &global_config,
+	.sched_policy = SCHED_RR,
+	.sched_priority = 10,
+};
+
+#define MAX_CLIENT_CONNECTIONS 1023
+#define SRV_SOCKET             0
+#define FIRST_CLIENT_SOCKET    1
+
+/* array keeping track of all connection file descriptors (plus server socket) */
+STATIC struct pollfd pollfds[MAX_CLIENT_CONNECTIONS+1];
+STATIC nfds_t num_fds = 1;
+
+STATIC void remove_client(int conn_socket)
+{
+	nfds_t i, j;
+	nfds_t removed = 0;
+
+	opae_api_unregister_all_events_for(conn_socket);
+	LOG("closing connection conn_socket=%d.\n", conn_socket);
+	close(conn_socket);
+
+	for (i = j = FIRST_CLIENT_SOCKET ; i < num_fds ; ++i) {
+		if (conn_socket != pollfds[i].fd) {
+			if (j != i)
+				pollfds[j] = pollfds[i];
+			++j;
+		} else {
+			++removed;
+		}
+	}
+
+	num_fds -= removed;
+}
+
+STATIC int handle_message(int conn_socket)
+{
+	struct msghdr mh;
+	struct cmsghdr *cmh;
+	struct iovec iov[1];
+	struct event_request req;
+	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;
+
+	n = recvmsg(conn_socket, &mh, 0);
+	if (n < 0) {
+		LOG("recvmsg() failed: %s\n", strerror(errno));
+		return (int)n;
+	}
+
+	if (!n) { // socket closed by peer
+		remove_client(conn_socket);
+		return (int)n;
+	}
+
+	switch (req.type) {
+
+	case REGISTER_EVENT:
+		fd_ptr = (int *)CMSG_DATA(cmh);
+
+		if (opae_api_register_event(conn_socket, *fd_ptr,
+				    req.event, req.object_id)) {
+			LOG("failed to register event\n");
+			return -1;
+		}
+
+		LOG("registered event sock=%d:fd=%d"
+		     "(event=%d object_id=0x%" PRIx64  ")\n",
+			conn_socket, *fd_ptr, req.event, req.object_id);
+
+		break;
+
+	case UNREGISTER_EVENT:
+
+		if (opae_api_unregister_event(conn_socket,
+					      req.event,
+					      req.object_id)) {
+			LOG("failed to unregister event\n");
+			return -1;
+		}
+
+		LOG("unregistered event sock=%d:"
+		     "(event=%d object_id=0x%" PRIx64  ")\n",
+			conn_socket, req.event, req.object_id);
+
+		break;
+
+	default:
+		LOG("unknown request type %d\n", req.type);
+		return -1;
+	}
+
+	return 0;
+}
+
+STATIC volatile bool evt_api_is_ready = false;
+
+bool events_api_is_ready(void)
+{
+	return evt_api_is_ready;
+}
+
+void *events_api_thread(void *thread_context)
+{
+	events_api_thread_config *c =
+		(events_api_thread_config *)thread_context;
+	struct sched_param sched_param;
+	int policy = 0;
+	int res;
+
+	nfds_t i;
+	struct sockaddr_un addr;
+	int server_socket;
+	int conn_socket;
+	size_t len;
+
+	LOG("starting\n");
+
+	res = pthread_getschedparam(pthread_self(), &policy, &sched_param);
+	if (res) {
+		LOG("error getting scheduler params: %s\n", strerror(res));
+	} else {
+		policy = c->sched_policy;
+		sched_param.sched_priority = c->sched_priority;
+
+		res = pthread_setschedparam(pthread_self(),
+					    policy,
+					    &sched_param);
+		if (res) {
+			LOG("error setting scheduler params"
+			    " (got root?): %s\n", strerror(res));
+		}
+	}
+
+	unlink(c->global->api_socket);
+
+	server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (server_socket < 0) {
+		LOG("failed to create server socket.\n");
+		goto out_exit;
+	}
+	LOG("created server socket.\n");
+
+	addr.sun_family = AF_UNIX;
+
+	len = strnlen(c->global->api_socket, sizeof(addr.sun_path) - 1);
+	memcpy(addr.sun_path, c->global->api_socket, len);
+	addr.sun_path[len] = '\0';
+
+	if (bind(server_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		LOG("failed to bind server socket.\n");
+		goto out_close_server;
+	}
+	LOG("server socket bind success.\n");
+
+	if (listen(server_socket, 20) < 0) {
+		LOG("failed to listen on socket.\n");
+		goto out_close_server;
+	}
+	LOG("listening for connections.\n");
+
+	evt_api_is_ready = true;
+
+	pollfds[SRV_SOCKET].fd = server_socket;
+	pollfds[SRV_SOCKET].events = POLLIN | POLLPRI;
+	num_fds = 1;
+
+	while (c->global->running) {
+
+		res = poll(pollfds, num_fds, 100);
+		if (res < 0) {
+			LOG("poll error\n");
+			continue;
+		}
+
+		if (0 == res) // timeout
+			continue;
+
+		if ((nfds_t)res > num_fds) { // weird
+			LOG("something bad happened during poll!\n");
+			continue;
+		}
+
+		// handle requests on existing sockets
+		for (i = FIRST_CLIENT_SOCKET ; i < num_fds ; ++i) {
+			if (pollfds[i].revents) {
+				handle_message(pollfds[i].fd);
+			}
+		}
+
+		// handle new connection requests
+		if (pollfds[SRV_SOCKET].revents) {
+
+			if (num_fds == MAX_CLIENT_CONNECTIONS+1) {
+				LOG("exceeded max connections!\n");
+				continue;
+			}
+
+			conn_socket = accept(server_socket, NULL, NULL);
+
+			if (conn_socket < 0) {
+				LOG("failed to accept new connection!\n");
+			} else {
+				LOG("accepting connection %d.\n", conn_socket);
+
+				pollfds[num_fds].fd = conn_socket;
+				pollfds[num_fds].events = POLLIN | POLLPRI;
+				++num_fds;
+			}
+
+		}
+
+	}
+
+	opae_api_unregister_all_events();
+
+	// close any active client sockets
+	for (i = FIRST_CLIENT_SOCKET ; i < num_fds ; ++i) {
+		close(pollfds[i].fd);
+	}
+
+out_close_server:
+	evt_api_is_ready = false;
+	close(server_socket);
+out_exit:
+	LOG("exiting\n");
+	return NULL;
+}
diff --git a/tools/fpgad/events_api_thread.h b/tools/fpgad/events_api_thread.h
new file mode 100644
index 00000000..c097a4dc
--- /dev/null
+++ b/tools/fpgad/events_api_thread.h
@@ -0,0 +1,43 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_EVENTS_API_THREAD_H__
+#define __FPGAD_EVENTS_API_THREAD_H__
+
+#include "fpgad.h"
+#include <opae/types.h>
+
+typedef struct _events_api_thread_config {
+	struct fpgad_config *global;
+	int sched_policy;
+	int sched_priority;
+} events_api_thread_config;
+
+extern events_api_thread_config events_api_config;
+
+void *events_api_thread(void *);
+
+#endif /* __FPGAD_EVENTS_API_THREAD_H__ */
diff --git a/tools/fpgad/fpgad.c b/tools/fpgad/fpgad.c
new file mode 100644
index 00000000..ff88525a
--- /dev/null
+++ b/tools/fpgad/fpgad.c
@@ -0,0 +1,192 @@
+// 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
+
+#include <signal.h>
+#include "fpgad.h"
+#include "monitor_thread.h"
+#include "event_dispatcher_thread.h"
+#include "events_api_thread.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("main: " format, ##__VA_ARGS__)
+
+struct fpgad_config global_config;
+
+void sig_handler(int sig, siginfo_t *info, void *unused)
+{
+	UNUSED_PARAM(info);
+	UNUSED_PARAM(unused);
+	switch (sig) {
+	case SIGINT:
+		// Process interrupted.
+		LOG("Got SIGINT. Exiting.\n");
+		global_config.running = false;
+		break;
+	case SIGTERM:
+		// Process terminated.
+		LOG("Got SIGTERM. Exiting.\n");
+		global_config.running = false;
+		break;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int res;
+	FILE *fp;
+
+	memset(&global_config, 0, sizeof(global_config));
+
+	global_config.poll_interval_usec = 100 * 1000;
+	global_config.running = true;
+	global_config.api_socket = "/tmp/fpga_event_socket";
+	global_config.num_null_gbs = 0;
+
+	log_set(stdout);
+
+	res = cmd_parse_args(&global_config, argc, argv);
+	if (res != 0) {
+		if (res == -2)
+			res = 0;
+		else
+			LOG("error parsing command line.\n");
+		goto out_destroy;
+	}
+
+	if (cmd_canonicalize_paths(&global_config)) {
+		LOG("error with paths.\n");
+		goto out_destroy;
+	}
+
+	if (global_config.daemon) {
+
+		res = daemonize(sig_handler,
+				global_config.filemode,
+				global_config.directory);
+		if (res != 0) {
+			LOG("daemonize failed: %s\n", strerror(res));
+			goto out_destroy;
+		}
+
+
+	} else {
+		struct sigaction sa;
+
+		memset(&sa, 0, sizeof(sa));
+		sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
+		sa.sa_sigaction = sig_handler;
+
+		res = sigaction(SIGINT, &sa, NULL);
+		if (res < 0) {
+			LOG("failed to register SIGINT handler.\n");
+			goto out_destroy;
+		}
+
+		res = sigaction(SIGTERM, &sa, NULL);
+		if (res < 0) {
+			LOG("failed to register SIGTERM handler.\n");
+			goto out_destroy;
+		}
+	}
+
+	if (log_open(global_config.logfile) < 0) {
+		LOG("failed to open log file\n");
+		res = 1;
+		goto out_destroy;
+	}
+
+	fp = fopen(global_config.pidfile, "w");
+	if (NULL == fp) {
+		LOG("failed to open pid file\n");
+		res = 1;
+		goto out_destroy;
+	}
+	fprintf(fp, "%d\n", getpid());
+	fclose(fp);
+
+	res = mon_enumerate(&global_config);
+	if (res) {
+		LOG("OPAE device enumeration failed\n");
+		goto out_destroy;
+	}
+
+	res = pthread_create(&global_config.event_dispatcher_thr,
+			     NULL,
+			     event_dispatcher_thread,
+			     &event_dispatcher_config);
+	if (res) {
+		LOG("failed to create event_dispatcher_thread\n");
+		global_config.running = false;
+		goto out_destroy;
+	}
+
+	while (!evt_dispatcher_is_ready())
+		usleep(1);
+
+	res = pthread_create(&global_config.monitor_thr,
+			     NULL,
+			     monitor_thread,
+			     &monitor_config);
+	if (res) {
+		LOG("failed to create monitor_thread\n");
+		global_config.running = false;
+		goto out_stop_event_dispatcher;
+	}
+
+	res = pthread_create(&global_config.events_api_thr,
+			     NULL,
+			     events_api_thread,
+			     &events_api_config);
+	if (res) {
+		LOG("failed to create events_api_thread\n");
+		global_config.running = false;
+		goto out_stop_monitor;
+	}
+
+	if (pthread_join(global_config.events_api_thr, NULL)) {
+		LOG("failed to join events_api_thread\n");
+	}
+out_stop_monitor:
+	if (pthread_join(global_config.monitor_thr, NULL)) {
+		LOG("failed to join monitor_thread\n");
+	}
+out_stop_event_dispatcher:
+	if (pthread_join(global_config.event_dispatcher_thr, NULL)) {
+		LOG("failed to join event_dispatcher_thread\n");
+	}
+out_destroy:
+	mon_destroy(&global_config);
+	cmd_destroy(&global_config);
+	log_close();
+	return res;
+}
diff --git a/tools/fpgad/fpgad.cfg b/tools/fpgad/fpgad.cfg
new file mode 100644
index 00000000..41a656e6
--- /dev/null
+++ b/tools/fpgad/fpgad.cfg
@@ -0,0 +1,38 @@
+{
+	"configurations": {
+		"fpgad-xfpga": {
+			"configuration": {
+			},
+			"enabled": true,
+			"plugin": "libfpgad-xfpga.so",
+			"devices": [
+				[ "0x8086", "0xbcc0" ],
+				[ "0x8086", "0xbcc1" ]
+			]
+		},
+		"fpgad-vc": {
+			"configuration": {
+				"cool-down": 30,
+				"config-sensors-enabled": true,
+				"sensors": [
+					{
+						"id": 25,
+						"low-warn": 11.40,
+						"low-fatal": 10.56
+					}
+				]
+			},
+			"enabled": true,
+			"plugin": "libfpgad-vc.so",
+			"devices": [
+				[ "0x8086", "0x0b30" ],
+				[ "0x8086", "0x0b31" ]
+			]
+		}
+	},
+
+	"plugins": [
+		"fpgad-xfpga",
+		"fpgad-vc"
+	]
+}
diff --git a/tools/fpgad/fpgad.conf b/tools/fpgad/fpgad.conf
new file mode 100644
index 00000000..7fad49d9
--- /dev/null
+++ b/tools/fpgad/fpgad.conf
@@ -0,0 +1,4 @@
+# Intel FPGA daemon variables
+PIDFILE=fpgad.pid
+LOGFILE=fpgad.log
+CFGFILE=/etc/opae/fpgad.cfg
diff --git a/tools/fpgad/fpgad.h b/tools/fpgad/fpgad.h
new file mode 100644
index 00000000..ed501096
--- /dev/null
+++ b/tools/fpgad/fpgad.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef __FPGAD_FPGAD_H__
+#define __FPGAD_FPGAD_H__
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <opae/fpga.h>
+
+#include "opae_int.h"
+
+#include "api/logging.h"
+#include "command_line.h"
+
+int daemonize(void (*hndlr)(int, siginfo_t *, void *),
+	      mode_t mask,
+	      const char *dir);
+
+
+#define fpgad_mutex_lock(__res, __mtx_ptr)                                     \
+	({                                                                     \
+		(__res) = pthread_mutex_lock(__mtx_ptr);                       \
+		if (__res)                                                     \
+			LOG("pthread_mutex_lock failed: %s",                   \
+				strerror(errno));                              \
+		__res;                                                         \
+	})
+
+#define fpgad_mutex_unlock(__res, __mtx_ptr)                                   \
+	({                                                                     \
+		(__res) = pthread_mutex_unlock(__mtx_ptr);                     \
+		if (__res)                                                     \
+			LOG("pthread_mutex_unlock failed: %s",                 \
+					strerror(errno));                      \
+		__res;                                                         \
+	})
+
+#endif /* __FPGAD_FPGAD_H__ */
diff --git a/tools/fpgad/fpgad.service.in b/tools/fpgad/fpgad.service.in
new file mode 100644
index 00000000..0ec5bac1
--- /dev/null
+++ b/tools/fpgad/fpgad.service.in
@@ -0,0 +1,21 @@
+# Intel FPGA daemon
+[Unit]
+Description = FPGA monitor
+
+[Service]
+Type=simple
+StandardOutput=journal+console
+StandardError=journal+console
+EnvironmentFile=-/etc/sysconfig/fpgad.conf
+Restart=always
+RestartSec=10s
+SendSIGHUP=yes
+KillSignal=SIGHUP
+ExecStart=@CMAKE_INSTALL_PREFIX@/bin/fpgad \
+          -l $LOGFILE \
+          -p $PIDFILE \
+          -c $CFGFILE
+RestartPreventExitStatus=1
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tools/fpgad/monitor_thread.c b/tools/fpgad/monitor_thread.c
new file mode 100644
index 00000000..d4cfd9cd
--- /dev/null
+++ b/tools/fpgad/monitor_thread.c
@@ -0,0 +1,261 @@
+// Copyright(c) 2018-2019, 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 <dlfcn.h>
+#include "monitored_device.h"
+#include "monitor_thread.h"
+#include "event_dispatcher_thread.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("monitor_thread: " format, ##__VA_ARGS__)
+
+monitor_thread_config monitor_config = {
+	.global = &global_config,
+	.sched_policy = SCHED_RR,
+	.sched_priority = 20,
+};
+
+STATIC pthread_mutex_t mon_list_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+STATIC fpgad_monitored_device *monitored_device_list;
+
+STATIC void mon_queue_response(fpgad_detection_status status,
+			       fpgad_respond_event_t response,
+			       fpgad_monitored_device *d,
+			       void *response_context)
+{
+	if (status == FPGAD_STATUS_DETECTED_HIGH) {
+
+		if (evt_queue_response_high(response,
+					    d,
+					    response_context)) {
+			pthread_yield();
+		} else {
+			LOG("high priority event queue is full. Dropping!\n");
+		}
+
+	} else if (status == FPGAD_STATUS_DETECTED) {
+
+		if (evt_queue_response(response,
+				       d,
+				       response_context)) {
+			pthread_yield();
+		} else {
+			LOG("event queue is full. Dropping!\n");
+		}
+
+	}
+}
+
+STATIC void mon_monitor(fpgad_monitored_device *d)
+{
+	unsigned i;
+
+	if (!d->detections)
+		return;
+
+	for (i = 0 ; d->detections[i] ; ++i) {
+		fpgad_detection_status result;
+		fpgad_detect_event_t detect =
+			d->detections[i];
+		void *detect_context =
+			d->detection_contexts ?
+			d->detection_contexts[i] : NULL;
+
+		result = detect(d, detect_context);
+
+		if (result != FPGAD_STATUS_NOT_DETECTED && d->responses) {
+			fpgad_respond_event_t response =
+				d->responses[i];
+			void *response_context =
+				d->response_contexts ?
+				d->response_contexts[i] : NULL;
+
+			if (response) {
+				mon_queue_response(result,
+						   response,
+						   d,
+						   response_context);
+			}
+		}
+	}
+}
+
+STATIC volatile bool mon_is_ready = false;
+
+bool monitor_is_ready(void)
+{
+	return mon_is_ready;
+}
+
+void *monitor_thread(void *thread_context)
+{
+	monitor_thread_config *c = (monitor_thread_config *)thread_context;
+	struct sched_param sched_param;
+	int policy = 0;
+	int res;
+	int err;
+	fpgad_monitored_device *d;
+
+	LOG("starting\n");
+
+	res = pthread_getschedparam(pthread_self(), &policy, &sched_param);
+	if (res) {
+		LOG("error getting scheduler params: %s\n", strerror(res));
+	} else {
+		policy = c->sched_policy;
+		sched_param.sched_priority = c->sched_priority;
+
+		res = pthread_setschedparam(pthread_self(),
+					    policy,
+					    &sched_param);
+		if (res) {
+			LOG("error setting scheduler params"
+			    " (got root?): %s\n", strerror(res));
+		}
+	}
+
+	mon_is_ready = true;
+
+	while (c->global->running) {
+		fpgad_mutex_lock(err, &mon_list_lock);
+
+		for (d = monitored_device_list ; d ; d = d->next) {
+			mon_monitor(d);
+		}
+
+		fpgad_mutex_unlock(err, &mon_list_lock);
+
+		usleep(c->global->poll_interval_usec);
+	}
+
+	while (evt_dispatcher_is_ready()) {
+		// Wait for the event dispatcher to complete
+		// before we destroy the monitored devices.
+		usleep(c->global->poll_interval_usec);
+	}
+
+	mon_destroy(c->global);
+	mon_is_ready = false;
+
+	LOG("exiting\n");
+	return NULL;
+}
+
+void mon_monitor_device(fpgad_monitored_device *d)
+{
+	int err;
+	fpgad_monitored_device *trav;
+
+	fpgad_mutex_lock(err, &mon_list_lock);
+
+	d->next = NULL;
+
+	if (!monitored_device_list) {
+		monitored_device_list = d;
+		goto out_unlock;
+	}
+
+	for (trav = monitored_device_list ;
+		trav->next ;
+			trav = trav->next)
+		/* find the end of the list */ ;
+
+	trav->next = d;
+
+out_unlock:
+	fpgad_mutex_unlock(err, &mon_list_lock);
+}
+
+void mon_destroy(struct fpgad_config *c)
+{
+	unsigned i;
+	int err;
+	fpgad_monitored_device *d;
+
+	fpgad_mutex_lock(err, &mon_list_lock);
+
+	for (d = monitored_device_list ; d ; ) {
+		fpgad_monitored_device *trash = d;
+		fpgad_plugin_destroy_t destroy;
+
+		d = d->next;
+
+		if (trash->type == FPGAD_PLUGIN_TYPE_THREAD) {
+
+			if (trash->thread_stop_fn) {
+				trash->thread_stop_fn();
+			} else {
+				LOG("Thread plugin \"%s\" has"
+				    " no thread_stop_fn\n",
+				    trash->supported->library_path);
+				pthread_cancel(trash->thread);
+			}
+
+			pthread_join(trash->thread, NULL);
+		}
+
+		destroy = (fpgad_plugin_destroy_t)
+			dlsym(trash->supported->dl_handle,
+				FPGAD_PLUGIN_DESTROY);
+
+		if (destroy) {
+			destroy(trash);
+		} else {
+			LOG("warning - no destructor for \"%s\"\n",
+				trash->supported->library_path);
+		}
+
+		if (trash->token)
+			fpgaDestroyToken(&trash->token);
+
+		free(trash);
+	}
+	monitored_device_list = NULL;
+
+	if (c->supported_devices) {
+
+		for (i = 0 ; c->supported_devices[i].library_path ; ++i) {
+			fpgad_supported_device *d = &c->supported_devices[i];
+
+			if (d->flags & FPGAD_DEV_LOADED) {
+				dlclose(d->dl_handle);
+			}
+
+			d->flags = 0;
+			d->dl_handle = NULL;
+		}
+
+	}
+
+	fpgad_mutex_unlock(err, &mon_list_lock);
+}
diff --git a/tools/fpgad/monitor_thread.h b/tools/fpgad/monitor_thread.h
new file mode 100644
index 00000000..407e179e
--- /dev/null
+++ b/tools/fpgad/monitor_thread.h
@@ -0,0 +1,50 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_MONITOR_THREAD_H__
+#define __FPGAD_MONITOR_THREAD_H__
+
+#include "fpgad.h"
+#include "monitored_device.h"
+
+typedef struct _monitor_thread_config {
+	struct fpgad_config *global;
+	int sched_policy;
+	int sched_priority;
+} monitor_thread_config;
+
+extern monitor_thread_config monitor_config;
+
+void *monitor_thread(void *);
+
+// 0 on success
+int mon_enumerate(struct fpgad_config *c);
+
+void mon_destroy(struct fpgad_config *c);
+
+void mon_monitor_device(fpgad_monitored_device *d);
+
+#endif /* __FPGAD_MONITOR_THREAD_H__ */
diff --git a/tools/fpgad/monitored_device.c b/tools/fpgad/monitored_device.c
new file mode 100644
index 00000000..9860c8a4
--- /dev/null
+++ b/tools/fpgad/monitored_device.c
@@ -0,0 +1,394 @@
+// 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
+
+#include <stdio.h>
+#include <linux/limits.h>
+#include <dlfcn.h>
+#include <glob.h>
+
+#include <uuid/uuid.h>
+
+#include "monitored_device.h"
+#include "monitor_thread.h"
+#include "api/sysfs.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("monitored_device: " format, ##__VA_ARGS__)
+
+fpgad_supported_device default_supported_devices_table[] = {
+	{ 0x8086, 0xbcc0, "libfpgad-xfpga.so", 0, NULL, "" },
+	{ 0x8086, 0xbcc1, "libfpgad-xfpga.so", 0, NULL, "" },
+	{ 0x8086, 0x0b30,    "libfpgad-vc.so", 0, NULL, "" },
+	{ 0x8086, 0x0b31,    "libfpgad-vc.so", 0, NULL, "" },
+	{      0,      0,                NULL, 0, NULL, "" },
+};
+
+STATIC fpgad_supported_device *mon_is_loaded(struct fpgad_config *c,
+					     const char *library_path)
+{
+	unsigned i;
+	int res = 0;
+
+	for (i = 0 ; c->supported_devices[i].library_path ; ++i) {
+		fpgad_supported_device *d = &c->supported_devices[i];
+
+		res = strcmp(library_path, d->library_path);
+
+		if (!res && (d->flags & FPGAD_DEV_LOADED))
+			return d;
+	}
+	return NULL;
+}
+
+STATIC fpgad_monitored_device *
+allocate_monitored_device(struct fpgad_config *config,
+			  fpgad_supported_device *supported,
+			  fpga_token token,
+			  uint64_t object_id,
+			  fpga_objtype object_type,
+			  opae_bitstream_info *bitstr)
+{
+	fpgad_monitored_device *d;
+
+	d = (fpgad_monitored_device *) calloc(
+			1, sizeof(fpgad_monitored_device));
+
+	if (!d) {
+		LOG("out of memory");
+		return NULL;
+	}
+
+	d->config = config;
+	d->supported = supported;
+	d->token = token;
+	d->object_id = object_id;
+	d->object_type = object_type;
+	d->bitstr = bitstr;
+
+	return d;
+}
+
+STATIC void *mon_find_plugin(const char *libpath)
+{
+	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], libpath);
+
+		dl_handle = dlopen(plugin_path, RTLD_LAZY|RTLD_LOCAL);
+		if (dl_handle)
+			return dl_handle;
+	}
+
+	return NULL;
+}
+
+STATIC bool mon_consider_device(struct fpgad_config *c, fpga_token token)
+{
+	unsigned i;
+	fpga_properties props = NULL;
+	fpga_token parent = NULL;
+	fpga_properties parent_props = NULL;
+	fpga_result res;
+	uint16_t vendor_id;
+	uint16_t device_id;
+	uint64_t object_id;
+	fpga_objtype object_type;
+	opae_bitstream_info *bitstr = NULL;
+	fpga_guid pr_ifc_id;
+	bool added = false;
+
+	res = fpgaGetProperties(token, &props);
+	if (res != FPGA_OK) {
+		LOG("failed to get properties\n");
+		return false;
+	}
+
+	vendor_id = 0;
+	res = fpgaPropertiesGetVendorID(props, &vendor_id);
+	if (res != FPGA_OK) {
+		LOG("failed to get vendor ID\n");
+		goto err_out_destroy;
+	}
+
+	device_id = 0;
+	res = fpgaPropertiesGetDeviceID(props, &device_id);
+	if (res != FPGA_OK) {
+		LOG("failed to get device ID\n");
+		goto err_out_destroy;
+	}
+
+	object_id = 0;
+	res = fpgaPropertiesGetObjectID(props, &object_id);
+	if (res != FPGA_OK) {
+		LOG("failed to get object ID\n");
+		goto err_out_destroy;
+	}
+
+	object_type = FPGA_ACCELERATOR;
+	res = fpgaPropertiesGetObjectType(props, &object_type);
+	if (res != FPGA_OK) {
+		LOG("failed to get object type\n");
+		goto err_out_destroy;
+	}
+
+	// Do we have a NULL GBS from the command line
+	// that matches this device?
+
+	if (object_type == FPGA_DEVICE) {
+		// The token's guid is the PR interface ID.
+
+		res = fpgaPropertiesGetGUID(props, &pr_ifc_id);
+		if (res != FPGA_OK) {
+			LOG("failed to get PR interface ID\n");\
+			goto err_out_destroy;
+		}
+
+		for (i = 0 ; i < c->num_null_gbs ; ++i) {
+			if (!uuid_compare(c->null_gbs[i].pr_interface_id,
+					  pr_ifc_id)) {
+				bitstr = &c->null_gbs[i];
+				break;
+			}
+		}
+	} else {
+		// The parent token's guid is the PR interface ID.
+
+		res = fpgaPropertiesGetParent(props, &parent);
+		if (res != FPGA_OK) {
+			LOG("failed to get parent token\n");
+			goto err_out_destroy;
+		}
+
+		res = fpgaGetProperties(parent, &parent_props);
+		if (res != FPGA_OK) {
+			LOG("failed to get parent properties\n");
+			goto err_out_destroy;
+		}
+
+		res = fpgaPropertiesGetGUID(parent_props, &pr_ifc_id);
+		if (res != FPGA_OK) {
+			LOG("failed to get PR interface ID\n");
+			goto err_out_destroy;
+		}
+
+		fpgaDestroyProperties(&parent_props);
+		fpgaDestroyToken(&parent);
+
+		for (i = 0 ; i < c->num_null_gbs ; ++i) {
+			if (!uuid_compare(c->null_gbs[i].pr_interface_id,
+					  pr_ifc_id)) {
+				bitstr = &c->null_gbs[i];
+				break;
+			}
+		}
+	}
+
+	fpgaDestroyProperties(&props);
+
+	for (i = 0 ; c->supported_devices[i].library_path ; ++i) {
+		fpgad_supported_device *d = &c->supported_devices[i];
+
+		// Do we support this device?
+		if (d->vendor_id == vendor_id &&
+		    d->device_id == device_id) {
+			fpgad_supported_device *loaded_by;
+			fpgad_monitored_device *monitored;
+			fpgad_plugin_configure_t cfg;
+			int res;
+
+			d->flags |= FPGAD_DEV_DETECTED;
+
+			// Is the fpgad plugin already loaded?
+			loaded_by = mon_is_loaded(c, d->library_path);
+
+			if (loaded_by) {
+				// The two table entries will share the
+				// same plugin handle (but only loaded_by
+				// will have FPGAD_DEV_LOADED).
+				d->dl_handle = loaded_by->dl_handle;
+			} else {
+				// Plugin hasn't been loaded.
+				// Load it now.
+				d->dl_handle =
+					mon_find_plugin(d->library_path);
+				if (!d->dl_handle) {
+					char *err = dlerror();
+					LOG("failed to load \"%s\" %s\n",
+							d->library_path,
+							err ? err : "");
+					continue;
+				}
+
+				d->flags |= FPGAD_DEV_LOADED;
+			}
+
+			if (!bitstr) {
+				LOG("Warning: no NULL GBS for vid=0x%04x "
+					"did=0x%04x objid=0x%x (%s)\n",
+				vendor_id,
+				device_id,
+				object_id,
+				object_type == FPGA_ACCELERATOR ?
+				"accelerator" : "device");
+			}
+
+			// Add the device to the monitored list.
+			monitored = allocate_monitored_device(c,
+							      d,
+							      token,
+							      object_id,
+							      object_type,
+							      bitstr);
+			if (!monitored) {
+				LOG("failed to add device 0x%04x:0x%04x\n",
+					vendor_id, device_id);
+				continue;
+			}
+
+			// Success
+			cfg = (fpgad_plugin_configure_t)
+				dlsym(d->dl_handle,
+				      FPGAD_PLUGIN_CONFIGURE);
+			if (!cfg) {
+				LOG("failed to find %s in \"%s\"\n",
+					FPGAD_PLUGIN_CONFIGURE,
+					d->library_path);
+				free(monitored);
+				continue;
+			}
+
+			res = cfg(monitored, d->config);
+			if (res) {
+				LOG("%s for \"%s\" returned %d.\n",
+				    FPGAD_PLUGIN_CONFIGURE,
+				    d->library_path,
+				    res);
+				free(monitored);
+				continue;
+			}
+
+			if (monitored->type == FPGAD_PLUGIN_TYPE_THREAD) {
+
+				if (monitored->thread_fn) {
+
+					if (pthread_create(&monitored->thread,
+							   NULL,
+							   monitored->thread_fn,
+							   monitored)) {
+						LOG("failed to create thread"
+						    " for \"%s\"\n",
+						    d->library_path);
+						free(monitored);
+						continue;
+					}
+
+				} else {
+					LOG("Thread plugin \"%s\" has no "
+					    "thread_fn\n", d->library_path);
+					free(monitored);
+					continue;
+				}
+
+			}
+
+			mon_monitor_device(monitored);
+			added = true;
+			break;
+		}
+	}
+
+	return added;
+
+err_out_destroy:
+	if (props)
+		fpgaDestroyProperties(&props);
+	if (parent)
+		fpgaDestroyToken(&parent);
+	if (parent_props)
+		fpgaDestroyProperties(&parent_props);
+	return false;
+}
+
+int mon_enumerate(struct fpgad_config *c)
+{
+	fpga_token *tokens = NULL;
+	fpga_result res;
+	uint32_t num_matches = 0;
+	uint32_t i;
+	unsigned monitored_devices = 0;
+
+	res = fpgaEnumerate(NULL, 0, NULL, 0, &num_matches);
+	if (res != FPGA_OK) {
+		LOG("enumeration failed\n");
+		return res;
+	}
+
+	if (!num_matches) {
+		res = 1;
+		return res;
+	}
+
+	tokens = calloc(num_matches, sizeof(fpga_token));
+	if (!tokens) {
+		res = 1;
+		LOG("out of memory\n");
+		return res;
+	}
+
+	res = fpgaEnumerate(NULL, 0, tokens, num_matches, &num_matches);
+	if (res != FPGA_OK) {
+		LOG("enumeration failed (2)\n");
+		goto out_exit;
+	}
+
+	for (i = 0 ; i < num_matches ; ++i) {
+		if (!mon_consider_device(c, tokens[i])) {
+			// Not monitoring it. Destroy the token.
+			fpgaDestroyToken(&tokens[i]);
+		} else {
+			++monitored_devices;
+		}
+	}
+
+out_exit:
+	if (tokens)
+		free(tokens);
+	return res + (monitored_devices ? 0 : 1);
+}
diff --git a/tools/fpgad/monitored_device.h b/tools/fpgad/monitored_device.h
new file mode 100644
index 00000000..c2ef50e5
--- /dev/null
+++ b/tools/fpgad/monitored_device.h
@@ -0,0 +1,123 @@
+// Copyright(c) 2018-2019, 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.
+
+#ifndef __FPGAD_MONITORED_DEVICE_H__
+#define __FPGAD_MONITORED_DEVICE_H__
+
+#include "fpgad.h"
+
+typedef struct _fpgad_supported_device {
+	uint16_t vendor_id;
+	uint16_t device_id;
+	const char *library_path;
+	uint32_t flags;
+#define FPGAD_DEV_DETECTED 0x00000001
+#define FPGAD_DEV_LOADED   0x00000002
+	void *dl_handle;
+	const char *config;
+} fpgad_supported_device;
+
+typedef enum _fpgad_plugin_type {
+	FPGAD_PLUGIN_TYPE_CALLBACK = 1,
+	FPGAD_PLUGIN_TYPE_THREAD
+} fpgad_plugin_type;
+
+struct _fpgad_monitored_device;
+
+typedef enum _fpgad_detection_status {
+	FPGAD_STATUS_NOT_DETECTED = 0, // no detection
+	FPGAD_STATUS_DETECTED,         // detected (normal priority)
+	FPGAD_STATUS_DETECTED_HIGH     // detected (high priority)
+} fpgad_detection_status;
+
+typedef fpgad_detection_status
+		(*fpgad_detect_event_t)(struct _fpgad_monitored_device *dev,
+					void *context);
+typedef void (*fpgad_respond_event_t)(struct _fpgad_monitored_device *dev,
+				      void *context);
+
+typedef void * (*fpgad_plugin_thread_t)(void *context);
+typedef void (*fpgad_plugin_thread_stop_t)(void);
+
+typedef struct _fpgad_monitored_device {
+	struct fpgad_config *config;
+	fpgad_supported_device *supported;
+	fpga_token token;
+	uint64_t object_id;
+	fpga_objtype object_type;
+	opae_bitstream_info *bitstr;
+
+	fpgad_plugin_type type;
+
+	// for type FPGAD_PLUGIN_TYPE_CALLBACK {
+
+	// must be NULL-terminated
+	fpgad_detect_event_t *detections;
+	void **detection_contexts;
+
+	fpgad_respond_event_t *responses;
+	void **response_contexts;
+
+	// }
+
+	// for type FPGAD_PLUGIN_TYPE_THREAD {
+
+	fpgad_plugin_thread_t thread_fn;
+
+	// The parameter to thread_fn will be the address
+	// of this fpgad_monitored_device. Use the
+	// following member to pass a thread-specific
+	// context:
+
+	void *thread_context;
+
+	// This routine is called to make the plugin
+	// thread stop execution in preparation for
+	// being joined.
+	fpgad_plugin_thread_stop_t thread_stop_fn;
+
+	pthread_t thread;
+
+	// }
+
+#define MAX_DEV_ERROR_OCCURRENCES 64
+	void *error_occurrences[MAX_DEV_ERROR_OCCURRENCES];
+	unsigned num_error_occurrences;
+
+#define MAX_DEV_SCRATCHPAD 2
+	uint64_t scratchpad[MAX_DEV_SCRATCHPAD];
+
+	struct _fpgad_monitored_device *next;
+} fpgad_monitored_device;
+
+#define FPGAD_PLUGIN_CONFIGURE "fpgad_plugin_configure"
+typedef int (*fpgad_plugin_configure_t)(fpgad_monitored_device *d,
+					const char *cfg);
+
+#define FPGAD_PLUGIN_DESTROY "fpgad_plugin_destroy"
+typedef void (*fpgad_plugin_destroy_t)(fpgad_monitored_device *d);
+
+#endif /* __FPGAD_MONITORED_DEVICE_H__ */
diff --git a/tools/fpgad/plugins/fpgad-vc/CMakeLists.txt b/tools/fpgad/plugins/fpgad-vc/CMakeLists.txt
new file mode 100644
index 00000000..fd43b5bd
--- /dev/null
+++ b/tools/fpgad/plugins/fpgad-vc/CMakeLists.txt
@@ -0,0 +1,41 @@
+## Copyright(c) 2019-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.
+
+opae_add_module_library(TARGET fpgad-vc
+    SOURCE fpgad-vc.c
+    LIBS
+        opae-c
+        fpgad-api
+        ${libjson-c_LIBRARIES}
+    COMPONENT toolfpgad_vc
+)
+
+target_include_directories(fpgad-vc
+    PRIVATE
+        ${OPAE_SDK_SOURCE}/tools
+        ${OPAE_LIBS_ROOT}/libopae-c
+        ${OPAE_LIBS_ROOT}/libbitstream
+)
diff --git a/tools/fpgad/plugins/fpgad-vc/fpgad-vc.c b/tools/fpgad/plugins/fpgad-vc/fpgad-vc.c
new file mode 100644
index 00000000..c05b626d
--- /dev/null
+++ b/tools/fpgad/plugins/fpgad-vc/fpgad-vc.c
@@ -0,0 +1,1085 @@
+// Copyright(c) 2019-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 <glob.h>
+#include <json-c/json.h>
+
+#include "fpgad/api/opae_events_api.h"
+#include "fpgad/api/device_monitoring.h"
+#include "fpgad/api/sysfs.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("fpgad-vc: " format, ##__VA_ARGS__)
+
+#define SYSFS_PATH_MAX 256
+
+#define TRIM_LOG_MODULUS 20
+#define LOG_MOD(__r, __fmt, ...) \
+do { \
+\
+	++(__r); \
+	if (!((__r) % TRIM_LOG_MODULUS)) { \
+		log_printf("fpgad-vc: " __fmt, ##__VA_ARGS__); \
+	} \
+} while (0)
+
+
+typedef struct _vc_sensor {
+	fpga_object sensor_object;
+	char *name;
+	char *type;
+	uint64_t id;
+	fpga_object value_object;
+	uint64_t value;
+	uint64_t high_fatal;
+	uint64_t high_warn;
+	uint64_t low_fatal;
+	uint64_t low_warn;
+	uint32_t flags;
+#define FPGAD_SENSOR_VC_IGNORE           0x00000001
+#define FPGAD_SENSOR_VC_HIGH_FATAL_VALID 0x00000002
+#define FPGAD_SENSOR_VC_HIGH_WARN_VALID  0x00000004
+#define FPGAD_SENSOR_VC_LOW_FATAL_VALID  0x00000008
+#define FPGAD_SENSOR_VC_LOW_WARN_VALID   0x00000010
+	uint32_t read_errors;
+#define FPGAD_SENSOR_VC_MAX_READ_ERRORS  25
+} vc_sensor;
+
+typedef struct _vc_config_sensor {
+	uint64_t id;
+	uint64_t high_fatal;
+	uint64_t high_warn;
+	uint64_t low_fatal;
+	uint64_t low_warn;
+	uint32_t flags;
+} vc_config_sensor;
+
+#define MAX_VC_SENSORS 128
+typedef struct _vc_device {
+	fpgad_monitored_device *base_device;
+	fpga_object group_object;
+	vc_sensor sensors[MAX_VC_SENSORS];
+	uint32_t num_sensors;
+	uint64_t max_sensor_id;
+	uint8_t *state_tripped; // bit set
+	uint8_t *state_last;    // bit set
+	uint64_t tripped_count;
+	uint32_t num_config_sensors;
+	vc_config_sensor *config_sensors;
+	bool aer_disabled;
+	uint32_t previous_ecap_aer[2];
+} vc_device;
+
+#define BIT_SET_MASK(__n)  (1 << ((__n) % 8))
+#define BIT_SET_INDEX(__n) ((__n) / 8)
+
+#define BIT_SET_SET(__s, __n) \
+((__s)[BIT_SET_INDEX(__n)] |= BIT_SET_MASK(__n))
+
+#define BIT_SET_CLR(__s, __n) \
+((__s)[BIT_SET_INDEX(__n)] &= ~BIT_SET_MASK(__n))
+
+#define BIT_IS_SET(__s, __n) \
+((__s)[BIT_SET_INDEX(__n)] & BIT_SET_MASK(__n))
+
+
+#define HIGH_FATAL(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_HIGH_FATAL_VALID) && \
+ ((__sens)->value > (__sens)->high_fatal))
+
+#define HIGH_WARN(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_HIGH_WARN_VALID) && \
+ ((__sens)->value > (__sens)->high_warn))
+
+#define HIGH_NORMAL(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_HIGH_WARN_VALID) && \
+ ((__sens)->value <= (__sens)->high_warn))
+
+#define LOW_FATAL(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_LOW_FATAL_VALID) && \
+ ((__sens)->value < (__sens)->low_fatal))
+
+#define LOW_WARN(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_LOW_WARN_VALID) && \
+ ((__sens)->value < (__sens)->low_warn))
+
+#define LOW_NORMAL(__sens) \
+(((__sens)->flags & FPGAD_SENSOR_VC_LOW_WARN_VALID) && \
+ ((__sens)->value >= (__sens)->low_warn))
+
+STATIC bool vc_threads_running = true;
+
+STATIC void stop_vc_threads(void)
+{
+	vc_threads_running = false;
+}
+
+STATIC void vc_destroy_sensor(vc_sensor *sensor)
+{
+	fpgaDestroyObject(&sensor->sensor_object);
+	if (sensor->name) {
+		free(sensor->name);
+		sensor->name = NULL;
+	}
+	if (sensor->type) {
+		free(sensor->type);
+		sensor->type = NULL;
+	}
+	if (sensor->value_object) {
+		fpgaDestroyObject(&sensor->value_object);
+		sensor->value_object = NULL;
+	}
+	sensor->flags = 0;
+	sensor->read_errors = 0;
+}
+
+STATIC void vc_destroy_sensors(vc_device *vc)
+{
+	uint32_t i;
+
+	for (i = 0 ; i < vc->num_sensors ; ++i) {
+		vc_destroy_sensor(&vc->sensors[i]);
+	}
+	vc->num_sensors = 0;
+	vc->max_sensor_id = 0;
+
+	if (vc->group_object) {
+		fpgaDestroyObject(&vc->group_object);
+		vc->group_object = NULL;
+	}
+
+	if (vc->state_tripped) {
+		free(vc->state_tripped);
+		vc->state_tripped = NULL;
+	}
+
+	if (vc->state_last) {
+		free(vc->state_last);
+		vc->state_last = NULL;
+	}
+}
+
+STATIC void vc_destroy_device(vc_device *vc)
+{
+	vc_destroy_sensors(vc);
+	if (vc->config_sensors) {
+		free(vc->config_sensors);
+		vc->config_sensors = NULL;
+		vc->num_config_sensors = 0;
+	}
+}
+
+STATIC fpga_result vc_sensor_get_string(vc_sensor *sensor,
+					const char *obj_name,
+					char **name)
+{
+	fpga_result res;
+	fpga_object obj = NULL;
+	char buf[SYSFS_PATH_MAX] = { 0, };
+	uint32_t len = 0;
+
+	res = fpgaObjectGetObject(sensor->sensor_object,
+				  obj_name,
+				  &obj,
+				  0);
+	if (res != FPGA_OK)
+		return res;
+
+	res = fpgaObjectGetSize(obj, &len, 0);
+	if (res != FPGA_OK)
+		goto out_free_obj;
+
+	res = fpgaObjectRead(obj, (uint8_t *)buf, 0, len, 0);
+	if (res != FPGA_OK)
+		goto out_free_obj;
+
+	if (buf[len-1] == '\n')
+		buf[len-1] = '\0';
+
+	*name = cstr_dup((const char *)buf);
+	if (!(*name))
+		res = FPGA_NO_MEMORY;
+
+out_free_obj:
+	fpgaDestroyObject(&obj);
+	return res;
+}
+
+STATIC fpga_result vc_sensor_get_u64(vc_sensor *sensor,
+				     const char *obj_name,
+				     uint64_t *value)
+{
+	fpga_result res;
+	fpga_object obj = NULL;
+
+	res = fpgaObjectGetObject(sensor->sensor_object,
+				  obj_name,
+				  &obj,
+				  0);
+	if (res != FPGA_OK)
+		return res;
+
+	res = fpgaObjectRead64(obj, value, 0);
+
+	fpgaDestroyObject(&obj);
+
+	return res;
+}
+
+// The percentage by which we adjust the power trip
+// points so that we catch anomolies before the hw does.
+#define VC_PERCENT_ADJUST_PWR 5
+// The number of degrees by which we adjust the
+// temperature trip points so that we catch anomolies
+// before the hw does.
+#define VC_DEGREES_ADJUST_TEMP 5
+STATIC fpga_result vc_sensor_get(vc_device *vc, vc_sensor *s)
+{
+	fpga_result res;
+	bool is_temperature;
+	int indicator = -1;
+	vc_config_sensor *cfg_sensor = NULL;
+	uint32_t i;
+
+	if (s->name) {
+		free(s->name);
+		s->name = NULL;
+	}
+
+	if (s->type) {
+		free(s->type);
+		s->type = NULL;
+	}
+
+	res = vc_sensor_get_string(s, "name", &s->name);
+	if (res != FPGA_OK)
+		return res;
+
+	res = vc_sensor_get_string(s, "type", &s->type);
+	if (res != FPGA_OK)
+		return res;
+
+	res = vc_sensor_get_u64(s, "id", &s->id);
+	if (res != FPGA_OK)
+		return res;
+
+	res = fpgaObjectRead64(s->value_object, &s->value, 0);
+	if (res != FPGA_OK)
+		return res;
+
+	indicator = strcmp(s->type, "Temperature");
+	is_temperature = (indicator == 0);
+
+	res = vc_sensor_get_u64(s, "high_fatal", &s->high_fatal);
+	if (res == FPGA_OK) {
+		s->flags |= FPGAD_SENSOR_VC_HIGH_FATAL_VALID;
+		if (is_temperature)
+			s->high_fatal -= VC_DEGREES_ADJUST_TEMP;
+		else
+			s->high_fatal -=
+				(s->high_fatal * VC_PERCENT_ADJUST_PWR) / 100;
+	} else
+		s->flags &= ~FPGAD_SENSOR_VC_HIGH_FATAL_VALID;
+
+	res = vc_sensor_get_u64(s, "high_warn", &s->high_warn);
+	if (res == FPGA_OK)
+		s->flags |= FPGAD_SENSOR_VC_HIGH_WARN_VALID;
+	else
+		s->flags &= ~FPGAD_SENSOR_VC_HIGH_WARN_VALID;
+
+	res = vc_sensor_get_u64(s, "low_fatal", &s->low_fatal);
+	if (res == FPGA_OK) {
+		s->flags |= FPGAD_SENSOR_VC_LOW_FATAL_VALID;
+		if (is_temperature)
+			s->low_fatal += VC_DEGREES_ADJUST_TEMP;
+		else
+			s->low_fatal +=
+				(s->low_fatal * VC_PERCENT_ADJUST_PWR) / 100;
+	} else
+		s->flags &= ~FPGAD_SENSOR_VC_LOW_FATAL_VALID;
+
+	res = vc_sensor_get_u64(s, "low_warn", &s->low_warn);
+	if (res == FPGA_OK)
+		s->flags |= FPGAD_SENSOR_VC_LOW_WARN_VALID;
+	else
+		s->flags &= ~FPGAD_SENSOR_VC_LOW_WARN_VALID;
+
+	/* Do we have a user override (via the config) for
+	 * this sensor? If so, then honor it.
+	 */
+	for (i = 0 ; i < vc->num_config_sensors ; ++i) {
+		if (vc->config_sensors[i].flags &
+		    FPGAD_SENSOR_VC_IGNORE)
+			continue;
+		if (vc->config_sensors[i].id == s->id) {
+			cfg_sensor = &vc->config_sensors[i];
+			break;
+		}
+	}
+
+	if (cfg_sensor) {
+
+		if (cfg_sensor->flags & FPGAD_SENSOR_VC_HIGH_FATAL_VALID) {
+			// Cap the sensor at the adjusted max
+			// allowed by the hardware.
+			if ((s->flags & FPGAD_SENSOR_VC_HIGH_FATAL_VALID) &&
+			    (cfg_sensor->high_fatal > s->high_fatal))
+				/* nothing */ ;
+			else
+				s->high_fatal = cfg_sensor->high_fatal;
+
+			s->flags |= FPGAD_SENSOR_VC_HIGH_FATAL_VALID;
+		} else
+			s->flags &= ~FPGAD_SENSOR_VC_HIGH_FATAL_VALID;
+
+		if (cfg_sensor->flags & FPGAD_SENSOR_VC_HIGH_WARN_VALID) {
+
+			if ((s->flags & FPGAD_SENSOR_VC_HIGH_WARN_VALID) &&
+			    (cfg_sensor->high_warn > s->high_warn))
+				/* nothing */ ;
+			else
+				s->high_warn = cfg_sensor->high_warn;
+
+			s->flags |= FPGAD_SENSOR_VC_HIGH_WARN_VALID;
+		} else
+			s->flags &= ~FPGAD_SENSOR_VC_HIGH_WARN_VALID;
+
+		if (cfg_sensor->flags & FPGAD_SENSOR_VC_LOW_FATAL_VALID) {
+
+			if ((s->flags & FPGAD_SENSOR_VC_LOW_FATAL_VALID) &&
+			    (cfg_sensor->low_fatal < s->low_fatal))
+				/* nothing */ ;
+			else
+				s->low_fatal = cfg_sensor->low_fatal;
+
+			s->flags |= FPGAD_SENSOR_VC_LOW_FATAL_VALID;
+		} else
+			s->flags &= ~FPGAD_SENSOR_VC_LOW_FATAL_VALID;
+
+		if (cfg_sensor->flags & FPGAD_SENSOR_VC_LOW_WARN_VALID) {
+
+			if ((s->flags & FPGAD_SENSOR_VC_LOW_WARN_VALID) &&
+			    (cfg_sensor->low_warn < s->low_warn))
+				/* nothing */ ;
+			else
+				s->low_warn = cfg_sensor->low_warn;
+
+			s->flags |= FPGAD_SENSOR_VC_LOW_WARN_VALID;
+		} else
+			s->flags &= ~FPGAD_SENSOR_VC_LOW_WARN_VALID;
+
+	}
+
+	return FPGA_OK;
+}
+
+STATIC fpga_result vc_enum_sensor(vc_device *vc,
+				  const char *name)
+{
+	fpga_result res;
+	vc_sensor *s;
+
+	if (vc->num_sensors == MAX_VC_SENSORS) {
+		LOG("exceeded max number of sensors.\n");
+		return FPGA_EXCEPTION;
+	}
+
+	s = &vc->sensors[vc->num_sensors];
+
+	res = fpgaObjectGetObject(vc->group_object,
+				  name,
+				  &s->sensor_object,
+				  0);
+
+	if (res != FPGA_OK)
+		return res;
+
+	res = fpgaObjectGetObject(s->sensor_object,
+				  "value",
+				  &s->value_object,
+				  0);
+
+	if (res != FPGA_OK) {
+		LOG("failed to get value object for %s.\n", name);
+		fpgaDestroyObject(&s->sensor_object);
+		return res;
+	}
+
+	res = vc_sensor_get(vc, s);
+	if (res == FPGA_OK)
+		++vc->num_sensors;
+	else {
+		LOG("warning: sensor attribute enumeration failed.\n");
+		vc_destroy_sensor(s);
+	}
+
+	return res;
+}
+
+STATIC fpga_result vc_enum_sensors(vc_device *vc)
+{
+	fpga_result res;
+	char name[SYSFS_PATH_MAX];
+	int i;
+
+	res = fpgaTokenGetObject(vc->base_device->token,
+				 "spi-altera.*.auto/spi_master/spi*/spi*.*",
+				 &vc->group_object,
+				 FPGA_OBJECT_GLOB);
+	if (res)
+		return res;
+
+	for (i = 0 ; i < MAX_VC_SENSORS ; ++i) {
+		snprintf(name, sizeof(name), "sensor%d", i);
+		vc_enum_sensor(vc, name);
+	}
+
+	if (vc->num_sensors > 0) {
+		vc_sensor *s = &vc->sensors[vc->num_sensors - 1];
+
+		vc->max_sensor_id = s->id;
+
+		vc->state_tripped = calloc((vc->max_sensor_id + 7) / 8, 1);
+		vc->state_last = calloc((vc->max_sensor_id + 7) / 8, 1);
+
+		return (vc->state_tripped && vc->state_last) ?
+			FPGA_OK : FPGA_NO_MEMORY;
+	}
+
+	return FPGA_NOT_FOUND;
+}
+
+STATIC fpga_result vc_disable_aer(vc_device *vc)
+{
+	fpga_token token;
+	fpga_result res;
+	fpga_properties prop = NULL;
+	char path[PATH_MAX];
+	char rlpath[PATH_MAX];
+	char *p;
+	char cmd[256];
+	char output[256];
+	FILE *fp;
+	size_t sz;
+
+	uint16_t seg = 0;
+	uint8_t bus = 0;
+	uint8_t dev = 0;
+	uint8_t fn = 0;
+
+	token = vc->base_device->token;
+
+	res = fpgaGetProperties(token, &prop);
+	if (res != FPGA_OK) {
+		LOG("failed to get fpga properties.\n");
+		return res;
+	}
+
+	if ((fpgaPropertiesGetSegment(prop, &seg) != FPGA_OK) ||
+	    (fpgaPropertiesGetBus(prop, &bus) != FPGA_OK) ||
+	    (fpgaPropertiesGetDevice(prop, &dev) != FPGA_OK) ||
+	    (fpgaPropertiesGetFunction(prop, &fn) != FPGA_OK)) {
+		LOG("failed to get PCI attributes.\n");
+		fpgaDestroyProperties(&prop);
+		return FPGA_EXCEPTION;
+	}
+
+	fpgaDestroyProperties(&prop);
+
+	snprintf(path, sizeof(path),
+			"/sys/bus/pci/devices/%04x:%02x:%02x.%d",
+			(int)seg, (int)bus, (int)dev, (int)fn);
+
+	memset(rlpath, 0, sizeof(rlpath));
+
+	if (readlink(path, rlpath, sizeof(rlpath)) < 0) {
+		LOG("readlink \"%s\" failed.\n", path);
+		return FPGA_EXCEPTION;
+	}
+
+	// (rlpath)
+	//                    1111111111
+	//          01234567890123456789
+	// ../../../devices/pci0000:ae/0000:ae:00.0/0000:af:00.0/
+	// 0000:b0:09.0/0000:b2:00.0
+
+	p = strstr(rlpath, "devices/pci");
+
+	p += 19;
+	*(p + 12) = '\0';
+
+	// Save the current ECAP_AER values.
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x08.L", p);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to read ECAP_AER+0x08 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	sz = fread(output, 1, sizeof(output), fp);
+
+	if (sz >= sizeof(output))
+		sz = sizeof(output) - 1;
+	output[sz] = '\0';
+
+	pclose(fp);
+
+	vc->previous_ecap_aer[0] = strtoul(output, NULL, 16);
+
+	LOG("saving previous ECAP_AER+0x08 value 0x%08x for %s\n",
+	    vc->previous_ecap_aer[0], p);
+
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x14.L", p);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to read ECAP_AER+0x14 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	sz = fread(output, 1, sizeof(output), fp);
+
+	if (sz >= sizeof(output))
+		sz = sizeof(output) - 1;
+	output[sz] = '\0';
+
+	pclose(fp);
+
+	vc->previous_ecap_aer[1] = strtoul(output, NULL, 16);
+
+	LOG("saving previous ECAP_AER+0x14 value 0x%08x for %s\n",
+	    vc->previous_ecap_aer[1], p);
+
+
+	// Disable AER.
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x08.L=0xffffffff", p);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to write ECAP_AER+0x08 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	pclose(fp);
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x14.L=0xffffffff", p);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to write ECAP_AER+0x14 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	pclose(fp);
+
+	return FPGA_OK;
+}
+
+STATIC fpga_result vc_enable_aer(vc_device *vc)
+{
+	fpga_token token;
+	fpga_result res;
+	fpga_properties prop = NULL;
+	char path[PATH_MAX];
+	char rlpath[PATH_MAX];
+	char *p;
+	char cmd[256];
+	FILE *fp;
+
+	uint16_t seg = 0;
+	uint8_t bus = 0;
+	uint8_t dev = 0;
+	uint8_t fn = 0;
+
+	token = vc->base_device->token;
+
+	res = fpgaGetProperties(token, &prop);
+	if (res != FPGA_OK) {
+		LOG("failed to get fpga properties.\n");
+		return res;
+	}
+
+	if ((fpgaPropertiesGetSegment(prop, &seg) != FPGA_OK) ||
+	    (fpgaPropertiesGetBus(prop, &bus) != FPGA_OK) ||
+	    (fpgaPropertiesGetDevice(prop, &dev) != FPGA_OK) ||
+	    (fpgaPropertiesGetFunction(prop, &fn) != FPGA_OK)) {
+		LOG("failed to get PCI attributes.\n");
+		fpgaDestroyProperties(&prop);
+		return FPGA_EXCEPTION;
+	}
+
+	fpgaDestroyProperties(&prop);
+
+	snprintf(path, sizeof(path),
+			"/sys/bus/pci/devices/%04x:%02x:%02x.%d",
+			(int)seg, (int)bus, (int)dev, (int)fn);
+
+	memset(rlpath, 0, sizeof(rlpath));
+
+	if (readlink(path, rlpath, sizeof(rlpath)) < 0) {
+		LOG("readlink \"%s\" failed.\n", path);
+		return FPGA_EXCEPTION;
+	}
+
+	// (rlpath)
+	//                    1111111111
+	//          01234567890123456789
+	// ../../../devices/pci0000:ae/0000:ae:00.0/0000:af:00.0/
+	// 0000:b0:09.0/0000:b2:00.0
+
+	p = strstr(rlpath, "devices/pci");
+
+	p += 19;
+	*(p + 12) = '\0';
+
+	// Write the saved ECAP_AER values to enable AER.
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x08.L=0x%08x",
+		      p, vc->previous_ecap_aer[0]);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to write ECAP_AER+0x08 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	pclose(fp);
+
+	LOG("restored previous ECAP_AER+0x08 value 0x%08x for %s\n",
+	    vc->previous_ecap_aer[0], p);
+
+
+	snprintf(cmd, sizeof(cmd),
+		      "setpci -s %s ECAP_AER+0x14.L=0x%08x",
+		      p, vc->previous_ecap_aer[1]);
+
+	fp = popen(cmd, "r");
+	if (!fp) {
+		LOG("failed to write ECAP_AER+0x14 for %s\n", p);
+		return FPGA_EXCEPTION;
+	}
+
+	pclose(fp);
+
+	LOG("restored previous ECAP_AER+0x14 value 0x%08x for %s\n",
+	    vc->previous_ecap_aer[1], p);
+
+	return FPGA_OK;
+}
+
+STATIC bool vc_monitor_sensors(vc_device *vc)
+{
+	uint32_t i;
+	uint32_t monitoring = 0;
+	bool negative_trans = false;
+	bool res = true;
+
+	for (i = 0 ; i < vc->num_sensors ; ++i) {
+		vc_sensor *s = &vc->sensors[i];
+
+		if (s->flags & FPGAD_SENSOR_VC_IGNORE)
+			continue;
+
+		if (s->flags & (FPGAD_SENSOR_VC_HIGH_WARN_VALID|
+				FPGAD_SENSOR_VC_LOW_WARN_VALID))
+			++monitoring;
+
+		if (fpgaObjectRead64(s->value_object,
+				     &s->value,
+				     FPGA_OBJECT_SYNC) != FPGA_OK) {
+			if (++s->read_errors >=
+				FPGAD_SENSOR_VC_MAX_READ_ERRORS)
+				s->flags |= FPGAD_SENSOR_VC_IGNORE;
+			continue;
+		}
+
+		if (HIGH_WARN(s) || LOW_WARN(s)) {
+			opae_api_send_EVENT_POWER_THERMAL(vc->base_device);
+			BIT_SET_SET(vc->state_tripped, s->id);
+			if (!BIT_IS_SET(vc->state_last, s->id)) {
+				LOG("sensor '%s' warning.\n", s->name);
+				if (!vc->aer_disabled) {
+					if (FPGA_OK == vc_disable_aer(vc))
+						vc->aer_disabled = true;
+				}
+			}
+		}
+
+		if (HIGH_NORMAL(s) || LOW_NORMAL(s)) {
+			if (BIT_IS_SET(vc->state_last, s->id)) {
+				negative_trans = true;
+				LOG("sensor '%s' back to normal.\n", s->name);
+			}
+		}
+
+		if (BIT_IS_SET(vc->state_last, s->id) &&
+		    BIT_IS_SET(vc->state_tripped, s->id)) {
+			LOG_MOD(vc->tripped_count,
+				"sensor '%s' still tripped.\n", s->name);
+		}
+	}
+
+	if (negative_trans) {
+		for (i = 0 ; i < vc->num_sensors ; ++i) {
+			if (BIT_IS_SET(vc->state_tripped, vc->sensors[i].id))
+				break;
+		}
+
+		if (i == vc->num_sensors) {
+			// no remaining tripped sensors
+			vc->tripped_count = 0;
+			if (vc->aer_disabled) {
+				if (FPGA_OK == vc_enable_aer(vc))
+					vc->aer_disabled = false;
+			}
+		}
+	}
+
+	/*
+	** Are we still monitoring any sensor that has a valid
+	** high/low warn threshold? If not, then this fn should
+	** return false so that we wait for sensor enumeration
+	** in the caller. It's possible that we're ignoring all
+	** of the sensors because they became unavailable due to
+	** driver removal, eg.
+	*/
+	if (!monitoring)
+		res = false;
+
+	for (i = 0 ; i < vc->num_sensors ; ++i) {
+		vc_sensor *s = &vc->sensors[i];
+		if (BIT_IS_SET(vc->state_tripped, s->id))
+			BIT_SET_SET(vc->state_last, s->id);
+		else
+			BIT_SET_CLR(vc->state_last, s->id);
+	}
+
+	memset(vc->state_tripped, 0, (vc->max_sensor_id + 7) / 8);
+
+	return res;
+}
+
+STATIC int cool_down = 30;
+
+STATIC void *monitor_fme_vc_thread(void *arg)
+{
+	fpgad_monitored_device *d =
+		(fpgad_monitored_device *)arg;
+
+	vc_device *vc = (vc_device *)d->thread_context;
+
+	uint32_t enum_retries = 0;
+	uint8_t *save_state_last = NULL;
+
+	while (vc_threads_running) {
+
+		while (vc_enum_sensors(vc) != FPGA_OK) {
+			LOG_MOD(enum_retries, "waiting to enumerate sensors.\n");
+			if (!vc_threads_running)
+				return NULL;
+			sleep(1);
+		}
+
+		if (save_state_last) {
+			free(vc->state_last);
+			vc->state_last = save_state_last;
+			save_state_last = NULL;
+		}
+
+		while (vc_monitor_sensors(vc)) {
+			usleep(d->config->poll_interval_usec);
+
+			if (!vc_threads_running) {
+				vc_destroy_sensors(vc);
+				return NULL;
+			}
+		}
+
+		save_state_last = vc->state_last;
+		vc->state_last = NULL;
+
+		vc_destroy_sensors(vc);
+
+	}
+
+	return NULL;
+}
+
+STATIC int vc_parse_config(vc_device *vc, const char *cfg)
+{
+	json_object *root;
+	enum json_tokener_error j_err = json_tokener_success;
+	json_object *j_cool_down = NULL;
+	json_object *j_config_sensors_enabled = NULL;
+	json_object *j_sensors = NULL;
+	int res = 1;
+	int sensor_entries;
+	int i;
+
+	root = json_tokener_parse_verbose(cfg, &j_err);
+	if (!root) {
+		LOG("error parsing config: %s\n",
+		    json_tokener_error_desc(j_err));
+		return 1;
+	}
+
+	if (!json_object_object_get_ex(root,
+				       "cool-down",
+				       &j_cool_down)) {
+		LOG("failed to find cool-down key in config.\n");
+		goto out_put;
+	}
+
+	if (!json_object_is_type(j_cool_down, json_type_int)) {
+		LOG("cool-down key not integer.\n");
+		goto out_put;
+	}
+
+	cool_down = json_object_get_int(j_cool_down);
+	if (cool_down < 0)
+		cool_down = 30;
+
+	LOG("set cool-down period to %d seconds.\n", cool_down);
+
+	res = 0;
+
+	if (!json_object_object_get_ex(root,
+				       "config-sensors-enabled",
+				       &j_config_sensors_enabled)) {
+		LOG("failed to find config-sensors-enabled key in config.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	if (!json_object_is_type(j_config_sensors_enabled, json_type_boolean)) {
+		LOG("config-sensors-enabled key not Boolean.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	if (!json_object_get_boolean(j_config_sensors_enabled)) {
+		LOG("config-sensors-enabled key set to false.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	if (!json_object_object_get_ex(root,
+				       "sensors",
+				       &j_sensors)) {
+		LOG("failed to find sensors key in config.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	if (!json_object_is_type(j_sensors, json_type_array)) {
+		LOG("sensors key not array.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	sensor_entries = json_object_array_length(j_sensors);
+	if (!sensor_entries) {
+		LOG("sensors key is empty array.\n"
+		    "Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	vc->config_sensors = calloc(sensor_entries, sizeof(vc_config_sensor));
+	if (!vc->config_sensors) {
+		LOG("calloc failed. Skipping user sensor config.\n");
+		goto out_put;
+	}
+
+	vc->num_config_sensors = (uint32_t)sensor_entries;
+
+	for (i = 0 ; i < sensor_entries ; ++i) {
+		json_object *j_sensor_sub_i = json_object_array_get_idx(j_sensors, i);
+		json_object *j_id;
+		json_object *j_high_fatal;
+		json_object *j_high_warn;
+		json_object *j_low_fatal;
+		json_object *j_low_warn;
+
+		if (!json_object_object_get_ex(j_sensor_sub_i,
+					       "id",
+					       &j_id)) {
+			LOG("failed to find id key in config sensors[%d].\n"
+			    "Skipping entry %d.\n", i, i);
+			vc->config_sensors[i].id = MAX_VC_SENSORS;
+			vc->config_sensors[i].flags = FPGAD_SENSOR_VC_IGNORE;
+			continue;
+		}
+
+		if (!json_object_is_type(j_id, json_type_int)) {
+			LOG("sensors[%d].id key not int.\n"
+			    "Skipping entry %d.\n", i, i);
+			vc->config_sensors[i].id = MAX_VC_SENSORS;
+			vc->config_sensors[i].flags = FPGAD_SENSOR_VC_IGNORE;
+			continue;
+		}
+
+		vc->config_sensors[i].id = json_object_get_int(j_id);
+
+		if (json_object_object_get_ex(j_sensor_sub_i,
+					      "high-fatal",
+					      &j_high_fatal)) {
+			if (json_object_is_type(j_high_fatal,
+						json_type_double)) {
+				vc->config_sensors[i].high_fatal =
+				(uint64_t)(json_object_get_double(j_high_fatal)
+					* 1000.0);
+				vc->config_sensors[i].flags |=
+					FPGAD_SENSOR_VC_HIGH_FATAL_VALID;
+				LOG("user sensor%u high-fatal: %f\n",
+				    vc->config_sensors[i].id,
+				    json_object_get_double(j_high_fatal));
+			}
+		}
+
+		if (json_object_object_get_ex(j_sensor_sub_i,
+					      "high-warn",
+					      &j_high_warn)) {
+			if (json_object_is_type(j_high_warn,
+						json_type_double)) {
+				vc->config_sensors[i].high_warn =
+				(uint64_t)(json_object_get_double(j_high_warn)
+					* 1000.0);
+				vc->config_sensors[i].flags |=
+					FPGAD_SENSOR_VC_HIGH_WARN_VALID;
+				LOG("user sensor%u high-warn: %f\n",
+				    vc->config_sensors[i].id,
+				    json_object_get_double(j_high_warn));
+			}
+		}
+
+		if (json_object_object_get_ex(j_sensor_sub_i,
+					      "low-fatal",
+					      &j_low_fatal)) {
+			if (json_object_is_type(j_low_fatal,
+						json_type_double)) {
+				vc->config_sensors[i].low_fatal =
+				(uint64_t)(json_object_get_double(j_low_fatal)
+					* 1000.0);
+				vc->config_sensors[i].flags |=
+					FPGAD_SENSOR_VC_LOW_FATAL_VALID;
+				LOG("user sensor%u low-fatal: %f\n",
+				    vc->config_sensors[i].id,
+				    json_object_get_double(j_low_fatal));
+			}
+		}
+
+		if (json_object_object_get_ex(j_sensor_sub_i,
+					      "low-warn",
+					      &j_low_warn)) {
+			if (json_object_is_type(j_low_warn,
+						json_type_double)) {
+				vc->config_sensors[i].low_warn =
+				(uint64_t)(json_object_get_double(j_low_warn)
+					* 1000.0);
+				vc->config_sensors[i].flags |=
+					FPGAD_SENSOR_VC_LOW_WARN_VALID;
+				LOG("user sensor%u low-warn: %f\n",
+				    vc->config_sensors[i].id,
+				    json_object_get_double(j_low_warn));
+			}
+		}
+	}
+
+out_put:
+	json_object_put(root);
+	return res;
+}
+
+int fpgad_plugin_configure(fpgad_monitored_device *d,
+			   const char *cfg)
+{
+	int res = 1;
+	vc_device *vc;
+
+	vc_threads_running = true;
+
+	d->type = FPGAD_PLUGIN_TYPE_THREAD;
+
+	if (d->object_type == FPGA_DEVICE) {
+
+		d->thread_fn = monitor_fme_vc_thread;
+		d->thread_stop_fn = stop_vc_threads;
+
+		vc = calloc(1, sizeof(vc_device));
+		if (!vc)
+			return res;
+
+		vc->base_device = d;
+		d->thread_context = vc;
+
+		LOG("monitoring vid=0x%04x did=0x%04x (%s)\n",
+			d->supported->vendor_id,
+			d->supported->device_id,
+			d->object_type == FPGA_ACCELERATOR ?
+			"accelerator" : "device");
+
+		res = vc_parse_config(vc, cfg);
+		if (res) {
+			free(vc);
+		}
+
+	}
+
+	// Not currently monitoring the Port device
+
+	return res;
+}
+
+void fpgad_plugin_destroy(fpgad_monitored_device *d)
+{
+	LOG("stop monitoring vid=0x%04x did=0x%04x (%s)\n",
+			d->supported->vendor_id,
+			d->supported->device_id,
+			d->object_type == FPGA_ACCELERATOR ?
+			"accelerator" : "device");
+
+	if (d->thread_context) {
+		vc_destroy_device((vc_device *)d->thread_context);
+		free(d->thread_context);
+		d->thread_context = NULL;
+	}
+}
diff --git a/tools/fpgad/plugins/fpgad-xfpga/CMakeLists.txt b/tools/fpgad/plugins/fpgad-xfpga/CMakeLists.txt
new file mode 100644
index 00000000..fd8c13d0
--- /dev/null
+++ b/tools/fpgad/plugins/fpgad-xfpga/CMakeLists.txt
@@ -0,0 +1,41 @@
+## 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.
+
+opae_add_module_library(TARGET fpgad-xfpga
+    SOURCE fpgad-xfpga.c
+    LIBS
+        opae-c
+        fpgad-api
+        ${libjson-c_LIBRARIES}
+    COMPONENT toolfpgad_xfpga
+)
+
+target_include_directories(fpgad-xfpga
+    PRIVATE
+        ${OPAE_SDK_SOURCE}/tools
+        ${OPAE_LIBS_ROOT}/libopae-c
+        ${OPAE_LIBS_ROOT}/libbitstream
+)
diff --git a/tools/fpgad/plugins/fpgad-xfpga/fpgad-xfpga.c b/tools/fpgad/plugins/fpgad-xfpga/fpgad-xfpga.c
new file mode 100644
index 00000000..fa5846bb
--- /dev/null
+++ b/tools/fpgad/plugins/fpgad-xfpga/fpgad-xfpga.c
@@ -0,0 +1,992 @@
+// Copyright(c) 2018-2019, 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 "fpgad/api/opae_events_api.h"
+#include "fpgad/api/device_monitoring.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(format, ...) \
+log_printf("fpgad-xfpga: " format, ##__VA_ARGS__)
+
+enum fpga_power_state {
+	FPGAD_NORMAL_PWR = 0,
+	FPGAD_AP1_STATE,
+	FPGAD_AP2_STATE,
+	FPGAD_AP6_STATE
+};
+
+typedef struct _fpgad_xfpga_AP_context {
+	const char *sysfs_file;
+	const char *message;
+	int low_bit;
+	int high_bit;
+} fpgad_xfpga_AP_context;
+
+fpgad_xfpga_AP_context fpgad_xfpga_AP_contexts[] = {
+	{ "ap1_event",   "AP1 Triggered!",         0, 0 },
+	{ "ap2_event",   "AP2 Triggered!",         0, 0 },
+	{ "power_state", "Power state changed to", 0, 1 },
+};
+
+fpgad_detection_status
+fpgad_xfpga_detect_AP1_or_AP2(fpgad_monitored_device *d,
+			      void *context)
+{
+	fpgad_xfpga_AP_context *c =
+		(fpgad_xfpga_AP_context *)context;
+	fpga_object obj = NULL;
+	fpga_result res;
+	uint64_t err = 0;
+	uint64_t mask;
+	uint64_t value;
+	int i;
+	bool detected = false;
+
+	res = fpgaTokenGetObject(d->token, c->sysfs_file,
+				 &obj, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to get error object\n");
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	res = fpgaObjectRead64(obj, &err, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to read error object\n");
+		fpgaDestroyObject(&obj);
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	fpgaDestroyObject(&obj);
+
+	mask = 0;
+	for (i = c->low_bit ; i <= c->high_bit ; ++i)
+		mask |= 1ULL << i;
+
+	value = mask & err;
+
+	if (value != 0 && !mon_has_error_occurred(d, context)) {
+		detected = mon_add_device_error(d, context);
+	}
+
+	if (value == 0 && mon_has_error_occurred(d, context)) {
+		mon_remove_device_error(d, context);
+	}
+
+	return detected ? FPGAD_STATUS_DETECTED : FPGAD_STATUS_NOT_DETECTED;
+}
+
+void fpgad_xfpga_respond_AP1_or_AP2(fpgad_monitored_device *d,
+				    void *context)
+{
+	fpgad_xfpga_AP_context *c =
+		(fpgad_xfpga_AP_context *)context;
+
+	LOG("%s\n", c->message);
+
+	// Signal OPAE events API
+	opae_api_send_EVENT_POWER_THERMAL(d);
+}
+
+fpgad_detection_status
+fpgad_xfpga_detect_PowerStateChange(fpgad_monitored_device *d,
+				    void *context)
+{
+	fpgad_xfpga_AP_context *c =
+		(fpgad_xfpga_AP_context *)context;
+	fpga_object obj = NULL;
+	fpga_result res;
+	uint64_t err = 0;
+	uint64_t mask;
+	uint64_t value;
+	int i;
+	bool detected = false;
+
+	res = fpgaTokenGetObject(d->token, c->sysfs_file,
+				 &obj, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to get error object\n");
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	res = fpgaObjectRead64(obj, &err, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to read error object\n");
+		fpgaDestroyObject(&obj);
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	fpgaDestroyObject(&obj);
+
+	mask = 0;
+	for (i = c->low_bit ; i <= c->high_bit ; ++i)
+		mask |= 1ULL << i;
+
+	value = mask & err;
+
+	if (value != d->scratchpad[0]) {
+		detected = true;
+	}
+
+	d->scratchpad[0] = value;
+
+	return detected ? FPGAD_STATUS_DETECTED : FPGAD_STATUS_NOT_DETECTED;
+}
+
+void fpgad_xfpga_respond_PowerStateChange(fpgad_monitored_device *d,
+					  void *context)
+{
+	const char *power_states[] = {
+		"Normal Power",
+		"AP1 Power State",
+		"AP2 Power State",
+		"AP6 Power State"
+	};
+
+	fpgad_xfpga_AP_context *c =
+		(fpgad_xfpga_AP_context *)context;
+
+	LOG("%s %s\n", c->message,
+			d->scratchpad[0] < 4 ?
+			power_states[d->scratchpad[0]] :
+			"unknown");
+}
+
+typedef struct _fpgad_xfpga_Error_context {
+	const char *sysfs_file;
+	const char *message;
+	int low_bit;
+	int high_bit;
+} fpgad_xfpga_Error_context;
+
+fpgad_xfpga_Error_context fpgad_xfpga_Error_contexts[] = {
+	/*  0 */ { "errors/errors", "PORT_ERROR[0x1010].VfFlrAccessError",                   51, 51 },
+	/*  1 */ { "errors/errors", "PORT_ERROR[0x1010].Ap6Event",                           50, 50 }, /* AP6 NULL GBS */
+	/*  2 */ { "errors/errors", "PORT_ERROR[0x1010].PMRError",                           49, 49 },
+	/*  3 */ { "errors/errors", "PORT_ERROR[0x1010].PageFault",                          48, 48 },
+	/*  4 */ { "errors/errors", "PORT_ERROR[0x1010].VgaMemRangeError",                   47, 47 },
+	/*  5 */ { "errors/errors", "PORT_ERROR[0x1010].LegRangeHighError",                  46, 46 },
+	/*  6 */ { "errors/errors", "PORT_ERROR[0x1010].LegRangeLowError",                   45, 45 },
+	/*  7 */ { "errors/errors", "PORT_ERROR[0x1010].GenProtRangeError",                  44, 44 },
+	/*  8 */ { "errors/errors", "PORT_ERROR[0x1010].L1prMesegError",                     43, 43 },
+	/*  9 */ { "errors/errors", "PORT_ERROR[0x1010].L1prSmrr2Error",                     42, 42 },
+	/* 10 */ { "errors/errors", "PORT_ERROR[0x1010].L1prSmrrError",                      41, 41 },
+	/* 11 */ { "errors/errors", "PORT_ERROR[0x1010].TxReqCounterOverflow",               40, 40 },
+	/* 12 */ { "errors/errors", "PORT_ERROR[0x1010].UnexpMMIOResp",                      34, 34 },
+	/* 13 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh2FifoOverflow",                  33, 33 },
+	/* 14 */ { "errors/errors", "PORT_ERROR[0x1010].MMIOTimedOut",                       32, 32 },
+	/* 15 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1NonZeroSOP",                    24, 24 },
+	/* 16 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1IncorrectAddr",                 23, 23 },
+	/* 17 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1DataPayloadOverrun",            22, 22 },
+	/* 18 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1InsufficientData",              21, 21 },
+	/* 19 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1Len4NotAligned",                20, 20 },
+	/* 20 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1Len2NotAligned",                19, 19 },
+	/* 21 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1Len3NotSupported",              18, 18 },
+	/* 22 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1InvalidReqEncoding",            17, 17 },
+	/* 23 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh1Overflow",                      16, 16 },
+	/* 24 */ { "errors/errors", "PORT_ERROR[0x1010].MMIOWrWhileRst",                     10, 10 },
+	/* 25 */ { "errors/errors", "PORT_ERROR[0x1010].MMIORdWhileRst",                      9,  9 },
+	/* 26 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh0Len4NotAligned",                 4,  4 },
+	/* 27 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh0Len2NotAligned",                 3,  3 },
+	/* 28 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh0Len3NotSupported",               2,  2 },
+	/* 29 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh0InvalidReqEncoding",             1,  1 },
+	/* 30 */ { "errors/errors", "PORT_ERROR[0x1010].TxCh0Overflow",                       0,  0 },
+
+	/* 31 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxReqCounterOverflow",    40, 40 },
+	/* 32 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh2FifoOverflow",       33, 33 },
+	/* 33 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].MMIOTimedOut",            32, 32 },
+	/* 34 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1IllegalVCsel",       25, 25 },
+	/* 35 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1NonZeroSOP",         24, 24 },
+	/* 36 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1IncorrectAddr",      23, 23 },
+	/* 37 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1DataPayloadOverrun", 22, 22 },
+	/* 38 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1InsufficientData",   21, 21 },
+	/* 39 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1Len4NotAligned",     20, 20 },
+	/* 40 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1Len2NotAligned",     19, 19 },
+	/* 41 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1Len3NotSupported",   18, 18 },
+	/* 42 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1InvalidReqEncoding", 17, 17 },
+	/* 43 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh1Overflow",           16, 16 },
+	/* 44 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].MMIOWrWhileRst",          10, 10 },
+	/* 45 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].MMIORdWhileRst",           9,  9 },
+	/* 46 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh0Len4NotAligned",      4,  4 },
+	/* 47 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh0Len2NotAligned",      3,  3 },
+	/* 48 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh0Len3NotSupported",    2,  2 },
+	/* 49 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh0InvalidReqEncoding",  1,  1 },
+	/* 50 */ { "errors/first_error", "PORT_FIRST_ERROR[0x1018].TxCh0Overflow",            0,  0 },
+
+	/* 51 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].CvlCdcParErro0",           17, 19 },
+	/* 52 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].Pcie1CdcParErr",           12, 16 },
+	/* 53 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].Pcie0CdcParErr",            7, 11 },
+	/* 54 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].MBPErr",                    6,  6 },
+	/* 55 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].AfuAccessModeErr",          5,  5 },
+	/* 56 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].IommuParityErr",            4,  4 },
+	/* 57 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].KtiCdcParityErr",           2,  3 },
+	/* 58 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].FabricFifoUOflow",          1,  1 },
+	/* 59 */ { "errors/fme-errors/errors", "FME_ERROR0[0x4010].FabricErr",                 0,  0 },
+
+	/* 60 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].FunctTypeErr",                 63, 63 },
+	/* 61 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].VFNumb",                       62, 62 },
+	/* 62 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].RxPoisonTlpErr",                9,  9 },
+	/* 63 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].ParityErr",                     8,  8 },
+	/* 64 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].CompTimeOutErr",                7,  7 },
+	/* 65 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].CompStatErr",                   6,  6 },
+	/* 66 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].CompTagErr",                    5,  5 },
+	/* 67 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].MRLengthErr",                   4,  4 },
+	/* 68 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].MRAddrErr",                     3,  3 },
+	/* 69 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].MWLengthErr",                   2,  2 },
+	/* 70 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].MWAddrErr",                     1,  1 },
+	/* 71 */ { "errors/pcie0_errors", "PCIE0_ERROR[0x4020].FormatTypeErr",                 0,  0 },
+
+	/* 72 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].RxPoisonTlpErr",                9,  9 },
+	/* 73 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].ParityErr",                     8,  8 },
+	/* 74 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].CompTimeOutErr",                7,  7 },
+	/* 75 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].CompStatErr",                   6,  6 },
+	/* 76 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].CompTagErr",                    5,  5 },
+	/* 77 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].MRLengthErr",                   4,  4 },
+	/* 78 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].MRAddrErr",                     3,  3 },
+	/* 79 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].MWLengthErr",                   2,  2 },
+	/* 80 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].MWAddrErr",                     1,  1 },
+	/* 81 */ { "errors/pcie1_errors", "PCIE1_ERROR[0x4030].FormatTypeErr",                 0,  0 },
+
+	/* 82 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].MBPErr",            12, 12 },
+	/* 83 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].PowerThreshAP2",    11, 11 },
+	/* 84 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].PowerThreshAP1",    10, 10 },
+	/* 85 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].TempThreshAP6",      9,  9 }, /* AP6 */
+	/* 86 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].InjectedWarningErr", 6,  6 },
+	/* 87 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].AfuAccessModeErr",   5,  5 },
+	/* 88 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].ProcHot",            4,  4 },
+	/* 89 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].PortFatalErr",       3,  3 },
+	/* 90 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].PcieError",          2,  2 },
+	/* 91 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].TempThreshAP2",      1,  1 },
+	/* 92 */ { "errors/nonfatal_errors",  "RAS_NOFAT_ERR_STAT[0x4050].TempThreshAP1",      0,  0 },
+
+	/* 93 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].InjectedCatastErr",  11, 11 },
+	/* 94 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].ThermCatastErr",     10, 10 },
+	/* 95 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].CrcCatastErr",        9,  9 },
+	/* 96 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].InjectedFatalErr",    8,  8 },
+	/* 97 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].PciePoisonErr",       7,  7 },
+	/* 98 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].FabricFatalErr",      6,  6 },
+	/* 99 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].IommuFatalErr",       5,  5 },
+	/*100 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].DramFatalErr",        4,  4 },
+	/*101 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].KtiProtoFatalErr",    3,  3 },
+	/*102 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].CciFatalErr",         2,  2 },
+	/*103 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].TagCchFatalErr",      1,  1 },
+	/*104 */ { "errors/catfatal_errors",  "RAS_CATFAT_ERROR_STAT[0x4060].KtiLinkFatalErr",     0,  0 },
+};
+
+fpgad_detection_status
+fpgad_xfpga_detect_Error(fpgad_monitored_device *d,
+			 void *context)
+{
+	fpgad_xfpga_Error_context *c =
+		(fpgad_xfpga_Error_context *)context;
+	fpga_object obj = NULL;
+	fpga_result res;
+	uint64_t err = 0;
+	uint64_t mask;
+	uint64_t value;
+	int i;
+	bool detected = false;
+
+	res = fpgaTokenGetObject(d->token, c->sysfs_file,
+				 &obj, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to get error object\n");
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	res = fpgaObjectRead64(obj, &err, 0);
+	if (res != FPGA_OK) {
+		LOG("failed to read error object\n");
+		fpgaDestroyObject(&obj);
+		return FPGAD_STATUS_NOT_DETECTED;
+	}
+
+	fpgaDestroyObject(&obj);
+
+	mask = 0;
+	for (i = c->low_bit ; i <= c->high_bit ; ++i)
+		mask |= 1ULL << i;
+
+	value = mask & err;
+
+	if (value != 0 && !mon_has_error_occurred(d, context)) {
+		detected = mon_add_device_error(d, context);
+	}
+
+	if (value == 0 && mon_has_error_occurred(d, context)) {
+		mon_remove_device_error(d, context);
+	}
+
+	return detected ? FPGAD_STATUS_DETECTED : FPGAD_STATUS_NOT_DETECTED;
+}
+
+fpgad_detection_status
+fpgad_xfpga_detect_High_Priority_Error(fpgad_monitored_device *d,
+				       void *context)
+{
+	fpgad_detection_status status;
+
+	status = fpgad_xfpga_detect_Error(d, context);
+
+	if (status == FPGAD_STATUS_DETECTED)
+		return FPGAD_STATUS_DETECTED_HIGH;
+
+	return status;
+}
+
+void fpgad_xfpga_respond_LogError(fpgad_monitored_device *d,
+				  void *context)
+{
+	fpgad_xfpga_Error_context *c =
+		(fpgad_xfpga_Error_context *)context;
+
+	LOG("%s\n", c->message);
+
+	// signal OPAE events API
+	opae_api_send_EVENT_ERROR(d);
+}
+
+void fpgad_xfpga_respond_AP6_and_Null_GBS(fpgad_monitored_device *d,
+					  void *context)
+{
+	fpgad_xfpga_Error_context *c =
+		(fpgad_xfpga_Error_context *)context;
+
+	LOG("%s\n", c->message);
+
+	// Program NULL GBS
+
+	// d will be the Port device. We need to find the parent
+	// (FME) device to perform the PR.
+
+	if (d->bitstr) {
+		fpga_result res;
+		fpga_properties prop = NULL;
+		fpga_token fme_tok = NULL;
+		fpga_handle fme_h = NULL;
+		const uint32_t slot = 0;
+
+		res = fpgaGetProperties(d->token, &prop);
+		if (res != FPGA_OK) {
+			LOG("(AP6) failed to get properties! : %s\n",
+			    fpgaErrStr(res));
+			goto out_signal;
+		}
+
+		res = fpgaPropertiesGetParent(prop, &fme_tok);
+		if (res != FPGA_OK || !fme_tok) {
+			LOG("(AP6) failed to get FME token! : %s\n",
+			    fpgaErrStr(res));
+			goto out_destroy_props;
+		}
+
+		res = fpgaOpen(fme_tok, &fme_h, 0);
+		if (res != FPGA_OK) {
+			LOG("(AP6) failed to get FME handle! : %s\n",
+			    fpgaErrStr(res));
+			goto out_destroy_fme_tok;
+		}
+
+		LOG("programming \"%s\": ", d->bitstr->filename);
+
+		res = fpgaReconfigureSlot(fme_h,
+					  slot,
+					  d->bitstr->data,
+					  d->bitstr->data_len,
+					  FPGA_RECONF_FORCE);
+		if (res != FPGA_OK)
+			LOG("SUCCESS\n");
+		else
+			LOG("FAILED : %s\n", fpgaErrStr(res));
+
+		fpgaClose(fme_h);
+out_destroy_fme_tok:
+		fpgaDestroyToken(&fme_tok);
+out_destroy_props:
+		fpgaDestroyProperties(&prop);
+	} else
+		LOG("no bitstream to program for AP6!\n");
+
+	// Signal OPAE events API
+out_signal:
+	opae_api_send_EVENT_POWER_THERMAL(d);
+}
+
+void fpgad_xfpga_respond_AP6(fpgad_monitored_device *d,
+			     void *context)
+{
+	fpgad_xfpga_Error_context *c =
+		(fpgad_xfpga_Error_context *)context;
+
+	LOG("%s\n", c->message);
+
+	// Signal OPAE events API
+	opae_api_send_EVENT_POWER_THERMAL(d);
+}
+
+// Port detections
+STATIC fpgad_detect_event_t fpgad_xfpga_port_detections[] = {
+	fpgad_xfpga_detect_AP1_or_AP2,
+	fpgad_xfpga_detect_AP1_or_AP2,
+	fpgad_xfpga_detect_PowerStateChange,
+
+	fpgad_xfpga_detect_Error, //  0
+	fpgad_xfpga_detect_High_Priority_Error, // 1 (AP6 and NULL GBS)
+	fpgad_xfpga_detect_Error, //  2
+	fpgad_xfpga_detect_Error, //  3
+	fpgad_xfpga_detect_Error, //  4
+	fpgad_xfpga_detect_Error, //  5
+	fpgad_xfpga_detect_Error, //  6
+	fpgad_xfpga_detect_Error, //  7
+	fpgad_xfpga_detect_Error, //  8
+	fpgad_xfpga_detect_Error, //  9
+	fpgad_xfpga_detect_Error, // 10
+	fpgad_xfpga_detect_Error, // 11
+	fpgad_xfpga_detect_Error, // 12
+	fpgad_xfpga_detect_Error, // 13
+	fpgad_xfpga_detect_Error, // 14
+	fpgad_xfpga_detect_Error, // 15
+	fpgad_xfpga_detect_Error, // 16
+	fpgad_xfpga_detect_Error, // 17
+	fpgad_xfpga_detect_Error, // 18
+	fpgad_xfpga_detect_Error, // 19
+	fpgad_xfpga_detect_Error, // 20
+	fpgad_xfpga_detect_Error, // 21
+	fpgad_xfpga_detect_Error, // 22
+	fpgad_xfpga_detect_Error, // 23
+	fpgad_xfpga_detect_Error, // 24
+	fpgad_xfpga_detect_Error, // 25
+	fpgad_xfpga_detect_Error, // 26
+	fpgad_xfpga_detect_Error, // 27
+	fpgad_xfpga_detect_Error, // 28
+	fpgad_xfpga_detect_Error, // 29
+	fpgad_xfpga_detect_Error, // 30
+
+	fpgad_xfpga_detect_Error, // 31
+	fpgad_xfpga_detect_Error, // 32
+	fpgad_xfpga_detect_Error, // 33
+	fpgad_xfpga_detect_Error, // 34
+	fpgad_xfpga_detect_Error, // 35
+	fpgad_xfpga_detect_Error, // 36
+	fpgad_xfpga_detect_Error, // 37
+	fpgad_xfpga_detect_Error, // 38
+	fpgad_xfpga_detect_Error, // 39
+	fpgad_xfpga_detect_Error, // 40
+	fpgad_xfpga_detect_Error, // 41
+	fpgad_xfpga_detect_Error, // 42
+	fpgad_xfpga_detect_Error, // 43
+	fpgad_xfpga_detect_Error, // 44
+	fpgad_xfpga_detect_Error, // 45
+	fpgad_xfpga_detect_Error, // 46
+	fpgad_xfpga_detect_Error, // 47
+	fpgad_xfpga_detect_Error, // 48
+	fpgad_xfpga_detect_Error, // 49
+	fpgad_xfpga_detect_Error, // 50
+
+	NULL
+};
+
+STATIC void *fpgad_xfpga_port_detection_contexts[] = {
+	&fpgad_xfpga_AP_contexts[0],
+	&fpgad_xfpga_AP_contexts[1],
+	&fpgad_xfpga_AP_contexts[2],
+
+	&fpgad_xfpga_Error_contexts[0],
+	&fpgad_xfpga_Error_contexts[1],
+	&fpgad_xfpga_Error_contexts[2],
+	&fpgad_xfpga_Error_contexts[3],
+	&fpgad_xfpga_Error_contexts[4],
+	&fpgad_xfpga_Error_contexts[5],
+	&fpgad_xfpga_Error_contexts[6],
+	&fpgad_xfpga_Error_contexts[7],
+	&fpgad_xfpga_Error_contexts[8],
+	&fpgad_xfpga_Error_contexts[9],
+	&fpgad_xfpga_Error_contexts[10],
+	&fpgad_xfpga_Error_contexts[11],
+	&fpgad_xfpga_Error_contexts[12],
+	&fpgad_xfpga_Error_contexts[13],
+	&fpgad_xfpga_Error_contexts[14],
+	&fpgad_xfpga_Error_contexts[15],
+	&fpgad_xfpga_Error_contexts[16],
+	&fpgad_xfpga_Error_contexts[17],
+	&fpgad_xfpga_Error_contexts[18],
+	&fpgad_xfpga_Error_contexts[19],
+	&fpgad_xfpga_Error_contexts[20],
+	&fpgad_xfpga_Error_contexts[21],
+	&fpgad_xfpga_Error_contexts[22],
+	&fpgad_xfpga_Error_contexts[23],
+	&fpgad_xfpga_Error_contexts[24],
+	&fpgad_xfpga_Error_contexts[25],
+	&fpgad_xfpga_Error_contexts[26],
+	&fpgad_xfpga_Error_contexts[27],
+	&fpgad_xfpga_Error_contexts[28],
+	&fpgad_xfpga_Error_contexts[29],
+	&fpgad_xfpga_Error_contexts[30],
+
+	&fpgad_xfpga_Error_contexts[31],
+	&fpgad_xfpga_Error_contexts[32],
+	&fpgad_xfpga_Error_contexts[33],
+	&fpgad_xfpga_Error_contexts[34],
+	&fpgad_xfpga_Error_contexts[35],
+	&fpgad_xfpga_Error_contexts[36],
+	&fpgad_xfpga_Error_contexts[37],
+	&fpgad_xfpga_Error_contexts[38],
+	&fpgad_xfpga_Error_contexts[39],
+	&fpgad_xfpga_Error_contexts[40],
+	&fpgad_xfpga_Error_contexts[41],
+	&fpgad_xfpga_Error_contexts[42],
+	&fpgad_xfpga_Error_contexts[43],
+	&fpgad_xfpga_Error_contexts[44],
+	&fpgad_xfpga_Error_contexts[45],
+	&fpgad_xfpga_Error_contexts[46],
+	&fpgad_xfpga_Error_contexts[47],
+	&fpgad_xfpga_Error_contexts[48],
+	&fpgad_xfpga_Error_contexts[49],
+	&fpgad_xfpga_Error_contexts[50],
+
+	NULL
+};
+
+// Port responses
+STATIC fpgad_respond_event_t fpgad_xfpga_port_responses[] = {
+	fpgad_xfpga_respond_AP1_or_AP2,
+	fpgad_xfpga_respond_AP1_or_AP2,
+	fpgad_xfpga_respond_PowerStateChange,
+
+	fpgad_xfpga_respond_LogError, //  0
+	fpgad_xfpga_respond_AP6_and_Null_GBS, // 1
+	fpgad_xfpga_respond_LogError, //  2
+	fpgad_xfpga_respond_LogError, //  3
+	fpgad_xfpga_respond_LogError, //  4
+	fpgad_xfpga_respond_LogError, //  5
+	fpgad_xfpga_respond_LogError, //  6
+	fpgad_xfpga_respond_LogError, //  7
+	fpgad_xfpga_respond_LogError, //  8
+	fpgad_xfpga_respond_LogError, //  9
+	fpgad_xfpga_respond_LogError, // 10
+	fpgad_xfpga_respond_LogError, // 11
+	fpgad_xfpga_respond_LogError, // 12
+	fpgad_xfpga_respond_LogError, // 13
+	fpgad_xfpga_respond_LogError, // 14
+	fpgad_xfpga_respond_LogError, // 15
+	fpgad_xfpga_respond_LogError, // 16
+	fpgad_xfpga_respond_LogError, // 17
+	fpgad_xfpga_respond_LogError, // 18
+	fpgad_xfpga_respond_LogError, // 19
+	fpgad_xfpga_respond_LogError, // 20
+	fpgad_xfpga_respond_LogError, // 21
+	fpgad_xfpga_respond_LogError, // 22
+	fpgad_xfpga_respond_LogError, // 23
+	fpgad_xfpga_respond_LogError, // 24
+	fpgad_xfpga_respond_LogError, // 25
+	fpgad_xfpga_respond_LogError, // 26
+	fpgad_xfpga_respond_LogError, // 27
+	fpgad_xfpga_respond_LogError, // 28
+	fpgad_xfpga_respond_LogError, // 29
+	fpgad_xfpga_respond_LogError, // 30
+
+	fpgad_xfpga_respond_LogError, // 31
+	fpgad_xfpga_respond_LogError, // 32
+	fpgad_xfpga_respond_LogError, // 33
+	fpgad_xfpga_respond_LogError, // 34
+	fpgad_xfpga_respond_LogError, // 35
+	fpgad_xfpga_respond_LogError, // 36
+	fpgad_xfpga_respond_LogError, // 37
+	fpgad_xfpga_respond_LogError, // 38
+	fpgad_xfpga_respond_LogError, // 39
+	fpgad_xfpga_respond_LogError, // 40
+	fpgad_xfpga_respond_LogError, // 41
+	fpgad_xfpga_respond_LogError, // 42
+	fpgad_xfpga_respond_LogError, // 43
+	fpgad_xfpga_respond_LogError, // 44
+	fpgad_xfpga_respond_LogError, // 45
+	fpgad_xfpga_respond_LogError, // 46
+	fpgad_xfpga_respond_LogError, // 47
+	fpgad_xfpga_respond_LogError, // 48
+	fpgad_xfpga_respond_LogError, // 49
+	fpgad_xfpga_respond_LogError, // 50
+
+	NULL
+};
+
+STATIC void *fpgad_xfpga_port_response_contexts[] = {
+	&fpgad_xfpga_AP_contexts[0],
+	&fpgad_xfpga_AP_contexts[1],
+	&fpgad_xfpga_AP_contexts[2],
+
+	&fpgad_xfpga_Error_contexts[0],
+	&fpgad_xfpga_Error_contexts[1],
+	&fpgad_xfpga_Error_contexts[2],
+	&fpgad_xfpga_Error_contexts[3],
+	&fpgad_xfpga_Error_contexts[4],
+	&fpgad_xfpga_Error_contexts[5],
+	&fpgad_xfpga_Error_contexts[6],
+	&fpgad_xfpga_Error_contexts[7],
+	&fpgad_xfpga_Error_contexts[8],
+	&fpgad_xfpga_Error_contexts[9],
+	&fpgad_xfpga_Error_contexts[10],
+	&fpgad_xfpga_Error_contexts[11],
+	&fpgad_xfpga_Error_contexts[12],
+	&fpgad_xfpga_Error_contexts[13],
+	&fpgad_xfpga_Error_contexts[14],
+	&fpgad_xfpga_Error_contexts[15],
+	&fpgad_xfpga_Error_contexts[16],
+	&fpgad_xfpga_Error_contexts[17],
+	&fpgad_xfpga_Error_contexts[18],
+	&fpgad_xfpga_Error_contexts[19],
+	&fpgad_xfpga_Error_contexts[20],
+	&fpgad_xfpga_Error_contexts[21],
+	&fpgad_xfpga_Error_contexts[22],
+	&fpgad_xfpga_Error_contexts[23],
+	&fpgad_xfpga_Error_contexts[24],
+	&fpgad_xfpga_Error_contexts[25],
+	&fpgad_xfpga_Error_contexts[26],
+	&fpgad_xfpga_Error_contexts[27],
+	&fpgad_xfpga_Error_contexts[28],
+	&fpgad_xfpga_Error_contexts[29],
+	&fpgad_xfpga_Error_contexts[30],
+
+	&fpgad_xfpga_Error_contexts[31],
+	&fpgad_xfpga_Error_contexts[32],
+	&fpgad_xfpga_Error_contexts[33],
+	&fpgad_xfpga_Error_contexts[34],
+	&fpgad_xfpga_Error_contexts[35],
+	&fpgad_xfpga_Error_contexts[36],
+	&fpgad_xfpga_Error_contexts[37],
+	&fpgad_xfpga_Error_contexts[38],
+	&fpgad_xfpga_Error_contexts[39],
+	&fpgad_xfpga_Error_contexts[40],
+	&fpgad_xfpga_Error_contexts[41],
+	&fpgad_xfpga_Error_contexts[42],
+	&fpgad_xfpga_Error_contexts[43],
+	&fpgad_xfpga_Error_contexts[44],
+	&fpgad_xfpga_Error_contexts[45],
+	&fpgad_xfpga_Error_contexts[46],
+	&fpgad_xfpga_Error_contexts[47],
+	&fpgad_xfpga_Error_contexts[48],
+	&fpgad_xfpga_Error_contexts[49],
+	&fpgad_xfpga_Error_contexts[50],
+
+	NULL
+};
+
+// FME detections
+STATIC fpgad_detect_event_t fpgad_xfpga_fme_detections[] = {
+	fpgad_xfpga_detect_Error, // 51
+	fpgad_xfpga_detect_Error, // 52
+	fpgad_xfpga_detect_Error, // 53
+	fpgad_xfpga_detect_Error, // 54
+	fpgad_xfpga_detect_Error, // 55
+	fpgad_xfpga_detect_Error, // 56
+	fpgad_xfpga_detect_Error, // 57
+	fpgad_xfpga_detect_Error, // 58
+	fpgad_xfpga_detect_Error, // 59
+
+	fpgad_xfpga_detect_Error, // 60
+	fpgad_xfpga_detect_Error, // 61
+	fpgad_xfpga_detect_Error, // 62
+	fpgad_xfpga_detect_Error, // 63
+	fpgad_xfpga_detect_Error, // 64
+	fpgad_xfpga_detect_Error, // 65
+	fpgad_xfpga_detect_Error, // 66
+	fpgad_xfpga_detect_Error, // 67
+	fpgad_xfpga_detect_Error, // 68
+	fpgad_xfpga_detect_Error, // 69
+	fpgad_xfpga_detect_Error, // 70
+	fpgad_xfpga_detect_Error, // 71
+
+	fpgad_xfpga_detect_Error, // 72
+	fpgad_xfpga_detect_Error, // 73
+	fpgad_xfpga_detect_Error, // 74
+	fpgad_xfpga_detect_Error, // 75
+	fpgad_xfpga_detect_Error, // 76
+	fpgad_xfpga_detect_Error, // 77
+	fpgad_xfpga_detect_Error, // 78
+	fpgad_xfpga_detect_Error, // 79
+	fpgad_xfpga_detect_Error, // 80
+	fpgad_xfpga_detect_Error, // 81
+
+	fpgad_xfpga_detect_Error, // 82
+	fpgad_xfpga_detect_Error, // 83
+	fpgad_xfpga_detect_Error, // 84
+	fpgad_xfpga_detect_Error, // 85
+	fpgad_xfpga_detect_Error, // 86
+	fpgad_xfpga_detect_Error, // 87
+	fpgad_xfpga_detect_Error, // 88
+	fpgad_xfpga_detect_Error, // 89
+	fpgad_xfpga_detect_Error, // 90
+	fpgad_xfpga_detect_Error, // 91
+	fpgad_xfpga_detect_Error, // 92
+
+	fpgad_xfpga_detect_Error, // 93
+	fpgad_xfpga_detect_Error, // 94
+	fpgad_xfpga_detect_Error, // 95
+	fpgad_xfpga_detect_Error, // 96
+	fpgad_xfpga_detect_Error, // 97
+	fpgad_xfpga_detect_Error, // 98
+	fpgad_xfpga_detect_Error, // 99
+	fpgad_xfpga_detect_Error, //100
+	fpgad_xfpga_detect_Error, //101
+	fpgad_xfpga_detect_Error, //102
+	fpgad_xfpga_detect_Error, //103
+	fpgad_xfpga_detect_Error, //104
+
+	NULL
+};
+
+STATIC void *fpgad_xfpga_fme_detection_contexts[] = {
+	&fpgad_xfpga_Error_contexts[51],
+	&fpgad_xfpga_Error_contexts[52],
+	&fpgad_xfpga_Error_contexts[53],
+	&fpgad_xfpga_Error_contexts[54],
+	&fpgad_xfpga_Error_contexts[55],
+	&fpgad_xfpga_Error_contexts[56],
+	&fpgad_xfpga_Error_contexts[57],
+	&fpgad_xfpga_Error_contexts[58],
+	&fpgad_xfpga_Error_contexts[59],
+
+	&fpgad_xfpga_Error_contexts[60],
+	&fpgad_xfpga_Error_contexts[61],
+	&fpgad_xfpga_Error_contexts[62],
+	&fpgad_xfpga_Error_contexts[63],
+	&fpgad_xfpga_Error_contexts[64],
+	&fpgad_xfpga_Error_contexts[65],
+	&fpgad_xfpga_Error_contexts[66],
+	&fpgad_xfpga_Error_contexts[67],
+	&fpgad_xfpga_Error_contexts[68],
+	&fpgad_xfpga_Error_contexts[69],
+	&fpgad_xfpga_Error_contexts[70],
+	&fpgad_xfpga_Error_contexts[71],
+
+	&fpgad_xfpga_Error_contexts[72],
+	&fpgad_xfpga_Error_contexts[73],
+	&fpgad_xfpga_Error_contexts[74],
+	&fpgad_xfpga_Error_contexts[75],
+	&fpgad_xfpga_Error_contexts[76],
+	&fpgad_xfpga_Error_contexts[77],
+	&fpgad_xfpga_Error_contexts[78],
+	&fpgad_xfpga_Error_contexts[79],
+	&fpgad_xfpga_Error_contexts[80],
+	&fpgad_xfpga_Error_contexts[81],
+
+	&fpgad_xfpga_Error_contexts[82],
+	&fpgad_xfpga_Error_contexts[83],
+	&fpgad_xfpga_Error_contexts[84],
+	&fpgad_xfpga_Error_contexts[85],
+	&fpgad_xfpga_Error_contexts[86],
+	&fpgad_xfpga_Error_contexts[87],
+	&fpgad_xfpga_Error_contexts[88],
+	&fpgad_xfpga_Error_contexts[89],
+	&fpgad_xfpga_Error_contexts[90],
+	&fpgad_xfpga_Error_contexts[91],
+	&fpgad_xfpga_Error_contexts[92],
+
+	&fpgad_xfpga_Error_contexts[93],
+	&fpgad_xfpga_Error_contexts[94],
+	&fpgad_xfpga_Error_contexts[95],
+	&fpgad_xfpga_Error_contexts[96],
+	&fpgad_xfpga_Error_contexts[97],
+	&fpgad_xfpga_Error_contexts[98],
+	&fpgad_xfpga_Error_contexts[99],
+	&fpgad_xfpga_Error_contexts[100],
+	&fpgad_xfpga_Error_contexts[101],
+	&fpgad_xfpga_Error_contexts[102],
+	&fpgad_xfpga_Error_contexts[103],
+	&fpgad_xfpga_Error_contexts[104],
+
+	NULL
+};
+
+// FME responses
+STATIC fpgad_respond_event_t fpgad_xfpga_fme_responses[] = {
+	fpgad_xfpga_respond_LogError, // 51
+	fpgad_xfpga_respond_LogError, // 52
+	fpgad_xfpga_respond_LogError, // 53
+	fpgad_xfpga_respond_LogError, // 54
+	fpgad_xfpga_respond_LogError, // 55
+	fpgad_xfpga_respond_LogError, // 56
+	fpgad_xfpga_respond_LogError, // 57
+	fpgad_xfpga_respond_LogError, // 58
+	fpgad_xfpga_respond_LogError, // 59
+
+	fpgad_xfpga_respond_LogError, // 60
+	fpgad_xfpga_respond_LogError, // 61
+	fpgad_xfpga_respond_LogError, // 62
+	fpgad_xfpga_respond_LogError, // 63
+	fpgad_xfpga_respond_LogError, // 64
+	fpgad_xfpga_respond_LogError, // 65
+	fpgad_xfpga_respond_LogError, // 66
+	fpgad_xfpga_respond_LogError, // 67
+	fpgad_xfpga_respond_LogError, // 68
+	fpgad_xfpga_respond_LogError, // 69
+	fpgad_xfpga_respond_LogError, // 70
+	fpgad_xfpga_respond_LogError, // 71
+
+	fpgad_xfpga_respond_LogError, // 72
+	fpgad_xfpga_respond_LogError, // 73
+	fpgad_xfpga_respond_LogError, // 74
+	fpgad_xfpga_respond_LogError, // 75
+	fpgad_xfpga_respond_LogError, // 76
+	fpgad_xfpga_respond_LogError, // 77
+	fpgad_xfpga_respond_LogError, // 78
+	fpgad_xfpga_respond_LogError, // 79
+	fpgad_xfpga_respond_LogError, // 80
+	fpgad_xfpga_respond_LogError, // 81
+
+	fpgad_xfpga_respond_LogError, // 82
+	fpgad_xfpga_respond_LogError, // 83
+	fpgad_xfpga_respond_LogError, // 84
+	fpgad_xfpga_respond_AP6,      // 85
+	fpgad_xfpga_respond_LogError, // 86
+	fpgad_xfpga_respond_LogError, // 87
+	fpgad_xfpga_respond_LogError, // 88
+	fpgad_xfpga_respond_LogError, // 89
+	fpgad_xfpga_respond_LogError, // 90
+	fpgad_xfpga_respond_LogError, // 91
+	fpgad_xfpga_respond_LogError, // 92
+
+	fpgad_xfpga_respond_LogError, // 93
+	fpgad_xfpga_respond_LogError, // 94
+	fpgad_xfpga_respond_LogError, // 95
+	fpgad_xfpga_respond_LogError, // 96
+	fpgad_xfpga_respond_LogError, // 97
+	fpgad_xfpga_respond_LogError, // 98
+	fpgad_xfpga_respond_LogError, // 99
+	fpgad_xfpga_respond_LogError, //100
+	fpgad_xfpga_respond_LogError, //101
+	fpgad_xfpga_respond_LogError, //102
+	fpgad_xfpga_respond_LogError, //103
+	fpgad_xfpga_respond_LogError, //104
+
+	NULL
+};
+
+STATIC void *fpgad_xfpga_fme_response_contexts[] = {
+	&fpgad_xfpga_Error_contexts[51],
+	&fpgad_xfpga_Error_contexts[52],
+	&fpgad_xfpga_Error_contexts[53],
+	&fpgad_xfpga_Error_contexts[54],
+	&fpgad_xfpga_Error_contexts[55],
+	&fpgad_xfpga_Error_contexts[56],
+	&fpgad_xfpga_Error_contexts[57],
+	&fpgad_xfpga_Error_contexts[58],
+	&fpgad_xfpga_Error_contexts[59],
+
+	&fpgad_xfpga_Error_contexts[60],
+	&fpgad_xfpga_Error_contexts[61],
+	&fpgad_xfpga_Error_contexts[62],
+	&fpgad_xfpga_Error_contexts[63],
+	&fpgad_xfpga_Error_contexts[64],
+	&fpgad_xfpga_Error_contexts[65],
+	&fpgad_xfpga_Error_contexts[66],
+	&fpgad_xfpga_Error_contexts[67],
+	&fpgad_xfpga_Error_contexts[68],
+	&fpgad_xfpga_Error_contexts[69],
+	&fpgad_xfpga_Error_contexts[70],
+	&fpgad_xfpga_Error_contexts[71],
+
+	&fpgad_xfpga_Error_contexts[72],
+	&fpgad_xfpga_Error_contexts[73],
+	&fpgad_xfpga_Error_contexts[74],
+	&fpgad_xfpga_Error_contexts[75],
+	&fpgad_xfpga_Error_contexts[76],
+	&fpgad_xfpga_Error_contexts[77],
+	&fpgad_xfpga_Error_contexts[78],
+	&fpgad_xfpga_Error_contexts[79],
+	&fpgad_xfpga_Error_contexts[80],
+	&fpgad_xfpga_Error_contexts[81],
+
+	&fpgad_xfpga_Error_contexts[82],
+	&fpgad_xfpga_Error_contexts[83],
+	&fpgad_xfpga_Error_contexts[84],
+	&fpgad_xfpga_Error_contexts[85],
+	&fpgad_xfpga_Error_contexts[86],
+	&fpgad_xfpga_Error_contexts[87],
+	&fpgad_xfpga_Error_contexts[88],
+	&fpgad_xfpga_Error_contexts[89],
+	&fpgad_xfpga_Error_contexts[90],
+	&fpgad_xfpga_Error_contexts[91],
+	&fpgad_xfpga_Error_contexts[92],
+
+	&fpgad_xfpga_Error_contexts[93],
+	&fpgad_xfpga_Error_contexts[94],
+	&fpgad_xfpga_Error_contexts[95],
+	&fpgad_xfpga_Error_contexts[96],
+	&fpgad_xfpga_Error_contexts[97],
+	&fpgad_xfpga_Error_contexts[98],
+	&fpgad_xfpga_Error_contexts[99],
+	&fpgad_xfpga_Error_contexts[100],
+	&fpgad_xfpga_Error_contexts[101],
+	&fpgad_xfpga_Error_contexts[102],
+	&fpgad_xfpga_Error_contexts[103],
+	&fpgad_xfpga_Error_contexts[104],
+
+	NULL
+};
+
+int fpgad_plugin_configure(fpgad_monitored_device *d,
+			   const char *cfg)
+{
+	UNUSED_PARAM(cfg);
+
+	LOG("monitoring vid=0x%04x did=0x%04x objid=0x%x (%s)\n",
+			d->supported->vendor_id,
+			d->supported->device_id,
+			d->object_id,
+			d->object_type == FPGA_ACCELERATOR ?
+			"accelerator" : "device");
+
+	d->type = FPGAD_PLUGIN_TYPE_CALLBACK;
+
+	if (d->object_type == FPGA_ACCELERATOR) {
+		d->detections = fpgad_xfpga_port_detections;
+		d->detection_contexts = fpgad_xfpga_port_detection_contexts;
+		d->responses = fpgad_xfpga_port_responses;
+		d->response_contexts = fpgad_xfpga_port_response_contexts;
+	} else {
+		d->detections = fpgad_xfpga_fme_detections;
+		d->detection_contexts = fpgad_xfpga_fme_detection_contexts;
+		d->responses = fpgad_xfpga_fme_responses;
+		d->response_contexts = fpgad_xfpga_fme_response_contexts;
+	}
+
+	return 0;
+}
+
+void fpgad_plugin_destroy(fpgad_monitored_device *d)
+{
+	LOG("stop monitoring vid=0x%04x did=0x%04x objid=0x%x (%s)\n",
+			d->supported->vendor_id,
+			d->supported->device_id,
+			d->object_id,
+			d->object_type == FPGA_ACCELERATOR ?
+			"accelerator" : "device");
+}
-- 
2.18.2