⛏️ index : haiku.git

/*
 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */


#include "paging/pae/X86VMTranslationMapPAE.h"

#include <interrupts.h>
#include <slab/Slab.h>
#include <thread.h>
#include <tracing.h>
#include <util/AutoLock.h>
#include <util/ThreadAutoLock.h>
#include <vm/vm_page.h>
#include <vm/VMAddressSpace.h>
#include <vm/VMCache.h>

#include "paging/pae/X86PagingMethodPAE.h"
#include "paging/pae/X86PagingStructuresPAE.h"
#include "paging/x86_physical_page_mapper.h"


//#define TRACE_X86_VM_TRANSLATION_MAP_PAE
#ifdef TRACE_X86_VM_TRANSLATION_MAP_PAE
#	define TRACE(x...) dprintf(x)
#else
#	define TRACE(x...) ;
#endif


#if B_HAIKU_PHYSICAL_BITS == 64


#if TRANSLATION_MAP_TRACING


namespace TranslationMapTracing {


class TranslationMapTraceEntryBase
	: public TRACE_ENTRY_SELECTOR(TRANSLATION_MAP_TRACING_STACK_TRACE) {
public:
	TranslationMapTraceEntryBase()
		:
		TraceEntryBase(TRANSLATION_MAP_TRACING_STACK_TRACE, 0, true)
	{
	}

	void PrintPageTableEntry(TraceOutput& out, pae_page_table_entry entry)
	{
		out.Print("%#" B_PRIx64  " %c%c%c%c%c %s %s %c%c",
			entry & X86_PAE_PTE_ADDRESS_MASK,
			(entry & X86_PAE_PTE_PRESENT) != 0 ? 'P' : '-',
			(entry & X86_PAE_PTE_WRITABLE) != 0 ? 'W' : '-',
			(entry & X86_PAE_PTE_USER) != 0 ? 'U' : '-',
			(entry & X86_PAE_PTE_NOT_EXECUTABLE) != 0 ? '-' : 'X',
			(entry & X86_PAE_PTE_GLOBAL) != 0 ? 'G' : '-',
			(entry & X86_PAE_PTE_WRITE_THROUGH) != 0 ? "WT" : "--",
			(entry & X86_PAE_PTE_CACHING_DISABLED) != 0 ? "UC" : "--",
			(entry & X86_PAE_PTE_ACCESSED) != 0 ? 'A' : '-',
			(entry & X86_PAE_PTE_DIRTY) != 0 ? 'D' : '-');
	}
};


class Map : public TranslationMapTraceEntryBase {
public:
	Map(X86VMTranslationMapPAE* map, addr_t virtualAddress,
		pae_page_table_entry entry)
		:
		TranslationMapTraceEntryBase(),
		fMap(map),
		fVirtualAddress(virtualAddress),
		fEntry(entry)
	{
		Initialized();
	}

	virtual void AddDump(TraceOutput& out)
	{
		out.Print("translation map map: %p: %#" B_PRIxADDR " -> ", fMap,
			fVirtualAddress);
		PrintPageTableEntry(out, fEntry);
	}

private:
	X86VMTranslationMapPAE*	fMap;
	addr_t					fVirtualAddress;
	pae_page_table_entry	fEntry;
};


class Unmap : public TranslationMapTraceEntryBase {
public:
	Unmap(X86VMTranslationMapPAE* map, addr_t virtualAddress,
		pae_page_table_entry entry)
		:
		TranslationMapTraceEntryBase(),
		fMap(map),
		fVirtualAddress(virtualAddress),
		fEntry(entry)
	{
		Initialized();
	}

	virtual void AddDump(TraceOutput& out)
	{
		out.Print("translation map unmap: %p: %#" B_PRIxADDR
			" -> ", fMap, fVirtualAddress);
		PrintPageTableEntry(out, fEntry);
	}

private:
	X86VMTranslationMapPAE*	fMap;
	addr_t					fVirtualAddress;
	pae_page_table_entry	fEntry;
};


class Protect : public TranslationMapTraceEntryBase {
public:
	Protect(X86VMTranslationMapPAE* map, addr_t virtualAddress,
		pae_page_table_entry oldEntry, pae_page_table_entry newEntry)
		:
		TranslationMapTraceEntryBase(),
		fMap(map),
		fVirtualAddress(virtualAddress),
		fOldEntry(oldEntry),
		fNewEntry(newEntry)
	{
		Initialized();
	}

	virtual void AddDump(TraceOutput& out)
	{
		out.Print("translation map protect: %p: %#" B_PRIxADDR
			" -> ", fMap, fVirtualAddress);
		PrintPageTableEntry(out, fNewEntry);
		out.Print(" (%c%c%c)",
			(fOldEntry & X86_PAE_PTE_WRITABLE) != 0 ? 'W' : '-',
			(fOldEntry & X86_PAE_PTE_USER) != 0 ? 'U' : '-',
			(fOldEntry & X86_PAE_PTE_NOT_EXECUTABLE) != 0 ? '-' : 'X');
	}

private:
	X86VMTranslationMapPAE*	fMap;
	addr_t					fVirtualAddress;
	pae_page_table_entry	fOldEntry;
	pae_page_table_entry	fNewEntry;
};


class ClearFlags : public TranslationMapTraceEntryBase {
public:
	ClearFlags(X86VMTranslationMapPAE* map, addr_t virtualAddress,
		pae_page_table_entry oldEntry, pae_page_table_entry flagsCleared)
		:
		TranslationMapTraceEntryBase(),
		fMap(map),
		fVirtualAddress(virtualAddress),
		fOldEntry(oldEntry),
		fFlagsCleared(flagsCleared)
	{
		Initialized();
	}

	virtual void AddDump(TraceOutput& out)
	{
		out.Print("translation map clear flags: %p: %#" B_PRIxADDR
			" -> ", fMap, fVirtualAddress);
		PrintPageTableEntry(out, fOldEntry & ~fFlagsCleared);
		out.Print(", cleared %c%c (%c%c)",
			(fOldEntry & fFlagsCleared & X86_PAE_PTE_ACCESSED) != 0 ? 'A' : '-',
			(fOldEntry & fFlagsCleared & X86_PAE_PTE_DIRTY) != 0 ? 'D' : '-',
			(fFlagsCleared & X86_PAE_PTE_ACCESSED) != 0 ? 'A' : '-',
			(fFlagsCleared & X86_PAE_PTE_DIRTY) != 0 ? 'D' : '-');
	}

private:
	X86VMTranslationMapPAE*	fMap;
	addr_t					fVirtualAddress;
	pae_page_table_entry	fOldEntry;
	pae_page_table_entry	fFlagsCleared;
};


class ClearFlagsUnmap : public TranslationMapTraceEntryBase {
public:
	ClearFlagsUnmap(X86VMTranslationMapPAE* map, addr_t virtualAddress,
		pae_page_table_entry entry)
		:
		TranslationMapTraceEntryBase(),
		fMap(map),
		fVirtualAddress(virtualAddress),
		fEntry(entry)
	{
		Initialized();
	}

	virtual void AddDump(TraceOutput& out)
	{
		out.Print("translation map clear flags unmap: %p: %#" B_PRIxADDR
			" -> ", fMap, fVirtualAddress);
		PrintPageTableEntry(out, fEntry);
	}

private:
	X86VMTranslationMapPAE*	fMap;
	addr_t					fVirtualAddress;
	pae_page_table_entry	fEntry;
};


}	// namespace TranslationMapTracing

#	define T(x)	new(std::nothrow) TranslationMapTracing::x

#else
#	define T(x)
#endif	// TRANSLATION_MAP_TRACING



X86VMTranslationMapPAE::X86VMTranslationMapPAE()
	:
	fPagingStructures(NULL)
{
}


X86VMTranslationMapPAE::~X86VMTranslationMapPAE()
{
	if (fPagingStructures == NULL)
		return;

	if (fPageMapper != NULL)
		fPageMapper->Delete();

	// cycle through and free all of the user space page tables

	STATIC_ASSERT(KERNEL_BASE == 0x80000000 && KERNEL_SIZE == 0x80000000);
		// assuming 1-1 split of the address space

	vm_page_reservation reservation = {};
	for (uint32 k = 0; k < 2; k++) {
		pae_page_directory_entry* pageDir
			= fPagingStructures->VirtualPageDirs()[k];
		if (pageDir == NULL)
			continue;

		for (uint32 i = 0; i < kPAEPageDirEntryCount; i++) {
			if ((pageDir[i] & X86_PAE_PDE_PRESENT) != 0) {
				phys_addr_t address = pageDir[i] & X86_PAE_PDE_ADDRESS_MASK;
				vm_page* page = vm_lookup_page(address / B_PAGE_SIZE);
				if (page == NULL)
					panic("X86VMTranslationMapPAE::~X86VMTranslationMapPAE: "
						"didn't find page table page: page address: %#"
						B_PRIxPHYSADDR ", virtual base: %#" B_PRIxADDR "\n",
						address,
						(k * kPAEPageDirEntryCount + i) * kPAEPageTableRange);
				DEBUG_PAGE_ACCESS_START(page);
				vm_page_free_etc(NULL, page, &reservation);
			}
		}
	}
	vm_page_unreserve_pages(&reservation);

	fPagingStructures->RemoveReference();
}


status_t
X86VMTranslationMapPAE::Init(bool kernel)
{
	TRACE("X86VMTranslationMapPAE::Init()\n");

	X86VMTranslationMap::Init(kernel);

	fPagingStructures = new(std::nothrow) X86PagingStructuresPAE;
	if (fPagingStructures == NULL)
		return B_NO_MEMORY;

	X86PagingMethodPAE* method = X86PagingMethodPAE::Method();

	if (kernel) {
		// kernel
		// get the physical page mapper
		fPageMapper = method->KernelPhysicalPageMapper();

		// we already know the kernel pgdir mapping
		fPagingStructures->Init(method->KernelVirtualPageDirPointerTable(),
			method->KernelPhysicalPageDirPointerTable(), NULL,
			method->KernelVirtualPageDirs(), method->KernelPhysicalPageDirs());
	} else {
		// user
		// allocate a physical page mapper
		status_t error = method->PhysicalPageMapper()
			->CreateTranslationMapPhysicalPageMapper(&fPageMapper);
		if (error != B_OK)
			return error;

		// The following code assumes that the kernel address space occupies the
		// upper half of the virtual address space. This simplifies things a
		// lot, since it allows us to just use the upper two page directories
		// of the kernel and create two new lower page directories for the
		// userland.
		STATIC_ASSERT(KERNEL_BASE == 0x80000000 && KERNEL_SIZE == 0x80000000);

		// allocate the page directories (both at once)
		pae_page_directory_entry* virtualPageDirs[4];
		phys_addr_t physicalPageDirs[4];
		virtualPageDirs[0] = (pae_page_directory_entry*)memalign(B_PAGE_SIZE,
			2 * B_PAGE_SIZE);
		if (virtualPageDirs[0] == NULL)
			return B_NO_MEMORY;
		virtualPageDirs[1] = virtualPageDirs[0] + kPAEPageTableEntryCount;

		// clear the userland page directories
		memset(virtualPageDirs[0], 0, 2 * B_PAGE_SIZE);

		// use the upper two kernel page directories
		for (int32 i = 2; i < 4; i++) {
			virtualPageDirs[i] = method->KernelVirtualPageDirs()[i];
			physicalPageDirs[i] = method->KernelPhysicalPageDirs()[i];
		}

		// look up the page directories' physical addresses
		for (int32 i = 0; i < 2; i++) {
			vm_get_page_mapping(VMAddressSpace::KernelID(),
				(addr_t)virtualPageDirs[i], &physicalPageDirs[i]);
		}

		// allocate the PDPT -- needs to have a 32 bit physical address
		phys_addr_t physicalPDPT;
		void* pdptHandle;
		pae_page_directory_pointer_table_entry* pdpt
			= (pae_page_directory_pointer_table_entry*)
				method->Allocate32BitPage(physicalPDPT, pdptHandle);
		if (pdpt == NULL) {
			free(virtualPageDirs[0]);
			return B_NO_MEMORY;
		}

		// init the PDPT entries
		for (int32 i = 0; i < 4; i++) {
			pdpt[i] = (physicalPageDirs[i] & X86_PAE_PDPTE_ADDRESS_MASK)
				| X86_PAE_PDPTE_PRESENT;
		}

		// init the paging structures
		fPagingStructures->Init(pdpt, physicalPDPT, pdptHandle, virtualPageDirs,
			physicalPageDirs);
	}

	return B_OK;
}


size_t
X86VMTranslationMapPAE::MaxPagesNeededToMap(addr_t start, addr_t end) const
{
	// If start == 0, the actual base address is not yet known to the caller and
	// we shall assume the worst case.
	if (start == 0) {
		// offset the range so it has the worst possible alignment
		start = kPAEPageTableRange - B_PAGE_SIZE;
		end += kPAEPageTableRange - B_PAGE_SIZE;
	}

	return end / kPAEPageTableRange + 1 - start / kPAEPageTableRange;
}


status_t
X86VMTranslationMapPAE::Map(addr_t virtualAddress, phys_addr_t physicalAddress,
	uint32 attributes, uint32 memoryType, vm_page_reservation* reservation)
{
	TRACE("X86VMTranslationMapPAE::Map(): %#" B_PRIxADDR " -> %#" B_PRIxPHYSADDR
		"\n", virtualAddress, physicalAddress);

	// check to see if a page table exists for this range
	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), virtualAddress);
	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
		// we need to allocate a page table
		vm_page *page = vm_page_allocate_page(reservation,
			PAGE_STATE_WIRED | VM_PAGE_ALLOC_CLEAR);

		DEBUG_PAGE_ACCESS_END(page);

		phys_addr_t physicalPageTable
			= (phys_addr_t)page->physical_page_number * B_PAGE_SIZE;

		TRACE("X86VMTranslationMapPAE::Map(): asked for free page for "
			"page table: %#" B_PRIxPHYSADDR "\n", physicalPageTable);

		// put it in the page dir
		X86PagingMethodPAE::PutPageTableInPageDir(pageDirEntry,
			physicalPageTable,
			attributes
				| ((attributes & B_USER_PROTECTION) != 0
						? B_WRITE_AREA : B_KERNEL_WRITE_AREA));

		fMapCount++;
	}

	// now, fill in the page table entry
	Thread* thread = thread_get_current_thread();
	ThreadCPUPinner pinner(thread);

	pae_page_table_entry* pageTable
		= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
			*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
	pae_page_table_entry* entry = pageTable
		+ virtualAddress / B_PAGE_SIZE % kPAEPageTableEntryCount;

	ASSERT_PRINT((*entry & X86_PAE_PTE_PRESENT) == 0,
		"virtual address: %#" B_PRIxADDR ", existing pte: %#" B_PRIx64 " @ %p",
		virtualAddress, *entry, entry);

	X86PagingMethodPAE::PutPageTableEntryInTable(entry, physicalAddress,
		attributes, memoryType, fIsKernelMap);

	T(Map(this, virtualAddress, *entry));

	pinner.Unlock();

	// Note: We don't need to invalidate the TLB for this address, as previously
	// the entry was not present and the TLB doesn't cache those entries.

	fMapCount++;

	return 0;
}


status_t
X86VMTranslationMapPAE::Unmap(addr_t start, addr_t end)
{
	start = ROUNDDOWN(start, B_PAGE_SIZE);
	if (start >= end)
		return B_OK;

	TRACE("X86VMTranslationMapPAE::Unmap(): %#" B_PRIxADDR " - %#" B_PRIxADDR
		"\n", start, end);

	do {
		pae_page_directory_entry* pageDirEntry
			= X86PagingMethodPAE::PageDirEntryForAddress(
				fPagingStructures->VirtualPageDirs(), start);
		if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
			// no page table here, move the start up to access the next page
			// table
			start = ROUNDUP(start + 1, kPAEPageTableRange);
			continue;
		}

		Thread* thread = thread_get_current_thread();
		ThreadCPUPinner pinner(thread);

		pae_page_table_entry* pageTable
			= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);

		uint32 index = start / B_PAGE_SIZE % kPAEPageTableEntryCount;
		for (; index < kPAEPageTableEntryCount && start < end;
				index++, start += B_PAGE_SIZE) {
			if ((pageTable[index] & X86_PAE_PTE_PRESENT) == 0) {
				// page mapping not valid
				continue;
			}

			TRACE("X86VMTranslationMapPAE::Unmap(): removing page %#"
				B_PRIxADDR "\n", start);

			pae_page_table_entry oldEntry
				= X86PagingMethodPAE::ClearTableEntry(&pageTable[index]);

			T(Unmap(this, start, oldEntry));

			fMapCount--;

			if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0) {
				// Note, that we only need to invalidate the address, if the
				// accessed flags was set, since only then the entry could have
				// been in any TLB.
				InvalidatePage(start);
			}
		}
	} while (start != 0 && start < end);

	return B_OK;
}


status_t
X86VMTranslationMapPAE::UnmapPage(VMArea* area, addr_t address,
	bool updatePageQueue, bool deletingAddressSpace, uint32* _flags)
{
	ASSERT(address % B_PAGE_SIZE == 0);
	ASSERT(_flags == NULL || !updatePageQueue);

	TRACE("X86VMTranslationMapPAE::UnmapPage(%#" B_PRIxADDR ")\n", address);

	ThreadCPUPinner pinner(thread_get_current_thread());

	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), address);

	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0)
		return B_ENTRY_NOT_FOUND;

	RecursiveLocker locker(fLock);

	pae_page_table_entry* pageTable
		= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
			*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);

	pae_page_table_entry oldEntry = X86PagingMethodPAE::ClearTableEntry(
		&pageTable[address / B_PAGE_SIZE % kPAEPageTableEntryCount]);

	T(Unmap(this, address, oldEntry));

	pinner.Unlock();

	if ((oldEntry & X86_PAE_PTE_PRESENT) == 0) {
		// page mapping not valid
		return B_ENTRY_NOT_FOUND;
	}

	fMapCount--;

	if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0) {
		// Note, that we only need to invalidate the address, if the
		// accessed flags was set, since only then the entry could have been
		// in any TLB.
		if (!deletingAddressSpace)
			InvalidatePage(address);

		if (_flags == NULL) {
			Flush();
				// flush explicitly, since we directly use the lock
		}

		// NOTE: Between clearing the page table entry and Flush() other
		// processors (actually even this processor with another thread of the
		// same team) could still access the page in question via their cached
		// entry. We can obviously lose a modified flag in this case, with the
		// effect that the page looks unmodified (and might thus be recycled),
		// but is actually modified.
		// In most cases this is harmless, but for vm_remove_all_page_mappings()
		// this is actually a problem.
		// Interestingly FreeBSD seems to ignore this problem as well
		// (cf. pmap_remove_all()), unless I've missed something.
	}

	if (_flags == NULL) {
		locker.Detach();
			// PageUnmapped() will unlock for us

		PageUnmapped(area, (oldEntry & X86_PAE_PTE_ADDRESS_MASK) / B_PAGE_SIZE,
			(oldEntry & X86_PAE_PTE_ACCESSED) != 0,
			(oldEntry & X86_PAE_PTE_DIRTY) != 0, updatePageQueue);
	} else {
		uint32 flags = PAGE_PRESENT;
		if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0)
			flags |= PAGE_ACCESSED;
		if ((oldEntry & X86_PAE_PTE_DIRTY) != 0)
			flags |= PAGE_MODIFIED;
		*_flags = flags;
	}

	return B_OK;
}


void
X86VMTranslationMapPAE::UnmapPages(VMArea* area, addr_t base, size_t size,
	bool updatePageQueue, bool deletingAddressSpace)
{
	if (size == 0)
		return;

	addr_t start = base;
	addr_t end = base + size - 1;

	TRACE("X86VMTranslationMapPAE::UnmapPages(%p, %#" B_PRIxADDR ", %#"
		B_PRIxADDR ")\n", area, start, end);

	VMAreaMappings queue;

	RecursiveLocker locker(fLock);

	do {
		pae_page_directory_entry* pageDirEntry
			= X86PagingMethodPAE::PageDirEntryForAddress(
				fPagingStructures->VirtualPageDirs(), start);
		if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
			// no page table here, move the start up to access the next page
			// table
			start = ROUNDUP(start + 1, kPAEPageTableRange);
			continue;
		}

		Thread* thread = thread_get_current_thread();
		ThreadCPUPinner pinner(thread);

		pae_page_table_entry* pageTable
			= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);

		uint32 index = start / B_PAGE_SIZE % kPAEPageTableEntryCount;
		for (; index < kPAEPageTableEntryCount && start < end;
				index++, start += B_PAGE_SIZE) {
			pae_page_table_entry oldEntry
				= X86PagingMethodPAE::ClearTableEntry(&pageTable[index]);
			if ((oldEntry & X86_PAE_PTE_PRESENT) == 0)
				continue;

			T(Unmap(this, start, oldEntry));

			fMapCount--;

			if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0) {
				// Note, that we only need to invalidate the address, if the
				// accessed flags was set, since only then the entry could have
				// been in any TLB.
				if (!deletingAddressSpace)
					InvalidatePage(start);
			}

			if (area->cache_type != CACHE_TYPE_DEVICE) {
				page_num_t page = (oldEntry & X86_PAE_PTE_ADDRESS_MASK) / B_PAGE_SIZE;
				PageUnmapped(area, page,
					(oldEntry & X86_PAE_PTE_ACCESSED) != 0,
					(oldEntry & X86_PAE_PTE_DIRTY) != 0,
					updatePageQueue, &queue);
			}
		}

		Flush();
			// flush explicitly, since we directly use the lock
	} while (start != 0 && start < end);

	// TODO: As in UnmapPage() we can lose page dirty flags here. ATM it's not
	// really critical here, as in all cases this method is used, the unmapped
	// area range is unmapped for good (resized/cut) and the pages will likely
	// be freed.

	locker.Unlock();

	// free removed mappings
	bool isKernelSpace = area->address_space == VMAddressSpace::Kernel();
	uint32 freeFlags = CACHE_DONT_WAIT_FOR_MEMORY
		| (isKernelSpace ? CACHE_DONT_LOCK_KERNEL_SPACE : 0);
	while (vm_page_mapping* mapping = queue.RemoveHead())
		vm_free_page_mapping(mapping->page->physical_page_number, mapping, freeFlags);
}


status_t
X86VMTranslationMapPAE::Query(addr_t virtualAddress,
	phys_addr_t* _physicalAddress, uint32* _flags)
{
	// default the flags to not present
	*_flags = 0;
	*_physicalAddress = 0;

	// get the page directory entry
	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), virtualAddress);
	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
		// no pagetable here
		return B_OK;
	}

	// get the page table entry
	Thread* thread = thread_get_current_thread();
	ThreadCPUPinner pinner(thread);

	pae_page_table_entry* pageTable
		= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
			*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
	pae_page_table_entry entry
		= pageTable[virtualAddress / B_PAGE_SIZE % kPAEPageTableEntryCount];

	pinner.Unlock();

	*_physicalAddress = entry & X86_PAE_PTE_ADDRESS_MASK;

	// translate the page state flags
	if ((entry & X86_PAE_PTE_USER) != 0) {
		*_flags |= ((entry & X86_PAE_PTE_WRITABLE) != 0 ? B_WRITE_AREA : 0)
			| B_READ_AREA
			| ((entry & X86_PAE_PTE_NOT_EXECUTABLE) == 0 ? B_EXECUTE_AREA : 0);
	}

	*_flags |= ((entry & X86_PAE_PTE_WRITABLE) != 0 ? B_KERNEL_WRITE_AREA : 0)
		| B_KERNEL_READ_AREA
		| ((entry & X86_PAE_PTE_NOT_EXECUTABLE) == 0
			? B_KERNEL_EXECUTE_AREA : 0)
		| ((entry & X86_PAE_PTE_DIRTY) != 0 ? PAGE_MODIFIED : 0)
		| ((entry & X86_PAE_PTE_ACCESSED) != 0 ? PAGE_ACCESSED : 0)
		| ((entry & X86_PAE_PTE_PRESENT) != 0 ? PAGE_PRESENT : 0);

	TRACE("X86VMTranslationMapPAE::Query(%#" B_PRIxADDR ") -> %#"
		B_PRIxPHYSADDR ":\n", virtualAddress, *_physicalAddress);

	return B_OK;
}


status_t
X86VMTranslationMapPAE::QueryInterrupt(addr_t virtualAddress,
	phys_addr_t* _physicalAddress, uint32* _flags)
{
	// default the flags to not present
	*_flags = 0;
	*_physicalAddress = 0;

	// get the page directory entry
	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), virtualAddress);
	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
		// no pagetable here
		return B_OK;
	}

	// get the page table entry
	pae_page_table_entry* pageTable
		= (pae_page_table_entry*)X86PagingMethodPAE::Method()
			->PhysicalPageMapper()->InterruptGetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
	pae_page_table_entry entry
		= pageTable[virtualAddress / B_PAGE_SIZE % kPAEPageTableEntryCount];

	*_physicalAddress = entry & X86_PAE_PTE_ADDRESS_MASK;

	// translate the page state flags
	if ((entry & X86_PAE_PTE_USER) != 0) {
		*_flags |= ((entry & X86_PAE_PTE_WRITABLE) != 0 ? B_WRITE_AREA : 0)
			| B_READ_AREA
			| ((entry & X86_PAE_PTE_NOT_EXECUTABLE) == 0 ? B_EXECUTE_AREA : 0);
	}

	*_flags |= ((entry & X86_PAE_PTE_WRITABLE) != 0 ? B_KERNEL_WRITE_AREA : 0)
		| B_KERNEL_READ_AREA
		| ((entry & X86_PAE_PTE_NOT_EXECUTABLE) == 0
			? B_KERNEL_EXECUTE_AREA : 0)
		| ((entry & X86_PAE_PTE_DIRTY) != 0 ? PAGE_MODIFIED : 0)
		| ((entry & X86_PAE_PTE_ACCESSED) != 0 ? PAGE_ACCESSED : 0)
		| ((entry & X86_PAE_PTE_PRESENT) != 0 ? PAGE_PRESENT : 0);

	TRACE("X86VMTranslationMapPAE::Query(%#" B_PRIxADDR ") -> %#"
		B_PRIxPHYSADDR ":\n", virtualAddress, *_physicalAddress);

	return B_OK;
}


status_t
X86VMTranslationMapPAE::Protect(addr_t start, addr_t end, uint32 attributes,
	uint32 memoryType)
{
	start = ROUNDDOWN(start, B_PAGE_SIZE);
	if (start >= end)
		return B_OK;

	TRACE("X86VMTranslationMapPAE::Protect(): %#" B_PRIxADDR " - %#" B_PRIxADDR
		", attributes: %#" B_PRIx32 "\n", start, end, attributes);

	// compute protection/memory type flags
	uint64 newFlags
		= X86PagingMethodPAE::MemoryTypeToPageTableEntryFlags(memoryType);
	if ((attributes & B_USER_PROTECTION) != 0) {
		newFlags |= X86_PAE_PTE_USER;
		if ((attributes & B_WRITE_AREA) != 0)
			newFlags |= X86_PAE_PTE_WRITABLE;
		if ((attributes & B_EXECUTE_AREA) == 0
			&& x86_check_feature(IA32_FEATURE_AMD_EXT_NX, FEATURE_EXT_AMD)) {
			newFlags |= X86_PAE_PTE_NOT_EXECUTABLE;
		}
	} else if ((attributes & B_KERNEL_WRITE_AREA) != 0)
		newFlags |= X86_PAE_PTE_WRITABLE;

	do {
		pae_page_directory_entry* pageDirEntry
			= X86PagingMethodPAE::PageDirEntryForAddress(
				fPagingStructures->VirtualPageDirs(), start);
		if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
			// no page table here, move the start up to access the next page
			// table
			start = ROUNDUP(start + 1, kPAEPageTableRange);
			continue;
		}

		Thread* thread = thread_get_current_thread();
		ThreadCPUPinner pinner(thread);

		pae_page_table_entry* pageTable
			= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);

		uint32 index = start / B_PAGE_SIZE % kPAEPageTableEntryCount;
		for (; index < kPAEPageTableEntryCount && start < end;
				index++, start += B_PAGE_SIZE) {
			pae_page_table_entry entry = pageTable[index];
			if ((pageTable[index] & X86_PAE_PTE_PRESENT) == 0) {
				// page mapping not valid
				continue;
			}

			TRACE("X86VMTranslationMapPAE::Protect(): protect page %#"
				B_PRIxADDR "\n", start);

			// set the new protection flags -- we want to do that atomically,
			// without changing the accessed or dirty flag
			pae_page_table_entry oldEntry;
			while (true) {
				oldEntry = X86PagingMethodPAE::TestAndSetTableEntry(
					&pageTable[index],
					(entry & ~(X86_PAE_PTE_PROTECTION_MASK
							| X86_PAE_PTE_MEMORY_TYPE_MASK))
						| newFlags,
					entry);
				if (oldEntry == entry)
					break;
				entry = oldEntry;
			}

			T(Protect(this, start, entry,
				(entry & ~(X86_PAE_PTE_PROTECTION_MASK
						| X86_PAE_PTE_MEMORY_TYPE_MASK))
					| newFlags));

			if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0) {
				// Note, that we only need to invalidate the address, if the
				// accessed flag was set, since only then the entry could have been
				// in any TLB.
				InvalidatePage(start);
			}
		}
	} while (start != 0 && start < end);

	return B_OK;
}


status_t
X86VMTranslationMapPAE::ClearFlags(addr_t address, uint32 flags)
{
	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), address);
	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0) {
		// no pagetable here
		return B_OK;
	}

	uint64 flagsToClear = ((flags & PAGE_MODIFIED) ? X86_PAE_PTE_DIRTY : 0)
		| ((flags & PAGE_ACCESSED) ? X86_PAE_PTE_ACCESSED : 0);

	Thread* thread = thread_get_current_thread();
	ThreadCPUPinner pinner(thread);

	pae_page_table_entry* entry
		= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK)
			+ address / B_PAGE_SIZE % kPAEPageTableEntryCount;

	// clear out the flags we've been requested to clear
	pae_page_table_entry oldEntry
		= X86PagingMethodPAE::ClearTableEntryFlags(entry, flagsToClear);

	pinner.Unlock();

	T(ClearFlags(this, address, oldEntry, flagsToClear));

	if ((oldEntry & flagsToClear) != 0)
		InvalidatePage(address);

	return B_OK;
}


bool
X86VMTranslationMapPAE::ClearAccessedAndModified(VMArea* area, addr_t address,
	bool unmapIfUnaccessed, bool& _modified)
{
	ASSERT(address % B_PAGE_SIZE == 0);

	TRACE("X86VMTranslationMapPAE::ClearAccessedAndModified(%#" B_PRIxADDR
		")\n", address);

	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(
			fPagingStructures->VirtualPageDirs(), address);

	RecursiveLocker locker(fLock);

	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0)
		return false;

	ThreadCPUPinner pinner(thread_get_current_thread());

	pae_page_table_entry* entry
		= (pae_page_table_entry*)fPageMapper->GetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK)
			+ address / B_PAGE_SIZE % kPAEPageTableEntryCount;

	// perform the deed
	pae_page_table_entry oldEntry;

	if (unmapIfUnaccessed) {
		while (true) {
			oldEntry = *entry;
			if ((oldEntry & X86_PAE_PTE_PRESENT) == 0) {
				// page mapping not valid
				return false;
			}

			if (oldEntry & X86_PAE_PTE_ACCESSED) {
				// page was accessed -- just clear the flags
				oldEntry = X86PagingMethodPAE::ClearTableEntryFlags(entry,
					X86_PAE_PTE_ACCESSED | X86_PAE_PTE_DIRTY);
				T(ClearFlags(this, address, oldEntry,
					X86_PAE_PTE_ACCESSED | X86_PAE_PTE_DIRTY));
				break;
			}

			// page hasn't been accessed -- unmap it
			if (X86PagingMethodPAE::TestAndSetTableEntry(entry, 0, oldEntry)
					== oldEntry) {
				T(ClearFlagsUnmap(this, address, oldEntry));
				break;
			}

			// something changed -- check again
		}
	} else {
		oldEntry = X86PagingMethodPAE::ClearTableEntryFlags(entry,
			X86_PAE_PTE_ACCESSED | X86_PAE_PTE_DIRTY);
		T(ClearFlags(this, address, oldEntry,
			X86_PAE_PTE_ACCESSED | X86_PAE_PTE_DIRTY));
	}

	pinner.Unlock();

	_modified = (oldEntry & X86_PAE_PTE_DIRTY) != 0;

	if ((oldEntry & X86_PAE_PTE_ACCESSED) != 0) {
		// Note, that we only need to invalidate the address, if the
		// accessed flags was set, since only then the entry could have been
		// in any TLB.
		InvalidatePage(address);
		Flush();

		return true;
	}

	if (!unmapIfUnaccessed)
		return false;

	// We have unmapped the address. Do the "high level" stuff.

	fMapCount--;

	locker.Detach();
		// UnaccessedPageUnmapped() will unlock for us

	UnaccessedPageUnmapped(area,
		(oldEntry & X86_PAE_PTE_ADDRESS_MASK) / B_PAGE_SIZE);

	return false;
}


void
X86VMTranslationMapPAE::DebugPrintMappingInfo(addr_t virtualAddress)
{
	// get the page directory
	pae_page_directory_entry* const* pdpt
		= fPagingStructures->VirtualPageDirs();
	pae_page_directory_entry* pageDirectory = pdpt[virtualAddress >> 30];
	kprintf("page directory: %p (PDPT[%zu])\n", pageDirectory,
		virtualAddress >> 30);

	// get the page directory entry
	pae_page_directory_entry* pageDirEntry
		= X86PagingMethodPAE::PageDirEntryForAddress(pdpt, virtualAddress);
	kprintf("page directory entry %zu (%p): %#" B_PRIx64 "\n",
		pageDirEntry - pageDirectory, pageDirEntry, *pageDirEntry);

	kprintf("  access: ");
	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) != 0)
		kprintf(" present");
	if ((*pageDirEntry & X86_PAE_PDE_WRITABLE) != 0)
		kprintf(" writable");
	if ((*pageDirEntry & X86_PAE_PDE_USER) != 0)
		kprintf(" user");
	if ((*pageDirEntry & X86_PAE_PDE_NOT_EXECUTABLE) == 0)
		kprintf(" executable");
	if ((*pageDirEntry & X86_PAE_PDE_LARGE_PAGE) != 0)
		kprintf(" large");

	kprintf("\n  caching:");
	if ((*pageDirEntry & X86_PAE_PDE_WRITE_THROUGH) != 0)
		kprintf(" write-through");
	if ((*pageDirEntry & X86_PAE_PDE_CACHING_DISABLED) != 0)
		kprintf(" uncached");

	kprintf("\n  flags:  ");
	if ((*pageDirEntry & X86_PAE_PDE_ACCESSED) != 0)
		kprintf(" accessed");
	kprintf("\n");

	if ((*pageDirEntry & X86_PAE_PDE_PRESENT) == 0)
		return;

	// get the page table entry
	pae_page_table_entry* pageTable
		= (pae_page_table_entry*)X86PagingMethodPAE::Method()
			->PhysicalPageMapper()->InterruptGetPageTableAt(
				*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
	kprintf("page table: %#" B_PRIx64 "\n",
		*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
	size_t pteIndex = virtualAddress / B_PAGE_SIZE % kPAEPageTableEntryCount;
	pae_page_table_entry entry = pageTable[pteIndex];
	kprintf("page table entry %zu (phys: %#" B_PRIx64 "): %#" B_PRIx64 "\n",
		pteIndex,
		(*pageDirEntry & X86_PAE_PDE_ADDRESS_MASK)
			+ pteIndex * sizeof(pae_page_table_entry),
		entry);

	kprintf("  access: ");
	if ((entry & X86_PAE_PTE_PRESENT) != 0)
		kprintf(" present");
	if ((entry & X86_PAE_PTE_WRITABLE) != 0)
		kprintf(" writable");
	if ((entry & X86_PAE_PTE_USER) != 0)
		kprintf(" user");
	if ((entry & X86_PAE_PTE_NOT_EXECUTABLE) == 0)
		kprintf(" executable");
	if ((entry & X86_PAE_PTE_GLOBAL) == 0)
		kprintf(" global");

	kprintf("\n  caching:");
	if ((entry & X86_PAE_PTE_WRITE_THROUGH) != 0)
		kprintf(" write-through");
	if ((entry & X86_PAE_PTE_CACHING_DISABLED) != 0)
		kprintf(" uncached");
	if ((entry & X86_PAE_PTE_PAT) != 0)
		kprintf(" PAT");

	kprintf("\n  flags:  ");
	if ((entry & X86_PAE_PTE_ACCESSED) != 0)
		kprintf(" accessed");
	if ((entry & X86_PAE_PTE_DIRTY) != 0)
		kprintf(" dirty");
	kprintf("\n");

	if ((entry & X86_PAE_PTE_PRESENT) != 0) {
		kprintf("  address: %#" B_PRIx64 "\n",
			entry & X86_PAE_PTE_ADDRESS_MASK);
	}
}


bool
X86VMTranslationMapPAE::DebugGetReverseMappingInfo(phys_addr_t physicalAddress,
	ReverseMappingInfoCallback& callback)
{
	pae_page_directory_entry* const* pdpt
		= fPagingStructures->VirtualPageDirs();
	for (uint32 pageDirIndex = fIsKernelMap ? 2 : 0;
		pageDirIndex < uint32(fIsKernelMap ? 4 : 2); pageDirIndex++) {
		// iterate through the page directory
		pae_page_directory_entry* pageDirectory = pdpt[pageDirIndex];
		for (uint32 pdeIndex = 0; pdeIndex < kPAEPageDirEntryCount;
			pdeIndex++) {
			pae_page_directory_entry& pageDirEntry = pageDirectory[pdeIndex];
			if ((pageDirEntry & X86_PAE_PDE_ADDRESS_MASK) == 0)
				continue;

			// get and iterate through the page table
			pae_page_table_entry* pageTable
				= (pae_page_table_entry*)X86PagingMethodPAE::Method()
					->PhysicalPageMapper()->InterruptGetPageTableAt(
						pageDirEntry & X86_PAE_PDE_ADDRESS_MASK);
			for (uint32 pteIndex = 0; pteIndex < kPAEPageTableEntryCount;
				pteIndex++) {
				pae_page_table_entry entry = pageTable[pteIndex];
				if ((entry & X86_PAE_PTE_PRESENT) != 0
					&& (entry & X86_PAE_PTE_ADDRESS_MASK) == physicalAddress) {
					addr_t virtualAddress = pageDirIndex * kPAEPageDirRange
						+ pdeIndex * kPAEPageTableRange
						+ pteIndex * B_PAGE_SIZE;
					if (callback.HandleVirtualAddress(virtualAddress))
						return true;
				}
			}
		}
	}

	return false;
}


X86PagingStructures*
X86VMTranslationMapPAE::PagingStructures() const
{
	return fPagingStructures;
}


#endif	// B_HAIKU_PHYSICAL_BITS == 64