PM / Sleep: Mitigate race between the freezer and request_firmware()
[linux-flexiantxendom0-3.2.10.git] / kernel / kmod.c
index da7fcca..05698a7 100644 (file)
@@ -322,7 +322,7 @@ static void __call_usermodehelper(struct work_struct *work)
  * land has been frozen during a system-wide hibernation or suspend operation).
  * Should always be manipulated under umhelper_sem acquired for write.
  */
-static int usermodehelper_disabled = 1;
+static enum umh_disable_depth usermodehelper_disabled = UMH_DISABLED;
 
 /* Number of helpers running */
 static atomic_t running_helpers = ATOMIC_INIT(0);
@@ -347,13 +347,30 @@ static DECLARE_WAIT_QUEUE_HEAD(usermodehelper_disabled_waitq);
 
 int usermodehelper_read_trylock(void)
 {
+       DEFINE_WAIT(wait);
        int ret = 0;
 
        down_read(&umhelper_sem);
-       if (usermodehelper_disabled) {
+       for (;;) {
+               prepare_to_wait(&usermodehelper_disabled_waitq, &wait,
+                               TASK_INTERRUPTIBLE);
+               if (!usermodehelper_disabled)
+                       break;
+
+               if (usermodehelper_disabled == UMH_DISABLED)
+                       ret = -EAGAIN;
+
                up_read(&umhelper_sem);
-               ret = -EAGAIN;
+
+               if (ret)
+                       break;
+
+               schedule();
+               try_to_freeze();
+
+               down_read(&umhelper_sem);
        }
+       finish_wait(&usermodehelper_disabled_waitq, &wait);
        return ret;
 }
 EXPORT_SYMBOL_GPL(usermodehelper_read_trylock);
@@ -392,25 +409,35 @@ void usermodehelper_read_unlock(void)
 EXPORT_SYMBOL_GPL(usermodehelper_read_unlock);
 
 /**
- * usermodehelper_enable - allow new helpers to be started again
+ * __usermodehelper_set_disable_depth - Modify usermodehelper_disabled.
+ * depth: New value to assign to usermodehelper_disabled.
+ *
+ * Change the value of usermodehelper_disabled (under umhelper_sem locked for
+ * writing) and wakeup tasks waiting for it to change.
  */
-void usermodehelper_enable(void)
+void __usermodehelper_set_disable_depth(enum umh_disable_depth depth)
 {
        down_write(&umhelper_sem);
-       usermodehelper_disabled = 0;
+       usermodehelper_disabled = depth;
        wake_up(&usermodehelper_disabled_waitq);
        up_write(&umhelper_sem);
 }
 
 /**
- * usermodehelper_disable - prevent new helpers from being started
+ * __usermodehelper_disable - Prevent new helpers from being started.
+ * @depth: New value to assign to usermodehelper_disabled.
+ *
+ * Set usermodehelper_disabled to @depth and wait for running helpers to exit.
  */
-int usermodehelper_disable(void)
+int __usermodehelper_disable(enum umh_disable_depth depth)
 {
        long retval;
 
+       if (!depth)
+               return -EINVAL;
+
        down_write(&umhelper_sem);
-       usermodehelper_disabled = 1;
+       usermodehelper_disabled = depth;
        up_write(&umhelper_sem);
 
        /*
@@ -425,7 +452,7 @@ int usermodehelper_disable(void)
        if (retval)
                return 0;
 
-       usermodehelper_enable();
+       __usermodehelper_set_disable_depth(UMH_ENABLED);
        return -EAGAIN;
 }