162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright(c) 2022 Intel Corporation. All rights reserved.
462306a36Sopenharmony_ci//
562306a36Sopenharmony_ci// Author: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
662306a36Sopenharmony_ci//
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/auxiliary_bus.h>
962306a36Sopenharmony_ci#include <linux/completion.h>
1062306a36Sopenharmony_ci#include <linux/debugfs.h>
1162306a36Sopenharmony_ci#include <linux/ktime.h>
1262306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/uaccess.h>
1762306a36Sopenharmony_ci#include <sound/sof/header.h>
1862306a36Sopenharmony_ci#include <sound/sof/ipc4/header.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "sof-client.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS	3000
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct sof_msg_inject_priv {
2562306a36Sopenharmony_ci	struct dentry *dfs_file;
2662306a36Sopenharmony_ci	size_t max_msg_size;
2762306a36Sopenharmony_ci	enum sof_ipc_type ipc_type;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	void *tx_buffer;
3062306a36Sopenharmony_ci	void *rx_buffer;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int sof_msg_inject_dfs_open(struct inode *inode, struct file *file)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct sof_client_dev *cdev = inode->i_private;
3662306a36Sopenharmony_ci	int ret;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED)
3962306a36Sopenharmony_ci		return -ENODEV;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	ret = debugfs_file_get(file->f_path.dentry);
4262306a36Sopenharmony_ci	if (unlikely(ret))
4362306a36Sopenharmony_ci		return ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ret = simple_open(inode, file);
4662306a36Sopenharmony_ci	if (ret)
4762306a36Sopenharmony_ci		debugfs_file_put(file->f_path.dentry);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return ret;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer,
5362306a36Sopenharmony_ci				       size_t count, loff_t *ppos)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct sof_client_dev *cdev = file->private_data;
5662306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
5762306a36Sopenharmony_ci	struct sof_ipc_reply *rhdr = priv->rx_buffer;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (!rhdr->hdr.size || !count || *ppos)
6062306a36Sopenharmony_ci		return 0;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (count > rhdr->hdr.size)
6362306a36Sopenharmony_ci		count = rhdr->hdr.size;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (copy_to_user(buffer, priv->rx_buffer, count))
6662306a36Sopenharmony_ci		return -EFAULT;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	*ppos += count;
6962306a36Sopenharmony_ci	return count;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic ssize_t sof_msg_inject_ipc4_dfs_read(struct file *file,
7362306a36Sopenharmony_ci					    char __user *buffer,
7462306a36Sopenharmony_ci					    size_t count, loff_t *ppos)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct sof_client_dev *cdev = file->private_data;
7762306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
7862306a36Sopenharmony_ci	struct sof_ipc4_msg *ipc4_msg = priv->rx_buffer;
7962306a36Sopenharmony_ci	size_t header_size = sizeof(ipc4_msg->header_u64);
8062306a36Sopenharmony_ci	size_t remaining;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!ipc4_msg->header_u64 || !count || *ppos)
8362306a36Sopenharmony_ci		return 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	/* we need space for the header at minimum (u64) */
8662306a36Sopenharmony_ci	if (count < header_size)
8762306a36Sopenharmony_ci		return -ENOSPC;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	remaining = header_size;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* Only get large config have payload */
9262306a36Sopenharmony_ci	if (SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_msg->primary) &&
9362306a36Sopenharmony_ci	    (SOF_IPC4_MSG_TYPE_GET(ipc4_msg->primary) == SOF_IPC4_MOD_LARGE_CONFIG_GET))
9462306a36Sopenharmony_ci		remaining += ipc4_msg->data_size;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (count > remaining)
9762306a36Sopenharmony_ci		count = remaining;
9862306a36Sopenharmony_ci	else if (count < remaining)
9962306a36Sopenharmony_ci		remaining = count;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* copy the header first */
10262306a36Sopenharmony_ci	if (copy_to_user(buffer, &ipc4_msg->header_u64, header_size))
10362306a36Sopenharmony_ci		return -EFAULT;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	*ppos += header_size;
10662306a36Sopenharmony_ci	remaining -= header_size;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (!remaining)
10962306a36Sopenharmony_ci		return count;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (remaining > ipc4_msg->data_size)
11262306a36Sopenharmony_ci		remaining = ipc4_msg->data_size;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* Copy the payload */
11562306a36Sopenharmony_ci	if (copy_to_user(buffer + *ppos, ipc4_msg->data_ptr, remaining))
11662306a36Sopenharmony_ci		return -EFAULT;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	*ppos += remaining;
11962306a36Sopenharmony_ci	return count;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic int sof_msg_inject_send_message(struct sof_client_dev *cdev)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
12562306a36Sopenharmony_ci	struct device *dev = &cdev->auxdev.dev;
12662306a36Sopenharmony_ci	int ret, err;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	ret = pm_runtime_resume_and_get(dev);
12962306a36Sopenharmony_ci	if (ret < 0 && ret != -EACCES) {
13062306a36Sopenharmony_ci		dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
13162306a36Sopenharmony_ci		return ret;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* send the message */
13562306a36Sopenharmony_ci	ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer,
13662306a36Sopenharmony_ci					priv->max_msg_size);
13762306a36Sopenharmony_ci	if (ret)
13862306a36Sopenharmony_ci		dev_err(dev, "IPC message send failed: %d\n", ret);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	pm_runtime_mark_last_busy(dev);
14162306a36Sopenharmony_ci	err = pm_runtime_put_autosuspend(dev);
14262306a36Sopenharmony_ci	if (err < 0)
14362306a36Sopenharmony_ci		dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return ret;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer,
14962306a36Sopenharmony_ci					size_t count, loff_t *ppos)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct sof_client_dev *cdev = file->private_data;
15262306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
15362306a36Sopenharmony_ci	ssize_t size;
15462306a36Sopenharmony_ci	int ret;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (*ppos)
15762306a36Sopenharmony_ci		return 0;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	size = simple_write_to_buffer(priv->tx_buffer, priv->max_msg_size,
16062306a36Sopenharmony_ci				      ppos, buffer, count);
16162306a36Sopenharmony_ci	if (size < 0)
16262306a36Sopenharmony_ci		return size;
16362306a36Sopenharmony_ci	if (size != count)
16462306a36Sopenharmony_ci		return -EFAULT;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	memset(priv->rx_buffer, 0, priv->max_msg_size);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	ret = sof_msg_inject_send_message(cdev);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/* return the error code if test failed */
17162306a36Sopenharmony_ci	if (ret < 0)
17262306a36Sopenharmony_ci		size = ret;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return size;
17562306a36Sopenharmony_ci};
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file,
17862306a36Sopenharmony_ci					     const char __user *buffer,
17962306a36Sopenharmony_ci					     size_t count, loff_t *ppos)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct sof_client_dev *cdev = file->private_data;
18262306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
18362306a36Sopenharmony_ci	struct sof_ipc4_msg *ipc4_msg = priv->tx_buffer;
18462306a36Sopenharmony_ci	size_t data_size;
18562306a36Sopenharmony_ci	int ret;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	if (*ppos)
18862306a36Sopenharmony_ci		return 0;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (count < sizeof(ipc4_msg->header_u64))
19162306a36Sopenharmony_ci		return -EINVAL;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* copy the header first */
19462306a36Sopenharmony_ci	if (copy_from_user(&ipc4_msg->header_u64, buffer,
19562306a36Sopenharmony_ci			   sizeof(ipc4_msg->header_u64)))
19662306a36Sopenharmony_ci		return -EFAULT;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	data_size = count - sizeof(ipc4_msg->header_u64);
19962306a36Sopenharmony_ci	if (data_size > priv->max_msg_size)
20062306a36Sopenharmony_ci		return -EINVAL;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	/* Copy the payload */
20362306a36Sopenharmony_ci	if (copy_from_user(ipc4_msg->data_ptr,
20462306a36Sopenharmony_ci			   buffer + sizeof(ipc4_msg->header_u64), data_size))
20562306a36Sopenharmony_ci		return -EFAULT;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	ipc4_msg->data_size = data_size;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* Initialize the reply storage */
21062306a36Sopenharmony_ci	ipc4_msg = priv->rx_buffer;
21162306a36Sopenharmony_ci	ipc4_msg->header_u64 = 0;
21262306a36Sopenharmony_ci	ipc4_msg->data_size = priv->max_msg_size;
21362306a36Sopenharmony_ci	memset(ipc4_msg->data_ptr, 0, priv->max_msg_size);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	ret = sof_msg_inject_send_message(cdev);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	/* return the error code if test failed */
21862306a36Sopenharmony_ci	if (ret < 0)
21962306a36Sopenharmony_ci		return ret;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return count;
22262306a36Sopenharmony_ci};
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic int sof_msg_inject_dfs_release(struct inode *inode, struct file *file)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	debugfs_file_put(file->f_path.dentry);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	return 0;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic const struct file_operations sof_msg_inject_fops = {
23262306a36Sopenharmony_ci	.open = sof_msg_inject_dfs_open,
23362306a36Sopenharmony_ci	.read = sof_msg_inject_dfs_read,
23462306a36Sopenharmony_ci	.write = sof_msg_inject_dfs_write,
23562306a36Sopenharmony_ci	.llseek = default_llseek,
23662306a36Sopenharmony_ci	.release = sof_msg_inject_dfs_release,
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	.owner = THIS_MODULE,
23962306a36Sopenharmony_ci};
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic const struct file_operations sof_msg_inject_ipc4_fops = {
24262306a36Sopenharmony_ci	.open = sof_msg_inject_dfs_open,
24362306a36Sopenharmony_ci	.read = sof_msg_inject_ipc4_dfs_read,
24462306a36Sopenharmony_ci	.write = sof_msg_inject_ipc4_dfs_write,
24562306a36Sopenharmony_ci	.llseek = default_llseek,
24662306a36Sopenharmony_ci	.release = sof_msg_inject_dfs_release,
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	.owner = THIS_MODULE,
24962306a36Sopenharmony_ci};
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic int sof_msg_inject_probe(struct auxiliary_device *auxdev,
25262306a36Sopenharmony_ci				const struct auxiliary_device_id *id)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
25562306a36Sopenharmony_ci	struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);
25662306a36Sopenharmony_ci	static const struct file_operations *fops;
25762306a36Sopenharmony_ci	struct device *dev = &auxdev->dev;
25862306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv;
25962306a36Sopenharmony_ci	size_t alloc_size;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	/* allocate memory for client data */
26262306a36Sopenharmony_ci	priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
26362306a36Sopenharmony_ci	if (!priv)
26462306a36Sopenharmony_ci		return -ENOMEM;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	priv->ipc_type = sof_client_get_ipc_type(cdev);
26762306a36Sopenharmony_ci	priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev);
26862306a36Sopenharmony_ci	alloc_size = priv->max_msg_size;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (priv->ipc_type == SOF_INTEL_IPC4)
27162306a36Sopenharmony_ci		alloc_size += sizeof(struct sof_ipc4_msg);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	priv->tx_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL);
27462306a36Sopenharmony_ci	priv->rx_buffer = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
27562306a36Sopenharmony_ci	if (!priv->tx_buffer || !priv->rx_buffer)
27662306a36Sopenharmony_ci		return -ENOMEM;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	if (priv->ipc_type == SOF_INTEL_IPC4) {
27962306a36Sopenharmony_ci		struct sof_ipc4_msg *ipc4_msg;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci		ipc4_msg = priv->tx_buffer;
28262306a36Sopenharmony_ci		ipc4_msg->data_ptr = priv->tx_buffer + sizeof(struct sof_ipc4_msg);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci		ipc4_msg = priv->rx_buffer;
28562306a36Sopenharmony_ci		ipc4_msg->data_ptr = priv->rx_buffer + sizeof(struct sof_ipc4_msg);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		fops = &sof_msg_inject_ipc4_fops;
28862306a36Sopenharmony_ci	} else {
28962306a36Sopenharmony_ci		fops = &sof_msg_inject_fops;
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	cdev->data = priv;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root,
29562306a36Sopenharmony_ci					     cdev, fops);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/* enable runtime PM */
29862306a36Sopenharmony_ci	pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
29962306a36Sopenharmony_ci	pm_runtime_use_autosuspend(dev);
30062306a36Sopenharmony_ci	pm_runtime_enable(dev);
30162306a36Sopenharmony_ci	pm_runtime_mark_last_busy(dev);
30262306a36Sopenharmony_ci	pm_runtime_idle(dev);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	return 0;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic void sof_msg_inject_remove(struct auxiliary_device *auxdev)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
31062306a36Sopenharmony_ci	struct sof_msg_inject_priv *priv = cdev->data;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	pm_runtime_disable(&auxdev->dev);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	debugfs_remove(priv->dfs_file);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic const struct auxiliary_device_id sof_msg_inject_client_id_table[] = {
31862306a36Sopenharmony_ci	{ .name = "snd_sof.msg_injector" },
31962306a36Sopenharmony_ci	{},
32062306a36Sopenharmony_ci};
32162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci/*
32462306a36Sopenharmony_ci * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus
32562306a36Sopenharmony_ci * type are enough to ensure that the parent SOF device resumes to bring the DSP
32662306a36Sopenharmony_ci * back to D0.
32762306a36Sopenharmony_ci * Driver name will be set based on KBUILD_MODNAME.
32862306a36Sopenharmony_ci */
32962306a36Sopenharmony_cistatic struct auxiliary_driver sof_msg_inject_client_drv = {
33062306a36Sopenharmony_ci	.probe = sof_msg_inject_probe,
33162306a36Sopenharmony_ci	.remove = sof_msg_inject_remove,
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	.id_table = sof_msg_inject_client_id_table,
33462306a36Sopenharmony_ci};
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cimodule_auxiliary_driver(sof_msg_inject_client_drv);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ciMODULE_DESCRIPTION("SOF IPC Message Injector Client Driver");
33962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
34062306a36Sopenharmony_ciMODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
341