370 lines
8.2 KiB
C
370 lines
8.2 KiB
C
|
/* 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 <linux/debugfs.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/miscdevice.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/wakelock.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
|
||
|
#include <mach/debug_mm.h>
|
||
|
#include <mach/msm_rpcrouter.h>
|
||
|
|
||
|
#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);
|