Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c90aeb94 authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by David S. Miller
Browse files

once: make helper generic for calling functions once



Make the get_random_once() helper generic enough, so that functions
in general would only be called once, where one user of this is then
net_get_random_once().

The only implementation specific call is to get_random_bytes(), all
the rest of this *_once() facility would be duplicated among different
subsystems otherwise. The new DO_ONCE() helper will be used by prandom()
later on, but might also be useful for other scenarios/subsystems as
well where a one-time initialization in often-called, possibly fast
path code could occur.

Signed-off-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Acked-by: default avatarAlexei Starovoitov <ast@kernel.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 46234253
Loading
Loading
Loading
Loading
+47 −14
Original line number Diff line number Diff line
@@ -4,21 +4,54 @@
#include <linux/types.h>
#include <linux/jump_label.h>

bool __get_random_once(void *buf, int nbytes, bool *done,
		       struct static_key *once_key);
bool __do_once_start(bool *done, unsigned long *flags);
void __do_once_done(bool *done, struct static_key *once_key,
		    unsigned long *flags);

#define get_random_once(buf, nbytes)					\
/* Call a function exactly once. The idea of DO_ONCE() is to perform
 * a function call such as initialization of random seeds, etc, only
 * once, where DO_ONCE() can live in the fast-path. After @func has
 * been called with the passed arguments, the static key will patch
 * out the condition into a nop. DO_ONCE() guarantees type safety of
 * arguments!
 *
 * Not that the following is not equivalent ...
 *
 *   DO_ONCE(func, arg);
 *   DO_ONCE(func, arg);
 *
 * ... to this version:
 *
 *   void foo(void)
 *   {
 *     DO_ONCE(func, arg);
 *   }
 *
 *   foo();
 *   foo();
 *
 * In case the one-time invocation could be triggered from multiple
 * places, then a common helper function must be defined, so that only
 * a single static key will be placed there!
 */
#define DO_ONCE(func, ...)						     \
	({								     \
		bool ___ret = false;					     \
		static bool ___done = false;				     \
		static struct static_key ___once_key =			\
			STATIC_KEY_INIT_TRUE;				\
		if (static_key_true(&___once_key))			\
			___ret = __get_random_once((buf),		\
						   (nbytes),		\
						   &___done,		\
						   &___once_key);	\
		static struct static_key ___once_key = STATIC_KEY_INIT_TRUE; \
		if (static_key_true(&___once_key)) {			     \
			unsigned long ___flags;				     \
			___ret = __do_once_start(&___done, &___flags);	     \
			if (unlikely(___ret)) {				     \
				func(__VA_ARGS__);			     \
				__do_once_done(&___done, &___once_key,	     \
					       &___flags);		     \
			}						     \
		}							     \
		___ret;							     \
	})

#define get_random_once(buf, nbytes)					     \
	DO_ONCE(get_random_bytes, (buf), (nbytes))

#endif /* _LINUX_ONCE_H */
+29 −21
Original line number Diff line number Diff line
@@ -3,52 +3,60 @@
#include <linux/once.h>
#include <linux/random.h>

struct __random_once_work {
struct once_work {
	struct work_struct work;
	struct static_key *key;
};

static void __random_once_deferred(struct work_struct *w)
static void once_deferred(struct work_struct *w)
{
	struct __random_once_work *work;
	struct once_work *work;

	work = container_of(w, struct __random_once_work, work);
	work = container_of(w, struct once_work, work);
	BUG_ON(!static_key_enabled(work->key));
	static_key_slow_dec(work->key);
	kfree(work);
}

static void __random_once_disable_jump(struct static_key *key)
static void once_disable_jump(struct static_key *key)
{
	struct __random_once_work *w;
	struct once_work *w;

	w = kmalloc(sizeof(*w), GFP_ATOMIC);
	if (!w)
		return;

	INIT_WORK(&w->work, __random_once_deferred);
	INIT_WORK(&w->work, once_deferred);
	w->key = key;
	schedule_work(&w->work);
}

bool __get_random_once(void *buf, int nbytes, bool *done,
		       struct static_key *once_key)
{
	static DEFINE_SPINLOCK(lock);
	unsigned long flags;
static DEFINE_SPINLOCK(once_lock);

	spin_lock_irqsave(&lock, flags);
bool __do_once_start(bool *done, unsigned long *flags)
	__acquires(once_lock)
{
	spin_lock_irqsave(&once_lock, *flags);
	if (*done) {
		spin_unlock_irqrestore(&lock, flags);
		spin_unlock_irqrestore(&once_lock, *flags);
		/* Keep sparse happy by restoring an even lock count on
		 * this lock. In case we return here, we don't call into
		 * __do_once_done but return early in the DO_ONCE() macro.
		 */
		__acquire(once_lock);
		return false;
	}

	get_random_bytes(buf, nbytes);
	*done = true;
	spin_unlock_irqrestore(&lock, flags);

	__random_once_disable_jump(once_key);

	return true;
}
EXPORT_SYMBOL(__get_random_once);
EXPORT_SYMBOL(__do_once_start);

void __do_once_done(bool *done, struct static_key *once_key,
		    unsigned long *flags)
	__releases(once_lock)
{
	*done = true;
	spin_unlock_irqrestore(&once_lock, *flags);
	once_disable_jump(once_key);
}
EXPORT_SYMBOL(__do_once_done);