diff options
Diffstat (limited to 'hal-interface.c')
-rw-r--r-- | hal-interface.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/hal-interface.c b/hal-interface.c new file mode 100644 index 0000000..b7bdc14 --- /dev/null +++ b/hal-interface.c @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * reference: + * amixer contents; amixer controls; + * http://www.tldp.org/HOWTO/Alsa-sound-6.html + */ +#define _GNU_SOURCE // needed for vasprintf +#include <string.h> +#include "hal-interface.h" +#include <systemd/sd-event.h> + +alsaHalSndCardT *halSndCard; + + +// Force specific HAL to depend on ShareHalLib +PUBLIC char* SharedHalLibVersion = "1.0"; + + +STATIC void pingtest(struct afb_req request) { + json_object *query = afb_req_json(request); + afb_req_success(request, query, NULL); +} + +// Subscribe to AudioBinding events + +STATIC void halSubscribe(afb_req request) { + const char *devidJ = afb_req_value(request, "devid"); + if (devidJ == NULL) { + afb_req_fail_f(request, "devidJ-missing", "devidJ=hw:xxx missing"); + } +} + +// Map HAL ctlName to ctlLabel + +STATIC int halCtlStringToIndex(const char* label) { + alsaHalMapT *halCtls = halSndCard->ctls; + + for (int idx = 0; halCtls[idx].tag != EndHalCrlTag; idx++) { + if (halCtls[idx].label && !strcasecmp(halCtls[idx].label, label)) return idx; + } + + // not found + return -1; +} + +STATIC int halCtlTagToIndex(halCtlsTagT tag) { + alsaHalMapT *halCtls = halSndCard->ctls; + + for (int idx = 0; halCtls[idx].tag != EndHalCrlTag; idx++) { + if (halCtls[idx].tag == tag) return idx; + } + + // not found + return -1; +} + + +// Return ALL HAL snd controls + +PUBLIC void halListCtls(afb_req request) { + alsaHalMapT *halCtls = halSndCard->ctls; + json_object *ctlsHalJ = json_object_new_array(); + + for (int idx = 0; halCtls[idx].ctl.numid; idx++) { + json_object *ctlHalJ = json_object_new_object(); + + if (halCtls[idx].label) json_object_object_add(ctlHalJ, "label", json_object_new_string(halCtls[idx].label)); + else json_object_object_add(ctlHalJ, "label", json_object_new_string("HAL Label Not Set ")); + json_object_object_add(ctlHalJ, "tag", json_object_new_int(halCtls[idx].tag)); + json_object_object_add(ctlHalJ, "count", json_object_new_int(halCtls[idx].ctl.count)); + + json_object_array_add(ctlsHalJ, ctlHalJ); + } + + afb_req_success(request, ctlsHalJ, NULL); +} + +STATIC int halGetCtlIndex(afb_req request, json_object*ctlInJ) { + json_object *tmpJ; + int tag, index, done; + + // check 1st short command mode [tag1, tag2, ...] + enum json_type jtype = json_object_get_type(ctlInJ); + switch (jtype) { + case json_type_array: + tmpJ = json_object_array_get_idx(ctlInJ, 0); + tag = json_object_get_int(tmpJ); + index = halCtlTagToIndex(tag); + break; + + case json_type_int: + tag = json_object_get_int(ctlInJ); + index = halCtlTagToIndex(tag); + break; + + case json_type_object: + done = json_object_object_get_ex(ctlInJ, "tag", &tmpJ); + if (done) { + tag = json_object_get_int(tmpJ); + index = halCtlTagToIndex(tag); + } else { + const char *label; + done = json_object_object_get_ex(ctlInJ, "label", &tmpJ); + if (!done) goto OnErrorExit; + label = json_object_get_string(tmpJ); + index = halCtlStringToIndex(label); + } + break; + + default: + goto OnErrorExit; + } + + if (index < 0) goto OnErrorExit; + + // return corresponding lowlevel numid to querylist + return index; + +OnErrorExit: + afb_req_fail_f(request, "ctl-invalid", "No Label/Tag given ctl='%s'", json_object_get_string(ctlInJ)); + return -1; +} + +STATIC int halCallAlsaSetCtls(json_object *ctlsOutJ) { + json_object *responseJ, *queryJ; + int err; + + // Call now level CTL + queryJ = json_object_new_object(); + json_object_object_add(queryJ, "devid", json_object_new_string(halSndCard->devid)); + json_object_object_add(queryJ, "ctl", ctlsOutJ); + + err = afb_service_call_sync("alsacore", "ctlset", queryJ, &responseJ); + json_object_put(responseJ); // let's ignore response + + return err; +} + + +// retrieve a single HAL control from its tag. + +PUBLIC int halSetCtlByTag(halRampEnumT tag, int value) { + json_object *ctlJ = json_object_new_array(); + alsaHalMapT *halCtls = halSndCard->ctls; + int err, index; + + index = halCtlTagToIndex(tag); + if (index < 0) goto OnErrorExit; + + json_object_array_add(ctlJ, json_object_new_int(halCtls[index].ctl.numid)); + json_object_array_add(ctlJ, volumeNormalise(ACTION_SET, &halCtls[index].ctl, json_object_new_int(value))); + + err = halCallAlsaSetCtls(ctlJ); + + return err; + +OnErrorExit: + return -1; +} + + +// Translate high level control to low level and call lower layer + +PUBLIC void halSetCtls(afb_req request) { + alsaHalMapT *halCtls = halSndCard->ctls; + int err, done, index; + json_object *ctlsInJ, *ctlsOutJ, *valuesJ; + + // get query from request + ctlsInJ = afb_req_json(request); + + switch (json_object_get_type(ctlsInJ)) { + case json_type_object: + { + ctlsOutJ = json_object_new_object(); + + // control is in literal form {tag=xxx, label=xxx, value=xxxx} + index = halGetCtlIndex(request, ctlsInJ); + if (index < 0) goto OnErrorExit; + + done = json_object_object_get_ex(ctlsInJ, "val", &valuesJ); + if (!done) { + afb_req_fail_f(request, "ctl-invalid", "No val=[val1, ...] ctl='%s'", json_object_get_string(ctlsInJ)); + goto OnErrorExit; + } + + json_object_object_add(ctlsOutJ, "id", json_object_new_int(halCtls[index].ctl.numid)); + json_object_object_add(ctlsOutJ, "val", volumeNormalise(ACTION_SET, &halCtls[index].ctl, valuesJ)); + break; + } + + case json_type_array: + { + ctlsOutJ = json_object_new_array(); + + for (int idx = 0; idx < json_object_array_length(ctlsInJ); idx++) { + json_object *ctlInJ = json_object_array_get_idx(ctlsInJ, idx); + index = halGetCtlIndex(request, ctlInJ); + if (index < 0) goto OnErrorExit; + + done = json_object_object_get_ex(ctlInJ, "val", &valuesJ); + if (!done) { + afb_req_fail_f(request, "ctl-invalid", "No val=[val1, ...] ctl='%s'", json_object_get_string(ctlsInJ)); + goto OnErrorExit; + } + // let's create alsa low level set control request + json_object *ctlOutJ = json_object_new_object(); + json_object_object_add(ctlOutJ, "id", json_object_new_int(halCtls[index].ctl.numid)); + json_object_object_add(ctlOutJ, "val", volumeNormalise(ACTION_SET, &halCtls[index].ctl, valuesJ)); + + json_object_array_add(ctlsOutJ, ctlOutJ); + } + break; + } + + default: + afb_req_fail_f(request, "ctl-invalid", "Not a valid JSON ctl='%s'", json_object_get_string(ctlsInJ)); + goto OnErrorExit; + } + + err = halCallAlsaSetCtls(ctlsOutJ); + if (err) { + afb_req_fail_f(request, "subcall:alsacore/setctl", "%s", json_object_get_string(ctlsOutJ)); + goto OnErrorExit; + } + + afb_req_success(request, NULL, NULL); + return; + +OnErrorExit: + return; +}; + +// Remap low level controls into HAL hight level ones + +STATIC json_object *HalGetPrepareResponse(afb_req request, json_object *ctlsJ) { + alsaHalMapT *halCtls = halSndCard->ctls; + json_object *halResponseJ; + int length; + + switch (json_object_get_type(ctlsJ)) { + case json_type_array: + // responseJ is a JSON array + halResponseJ = json_object_new_array(); + length = json_object_array_length(ctlsJ); + break; + case json_type_object: + halResponseJ = NULL; + length = 1; + break; + default: + afb_req_fail_f(request, "ctls-notarray", "Invalid Controls return from alsa/getcontrol ctlsJ=%s", json_object_get_string(ctlsJ)); + goto OnErrorExit; + } + + // loop on array and store values into client context + for (int idx = 0; idx < length; idx++) { + json_object *sndCtlJ, *valJ, *numidJ; + int numid; + + // extract control from array if any + if (halResponseJ) sndCtlJ = json_object_array_get_idx(ctlsJ, idx); + else sndCtlJ = ctlsJ; + + if (!json_object_object_get_ex(sndCtlJ, "id", &numidJ) || !json_object_object_get_ex(sndCtlJ, "val", &valJ)) { + afb_req_fail_f(request, "ctl-invalid", "Invalid Control return from alsa/getcontrol ctl=%s", json_object_get_string(sndCtlJ)); + goto OnErrorExit; + } + + // HAL and Business logic use the same AlsaMixerHal.h direct conversion + numid = (halCtlsTagT) json_object_get_int(numidJ); + + for (int idx = 0; halCtls[idx].ctl.numid; idx++) { + if (halCtls[idx].ctl.numid == numid) { + // translate low level numid to HAL one and normalise values + json_object *halCtlJ = json_object_new_object(); + json_object_object_add(halCtlJ, "label", json_object_new_string(halCtls[idx].label)); // idx+1 == HAL/NUMID + json_object_object_add(halCtlJ, "tag", json_object_new_int(halCtls[idx].tag)); // idx+1 == HAL/NUMID + json_object_object_add(halCtlJ, "val", volumeNormalise(ACTION_GET, &halCtls[idx].ctl, valJ)); + + if (halResponseJ) json_object_array_add(halResponseJ, halCtlJ); + else halResponseJ = halCtlJ; + + break; + } + } + if (halCtls[idx].ctl.numid == 0) { + afb_req_fail_f(request, "ctl-invalid", "Invalid Control numid=%d from alsa/getcontrol ctlJ=%s", numid, json_object_get_string(sndCtlJ)); + goto OnErrorExit; + } + } + return halResponseJ; + +OnErrorExit: + return NULL; +} + +STATIC json_object *halCallAlsaGetCtls(json_object *ctlsOutJ) { + json_object *responseJ, *queryJ; + int err, done; + + // Call now level CTL + queryJ = json_object_new_object(); + json_object_object_add(queryJ, "devid", json_object_new_string(halSndCard->devid)); + json_object_object_add(queryJ, "ctl", ctlsOutJ); + + err = afb_service_call_sync("alsacore", "ctlget", queryJ, &responseJ); + if (err) goto OnErrorExit; + + // Let ignore info data if any and keep on response + done = json_object_object_get_ex(responseJ, "response", &responseJ); + if (!done) goto OnErrorExit; + + return responseJ; + +OnErrorExit: + return NULL; +} + +// retrieve a single HAL control from its tag. + +PUBLIC json_object *halGetCtlByTag(halRampEnumT tag) { + json_object *responseJ, *valJ; + alsaHalMapT *halCtls = halSndCard->ctls; + int done, index; + + index = halCtlTagToIndex(tag); + if (index < 0) goto OnErrorExit; + responseJ = halCallAlsaGetCtls(json_object_new_int(halCtls[index].ctl.numid)); + + done = json_object_object_get_ex(responseJ, "val", &valJ); + if (!done) goto OnErrorExit; + + return volumeNormalise(ACTION_GET, &halCtls[index].ctl, valJ); + +OnErrorExit: + return NULL; +} + + +// Translate high level control to low level and call lower layer + +PUBLIC void halGetCtls(afb_req request) { + int index; + alsaHalMapT *halCtls = halSndCard->ctls; + json_object *ctlsInJ, *ctlsOutJ, *responseJ; + + // get query from request + ctlsInJ = afb_req_json(request); + ctlsOutJ = json_object_new_array(); + + switch (json_object_get_type(ctlsInJ)) { + case json_type_object: + { + + index = halGetCtlIndex(request, ctlsInJ); + if (index < 0) goto OnErrorExit; + json_object_array_add(ctlsOutJ, json_object_new_int(halCtls[index].ctl.numid)); + break; + } + + case json_type_array: + { + + for (int idx = 0; idx < json_object_array_length(ctlsInJ); idx++) { + json_object *ctlInJ = json_object_array_get_idx(ctlsInJ, idx); + index = halGetCtlIndex(request, ctlInJ); + if (index < 0) goto OnErrorExit; + json_object_array_add(ctlsOutJ, json_object_new_int(halCtls[index].ctl.numid)); + } + break; + } + + default: + afb_req_fail_f(request, "ctl-invalid", "Not a valid JSON ctl='%s'", json_object_get_string(ctlsInJ)); + goto OnErrorExit; + } + + // Call now level CTL + responseJ = halCallAlsaGetCtls(ctlsOutJ); + if (!responseJ) { + afb_req_fail_f(request, "subcall:alsacore/getctl", "%s", json_object_get_string(responseJ)); + goto OnErrorExit; + } + + // map back low level response to HAL ctl with normalised values + json_object *halResponse = HalGetPrepareResponse(request, responseJ); + if (!halResponse) goto OnErrorExit; + + afb_req_success(request, halResponse, NULL); + return; + +OnErrorExit: + return; +}; + +STATIC int UpdateOneSndCtl(alsaHalCtlMapT *ctl, json_object *sndCtlJ) { + json_object *tmpJ, *ctlJ; + + json_object_object_get_ex(sndCtlJ, "name", &tmpJ); + ctl->name = (char*) json_object_get_string(tmpJ); + + json_object_object_get_ex(sndCtlJ, "id", &tmpJ); + ctl->numid = json_object_get_int(tmpJ); + + // make sure we face a valid Alsa Low level ctl + if (!json_object_object_get_ex(sndCtlJ, "ctl", &ctlJ)) goto OnErrorExit; + + json_object_object_get_ex(ctlJ, "min", &tmpJ); + ctl->minval = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "max", &tmpJ); + ctl->maxval = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "step", &tmpJ); + ctl->step = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "count", &tmpJ); + ctl->count = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "type", &tmpJ); + ctl->type = (snd_ctl_elem_type_t) json_object_get_int(tmpJ); + + // process dbscale TLV if any + if (json_object_object_get_ex(sndCtlJ, "tlv", &tmpJ)) { + json_object *dbscaleJ; + + if (!json_object_object_get_ex(tmpJ, "dbscale", &dbscaleJ)) { + AFB_WARNING("TLV found but not DBscale attached ctl name=%s numid=%d", ctl->name, ctl->numid); + } else { + ctl->dbscale = malloc(sizeof (alsaHalDBscaleT)); + + json_object_object_get_ex(dbscaleJ, "min", &tmpJ); + ctl->dbscale->min = (snd_ctl_elem_type_t) json_object_get_int(tmpJ); + + json_object_object_get_ex(dbscaleJ, "max", &tmpJ); + ctl->dbscale->max = (snd_ctl_elem_type_t) json_object_get_int(tmpJ); + + json_object_object_get_ex(dbscaleJ, "step", &tmpJ); + ctl->dbscale->step = (snd_ctl_elem_type_t) json_object_get_int(tmpJ); + + json_object_object_get_ex(dbscaleJ, "mute", &tmpJ); + ctl->dbscale->mute = (snd_ctl_elem_type_t) json_object_get_int(tmpJ); + } + } + + return 0; + +OnErrorExit: + return -1; +} + +// this is call when after all bindings are loaded + +PUBLIC int halServiceInit(const char *apiPrefix, alsaHalSndCardT *alsaHalSndCard) { + int err; + json_object *queryurl, *responseJ, *devidJ, *ctlsJ, *tmpJ; + alsaHalMapT *halCtls = alsaHalSndCard->ctls; + + // if not volume normalisation CB provided use default one + if (!alsaHalSndCard->volumeCB) alsaHalSndCard->volumeCB = volumeNormalise; + halSndCard = alsaHalSndCard; + + err = afb_daemon_require_api("alsacore", 1); + if (err) { + AFB_ERROR("AlsaCore missing cannot use AlsaHAL"); + goto OnErrorExit; + } + + // register HAL with Alsa Low Level Binder + queryurl = json_object_new_object(); + json_object_object_add(queryurl, "prefix", json_object_new_string(apiPrefix)); + json_object_object_add(queryurl, "sndname", json_object_new_string(alsaHalSndCard->name)); + + err = afb_service_call_sync("alsacore", "halregister", queryurl, &responseJ); + json_object_put(queryurl); + if (err) { + AFB_NOTICE("Fail to register HAL to ALSA lowlevel binding Response='%s'", json_object_get_string(responseJ)); + goto OnErrorExit; + } + + // extract sound devidJ from HAL registration + if (!json_object_object_get_ex(responseJ, "response", &tmpJ) || !json_object_object_get_ex(tmpJ, "devid", &devidJ)) { + AFB_ERROR("Ooops: Internal error no devidJ return from HAL registration Response='%s'", json_object_get_string(responseJ)); + goto OnErrorExit; + } + + // save devid for future use + halSndCard->devid = strdup(json_object_get_string(devidJ)); + + // for each Non Alsa Control callback create a custom control + ctlsJ = json_object_new_array(); + for (int idx = 0; (halCtls[idx].ctl.name || halCtls[idx].ctl.numid); idx++) { + json_object *ctlJ; + + // Try to find best equivalent label for tag + if (halCtls[idx].tag >StartHalCrlTag && halCtls[idx].tag < EndHalCrlTag && halCtlsLabels[halCtls[idx].tag] != NULL) { + halCtls[idx].label = halCtlsLabels[halCtls[idx].tag]; + } else { + if (halCtls[idx].ctl.name) halCtls[idx].label=halCtls[idx].ctl.name; + else if (halCtls[idx].info) halCtls[idx].label=halCtls[idx].info; + else halCtls[idx].label="NoHalCttNameSet"; + } + + ctlJ = json_object_new_object(); + if (halCtls[idx].ctl.numid) json_object_object_add(ctlJ, "ctl", json_object_new_int(halCtls[idx].ctl.numid)); + if (halCtls[idx].ctl.name) json_object_object_add(ctlJ, "name", json_object_new_string(halCtls[idx].ctl.name)); + if (halCtls[idx].ctl.minval) json_object_object_add(ctlJ, "min", json_object_new_int(halCtls[idx].ctl.minval)); + if (halCtls[idx].ctl.maxval) json_object_object_add(ctlJ, "max", json_object_new_int(halCtls[idx].ctl.maxval)); + if (halCtls[idx].ctl.step) json_object_object_add(ctlJ, "step", json_object_new_int(halCtls[idx].ctl.step)); + if (halCtls[idx].ctl.type) json_object_object_add(ctlJ, "type", json_object_new_int(halCtls[idx].ctl.type)); + if (halCtls[idx].ctl.count) json_object_object_add(ctlJ, "count", json_object_new_int(halCtls[idx].ctl.count)); + if (halCtls[idx].ctl.value) json_object_object_add(ctlJ, "val", json_object_new_int(halCtls[idx].ctl.value)); + + if (halCtls[idx].ctl.dbscale) { + json_object *dbscaleJ = json_object_new_object(); + if (halCtls[idx].ctl.dbscale->max) json_object_object_add(dbscaleJ, "max", json_object_new_int(halCtls[idx].ctl.dbscale->max)); + if (halCtls[idx].ctl.dbscale->min) json_object_object_add(dbscaleJ, "min", json_object_new_int(halCtls[idx].ctl.dbscale->min)); + if (halCtls[idx].ctl.dbscale->step) json_object_object_add(dbscaleJ, "step", json_object_new_int(halCtls[idx].ctl.dbscale->step)); + if (halCtls[idx].ctl.dbscale->mute) json_object_object_add(dbscaleJ, "mute", json_object_new_int(halCtls[idx].ctl.dbscale->mute)); + json_object_object_add(ctlJ, "dbscale", dbscaleJ); + } + + if (halCtls[idx].ctl.enums) { + json_object *enumsJ = json_object_new_array(); + for (int jdx = 0; halCtls[idx].ctl.enums[jdx]; jdx++) { + json_object_array_add(enumsJ, json_object_new_string(halCtls[idx].ctl.enums[jdx])); + } + json_object_object_add(ctlJ, "enums", enumsJ); + } + json_object_array_add(ctlsJ, ctlJ); + } + + // Build new queryJ to add HAL custom control if any + if (json_object_array_length(ctlsJ) > 0) { + queryurl = json_object_new_object(); + json_object_get(devidJ); // make sure devidJ does not get free by 1st call. + json_object_object_add(queryurl, "devid", devidJ); + json_object_object_add(queryurl, "ctl", ctlsJ); + json_object_object_add(queryurl, "mode", json_object_new_int(QUERY_COMPACT)); + err = afb_service_call_sync("alsacore", "addcustomctl", queryurl, &responseJ); + if (err) { + AFB_ERROR("Fail creating HAL Custom ALSA ctls Response='%s'", json_object_get_string(responseJ)); + goto OnErrorExit; + } + } + + // Make sure response is valid + json_object_object_get_ex(responseJ, "response", &ctlsJ); + if (json_object_get_type(ctlsJ) != json_type_array) { + AFB_ERROR("Response Invalid JSON array ctls Response='%s'", json_object_get_string(responseJ)); + goto OnErrorExit; + } + + // update HAL data from JSON response + for (int idx = 0; idx < json_object_array_length(ctlsJ); idx++) { + json_object *ctlJ = json_object_array_get_idx(ctlsJ, idx); + err = UpdateOneSndCtl(&halCtls[idx].ctl, ctlJ); + if (err) { + AFB_ERROR("Fail found MAP Alsa Low level=%s", json_object_get_string(ctlJ)); + goto OnErrorExit; + } + } + + + // finally register for alsa lowlevel event + queryurl = json_object_new_object(); + json_object_object_add(queryurl, "devid", devidJ); + err = afb_service_call_sync("alsacore", "subscribe", queryurl, &responseJ); + if (err) { + AFB_ERROR("Fail subscribing to ALSA lowlevel events"); + goto OnErrorExit; + } + + return (0); + +OnErrorExit: + return (1); +}; + + +// This receive all event this binding subscribe to + +PUBLIC void halServiceEvent(const char *evtname, json_object *eventJ) { + int numid; + alsaHalMapT *halCtls = halSndCard->ctls; + json_object *numidJ, *valuesJ; + + AFB_DEBUG("halServiceEvent evtname=%s [msg=%s]", evtname, json_object_get_string(eventJ)); + + json_object_object_get_ex(eventJ, "id", &numidJ); + numid = json_object_get_int(numidJ); + if (!numid) { + AFB_ERROR("halServiceEvent noid: evtname=%s [msg=%s]", evtname, json_object_get_string(eventJ)); + return; + } + json_object_object_get_ex(eventJ, "val", &valuesJ); + + // search it corresponding numid in halCtls attach a callback + if (numid) { + for (int idx = 0; halCtls[idx].ctl.numid; idx++) { + if (halCtls[idx].ctl.numid == numid && halCtls[idx].cb.callback != NULL) { + halCtls[idx].cb.callback(halCtls[idx].tag, &halCtls[idx].ctl, halCtls[idx].cb.handle, valuesJ); + } + } + } +} + +// Every HAL export the same API & Interface Mapping from SndCard to AudioLogic is done through alsaHalSndCardT +PUBLIC afb_verb_v2 halServiceApi[] = { + /* VERB'S NAME FUNCTION TO CALL SHORT DESCRIPTION */ + { .verb = "ping", .callback = pingtest, .info = "ping test for API"}, + { .verb = "ctllist", .callback = halListCtls, .info = "List AGL normalised Sound Controls"}, + { .verb = "ctlget", .callback = halGetCtls, .info = "Get one/many sound controls"}, + { .verb = "ctlset", .callback = halSetCtls, .info = "Set one/many sound controls"}, + { .verb = "evtsub", .callback = halSubscribe, .info = "Subscribe to HAL events"}, + { .verb = NULL} /* marker for end of the array */ +}; |