From e2d22898f23c3174f3a3fc25465bc41ce66562de Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Fri, 9 May 2014 17:14:55 +0300 Subject: [PATCH] gam-control: initial experimental/demo plugin implementation. 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 | 33 + src/Makefile.am | 51 ++ src/core/domain-types.h | 3 +- src/daemon/sample-config/main.cfg | 2 + src/plugins/gam-control/Makefile | 7 + src/plugins/gam-control/plugin-gam-control.c | 914 +++++++++++++++++++++++++++ 6 files changed, 1009 insertions(+), 1 deletion(-) create mode 100644 src/plugins/gam-control/Makefile create mode 100644 src/plugins/gam-control/plugin-gam-control.c diff --git a/configure.ac b/configure.ac index 8ef83b3..bd44fe0 100644 --- a/configure.ac +++ b/configure.ac @@ -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, diff --git a/src/Makefile.am b/src/Makefile.am index f6c2b3a..f02358b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 # diff --git a/src/core/domain-types.h b/src/core/domain-types.h index 5064078..111ab4c 100644 --- a/src/core/domain-types.h +++ b/src/core/domain-types.h @@ -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; diff --git a/src/daemon/sample-config/main.cfg b/src/daemon/sample-config/main.cfg index 2419396..306f0bf 100644 --- a/src/daemon/sample-config/main.cfg +++ b/src/daemon/sample-config/main.cfg @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/plugins/gam-control/Makefile @@ -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 index 0000000..539371f --- /dev/null +++ b/src/plugins/gam-control/plugin-gam-control.c @@ -0,0 +1,914 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 " + +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); -- 2.7.4