brintos

brintos / linux-shallow public Read only

0
0
Text · 12.8 KiB · d0ffa1d Raw
471 lines · c
1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)2//3// This file is provided under a dual BSD/GPLv2 license.  When using or4// redistributing this file, you may do so under either license.5//6// Copyright(c) 2018 Intel Corporation7//8// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>9//10// Generic debug routines used to export DSP MMIO and memories to userspace11// for firmware debugging.12//13 14#include <linux/debugfs.h>15#include <linux/io.h>16#include <linux/pm_runtime.h>17#include <sound/sof/ext_manifest.h>18#include <sound/sof/debug.h>19#include "sof-priv.h"20#include "ops.h"21 22static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer,23				 size_t count, loff_t *ppos)24{25	struct snd_sof_dfsentry *dfse = file->private_data;26	struct snd_sof_dev *sdev = dfse->sdev;27	loff_t pos = *ppos;28	size_t size_ret;29	int skip = 0;30	int size;31	u8 *buf;32 33	size = dfse->size;34 35	/* validate position & count */36	if (pos < 0)37		return -EINVAL;38	if (pos >= size || !count)39		return 0;40	/* find the minimum. min() is not used since it adds sparse warnings */41	if (count > size - pos)42		count = size - pos;43 44	/* align io read start to u32 multiple */45	pos = ALIGN_DOWN(pos, 4);46 47	/* intermediate buffer size must be u32 multiple */48	size = ALIGN(count, 4);49 50	/* if start position is unaligned, read extra u32 */51	if (unlikely(pos != *ppos)) {52		skip = *ppos - pos;53		if (pos + size + 4 < dfse->size)54			size += 4;55	}56 57	buf = kzalloc(size, GFP_KERNEL);58	if (!buf)59		return -ENOMEM;60 61	if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) {62#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)63		/*64		 * If the DSP is active: copy from IO.65		 * If the DSP is suspended:66		 *	- Copy from IO if the memory is always accessible.67		 *	- Otherwise, copy from cached buffer.68		 */69		if (pm_runtime_active(sdev->dev) ||70		    dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) {71			memcpy_fromio(buf, dfse->io_mem + pos, size);72		} else {73			dev_info(sdev->dev,74				 "Copying cached debugfs data\n");75			memcpy(buf, dfse->cache_buf + pos, size);76		}77#else78		/* if the DSP is in D3 */79		if (!pm_runtime_active(sdev->dev) &&80		    dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {81			dev_err(sdev->dev,82				"error: debugfs entry cannot be read in DSP D3\n");83			kfree(buf);84			return -EINVAL;85		}86 87		memcpy_fromio(buf, dfse->io_mem + pos, size);88#endif89	} else {90		memcpy(buf, ((u8 *)(dfse->buf) + pos), size);91	}92 93	/* copy to userspace */94	size_ret = copy_to_user(buffer, buf + skip, count);95 96	kfree(buf);97 98	/* update count & position if copy succeeded */99	if (size_ret)100		return -EFAULT;101 102	*ppos = pos + count;103 104	return count;105}106 107static const struct file_operations sof_dfs_fops = {108	.open = simple_open,109	.read = sof_dfsentry_read,110	.llseek = default_llseek,111};112 113/* create FS entry for debug files that can expose DSP memories, registers */114static int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,115				   void __iomem *base, size_t size,116				   const char *name,117				   enum sof_debugfs_access_type access_type)118{119	struct snd_sof_dfsentry *dfse;120 121	if (!sdev)122		return -EINVAL;123 124	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);125	if (!dfse)126		return -ENOMEM;127 128	dfse->type = SOF_DFSENTRY_TYPE_IOMEM;129	dfse->io_mem = base;130	dfse->size = size;131	dfse->sdev = sdev;132	dfse->access_type = access_type;133 134#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)135	/*136	 * allocate cache buffer that will be used to save the mem window137	 * contents prior to suspend138	 */139	if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {140		dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL);141		if (!dfse->cache_buf)142			return -ENOMEM;143	}144#endif145 146	debugfs_create_file(name, 0444, sdev->debugfs_root, dfse,147			    &sof_dfs_fops);148 149	/* add to dfsentry list */150	list_add(&dfse->list, &sdev->dfsentry_list);151 152	return 0;153}154 155int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev,156					  enum snd_sof_fw_blk_type blk_type, u32 offset,157					  size_t size, const char *name,158					  enum sof_debugfs_access_type access_type)159{160	int bar = snd_sof_dsp_get_bar_index(sdev, blk_type);161 162	if (bar < 0)163		return bar;164 165	return snd_sof_debugfs_io_item(sdev, sdev->bar[bar] + offset, size, name,166				       access_type);167}168EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem);169 170/* create FS entry for debug files to expose kernel memory */171int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,172			     void *base, size_t size,173			     const char *name, mode_t mode)174{175	struct snd_sof_dfsentry *dfse;176 177	if (!sdev)178		return -EINVAL;179 180	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);181	if (!dfse)182		return -ENOMEM;183 184	dfse->type = SOF_DFSENTRY_TYPE_BUF;185	dfse->buf = base;186	dfse->size = size;187	dfse->sdev = sdev;188 189	debugfs_create_file(name, mode, sdev->debugfs_root, dfse,190			    &sof_dfs_fops);191	/* add to dfsentry list */192	list_add(&dfse->list, &sdev->dfsentry_list);193 194	return 0;195}196EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);197 198static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_size)199{200	struct sof_ipc_cmd_hdr msg = {201		.size = sizeof(struct sof_ipc_cmd_hdr),202		.cmd = SOF_IPC_GLB_DEBUG | SOF_IPC_DEBUG_MEM_USAGE,203	};204	struct sof_ipc_dbg_mem_usage *reply;205	int len;206	int ret;207	int i;208 209	reply = kmalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);210	if (!reply)211		return -ENOMEM;212 213	ret = pm_runtime_resume_and_get(sdev->dev);214	if (ret < 0 && ret != -EACCES) {215		dev_err(sdev->dev, "error: enabling device failed: %d\n", ret);216		goto error;217	}218 219	ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE);220	pm_runtime_mark_last_busy(sdev->dev);221	pm_runtime_put_autosuspend(sdev->dev);222	if (ret < 0 || reply->rhdr.error < 0) {223		ret = min(ret, reply->rhdr.error);224		dev_err(sdev->dev, "error: reading memory info failed, %d\n", ret);225		goto error;226	}227 228	if (struct_size(reply, elems, reply->num_elems) != reply->rhdr.hdr.size) {229		dev_err(sdev->dev, "error: invalid memory info ipc struct size, %d\n",230			reply->rhdr.hdr.size);231		ret = -EINVAL;232		goto error;233	}234 235	for (i = 0, len = 0; i < reply->num_elems; i++) {236		ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",237				reply->elems[i].zone, reply->elems[i].id,238				reply->elems[i].used, reply->elems[i].free);239		if (ret < 0)240			goto error;241		len += ret;242	}243 244	ret = len;245error:246	kfree(reply);247	return ret;248}249 250static ssize_t memory_info_read(struct file *file, char __user *to, size_t count, loff_t *ppos)251{252	struct snd_sof_dfsentry *dfse = file->private_data;253	struct snd_sof_dev *sdev = dfse->sdev;254	int data_length;255 256	/* read memory info from FW only once for each file read */257	if (!*ppos) {258		dfse->buf_data_size = 0;259		data_length = memory_info_update(sdev, dfse->buf, dfse->size);260		if (data_length < 0)261			return data_length;262		dfse->buf_data_size = data_length;263	}264 265	return simple_read_from_buffer(to, count, ppos, dfse->buf, dfse->buf_data_size);266}267 268static int memory_info_open(struct inode *inode, struct file *file)269{270	struct snd_sof_dfsentry *dfse = inode->i_private;271	struct snd_sof_dev *sdev = dfse->sdev;272 273	file->private_data = dfse;274 275	/* allocate buffer memory only in first open run, to save memory when unused */276	if (!dfse->buf) {277		dfse->buf = devm_kmalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);278		if (!dfse->buf)279			return -ENOMEM;280		dfse->size = PAGE_SIZE;281	}282 283	return 0;284}285 286static const struct file_operations memory_info_fops = {287	.open = memory_info_open,288	.read = memory_info_read,289	.llseek = default_llseek,290};291 292int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev)293{294	struct snd_sof_dfsentry *dfse;295 296	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);297	if (!dfse)298		return -ENOMEM;299 300	/* don't allocate buffer before first usage, to save memory when unused */301	dfse->type = SOF_DFSENTRY_TYPE_BUF;302	dfse->sdev = sdev;303 304	debugfs_create_file("memory_info", 0444, sdev->debugfs_root, dfse, &memory_info_fops);305 306	/* add to dfsentry list */307	list_add(&dfse->list, &sdev->dfsentry_list);308	return 0;309}310EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init);311 312int snd_sof_dbg_init(struct snd_sof_dev *sdev)313{314	const struct snd_sof_dsp_ops *ops = sof_ops(sdev);315	struct snd_sof_pdata *plat_data = sdev->pdata;316	const struct snd_sof_debugfs_map *map;317	struct dentry *fw_profile;318	int i;319	int err;320 321	/* use "sof" as top level debugFS dir */322	sdev->debugfs_root = debugfs_create_dir("sof", NULL);323 324	/* expose firmware/topology prefix/names for test purposes */325	fw_profile = debugfs_create_dir("fw_profile", sdev->debugfs_root);326 327	debugfs_create_str("fw_path", 0444, fw_profile,328			   (char **)&plat_data->fw_filename_prefix);329	/* library path is not valid for IPC3 */330	if (plat_data->ipc_type != SOF_IPC_TYPE_3) {331		/*332		 * fw_lib_prefix can be NULL if the vendor/platform does not333		 * support loadable libraries334		 */335		if (plat_data->fw_lib_prefix) {336			debugfs_create_str("fw_lib_path", 0444, fw_profile,337					   (char **)&plat_data->fw_lib_prefix);338		} else {339			static char *fw_lib_path;340 341			fw_lib_path = devm_kasprintf(sdev->dev, GFP_KERNEL,342						     "Not supported");343			if (!fw_lib_path)344				return -ENOMEM;345 346			debugfs_create_str("fw_lib_path", 0444, fw_profile,347					   (char **)&fw_lib_path);348		}349	}350	debugfs_create_str("tplg_path", 0444, fw_profile,351			   (char **)&plat_data->tplg_filename_prefix);352	debugfs_create_str("fw_name", 0444, fw_profile,353			   (char **)&plat_data->fw_filename);354	debugfs_create_str("tplg_name", 0444, fw_profile,355			   (char **)&plat_data->tplg_filename);356	debugfs_create_u32("ipc_type", 0444, fw_profile,357			   (u32 *)&plat_data->ipc_type);358 359	/* init dfsentry list */360	INIT_LIST_HEAD(&sdev->dfsentry_list);361 362	/* create debugFS files for platform specific MMIO/DSP memories */363	for (i = 0; i < ops->debug_map_count; i++) {364		map = &ops->debug_map[i];365 366		err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] +367					      map->offset, map->size,368					      map->name, map->access_type);369		/* errors are only due to memory allocation, not debugfs */370		if (err < 0)371			return err;372	}373 374	return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state,375					sizeof(sdev->fw_state),376					"fw_state", 0444);377}378EXPORT_SYMBOL_GPL(snd_sof_dbg_init);379 380void snd_sof_free_debug(struct snd_sof_dev *sdev)381{382	debugfs_remove_recursive(sdev->debugfs_root);383}384EXPORT_SYMBOL_GPL(snd_sof_free_debug);385 386static const struct soc_fw_state_info {387	enum sof_fw_state state;388	const char *name;389} fw_state_dbg[] = {390	{SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"},391	{SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"},392	{SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"},393	{SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"},394	{SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"},395	{SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"},396	{SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"},397	{SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"},398	{SOF_FW_CRASHED, "SOF_FW_CRASHED"},399};400 401static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level)402{403	int i;404 405	for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) {406		if (sdev->fw_state == fw_state_dbg[i].state) {407			dev_printk(level, sdev->dev, "fw_state: %s (%d)\n",408				   fw_state_dbg[i].name, i);409			return;410		}411	}412 413	dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state);414}415 416void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags)417{418	char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;419	bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS);420 421	if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all)422		return;423 424	if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) {425		dev_printk(level, sdev->dev,426			   "------------[ DSP dump start ]------------\n");427		if (msg)428			dev_printk(level, sdev->dev, "%s\n", msg);429		snd_sof_dbg_print_fw_state(sdev, level);430		sof_ops(sdev)->dbg_dump(sdev, flags);431		dev_printk(level, sdev->dev,432			   "------------[ DSP dump end ]------------\n");433		if (!print_all)434			sdev->dbg_dump_printed = true;435	} else if (msg) {436		dev_printk(level, sdev->dev, "%s\n", msg);437	}438}439EXPORT_SYMBOL(snd_sof_dsp_dbg_dump);440 441static void snd_sof_ipc_dump(struct snd_sof_dev *sdev)442{443	if (sof_ops(sdev)->ipc_dump  && !sdev->ipc_dump_printed) {444		dev_err(sdev->dev, "------------[ IPC dump start ]------------\n");445		sof_ops(sdev)->ipc_dump(sdev);446		dev_err(sdev->dev, "------------[ IPC dump end ]------------\n");447		if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS))448			sdev->ipc_dump_printed = true;449	}450}451 452void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg)453{454	if ((IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||455	    sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) && !sdev->d3_prevented) {456		/* should we prevent DSP entering D3 ? */457		if (!sdev->ipc_dump_printed)458			dev_info(sdev->dev,459				 "Attempting to prevent DSP from entering D3 state to preserve context\n");460 461		if (pm_runtime_get_if_in_use(sdev->dev) == 1)462			sdev->d3_prevented = true;463	}464 465	/* dump vital information to the logs */466	snd_sof_ipc_dump(sdev);467	snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);468	sof_fw_trace_fw_crashed(sdev);469}470EXPORT_SYMBOL(snd_sof_handle_fw_exception);471