/* * Copyright (C) 2018 "IoT.bzh" * Author Jonathan Aillet * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include "../4a-hal-utilities/4a-hal-utilities-data.h" #include "../4a-hal-utilities/4a-hal-utilities-appfw-responses-handler.h" #include "4a-hal-controllers-alsacore-link.h" #include "4a-hal-controllers-value-handler.h" /******************************************************************************* * Map to alsa control types * ******************************************************************************/ static const char *const snd_ctl_elem_type_names[] = { [SND_CTL_ELEM_TYPE_NONE]= "NONE", [SND_CTL_ELEM_TYPE_BOOLEAN]= "BOOLEAN", [SND_CTL_ELEM_TYPE_INTEGER]="INTEGER", [SND_CTL_ELEM_TYPE_ENUMERATED]="ENUMERATED", [SND_CTL_ELEM_TYPE_BYTES]="BYTES", [SND_CTL_ELEM_TYPE_IEC958]="IEC958", [SND_CTL_ELEM_TYPE_INTEGER64]="INTEGER64", }; /******************************************************************************* * Alsa control types map from string function * ******************************************************************************/ snd_ctl_elem_type_t HalCtlsMapsAlsaTypeToEnum(const char *label) { int idx; static int length = sizeof(snd_ctl_elem_type_names) / sizeof(char *); for(idx = 0; idx < length; idx++) { if(! strcasecmp(label, snd_ctl_elem_type_names[idx])) return (snd_ctl_elem_type_t) idx; } return SND_CTL_ELEM_TYPE_NONE; } /******************************************************************************* * Free contents of 'CtlHalAlsaMapT' data structure * ******************************************************************************/ uint8_t HalCtlsFreeAlsaCtlsMap(struct CtlHalAlsaMapT *alsaCtlsMap) { int idx; if(! alsaCtlsMap) return -1; if(alsaCtlsMap->ctlsCount > 0 && ! alsaCtlsMap->ctls) return -2; for(idx = 0; idx < alsaCtlsMap->ctlsCount; idx++) { free(alsaCtlsMap->ctls[idx].action); free(alsaCtlsMap->ctls[idx].ctl.alsaCtlProperties.enums); free(alsaCtlsMap->ctls[idx].ctl.alsaCtlProperties.dbscale); } free(alsaCtlsMap->ctls); free(alsaCtlsMap); return 0; } /******************************************************************************* * HAL controllers alsacore calls funtions * ******************************************************************************/ int HalCtlsGetCardIdByCardPath(AFB_ApiT apiHandle, char *devPath) { int cardId = -1; char *returnedStatus = NULL, *returnedInfo = NULL, *cardIdString = NULL; enum CallError returnedError; json_object *toSendJ, *returnJ = NULL, *responsJ, *devidJ; if(! apiHandle) { AFB_ApiError(apiHandle, "Api handle not available"); return -1; } if(! devPath) { AFB_ApiError(apiHandle, "Dev path is not available"); return -2; } wrap_json_pack(&toSendJ, "{s:s}", "devpath", devPath); if(AFB_ServiceSync(apiHandle, ALSACORE_API, ALSACORE_GETINFO_VERB, toSendJ, &returnJ)) { returnedError = HalUtlHandleAppFwCallError(apiHandle, ALSACORE_API, ALSACORE_GETINFO_VERB, returnJ, &returnedStatus, &returnedInfo); AFB_ApiWarning(apiHandle, "Error %i during call to verb %s of %s api with status '%s' and info '%s'", (int) returnedError, ALSACORE_GETINFO_VERB, ALSACORE_API, returnedStatus ? returnedStatus : "not returned", returnedInfo ? returnedInfo : "not returned"); } else if(json_object_object_get_ex(returnJ, "response", &responsJ)) { if(json_object_object_get_ex(responsJ, "devid", &devidJ) && json_object_is_type(devidJ, json_type_string)) { cardIdString = (char *) json_object_get_string(devidJ); if(sscanf(cardIdString, "hw:%i", &cardId) <= 0) { AFB_ApiWarning(apiHandle, "Couldn't get valid devid from string: '%s'", cardIdString); cardId = -2; } } else { AFB_ApiWarning(apiHandle, "Response devid is not present/valid"); } } if(returnJ) json_object_put(returnJ); return cardId; } int HalCtlsSubscribeToAlsaCardEvent(AFB_ApiT apiHandle, char *cardId) { int err = 0; char *returnedStatus = NULL, *returnedInfo = NULL; enum CallError returnedError; json_object *subscribeQueryJ, *returnedJ = NULL, *returnedWarningJ; wrap_json_pack(&subscribeQueryJ, "{s:s}", "devid", cardId); if(AFB_ServiceSync(apiHandle, ALSACORE_API, ALSACORE_SUBSCRIBE_VERB, subscribeQueryJ, &returnedJ)) { returnedError = HalUtlHandleAppFwCallError(apiHandle, ALSACORE_API, ALSACORE_SUBSCRIBE_VERB, returnedJ, &returnedStatus, &returnedInfo); AFB_ApiError(apiHandle, "Error %i during call to verb %s of %s api with status '%s' and info '%s'", (int) returnedError, ALSACORE_SUBSCRIBE_VERB, ALSACORE_API, returnedStatus ? returnedStatus : "not returned", returnedInfo ? returnedInfo : "not returned"); err = -1; } else if(! wrap_json_unpack(returnedJ, "{s:{s:o}}", "request", "info", &returnedWarningJ)) { AFB_ApiError(apiHandle, "Warning raised during call to verb %s of %s api : '%s'", ALSACORE_SUBSCRIBE_VERB, ALSACORE_API, json_object_get_string(returnedWarningJ)); err = -2; } if(returnedJ) json_object_put(returnedJ); return err; } int HalCtlsGetAlsaCtlInfo(AFB_ApiT apiHandle, char *cardId, struct CtlHalAlsaCtl *currentAlsaCtl, json_object **returnedDataJ) { int err = 0; char *returnedStatus = NULL, *returnedInfo = NULL; enum CallError returnedError; json_object *queryJ, *returnedJ; *returnedDataJ = NULL; if(! apiHandle) { AFB_ApiError(apiHandle, "Api handle not available"); return -1; } if(! cardId) { AFB_ApiError(apiHandle, "Card id is not available"); return -2; } if(! currentAlsaCtl) { AFB_ApiError(apiHandle, "Alsa control data structure is not available"); return -3; } if(currentAlsaCtl->name && currentAlsaCtl->numid > 0) { AFB_ApiDebug(apiHandle, "Both a control name (%s) and a control uid (%i) are specified, control uid will be used", currentAlsaCtl->name, currentAlsaCtl->numid); } if(currentAlsaCtl->numid > 0) { wrap_json_pack(&queryJ, "{s:s s:i s:i}", "devid", cardId, "ctl", currentAlsaCtl->numid, "mode", 3); } else if(currentAlsaCtl->name) { wrap_json_pack(&queryJ, "{s:s s:s s:i}", "devid", cardId, "ctl", currentAlsaCtl->name, "mode", 3); } else { AFB_ApiError(apiHandle, "Need at least a control name or a control uid"); return -4; } if(AFB_ServiceSync(apiHandle, ALSACORE_API, ALSACORE_CTLGET_VERB, queryJ, &returnedJ)) { returnedError = HalUtlHandleAppFwCallError(apiHandle, ALSACORE_API, ALSACORE_CTLGET_VERB, returnedJ, &returnedStatus, &returnedInfo); AFB_ApiError(apiHandle, "Error %i during call to verb %s of %s api with status '%s' and info '%s'", (int) returnedError, ALSACORE_CTLGET_VERB, ALSACORE_API, returnedStatus ? returnedStatus : "not returned", returnedInfo ? returnedInfo : "not returned"); return -5; } else if(currentAlsaCtl->name && wrap_json_unpack(returnedJ, "{s:{s:i}}", "response", "id", ¤tAlsaCtl->numid)) { AFB_ApiError(apiHandle, "Can't find alsa control 'id' from control 'name': '%s' on device '%s'", currentAlsaCtl->name, cardId); err = -6; } else if(! json_object_object_get_ex(returnedJ, "response", NULL)) { AFB_ApiError(apiHandle, "Can't find alsa control 'id': %i on device '%s'", currentAlsaCtl->numid, cardId); err = -7; } if(err) json_object_put(returnedJ); else *returnedDataJ = returnedJ; return err; } int HalCtlsUpdateAlsaCtlProperties(AFB_ApiT apiHandle, char *cardId, struct CtlHalAlsaCtl *currentAlsaCtl) { int err = 0; json_object *returnedDataJ; if((err = HalCtlsGetAlsaCtlInfo(apiHandle, cardId, currentAlsaCtl, &returnedDataJ))) { return err; } // TBD JAI : get dblinear/dbminmax/... values else if(wrap_json_unpack(returnedDataJ, "{s:{s:{s?:i s?:i s?:i s?:i s?:i}}}", "response", "ctl", "type", (int *) ¤tAlsaCtl->alsaCtlProperties.type, "count", ¤tAlsaCtl->alsaCtlProperties.count, "min", ¤tAlsaCtl->alsaCtlProperties.minval, "max", ¤tAlsaCtl->alsaCtlProperties.maxval, "step", ¤tAlsaCtl->alsaCtlProperties.step)) { AFB_ApiError(apiHandle, "Didn't succeed to get control %i properties on device '%s' : '%s'", currentAlsaCtl->numid, cardId, json_object_get_string(returnedDataJ)); err = -8; } if(returnedDataJ) json_object_put(returnedDataJ); return err; } int HalCtlsGetAlsaCtlValues(AFB_ApiT apiHandle, char *cardId, struct CtlHalAlsaCtl *currentAlsaCtl, json_object **returnedValuesJ) { int err = 0; json_object *returnedDataJ = NULL, *returnedValuesArrayJ; *returnedValuesJ = NULL; if((err = HalCtlsGetAlsaCtlInfo(apiHandle, cardId, currentAlsaCtl, &returnedDataJ))) { return err; } else if(wrap_json_unpack(returnedDataJ, "{s:{s:o}}", "response", "val", &returnedValuesArrayJ)) { AFB_ApiError(apiHandle, "Didn't succeed to get control %i values on device '%s' : '%s'", currentAlsaCtl->numid, cardId, json_object_get_string(returnedValuesArrayJ)); err = -8; } else if(! json_object_is_type(returnedValuesArrayJ, json_type_array)) { AFB_ApiError(apiHandle, "Json returned by control %i values on device '%s' are not an array ('%s')", currentAlsaCtl->numid, cardId, json_object_get_string(returnedValuesArrayJ)); err = -9; } if(! err) *returnedValuesJ = json_object_get(returnedValuesArrayJ); if(returnedDataJ) json_object_put(returnedDataJ); return err; } int HalCtlsSetAlsaCtlValue(AFB_ApiT apiHandle, char *cardId, int ctlId, json_object *valuesJ) { int err = 0; char *returnedStatus = NULL, *returnedInfo = NULL; enum CallError returnedError; json_object *queryJ, *returnedJ = NULL, *returnedWarningJ; if(! apiHandle) { AFB_ApiError(apiHandle, "Api handle not available"); return -1; } if(! cardId) { AFB_ApiError(apiHandle, "Card id is not available"); return -2; } if(ctlId <= 0) { AFB_ApiError(apiHandle, "Alsa control id is not valid"); return -3; } if(! valuesJ) { AFB_ApiError(apiHandle, "Values to set json is not available"); return -4; } wrap_json_pack(&queryJ, "{s:s s:{s:i s:o}}", "devid", cardId, "ctl", "id", ctlId, "val", json_object_get(valuesJ)); if(AFB_ServiceSync(apiHandle, ALSACORE_API, ALSACORE_CTLSET_VERB, queryJ, &returnedJ)) { returnedError = HalUtlHandleAppFwCallError(apiHandle, ALSACORE_API, ALSACORE_CTLSET_VERB, returnedJ, &returnedStatus, &returnedInfo); AFB_ApiError(apiHandle, "Error %i during call to verb %s of %s api with status '%s' and info '%s'", (int) returnedError, ALSACORE_CTLSET_VERB, ALSACORE_API, returnedStatus ? returnedStatus : "not returned", returnedInfo ? returnedInfo : "not returned"); err = 1; } else if(! wrap_json_unpack(returnedJ, "{s:{s:o}}", "request", "info", &returnedWarningJ)) { AFB_ApiError(apiHandle, "Warning raised during call to verb %s of %s api : '%s'", ALSACORE_CTLSET_VERB, ALSACORE_API, json_object_get_string(returnedWarningJ)); err = 2; } if(returnedJ) json_object_put(returnedJ); return err; } int HalCtlsCreateAlsaCtl(AFB_ApiT apiHandle, char *cardId, struct CtlHalAlsaCtl *alsaCtlToCreate) { int err = 0; char *returnedStatus = NULL, *returnedInfo = NULL; enum CallError returnedError; json_object *queryJ, *returnedJ = NULL, *returnedWarningJ, *responseJ; if(! apiHandle) { AFB_ApiError(apiHandle, "Api handle not available"); return -1; } if(! cardId) { AFB_ApiError(apiHandle, "Card id is not available"); return -2; } if(! alsaCtlToCreate) { AFB_ApiError(apiHandle, "Alsa control data structure is not available"); return -3; } if(! alsaCtlToCreate->alsaCtlCreation) { AFB_ApiError(apiHandle, "Alsa control data for creation structure is not available"); return -4; } wrap_json_pack(&queryJ, "{s:s s:{s:i s:s s:i s:i s:i s:i s:i}}", "devid", cardId, "ctl", "ctl", -1, "name", alsaCtlToCreate->name, "min", alsaCtlToCreate->alsaCtlCreation->minval, "max", alsaCtlToCreate->alsaCtlCreation->maxval, "step", alsaCtlToCreate->alsaCtlCreation->step, "type", (int) alsaCtlToCreate->alsaCtlCreation->type, "count", alsaCtlToCreate->alsaCtlCreation->count); if(AFB_ServiceSync(apiHandle, ALSACORE_API, ALSACORE_ADDCTL_VERB, queryJ, &returnedJ)) { returnedError = HalUtlHandleAppFwCallError(apiHandle, ALSACORE_API, ALSACORE_ADDCTL_VERB, returnedJ, &returnedStatus, &returnedInfo); AFB_ApiError(apiHandle, "Error %i during call to verb %s of %s api with status '%s' and info '%s'", (int) returnedError, ALSACORE_GETINFO_VERB, ALSACORE_API, returnedStatus ? returnedStatus : "not returned", returnedInfo ? returnedInfo : "not returned"); err = -5; } else if(! wrap_json_unpack(returnedJ, "{s:{s:o}}", "request", "info", &returnedWarningJ)) { AFB_ApiError(apiHandle, "Warning raised during call to verb %s of %s api : '%s'", ALSACORE_GETINFO_VERB, ALSACORE_API, json_object_get_string(returnedWarningJ)); err = -6; } else if(wrap_json_unpack(returnedJ, "{s:o}", "response", &responseJ)) { AFB_ApiError(apiHandle, "Can't get response of call to verb %s of %s api : %s", ALSACORE_GETINFO_VERB, ALSACORE_API, json_object_get_string(returnedJ)); err = -7; } else if(wrap_json_unpack(responseJ, "{s:i}", "id", &alsaCtlToCreate->numid)) { AFB_ApiError(apiHandle, "Can't get create id from %s of %s api", ALSACORE_GETINFO_VERB, ALSACORE_API); err = -8; } else if(wrap_json_unpack(responseJ, "{s:o}", "ctl", NULL)) { AFB_ApiWarning(apiHandle, "Control %s was already present but has been updated", alsaCtlToCreate->name); } if(returnedJ) json_object_put(returnedJ); return err; } /******************************************************************************* * HAL controllers alsacore controls request callback * ******************************************************************************/ void HalCtlsActionOnAlsaCtl(AFB_ReqT request) { char cardIdString[6]; AFB_ApiT apiHandle; CtlConfigT *ctrlConfig; struct SpecificHalData *currentCtlHalData; struct CtlHalAlsaMap *currentAlsaCtl; json_object *requestJson, *valueJ, *convertedJ, *answerJ, *previousControlValuesJ, *normalizedPreviousControlValuesJ, *appliedControlValuesJ, *normalizedAppliedControlValuesJ; apiHandle = (AFB_ApiT) afb_request_get_dynapi(request); if(! apiHandle) { AFB_ReqFail(request, "api_handle", "Can't get current hal controller api handle"); return; } ctrlConfig = (CtlConfigT *) afb_dynapi_get_userdata(apiHandle); if(! ctrlConfig) { AFB_ReqFail(request, "hal_controller_config", "Can't get current hal controller config"); return; } currentCtlHalData = (struct SpecificHalData *) ctrlConfig->external; if(! currentCtlHalData) { AFB_ReqFail(request, "hal_controller_data", "Can't get current hal controller data"); return; } if(currentCtlHalData->status == HAL_STATUS_UNAVAILABLE) { AFB_ReqFail(request, "hal_unavailable", "Seems that hal is not available"); return; } currentAlsaCtl = (struct CtlHalAlsaMap *) afb_request_get_vcbdata(request); if(! currentAlsaCtl) { AFB_ReqFail(request, "alsa_control_data", "Can't get current alsa control data"); return; } if(currentAlsaCtl->ctl.numid <= 0) { AFB_ReqFail(request, "alsa_control_id", "Alsa control id is not valid"); return; } snprintf(cardIdString, 6, "hw:%i", currentCtlHalData->sndCardId); if(HalCtlsGetAlsaCtlValues(apiHandle, cardIdString, ¤tAlsaCtl->ctl, &previousControlValuesJ)) { AFB_ReqFailF(request, "previous_values", "Error when trying to get unchanged alsa control values"); return; } else if(HalCtlsConvertJsonValues(apiHandle, ¤tAlsaCtl->ctl.alsaCtlProperties, previousControlValuesJ, &normalizedPreviousControlValuesJ, CONVERSION_ALSACORE_TO_NORMALIZED)) { AFB_ReqFailF(request, "request_json", "Error when trying to normalize unchanged alsa control values json '%s'", json_object_get_string(previousControlValuesJ)); json_object_put(previousControlValuesJ); return; } requestJson = AFB_ReqJson(request); if(! requestJson) { wrap_json_pack(&answerJ, "{s:o}", "current", normalizedPreviousControlValuesJ); AFB_ReqSuccess(request, answerJ, "Current controls values"); json_object_put(previousControlValuesJ); return; } if(! json_object_is_type(requestJson, json_type_object)) { AFB_ReqFailF(request, "request_json", "Request json is not valid '%s'", json_object_get_string(requestJson)); json_object_put(previousControlValuesJ); json_object_put(normalizedPreviousControlValuesJ); return; } if(wrap_json_unpack(requestJson, "{s:o}", "value", &valueJ)) { AFB_ReqFailF(request, "request_json", "Error when trying to get request value object inside request '%s'", json_object_get_string(requestJson)); json_object_put(previousControlValuesJ); json_object_put(normalizedPreviousControlValuesJ); return; } if((! json_object_is_type(valueJ, json_type_string)) && HalCtlsConvertJsonValues(apiHandle, ¤tAlsaCtl->ctl.alsaCtlProperties, valueJ, &convertedJ, CONVERSION_NORMALIZED_TO_ALSACORE)) { AFB_ReqFailF(request, "request_json", "Error when trying to convert request values '%s'", json_object_get_string(valueJ)); json_object_put(previousControlValuesJ); json_object_put(normalizedPreviousControlValuesJ); return; } else if(json_object_is_type(valueJ, json_type_string) && HalCtlsChangePreviousValuesUsingJson(apiHandle, ¤tAlsaCtl->ctl.alsaCtlProperties, valueJ, previousControlValuesJ, &convertedJ)) { AFB_ReqFailF(request, "previous_values", "Error when trying to generate changed alsa control values (values : '%s', previous :'%s')", json_object_get_string(valueJ), json_object_get_string(previousControlValuesJ)); json_object_put(previousControlValuesJ); json_object_put(normalizedPreviousControlValuesJ); return; } json_object_put(previousControlValuesJ); if(HalCtlsSetAlsaCtlValue(apiHandle, cardIdString, currentAlsaCtl->ctl.numid, convertedJ)) { AFB_ReqFailF(request, "alsa_control_call_error", "Error while trying to set value on alsa control %i, device '%s', converted message '%s'", currentAlsaCtl->ctl.numid, cardIdString, json_object_get_string(convertedJ)); json_object_put(convertedJ); json_object_put(normalizedPreviousControlValuesJ); return; } json_object_put(convertedJ); if(HalCtlsGetAlsaCtlValues(apiHandle, cardIdString, ¤tAlsaCtl->ctl, &appliedControlValuesJ)) { AFB_ReqFailF(request, "applied_values", "Error when trying to get applied alsa control values"); json_object_put(normalizedPreviousControlValuesJ); return; } else if(HalCtlsConvertJsonValues(apiHandle, ¤tAlsaCtl->ctl.alsaCtlProperties, appliedControlValuesJ, &normalizedAppliedControlValuesJ, CONVERSION_ALSACORE_TO_NORMALIZED)) { AFB_ReqFailF(request, "request_json", "Error when trying to normalize applied values json '%s'", json_object_get_string(appliedControlValuesJ)); json_object_put(normalizedPreviousControlValuesJ); json_object_put(appliedControlValuesJ); return; } json_object_put(appliedControlValuesJ); wrap_json_pack(&answerJ, "{s:o, s:o}", "previous", normalizedPreviousControlValuesJ, "current", normalizedAppliedControlValuesJ); AFB_ReqSuccess(request, answerJ, "Values correctly applied on alsa control"); }