/**************************************************************************** * audio/audio_comp.c * * A general audio driver to composite other lower level audio driver. * * Copyright (C) 2018 Pinecone Inc. All rights reserved. * Author: Xiang Xiao * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the internal state of the audio composite */ struct audio_comp_priv_s { /* This is is our appearance to the outside world. This *MUST* be the * first element of the structure so that we can freely cast between * types struct audio_lowerhalf and struct audio_comp_dev_s. */ struct audio_lowerhalf_s export; /* This is the contained, low-level audio device array and count. */ FAR struct audio_lowerhalf_s **lower; int count; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int audio_comp_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps); #else static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps); #endif static int audio_comp_shutdown(FAR struct audio_lowerhalf_s *dev); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_start(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_start(FAR struct audio_lowerhalf_s *dev); #endif #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev); #endif #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session); static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev); static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev); #endif #endif static int audio_comp_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc); static int audio_comp_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc); static int audio_comp_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); static int audio_comp_cancelbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); static int audio_comp_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg); static int audio_comp_read(FAR struct audio_lowerhalf_s *dev, FAR char *buffer, size_t buflen); static int audio_comp_write(FAR struct audio_lowerhalf_s *dev, FAR const char *buffer, size_t buflen); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session); #else static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev); #endif #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_release(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_release(FAR struct audio_lowerhalf_s *dev); #endif #ifdef CONFIG_AUDIO_MULTI_SESSION static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status, FAR void *session); #else static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status); #endif /**************************************************************************** * Private Data ****************************************************************************/ static const struct audio_ops_s g_audio_comp_ops = { audio_comp_getcaps, /* getcaps */ audio_comp_configure, /* configure */ audio_comp_shutdown, /* shutdown */ audio_comp_start, /* start */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP audio_comp_stop, /* stop */ #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME audio_comp_pause, /* pause */ audio_comp_resume, /* resume */ #endif audio_comp_allocbuffer, /* allocbuffer */ audio_comp_freebuffer, /* freebuffer */ audio_comp_enqueuebuffer, /* enqueue_buffer */ audio_comp_cancelbuffer, /* cancel_buffer */ audio_comp_ioctl, /* ioctl */ audio_comp_read, /* read */ audio_comp_write, /* write */ audio_comp_reserve, /* reserve */ audio_comp_release /* release */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: audio_comp_getcaps * * Description: Get the audio device capabilities * ****************************************************************************/ static int audio_comp_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; caps->ac_channels = 0; caps->ac_format.hw = 0; caps->ac_controls.w = 0; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->getcaps) { FAR struct audio_caps_s dup = *caps; int tmp = lower[i]->ops->getcaps(lower[i], type, &dup); if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } if (caps->ac_channels < dup.ac_channels) { caps->ac_channels = dup.ac_channels; } caps->ac_format.hw |= dup.ac_format.hw; caps->ac_controls.w |= dup.ac_controls.w; } } return ret; } /**************************************************************************** * Name: audio_comp_configure * * Description: * Configure the audio device for the specified mode of operation. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps) #else static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->configure) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->configure(lower[i], sess[i], caps); #else int tmp = lower[i]->ops->configure(lower[i], caps); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_shutdown * * Description: * Shutdown the driver and put it in the lowest power state possible. * ****************************************************************************/ static int audio_comp_shutdown(FAR struct audio_lowerhalf_s *dev) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->shutdown) { int tmp = lower[i]->ops->shutdown(lower[i]); if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } /**************************************************************************** * Name: audio_comp_start * * Description: * Start the configured operation (audio streaming, volume enabled, etc.). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_start(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_start(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->start) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->start(lower[i], sess[i]); #else int tmp = lower[i]->ops->start(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->stop) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->stop(lower[i], sess[i]); #else lower[i]->ops->stop(lower[i]); #endif } } break; } } return ret; } /**************************************************************************** * Name: audio_comp_stop * * Description: Stop the configured operation (audio streaming, volume * disabled, etc.). * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->stop) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->stop(lower[i], sess[i]); #else int tmp = lower[i]->ops->stop(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } #endif /**************************************************************************** * Name: audio_comp_pause * * Description: Pauses the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->pause) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->pause(lower[i], sess[i]); #else int tmp = lower[i]->ops->pause(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: audio_comp_resume * * Description: Resumes the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->resume) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->resume(lower[i], sess[i]); #else int tmp = lower[i]->ops->resume(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->pause) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->pause(lower[i], sess[i]); #else lower[i]->ops->pause(lower[i]); #endif } } break; } } return ret; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: audio_comp_allocbuffer * * Description: Allocate an audio pipeline buffer. * ****************************************************************************/ static int audio_comp_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->allocbuffer) { ret = lower[i]->ops->allocbuffer(lower[i], bufdesc); if (ret != -ENOTTY) { break; } } } if (ret == -ENOTTY) { ret = apb_alloc(bufdesc); } return ret; } /**************************************************************************** * Name: audio_comp_freebuffer * * Description: Free an audio pipeline buffer. * ****************************************************************************/ static int audio_comp_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->freebuffer) { ret = lower[i]->ops->freebuffer(lower[i], bufdesc); if (ret != -ENOTTY) { break; } } } if (ret == -ENOTTY) { apb_free(bufdesc->u.buffer); ret = sizeof(*bufdesc); } return ret; } /**************************************************************************** * Name: audio_comp_enqueuebuffer * * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. * ****************************************************************************/ static int audio_comp_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->enqueuebuffer) { ret = lower[i]->ops->enqueuebuffer(lower[i], apb); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_cancelbuffer * * Description: Called when an enqueued buffer is being cancelled. * ****************************************************************************/ static int audio_comp_cancelbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->cancelbuffer) { ret = lower[i]->ops->cancelbuffer(lower[i], apb); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_ioctl * * Description: Perform a device ioctl * ****************************************************************************/ static int audio_comp_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->ioctl) { int tmp = lower[i]->ops->ioctl(lower[i], cmd, arg); if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_read * * Description: Lower-half logic for read commands. * ****************************************************************************/ static int audio_comp_read(FAR struct audio_lowerhalf_s *dev, FAR char *buffer, size_t buflen) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->read) { ret = lower[i]->ops->read(lower[i], buffer, buflen); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_write * * Description: Lower-half logic for write commands. * ****************************************************************************/ static int audio_comp_write(FAR struct audio_lowerhalf_s *dev, FAR const char *buffer, size_t buflen) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->write) { ret = lower[i]->ops->write(lower[i], buffer, buflen); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_reserve * * Description: Reserves a session. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session) #else static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess; #endif int ret = OK; int i; #ifdef CONFIG_AUDIO_MULTI_SESSION sess = kmm_calloc(priv->count, sizeof(*sess)); if (sess == NULL) { return -ENOMEM; } #endif for (i = 0; i < priv->count; i++) { if (lower[i]->ops->reserve) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->reserve(lower[i], &sess[i]); #else int tmp = lower[i]->ops->reserve(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->release) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->release(lower[i], sess[i]); #else lower[i]->ops->release(lower[i]); #endif } } #ifdef CONFIG_AUDIO_MULTI_SESSION kmm_free(sess); sess = NULL; #endif break; } } #ifdef CONFIG_AUDIO_MULTI_SESSION *session = sess; #endif return ret; } /**************************************************************************** * Name: audio_comp_release * * Description: Releases the session. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_release(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_release(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = OK; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->release) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->release(lower[i], sess[i]); #else int tmp = lower[i]->ops->release(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret >= 0) { ret = tmp; } } } #ifdef CONFIG_AUDIO_MULTI_SESSION kmm_free(sess); #endif return ret; } /**************************************************************************** * Name: audio_comp_callback * * Description: * Lower-to-upper level callback for buffer dequeueing. * * Input Parameters: * arg - The value of the 'priv' field from audio_lowerhalf_s. * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status, FAR void *session) #else static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status) #endif { FAR struct audio_comp_priv_s *priv = arg; #ifdef CONFIG_AUDIO_MULTI_SESSION priv->export.upper(priv->export.priv, reason, apb, status, session); #else priv->export.upper(priv->export.priv, reason, apb, status); #endif } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: audio_comp_initialize * * Description: * Initialize the composite audio device. * * Input Parameters: * name - The name of the audio device. * ... - The list of the lower half audio driver. * * Returned Value: * Zero on success; a negated errno value on failure. * * Note * The variable argument list must be NULL terminated. * ****************************************************************************/ int audio_comp_initialize(FAR const char *name, ...) { FAR struct audio_comp_priv_s *priv; va_list ap; va_list cp; int ret = -ENOMEM; int i; va_start(ap, name); va_copy(cp, ap); priv = kmm_zalloc(sizeof(struct audio_comp_priv_s)); if (priv == NULL) { goto end_va; } priv->export.ops = &g_audio_comp_ops; while (va_arg(ap, FAR struct audio_lowerhalf_s *)) { priv->count++; } priv->lower = kmm_calloc(priv->count, sizeof(FAR struct audio_lowerhalf_s *)); if (priv->lower == NULL) { goto free_priv; } for (i = 0; i < priv->count; i++) { FAR struct audio_lowerhalf_s *tmp; tmp = va_arg(cp, FAR struct audio_lowerhalf_s *); tmp->upper = audio_comp_callback; tmp->priv = priv; priv->lower[i] = tmp; } ret = audio_register(name, &priv->export); if (ret < 0) { goto free_lower; } va_end(ap); return OK; free_lower: kmm_free(priv->lower); free_priv: kmm_free(priv); end_va: va_end(ap); return ret; }