/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
 *   Copyright 2009 Intel Corporation; author: H. Peter Anvin
 *
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software and associated documentation
 *   files (the "Software"), to deal in the Software without
 *   restriction, including without limitation the rights to use,
 *   copy, modify, merge, publish, distribute, sublicense, and/or
 *   sell copies of the Software, and to permit persons to whom
 *   the Software is furnished to do so, subject to the following
 *   conditions:
 *
 *   The above copyright notice and this permission notice shall
 *   be included in all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *   OTHER DEALINGS IN THE SOFTWARE.
 *
 * ----------------------------------------------------------------------- */

/*
 * shuffle.c
 *
 * Common code for "shuffle and boot" operation; generates a shuffle list
 * and puts it in the bounce buffer.  Returns the number of shuffle
 * descriptors.
 */

#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <com32.h>
#include <core.h>
#include <minmax.h>
#include <dprintf.h>
#include <syslinux/movebits.h>
#include <klibc/compiler.h>

struct shuffle_descriptor {
    uint32_t dst, src, len;
};

static int shuffler_size;

static void __constructor __syslinux_get_shuffer_size(void)
{
    /* +15 padding is to guarantee alignment */
    shuffler_size = __bcopyxx_len + 15;
}

/*
 * Allocate descriptor memory in these chunks; if this is large we may
 * waste memory, if it is small we may get slow convergence.
 */
#define DESC_BLOCK_SIZE	256

int syslinux_do_shuffle(struct syslinux_movelist *fraglist,
			struct syslinux_memmap *memmap,
			addr_t entry_point, addr_t entry_type,
			uint16_t bootflags)
{
    int rv = -1;
    struct syslinux_movelist *moves = NULL, *mp;
    struct syslinux_memmap *rxmap = NULL, *ml;
    struct shuffle_descriptor *dp, *dbuf;
    int np;
    int desc_blocks, need_blocks;
    int need_ptrs;
    addr_t desczone, descfree, descaddr;
    int nmoves, nzero;
    com32sys_t ireg;

    descaddr = 0;
    dp = dbuf = NULL;

    /* Count the number of zero operations */
    nzero = 0;
    for (ml = memmap; ml->type != SMT_END; ml = ml->next) {
	if (ml->type == SMT_ZERO)
	    nzero++;
    }

    /* Find the largest contiguous region unused by input *and* output;
       this is where we put the move descriptor list and safe area */

    rxmap = syslinux_dup_memmap(memmap);
    if (!rxmap)
	goto bail;
    /* Avoid using the low 1 MB for the shuffle area -- this avoids
       possible interference with the real mode code or stack */
    if (syslinux_add_memmap(&rxmap, 0, 1024 * 1024, SMT_RESERVED))
	goto bail;
    for (mp = fraglist; mp; mp = mp->next) {
	if (syslinux_add_memmap(&rxmap, mp->src, mp->len, SMT_ALLOC) ||
	    syslinux_add_memmap(&rxmap, mp->dst, mp->len, SMT_ALLOC))
	    goto bail;
    }
    if (syslinux_memmap_largest(rxmap, SMT_FREE, &desczone, &descfree))
	goto bail;

    syslinux_free_memmap(rxmap);

    dprintf("desczone = 0x%08x, descfree = 0x%08x\n", desczone, descfree);

    rxmap = syslinux_dup_memmap(memmap);
    if (!rxmap)
	goto bail;

    desc_blocks = (nzero + DESC_BLOCK_SIZE - 1) / DESC_BLOCK_SIZE;
    for (;;) {
	/* We want (desc_blocks) allocation blocks, plus the terminating
	   descriptor, plus the shuffler safe area. */
	addr_t descmem = desc_blocks *
	    sizeof(struct shuffle_descriptor) * DESC_BLOCK_SIZE
	    + sizeof(struct shuffle_descriptor) + shuffler_size;

	descaddr = (desczone + descfree - descmem) & ~3;

	if (descaddr < desczone)
	    goto bail;		/* No memory block large enough */

	/* Mark memory used by shuffle descriptors as reserved */
	if (syslinux_add_memmap(&rxmap, descaddr, descmem, SMT_RESERVED))
	    goto bail;

#if DEBUG > 1
	syslinux_dump_movelist(fraglist);
#endif

	if (syslinux_compute_movelist(&moves, fraglist, rxmap))
	    goto bail;

	nmoves = 0;
	for (mp = moves; mp; mp = mp->next)
	    nmoves++;

	need_blocks = (nmoves + nzero + DESC_BLOCK_SIZE - 1) / DESC_BLOCK_SIZE;

	if (desc_blocks >= need_blocks)
	    break;		/* Sufficient memory, yay */

	desc_blocks = need_blocks;	/* Try again... */
    }

#if DEBUG > 1
    dprintf("Final movelist:\n");
    syslinux_dump_movelist(moves);
#endif

    syslinux_free_memmap(rxmap);
    rxmap = NULL;

    need_ptrs = nmoves + nzero + 1;
    dbuf = malloc(need_ptrs * sizeof(struct shuffle_descriptor));
    if (!dbuf)
	goto bail;

#if DEBUG
    {
	addr_t descoffs = descaddr - (addr_t) dbuf;

	dprintf("nmoves = %d, nzero = %d, dbuf = %p, offs = 0x%08x\n",
		nmoves, nzero, dbuf, descoffs);
    }
#endif

    /* Copy the move sequence into the descriptor buffer */
    np = 0;
    dp = dbuf;
    for (mp = moves; mp; mp = mp->next) {
	dp->dst = mp->dst;
	dp->src = mp->src;
	dp->len = mp->len;
	dprintf2("[ %08x %08x %08x ]\n", dp->dst, dp->src, dp->len);
	dp++;
	np++;
    }

    /* Copy bzero operations into the descriptor buffer */
    for (ml = memmap; ml->type != SMT_END; ml = ml->next) {
	if (ml->type == SMT_ZERO) {
	    dp->dst = ml->start;
	    dp->src = (addr_t) - 1;	/* bzero region */
	    dp->len = ml->next->start - ml->start;
	    dprintf2("[ %08x %08x %08x ]\n", dp->dst, dp->src, dp->len);
	    dp++;
	    np++;
	}
    }

    /* Finally, record the termination entry */
    dp->dst = entry_point;
    dp->src = entry_type;
    dp->len = 0;
    dp++;
    np++;

    if (np != need_ptrs) {
	dprintf("!!! np = %d : nmoves = %d, nzero = %d, desc_blocks = %d\n",
		np, nmoves, nzero, desc_blocks);
    }

    rv = 0;

bail:
    /* This is safe only because free() doesn't use the bounce buffer!!!! */
    if (moves)
	syslinux_free_movelist(moves);
    if (rxmap)
	syslinux_free_memmap(rxmap);

    if (rv)
	return rv;

    /* Actually do it... */
    memset(&ireg, 0, sizeof ireg);
    ireg.edi.l = descaddr;
    ireg.esi.l = (addr_t) dbuf;
    ireg.ecx.l = (addr_t) dp - (addr_t) dbuf;
    ireg.edx.w[0] = bootflags;
    ireg.eax.w[0] = 0x0024;
    __intcall(0x22, &ireg, NULL);

    return -1;			/* Shouldn't have returned! */
}

/*
 * Common helper routine: takes a memory map and blots out the
 * zones which are used in the destination of a fraglist
 */
struct syslinux_memmap *syslinux_target_memmap(struct syslinux_movelist
					       *fraglist,
					       struct syslinux_memmap *memmap)
{
    struct syslinux_memmap *tmap;
    struct syslinux_movelist *mp;

    tmap = syslinux_dup_memmap(memmap);
    if (!tmap)
	return NULL;

    for (mp = fraglist; mp; mp = mp->next) {
	if (syslinux_add_memmap(&tmap, mp->dst, mp->len, SMT_ALLOC)) {
	    syslinux_free_memmap(tmap);
	    return NULL;
	}
    }

    return tmap;
}
