Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 934297ae authored by Vicky Sehrawat's avatar Vicky Sehrawat
Browse files

msm: Add driver to communicate with voice services on DSP over APR



Add new driver in kernel to expose voice services on DSP over APR.
Userspace client voice modules in user space will use read/write
interface to send/receive messages between voice services on DSP
and APSS via APR.

CRs-fixed: 642690
Change-Id: Icc75b01e19b757d7da81f143fc10f09185e92d31
Signed-off-by: default avatarVicky Sehrawat <vickys@codeaurora.org>
parent 27a70795
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
obj-$(CONFIG_MSM_QDSP6_APRV2) += apr.o apr_v2.o apr_tal.o
obj-$(CONFIG_MSM_QDSP6_APRV3) += apr.o apr_v3.o apr_tal.o
obj-$(CONFIG_MSM_QDSP6_APRV2) += apr.o apr_v2.o apr_tal.o voice_svc.o
obj-$(CONFIG_MSM_QDSP6_APRV3) += apr.o apr_v3.o apr_tal.o voice_svc.o
obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += msm_audio_ion.o
obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o
+692 −0
Original line number Diff line number Diff line
/* Copyright (c) 2014, 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/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/qdsp6v2/apr_tal.h>
#include <linux/qdsp6v2/apr.h>
#include <sound/voice_svc.h>

#define MINOR_NUMBER 1
#define APR_MAX_RESPONSE 10
#define TIMEOUT_MS 1000

#define MAX(a, b) ((a) >= (b) ? (a) : (b))

struct voice_svc_device {
	struct cdev *cdev;
	struct device *dev;
	int major;
};

struct voice_svc_prvt {
	void *apr_q6_mvm;
	void *apr_q6_cvs;
	uint16_t response_count;
	struct list_head response_queue;
	wait_queue_head_t response_wait;
	spinlock_t response_lock;
};

struct apr_data {
	struct apr_hdr hdr;
	__u8 payload[0];
} __packed;

struct apr_response_list {
	struct list_head list;
	struct voice_svc_cmd_response resp;
};

static struct voice_svc_device *voice_svc_dev;
static struct class *voice_svc_class;
dev_t device_num;

static int32_t qdsp_apr_callback(struct apr_client_data *data, void *priv)
{
	struct voice_svc_prvt *prtd;
	struct apr_response_list *response_list;
	unsigned long spin_flags;

	if ((data == NULL) || (priv == NULL)) {
		pr_err("%s: data or priv is NULL\n", __func__);

		return -EINVAL;
	}

	prtd = (struct voice_svc_prvt *)priv;
	if (prtd == NULL) {
		pr_err("%s: private data is NULL\n", __func__);

		return -EINVAL;
	}

	pr_debug("%s: data->opcode %x\n", __func__,
		 data->opcode);

	if (data->opcode == RESET_EVENTS) {
		if (data->reset_proc == APR_DEST_QDSP6) {
			pr_debug("%s: Received ADSP reset event\n", __func__);

			if (prtd->apr_q6_mvm != NULL) {
				apr_reset(prtd->apr_q6_mvm);
				prtd->apr_q6_mvm = NULL;
			}

			if (prtd->apr_q6_cvs != NULL) {
				apr_reset(prtd->apr_q6_cvs);
				prtd->apr_q6_cvs = NULL;
			}
		} else if (data->reset_proc == APR_DEST_MODEM) {
			pr_debug("%s: Received Modem reset event\n", __func__);
		}
	}

	spin_lock_irqsave(&prtd->response_lock, spin_flags);

	if (prtd->response_count < APR_MAX_RESPONSE) {
		response_list = kmalloc(sizeof(struct apr_response_list) +
					data->payload_size, GFP_ATOMIC);
		if (response_list == NULL) {
			pr_err("%s: kmalloc failed\n", __func__);

			spin_unlock_irqrestore(&prtd->response_lock,
					       spin_flags);
			return -ENOMEM;
		}

		response_list->resp.src_port = data->src_port;

		/* Reverting the bit manipulation done in voice_svc_update_hdr
		 * to the src_port which is returned to us as dest_port.
		 */
		response_list->resp.dest_port = ((data->dest_port) >> 8);
		response_list->resp.token = data->token;
		response_list->resp.opcode = data->opcode;
		response_list->resp.payload_size = data->payload_size;
		if (data->payload != NULL && data->payload_size > 0) {
			memcpy(response_list->resp.payload, data->payload,
			       data->payload_size);
		}

		list_add_tail(&response_list->list, &prtd->response_queue);
		prtd->response_count++;
		spin_unlock_irqrestore(&prtd->response_lock, spin_flags);

		wake_up(&prtd->response_wait);
	} else {
		spin_unlock_irqrestore(&prtd->response_lock, spin_flags);
		pr_err("%s: Response dropped since the queue is full\n",
		       __func__);
	}

	return 0;
}

static void voice_svc_update_hdr(struct voice_svc_cmd_request *apr_req_data,
				 struct apr_data *aprdata)
{

	aprdata->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
				       APR_HDR_LEN(sizeof(struct apr_hdr)),
				       APR_PKT_VER);
	/* Bit manipulation is done on src_port so that a unique ID is sent.
	 * This manipulation can be used in the future where the same service
	 * is tried to open multiple times with the same src_port. At that
	 * time 0x0001 can be replaced with other values depending on the
	 * count.
	 */
	aprdata->hdr.src_port = ((apr_req_data->src_port) << 8 | 0x0001);
	aprdata->hdr.dest_port = apr_req_data->dest_port;
	aprdata->hdr.token = apr_req_data->token;
	aprdata->hdr.opcode = apr_req_data->opcode;
	aprdata->hdr.pkt_size  = APR_PKT_SIZE(APR_HDR_SIZE,
					apr_req_data->payload_size);
	memcpy(aprdata->payload, apr_req_data->payload,
	       apr_req_data->payload_size);
}

static int voice_svc_send_req(struct voice_svc_cmd_request *apr_request,
			      struct voice_svc_prvt *prtd)
{
	int ret = 0;
	void *apr_handle = NULL;
	struct apr_data *aprdata = NULL;
	uint32_t user_payload_size = 0;

	pr_debug("%s\n", __func__);

	if (apr_request == NULL) {
		pr_err("%s: apr_request is NULL\n", __func__);

		ret = -EINVAL;
		goto done;
	}

	user_payload_size = apr_request->payload_size;

	aprdata = kmalloc(sizeof(struct apr_data) + user_payload_size,
			  GFP_KERNEL);

	if (aprdata == NULL) {
		pr_err("%s: aprdata kmalloc failed.\n", __func__);

		ret = -ENOMEM;
		goto done;
	}

	voice_svc_update_hdr(apr_request, aprdata);

	if (!strcmp(apr_request->svc_name, VOICE_SVC_CVS_STR)) {
		apr_handle = prtd->apr_q6_cvs;
	} else if (!strcmp(apr_request->svc_name, VOICE_SVC_MVM_STR)) {
		apr_handle = prtd->apr_q6_mvm;
	} else {
		pr_err("%s: Invalid service %s\n", __func__,
			apr_request->svc_name);

		ret = -EINVAL;
		goto done;
	}

	ret = apr_send_pkt(apr_handle, (uint32_t *)aprdata);

	if (ret < 0) {
		pr_err("%s: Fail in sending request %d\n",
			__func__, ret);
		ret = -EINVAL;
	} else {
		pr_debug("%s: apr packet sent successfully %d\n",
			 __func__, ret);
		ret = 0;
	}

done:
	kfree(aprdata);
	return ret;
}
static int voice_svc_reg(char *svc, uint32_t src_port,
			 struct voice_svc_prvt *prtd, void **handle)
{
	int ret = 0;

	pr_debug("%s\n", __func__);

	if (handle == NULL) {
		pr_err("%s: handle is NULL\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	if (*handle != NULL) {
		pr_err("%s: svc handle not NULL\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	*handle = apr_register("ADSP",
			       svc, qdsp_apr_callback,
			       ((src_port) << 8 | 0x0001),
			       prtd);

	if (*handle == NULL) {
		pr_err("%s: Unable to register %s\n",
		       __func__, svc);

		ret = -EFAULT;
		goto done;
	}
	pr_debug("%s: Register %s successful\n",
		__func__, svc);
done:
	return ret;
}

static int voice_svc_dereg(char *svc, void **handle)
{
	int ret = 0;

	pr_debug("%s\n", __func__);

	if (handle == NULL) {
		pr_err("%s: handle is NULL\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	if (*handle == NULL) {
		pr_err("%s: svc handle is NULL\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	ret = apr_deregister(*handle);
	if (ret) {
		pr_err("%s: Unable to deregister service %s; error: %d\n",
		       __func__, svc, ret);

		goto done;
	}
	*handle = NULL;
	pr_debug("%s: deregister %s successful\n", __func__, svc);

done:
	return ret;
}

static int process_reg_cmd(struct voice_svc_register *apr_reg_svc,
			   struct voice_svc_prvt *prtd)
{
	int ret = 0;
	char *svc = NULL;
	void **handle = NULL;

	pr_debug("%s\n", __func__);

	if (!strcmp(apr_reg_svc->svc_name, VOICE_SVC_MVM_STR)) {
		svc = VOICE_SVC_MVM_STR;
		handle = &prtd->apr_q6_mvm;
	} else if (!strcmp(apr_reg_svc->svc_name, VOICE_SVC_CVS_STR)) {
		svc = VOICE_SVC_CVS_STR;
		handle = &prtd->apr_q6_cvs;
	} else {
		pr_err("%s: Invalid Service: %s\n", __func__,
		       apr_reg_svc->svc_name);
		ret = -EINVAL;
		goto done;
	}

	if (apr_reg_svc->reg_flag) {
		ret = voice_svc_reg(svc, apr_reg_svc->src_port, prtd,
				    handle);
	} else if (!apr_reg_svc->reg_flag) {
		ret = voice_svc_dereg(svc, handle);
	}

done:
	return ret;
}

static ssize_t voice_svc_write(struct file *file, const char __user *buf,
			       size_t count, loff_t *ppos)
{
	int ret = 0;
	struct voice_svc_prvt *prtd;
	struct voice_svc_write_msg *data = NULL;
	uint32_t cmd;

	pr_debug("%s\n", __func__);

	data = kmalloc(count, GFP_KERNEL);

	if (data == NULL) {
		pr_err("%s: data kmalloc failed.\n", __func__);

		ret = -ENOMEM;
		goto done;
	}

	ret = copy_from_user(data, buf, count);
	if (ret) {
		pr_err("%s: copy_from_user failed %d\n", __func__, ret);

		ret = -EPERM;
		goto done;
	}

	cmd = data->msg_type;
	prtd = (struct voice_svc_prvt *)file->private_data;
	if (prtd == NULL) {
		pr_err("%s: prtd is NULL\n", __func__);

		ret = -EINVAL;
		goto done;
	}

	switch (cmd) {
	case MSG_REGISTER:
		ret = process_reg_cmd(
			(struct voice_svc_register *)data->payload, prtd);
		if (!ret)
			ret = count;

		break;
	case MSG_REQUEST:
		ret = voice_svc_send_req(
			(struct voice_svc_cmd_request *)data->payload, prtd);
		if (!ret)
			ret = count;

		break;
	default:
		pr_debug("%s: Invalid command: %u\n", __func__, cmd);
		ret = -EINVAL;
	}

done:
	kfree(data);
	return ret;
}

static ssize_t voice_svc_read(struct file *file, char __user *arg,
			      size_t count, loff_t *ppos)
{
	int ret = 0;
	struct voice_svc_prvt *prtd;
	struct apr_response_list *resp;
	unsigned long spin_flags;
	int size;

	pr_debug("%s\n", __func__);

	prtd = (struct voice_svc_prvt *)file->private_data;
	if (prtd == NULL) {
		pr_err("%s: prtd is NULL\n", __func__);

		ret = -EINVAL;
		goto done;
	}

	spin_lock_irqsave(&prtd->response_lock, spin_flags);

	if (list_empty(&prtd->response_queue)) {
		spin_unlock_irqrestore(&prtd->response_lock, spin_flags);
		pr_debug("%s: wait for a response\n", __func__);

		ret = wait_event_interruptible_timeout(prtd->response_wait,
					!list_empty(&prtd->response_queue),
					msecs_to_jiffies(TIMEOUT_MS));
		if (ret == 0) {
			pr_debug("%s: Read timeout\n", __func__);

			ret = -ETIMEDOUT;
			goto done;
		} else if (ret > 0 && !list_empty(&prtd->response_queue)) {
			pr_debug("%s: Interrupt recieved for response\n",
				 __func__);
		} else if (ret < 0) {
			pr_debug("%s: Interrupted by SIGNAL %d\n",
				 __func__, ret);

			goto done;
		}

		spin_lock_irqsave(&prtd->response_lock, spin_flags);
	}

	resp = list_first_entry(&prtd->response_queue,
				struct apr_response_list, list);

	spin_unlock_irqrestore(&prtd->response_lock, spin_flags);

	size = resp->resp.payload_size +
	       sizeof(struct voice_svc_cmd_response);

	if (count < size) {
		pr_err("%s: Invalid payload size %zd, %d\n",
		       __func__, count, size);

		ret = -ENOMEM;
		goto done;
	}

	if (!access_ok(VERIFY_WRITE, arg, size)) {
		pr_err("%s: Access denied to write\n",
		       __func__);

		ret = -EPERM;
		goto done;
	}

	ret = copy_to_user(arg, &resp->resp,
			 sizeof(struct voice_svc_cmd_response) +
			 resp->resp.payload_size);
	if (ret) {
		pr_err("%s: copy_to_user failed %d\n", __func__, ret);

		ret = -EPERM;
		goto done;
	}

	spin_lock_irqsave(&prtd->response_lock, spin_flags);

	list_del(&resp->list);
	prtd->response_count--;
	kfree(resp);

	spin_unlock_irqrestore(&prtd->response_lock,
				spin_flags);

	ret = count;

done:
	return ret;
}

static int voice_svc_open(struct inode *inode, struct file *file)
{
	struct voice_svc_prvt *prtd = NULL;

	pr_debug("%s\n", __func__);

	prtd = kmalloc(sizeof(struct voice_svc_prvt), GFP_KERNEL);

	if (prtd == NULL) {
		pr_err("%s: kmalloc failed\n", __func__);
		return -ENOMEM;
	}

	memset(prtd, 0, sizeof(struct voice_svc_prvt));
	prtd->apr_q6_cvs = NULL;
	prtd->apr_q6_mvm = NULL;
	prtd->response_count = 0;
	INIT_LIST_HEAD(&prtd->response_queue);
	init_waitqueue_head(&prtd->response_wait);
	spin_lock_init(&prtd->response_lock);
	file->private_data = (void *)prtd;

	return 0;
}

static int voice_svc_flush(struct file *file, fl_owner_t id)
{
	int ret = 0;
	struct apr_response_list *resp = NULL;
	unsigned long spin_flags;
	struct voice_svc_prvt *prtd = NULL;
	char *svc_name = NULL;
	void **handle = NULL;

	pr_debug("%s\n", __func__);

	prtd = (struct voice_svc_prvt *)file->private_data;
	if (prtd == NULL) {
		pr_err("%s: prtd is NULL\n", __func__);

		ret = -EINVAL;
		goto done;
	}

	if (prtd->apr_q6_cvs != NULL) {
		svc_name = VOICE_SVC_MVM_STR;
		handle = &prtd->apr_q6_cvs;
		ret = voice_svc_dereg(svc_name, handle);
		if (ret)
			pr_err("%s: Failed to dereg CVS %d\n", __func__, ret);
	}

	if (prtd->apr_q6_mvm != NULL) {
		svc_name = VOICE_SVC_MVM_STR;
		handle = &prtd->apr_q6_mvm;
		ret = voice_svc_dereg(svc_name, handle);
		if (ret)
			pr_err("%s: Failed to dereg MVM %d\n", __func__, ret);
	}

	spin_lock_irqsave(&prtd->response_lock, spin_flags);

	while (!list_empty(&prtd->response_queue)) {
		pr_debug("%s: Remove item from response queue\n", __func__);

		resp = list_first_entry(&prtd->response_queue,
					struct apr_response_list, list);
		list_del(&resp->list);
		prtd->response_count--;
		kfree(resp);
	}

	spin_unlock_irqrestore(&prtd->response_lock, spin_flags);

done:
	return ret;
}

static int voice_svc_release(struct inode *inode, struct file *file)
{
	pr_debug("%s\n", __func__);

	kfree(file->private_data);
	file->private_data = NULL;

	return 0;
}

static const struct file_operations voice_svc_fops = {
	.owner =                THIS_MODULE,
	.open =                 voice_svc_open,
	.read =                 voice_svc_read,
	.write =                voice_svc_write,
	.flush =                voice_svc_flush,
	.release =              voice_svc_release,
};


static int voice_svc_probe(struct platform_device *pdev)
{
	int ret = 0;

	pr_debug("%s\n", __func__);

	voice_svc_dev = devm_kzalloc(&pdev->dev,
				  sizeof(struct voice_svc_device), GFP_KERNEL);
	if (!voice_svc_dev) {
		pr_err("%s: kzalloc failed\n", __func__);
		ret = -ENOMEM;
		goto done;
	}

	ret = alloc_chrdev_region(&device_num, 0, MINOR_NUMBER,
				  VOICE_SVC_DRIVER_NAME);
	if (ret) {
		pr_err("%s: Failed to alloc chrdev\n", __func__);
		ret = -ENODEV;
		goto chrdev_err;
	}

	voice_svc_dev->major = MAJOR(device_num);
	voice_svc_class = class_create(THIS_MODULE, VOICE_SVC_DRIVER_NAME);
	if (IS_ERR(voice_svc_class)) {
		ret = PTR_ERR(voice_svc_class);
		pr_err("%s: Failed to create class; err = %d\n", __func__,
			ret);
		goto class_err;
	}

	voice_svc_dev->dev = device_create(voice_svc_class, NULL, device_num,
					   NULL, VOICE_SVC_DRIVER_NAME);
	if (IS_ERR(voice_svc_dev->dev)) {
		ret = PTR_ERR(voice_svc_dev->dev);
		pr_err("%s: Failed to create device; err = %d\n", __func__,
			ret);
		goto dev_err;
	}

	voice_svc_dev->cdev = cdev_alloc();
	cdev_init(voice_svc_dev->cdev, &voice_svc_fops);
	ret = cdev_add(voice_svc_dev->cdev, device_num, MINOR_NUMBER);
	if (ret) {
		pr_err("%s: Failed to register chrdev; err = %d\n", __func__,
			ret);
		goto add_err;
	}
	pr_debug("%s: Device created\n", __func__);
	goto done;

add_err:
	cdev_del(voice_svc_dev->cdev);
	device_destroy(voice_svc_class, device_num);
dev_err:
	class_destroy(voice_svc_class);
class_err:
	unregister_chrdev_region(0, MINOR_NUMBER);
chrdev_err:
	kfree(voice_svc_dev);
done:
	return ret;
}

static int voice_svc_remove(struct platform_device *pdev)
{
	pr_debug("%s\n", __func__);

	cdev_del(voice_svc_dev->cdev);
	kfree(voice_svc_dev->cdev);
	device_destroy(voice_svc_class, device_num);
	class_destroy(voice_svc_class);
	unregister_chrdev_region(0, MINOR_NUMBER);
	kfree(voice_svc_dev);

	return 0;
}

static struct of_device_id voice_svc_of_match[] = {
	{.compatible = "qcom,msm-voice-svc"},
	{ }
};
MODULE_DEVICE_TABLE(of, voice_svc_of_match);

static struct platform_driver voice_svc_driver = {
	.probe          = voice_svc_probe,
	.remove         = voice_svc_remove,
	.driver         = {
		.name   = "msm-voice-svc",
		.owner  = THIS_MODULE,
		.of_match_table = voice_svc_of_match,
	},
};

static int __init voice_svc_init(void)
{
	pr_debug("%s\n", __func__);

	return platform_driver_register(&voice_svc_driver);
}

static void __exit voice_svc_exit(void)
{
	pr_debug("%s\n", __func__);

	platform_driver_unregister(&voice_svc_driver);
}

module_init(voice_svc_init);
module_exit(voice_svc_exit);

MODULE_DESCRIPTION("Soc QDSP6v2 Voice Service driver");
MODULE_LICENSE("GPL v2");
+47 −0
Original line number Diff line number Diff line
#ifndef __VOICE_SVC_H__
#define __VOICE_SVC_H__

#include <linux/types.h>
#include <linux/ioctl.h>

#define VOICE_SVC_DRIVER_NAME "voice_svc"

#define VOICE_SVC_MVM_STR "MVM"
#define VOICE_SVC_CVS_STR "CVS"
#define MAX_APR_SERVICE_NAME_LEN  64

#define MSG_REGISTER 0x1
#define MSG_REQUEST  0x2
#define MSG_RESPONSE 0x3

struct voice_svc_write_msg {
	__u32 msg_type;
	__u8 payload[0];
};

struct voice_svc_register {
	char svc_name[MAX_APR_SERVICE_NAME_LEN];
	__u32 src_port;
	__u8 reg_flag;
};

struct voice_svc_cmd_response {
	__u32 src_port;
	__u32 dest_port;
	__u32 token;
	__u32 opcode;
	__u32 payload_size;
	__u8 payload[0];
};

struct voice_svc_cmd_request {
	char svc_name[MAX_APR_SERVICE_NAME_LEN];
	__u32 src_port;
	__u32 dest_port;
	__u32 token;
	__u32 opcode;
	__u32 payload_size;
	__u8 payload[0];
};

#endif
+1 −0
Original line number Diff line number Diff line
@@ -14,3 +14,4 @@ header-y += lsm_params.h
header-y += audio_slimslave.h
header-y += voice_params.h
header-y += audio_effects.h
header-y += voice_svc.h