/* * Copyright(C) 2016 Linaro Limited. All rights reserved. * Author: Mathieu Poirier * * 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. * * 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. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #include "coresight-priv.h" #include "coresight-tmc.h" static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) { u32 axictl, sts; /* Zero out the memory to help with debug */ memset(drvdata->vaddr, 0, drvdata->size); CS_UNLOCK(drvdata->base); /* Wait for TMCSReady bit to be set */ tmc_wait_for_tmcready(drvdata); writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ); writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); axictl = readl_relaxed(drvdata->base + TMC_AXICTL); axictl &= ~TMC_AXICTL_CLEAR_MASK; axictl |= (TMC_AXICTL_PROT_CTL_B1 | TMC_AXICTL_WR_BURST_16); axictl |= TMC_AXICTL_AXCACHE_OS; if (tmc_etr_has_cap(drvdata, TMC_ETR_AXI_ARCACHE)) { axictl &= ~TMC_AXICTL_ARCACHE_MASK; axictl |= TMC_AXICTL_ARCACHE_OS; } writel_relaxed(axictl, drvdata->base + TMC_AXICTL); tmc_write_dba(drvdata, drvdata->paddr); /* * If the TMC pointers must be programmed before the session, * we have to set it properly (i.e, RRP/RWP to base address and * STS to "not full"). */ if (tmc_etr_has_cap(drvdata, TMC_ETR_SAVE_RESTORE)) { tmc_write_rrp(drvdata, drvdata->paddr); tmc_write_rwp(drvdata, drvdata->paddr); sts = readl_relaxed(drvdata->base + TMC_STS) & ~TMC_STS_FULL; writel_relaxed(sts, drvdata->base + TMC_STS); } writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | TMC_FFCR_TRIGON_TRIGIN, drvdata->base + TMC_FFCR); writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); tmc_enable_hw(drvdata); CS_LOCK(drvdata->base); } static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) { const u32 *barrier; u32 val; u32 *temp; u64 rwp; rwp = tmc_read_rwp(drvdata); val = readl_relaxed(drvdata->base + TMC_STS); /* * Adjust the buffer to point to the beginning of the trace data * and update the available trace data. */ if (val & TMC_STS_FULL) { drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; drvdata->len = drvdata->size; barrier = barrier_pkt; temp = (u32 *)drvdata->buf; while (*barrier) { *temp = *barrier; temp++; barrier++; } } else { drvdata->buf = drvdata->vaddr; drvdata->len = rwp - drvdata->paddr; } } static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) { CS_UNLOCK(drvdata->base); tmc_flush_and_stop(drvdata); /* * When operating in sysFS mode the content of the buffer needs to be * read before the TMC is disabled. */ if (drvdata->mode == CS_MODE_SYSFS) tmc_etr_dump_hw(drvdata); tmc_disable_hw(drvdata); CS_LOCK(drvdata->base); } static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) { int ret = 0; bool used = false; unsigned long flags; void __iomem *vaddr = NULL; dma_addr_t paddr; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); /* * If we don't have a buffer release the lock and allocate memory. * Otherwise keep the lock and move along. */ spin_lock_irqsave(&drvdata->spinlock, flags); if (!drvdata->vaddr) { spin_unlock_irqrestore(&drvdata->spinlock, flags); /* * Contiguous memory can't be allocated while a spinlock is * held. As such allocate memory here and free it if a buffer * has already been allocated (from a previous session). */ vaddr = dma_alloc_coherent(drvdata->dev, drvdata->size, &paddr, GFP_KERNEL); if (!vaddr) return -ENOMEM; /* Let's try again */ spin_lock_irqsave(&drvdata->spinlock, flags); } if (drvdata->reading) { ret = -EBUSY; goto out; } /* * In sysFS mode we can have multiple writers per sink. Since this * sink is already enabled no memory is needed and the HW need not be * touched. */ if (drvdata->mode == CS_MODE_SYSFS) goto out; /* * If drvdata::buf == NULL, use the memory allocated above. * Otherwise a buffer still exists from a previous session, so * simply use that. */ if (drvdata->buf == NULL) { used = true; drvdata->vaddr = vaddr; drvdata->paddr = paddr; drvdata->buf = drvdata->vaddr; } drvdata->mode = CS_MODE_SYSFS; tmc_etr_enable_hw(drvdata); out: spin_unlock_irqrestore(&drvdata->spinlock, flags); /* Free memory outside the spinlock if need be */ if (!used && vaddr) dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); if (!ret) dev_info(drvdata->dev, "TMC-ETR enabled\n"); return ret; } static int tmc_enable_etr_sink_perf(struct coresight_device *csdev) { int ret = 0; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { ret = -EINVAL; goto out; } /* * In Perf mode there can be only one writer per sink. There * is also no need to continue if the ETR is already operated * from sysFS. */ if (drvdata->mode != CS_MODE_DISABLED) { ret = -EINVAL; goto out; } drvdata->mode = CS_MODE_PERF; tmc_etr_enable_hw(drvdata); out: spin_unlock_irqrestore(&drvdata->spinlock, flags); return ret; } static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode) { switch (mode) { case CS_MODE_SYSFS: return tmc_enable_etr_sink_sysfs(csdev); case CS_MODE_PERF: return tmc_enable_etr_sink_perf(csdev); } /* We shouldn't be here */ return -EINVAL; } static void tmc_disable_etr_sink(struct coresight_device *csdev) { unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { spin_unlock_irqrestore(&drvdata->spinlock, flags); return; } /* Disable the TMC only if it needs to */ if (drvdata->mode != CS_MODE_DISABLED) { tmc_etr_disable_hw(drvdata); drvdata->mode = CS_MODE_DISABLED; } spin_unlock_irqrestore(&drvdata->spinlock, flags); dev_info(drvdata->dev, "TMC-ETR disabled\n"); } static const struct coresight_ops_sink tmc_etr_sink_ops = { .enable = tmc_enable_etr_sink, .disable = tmc_disable_etr_sink, }; const struct coresight_ops tmc_etr_cs_ops = { .sink_ops = &tmc_etr_sink_ops, }; int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) { int ret = 0; unsigned long flags; /* config types are set a boot time and never change */ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR)) return -EINVAL; spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { ret = -EBUSY; goto out; } /* Don't interfere if operated from Perf */ if (drvdata->mode == CS_MODE_PERF) { ret = -EINVAL; goto out; } /* If drvdata::buf is NULL the trace data has been read already */ if (drvdata->buf == NULL) { ret = -EINVAL; goto out; } /* Disable the TMC if need be */ if (drvdata->mode == CS_MODE_SYSFS) tmc_etr_disable_hw(drvdata); drvdata->reading = true; out: spin_unlock_irqrestore(&drvdata->spinlock, flags); return ret; } int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) { unsigned long flags; dma_addr_t paddr; void __iomem *vaddr = NULL; /* config types are set a boot time and never change */ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR)) return -EINVAL; spin_lock_irqsave(&drvdata->spinlock, flags); /* RE-enable the TMC if need be */ if (drvdata->mode == CS_MODE_SYSFS) { /* * The trace run will continue with the same allocated trace * buffer. The trace buffer is cleared in tmc_etr_enable_hw(), * so we don't have to explicitly clear it. Also, since the * tracer is still enabled drvdata::buf can't be NULL. */ tmc_etr_enable_hw(drvdata); } else { /* * The ETR is not tracing and the buffer was just read. * As such prepare to free the trace buffer. */ vaddr = drvdata->vaddr; paddr = drvdata->paddr; drvdata->buf = drvdata->vaddr = NULL; } drvdata->reading = false; spin_unlock_irqrestore(&drvdata->spinlock, flags); /* Free allocated memory out side of the spinlock */ if (vaddr) dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); return 0; }