[BACK]Return to mmu.c CVS log [TXT][DIR] Up to [local] / prex-old / sys / arch / i386 / i386

File: [local] / prex-old / sys / arch / i386 / i386 / mmu.c (download)

Revision 1.1, Tue Jun 3 09:38:45 2008 UTC (16 years ago) by nbrk
Branch point for: MAIN

Initial revision

/*-
 * Copyright (c) 2005, Kohsuke Ohtani
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * mmu.c - memory management unit support routines
 */

/*
 * This module provides virtual/physical address translation for
 * intel x86 MMU. This kernel will do only page level translation
 * and protection and it does not use x86 segment mechanism.
 */

#include <kernel.h>
#include <page.h>
#include <cpu.h>

/*
 * Map physical memory range into virtual address
 *
 * Returns 0 on success, or -1 on failure.
 *
 * Map type can be one of the following type.
 *   PG_UNMAP - Remove mapping
 *   PG_READ  - Read only mapping
 *   PG_WRITE - Read/write allowed
 *
 * Setup the appropriate page tables for mapping. If there is no
 * page table for the specified address, new page table is allocated.
 *
 * This routine does not return any error even if the specified
 * address has been already mapped to other physical address.
 * In this case, it will just override the existing mapping.
 *
 * In order to unmap the page, pg_type is specified as 0.
 * But, the page tables are not released even if there is no valid
 * page entry in it. All page tables are released when mmu_delmap()
 * is called when task is terminated.
 *
 * TODO: TLB should be flushed for specific page by invalpg in case of i486.
 */
int
mmu_map(pgd_t pgd, void *phys, void *virt, size_t size, int type)
{
	long pg_type;
	page_table_t pte;
	void *pg;	/* page */
	u_long va, pa;

	pa = PAGE_ALIGN(phys);
	va = PAGE_ALIGN(virt);
	size = PAGE_TRUNC(size);

	/* Build page type */
	pg_type = 0;
	switch (type) {
	case PG_UNMAP:
		break;
	case PG_READ:
		pg_type = PTE_USER | PTE_PRESENT;
		break;
	case PG_WRITE:
		pg_type = PTE_USER | PTE_WRITE | PTE_PRESENT;
		break;
	}
	/* Map all pages */
	while (size > 0) {
		if (pte_present(pgd, va)) {
			/* Page table already exists for the address */
			pte = pgd_to_pte(pgd, va);
		} else {
			ASSERT(pg_type != 0);
			if ((pg = page_alloc(PAGE_SIZE)) == NULL) {
				printk("Error: MMU mapping failed\n");
				return -1;
			}
			pgd[PAGE_DIR(va)] =
			    (u_long)pg | PDE_PRESENT | PDE_WRITE | PDE_USER;
			pte = phys_to_virt(pg);
			memset(pte, 0, PAGE_SIZE);
		}
		/* Set new entry into page table */
		pte[PAGE_TABLE(va)] = pa | pg_type;

		/* Process next page */
		pa += PAGE_SIZE;
		va += PAGE_SIZE;
		size -= PAGE_SIZE;
	}
	flush_tlb();
	return 0;
}

/*
 * Create new page map.
 * Returns a page directory on success, or NULL on failure.
 * This routine is called when new task is created. All page
 * map must have the same kernel page table in it. So, the kernel
 * page tables are copied to newly created map.
 */
pgd_t
mmu_newmap(void)
{
	void *pg;
	pgd_t pgd, kern_pgd;
	u_long i;

	/* Allocate page directory */
	if ((pg = page_alloc(PAGE_SIZE)) == NULL)
		return NULL;
	pgd = phys_to_virt(pg);
	memset(pgd, 0, PAGE_SIZE);

	/* Copy kernel page tables */
	kern_pgd = phys_to_virt(KERNEL_PGD);
	i = PAGE_DIR(PAGE_OFFSET);
	memcpy(&pgd[i], &kern_pgd[i], 1024 - i);
	return pgd;
}

/*
 * Delete all page map.
 */
void
mmu_delmap(pgd_t pgd)
{
	u_long i;
	page_table_t pte;

	flush_tlb();

	/* Release all user page table */
	for (i = 0; i < PAGE_DIR(PAGE_OFFSET); i++) {
		pte = (page_table_t)pgd[i];
		if (pte != 0)
			page_free((void *)((u_long)pte & PTE_ADDRESS),
				  PAGE_SIZE);
	}
	/* Release page directory */
	page_free(virt_to_phys(pgd), PAGE_SIZE);
}

/*
 * Switch to new page directory
 *
 * This is called when context is switched.
 * Whole TLB are flushed automatically by loading
 * CR3 register.
 */
void
mmu_switch(pgd_t pgd)
{
	u_long phys = (u_long)virt_to_phys(pgd);

	if (phys != get_cr3())
		set_cr3(phys);
}

/*
 * Returns the physical address for the specified virtual address.
 * This routine checks if the virtual area actually exist.
 * It returns NULL if at least one page is not mapped.
 */
void *
mmu_extract(pgd_t pgd, void *virt, size_t size)
{
	page_table_t pte;
	u_long start, end, pg;

	start = PAGE_TRUNC(virt);
	end = PAGE_TRUNC((u_long)virt + size - 1);

	/* Check all pages exist */
	for (pg = start; pg <= end; pg += PAGE_SIZE) {
		if (!pte_present(pgd, pg))
			return NULL;
		pte = pgd_to_pte(pgd, pg);
		if (!page_present(pte, pg))
			return NULL;
	}

	/* Get physical address */
	pte = pgd_to_pte(pgd, start);
	pg = pte_to_page(pte, start);
	return (void *)(pg + ((u_long)virt - start));
}

/*
 * Initialize mmu
 *
 * Paging is already enabled in locore.S. And, physical address
 * 0-4M has been already mapped into kernel space in locore.S.
 * Now, all physical memory is mapped into kernel virtual address
 * as straight 1:1 mapping. User mode access is not allowed for
 * these kernel pages.
 * page_init() must be called before calling this routine.
 *
 * Note: This routine requires 4K bytes to map 4M bytes memory. So,
 * if the system has a lot of RAM, the "used memory" by kernel will
 * become large, too. For example, page table requires 512K bytes
 * for 512M bytes system RAM.
 */
void
mmu_init(void)
{
	pgd_t kern_pgd;
	int npages, nptes;
	u_long pte_entry, pgd_index, *pte;
	int i, j;
	void *pg;

	kern_pgd = phys_to_virt(KERNEL_PGD);
	npages = boot_info->main_mem.size / PAGE_SIZE;
	nptes = (npages + 1023) / 1024;
	pgd_index = PAGE_DIR(PAGE_OFFSET);
	pte_entry = 0 | PTE_PRESENT | PTE_WRITE;

	/*
	 * Build kernel page tables for whole physical pages.
	 */
	for (i = 0; i < nptes; i++) {
		/* Allocate new page table */
		if ((pg = page_alloc(PAGE_SIZE)) == NULL)
			panic("mmu_init: out of memory");
		pte = phys_to_virt(pg);
		memset(pte, 0, PAGE_SIZE);

		/* Fill all entries in this page table */
		for (j = 0; j < 1024; j++) {
			pte[j] = pte_entry;
			pte_entry += PAGE_SIZE;
			if (--npages <= 0)
				break;
		}
		/* Set the page table address into page directory. */
		kern_pgd[pgd_index] = (u_long)pg | PDE_PRESENT | PDE_WRITE;
		pgd_index++;
	}
	/* Unmap address 0 for NULL pointer detection in kernel mode */
	pte = phys_to_virt(kern_pgd[PAGE_DIR(PAGE_OFFSET)] & PDE_ADDRESS);
	pte[0] = 0;

	/* Flush translation look-aside buffer */
	flush_tlb();
}