/*
 * include/vservices/wait.h
 *
 * Copyright (c) 2012-2018 General Dynamics
 * Copyright (c) 2014 Open Kernel Labs, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Generic wait event helpers for Virtual Service drivers.
 */

#ifndef _VSERVICE_SERVICE_WAIT_H
#define _VSERVICE_SERVICE_WAIT_H

#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/sched/signal.h>

/* Older kernels don't have lockdep_assert_held_once(). */
#ifndef lockdep_assert_held_once
#ifdef CONFIG_LOCKDEP
#define lockdep_assert_held_once(l) do {				\
		WARN_ON_ONCE(debug_locks && !lockdep_is_held(l));	\
	} while (0)
#else
#define lockdep_assert_held_once(l) do { } while (0)
#endif
#endif

/* Legacy wait macro; needs rewriting to use vs_state_lock_safe(). */
/* FIXME: Redmine ticket #229 - philip. */
/**
 * __vs_service_wait_event - Wait for a condition to become true for a
 * Virtual Service.
 *
 * @_service: The service to wait for the condition to be true for.
 * @_wq: Waitqueue to wait on.
 * @_condition: Condition to wait for.
 *
 * Returns: This function returns 0 if the condition is true, or a -ERESTARTSYS
 *          if the wait loop wait interrupted. If _state is TASK_UNINTERRUPTIBLE
 *          then this function will always return 0.
 *
 * This function must be called with the service's state lock held. The wait
 * is performed without the state lock held, but the condition is re-checked
 * after reacquiring the state lock. This property allows this function to
 * check the state of the service's protocol in a thread safe manner.
 *
 * The caller is responsible for ensuring that it has not been detached from
 * the given service.
 *
 * It is nearly always wrong to call this on the service workqueue, since
 * the workqueue is single-threaded and the state can only change when a
 * handler function is called on it.
 */
#define __vs_service_wait_event(_service, _wq, _cond, _state)		\
	({								\
		DEFINE_WAIT(__wait);					\
		int __ret = 0;						\
									\
		lockdep_assert_held_once(&(_service)->state_mutex);	\
		do {							\
			prepare_to_wait(&(_wq), &__wait, (_state));	\
									\
			if (_cond)					\
				break;					\
									\
			if ((_state) == TASK_INTERRUPTIBLE &&		\
					signal_pending(current)) {	\
				__ret = -ERESTARTSYS;			\
				break;					\
			}						\
									\
			vs_service_state_unlock(_service);		\
			schedule();					\
			vs_service_state_lock(_service);		\
		} while (!(_cond));					\
									\
		finish_wait(&(_wq), &__wait);				\
		__ret;							\
	})

/* Legacy wait macros; need rewriting to use __vs_wait_state(). */
/* FIXME: Redmine ticket #229 - philip. */
#define vs_service_wait_event(_service, _wq, _cond) \
	__vs_service_wait_event(_service, _wq, _cond, TASK_INTERRUPTIBLE)
#define vs_service_wait_event_nointr(_service, _wq, _cond) \
	__vs_service_wait_event(_service, _wq, _cond, TASK_UNINTERRUPTIBLE)

/**
 * __vs_wait_state - block until a condition becomes true on a service state.
 *
 * @_state: The protocol state to wait on.
 * @_cond: Condition to wait for.
 * @_intr: If true, perform an interruptible wait; the wait may then fail
 *         with -ERESTARTSYS.
 * @_timeout: A timeout in jiffies, or negative for no timeout. If the
 *         timeout expires, the wait will fail with -ETIMEDOUT.
 * @_bh: The token _bh if this service uses tx_atomic (sends from a
 *         non-framework tasklet); otherwise nothing.
 *
 * Return: Return a pointer to a message buffer on successful allocation,
 *         or an error code in ERR_PTR form.
 *
 * This macro blocks waiting until a particular condition becomes true on a
 * service state. The service must be running; if not, or if it ceases to be
 * running during the wait, -ECANCELED will be returned.
 *
 * This is not an exclusive wait. If an exclusive wait is desired it is
 * usually better to use the waiting alloc or send functions.
 *
 * This macro must be called with a reference to the service held, and with
 * the service's state lock held. The state lock will be dropped by waiting
 * but reacquired before returning, unless -ENOLINK is returned, in which case
 * the service driver has been unbound and the lock cannot be reacquired.
 */
#define __vs_wait_state(_state, _cond, _intr, _timeout, _bh)	\
	({								\
		DEFINE_WAIT(__wait);					\
		int __ret;						\
		int __jiffies __maybe_unused = (_timeout);		\
		struct vs_service_device *__service = (_state)->service;\
									\
		while (1) {						\
			prepare_to_wait(&__service->quota_wq, &__wait,	\
					_intr ? TASK_INTERRUPTIBLE :    \
					TASK_UNINTERRUPTIBLE);		\
									\
			if (!VSERVICE_BASE_STATE_IS_RUNNING(		\
					(_state)->state.base)) {	\
				__ret = -ECANCELED;			\
				break;					\
			}						\
									\
			if (_cond) {					\
				__ret = 0;				\
				break;					\
			}						\
									\
			if (_intr && signal_pending(current)) {		\
				__ret = -ERESTARTSYS;			\
				break;					\
			}						\
									\
			vs_state_unlock##_bh(_state);			\
									\
			if (_timeout >= 0) {				\
				__jiffies = schedule_timeout(__jiffies);\
				if (!__jiffies) {			\
					__ret = -ETIMEDOUT;		\
					break;				\
				}					\
			} else {					\
				schedule();				\
			}						\
									\
			if (!vs_state_lock_safe##_bh(_state)) {		\
				__ret = -ENOLINK;			\
				break;					\
			}						\
		}							\
									\
		finish_wait(&__service->quota_wq, &__wait);		\
		__ret;							\
	})

/* Specialisations of __vs_wait_state for common uses. */
#define vs_wait_state(_state, _cond) \
	__vs_wait_state(_state, _cond, true, -1,)
#define vs_wait_state_timeout(_state, _cond, _timeout) \
	__vs_wait_state(_state, _cond, true, _timeout,)
#define vs_wait_state_nointr(_state, _cond) \
	__vs_wait_state(_state, _cond, false, -1,)
#define vs_wait_state_nointr_timeout(_state, _cond, _timeout) \
	__vs_wait_state(_state, _cond, false, _timeout,)
#define vs_wait_state_bh(_state, _cond) \
	__vs_wait_state(_state, _cond, true, -1, _bh)
#define vs_wait_state_timeout_bh(_state, _cond, _timeout) \
	__vs_wait_state(_state, _cond, true, _timeout, _bh)
#define vs_wait_state_nointr_bh(_state, _cond) \
	__vs_wait_state(_state, _cond, false, -1, _bh)
#define vs_wait_state_nointr_timeout_bh(_state, _cond, _timeout) \
	__vs_wait_state(_state, _cond, false, _timeout, _bh)

/**
 * __vs_wait_alloc - block until quota is available, then allocate a buffer.
 *
 * @_state: The protocol state to allocate a message for.
 * @_alloc_func: The message buffer allocation function to run. This is the
 *         full function invocation, not a pointer to the function.
 * @_cond: Additional condition which must remain true, or else the wait
 *         will fail with -ECANCELED. This is typically used to check the
 *         service's protocol state. Note that this condition will only
 *         be checked after sleeping; it is assumed to be true when the
 *         macro is first called.
 * @_unlock: If true, drop the service state lock before sleeping. The wait
 *         may then fail with -ENOLINK if the driver is detached from the
 *         service, in which case the lock is dropped.
 * @_intr: If true, perform an interruptible wait; the wait may then fail
 *         with -ERESTARTSYS.
 * @_timeout: A timeout in jiffies, or negative for no timeout. If the
 *         timeout expires, the wait will fail with -ETIMEDOUT.
 * @_bh: The token _bh if this service uses tx_atomic (sends from a
 *         non-framework tasklet); otherwise nothing.
 *
 * Return: Return a pointer to a message buffer on successful allocation,
 *         or an error code in ERR_PTR form.
 *
 * This macro calls a specified message allocation function, and blocks
 * if it returns -ENOBUFS, waiting until quota is available on the service
 * before retrying. It aborts the wait if the service resets, or if the
 * optionally specified condition becomes false. Note that a reset followed
 * quickly by an activate might not trigger a failure; if that is significant
 * for your driver, use the optional condition to detect it.
 *
 * This macro must be called with a reference to the service held, and with
 * the service's state lock held. The reference and state lock will still be
 * held on return, unless -ENOLINK is returned, in which case the lock has been
 * dropped and cannot be reacquired.
 *
 * This is always an exclusive wait. It is safe to call without separately
 * waking the waitqueue afterwards; if the allocator function fails for any
 * reason other than quota exhaustion then another waiter will be woken.
 *
 * Be wary of potential deadlocks when using this macro on the service
 * workqueue. If both ends block their service workqueues waiting for quota,
 * then no progress can be made. It is usually only correct to block the
 * service workqueue on the server side.
 */
#define __vs_wait_alloc(_state, _alloc_func, _cond, _unlock, _intr, 	\
		_timeout, _bh)						\
	({								\
		DEFINE_WAIT(__wait);					\
		struct vs_mbuf *__mbuf = NULL;				\
		int __jiffies __maybe_unused = (_timeout);		\
		struct vs_service_device *__service = (_state)->service;\
									\
		while (!vs_service_send_mbufs_available(__service)) {	\
			if (_intr && signal_pending(current)) {		\
				__mbuf = ERR_PTR(-ERESTARTSYS);		\
				break;					\
			}						\
									\
			prepare_to_wait_exclusive(			\
					&__service->quota_wq, &__wait,	\
					_intr ? TASK_INTERRUPTIBLE :    \
					TASK_UNINTERRUPTIBLE);		\
									\
			if (_unlock)					\
				vs_state_unlock##_bh(_state);		\
									\
			if (_timeout >= 0) {				\
				__jiffies = schedule_timeout(__jiffies);\
				if (!__jiffies) {			\
					__mbuf = ERR_PTR(-ETIMEDOUT);	\
					break;				\
				}					\
			} else {					\
				schedule();				\
			}						\
									\
			if (_unlock && !vs_state_lock_safe##_bh(	\
						_state)) {		\
				__mbuf = ERR_PTR(-ENOLINK);		\
				break;					\
			}						\
									\
			if (!VSERVICE_BASE_STATE_IS_RUNNING(		\
					(_state)->state.base) ||	\
					!(_cond)) {			\
				__mbuf = ERR_PTR(-ECANCELED);		\
				break;					\
			}						\
		}							\
		finish_wait(&__service->quota_wq, &__wait);		\
									\
		if (__mbuf == NULL)					\
			__mbuf = (_alloc_func);				\
		if (IS_ERR(__mbuf) && (PTR_ERR(__mbuf) != -ENOBUFS))	\
			wake_up(&__service->quota_wq);			\
		__mbuf;							\
	})

/* Specialisations of __vs_wait_alloc for common uses. */
#define vs_wait_alloc(_state, _cond, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,)
#define vs_wait_alloc_timeout(_state, _cond, _alloc_func, _timeout) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout,)
#define vs_wait_alloc_nointr(_state, _cond, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,)
#define vs_wait_alloc_nointr_timeout(_state, _cond, _alloc_func, _timeout) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout,)
#define vs_wait_alloc_bh(_state, _cond, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1, _bh)
#define vs_wait_alloc_timeout_bh(_state, _cond, _alloc_func, _timeout) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout, _bh)
#define vs_wait_alloc_nointr_bh(_state, _cond, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1, _bh)
#define vs_wait_alloc_nointr_timeout_bh(_state, _cond, _alloc_func, _timeout) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout, _bh)
#define vs_wait_alloc_locked(_state, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, true, false, true, -1,)

/* Legacy wait macros, to be removed and replaced with those above. */
/* FIXME: Redmine ticket #229 - philip. */
#define vs_service_waiting_alloc(_state, _alloc_func) \
	__vs_wait_alloc(_state, _alloc_func, true, false, true, -1,)
#define vs_service_waiting_alloc_cond_locked(_state, _alloc_func, _cond) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,)
#define vs_service_waiting_alloc_cond_locked_nointr(_state, _alloc_func, _cond) \
	__vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,)

/**
 * __vs_wait_send - block until quota is available, then send a message.
 *
 * @_state: The protocol state to send a message for.
 * @_cond: Additional condition which must remain true, or else the wait
 *         will fail with -ECANCELED. This is typically used to check the
 *         service's protocol state. Note that this condition will only
 *         be checked after sleeping; it is assumed to be true when the
 *         macro is first called.
 * @_send_func: The message send function to run. This is the full function
 *         invocation, not a pointer to the function.
 * @_unlock: If true, drop the service state lock before sleeping. The wait
 *         may then fail with -ENOLINK if the driver is detached from the
 *         service, in which case the lock is dropped.
 * @_check_running: If true, the wait will return -ECANCELED if the service's
 *         base state is not active, or ceases to be active.
 * @_intr: If true, perform an interruptible wait; the wait may then fail
 *         with -ERESTARTSYS.
 * @_timeout: A timeout in jiffies, or negative for no timeout. If the
 *         timeout expires, the wait will fail with -ETIMEDOUT.
 * @_bh: The token _bh if this service uses tx_atomic (sends from a
 *         non-framework tasklet); otherwise nothing.
 *
 * Return: If the send succeeds, then 0 is returned; otherwise an error
 *         code may be returned as described above.
 *
 * This macro calls a specified message send function, and blocks if it
 * returns -ENOBUFS, waiting until quota is available on the service before
 * retrying. It aborts the wait if it finds the service in reset, or if the
 * optionally specified condition becomes false. Note that a reset followed
 * quickly by an activate might not trigger a failure; if that is significant
 * for your driver, use the optional condition to detect it.
 *
 * This macro must be called with a reference to the service held, and with
 * the service's state lock held. The reference and state lock will still be
 * held on return, unless -ENOLINK is returned, in which case the lock has been
 * dropped and cannot be reacquired.
 *
 * This is always an exclusive wait. It is safe to call without separately
 * waking the waitqueue afterwards; if the allocator function fails for any
 * reason other than quota exhaustion then another waiter will be woken.
 *
 * Be wary of potential deadlocks when calling this function on the service
 * workqueue. If both ends block their service workqueues waiting for quota,
 * then no progress can be made. It is usually only correct to block the
 * service workqueue on the server side.
 */
#define __vs_wait_send(_state, _cond, _send_func, _unlock, 		\
		_check_running, _intr, _timeout, _bh)			\
	({								\
		DEFINE_WAIT(__wait);					\
		int __ret = 0;						\
		int __jiffies __maybe_unused = (_timeout);		\
		struct vs_service_device *__service = (_state)->service;\
									\
		while (!vs_service_send_mbufs_available(__service)) {	\
			if (_intr && signal_pending(current)) {		\
				__ret = -ERESTARTSYS;			\
				break;					\
			}						\
									\
			prepare_to_wait_exclusive(			\
					&__service->quota_wq, &__wait,	\
					_intr ? TASK_INTERRUPTIBLE :    \
					TASK_UNINTERRUPTIBLE);		\
									\
			if (_unlock)					\
				vs_state_unlock##_bh(_state);		\
									\
			if (_timeout >= 0) {				\
				__jiffies = schedule_timeout(__jiffies);\
				if (!__jiffies) {			\
					__ret = -ETIMEDOUT;		\
					break;				\
				}					\
			} else {					\
				schedule();				\
			}						\
									\
			if (_unlock && !vs_state_lock_safe##_bh(	\
						_state)) {		\
				__ret = -ENOLINK;			\
				break;					\
			}						\
									\
			if ((_check_running &&				\
					!VSERVICE_BASE_STATE_IS_RUNNING(\
					(_state)->state.base)) ||	\
					!(_cond)) {			\
				__ret = -ECANCELED;			\
				break;					\
			}						\
		}							\
		finish_wait(&__service->quota_wq, &__wait);		\
									\
		if (!__ret)						\
			__ret = (_send_func);				\
		if ((__ret < 0) && (__ret != -ENOBUFS))			\
			wake_up(&__service->quota_wq);			\
		__ret;							\
	})

/* Specialisations of __vs_wait_send for common uses. */
#define vs_wait_send(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, true, -1,)
#define vs_wait_send_timeout(_state, _cond, _send_func, _timeout) \
	__vs_wait_send(_state, _cond, _send_func, true, true, true, _timeout,)
#define vs_wait_send_nointr(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, false, -1,)
#define vs_wait_send_nointr_timeout(_state, _cond, _send_func, _timeout) \
	__vs_wait_send(_state, _cond, _send_func, true, true, false, _timeout,)
#define vs_wait_send_bh(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, true, -1, _bh)
#define vs_wait_send_timeout_bh(_state, _cond, _send_func, _timeout) \
	__vs_wait_send(_state, _cond, _send_func, true, true, true, \
			_timeout, _bh)
#define vs_wait_send_nointr_bh(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, false, -1, _bh)
#define vs_wait_send_nointr_timeout_bh(_state, _cond, _send_func, _timeout) \
	__vs_wait_send(_state, _cond, _send_func, true, true, false, \
			_timeout, _bh)
#define vs_wait_send_locked(_state, _send_func) \
	__vs_wait_send(_state, true, _send_func, false, true, true, -1,)
#define vs_wait_send_locked_nocheck(_state, _send_func) \
	__vs_wait_send(_state, true, _send_func, false, false, true, -1,)

/* Legacy wait macros, to be removed and replaced with those above. */
/* FIXME: Redmine ticket #229 - philip. */
#define vs_service_waiting_send(_state, _send_func) \
	__vs_wait_send(_state, true, _send_func, true, true, true, -1,)
#define vs_service_waiting_send_nointr(_state, _send_func) \
	__vs_wait_send(_state, true, _send_func, true, true, false, -1,)
#define vs_service_waiting_send_cond(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, true, -1,)
#define vs_service_waiting_send_cond_nointr(_state, _cond, _send_func) \
	__vs_wait_send(_state, _cond, _send_func, true, true, false, -1,)
#define vs_service_waiting_send_nocheck(_state, _send_func) \
	__vs_wait_send(_state, true, _send_func, true, false, true, -1,)

#endif /* _VSERVICE_SERVICE_WAIT_H */
