| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 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 |
|
| 22 | static 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 |
|
| 36 | if (pos < 0)
|
| 37 | return -EINVAL;
|
| 38 | if (pos >= size || !count)
|
| 39 | return 0;
|
| 40 |
|
| 41 | if (count > size - pos)
|
| 42 | count = size - pos;
|
| 43 |
|
| 44 |
|
| 45 | pos = ALIGN_DOWN(pos, 4);
|
| 46 |
|
| 47 |
|
| 48 | size = ALIGN(count, 4);
|
| 49 |
|
| 50 |
|
| 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 | #else
|
| 78 |
|
| 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 | #endif
|
| 89 | } else {
|
| 90 | memcpy(buf, ((u8 *)(dfse->buf) + pos), size);
|
| 91 | }
|
| 92 |
|
| 93 |
|
| 94 | size_ret = copy_to_user(buffer, buf + skip, count);
|
| 95 |
|
| 96 | kfree(buf);
|
| 97 |
|
| 98 |
|
| 99 | if (size_ret)
|
| 100 | return -EFAULT;
|
| 101 |
|
| 102 | *ppos = pos + count;
|
| 103 |
|
| 104 | return count;
|
| 105 | }
|
| 106 |
|
| 107 | static const struct file_operations sof_dfs_fops = {
|
| 108 | .open = simple_open,
|
| 109 | .read = sof_dfsentry_read,
|
| 110 | .llseek = default_llseek,
|
| 111 | };
|
| 112 |
|
| 113 |
|
| 114 | static 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 window
|
| 137 | * contents prior to suspend
|
| 138 |
|
| 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 | #endif
|
| 145 |
|
| 146 | debugfs_create_file(name, 0444, sdev->debugfs_root, dfse,
|
| 147 | &sof_dfs_fops);
|
| 148 |
|
| 149 |
|
| 150 | list_add(&dfse->list, &sdev->dfsentry_list);
|
| 151 |
|
| 152 | return 0;
|
| 153 | }
|
| 154 |
|
| 155 | int 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 | }
|
| 168 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem);
|
| 169 |
|
| 170 |
|
| 171 | int 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 |
|
| 192 | list_add(&dfse->list, &sdev->dfsentry_list);
|
| 193 |
|
| 194 | return 0;
|
| 195 | }
|
| 196 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);
|
| 197 |
|
| 198 | static 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 |
|
| 220 | ret = snd_sof_boot_dsp_firmware(sdev);
|
| 221 | if (!ret)
|
| 222 | ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply,
|
| 223 | SOF_IPC_MSG_MAX_SIZE);
|
| 224 |
|
| 225 | pm_runtime_put_autosuspend(sdev->dev);
|
| 226 | if (ret < 0 || reply->rhdr.error < 0) {
|
| 227 | ret = min(ret, reply->rhdr.error);
|
| 228 | dev_err(sdev->dev, "error: reading memory info failed, %d\n", ret);
|
| 229 | goto error;
|
| 230 | }
|
| 231 |
|
| 232 | if (struct_size(reply, elems, reply->num_elems) != reply->rhdr.hdr.size) {
|
| 233 | dev_err(sdev->dev, "error: invalid memory info ipc struct size, %d\n",
|
| 234 | reply->rhdr.hdr.size);
|
| 235 | ret = -EINVAL;
|
| 236 | goto error;
|
| 237 | }
|
| 238 |
|
| 239 | for (i = 0, len = 0; i < reply->num_elems; i++) {
|
| 240 | ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",
|
| 241 | reply->elems[i].zone, reply->elems[i].id,
|
| 242 | reply->elems[i].used, reply->elems[i].free);
|
| 243 | if (ret < 0)
|
| 244 | goto error;
|
| 245 | len += ret;
|
| 246 | }
|
| 247 |
|
| 248 | ret = len;
|
| 249 | error:
|
| 250 | kfree(reply);
|
| 251 | return ret;
|
| 252 | }
|
| 253 |
|
| 254 | static ssize_t memory_info_read(struct file *file, char __user *to, size_t count, loff_t *ppos)
|
| 255 | {
|
| 256 | struct snd_sof_dfsentry *dfse = file->private_data;
|
| 257 | struct snd_sof_dev *sdev = dfse->sdev;
|
| 258 | int data_length;
|
| 259 |
|
| 260 |
|
| 261 | if (!*ppos) {
|
| 262 | dfse->buf_data_size = 0;
|
| 263 | data_length = memory_info_update(sdev, dfse->buf, dfse->size);
|
| 264 | if (data_length < 0)
|
| 265 | return data_length;
|
| 266 | dfse->buf_data_size = data_length;
|
| 267 | }
|
| 268 |
|
| 269 | return simple_read_from_buffer(to, count, ppos, dfse->buf, dfse->buf_data_size);
|
| 270 | }
|
| 271 |
|
| 272 | static int memory_info_open(struct inode *inode, struct file *file)
|
| 273 | {
|
| 274 | struct snd_sof_dfsentry *dfse = inode->i_private;
|
| 275 | struct snd_sof_dev *sdev = dfse->sdev;
|
| 276 |
|
| 277 | file->private_data = dfse;
|
| 278 |
|
| 279 |
|
| 280 | if (!dfse->buf) {
|
| 281 | dfse->buf = devm_kmalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);
|
| 282 | if (!dfse->buf)
|
| 283 | return -ENOMEM;
|
| 284 | dfse->size = PAGE_SIZE;
|
| 285 | }
|
| 286 |
|
| 287 | return 0;
|
| 288 | }
|
| 289 |
|
| 290 | static const struct file_operations memory_info_fops = {
|
| 291 | .open = memory_info_open,
|
| 292 | .read = memory_info_read,
|
| 293 | .llseek = default_llseek,
|
| 294 | };
|
| 295 |
|
| 296 | int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev)
|
| 297 | {
|
| 298 | struct snd_sof_dfsentry *dfse;
|
| 299 |
|
| 300 | dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
|
| 301 | if (!dfse)
|
| 302 | return -ENOMEM;
|
| 303 |
|
| 304 |
|
| 305 | dfse->type = SOF_DFSENTRY_TYPE_BUF;
|
| 306 | dfse->sdev = sdev;
|
| 307 |
|
| 308 | debugfs_create_file("memory_info", 0444, sdev->debugfs_root, dfse, &memory_info_fops);
|
| 309 |
|
| 310 |
|
| 311 | list_add(&dfse->list, &sdev->dfsentry_list);
|
| 312 | return 0;
|
| 313 | }
|
| 314 | EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init);
|
| 315 |
|
| 316 | int snd_sof_dbg_init(struct snd_sof_dev *sdev)
|
| 317 | {
|
| 318 | const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
|
| 319 | struct snd_sof_pdata *plat_data = sdev->pdata;
|
| 320 | const struct snd_sof_debugfs_map *map;
|
| 321 | struct dentry *fw_profile;
|
| 322 | int i;
|
| 323 | int err;
|
| 324 |
|
| 325 |
|
| 326 | sdev->debugfs_root = debugfs_create_dir("sof", NULL);
|
| 327 |
|
| 328 |
|
| 329 | fw_profile = debugfs_create_dir("fw_profile", sdev->debugfs_root);
|
| 330 |
|
| 331 | debugfs_create_str("fw_path", 0444, fw_profile,
|
| 332 | (char **)&plat_data->fw_filename_prefix);
|
| 333 |
|
| 334 | if (plat_data->ipc_type != SOF_IPC_TYPE_3) {
|
| 335 | |
| 336 | * fw_lib_prefix can be NULL if the vendor/platform does not
|
| 337 | * support loadable libraries
|
| 338 |
|
| 339 | if (plat_data->fw_lib_prefix) {
|
| 340 | debugfs_create_str("fw_lib_path", 0444, fw_profile,
|
| 341 | (char **)&plat_data->fw_lib_prefix);
|
| 342 | } else {
|
| 343 | static char *fw_lib_path;
|
| 344 |
|
| 345 | fw_lib_path = devm_kasprintf(sdev->dev, GFP_KERNEL,
|
| 346 | "Not supported");
|
| 347 | if (!fw_lib_path)
|
| 348 | return -ENOMEM;
|
| 349 |
|
| 350 | debugfs_create_str("fw_lib_path", 0444, fw_profile,
|
| 351 | (char **)&fw_lib_path);
|
| 352 | }
|
| 353 | }
|
| 354 | debugfs_create_str("tplg_path", 0444, fw_profile,
|
| 355 | (char **)&plat_data->tplg_filename_prefix);
|
| 356 | debugfs_create_str("fw_name", 0444, fw_profile,
|
| 357 | (char **)&plat_data->fw_filename);
|
| 358 | debugfs_create_str("tplg_name", 0444, fw_profile,
|
| 359 | (char **)&plat_data->tplg_filename);
|
| 360 | debugfs_create_u32("ipc_type", 0444, fw_profile,
|
| 361 | (u32 *)&plat_data->ipc_type);
|
| 362 |
|
| 363 |
|
| 364 | INIT_LIST_HEAD(&sdev->dfsentry_list);
|
| 365 |
|
| 366 |
|
| 367 | for (i = 0; i < ops->debug_map_count; i++) {
|
| 368 | map = &ops->debug_map[i];
|
| 369 |
|
| 370 | err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] +
|
| 371 | map->offset, map->size,
|
| 372 | map->name, map->access_type);
|
| 373 |
|
| 374 | if (err < 0)
|
| 375 | return err;
|
| 376 | }
|
| 377 |
|
| 378 | return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state,
|
| 379 | sizeof(sdev->fw_state),
|
| 380 | "fw_state", 0444);
|
| 381 | }
|
| 382 | EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
|
| 383 |
|
| 384 | void snd_sof_free_debug(struct snd_sof_dev *sdev)
|
| 385 | {
|
| 386 | debugfs_remove_recursive(sdev->debugfs_root);
|
| 387 | }
|
| 388 | EXPORT_SYMBOL_GPL(snd_sof_free_debug);
|
| 389 |
|
| 390 | static const struct soc_fw_state_info {
|
| 391 | enum sof_fw_state state;
|
| 392 | const char *name;
|
| 393 | } fw_state_dbg[] = {
|
| 394 | {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"},
|
| 395 | {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"},
|
| 396 | {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"},
|
| 397 | {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"},
|
| 398 | {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"},
|
| 399 | {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"},
|
| 400 | {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"},
|
| 401 | {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"},
|
| 402 | {SOF_FW_CRASHED, "SOF_FW_CRASHED"},
|
| 403 | };
|
| 404 |
|
| 405 | static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level)
|
| 406 | {
|
| 407 | int i;
|
| 408 |
|
| 409 | for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) {
|
| 410 | if (sdev->fw_state == fw_state_dbg[i].state) {
|
| 411 | dev_printk(level, sdev->dev, "fw_state: %s (%d)\n",
|
| 412 | fw_state_dbg[i].name, i);
|
| 413 | return;
|
| 414 | }
|
| 415 | }
|
| 416 |
|
| 417 | dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state);
|
| 418 | }
|
| 419 |
|
| 420 | void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags)
|
| 421 | {
|
| 422 | char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
|
| 423 | bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS);
|
| 424 |
|
| 425 | if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all)
|
| 426 | return;
|
| 427 |
|
| 428 | if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) {
|
| 429 | dev_printk(level, sdev->dev,
|
| 430 | "------------[ DSP dump start ]------------\n");
|
| 431 | if (msg)
|
| 432 | dev_printk(level, sdev->dev, "%s\n", msg);
|
| 433 | snd_sof_dbg_print_fw_state(sdev, level);
|
| 434 | sof_ops(sdev)->dbg_dump(sdev, flags);
|
| 435 | dev_printk(level, sdev->dev,
|
| 436 | "------------[ DSP dump end ]------------\n");
|
| 437 | if (!print_all)
|
| 438 | sdev->dbg_dump_printed = true;
|
| 439 | } else if (msg) {
|
| 440 | dev_printk(level, sdev->dev, "%s\n", msg);
|
| 441 | }
|
| 442 | }
|
| 443 | EXPORT_SYMBOL(snd_sof_dsp_dbg_dump);
|
| 444 |
|
| 445 | static void snd_sof_ipc_dump(struct snd_sof_dev *sdev)
|
| 446 | {
|
| 447 | if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) {
|
| 448 | dev_err(sdev->dev, "------------[ IPC dump start ]------------\n");
|
| 449 | sof_ops(sdev)->ipc_dump(sdev);
|
| 450 | dev_err(sdev->dev, "------------[ IPC dump end ]------------\n");
|
| 451 | if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS))
|
| 452 | sdev->ipc_dump_printed = true;
|
| 453 | }
|
| 454 | }
|
| 455 |
|
| 456 | void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg)
|
| 457 | {
|
| 458 | if ((IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
|
| 459 | sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) && !sdev->d3_prevented) {
|
| 460 |
|
| 461 | if (!sdev->ipc_dump_printed)
|
| 462 | dev_info(sdev->dev,
|
| 463 | "Attempting to prevent DSP from entering D3 state to preserve context\n");
|
| 464 |
|
| 465 | if (pm_runtime_get_if_in_use(sdev->dev) == 1)
|
| 466 | sdev->d3_prevented = true;
|
| 467 | }
|
| 468 |
|
| 469 |
|
| 470 | snd_sof_ipc_dump(sdev);
|
| 471 | snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
|
| 472 | sof_fw_trace_fw_crashed(sdev);
|
| 473 | }
|
| 474 | EXPORT_SYMBOL(snd_sof_handle_fw_exception);
|