/* Copyright (c) 2011, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include "audmgr_new.h" #define VOICELOOPBACK_PROG 0x300000B8 #define VOICELOOP_VERS 0x00010001 #define VOICELOOPBACK_START_PROC 2 #define VOICELOOPBACK_STOP_PROC 3 #define RPC_TYPE_REQUEST 0 #define RPC_TYPE_REPLY 1 #define RPC_STATUS_FAILURE 0 #define RPC_STATUS_SUCCESS 1 #define RPC_STATUS_REJECT 1 #define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) #define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) #define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) #define MAX_LEN 32 struct audio { struct msm_rpc_endpoint *rpc_endpt; uint32_t rpc_prog; uint32_t rpc_ver; uint32_t rpc_status; struct audmgr audmgr; struct dentry *dentry; struct mutex lock; struct task_struct *task; wait_queue_head_t wait; int enabled; int thread_exit; }; static struct audio the_audio; static int audio_voice_loopback_thread(void *data) { struct audio *audio = data; struct rpc_request_hdr *rpc_hdr = NULL; int rpc_hdr_len; MM_DBG("\n"); while (!kthread_should_stop()) { if (rpc_hdr != NULL) { kfree(rpc_hdr); rpc_hdr = NULL; } if (audio->thread_exit) break; rpc_hdr_len = msm_rpc_read(audio->rpc_endpt, (void **) &rpc_hdr, -1, -1); if (rpc_hdr_len < 0) { MM_ERR("RPC read failed %d\n", rpc_hdr_len); break; } else if (rpc_hdr_len < RPC_COMMON_HDR_SZ) { continue; } else { uint32_t rpc_type = be32_to_cpu(rpc_hdr->type); if (rpc_type == RPC_TYPE_REPLY) { struct rpc_reply_hdr *rpc_reply = (void *) rpc_hdr; uint32_t reply_status; reply_status = be32_to_cpu(rpc_reply->reply_stat); if (reply_status == RPC_ACCEPTSTAT_SUCCESS) audio->rpc_status = \ RPC_STATUS_SUCCESS; else { audio->rpc_status = \ RPC_STATUS_REJECT; MM_ERR("RPC reply status denied\n"); } wake_up(&audio->wait); } else { MM_ERR("Unexpected RPC type %d\n", rpc_type); } } } kfree(rpc_hdr); rpc_hdr = NULL; MM_DBG("Audio Voice Looopback thread stopped\n"); return 0; } static int audio_voice_loopback_start(struct audio *audio) { int rc = 0; struct audmgr_config cfg; struct rpc_request_hdr rpc_hdr; MM_DBG("\n"); cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; cfg.def_method = RPC_AUD_DEF_METHOD_VOICE; cfg.codec = RPC_AUD_DEF_CODEC_VOC_CDMA; cfg.snd_method = RPC_SND_METHOD_VOICE; rc = audmgr_enable(&audio->audmgr, &cfg); if (rc < 0) { MM_ERR("audmgr open failed, freeing instance\n"); rc = -EINVAL; goto done; } memset(&rpc_hdr, 0, sizeof(rpc_hdr)); msm_rpc_setup_req(&rpc_hdr, audio->rpc_prog, audio->rpc_ver, VOICELOOPBACK_START_PROC); audio->rpc_status = RPC_STATUS_FAILURE; rc = msm_rpc_write(audio->rpc_endpt, &rpc_hdr, sizeof(rpc_hdr)); if (rc >= 0) { rc = wait_event_timeout(audio->wait, (audio->rpc_status != RPC_STATUS_FAILURE), 1 * HZ); if (rc > 0) { if (audio->rpc_status != RPC_STATUS_SUCCESS) { MM_ERR("Start loopback failed %d\n", rc); rc = -EBUSY; } else { rc = 0; } } else { MM_ERR("Wait event for acquire failed %d\n", rc); rc = -EBUSY; } } else { audmgr_disable(&audio->audmgr); MM_ERR("RPC write for start loopback failed %d\n", rc); rc = -EBUSY; } done: return rc; } static int audio_voice_loopback_stop(struct audio *audio) { int rc = 0; struct rpc_request_hdr rpc_hdr; MM_DBG("\n"); memset(&rpc_hdr, 0, sizeof(rpc_hdr)); msm_rpc_setup_req(&rpc_hdr, audio->rpc_prog, audio->rpc_ver, VOICELOOPBACK_STOP_PROC); audio->rpc_status = RPC_STATUS_FAILURE; audio->thread_exit = 1; rc = msm_rpc_write(audio->rpc_endpt, &rpc_hdr, sizeof(rpc_hdr)); if (rc >= 0) { rc = wait_event_timeout(audio->wait, (audio->rpc_status != RPC_STATUS_FAILURE), 1 * HZ); if (rc > 0) { MM_DBG("Wait event for release succeeded\n"); rc = 0; } else { MM_ERR("Wait event for release failed %d\n", rc); } } else { MM_ERR("RPC write for release failed %d\n", rc); } audmgr_disable(&audio->audmgr); return rc; } static int audio_voice_loopback_open(struct audio *audio_info) { int rc = 0; MM_DBG("\n"); rc = audmgr_open(&audio_info->audmgr); if (rc) { MM_ERR("audmgr open failed, freeing instance\n"); rc = -EINVAL; goto done; } audio_info->rpc_endpt = msm_rpc_connect_compatible(VOICELOOPBACK_PROG, VOICELOOP_VERS, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(audio_info->rpc_endpt)) { MM_ERR("VOICE LOOPBACK RPC connect\ failed ver 0x%x\n", VOICELOOP_VERS); rc = PTR_ERR(audio_info->rpc_endpt); audio_info->rpc_endpt = NULL; rc = -EINVAL; } else { MM_DBG("VOICE LOOPBACK connect succeeded ver 0x%x\n", VOICELOOP_VERS); audio_info->thread_exit = 0; audio_info->task = kthread_run(audio_voice_loopback_thread, audio_info, "audio_voice_loopback"); if (IS_ERR(audio_info->task)) { MM_ERR("voice loopback thread create failed\n"); rc = PTR_ERR(audio_info->task); audio_info->task = NULL; msm_rpc_close(audio_info->rpc_endpt); audio_info->rpc_endpt = NULL; rc = -EINVAL; } audio_info->rpc_prog = VOICELOOPBACK_PROG; audio_info->rpc_ver = VOICELOOP_VERS; } done: return rc; } static int audio_voice_loopback_close(struct audio *audio_info) { MM_DBG("\n"); msm_rpc_close(audio_info->rpc_endpt); audio_info->rpc_endpt = NULL; audmgr_close(&audio_info->audmgr); audio_info->task = NULL; return 0; } static ssize_t audio_voice_loopback_debug_write(struct file *file, const char __user *buf, size_t cnt, loff_t *ppos) { char lbuf[MAX_LEN]; int rc = 0; if (cnt > (MAX_LEN - 1)) return -EINVAL; memset(&lbuf[0], 0, sizeof(lbuf)); rc = copy_from_user(lbuf, buf, cnt); if (rc) { MM_ERR("Unable to copy data from user space\n"); return -EFAULT; } lbuf[cnt] = '\0'; if (!strncmp(&lbuf[0], "1", cnt-1)) { mutex_lock(&the_audio.lock); if (!the_audio.enabled) { rc = audio_voice_loopback_open(&the_audio); if (!rc) { rc = audio_voice_loopback_start(&the_audio); if (rc < 0) { the_audio.enabled = 0; audio_voice_loopback_close(&the_audio); } else { the_audio.enabled = 1; } } } mutex_unlock(&the_audio.lock); } else if (!strncmp(lbuf, "0", cnt-1)) { mutex_lock(&the_audio.lock); if (the_audio.enabled) { audio_voice_loopback_stop(&the_audio); audio_voice_loopback_close(&the_audio); the_audio.enabled = 0; } mutex_unlock(&the_audio.lock); } else { rc = -EINVAL; } if (rc == 0) { rc = cnt; } else { MM_INFO("rc = %d\n", rc); MM_INFO("\nWrong command: Use =>\n"); MM_INFO("-------------------------\n"); MM_INFO("To Start Loopback:: echo \"1\">/sys/kernel/debug/\ voice_loopback\n"); MM_INFO("To Stop Loopback:: echo \"0\">/sys/kernel/debug/\ voice_loopback\n"); MM_INFO("------------------------\n"); } return rc; } static ssize_t audio_voice_loopback_debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; MM_DBG("Audio Voiceloop debugfs opened\n"); return 0; } static const struct file_operations voice_loopback_debug_fops = { .write = audio_voice_loopback_debug_write, .open = audio_voice_loopback_debug_open, }; static int __init audio_init(void) { int rc = 0; memset(&the_audio, 0, sizeof(the_audio)); mutex_init(&the_audio.lock); init_waitqueue_head(&the_audio.wait); the_audio.dentry = debugfs_create_file("voice_loopback", S_IFREG | S_IRUGO, NULL, NULL, &voice_loopback_debug_fops); if (IS_ERR(the_audio.dentry)) MM_ERR("debugfs_create_file failed\n"); return rc; } late_initcall(audio_init);