gam-control: initial experimental/demo plugin implementation.
authorKrisztian Litkey <krisztian.litkey@intel.com>
Fri, 9 May 2014 14:14:55 +0000 (17:14 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 8 Jan 2015 16:37:16 +0000 (18:37 +0200)
This is the counterpart to our Genivi Audio Manager Murphy control
plugin. It does route selection and resource allocation on behalf
of GAM.

Change-Id: I7f61c3c4bd79a73e6fdf86bc763db83a8875bf54

configure.ac
src/Makefile.am
src/core/domain-types.h
src/daemon/sample-config/main.cfg
src/plugins/gam-control/Makefile [new file with mode: 0644]
src/plugins/gam-control/plugin-gam-control.c [new file with mode: 0644]

index 8ef83b3..bd44fe0 100644 (file)
@@ -614,6 +614,35 @@ fi
 AM_CONDITIONAL(RESOURCE_ASM_ENABLED, [test "$enable_resource_asm" = "yes"])
 AC_SUBST(RESOURCE_ASM_ENABLED)
 
+# Check if Immelmann plugin was enabled.
+AC_ARG_ENABLE(immelmann,
+              [  --enable-immelmann  build Immelmann plugin],
+        [enable_immelmann=$enableval], [enable_immelmann=no])
+if test "$enable_immelmann" = "yes"; then
+    AC_DEFINE([IMMELMANN_ENABLED], 1, [Enable Immelmann support ?])
+else
+    AC_MSG_NOTICE([Immelmann support is disabled.])
+fi
+AM_CONDITIONAL(IMMELMANN_ENABLED, [test "$enable_immelmann" = "yes"])
+AC_SUBST(IMMELMANN_ENABLED)
+
+# Check if Genivi Audio Manager control plugin should be built.
+AC_ARG_ENABLE(gam-control,
+              [  --enable-gam-control     enable Audio Manager control plugin],
+             [enable_gam_control=$enableval], [enable_gam_control=no])
+
+if test "$enable_gam_control" != "yes"; then
+    AC_MSG_NOTICE([Genivi Audio Manager control plugin is enabled.])
+else
+    AC_MSG_NOTICE([Genivi Audio Manager control plugin is disabled.])
+fi
+
+if test "$enable_gam_control" = "yes"; then
+    AC_DEFINE([GAM_CONTROL_ENABLED], 1, [Enable Genivi Audio Manager control ?])
+fi
+AM_CONDITIONAL(GAM_CONTROL_ENABLED, [test "$enable_gam_control" = "yes"])
+AC_SUBST(GAM_CONTROL_ENABLED)
+
 # Set up murphy CFLAGS and LIBS.
 MURPHY_CFLAGS=""
 MURPHY_LIBS=""
@@ -722,6 +751,8 @@ AM_CONDITIONAL(DISABLED_PLUGIN_RESOURCE_ASM,     [check_if_disabled resource-asm
 
 AM_CONDITIONAL(DISABLED_PLUGIN_SYSTEMCTL,  [test $enable_systemctl != yes])
 AM_CONDITIONAL(DISABLED_PLUGIN_SYSMON,  [test $enable_sysmon != yes])
+AM_CONDITIONAL(DISABLED_PLUGIN_IMMELMANN,  [check_if_disabled immelmann])
+AM_CONDITIONAL(DISABLED_PLUGIN_GAM_CONTROL, [check_if_disabled gam-control])
 
 AM_CONDITIONAL(BUILTIN_PLUGIN_TEST,     [check_if_internal test])
 AM_CONDITIONAL(BUILTIN_PLUGIN_DBUS,     [check_if_internal dbus])
@@ -739,6 +770,8 @@ AM_CONDITIONAL(BUILTIN_PLUGIN_TELEPHONY, [check_if_internal telephony])
 AM_CONDITIONAL(BUILTIN_PLUGIN_RESOURCE_ASM,      [check_if_internal resource-asm])
 AM_CONDITIONAL(BUILTIN_PLUGIN_SYSTEMCTL, [check_if_internal system-controller])
 AM_CONDITIONAL(BUILTIN_PLUGIN_SYSMON, [check_if_internal system-monitor])
+AM_CONDITIONAL(BUILTIN_PLUGIN_IMMELMANN,  [check_if_internal immelmann])
+AM_CONDITIONAL(BUILTIN_PLUGIN_GAM_CONTROL, [check_if_internal gam-control])
 
 # Check for Check (unit test framework).
 PKG_CHECK_MODULES(CHECK,
index f6c2b3a..f02358b 100644 (file)
@@ -1855,6 +1855,57 @@ endif
 
 endif
 
+# Immelmann plugin
+IMMELMANN_PLUGIN_SOURCES = plugins/plugin-immelmann.c
+IMMELMANN_PLUGIN_CFLAGS  =
+IMMELMANN_PLUGIN_LIBS    =     libmurphy-core.la       \
+                               libmurphy-common.la     \
+                               $(RESOURCE_LIBRARY)
+
+if IMMELMANN_ENABLED
+
+if !DISABLED_PLUGIN_IMMELMANN
+
+if BUILTIN_PLUGIN_IMMELMANN
+BUILTIN_PLUGINS += $(IMMELMANN_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(IMMELMANN_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(IMMELMANN_PLUGIN_LIBS)
+else
+plugin_immelmann_la_SOURCES = $(IMMELMANN_PLUGIN_SOURCES)
+plugin_immelmann_la_CFLAGS  = $(IMMELMANN_PLUGIN_CFLAGS) $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_immelmann_la_LDFLAGS = -module -avoid-version
+plugin_immelmann_la_LIBADD  = $(IMMELMANN_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES    += plugin-immelmann.la
+endif
+endif
+
+endif
+
+# Genivi Audio Manager control plugin
+if GAM_CONTROL_ENABLED
+GAM_CONTROL_PLUGIN_SOURCES = plugins/gam-control/plugin-gam-control.c
+GAM_CONTROL_PLUGIN_CFLAGS  =
+GAM_COTNROL_PLUGIN_LIBS    =
+
+if !DISABLED_PLUGIN_GAM_CONTROL
+if BUILTIN_PLUGIN_GAM_CONTROL
+BUILTIN_PLUGINS += $(GAM_CONTROL_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(GAM_CONTROL_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(GAM_CONTROL_PLUGIN_LIBS)
+else
+plugin_gam_control_la_SOURCES = $(GAM_CONTROL_PLUGIN_SOURCES)
+plugin_gam_control_la_CFLAGS  = $(GAM_CONTROL_PLUGIN_CFLAGS) \
+                            $(MURPHY_CFLAGS) \
+                            $(AM_CFLAGS)
+plugin_gam_control_la_LDFLAGS = -module -avoid-version
+plugin_gam_control_la_LIBADD  = $(GAM_CONTROL_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES += plugin-gam-control.la
+endif
+endif
+endif
+
 ###################################
 # murphy daemon
 #
index 5064078..111ab4c 100644 (file)
@@ -55,7 +55,8 @@ typedef enum {
     MRP_DOMCTL_UINT64   = MRP_MSG_FIELD_UINT64,
     MRP_DOMCTL_INT64    = MRP_MSG_FIELD_INT64,
 
-#define MRP_DOMCTL_ARRAY(_type)      MRP_MSG_FIELD_ARRAY_OF(_type)
+#define MRP_DOMCTL_ARRAY(_type)      \
+    (mrp_domctl_type_t)MRP_MSG_FIELD_ARRAY_OF(_type)
 #define MRP_DOMCTL_IS_ARRAY(_type)   MRP_MSG_FIELD_IS_ARRAY(_type)
 #define MRP_DOMCTL_ARRAY_TYPE(_type) MRP_MSG_FIELD_ARRAY_TYPE(_type)
 } mrp_domctl_type_t;
index 2419396..306f0bf 100644 (file)
@@ -14,10 +14,12 @@ config = {
    { 'console'          , OPTIONAL  },
    { 'systemd'          , OPTIONAL  },
    { 'glib'             , OPTIONAL  },
+   { 'test'             , OPTIONAL  },
    { 'resource'         , MANDATORY },
    { 'domain-control'   , MANDATORY },
    { 'system-controller', OPTIONAL  },
    { 'system-monitor'   , OPTIONAL  },
+   { 'gam-control'      , OPTIONAL  },
 }
 
 ruleset = {
diff --git a/src/plugins/gam-control/Makefile b/src/plugins/gam-control/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/plugins/gam-control/plugin-gam-control.c b/src/plugins/gam-control/plugin-gam-control.c
new file mode 100644 (file)
index 0000000..539371f
--- /dev/null
@@ -0,0 +1,914 @@
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <murphy/common.h>
+#include <murphy/core.h>
+#include <murphy/core/domain.h>
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/manager-api.h>
+
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+#include <murphy-db/mql-result.h>
+
+#define SOURCETBL "audio_manager_sources"
+#define SINKTBL   "audio_manager_sinks"
+
+typedef struct gamctl_s gamctl_t;
+typedef struct route_s  route_t;
+
+
+/*
+ * plugin context/state
+ */
+
+struct gamctl_s {
+    mrp_plugin_t          *self;         /* us, this plugin */
+    mrp_resource_client_t *rsc;          /* resource client */
+    mrp_htbl_t            *rstbl;        /* resource sets */
+    uint32_t               seq;          /* request sequence number */
+    uint32_t               sourcef;      /* source table fingerprint (stamp) */
+    uint32_t               sinkf;        /* sink table fingerprint (stamp) */
+    route_t               *routes;       /* routing table */
+    size_t                 nroute;       /* number of routing entries */
+    mrp_deferred_t        *recalc;       /* deferred resource recalculation */
+};
+
+
+/*
+ * a route from source to sink
+ */
+
+struct route_s {
+    char     *source;                    /* source name */
+    char     *sink;                      /* sink name */
+    uint16_t *hops;                      /* routing hops */
+    size_t    nhop;                      /* number of hops */
+};
+
+
+/*
+ * a source or sink node to id mapping
+ */
+
+typedef struct {
+    const char *name;                    /* source/sink name */
+    uint16_t    id;                      /* source/sink id */
+} node_t;
+
+
+/*
+ * a (predefined) routing path
+ */
+
+typedef struct {
+    size_t  nhop;                        /* number of hops */
+    char   *hops[32];                    /* hop names */
+} path_t;
+
+
+
+static int  resctl_init(gamctl_t *gam);
+static void resctl_exit(gamctl_t *gam);
+static int  domctl_init(gamctl_t *gam);
+static void domctl_exit(gamctl_t *gam);
+static void gamctl_exit(mrp_plugin_t *plugin);
+
+static int gamctl_route_cb(int narg, mrp_domctl_arg_t *args,
+                           uint32_t *nout, mrp_domctl_arg_t *outs,
+                           void *user_data);
+static int gamctl_disconnect_cb(int narg, mrp_domctl_arg_t *args,
+                                uint32_t *nout, mrp_domctl_arg_t *outs,
+                                void *user_data);
+
+static char *route_dump(gamctl_t *gam, char *buf, size_t size,
+                        route_t *r, int verbose);
+
+
+/*
+ * hardwired sources, sinks and routing paths
+ */
+
+static node_t sources[] = {
+    { "wrtApplication", 0 },
+    { "icoApplication", 0 },
+    { "phoneSource"   , 0 },
+    { "radio"         , 0 },
+    { "microphone"    , 0 },
+    { "navigator"     , 0 },
+    { "gw1Source"     , 0 },
+    { "gw2Source"     , 0 },
+    { "gw3Source"     , 0 },
+    { "gw4Source"     , 0 },
+    { "gw5Source"     , 0 },
+    { "gw6Source"     , 0 },
+    { NULL, 0 }
+};
+
+static node_t sinks[] = {
+    { "btHeadset"       , 0 },
+    { "usbHeadset"      , 0 },
+    { "speakers"        , 0 },
+    { "wiredHeadset"    , 0 },
+    { "phoneSink"       , 0 },
+    { "voiceRecognition", 0 },
+    { "gw1Sink"         , 0 },
+    { "gw2Sink"         , 0 },
+    { "gw3Sink"         , 0 },
+    { "gw4Sink"         , 0 },
+    { "gw5Sink"         , 0 },
+    { "gw6Sink"         , 0 },
+    { NULL, 0 }
+};
+
+static path_t paths[] = {
+    { 2, { "wrtApplication", "btHeadset"  } },
+    { 2, { "wrtApplication", "usbHeadset" } },
+    { 4, { "wrtApplication", "gw2Sink", "gw2Source", "speakers"     } },
+    { 4, { "wrtApplication", "gw2Sink", "gw2Source", "wiredHeadset" } },
+
+    { 2, { "icoApplication", "btHeadset"  } },
+    { 2, { "icoApplication", "usbHeadset" } },
+    { 4, { "icoApplication", "gw1Sink", "gw1Source", "speakers"     } },
+    { 4, { "icoApplication", "gw1Sink", "gw1Source", "wiredHeadset" } },
+
+    { 2, { "phoneSource", "btHeadset"  } },
+    { 2, { "phoneSource", "usbHeadset" } },
+    { 4, { "phoneSource", "gw1Sink", "gw1Source", "speakers"     } },
+    { 4, { "phoneSource", "gw1Sink", "gw1Source", "wiredHeadset" } },
+
+    { 4, { "radio", "gw3Sink", "gw3Source", "btHeadset"  } },
+    { 4, { "radio", "gw3Sink", "gw3Source", "usbHeadset" } },
+    { 2, { "radio", "speakers"     } },
+    { 2, { "radio", "wiredHeadset" } },
+
+    { 4, { "microphone", "gw6Sink", "gw6Source", "phoneSink"        } },
+    { 4, { "microphone", "gw5Sink", "gw5Source", "voiceRecognition" } },
+
+    { 4, { "navigator", "gw4Sink", "gw4Source", "speakers"     } },
+    { 4, { "navigator", "gw4Sink", "gw4Source", "wiredHeadset" } },
+};
+
+
+static uint16_t node_id(node_t *nodes, const char *name)
+{
+    node_t *node;
+
+    for (node = nodes; node->name != NULL; node++)
+        if (!strcmp(node->name, name))
+            return node->id;
+
+    return 0;
+}
+
+
+static uint16_t source_id(const char *name)
+{
+    return node_id(sources, name);
+}
+
+
+static uint16_t sink_id(const char *name)
+{
+    return node_id(sinks, name);
+}
+
+
+static int exec_mql(mql_result_type_t type, mql_result_t **resultp,
+                    const char *format, ...)
+{
+    mql_result_t *r;
+    char          buf[4096];
+    va_list       ap;
+    int           success, n;
+
+    va_start(ap, format);
+    n = vsnprintf(buf, sizeof(buf), format, ap);
+    va_end(ap);
+
+    if (n < (int)sizeof(buf)) {
+        r       = mql_exec_string(type, buf);
+        success = (r == NULL || mql_result_is_success(r));
+
+        if (resultp != NULL) {
+            *resultp = r;
+            return success;
+        }
+        else {
+            mql_result_free(r);
+            return success;
+        }
+    }
+    else {
+        if (resultp != NULL)
+            *resultp = NULL;
+
+        return FALSE;
+    }
+}
+
+
+static void reset_nodes(node_t *nodes)
+{
+    node_t *node;
+
+    for (node = nodes; node->name != NULL; node++)
+        node->id = 0;
+}
+
+
+static int resolve_nodes(const char *type, node_t *nodes,
+                         const char *tbl, uint32_t *age)
+{
+    mql_result_t *r = NULL;
+    uint32_t      h, stamp;
+    node_t       *node;
+    uint16_t      nrow, i, id;
+    const char   *name;
+
+    if ((h = mqi_get_table_handle((char *)tbl)) == MQI_HANDLE_INVALID)
+        return FALSE;
+
+    if (*age >= (stamp = mqi_get_table_stamp(h)))
+        return TRUE;
+
+    if (!exec_mql(mql_result_rows, &r, "select id,name from %s", tbl))
+        return FALSE;
+
+    reset_nodes(nodes);
+
+    if (r == NULL)
+        return FALSE;
+
+    nrow = mql_result_rows_get_row_count(r);
+
+    if (nrow == 0)
+        return FALSE;
+
+    if (mql_result_rows_get_row_column_type(r, 0) != mqi_integer ||
+        mql_result_rows_get_row_column_type(r, 1) != mqi_string) {
+        mrp_log_error("Invalid column types for table '%s'.", tbl);
+        return FALSE;
+    }
+
+    for (i = 0; i < nrow; i++) {
+        id   = mql_result_rows_get_integer(r, 0, i);
+        name = mql_result_rows_get_string(r, 1, i, NULL, 0);
+
+        for (node = nodes; node->name != NULL; node++) {
+            if (!strcmp(node->name, name)) {
+                node->id = id;
+                mrp_log_info("%s '%s' has now id %u", type, name, id);
+                break;
+            }
+        }
+
+        if (node->name == NULL)
+            mrp_log_info("(unused) %s '%s' has id %u", type, name, id);
+    }
+
+    mql_result_free(r);
+
+    *age = stamp;
+
+    return TRUE;
+}
+
+
+static int resolve_routes(gamctl_t *gam)
+{
+    const char *type, *node;
+    char        route[4096];
+    route_t    *r;
+    path_t     *p;
+    uint16_t    id;
+    size_t      i;
+    int         incomplete;
+
+    for (r = gam->routes, p = paths; r->nhop; r++, p++) {
+        incomplete = FALSE;
+        for (i = 0; i < r->nhop; i++) {
+            node = p->hops[i];
+
+            if (i & 0x1) {
+                type = "sink";
+                id   = sink_id(node);
+            }
+            else {
+                type = "source";
+                id   = source_id(node);
+            }
+
+            if (!id) {
+                mrp_log_warning("Unresolved %s '%s'.",type, node);
+                incomplete = TRUE;
+            }
+
+            r->hops[i] = id;
+        }
+
+        if (!incomplete)
+            mrp_log_info("Resolved route: %s",
+                         route_dump(gam, route, sizeof(route), r, TRUE));
+        else
+            mrp_log_warning("Unresolvable route: %s",
+                            route_dump(gam, route, sizeof(route), r, TRUE));
+    }
+
+    return TRUE;
+}
+
+
+static int connid_cmp(const void *key1, const void *key2)
+{
+    return key2 - key1;
+}
+
+
+static uint32_t connid_hash(const void *key)
+{
+    uint16_t connid = (uint16_t)(ptrdiff_t)key;
+
+    return connid;
+}
+
+
+static int resctl_init(gamctl_t *gam)
+{
+    mrp_htbl_config_t hcfg;
+
+    mrp_clear(&hcfg);
+    hcfg.comp  = connid_cmp;
+    hcfg.hash  = connid_hash;
+    hcfg.free  = NULL;
+
+    gam->rstbl = mrp_htbl_create(&hcfg);
+
+    if (gam->rstbl == NULL)
+        return FALSE;
+
+    gam->seq = 1;
+    gam->rsc = mrp_resource_client_create("genivi-audio-manager", gam);
+
+    if (gam->rsc == NULL) {
+        mrp_log_error("Failed to create Genivi Audio Manager resource client.");
+        return FALSE;
+    }
+    else {
+        mrp_log_info("Created Genivi Audio Manager resource client.");
+        return TRUE;
+    }
+}
+
+
+static void resctl_event_cb(uint32_t seq, mrp_resource_set_t *set,
+                            void *user_data)
+{
+    gamctl_t *gam = (gamctl_t *)user_data;
+
+    MRP_UNUSED(seq);
+    MRP_UNUSED(set);
+    MRP_UNUSED(gam);
+}
+
+
+static mrp_resource_set_t *resctl_create(gamctl_t *gam, uint16_t source,
+                                         uint16_t sink, uint16_t connid,
+                                         uint16_t connno)
+{
+    mrp_resource_client_t *rsc    = gam->rsc;
+    uint32_t               seq    = gam->seq++;
+    const char            *zone   = "driver";
+    const char            *cls    = "player";
+    const char            *play   = "audio_playback";
+    uint32_t               prio   = 0;
+    bool                   ar     = false;
+    bool                   nowait = false;
+    mrp_resource_set_t    *set;
+    mrp_attr_t             attrs[16], *attrp;
+
+    mrp_log_info("Creating resource set for Genivi Audio Manager connection "
+                 "%u (%u -> %u, #%u).", connid, source, sink, connno);
+
+    set = mrp_resource_set_create(rsc, ar, nowait, prio, resctl_event_cb, gam);
+
+    if (set == NULL) {
+        mrp_log_error("Failed to create resource set for Genivi Audio Manager "
+                      "connection %u (%u -> %u).", connid, source, sink);
+        goto fail;
+    }
+
+    attrs[0].type          = mqi_integer;
+    attrs[0].name          = "source_id";
+    attrs[0].value.integer = source;
+    attrs[1].type          = mqi_integer;
+    attrs[1].name          = "sink_id";
+    attrs[1].value.integer = sink;
+    attrs[2].type          = mqi_integer;
+    attrs[2].name          = "connid";
+    attrs[2].value.integer = connid;
+    attrs[3].type          = mqi_integer;
+    attrs[3].name          = "connno";
+    attrs[3].value.integer = connno;
+    attrs[4].name          = NULL;
+
+    attrp = &attrs[0];
+
+    if (mrp_resource_set_add_resource(set, play, true, attrp, true) < 0) {
+        mrp_log_error("Failed to add resource %s to Genivi Audio "
+                      "Manager resource set.", play);
+        goto fail;
+    }
+
+    if (mrp_application_class_add_resource_set(cls, zone, set, seq) != 0) {
+        mrp_log_error("Failed to add Genivi Audio Manager resource set "
+                      "to application class %s in zone %s.", cls, zone);
+        goto fail;
+    }
+
+    if (mrp_htbl_insert(gam->rstbl, (void *)(ptrdiff_t)connid, set))
+        return set;
+    else
+        mrp_log_error("Failed to associate resource set with connection %u.",
+                      connid);
+    /* fallthru */
+
+ fail:
+    if (set != NULL)
+        mrp_resource_set_destroy(set);
+
+    return NULL;
+}
+
+
+static int resctl_update(gamctl_t *gam, uint32_t rsetid,
+                         uint16_t source, uint16_t sink, uint16_t conn)
+{
+    static int srcidx = -1, sinkidx = -1, connidx = -1;
+
+    mrp_resource_set_t *rset  = mrp_resource_set_find_by_id(rsetid);
+    mrp_attr_t         *attrs = NULL, *a;
+    int                 i, status;
+
+    MRP_UNUSED(gam);
+
+    if (rset == NULL) {
+        mrp_log_error("Failed to update resource set, can't find set 0x%x.",
+                      rsetid);
+        return FALSE;
+    }
+
+    attrs = mrp_resource_set_read_all_attributes(rset, "audio_playback", 0,NULL);
+
+    if (attrs == NULL) {
+        mrp_log_error("Failed to read resource set attribute list.");
+        return FALSE;
+    }
+
+    if (srcidx >= 0 && sinkidx >= 0 && connidx >= 0) {
+        attrs[srcidx].value.integer  = source;
+        attrs[sinkidx].value.integer = sink;
+        attrs[connidx].value.integer = conn;
+    }
+    else {
+        for (a = attrs, i = 0; a->name != NULL; a++, i++) {
+            if (a->type != mqi_integer)
+                continue;
+
+            if (!strcmp(a->name, "source_id")) {
+                a->value.integer = source;
+                srcidx = i;
+            }
+            else if (!strcmp(a->name, "sink_id")) {
+                a->value.integer = sink;
+                sinkidx = i;
+            }
+            else if (!strcmp(a->name, "connid")) {
+                a->value.integer = conn;
+                connidx = i;
+            }
+        }
+    }
+
+    status = mrp_resource_set_write_attributes(rset, "audio_playback", attrs);
+
+    if (status < 0)
+        mrp_log_error("Failed to update resource set attributes.");
+    else
+        mrp_log_info("Resource set attributes updated.");
+
+    return status;
+}
+
+
+static void resctl_acquire(gamctl_t *gam, mrp_resource_set_t *set)
+{
+    mrp_log_info("Acquiring Genivi Audio Manager resource set.");
+    mrp_resource_set_acquire(set, gam->seq++);
+}
+
+
+static int resctl_destroy(gamctl_t *gam, uint16_t connid)
+{
+    mrp_resource_set_t *rset;
+
+    rset = mrp_htbl_remove(gam->rstbl, (void *)(ptrdiff_t)connid, FALSE);
+
+    if (rset != NULL) {
+        mrp_resource_set_destroy(rset);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void resctl_recalc(gamctl_t *gam, int zoneid)
+{
+    uint32_t z;
+
+    MRP_UNUSED(gam);
+
+    mrp_log_info("Recalculating resource set allocations.");
+
+    if (zoneid >= 0)
+        mrp_resource_owner_recalc(zoneid);
+    else
+        for (z = 0; z < mrp_zone_count(); z++)
+            mrp_resource_owner_recalc(z);
+}
+
+
+static void recalc_cb(mrp_deferred_t *d, void *user_data)
+{
+    gamctl_t *gam = (gamctl_t *)user_data;
+
+    MRP_UNUSED(d);
+
+    mrp_disable_deferred(gam->recalc);
+
+    resctl_recalc(gam, -1);
+}
+
+
+static void resctl_schedule_recalc(gamctl_t *gam)
+{
+    mrp_log_info("Scheduling resource recalculation.");
+
+    if (gam->recalc == NULL)
+        gam->recalc = mrp_add_deferred(gam->self->ctx->ml, recalc_cb, gam);
+    else
+        mrp_enable_deferred(gam->recalc);
+}
+
+
+static void resctl_exit(gamctl_t *gam)
+{
+    if (gam != NULL) {
+        if (gam->rsc != NULL) {
+            mrp_log_info("Destroying Genivi Audio Manager resource client.");
+            mrp_resource_client_destroy(gam->rsc);
+            gam->rsc = NULL;
+        }
+
+        mrp_htbl_destroy(gam->rstbl, FALSE);
+        gam->rstbl = NULL;
+    }
+}
+
+
+static int route_init(gamctl_t *gam)
+{
+    route_t *r;
+    path_t  *p;
+    size_t   nroute, i;
+
+    nroute      = MRP_ARRAY_SIZE(paths);
+    gam->routes = mrp_allocz_array(route_t, nroute + 1);
+
+    if (gam->routes == NULL)
+        return FALSE;
+
+    r = gam->routes;
+    p = paths;
+    for (i = 0; i < nroute; i++) {
+        r->nhop = p->nhop;
+        r->hops = mrp_allocz_array(uint16_t, r->nhop + 1);
+
+        if (r->hops == NULL && r->nhop != 0)
+            return FALSE;
+
+        r->source = p->hops[0];
+        r->sink   = p->hops[p->nhop - 1];
+
+        mrp_log_info("Added a routing table entry for '%s' -> '%s'.",
+                     r->source, r->sink);
+
+        r++;
+        p++;
+    }
+
+    gam->sourcef = 0;
+    gam->sinkf   = 0;
+
+    return TRUE;
+}
+
+
+static void route_exit(gamctl_t *gam)
+{
+    route_t *r;
+
+    if (gam->routes == NULL)
+        return;
+
+    for (r = gam->routes; r->source != NULL; r++) {
+        mrp_free(r->hops);
+        r->hops = NULL;
+        r->nhop = 0;
+    }
+}
+
+
+static char *route_dump(gamctl_t *gam, char *buf, size_t size,
+                        route_t *r, int verbose)
+{
+    path_t     *p;
+    const char *t;
+    char       *b;
+    int         n, i;
+    size_t      h, l;
+
+    i = r - gam->routes;
+    p = paths + i;
+
+    t = "";
+    b = buf;
+    l = size;
+
+    for (h = 0; h < r->nhop; h++) {
+        if (verbose)
+            n = snprintf(b, l, "%s%u(%s)", t, r->hops[h], p->hops[h]);
+        else
+            n = snprintf(b, l, "%s%u", t, r->hops[h]);
+
+        if (n >= (int)l)
+            return "dump_route: insufficient buffer";
+
+        b += n;
+        l -= n;
+        t  = " -> ";
+    }
+
+    return buf;
+}
+
+
+static int route_incomplete(route_t *r)
+{
+    size_t h;
+
+    for (h = 0; h < r->nhop; h++)
+        if (!r->hops[h])
+            return TRUE;
+
+    return FALSE;
+}
+
+
+static route_t *route_connection(gamctl_t *gam, uint16_t source, uint16_t sink)
+{
+    uint32_t  sourcef = gam->sourcef;
+    uint32_t  sinkf   = gam->sinkf;
+    char      route[1024];
+    route_t  *r;
+
+    if (!resolve_nodes("source", sources, SOURCETBL, &gam->sourcef) ||
+        !resolve_nodes("sink"  , sinks  , SINKTBL  , &gam->sinkf))
+        return NULL;
+
+    if (sourcef != gam->sourcef || sinkf != gam->sinkf)
+        if (!resolve_routes(gam))
+            return NULL;
+
+    for (r = gam->routes; r->source; r++) {
+        if (r->hops[0] == source && r->hops[r->nhop - 1] == sink) {
+            if (!route_incomplete(r)) {
+                mrp_log_info("Chosen route for connection: %s",
+                             route_dump(gam, route, sizeof(route), r, TRUE));
+                return r;
+            }
+            else {
+                mrp_log_error("Route %u -> %u is unresolved/incomplete.",
+                              source, sink);
+                return NULL;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+static int domctl_init(gamctl_t *gam)
+{
+    mrp_context_t           *ctx           = gam->self->ctx;
+    mrp_domain_method_def_t  gam_methods[] = {
+        { "request_route"    , 32, gamctl_route_cb     , gam },
+        { "notify_disconnect",  8, gamctl_disconnect_cb, gam },
+    };
+    size_t                   gam_nmethod   = MRP_ARRAY_SIZE(gam_methods);
+
+    if (mrp_register_domain_methods(ctx, gam_methods, gam_nmethod)) {
+        mrp_log_info("Registered Genivi Audio Manager domain methods.");
+        return TRUE;
+    }
+    else {
+        mrp_log_error("Failed to register Genivi Audio Manager domain methods.");
+        return FALSE;
+    }
+}
+
+
+static void domctl_exit(gamctl_t *gam)
+{
+    MRP_UNUSED(gam);
+}
+
+
+static int gamctl_route_cb(int narg, mrp_domctl_arg_t *args,
+                           uint32_t *nout, mrp_domctl_arg_t *outs,
+                           void *user_data)
+{
+    gamctl_t           *gam = (gamctl_t *)user_data;
+    uint16_t            source, sink, conn, *path;
+    uint32_t            rsetid;
+    const char         *error;
+    size_t              i;
+    mrp_resource_set_t *rset;
+    route_t            *r;
+
+    if (narg < 4) {
+        error = "too few route request arguments (need route, conn, "
+            "rset and paths)";
+        goto error;
+    }
+
+    if (args[0].type != MRP_DOMCTL_ARRAY(UINT16)) {
+        error = "invalid route (arg #0), array of uint16_t expected";
+        goto error;
+    }
+
+    if (args[0].size != 2) {
+        error = "invalid route (arg #0), 2 endpoints expected";
+        goto error;
+    }
+
+    if (args[1].type != MRP_DOMCTL_UINT16) {
+        error = "invalid connection id (arg #1), uint16_t expected";
+        goto error;
+    }
+
+    if (args[2].type != MRP_DOMCTL_UINT32) {
+        error = "invalid resource set id (arg #2), uint32_t expected";
+        goto error;
+    }
+
+    source = ((uint16_t *)args[0].arr)[0];
+    sink   = ((uint16_t *)args[0].arr)[1];
+    conn   = args[1].u16;
+    rsetid = args[2].u32;
+
+    mrp_log_info("Got routing request for connection #%u:%u -> %u "
+                 "(rset 0x%x) with %d possible routes.", conn, source, sink,
+                 rsetid, narg - 3);
+
+    r = route_connection(gam, source, sink);
+
+    if (r == NULL || r->nhop == 0) {
+        error = "no route";
+        goto error;
+    }
+
+    if (!rsetid) {
+        if ((rset = resctl_create(gam, source, sink, conn, 0)) == NULL) {
+            error = "failed to create resouce set";
+            goto error;
+        }
+
+        resctl_acquire(gam, rset);
+    }
+    else {
+        resctl_update(gam, rsetid, source, sink, conn);
+        resctl_schedule_recalc(gam);
+    }
+
+    path = mrp_allocz(r->nhop * sizeof(path[0]));
+
+    if (path == NULL) {
+        error = NULL;
+        goto error;
+    }
+
+    for (i = 0; i < r->nhop; i++)
+        path[i] = r->hops[i];
+
+    *nout = 1;
+    outs[0].type = MRP_DOMCTL_ARRAY(UINT16);
+    outs[0].arr  = path;
+    outs[0].size = r->nhop;
+
+    return 0;
+
+ error:
+    *nout = 1;
+    outs[0].type = MRP_DOMCTL_STRING;
+    outs[0].str  = mrp_strdup(error);
+
+    return -1;
+}
+
+
+static int gamctl_disconnect_cb(int narg, mrp_domctl_arg_t *args,
+                                uint32_t *nout, mrp_domctl_arg_t *outs,
+                                void *user_data)
+{
+    gamctl_t   *gam = (gamctl_t *)user_data;
+    const char *error;
+    uint16_t    conn;
+
+    if (narg != 1) {
+        error = "too few disconnect notification arguments (need connid)";
+        goto error;
+    }
+
+    if (args[0].type != MRP_DOMCTL_UINT16) {
+        error = "invalid disconnect connid (arg #0), uint16_t expected";
+        goto error;
+    }
+
+    conn = args[0].u16;
+
+    mrp_log_info("Got disconnect request for connection #%u.", conn);
+
+    resctl_destroy(gam, conn);
+    return TRUE;
+
+ error:
+    *nout = 1;
+    outs[0].type = MRP_DOMCTL_STRING;
+    outs[0].str  = mrp_strdup(error);
+
+    return FALSE;
+}
+
+
+static int gamctl_init(mrp_plugin_t *plugin)
+{
+    gamctl_t *gam;
+
+    gam = mrp_allocz(sizeof(*gam));
+
+    if (gam == NULL)
+        goto fail;
+
+    gam->self    = plugin;
+    plugin->data = gam;
+
+    if (!resctl_init(gam))
+        goto fail;
+
+    if (!domctl_init(gam))
+        goto fail;
+
+    if (!route_init(gam))
+        goto fail;
+
+    return TRUE;
+
+ fail:
+    gamctl_exit(plugin);
+    return FALSE;
+}
+
+
+static void gamctl_exit(mrp_plugin_t *plugin)
+{
+    gamctl_t *gam = plugin->data;
+
+    resctl_exit(gam);
+    domctl_exit(gam);
+    route_exit(gam);
+}
+
+
+#define GAMCTL_DESCRIPTION "Genivi Audio Manager control plugin for Murphy"
+#define GAMCTL_HELP        "Genivi Audio Manager control plugin for Murphy."
+#define GAMCTL_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define GAMCTL_AUTHORS     "Krisztian Litkey <kli@iki.fi>"
+
+MURPHY_REGISTER_PLUGIN("gam-control",
+                       GAMCTL_VERSION, GAMCTL_DESCRIPTION,
+                       GAMCTL_AUTHORS, GAMCTL_HELP, MRP_SINGLETON,
+                       gamctl_init, gamctl_exit,
+                       NULL, 0, NULL, 0, NULL, 0, NULL);