diff --git a/Makefile.tools b/Makefile.tools
index f4b951474..a99d768dc 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -87,7 +87,7 @@ noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \
 					tools/smp-tester tools/hci-tester \
 					tools/rfcomm-tester tools/bnep-tester \
 					tools/userchan-tester tools/iso-tester \
-					tools/mesh-tester
+					tools/mesh-tester tools/ioctl-tester
 
 emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
 				emulator/serial.h emulator/serial.c \
@@ -213,6 +213,15 @@ tools_iso_tester_SOURCES = tools/iso-tester.c monitor/bt.h \
 				emulator/smp.c
 tools_iso_tester_LDADD = lib/libbluetooth-internal.la \
 				src/libshared-glib.la $(GLIB_LIBS)
+
+tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \
+				emulator/hciemu.h emulator/hciemu.c \
+				emulator/vhci.h emulator/vhci.c \
+				emulator/btdev.h emulator/btdev.c \
+				emulator/bthost.h emulator/bthost.c \
+				emulator/smp.c
+tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \
+				src/libshared-glib.la $(GLIB_LIBS)
 endif
 
 if TOOLS
diff --git a/tools/ioctl-tester.c b/tools/ioctl-tester.c
new file mode 100644
index 000000000..098b58608
--- /dev/null
+++ b/tools/ioctl-tester.c
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2022  Intel Corporation.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdbool.h>
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/mgmt.h"
+#include "lib/hci.h"
+#include "lib/hci_lib.h"
+
+#include "monitor/bt.h"
+#include "emulator/bthost.h"
+#include "emulator/hciemu.h"
+
+#include "src/shared/tester.h"
+#include "src/shared/mgmt.h"
+#include "src/shared/util.h"
+
+struct test_data {
+	const void *test_data;
+	int sock_fd;
+	struct hciemu *hciemu;
+	enum hciemu_type hciemu_type;
+	uint8_t client_num;
+	uint16_t hci_dev_id;
+
+	struct mgmt *mgmt;
+	uint16_t mgmt_index;
+	struct mgmt *mgmt_alt;
+	unsigned int mgmt_alt_ev_id;
+
+	uint16_t handle;
+	uint16_t acl_handle;
+	GIOChannel *io;
+	unsigned int io_id[2];
+	int step;
+	bool reconnect;
+
+	int unmet_conditions;
+};
+
+struct ioctl_data {
+	uint32_t cmd;
+	const uint32_t opt;
+	const void *param;
+	int (*cmd_param_func)(void *param, uint32_t *length);
+	int expected_ioctl_err;
+	const void *block_bdaddr;
+	const void *expected_data;
+	int (*expect_data_check_func)(const void *param, uint32_t length);
+};
+
+static void print_debug(const char *str, void *user_data)
+{
+	const char *prefix = user_data;
+
+	tester_print("%s%s", prefix, str);
+}
+
+static void test_add_condition(struct test_data *data)
+{
+	data->unmet_conditions++;
+
+	tester_print("Test condition added, total %d", data->unmet_conditions);
+}
+
+static void test_condition_complete(struct test_data *data)
+{
+	data->unmet_conditions--;
+
+	tester_print("Test condition complete, %d left",
+						data->unmet_conditions);
+
+	if (data->unmet_conditions > 0)
+		return;
+
+	tester_test_passed();
+}
+
+static int update_hci_dev_id(struct test_data *data)
+{
+	struct hci_dev_list_req *dl;
+	struct hci_dev_req *dr;
+	int ret = 0;
+
+	dl = malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(uint16_t));
+	if (!dl)
+		return -ENOMEM;
+
+	dl->dev_num = HCI_MAX_DEV;
+	dr = dl->dev_req;
+
+	if (ioctl(data->sock_fd, HCIGETDEVLIST, (void *) dl) < 0) {
+		ret = -EIO;
+		goto exit;
+	}
+
+	if (dl->dev_num != 1) {
+		tester_warn("dev num mismatch returned %d:expected 1",
+								dl->dev_num);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	data->hci_dev_id = dr->dev_id;
+	tester_print("HCI device id: %d", data->hci_dev_id);
+
+exit:
+	free(dl);
+	return ret;
+}
+
+static void read_info_callback(uint8_t status, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct mgmt_rp_read_info *rp = param;
+	char addr[18];
+	uint16_t manufacturer;
+	uint32_t supported_settings, current_settings;
+
+	tester_print("Read Info callback");
+	tester_print("  Status: 0x%02x", status);
+
+	if (status || !param) {
+		tester_pre_setup_failed();
+		return;
+	}
+
+	ba2str(&rp->bdaddr, addr);
+	manufacturer = btohs(rp->manufacturer);
+	supported_settings = btohl(rp->supported_settings);
+	current_settings = btohl(rp->current_settings);
+
+	tester_print("  Address: %s", addr);
+	tester_print("  Version: 0x%02x", rp->version);
+	tester_print("  Manufacturer: 0x%04x", manufacturer);
+	tester_print("  Supported settings: 0x%08x", supported_settings);
+	tester_print("  Current settings: 0x%08x", current_settings);
+	tester_print("  Class: 0x%02x%02x%02x",
+			rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]);
+	tester_print("  Name: %s", rp->name);
+	tester_print("  Short name: %s", rp->short_name);
+
+	if (strcmp(hciemu_get_address(data->hciemu), addr)) {
+		tester_pre_setup_failed();
+		return;
+	}
+
+	tester_pre_setup_complete();
+}
+
+static void index_added_callback(uint16_t index, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Index Added callback");
+	tester_print("  Index: 0x%04x", index);
+
+	data->mgmt_index = index;
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_INFO, data->mgmt_index, 0, NULL,
+					read_info_callback, NULL, NULL);
+}
+
+static void index_removed_callback(uint16_t index, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Index Removed callback");
+	tester_print("  Index: 0x%04x", index);
+
+	if (index != data->mgmt_index)
+		return;
+
+	mgmt_unregister_index(data->mgmt, data->mgmt_index);
+	mgmt_unregister_index(data->mgmt_alt, data->mgmt_index);
+
+	mgmt_unref(data->mgmt);
+	data->mgmt = NULL;
+
+	mgmt_unref(data->mgmt_alt);
+	data->mgmt_alt = NULL;
+
+	tester_post_teardown_complete();
+}
+
+static void read_index_list_callback(uint8_t status, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Read Index List callback");
+	tester_print("  Status: 0x%02x", status);
+
+	if (status || !param) {
+		tester_pre_setup_failed();
+		return;
+	}
+
+	mgmt_register(data->mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,
+					index_added_callback, NULL, NULL);
+
+	mgmt_register(data->mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE,
+					index_removed_callback, NULL, NULL);
+
+	data->hciemu = hciemu_new(data->hciemu_type);
+	if (!data->hciemu) {
+		tester_warn("Failed to setup HCI emulation");
+		tester_pre_setup_failed();
+	}
+
+	if (tester_use_debug())
+		hciemu_set_debug(data->hciemu, print_debug, "hciemu: ", NULL);
+
+	tester_print("New hciemu instance created");
+
+	data->sock_fd = hci_open_dev(0);
+	if (data->sock_fd < 0) {
+		tester_warn("Failed to open socket for ioctl");
+		tester_pre_setup_failed();
+		return;
+	}
+
+	update_hci_dev_id(data);
+}
+
+static void test_pre_setup(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+
+	data->mgmt = mgmt_new_default();
+	if (!data->mgmt) {
+		tester_warn("Failed to setup mgmt interface");
+		tester_pre_setup_failed();
+		return;
+	}
+
+	data->mgmt_alt = mgmt_new_default();
+	if (!data->mgmt_alt) {
+		tester_warn("Failed to setup alternate management interface");
+		tester_pre_setup_failed();
+
+		mgmt_unref(data->mgmt);
+		data->mgmt = NULL;
+		return;
+	}
+
+
+	if (tester_use_debug()) {
+		mgmt_set_debug(data->mgmt, print_debug, "mgmt: ", NULL);
+		mgmt_set_debug(data->mgmt_alt, print_debug, "mgmt-alt: ", NULL);
+	}
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL,
+					read_index_list_callback, NULL, NULL);
+}
+
+static void test_post_teardown(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+
+	if (data->sock_fd >= 0) {
+		tester_print("Socket closed");
+		hci_close_dev(data->sock_fd);
+	}
+
+	hciemu_unref(data->hciemu);
+	data->hciemu = NULL;
+}
+
+static void test_data_free(void *test_data)
+{
+	struct test_data *data = test_data;
+
+	// TODO: free any data allocated during pre-setup
+
+	free(data);
+}
+
+#define test_ioctl_full(name, data, setup, func, num) \
+	do { \
+		struct test_data *user; \
+		user = new0(struct test_data, 1); \
+		if (!user) \
+			break; \
+		user->hciemu_type = HCIEMU_TYPE_BREDRLE; \
+		user->test_data = data; \
+		user->client_num = num; \
+		tester_add_full(name, data, \
+				test_pre_setup, setup, func, NULL, \
+				test_post_teardown, 2, user, test_data_free); \
+	} while (0)
+
+#define test_ioctl(name, data, setup, func) \
+	test_ioctl_full(name, data, setup, func, 1)
+
+static void setup_powered_callback(uint8_t status, uint16_t length,
+					const void *param, void *user_data)
+{
+	if (status != MGMT_STATUS_SUCCESS) {
+		tester_setup_failed();
+		return;
+	}
+
+	tester_print("Controller powered on");
+
+	tester_setup_complete();
+}
+
+static void setup_powered(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	unsigned char param[] = { 0x01 };
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_BONDABLE, data->mgmt_index,
+				sizeof(param), param, NULL, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_CONNECTABLE, data->mgmt_index,
+				sizeof(param), param, NULL, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_SSP, data->mgmt_index,
+				sizeof(param), param, NULL, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
+					sizeof(param), param,
+					setup_powered_callback, NULL, NULL);
+}
+
+static void setup_add_block_bdaddr(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+
+	if (!ioctl_data->block_bdaddr) {
+		tester_warn("Invalid test data: block bdaddr");
+		tester_setup_failed();
+		return;
+	}
+
+	if (ioctl(data->sock_fd, HCIBLOCKADDR, ioctl_data->block_bdaddr) < 0) {
+		tester_warn("Failed to add block bdaddr");
+		tester_setup_failed();
+		return;
+	}
+
+	tester_print("Added block BDADDR");
+
+	tester_setup_complete();
+}
+
+static int conn_list_empty_check_func(const void *param, uint32_t length)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+	const struct hci_conn_list_req *cl_input = ioctl_data->expected_data;
+	const struct hci_conn_list_req *cl = param;
+
+	if (cl->conn_num != cl_input->conn_num)
+		return -1;
+
+	return 0;
+}
+
+static int conn_info_cmd_param_func(void *param, uint32_t *length)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+	const struct hci_conn_info_req *cr_input = ioctl_data->param;
+	struct hci_conn_info_req *cr = param;
+
+	memcpy(&cr->bdaddr, &cr_input->bdaddr, sizeof(bdaddr_t));
+	cr->type = cr_input->type;
+
+	return 0;
+}
+
+static int auth_info_cmd_param_func(void *param, uint32_t *length)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+	const struct hci_auth_info_req *ar_input = ioctl_data->param;
+	struct hci_auth_info_req *ar = param;
+
+	memcpy(&ar->bdaddr, &ar_input->bdaddr, sizeof(bdaddr_t));
+	if (ar_input->type)
+		ar->type = ar_input->type;
+
+	return 0;
+}
+
+static const struct ioctl_data dev_down = {
+	.cmd = HCIDEVDOWN,
+};
+
+static const struct hci_dev_list_req dev_list_1 = {
+	.dev_num = 0x01,
+	.dev_req = {{
+		.dev_id = 0x00,
+		.dev_opt = 0x04,
+	}},
+};
+
+static const struct ioctl_data dev_list = {
+	.cmd = HCIGETDEVLIST,
+	.expected_data = (void *)&dev_list_1,
+};
+
+static const struct hci_dev_list_req dev_list_invalid_1_param = {
+	.dev_num = 0x00,
+};
+
+static const struct ioctl_data dev_list_invalid_1 = {
+	.cmd = HCIGETDEVLIST,
+	.param = (void *)&dev_list_invalid_1_param,
+	.expected_ioctl_err = EINVAL,
+};
+
+static const struct ioctl_data dev_info = {
+	.cmd = HCIGETDEVINFO,
+};
+
+static const struct ioctl_data reset_stat = {
+	.cmd = HCIDEVRESTAT,
+};
+
+static const struct ioctl_data set_link_mode_master = {
+	.cmd = HCISETLINKMODE,
+	.opt = HCI_LM_MASTER,
+};
+
+static const struct ioctl_data set_link_mode_accept = {
+	.cmd = HCISETLINKMODE,
+	.opt = HCI_LM_ACCEPT,
+};
+
+static const struct ioctl_data set_pkt_type_dm = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_DM1 | HCI_DM3 | HCI_DM5,
+};
+
+static const struct ioctl_data set_pkt_type_dh = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_DH1 | HCI_DH3 | HCI_DH5,
+};
+
+static const struct ioctl_data set_pkt_type_hv = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_HV1 | HCI_HV2 | HCI_HV3,
+};
+
+static const struct ioctl_data set_pkt_type_2dh = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_2DH1 | HCI_2DH3 | HCI_2DH5,
+};
+
+static const struct ioctl_data set_pkt_type_3dh = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_3DH1 | HCI_3DH3 | HCI_3DH5,
+};
+
+static const struct ioctl_data set_pkt_type_all = {
+	.cmd = HCISETPTYPE,
+	.opt = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5 |
+	       HCI_HV1 | HCI_HV2 | HCI_HV3 | HCI_2DH1 | HCI_2DH3 | HCI_2DH5 |
+	       HCI_3DH1 | HCI_3DH3 | HCI_3DH5,
+};
+
+static const struct ioctl_data set_acl_mtu_1 = {
+	.cmd = HCISETACLMTU,
+	.opt = 0x1 | (0x3FE << 16),
+};
+
+static const struct ioctl_data set_acl_mtu_2 = {
+	.cmd = HCISETACLMTU,
+	.opt = 0x4 | (0x63 << 16),
+};
+
+static const struct ioctl_data set_sco_mtu_1 = {
+	.cmd = HCISETSCOMTU,
+	.opt = 0x1 | (0x3FE << 16),
+};
+
+static const struct ioctl_data set_sco_mtu_2 = {
+	.cmd = HCISETSCOMTU,
+	.opt = 0x4 | (0x63 << 16),
+};
+
+static const uint8_t bdaddr1[] = {
+	0x11, 0x22, 0x33, 0x44, 0x55, 0x66
+};
+
+static const struct ioctl_data block_bdaddr_success = {
+	.cmd = HCIBLOCKADDR,
+	.param = bdaddr1,
+};
+
+static const struct ioctl_data block_bdaddr_fail = {
+	.cmd = HCIBLOCKADDR,
+	.param = bdaddr1,
+	.expected_ioctl_err = EEXIST,
+	.block_bdaddr = bdaddr1,
+};
+
+static const struct ioctl_data unblock_bdaddr_success = {
+	.cmd = HCIUNBLOCKADDR,
+	.param = bdaddr1,
+	.block_bdaddr = bdaddr1,
+};
+
+static const struct ioctl_data unblock_bdaddr_fail = {
+	.cmd = HCIUNBLOCKADDR,
+	.param = bdaddr1,
+	.expected_ioctl_err = ENOENT,
+};
+
+static const struct hci_conn_list_req conn_list_empty = {
+	.dev_id = 0x00,
+	.conn_num = 0x00,
+};
+
+static const struct ioctl_data conn_list_no_conn = {
+	.cmd = HCIGETCONNLIST,
+	.expected_data = (void *)&conn_list_empty,
+	.expect_data_check_func = conn_list_empty_check_func,
+};
+
+static const struct hci_conn_list_req conn_list_req_1 = {
+	.dev_id = 0x00,
+	.conn_num = 0x01,
+	.conn_info = {{
+		.handle = 0x002a,
+		.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+		.type = 0x01,
+		.out = 0x00,
+		.state = 0x0001,
+		.link_mode = 0x00000000,
+	}},
+};
+
+static const struct ioctl_data conn_list = {
+	.cmd = HCIGETCONNLIST,
+	.expected_data = (void *)&conn_list_req_1,
+};
+
+static const struct hci_conn_info_req conn_info_req = {
+	.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+	.type = ACL_LINK,
+	.conn_info = {{
+		.handle = 0x002a,
+		.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+		.type = 0x01,
+		.out = 0x00,
+		.state = 0x0001,
+		.link_mode = 0x00000000,
+	}},
+};
+
+static const struct hci_conn_info_req conn_info_req_acl = {
+	.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+	.type = ACL_LINK,
+};
+
+static const struct hci_conn_info_req conn_info_req_sco = {
+	.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+	.type = SCO_LINK,
+};
+
+static const struct ioctl_data conn_info = {
+	.cmd = HCIGETCONNINFO,
+	.param = (void *)&conn_info_req_acl,
+	.cmd_param_func = conn_info_cmd_param_func,
+	.expected_data = (void *)&conn_info_req,
+};
+
+static const struct ioctl_data conn_info_no_conn = {
+	.cmd = HCIGETCONNINFO,
+	.param = (void *)&conn_info_req_acl,
+	.expected_ioctl_err = ENOENT,
+	.cmd_param_func = conn_info_cmd_param_func,
+};
+
+static const struct ioctl_data conn_info_wrong_type = {
+	.cmd = HCIGETCONNINFO,
+	.param = (void *)&conn_info_req_sco,
+	.expected_ioctl_err = ENOENT,
+	.cmd_param_func = conn_info_cmd_param_func,
+};
+
+static const struct hci_auth_info_req auth_info_req = {
+	.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+};
+
+static const struct hci_auth_info_req auth_info_connected = {
+	.bdaddr = {{ 0x00, 0x00, 0x01, 0x01, 0xaa, 0x00 }},
+	.type = 0x04,
+};
+
+static const struct ioctl_data auth_info_no_conn = {
+	.cmd = HCIGETAUTHINFO,
+	.param = (void *)&auth_info_req,
+	.expected_ioctl_err = ENOENT,
+	.cmd_param_func = auth_info_cmd_param_func,
+};
+
+static const struct ioctl_data auth_info = {
+	.cmd = HCIGETAUTHINFO,
+	.param = (void *)&auth_info_req,
+	.cmd_param_func = auth_info_cmd_param_func,
+	.expected_data = (void *)&auth_info_connected,
+};
+
+/* Allocate the command request parameters based on the command.
+ * returns the allocated request buffer and its length
+ */
+static int test_alloc_cmd_param(void **req, uint32_t *req_len)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+	struct hci_dev_req *dr = NULL;
+	struct hci_dev_info *di = NULL;
+	struct hci_dev_list_req *dl = NULL;
+	struct hci_conn_list_req *cl = NULL;
+	struct hci_conn_info *ci = NULL;
+	struct hci_conn_info_req *cr = NULL;
+	struct hci_auth_info_req *ar = NULL;
+	bdaddr_t *bdaddr = NULL;
+	uint32_t len;
+
+	switch (ioctl_data->cmd) {
+	case HCISETAUTH:
+	case HCISETENCRYPT:
+	case HCISETLINKMODE:
+	case HCISETPTYPE:
+	case HCISETACLMTU:
+	case HCISETSCOMTU:
+		len = sizeof(*dr);
+		dr = malloc(len);
+		if (!dr)
+			return -ENOMEM;
+		memset(dr, 0, len);
+		dr->dev_id = data->hci_dev_id;
+		dr->dev_opt = ioctl_data->opt;
+		*req = dr;
+		*req_len = len;
+		break;
+	case HCIGETDEVINFO:
+		len = sizeof(*di);
+		di = malloc(len);
+		if (!di)
+			return -ENOMEM;
+		memset(di, 0, len);
+		di->dev_id = data->hci_dev_id;
+		*req = di;
+		*req_len = len;
+		break;
+	case HCIGETDEVLIST:
+		len = sizeof(*dr) + sizeof(uint16_t);
+		dl = malloc(len);
+		if (!dl)
+			return -ENOMEM;
+		memset(dl, 0, len);
+		dl->dev_num = 1;
+		*req = dl;
+		*req_len = len;
+		break;
+	case HCIGETCONNLIST:
+		len = sizeof(*cl) + sizeof(*ci);
+		cl = malloc(len);
+		if (!cl)
+			return -ENOMEM;
+		memset(cl, 0, len);
+		cl->dev_id = data->hci_dev_id;
+		cl->conn_num = 1;
+		*req = cl;
+		*req_len = len;
+		break;
+	case HCIGETCONNINFO:
+		len = sizeof(*cr) + sizeof(*ci);
+		cr = malloc(len);
+		if (!cr)
+			return -ENOMEM;
+		memset(cr, 0, len);
+		*req = cr;
+		*req_len = len;
+		break;
+	case HCIGETAUTHINFO:
+		len = sizeof(*ar);
+		ar = malloc(len);
+		if (!ar)
+			return -ENOMEM;
+		memset(ar, 0, len);
+		*req = ar;
+		*req_len = len;
+		break;
+	case HCIBLOCKADDR:
+	case HCIUNBLOCKADDR:
+		len = sizeof(bdaddr_t);
+		bdaddr = malloc(len);
+		if (!bdaddr)
+			return -ENOMEM;
+		memset(bdaddr, 0, len);
+		*req = bdaddr;
+		*req_len = len;
+		break;
+	case HCIDEVUP:
+	case HCIDEVDOWN:
+	case HCIDEVRESET:
+	case HCIDEVRESTAT:
+		/* These command uses the HCI dev id for param */
+		return -ENODATA;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void test_ioctl_common(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct ioctl_data *ioctl_data = data->test_data;
+	bool use_dev_id = false;
+	void *req = NULL;
+	uint32_t req_len = 0;
+	int ret;
+
+	ret = test_alloc_cmd_param(&req, &req_len);
+	if (ret < 0) {
+		if (ret == -ENODATA)
+			use_dev_id = true;
+		else {
+			tester_warn("Failed to allocate CMD parameter");
+			tester_test_failed();
+			return;
+		}
+	}
+
+	if (ioctl_data->expected_ioctl_err)
+		test_add_condition(data);
+
+	if (ioctl_data->expected_data)
+		test_add_condition(data);
+
+	if (!use_dev_id && ioctl_data->param) {
+		test_add_condition(data);
+		if (ioctl_data->cmd_param_func) {
+			ret = ioctl_data->cmd_param_func(req, &req_len);
+			if (ret) {
+				tester_warn("Failed to update cmd param");
+				tester_test_failed();
+				goto exit_free;
+			}
+		} else
+			memcpy(req, ioctl_data->param, req_len);
+
+		tester_print("Command Parameter is updated");
+		test_condition_complete(data);
+	}
+
+	if (use_dev_id)
+		ret = ioctl(data->sock_fd, ioctl_data->cmd, data->hci_dev_id);
+	else
+		ret = ioctl(data->sock_fd, ioctl_data->cmd, req);
+
+	if (ret < 0) {
+		if (ioctl_data->expected_ioctl_err) {
+			if (errno != ioctl_data->expected_ioctl_err) {
+				tester_warn("Unexpected error: %d expected: %d",
+					errno, ioctl_data->expected_ioctl_err);
+				tester_test_failed();
+				goto exit_free;
+			}
+
+			test_condition_complete(data);
+			tester_print("Received expected error: %d", errno);
+			goto exit_pass;
+		}
+
+		tester_warn("IOCTL failed with error: %d", errno);
+		tester_test_failed();
+		goto exit_free;
+	}
+
+	if (ioctl_data->expected_data && req) {
+		if (ioctl_data->expect_data_check_func)
+			ret = ioctl_data->expect_data_check_func(req, req_len);
+		else
+			ret = memcmp(req, ioctl_data->expected_data, req_len);
+
+		if (ret != 0) {
+			tester_warn("Mismatch expected data");
+			util_hexdump('>', req, req_len, print_debug, "");
+			util_hexdump('!', ioctl_data->expected_data, req_len,
+							print_debug, "");
+			tester_test_failed();
+			goto exit_free;
+		}
+
+		test_condition_complete(data);
+	}
+
+exit_pass:
+	tester_test_passed();
+exit_free:
+	if (req)
+		free(req);
+
+}
+
+static void test_ioctl_connected_event(uint16_t index, uint16_t length,
+					const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Device Connected");
+
+	test_ioctl_common(data);
+}
+
+static void test_ioctl_connection(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	unsigned int id;
+	const uint8_t *central_bdaddr;
+	struct bthost *bthost;
+	uint8_t addr_type;
+
+	tester_print("Registering %s notification",
+					mgmt_evstr(MGMT_EV_DEVICE_CONNECTED));
+	id = mgmt_register(data->mgmt_alt, MGMT_EV_DEVICE_CONNECTED,
+				data->mgmt_index,
+				test_ioctl_connected_event,
+				NULL, NULL);
+	data->mgmt_alt_ev_id = id;
+
+	central_bdaddr = hciemu_get_central_bdaddr(data->hciemu);
+	if (!central_bdaddr) {
+		tester_warn("No central bdaddr");
+		tester_setup_failed();
+		return;
+	}
+
+	addr_type = data->hciemu_type == HCIEMU_TYPE_BREDRLE ? BDADDR_BREDR :
+							BDADDR_LE_PUBLIC;
+	tester_print("ADDR TYPE: %d", addr_type);
+	bthost = hciemu_client_get_host(data->hciemu);
+	bthost_hci_connect(bthost, central_bdaddr, addr_type);
+}
+
+int main(int argc, char *argv[])
+{
+	tester_init(&argc, &argv);
+
+	test_ioctl("HCI Down", &dev_down, NULL, test_ioctl_common);
+
+	test_ioctl("Device List", &dev_list,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Device List - Invalid Param 1", &dev_list_invalid_1,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Device Info", &dev_info,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Reset Stat", &reset_stat,
+				setup_powered, test_ioctl_common);
+
+	test_ioctl("Set Link Mode - ACCEPT", &set_link_mode_accept,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Link Mode - MASTER", &set_link_mode_master,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - DM", &set_pkt_type_dm,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - DH", &set_pkt_type_dh,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - HV", &set_pkt_type_hv,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - 2-DH", &set_pkt_type_2dh,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - 2-DH", &set_pkt_type_3dh,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set Pkt Type - ALL", &set_pkt_type_all,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set ACL MTU - 1", &set_acl_mtu_1,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set ACL MTU - 2", &set_acl_mtu_2,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set SCO MTU - 1", &set_sco_mtu_1,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Set SCO MTU - 2", &set_sco_mtu_2,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Block BDADDR - Success", &block_bdaddr_success,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Block BDADDR - Fail", &block_bdaddr_fail,
+				setup_add_block_bdaddr, test_ioctl_common);
+
+	test_ioctl("Unblock BDADDR - Success", &unblock_bdaddr_success,
+				setup_add_block_bdaddr, test_ioctl_common);
+
+	test_ioctl("Unblock BDADDR - Fail", &unblock_bdaddr_fail,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Connection List - No Conn", &conn_list_no_conn,
+				NULL, test_ioctl_common);
+
+	test_ioctl("Connection List", &conn_list,
+				setup_powered, test_ioctl_connection);
+
+	test_ioctl("Connection Info", &conn_info,
+				setup_powered, test_ioctl_connection);
+
+	test_ioctl("Connection Info - No Connection", &conn_info_no_conn,
+				setup_powered, test_ioctl_common);
+
+	test_ioctl("Connection Info - Wrong Type", &conn_info_wrong_type,
+				setup_powered, test_ioctl_common);
+
+	test_ioctl("Authentication Info - No Connection", &auth_info_no_conn,
+				setup_powered, test_ioctl_common);
+
+	test_ioctl("Authentication Info", &auth_info,
+				setup_powered, test_ioctl_connection);
+
+	return tester_run();
+}
diff --git a/tools/test-runner.c b/tools/test-runner.c
index e1794e8c1..560d638e4 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -615,6 +615,7 @@ static const char *test_table[] = {
 	"sco-tester",
 	"iso-tester",
 	"mesh-tester",
+	"ioctl-tester",
 	"bnep-tester",
 	"check-selftest",
 	"tools/mgmt-tester",
@@ -624,6 +625,7 @@ static const char *test_table[] = {
 	"tools/sco-tester",
 	"tools/iso-tester",
 	"tools/mesh-tester",
+	"tools/ioctl-tester",
 	"tools/bnep-tester",
 	"tools/check-selftest",
 	NULL
