/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include "mdss.h" #include "mdss_mdp.h" #include "mdss_debug.h" #ifdef CONFIG_FB_MSM_MDSS_XLOG_DEBUG #define XLOG_DEFAULT_ENABLE 1 #else #define XLOG_DEFAULT_ENABLE 0 #endif #define XLOG_DEFAULT_PANIC 1 #define XLOG_DEFAULT_REGDUMP 0x2 /* dump in RAM */ #define XLOG_DEFAULT_DBGBUSDUMP 0x2 /* dump in RAM */ #define XLOG_DEFAULT_VBIF_DBGBUSDUMP 0x2 /* dump in RAM */ /* * xlog will print this number of entries when it is called through * sysfs node or panic. This prevents kernel log from xlog message * flood. */ #define MDSS_XLOG_PRINT_ENTRY 256 /* * xlog keeps this number of entries in memory for debug purpose. This * number must be greater than print entry to prevent out of bound xlog * entry array access. */ #define MDSS_XLOG_ENTRY (MDSS_XLOG_PRINT_ENTRY * 4) #define MDSS_XLOG_MAX_DATA 15 #define MDSS_XLOG_BUF_MAX 512 #define MDSS_XLOG_BUF_ALIGN 32 DEFINE_SPINLOCK(xlock); struct tlog { u32 counter; s64 time; const char *name; int line; u32 data[MDSS_XLOG_MAX_DATA]; u32 data_cnt; int pid; }; struct mdss_dbg_xlog { struct tlog logs[MDSS_XLOG_ENTRY]; u32 first; u32 last; u32 curr; struct dentry *xlog; u32 xlog_enable; u32 panic_on_err; u32 enable_reg_dump; u32 enable_dbgbus_dump; u32 enable_vbif_dbgbus_dump; struct work_struct xlog_dump_work; struct mdss_debug_base *blk_arr[MDSS_DEBUG_BASE_MAX]; bool work_panic; bool work_dbgbus; bool work_vbif_dbgbus; u32 *dbgbus_dump; /* address for the debug bus dump */ u32 *vbif_dbgbus_dump; /* address for the vbif debug bus dump */ u32 *nrt_vbif_dbgbus_dump; /* address for the nrt vbif debug bus dump */ } mdss_dbg_xlog; static inline bool mdss_xlog_is_enabled(u32 flag) { return (flag & mdss_dbg_xlog.xlog_enable) || (flag == MDSS_XLOG_ALL && mdss_dbg_xlog.xlog_enable); } void mdss_xlog(const char *name, int line, int flag, ...) { unsigned long flags; int i, val = 0; va_list args; struct tlog *log; if (!mdss_xlog_is_enabled(flag)) return; spin_lock_irqsave(&xlock, flags); log = &mdss_dbg_xlog.logs[mdss_dbg_xlog.curr]; log->time = ktime_to_us(ktime_get()); log->name = name; log->line = line; log->data_cnt = 0; log->pid = current->pid; va_start(args, flag); for (i = 0; i < MDSS_XLOG_MAX_DATA; i++) { val = va_arg(args, int); if (val == DATA_LIMITER) break; log->data[i] = val; } va_end(args); log->data_cnt = i; mdss_dbg_xlog.curr = (mdss_dbg_xlog.curr + 1) % MDSS_XLOG_ENTRY; mdss_dbg_xlog.last++; spin_unlock_irqrestore(&xlock, flags); } /* always dump the last entries which are not dumped yet */ static bool __mdss_xlog_dump_calc_range(void) { static u32 next; bool need_dump = true; unsigned long flags; struct mdss_dbg_xlog *xlog = &mdss_dbg_xlog; spin_lock_irqsave(&xlock, flags); xlog->first = next; if (xlog->last == xlog->first) { need_dump = false; goto dump_exit; } if (xlog->last < xlog->first) { xlog->first %= MDSS_XLOG_ENTRY; if (xlog->last < xlog->first) xlog->last += MDSS_XLOG_ENTRY; } if ((xlog->last - xlog->first) > MDSS_XLOG_PRINT_ENTRY) { pr_warn("xlog buffer overflow before dump: %d\n", xlog->last - xlog->first); xlog->first = xlog->last - MDSS_XLOG_PRINT_ENTRY; } next = xlog->first + 1; dump_exit: spin_unlock_irqrestore(&xlock, flags); return need_dump; } static ssize_t mdss_xlog_dump_entry(char *xlog_buf, ssize_t xlog_buf_size) { int i; ssize_t off = 0; struct tlog *log, *prev_log; unsigned long flags; spin_lock_irqsave(&xlock, flags); log = &mdss_dbg_xlog.logs[mdss_dbg_xlog.first % MDSS_XLOG_ENTRY]; prev_log = &mdss_dbg_xlog.logs[(mdss_dbg_xlog.first - 1) % MDSS_XLOG_ENTRY]; off = snprintf((xlog_buf + off), (xlog_buf_size - off), "%s:%-4d", log->name, log->line); if (off < MDSS_XLOG_BUF_ALIGN) { memset((xlog_buf + off), 0x20, (MDSS_XLOG_BUF_ALIGN - off)); off = MDSS_XLOG_BUF_ALIGN; } off += snprintf((xlog_buf + off), (xlog_buf_size - off), "=>[%-8d:%-11llu:%9llu][%-4d]:", mdss_dbg_xlog.first, log->time, (log->time - prev_log->time), log->pid); for (i = 0; i < log->data_cnt; i++) off += snprintf((xlog_buf + off), (xlog_buf_size - off), "%x ", log->data[i]); off += snprintf((xlog_buf + off), (xlog_buf_size - off), "\n"); spin_unlock_irqrestore(&xlock, flags); return off; } static void mdss_xlog_dump_all(void) { char xlog_buf[MDSS_XLOG_BUF_MAX]; while (__mdss_xlog_dump_calc_range()) { mdss_xlog_dump_entry(xlog_buf, MDSS_XLOG_BUF_MAX); pr_info("%s", xlog_buf); } } u32 get_dump_range(struct dump_offset *range_node, size_t max_offset) { u32 length = 0; if ((range_node->start > range_node->end) || (range_node->end > max_offset) || (range_node->start == 0 && range_node->end == 0)) { length = max_offset; } else { length = range_node->end - range_node->start; } return length; } static void mdss_dump_debug_bus(u32 bus_dump_flag, u32 **dump_mem) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool in_log, in_mem; u32 *dump_addr = NULL; u32 status = 0; struct debug_bus *head; phys_addr_t phys = 0; int list_size = mdata->dbg_bus_size; int i; if (!(mdata->dbg_bus && list_size)) return; /* will keep in memory 4 entries of 4 bytes each */ list_size = (list_size * 4 * 4); in_log = (bus_dump_flag & MDSS_DBG_DUMP_IN_LOG); in_mem = (bus_dump_flag & MDSS_DBG_DUMP_IN_MEM); pr_info("======== Debug bus DUMP =========\n"); if (in_mem) { if (!(*dump_mem)) *dump_mem = dma_alloc_coherent(&mdata->pdev->dev, list_size, &phys, GFP_KERNEL); if (*dump_mem) { dump_addr = *dump_mem; pr_info("%s: start_addr:0x%p end_addr:0x%p\n", __func__, dump_addr, dump_addr + list_size); } else { in_mem = false; pr_err("dump_mem: allocation fails\n"); } } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); for (i = 0; i < mdata->dbg_bus_size; i++) { head = mdata->dbg_bus + i; writel_relaxed(TEST_MASK(head->block_id, head->test_id), mdss_res->mdp_base + head->wr_addr); wmb(); /* make sure test bits were written */ status = readl_relaxed(mdss_res->mdp_base + head->wr_addr + 0x4); if (in_log) pr_err("waddr=0x%x blk=%d tst=%d val=0x%x\n", head->wr_addr, head->block_id, head->test_id, status); if (dump_addr && in_mem) { dump_addr[i*4] = head->wr_addr; dump_addr[i*4 + 1] = head->block_id; dump_addr[i*4 + 2] = head->test_id; dump_addr[i*4 + 3] = status; } /* Disable debug bus once we are done */ writel_relaxed(0, mdss_res->mdp_base + head->wr_addr); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); pr_info("========End Debug bus=========\n"); } static void __vbif_debug_bus(struct vbif_debug_bus *head, void __iomem *vbif_base, u32 *dump_addr, bool in_log) { int i, j; u32 val; if (!dump_addr && !in_log) return; for (i = 0; i < head->block_cnt; i++) { writel_relaxed(1 << (i + head->bit_offset), vbif_base + head->block_bus_addr); /* make sure that current bus blcok enable */ wmb(); for (j = 0; j < head->test_pnt_cnt; j++) { writel_relaxed(j, vbif_base + head->block_bus_addr + 4); /* make sure that test point is enabled */ wmb(); val = readl_relaxed(vbif_base + MMSS_VBIF_TEST_BUS_OUT); if (dump_addr) { *dump_addr++ = head->block_bus_addr; *dump_addr++ = i; *dump_addr++ = j; *dump_addr++ = val; } if (in_log) pr_err("testpoint:%x arb/xin id=%d index=%d val=0x%x\n", head->block_bus_addr, i, j, val); } } } static void mdss_dump_vbif_debug_bus(u32 bus_dump_flag, u32 **dump_mem, bool real_time) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool in_log, in_mem; u32 *dump_addr = NULL; u32 value; struct vbif_debug_bus *head; phys_addr_t phys = 0; int i, list_size = 0; void __iomem *vbif_base; struct vbif_debug_bus *dbg_bus; u32 bus_size; if (real_time) { pr_info("======== VBIF Debug bus DUMP =========\n"); vbif_base = mdata->vbif_io.base; dbg_bus = mdata->vbif_dbg_bus; bus_size = mdata->vbif_dbg_bus_size; } else { pr_info("======== NRT VBIF Debug bus DUMP =========\n"); vbif_base = mdata->vbif_nrt_io.base; dbg_bus = mdata->nrt_vbif_dbg_bus; bus_size = mdata->nrt_vbif_dbg_bus_size; } if (!dbg_bus || !bus_size) return; /* allocate memory for each test point */ for (i = 0; i < bus_size; i++) { head = dbg_bus + i; list_size += (head->block_cnt * head->test_pnt_cnt); } /* 4 bytes * 4 entries for each test point*/ list_size *= 16; in_log = (bus_dump_flag & MDSS_DBG_DUMP_IN_LOG); in_mem = (bus_dump_flag & MDSS_DBG_DUMP_IN_MEM); if (in_mem) { if (!(*dump_mem)) *dump_mem = dma_alloc_coherent(&mdata->pdev->dev, list_size, &phys, GFP_KERNEL); if (*dump_mem) { dump_addr = *dump_mem; pr_info("%s: start_addr:0x%p end_addr:0x%p\n", __func__, dump_addr, dump_addr + list_size); } else { in_mem = false; pr_err("dump_mem: allocation fails\n"); } } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); value = readl_relaxed(vbif_base + MMSS_VBIF_CLKON); writel_relaxed(value | BIT(1), vbif_base + MMSS_VBIF_CLKON); /* make sure that vbif core is on */ wmb(); for (i = 0; i < bus_size; i++) { head = dbg_bus + i; writel_relaxed(0, vbif_base + head->disable_bus_addr); writel_relaxed(BIT(0), vbif_base + MMSS_VBIF_TEST_BUS_OUT_CTRL); /* make sure that other bus is off */ wmb(); __vbif_debug_bus(head, vbif_base, dump_addr, in_log); if (dump_addr) dump_addr += (head->block_cnt * head->test_pnt_cnt * 4); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); pr_info("========End VBIF Debug bus=========\n"); } static void mdss_dump_reg(const char *dump_name, u32 reg_dump_flag, char *addr, int len, u32 **dump_mem) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool in_log, in_mem; u32 *dump_addr = NULL; phys_addr_t phys = 0; int i; in_log = (reg_dump_flag & MDSS_DBG_DUMP_IN_LOG); in_mem = (reg_dump_flag & MDSS_DBG_DUMP_IN_MEM); pr_debug("reg_dump_flag=%d in_log=%d in_mem=%d\n", reg_dump_flag, in_log, in_mem); if (len % 16) len += 16; len /= 16; if (in_mem) { if (!(*dump_mem)) *dump_mem = dma_alloc_coherent(&mdata->pdev->dev, len * 16, &phys, GFP_KERNEL); if (*dump_mem) { dump_addr = *dump_mem; pr_info("%s: start_addr:0x%p end_addr:0x%p reg_addr=0x%p\n", dump_name, dump_addr, dump_addr + (u32)len * 16, addr); } else { in_mem = false; pr_err("dump_mem: kzalloc fails!\n"); } } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); for (i = 0; i < len; i++) { u32 x0, x4, x8, xc; x0 = readl_relaxed(addr+0x0); x4 = readl_relaxed(addr+0x4); x8 = readl_relaxed(addr+0x8); xc = readl_relaxed(addr+0xc); if (in_log) pr_info("%p : %08x %08x %08x %08x\n", addr, x0, x4, x8, xc); if (dump_addr && in_mem) { dump_addr[i*4] = x0; dump_addr[i*4 + 1] = x4; dump_addr[i*4 + 2] = x8; dump_addr[i*4 + 3] = xc; } addr += 16; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } static void mdss_dump_reg_by_ranges(struct mdss_debug_base *dbg, u32 reg_dump_flag) { char *addr; int len; struct range_dump_node *xlog_node, *xlog_tmp; if (!dbg || !dbg->base) { pr_err("dbg base is null!\n"); return; } pr_info("%s:=========%s DUMP=========\n", __func__, dbg->name); /* If there is a list to dump the registers by ranges, use the ranges */ if (!list_empty(&dbg->dump_list)) { list_for_each_entry_safe(xlog_node, xlog_tmp, &dbg->dump_list, head) { len = get_dump_range(&xlog_node->offset, dbg->max_offset); addr = dbg->base + xlog_node->offset.start; pr_debug("%s: range_base=0x%p start=0x%x end=0x%x\n", xlog_node->range_name, addr, xlog_node->offset.start, xlog_node->offset.end); mdss_dump_reg((const char *)xlog_node->range_name, reg_dump_flag, addr, len, &xlog_node->reg_dump); } } else { /* If there is no list to dump ranges, dump all registers */ pr_info("Ranges not found, will dump full registers"); pr_info("base:0x%p len:0x%zu\n", dbg->base, dbg->max_offset); addr = dbg->base; len = dbg->max_offset; mdss_dump_reg((const char *)dbg->name, reg_dump_flag, addr, len, &dbg->reg_dump); } } static void mdss_dump_reg_by_blk(const char *blk_name) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_debug_data *mdd = mdata->debug_inf.debug_data; struct mdss_debug_base *blk_base, *tmp; if (!mdd) return; list_for_each_entry_safe(blk_base, tmp, &mdd->base_list, head) { if (strlen(blk_base->name) && !strcmp(blk_base->name, blk_name)) { mdss_dump_reg_by_ranges(blk_base, mdss_dbg_xlog.enable_reg_dump); break; } } } static void mdss_dump_reg_all(void) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_debug_data *mdd = mdata->debug_inf.debug_data; struct mdss_debug_base *blk_base, *tmp; if (!mdd) return; list_for_each_entry_safe(blk_base, tmp, &mdd->base_list, head) { if (strlen(blk_base->name)) mdss_dump_reg_by_blk(blk_base->name); } } static void clear_dump_blk_arr(struct mdss_debug_base *blk_arr[], u32 blk_len) { int i; for (i = 0; i < blk_len; i++) blk_arr[i] = NULL; } struct mdss_debug_base *get_dump_blk_addr(const char *blk_name) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_debug_data *mdd = mdata->debug_inf.debug_data; struct mdss_debug_base *blk_base, *tmp; if (!mdd) return NULL; list_for_each_entry_safe(blk_base, tmp, &mdd->base_list, head) { if (strlen(blk_base->name) && !strcmp(blk_base->name, blk_name)) return blk_base; } return NULL; } static void mdss_xlog_dump_array(struct mdss_debug_base *blk_arr[], u32 len, bool dead, const char *name, bool dump_dbgbus, bool dump_vbif_dbgbus) { int i; for (i = 0; i < len; i++) { if (blk_arr[i] != NULL) mdss_dump_reg_by_ranges(blk_arr[i], mdss_dbg_xlog.enable_reg_dump); } mdss_xlog_dump_all(); if (dump_dbgbus) mdss_dump_debug_bus(mdss_dbg_xlog.enable_dbgbus_dump, &mdss_dbg_xlog.dbgbus_dump); if (dump_vbif_dbgbus) { mdss_dump_vbif_debug_bus(mdss_dbg_xlog.enable_vbif_dbgbus_dump, &mdss_dbg_xlog.vbif_dbgbus_dump, true); mdss_dump_vbif_debug_bus(mdss_dbg_xlog.enable_vbif_dbgbus_dump, &mdss_dbg_xlog.nrt_vbif_dbgbus_dump, false); } if (dead && mdss_dbg_xlog.panic_on_err) panic(name); } static void xlog_debug_work(struct work_struct *work) { mdss_xlog_dump_array(mdss_dbg_xlog.blk_arr, ARRAY_SIZE(mdss_dbg_xlog.blk_arr), mdss_dbg_xlog.work_panic, "xlog_workitem", mdss_dbg_xlog.work_dbgbus, mdss_dbg_xlog.work_vbif_dbgbus); } void mdss_xlog_tout_handler_default(bool queue, const char *name, ...) { int i, index = 0; bool dead = false; bool dump_dbgbus = false, dump_vbif_dbgbus = false; va_list args; char *blk_name = NULL; struct mdss_debug_base *blk_base = NULL; struct mdss_debug_base **blk_arr; u32 blk_len; if (!mdss_xlog_is_enabled(MDSS_XLOG_DEFAULT)) return; if (queue && work_pending(&mdss_dbg_xlog.xlog_dump_work)) return; blk_arr = &mdss_dbg_xlog.blk_arr[0]; blk_len = ARRAY_SIZE(mdss_dbg_xlog.blk_arr); clear_dump_blk_arr(blk_arr, blk_len); va_start(args, name); for (i = 0; i < MDSS_XLOG_MAX_DATA; i++) { blk_name = va_arg(args, char*); if (IS_ERR_OR_NULL(blk_name)) break; blk_base = get_dump_blk_addr(blk_name); if (blk_base && (index < blk_len)) { blk_arr[index] = blk_base; index++; } if (!strcmp(blk_name, "dbg_bus")) dump_dbgbus = true; if (!strcmp(blk_name, "vbif_dbg_bus")) dump_vbif_dbgbus = true; if (!strcmp(blk_name, "panic")) dead = true; } va_end(args); if (queue) { /* schedule work to dump later */ mdss_dbg_xlog.work_panic = dead; mdss_dbg_xlog.work_dbgbus = dump_dbgbus; mdss_dbg_xlog.work_vbif_dbgbus = dump_vbif_dbgbus; schedule_work(&mdss_dbg_xlog.xlog_dump_work); } else { mdss_xlog_dump_array(blk_arr, blk_len, dead, name, dump_dbgbus, dump_vbif_dbgbus); } } static int mdss_xlog_dump_open(struct inode *inode, struct file *file) { /* non-seekable */ file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); file->private_data = inode->i_private; return 0; } static ssize_t mdss_xlog_dump_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { ssize_t len = 0; char xlog_buf[MDSS_XLOG_BUF_MAX]; if (__mdss_xlog_dump_calc_range()) { len = mdss_xlog_dump_entry(xlog_buf, MDSS_XLOG_BUF_MAX); if (copy_to_user(buff, xlog_buf, len)) return -EFAULT; *ppos += len; } return len; } static ssize_t mdss_xlog_dump_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { mdss_dump_reg_all(); mdss_xlog_dump_all(); if (mdss_dbg_xlog.panic_on_err) panic("mdss"); return count; } static const struct file_operations mdss_xlog_fops = { .open = mdss_xlog_dump_open, .read = mdss_xlog_dump_read, .write = mdss_xlog_dump_write, }; int mdss_create_xlog_debug(struct mdss_debug_data *mdd) { int i; mdss_dbg_xlog.xlog = debugfs_create_dir("xlog", mdd->root); if (IS_ERR_OR_NULL(mdss_dbg_xlog.xlog)) { pr_err("debugfs_create_dir fail, error %ld\n", PTR_ERR(mdss_dbg_xlog.xlog)); mdss_dbg_xlog.xlog = NULL; return -ENODEV; } INIT_WORK(&mdss_dbg_xlog.xlog_dump_work, xlog_debug_work); mdss_dbg_xlog.work_panic = false; for (i = 0; i < MDSS_XLOG_ENTRY; i++) mdss_dbg_xlog.logs[i].counter = i; debugfs_create_file("dump", 0644, mdss_dbg_xlog.xlog, NULL, &mdss_xlog_fops); debugfs_create_u32("enable", 0644, mdss_dbg_xlog.xlog, &mdss_dbg_xlog.xlog_enable); debugfs_create_bool("panic", 0644, mdss_dbg_xlog.xlog, &mdss_dbg_xlog.panic_on_err); debugfs_create_u32("reg_dump", 0644, mdss_dbg_xlog.xlog, &mdss_dbg_xlog.enable_reg_dump); debugfs_create_u32("dbgbus_dump", 0644, mdss_dbg_xlog.xlog, &mdss_dbg_xlog.enable_dbgbus_dump); debugfs_create_u32("vbif_dbgbus_dump", 0644, mdss_dbg_xlog.xlog, &mdss_dbg_xlog.enable_vbif_dbgbus_dump); mdss_dbg_xlog.xlog_enable = XLOG_DEFAULT_ENABLE; mdss_dbg_xlog.panic_on_err = XLOG_DEFAULT_PANIC; mdss_dbg_xlog.enable_reg_dump = XLOG_DEFAULT_REGDUMP; mdss_dbg_xlog.enable_dbgbus_dump = XLOG_DEFAULT_DBGBUSDUMP; mdss_dbg_xlog.enable_vbif_dbgbus_dump = XLOG_DEFAULT_VBIF_DBGBUSDUMP; pr_info("xlog_status: enable:%d, panic:%d, dump:%d\n", mdss_dbg_xlog.xlog_enable, mdss_dbg_xlog.panic_on_err, mdss_dbg_xlog.enable_reg_dump); return 0; }