18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/slab.h>
38c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
48c2ecf20Sopenharmony_ci#include <linux/once.h>
58c2ecf20Sopenharmony_ci#include <linux/random.h>
68c2ecf20Sopenharmony_ci#include <linux/module.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_cistruct once_work {
98c2ecf20Sopenharmony_ci	struct work_struct work;
108c2ecf20Sopenharmony_ci	struct static_key_true *key;
118c2ecf20Sopenharmony_ci	struct module *module;
128c2ecf20Sopenharmony_ci};
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic void once_deferred(struct work_struct *w)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	struct once_work *work;
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci	work = container_of(w, struct once_work, work);
198c2ecf20Sopenharmony_ci	BUG_ON(!static_key_enabled(work->key));
208c2ecf20Sopenharmony_ci	static_branch_disable(work->key);
218c2ecf20Sopenharmony_ci	module_put(work->module);
228c2ecf20Sopenharmony_ci	kfree(work);
238c2ecf20Sopenharmony_ci}
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic void once_disable_jump(struct static_key_true *key, struct module *mod)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	struct once_work *w;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	w = kmalloc(sizeof(*w), GFP_ATOMIC);
308c2ecf20Sopenharmony_ci	if (!w)
318c2ecf20Sopenharmony_ci		return;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	INIT_WORK(&w->work, once_deferred);
348c2ecf20Sopenharmony_ci	w->key = key;
358c2ecf20Sopenharmony_ci	w->module = mod;
368c2ecf20Sopenharmony_ci	__module_get(mod);
378c2ecf20Sopenharmony_ci	schedule_work(&w->work);
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(once_lock);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cibool __do_once_start(bool *done, unsigned long *flags)
438c2ecf20Sopenharmony_ci	__acquires(once_lock)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	spin_lock_irqsave(&once_lock, *flags);
468c2ecf20Sopenharmony_ci	if (*done) {
478c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&once_lock, *flags);
488c2ecf20Sopenharmony_ci		/* Keep sparse happy by restoring an even lock count on
498c2ecf20Sopenharmony_ci		 * this lock. In case we return here, we don't call into
508c2ecf20Sopenharmony_ci		 * __do_once_done but return early in the DO_ONCE() macro.
518c2ecf20Sopenharmony_ci		 */
528c2ecf20Sopenharmony_ci		__acquire(once_lock);
538c2ecf20Sopenharmony_ci		return false;
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	return true;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__do_once_start);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_civoid __do_once_done(bool *done, struct static_key_true *once_key,
618c2ecf20Sopenharmony_ci		    unsigned long *flags, struct module *mod)
628c2ecf20Sopenharmony_ci	__releases(once_lock)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	*done = true;
658c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&once_lock, *flags);
668c2ecf20Sopenharmony_ci	once_disable_jump(once_key, mod);
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__do_once_done);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(once_mutex);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cibool __do_once_slow_start(bool *done)
738c2ecf20Sopenharmony_ci	__acquires(once_mutex)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	mutex_lock(&once_mutex);
768c2ecf20Sopenharmony_ci	if (*done) {
778c2ecf20Sopenharmony_ci		mutex_unlock(&once_mutex);
788c2ecf20Sopenharmony_ci		/* Keep sparse happy by restoring an even lock count on
798c2ecf20Sopenharmony_ci		 * this mutex. In case we return here, we don't call into
808c2ecf20Sopenharmony_ci		 * __do_once_done but return early in the DO_ONCE_SLOW() macro.
818c2ecf20Sopenharmony_ci		 */
828c2ecf20Sopenharmony_ci		__acquire(once_mutex);
838c2ecf20Sopenharmony_ci		return false;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return true;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__do_once_slow_start);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_civoid __do_once_slow_done(bool *done, struct static_key_true *once_key,
918c2ecf20Sopenharmony_ci			 struct module *mod)
928c2ecf20Sopenharmony_ci	__releases(once_mutex)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	*done = true;
958c2ecf20Sopenharmony_ci	mutex_unlock(&once_mutex);
968c2ecf20Sopenharmony_ci	once_disable_jump(once_key, mod);
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__do_once_slow_done);
99