Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*	$NetBSD: drm_wait_netbsd.h,v 1.19 2021/12/19 12:41:15 riastradh Exp $	*/

/*-
 * Copyright (c) 2013 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Taylor R. Campbell.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _DRM_DRM_WAIT_NETBSD_H_
#define _DRM_DRM_WAIT_NETBSD_H_

#include <sys/param.h>
#include <sys/condvar.h>
#include <sys/cpu.h>		/* cpu_intr_p */
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <sys/systm.h>

#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/sched.h>

typedef kcondvar_t drm_waitqueue_t;

#define	DRM_UDELAY	DELAY

static inline void
DRM_INIT_WAITQUEUE(drm_waitqueue_t *q, const char *name)
{
	cv_init(q, name);
}

static inline void
DRM_DESTROY_WAITQUEUE(drm_waitqueue_t *q)
{
	cv_destroy(q);
}

static inline bool
DRM_WAITERS_P(drm_waitqueue_t *q, struct mutex *interlock)
{
	KASSERT(mutex_is_locked(interlock));
	return cv_has_waiters(q);
}

static inline void
DRM_WAKEUP_ONE(drm_waitqueue_t *q, struct mutex *interlock)
{
	KASSERT(mutex_is_locked(interlock));
	cv_signal(q);
}

static inline void
DRM_WAKEUP_ALL(drm_waitqueue_t *q, struct mutex *interlock)
{
	KASSERT(mutex_is_locked(interlock));
	cv_broadcast(q);
}

static inline bool
DRM_SPIN_WAITERS_P(drm_waitqueue_t *q, spinlock_t *interlock)
{
	KASSERT(spin_is_locked(interlock));
	return cv_has_waiters(q);
}

static inline void
DRM_SPIN_WAKEUP_ONE(drm_waitqueue_t *q, spinlock_t *interlock)
{
	KASSERT(spin_is_locked(interlock));
	cv_signal(q);
}

static inline void
DRM_SPIN_WAKEUP_ALL(drm_waitqueue_t *q, spinlock_t *interlock)
{
	KASSERT(spin_is_locked(interlock));
	cv_broadcast(q);
}

/*
 * DRM_SPIN_WAIT_ON is a replacement for the legacy DRM_WAIT_ON
 * portability macro.  It requires a spin interlock, which may require
 * changes to the surrounding code so that the waits actually are
 * interlocked by a spin lock.  It also polls the condition at every
 * tick, which masks missing wakeups.  Since DRM_WAIT_ON is going away,
 * in favour of Linux's native wait_event* API, waits in new code
 * should be written to use the DRM_*WAIT*_UNTIL macros below.
 *
 * Like the legacy DRM_WAIT_ON, DRM_SPIN_WAIT_ON returns
 *
 * . -EBUSY if timed out (yes, -EBUSY, not -ETIMEDOUT or -EWOULDBLOCK),
 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
 * . 0 if the condition was true before or just after the timeout.
 *
 * Note that cv_timedwait* return EWOULDBLOCK, not EBUSY, on timeout.
 *
 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
 * code and must be converted for use in NetBSD code (user or kernel.)
 */

#define	DRM_SPIN_WAIT_ON(RET, Q, INTERLOCK, TICKS, CONDITION)	do	      \
{									      \
	unsigned _dswo_ticks = (TICKS);					      \
	unsigned _dswo_start, _dswo_end;				      \
									      \
	KASSERT(spin_is_locked((INTERLOCK)));				      \
	KASSERT(!cpu_intr_p());						      \
	KASSERT(!cpu_softintr_p());					      \
	KASSERT(!cold);							      \
									      \
	for (;;) {							      \
		if (CONDITION) {					      \
			(RET) = 0;					      \
			break;						      \
		}							      \
		if (_dswo_ticks == 0) {					      \
			(RET) = -EBUSY;		/* Match Linux...  */	      \
			break;						      \
		}							      \
		_dswo_start = getticks();				      \
		/* XXX errno NetBSD->Linux */				      \
		(RET) = -cv_timedwait_sig((Q), &(INTERLOCK)->sl_lock, 1);     \
		_dswo_end = getticks();					      \
		if (_dswo_ticks == MAX_SCHEDULE_TIMEOUT)		      \
			/* nothing, never time out */;			      \
		else if (_dswo_end - _dswo_start < _dswo_ticks)		      \
			_dswo_ticks -= _dswo_end - _dswo_start;		      \
		else							      \
			_dswo_ticks = 0;				      \
		if (RET) {						      \
			if ((RET) == -ERESTART)				      \
				(RET) = -ERESTARTSYS;			      \
			if ((RET) == -EWOULDBLOCK)			      \
				/* Waited only one tick.  */		      \
				continue;				      \
			break;						      \
		}							      \
	}								      \
} while (0)

/*
 * The DRM_*WAIT*_UNTIL macros are replacements for the Linux
 * wait_event* macros.  Like DRM_SPIN_WAIT_ON, they add an interlock,
 * and so may require some changes to the surrounding code.  They have
 * a different return value convention from DRM_SPIN_WAIT_ON and a
 * different return value convention from cv_*wait*.
 *
 * The untimed DRM_*WAIT*_UNTIL macros return
 *
 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
 * . zero if the condition evaluated
 *
 * The timed DRM_*TIMED_WAIT*_UNTIL macros return
 *
 * . -EINTR/-ERESTARTSYS if interrupted by a signal,
 * . 0 if the condition was false after the timeout,
 * . 1 if the condition was true just after the timeout, or
 * . the number of ticks remaining if the condition was true before the
 * timeout.
 *
 * Contrast DRM_SPIN_WAIT_ON which returns -EINTR/-ERESTARTSYS on
 * signal, -EBUSY on timeout, and zero on success; and cv_*wait*, which
 * return EINTR/ERESTARTSYS on signal, EWOULDBLOCK on timeout, and zero
 * on success.
 *
 * XXX In retrospect, giving the timed and untimed macros a different
 * return convention from one another to match Linux may have been a
 * bad idea.  All of this inconsistent timeout return convention logic
 * has been a consistent source of bugs.
 *
 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
 * code and must be converted for use in NetBSD code (user or kernel.)
 */

#define	_DRM_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do		\
{									\
	KASSERT(mutex_is_locked((INTERLOCK)));				\
	ASSERT_SLEEPABLE();						\
	KASSERT(!cold);							\
	for (;;) {							\
		if (CONDITION) {					\
			(RET) = 0;					\
			break;						\
		}							\
		/* XXX errno NetBSD->Linux */				\
		(RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock);		\
		if (RET) {						\
			if ((RET) == -ERESTART)				\
				(RET) = -ERESTARTSYS;			\
			break;						\
		}							\
	}								\
} while (0)

#define	cv_wait_nointr(Q, I)	(cv_wait((Q), (I)), 0)

#define	DRM_WAIT_NOINTR_UNTIL(RET, Q, I, C)				\
	_DRM_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)

#define	DRM_WAIT_UNTIL(RET, Q, I, C)				\
	_DRM_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)

#define	_DRM_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) do \
{									\
	unsigned _dtwu_ticks = (TICKS);					\
	unsigned _dtwu_start, _dtwu_end;				\
									\
	KASSERT(mutex_is_locked((INTERLOCK)));				\
	ASSERT_SLEEPABLE();						\
	KASSERT(!cold);							\
									\
	for (;;) {							\
		if (CONDITION) {					\
			(RET) = MAX(_dtwu_ticks, 1);			\
			break;						\
		}							\
		if (_dtwu_ticks == 0) {					\
			(RET) = 0;					\
			break;						\
		}							\
		_dtwu_start = getticks();				\
		/* XXX errno NetBSD->Linux */				\
		(RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock,		\
		    MIN(_dtwu_ticks, INT_MAX/2));			\
		_dtwu_end = getticks();					\
		if (_dtwu_ticks == MAX_SCHEDULE_TIMEOUT)		\
			/* nothing, never time out */;			\
		else if ((_dtwu_end - _dtwu_start) < _dtwu_ticks)	\
			_dtwu_ticks -= _dtwu_end - _dtwu_start;		\
		else							\
			_dtwu_ticks = 0;				\
		if (RET) {						\
			if ((RET) == -ERESTART)				\
				(RET) = -ERESTARTSYS;			\
			if ((RET) == -EWOULDBLOCK)			\
				(RET) = (CONDITION) ? 1 : 0;		\
			break;						\
		}							\
	}								\
} while (0)

#define	DRM_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C)			\
	_DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)

#define	DRM_TIMED_WAIT_UNTIL(RET, Q, I, T, C)			\
	_DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)

/*
 * XXX Can't assert sleepable here because we hold a spin lock.  At
 * least we can assert that we're not in (soft) interrupt context, and
 * hope that nobody tries to use these with a sometimes quickly
 * satisfied condition while holding a different spin lock.
 */

#define	_DRM_SPIN_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do	\
{									\
	KASSERT(spin_is_locked((INTERLOCK)));				\
	KASSERT(!cpu_intr_p());						\
	KASSERT(!cpu_softintr_p());					\
	KASSERT(!cold);							\
	(RET) = 0;							\
	while (!(CONDITION)) {						\
		/* XXX errno NetBSD->Linux */				\
		(RET) = -WAIT((Q), &(INTERLOCK)->sl_lock);		\
		if ((RET) == -ERESTART)					\
			(RET) = -ERESTARTSYS;				\
		if (RET)						\
			break;						\
	}								\
} while (0)

#define	DRM_SPIN_WAIT_NOINTR_UNTIL(RET, Q, I, C)			\
	_DRM_SPIN_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)

#define	DRM_SPIN_WAIT_UNTIL(RET, Q, I, C)				\
	_DRM_SPIN_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)

#define	_DRM_SPIN_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) \
	do								\
{									\
	unsigned _dstwu_ticks = (TICKS);				\
	unsigned _dstwu_start, _dstwu_end;				\
									\
	KASSERT(spin_is_locked((INTERLOCK)));				\
	KASSERT(!cpu_intr_p());						\
	KASSERT(!cpu_softintr_p());					\
	KASSERT(!cold);							\
									\
	for (;;) {							\
		if (CONDITION) {					\
			(RET) = MAX(_dstwu_ticks, 1);			\
			break;						\
		}							\
		if (_dstwu_ticks == 0) {				\
			(RET) = 0;					\
			break;						\
		}							\
		_dstwu_start = getticks();				\
		/* XXX errno NetBSD->Linux */				\
		(RET) = -WAIT((Q), &(INTERLOCK)->sl_lock,		\
		    MIN(_dstwu_ticks, INT_MAX/2));			\
		_dstwu_end = getticks();				\
		if (_dstwu_ticks == MAX_SCHEDULE_TIMEOUT)		\
			/* nothing, never time out */;			\
		else if ((_dstwu_end - _dstwu_start) < _dstwu_ticks)	\
			_dstwu_ticks -= _dstwu_end - _dstwu_start;	\
		else							\
			_dstwu_ticks = 0;				\
		if (RET) {						\
			if ((RET) == -ERESTART)				\
				(RET) = -ERESTARTSYS;			\
			if ((RET) == -EWOULDBLOCK)			\
				(RET) = (CONDITION) ? 1 : 0;		\
			break;						\
		}							\
	}								\
} while (0)

#define	DRM_SPIN_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C)		\
	_DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)

#define	DRM_SPIN_TIMED_WAIT_UNTIL(RET, Q, I, T, C)			\
	_DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)

#endif  /* _DRM_DRM_WAIT_NETBSD_H_ */