/* * arch/arm/mach-tegra/tegra_ptm.c * * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pm.h" #include "tegra_ptm.h" /* * inside ETB trace buffer, each instruction can be identified by the CPU. For * the LP cluster, we assign it to a different ID in order to differentiate it * from CPU core 0. */ #define LP_CLUSTER_CPU_ID 0x9 enum range_type { NOT_USED = 0, RANGE_EXCLUDE, RANGE_INCLUDE, }; /* PTM tracer state */ struct tracectx { void __iomem *tpiu_regs; void __iomem *funnel_regs; void __iomem *etb_regs; int *last_etb; unsigned int etb_depth; void __iomem *ptm_regs[8]; int ptm_regs_count; unsigned long flags; int ncmppairs; int ptm_contextid_size; u32 etb_fc; unsigned long range_start[8]; unsigned long range_end[8]; enum range_type range_type[8]; bool dump_initial_etb; struct device *dev; struct mutex mutex; unsigned int timestamp_interval; struct clk *coresight_clk; struct clk *orig_parent_clk; int orig_clk_rate; struct clk *pll_p; }; static struct tracectx tracer = { .range_start[0] = (unsigned long)_stext, .range_end[0] = (unsigned long)_etext, .range_type[0] = RANGE_INCLUDE, .flags = TRACER_BRANCHOUTPUT, .etb_fc = ETB_FF_CTRL_ENFTC, .ptm_contextid_size = 2, .timestamp_interval = 0x8000, }; static inline bool trace_isrunning(struct tracectx *t) { return !!(t->flags & TRACER_RUNNING); } static int ptm_set_power(struct tracectx *t, int id, bool on) { u32 v; v = ptm_readl(t, id, PTM_CTRL); if (on) v &= ~PTM_CTRL_POWERDOWN; else v |= PTM_CTRL_POWERDOWN; ptm_writel(t, id, v, PTM_CTRL); return 0; } static int ptm_set_programming_bit(struct tracectx *t, int id, bool on) { u32 v; unsigned long timeout = TRACER_TIMEOUT; v = ptm_readl(t, id, PTM_CTRL); if (on) v |= PTM_CTRL_PROGRAM; else v &= ~PTM_CTRL_PROGRAM; ptm_writel(t, id, v, PTM_CTRL); while (--timeout) { if (on && ptm_progbit(t, id)) break; if (!on && !ptm_progbit(t, id)) break; } if (0 == timeout) { dev_err(t->dev, "PTM%d: %s progbit failed\n", id, on ? "set" : "clear"); return -EFAULT; } return 0; } static void trace_set_cpu_funnel_port(struct tracectx *t, int id, bool on) { int cpu_funnel_mask[] = { FUNNEL_CTRL_CPU0, FUNNEL_CTRL_CPU1, FUNNEL_CTRL_CPU2, FUNNEL_CTRL_CPU3 }; int funnel_ports; etb_regs_unlock(t); funnel_ports = funnel_readl(t, FUNNEL_CTRL); if (on) funnel_ports |= cpu_funnel_mask[id]; else funnel_ports &= ~cpu_funnel_mask[id]; funnel_writel(t, funnel_ports, FUNNEL_CTRL); etb_regs_lock(t); } static int ptm_setup_address_range(struct tracectx *t, int ptm_id, int cmp_id, unsigned long start, unsigned long end) { u32 flags; /* * We need know: * 1. PFT 1.0 or PFT 1.1, and * 2. Security Extension is implemented or not, and * 3. privilege mode or user mode tracing required, and * 4. security or non-security state tracing * in order to set correct matching mode and state for this register. * * However using PTM_ACC_TYPE_PFT10_IGN_SECURITY will enable matching * all modes and states under PFT 1.0 and 1.1 */ flags = PTM_ACC_TYPE_IGN_CONTEXTID | PTM_ACC_TYPE_PFT10_IGN_SECURITY; /* PTM only supports instruction execute */ flags |= PTM_ACC_TYPE_INSTR_ONLY; if (cmp_id < 0 || cmp_id >= t->ncmppairs) return -EINVAL; /* first comparator for the range */ ptm_writel(t, ptm_id, flags, PTM_COMP_ACC_TYPE(cmp_id * 2)); ptm_writel(t, ptm_id, start, PTM_COMP_VAL(cmp_id * 2)); /* second comparator is right next to it */ ptm_writel(t, ptm_id, flags, PTM_COMP_ACC_TYPE(cmp_id * 2 + 1)); ptm_writel(t, ptm_id, end, PTM_COMP_VAL(cmp_id * 2 + 1)); return 0; } static int trace_config_periodic_timestamp(struct tracectx *t, int id) { if (0 == (t->flags & TRACER_TIMESTAMP)) return 0; /* if the counter down value is 0, we disable periodic timestamp */ if (0 == t->timestamp_interval) return 0; /* config counter0 to counter down: */ /* set all the load value and reload vlaue */ ptm_writel(t, id, t->timestamp_interval, PTMCNTVR(0)); ptm_writel(t, id, t->timestamp_interval, PTMCNTRLDVR(0)); /* reload the counter0 value if counter0 reach 0 */ ptm_writel(t, id, DEF_PTM_EVENT(LOGIC_A, 0, COUNTER0), PTMCNTRLDEVR(0)); /* config the timestamp trigger event if counter0 is 0 */ ptm_writel(t, id, DEF_PTM_EVENT(LOGIC_A, 0, COUNTER0), PTMTSEVR); /* start the counter0 now */ ptm_writel(t, id, DEF_PTM_EVENT(LOGIC_A, 0, ALWAYS_TRUE), PTMCNTENR(0)); return 0; } static int trace_program_ptm(struct tracectx *t, int id) { u32 v; int i; int excl_flags = PTM_TRACE_ENABLE_EXCL_CTRL; int incl_flags = PTM_TRACE_ENABLE_INCL_CTRL; /* we must maintain programming bit here */ v = PTM_CTRL_PROGRAM; v |= PTM_CTRL_CONTEXTIDSIZE(t->ptm_contextid_size); if (t->flags & TRACER_CYCLE_ACC) v |= PTM_CTRL_CYCLEACCURATE; if (t->flags & TRACER_BRANCHOUTPUT) v |= PTM_CTRL_BRANCH_OUTPUT; if (t->flags & TRACER_TIMESTAMP) v |= PTM_CTRL_TIMESTAMP_EN; if (t->flags & TRACER_RETURN_STACK) v |= PTM_CTRL_RETURN_STACK_EN; ptm_writel(t, id, v, PTM_CTRL); for (i = 0; i < ARRAY_SIZE(t->range_start); i++) if (t->range_type[i] != NOT_USED) ptm_setup_address_range(t, id, i, t->range_start[i], t->range_end[i]); /* * after the range is set up, we enable the comparators based on * inc/exc flags */ for (i = 0; i < ARRAY_SIZE(t->range_type); i++) { if (t->range_type[i] == RANGE_EXCLUDE) { excl_flags |= 1 << i; ptm_writel(t, id, excl_flags, PTM_TRACE_ENABLE_CTRL1); } if (t->range_type[i] == RANGE_INCLUDE) { incl_flags |= 1 << i; ptm_writel(t, id, incl_flags, PTM_TRACE_ENABLE_CTRL1); } } /* trace all permitted processor execution... */ ptm_writel(t, id, DEF_PTM_EVENT(LOGIC_A, 0, ALWAYS_TRUE), PTM_TRACE_ENABLE_EVENT); /* assigning ATID for low power CPU */ if (is_lp_cluster()) ptm_writel(t, 0, LP_CLUSTER_CPU_ID, PTM_TRACEIDR); else ptm_writel(t, id, id, PTM_TRACEIDR); /* programming the Isync packet frequency */ ptm_writel(t, id, 100, PTM_SYNC_FREQ); trace_config_periodic_timestamp(t, id); return 0; } static void trace_start_ptm(struct tracectx *t, int id) { int ret; trace_set_cpu_funnel_port(t, id, true); ptm_regs_unlock(t, id); ptm_os_unlock(t, id); ptm_set_power(t, id, true); ptm_set_programming_bit(t, id, true); ret = trace_program_ptm(t, id); if (ret) dev_err(t->dev, "enable PTM%d failed\n", id); ptm_set_programming_bit(t, id, false); ptm_regs_lock(t, id); } static int trace_start(struct tracectx *t) { int id; u32 etb_fc = t->etb_fc; int ret; t->orig_clk_rate = clk_get_rate(t->coresight_clk); t->orig_parent_clk = clk_get_parent(t->coresight_clk); ret = clk_set_parent(t->coresight_clk, t->pll_p); if (ret < 0) return ret; ret = clk_set_rate(t->coresight_clk, 144000000); if (ret < 0) return ret; etb_regs_unlock(t); etb_writel(t, 0, ETB_WRITEADDR); etb_writel(t, etb_fc, ETB_FF_CTRL); etb_writel(t, TRACE_CAPATURE_ENABLE, ETB_CTRL); t->dump_initial_etb = false; etb_regs_lock(t); /* configure ptm(s) */ for_each_online_cpu(id) trace_start_ptm(t, id); t->flags |= TRACER_RUNNING; return 0; } static int trace_stop_ptm(struct tracectx *t, int id) { ptm_regs_unlock(t, id); /* * when the programming bit is 1: * 1. trace generation is stopped. * 2. PTM FIFO is emptied * 3. counter, sequencer and start/stop are held in current state. * 4. external outputs are forced low. */ ptm_set_programming_bit(t, id, true); ptm_set_power(t, id, false); ptm_os_lock(t, id); ptm_regs_lock(t, id); /* * Per ARM errata 780121 requires to disable the funnel port after PTM * is disabled */ trace_set_cpu_funnel_port(t, id, false); return 0; } static int trace_stop(struct tracectx *t) { int id; if (!trace_isrunning(t)) return 0; for_each_online_cpu(id) trace_stop_ptm(t, id); etb_regs_unlock(t); etb_writel(t, TRACE_CAPATURE_DISABLE, ETB_CTRL); etb_regs_lock(t); clk_set_parent(t->coresight_clk, t->orig_parent_clk); clk_set_rate(t->coresight_clk, t->orig_clk_rate); t->flags &= ~TRACER_RUNNING; return 0; } static int etb_getdatalen(struct tracectx *t) { u32 v; int wp; v = etb_readl(t, ETB_STATUS); if (v & ETB_STATUS_FULL) return t->etb_depth; wp = etb_readl(t, ETB_WRITEADDR); return wp; } /* sysrq+v will always stop the running trace and leave it at that */ static void ptm_dump(void) { struct tracectx *t = &tracer; u32 first = 0; int length; if (!t->etb_regs) { pr_info("No tracing hardware found\n"); return; } if (trace_isrunning(t)) trace_stop(t); etb_regs_unlock(t); length = etb_getdatalen(t); if (length == t->etb_depth) first = etb_readl(t, ETB_WRITEADDR); etb_writel(t, first, ETB_READADDR); pr_info("Trace buffer contents length: %d\n", length); pr_info("--- ETB buffer begin ---\n"); for (; length; length--) pr_cont("%08x", cpu_to_be32(etb_readl(t, ETB_READMEM))); pr_info("\n--- ETB buffer end ---\n"); etb_regs_lock(t); } static int etb_open(struct inode *inode, struct file *file) { struct miscdevice *miscdev = file->private_data; struct tracectx *t = dev_get_drvdata(miscdev->parent); if (NULL == t->etb_regs) return -ENODEV; file->private_data = t; return nonseekable_open(inode, file); } static ssize_t etb_read(struct file *file, char __user *data, size_t len, loff_t *ppos) { int total, i; long length; struct tracectx *t = file->private_data; u32 first = 0; u32 *buf; int wpos; int skip; long wlength; loff_t pos = *ppos; mutex_lock(&t->mutex); if (trace_isrunning(t)) { length = 0; goto out; } etb_regs_unlock(t); total = etb_getdatalen(t); if (total == 0 && t->dump_initial_etb) total = t->etb_depth; if (total == t->etb_depth) first = etb_readl(t, ETB_WRITEADDR); if (pos > total * 4) { skip = 0; wpos = total; } else { skip = (int)pos % 4; wpos = (int)pos / 4; } total -= wpos; first = (first + wpos) % t->etb_depth; etb_writel(t, first, ETB_READADDR); wlength = min(total, DIV_ROUND_UP(skip + (int)len, 4)); length = min(total * 4 - skip, (int)len); if (wlength == 0) { etb_regs_lock(t); mutex_unlock(&t->mutex); return 0; } buf = vmalloc(wlength * 4); dev_dbg(t->dev, "ETB read %ld bytes to %lld from %ld words at %d\n", length, pos, wlength, first); dev_dbg(t->dev, "ETB buffer length: %d\n", total + wpos); dev_dbg(t->dev, "ETB status reg: %x\n", etb_readl(t, ETB_STATUS)); for (i = 0; i < wlength; i++) buf[i] = etb_readl(t, ETB_READMEM); etb_regs_lock(t); length -= copy_to_user(data, (u8 *)buf + skip, length); vfree(buf); *ppos = pos + length; out: mutex_unlock(&t->mutex); return length; } /* * this function will be called right after the PTM driver is initialized, it * will save the ETB contents from up to last reset. */ static ssize_t etb_save_last(struct tracectx *t) { u32 first = 0; int i; int total; BUG_ON(!t->dump_initial_etb); etb_regs_unlock(t); /* * not all ETB can maintain the ETB buffer write pointer after WDT * reset, and we just read 0 for the write pointer; but we can still * read/parse the ETB partially in this case. */ total = etb_getdatalen(t); if (total == 0 && t->dump_initial_etb) total = t->etb_depth; first = 0; if (total == t->etb_depth) first = etb_readl(t, ETB_WRITEADDR); etb_writel(t, first, ETB_READADDR); for (i = 0; i < t->etb_depth; i++) t->last_etb[i] = etb_readl(t, ETB_READMEM); etb_regs_lock(t); return 0; } static ssize_t last_etb_read(struct file *file, char __user *data, size_t len, loff_t *ppos) { struct tracectx *t = file->private_data; size_t last_etb_size; size_t ret; mutex_lock(&t->mutex); ret = 0; last_etb_size = t->etb_depth * sizeof(*t->last_etb); if (*ppos >= last_etb_size) goto out; if (*ppos + len > last_etb_size) len = last_etb_size - *ppos; if (copy_to_user(data, (char *) t->last_etb + *ppos, len)) { ret = -EFAULT; goto out; } *ppos += len; ret = len; out: mutex_unlock(&t->mutex); return ret; } static int etb_release(struct inode *inode, struct file *file) { /* there's nothing to do here, actually */ return 0; } /* use a sysfs file "trace_running" to start/stop tracing */ static ssize_t trace_running_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%x\n", trace_isrunning(&tracer)); } static ssize_t trace_running_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int value; int ret; if (sscanf(buf, "%u", &value) != 1) return -EINVAL; if (!tracer.etb_regs) return -ENODEV; mutex_lock(&tracer.mutex); if (value != 0) ret = trace_start(&tracer); else ret = trace_stop(&tracer); mutex_unlock(&tracer.mutex); return ret ? : n; } static ssize_t trace_info_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { u32 etb_wa, etb_ra, etb_st, etb_fc, ptm_ctrl, ptm_st; int datalen; int id; int ret; mutex_lock(&tracer.mutex); if (tracer.etb_regs) { etb_regs_unlock(&tracer); datalen = etb_getdatalen(&tracer); etb_wa = etb_readl(&tracer, ETB_WRITEADDR); etb_ra = etb_readl(&tracer, ETB_READADDR); etb_st = etb_readl(&tracer, ETB_STATUS); etb_fc = etb_readl(&tracer, ETB_FF_CTRL); etb_regs_lock(&tracer); } else { etb_wa = etb_ra = etb_st = etb_fc = ~0; datalen = -1; } ret = sprintf(buf, "Trace buffer len: %d\nComparator pairs: %d\n" "ETB_WRITEADDR:\t%08x\nETB_READADDR:\t%08x\n" "ETB_STATUS:\t%08x\nETB_FF_CTRL:\t%08x\n", datalen, tracer.ncmppairs, etb_wa, etb_ra, etb_st, etb_fc); for (id = 0; id < tracer.ptm_regs_count; id++) { if (!cpu_online(id)) { ret += sprintf(buf + ret, "PTM_CTRL%d:\tOFFLINE\n" "PTM_STATUS%d:\tOFFLINE\n", id, id); continue; } ptm_regs_unlock(&tracer, id); ptm_ctrl = ptm_readl(&tracer, id, PTM_CTRL); ptm_st = ptm_readl(&tracer, id, PTM_STATUS); ptm_regs_lock(&tracer, id); ret += sprintf(buf + ret, "PTM_CTRL%d:\t%08x\n" "PTM_STATUS%d:\t%08x\n", id, ptm_ctrl, id, ptm_st); } mutex_unlock(&tracer.mutex); return ret; } static ssize_t trace_cycle_accurate_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_CYCLE_ACC)); } static ssize_t trace_cycle_accurate_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int cycacc; if (sscanf(buf, "%u", &cycacc) != 1) return -EINVAL; mutex_lock(&tracer.mutex); if (cycacc) tracer.flags |= TRACER_CYCLE_ACC; else tracer.flags &= ~TRACER_CYCLE_ACC; mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_contextid_size_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { /* 0: No context id tracing, 1: One byte, 2: Two bytes, 3: Four bytes */ return sprintf(buf, "%d\n", (1 << tracer.ptm_contextid_size) >> 1); } static ssize_t trace_contextid_size_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int contextid_size; if (sscanf(buf, "%u", &contextid_size) != 1) return -EINVAL; if (contextid_size == 3 || contextid_size > 4) return -EINVAL; mutex_lock(&tracer.mutex); tracer.ptm_contextid_size = fls(contextid_size); mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_branch_output_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_BRANCHOUTPUT)); } static ssize_t trace_branch_output_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int branch_output; if (sscanf(buf, "%u", &branch_output) != 1) return -EINVAL; mutex_lock(&tracer.mutex); if (branch_output) { tracer.flags |= TRACER_BRANCHOUTPUT; /* Branch broadcasting is incompatible with the return stack */ if (tracer.flags == TRACER_RETURN_STACK) dev_err(tracer.dev, "Need turn off return stack too\n"); tracer.flags &= ~TRACER_RETURN_STACK; } else { tracer.flags &= ~TRACER_BRANCHOUTPUT; } mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_return_stack_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_RETURN_STACK)); } static ssize_t trace_return_stack_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int return_stack; if (sscanf(buf, "%u", &return_stack) != 1) return -EINVAL; mutex_lock(&tracer.mutex); if (return_stack) { tracer.flags |= TRACER_RETURN_STACK; /* Return stack is incompatible with branch broadcasting */ tracer.flags &= ~TRACER_BRANCHOUTPUT; } else { tracer.flags &= ~TRACER_RETURN_STACK; } mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_timestamp_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_TIMESTAMP)); } static ssize_t trace_timestamp_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned int timestamp; if (sscanf(buf, "%d", ×tamp) < 1) return -EINVAL; mutex_lock(&tracer.mutex); if (timestamp) tracer.flags |= TRACER_TIMESTAMP; else tracer.flags &= ~TRACER_TIMESTAMP; mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_timestamp_interval_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", tracer.timestamp_interval); } static ssize_t trace_timestamp_interval_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { if (sscanf(buf, "%d", &tracer.timestamp_interval) != 1) return -EINVAL; return n; } static ssize_t trace_range_addr_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int id; if (sscanf(attr->attr.name, "trace_range%d", &id) != 1) return -EINVAL; if (id >= tracer.ncmppairs) return sprintf(buf, "invalid trace range comparator\n"); return sprintf(buf, "%08lx %08lx\n", tracer.range_start[id], tracer.range_end[id]); } static ssize_t trace_range_addr_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned long range_start, range_end; int id; /* get the trace range ID */ if (sscanf(attr->attr.name, "trace_range%d", &id) != 1) return -EINVAL; if (sscanf(buf, "%lx %lx", &range_start, &range_end) != 2) return -EINVAL; if (id >= tracer.ncmppairs) return -EINVAL; mutex_lock(&tracer.mutex); tracer.range_start[id] = range_start; tracer.range_end[id] = range_end; mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_range_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int id; char *str; if (sscanf(attr->attr.name, "trace_range_type%d", &id) != 1) return -EINVAL; if (id >= tracer.ncmppairs) return sprintf(buf, "invalid trace range comparator\n"); if (tracer.range_type[id] == NOT_USED) str = "disable"; if (tracer.range_type[id] == RANGE_EXCLUDE) str = "exclude"; if (tracer.range_type[id] == RANGE_INCLUDE) str = "include"; return sprintf(buf, "%s\n", str); } static ssize_t trace_range_type_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { int id; size_t size; if (sscanf(attr->attr.name, "trace_range_type%d", &id) != 1) return -EINVAL; if (id >= tracer.ncmppairs) return -EINVAL; mutex_lock(&tracer.mutex); size = n - 1; if (0 == strncmp("disable", buf, size) || 0 == strncmp("0", buf, size)) tracer.range_type[id] = NOT_USED; else if (0 == strncmp("include", buf, size)) tracer.range_type[id] = RANGE_INCLUDE; else if (0 == strncmp("exclude", buf, size)) tracer.range_type[id] = RANGE_EXCLUDE; else n = -EINVAL; mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_function_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { unsigned long range_start, size, offset; char name[KSYM_NAME_LEN]; char mod_name[KSYM_NAME_LEN]; int id; if (sscanf(attr->attr.name, "trace_range_function%d", &id) != 1) return -EINVAL; if (id >= tracer.ncmppairs) return -EINVAL; n = min(n, sizeof(name)); strncpy(name, buf, n); name[n - 1] = '\0'; if (strncmp("all", name, 3) == 0) { /* magic "all" for tracking the kernel */ range_start = (unsigned long) _stext; size = (unsigned long) _etext - (unsigned long) _stext + 4; } else { range_start = kallsyms_lookup_name(name); if (range_start == 0) return -EINVAL; if (0 > lookup_symbol_attrs(range_start, &size, &offset, mod_name, name)) return -EINVAL; } mutex_lock(&tracer.mutex); tracer.range_start[id] = range_start; tracer.range_end[id] = range_start + size - 4; mutex_unlock(&tracer.mutex); return n; } static ssize_t trace_function_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { unsigned long range_start, size, offset; char name[KSYM_NAME_LEN]; char mod_name[KSYM_NAME_LEN]; int id; int ret; if (sscanf(attr->attr.name, "trace_range_function%d", &id) != 1) return -EINVAL; if (id >= tracer.ncmppairs) return -EINVAL; range_start = tracer.range_start[id]; ret = 0; while (range_start <= tracer.range_end[id]) { if (0 > lookup_symbol_attrs(range_start, &size, &offset, mod_name, name)) return -EINVAL; range_start += size; ret += sprintf(buf + ret, "%s\n", name); if (ret > (PAGE_SIZE - KSYM_NAME_LEN)) { ret += sprintf(buf + ret, "...\nGood news, everyone!"); ret += sprintf(buf + ret, " Too many to list\n"); break; } } return ret; } #define A(a, b, c, d) __ATTR(trace_##a, b, \ trace_##c##_show, trace_##d##_store) static const struct kobj_attribute trace_attr[] = { __ATTR(trace_info, 0444, trace_info_show, NULL), A(running, 0644, running, running), A(range0, 0644, range_addr, range_addr), A(range1, 0644, range_addr, range_addr), A(range2, 0644, range_addr, range_addr), A(range3, 0644, range_addr, range_addr), A(range_function0, 0644, function, function), A(range_function1, 0644, function, function), A(range_function2, 0644, function, function), A(range_function3, 0644, function, function), A(range_type0, 0644, range_type, range_type), A(range_type1, 0644, range_type, range_type), A(range_type2, 0644, range_type, range_type), A(range_type3, 0644, range_type, range_type), A(cycle_accurate, 0644, cycle_accurate, cycle_accurate), A(contextid_size, 0644, contextid_size, contextid_size), A(branch_output, 0644, branch_output, branch_output), A(return_stack, 0644, return_stack, return_stack), A(timestamp, 0644, timestamp, timestamp), A(timestamp_interval, 0644, timestamp_interval, timestamp_interval) }; #define clk_readl(reg) __raw_readl(reg_clk_base + (reg)) #define clk_writel(value, reg) __raw_writel(value, reg_clk_base + (reg)) static void __iomem *reg_clk_base = IO_ADDRESS(TEGRA_CLK_RESET_BASE); static int funnel_and_tpiu_init(struct tracectx *t) { u32 tmp; /* Enable the trace clk for the TPIU */ tmp = clk_readl(CLK_TPIU_OUT_ENB_U); tmp |= CLK_TPIU_OUT_ENB_U_TRACKCLK_IN; clk_writel(tmp, CLK_TPIU_OUT_ENB_U); /* set trace clk of TPIU using the pll_p */ tmp = clk_readl(CLK_TPIU_SRC_TRACECLKIN); tmp &= ~CLK_TPIU_SRC_TRACECLKIN_SRC_MASK; tmp |= CLK_TPIU_SRC_TRACECLKIN_PLLP; clk_writel(tmp, CLK_TPIU_SRC_TRACECLKIN); /* disable TPIU */ tpiu_regs_unlock(t); tpiu_writel(t, TPIU_FF_CTRL_STOPFL, TPIU_FF_CTRL); tpiu_writel(t, TPIU_FF_CTRL_STOPFL | TPIU_FF_CTRL_MANUAL_FLUSH, TPIU_FF_CTRL); tpiu_regs_lock(t); /* Disable TPIU clk */ tmp = clk_readl(CLK_TPIU_OUT_ENB_U); tmp &= ~CLK_TPIU_OUT_ENB_U_TRACKCLK_IN; clk_writel(tmp, CLK_TPIU_OUT_ENB_U); funnel_regs_unlock(t); /* enable CPU0 - 3 for funnel ports, also assign funnel hold time */ funnel_writel(t, FUNNEL_MINIMUM_HOLD_TIME(0) | FUNNEL_CTRL_CPU0 | FUNNEL_CTRL_CPU1 | FUNNEL_CTRL_CPU2 | FUNNEL_CTRL_CPU3, FUNNEL_CTRL); /* Assign CPU0-3 funnel priority */ funnel_writel(t, 0xFFFFFFFF, FUNNEL_PRIORITY); funnel_writel(t, FUNNEL_PRIORITY_CPU0(0) | FUNNEL_PRIORITY_CPU1(0) | FUNNEL_PRIORITY_CPU2(0) | FUNNEL_PRIORITY_CPU3(0), FUNNEL_PRIORITY); funnel_regs_lock(t); return 0; } static const struct file_operations etb_fops = { .owner = THIS_MODULE, .read = etb_read, .open = etb_open, .release = etb_release, .llseek = no_llseek, }; static const struct file_operations last_etb_fops = { .owner = THIS_MODULE, .read = last_etb_read, .open = etb_open, .release = etb_release, .llseek = no_llseek, }; static struct miscdevice etb_miscdev = { .name = "etb", .minor = MISC_DYNAMIC_MINOR, .fops = &etb_fops, }; static struct miscdevice last_etb_miscdev = { .name = "last_etb", .minor = MISC_DYNAMIC_MINOR, .fops = &last_etb_fops, }; void ptm_power_idle_resume(int cpu) { struct tracectx *t = &tracer; if (trace_isrunning(&tracer)) trace_start_ptm(t, cpu); } static int etb_init(struct tracectx *t) { int ret; t->dump_initial_etb = true; etb_regs_unlock(t); t->etb_depth = etb_readl(t, ETB_DEPTH); /* make sure trace capture is disabled */ etb_writel(t, TRACE_CAPATURE_DISABLE, ETB_CTRL); etb_writel(t, ETB_FF_CTRL_STOPFL, ETB_FF_CTRL); etb_regs_lock(t); t->last_etb = devm_kzalloc(t->dev, t->etb_depth * sizeof(*t->last_etb), GFP_KERNEL); if (NULL == t->last_etb) { dev_err(t->dev, "failes to allocate memory to hold ETB\n"); return -ENOMEM; } etb_miscdev.parent = t->dev; ret = misc_register(&etb_miscdev); if (ret) { dev_err(t->dev, "failes to register /dev/etb\n"); return ret; } last_etb_miscdev.parent = t->dev; ret = misc_register(&last_etb_miscdev); if (ret) { dev_err(t->dev, "failes to register /dev/last_etb\n"); return ret; } dev_info(t->dev, "ETB is initialized.\n"); return 0; } static void tegra_ptm_enter_resume(void) { #ifdef CONFIG_PM_SLEEP if (trace_isrunning(&tracer)) { /* * On Tegra, LP0 will cut off VDD_CORE, and in that case * TPIU and FUNNEL need to be initialized again. */ if (!(TRACE_CAPATURE_ENABLE & etb_readl(&tracer, ETB_CTRL))) { funnel_and_tpiu_init(&tracer); trace_start(&tracer); } } #endif } static struct syscore_ops tegra_ptm_enter_syscore_ops = { .resume = tegra_ptm_enter_resume, }; static int tegra_ptm_cpu_notify(struct notifier_block *self, unsigned long action, void *cpu) { struct tracectx *t = &tracer; /* re-initialize the PTM if the PTM's CPU back on online */ switch (action) { case CPU_STARTING: if (trace_isrunning(t)) trace_start_ptm(t, (int)cpu); break; default: break; } return NOTIFY_OK; } static struct notifier_block tegra_ptm_cpu_nb = { .notifier_call = tegra_ptm_cpu_notify, }; static int ptm_probe(struct platform_device *dev) { struct tracectx *t = &tracer; int ret = 0; int i; mutex_lock(&t->mutex); t->dev = &dev->dev; platform_set_drvdata(dev, t); /* PLL_P can be used for CoreSight parent clock at high freq */ t->pll_p = clk_get_sys(NULL, "pll_p"); if (IS_ERR(t->pll_p)) { dev_err(&dev->dev, "Could not get pll_p clock\n"); goto out; } /* eanble the CoreSight(csite) clock for PTM/FUNNEL/ETB/TPIU */ t->coresight_clk = clk_get_sys("csite", NULL); if (IS_ERR(t->coresight_clk)) { dev_err(&dev->dev, "Could not get csite clock\n"); goto out; } clk_enable(t->coresight_clk); /* get all PTM resrouces */ t->ptm_regs_count = 0; ret = -ENOMEM; for (i = 0; i < dev->num_resources; i++) { struct resource *res; void __iomem *addr; res = platform_get_resource(dev, IORESOURCE_MEM, i); if (NULL == res) goto out; addr = devm_ioremap_nocache(&dev->dev, res->start, resource_size(res)); if (NULL == addr) goto out; if (0 == strncmp("ptm", res->name, 3)) { t->ptm_regs[t->ptm_regs_count] = addr; t->ptm_regs_count++; } if (0 == strncmp("etb", res->name, 3)) t->etb_regs = addr; if (0 == strncmp("tpiu", res->name, 4)) t->tpiu_regs = addr; if (0 == strncmp("funnel", res->name, 6)) t->funnel_regs = addr; } /* at least one PTM is required */ if (t->ptm_regs[0] == NULL || t->etb_regs == NULL || t->tpiu_regs == NULL || t->funnel_regs == NULL) { dev_err(&dev->dev, "Could not get PTM resources\n"); goto out; } if (0x13 != (0xff & ptm_readl(t, 0, CORESIGHT_DEVTYPE))) { dev_err(&dev->dev, "Did not find correct PTM device type\n"); goto out; } t->ncmppairs = 0xf & ptm_readl(t, 0, PTM_CONFCODE); /* initialize ETB, TPIU, FUNNEL and PTM */ ret = etb_init(t); if (ret) goto out; ret = funnel_and_tpiu_init(t); if (ret) goto out; for (i = 0; i < t->ptm_regs_count; i++) trace_stop_ptm(t, i); /* create sysfs */ for (i = 0; i < ARRAY_SIZE(trace_attr); i++) { ret = sysfs_create_file(&dev->dev.kobj, &trace_attr[i].attr); if (ret) dev_err(&dev->dev, "failed to create %s\n", trace_attr[i].attr.name); } /* register CPU PM/hotplug related callback */ register_syscore_ops(&tegra_ptm_enter_syscore_ops); register_cpu_notifier(&tegra_ptm_cpu_nb); dev_info(&dev->dev, "PTM driver initialized.\n"); etb_save_last(t); /* start the PTM and ETB now */ trace_start(t); out: dev_err(&dev->dev, "Failed to start the PTM device\n"); mutex_unlock(&t->mutex); return ret; } static int ptm_remove(struct platform_device *dev) { struct tracectx *t = &tracer; int i; unregister_cpu_notifier(&tegra_ptm_cpu_nb); unregister_syscore_ops(&tegra_ptm_enter_syscore_ops); for (i = 0; i < ARRAY_SIZE(trace_attr); i++) sysfs_remove_file(&dev->dev.kobj, &trace_attr[i].attr); mutex_lock(&t->mutex); devm_iounmap(&dev->dev, t->ptm_regs); devm_iounmap(&dev->dev, t->tpiu_regs); devm_iounmap(&dev->dev, t->funnel_regs); devm_iounmap(&dev->dev, t->etb_regs); t->etb_regs = NULL; clk_disable(t->coresight_clk); clk_put(t->coresight_clk); mutex_unlock(&t->mutex); return 0; } static struct platform_driver ptm_driver = { .probe = ptm_probe, .remove = ptm_remove, .driver = { .name = "ptm", .owner = THIS_MODULE, }, }; static void sysrq_ptm_dump(int key) { if (!mutex_trylock(&tracer.mutex)) { pr_info("Tracing hardware busy\n"); return; } dev_dbg(tracer.dev, "Dumping ETB buffer\n"); ptm_dump(); mutex_unlock(&tracer.mutex); } static struct sysrq_key_op sysrq_ptm_op = { .handler = sysrq_ptm_dump, .help_msg = "PTM buffer dump(V)", .action_msg = "ptm", }; static int __init tegra_ptm_driver_init(void) { int retval; mutex_init(&tracer.mutex); retval = platform_driver_register(&ptm_driver); if (retval) { pr_err("Failed to probe ptm\n"); return retval; } /* not being able to install this handler is not fatal */ (void)register_sysrq_key('v', &sysrq_ptm_op); return 0; } device_initcall(tegra_ptm_driver_init);