| /* |
| ** Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. |
| ** Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. |
| ** |
| ** Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions are |
| ** met: |
| ** * Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** * 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. |
| ** * Neither the name of The Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED |
| ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| ** 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. |
| **/ |
| |
| #define LOG_TAG "AGM: device" |
| |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sched.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <agm/device.h> |
| #include <agm/metadata.h> |
| #include <agm/utils.h> |
| #ifdef DEVICE_USES_ALSALIB |
| #include <alsa/asoundlib.h> |
| #else |
| #include <tinyalsa/asoundlib.h> |
| #endif |
| |
| #define SNDCARD_PATH "/sys/kernel/snd_card/card_state" |
| #define PCM_DEVICE_FILE "/proc/asound/pcm" |
| #define MAX_RETRY 100 /*Device will try these many times before return an error*/ |
| #define RETRY_INTERVAL 1 /*Retry interval in seconds*/ |
| |
| #ifdef DYNAMIC_LOG_ENABLED |
| #include <log_xml_parser.h> |
| #define LOG_MASK AGM_MOD_FILE_DEVICE |
| #include <log_utils.h> |
| #endif |
| |
| #define TRUE 1 |
| #define FALSE 0 |
| |
| #define DEVICE_ENABLE 1 |
| #define DEVICE_DISABLE 0 |
| |
| #define BUF_SIZE 1024 |
| #define FILE_PATH_EXTN_MAX_SIZE 80 |
| #define MAX_RETRY_CNT 20 |
| #define SND_CARD_DEVICE_FILE "/proc/asound/cards" |
| |
| /* Global list to store supported devices */ |
| static struct listnode device_list; |
| static struct listnode device_group_data_list; |
| static uint32_t num_audio_intfs; |
| static uint32_t num_group_devices; |
| |
| #ifdef DEVICE_USES_ALSALIB |
| static snd_ctl_t *mixer; |
| #else |
| static struct mixer *mixer = NULL; |
| #endif |
| |
| #define SYSFS_FD_PATH "/sys/kernel/aud_dev/state" |
| static int sysfs_fd = -1; |
| |
| #define MAX_BUF_SIZE 2048 |
| /** |
| * The maximum period bytes for dummy dai is 8192 bytes. |
| * Hard coding of period size to 960 frames was leading |
| * to bigger period bytes for multi channels. |
| * So, now based on frame size, Period size is being calculated. |
| * 1 frame = bytes_per_sample * channels |
| * period size = 8192/(bytes_per_sample * channels) |
| */ |
| #define MAX_PERIOD_BUFFER 8192 |
| #define DEFAULT_PERIOD_COUNT 2 |
| |
| #define MAX_USR_INPUT 9 |
| |
| #define AGM_PCM_RATE_5512 (5512) |
| #define AGM_PCM_RATE_8000 (8000) |
| #define AGM_PCM_RATE_11025 (11025) |
| #define AGM_PCM_RATE_16000 (16000) |
| #define AGM_PCM_RATE_22050 (2205) |
| #define AGM_PCM_RATE_32000 (32000) |
| #define AGM_PCM_RATE_44100 (44100) |
| #define AGM_PCM_RATE_48000 (48000) |
| #define AGM_PCM_RATE_64000 (64000) |
| #define AGM_PCM_RATE_88200 (88200) |
| #define AGM_PCM_RATE_96000 (96000) |
| #define AGM_PCM_RATE_176400 (176400) |
| #define AGM_PCM_RATE_192000 (192000) |
| #define AGM_PCM_RATE_352800 (352800) |
| #define AGM_PCM_RATE_384000 (384000) |
| |
| #define AGM_DEFAULT_PCM_RATE (AGM_PCM_RATE_48000) |
| |
| /** Sound card state */ |
| typedef enum snd_card_status_t { |
| SND_CARD_STATUS_OFFLINE = 0, |
| SND_CARD_STATUS_ONLINE, |
| SND_CARD_STATUS_NONE, |
| } snd_card_status_t; |
| |
| int get_pcm_bits_per_sample(enum agm_media_format fmt_id) |
| { |
| int bits_per_sample = 16; |
| |
| switch(fmt_id) { |
| case AGM_FORMAT_PCM_S8: /**< 8-bit signed */ |
| bits_per_sample = 8; |
| break; |
| case AGM_FORMAT_PCM_S24_LE: /**< 24-bits in 4-bytes, 8_24 form*/ |
| bits_per_sample = 32; |
| break; |
| case AGM_FORMAT_PCM_S24_3LE: /**< 24-bits in 3-bytes */ |
| bits_per_sample = 24; |
| break; |
| case AGM_FORMAT_PCM_S32_LE: /**< 32-bit signed */ |
| bits_per_sample = 32; |
| break; |
| case AGM_FORMAT_PCM_S16_LE: /**< 16-bit signed */ |
| default: |
| bits_per_sample = 16; |
| break; |
| } |
| return bits_per_sample; |
| } |
| |
| static void update_sysfs_fd (int8_t pcm_id, int8_t state) |
| { |
| char buf[MAX_USR_INPUT]={0}; |
| snprintf(buf, MAX_USR_INPUT,"%d %d", pcm_id, state); |
| if (sysfs_fd >= 0) |
| write(sysfs_fd, buf, MAX_USR_INPUT); |
| else { |
| /*AGM service and sysfs file creation are async events. |
| *Also when the syfs node is first created the default |
| *user attribute for the sysfs file is root. |
| *To change the same we need to execute a command from |
| *init scripts, which again are async and there is no |
| *deterministic way of scheduling this command only after |
| *the sysfs node is created. And all this should happen before |
| *we try to open the file from agm context, otherwise the open |
| *call would fail with permission denied error. |
| *Hence we try to open the file first time when we access it instead |
| *of doing it from agm_init. |
| *This gives the system enough time for the file attributes to |
| *be changed. |
| */ |
| sysfs_fd = open(SYSFS_FD_PATH, O_WRONLY); |
| if (sysfs_fd >= 0) { |
| write(sysfs_fd, buf, MAX_USR_INPUT); |
| } else { |
| AGM_LOGE("invalid file handle\n"); |
| } |
| } |
| } |
| |
| int device_get_snd_card_id() |
| { |
| struct device_obj *dev_obj = node_to_item(list_head(&device_list), |
| struct device_obj, list_node); |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("%s: Invalid device object\n", __func__); |
| return -EINVAL; |
| } |
| return dev_obj->card_id; |
| } |
| |
| static struct device_obj *device_get_pcm_obj(struct device_obj *dev_obj) |
| { |
| if (dev_obj->parent_dev) |
| return dev_obj->parent_dev; |
| else |
| return dev_obj; |
| } |
| |
| #ifdef DEVICE_USES_ALSALIB |
| snd_pcm_format_t agm_to_alsa_format(enum agm_media_format format) |
| { |
| switch (format) { |
| case AGM_FORMAT_PCM_S32_LE: |
| return SND_PCM_FORMAT_S32_LE; |
| case AGM_FORMAT_PCM_S8: |
| return SND_PCM_FORMAT_S8; |
| case AGM_FORMAT_PCM_S24_3LE: |
| return SND_PCM_FORMAT_S24_3LE; |
| case AGM_FORMAT_PCM_S24_LE: |
| return SND_PCM_FORMAT_S24_LE; |
| default: |
| case AGM_FORMAT_PCM_S16_LE: |
| return SND_PCM_FORMAT_S16_LE; |
| }; |
| } |
| |
| |
| int device_open(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| snd_pcm_t *pcm; |
| char pcm_name[80]; |
| snd_pcm_stream_t stream; |
| snd_pcm_hw_params_t *hwparams; |
| snd_pcm_format_t format; |
| snd_pcm_uframes_t period_size; |
| unsigned int rate, channels, period_count; |
| struct device_group_data *grp_data = NULL; |
| struct agm_media_config *media_config = NULL; |
| struct device_obj *obj = NULL; |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("%s: Invalid device object\n", __func__); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| |
| snprintf(pcm_name, sizeof(pcm_name), "hw:%u,%u", obj->card_id, obj->pcm_id); |
| |
| stream = (obj->hw_ep_info.dir == AUDIO_OUTPUT) ? |
| SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE; |
| |
| pthread_mutex_lock(&obj->lock); |
| |
| if (obj->group_data) |
| grp_data = obj->group_data; |
| |
| if (obj->refcnt.open) { |
| AGM_LOGE("%s: PCM device %u already opened\n", |
| __func__, obj->pcm_id); |
| obj->refcnt.open++; |
| if (grp_data) |
| grp_data->refcnt.open++; |
| goto done; |
| } |
| |
| if (grp_data && !grp_data->has_multiple_dai_link) |
| media_config = &grp_data->media_config.config; |
| else |
| media_config = &dev_obj->media_config; |
| |
| channels = media_config->channels; |
| rate = media_config->rate; |
| format = agm_to_alsa_format(media_config->format); |
| period_size = (MAX_PERIOD_BUFFER)/(channels * |
| (get_pcm_bits_per_sample(media_config->format)/8)); |
| period_count = DEFAULT_PERIOD_COUNT; |
| |
| ret = snd_pcm_open(&pcm, pcm_name, stream, 0); |
| if (ret < 0) { |
| AGM_LOGE("%s: Unable to open PCM device %s", __func__, pcm_name); |
| goto done; |
| } |
| |
| snd_pcm_hw_params_alloca(&hwparams); |
| |
| ret = snd_pcm_hw_params_any(pcm, hwparams); |
| if (ret < 0) { |
| AGM_LOGE("Default config not available for %s, exiting", pcm_name); |
| goto done; |
| } |
| snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); |
| snd_pcm_hw_params_set_format(pcm, hwparams, format); |
| snd_pcm_hw_params_set_channels(pcm, hwparams, channels); |
| snd_pcm_hw_params_set_rate(pcm, hwparams, rate, 0); |
| snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0); |
| snd_pcm_hw_params_set_periods(pcm, hwparams, period_count, 0); |
| |
| ret = snd_pcm_hw_params(pcm, hwparams); |
| if (ret < 0) { |
| AGM_LOGE("%s unable to set hw params for %s, rate[%u], ch[%u], fmt[%u]", |
| __func__, pcm_name, rate, channels, format); |
| goto done; |
| } |
| |
| update_sysfs_fd(obj->pcm_id, DEVICE_ENABLE); |
| obj->pcm = pcm; |
| obj->state = DEV_OPENED; |
| obj->refcnt.open++; |
| if (grp_data) |
| grp_data->refcnt.open++; |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| #else |
| enum pcm_format agm_to_pcm_format(enum agm_media_format format) |
| { |
| switch (format) { |
| case AGM_FORMAT_PCM_S32_LE: |
| return PCM_FORMAT_S32_LE; |
| case AGM_FORMAT_PCM_S8: |
| return PCM_FORMAT_S8; |
| case AGM_FORMAT_PCM_S24_3LE: |
| return PCM_FORMAT_S24_3LE; |
| case AGM_FORMAT_PCM_S24_LE: |
| return PCM_FORMAT_S24_LE; |
| default: |
| case AGM_FORMAT_PCM_S16_LE: |
| return PCM_FORMAT_S16_LE; |
| }; |
| } |
| |
| bool device_pcm_is_rate_supported(unsigned int rate) |
| { |
| switch(rate) { |
| case AGM_PCM_RATE_5512: |
| case AGM_PCM_RATE_8000: |
| case AGM_PCM_RATE_11025: |
| case AGM_PCM_RATE_16000: |
| case AGM_PCM_RATE_22050: |
| case AGM_PCM_RATE_32000: |
| case AGM_PCM_RATE_44100: |
| case AGM_PCM_RATE_48000: |
| case AGM_PCM_RATE_64000: |
| case AGM_PCM_RATE_88200: |
| case AGM_PCM_RATE_96000: |
| case AGM_PCM_RATE_176400: |
| case AGM_PCM_RATE_192000: |
| case AGM_PCM_RATE_352800: |
| case AGM_PCM_RATE_384000: |
| return true; |
| default: |
| return false; |
| }; |
| } |
| |
| int device_open(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| struct pcm *pcm = NULL; |
| struct pcm_config config; |
| uint32_t pcm_flags; |
| struct device_group_data *grp_data = NULL; |
| struct agm_media_config *media_config = NULL; |
| struct device_obj *obj = NULL; |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| pthread_mutex_lock(&obj->lock); |
| |
| if (obj->group_data) |
| grp_data = obj->group_data; |
| |
| if (obj->refcnt.open) { |
| AGM_LOGI("PCM device %u already opened\n", |
| obj->pcm_id); |
| obj->refcnt.open++; |
| if (grp_data) |
| grp_data->refcnt.open++; |
| goto done; |
| } |
| |
| memset(&config, 0, sizeof(struct pcm_config)); |
| if (grp_data && !grp_data->has_multiple_dai_link) |
| media_config = &grp_data->media_config.config; |
| else |
| media_config = &dev_obj->media_config; |
| |
| config.channels = media_config->channels; |
| config.rate = media_config->rate; |
| if (!device_pcm_is_rate_supported(config.rate)) { |
| AGM_LOGD("Unsupported PCM rate %d changing to default rate %d\n", |
| config.rate, AGM_DEFAULT_PCM_RATE); |
| config.rate = AGM_DEFAULT_PCM_RATE; |
| } |
| |
| config.format = agm_to_pcm_format(media_config->format); |
| config.period_size = (MAX_PERIOD_BUFFER)/(config.channels * |
| (get_pcm_bits_per_sample(media_config->format)/8)); |
| |
| config.period_count = DEFAULT_PERIOD_COUNT; |
| config.start_threshold = config.period_size / 4; |
| config.stop_threshold = INT_MAX; |
| |
| pcm_flags = (obj->hw_ep_info.dir == AUDIO_OUTPUT) ? PCM_OUT : PCM_IN; |
| pcm = pcm_open(obj->card_id, obj->pcm_id, pcm_flags, |
| &config); |
| if (!pcm || !pcm_is_ready(pcm)) { |
| AGM_LOGE("Unable to open PCM device %u (%s) rate %u ch %d fmt %u", |
| obj->pcm_id, pcm_get_error(pcm), config.rate, |
| config.channels, config.format); |
| AGM_LOGE("Period Size %d \n", config.period_size); |
| ret = -EIO; |
| goto done; |
| } |
| update_sysfs_fd(obj->pcm_id, DEVICE_ENABLE); |
| obj->pcm = pcm; |
| obj->state = DEV_OPENED; |
| obj->refcnt.open++; |
| if (grp_data) |
| grp_data->refcnt.open++; |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| #endif |
| |
| int device_prepare(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| struct device_group_data *grp_data = NULL; |
| struct device_obj *obj = NULL; |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| |
| pthread_mutex_lock(&obj->lock); |
| |
| if (obj->group_data) |
| grp_data = obj->group_data; |
| |
| if (obj->refcnt.prepare) { |
| AGM_LOGD("PCM device %u already in prepare state\n", |
| obj->pcm_id); |
| obj->refcnt.prepare++; |
| if (grp_data) |
| grp_data->refcnt.prepare++; |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| #ifdef DEVICE_USES_ALSALIB |
| ret = snd_pcm_prepare(obj->pcm); |
| #else |
| ret = pcm_prepare(obj->pcm); |
| #endif |
| if (ret) { |
| AGM_LOGE("PCM device %u prepare failed, ret = %d\n", |
| obj->pcm_id, ret); |
| goto done; |
| } |
| |
| obj->state = DEV_PREPARED; |
| obj->refcnt.prepare++; |
| |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| |
| int device_start(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| struct device_group_data *grp_data = NULL; |
| struct device_obj *obj = NULL; |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| |
| pthread_mutex_lock(&obj->lock); |
| if (obj->state < DEV_PREPARED) { |
| AGM_LOGE("PCM device %u not yet prepared, exiting\n", |
| obj->pcm_id); |
| ret = -1; |
| goto done; |
| } |
| |
| if (obj->group_data) |
| grp_data = obj->group_data; |
| |
| if (obj->refcnt.start) { |
| AGM_LOGI("PCM device %u already in start state\n", |
| obj->pcm_id); |
| obj->refcnt.start++; |
| if (grp_data) |
| grp_data->refcnt.start++; |
| goto done; |
| } |
| |
| obj->state = DEV_STARTED; |
| obj->refcnt.start++; |
| if (grp_data) |
| grp_data->refcnt.start++; |
| |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| |
| int device_stop(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| struct device_group_data *grp_data = NULL; |
| struct device_obj *obj = NULL; |
| |
| if(dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| |
| pthread_mutex_lock(&obj->lock); |
| if (!obj->refcnt.start) { |
| AGM_LOGE("PCM device %u already stopped\n", |
| obj->pcm_id); |
| goto done; |
| } |
| |
| if (obj->group_data) { |
| grp_data = obj->group_data; |
| grp_data->refcnt.start--; |
| } |
| |
| obj->refcnt.start--; |
| if (obj->refcnt.start == 0) { |
| #ifdef DEVICE_USES_ALSALIB |
| ret = snd_pcm_drop(obj->pcm); |
| #else |
| ret = pcm_stop(obj->pcm); |
| #endif |
| if (ret) { |
| AGM_LOGE("PCM device %u stop failed, ret = %d\n", |
| obj->pcm_id, ret); |
| } |
| obj->state = DEV_STOPPED; |
| } |
| |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| |
| int device_close(struct device_obj *dev_obj) |
| { |
| int ret = 0; |
| struct device_group_data *grp_data = NULL; |
| struct device_obj *obj = NULL; |
| |
| if (dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| obj = device_get_pcm_obj(dev_obj); |
| |
| pthread_mutex_lock(&obj->lock); |
| if (!obj->refcnt.open) { |
| AGM_LOGE("PCM device %u already closed\n", |
| obj->pcm_id); |
| goto done; |
| } |
| |
| if (obj->group_data) { |
| grp_data = obj->group_data; |
| if (--grp_data->refcnt.open == 0) { |
| grp_data->refcnt.prepare = 0; |
| grp_data->refcnt.start = 0; |
| } |
| } |
| |
| if (--obj->refcnt.open == 0) { |
| update_sysfs_fd(obj->pcm_id, DEVICE_DISABLE); |
| #ifdef DEVICE_USES_ALSALIB |
| ret = snd_pcm_close(obj->pcm); |
| #else |
| ret = pcm_close(obj->pcm); |
| #endif |
| if (ret) { |
| AGM_LOGE("PCM device %u close failed, ret = %d\n", |
| obj->pcm_id, ret); |
| } |
| obj->state = DEV_CLOSED; |
| obj->refcnt.prepare = 0; |
| obj->refcnt.start = 0; |
| } |
| |
| done: |
| pthread_mutex_unlock(&obj->lock); |
| return ret; |
| } |
| |
| enum device_state device_current_state(struct device_obj *dev_obj) |
| { |
| return dev_obj->state; |
| } |
| |
| int device_get_aif_info_list(struct aif_info *aif_list, size_t *audio_intfs) |
| { |
| struct device_obj *dev_obj; |
| uint32_t copied = 0; |
| uint32_t requested = *audio_intfs; |
| struct listnode *dev_node, *temp; |
| |
| if (*audio_intfs == 0){ |
| *audio_intfs = num_audio_intfs; |
| } else { |
| list_for_each_safe(dev_node, temp, &device_list) { |
| dev_obj = node_to_item(dev_node, struct device_obj, list_node); |
| strlcpy(aif_list[copied].aif_name, dev_obj->name, |
| AIF_NAME_MAX_LEN); |
| aif_list[copied].dir = dev_obj->hw_ep_info.dir; |
| copied++; |
| if (copied == requested) |
| break; |
| } |
| *audio_intfs = copied; |
| } |
| return 0; |
| } |
| |
| int device_get_group_list(struct aif_info *aif_list, size_t *num_groups) |
| { |
| struct device_group_data *grp_data; |
| uint32_t copied = 0; |
| uint32_t requested = *num_groups; |
| struct listnode *grp_node, *temp; |
| |
| if (*num_groups == 0){ |
| *num_groups = num_group_devices; |
| } else { |
| list_for_each_safe(grp_node, temp, &device_group_data_list) { |
| grp_data = node_to_item(grp_node, struct device_group_data, list_node); |
| strlcpy(aif_list[copied].aif_name, grp_data->name, |
| AIF_NAME_MAX_LEN); |
| copied++; |
| if (copied == requested) |
| break; |
| } |
| *num_groups = copied; |
| } |
| return 0; |
| } |
| |
| int device_get_obj(uint32_t device_idx, struct device_obj **dev_obj) |
| { |
| int i = 0; |
| struct listnode *dev_node, *temp; |
| struct device_obj *obj; |
| |
| if (device_idx > num_audio_intfs) { |
| AGM_LOGE("Invalid device_id %u, max_supported device id: %d\n", |
| device_idx, num_audio_intfs); |
| return -EINVAL; |
| } |
| |
| list_for_each_safe(dev_node, temp, &device_list) { |
| if (i++ == device_idx) { |
| obj = node_to_item(dev_node, struct device_obj, list_node); |
| *dev_obj = obj; |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| int device_get_group_data(uint32_t group_id , struct device_group_data **grp_data) |
| { |
| int i = 0; |
| struct listnode *group_node, *temp; |
| struct device_group_data *data; |
| |
| if (group_id >= num_group_devices) { |
| AGM_LOGE("Invalid group_id %u, max_supported device id: %d\n", |
| group_id, num_group_devices); |
| return -EINVAL; |
| } |
| |
| list_for_each_safe(group_node, temp, &device_group_data_list) { |
| if (i++ == group_id) { |
| data = node_to_item(group_node, struct device_group_data, list_node); |
| *grp_data = data; |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| int device_set_media_config(struct device_obj *dev_obj, |
| struct agm_media_config *device_media_config) |
| { |
| if (dev_obj == NULL || device_media_config == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return -EINVAL; |
| } |
| |
| pthread_mutex_lock(&dev_obj->lock); |
| dev_obj->media_config.channels = device_media_config->channels; |
| dev_obj->media_config.rate = device_media_config->rate; |
| dev_obj->media_config.format = device_media_config->format; |
| dev_obj->media_config.data_format = device_media_config->data_format; |
| pthread_mutex_unlock(&dev_obj->lock); |
| |
| return 0; |
| } |
| |
| int device_group_set_media_config(struct device_group_data *grp_data, |
| struct agm_group_media_config *media_config) |
| { |
| if (grp_data == NULL || media_config == NULL) { |
| AGM_LOGE("Invalid input\n"); |
| return -EINVAL; |
| } |
| memcpy(&grp_data->media_config, media_config, sizeof(*media_config)); |
| |
| return 0; |
| } |
| |
| int device_set_metadata(struct device_obj *dev_obj, uint32_t size, |
| uint8_t *metadata) |
| { |
| int ret = 0; |
| |
| pthread_mutex_lock(&dev_obj->lock); |
| metadata_free(&dev_obj->metadata); |
| ret = metadata_copy(&(dev_obj->metadata), size, metadata); |
| pthread_mutex_unlock(&dev_obj->lock); |
| |
| return ret; |
| } |
| |
| int device_set_params(struct device_obj *dev_obj, |
| void *payload, size_t size) |
| { |
| int ret = 0; |
| |
| pthread_mutex_lock(&dev_obj->lock); |
| |
| if (dev_obj->params) { |
| free(dev_obj->params); |
| dev_obj->params = NULL; |
| dev_obj->params_size = 0; |
| } |
| |
| dev_obj->params = calloc(1, size); |
| if (!dev_obj->params) { |
| AGM_LOGE("No memory for dev params on dev_id:%d\n", |
| dev_obj->pcm_id); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| memcpy(dev_obj->params, payload, size); |
| dev_obj->params_size = size; |
| |
| done: |
| pthread_mutex_unlock(&dev_obj->lock); |
| return ret; |
| } |
| |
| #ifdef DEVICE_USES_ALSALIB |
| int device_get_channel_map(struct device_obj *dev_obj, uint32_t **chmap) |
| { |
| snd_ctl_elem_id_t *id; |
| snd_ctl_elem_info_t *info; |
| snd_ctl_elem_value_t *control; |
| char *mixer_str = NULL; |
| int i, ctl_len = 0, ret = 0, card_id; |
| uint8_t *payload = NULL; |
| char card[16]; |
| char *dev_name = NULL; |
| |
| if (mixer == NULL) { |
| card_id = device_get_snd_card_id(); |
| if (card_id < 0) { |
| AGM_LOGE("%s: failed to get card_id", __func__); |
| return -EINVAL; |
| } |
| |
| snprintf(card, 16, "hw:%u", card_id); |
| if ((ret = snd_ctl_open(&mixer, card, 0)) < 0) { |
| AGM_LOGE("Control device %s open error: %s", card, snd_strerror(ret)); |
| return ret; |
| } |
| |
| } |
| |
| if (dev_obj->parent_dev) |
| dev_name = dev_obj->parent_dev->name; |
| else |
| dev_name = dev_obj->name; |
| |
| ctl_len = strlen(dev_name) + 1 + strlen("Channel Map") + 1; |
| mixer_str = calloc(1, ctl_len); |
| if (!mixer_str) { |
| AGM_LOGE("%s: Failed to allocate memory for mixer_str", __func__); |
| return -ENOMEM; |
| } |
| |
| payload = calloc(16, sizeof(uint32_t)); |
| if (!payload) { |
| AGM_LOGE("%s: Failed to allocate memory for payload", __func__); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| snprintf(mixer_str, ctl_len, "%s %s", dev_name, "Channel Map"); |
| |
| snd_ctl_elem_id_alloca(&id); |
| snd_ctl_elem_info_alloca(&info); |
| snd_ctl_elem_value_alloca(&control); |
| |
| snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); |
| snd_ctl_elem_id_set_name(id, (const char *)mixer_str); |
| |
| snd_ctl_elem_info_set_id(info, id); |
| ret = snd_ctl_elem_info(mixer, info); |
| if (ret < 0) { |
| AGM_LOGE("Cannot get element info for %s\n", mixer_str); |
| goto done; |
| } |
| snd_ctl_elem_info_get_id(info, id); |
| |
| snd_ctl_elem_value_set_id(control, id); |
| ret = snd_ctl_elem_read(mixer, control); |
| if (ret < 0) { |
| AGM_LOGE("Failed to mixer_ctl_get_array\n"); |
| goto err_get_ctl; |
| } |
| |
| for (i = 0; i < 16 * sizeof(uint32_t); i++) |
| payload[i] = snd_ctl_elem_value_get_byte(control, i); |
| |
| *chmap = payload; |
| goto done; |
| |
| err_get_ctl: |
| done: |
| free(mixer_str); |
| return ret; |
| } |
| #else |
| int device_get_channel_map(struct device_obj *dev_obj, uint32_t **chmap) |
| { |
| struct mixer_ctl *ctl = NULL; |
| char *mixer_str = NULL; |
| int ctl_len = 0, ret = 0, card_id; |
| void *payload = NULL; |
| char *dev_name = NULL; |
| |
| if (mixer == NULL) { |
| card_id = device_get_snd_card_id(); |
| if (card_id < 0) { |
| AGM_LOGE("failed to get card_id"); |
| return -EINVAL; |
| } |
| |
| mixer = mixer_open(card_id); |
| if (!mixer) { |
| AGM_LOGE("failed to get mixer handle"); |
| return -EINVAL; |
| } |
| } |
| |
| if (dev_obj->parent_dev) |
| dev_name = dev_obj->parent_dev->name; |
| else |
| dev_name = dev_obj->name; |
| |
| ctl_len = strlen(dev_name) + 1 + strlen("Channel Map") + 1; |
| mixer_str = calloc(1, ctl_len); |
| if (!mixer_str) { |
| AGM_LOGE("Failed to allocate memory for mixer_str"); |
| return -ENOMEM; |
| } |
| |
| payload = calloc(16, sizeof(uint32_t)); |
| if (!payload) { |
| AGM_LOGE("Failed to allocate memory for payload"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| snprintf(mixer_str, ctl_len, "%s %s", dev_name, "Channel Map"); |
| |
| ctl = mixer_get_ctl_by_name(mixer, mixer_str); |
| if (!ctl) { |
| AGM_LOGE("Invalid mixer control: %s\n", mixer_str); |
| ret = -ENOENT; |
| goto err_get_ctl; |
| } |
| |
| ret = mixer_ctl_get_array(ctl, payload, 16 * sizeof(uint32_t)); |
| if (ret < 0) { |
| AGM_LOGE("Failed to mixer_ctl_get_array\n"); |
| goto err_get_ctl; |
| } |
| *chmap = payload; |
| goto done; |
| |
| err_get_ctl: |
| free(payload); |
| done: |
| free(mixer_str); |
| return ret; |
| } |
| #endif |
| |
| int device_get_start_refcnt(struct device_obj *dev_obj) |
| { |
| if (dev_obj == NULL) { |
| AGM_LOGE("Invalid device object\n"); |
| return 0; |
| } |
| |
| if (dev_obj->group_data) |
| return dev_obj->group_data->refcnt.start; |
| else |
| return dev_obj->refcnt.start; |
| |
| } |
| |
| static struct device_group_data* device_get_group_data_by_name(char *dev_name) |
| { |
| struct device_group_data *grp_data = NULL; |
| char group_name[MAX_DEV_NAME_LEN]; |
| char *ptr = NULL; |
| int pos = 0; |
| struct listnode *grp_node, *temp; |
| |
| memset(group_name, 0, MAX_DEV_NAME_LEN); |
| |
| ptr = strstr(dev_name, "-VIRT"); |
| pos = ptr - dev_name + 1; |
| strlcpy(group_name, dev_name, pos); |
| |
| list_for_each_safe(grp_node, temp, &device_group_data_list) { |
| grp_data = node_to_item(grp_node, struct device_group_data, list_node); |
| if (!strncmp(group_name, grp_data->name, MAX_DEV_NAME_LEN)) { |
| grp_data->has_multiple_dai_link = true; |
| goto done; |
| } |
| } |
| |
| grp_data = calloc(1, sizeof(struct device_group_data)); |
| if (grp_data == NULL) { |
| AGM_LOGE("memory allocation for group_data failed\n"); |
| return NULL; |
| } |
| |
| strlcpy(grp_data->name, group_name, pos); |
| list_add_tail(&device_group_data_list, &grp_data->list_node); |
| num_group_devices++; |
| |
| done: |
| return grp_data; |
| } |
| |
| int parse_snd_card() |
| { |
| char buffer[MAX_BUF_SIZE]; |
| unsigned int count = 0, i = 0; |
| FILE *fp; |
| int ret = 0; |
| struct listnode *dev_node, *temp; |
| struct device_obj *dev_obj = NULL; |
| |
| fp = fopen(PCM_DEVICE_FILE, "r"); |
| if (!fp) { |
| AGM_LOGE("ERROR. %s file open failed\n", |
| PCM_DEVICE_FILE); |
| return -ENODEV; |
| } |
| |
| list_init(&device_list); |
| list_init(&device_group_data_list); |
| num_group_devices = 0; |
| while (fgets(buffer, MAX_BUF_SIZE - 1, fp) != NULL) |
| { |
| dev_obj = calloc(1, sizeof(struct device_obj)); |
| |
| if (!dev_obj) { |
| AGM_LOGE("failed to allocate device_obj mem\n"); |
| ret = -ENOMEM; |
| goto free_device; |
| } |
| |
| AGM_LOGV("buffer: %s\n", buffer); |
| /* For Non-DPCM Dai-links, it is in the format of: |
| * <card_num>-<pcm_device_id>: <pcm->idname> : <pcm->name> : |
| <playback/capture> 1 |
| * Here, pcm->idname is in the form of "<dai_link->stream_name> |
| <codec_name>-<num_codecs>" |
| */ |
| sscanf(buffer, "%02u-%02u: %80s", &dev_obj->card_id, |
| &dev_obj->pcm_id, dev_obj->name); |
| AGM_LOGD("%d:%d:%s\n", dev_obj->card_id, dev_obj->pcm_id, dev_obj->name); |
| |
| /* populate the hw_ep_info for all the available pcm-id's*/ |
| ret = populate_device_hw_ep_info(dev_obj); |
| if (ret) { |
| AGM_LOGE("hw_ep_info parsing failed %s\n", |
| dev_obj->name); |
| free(dev_obj); |
| ret = 0; |
| continue; |
| } |
| |
| pthread_mutex_init(&dev_obj->lock, (const pthread_mutexattr_t *) NULL); |
| list_add_tail(&device_list, &dev_obj->list_node); |
| count++; |
| if (dev_obj->num_virtual_child) { |
| dev_obj->group_data = device_get_group_data_by_name(dev_obj->name); |
| |
| /* Enumerate virtual backends */ |
| for (int i = 0; i < dev_obj->num_virtual_child; i++) { |
| struct device_obj *child_dev_obj = populate_virtual_device_hw_ep_info(dev_obj, i); |
| if (child_dev_obj) { |
| list_add_tail(&device_list, &child_dev_obj->list_node); |
| count++; |
| } |
| child_dev_obj = NULL; |
| } |
| } |
| |
| dev_obj = NULL; |
| } |
| /* |
| * count 0 indicates that expected sound card was not registered |
| * inform the upper layers to try again after sometime. |
| */ |
| if (count == 0) { |
| ret = -EAGAIN; |
| goto free_device; |
| } |
| |
| num_audio_intfs = count; |
| goto close_file; |
| |
| free_device: |
| list_for_each_safe(dev_node, temp, &device_list) { |
| dev_obj = node_to_item(dev_node, struct device_obj, list_node); |
| list_remove(dev_node); |
| free(dev_obj); |
| dev_obj = NULL; |
| } |
| |
| list_remove(&device_group_data_list); |
| list_remove(&device_list); |
| close_file: |
| fclose(fp); |
| return ret; |
| } |
| |
| static int wait_for_snd_card_to_online() |
| { |
| int ret = 0; |
| uint32_t retries = MAX_RETRY; |
| int fd = -1; |
| char buf[2]; |
| snd_card_status_t card_status = SND_CARD_STATUS_NONE; |
| |
| /* wait here till snd card is registered */ |
| /* maximum wait period = (MAX_RETRY * RETRY_INTERVAL_US) micro-seconds */ |
| do { |
| if ((fd = open(SNDCARD_PATH, O_RDWR)) < 0) { |
| AGM_LOGE(LOG_TAG, "Failed to open snd sysfs node, will retry for %d times ...", (retries - 1)); |
| } else { |
| memset(buf , 0 ,sizeof(buf)); |
| lseek(fd,0L,SEEK_SET); |
| read(fd, buf, 1); |
| close(fd); |
| fd = -1; |
| |
| buf[sizeof(buf) - 1] = '\0'; |
| card_status = SND_CARD_STATUS_NONE; |
| sscanf(buf , "%d", &card_status); |
| |
| if (card_status == SND_CARD_STATUS_ONLINE) { |
| AGM_LOGV(LOG_TAG, "snd sysfs node open successful"); |
| break; |
| } |
| } |
| retries--; |
| sleep(RETRY_INTERVAL); |
| } while ( retries > 0); |
| |
| if (0 == retries) { |
| AGM_LOGE(LOG_TAG, "Failed to open snd sysfs node, exiting ... "); |
| ret = -EIO; |
| } |
| |
| return ret; |
| } |
| |
| int device_init() |
| { |
| int ret = 0; |
| |
| ret = wait_for_snd_card_to_online(); |
| if (ret) { |
| AGM_LOGE("Not found any SND card online\n"); |
| return ret; |
| } |
| |
| ret = parse_snd_card(); |
| if (ret) |
| AGM_LOGE("no valid snd device found\n"); |
| |
| return ret; |
| } |
| |
| void device_deinit() |
| { |
| unsigned int list_count = 0; |
| struct device_obj *dev_obj = NULL; |
| struct device_group_data *grp_data = NULL; |
| struct listnode *dev_node, *grp_node, *temp; |
| |
| AGM_LOGE("device deinit called\n"); |
| list_for_each_safe(dev_node, temp, &device_list) { |
| dev_obj = node_to_item(dev_node, struct device_obj, list_node); |
| list_remove(dev_node); |
| |
| metadata_free(&dev_obj->metadata); |
| |
| if (dev_obj->params) |
| free(dev_obj->params); |
| |
| free(dev_obj); |
| dev_obj = NULL; |
| } |
| |
| list_for_each_safe(grp_node, temp, &device_group_data_list) { |
| grp_data = node_to_item(grp_node, struct device_group_data, list_node); |
| list_remove(grp_node); |
| free(grp_data); |
| grp_data = NULL; |
| } |
| |
| list_remove(&device_group_data_list); |
| list_remove(&device_list); |
| if (sysfs_fd >= 0) |
| close(sysfs_fd); |
| sysfs_fd = -1; |
| |
| #ifdef DEVICE_USES_ALSALIB |
| if (mixer) |
| snd_ctl_close(mixer); |
| #else |
| if (mixer) |
| mixer_close(mixer); |
| #endif |
| } |
| |
| static void split_snd_card_name(const char * in_snd_card_name, char* file_path_extn, |
| char* file_path_extn_wo_variant) |
| { |
| /* Sound card name follows below mentioned convention: |
| <target name>-<form factor>-<variant>-snd-card. |
| */ |
| char *snd_card_name = NULL; |
| char *tmp = NULL; |
| char *card_sub_str = NULL; |
| |
| snd_card_name = strdup(in_snd_card_name); |
| if (snd_card_name == NULL) { |
| goto done; |
| } |
| |
| card_sub_str = strtok_r(snd_card_name, "-", &tmp); |
| if (card_sub_str == NULL) { |
| AGM_LOGE("called on invalid snd card name(%s)", in_snd_card_name); |
| goto done; |
| } |
| strlcat(file_path_extn, card_sub_str, FILE_PATH_EXTN_MAX_SIZE); |
| |
| while ((card_sub_str = strtok_r(NULL, "-", &tmp))) { |
| if (strncmp(card_sub_str, "snd", strlen("snd"))) { |
| strlcpy(file_path_extn_wo_variant, file_path_extn, FILE_PATH_EXTN_MAX_SIZE); |
| strlcat(file_path_extn, "_", FILE_PATH_EXTN_MAX_SIZE); |
| strlcat(file_path_extn, card_sub_str, FILE_PATH_EXTN_MAX_SIZE); |
| } |
| else |
| break; |
| } |
| |
| done: |
| if (snd_card_name) |
| free(snd_card_name); |
| } |
| |
| static bool check_and_update_snd_card(char *in_snd_card_str) |
| { |
| char *str = NULL, *tmp = NULL; |
| int card = 0, card_id = -1; |
| bool is_updated = false; |
| char *snd_card_str = strdup(in_snd_card_str); |
| |
| if (snd_card_str == NULL) { |
| return is_updated; |
| } |
| |
| card = device_get_snd_card_id(); |
| if (card < 0) { |
| goto done; |
| } |
| |
| str = strtok_r(snd_card_str, "[:] ", &tmp); |
| if (str == NULL) { |
| goto done; |
| } |
| card_id = atoi(str); |
| |
| str = strtok_r(NULL, "[:] ", &tmp); |
| if (str == NULL) { |
| goto done; |
| } |
| |
| if (card_id == card) { |
| is_updated = true; |
| } |
| |
| done: |
| if (snd_card_str) |
| free(snd_card_str); |
| return is_updated; |
| } |
| |
| static bool update_snd_card_info(char snd_card_name[]) |
| { |
| bool is_updated = false; |
| char line1[BUF_SIZE] = {0}, line2[BUF_SIZE] = {0}; |
| FILE *file = NULL; |
| int len = 0, retries = MAX_RETRY; |
| char *card_name = NULL, *tmp = NULL; |
| |
| if (access(SND_CARD_DEVICE_FILE, F_OK) != -1) { |
| file = fopen(SND_CARD_DEVICE_FILE, "r"); |
| if (file == NULL) { |
| AGM_LOGE("open %s: failed\n", SND_CARD_DEVICE_FILE); |
| goto done; |
| } |
| } else { |
| AGM_LOGE("Unable to access %s\n", SND_CARD_DEVICE_FILE); |
| goto done; |
| } |
| |
| /* Look for only default codec sound card */ |
| /* Ignore USB sound card if detected */ |
| /* Example of data read from /proc/asound/cards: */ |
| /* card-id [dummyidpsndcard]: dummy-idp-variant-snd- - dummy-idp-variant-snd-card */ |
| /* dummy-idp-variant-snd-card */ |
| do { |
| if (!fgets(line1, BUF_SIZE - 1, file)) { |
| break; |
| } |
| len = strnlen(line1, BUF_SIZE); |
| line1[len - 1] = '\0'; |
| |
| if (!fgets(line2, BUF_SIZE - 1, file)) { |
| break; |
| } |
| len = strnlen(line2, BUF_SIZE); |
| line2[len - 1] = '\0'; |
| |
| if (check_and_update_snd_card(line1)) { |
| card_name = strtok_r(line2, "[:] ", &tmp); |
| if (card_name != NULL) { |
| strlcpy(snd_card_name, card_name, FILE_PATH_EXTN_MAX_SIZE); |
| is_updated = true; |
| } |
| } |
| } while(!is_updated && --retries); |
| |
| done: |
| if (file) |
| fclose(file); |
| return is_updated; |
| } |
| |
| bool get_file_path_extn(char* file_path_extn, char* file_path_extn_wo_variant) |
| { |
| int snd_card_found = false, retry = 0; |
| char snd_card_name[FILE_PATH_EXTN_MAX_SIZE]; |
| |
| do { |
| snd_card_found = update_snd_card_info(snd_card_name); |
| |
| if (snd_card_found) { |
| split_snd_card_name(snd_card_name, file_path_extn, file_path_extn_wo_variant); |
| AGM_LOGV("Found Codec sound card"); |
| break; |
| } else { |
| AGM_LOGI("Sound card not found, retry %d", retry++); |
| sleep(1); |
| } |
| } while (!snd_card_found && retry <= MAX_RETRY_CNT); |
| |
| return snd_card_found; |
| } |