diff options
Diffstat (limited to 'drivers/hwtracing')
20 files changed, 315 insertions, 113 deletions
diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 2cd7c718198a..17741969026e 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -202,6 +202,21 @@ static void *etm_setup_aux(int event_cpu, void **pages, if (!event_data) return NULL; + /* + * In theory nothing prevent tracers in a trace session from being + * associated with different sinks, nor having a sink per tracer. But + * until we have HW with this kind of topology we need to assume tracers + * in a trace session are using the same sink. Therefore go through + * the coresight bus and pick the first enabled sink. + * + * When operated from sysFS users are responsible to enable the sink + * while from perf, the perf tools will do it based on the choice made + * on the cmd line. As such the "enable_sink" flag in sysFS is reset. + */ + sink = coresight_get_enabled_sink(true); + if (!sink) + goto err; + INIT_WORK(&event_data->work, free_event_data); mask = &event_data->mask; @@ -219,25 +234,11 @@ static void *etm_setup_aux(int event_cpu, void **pages, * list of devices from source to sink that can be * referenced later when the path is actually needed. */ - event_data->path[cpu] = coresight_build_path(csdev); + event_data->path[cpu] = coresight_build_path(csdev, sink); if (IS_ERR(event_data->path[cpu])) goto err; } - /* - * In theory nothing prevent tracers in a trace session from being - * associated with different sinks, nor having a sink per tracer. But - * until we have HW with this kind of topology and a way to convey - * sink assignement from the perf cmd line we need to assume tracers - * in a trace session are using the same sink. Therefore pick the sink - * found at the end of the first available path. - */ - cpu = cpumask_first(mask); - /* Grab the sink at the end of the path */ - sink = coresight_get_sink(event_data->path[cpu]); - if (!sink) - goto err; - if (!sink_ops(sink)->alloc_buffer) goto err; diff --git a/drivers/hwtracing/coresight/coresight-etm.h b/drivers/hwtracing/coresight/coresight-etm.h index 4a18ee499965..ad063d7444e1 100644 --- a/drivers/hwtracing/coresight/coresight-etm.h +++ b/drivers/hwtracing/coresight/coresight-etm.h @@ -89,11 +89,13 @@ /* ETMCR - 0x00 */ #define ETMCR_PWD_DWN BIT(0) #define ETMCR_STALL_MODE BIT(7) +#define ETMCR_BRANCH_BROADCAST BIT(8) #define ETMCR_ETM_PRG BIT(10) #define ETMCR_ETM_EN BIT(11) #define ETMCR_CYC_ACC BIT(12) #define ETMCR_CTXID_SIZE (BIT(14)|BIT(15)) #define ETMCR_TIMESTAMP_EN BIT(28) +#define ETMCR_RETURN_STACK BIT(29) /* ETMCCR - 0x04 */ #define ETMCCR_FIFOFULL BIT(23) /* ETMPDCR - 0x310 */ @@ -110,8 +112,11 @@ #define ETM_MODE_STALL BIT(2) #define ETM_MODE_TIMESTAMP BIT(3) #define ETM_MODE_CTXID BIT(4) +#define ETM_MODE_BBROAD BIT(5) +#define ETM_MODE_RET_STACK BIT(6) #define ETM_MODE_ALL (ETM_MODE_EXCLUDE | ETM_MODE_CYCACC | \ ETM_MODE_STALL | ETM_MODE_TIMESTAMP | \ + ETM_MODE_BBROAD | ETM_MODE_RET_STACK | \ ETM_MODE_CTXID | ETM_MODE_EXCL_KERN | \ ETM_MODE_EXCL_USER) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c index e9b071953f80..ca98ad13bb8c 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c @@ -146,7 +146,7 @@ static ssize_t mode_store(struct device *dev, goto err_unlock; } config->ctrl |= ETMCR_STALL_MODE; - } else + } else config->ctrl &= ~ETMCR_STALL_MODE; if (config->mode & ETM_MODE_TIMESTAMP) { @@ -164,6 +164,16 @@ static ssize_t mode_store(struct device *dev, else config->ctrl &= ~ETMCR_CTXID_SIZE; + if (config->mode & ETM_MODE_BBROAD) + config->ctrl |= ETMCR_BRANCH_BROADCAST; + else + config->ctrl &= ~ETMCR_BRANCH_BROADCAST; + + if (config->mode & ETM_MODE_RET_STACK) + config->ctrl |= ETMCR_RETURN_STACK; + else + config->ctrl &= ~ETMCR_RETURN_STACK; + if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) etm_config_trace_mode(config); diff --git a/drivers/hwtracing/coresight/coresight-etm3x.c b/drivers/hwtracing/coresight/coresight-etm3x.c index 3fe368b23d15..a51b6b64ecdf 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x.c +++ b/drivers/hwtracing/coresight/coresight-etm3x.c @@ -804,10 +804,10 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id) if (!etm_count++) { cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING, - "AP_ARM_CORESIGHT_STARTING", + "arm/coresight:starting", etm_starting_cpu, etm_dying_cpu); ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, - "AP_ARM_CORESIGHT_ONLINE", + "arm/coresight:online", etm_online_cpu, NULL); if (ret < 0) goto err_arch_supported; diff --git a/drivers/hwtracing/coresight/coresight-etm4x.c b/drivers/hwtracing/coresight/coresight-etm4x.c index 4db8d6a4d0cb..031480f2c34d 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.c +++ b/drivers/hwtracing/coresight/coresight-etm4x.c @@ -986,11 +986,11 @@ static int etm4_probe(struct amba_device *adev, const struct amba_id *id) dev_err(dev, "ETM arch init failed\n"); if (!etm4_count++) { - cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT4_STARTING, - "AP_ARM_CORESIGHT4_STARTING", + cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING, + "arm/coresight4:starting", etm4_starting_cpu, etm4_dying_cpu); ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, - "AP_ARM_CORESIGHT4_ONLINE", + "arm/coresight4:online", etm4_online_cpu, NULL); if (ret < 0) goto err_arch_supported; @@ -1037,7 +1037,7 @@ static int etm4_probe(struct amba_device *adev, const struct amba_id *id) err_arch_supported: if (--etm4_count == 0) { - cpuhp_remove_state_nocalls(CPUHP_AP_ARM_CORESIGHT4_STARTING); + cpuhp_remove_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING); if (hp_online) cpuhp_remove_state_nocalls(hp_online); } diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h index 196a14be4b3d..ef9d8e93e3b2 100644 --- a/drivers/hwtracing/coresight/coresight-priv.h +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -111,7 +111,9 @@ static inline void CS_UNLOCK(void __iomem *addr) void coresight_disable_path(struct list_head *path); int coresight_enable_path(struct list_head *path, u32 mode); struct coresight_device *coresight_get_sink(struct list_head *path); -struct list_head *coresight_build_path(struct coresight_device *csdev); +struct coresight_device *coresight_get_enabled_sink(bool reset); +struct list_head *coresight_build_path(struct coresight_device *csdev, + struct coresight_device *sink); void coresight_release_path(struct list_head *path); #ifdef CONFIG_CORESIGHT_SOURCE_ETM3X diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index 49e0f1b925a5..e4c55c5f9988 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -406,7 +406,7 @@ static long stm_generic_set_options(struct stm_data *stm_data, return 0; } -static ssize_t stm_generic_packet(struct stm_data *stm_data, +static ssize_t notrace stm_generic_packet(struct stm_data *stm_data, unsigned int master, unsigned int channel, unsigned int packet, @@ -419,10 +419,10 @@ static ssize_t stm_generic_packet(struct stm_data *stm_data, struct stm_drvdata, stm); if (!(drvdata && local_read(&drvdata->mode))) - return 0; + return -EACCES; if (channel >= drvdata->numsp) - return 0; + return -EINVAL; ch_addr = (unsigned long)stm_channel_addr(drvdata, channel); @@ -920,6 +920,11 @@ static struct amba_id stm_ids[] = { .mask = 0x0003ffff, .data = "STM32", }, + { + .id = 0x0003b963, + .mask = 0x0003ffff, + .data = "STM500", + }, { 0, 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c index d6941ea24d8d..1549436e2492 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etf.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c @@ -70,7 +70,7 @@ static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata) * When operating in sysFS mode the content of the buffer needs to be * read before the TMC is disabled. */ - if (local_read(&drvdata->mode) == CS_MODE_SYSFS) + if (drvdata->mode == CS_MODE_SYSFS) tmc_etb_dump_hw(drvdata); tmc_disable_hw(drvdata); @@ -103,19 +103,14 @@ static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata) CS_LOCK(drvdata->base); } -static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev, u32 mode) +static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev) { int ret = 0; bool used = false; char *buf = NULL; - long val; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - /* This shouldn't be happening */ - if (WARN_ON(mode != CS_MODE_SYSFS)) - return -EINVAL; - /* * If we don't have a buffer release the lock and allocate memory. * Otherwise keep the lock and move along. @@ -138,13 +133,12 @@ static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev, u32 mode) goto out; } - val = local_xchg(&drvdata->mode, mode); /* * 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 (val == CS_MODE_SYSFS) + if (drvdata->mode == CS_MODE_SYSFS) goto out; /* @@ -163,6 +157,7 @@ static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev, u32 mode) drvdata->buf = buf; } + drvdata->mode = CS_MODE_SYSFS; tmc_etb_enable_hw(drvdata); out: spin_unlock_irqrestore(&drvdata->spinlock, flags); @@ -177,34 +172,29 @@ out: return ret; } -static int tmc_enable_etf_sink_perf(struct coresight_device *csdev, u32 mode) +static int tmc_enable_etf_sink_perf(struct coresight_device *csdev) { int ret = 0; - long val; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - /* This shouldn't be happening */ - if (WARN_ON(mode != CS_MODE_PERF)) - return -EINVAL; - spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { ret = -EINVAL; goto out; } - val = local_xchg(&drvdata->mode, mode); /* * In Perf mode there can be only one writer per sink. There * is also no need to continue if the ETB/ETR is already operated * from sysFS. */ - if (val != CS_MODE_DISABLED) { + if (drvdata->mode != CS_MODE_DISABLED) { ret = -EINVAL; goto out; } + drvdata->mode = CS_MODE_PERF; tmc_etb_enable_hw(drvdata); out: spin_unlock_irqrestore(&drvdata->spinlock, flags); @@ -216,9 +206,9 @@ static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode) { switch (mode) { case CS_MODE_SYSFS: - return tmc_enable_etf_sink_sysfs(csdev, mode); + return tmc_enable_etf_sink_sysfs(csdev); case CS_MODE_PERF: - return tmc_enable_etf_sink_perf(csdev, mode); + return tmc_enable_etf_sink_perf(csdev); } /* We shouldn't be here */ @@ -227,7 +217,6 @@ static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode) static void tmc_disable_etf_sink(struct coresight_device *csdev) { - long val; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); @@ -237,10 +226,11 @@ static void tmc_disable_etf_sink(struct coresight_device *csdev) return; } - val = local_xchg(&drvdata->mode, CS_MODE_DISABLED); /* Disable the TMC only if it needs to */ - if (val != CS_MODE_DISABLED) + if (drvdata->mode != CS_MODE_DISABLED) { tmc_etb_disable_hw(drvdata); + drvdata->mode = CS_MODE_DISABLED; + } spin_unlock_irqrestore(&drvdata->spinlock, flags); @@ -260,7 +250,7 @@ static int tmc_enable_etf_link(struct coresight_device *csdev, } tmc_etf_enable_hw(drvdata); - local_set(&drvdata->mode, CS_MODE_SYSFS); + drvdata->mode = CS_MODE_SYSFS; spin_unlock_irqrestore(&drvdata->spinlock, flags); dev_info(drvdata->dev, "TMC-ETF enabled\n"); @@ -280,7 +270,7 @@ static void tmc_disable_etf_link(struct coresight_device *csdev, } tmc_etf_disable_hw(drvdata); - local_set(&drvdata->mode, CS_MODE_DISABLED); + drvdata->mode = CS_MODE_DISABLED; spin_unlock_irqrestore(&drvdata->spinlock, flags); dev_info(drvdata->dev, "TMC disabled\n"); @@ -383,7 +373,7 @@ static void tmc_update_etf_buffer(struct coresight_device *csdev, return; /* This shouldn't happen */ - if (WARN_ON_ONCE(local_read(&drvdata->mode) != CS_MODE_PERF)) + if (WARN_ON_ONCE(drvdata->mode != CS_MODE_PERF)) return; CS_UNLOCK(drvdata->base); @@ -504,7 +494,6 @@ const struct coresight_ops tmc_etf_cs_ops = { int tmc_read_prepare_etb(struct tmc_drvdata *drvdata) { - long val; enum tmc_mode mode; int ret = 0; unsigned long flags; @@ -528,9 +517,8 @@ int tmc_read_prepare_etb(struct tmc_drvdata *drvdata) goto out; } - val = local_read(&drvdata->mode); /* Don't interfere if operated from Perf */ - if (val == CS_MODE_PERF) { + if (drvdata->mode == CS_MODE_PERF) { ret = -EINVAL; goto out; } @@ -542,7 +530,7 @@ int tmc_read_prepare_etb(struct tmc_drvdata *drvdata) } /* Disable the TMC if need be */ - if (val == CS_MODE_SYSFS) + if (drvdata->mode == CS_MODE_SYSFS) tmc_etb_disable_hw(drvdata); drvdata->reading = true; @@ -573,7 +561,7 @@ int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata) } /* Re-enable the TMC if need be */ - if (local_read(&drvdata->mode) == CS_MODE_SYSFS) { + if (drvdata->mode == CS_MODE_SYSFS) { /* * The trace run will continue with the same allocated trace * buffer. As such zero-out the buffer so that we don't end diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 886ea83c68e0..5d312699b3b9 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -86,26 +86,22 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) * When operating in sysFS mode the content of the buffer needs to be * read before the TMC is disabled. */ - if (local_read(&drvdata->mode) == CS_MODE_SYSFS) + 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, u32 mode) +static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) { int ret = 0; bool used = false; - long val; unsigned long flags; void __iomem *vaddr = NULL; dma_addr_t paddr; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - /* This shouldn't be happening */ - if (WARN_ON(mode != CS_MODE_SYSFS)) - return -EINVAL; /* * If we don't have a buffer release the lock and allocate memory. @@ -134,13 +130,12 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev, u32 mode) goto out; } - val = local_xchg(&drvdata->mode, mode); /* * 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 (val == CS_MODE_SYSFS) + if (drvdata->mode == CS_MODE_SYSFS) goto out; /* @@ -155,8 +150,7 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev, u32 mode) drvdata->buf = drvdata->vaddr; } - memset(drvdata->vaddr, 0, drvdata->size); - + drvdata->mode = CS_MODE_SYSFS; tmc_etr_enable_hw(drvdata); out: spin_unlock_irqrestore(&drvdata->spinlock, flags); @@ -171,34 +165,29 @@ out: return ret; } -static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, u32 mode) +static int tmc_enable_etr_sink_perf(struct coresight_device *csdev) { int ret = 0; - long val; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - /* This shouldn't be happening */ - if (WARN_ON(mode != CS_MODE_PERF)) - return -EINVAL; - spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { ret = -EINVAL; goto out; } - val = local_xchg(&drvdata->mode, mode); /* * 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 (val != CS_MODE_DISABLED) { + 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); @@ -210,9 +199,9 @@ 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, mode); + return tmc_enable_etr_sink_sysfs(csdev); case CS_MODE_PERF: - return tmc_enable_etr_sink_perf(csdev, mode); + return tmc_enable_etr_sink_perf(csdev); } /* We shouldn't be here */ @@ -221,7 +210,6 @@ static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode) static void tmc_disable_etr_sink(struct coresight_device *csdev) { - long val; unsigned long flags; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); @@ -231,10 +219,11 @@ static void tmc_disable_etr_sink(struct coresight_device *csdev) return; } - val = local_xchg(&drvdata->mode, CS_MODE_DISABLED); /* Disable the TMC only if it needs to */ - if (val != CS_MODE_DISABLED) + if (drvdata->mode != CS_MODE_DISABLED) { tmc_etr_disable_hw(drvdata); + drvdata->mode = CS_MODE_DISABLED; + } spin_unlock_irqrestore(&drvdata->spinlock, flags); @@ -253,7 +242,6 @@ const struct coresight_ops tmc_etr_cs_ops = { int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) { int ret = 0; - long val; unsigned long flags; /* config types are set a boot time and never change */ @@ -266,9 +254,8 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) goto out; } - val = local_read(&drvdata->mode); /* Don't interfere if operated from Perf */ - if (val == CS_MODE_PERF) { + if (drvdata->mode == CS_MODE_PERF) { ret = -EINVAL; goto out; } @@ -280,7 +267,7 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) } /* Disable the TMC if need be */ - if (val == CS_MODE_SYSFS) + if (drvdata->mode == CS_MODE_SYSFS) tmc_etr_disable_hw(drvdata); drvdata->reading = true; @@ -303,7 +290,7 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) spin_lock_irqsave(&drvdata->spinlock, flags); /* RE-enable the TMC if need be */ - if (local_read(&drvdata->mode) == CS_MODE_SYSFS) { + 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(), diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 44b3ae346118..51c01851533e 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -117,7 +117,7 @@ struct tmc_drvdata { void __iomem *vaddr; u32 size; u32 len; - local_t mode; + u32 mode; enum tmc_config_type config_type; enum tmc_mem_intf_width memwidth; u32 trigger_cntr; diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index 7bf00a0beb6f..0c37356e417c 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -368,6 +368,52 @@ struct coresight_device *coresight_get_sink(struct list_head *path) return csdev; } +static int coresight_enabled_sink(struct device *dev, void *data) +{ + bool *reset = data; + struct coresight_device *csdev = to_coresight_device(dev); + + if ((csdev->type == CORESIGHT_DEV_TYPE_SINK || + csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && + csdev->activated) { + /* + * Now that we have a handle on the sink for this session, + * disable the sysFS "enable_sink" flag so that possible + * concurrent perf session that wish to use another sink don't + * trip on it. Doing so has no ramification for the current + * session. + */ + if (*reset) + csdev->activated = false; + + return 1; + } + + return 0; +} + +/** + * coresight_get_enabled_sink - returns the first enabled sink found on the bus + * @deactivate: Whether the 'enable_sink' flag should be reset + * + * When operated from perf the deactivate parameter should be set to 'true'. + * That way the "enabled_sink" flag of the sink that was selected can be reset, + * allowing for other concurrent perf sessions to choose a different sink. + * + * When operated from sysFS users have full control and as such the deactivate + * parameter should be set to 'false', hence mandating users to explicitly + * clear the flag. + */ +struct coresight_device *coresight_get_enabled_sink(bool deactivate) +{ + struct device *dev = NULL; + + dev = bus_find_device(&coresight_bustype, NULL, &deactivate, + coresight_enabled_sink); + + return dev ? to_coresight_device(dev) : NULL; +} + /** * _coresight_build_path - recursively build a path from a @csdev to a sink. * @csdev: The device to start from. @@ -380,6 +426,7 @@ struct coresight_device *coresight_get_sink(struct list_head *path) * last one. */ static int _coresight_build_path(struct coresight_device *csdev, + struct coresight_device *sink, struct list_head *path) { int i; @@ -387,15 +434,15 @@ static int _coresight_build_path(struct coresight_device *csdev, struct coresight_node *node; /* An activated sink has been found. Enqueue the element */ - if ((csdev->type == CORESIGHT_DEV_TYPE_SINK || - csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && csdev->activated) + if (csdev == sink) goto out; /* Not a sink - recursively explore each port found on this element */ for (i = 0; i < csdev->nr_outport; i++) { struct coresight_device *child_dev = csdev->conns[i].child_dev; - if (child_dev && _coresight_build_path(child_dev, path) == 0) { + if (child_dev && + _coresight_build_path(child_dev, sink, path) == 0) { found = true; break; } @@ -422,18 +469,22 @@ out: return 0; } -struct list_head *coresight_build_path(struct coresight_device *csdev) +struct list_head *coresight_build_path(struct coresight_device *source, + struct coresight_device *sink) { struct list_head *path; int rc; + if (!sink) + return ERR_PTR(-EINVAL); + path = kzalloc(sizeof(struct list_head), GFP_KERNEL); if (!path) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(path); - rc = _coresight_build_path(csdev, path); + rc = _coresight_build_path(source, sink, path); if (rc) { kfree(path); return ERR_PTR(rc); @@ -497,6 +548,7 @@ static int coresight_validate_source(struct coresight_device *csdev, int coresight_enable(struct coresight_device *csdev) { int cpu, ret = 0; + struct coresight_device *sink; struct list_head *path; mutex_lock(&coresight_mutex); @@ -508,7 +560,17 @@ int coresight_enable(struct coresight_device *csdev) if (csdev->enable) goto out; - path = coresight_build_path(csdev); + /* + * Search for a valid sink for this session but don't reset the + * "enable_sink" flag in sysFS. Users get to do that explicitly. + */ + sink = coresight_get_enabled_sink(false); + if (!sink) { + ret = -EINVAL; + goto out; + } + + path = coresight_build_path(csdev, sink); if (IS_ERR(path)) { pr_err("building path(s) failed\n"); ret = PTR_ERR(path); diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index 6f0a51a2c6ec..cdd9b3b26195 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -29,6 +29,9 @@ #include "intel_th.h" #include "debug.h" +static bool host_mode __read_mostly; +module_param(host_mode, bool, 0444); + static DEFINE_IDA(intel_th_ida); static int intel_th_match(struct device *dev, struct device_driver *driver) @@ -380,7 +383,7 @@ static void intel_th_device_free(struct intel_th_device *thdev) /* * Intel(R) Trace Hub subdevices */ -static struct intel_th_subdevice { +static const struct intel_th_subdevice { const char *name; struct resource res[3]; unsigned nres; @@ -527,14 +530,19 @@ static int intel_th_populate(struct intel_th *th, struct resource *devres, { struct resource res[3]; unsigned int req = 0; - int i, err; + int src, dst, err; /* create devices for each intel_th_subdevice */ - for (i = 0; i < ARRAY_SIZE(intel_th_subdevices); i++) { - struct intel_th_subdevice *subdev = &intel_th_subdevices[i]; + for (src = 0, dst = 0; src < ARRAY_SIZE(intel_th_subdevices); src++) { + const struct intel_th_subdevice *subdev = + &intel_th_subdevices[src]; struct intel_th_device *thdev; int r; + /* only allow SOURCE and SWITCH devices in host mode */ + if (host_mode && subdev->type == INTEL_TH_OUTPUT) + continue; + thdev = intel_th_device_alloc(th, subdev->type, subdev->name, subdev->id); if (!thdev) { @@ -577,10 +585,12 @@ static int intel_th_populate(struct intel_th *th, struct resource *devres, } if (subdev->type == INTEL_TH_OUTPUT) { - thdev->dev.devt = MKDEV(th->major, i); + thdev->dev.devt = MKDEV(th->major, dst); thdev->output.type = subdev->otype; thdev->output.port = -1; thdev->output.scratchpad = subdev->scrpd; + } else if (subdev->type == INTEL_TH_SWITCH) { + thdev->host_mode = host_mode; } err = device_add(&thdev->dev); @@ -597,14 +607,14 @@ static int intel_th_populate(struct intel_th *th, struct resource *devres, req++; } - th->thdev[i] = thdev; + th->thdev[dst++] = thdev; } return 0; kill_subdevs: - for (i-- ; i >= 0; i--) - intel_th_device_remove(th->thdev[i]); + for (; dst >= 0; dst--) + intel_th_device_remove(th->thdev[dst]); return err; } @@ -717,7 +727,7 @@ void intel_th_free(struct intel_th *th) intel_th_request_hub_module_flush(th); for (i = 0; i < TH_SUBDEVICE_MAX; i++) - if (th->thdev[i] != th->hub) + if (th->thdev[i] && th->thdev[i] != th->hub) intel_th_device_remove(th->thdev[i]); intel_th_device_remove(th->hub); diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c index 33e09369a491..dd32d0bad687 100644 --- a/drivers/hwtracing/intel_th/gth.c +++ b/drivers/hwtracing/intel_th/gth.c @@ -564,6 +564,9 @@ static int intel_th_gth_assign(struct intel_th_device *thdev, struct gth_device *gth = dev_get_drvdata(&thdev->dev); int i, id; + if (thdev->host_mode) + return -EBUSY; + if (othdev->type != INTEL_TH_OUTPUT) return -EINVAL; @@ -600,6 +603,9 @@ static void intel_th_gth_unassign(struct intel_th_device *thdev, struct gth_device *gth = dev_get_drvdata(&thdev->dev); int port = othdev->output.port; + if (thdev->host_mode) + return; + spin_lock(>h->gth_lock); othdev->output.port = -1; othdev->output.active = false; @@ -654,9 +660,24 @@ static int intel_th_gth_probe(struct intel_th_device *thdev) gth->base = base; spin_lock_init(>h->gth_lock); + /* + * Host mode can be signalled via SW means or via SCRPD_DEBUGGER_IN_USE + * bit. Either way, don't reset HW in this case, and don't export any + * capture configuration attributes. Also, refuse to assign output + * drivers to ports, see intel_th_gth_assign(). + */ + if (thdev->host_mode) + goto done; + ret = intel_th_gth_reset(gth); - if (ret) - return ret; + if (ret) { + if (ret != -EBUSY) + return ret; + + thdev->host_mode = true; + + goto done; + } for (i = 0; i < TH_CONFIGURABLE_MASTERS + 1; i++) gth->master[i] = -1; @@ -677,6 +698,7 @@ static int intel_th_gth_probe(struct intel_th_device *thdev) return -ENOMEM; } +done: dev_set_drvdata(dev, gth); return 0; diff --git a/drivers/hwtracing/intel_th/intel_th.h b/drivers/hwtracing/intel_th/intel_th.h index 4c195786bf1f..3096e7054f6d 100644 --- a/drivers/hwtracing/intel_th/intel_th.h +++ b/drivers/hwtracing/intel_th/intel_th.h @@ -54,6 +54,7 @@ struct intel_th_output { * @num_resources: number of resources in @resource array * @type: INTEL_TH_{SOURCE,OUTPUT,SWITCH} * @id: device instance or -1 + * @host_mode: Intel TH is controlled by an external debug host * @output: output descriptor for INTEL_TH_OUTPUT devices * @name: device name to match the driver */ @@ -64,6 +65,9 @@ struct intel_th_device { unsigned int type; int id; + /* INTEL_TH_SWITCH specific */ + bool host_mode; + /* INTEL_TH_OUTPUT specific */ struct intel_th_output output; diff --git a/drivers/hwtracing/intel_th/sth.c b/drivers/hwtracing/intel_th/sth.c index e1aee61dd7b3..b03444624648 100644 --- a/drivers/hwtracing/intel_th/sth.c +++ b/drivers/hwtracing/intel_th/sth.c @@ -67,10 +67,13 @@ static void sth_iowrite(void __iomem *dest, const unsigned char *payload, } } -static ssize_t sth_stm_packet(struct stm_data *stm_data, unsigned int master, - unsigned int channel, unsigned int packet, - unsigned int flags, unsigned int size, - const unsigned char *payload) +static ssize_t notrace sth_stm_packet(struct stm_data *stm_data, + unsigned int master, + unsigned int channel, + unsigned int packet, + unsigned int flags, + unsigned int size, + const unsigned char *payload) { struct sth_device *sth = container_of(stm_data, struct sth_device, stm); struct intel_th_channel __iomem *out = diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig index 847a39b35307..723e2d90083d 100644 --- a/drivers/hwtracing/stm/Kconfig +++ b/drivers/hwtracing/stm/Kconfig @@ -39,4 +39,15 @@ config STM_SOURCE_HEARTBEAT If you want to send heartbeat messages over STM devices, say Y. +config STM_SOURCE_FTRACE + tristate "Copy the output from kernel Ftrace to STM engine" + depends on FUNCTION_TRACER + help + This option can be used to copy the output from kernel Ftrace + to STM engine. Enabling this option will introduce a slight + timing effect. + + If you want to send kernel Ftrace messages over STM devices, + say Y. + endif diff --git a/drivers/hwtracing/stm/Makefile b/drivers/hwtracing/stm/Makefile index a9ce3d487e57..3abd84ce13d4 100644 --- a/drivers/hwtracing/stm/Makefile +++ b/drivers/hwtracing/stm/Makefile @@ -6,6 +6,8 @@ obj-$(CONFIG_STM_DUMMY) += dummy_stm.o obj-$(CONFIG_STM_SOURCE_CONSOLE) += stm_console.o obj-$(CONFIG_STM_SOURCE_HEARTBEAT) += stm_heartbeat.o +obj-$(CONFIG_STM_SOURCE_FTRACE) += stm_ftrace.o stm_console-y := console.o stm_heartbeat-y := heartbeat.o +stm_ftrace-y := ftrace.o diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c index 51f81d64ca37..0e731143f6a4 100644 --- a/drivers/hwtracing/stm/core.c +++ b/drivers/hwtracing/stm/core.c @@ -361,7 +361,7 @@ static int stm_char_open(struct inode *inode, struct file *file) struct stm_file *stmf; struct device *dev; unsigned int major = imajor(inode); - int err = -ENODEV; + int err = -ENOMEM; dev = class_find_device(&stm_class, NULL, &major, major_match); if (!dev) @@ -369,8 +369,9 @@ static int stm_char_open(struct inode *inode, struct file *file) stmf = kzalloc(sizeof(*stmf), GFP_KERNEL); if (!stmf) - return -ENOMEM; + goto err_put_device; + err = -ENODEV; stm_output_init(&stmf->output); stmf->stm = to_stm_device(dev); @@ -382,9 +383,10 @@ static int stm_char_open(struct inode *inode, struct file *file) return nonseekable_open(inode, file); err_free: + kfree(stmf); +err_put_device: /* matches class_find_device() above */ put_device(dev); - kfree(stmf); return err; } @@ -425,7 +427,7 @@ static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width) return ret; } -static ssize_t stm_write(struct stm_data *data, unsigned int master, +static ssize_t notrace stm_write(struct stm_data *data, unsigned int master, unsigned int channel, const char *buf, size_t count) { unsigned int flags = STP_PACKET_TIMESTAMPED; @@ -1121,8 +1123,9 @@ void stm_source_unregister_device(struct stm_source_data *data) } EXPORT_SYMBOL_GPL(stm_source_unregister_device); -int stm_source_write(struct stm_source_data *data, unsigned int chan, - const char *buf, size_t count) +int notrace stm_source_write(struct stm_source_data *data, + unsigned int chan, + const char *buf, size_t count) { struct stm_source_device *src = data->src; struct stm_device *stm; diff --git a/drivers/hwtracing/stm/dummy_stm.c b/drivers/hwtracing/stm/dummy_stm.c index a86612d989f9..c5f94ca31c4d 100644 --- a/drivers/hwtracing/stm/dummy_stm.c +++ b/drivers/hwtracing/stm/dummy_stm.c @@ -21,7 +21,7 @@ #include <linux/slab.h> #include <linux/stm.h> -static ssize_t +static ssize_t notrace dummy_stm_packet(struct stm_data *stm_data, unsigned int master, unsigned int channel, unsigned int packet, unsigned int flags, unsigned int size, const unsigned char *payload) diff --git a/drivers/hwtracing/stm/ftrace.c b/drivers/hwtracing/stm/ftrace.c new file mode 100644 index 000000000000..bd126a7c6da2 --- /dev/null +++ b/drivers/hwtracing/stm/ftrace.c @@ -0,0 +1,87 @@ +/* + * Simple kernel driver to link kernel Ftrace and an STM device + * Copyright (c) 2016, Linaro Ltd. + * + * 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. + * + * STM Ftrace will be registered as a trace_export. + */ + +#include <linux/module.h> +#include <linux/stm.h> +#include <linux/trace.h> + +#define STM_FTRACE_NR_CHANNELS 1 +#define STM_FTRACE_CHAN 0 + +static int stm_ftrace_link(struct stm_source_data *data); +static void stm_ftrace_unlink(struct stm_source_data *data); + +static struct stm_ftrace { + struct stm_source_data data; + struct trace_export ftrace; +} stm_ftrace = { + .data = { + .name = "ftrace", + .nr_chans = STM_FTRACE_NR_CHANNELS, + .link = stm_ftrace_link, + .unlink = stm_ftrace_unlink, + }, +}; + +/** + * stm_ftrace_write() - write data to STM via 'stm_ftrace' source + * @buf: buffer containing the data packet + * @len: length of the data packet + */ +static void notrace +stm_ftrace_write(const void *buf, unsigned int len) +{ + stm_source_write(&stm_ftrace.data, STM_FTRACE_CHAN, buf, len); +} + +static int stm_ftrace_link(struct stm_source_data *data) +{ + struct stm_ftrace *sf = container_of(data, struct stm_ftrace, data); + + sf->ftrace.write = stm_ftrace_write; + + return register_ftrace_export(&sf->ftrace); +} + +static void stm_ftrace_unlink(struct stm_source_data *data) +{ + struct stm_ftrace *sf = container_of(data, struct stm_ftrace, data); + + unregister_ftrace_export(&sf->ftrace); +} + +static int __init stm_ftrace_init(void) +{ + int ret; + + ret = stm_source_register_device(NULL, &stm_ftrace.data); + if (ret) + pr_err("Failed to register stm_source - ftrace.\n"); + + return ret; +} + +static void __exit stm_ftrace_exit(void) +{ + stm_source_unregister_device(&stm_ftrace.data); +} + +module_init(stm_ftrace_init); +module_exit(stm_ftrace_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("stm_ftrace driver"); +MODULE_AUTHOR("Chunyan Zhang <zhang.chunyan@linaro.org>"); |