diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..36cbc970afdbd8ed92aba87f5fb92d7fcf800bdc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+.built
+.built_check
+.configured_
+.dep_files
+.git_update
+.prepared_*
+ipkg-*
+**/*.o
+**/*.so
+src/tags
+src/mapcontroller
+.configured_*
+.pkgdir
diff --git a/src/Makefile b/src/Makefile
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5cb37d52ce6aa9143c5a9b18baf5dfac471d1ef6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -0,0 +1,64 @@
+EXECS = mapcontroller
+CFLAGS+=-I. -Iinclude -Iutils -Icore -Iipc -D_GNU_SOURCE
+CFLAGS+= -g -Wall
+
+OBJS = \
+	utils/debug.o \
+	utils/liblist.o \
+	utils/utils.o \
+	utils/alloctrace.o
+
+
+IPC_OBJS = \
+	ipc/comm.o \
+	ipc/worker.o \
+	ipc/msgqueue.o \
+	ipc/comm_ubus.o
+
+CNTLR_OBJS = \
+	core/allsta.o \
+	core/cntlr_ubus.o \
+	core/cntlr.o \
+	core/cntlr_map.o \
+	core/main.o
+	#core/config.o \
+
+LIBS = -lubus -lubox -ljson-c -lblobmsg_json -luci -pthread
+LIBS += -rdynamic -ldl
+
+plugin_subdirs ?= $(wildcard plugins/*)
+plugin_sofile = $(wildcard $(d)/*.so)
+plugin_files = $(foreach d, $(plugin_subdirs), $(plugin_sofile))
+
+
+.PHONY: all check clean plugins FORCE
+
+all: $(EXECS) plugins
+
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+mapcontroller: $(OBJS) $(IPC_OBJS) $(CNTLR_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+plugins:
+	@echo "$(plugin_subdirs)"
+	for i in $(plugin_subdirs); do [ -d $$i ] && $(MAKE) -C $$i all; done
+
+
+check: FORCE
+	@cppcheck --quiet --enable=all --inconclusive --std=c99 \
+		--suppress=variableScope \
+		--suppress=unusedVariable \
+		--suppress=unreadVariable \
+		--suppress=funcArgNamesDifferent \
+		--suppress=unusedFunction \
+		-I. -Iutils -Icore -Iipc -Iinclude \
+		. 2> cppcheck.out
+
+clean:
+	rm -f *.o *.so utils/*.o utils/*.so ipc/*.o core/*.o $(EXECS)
+	for i in $(plugin_subdirs); do [ -d $$i ] && $(MAKE) -C $$i clean; done
+
+FORCE:
diff --git a/src/controller.conf b/src/controller.conf
index 4b6bb7357cbc5ef8e00c42c6b1715317778b6b2b..f46ae102d9d97600b8f6a6152c29c36be181f931 100644
--- a/src/controller.conf
+++ b/src/controller.conf
@@ -68,6 +68,7 @@ config agent-policy
 	option disallow_bsta_p2 '0'                # 0 or 1 profile-2 bSTA
 
 
+### do not parse following now ###
 config steer-param 'rssi'
 	option rssi_threshold '-68'
 	option hysteresis '5'
@@ -78,7 +79,6 @@ config steer-param 'bssload'
 	option priority '0'
 	option bssload_threshold '80'
 
-### custom rules follows ###
 config rule-custom
 	option action steer
 	option sta 'd8:32:e3:4d:35:d2'
diff --git a/src/core/cntlr.c b/src/core/cntlr.c
index 5d1723442f24b2bce19943523783723673a3efa8..69940929b706cf03197076a10f50b147d191c501 100644
--- a/src/core/cntlr.c
+++ b/src/core/cntlr.c
@@ -2,7 +2,7 @@
  * cntlr.c
  * wifi controller core
  *
- * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
  * Author: anjan.chanda@iopsys.eu
  *
@@ -27,7 +27,7 @@
 #include <libubus.h>
 
 #include <easy/easy.h>
-#include <wifi.h>
+#include <wifi.h>	// FIXME: should not be included
 
 #include "debug.h"
 #include "utils.h"
@@ -36,6 +36,7 @@
 #include "hlist.h"
 #include "allsta.h"
 #include "cntlr_ubus.h"
+#include "cntlr_map.h"
 
 
 static int update_fronthaul_bsslist(struct node *n, struct netif_fhbss *fh);
@@ -129,7 +130,7 @@ static int forall_node_update_neighbors(struct controller *c)
 		btostr((unsigned char *)nbrmac, sizeof(nbrmac), nbrstr);
 		cntlr_dbg("Sending DEL_NEIGHBOR Async to node (%p:%p)---->\n",
 						n, n->uobj[4]);
-		CMD(c->comm, c, (void *)n->uobj[4],
+		CMD(c->comm, c, (void *)(uintptr_t)n->uobj[4],
 				CMD_DEL_NEIGHBOR,
 				nbrstr, sizeof(nbrstr),
 				NULL, NULL);
@@ -1722,7 +1723,7 @@ static void cntlr_event_handler(struct ubus_context *ctx,
 	}
 }
 
-static int register_wifiagent_events(struct controller *c)
+static int register_bus_events(struct controller *c)
 {
 #if 0
 	if (c->topology != UBUS_OBJECT_INVALID) {
@@ -1730,13 +1731,123 @@ static int register_wifiagent_events(struct controller *c)
 		ubus_register_event_handler(c->ubus_ctx, &c->ubus_ev, "topology.*");
 	}
 #endif
-	cntlr_dbg("Setting up event handlers for 'wifi.agent' nodes...\n");
+	cntlr_dbg("Setting up bus event handlers...\n");
 	ubus_register_event_handler(c->ubus_ctx, &c->ubusx_ev, "ubus.object.add");
 	ubus_register_event_handler(c->ubus_ctx, &c->ubusx_ev, "ubus.object.remove");
+	ubus_register_event_handler(c->ubus_ctx, &c->ieee1905_evh, "ieee1905.*");
 
 	return 0;
 }
 
+static void ieee1905_cmdu_event_handler(void *e, struct blob_attr *msg)
+{
+#define MAX_TLV_BUF	1500		/* FIXME */
+
+	struct controller *c = (struct controller *)e;
+	uint8_t tlvlist[MAX_TLV_BUF] = { 0 };
+	char in_ifname[16] = {0};
+	struct blob_attr *attr;
+	char src[18] = { 0 };
+	uint8_t srcmac[6];
+	int pos = 0;
+	uint16_t type;
+	int mid = 0;
+	struct blob_attr *tb[5];
+	static const struct blobmsg_policy cmdu_attrs[5] = {
+		[0] = { .name = "cmdu_type", .type = BLOBMSG_TYPE_INT32 },
+		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
+		[2] = { .name = "recv_intf", .type = BLOBMSG_TYPE_STRING },
+		[3] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
+		[4] = { .name = "streams", .type = BLOBMSG_TYPE_ARRAY },
+	};
+
+
+	trace("%s: ------------>\n", __func__);
+
+	blobmsg_parse(cmdu_attrs, 5, tb, blob_data(msg), blob_len(msg));
+
+	if (!tb[0] || !tb[1])
+		return;
+
+	if (tb[0]) {
+		type = (uint16_t)blobmsg_get_u32(tb[0]);
+		if (type < 0 || !is_cmdu_for_us(c, type))
+			return;
+	}
+
+	if (tb[1])
+		mid = blobmsg_get_u32(tb[1]);
+
+
+	if (tb[2]) {
+		strncpy(in_ifname, blobmsg_data(tb[2]), 15);
+	}
+
+	if (tb[3]) {
+		strncpy(src, blobmsg_data(tb[3]), 17);
+		hwaddr_aton(src, srcmac);
+
+	}
+
+
+	if (tb[4]) {
+		int rem;
+		int len;
+
+		blobmsg_for_each_attr(attr, tb[4], rem) {
+			char tlvstr[513] = { 0 };
+			uint8_t tlv[256] = { 0 };
+
+			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
+				continue;
+
+			len = blobmsg_data_len(attr);
+			memcpy(tlvstr, blobmsg_data(attr), len);
+
+			len = (len - 1) / 2;
+			if (pos + len > MAX_TLV_BUF)
+				break;
+
+			strtob(tlvstr, len, tlv);
+			memcpy(&tlvlist[pos], tlv, len);
+			pos += len;
+		}
+	}
+
+	cntlr_handle_map_event(c, type, mid, tlvlist, pos);
+
+}
+
+static void ubus_1905_event_handler(struct ubus_context *ctx,
+					struct ubus_event_handler *ev,
+					const char *type,
+					struct blob_attr *msg)
+{
+	int i;
+	char *str;
+	struct controller *c = container_of(ev, struct controller, ieee1905_evh);
+	struct wifi_ev_handler {
+		const char *ev_type;
+		void (*handler)(void *ctx, struct blob_attr *ev_data);
+	} evs[] = {	{"ieee1905.data",     ieee1905_cmdu_event_handler} };
+
+	str = blobmsg_format_json(msg, true);
+	if (!str)
+		return;
+
+	info("[ &agent = %p ] Received [event = %s]  [val = %s]\n",
+				c, type, str);
+
+	for (i = 0; i < sizeof(evs)/sizeof(evs[0]); i++) {
+		if (!strcmp(type, evs[i].ev_type)) {
+			evs[i].handler(c, msg);
+			break;
+		}
+	}
+
+	free(str);
+}
+
 /* This function will also be called from topology change event */
 static int get_topology(struct controller *c)
 {
@@ -1843,6 +1954,7 @@ int start_controller(void)
 	/* ev = &c->ubus_ev; */
 	/* ev->cb = cntlr_event_handler_local; */
 	c->ubusx_ev.cb = cntlr_event_handler;
+	c->ieee1905_evh.cb = ubus_1905_event_handler;
 	c->topology = WIFI_OBJECT_INVALID;
 	INIT_LIST_HEAD(&c->nodelist);
 	INIT_LIST_HEAD(&c->watchlist);
@@ -1862,13 +1974,15 @@ int start_controller(void)
 		enumerate_topology_indirect(c);
 	}
 	perform_tasks_topology_init(c);
-	register_wifiagent_events(c);
+	register_bus_events(c);
 	c->heartbeat.cb = cntlr_periodic_run;
 	c->radar_timer.cb = cntlr_radar_exit;
 	uloop_timeout_set(&c->heartbeat, 2 * 1000);
 
 	cntlr_publish_object(c);
 
+	cntlr_register_module(c);
+
 	uloop_run();
 
 /* out_and_exit: */
diff --git a/src/core/cntlr.h b/src/core/cntlr.h
index ae2c0c22c52bbb1aa7abbc24d1089dfcd174b9bc..c4b2015a242a1dd6124b7439cce85a42e39efc22 100644
--- a/src/core/cntlr.h
+++ b/src/core/cntlr.h
@@ -1,5 +1,5 @@
 /*
- * cntlr.h - wificontroller header
+ * cntlr.h - MAP controller header file
  *
  * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
@@ -10,8 +10,6 @@
 #ifndef CNTLR_H
 #define CNTLR_H
 
-#include "data.h"
-
 #define cntlr_dbg(...)     dbg(green "CNTLR: " nocl __VA_ARGS__)
 #define cntlr_warn(...)    warn(red "CNTLR: " __VA_ARGS__)
 #define cntlr_info(...)    info(blue "CNTLR: " nocl __VA_ARGS__)
@@ -85,6 +83,7 @@ struct watchnode {
 	struct list_head list;
 };
 
+/* struct node - maps to a 1905 device */
 struct node {
 	unsigned char hwaddr[6];
 	struct in_addr ipaddr;
@@ -116,6 +115,7 @@ struct controller {
 	struct ubus_context *ubus_ctx;
 	/* struct ubus_event_handler ubus_ev; */  /** for local events */
 	struct ubus_event_handler ubusx_ev; /** for events from remote objs */
+	struct ubus_event_handler ieee1905_evh;
 	struct uloop_timeout heartbeat;
 	ubus_object_t topology;
 	int num_nodes;
diff --git a/src/core/cntlr_map.c b/src/core/cntlr_map.c
new file mode 100644
index 0000000000000000000000000000000000000000..f90bb5a1adec29f6a51f91d45726cd1b83c4afad
--- /dev/null
+++ b/src/core/cntlr_map.c
@@ -0,0 +1,268 @@
+/*
+ * cntlr_map.c - implements MAP2 CMDUs handling
+ *
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <net/if_arp.h>
+#include <pthread.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <json-c/json.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/uloop.h>
+#include <libubox/ustream.h>
+#include <libubox/utils.h>
+#include <libubus.h>
+
+
+#include "map_module.h"
+#include "utils.h"
+#include "debug.h"
+#include "liblist.h"
+#include "config.h"
+#include "comm.h"
+#include "msgqueue.h"
+#include "worker.h"
+#include "cntlr.h"
+
+
+#define for_each_tlv(e, _buf, _len)					  \
+for ((e) = (struct tlv *)(_buf);					  \
+	(e)->len && (_buf) + (_len) - (uint8_t *)(e) - 3 - (e)->len >= 0; \
+		(e) = (struct tlv *)(_buf + 3 + (e)->len))
+
+struct tlv {
+	uint8_t type;
+	uint16_t len;
+	uint8_t value[];
+} __attribute__ ((packed));
+
+
+typedef int (*map_cmdu_handler_t)(void *cntlr, uint16_t mid, uint8_t *tlvs,
+								int len);
+
+
+int handle_topology_notification(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_topology_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_ap_autoconfig_search(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_ap_autoconfig_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_ap_autoconfig_wsc(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+
+int handle_1905_ack(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+
+	return 0;
+}
+
+int handle_ap_caps_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+
+	return 0;
+}
+
+
+int handle_channel_pref_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+
+	return 0;
+}
+
+int handle_channel_sel_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+
+	return 0;
+}
+
+int handle_oper_channel_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+
+	return 0;
+}
+
+int handle_sta_caps_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_ap_metrics_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_sta_link_metrics_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_unassoc_sta_link_metrics_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_beacon_metrics_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_sta_steer_btm_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_sta_steer_complete(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_backhaul_sta_steer_response(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_channel_scan_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_sta_disassoc_stats(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_assoc_status_notification(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_tunneled_message(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+int handle_backhaul_sta_caps_report(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+
+int handle_failed_connection_msg(void *cntlr, uint16_t mid, uint8_t *tlvs, int len)
+{
+	return 0;
+}
+
+
+#define CMDU_TYPE_1905_START	0x0001
+#define CMDU_TYPE_1905_END	0x0009
+
+
+#define CMDU_TYPE_MAP_START	0x8000
+#define CMDU_TYPE_MAP_END	0x8033
+
+/* mind the holes in the following two tables */
+static const map_cmdu_handler_t i1905ftable[] = {
+	[0x01] = handle_topology_notification,
+	[0x03] = handle_topology_response,
+	[0x07] = handle_ap_autoconfig_search,
+	[0x08] = handle_ap_autoconfig_response,
+	[0x09] = handle_ap_autoconfig_wsc,
+};
+
+static const map_cmdu_handler_t cntlr_mapftable[] = {
+	[0x00] = handle_1905_ack,
+	[0x02] = handle_ap_caps_report,
+	[0x05] = handle_channel_pref_report,
+	[0x07] = handle_channel_sel_response,
+	[0x08] = handle_oper_channel_report,
+	[0x0a] = handle_sta_caps_report,
+	[0x0c] = handle_ap_metrics_response,
+	[0x0e] = handle_sta_link_metrics_response,
+	[0x10] = handle_unassoc_sta_link_metrics_response,
+	[0x12] = handle_beacon_metrics_response,
+	[0x15] = handle_sta_steer_btm_report,
+	[0x17] = handle_sta_steer_complete,
+	[0x1a] = handle_backhaul_sta_steer_response,
+	[0x1c] = handle_channel_scan_report,
+	[0x22] = handle_sta_disassoc_stats,
+	[0x25] = handle_assoc_status_notification,
+	[0x26] = handle_tunneled_message,
+	[0x28] = handle_backhaul_sta_caps_report,
+	[0x33] = handle_failed_connection_msg,
+};
+
+
+bool is_cmdu_for_us(void *module, uint16_t type)
+{
+	struct controller *c = (struct controller *)module;
+
+	/* TODO: handle cmdu types relevant for operating profile. */
+
+	if (type >= CMDU_TYPE_1905_START && type <= CMDU_TYPE_MAP_START) {
+		if (i1905ftable[type])
+			return true;
+	} else if (type >= CMDU_TYPE_MAP_START && type <= CMDU_TYPE_MAP_END) {
+		if (cntlr_mapftable[type - CMDU_TYPE_MAP_START])
+			return true;
+	}
+
+	return false;
+}
+
+int cntlr_handle_map_event(void *module, uint16_t cmdutype, uint16_t mid,
+						uint8_t *tlvs, int len)
+{
+	struct controller *c = (struct controller *)module;
+	const map_cmdu_handler_t *f;
+	int idx;
+	int ret;
+
+	trace("%s: ---> cmdu = %d\n", __func__, cmdutype);
+
+	if (cmdutype >= CMDU_TYPE_MAP_START) {
+		idx = cmdutype - CMDU_TYPE_MAP_START;
+		f = cntlr_mapftable;
+	} else {
+		idx = cmdutype;
+		f = i1905ftable;
+	}
+
+	if (f[idx]) {
+		ret = f[idx](c, mid, tlvs, len);
+	}
+
+	//TODO: check ret
+
+	return ret;
+}
diff --git a/src/core/cntlr_map.h b/src/core/cntlr_map.h
new file mode 100644
index 0000000000000000000000000000000000000000..49a889d1d167af2606857ed9148aae629fae1a9a
--- /dev/null
+++ b/src/core/cntlr_map.h
@@ -0,0 +1,13 @@
+
+#ifndef CNTLR_MAP_H
+#define CNTLR_MAP_H
+
+
+extern bool is_cmdu_for_us(void *module, uint16_t type);
+
+extern int cntlr_handle_map_event(void *module, uint16_t cmdutype, uint16_t mid,
+						uint8_t *tlvs, int len);
+
+
+
+#endif /* CNTLR_MAP_H */
diff --git a/src/core/cntlr_ubus.c b/src/core/cntlr_ubus.c
index 8ff05cf41316115a3e5ae1bb3d0918ebd71f5108..c42064bc7ddfbed005823be365d53067cdeb2438 100644
--- a/src/core/cntlr_ubus.c
+++ b/src/core/cntlr_ubus.c
@@ -1,5 +1,5 @@
 /*
- * cntlr_ubus.c - provides cntlr's object
+ * cntlr_ubus.c - provides map controller management object
  *
  * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
  *
@@ -20,7 +20,7 @@
 #include <uci.h>
 
 #include <easy/easy.h>
-#include <wifi.h>
+//#include <wifi.h>
 #include "utils.h"
 #include "debug.h"
 #include "config.h"
@@ -29,6 +29,8 @@
 #include "allsta.h"
 #include "cntlr_ubus.h"
 
+#include "map_module.h"
+
 
 static struct controller *this_cntlr;
 
@@ -123,10 +125,10 @@ struct ubus_method wificntlr_methods[] = {
 };
 
 struct ubus_object_type wificntlr_type =
-	UBUS_OBJECT_TYPE("wifi.cntlr", wificntlr_methods);
+	UBUS_OBJECT_TYPE("map.controller", wificntlr_methods);
 
 struct ubus_object wificntlr_object = {
-	.name = "wifi.cntlr",
+	.name = "map.controller",
 	.type = &wificntlr_type,
 	.methods = wificntlr_methods,
 	.n_methods = ARRAY_SIZE(wificntlr_methods),
@@ -140,7 +142,7 @@ void cntlr_publish_object(struct controller *c)
 	this_cntlr = c;
 	ret = ubus_add_object(ctx, &wificntlr_object);
 	if (ret)
-		err("Failed to add 'wifi.cntlr' ubus object: %s\n",
+		err("Failed to add 'map.controller' ubus object: %s\n",
 				ubus_strerror(ret));
 }
 
@@ -154,3 +156,67 @@ void cntlr_remove_object(struct controller *c)
 	this_cntlr = NULL;
 }
 
+int cntlr_register_module(struct controller *c)
+{
+#define cntlr_sign		"f6dfa346957f45cb8d82dc6b77e2df61"
+	uint8_t cntlr_sig[16];
+	char data[2 * sizeof(struct map_module) + 1] = {0};
+	int ret;
+	uint32_t map_id;
+	struct blob_buf bb = {};
+	struct map_module m = {
+		.role = MAP_ROLE_CONTROLLER,
+		.profile = MAP_PROFILE_2,
+	};
+	const char *map_plugin = "map.1905";
+	struct ubus_context *ctx = c->ubus_ctx;
+
+	strtob(cntlr_sign, strlen(cntlr_sign) / 2, cntlr_sig);
+	memcpy(&m.sign, cntlr_sig, 16);
+
+	/* register with map.1905 plugin */
+	ret = ubus_lookup_id(ctx, map_plugin, &map_id);
+	if (ret) {
+		/* TODO: if map plugin is not installed, wait for it to appear.
+		 * Retry after some time.
+		 */
+		dbg("plugin '%s' lookup failed. %s\n", map_plugin,
+						ubus_strerror(ret));
+		return -1;
+	}
+
+	blob_buf_init(&bb, 0);
+	blobmsg_add_u32(&bb, "module", wificntlr_object.id);
+	btostr((unsigned char *)&m, sizeof(struct map_module), data);
+	blobmsg_add_string(&bb, "data", data);
+	ret = ubus_invoke(ctx, map_id, "register", bb.head, NULL, 0, 3000);
+	if (ret) {
+		warn("Failed to 'register' with %s (err = %s)\n",
+				map_plugin, ubus_strerror(ret));
+	}
+
+	blob_buf_free(&bb);
+
+#if 1	// testing notify event
+	blob_buf_init(&bb, 0);
+	blobmsg_add_u32(&bb, "module", wificntlr_object.id);
+	blobmsg_add_string(&bb, "data", "Hello World");
+	ret = ubus_notify(ctx, &wificntlr_object, "1905-NOTIFICATION", bb.head, -1);
+	if (ret)
+		warn("Failed to notify 1905-NOTIFICATION\n");
+
+	blob_buf_free(&bb);
+
+	// testing send
+	blob_buf_init(&bb, 0);
+	blobmsg_add_u32(&bb, "type", 0x800a);
+	blobmsg_add_string(&bb, "data", "aabbccddeeff00112233445566778899");
+	ret = ubus_invoke(ctx, map_id, "send", bb.head, NULL, 0, 1000);
+	if (ret)
+		warn("map.1905 plugin 'send' ret (%s)\n", ubus_strerror(ret));
+
+	blob_buf_free(&bb);
+#endif
+
+	return 0;
+}
diff --git a/src/core/cntlr_ubus.h b/src/core/cntlr_ubus.h
index 3750855355e8c4c609445e9d50b08ce1e9ccf20a..b7eb46dc6f0c36ee9c242dbb0dac14f9ff9fd981 100644
--- a/src/core/cntlr_ubus.h
+++ b/src/core/cntlr_ubus.h
@@ -1,7 +1,7 @@
 /*
  * cntlr_ubus.h - cntlr's ubus object header
  *
- * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
  * Author: anjan.chanda@iopsys.eu
  *
@@ -10,7 +10,11 @@
 #ifndef CNTLR_UBUS_H
 #define CNTLR_UBUS_H
 
-void cntlr_publish_object(struct controller *c);
-void cntlr_remove_object(struct controller *c);
+extern void cntlr_publish_object(struct controller *c);
+extern void cntlr_remove_object(struct controller *c);
+
+
+extern int cntlr_register_module(struct controller *c);
+
 
 #endif /* CNTLR_UBUS_H */
diff --git a/src/core/config.c b/src/core/config.c
new file mode 100644
index 0000000000000000000000000000000000000000..bbcbf746141a57a48b28b79d71b7f6cd19123380
--- /dev/null
+++ b/src/core/config.c
@@ -0,0 +1,817 @@
+/*
+ * config.c - controller configuration handling
+ *
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <json-c/json.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/uloop.h>
+#include <libubox/ustream.h>
+#include <libubox/utils.h>
+#include <libubus.h>
+#include <uci.h>
+
+#include "debug.h"
+#include "utils.h"
+#include "config.h"
+//#include "steer_rules.h"
+#include "comm.h"
+#include "msgqueue.h"
+#include "worker.h"
+#include "cntlr.h"
+
+//TODO: remove
+struct stax{
+	char macstring[32];	/* ':' separated mac address string */
+	struct list_head list;
+};
+////////////
+
+static struct netif_fhcfg *get_netif_fhcfg_by_name(struct cntlr_config *c,
+							const char *name)
+{
+	struct netif_fhcfg *p;
+
+	list_for_each_entry(p, &c->fhlist, list) {
+		if (!strcmp(name, p->name))
+			return p;
+	}
+
+	return NULL;
+}
+
+static struct steer_policy *get_steer_policy_by_name(struct netif_fhcfg *c,
+							const char *name)
+{
+	struct steer_policy *p;
+
+	if (!c)
+		return NULL;
+
+	list_for_each_entry(p, &c->steer_policylist, list) {
+		if (!strcmp(name, p->name))
+			return p;
+	}
+
+	return NULL;
+}
+
+void stax_add_entry(struct list_head *h, char *sta_macstr)
+{
+	struct stax *n;
+
+	n = calloc(1, sizeof(struct stax));
+	if (n) {
+		snprintf(n->macstring, 18, "%s", sta_macstr);
+		list_add(&n->list, h);
+	}
+}
+
+void stax_del_entry(struct list_head *h, char *sta_macstr)
+{
+	struct stax *s, *tmp;
+
+	list_for_each_entry_safe(s, tmp, h, list) {
+		if (!strncmp(s->macstring, sta_macstr, sizeof(s->macstring))) {
+			list_del(&s->list);
+			free(s);
+			return;
+		}
+	}
+}
+
+void cntlr_config_dump(struct cntlr_config *cfg)
+{
+	struct netif_fhcfg *n;
+	struct steer_policy *pol;
+	struct stax *x;
+
+	if (!cfg)
+		return;
+
+	list_for_each_entry(n, &cfg->fhlist, list) {
+		dbg("name: %s\n", n->name);
+		dbg("  enabled  : %s\n", n->enabled ? "true" : "false");
+		dbg("  assocctrl: %s\n", n->assoc_control ? "true" : "false");
+
+		dbg("  Policies -------\n");
+		list_for_each_entry(pol, &n->steer_policylist, list) {
+			dbg("    name: %s\n", pol->name);
+			dbg("    enabled  : %s\n", pol->enabled ? "true" : "false");
+			/* if (pol->dump_config)
+				pol->dump_config(pol, pol->policy); */
+		}
+
+		dbg("  Steer Exclude Lists -------\n");
+		list_for_each_entry(x, &n->steer_excludelist, list) {
+			dbg("    mac: %s\n", x->macstring);
+		}
+
+		dbg("  Steer BTM Exclude Lists -------\n");
+		list_for_each_entry(x, &n->steer_btm_excludelist, list) {
+			dbg("    mac: %s\n", x->macstring);
+		}
+
+		dbg("  Assoc Ctrl Lists -------\n");
+		list_for_each_entry(x, &n->assoc_ctrllist, list) {
+			dbg("    mac: %s\n", x->macstring);
+		}
+	}
+}
+
+int cntlr_config_defaults(struct cntlr *a, struct cntlr_config *cfg)
+{
+	struct list_head *p, *tmp;
+	struct netif_fh *ifptr;
+	struct netif_fhcfg *new;
+
+	if (!cfg)
+		return -1;
+
+	cfg->enabled = false;
+	cfg->runfreq = AGENT_RUN_AUTO;
+
+	INIT_LIST_HEAD(&cfg->fhlist);
+	INIT_LIST_HEAD(&cfg->bklist);
+	list_for_each_entry(ifptr, &a->fhlist, list) {
+		/* struct list_head pollist; */
+		struct steer_rule *r;
+
+		new = calloc(1, sizeof(struct netif_fhcfg));
+		if (!new) {
+			warn("OOM! config\n");
+			goto err_alloc;
+		}
+
+		snprintf(new->name, 16, "%s", ifptr->name);
+		new->enabled = false;
+		new->steer_btm_retry = STEER_BTM_RETRY;
+		new->steer_btm_retry_secs = STEER_BTM_RETRY_INT;
+		new->fallback_legacy = STEER_LEGACY_FALLBACK_INT;
+		new->steer_legacy_reassoc_secs = STEER_LEGACY_REASSOC_INT;
+		new->steer_legacy_retry_secs = STEER_LEGACY_RETRY_INT;
+		new->assoc_control_time = ASSOC_CONTROL_INT;
+		INIT_LIST_HEAD(&new->steer_policylist);
+		/* nrules = get_registered_steer_rules(&pollist); */ /* TODO */
+		list_for_each_entry(r, &regd_steer_rules, list) {
+			struct steer_policy *pol;
+
+			pol = calloc(1, sizeof(struct steer_policy));
+			if (!pol)
+				goto err_alloc_policy;
+
+			snprintf(pol->name, 16, "%s", r->name);
+			pol->enabled = false;
+			if (r->init_config)
+				r->init_config(r, &pol->policy);
+			list_add(&pol->list, &new->steer_policylist);
+		}
+
+		INIT_LIST_HEAD(&new->steer_excludelist);
+		INIT_LIST_HEAD(&new->steer_btm_excludelist);
+		INIT_LIST_HEAD(&new->assoc_ctrllist);
+
+		ifptr->cfg = new;
+		dbg("%s: %s netif_fh->cfg = %p\n", __func__, ifptr->name, ifptr->cfg);
+		list_add(&new->list, &cfg->fhlist);
+	}
+
+	return 0;
+
+err_alloc_policy:
+	/* TODO */
+err_alloc:
+	list_for_each_safe(p, tmp, &cfg->fhlist) {
+		list_del(p);
+		free(p);
+	}
+
+	return -1;
+}
+
+/* create fh-iface config and initialize with default values */
+struct netif_fhcfg *create_fronthaul_iface_config(struct cntlr_config *cfg,
+							const char *ifname)
+{
+	struct netif_fhcfg *new;
+	struct steer_rule *r;
+
+	if (!cfg)
+		return NULL;
+
+	new = calloc(1, sizeof(struct netif_fhcfg));
+	if (!new) {
+		warn("OOM! config\n");
+		return NULL;
+	}
+
+	snprintf(new->name, 16, "%s", ifname);
+	new->enabled = true;
+	new->fallback_legacy = STEER_LEGACY_FALLBACK_INT;
+	new->steer_btm_retry_secs = STEER_BTM_RETRY_INT;
+	new->steer_legacy_reassoc_secs = STEER_LEGACY_REASSOC_INT;
+	new->steer_legacy_retry_secs = STEER_LEGACY_RETRY_INT;
+	new->assoc_control_time = ASSOC_CONTROL_INT;
+	INIT_LIST_HEAD(&new->steer_policylist);
+	/* nrules = get_registered_steer_rules(&pollist); */ /* TODO */
+	list_for_each_entry(r, &regd_steer_rules, list) {
+		struct steer_policy *pol;
+
+		pol = calloc(1, sizeof(struct steer_policy));
+		if (!pol) {
+			goto err_oom;
+		}
+
+		snprintf(pol->name, 16, "%s", r->name);
+		pol->enabled = false;
+		if (r->init_config)
+			r->init_config(r, &pol->policy);
+		list_add(&pol->list, &new->steer_policylist);
+	}
+
+	INIT_LIST_HEAD(&new->steer_excludelist);
+	INIT_LIST_HEAD(&new->steer_btm_excludelist);
+	INIT_LIST_HEAD(&new->assoc_ctrllist);
+
+	/* f->cfg = new; */
+	dbg("%s: %s netif_fh->cfg = %p\n", __func__, new->name, new);
+
+	list_add(&new->list, &cfg->fhlist);
+
+	return new;
+
+err_oom:
+	list_flush(&new->steer_policylist, struct steer_policy, list);
+	free(new);
+	return NULL;
+}
+
+int cntlr_config_reload(const char *confname, struct cntlr_config *cfg)
+{
+	struct uci_context *ctx = NULL;
+	struct uci_package *pkg = NULL;
+	struct uci_element *e;
+
+	ctx = uci_alloc_context();
+	if (ctx && uci_load(ctx, confname, &pkg) != UCI_OK) {
+		err("config file '%s' not found!\n", confname);
+		uci_free_context(ctx);
+		return -1;
+	}
+
+	uci_foreach_element(&pkg->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+
+		if (!strcmp(s->type, "wificntlr")) {
+			struct uci_element *x;
+			struct uci_option *op;
+			bool enabled = false;
+
+			uci_foreach_element(&s->options, x) {
+				op = uci_to_option(x);
+				if (!strncmp(x->name, "enabled", 7) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("wificntlr: enabled = %d\n", atoi(op->v.string));
+					enabled = atoi(op->v.string) == 1 ?
+							true : false;
+					cfg->enabled = enabled;
+				} else if (!strncmp(x->name, "runfreq", 7) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("wificntlr: speed = %s\n", op->v.string);
+					if (!strncasecmp(op->v.string, "auto", 4))
+						cfg->runfreq = AGENT_RUN_AUTO;
+					else if (!strncasecmp(op->v.string, "low", 3))
+						cfg->runfreq = AGENT_RUN_LOW;
+					else if (!strncasecmp(op->v.string, "high", 4))
+						cfg->runfreq = AGENT_RUN_HIGH;
+					else if (!strncasecmp(op->v.string, "off", 3))
+						cfg->runfreq = AGENT_RUN_OFF;
+				} /* else if (!strncmp(x->name, "ifname", 6) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("wificntlr: ifname: ");
+					uci_foreach_element(&op->v.list, xi) {
+						dbg("%s ", xi->name);
+					}
+					dbg("\n");
+				} */
+			}
+		} else if (!strcmp(s->type, "fh-iface")) {
+			struct uci_element *x;
+			struct uci_option *op;
+			bool disabled = false;
+			unsigned int steer_btm_retry = STEER_BTM_RETRY;
+			unsigned int steer_btm_retry_secs = STEER_BTM_RETRY_INT;
+			unsigned int legacy = STEER_LEGACY_FALLBACK_INT;
+			unsigned int steer_legacy_reassoc_secs = STEER_LEGACY_REASSOC_INT;
+			unsigned int steer_legacy_retry_secs = STEER_LEGACY_RETRY_INT;
+			unsigned int assoc_ctrl_secs = ASSOC_CONTROL_INT;
+			struct netif_fhcfg *ifcfg = NULL;
+
+			uci_foreach_element(&s->options, x) {
+				op = uci_to_option(x);
+				if (!strncmp(x->name, "disabled", 8) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("wificntlr: disabled = %d\n", atoi(op->v.string));
+					disabled = atoi(op->v.string) == 1 ?
+								true : false;
+				} else if (!strncmp(x->name, "ifname", 6) &&
+						op->type == UCI_TYPE_STRING) {
+					/* struct uci_element *xi; */
+
+					ifcfg = get_netif_fhcfg_by_name(cfg, op->v.string);
+					if (!ifcfg) {
+						ifcfg = create_fronthaul_iface_config(cfg, op->v.string);
+						if (!ifcfg) {
+							warn("%s: OOM!\n", __func__);
+							break;
+						}
+					} else {
+						warn("Duplicate 'fh-iface %s' config!! ignore\n",
+								op->v.string);
+					}
+
+					list_flush(&ifcfg->steer_excludelist, struct stax, list);
+					list_flush(&ifcfg->steer_btm_excludelist, struct stax, list);
+					list_flush(&ifcfg->assoc_ctrllist, struct stax, list);
+
+				} else if (!strncmp(x->name, "btm_retry", 9) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: btm_retry = %d\n",
+						atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						steer_btm_retry = atoi(op->v.string);
+				} else if (!strncmp(x->name, "btm_retry_secs", 14) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: btm_retry_secs = %d\n",
+						atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						steer_btm_retry_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "fallback_legacy", 15) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: legacy = %d\n",
+							atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						legacy = atoi(op->v.string);
+				} else if (!strncmp(x->name, "steer_legacy_reassoc_secs", 25) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: steer_legacy_rassoc_secs = %d\n",
+						atoi(op->v.string));
+					steer_legacy_reassoc_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "steer_legacy_retry_secs", 23) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: steer_legacy_retry_secs = %d\n",
+						atoi(op->v.string));
+					steer_legacy_retry_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "assoc_ctrl", 10) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: assoc_ctlr = %d\n",
+							atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						assoc_ctrl_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "steer", 5) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: param: ");
+					uci_foreach_element(&op->v.list, xi) {
+						struct steer_policy *p = NULL;
+						struct steer_rule *r;
+
+						dbg("%s ", xi->name);
+						p = get_steer_policy_by_name(ifcfg, xi->name);
+						if (!p) {
+							/* TODO? */
+							dbg("TODO!! steer before ifname\n");
+							continue;
+						}
+						p->enabled = true;
+						r = get_steer_rule_by_name(xi->name);
+						if (r)
+							r->enabled = true;
+					}
+					dbg("\n");
+				} else if (!strncmp(x->name, "exclude_btm", 11) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: exclude_btm: ");
+					uci_foreach_element(&op->v.list, xi) {
+						dbg("%s ", xi->name);
+						stax_add_entry(&ifcfg->steer_btm_excludelist, xi->name);
+					}
+					dbg("\n");
+				} else if (!strncmp(x->name, "exclude", 7) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: exclude: ");
+					uci_foreach_element(&op->v.list, xi) {
+						dbg("%s ", xi->name);
+						stax_add_entry(&ifcfg->steer_excludelist, xi->name);
+					}
+					dbg("\n");
+				}
+			}
+			if (ifcfg) {
+				ifcfg->enabled = !disabled;
+				ifcfg->steer_btm_retry = steer_btm_retry;
+				ifcfg->steer_btm_retry_secs = steer_btm_retry_secs;
+				if (legacy) {
+					/* wait atleast these many secs before
+					 * falling to legacy steering.
+					 */
+					ifcfg->fallback_legacy = 1 +
+						steer_btm_retry * steer_btm_retry_secs +
+						legacy;
+				}
+				ifcfg->steer_legacy_reassoc_secs = steer_legacy_reassoc_secs;
+				ifcfg->steer_legacy_retry_secs = steer_legacy_retry_secs;
+				ifcfg->assoc_control_time = assoc_ctrl_secs;
+			}
+		}
+#if 0
+		else if (!strcmp(s->type, "steer")) {
+			struct uci_element *x, *tmp;
+			struct uci_option *op;
+			bool enabled = false;
+			unsigned int legacy = STEER_LEGACY_FALLBACK_INT;
+			unsigned int steer_legacy_reassoc_secs = STEER_LEGACY_REASSOC_INT;
+			unsigned int steer_legacy_retry_secs = STEER_LEGACY_RETRY_INT;
+			unsigned int steer_btm_retry = STEER_BTM_RETRY;
+			unsigned int steer_btm_retry_secs = STEER_BTM_RETRY_INT;
+			struct netif_fhcfg *ifcfg = NULL;
+
+			uci_foreach_element_safe(&s->options, tmp, x) {
+				op = uci_to_option(x);
+				if (!strncmp(x->name, "enabled", 7) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: enabled = %d\n", atoi(op->v.string));
+					enabled = atoi(op->v.string) == 1 ?
+							true : false;
+				} else if (!strncmp(x->name, "btm_retry", 9) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: btm_retry = %d\n",
+						atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						steer_btm_retry = atoi(op->v.string);
+				} else if (!strncmp(x->name, "btm_retry_secs", 14) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: btm_retry_secs = %d\n",
+						atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						steer_btm_retry_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "fallback_legacy", 15) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: legacy = %d\n",
+							atoi(op->v.string));
+					if (atoi(op->v.string) >= 0)
+						legacy = atoi(op->v.string);
+				} else if (!strncmp(x->name, "steer_legacy_reassoc_secs", 25) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: steer_legacy_rassoc_secs = %d\n",
+						atoi(op->v.string));
+					steer_legacy_reassoc_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "steer_legacy_retry_secs", 23) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: steer_legacy_retry_secs = %d\n",
+						atoi(op->v.string));
+					steer_legacy_retry_secs = atoi(op->v.string);
+				} else if (!strncmp(x->name, "ifname", 6) &&
+						op->type == UCI_TYPE_STRING) {
+					dbg("Steer: ifname = %s\n", op->v.string);
+					ifcfg = get_netif_fhcfg_by_name(cfg, op->v.string);
+					if (!ifcfg) {
+						warn("stale config for '%s'\n", op->v.string);
+						break;
+					}
+
+					list_flush(&ifcfg->steer_excludelist, struct stax, list);
+					list_flush(&ifcfg->steer_btm_excludelist, struct stax, list);
+					list_flush(&ifcfg->assoc_ctrllist, struct stax, list);
+				} else if (!strncmp(x->name, "param", 5) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: param: ");
+					uci_foreach_element(&op->v.list, xi) {
+						struct steer_policy *p = NULL;
+						struct steer_rule *r;
+
+						dbg("%s ", xi->name);
+						p = get_steer_policy_by_name(ifcfg, xi->name);
+						if (!p) {
+							dbg("TODO ?");
+							continue;
+						}
+						p->enabled = enabled;
+						r = get_steer_rule_by_name(xi->name);
+						if (r)
+							r->enabled = enabled;
+					}
+					dbg("\n");
+				} else if (!strncmp(x->name, "exclude_btm", 11) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: exclude_btm: ");
+					uci_foreach_element(&op->v.list, xi) {
+						dbg("%s ", xi->name);
+						stax_add_entry(&ifcfg->steer_btm_excludelist, xi->name);
+					}
+					dbg("\n");
+				} else if (!strncmp(x->name, "exclude", 7) &&
+						op->type == UCI_TYPE_LIST) {
+					struct uci_element *xi;
+
+					dbg("Steer: exclude: ");
+					uci_foreach_element(&op->v.list, xi) {
+						dbg("%s ", xi->name);
+						stax_add_entry(&ifcfg->steer_excludelist, xi->name);
+					}
+					dbg("\n");
+				}
+			}
+
+			if (ifcfg) {
+				ifcfg->enabled = enabled;
+				ifcfg->steer_btm_retry = steer_btm_retry;
+				ifcfg->steer_btm_retry_secs = steer_btm_retry_secs;
+				if (legacy) {
+					/* wait atleast these many secs before
+					 * falling to legacy steering.
+					 */
+					ifcfg->fallback_legacy = 1 +
+						steer_btm_retry * steer_btm_retry_secs +
+						legacy;
+				}
+				ifcfg->steer_legacy_reassoc_secs = steer_legacy_reassoc_secs;
+				ifcfg->steer_legacy_retry_secs = steer_legacy_retry_secs;
+			}
+		}
+#endif
+		else if (!strcmp(s->type, "steer-param")) {
+			/* For every steer-param name, find matching rule
+			 * module. If one is available, then the rule module
+			 * takes care of handling the get/set config for this
+			 * parameter specific optins.
+			 */
+			struct steer_rule *r;
+
+			dbg("Steer-param: %s\n", s->e.name);
+			r = get_steer_rule_by_name(s->e.name);
+			if (r) {
+				dbg("Rule to handle steer-param '%s' available\n", s->e.name);
+				r->config(r, cfg, s);
+			}
+		} else if (!strncmp(s->type, "wifi-iface", 10)) {
+			struct uci_element *x;
+			int mob_id = 500;
+
+			uci_foreach_element(&s->options, x) {
+				struct uci_option *op;
+
+				op = uci_to_option(x);
+				if (!strncmp(x->name, "mobility_domain", 7) &&
+						op->type == UCI_TYPE_STRING)
+					mob_id = atoi(op->v.string);
+			}
+
+			dbg("wireless: mobility_domain = %d\n", mob_id);
+			memcpy(cfg->mobility_domain, &mob_id, 2);
+			dbg("wireless: mobility_domain hex = %02x:%02x\n",
+					cfg->mobility_domain[0],
+					cfg->mobility_domain[1]);
+
+			break;
+		}
+	}
+
+	uci_unload(ctx, pkg);
+	uci_free_context(ctx);
+	return 0;
+}
+
+static void config_update_entry(struct uci_context *ctx, struct uci_package *p,
+				struct uci_section *s, const char *optname,
+				int add, void *val, int len)
+{
+	struct uci_ptr ptr;
+
+	memset(&ptr, 0, sizeof(struct uci_ptr));
+	ptr.p = p;
+	ptr.s = s;
+	ptr.package = p->e.name;
+	ptr.section = s->e.name;
+	ptr.option = optname;
+	ptr.target = UCI_TYPE_OPTION;
+	ptr.flags |= UCI_LOOKUP_EXTENDED;
+	ptr.value = (char *)val;
+
+	if (add) {
+		dbg("config: add list option: %s\n", (char *)val);
+		uci_add_list(ctx, &ptr);
+	} else {
+		dbg("config: del list option: %s\n", (char *)val);
+		uci_del_list(ctx, &ptr);
+	}
+	uci_commit(ctx, &p, false);
+}
+
+int config_update(const char *confname, struct cntlr_config *cfg,
+			const char *section, const char *option,
+			int add,
+			void *value, int len)
+{
+	struct uci_context *ctx = NULL;
+	struct uci_package *pkg = NULL;
+	struct uci_element *e;
+
+	ctx = uci_alloc_context();
+	if (ctx && uci_load(ctx, confname, &pkg) != UCI_OK) {
+		dbg("config file '%s' not found!\n", confname);
+		free(ctx);
+		return -1;
+	}
+
+	uci_foreach_element(&pkg->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+		struct uci_element *x, *tmp;
+		struct uci_option *op;
+
+		if (strcmp(s->type, section))
+			continue;
+
+		/* iter through matched 'section' for the 'option' */
+		uci_foreach_element_safe(&s->options, tmp, x) {
+			if (strcmp(x->name, option))
+				continue;
+
+			op = uci_to_option(x);
+			if (op->type == UCI_TYPE_LIST) {
+				uci_foreach_element(&op->v.list, x) {
+					if (!strncmp(x->name, value, len)) {
+						if (!add)
+							config_update_entry(ctx,
+								pkg, s, option, 0, value, len);
+
+						goto out_exit;
+					}
+				}
+				/* add new exclude at end of list */
+				if (add)
+					config_update_entry(ctx, pkg, s, option, 1, value, len);
+
+				goto out_exit;
+			}
+		}
+		/* 'option' name not present in 'section'
+		 * Create a new one at end of 'section'.
+		 */
+		if (add)
+			config_update_entry(ctx, pkg, s, option, 1, value, len);
+
+		goto out_exit;
+	}
+out_exit:
+	uci_free_context(ctx);
+	return 0;
+}
+
+int config_update2(const char *confname, struct cntlr_config *cfg,
+		const char *section_type,
+		const char *match_option,
+		const char *match_option_value,
+		const char *option,
+		int add,
+		void *value, int len)
+{
+	struct uci_context *ctx = NULL;
+	struct uci_package *pkg = NULL;
+	struct uci_element *e;
+
+	ctx = uci_alloc_context();
+	if (ctx && uci_load(ctx, confname, &pkg) != UCI_OK) {
+		dbg("config file '%s' not found!\n", confname);
+		free(ctx);
+		return -1;
+	}
+
+	uci_foreach_element(&pkg->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+		struct uci_element *x, *tmp;
+		struct uci_option *op;
+		const char *optstring;
+
+		if (strcmp(s->type, section_type))
+			continue;
+
+		if (match_option && match_option_value) {
+			optstring = uci_lookup_option_string(ctx, s, match_option);
+			if (!optstring || strcmp(optstring, match_option_value))
+				continue;
+		}
+
+		/* iter through matched 'section' for the 'option' */
+		uci_foreach_element_safe(&s->options, tmp, x) {
+			if (strcmp(x->name, option))
+				continue;
+
+			op = uci_to_option(x);
+			if (op->type == UCI_TYPE_LIST) {
+				uci_foreach_element(&op->v.list, x) {
+					if (!strncmp(x->name, value, len)) {
+						if (!add) {
+							config_update_entry(ctx,
+								pkg, s, option,
+								0, value, len);
+						}
+
+						goto out_exit;
+					}
+				}
+				/* add new 'option' at end of list */
+				if (add) {
+					config_update_entry(ctx, pkg, s, option,
+								1, value, len);
+				}
+
+				goto out_exit;
+			}
+		}
+		/* 'option' name not present in 'section'
+		 * Create a new one at end of 'section'.
+		 */
+		if (add)
+			config_update_entry(ctx, pkg, s, option,
+							1, value, len);
+
+		goto out_exit;
+	}
+out_exit:
+	uci_free_context(ctx);
+	return 0;
+}
+
+
+#if 0
+struct config uci_config = {
+	.name = "uci",
+	.priv = uci_ctx;
+	.get = uci_get_config,
+	.set = uci_set_config,
+	.init = uci_setup,
+	.exit = uci_exit,
+};
+
+#define priv_get_config(priv)	container_of(priv, struct config, priv)
+
+void register_config(struct config *c)
+{
+	static struct uci_context *ctx;
+	static struct uci_package *pkg;
+	struct uci_element *e;
+	int ret = 0;
+
+	if (uci_ctx)
+		return priv_get_config(uci_ctx);
+
+	ctx = uci_alloc_context();
+	if (ctx) {
+		uci_ctx = ctx;
+		memcpy(c, &uci_config, sizeof(*cfg));
+	}
+	if (uci_load(ctx, "wificntlr", &pkg))
+		return -1;
+
+	uci_foreach_element(&pkg->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+		const char *option_val;
+
+		if (strcmp(s->type, "wificntlr"))
+			continue;
+
+		option_val = uci_lookup_option_string(ctx, s, name);
+		if (option_val)
+			sprintf(val, "%s", option_val);
+		else
+			ret = -1;
+	}
+	uci_free_context(ctx);
+	return ret;
+
+}
+#endif
diff --git a/src/core/config.h b/src/core/config.h
index 049702d9598615e29d3e1a9c895039da9a8649b3..e9c6a9b93c330adc88143f3bc51c2dc0944a9af9 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -1,7 +1,7 @@
 /*
- * config.h - controller configurations
+ * config.h - MAP Controller configuration header file
  *
- * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
  * Author: anjan.chanda@iopsys.eu
  *
@@ -10,14 +10,77 @@
 #ifndef CONFIG_H
 #define CONFIG_H
 
-/* TODO: placeholder for now */
+
+enum wifi_band {
+	WIFI_BAND_NONE,
+	WIFI_BAND_2       = 1 << 0,        /**< 0x1 for 2.4Ghz band. */
+	WIFI_BAND_5       = 1 << 1,        /**< 0x2 for 5Ghz band. */
+	WIFI_BAND_60      = 1 << 2,        /**< 0x4 for 60Ghz band. */
+	WIFI_BAND_6       = 1 << 3,        /**< 0x8 for 6Ghz */
+};
+
+struct iface_credential {
+	uint8_t band;
+	uint8_t bssid[6];
+	uint8_t ssid[33];
+	char encryption[64];
+	uint8_t key[64];
+	uint16_t vlan_id;
+};
+
+enum agent_steer_policy {
+	AGENT_STEER_DISALLOW,            /* agent shall not steer based on rcpi */
+	AGENT_STEER_RCPI_MANDATE,        /* agent shall steer based on rcpi */
+	AGENT_STEER_RCPI_ALLOW,          /* agent may steer based on rcpi */
+};
+
+struct agent_policy {
+	uint8_t alid[6];                  /* ieee1905 AL macaddress */
+	enum agent_steer_policy policy;   /* 0, 1, 2 - see MultiAP specs */
+	uint8_t util_threshold;           /* utilization as in BSS load IE */
+	uint8_t rcpi_threshold;           /* 0 - 220 */
+	bool report_scan;                 /* report independent scans */
+	bool report_sta_assocfails;       /* report STA assoc fails */
+	uint8_t report_metric_periodic;   /* 0 = disable, else 1 - 255 in secs */
+	uint8_t report_rcpi_threshold;    /* 0, or 1 - 220 */
+	uint8_t report_util_threshold;    /* 0, or channel utilization value */
+	bool include_sta_stats;           /* sta stats in AP metric response */
+	bool include_sta_metric;          /* sta metric in AP metric response */
+	uint16_t pvid;                    /* primary vlan id */
+	uint8_t pcp_default;              /* default vlan pcp */
+	bool disallow_bsta_p1;            /* disallow profile1 bSTA link */
+	bool disallow_bsta_p2;            /* disallow profile2 bSTA link */
+
+	struct list_head list;            /* link to next policy */
+};
 
 struct controller_config {
 	bool enabled;
-};
+	uint8_t wps_supp_bands;           /* WPS registrar supported bands */
+
+	struct iface_credential fh5;
+	struct iface_credential fh2;
 
-struct config {
-	struct controller_config cntlr;
+	struct iface_credential bk5;
+	struct iface_credential bk2;
+
+	struct list_head policylist;      /* list of struct agent_policy */
 };
 
+struct controller;
+
+int cntlr_config_reload(const char *confname, struct controller_config *cfg);
+int cntlr_config_defaults(struct controller *c, struct controller_config *cfg);
+void cntlr_config_dump(struct controller_config *cfg);
+
+int config_update(const char *confname, struct controller_config *cfg,
+			const char *section, const char *option, int add,
+			void *value, int len);
+
+
+int config_update2(const char *confname, struct controller_config *cfg,
+			const char *section_type,
+			const char *match_option, const char *match_option_value,
+			const char *option, int add, void *value, int len);
+
 #endif
diff --git a/src/include/map_module.h b/src/include/map_module.h
index 9d035472d86b1409e1cf54dae35f1341ea95bb84..50dece6133784c763a28c6f7b69447e942129c2d 100644
--- a/src/include/map_module.h
+++ b/src/include/map_module.h
@@ -1,5 +1,5 @@
 /*
- * map_module.h - multi-ap module API header.
+ * map_module.h - multi-ap module(s) registration API header.
  *
  * Copyright (C) 2020 iopsys Software Solutions AB. All rights reserved.
  *
@@ -9,11 +9,6 @@
  *
  */
 
-//TODO: this file belongs to the 'map-1905' plugin.
-//MAP agent/controller use the functions here to register with the map-1905
-//plugin.
-
-
 #ifndef MAP_MODULE_H
 #define MAP_MODULE_H
 
@@ -34,7 +29,7 @@ extern "C" {
  */
 typedef int (*cmdu_handler_t)(uint16_t cmdu_type, void *msg, size_t msglen);
 
-typedef void *map_module_id_t;
+typedef uint8_t map_module_id_t[32];
 
 enum map_role {
 	MAP_ROLE_AGENT = 0x1,
@@ -56,7 +51,8 @@ enum map_profile {
  * module's CMDU handler with the cmdu_type and the 'msg' payload.
  */
 struct map_module {
-	map_module_id_t *uid;        /**< id assigned by the libmap plugin */
+	map_module_id_t uid;         /**< handle to module object */
+	uint8_t sign[32];
 	uint32_t role;               /**< bitmap of MAP_ROLE_* */
 	enum map_profile profile;    /**< one of MAP_PROFILE_* */
 	cmdu_handler_t map_cmdu_rx;  /**< multiap CMDU handler function */
diff --git a/src/ipc/comm.c b/src/ipc/comm.c
index eacdbdf6e703981e78ba405dcd08e6e0b4e545d1..4bd7b596bac3eb8c5bb04e9664954b55d8d4c431 100644
--- a/src/ipc/comm.c
+++ b/src/ipc/comm.c
@@ -1,5 +1,6 @@
 /*
- * comm.c - ipc utility constructs - msg_queue, worker thread etc.
+ * comm_impl.c
+ * Communication interface between controller and agents.
  *
  * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
@@ -7,152 +8,48 @@
  *
  */
 
-#include <stdio.h>
-#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
 #include <pthread.h>
-#include "liblist.h"
-#include "utils.h"
+
+#include <libubox/list.h>
 #include "debug.h"
 #include "comm.h"
 
-//TODO: almost everything will be moved to utils
-
-int mq_init(struct msg_queue *q)
-{
-	INIT_LIST_HEAD(&q->mq);
-	pthread_mutex_init(&q->lock, NULL);
-	pthread_cond_init(&q->empty, NULL);
-
-	return 0;
-}
-
-int mq_destroy(struct msg_queue *q)
-{
-	pthread_mutex_destroy(&q->lock);
-	pthread_cond_destroy(&q->empty);
-
-	return 0;
-}
-
-void mq_flush(struct msg_queue *q)
-{
-	pthread_mutex_lock(&q->lock);
-	list_flush(&q->mq, struct msg, list);
-	//TODO: free msg.data
-	pthread_mutex_unlock(&q->lock);
-}
-
-static int __mq_length(struct msg_queue *q)
-{
-	struct list_head *p;
-	int count = 0;
-
-	list_for_each(p, &q->mq)
-		count++;
-
-	return count;
+int _comm_cmd(void *comm, void *self, void *dest,
+		int cmd, void *data, int len,
+		void (*response)(void *self, void *resp, int rlen, void *cookie),
+		void *cookie, int is_async)
+{
+#define CMD_TIMEOUT	8  /* in secs */
+	int ret = -1;
+	struct CMD_struct *cmdi;
+
+	if (cmd <= CMD_UNDEFINED || cmd > CMD_MAX) {
+		warn("Cntlr: Unknown CMD '%d'\n", cmd);
+		return ret;
+	}
+
+	cmdi = alloca(sizeof(struct CMD_struct));
+	if (!cmdi) {
+		warn("oom: alloc CMD failed!\n");
+		return ret;
+	}
+	memset(cmdi, 0, sizeof(*cmdi));
+	cmdi->id = cmd;
+	cmdi->len = len;
+	cmdi->data = data;
+	cmdi->timeout = CMD_TIMEOUT;
+	cmdi->resp = response;
+	cmdi->self = self;
+	cmdi->cookie = cookie;
+	cmdi->ipc = NULL;
+	trace_cmd("Sending CMD %s  (&cmd = %p,  dest = %p) --->\n",
+					CMDSTRING(cmd), &cmdi, dest);
+
+	ret = is_async ?
+			comm_send_msg_async(comm, dest, cmdi) :
+			comm_send_msg(comm, dest, cmdi);
+
+	return ret;
 }
-
-void mq_free_msg(struct msg *m)
-{
-	if (m->msg.data)
-		free(m->msg.data);
-
-	free(m);
-}
-
-struct msg *mq_alloc_msg(int sizeof_ipc)
-{
-	struct msg *m;
-
-	m = calloc(1, sizeof(struct msg) + sizeof_ipc);
-	if (!m)
-		return NULL;
-
-	m->msg.ipc = sizeof_ipc ? (m + 1) : NULL;
-
-	return m;
-}
-
-void mq_wait_for_msg(struct msg_queue *q)
-{
-	pthread_mutex_lock(&q->lock);
-	while (list_empty(&q->mq))
-		pthread_cond_wait(&q->empty, &q->lock);
-
-	pthread_mutex_unlock(&q->lock);
-}
-
-void mq_signal_msg(struct msg_queue *q)
-{
-	pthread_mutex_lock(&q->lock);
-	if (!list_empty(&q->mq))
-		pthread_cond_signal(&q->empty);
-
-	pthread_mutex_unlock(&q->lock);
-}
-
-void mq_enqueue_msg(struct msg_queue *q, struct msg *m)
-{
-	pthread_mutex_lock(&q->lock);
-	list_add_tail(&m->list, &q->mq);
-	loud("After enqueue msg (%p),  qlen =%d\n",
-					m, __mq_length(q));
-	pthread_mutex_unlock(&q->lock);
-}
-
-static struct msg *__mq_dequeue_msg(struct msg_queue *q)
-{
-	struct msg *m = NULL;
-
-	m = list_first_entry(&q->mq, struct msg, list);
-	if (m)
-		list_del(&m->list);
-
-	return m;
-}
-
-struct msg *mq_dequeue_msg(struct msg_queue *q)
-{
-	struct msg *m;
-
-	pthread_mutex_lock(&q->lock);
-	while (list_empty(&q->mq))
-		pthread_cond_wait(&q->empty, &q->lock);
-
-	m = __mq_dequeue_msg(q);
-	loud("After dequeue msg (%p),  qlen =%d\n",
-					m, __mq_length(q));
-
-	pthread_mutex_unlock(&q->lock);
-
-	return m;
-}
-
-void *worker_run(void *data)
-{
-	struct worker *w = (struct worker *)data;
-
-	for (;;)
-		w->func(w->data);
-
-	return NULL;
-}
-
-int worker_init(struct worker *w)
-{
-	int ret;
-
-	ret = pthread_create(&w->id, NULL, worker_run, w);
-	if (ret)
-		return -1;
-
-	return 0;
-}
-
-void worker_exit(struct worker *w)
-{
-	pthread_cancel(w->id);
-	pthread_join(w->id, NULL);
-}
-
diff --git a/src/ipc/comm.h b/src/ipc/comm.h
index abc46087f37e41e114260758b0371627bae0e755..9c75bc34f007626fe72d1df1ec31134cf4f67da7 100644
--- a/src/ipc/comm.h
+++ b/src/ipc/comm.h
@@ -35,8 +35,11 @@ enum {
 	CMD_GET_MOBID,
 	CMD_GET_OUI,
 	CMD_FLUSH_ARP,
-	CMD_DISCONNECT_STA,
+	CMD_DISCONNECT_STA = 20,
+
+	CMD_MAP_CMDU,
 	/* .. others as needed */
+
 	CMD_MAX,
 };
 
@@ -62,6 +65,8 @@ static char *_CMDSTRING[] = {
 	[CMD_GET_OUI] =			"GET_OUI",
 	[CMD_FLUSH_ARP] =		"FLUSH_ARP",
 	[CMD_DISCONNECT_STA] =		"CMD_DISCONNECT_STA",
+
+	[CMD_MAP_CMDU] =                "CMD_MAP_CMDU",
 };
 
 static inline char *CMDSTRING(int m)
@@ -130,36 +135,6 @@ struct msg {
 	struct list_head list;
 };
 
-/* shared msg queue */
-struct msg_queue {
-	struct list_head mq;
-	pthread_mutex_t lock;
-	pthread_cond_t empty;
-};
-
-int mq_init(struct msg_queue *q);
-int mq_destroy(struct msg_queue *q);
-void mq_flush(struct msg_queue *q);
-struct msg *mq_alloc_msg(int sizeof_ipc);
-void mq_free_msg(struct msg *m);
-
-#define msg_ipc(__m)	__m->msg.ipc
-
-extern void mq_wait_for_msg(struct msg_queue *q);
-extern void mq_signal_msg(struct msg_queue *q);
-extern void mq_enqueue_msg(struct msg_queue *q, struct msg *m);
-extern struct msg *mq_dequeue_msg(struct msg_queue *q);
-
-/* worker thread */
-struct worker {
-	pthread_t id;
-	void *data;
-	int (*func)(void *data);
-};
-
-extern int worker_init(struct worker *w);
-extern void worker_exit(struct worker *w);
-//extern void *worker_run(void *data);
 
 //TODO: following structs will be deprecated in favour of TLVs
 
diff --git a/src/ipc/comm_impl.c b/src/ipc/comm_impl.c
deleted file mode 100644
index 4bd7b596bac3eb8c5bb04e9664954b55d8d4c431..0000000000000000000000000000000000000000
--- a/src/ipc/comm_impl.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * comm_impl.c
- * Communication interface between controller and agents.
- *
- * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
- *
- * Author: anjan.chanda@iopsys.eu
- *
- */
-
-#include <string.h>
-#include <alloca.h>
-#include <pthread.h>
-
-#include <libubox/list.h>
-#include "debug.h"
-#include "comm.h"
-
-int _comm_cmd(void *comm, void *self, void *dest,
-		int cmd, void *data, int len,
-		void (*response)(void *self, void *resp, int rlen, void *cookie),
-		void *cookie, int is_async)
-{
-#define CMD_TIMEOUT	8  /* in secs */
-	int ret = -1;
-	struct CMD_struct *cmdi;
-
-	if (cmd <= CMD_UNDEFINED || cmd > CMD_MAX) {
-		warn("Cntlr: Unknown CMD '%d'\n", cmd);
-		return ret;
-	}
-
-	cmdi = alloca(sizeof(struct CMD_struct));
-	if (!cmdi) {
-		warn("oom: alloc CMD failed!\n");
-		return ret;
-	}
-	memset(cmdi, 0, sizeof(*cmdi));
-	cmdi->id = cmd;
-	cmdi->len = len;
-	cmdi->data = data;
-	cmdi->timeout = CMD_TIMEOUT;
-	cmdi->resp = response;
-	cmdi->self = self;
-	cmdi->cookie = cookie;
-	cmdi->ipc = NULL;
-	trace_cmd("Sending CMD %s  (&cmd = %p,  dest = %p) --->\n",
-					CMDSTRING(cmd), &cmdi, dest);
-
-	ret = is_async ?
-			comm_send_msg_async(comm, dest, cmdi) :
-			comm_send_msg(comm, dest, cmdi);
-
-	return ret;
-}
diff --git a/src/ipc/comm_ubus.c b/src/ipc/comm_ubus.c
index 3e113774c92e34655720e1bfc5bf1b516a0db9bd..fafb1dc6fbb06e0cbaa19a1a12cf46bf59d72511 100644
--- a/src/ipc/comm_ubus.c
+++ b/src/ipc/comm_ubus.c
@@ -105,7 +105,7 @@ int comm_ubus_send_msg(void *handle, void *dest, struct CMD_struct *cmd)
 	if (cmd->data)
 		blobmsg_add_string(&bb, "data", cmd->data);
 
-	ret = ubus_invoke(priv->ctx, (ubus_object_t)dest,
+	ret = ubus_invoke(priv->ctx, (ubus_object_t)(uintptr_t)dest,
 				"cmd", bb.head,
 				comm_ubus_msg_resphandler,
 				cmd, cmd->timeout * 1000);
@@ -136,7 +136,7 @@ int comm_ubus_send_msg_async(void *handle, void *dest, struct CMD_struct *cmd)
 		blobmsg_add_string(&bb, "data", cmd->data);
 
 	ret = ubus_invoke_async(priv->ctx,
-				(ubus_object_t)dest, "cmdi",
+				(ubus_object_t)(uintptr_t)dest, "cmdi",
 				bb.head, ureq);
 	if (ret) {
 		warn("CMD %s failed (ret = %s)\n",
diff --git a/src/ipc/msgqueue.c b/src/ipc/msgqueue.c
new file mode 100644
index 0000000000000000000000000000000000000000..52c1afbe065afaf9142fedab7cecb4f7aec85e4b
--- /dev/null
+++ b/src/ipc/msgqueue.c
@@ -0,0 +1,128 @@
+/*
+ * msgqueue.c - message queue utility functions.
+ *
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "liblist.h"
+#include "utils.h"
+#include "debug.h"
+#include "comm.h"
+#include "msgqueue.h"
+
+int mq_init(struct msg_queue *q)
+{
+	INIT_LIST_HEAD(&q->mq);
+	pthread_mutex_init(&q->lock, NULL);
+	pthread_cond_init(&q->empty, NULL);
+
+	return 0;
+}
+
+int mq_destroy(struct msg_queue *q)
+{
+	pthread_mutex_destroy(&q->lock);
+	pthread_cond_destroy(&q->empty);
+
+	return 0;
+}
+
+void mq_flush(struct msg_queue *q)
+{
+	pthread_mutex_lock(&q->lock);
+	list_flush(&q->mq, struct msg, list);
+	//TODO: free msg.data
+	pthread_mutex_unlock(&q->lock);
+}
+
+static int __mq_length(struct msg_queue *q)
+{
+	struct list_head *p;
+	int count = 0;
+
+	list_for_each(p, &q->mq)
+		count++;
+
+	return count;
+}
+
+void mq_free_msg(struct msg *m)
+{
+	if (m->msg.data)
+		free(m->msg.data);
+
+	free(m);
+}
+
+struct msg *mq_alloc_msg(int sizeof_ipc)
+{
+	struct msg *m;
+
+	m = calloc(1, sizeof(struct msg) + sizeof_ipc);
+	if (!m)
+		return NULL;
+
+	m->msg.ipc = sizeof_ipc ? (m + 1) : NULL;
+
+	return m;
+}
+
+void mq_wait_for_msg(struct msg_queue *q)
+{
+	pthread_mutex_lock(&q->lock);
+	while (list_empty(&q->mq))
+		pthread_cond_wait(&q->empty, &q->lock);
+
+	pthread_mutex_unlock(&q->lock);
+}
+
+void mq_signal_msg(struct msg_queue *q)
+{
+	pthread_mutex_lock(&q->lock);
+	if (!list_empty(&q->mq))
+		pthread_cond_signal(&q->empty);
+
+	pthread_mutex_unlock(&q->lock);
+}
+
+void mq_enqueue_msg(struct msg_queue *q, struct msg *m)
+{
+	pthread_mutex_lock(&q->lock);
+	list_add_tail(&m->list, &q->mq);
+	loud("After enqueue msg (%p),  qlen =%d\n",
+					m, __mq_length(q));
+	pthread_mutex_unlock(&q->lock);
+}
+
+static struct msg *__mq_dequeue_msg(struct msg_queue *q)
+{
+	struct msg *m = NULL;
+
+	m = list_first_entry(&q->mq, struct msg, list);
+	if (m)
+		list_del(&m->list);
+
+	return m;
+}
+
+struct msg *mq_dequeue_msg(struct msg_queue *q)
+{
+	struct msg *m;
+
+	pthread_mutex_lock(&q->lock);
+	while (list_empty(&q->mq))
+		pthread_cond_wait(&q->empty, &q->lock);
+
+	m = __mq_dequeue_msg(q);
+	loud("After dequeue msg (%p),  qlen =%d\n",
+					m, __mq_length(q));
+
+	pthread_mutex_unlock(&q->lock);
+
+	return m;
+}
diff --git a/src/ipc/msgqueue.h b/src/ipc/msgqueue.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a6de3618b4a9743c6d1ff6175a9af55ed33246b
--- /dev/null
+++ b/src/ipc/msgqueue.h
@@ -0,0 +1,34 @@
+/*
+ * msgqueue.h
+ * Simple message queue header file
+ *
+ * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+
+#ifndef MSGQUEUE_H
+#define MSGQUEUE_H
+
+/* shared msg queue */
+struct msg_queue {
+	struct list_head mq;
+	pthread_mutex_t lock;
+	pthread_cond_t empty;
+};
+
+extern int mq_init(struct msg_queue *q);
+extern int mq_destroy(struct msg_queue *q);
+extern void mq_flush(struct msg_queue *q);
+extern struct msg *mq_alloc_msg(int sizeof_ipc);
+extern void mq_free_msg(struct msg *m);
+
+#define msg_ipc(__m)	__m->msg.ipc
+
+extern void mq_wait_for_msg(struct msg_queue *q);
+extern void mq_signal_msg(struct msg_queue *q);
+extern void mq_enqueue_msg(struct msg_queue *q, struct msg *m);
+extern struct msg *mq_dequeue_msg(struct msg_queue *q);
+
+#endif /* MSGQUEUE_H */
diff --git a/src/ipc/worker.c b/src/ipc/worker.c
new file mode 100644
index 0000000000000000000000000000000000000000..808469ab2afcdc89b81007acb656ddf6d875eaf3
--- /dev/null
+++ b/src/ipc/worker.c
@@ -0,0 +1,44 @@
+/*
+ * worker.c - worker thread and pool utility functions
+ *
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "liblist.h"
+#include "utils.h"
+#include "debug.h"
+#include "comm.h"
+#include "worker.h"
+
+static void *worker_run(void *data)
+{
+	struct worker *w = (struct worker *)data;
+
+	for (;;)
+		w->func(w->data);
+
+	return NULL;
+}
+
+int worker_init(struct worker *w)
+{
+	int ret;
+
+	ret = pthread_create(&w->id, NULL, worker_run, w);
+	if (ret)
+		return -1;
+
+	return 0;
+}
+
+void worker_exit(struct worker *w)
+{
+	pthread_cancel(w->id);
+	pthread_join(w->id, NULL);
+}
diff --git a/src/ipc/worker.h b/src/ipc/worker.h
new file mode 100644
index 0000000000000000000000000000000000000000..155e467ecea238c233b748c4b63b8892694dbdcd
--- /dev/null
+++ b/src/ipc/worker.h
@@ -0,0 +1,25 @@
+/*
+ * worker.h
+ * Defines worker thread and utility functions.
+ *
+ * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ * Author: anjan.chanda@iopsys.eu
+ *
+ */
+
+#ifndef WORKER_H
+#define WORKER_H
+
+
+/* worker thread */
+struct worker {
+	pthread_t id;
+	void *data;
+	int (*func)(void *data);
+};
+
+extern int worker_init(struct worker *w);
+extern void worker_exit(struct worker *w);
+
+#endif /* WORKER_H */
diff --git a/src/plugins/README-plugins.md b/src/plugins/README-plugins.md
deleted file mode 100644
index 945513db09edee643b21ec9b551b31e8be78965e..0000000000000000000000000000000000000000
--- a/src/plugins/README-plugins.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This file will describe how to add a new plugin (extra feature) to the
-controller.
diff --git a/src/utils/alloctrace.h b/src/utils/alloctrace.h
index c41c3046713e15425b32fcb8d9e287d2376f89b6..2480db069df6650affbc8c2ceb332d1a861a5d08 100644
--- a/src/utils/alloctrace.h
+++ b/src/utils/alloctrace.h
@@ -1,7 +1,7 @@
 /*
  * alloctrace.h - trace heap memory alloc and free
  *
- * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
+ * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
  *
  * Author: anjan.chanda@iopsys.eu
  *
@@ -30,9 +30,14 @@ static inline void init_alloctrace(const char *progname)
 {
 }
 
-static void exit_alloctrace(void)
+static inline void exit_alloctrace(void)
 {
 }
+
+#define dbg_malloc
+#define dbg_free
+#define dbg_calloc
+
 #endif /* TRACE_ALLOC */
 
 #endif /* ALLOCTRACE_H */