/*	$OpenBSD: tib.h,v 1.3 2016/05/07 19:05:21 guenther Exp $	*/
/*
 * Copyright (c) 2011,2014 Philip Guenther <guenther@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Thread Information Block (TIB) and Thread Local Storage (TLS) handling
 * (the TCB, Thread Control Block, is part of the TIB)
 */

#ifndef	_TIB_H_
#define	_TIB_H_

#include <sys/types.h>
#include <machine/tcb.h>

#include <stddef.h>


/*
 * This header defines struct tib and at least eight macros:
 *	TLS_VARIANT
 *		Either 1 or 2  (Actually defined by <machine/tcb.h>)
 *
 *	TCB_SET(tcb)
 *		Set the TCB pointer for this thread to 'tcb'
 *
 *	TCB_GET()
 *		Return the TCB pointer for this thread
 *
 *	TCB_TO_TIB(tcb)
 *		Given a TCB pointer, return the matching TIB pointer
 *
 *	TIB_TO_TCB(tib)
 *		Given a TIB pointer, return the matching TCB pointer
 *
 *	TIB_INIT(tib, dtv, thread)
 *		Initializes a TIB for a new thread, using the supplied
 *		values for its dtv and thread pointers
 *
 *	TIB_GET()
 *		Short-hand for TCB_TO_TIB(TCB_GET())
 *
 *	TIB_EXTRA_ALIGN
 *		On TLS varaint 2 archs, what alignment is sufficient
 *		for the extra space that will be used for struct pthread?
 *
 * The following functions are provided by either ld.so (dynamic) or
 * libc (static) for allocating and freeing a common memory block that
 * will hold both the TIB and the pthread structure:
 *	_dl_allocate_tib(sizeof(struct pthread))
 *		Allocates a combined TIB and pthread memory region.
 *		The argument is the amount of space to reserve
 *		for the pthread structure.  Returns a pointer to
 *		the TIB inside the allocated block.
 *
 * 	_dl_free_tib(tib, sizeof(struct pthread))
 *		Frees a TIB and pthread block previously allocated
 *		with _dl_allocate_tib().  Must be passed the return
 *		value of that previous call.
 */

/*
 * Regarding <machine/tcb.h>:
 *  - it must define the TLS_VARIANT macro
 *  - it may define TCB_OFFSET if the TCB address in the kernel and/or
 *    register is offset from the actual TCB address.  TCB_OFFSET > 0
 *    means the kernel/register points to *after* the real data.
 *  - if there's a faster way to get or set the TCB pointer for the thread
 *    than the __{get,set}_tcb() syscalls, it should define either or both
 *    the TCB_{GET,SET} macros to do so.
 */


/* If <machine/tcb.h> doesn't provide a better way, then use the default */
#ifdef	TCB_GET
# define TCB_HAVE_MD_GET	1
#else
# define TCB_GET()		__get_tcb()
#endif
#ifdef	TCB_SET
# define TCB_HAVE_MD_SET	1
#else
# define TCB_SET(tcb)		__set_tcb(tcb)
#endif
#ifndef TCB_OFFSET
# define TCB_OFFSET	0
#endif

/*
 * tib_cantcancel values is non-zero is the thread should skip all
 * cancellation processing
 */
#define CANCEL_DISABLED	1
#define CANCEL_DYING	2

/*
 * tib_cancel_point is non-zero if we're in a cancel point; its modified
 * by the cancel point code and read by the cancellation signal handler
 */
#define CANCEL_POINT		1
#define CANCEL_POINT_DELAYED	2


#if TLS_VARIANT == 1
/*
 * ABI specifies that the static TLS data starts two words after the
 * (notional) thread pointer, with the first of those two words being
 * the TLS dtv pointer.  The other (second) word is reserved for the
 * implementation, so we place the pointer to the thread structure there,
 * but we place our actual thread bits before the TCB, at negative offsets
 * from the TCB pointer.  Ergo, memory is laid out, low to high, as:
 *
 *	[pthread structure]
 *	TIB {
 *		...cancelation and other int-sized info...
 *		int errno
 *		void *locale
 *		TCB (- TCB_OFFSET) {
 *			void *dtv
 *			struct pthread *thread
 *		}
 *	}
 *	static TLS data
 */

struct tib {
	int	tib_thread_flags;	/* internal to libpthread */
	pid_t	tib_tid;
	int	tib_cantcancel;
	int	tib_cancel_point;
	int	tib_canceled;
	int	tib_errno;
	void	*tib_locale;
	void	*tib_dtv;		/* internal to the runtime linker */
	void	*tib_thread;
};


#elif TLS_VARIANT == 2
/*
 * ABI specifies that the static TLS data occupies the memory before
 * the TCB pointer, at negative offsets, and that on i386 and amd64
 * the word the TCB points to contains a pointer to itself.  So,
 * we place errno and our thread bits after that.  Memory is laid
 * out, low to high, as:
 *	static TLS data
 *	TIB {
 *		TCB (- TCB_OFFSET) {
 *			self pointer [i386/amd64 only]
 *			void *dtv
 *		}
 *		struct pthread *thread
 *		void *locale
 *		int errno
 *		...cancelation and other int-sized info...
 *	}
 *	[pthread structure]
 */

struct tib {
#if defined(__i386) || defined(__amd64)
	struct	tib *__tib_self;
# define __tib_tcb __tib_self
#endif
	void	*tib_dtv;		/* internal to the runtime linker */
	void	*tib_thread;
	void	*tib_locale;
	int	tib_errno;
	int	tib_canceled;
	int	tib_cancel_point;
	int	tib_cantcancel;
	pid_t	tib_tid;
	int	tib_thread_flags;	/* internal to libpthread */
#if !defined(__LP64__) && !defined(__i386)
	int	__tib_padding;		/* padding for 8byte alignment */
#endif
};

#if defined(__i386) || defined(__amd64)
# define _TIB_PREP(tib)	\
	((void)((tib)->__tib_self = (tib)))
#elif !defined(__LP64__) && !defined(__i386)
# define _TIB_PREP(tib)	((void)((tib)->__tib_padding = 0))
#endif

#define	TIB_EXTRA_ALIGN		sizeof(void *)

#else
# error "unknown TLS variant"
#endif

/* nothing to do by default */
#ifndef	_TIB_PREP
# define _TIB_PREP(tib)	((void)0)
#endif

#define	TIB_INIT(tib, dtv, thread)	do {		\
		(tib)->tib_thread	= (thread);	\
		(tib)->tib_locale	= NULL;		\
		(tib)->tib_cantcancel	= 0;		\
		(tib)->tib_cancel_point	= 0;		\
		(tib)->tib_canceled	= 0;		\
		(tib)->tib_dtv		= (dtv);	\
		(tib)->tib_errno	= 0;		\
		_TIB_PREP(tib);				\
	} while (0)

#ifndef	__tib_tcb
# define __tib_tcb		tib_dtv
#endif
#define	_TIBO_TCB		(offsetof(struct tib, __tib_tcb) + TCB_OFFSET)

#define	TCB_TO_TIB(tcb)		((struct tib *)((char *)(tcb) - _TIBO_TCB))
#define	TIB_TO_TCB(tib)		((char *)(tib) + _TIBO_TCB)
#define	TIB_GET()		TCB_TO_TIB(TCB_GET())


__BEGIN_DECLS
void	*_dl_allocate_tib(size_t _extra) __dso_public;
void	_dl_free_tib(void *_tib, size_t _extra) __dso_public;

/* The actual syscalls */
void	*__get_tcb(void);
void	__set_tcb(void *_tcb);
__END_DECLS

#endif /* _TIB_H_ */
