/*
 * HVM e820 support.
 *
 * Leendert van Doorn, leendert@watson.ibm.com
 * Copyright (c) 2005, International Business Machines Corporation.
 * Copyright (c) 2006, Keir Fraser, XenSource Inc.
 * Copyright (c) 2011, Citrix Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include "util.h"

struct e820map memory_map;

void memory_map_setup(void)
{
    unsigned int nr_entries = E820MAX, i;
    int rc;
    uint64_t alloc_addr = RESERVED_MEMORY_DYNAMIC_START;
    uint64_t alloc_size = RESERVED_MEMORY_DYNAMIC_END - alloc_addr;

    rc = get_mem_mapping_layout(memory_map.map, &nr_entries);

    if ( rc || !nr_entries )
    {
        printf("Get guest memory maps[%d] failed. (%d)\n", nr_entries, rc);
        BUG();
    }

    memory_map.nr_map = nr_entries;

    for ( i = 0; i < nr_entries; i++ )
    {
        if ( memory_map.map[i].type == E820_RESERVED &&
             check_overlap(alloc_addr, alloc_size,
                           memory_map.map[i].addr, memory_map.map[i].size) )
        {
            printf("Fail to setup memory map due to conflict");
            printf(" on dynamic reserved memory range.\n");
            BUG();
        }
    }
}

/*
 * Sometimes hvmloader may have relocated RAM so low_mem_pgend/high_mem_end
 * would be changed over there. But memory_map[] just records the
 * original low/high memory, so we need to sync these entries once
 * hvmloader modifies low/high memory.
 */
void adjust_memory_map(void)
{
    uint32_t low_mem_end = hvm_info->low_mem_pgend << PAGE_SHIFT;
    uint64_t high_mem_end = (uint64_t)hvm_info->high_mem_pgend << PAGE_SHIFT;
    unsigned int i;

    for ( i = 0; i < memory_map.nr_map; i++ )
    {
        uint64_t map_start = memory_map.map[i].addr;
        uint64_t map_size = memory_map.map[i].size;
        uint64_t map_end = map_start + map_size;

        /* If we need to adjust lowmem. */
        if ( memory_map.map[i].type == E820_RAM &&
             low_mem_end > map_start && low_mem_end < map_end )
        {
            memory_map.map[i].size = low_mem_end - map_start;
            continue;
        }

        /* Modify the existing highmem region if it exists. */
        if ( memory_map.map[i].type == E820_RAM &&
             high_mem_end && map_start == GB(4) )
        {
            if ( high_mem_end != map_end )
                memory_map.map[i].size = high_mem_end - map_start;
            high_mem_end = 0;
            continue;
        }
    }

    /* If there was no highmem region, just create one. */
    if ( high_mem_end )
    {
        memory_map.map[i].addr = GB(4);
        memory_map.map[i].size =
                ((uint64_t)hvm_info->high_mem_pgend << PAGE_SHIFT) -
                    memory_map.map[i].addr;
        memory_map.map[i].type = E820_RAM;
        memory_map.nr_map++;
    }
}

void dump_e820_table(struct e820entry *e820, unsigned int nr)
{
    uint64_t last_end = 0, start, end;
    int i;

    printf("E820 table:\n");

    for ( i = 0; i < nr; i++ )
    {
        start = e820[i].addr;
        end = e820[i].addr + e820[i].size;

        if ( start < last_end )
            printf(" OVERLAP!!\n");
        else if ( start > last_end )
            printf(" HOLE: %08x:%08x - %08x:%08x\n",
                   (uint32_t)(last_end >> 32), (uint32_t)last_end,
                   (uint32_t)(start >> 32), (uint32_t)start);

        printf(" [%02d]: %08x:%08x - %08x:%08x: ", i,
               (uint32_t)(start >> 32), (uint32_t)start,
               (uint32_t)(end >> 32), (uint32_t)end);
        switch ( e820[i].type )
        {
        case E820_RAM:
            printf("RAM\n");
            break;
        case E820_RESERVED:
            printf("RESERVED\n");
            break;
        case E820_ACPI:
            printf("ACPI\n");
            break;
        case E820_NVS:
            printf("NVS\n");
            break;
        default:
            printf("UNKNOWN (%08x)\n", e820[i].type);
            break;
        }

        last_end = end;
    }
}

/* Create an E820 table based on memory parameters provided in hvm_info. */
int build_e820_table(struct e820entry *e820,
                     unsigned int lowmem_reserved_base,
                     unsigned int bios_image_base)
{
    unsigned int nr = 0, i, j;
    uint32_t low_mem_end = hvm_info->low_mem_pgend << PAGE_SHIFT;
    unsigned long acpi_mem_end = acpi_enabled ?
        ACPI_MEMORY_DYNAMIC_START + (acpi_pages_allocated() << PAGE_SHIFT) :
        RESERVED_MEMBASE;

    if ( !lowmem_reserved_base )
            lowmem_reserved_base = 0xA0000;

    /* Lowmem must be at least 512K to keep Windows happy) */
    ASSERT ( lowmem_reserved_base > 512<<10 );

    ASSERT ( bios_image_base < 0x100000 );

    /*
     * 0x0-lowmem_reserved_base: Ordinary RAM.
     */
    e820[nr].addr = 0x00000;
    e820[nr].size = lowmem_reserved_base;
    e820[nr].type = E820_RAM;
    nr++;

    /* lowmem_reserved_base-0xA0000: reserved by BIOS implementation. */
    if ( lowmem_reserved_base < 0xA0000 )
    {
        /* Reserved for internal use. */
        e820[nr].addr = lowmem_reserved_base;
        e820[nr].size = 0xA0000-lowmem_reserved_base;
        e820[nr].type = E820_RESERVED;
        nr++;
    }

    /*
     * Following regions are standard regions of the PC memory map.
     * They are not covered by e820 regions. OSes will not use as RAM.
     * 0xA0000-0xC0000: VGA memory-mapped I/O. Not covered by E820.
     * 0xC0000-0xE0000: 16-bit devices, expansion ROMs (inc. vgabios).
     * TODO: free pages which turn out to be unused.
     */

    /*
     * BIOS region.
     */
    e820[nr].addr = bios_image_base;
    e820[nr].size = 0x100000-bios_image_base;
    e820[nr].type = E820_RESERVED;
    nr++;

    /*
     * Mark populated reserved memory that contains ACPI tables as ACPI NVS.
     * That should help the guest to treat it correctly later: e.g. pass to
     * the next kernel on kexec.
     *
     * Using NVS type instead of a regular one helps to prevent potential
     * space reuse by an ACPI unaware / buggy bootloader, option ROM, etc.
     * before an ACPI OS takes control. This is possible due to the fact that
     * ACPI NVS memory is explicitly described as non-reclaimable in ACPI spec.
     *
     * Furthermore, Xen relies on accessing ACPI tables from within the AML
     * code exposed to guests. So Xen's ACPI tables are not, in general,
     * reclaimable.
     */

    if ( acpi_enabled )
    {
        e820[nr].addr = RESERVED_MEMBASE;
        e820[nr].size = acpi_mem_end - RESERVED_MEMBASE;
        e820[nr].type = E820_NVS;
        nr++;
    }

    /*
     * Explicitly reserve space for special pages.
     * This space starts right after ACPI region (to avoid creating a hole that
     * might be accidentally occupied by MMIO) and extends to cover various
     * fixed hardware mappings (e.g., LAPIC, IOAPIC, default SVGA framebuffer).
     *
     * If igd_opregion_pgbase we need to split the RESERVED region in two.
     */

    if ( igd_opregion_pgbase )
    {
        uint32_t igd_opregion_base = igd_opregion_pgbase << PAGE_SHIFT;

        e820[nr].addr = acpi_mem_end;
        e820[nr].size = igd_opregion_base - acpi_mem_end;
        e820[nr].type = E820_RESERVED;
        nr++;

        e820[nr].addr = igd_opregion_base;
        e820[nr].size = IGD_OPREGION_PAGES * PAGE_SIZE;
        e820[nr].type = E820_NVS;
        nr++;

        e820[nr].addr = igd_opregion_base + IGD_OPREGION_PAGES * PAGE_SIZE;
        e820[nr].size = (uint32_t)-e820[nr].addr;
        e820[nr].type = E820_RESERVED;
        nr++;
    }
    else
    {
        e820[nr].addr = acpi_mem_end;
        e820[nr].size = (uint32_t)-e820[nr].addr;
        e820[nr].type = E820_RESERVED;
        nr++;
    }

    /* Low RAM goes here. Reserve space for special pages. */
    BUG_ON(low_mem_end < MB(2));

    /*
     * Construct E820 table according to recorded memory map.
     *
     * The memory map created by toolstack may include,
     *
     * #1. Low memory region
     *
     * Low RAM starts at least from 1M to make sure all standard regions
     * of the PC memory map, like BIOS, VGA memory-mapped I/O and vgabios,
     * have enough space.
     *
     * #2. Reserved regions if they exist
     *
     * #3. High memory region if it exists
     *
     * Note we just have one low memory entry and one high mmeory entry if
     * exists.
     */
    for ( i = 0; i < memory_map.nr_map; i++ )
    {
        e820[nr] = memory_map.map[i];
        nr++;
    }

    /* Finally we need to sort all e820 entries. */
    for ( j = 0; j < nr - 1; j++ )
    {
        for ( i = j + 1; i < nr; i++ )
        {
            if ( e820[j].addr > e820[i].addr )
            {
                struct e820entry tmp = e820[j];

                e820[j] = e820[i];
                e820[i] = tmp;
            }
        }
    }

    return nr;
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
