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