From 27ab716422f9fe7a68d6adae0c29880900e2a7f2 Mon Sep 17 00:00:00 2001 From: Alexander Kudryavtsev Date: Fri, 16 Mar 2012 16:55:41 +0400 Subject: [PATCH] SEABIOS, E820, Large memory, NUMA support. List of features implemented: 1) Support for changing BIOS type between BOCHS and SEABIOS. - Version of SEABIOS is 1.6.3.1 with modifications. - bios/seabios folder contains all necessary files. 2) E820 memory map support for both BOCHS and SEABIOS. 3) Large memory support with memory chunks: 3072 4096 - this will create VM with 7GB of RAM. 1024 is still supported. 4) NUMA support (works only with SEABIOS). Example: - set node count 1024 - set node for this chunk - set node for core 5) Many changes required for SEABIOS to work correctly, including new fields in NVRAM, Firmware configuration interface (FW_CFG), etc. --- Kconfig | 38 +++- bios/rombios/BIOS-bochs-latest | Bin 65536 -> 65536 bytes bios/rombios/rombios.c | 2 +- bios/seabios/COPYING.LESSER | 165 ++++++++++++ bios/seabios/README | 1 + bios/seabios/bios.bin | Bin 0 -> 131072 bytes bios/seabios/palacios_config | 72 ++++++ bios/seabios/palacios_fixes.patch | 148 +++++++++++ palacios/include/palacios/vm_guest.h | 20 ++ palacios/include/palacios/vmm_fw_cfg.h | 31 +++ palacios/include/palacios/vmm_list.h | 33 +++ palacios/include/palacios/vmm_mem.h | 33 +++- palacios/src/devices/Kconfig | 2 +- palacios/src/devices/nvram.c | 44 ++- palacios/src/palacios/Makefile | 2 +- palacios/src/palacios/vm_guest.c | 2 + palacios/src/palacios/vmm_binaries.S | 4 + palacios/src/palacios/vmm_config.c | 281 ++++++++++++++++----- palacios/src/palacios/vmm_config_class.h | 43 +++- palacios/src/palacios/vmm_fw_cfg.c | 344 +++++++++++++++++++++++++ palacios/src/palacios/vmm_mem.c | 412 ++++++++++++++++++++++++++++-- 21 files changed, 1563 insertions(+), 114 deletions(-) create mode 100644 bios/seabios/COPYING.LESSER create mode 100644 bios/seabios/README create mode 100644 bios/seabios/bios.bin create mode 100644 bios/seabios/palacios_config create mode 100644 bios/seabios/palacios_fixes.patch create mode 100644 palacios/include/palacios/vmm_fw_cfg.h create mode 100644 palacios/src/palacios/vmm_fw_cfg.c diff --git a/Kconfig b/Kconfig index 0fb4b9a..2fcfa76 100644 --- a/Kconfig +++ b/Kconfig @@ -427,17 +427,45 @@ endmenu menu "BIOS Selection" +choice + prompt "Guest BIOS type" + default BOCHSBIOS + +config BOCHSBIOS + bool "BOCHS BIOS" + help + This uses BOCHS BIOS as guest BIOS. In this case, you + should consider using MPTABLE virtual device etc. + +config SEABIOS + bool "SEABIOS" + help + This uses SEABIOS as a guest BIOS. This BIOS builds all + necessary tables (E820, MPTABLE, ACPI and other), so + MPTABLE device is disabled. +endchoice + +if BOCHSBIOS config ROMBIOS_PATH string "Path to pre-built ROMBIOS binary" - default "./bios/rombios/BIOS-bochs-latest" + default "./bios/rombios/BIOS-bochs-latest" help This is the rombios that will be used for the guests +endif -config VGABIOS_PATH - string "Path to pre-built VGABIOS binary" - default "./bios/vgabios/VGABIOS-lgpl-latest.bin" +if SEABIOS +config SEABIOS_PATH + string "Path to pre-built SEABIOS ROMBIOS binary" + default "./bios/seabios/bios.bin" help - This is the vgabios that will be used for the guests + This is the rombios that will be used for the guests +endif + +config VGABIOS_PATH + string "Path to pre-built VGABIOS binary" + default "./bios/vgabios/VGABIOS-lgpl-latest.bin" + help + This is the vgabios that will be used for the guests config VMXASSIST_PATH string "Path to pre-built VMXASSIST binary" diff --git a/bios/rombios/BIOS-bochs-latest b/bios/rombios/BIOS-bochs-latest index ea9a1ec75aff858c15017bc387fcaf297076676d..efcb7cafce0dfad41c320d513baa081035347f60 100644 GIT binary patch delta 11898 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 RP7uo=ARZtYG(ORo`1aE@kkpw~OdbZxy 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 diff --git a/bios/rombios/rombios.c b/bios/rombios/rombios.c index 1e1af50..8d716a5 100644 --- a/bios/rombios/rombios.c +++ b/bios/rombios/rombios.c @@ -4307,7 +4307,7 @@ ASM_END case 0x20: // coded by osmaker aka K.J. if(regs.u.r32.edx == 0x534D4150) /* SMAP */ { -#if defined(HVMASSIST) && 0 +#if defined(HVMASSIST) if ((regs.u.r16.bx / 0x14) * 0x14 == regs.u.r16.bx) { Bit16u e820_table_size = read_word(0xe000, 0x8) * 0x14; diff --git a/bios/seabios/COPYING.LESSER b/bios/seabios/COPYING.LESSER new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/bios/seabios/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/bios/seabios/README b/bios/seabios/README new file mode 100644 index 0000000..d313f8a --- /dev/null +++ b/bios/seabios/README @@ -0,0 +1 @@ +Patch should be applied to SEABIOS version 1.6.3.1 diff --git a/bios/seabios/bios.bin b/bios/seabios/bios.bin new file mode 100644 index 0000000000000000000000000000000000000000..751872837646441d1fac902480cd99fe4508f4cb GIT binary patch literal 131072 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 +Date: Mon, 12 Mar 2012 13:07:07 +0400 +Subject: [PATCH] Fixes needed to launch SEABIOS inside Palacios's VM: 1. + Allow to build ACPI tables without PIIX4 PM device 2. + Disable HPET table generation 3. Do not perform PCI setup + since it breaks Palacios 4. Do not initialize AP cores + since it breaks Palacios 5. Fix SRAT generation - do not + cut PCI hole since it is already cut off by Palacios + +--- + src/acpi.c | 44 +++++++++++++++++++++++++++++--------------- + src/pciinit.c | 2 +- + src/smp.c | 5 ++++- + 3 files changed, 34 insertions(+), 17 deletions(-) + +diff --git a/src/acpi.c b/src/acpi.c +index 3d8b7c8..7d50990 100644 +--- a/src/acpi.c ++++ b/src/acpi.c +@@ -262,18 +262,32 @@ build_fadt(struct pci_device *pci) + fadt->dsdt = cpu_to_le32((u32)dsdt); + fadt->model = 1; + fadt->reserved1 = 0; +- int pm_sci_int = pci_config_readb(pci->bdf, PCI_INTERRUPT_LINE); ++ int pm_sci_int; ++ if (pci) ++ pci_config_readb(pci->bdf, PCI_INTERRUPT_LINE); ++ else ++ pm_sci_int = 0; + fadt->sci_int = cpu_to_le16(pm_sci_int); +- fadt->smi_cmd = cpu_to_le32(PORT_SMI_CMD); +- fadt->pm1a_evt_blk = cpu_to_le32(PORT_ACPI_PM_BASE); +- fadt->pm1a_cnt_blk = cpu_to_le32(PORT_ACPI_PM_BASE + 0x04); +- fadt->pm_tmr_blk = cpu_to_le32(PORT_ACPI_PM_BASE + 0x08); +- fadt->pm1_evt_len = 4; +- fadt->pm1_cnt_len = 2; +- fadt->pm_tmr_len = 4; ++ if (pci) { ++ fadt->smi_cmd = cpu_to_le32(PORT_SMI_CMD); ++ fadt->pm1a_evt_blk = cpu_to_le32(PORT_ACPI_PM_BASE); ++ fadt->pm1a_cnt_blk = cpu_to_le32(PORT_ACPI_PM_BASE + 0x04); ++ fadt->pm_tmr_blk = cpu_to_le32(PORT_ACPI_PM_BASE + 0x08); ++ fadt->pm1_evt_len = 4; ++ fadt->pm1_cnt_len = 2; ++ fadt->pm_tmr_len = 4; ++ pci_init_device(fadt_init_tbl, pci, fadt); ++ } else { ++ fadt->smi_cmd = 0; ++ fadt->pm1a_evt_blk = 0; ++ fadt->pm1a_cnt_blk = 0; ++ fadt->pm_tmr_blk = 0; ++ fadt->pm1_evt_len = 0; ++ fadt->pm1_cnt_len = 0; ++ fadt->pm_tmr_len = 0; ++ } + fadt->plvl2_lat = cpu_to_le16(0xfff); // C2 state not supported + fadt->plvl3_lat = cpu_to_le16(0xfff); // C3 state not supported +- pci_init_device(fadt_init_tbl, pci, fadt); + /* WBINVD + PROC_C1 + SLP_BUTTON + FIX_RTC + RTC_S4 */ + fadt->flags = cpu_to_le32((1 << 0) | (1 << 2) | (1 << 5) | (1 << 6) | (1 << 7)); + +@@ -516,6 +530,7 @@ build_srat(void) + if (nb_numa_nodes == 0) + return NULL; + ++ + u64 *numadata = malloc_tmphigh(sizeof(u64) * (MaxCountCPUs + nb_numa_nodes)); + if (!numadata) { + warn_noalloc(); +@@ -577,7 +592,7 @@ build_srat(void) + next_base = mem_base + mem_len; + + /* Cut out the PCI hole */ +- if (mem_base <= RamSize && next_base > RamSize) { ++ /*if (mem_base <= RamSize && next_base > RamSize) { + mem_len -= next_base - RamSize; + if (mem_len > 0) { + acpi_build_srat_memory(numamem, mem_base, mem_len, i-1, 1); +@@ -587,7 +602,7 @@ build_srat(void) + mem_base = 1ULL << 32; + mem_len = next_base - RamSize; + next_base += (1ULL << 32) - RamSize; +- } ++ }*/ + acpi_build_srat_memory(numamem, mem_base, mem_len, i-1, 1); + numamem++; + slots++; +@@ -623,9 +638,7 @@ acpi_bios_init(void) + + // This code is hardcoded for PIIX4 Power Management device. + struct pci_device *pci = pci_find_init_device(acpi_find_tbl, NULL); +- if (!pci) +- // Device not found +- return; ++ // If no PIIX4 is found we will not build certain structures and init it. + + // Build ACPI tables + u32 tables[MAX_ACPI_TABLES], tbl_idx = 0; +@@ -640,7 +653,8 @@ acpi_bios_init(void) + ACPI_INIT_TABLE(build_fadt(pci)); + ACPI_INIT_TABLE(build_ssdt()); + ACPI_INIT_TABLE(build_madt()); +- ACPI_INIT_TABLE(build_hpet()); ++ if(pci) ++ ACPI_INIT_TABLE(build_hpet()); + ACPI_INIT_TABLE(build_srat()); + + u16 i, external_tables = qemu_cfg_acpi_additional_tables(); +diff --git a/src/pciinit.c b/src/pciinit.c +index 0d8758e..ef8ccda 100644 +--- a/src/pciinit.c ++++ b/src/pciinit.c +@@ -575,7 +575,7 @@ static int pci_bios_init_root_regions(u32 start, u32 end) + void + pci_setup(void) + { +- if (CONFIG_COREBOOT || usingXen()) { ++ if (1 || CONFIG_COREBOOT || usingXen()) { + // PCI setup already done by coreboot or Xen - just do probe. + pci_probe_devices(); + return; +diff --git a/src/smp.c b/src/smp.c +index 8c077a1..21e7437 100644 +--- a/src/smp.c ++++ b/src/smp.c +@@ -81,7 +81,7 @@ smp_probe(void) + MaxCountCPUs = 1; + return; + } +- ++#if 0 + // Init the counter. + writel(&CountCPUs, 1); + +@@ -121,6 +121,9 @@ smp_probe(void) + + // Restore memory. + *(u64*)BUILD_AP_BOOT_ADDR = old; ++#endif ++ ++ CountCPUs = inb_cmos(CMOS_BIOS_SMP_COUNT) + 1; // Fix for Palacios + + MaxCountCPUs = qemu_cfg_get_max_cpus(); + if (!MaxCountCPUs || MaxCountCPUs < CountCPUs) +-- +1.7.5.4 + diff --git a/palacios/include/palacios/vm_guest.h b/palacios/include/palacios/vm_guest.h index a63d19e..158a774 100644 --- a/palacios/include/palacios/vm_guest.h +++ b/palacios/include/palacios/vm_guest.h @@ -149,7 +149,21 @@ struct v3_vm_info { v3_vm_class_t vm_class; addr_t mem_size; /* In bytes for now */ + addr_t mem_size_lo, mem_size_hi; /* Contiguous space up to 4GB and after 4GB in bytes. */ uint32_t mem_align; + + v3_paging_mode_t shdw_pg_mode; + union { + uint32_t flags; + struct { + uint8_t use_large_pages : 1; /* Enable virtual page tables to use large pages */ + uint8_t use_giant_pages : 1; /* Enable virtual page tables to use giant (1GB) pages */ + uint32_t rsvd : 30; + } __attribute__((packed)); + } __attribute__((packed)); + + struct list_head mem_chunks; + int mem_chunks_cnt; struct v3_mem_map mem_map; struct v3_mem_hooks mem_hooks; @@ -198,6 +212,12 @@ struct v3_vm_info { void * host_priv_data; int num_cores; + int num_nodes; + int num_host_nodes; +#define MAX_NODE_COUNT 16 + uint64_t node_cpumask[MAX_NODE_COUNT]; // XXX: this limits max CPU count to 64 + int node_config_to_final[MAX_NODE_COUNT]; + void *fw_cfg_state; // JRL: This MUST be the last entry... struct guest_info cores[0]; diff --git a/palacios/include/palacios/vmm_fw_cfg.h b/palacios/include/palacios/vmm_fw_cfg.h new file mode 100644 index 0000000..196526c --- /dev/null +++ b/palacios/include/palacios/vmm_fw_cfg.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Palacios Virtual Machine Monitor developed + * by the V3VEE Project with funding from the United States National + * Science Foundation and the Department of Energy. + * + * The V3VEE Project is a joint project between Northwestern University + * and the University of New Mexico. You can find out more at + * http://www.v3vee.org + * + * Copyright (c) 2008, The V3VEE Project + * All rights reserved. + * + * Author: Alexander Kudryavtsev + * + * This is free software. You are permitted to use, + * redistribute, and modify it as specified in the file "V3VEE_LICENSE". + */ + +#ifndef __VMM_FW_CFG_H__ +#define __VMM_FW_CFG_H__ + +#ifdef __V3VEE__ + +struct v3_vm_info; + +int v3_fw_cfg_init(struct v3_vm_info *vm); +void v3_delete_fw_cfg(struct v3_vm_info *vm); + +#endif + +#endif diff --git a/palacios/include/palacios/vmm_list.h b/palacios/include/palacios/vmm_list.h index 22f447c..efefc68 100644 --- a/palacios/include/palacios/vmm_list.h +++ b/palacios/include/palacios/vmm_list.h @@ -446,6 +446,39 @@ static inline void list_splice_init(struct list_head *list, &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) +static inline void list_sort_bubble(void *priv, struct list_head *head, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b)) { + struct list_head *pos, *posn, *n, new_head; + + if(list_empty(head)) return; + INIT_LIST_HEAD(&new_head); + list_for_each_safe(pos, n, head) { + list_del(pos); + + if(list_empty(&new_head)) { + list_add(pos, &new_head); + } else { + int added = 0; + list_for_each(posn, &new_head) { + if(cmp(priv, pos, posn) < 0) { + list_add_tail(pos, posn); + added = 1; + break; + } + } + if(!added) { + list_add_tail(pos, &new_head); + } + } + } + // at least one element is expected + head->next = new_head.next; + head->prev = new_head.prev; + head->next->prev = head; + head->prev->next = head; +} + /* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is diff --git a/palacios/include/palacios/vmm_mem.h b/palacios/include/palacios/vmm_mem.h index 6974344..b8bab15 100644 --- a/palacios/include/palacios/vmm_mem.h +++ b/palacios/include/palacios/vmm_mem.h @@ -76,20 +76,51 @@ struct v3_mem_region { struct rb_node tree_node; // This for memory regions mapped to the global map }; +enum v3_e820_types { + E820_TYPE_FREE = 1, + E820_TYPE_RESV = 2, + E820_TYPE_ACPI_RECL = 3, + E820_TYPE_ACPI_NVS = 4, + E820_TYPE_BAD = 5 +}; + +/* This is an "extended" E820 entry */ +struct v3_e820_entry { + uint64_t addr; + uint64_t size; + uint32_t type; + int node; + int chunk_idx; + + struct list_head list; +}; struct v3_mem_map { struct v3_mem_region base_region; + struct list_head e820_list; /* List of e820_entries */ + int e820_count; struct rb_root mem_regions; }; +struct v3_mem_chunk { + size_t size; /* Size in bytes, requested in configuration */ + addr_t host_pa; /* Allocated host PA */ + int node; /* VM node number */ + int target_node; /* Target (host) node number */ + int idx; /* Index in configuration */ + + struct list_head list; +}; + + int v3_init_mem_map(struct v3_vm_info * vm); void v3_delete_mem_map(struct v3_vm_info * vm); - +int v3_finalize_e820(struct v3_vm_info *vm); struct v3_mem_region * v3_create_mem_region(struct v3_vm_info * vm, uint16_t core_id, addr_t guest_addr_start, addr_t guest_addr_end); diff --git a/palacios/src/devices/Kconfig b/palacios/src/devices/Kconfig index e9b2dc8..c52ac21 100644 --- a/palacios/src/devices/Kconfig +++ b/palacios/src/devices/Kconfig @@ -39,7 +39,7 @@ config DEBUG_IO_APIC config MPTABLE bool "MPTABLE" default y - depends on APIC + depends on APIC && BOCHSBIOS help Includes the MPTABLE to map the APICs and IO-APIC diff --git a/palacios/src/devices/nvram.c b/palacios/src/devices/nvram.c index d1f27a2..a478ee7 100644 --- a/palacios/src/devices/nvram.c +++ b/palacios/src/devices/nvram.c @@ -98,6 +98,12 @@ typedef enum {NVRAM_READY, NVRAM_REG_POSTED} nvram_state_t; #define CHECKSUM_REGION_FIRST_BYTE 0x10 #define CHECKSUM_REGION_LAST_BYTE 0x2d +// Following fields are used by SEABIOS +#define NVRAM_REG_HIGHMEM_LOW 0x5b +#define NVRAM_REG_HIGHMEM_MID 0x5c +#define NVRAM_REG_HIGHMEM_HIGH 0x5d +#define NVRAM_REG_SMPCPUS 0x5f + struct nvram_internal { nvram_state_t dev_state; @@ -480,18 +486,22 @@ static void nvram_update_timer(struct guest_info *vm, } -static void set_memory_size(struct nvram_internal * nvram, addr_t bytes) { +void set_memory_size(struct nvram_internal * nvram, + addr_t bytes_lo, addr_t bytes_hi) { // 1. Conventional Mem: 0-640k in K // 2. Extended Mem: 0-16MB in K // 3. Big Mem: 0-4G in 64K + // 4. High mem: 4G-... in 64K uint16_t memk; uint16_t mem_chunks; + PrintDebug("setting bytes_lo %lx hi %lx\n", bytes_lo, bytes_hi); + // at most 640K of conventional memory - if (bytes > 640 * 1024) { - memk=640; + if (bytes_lo > 640 * 1024) { + memk = 640; } else { - memk = bytes/1024; + memk = bytes_lo / 1024; } set_memory(nvram, NVRAM_REG_BASE_MEMORY_HIGH, (memk >> 8) & 0x00ff); @@ -499,11 +509,11 @@ static void set_memory_size(struct nvram_internal * nvram, addr_t bytes) { // set extended memory - first 1 MB is lost to 640K chunk // extended memory is min(0MB, bytes-1MB) - if (bytes < 1024*1024) { - // no extended memory - memk = 0; + if (bytes_lo < 1024 * 1024) { + // no extended memory + memk = 0; } else { - memk = (bytes - 1024 * 1024 ) / 1024; + memk = (bytes_lo - 1024 * 1024 ) / 1024; } set_memory(nvram, NVRAM_REG_EXT_MEMORY_HIGH, (memk >> 8) & 0x00ff); @@ -513,15 +523,20 @@ static void set_memory_size(struct nvram_internal * nvram, addr_t bytes) { // Set the extended memory beyond 16 MB in 64k chunks // this is min(0, bytes-16MB) - if (bytes<(1024*1024*16)) { - mem_chunks=0; + if (bytes_lo < (1024 * 1024 * 16)) { + mem_chunks = 0; } else { - mem_chunks = (bytes - (1024 * 1024 * 16)) / (1024 * 64); + mem_chunks = (bytes_lo - (1024 * 1024 * 16)) / (1024 * 64); } set_memory(nvram, NVRAM_REG_AMI_BIG_MEMORY_HIGH, (mem_chunks >> 8) & 0x00ff); set_memory(nvram, NVRAM_REG_AMI_BIG_MEMORY_LOW, mem_chunks & 0x00ff); + // Set high (>4GB) memory size. + uint32_t mem_chunks_hi = bytes_hi / (1024 * 64); + set_memory(nvram, NVRAM_REG_HIGHMEM_LOW, mem_chunks_hi & 0xff); + set_memory(nvram, NVRAM_REG_HIGHMEM_MID, (mem_chunks_hi >> 8) & 0xff); + set_memory(nvram, NVRAM_REG_HIGHMEM_HIGH, (mem_chunks_hi >> 16) & 0xff); return; } @@ -690,7 +705,7 @@ static int init_nvram_state(struct v3_vm_info * vm, struct nvram_internal * nvra nvram->us = 0; nvram->pus = 0; - set_memory_size(nvram, vm->mem_size); + set_memory_size(nvram, vm->mem_size_lo, vm->mem_size_hi); init_harddrives(nvram); /* compute checksum (must follow all assignments here) */ @@ -698,7 +713,7 @@ static int init_nvram_state(struct v3_vm_info * vm, struct nvram_internal * nvra set_memory(nvram, NVRAM_REG_CSUM_HIGH, (checksum >> 8) & 0xff); set_memory(nvram, NVRAM_REG_CSUM_LOW, checksum & 0xff); - + set_memory(nvram, NVRAM_REG_SMPCPUS, vm->num_cores - 1); nvram->dev_state = NVRAM_READY; nvram->thereg = 0; @@ -761,7 +776,7 @@ static int nvram_write_data_port(struct guest_info * core, uint16_t port, v3_unlock_irqrestore(data->nvram_lock, irq_state); - PrintDebug("nvram: nvram_write_data_port(0x%x) = 0x%x\n", + PrintDebug("nvram: nvram_write_data_port(0x%x) = 0x%x\n", data->thereg, data->mem_state[data->thereg]); return 1; @@ -830,7 +845,6 @@ static int nvram_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { // hook ports ret |= v3_dev_hook_io(dev, NVRAM_REG_PORT, NULL, &nvram_write_reg_port); ret |= v3_dev_hook_io(dev, NVRAM_DATA_PORT, &nvram_read_data_port, &nvram_write_data_port); - if (ret != 0) { PrintError("nvram: Error hooking NVRAM IO ports\n"); v3_remove_device(dev); diff --git a/palacios/src/palacios/Makefile b/palacios/src/palacios/Makefile index cdbf81a..e17e10d 100644 --- a/palacios/src/palacios/Makefile +++ b/palacios/src/palacios/Makefile @@ -37,6 +37,7 @@ obj-y := \ vmm_bitmap.o \ vmm_barrier.o \ vmm_timeout.o \ + vmm_fw_cfg.o \ obj-$(V3_CONFIG_XED) += vmm_xed.o @@ -75,5 +76,4 @@ obj-$(V3_CONFIG_SYMBIOTIC) += vmm_symbiotic.o vmm_symspy.o obj-$(V3_CONFIG_SYMCALL) += vmm_symcall.o obj-$(V3_CONFIG_SYMMOD) += vmm_symmod.o - obj-y += mmu/ diff --git a/palacios/src/palacios/vm_guest.c b/palacios/src/palacios/vm_guest.c index df30d1d..a79b985 100644 --- a/palacios/src/palacios/vm_guest.c +++ b/palacios/src/palacios/vm_guest.c @@ -31,6 +31,7 @@ #include #include #include +#include v3_cpu_mode_t v3_get_vm_cpu_mode(struct guest_info * info) { @@ -641,6 +642,7 @@ int v3_free_vm_internal(struct v3_vm_info * vm) { v3_deinit_mem_hooks(vm); v3_delete_mem_map(vm); + v3_delete_fw_cfg(vm); v3_deinit_shdw_impl(vm); v3_deinit_intr_routers(vm); diff --git a/palacios/src/palacios/vmm_binaries.S b/palacios/src/palacios/vmm_binaries.S index b834952..d89308e 100644 --- a/palacios/src/palacios/vmm_binaries.S +++ b/palacios/src/palacios/vmm_binaries.S @@ -38,7 +38,11 @@ v3_vgabios_end: .globl v3_rombios_start v3_rombios_start: +#ifdef V3_CONFIG_BOCHSBIOS .incbin V3_CONFIG_ROMBIOS_PATH +#elif V3_CONFIG_SEABIOS +.incbin V3_CONFIG_SEABIOS_PATH +#endif .globl v3_rombios_end v3_rombios_end: diff --git a/palacios/src/palacios/vmm_config.c b/palacios/src/palacios/vmm_config.c index de882d0..a1de330 100644 --- a/palacios/src/palacios/vmm_config.c +++ b/palacios/src/palacios/vmm_config.c @@ -211,39 +211,186 @@ static inline uint32_t get_alignment(char * align_str) { return alignment; } +static struct v3_mem_chunk *new_mem_chunk(int chunk_idx, int node, + int target_node, size_t size) { + struct v3_mem_chunk *chunk = V3_Malloc(sizeof(*chunk)); + if(!chunk) { + PrintError("Out of memory!\n"); + return NULL; + } + memset(chunk, 0x0, sizeof(*chunk)); + chunk->idx = chunk_idx; + chunk->node = node; + chunk->target_node = target_node; + chunk->size = size; + chunk->host_pa = (addr_t)-1; + return chunk; +} -static int pre_config_vm(struct v3_vm_info * vm, v3_cfg_tree_t * vm_cfg) { - char * memory_str = v3_cfg_val(vm_cfg, "memory"); - char * schedule_hz_str = v3_cfg_val(vm_cfg, "schedule_hz"); - char * vm_class = v3_cfg_val(vm_cfg, "class"); - char * align_str = v3_cfg_val(v3_cfg_subtree(vm_cfg, "memory"), "alignment"); - uint32_t sched_hz = 100; // set the schedule frequency to 100 HZ +static int mem_chunks_free(struct v3_vm_info *vm) { + struct v3_mem_chunk *c, *cn; + list_for_each_entry_safe(c, cn, &vm->mem_chunks, list) { + list_del(&c->list); + V3_Free(c); + } + return -1; +} +static int parse_memory_config(struct v3_vm_info * vm, v3_cfg_tree_t * vm_cfg) { + int chunk_idx = 0, node, target_node; + size_t size, total_mem_bytes = 0; + struct v3_mem_chunk *chunk; + v3_cfg_tree_t * mem_cfg = v3_cfg_subtree(vm_cfg, "memory"); + if(!mem_cfg) { + PrintError("Memory is a required configuration parameter\n"); + return -1; + } + char * align_str = v3_cfg_val(mem_cfg, "alignment"); + vm->mem_align = get_alignment(align_str); - if (!memory_str) { - PrintError("Memory is a required configuration parameter\n"); - return -1; + INIT_LIST_HEAD(&vm->mem_chunks); + v3_cfg_tree_t *cfg = v3_cfg_subtree(mem_cfg, "chunk"); + // Use the following strategy: we allow different things to be specified + // in node and target_node, but we may fail later when allocating memory + // for a lot of reasons. + while(cfg) { + if(!strcmp(cfg->name, "chunk")) { + + size = (size_t)atoi(v3_xml_txt(cfg)) * 1024 * 1024; + if(size <= 0) { + PrintError("Incorrect memory size for chunk %d!\n", chunk_idx); + return mem_chunks_free(vm); + } + if(size % vm->mem_align != 0) { + PrintError("Requested size for chunk %d is inconsistent with alignment 0x%x!\n", + chunk_idx, vm->mem_align); + return mem_chunks_free(vm); + } + + node = 0; // by default, memory goes to node 0. + if(v3_cfg_val(cfg, "node")) { + node = atoi(v3_cfg_val(cfg, "node")); + if(node < 0 || node >= vm->num_nodes) { + PrintError("Incorrect node for chunk %d!\n", chunk_idx); + return mem_chunks_free(vm); + } + } + target_node = -1; // by default, do not allocate memory directly from nodes. + if(v3_cfg_val(cfg, "target_node")) { + target_node = atoi(v3_cfg_val(cfg, "target_node")); + if(target_node < 0 || target_node >= vm->num_host_nodes) { + PrintError("Incorrect target_node for chunk %d!\n", chunk_idx); + return mem_chunks_free(vm); + } + } + if(!(chunk = new_mem_chunk(chunk_idx, node, target_node, size))) + return mem_chunks_free(vm); + list_add_tail(&chunk->list, &vm->mem_chunks); + + total_mem_bytes += chunk->size; + ++chunk_idx; + } + + cfg = v3_cfg_next_branch(cfg); + } + if(!chunk_idx) { /* For XXXX case. */ + size = (size_t)atoi(v3_xml_txt(mem_cfg)) * 1024 * 1024; + if(size <= 0) { + PrintError("Incorrect memory size for VM!\n"); + return mem_chunks_free(vm); + } + if(!(chunk = new_mem_chunk(chunk_idx, 0, -1, size))) + return mem_chunks_free(vm); + list_add(&chunk->list, &vm->mem_chunks); + + total_mem_bytes += size; + ++chunk_idx; + } + + PrintDebug("Total memory requested for guest: %lu bytes, chunks count: %d, chunks:\n", + total_mem_bytes, chunk_idx); + list_for_each_entry(chunk, &vm->mem_chunks, list) { + PrintDebug("\t%luMB node %d target_node %d\n", chunk->size / (1024 * 1024), + chunk->node, chunk->target_node); } - - PrintDebug("Memory=%s\n", memory_str); + if (align_str) { - PrintDebug("Alignment=%s\n", align_str); + PrintDebug("Alignment=%s\n", align_str); } else { - PrintDebug("Alignment defaulted to 4KB.\n"); + PrintDebug("Alignment defaulted to 4KB.\n"); } - // Amount of ram the Guest will have, always in MB - vm->mem_size = (addr_t)atoi(memory_str) * 1024 * 1024; - vm->mem_align = get_alignment(align_str); + // Amount of RAM the Guest will have + vm->mem_size = total_mem_bytes; + vm->mem_chunks_cnt = chunk_idx; + + PrintDebug("Alignment for %lu bytes of memory computed as 0x%x\n", total_mem_bytes, vm->mem_align); + + return 0; +} + +static int determine_paging_mode(struct v3_vm_info * info, v3_cfg_tree_t * vm_tree) { + extern v3_cpu_arch_t v3_mach_type; + + v3_cfg_tree_t * pg_tree = v3_cfg_subtree(vm_tree, "paging"); + char * pg_mode = v3_cfg_val(pg_tree, "mode"); + + PrintDebug("Paging mode specified as %s\n", pg_mode); + + if (pg_mode) { + if ((strcasecmp(pg_mode, "nested") == 0)) { + // we assume symmetric cores, so if core 0 has nested paging they all do + if ((v3_mach_type == V3_SVM_REV3_CPU) || + (v3_mach_type == V3_VMX_EPT_CPU) || + (v3_mach_type == V3_VMX_EPT_UG_CPU)) { + info->shdw_pg_mode = NESTED_PAGING; + } else { + PrintError("Nested paging not supported on this hardware. Defaulting to shadow paging\n"); + info->shdw_pg_mode = SHADOW_PAGING; + } + } else if ((strcasecmp(pg_mode, "shadow") == 0)) { + info->shdw_pg_mode = SHADOW_PAGING; + } else { + PrintError("Invalid paging mode (%s) specified in configuration. Defaulting to shadow paging\n", pg_mode); + info->shdw_pg_mode = SHADOW_PAGING; + } + } else { + PrintDebug("No paging type specified in configuration. Defaulting to shadow paging\n"); + info->shdw_pg_mode = SHADOW_PAGING; + } + + + if (v3_cfg_val(pg_tree, "large_pages") != NULL) { + if (strcasecmp(v3_cfg_val(pg_tree, "large_pages"), "true") == 0) { + info->use_large_pages = 1; + PrintDebug("Use of large pages in memory virtualization enabled.\n"); + } + } + return 0; +} + +static int pre_config_vm(struct v3_vm_info * vm, v3_cfg_tree_t * vm_cfg) { + char * schedule_hz_str = v3_cfg_val(vm_cfg, "schedule_hz"); + char * vm_class = v3_cfg_val(vm_cfg, "class"); + uint32_t sched_hz = 100; // set the schedule frequency to 100 HZ + + if(determine_paging_mode(vm, vm_cfg) != 0) { + PrintError("Failed to determine paging mode!\n"); + return -1; + } + + if(parse_memory_config(vm, vm_cfg) != 0) { + PrintError("Failed to parse memory configuration\n"); + return -1; + } - PrintDebug("Alignment for %lu bytes of memory computed as 0x%x\n", vm->mem_size, vm->mem_align); if (strcasecmp(vm_class, "PC") == 0) { vm->vm_class = V3_PC_VM; } else { PrintError("Invalid VM class\n"); - return -1; + return mem_chunks_free(vm); } #ifdef V3_CONFIG_TELEMETRY @@ -279,52 +426,35 @@ static int pre_config_vm(struct v3_vm_info * vm, v3_cfg_tree_t * vm_cfg) { } -static int determine_paging_mode(struct guest_info * info, v3_cfg_tree_t * core_cfg) { +static int set_core_paging_mode(struct guest_info *info) { extern v3_cpu_arch_t v3_mach_type; - v3_cfg_tree_t * vm_tree = info->vm_info->cfg_data->cfg; - v3_cfg_tree_t * pg_tree = v3_cfg_subtree(vm_tree, "paging"); - char * pg_mode = v3_cfg_val(pg_tree, "mode"); - - PrintDebug("Paging mode specified as %s\n", pg_mode); - - if (pg_mode) { - if ((strcasecmp(pg_mode, "nested") == 0)) { - // we assume symmetric cores, so if core 0 has nested paging they all do - if ((v3_mach_type == V3_SVM_REV3_CPU) || - (v3_mach_type == V3_VMX_EPT_CPU) || - (v3_mach_type == V3_VMX_EPT_UG_CPU)) { - info->shdw_pg_mode = NESTED_PAGING; - } else { - PrintError("Nested paging not supported on this hardware. Defaulting to shadow paging\n"); - info->shdw_pg_mode = SHADOW_PAGING; - } - } else if ((strcasecmp(pg_mode, "shadow") == 0)) { - info->shdw_pg_mode = SHADOW_PAGING; - } else { - PrintError("Invalid paging mode (%s) specified in configuration. Defaulting to shadow paging\n", pg_mode); - info->shdw_pg_mode = SHADOW_PAGING; - } - } else { - PrintDebug("No paging type specified in configuration. Defaulting to shadow paging\n"); - info->shdw_pg_mode = SHADOW_PAGING; - } - - - if (v3_cfg_val(pg_tree, "large_pages") != NULL) { - if (strcasecmp(v3_cfg_val(pg_tree, "large_pages"), "true") == 0) { - info->use_large_pages = 1; - PrintDebug("Use of large pages in memory virtualization enabled.\n"); - } - } + info->shdw_pg_mode = info->vm_info->shdw_pg_mode; + info->flags = info->vm_info->flags; return 0; } static int pre_config_core(struct guest_info * info, v3_cfg_tree_t * core_cfg) { - if (determine_paging_mode(info, core_cfg) != 0) { + if (set_core_paging_mode(info) != 0) { return -1; } + int node = 0, final_node; + if(v3_cfg_val(core_cfg, "node")) { + node = atoi(v3_cfg_val(core_cfg, "node")); + if(node < 0 || node >= info->vm_info->num_nodes) { + PrintError("Node number for core %d is incorrect!\n", info->vcpu_id); + return -1; + } + } + + final_node = info->vm_info->node_config_to_final[node]; + if(final_node != node) { + V3_Print("Core %d: node changed (%d -> %d) due to node number renaming.\n", + info->vcpu_id, node, final_node); + } + info->vm_info->node_cpumask[final_node] |= 1 << info->vcpu_id; + v3_init_core(info); if (info->vm_info->vm_class == V3_PC_VM) { @@ -372,6 +502,20 @@ static int post_config_vm(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { return -1; } + /* + * We need one extra step after devices configured to fix 4GB memory hole in + * E820 map and to finally give E820 to the BIOS. + */ + if (vm->vm_class == V3_PC_VM) { + if (final_config_pc(vm, cfg) == -1) { + PrintError("PC final configuration failure\n"); + return -1; + } + } else { + PrintError("Invalid VM Class\n"); + return -1; + } + // v3_print_io_map(info); v3_print_msr_map(vm); @@ -418,13 +562,16 @@ static int post_config_core(struct guest_info * info, v3_cfg_tree_t * cfg) { -static struct v3_vm_info * allocate_guest(int num_cores) { +static struct v3_vm_info * allocate_guest(int num_cores, int num_nodes) { int guest_state_size = sizeof(struct v3_vm_info) + (sizeof(struct guest_info) * num_cores); struct v3_vm_info * vm = V3_Malloc(guest_state_size); memset(vm, 0, guest_state_size); vm->num_cores = num_cores; + vm->num_nodes = num_nodes; + + vm->num_host_nodes = 2; // XXX: ask host about it! return vm; } @@ -435,8 +582,9 @@ struct v3_vm_info * v3_config_guest(void * cfg_blob, void * priv_data) { extern v3_cpu_arch_t v3_mach_type; struct v3_config * cfg_data = NULL; struct v3_vm_info * vm = NULL; - int num_cores = 0; + int num_cores = 0, num_nodes = 0; int i = 0; + v3_cfg_tree_t * nodes_cfg = NULL; v3_cfg_tree_t * cores_cfg = NULL; v3_cfg_tree_t * per_core_cfg = NULL; @@ -464,10 +612,25 @@ struct v3_vm_info * v3_config_guest(void * cfg_blob, void * priv_data) { PrintError("No cores specified in configuration\n"); return NULL; } + nodes_cfg = v3_cfg_subtree(cfg_data->cfg, "nodes"); + if(nodes_cfg) { + if(!v3_cfg_val(nodes_cfg, "count")) { + PrintError("Node count should be specified!\n"); + return NULL; + } + if(v3_cfg_val(nodes_cfg, "count")) { + num_nodes = atoi(v3_cfg_val(nodes_cfg, "count")); + if (num_nodes > MAX_NODE_COUNT) { + PrintError("Too much nodes! Max. supported is %d\n", MAX_NODE_COUNT); + return NULL; + } + } + } + - V3_Print("Configuring %d cores\n", num_cores); + V3_Print("Configuring %d cores, %d nodes\n", num_cores, num_nodes); - vm = allocate_guest(num_cores); + vm = allocate_guest(num_cores, num_nodes); if (!vm) { PrintError("Could not allocate %d core guest\n", vm->num_cores); diff --git a/palacios/src/palacios/vmm_config_class.h b/palacios/src/palacios/vmm_config_class.h index 3f5b238..b79f828 100644 --- a/palacios/src/palacios/vmm_config_class.h +++ b/palacios/src/palacios/vmm_config_class.h @@ -18,7 +18,7 @@ */ #include - +#include static int pre_config_pc_core(struct guest_info * info, v3_cfg_tree_t * cfg) { @@ -46,8 +46,7 @@ static int post_config_pc_core(struct guest_info * info, v3_cfg_tree_t * cfg) { static int post_config_pc(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { #define VGABIOS_START 0x000c0000 -#define ROMBIOS_START 0x000f0000 - + /* layout vgabios */ { extern uint8_t v3_vgabios_start[]; @@ -67,16 +66,48 @@ static int post_config_pc(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { extern uint8_t v3_rombios_start[]; extern uint8_t v3_rombios_end[]; void * rombios_dst = 0; + size_t rombios_size = v3_rombios_end - v3_rombios_start; - if (v3_gpa_to_hva(&(vm->cores[0]), ROMBIOS_START, (addr_t *)&rombios_dst) == -1) { +#ifdef V3_CONFIG_BOCHSBIOS + size_t required_size = 0x10000; +#elif V3_CONFIG_SEABIOS + size_t required_size = 0x20000; +#endif + if(rombios_size != required_size) { + PrintError("Bad BIOS size: 0x%lx (expected %lx)!\n", rombios_size, required_size); + return -1; + } + + if (v3_gpa_to_hva(&(vm->cores[0]), 0x100000 - rombios_size, (addr_t *)&rombios_dst) == -1) { PrintError("Could not find ROMBIOS destination address\n"); return -1; } - memcpy(rombios_dst, v3_rombios_start, v3_rombios_end - v3_rombios_start); - } + memcpy(rombios_dst, v3_rombios_start, rombios_size); + +#ifdef V3_CONFIG_SEABIOS + /* seabios wants to be mapped into the upper memory */ + if(v3_add_shadow_mem(vm, V3_MEM_CORE_ANY, (uint32_t)(-rombios_size), + 0x100000000ULL, (addr_t)V3_PAddr(rombios_dst)) == -1) { + PrintError("Failed to map BIOS!\n"); + return -1; + } +#endif + } return 0; } +static int final_config_pc(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { + + if(v3_finalize_e820(vm) != 0) { + PrintError("Failed to finalize E820 table!\n"); + return -1; + } + if(v3_fw_cfg_init(vm) != 0) { + PrintError("Failed to configure FW interface!\n"); + return -1; + } + return 0; +} diff --git a/palacios/src/palacios/vmm_fw_cfg.c b/palacios/src/palacios/vmm_fw_cfg.c new file mode 100644 index 0000000..2e2c629 --- /dev/null +++ b/palacios/src/palacios/vmm_fw_cfg.c @@ -0,0 +1,344 @@ +/* + * This file is part of the Palacios Virtual Machine Monitor developed + * by the V3VEE Project with funding from the United States National + * Science Foundation and the Department of Energy. + * + * The V3VEE Project is a joint project between Northwestern University + * and the University of New Mexico. You can find out more at + * http://www.v3vee.org + * + * Copyright (c) 2008, The V3VEE Project + * All rights reserved. + * + * Author: Alexander Kudryavtsev + * Based on QEMU implementation. + * + * This is free software. You are permitted to use, + * redistribute, and modify it as specified in the file "V3VEE_LICENSE". + */ + +#include +#include +#include +#include + +#define FW_CFG_CTL_PORT 0x510 +#define FW_CFG_DATA_PORT 0x511 + +#define FW_CFG_SIGNATURE 0x00 +#define FW_CFG_ID 0x01 +#define FW_CFG_UUID 0x02 +#define FW_CFG_RAM_SIZE 0x03 +#define FW_CFG_NOGRAPHIC 0x04 +#define FW_CFG_NB_CPUS 0x05 +#define FW_CFG_MACHINE_ID 0x06 +#define FW_CFG_KERNEL_ADDR 0x07 +#define FW_CFG_KERNEL_SIZE 0x08 +#define FW_CFG_KERNEL_CMDLINE 0x09 +#define FW_CFG_INITRD_ADDR 0x0a +#define FW_CFG_INITRD_SIZE 0x0b +#define FW_CFG_BOOT_DEVICE 0x0c +#define FW_CFG_NUMA 0x0d +#define FW_CFG_BOOT_MENU 0x0e +#define FW_CFG_MAX_CPUS 0x0f +#define FW_CFG_KERNEL_ENTRY 0x10 +#define FW_CFG_KERNEL_DATA 0x11 +#define FW_CFG_INITRD_DATA 0x12 +#define FW_CFG_CMDLINE_ADDR 0x13 +#define FW_CFG_CMDLINE_SIZE 0x14 +#define FW_CFG_CMDLINE_DATA 0x15 +#define FW_CFG_SETUP_ADDR 0x16 +#define FW_CFG_SETUP_SIZE 0x17 +#define FW_CFG_SETUP_DATA 0x18 +#define FW_CFG_FILE_DIR 0x19 + +#define FW_CFG_FILE_FIRST 0x20 +#define FW_CFG_FILE_SLOTS 0x10 +#define FW_CFG_MAX_ENTRY (FW_CFG_FILE_FIRST+FW_CFG_FILE_SLOTS) + +#define FW_CFG_WRITE_CHANNEL 0x4000 +#define FW_CFG_ARCH_LOCAL 0x8000 +#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) + +#define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0) +#define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1) +#define FW_CFG_IRQ0_OVERRIDE (FW_CFG_ARCH_LOCAL + 2) +#define FW_CFG_E820_TABLE (FW_CFG_ARCH_LOCAL + 3) +#define FW_CFG_HPET (FW_CFG_ARCH_LOCAL + 4) + +#define FW_CFG_INVALID 0xffff + +typedef void (*cfg_cb)(void *opaque, uint8_t *data); + +struct cfg_entry { + uint32_t len; + uint8_t *data; + void *callback_opaque; + cfg_cb callback; +}; + +struct cfg_state { + struct cfg_entry entries[2][FW_CFG_MAX_ENTRY]; + uint16_t cur_entry; + uint32_t cur_offset; +}; + +static int fw_cfg_add_bytes(struct cfg_state *s, uint16_t key, uint8_t *data, uint32_t len) +{ + int arch = !!(key & FW_CFG_ARCH_LOCAL); + + key &= FW_CFG_ENTRY_MASK; + + if (key >= FW_CFG_MAX_ENTRY) + return 0; + + s->entries[arch][key].data = data; + s->entries[arch][key].len = len; + + return 1; +} + +static int fw_cfg_add_i16(struct cfg_state *s, uint16_t key, uint16_t value) +{ + uint16_t *copy; + + copy = V3_Malloc(sizeof(value)); + *copy = value; + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} +static int fw_cfg_add_i32(struct cfg_state *s, uint16_t key, uint32_t value) +{ + uint32_t *copy; + + copy = V3_Malloc(sizeof(value)); + *copy = value; + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} +static int fw_cfg_add_i64(struct cfg_state *s, uint16_t key, uint64_t value) +{ + uint64_t *copy; + + copy = V3_Malloc(sizeof(value)); + *copy = value; + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} + +static int fw_cfg_ctl_read(struct guest_info *core, ushort_t port, void *src, uint_t length, void *priv_data) { + return length; +} +static int fw_cfg_ctl_write(struct guest_info *core, ushort_t port, void *src, uint_t length, void *priv_data) { + V3_ASSERT(length == 2); + struct cfg_state * s = (struct cfg_state *)priv_data; + uint16_t key = *(uint16_t*)src; + int ret; + + s->cur_offset = 0; + if ((key & FW_CFG_ENTRY_MASK) >= FW_CFG_MAX_ENTRY) { + s->cur_entry = FW_CFG_INVALID; + ret = 0; + } else { + s->cur_entry = key; + ret = 1; + } + + return length; +} +static int fw_cfg_data_read(struct guest_info *core, ushort_t port, void *src, uint_t length, void *priv_data) { + V3_ASSERT(length == 1); + struct cfg_state * s = (struct cfg_state *)priv_data; + int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); + struct cfg_entry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; + uint8_t ret; + + if (s->cur_entry == FW_CFG_INVALID || !e->data || s->cur_offset >= e->len) + ret = 0; + else + ret = e->data[s->cur_offset++]; + + *(uint8_t*)src = ret; + + return length; +} +static int fw_cfg_data_write(struct guest_info *core, ushort_t port, void *src, uint_t length, void *priv_data) { + V3_ASSERT(length == 1); + struct cfg_state * s = (struct cfg_state *)priv_data; + int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); + struct cfg_entry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; + + if (s->cur_entry & FW_CFG_WRITE_CHANNEL && e->callback && + s->cur_offset < e->len) { + e->data[s->cur_offset++] = *(uint8_t*)src; + if (s->cur_offset == e->len) { + e->callback(e->callback_opaque, e->data); + s->cur_offset = 0; + } + } + return length; +} + +#define E820_MAX_COUNT 128 +struct e820_entry_packed { + uint64_t addr; + uint64_t size; + uint32_t type; +} __attribute__((packed)); +struct e820_table { + uint32_t count; + struct e820_entry_packed entry[E820_MAX_COUNT]; +} __attribute__((packed)) __attribute((__aligned__(4))); + +static struct e820_table *e820_populate(struct v3_vm_info *vm) { + struct v3_e820_entry *e; + struct e820_table *e820; + int i = 0; + + if(vm->mem_map.e820_count > E820_MAX_COUNT) { + PrintError("Too much E820 table entries! (max is %d)\n", E820_MAX_COUNT); + return NULL; + } + e820 = V3_Malloc(sizeof(*e820)); + if(!e820) { + PrintError("Out of memory!\n"); + return NULL; + } + e820->count = vm->mem_map.e820_count; + list_for_each_entry(e, &vm->mem_map.e820_list, list) { + e820->entry[i].addr = e->addr; + e820->entry[i].size = e->size; + e820->entry[i].type = e->type; + ++i; + } + return e820; +} + +int v3_fw_cfg_init(struct v3_vm_info *vm) { + + struct cfg_state *s; + struct e820_table *e820 = e820_populate(vm); + if(!e820) { + PrintError("Failed to populate E820 for FW interface!\n"); + return -1; + } + +#ifdef V3_CONFIG_BOCHSBIOS +/* E820 location in HVM virtual address space. Taken from VMXASSIST. */ +#define HVM_E820_PAGE 0x00090000 +#define HVM_E820_NR_OFFSET 0x000001E8 +#define HVM_E820_OFFSET 0x000002D0 + // Copy E820 to BIOS. See rombios.c, copy_e820_table function. + addr_t e820_ptr = (addr_t)V3_VAddr((void*)(vm->mem_map.base_region.host_addr + HVM_E820_PAGE)); + + *(uint16_t*)(e820_ptr + HVM_E820_NR_OFFSET) = e820->count; + memcpy((void*)(e820_ptr + HVM_E820_OFFSET), &e820->entry[0], sizeof(e820->entry[0]) * e820->count); + V3_Free(e820); + return 0; +#endif + // else, we have SEABIOS. + + s = V3_Malloc(sizeof(*s)); + memset(s, 0x00, sizeof(*s)); + + int ret = 0; + ret |= v3_hook_io_port(vm, FW_CFG_CTL_PORT, fw_cfg_ctl_read, &fw_cfg_ctl_write, s); + ret |= v3_hook_io_port(vm, FW_CFG_DATA_PORT, fw_cfg_data_read, &fw_cfg_data_write, s); + + if(ret != 0) { + V3_Free(e820); + V3_Free(s); + PrintError("Failed to hook FW CFG ports!\n"); + return -1; + } + + fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (uint8_t *)"QEMU", 4); + //fw_cfg_add_bytes(s, FW_CFG_UUID, qemu_uuid, 16); + fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, /*(uint16_t)(display_type == DT_NOGRAPHIC)*/0); + fw_cfg_add_i16(s, FW_CFG_NB_CPUS, (uint16_t)vm->num_cores); + fw_cfg_add_i16(s, FW_CFG_MAX_CPUS, (uint16_t)vm->num_cores); + fw_cfg_add_i16(s, FW_CFG_BOOT_MENU, (uint16_t)1); + //fw_cfg_bootsplash(s); + + fw_cfg_add_i32(s, FW_CFG_ID, 1); + fw_cfg_add_i64(s, FW_CFG_RAM_SIZE, (uint64_t)vm->mem_size / (1024*1024)); + + //fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES, (uint8_t *)acpi_tables, + // acpi_tables_len); + + fw_cfg_add_i32(s, FW_CFG_IRQ0_OVERRIDE, 1); + + /*smbios_table = smbios_get_table(&smbios_len); + if (smbios_table) + fw_cfg_add_bytes(fw_cfg, FW_CFG_SMBIOS_ENTRIES, + smbios_table, smbios_len);*/ + + fw_cfg_add_bytes(s, FW_CFG_E820_TABLE, (uint8_t *)e820, + sizeof(*e820)); + + /*fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, (uint8_t *)&hpet_cfg, + sizeof(struct hpet_fw_config));*/ + + if(vm->num_nodes != 0) { + int i, j; + uint64_t *numa_fw_cfg = V3_Malloc((1 + vm->num_cores + vm->num_nodes) * 8); + uint64_t core_to_node[vm->num_cores]; + memset(numa_fw_cfg, 0, (1 + vm->num_cores + vm->num_nodes) * 8); + numa_fw_cfg[0] = vm->num_nodes; + for (i = 0; i < vm->num_cores; i++) { + for (j = 0; j < vm->num_nodes; j++) { + if (vm->node_cpumask[j] & (1 << i)) { + core_to_node[i] = j; + break; + } + } + } + + memcpy(numa_fw_cfg + 1, core_to_node, sizeof(core_to_node)); + uint64_t node_mem[vm->num_nodes]; + memset(node_mem, 0, sizeof(node_mem)); + struct v3_e820_entry *e, *ep; + uint64_t start, end = 0; + int node; + + // Ranges for one node can only be consequent. List is sorted by address. + e = list_first_entry(&vm->mem_map.e820_list, struct v3_e820_entry, list); + node = e->node; + start = e->addr; + list_for_each_entry(e, &vm->mem_map.e820_list, list) { + if(e->node != node) { + end = e->addr; + node_mem[node] = end - start; + node = e->node; + start = end; + } + ep = e; + } + node_mem[node] = ep->addr + ep->size - start; + + PrintDebug("Node configuration is:\n"); + for (i = 0; i < vm->num_nodes; i++) { + PrintDebug(" Node %d: CPUs: ", i); + for(j = 0; j < vm->num_cores; ++j) { + if(vm->node_cpumask[i] & (1 << j)) + PrintDebug("%d ", j); + } + PrintDebug("Memory: 0x%llx bytes.\n", node_mem[i]); + numa_fw_cfg[vm->num_cores + 1 + i] = node_mem[i]; + } + fw_cfg_add_bytes(s, FW_CFG_NUMA, (uint8_t *)numa_fw_cfg, + (1 + vm->num_cores + vm->num_nodes) * 8); + } + + vm->fw_cfg_state = s; + return 0; +} + +void v3_delete_fw_cfg(struct v3_vm_info *vm) { + struct cfg_state *s = (struct cfg_state*)vm->fw_cfg_state; + int i, j; + for(i = 0; i < 2; ++i) + for(j = 0; j < FW_CFG_MAX_ENTRY; ++j) { + if(s->entries[i][j].data != NULL) + V3_Free(s->entries[i][j].data); + } + V3_Free(s); + vm->fw_cfg_state = NULL; +} diff --git a/palacios/src/palacios/vmm_mem.c b/palacios/src/palacios/vmm_mem.c index 3753a93..92a41c7 100644 --- a/palacios/src/palacios/vmm_mem.c +++ b/palacios/src/palacios/vmm_mem.c @@ -51,9 +51,297 @@ static int unhandled_err(struct guest_info * core, addr_t guest_va, addr_t guest return -1; } +static void mem_chunks_free_all(struct v3_vm_info *vm) { + struct v3_mem_chunk *c, *cn; + list_for_each_entry_safe(c, cn, &vm->mem_chunks, list) { + if(c->host_pa != (addr_t)-1) + V3_FreePages((void*)c->host_pa, c->size >> 12); + list_del(&c->list); + V3_Free(c); + } +} + +#define MEM_HI 0x100000000 +static int cmp_chunks_by_host_pa(void *priv, struct list_head *h1, struct list_head *h2) { + struct v3_mem_chunk *ch1 = list_entry(h1, struct v3_mem_chunk, list); + struct v3_mem_chunk *ch2 = list_entry(h2, struct v3_mem_chunk, list); + if(ch1->host_pa < ch2->host_pa) + return -1; + return 1; +} +static int allocate_chunks(struct v3_vm_info * vm) { + struct v3_mem_chunk *chunk, *cn; + int i = 0, renamed, j, nodes_with_memory = 0; + + list_for_each_entry(chunk, &vm->mem_chunks, list) { + addr_t mem_pages = chunk->size >> 12; + //XXX: add support for target_node - allocate memory from selected node if necessary! + // 2MB page alignment needed for 2MB hardware nested paging +#ifdef V3_CONFIG_ALIGNED_PG_ALLOC + chunk->host_pa = (addr_t)V3_AllocAlignedPages(mem_pages, vm->mem_align); +#else + chunk->host_pa = (addr_t)V3_AllocPages(mem_pages); +#endif + if(!chunk->host_pa) { + PrintError("Failed to allocate chunk %d, size %lu!\n", chunk->idx, + chunk->size); + mem_chunks_free_all(vm); + return -1; + } + memset(V3_VAddr((void*)chunk->host_pa), 0x0, mem_pages * PAGE_SIZE_4KB); + } + // Now, sort by host_pa in increasing order + list_sort_bubble(NULL, &vm->mem_chunks, cmp_chunks_by_host_pa); + + PrintDebug("Chunks allocated: (index in config, host PA, size, node, target_node)\n"); + list_for_each_entry(chunk, &vm->mem_chunks, list) { + PrintDebug("%3d 0x%016lx 0x%016lx %d %d\n", chunk->idx, chunk->host_pa, + chunk->size, chunk->node, chunk->target_node); + } + + // Early fix for allocated memory: in case of shadow paging mode, we cannot + // use memory which is above 4GB in host but below 4GB in guest, + // because 32-bit page tables cannot map to addresses above 4GB. + if(vm->shdw_pg_mode == SHADOW_PAGING) { + uint64_t offset = list_first_entry(&vm->mem_chunks, struct v3_mem_chunk, list)->host_pa; + list_for_each_entry_safe(chunk, cn, &vm->mem_chunks, list) { + // We look for intersections of allocated ranges with + // [4GB, 4GB + guest_offset] range. + uint64_t start = chunk->host_pa; + uint64_t end = chunk->host_pa + chunk->size; + uint64_t low = MEM_HI > start ? MEM_HI : start; + uint64_t high = MEM_HI + offset < end ? MEM_HI + offset : end; + size_t free_space = 0; + addr_t free_addr = 0; + if(low < high) { + if(start < low && end > low && end <= high) { + // Cut right end + chunk->size = low - start; + free_addr = low; + free_space += end - low; + V3_Print("Chunk %d modified to meet shadow paging" + " requirements. New size is 0x%lx. ", chunk->idx, + chunk->size); + } else if(start >= low && start < high && end > high) { + // Cut left end + free_addr = chunk->host_pa; + chunk->host_pa = high; + chunk->size = end - high;//! + free_space += high - start; + V3_Print("Chunk %d modified to meet shadow paging" + " requirements. New host PA is 0x%lx, size is 0x%lx. ", + chunk->idx, chunk->host_pa, chunk->size); + } else if(start >= low && end <= high) { + // Whole range is inside hole, delete it + list_del(&chunk->list); + free_addr = chunk->host_pa; + free_space = chunk->size; + V3_Print("Chunk %d is deleted to meet shadow paging" + " requirements. ", chunk->idx); + V3_Free(chunk); + vm->mem_chunks_cnt -= 1; + } else if(start < low && end > high) { + // Hole is inside range + chunk->size = low - start; + + struct v3_mem_chunk *new_chunk = V3_Malloc(sizeof(*chunk)); + if(!new_chunk) { + PrintError("Out of memory!\n"); + mem_chunks_free_all(vm); + return -1; + } + memset(new_chunk, 0x0, sizeof(*chunk)); + new_chunk->idx = chunk->idx; + new_chunk->node = chunk->node; + new_chunk->target_node = chunk->target_node; + new_chunk->size = end - high; + new_chunk->host_pa = high; + free_addr = low; + free_space = high - low; + vm->mem_chunks_cnt += 1; + + V3_Print("Chunk %d split to meet shadow paging" + " requirements. First chunk size changed to 0x%lx." + " New chunk host PA 0x%lx, size 0x%lx. ", chunk->idx, + chunk->size, new_chunk->host_pa, new_chunk->size); + + list_add(&new_chunk->list, &chunk->list); + } + if(free_addr) { + V3_Print("Freeing 0x%lx bytes at 0x%p.\n", free_space, (void*)free_addr); + V3_FreePages((void*)free_addr, free_space >> 12); + } + } + } + } + + // Check for consistency: disallow node memory interleaving; + uint8_t node_map[vm->num_nodes]; + memset(node_map, 0xff, sizeof(node_map)); + list_for_each_entry(chunk, &vm->mem_chunks, list) { + if(node_map[chunk->node] != 0xff && i - node_map[chunk->node] > 1) { + PrintError("Node memory conflict for node %d!\n", chunk->node); + mem_chunks_free_all(vm); + return -1; + } + node_map[chunk->node] = i++; + } + + // Rename node numbers to be ascending. The order is fixed and will not change. + // Don't forget that there can be nodes without memory. + // Nodes with memory should go first, this is caused by SEABIOS implementation. + for(i = 0; i < vm->num_nodes; ++i) { + vm->node_config_to_final[i] = i; + + if(node_map[i] != 0xff) nodes_with_memory += 1; + } + chunk = list_first_entry(&vm->mem_chunks, struct v3_mem_chunk, list); + int node = chunk->node; + i = 0, renamed = 0; + list_for_each_entry(chunk, &vm->mem_chunks, list) { + if(chunk->node != node) { + if(i != node) renamed = 1; + vm->node_config_to_final[node] = i++; + node = chunk->node; + } + } + if(i != node) renamed = 1; + vm->node_config_to_final[node] = i; + // Rename nodes without memory too + j = nodes_with_memory; + for(i = 0; i < vm->num_nodes; ++i) { + if(node_map[i] == 0xff) { + if(j != i) renamed = 1; + vm->node_config_to_final[i] = j++; + } + } + + if(renamed) { + V3_Print("NOTE: the following node numbers are changed to make nodes ordered:\n"); + for(i = 0; i < vm->num_nodes; ++i) { + if(vm->node_config_to_final[i] != i) + V3_Print(" %d -> %d;", i, vm->node_config_to_final[i]); + } + V3_Print("\nConsider renaming these nodes in configuration.\n"); + + list_for_each_entry(chunk, &vm->mem_chunks, list) { + chunk->node = vm->node_config_to_final[chunk->node]; + } + } + + return 0; +} + + +static const char *e820_type_str(int type) { + switch(type) { + case E820_TYPE_FREE: return "Free"; + case E820_TYPE_RESV: return "Reserved"; + case E820_TYPE_ACPI_RECL: return "ACPI Recl."; + case E820_TYPE_ACPI_NVS: return "ACPI NVS"; + case E820_TYPE_BAD: return "Bad"; + default: return NULL; + } +} + +static struct v3_e820_entry *new_e820_entry(uint32_t type, uint64_t addr, + uint64_t size, int node, int chunk_idx) { + struct v3_e820_entry *e = V3_Malloc(sizeof(*e)); + if(!e) { + PrintError("Out of memory!\n"); + return NULL; + } + memset(e, 0x0, sizeof(*e)); + + e->type = type; + e->addr = addr; + e->size = size; + e->node = node; + e->chunk_idx = chunk_idx; + return e; +} + +static int e820_entries_free(struct v3_vm_info *vm) { + struct v3_e820_entry *e, *en; + list_for_each_entry_safe(e, en, &vm->mem_map.e820_list, list) { + list_del(&e->list); + V3_Free(e); + } + return -1; +} + +#define EBDA_START 0x9fc00 +static int init_e820(struct v3_vm_info * vm) { + struct v3_mem_map * map = &(vm->mem_map); + int count = 0; + struct v3_e820_entry *e, *ep = NULL; + struct v3_mem_chunk *c; + addr_t host_base = map->base_region.host_addr, offset; + addr_t bytes_lo = 0x100000, bytes_hi = 0, start, end; + int hole_lo = 0, hole_hi = 0; + INIT_LIST_HEAD(&map->e820_list); + + // Special handling for BIOS area.. + c = list_first_entry(&vm->mem_chunks, struct v3_mem_chunk, list); + if(!(e = new_e820_entry(E820_TYPE_FREE, 0, EBDA_START, c->node, c->idx))) + return -1; + list_add(&e->list, &map->e820_list); + + offset = 0x100000; + count = 1; + list_for_each_entry(c, &vm->mem_chunks, list) { + if(!(e = new_e820_entry(E820_TYPE_FREE, + c->host_pa - host_base + offset, + c->size - offset, c->node, c->idx))) + return e820_entries_free(vm); + list_add_tail(&e->list, &map->e820_list); + offset = 0; + ++count; + } + map->e820_count = count; + + // Detect contiguous memory below and above 4GB. + // XXX: we do it here since NVRAM will use these values; + // we update E820 after all devices are initialized (detect and cut 4GB hole), + // and there is no simple way to update memory parameters in NVRAM. + list_for_each_entry(e, &map->e820_list, list) { + start = e->addr; + end = e->addr + e->size; + if(start == 0) { + // First entry is special, we skip it + continue; + } + if(ep && start < MEM_HI && start != ep->addr + ep->size) + hole_lo = 1; + if(ep && ep->addr + ep->size > MEM_HI && start != ep->addr + ep->size) + hole_hi = 1; + if(start < MEM_HI && !hole_lo) + bytes_lo += (end > MEM_HI ? MEM_HI : end) - start; + if(end > MEM_HI && !hole_hi) + bytes_hi += end - (start < MEM_HI? MEM_HI : start); + ep = e; + } + vm->mem_size_lo = bytes_lo; + vm->mem_size_hi = bytes_hi; + PrintDebug("Calculated 0x%08lx lo and 0x%016lx hi contiguous memory in bytes.\n", + bytes_lo, bytes_hi); + + PrintDebug("Initial E820 memory map - total %d entries:\n", map->e820_count); + list_for_each_entry(e, &map->e820_list, list) { + PrintDebug(" [chunk %d, node %d] 0x%016llx - 0x%016llx, %s\n", e->chunk_idx, e->node, + e->addr, e->addr + e->size, e820_type_str(e->type)); + } + + return 0; +} + int v3_init_mem_map(struct v3_vm_info * vm) { struct v3_mem_map * map = &(vm->mem_map); - addr_t mem_pages = vm->mem_size >> 12; + + if(allocate_chunks(vm) != 0) { + PrintError("Failed to allocate chunks!\n"); + return -1; + } memset(&(map->base_region), 0, sizeof(struct v3_mem_region)); @@ -64,17 +352,10 @@ int v3_init_mem_map(struct v3_vm_info * vm) { // 2MB page alignment needed for 2MB hardware nested paging map->base_region.guest_start = 0; - map->base_region.guest_end = mem_pages * PAGE_SIZE_4KB; - -#ifdef V3_CONFIG_ALIGNED_PG_ALLOC - map->base_region.host_addr = (addr_t)V3_AllocAlignedPages(mem_pages, vm->mem_align); -#else - map->base_region.host_addr = (addr_t)V3_AllocPages(mem_pages); -#endif - - // Clear the memory... - memset(V3_VAddr((void *)map->base_region.host_addr), 0, mem_pages * PAGE_SIZE_4KB); + map->base_region.guest_end = vm->mem_size; + map->base_region.host_addr = + list_first_entry(&vm->mem_chunks, struct v3_mem_chunk, list)->host_pa; map->base_region.flags.read = 1; map->base_region.flags.write = 1; @@ -84,11 +365,12 @@ int v3_init_mem_map(struct v3_vm_info * vm) { map->base_region.unhandled = unhandled_err; - if ((void *)map->base_region.host_addr == NULL) { - PrintError("Could not allocate Guest memory\n"); - return -1; + if(init_e820(vm) != 0) { + PrintError("Failed to initialize E820 map.\n"); + mem_chunks_free_all(vm); + return -1; } - + //memset(V3_VAddr((void *)map->base_region.host_addr), 0xffffffff, map->base_region.guest_end); v3_register_hypercall(vm, MEM_OFFSET_HCALL, mem_offset_hypercall, NULL); @@ -96,13 +378,79 @@ int v3_init_mem_map(struct v3_vm_info * vm) { return 0; } +int v3_finalize_e820(struct v3_vm_info *vm) { + struct v3_e820_entry *e, *en; + struct rb_node * node = v3_rb_first(&(vm->mem_map.mem_regions)); + struct v3_mem_region * reg; + addr_t hole_start = (addr_t)-1; + addr_t hole_end = 0x100000000ULL; + + if (node == NULL) { + return 0; + } + do { + reg = rb_entry(node, struct v3_mem_region, tree_node); + if (reg->guest_start != 0xA0000 && reg->guest_start < hole_end + && reg->guest_start < hole_start) + hole_start = reg->guest_start; + } while ((node = v3_rb_next(node))); + + //hole_start &= 0xf0000000; + if(!hole_start) { + // This should be really rare case - it means no APIC, no devices with MMIO.. + hole_start = 0xffe00000; + } else { + } + V3_Print("Calculated 4GB hole start: %016lx\n", hole_start); + + uint64_t lost_space = 0; + // now cut off 4GB hole from E820 + list_for_each_entry_safe(e, en, &vm->mem_map.e820_list, list) { + uint64_t start = e->addr; + uint64_t end = e->addr + e->size; + + if(start < hole_start && end > hole_start && end <= hole_end) { + // Cut right end + e->size = hole_start - start; + lost_space += end - hole_start; + } else if(start >= hole_start && start < hole_end && end > hole_end) { + // Cut left end + e->addr = hole_end; + e->size = end - hole_end; + lost_space += hole_end - start; + } else if(start >= hole_start && end <= hole_end) { + // Whole range is inside hole, delete it + list_del(&e->list); + V3_Free(e); + vm->mem_map.e820_count -= 1; + lost_space += end - start; + } else if(start < hole_start && end > hole_end) { + // Hole is inside range + e->size = hole_start - start; + list_add(&new_e820_entry(E820_TYPE_FREE, hole_end, end - hole_end, + e->node, e->chunk_idx)->list, &e->list); + + vm->mem_map.e820_count += 1; + lost_space += hole_end - hole_start; + } + } + if(lost_space != 0) { + PrintDebug("Note: %u MB of memory is lost due to 4GB hole.\n", + (uint32_t)(lost_space / (1024 * 1024))); + } + PrintDebug("Final E820 memory map - total %d entries:\n", vm->mem_map.e820_count); + list_for_each_entry(e, &vm->mem_map.e820_list, list) { + PrintDebug(" [chunk %d, node %d] 0x%016llx - 0x%016llx, %s\n", e->chunk_idx, e->node, + e->addr, e->addr + e->size, e820_type_str(e->type)); + } + return 0; +} void v3_delete_mem_map(struct v3_vm_info * vm) { struct rb_node * node = v3_rb_first(&(vm->mem_map.mem_regions)); struct v3_mem_region * reg; struct rb_node * tmp_node = NULL; - addr_t mem_pages = vm->mem_size >> 12; - + while (node) { reg = rb_entry(node, struct v3_mem_region, tree_node); tmp_node = node; @@ -111,7 +459,8 @@ void v3_delete_mem_map(struct v3_vm_info * vm) { v3_delete_mem_region(vm, reg); } - V3_FreePages((void *)(vm->mem_map.base_region.host_addr), mem_pages); + mem_chunks_free_all(vm); + e820_entries_free(vm); } @@ -287,14 +636,27 @@ struct v3_mem_region * v3_get_mem_region(struct v3_vm_info * vm, uint16_t core_i } - // There is not registered region, so we check if its a valid address in the base region - - if (guest_addr > vm->mem_map.base_region.guest_end) { - PrintError("Guest Address Exceeds Base Memory Size (ga=0x%p), (limit=0x%p) (core=0x%x)\n", - (void *)guest_addr, (void *)vm->mem_map.base_region.guest_end, core_id); - v3_print_mem_map(vm); + // There is not registered region, so we check if its a valid address in E820 map. + int ok = 0; + struct v3_e820_entry *e; + if(guest_addr >= 0x100000) { + // Find address in e820 table + list_for_each_entry(e, &vm->mem_map.e820_list, list) { + if(e->type == E820_TYPE_FREE && guest_addr >= e->addr + && guest_addr < e->addr + e->size) { + ok = 1; break; + } + } + } else { + // we have a hole for BIOS at 0x9fc00 .. 0x100000, so allow these accesses by default + ok = 1; + } + if (!ok) { + PrintError("Guest Address is out of E820 map (GA=0x%p), (core=0x%x)\n", + (void *)guest_addr, core_id); + v3_print_mem_map(vm); - return NULL; + return NULL; } return &(vm->mem_map.base_region); -- 1.7.5.4