]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
video: tegra: hdmi: Fix locks and race condition
authorAly Hirani <ahirani@nvidia.com>
Wed, 22 Jul 2015 20:52:23 +0000 (13:52 -0700)
committerAly Hirani <ahirani@nvidia.com>
Wed, 5 Aug 2015 21:09:24 +0000 (14:09 -0700)
The HDMI driver had a couple of race conditions that this change
attempts to fix.

1. ddc_enable/ddc_disable

tegra_hdmi_ddc_enable() and _ddc_disable() functions need to be
called before performing any ddc/i2c transactions on T210. These are
called at least from (1) edid thread, (2) scdc thread, (3) hdcp thread,
(4) from userspace by crc checker. The enable and disable functions
makes sure that the pads are powered on or powered off (with refcounting).
The hdmi driver was using a standard int with no locking around it,
causing typical race conditions and refcounting to go bad. This change
adds a new mutex to protect ddc_refcount.

2. clock_refcount_lock

The hdmi clocks are also refcounted with tegra_hdmi_get() and
tegra_hdmi_put(). They also make sure that the hdmi clocks are only
enabled and configured on the first enable() and turned off on the last
disable(). It was also using a standard int with no refcounting. This
change also adds a new mutex to protect clock_refcount_lock.

3. ddc_lock

ddc_lock was being held across some scdc transactions. However, this
is not needed. The i2c_transfer() function should be responsible for
maintaining the bus lock. Also, this is made worse by the fact that not
all ddc transactions were holding this lock.

Bug 200103101

Change-Id: Idaece90a5d7f9575c3f97e2848bcf1c605de693c
Signed-off-by: Aly Hirani <ahirani@nvidia.com>
Reviewed-on: http://git-master/r/766759
(cherry picked from commit 0a4c926462a5ef79872fe2af34f3baec7ef5ff78)
Reviewed-on: http://git-master/r/778832
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Jon Mayo <jmayo@nvidia.com>
drivers/video/tegra/dc/hdmi2.0.c
drivers/video/tegra/dc/hdmi2.0.h

index 0287e553c0fde0271fe60f271124e6f404e16e81..2fcf38f2ecdb5bd7c8c821a104fd676b81f53c1c 100644 (file)
@@ -290,8 +290,9 @@ int tegra_hdmi_setup_hda_presence(void)
 
 static inline void _tegra_hdmi_ddc_enable(struct tegra_hdmi *hdmi)
 {
+       mutex_lock(&hdmi->ddc_refcount_lock);
        if (hdmi->ddc_refcount++)
-               return;
+               goto fail;
        tegra_hdmi_get(hdmi->dc);
        /*
         * hdmi uses i2c lane muxed on dpaux1 pad.
@@ -299,20 +300,29 @@ static inline void _tegra_hdmi_ddc_enable(struct tegra_hdmi *hdmi)
         */
        tegra_dpaux_config_pad_mode(hdmi->dc, TEGRA_DPAUX_INSTANCE_1,
                                        TEGRA_DPAUX_PAD_MODE_I2C);
+
+fail:
+       mutex_unlock(&hdmi->ddc_refcount_lock);
 }
 
 static inline void _tegra_hdmi_ddc_disable(struct tegra_hdmi *hdmi)
 {
+       mutex_lock(&hdmi->ddc_refcount_lock);
+
        if (WARN_ONCE(hdmi->ddc_refcount <= 0, "ddc refcount imbalance"))
-               return;
+               goto fail;
        if (--hdmi->ddc_refcount != 0)
-               return;
+               goto fail;
+
        /*
         * hdmi uses i2c lane muxed on dpaux1 pad.
         * Disable dpaux1 pads.
         */
        tegra_dpaux_pad_power(hdmi->dc, TEGRA_DPAUX_INSTANCE_1, false);
        tegra_hdmi_put(hdmi->dc);
+
+fail:
+       mutex_unlock(&hdmi->ddc_refcount_lock);
 }
 
 static int tegra_hdmi_ddc_i2c_xfer(struct tegra_dc *dc,
@@ -364,8 +374,6 @@ static int tegra_hdmi_ddc_init(struct tegra_hdmi *hdmi, int edid_src)
                goto fail_edid_free;
        }
 
-       mutex_init(&hdmi->ddc_lock);
-
        return 0;
 fail_edid_free:
        tegra_edid_destroy(hdmi->edid);
@@ -612,9 +620,7 @@ static inline int tegra_hdmi_edid_read(struct tegra_hdmi *hdmi)
 {
        int err;
 
-       mutex_lock(&hdmi->ddc_lock);
        err = tegra_hdmi_get_mon_spec(hdmi);
-       mutex_unlock(&hdmi->ddc_lock);
 
        return err;
 }
@@ -1037,6 +1043,7 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc)
        hdmi->dc = dc;
        dc_hdmi = hdmi;
        hdmi->ddc_refcount = 0; /* assumes this is disabled when starting */
+       mutex_init(&hdmi->ddc_refcount_lock);
        hdmi->nvhdcp = NULL;
        hdmi->mon_spec_valid = false;
        hdmi->eld_valid = false;
@@ -1046,6 +1053,7 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc)
        } else {
                hdmi->enabled = false;
                hdmi->clock_refcount = 0;
+               mutex_init(&hdmi->clock_refcount_lock);
        }
 
 #ifdef CONFIG_TEGRA_HDMIHDCP
@@ -1828,9 +1836,7 @@ static int _tegra_hdmi_v2_x_config(struct tegra_hdmi *hdmi)
 
 static int tegra_hdmi_v2_x_config(struct tegra_hdmi *hdmi)
 {
-       mutex_lock(&hdmi->ddc_lock);
        _tegra_hdmi_v2_x_config(hdmi);
-       mutex_unlock(&hdmi->ddc_lock);
 
        return 0;
 }
@@ -1849,8 +1855,6 @@ static void tegra_hdmi_scdc_worker(struct work_struct *work)
        if (hdmi->dc->vedid)
                goto skip_scdc_i2c;
 
-       mutex_lock(&hdmi->ddc_lock);
-
        tegra_hdmi_scdc_read(hdmi, rd_tmds_config, ARRAY_SIZE(rd_tmds_config));
        if (!rd_tmds_config[0][1]  && (hdmi->dc->mode.pclk > 340000000)) {
                dev_info(&hdmi->dc->ndev->dev, "hdmi: scdc tmds_config lost, "
@@ -1858,8 +1862,6 @@ static void tegra_hdmi_scdc_worker(struct work_struct *work)
                _tegra_hdmi_v2_x_config(hdmi);
        }
 
-       mutex_unlock(&hdmi->ddc_lock);
-
 skip_scdc_i2c:
        /* reschedule the worker */
        cancel_delayed_work(&hdmi->scdc_work);
@@ -1885,23 +1887,30 @@ static void tegra_hdmi_get(struct tegra_dc *dc)
 {
        struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
 
+       mutex_lock(&hdmi->clock_refcount_lock);
+
        if (hdmi->clock_refcount++)
-               return;
+               goto fail;
        _tegra_hdmi_clock_enable(hdmi);
+
+fail:
+       mutex_unlock(&hdmi->clock_refcount_lock);
 }
 
 static void tegra_hdmi_put(struct tegra_dc *dc)
 {
-
        struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
 
        if (WARN_ONCE(hdmi->clock_refcount <= 0,
                "hdmi: clock refcount imbalance"))
-               return;
+               goto fail;
        if (--hdmi->clock_refcount != 0)
-               return;
+               goto fail;
 
        _tegra_hdmi_clock_disable(hdmi);
+
+fail:
+       mutex_unlock(&hdmi->clock_refcount_lock);
 }
 
 /* TODO: add support for other deep colors */
index c802d13f2e36fa211c18c0c750936d2d0c605709..457b87e4c24e4334a59d21f47fee0f1f30ad658d 100644 (file)
@@ -282,6 +282,7 @@ struct tegra_hdmi {
        struct hdmi_avi_infoframe avi;
        bool enabled;
        int clock_refcount;
+       struct mutex clock_refcount_lock;
 
        bool dvi;
 
@@ -295,7 +296,6 @@ struct tegra_hdmi {
 
        struct tegra_edid *edid;
        struct i2c_client *ddc_i2c_client;
-       struct mutex ddc_lock;
 
        struct i2c_client *scdc_i2c_client;
        struct delayed_work scdc_work;
@@ -322,6 +322,7 @@ struct tegra_hdmi {
        int irq;
        struct tegra_prod_list *prod_list;
        int ddc_refcount;
+       struct mutex ddc_refcount_lock;
        bool device_shutdown;
        int plug_state;
 };