/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/param.h>		/* for NULL */
#include <sys/sbd_ioctl.h>
#include <sys/dr_util.h>
#include <sys/varargs.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>

/* sbd_etab[] and sbd_etab_len provided by sbdgenerr.pl */
extern sbd_etab_t sbd_etab[];
extern int sbd_etab_len;

sbd_error_t *
sbd_err_new(int e_code, char *fmt, va_list args)
{
	sbd_error_t	*new;

	new = GETSTRUCT(sbd_error_t, 1);
	new->e_code = e_code;

	if (fmt)
		(void) vsnprintf(new->e_rsc, sizeof (new->e_rsc), fmt, args);

	return (new);
}

void
sbd_err_log(sbd_error_t *ep, int ce)
{
	char		 buf[32];
	char		*fmt;
	char		*txt;
	int		 i;
	sbd_etab_t	*tp;

	if (!ep)
		return;

	if (ep->e_rsc[0] == '\0')
		fmt = "%s";
	else
		fmt = "%s: %s";

	for (tp = sbd_etab, i = 0; i < sbd_etab_len; i++, tp++)
		if (ep->e_code >= tp->t_base && ep->e_code <= tp->t_bnd)
			break;

	if (i < sbd_etab_len)
		txt = tp->t_text[ep->e_code - tp->t_base];
	else {
		(void) snprintf(buf, sizeof (buf), "error %d", ep->e_code);
		txt = buf;
	}

	cmn_err(ce, fmt, txt, ep->e_rsc);
}

void
sbd_err_clear(sbd_error_t **ep)
{
	FREESTRUCT(*ep, sbd_error_t, 1);
	*ep = NULL;
}

void
sbd_err_set_c(sbd_error_t **ep, int ce, int e_code, char *fmt, ...)
{
	sbd_error_t	*tmp;
	va_list		args;

	va_start(args, fmt);

	tmp = sbd_err_new(e_code, fmt, args);

	sbd_err_log(tmp, ce);

	if (*ep == NULL)
		*ep = tmp;
	else
		sbd_err_clear(&tmp);

	va_end(args);
}

void
sbd_err_set(sbd_error_t **ep, int ce, int e_code, char *fmt, ...)
{
	sbd_error_t	*tmp;
	va_list		args;

	va_start(args, fmt);

	tmp = sbd_err_new(e_code, fmt, args);

	sbd_err_log(tmp, ce);

	*ep = tmp;

	va_end(args);
}

sbd_error_t *
drerr_new_v(int e_code, char *fmt, va_list args)
{
	return (sbd_err_new(e_code, fmt, args));
}

sbd_error_t *
drerr_new(int log, int e_code, char *fmt, ...)
{
	sbd_error_t	*ep;
	va_list		 args;

	va_start(args, fmt);
	ep = sbd_err_new(e_code, fmt, args);
	va_end(args);

	if (log)
		sbd_err_log(ep, CE_WARN);

	return (ep);
}

void
drerr_set_c(int log, sbd_error_t **ep, int e_code, char *fmt, ...)
{
	sbd_error_t	*err;
	va_list		 args;

	va_start(args, fmt);
	err = sbd_err_new(e_code, fmt, args);
	va_end(args);

	if (log)
		sbd_err_log(err, CE_WARN);

	if (*ep == NULL)
		*ep = err;
	else
		sbd_err_clear(&err);
}


/*
 * Memlist support.
 */
void
dr_memlist_delete(struct memlist *mlist)
{
	register struct memlist	*ml;

	for (ml = mlist; ml; ml = mlist) {
		mlist = ml->ml_next;
		FREESTRUCT(ml, struct memlist, 1);
	}
}

int
dr_memlist_intersect(struct memlist *al, struct memlist *bl)
{
	uint64_t	astart, aend, bstart, bend;

	if ((al == NULL) || (bl == NULL))
		return (0);

	aend = al->ml_address + al->ml_size;
	bstart = bl->ml_address;
	bend = bl->ml_address + bl->ml_size;

	while (al && bl) {
		while (al && (aend <= bstart))
			if ((al = al->ml_next) != NULL)
				aend = al->ml_address + al->ml_size;
		if (al == NULL)
			return (0);

		if ((astart = al->ml_address) <= bstart)
			return (1);

		while (bl && (bend <= astart))
			if ((bl = bl->ml_next) != NULL)
				bend = bl->ml_address + bl->ml_size;
		if (bl == NULL)
			return (0);

		if ((bstart = bl->ml_address) <= astart)
			return (1);
	}

	return (0);
}

void
dr_memlist_coalesce(struct memlist *mlist)
{
	uint64_t	end, nend;

	if ((mlist == NULL) || (mlist->ml_next == NULL))
		return;

	while (mlist->ml_next) {
		end = mlist->ml_address + mlist->ml_size;
		if (mlist->ml_next->ml_address <= end) {
			struct memlist 	*nl;

			nend = mlist->ml_next->ml_address +
			    mlist->ml_next->ml_size;
			if (nend > end)
				mlist->ml_size += (nend - end);
			nl = mlist->ml_next;
			mlist->ml_next = mlist->ml_next->ml_next;
			if (nl) {
				FREESTRUCT(nl, struct memlist, 1);
			}
			if (mlist->ml_next)
				mlist->ml_next->ml_prev = mlist;
		} else {
			mlist = mlist->ml_next;
		}
	}
}

#ifdef DEBUG
void
memlist_dump(struct memlist *mlist)
{
	register struct memlist *ml;

	if (mlist == NULL)
		printf("memlist> EMPTY\n");
	else for (ml = mlist; ml; ml = ml->ml_next)
		printf("memlist> 0x%lx, 0x%lx\n", ml->ml_address, ml->ml_size);
}
#endif

struct memlist *
dr_memlist_dup(struct memlist *mlist)
{
	struct memlist *hl = NULL, *tl, **mlp;

	if (mlist == NULL)
		return (NULL);

	mlp = &hl;
	tl = *mlp;
	for (; mlist; mlist = mlist->ml_next) {
		*mlp = GETSTRUCT(struct memlist, 1);
		(*mlp)->ml_address = mlist->ml_address;
		(*mlp)->ml_size = mlist->ml_size;
		(*mlp)->ml_prev = tl;
		tl = *mlp;
		mlp = &((*mlp)->ml_next);
	}
	*mlp = NULL;

	return (hl);
}

struct memlist *
dr_memlist_add_span(struct memlist *mlist, uint64_t base, uint64_t len)
{
	struct memlist	*ml, *tl, *nl;

	if (len == 0ull)
		return (NULL);

	if (mlist == NULL) {
		mlist = GETSTRUCT(struct memlist, 1);
		mlist->ml_address = base;
		mlist->ml_size = len;
		mlist->ml_next = mlist->ml_prev = NULL;

		return (mlist);
	}

	for (tl = ml = mlist; ml; tl = ml, ml = ml->ml_next) {
		if (base < ml->ml_address) {
			if ((base + len) < ml->ml_address) {
				nl = GETSTRUCT(struct memlist, 1);
				nl->ml_address = base;
				nl->ml_size = len;
				nl->ml_next = ml;
				if ((nl->ml_prev = ml->ml_prev) != NULL)
					nl->ml_prev->ml_next = nl;
				ml->ml_prev = nl;
				if (mlist == ml)
					mlist = nl;
			} else {
				ml->ml_size = MAX((base + len),
				    (ml->ml_address + ml->ml_size)) - base;
				ml->ml_address = base;
			}
			break;

		} else if (base <= (ml->ml_address + ml->ml_size)) {
			ml->ml_size = MAX((base + len),
			    (ml->ml_address + ml->ml_size)) -
			    MIN(ml->ml_address, base);
			ml->ml_address = MIN(ml->ml_address, base);
			break;
		}
	}
	if (ml == NULL) {
		nl = GETSTRUCT(struct memlist, 1);
		nl->ml_address = base;
		nl->ml_size = len;
		nl->ml_next = NULL;
		nl->ml_prev = tl;
		tl->ml_next = nl;
	}

	dr_memlist_coalesce(mlist);

	return (mlist);
}

struct memlist *
dr_memlist_del_span(struct memlist *mlist, uint64_t base, uint64_t len)
{
	uint64_t	end;
	struct memlist	*ml, *tl, *nlp;

	if (mlist == NULL)
		return (NULL);

	end = base + len;
	if ((end <= mlist->ml_address) || (base == end))
		return (mlist);

	for (tl = ml = mlist; ml; tl = ml, ml = nlp) {
		uint64_t	mend;

		nlp = ml->ml_next;

		if (end <= ml->ml_address)
			break;

		mend = ml->ml_address + ml->ml_size;
		if (base < mend) {
			if (base <= ml->ml_address) {
				ml->ml_address = end;
				if (end >= mend)
					ml->ml_size = 0ull;
				else
					ml->ml_size = mend - ml->ml_address;
			} else {
				ml->ml_size = base - ml->ml_address;
				if (end < mend) {
					struct memlist	*nl;
					/*
					 * splitting an memlist entry.
					 */
					nl = GETSTRUCT(struct memlist, 1);
					nl->ml_address = end;
					nl->ml_size = mend - nl->ml_address;
					if ((nl->ml_next = nlp) != NULL)
						nlp->ml_prev = nl;
					nl->ml_prev = ml;
					ml->ml_next = nl;
					nlp = nl;
				}
			}
			if (ml->ml_size == 0ull) {
				if (ml == mlist) {
					if ((mlist = nlp) != NULL)
						nlp->ml_prev = NULL;
					FREESTRUCT(ml, struct memlist, 1);
					if (mlist == NULL)
						break;
					ml = nlp;
				} else {
					if ((tl->ml_next = nlp) != NULL)
						nlp->ml_prev = tl;
					FREESTRUCT(ml, struct memlist, 1);
					ml = tl;
				}
			}
		}
	}

	return (mlist);
}

/*
 * add span without merging
 */
struct memlist *
dr_memlist_cat_span(struct memlist *mlist, uint64_t base, uint64_t len)
{
	struct memlist	*ml, *tl, *nl;

	if (len == 0ull)
		return (NULL);

	if (mlist == NULL) {
		mlist = GETSTRUCT(struct memlist, 1);
		mlist->ml_address = base;
		mlist->ml_size = len;
		mlist->ml_next = mlist->ml_prev = NULL;

		return (mlist);
	}

	for (tl = ml = mlist; ml; tl = ml, ml = ml->ml_next) {
		if (base < ml->ml_address) {
			nl = GETSTRUCT(struct memlist, 1);
			nl->ml_address = base;
			nl->ml_size = len;
			nl->ml_next = ml;
			if ((nl->ml_prev = ml->ml_prev) != NULL)
				nl->ml_prev->ml_next = nl;
			ml->ml_prev = nl;
			if (mlist == ml)
				mlist = nl;
			break;
		}
	}

	if (ml == NULL) {
		nl = GETSTRUCT(struct memlist, 1);
		nl->ml_address = base;
		nl->ml_size = len;
		nl->ml_next = NULL;
		nl->ml_prev = tl;
		tl->ml_next = nl;
	}

	return (mlist);
}