// Paravirtualization support.
//
// Copyright (C) 2013  Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2009 Red Hat Inc.
//
// Authors:
//  Gleb Natapov <gnatapov@redhat.com>
//
// This file may be distributed under the terms of the GNU LGPLv3 license.

#include "config.h" // CONFIG_QEMU
#include "util.h" // dprintf
#include "byteorder.h" // be32_to_cpu
#include "ioport.h" // outw
#include "paravirt.h" // qemu_cfg_preinit
#include "smbios.h" // smbios_setup
#include "memmap.h" // add_e820
#include "cmos.h" // CMOS_*
#include "acpi.h" // acpi_setup
#include "mptable.h" // mptable_setup
#include "pci.h" // create_pirtable
#include "xen.h" // xen_biostable_setup

// Amount of continuous ram under 4Gig
u32 RamSize;
// Amount of continuous ram >4Gig
u64 RamSizeOver4G;
// Type of emulator platform.
int PlatformRunningOn VARFSEG;

/* This CPUID returns the signature 'KVMKVMKVM' in ebx, ecx, and edx.  It
 * should be used to determine that a VM is running under KVM.
 */
#define KVM_CPUID_SIGNATURE     0x40000000

static void kvm_preinit(void)
{
    if (!CONFIG_QEMU)
        return;
    unsigned int eax, ebx, ecx, edx;
    char signature[13];

    cpuid(KVM_CPUID_SIGNATURE, &eax, &ebx, &ecx, &edx);
    memcpy(signature + 0, &ebx, 4);
    memcpy(signature + 4, &ecx, 4);
    memcpy(signature + 8, &edx, 4);
    signature[12] = 0;

    if (strcmp(signature, "KVMKVMKVM") == 0) {
        dprintf(1, "Running on KVM\n");
        PlatformRunningOn |= PF_KVM;
    }
}

void
qemu_preinit(void)
{
    if (!CONFIG_QEMU)
        return;

    if (runningOnXen()) {
        xen_ramsize_preinit();
        return;
    }

    PlatformRunningOn = PF_QEMU;
    kvm_preinit();

    // On emulators, get memory size from nvram.
    u32 rs = ((inb_cmos(CMOS_MEM_EXTMEM2_LOW) << 16)
              | (inb_cmos(CMOS_MEM_EXTMEM2_HIGH) << 24));
    if (rs)
        rs += 16 * 1024 * 1024;
    else
        rs = (((inb_cmos(CMOS_MEM_EXTMEM_LOW) << 10)
               | (inb_cmos(CMOS_MEM_EXTMEM_HIGH) << 18))
              + 1 * 1024 * 1024);
    RamSize = rs;
    add_e820(0, rs, E820_RAM);

    // Check for memory over 4Gig
    u64 high = ((inb_cmos(CMOS_MEM_HIGHMEM_LOW) << 16)
                | ((u32)inb_cmos(CMOS_MEM_HIGHMEM_MID) << 24)
                | ((u64)inb_cmos(CMOS_MEM_HIGHMEM_HIGH) << 32));
    RamSizeOver4G = high;
    add_e820(0x100000000ull, high, E820_RAM);

    /* reserve 256KB BIOS area at the end of 4 GB */
    add_e820(0xfffc0000, 256*1024, E820_RESERVED);

    dprintf(1, "Ram Size=0x%08x (0x%016llx high)\n", RamSize, RamSizeOver4G);
}

void
qemu_platform_setup(void)
{
    if (!CONFIG_QEMU)
        return;

    if (runningOnXen()) {
        pci_probe_devices();
        xen_hypercall_setup();
        xen_biostable_setup();
        return;
    }

    // Initialize pci
    pci_setup();
    smm_device_setup();
    smm_setup();

    // Initialize mtrr and smp
    mtrr_setup();
    smp_setup();

    // Create bios tables
    pirtable_setup();
    mptable_setup();
    smbios_setup();
    acpi_setup();
}


/****************************************************************
 * QEMU firmware config (fw_cfg) interface
 ****************************************************************/

// List of QEMU fw_cfg entries.  DO NOT ADD MORE.  (All new content
// should be passed via the fw_cfg "file" interface.)
#define QEMU_CFG_SIGNATURE              0x00
#define QEMU_CFG_ID                     0x01
#define QEMU_CFG_UUID                   0x02
#define QEMU_CFG_NUMA                   0x0d
#define QEMU_CFG_BOOT_MENU              0x0e
#define QEMU_CFG_MAX_CPUS               0x0f
#define QEMU_CFG_FILE_DIR               0x19
#define QEMU_CFG_ARCH_LOCAL             0x8000
#define QEMU_CFG_ACPI_TABLES            (QEMU_CFG_ARCH_LOCAL + 0)
#define QEMU_CFG_SMBIOS_ENTRIES         (QEMU_CFG_ARCH_LOCAL + 1)
#define QEMU_CFG_IRQ0_OVERRIDE          (QEMU_CFG_ARCH_LOCAL + 2)
#define QEMU_CFG_E820_TABLE             (QEMU_CFG_ARCH_LOCAL + 3)

static void
qemu_cfg_select(u16 f)
{
    outw(f, PORT_QEMU_CFG_CTL);
}

static void
qemu_cfg_read(void *buf, int len)
{
    insb(PORT_QEMU_CFG_DATA, buf, len);
}

static void
qemu_cfg_skip(int len)
{
    while (len--)
        inb(PORT_QEMU_CFG_DATA);
}

static void
qemu_cfg_read_entry(void *buf, int e, int len)
{
    qemu_cfg_select(e);
    qemu_cfg_read(buf, len);
}

struct qemu_romfile_s {
    struct romfile_s file;
    int select, skip;
};

static int
qemu_cfg_read_file(struct romfile_s *file, void *dst, u32 maxlen)
{
    if (file->size > maxlen)
        return -1;
    struct qemu_romfile_s *qfile;
    qfile = container_of(file, struct qemu_romfile_s, file);
    qemu_cfg_select(qfile->select);
    qemu_cfg_skip(qfile->skip);
    qemu_cfg_read(dst, file->size);
    return file->size;
}

static void
qemu_romfile_add(char *name, int select, int skip, int size)
{
    struct qemu_romfile_s *qfile = malloc_tmp(sizeof(*qfile));
    if (!qfile) {
        warn_noalloc();
        return;
    }
    memset(qfile, 0, sizeof(*qfile));
    strtcpy(qfile->file.name, name, sizeof(qfile->file.name));
    qfile->file.size = size;
    qfile->select = select;
    qfile->skip = skip;
    qfile->file.copy = qemu_cfg_read_file;
    romfile_add(&qfile->file);
}

struct e820_reservation {
    u64 address;
    u64 length;
    u32 type;
};

#define SMBIOS_FIELD_ENTRY 0
#define SMBIOS_TABLE_ENTRY 1

struct qemu_smbios_header {
    u16 length;
    u8 headertype;
    u8 tabletype;
    u16 fieldoffset;
} PACKED;

// Populate romfile entries for legacy fw_cfg ports (that predate the
// "file" interface).
static void
qemu_cfg_legacy(void)
{
    if (!CONFIG_QEMU)
        return;

    // Misc config items.
    qemu_romfile_add("etc/show-boot-menu", QEMU_CFG_BOOT_MENU, 0, 2);
    qemu_romfile_add("etc/irq0-override", QEMU_CFG_IRQ0_OVERRIDE, 0, 1);
    qemu_romfile_add("etc/max-cpus", QEMU_CFG_MAX_CPUS, 0, 2);

    // NUMA data
    u64 numacount;
    qemu_cfg_read_entry(&numacount, QEMU_CFG_NUMA, sizeof(numacount));
    int max_cpu = romfile_loadint("etc/max-cpus", 0);
    qemu_romfile_add("etc/numa-cpu-map", QEMU_CFG_NUMA, sizeof(numacount)
                     , max_cpu*sizeof(u64));
    qemu_romfile_add("etc/numa-nodes", QEMU_CFG_NUMA
                     , sizeof(numacount) + max_cpu*sizeof(u64)
                     , numacount*sizeof(u64));

    // e820 data
    u32 count32;
    qemu_cfg_read_entry(&count32, QEMU_CFG_E820_TABLE, sizeof(count32));
    if (count32) {
        struct e820_reservation entry;
        int i;
        for (i = 0; i < count32; i++) {
            qemu_cfg_read(&entry, sizeof(entry));
            add_e820(entry.address, entry.length, entry.type);
        }
    } else if (runningOnKVM()) {
        // Backwards compatibility - provide hard coded range.
        // 4 pages before the bios, 3 pages for vmx tss pages, the
        // other page for EPT real mode pagetable
        add_e820(0xfffbc000, 4*4096, E820_RESERVED);
    }

    // ACPI tables
    char name[128];
    u16 cnt;
    qemu_cfg_read_entry(&cnt, QEMU_CFG_ACPI_TABLES, sizeof(cnt));
    int i, offset = sizeof(cnt);
    for (i = 0; i < cnt; i++) {
        u16 len;
        qemu_cfg_read(&len, sizeof(len));
        offset += sizeof(len);
        snprintf(name, sizeof(name), "acpi/table%d", i);
        qemu_romfile_add(name, QEMU_CFG_ACPI_TABLES, offset, len);
        qemu_cfg_skip(len);
        offset += len;
    }

    // SMBIOS info
    qemu_cfg_read_entry(&cnt, QEMU_CFG_SMBIOS_ENTRIES, sizeof(cnt));
    offset = sizeof(cnt);
    for (i = 0; i < cnt; i++) {
        struct qemu_smbios_header header;
        qemu_cfg_read(&header, sizeof(header));
        if (header.headertype == SMBIOS_FIELD_ENTRY) {
            snprintf(name, sizeof(name), "smbios/field%d-%d"
                     , header.tabletype, header.fieldoffset);
            qemu_romfile_add(name, QEMU_CFG_SMBIOS_ENTRIES
                             , offset + sizeof(header)
                             , header.length - sizeof(header));
        } else {
            snprintf(name, sizeof(name), "smbios/table%d-%d"
                     , header.tabletype, i);
            qemu_romfile_add(name, QEMU_CFG_SMBIOS_ENTRIES
                             , offset + 3, header.length - 3);
        }
        qemu_cfg_skip(header.length - sizeof(header));
        offset += header.length;
    }
}

struct QemuCfgFile {
    u32  size;        /* file size */
    u16  select;      /* write this to 0x510 to read it */
    u16  reserved;
    char name[56];
};

void qemu_cfg_init(void)
{
    if (!runningOnQEMU())
        return;

    // Detect fw_cfg interface.
    qemu_cfg_select(QEMU_CFG_SIGNATURE);
    char *sig = "QEMU";
    int i;
    for (i = 0; i < 4; i++)
        if (inb(PORT_QEMU_CFG_DATA) != sig[i])
            return;
    dprintf(1, "Found QEMU fw_cfg\n");

    // Populate romfiles for legacy fw_cfg entries
    qemu_cfg_legacy();

    // Load files found in the fw_cfg file directory
    u32 count;
    qemu_cfg_read_entry(&count, QEMU_CFG_FILE_DIR, sizeof(count));
    count = be32_to_cpu(count);
    u32 e;
    for (e = 0; e < count; e++) {
        struct QemuCfgFile qfile;
        qemu_cfg_read(&qfile, sizeof(qfile));
        qemu_romfile_add(qfile.name, be16_to_cpu(qfile.select)
                         , 0, be32_to_cpu(qfile.size));
    }
}
