brintos

brintos / linux-shallow public Read only

0
0
Text · 10.7 KiB · e7d2001 Raw
400 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2//3// Copyright(c) 2022 Intel Corporation4//5// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>6//	    Peter Ujfalusi <peter.ujfalusi@linux.intel.com>7//8 9#include <linux/auxiliary_bus.h>10#include <linux/completion.h>11#include <linux/debugfs.h>12#include <linux/ktime.h>13#include <linux/mod_devicetable.h>14#include <linux/module.h>15#include <linux/pm_runtime.h>16#include <linux/slab.h>17#include <linux/uaccess.h>18#include <sound/sof/header.h>19 20#include "sof-client.h"21 22#define MAX_IPC_FLOOD_DURATION_MS	100023#define MAX_IPC_FLOOD_COUNT		1000024#define IPC_FLOOD_TEST_RESULT_LEN	51225#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS	300026 27#define DEBUGFS_IPC_FLOOD_COUNT		"ipc_flood_count"28#define DEBUGFS_IPC_FLOOD_DURATION	"ipc_flood_duration_ms"29 30struct sof_ipc_flood_priv {31	struct dentry *dfs_root;32	struct dentry *dfs_link[2];33	char *buf;34};35 36static int sof_ipc_flood_dfs_open(struct inode *inode, struct file *file)37{38	struct sof_client_dev *cdev = inode->i_private;39	int ret;40 41	if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED)42		return -ENODEV;43 44	ret = debugfs_file_get(file->f_path.dentry);45	if (unlikely(ret))46		return ret;47 48	ret = simple_open(inode, file);49	if (ret)50		debugfs_file_put(file->f_path.dentry);51 52	return ret;53}54 55/*56 * helper function to perform the flood test. Only one of the two params, ipc_duration_ms57 * or ipc_count, will be non-zero and will determine the type of test58 */59static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev,60				    bool flood_duration_test,61				    unsigned long ipc_duration_ms,62				    unsigned long ipc_count)63{64	struct sof_ipc_flood_priv *priv = cdev->data;65	struct device *dev = &cdev->auxdev.dev;66	struct sof_ipc_cmd_hdr hdr;67	u64 min_response_time = U64_MAX;68	ktime_t start, end, test_end;69	u64 avg_response_time = 0;70	u64 max_response_time = 0;71	u64 ipc_response_time;72	int i = 0;73	int ret;74 75	/* configure test IPC */76	hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;77	hdr.size = sizeof(hdr);78 79	/* set test end time for duration flood test */80	if (flood_duration_test)81		test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;82 83	/* send test IPC's */84	while (1) {85		start = ktime_get();86		ret = sof_client_ipc_tx_message_no_reply(cdev, &hdr);87		end = ktime_get();88 89		if (ret < 0)90			break;91 92		/* compute min and max response times */93		ipc_response_time = ktime_to_ns(ktime_sub(end, start));94		min_response_time = min(min_response_time, ipc_response_time);95		max_response_time = max(max_response_time, ipc_response_time);96 97		/* sum up response times */98		avg_response_time += ipc_response_time;99		i++;100 101		/* test complete? */102		if (flood_duration_test) {103			if (ktime_to_ns(end) >= test_end)104				break;105		} else {106			if (i == ipc_count)107				break;108		}109	}110 111	if (ret < 0)112		dev_err(dev, "ipc flood test failed at %d iterations\n", i);113 114	/* return if the first IPC fails */115	if (!i)116		return ret;117 118	/* compute average response time */119	do_div(avg_response_time, i);120 121	/* clear previous test output */122	memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN);123 124	if (!ipc_count) {125		dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms);126		snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN,127			 "IPC Flood test duration: %lums\n", ipc_duration_ms);128	}129 130	dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n",131		i, avg_response_time);132	dev_dbg(dev, "Max response time: %lluns\n", max_response_time);133	dev_dbg(dev, "Min response time: %lluns\n", min_response_time);134 135	/* format output string and save test results */136	snprintf(priv->buf + strlen(priv->buf),137		 IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),138		 "IPC Flood count: %d\nAvg response time: %lluns\n",139		 i, avg_response_time);140 141	snprintf(priv->buf + strlen(priv->buf),142		 IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),143		 "Max response time: %lluns\nMin response time: %lluns\n",144		 max_response_time, min_response_time);145 146	return ret;147}148 149/*150 * Writing to the debugfs entry initiates the IPC flood test based on151 * the IPC count or the duration specified by the user.152 */153static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer,154				       size_t count, loff_t *ppos)155{156	struct sof_client_dev *cdev = file->private_data;157	struct device *dev = &cdev->auxdev.dev;158	unsigned long ipc_duration_ms = 0;159	bool flood_duration_test = false;160	unsigned long ipc_count = 0;161	struct dentry *dentry;162	int err;163	char *string;164	int ret;165 166	if (*ppos != 0)167		return -EINVAL;168 169	string = kzalloc(count + 1, GFP_KERNEL);170	if (!string)171		return -ENOMEM;172 173	if (copy_from_user(string, buffer, count)) {174		ret = -EFAULT;175		goto out;176	}177 178	/*179	 * write op is only supported for ipc_flood_count or180	 * ipc_flood_duration_ms debugfs entries atm.181	 * ipc_flood_count floods the DSP with the number of IPC's specified.182	 * ipc_duration_ms test floods the DSP for the time specified183	 * in the debugfs entry.184	 */185	dentry = file->f_path.dentry;186	if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) &&187	    strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {188		ret = -EINVAL;189		goto out;190	}191 192	if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION))193		flood_duration_test = true;194 195	/* test completion criterion */196	if (flood_duration_test)197		ret = kstrtoul(string, 0, &ipc_duration_ms);198	else199		ret = kstrtoul(string, 0, &ipc_count);200	if (ret < 0)201		goto out;202 203	/* limit max duration/ipc count for flood test */204	if (flood_duration_test) {205		if (!ipc_duration_ms) {206			ret = count;207			goto out;208		}209 210		/* find the minimum. min() is not used to avoid warnings */211		if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)212			ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;213	} else {214		if (!ipc_count) {215			ret = count;216			goto out;217		}218 219		/* find the minimum. min() is not used to avoid warnings */220		if (ipc_count > MAX_IPC_FLOOD_COUNT)221			ipc_count = MAX_IPC_FLOOD_COUNT;222	}223 224	ret = pm_runtime_resume_and_get(dev);225	if (ret < 0 && ret != -EACCES) {226		dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);227		goto out;228	}229 230	/* flood test */231	ret = sof_debug_ipc_flood_test(cdev, flood_duration_test,232				       ipc_duration_ms, ipc_count);233 234	pm_runtime_mark_last_busy(dev);235	err = pm_runtime_put_autosuspend(dev);236	if (err < 0)237		dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);238 239	/* return count if test is successful */240	if (ret >= 0)241		ret = count;242out:243	kfree(string);244	return ret;245}246 247/* return the result of the last IPC flood test */248static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer,249				      size_t count, loff_t *ppos)250{251	struct sof_client_dev *cdev = file->private_data;252	struct sof_ipc_flood_priv *priv = cdev->data;253	size_t size_ret;254 255	struct dentry *dentry;256 257	dentry = file->f_path.dentry;258	if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) ||259	    !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {260		if (*ppos)261			return 0;262 263		count = min_t(size_t, count, strlen(priv->buf));264		size_ret = copy_to_user(buffer, priv->buf, count);265		if (size_ret)266			return -EFAULT;267 268		*ppos += count;269		return count;270	}271	return count;272}273 274static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file)275{276	debugfs_file_put(file->f_path.dentry);277 278	return 0;279}280 281static const struct file_operations sof_ipc_flood_fops = {282	.open = sof_ipc_flood_dfs_open,283	.read = sof_ipc_flood_dfs_read,284	.llseek = default_llseek,285	.write = sof_ipc_flood_dfs_write,286	.release = sof_ipc_flood_dfs_release,287 288	.owner = THIS_MODULE,289};290 291/*292 * The IPC test client creates a couple of debugfs entries that will be used293 * flood tests. Users can write to these entries to execute the IPC flood test294 * by specifying either the number of IPCs to flood the DSP with or the duration295 * (in ms) for which the DSP should be flooded with test IPCs. At the296 * end of each test, the average, min and max response times are reported back.297 * The results of the last flood test can be accessed by reading the debugfs298 * entries.299 */300static int sof_ipc_flood_probe(struct auxiliary_device *auxdev,301			       const struct auxiliary_device_id *id)302{303	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);304	struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);305	struct device *dev = &auxdev->dev;306	struct sof_ipc_flood_priv *priv;307 308	/* allocate memory for client data */309	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);310	if (!priv)311		return -ENOMEM;312 313	priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);314	if (!priv->buf)315		return -ENOMEM;316 317	cdev->data = priv;318 319	/* create debugfs root folder with device name under parent SOF dir */320	priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root);321	if (!IS_ERR_OR_NULL(priv->dfs_root)) {322		/* create read-write ipc_flood_count debugfs entry */323		debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root,324				    cdev, &sof_ipc_flood_fops);325 326		/* create read-write ipc_flood_duration_ms debugfs entry */327		debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644,328				    priv->dfs_root, cdev, &sof_ipc_flood_fops);329 330		if (auxdev->id == 0) {331			/*332			 * Create symlinks for backwards compatibility to the333			 * first IPC flood test instance334			 */335			char target[100];336 337			snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT,338				 dev_name(dev));339			priv->dfs_link[0] =340				debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT,341						       debugfs_root, target);342 343			snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION,344				 dev_name(dev));345			priv->dfs_link[1] =346				debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION,347						       debugfs_root, target);348		}349	}350 351	/* enable runtime PM */352	pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);353	pm_runtime_use_autosuspend(dev);354	pm_runtime_enable(dev);355	pm_runtime_mark_last_busy(dev);356	pm_runtime_idle(dev);357 358	return 0;359}360 361static void sof_ipc_flood_remove(struct auxiliary_device *auxdev)362{363	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);364	struct sof_ipc_flood_priv *priv = cdev->data;365 366	pm_runtime_disable(&auxdev->dev);367 368	if (auxdev->id == 0) {369		debugfs_remove(priv->dfs_link[0]);370		debugfs_remove(priv->dfs_link[1]);371	}372 373	debugfs_remove_recursive(priv->dfs_root);374}375 376static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = {377	{ .name = "snd_sof.ipc_flood" },378	{},379};380MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table);381 382/*383 * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus384 * type are enough to ensure that the parent SOF device resumes to bring the DSP385 * back to D0.386 * Driver name will be set based on KBUILD_MODNAME.387 */388static struct auxiliary_driver sof_ipc_flood_client_drv = {389	.probe = sof_ipc_flood_probe,390	.remove = sof_ipc_flood_remove,391 392	.id_table = sof_ipc_flood_client_id_table,393};394 395module_auxiliary_driver(sof_ipc_flood_client_drv);396 397MODULE_LICENSE("GPL");398MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver");399MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);400