diff options
| -rw-r--r-- | drivers/base/firmware_class.c | 51 | ||||
| -rw-r--r-- | include/linux/kmod.h | 1 | ||||
| -rw-r--r-- | kernel/kmod.c | 58 | 
3 files changed, 76 insertions, 34 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 60290671f04..72c644b191a 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -81,6 +81,11 @@ enum {  static int loading_timeout = 60;	/* In seconds */ +static inline long firmware_loading_timeout(void) +{ +	return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT; +} +  /* fw_lock could be moved to 'struct firmware_priv' but since it is just   * guarding for corner cases a global lock should be OK */  static DEFINE_MUTEX(fw_lock); @@ -541,31 +546,22 @@ static void _request_firmware_cleanup(const struct firmware **firmware_p)  static int _request_firmware(const struct firmware *firmware,  			     const char *name, struct device *device, -			     bool uevent, bool nowait) +			     bool uevent, bool nowait, long timeout)  {  	struct firmware_priv *fw_priv; -	int retval; - -	retval = usermodehelper_read_trylock(); -	if (WARN_ON(retval)) { -		dev_err(device, "firmware: %s will not be loaded\n", name); -		return retval; -	} +	int retval = 0;  	if (uevent)  		dev_dbg(device, "firmware: requesting %s\n", name);  	fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); -	if (IS_ERR(fw_priv)) { -		retval = PTR_ERR(fw_priv); -		goto out; -	} +	if (IS_ERR(fw_priv)) +		return PTR_ERR(fw_priv);  	if (uevent) { -		if (loading_timeout > 0) +		if (timeout != MAX_SCHEDULE_TIMEOUT)  			mod_timer(&fw_priv->timeout, -				  round_jiffies_up(jiffies + -						   loading_timeout * HZ)); +				  round_jiffies_up(jiffies + timeout));  		kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);  	} @@ -582,9 +578,6 @@ static int _request_firmware(const struct firmware *firmware,  	mutex_unlock(&fw_lock);  	fw_destroy_instance(fw_priv); - -out: -	usermodehelper_read_unlock();  	return retval;  } @@ -613,7 +606,14 @@ request_firmware(const struct firmware **firmware_p, const char *name,  	if (ret <= 0)  		return ret; -	ret = _request_firmware(*firmware_p, name, device, true, false); +	ret = usermodehelper_read_trylock(); +	if (WARN_ON(ret)) { +		dev_err(device, "firmware: %s will not be loaded\n", name); +	} else { +		ret = _request_firmware(*firmware_p, name, device, true, false, +					firmware_loading_timeout()); +		usermodehelper_read_unlock(); +	}  	if (ret)  		_request_firmware_cleanup(firmware_p); @@ -648,6 +648,7 @@ static int request_firmware_work_func(void *arg)  {  	struct firmware_work *fw_work = arg;  	const struct firmware *fw; +	long timeout;  	int ret;  	if (!arg) { @@ -659,8 +660,16 @@ static int request_firmware_work_func(void *arg)  	if (ret <= 0)  		goto out; -	ret = _request_firmware(fw, fw_work->name, fw_work->device, -				fw_work->uevent, true); +	timeout = usermodehelper_read_lock_wait(firmware_loading_timeout()); +	if (timeout) { +		ret = _request_firmware(fw, fw_work->name, fw_work->device, +					fw_work->uevent, true, timeout); +		usermodehelper_read_unlock(); +	} else { +		dev_dbg(fw_work->device, "firmware: %s loading timed out\n", +			fw_work->name); +		ret = -EAGAIN; +	}  	if (ret)  		_request_firmware_cleanup(&fw); diff --git a/include/linux/kmod.h b/include/linux/kmod.h index 97d22c3e08b..b087377ae2c 100644 --- a/include/linux/kmod.h +++ b/include/linux/kmod.h @@ -115,6 +115,7 @@ extern void usermodehelper_init(void);  extern int usermodehelper_disable(void);  extern void usermodehelper_enable(void);  extern int usermodehelper_read_trylock(void); +extern long usermodehelper_read_lock_wait(long timeout);  extern void usermodehelper_read_unlock(void);  #endif /* __LINUX_KMOD_H__ */ diff --git a/kernel/kmod.c b/kernel/kmod.c index 4079ac1d5e7..da7fcca279f 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c @@ -334,6 +334,12 @@ static atomic_t running_helpers = ATOMIC_INIT(0);  static DECLARE_WAIT_QUEUE_HEAD(running_helpers_waitq);  /* + * Used by usermodehelper_read_lock_wait() to wait for usermodehelper_disabled + * to become 'false'. + */ +static DECLARE_WAIT_QUEUE_HEAD(usermodehelper_disabled_waitq); + +/*   * Time to wait for running_helpers to become zero before the setting of   * usermodehelper_disabled in usermodehelper_disable() fails   */ @@ -352,6 +358,33 @@ int usermodehelper_read_trylock(void)  }  EXPORT_SYMBOL_GPL(usermodehelper_read_trylock); +long usermodehelper_read_lock_wait(long timeout) +{ +	DEFINE_WAIT(wait); + +	if (timeout < 0) +		return -EINVAL; + +	down_read(&umhelper_sem); +	for (;;) { +		prepare_to_wait(&usermodehelper_disabled_waitq, &wait, +				TASK_UNINTERRUPTIBLE); +		if (!usermodehelper_disabled) +			break; + +		up_read(&umhelper_sem); + +		timeout = schedule_timeout(timeout); +		if (!timeout) +			break; + +		down_read(&umhelper_sem); +	} +	finish_wait(&usermodehelper_disabled_waitq, &wait); +	return timeout; +} +EXPORT_SYMBOL_GPL(usermodehelper_read_lock_wait); +  void usermodehelper_read_unlock(void)  {  	up_read(&umhelper_sem); @@ -359,6 +392,17 @@ void usermodehelper_read_unlock(void)  EXPORT_SYMBOL_GPL(usermodehelper_read_unlock);  /** + * usermodehelper_enable - allow new helpers to be started again + */ +void usermodehelper_enable(void) +{ +	down_write(&umhelper_sem); +	usermodehelper_disabled = 0; +	wake_up(&usermodehelper_disabled_waitq); +	up_write(&umhelper_sem); +} + +/**   * usermodehelper_disable - prevent new helpers from being started   */  int usermodehelper_disable(void) @@ -381,22 +425,10 @@ int usermodehelper_disable(void)  	if (retval)  		return 0; -	down_write(&umhelper_sem); -	usermodehelper_disabled = 0; -	up_write(&umhelper_sem); +	usermodehelper_enable();  	return -EAGAIN;  } -/** - * usermodehelper_enable - allow new helpers to be started again - */ -void usermodehelper_enable(void) -{ -	down_write(&umhelper_sem); -	usermodehelper_disabled = 0; -	up_write(&umhelper_sem); -} -  static void helper_lock(void)  {  	atomic_inc(&running_helpers);  |