Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / u-boot / arch / x86 / lib / physmem.c
diff --git a/qemu/roms/u-boot/arch/x86/lib/physmem.c b/qemu/roms/u-boot/arch/x86/lib/physmem.c
new file mode 100644 (file)
index 0000000..59b3fe9
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ */
+
+#include <common.h>
+#include <physmem.h>
+#include <linux/compiler.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Large pages are 2MB. */
+#define LARGE_PAGE_SIZE ((1 << 20) * 2)
+
+/*
+ * Paging data structures.
+ */
+
+struct pdpe {
+       uint64_t p:1;
+       uint64_t mbz_0:2;
+       uint64_t pwt:1;
+       uint64_t pcd:1;
+       uint64_t mbz_1:4;
+       uint64_t avl:3;
+       uint64_t base:40;
+       uint64_t mbz_2:12;
+};
+
+typedef struct pdpe pdpt_t[512];
+
+struct pde {
+       uint64_t p:1;      /* present */
+       uint64_t rw:1;     /* read/write */
+       uint64_t us:1;     /* user/supervisor */
+       uint64_t pwt:1;    /* page-level writethrough */
+       uint64_t pcd:1;    /* page-level cache disable */
+       uint64_t a:1;      /* accessed */
+       uint64_t d:1;      /* dirty */
+       uint64_t ps:1;     /* page size */
+       uint64_t g:1;      /* global page */
+       uint64_t avl:3;    /* available to software */
+       uint64_t pat:1;    /* page-attribute table */
+       uint64_t mbz_0:8;  /* must be zero */
+       uint64_t base:31;  /* base address */
+};
+
+typedef struct pde pdt_t[512];
+
+static pdpt_t pdpt __aligned(4096);
+static pdt_t pdts[4] __aligned(4096);
+
+/*
+ * Map a virtual address to a physical address and optionally invalidate any
+ * old mapping.
+ *
+ * @param virt         The virtual address to use.
+ * @param phys         The physical address to use.
+ * @param invlpg       Whether to use invlpg to clear any old mappings.
+ */
+static void x86_phys_map_page(uintptr_t virt, phys_addr_t phys, int invlpg)
+{
+       /* Extract the two bit PDPT index and the 9 bit PDT index. */
+       uintptr_t pdpt_idx = (virt >> 30) & 0x3;
+       uintptr_t pdt_idx = (virt >> 21) & 0x1ff;
+
+       /* Set up a handy pointer to the appropriate PDE. */
+       struct pde *pde = &(pdts[pdpt_idx][pdt_idx]);
+
+       memset(pde, 0, sizeof(struct pde));
+       pde->p = 1;
+       pde->rw = 1;
+       pde->us = 1;
+       pde->ps = 1;
+       pde->base = phys >> 21;
+
+       if (invlpg) {
+               /* Flush any stale mapping out of the TLBs. */
+               __asm__ __volatile__(
+                       "invlpg %0\n\t"
+                       :
+                       : "m" (*(uint8_t *)virt)
+               );
+       }
+}
+
+/* Identity map the lower 4GB and turn on paging with PAE. */
+static void x86_phys_enter_paging(void)
+{
+       phys_addr_t page_addr;
+       unsigned i;
+
+       /* Zero out the page tables. */
+       memset(pdpt, 0, sizeof(pdpt));
+       memset(pdts, 0, sizeof(pdts));
+
+       /* Set up the PDPT. */
+       for (i = 0; i < ARRAY_SIZE(pdts); i++) {
+               pdpt[i].p = 1;
+               pdpt[i].base = ((uintptr_t)&pdts[i]) >> 12;
+       }
+
+       /* Identity map everything up to 4GB. */
+       for (page_addr = 0; page_addr < (1ULL << 32);
+                       page_addr += LARGE_PAGE_SIZE) {
+               /* There's no reason to invalidate the TLB with paging off. */
+               x86_phys_map_page(page_addr, page_addr, 0);
+       }
+
+       /* Turn on paging */
+       __asm__ __volatile__(
+               /* Load the page table address */
+               "movl   %0, %%cr3\n\t"
+               /* Enable pae */
+               "movl   %%cr4, %%eax\n\t"
+               "orl    $0x00000020, %%eax\n\t"
+               "movl   %%eax, %%cr4\n\t"
+               /* Enable paging */
+               "movl   %%cr0, %%eax\n\t"
+               "orl    $0x80000000, %%eax\n\t"
+               "movl   %%eax, %%cr0\n\t"
+               :
+               : "r" (pdpt)
+               : "eax"
+       );
+}
+
+/* Disable paging and PAE mode. */
+static void x86_phys_exit_paging(void)
+{
+       /* Turn off paging */
+       __asm__ __volatile__ (
+               /* Disable paging */
+               "movl   %%cr0, %%eax\n\t"
+               "andl   $0x7fffffff, %%eax\n\t"
+               "movl   %%eax, %%cr0\n\t"
+               /* Disable pae */
+               "movl   %%cr4, %%eax\n\t"
+               "andl   $0xffffffdf, %%eax\n\t"
+               "movl   %%eax, %%cr4\n\t"
+               :
+               :
+               : "eax"
+       );
+}
+
+/*
+ * Set physical memory to a particular value when the whole region fits on one
+ * page.
+ *
+ * @param map_addr     The address that starts the physical page.
+ * @param offset       How far into that page to start setting a value.
+ * @param c            The value to set memory to.
+ * @param size         The size in bytes of the area to set.
+ */
+static void x86_phys_memset_page(phys_addr_t map_addr, uintptr_t offset, int c,
+                                unsigned size)
+{
+       /*
+        * U-Boot should be far away from the beginning of memory, so that's a
+        * good place to map our window on top of.
+        */
+       const uintptr_t window = LARGE_PAGE_SIZE;
+
+       /* Make sure the window is below U-Boot. */
+       assert(window + LARGE_PAGE_SIZE <
+              gd->relocaddr - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_STACK_SIZE);
+       /* Map the page into the window and then memset the appropriate part. */
+       x86_phys_map_page(window, map_addr, 1);
+       memset((void *)(window + offset), c, size);
+}
+
+/*
+ * A physical memory anologue to memset with matching parameters and return
+ * value.
+ */
+phys_addr_t arch_phys_memset(phys_addr_t start, int c, phys_size_t size)
+{
+       const phys_addr_t max_addr = (phys_addr_t)~(uintptr_t)0;
+       const phys_addr_t orig_start = start;
+
+       if (!size)
+               return orig_start;
+
+       /* Handle memory below 4GB. */
+       if (start <= max_addr) {
+               phys_size_t low_size = MIN(max_addr + 1 - start, size);
+               void *start_ptr = (void *)(uintptr_t)start;
+
+               assert(((phys_addr_t)(uintptr_t)start) == start);
+               memset(start_ptr, c, low_size);
+               start += low_size;
+               size -= low_size;
+       }
+
+       /* Use paging and PAE to handle memory above 4GB up to 64GB. */
+       if (size) {
+               phys_addr_t map_addr = start & ~(LARGE_PAGE_SIZE - 1);
+               phys_addr_t offset = start - map_addr;
+
+               x86_phys_enter_paging();
+
+               /* Handle the first partial page. */
+               if (offset) {
+                       phys_addr_t end =
+                               MIN(map_addr + LARGE_PAGE_SIZE, start + size);
+                       phys_size_t cur_size = end - start;
+                       x86_phys_memset_page(map_addr, offset, c, cur_size);
+                       size -= cur_size;
+                       map_addr += LARGE_PAGE_SIZE;
+               }
+               /* Handle the complete pages. */
+               while (size > LARGE_PAGE_SIZE) {
+                       x86_phys_memset_page(map_addr, 0, c, LARGE_PAGE_SIZE);
+                       size -= LARGE_PAGE_SIZE;
+                       map_addr += LARGE_PAGE_SIZE;
+               }
+               /* Handle the last partial page. */
+               if (size)
+                       x86_phys_memset_page(map_addr, 0, c, size);
+
+               x86_phys_exit_paging();
+       }
+       return orig_start;
+}