diff --git a/Makefile.am b/Makefile.am
index d0c4ead0c18e53416776e71a64265b4f84af253b..8409a40441c4e0bba8114284cb6a4638e38e0091 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,4 +5,6 @@ gbridge_CFLAGS += `pkg-config --cflags libnl-3.0 libnl-genl-3.0`
 
 gbridge_SOURCES = main.c \
 		  netlink.c \
-		  debug.c
\ No newline at end of file
+		  debug.c \
+		  greybus.c \
+		  svc.c
\ No newline at end of file
diff --git a/gbridge.h b/gbridge.h
index b820da0658b3e52f2dc618ebbc7f7df1736fc284..6f66667f90273121e5d7ede6657b5010e525fd7d 100644
--- a/gbridge.h
+++ b/gbridge.h
@@ -25,14 +25,42 @@
 
 #define __packed  __attribute__((__packed__))
 
-/* Include kernel headers */
 #include <greybus.h>
 #include <greybus_protocols.h>
 #include <gb_netlink.h>
 
 #define SVC_CPORT		0
+#define OP_RESPONSE		0x80
+
+struct operation {
+	struct gb_operation_msg_hdr *req;
+	struct gb_operation_msg_hdr *resp;
+	uint16_t cport_id;
+	 TAILQ_ENTRY(operation) cnode;
+};
+
+typedef int (*greybus_handler_t) (struct operation *);
+
+#define operation_to_request(op)	\
+	(void *)((op)->req + 1)
+
+#define operation_to_response(op)	\
+	(void *)((op)->resp + 1)
 
 #define gb_operation_msg_size(hdr)	\
 	le16toh(((struct gb_operation_msg_hdr *)(hdr))->size)
 
+int svc_init(void);
+int svc_handler(struct operation *op);
+int svc_send_intf_hotplug_event(uint8_t intf_id,
+				uint32_t vendor_id,
+				uint32_t product_id, uint64_t serial_number);
+
+int greybus_init(void);
+struct operation *greybus_alloc_operation(uint8_t type,
+					  void *payload, size_t len);
+int greybus_alloc_response(struct operation *op, size_t size);
+int greybus_send_request(uint16_t cport_id, struct operation *op);
+int greybus_handler(uint16_t cport_id, struct gb_operation_msg_hdr *hdr);
+
 #endif /* _GBRIDGE_H_ */
diff --git a/greybus.c b/greybus.c
new file mode 100644
index 0000000000000000000000000000000000000000..8dd8abb798a6346881dcc920a3f3b5b38df18a75
--- /dev/null
+++ b/greybus.c
@@ -0,0 +1,271 @@
+/*
+ * 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 <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <debug.h>
+#include <gb_netlink.h>
+
+#include "gbridge.h"
+#include "netlink.h"
+
+static greybus_handler_t gb_handlers[GB_NETLINK_NUM_CPORT];
+static TAILQ_HEAD(head, operation) operations;
+
+enum gb_operation_result {
+	GB_OP_SUCCESS		= 0x00,
+	GB_OP_INTERRUPTED	= 0x01,
+	GB_OP_TIMEOUT		= 0x02,
+	GB_OP_NO_MEMORY		= 0x03,
+	GB_OP_PROTOCOL_BAD	= 0x04,
+	GB_OP_OVERFLOW		= 0x05,
+	GB_OP_INVALID		= 0x06,
+	GB_OP_RETRY		= 0x07,
+	GB_OP_NONEXISTENT	= 0x08,
+	GB_OP_UNKNOWN_ERROR	= 0xfe,
+	GB_OP_INTERNAL		= 0xff,
+};
+
+uint8_t greybus_errno_to_result(int err)
+{
+	switch (err) {
+	case 0:
+		return GB_OP_SUCCESS;
+	case -ENOMEM:
+		return GB_OP_NO_MEMORY;
+	case -EINTR:
+		return GB_OP_INTERRUPTED;
+	case -ETIMEDOUT:
+		return GB_OP_TIMEOUT;
+	case -EPROTO:
+	case -ENOSYS:
+		return GB_OP_PROTOCOL_BAD;
+	case -EINVAL:
+		return GB_OP_INVALID;
+	case -EOVERFLOW:
+		return GB_OP_OVERFLOW;
+	case -ENODEV:
+	case -ENXIO:
+		return GB_OP_NONEXISTENT;
+	case -EBUSY:
+		return GB_OP_RETRY;
+	default:
+		return GB_OP_UNKNOWN_ERROR;
+	}
+}
+
+static struct operation *_greybus_alloc_operation(struct gb_operation_msg_hdr
+						  *hdr)
+{
+	struct operation *op;
+
+	op = malloc(sizeof(*op));
+	if (!op)
+		return NULL;
+
+	op->req = malloc(gb_operation_msg_size(hdr));
+	if (!op->req) {
+		free(op);
+		return NULL;
+	}
+
+	op->resp = NULL;
+	memcpy(op->req, hdr, gb_operation_msg_size(hdr));
+
+	return op;
+}
+
+struct operation *greybus_alloc_operation(uint8_t type,
+					  void *payload, size_t len)
+{
+	uint16_t id = 0;
+	struct operation *op;
+
+	op = malloc(sizeof(*op));
+	if (!op)
+		return NULL;
+
+	op->req = malloc(sizeof(*op->req) + len);
+	if (!op->req) {
+		free(op);
+		return NULL;
+	}
+
+	if (++id == 0)
+		++id;
+
+	op->resp = NULL;
+	op->req->type = type;
+	op->req->size = htole16(sizeof(*op->req) + len);
+	op->req->operation_id = htole16(id);
+	memcpy(op->req + 1, payload, len);
+
+	return op;
+}
+
+static int _greybus_alloc_response(struct operation *op,
+				   struct gb_operation_msg_hdr *hdr)
+{
+	op->resp = malloc(gb_operation_msg_size(hdr));
+	if (!op->resp)
+		return -ENOMEM;
+
+	memcpy(op->resp, hdr, gb_operation_msg_size(hdr));
+
+	return 0;
+}
+
+int greybus_alloc_response(struct operation *op, size_t size)
+{
+	size += sizeof(*op->resp);
+	if (size > GB_NETLINK_MTU)
+		return -EINVAL;
+
+	op->resp = malloc(size);
+	if (!op->resp)
+		return -ENOMEM;
+
+	op->resp->operation_id = op->req->operation_id;
+	op->resp->size = size;
+	op->resp->type = op->req->type | 0x80;
+
+	return 0;
+}
+
+static void greybus_free_operation(struct operation *op)
+{
+	free(op->req);
+	free(op->resp);
+	free(op);
+}
+
+int greybus_send_request(uint16_t cport_id, struct operation *op)
+{
+	int len;
+	int ret;
+
+	len = gb_operation_msg_size(op->req);
+	pr_dump(op->req, len);
+
+	op->cport_id = cport_id;
+	TAILQ_INSERT_TAIL(&operations, op, cnode);
+	ret = netlink_send(cport_id, op->req, len);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int greybus_send_response(uint16_t cport_id, struct operation *op)
+{
+	int len;
+	int ret;
+
+	len = gb_operation_msg_size(op->resp);
+	pr_dump(op->resp, len);
+
+	ret = netlink_send(cport_id, op->resp, len);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+struct operation *greybus_find_operation(uint16_t cport_id, uint8_t id)
+{
+	struct operation *op;
+	TAILQ_FOREACH(op, &operations, cnode) {
+		if (op->req->operation_id == id) {
+			if (op->cport_id == cport_id) {
+				return op;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+int greybus_handler(uint16_t cport_id, struct gb_operation_msg_hdr *hdr)
+{
+	int ret;
+	struct operation *op;
+
+	pr_dump(hdr, gb_operation_msg_size(hdr));
+
+	if (!gb_handlers[cport_id]) {
+		pr_err("No driver registered for cport %d\n", cport_id);
+		return -EINVAL;
+	}
+
+	if (hdr->type & OP_RESPONSE) {
+		op = greybus_find_operation(cport_id, hdr->operation_id);
+		if (!op) {
+			pr_err("Invalid response id %d on cport %d\n",
+			       le16toh(hdr->operation_id), cport_id);
+			return -EINVAL;
+		}
+		TAILQ_REMOVE(&operations, op, cnode);
+		if (_greybus_alloc_response(op, hdr))
+			return -ENOMEM;
+		ret = (gb_handlers[cport_id])(op);
+	} else {
+		op = _greybus_alloc_operation(hdr);
+		if (!op)
+			return -ENOMEM;
+
+		ret = (gb_handlers[cport_id])(op);
+		if (!op->resp) {
+			if (greybus_alloc_response(op, 0)) {
+				pr_err("Failed to alloc greybus response\n");
+				ret = -ENOMEM;
+				goto free_op;
+			}
+		}
+		op->resp->result = greybus_errno_to_result(ret);
+
+		ret = greybus_send_response(cport_id, op);
+	}
+
+ free_op:
+	greybus_free_operation(op);
+
+	return ret;
+}
+
+int greybus_register_driver(uint16_t cport_id, greybus_handler_t handler)
+{
+	if (cport_id >= GB_NETLINK_NUM_CPORT) {
+		pr_err("Invalid cport id %d\n", cport_id);
+		return -EINVAL;
+	}
+	gb_handlers[cport_id] = handler;
+
+	return 0;
+}
+
+int greybus_init(void)
+{
+	TAILQ_INIT(&operations);
+	memset(gb_handlers, 0,
+	       sizeof(greybus_handler_t) * GB_NETLINK_NUM_CPORT);
+
+	return greybus_register_driver(0, svc_handler);
+}
diff --git a/main.c b/main.c
index c3ecaf13c7895479ccadf24251cb8b0d7afa2d5e..e9759d302cfe2073477013f12c8d1b4303fd579b 100644
--- a/main.c
+++ b/main.c
@@ -19,6 +19,8 @@
 #include <signal.h>
 
 #include <debug.h>
+
+#include "gbridge.h"
 #include "netlink.h"
 
 static void signal_handler(int sig)
@@ -34,14 +36,33 @@ int main(int argc, char *argv[])
 	signal(SIGHUP, signal_handler);
 	signal(SIGTERM, signal_handler);
 
+	ret = greybus_init();
+	if (ret) {
+		pr_err("Failed to init Greybus\n");
+		return ret;
+	}
+
 	ret = netlink_init();
 	if (ret) {
 		pr_err("Failed to init netlink\n");
 		return ret;
 	}
 
+	ret = svc_init();
+	if (ret) {
+		pr_err("Failed to init SVC\n");
+		goto err_netlink_exit;
+	}
+
 	netlink_loop();
 	netlink_exit();
 
 	return 0;
+
+ err_netlink_exit:
+	netlink_cancel();
+	netlink_loop();
+	netlink_exit();
+
+	return ret;
 }
diff --git a/netlink.c b/netlink.c
index a1222c0939ad66901f4277d3813b3997318b2c13..782b41c43d6f51fc513114d8209c1f05aab5efb1 100644
--- a/netlink.c
+++ b/netlink.c
@@ -40,6 +40,7 @@ parse_gb_nl_msg(struct nl_cache_ops *unused, struct genl_cmd *cmd,
 	struct gb_operation_msg_hdr *hdr;
 	uint16_t hd_cport_id;
 	size_t len;
+	int ret;
 
 	if (!info->attrs[GB_NL_A_DATA] || !info->attrs[GB_NL_A_CPORT])
 		return -EPROTO;
@@ -54,7 +55,11 @@ parse_gb_nl_msg(struct nl_cache_ops *unused, struct genl_cmd *cmd,
 	}
 
 	if (hd_cport_id == SVC_CPORT) {
-		/* TODO: handle SVC operations */
+		ret = greybus_handler(hd_cport_id, hdr);
+		if (ret) {
+			pr_err("Failed to handle svc operation %d: %d\n",
+			       hdr->type, ret);
+		}
 	} else {
 		/* TODO: transfer data to modules */
 	}
diff --git a/svc.c b/svc.c
new file mode 100644
index 0000000000000000000000000000000000000000..a5a200b5c3e0be82cecb64002137e5f19fafddd9
--- /dev/null
+++ b/svc.c
@@ -0,0 +1,296 @@
+/*
+ * 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 <endian.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <gbridge.h>
+
+/* TODO: Can we use other IDs ? */
+#define ENDO_ID 0x4755
+#define AP_INTF_ID 0x0
+
+static int svc_send_hello_request(void);
+
+static int svc_interface_v_sys_enable_response(struct operation *op,
+					       uint8_t result_code)
+{
+	struct gb_svc_intf_vsys_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_v_sys_disable_response(struct operation *op,
+						uint8_t result_code)
+{
+	struct gb_svc_intf_vsys_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_refclk_enable_response(struct operation *op,
+						uint8_t result_code)
+{
+	struct gb_svc_intf_refclk_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_refclk_disable_response(struct operation *op,
+						 uint8_t result_code)
+{
+	struct gb_svc_intf_refclk_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_unipro_enable_response(struct operation *op,
+						uint8_t result_code)
+{
+	struct gb_svc_intf_unipro_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_unipro_disable_response(struct operation *op,
+						 uint8_t result_code)
+{
+	struct gb_svc_intf_unipro_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 = result_code;
+
+	return 0;
+}
+
+static int svc_interface_activate_response(struct operation *op,
+					   uint8_t intf_type)
+{
+	struct gb_svc_intf_activate_response *resp;
+	size_t op_size = sizeof(*resp);
+
+	if (greybus_alloc_response(op, op_size))
+		return -ENOMEM;
+
+	resp = operation_to_response(op);
+	resp->intf_type = intf_type;
+
+	return 0;
+}
+
+static int svc_ping_request(struct operation *op)
+{
+	return 0;
+}
+
+static int svc_interface_device_id_request(struct operation *op)
+{
+	return 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);
+}
+
+static int svc_interface_v_sys_disable_request(struct operation *op)
+{
+	return svc_interface_v_sys_disable_response(op, GB_SVC_INTF_VSYS_OK);;
+}
+
+static int svc_interface_refclk_enable_request(struct operation *op)
+{
+	return svc_interface_refclk_enable_response(op, GB_SVC_INTF_REFCLK_OK);
+}
+
+static int svc_interface_refclk_disable_request(struct operation *op)
+{
+	return svc_interface_refclk_disable_response(op, GB_SVC_INTF_REFCLK_OK);
+}
+
+static int svc_interface_unipro_enable_request(struct operation *op)
+{
+	return svc_interface_unipro_enable_response(op, GB_SVC_INTF_UNIPRO_OK);
+}
+
+static int svc_interface_unipro_disable_request(struct operation *op)
+{
+	return svc_interface_unipro_disable_response(op, GB_SVC_INTF_UNIPRO_OK);
+}
+
+static int svc_interface_activate_request(struct operation *op)
+{
+	return svc_interface_activate_response(op, GB_SVC_INTF_TYPE_GREYBUS);
+}
+
+static int svc_interface_hotplug_request(struct operation *op)
+{
+	return -ENOSYS;
+}
+
+static int svc_interface_hot_unplug_request(struct operation *op)
+{
+	return -ENOSYS;
+}
+
+static int svc_handler_response(struct operation *op)
+{
+	switch (op->resp->type & ~OP_RESPONSE) {
+	case GB_REQUEST_TYPE_PROTOCOL_VERSION:
+		return svc_send_hello_request();
+	case GB_SVC_TYPE_SVC_HELLO:
+	case GB_SVC_TYPE_INTF_HOTPLUG:
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+int svc_handler(struct operation *op)
+{
+	if (op->resp)
+		return svc_handler_response(op);
+
+	switch (op->req->type) {
+	case GB_SVC_TYPE_PING:
+		return svc_ping_request(op);
+	case GB_SVC_TYPE_INTF_DEVICE_ID:
+		return svc_interface_device_id_request(op);
+	case GB_SVC_TYPE_INTF_HOTPLUG:
+		return svc_interface_hotplug_request(op);
+	case GB_SVC_TYPE_INTF_HOT_UNPLUG:
+		return svc_interface_hot_unplug_request(op);
+	case GB_SVC_TYPE_INTF_VSYS_ENABLE:
+		return svc_interface_v_sys_enable_request(op);
+	case GB_SVC_TYPE_INTF_VSYS_DISABLE:
+		return svc_interface_v_sys_disable_request(op);
+	case GB_SVC_TYPE_INTF_REFCLK_ENABLE:
+		return svc_interface_refclk_enable_request(op);
+	case GB_SVC_TYPE_INTF_REFCLK_DISABLE:
+		return svc_interface_refclk_disable_request(op);
+	case GB_SVC_TYPE_INTF_UNIPRO_ENABLE:
+		return svc_interface_unipro_enable_request(op);
+	case GB_SVC_TYPE_INTF_UNIPRO_DISABLE:
+		return svc_interface_unipro_disable_request(op);
+	case GB_SVC_TYPE_INTF_ACTIVATE:
+		return svc_interface_activate_request(op);
+	}
+
+	return -EINVAL;
+}
+
+int svc_send_protocol_version_request(void)
+{
+	struct gb_protocol_version_request req;
+	struct operation *op;
+
+	req.major = GB_SVC_VERSION_MAJOR;
+	req.minor = GB_SVC_VERSION_MINOR;
+
+	op = greybus_alloc_operation(GB_REQUEST_TYPE_PROTOCOL_VERSION,
+				     &req, sizeof(req));
+	if (!op)
+		return -ENOMEM;
+
+	return greybus_send_request(0, op);
+}
+
+int svc_send_hello_request(void)
+{
+	struct gb_svc_hello_request req;
+	struct operation *op;
+
+	req.endo_id = htole16(ENDO_ID);
+	req.interface_id = AP_INTF_ID;
+
+	op = greybus_alloc_operation(GB_SVC_TYPE_SVC_HELLO, &req, sizeof(req));
+	if (!op)
+		return -ENOMEM;
+
+	return greybus_send_request(0, op);
+}
+
+int svc_send_intf_hotplug_event(uint8_t intf_id,
+				uint32_t vendor_id,
+				uint32_t product_id, uint64_t serial_number)
+{
+	struct gb_svc_intf_hotplug_request req;
+	struct operation *op;
+
+	req.intf_id = intf_id;
+	req.data.ara_vend_id = htole32(vendor_id);
+	req.data.ara_prod_id = htole32(product_id);
+	req.data.serial_number = htole64(serial_number);
+
+	//FIXME: Use some real version numbers here ?
+	req.data.ddbl1_mfr_id = htole32(1);
+	req.data.ddbl1_prod_id = htole32(1);
+
+	op = greybus_alloc_operation(GB_SVC_TYPE_INTF_HOTPLUG,
+				     &req, sizeof(req));
+	if (!op)
+		return -ENOMEM;
+
+	return greybus_send_request(0, op);
+}
+
+int svc_init(void)
+{
+	return svc_send_protocol_version_request();
+}