Commit edda32da authored by Yabin Cui's avatar Yabin Cui Committed by Greg Kroah-Hartman

coresight: Serialize enabling/disabling a link device.

When tracing etm data of multiple threads on multiple cpus through perf
interface, some link devices are shared between paths of different cpus.
It creates race conditions when different cpus wants to enable/disable
the same link device at the same time.

Example 1:
Two cpus want to enable different ports of a coresight funnel, thus
calling the funnel enable operation at the same time. But the funnel
enable operation isn't reentrantable.

Example 2:
For an enabled coresight dynamic replicator with refcnt=1, one cpu wants
to disable it, while another cpu wants to enable it. Ideally we still have
an enabled replicator with refcnt=1 at the end. But in reality the result
is uncertain.

Since coresight devices claim themselves when enabled for self-hosted
usage, the race conditions above usually make the link devices not usable
after many cycles.

To fix the race conditions, this patch uses spinlocks to serialize
enabling/disabling link devices.

Fixes: a06ae860 ("coresight: add CoreSight core layer framework")
Signed-off-by: default avatarYabin Cui <yabinc@google.com>
Signed-off-by: default avatarMathieu Poirier <mathieu.poirier@linaro.org>
Cc: stable <stable@vger.kernel.org> # 5.3
Link: https://lore.kernel.org/r/20191104181251.26732-14-mathieu.poirier@linaro.orgSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f08d6882
...@@ -38,12 +38,14 @@ DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel"); ...@@ -38,12 +38,14 @@ DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel");
* @atclk: optional clock for the core parts of the funnel. * @atclk: optional clock for the core parts of the funnel.
* @csdev: component vitals needed by the framework. * @csdev: component vitals needed by the framework.
* @priority: port selection order. * @priority: port selection order.
* @spinlock: serialize enable/disable operations.
*/ */
struct funnel_drvdata { struct funnel_drvdata {
void __iomem *base; void __iomem *base;
struct clk *atclk; struct clk *atclk;
struct coresight_device *csdev; struct coresight_device *csdev;
unsigned long priority; unsigned long priority;
spinlock_t spinlock;
}; };
static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port) static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
...@@ -76,11 +78,21 @@ static int funnel_enable(struct coresight_device *csdev, int inport, ...@@ -76,11 +78,21 @@ static int funnel_enable(struct coresight_device *csdev, int inport,
{ {
int rc = 0; int rc = 0;
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
unsigned long flags;
bool first_enable = false;
spin_lock_irqsave(&drvdata->spinlock, flags);
if (atomic_read(&csdev->refcnt[inport]) == 0) {
if (drvdata->base) if (drvdata->base)
rc = dynamic_funnel_enable_hw(drvdata, inport); rc = dynamic_funnel_enable_hw(drvdata, inport);
if (!rc) if (!rc)
first_enable = true;
}
if (!rc)
atomic_inc(&csdev->refcnt[inport]);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (first_enable)
dev_dbg(&csdev->dev, "FUNNEL inport %d enabled\n", inport); dev_dbg(&csdev->dev, "FUNNEL inport %d enabled\n", inport);
return rc; return rc;
} }
...@@ -107,10 +119,18 @@ static void funnel_disable(struct coresight_device *csdev, int inport, ...@@ -107,10 +119,18 @@ static void funnel_disable(struct coresight_device *csdev, int inport,
int outport) int outport)
{ {
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
unsigned long flags;
bool last_disable = false;
spin_lock_irqsave(&drvdata->spinlock, flags);
if (atomic_dec_return(&csdev->refcnt[inport]) == 0) {
if (drvdata->base) if (drvdata->base)
dynamic_funnel_disable_hw(drvdata, inport); dynamic_funnel_disable_hw(drvdata, inport);
last_disable = true;
}
spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (last_disable)
dev_dbg(&csdev->dev, "FUNNEL inport %d disabled\n", inport); dev_dbg(&csdev->dev, "FUNNEL inport %d disabled\n", inport);
} }
......
...@@ -31,11 +31,13 @@ DEFINE_CORESIGHT_DEVLIST(replicator_devs, "replicator"); ...@@ -31,11 +31,13 @@ DEFINE_CORESIGHT_DEVLIST(replicator_devs, "replicator");
* whether this one is programmable or not. * whether this one is programmable or not.
* @atclk: optional clock for the core parts of the replicator. * @atclk: optional clock for the core parts of the replicator.
* @csdev: component vitals needed by the framework * @csdev: component vitals needed by the framework
* @spinlock: serialize enable/disable operations.
*/ */
struct replicator_drvdata { struct replicator_drvdata {
void __iomem *base; void __iomem *base;
struct clk *atclk; struct clk *atclk;
struct coresight_device *csdev; struct coresight_device *csdev;
spinlock_t spinlock;
}; };
static void dynamic_replicator_reset(struct replicator_drvdata *drvdata) static void dynamic_replicator_reset(struct replicator_drvdata *drvdata)
...@@ -97,10 +99,22 @@ static int replicator_enable(struct coresight_device *csdev, int inport, ...@@ -97,10 +99,22 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
{ {
int rc = 0; int rc = 0;
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
unsigned long flags;
bool first_enable = false;
spin_lock_irqsave(&drvdata->spinlock, flags);
if (atomic_read(&csdev->refcnt[outport]) == 0) {
if (drvdata->base) if (drvdata->base)
rc = dynamic_replicator_enable(drvdata, inport, outport); rc = dynamic_replicator_enable(drvdata, inport,
outport);
if (!rc) if (!rc)
first_enable = true;
}
if (!rc)
atomic_inc(&csdev->refcnt[outport]);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (first_enable)
dev_dbg(&csdev->dev, "REPLICATOR enabled\n"); dev_dbg(&csdev->dev, "REPLICATOR enabled\n");
return rc; return rc;
} }
...@@ -137,9 +151,18 @@ static void replicator_disable(struct coresight_device *csdev, int inport, ...@@ -137,9 +151,18 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
int outport) int outport)
{ {
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
unsigned long flags;
bool last_disable = false;
spin_lock_irqsave(&drvdata->spinlock, flags);
if (atomic_dec_return(&csdev->refcnt[outport]) == 0) {
if (drvdata->base) if (drvdata->base)
dynamic_replicator_disable(drvdata, inport, outport); dynamic_replicator_disable(drvdata, inport, outport);
last_disable = true;
}
spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (last_disable)
dev_dbg(&csdev->dev, "REPLICATOR disabled\n"); dev_dbg(&csdev->dev, "REPLICATOR disabled\n");
} }
......
...@@ -334,9 +334,10 @@ static int tmc_disable_etf_sink(struct coresight_device *csdev) ...@@ -334,9 +334,10 @@ static int tmc_disable_etf_sink(struct coresight_device *csdev)
static int tmc_enable_etf_link(struct coresight_device *csdev, static int tmc_enable_etf_link(struct coresight_device *csdev,
int inport, int outport) int inport, int outport)
{ {
int ret; int ret = 0;
unsigned long flags; unsigned long flags;
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
bool first_enable = false;
spin_lock_irqsave(&drvdata->spinlock, flags); spin_lock_irqsave(&drvdata->spinlock, flags);
if (drvdata->reading) { if (drvdata->reading) {
...@@ -344,12 +345,18 @@ static int tmc_enable_etf_link(struct coresight_device *csdev, ...@@ -344,12 +345,18 @@ static int tmc_enable_etf_link(struct coresight_device *csdev,
return -EBUSY; return -EBUSY;
} }
if (atomic_read(&csdev->refcnt[0]) == 0) {
ret = tmc_etf_enable_hw(drvdata); ret = tmc_etf_enable_hw(drvdata);
if (!ret) if (!ret) {
drvdata->mode = CS_MODE_SYSFS; drvdata->mode = CS_MODE_SYSFS;
first_enable = true;
}
}
if (!ret)
atomic_inc(&csdev->refcnt[0]);
spin_unlock_irqrestore(&drvdata->spinlock, flags); spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (!ret) if (first_enable)
dev_dbg(&csdev->dev, "TMC-ETF enabled\n"); dev_dbg(&csdev->dev, "TMC-ETF enabled\n");
return ret; return ret;
} }
...@@ -359,6 +366,7 @@ static void tmc_disable_etf_link(struct coresight_device *csdev, ...@@ -359,6 +366,7 @@ static void tmc_disable_etf_link(struct coresight_device *csdev,
{ {
unsigned long flags; unsigned long flags;
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
bool last_disable = false;
spin_lock_irqsave(&drvdata->spinlock, flags); spin_lock_irqsave(&drvdata->spinlock, flags);
if (drvdata->reading) { if (drvdata->reading) {
...@@ -366,10 +374,14 @@ static void tmc_disable_etf_link(struct coresight_device *csdev, ...@@ -366,10 +374,14 @@ static void tmc_disable_etf_link(struct coresight_device *csdev,
return; return;
} }
if (atomic_dec_return(&csdev->refcnt[0]) == 0) {
tmc_etf_disable_hw(drvdata); tmc_etf_disable_hw(drvdata);
drvdata->mode = CS_MODE_DISABLED; drvdata->mode = CS_MODE_DISABLED;
last_disable = true;
}
spin_unlock_irqrestore(&drvdata->spinlock, flags); spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (last_disable)
dev_dbg(&csdev->dev, "TMC-ETF disabled\n"); dev_dbg(&csdev->dev, "TMC-ETF disabled\n");
} }
......
...@@ -253,9 +253,9 @@ static int coresight_enable_link(struct coresight_device *csdev, ...@@ -253,9 +253,9 @@ static int coresight_enable_link(struct coresight_device *csdev,
struct coresight_device *parent, struct coresight_device *parent,
struct coresight_device *child) struct coresight_device *child)
{ {
int ret; int ret = 0;
int link_subtype; int link_subtype;
int refport, inport, outport; int inport, outport;
if (!parent || !child) if (!parent || !child)
return -EINVAL; return -EINVAL;
...@@ -264,29 +264,17 @@ static int coresight_enable_link(struct coresight_device *csdev, ...@@ -264,29 +264,17 @@ static int coresight_enable_link(struct coresight_device *csdev,
outport = coresight_find_link_outport(csdev, child); outport = coresight_find_link_outport(csdev, child);
link_subtype = csdev->subtype.link_subtype; link_subtype = csdev->subtype.link_subtype;
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG && inport < 0)
refport = inport; return inport;
else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT && outport < 0)
refport = outport; return outport;
else
refport = 0;
if (refport < 0)
return refport;
if (atomic_inc_return(&csdev->refcnt[refport]) == 1) { if (link_ops(csdev)->enable)
if (link_ops(csdev)->enable) {
ret = link_ops(csdev)->enable(csdev, inport, outport); ret = link_ops(csdev)->enable(csdev, inport, outport);
if (ret) { if (!ret)
atomic_dec(&csdev->refcnt[refport]);
return ret;
}
}
}
csdev->enable = true; csdev->enable = true;
return 0; return ret;
} }
static void coresight_disable_link(struct coresight_device *csdev, static void coresight_disable_link(struct coresight_device *csdev,
...@@ -295,7 +283,7 @@ static void coresight_disable_link(struct coresight_device *csdev, ...@@ -295,7 +283,7 @@ static void coresight_disable_link(struct coresight_device *csdev,
{ {
int i, nr_conns; int i, nr_conns;
int link_subtype; int link_subtype;
int refport, inport, outport; int inport, outport;
if (!parent || !child) if (!parent || !child)
return; return;
...@@ -305,20 +293,15 @@ static void coresight_disable_link(struct coresight_device *csdev, ...@@ -305,20 +293,15 @@ static void coresight_disable_link(struct coresight_device *csdev,
link_subtype = csdev->subtype.link_subtype; link_subtype = csdev->subtype.link_subtype;
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) { if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) {
refport = inport;
nr_conns = csdev->pdata->nr_inport; nr_conns = csdev->pdata->nr_inport;
} else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) { } else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) {
refport = outport;
nr_conns = csdev->pdata->nr_outport; nr_conns = csdev->pdata->nr_outport;
} else { } else {
refport = 0;
nr_conns = 1; nr_conns = 1;
} }
if (atomic_dec_return(&csdev->refcnt[refport]) == 0) {
if (link_ops(csdev)->disable) if (link_ops(csdev)->disable)
link_ops(csdev)->disable(csdev, inport, outport); link_ops(csdev)->disable(csdev, inport, outport);
}
for (i = 0; i < nr_conns; i++) for (i = 0; i < nr_conns; i++)
if (atomic_read(&csdev->refcnt[i]) != 0) if (atomic_read(&csdev->refcnt[i]) != 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment