uboot/arch/x86/lib/physmem.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
   3 * Use of this source code is governed by a BSD-style license that can be
   4 * found in the LICENSE file.
   5 *
   6 * Alternatively, this software may be distributed under the terms of the
   7 * GNU General Public License ("GPL") version 2 as published by the Free
   8 * Software Foundation.
   9 */
  10
  11#include <common.h>
  12#include <log.h>
  13#include <physmem.h>
  14#include <asm/cpu.h>
  15#include <linux/compiler.h>
  16
  17DECLARE_GLOBAL_DATA_PTR;
  18
  19/* Large pages are 2MB. */
  20#define LARGE_PAGE_SIZE ((1 << 20) * 2)
  21
  22/*
  23 * Paging data structures.
  24 */
  25
  26struct pdpe {
  27        uint64_t p:1;
  28        uint64_t mbz_0:2;
  29        uint64_t pwt:1;
  30        uint64_t pcd:1;
  31        uint64_t mbz_1:4;
  32        uint64_t avl:3;
  33        uint64_t base:40;
  34        uint64_t mbz_2:12;
  35};
  36
  37typedef struct pdpe pdpt_t[512];
  38
  39struct pde {
  40        uint64_t p:1;      /* present */
  41        uint64_t rw:1;     /* read/write */
  42        uint64_t us:1;     /* user/supervisor */
  43        uint64_t pwt:1;    /* page-level writethrough */
  44        uint64_t pcd:1;    /* page-level cache disable */
  45        uint64_t a:1;      /* accessed */
  46        uint64_t d:1;      /* dirty */
  47        uint64_t ps:1;     /* page size */
  48        uint64_t g:1;      /* global page */
  49        uint64_t avl:3;    /* available to software */
  50        uint64_t pat:1;    /* page-attribute table */
  51        uint64_t mbz_0:8;  /* must be zero */
  52        uint64_t base:31;  /* base address */
  53};
  54
  55typedef struct pde pdt_t[512];
  56
  57static pdpt_t pdpt __aligned(4096);
  58static pdt_t pdts[4] __aligned(4096);
  59
  60/*
  61 * Map a virtual address to a physical address and optionally invalidate any
  62 * old mapping.
  63 *
  64 * @param virt          The virtual address to use.
  65 * @param phys          The physical address to use.
  66 * @param invlpg        Whether to use invlpg to clear any old mappings.
  67 */
  68static void x86_phys_map_page(uintptr_t virt, phys_addr_t phys, int invlpg)
  69{
  70        /* Extract the two bit PDPT index and the 9 bit PDT index. */
  71        uintptr_t pdpt_idx = (virt >> 30) & 0x3;
  72        uintptr_t pdt_idx = (virt >> 21) & 0x1ff;
  73
  74        /* Set up a handy pointer to the appropriate PDE. */
  75        struct pde *pde = &(pdts[pdpt_idx][pdt_idx]);
  76
  77        memset(pde, 0, sizeof(struct pde));
  78        pde->p = 1;
  79        pde->rw = 1;
  80        pde->us = 1;
  81        pde->ps = 1;
  82        pde->base = phys >> 21;
  83
  84        if (invlpg) {
  85                /* Flush any stale mapping out of the TLBs. */
  86                __asm__ __volatile__(
  87                        "invlpg %0\n\t"
  88                        :
  89                        : "m" (*(uint8_t *)virt)
  90                );
  91        }
  92}
  93
  94/* Identity map the lower 4GB and turn on paging with PAE. */
  95static void x86_phys_enter_paging(void)
  96{
  97        phys_addr_t page_addr;
  98        unsigned i;
  99
 100        /* Zero out the page tables. */
 101        memset(pdpt, 0, sizeof(pdpt));
 102        memset(pdts, 0, sizeof(pdts));
 103
 104        /* Set up the PDPT. */
 105        for (i = 0; i < ARRAY_SIZE(pdts); i++) {
 106                pdpt[i].p = 1;
 107                pdpt[i].base = ((uintptr_t)&pdts[i]) >> 12;
 108        }
 109
 110        /* Identity map everything up to 4GB. */
 111        for (page_addr = 0; page_addr < (1ULL << 32);
 112                        page_addr += LARGE_PAGE_SIZE) {
 113                /* There's no reason to invalidate the TLB with paging off. */
 114                x86_phys_map_page(page_addr, page_addr, 0);
 115        }
 116
 117        cpu_enable_paging_pae((ulong)pdpt);
 118}
 119
 120/* Disable paging and PAE mode. */
 121static void x86_phys_exit_paging(void)
 122{
 123        cpu_disable_paging_pae();
 124}
 125
 126/*
 127 * Set physical memory to a particular value when the whole region fits on one
 128 * page.
 129 *
 130 * @param map_addr      The address that starts the physical page.
 131 * @param offset        How far into that page to start setting a value.
 132 * @param c             The value to set memory to.
 133 * @param size          The size in bytes of the area to set.
 134 */
 135static void x86_phys_memset_page(phys_addr_t map_addr, uintptr_t offset, int c,
 136                                 unsigned size)
 137{
 138        /*
 139         * U-Boot should be far away from the beginning of memory, so that's a
 140         * good place to map our window on top of.
 141         */
 142        const uintptr_t window = LARGE_PAGE_SIZE;
 143
 144        /* Make sure the window is below U-Boot. */
 145        assert(window + LARGE_PAGE_SIZE <
 146               gd->relocaddr - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_STACK_SIZE);
 147        /* Map the page into the window and then memset the appropriate part. */
 148        x86_phys_map_page(window, map_addr, 1);
 149        memset((void *)(window + offset), c, size);
 150}
 151
 152/*
 153 * A physical memory anologue to memset with matching parameters and return
 154 * value.
 155 */
 156phys_addr_t arch_phys_memset(phys_addr_t start, int c, phys_size_t size)
 157{
 158        const phys_addr_t max_addr = (phys_addr_t)~(uintptr_t)0;
 159        const phys_addr_t orig_start = start;
 160
 161        if (!size)
 162                return orig_start;
 163
 164        /* Handle memory below 4GB. */
 165        if (start <= max_addr) {
 166                phys_size_t low_size = min(max_addr + 1 - start, size);
 167                void *start_ptr = (void *)(uintptr_t)start;
 168
 169                assert(((phys_addr_t)(uintptr_t)start) == start);
 170                memset(start_ptr, c, low_size);
 171                start += low_size;
 172                size -= low_size;
 173        }
 174
 175        /* Use paging and PAE to handle memory above 4GB up to 64GB. */
 176        if (size) {
 177                phys_addr_t map_addr = start & ~(LARGE_PAGE_SIZE - 1);
 178                phys_addr_t offset = start - map_addr;
 179
 180                x86_phys_enter_paging();
 181
 182                /* Handle the first partial page. */
 183                if (offset) {
 184                        phys_addr_t end =
 185                                min(map_addr + LARGE_PAGE_SIZE, start + size);
 186                        phys_size_t cur_size = end - start;
 187                        x86_phys_memset_page(map_addr, offset, c, cur_size);
 188                        size -= cur_size;
 189                        map_addr += LARGE_PAGE_SIZE;
 190                }
 191                /* Handle the complete pages. */
 192                while (size > LARGE_PAGE_SIZE) {
 193                        x86_phys_memset_page(map_addr, 0, c, LARGE_PAGE_SIZE);
 194                        size -= LARGE_PAGE_SIZE;
 195                        map_addr += LARGE_PAGE_SIZE;
 196                }
 197                /* Handle the last partial page. */
 198                if (size)
 199                        x86_phys_memset_page(map_addr, 0, c, size);
 200
 201                x86_phys_exit_paging();
 202        }
 203        return orig_start;
 204}
 205