cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

sof-client-ipc-flood-test.c (10998B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2//
      3// Copyright(c) 2022 Intel Corporation. All rights reserved.
      4//
      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	1000
     23#define MAX_IPC_FLOOD_COUNT		10000
     24#define IPC_FLOOD_TEST_RESULT_LEN	512
     25#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS	3000
     26
     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_ms
     57 * or ipc_count, will be non-zero and will determine the type of test
     58 */
     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	struct sof_ipc_reply reply;
     68	u64 min_response_time = U64_MAX;
     69	ktime_t start, end, test_end;
     70	u64 avg_response_time = 0;
     71	u64 max_response_time = 0;
     72	u64 ipc_response_time;
     73	int i = 0;
     74	int ret;
     75
     76	/* configure test IPC */
     77	hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;
     78	hdr.size = sizeof(hdr);
     79
     80	/* set test end time for duration flood test */
     81	if (flood_duration_test)
     82		test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
     83
     84	/* send test IPC's */
     85	while (1) {
     86		start = ktime_get();
     87		ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply));
     88		end = ktime_get();
     89
     90		if (ret < 0)
     91			break;
     92
     93		/* compute min and max response times */
     94		ipc_response_time = ktime_to_ns(ktime_sub(end, start));
     95		min_response_time = min(min_response_time, ipc_response_time);
     96		max_response_time = max(max_response_time, ipc_response_time);
     97
     98		/* sum up response times */
     99		avg_response_time += ipc_response_time;
    100		i++;
    101
    102		/* test complete? */
    103		if (flood_duration_test) {
    104			if (ktime_to_ns(end) >= test_end)
    105				break;
    106		} else {
    107			if (i == ipc_count)
    108				break;
    109		}
    110	}
    111
    112	if (ret < 0)
    113		dev_err(dev, "ipc flood test failed at %d iterations\n", i);
    114
    115	/* return if the first IPC fails */
    116	if (!i)
    117		return ret;
    118
    119	/* compute average response time */
    120	do_div(avg_response_time, i);
    121
    122	/* clear previous test output */
    123	memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
    124
    125	if (!ipc_count) {
    126		dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms);
    127		snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN,
    128			 "IPC Flood test duration: %lums\n", ipc_duration_ms);
    129	}
    130
    131	dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n",
    132		i, avg_response_time);
    133	dev_dbg(dev, "Max response time: %lluns\n", max_response_time);
    134	dev_dbg(dev, "Min response time: %lluns\n", min_response_time);
    135
    136	/* format output string and save test results */
    137	snprintf(priv->buf + strlen(priv->buf),
    138		 IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),
    139		 "IPC Flood count: %d\nAvg response time: %lluns\n",
    140		 i, avg_response_time);
    141
    142	snprintf(priv->buf + strlen(priv->buf),
    143		 IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),
    144		 "Max response time: %lluns\nMin response time: %lluns\n",
    145		 max_response_time, min_response_time);
    146
    147	return ret;
    148}
    149
    150/*
    151 * Writing to the debugfs entry initiates the IPC flood test based on
    152 * the IPC count or the duration specified by the user.
    153 */
    154static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer,
    155				       size_t count, loff_t *ppos)
    156{
    157	struct sof_client_dev *cdev = file->private_data;
    158	struct device *dev = &cdev->auxdev.dev;
    159	unsigned long ipc_duration_ms = 0;
    160	bool flood_duration_test = false;
    161	unsigned long ipc_count = 0;
    162	struct dentry *dentry;
    163	int err;
    164	size_t size;
    165	char *string;
    166	int ret;
    167
    168	string = kzalloc(count + 1, GFP_KERNEL);
    169	if (!string)
    170		return -ENOMEM;
    171
    172	size = simple_write_to_buffer(string, count, ppos, buffer, count);
    173
    174	/*
    175	 * write op is only supported for ipc_flood_count or
    176	 * ipc_flood_duration_ms debugfs entries atm.
    177	 * ipc_flood_count floods the DSP with the number of IPC's specified.
    178	 * ipc_duration_ms test floods the DSP for the time specified
    179	 * in the debugfs entry.
    180	 */
    181	dentry = file->f_path.dentry;
    182	if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) &&
    183	    strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {
    184		ret = -EINVAL;
    185		goto out;
    186	}
    187
    188	if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION))
    189		flood_duration_test = true;
    190
    191	/* test completion criterion */
    192	if (flood_duration_test)
    193		ret = kstrtoul(string, 0, &ipc_duration_ms);
    194	else
    195		ret = kstrtoul(string, 0, &ipc_count);
    196	if (ret < 0)
    197		goto out;
    198
    199	/* limit max duration/ipc count for flood test */
    200	if (flood_duration_test) {
    201		if (!ipc_duration_ms) {
    202			ret = size;
    203			goto out;
    204		}
    205
    206		/* find the minimum. min() is not used to avoid warnings */
    207		if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
    208			ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
    209	} else {
    210		if (!ipc_count) {
    211			ret = size;
    212			goto out;
    213		}
    214
    215		/* find the minimum. min() is not used to avoid warnings */
    216		if (ipc_count > MAX_IPC_FLOOD_COUNT)
    217			ipc_count = MAX_IPC_FLOOD_COUNT;
    218	}
    219
    220	ret = pm_runtime_resume_and_get(dev);
    221	if (ret < 0 && ret != -EACCES) {
    222		dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
    223		goto out;
    224	}
    225
    226	/* flood test */
    227	ret = sof_debug_ipc_flood_test(cdev, flood_duration_test,
    228				       ipc_duration_ms, ipc_count);
    229
    230	pm_runtime_mark_last_busy(dev);
    231	err = pm_runtime_put_autosuspend(dev);
    232	if (err < 0)
    233		dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
    234
    235	/* return size if test is successful */
    236	if (ret >= 0)
    237		ret = size;
    238out:
    239	kfree(string);
    240	return ret;
    241}
    242
    243/* return the result of the last IPC flood test */
    244static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer,
    245				      size_t count, loff_t *ppos)
    246{
    247	struct sof_client_dev *cdev = file->private_data;
    248	struct sof_ipc_flood_priv *priv = cdev->data;
    249	size_t size_ret;
    250
    251	struct dentry *dentry;
    252
    253	dentry = file->f_path.dentry;
    254	if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) ||
    255	    !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {
    256		if (*ppos)
    257			return 0;
    258
    259		count = min_t(size_t, count, strlen(priv->buf));
    260		size_ret = copy_to_user(buffer, priv->buf, count);
    261		if (size_ret)
    262			return -EFAULT;
    263
    264		*ppos += count;
    265		return count;
    266	}
    267	return count;
    268}
    269
    270static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file)
    271{
    272	debugfs_file_put(file->f_path.dentry);
    273
    274	return 0;
    275}
    276
    277static const struct file_operations sof_ipc_flood_fops = {
    278	.open = sof_ipc_flood_dfs_open,
    279	.read = sof_ipc_flood_dfs_read,
    280	.llseek = default_llseek,
    281	.write = sof_ipc_flood_dfs_write,
    282	.release = sof_ipc_flood_dfs_release,
    283
    284	.owner = THIS_MODULE,
    285};
    286
    287/*
    288 * The IPC test client creates a couple of debugfs entries that will be used
    289 * flood tests. Users can write to these entries to execute the IPC flood test
    290 * by specifying either the number of IPCs to flood the DSP with or the duration
    291 * (in ms) for which the DSP should be flooded with test IPCs. At the
    292 * end of each test, the average, min and max response times are reported back.
    293 * The results of the last flood test can be accessed by reading the debugfs
    294 * entries.
    295 */
    296static int sof_ipc_flood_probe(struct auxiliary_device *auxdev,
    297			       const struct auxiliary_device_id *id)
    298{
    299	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
    300	struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);
    301	struct device *dev = &auxdev->dev;
    302	struct sof_ipc_flood_priv *priv;
    303
    304	/* allocate memory for client data */
    305	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    306	if (!priv)
    307		return -ENOMEM;
    308
    309	priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);
    310	if (!priv->buf)
    311		return -ENOMEM;
    312
    313	cdev->data = priv;
    314
    315	/* create debugfs root folder with device name under parent SOF dir */
    316	priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root);
    317	if (!IS_ERR_OR_NULL(priv->dfs_root)) {
    318		/* create read-write ipc_flood_count debugfs entry */
    319		debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root,
    320				    cdev, &sof_ipc_flood_fops);
    321
    322		/* create read-write ipc_flood_duration_ms debugfs entry */
    323		debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644,
    324				    priv->dfs_root, cdev, &sof_ipc_flood_fops);
    325
    326		if (auxdev->id == 0) {
    327			/*
    328			 * Create symlinks for backwards compatibility to the
    329			 * first IPC flood test instance
    330			 */
    331			char target[100];
    332
    333			snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT,
    334				 dev_name(dev));
    335			priv->dfs_link[0] =
    336				debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT,
    337						       debugfs_root, target);
    338
    339			snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION,
    340				 dev_name(dev));
    341			priv->dfs_link[1] =
    342				debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION,
    343						       debugfs_root, target);
    344		}
    345	}
    346
    347	/* enable runtime PM */
    348	pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
    349	pm_runtime_use_autosuspend(dev);
    350	pm_runtime_enable(dev);
    351	pm_runtime_mark_last_busy(dev);
    352	pm_runtime_idle(dev);
    353
    354	return 0;
    355}
    356
    357static void sof_ipc_flood_remove(struct auxiliary_device *auxdev)
    358{
    359	struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
    360	struct sof_ipc_flood_priv *priv = cdev->data;
    361
    362	pm_runtime_disable(&auxdev->dev);
    363
    364	if (auxdev->id == 0) {
    365		debugfs_remove(priv->dfs_link[0]);
    366		debugfs_remove(priv->dfs_link[1]);
    367	}
    368
    369	debugfs_remove_recursive(priv->dfs_root);
    370}
    371
    372static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = {
    373	{ .name = "snd_sof.ipc_flood" },
    374	{},
    375};
    376MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table);
    377
    378/*
    379 * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus
    380 * type are enough to ensure that the parent SOF device resumes to bring the DSP
    381 * back to D0.
    382 * Driver name will be set based on KBUILD_MODNAME.
    383 */
    384static struct auxiliary_driver sof_ipc_flood_client_drv = {
    385	.probe = sof_ipc_flood_probe,
    386	.remove = sof_ipc_flood_remove,
    387
    388	.id_table = sof_ipc_flood_client_id_table,
    389};
    390
    391module_auxiliary_driver(sof_ipc_flood_client_drv);
    392
    393MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver");
    394MODULE_LICENSE("GPL");
    395MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);