From: Mike Bazov mike@perception-point.io
Introducing the get_trace() sink operation. This is merely exposing the tmc_etr_get_sysfs_trace() functionality as a sink operation, and also implementing for CS_MODE_API.
When using get_trace() with request CS_MODE_API, the trace is fetched from the __current__ etr buffer, which is assumed to be the CS_MODE_API buffer set by set_buffer(). The get_trace() can be further exposed to kernel clients to easily get the trace buffer and copy trace data from it.
In addition, the tmc-etf is also made aware of this sink op, without supporting CS_MODE_API.
Signed-off-by: Mike Bazov mike@perception-point.io --- drivers/hwtracing/coresight/coresight-tmc-etf.c | 14 ++++ drivers/hwtracing/coresight/coresight-tmc-etr.c | 100 +++++++++++++++++++++--- include/linux/coresight.h | 3 + 3 files changed, 108 insertions(+), 9 deletions(-)
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c index 897dfc1bcbaa..656a786de808 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etf.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c @@ -120,6 +120,19 @@ ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata, return actual; }
+ssize_t tmc_etf_get_trace(struct coresight_device *csdev, u32 mode, + loff_t pos, size_t len, char **bufpp) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + switch (mode) { + case CS_MODE_SYSFS: + return tmc_etb_get_sysfs_trace(drvdata, pos, len, bufpp); + default: + return -EINVAL; + } +} + static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev) { int ret = 0; @@ -490,6 +503,7 @@ static const struct coresight_ops_sink tmc_etf_sink_ops = { .alloc_buffer = tmc_alloc_etf_buffer, .free_buffer = tmc_free_etf_buffer, .update_buffer = tmc_update_etf_buffer, + .get_trace = tmc_etf_get_trace, };
static const struct coresight_ops_link tmc_etf_link_ops = { diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 6ffcaaf76680..f60ac8942c1f 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -37,6 +37,8 @@ struct etr_perf_buffer { void **pages; };
+static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata); + /* Convert the perf index to an offset within the ETR buffer */ #define PERF_IDX2OFF(idx, buf) ((idx) % ((buf)->nr_pages << PAGE_SHIFT))
@@ -907,6 +909,7 @@ static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata) rrp = tmc_read_rrp(drvdata); rwp = tmc_read_rwp(drvdata); status = readl_relaxed(drvdata->base + TMC_STS); + etr_buf->full = status & TMC_STS_FULL;
WARN_ON(!etr_buf->ops || !etr_buf->ops->sync); @@ -988,29 +991,91 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata, * also updating the @bufpp on where to find it. Since the trace data * starts at anywhere in the buffer, depending on the RRP, we adjust the * @len returned to handle buffer wrapping around. - * - * We are protected here by drvdata->reading != 0, which ensures the - * sysfs_buf stays alive. */ -ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, - loff_t pos, size_t len, char **bufpp) +static ssize_t etr_buf_get_trace(struct etr_buf *etr_buf, + loff_t pos, size_t len, char **bufpp) { s64 offset; ssize_t actual = len; - struct etr_buf *etr_buf = drvdata->sysfs_buf; + + if (!etr_buf) + return -EINVAL;
if (pos + actual > etr_buf->len) actual = etr_buf->len - pos; if (actual <= 0) - return actual; + return 0;
/* Compute the offset from which we read the data */ offset = etr_buf->offset + pos; if (offset >= etr_buf->size) offset -= etr_buf->size; + return tmc_etr_buf_get_data(etr_buf, offset, actual, bufpp); }
+/* We are protected here by drvdata->reading != 0, which ensures + * the sysfs_buf stays alive. + */ +ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp) +{ + return etr_buf_get_trace(drvdata->sysfs_buf, pos, len, bufpp); +} + +/* It is the user's responsbility to keep the coresight session buffer + * valid. + */ +static ssize_t tmc_etr_get_api_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp) +{ + ssize_t ret = 0; + unsigned long flags; + + spin_lock_irqsave(&drvdata->spinlock, flags); + + if (drvdata->mode != CS_MODE_API) { + ret = -EINVAL; + goto out; + } + + if (drvdata->api_buf == NULL) { + ret = -ENOENT; + goto out; + } + + /* Make sure to stop and sync the buffer if we're getting a trace + * from a running session. + */ + tmc_etr_disable_hw(drvdata); + + ret = etr_buf_get_trace(drvdata->api_buf, pos, len, bufpp); + + /* Re-enable the trace session. Again, it is entirely up the user to + * read the actual trace data before reusing the existing buffer, or, + * perhaps, use a different buffer. + */ + tmc_etr_enable_hw(drvdata, drvdata->api_buf); +out: + spin_unlock_irqrestore(&drvdata->spinlock, flags); + return ret; +} + +ssize_t tmc_etr_get_trace(struct coresight_device *csdev, u32 mode, + loff_t poss, size_t len, char **buffpp) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + switch (mode) { + case CS_MODE_SYSFS: + return tmc_etr_get_sysfs_trace(drvdata, poss, len, buffpp); + case CS_MODE_API: + return tmc_etr_get_api_trace(drvdata, poss, len, buffpp); + default: + return -EINVAL; + } +} + static struct etr_buf * tmc_etr_setup_sysfs_buf(struct tmc_drvdata *drvdata) { @@ -1037,6 +1102,17 @@ static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata) } }
+static void tmc_etr_sync_api_buf(struct tmc_drvdata *drvdata) +{ + struct etr_buf *etr_buf = drvdata->etr_buf; + + /* We aren't certain there is indeed an etr buffer here, the user + * might set it to NULL, he is controling the buffer. + */ + if (etr_buf && !WARN_ON(drvdata->api_buf != etr_buf)) + tmc_sync_etr_buf(drvdata); +} + static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) { CS_UNLOCK(drvdata->base); @@ -1048,9 +1124,10 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) */ if (drvdata->mode == CS_MODE_SYSFS) tmc_etr_sync_sysfs_buf(drvdata); + else if (drvdata->mode == CS_MODE_API) + tmc_etr_sync_api_buf(drvdata);
tmc_disable_hw(drvdata); - CS_LOCK(drvdata->base);
/* Disable CATU device if this ETR is connected to one */ @@ -1460,7 +1537,11 @@ void tmc_disable_etr_sink(struct coresight_device *csdev)
/* Disable the TMC only if it needs to */ if (drvdata->mode != CS_MODE_DISABLED) { - tmc_etr_disable_hw(drvdata); + /* Only disable the hardware if the mode isn't API, + * or, if the mode is API, only if we have a valid buffer. + */ + if (drvdata->mode != CS_MODE_API || drvdata->api_buf != NULL) + tmc_etr_disable_hw(drvdata); drvdata->mode = CS_MODE_DISABLED; }
@@ -1476,6 +1557,7 @@ static const struct coresight_ops_sink tmc_etr_sink_ops = { .update_buffer = tmc_update_etr_buffer, .set_buffer = tmc_etr_set_buffer, .free_buffer = tmc_free_etr_buffer, + .get_trace = tmc_etr_get_trace };
const struct coresight_ops tmc_etr_cs_ops = { diff --git a/include/linux/coresight.h b/include/linux/coresight.h index c29a3bb4c386..0a57a2ce8d4d 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -187,6 +187,7 @@ struct coresight_device { * @free_buffer: release memory allocated in @get_config. * @set_buffer: set the trace buffer. * @update_buffer: update buffer pointers after a trace session. + * @get_trace: get a pointer to the trace data. */ struct coresight_ops_sink { int (*enable)(struct coresight_device *csdev, u32 mode, void *data); @@ -200,6 +201,8 @@ struct coresight_ops_sink { unsigned long (*update_buffer)(struct coresight_device *csdev, struct perf_output_handle *handle, void *sink_config); + ssize_t (*get_trace)(struct coresight_device *csdev, u32 mode, + loff_t poss, size_t len, char **buf); };
/**