// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA SoC - Samsung Abox Debug driver * * Copyright (c) 2016 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include "abox_util.h" #include "abox_proc.h" #include "abox_gic.h" #include "abox_core.h" #include "abox_oem.h" #include "abox_dbg.h" #include "abox_memlog.h" #define ABOX_DBG_DUMP_MAGIC_SRAM 0x3935303030504D44ull /* DMP00059 */ #define ABOX_DBG_DUMP_MAGIC_DRAM 0x3231303038504D44ull /* DMP80012 */ #define ABOX_DBG_DUMP_MAGIC_LOG 0x3142303038504D44ull /* DMP800B1 */ #define ABOX_DBG_DUMP_MAGIC_SFR 0x5246533030504D44ull /* DMP00SFR */ #define ABOX_DBG_DUMP_MAGIC_ATUNE 0x5554413030504D44ull /* DMP00ATU */ #define ABOX_DBG_DUMP_LIMIT_NS (5 * NSEC_PER_SEC) static struct mutex lock; void abox_dbg_print_gpr_from_addr(struct device *dev, struct abox_data *data, unsigned int *addr) { abox_core_print_gpr_dump(addr); } void abox_dbg_print_gpr(struct device *dev, struct abox_data *data) { abox_core_print_gpr(); } struct abox_dbg_dump_sram { unsigned long long magic; char dump[SRAM_FIRMWARE_SIZE]; } __packed; struct abox_dbg_dump_dram { unsigned long long magic; char dump[DRAM_FIRMWARE_SIZE]; } __packed; struct abox_dbg_dump_log { unsigned long long magic; char dump[ABOX_LOG_SIZE]; } __packed; struct abox_dbg_dump_sfr { unsigned long long magic; u32 dump[SZ_64K / sizeof(u32)]; } __packed; struct abox_dbg_dump { struct abox_dbg_dump_sram sram; struct abox_dbg_dump_dram dram; struct abox_dbg_dump_sfr sfr; u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; unsigned int gpr[SZ_128]; struct abox_dbg_dump_sfr atune; long long time; char reason[SZ_32]; unsigned int previous_gpr; unsigned int previous_mem; } __packed; struct abox_dbg_dump_min { struct abox_dbg_dump_sram sram; struct abox_dbg_dump_log log; struct abox_dbg_dump_log log_01; struct abox_dbg_dump_sfr sfr; u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; unsigned int gpr[SZ_128]; struct abox_dbg_dump_sfr atune; long long time; char reason[SZ_32]; unsigned int previous_gpr; unsigned int previous_mem; } __packed; struct abox_dbg_dump_info { struct abox_proc_bin sram; struct abox_proc_bin dram; struct abox_proc_bin log; struct abox_proc_bin log_01; struct abox_proc_bin sfr; struct abox_proc_bin gicd; struct abox_proc_bin gpr; struct abox_proc_bin reason; }; static struct abox_dbg_dump (*p_abox_dbg_dump)[ABOX_DBG_DUMP_COUNT]; static struct abox_dbg_dump_min (*p_abox_dbg_dump_min)[ABOX_DBG_DUMP_COUNT]; static struct abox_dbg_dump_info abox_dbg_dump_info[ABOX_DBG_DUMP_COUNT]; /* revisited free_reserved_area() of /mm/page_alloc.c */ static unsigned long __free_reserved_area(phys_addr_t start, phys_addr_t end, const char *s) { phys_addr_t pos; unsigned long pages = 0; start = PAGE_ALIGN(start); end &= PAGE_MASK; for (pos = start; pos < end; pos += PAGE_SIZE, pages++) free_reserved_page(phys_to_page(pos)); if (pages && s) pr_info("Freeing %s memory: %ldK\n", s, pages << (PAGE_SHIFT - 10)); return pages; } static void abox_dbg_resize_rmem(struct device *dev, struct reserved_mem *rmem, size_t new_size, const char *tag) { size_t old_size = rmem->size; if (old_size < new_size) { abox_warn(dev, "%s: new size %#zx is bigger than reserved size %#zx\n", tag, new_size, old_size); return; } rmem->size = new_size; __free_reserved_area(rmem->base + new_size, rmem->base + old_size, tag); abox_info(dev, "%s: %s new size %#lx\n", __func__, tag, rmem->size); } static struct reserved_mem *abox_dbg_slog; static int __init abox_dbg_slog_setup(struct reserved_mem *rmem) { pr_info("%s: size=%pa\n", __func__, &rmem->size); abox_dbg_slog = rmem; return 0; } RESERVEDMEM_OF_DECLARE(abox_dbg_slog, "exynos,abox_slog", abox_dbg_slog_setup); static void abox_dbg_slog_init(struct abox_data *data) { struct device *dev_abox = data->dev; abox_info(dev_abox, "%s: size=%pa\n", __func__, &abox_dbg_slog->size); data->slog_phys = abox_dbg_slog->base; data->slog_size = abox_dbg_slog->size; data->slog_base = rmem_vmap(abox_dbg_slog); abox_iommu_map(dev_abox, IOVA_SILENT_LOG, data->slog_phys, data->slog_size, data->slog_base); } static struct reserved_mem *abox_dbg_rmem; static int __init abox_dbg_rmem_setup(struct reserved_mem *rmem) { pr_info("%s: size=%pa\n", __func__, &rmem->size); abox_dbg_rmem = rmem; return 0; } RESERVEDMEM_OF_DECLARE(abox_dbg_rmem, "exynos,abox_dbg", abox_dbg_rmem_setup); static bool abox_dbg_dump_valid(int idx) { bool ret = false; if (idx >= ABOX_DBG_DUMP_COUNT) return false; if (p_abox_dbg_dump) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[idx]; ret = (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR); } else if (p_abox_dbg_dump_min) { struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[idx]; ret = (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR); } return ret; } static void abox_dbg_clear_valid(int idx) { if (idx >= ABOX_DBG_DUMP_COUNT) return; if (p_abox_dbg_dump) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[idx]; p_dump->sfr.magic = 0; } else if (p_abox_dbg_dump_min) { struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[idx]; p_dump->sfr.magic = 0; } } static ssize_t abox_dbg_read_valid(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { int idx = (int)(long)abox_proc_data(file); bool valid = abox_dbg_dump_valid(idx); char buf_val[4]; /* enough to store a bool and "\n\0" */ if (valid) buf_val[0] = 'Y'; else buf_val[0] = 'N'; buf_val[1] = '\n'; buf_val[2] = 0x00; return simple_read_from_buffer(user_buf, count, ppos, buf_val, 2); } static const struct proc_ops abox_dbg_fops_valid = { .proc_open = simple_open, .proc_read = abox_dbg_read_valid, .proc_lseek = default_llseek, }; static ssize_t abox_dbg_read_clear(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { int idx = (int)(long)abox_proc_data(file); abox_dbg_clear_valid(idx); return 0; } static ssize_t abox_dbg_write_clear(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int idx = (int)(long)abox_proc_data(file); abox_dbg_clear_valid(idx); return 0; } static const struct proc_ops abox_dbg_fops_clear = { .proc_open = simple_open, .proc_read = abox_dbg_read_clear, .proc_write = abox_dbg_write_clear, .proc_lseek = no_llseek, }; static void dump_gpr_from_addr_full(struct device *dev, unsigned int *addr, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr_dump(p_dump->gpr, addr); } static void dump_gpr_from_addr_half(struct device *dev, unsigned int *addr, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0]; if (src == ABOX_DBG_DUMP_KERNEL) { if (abox_dbg_dump_valid(0) && p_dump->previous_gpr == 0) { abox_info(dev, "%s(%d): skipped\n", __func__, src); return; } } p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr_dump(p_dump->gpr, addr); } static void dump_gpr_from_addr_min(struct device *dev, unsigned int *addr, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr_dump(p_dump->gpr, addr); } static void dump_gpr_full(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr(p_dump->gpr); } static void dump_gpr_half(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0]; if (src == ABOX_DBG_DUMP_KERNEL) { if (abox_dbg_dump_valid(0) && p_dump->previous_gpr == 0) { abox_info(dev, "%s(%d): skipped\n", __func__, src); return; } } p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr(p_dump->gpr); } static void dump_gpr_min(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; p_dump->time = time; p_dump->previous_gpr = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); abox_core_dump_gpr(p_dump->gpr); } static void dump_mem_full(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; p_dump->time = time; p_dump->previous_mem = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); memcpy_fromio(p_dump->sram.dump, data->sram_base, SRAM_FIRMWARE_SIZE); p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM; memcpy(p_dump->dram.dump, data->dram_base, DRAM_FIRMWARE_SIZE); p_dump->dram.magic = ABOX_DBG_DUMP_MAGIC_DRAM; memcpy_fromio(p_dump->sfr.dump, data->sfr_base, sizeof(p_dump->sfr.dump)); p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR; memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET, sizeof(p_dump->atune.dump)); p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE; abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0, sizeof(p_dump->sfr_gic_gicd)); } static void dump_mem_half(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0]; if (src == ABOX_DBG_DUMP_KERNEL) { if (abox_dbg_dump_valid(0) && p_dump->previous_mem == 0) { abox_info(dev, "%s(%d): skipped\n", __func__, src); return; } } p_dump->time = time; p_dump->previous_mem = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); memcpy_fromio(p_dump->sram.dump, data->sram_base, SRAM_FIRMWARE_SIZE); p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM; memcpy(p_dump->dram.dump, data->dram_base, DRAM_FIRMWARE_SIZE); p_dump->dram.magic = ABOX_DBG_DUMP_MAGIC_DRAM; memcpy_fromio(p_dump->sfr.dump, data->sfr_base, sizeof(p_dump->sfr.dump)); p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR; memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET, sizeof(p_dump->atune.dump)); p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE; abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0, sizeof(p_dump->sfr_gic_gicd)); } static void dump_mem_min(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time) { struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; p_dump->time = time; p_dump->previous_mem = 0; strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); memcpy_fromio(p_dump->sram.dump, data->sram_base, SRAM_FIRMWARE_SIZE); p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM; memcpy(p_dump->log.dump, data->dram_base + ABOX_LOG_OFFSET, ABOX_LOG_SIZE); p_dump->log.magic = ABOX_DBG_DUMP_MAGIC_LOG; memcpy(p_dump->log_01.dump, data->dram_base + ABOX_LOG_MAJOR_OFFSET, ABOX_LOG_SIZE); p_dump->log_01.magic = ABOX_DBG_DUMP_MAGIC_LOG; memcpy_fromio(p_dump->sfr.dump, data->sfr_base, sizeof(p_dump->sfr.dump)); p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR; memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET, sizeof(p_dump->atune.dump)); p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE; abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0, sizeof(p_dump->sfr_gic_gicd)); } static int abox_dbg_dump_count; static void (*p_dump_gpr_from_addr)(struct device *dev, unsigned int *addr, enum abox_dbg_dump_src src, const char *reason, unsigned long long time); static void (*p_dump_gpr)(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time); static void (*p_dump_mem)(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, unsigned long long time); static int abox_dbg_dump_create_file(struct abox_data *data) { const char *dir_fmt = "snapshot_%d"; struct proc_dir_entry *dir; struct abox_dbg_dump_info *info; char *dir_name; int i; abox_dbg(data->dev, "%s\n", __func__); if (p_abox_dbg_dump) { struct abox_dbg_dump *p_dump; for (i = 0; i < abox_dbg_dump_count; i++) { dir_name = kasprintf(GFP_KERNEL, dir_fmt, i); dir = abox_proc_mkdir(dir_name, NULL); p_dump = &(*p_abox_dbg_dump)[i]; info = &abox_dbg_dump_info[i]; info->sram.data = p_dump->sram.dump; info->sram.size = sizeof(p_dump->sram.dump); abox_proc_create_bin("sram", 0440, dir, &info->sram); info->dram.data = p_dump->dram.dump; info->dram.size = sizeof(p_dump->dram.dump); abox_proc_create_bin("dram", 0440, dir, &info->dram); info->log.data = p_dump->dram.dump + ABOX_LOG_OFFSET; info->log.size = ABOX_LOG_SIZE; abox_proc_create_bin("log", 0444, dir, &info->log); info->sfr.data = p_dump->sfr.dump; info->sfr.size = sizeof(p_dump->sfr.dump); abox_proc_create_bin("sfr", 0440, dir, &info->sfr); info->gicd.data = p_dump->sfr_gic_gicd; info->gicd.size = sizeof(p_dump->sfr_gic_gicd); abox_proc_create_bin("gicd", 0440, dir, &info->gicd); info->gpr.data = p_dump->gpr; info->gpr.size = sizeof(p_dump->gpr); abox_proc_create_bin("gpr", 0440, dir, &info->gpr); abox_proc_create_u64("time", 0440, dir, &p_dump->time); info->reason.data = p_dump->reason; info->reason.size = sizeof(p_dump->reason); abox_proc_create_bin("reason", 0440, dir, &info->reason); abox_proc_create_u32("previous", 0440, dir, &p_dump->previous_mem); abox_proc_create_file("valid", 0440, dir, &abox_dbg_fops_valid, (void *)(long)i, 0); abox_proc_create_file("clear", 0440, dir, &abox_dbg_fops_clear, (void *)(long)i, 0); kfree(dir_name); } } else if (p_abox_dbg_dump_min) { struct abox_dbg_dump_min *p_dump; for (i = 0; i < abox_dbg_dump_count; i++) { dir_name = kasprintf(GFP_KERNEL, dir_fmt, i); dir = abox_proc_mkdir(dir_name, NULL); p_dump = &(*p_abox_dbg_dump_min)[i]; info = &abox_dbg_dump_info[i]; info->sram.data = p_dump->sram.dump; info->sram.size = sizeof(p_dump->sram.dump); abox_proc_create_bin("sram", 0440, dir, &info->sram); info->log.data = p_dump->log.dump; info->log.size = ABOX_LOG_SIZE; abox_proc_create_bin("log", 0444, dir, &info->log); info->log_01.data = p_dump->log_01.dump; info->log_01.size = ABOX_LOG_SIZE; abox_proc_create_bin("log-01", 0444, dir, &info->log_01); info->sfr.data = p_dump->sfr.dump; info->sfr.size = sizeof(p_dump->sfr.dump); abox_proc_create_bin("sfr", 0440, dir, &info->sfr); info->gicd.data = p_dump->sfr_gic_gicd; info->gicd.size = sizeof(p_dump->sfr_gic_gicd); abox_proc_create_bin("gicd", 0440, dir, &info->gicd); info->gpr.data = p_dump->gpr; info->gpr.size = sizeof(p_dump->gpr); abox_proc_create_bin("gpr", 0440, dir, &info->gpr); abox_proc_create_u64("time", 0440, dir, &p_dump->time); info->reason.data = p_dump->reason; info->reason.size = sizeof(p_dump->reason); abox_proc_create_bin("reason", 0440, dir, &info->reason); abox_proc_create_u32("previous", 0440, dir, &p_dump->previous_mem); abox_proc_create_file("valid", 0440, dir, &abox_dbg_fops_valid, (void *)(long)i, 0); abox_proc_create_file("clear", 0440, dir, &abox_dbg_fops_clear, (void *)(long)i, 0); kfree(dir_name); } } else { return -ENOMEM; } return 0; } static void abox_dbg_rmem_init(struct abox_data *data) { struct device *dev_abox = data->dev; int i; abox_info(dev_abox, "%s: size=%pa\n", __func__, &abox_dbg_rmem->size); if (sizeof(*p_abox_dbg_dump) <= abox_dbg_rmem->size) { struct abox_dbg_dump *p_dump; p_abox_dbg_dump = rmem_vmap(abox_dbg_rmem); data->dump_base = p_abox_dbg_dump; for (i = 0; i < ABOX_DBG_DUMP_COUNT; i++) { p_dump = &(*p_abox_dbg_dump)[i]; if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) { p_dump->previous_gpr++; p_dump->previous_mem++; } else { p_dump->previous_gpr = 0; p_dump->previous_mem = 0; } } abox_dbg_dump_count = ABOX_DBG_DUMP_COUNT; p_dump_gpr_from_addr = dump_gpr_from_addr_full; p_dump_gpr = dump_gpr_full; p_dump_mem = dump_mem_full; abox_info(dev_abox, "%s debug dump\n", "full"); } else if (sizeof((*p_abox_dbg_dump)[0]) <= abox_dbg_rmem->size) { struct abox_dbg_dump *p_dump; p_abox_dbg_dump = rmem_vmap(abox_dbg_rmem); data->dump_base = p_abox_dbg_dump; p_dump = &(*p_abox_dbg_dump)[0]; if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) { p_dump->previous_gpr++; p_dump->previous_mem++; } else { p_dump->previous_gpr = 0; p_dump->previous_mem = 0; } abox_dbg_dump_count = 1; p_dump_gpr_from_addr = dump_gpr_from_addr_half; p_dump_gpr = dump_gpr_half; p_dump_mem = dump_mem_half; abox_info(dev_abox, "%s debug dump\n", "half"); } else if (sizeof(*p_abox_dbg_dump_min) <= abox_dbg_rmem->size) { struct abox_dbg_dump_min *p_dump; p_abox_dbg_dump_min = rmem_vmap(abox_dbg_rmem); data->dump_base = p_abox_dbg_dump_min; for (i = 0; i < ABOX_DBG_DUMP_COUNT; i++) { p_dump = &(*p_abox_dbg_dump_min)[i]; if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) { p_dump->previous_gpr++; p_dump->previous_mem++; } else { p_dump->previous_gpr = 0; p_dump->previous_mem = 0; } } abox_dbg_dump_count = ABOX_DBG_DUMP_COUNT; p_dump_gpr_from_addr = dump_gpr_from_addr_min; p_dump_gpr = dump_gpr_min; p_dump_mem = dump_mem_min; mutex_init(&lock); abox_info(dev_abox, "%s debug dump\n", "min"); } abox_dbg_dump_create_file(data); data->dump_phys = abox_dbg_rmem->base; abox_iommu_map(dev_abox, IOVA_DUMP_BUFFER, abox_dbg_rmem->base, abox_dbg_rmem->size, data->dump_base); } void abox_dbg_dump_gpr_from_addr(struct device *dev, struct abox_data *data, unsigned int *addr, enum abox_dbg_dump_src src, const char *reason) { static unsigned long long called[ABOX_DBG_DUMP_COUNT]; unsigned long long time = sched_clock(); abox_dbg(dev, "%s\n", __func__); if (pm_runtime_suspended(data->dev)) { abox_info(dev, "%s is skipped due to no power\n", __func__); return; } if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) { dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src); called[src] = time; return; } called[src] = time; if (p_dump_gpr_from_addr) p_dump_gpr_from_addr(dev, addr, src, reason, time); else abox_err(dev, "abox dbg isn't initialized\n"); } void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason) { static unsigned long long called[ABOX_DBG_DUMP_COUNT]; unsigned long long time = sched_clock(); abox_dbg(dev, "%s\n", __func__); if (pm_runtime_suspended(data->dev)) { abox_info(dev, "%s is skipped due to no power\n", __func__); return; } if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) { dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src); called[src] = time; return; } called[src] = time; if (p_dump_gpr) p_dump_gpr(dev, data, src, reason, time); else abox_err(dev, "abox dbg isn't initialized\n"); } struct dump_mem_work_struct { struct work_struct work; struct device *dev; struct abox_data *data; enum abox_dbg_dump_src src; const char *reason; unsigned long long time; }; static void dump_mem_work_func(struct work_struct *work) { struct dump_mem_work_struct *dmw; dmw = container_of(work, typeof(*dmw), work); p_dump_mem(dmw->dev, dmw->data, dmw->src, dmw->reason, dmw->time); } static void dump_mem(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason, bool atomic) { static unsigned long long called[ABOX_DBG_DUMP_COUNT]; unsigned long long time = sched_clock(); abox_dbg(dev, "%s\n", __func__); if (pm_runtime_suspended(data->dev)) { abox_info(dev, "%s is skipped due to no power\n", __func__); return; } if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) { dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src); called[src] = time; return; } called[src] = time; if (p_dump_mem) { if (atomic) { p_dump_mem(dev, data, src, reason, time); } else { struct dump_mem_work_struct dmw; INIT_WORK_ONSTACK(&dmw.work, dump_mem_work_func); dmw.dev = dev; dmw.data = data; dmw.src = src; dmw.reason = reason; dmw.time = time; queue_work(system_unbound_wq, &dmw.work); flush_work(&dmw.work); destroy_work_on_stack(&dmw.work); } } else { abox_err(dev, "abox dbg isn't initialized\n"); } } void abox_dbg_dump_mem(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason) { dump_mem(dev, data, src, reason, true); } void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data, enum abox_dbg_dump_src src, const char *reason) { abox_dbg_dump_gpr(dev, data, src, reason); dump_mem(dev, data, src, reason, false); } struct abox_dbg_dump_simple { struct abox_dbg_dump_sram sram; struct abox_dbg_dump_log log; struct abox_dbg_dump_sfr sfr; u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; unsigned int gpr[SZ_128]; struct abox_dbg_dump_sfr atune; long long time; char reason[SZ_32]; } __packed; static struct abox_dbg_dump_simple abox_dump_simple; void abox_dbg_dump_simple(struct device *dev, struct abox_data *data, const char *reason) { static unsigned long long called; unsigned long long time = sched_clock(); abox_info(dev, "%s\n", __func__); if (pm_runtime_suspended(data->dev)) { abox_info(dev, "%s is skipped due to no power\n", __func__); return; } if (called && time - called < ABOX_DBG_DUMP_LIMIT_NS) { dev_dbg_ratelimited(dev, "%s: skipped\n", __func__); called = time; return; } called = time; abox_dump_simple.time = time; strncpy(abox_dump_simple.reason, reason, sizeof(abox_dump_simple.reason) - 1); abox_core_dump_gpr(abox_dump_simple.gpr); memcpy_fromio(abox_dump_simple.sram.dump, data->sram_base, SRAM_FIRMWARE_SIZE); abox_dump_simple.sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM; memcpy(abox_dump_simple.log.dump, data->dram_base + ABOX_LOG_OFFSET, ABOX_LOG_SIZE); abox_dump_simple.log.magic = ABOX_DBG_DUMP_MAGIC_LOG; memcpy_fromio(abox_dump_simple.sfr.dump, data->sfr_base, sizeof(abox_dump_simple.sfr.dump)); abox_dump_simple.sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR; memcpy_fromio(abox_dump_simple.atune.dump, data->sfr_base + ATUNE_OFFSET, sizeof(abox_dump_simple.atune.dump)); abox_dump_simple.atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE; abox_gicd_dump(data->dev_gic, (char *)abox_dump_simple.sfr_gic_gicd, 0, sizeof(abox_dump_simple.sfr_gic_gicd)); } static struct abox_dbg_dump_simple abox_dump_suspend; void abox_dbg_dump_suspend(struct device *dev, struct abox_data *data) { abox_dbg(dev, "%s\n", __func__); abox_dump_suspend.time = sched_clock(); strncpy(abox_dump_suspend.reason, "suspend", sizeof(abox_dump_suspend.reason) - 1); abox_core_dump_gpr(abox_dump_suspend.gpr); memcpy_fromio(abox_dump_suspend.sram.dump, data->sram_base, SRAM_FIRMWARE_SIZE); abox_dump_suspend.sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM; memcpy(abox_dump_suspend.log.dump, data->dram_base + ABOX_LOG_OFFSET, ABOX_LOG_SIZE); abox_dump_suspend.log.magic = ABOX_DBG_DUMP_MAGIC_LOG; memcpy_fromio(abox_dump_suspend.sfr.dump, data->sfr_base, sizeof(abox_dump_suspend.sfr.dump)); abox_dump_suspend.sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR; memcpy_fromio(abox_dump_suspend.atune.dump, data->sfr_base + ATUNE_OFFSET, sizeof(abox_dump_suspend.atune.dump)); abox_dump_suspend.atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE; abox_gicd_dump(data->dev_gic, (char *)abox_dump_suspend.sfr_gic_gicd, 0, sizeof(abox_dump_suspend.sfr_gic_gicd)); } static atomic_t abox_error_count = ATOMIC_INIT(0); void abox_dbg_report_status(struct device *dev, bool ok) { char env[32] = {0,}; char *envp[2] = {env, NULL}; abox_info(dev, "%s\n", __func__); if (ok) atomic_set(&abox_error_count, 0); else atomic_inc(&abox_error_count); snprintf(env, sizeof(env), "ERR_CNT=%d", atomic_read(&abox_error_count)); kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); } int abox_dbg_get_error_count(struct device *dev) { int count = atomic_read(&abox_error_count); abox_dbg(dev, "%s: %d\n", __func__, count); return count; } static ssize_t calliope_sram_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t off, size_t size) { struct device *dev = kobj_to_dev(kobj); struct device *dev_abox = dev->parent; abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); if (!pm_runtime_suspended(dev_abox)) { pm_runtime_get(dev_abox); if (off == 0) abox_core_flush(); memcpy_fromio(buf, battr->private + off, size); pm_runtime_put(dev_abox); } else { memset(buf, 0x0, size); } return size; } static ssize_t calliope_dram_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t off, size_t size) { struct device *dev = kobj_to_dev(kobj); struct device *dev_abox = dev->parent; abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); /* if the area isn't existed, private(=base address) is 0 */ if (!battr->private) return 0; if (!pm_runtime_suspended(dev_abox)) { pm_runtime_get(dev_abox); if (off == 0) abox_core_flush(); pm_runtime_put(dev_abox); } memcpy(buf, battr->private + off, size); return size; } static ssize_t calliope_log_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t off, size_t size) { return calliope_dram_read(file, kobj, battr, buf, off, size); } static ssize_t calliope_slog_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t off, size_t size) { return calliope_dram_read(file, kobj, battr, buf, off, size); } static ssize_t gicd_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t off, size_t size) { struct device *dev = kobj_to_dev(kobj); struct device *dev_abox = dev->parent; struct abox_data *data = dev_get_drvdata(dev_abox); abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); pm_runtime_get(dev_abox); abox_gicd_dump(data->dev_gic, buf, off, size); pm_runtime_put(dev_abox); return size; } /* size will be updated later */ static BIN_ATTR(calliope_sram, 0440, calliope_sram_read, NULL, 0); static BIN_ATTR(calliope_dram, 0440, calliope_dram_read, NULL, DRAM_FIRMWARE_SIZE); static BIN_ATTR_RO(calliope_log, ABOX_LOG_SIZE); static BIN_ATTR(calliope_slog, 0440, calliope_slog_read, NULL, 0); static BIN_ATTR(gicd, 0440, gicd_read, NULL, SZ_4K); static struct bin_attribute *calliope_bin_attrs[] = { &bin_attr_calliope_sram, &bin_attr_calliope_dram, &bin_attr_calliope_log, &bin_attr_calliope_slog, &bin_attr_gicd, }; static ssize_t gpr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct device *dev_abox = dev->parent; if (pm_runtime_suspended(dev_abox)) { abox_info(dev, "%s is skipped due to no power\n", __func__); return -EFAULT; } return abox_core_show_gpr(buf); } static DEVICE_ATTR(gpr, 0440, gpr_show, NULL); struct abox_dbg_file_info { struct proc_dir_entry *file; struct abox_data *abox_data; const char *name; const struct proc_ops *fops; void __iomem *io_base; void *base; void *buf; size_t size; }; static ssize_t abox_dbg_file_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { struct abox_dbg_file_info *info = abox_proc_data(file); return simple_read_from_buffer(buf, count, pos, info->buf, info->size); } static int abox_dbg_file_open_sram(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); struct device *dev_abox = info->abox_data->dev; info->buf = vmalloc(info->size); if (!info->buf) return -ENOMEM; if (pm_runtime_get_if_in_use(dev_abox) > 0) { abox_core_flush(); memcpy_fromio(info->buf, info->io_base, info->size); pm_runtime_put(dev_abox); } else { memset(info->buf, 0x0, info->size); } return simple_open(inode, file); } static int abox_dbg_file_open_dram(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); struct device *dev_abox = info->abox_data->dev; info->buf = vmalloc(info->size); if (!info->buf) return -ENOMEM; if (pm_runtime_get_if_in_use(dev_abox) > 0) { abox_core_flush(); memcpy(info->buf, info->base, info->size); pm_runtime_put(dev_abox); } else { memcpy(info->buf, info->base, info->size); } return simple_open(inode, file); } static int abox_dbg_file_open_sfr(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); struct device *dev_abox = info->abox_data->dev; info->buf = vmalloc(info->size); if (!info->buf) return -ENOMEM; if (pm_runtime_get_if_in_use(dev_abox) > 0) { memcpy_fromio(info->buf, info->io_base, info->size); pm_runtime_put(dev_abox); } else { memset(info->buf, 0x0, info->size); } return simple_open(inode, file); } static int abox_dbg_file_open_gpr(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); struct device *dev_abox = info->abox_data->dev; info->buf = vmalloc(info->size); if (!info->buf) return -ENOMEM; if (pm_runtime_get_if_in_use(dev_abox) > 0) { abox_core_show_gpr(info->buf); pm_runtime_put(dev_abox); } else { memset(info->buf, 0x0, info->size); } return simple_open(inode, file); } static int abox_dbg_file_open_gicd(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); struct device *dev_abox = info->abox_data->dev; info->buf = vmalloc(info->size); if (!info->buf) return -ENOMEM; if (pm_runtime_get_if_in_use(dev_abox) > 0) { abox_gicd_dump(info->abox_data->dev_gic, info->buf, 0, info->size); pm_runtime_put(dev_abox); } else { memset(info->buf, 0x0, info->size); } return simple_open(inode, file); } static int abox_dbg_file_release(struct inode *inode, struct file *file) { struct abox_dbg_file_info *info = abox_proc_data(file); vfree(info->buf); return 0; } static const struct proc_ops abox_dbg_file_fops_sram = { .proc_read = abox_dbg_file_read, .proc_open = abox_dbg_file_open_sram, .proc_release = abox_dbg_file_release, .proc_lseek = default_llseek, }; static const struct proc_ops abox_dbg_file_fops_dram = { .proc_read = abox_dbg_file_read, .proc_open = abox_dbg_file_open_dram, .proc_release = abox_dbg_file_release, .proc_lseek = default_llseek, }; static const struct proc_ops abox_dbg_file_fops_sfr = { .proc_read = abox_dbg_file_read, .proc_open = abox_dbg_file_open_sfr, .proc_release = abox_dbg_file_release, .proc_lseek = default_llseek, }; static const struct proc_ops abox_dbg_file_fops_gpr = { .proc_read = abox_dbg_file_read, .proc_open = abox_dbg_file_open_gpr, .proc_release = abox_dbg_file_release, .proc_lseek = default_llseek, }; static const struct proc_ops abox_dbg_file_fops_gicd = { .proc_read = abox_dbg_file_read, .proc_open = abox_dbg_file_open_gicd, .proc_release = abox_dbg_file_release, .proc_lseek = default_llseek, }; static struct abox_dbg_file_info sram_info = { .name = "sram", .fops = &abox_dbg_file_fops_sram, }; static struct abox_dbg_file_info dram_info = { .name = "dram", .fops = &abox_dbg_file_fops_dram, }; static struct abox_dbg_file_info log_info = { .name = "log", .fops = &abox_dbg_file_fops_dram, }; static struct abox_dbg_file_info slog_info = { .name = "slog", .fops = &abox_dbg_file_fops_dram, }; static struct abox_dbg_file_info sfr_info = { .name = "sfr", .fops = &abox_dbg_file_fops_sfr, }; static struct abox_dbg_file_info gpr_info = { .name = "gpr", .fops = &abox_dbg_file_fops_gpr, }; static struct abox_dbg_file_info gicd_info = { .name = "gicd", .fops = &abox_dbg_file_fops_gicd, }; static int abox_dbg_create_file(struct abox_dbg_file_info *info, struct proc_dir_entry *parent, struct abox_data *data, void __iomem *io_base, void *base, size_t size) { info->abox_data = data; info->io_base = io_base; info->base = base; info->size = size; info->file = abox_proc_create_file(info->name, 0444, parent, info->fops, info, info->size); return PTR_ERR_OR_ZERO(info->file); } static void abox_dbg_create_files(struct abox_data *data) { struct proc_dir_entry *dir; dir = abox_proc_mkdir("runtime", NULL); abox_dbg_create_file(&sram_info, dir, data, data->sram_base, NULL, SRAM_FIRMWARE_SIZE); abox_dbg_create_file(&dram_info, dir, data, NULL, data->dram_base, DRAM_FIRMWARE_SIZE); abox_dbg_create_file(&log_info, dir, data, NULL, data->dram_base + ABOX_LOG_OFFSET, ABOX_LOG_SIZE); abox_dbg_create_file(&slog_info, dir, data, NULL, data->slog_base, data->slog_size); abox_dbg_create_file(&sfr_info, dir, data, data->sfr_base, NULL, data->sfr_size); abox_dbg_create_file(&gpr_info, dir, data, NULL, NULL, PAGE_SIZE); abox_dbg_create_file(&gicd_info, dir, data, NULL, NULL, PAGE_SIZE); } struct abox_dbg_memlog_entity { const char *name; struct memlog_obj *fobj; struct memlog_obj *mobj; enum abox_region rid; size_t buf_size; void *src_buf; }; static struct abox_dbg_memlog_entity abox_dbg_memlogs[] = { { .name = "sram", .rid = ABOX_REG_SRAM, }, { .name = "dram", .rid = ABOX_REG_DRAM, .buf_size = DRAM_FIRMWARE_SIZE }, { .name = "log", .rid = ABOX_REG_LOG, .buf_size = ABOX_LOG_SIZE }, { .name = "slog", .rid = ABOX_REG_SLOG, }, { .name = "sfr", .rid = ABOX_REG_SFR, }, { .name = "gpr", .rid = ABOX_REG_GPR, .buf_size = SZ_128 }, { .name = "gicd", .rid = ABOX_REG_GICD, .buf_size = (SZ_4K / sizeof(u32))}, }; static void abox_dbg_unregister_memlog(struct abox_data *data) { struct device *dev = data->dev; size_t len = ARRAY_SIZE(abox_dbg_memlogs); struct abox_dbg_memlog_entity *me; for (me = abox_dbg_memlogs; me - abox_dbg_memlogs < len; me++) { if (me->fobj) memlog_free(me->fobj); if (me->rid < ABOX_REG_DEVICE_LAST) { if (me->mobj) memlog_free(me->mobj); } else if (me->rid < ABOX_REG_INT_LAST) { if (me->src_buf) devm_kfree(dev, me->src_buf); } } } static int abox_dbg_register_memlog(struct abox_data *data) { phys_addr_t paddr; char name[32] = {0,}; int i, ret = 0; for (i = 0; i < ARRAY_SIZE(abox_dbg_memlogs); i++) { snprintf(name, sizeof(name), "%s-file", abox_dbg_memlogs[i].name); paddr = (phys_addr_t)abox_get_resource_info(data, abox_dbg_memlogs[i].rid, false, &abox_dbg_memlogs[i].buf_size); abox_dbg_memlogs[i].fobj = memlog_alloc_file(data->drvlog_desc, name, abox_dbg_memlogs[i].buf_size, abox_dbg_memlogs[i].buf_size, 20, 10); if (!abox_dbg_memlogs[i].fobj) { abox_err(data->dev, "Failed to allocate file for %s\n", name); ret = -ENOMEM; break; } snprintf(name, sizeof(name), "%s-mem", abox_dbg_memlogs[i].name); if (abox_dbg_memlogs[i].rid < ABOX_REG_DEVICE_LAST) { abox_dbg_memlogs[i].mobj = memlog_alloc_dump(data->drvlog_desc, abox_dbg_memlogs[i].buf_size, paddr, true, abox_dbg_memlogs[i].fobj, name); if (!abox_dbg_memlogs[i].mobj) { abox_err(data->dev, "Fail:alloc mem for %s\n", name); ret = -ENOMEM; break; } } else if (abox_dbg_memlogs[i].rid < ABOX_REG_INT_LAST) { abox_dbg_memlogs[i].src_buf = devm_kzalloc(data->dev, abox_dbg_memlogs[i].buf_size, GFP_KERNEL); if (!abox_dbg_memlogs[i].src_buf) { abox_err(data->dev, "Fail:alloc mem for %s\n", name); ret = -ENOMEM; break; } } else { abox_dbg_memlogs[i].src_buf = abox_get_resource_info(data, abox_dbg_memlogs[i].rid, true, NULL); if (!abox_dbg_memlogs[i].src_buf) { abox_err(data->dev, "Fail:alloc mem for %s\n", name); ret = -ENOMEM; break; } } } if (ret < 0) abox_dbg_unregister_memlog(data); return ret; } void abox_dbg_dump_memlog(struct abox_data *data) { int i; size_t size; /* dump only if power is on */ if (!pm_runtime_get_if_in_use(data->dev)) { abox_info(data->dev, "memlog skip: no power\n"); return; } for (i = 0; i < ARRAY_SIZE(abox_dbg_memlogs); i++) { if (abox_dbg_memlogs[i].rid < ABOX_REG_DEVICE_LAST) { if (abox_dbg_memlogs[i].mobj) memlog_do_dump(abox_dbg_memlogs[i].mobj, MEMLOG_LEVEL_EMERG); } else { if (abox_dbg_memlogs[i].rid == ABOX_REG_GPR) abox_core_dump_gpr( (unsigned int *)abox_dbg_memlogs[i].src_buf); else if (abox_dbg_memlogs[i].rid == ABOX_REG_GICD) abox_gicd_dump(data->dev_gic, (char *)abox_dbg_memlogs[i].src_buf, 0, abox_dbg_memlogs[i].buf_size); if (abox_dbg_memlogs[i].fobj) { size = memlog_write_file(abox_dbg_memlogs[i].fobj, abox_dbg_memlogs[i].src_buf, abox_dbg_memlogs[i].buf_size); if (size <= 0) abox_err(data->dev, "%s: Failed to write memlog: %zu\n", abox_dbg_memlogs[i].name, size); } } } memlog_sync_to_file(data->drv_log_obj); pm_runtime_put(data->dev); } static int samsung_abox_debug_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device *dev_abox = dev->parent; struct abox_data *data = dev_get_drvdata(dev_abox); ssize_t new_size; int i, ret; abox_dbg(dev, "%s\n", __func__); if (!abox_dbg_rmem) { struct device_node *np_tmp; np_tmp = of_parse_phandle(dev->of_node, "memory-region", 0); if (np_tmp) abox_dbg_rmem = of_reserved_mem_lookup(np_tmp); } if (abox_dbg_rmem) { new_size = abox_oem_resize_reserved_memory(ABOX_OEM_RESERVED_MEMORY_DBG); if (new_size >= 0) abox_dbg_resize_rmem(dev, abox_dbg_rmem, new_size, "abox_dbg"); abox_dbg_rmem_init(data); } if (!abox_dbg_slog) { struct device_node *np_tmp; np_tmp = of_parse_phandle(dev->of_node, "memory-region", 1); if (np_tmp) abox_dbg_slog = of_reserved_mem_lookup(np_tmp); } if (abox_dbg_slog) { new_size = abox_oem_resize_reserved_memory(ABOX_OEM_RESERVED_MEMORY_SLOG); if (new_size >= 0) abox_dbg_resize_rmem(dev, abox_dbg_slog, new_size, "abox_slog"); abox_dbg_slog_init(data); } ret = abox_dbg_register_memlog(data); if (ret < 0) dev_warn(dev, "Failed to register memlog: %d\n", ret); abox_dbg_create_files(data); ret = device_create_file(dev, &dev_attr_gpr); if (ret < 0) abox_warn(dev, "Failed to create file: %s\n", dev_attr_gpr.attr.name); bin_attr_calliope_sram.size = SRAM_FIRMWARE_SIZE; bin_attr_calliope_sram.private = data->sram_base; bin_attr_calliope_dram.private = data->dram_base; bin_attr_calliope_log.private = data->dram_base + ABOX_LOG_OFFSET; if (data->slog_size >= ABOX_SLOG_DATA_OFFSET) { bin_attr_calliope_slog.size = data->slog_size - ABOX_SLOG_DATA_OFFSET; bin_attr_calliope_slog.private = data->slog_base + ABOX_SLOG_DATA_OFFSET; } else { bin_attr_calliope_slog.size = data->slog_size; bin_attr_calliope_slog.private = data->slog_base; } for (i = 0; i < ARRAY_SIZE(calliope_bin_attrs); i++) { struct bin_attribute *battr = calliope_bin_attrs[i]; ret = device_create_bin_file(dev, battr); if (ret < 0) abox_warn(dev, "Failed to create file: %s\n", battr->attr.name); } abox_proc_symlink_dev(dev, "debug"); return ret; } static int samsung_abox_debug_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; abox_dbg(dev, "%s\n", __func__); return 0; } static const struct of_device_id samsung_abox_debug_match[] = { { .compatible = "samsung,abox-debug", }, {}, }; MODULE_DEVICE_TABLE(of, samsung_abox_debug_match); struct platform_driver samsung_abox_debug_driver = { .probe = samsung_abox_debug_probe, .remove = samsung_abox_debug_remove, .driver = { .name = "abox-debug", .owner = THIS_MODULE, .of_match_table = of_match_ptr(samsung_abox_debug_match), }, };