diff --git a/MAINTAINERS b/MAINTAINERS
index 5fb4880758..1b395e181d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1545,6 +1545,11 @@ M: Chenbo Xia <chenbo.xia@intel.com>
 M: Xiuchun Lu <xiuchun.lu@intel.com>
 F: lib/librte_vfio_user/
 
+Emudev - EXPERIMENTAL
+M: Chenbo Xia <chenbo.xia@intel.com>
+M: Xiuchun Lu <xiuchun.lu@intel.com>
+F: lib/librte_emudev/
+
 Test Applications
 -----------------
 
diff --git a/lib/librte_emudev/meson.build b/lib/librte_emudev/meson.build
new file mode 100644
index 0000000000..4e16cecbaf
--- /dev/null
+++ b/lib/librte_emudev/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2020 Intel Corporation
+
+sources = files('rte_emudev.c')
+headers = files('rte_emudev.h', 'rte_emudev_vdev.h')
diff --git a/lib/librte_emudev/rte_emudev.c b/lib/librte_emudev/rte_emudev.c
new file mode 100644
index 0000000000..2bbf3970d8
--- /dev/null
+++ b/lib/librte_emudev/rte_emudev.c
@@ -0,0 +1,486 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+
+#include <string.h>
+
+#include <rte_malloc.h>
+#include <rte_memcpy.h>
+
+#include "rte_emudev.h"
+
+#define RTE_MAX_EMU_DEV 1024
+struct rte_emudev rte_emu_devices[RTE_MAX_EMU_DEV];
+
+static struct rte_emudev_global emu_dev_globals = {
+	.nb_devs = 0
+};
+
+static inline uint16_t rte_emu_alloc_dev_id(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < RTE_MAX_EMU_DEV; i++) {
+		if (rte_emu_devices[i].name[0] == '\0')
+			return i;
+	}
+	return RTE_MAX_EMU_DEV;
+}
+
+uint8_t
+rte_emudev_count(void)
+{
+	return emu_dev_globals.nb_devs;
+}
+
+int
+rte_emudev_get_dev_id(const char *name)
+{
+	uint16_t i;
+
+	if (!name) {
+		RTE_EMUDEV_LOG(ERR, "Failed to get device ID: "
+			"NULL device name\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < emu_dev_globals.nb_devs; i++)
+		if (!strncmp(rte_emu_devices[i].name, name,
+			RTE_EMU_NAME_MAX_LEN))
+			return i;
+
+	return -ENODEV;
+}
+
+struct rte_emudev *
+rte_emudev_allocate(const char *name)
+{
+	uint16_t dev_id;
+	struct rte_emudev *emu_dev = NULL;
+	size_t name_len;
+
+	if (!name) {
+		RTE_EMUDEV_LOG(ERR, "Failed to allocate emudev: "
+			"NULL device name\n");
+		return NULL;
+	}
+
+	name_len = strnlen(name, RTE_EMU_NAME_MAX_LEN);
+	if (!name_len) {
+		RTE_EMUDEV_LOG(ERR, "Emulated device name has zero length\n");
+		return NULL;
+	}
+
+	if (name_len >= RTE_EMU_NAME_MAX_LEN) {
+		RTE_EMUDEV_LOG(ERR, "Emulated device name too long\n");
+		return NULL;
+	}
+
+	if (rte_emudev_allocated(name) != NULL) {
+		RTE_EMUDEV_LOG(ERR,
+			"Emulated device with name %s already exists\n",
+			name);
+		return NULL;
+	}
+
+	dev_id = rte_emu_alloc_dev_id();
+	if (dev_id == RTE_MAX_EMU_DEV) {
+		RTE_EMUDEV_LOG(ERR, "Reached max number of Emulated device\n");
+		return NULL;
+	}
+
+	emu_dev = &rte_emu_devices[dev_id];
+	strncpy(emu_dev->name, name, sizeof(emu_dev->name));
+	emu_dev->dev_id = dev_id;
+	emu_dev_globals.nb_devs++;
+
+	return emu_dev;
+}
+
+int
+rte_emudev_release(struct rte_emudev *dev)
+{
+	if (!dev)
+		return -EINVAL;
+
+	if (dev->priv_data) {
+		rte_free(dev->priv_data);
+		dev->priv_data = NULL;
+	}
+
+	memset(dev, 0, sizeof(*dev));
+	emu_dev_globals.nb_devs--;
+	return 0;
+}
+
+struct rte_emudev *
+rte_emudev_allocated(const char *name)
+{
+	unsigned int i;
+
+	if (!name) {
+		RTE_EMUDEV_LOG(ERR, "Failed to find emudev: "
+			"NULL device name\n");
+		return NULL;
+	}
+
+	for (i = 0; i < RTE_MAX_EMU_DEV; i++) {
+		if (rte_emu_devices[i].dev_ops != NULL &&
+		    strcmp(rte_emu_devices[i].device->name, name) == 0)
+			return &rte_emu_devices[i];
+	}
+	return NULL;
+}
+
+int rte_emudev_is_valid_id(uint16_t dev_id)
+{
+	if (dev_id >= RTE_MAX_EMU_DEV ||
+		rte_emu_devices[dev_id].name[0] == '\0')
+		return 0;
+	else
+		return 1;
+}
+
+int
+rte_emudev_selftest(uint16_t dev_id)
+{
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	struct rte_emudev *dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_selftest, -ENOTSUP);
+
+	return (*dev->dev_ops->dev_selftest)(dev_id);
+}
+
+
+int rte_emudev_start(uint16_t dev_id)
+{
+	struct rte_emudev *dev;
+	int ret;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (dev->started) {
+		RTE_EMUDEV_LOG(ERR, "Device %u already started\n", dev_id);
+		return 0;
+	}
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_start, -ENOTSUP);
+
+	ret = (*dev->dev_ops->dev_start)(dev);
+	if (ret)
+		return ret;
+
+	dev->started = 1;
+	return 0;
+}
+
+void rte_emudev_stop(uint16_t dev_id)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID(dev_id);
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (!dev->started) {
+		RTE_EMUDEV_LOG(ERR, "Device %u already stopped\n", dev_id);
+		return;
+	}
+
+	RTE_FUNC_PTR_OR_RET(*dev->dev_ops->dev_stop);
+
+	(*dev->dev_ops->dev_stop)(dev);
+
+	dev->started = 0;
+}
+
+int rte_emudev_configure(uint16_t dev_id, struct rte_emudev_info *dev_conf)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (!dev_conf)
+		return -EINVAL;
+
+	if (dev->started) {
+		RTE_EMUDEV_LOG(ERR, "Device %u must be stopped "
+			"before configure\n", dev_id);
+		return -EBUSY;
+	}
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_configure, -ENOTSUP);
+
+	if (strcmp(dev_conf->dev_type, dev->dev_info.dev_type)) {
+		RTE_EMUDEV_LOG(ERR, "Wrong device type to configure"
+			" for device %u\n", dev_id);
+		return -EINVAL;
+	}
+
+	return (*dev->dev_ops->dev_configure)(dev, dev_conf);
+}
+
+int rte_emudev_close(uint16_t dev_id)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (dev->started) {
+		RTE_EMUDEV_LOG(ERR, "Device %u must be stopped "
+			"before close\n", dev_id);
+		return -EBUSY;
+	}
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_close, -ENOTSUP);
+
+	(*dev->dev_ops->dev_close)(dev);
+
+	rte_emudev_release(dev);
+	return 0;
+}
+
+int rte_emudev_subscribe_event(uint16_t dev_id,
+		const rte_emudev_event_chnl_t ev_chnl)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!ev_chnl) {
+		RTE_EMUDEV_LOG(ERR, "Failed to subscribe because of NULL"
+			" event channel for device %u\n", dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->subscribe_event, -ENOTSUP);
+
+	return (*dev->dev_ops->subscribe_event)(dev, ev_chnl);
+}
+
+int rte_emudev_unsubscribe_event(uint16_t dev_id,
+		const rte_emudev_event_chnl_t ev_chnl)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!ev_chnl) {
+		RTE_EMUDEV_LOG(ERR, "Failed to unsubscribe because of NULL"
+			" event channel for device %u\n", dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->unsubscribe_event, -ENOTSUP);
+
+	return (*dev->dev_ops->unsubscribe_event)(dev, ev_chnl);
+}
+
+int rte_emudev_get_dev_info(uint16_t dev_id, struct rte_emudev_info *info)
+{
+	struct rte_emudev *dev;
+	struct rte_emudev_info *dev_info;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!info) {
+		RTE_EMUDEV_LOG(ERR, "NULL device info for device %u\n",
+			dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+	dev_info = &dev->dev_info;
+
+	strcpy(info->dev_type, dev_info->dev_type);
+	info->max_qp_num = dev_info->max_qp_num;
+	info->region_num = dev_info->region_num;
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_info_get, -ENOTSUP);
+
+	return (*dev->dev_ops->dev_info_get)(dev, info->dev_priv);
+}
+
+int rte_emudev_get_mem_table(uint16_t dev_id, rte_emudev_mem_table_t *tb)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!tb) {
+		RTE_EMUDEV_LOG(ERR, "NULL memory table for device %u\n",
+			dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->get_mem_table, -ENOTSUP);
+
+	return (*dev->dev_ops->get_mem_table)(dev, tb);
+}
+
+int rte_emudev_get_queue_info(uint16_t dev_id, uint32_t queue,
+	struct rte_emudev_q_info *info)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!info) {
+		RTE_EMUDEV_LOG(ERR, "NULL queue info for queue %d"
+			" of device %u\n", queue, dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (queue >= dev->dev_info.max_qp_num * 2) {
+		RTE_EMUDEV_LOG(ERR, "Queue index of device %u exceeds max\n",
+			dev_id);
+		return -EINVAL;
+	}
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->get_queue_info, -ENOTSUP);
+
+	memset(info, 0, sizeof(*info));
+
+	return (*dev->dev_ops->get_queue_info)(dev, queue, info);
+}
+
+int rte_emudev_get_irq_info(uint16_t dev_id, uint32_t vector,
+	struct rte_emudev_irq_info *info)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!info) {
+		RTE_EMUDEV_LOG(ERR, "NULL irq info for vector %u"
+			" of device %u\n", vector, dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->get_irq_info, -ENOTSUP);
+
+	memset(info, 0, sizeof(*info));
+
+	return (*dev->dev_ops->get_irq_info)(dev, vector, info);
+}
+
+int rte_emudev_get_db_info(uint16_t dev_id, uint32_t doorbell,
+	struct rte_emudev_db_info *info)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!info) {
+		RTE_EMUDEV_LOG(ERR, "NULL doorbell info of device %u"
+			" for id %u\n", dev_id, doorbell);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->get_db_info, -ENOTSUP);
+
+	memset(info, 0, sizeof(*info));
+
+	return (*dev->dev_ops->get_db_info)(dev, doorbell, info);
+}
+
+int rte_emudev_set_attr(uint16_t dev_id, const char *attr_name,
+	rte_emudev_attr_t attr)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!attr_name) {
+		RTE_EMUDEV_LOG(ERR, "NULL attribute name of device %u "
+			"for set\n", dev_id);
+		return -EINVAL;
+	}
+
+	if (!attr) {
+		RTE_EMUDEV_LOG(ERR, "NULL attribute of device %u "
+			"for set_attr\n", dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->set_attr, -ENOTSUP);
+
+	return (*dev->dev_ops->set_attr)(dev, attr_name, attr);
+}
+
+int rte_emudev_get_attr(uint16_t dev_id, const char *attr_name,
+	rte_emudev_attr_t attr)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!attr_name) {
+		RTE_EMUDEV_LOG(ERR, "NULL attribute name of device %u "
+			"for get\n", dev_id);
+		return -EINVAL;
+	}
+
+	if (!attr) {
+		RTE_EMUDEV_LOG(ERR, "NULL attribute of device %u "
+			"for get_attr\n", dev_id);
+		return -EINVAL;
+	}
+
+	dev = &rte_emu_devices[dev_id];
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->get_attr, -ENOTSUP);
+
+	return (*dev->dev_ops->get_attr)(dev, attr_name, attr);
+}
+
+int rte_emudev_region_map(uint16_t dev_id, uint32_t index,
+	uint64_t *region_size, uint64_t *base_addr)
+{
+	struct rte_emudev *dev;
+
+	RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id);
+
+	if (!region_size || !base_addr)
+		return -EINVAL;
+
+	dev = &rte_emu_devices[dev_id];
+
+	if (index >= dev->dev_info.region_num) {
+		RTE_EMUDEV_LOG(ERR, "Wrong region index for device %u\n",
+			dev_id);
+		return -EINVAL;
+	}
+
+	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->region_map, -ENOTSUP);
+
+	memset(region_size, 0, sizeof(*region_size));
+	memset(base_addr, 0, sizeof(*base_addr));
+
+	return (*dev->dev_ops->region_map)(dev, index, region_size,
+		base_addr);
+}
+
+RTE_LOG_REGISTER(rte_emudev_logtype, lib.emudev, INFO);
diff --git a/lib/librte_emudev/rte_emudev.h b/lib/librte_emudev/rte_emudev.h
new file mode 100644
index 0000000000..df31c631b7
--- /dev/null
+++ b/lib/librte_emudev/rte_emudev.h
@@ -0,0 +1,410 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+
+#ifndef _RTE_EMUDEV_H_
+#define _RTE_EMUDEV_H_
+
+#include <rte_config.h>
+#include <rte_dev.h>
+#include <rte_compat.h>
+
+#define RTE_EMU_NAME_MAX_LEN RTE_DEV_NAME_MAX_LEN
+
+extern int rte_emudev_logtype;
+
+#define RTE_EMUDEV_LOG(level, ...) \
+	rte_log(RTE_LOG_ ## level, rte_emudev_logtype, "" __VA_ARGS__)
+
+/* Macros to check for valid dev id */
+#define RTE_EMU_CHECK_VALID_DEVID_ERR(dev_id) do { \
+	if (!rte_emudev_is_valid_id(dev_id)) { \
+		RTE_EMUDEV_LOG(ERR, "Invalid dev_id=%u\n", dev_id); \
+		return -ENODEV; \
+	} \
+} while (0)
+
+#define RTE_EMU_CHECK_VALID_DEVID(dev_id) do { \
+	if (!rte_emudev_is_valid_id(dev_id)) { \
+		RTE_EMUDEV_LOG(ERR, "Invalid dev_id=%u\n", dev_id); \
+		return; \
+	} \
+} while (0)
+
+typedef void *rte_emudev_obj_t;
+typedef void *rte_emudev_attr_t;
+typedef void *rte_emudev_mem_table_t;
+typedef void *rte_emudev_event_chnl_t;
+
+struct rte_emudev;
+
+/** 
+ * Global structure used for maintaining state
+ * of allocated emu devices
+ */
+struct rte_emudev_global {
+	uint8_t nb_devs;	/**< Number of devices found */
+};
+
+struct rte_emudev_info {
+	char dev_type[RTE_EMU_NAME_MAX_LEN];
+	uint32_t region_num;
+	uint32_t max_qp_num;
+	rte_emudev_obj_t dev_priv;
+};
+
+struct rte_emudev_q_info {
+	uint64_t base;
+	uint64_t size;
+	int doorbell_id;
+	int irq_vector;
+};
+
+struct rte_emudev_irq_info {
+	uint32_t vector;
+	bool enable;
+	int eventfd;
+};
+
+struct rte_emudev_db_info {
+	uint32_t id;
+	uint32_t flag;
+#define RTE_EMUDEV_DB_FD	(0x1 << 0)
+#define RTE_EMUDEV_DB_MEM	(0x1 << 1)
+	union {
+		int eventfd;
+		struct {
+			uint64_t base;
+			uint64_t size;
+		} mem;
+	} data;
+};
+
+struct rte_emudev_ops {
+	int (*dev_start)(struct rte_emudev *dev);
+	void (*dev_stop)(struct rte_emudev *dev);
+	int (*dev_configure)(struct rte_emudev *dev,
+		struct rte_emudev_info *conf);
+	int (*dev_close)(struct rte_emudev *dev);
+	int (*dev_info_get)(struct rte_emudev *dev, rte_emudev_obj_t info);
+	int (*subscribe_event)(struct rte_emudev *dev,
+		rte_emudev_event_chnl_t ev_chnl);
+	int (*unsubscribe_event)(struct rte_emudev *dev,
+		rte_emudev_event_chnl_t ev_chnl);
+	int (*get_mem_table)(struct rte_emudev *dev,
+		rte_emudev_mem_table_t *tb);
+	int (*get_queue_info)(struct rte_emudev *dev, uint32_t queue,
+		struct rte_emudev_q_info *info);
+	int (*get_irq_info)(struct rte_emudev *dev, uint32_t vector,
+		struct rte_emudev_irq_info *info);
+	int (*get_db_info)(struct rte_emudev *dev, uint32_t doorbell,
+		struct rte_emudev_db_info *info);
+	int (*get_attr)(struct rte_emudev *dev, const char *attr_name,
+		rte_emudev_attr_t attr);
+	int (*set_attr)(struct rte_emudev *dev, const char *attr_name,
+		rte_emudev_attr_t attr);
+	int (*region_map)(struct rte_emudev *dev, uint32_t index,
+		uint64_t *region_size, uint64_t *base_addr);
+	int (*dev_selftest)(uint16_t dev_id);
+};
+
+struct rte_emudev {
+	char name[RTE_EMU_NAME_MAX_LEN];
+	uint16_t dev_id;
+	int numa_node;
+	int started;
+	struct rte_device *device;
+	struct rte_emudev_info dev_info;
+	const struct rte_emudev_ops *dev_ops;
+	void *priv_data;
+	void *backend_priv;
+} __rte_cache_aligned;
+
+/**
+ * Note that 'rte_emudev_allocate', 'rte_emudev_release' and
+ * 'rte_emudev_allocated' should be called by emulated device
+ * provider.
+ */
+
+/**
+ * Allocate a new emudev for an emulation device and returns the pointer
+ * to the emudev.
+ *
+ * @param name
+ *   Name of the emudev
+ * @return
+ *   Pointer to rte_emudev on success, NULL on failure
+ */
+__rte_experimental
+struct rte_emudev *rte_emudev_allocate(const char *name);
+
+/**
+ * Release the emudev.
+ *
+ * @param dev
+ *   The emulated device
+ * @return
+ *   - 0: Success, device release
+ *   - <0: Failure on release
+ */
+__rte_experimental
+int rte_emudev_release(struct rte_emudev *dev);
+
+/**
+ * Find an emudev using name.
+ *
+ * @param name
+ *   Name of the emudev
+ * @return
+ *   Pointer to rte_emudev on success, NULL on failure
+ */
+__rte_experimental
+struct rte_emudev *rte_emudev_allocated(const char *name);
+
+/**
+ * Start an emulation device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @return
+ *   - 0: Success, device start
+ *   - <0: Failure on start
+ */
+__rte_experimental
+int rte_emudev_start(uint16_t dev_id);
+
+/**
+ * Stop an emulation device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ */
+__rte_experimental
+void rte_emudev_stop(uint16_t dev_id);
+
+/**
+ * Configure an emulation device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param dev_conf
+ *   Device configure info
+ * @return
+ *   - 0: Success, device configured
+ *   - <0: Failure on configure
+ */
+__rte_experimental
+int rte_emudev_configure(uint16_t dev_id, struct rte_emudev_info *dev_conf);
+
+/**
+ * Close an emulation device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @return
+ *   - 0: Success, device close
+ *   - <0: Failure on close
+ */
+__rte_experimental
+int rte_emudev_close(uint16_t dev_id);
+
+/* Note that below APIs should only be called by back-end (data path) driver */
+
+/**
+ * Back-end driver subscribes events of the emulated device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param ev_chnl
+ *   Event channel that events should be passed to
+ * @return
+ *   - 0: Success, event subscribed
+ *   - <0: Failure on subscribe
+ */
+__rte_experimental
+int rte_emudev_subscribe_event(uint16_t dev_id,
+		const rte_emudev_event_chnl_t ev_chnl);
+
+/**
+ * Back-end driver unsubscribes events of the emulated device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param set
+ *   Event channel that events should be passed to
+ * @return
+ *   - 0: Success, event unsubscribed
+ *   - <0: Failure on unsubscribe
+ */
+__rte_experimental
+int rte_emudev_unsubscribe_event(uint16_t dev_id,
+		const rte_emudev_event_chnl_t ev_chnl);
+
+/**
+ * Get the total number of emulated devices that have been
+ * successfully initialised.
+ *
+ * @return
+ *   The total number of usable emudev.
+ */
+__rte_experimental
+uint8_t rte_emudev_count(void);
+
+/**
+ * Get the device identifier for the named emulated device.
+ *
+ * @param name
+ *   Emulated device name to select the device identifier.
+ *
+ * @return
+ *   - 0: Success, emulated device identifier returned
+ *   - <0: Failure on unsubscribe
+ */
+__rte_experimental
+int rte_emudev_get_dev_id(const char *name);
+
+/**
+ * Back-end driver gets the device info of the emulated device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @return
+ *   - 0: Success, emulated device info updated
+ *   - <0: Failure on get device information
+ */
+__rte_experimental
+int rte_emudev_get_dev_info(uint16_t dev_id, struct rte_emudev_info *info);
+
+/**
+ * Get the memory table content and operations of the emulated device.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @return
+ *   - 0: Success, memory table of emulated device updated
+ *   - <0: Failure on get memory table
+ */
+__rte_experimental
+int rte_emudev_get_mem_table(uint16_t dev_id, rte_emudev_mem_table_t *tb);
+
+/**
+ * Get queue info of the emudev.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param queue
+ *   Queue ID of emudev
+ * @return
+ *   - 0: Success, queue information of emulated device updated
+ *   - <0: Failure on get queue information
+ */
+__rte_experimental
+int rte_emudev_get_queue_info(uint16_t dev_id, uint32_t queue,
+	struct rte_emudev_q_info *info);
+
+/**
+ * Get irq info of the emudev.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param vector
+ *   Interrupt vector
+ * @return
+ *   - 0: Success, irq information of emulated device updated
+ *   - <0: Failure on get irq information
+ */
+__rte_experimental
+int rte_emudev_get_irq_info(uint16_t dev_id, uint32_t vector,
+	struct rte_emudev_irq_info *info);
+
+/**
+ * Get doorbell info of the emudev.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param doorbell
+ *   Doorbell ID
+ * @return
+ *   - 0: Success, doorbell information of emulated device updated
+ *   - <0: Failure on get doorbell information
+ */
+__rte_experimental
+int rte_emudev_get_db_info(uint16_t dev_id, uint32_t doorbell,
+	struct rte_emudev_db_info *info);
+
+/**
+ * Set attribute of the emudev.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param attr_name
+ *   Opaque object representing an attribute in implementation.
+ * @param attr
+ *   Pointer to attribute
+ * @return
+ *   - 0: Success, attribute set
+ *   - <0: Failure on attribute set
+ */
+__rte_experimental
+int rte_emudev_set_attr(uint16_t dev_id, const char *attr_name,
+	rte_emudev_attr_t attr);
+
+/**
+ * Get attribute of the emudev.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param attr_name
+ *   Opaque object representing an attribute in implementation.
+ * @return
+ *   - 0: Success, attribute of emulated device updated
+ *   - <0: Failure on attribute get
+ */
+__rte_experimental
+int rte_emudev_get_attr(uint16_t dev_id, const char *attr_name,
+	rte_emudev_attr_t attr);
+
+/**
+ * Back-end driver maps a region to the emulated device.
+ * Region name identifies the meaning of the region and the emulated
+ * device and the back-end driver should have the same definition of
+ * region name and its meaning.
+ *
+ * @param dev_id
+ *   Device ID of emudev
+ * @param index
+ *   Region index
+ * @param attr
+ *   Pointer to attribute
+ * @return
+ *   - 0: Success, region mapped
+ *   - <0: Failure on region map
+ */
+__rte_experimental
+int rte_emudev_region_map(uint16_t dev_id, uint32_t index,
+	uint64_t *region_size, uint64_t *base_addr);
+
+/**
+ * Trigger the emudev self test.
+ *
+ * @param dev_id
+ *   The identifier of the device
+ * @return
+ *   - 0: Selftest successful
+ *   - <0: Failure on selftest
+ */
+__rte_experimental
+int rte_emudev_selftest(uint16_t dev_id);
+
+/**
+ * Check if an emudev device ID is valid.
+ *
+ * @param dev_id
+ *   The identifier of the device
+ * @return
+ *   0 on failure, 1 on success
+ */
+__rte_experimental
+int rte_emudev_is_valid_id(uint16_t dev_id);
+
+#endif /* _RTE_EMUDEV_H_ */
diff --git a/lib/librte_emudev/rte_emudev_vdev.h b/lib/librte_emudev/rte_emudev_vdev.h
new file mode 100644
index 0000000000..85f534b4bd
--- /dev/null
+++ b/lib/librte_emudev/rte_emudev_vdev.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+
+#ifndef _RTE_EMUDEV_VDEV_H_
+#define _RTE_EMUDEV_VDEV_H_
+
+#include <rte_bus_vdev.h>
+#include <rte_malloc.h>
+
+#include "rte_emudev.h"
+
+/**
+ * @internal
+ * Allocates a new emudev instance for an emulated device and
+ * returns the pointer to that instance for the driver to use.
+ *
+ * @param dev
+ *	Pointer to virtual device
+ *
+ * @param private_data_size
+ *	Size of private data structure
+ *
+ * @return
+ *	A pointer to a rte_emudev or NULL if allocation failed.
+ */
+static inline struct rte_emudev *
+rte_emu_vdev_allocate(struct rte_vdev_device *dev, size_t private_data_size)
+{
+	struct rte_emudev *emu_dev;
+	const char *name = rte_vdev_device_name(dev);
+
+	emu_dev = rte_emudev_allocate(name);
+	if (!emu_dev)
+		return NULL;
+
+	if (private_data_size) {
+		emu_dev->priv_data = rte_zmalloc_socket(name,
+			private_data_size, RTE_CACHE_LINE_SIZE,
+			dev->device.numa_node);
+		if (!emu_dev->priv_data) {
+			rte_emudev_release(emu_dev);
+			return NULL;
+		}
+	}
+
+	emu_dev->device = &dev->device;
+	emu_dev->numa_node = dev->device.numa_node;
+
+	return emu_dev;
+}
+
+#endif /* _RTE_EMUDEV_VDEV_H_ */
diff --git a/lib/librte_emudev/version.map b/lib/librte_emudev/version.map
new file mode 100644
index 0000000000..f800b4c21c
--- /dev/null
+++ b/lib/librte_emudev/version.map
@@ -0,0 +1,27 @@
+EXPERIMENTAL {
+	global:
+
+	rte_emudev_allocate;
+	rte_emudev_release;
+	rte_emudev_allocated;
+	rte_emudev_start;
+	rte_emudev_stop;
+	rte_emudev_configure;
+	rte_emudev_close;
+	rte_emudev_subscribe_event;
+	rte_emudev_unsubscribe_event;
+	rte_emudev_count;
+	rte_emudev_get_dev_id;
+	rte_emudev_get_dev_info;
+	rte_emudev_get_mem_table;
+	rte_emudev_get_queue_info;
+	rte_emudev_get_irq_info;
+	rte_emudev_get_db_info;
+	rte_emudev_set_attr;
+	rte_emudev_get_attr;
+	rte_emudev_region_map;
+	rte_emudev_selftest;
+	rte_emudev_is_valid_id;
+
+	local: *;
+};
diff --git a/lib/meson.build b/lib/meson.build
index b7fbfcc95b..6dd07fb73e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -28,7 +28,7 @@ libraries = [
 	'rib', 'reorder', 'sched', 'security', 'stack', 'vhost',
 	# ipsec lib depends on net, crypto and security
 	'ipsec',
-	'vfio_user',
+	'vfio_user', 'emudev',
 	#fib lib depends on rib
 	'fib',
 	# add pkt framework libs which use other libs from above
