diff options
| author | Tom Rini <trini@ti.com> | 2012-12-07 06:43:40 -0700 | 
|---|---|---|
| committer | Tom Rini <trini@ti.com> | 2012-12-07 08:47:59 -0700 | 
| commit | fd4d564b3c80b111f18c93adb14233a6a7ddb0e9 (patch) | |
| tree | a42d63aae4f7c07f441321c18098a85cbcc45dee /arch/x86/lib/physmem.c | |
| parent | 13d43555a9154cf12255023c47e80d947d7d0604 (diff) | |
| parent | ac426b7290e3a96c97fbc093f15cd0660e0edaf2 (diff) | |
| download | olio-uboot-2014.01-fd4d564b3c80b111f18c93adb14233a6a7ddb0e9.tar.xz olio-uboot-2014.01-fd4d564b3c80b111f18c93adb14233a6a7ddb0e9.zip | |
Merge branch 'master' of git://git.denx.de/u-boot-x86
Diffstat (limited to 'arch/x86/lib/physmem.c')
| -rw-r--r-- | arch/x86/lib/physmem.c | 228 | 
1 files changed, 228 insertions, 0 deletions
| diff --git a/arch/x86/lib/physmem.c b/arch/x86/lib/physmem.c new file mode 100644 index 000000000..18f0e62ac --- /dev/null +++ b/arch/x86/lib/physmem.c @@ -0,0 +1,228 @@ +/* + * 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> + +/* 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; +} |