/*	$NetBSD: lock_stubs.S,v 1.19 2022/04/06 22:47:58 riastradh Exp $	*/

/*-
 * Copyright (c) 2002, 2006, 2007 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe and Andrew Doran.
 *
 * 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.
 */

/*
 * Bugfix for mutex_spin_enter, which wasn't reentrant.
 *
 * Fixed by Johnny Billquist and Chuck Silvers
 */

#include "opt_lockdebug.h"
#include "opt_multiprocessor.h"
#include <machine/asm.h>
#include "assym.h"

#ifdef MULTIPROCESSOR
	.section	.bss
	.p2align	2
	.lcomm		cashash,256	/* 2048 bits */
#endif

#ifndef LOCKDEBUG
#if MTX_OWNER != 0
#error MTX_OWNER != 0, need to add offset to (%r1)
#endif

/*
 * void mutex_enter(kmutex_t *);
 */
ENTRY(mutex_enter, 0)
	movl	4(%ap), %r1		/* get mutex (ptr) */
#ifdef DIAGNOSTIC
	blbs	(%r1), 1f		/* go slow if this is SPIN */
#endif
	clrl	%r2			/* set old value (zero) */
	mfpr	$PR_SSP, %r3		/* set new value (curlwp) */
#ifndef MULTIPROCESSOR
	addl3	$CI_CAS_ADDR, L_CPU(%r3), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bsbw	_do_cas+2		/* do the compare-and-swap */
	tstl	%r0			/* is the old value what we wanted? */
	beql	2f			/*  yep, just branch to the return */
1:	callg	(%ap), _C_LABEL(mutex_vector_enter)
					/*  nope, there's an owner so go slow */
2:	ret

/*
 * void mutex_exit(kmutex_t *);
 */
ENTRY(mutex_exit, 0)
	movl	4(%ap), %r1		/* get mutex (ptr) */
#ifdef DIAGNOSTIC
	blbs	(%r1), 1f		/* go slow if this is SPIN */
#endif
	mfpr	$PR_SSP, %r2		/* get curlwp (old) */
	clrl	%r3			/* get zero (new) */
#ifndef MULTIPROCESSOR
	addl3	$CI_CAS_ADDR, L_CPU(%r2), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bsbw	_do_cas+2		/* do the compare-and-swap */
	cmpl	%r0,%r2			/* return == old? */
	beql	2f			/*   yes, branch to return */
1:	callg	(%ap), _C_LABEL(mutex_vector_exit) /*   no, slow path */
2:	ret

/*
 * void mutex_spin_enter(kmutex_t *);
 */
ENTRY(mutex_spin_enter, 0)
	movl	4(%ap), %r0			/* get spin mutex */
#ifdef DIAGNOSTIC
	blbc	(%r0), 3f
#endif
	mfpr	$PR_IPL, %r2			/* get current IPL */
	movzbl	MTX_IPL(%r0), %r3
	cmpl	%r3, %r2			/* does mutex have > IPL? */
	bleq	1f				/*   no, leave IPL alone */
	mtpr	%r3, $PR_IPL			/*   yes, raise IPL */
1:	mfpr	$PR_SSP, %r4			/* get curlwp */
	movl	L_CPU(%r4),%r4			/* get cpu_info */
	decl	CI_MTX_COUNT(%r4)		/* decr muntex count */
	bcc	3f				/* previous active - don't save IPL */
	movl	%r2, CI_MTX_OLDSPL(%r4)		/*   nope, save old IPL */
3:
#if defined(DIAGNOSTIC) || defined(MULTIPROCESSOR)
	bbssi	$0, MTX_LOCK(%r0), 4f		/* take out mutex */
	ret
4:	callg	(%ap), _C_LABEL(mutex_spin_retry)	/* slow path */
#else
	movb	$1, MTX_LOCK(%r0)		/* for ddb use only */
#endif
	ret

/*
 * void mutex_spin_exit(kmutex_t *);
 */
ENTRY(mutex_spin_exit, 0)
	movl	4(%ap), %r0			/* get spin mutex */
#if defined(DIAGNOSTIC)
	blbc	(%r0), 2f			/* assert this is a spinlock */
#endif
#if defined(DIAGNOSTIC) || defined(MULTIPROCESSOR)
	bbcci	$0, MTX_LOCK(%r0), 2f		/* clear mutex */
#else
	clrb	MTX_LOCK(%r0)			/* for ddb use only */
#endif
	mfpr	$PR_SSP, %r4			/* get curlwp */
	movl	L_CPU(%r4), %r4			/* get cpu_info */
	movl	CI_MTX_OLDSPL(%r4), %r2		/* fetch oldspl */
	incl	CI_MTX_COUNT(%r4)		/* incr mtx count */
	bneq	1f				/* still held? */
	mtpr	%r2, $PR_IPL			/*   no, restore saved ipl */
1:	ret

#if defined(DIAGNOSTIC) || defined(MULTIPROCESSOR)
2:	callg	(%ap), _C_LABEL(mutex_vector_exit)	/* slow path */
	ret
#endif

#if RW_READER != 0
#error RW_READER != 0, change tstl to cmpl $RW_READER
#endif
#if RW_HAS_WAITERS != 1
#error RW_HAS_WAITERS != 1, don't use blbs
#endif
#if RW_OWNER != 0
#error RW_OWNER != 0, need to add to loads
#endif
/*
 * void rw_enter(krwlock_t *rwl, krw_t op);
 */
ENTRY(rw_enter, 0)
	movl	4(%ap), %r1			/* grab rwlock ptr */
	tstl	8(%ap)				/* is this a reader op? */
	bneq	2f				/*   nope, branch to writer */

	movl	(%r1), %r2			/* get owner field */
	bitl	$(RW_WRITE_LOCKED|RW_WRITE_WANTED), %r2
						/* write active or pending? */
	bneq	3f				/*   yep, go slow */
	addl3	$RW_READ_INCR, %r2, %r3		/* incr. reader count (new) */
#ifndef MULTIPROCESSOR
	mfpr	$PR_SSP, %r4
	addl3	$CI_CAS_ADDR, L_CPU(%r4), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
1:	bsbw	_do_cas+2			/* do the compare-and-swap */
	cmpl	%r0, %r2			/* did it succeed? */
	bneq	3f				/*   nope, go slow */
	ret					/*   yep, return */

2:	clrl	%r2				/* get old value (zero) */
	mfpr	$PR_SSP, %r3			/* get new value (curlwp) */
#ifndef MULTIPROCESSOR
	addl3	$CI_CAS_ADDR, L_CPU(%r3), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bisl2	$RW_WRITE_LOCKED, %r3		/* show that it's a write */
	brb	1b				/* do the compare-and-swap */

3:	callg	(%ap), _C_LABEL(rw_vector_enter)
	ret

/*
 * void rw_exit(krwlock_t *rwl, krw_t op);
 */
ENTRY(rw_exit, 0)
	movl	4(%ap), %r1			/* grab rwlock ptr */
	movl	(%r1), %r2			/* grab owner (old) */
	bitl	$RW_WRITE_LOCKED, %r2		/* is it write locked? */
	bneq	2f				/*   yep, do the write case */

	blbs	%r2, 3f				/* RW_HAS_WAITERS mbz */
	subl3	$RW_READ_INCR, %r2, %r3		/* decr. reader count (new) */
	blss	3f				/* if less than 0, go slow */
#ifndef MULTIPROCESSOR
	mfpr	$PR_SSP, %r4			/* get curlwp */
	addl3	$CI_CAS_ADDR, L_CPU(%r4), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
1:	bsbw	_do_cas+2			/* do the compare-and-swap */
	cmpl	%r0, %r2			/* did it succeed? */
	bneq	3f				/*   nope, go slow */
	ret					/*   yes, return */

2:	mfpr	$PR_SSP, %r2			/* get old (curlwp) */
#ifndef MULTIPROCESSOR
	addl3	$CI_CAS_ADDR, L_CPU(%r2), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bisl2	$RW_WRITE_LOCKED, %r2		/* show that it's a write */
	clrl	%r3				/* get new (zero) */
	brb	1b				/* do the compare-and-swap */

3:	callg	(%ap), _C_LABEL(rw_vector_exit)
	ret

/*
 * bool rw_tryenter(krwlock_t *krw, krw_t op);
 */
ENTRY(rw_tryenter, 0)
	movl	4(%ap), %r1			/* get rwlock ptr */
	tstl	8(%ap)				/* is this a reader op? */
	bneq	3f				/*   nope, branch to writer */

	movl	(%r1), %r2			/* get owner field (old) */
	bitl	$(RW_WRITE_LOCKED|RW_WRITE_WANTED), %r2
						/* write active or pending? */
	bneq	2f				/*   yes, return failure */
	addl3	$RW_READ_INCR, %r2, %r3		/* incr reader count (new) */
#ifndef MULTIPROCESSOR
	mfpr	$PR_SSP, %r4
	addl3	$CI_CAS_ADDR, L_CPU(%r4), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
1:	bsbw	_do_cas+2			/* do the compare-and-swap */
	cmpl	%r0, %r2			/* did it succeed? */
	bneq	2f				/*   no, we failed. */
	movl	$1,%r0				/*   yes, indicate success */
	ret					/* return */
2:	clrl	%r0				/* indicate failure */
	ret					/* return */

3:	clrl	%r2				/* set old value (0) */
	mfpr	$PR_SSP, %r3			/* set new value (curlwp) */
#ifndef MULTIPROCESSOR
	addl3	$CI_CAS_ADDR, L_CPU(%r3), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bisl2	$RW_WRITE_LOCKED, %r3		/* show that it's a write */
	brb	1b				/* do the compare-and-swap */
#endif /* LOCKDEBUG */

/*
 * _atomic_cas_32(volatile uint32_t *p, uint32_t old, uint32_t new);
 */
ENTRY(_atomic_cas_32, 0)
	movq	4(%ap), %r1		/* cache ptr, old */
	movl	12(%ap), %r3		/* cache new */
#ifndef MULTIPROCESSOR
	mfpr	$PR_SSP, %r4
	addl3	$CI_CAS_ADDR, L_CPU(%r4), %r4 /* r4 == &curcpu()->ci_cas_addr */
#endif
	bsbw	_do_cas+2		/* do it */
	ret

ENTRY(_do_cas, 0)
#ifdef MULTIPROCESSOR
	movl	(%r1), %r0		/* get value */
	cmpl	%r0, %r2		/* does it equal old? */
	bneq	4f			/*    nope, return */
	extzv	$2,$11,%r1,%r4		/* gets bits 2-12 (our hash) */
	/*
	 * Lock everyone out on this cpu.
	 */
	mfpr	$PR_IPL, %r5		/* save IPL */
	mtpr	$IPL_HIGH, $PR_IPL	/* block everything */
1:	bbssi	%r4,cashash,1b		/* is this pos in the hash table set */
	movl	(%r1), %r0		/* get value again */
	cmpl	%r0, %r2		/* does it still equal old? */
	bneq	2f			/*    nope, return */
	movl	%r3,(%r1)		/* update *ptr with new */
2:
	bbcci	%r4,cashash,3f		/* clear this pos in the hash table */
3:
	mtpr	%r5, $PR_IPL		/* restore IPL */
4:
	rsb				/* return */
#else
/*
 * entry:
 *	r1 = address to be CAS'ed
 *	r2 = old value
 *	r3 = new value
 *	r4 = global cell to hold CAS address (common to all callers)
 *	     e.g. address of curcpu()->ci_cas_addr
 * exit:
 *	r0 = old value
 */
	.globl	cas32_ras_start, cas32_ras_end
cas32_ras_start:
	movl	%r1, (%r4)
	movl	*(%r4), %r0
	cmpl	%r2, %r0
	bneq	1f
	movl	%r3, *(%r4)
cas32_ras_end:
1:
	movl	$CASMAGIC, (%r4)
	rsb
#endif /* !MULTIPROCESSOR */
STRONG_ALIAS(atomic_cas_ptr,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_ptr,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_uint,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_uint,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_ulong,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_ulong,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_32,_atomic_cas_32)

STRONG_ALIAS(atomic_cas_ptr_ni,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_ptr_ni,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_uint_ni,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_uint_ni,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_ulong_ni,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_ulong_ni,_atomic_cas_32)
STRONG_ALIAS(atomic_cas_32_ni,_atomic_cas_32)
STRONG_ALIAS(_atomic_cas_32_ni,_atomic_cas_32)
