coresight: Move reference counting inside sink drivers
authorMathieu Poirier <mathieu.poirier@linaro.org>
Thu, 25 Apr 2019 19:52:56 +0000 (13:52 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 Apr 2019 20:00:16 +0000 (22:00 +0200)
When operating in CPU-wide mode with an N:1 source/sink HW topology,
multiple CPUs can access a sink concurrently.  As such reference counting
needs to happen when the device's spinlock is held to avoid racing with
other operations (start(), update(), stop()), such as:

session A Session B
----- -------

enable_sink
atomic_inc(refcount)  = 1

...

atomic_dec(refcount) = 0 enable_sink
if (refcount == 0) disable_sink
atomic_inc()

Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Tested-by: Leo Yan <leo.yan@linaro.org>
Tested-by: Robert Walker <robert.walker@arm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/hwtracing/coresight/coresight-etb10.c
drivers/hwtracing/coresight/coresight-tmc-etf.c
drivers/hwtracing/coresight/coresight-tmc-etr.c
drivers/hwtracing/coresight/coresight-tpiu.c
drivers/hwtracing/coresight/coresight.c

index 23b049cef19a4be3f1ceb04a70b7528b7d8f1817..8e63863cf9508118d41df1a806dcd62dc9fdf2c9 100644 (file)
@@ -5,6 +5,7 @@
  * Description: CoreSight Embedded Trace Buffer driver
  */
 
+#include <linux/atomic.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
@@ -151,14 +152,15 @@ static int etb_enable_sysfs(struct coresight_device *csdev)
                goto out;
        }
 
-       /* Nothing to do, the tracer is already enabled. */
-       if (drvdata->mode == CS_MODE_SYSFS)
-               goto out;
+       if (drvdata->mode == CS_MODE_DISABLED) {
+               ret = etb_enable_hw(drvdata);
+               if (ret)
+                       goto out;
 
-       ret = etb_enable_hw(drvdata);
-       if (!ret)
                drvdata->mode = CS_MODE_SYSFS;
+       }
 
+       atomic_inc(csdev->refcnt);
 out:
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
        return ret;
@@ -188,8 +190,10 @@ static int etb_enable_perf(struct coresight_device *csdev, void *data)
                goto out;
 
        ret = etb_enable_hw(drvdata);
-       if (!ret)
+       if (!ret) {
                drvdata->mode = CS_MODE_PERF;
+               atomic_inc(csdev->refcnt);
+       }
 
 out:
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
@@ -324,6 +328,11 @@ static int etb_disable(struct coresight_device *csdev)
 
        spin_lock_irqsave(&drvdata->spinlock, flags);
 
+       if (atomic_dec_return(csdev->refcnt)) {
+               spin_unlock_irqrestore(&drvdata->spinlock, flags);
+               return -EBUSY;
+       }
+
        /* Disable the ETB only if it needs to */
        if (drvdata->mode != CS_MODE_DISABLED) {
                etb_disable_hw(drvdata);
index d4213e7c2c4535254626fe80a4665eb90fa5e134..d50a608a60f1d0902f4b08f94963aa3f049a60f6 100644 (file)
@@ -4,6 +4,7 @@
  * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
  */
 
+#include <linux/atomic.h>
 #include <linux/circ_buf.h>
 #include <linux/coresight.h>
 #include <linux/perf_event.h>
@@ -180,8 +181,10 @@ static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev)
         * sink is already enabled no memory is needed and the HW need not be
         * touched.
         */
-       if (drvdata->mode == CS_MODE_SYSFS)
+       if (drvdata->mode == CS_MODE_SYSFS) {
+               atomic_inc(csdev->refcnt);
                goto out;
+       }
 
        /*
         * If drvdata::buf isn't NULL, memory was allocated for a previous
@@ -200,11 +203,13 @@ static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev)
        }
 
        ret = tmc_etb_enable_hw(drvdata);
-       if (!ret)
+       if (!ret) {
                drvdata->mode = CS_MODE_SYSFS;
-       else
+               atomic_inc(csdev->refcnt);
+       } else {
                /* Free up the buffer if we failed to enable */
                used = false;
+       }
 out:
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
 
@@ -239,8 +244,10 @@ static int tmc_enable_etf_sink_perf(struct coresight_device *csdev, void *data)
                if (ret)
                        break;
                ret  = tmc_etb_enable_hw(drvdata);
-               if (!ret)
+               if (!ret) {
                        drvdata->mode = CS_MODE_PERF;
+                       atomic_inc(csdev->refcnt);
+               }
        } while (0);
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
 
@@ -279,11 +286,17 @@ static int tmc_disable_etf_sink(struct coresight_device *csdev)
        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 -EBUSY;
        }
 
+       if (atomic_dec_return(csdev->refcnt)) {
+               spin_unlock_irqrestore(&drvdata->spinlock, flags);
+               return -EBUSY;
+       }
+
        /* Disable the TMC only if it needs to */
        if (drvdata->mode != CS_MODE_DISABLED) {
                tmc_etb_disable_hw(drvdata);
index c6a0f589768434765960e22d4659c19af933c8ab..6a8f39d03de363c08d63e9d1506f91d9b4b65aa1 100644 (file)
@@ -4,6 +4,7 @@
  * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
  */
 
+#include <linux/atomic.h>
 #include <linux/coresight.h>
 #include <linux/dma-mapping.h>
 #include <linux/iommu.h>
@@ -1125,8 +1126,10 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
         * sink is already enabled no memory is needed and the HW need not be
         * touched, even if the buffer size has changed.
         */
-       if (drvdata->mode == CS_MODE_SYSFS)
+       if (drvdata->mode == CS_MODE_SYSFS) {
+               atomic_inc(csdev->refcnt);
                goto out;
+       }
 
        /*
         * If we don't have a buffer or it doesn't match the requested size,
@@ -1139,8 +1142,10 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
        }
 
        ret = tmc_etr_enable_hw(drvdata, drvdata->sysfs_buf);
-       if (!ret)
+       if (!ret) {
                drvdata->mode = CS_MODE_SYSFS;
+               atomic_inc(csdev->refcnt);
+       }
 out:
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
 
@@ -1371,8 +1376,10 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
        etr_perf->head = PERF_IDX2OFF(handle->head, etr_perf);
        drvdata->perf_data = etr_perf;
        rc = tmc_etr_enable_hw(drvdata, etr_perf->etr_buf);
-       if (!rc)
+       if (!rc) {
                drvdata->mode = CS_MODE_PERF;
+               atomic_inc(csdev->refcnt);
+       }
 
 unlock_out:
        spin_unlock_irqrestore(&drvdata->spinlock, flags);
@@ -1399,11 +1406,17 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
        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 -EBUSY;
        }
 
+       if (atomic_dec_return(csdev->refcnt)) {
+               spin_unlock_irqrestore(&drvdata->spinlock, flags);
+               return -EBUSY;
+       }
+
        /* Disable the TMC only if it needs to */
        if (drvdata->mode != CS_MODE_DISABLED) {
                tmc_etr_disable_hw(drvdata);
index c1b77e3c3187da36782aab8592b05cd531818233..63d9af31f57fb66eefd416144a70a355bd8632e1 100644 (file)
@@ -5,6 +5,7 @@
  * Description: CoreSight Trace Port Interface Unit driver
  */
 
+#include <linux/atomic.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/device.h>
@@ -73,7 +74,7 @@ static int tpiu_enable(struct coresight_device *csdev, u32 mode, void *__unused)
        struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
 
        tpiu_enable_hw(drvdata);
-
+       atomic_inc(csdev->refcnt);
        dev_dbg(drvdata->dev, "TPIU enabled\n");
        return 0;
 }
@@ -98,6 +99,9 @@ static int tpiu_disable(struct coresight_device *csdev)
 {
        struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
 
+       if (atomic_dec_return(csdev->refcnt))
+               return -EBUSY;
+
        tpiu_disable_hw(drvdata);
 
        dev_dbg(drvdata->dev, "TPIU disabled\n");
index 66c25b0a785ea1d250e928098f3b388e34ffced4..4b130281236a25c0e9ebaac850ba5ac1f2ac0a99 100644 (file)
@@ -225,14 +225,13 @@ static int coresight_enable_sink(struct coresight_device *csdev,
         * We need to make sure the "new" session is compatible with the
         * existing "mode" of operation.
         */
-       if (sink_ops(csdev)->enable) {
-               ret = sink_ops(csdev)->enable(csdev, mode, data);
-               if (ret)
-                       return ret;
-               csdev->enable = true;
-       }
+       if (!sink_ops(csdev)->enable)
+               return -EINVAL;
 
-       atomic_inc(csdev->refcnt);
+       ret = sink_ops(csdev)->enable(csdev, mode, data);
+       if (ret)
+               return ret;
+       csdev->enable = true;
 
        return 0;
 }
@@ -241,14 +240,13 @@ static void coresight_disable_sink(struct coresight_device *csdev)
 {
        int ret;
 
-       if (atomic_dec_return(csdev->refcnt) == 0) {
-               if (sink_ops(csdev)->disable) {
-                       ret = sink_ops(csdev)->disable(csdev);
-                       if (ret)
-                               return;
-                       csdev->enable = false;
-               }
-       }
+       if (!sink_ops(csdev)->disable)
+               return;
+
+       ret = sink_ops(csdev)->disable(csdev);
+       if (ret)
+               return;
+       csdev->enable = false;
 }
 
 static int coresight_enable_link(struct coresight_device *csdev,