diff --git a/Makefile.am b/Makefile.am
index 8409a40441c4e0bba8114284cb6a4638e38e0091..935a422c66e3ce44fe17ccdcc7a88b2d4fa35e48 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,4 +7,5 @@ gbridge_SOURCES = main.c \
 		  netlink.c \
 		  debug.c \
 		  greybus.c \
-		  svc.c
\ No newline at end of file
+		  svc.c \
+		  controller.c
\ No newline at end of file
diff --git a/controller.c b/controller.c
new file mode 100644
index 0000000000000000000000000000000000000000..120184af62295ea56e128cfd2aa2b285c1b01fe9
--- /dev/null
+++ b/controller.c
@@ -0,0 +1,369 @@
+/*
+ * GBridge (Greybus Bridge)
+ * Copyright (c) 2016 Alexandre Bailon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <debug.h>
+#include <gbridge.h>
+#include <netlink.h>
+#include <controller.h>
+
+static
+TAILQ_HEAD(conn_head, connection)
+connections = TAILQ_HEAD_INITIALIZER(connections);
+static
+TAILQ_HEAD(ctrl_head, controller)
+controllers = TAILQ_HEAD_INITIALIZER(controllers);
+static uint8_t g_intf_id = 0;
+static pthread_mutex_t intf_alloc_lock;
+
+void cport_pack(struct gb_operation_msg_hdr *header, uint16_t cport_id)
+{
+	header->pad[0] = cport_id;
+}
+
+uint16_t cport_unpack(struct gb_operation_msg_hdr *header)
+{
+	return (uint16_t) header->pad[0];
+}
+
+void cport_clear(struct gb_operation_msg_hdr *header)
+{
+	header->pad[0] = 0;
+}
+
+static int _read(int fd, void *data, size_t len)
+{
+	int ret;
+	uint8_t *pdata = data;
+
+	do {
+		ret = read(fd, pdata, len);
+		if (ret < 0) {
+			pr_err("Failed to read header\n");
+			return ret;
+		}
+		pdata += ret;
+		len -= ret;
+	}
+	while (len);
+
+	return 0;
+}
+
+int read_gb_msg(int fd, void *data, size_t max_len)
+{
+	int ret;
+	uint8_t *pdata = data;
+	size_t len = sizeof(struct gb_operation_msg_hdr);
+
+	ret = _read(fd, pdata, len);
+	if (ret < 0)
+		return ret;
+
+	pdata += len;
+	pr_dump(data, 8);
+	len = gb_operation_msg_size(data);
+	if (len > max_len)
+		return -EMSGSIZE;
+	len -= sizeof(struct gb_operation_msg_hdr);
+
+	ret = _read(fd, pdata, len);
+	if (ret < 0)
+		return ret;
+
+	return gb_operation_msg_size(data);
+}
+
+static struct connection *cport_id_to_connection(uint16_t cport_id)
+{
+	struct connection *conn;
+
+	TAILQ_FOREACH(conn, &connections, node) {
+		if (conn->cport2_id == cport_id)
+			return conn;
+	}
+
+	return NULL;
+}
+
+static struct connection *hd_cport_id_to_connection(uint16_t cport_id)
+{
+	struct connection *conn;
+
+	TAILQ_FOREACH(conn, &connections, node) {
+		if (conn->cport1_id == cport_id)
+			return conn;
+	}
+
+	return NULL;
+}
+
+static void *interface_recv(void *data)
+{
+	int ret;
+	uint16_t cport_id;
+	struct connection *conn;
+	struct interface *intf = data;
+	struct controller *ctrl = intf->ctrl;
+	uint8_t buffer[GB_NETLINK_MTU];
+
+	while (1) {
+		ret = ctrl->intf_read(intf, &cport_id, buffer, GB_NETLINK_MTU);
+		if (ret < 0) {
+			pr_err("Failed to read data: %d\n", ret);
+			continue;
+		}
+
+		pr_dump(buffer, ret);
+
+		conn = cport_id_to_connection(cport_id);
+		if (!conn) {
+			pr_err("Received data on invalid cport number\n");
+			continue;
+		}
+
+		ret = netlink_send(conn->cport1_id, buffer, ret);
+		if (ret < 0) {
+			pr_err("Failed to transmit data\n");
+		}
+	}
+
+	return NULL;
+}
+
+static uint8_t intf_id_alloc(void)
+{
+	static uint8_t intf_id;
+
+	pthread_mutex_lock(&intf_alloc_lock);
+	intf_id = ++g_intf_id;
+	pthread_mutex_unlock(&intf_alloc_lock);
+
+	return intf_id;
+}
+
+struct interface *interface_create(struct controller *ctrl, uint32_t vendor_id,
+				   uint32_t product_id, uint64_t serial_id,
+				   void *priv)
+{
+	int ret;
+	struct interface *intf;
+
+	intf = malloc(sizeof(*intf));
+	if (!intf)
+		return NULL;
+
+	intf->ctrl = ctrl;
+	intf->priv = priv;
+	intf->id = intf_id_alloc();
+	intf->vendor_id = vendor_id;
+	intf->product_id = product_id;
+	intf->serial_id = serial_id;
+
+	if (ctrl->interface_create)
+		if (ctrl->interface_create(intf))
+			goto err_free_intf;
+
+	if (ctrl->intf_read) {
+		ret = pthread_create(&intf->thread, NULL, interface_recv, intf);
+		if (ret)
+			goto err_destroy_intf;
+	}
+
+	TAILQ_INSERT_TAIL(&ctrl->interfaces, intf, node);
+
+	return intf;
+
+ err_destroy_intf:
+	if (ctrl->interface_destroy)
+		ctrl->interface_destroy(intf);
+ err_free_intf:
+	free(intf);
+
+	return NULL;
+}
+
+void interface_destroy(struct interface *intf)
+{
+	if (intf->ctrl->intf_read) {
+		pthread_cancel(intf->thread);
+		pthread_join(intf->thread, NULL);
+	}
+
+	TAILQ_REMOVE(&intf->ctrl->interfaces, intf, node);
+
+	if (intf->ctrl->interface_destroy)
+		intf->ctrl->interface_destroy(intf);
+	free(intf);
+}
+
+void interfaces_destroy(struct controller *ctrl)
+{
+	struct interface *intf, *tmp;
+
+	TAILQ_FOREACH_SAFE(intf, &ctrl->interfaces, node, tmp) {
+		interface_destroy(intf);
+	}
+}
+
+int interface_hotplug(struct interface *intf)
+{
+	int ret;
+	ret = svc_send_intf_hotplug_event(intf->id, intf->vendor_id,
+					  intf->product_id, intf->serial_id);
+
+	return ret;
+}
+
+int interface_hot_unplug(struct interface *intf)
+{
+	return 0;
+}
+
+static struct interface *get_interface(uint8_t intf_id)
+{
+	struct controller *ctrl;
+	struct interface *intf;
+
+	TAILQ_FOREACH(ctrl, &controllers, node) {
+		TAILQ_FOREACH(intf, &ctrl->interfaces, node) {
+			if (intf->id == intf_id)
+				return intf;
+		}
+	}
+
+	return NULL;
+}
+
+int
+connection_create(uint8_t intf1_id, uint16_t cport1_id,
+		  uint8_t intf2_id, uint16_t cport2_id)
+{
+	struct interface *intf;
+	struct connection *conn;
+
+	intf = get_interface(intf2_id);
+	if (!intf)
+		return -EINVAL;
+
+	conn = malloc(sizeof(*conn));
+	if (!conn)
+		return -ENOMEM;
+
+	conn->intf = intf;
+	conn->cport1_id = cport1_id;
+	conn->cport2_id = cport2_id;
+	TAILQ_INSERT_TAIL(&connections, conn, node);
+
+	return 0;
+}
+
+int
+connection_destroy(uint8_t intf1_id, uint16_t cport1_id,
+		   uint8_t intf2_id, uint16_t cport2_id)
+{
+	struct connection *conn;
+
+	conn = hd_cport_id_to_connection(cport1_id);
+	if (!conn) {
+		return -EINVAL;
+	}
+
+	TAILQ_REMOVE(&connections, conn, node);
+	free(conn);
+
+	return 0;
+}
+
+static void register_controllers(void)
+{
+	/* TODO Register controllers */
+}
+
+static void *controller_loop(void *data)
+{
+	struct controller *ctrl = data;
+
+	ctrl->event_loop(ctrl);
+
+	return NULL;
+}
+
+static int controller_loop_init(struct controller *ctrl)
+{
+	if (!ctrl->event_loop)
+		return 0;
+
+	return pthread_create(&ctrl->thread, NULL, controller_loop, ctrl);
+}
+
+static void controller_loop_exit(struct controller *ctrl)
+{
+	if (!ctrl->event_loop)
+		return;
+
+	pthread_cancel(ctrl->thread);
+	pthread_join(ctrl->thread, NULL);
+}
+
+int controller_write(uint16_t cport_id, void *data, size_t len)
+{
+	struct connection *conn;
+	struct controller *ctrl;
+
+	conn = hd_cport_id_to_connection(cport_id);
+	if (!conn)
+		return -EINVAL;
+
+	pr_dump(data, len);
+
+	ctrl = conn->intf->ctrl;
+	return ctrl->write(conn, data, len);
+}
+
+void controllers_init(void)
+{
+	int ret;
+	struct controller *ctrl, *tmp;
+
+	register_controllers();
+	TAILQ_FOREACH_SAFE(ctrl, &controllers, node, tmp) {
+		ret = ctrl->init(ctrl);
+		if (ret) {
+			pr_err("Failed to init %s: %d\n", ctrl->name, ret);
+			TAILQ_REMOVE(&controllers, ctrl, node);
+			continue;
+		}
+		TAILQ_INIT(&ctrl->interfaces);
+		controller_loop_init(ctrl);
+	}
+}
+
+void controllers_exit(void)
+{
+	struct controller *ctrl;
+
+	TAILQ_FOREACH(ctrl, &controllers, node) {
+		controller_loop_exit(ctrl);
+		interfaces_destroy(ctrl);
+		ctrl->exit(ctrl);
+	}
+}
diff --git a/controller.h b/controller.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7b5b588c8ff065867eeae5c187e509015132361
--- /dev/null
+++ b/controller.h
@@ -0,0 +1,95 @@
+/*
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * Author: Alexandre Bailon <abailon@baylibre.com>
+ * Copyright (c) 2016 Alexandre Bailon
+ */
+
+#ifndef __CONTROLLER_H__
+#define __CONTROLLER_H__
+
+#include <pthread.h>
+#include <sys/queue.h>
+
+#include <gbridge.h>
+
+struct connection {
+	uint16_t cport1_id;
+	uint16_t cport2_id;
+	struct interface *intf;
+	 TAILQ_ENTRY(connection) node;
+};
+
+struct interface {
+	uint32_t vendor_id;
+	uint32_t product_id;
+	uint64_t serial_id;
+
+	void *priv;
+
+	/* gb intf private data */
+	uint8_t id;
+	struct controller *ctrl;
+	 TAILQ_ENTRY(interface) node;
+	pthread_t thread;
+};
+
+struct controller {
+	const char *name;
+
+	int (*init) (struct controller * ctrl);
+	void (*exit) (struct controller * ctrl);
+	int (*event_loop) (struct controller * ctrl);
+
+	int (*interface_create) (struct interface * intf);
+	void (*interface_destroy) (struct interface * intf);
+
+	int (*write) (struct connection * conn, void *data, size_t len);
+	int (*read) (struct connection * conn, void *data, size_t len);
+	int (*intf_read) (struct interface * intf,
+			  uint16_t * cport_id, void *data, size_t len);
+
+	void *priv;
+
+	/* gb controller private data */
+	pthread_t thread;
+	 TAILQ_ENTRY(controller) node;
+	 TAILQ_HEAD(head, interface) interfaces;
+};
+
+extern struct controller bluetooth_controller;
+
+void cport_pack(struct gb_operation_msg_hdr *header, uint16_t cport_id);
+uint16_t cport_unpack(struct gb_operation_msg_hdr *header);
+void cport_clear(struct gb_operation_msg_hdr *header);
+int read_gb_msg(int fd, void *data, size_t len);
+
+struct interface *interface_create(struct controller *ctrl,
+				   uint32_t vendor_id, uint32_t product_id,
+				   uint64_t serial_id, void *priv);
+int interface_hotplug(struct interface *intf);
+int interface_hot_unplug(struct interface *intf);
+void interface_destroy(struct interface *intf);
+void interfaces_destroy(struct controller *ctrl);
+
+int connection_create(uint8_t intf1_id, uint16_t cport1_id,
+		      uint8_t intf2_id, uint16_t cport2_id);
+int connection_destroy(uint8_t intf1_id, uint16_t cport1_id,
+		       uint8_t intf2_id, uint16_t cport2_id);
+
+int controller_write(uint16_t cport_id, void *data, size_t len);
+void controllers_init(void);
+void controllers_exit(void);
+
+#endif				/* __CONTROLLER_H__ */
diff --git a/main.c b/main.c
index e9759d302cfe2073477013f12c8d1b4303fd579b..d3c73408cd84672cbe26d0fb4ae7d55fba6de81e 100644
--- a/main.c
+++ b/main.c
@@ -19,6 +19,7 @@
 #include <signal.h>
 
 #include <debug.h>
+#include <controller.h>
 
 #include "gbridge.h"
 #include "netlink.h"
@@ -54,7 +55,12 @@ int main(int argc, char *argv[])
 		goto err_netlink_exit;
 	}
 
+	controllers_init();
+
 	netlink_loop();
+
+	controllers_exit();
+
 	netlink_exit();
 
 	return 0;
diff --git a/netlink.c b/netlink.c
index 782b41c43d6f51fc513114d8209c1f05aab5efb1..51ff4c3ca5870d463c27642473776d4e1273c54e 100644
--- a/netlink.c
+++ b/netlink.c
@@ -21,6 +21,7 @@
 
 #include <debug.h>
 #include <gbridge.h>
+#include <controller.h>
 
 #include <netlink/genl/mngt.h>
 #include <netlink/genl/ctrl.h>
@@ -61,7 +62,7 @@ parse_gb_nl_msg(struct nl_cache_ops *unused, struct genl_cmd *cmd,
 			       hdr->type, ret);
 		}
 	} else {
-		/* TODO: transfer data to modules */
+		ret = controller_write(hd_cport_id, hdr, gb_operation_msg_size(hdr));
 	}
 
 	return 0;
diff --git a/svc.c b/svc.c
index a5a200b5c3e0be82cecb64002137e5f19fafddd9..6f448fc0210a7def351c891fd43dc059f8e1857b 100644
--- a/svc.c
+++ b/svc.c
@@ -22,6 +22,7 @@
 #include <string.h>
 
 #include <gbridge.h>
+#include <controller.h>
 
 /* TODO: Can we use other IDs ? */
 #define ENDO_ID 0x4755
@@ -29,6 +30,36 @@
 
 static int svc_send_hello_request(void);
 
+static int svc_dme_peer_get_response(struct operation *op, uint16_t result_code,
+				     uint32_t attr_value)
+{
+	struct gb_svc_dme_peer_get_response *resp;
+	size_t op_size = sizeof(*resp);
+
+	if (greybus_alloc_response(op, op_size))
+		return -ENOMEM;
+
+	resp = operation_to_response(op);
+	resp->result_code = htole16(result_code);
+	resp->attr_value = htole32(attr_value);
+
+	return 0;
+}
+
+static int svc_dme_peer_set_response(struct operation *op, uint16_t result_code)
+{
+	struct gb_svc_dme_peer_set_response *resp;
+	size_t op_size = sizeof(*resp);
+
+	if (greybus_alloc_response(op, op_size))
+		return -ENOMEM;
+
+	resp = operation_to_response(op);
+	resp->result_code = htole16(result_code);
+
+	return 0;
+}
+
 static int svc_interface_v_sys_enable_response(struct operation *op,
 					       uint8_t result_code)
 {
@@ -144,6 +175,50 @@ static int svc_interface_device_id_request(struct operation *op)
 	return 0;
 }
 
+static int svc_connection_create_request(struct operation *op)
+{
+	struct gb_svc_conn_create_request *req;
+	uint8_t intf1_id;
+	uint16_t cport1_id;
+	uint8_t intf2_id;
+	uint16_t cport2_id;
+
+	req = operation_to_request(op);
+	intf1_id = req->intf1_id;
+	cport1_id = le16toh(req->cport1_id);
+	intf2_id = req->intf2_id;
+	cport2_id = le16toh(req->cport2_id);
+
+	return connection_create(intf1_id, cport1_id, intf2_id, cport2_id);
+}
+
+static int svc_connection_destroy_request(struct operation *op)
+{
+	struct gb_svc_conn_destroy_request *req;
+	uint8_t intf1_id;
+	uint16_t cport1_id;
+	uint8_t intf2_id;
+	uint16_t cport2_id;
+
+	req = operation_to_request(op);
+	intf1_id = req->intf1_id;
+	cport1_id = le16toh(req->cport1_id);
+	intf2_id = req->intf2_id;
+	cport2_id = le16toh(req->cport2_id);
+
+	return connection_destroy(intf1_id, cport1_id, intf2_id, cport2_id);
+}
+
+static int svc_dme_peer_get_request(struct operation *op)
+{
+	return svc_dme_peer_get_response(op, 0, 0x0126);
+}
+
+static int svc_dme_peer_set_request(struct operation *op)
+{
+	return svc_dme_peer_set_response(op, 0);
+}
+
 static int svc_interface_v_sys_enable_request(struct operation *op)
 {
 	return svc_interface_v_sys_enable_response(op, GB_SVC_INTF_VSYS_OK);
@@ -212,6 +287,14 @@ int svc_handler(struct operation *op)
 		return svc_ping_request(op);
 	case GB_SVC_TYPE_INTF_DEVICE_ID:
 		return svc_interface_device_id_request(op);
+	case GB_SVC_TYPE_CONN_CREATE:
+		return svc_connection_create_request(op);
+	case GB_SVC_TYPE_CONN_DESTROY:
+		return svc_connection_destroy_request(op);
+	case GB_SVC_TYPE_DME_PEER_GET:
+		return svc_dme_peer_get_request(op);
+	case GB_SVC_TYPE_DME_PEER_SET:
+		return svc_dme_peer_set_request(op);
 	case GB_SVC_TYPE_INTF_HOTPLUG:
 		return svc_interface_hotplug_request(op);
 	case GB_SVC_TYPE_INTF_HOT_UNPLUG: