Duy Truong | 790f06d | 2013-02-13 16:38:12 -0800 | [diff] [blame] | 1 | /* Copyright (c) 2011, The Linux Foundation. All rights reserved. |
Manish Dewangan | 881ef4c | 2011-09-14 11:58:57 +0530 | [diff] [blame] | 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | #include <linux/debugfs.h> |
| 15 | #include <linux/delay.h> |
| 16 | #include <linux/fs.h> |
| 17 | #include <linux/kthread.h> |
| 18 | #include <linux/miscdevice.h> |
| 19 | #include <linux/module.h> |
| 20 | #include <linux/wakelock.h> |
| 21 | #include <linux/slab.h> |
| 22 | #include <linux/uaccess.h> |
| 23 | |
| 24 | #include <mach/debug_mm.h> |
| 25 | #include <mach/msm_rpcrouter.h> |
| 26 | |
| 27 | #include "audmgr_new.h" |
| 28 | |
| 29 | #define VOICELOOPBACK_PROG 0x300000B8 |
| 30 | #define VOICELOOP_VERS 0x00010001 |
| 31 | |
| 32 | #define VOICELOOPBACK_START_PROC 2 |
| 33 | #define VOICELOOPBACK_STOP_PROC 3 |
| 34 | |
| 35 | #define RPC_TYPE_REQUEST 0 |
| 36 | #define RPC_TYPE_REPLY 1 |
| 37 | |
| 38 | #define RPC_STATUS_FAILURE 0 |
| 39 | #define RPC_STATUS_SUCCESS 1 |
| 40 | #define RPC_STATUS_REJECT 1 |
| 41 | |
| 42 | #define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) |
| 43 | #define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) |
| 44 | #define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) |
| 45 | |
| 46 | #define MAX_LEN 32 |
| 47 | |
| 48 | struct audio { |
| 49 | struct msm_rpc_endpoint *rpc_endpt; |
| 50 | uint32_t rpc_prog; |
| 51 | uint32_t rpc_ver; |
| 52 | uint32_t rpc_status; |
| 53 | struct audmgr audmgr; |
| 54 | |
| 55 | struct dentry *dentry; |
| 56 | |
| 57 | struct mutex lock; |
| 58 | |
| 59 | struct task_struct *task; |
| 60 | |
| 61 | wait_queue_head_t wait; |
| 62 | int enabled; |
| 63 | int thread_exit; |
| 64 | }; |
| 65 | |
| 66 | static struct audio the_audio; |
| 67 | |
| 68 | static int audio_voice_loopback_thread(void *data) |
| 69 | { |
| 70 | struct audio *audio = data; |
| 71 | struct rpc_request_hdr *rpc_hdr = NULL; |
| 72 | int rpc_hdr_len; |
| 73 | |
| 74 | MM_DBG("\n"); |
| 75 | |
| 76 | while (!kthread_should_stop()) { |
| 77 | if (rpc_hdr != NULL) { |
| 78 | kfree(rpc_hdr); |
| 79 | rpc_hdr = NULL; |
| 80 | } |
| 81 | |
| 82 | if (audio->thread_exit) |
| 83 | break; |
| 84 | |
| 85 | rpc_hdr_len = msm_rpc_read(audio->rpc_endpt, |
| 86 | (void **) &rpc_hdr, |
| 87 | -1, |
| 88 | -1); |
| 89 | if (rpc_hdr_len < 0) { |
| 90 | MM_ERR("RPC read failed %d\n", rpc_hdr_len); |
| 91 | break; |
| 92 | } else if (rpc_hdr_len < RPC_COMMON_HDR_SZ) { |
| 93 | continue; |
| 94 | } else { |
| 95 | uint32_t rpc_type = be32_to_cpu(rpc_hdr->type); |
| 96 | if (rpc_type == RPC_TYPE_REPLY) { |
| 97 | struct rpc_reply_hdr *rpc_reply = |
| 98 | (void *) rpc_hdr; |
| 99 | uint32_t reply_status; |
| 100 | |
| 101 | reply_status = |
| 102 | be32_to_cpu(rpc_reply->reply_stat); |
| 103 | |
| 104 | if (reply_status == RPC_ACCEPTSTAT_SUCCESS) |
| 105 | audio->rpc_status = \ |
| 106 | RPC_STATUS_SUCCESS; |
| 107 | else { |
| 108 | audio->rpc_status = \ |
| 109 | RPC_STATUS_REJECT; |
| 110 | MM_ERR("RPC reply status denied\n"); |
| 111 | } |
| 112 | wake_up(&audio->wait); |
| 113 | } else { |
| 114 | MM_ERR("Unexpected RPC type %d\n", rpc_type); |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | kfree(rpc_hdr); |
| 119 | rpc_hdr = NULL; |
| 120 | |
| 121 | MM_DBG("Audio Voice Looopback thread stopped\n"); |
| 122 | |
| 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | static int audio_voice_loopback_start(struct audio *audio) |
| 127 | { |
| 128 | int rc = 0; |
| 129 | struct audmgr_config cfg; |
| 130 | struct rpc_request_hdr rpc_hdr; |
| 131 | |
| 132 | MM_DBG("\n"); |
| 133 | |
| 134 | cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; |
| 135 | cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; |
| 136 | cfg.def_method = RPC_AUD_DEF_METHOD_VOICE; |
| 137 | cfg.codec = RPC_AUD_DEF_CODEC_VOC_CDMA; |
| 138 | cfg.snd_method = RPC_SND_METHOD_VOICE; |
| 139 | rc = audmgr_enable(&audio->audmgr, &cfg); |
| 140 | if (rc < 0) { |
| 141 | MM_ERR("audmgr open failed, freeing instance\n"); |
| 142 | rc = -EINVAL; |
| 143 | goto done; |
| 144 | } |
| 145 | |
| 146 | memset(&rpc_hdr, 0, sizeof(rpc_hdr)); |
| 147 | |
| 148 | msm_rpc_setup_req(&rpc_hdr, |
| 149 | audio->rpc_prog, |
| 150 | audio->rpc_ver, |
| 151 | VOICELOOPBACK_START_PROC); |
| 152 | |
| 153 | audio->rpc_status = RPC_STATUS_FAILURE; |
| 154 | rc = msm_rpc_write(audio->rpc_endpt, |
| 155 | &rpc_hdr, |
| 156 | sizeof(rpc_hdr)); |
| 157 | if (rc >= 0) { |
| 158 | rc = wait_event_timeout(audio->wait, |
| 159 | (audio->rpc_status != RPC_STATUS_FAILURE), |
| 160 | 1 * HZ); |
| 161 | if (rc > 0) { |
| 162 | if (audio->rpc_status != RPC_STATUS_SUCCESS) { |
| 163 | MM_ERR("Start loopback failed %d\n", rc); |
| 164 | rc = -EBUSY; |
| 165 | } else { |
| 166 | rc = 0; |
| 167 | } |
| 168 | } else { |
| 169 | MM_ERR("Wait event for acquire failed %d\n", rc); |
| 170 | rc = -EBUSY; |
| 171 | } |
| 172 | } else { |
| 173 | audmgr_disable(&audio->audmgr); |
| 174 | MM_ERR("RPC write for start loopback failed %d\n", rc); |
| 175 | rc = -EBUSY; |
| 176 | } |
| 177 | done: |
| 178 | return rc; |
| 179 | } |
| 180 | |
| 181 | static int audio_voice_loopback_stop(struct audio *audio) |
| 182 | { |
| 183 | int rc = 0; |
| 184 | struct rpc_request_hdr rpc_hdr; |
| 185 | |
| 186 | MM_DBG("\n"); |
| 187 | |
| 188 | memset(&rpc_hdr, 0, sizeof(rpc_hdr)); |
| 189 | |
| 190 | msm_rpc_setup_req(&rpc_hdr, |
| 191 | audio->rpc_prog, |
| 192 | audio->rpc_ver, |
| 193 | VOICELOOPBACK_STOP_PROC); |
| 194 | |
| 195 | audio->rpc_status = RPC_STATUS_FAILURE; |
| 196 | audio->thread_exit = 1; |
| 197 | rc = msm_rpc_write(audio->rpc_endpt, |
| 198 | &rpc_hdr, |
| 199 | sizeof(rpc_hdr)); |
| 200 | if (rc >= 0) { |
| 201 | |
| 202 | rc = wait_event_timeout(audio->wait, |
| 203 | (audio->rpc_status != RPC_STATUS_FAILURE), |
| 204 | 1 * HZ); |
| 205 | if (rc > 0) { |
| 206 | MM_DBG("Wait event for release succeeded\n"); |
| 207 | rc = 0; |
| 208 | } else { |
| 209 | MM_ERR("Wait event for release failed %d\n", rc); |
| 210 | } |
| 211 | } else { |
| 212 | MM_ERR("RPC write for release failed %d\n", rc); |
| 213 | } |
| 214 | |
| 215 | audmgr_disable(&audio->audmgr); |
| 216 | |
| 217 | return rc; |
| 218 | } |
| 219 | |
| 220 | static int audio_voice_loopback_open(struct audio *audio_info) |
| 221 | { |
| 222 | int rc = 0; |
| 223 | |
| 224 | MM_DBG("\n"); |
| 225 | |
| 226 | rc = audmgr_open(&audio_info->audmgr); |
| 227 | if (rc) { |
| 228 | MM_ERR("audmgr open failed, freeing instance\n"); |
| 229 | rc = -EINVAL; |
| 230 | goto done; |
| 231 | } |
| 232 | |
| 233 | audio_info->rpc_endpt = msm_rpc_connect_compatible(VOICELOOPBACK_PROG, |
| 234 | VOICELOOP_VERS, |
| 235 | MSM_RPC_UNINTERRUPTIBLE); |
| 236 | if (IS_ERR(audio_info->rpc_endpt)) { |
| 237 | MM_ERR("VOICE LOOPBACK RPC connect\ |
| 238 | failed ver 0x%x\n", |
| 239 | VOICELOOP_VERS); |
| 240 | rc = PTR_ERR(audio_info->rpc_endpt); |
| 241 | audio_info->rpc_endpt = NULL; |
| 242 | rc = -EINVAL; |
| 243 | } else { |
| 244 | MM_DBG("VOICE LOOPBACK connect succeeded ver 0x%x\n", |
| 245 | VOICELOOP_VERS); |
| 246 | audio_info->thread_exit = 0; |
| 247 | audio_info->task = kthread_run(audio_voice_loopback_thread, |
| 248 | audio_info, |
| 249 | "audio_voice_loopback"); |
| 250 | if (IS_ERR(audio_info->task)) { |
| 251 | MM_ERR("voice loopback thread create failed\n"); |
| 252 | rc = PTR_ERR(audio_info->task); |
| 253 | audio_info->task = NULL; |
| 254 | msm_rpc_close(audio_info->rpc_endpt); |
| 255 | audio_info->rpc_endpt = NULL; |
| 256 | rc = -EINVAL; |
| 257 | } |
| 258 | audio_info->rpc_prog = VOICELOOPBACK_PROG; |
| 259 | audio_info->rpc_ver = VOICELOOP_VERS; |
| 260 | } |
| 261 | done: |
| 262 | return rc; |
| 263 | } |
| 264 | |
| 265 | static int audio_voice_loopback_close(struct audio *audio_info) |
| 266 | { |
| 267 | MM_DBG("\n"); |
| 268 | msm_rpc_close(audio_info->rpc_endpt); |
| 269 | audio_info->rpc_endpt = NULL; |
| 270 | audmgr_close(&audio_info->audmgr); |
| 271 | audio_info->task = NULL; |
| 272 | return 0; |
| 273 | } |
| 274 | |
| 275 | static ssize_t audio_voice_loopback_debug_write(struct file *file, |
| 276 | const char __user *buf, |
| 277 | size_t cnt, loff_t *ppos) |
| 278 | { |
| 279 | char lbuf[MAX_LEN]; |
| 280 | int rc = 0; |
| 281 | |
| 282 | if (cnt > (MAX_LEN - 1)) |
| 283 | return -EINVAL; |
| 284 | |
| 285 | memset(&lbuf[0], 0, sizeof(lbuf)); |
| 286 | |
| 287 | rc = copy_from_user(lbuf, buf, cnt); |
| 288 | if (rc) { |
| 289 | MM_ERR("Unable to copy data from user space\n"); |
| 290 | return -EFAULT; |
| 291 | } |
| 292 | |
| 293 | lbuf[cnt] = '\0'; |
| 294 | |
| 295 | if (!strncmp(&lbuf[0], "1", cnt-1)) { |
| 296 | mutex_lock(&the_audio.lock); |
| 297 | if (!the_audio.enabled) { |
| 298 | rc = audio_voice_loopback_open(&the_audio); |
| 299 | if (!rc) { |
| 300 | rc = audio_voice_loopback_start(&the_audio); |
| 301 | if (rc < 0) { |
| 302 | the_audio.enabled = 0; |
| 303 | audio_voice_loopback_close(&the_audio); |
| 304 | } else { |
| 305 | the_audio.enabled = 1; |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | mutex_unlock(&the_audio.lock); |
| 310 | } else if (!strncmp(lbuf, "0", cnt-1)) { |
| 311 | mutex_lock(&the_audio.lock); |
| 312 | if (the_audio.enabled) { |
| 313 | audio_voice_loopback_stop(&the_audio); |
| 314 | audio_voice_loopback_close(&the_audio); |
| 315 | the_audio.enabled = 0; |
| 316 | } |
| 317 | mutex_unlock(&the_audio.lock); |
| 318 | } else { |
| 319 | rc = -EINVAL; |
| 320 | } |
| 321 | |
| 322 | if (rc == 0) { |
| 323 | rc = cnt; |
| 324 | } else { |
| 325 | MM_INFO("rc = %d\n", rc); |
| 326 | MM_INFO("\nWrong command: Use =>\n"); |
| 327 | MM_INFO("-------------------------\n"); |
| 328 | MM_INFO("To Start Loopback:: echo \"1\">/sys/kernel/debug/\ |
| 329 | voice_loopback\n"); |
| 330 | MM_INFO("To Stop Loopback:: echo \"0\">/sys/kernel/debug/\ |
| 331 | voice_loopback\n"); |
| 332 | MM_INFO("------------------------\n"); |
| 333 | } |
| 334 | |
| 335 | return rc; |
| 336 | } |
| 337 | |
| 338 | static ssize_t audio_voice_loopback_debug_open(struct inode *inode, |
| 339 | struct file *file) |
| 340 | { |
| 341 | file->private_data = inode->i_private; |
| 342 | MM_DBG("Audio Voiceloop debugfs opened\n"); |
| 343 | return 0; |
| 344 | } |
| 345 | |
| 346 | static const struct file_operations voice_loopback_debug_fops = { |
| 347 | .write = audio_voice_loopback_debug_write, |
| 348 | .open = audio_voice_loopback_debug_open, |
| 349 | }; |
| 350 | |
| 351 | static int __init audio_init(void) |
| 352 | { |
| 353 | int rc = 0; |
| 354 | memset(&the_audio, 0, sizeof(the_audio)); |
| 355 | |
| 356 | mutex_init(&the_audio.lock); |
| 357 | |
| 358 | init_waitqueue_head(&the_audio.wait); |
| 359 | |
| 360 | the_audio.dentry = debugfs_create_file("voice_loopback", |
| 361 | S_IFREG | S_IRUGO, |
| 362 | NULL, |
| 363 | NULL, &voice_loopback_debug_fops); |
| 364 | if (IS_ERR(the_audio.dentry)) |
| 365 | MM_ERR("debugfs_create_file failed\n"); |
| 366 | |
| 367 | return rc; |
| 368 | } |
| 369 | late_initcall(audio_init); |