misc: nct1008: prevent irq revival
authorSang-Hun Lee <sanlee@nvidia.com>
Mon, 29 Apr 2013 16:07:03 +0000 (09:07 -0700)
committerRiham Haidar <rhaidar@nvidia.com>
Mon, 6 May 2013 19:06:13 +0000 (12:06 -0700)
Problem description:
 - nct1008_shutdown and nct1008_suspend disable nct1008_irq
 - If nct1008_irq triggers during shutdown or suspend, the irq handler will
   schedule nct1008_work_func, which will re-enable nct1008_irq
 - This work could trigger after disabling the power rail as well, causing
   an access of nct1008 after disabling its power rail

Fix description:
 - Add a new flag stop_workqueue
 - While the flag is set, nct1008_work_func will return without doing anything
 - After setting the flag to effectively disable nct1008_work_func, wait for
   running nct1008_work_func to complete then disable nct1008_irq

Bug 1280843

Change-Id: I69176ad466f4c49e62932425fdf7a8583b2cf8bf
Signed-off-by: Sang-Hun Lee <sanlee@nvidia.com>
Reviewed-on: http://git-master/r/223906
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Gaurav Batra <gbatra@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>
drivers/misc/nct1008.c

index 61cbdcaaf7a35e92bfe32a51f2d0fd2674104519..3808f4dce7f7f2658bf1d41d9770055231ebbea8 100644 (file)
@@ -96,7 +96,7 @@ struct nct1008_data {
        int conv_period_ms;
        long etemp;
        int shutdown_complete;
-
+       int stop_workqueue;
        struct thermal_zone_device *nct_int;
        struct thermal_zone_device *nct_ext;
 };
@@ -853,6 +853,13 @@ static void nct1008_work_func(struct work_struct *work)
        int err;
        struct timespec ts;
 
+       mutex_lock(&data->mutex);
+       if (data->stop_workqueue) {
+               mutex_unlock(&data->mutex);
+               return;
+       }
+       mutex_unlock(&data->mutex);
+
        err = nct1008_disable(data->client);
        if (err == -ENODEV)
                return;
@@ -1198,8 +1205,11 @@ static int __devexit nct1008_remove(struct i2c_client *client)
        if (data->dent)
                debugfs_remove(data->dent);
 
-       free_irq(data->client->irq, data);
+       mutex_lock(&data->mutex);
+       data->stop_workqueue = 1;
+       mutex_unlock(&data->mutex);
        cancel_work_sync(&data->work);
+       free_irq(data->client->irq, data);
        sysfs_remove_group(&client->dev.kobj, &nct1008_attr_group);
        nct1008_power_control(data, false);
        if (data->nct_reg)
@@ -1213,10 +1223,13 @@ static int __devexit nct1008_remove(struct i2c_client *client)
 static void nct1008_shutdown(struct i2c_client *client)
 {
        struct nct1008_data *data = i2c_get_clientdata(client);
-       if (client->irq)
-               disable_irq(client->irq);
 
+       mutex_lock(&data->mutex);
+       data->stop_workqueue = 1;
+       mutex_unlock(&data->mutex);
        cancel_work_sync(&data->work);
+       if (client->irq)
+               disable_irq(client->irq);
 
        mutex_lock(&data->mutex);
        data->shutdown_complete = 1;
@@ -1230,7 +1243,12 @@ static int nct1008_suspend(struct device *dev)
        int err;
        struct nct1008_data *data = i2c_get_clientdata(client);
 
+       mutex_lock(&data->mutex);
+       data->stop_workqueue = 1;
+       mutex_unlock(&data->mutex);
+       cancel_work_sync(&data->work);
        disable_irq(client->irq);
+
        err = nct1008_disable(client);
        nct1008_power_control(data, false);
        return err;
@@ -1251,6 +1269,9 @@ static int nct1008_resume(struct device *dev)
                return err;
        }
        nct1008_update(data);
+       mutex_lock(&data->mutex);
+       data->stop_workqueue = 0;
+       mutex_unlock(&data->mutex);
        enable_irq(client->irq);
 
        return 0;