/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"


#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <libc_int.h>
#include <_rtld.h>
#include <_elf.h>
#include <msg.h>
#include <debug.h>

static ulong_t	tls_static_size = 0;	/* static TLS buffer size */

#define	TLSBLOCKCNT	16	/* number of blocks of tmi_bits to allocate */
				/* at a time. */
typedef struct {
	uint_t	*tmi_bits;
	ulong_t	tmi_lowfree;
	ulong_t	tmi_cnt;
} Tlsmodid;

static Tlsmodid	tmid = {0, 0, 0};

ulong_t
tls_getmodid()
{
	ulong_t		ndx;
	ulong_t		i;

	if (tmid.tmi_bits == 0) {
		if ((tmid.tmi_bits = calloc(TLSBLOCKCNT, sizeof (uint_t))) == 0)
			return ((ulong_t)-1);
		tmid.tmi_bits[0] = 1;
		tmid.tmi_lowfree = 1;
		tmid.tmi_cnt = TLSBLOCKCNT;
		return (0);
	}

	for (i = tmid.tmi_lowfree / (sizeof (uint_t) * 8);
	    i < tmid.tmi_cnt; i++) {
		uint_t	j;
		/*
		 * If all bits are assigned - move on.
		 */
		if ((tmid.tmi_bits[i] ^ ~((uint_t)0)) == 0)
			continue;
		for (ndx = 0, j = 1; j; j = j << 1, ndx++) {
			if ((tmid.tmi_bits[i] & j) == 0) {
				tmid.tmi_bits[i] |= j;
				ndx = (i * (sizeof (uint_t)) * 8) + ndx;
				tmid.tmi_lowfree = ndx + 1;
				return (ndx);
			}
		}
	}

	/*
	 * All bits taken - must allocate a new block
	 */
	if ((tmid.tmi_bits = realloc(tmid.tmi_bits,
	    ((tmid.tmi_cnt * sizeof (uint_t)) +
	    (TLSBLOCKCNT * sizeof (uint_t))))) == 0)
		return ((ulong_t)-1);

	/*
	 * Clear out the tail of the new allocation.
	 */
	bzero(&(tmid.tmi_bits[tmid.tmi_cnt]), TLSBLOCKCNT * sizeof (uint_t));
	tmid.tmi_bits[tmid.tmi_cnt] = 1;
	ndx = (tmid.tmi_cnt * sizeof (uint_t)) * 8;
	tmid.tmi_lowfree = ndx + 1;
	tmid.tmi_cnt += TLSBLOCKCNT;

	return (ndx);
}

void
tls_freemodid(ulong_t modid)
{
	ulong_t	i;
	uint_t	j;

	i = modid / (sizeof (uint_t) * 8);
	/* LINTED */
	j = modid % (sizeof (uint_t) * 8);
	j = ~(1 << j);
	tmid.tmi_bits[i] &= j;
	if (modid < tmid.tmi_lowfree)
		tmid.tmi_lowfree = modid;
}

void
tls_modaddrem(Rt_map *lmp, uint_t flag)
{
	Lm_list		*lml = LIST(lmp);
	TLS_modinfo	tmi;
	Phdr		*tlsphdr;
	void		(*fptr)(TLS_modinfo *);

	if (flag & TM_FLG_MODADD) {
		fptr = (void (*)())lml->lm_lcs[CI_TLS_MODADD].lc_un.lc_func;
	} else if (FLAGS1(lmp) & FL1_RT_TLSADD) {
		fptr = (void (*)())lml->lm_lcs[CI_TLS_MODREM].lc_un.lc_func;
	} else {
		return;
	}

	tlsphdr = PTTLS(lmp);

	bzero(&tmi, sizeof (tmi));
	tmi.tm_modname = PATHNAME(lmp);
	tmi.tm_modid = TLSMODID(lmp);
	tmi.tm_tlsblock = (void *)(tlsphdr->p_vaddr);

	if (!(FLAGS(lmp) & FLG_RT_FIXED))
		tmi.tm_tlsblock = (void *)((uintptr_t)tmi.tm_tlsblock +
			ADDR(lmp));

	tmi.tm_filesz = tlsphdr->p_filesz;
	tmi.tm_memsz = tlsphdr->p_memsz;
	tmi.tm_flags = 0;
	tmi.tm_stattlsoffset = 0;

	DBG_CALL(Dbg_tls_modactivity(LIST(lmp), &tmi, flag));
	(*fptr)(&tmi);

	/*
	 * Tag that this link-map has registered its TLS, and free up the
	 * moduleid
	 */
	FLAGS1(lmp) |= FL1_RT_TLSADD;

	if (flag & TM_FLG_MODREM)
		tls_freemodid(TLSMODID(lmp));
}

void
tls_assign_soffset(Rt_map *lmp)
{
	/*
	 * Only objects on the primary link-map list are associated
	 * with the STATIC tls block.
	 */
	if (LIST(lmp)->lm_flags & LML_FLG_BASELM) {
		tls_static_size += S_ROUND(PTTLS(lmp)->p_memsz, M_TLSSTATALIGN);
		TLSSTATOFF(lmp) = tls_static_size;
	}

	/*
	 * Everyone get's a dynamic TLS modid.
	 */
	TLSMODID(lmp) = tls_getmodid();
}

int
tls_statmod(Lm_list *lml, Rt_map *lmp)
{
	uint_t		tlsmodndx, tlsmodcnt = lml->lm_tls;
	TLS_modinfo	**tlsmodlist, *tlsbuflist;
	Phdr		*tlsphdr;
	void		(*fptr)(TLS_modinfo **, ulong_t);

	fptr = (void (*)())lml->lm_lcs[CI_TLS_STATMOD].lc_un.lc_func;

	/*
	 * If we don't have any TLS modules - report that and return.
	 */
	if (tlsmodcnt == 0) {
		if (fptr)
			(*fptr)(0, 0);
		return (1);
	}
	lml->lm_tls = 0;

	/*
	 * Allocate a buffer to report the TLS modules, the buffer consists of:
	 *
	 *	TLS_modinfo *	ptrs[tlsmodcnt + 1]
	 *	TLS_modinfo	bufs[tlsmodcnt]
	 *
	 * The ptrs are initialized to the bufs - except the last
	 * one which null terminates the array.
	 */
	if ((tlsmodlist = calloc((sizeof (TLS_modinfo *) * (tlsmodcnt + 1)) +
	    (sizeof (TLS_modinfo) * tlsmodcnt), 1)) == 0)
		return (0);

	tlsbuflist = (TLS_modinfo *)((uintptr_t)tlsmodlist +
	    ((tlsmodcnt + 1) * sizeof (TLS_modinfo *)));

	for (tlsmodndx = 0; tlsmodndx < tlsmodcnt; tlsmodndx++)
		tlsmodlist[tlsmodndx] = &tlsbuflist[tlsmodndx];

	/*
	 * Account for the initial dtv ptr in the TLSSIZE calculation.
	 */
	tlsmodndx = 0;
	for (lmp = lml->lm_head; lmp; lmp = (Rt_map *)NEXT(lmp)) {
		if ((FCT(lmp) != &elf_fct) ||
		    (PTTLS(lmp) == 0) || (PTTLS(lmp)->p_memsz == 0))
			continue;

		tlsphdr = PTTLS(lmp);

		tlsmodlist[tlsmodndx]->tm_modname = PATHNAME(lmp);
		tlsmodlist[tlsmodndx]->tm_modid = TLSMODID(lmp);
		tlsmodlist[tlsmodndx]->tm_tlsblock = (void *)(tlsphdr->p_vaddr);

		if (!(FLAGS(lmp) & FLG_RT_FIXED)) {
			tlsmodlist[tlsmodndx]->tm_tlsblock = (void *)
			    ((uintptr_t)tlsmodlist[tlsmodndx]->tm_tlsblock +
			    ADDR(lmp));
		}
		tlsmodlist[tlsmodndx]->tm_filesz = tlsphdr->p_filesz;
		tlsmodlist[tlsmodndx]->tm_memsz = tlsphdr->p_memsz;
		tlsmodlist[tlsmodndx]->tm_flags = TM_FLG_STATICTLS;
		tlsmodlist[tlsmodndx]->tm_stattlsoffset = TLSSTATOFF(lmp);
		tlsmodndx++;
	}

	DBG_CALL(Dbg_tls_static_block(&lml_main, (void *)tlsmodlist,
	    tls_static_size));
	(*fptr)(tlsmodlist, tls_static_size);

	/*
	 * We're done with the list - clean it up.
	 */
	free(tlsmodlist);
	return (1);
}