/*	$NetBSD: locks_up.c,v 1.14 2023/11/02 10:31:55 martin Exp $	*/

/*
 * Copyright (c) 2010 Antti Kantee.  All Rights Reserved.
 *
 * 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 AUTHOR ``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 AUTHOR 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.
 */

/*
 * Virtual uniprocessor rump kernel version of locks.  Since the entire
 * kernel is running on only one CPU in the system, there is no need 
 * to perform slow cache-coherent MP locking operations.  This speeds
 * up things quite dramatically and is a good example of that two
 * disjoint kernels running simultaneously in an MP system can be
 * massively faster than one with fine-grained locking.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: locks_up.c,v 1.14 2023/11/02 10:31:55 martin Exp $");

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>

#include <rump-sys/kern.h>

#include <rump/rumpuser.h>

struct upmtx {
	struct lwp *upm_owner;
	int upm_wanted;
	struct rumpuser_cv *upm_rucv;
};
#define UPMTX(mtx) struct upmtx *upm = *(struct upmtx **)mtx

static inline void
checkncpu(void)
{

	if (__predict_false(ncpu != 1))
		panic("UP lock implementation requires RUMP_NCPU == 1");
}

void
mutex_init(kmutex_t *mtx, kmutex_type_t type, int ipl)
{
	struct upmtx *upm;

	CTASSERT(sizeof(kmutex_t) >= sizeof(void *));
	checkncpu();

	/*
	 * In uniprocessor locking we don't need to differentiate
	 * between spin mutexes and adaptive ones.  We could
	 * replace mutex_enter() with a NOP for spin mutexes, but
	 * not bothering with that for now.
	 */

	/*
	 * XXX: pool_cache would be nice, but not easily possible,
	 * as pool cache init wants to call mutex_init() ...
	 */
	upm = rump_hypermalloc(sizeof(*upm), 0, true, "mutex_init");
	memset(upm, 0, sizeof(*upm));
	rumpuser_cv_init(&upm->upm_rucv);
	memcpy(mtx, &upm, sizeof(void *));
}

void
mutex_destroy(kmutex_t *mtx)
{
	UPMTX(mtx);

	KASSERT(upm->upm_owner == NULL);
	KASSERT(upm->upm_wanted == 0);
	rumpuser_cv_destroy(upm->upm_rucv);
	rump_hyperfree(upm, sizeof(*upm));
}

void
mutex_enter(kmutex_t *mtx)
{
	UPMTX(mtx);

	/* fastpath? */
	if (mutex_tryenter(mtx))
		return;

	/*
	 * No?  bummer, do it the slow and painful way then.
	 */
	upm->upm_wanted++;
	while (!mutex_tryenter(mtx)) {
		rump_schedlock_cv_wait(upm->upm_rucv);
	}
	upm->upm_wanted--;

	KASSERT(upm->upm_wanted >= 0);
}

void
mutex_spin_enter(kmutex_t *mtx)
{

	mutex_enter(mtx);
}

int
mutex_tryenter(kmutex_t *mtx)
{
	UPMTX(mtx);

	if (upm->upm_owner)
		return 0;

	upm->upm_owner = curlwp;
	return 1;
}

void
mutex_exit(kmutex_t *mtx)
{
	UPMTX(mtx);

	if (upm->upm_wanted) {
		rumpuser_cv_signal(upm->upm_rucv); /* CPU is our interlock */
	}
	upm->upm_owner = NULL;
}

void
mutex_spin_exit(kmutex_t *mtx)
{

	mutex_exit(mtx);
}

int
mutex_owned(kmutex_t *mtx)
{
	UPMTX(mtx);

	return upm->upm_owner == curlwp;
}

struct uprw {
	struct lwp *uprw_owner;
	int uprw_readers;
	uint16_t uprw_rwant;
	uint16_t uprw_wwant;
	struct rumpuser_cv *uprw_rucv_reader;
	struct rumpuser_cv *uprw_rucv_writer;
};

#define UPRW(rw) struct uprw *uprw = *(struct uprw **)rw

/* reader/writer locks */

void
rw_init(krwlock_t *rw)
{
	struct uprw *uprw;

	CTASSERT(sizeof(krwlock_t) >= sizeof(void *));
	checkncpu();

	uprw = rump_hypermalloc(sizeof(*uprw), 0, true, "rwinit");
	memset(uprw, 0, sizeof(*uprw));
	rumpuser_cv_init(&uprw->uprw_rucv_reader);
	rumpuser_cv_init(&uprw->uprw_rucv_writer);
	memcpy(rw, &uprw, sizeof(void *));
}

void
rw_destroy(krwlock_t *rw)
{
	UPRW(rw);

	rumpuser_cv_destroy(uprw->uprw_rucv_reader);
	rumpuser_cv_destroy(uprw->uprw_rucv_writer);
	rump_hyperfree(uprw, sizeof(*uprw));
}

/* take rwlock.  prefer writers over readers (see rw_tryenter and rw_exit) */
void
rw_enter(krwlock_t *rw, const krw_t op)
{
	UPRW(rw);
	struct rumpuser_cv *rucv;
	uint16_t *wp;

	if (rw_tryenter(rw, op))
		return;

	/* lagpath */
	if (op == RW_READER) {
		rucv = uprw->uprw_rucv_reader;
		wp = &uprw->uprw_rwant;
	} else {
		rucv = uprw->uprw_rucv_writer;
		wp = &uprw->uprw_wwant;
	}

	(*wp)++;
	while (!rw_tryenter(rw, op)) {
		rump_schedlock_cv_wait(rucv);
	}
	(*wp)--;
}

int
rw_tryenter(krwlock_t *rw, const krw_t op)
{
	UPRW(rw);

	switch (op) {
	case RW_READER:
		if (uprw->uprw_owner == NULL && uprw->uprw_wwant == 0) {
			uprw->uprw_readers++;
			return 1;
		}
		break;
	case RW_WRITER:
		if (uprw->uprw_owner == NULL && uprw->uprw_readers == 0) {
			uprw->uprw_owner = curlwp;
			return 1;
		}
		break;
	}

	return 0;
}

void
rw_exit(krwlock_t *rw)
{
	UPRW(rw);

	if (uprw->uprw_readers > 0) {
		uprw->uprw_readers--;
	} else {
		KASSERT(uprw->uprw_owner == curlwp);
		uprw->uprw_owner = NULL;
	}

	if (uprw->uprw_wwant) {
		rumpuser_cv_signal(uprw->uprw_rucv_writer);
	} else if (uprw->uprw_rwant) {
		rumpuser_cv_signal(uprw->uprw_rucv_reader);
	}
}

int
rw_tryupgrade(krwlock_t *rw)
{
	UPRW(rw);

	if (uprw->uprw_readers == 1 && uprw->uprw_owner == NULL) {
		uprw->uprw_readers = 0;
		uprw->uprw_owner = curlwp;
		return 1;
	} else {
		return 0;
	}
}

int
rw_write_held(krwlock_t *rw)
{
	UPRW(rw);

	return uprw->uprw_owner == curlwp;
}

int
rw_read_held(krwlock_t *rw)
{
	UPRW(rw);

	return uprw->uprw_readers > 0;
}

int
rw_lock_held(krwlock_t *rw)
{
	UPRW(rw);

	return uprw->uprw_owner || uprw->uprw_readers;
}

krw_t
rw_lock_op(krwlock_t *rw)
{

	return rw_write_held(rw) ? RW_WRITER : RW_READER;
}

/*
 * Condvars are almost the same as in the MP case except that we
 * use the scheduler mutex as the pthread interlock instead of the
 * mutex associated with the condvar.
 */

#define RUMPCV(cv) (*(struct rumpuser_cv **)(cv))

void
cv_init(kcondvar_t *cv, const char *msg)
{

	CTASSERT(sizeof(kcondvar_t) >= sizeof(void *));
	checkncpu();

	rumpuser_cv_init((struct rumpuser_cv **)cv);
}

void
cv_destroy(kcondvar_t *cv)
{

	rumpuser_cv_destroy(RUMPCV(cv));
}

void
cv_wait(kcondvar_t *cv, kmutex_t *mtx)
{
#ifdef DIAGNOSTIC
	UPMTX(mtx);
	KASSERT(upm->upm_owner == curlwp);

	if (rump_threads == 0)
		panic("cv_wait without threads");
#endif

	/*
	 * NOTE: we must atomically release the *CPU* here, i.e.
	 * nothing between mutex_exit and entering rumpuser condwait
	 * may preempt us from the virtual CPU.
	 */
	mutex_exit(mtx);
	rump_schedlock_cv_wait(RUMPCV(cv));
	mutex_enter(mtx);
}

int
cv_wait_sig(kcondvar_t *cv, kmutex_t *mtx)
{

	cv_wait(cv, mtx);
	return 0;
}

int
cv_timedwait(kcondvar_t *cv, kmutex_t *mtx, int ticks)
{
	struct timespec ts;

#ifdef DIAGNOSTIC
	UPMTX(mtx);
	KASSERT(upm->upm_owner == curlwp);
#endif

	ts.tv_sec = ticks / hz;
	ts.tv_nsec = (ticks % hz) * (1000000000/hz);

	if (ticks == 0) {
		cv_wait(cv, mtx);
		return 0;
	} else {
		int rv;
		mutex_exit(mtx);
		rv = rump_schedlock_cv_timedwait(RUMPCV(cv), &ts);
		mutex_enter(mtx);
		if (rv)
			return EWOULDBLOCK;
		else
			return 0;
	}
}

int
cv_timedwait_sig(kcondvar_t *cv, kmutex_t *mtx, int ticks)
{

	return cv_timedwait(cv, mtx, ticks);
}

void
cv_signal(kcondvar_t *cv)
{

	/* CPU == interlock */
	rumpuser_cv_signal(RUMPCV(cv));
}

void
cv_broadcast(kcondvar_t *cv)
{

	/* CPU == interlock */
	rumpuser_cv_broadcast(RUMPCV(cv));
}

bool
cv_has_waiters(kcondvar_t *cv)
{
	int n;

	rumpuser_cv_has_waiters(RUMPCV(cv), &n);

	return n > 0;
}

/* this is not much of an attempt, but ... */
bool
cv_is_valid(kcondvar_t *cv)
{

	return RUMPCV(cv) != NULL;
}